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