diff options
Diffstat (limited to 'drivers/virtio/virtio_rtc_driver.c')
| -rw-r--r-- | drivers/virtio/virtio_rtc_driver.c | 520 |
1 files changed, 513 insertions, 7 deletions
diff --git a/drivers/virtio/virtio_rtc_driver.c b/drivers/virtio/virtio_rtc_driver.c index 7499908ee34c..a57d5e06e19d 100644 --- a/drivers/virtio/virtio_rtc_driver.c +++ b/drivers/virtio/virtio_rtc_driver.c @@ -18,9 +18,12 @@ #include "virtio_rtc_internal.h" +#define VIORTC_ALARMQ_BUF_CAP sizeof(union virtio_rtc_notif_alarmq) + /* virtqueue order */ enum { VIORTC_REQUESTQ, + VIORTC_ALARMQ, VIORTC_MAX_NR_QUEUES, }; @@ -37,17 +40,23 @@ struct viortc_vq { /** * struct viortc_dev - virtio_rtc device data * @vdev: virtio device + * @viortc_class: RTC class wrapper for UTC-like clock, NULL if not available * @vqs: virtqueues * @clocks_to_unregister: Clock references, which are only used during device * removal. * For other uses, there would be a race between device * creation and setting the pointers here. + * @alarmq_bufs: alarmq buffers list + * @num_alarmq_bufs: # of alarmq buffers * @num_clocks: # of virtio_rtc clocks */ struct viortc_dev { struct virtio_device *vdev; + struct viortc_class *viortc_class; struct viortc_vq vqs[VIORTC_MAX_NR_QUEUES]; struct viortc_ptp_clock **clocks_to_unregister; + void **alarmq_bufs; + unsigned int num_alarmq_bufs; u16 num_clocks; }; @@ -79,6 +88,60 @@ struct viortc_msg { }; /** + * viortc_class_from_dev() - Get RTC class object from virtio device. + * @dev: virtio device + * + * Context: Any context. + * Return: RTC class object if available, ERR_PTR otherwise. + */ +struct viortc_class *viortc_class_from_dev(struct device *dev) +{ + struct virtio_device *vdev; + struct viortc_dev *viortc; + + vdev = container_of(dev, typeof(*vdev), dev); + viortc = vdev->priv; + + return viortc->viortc_class ?: ERR_PTR(-ENODEV); +} + +/** + * viortc_alarms_supported() - Whether device and driver support alarms. + * @vdev: virtio device + * + * NB: Device and driver may not support alarms for the same clocks. + * + * Context: Any context. + * Return: True if both device and driver can support alarms. + */ +static bool viortc_alarms_supported(struct virtio_device *vdev) +{ + return IS_ENABLED(CONFIG_VIRTIO_RTC_CLASS) && + virtio_has_feature(vdev, VIRTIO_RTC_F_ALARM); +} + +/** + * viortc_feed_vq() - Make a device write-only buffer available. + * @viortc: device data + * @vq: notification virtqueue + * @buf: buffer + * @buf_len: buffer capacity in bytes + * @data: token, identifying buffer + * + * Context: Caller must prevent concurrent access to vq. + * Return: Zero on success, negative error code otherwise. + */ +static int viortc_feed_vq(struct viortc_dev *viortc, struct virtqueue *vq, + void *buf, unsigned int buf_len, void *data) +{ + struct scatterlist sg; + + sg_init_one(&sg, buf, buf_len); + + return virtqueue_add_inbuf(vq, &sg, 1, data, GFP_ATOMIC); +} + +/** * viortc_msg_init() - Allocate and initialize requestq message. * @viortc: device data * @msg_type: virtio_rtc message type @@ -237,6 +300,85 @@ static void viortc_cb_requestq(struct virtqueue *vq) } /** + * viortc_alarmq_hdlr() - process an alarmq used buffer + * @token: token identifying the buffer + * @len: bytes written by device + * @vq: virtqueue + * @viortc_vq: device specific data for virtqueue + * @viortc: device data + * + * Processes a VIRTIO_RTC_NOTIF_ALARM notification by calling the RTC class + * driver. Makes the buffer available again. + * + * Context: virtqueue callback + */ +static void viortc_alarmq_hdlr(void *token, unsigned int len, + struct virtqueue *vq, + struct viortc_vq *viortc_vq, + struct viortc_dev *viortc) +{ + struct virtio_rtc_notif_alarm *notif = token; + struct virtio_rtc_notif_head *head = token; + unsigned long flags; + u16 clock_id; + bool notify; + + if (len < sizeof(*head)) { + dev_err_ratelimited(&viortc->vdev->dev, + "%s: ignoring notification with short header\n", + __func__); + goto feed_vq; + } + + if (virtio_le_to_cpu(head->msg_type) != VIRTIO_RTC_NOTIF_ALARM) { + dev_err_ratelimited(&viortc->vdev->dev, + "%s: ignoring unknown notification type 0x%x\n", + __func__, virtio_le_to_cpu(head->msg_type)); + goto feed_vq; + } + + if (len < sizeof(*notif)) { + dev_err_ratelimited(&viortc->vdev->dev, + "%s: ignoring too small alarm notification\n", + __func__); + goto feed_vq; + } + + clock_id = virtio_le_to_cpu(notif->clock_id); + + if (!viortc->viortc_class) + dev_warn_ratelimited(&viortc->vdev->dev, + "ignoring alarm, no RTC class device available\n"); + else + viortc_class_alarm(viortc->viortc_class, clock_id); + +feed_vq: + spin_lock_irqsave(&viortc_vq->lock, flags); + + if (viortc_feed_vq(viortc, vq, notif, VIORTC_ALARMQ_BUF_CAP, token)) + dev_warn(&viortc->vdev->dev, + "%s: failed to re-expose input buffer\n", __func__); + + notify = virtqueue_kick_prepare(vq); + + spin_unlock_irqrestore(&viortc_vq->lock, flags); + + if (notify) + virtqueue_notify(vq); +} + +/** + * viortc_cb_alarmq() - callback for alarmq + * @vq: virtqueue + * + * Context: virtqueue callback + */ +static void viortc_cb_alarmq(struct virtqueue *vq) +{ + viortc_do_cb(vq, viortc_alarmq_hdlr); +} + +/** * viortc_get_resp_errno() - converts virtio_rtc errnos to system errnos * @resp_head: message response header * @@ -561,12 +703,13 @@ out_release: * @vio_clk_id: virtio_rtc clock id * @type: virtio_rtc clock type * @leap_second_smearing: virtio_rtc smearing variant + * @flags: struct virtio_rtc_resp_clock_cap.flags * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ static int viortc_clock_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 *type, - u8 *leap_second_smearing) + u8 *leap_second_smearing, u8 *flags) { VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_CLOCK_CAP, struct virtio_rtc_req_clock_cap, @@ -589,6 +732,7 @@ static int viortc_clock_cap(struct viortc_dev *viortc, u16 vio_clk_id, u8 *type, VIORTC_MSG_READ(hdl, type, type); VIORTC_MSG_READ(hdl, leap_second_smearing, leap_second_smearing); + VIORTC_MSG_READ(hdl, flags, flags); out_release: viortc_msg_release(VIORTC_MSG(hdl)); @@ -639,11 +783,190 @@ out_release: return ret; } +/** + * viortc_read_alarm() - VIRTIO_RTC_REQ_READ_ALARM wrapper + * @viortc: device data + * @vio_clk_id: virtio_rtc clock id + * @alarm_time: alarm time in ns + * @enabled: whether alarm is enabled + * + * Context: Process context. + * Return: Zero on success, negative error code otherwise. + */ +int viortc_read_alarm(struct viortc_dev *viortc, u16 vio_clk_id, + u64 *alarm_time, bool *enabled) +{ + VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_READ_ALARM, + struct virtio_rtc_req_read_alarm, + struct virtio_rtc_resp_read_alarm); + u8 flags; + int ret; + + ret = VIORTC_MSG_INIT(hdl, viortc); + if (ret) + return ret; + + VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); + + ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), + 0); + if (ret) { + dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, + ret); + goto out_release; + } + + VIORTC_MSG_READ(hdl, alarm_time, alarm_time); + VIORTC_MSG_READ(hdl, flags, &flags); + + *enabled = !!(flags & VIRTIO_RTC_FLAG_ALARM_ENABLED); + +out_release: + viortc_msg_release(VIORTC_MSG(hdl)); + + return ret; +} + +/** + * viortc_set_alarm() - VIRTIO_RTC_REQ_SET_ALARM wrapper + * @viortc: device data + * @vio_clk_id: virtio_rtc clock id + * @alarm_time: alarm time in ns + * @alarm_enable: enable or disable alarm + * + * Context: Process context. + * Return: Zero on success, negative error code otherwise. + */ +int viortc_set_alarm(struct viortc_dev *viortc, u16 vio_clk_id, u64 alarm_time, + bool alarm_enable) +{ + VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_SET_ALARM, + struct virtio_rtc_req_set_alarm, + struct virtio_rtc_resp_set_alarm); + u8 flags = 0; + int ret; + + ret = VIORTC_MSG_INIT(hdl, viortc); + if (ret) + return ret; + + if (alarm_enable) + flags |= VIRTIO_RTC_FLAG_ALARM_ENABLED; + + VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); + VIORTC_MSG_WRITE(hdl, alarm_time, &alarm_time); + VIORTC_MSG_WRITE(hdl, flags, &flags); + + ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), + 0); + if (ret) { + dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, + ret); + goto out_release; + } + +out_release: + viortc_msg_release(VIORTC_MSG(hdl)); + + return ret; +} + +/** + * viortc_set_alarm_enabled() - VIRTIO_RTC_REQ_SET_ALARM_ENABLED wrapper + * @viortc: device data + * @vio_clk_id: virtio_rtc clock id + * @alarm_enable: enable or disable alarm + * + * Context: Process context. + * Return: Zero on success, negative error code otherwise. + */ +int viortc_set_alarm_enabled(struct viortc_dev *viortc, u16 vio_clk_id, + bool alarm_enable) +{ + VIORTC_DECLARE_MSG_HDL_ONSTACK(hdl, VIRTIO_RTC_REQ_SET_ALARM_ENABLED, + struct virtio_rtc_req_set_alarm_enabled, + struct virtio_rtc_resp_set_alarm_enabled); + u8 flags = 0; + int ret; + + ret = VIORTC_MSG_INIT(hdl, viortc); + if (ret) + return ret; + + if (alarm_enable) + flags |= VIRTIO_RTC_FLAG_ALARM_ENABLED; + + VIORTC_MSG_WRITE(hdl, clock_id, &vio_clk_id); + VIORTC_MSG_WRITE(hdl, flags, &flags); + + ret = viortc_msg_xfer(&viortc->vqs[VIORTC_REQUESTQ], VIORTC_MSG(hdl), + 0); + if (ret) { + dev_dbg(&viortc->vdev->dev, "%s: xfer returned %d\n", __func__, + ret); + goto out_release; + } + +out_release: + viortc_msg_release(VIORTC_MSG(hdl)); + + return ret; +} + /* * init, deinit */ /** + * viortc_init_rtc_class_clock() - init and register a RTC class device + * @viortc: device data + * @vio_clk_id: virtio_rtc clock id + * @clock_type: virtio_rtc clock type + * @flags: struct virtio_rtc_resp_clock_cap.flags + * + * The clock must be a UTC-like clock. + * + * Context: Process context. + * Return: Positive if registered, zero if not supported by configuration, + * negative error code otherwise. + */ +static int viortc_init_rtc_class_clock(struct viortc_dev *viortc, + u16 vio_clk_id, u8 clock_type, u8 flags) +{ + struct virtio_device *vdev = viortc->vdev; + struct viortc_class *viortc_class; + struct device *dev = &vdev->dev; + bool have_alarm; + + if (clock_type != VIRTIO_RTC_CLOCK_UTC_SMEARED) { + dev_info(dev, + "not creating RTC class device for clock %d, which may step on leap seconds\n", + vio_clk_id); + return 0; + } + + if (viortc->viortc_class) { + dev_warn_once(dev, + "multiple UTC-like clocks are present, but creating only one RTC class device\n"); + return 0; + } + + have_alarm = viortc_alarms_supported(vdev) && + !!(flags & VIRTIO_RTC_FLAG_ALARM_CAP); + + viortc_class = viortc_class_init(viortc, vio_clk_id, have_alarm, dev); + if (IS_ERR(viortc_class)) + return PTR_ERR(viortc_class); + + viortc->viortc_class = viortc_class; + + if (have_alarm) + devm_device_init_wakeup(dev); + + return viortc_class_register(viortc_class) ?: 1; +} + +/** * viortc_init_ptp_clock() - init and register PTP clock * @viortc: device data * @vio_clk_id: virtio_rtc clock id @@ -682,22 +1005,34 @@ static int viortc_init_ptp_clock(struct viortc_dev *viortc, u16 vio_clk_id, * @viortc: device data * @vio_clk_id: virtio_rtc clock id * - * Initializes PHC to represent virtio_rtc clock. + * Initializes PHC and/or RTC class device to represent virtio_rtc clock. * * Context: Process context. * Return: Zero on success, negative error code otherwise. */ static int viortc_init_clock(struct viortc_dev *viortc, u16 vio_clk_id) { - u8 clock_type, leap_second_smearing; + u8 clock_type, leap_second_smearing, flags; bool is_exposed = false; int ret; ret = viortc_clock_cap(viortc, vio_clk_id, &clock_type, - &leap_second_smearing); + &leap_second_smearing, &flags); if (ret) return ret; + if (IS_ENABLED(CONFIG_VIRTIO_RTC_CLASS) && + (clock_type == VIRTIO_RTC_CLOCK_UTC || + clock_type == VIRTIO_RTC_CLOCK_UTC_SMEARED || + clock_type == VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED)) { + ret = viortc_init_rtc_class_clock(viortc, vio_clk_id, + clock_type, flags); + if (ret < 0) + return ret; + if (ret > 0) + is_exposed = true; + } + if (IS_ENABLED(CONFIG_VIRTIO_RTC_PTP)) { ret = viortc_init_ptp_clock(viortc, vio_clk_id, clock_type, leap_second_smearing); @@ -716,7 +1051,7 @@ static int viortc_init_clock(struct viortc_dev *viortc, u16 vio_clk_id) } /** - * viortc_clocks_deinit() - unregister PHCs + * viortc_clocks_deinit() - unregister PHCs, stop RTC ops * @viortc: device data */ static void viortc_clocks_deinit(struct viortc_dev *viortc) @@ -734,6 +1069,9 @@ static void viortc_clocks_deinit(struct viortc_dev *viortc) WARN_ON(viortc_ptp_unregister(vio_ptp, &viortc->vdev->dev)); } + + if (viortc->viortc_class) + viortc_class_stop(viortc->viortc_class); } /** @@ -781,6 +1119,88 @@ err_deinit_clocks: } /** + * viortc_populate_vq() - populate alarmq with device-writable buffers + * @viortc: device data + * @viortc_vq: device specific data for virtqueue + * @buf_cap: device-writable buffer size in bytes + * @lock: lock queue during accesses + * + * Populates the alarmq with pre-allocated buffers. + * + * The caller is responsible for kicking the device. + * + * Context: Process context. + * Return: Zero on success, negative error code otherwise. + */ +static int viortc_populate_vq(struct viortc_dev *viortc, + struct viortc_vq *viortc_vq, u32 buf_cap, + bool lock) +{ + unsigned int num_elems, i; + struct virtqueue *vq; + unsigned long flags; + void *buf; + int ret; + + num_elems = viortc->num_alarmq_bufs; + vq = viortc_vq->vq; + + for (i = 0; i < num_elems; i++) { + buf = viortc->alarmq_bufs[i]; + + if (lock) { + spin_lock_irqsave(&viortc_vq->lock, flags); + + ret = viortc_feed_vq(viortc, vq, buf, buf_cap, buf); + + spin_unlock_irqrestore(&viortc_vq->lock, flags); + } else { + ret = viortc_feed_vq(viortc, vq, buf, buf_cap, buf); + } + + if (ret) + return ret; + } + + return 0; +} + +/** + * viortc_alloc_vq_bufs() - allocate alarmq buffers + * @viortc: device data + * @num_elems: # of buffers + * @buf_cap: per-buffer device-writable bytes + * + * Context: Process context. + * Return: Zero on success, negative error code otherwise. + */ +static int viortc_alloc_vq_bufs(struct viortc_dev *viortc, + unsigned int num_elems, u32 buf_cap) +{ + struct device *dev = &viortc->vdev->dev; + void **buf_list; + unsigned int i; + void *buf; + + buf_list = devm_kcalloc(dev, num_elems, sizeof(*buf_list), GFP_KERNEL); + if (!buf_list) + return -ENOMEM; + + viortc->alarmq_bufs = buf_list; + viortc->num_alarmq_bufs = num_elems; + + for (i = 0; i < num_elems; i++) { + buf = devm_kzalloc(dev, buf_cap, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf_list[i] = buf; + } + + return 0; +} + +/** * viortc_init_vqs() - init virtqueues * @viortc: device data * @@ -794,11 +1214,19 @@ static int viortc_init_vqs(struct viortc_dev *viortc) struct virtqueue *vqs[VIORTC_MAX_NR_QUEUES]; struct virtqueue_info vqs_info[] = { { "requestq", viortc_cb_requestq }, + { "alarmq", viortc_cb_alarmq }, }; struct virtio_device *vdev = viortc->vdev; + unsigned int num_elems; int nr_queues, ret; + bool have_alarms; - nr_queues = VIORTC_REQUESTQ + 1; + have_alarms = viortc_alarms_supported(vdev); + + if (have_alarms) + nr_queues = VIORTC_ALARMQ + 1; + else + nr_queues = VIORTC_REQUESTQ + 1; ret = virtio_find_vqs(vdev, nr_queues, vqs, vqs_info, NULL); if (ret) @@ -807,6 +1235,25 @@ static int viortc_init_vqs(struct viortc_dev *viortc) viortc->vqs[VIORTC_REQUESTQ].vq = vqs[VIORTC_REQUESTQ]; spin_lock_init(&viortc->vqs[VIORTC_REQUESTQ].lock); + if (have_alarms) { + viortc->vqs[VIORTC_ALARMQ].vq = vqs[VIORTC_ALARMQ]; + spin_lock_init(&viortc->vqs[VIORTC_ALARMQ].lock); + + num_elems = virtqueue_get_vring_size(vqs[VIORTC_ALARMQ]); + if (num_elems == 0) + return -ENOSPC; + + if (!viortc->alarmq_bufs) { + ret = viortc_alloc_vq_bufs(viortc, num_elems, + VIORTC_ALARMQ_BUF_CAP); + if (ret) + return ret; + } else { + viortc->num_alarmq_bufs = + min(num_elems, viortc->num_alarmq_bufs); + } + } + return 0; } @@ -819,7 +1266,11 @@ static int viortc_init_vqs(struct viortc_dev *viortc) */ static int viortc_probe(struct virtio_device *vdev) { + struct viortc_vq *alarm_viortc_vq; + struct virtqueue *alarm_vq; struct viortc_dev *viortc; + unsigned long flags; + bool notify; int ret; viortc = devm_kzalloc(&vdev->dev, sizeof(*viortc), GFP_KERNEL); @@ -839,8 +1290,30 @@ static int viortc_probe(struct virtio_device *vdev) if (ret) goto err_reset_vdev; + if (viortc_alarms_supported(vdev)) { + alarm_viortc_vq = &viortc->vqs[VIORTC_ALARMQ]; + alarm_vq = alarm_viortc_vq->vq; + + ret = viortc_populate_vq(viortc, alarm_viortc_vq, + VIORTC_ALARMQ_BUF_CAP, true); + if (ret) + goto err_deinit_clocks; + + spin_lock_irqsave(&alarm_viortc_vq->lock, flags); + notify = virtqueue_kick_prepare(alarm_vq); + spin_unlock_irqrestore(&alarm_viortc_vq->lock, flags); + + if (notify && !virtqueue_notify(alarm_vq)) { + ret = -EIO; + goto err_deinit_clocks; + } + } + return 0; +err_deinit_clocks: + viortc_clocks_deinit(viortc); + err_reset_vdev: virtio_reset_device(vdev); vdev->config->del_vqs(vdev); @@ -875,10 +1348,41 @@ static int viortc_freeze(struct virtio_device *dev) static int viortc_restore(struct virtio_device *dev) { struct viortc_dev *viortc = dev->priv; + struct viortc_vq *alarm_viortc_vq; + struct virtqueue *alarm_vq; + bool notify = false; + int ret; - return viortc_init_vqs(viortc); + ret = viortc_init_vqs(viortc); + if (ret) + return ret; + + alarm_viortc_vq = &viortc->vqs[VIORTC_ALARMQ]; + alarm_vq = alarm_viortc_vq->vq; + + if (viortc_alarms_supported(dev)) { + ret = viortc_populate_vq(viortc, alarm_viortc_vq, + VIORTC_ALARMQ_BUF_CAP, false); + if (ret) + return ret; + + notify = virtqueue_kick_prepare(alarm_vq); + } + + virtio_device_ready(dev); + + if (notify && !virtqueue_notify(alarm_vq)) + ret = -EIO; + + return ret; } +static unsigned int features[] = { +#if IS_ENABLED(CONFIG_VIRTIO_RTC_CLASS) + VIRTIO_RTC_F_ALARM, +#endif +}; + static struct virtio_device_id id_table[] = { { VIRTIO_ID_CLOCK, VIRTIO_DEV_ANY_ID }, { 0 }, @@ -887,6 +1391,8 @@ MODULE_DEVICE_TABLE(virtio, id_table); static struct virtio_driver virtio_rtc_drv = { .driver.name = KBUILD_MODNAME, + .feature_table = features, + .feature_table_size = ARRAY_SIZE(features), .id_table = id_table, .probe = viortc_probe, .remove = viortc_remove, |
