blob: a54b697f5285dd44ac18d99a0cbb5469303b8e48 [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <autoconf.h>
#include <platsupport/gen_config.h>
#include <errno.h>
#include <platsupport/timer.h>
#include <platsupport/plat/hpet.h>
#include <platsupport/plat/acpi/acpi.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <utils/attribute.h>
#include <utils/util.h>
#include <utils/fence.h>
/* HPET timer config bits - these can't be changed, but allow us to
* find out details of the timer */
enum {
/* 0 is reserved */
/* 0 if edge triggered, 1 if level triggered. */
TN_INT_TYPE_CNF = 1,
/* Set to 1 to cause an interrupt when main timer hits comparator for this timer */
TN_INT_ENB_CNF = 2,
/* If this bit is 1 you can write a 1 to it for periodic interrupts,
* or a 0 for non-periodic interrupts */
TN_TYPE_CNF = 3,
/* If this bit is 1, hardware supports periodic mode for this timer */
TN_PER_INT_CAP = 4,
/* 1 = timer is 64 bit, 0 = timer is 32 bit */
TN_SIZE_CAP = 5,
/* Writing 1 to this bit allows software to directly set a periodic timers accumulator */
TN_VAL_SET_CNF = 6,
/* 7 is reserved */
/* Set this bit to force the timer to be a 32-bit timer (only works on a 64-bit timer) */
TN_32MODE_CNF = 8,
/* 5 bit wide field (9:13). Specifies routing for IO APIC if using */
TN_INT_ROUTE_CNF = 9,
/* Set this bit to force interrupt delivery to the front side bus, don't use the IO APIC */
TN_FSB_EN_CNF = 14,
/* If this bit is one, bit TN_FSB_EN_CNF can be set */
TN_FSB_INT_DEL_CAP = 15,
/* Bits 16:31 are reserved */
/* Read-only 32-bit field that specifies which routes in the IO APIC this timer can be configured
to take */
TN_INT_ROUTE_CAP = 32
};
/* General HPET config bits */
enum {
/* 1 if main counter is running and interrupts are enabled */
ENABLE_CNF = 0,
/* 1 if LegacyReplacementRoute is being used */
LEG_RT_CNF = 1
};
/* MSI registers - used to configure front side bus delivery of the
* HPET interrupt. This allows us to avoid writing an I/O APIC driver.
*
* For details see section 10.10 "APIC message passing mechanism
* and protocol (P6 family,pentium processors)" in "Intel 64 and IA-32
* Architectures Software Developers Manual, Volume 3 (3A, 3B & 3C),
* System Programming Guide" */
/* Message value register layout */
enum {
/* 0:7 irq_vector */
IRQ_VECTOR = 0,
/* 8:10 */
DELIVERY_MODE = 8,
/* 11:13 reserved */
LEVEL_TRIGGER = 14,
TRIGGER_MODE = 15,
/* 16:32 reserved */
};
/* Message address register layout */
enum {
/* 0:1 reserved */
DESTINATION_MODE = 2,
REDIRECTION_HINT = 3,
/* 4:11 reserved */
/* 12:19 Destination ID */
DESTINATION_ID = 12,
/* 20:31 Fixed value 0x0FEE */
FIXED = 20
};
#define CAP_ID_REG 0x0
#define GENERAL_CONFIG_REG 0x10
#define MAIN_COUNTER_REG 0xF0
#define TIMERS_OFFSET 0x100
static inline uint64_t *hpet_get_general_config(void *vaddr)
{
return (uint64_t *)((uintptr_t)vaddr + GENERAL_CONFIG_REG);
}
static inline uint64_t *hpet_get_main_counter(void *vaddr)
{
return (uint64_t *)((uintptr_t)vaddr + MAIN_COUNTER_REG);
}
static inline uint64_t *hpet_get_cap_id(void *vaddr)
{
return (uint64_t *)((uintptr_t)vaddr + CAP_ID_REG);
}
static inline hpet_timer_t *hpet_get_hpet_timer(void *vaddr, unsigned int timer)
{
return ((hpet_timer_t *)((uintptr_t)vaddr + TIMERS_OFFSET)) + timer;
}
int hpet_start(const hpet_t *hpet)
{
hpet_timer_t *timer = hpet_get_hpet_timer(hpet->base_addr, 0);
/* enable the global timer */
/* volatile is used here to try and prevent the compiler from satisfying this
bitwise operation via byte only reads and writes. */
volatile uint64_t *general_config = hpet_get_general_config(hpet->base_addr);
*general_config |= BIT(ENABLE_CNF);
/* make sure the comparator is 0 before we turn time0 on*/
timer->comparator = 0llu;
COMPILER_MEMORY_RELEASE();
/* turn timer0 on */
timer->config |= BIT(TN_INT_ENB_CNF);
/* ensure the compiler sends the writes to the hardware */
COMPILER_MEMORY_RELEASE();
return 0;
}
int hpet_stop(const hpet_t *hpet)
{
hpet_timer_t *timer = hpet_get_hpet_timer(hpet->base_addr, 0);
/* turn off timer0 */
timer->config &= ~(BIT(TN_INT_ENB_CNF));
/* turn the global timer off */
*hpet_get_general_config(hpet->base_addr) &= ~BIT(ENABLE_CNF);
/* ensure the compiler sends the writes to the hardware */
COMPILER_MEMORY_RELEASE();
return 0;
}
uint64_t hpet_get_time(const hpet_t *hpet)
{
uint64_t time;
do {
time = *hpet_get_main_counter(hpet->base_addr);
COMPILER_MEMORY_ACQUIRE();
/* race condition on 32-bit systems: check the bottom 32 bits didn't overflow */
} while (CONFIG_WORD_SIZE == 32
&& ((uint32_t)(time >> 32llu)) != ((uint32_t *)hpet_get_main_counter(hpet->base_addr))[1]);
return time * hpet->period_ns;
}
int hpet_set_timeout(const hpet_t *hpet, uint64_t absolute_ns)
{
hpet_timer_t *timer = hpet_get_hpet_timer(hpet->base_addr, 0);
uint64_t absolute_fs = absolute_ns / hpet->period_ns;
timer->comparator = absolute_fs;
COMPILER_MEMORY_RELEASE();
if (hpet_get_time(hpet) > absolute_ns) {
return ETIME;
}
return 0;
}
bool hpet_supports_fsb_delivery(void *vaddr)
{
hpet_timer_t *timer0 = hpet_get_hpet_timer(vaddr, 0);
uint32_t timer0_config_low = timer0->config;
return !!(timer0_config_low & BIT(TN_FSB_INT_DEL_CAP));
}
uint32_t hpet_ioapic_irq_delivery_mask(void *vaddr)
{
hpet_timer_t *timer0 = hpet_get_hpet_timer(vaddr, 0);
uint32_t irq_mask = timer0->config >> TN_INT_ROUTE_CAP;
return irq_mask;
}
uint32_t hpet_level(void *vaddr)
{
hpet_timer_t *timer0 = hpet_get_hpet_timer(vaddr, 0);
return timer0->config & BIT(TN_INT_TYPE_CNF);
}
int hpet_init(hpet_t *hpet, hpet_config_t config)
{
hpet->base_addr = config.vaddr;
hpet_timer_t *hpet_timer = hpet_get_hpet_timer(hpet->base_addr, 0);
uint32_t timer0_config_low = (uint32_t) hpet_timer->config;
/* check that this timer is edge triggered */
if (timer0_config_low & BIT(TN_INT_TYPE_CNF)) {
ZF_LOGE("This driver expects the timer to be edge triggered");
return -1;
}
/* check that this timer is 64 bit */
if (!(timer0_config_low & BIT(TN_SIZE_CAP))) {
ZF_LOGE("This driver expects hpet timer0 to be 64bit");
return -1;
}
if (config.ioapic_delivery) {
/* Check if this IO/APIC offset is valid */
uint32_t irq_mask = hpet_timer->config >> TN_INT_ROUTE_CAP;
if (!(BIT(config.irq) & irq_mask)) {
ZF_LOGE("IRQ %d not in the support mask 0x%x", config.irq, irq_mask);
return -1;
}
/* Remove any legacy replacement route so our interrupts go where we want them
* NOTE: PIT will cease to function from here on */
*hpet_get_general_config(hpet->base_addr) &= ~BIT(LEG_RT_CNF);
/* Make sure we're not deliverying by MSI */
hpet_timer->config &= ~BIT(TN_FSB_EN_CNF);
/* Put the IO/APIC offset in (this is called an irq, but in reality it is
* an index into whichever IO/APIC the HPET delivers to */
hpet_timer->config &= ~(MASK(5) << TN_INT_ROUTE_CNF);
hpet_timer->config |= config.irq << TN_INT_ROUTE_CNF;
} else {
/* check that this timer supports front size bus delivery */
if (!(timer0_config_low & BIT(TN_FSB_INT_DEL_CAP))) {
ZF_LOGE("Requested fsb delivery, but timer0 does not support");
return ENOSYS;
}
/* set timer 0 to delivery interrupts via the front side bus (using MSIs) */
hpet_timer->config |= BIT(TN_FSB_EN_CNF);
/* set up the message address register and message value register so we receive
* MSIs for timer 0*/
hpet_timer->fsb_irr =
/* top 32 bits is the message address register */
((0x0FEEllu << FIXED) << 32llu)
/* bottom 32 bits is the message value register */
| config.irq;
}
COMPILER_MEMORY_RELEASE();
/* read the period of the timer (its in femptoseconds) and calculate no of ticks per ns */
uint32_t tick_period_fs = (uint32_t)(*hpet_get_cap_id(hpet->base_addr) >> 32llu);
hpet->period_ns = tick_period_fs / 1000000;
return 0;
}
int hpet_parse_acpi(acpi_t *acpi, pmem_region_t *region)
{
if (!acpi || !region) {
ZF_LOGE("arguments cannot be NULL");
return EINVAL;
}
acpi_hpet_t *header = (acpi_hpet_t *) acpi_find_region(acpi, ACPI_HPET);
if (header == NULL) {
ZF_LOGE("Could not find HPET ACPI header");
return ENOSYS;
}
region->base_addr = header->base_address.address;
region->length = header->header.length;
return 0;
}