// SPDX-License-Identifier: GPL-2.0-or-later
/*
* PTP virtual clock driver
*
* Copyright 2021 NXP
*/
#include <linux/slab.h>
#include <linux/hashtable.h>
#include "ptp_private.h"
#define PTP_VCLOCK_CC_SHIFT 31
#define PTP_VCLOCK_CC_MULT (1 << PTP_VCLOCK_CC_SHIFT)
#define PTP_VCLOCK_FADJ_SHIFT 9
#define PTP_VCLOCK_FADJ_DENOMINATOR 15625ULL
#define PTP_VCLOCK_REFRESH_INTERVAL (HZ * 2)
/* protects vclock_hash addition/deletion */
static DEFINE_SPINLOCK(vclock_hash_lock);
static DEFINE_READ_MOSTLY_HASHTABLE(vclock_hash, 8);
static void ptp_vclock_hash_add(struct ptp_vclock *vclock)
{
spin_lock(&vclock_hash_lock);
hlist_add_head_rcu(&vclock->vclock_hash_node,
&vclock_hash[vclock->clock->index % HASH_SIZE(vclock_hash)]);
spin_unlock(&vclock_hash_lock);
}
static void ptp_vclock_hash_del(struct ptp_vclock *vclock)
{
spin_lock(&vclock_hash_lock);
hlist_del_init_rcu(&vclock->vclock_hash_node);
spin_unlock(&vclock_hash_lock);
synchronize_rcu();
}
static int ptp_vclock_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct ptp_vclock *vclock = info_to_vclock(ptp);
s64 adj;
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;
mutex_unlock(&vclock->lock);
return 0;
}
static int ptp_vclock_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct ptp_vclock *vclock = info_to_vclock(ptp);
if (mutex_lock_interruptible(&vclock->lock))
return -EINTR;
timecounter_adjtime(&vclock->tc, delta);
mutex_unlock(&vclock->lock);
return 0;
}
static int ptp_vclock_gettime(struct ptp_clock_info *ptp,
struct timespec64 *ts)
{
struct ptp_vclock *vclock = info_to_vclock(ptp);
u64 ns;
if (mutex_lock_interruptible(&vclock->lock))
return -EINTR;
ns = timecounter_read(&vclock->tc);
mutex_unlock(&vclock->lock);
*ts = ns_to_timespec64(ns);
return 0;
}
static int ptp_vclock_gettimex(struct ptp_clock_info *ptp,
struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct ptp_vclock *vclock = info_to_vclock(ptp);
struct ptp_clock *pptp = vclock->pclock;
struct timespec64 pts;
int err;
u64 ns;
err = pptp->info->getcyclesx64(pptp->info, &pts, sts);
if (err)
return err;
if (mutex_lock_interruptible(&vclock->lock))
return -EINTR;
ns = timecounter_cyc2time(&vclock->tc, timespec64_to_ns(&pts));
mutex_unlock(&vclock->lock);
*ts = ns_to_timespec64(ns);
return 0;
}
static int ptp_vclock_settime(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct ptp_vclock *vclock = info_to_vclock(ptp);
u64 ns = timespec64_to_ns(ts);
if (mutex_lock_interruptible(&vclock->lock))
return -EINTR;
timecounter_init(&vclock->tc, &vclock->cc, ns);
mutex_unlock(&vclock->lock);
return 0;
}
static int ptp_vclock_getcrosststamp(struct ptp_clock_info *ptp,
struct system_device_crosststamp *xtstamp)
{
struct ptp_vclock *vclock = info_to_vclock(ptp);
struct ptp_clock *pptp = vclock->pclock;
int err;
u64 ns;
err = pptp->info->getcrosscycles(pptp->info, xtstamp);
if (err)
return err;
if (mutex_lock_interruptible(&vclock->lock))
return -EINTR;
ns = timecounter_cyc2time(&vclock->tc, ktime_to_ns(xtstamp->device));
mutex_unlock(&vclock->lock);
xtstamp->device = ns_to_ktime(ns);
return 0;
}
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);
return PTP_VCLOCK_REFRESH_INTERVAL;
}
static const struct ptp_clock_info ptp_vclock_info = {
.owner = THIS_MODULE,
.name = "ptp virtual clock",
.max_adj = 500000000,
.adjfine = ptp_vclock_adjfine,
.adjtime = ptp_vclock_adjtime,
.settime64 = ptp_vclock_settime,
.do_aux_work = ptp_vclock_refresh,
};
static u64 ptp_vclock_read(const 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);
}
static const struct cyclecounter ptp_vclock_cc = {
.read = ptp_vclock_read,
.mask = CYCLECOUNTER_MASK(32),
.mult = PTP_VCLOCK_CC_MULT,
.shift = PTP_VCLOCK_CC_SHIFT,
};
struct ptp_vclock *ptp_vclock_register(struct ptp_clock *pclock)
{
struct ptp_vclock *vclock;
vclock = kzalloc(sizeof(*vclock), GFP_KERNEL);
if (!vclock)
return NULL;
vclock->pclock = pclock;
vclock->info = ptp_vclock_info;
if (pclock->info->getcyclesx64)
vclock->info.gettimex64 = ptp_vclock_gettimex;
else
vclock->info.gettime64 = ptp_vclock_gettime;
if (pclock->info->getcrosscycles)
vclock->info.getcrosststamp = ptp_vclock_getcrosststamp;
vclock->cc = ptp_vclock_cc;
snprintf(vclock->info.name, PTP_CLOCK_NAME_LEN, "ptp%d_virt",
pclock->index);
INIT_HLIST_NODE(&vclock->vclock_hash_node);
mutex_init(&vclock->lock);
vclock->clock = ptp_clock_register(&vclock->info, &pclock->dev);
if (IS_ERR_OR_NULL(vclock->clock)) {
kfree(vclock);
return NULL;
}
timecounter_init(&vclock->tc, &vclock->cc, 0);
ptp_schedule_worker(vclock->clock, PTP_VCLOCK_REFRESH_INTERVAL);
ptp_vclock_hash_add(vclock);
return vclock;
}
void ptp_vclock_unregister(struct ptp_vclock *vclock)
{
ptp_vclock_hash_del(vclock);
ptp_clock_unregister(vclock->clock);
kfree(vclock);
}
#if IS_BUILTIN(CONFIG_PTP_1588_CLOCK)
int ptp_get_vclocks_index(int pclock_index, int **vclock_index)
{
char name[PTP_CLOCK_NAME_LEN] = "";
struct ptp_clock *ptp;
struct device *dev;
int num = 0;
if (pclock_index < 0)
return num;
snprintf(name, PTP_CLOCK_NAME_LEN, "ptp%d", pclock_index);
dev = class_find_device_by_name(ptp_class, name);
if (!dev)
return num;
ptp = dev_get_drvdata(dev);
if (mutex_lock_interruptible(&ptp->n_vclocks_mux)) {
put_device(dev);
return num;
}
*vclock_index = kzalloc(sizeof(int) * ptp->n_vclocks, GFP_KERNEL);
if (!(*vclock_index))
goto out;
memcpy(*vclock_index, ptp->vclock_index, sizeof(int) * ptp->n_vclocks);
num = ptp->n_vclocks;
out:
mutex_unlock(&ptp->n_vclocks_mux);
put_device(dev);
return num;
}
EXPORT_SYMBOL(ptp_get_vclocks_index);
ktime_t ptp_convert_timestamp(const ktime_t *hwtstamp, int vclock_index)
{
unsigned int hash = vclock_index % HASH_SIZE(vclock_hash);
struct ptp_vclock *vclock;
u64 ns;
u64 vclock_ns = 0;
ns = ktime_to_ns(*hwtstamp);
rcu_read_lock();
hlist_for_each_entry_rcu(vclock, &vclock_hash[hash], vclock_hash_node) {
if (vclock->clock->index != vclock_index)
continue;
if (mutex_lock_interruptible(&vclock->lock))
break;
vclock_ns = timecounter_cyc2time(&vclock->tc, ns);
mutex_unlock(&vclock->lock);
break;
}
rcu_read_unlock();
return ns_to_ktime(vclock_ns);
}
EXPORT_SYMBOL(ptp_convert_timestamp);
#endif