diff options
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/ptp/ptp_private.h | 6 | ||||
| -rw-r--r-- | drivers/ptp/ptp_sysfs.c | 4 | ||||
| -rw-r--r-- | drivers/ptp/ptp_vclock.c | 89 |
3 files changed, 76 insertions, 23 deletions
diff --git a/drivers/ptp/ptp_private.h b/drivers/ptp/ptp_private.h index ec8633126d6b..99e7eb241336 100644 --- a/drivers/ptp/ptp_private.h +++ b/drivers/ptp/ptp_private.h @@ -81,7 +81,11 @@ struct ptp_vclock { struct hlist_node vclock_hash_node; struct cyclecounter cc; struct timecounter tc; - struct mutex lock; /* protects tc/cc */ + u64 cycles; + struct mutex lock; /* protects tc/cc and the cached sample */ + /* Owned by the auxiliary worker. */ + unsigned long retry_delay; + unsigned long refresh_deadline; }; /* diff --git a/drivers/ptp/ptp_sysfs.c b/drivers/ptp/ptp_sysfs.c index 2d46f69e8c10..8e0e0aa09981 100644 --- a/drivers/ptp/ptp_sysfs.c +++ b/drivers/ptp/ptp_sysfs.c @@ -228,8 +228,10 @@ static ssize_t n_vclocks_store(struct device *dev, if (num > ptp->n_vclocks) { for (i = 0; i < num - ptp->n_vclocks; i++) { vclock = ptp_vclock_register(ptp); - if (!vclock) + if (IS_ERR(vclock)) { + err = PTR_ERR(vclock); goto err_register; + } *(ptp->vclock_index + ptp->n_vclocks + i) = vclock->clock->index; diff --git a/drivers/ptp/ptp_vclock.c b/drivers/ptp/ptp_vclock.c index 84cb527f59cc..b97f3a877260 100644 --- a/drivers/ptp/ptp_vclock.c +++ b/drivers/ptp/ptp_vclock.c @@ -4,6 +4,7 @@ * * Copyright 2021 NXP */ +#include <linux/jiffies.h> #include <linux/slab.h> #include <linux/hashtable.h> #include "ptp_private.h" @@ -13,6 +14,7 @@ #define PTP_VCLOCK_FADJ_SHIFT 9 #define PTP_VCLOCK_FADJ_DENOMINATOR 15625ULL #define PTP_VCLOCK_REFRESH_INTERVAL (HZ * 2) +#define PTP_VCLOCK_RETRY_INTERVAL msecs_to_jiffies(10) /* protects vclock_hash addition/deletion */ static DEFINE_SPINLOCK(vclock_hash_lock); @@ -42,21 +44,39 @@ static void ptp_vclock_hash_del(struct ptp_vclock *vclock) synchronize_srcu(&vclock_srcu); } +/* The caller holds vclock->lock, or has not published the clock yet. */ +static int ptp_vclock_sample(struct ptp_vclock *vclock) +{ + struct ptp_clock *ptp = vclock->pclock; + struct timespec64 ts; + int err; + + err = ptp->info->getcycles64(ptp->info, &ts); + if (!err) + vclock->cycles = timespec64_to_ns(&ts); + + return err; +} + static int ptp_vclock_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) { struct ptp_vclock *vclock = info_to_vclock(ptp); s64 adj; + int err; adj = (s64)scaled_ppm << PTP_VCLOCK_FADJ_SHIFT; adj = div_s64(adj, PTP_VCLOCK_FADJ_DENOMINATOR); if (mutex_lock_interruptible(&vclock->lock)) return -EINTR; - timecounter_read(&vclock->tc); - vclock->cc.mult = PTP_VCLOCK_CC_MULT + adj; + err = ptp_vclock_sample(vclock); + if (!err) { + timecounter_read(&vclock->tc); + vclock->cc.mult = PTP_VCLOCK_CC_MULT + adj; + } mutex_unlock(&vclock->lock); - return 0; + return err; } static int ptp_vclock_adjtime(struct ptp_clock_info *ptp, s64 delta) @@ -75,15 +95,16 @@ static int ptp_vclock_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) { struct ptp_vclock *vclock = info_to_vclock(ptp); - u64 ns; + int err; if (mutex_lock_interruptible(&vclock->lock)) return -EINTR; - ns = timecounter_read(&vclock->tc); + err = ptp_vclock_sample(vclock); + if (!err) + *ts = ns_to_timespec64(timecounter_read(&vclock->tc)); mutex_unlock(&vclock->lock); - *ts = ns_to_timespec64(ns); - return 0; + return err; } static int ptp_vclock_gettimex(struct ptp_clock_info *ptp, @@ -115,13 +136,16 @@ static int ptp_vclock_settime(struct ptp_clock_info *ptp, { struct ptp_vclock *vclock = info_to_vclock(ptp); u64 ns = timespec64_to_ns(ts); + int err; if (mutex_lock_interruptible(&vclock->lock)) return -EINTR; - timecounter_init(&vclock->tc, &vclock->cc, ns); + err = ptp_vclock_sample(vclock); + if (!err) + timecounter_init(&vclock->tc, &vclock->cc, ns); mutex_unlock(&vclock->lock); - return 0; + return err; } static int ptp_vclock_getcrosststamp(struct ptp_clock_info *ptp, @@ -151,8 +175,24 @@ static long ptp_vclock_refresh(struct ptp_clock_info *ptp) struct ptp_vclock *vclock = info_to_vclock(ptp); struct timespec64 ts; - ptp_vclock_gettime(&vclock->info, &ts); + /* Retry transient failures promptly, but back off during long outages. */ + if (ptp_vclock_gettime(&vclock->info, &ts)) { + unsigned long now = jiffies; + + /* Retries must not postpone the next regular refresh attempt. */ + if (!vclock->retry_delay || time_after_eq(now, vclock->refresh_deadline)) + vclock->refresh_deadline = now + PTP_VCLOCK_REFRESH_INTERVAL; + + if (!vclock->retry_delay) + vclock->retry_delay = PTP_VCLOCK_RETRY_INTERVAL; + else + vclock->retry_delay = min_t(unsigned long, + vclock->retry_delay * 2, + PTP_VCLOCK_REFRESH_INTERVAL); + return min(vclock->retry_delay, vclock->refresh_deadline - now); + } + vclock->retry_delay = 0; return PTP_VCLOCK_REFRESH_INTERVAL; } @@ -174,12 +214,9 @@ static const struct ptp_clock_info ptp_vclock_info = { static u64 ptp_vclock_read(struct cyclecounter *cc) { struct ptp_vclock *vclock = cc_to_vclock(cc); - struct ptp_clock *ptp = vclock->pclock; - struct timespec64 ts = {}; - - ptp->info->getcycles64(ptp->info, &ts); - return timespec64_to_ns(&ts); + /* The cyclecounter callback cannot report a failed PHC read. */ + return vclock->cycles; } static const struct cyclecounter ptp_vclock_cc = { @@ -192,10 +229,11 @@ static const struct cyclecounter ptp_vclock_cc = { struct ptp_vclock *ptp_vclock_register(struct ptp_clock *pclock) { struct ptp_vclock *vclock; + int err; vclock = kzalloc_obj(*vclock); if (!vclock) - return NULL; + return ERR_PTR(-ENOMEM); vclock->pclock = pclock; vclock->info = ptp_vclock_info; @@ -214,20 +252,29 @@ struct ptp_vclock *ptp_vclock_register(struct ptp_clock *pclock) mutex_init(&vclock->lock); + err = ptp_vclock_sample(vclock); + if (err) + goto free_vclock; + timecounter_init(&vclock->tc, &vclock->cc, 0); + vclock->clock = ptp_clock_register(&vclock->info, &pclock->dev); - if (IS_ERR_OR_NULL(vclock->clock)) { - kfree(vclock); - return NULL; + if (IS_ERR(vclock->clock)) { + err = PTR_ERR(vclock->clock); + goto free_vclock; } ptp_vclock_set_subclass(vclock->clock); - timecounter_init(&vclock->tc, &vclock->cc, 0); - ptp_schedule_worker(vclock->clock, PTP_VCLOCK_REFRESH_INTERVAL); + /* The initial sample predates registration; refresh without more delay. */ + ptp_schedule_worker(vclock->clock, 0); ptp_vclock_hash_add(vclock); return vclock; + +free_vclock: + kfree(vclock); + return ERR_PTR(err); } void ptp_vclock_unregister(struct ptp_vclock *vclock) |
