mirror of
https://github.com/justinian/jsix.git
synced 2025-12-10 08:24:32 -08:00
Enable AHCI interrupts.
* Implement MSI style interrupts * Move interrupt handling to device_manager for IRQs * Give device_manager the ability to allocate IRQs * Move achi::port to an interrupt-based scheme
This commit is contained in:
@@ -14,6 +14,15 @@ ahci_driver::register_device(pci_device *device)
|
||||
log::info(logs::driver, "AHCI registering device %d:%d:%d:",
|
||||
device->bus(), device->device(), device->function());
|
||||
|
||||
m_devices.emplace(device);
|
||||
ahci::hba &hba = m_devices.emplace(device);
|
||||
}
|
||||
|
||||
ahci::port *
|
||||
ahci_driver::find_disk()
|
||||
{
|
||||
for (auto &hba : m_devices) {
|
||||
ahci::port *d = hba.find_disk();
|
||||
if (d) return d;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ public:
|
||||
/// \arg device The PCI device to remove
|
||||
void unregister_device(pci_device *device);
|
||||
|
||||
/// Debug: find the first disk
|
||||
ahci::port * find_disk();
|
||||
|
||||
private:
|
||||
kutil::vector<ahci::hba> m_devices;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <stdint.h>
|
||||
#include "ahci/ata.h"
|
||||
#include "ahci/hba.h"
|
||||
#include "console.h"
|
||||
#include "device_manager.h"
|
||||
#include "log.h"
|
||||
#include "page_manager.h"
|
||||
#include "pci.h"
|
||||
@@ -46,6 +47,13 @@ struct hba_data
|
||||
} __attribute__ ((packed));
|
||||
|
||||
|
||||
void irq_cb(void *data)
|
||||
{
|
||||
hba *h = reinterpret_cast<hba *>(data);
|
||||
h->handle_interrupt();
|
||||
}
|
||||
|
||||
|
||||
hba::hba(pci_device *device)
|
||||
{
|
||||
page_manager *pm = page_manager::get();
|
||||
@@ -54,6 +62,9 @@ hba::hba(pci_device *device)
|
||||
m_data = reinterpret_cast<hba_data *>(bar5 & ~0xfffull);
|
||||
pm->map_offset_pointer(reinterpret_cast<void **>(&m_data), 0x2000);
|
||||
|
||||
if (! bitfield_has(m_data->cap, hba_cap::ahci_only))
|
||||
m_data->host_control |= 0x80000000; // Enable AHCI mode
|
||||
|
||||
uint32_t icap = static_cast<uint32_t>(m_data->cap);
|
||||
|
||||
unsigned ports = (icap & 0xf) + 1;
|
||||
@@ -65,24 +76,42 @@ hba::hba(pci_device *device)
|
||||
port_data *pd = reinterpret_cast<port_data *>(
|
||||
kutil::offset_pointer(m_data, 0x100));
|
||||
|
||||
bool needs_interrupt = false;
|
||||
m_ports.ensure_capacity(ports);
|
||||
for (unsigned i = 0; i < ports; ++i) {
|
||||
bool impl = ((m_data->port_impl & (1 << i)) != 0);
|
||||
port &p = m_ports.emplace(i, kutil::offset_pointer(pd, 0x80 * i), impl);
|
||||
if (p.get_state() == port::state::active) {
|
||||
uint8_t buf[512];
|
||||
p.read(1, sizeof(buf), buf);
|
||||
if (p.get_state() == port::state::active)
|
||||
needs_interrupt = true;
|
||||
}
|
||||
|
||||
console *cons = console::get();
|
||||
uint8_t *p = &buf[0];
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
cons->printf(" %02x", *p++);
|
||||
}
|
||||
cons->putc('\n');
|
||||
}
|
||||
if (needs_interrupt) {
|
||||
device_manager::get().allocate_msi("AHCI Device", *device, irq_cb, this);
|
||||
m_data->host_control |= 0x02; // enable interrupts
|
||||
}
|
||||
}
|
||||
|
||||
port *
|
||||
hba::find_disk()
|
||||
{
|
||||
for (auto &port : m_ports) {
|
||||
if (port.get_state() == port::state::active &&
|
||||
port.get_type() == sata_signature::sata_drive)
|
||||
return &port;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
hba::handle_interrupt()
|
||||
{
|
||||
for (auto &port : m_ports) {
|
||||
if (m_data->int_status & (1 << port.index())) {
|
||||
port.handle_interrupt();
|
||||
}
|
||||
}
|
||||
m_data->int_status = 0;
|
||||
}
|
||||
|
||||
} // namespace ahci
|
||||
|
||||
@@ -22,6 +22,12 @@ public:
|
||||
/// \arg device The PCI device for this HBA
|
||||
hba(pci_device *device);
|
||||
|
||||
/// Interrupt handler.
|
||||
void handle_interrupt();
|
||||
|
||||
/// Debug: find the first disk
|
||||
port * find_disk();
|
||||
|
||||
private:
|
||||
pci_device *m_device;
|
||||
hba_data *m_data;
|
||||
|
||||
@@ -154,6 +154,7 @@ port::update()
|
||||
|
||||
rebase();
|
||||
m_pending.set_size(32);
|
||||
m_data->interrupt_enable = 1;
|
||||
} else {
|
||||
m_state = state::inactive;
|
||||
}
|
||||
@@ -290,25 +291,20 @@ port::issue_command(int slot)
|
||||
// Set bit in CI. Note that only new bits should be written, not
|
||||
// previous state.
|
||||
m_data->cmd_issue = (1 << slot);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: interrupt-based
|
||||
while (true) {
|
||||
if ((m_data->cmd_issue & (1 << slot)) == 0) break;
|
||||
if (m_data->interrupt_status & 0x40000000) {
|
||||
log::error(logs::driver, "AHCI task file error");
|
||||
// TODO: clean up!
|
||||
return false;
|
||||
}
|
||||
io_wait();
|
||||
}
|
||||
void
|
||||
port::handle_interrupt()
|
||||
{
|
||||
log::debug(logs::driver, "AHCI port %d got an interrupt");
|
||||
|
||||
// This is where interrupt handler would begin
|
||||
// TODO: handle other states in interrupt_status
|
||||
|
||||
if (m_data->interrupt_status & 0x40000000) {
|
||||
log::error(logs::driver, "AHCI task file error");
|
||||
// TODO: clean up!
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
log::debug(logs::driver, "AHCI interrupt status: %08lx %08lx",
|
||||
@@ -329,8 +325,7 @@ port::issue_command(int slot)
|
||||
p.type = command_type::none;
|
||||
p.data = nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
m_data->interrupt_status = m_data->interrupt_status;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -27,15 +27,19 @@ public:
|
||||
/// Destructor
|
||||
~port();
|
||||
|
||||
/// Get the index of this port on the HBA
|
||||
/// \returns The port index
|
||||
inline uint8_t index() const { return m_index; }
|
||||
|
||||
enum class state : uint8_t { unimpl, inactive, active };
|
||||
|
||||
/// Get the current state of this device
|
||||
/// \returns An enum representing the state
|
||||
state get_state() const { return m_state; }
|
||||
inline state get_state() const { return m_state; }
|
||||
|
||||
/// Get the type signature of this device
|
||||
/// \returns An enum representing the type of device
|
||||
sata_signature get_type() const { return m_type; }
|
||||
inline sata_signature get_type() const { return m_type; }
|
||||
|
||||
/// Update the state of this object from the register data
|
||||
void update();
|
||||
@@ -56,6 +60,9 @@ public:
|
||||
/// \returns True if the command succeeded
|
||||
bool read(uint64_t sector, size_t length, void *dest);
|
||||
|
||||
/// Handle an incoming interrupt
|
||||
void handle_interrupt();
|
||||
|
||||
private:
|
||||
/// Rebase the port command structures to a new location in system
|
||||
/// memory, to be allocated from the page manager.
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
#include "acpi_tables.h"
|
||||
#include "ahci/driver.h"
|
||||
#include "apic.h"
|
||||
#include "console.h"
|
||||
#include "device_manager.h"
|
||||
#include "interrupts.h"
|
||||
#include "log.h"
|
||||
#include "memory.h"
|
||||
#include "page_manager.h"
|
||||
|
||||
|
||||
static const char expected_signature[] = "RSD PTR ";
|
||||
|
||||
device_manager device_manager::s_instance(nullptr);
|
||||
ahci_driver ahcid;
|
||||
|
||||
struct acpi1_rsdp
|
||||
@@ -56,6 +59,21 @@ acpi_table_header::validate(uint32_t expected_type) const
|
||||
}
|
||||
|
||||
|
||||
void irq2_callback(void *)
|
||||
{
|
||||
console *cons = console::get();
|
||||
cons->set_color(11);
|
||||
cons->puts(".");
|
||||
cons->set_color();
|
||||
}
|
||||
|
||||
void irq4_callback(void *)
|
||||
{
|
||||
// TODO: move this to a real serial driver
|
||||
console *cons = console::get();
|
||||
cons->echo();
|
||||
}
|
||||
|
||||
|
||||
device_manager::device_manager(const void *root_table) :
|
||||
m_lapic(nullptr)
|
||||
@@ -81,6 +99,11 @@ device_manager::device_manager(const void *root_table) :
|
||||
kassert(sum == 0, "ACPI 2.0 RSDP checksum mismatch.");
|
||||
|
||||
load_xsdt(reinterpret_cast<const acpi_xsdt *>(acpi2->xsdt_address));
|
||||
|
||||
m_irqs.ensure_capacity(32);
|
||||
m_irqs.set_size(16);
|
||||
m_irqs[2] = {"Clock interrupt", irq2_callback, nullptr};
|
||||
m_irqs[4] = {"Serial interrupt", irq4_callback, nullptr};
|
||||
}
|
||||
|
||||
ioapic *
|
||||
@@ -281,3 +304,19 @@ device_manager::init_drivers()
|
||||
ahcid.register_device(&device);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ struct acpi_mcfg;
|
||||
class lapic;
|
||||
class ioapic;
|
||||
|
||||
using irq_callback = void (*)(void *);
|
||||
|
||||
|
||||
/// Manager for all system hardware devices
|
||||
class device_manager
|
||||
@@ -19,6 +21,10 @@ public:
|
||||
/// \arg root_table Pointer to the ACPI RSDP
|
||||
device_manager(const void *root_table);
|
||||
|
||||
/// Get the system global device manager.
|
||||
/// \returns A reference to the system device manager
|
||||
static device_manager & get() { return s_instance; }
|
||||
|
||||
/// Get the LAPIC
|
||||
/// \returns An object representing the local APIC
|
||||
lapic * get_lapic() { return m_lapic; }
|
||||
@@ -32,6 +38,33 @@ public:
|
||||
/// Intialize drivers for the current device list.
|
||||
void init_drivers();
|
||||
|
||||
/// Allocate an MSI IRQ for a device
|
||||
/// \arg name Name of the interrupt, for display to user
|
||||
/// \arg device Device this MSI is being allocated for
|
||||
/// \arg cb Callback to call when the interrupt is received
|
||||
/// \arg data Data to pass to the callback
|
||||
/// \returns True if an interrupt was allocated successfully
|
||||
bool allocate_msi(
|
||||
const char *name,
|
||||
pci_device &device,
|
||||
irq_callback cb,
|
||||
void *data);
|
||||
|
||||
/// Dispatch an IRQ interrupt
|
||||
/// \arg irq The irq number of the interrupt
|
||||
/// \returns True if the interrupt was handled
|
||||
inline bool dispatch_irq(uint8_t irq)
|
||||
{
|
||||
if (irq < m_irqs.count()) {
|
||||
irq_allocation &cba = m_irqs[irq];
|
||||
if (cba.callback) {
|
||||
cba.callback(cba.data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
/// Parse the ACPI XSDT and load relevant sub-tables.
|
||||
/// \arg xsdt Pointer to the XSDT from the firmware
|
||||
@@ -49,12 +82,27 @@ private:
|
||||
/// device list. The device list is destroyed and rebuilt.
|
||||
void probe_pci();
|
||||
|
||||
/// Handle a bad IRQ. Called when an interrupt is dispatched
|
||||
/// that has no callback.
|
||||
void bad_irq(uint8_t irq);
|
||||
|
||||
lapic *m_lapic;
|
||||
kutil::vector<ioapic *> m_ioapics;
|
||||
|
||||
kutil::vector<pci_group> m_pci;
|
||||
kutil::vector<pci_device> m_devices;
|
||||
|
||||
struct irq_allocation
|
||||
{
|
||||
const char *name;
|
||||
irq_callback callback;
|
||||
void *data;
|
||||
};
|
||||
kutil::vector<irq_allocation> m_irqs;
|
||||
|
||||
static device_manager s_instance;
|
||||
|
||||
device_manager() = delete;
|
||||
device_manager(const device_manager &) = delete;
|
||||
device_manager operator=(const device_manager &) = delete;
|
||||
};
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
#include "kutil/enum_bitfields.h"
|
||||
#include "kutil/memory.h"
|
||||
#include "console.h"
|
||||
#include "device_manager.h"
|
||||
#include "interrupts.h"
|
||||
#include "io.h"
|
||||
#include "log.h"
|
||||
#include "page_manager.h"
|
||||
|
||||
enum class gdt_flags : uint8_t
|
||||
{
|
||||
@@ -319,22 +319,9 @@ irq_handler(registers regs)
|
||||
{
|
||||
console *cons = console::get();
|
||||
uint8_t irq = get_irq(regs.interrupt);
|
||||
|
||||
switch (irq) {
|
||||
case 2:
|
||||
if (! device_manager::get().dispatch_irq(irq)) {
|
||||
cons->set_color(11);
|
||||
cons->puts(".");
|
||||
cons->set_color();
|
||||
break;
|
||||
|
||||
case 4:
|
||||
// TODO: move this to a real serial driver
|
||||
cons->echo();
|
||||
break;
|
||||
|
||||
default:
|
||||
cons->set_color(11);
|
||||
cons->printf("\nReceived IRQ interrupt: %d (vec %d)\n",
|
||||
cons->printf("\nReceived unknown IRQ: %d (vec %d)\n",
|
||||
irq, regs.interrupt);
|
||||
cons->set_color();
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
#include "kutil/assert.h"
|
||||
#include "kutil/memory.h"
|
||||
|
||||
#include "ahci/driver.h"
|
||||
#include "ahci/port.h"
|
||||
|
||||
#include "console.h"
|
||||
#include "cpu.h"
|
||||
#include "device_manager.h"
|
||||
@@ -23,6 +27,8 @@ extern "C" {
|
||||
void *__bss_start, *__bss_end;
|
||||
}
|
||||
|
||||
extern ahci_driver ahcid;
|
||||
|
||||
extern void __kernel_assert(const char *, unsigned, const char *);
|
||||
|
||||
void
|
||||
@@ -79,7 +85,10 @@ kernel_main(popcorn_data *header)
|
||||
// pager->dump_blocks();
|
||||
|
||||
interrupts_init();
|
||||
device_manager devices(header->acpi_table);
|
||||
|
||||
device_manager *devices =
|
||||
new (&device_manager::get()) device_manager(header->acpi_table);
|
||||
|
||||
interrupts_enable();
|
||||
|
||||
cpu_id cpu;
|
||||
@@ -87,7 +96,25 @@ kernel_main(popcorn_data *header)
|
||||
log::info(logs::boot, "CPU Family %x Model %x Stepping %x",
|
||||
cpu.family(), cpu.model(), cpu.stepping());
|
||||
|
||||
devices.init_drivers();
|
||||
devices->init_drivers();
|
||||
|
||||
ahci::port *disk = ahcid.find_disk();
|
||||
if (disk) {
|
||||
uint8_t buf[512];
|
||||
kutil::memset(buf, 0, 512);
|
||||
|
||||
disk->read(1, sizeof(buf), buf);
|
||||
while (buf[0] == 0) io_wait();
|
||||
|
||||
console *cons = console::get();
|
||||
uint8_t *p = &buf[0];
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
cons->printf(" %02x", *p++);
|
||||
}
|
||||
cons->putc('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// do_error_1();
|
||||
// __asm__ __volatile__("int $15");
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
#include "interrupts.h"
|
||||
#include "pci.h"
|
||||
|
||||
struct pci_cap_msi :
|
||||
public pci_cap
|
||||
{
|
||||
uint16_t control;
|
||||
uint64_t address;
|
||||
uint16_t data;
|
||||
uint16_t reserved;
|
||||
uint32_t mask;
|
||||
uint32_t pending;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct pci_cap_msix :
|
||||
public pci_cap
|
||||
{
|
||||
uint16_t control;
|
||||
uint64_t address;
|
||||
uint16_t data;
|
||||
uint16_t reserved;
|
||||
uint32_t mask;
|
||||
uint32_t pending;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
|
||||
pci_device::pci_device() :
|
||||
m_base(nullptr),
|
||||
@@ -20,6 +42,7 @@ pci_device::pci_device() :
|
||||
|
||||
pci_device::pci_device(pci_group &group, uint8_t bus, uint8_t device, uint8_t func) :
|
||||
m_base(group.base_for(bus, device, func)),
|
||||
m_msi(nullptr),
|
||||
m_bus_addr(bus_addr(bus, device, func)),
|
||||
m_irq(isr::isrIgnoreF)
|
||||
{
|
||||
@@ -34,8 +57,24 @@ pci_device::pci_device(pci_group &group, uint8_t bus, uint8_t device, uint8_t fu
|
||||
m_header_type = (m_base[3] >> 16) & 0x7f;
|
||||
m_multi = ((m_base[3] >> 16) & 0x80) == 0x80;
|
||||
|
||||
uint16_t *command = reinterpret_cast<uint16_t *>(&m_base[1]);
|
||||
*command |= 0x400; // Mask old INTx style interrupts
|
||||
|
||||
log::info(logs::device, "Found PCIe device at %02d:%02d:%d of type %d.%d id %04x:%04x",
|
||||
bus, device, func, m_class, m_subclass, m_vendor, m_device);
|
||||
|
||||
// Walk the extended capabilities list
|
||||
uint8_t next = m_base[13] & 0xff;
|
||||
while (next) {
|
||||
pci_cap *cap = reinterpret_cast<pci_cap *>(kutil::offset_pointer(m_base, next));
|
||||
next = cap->next;
|
||||
|
||||
if (cap->id == pci_cap::type::msi) {
|
||||
m_msi = cap;
|
||||
pci_cap_msi *mcap = reinterpret_cast<pci_cap_msi *>(cap);
|
||||
mcap->control |= ~0x1; // Mask interrupts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
@@ -66,6 +105,19 @@ pci_device::set_bar(unsigned i, uint32_t val)
|
||||
m_base[4+i] = val;
|
||||
}
|
||||
|
||||
void
|
||||
pci_device::write_msi_regs(addr_t address, uint16_t data)
|
||||
{
|
||||
kassert(m_msi, "Tried to write MSI for a device without that cap");
|
||||
if (m_msi->id == pci_cap::type::msi) {
|
||||
pci_cap_msi *mcap = reinterpret_cast<pci_cap_msi *>(m_msi);
|
||||
mcap->address = address;
|
||||
mcap->data = data;
|
||||
mcap->control |= 1;
|
||||
} else {
|
||||
kassert(0, "MIS-X is NYI");
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
pci_group::has_device(uint8_t bus, uint8_t device, uint8_t func)
|
||||
|
||||
@@ -7,6 +7,18 @@
|
||||
struct pci_group;
|
||||
enum class isr : uint8_t;
|
||||
|
||||
struct pci_cap
|
||||
{
|
||||
enum class type : uint8_t
|
||||
{
|
||||
msi = 0x05,
|
||||
msix = 0x11
|
||||
};
|
||||
|
||||
type id;
|
||||
uint8_t next;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
|
||||
/// Information about a discovered PCIe device
|
||||
class pci_device
|
||||
@@ -60,6 +72,11 @@ public:
|
||||
/// \arg val The value to write
|
||||
void set_bar(unsigned i, uint32_t val);
|
||||
|
||||
/// Write to the MSI registers
|
||||
/// \arg addr The address to write to the MSI address registers
|
||||
/// \arg data The value to write to the MSI data register
|
||||
void write_msi_regs(addr_t addr, uint16_t data);
|
||||
|
||||
/// Get a bus address, given the bus/device/function numbers.
|
||||
/// \arg bus Number of the bus
|
||||
/// \arg device Index of the device on the bus
|
||||
@@ -72,6 +89,7 @@ public:
|
||||
|
||||
private:
|
||||
uint32_t *m_base;
|
||||
pci_cap *m_msi;
|
||||
|
||||
/// Bus address: 15:8 bus, 7:3 device, 2:0 device
|
||||
uint16_t m_bus_addr;
|
||||
|
||||
Reference in New Issue
Block a user