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 2020 Joyent, Inc. 14 */ 15 16 #include <sys/pit.h> 17 #include <sys/tsc.h> 18 #include <sys/archsystm.h> 19 #include <sys/prom_debug.h> 20 21 extern uint64_t freq_tsc_pit(uint32_t *); 22 23 /* 24 * Traditionally, the PIT has been used to calibrate both the TSC and the 25 * APIC. As we transition to supporting alternate TSC calibration sources 26 * and using the TSC to calibrate the APIC, we may still want (for diagnostic 27 * purposes) to know what would have happened if we had used the PIT 28 * instead. As a result, if we are using an alternate calibration source 29 * we will still measure the frequency using the PIT and save the result in 30 * pit_tsc_hz for use by the APIC (to similarly save the timings using the 31 * PIT). 32 * 33 * A wrinkle in this is that some systems no longer have a functioning PIT. 34 * In these instances, we simply have no way to provide the 'what if the PIT 35 * was used' values. When we try to use the PIT, we first perform a small 36 * test to see if it appears to be working (i.e. will it count down). If 37 * it does not, we set pit_is_broken to let the APIC calibration code that 38 * it shouldn't attempt to get PIC timings. 39 * 40 * While the systems without a functioning PIT don't seem to experience 41 * any undesirable behavior when attempting to use the non-functional/not 42 * present PIT (i.e. they don't lock up or otherwise act funny -- the counter 43 * values that are read just never change), we still allow pit_is_broken to be 44 * set in /etc/system to inform the system to avoid attempting to use the PIT 45 * at all. 46 * 47 * In the future, we could remove these transitional bits once we have more 48 * history built up using the alternative calibration sources. 49 */ 50 uint64_t pit_tsc_hz; 51 int pit_is_broken; 52 53 /* 54 * On all of the systems seen so far without functioning PITs, it appears 55 * that they always just return the values written to the PITCTR0_PORT (or 56 * more specifically when they've been programmed to start counting down from 57 * 0xFFFF, they always return 0xFFFF no matter how little/much time has 58 * elapsed). 59 * 60 * Since we have no better way to know if the PIT is broken, we use this 61 * behavior to sanity check the PIT. We program the PIT to count down from 62 * 0xFFFF and wait an amount of time and re-read the result. While we cannot 63 * rely on the TSC frequency being known at this point, we do know that 64 * we are almost certainly never going to see a TSC frequency below 1GHz 65 * on any supported system. 66 * 67 * As such, we (somewhat) arbitrarily pick 400,000 TSC ticks as the amount 68 * of time we wait before re-reading the PIT counter. On a 1GHz machine, 69 * 1 PIT tick would correspond to approximately 838 TSC ticks, therefore 70 * waiting 400,000 TSC ticks should correspond to approx 477 PIT ticks. 71 * On a (currently) theoritical 100GHz machine, 400,000 TSC ticks would still 72 * correspond to approx 4-5 PIT ticks, so this seems a reasonably safe value. 73 */ 74 #define TSC_MIN_TICKS 400000ULL 75 76 static boolean_t 77 pit_sanity_check(void) 78 { 79 uint64_t tsc_now, tsc_end; 80 ulong_t flags; 81 uint16_t pit_count; 82 83 flags = clear_int_flag(); 84 85 tsc_now = tsc_read(); 86 tsc_end = tsc_now + TSC_MIN_TICKS; 87 88 /* 89 * Put the PIT in mode 0, "Interrupt On Terminal Count": 90 */ 91 outb(PITCTL_PORT, PIT_C0 | PIT_LOADMODE | PIT_ENDSIGMODE); 92 93 outb(PITCTR0_PORT, 0xFF); 94 outb(PITCTR0_PORT, 0xFF); 95 96 while (tsc_now < tsc_end) 97 tsc_now = tsc_read(); 98 99 /* 100 * Latch the counter value and status for counter 0 with the 101 * readback command. 102 */ 103 outb(PITCTL_PORT, PIT_READBACK | PIT_READBACKC0); 104 105 /* 106 * In readback mode, reading from the counter port produces a 107 * status byte, the low counter byte, and finally the high counter byte. 108 * 109 * We ignore the status byte -- as noted above, we've delayed for an 110 * amount of time that should allow the counter to count off at least 111 * 4-5 ticks (and more realistically at least a hundred), so we just 112 * want to see if the count has changed at all. 113 */ 114 (void) inb(PITCTR0_PORT); 115 pit_count = inb(PITCTR0_PORT); 116 pit_count |= inb(PITCTR0_PORT) << 8; 117 118 restore_int_flag(flags); 119 120 if (pit_count == 0xFFFF) { 121 pit_is_broken = 1; 122 return (B_FALSE); 123 } 124 125 return (B_TRUE); 126 } 127 128 static boolean_t 129 tsc_calibrate_pit(uint64_t *freqp) 130 { 131 uint64_t processor_clks; 132 ulong_t flags; 133 uint32_t pit_counter; 134 135 if (pit_is_broken) 136 return (B_FALSE); 137 138 if (!pit_sanity_check()) 139 return (B_FALSE); 140 141 /* 142 * freq_tsc_pit() is a hand-rolled assembly function that returns 143 * the number of TSC ticks and sets pit_counter to the number 144 * of corresponding PIT ticks in the same time period. 145 */ 146 flags = clear_int_flag(); 147 processor_clks = freq_tsc_pit(&pit_counter); 148 restore_int_flag(flags); 149 150 if (pit_counter == 0 || processor_clks == 0 || 151 processor_clks > (((uint64_t)-1) / PIT_HZ)) { 152 return (B_FALSE); 153 } 154 155 *freqp = pit_tsc_hz = ((uint64_t)PIT_HZ * processor_clks) / pit_counter; 156 return (B_TRUE); 157 } 158 159 /* 160 * Typically any source besides the PIT is going to provide better 161 * results, so a low preference is assigned to the PIT so it is tried last. 162 */ 163 static tsc_calibrate_t tsc_calibration_pit = { 164 .tscc_source = "PIT", 165 .tscc_preference = 10, 166 .tscc_calibrate = tsc_calibrate_pit, 167 }; 168 TSC_CALIBRATION_SOURCE(tsc_calibration_pit); 169