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 #define	IOP_RTC_ADDR	0x70
21 #define	IOP_RTC_DATA	0x71
22 
23 /* Convenience definitions for RTC offsets */
24 #define	RTC_SEC		0x00
25 #define	RTC_MIN		0x02
26 #define	RTC_HOUR	0x04
27 #define	RTC_DAY		0x07
28 #define	RTC_MONTH	0x08
29 #define	RTC_YEAR	0x09
30 #define	RTC_CENTURY	0x32
31 
32 #define	RTC_REGA	0x0a
33 #define	RTC_REGB	0x0b
34 #define	RTC_REGC	0x0c
35 #define	RTC_REGD	0x0d
36 
37 #define	REGA_DIVIDER_32K	0x20
38 #define	REGA_DIVIDER_DIS	0x70
39 #define	REGA_PERIOD_512HZ	0x07
40 #define	REGA_PERIOD_128HZ	0x09
41 
42 #define	REGB_HALT		0x80
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 void
92 start(void)
93 {
94 	/*
95 	 * Initialize RTC to known state:
96 	 * - rega: divider and periodic timer disabled
97 	 * - regb: updates halted, intr disabled, 24hr time, binary fmt, no DST
98 	 * - regc: cleared (by read)
99 	 */
100 	rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
101 	rtc_write(RTC_REGB, REGB_HALT | REGB_DATA_BIN | REGB_24HR);
102 	(void) rtc_read(RTC_REGC);
103 
104 	/* Start at 1970 epoch */
105 	rtc_write(RTC_DAY, 1);
106 	rtc_write(RTC_MONTH, 1);
107 	rtc_write(RTC_YEAR, 70);
108 	rtc_write(RTC_CENTURY, 19);
109 	rtc_write(RTC_HOUR, 0);
110 	rtc_write(RTC_MIN, 0);
111 	rtc_write(RTC_SEC, 0);
112 
113 	uint64_t start, end;
114 	/*
115 	 * After allowing the divider to run, and enabling time updates, we
116 	 * expect a 500ms delay until the first update to the date/time data.
117 	 * Measure this with the TSC, even though we do not have a calibration
118 	 * for its frequency.
119 	 */
120 	rtc_write(RTC_REGA, REGA_DIVIDER_32K);
121 	start = rdtsc();
122 	rtc_write(RTC_REGB, REGB_DATA_BIN | REGB_24HR);
123 
124 	if (rtc_read(RTC_REGC) != 0) {
125 		TEST_ABORT("unexpected flags set in regC");
126 	}
127 
128 	(void) wait_for_flag(REGC_UPDATE);
129 	end = rdtsc();
130 
131 	const uint64_t tsc_500ms = end - start;
132 	start = end;
133 
134 	/* Expect the clock to read 00:00:01 after the first update */
135 	if (rtc_read(RTC_SEC) != 1) {
136 		TEST_ABORT("did not find 01 in seconds field");
137 	}
138 
139 	/* Wait for another update to pass by */
140 	(void) wait_for_flag(REGC_UPDATE);
141 	end = rdtsc();
142 
143 	const uint64_t tsc_1s = end - start;
144 
145 	/* Expect the clock to read 00:00:02 after the second update */
146 	if (rtc_read(RTC_SEC) != 2) {
147 		TEST_ABORT("did not find 02 in seconds field");
148 	}
149 
150 	/*
151 	 * Determine ratio between the intervals which should be 500ms and
152 	 * 1000ms long, as measured by the TSC.
153 	 */
154 	int64_t ppm_delta = (int64_t)(tsc_500ms * 2 * 1000000) / tsc_1s;
155 	ppm_delta = ABS(ppm_delta - 1000000);
156 
157 	if (ppm_delta > PPM_THRESHOLD) {
158 		TEST_ABORT("clock update timing outside threshold");
159 	}
160 
161 	/* Put RTC in 12-hr, BCD-formatted mode */
162 	rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
163 	rtc_write(RTC_REGB, REGB_HALT);
164 
165 	/* Set time to 11:59:59, prepping for roll-over into noon */
166 	rtc_write(RTC_HOUR, 0x11);
167 	rtc_write(RTC_MIN, 0x59);
168 	rtc_write(RTC_SEC, 0x59);
169 
170 	/* Release the clock to run again */
171 	rtc_write(RTC_REGA, REGA_DIVIDER_32K);
172 	rtc_write(RTC_REGB, 0);
173 
174 	/* Wait for it to tick over */
175 	(void) wait_for_flag(REGC_UPDATE);
176 
177 	if (rtc_read(RTC_SEC) != 0) {
178 		TEST_ABORT("invalid RTC_SEC value");
179 	}
180 	if (rtc_read(RTC_MIN) != 0) {
181 		TEST_ABORT("invalid RTC_MIN value");
182 	}
183 	/* Hour field should now hold 0x12 (BCD noon) | 0x80 (PM flag) */
184 	if (rtc_read(RTC_HOUR) != 0x92) {
185 		TEST_ABORT("invalid RTC_HOUR value");
186 	}
187 
188 	/* Halt the RTC once again to prep for test of periodic timer */
189 	rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
190 	rtc_write(RTC_REGB, REGB_HALT);
191 
192 	/* Clear any pending event flags */
193 	(void) rtc_read(RTC_REGC);
194 
195 	/* Release divider to run, configuring a 512Hz periodic timer */
196 	rtc_write(RTC_REGA, REGA_DIVIDER_32K | REGA_PERIOD_512HZ);
197 	rtc_write(RTC_REGB, 0);
198 
199 	/* Count periodic firings until the next time update */
200 	uint_t periodic_fire = 0;
201 	uint8_t events = 0;
202 	do {
203 		events = wait_for_flag(REGC_UPDATE | REGC_PERIODIC);
204 
205 		if ((events & REGC_PERIODIC) != 0) {
206 			periodic_fire++;
207 		}
208 	} while ((events & REGC_UPDATE) == 0);
209 
210 	/*
211 	 * In the 500ms between releasing the divider and the first time update,
212 	 * we expect 256 firings of the 512Hz periodic timer.
213 	 */
214 	if (periodic_fire != 256) {
215 		TEST_ABORT("unexpected periodic firing count at 512Hz");
216 	}
217 
218 	/* Change the periodic timer to 128Hz */
219 	rtc_write(RTC_REGA, REGA_DIVIDER_32K | REGA_PERIOD_128HZ);
220 
221 	/* Count periodic firings until the next time update */
222 	periodic_fire = 0;
223 	do {
224 		events = wait_for_flag(REGC_UPDATE | REGC_PERIODIC);
225 
226 		if ((events & REGC_PERIODIC) != 0) {
227 			periodic_fire++;
228 		}
229 	} while ((events & REGC_UPDATE) == 0);
230 
231 	/*
232 	 * With 1s between time updates, we expect 128 firings for the
233 	 * reconfigured 128Hz periodic timer.
234 	 */
235 	if (periodic_fire != 128) {
236 		TEST_ABORT("unexpected periodic firing count at 128Hz");
237 	}
238 
239 	/*
240 	 * TODO - Add additional tests:
241 	 * - alarm interrupts
242 	 */
243 
244 	/* Happy for now */
245 	test_result_pass();
246 }
247