ptp: support ptp physical/virtual clocks conversion
Support ptp physical/virtual clocks conversion via sysfs. There will be a new attribute n_vclocks under ptp physical clock sysfs. - In default, the value is 0 meaning only ptp physical clock is in use. - Setting the value can create corresponding number of ptp virtual clocks to use. But current physical clock is guaranteed to stay free running. - Setting the value back to 0 can delete virtual clocks and back use physical clock again. Another new attribute max_vclocks control the maximum number of ptp vclocks. Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
5d43f951b1
commit
73f37068d5
@ -33,6 +33,13 @@ Description:
|
||||
frequency adjustment value (a positive integer) in
|
||||
parts per billion.
|
||||
|
||||
What: /sys/class/ptp/ptpN/max_vclocks
|
||||
Date: May 2021
|
||||
Contact: Yangbo Lu <yangbo.lu@nxp.com>
|
||||
Description:
|
||||
This file contains the maximum number of ptp vclocks.
|
||||
Write integer to re-configure it.
|
||||
|
||||
What: /sys/class/ptp/ptpN/n_alarms
|
||||
Date: September 2010
|
||||
Contact: Richard Cochran <richardcochran@gmail.com>
|
||||
@ -61,6 +68,19 @@ Description:
|
||||
This file contains the number of programmable pins
|
||||
offered by the PTP hardware clock.
|
||||
|
||||
What: /sys/class/ptp/ptpN/n_vclocks
|
||||
Date: May 2021
|
||||
Contact: Yangbo Lu <yangbo.lu@nxp.com>
|
||||
Description:
|
||||
This file contains the number of virtual PTP clocks in
|
||||
use. By default, the value is 0 meaning that only the
|
||||
physical clock is in use. Setting the value creates
|
||||
the corresponding number of virtual clocks and causes
|
||||
the physical clock to become free running. Setting the
|
||||
value back to 0 deletes the virtual clocks and
|
||||
switches the physical clock back to normal, adjustable
|
||||
operation.
|
||||
|
||||
What: /sys/class/ptp/ptpN/pins
|
||||
Date: March 2014
|
||||
Contact: Richard Cochran <richardcochran@gmail.com>
|
||||
|
@ -76,6 +76,11 @@ static int ptp_clock_settime(struct posix_clock *pc, const struct timespec64 *tp
|
||||
{
|
||||
struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
|
||||
|
||||
if (ptp_vclock_in_use(ptp)) {
|
||||
pr_err("ptp: virtual clock in use\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
return ptp->info->settime64(ptp->info, tp);
|
||||
}
|
||||
|
||||
@ -97,6 +102,11 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
|
||||
struct ptp_clock_info *ops;
|
||||
int err = -EOPNOTSUPP;
|
||||
|
||||
if (ptp_vclock_in_use(ptp)) {
|
||||
pr_err("ptp: virtual clock in use\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ops = ptp->info;
|
||||
|
||||
if (tx->modes & ADJ_SETOFFSET) {
|
||||
@ -161,6 +171,7 @@ static void ptp_clock_release(struct device *dev)
|
||||
ptp_cleanup_pin_groups(ptp);
|
||||
mutex_destroy(&ptp->tsevq_mux);
|
||||
mutex_destroy(&ptp->pincfg_mux);
|
||||
mutex_destroy(&ptp->n_vclocks_mux);
|
||||
ida_simple_remove(&ptp_clocks_map, ptp->index);
|
||||
kfree(ptp);
|
||||
}
|
||||
@ -208,6 +219,7 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
|
||||
spin_lock_init(&ptp->tsevq.lock);
|
||||
mutex_init(&ptp->tsevq_mux);
|
||||
mutex_init(&ptp->pincfg_mux);
|
||||
mutex_init(&ptp->n_vclocks_mux);
|
||||
init_waitqueue_head(&ptp->tsev_wq);
|
||||
|
||||
if (ptp->info->do_aux_work) {
|
||||
@ -221,6 +233,14 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
|
||||
ptp->pps_source->lookup_cookie = ptp;
|
||||
}
|
||||
|
||||
/* PTP virtual clock is being registered under physical clock */
|
||||
if (parent->class && parent->class->name &&
|
||||
strcmp(parent->class->name, "ptp") == 0)
|
||||
ptp->is_virtual_clock = true;
|
||||
|
||||
if (!ptp->is_virtual_clock)
|
||||
ptp->max_vclocks = PTP_DEFAULT_MAX_VCLOCKS;
|
||||
|
||||
err = ptp_populate_pin_groups(ptp);
|
||||
if (err)
|
||||
goto no_pin_groups;
|
||||
@ -270,6 +290,7 @@ no_pin_groups:
|
||||
kworker_err:
|
||||
mutex_destroy(&ptp->tsevq_mux);
|
||||
mutex_destroy(&ptp->pincfg_mux);
|
||||
mutex_destroy(&ptp->n_vclocks_mux);
|
||||
ida_simple_remove(&ptp_clocks_map, index);
|
||||
no_slot:
|
||||
kfree(ptp);
|
||||
@ -280,6 +301,11 @@ EXPORT_SYMBOL(ptp_clock_register);
|
||||
|
||||
int ptp_clock_unregister(struct ptp_clock *ptp)
|
||||
{
|
||||
if (ptp_vclock_in_use(ptp)) {
|
||||
pr_err("ptp: virtual clock in use\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ptp->defunct = 1;
|
||||
wake_up_interruptible(&ptp->tsev_wq);
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#define PTP_MAX_TIMESTAMPS 128
|
||||
#define PTP_BUF_TIMESTAMPS 30
|
||||
#define PTP_DEFAULT_MAX_VCLOCKS 20
|
||||
|
||||
struct timestamp_event_queue {
|
||||
struct ptp_extts_event buf[PTP_MAX_TIMESTAMPS];
|
||||
@ -46,6 +47,10 @@ struct ptp_clock {
|
||||
const struct attribute_group *pin_attr_groups[2];
|
||||
struct kthread_worker *kworker;
|
||||
struct kthread_delayed_work aux_work;
|
||||
unsigned int max_vclocks;
|
||||
unsigned int n_vclocks;
|
||||
struct mutex n_vclocks_mux; /* protect concurrent n_vclocks access */
|
||||
bool is_virtual_clock;
|
||||
};
|
||||
|
||||
#define info_to_vclock(d) container_of((d), struct ptp_vclock, info)
|
||||
@ -74,6 +79,22 @@ static inline int queue_cnt(struct timestamp_event_queue *q)
|
||||
return cnt < 0 ? PTP_MAX_TIMESTAMPS + cnt : cnt;
|
||||
}
|
||||
|
||||
/* Check if ptp virtual clock is in use */
|
||||
static inline bool ptp_vclock_in_use(struct ptp_clock *ptp)
|
||||
{
|
||||
bool in_use = false;
|
||||
|
||||
if (mutex_lock_interruptible(&ptp->n_vclocks_mux))
|
||||
return true;
|
||||
|
||||
if (!ptp->is_virtual_clock && ptp->n_vclocks)
|
||||
in_use = true;
|
||||
|
||||
mutex_unlock(&ptp->n_vclocks_mux);
|
||||
|
||||
return in_use;
|
||||
}
|
||||
|
||||
/*
|
||||
* see ptp_chardev.c
|
||||
*/
|
||||
|
@ -3,6 +3,7 @@
|
||||
* PTP 1588 clock support - sysfs interface.
|
||||
*
|
||||
* Copyright (C) 2010 OMICRON electronics GmbH
|
||||
* Copyright 2021 NXP
|
||||
*/
|
||||
#include <linux/capability.h>
|
||||
#include <linux/slab.h>
|
||||
@ -148,6 +149,137 @@ out:
|
||||
}
|
||||
static DEVICE_ATTR(pps_enable, 0220, NULL, pps_enable_store);
|
||||
|
||||
static int unregister_vclock(struct device *dev, void *data)
|
||||
{
|
||||
struct ptp_clock *ptp = dev_get_drvdata(dev);
|
||||
struct ptp_clock_info *info = ptp->info;
|
||||
struct ptp_vclock *vclock;
|
||||
u8 *num = data;
|
||||
|
||||
vclock = info_to_vclock(info);
|
||||
dev_info(dev->parent, "delete virtual clock ptp%d\n",
|
||||
vclock->clock->index);
|
||||
|
||||
ptp_vclock_unregister(vclock);
|
||||
(*num)--;
|
||||
|
||||
/* For break. Not error. */
|
||||
if (*num == 0)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t n_vclocks_show(struct device *dev,
|
||||
struct device_attribute *attr, char *page)
|
||||
{
|
||||
struct ptp_clock *ptp = dev_get_drvdata(dev);
|
||||
ssize_t size;
|
||||
|
||||
if (mutex_lock_interruptible(&ptp->n_vclocks_mux))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
size = snprintf(page, PAGE_SIZE - 1, "%d\n", ptp->n_vclocks);
|
||||
|
||||
mutex_unlock(&ptp->n_vclocks_mux);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t n_vclocks_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct ptp_clock *ptp = dev_get_drvdata(dev);
|
||||
struct ptp_vclock *vclock;
|
||||
int err = -EINVAL;
|
||||
u32 num, i;
|
||||
|
||||
if (kstrtou32(buf, 0, &num))
|
||||
return err;
|
||||
|
||||
if (mutex_lock_interruptible(&ptp->n_vclocks_mux))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
if (num > ptp->max_vclocks) {
|
||||
dev_err(dev, "max value is %d\n", ptp->max_vclocks);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Need to create more vclocks */
|
||||
if (num > ptp->n_vclocks) {
|
||||
for (i = 0; i < num - ptp->n_vclocks; i++) {
|
||||
vclock = ptp_vclock_register(ptp);
|
||||
if (!vclock)
|
||||
goto out;
|
||||
|
||||
dev_info(dev, "new virtual clock ptp%d\n",
|
||||
vclock->clock->index);
|
||||
}
|
||||
}
|
||||
|
||||
/* Need to delete vclocks */
|
||||
if (num < ptp->n_vclocks) {
|
||||
i = ptp->n_vclocks - num;
|
||||
device_for_each_child_reverse(dev, &i,
|
||||
unregister_vclock);
|
||||
}
|
||||
|
||||
if (num == 0)
|
||||
dev_info(dev, "only physical clock in use now\n");
|
||||
else
|
||||
dev_info(dev, "guarantee physical clock free running\n");
|
||||
|
||||
ptp->n_vclocks = num;
|
||||
mutex_unlock(&ptp->n_vclocks_mux);
|
||||
|
||||
return count;
|
||||
out:
|
||||
mutex_unlock(&ptp->n_vclocks_mux);
|
||||
return err;
|
||||
}
|
||||
static DEVICE_ATTR_RW(n_vclocks);
|
||||
|
||||
static ssize_t max_vclocks_show(struct device *dev,
|
||||
struct device_attribute *attr, char *page)
|
||||
{
|
||||
struct ptp_clock *ptp = dev_get_drvdata(dev);
|
||||
ssize_t size;
|
||||
|
||||
size = snprintf(page, PAGE_SIZE - 1, "%d\n", ptp->max_vclocks);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t max_vclocks_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct ptp_clock *ptp = dev_get_drvdata(dev);
|
||||
u32 max;
|
||||
|
||||
if (kstrtou32(buf, 0, &max) || max == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (max == ptp->max_vclocks)
|
||||
return count;
|
||||
|
||||
if (mutex_lock_interruptible(&ptp->n_vclocks_mux))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
if (max < ptp->n_vclocks) {
|
||||
mutex_unlock(&ptp->n_vclocks_mux);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ptp->max_vclocks = max;
|
||||
|
||||
mutex_unlock(&ptp->n_vclocks_mux);
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(max_vclocks);
|
||||
|
||||
static struct attribute *ptp_attrs[] = {
|
||||
&dev_attr_clock_name.attr,
|
||||
|
||||
@ -162,6 +294,8 @@ static struct attribute *ptp_attrs[] = {
|
||||
&dev_attr_fifo.attr,
|
||||
&dev_attr_period.attr,
|
||||
&dev_attr_pps_enable.attr,
|
||||
&dev_attr_n_vclocks.attr,
|
||||
&dev_attr_max_vclocks.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
@ -183,6 +317,10 @@ static umode_t ptp_is_attribute_visible(struct kobject *kobj,
|
||||
} else if (attr == &dev_attr_pps_enable.attr) {
|
||||
if (!info->pps)
|
||||
mode = 0;
|
||||
} else if (attr == &dev_attr_n_vclocks.attr ||
|
||||
attr == &dev_attr_max_vclocks.attr) {
|
||||
if (ptp->is_virtual_clock)
|
||||
mode = 0;
|
||||
}
|
||||
|
||||
return mode;
|
||||
|
Loading…
Reference in New Issue
Block a user