Alioth Code Coverage

config.rs92.52%

1// Copyright 2024 Google LLC
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// https://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
15use std::cmp::max;
16use std::iter::zip;
17use std::mem::size_of;
18use std::sync::Arc;
19
20use alioth_macros::Layout;
21use parking_lot::RwLock;
22use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
23
24use crate::mem::MemRegionCallback;
25use crate::mem::addressable::SlotBackend;
26use crate::mem::emulated::{Action, ChangeLayout, Mmio};
27use crate::pci::cap::PciCapList;
28use crate::pci::{Bdf, PciBar, Result};
29use crate::{assign_bits, bitflags, consts, impl_mmio_for_zerocopy, mask_bits, mem};
30
31pub trait PciConfigArea: Mmio {
32 fn reset(&self) -> Result<()>;
33}
34
35impl Mmio for Box<dyn PciConfigArea> {
36 fn read(&self, offset: u64, size: u8) -> mem::Result<u64> {
37 Mmio::read(self.as_ref(), offset, size)
38 }
39
40 fn write(&self, offset: u64, size: u8, val: u64) -> mem::Result<Action> {
41 Mmio::write(self.as_ref(), offset, size, val)
42 }
43
44 fn size(&self) -> u64 {
45 Mmio::size(self.as_ref())
46 }
47}
48
49impl SlotBackend for Box<dyn PciConfigArea> {
50 fn size(&self) -> u64 {
51 Mmio::size(self.as_ref())
52 }
53}
54
55bitflags! {
56 #[derive(Default, FromBytes, Immutable, KnownLayout, IntoBytes)]
57 pub struct Command(u16) {
58 INTX_DISABLE = 1 << 10;
59 SERR = 1 << 8;
60 PARITY_ERR = 1 << 6;
61 BUS_MASTER = 1 << 2;
62 MEM = 1 << 1;
63 IO = 1 << 0;
64 WRITABLE_BITS = Self::INTX_DISABLE.bits()
65 | Self::SERR.bits()
66 | Self::PARITY_ERR.bits()
67 | Self::BUS_MASTER.bits()
68 | Self::MEM.bits()
69 | Self::IO.bits();
70 }
71}
72
73bitflags! {
74 #[derive(Default, FromBytes, Immutable, KnownLayout, IntoBytes)]
75 pub struct Status(u16) {
76 PARITY_ERR = 1 << 15;
77 SYSTEM_ERR = 1 << 14;
78 RECEIVED_MASTER_ABORT = 1 << 13;
79 RECEIVED_TARGET_ABORT = 1 << 12;
80 SIGNALED_TARGET_ABORT = 1 << 11;
81 MASTER_PARITY_ERR = 1 << 8;
82 CAP = 1 << 4;
83 INTX = 1 << 3;
84 IMMEDIATE_READINESS = 1 << 0;
85 RW1C_BITS = Self::PARITY_ERR.bits()
86 | Self::SYSTEM_ERR.bits()
87 | Self::RECEIVED_MASTER_ABORT.bits()
88 | Self::RECEIVED_TARGET_ABORT.bits()
89 | Self::SIGNALED_TARGET_ABORT.bits()
90 | Self::MASTER_PARITY_ERR.bits();
91 }
92}
93
94consts! {
95 #[derive(Default, FromBytes, Immutable, KnownLayout, IntoBytes)]
96 pub struct HeaderType(u8) {
97 DEVICE = 0;
98 BRIDGE = 1;
99 }
100}
101
102#[derive(Debug, Clone, Default, FromBytes, Immutable, KnownLayout, IntoBytes, Layout)]
103#[repr(C, align(8))]
104pub struct CommonHeader {
105 pub vendor: u16,
106 pub device: u16,
107 pub command: Command,
108 pub status: Status,
109 pub revision: u8,
110 pub prog_if: u8,
111 pub subclass: u8,
112 pub class: u8,
113 pub cache_line_size: u8,
114 pub latency_timer: u8,
115 pub header_type: HeaderType,
116 pub bist: u8,
117}
118
119#[derive(Debug, Clone, Default, FromBytes, Immutable, KnownLayout, IntoBytes, Layout)]
120#[repr(C, align(8))]
121pub struct DeviceHeader {
122 pub common: CommonHeader,
123 pub bars: [u32; 6],
124 pub cardbus_cis_pointer: u32,
125 pub subsystem_vendor: u16,
126 pub subsystem: u16,
127 pub expansion_rom: u32,
128 pub capability_pointer: u8,
129 pub reserved: [u8; 7],
130 pub intx_line: u8,
131 pub intx_pin: u8,
132 pub min_gnt: u8,
133 pub max_lat: u8,
134}
135impl_mmio_for_zerocopy!(DeviceHeader);
136
137pub const fn offset_bar(index: usize) -> usize {108x
138 DeviceHeader::OFFSET_BARS + index * size_of::<u32>()108x
139}108x
140
141pub const OFFSET_BAR0: usize = offset_bar(0);
142pub const OFFSET_BAR5: usize = offset_bar(5);
143
144pub const BAR_PREFETCHABLE: u32 = 0b1000;
145pub const BAR_MEM64: u32 = 0b0100;
146pub const BAR_MEM32: u32 = 0b0000;
147pub const BAR_IO: u32 = 0b01;
148
149pub const BAR_IO_MASK: u32 = 0b11;
150pub const BAR_MEM_MASK: u32 = 0b1111;
151
152#[derive(Debug)]
153pub enum ConfigHeader {
154 Device(DeviceHeader),
155}
156
157impl ConfigHeader {
158 pub fn bars(&self) -> [u32; 6] {143x
159 match self {143x
160 ConfigHeader::Device(header) => header.bars,143x
161 }
162 }143x
163}
164
165#[derive(Debug)]
166struct UpdateCommandCallback {
167 pci_bars: [PciBar; 6],
168 bars: [u32; 6],
169 changed: Command,
170 current: Command,
171}
172
173impl ChangeLayout for UpdateCommandCallback {
174 fn change(&self, memory: &mem::Memory) -> mem::Result<()> {12x
175 for (i, (pci_bar, bar)) in zip(&self.pci_bars, self.bars).enumerate() {72x
176 match pci_bar {72x
177 PciBar::Empty => {}36x
178 PciBar::Mem(region) => {24x
179 if !self.changed.contains(Command::MEM) {24x
180 continue;12x
181 }12x
182 let mut addr = (bar & !BAR_MEM_MASK) as u64;12x
183 if bar & BAR_MEM64 == BAR_MEM64 {12x
184 addr |= (self.bars[i + 1] as u64) << 32;6x
185 }6x
186 if self.current.contains(Command::MEM) {12x
187 memory.add_region(addr, region.clone())?;6x
188 } else {
189 memory.remove_region(addr)?;6x
190 }
191 }
192 PciBar::Io(region) => {12x
193 if !self.changed.contains(Command::IO) {12x
194 continue;6x
195 }6x
196 let port = (bar & !BAR_IO_MASK) as u16;6x
197 if self.current.contains(Command::IO) {6x
198 memory.add_io_region(port, region.clone())?;3x
199 } else {
200 memory.remove_io_region(port)?;3x
201 }
202 }
203 }
204 }
205 Ok(())12x
206 }12x
207}
208
209#[derive(Debug)]
210struct MoveBarCallback {
211 bdf: Bdf,
212 src: u64,
213 dst: u64,
214}
215
216impl ChangeLayout for MoveBarCallback {
217 fn change(&self, memory: &mem::Memory) -> mem::Result<()> {9x
218 log::debug!(9x
219 "{}: moving bar from {:#x} to {:#x}...",
220 self.bdf,
221 self.src,
222 self.dst
223 );
224 if self.src as u32 & BAR_IO == BAR_IO {9x
225 let src_port = self.src & !(BAR_IO_MASK as u64);3x
226 let dst_port = self.dst & !(BAR_IO_MASK as u64);3x
227 let region = memory.remove_io_region(src_port as u16)?;3x
228 memory.add_io_region(dst_port as u16, region)?;3x
229 } else {
230 let src_addr = self.src & !(BAR_MEM_MASK as u64);6x
231 let dst_addr = self.dst & !(BAR_MEM_MASK as u64);6x
232 let region = memory.remove_region(src_addr)?;6x
233 memory.add_region(dst_addr, region)?;6x
234 }
235 Ok(())9x
236 }9x
237}
238
239#[derive(Debug)]
240pub struct HeaderData {
241 header: ConfigHeader,
242 bar_masks: [u32; 6],
243 bdf: Bdf,
244}
245
246impl HeaderData {
247 pub fn set_bar(&mut self, index: usize, val: u32) -> (u32, u32) {24x
248 match &mut self.header {24x
249 ConfigHeader::Device(header) => {24x
250 let mask = self.bar_masks[index];24x
251 let old_val = header.bars[index];24x
252 let masked_val = mask_bits!(old_val, val, mask);24x
253 header.bars[index] = masked_val;24x
254 log::info!(24x
255 "{}: bar {index}: set to {val:#010x}, update: {old_val:#010x} -> {masked_val:#010x}",
256 self.bdf
257 );
258 (old_val, masked_val)24x
259 }
260 }
261 }24x
262
263 pub fn get_bar(&self, index: usize) -> (u32, u32) {18x
264 match &self.header {18x
265 ConfigHeader::Device(header) => (header.bars[index], self.bar_masks[index]),18x
266 }
267 }18x
268
269 pub fn set_command(&mut self, command: Command) {39x
270 match &mut self.header {39x
271 ConfigHeader::Device(header) => header.common.command = command,39x
272 }
273 }39x
274
275 fn write_header(78x
276 &mut self,78x
277 offset: u64,78x
278 size: u8,78x
279 val: u64,78x
280 pci_bars: &[PciBar; 6],78x
281 ) -> Option<Box<dyn ChangeLayout>> {78x
282 let bdf = self.bdf;78x
283 let offset = offset as usize;78x
284 match &mut self.header {78x
285 ConfigHeader::Device(header) => match (offset, size as usize) {78x
286 CommonHeader::LAYOUT_COMMAND => {
287 let val = Command::from_bits_retain(val as u16);24x
288 let old = header.common.command;24x
289 assign_bits!(header.common.command, val, Command::WRITABLE_BITS);24x
290 let current = header.common.command;24x
291 log::trace!("{bdf}: write command: {val:x?}\n {old:x?}\n-> {current:x?}",);24x
292 let changed = old ^ current;24x
293 if !(changed & (Command::MEM | Command::IO)).is_empty() {24x
294 Some(Box::new(UpdateCommandCallback {21x
295 pci_bars: pci_bars.clone(),21x
296 bars: header.bars,21x
297 changed,21x
298 current,21x
299 }))21x
300 } else {
301 None3x
302 }
303 }
304 CommonHeader::LAYOUT_STATUS => {
305 let val = Status::from_bits_retain(val as u16);12x
306 let old = header.common.status;12x
307 header.common.status &= !(val & Status::RW1C_BITS);12x
308 log::trace!(12x
309 "{bdf}: write status: {val:x?}\n {old:x?}\n-> {:x?}",
310 header.common.status,
311 );
312 None12x
313 }
314 (OFFSET_BAR0..=OFFSET_BAR5, 4) => {42x
315 let bar_index = (offset - OFFSET_BAR0) >> 2;42x
316
317 let mask = self.bar_masks[bar_index];42x
318 let old_val = header.bars[bar_index];42x
319 let masked_val = mask_bits!(old_val, val as u32, mask);42x
320 if old_val == masked_val {42x
321 return None;9x
322 }33x
323 log::info!(33x
324 "{bdf}: updating bar {bar_index}: {old_val:#010x} -> {masked_val:#010x}, mask={mask:#010x}",
325 );
326 let command = header.common.command;33x
327 match &pci_bars[bar_index] {33x
328 PciBar::Io(_) if command.contains(Command::IO) => {9x
329 Some(Box::new(MoveBarCallback {6x
330 bdf,6x
331 src: old_val as u64,6x
332 dst: masked_val as u64,6x
333 }))6x
334 }
335 PciBar::Mem(_) if command.contains(Command::MEM) => {18x
336 let hi_32 = if old_val & BAR_MEM64 == BAR_MEM64 {9x
337 (header.bars[bar_index + 1] as u64) << 323x
338 } else {
339 06x
340 };
341 Some(Box::new(MoveBarCallback {9x
342 bdf,9x
343 src: old_val as u64 | hi_32,9x
344 dst: masked_val as u64 | hi_32,9x
345 }))9x
346 }
347 PciBar::Empty
348 if command.contains(Command::MEM)6x
349 && bar_index > 06x
350 && header.bars[bar_index - 1] & BAR_MEM64 == BAR_MEM64 =>6x
351 {
352 let lo_32 = header.bars[bar_index - 1] as u64;6x
353 Some(Box::new(MoveBarCallback {6x
354 bdf,6x
355 src: lo_32 | ((old_val as u64) << 32),6x
356 dst: lo_32 | ((masked_val as u64) << 32),6x
357 }))6x
358 }
359 _ => {
360 header.bars[bar_index] = masked_val;12x
361 log::info!(12x
362 "{bdf}: bar {bar_index}: write {val:#010x}, update: {old_val:#010x} -> {masked_val:#010x}"
363 );
364 None12x
365 }
366 }
367 }
368 DeviceHeader::LAYOUT_EXPANSION_ROM => {
369 log::info!("{bdf}: write {val:#010x} to expansion_rom: ignored");
370 None
371 }
372 _ => {
373 log::warn!(
374 "{bdf}: unknown write: offset = {offset:#x}, size = {size}, val = {val:#x}"
375 );
376 None
377 }
378 },
379 }
380 }78x
381}
382
383#[derive(Debug)]
384struct BarCallback {
385 index: u8,
386 header: Arc<RwLock<HeaderData>>,
387}
388
389impl MemRegionCallback for BarCallback {
390 fn mapped(&self, addr: u64) -> mem::Result<()> {18x
391 let mut header = self.header.write();18x
392 let (old, _) = header.get_bar(self.index as usize);18x
393 header.set_bar(self.index as usize, addr as u32);18x
394 if old & BAR_MEM64 == BAR_MEM64 {18x
395 header.set_bar(self.index as usize + 1, (addr >> 32) as u32);6x
396 }12x
397 Ok(())18x
398 }18x
399}
400
401#[derive(Debug)]
402pub struct EmulatedHeader {
403 pub data: Arc<RwLock<HeaderData>>,
404 pub bars: [PciBar; 6],
405}
406
407impl EmulatedHeader {
408 pub fn new(header: ConfigHeader, bars: [PciBar; 6]) -> Self {143x
409 let mut bar_masks = [0u32; 6];143x
410 let mut mask_hi = 0;143x
411 for ((bar, val), mask) in bars.iter().zip(header.bars()).zip(&mut bar_masks) {858x
412 match bar {858x
413 PciBar::Empty => {603x
414 *mask = mask_hi;603x
415 mask_hi = 0;603x
416 }603x
417 PciBar::Io(region) => {60x
418 debug_assert_eq!(val & BAR_IO_MASK, BAR_IO);60x
419 let size = max(region.size().next_power_of_two(), 1 << 2);60x
420 *mask = !(size - 1) as u32;60x
421 }
422 PciBar::Mem(region) => {195x
423 debug_assert_ne!(val & BAR_IO_MASK, BAR_IO);195x
424 let size = max(region.size().next_power_of_two(), 4 << 10);195x
425 let mask_64 = !(size - 1);195x
426 *mask = mask_64 as u32;195x
427 if BAR_MEM64 & val == BAR_MEM64 {195x
428 mask_hi = (mask_64 >> 32) as u32;135x
429 }135x
430 }
431 }
432 }
433
434 let data = Arc::new(RwLock::new(HeaderData {143x
435 header,143x
436 bar_masks,143x
437 bdf: Bdf(0),143x
438 }));143x
439
440 for (index, bar) in bars.iter().enumerate() {858x
441 let callbacks = match bar {858x
442 PciBar::Empty => continue,603x
443 PciBar::Mem(region) => &region.callbacks,195x
444 PciBar::Io(region) => &region.callbacks,60x
445 };
446 callbacks.lock().push(Box::new(BarCallback {255x
447 index: index as u8,255x
448 header: data.clone(),255x
449 }));255x
450 }
451
452 Self { data, bars }143x
453 }143x
454
455 pub fn set_bdf(&self, bdf: Bdf) {24x
456 self.data.write().bdf = bdf24x
457 }24x
458
459 pub fn set_command(&self, command: Command) {36x
460 let mut header = self.data.write();36x
461 header.set_command(command)36x
462 }36x
463}
464
465impl Mmio for EmulatedHeader {
466 fn size(&self) -> u64 {
467 0x40
468 }
469
470 fn read(&self, offset: u64, size: u8) -> mem::Result<u64> {133x
471 let data = self.data.read();133x
472 match &data.header {133x
473 ConfigHeader::Device(header) => Mmio::read(header, offset, size),133x
474 }
475 }133x
476
477 fn write(&self, offset: u64, size: u8, val: u64) -> mem::Result<Action> {78x
478 let mut data = self.data.write();78x
479 if let Some(callback) = data.write_header(offset, size, val, &self.bars) {78x
480 Ok(Action::ChangeLayout { callback })42x
481 } else {
482 Ok(Action::None)36x
483 }
484 }78x
485}
486
487impl PciConfigArea for EmulatedHeader {
488 fn reset(&self) -> Result<()> {3x
489 let mut header = self.data.write();3x
490 header.set_command(Command::empty());3x
491 Ok(())3x
492 }3x
493}
494
495pub trait PciConfig: Mmio {
496 fn get_header(&self) -> &EmulatedHeader;
497 fn reset(&self) -> Result<()>;
498}
499
500#[derive(Debug)]
501pub struct EmulatedConfig {
502 pub header: EmulatedHeader,
503 pub caps: PciCapList,
504}
505
506impl Mmio for EmulatedConfig {
507 fn read(&self, offset: u64, size: u8) -> mem::Result<u64> {82x
508 if offset < size_of::<DeviceHeader>() as u64 {82x
509 self.header.read(offset, size)64x
510 } else {
511 self.caps.read(offset, size)18x
512 }
513 }82x
514
515 fn write(&self, offset: u64, size: u8, val: u64) -> mem::Result<Action> {12x
516 if offset < size_of::<DeviceHeader>() as u64 {12x
517 self.header.write(offset, size, val)9x
518 } else {
519 self.caps.write(offset, size, val)3x
520 }
521 }12x
522
523 fn size(&self) -> u64 {3x
524 40963x
525 }3x
526}
527
528impl EmulatedConfig {
529 pub fn new_device(83x
530 mut header: DeviceHeader,83x
531 bars: [PciBar; 6],83x
532 caps: PciCapList,83x
533 ) -> EmulatedConfig {83x
534 if !caps.is_empty() {83x
535 header.common.status |= Status::CAP;3x
536 header.capability_pointer = size_of::<DeviceHeader>() as u8;3x
537 }80x
538 EmulatedConfig {83x
539 header: EmulatedHeader::new(ConfigHeader::Device(header), bars),83x
540 caps,83x
541 }83x
542 }83x
543}
544
545impl PciConfig for EmulatedConfig {
546 fn get_header(&self) -> &EmulatedHeader {3x
547 &self.header3x
548 }3x
549
550 fn reset(&self) -> Result<()> {3x
551 self.header.reset()?;3x
552 self.caps.reset()3x
553 }3x
554}
555
556#[cfg(test)]
557#[path = "config_test.rs"]
558mod tests;
559