blob: 3e6915afe1ca6854f25c24b9733e2ac2f0313fce [file] [log] [blame]
Andrew Walbran19690632022-12-07 16:41:30 +00001// Copyright 2022, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Functions to scan the PCI bus for VirtIO device and allocate BARs.
16
Andrew Walbran0d8b54d2022-12-08 16:32:33 +000017use crate::{
18 entry::RebootReason,
19 memory::{MemoryRange, MemoryTracker},
20};
21use core::{ffi::CStr, ops::Range};
22use libfdt::{AddressRange, Fdt, FdtNode};
Andrew Walbran19690632022-12-07 16:41:30 +000023use log::{debug, error};
24
25/// PCI MMIO configuration region size.
26const PCI_CFG_SIZE: usize = 0x100_0000;
27
Andrew Walbran0d8b54d2022-12-08 16:32:33 +000028/// Information about the PCI bus parsed from the device tree.
29#[derive(Debug)]
30pub struct PciInfo {
31 /// The MMIO range used by the memory-mapped PCI CAM.
32 cam_range: MemoryRange,
33 /// The MMIO range from which 32-bit PCI BARs should be allocated.
34 bar_range: Range<u32>,
35}
36
37impl PciInfo {
38 /// Finds the PCI node in the FDT, parses its properties and validates it.
39 pub fn from_fdt(fdt: &Fdt) -> Result<Self, RebootReason> {
40 let pci_node = pci_node(fdt)?;
41
42 let cam_range = parse_cam_range(&pci_node)?;
43 let bar_range = parse_ranges(&pci_node)?;
44
45 Ok(Self { cam_range, bar_range })
46 }
47
48 /// Maps the CAM and BAR range in the page table and MMIO guard.
49 pub fn map(&self, memory: &mut MemoryTracker) -> Result<(), RebootReason> {
50 memory.map_mmio_range(self.cam_range.clone()).map_err(|e| {
51 error!("Failed to map PCI CAM: {}", e);
52 RebootReason::InternalError
53 })?;
54
55 memory.map_mmio_range(self.bar_range.start as usize..self.bar_range.end as usize).map_err(
56 |e| {
57 error!("Failed to map PCI MMIO range: {}", e);
58 RebootReason::InternalError
59 },
60 )?;
61
62 Ok(())
63 }
64}
65
Andrew Walbran19690632022-12-07 16:41:30 +000066/// Finds an FDT node with compatible=pci-host-cam-generic.
Andrew Walbran0d8b54d2022-12-08 16:32:33 +000067fn pci_node(fdt: &Fdt) -> Result<FdtNode, RebootReason> {
Andrew Walbran19690632022-12-07 16:41:30 +000068 fdt.compatible_nodes(CStr::from_bytes_with_nul(b"pci-host-cam-generic\0").unwrap())
69 .map_err(|e| {
70 error!("Failed to find PCI bus in FDT: {}", e);
71 RebootReason::InvalidFdt
72 })?
73 .next()
74 .ok_or(RebootReason::InvalidFdt)
75}
76
Andrew Walbran0d8b54d2022-12-08 16:32:33 +000077/// Parses the "reg" property of the given PCI FDT node to find the MMIO CAM range.
78fn parse_cam_range(pci_node: &FdtNode) -> Result<MemoryRange, RebootReason> {
Andrew Walbran19690632022-12-07 16:41:30 +000079 let pci_reg = pci_node
80 .reg()
81 .map_err(|e| {
82 error!("Error getting reg property from PCI node: {}", e);
83 RebootReason::InvalidFdt
84 })?
85 .ok_or_else(|| {
86 error!("PCI node missing reg property.");
87 RebootReason::InvalidFdt
88 })?
89 .next()
90 .ok_or_else(|| {
91 error!("Empty reg property on PCI node.");
92 RebootReason::InvalidFdt
93 })?;
94 let cam_addr = pci_reg.addr as usize;
95 let cam_size = pci_reg.size.ok_or_else(|| {
96 error!("PCI reg property missing size.");
97 RebootReason::InvalidFdt
98 })? as usize;
99 debug!("Found PCI CAM at {:#x}-{:#x}", cam_addr, cam_addr + cam_size);
100 // Check that the CAM is the size we expect, so we don't later try accessing it beyond its
101 // bounds. If it is a different size then something is very wrong and we shouldn't continue to
102 // access it; maybe there is some new version of PCI we don't know about.
103 if cam_size != PCI_CFG_SIZE {
104 error!("FDT says PCI CAM is {} bytes but we expected {}.", cam_size, PCI_CFG_SIZE);
105 return Err(RebootReason::InvalidFdt);
106 }
107
Andrew Walbran0d8b54d2022-12-08 16:32:33 +0000108 Ok(cam_addr..cam_addr + cam_size)
109}
Andrew Walbran19690632022-12-07 16:41:30 +0000110
Andrew Walbran0d8b54d2022-12-08 16:32:33 +0000111/// Parses the "ranges" property of the given PCI FDT node, and returns the largest suitable range
112/// to use for non-prefetchable 32-bit memory BARs.
113fn parse_ranges(pci_node: &FdtNode) -> Result<Range<u32>, RebootReason> {
114 let mut memory_address = 0;
115 let mut memory_size = 0;
116
117 for AddressRange { addr: (flags, bus_address), parent_addr: cpu_physical, size } in pci_node
118 .ranges::<(u32, u64), u64, u64>()
119 .map_err(|e| {
120 error!("Error getting ranges property from PCI node: {}", e);
121 RebootReason::InvalidFdt
122 })?
123 .ok_or_else(|| {
124 error!("PCI node missing ranges property.");
125 RebootReason::InvalidFdt
126 })?
127 {
128 let flags = PciMemoryFlags(flags);
129 let prefetchable = flags.prefetchable();
130 let range_type = flags.range_type();
131 debug!(
132 "range: {:?} {}prefetchable bus address: {:#018x} CPU physical address: {:#018x} size: {:#018x}",
133 range_type,
134 if prefetchable { "" } else { "non-" },
135 bus_address,
136 cpu_physical,
137 size,
138 );
139
140 // Use a 64-bit range for 32-bit memory, if it is low enough, because crosvm doesn't
141 // currently provide any 32-bit ranges.
142 if !prefetchable
143 && matches!(range_type, PciRangeType::Memory32 | PciRangeType::Memory64)
144 && size > memory_size.into()
145 && bus_address + size < u32::MAX.into()
146 {
147 if bus_address != cpu_physical {
148 error!(
149 "bus address {:#018x} != CPU physical address {:#018x}",
150 bus_address, cpu_physical
151 );
152 return Err(RebootReason::InvalidFdt);
153 }
154 memory_address = u32::try_from(cpu_physical).unwrap();
155 memory_size = u32::try_from(size).unwrap();
156 }
157 }
158
159 if memory_size == 0 {
160 error!("No suitable PCI memory range found.");
161 return Err(RebootReason::InvalidFdt);
162 }
163
164 Ok(memory_address..memory_address + memory_size)
165}
166
167#[derive(Copy, Clone, Debug, Eq, PartialEq)]
168struct PciMemoryFlags(u32);
169
170impl PciMemoryFlags {
171 pub fn prefetchable(self) -> bool {
172 self.0 & 0x80000000 != 0
173 }
174
175 pub fn range_type(self) -> PciRangeType {
176 PciRangeType::from((self.0 & 0x3000000) >> 24)
177 }
178}
179
180#[derive(Copy, Clone, Debug, Eq, PartialEq)]
181enum PciRangeType {
182 ConfigurationSpace,
183 IoSpace,
184 Memory32,
185 Memory64,
186}
187
188impl From<u32> for PciRangeType {
189 fn from(value: u32) -> Self {
190 match value {
191 0 => Self::ConfigurationSpace,
192 1 => Self::IoSpace,
193 2 => Self::Memory32,
194 3 => Self::Memory64,
195 _ => panic!("Tried to convert invalid range type {}", value),
196 }
197 }
Andrew Walbran19690632022-12-07 16:41:30 +0000198}