Files
jsix_import/src/kernel/device_manager.cpp
Justin C. Miller f7ae2e2220 [kernel] Re-design thread blocking
In preparation for the new mailbox IPC model, blocking threads needed an
overhaul. The `wait_on_*` and `wake_on_*` methods are gone, and the
`block()` and `wake()` calls on threads now pass a value between the
waker and the blocked thread.

As part of this change, the concept of signals on the base kobject class
was removed, along with the queue of blocked threads waiting on any
given object. Signals are now exclusively the domain of the event object
type, and the new wait_queue utility class helps manage waiting threads
when an object does actually need this functionality. In some cases (eg,
logger) an event object is used instead of the lower-level wait_queue.

Since this change has a lot of ramifications, this large commit includes
the following additional changes:

- The j6_object_wait, j6_object_wait_many, and j6_thread_pause syscalls
  have been removed.
- The j6_event_clear syscall has been removed - events are "cleared" by
  reading them now. A new j6_event_wait syscall has been added to read
  events.
- The generic close() method on kobject has been removed.
- The on_no_handles() method on kobject now deletes the object by
  default, and needs to be overridden by classes that should not be.
- The j6_system_bind_irq syscall now takes an event handle, as well as a
  signal that the IRQ should set on the event. IRQs will cause a waiting
  thread to be woken with the appropriate bit set.
- Threads waking due to timeout is simplified to just having a
  wake_timeout() accessor that returns a timestamp.
- The new wait_queue uses util::deque, which caused the disovery of two
  bugs in the deque implementation: empty deques could still have a
  single array allocated and thus return true for empty(), and new
  arrays getting allocated were not being zeroed first.
- Exposed a new erase() method on util::map that takes a node pointer
  instead of a key, skipping lookup.
2022-02-22 00:00:15 -08:00

436 lines
12 KiB
C++

#include <new>
#include <stddef.h>
#include <stdint.h>
#include <util/misc.h> // for checksum
#include <util/pointers.h>
#include "assert.h"
#include "acpi_tables.h"
#include "apic.h"
#include "clock.h"
#include "device_manager.h"
#include "interrupts.h"
#include "logger.h"
#include "memory.h"
#include "objects/event.h"
static obj::event * const ignore_event = reinterpret_cast<obj::event*>(-1ull);
static const char expected_signature[] = "RSD PTR ";
device_manager device_manager::s_instance;
struct acpi1_rsdp
{
char signature[8];
uint8_t checksum;
char oem_id[6];
uint8_t revision;
uint32_t rsdt_address;
} __attribute__ ((packed));
struct acpi2_rsdp
{
char signature[8];
uint8_t checksum10;
char oem_id[6];
uint8_t revision;
uint32_t rsdt_address;
uint32_t length;
acpi_table_header *xsdt_address;
uint8_t checksum20;
uint8_t reserved[3];
} __attribute__ ((packed));
bool
acpi_table_header::validate(uint32_t expected_type) const
{
if (util::checksum(this, length) != 0) return false;
return !expected_type || (expected_type == type);
}
device_manager::device_manager() :
m_lapic_base(0)
{
m_irqs.ensure_capacity(32);
m_irqs.set_size(16);
for (int i = 0; i < 16; ++i)
m_irqs[i] = {nullptr, 0};
m_irqs[2] = {ignore_event, 0};
}
template <typename T> static const T *
check_get_table(const acpi_table_header *header)
{
kassert(header && header->validate(T::type_id), "Invalid ACPI table.");
return reinterpret_cast<const T *>(header);
}
void
device_manager::parse_acpi(const void *root_table)
{
kassert(root_table != 0, "ACPI root table pointer is null.");
const acpi1_rsdp *acpi1 = mem::to_virtual(
reinterpret_cast<const acpi1_rsdp *>(root_table));
for (int i = 0; i < sizeof(acpi1->signature); ++i)
kassert(acpi1->signature[i] == expected_signature[i],
"ACPI RSDP table signature mismatch");
uint8_t sum = util::checksum(acpi1, sizeof(acpi1_rsdp), 0);
kassert(sum == 0, "ACPI 1.0 RSDP checksum mismatch.");
kassert(acpi1->revision > 1, "ACPI 1.0 not supported.");
const acpi2_rsdp *acpi2 =
reinterpret_cast<const acpi2_rsdp *>(acpi1);
sum = util::checksum(acpi2, sizeof(acpi2_rsdp), sizeof(acpi1_rsdp));
kassert(sum == 0, "ACPI 2.0 RSDP checksum mismatch.");
load_xsdt(mem::to_virtual(acpi2->xsdt_address));
}
const device_manager::apic_nmi *
device_manager::get_lapic_nmi(uint8_t id) const
{
for (const auto &nmi : m_nmis) {
if (nmi.cpu == 0xff || nmi.cpu == id)
return &nmi;
}
return nullptr;
}
const device_manager::irq_override *
device_manager::get_irq_override(uint8_t irq) const
{
for (const auto &o : m_overrides)
if (o.source == irq) return &o;
return nullptr;
}
ioapic *
device_manager::get_ioapic(int i)
{
return (i < m_ioapics.count()) ? &m_ioapics[i] : nullptr;
}
static void
put_sig(char *into, uint32_t type)
{
for (int j=0; j<4; ++j) into[j] = reinterpret_cast<char *>(&type)[j];
}
void
device_manager::load_xsdt(const acpi_table_header *header)
{
const auto *xsdt = check_get_table<acpi_xsdt>(header);
char sig[5] = {0,0,0,0,0};
log::info(logs::device, "ACPI 2.0+ tables loading");
put_sig(sig, xsdt->header.type);
log::debug(logs::device, " Found table %s", sig);
size_t num_tables = acpi_table_entries(xsdt, sizeof(void*));
for (size_t i = 0; i < num_tables; ++i) {
const acpi_table_header *header =
mem::to_virtual(xsdt->headers[i]);
put_sig(sig, header->type);
log::debug(logs::device, " Found table %s", sig);
kassert(header->validate(), "Table failed validation.");
switch (header->type) {
case acpi_apic::type_id:
load_apic(header);
break;
case acpi_mcfg::type_id:
load_mcfg(header);
break;
case acpi_hpet::type_id:
load_hpet(header);
break;
default:
break;
}
}
}
void
device_manager::load_apic(const acpi_table_header *header)
{
const auto *apic = check_get_table<acpi_apic>(header);
m_lapic_base = apic->local_address;
size_t count = acpi_table_entries(apic, 1);
uint8_t const *p = apic->controller_data;
uint8_t const *end = p + count;
// Pass one: count objcts
unsigned num_lapics = 0;
unsigned num_ioapics = 0;
unsigned num_overrides = 0;
unsigned num_nmis = 0;
while (p < end) {
const uint8_t type = p[0];
const uint8_t length = p[1];
switch (type) {
case 0: ++num_lapics; break;
case 1: ++num_ioapics; break;
case 2: ++num_overrides; break;
case 4: ++num_nmis; break;
default: break;
}
p += length;
}
m_apic_ids.set_capacity(num_lapics);
m_ioapics.set_capacity(num_ioapics);
m_overrides.set_capacity(num_overrides);
m_nmis.set_capacity(num_nmis);
// Pass two: configure objects
p = apic->controller_data;
while (p < end) {
const uint8_t type = p[0];
const uint8_t length = p[1];
switch (type) {
case 0: { // Local APIC
uint8_t uid = util::read_from<uint8_t>(p+2);
uint8_t id = util::read_from<uint8_t>(p+3);
m_apic_ids.append(id);
log::debug(logs::device, " Local APIC uid %x id %x", uid, id);
}
break;
case 1: { // I/O APIC
uintptr_t base = util::read_from<uint32_t>(p+4);
uint32_t base_gsi = util::read_from<uint32_t>(p+8);
m_ioapics.emplace(base, base_gsi);
log::debug(logs::device, " IO APIC gsi %x base %x", base_gsi, base);
}
break;
case 2: { // Interrupt source override
irq_override o;
o.source = util::read_from<uint8_t>(p+3);
o.gsi = util::read_from<uint32_t>(p+4);
o.flags = util::read_from<uint16_t>(p+8);
m_overrides.append(o);
log::debug(logs::device, " Intr source override IRQ %d -> %d Pol %d Tri %d",
o.source, o.gsi, (o.flags & 0x3), ((o.flags >> 2) & 0x3));
}
break;
case 4: {// LAPIC NMI
apic_nmi nmi;
nmi.cpu = util::read_from<uint8_t>(p + 2);
nmi.lint = util::read_from<uint8_t>(p + 5);
nmi.flags = util::read_from<uint16_t>(p + 3);
m_nmis.append(nmi);
log::debug(logs::device, " LAPIC NMI Proc %02x LINT%d Pol %d Tri %d",
nmi.cpu, nmi.lint, nmi.flags & 0x3, (nmi.flags >> 2) & 0x3);
}
break;
default:
log::debug(logs::device, " APIC entry type %d", type);
}
p += length;
}
m_ioapics[0].mask(3, false);
m_ioapics[0].mask(4, false);
}
void
device_manager::load_mcfg(const acpi_table_header *header)
{
const auto *mcfg = check_get_table<acpi_mcfg>(header);
size_t count = acpi_table_entries(mcfg, sizeof(acpi_mcfg_entry));
m_pci.set_size(count);
m_devices.set_capacity(16);
for (unsigned i = 0; i < count; ++i) {
const acpi_mcfg_entry &mcfge = mcfg->entries[i];
m_pci[i].group = mcfge.group;
m_pci[i].bus_start = mcfge.bus_start;
m_pci[i].bus_end = mcfge.bus_end;
m_pci[i].base = mem::to_virtual<uint32_t>(mcfge.base);
log::debug(logs::device, " Found MCFG entry: base %lx group %d bus %d-%d",
mcfge.base, mcfge.group, mcfge.bus_start, mcfge.bus_end);
}
probe_pci();
}
void
device_manager::load_hpet(const acpi_table_header *header)
{
const auto *hpet = check_get_table<acpi_hpet>(header);
log::debug(logs::device, " Found HPET device #%3d: base %016lx pmin %d attr %02x",
hpet->index, hpet->base_address.address, hpet->periodic_min, hpet->attributes);
uint32_t hwid = hpet->hardware_id;
uint8_t rev_id = hwid & 0xff;
uint8_t comparators = (hwid >> 8) & 0x1f;
uint8_t count_size_cap = (hwid >> 13) & 1;
uint8_t legacy_replacement = (hwid >> 15) & 1;
uint32_t pci_vendor_id = (hwid >> 16);
log::debug(logs::device, " rev:%02d comparators:%02d count_size_cap:%1d legacy_repl:%1d",
rev_id, comparators, count_size_cap, legacy_replacement);
log::debug(logs::device, " pci vendor id: %04x", pci_vendor_id);
m_hpets.emplace(hpet->index,
reinterpret_cast<uint64_t*>(hpet->base_address.address + mem::linear_offset));
}
void
device_manager::probe_pci()
{
for (auto &pci : m_pci) {
log::debug(logs::device, "Probing PCI group at base %016lx", pci.base);
for (int bus = pci.bus_start; bus <= pci.bus_end; ++bus) {
for (int dev = 0; dev < 32; ++dev) {
if (!pci.has_device(bus, dev, 0)) continue;
auto &d0 = m_devices.emplace(pci, bus, dev, 0);
if (!d0.multi()) continue;
for (int i = 1; i < 8; ++i) {
if (pci.has_device(bus, dev, i))
m_devices.emplace(pci, bus, dev, i);
}
}
}
}
}
static uint64_t
tsc_clock_source(void*)
{
uint32_t lo = 0, hi = 0;
asm volatile ("rdtsc" : "=a" (lo), "=d" (hi));
return ((uint64_t)hi << 32) | lo;
}
void
device_manager::init_drivers()
{
// Eventually this should be e.g. a lookup into a loadable driver list
// for now, just look for AHCI devices
/*
for (auto &device : m_devices) {
if (device.devclass() != 1 || device.subclass() != 6)
continue;
if (device.progif() != 1) {
log::warn(logs::device, "Found SATA device %d:%d:%d, but not an AHCI interface.",
device.bus(), device.device(), device.function());
}
ahcid.register_device(&device);
}
*/
clock *master_clock = nullptr;
if (m_hpets.count() > 0) {
hpet &h = m_hpets[0];
h.enable();
// becomes the singleton
master_clock = new clock(h.rate(), hpet_clock_source, &h);
log::info(logs::clock, "Created master clock using HPET 0: Rate %d", h.rate());
} else {
//TODO: Other clocks, APIC clock?
master_clock = new clock(5000, tsc_clock_source, nullptr);
}
kassert(master_clock, "Failed to allocate master clock");
}
bool
device_manager::dispatch_irq(unsigned irq)
{
if (irq >= m_irqs.count())
return false;
irq_binding &binding = m_irqs[irq];
if (!binding.target || binding.target == ignore_event)
return binding.target == ignore_event;
binding.target->signal(1 << binding.signal);
return true;
}
bool
device_manager::bind_irq(unsigned irq, obj::event *target, unsigned signal)
{
// TODO: grow if under max size
if (irq >= m_irqs.count())
return false;
m_irqs[irq] = {target, signal};
return true;
}
void
device_manager::unbind_irqs(obj::event *target)
{
const size_t count = m_irqs.count();
for (size_t i = 0; i < count; ++i) {
if (m_irqs[i].target == target)
m_irqs[i] = {nullptr, 0};
}
}
bool
device_manager::allocate_msi(const char *name, pci_device &device, irq_callback cb, void *data)
{
/*
// TODO: find gaps to fill
uint8_t irq = m_irqs.count();
isr vector = isr::irq00 + irq;
m_irqs.append({name, cb, data});
log::debug(logs::device, "Allocating IRQ %02x to %s.", irq, name);
device.write_msi_regs(
0xFEE00000,
static_cast<uint16_t>(vector));
*/
return true;
}
void
device_manager::register_block_device(block_device *blockdev)
{
m_blockdevs.append(blockdev);
}