diff --git a/src/kernel/ahci.cpp b/src/kernel/ahci.cpp deleted file mode 100644 index 03b94f2..0000000 --- a/src/kernel/ahci.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include "kutil/enum_bitfields.h" -#include "ahci.h" -#include "log.h" -#include "device_manager.h" -#include "page_manager.h" - -enum class ata_status : uint8_t -{ - error = 0x01, - index = 0x02, - corrected = 0x04, - data_ready = 0x08, - seek_done = 0x10, - fault = 0x20, - ready = 0x40, - busy = 0x80 -}; -IS_BITFIELD(ata_status); - -enum class ata_error : uint8_t -{ - amnf = 0x01, // Address mark not found - tkznf = 0x02, // Track 0 not found - abort = 0x04, // Command abort - mcr = 0x08, // No media - idnf = 0x10, // Id not found - mc = 0x20, // No media - unc = 0x40, // Uncorrectable - bbk = 0x80, // Bad sector -}; -IS_BITFIELD(ata_error); - -enum class ata_cmd : uint8_t -{ - read_pio = 0x20, - read_pio_ext = 0x24, - read_dma = 0xC8, - read_dma_ext = 0x25, - write_pio = 0x30, - write_pio_ext = 0x34, - write_dma = 0xCA, - write_dma_ext = 0x35, - cache_flush = 0xE7, - cache_flush_ext = 0xEA, - packet = 0xA0, - identify_packet = 0xA1, - identify = 0xEC -}; - -struct ahci_port_data -{ - uint64_t command_base; - uint64_t fis_base; - uint32_t interrupt_status; - uint32_t interrupt_enable; - uint32_t command; - uint32_t reserved; - uint32_t task_file; - uint32_t signature; - uint32_t serial_status; - uint32_t serial_control; - uint32_t serial_error; -} __attribute__ ((packed)); - - -ahci_port::ahci_port(ahci_port_data *data, bool impl) : - m_data(data), - m_state(state::unimpl) -{ - if (impl) { - m_state = state::inactive; - update(); - } -} - -void -ahci_port::update() -{ - if (m_state == state::unimpl) return; - - uint32_t detected = m_data->serial_status & 0x0f; - uint32_t power = (m_data->serial_status >> 8) & 0x0f; - - if (detected == 0x3 && power == 0x1) - m_state = state::active; - else - m_state = state::inactive; - -} - - -enum class ahci_hba::cap : uint32_t -{ - cccs = 0x00000080, // Command completion coalescing - sam = 0x00040000, // ACHI-only mode - sclo = 0x01000000, // Command list override - ssntf = 0x40000000, // SNotification register - sncq = 0x40000000, // Native command queuing - s64a = 0x80000000 // 64bit addressing -}; -IS_BITFIELD(ahci_hba::cap); - -enum class ahci_hba::cap2 : uint32_t -{ - boh = 0x00000001 // BIOS OS hand-off -}; -IS_BITFIELD(ahci_hba::cap2); - - - -ahci_hba::ahci_hba(pci_device *device) -{ - page_manager *pm = page_manager::get(); - - uint32_t bar5 = device->get_bar(5); - m_bara = reinterpret_cast(bar5 & ~0xfffull); - pm->map_offset_pointer(reinterpret_cast(&m_bara), 0x2000); - - ahci_port_data *port_data = reinterpret_cast( - kutil::offset_pointer(m_bara, 0x100)); - - uint32_t cap_reg = m_bara[0]; - m_cap = static_cast(cap_reg); - m_cap2 = static_cast(m_bara[6]); - - uint32_t ports = (cap_reg & 0xf) + 1; - uint32_t slots = ((cap_reg >> 8) & 0x1f) + 1; - - log::info(logs::driver, " %d ports", ports); - log::info(logs::driver, " %d command slots", slots); - - m_ports.ensure_capacity(ports); - - uint32_t pi = m_bara[3]; - for (int i = 0; i < ports; ++i) { - bool impl = ((pi & (1 << i)) != 0); - m_ports.emplace(&port_data[i], impl); - } -} - - -ahci_driver::ahci_driver() -{ -} - -void -ahci_driver::register_device(pci_device *device) -{ - log::info(logs::driver, "AHCI registering device %d:%d:%d:", - device->bus(), device->device(), device->function()); - - ahci_hba &hba = m_devices.emplace(device); -} diff --git a/src/kernel/ahci.h b/src/kernel/ahci.h deleted file mode 100644 index 566163f..0000000 --- a/src/kernel/ahci.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once -/// \file ahci.h -/// AHCI driver and related definitions -#include "kutil/vector.h" - -class pci_device; -struct ahci_port_data; - - -/// A port on an AHCI HBA -class ahci_port -{ -public: - /// Constructor. - /// \arg data Pointer to the device's registers for this port - /// \arg impl Whether this port is marked as implemented in the HBA - ahci_port(ahci_port_data *data, bool impl); - - enum class state { unimpl, inactive, active }; - - /// Get the current state of this device - /// \returns An enum representing the state - state get_state() const { return m_state; } - - /// Update the state of this object from the register data - void update(); - -private: - ahci_port_data *m_data; - state m_state; -}; - - -/// An AHCI host bus adapter -class ahci_hba -{ -public: - /// Constructor. - /// \arg device The PCI device for this HBA - ahci_hba(pci_device *device); - -private: - enum class cap : uint32_t; - enum class cap2 : uint32_t; - - pci_device *m_device; - uint32_t *m_bara; - kutil::vector m_ports; - - cap m_cap; - cap2 m_cap2; -}; - - -/// Basic AHCI driver -class ahci_driver -{ -public: - /// Constructor. - ahci_driver(); - - /// Register a device with the driver - /// \arg device The PCI device to handle - void register_device(pci_device *device); - - /// Unregister a device from the driver - /// \arg device The PCI device to remove - void unregister_device(pci_device *device); - -private: - kutil::vector m_devices; -}; diff --git a/src/kernel/ahci/ata.h b/src/kernel/ahci/ata.h new file mode 100644 index 0000000..e8f4212 --- /dev/null +++ b/src/kernel/ahci/ata.h @@ -0,0 +1,57 @@ +#pragma once +/// \file ata.h +/// Definitions for ATA codes +#include +#include "kutil/enum_bitfields.h" + +namespace ahci { + + +enum class ata_status : uint8_t +{ + error = 0x01, + index = 0x02, + corrected = 0x04, + data_ready = 0x08, + seek_done = 0x10, + fault = 0x20, + ready = 0x40, + busy = 0x80 +}; + + +enum class ata_error : uint8_t +{ + amnf = 0x01, // Address mark not found + tkznf = 0x02, // Track 0 not found + abort = 0x04, // Command abort + mcr = 0x08, // No media + idnf = 0x10, // Id not found + mc = 0x20, // No media + unc = 0x40, // Uncorrectable + bbk = 0x80, // Bad sector +}; + + +enum class ata_cmd : uint8_t +{ + read_pio = 0x20, + read_pio_ext = 0x24, + read_dma = 0xC8, + read_dma_ext = 0x25, + write_pio = 0x30, + write_pio_ext = 0x34, + write_dma = 0xCA, + write_dma_ext = 0x35, + cache_flush = 0xE7, + cache_flush_ext = 0xEA, + packet = 0xA0, + identify_packet = 0xA1, + identify = 0xEC +}; + + +} // namespace ahci + +IS_BITFIELD(ahci::ata_status); +IS_BITFIELD(ahci::ata_error); diff --git a/src/kernel/ahci/driver.cpp b/src/kernel/ahci/driver.cpp new file mode 100644 index 0000000..e9f915b --- /dev/null +++ b/src/kernel/ahci/driver.cpp @@ -0,0 +1,19 @@ +#include "kutil/enum_bitfields.h" +#include "ahci/driver.h" +#include "log.h" +#include "pci.h" + + +ahci_driver::ahci_driver() +{ +} + +void +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); +} + diff --git a/src/kernel/ahci/driver.h b/src/kernel/ahci/driver.h new file mode 100644 index 0000000..1099f39 --- /dev/null +++ b/src/kernel/ahci/driver.h @@ -0,0 +1,28 @@ +#pragma once +/// \file ahci.h +/// AHCI driver and related definitions +#include "kutil/vector.h" +#include "ahci/hba.h" + +class pci_device; + + +/// Basic AHCI driver +class ahci_driver +{ +public: + /// Constructor. + ahci_driver(); + + /// Register a device with the driver + /// \arg device The PCI device to handle + void register_device(pci_device *device); + + /// Unregister a device from the driver + /// \arg device The PCI device to remove + void unregister_device(pci_device *device); + +private: + kutil::vector m_devices; +}; + diff --git a/src/kernel/ahci/fis.h b/src/kernel/ahci/fis.h new file mode 100644 index 0000000..67a6d47 --- /dev/null +++ b/src/kernel/ahci/fis.h @@ -0,0 +1,49 @@ +#pragma once +/// \file fis.h +/// Definitions for Frame Information Structure types. (Not for pescatarians.) +#include + + +namespace ahci { + +enum class ata_cmd : uint8_t; + +enum class fis_type : uint8_t +{ + register_h2d = 0x27, + register_d2h = 0x34, + dma_activate = 0x39, + dma_setup = 0x41, + data = 0x46, + bist = 0x58, + pio_setup = 0x5f, + device_bits = 0xa1 +}; + + +struct fis_register_h2d +{ + fis_type type; + uint8_t pm_port; // high bit (0x80) is set for the command register flag + ata_cmd command; + uint8_t features; + + uint8_t lba0; + uint8_t lba1; + uint8_t lba2; + uint8_t device; + + uint8_t lba3; + uint8_t lba4; + uint8_t lba5; + uint8_t features2; + + uint8_t count0; + uint8_t count1; + uint8_t icc; + uint8_t control; + + uint32_t reserved; +}; + +} // namespace ahci diff --git a/src/kernel/ahci/hba.cpp b/src/kernel/ahci/hba.cpp new file mode 100644 index 0000000..b5221ca --- /dev/null +++ b/src/kernel/ahci/hba.cpp @@ -0,0 +1,76 @@ +#include +#include "ahci/hba.h" +#include "log.h" +#include "page_manager.h" +#include "pci.h" + + +IS_BITFIELD(ahci::hba_cap); +IS_BITFIELD(ahci::hba_cap2); + +namespace ahci { + + +enum class hba_cap : uint32_t +{ + ccc = 0x00000080, // Command completion coalescing + ahci_only = 0x00040000, // ACHI-only mode + clo = 0x01000000, // Command list override + snotify = 0x40000000, // SNotification register + ncq = 0x40000000, // Native command queuing + addr64 = 0x80000000 // 64bit addressing +}; + + +enum class hba_cap2 : uint32_t +{ + handoff = 0x00000001 // BIOS OS hand-off +}; + + +struct hba_data +{ + hba_cap cap; + uint32_t host_control; + uint32_t int_status; + uint32_t port_impl; + uint32_t version; + uint32_t ccc_control; + uint32_t ccc_ports; + uint32_t em_location; + uint32_t em_control; + hba_cap2 cap2; + uint32_t handoff_control; + +} __attribute__ ((packed)); + + +hba::hba(pci_device *device) +{ + page_manager *pm = page_manager::get(); + + uint32_t bar5 = device->get_bar(5); + m_data = reinterpret_cast(bar5 & ~0xfffull); + pm->map_offset_pointer(reinterpret_cast(&m_data), 0x2000); + + uint32_t icap = static_cast(m_data->cap); + + unsigned ports = (icap & 0xf) + 1; + unsigned slots = ((icap >> 8) & 0x1f) + 1; + + log::debug(logs::driver, " %d ports", ports); + log::debug(logs::driver, " %d command slots", slots); + + port_data *pd = reinterpret_cast( + kutil::offset_pointer(m_data, 0x100)); + + m_ports.ensure_capacity(ports); + for (unsigned i = 0; i < ports; ++i) { + log::debug(logs::driver, " Registering port %d", i); + bool impl = ((m_data->port_impl & (1 << i)) != 0); + m_ports.emplace(kutil::offset_pointer(pd, 0x80 * i), impl); + } +} + +} // namespace ahci + diff --git a/src/kernel/ahci/hba.h b/src/kernel/ahci/hba.h new file mode 100644 index 0000000..ff0df41 --- /dev/null +++ b/src/kernel/ahci/hba.h @@ -0,0 +1,31 @@ +#pragma once +/// \file hba.h +/// Definition for AHCI host bus adapters +#include "kutil/vector.h" +#include "ahci/port.h" + +class pci_device; + + +namespace ahci { + +enum class hba_cap : uint32_t; +enum class hba_cap2 : uint32_t; +struct hba_data; + + +/// An AHCI host bus adapter +class hba +{ +public: + /// Constructor. + /// \arg device The PCI device for this HBA + hba(pci_device *device); + +private: + pci_device *m_device; + hba_data *m_data; + kutil::vector m_ports; +}; + +} // namespace ahci diff --git a/src/kernel/ahci/port.cpp b/src/kernel/ahci/port.cpp new file mode 100644 index 0000000..f4d0eca --- /dev/null +++ b/src/kernel/ahci/port.cpp @@ -0,0 +1,342 @@ +#include +#include "kutil/assert.h" +#include "kutil/enum_bitfields.h" +#include "ahci/ata.h" +#include "ahci/fis.h" +#include "ahci/port.h" +#include "io.h" +#include "log.h" +#include "page_manager.h" + +IS_BITFIELD(ahci::port_cmd); + +namespace ahci { + +const unsigned max_prd_count = 16; + + +enum class cmd_list_flags : uint16_t +{ + atapi = 0x0020, + write = 0x0040, + prefetch = 0x0080, + reset = 0x0100, + bist = 0x0200, + clear_busy = 0x0400 +}; + +inline cmd_list_flags +cmd_list_fis_size(uint8_t size) +{ + return static_cast((size/4) & 0x1f); +} + + +struct cmd_list_entry +{ + cmd_list_flags flags; + uint16_t prd_table_length; + uint32_t prd_byte_count; + uint32_t cmd_table_base_low; + uint32_t cmd_table_base_high; + uint32_t reserved[4]; + +} __attribute__ ((packed)); + + +struct prdt_entry +{ + uint32_t data_base_low; + uint32_t data_base_high; + uint32_t reserved; + uint32_t byte_count; +} __attribute__ ((packed)); + + +struct cmd_table +{ + uint8_t cmd_fis[64]; + uint8_t atapi_cmd[16]; + uint8_t reserved[48]; + prdt_entry entries[0]; +} __attribute__ ((packed)); + + +enum class port_cmd : uint32_t +{ + start = 0x00000001, + spinup = 0x00000002, + poweron = 0x00000004, + clo = 0x00000008, + fis_recv = 0x00000010, + fisr_running = 0x00004000, + cmds_running = 0x00008000, + + none = 0x00000000 +}; + + +struct port_data +{ + uint32_t cmd_base_low; + uint32_t cmd_base_high; + uint32_t fis_base_low; + uint32_t fis_base_high; + + uint32_t interrupt_status; + uint32_t interrupt_enable; + + port_cmd command; + + uint32_t reserved0; + + uint8_t task_file_status; + uint8_t task_file_error; + uint16_t reserved1; + + uint32_t signature; + uint32_t serial_status; + uint32_t serial_control; + uint32_t serial_error; + uint32_t serial_active; + uint32_t cmd_issue; + uint32_t serial_notify; + uint32_t fis_switching; + uint32_t dev_sleep; + + uint8_t reserved2[40]; + uint8_t vendor[16]; +} __attribute__ ((packed)); + + +port::port(port_data *data, bool impl) : + m_state(state::unimpl), + m_data(data), + m_fis(nullptr), + m_cmd_list(nullptr), + m_cmd_table(nullptr) +{ + if (impl) { + m_state = state::inactive; + update(); + if (m_state == state::active) + rebase(); + } +} + +port::~port() +{ + if (m_cmd_list) { + page_manager *pm = page_manager::get(); + pm->unmap_pages(m_cmd_list, 3); + } +} + +void +port::update() +{ + if (m_state == state::unimpl) return; + + uint32_t detected = m_data->serial_status & 0x0f; + uint32_t power = (m_data->serial_status >> 8) & 0x0f; + + if (detected == 0x3 && power == 0x1) + m_state = state::active; + else + m_state = state::inactive; +} + +bool +port::busy() +{ + return (m_data->task_file_status & 0x88) != 0; +} + +void +port::start_commands() +{ + while (bitfield_has(m_data->command, port_cmd::cmds_running)) + io_wait(); + + m_data->command |= port_cmd::fis_recv; + m_data->command |= port_cmd::start; +} + +void +port::stop_commands() +{ + m_data->command &= ~port_cmd::start; + + while ( + bitfield_has(m_data->command, port_cmd::cmds_running) || + bitfield_has(m_data->command, port_cmd::fisr_running)) + io_wait(); + + m_data->command &= ~port_cmd::fis_recv; +} + +bool +port::read(uint64_t sector, size_t length) +{ + m_data->interrupt_status = ~0u; + + int slot = get_cmd_slot(); + if (slot < 0) { + log::info(logs::driver, "AHCI could not get a free command slot."); + return false; + } + + page_manager *pm = page_manager::get(); + + cmd_list_entry &ent = m_cmd_list[slot]; + cmd_table &cmdt = m_cmd_table[slot]; + + kutil::memset(&cmdt, 0, sizeof(cmd_table) + + max_prd_count * sizeof(prdt_entry)); + + ent.flags = cmd_list_fis_size(sizeof(fis_register_h2d)); + + void *buffers[32]; + + size_t remaining = length; + for (int i = 0; i < max_prd_count; ++i) { + size_t prd_len = std::min(remaining, 0x200000ul); + remaining -= prd_len; + + void *mem = pm->map_offset_pages(page_count(prd_len)); + buffers[i] = mem; + addr_t phys = pm->offset_phys(mem); + cmdt.entries[i].data_base_low = phys & 0xffffffff; + cmdt.entries[i].data_base_high = phys >> 32; + cmdt.entries[i].byte_count = prd_len - 1; + if (remaining == 0 || i == max_prd_count - 1) { + // If this is the last one, set the interrupt flag + cmdt.entries[i].byte_count |= 0x80000000; + ent.prd_table_length = i; + break; + } + } + + fis_register_h2d *fis = reinterpret_cast(&cmdt.cmd_fis); + fis->type = fis_type::register_h2d; + fis->pm_port = 0x80; // set command register flag + fis->command = ata_cmd::read_dma_ext; + + fis->lba0 = (sector ) & 0xff; + fis->lba1 = (sector >> 8) & 0xff; + fis->lba2 = (sector >> 16) & 0xff; + fis->lba3 = (sector >> 24) & 0xff; + fis->lba4 = (sector >> 32) & 0xff; + fis->lba5 = (sector >> 40) & 0xff; + + size_t count = length >> 9; // count is in sectors + fis->count0 = (count ) & 0xff; + fis->count1 = (count >> 8) & 0xff; + + const int max_tries = 10; + int tries = 0; + while (busy()) { + if (++tries == max_tries) { + log::warn(logs::driver, "AHCI port was busy too long"); + return false; + } + io_wait(); + } + + if (tries == max_tries) { + // TODO: clean up!!! + return false; + } + + 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(); + } + + if (m_data->interrupt_status & 0x40000000) { + log::error(logs::driver, "AHCI task file error"); + // TODO: clean up! + return false; + } + + log::warn(logs::driver, "AHCI read success!? '%s'", buffers[0]); + + return true; +} + +void +port::rebase() +{ + kassert(!m_cmd_list, "AHCI port called rebase() twice"); + + page_manager *pm = page_manager::get(); + size_t prd_size = sizeof(cmd_table) + (max_prd_count * sizeof(prdt_entry)); + + // 1 for FIS + command list, N for PRD + size_t pages = 1 + page_count(prd_size * 32); + + void *mem = pm->map_offset_pages(pages); + addr_t phys = pm->offset_phys(mem); + + log::debug(logs::driver, "Rebasing address for AHCI port to %lx [%d]", mem, pages); + + stop_commands(); + + // Command list + m_cmd_list = reinterpret_cast(mem); + m_data->cmd_base_low = phys & 0xffffffff; + m_data->cmd_base_high = phys >> 32; + kutil::memset(mem, 0, 1024); + + mem = kutil::offset_pointer(mem, 32 * sizeof(cmd_list_entry)); + phys = pm->offset_phys(mem); + + // FIS + m_fis = mem; + m_data->fis_base_low = phys & 0xffffffff; + m_data->fis_base_high = phys >> 32; + kutil::memset(mem, 0, 256); + + mem = page_align(kutil::offset_pointer(mem, 256)); + phys = pm->offset_phys(mem); + + // Command table + m_cmd_table = reinterpret_cast(mem); + size_t cmdt_len = sizeof(cmd_table) + + max_prd_count * sizeof(prdt_entry); + + kutil::memset(m_cmd_table, 0, cmdt_len * 32); + + // set up each entry in the command list to point to the + // corresponding command table + for (int i = 0; i < 32; ++i) { + m_cmd_list[i].prd_table_length = max_prd_count; + m_cmd_list[i].cmd_table_base_low = phys & 0xffffffff; + m_cmd_list[i].cmd_table_base_high = phys >> 32; + + mem = kutil::offset_pointer(mem, cmdt_len); + phys = pm->offset_phys(mem); + } + + start_commands(); +} + +int +port::get_cmd_slot() +{ + uint32_t used = (m_data->serial_active | m_data->cmd_issue); + for (int i = 0; i < 32; ++i) + if (used & (1 << i)) return i; + + return -1; +} + +} // namespace ahci diff --git a/src/kernel/ahci/port.h b/src/kernel/ahci/port.h new file mode 100644 index 0000000..af3aa83 --- /dev/null +++ b/src/kernel/ahci/port.h @@ -0,0 +1,68 @@ +#pragma once +/// \file port.h +/// Definition for AHCI ports +#include +#include + +namespace ahci { + +struct cmd_list_entry; +struct cmd_table; +enum class port_cmd : uint32_t; +struct port_data; + + +/// A port on an AHCI HBA +class port +{ +public: + /// Constructor. + /// \arg data Pointer to the device's registers for this port + /// \arg impl Whether this port is marked as implemented in the HBA + port(port_data *data, bool impl); + + /// Destructor + ~port(); + + enum class state { unimpl, inactive, active }; + + /// Get the current state of this device + /// \returns An enum representing the state + state get_state() const { return m_state; } + + /// Update the state of this object from the register data + void update(); + + /// Return whether the port is currently busy + bool busy(); + + /// Start command processing from this port + void start_commands(); + + /// Stop command processing from this port + void stop_commands(); + + /// Read data from the drive. + /// \arg sector Starting sector to read + /// \arg length Number of bytes to read + /// \returns True if the command succeeded + bool read(uint64_t sector, size_t length); + +private: + /// Rebase the port command structures to a new location in system + /// memory, to be allocated from the page manager. + void rebase(); + + /// Get a free command slot + /// \returns The index of the command slot, or -1 if none available + int get_cmd_slot(); + + state m_state; + port_data *m_data; + + void *m_fis; + cmd_list_entry *m_cmd_list; + cmd_table *m_cmd_table; +}; + +} // namespace ahci diff --git a/src/kernel/device_manager.cpp b/src/kernel/device_manager.cpp index a1589e0..76581c7 100644 --- a/src/kernel/device_manager.cpp +++ b/src/kernel/device_manager.cpp @@ -4,7 +4,7 @@ #include "kutil/assert.h" #include "kutil/memory.h" #include "acpi_tables.h" -#include "ahci.h" +#include "ahci/driver.h" #include "apic.h" #include "device_manager.h" #include "interrupts.h" @@ -14,7 +14,7 @@ static const char expected_signature[] = "RSD PTR "; -ahci_driver ahci; +ahci_driver ahcid; struct acpi1_rsdp { @@ -278,6 +278,6 @@ device_manager::init_drivers() device.bus(), device.device(), device.function()); } - ahci.register_device(&device); + ahcid.register_device(&device); } } diff --git a/src/kernel/page_manager.cpp b/src/kernel/page_manager.cpp index 491d5ef..275e1b6 100644 --- a/src/kernel/page_manager.cpp +++ b/src/kernel/page_manager.cpp @@ -353,6 +353,9 @@ page_manager::map_pages(addr_t address, size_t count) block->physical_address = phys; block->virtual_address = address; block->count = n; + block->flags = + page_block_flags::used | + page_block_flags::mapped; page_block::insert(m_used, block); page_in(pml4, phys, address, n); @@ -364,12 +367,59 @@ page_manager::map_pages(addr_t address, size_t count) return ret; } -void -page_manager::unmap_pages(addr_t address, size_t count) +void * +page_manager::map_offset_pages(size_t count) { + page_table *pml4 = get_pml4(); + page_block *free = m_free; + page_block *prev = nullptr; + + log::debug(logs::memory, "Got request to offset map %d pages", count); + + while (free) { + if (free->count < count) { + prev = free; + free = free->next; + continue; + } + + page_block *used = get_block(); + used->count = count; + used->physical_address = free->physical_address; + used->virtual_address = used->physical_address + page_offset; + used->flags = + page_block_flags::used | + page_block_flags::mapped; + page_block::insert(m_used, used); + + free->physical_address += count * page_size; + free->count -= count; + + if (free->count == 0) { + if (prev) + prev->next = free->next; + else + m_free = free->next; + + free->zero(m_block_cache); + m_block_cache = free; + } + + page_in(pml4, used->physical_address, used->virtual_address, count); + return reinterpret_cast(used->virtual_address); + } + + return nullptr; +} + +void +page_manager::unmap_pages(void* address, size_t count) +{ + addr_t addr = reinterpret_cast(address); + page_block **prev = &m_used; page_block *cur = m_used; - while (cur && !cur->contains(address)) { + while (cur && !cur->contains(addr)) { prev = &cur->next; cur = cur->next; } @@ -377,10 +427,10 @@ page_manager::unmap_pages(addr_t address, size_t count) kassert(cur, "Couldn't find existing mapped pages to unmap"); size_t size = page_size * count; - addr_t end = address + size; + addr_t end = addr + size; - while (cur && cur->contains(address)) { - size_t leading = address - cur->virtual_address; + while (cur && cur->contains(addr)) { + size_t leading = addr - cur->virtual_address; size_t trailing = end > cur->virtual_end() ? 0 : (cur->virtual_end() - end); @@ -416,7 +466,7 @@ page_manager::unmap_pages(addr_t address, size_t count) cur->next = trail_block; } - address += cur->count * page_size; + addr += cur->count * page_size; page_block *next = cur->next; *prev = cur->next; diff --git a/src/kernel/page_manager.h b/src/kernel/page_manager.h index 508fc8d..519245b 100644 --- a/src/kernel/page_manager.h +++ b/src/kernel/page_manager.h @@ -34,16 +34,39 @@ public: /// \returns A pointer to the start of the mapped region void * map_pages(addr_t address, size_t count); + /// Allocate and map contiguous pages into virtual memory, with + /// a constant offset from their physical address. + /// \arg count The number of pages to map + /// \returns A pointer to the start of the mapped region, or + /// nullptr if no region could be found to fit the request. + void * map_offset_pages(size_t count); + /// Unmap existing pages from memory. /// \arg address The virtual address of the memory to unmap /// \arg count The number of pages to unmap - void unmap_pages(addr_t address, size_t count); + void unmap_pages(void *address, size_t count); /// Offset-map a pointer. No physical pages will be mapped. /// \arg pointer Pointer to a pointer to the memory area to be mapped /// \arg length Length of the memory area to be mapped void map_offset_pointer(void **pointer, size_t length); + /// Get the physical address of an offset-mapped pointer + /// \arg p Virtual address of memory that has been offset-mapped + /// \returns Physical address of the memory pointed to by p + inline addr_t offset_phys(void *p) const + { + return reinterpret_cast(kutil::offset_pointer(p, -page_offset)); + } + + /// Get the virtual address of an offset-mapped physical address + /// \arg a Physical address of memory that has been offset-mapped + /// \returns Virtual address of the memory at address a + inline void * offset_virt(addr_t a) const + { + return kutil::offset_pointer(reinterpret_cast(a), page_offset); + } + /// Log the current free/used block lists. void dump_blocks(); @@ -179,7 +202,7 @@ struct page_block page_block_flags flags; page_block *next; - inline bool has_flag(page_block_flags f) const { return bitfield_contains(flags, f); } + inline bool has_flag(page_block_flags f) const { return bitfield_has(flags, f); } inline addr_t physical_end() const { return physical_address + (count * page_manager::page_size); } inline addr_t virtual_end() const { return virtual_address + (count * page_manager::page_size); } @@ -279,16 +302,29 @@ struct page_table_indices /// Calculate a page-aligned address. /// \arg p The address to align. /// \returns The next page-aligned address _after_ `p`. -template inline T page_align(T p) +template inline T +page_align(T p) { - return ((p - 1) & ~(page_manager::page_size - 1)) + page_manager::page_size; + return reinterpret_cast( + ((reinterpret_cast(p) - 1) & ~(page_manager::page_size - 1)) + + page_manager::page_size); } /// Calculate a page-table-aligned address. That is, an address that is /// page-aligned to the first page in a page table. /// \arg p The address to align. /// \returns The next page-table-aligned address _after_ `p`. -template inline T page_table_align(T p) { return ((p - 1) & ~0x1fffffull) + 0x200000; } +template inline T +page_table_align(T p) +{ + return ((p - 1) & ~0x1fffffull) + 0x200000; +} + + +/// Calculate the number of pages needed for the give number of bytes. +/// \arg n Number of bytes +/// \returns Number of pages +inline size_t page_count(size_t n) { return ((n - 1) / page_manager::page_size) + 1; } /// Bootstrap the memory managers. diff --git a/src/modules/kutil/enum_bitfields.h b/src/modules/kutil/enum_bitfields.h index 0778620..dc55a84 100644 --- a/src/modules/kutil/enum_bitfields.h +++ b/src/modules/kutil/enum_bitfields.h @@ -6,7 +6,7 @@ template struct is_enum_bitfield { static constexpr bool value = false; }; #define IS_BITFIELD(name) \ - template<> struct is_enum_bitfield {static constexpr bool value=true;} + template<> struct ::is_enum_bitfield {static constexpr bool value=true;} template typename std::enable_if::value,E>::type @@ -84,7 +84,7 @@ operator ! (E rhs) template typename std::enable_if::value,bool>::type -bitfield_contains(E set, E flag) +bitfield_has(E set, E flag) { return (set & flag) == flag; } diff --git a/src/modules/kutil/memory.h b/src/modules/kutil/memory.h index e3be364..5bb4448 100644 --- a/src/modules/kutil/memory.h +++ b/src/modules/kutil/memory.h @@ -51,7 +51,7 @@ inline T read_from(const void *p) /// \arg n The offset in bytes /// \returns The offset pointer template -inline T * offset_pointer(T *p, size_t n) +inline T * offset_pointer(T *p, ptrdiff_t n) { return reinterpret_cast(reinterpret_cast(p) + n); }