// 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 #include #include #include #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); }