blob: d971c7b344516f7484cd5e474963c86c923b458d [file] [log] [blame]
// 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 crate::{
entry::RebootReason,
memory::{MemoryRange, MemoryTracker},
};
use core::{
ffi::CStr,
fmt::{self, Display, Formatter},
ops::Range,
};
use libfdt::{AddressRange, Fdt, FdtError, FdtNode};
use log::{debug, error};
use virtio_drivers::pci::{
bus::{self, BarInfo, Cam, Command, DeviceFunction, MemoryBarType, PciRoot},
virtio_device_type,
};
/// PCI MMIO configuration region size.
const PCI_CFG_SIZE: usize = 0x100_0000;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PciError {
FdtErrorPci(FdtError),
FdtNoPci,
FdtErrorReg(FdtError),
FdtMissingReg,
FdtRegEmpty,
FdtRegMissingSize,
CamWrongSize(usize),
FdtErrorRanges(FdtError),
FdtMissingRanges,
RangeAddressMismatch { bus_address: u64, cpu_physical: u64 },
NoSuitableRange,
BarInfoFailed(bus::PciError),
BarAllocationFailed { size: u32, device_function: DeviceFunction },
UnsupportedBarType(MemoryBarType),
}
impl Display for PciError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::FdtErrorPci(e) => write!(f, "Error getting PCI node from FDT: {}", e),
Self::FdtNoPci => write!(f, "Failed to find PCI bus in FDT."),
Self::FdtErrorReg(e) => write!(f, "Error getting reg property from PCI node: {}", e),
Self::FdtMissingReg => write!(f, "PCI node missing reg property."),
Self::FdtRegEmpty => write!(f, "Empty reg property on PCI node."),
Self::FdtRegMissingSize => write!(f, "PCI reg property missing size."),
Self::CamWrongSize(cam_size) => write!(
f,
"FDT says PCI CAM is {} bytes but we expected {}.",
cam_size, PCI_CFG_SIZE
),
Self::FdtErrorRanges(e) => {
write!(f, "Error getting ranges property from PCI node: {}", e)
}
Self::FdtMissingRanges => write!(f, "PCI node missing ranges property."),
Self::RangeAddressMismatch { bus_address, cpu_physical } => {
write!(
f,
"bus address {:#018x} != CPU physical address {:#018x}",
bus_address, cpu_physical
)
}
Self::NoSuitableRange => write!(f, "No suitable PCI memory range found."),
Self::BarInfoFailed(e) => write!(f, "Error getting PCI BAR information: {}", e),
Self::BarAllocationFailed { size, device_function } => write!(
f,
"Failed to allocate memory BAR of size {} for PCI device {}.",
size, device_function
),
Self::UnsupportedBarType(address_type) => {
write!(f, "Memory BAR address type {:?} not supported.", address_type)
}
}
}
}
/// Information about the PCI bus parsed from the device tree.
#[derive(Debug)]
pub struct PciInfo {
/// The MMIO range used by the memory-mapped PCI CAM.
cam_range: MemoryRange,
/// The MMIO range from which 32-bit PCI BARs should be allocated.
bar_range: Range<u32>,
}
impl PciInfo {
/// Finds the PCI node in the FDT, parses its properties and validates it.
pub fn from_fdt(fdt: &Fdt) -> Result<Self, PciError> {
let pci_node = pci_node(fdt)?;
let cam_range = parse_cam_range(&pci_node)?;
let bar_range = parse_ranges(&pci_node)?;
Ok(Self { cam_range, bar_range })
}
/// Maps the CAM and BAR range in the page table and MMIO guard.
pub fn map(&self, memory: &mut MemoryTracker) -> Result<(), RebootReason> {
memory.map_mmio_range(self.cam_range.clone()).map_err(|e| {
error!("Failed to map PCI CAM: {}", e);
RebootReason::InternalError
})?;
memory.map_mmio_range(self.bar_range.start as usize..self.bar_range.end as usize).map_err(
|e| {
error!("Failed to map PCI MMIO range: {}", e);
RebootReason::InternalError
},
)?;
Ok(())
}
/// Returns the `PciRoot` for the memory-mapped CAM found in the FDT. The CAM should be mapped
/// before this is called, by calling [`PciInfo::map`].
///
/// # Safety
///
/// To prevent concurrent access, only one `PciRoot` should exist in the program. Thus this
/// method must only be called once, and there must be no other `PciRoot` constructed using the
/// same CAM.
pub unsafe fn make_pci_root(&self) -> PciRoot {
PciRoot::new(self.cam_range.start as *mut u8, Cam::MmioCam)
}
}
/// Finds an FDT node with compatible=pci-host-cam-generic.
fn pci_node(fdt: &Fdt) -> Result<FdtNode, PciError> {
fdt.compatible_nodes(CStr::from_bytes_with_nul(b"pci-host-cam-generic\0").unwrap())
.map_err(PciError::FdtErrorPci)?
.next()
.ok_or(PciError::FdtNoPci)
}
/// Parses the "reg" property of the given PCI FDT node to find the MMIO CAM range.
fn parse_cam_range(pci_node: &FdtNode) -> Result<MemoryRange, PciError> {
let pci_reg = pci_node
.reg()
.map_err(PciError::FdtErrorReg)?
.ok_or(PciError::FdtMissingReg)?
.next()
.ok_or(PciError::FdtRegEmpty)?;
let cam_addr = pci_reg.addr as usize;
let cam_size = pci_reg.size.ok_or(PciError::FdtRegMissingSize)? as usize;
debug!("Found PCI CAM at {:#x}-{:#x}", cam_addr, cam_addr + cam_size);
// 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.
if cam_size != PCI_CFG_SIZE {
return Err(PciError::CamWrongSize(cam_size));
}
Ok(cam_addr..cam_addr + cam_size)
}
/// Parses the "ranges" property of the given PCI FDT node, and returns the largest suitable range
/// to use for non-prefetchable 32-bit memory BARs.
fn parse_ranges(pci_node: &FdtNode) -> Result<Range<u32>, PciError> {
let mut memory_address = 0;
let mut memory_size = 0;
for AddressRange { addr: (flags, bus_address), parent_addr: cpu_physical, size } in pci_node
.ranges::<(u32, u64), u64, u64>()
.map_err(PciError::FdtErrorRanges)?
.ok_or(PciError::FdtMissingRanges)?
{
let flags = PciMemoryFlags(flags);
let prefetchable = flags.prefetchable();
let range_type = flags.range_type();
debug!(
"range: {:?} {}prefetchable bus address: {:#018x} CPU physical address: {:#018x} size: {:#018x}",
range_type,
if prefetchable { "" } else { "non-" },
bus_address,
cpu_physical,
size,
);
// Use a 64-bit range for 32-bit memory, if it is low enough, because crosvm doesn't
// currently provide any 32-bit ranges.
if !prefetchable
&& matches!(range_type, PciRangeType::Memory32 | PciRangeType::Memory64)
&& size > memory_size.into()
&& bus_address + size < u32::MAX.into()
{
if bus_address != cpu_physical {
return Err(PciError::RangeAddressMismatch { bus_address, cpu_physical });
}
memory_address = u32::try_from(cpu_physical).unwrap();
memory_size = u32::try_from(size).unwrap();
}
}
if memory_size == 0 {
return Err(PciError::NoSuitableRange);
}
Ok(memory_address..memory_address + memory_size)
}
#[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)
}
}
#[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 BARs for all VirtIO PCI devices.
pub fn allocate_all_virtio_bars(
pci_root: &mut PciRoot,
allocator: &mut PciMemory32Allocator,
) -> Result<(), PciError> {
for (device_function, info) in pci_root.enumerate_bus(0) {
let (status, command) = pci_root.get_status_command(device_function);
debug!(
"Found PCI device {} at {}, status {:?} command {:?}",
info, device_function, status, command
);
if let Some(virtio_type) = virtio_device_type(&info) {
debug!(" VirtIO {:?}", virtio_type);
allocate_bars(pci_root, device_function, allocator)?;
}
}
Ok(())
}
/// Allocates 32-bit memory addresses for PCI BARs.
#[derive(Debug)]
pub struct PciMemory32Allocator {
/// The start of the available (not yet allocated) address space for PCI BARs.
start: u32,
/// The end of the available address space.
end: u32,
}
impl PciMemory32Allocator {
pub fn new(pci_info: &PciInfo) -> Self {
Self { start: pci_info.bar_range.start, end: pci_info.bar_range.end }
}
/// 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
}
}
}
/// 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,
) -> Result<(), PciError> {
let mut bar_index = 0;
while bar_index < 6 {
let info = root.bar_info(device_function, bar_index).map_err(PciError::BarInfoFailed)?;
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)
.ok_or(PciError::BarAllocationFailed { size, device_function })?;
debug!("Allocated address {:#010x}", address);
root.set_bar_32(device_function, bar_index, address);
}
_ => {
return Err(PciError::UnsupportedBarType(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);
Ok(())
}
// TODO: Make the alignment functions in the helpers module generic once const_trait_impl is stable.
const fn align_up(value: u32, alignment: u32) -> u32 {
((value - 1) | (alignment - 1)) + 1
}