--- /dev/null
+// 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);
 
--- /dev/null
+// 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]);
+}