From a6dcdf6952a5bf11dcb80efb624d3ad149343a1e Mon Sep 17 00:00:00 2001 From: Yuke Peng Date: Sun, 23 Jul 2023 03:31:43 -0700 Subject: [PATCH] Implement the PCI bus framework --- framework/jinux-frame/src/bus/mod.rs | 4 + framework/jinux-frame/src/bus/pci/bus.rs | 95 +++++++++ .../jinux-frame/src/bus/pci/capability/mod.rs | 135 ++++++++++++ .../src/bus/pci/capability/msix.rs | 141 ++++++++++++ .../src/bus/pci/capability/vendor.rs | 63 ++++++ .../jinux-frame/src/bus/pci/cfg_space.rs | 201 ++++++++++++++++++ .../jinux-frame/src/bus/pci/common_device.rs | 153 +++++++++++++ .../jinux-frame/src/bus/pci/device_info.rs | 141 ++++++++++++ framework/jinux-frame/src/bus/pci/mod.rs | 108 ++++++---- framework/jinux-frame/src/lib.rs | 1 + 10 files changed, 1001 insertions(+), 41 deletions(-) create mode 100644 framework/jinux-frame/src/bus/pci/bus.rs create mode 100644 framework/jinux-frame/src/bus/pci/capability/mod.rs create mode 100644 framework/jinux-frame/src/bus/pci/capability/msix.rs create mode 100644 framework/jinux-frame/src/bus/pci/capability/vendor.rs create mode 100644 framework/jinux-frame/src/bus/pci/cfg_space.rs create mode 100644 framework/jinux-frame/src/bus/pci/common_device.rs create mode 100644 framework/jinux-frame/src/bus/pci/device_info.rs diff --git a/framework/jinux-frame/src/bus/mod.rs b/framework/jinux-frame/src/bus/mod.rs index 7652d2c40..0546e9fa0 100644 --- a/framework/jinux-frame/src/bus/mod.rs +++ b/framework/jinux-frame/src/bus/mod.rs @@ -1 +1,5 @@ pub mod pci; + +pub fn init() { + pci::init(); +} diff --git a/framework/jinux-frame/src/bus/pci/bus.rs b/framework/jinux-frame/src/bus/pci/bus.rs new file mode 100644 index 000000000..8b545de00 --- /dev/null +++ b/framework/jinux-frame/src/bus/pci/bus.rs @@ -0,0 +1,95 @@ +use core::fmt::Debug; + +use alloc::{collections::VecDeque, sync::Arc, vec::Vec}; +use log::{debug, error}; + +use super::{device_info::PciDeviceId, PciCommonDevice}; + +pub trait PciDevice: Sync + Send + Debug { + fn device_id(&self) -> PciDeviceId; +} + +#[derive(Debug)] +pub enum PciDriverProbeError { + DeviceNotMatch, + ConfigurationSpaceError, +} + +/// PCI device driver, PCI bus will pass the device through the `probe` function when a new device is registered. +pub trait PciDriver: Sync + Send + Debug { + /// Probe an unclaimed PCI device. + /// + /// If the driver matches and succeeds in initializing the unclaimed device, + /// then the driver will return an claimed instance of the device, + /// signaling that the PCI device is now ready to work. + /// + /// Once a device is matched and claimed by a driver, + /// it won't be fed to another driver for probing. + fn probe( + &self, + device: PciCommonDevice, + ) -> Result, (PciDriverProbeError, PciCommonDevice)>; +} + +/// The PCI bus used to register PCI devices. If a component wishes to drive a PCI device, it needs to provide the following: +/// +/// 1. The structure that implements the PciDevice trait. +/// 2. PCI driver. +pub struct PciBus { + common_devices: VecDeque, + devices: Vec>, + drivers: Vec>, +} + +impl PciBus { + pub fn register_driver(&mut self, driver: Arc) { + debug!("Register driver:{:#x?}", driver); + let length = self.common_devices.len(); + for i in (0..length).rev() { + let common_device = self.common_devices.pop_front().unwrap(); + let device_id = *common_device.device_id(); + let device = match driver.probe(common_device) { + Ok(device) => { + debug_assert!(device_id == device.device_id()); + self.devices.push(device); + continue; + } + Err((err, common_device)) => { + error!("PCI device construction failed, reason: {:?}", err); + debug_assert!(device_id == *common_device.device_id()); + common_device + } + }; + self.common_devices.push_back(device); + } + self.drivers.push(driver); + } + + pub(super) fn register_common_device(&mut self, mut common_device: PciCommonDevice) { + debug!("Find pci common devices:{:x?}", common_device); + let device_id = common_device.device_id().clone(); + for driver in self.drivers.iter() { + common_device = match driver.probe(common_device) { + Ok(device) => { + debug_assert!(device_id == device.device_id()); + self.devices.push(device); + return; + } + Err((err, common_device)) => { + error!("PCI device construction failed, reason: {:?}", err); + debug_assert!(device_id == *common_device.device_id()); + common_device + } + }; + } + self.common_devices.push_back(common_device); + } + + pub(super) const fn new() -> Self { + Self { + common_devices: VecDeque::new(), + devices: Vec::new(), + drivers: Vec::new(), + } + } +} diff --git a/framework/jinux-frame/src/bus/pci/capability/mod.rs b/framework/jinux-frame/src/bus/pci/capability/mod.rs new file mode 100644 index 000000000..2072ba7c2 --- /dev/null +++ b/framework/jinux-frame/src/bus/pci/capability/mod.rs @@ -0,0 +1,135 @@ +use alloc::vec::Vec; + +use self::{msix::CapabilityMsixData, vendor::CapabilityVndrData}; + +use super::{ + cfg_space::{PciDeviceCommonCfgOffset, Status}, + common_device::PciCommonDevice, + PciDeviceLocation, +}; + +pub mod msix; +pub mod vendor; + +#[derive(Debug)] +pub struct Capability { + id: u8, + /// Pointer to the capability. + pos: u16, + /// Next Capability pointer, 0xFC if self is the last one. + next_ptr: u16, + /// The length of this Capability + len: u16, + cap_data: CapabilityData, +} + +#[derive(Debug, Clone)] +pub enum CapabilityData { + /// Id:0x01, Power Management + Pm, + /// Id:0x02, Accelerated Graphics Part + Agp, + /// Id:0x03, Vital Product Data + Vpd, + /// Id:0x04, Slot Identification + SlotId, + /// Id:0x05, Message Signalled Interrupts + Msi, + /// Id:0x06, CompactPCI HotSwap + Chswp, + /// Id:0x07, PCI-X + PciX, + /// Id:0x08, HyperTransport + Hp, + /// Id:0x09, Vendor-Specific + Vndr(CapabilityVndrData), + /// Id:0x0A, Debug port + Dbg, + /// Id:0x0B, CompactPCI Central Resource Control + Ccrc, + /// Id:0x0C, PCI Standard Hot-Plug Controller + Shpc, + /// Id:0x0D, Bridge subsystem vendor/device ID + Ssvid, + /// Id:0x0R, AGP Target PCI-PCI bridge + Agp3, + /// Id:0x0F, Secure Device + Secdev, + /// Id:0x10, PCI Express + Exp, + /// Id:0x11, MSI-X + Msix(CapabilityMsixData), + /// Id:0x12, SATA Data/Index Conf + Sata, + /// Id:0x13, PCI Advanced Features + Af, + /// Id:0x14, Enhanced Allocation + Ea, + /// Id:?, Unknown + Unknown(u8), +} + +impl Capability { + /// 0xFC, the top of the capability position. + const CAPABILITY_TOP: u16 = 0xFC; + + /// get the capabilities of one device + pub fn device_capabilities(dev: &mut PciCommonDevice) -> Vec { + if !dev.status().contains(Status::CAPABILITIES_LIST) { + return Vec::new(); + } + let mut capabilities = Vec::new(); + let mut cap_ptr = + dev.location() + .read8(PciDeviceCommonCfgOffset::CapabilitiesPointer as u16) as u16 + & PciDeviceLocation::BIT32_ALIGN_MASK; + let mut cap_ptr_vec = Vec::new(); + // read all cap_ptr so that it is easy for us to get the length. + while cap_ptr > 0 { + cap_ptr_vec.push(cap_ptr); + cap_ptr = dev.location().read8(cap_ptr + 1) as u16 & PciDeviceLocation::BIT32_ALIGN_MASK; + } + cap_ptr_vec.sort(); + // Push here so that we can calculate the length of the last capability. + cap_ptr_vec.push(Self::CAPABILITY_TOP); + let length = cap_ptr_vec.len(); + for i in 0..length - 1 { + let cap_ptr = cap_ptr_vec[i]; + let next_ptr = cap_ptr_vec[i + 1]; + let cap_type = dev.location().read8(cap_ptr); + let data = match cap_type { + 0x01 => CapabilityData::Pm, + 0x02 => CapabilityData::Agp, + 0x03 => CapabilityData::Vpd, + 0x04 => CapabilityData::SlotId, + 0x05 => CapabilityData::Msi, + 0x06 => CapabilityData::Chswp, + 0x07 => CapabilityData::PciX, + 0x08 => CapabilityData::Hp, + 0x09 => { + CapabilityData::Vndr(CapabilityVndrData::new(dev, cap_ptr, next_ptr - cap_ptr)) + } + 0x0A => CapabilityData::Dbg, + 0x0B => CapabilityData::Ccrc, + 0x0C => CapabilityData::Shpc, + 0x0D => CapabilityData::Ssvid, + 0x0E => CapabilityData::Agp3, + 0x0F => CapabilityData::Secdev, + 0x10 => CapabilityData::Exp, + 0x11 => CapabilityData::Msix(CapabilityMsixData::new(dev, cap_ptr)), + 0x12 => CapabilityData::Sata, + 0x13 => CapabilityData::Af, + 0x14 => CapabilityData::Ea, + _ => CapabilityData::Unknown(cap_type), + }; + capabilities.push(Self { + id: cap_type, + pos: cap_ptr, + next_ptr, + len: next_ptr - cap_ptr, + cap_data: data, + }); + } + capabilities + } +} diff --git a/framework/jinux-frame/src/bus/pci/capability/msix.rs b/framework/jinux-frame/src/bus/pci/capability/msix.rs new file mode 100644 index 000000000..78857f75a --- /dev/null +++ b/framework/jinux-frame/src/bus/pci/capability/msix.rs @@ -0,0 +1,141 @@ +use alloc::{sync::Arc, vec::Vec}; + +use crate::{ + bus::pci::{ + cfg_space::{Bar, MemoryBar}, + common_device::PciCommonDevice, + device_info::PciDeviceLocation, + }, + trap::IrqAllocateHandle, + vm::VmIo, +}; + +/// MSI-X capability. It will set the BAR space it uses to be hidden. +#[derive(Debug, Clone)] +#[repr(C)] +pub struct CapabilityMsixData { + loc: PciDeviceLocation, + ptr: u16, + table_size: u16, + /// MSIX table entry content: + /// | Vector Control: u32 | Msg Data: u32 | Msg Upper Addr: u32 | Msg Addr: u32 | + table_bar: Arc, + /// Pending bits table. + pending_table_bar: Arc, + irq_allocate_handles: Vec>>, +} + +impl CapabilityMsixData { + pub(super) fn new(dev: &mut PciCommonDevice, cap_ptr: u16) -> Self { + // Get Table and PBA offset, provide functions to modify them + let table_info = dev.location().read32(cap_ptr + 4); + let pba_info = dev.location().read32(cap_ptr + 8); + + let table_bar; + let pba_bar; + + let bar_manager = dev.bar_manager_mut(); + bar_manager.set_invisible((pba_info & 0b111) as u8); + bar_manager.set_invisible((table_info & 0b111) as u8); + match bar_manager + .bar_space_without_invisible((pba_info & 0b111) as u8) + .expect("MSIX cfg:pba BAR is none") + { + Bar::Memory(memory) => { + pba_bar = memory; + } + Bar::Io(_) => { + panic!("MSIX cfg:pba BAR is IO type") + } + }; + match bar_manager + .bar_space_without_invisible((table_info & 0b111) as u8) + .expect("MSIX cfg:table BAR is none") + { + Bar::Memory(memory) => { + table_bar = memory; + } + Bar::Io(_) => { + panic!("MSIX cfg:table BAR is IO type") + } + } + + let table_size = (dev.location().read16(cap_ptr + 2) & 0b11_1111_1111) + 1; + // TODO: Different architecture seems to have different, so we should set different address here. + let message_address = 0xFEE0_000 as u32; + let message_upper_address = 0 as u32; + + // Set message address 0xFEE0_0000 + for i in 0..table_size { + // Set message address and disable this msix entry + table_bar + .mmio() + .write_val((16 * i) as usize, &message_address) + .unwrap(); + table_bar + .mmio() + .write_val((16 * i + 4) as usize, &message_upper_address) + .unwrap(); + table_bar + .mmio() + .write_val((16 * i + 12) as usize, &(1 as u32)) + .unwrap(); + } + + let mut irq_allocate_handles = Vec::with_capacity(table_size as usize); + for i in 0..table_size { + irq_allocate_handles.push(None); + } + + Self { + loc: dev.location().clone(), + ptr: cap_ptr, + table_size: (dev.location().read16(cap_ptr + 2) & 0b11_1111_1111) + 1, + table_bar, + pending_table_bar: pba_bar, + irq_allocate_handles, + } + } + + pub fn table_size(&self) -> u16 { + // bit 10:0 table size + (self.loc.read16(self.ptr + 2) & 0b11_1111_1111) + 1 + } + + pub fn set_msix_enable(&self, enable: bool) { + // bit15: msix enable + let value = (enable as u16) << 15; + // message control + self.loc.write16( + self.ptr + 2, + set_bit(self.loc.read16(self.ptr + 2), 15, enable), + ) + } + + pub fn set_interrupt_enable(&self, enable: bool) { + // bit14: msix enable + let value = (enable as u16) << 14; + // message control + self.loc.write16( + self.ptr + 2, + set_bit(self.loc.read16(self.ptr + 2), 14, enable), + ) + } + + pub fn set_interrupt_vector(&mut self, vector: Arc, index: u16) { + if index >= self.table_size { + return; + } + let old_handles = + core::mem::replace(&mut self.irq_allocate_handles[index as usize], Some(vector)); + // Enable this msix vector + self.table_bar + .mmio() + .write_val((16 * index + 12) as usize, &(0 as u32)) + .unwrap(); + } +} + +fn set_bit(origin_value: u16, offset: usize, set: bool) -> u16 { + (origin_value & (!(1 << offset))) | ((set as u16) << offset) +} diff --git a/framework/jinux-frame/src/bus/pci/capability/vendor.rs b/framework/jinux-frame/src/bus/pci/capability/vendor.rs new file mode 100644 index 000000000..b4bc2d1d9 --- /dev/null +++ b/framework/jinux-frame/src/bus/pci/capability/vendor.rs @@ -0,0 +1,63 @@ +use crate::bus::pci::{common_device::PciCommonDevice, device_info::PciDeviceLocation}; +use crate::{Error, Result}; + +/// Vendor specific capability. Users can access this capability area at will, +/// except for the PCI configuration space which cannot be accessed at will through this structure. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct CapabilityVndrData { + location: PciDeviceLocation, + cap_ptr: u16, + length: u16, +} + +impl CapabilityVndrData { + pub(super) fn new(dev: &PciCommonDevice, cap_ptr: u16, length: u16) -> Self { + Self { + location: dev.location().clone(), + cap_ptr, + length, + } + } + + pub fn len(&self) -> u16 { + self.length + } + + pub fn read8(&self, offset: u16) -> Result { + self.check_range(offset)?; + Ok(self.location.read8(self.cap_ptr + offset)) + } + + pub fn write8(&self, offset: u16, value: u8) -> Result<()> { + self.check_range(offset)?; + Ok(self.location.write8(self.cap_ptr + offset, value)) + } + + pub fn read16(&self, offset: u16) -> Result { + self.check_range(offset)?; + Ok(self.location.read16(self.cap_ptr + offset)) + } + + pub fn write16(&self, offset: u16, value: u16) -> Result<()> { + self.check_range(offset)?; + Ok(self.location.write16(self.cap_ptr + offset, value)) + } + + pub fn read32(&self, offset: u16) -> Result { + self.check_range(offset)?; + Ok(self.location.read32(self.cap_ptr + offset)) + } + + pub fn write32(&self, offset: u16, value: u32) -> Result<()> { + self.check_range(offset)?; + Ok(self.location.write32(self.cap_ptr + offset, value)) + } + + #[inline] + fn check_range(&self, offset: u16) -> Result<()> { + if self.length > offset { + return Err(Error::InvalidArgs); + } + Ok(()) + } +} diff --git a/framework/jinux-frame/src/bus/pci/cfg_space.rs b/framework/jinux-frame/src/bus/pci/cfg_space.rs new file mode 100644 index 000000000..5bad63c43 --- /dev/null +++ b/framework/jinux-frame/src/bus/pci/cfg_space.rs @@ -0,0 +1,201 @@ +use alloc::sync::Arc; +use bitflags::bitflags; + +use crate::{mmio::Mmio, Error, Result}; + +use super::PciDeviceLocation; + +#[repr(u16)] +pub enum PciDeviceCommonCfgOffset { + VendorId = 0x00, + DeviceId = 0x02, + Command = 0x04, + Status = 0x06, + RevisionId = 0x08, + ClassCode = 0x09, + CacheLineSize = 0x0C, + LatencyTimer = 0x0D, + HeaderType = 0x0E, + Bist = 0x0F, + Bar0 = 0x10, + Bar1 = 0x14, + Bar2 = 0x18, + Bar3 = 0x1C, + Bar4 = 0x20, + Bar5 = 0x24, + CardbusCisPtr = 0x28, + SubsystemVendorId = 0x2C, + SubsystemId = 0x2E, + XromBar = 0x30, + CapabilitiesPointer = 0x34, + InterruptLine = 0x3C, + InterruptPin = 0x3D, + MinGrant = 0x3E, + MaxLatency = 0x3F, +} + +bitflags! { + /// PCI device common config space command register. + pub struct Command: u16 { + const IO_SPACE = 1 << 0; + const MEMORY_SPACE = 1 << 1; + const BUS_MASTER = 1 << 2; + const SPECIAL_CYCLES = 1 << 3; + const MWI_ENABLE = 1 << 4; + const VGA_PALETTE_SNOOP = 1 << 5; + const PARITY_ERROR_RESPONSE = 1 << 6; + const STEPPING_CONTROL = 1 << 7; + const SERR_ENABLE = 1 << 8; + const FAST_BACK_TO_BACK_ENABLE = 1 << 9; + const INTERRUPT_DISABLE = 1 << 10; + } +} + +bitflags! { + /// PCI device common config space status register. + pub struct Status: u16 { + const INTERRUPT_STATUS = 1 << 3; + const CAPABILITIES_LIST = 1 << 4; + const MHZ66_CAPABLE = 1 << 5; + const FAST_BACK_TO_BACK_CAPABLE = 1 << 7; + const MASTER_DATA_PARITY_ERROR = 1 << 8; + const DEVSEL_MEDIUM_TIMING = 1 << 9; + const DEVSEL_SLOW_TIMING = 1 << 10; + const SIGNALED_TARGET_ABORT = 1 << 11; + const RECEIVED_TARGET_ABORT = 1 << 12; + const RECEIVED_MASTER_ABORT = 1 << 13; + const SIGNALED_SYSTEM_ERROR = 1 << 14; + const DETECTED_PARITY_ERROR = 1 << 15; + } +} + +/// BAR space in PCI common config space. +#[derive(Debug, Clone)] +pub enum Bar { + Memory(Arc), + Io(Arc), +} + +impl Bar { + pub(super) fn new(location: PciDeviceLocation, index: u8) -> Result { + if index >= 6 { + return Err(Error::InvalidArgs); + } + // Get the original value first, then write all 1 to the register to get the length + let raw = location.read32(index as u16 * 4 + PciDeviceCommonCfgOffset::Bar0 as u16); + if raw == 0 { + // no BAR + return Err(Error::InvalidArgs); + } + Ok(if raw & 1 == 0 { + Self::Memory(Arc::new(MemoryBar::new(&location, index)?)) + } else { + // IO BAR + Self::Io(Arc::new(IoBar::new(&location, index)?)) + }) + } +} + +#[derive(Debug, Clone)] +pub struct MemoryBar { + base: u64, + size: u32, + /// Whether this bar is prefetchable, allowing the CPU to get the data + /// in advance. + prefetchable: bool, + address_length: AddrLen, + mmio: Mmio, +} + +impl MemoryBar { + /// Memory BAR bits type + pub fn address_length(&self) -> AddrLen { + self.address_length + } + + pub fn prefetchable(&self) -> bool { + self.prefetchable + } + + pub fn base(&self) -> u64 { + self.base + } + + pub fn size(&self) -> u32 { + self.size + } + + pub fn mmio(&self) -> &Mmio { + &self.mmio + } + + /// Create a memory BAR structure. + fn new(location: &PciDeviceLocation, index: u8) -> Result { + // Get the original value first, then write all 1 to the register to get the length + let offset = index as u16 * 4 + PciDeviceCommonCfgOffset::Bar0 as u16; + let raw = location.read32(offset); + location.write32(offset, !0); + let len_encoded = location.read32(offset); + location.write32(offset, raw); + let mut address_length = AddrLen::Bits32; + // base address, it may be bit64 or bit32 + let base: u64 = match (raw & 0b110) >> 1 { + // bits32 + 0 => (raw & !0xF) as u64, + // bits64 + 2 => { + address_length = AddrLen::Bits64; + ((raw & !0xF) as u64) | ((location.read32(offset + 4) as u64) << 32) + } + _ => { + return Err(Error::InvalidArgs); + } + }; + // length + let size = !(len_encoded & !0xF).wrapping_add(1); + let prefetchable = if raw & 0b1000 == 0 { false } else { true }; + Ok(MemoryBar { + base, + size, + prefetchable, + address_length, + mmio: Mmio::new((base as usize)..((base + size as u64) as usize)).unwrap(), + }) + } +} + +/// Whether this BAR is 64bit address or 32bit address +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum AddrLen { + Bits32, + Bits64, +} + +#[derive(Debug, Clone, Copy)] +pub struct IoBar { + base: u32, + size: u32, +} + +impl IoBar { + pub fn base(&self) -> u32 { + self.base + } + + pub fn size(&self) -> u32 { + self.size + } + + fn new(location: &PciDeviceLocation, index: u8) -> Result { + let offset = index as u16 * 4 + PciDeviceCommonCfgOffset::Bar0 as u16; + let raw = location.read32(offset); + location.write32(offset, !0); + let len_encoded = location.read32(offset); + location.write32(offset, raw); + let len = !(len_encoded & !0x3) + 1; + Ok(Self { + base: raw & !0x3, + size: len, + }) + } +} diff --git a/framework/jinux-frame/src/bus/pci/common_device.rs b/framework/jinux-frame/src/bus/pci/common_device.rs new file mode 100644 index 000000000..4af6491c2 --- /dev/null +++ b/framework/jinux-frame/src/bus/pci/common_device.rs @@ -0,0 +1,153 @@ +use alloc::vec::Vec; + +use super::{ + capability::Capability, + cfg_space::{AddrLen, Bar, Command, PciDeviceCommonCfgOffset, Status}, + device_info::{PciDeviceId, PciDeviceLocation}, +}; + +/// PCI common device, Contains a range of information and functions common to PCI devices. +#[derive(Debug)] +pub struct PciCommonDevice { + device_id: PciDeviceId, + location: PciDeviceLocation, + bar_manager: BarManager, + capabilities: Vec, +} + +impl PciCommonDevice { + pub fn device_id(&self) -> &PciDeviceId { + &self.device_id + } + + pub fn location(&self) -> &PciDeviceLocation { + &self.location + } + + pub fn bar_manager(&self) -> &BarManager { + &self.bar_manager + } + + pub fn capabilities(&self) -> &Vec { + &self.capabilities + } + + pub fn command(&self) -> Command { + Command::from_bits_truncate( + self.location + .read16(PciDeviceCommonCfgOffset::Command as u16), + ) + } + + pub fn set_command(&self, command: Command) { + self.location + .write16(PciDeviceCommonCfgOffset::Command as u16, command.bits()) + } + + pub fn status(&self) -> Status { + Status::from_bits_truncate( + self.location + .read16(PciDeviceCommonCfgOffset::Status as u16), + ) + } + + pub(super) fn new(location: PciDeviceLocation) -> Option { + if location.read16(0) == 0xFFFF { + // not exists + return None; + } + + let capabilities = Vec::new(); + let device_id = PciDeviceId::new(location); + let bar_manager = BarManager::new(location); + let mut device = Self { + device_id, + location, + bar_manager, + capabilities, + }; + device.capabilities = Capability::device_capabilities(&mut device); + Some(device) + } + + pub(super) fn bar_manager_mut(&mut self) -> &mut BarManager { + &mut self.bar_manager + } + + pub(super) fn capabilities_mut(&mut self) -> &mut Vec { + &mut self.capabilities + } +} + +#[derive(Debug)] +pub struct BarManager { + /// BARs, the bool indicate whether this bar should exposed to unprivileged part. + bars: [Option<(Bar, bool)>; 6], +} + +impl BarManager { + /// Gain access to the BAR space and return None if that BAR is set to be invisible or absent. + pub fn bar(&self, idx: u8) -> Option { + let Some((bar,visible)) = self.bars[idx as usize].clone() else{ + return None; + }; + if visible { + Some(bar) + } else { + None + } + } + + /// Parse the BAR space by PCI device location. + fn new(location: PciDeviceLocation) -> Self { + let header_type = location.read8(PciDeviceCommonCfgOffset::HeaderType as u16) & !(1 << 7); + // Get the max bar amount, header type=0 => end device; header type=1 => PCI bridge. + let max = match header_type { + 0 => 6, + 1 => 2, + _ => 0, + }; + let mut idx = 0; + let mut bars = [None, None, None, None, None, None]; + while idx < max { + match Bar::new(location, idx) { + Ok(bar) => { + match &bar { + Bar::Memory(memory_bar) => { + if memory_bar.address_length() == AddrLen::Bits64 { + idx += 1; + } + } + Bar::Io(_) => {} + } + bars[idx as usize] = Some((bar, false)); + } + // ignore for now + Err(_) => {} + } + idx += 1; + } + Self { bars } + } + + pub(super) fn set_invisible(&mut self, idx: u8) { + if self.bars[idx as usize].is_some() { + let Some((bar,_)) = self.bars[idx as usize].clone() else{ + return; + }; + self.bars[idx as usize] = Some((bar, false)); + } + let Some((_,visible)) = self.bars[idx as usize] else{ + return; + }; + } + + /// Gain access to the BAR space and return None if that BAR is absent. + pub(super) fn bar_space_without_invisible(&self, idx: u8) -> Option { + if let Some((bar, _)) = self.bars[idx as usize].clone() { + Some(bar) + } else { + None + } + } +} diff --git a/framework/jinux-frame/src/bus/pci/device_info.rs b/framework/jinux-frame/src/bus/pci/device_info.rs new file mode 100644 index 000000000..6d31ded94 --- /dev/null +++ b/framework/jinux-frame/src/bus/pci/device_info.rs @@ -0,0 +1,141 @@ +use core::iter; + +use crate::arch::device::pci::{PCI_ADDRESS_PORT, PCI_DATA_PORT}; + +use super::cfg_space::PciDeviceCommonCfgOffset; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct PciDeviceId { + pub vendor_id: u16, + pub device_id: u16, + pub revision_id: u8, + pub prog_if: u8, + pub subclass: u8, + pub class: u8, + pub subsystem_vendor_id: u16, + pub subsystem_id: u16, +} + +impl PciDeviceId { + pub(super) fn new(location: PciDeviceLocation) -> Self { + let vendor_id = location.read16(PciDeviceCommonCfgOffset::VendorId as u16); + let device_id = location.read16(PciDeviceCommonCfgOffset::DeviceId as u16); + let revision_id = location.read8(PciDeviceCommonCfgOffset::RevisionId as u16); + let prog_if = location.read8(PciDeviceCommonCfgOffset::ClassCode as u16); + let subclass = location.read8(PciDeviceCommonCfgOffset::ClassCode as u16 + 1); + let class = location.read8(PciDeviceCommonCfgOffset::ClassCode as u16 + 1); + let subsystem_vendor_id = + location.read16(PciDeviceCommonCfgOffset::SubsystemVendorId as u16); + let subsystem_id = location.read16(PciDeviceCommonCfgOffset::SubsystemId as u16); + Self { + vendor_id, + device_id, + revision_id, + prog_if, + subclass, + class, + subsystem_vendor_id, + subsystem_id, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PciDeviceLocation { + pub bus: u8, + /// Max 31 + pub device: u8, + /// Max 7 + pub function: u8, +} + +impl PciDeviceLocation { + pub const MIN_BUS: u8 = 0; + pub const MAX_BUS: u8 = 255; + pub const MIN_DEVICE: u8 = 0; + pub const MAX_DEVICE: u8 = 31; + pub const MIN_FUNCTION: u8 = 0; + pub const MAX_FUNCTION: u8 = 7; + /// By encoding bus, device, and function into u32, user can access a PCI device in x86 by passing in this value. + #[inline(always)] + pub fn encode_as_x86_address_value(self) -> u32 { + // 1 << 31: Configuration enable + (1 << 31) + | ((self.bus as u32) << 16) + | (((self.device as u32) & 0b11111) << 11) + | (((self.function as u32) & 0b111) << 8) + } + + /// Returns an iterator that enumerates all possible PCI device locations. + pub fn all() -> impl Iterator { + iter::from_generator(|| { + for bus in Self::MIN_BUS..=Self::MAX_BUS { + for device in Self::MIN_DEVICE..=Self::MAX_DEVICE { + for function in Self::MIN_FUNCTION..=Self::MAX_FUNCTION { + let loc = PciDeviceLocation { + bus, + device, + function, + }; + yield loc; + } + } + } + }) + } +} + +impl PciDeviceLocation { + pub(super) const BIT32_ALIGN_MASK: u16 = 0xFFFC; + + pub(super) fn read8(&self, offset: u16) -> u8 { + let val = self.read32(offset & Self::BIT32_ALIGN_MASK); + ((val >> ((offset as usize & 0b11) << 3)) & 0xFF) as u8 + } + + pub(super) fn read16(&self, offset: u16) -> u16 { + let val = self.read32(offset & Self::BIT32_ALIGN_MASK); + ((val >> ((offset as usize & 0b10) << 3)) & 0xFFFF) as u16 + } + + pub(super) fn read32(&self, offset: u16) -> u32 { + debug_assert!( + (offset & 0b11) == 0, + "misaligned PCI configuration dword u32 read" + ); + PCI_ADDRESS_PORT + .write(self.encode_as_x86_address_value() | (offset & Self::BIT32_ALIGN_MASK) as u32); + PCI_DATA_PORT.read().to_le() + } + + pub(super) fn write8(&self, offset: u16, val: u8) { + let old = self.read32(offset & Self::BIT32_ALIGN_MASK); + let dest = offset as usize & 0b11 << 3; + let mask = (0xFF << dest) as u32; + self.write32( + offset & Self::BIT32_ALIGN_MASK, + ((val as u32) << dest | (old & !mask)).to_le(), + ); + } + + pub(super) fn write16(&self, offset: u16, val: u16) { + let old = self.read32(offset & Self::BIT32_ALIGN_MASK); + let dest = offset as usize & 0b10 << 3; + let mask = (0xFFFF << dest) as u32; + self.write32( + offset & Self::BIT32_ALIGN_MASK, + ((val as u32) << dest | (old & !mask)).to_le(), + ); + } + + pub(super) fn write32(&self, offset: u16, val: u32) { + debug_assert!( + (offset & 0b11) == 0, + "misaligned PCI configuration dword u32 write" + ); + + PCI_ADDRESS_PORT + .write(self.encode_as_x86_address_value() | (offset & Self::BIT32_ALIGN_MASK) as u32); + PCI_DATA_PORT.write(val.to_le()) + } +} diff --git a/framework/jinux-frame/src/bus/pci/mod.rs b/framework/jinux-frame/src/bus/pci/mod.rs index dab095252..d8a7c9aee 100644 --- a/framework/jinux-frame/src/bus/pci/mod.rs +++ b/framework/jinux-frame/src/bus/pci/mod.rs @@ -1,46 +1,72 @@ -use core::iter; +//! PCI bus +//! +//! Users can implement the bus under the `PciDriver` to the PCI bus to register devices, +//! when the physical device and the driver match successfully, it will be provided through the driver `construct` function +//! to construct a structure that implements the `PciDevice` trait. And in the end, +//! PCI bus will store a reference to the structure and finally call the driver's probe function to remind the driver of a new device access. +//! +//! Use case: +//! +//! ```rust norun +//! #[derive(Debug)] +//! pub struct PciDeviceA { +//! common_device: PciCommonDevice, +//! } +//! +//! impl PciDevice for PciDeviceA { +//! fn device_id(&self) -> PciDeviceId { +//! self.common_device.device_id().clone() +//! } +//! } +//! +//! #[derive(Debug)] +//! pub struct PciDriverA { +//! devices: Mutex>>, +//! } +//! +//! impl PciDriver for PciDriverA { +//! fn probe( +//! &self, +//! device: PciCommonDevice, +//! ) -> Result, (PciDriverProbeError, PciCommonDevice)> { +//! if device.device_id().vendor_id != 0x1234 { +//! return Err((PciDriverProbeError::DeviceNotMatch, device)); +//! } +//! let device = Arc::new(PciDeviceA { +//! common_device: device, +//! }); +//! self.devices.lock().push(device.clone()); +//! Ok(device) +//! } +//! } +//! +//! pub fn driver_a_init() { +//! let driver_a = Arc::new(PciDriverA { +//! devices: Mutex::new(Vec::new()), +//! }); +//! PCI_BUS.lock().register_driver(driver_a); +//! } +//! ``` -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct PciDeviceLocation { - pub bus: u8, - /// Max 31 - pub device: u8, - /// Max 7 - pub function: u8, -} +pub mod bus; +mod capability; +mod cfg_space; +mod common_device; +mod device_info; -impl PciDeviceLocation { - pub const MIN_BUS: u8 = 0; - pub const MAX_BUS: u8 = 255; - pub const MIN_DEVICE: u8 = 0; - pub const MAX_DEVICE: u8 = 31; - pub const MIN_FUNCTION: u8 = 0; - pub const MAX_FUNCTION: u8 = 7; - /// By encoding bus, device, and function into u32, user can access a PCI device in x86 by passing in this value. - #[inline(always)] - pub fn encode_as_x86_address_value(self) -> u32 { - // 1 << 31: Configuration enable - (1 << 31) - | ((self.bus as u32) << 16) - | (((self.device as u32) & 0b11111) << 11) - | (((self.function as u32) & 0b111) << 8) - } +pub use device_info::{PciDeviceId, PciDeviceLocation}; +use spin::Mutex; - /// Returns an iterator that enumerates all possible PCI device locations. - pub fn all() -> impl Iterator { - iter::from_generator(|| { - for bus in Self::MIN_BUS..=Self::MAX_BUS { - for device in Self::MIN_DEVICE..=Self::MAX_DEVICE { - for function in Self::MIN_FUNCTION..=Self::MAX_FUNCTION { - let loc = PciDeviceLocation { - bus, - device, - function, - }; - yield loc; - } - } - } - }) +use self::{bus::PciBus, common_device::PciCommonDevice}; + +pub static PCI_BUS: Mutex = Mutex::new(PciBus::new()); + +pub(crate) fn init() { + let mut lock = PCI_BUS.lock(); + for location in PciDeviceLocation::all() { + let Some(device) = PciCommonDevice::new(location)else{ + continue; + }; + lock.register_common_device(device); } } diff --git a/framework/jinux-frame/src/lib.rs b/framework/jinux-frame/src/lib.rs index 2a7365d95..5134951f9 100644 --- a/framework/jinux-frame/src/lib.rs +++ b/framework/jinux-frame/src/lib.rs @@ -53,6 +53,7 @@ pub fn init() { trap::init(); arch::after_all_init(); mmio::init(); + bus::init(); register_irq_common_callback(); invoke_c_init_funcs(); }