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
rtc_read(uint8_t off)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
rtc_write(uint8_t off,uint8_t data)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
wait_for_flag(uint8_t mask)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
atpic_init(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
atpit_poll_for_intr(void)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
test_periodic_polling(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
test_periodic_interrupts(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
start(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