x86-64: add PCIe enumeration and general PCI support

This commit is contained in:
Mark Poliakov 2023-09-04 11:27:16 +03:00
parent 3b89f444ff
commit 309c5c3e9f
9 changed files with 489 additions and 2 deletions

View File

@ -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" } 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_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" } 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] [features]
default = [] default = []

View File

@ -2,7 +2,7 @@
use core::{ptr::NonNull, sync::atomic::Ordering}; use core::{ptr::NonNull, sync::atomic::Ordering};
use abi::error::Error; 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 alloc::boxed::Box;
use cpu::Cpu; use cpu::Cpu;
use device_api::{ use device_api::{
@ -24,6 +24,7 @@ use crate::{
debug::{self, LogLevel}, debug::{self, LogLevel},
device::{ device::{
self, self,
bus::pci::PciBusManager,
display::{console, fb_console::FramebufferConsole, linear_fb::LinearFramebuffer}, display::{console, fb_console::FramebufferConsole, linear_fb::LinearFramebuffer},
tty::combined::CombinedTerminal, tty::combined::CombinedTerminal,
}, },
@ -392,6 +393,14 @@ impl X86_64 {
self.timer.init(Hpet::from_acpi(&hpet).unwrap()); self.timer.init(Hpet::from_acpi(&hpet).unwrap());
acpi::init_acpi(acpi).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) { unsafe fn init_framebuffer(&'static self) {
@ -469,6 +478,9 @@ impl X86_64 {
device::register_device(self.timer.get()); device::register_device(self.timer.get());
device::register_device(ps2); device::register_device(ps2);
// Initialize devices from PCI bus
PciBusManager::setup_bus_devices().unwrap();
infoln!("Device list:"); infoln!("Device list:");
for device in device::manager_lock().devices() { for device in device::manager_lock().devices() {
infoln!("* {}", device.display_name()); infoln!("* {}", device.display_name());

View File

@ -72,6 +72,28 @@ impl FixedTables {
Ok(virt) Ok(virt)
} else { } else {
// 4KiB mappings // 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 { if self.device_l3i + count > 512 {
return Err(Error::OutOfMemory); return Err(Error::OutOfMemory);
} }

View File

@ -2,3 +2,5 @@
#[cfg(feature = "device-tree")] #[cfg(feature = "device-tree")]
pub mod simple_bus; pub mod simple_bus;
pub mod pci;

238
src/device/bus/pci/mod.rs Normal file
View 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());

View 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!()
}
}
}

View 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!(),
}
}
}
}

View File

@ -1,5 +1,6 @@
//! osdev-x kernel crate //! osdev-x kernel crate
#![feature( #![feature(
decl_macro,
naked_functions, naked_functions,
asm_const, asm_const,
panic_info_message, panic_info_message,

View File

@ -6,7 +6,7 @@ use abi::error::Error;
use crate::arch::{Architecture, ARCHITECTURE}; use crate::arch::{Architecture, ARCHITECTURE};
/// Generic MMIO access mapping /// Generic MMIO access mapping
#[derive(Clone)] #[derive(Clone, Debug)]
#[allow(unused)] #[allow(unused)]
pub struct DeviceMemory { pub struct DeviceMemory {
name: &'static str, name: &'static str,