Files
jsix_import/src/kernel/device_manager.cpp
Justin C. Miller c88170f6e0 [kernel] Start all other processors in the system
This very large commit is mainly focused on getting the APs started and
to a state where they're waiting to have work scheduled. (Actually
scheduling on them is for another commit.)

To do this, a bunch of major changes were needed:

- Moving a lot of the CPU initialization (including for the BSP) to
  init_cpu(). This includes setting up IST stacks, writing MSRs, and
  creating the cpu_data structure. For the APs, this also creates and
  installs the GDT and TSS, and installs the global IDT.

- Creating the AP startup code, which tries to be as position
  independent as possible. It's copied from its location to 0x8000 for
  AP startup, and some of it is fixed at that address. The AP startup
  code jumps from real mode to long mode with paging in one swell foop.

- Adding limited IPI capability to the lapic class. This will need to
  improve.

- Renaming cpu/cpu.* to cpu/cpu_id.* because it was just annoying in GDB
  and really isn't anything but cpu_id anymore.

- Moved all the GDT, TSS, and IDT code into their own files and made
  them classes instead of a mess of free functions.

- Got rid of bsp_cpu_data everywhere. Now always call the new
  current_cpu() to get the current CPU's cpu_data.

- Device manager keeps a list of APIC ids now. This should go somewhere
  else eventually, device_manager needs to be refactored away.

- Moved some more things (notably the g_kernel_stacks vma) to the
  pre-constructor setup in memory_bootstrap. That whole file is in bad
  need of a refactor.
2021-02-07 23:44:28 -08:00

430 lines
10 KiB
C++

#include <stddef.h>
#include <stdint.h>
#include "kutil/assert.h"
#include "kutil/memory.h"
#include "acpi_tables.h"
#include "apic.h"
#include "clock.h"
#include "console.h"
#include "device_manager.h"
#include "interrupts.h"
#include "kernel_memory.h"
#include "log.h"
#include "objects/endpoint.h"
static endpoint * const ignore_endpoint = reinterpret_cast<endpoint*>(-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 (kutil::checksum(this, length) != 0) return false;
return !expected_type || (expected_type == type);
}
void irq2_callback(void *)
{
}
void irq4_callback(void *)
{
// TODO: move this to a real serial driver
console *cons = console::get();
cons->echo();
}
device_manager::device_manager() :
m_lapic(nullptr)
{
m_irqs.ensure_capacity(32);
m_irqs.set_size(16);
for (int i = 0; i < 16; ++i)
m_irqs[i] = nullptr;
m_irqs[2] = ignore_endpoint;
}
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 = memory::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 = kutil::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 = kutil::checksum(acpi2, sizeof(acpi2_rsdp), sizeof(acpi1_rsdp));
kassert(sum == 0, "ACPI 2.0 RSDP checksum mismatch.");
load_xsdt(memory::to_virtual(acpi2->xsdt_address));
}
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 =
memory::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);
uintptr_t local = apic->local_address;
m_lapic = new lapic(local, isr::isrSpurious);
size_t count = acpi_table_entries(apic, 1);
uint8_t const *p = apic->controller_data;
uint8_t const *end = p + count;
// Pass one: count IOAPIC objcts
int num_ioapics = 0;
while (p < end) {
const uint8_t type = p[0];
const uint8_t length = p[1];
if (type == 1) num_ioapics++;
p += length;
}
m_ioapics.set_capacity(num_ioapics);
// Pass two: set up IOAPIC objcts
p = apic->controller_data;
while (p < end) {
const uint8_t type = p[0];
const uint8_t length = p[1];
if (type == 1) {
uintptr_t base = kutil::read_from<uint32_t>(p+4);
uint32_t base_gsr = kutil::read_from<uint32_t>(p+8);
m_ioapics.emplace(base, base_gsr);
}
p += length;
}
// Pass three: configure APIC 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 = kutil::read_from<uint8_t>(p+2);
uint8_t id = kutil::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
break;
case 2: { // Interrupt source override
uint8_t source = kutil::read_from<uint8_t>(p+3);
isr gsi = isr::irq00 + kutil::read_from<uint32_t>(p+4);
uint16_t flags = kutil::read_from<uint16_t>(p+8);
log::debug(logs::device, " Intr source override IRQ %d -> %d Pol %d Tri %d",
source, gsi, (flags & 0x3), ((flags >> 2) & 0x3));
// TODO: in a multiple-IOAPIC system this might be elsewhere
m_ioapics[0].redirect(source, static_cast<isr>(gsi), flags, true);
}
break;
case 4: {// LAPIC NMI
uint8_t cpu = kutil::read_from<uint8_t>(p + 2);
uint8_t num = kutil::read_from<uint8_t>(p + 5);
uint16_t flags = kutil::read_from<uint16_t>(p + 3);
log::debug(logs::device, " LAPIC NMI Proc %d LINT%d Pol %d Tri %d",
kutil::read_from<uint8_t>(p+2),
kutil::read_from<uint8_t>(p+5),
kutil::read_from<uint16_t>(p+3) & 0x3,
(kutil::read_from<uint16_t>(p+3) >> 2) & 0x3);
m_lapic->enable_lint(num, num == 0 ? isr::isrLINT0 : isr::isrLINT1, true, flags);
}
break;
default:
log::debug(logs::device, " APIC entry type %d", type);
}
p += length;
}
/*
for (uint8_t i = 0; i < m_ioapics[0].get_num_gsi(); ++i) {
switch (i) {
case 2: break;
default: m_ioapics[0].mask(i, false);
}
}
*/
m_lapic->enable();
}
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 = memory::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 + ::memory::page_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
fake_clock_source(void*)
{
static uint64_t value = 0;
return value++;
}
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, fake_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;
endpoint *e = m_irqs[irq];
if (!e || e == ignore_endpoint)
return e == ignore_endpoint;
e->signal_irq(irq);
return true;
}
bool
device_manager::bind_irq(unsigned irq, endpoint *target)
{
// TODO: grow if under max size
if (irq >= m_irqs.count())
return false;
m_irqs[irq]= target;
return true;
}
void
device_manager::unbind_irqs(endpoint *target)
{
const size_t count = m_irqs.count();
for (size_t i = 0; i < count; ++i) {
if (m_irqs[i] == target)
m_irqs[i] = nullptr;
}
}
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);
}