blob: 737690bf262d2564c40c7e05da9f2a7117369dca [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <bits.h>
#include <lib/acpi_lite.h>
#include <lib/acpi_lite/structures.h>
#include <lib/affine/ratio.h>
#include <lib/console.h>
#include <lib/fit/defer.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <fbl/algorithm.h>
#include <kernel/lockdep.h>
#include <kernel/spinlock.h>
#include <ktl/algorithm.h>
#include <ktl/limits.h>
#include <lk/init.h>
#include <platform/pc/acpi.h>
#include <platform/pc/hpet.h>
#include <vm/vm_aspace.h>
#include <ktl/enforce.h>
struct hpet_timer_registers {
volatile uint64_t conf_caps;
volatile uint64_t comparator_value;
volatile uint64_t fsb_int_route;
uint8_t _reserved[8];
} __PACKED;
struct hpet_registers {
volatile uint64_t general_caps;
uint8_t _reserved0[8];
volatile uint64_t general_config;
uint8_t _reserved1[8];
volatile uint64_t general_int_status;
uint8_t _reserved2[0xf0 - 0x28];
volatile uint64_t main_counter_value;
uint8_t _reserved3[8];
struct hpet_timer_registers timers[];
} __PACKED;
DECLARE_SINGLETON_SPINLOCK(hpet_lock);
static bool hpet_present = false;
static struct hpet_registers* hpet_regs;
uint64_t _hpet_ticks_per_ms;
static uint8_t num_timers;
#define MAX_PERIOD_IN_FS 0x05F5E100ULL
/* Bit masks for the general_config register */
#define GEN_CONF_EN 1
/* Bit masks for the per-time conf_caps register */
#define TIMER_CONF_INT_EN (1ULL << 2)
static void hpet_init(uint level) {
// Look up the HPET table.
const acpi_lite::AcpiHpetTable* hpet_desc =
acpi_lite::GetTableByType<acpi_lite::AcpiHpetTable>(GlobalAcpiLiteParser());
if (hpet_desc == nullptr) {
dprintf(INFO, "No HPET ACPI table found.\n");
return;
}
// Ensure the HPET table uses MMIO.
if (hpet_desc->address.address_space_id != ACPI_ADDR_SPACE_MEMORY) {
dprintf(INFO, "HPET unsupported: require MMIO-based HPET.\n");
return;
}
zx_status_t res = VmAspace::kernel_aspace()->AllocPhysical(
"hpet", PAGE_SIZE, /* size */
(void**)&hpet_regs, /* returned virtual address */
PAGE_SIZE_SHIFT, /* alignment log2 */
hpet_desc->address.address, /* physical address */
0, /* vmm flags */
ARCH_MMU_FLAG_UNCACHED_DEVICE | ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE);
if (res != ZX_OK) {
return;
}
// If something goes wrong, make sure we free the HPET registers.
auto cleanup = fit::defer([]() {
VmAspace::kernel_aspace()->FreeRegion(reinterpret_cast<vaddr_t>(hpet_regs));
hpet_regs = nullptr;
num_timers = 0;
});
bool has_64bit_count = BIT_SET(hpet_regs->general_caps, 13);
uint64_t tick_period_in_fs = hpet_regs->general_caps >> 32;
if (tick_period_in_fs == 0 || tick_period_in_fs > MAX_PERIOD_IN_FS) {
return;
}
/* We only support HPETs that are 64-bit and have at least two timers */
num_timers = static_cast<uint8_t>(BITS_SHIFT(hpet_regs->general_caps, 12, 8) + 1);
if (!has_64bit_count || num_timers < 2) {
return;
}
/* Make sure all timers have interrupts disabled */
for (uint8_t i = 0; i < num_timers; ++i) {
hpet_regs->timers[i].conf_caps = hpet_regs->timers[i].conf_caps & ~TIMER_CONF_INT_EN;
}
// Figure out the nominal ratio of clock monotonic ticks (nsec) to HPET ticks.
// This is the scaling factor when converting from HPET to clock monotonic.
// Unfortunately, the HPET's rate is reported by the registers as a nominal
// period (in femtosecond) instead of a nominal frequency (in Hz, or even
// mHz).
//
// In the real world, the HPET is most likely running at the bus-issue rate
// for the motherboard (24MHz, 100MHz, etc) or the CPU issue rate (2.4GHz, 4.0
// GHZ, etc), meaning that the nominal period reported is off by some fraction
// of a femtosecond because the nominal frequency of the counter does not
// perfectly divide the value 10^15.
//
// For example, when the actual nominal HPET rate is 24MHz, the value which
// will be reported by the register is 41,666,667 instead of the more precise
// 41,666,666 + 2/3 (the actual nominal ratio).
//
// So: when computing the HPET -> clock monotonic ticks ratio, assume that the
// underlying period actually comes from a clock expressed as an integer
// number of Hz, and try to reconstruct that frequency from the reported
// period by dividing and rounding up instead of rounding down.
constexpr uint64_t val10e15 = 1'000'000'000'000'000;
const uint64_t hpet_nominal_frequency = (val10e15 + tick_period_in_fs - 1) / tick_period_in_fs;
uint64_t N = 1'000'000'000;
uint64_t D = hpet_nominal_frequency;
affine::Ratio::Reduce(&N, &D);
// If ratio between HPET's rate and clock monotonic's rate cannot be stored
// in a 32 bit integer, then we cannot use HPET as our reference timer.
if ((N > ktl::numeric_limits<uint32_t>::max()) || (D > ktl::numeric_limits<uint32_t>::max())) {
printf(
"HPET to clock monotonic rate ratio (%lu/%lu) cannot be stored as a 32 bit ratio! Ignoring "
"HPET\n",
N, D);
return;
}
hpet_ticks_to_clock_monotonic = {static_cast<uint32_t>(N), static_cast<uint32_t>(D)};
_hpet_ticks_per_ms = static_cast<uint64_t>(hpet_nominal_frequency) / 1000;
hpet_present = true;
dprintf(INFO, "HPET: detected at %#" PRIx64 " ticks per ms %" PRIu64 " num timers %hhu\n",
hpet_desc->address.address, _hpet_ticks_per_ms, num_timers);
// things went well, cancel our cleanup auto-call
cleanup.cancel();
}
/* Begin running after ACPI tables are up */
LK_INIT_HOOK(hpet, hpet_init, LK_INIT_LEVEL_VM + 2)
uint64_t hpet_get_value(void) {
uint64_t v = hpet_regs->main_counter_value;
uint64_t v2 = hpet_regs->main_counter_value;
/* Even though the specification says it should not be necessary to read
* multiple times, we have observed that QEMU converts the 64-bit
* memory access in to two 32-bit accesses, resulting in bad reads. QEMU
* reads the low 32-bits first, so the result is a large jump when it
* wraps 32 bits. To work around this, we return the lesser of two reads.
*/
return ktl::min(v, v2);
}
zx_status_t hpet_set_value(uint64_t v) {
Guard<SpinLock, NoIrqSave> guard{hpet_lock::Get()};
if (hpet_regs->general_config & GEN_CONF_EN) {
return ZX_ERR_BAD_STATE;
}
hpet_regs->main_counter_value = v;
return ZX_OK;
}
bool hpet_is_present(void) { return hpet_present; }
void hpet_enable(void) {
DEBUG_ASSERT(hpet_is_present());
Guard<SpinLock, NoIrqSave> guard{hpet_lock::Get()};
hpet_regs->general_config = hpet_regs->general_config | GEN_CONF_EN;
}
void hpet_disable(void) {
DEBUG_ASSERT(hpet_is_present());
Guard<SpinLock, NoIrqSave> guard{hpet_lock::Get()};
hpet_regs->general_config = hpet_regs->general_config & ~GEN_CONF_EN;
}
/* Blocks for the requested number of milliseconds.
* For use in calibration */
void hpet_wait_ms(uint16_t ms) {
uint64_t init_timer_value = hpet_regs->main_counter_value;
uint64_t target = (uint64_t)ms * _hpet_ticks_per_ms;
while (hpet_regs->main_counter_value - init_timer_value <= target)
;
}
namespace {
int cmd_show_hpet_regs() {
if (!hpet_is_present()) {
printf("HPET is not present.\n");
return -1;
}
if (hpet_regs == nullptr) {
printf("HPET registers are NULL.\n");
return -1;
}
auto Dump = [](uint64_t reg_val, uint32_t high_bit, uint32_t low_bit, const char* name) {
uint64_t mask = (static_cast<uint64_t>(0x1) << (high_bit - low_bit + 1)) - 1;
uint64_t val = (reg_val >> low_bit) & mask;
printf("%16s : 0x%lx (%lu)\n", name, val, val);
};
printf("HPET registers are mapped at %p\n", hpet_regs);
Dump(hpet_regs->general_caps, 63, 0, "CAPS (all)");
Dump(hpet_regs->general_caps, 63, 32, "CLK_PERIOD");
Dump(hpet_regs->general_caps, 31, 16, "VENDOR_ID");
Dump(hpet_regs->general_caps, 15, 15, "LEG_RT_CAP");
Dump(hpet_regs->general_caps, 13, 13, "COUNT_SIZE_CAP");
Dump(hpet_regs->general_caps, 12, 8, "NUM_TIM_CAP");
Dump(hpet_regs->general_caps, 7, 0, "REV_ID");
printf("\n");
Dump(hpet_regs->general_config, 63, 0, "CONFIG (all)");
Dump(hpet_regs->general_config, 1, 1, "LEG_RT_CNF");
Dump(hpet_regs->general_config, 0, 0, "ENABLE_CNF");
printf("\n");
Dump(hpet_regs->general_int_status, 63, 0, "INT_STS (all)");
printf("\n");
Dump(hpet_regs->general_int_status, 63, 0, "COUNT");
return 0;
}
int cmd_hpet(int argc, const cmd_args* argv, uint32_t flags) {
auto usage = [prog_name = argv[0].str]() -> int {
printf("Usage:\n");
printf("%s regs : show the HPET registers\n", prog_name);
return -1;
};
if (argc < 2) {
return usage();
}
if (!strcmp(argv[1].str, "regs")) {
return cmd_show_hpet_regs();
} else {
printf("Unrecognized command \"%s\".\n", argv[1].str);
return usage();
}
}
} // namespace
STATIC_COMMAND_START
STATIC_COMMAND_MASKED("hpet", "HPET commands", &cmd_hpet, CMD_AVAIL_ALWAYS)
STATIC_COMMAND_END(kernel)