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:
Justin C. Miller
2018-05-12 18:27:13 -07:00
parent c9277e4b12
commit 289104cde0
12 changed files with 267 additions and 47 deletions

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
// 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();
return true;
}
// This is where interrupt handler would begin
void
port::handle_interrupt()
{
log::debug(logs::driver, "AHCI port %d got an interrupt");
// 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

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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();

View File

@@ -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");

View File

@@ -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)

View File

@@ -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;