123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright (C) 2020 Intel Corporation
- * Author: Johannes Berg <[email protected]>
- */
- #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);
|