mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
348 lines
8.6 KiB
C
348 lines
8.6 KiB
C
![]() |
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
/*
|
||
|
* Expose virtio_rtc clocks as PTP clocks.
|
||
|
*
|
||
|
* Copyright (C) 2022-2023 OpenSynergy GmbH
|
||
|
* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
||
|
*
|
||
|
* Derived from ptp_kvm_common.c, virtual PTP 1588 clock for use with KVM
|
||
|
* guests.
|
||
|
*
|
||
|
* Copyright (C) 2017 Red Hat Inc.
|
||
|
*/
|
||
|
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/ptp_clock_kernel.h>
|
||
|
|
||
|
#include <uapi/linux/virtio_rtc.h>
|
||
|
|
||
|
#include "virtio_rtc_internal.h"
|
||
|
|
||
|
/**
|
||
|
* struct viortc_ptp_clock - PTP clock abstraction
|
||
|
* @ptp_clock: PTP clock handle for unregistering
|
||
|
* @viortc: virtio_rtc device data
|
||
|
* @ptp_info: PTP clock description
|
||
|
* @vio_clk_id: virtio_rtc clock id
|
||
|
* @have_cross: device supports crosststamp with available HW counter
|
||
|
*/
|
||
|
struct viortc_ptp_clock {
|
||
|
struct ptp_clock *ptp_clock;
|
||
|
struct viortc_dev *viortc;
|
||
|
struct ptp_clock_info ptp_info;
|
||
|
u16 vio_clk_id;
|
||
|
bool have_cross;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* struct viortc_ptp_cross_ctx - context for get_device_system_crosststamp()
|
||
|
* @device_time: device clock reading
|
||
|
* @system_counterval: HW counter value at device_time
|
||
|
*
|
||
|
* Provides the already obtained crosststamp to get_device_system_crosststamp().
|
||
|
*/
|
||
|
struct viortc_ptp_cross_ctx {
|
||
|
ktime_t device_time;
|
||
|
struct system_counterval_t system_counterval;
|
||
|
};
|
||
|
|
||
|
/* Weak function in case get_device_system_crosststamp() is not supported */
|
||
|
int __weak viortc_hw_xtstamp_params(u8 *hw_counter, enum clocksource_ids *cs_id)
|
||
|
{
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* viortc_ptp_get_time_fn() - callback for get_device_system_crosststamp()
|
||
|
* @device_time: device clock reading
|
||
|
* @system_counterval: HW counter value at device_time
|
||
|
* @ctx: context with already obtained crosststamp
|
||
|
*
|
||
|
* Return: zero (success).
|
||
|
*/
|
||
|
static int viortc_ptp_get_time_fn(ktime_t *device_time,
|
||
|
struct system_counterval_t *system_counterval,
|
||
|
void *ctx)
|
||
|
{
|
||
|
struct viortc_ptp_cross_ctx *vio_ctx = ctx;
|
||
|
|
||
|
*device_time = vio_ctx->device_time;
|
||
|
*system_counterval = vio_ctx->system_counterval;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* viortc_ptp_do_xtstamp() - get crosststamp from device
|
||
|
* @vio_ptp: virtio_rtc PTP clock
|
||
|
* @hw_counter: virtio_rtc HW counter type
|
||
|
* @cs_id: clocksource id corresponding to hw_counter
|
||
|
* @ctx: context for get_device_system_crosststamp()
|
||
|
*
|
||
|
* Reads HW-specific crosststamp from device.
|
||
|
*
|
||
|
* Context: Process context.
|
||
|
* Return: Zero on success, negative error code otherwise.
|
||
|
*/
|
||
|
static int viortc_ptp_do_xtstamp(struct viortc_ptp_clock *vio_ptp,
|
||
|
u8 hw_counter, enum clocksource_ids cs_id,
|
||
|
struct viortc_ptp_cross_ctx *ctx)
|
||
|
{
|
||
|
u64 max_ns, ns;
|
||
|
int ret;
|
||
|
|
||
|
ctx->system_counterval.cs_id = cs_id;
|
||
|
|
||
|
ret = viortc_read_cross(vio_ptp->viortc, vio_ptp->vio_clk_id,
|
||
|
hw_counter, &ns,
|
||
|
&ctx->system_counterval.cycles);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
max_ns = (u64)ktime_to_ns(KTIME_MAX);
|
||
|
if (ns > max_ns)
|
||
|
return -EINVAL;
|
||
|
|
||
|
ctx->device_time = ns_to_ktime(ns);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* PTP clock operations
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* viortc_ptp_getcrosststamp() - PTP clock getcrosststamp op
|
||
|
* @ptp: PTP clock info
|
||
|
* @xtstamp: crosststamp
|
||
|
*
|
||
|
* Context: Process context.
|
||
|
* Return: Zero on success, negative error code otherwise.
|
||
|
*/
|
||
|
static int viortc_ptp_getcrosststamp(struct ptp_clock_info *ptp,
|
||
|
struct system_device_crosststamp *xtstamp)
|
||
|
{
|
||
|
struct viortc_ptp_clock *vio_ptp =
|
||
|
container_of(ptp, struct viortc_ptp_clock, ptp_info);
|
||
|
struct system_time_snapshot history_begin;
|
||
|
struct viortc_ptp_cross_ctx ctx;
|
||
|
enum clocksource_ids cs_id;
|
||
|
u8 hw_counter;
|
||
|
int ret;
|
||
|
|
||
|
if (!vio_ptp->have_cross)
|
||
|
return -EOPNOTSUPP;
|
||
|
|
||
|
ret = viortc_hw_xtstamp_params(&hw_counter, &cs_id);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ktime_get_snapshot(&history_begin);
|
||
|
if (history_begin.cs_id != cs_id)
|
||
|
return -EOPNOTSUPP;
|
||
|
|
||
|
/*
|
||
|
* Getting the timestamp can take many milliseconds with a slow Virtio
|
||
|
* device. This is too long for viortc_ptp_get_time_fn() passed to
|
||
|
* get_device_system_crosststamp(), which has to usually return before
|
||
|
* the timekeeper seqcount increases (every tick or so).
|
||
|
*
|
||
|
* So, get the actual cross-timestamp first.
|
||
|
*/
|
||
|
ret = viortc_ptp_do_xtstamp(vio_ptp, hw_counter, cs_id, &ctx);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = get_device_system_crosststamp(viortc_ptp_get_time_fn, &ctx,
|
||
|
&history_begin, xtstamp);
|
||
|
if (ret)
|
||
|
pr_debug("%s: get_device_system_crosststamp() returned %d\n",
|
||
|
__func__, ret);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* viortc_ptp_adjfine() - unsupported PTP clock adjfine op */
|
||
|
static int viortc_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
|
||
|
{
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
/* viortc_ptp_adjtime() - unsupported PTP clock adjtime op */
|
||
|
static int viortc_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
|
||
|
{
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
/* viortc_ptp_settime64() - unsupported PTP clock settime64 op */
|
||
|
static int viortc_ptp_settime64(struct ptp_clock_info *ptp,
|
||
|
const struct timespec64 *ts)
|
||
|
{
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* viortc_ptp_gettimex64() - PTP clock gettimex64 op
|
||
|
*
|
||
|
* Context: Process context.
|
||
|
*/
|
||
|
static int viortc_ptp_gettimex64(struct ptp_clock_info *ptp,
|
||
|
struct timespec64 *ts,
|
||
|
struct ptp_system_timestamp *sts)
|
||
|
{
|
||
|
struct viortc_ptp_clock *vio_ptp =
|
||
|
container_of(ptp, struct viortc_ptp_clock, ptp_info);
|
||
|
int ret;
|
||
|
u64 ns;
|
||
|
|
||
|
ptp_read_system_prets(sts);
|
||
|
ret = viortc_read(vio_ptp->viortc, vio_ptp->vio_clk_id, &ns);
|
||
|
ptp_read_system_postts(sts);
|
||
|
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (ns > (u64)S64_MAX)
|
||
|
return -EINVAL;
|
||
|
|
||
|
*ts = ns_to_timespec64((s64)ns);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* viortc_ptp_enable() - unsupported PTP clock enable op */
|
||
|
static int viortc_ptp_enable(struct ptp_clock_info *ptp,
|
||
|
struct ptp_clock_request *rq, int on)
|
||
|
{
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* viortc_ptp_info_template - ptp_clock_info template
|
||
|
*
|
||
|
* The .name member will be set for individual virtio_rtc PTP clocks.
|
||
|
*
|
||
|
* The .getcrosststamp member will be cleared for PTP clocks not supporting
|
||
|
* crosststamp.
|
||
|
*/
|
||
|
static const struct ptp_clock_info viortc_ptp_info_template = {
|
||
|
.owner = THIS_MODULE,
|
||
|
/* .name is set according to clock type */
|
||
|
.adjfine = viortc_ptp_adjfine,
|
||
|
.adjtime = viortc_ptp_adjtime,
|
||
|
.gettimex64 = viortc_ptp_gettimex64,
|
||
|
.settime64 = viortc_ptp_settime64,
|
||
|
.enable = viortc_ptp_enable,
|
||
|
.getcrosststamp = viortc_ptp_getcrosststamp,
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* viortc_ptp_unregister() - PTP clock unregistering wrapper
|
||
|
* @vio_ptp: virtio_rtc PTP clock
|
||
|
* @parent_dev: parent device of PTP clock
|
||
|
*
|
||
|
* Return: Zero on success, negative error code otherwise.
|
||
|
*/
|
||
|
int viortc_ptp_unregister(struct viortc_ptp_clock *vio_ptp,
|
||
|
struct device *parent_dev)
|
||
|
{
|
||
|
int ret = ptp_clock_unregister(vio_ptp->ptp_clock);
|
||
|
|
||
|
if (!ret)
|
||
|
devm_kfree(parent_dev, vio_ptp);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* viortc_ptp_get_cross_cap() - get xtstamp support info from device
|
||
|
* @viortc: virtio_rtc device data
|
||
|
* @vio_ptp: virtio_rtc PTP clock abstraction
|
||
|
*
|
||
|
* Context: Process context.
|
||
|
* Return: Zero on success, negative error code otherwise.
|
||
|
*/
|
||
|
static int viortc_ptp_get_cross_cap(struct viortc_dev *viortc,
|
||
|
struct viortc_ptp_clock *vio_ptp)
|
||
|
{
|
||
|
enum clocksource_ids cs_id;
|
||
|
bool xtstamp_supported;
|
||
|
u8 hw_counter;
|
||
|
int ret;
|
||
|
|
||
|
ret = viortc_hw_xtstamp_params(&hw_counter, &cs_id);
|
||
|
if (ret) {
|
||
|
vio_ptp->have_cross = false;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ret = viortc_cross_cap(viortc, vio_ptp->vio_clk_id, hw_counter,
|
||
|
&xtstamp_supported);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
vio_ptp->have_cross = xtstamp_supported;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* viortc_ptp_register() - prepare and register PTP clock
|
||
|
* @viortc: virtio_rtc device data
|
||
|
* @parent_dev: parent device for PTP clock
|
||
|
* @vio_clk_id: id of virtio_rtc clock which backs PTP clock
|
||
|
* @ptp_clock_name: PTP clock name
|
||
|
*
|
||
|
* Context: Process context.
|
||
|
* Return: Pointer on success, ERR_PTR() otherwise; NULL if PTP clock support
|
||
|
* not available.
|
||
|
*/
|
||
|
struct viortc_ptp_clock *viortc_ptp_register(struct viortc_dev *viortc,
|
||
|
struct device *parent_dev,
|
||
|
u16 vio_clk_id,
|
||
|
const char *ptp_clock_name)
|
||
|
{
|
||
|
struct viortc_ptp_clock *vio_ptp;
|
||
|
struct ptp_clock *ptp_clock;
|
||
|
ssize_t len;
|
||
|
int ret;
|
||
|
|
||
|
vio_ptp = devm_kzalloc(parent_dev, sizeof(*vio_ptp), GFP_KERNEL);
|
||
|
if (!vio_ptp)
|
||
|
return ERR_PTR(-ENOMEM);
|
||
|
|
||
|
vio_ptp->viortc = viortc;
|
||
|
vio_ptp->vio_clk_id = vio_clk_id;
|
||
|
vio_ptp->ptp_info = viortc_ptp_info_template;
|
||
|
len = strscpy(vio_ptp->ptp_info.name, ptp_clock_name,
|
||
|
sizeof(vio_ptp->ptp_info.name));
|
||
|
if (len < 0) {
|
||
|
ret = len;
|
||
|
goto err_free_dev;
|
||
|
}
|
||
|
|
||
|
ret = viortc_ptp_get_cross_cap(viortc, vio_ptp);
|
||
|
if (ret)
|
||
|
goto err_free_dev;
|
||
|
|
||
|
if (!vio_ptp->have_cross)
|
||
|
vio_ptp->ptp_info.getcrosststamp = NULL;
|
||
|
|
||
|
ptp_clock = ptp_clock_register(&vio_ptp->ptp_info, parent_dev);
|
||
|
if (IS_ERR(ptp_clock))
|
||
|
goto err_on_register;
|
||
|
|
||
|
vio_ptp->ptp_clock = ptp_clock;
|
||
|
|
||
|
return vio_ptp;
|
||
|
|
||
|
err_on_register:
|
||
|
ret = PTR_ERR(ptp_clock);
|
||
|
|
||
|
err_free_dev:
|
||
|
devm_kfree(parent_dev, vio_ptp);
|
||
|
return ERR_PTR(ret);
|
||
|
}
|