diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index d8427116..6a341657 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -1,6 +1,9 @@ # The Moon kernel for Luna. +file(GLOB_RECURSE HEADERS src/*.h) + set(SOURCES + ${HEADERS} src/main.cpp src/Log.cpp src/cxxabi.cpp @@ -14,6 +17,7 @@ set(SOURCES src/boot/Init.cpp src/arch/Serial.cpp src/arch/Timer.cpp + src/arch/PCI.cpp src/thread/Spinlock.cpp src/thread/Thread.cpp src/thread/Scheduler.cpp @@ -36,6 +40,7 @@ if("${LUNA_ARCH}" MATCHES "x86_64") src/arch/x86_64/CPU.cpp src/arch/x86_64/Timer.cpp src/arch/x86_64/Thread.cpp + src/arch/x86_64/PCI.cpp src/arch/x86_64/init/GDT.cpp src/arch/x86_64/init/IDT.cpp src/arch/x86_64/init/PIC.cpp diff --git a/kernel/src/arch/PCI.cpp b/kernel/src/arch/PCI.cpp new file mode 100644 index 00000000..e3934185 --- /dev/null +++ b/kernel/src/arch/PCI.cpp @@ -0,0 +1,99 @@ +#include "arch/PCI.h" +#include "Log.h" + +namespace PCI +{ + Device::ID read_id(const Device::Address& address) + { + u16 vendor = read16(address, Field::VendorID); + u16 device = read16(address, Field::DeviceID); + return { vendor, device }; + } + + Device::Type read_type(const Device::Address& address) + { + u8 klass = read8(address, Field::Class); + u8 subclass = read8(address, Field::Subclass); + u8 prog_if = read8(address, Field::ProgIF); + return { klass, subclass, prog_if }; + } + + constexpr u8 PCI_BRIDGE_CLASS = 0x06; + constexpr u8 PCI_TO_PCI_BRIDGE_SUBCLASS = 0x04; + + static bool is_pci_to_pci_bridge(const Device::Address& address) + { + return read8(address, Field::Class) == PCI_BRIDGE_CLASS && + read8(address, Field::Subclass) == PCI_TO_PCI_BRIDGE_SUBCLASS; + } + + void scan_bus(u32); + + void scan_function(const Device::Address& address) + { + Device::ID id = read_id(address); + Device::Type type = read_type(address); + + kinfoln("Found PCI device %.4x:%.4x (Class %.2x, Subclass %.2x, ProgIF %.2x) at PCI address %u:%u:%u", + id.vendor, id.device, type.klass, type.subclass, type.prog_if, address.bus, address.slot, + address.function); + + if (is_pci_to_pci_bridge(address)) + { + kdbgln("PCI-to-PCI bridge detected, performing another bus scan"); + scan_bus(read8(address, Field::SecondaryBus)); + } + } + + void scan_slot(u32 bus, u32 slot) + { + Device::Address address { bus, slot, 0 }; + + // No device on this slot! + if (read16(address, Field::VendorID) == PCI::INVALID_ID) return; + + scan_function(address); + + u8 header_type = read8(address, Field::HeaderType); + + // Multiple-function PCI device + if ((header_type & 0x80) == 0x80) + { + for (u32 function = 1; function < 8; function++) + { + address.function = function; + if (read16(address, Field::VendorID) == PCI::INVALID_ID) continue; + scan_function(address); + } + } + } + + void scan_bus(u32 bus) + { + for (u32 slot = 0; slot < 32; slot++) { scan_slot(bus, slot); } + } + + void scan() + { + u8 header_type = read8({ 0, 0, 0 }, Field::HeaderType); + + // Single-function PCI bus + if ((header_type & 0x80) == 0) + { + kdbgln("PCI bus is single-function"); + scan_bus(0); + } + else + { + kdbgln("PCI bus is multiple-function"); + for (u32 function = 0; function < 8; function++) + { + if (read16({ 0, 0, function }, Field::VendorID) != PCI::INVALID_ID) + { + kdbgln("PCI bus has function %u", function); + scan_bus(function); + } + } + } + } +} diff --git a/kernel/src/arch/PCI.h b/kernel/src/arch/PCI.h new file mode 100644 index 00000000..15d552d3 --- /dev/null +++ b/kernel/src/arch/PCI.h @@ -0,0 +1,60 @@ +#pragma once +#include + +namespace PCI +{ + enum Field : u32 + { + VendorID = 0x00, + DeviceID = 0x02, + Command = 0x04, + Status = 0x06, + + RevisionID = 0x08, + ProgIF = 0x09, + Subclass = 0x0a, + Class = 0x0b, + + HeaderType = 0x0e, + + SecondaryBus = 0x19 + }; + + namespace Device + { + struct ID + { + u16 vendor; + u16 device; + }; + + struct Type + { + u8 klass; + u8 subclass; + u8 prog_if; + }; + + struct Address + { + u32 bus; + u32 slot; + u32 function; + }; + } + + constexpr u16 INVALID_ID = 0xFFFF; + + // Architecture-dependent. + u8 read8(const Device::Address& address, u32 field); + u16 read16(const Device::Address& address, u32 field); + u32 read32(const Device::Address& address, u32 field); + void write8(const Device::Address& address, u32 field, u8 value); + void write16(const Device::Address& address, u32 field, u16 value); + void write32(const Device::Address& address, u32 field, u32 value); + + Device::ID read_id(const Device::Address& address); + Device::Type read_type(const Device::Address& address); + + void scan(); +} diff --git a/kernel/src/arch/x86_64/PCI.cpp b/kernel/src/arch/x86_64/PCI.cpp new file mode 100644 index 00000000..c8458297 --- /dev/null +++ b/kernel/src/arch/x86_64/PCI.cpp @@ -0,0 +1,47 @@ +#include "arch/PCI.h" +#include "arch/x86_64/IO.h" +#include + +#define PCI_ADDRESS_PORT 0xCF8 +#define PCI_VALUE_PORT 0xCFC + +static inline constexpr u32 make_pci_address(const PCI::Device::Address& address, u32 field) +{ + return 0x80000000 | (address.bus << 16) | (address.slot << 11) | (address.function << 8) | ((field)&0xFC); +} + +namespace PCI +{ + u8 read8(const Device::Address& address, u32 field) + { + IO::outl(PCI_ADDRESS_PORT, make_pci_address(address, field)); + return IO::inb(PCI_VALUE_PORT + (field & 3)); + } + + u16 read16(const Device::Address& address, u32 field) + { + IO::outl(PCI_ADDRESS_PORT, make_pci_address(address, field)); + return IO::inw(PCI_VALUE_PORT + (field & 2)); + } + + u32 read32(const Device::Address& address, u32 field) + { + IO::outl(PCI_ADDRESS_PORT, make_pci_address(address, field)); + return IO::inl(PCI_VALUE_PORT); + } + + void write8(const Device::Address&, u32, u8) + { + todo(); + } + + void write16(const Device::Address&, u32, u16) + { + todo(); + } + + void write32(const Device::Address&, u32, u32) + { + todo(); + } +} diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp index 9f5e7d3f..69e5f670 100644 --- a/kernel/src/main.cpp +++ b/kernel/src/main.cpp @@ -3,6 +3,7 @@ #include "Log.h" #include "arch/CPU.h" #include "arch/MMU.h" +#include "arch/PCI.h" #include "arch/Timer.h" #include "boot/Init.h" #include "config.h" @@ -69,6 +70,8 @@ Result init() TRY(Scheduler::new_kernel_thread(heap_thread)); TRY(Scheduler::new_kernel_thread(reap_thread)); + PCI::scan(); + CPU::platform_finish_init(); // Disable console logging before transferring control to userspace.