1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2020 Intel Corporation |
4 | * Author: Johannes Berg <johannes@sipsolutions.net> |
5 | */ |
6 | #include <linux/platform_device.h> |
7 | #include <linux/time-internal.h> |
8 | #include <linux/suspend.h> |
9 | #include <linux/err.h> |
10 | #include <linux/rtc.h> |
11 | #include <kern_util.h> |
12 | #include <irq_kern.h> |
13 | #include <os.h> |
14 | #include "rtc.h" |
15 | |
16 | static time64_t uml_rtc_alarm_time; |
17 | static bool uml_rtc_alarm_enabled; |
18 | static struct rtc_device *uml_rtc; |
19 | static int uml_rtc_irq_fd, uml_rtc_irq; |
20 | |
21 | #ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT |
22 | |
23 | static void uml_rtc_time_travel_alarm(struct time_travel_event *ev) |
24 | { |
25 | uml_rtc_send_timetravel_alarm(); |
26 | } |
27 | |
28 | static struct time_travel_event uml_rtc_alarm_event = { |
29 | .fn = uml_rtc_time_travel_alarm, |
30 | }; |
31 | #endif |
32 | |
33 | static int uml_rtc_read_time(struct device *dev, struct rtc_time *tm) |
34 | { |
35 | struct timespec64 ts; |
36 | |
37 | /* Use this to get correct time in time-travel mode */ |
38 | read_persistent_clock64(ts: &ts); |
39 | rtc_time64_to_tm(time: timespec64_to_ktime(ts) / NSEC_PER_SEC, tm); |
40 | |
41 | return 0; |
42 | } |
43 | |
44 | static int uml_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) |
45 | { |
46 | rtc_time64_to_tm(time: uml_rtc_alarm_time, tm: &alrm->time); |
47 | alrm->enabled = uml_rtc_alarm_enabled; |
48 | |
49 | return 0; |
50 | } |
51 | |
52 | static int uml_rtc_alarm_irq_enable(struct device *dev, unsigned int enable) |
53 | { |
54 | unsigned long long secs; |
55 | |
56 | if (!enable && !uml_rtc_alarm_enabled) |
57 | return 0; |
58 | |
59 | uml_rtc_alarm_enabled = enable; |
60 | |
61 | secs = uml_rtc_alarm_time - ktime_get_real_seconds(); |
62 | |
63 | if (time_travel_mode == TT_MODE_OFF) { |
64 | if (!enable) { |
65 | uml_rtc_disable_alarm(); |
66 | return 0; |
67 | } |
68 | |
69 | /* enable or update */ |
70 | return uml_rtc_enable_alarm(delta_seconds: secs); |
71 | } else { |
72 | time_travel_del_event(¨_rtc_alarm_event); |
73 | |
74 | if (enable) |
75 | time_travel_add_event_rel(¨_rtc_alarm_event, |
76 | secs * NSEC_PER_SEC); |
77 | } |
78 | |
79 | return 0; |
80 | } |
81 | |
82 | static int uml_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) |
83 | { |
84 | uml_rtc_alarm_irq_enable(dev, enable: 0); |
85 | uml_rtc_alarm_time = rtc_tm_to_time64(tm: &alrm->time); |
86 | uml_rtc_alarm_irq_enable(dev, enable: alrm->enabled); |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | static const struct rtc_class_ops uml_rtc_ops = { |
92 | .read_time = uml_rtc_read_time, |
93 | .read_alarm = uml_rtc_read_alarm, |
94 | .alarm_irq_enable = uml_rtc_alarm_irq_enable, |
95 | .set_alarm = uml_rtc_set_alarm, |
96 | }; |
97 | |
98 | static irqreturn_t uml_rtc_interrupt(int irq, void *data) |
99 | { |
100 | unsigned long long c = 0; |
101 | |
102 | /* alarm triggered, it's now off */ |
103 | uml_rtc_alarm_enabled = false; |
104 | |
105 | os_read_file(uml_rtc_irq_fd, &c, sizeof(c)); |
106 | WARN_ON(c == 0); |
107 | |
108 | pm_system_wakeup(); |
109 | rtc_update_irq(rtc: uml_rtc, num: 1, RTC_IRQF | RTC_AF); |
110 | |
111 | return IRQ_HANDLED; |
112 | } |
113 | |
114 | static int uml_rtc_setup(void) |
115 | { |
116 | int err; |
117 | |
118 | err = uml_rtc_start(timetravel: time_travel_mode != TT_MODE_OFF); |
119 | if (WARN(err < 0, "err = %d\n" , err)) |
120 | return err; |
121 | |
122 | uml_rtc_irq_fd = err; |
123 | |
124 | err = um_request_irq(UM_IRQ_ALLOC, uml_rtc_irq_fd, IRQ_READ, |
125 | uml_rtc_interrupt, 0, "rtc" , NULL); |
126 | if (err < 0) { |
127 | uml_rtc_stop(timetravel: time_travel_mode != TT_MODE_OFF); |
128 | return err; |
129 | } |
130 | |
131 | irq_set_irq_wake(irq: err, on: 1); |
132 | |
133 | uml_rtc_irq = err; |
134 | return 0; |
135 | } |
136 | |
137 | static void uml_rtc_cleanup(void) |
138 | { |
139 | um_free_irq(uml_rtc_irq, NULL); |
140 | uml_rtc_stop(timetravel: time_travel_mode != TT_MODE_OFF); |
141 | } |
142 | |
143 | static int uml_rtc_probe(struct platform_device *pdev) |
144 | { |
145 | int err; |
146 | |
147 | err = uml_rtc_setup(); |
148 | if (err) |
149 | return err; |
150 | |
151 | uml_rtc = devm_rtc_allocate_device(dev: &pdev->dev); |
152 | if (IS_ERR(ptr: uml_rtc)) { |
153 | err = PTR_ERR(ptr: uml_rtc); |
154 | goto cleanup; |
155 | } |
156 | |
157 | uml_rtc->ops = ¨_rtc_ops; |
158 | |
159 | device_init_wakeup(dev: &pdev->dev, enable: 1); |
160 | |
161 | err = devm_rtc_register_device(uml_rtc); |
162 | if (err) |
163 | goto cleanup; |
164 | |
165 | return 0; |
166 | cleanup: |
167 | uml_rtc_cleanup(); |
168 | return err; |
169 | } |
170 | |
171 | static int uml_rtc_remove(struct platform_device *pdev) |
172 | { |
173 | device_init_wakeup(dev: &pdev->dev, enable: 0); |
174 | uml_rtc_cleanup(); |
175 | return 0; |
176 | } |
177 | |
178 | static struct platform_driver uml_rtc_driver = { |
179 | .probe = uml_rtc_probe, |
180 | .remove = uml_rtc_remove, |
181 | .driver = { |
182 | .name = "uml-rtc" , |
183 | }, |
184 | }; |
185 | |
186 | static int __init uml_rtc_init(void) |
187 | { |
188 | struct platform_device *pdev; |
189 | int err; |
190 | |
191 | err = platform_driver_register(¨_rtc_driver); |
192 | if (err) |
193 | return err; |
194 | |
195 | pdev = platform_device_alloc(name: "uml-rtc" , id: 0); |
196 | if (!pdev) { |
197 | err = -ENOMEM; |
198 | goto unregister; |
199 | } |
200 | |
201 | err = platform_device_add(pdev); |
202 | if (err) |
203 | goto unregister; |
204 | return 0; |
205 | |
206 | unregister: |
207 | platform_device_put(pdev); |
208 | platform_driver_unregister(¨_rtc_driver); |
209 | return err; |
210 | } |
211 | device_initcall(uml_rtc_init); |
212 | |