x86-64: add PCIe enumeration and general PCI support
This commit is contained in:
parent
3b89f444ff
commit
309c5c3e9f
@ -42,6 +42,8 @@ yboot-proto = { git = "https://git.alnyan.me/yggdrasil/yboot-proto.git" }
|
||||
aml = { git = "https://github.com/alnyan/acpi.git", version = "0.16.4" }
|
||||
acpi_lib = { git = "https://github.com/alnyan/acpi.git", version = "4.1.1", package = "acpi" }
|
||||
acpi-system = { git = "https://github.com/alnyan/acpi-system.git", version = "0.1.0" }
|
||||
# TODO currently only supported here
|
||||
xhci_lib = { git = "https://github.com/rust-osdev/xhci.git", package = "xhci" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
@ -2,7 +2,7 @@
|
||||
use core::{ptr::NonNull, sync::atomic::Ordering};
|
||||
|
||||
use abi::error::Error;
|
||||
use acpi_lib::{AcpiHandler, AcpiTables, HpetInfo, InterruptModel, PhysicalMapping};
|
||||
use acpi_lib::{mcfg::Mcfg, AcpiHandler, AcpiTables, HpetInfo, InterruptModel, PhysicalMapping};
|
||||
use alloc::boxed::Box;
|
||||
use cpu::Cpu;
|
||||
use device_api::{
|
||||
@ -24,6 +24,7 @@ use crate::{
|
||||
debug::{self, LogLevel},
|
||||
device::{
|
||||
self,
|
||||
bus::pci::PciBusManager,
|
||||
display::{console, fb_console::FramebufferConsole, linear_fb::LinearFramebuffer},
|
||||
tty::combined::CombinedTerminal,
|
||||
},
|
||||
@ -392,6 +393,14 @@ impl X86_64 {
|
||||
self.timer.init(Hpet::from_acpi(&hpet).unwrap());
|
||||
|
||||
acpi::init_acpi(acpi).unwrap();
|
||||
|
||||
// Enumerate PCIe root devices
|
||||
// TODO can there be multiple MCFGs?
|
||||
if let Ok(mcfg) = acpi.find_table::<Mcfg>() {
|
||||
for entry in mcfg.entries() {
|
||||
PciBusManager::add_segment_from_mcfg(entry).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn init_framebuffer(&'static self) {
|
||||
@ -469,6 +478,9 @@ impl X86_64 {
|
||||
device::register_device(self.timer.get());
|
||||
device::register_device(ps2);
|
||||
|
||||
// Initialize devices from PCI bus
|
||||
PciBusManager::setup_bus_devices().unwrap();
|
||||
|
||||
infoln!("Device list:");
|
||||
for device in device::manager_lock().devices() {
|
||||
infoln!("* {}", device.display_name());
|
||||
|
@ -72,6 +72,28 @@ impl FixedTables {
|
||||
Ok(virt)
|
||||
} else {
|
||||
// 4KiB mappings
|
||||
// Check if a mapping to that address already exists
|
||||
if self.device_l3i >= count {
|
||||
for i in 0..self.device_l3i {
|
||||
let mut matches = true;
|
||||
|
||||
for j in 0..count {
|
||||
let page = phys + j * 0x1000;
|
||||
let existing = self.device_l3[i].as_page().unwrap();
|
||||
|
||||
if page != existing {
|
||||
matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if matches {
|
||||
let virt = DEVICE_VIRT_OFFSET + (i << 12);
|
||||
return Ok(virt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.device_l3i + count > 512 {
|
||||
return Err(Error::OutOfMemory);
|
||||
}
|
||||
|
@ -2,3 +2,5 @@
|
||||
|
||||
#[cfg(feature = "device-tree")]
|
||||
pub mod simple_bus;
|
||||
|
||||
pub mod pci;
|
||||
|
238
src/device/bus/pci/mod.rs
Normal file
238
src/device/bus/pci/mod.rs
Normal file
@ -0,0 +1,238 @@
|
||||
use core::fmt;
|
||||
|
||||
use acpi_lib::mcfg::McfgEntry;
|
||||
use alloc::{rc::Rc, vec::Vec};
|
||||
use device_api::Device;
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
mod space;
|
||||
|
||||
pub use space::{
|
||||
ecam::PciEcam, PciConfigSpace, PciConfigurationSpace, PciLegacyConfigurationSpace,
|
||||
};
|
||||
|
||||
use crate::sync::IrqSafeSpinlock;
|
||||
|
||||
pub const PCI_COMMAND_IO: u16 = 1 << 0;
|
||||
pub const PCI_COMMAND_MEMORY: u16 = 1 << 1;
|
||||
pub const PCI_COMMAND_BUS_MASTER: u16 = 1 << 2;
|
||||
pub const PCI_COMMAND_INTERRUPT_DISABLE: u16 = 1 << 10;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct PciAddress {
|
||||
pub segment: u8,
|
||||
pub bus: u8,
|
||||
pub device: u8,
|
||||
pub function: u8,
|
||||
}
|
||||
|
||||
// TODO other attributes
|
||||
#[derive(Debug)]
|
||||
pub enum PciBaseAddress {
|
||||
Memory(usize),
|
||||
Io(u16),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PciDeviceInfo {
|
||||
pub address: PciAddress,
|
||||
pub config_space: PciConfigSpace,
|
||||
}
|
||||
|
||||
pub trait FromPciBus: Sized {
|
||||
fn from_pci_bus(info: &PciDeviceInfo) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
pub struct PciBusDevice {
|
||||
info: PciDeviceInfo,
|
||||
driver: Option<Rc<dyn Device>>,
|
||||
}
|
||||
|
||||
pub struct PciBusSegment {
|
||||
segment_number: u8,
|
||||
bus_number_start: u8,
|
||||
bus_number_end: u8,
|
||||
ecam_phys_base: Option<usize>,
|
||||
|
||||
devices: Vec<PciBusDevice>,
|
||||
}
|
||||
|
||||
pub struct PciBusManager {
|
||||
segments: Vec<PciBusSegment>,
|
||||
}
|
||||
|
||||
impl PciBusSegment {
|
||||
fn probe_config_space(&self, address: PciAddress) -> Result<Option<PciConfigSpace>, Error> {
|
||||
match self.ecam_phys_base {
|
||||
Some(ecam_phys_base) => Ok(unsafe {
|
||||
PciEcam::probe_raw_parts(ecam_phys_base, self.bus_number_start, address)?
|
||||
}
|
||||
.map(PciConfigSpace::Ecam)),
|
||||
None => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn enumerate_function(
|
||||
&mut self,
|
||||
parent: &mut PciBusManager,
|
||||
address: PciAddress,
|
||||
) -> Result<(), Error> {
|
||||
let Some(config) = self.probe_config_space(address)? else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let header_type = config.header_type();
|
||||
|
||||
// Enumerate multi-function devices
|
||||
if address.function == 0 && header_type & 0x80 != 0 {
|
||||
for function in 1..8 {
|
||||
self.enumerate_function(parent, address.with_function(function))?;
|
||||
}
|
||||
}
|
||||
|
||||
// PCI-to-PCI bridge
|
||||
// if config.class_code() == 0x06 && config.subclass() == 0x04 {
|
||||
// let secondary_bus = config.secondary_bus();
|
||||
// // TODO
|
||||
// }
|
||||
|
||||
let info = PciDeviceInfo {
|
||||
address,
|
||||
config_space: config,
|
||||
};
|
||||
self.devices.push(PciBusDevice { info, driver: None });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn enumerate_bus(&mut self, parent: &mut PciBusManager, bus: u8) -> Result<(), Error> {
|
||||
let address = PciAddress::for_bus(self.segment_number, bus);
|
||||
|
||||
for i in 0..32 {
|
||||
let device_address = address.with_device(i);
|
||||
|
||||
self.enumerate_function(parent, device_address)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn enumerate(&mut self, parent: &mut PciBusManager) -> Result<(), Error> {
|
||||
for bus in self.bus_number_start..self.bus_number_end {
|
||||
self.enumerate_bus(parent, bus)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PciBusManager {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
segments: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_bus_devices() -> Result<(), Error> {
|
||||
Self::walk_bus_devices(|device| {
|
||||
setup_bus_device(device)?;
|
||||
Ok(true)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn walk_bus_devices<F: FnMut(&mut PciBusDevice) -> Result<bool, Error>>(
|
||||
mut f: F,
|
||||
) -> Result<(), Error> {
|
||||
let mut this = PCI_MANAGER.lock();
|
||||
|
||||
for segment in this.segments.iter_mut() {
|
||||
for device in segment.devices.iter_mut() {
|
||||
if !f(device)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_segment_from_mcfg(entry: &McfgEntry) -> Result<(), Error> {
|
||||
let mut bus_segment = PciBusSegment {
|
||||
segment_number: entry.pci_segment_group as u8,
|
||||
bus_number_start: entry.bus_number_start,
|
||||
bus_number_end: entry.bus_number_end,
|
||||
ecam_phys_base: Some(entry.base_address as usize),
|
||||
|
||||
devices: Vec::new(),
|
||||
};
|
||||
|
||||
let mut this = PCI_MANAGER.lock();
|
||||
bus_segment.enumerate(&mut *this)?;
|
||||
this.segments.push(bus_segment);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PciAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}:{}", self.bus, self.device, self.function)
|
||||
}
|
||||
}
|
||||
|
||||
impl PciAddress {
|
||||
pub const fn for_bus(segment: u8, bus: u8) -> Self {
|
||||
Self {
|
||||
segment,
|
||||
bus,
|
||||
device: 0,
|
||||
function: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn with_device(self, device: u8) -> Self {
|
||||
Self {
|
||||
device,
|
||||
function: 0,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn with_function(self, function: u8) -> Self {
|
||||
Self { function, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
impl PciConfigurationSpace for PciConfigSpace {
|
||||
fn read_u32(&self, offset: usize) -> u32 {
|
||||
match self {
|
||||
Self::Ecam(ecam) => ecam.read_u32(offset),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_bus_device(device: &mut PciBusDevice) -> Result<(), Error> {
|
||||
if device.driver.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let config = &device.info.config_space;
|
||||
|
||||
debugln!(
|
||||
"{}: {:04x}:{:04x}",
|
||||
device.info.address,
|
||||
config.vendor_id(),
|
||||
config.device_id()
|
||||
);
|
||||
|
||||
// Match by class
|
||||
match (config.class_code(), config.subclass(), config.prog_if()) {
|
||||
_ => {
|
||||
debugln!(" -> No driver");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
static PCI_MANAGER: IrqSafeSpinlock<PciBusManager> = IrqSafeSpinlock::new(PciBusManager::new());
|
79
src/device/bus/pci/space/ecam.rs
Normal file
79
src/device/bus/pci/space/ecam.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::{
|
||||
device::bus::pci::{PciConfigSpace, PciDeviceInfo},
|
||||
mem::{device::DeviceMemory, ConvertAddress},
|
||||
};
|
||||
|
||||
use super::{PciAddress, PciConfigurationSpace};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct PciEcam {
|
||||
mapping: DeviceMemory,
|
||||
}
|
||||
|
||||
// Only used for probing
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
struct PciRawEcam {
|
||||
virt_addr: usize,
|
||||
}
|
||||
|
||||
impl PciConfigurationSpace for PciRawEcam {
|
||||
fn read_u32(&self, offset: usize) -> u32 {
|
||||
assert_eq!(offset & 3, 0);
|
||||
unsafe { ((self.virt_addr + offset) as *const u32).read_volatile() }
|
||||
}
|
||||
}
|
||||
|
||||
impl PciConfigurationSpace for PciEcam {
|
||||
fn read_u32(&self, offset: usize) -> u32 {
|
||||
assert_eq!(offset & 3, 0);
|
||||
unsafe { ((self.mapping.base() + offset) as *const u32).read_volatile() }
|
||||
}
|
||||
}
|
||||
|
||||
impl PciEcam {
|
||||
pub unsafe fn map(phys_addr: usize) -> Result<Self, Error> {
|
||||
// TODO check align
|
||||
let mapping = DeviceMemory::map("pcie-ecam", phys_addr, 0x1000)?;
|
||||
|
||||
Ok(PciEcam { mapping })
|
||||
}
|
||||
|
||||
pub unsafe fn from_raw_parts(
|
||||
segment_phys_addr: usize,
|
||||
bus_offset: u8,
|
||||
address: PciAddress,
|
||||
) -> Result<Self, Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub unsafe fn probe_raw_parts(
|
||||
segment_phys_addr: usize,
|
||||
bus_offset: u8,
|
||||
address: PciAddress,
|
||||
) -> Result<Option<Self>, Error> {
|
||||
let phys_addr = segment_phys_addr
|
||||
+ ((address.bus - bus_offset) as usize * 256
|
||||
+ address.device as usize * 8
|
||||
+ address.function as usize)
|
||||
* 0x1000;
|
||||
|
||||
if phys_addr + 0xFFF < 0x100000000 {
|
||||
// Probe without allocating a mapping
|
||||
let raw = PciRawEcam {
|
||||
virt_addr: phys_addr.virtualize(),
|
||||
};
|
||||
|
||||
if !raw.is_valid() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Self::map(phys_addr).map(Some)
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
131
src/device/bus/pci/space/mod.rs
Normal file
131
src/device/bus/pci/space/mod.rs
Normal file
@ -0,0 +1,131 @@
|
||||
use super::{PciAddress, PciBaseAddress, PciEcam};
|
||||
|
||||
pub(super) mod ecam;
|
||||
|
||||
macro_rules! pci_config_field_getter {
|
||||
(u32, $name:ident, $offset:expr) => {
|
||||
fn $name(&self) -> u32 {
|
||||
self.read_u32($offset)
|
||||
}
|
||||
};
|
||||
|
||||
(u16, $name:ident, $offset:expr) => {
|
||||
fn $name(&self) -> u16 {
|
||||
self.read_u16($offset)
|
||||
}
|
||||
};
|
||||
|
||||
(u8, $name:ident, $offset:expr) => {
|
||||
fn $name(&self) -> u8 {
|
||||
self.read_u8($offset)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! pci_config_field {
|
||||
($offset:expr => $ty:ident, $getter:ident $(, $setter:ident)?) => {
|
||||
pci_config_field_getter!($ty, $getter, $offset);
|
||||
|
||||
$(
|
||||
fn $setter(&self, value: $ty) {
|
||||
todo!()
|
||||
}
|
||||
)?
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct PciLegacyConfigurationSpace {
|
||||
address: PciAddress,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PciConfigSpace {
|
||||
Legacy(PciAddress),
|
||||
Ecam(PciEcam),
|
||||
}
|
||||
|
||||
pub trait PciConfigurationSpace {
|
||||
fn read_u32(&self, offset: usize) -> u32;
|
||||
|
||||
fn read_u16(&self, offset: usize) -> u16 {
|
||||
assert_eq!(offset & 1, 0);
|
||||
let value = self.read_u32(offset & !3);
|
||||
(value >> ((offset & 3) * 8)) as u16
|
||||
}
|
||||
|
||||
fn read_u8(&self, offset: usize) -> u8 {
|
||||
let value = self.read_u32(offset & !3);
|
||||
(value >> ((offset & 3) * 8)) as u8
|
||||
}
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
self.vendor_id() != 0xFFFF && self.device_id() != 0xFFFF
|
||||
}
|
||||
|
||||
pci_config_field!(0x00 => u16, vendor_id);
|
||||
pci_config_field!(0x02 => u16, device_id);
|
||||
pci_config_field!(0x04 => u16, command, set_command);
|
||||
pci_config_field!(0x06 => u16, status);
|
||||
|
||||
pci_config_field!(0x08 => u8, rev_id);
|
||||
pci_config_field!(0x09 => u8, prog_if);
|
||||
pci_config_field!(0x0A => u8, subclass);
|
||||
pci_config_field!(0x0B => u8, class_code);
|
||||
|
||||
// ...
|
||||
pci_config_field!(0x0E => u8, header_type);
|
||||
// Header Type 1 only
|
||||
pci_config_field!(0x19 => u8, secondary_bus);
|
||||
|
||||
// Header Type 0 only
|
||||
fn bar(&self, index: usize) -> Option<PciBaseAddress> {
|
||||
assert!(index < 6);
|
||||
|
||||
if index % 2 == 0 {
|
||||
let w0 = self.read_u32(0x10 + index * 4);
|
||||
|
||||
match w0 & 0 {
|
||||
0 => match (w0 >> 1) & 3 {
|
||||
0 => {
|
||||
// 32-bit memory BAR
|
||||
Some(PciBaseAddress::Memory((w0 as usize) & !0xF))
|
||||
}
|
||||
2 => {
|
||||
// 64-bit memory BAR
|
||||
let w1 = self.read_u32(0x10 + (index + 1) * 4);
|
||||
Some(PciBaseAddress::Memory(
|
||||
((w1 as usize) << 32) | ((w0 as usize) & !0xF),
|
||||
))
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
},
|
||||
1 => todo!(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
let prev_w0 = self.read_u32(0x10 + (index - 1) * 4);
|
||||
if prev_w0 & 0x7 == 0x4 {
|
||||
// Previous BAR is 64-bit memory and this one is its continuation
|
||||
return None;
|
||||
}
|
||||
|
||||
let w0 = self.read_u32(0x10 + index * 4);
|
||||
|
||||
match w0 & 0 {
|
||||
0 => match (w0 >> 1) & 3 {
|
||||
0 => {
|
||||
// 32-bit memory BAR
|
||||
Some(PciBaseAddress::Memory((w0 as usize) & !0xF))
|
||||
}
|
||||
// TODO can 64-bit BARs not be on a 64-bit boundary?
|
||||
2 => todo!(),
|
||||
_ => unimplemented!(),
|
||||
},
|
||||
1 => todo!(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
//! osdev-x kernel crate
|
||||
#![feature(
|
||||
decl_macro,
|
||||
naked_functions,
|
||||
asm_const,
|
||||
panic_info_message,
|
||||
|
@ -6,7 +6,7 @@ use abi::error::Error;
|
||||
use crate::arch::{Architecture, ARCHITECTURE};
|
||||
|
||||
/// Generic MMIO access mapping
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(unused)]
|
||||
pub struct DeviceMemory {
|
||||
name: &'static str,
|
||||
|
Loading…
x
Reference in New Issue
Block a user