1// Copyright 2016 The Fuchsia Authors 2// 3// Use of this source code is governed by a MIT-style 4// license that can be found in the LICENSE file or at 5// https://opensource.org/licenses/MIT 6 7#include <platform/pc/hpet.h> 8 9#include <bits.h> 10#include <err.h> 11#include <fbl/algorithm.h> 12#include <kernel/auto_lock.h> 13#include <kernel/spinlock.h> 14#include <lk/init.h> 15#include <vm/vm_aspace.h> 16#include <zircon/types.h> 17 18struct hpet_timer_registers { 19 volatile uint64_t conf_caps; 20 volatile uint64_t comparator_value; 21 volatile uint64_t fsb_int_route; 22 uint8_t _reserved[8]; 23} __PACKED; 24 25struct hpet_registers { 26 volatile uint64_t general_caps; 27 uint8_t _reserved0[8]; 28 volatile uint64_t general_config; 29 uint8_t _reserved1[8]; 30 volatile uint64_t general_int_status; 31 uint8_t _reserved2[0xf0 - 0x28]; 32 volatile uint64_t main_counter_value; 33 uint8_t _reserved3[8]; 34 struct hpet_timer_registers timers[]; 35} __PACKED; 36 37static spin_lock_t lock = SPIN_LOCK_INITIAL_VALUE; 38 39static struct acpi_hpet_descriptor hpet_desc; 40static bool hpet_present = false; 41static struct hpet_registers* hpet_regs; 42uint64_t _hpet_ticks_per_ms; 43static uint64_t tick_period_in_fs; 44static uint8_t num_timers; 45 46/* Minimum number of ticks ahead a oneshot timer needs to be. Targetted 47 * to be 100ns */ 48static uint64_t min_ticks_ahead; 49 50#define MAX_PERIOD_IN_FS 0x05F5E100ULL 51 52/* Bit masks for the general_config register */ 53#define GEN_CONF_EN 1 54 55/* Bit masks for the per-time conf_caps register */ 56#define TIMER_CONF_LEVEL_TRIGGERED (1ULL << 1) 57#define TIMER_CONF_INT_EN (1ULL << 2) 58#define TIMER_CONF_PERIODIC (1ULL << 3) 59#define TIMER_CAP_PERIODIC(reg) BIT_SET(reg, 4) 60#define TIMER_CAP_64BIT(reg) BIT_SET(reg, 5) 61#define TIMER_CONF_PERIODIC_SET_COUNT (1ULL << 6) 62#define TIMER_CONF_IRQ(n) ((uint64_t)((n) & (0x1f)) << 9) 63#define TIMER_CAP_IRQS(reg) static_cast<uint32_t>(BITS_SHIFT(reg, 63, 32)) 64 65static void hpet_init(uint level) { 66 zx_status_t status = platform_find_hpet(&hpet_desc); 67 if (status != ZX_OK) { 68 return; 69 } 70 71 if (hpet_desc.port_io) { 72 return; 73 } 74 75 zx_status_t res = VmAspace::kernel_aspace()->AllocPhysical( 76 "hpet", 77 PAGE_SIZE, /* size */ 78 (void**)&hpet_regs, /* returned virtual address */ 79 PAGE_SIZE_SHIFT, /* alignment log2 */ 80 (paddr_t)hpet_desc.address, /* physical address */ 81 0, /* vmm flags */ 82 ARCH_MMU_FLAG_UNCACHED_DEVICE | ARCH_MMU_FLAG_PERM_READ | 83 ARCH_MMU_FLAG_PERM_WRITE); 84 if (res != ZX_OK) { 85 return; 86 } 87 88 bool has_64bit_count = BIT_SET(hpet_regs->general_caps, 13); 89 tick_period_in_fs = hpet_regs->general_caps >> 32; 90 if (tick_period_in_fs == 0 || tick_period_in_fs > MAX_PERIOD_IN_FS) { 91 goto fail; 92 } 93 94 /* We only support HPETs that are 64-bit and have at least two timers */ 95 num_timers = static_cast<uint8_t>(BITS_SHIFT(hpet_regs->general_caps, 12, 8) + 1); 96 if (!has_64bit_count || num_timers < 2) { 97 goto fail; 98 } 99 100 /* Make sure all timers have interrupts disabled */ 101 for (uint8_t i = 0; i < num_timers; ++i) { 102 hpet_regs->timers[i].conf_caps &= ~TIMER_CONF_INT_EN; 103 } 104 105 _hpet_ticks_per_ms = 1000000000000ULL / tick_period_in_fs; 106 min_ticks_ahead = 100000000ULL / tick_period_in_fs; 107 hpet_present = true; 108 return; 109 110fail: 111 VmAspace::kernel_aspace()->FreeRegion(reinterpret_cast<vaddr_t>(hpet_regs)); 112 hpet_regs = nullptr; 113 num_timers = 0; 114} 115/* Begin running after ACPI tables are up */ 116LK_INIT_HOOK(hpet, hpet_init, LK_INIT_LEVEL_VM + 2); 117 118zx_status_t hpet_timer_disable(uint n) { 119 if (unlikely(n >= num_timers)) { 120 return ZX_ERR_NOT_SUPPORTED; 121 } 122 123 AutoSpinLockNoIrqSave guard(&lock); 124 hpet_regs->timers[n].conf_caps &= ~TIMER_CONF_INT_EN; 125 126 return ZX_OK; 127} 128 129uint64_t hpet_get_value(void) { 130 uint64_t v = hpet_regs->main_counter_value; 131 uint64_t v2 = hpet_regs->main_counter_value; 132 /* Even though the specification says it should not be necessary to read 133 * multiple times, we have observed that QEMU converts the 64-bit 134 * memory access in to two 32-bit accesses, resulting in bad reads. QEMU 135 * reads the low 32-bits first, so the result is a large jump when it 136 * wraps 32 bits. To work around this, we return the lesser of two reads. 137 */ 138 return fbl::min(v, v2); 139} 140 141zx_status_t hpet_set_value(uint64_t v) { 142 AutoSpinLockNoIrqSave guard(&lock); 143 144 if (hpet_regs->general_config & GEN_CONF_EN) { 145 return ZX_ERR_BAD_STATE; 146 } 147 148 hpet_regs->main_counter_value = v; 149 return ZX_OK; 150} 151 152zx_status_t hpet_timer_configure_irq(uint n, uint irq) { 153 if (unlikely(n >= num_timers)) { 154 return ZX_ERR_NOT_SUPPORTED; 155 } 156 157 AutoSpinLockNoIrqSave guard(&lock); 158 159 uint32_t irq_bitmap = TIMER_CAP_IRQS(hpet_regs->timers[n].conf_caps); 160 if (irq >= 32 || !BIT_SET(irq_bitmap, irq)) { 161 return ZX_ERR_NOT_SUPPORTED; 162 } 163 164 uint64_t conf = hpet_regs->timers[n].conf_caps; 165 conf &= ~TIMER_CONF_IRQ(~0ULL); 166 conf |= TIMER_CONF_IRQ(irq); 167 hpet_regs->timers[n].conf_caps = conf; 168 169 return ZX_OK; 170} 171 172zx_status_t hpet_timer_set_oneshot(uint n, uint64_t deadline) { 173 if (unlikely(n >= num_timers)) { 174 return ZX_ERR_NOT_SUPPORTED; 175 } 176 177 AutoSpinLockNoIrqSave guard(&lock); 178 179 uint64_t difference = deadline - hpet_get_value(); 180 if (unlikely(difference > (1ULL >> 63))) { 181 /* Either this is a very long timer, or we wrapped around */ 182 return ZX_ERR_INVALID_ARGS; 183 } 184 if (unlikely(difference < min_ticks_ahead)) { 185 return ZX_ERR_INVALID_ARGS; 186 } 187 188 hpet_regs->timers[n].conf_caps &= ~(TIMER_CONF_PERIODIC | 189 TIMER_CONF_PERIODIC_SET_COUNT); 190 hpet_regs->timers[n].comparator_value = deadline; 191 hpet_regs->timers[n].conf_caps |= TIMER_CONF_INT_EN; 192 193 return ZX_OK; 194} 195 196zx_status_t hpet_timer_set_periodic(uint n, uint64_t period) { 197 if (unlikely(n >= num_timers)) { 198 return ZX_ERR_NOT_SUPPORTED; 199 } 200 201 AutoSpinLockNoIrqSave guard(&lock); 202 203 if (!TIMER_CAP_PERIODIC(hpet_regs->timers[n].conf_caps)) { 204 return ZX_ERR_NOT_SUPPORTED; 205 } 206 207 /* It's unsafe to set a periodic timer while the hpet is running or the 208 * main counter value is not 0. */ 209 if ((hpet_regs->general_config & GEN_CONF_EN) || 210 hpet_regs->main_counter_value != 0ULL) { 211 return ZX_ERR_BAD_STATE; 212 } 213 214 hpet_regs->timers[n].conf_caps |= TIMER_CONF_PERIODIC | 215 TIMER_CONF_PERIODIC_SET_COUNT; 216 hpet_regs->timers[n].comparator_value = period; 217 hpet_regs->timers[n].conf_caps |= TIMER_CONF_INT_EN; 218 219 return ZX_OK; 220} 221 222bool hpet_is_present(void) { 223 return hpet_present; 224} 225 226void hpet_enable(void) { 227 DEBUG_ASSERT(hpet_is_present()); 228 229 AutoSpinLockNoIrqSave guard(&lock); 230 hpet_regs->general_config |= GEN_CONF_EN; 231} 232 233void hpet_disable(void) { 234 DEBUG_ASSERT(hpet_is_present()); 235 236 AutoSpinLockNoIrqSave guard(&lock); 237 hpet_regs->general_config &= ~GEN_CONF_EN; 238} 239 240/* Blocks for the requested number of milliseconds. 241 * For use in calibration */ 242void hpet_wait_ms(uint16_t ms) { 243 uint64_t init_timer_value = hpet_regs->main_counter_value; 244 uint64_t target = (uint64_t)ms * _hpet_ticks_per_ms; 245 while (hpet_regs->main_counter_value - init_timer_value <= target) 246 ; 247} 248