xref: /illumos-gate/usr/src/test/bhyve-tests/tests/kdev/payload_vrtc_ops.c (revision e00bdde3c6d406f40f53f3025defadc22f7ec31a)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2023 Oxide Computer Company
14  */
15 
16 #include "payload_common.h"
17 #include "payload_utils.h"
18 #include "test_defs.h"
19 
20 /* Convenience definitions for RTC offsets */
21 #define	RTC_SEC		0x00
22 #define	RTC_MIN		0x02
23 #define	RTC_HOUR	0x04
24 #define	RTC_DAY		0x07
25 #define	RTC_MONTH	0x08
26 #define	RTC_YEAR	0x09
27 #define	RTC_CENTURY	0x32
28 
29 #define	RTC_REGA	0x0a
30 #define	RTC_REGB	0x0b
31 #define	RTC_REGC	0x0c
32 #define	RTC_REGD	0x0d
33 
34 #define	REGA_DIVIDER_32K	0x20
35 #define	REGA_DIVIDER_DIS	0x70
36 #define	REGA_PERIOD_512HZ	0x07
37 #define	REGA_PERIOD_128HZ	0x09
38 
39 #define	REGB_HALT		0x80
40 #define	REGB_IE_PERIODIC	0x40
41 #define	REGB_IE_ALARM		0x20
42 #define	REGB_IE_UPDATE		0x10
43 #define	REGB_DATA_BIN		0x04
44 #define	REGB_24HR		0x02
45 #define	REGB_DST		0x01
46 
47 #define	REGC_IRQ		0x80
48 #define	REGC_PERIODIC		0x40
49 #define	REGC_ALARM		0x20
50 #define	REGC_UPDATE		0x10
51 
52 #define	PPM_THRESHOLD	500
53 #define	ABS(x)	((x) < 0 ? -(x) : (x))
54 
55 static uint8_t rtc_last_off = 0xff;
56 
57 static uint8_t
58 rtc_read(uint8_t off)
59 {
60 	if (off != rtc_last_off) {
61 		outb(IOP_RTC_ADDR, off);
62 		rtc_last_off = off;
63 	}
64 
65 	return (inb(IOP_RTC_DATA));
66 }
67 
68 static void
69 rtc_write(uint8_t off, uint8_t data)
70 {
71 	if (off != rtc_last_off) {
72 		outb(IOP_RTC_ADDR, off);
73 		rtc_last_off = off;
74 	}
75 
76 	return (outb(IOP_RTC_DATA, data));
77 }
78 
79 static uint8_t
80 wait_for_flag(uint8_t mask)
81 {
82 	uint8_t regc;
83 
84 	do {
85 		regc = rtc_read(RTC_REGC);
86 	} while ((regc & mask) == 0);
87 
88 	return (regc);
89 }
90 
91 /* Prepare the subordinate PIC to process interrupts from RTC */
92 static void
93 atpic_init(void)
94 {
95 	/* ICW1: INIT | ICW4 */
96 	outb(IOP_ATPIC_SCMD, 0x11);
97 	/* ICW2: vector offset (useless in context) */
98 	outb(IOP_ATPIC_SDATA, 0x20);
99 	/* ICW3: cascade info (ignored) */
100 	outb(IOP_ATPIC_SDATA, 0x00);
101 	/* ICW3: 8086_MODE | AEOI */
102 	outb(IOP_ATPIC_SDATA, 0x03);
103 	/* No masked bits */
104 	outb(IOP_ATPIC_SDATA, 0x00);
105 
106 }
107 
108 /* Poll the subordinate PIC for an IRQ */
109 static uint8_t
110 atpit_poll_for_intr(void)
111 {
112 	uint8_t val = 0;
113 
114 	do {
115 		/* OCW3: POLL */
116 		outb(IOP_ATPIC_SCMD, 0x0c);
117 
118 		val = inb(IOP_ATPIC_SDATA);
119 	} while ((val & 0x80) == 0);
120 
121 	return (val);
122 }
123 
124 static void
125 test_periodic_polling(void)
126 {
127 	/* Halt the RTC to prep for test of periodic timer */
128 	rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
129 	rtc_write(RTC_REGB, REGB_HALT);
130 
131 	/* Clear any pending event flags */
132 	(void) rtc_read(RTC_REGC);
133 
134 	test_msg("testing periodic (polling)");
135 
136 	/* Release divider to run, configuring a 512Hz periodic timer */
137 	rtc_write(RTC_REGA, REGA_DIVIDER_32K | REGA_PERIOD_512HZ);
138 	rtc_write(RTC_REGB, 0);
139 
140 	/* Count periodic firings until the next time update */
141 	uint_t periodic_fire = 0;
142 	uint8_t events = 0;
143 	do {
144 		events = wait_for_flag(REGC_UPDATE | REGC_PERIODIC);
145 
146 		if ((events & REGC_PERIODIC) != 0) {
147 			periodic_fire++;
148 		}
149 	} while ((events & REGC_UPDATE) == 0);
150 
151 	/*
152 	 * In the 500ms between releasing the divider and the first time update,
153 	 * we expect 256 firings of the 512Hz periodic timer.
154 	 */
155 	if (periodic_fire != 256) {
156 		TEST_ABORT("unexpected periodic firing count at 512Hz");
157 	}
158 
159 	/* Change the periodic timer to 128Hz */
160 	rtc_write(RTC_REGA, REGA_DIVIDER_32K | REGA_PERIOD_128HZ);
161 
162 	/* Count periodic firings until the next time update */
163 	periodic_fire = 0;
164 	do {
165 		events = wait_for_flag(REGC_UPDATE | REGC_PERIODIC);
166 
167 		if ((events & REGC_PERIODIC) != 0) {
168 			periodic_fire++;
169 		}
170 	} while ((events & REGC_UPDATE) == 0);
171 
172 	/*
173 	 * With 1s between time updates, we expect 128 firings for the
174 	 * reconfigured 128Hz periodic timer.
175 	 */
176 	if (periodic_fire != 128) {
177 		TEST_ABORT("unexpected periodic firing count at 128Hz");
178 	}
179 }
180 
181 static void
182 test_periodic_interrupts(void)
183 {
184 	/* Halt the RTC to prep for test of periodic timer */
185 	rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
186 	rtc_write(RTC_REGB, REGB_HALT);
187 
188 	/* Clear any pending event flags */
189 	(void) rtc_read(RTC_REGC);
190 
191 	test_msg("testing periodic (interrupts)");
192 
193 	/*
194 	 * The RTC IRQ is routed on line 8, which corresponds to pin 0 on the
195 	 * subordinate PIC.  Initialize it now so we can poll for interrupts.
196 	 */
197 	atpic_init();
198 
199 	/* Release divider to run, configuring a 512Hz periodic timer */
200 	rtc_write(RTC_REGA, REGA_DIVIDER_32K | REGA_PERIOD_512HZ);
201 	/* Enable interrupts for periodic timer and 1Hz update */
202 	rtc_write(RTC_REGB, REGB_IE_PERIODIC | REGB_IE_UPDATE);
203 
204 	/* Count periodic firings until the next time update */
205 	uint_t periodic_fire = 0;
206 	uint8_t events = 0;
207 	do {
208 		const uint8_t irq = atpit_poll_for_intr();
209 		if (irq != 0x80) {
210 			/*
211 			 * RTC is pin 0 on the subordinate PIC chip, so we
212 			 * expect only the interrupt-present bit set
213 			 */
214 			TEST_ABORT("spurious interrupt on PIC");
215 		}
216 
217 		events = rtc_read(RTC_REGC);
218 
219 		/* Since we waited for the interrupt, the flag should be here */
220 		if ((events & REGC_IRQ) == 0) {
221 			TEST_ABORT("missing IRQ flag in regc");
222 		}
223 
224 		if ((events & REGC_PERIODIC) != 0) {
225 			periodic_fire++;
226 		}
227 	} while ((events & REGC_UPDATE) == 0);
228 
229 	/*
230 	 * Like the polling periodic test, we expect 256 firings of the 512Hz
231 	 * timer between the release of the divider and the first update.
232 	 */
233 	if (periodic_fire != 256) {
234 		TEST_ABORT("unexpected periodic firing count at 512Hz");
235 	}
236 
237 	/* Disable periodic configuration from RTC */
238 	rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
239 	rtc_write(RTC_REGB, REGB_HALT);
240 }
241 
242 void
243 start(void)
244 {
245 	/*
246 	 * Initialize RTC to known state:
247 	 * - rega: divider and periodic timer disabled
248 	 * - regb: updates halted, intr disabled, 24hr time, binary fmt, no DST
249 	 * - regc: cleared (by read)
250 	 */
251 	rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
252 	rtc_write(RTC_REGB, REGB_HALT | REGB_DATA_BIN | REGB_24HR);
253 	(void) rtc_read(RTC_REGC);
254 
255 	/* Start at 1970 epoch */
256 	rtc_write(RTC_DAY, 1);
257 	rtc_write(RTC_MONTH, 1);
258 	rtc_write(RTC_YEAR, 70);
259 	rtc_write(RTC_CENTURY, 19);
260 	rtc_write(RTC_HOUR, 0);
261 	rtc_write(RTC_MIN, 0);
262 	rtc_write(RTC_SEC, 0);
263 
264 	uint64_t start, end;
265 	/*
266 	 * After allowing the divider to run, and enabling time updates, we
267 	 * expect a 500ms delay until the first update to the date/time data.
268 	 * Measure this with the TSC, even though we do not have a calibration
269 	 * for its frequency.
270 	 */
271 	rtc_write(RTC_REGA, REGA_DIVIDER_32K);
272 	start = rdtsc();
273 	rtc_write(RTC_REGB, REGB_DATA_BIN | REGB_24HR);
274 
275 	if (rtc_read(RTC_REGC) != 0) {
276 		TEST_ABORT("unexpected flags set in regC");
277 	}
278 
279 	test_msg("waiting for first update");
280 	(void) wait_for_flag(REGC_UPDATE);
281 	end = rdtsc();
282 
283 	const uint64_t tsc_500ms = end - start;
284 	start = end;
285 
286 	/* Expect the clock to read 00:00:01 after the first update */
287 	if (rtc_read(RTC_SEC) != 1) {
288 		TEST_ABORT("did not find 01 in seconds field");
289 	}
290 
291 	/* Wait for another update to pass by */
292 	test_msg("waiting for second update");
293 	(void) wait_for_flag(REGC_UPDATE);
294 	end = rdtsc();
295 
296 	const uint64_t tsc_1s = end - start;
297 
298 	/* Expect the clock to read 00:00:02 after the second update */
299 	if (rtc_read(RTC_SEC) != 2) {
300 		TEST_ABORT("did not find 02 in seconds field");
301 	}
302 
303 	/*
304 	 * Determine ratio between the intervals which should be 500ms and
305 	 * 1000ms long, as measured by the TSC.
306 	 */
307 	int64_t ppm_delta = (int64_t)(tsc_500ms * 2 * 1000000) / tsc_1s;
308 	ppm_delta = ABS(ppm_delta - 1000000);
309 
310 	if (ppm_delta > PPM_THRESHOLD) {
311 		TEST_ABORT("clock update timing outside threshold");
312 	}
313 
314 	/* Put RTC in 12-hr, BCD-formatted mode */
315 	rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
316 	rtc_write(RTC_REGB, REGB_HALT);
317 
318 	/* Set time to 11:59:59, prepping for roll-over into noon */
319 	rtc_write(RTC_HOUR, 0x11);
320 	rtc_write(RTC_MIN, 0x59);
321 	rtc_write(RTC_SEC, 0x59);
322 
323 	/* Release the clock to run again */
324 	rtc_write(RTC_REGA, REGA_DIVIDER_32K);
325 	rtc_write(RTC_REGB, 0);
326 
327 	/* Wait for it to tick over */
328 	test_msg("waiting for noon tick-over");
329 	(void) wait_for_flag(REGC_UPDATE);
330 
331 	if (rtc_read(RTC_SEC) != 0) {
332 		TEST_ABORT("invalid RTC_SEC value");
333 	}
334 	if (rtc_read(RTC_MIN) != 0) {
335 		TEST_ABORT("invalid RTC_MIN value");
336 	}
337 	/* Hour field should now hold 0x12 (BCD noon) | 0x80 (PM flag) */
338 	if (rtc_read(RTC_HOUR) != 0x92) {
339 		TEST_ABORT("invalid RTC_HOUR value");
340 	}
341 
342 	test_periodic_polling();
343 
344 	test_periodic_interrupts();
345 
346 	/*
347 	 * TODO - Add additional tests:
348 	 * - alarm interrupts
349 	 */
350 
351 	/* Happy for now */
352 	test_result_pass();
353 }
354