1/* 2 * Copyright 2017, Data61 3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) 4 * ABN 41 687 119 230. 5 * 6 * This software may be distributed and modified according to the terms of 7 * the BSD 2-Clause license. Note that NO WARRANTY is provided. 8 * See "LICENSE_BSD2.txt" for details. 9 * 10 * @TAG(DATA61_BSD) 11 */ 12 13#include <autoconf.h> 14#include <platsupport/gen_config.h> 15#include <errno.h> 16 17#include <platsupport/timer.h> 18#include <platsupport/plat/hpet.h> 19#include <platsupport/plat/acpi/acpi.h> 20 21#include <stdbool.h> 22#include <stdio.h> 23#include <stdlib.h> 24#include <inttypes.h> 25 26#include <utils/attribute.h> 27#include <utils/util.h> 28#include <utils/fence.h> 29 30/* HPET timer config bits - these can't be changed, but allow us to 31 * find out details of the timer */ 32 33enum { 34 /* 0 is reserved */ 35 /* 0 if edge triggered, 1 if level triggered. */ 36 TN_INT_TYPE_CNF = 1, 37 /* Set to 1 to cause an interrupt when main timer hits comparator for this timer */ 38 TN_INT_ENB_CNF = 2, 39 /* If this bit is 1 you can write a 1 to it for periodic interrupts, 40 * or a 0 for non-periodic interrupts */ 41 TN_TYPE_CNF = 3, 42 /* If this bit is 1, hardware supports periodic mode for this timer */ 43 TN_PER_INT_CAP = 4, 44 /* 1 = timer is 64 bit, 0 = timer is 32 bit */ 45 TN_SIZE_CAP = 5, 46 /* Writing 1 to this bit allows software to directly set a periodic timers accumulator */ 47 TN_VAL_SET_CNF = 6, 48 /* 7 is reserved */ 49 /* Set this bit to force the timer to be a 32-bit timer (only works on a 64-bit timer) */ 50 TN_32MODE_CNF = 8, 51 /* 5 bit wide field (9:13). Specifies routing for IO APIC if using */ 52 TN_INT_ROUTE_CNF = 9, 53 /* Set this bit to force interrupt delivery to the front side bus, don't use the IO APIC */ 54 TN_FSB_EN_CNF = 14, 55 /* If this bit is one, bit TN_FSB_EN_CNF can be set */ 56 TN_FSB_INT_DEL_CAP = 15, 57 /* Bits 16:31 are reserved */ 58 /* Read-only 32-bit field that specifies which routes in the IO APIC this timer can be configured 59 to take */ 60 TN_INT_ROUTE_CAP = 32 61}; 62 63/* General HPET config bits */ 64enum { 65 /* 1 if main counter is running and interrupts are enabled */ 66 ENABLE_CNF = 0, 67 /* 1 if LegacyReplacementRoute is being used */ 68 LEG_RT_CNF = 1 69}; 70 71/* MSI registers - used to configure front side bus delivery of the 72 * HPET interrupt. This allows us to avoid writing an I/O APIC driver. 73 * 74 * For details see section 10.10 "APIC message passing mechanism 75 * and protocol (P6 family,pentium processors)" in "Intel 64 and IA-32 76 * Architectures Software Developers Manual, Volume 3 (3A, 3B & 3C), 77 * System Programming Guide" */ 78/* Message value register layout */ 79enum { 80 /* 0:7 irq_vector */ 81 IRQ_VECTOR = 0, 82 /* 8:10 */ 83 DELIVERY_MODE = 8, 84 /* 11:13 reserved */ 85 LEVEL_TRIGGER = 14, 86 TRIGGER_MODE = 15, 87 /* 16:32 reserved */ 88}; 89 90/* Message address register layout */ 91enum { 92 /* 0:1 reserved */ 93 DESTINATION_MODE = 2, 94 REDIRECTION_HINT = 3, 95 /* 4:11 reserved */ 96 /* 12:19 Destination ID */ 97 DESTINATION_ID = 12, 98 /* 20:31 Fixed value 0x0FEE */ 99 FIXED = 20 100}; 101 102#define CAP_ID_REG 0x0 103#define GENERAL_CONFIG_REG 0x10 104#define MAIN_COUNTER_REG 0xF0 105#define TIMERS_OFFSET 0x100 106 107static inline uint64_t *hpet_get_general_config(void *vaddr) 108{ 109 return (uint64_t*)((uintptr_t)vaddr + GENERAL_CONFIG_REG); 110} 111 112static inline uint64_t *hpet_get_main_counter(void *vaddr) 113{ 114 return (uint64_t*)((uintptr_t)vaddr + MAIN_COUNTER_REG); 115} 116 117static inline uint64_t *hpet_get_cap_id(void *vaddr) 118{ 119 return (uint64_t*)((uintptr_t)vaddr + CAP_ID_REG); 120} 121 122static inline hpet_timer_t *hpet_get_hpet_timer(void *vaddr, unsigned int timer) 123{ 124 return ((hpet_timer_t*)((uintptr_t)vaddr + TIMERS_OFFSET)) + timer; 125} 126 127int hpet_start(const hpet_t *hpet) 128{ 129 130 hpet_timer_t *timer = hpet_get_hpet_timer(hpet->base_addr, 0); 131 /* enable the global timer */ 132 *hpet_get_general_config(hpet->base_addr) |= BIT(ENABLE_CNF); 133 134 /* make sure the comparator is 0 before we turn time0 on*/ 135 timer->comparator = 0llu; 136 COMPILER_MEMORY_RELEASE(); 137 138 /* turn timer0 on */ 139 timer->config |= BIT(TN_INT_ENB_CNF); 140 141 /* ensure the compiler sends the writes to the hardware */ 142 COMPILER_MEMORY_RELEASE(); 143 return 0; 144} 145 146int hpet_stop(const hpet_t *hpet) 147{ 148 hpet_timer_t *timer = hpet_get_hpet_timer(hpet->base_addr, 0); 149 150 /* turn off timer0 */ 151 timer->config &= ~(BIT(TN_INT_ENB_CNF)); 152 153 /* turn the global timer off */ 154 *hpet_get_general_config(hpet->base_addr) &= ~BIT(ENABLE_CNF); 155 156 /* ensure the compiler sends the writes to the hardware */ 157 COMPILER_MEMORY_RELEASE(); 158 return 0; 159} 160 161uint64_t hpet_get_time(const hpet_t *hpet) 162{ 163 uint64_t time; 164 165 do { 166 time = *hpet_get_main_counter(hpet->base_addr); 167 COMPILER_MEMORY_ACQUIRE(); 168 /* race condition on 32-bit systems: check the bottom 32 bits didn't overflow */ 169 } while (CONFIG_WORD_SIZE == 32 && ((uint32_t) (time >> 32llu)) != ((uint32_t *)hpet_get_main_counter(hpet->base_addr))[1]); 170 171 return time * hpet->period_ns; 172} 173 174int hpet_set_timeout(const hpet_t *hpet, uint64_t absolute_ns) 175{ 176 hpet_timer_t *timer = hpet_get_hpet_timer(hpet->base_addr, 0); 177 uint64_t absolute_fs = absolute_ns / hpet->period_ns; 178 179 timer->comparator = absolute_fs; 180 COMPILER_MEMORY_RELEASE(); 181 182 if (hpet_get_time(hpet) > absolute_ns) { 183 return ETIME; 184 } 185 186 return 0; 187} 188 189bool hpet_supports_fsb_delivery(void *vaddr) 190{ 191 hpet_timer_t *timer0 = hpet_get_hpet_timer(vaddr, 0); 192 uint32_t timer0_config_low = timer0->config; 193 return !!(timer0_config_low & BIT(TN_FSB_INT_DEL_CAP)); 194} 195 196uint32_t hpet_ioapic_irq_delivery_mask(void *vaddr) 197{ 198 hpet_timer_t *timer0 = hpet_get_hpet_timer(vaddr, 0); 199 uint32_t irq_mask = timer0->config >> TN_INT_ROUTE_CAP; 200 return irq_mask; 201} 202 203uint32_t hpet_level(void *vaddr) 204{ 205 hpet_timer_t *timer0 = hpet_get_hpet_timer(vaddr, 0); 206 return timer0->config & BIT(TN_INT_TYPE_CNF); 207} 208 209int hpet_init(hpet_t *hpet, hpet_config_t config) 210{ 211 hpet->base_addr = config.vaddr; 212 hpet_timer_t *hpet_timer = hpet_get_hpet_timer(hpet->base_addr, 0); 213 214 uint32_t timer0_config_low = (uint32_t) hpet_timer->config; 215 216 /* check that this timer is edge triggered */ 217 if (timer0_config_low & BIT(TN_INT_TYPE_CNF)) { 218 ZF_LOGE("This driver expects the timer to be edge triggered"); 219 return -1; 220 } 221 222 /* check that this timer is 64 bit */ 223 if (!(timer0_config_low & BIT(TN_SIZE_CAP))) { 224 ZF_LOGE("This driver expects hpet timer0 to be 64bit"); 225 return -1; 226 } 227 228 if (config.ioapic_delivery) { 229 /* Check if this IO/APIC offset is valid */ 230 uint32_t irq_mask = hpet_timer->config >> TN_INT_ROUTE_CAP; 231 if (!(BIT(config.irq) & irq_mask)) { 232 ZF_LOGE("IRQ %d not in the support mask 0x%x", config.irq, irq_mask); 233 return -1; 234 } 235 /* Remove any legacy replacement route so our interrupts go where we want them 236 * NOTE: PIT will cease to function from here on */ 237 *hpet_get_general_config(hpet->base_addr) &= ~BIT(LEG_RT_CNF); 238 /* Make sure we're not deliverying by MSI */ 239 hpet_timer->config &= ~BIT(TN_FSB_EN_CNF); 240 /* Put the IO/APIC offset in (this is called an irq, but in reality it is 241 * an index into whichever IO/APIC the HPET delivers to */ 242 hpet_timer->config &= ~(MASK(5) << TN_INT_ROUTE_CNF); 243 hpet_timer->config |= config.irq << TN_INT_ROUTE_CNF; 244 } else { 245 /* check that this timer supports front size bus delivery */ 246 if (!(timer0_config_low & BIT(TN_FSB_INT_DEL_CAP))) { 247 ZF_LOGE("Requested fsb delivery, but timer0 does not support"); 248 return ENOSYS; 249 } 250 251 /* set timer 0 to delivery interrupts via the front side bus (using MSIs) */ 252 hpet_timer->config |= BIT(TN_FSB_EN_CNF); 253 254 /* set up the message address register and message value register so we receive 255 * MSIs for timer 0*/ 256 hpet_timer->fsb_irr = 257 /* top 32 bits is the message address register */ 258 ((0x0FEEllu << FIXED) << 32llu) 259 /* bottom 32 bits is the message value register */ 260 | config.irq; 261 } 262 COMPILER_MEMORY_RELEASE(); 263 264 /* read the period of the timer (its in femptoseconds) and calculate no of ticks per ns */ 265 uint32_t tick_period_fs = (uint32_t) (*hpet_get_cap_id(hpet->base_addr) >> 32llu); 266 hpet->period_ns = tick_period_fs / 1000000; 267 268 return 0; 269} 270 271int hpet_parse_acpi(acpi_t *acpi, pmem_region_t *region) 272{ 273 if (!acpi || !region) { 274 ZF_LOGE("arguments cannot be NULL"); 275 return EINVAL; 276 } 277 278 acpi_hpet_t *header = (acpi_hpet_t *) acpi_find_region(acpi, ACPI_HPET); 279 if (header == NULL) { 280 ZF_LOGE("Could not find HPET ACPI header"); 281 return ENOSYS; 282 } 283 284 region->base_addr = header->base_address.address; 285 region->length = header->header.length; 286 return 0; 287} 288