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