xref: /openbsd/usr.sbin/vmd/mc146818.c (revision 4fb9ab68)
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
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
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
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
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
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
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
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
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
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
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
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
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
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