diff --git a/src/kernel/ahci/driver.cpp b/src/kernel/ahci/driver.cpp index e9f915b..ba8d0ce 100644 --- a/src/kernel/ahci/driver.cpp +++ b/src/kernel/ahci/driver.cpp @@ -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; +} diff --git a/src/kernel/ahci/driver.h b/src/kernel/ahci/driver.h index 1099f39..c19b4b8 100644 --- a/src/kernel/ahci/driver.h +++ b/src/kernel/ahci/driver.h @@ -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 m_devices; }; diff --git a/src/kernel/ahci/hba.cpp b/src/kernel/ahci/hba.cpp index e5b0050..ea42258 100644 --- a/src/kernel/ahci/hba.cpp +++ b/src/kernel/ahci/hba.cpp @@ -1,6 +1,7 @@ #include +#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(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(bar5 & ~0xfffull); pm->map_offset_pointer(reinterpret_cast(&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(m_data->cap); unsigned ports = (icap & 0xf) + 1; @@ -65,24 +76,42 @@ hba::hba(pci_device *device) port_data *pd = reinterpret_cast( 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 diff --git a/src/kernel/ahci/hba.h b/src/kernel/ahci/hba.h index ff0df41..44f3248 100644 --- a/src/kernel/ahci/hba.h +++ b/src/kernel/ahci/hba.h @@ -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; diff --git a/src/kernel/ahci/port.cpp b/src/kernel/ahci/port.cpp index 404c2ac..393d003 100644 --- a/src/kernel/ahci/port.cpp +++ b/src/kernel/ahci/port.cpp @@ -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 diff --git a/src/kernel/ahci/port.h b/src/kernel/ahci/port.h index 6fdf9cd..4b3b914 100644 --- a/src/kernel/ahci/port.h +++ b/src/kernel/ahci/port.h @@ -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. diff --git a/src/kernel/device_manager.cpp b/src/kernel/device_manager.cpp index 76581c7..7de8921 100644 --- a/src/kernel/device_manager.cpp +++ b/src/kernel/device_manager.cpp @@ -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(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(vector)); + return true; +} diff --git a/src/kernel/device_manager.h b/src/kernel/device_manager.h index e16ec23..cefea07 100644 --- a/src/kernel/device_manager.h +++ b/src/kernel/device_manager.h @@ -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 m_ioapics; kutil::vector m_pci; kutil::vector m_devices; + struct irq_allocation + { + const char *name; + irq_callback callback; + void *data; + }; + kutil::vector m_irqs; + + static device_manager s_instance; + device_manager() = delete; device_manager(const device_manager &) = delete; + device_manager operator=(const device_manager &) = delete; }; diff --git a/src/kernel/interrupts.cpp b/src/kernel/interrupts.cpp index a4c300f..35a4fe3 100644 --- a/src/kernel/interrupts.cpp +++ b/src/kernel/interrupts.cpp @@ -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(); diff --git a/src/kernel/main.cpp b/src/kernel/main.cpp index 74ce5be..781c1a9 100644 --- a/src/kernel/main.cpp +++ b/src/kernel/main.cpp @@ -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"); diff --git a/src/kernel/pci.cpp b/src/kernel/pci.cpp index 3f14029..577dc84 100644 --- a/src/kernel/pci.cpp +++ b/src/kernel/pci.cpp @@ -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(&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(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(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(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) diff --git a/src/kernel/pci.h b/src/kernel/pci.h index a0ba480..263ea41 100644 --- a/src/kernel/pci.h +++ b/src/kernel/pci.h @@ -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;