xref: /illumos-gate/usr/src/test/bhyve-tests/tests/kdev/payload_vrtc_ops.c (revision 8c4267180173328ebba9487634f0f232387d067f)
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 
40 #define	REGB_HALT		0x80
41 #define	REGB_DATA_BIN		0x04
42 #define	REGB_24HR		0x02
43 #define	REGB_DST		0x01
44 
45 #define	REGC_IRQ		0x80
46 #define	REGC_PERIODIC		0x40
47 #define	REGC_ALARM		0x20
48 #define	REGC_UPDATE		0x10
49 
50 #define	PPM_THRESHOLD	500
51 #define	ABS(x)	((x) < 0 ? -(x) : (x))
52 
53 static uint8_t rtc_last_off = 0xff;
54 
55 static uint8_t
56 rtc_read(uint8_t off)
57 {
58 	if (off != rtc_last_off) {
59 		outb(IOP_RTC_ADDR, off);
60 		rtc_last_off = off;
61 	}
62 
63 	return (inb(IOP_RTC_DATA));
64 }
65 
66 static void
67 rtc_write(uint8_t off, uint8_t data)
68 {
69 	if (off != rtc_last_off) {
70 		outb(IOP_RTC_ADDR, off);
71 		rtc_last_off = off;
72 	}
73 
74 	return (outb(IOP_RTC_DATA, data));
75 }
76 
77 static void
78 wait_for_flag(uint8_t mask)
79 {
80 	uint8_t regc;
81 
82 	do {
83 		regc = rtc_read(RTC_REGC);
84 	} while ((regc & mask) == 0);
85 }
86 
87 void
88 start(void)
89 {
90 	/*
91 	 * Initialize RTC to known state:
92 	 * - rega: divider and periodic timer disabled
93 	 * - regb: updates halted, intr disabled, 24hr time, binary fmt, no DST
94 	 * - regc: cleared (by read)
95 	 */
96 	rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
97 	rtc_write(RTC_REGB, REGB_HALT | REGB_DATA_BIN | REGB_24HR);
98 	(void) rtc_read(RTC_REGC);
99 
100 	/* Start at 1970 epoch */
101 	rtc_write(RTC_DAY, 1);
102 	rtc_write(RTC_MONTH, 1);
103 	rtc_write(RTC_YEAR, 70);
104 	rtc_write(RTC_CENTURY, 19);
105 	rtc_write(RTC_HOUR, 0);
106 	rtc_write(RTC_MIN, 0);
107 	rtc_write(RTC_SEC, 0);
108 
109 	uint64_t start, end;
110 	/*
111 	 * After allowing the divider to run, and enabling time updates, we
112 	 * expect a 500ms delay until the first update to the date/time data.
113 	 * Measure this with the TSC, even though we do not have a calibration
114 	 * for its frequency.
115 	 */
116 	rtc_write(RTC_REGA, REGA_DIVIDER_32K);
117 	start = rdtsc();
118 	rtc_write(RTC_REGB, REGB_DATA_BIN | REGB_24HR);
119 
120 	if (rtc_read(RTC_REGC) != 0) {
121 		TEST_ABORT("unexpected flags set in regC");
122 	}
123 
124 	wait_for_flag(REGC_UPDATE);
125 	end = rdtsc();
126 
127 	const uint64_t tsc_500ms = end - start;
128 	start = end;
129 
130 	/* Expect the clock to read 00:00:01 after the first update */
131 	if (rtc_read(RTC_SEC) != 1) {
132 		TEST_ABORT("did not find 01 in seconds field");
133 	}
134 
135 	/* Wait for another update to pass by */
136 	wait_for_flag(REGC_UPDATE);
137 	end = rdtsc();
138 
139 	const uint64_t tsc_1s = end - start;
140 
141 	/* Expect the clock to read 00:00:02 after the second update */
142 	if (rtc_read(RTC_SEC) != 2) {
143 		TEST_ABORT("did not find 02 in seconds field");
144 	}
145 
146 	/*
147 	 * Determine ratio between the intervals which should be 500ms and
148 	 * 1000ms long, as measured by the TSC.
149 	 */
150 	int64_t ppm_delta = (int64_t)(tsc_500ms * 2 * 1000000) / tsc_1s;
151 	ppm_delta = ABS(ppm_delta - 1000000);
152 
153 	if (ppm_delta > PPM_THRESHOLD) {
154 		TEST_ABORT("clock update timing outside threshold");
155 	}
156 
157 
158 	/* Put RTC in 12-hr, BCD-formatted mode */
159 	rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
160 	rtc_write(RTC_REGB, REGB_HALT);
161 
162 	/* Set time to 11:59:59, prepping for roll-over into noon */
163 	rtc_write(RTC_HOUR, 0x11);
164 	rtc_write(RTC_MIN, 0x59);
165 	rtc_write(RTC_SEC, 0x59);
166 
167 	/* Release the clock to run again */
168 	rtc_write(RTC_REGA, REGA_DIVIDER_32K);
169 	rtc_write(RTC_REGB, 0);
170 
171 	/* Wait for it to tick over */
172 	wait_for_flag(REGC_UPDATE);
173 
174 	if (rtc_read(RTC_SEC) != 0) {
175 		TEST_ABORT("invalid RTC_SEC value");
176 	}
177 	if (rtc_read(RTC_MIN) != 0) {
178 		TEST_ABORT("invalid RTC_MIN value");
179 	}
180 	/* Hour field should now hold 0x12 (BCD noon) | 0x80 (PM flag) */
181 	if (rtc_read(RTC_HOUR) != 0x92) {
182 		TEST_ABORT("invalid RTC_HOUR value");
183 	}
184 
185 	/*
186 	 * TODO - Add additional tests:
187 	 * - periodic interrupts
188 	 * - alarm interrupts
189 	 */
190 
191 	/* Happy for now */
192 	test_result_pass();
193 }
194