hardware/aeolia: Add HPET device

This commit is contained in:
Alexandro Sanchez Bach 2021-11-07 16:28:03 +01:00
parent d1a45f452f
commit 5fe2e96f4f
6 changed files with 354 additions and 14 deletions

View file

@ -43,6 +43,8 @@ ORBITAL_SOURCES_APPEND(${ORBITAL_DIR_SOURCES}/orbital/host)
ORBITAL_SOURCES_APPEND(${ORBITAL_DIR_SOURCES}/orbital/host/graphics)
ORBITAL_SOURCES_APPEND(${ORBITAL_DIR_SOURCES}/orbital/hardware)
ORBITAL_SOURCES_APPEND(${ORBITAL_DIR_SOURCES}/orbital/hardware/aeolia)
ORBITAL_SOURCES_APPEND(${ORBITAL_DIR_SOURCES}/orbital/hardware/aeolia/hpet)
ORBITAL_SOURCES_APPEND(${ORBITAL_DIR_SOURCES}/orbital/hardware/aeolia/msic)
ORBITAL_SOURCES_APPEND(${ORBITAL_DIR_SOURCES}/orbital/hardware/aeolia/uart)
ORBITAL_SOURCES_APPEND(${ORBITAL_DIR_SOURCES}/orbital/hardware/liverpool)
ORBITAL_SOURCES_APPEND(${ORBITAL_DIR_SOURCES}/orbital/hardware/liverpool/gca)

View file

@ -65,7 +65,7 @@ static U16 icc_checksum(const IccMessageHeader& message) {
}
AeoliaPCIeDevice::AeoliaPCIeDevice(PCIeBus* bus, const AeoliaPCIeDeviceConfig& config)
: PCIeDevice(bus, config), msic(bus->space_mem()) {
: PCIeDevice(bus, config), hpet(bus->space_mem()), msic(bus->space_mem()) {
// Create sub-devices
AeoliaUARTDeviceConfig uart0_config(config.backend_uart0);
AeoliaUARTDeviceConfig uart1_config(config.backend_uart1);
@ -189,7 +189,8 @@ U64 AeoliaPCIeDevice::peripherals_read(U64 addr, U64 size) {
fprintf(stderr, "AeoliaPCIeDevice::peripherals_read: addr=0x%llX, size=0x%llX\n", addr, size);
}
else if (range_hpet.contains(addr)) {
assert_always("Unimplemented");
addr -= range_hpet.base;
value = hpet.mmio_read(addr, size);
}
else if (range_unk3c.contains(addr)) {
fprintf(stderr, "AeoliaPCIeDevice::peripherals_read: addr=0x%llX, size=0x%llX\n", addr, size);
@ -254,7 +255,8 @@ void AeoliaPCIeDevice::peripherals_write(U64 addr, U64 value, U64 size) {
fprintf(stderr, "AeoliaPCIeDevice::peripherals_write: addr=0x%llX, value=0x%llX, size=0x%llX\n", addr, value, size);
}
else if (range_hpet.contains(addr)) {
assert_always("Unimplemented");
addr -= range_hpet.base;
hpet.mmio_write(addr, value, size);
}
else if (range_unk3c.contains(addr)) {
fprintf(stderr, "AeoliaPCIeDevice::peripherals_write: addr=0x%llX, value=0x%llX, size=0x%llX\n", addr, value, size);
@ -349,12 +351,6 @@ void AeoliaPCIeDevice::update_icc() {
fprintf(stderr, "icc: Unknown buzzer query 0x%04X!\n", query.minor);
}
break;
case ICC_CMD_UNK0D:
switch (query.minor) {
default:
fprintf(stderr, "icc: Unknown unk_0D query 0x%04X!\n", query.minor);
}
break;
case ICC_CMD_NVRAM:
switch (query.minor) {
case ICC_CMD_NVRAM_OP_WRITE:
@ -386,7 +382,7 @@ void AeoliaPCIeDevice::update_icc() {
spm_data[ASPM_ICC_REPLY_R] = 0;
icc_status |= APCIE_ICC_MSG_PENDING | APCIE_ICC_IRQ_PENDING;
icc_doorbell &= ~APCIE_ICC_MSG_PENDING;
// apcie_msi_trigger(&s->msic, 4, APCIE_MSI_FNC4_ICC);
msic.msi_trigger(4, APCIE_MSI_FNC4_ICC);
}
AeoliaPCIeDevice::IccReply AeoliaPCIeDevice::icc_cmd_service_version() {

View file

@ -11,6 +11,7 @@
#pragma once
#include "icc/icc.h"
#include "hpet/aeolia_hpet.h"
#include "msic/aeolia_msic.h"
#include "nvs/aeolia_nvs.h"
@ -60,6 +61,7 @@ private:
std::unique_ptr<AeoliaUARTDevice> uart1;
// State
AeoliaHpet hpet;
AeoliaMsic msic;
AeoliaNVS nvs{};
uint32_t icc_doorbell;

View file

@ -0,0 +1,230 @@
/**
* Aeolia High-Precision Event Timer (HPET) device.
*
* Copyright 2017-2021. Orbital project.
* Released under MIT license. Read LICENSE for more details.
*
* Authors:
* - Alexandro Sanchez Bach <alexandro@phi.nz>
*/
#include "aeolia_hpet.h"
#include <chrono>
using namespace std::chrono_literals;
enum HPETGeneralReg {
HPET_REG_CAP = 0x00,
HPET_REG_CFG = 0x10,
HPET_REG_IS = 0x20,
HPET_REG_CNT = 0xF0,
};
enum HPETTimerReg {
HPET_REG_TNCFG_0 = 0x00,
HPET_REG_TNCFG_1 = 0x04,
HPET_REG_TNCMP_0 = 0x08,
HPET_REG_TNCMP_1 = 0x0C,
HPET_REG_TNROUTE_0 = 0x10,
HPET_REG_TNROUTE_1 = 0x14,
};
AeoliaHpet::AeoliaHpet(ContainerSpace* mem, const AeoliaHpetConfig& config)
: Device(nullptr, config), mem(mem) {
// Initialize MMIO
mmio = new MemorySpace(this, 0x1000, {
static_cast<MemorySpaceReadOp>(&AeoliaHpet::mmio_read),
static_cast<MemorySpaceWriteOp>(&AeoliaHpet::mmio_write),
});
mem->addSubspace(mmio, config.base);
// Create timers
assert_true(config.count >= 3);
assert_true(config.count <= 32);
timers.resize(config.count);
// Capabilities
s.cap.num_tim_cap = config.count - 1;
s.cap.period = config.period_fs;
reset();
}
AeoliaHpet::~AeoliaHpet() {}
void AeoliaHpet::reset() {
for (auto& timer : timers) {
timer.comparator = UINT64_MAX;
// TODO
}
s.config.value = 0;
s.counter.value = 0;
// TODO
}
U64 AeoliaHpet::mmio_read(U64 addr, U64 size) {
U64 value = 0;
// Sanity checks
assert_true(size == 4);
if (addr < 0x100) {
/* General access */
switch (addr) {
case HPET_REG_CAP + 0:
value = s.cap.value_lo;
break;
case HPET_REG_CAP + 4:
value = s.cap.value_hi;
break;
case HPET_REG_CFG + 0:
value = s.config.value;
break;
case HPET_REG_IS + 0:
value = s.isr.value_lo;
break;
case HPET_REG_IS + 4:
value = s.isr.value_hi;
break;
case HPET_REG_CNT + 0:
value = get_counter();
break;
case HPET_REG_CNT + 4:
value = get_counter() >> 32;
break;
// Ignored upper-accesses
case HPET_REG_CFG + 4:
break;
default:
assert_always("Invalid register access");
}
}
else {
/* Timer-N access */
const U64 index = (addr - 0x100) / 0x20;
const U64 offset = addr % 0x20;
assert_true(index < timers.size());
const auto& timer = timers[index];
switch (offset) {
case HPET_REG_TNCFG_0:
value = timer.config.value_lo;
break;
case HPET_REG_TNCFG_1:
value = timer.config.value_hi;
break;
case HPET_REG_TNCMP_0:
value = timer.comparator;
break;
case HPET_REG_TNCMP_1:
value = timer.comparator >> 32;
break;
case HPET_REG_TNROUTE_0:
value = timer.fsb.int_val;
break;
case HPET_REG_TNROUTE_1:
value = timer.fsb.int_addr;
break;
default:
assert_always("Invalid register access");
}
}
return static_cast<U32>(value);
}
void AeoliaHpet::mmio_write(U64 addr, U64 value, U64 size) {
// Sanity checks
assert_true(size == 4);
if (addr < 0x100) {
// General access
switch (addr) {
case HPET_REG_CNT + 0:
s.counter.lo = value;
break;
case HPET_REG_CNT + 4:
s.counter.hi = value;
break;
case HPET_REG_CFG + 0:
s.config.value = value;
break;
case HPET_REG_IS + 0:
value &= s.isr.value_lo;
for (int i = 0; i < timers.size(); i++) {
if (value & (1 << i)) {
update_irq(timers[i], i);
}
}
break;
// Ignored accesses
case HPET_REG_CAP + 0:
break;
default:
assert_always("Invalid register access");
}
}
else {
// Timer-N access
const U64 index = (addr - 0x100) / 0x20;
const U64 offset = addr % 0x20;
assert_true(index < timers.size());
auto& tn = timers[index];
switch (offset) {
case HPET_REG_TNCFG_0:
tn.config.value_lo = value;
break;
case HPET_REG_TNROUTE_0:
tn.fsb.int_val = value;
break;
case HPET_REG_TNROUTE_1:
tn.fsb.int_addr = value;
break;
default:
assert_always("Invalid register access");
}
}
}
U64 AeoliaHpet::get_counter() {
if (s.config.enable_cnf) {
s.counter.value = get_ticks();
}
return s.counter.value;
}
U64 AeoliaHpet::get_ticks() {
return Clock::now().time_since_epoch().count() / 100; // 100ns period
}
void AeoliaHpet::update_irq(HPETTimer& timer, bool set) {
if (!s.config.enable_cnf) {
assert_always("Unexpected");
return;
}
/**
* LegacyReplacement Route
* =======================
* If the ENABLE_CNF bit and the LEG_RT_CNF bit are both set,
* then the interrupts will be routed as follows:
* - Timer 0 will be routed to IRQ0 in Non-APIC or IRQ2 in the I/O APIC
* - Timer 1 will be routed to IRQ8 in Non-APIC or IRQ8 in the I/O APIC
*/
if (s.config.leg_rt_cnf) {
assert_always("Unimplemented");
return;
}
// FSB IRQ route
if (timer.config.fsb_en_cnf) {
mem->write<U32>(timer.fsb.int_addr, timer.fsb.int_val);
return;
}
}

View file

@ -0,0 +1,111 @@
/**
* Aeolia High-Precision Event Timer (HPET) device.
*
* Copyright 2017-2021. Orbital project.
* Released under MIT license. Read LICENSE for more details.
*
* Authors:
* - Alexandro Sanchez Bach <alexandro@phi.nz>
*/
#pragma once
#include <orbital/core.h>
struct HPETTimer {
union {
U64 value;
struct {
U32 value_lo;
U32 value_hi;
};
Bit<U64, 1> int_type_cnf;
Bit<U64, 2> int_enb_cnf;
Bit<U64, 3> type_cnf;
Bit<U64, 4> per_int_cap;
Bit<U64, 5> size_cap;
Bit<U64, 6> val_set_cnf;
Bit<U64, 8> mode32_cnf;
Bitrange<U64, 9, 13> int_route_cnf;
Bitrange<U64, 14, 14> fsb_en_cnf;
Bitrange<U64, 15, 15> fsb_int_del_cap;
Bitrange<U64, 32, 63> int_route_cap;
} config;
U64 comparator;
union {
U64 value;
struct {
U32 int_val;
U32 int_addr;
};
} fsb;
};
struct AeoliaHpetConfig : DeviceConfig {
U64 base = 0xFED00000;
U64 count = 4;
U64 period_fs = 100 * 1000000; // 100ns
};
class AeoliaHpet : public Device {
public:
AeoliaHpet(ContainerSpace* mem, const AeoliaHpetConfig& config = {});
~AeoliaHpet();
void reset() override;
U64 mmio_read(U64 addr, U64 size);
void mmio_write(U64 addr, U64 value, U64 size);
private:
Space* mem;
MemorySpace* mmio;
std::vector<HPETTimer> timers;
struct {
union {
U64 value;
struct {
U32 value_lo;
U32 value_hi;
};
Bitrange<U64, 0, 7> rev_id;
Bitrange<U64, 8, 12> num_tim_cap;
Bitrange<U64, 13, 13> count_size_cap;
Bitrange<U64, 15, 15> leg_rt_cap;
Bitrange<U64, 16, 21> vendor_id;
Bitrange<U64, 32, 63> period;
} cap;
union {
U64 value;
struct {
U32 value_lo;
U32 value_hi;
};
Bit<U64, 1> leg_rt_cnf;
Bit<U64, 0> enable_cnf;
} config;
union {
U64 value;
struct {
U32 value_lo;
U32 value_hi;
};
Bitset<64> tn_int_sts;
} isr;
union {
U64 value;
struct {
U32 lo;
U32 hi;
};
} counter;
} s = {};
// Helpers
U64 get_counter();
U64 get_ticks();
// Interrupts
void update_irq(HPETTimer& timer, bool set);
};

View file

@ -17,12 +17,10 @@ enum IccCommand : U16 {
ICC_CMD_SERVICE = 0x01,
ICC_CMD_BOARD = 0x02,
ICC_CMD_NVRAM = 0x03,
ICC_CMD_UNK04 = 0x04, // icc_power_init
ICC_CMD_POWER = 0x04,
ICC_CMD_BUTTONS = 0x08,
ICC_CMD_BUZZER = 0x09,
ICC_CMD_SAVE_CONTEXT = 0x0B, // thermal
ICC_CMD_LOAD_CONTEXT = 0x0C,
ICC_CMD_UNK0D = 0x0D, // icc_configuration_get_devlan_setting
ICC_CMD_THERMAL = 0x0B,
ICC_CMD_UNK70 = 0x70, // sceControlEmcHdmiService
ICC_CMD_SNVRAM_READ = 0x8D,
};
@ -64,6 +62,7 @@ enum IccCommandBoardOp : U16 {
enum IccCommandNvramOp : U16 {
ICC_CMD_NVRAM_OP_WRITE = 0x0000,
ICC_CMD_NVRAM_OP_READ = 0x0001,
ICC_CMD_NVRAM_OP_FLUSH = 0x0002,
};
enum IccCommandButtonsOp : U16 {