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