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