summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/ptp/ptp_private.h6
-rw-r--r--drivers/ptp/ptp_sysfs.c4
-rw-r--r--drivers/ptp/ptp_vclock.c89
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)