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