mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-19 20:46:35 +00:00
Implement the PCI bus framework
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
13c4c614b5
commit
a6dcdf6952
@ -1 +1,5 @@
|
||||
pub mod pci;
|
||||
|
||||
pub fn init() {
|
||||
pci::init();
|
||||
}
|
||||
|
95
framework/jinux-frame/src/bus/pci/bus.rs
Normal file
95
framework/jinux-frame/src/bus/pci/bus.rs
Normal file
@ -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<Arc<dyn PciDevice>, (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<PciCommonDevice>,
|
||||
devices: Vec<Arc<dyn PciDevice>>,
|
||||
drivers: Vec<Arc<dyn PciDriver>>,
|
||||
}
|
||||
|
||||
impl PciBus {
|
||||
pub fn register_driver(&mut self, driver: Arc<dyn PciDriver>) {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
135
framework/jinux-frame/src/bus/pci/capability/mod.rs
Normal file
135
framework/jinux-frame/src/bus/pci/capability/mod.rs
Normal file
@ -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<Self> {
|
||||
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
|
||||
}
|
||||
}
|
141
framework/jinux-frame/src/bus/pci/capability/msix.rs
Normal file
141
framework/jinux-frame/src/bus/pci/capability/msix.rs
Normal file
@ -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<MemoryBar>,
|
||||
/// Pending bits table.
|
||||
pending_table_bar: Arc<MemoryBar>,
|
||||
irq_allocate_handles: Vec<Option<Arc<IrqAllocateHandle>>>,
|
||||
}
|
||||
|
||||
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<IrqAllocateHandle>, 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)
|
||||
}
|
63
framework/jinux-frame/src/bus/pci/capability/vendor.rs
Normal file
63
framework/jinux-frame/src/bus/pci/capability/vendor.rs
Normal file
@ -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<u8> {
|
||||
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<u16> {
|
||||
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<u32> {
|
||||
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(())
|
||||
}
|
||||
}
|
201
framework/jinux-frame/src/bus/pci/cfg_space.rs
Normal file
201
framework/jinux-frame/src/bus/pci/cfg_space.rs
Normal file
@ -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<MemoryBar>),
|
||||
Io(Arc<IoBar>),
|
||||
}
|
||||
|
||||
impl Bar {
|
||||
pub(super) fn new(location: PciDeviceLocation, index: u8) -> Result<Self> {
|
||||
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<Self> {
|
||||
// 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<Self> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
153
framework/jinux-frame/src/bus/pci/common_device.rs
Normal file
153
framework/jinux-frame/src/bus/pci/common_device.rs
Normal file
@ -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<Capability>,
|
||||
}
|
||||
|
||||
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<Capability> {
|
||||
&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<Self> {
|
||||
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<Capability> {
|
||||
&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<Bar> {
|
||||
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<Bar> {
|
||||
if let Some((bar, _)) = self.bars[idx as usize].clone() {
|
||||
Some(bar)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
141
framework/jinux-frame/src/bus/pci/device_info.rs
Normal file
141
framework/jinux-frame/src/bus/pci/device_info.rs
Normal file
@ -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<Item = PciDeviceLocation> {
|
||||
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())
|
||||
}
|
||||
}
|
@ -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<Vec<Arc<PciDeviceA>>>,
|
||||
//! }
|
||||
//!
|
||||
//! impl PciDriver for PciDriverA {
|
||||
//! fn probe(
|
||||
//! &self,
|
||||
//! device: PciCommonDevice,
|
||||
//! ) -> Result<Arc<dyn PciDevice>, (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<Item = PciDeviceLocation> {
|
||||
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<PciBus> = 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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
Reference in New Issue
Block a user