436 lines
13 KiB
C
436 lines
13 KiB
C
#include "arch/amd64/hw/ioapic.h"
|
|
#include "arch/amd64/hw/irq.h"
|
|
#include "drivers/pci/pci.h"
|
|
#include "sys/string.h"
|
|
#include "sys/mm.h"
|
|
#include "sys/debug.h"
|
|
#include "sys/panic.h"
|
|
#include "sys/assert.h"
|
|
#include "acpi.h"
|
|
|
|
uintptr_t amd64_ioapic_base = 0;
|
|
|
|
// Legacy PIC -> I/O APIC routing table
|
|
static uint32_t ioapic_leg_rt[16] = { 0xFFFFFFFF };
|
|
|
|
uint32_t amd64_ioapic_read(uint8_t reg) {
|
|
if (!amd64_ioapic_base) {
|
|
// TODO: panic
|
|
kdebug("Reading non-existent I/O APIC\n");
|
|
while (1);
|
|
}
|
|
uint32_t volatile *ioapic = (uint32_t volatile *) amd64_ioapic_base;
|
|
ioapic[0] = (reg & 0xFF);
|
|
return ioapic[4];
|
|
}
|
|
|
|
void amd64_ioapic_write(uint8_t reg, uint32_t v) {
|
|
if (!amd64_ioapic_base) {
|
|
// TODO: panic
|
|
kdebug("Writing non-existent I/O APIC\n");
|
|
while (1);
|
|
}
|
|
uint32_t volatile *ioapic = (uint32_t volatile *) amd64_ioapic_base;
|
|
ioapic[0] = reg & 0xFF;
|
|
ioapic[4] = v;
|
|
}
|
|
|
|
void amd64_ioapic_int_src_override(uint8_t bus_src, uint8_t irq_src, uint32_t no, uint16_t flags) {
|
|
kdebug("IRQ Override: %02x:%02x -> %d\n", bus_src, irq_src, no);
|
|
if (bus_src == 0 && irq_src < 16) {
|
|
// Totally a legacy IRQ
|
|
ioapic_leg_rt[irq_src] = no;
|
|
}
|
|
|
|
// Setup flags properly
|
|
uint32_t low = amd64_ioapic_read(0x10 + no * 2);
|
|
|
|
if (flags & (1 << 1)) {
|
|
// Low-active IRQ
|
|
low |= IOAPIC_REDIR_POL_LOW;
|
|
} else {
|
|
low &= ~IOAPIC_REDIR_POL_LOW;
|
|
}
|
|
|
|
if (flags & (1 << 3)) {
|
|
low |= IOAPIC_REDIR_TRG_LEVEL;
|
|
} else {
|
|
low &= ~IOAPIC_REDIR_TRG_LEVEL;
|
|
}
|
|
|
|
amd64_ioapic_write(0x10 + no * 2, low);
|
|
}
|
|
|
|
void amd64_ioapic_set(uintptr_t addr) {
|
|
if (amd64_ioapic_base != 0 && amd64_ioapic_base != addr) {
|
|
// TODO: panic
|
|
kdebug("I/O APIC base already set to %p, requested %p\n", amd64_ioapic_base, addr);
|
|
return;
|
|
while (1);
|
|
}
|
|
amd64_ioapic_base = addr;
|
|
memset(ioapic_leg_rt, 0xFF, sizeof(ioapic_leg_rt));
|
|
}
|
|
|
|
void amd64_ioapic_map_gsi(uint8_t gsi, uint8_t lapic, uint8_t vector) {
|
|
uint32_t low = amd64_ioapic_read((gsi * 2) + 0x10);
|
|
uint32_t high = amd64_ioapic_read((gsi * 2) + 0x11);
|
|
|
|
low &= (~0xFF) & (IOAPIC_REDIR_POL_LOW | IOAPIC_REDIR_TRG_LEVEL);
|
|
low |= vector;
|
|
|
|
high = IOAPIC_REDIR_DST_SET(lapic);
|
|
|
|
amd64_ioapic_write((gsi * 2) + 0x10, low);
|
|
amd64_ioapic_write((gsi * 2) + 0x11, high);
|
|
}
|
|
|
|
void amd64_ioapic_mask(uint8_t gsi) {
|
|
uint32_t low = amd64_ioapic_read((gsi * 2) + 0x10);
|
|
|
|
low |= IOAPIC_REDIR_MSK;
|
|
|
|
amd64_ioapic_write((gsi * 2) + 0x10, low);
|
|
}
|
|
|
|
void amd64_ioapic_unmask(uint8_t gsi) {
|
|
uint32_t low = amd64_ioapic_read((gsi * 2) + 0x10);
|
|
|
|
low &= ~IOAPIC_REDIR_MSK;
|
|
|
|
amd64_ioapic_write((gsi * 2) + 0x10, low);
|
|
}
|
|
|
|
uint8_t amd64_ioapic_leg_gsi(uint8_t leg_irq) {
|
|
_assert(leg_irq && leg_irq < 16);
|
|
if (ioapic_leg_rt[leg_irq] != 0xFFFFFFFF) {
|
|
return ioapic_leg_rt[leg_irq];
|
|
}
|
|
return leg_irq;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define PCI_LINK_MAX_POSSIBLE_IRQS 16
|
|
#define PCI_MAX_IRQ_ROUTES 128
|
|
#define PCI_MAX_LINKS 32
|
|
|
|
static struct acpi_pci_link {
|
|
// Fully-qualified path name
|
|
char acpi_name[32];
|
|
uint32_t possible_count;
|
|
uint32_t possible_interrupts[PCI_LINK_MAX_POSSIBLE_IRQS];
|
|
uint32_t current_interrupt;
|
|
} pci_links[PCI_MAX_LINKS];
|
|
|
|
static struct acpi_irq_route {
|
|
uint8_t bus;
|
|
uint8_t dev;
|
|
uint8_t pin;
|
|
|
|
int is_named;
|
|
union {
|
|
char dst_name[32];
|
|
uint32_t dst_interrupt;
|
|
};
|
|
} pci_irq_routing[PCI_MAX_IRQ_ROUTES];
|
|
static size_t acpi_pci_irq_routes = 0;
|
|
|
|
static struct acpi_pci_link *acpi_pci_link_allocate(void) {
|
|
for (size_t i = 0; i < PCI_MAX_LINKS; ++i) {
|
|
if (!pci_links[i].acpi_name[0]) {
|
|
return &pci_links[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int acpi_pci_link_add_possible_interrupt(struct acpi_pci_link *link, uint32_t irq) {
|
|
if (link->possible_count == PCI_LINK_MAX_POSSIBLE_IRQS) {
|
|
return -1;
|
|
}
|
|
|
|
link->possible_interrupts[link->possible_count++] = irq;
|
|
return 0;
|
|
}
|
|
|
|
static int acpi_pci_add_named_route(uint8_t bus, uint8_t dev, uint8_t pin, const char *link_name) {
|
|
if (acpi_pci_irq_routes == PCI_MAX_IRQ_ROUTES) {
|
|
return -1;
|
|
}
|
|
_assert(strlen(link_name) < 32);
|
|
struct acpi_irq_route *route = &pci_irq_routing[acpi_pci_irq_routes++];
|
|
|
|
strcpy(route->dst_name, link_name);
|
|
route->is_named = 1;
|
|
route->bus = bus;
|
|
route->dev = dev;
|
|
route->pin = pin;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int acpi_pci_add_hard_route(uint8_t bus, uint8_t dev, uint8_t pin, uint32_t irq) {
|
|
if (acpi_pci_irq_routes == PCI_MAX_IRQ_ROUTES) {
|
|
return -1;
|
|
}
|
|
struct acpi_irq_route *route = &pci_irq_routing[acpi_pci_irq_routes++];
|
|
|
|
route->dst_interrupt = irq;
|
|
route->is_named = 0;
|
|
route->bus = bus;
|
|
route->dev = dev;
|
|
route->pin = pin;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ACPI_STATUS acpi_dev_walk_pci_link_possible_resources(ACPI_RESOURCE *Resource, void *Ctx) {
|
|
struct acpi_pci_link *link = Ctx;
|
|
|
|
switch (Resource->Type) {
|
|
case ACPI_RESOURCE_TYPE_IRQ: {
|
|
ACPI_RESOURCE_IRQ *Irq = &Resource->Data.Irq;
|
|
|
|
for (UINT32 i = 0; i < Irq->InterruptCount; ++i) {
|
|
_assert(acpi_pci_link_add_possible_interrupt(link, Irq->Interrupts[i]) == 0);
|
|
}
|
|
}
|
|
return AE_OK;
|
|
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: {
|
|
ACPI_RESOURCE_EXTENDED_IRQ *ExtendedIrq = &Resource->Data.ExtendedIrq;
|
|
|
|
for (UINT32 i = 0; i < ExtendedIrq->InterruptCount; ++i) {
|
|
_assert(acpi_pci_link_add_possible_interrupt(link, ExtendedIrq->Interrupts[i]) == 0);
|
|
}
|
|
}
|
|
return AE_OK;
|
|
case ACPI_RESOURCE_TYPE_END_TAG:
|
|
return AE_OK;
|
|
default:
|
|
panic("Unknown resource type for link: %02x\n", Resource->Type);
|
|
}
|
|
}
|
|
|
|
static ACPI_STATUS acpi_dev_walk_pci_link_current_resources(ACPI_RESOURCE *Resource, void *Ctx) {
|
|
struct acpi_pci_link *link = Ctx;
|
|
|
|
switch (Resource->Type) {
|
|
case ACPI_RESOURCE_TYPE_IRQ: {
|
|
ACPI_RESOURCE_IRQ *Irq = &Resource->Data.Irq;
|
|
if (Irq->InterruptCount == 0) {
|
|
// Leave in "Unconfigured" state
|
|
return AE_OK;
|
|
}
|
|
assert(Irq->InterruptCount == 1, "Only one current IRQ config supported now\n");
|
|
|
|
link->current_interrupt = Irq->Interrupts[0];
|
|
}
|
|
return AE_OK;
|
|
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: {
|
|
ACPI_RESOURCE_EXTENDED_IRQ *ExtendedIrq = &Resource->Data.ExtendedIrq;
|
|
if (ExtendedIrq->InterruptCount == 0) {
|
|
// Leave in "Unconfigured" state
|
|
return AE_OK;
|
|
}
|
|
assert(ExtendedIrq->InterruptCount == 1, "Only one current IRQ config supported now\n");
|
|
|
|
link->current_interrupt = ExtendedIrq->Interrupts[0];
|
|
}
|
|
return AE_OK;
|
|
case ACPI_RESOURCE_TYPE_END_TAG:
|
|
return AE_OK;
|
|
default:
|
|
panic("Unknown resource type for link: %02x\n", Resource->Type);
|
|
}
|
|
}
|
|
|
|
static ACPI_STATUS acpi_dev_walk_pci_link(ACPI_HANDLE LinkDevice, UINT32 NestingLevel, void *Ctx, void **Res) {
|
|
ACPI_STATUS ret;
|
|
char ResourceBufferData[256];
|
|
char LinkName[256];
|
|
ACPI_BUFFER ResourceBuffer = { sizeof(ResourceBufferData), ResourceBufferData };
|
|
ACPI_BUFFER LinkNameBuffer = { sizeof(LinkName), LinkName };
|
|
|
|
if (ACPI_FAILURE(ret = AcpiGetName(LinkDevice, ACPI_FULL_PATHNAME, &LinkNameBuffer))) {
|
|
return ret;
|
|
}
|
|
|
|
struct acpi_pci_link *link = acpi_pci_link_allocate();
|
|
_assert(link);
|
|
_assert(strlen(LinkName) < 32);
|
|
strcpy(link->acpi_name, LinkName);
|
|
link->possible_count = 0;
|
|
link->current_interrupt = PCI_IRQ_NO_ROUTE;
|
|
|
|
if (ACPI_FAILURE(ret = AcpiGetPossibleResources(LinkDevice, &ResourceBuffer))) {
|
|
return ret;
|
|
}
|
|
|
|
if (ACPI_FAILURE(ret = AcpiWalkResourceBuffer(&ResourceBuffer, acpi_dev_walk_pci_link_possible_resources, link))) {
|
|
return ret;
|
|
}
|
|
|
|
if (ACPI_FAILURE(ret = AcpiGetCurrentResources(LinkDevice, &ResourceBuffer))) {
|
|
return ret;
|
|
}
|
|
|
|
if (ACPI_FAILURE(ret = AcpiWalkResourceBuffer(&ResourceBuffer, acpi_dev_walk_pci_link_current_resources, link))) {
|
|
return ret;
|
|
}
|
|
|
|
return AE_OK;
|
|
}
|
|
|
|
static ACPI_STATUS acpi_dev_walk_pci_buses(ACPI_HANDLE BusDevice, UINT32 NestingLevel, void *Ctx, void **Res) {
|
|
ACPI_STATUS ret;
|
|
|
|
char BusName[256];
|
|
ACPI_OBJECT BusNumberObject;
|
|
ACPI_PCI_ROUTING_TABLE IrqRoutingTable[256];
|
|
ACPI_DEVICE_INFO *DeviceInfo = NULL;
|
|
|
|
ACPI_BUFFER BusNameBuffer = { sizeof(BusName), BusName };
|
|
ACPI_BUFFER BusNumberObjectBuffer = { sizeof(BusNumberObject), &BusNumberObject };
|
|
ACPI_BUFFER IrqRoutingTableBuffer = { sizeof(IrqRoutingTable), IrqRoutingTable };
|
|
|
|
if (ACPI_FAILURE(ret = AcpiGetName(BusDevice, ACPI_FULL_PATHNAME, &BusNameBuffer))) {
|
|
return ret;
|
|
}
|
|
|
|
if (ACPI_FAILURE(ret = AcpiGetObjectInfo(BusDevice, &DeviceInfo))) {
|
|
return ret;
|
|
}
|
|
|
|
UINT8 Bus = 0;
|
|
int assigned_bus_number = 0;
|
|
|
|
// Try to get bus number from _BBN (Base Bus Number)
|
|
if (ACPI_FAILURE(ret = AcpiEvaluateObjectTyped(BusDevice, "_BBN", NULL, &BusNumberObjectBuffer, ACPI_TYPE_INTEGER))) {
|
|
if (ret != AE_NOT_FOUND) {
|
|
return ret;
|
|
}
|
|
} else {
|
|
assigned_bus_number = 1;
|
|
kdebug("%s._BBN = %p\n", BusName, BusNumberObject.Integer.Value);
|
|
if (BusNumberObject.Integer.Value > 0xFF) {
|
|
panic("Bus number is too high: %p\n", BusNumberObject.Integer.Value);
|
|
}
|
|
Bus = BusNumberObject.Integer.Value;
|
|
}
|
|
|
|
kdebug("PCI Root Bus: 0x%02x (%s)\n", Bus, BusName);
|
|
kdebug(" _HID = %s, _CLS = %s\n",
|
|
((DeviceInfo->Valid & ACPI_VALID_HID) ? DeviceInfo->HardwareId.String : "NONE"),
|
|
((DeviceInfo->Valid & ACPI_VALID_CLS) ? DeviceInfo->ClassCode.String : "NONE"));
|
|
if (DeviceInfo->Valid & ACPI_VALID_CID) {
|
|
for (size_t i = 0; i < DeviceInfo->CompatibleIdList.Count; ++i) {
|
|
kdebug(" _CID = %s\n", DeviceInfo->CompatibleIdList.Ids[i].String);
|
|
}
|
|
}
|
|
|
|
//pci_add_root_bus(Bus);
|
|
|
|
// Get _PRT
|
|
if (ACPI_FAILURE(ret = AcpiGetIrqRoutingTable(BusDevice, &IrqRoutingTableBuffer))) {
|
|
return ret;
|
|
}
|
|
|
|
UINT32 Offset = 0;
|
|
|
|
while (Offset < IrqRoutingTableBuffer.Length) {
|
|
ACPI_PCI_ROUTING_TABLE *Route = (ACPI_PCI_ROUTING_TABLE *) ((uintptr_t) IrqRoutingTable + Offset);
|
|
|
|
if (!Route->Length) {
|
|
break;
|
|
}
|
|
|
|
UINT8 Device = (Route->Address >> 16) & 0xFF;
|
|
_assert((Route->Address & 0xFFFF) == 0xFFFF);
|
|
|
|
// Check if it's a "hard" route, meaning it's not a reference to a PCI Link device but
|
|
// GSI number is specified directly instead
|
|
if (Route->SourceIndex == 0) {
|
|
// It's a LNKx reference
|
|
_assert(acpi_pci_add_named_route(Bus, Device, Route->Pin, Route->Source) == 0);
|
|
} else {
|
|
// IRQ number specified directly
|
|
_assert(acpi_pci_add_hard_route(Bus, Device, Route->Pin, Route->SourceIndex) == 0);
|
|
}
|
|
|
|
Offset += Route->Length;
|
|
}
|
|
|
|
return AE_OK;
|
|
}
|
|
|
|
int amd64_pci_init_irqs(void) {
|
|
ACPI_STATUS ret;
|
|
|
|
// 1. Walk links
|
|
if (ACPI_FAILURE(ret = AcpiGetDevices("PNP0C0F", acpi_dev_walk_pci_link, NULL, NULL))) {
|
|
kerror("ACPI INIT failure %s\n", AcpiFormatException(ret));
|
|
return -1;
|
|
}
|
|
|
|
// 2. Walk buses
|
|
// Will also match PCI Express buses
|
|
if (ACPI_FAILURE(ret = AcpiGetDevices("PNP0A03" /* PCI Bus */, acpi_dev_walk_pci_buses, NULL, NULL))) {
|
|
kerror("ACPI INIT failure %s\n", AcpiFormatException(ret));
|
|
return -1;
|
|
}
|
|
|
|
// Dump PCI Link table
|
|
kdebug("PCI Interrupt Link Devices:\n");
|
|
for (size_t i = 0; i < 8; ++i) {
|
|
if (pci_links[i].acpi_name[0]) {
|
|
if (pci_links[i].current_interrupt == PCI_IRQ_NO_ROUTE) {
|
|
kdebug("%s: Unmapped\n", pci_links[i].acpi_name);
|
|
} else {
|
|
kdebug("%s: Current IRQ %u\n", pci_links[i].acpi_name, pci_links[i].current_interrupt);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t amd64_pci_link_route(const char *link_name) {
|
|
for (size_t i = 0; i < PCI_MAX_LINKS; ++i) {
|
|
if (!strcmp(link_name, pci_links[i].acpi_name)) {
|
|
return pci_links[i].current_interrupt;
|
|
}
|
|
}
|
|
|
|
// No such link exists
|
|
kdebug("No link named %s\n", link_name);
|
|
return PCI_IRQ_INVALID;
|
|
}
|
|
|
|
uint32_t amd64_pci_pin_irq_route(struct pci_device *dev, uint8_t pin) {
|
|
uint8_t b, d, f;
|
|
pci_get_device_address(dev, &b, &d, &f);
|
|
|
|
for (size_t i = 0; i < acpi_pci_irq_routes; ++i) {
|
|
struct acpi_irq_route *route = &pci_irq_routing[i];
|
|
|
|
if (route->bus == b && route->dev == d && route->pin == pin) {
|
|
// Found matching route
|
|
if (route->is_named) {
|
|
// Resolve LNKx
|
|
return amd64_pci_link_route(route->dst_name);
|
|
} else {
|
|
return route->dst_interrupt;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No such route exists
|
|
kerror("No PCI IRQ route %02x:%02x:%02x:INT%c#\n", b, d, f, pin + 'A');
|
|
return PCI_IRQ_INVALID;
|
|
}
|