1 /* $OpenBSD: mc146818.c,v 1.29 2024/07/10 09:27:33 dv Exp $ */
2 /*
3 * Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <sys/types.h>
19
20 #include <dev/ic/mc146818reg.h>
21 #include <dev/isa/isareg.h>
22 #include <dev/vmm/vmm.h>
23
24 #include <event.h>
25 #include <stddef.h>
26 #include <string.h>
27 #include <time.h>
28 #include <unistd.h>
29
30 #include "atomicio.h"
31 #include "mc146818.h"
32 #include "virtio.h"
33 #include "vmd.h"
34
35 #define MC_RATE_MASK 0xf
36
37 #define NVRAM_CENTURY 0x32
38 #define NVRAM_MEMSIZE_LO 0x34
39 #define NVRAM_MEMSIZE_HI 0x35
40 #define NVRAM_HIMEMSIZE_LO 0x5B
41 #define NVRAM_HIMEMSIZE_MID 0x5C
42 #define NVRAM_HIMEMSIZE_HI 0x5D
43 #define NVRAM_SMP_COUNT 0x5F
44
45 #define NVRAM_SIZE 0x60
46
47 #define TOBCD(x) (((x) / 10 * 16) + ((x) % 10))
48
49 struct mc146818 {
50 time_t now;
51 uint8_t idx;
52 uint8_t regs[NVRAM_SIZE];
53 uint32_t vm_id;
54 struct event sec;
55 struct timeval sec_tv;
56 struct event per;
57 struct timeval per_tv;
58 };
59
60 struct mc146818 rtc;
61
62 static struct vm_dev_pipe dev_pipe;
63
64 static void rtc_reschedule_per(void);
65
66 /*
67 * mc146818_pipe_dispatch
68 *
69 * Reads a message off the pipe, expecting a request to reschedule periodic
70 * interrupt rate.
71 */
72 static void
mc146818_pipe_dispatch(int fd,short event,void * arg)73 mc146818_pipe_dispatch(int fd, short event, void *arg)
74 {
75 enum pipe_msg_type msg;
76
77 msg = vm_pipe_recv(&dev_pipe);
78 if (msg == MC146818_RESCHEDULE_PER) {
79 rtc_reschedule_per();
80 } else {
81 fatalx("%s: unexpected pipe message %d", __func__, msg);
82 }
83 }
84
85 /*
86 * rtc_updateregs
87 *
88 * Updates the RTC TOD bytes, reflecting 'now'.
89 */
90 static void
rtc_updateregs(void)91 rtc_updateregs(void)
92 {
93 struct tm *gnow;
94
95 rtc.regs[MC_REGD] &= ~MC_REGD_VRT;
96 gnow = gmtime(&rtc.now);
97
98 rtc.regs[MC_SEC] = TOBCD(gnow->tm_sec);
99 rtc.regs[MC_MIN] = TOBCD(gnow->tm_min);
100 rtc.regs[MC_HOUR] = TOBCD(gnow->tm_hour);
101 rtc.regs[MC_DOW] = TOBCD(gnow->tm_wday + 1);
102 rtc.regs[MC_DOM] = TOBCD(gnow->tm_mday);
103 rtc.regs[MC_MONTH] = TOBCD(gnow->tm_mon + 1);
104 rtc.regs[MC_YEAR] = TOBCD((gnow->tm_year + 1900) % 100);
105 rtc.regs[NVRAM_CENTURY] = TOBCD((gnow->tm_year + 1900) / 100);
106 rtc.regs[MC_REGD] |= MC_REGD_VRT;
107 }
108
109 /*
110 * rtc_fire1
111 *
112 * Callback for the 1s periodic TOD refresh timer
113 *
114 * Parameters:
115 * fd: unused
116 * type: unused
117 * arg: unused
118 */
119 static void
rtc_fire1(int fd,short type,void * arg)120 rtc_fire1(int fd, short type, void *arg)
121 {
122 time_t old = rtc.now;
123
124 time(&rtc.now);
125
126 rtc_updateregs();
127 if (rtc.now - old > 5) {
128 log_debug("%s: RTC clock drift (%llds), requesting guest "
129 "resync", __func__, (rtc.now - old));
130 vmmci_ctl(VMMCI_SYNCRTC);
131 }
132 evtimer_add(&rtc.sec, &rtc.sec_tv);
133 }
134
135 /*
136 * rtc_fireper
137 *
138 * Callback for the periodic interrupt timer
139 *
140 * Parameters:
141 * fd: unused
142 * type: unused
143 * arg: (as uint32_t), VM ID to which this RTC belongs
144 */
145 static void
rtc_fireper(int fd,short type,void * arg)146 rtc_fireper(int fd, short type, void *arg)
147 {
148 rtc.regs[MC_REGC] |= MC_REGC_PF;
149
150 vcpu_assert_irq((ptrdiff_t)arg, 0, 8);
151
152 evtimer_add(&rtc.per, &rtc.per_tv);
153 }
154
155 /*
156 * mc146818_init
157 *
158 * Initializes the emulated RTC/NVRAM
159 *
160 * Parameters:
161 * vm_id: VM ID to which this RTC belongs
162 * memlo: size of memory in bytes between 16MB .. 4GB
163 * memhi: size of memory in bytes after 4GB
164 */
165 void
mc146818_init(uint32_t vm_id,uint64_t memlo,uint64_t memhi)166 mc146818_init(uint32_t vm_id, uint64_t memlo, uint64_t memhi)
167 {
168 memset(&rtc, 0, sizeof(rtc));
169 time(&rtc.now);
170
171 rtc.regs[MC_REGB] = MC_REGB_24HR;
172
173 memlo /= 65536;
174 memhi /= 65536;
175
176 rtc.regs[NVRAM_MEMSIZE_HI] = (memlo >> 8) & 0xFF;
177 rtc.regs[NVRAM_MEMSIZE_LO] = memlo & 0xFF;
178 rtc.regs[NVRAM_HIMEMSIZE_HI] = (memhi >> 16) & 0xFF;
179 rtc.regs[NVRAM_HIMEMSIZE_MID] = (memhi >> 8) & 0xFF;
180 rtc.regs[NVRAM_HIMEMSIZE_LO] = memhi & 0xFF;
181
182 rtc.regs[NVRAM_SMP_COUNT] = 0;
183
184 rtc_updateregs();
185 rtc.vm_id = vm_id;
186
187 timerclear(&rtc.sec_tv);
188 rtc.sec_tv.tv_sec = 1;
189
190 timerclear(&rtc.per_tv);
191
192 evtimer_set(&rtc.sec, rtc_fire1, NULL);
193 evtimer_add(&rtc.sec, &rtc.sec_tv);
194
195 evtimer_set(&rtc.per, rtc_fireper, (void *)(intptr_t)rtc.vm_id);
196
197 vm_pipe_init(&dev_pipe, mc146818_pipe_dispatch);
198 event_add(&dev_pipe.read_ev, NULL);
199 }
200
201 /*
202 * rtc_reschedule_per
203 *
204 * Reschedule the periodic interrupt firing rate, based on the currently
205 * selected REGB values.
206 */
207 static void
rtc_reschedule_per(void)208 rtc_reschedule_per(void)
209 {
210 uint16_t rate;
211 uint64_t us;
212
213 if (rtc.regs[MC_REGB] & MC_REGB_PIE) {
214 rate = 32768 >> ((rtc.regs[MC_REGA] & MC_RATE_MASK) - 1);
215 us = (1.0 / rate) * 1000000;
216 rtc.per_tv.tv_usec = us;
217 if (evtimer_pending(&rtc.per, NULL))
218 evtimer_del(&rtc.per);
219
220 evtimer_add(&rtc.per, &rtc.per_tv);
221 }
222 }
223
224 /*
225 * rtc_update_rega
226 *
227 * Updates the RTC's REGA register
228 *
229 * Parameters:
230 * data: REGA register data
231 */
232 static void
rtc_update_rega(uint32_t data)233 rtc_update_rega(uint32_t data)
234 {
235 rtc.regs[MC_REGA] = data;
236 if ((rtc.regs[MC_REGA] ^ data) & 0x0f)
237 vm_pipe_send(&dev_pipe, MC146818_RESCHEDULE_PER);
238 }
239
240 /*
241 * rtc_update_regb
242 *
243 * Updates the RTC's REGB register
244 *
245 * Parameters:
246 * data: REGB register data
247 */
248 static void
rtc_update_regb(uint32_t data)249 rtc_update_regb(uint32_t data)
250 {
251 if (data & MC_REGB_DSE)
252 log_warnx("%s: DSE mode not supported", __func__);
253
254 if (!(data & MC_REGB_24HR))
255 log_warnx("%s: 12 hour mode not supported", __func__);
256
257 rtc.regs[MC_REGB] = data;
258
259 if (data & MC_REGB_PIE)
260 vm_pipe_send(&dev_pipe, MC146818_RESCHEDULE_PER);
261 }
262
263 /*
264 * vcpu_exit_mc146818
265 *
266 * Handles emulated MC146818 RTC access (in/out instruction to RTC ports).
267 *
268 * Parameters:
269 * vrp: vm run parameters containing exit information for the I/O
270 * instruction being performed
271 *
272 * Return value:
273 * Interrupt to inject to the guest VM, or 0xFF if no interrupt should
274 * be injected.
275 */
276 uint8_t
vcpu_exit_mc146818(struct vm_run_params * vrp)277 vcpu_exit_mc146818(struct vm_run_params *vrp)
278 {
279 struct vm_exit *vei = vrp->vrp_exit;
280 uint16_t port = vei->vei.vei_port;
281 uint8_t dir = vei->vei.vei_dir;
282 uint32_t data = 0;
283
284 get_input_data(vei, &data);
285
286 if (port == IO_RTC) {
287 /* Discard NMI bit */
288 if (data & 0x80)
289 data &= ~0x80;
290
291 if (dir == 0) {
292 if (data < (NVRAM_SIZE))
293 rtc.idx = data;
294 else
295 rtc.idx = MC_REGD;
296 } else
297 set_return_data(vei, rtc.idx);
298 } else if (port == IO_RTC + 1) {
299 if (dir == 0) {
300 switch (rtc.idx) {
301 case MC_SEC ... MC_YEAR:
302 case MC_NVRAM_START ... MC_NVRAM_START + MC_NVRAM_SIZE:
303 rtc.regs[rtc.idx] = data;
304 break;
305 case MC_REGA:
306 rtc_update_rega(data);
307 break;
308 case MC_REGB:
309 rtc_update_regb(data);
310 break;
311 case MC_REGC:
312 case MC_REGD:
313 log_warnx("%s: mc146818 illegal write "
314 "of reg 0x%x", __func__, rtc.idx);
315 break;
316 default:
317 log_warnx("%s: mc146818 illegal reg %x\n",
318 __func__, rtc.idx);
319 }
320 } else {
321 data = rtc.regs[rtc.idx];
322 set_return_data(vei, data);
323
324 if (rtc.idx == MC_REGC) {
325 /* Reset IRQ state */
326 rtc.regs[MC_REGC] &= ~MC_REGC_PF;
327 }
328 }
329 } else {
330 log_warnx("%s: mc146818 unknown port 0x%x",
331 __func__, vei->vei.vei_port);
332 }
333
334 return 0xFF;
335 }
336
337 int
mc146818_dump(int fd)338 mc146818_dump(int fd)
339 {
340 log_debug("%s: sending RTC", __func__);
341 if (atomicio(vwrite, fd, &rtc, sizeof(rtc)) != sizeof(rtc)) {
342 log_warnx("%s: error writing RTC to fd", __func__);
343 return (-1);
344 }
345 return (0);
346 }
347
348 int
mc146818_restore(int fd,uint32_t vm_id)349 mc146818_restore(int fd, uint32_t vm_id)
350 {
351 log_debug("%s: restoring RTC", __func__);
352 if (atomicio(read, fd, &rtc, sizeof(rtc)) != sizeof(rtc)) {
353 log_warnx("%s: error reading RTC from fd", __func__);
354 return (-1);
355 }
356 rtc.vm_id = vm_id;
357
358 memset(&rtc.sec, 0, sizeof(struct event));
359 memset(&rtc.per, 0, sizeof(struct event));
360 evtimer_set(&rtc.sec, rtc_fire1, NULL);
361 evtimer_set(&rtc.per, rtc_fireper, (void *)(intptr_t)rtc.vm_id);
362
363 vm_pipe_init(&dev_pipe, mc146818_pipe_dispatch);
364
365 return (0);
366 }
367
368 void
mc146818_stop(void)369 mc146818_stop(void)
370 {
371 evtimer_del(&rtc.per);
372 evtimer_del(&rtc.sec);
373 event_del(&dev_pipe.read_ev);
374 }
375
376 void
mc146818_start(void)377 mc146818_start(void)
378 {
379 evtimer_add(&rtc.sec, &rtc.sec_tv);
380 event_add(&dev_pipe.read_ev, NULL);
381 rtc_reschedule_per();
382 }
383