diff options
Diffstat (limited to 'arch/um/drivers')
-rw-r--r-- | arch/um/drivers/Kconfig | 13 | ||||
-rw-r--r-- | arch/um/drivers/Makefile | 2 | ||||
-rw-r--r-- | arch/um/drivers/rtc.h | 15 | ||||
-rw-r--r-- | arch/um/drivers/rtc_kern.c | 211 | ||||
-rw-r--r-- | arch/um/drivers/rtc_user.c | 80 | ||||
-rw-r--r-- | arch/um/drivers/ubd_kern.c | 6 | ||||
-rw-r--r-- | arch/um/drivers/virtio_uml.c | 148 |
7 files changed, 425 insertions, 50 deletions
diff --git a/arch/um/drivers/Kconfig b/arch/um/drivers/Kconfig index 2e7b8e0e7194..03ba34b61115 100644 --- a/arch/um/drivers/Kconfig +++ b/arch/um/drivers/Kconfig @@ -323,7 +323,7 @@ config UML_NET_SLIRP frames. In general, slirp allows the UML the same IP connectivity to the outside world that the host user is permitted, and unlike other transports, SLiRP works without the need of root level - privleges, setuid binaries, or SLIP devices on the host. This + privileges, setuid binaries, or SLIP devices on the host. This also means not every type of connection is possible, but most situations can be accommodated with carefully crafted slirp commands that can be passed along as part of the network device's @@ -346,3 +346,14 @@ config VIRTIO_UML help This driver provides support for virtio based paravirtual device drivers over vhost-user sockets. + +config UML_RTC + bool "UML RTC driver" + depends on RTC_CLASS + # there's no use in this if PM_SLEEP isn't enabled ... + depends on PM_SLEEP + help + When PM_SLEEP is configured, it may be desirable to wake up using + rtcwake, especially in time-travel mode. This driver enables that + by providing a fake RTC clock that causes a wakeup at the right + time. diff --git a/arch/um/drivers/Makefile b/arch/um/drivers/Makefile index 2a249f619467..dcc64a02f81f 100644 --- a/arch/um/drivers/Makefile +++ b/arch/um/drivers/Makefile @@ -17,6 +17,7 @@ hostaudio-objs := hostaudio_kern.o ubd-objs := ubd_kern.o ubd_user.o port-objs := port_kern.o port_user.o harddog-objs := harddog_kern.o harddog_user.o +rtc-objs := rtc_kern.o rtc_user.o LDFLAGS_pcap.o = $(shell $(CC) $(KBUILD_CFLAGS) -print-file-name=libpcap.a) @@ -62,6 +63,7 @@ obj-$(CONFIG_UML_WATCHDOG) += harddog.o obj-$(CONFIG_BLK_DEV_COW_COMMON) += cow_user.o obj-$(CONFIG_UML_RANDOM) += random.o obj-$(CONFIG_VIRTIO_UML) += virtio_uml.o +obj-$(CONFIG_UML_RTC) += rtc.o # pcap_user.o must be added explicitly. USER_OBJS := fd.o null.o pty.o tty.o xterm.o slip_common.o pcap_user.o vde_user.o vector_user.o diff --git a/arch/um/drivers/rtc.h b/arch/um/drivers/rtc.h new file mode 100644 index 000000000000..95e41c7d35c4 --- /dev/null +++ b/arch/um/drivers/rtc.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Intel Corporation + * Author: Johannes Berg <johannes@sipsolutions.net> + */ +#ifndef __UM_RTC_H__ +#define __UM_RTC_H__ + +int uml_rtc_start(bool timetravel); +int uml_rtc_enable_alarm(unsigned long long delta_seconds); +void uml_rtc_disable_alarm(void); +void uml_rtc_stop(bool timetravel); +void uml_rtc_send_timetravel_alarm(void); + +#endif /* __UM_RTC_H__ */ diff --git a/arch/um/drivers/rtc_kern.c b/arch/um/drivers/rtc_kern.c new file mode 100644 index 000000000000..97ceb205cfe6 --- /dev/null +++ b/arch/um/drivers/rtc_kern.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Intel Corporation + * Author: Johannes Berg <johannes@sipsolutions.net> + */ +#include <linux/platform_device.h> +#include <linux/time-internal.h> +#include <linux/suspend.h> +#include <linux/err.h> +#include <linux/rtc.h> +#include <kern_util.h> +#include <irq_kern.h> +#include <os.h> +#include "rtc.h" + +static time64_t uml_rtc_alarm_time; +static bool uml_rtc_alarm_enabled; +static struct rtc_device *uml_rtc; +static int uml_rtc_irq_fd, uml_rtc_irq; + +#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT + +static void uml_rtc_time_travel_alarm(struct time_travel_event *ev) +{ + uml_rtc_send_timetravel_alarm(); +} + +static struct time_travel_event uml_rtc_alarm_event = { + .fn = uml_rtc_time_travel_alarm, +}; +#endif + +static int uml_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct timespec64 ts; + + /* Use this to get correct time in time-travel mode */ + read_persistent_clock64(&ts); + rtc_time64_to_tm(timespec64_to_ktime(ts) / NSEC_PER_SEC, tm); + + return 0; +} + +static int uml_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + rtc_time64_to_tm(uml_rtc_alarm_time, &alrm->time); + alrm->enabled = uml_rtc_alarm_enabled; + + return 0; +} + +static int uml_rtc_alarm_irq_enable(struct device *dev, unsigned int enable) +{ + unsigned long long secs; + + if (!enable && !uml_rtc_alarm_enabled) + return 0; + + uml_rtc_alarm_enabled = enable; + + secs = uml_rtc_alarm_time - ktime_get_real_seconds(); + + if (time_travel_mode == TT_MODE_OFF) { + if (!enable) { + uml_rtc_disable_alarm(); + return 0; + } + + /* enable or update */ + return uml_rtc_enable_alarm(secs); + } else { + time_travel_del_event(¨_rtc_alarm_event); + + if (enable) + time_travel_add_event_rel(¨_rtc_alarm_event, + secs * NSEC_PER_SEC); + } + + return 0; +} + +static int uml_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + uml_rtc_alarm_irq_enable(dev, 0); + uml_rtc_alarm_time = rtc_tm_to_time64(&alrm->time); + uml_rtc_alarm_irq_enable(dev, alrm->enabled); + + return 0; +} + +static const struct rtc_class_ops uml_rtc_ops = { + .read_time = uml_rtc_read_time, + .read_alarm = uml_rtc_read_alarm, + .alarm_irq_enable = uml_rtc_alarm_irq_enable, + .set_alarm = uml_rtc_set_alarm, +}; + +static irqreturn_t uml_rtc_interrupt(int irq, void *data) +{ + unsigned long long c = 0; + + /* alarm triggered, it's now off */ + uml_rtc_alarm_enabled = false; + + os_read_file(uml_rtc_irq_fd, &c, sizeof(c)); + WARN_ON(c == 0); + + pm_system_wakeup(); + rtc_update_irq(uml_rtc, 1, RTC_IRQF | RTC_AF); + + return IRQ_HANDLED; +} + +static int uml_rtc_setup(void) +{ + int err; + + err = uml_rtc_start(time_travel_mode != TT_MODE_OFF); + if (WARN(err < 0, "err = %d\n", err)) + return err; + + uml_rtc_irq_fd = err; + + err = um_request_irq(UM_IRQ_ALLOC, uml_rtc_irq_fd, IRQ_READ, + uml_rtc_interrupt, 0, "rtc", NULL); + if (err < 0) { + uml_rtc_stop(time_travel_mode != TT_MODE_OFF); + return err; + } + + irq_set_irq_wake(err, 1); + + uml_rtc_irq = err; + return 0; +} + +static void uml_rtc_cleanup(void) +{ + um_free_irq(uml_rtc_irq, NULL); + uml_rtc_stop(time_travel_mode != TT_MODE_OFF); +} + +static int uml_rtc_probe(struct platform_device *pdev) +{ + int err; + + err = uml_rtc_setup(); + if (err) + return err; + + uml_rtc = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(uml_rtc)) { + err = PTR_ERR(uml_rtc); + goto cleanup; + } + + uml_rtc->ops = ¨_rtc_ops; + + device_init_wakeup(&pdev->dev, 1); + + err = devm_rtc_register_device(uml_rtc); + if (err) + goto cleanup; + + return 0; +cleanup: + uml_rtc_cleanup(); + return err; +} + +static int uml_rtc_remove(struct platform_device *pdev) +{ + device_init_wakeup(&pdev->dev, 0); + uml_rtc_cleanup(); + return 0; +} + +static struct platform_driver uml_rtc_driver = { + .probe = uml_rtc_probe, + .remove = uml_rtc_remove, + .driver = { + .name = "uml-rtc", + }, +}; + +static int __init uml_rtc_init(void) +{ + struct platform_device *pdev; + int err; + + err = platform_driver_register(¨_rtc_driver); + if (err) + return err; + + pdev = platform_device_alloc("uml-rtc", 0); + if (!pdev) { + err = -ENOMEM; + goto unregister; + } + + err = platform_device_add(pdev); + if (err) + goto unregister; + return 0; + +unregister: + platform_device_put(pdev); + platform_driver_unregister(¨_rtc_driver); + return err; +} +device_initcall(uml_rtc_init); diff --git a/arch/um/drivers/rtc_user.c b/arch/um/drivers/rtc_user.c new file mode 100644 index 000000000000..4016bc1d577e --- /dev/null +++ b/arch/um/drivers/rtc_user.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Intel Corporation + * Author: Johannes Berg <johannes@sipsolutions.net> + */ +#include <os.h> +#include <errno.h> +#include <sched.h> +#include <unistd.h> +#include <kern_util.h> +#include <sys/select.h> +#include <stdio.h> +#include <sys/timerfd.h> +#include "rtc.h" + +static int uml_rtc_irq_fds[2]; + +void uml_rtc_send_timetravel_alarm(void) +{ + unsigned long long c = 1; + + CATCH_EINTR(write(uml_rtc_irq_fds[1], &c, sizeof(c))); +} + +int uml_rtc_start(bool timetravel) +{ + int err; + + if (timetravel) { + int err = os_pipe(uml_rtc_irq_fds, 1, 1); + if (err) + goto fail; + } else { + uml_rtc_irq_fds[0] = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC); + if (uml_rtc_irq_fds[0] < 0) { + err = -errno; + goto fail; + } + + /* apparently timerfd won't send SIGIO, use workaround */ + sigio_broken(uml_rtc_irq_fds[0]); + err = add_sigio_fd(uml_rtc_irq_fds[0]); + if (err < 0) { + close(uml_rtc_irq_fds[0]); + goto fail; + } + } + + return uml_rtc_irq_fds[0]; +fail: + uml_rtc_stop(timetravel); + return err; +} + +int uml_rtc_enable_alarm(unsigned long long delta_seconds) +{ + struct itimerspec it = { + .it_value = { + .tv_sec = delta_seconds, + }, + }; + + if (timerfd_settime(uml_rtc_irq_fds[0], 0, &it, NULL)) + return -errno; + return 0; +} + +void uml_rtc_disable_alarm(void) +{ + uml_rtc_enable_alarm(0); +} + +void uml_rtc_stop(bool timetravel) +{ + if (timetravel) + os_close_file(uml_rtc_irq_fds[1]); + else + ignore_sigio_fd(uml_rtc_irq_fds[0]); + os_close_file(uml_rtc_irq_fds[0]); +} diff --git a/arch/um/drivers/ubd_kern.c b/arch/um/drivers/ubd_kern.c index 13b1fe694b90..8e0b43cf089f 100644 --- a/arch/um/drivers/ubd_kern.c +++ b/arch/um/drivers/ubd_kern.c @@ -375,11 +375,11 @@ break_loop: file = NULL; backing_file = strsep(&str, ",:"); - if (*backing_file == '\0') + if (backing_file && *backing_file == '\0') backing_file = NULL; serial = strsep(&str, ",:"); - if (*serial == '\0') + if (serial && *serial == '\0') serial = NULL; if (backing_file && ubd_dev->no_cow) { @@ -1241,7 +1241,7 @@ static int __init ubd_driver_init(void){ /* Letting ubd=sync be like using ubd#s= instead of ubd#= is * enough. So use anyway the io thread. */ } - stack = alloc_stack(0); + stack = alloc_stack(0, 0); io_pid = start_io_thread(stack + PAGE_SIZE - sizeof(void *), &thread_fd); if(io_pid < 0){ diff --git a/arch/um/drivers/virtio_uml.c b/arch/um/drivers/virtio_uml.c index 27e92d3881ff..91ddf74ca888 100644 --- a/arch/um/drivers/virtio_uml.c +++ b/arch/um/drivers/virtio_uml.c @@ -55,16 +55,16 @@ struct virtio_uml_device { u64 protocol_features; u8 status; u8 registered:1; + u8 suspended:1; + + u8 config_changed_irq:1; + uint64_t vq_irq_vq_map; }; struct virtio_uml_vq_info { int kick_fd, call_fd; char name[32]; -#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT - struct virtqueue *vq; - vq_callback_t *callback; - struct time_travel_event defer; -#endif + bool suspended; }; extern unsigned long long physmem_size, highmem; @@ -97,6 +97,9 @@ static int full_read(int fd, void *buf, int len, bool abortable) { int rc; + if (!len) + return 0; + do { rc = os_read_file(fd, buf, len); if (rc > 0) { @@ -347,9 +350,9 @@ static void vhost_user_reply(struct virtio_uml_device *vu_dev, rc, size); } -static irqreturn_t vu_req_interrupt(int irq, void *data) +static irqreturn_t vu_req_read_message(struct virtio_uml_device *vu_dev, + struct time_travel_event *ev) { - struct virtio_uml_device *vu_dev = data; struct virtqueue *vq; int response = 1; struct { @@ -367,14 +370,14 @@ static irqreturn_t vu_req_interrupt(int irq, void *data) switch (msg.msg.header.request) { case VHOST_USER_SLAVE_CONFIG_CHANGE_MSG: - virtio_config_changed(&vu_dev->vdev); + vu_dev->config_changed_irq = true; response = 0; break; case VHOST_USER_SLAVE_VRING_CALL: virtio_device_for_each_vq((&vu_dev->vdev), vq) { if (vq->index == msg.msg.payload.vring_state.index) { response = 0; - vring_interrupt(0 /* ignored */, vq); + vu_dev->vq_irq_vq_map |= BIT_ULL(vq->index); break; } } @@ -388,12 +391,45 @@ static irqreturn_t vu_req_interrupt(int irq, void *data) msg.msg.header.request); } + if (ev && !vu_dev->suspended) + time_travel_add_irq_event(ev); + if (msg.msg.header.flags & VHOST_USER_FLAG_NEED_REPLY) vhost_user_reply(vu_dev, &msg.msg, response); return IRQ_HANDLED; } +static irqreturn_t vu_req_interrupt(int irq, void *data) +{ + struct virtio_uml_device *vu_dev = data; + irqreturn_t ret = IRQ_HANDLED; + + if (!um_irq_timetravel_handler_used()) + ret = vu_req_read_message(vu_dev, NULL); + + if (vu_dev->vq_irq_vq_map) { + struct virtqueue *vq; + + virtio_device_for_each_vq((&vu_dev->vdev), vq) { + if (vu_dev->vq_irq_vq_map & BIT_ULL(vq->index)) + vring_interrupt(0 /* ignored */, vq); + } + vu_dev->vq_irq_vq_map = 0; + } else if (vu_dev->config_changed_irq) { + virtio_config_changed(&vu_dev->vdev); + vu_dev->config_changed_irq = false; + } + + return ret; +} + +static void vu_req_interrupt_comm_handler(int irq, int fd, void *data, + struct time_travel_event *ev) +{ + vu_req_read_message(data, ev); +} + static int vhost_user_init_slave_req(struct virtio_uml_device *vu_dev) { int rc, req_fds[2]; @@ -404,9 +440,10 @@ static int vhost_user_init_slave_req(struct virtio_uml_device *vu_dev) return rc; vu_dev->req_fd = req_fds[0]; - rc = um_request_irq(UM_IRQ_ALLOC, vu_dev->req_fd, IRQ_READ, - vu_req_interrupt, IRQF_SHARED, - vu_dev->pdev->name, vu_dev); + rc = um_request_irq_tt(UM_IRQ_ALLOC, vu_dev->req_fd, IRQ_READ, + vu_req_interrupt, IRQF_SHARED, + vu_dev->pdev->name, vu_dev, + vu_req_interrupt_comm_handler); if (rc < 0) goto err_close; @@ -722,6 +759,9 @@ static bool vu_notify(struct virtqueue *vq) const uint64_t n = 1; int rc; + if (info->suspended) + return true; + time_travel_propagate_time(); if (info->kick_fd < 0) { @@ -875,23 +915,6 @@ out: return rc; } -#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT -static void vu_defer_irq_handle(struct time_travel_event *d) -{ - struct virtio_uml_vq_info *info; - - info = container_of(d, struct virtio_uml_vq_info, defer); - info->callback(info->vq); -} - -static void vu_defer_irq_callback(struct virtqueue *vq) -{ - struct virtio_uml_vq_info *info = vq->priv; - - time_travel_add_irq_event(&info->defer); -} -#endif - static struct virtqueue *vu_setup_vq(struct virtio_device *vdev, unsigned index, vq_callback_t *callback, const char *name, bool ctx) @@ -911,19 +934,6 @@ static struct virtqueue *vu_setup_vq(struct virtio_device *vdev, snprintf(info->name, sizeof(info->name), "%s.%d-%s", pdev->name, pdev->id, name); -#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT - /* - * When we get an interrupt, we must bounce it through the simulation - * calendar (the simtime device), except for the simtime device itself - * since that's part of the simulation control. - */ - if (time_travel_mode == TT_MODE_EXTERNAL && callback) { - info->callback = callback; - callback = vu_defer_irq_callback; - time_travel_set_event_fn(&info->defer, vu_defer_irq_handle); - } -#endif - vq = vring_create_virtqueue(index, num, PAGE_SIZE, vdev, true, true, ctx, vu_notify, callback, info->name); if (!vq) { @@ -932,9 +942,6 @@ static struct virtqueue *vu_setup_vq(struct virtio_device *vdev, } vq->priv = info; num = virtqueue_get_vring_size(vq); -#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT - info->vq = vq; -#endif if (vu_dev->protocol_features & BIT_ULL(VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS)) { @@ -993,6 +1000,10 @@ static int vu_find_vqs(struct virtio_device *vdev, unsigned nvqs, int i, queue_idx = 0, rc; struct virtqueue *vq; + /* not supported for now */ + if (WARN_ON(nvqs > 64)) + return -EINVAL; + rc = vhost_user_set_mem_table(vu_dev); if (rc) return rc; @@ -1084,6 +1095,7 @@ static void virtio_uml_release_dev(struct device *d) } os_close_file(vu_dev->sock); + kfree(vu_dev); } /* Platform device */ @@ -1097,7 +1109,7 @@ static int virtio_uml_probe(struct platform_device *pdev) if (!pdata) return -EINVAL; - vu_dev = devm_kzalloc(&pdev->dev, sizeof(*vu_dev), GFP_KERNEL); + vu_dev = kzalloc(sizeof(*vu_dev), GFP_KERNEL); if (!vu_dev) return -ENOMEM; @@ -1124,6 +1136,8 @@ static int virtio_uml_probe(struct platform_device *pdev) platform_set_drvdata(pdev, vu_dev); + device_set_wakeup_capable(&vu_dev->vdev.dev, true); + rc = register_virtio_device(&vu_dev->vdev); if (rc) put_device(&vu_dev->vdev.dev); @@ -1285,6 +1299,46 @@ static const struct of_device_id virtio_uml_match[] = { }; MODULE_DEVICE_TABLE(of, virtio_uml_match); +static int virtio_uml_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct virtio_uml_device *vu_dev = platform_get_drvdata(pdev); + struct virtqueue *vq; + + virtio_device_for_each_vq((&vu_dev->vdev), vq) { + struct virtio_uml_vq_info *info = vq->priv; + + info->suspended = true; + vhost_user_set_vring_enable(vu_dev, vq->index, false); + } + + if (!device_may_wakeup(&vu_dev->vdev.dev)) { + vu_dev->suspended = true; + return 0; + } + + return irq_set_irq_wake(vu_dev->irq, 1); +} + +static int virtio_uml_resume(struct platform_device *pdev) +{ + struct virtio_uml_device *vu_dev = platform_get_drvdata(pdev); + struct virtqueue *vq; + + virtio_device_for_each_vq((&vu_dev->vdev), vq) { + struct virtio_uml_vq_info *info = vq->priv; + + info->suspended = false; + vhost_user_set_vring_enable(vu_dev, vq->index, true); + } + + vu_dev->suspended = false; + + if (!device_may_wakeup(&vu_dev->vdev.dev)) + return 0; + + return irq_set_irq_wake(vu_dev->irq, 0); +} + static struct platform_driver virtio_uml_driver = { .probe = virtio_uml_probe, .remove = virtio_uml_remove, @@ -1292,6 +1346,8 @@ static struct platform_driver virtio_uml_driver = { .name = "virtio-uml", .of_match_table = virtio_uml_match, }, + .suspend = virtio_uml_suspend, + .resume = virtio_uml_resume, }; static int __init virtio_uml_init(void) |