Probe for VirtIO PCI devices and allocate BARs.
Bug: 237249346
Test: Ran vmbase example
Change-Id: I6ce254a5d5a5740011ddc8efecd4052f40a2a53c
diff --git a/vmbase/example/Android.bp b/vmbase/example/Android.bp
index 505de6b..e0a87db 100644
--- a/vmbase/example/Android.bp
+++ b/vmbase/example/Android.bp
@@ -14,6 +14,7 @@
"libdice_nostd",
"liblibfdt",
"liblog_rust_nostd",
+ "libvirtio_drivers",
"libvmbase",
],
static_libs: [
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index bb64651..df81b2b 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -20,6 +20,7 @@
mod exceptions;
mod layout;
+mod pci;
extern crate alloc;
@@ -27,17 +28,18 @@
bionic_tls, dtb_range, print_addresses, rodata_range, stack_chk_guard, text_range,
writable_region, DEVICE_REGION,
};
-use aarch64_paging::{
- idmap::IdMap,
- paging::{Attributes, MemoryRegion},
-};
+use crate::pci::{check_pci, pci_node, PciMemory32Allocator};
+use aarch64_paging::{idmap::IdMap, paging::Attributes};
use alloc::{vec, vec::Vec};
use buddy_system_allocator::LockedHeap;
use core::ffi::CStr;
use libfdt::Fdt;
-use log::{info, LevelFilter};
+use log::{debug, info, LevelFilter};
use vmbase::{logger, main, println};
+/// PCI MMIO configuration region size.
+const AARCH64_PCI_CFG_SIZE: u64 = 0x1000000;
+
static INITIALISED_DATA: [u32; 4] = [1, 2, 3, 4];
static mut ZEROED_DATA: [u32; 10] = [0; 10];
static mut MUTABLE_DATA: [u32; 4] = [1, 2, 3, 4];
@@ -62,7 +64,27 @@
assert_eq!(arg0, dtb_range().start.0 as u64);
check_data();
check_stack_guard();
- check_fdt();
+
+ info!("Checking FDT...");
+ let fdt = dtb_range();
+ let fdt =
+ unsafe { core::slice::from_raw_parts_mut(fdt.start.0 as *mut u8, fdt.end.0 - fdt.start.0) };
+ let fdt = Fdt::from_mut_slice(fdt).unwrap();
+ info!("FDT passed verification.");
+ check_fdt(fdt);
+
+ let pci_node = pci_node(fdt);
+ // Parse reg property to find CAM.
+ let pci_reg = pci_node.reg().unwrap().unwrap().next().unwrap();
+ debug!("Found PCI CAM at {:#x}-{:#x}", pci_reg.addr, pci_reg.addr + pci_reg.size.unwrap());
+ // Check that the CAM is the size we expect, so we don't later try accessing it beyond its
+ // bounds. If it is a different size then something is very wrong and we shouldn't continue to
+ // access it; maybe there is some new version of PCI we don't know about.
+ assert_eq!(pci_reg.size.unwrap(), AARCH64_PCI_CFG_SIZE);
+ // Parse ranges property to find memory ranges from which to allocate PCI BARs.
+ let mut pci_allocator = PciMemory32Allocator::for_pci_ranges(&pci_node);
+
+ modify_fdt(fdt);
unsafe {
HEAP_ALLOCATOR.lock().init(HEAP.as_mut_ptr() as usize, HEAP.len());
@@ -93,6 +115,21 @@
Attributes::NORMAL | Attributes::NON_GLOBAL | Attributes::EXECUTE_NEVER,
)
.unwrap();
+ idmap
+ .map_range(
+ &dtb_range().into(),
+ Attributes::NORMAL
+ | Attributes::NON_GLOBAL
+ | Attributes::READ_ONLY
+ | Attributes::EXECUTE_NEVER,
+ )
+ .unwrap();
+ idmap
+ .map_range(
+ &pci_allocator.get_region(),
+ Attributes::DEVICE_NGNRE | Attributes::EXECUTE_NEVER,
+ )
+ .unwrap();
info!("Activating IdMap...");
info!("{:?}", idmap);
@@ -101,6 +138,8 @@
check_data();
check_dice();
+
+ check_pci(pci_reg, &mut pci_allocator);
}
fn check_stack_guard() {
@@ -144,13 +183,7 @@
info!("Data looks good");
}
-fn check_fdt() {
- info!("Checking FDT...");
- let fdt = MemoryRegion::from(layout::dtb_range());
- let fdt = unsafe { core::slice::from_raw_parts_mut(fdt.start().0 as *mut u8, fdt.len()) };
-
- let reader = Fdt::from_slice(fdt).unwrap();
- info!("FDT passed verification.");
+fn check_fdt(reader: &Fdt) {
for reg in reader.memory().unwrap().unwrap() {
info!("memory @ {reg:#x?}");
}
@@ -161,8 +194,9 @@
let reg = c.reg().unwrap().unwrap().next().unwrap();
info!("node compatible with '{}' at {reg:?}", compatible.to_str().unwrap());
}
+}
- let writer = Fdt::from_mut_slice(fdt).unwrap();
+fn modify_fdt(writer: &mut Fdt) {
writer.unpack().unwrap();
info!("FDT successfully unpacked.");
diff --git a/vmbase/example/src/pci.rs b/vmbase/example/src/pci.rs
new file mode 100644
index 0000000..e4a4b04
--- /dev/null
+++ b/vmbase/example/src/pci.rs
@@ -0,0 +1,247 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Functions to scan the PCI bus for VirtIO device and allocate BARs.
+
+use aarch64_paging::paging::MemoryRegion;
+use alloc::alloc::{alloc, dealloc, Layout};
+use core::ffi::CStr;
+use libfdt::{Fdt, FdtNode, Reg};
+use log::{debug, info};
+use virtio_drivers::{
+ pci::{
+ bus::{BarInfo, Cam, Command, DeviceFunction, MemoryBarType, PciRoot},
+ virtio_device_type, PciTransport,
+ },
+ DeviceType, Hal, PhysAddr, Transport, VirtAddr, VirtIOBlk, PAGE_SIZE,
+};
+
+/// The standard sector size of a VirtIO block device, in bytes.
+const SECTOR_SIZE_BYTES: u64 = 512;
+
+/// Finds an FDT node with compatible=pci-host-cam-generic.
+pub fn pci_node(fdt: &Fdt) -> FdtNode {
+ fdt.compatible_nodes(CStr::from_bytes_with_nul(b"pci-host-cam-generic\0").unwrap())
+ .unwrap()
+ .next()
+ .unwrap()
+}
+
+pub fn check_pci(reg: Reg<u64>, allocator: &mut PciMemory32Allocator) {
+ let mut pci_root = unsafe { PciRoot::new(reg.addr as *mut u8, Cam::MmioCam) };
+ for (device_function, info) in pci_root.enumerate_bus(0) {
+ let (status, command) = pci_root.get_status_command(device_function);
+ info!("Found {} at {}, status {:?} command {:?}", info, device_function, status, command);
+ if let Some(virtio_type) = virtio_device_type(&info) {
+ info!(" VirtIO {:?}", virtio_type);
+ allocate_bars(&mut pci_root, device_function, allocator);
+ let mut transport =
+ PciTransport::new::<HalImpl>(&mut pci_root, device_function).unwrap();
+ info!(
+ "Detected virtio PCI device with device type {:?}, features {:#018x}",
+ transport.device_type(),
+ transport.read_device_features(),
+ );
+ instantiate_virtio_driver(transport, virtio_type);
+ }
+ }
+}
+
+fn instantiate_virtio_driver(transport: impl Transport, device_type: DeviceType) {
+ if device_type == DeviceType::Block {
+ let blk = VirtIOBlk::<HalImpl, _>::new(transport).expect("failed to create blk driver");
+ info!("Found {} KiB block device.", blk.capacity() * SECTOR_SIZE_BYTES / 1024);
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+struct PciMemoryFlags(u32);
+
+impl PciMemoryFlags {
+ pub fn prefetchable(self) -> bool {
+ self.0 & 0x80000000 != 0
+ }
+
+ pub fn range_type(self) -> PciRangeType {
+ PciRangeType::from((self.0 & 0x3000000) >> 24)
+ }
+}
+
+/// Allocates 32-bit memory addresses for PCI BARs.
+pub struct PciMemory32Allocator {
+ start: u32,
+ end: u32,
+}
+
+impl PciMemory32Allocator {
+ /// Creates a new allocator based on the ranges property of the given PCI node.
+ pub fn for_pci_ranges(pci_node: &FdtNode) -> Self {
+ let mut memory_32_address = 0;
+ let mut memory_32_size = 0;
+ for range in pci_node
+ .ranges::<u128, u64, u64>()
+ .expect("Error getting ranges property from PCI node")
+ .expect("PCI node missing ranges property.")
+ {
+ let flags = PciMemoryFlags((range.addr >> 64) as u32);
+ let prefetchable = flags.prefetchable();
+ let range_type = flags.range_type();
+ let bus_address = range.addr as u64;
+ let cpu_physical = range.parent_addr;
+ let size = range.size;
+ info!(
+ "range: {:?} {}prefetchable bus address: {:#018x} host physical address: {:#018x} size: {:#018x}",
+ range_type,
+ if prefetchable { "" } else { "non-" },
+ bus_address,
+ cpu_physical,
+ size,
+ );
+ if !prefetchable
+ && ((range_type == PciRangeType::Memory32 && size > memory_32_size.into())
+ || (range_type == PciRangeType::Memory64
+ && size > memory_32_size.into()
+ && bus_address + size < u32::MAX.into()))
+ {
+ // Use the 64-bit range for 32-bit memory, if it is low enough.
+ assert_eq!(bus_address, cpu_physical);
+ memory_32_address = u32::try_from(cpu_physical).unwrap();
+ memory_32_size = u32::try_from(size).unwrap();
+ }
+ }
+ if memory_32_size == 0 {
+ panic!("No PCI memory regions found.");
+ }
+
+ Self { start: memory_32_address, end: memory_32_address + memory_32_size }
+ }
+
+ /// Gets a memory region covering the address space from which this allocator will allocate.
+ pub fn get_region(&self) -> MemoryRegion {
+ MemoryRegion::new(self.start as usize, self.end as usize)
+ }
+
+ /// Allocates a 32-bit memory address region for a PCI BAR of the given power-of-2 size.
+ ///
+ /// It will have alignment matching the size. The size must be a power of 2.
+ pub fn allocate_memory_32(&mut self, size: u32) -> Option<u32> {
+ assert!(size.is_power_of_two());
+ let allocated_address = align_up(self.start, size);
+ if allocated_address + size <= self.end {
+ self.start = allocated_address + size;
+ Some(allocated_address)
+ } else {
+ None
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+enum PciRangeType {
+ ConfigurationSpace,
+ IoSpace,
+ Memory32,
+ Memory64,
+}
+
+impl From<u32> for PciRangeType {
+ fn from(value: u32) -> Self {
+ match value {
+ 0 => Self::ConfigurationSpace,
+ 1 => Self::IoSpace,
+ 2 => Self::Memory32,
+ 3 => Self::Memory64,
+ _ => panic!("Tried to convert invalid range type {}", value),
+ }
+ }
+}
+
+/// Allocates appropriately-sized memory regions and assigns them to the device's BARs.
+fn allocate_bars(
+ root: &mut PciRoot,
+ device_function: DeviceFunction,
+ allocator: &mut PciMemory32Allocator,
+) {
+ let mut bar_index = 0;
+ while bar_index < 6 {
+ let info = root.bar_info(device_function, bar_index).unwrap();
+ debug!("BAR {}: {}", bar_index, info);
+ // Ignore I/O bars, as they aren't required for the VirtIO driver.
+ if let BarInfo::Memory { address_type, size, .. } = info {
+ match address_type {
+ _ if size == 0 => {}
+ MemoryBarType::Width32 => {
+ let address = allocator.allocate_memory_32(size).unwrap();
+ debug!("Allocated address {:#010x}", address);
+ root.set_bar_32(device_function, bar_index, address);
+ }
+ MemoryBarType::Width64 => {
+ let address = allocator.allocate_memory_32(size).unwrap();
+ debug!("Allocated address {:#010x}", address);
+ root.set_bar_64(device_function, bar_index, address.into());
+ }
+ _ => panic!("Memory BAR address type {:?} not supported.", address_type),
+ }
+ }
+
+ bar_index += 1;
+ if info.takes_two_entries() {
+ bar_index += 1;
+ }
+ }
+
+ // Enable the device to use its BARs.
+ root.set_command(
+ device_function,
+ Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER,
+ );
+ let (status, command) = root.get_status_command(device_function);
+ debug!("Allocated BARs and enabled device, status {:?} command {:?}", status, command);
+}
+
+const fn align_up(value: u32, alignment: u32) -> u32 {
+ ((value - 1) | (alignment - 1)) + 1
+}
+
+struct HalImpl;
+
+impl Hal for HalImpl {
+ fn dma_alloc(pages: usize) -> PhysAddr {
+ debug!("dma_alloc: pages={}", pages);
+ let layout = Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap();
+ // Safe because the layout has a non-zero size.
+ let vaddr = unsafe { alloc(layout) } as VirtAddr;
+ Self::virt_to_phys(vaddr)
+ }
+
+ fn dma_dealloc(paddr: PhysAddr, pages: usize) -> i32 {
+ debug!("dma_dealloc: paddr={:#x}, pages={}", paddr, pages);
+ let vaddr = Self::phys_to_virt(paddr);
+ let layout = Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap();
+ // Safe because the memory was allocated by `dma_alloc` above using the same allocator, and
+ // the layout is the same as was used then.
+ unsafe {
+ dealloc(vaddr as *mut u8, layout);
+ }
+ 0
+ }
+
+ fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
+ paddr
+ }
+
+ fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
+ vaddr
+ }
+}