Skip to content

Commit 565ef14

Browse files
authored
Merge pull request #1822 from zyuiop/feat/mmap-dev-discovery
feat(x86_64): add PCIe support
2 parents 00ed001 + 817f4c9 commit 565ef14

File tree

5 files changed

+204
-14
lines changed

5 files changed

+204
-14
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,12 @@ jobs:
239239
if: matrix.arch != 'riscv64'
240240
- run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} --package httpd --features ci,hermit/dhcpv4,hermit/virtio-net qemu ${{ matrix.flags }} --devices virtio-net-pci --no-default-virtio-features
241241
if: matrix.arch != 'riscv64'
242+
- run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} --package httpd --features ci,hermit/dhcpv4,hermit/virtio-net qemu ${{ matrix.flags }} --devices virtio-net-pci --pci-e
243+
if: matrix.arch == 'x86_64'
242244
- run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} --package httpd --features ci,hermit/dhcpv4,hermit/virtio-net qemu ${{ matrix.flags }} --devices virtio-net-pci --uefi
243245
if: matrix.arch == 'x86_64'
246+
- run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} --package httpd --features ci,hermit/dhcpv4,hermit/virtio-net qemu ${{ matrix.flags }} --devices virtio-net-pci --uefi --pci-e
247+
if: matrix.arch == 'x86_64'
244248
- run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} --package httpd --no-default-features --features ci,hermit/dhcpv4,hermit/tcp,hermit/virtio-net qemu ${{ matrix.flags }} --microvm --devices virtio-net-mmio
245249
if: matrix.arch == 'x86_64'
246250
- run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} --package httpd --no-default-features --features ci,hermit/dhcpv4,hermit/tcp,hermit/virtio-net qemu ${{ matrix.flags }} --devices virtio-net-mmio

src/arch/x86_64/kernel/acpi.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ const SLP_EN: u16 = 1 << 13;
4545

4646
/// The "Multiple APIC Description Table" (MADT) preserved for get_apic_table().
4747
static MADT: OnceCell<AcpiTable<'_>> = OnceCell::new();
48+
49+
/// The MCFG table, to address PCIe configuration space
50+
#[cfg(feature = "pci")]
51+
static MCFG: OnceCell<AcpiTable<'_>> = OnceCell::new();
52+
4853
/// The PM1A Control I/O Port for powering off the computer through ACPI.
4954
static PM1A_CNT_BLK: OnceCell<Port<u16>> = OnceCell::new();
5055
/// The Sleeping State Type code for powering off the computer through ACPI.
@@ -487,6 +492,11 @@ pub fn get_madt() -> Option<&'static AcpiTable<'static>> {
487492
MADT.get()
488493
}
489494

495+
#[cfg(feature = "pci")]
496+
pub fn get_mcfg_table() -> Option<&'static AcpiTable<'static>> {
497+
MCFG.get()
498+
}
499+
490500
pub fn poweroff() {
491501
if let (Some(mut pm1a_cnt_blk), Some(&slp_typa)) = (PM1A_CNT_BLK.get().cloned(), SLP_TYPA.get())
492502
{
@@ -562,6 +572,16 @@ pub fn init() {
562572
"SSDT at {table_physical_address:p} has invalid checksum"
563573
);
564574
parse_ssdt(table);
575+
} else if table.header.signature() == "MCFG" {
576+
#[cfg(feature = "pci")]
577+
{
578+
assert!(
579+
verify_checksum(table.header_start_address(), table.header.length as usize)
580+
.is_ok(),
581+
"MCFG at {table_physical_address:p} has invalid checksum"
582+
);
583+
MCFG.set(table).unwrap();
584+
}
565585
}
566586
}
567587
}

src/arch/x86_64/kernel/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,14 @@ pub fn boot_processor_init() {
115115
interrupts::install();
116116
systemtime::init();
117117

118-
if is_uhyve_with_pci() || !is_uhyve() {
119-
#[cfg(feature = "pci")]
120-
pci::init();
121-
}
122118
if !env::is_uhyve() {
123119
#[cfg(feature = "acpi")]
124120
acpi::init();
125121
}
122+
if is_uhyve_with_pci() || !is_uhyve() {
123+
#[cfg(feature = "pci")]
124+
pci::init();
125+
}
126126

127127
apic::init();
128128
scheduler::install_timer_handler();

src/arch/x86_64/kernel/pci.rs

Lines changed: 169 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,54 @@
1+
use core::ops::Range;
2+
13
use pci_types::{ConfigRegionAccess, PciAddress, PciHeader};
24
use x86_64::instructions::port::Port;
35

46
use crate::drivers::pci::{PCI_DEVICES, PciDevice};
57

6-
const PCI_MAX_BUS_NUMBER: u8 = 32;
7-
const PCI_MAX_DEVICE_NUMBER: u8 = 32;
8-
98
const PCI_CONFIG_ADDRESS_ENABLE: u32 = 1 << 31;
109

1110
const CONFIG_ADDRESS: Port<u32> = Port::new(0xcf8);
1211
const CONFIG_DATA: Port<u32> = Port::new(0xcfc);
1312

1413
#[derive(Debug, Copy, Clone)]
15-
pub(crate) struct PciConfigRegion;
14+
pub enum PciConfigRegion {
15+
Pci(LegacyPciConfigRegion),
16+
#[cfg(feature = "acpi")]
17+
PciE(pcie::McfgEntry),
18+
}
19+
20+
impl ConfigRegionAccess for PciConfigRegion {
21+
unsafe fn read(&self, address: PciAddress, offset: u16) -> u32 {
22+
match self {
23+
PciConfigRegion::Pci(entry) => unsafe { entry.read(address, offset) },
24+
#[cfg(feature = "acpi")]
25+
PciConfigRegion::PciE(entry) => unsafe { entry.read(address, offset) },
26+
}
27+
}
28+
29+
unsafe fn write(&self, address: PciAddress, offset: u16, value: u32) {
30+
match self {
31+
PciConfigRegion::Pci(entry) => unsafe {
32+
entry.write(address, offset, value);
33+
},
34+
#[cfg(feature = "acpi")]
35+
PciConfigRegion::PciE(entry) => unsafe {
36+
entry.write(address, offset, value);
37+
},
38+
}
39+
}
40+
}
41+
42+
#[derive(Debug, Copy, Clone)]
43+
pub(crate) struct LegacyPciConfigRegion;
1644

17-
impl PciConfigRegion {
45+
impl LegacyPciConfigRegion {
1846
pub const fn new() -> Self {
1947
Self {}
2048
}
2149
}
2250

23-
impl ConfigRegionAccess for PciConfigRegion {
51+
impl ConfigRegionAccess for LegacyPciConfigRegion {
2452
#[inline]
2553
unsafe fn read(&self, pci_addr: PciAddress, register: u16) -> u32 {
2654
let mut config_address = CONFIG_ADDRESS;
@@ -57,13 +85,29 @@ impl ConfigRegionAccess for PciConfigRegion {
5785
}
5886

5987
pub(crate) fn init() {
60-
debug!("Scanning PCI Busses 0 to {}", PCI_MAX_BUS_NUMBER - 1);
88+
#[cfg(feature = "acpi")]
89+
if pcie::init_pcie() {
90+
info!("Initialized PCIe");
91+
return;
92+
}
93+
94+
// For Hermit, we currently limit scanning to the first 32 buses.
95+
const PCI_MAX_BUS_NUMBER: u8 = 32;
96+
scan_bus(
97+
0..PCI_MAX_BUS_NUMBER,
98+
PciConfigRegion::Pci(LegacyPciConfigRegion::new()),
99+
);
100+
info!("Initialized PCI");
101+
}
102+
103+
fn scan_bus(bus_range: Range<u8>, pci_config: PciConfigRegion) {
104+
debug!("Scanning PCI buses {bus_range:?}");
61105

62106
// Hermit only uses PCI for network devices.
63107
// Therefore, multifunction devices as well as additional bridges are not scanned.
64-
// We also limit scanning to the first 32 buses.
65-
let pci_config = PciConfigRegion::new();
66-
for bus in 0..PCI_MAX_BUS_NUMBER {
108+
for bus in bus_range {
109+
// For Hermit, we currently limit scanning to the first 32 devices.
110+
const PCI_MAX_DEVICE_NUMBER: u8 = 32;
67111
for device in 0..PCI_MAX_DEVICE_NUMBER {
68112
let pci_address = PciAddress::new(0, bus, device, 0);
69113
let header = PciHeader::new(pci_address);
@@ -76,3 +120,118 @@ pub(crate) fn init() {
76120
}
77121
}
78122
}
123+
124+
#[cfg(feature = "acpi")]
125+
mod pcie {
126+
use core::{ptr, slice};
127+
128+
use memory_addresses::{PhysAddr, VirtAddr};
129+
use pci_types::{ConfigRegionAccess, PciAddress};
130+
131+
use super::PciConfigRegion;
132+
use crate::arch::mm::paging::{
133+
self, LargePageSize, PageTableEntryFlags, PageTableEntryFlagsExt,
134+
};
135+
use crate::env::kernel::acpi;
136+
use crate::mm::device_alloc::DeviceAlloc;
137+
138+
pub fn init_pcie() -> bool {
139+
let Some(table) = acpi::get_mcfg_table() else {
140+
return false;
141+
};
142+
143+
let start = ptr::with_exposed_provenance::<McfgEntry>(table.table_start_address() + 8);
144+
let end = ptr::with_exposed_provenance::<McfgEntry>(table.table_end_address());
145+
let entries = unsafe { slice::from_ptr_range(start..end) };
146+
147+
if entries.is_empty() {
148+
return false;
149+
}
150+
151+
for entry in entries {
152+
init_pcie_bus(entry);
153+
}
154+
155+
true
156+
}
157+
158+
#[derive(Clone, Copy, Debug)]
159+
#[repr(C, packed)]
160+
pub struct McfgEntry {
161+
pub base_address: u64,
162+
pub pci_segment_group: u16,
163+
pub bus_number_start: u8,
164+
pub bus_number_end: u8,
165+
_reserved: u32,
166+
}
167+
168+
impl McfgEntry {
169+
pub fn pci_config_space_address(
170+
&self,
171+
bus_number: u8,
172+
device: u8,
173+
function: u8,
174+
) -> PhysAddr {
175+
PhysAddr::new(
176+
self.base_address
177+
+ ((u64::from(bus_number) << 20)
178+
| ((u64::from(device) & 0x1f) << 15)
179+
| ((u64::from(function) & 0x7) << 12)),
180+
)
181+
}
182+
}
183+
184+
impl ConfigRegionAccess for McfgEntry {
185+
unsafe fn read(&self, address: PciAddress, offset: u16) -> u32 {
186+
assert!(address.segment() == self.pci_segment_group);
187+
assert!(address.bus() >= self.bus_number_start);
188+
assert!(address.bus() <= self.bus_number_end);
189+
190+
let phys_addr =
191+
self.pci_config_space_address(address.bus(), address.device(), address.function())
192+
+ u64::from(offset);
193+
let ptr = DeviceAlloc.ptr_from::<u32>(phys_addr);
194+
195+
unsafe { ptr.read_volatile() }
196+
}
197+
198+
unsafe fn write(&self, address: PciAddress, offset: u16, value: u32) {
199+
assert!(address.segment() == self.pci_segment_group);
200+
assert!(address.bus() >= self.bus_number_start);
201+
assert!(address.bus() <= self.bus_number_end);
202+
203+
let phys_addr =
204+
self.pci_config_space_address(address.bus(), address.device(), address.function())
205+
+ u64::from(offset);
206+
let ptr = DeviceAlloc.ptr_from::<u32>(phys_addr);
207+
208+
unsafe {
209+
ptr.write_volatile(value);
210+
}
211+
}
212+
}
213+
214+
fn init_pcie_bus(bus_entry: &McfgEntry) {
215+
let phys_addr = PhysAddr::new(bus_entry.base_address);
216+
let virt_addr = VirtAddr::from_ptr(DeviceAlloc.ptr_from::<()>(phys_addr));
217+
if paging::virtual_to_physical(virt_addr) != Some(phys_addr) {
218+
debug!("Mapping PCIe memory");
219+
let flags = {
220+
let mut flags = PageTableEntryFlags::empty();
221+
flags.normal().writable().execute_disable();
222+
flags
223+
};
224+
paging::map::<LargePageSize>(
225+
virt_addr,
226+
phys_addr,
227+
bus_entry.bus_number_end.into(),
228+
flags,
229+
);
230+
}
231+
232+
super::scan_bus(
233+
bus_entry.bus_number_start..bus_entry.bus_number_end,
234+
PciConfigRegion::PciE(*bus_entry),
235+
);
236+
}
237+
}

xtask/src/ci/qemu.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ pub struct Qemu {
2929
#[arg(long)]
3030
microvm: bool,
3131

32+
/// Enable PCIe support.
33+
#[arg(long)]
34+
pci_e: bool,
35+
3236
/// Enable UEFI.
3337
#[arg(long)]
3438
uefi: bool,
@@ -182,6 +186,7 @@ impl Qemu {
182186

183187
let image_args = if self.uefi {
184188
let sh = crate::sh()?;
189+
sh.remove_path("target/esp")?;
185190
sh.create_dir("target/esp/efi/boot")?;
186191
sh.copy_file(loader, "target/esp/efi/boot/bootx64.efi")?;
187192
sh.copy_file(image, "target/esp/efi/boot/hermit-app")?;
@@ -233,6 +238,8 @@ impl Qemu {
233238
"-nodefaults".to_string(),
234239
"-no-user-config".to_string(),
235240
]
241+
} else if self.pci_e {
242+
vec!["-machine".to_owned(), "q35".to_owned()]
236243
} else if arch == Arch::Aarch64 || arch == Arch::Aarch64Be {
237244
vec!["-machine".to_string(), "virt,gic-version=3".to_string()]
238245
} else if arch == Arch::Riscv64 {

0 commit comments

Comments
 (0)