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 31static struct hpet_regs *sHPETRegs; 32static volatile struct hpet_timer *sTimer; 33static uint64 sHPETPeriod; 34 35static int hpet_get_priority(); 36static status_t hpet_set_hardware_timer(bigtime_t relativeTimeout); 37static status_t hpet_clear_hardware_timer(); 38static status_t hpet_init(struct kernel_args *args); 39 40 41struct 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 50static int 51hpet_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 63static int32 64hpet_timer_interrupt(void *arg) 65{ 66 //dprintf_no_syslog("HPET timer_interrupt!!!!\n"); 67 return timer_interrupt(); 68} 69 70 71static inline bigtime_t 72hpet_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 81static status_t 82hpet_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 103static status_t 104hpet_clear_hardware_timer() 105{ 106 // Disable timer interrupt 107 sTimer->config &= ~HPET_CONF_TIMER_INT_ENABLE; 108 return B_OK; 109} 110 111 112static status_t 113hpet_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 123static status_t 124hpet_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 141static void 142hpet_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 174static void 175hpet_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 199static status_t 200hpet_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 215static status_t 216hpet_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