1 /* 2 * Copyright 2009-2010, Stefano Ceccherini (stefano.ceccherini@gmail.com) 3 * Copyright 2008, Dustin Howett, dustin.howett@gmail.com. All rights reserved. 4 * Distributed under the terms of the MIT License. 5 */ 6 7 #include <debug.h> 8 #include <int.h> 9 #include <smp.h> 10 #include <timer.h> 11 12 #include <arch/int.h> 13 #include <arch/cpu.h> 14 #include <arch/x86/timer.h> 15 #include <arch/x86/arch_hpet.h> 16 17 #include <boot/kernel_args.h> 18 #include <vm/vm.h> 19 20 21 //#define TRACE_HPET 22 #ifdef TRACE_HPET 23 # define TRACE(x...) dprintf("hpet: " x) 24 #else 25 # define TRACE(x...) ; 26 #endif 27 28 #define TEST_HPET 29 30 31 static struct hpet_regs *sHPETRegs; 32 static volatile struct hpet_timer *sTimer; 33 static uint64 sHPETPeriod; 34 35 static int hpet_get_priority(); 36 static status_t hpet_set_hardware_timer(bigtime_t relativeTimeout); 37 static status_t hpet_clear_hardware_timer(); 38 static status_t hpet_init(struct kernel_args *args); 39 40 41 struct timer_info gHPETTimer = { 42 "HPET", 43 &hpet_get_priority, 44 &hpet_set_hardware_timer, 45 &hpet_clear_hardware_timer, 46 &hpet_init 47 }; 48 49 50 static int 51 hpet_get_priority() 52 { 53 // TODO: Fix HPET in SMP mode. 54 if (smp_get_num_cpus() > 1) 55 return 0; 56 57 // HPET timers, being off-chip, are more expensive to setup 58 // than the LAPIC. 59 return 0; 60 } 61 62 63 static int32 64 hpet_timer_interrupt(void *arg) 65 { 66 //dprintf_no_syslog("HPET timer_interrupt!!!!\n"); 67 return timer_interrupt(); 68 } 69 70 71 static inline bigtime_t 72 hpet_convert_timeout(const bigtime_t &relativeTimeout) 73 { 74 return ((relativeTimeout * 1000000000ULL) 75 / sHPETPeriod) + sHPETRegs->u0.counter64; 76 } 77 78 79 #define MIN_TIMEOUT 3000 80 81 static status_t 82 hpet_set_hardware_timer(bigtime_t relativeTimeout) 83 { 84 cpu_status state = disable_interrupts(); 85 86 // enable timer interrupt 87 sTimer->config |= HPET_CONF_TIMER_INT_ENABLE; 88 89 // TODO: 90 if (relativeTimeout < MIN_TIMEOUT) 91 relativeTimeout = MIN_TIMEOUT; 92 93 bigtime_t timerValue = hpet_convert_timeout(relativeTimeout); 94 95 sTimer->u0.comparator64 = timerValue; 96 97 restore_interrupts(state); 98 99 return B_OK; 100 } 101 102 103 static status_t 104 hpet_clear_hardware_timer() 105 { 106 // Disable timer interrupt 107 sTimer->config &= ~HPET_CONF_TIMER_INT_ENABLE; 108 return B_OK; 109 } 110 111 112 static status_t 113 hpet_set_enabled(bool enabled) 114 { 115 if (enabled) 116 sHPETRegs->config |= HPET_CONF_MASK_ENABLED; 117 else 118 sHPETRegs->config &= ~HPET_CONF_MASK_ENABLED; 119 return B_OK; 120 } 121 122 123 static status_t 124 hpet_set_legacy(bool enabled) 125 { 126 if (!HPET_IS_LEGACY_CAPABLE(sHPETRegs)) { 127 dprintf("hpet_init: HPET doesn't support legacy mode. Skipping.\n"); 128 return B_NOT_SUPPORTED; 129 } 130 131 if (enabled) 132 sHPETRegs->config |= HPET_CONF_MASK_LEGACY; 133 else 134 sHPETRegs->config &= ~HPET_CONF_MASK_LEGACY; 135 136 return B_OK; 137 } 138 139 140 #ifdef TRACE_HPET 141 static void 142 hpet_dump_timer(volatile struct hpet_timer *timer) 143 { 144 dprintf("HPET Timer %ld:\n", (timer - sHPETRegs->timer)); 145 146 dprintf("\troutable IRQs: "); 147 uint32 interrupts = (uint32)HPET_GET_CAP_TIMER_ROUTE(timer); 148 for (int i = 0; i < 32; i++) { 149 if (interrupts & (1 << i)) 150 dprintf("%d ", i); 151 } 152 dprintf("\n"); 153 dprintf("\tconfiguration: 0x%" B_PRIx64 "\n", timer->config); 154 dprintf("\tFSB Enabled: %s\n", 155 timer->config & HPET_CONF_TIMER_FSB_ENABLE ? "Yes" : "No"); 156 dprintf("\tInterrupt Enabled: %s\n", 157 timer->config & HPET_CONF_TIMER_INT_ENABLE ? "Yes" : "No"); 158 dprintf("\tTimer type: %s\n", 159 timer->config & HPET_CONF_TIMER_TYPE ? "Periodic" : "OneShot"); 160 dprintf("\tInterrupt Type: %s\n", 161 timer->config & HPET_CONF_TIMER_INT_TYPE ? "Level" : "Edge"); 162 163 dprintf("\tconfigured IRQ: %" B_PRId64 "\n", 164 HPET_GET_CONF_TIMER_INT_ROUTE(timer)); 165 166 if (timer->config & HPET_CONF_TIMER_FSB_ENABLE) { 167 dprintf("\tfsb_route[0]: 0x%" B_PRIx64 "\n", timer->fsb_route[0]); 168 dprintf("\tfsb_route[1]: 0x%" B_PRIx64 "\n", timer->fsb_route[1]); 169 } 170 } 171 #endif 172 173 174 static void 175 hpet_init_timer(volatile struct hpet_timer *timer) 176 { 177 sTimer = timer; 178 179 uint32 interrupt = 0; 180 181 sTimer->config |= (interrupt << HPET_CONF_TIMER_INT_ROUTE_SHIFT) 182 & HPET_CONF_TIMER_INT_ROUTE_MASK; 183 184 // Non-periodic mode, edge triggered 185 sTimer->config &= ~(HPET_CONF_TIMER_TYPE | HPET_CONF_TIMER_INT_TYPE); 186 187 sTimer->config &= ~HPET_CONF_TIMER_FSB_ENABLE; 188 sTimer->config &= ~HPET_CONF_TIMER_32MODE; 189 190 // Enable timer 191 sTimer->config |= HPET_CONF_TIMER_INT_ENABLE; 192 193 #ifdef TRACE_HPET 194 hpet_dump_timer(sTimer); 195 #endif 196 } 197 198 199 static status_t 200 hpet_test() 201 { 202 uint64 initialValue = sHPETRegs->u0.counter64; 203 spin(10); 204 uint64 finalValue = sHPETRegs->u0.counter64; 205 206 if (initialValue == finalValue) { 207 dprintf("hpet_test: counter does not increment\n"); 208 return B_ERROR; 209 } 210 211 return B_OK; 212 } 213 214 215 static status_t 216 hpet_init(struct kernel_args *args) 217 { 218 /* hpet_acpi_probe() through a similar "scan spots" table 219 to that of smp.cpp. 220 Seems to be the most elegant solution right now. */ 221 if (args->arch_args.hpet == NULL) 222 return B_ERROR; 223 224 if (sHPETRegs == NULL) { 225 sHPETRegs = (struct hpet_regs *)args->arch_args.hpet.Pointer(); 226 if (vm_map_physical_memory(B_SYSTEM_TEAM, "hpet", 227 (void **)&sHPETRegs, B_EXACT_ADDRESS, B_PAGE_SIZE, 228 B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, 229 (phys_addr_t)args->arch_args.hpet_phys, true) < B_OK) { 230 // Would it be better to panic here? 231 dprintf("hpet_init: Failed to map memory for the HPET registers."); 232 return B_ERROR; 233 } 234 } 235 236 sHPETPeriod = HPET_GET_PERIOD(sHPETRegs); 237 238 TRACE("hpet_init: HPET is at %p.\n\tVendor ID: %llx, rev: %llx, period: %" B_PRId64 "\n", 239 sHPETRegs, HPET_GET_VENDOR_ID(sHPETRegs), HPET_GET_REVID(sHPETRegs), 240 sHPETPeriod); 241 242 status_t status = hpet_set_enabled(false); 243 if (status != B_OK) 244 return status; 245 246 status = hpet_set_legacy(true); 247 if (status != B_OK) 248 return status; 249 250 uint32 numTimers = HPET_GET_NUM_TIMERS(sHPETRegs) + 1; 251 252 TRACE("hpet_init: HPET supports %" B_PRIu32 " timers, and is %s bits wide.\n", 253 numTimers, HPET_IS_64BIT(sHPETRegs) ? "64" : "32"); 254 255 TRACE("hpet_init: configuration: 0x%" B_PRIx64 ", timer_interrupts: 0x%" B_PRIx64 "\n", 256 sHPETRegs->config, sHPETRegs->interrupt_status); 257 258 if (numTimers < 3) { 259 dprintf("hpet_init: HPET does not have at least 3 timers. Skipping.\n"); 260 return B_ERROR; 261 } 262 263 #ifdef TRACE_HPET 264 for (uint32 c = 0; c < numTimers; c++) 265 hpet_dump_timer(&sHPETRegs->timer[c]); 266 #endif 267 268 hpet_init_timer(&sHPETRegs->timer[0]); 269 270 status = hpet_set_enabled(true); 271 if (status != B_OK) 272 return status; 273 274 #ifdef TEST_HPET 275 status = hpet_test(); 276 if (status != B_OK) 277 return status; 278 #endif 279 280 int32 configuredIRQ = HPET_GET_CONF_TIMER_INT_ROUTE(sTimer); 281 282 install_io_interrupt_handler(configuredIRQ, &hpet_timer_interrupt, 283 NULL, B_NO_LOCK_VECTOR); 284 285 return status; 286 } 287 288