Nothing to see here, move along meow
1use crate::cap::cnode;
2use crate::cap::object::ObjectTag;
3use crate::cap::pool::POOL;
4use crate::cap::table::{CapRef, Rights};
5use crate::error::KernelError;
6use crate::mem::phys::BitmapFrameAllocator;
7use crate::pci::{BarInfo, DEVICE_TABLE};
8use crate::proc::PROCESSES;
9use crate::types::Pid;
10use lancer_core::header::KernelObjectHeader;
11use lancer_core::object_layout::{KernelObject, PciDeviceObject};
12use x86_64::structures::paging::{PhysFrame, Size4KiB};
13use x86_64::{PhysAddr, VirtAddr};
14
15use crate::proc::address_space::{map_fb_page_inner, unmap_user_page};
16
17fn find_virtio_net_idx() -> Option<usize> {
18 let table = DEVICE_TABLE.lock();
19 table
20 .iter()
21 .enumerate()
22 .find(|(_, dev)| dev.vendor_id == 0x1AF4 && dev.class_code == 0x02)
23 .map(|(i, _)| i)
24}
25
26fn find_first_memory_bar(idx: usize) -> Option<(usize, u64, u64)> {
27 let table = DEVICE_TABLE.lock();
28 let dev = table.get(idx)?;
29 dev.bars
30 .iter()
31 .enumerate()
32 .find_map(|(bar_idx, bar)| match bar {
33 BarInfo::Memory {
34 phys_base, size, ..
35 } => Some((bar_idx, *phys_base, *size)),
36 _ => None,
37 })
38}
39
40fn bootstrap_test_cnode(pid: Pid, ptable: &mut crate::proc::ProcessManager) {
41 crate::tests::helpers::bootstrap_test_cnode(pid, ptable);
42}
43
44fn setup_process_with_pci_cap(device_table_idx: u8) -> (Pid, u64) {
45 let mut allocator = BitmapFrameAllocator;
46 let mut ptable = PROCESSES.lock();
47 let created = ptable.allocate(&mut allocator).expect("alloc process");
48 ptable.start(created).expect("start");
49 let pid = created.pid();
50 bootstrap_test_cnode(pid, &mut ptable);
51
52 let (cnode_id, cnode_gen, depth, gv, gb) =
53 cnode::cnode_coords(pid, &ptable).expect("cnode coords");
54 let address = 0u64;
55 {
56 let mut pool = POOL.lock_after(&ptable);
57 let header = KernelObjectHeader::new(ObjectTag::PciDevice, 0, 64);
58 let mut pci_obj = PciDeviceObject::init_default(header);
59 pci_obj.device_table_idx = device_table_idx;
60 let (obj_id, obj_gen) =
61 crate::tests::helpers::alloc_typed(&mut pool, ObjectTag::PciDevice, pci_obj)
62 .expect("alloc pci");
63 let cap = CapRef::new(ObjectTag::PciDevice, obj_id, Rights::ALL, obj_gen);
64 cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, address, depth, gv, gb, cap)
65 .expect("insert pci cap");
66 }
67
68 (pid, address)
69}
70
71crate::kernel_test!(
72 fn pci_discovers_devices() {
73 let table = DEVICE_TABLE.lock();
74 assert!(
75 !table.is_empty(),
76 "PCI enumeration found 0 devices (Q35 has built-in devices)"
77 );
78 }
79);
80
81crate::kernel_test!(
82 fn pci_finds_virtio_net() {
83 let table = DEVICE_TABLE.lock();
84 let found = table
85 .iter()
86 .any(|dev| dev.vendor_id == 0x1AF4 && dev.class_code == 0x02);
87 assert!(
88 found,
89 "virtio-net device (vendor=1AF4 class=02) not found in PCI device table"
90 );
91 }
92);
93
94crate::kernel_test!(
95 fn pci_virtio_net_has_io_bar() {
96 let table = DEVICE_TABLE.lock();
97 let dev = table
98 .iter()
99 .find(|d| d.vendor_id == 0x1AF4 && d.class_code == 0x02)
100 .expect("virtio-net device must exist");
101
102 let has_io = dev.bars.iter().any(|bar| matches!(bar, BarInfo::Io { .. }));
103 assert!(
104 has_io,
105 "transitional virtio-net (device_id=0x1000) should have an IO BAR for legacy interface"
106 );
107 }
108);
109
110crate::kernel_test!(
111 fn pci_virtio_net_has_memory_bar() {
112 let idx = find_virtio_net_idx().expect("virtio-net must exist");
113 let (bar_idx, phys_base, size) =
114 find_first_memory_bar(idx).expect("virtio-net should have at least one Memory BAR");
115
116 assert!(
117 phys_base != 0,
118 "BAR{} phys_base should be non-zero",
119 bar_idx
120 );
121 assert!(size > 0, "BAR{} size should be non-zero", bar_idx);
122 assert!(
123 phys_base & 0xF == 0,
124 "BAR{} phys_base should be 16-byte aligned",
125 bar_idx
126 );
127 }
128);
129
130crate::kernel_test!(
131 fn pci_device_cap_stores_table_idx() {
132 let idx = find_virtio_net_idx().expect("virtio-net must exist") as u8;
133 let (pid, address) = setup_process_with_pci_cap(idx);
134
135 let ptable = PROCESSES.lock();
136 let pool = POOL.lock_after(&ptable);
137 let (cnode_id, cnode_gen, depth, gv, gb) =
138 cnode::cnode_coords(pid, &ptable).expect("cnode coords");
139 let cap = cnode::resolve_and_validate(
140 &pool,
141 cnode_id,
142 cnode_gen,
143 address,
144 depth,
145 gv,
146 gb,
147 ObjectTag::PciDevice,
148 Rights::READ,
149 )
150 .expect("validate pci cap");
151
152 let pci_data = pool
153 .read_as::<PciDeviceObject>(cap.phys(), cap.generation())
154 .expect("retrieve pci data");
155
156 assert!(
157 pci_data.device_table_idx == idx,
158 "stored idx {} != expected {}",
159 pci_data.device_table_idx,
160 idx
161 );
162 drop(pool);
163 drop(ptable);
164
165 let mut ptable = PROCESSES.lock();
166 ptable.destroy(pid, &mut BitmapFrameAllocator);
167 }
168);
169
170crate::kernel_test!(
171 fn pci_bar_map_mmio_pages() {
172 let idx = find_virtio_net_idx().expect("virtio-net must exist");
173 let (phys_base, size) = find_first_memory_bar(idx)
174 .map(|(_, base, sz)| (base, sz))
175 .expect("need a Memory BAR");
176 let page_count = size.div_ceil(4096) as usize;
177 let (pid, _address) = setup_process_with_pci_cap(idx as u8);
178
179 let pml4_phys = {
180 let ptable = PROCESSES.lock();
181 ptable.exec(pid).unwrap().pml4_phys
182 };
183 let mut allocator = BitmapFrameAllocator;
184 let base_vaddr = 0x0000_2000_0000_0000u64;
185
186 let mapped = (0..page_count).try_fold(0usize, |count, i| {
187 let phys = PhysAddr::new(phys_base + (i as u64) * 4096);
188 let virt = VirtAddr::new(base_vaddr + (i as u64) * 4096);
189 let frame = PhysFrame::<Size4KiB>::containing_address(phys);
190 match map_fb_page_inner(pml4_phys, virt, frame, &mut allocator) {
191 Ok(()) => Ok(count + 1),
192 Err(e) => Err((e, count)),
193 }
194 });
195
196 assert!(
197 mapped.is_ok(),
198 "mapping Memory BAR pages should succeed, got {:?}",
199 mapped
200 );
201 assert!(
202 mapped.unwrap() == page_count,
203 "should have mapped {} pages",
204 page_count
205 );
206
207 (0..page_count).for_each(|i| {
208 let virt = VirtAddr::new(base_vaddr + (i as u64) * 4096);
209 let _ = unmap_user_page(pml4_phys, virt);
210 });
211
212 let mut ptable = PROCESSES.lock();
213 ptable.destroy(pid, &mut BitmapFrameAllocator);
214 }
215);
216
217crate::kernel_test!(
218 fn pci_bar_unmap_returns_correct_frames() {
219 let idx = find_virtio_net_idx().expect("virtio-net must exist");
220 let (phys_base, size) = find_first_memory_bar(idx)
221 .map(|(_, base, sz)| (base, sz))
222 .expect("need a Memory BAR");
223 let page_count = size.div_ceil(4096) as usize;
224 let (pid, _address) = setup_process_with_pci_cap(idx as u8);
225
226 let pml4_phys = {
227 let ptable = PROCESSES.lock();
228 ptable.exec(pid).unwrap().pml4_phys
229 };
230 let mut allocator = BitmapFrameAllocator;
231 let base_vaddr = 0x0000_3000_0000_0000u64;
232
233 (0..page_count).for_each(|i| {
234 let phys = PhysAddr::new(phys_base + (i as u64) * 4096);
235 let virt = VirtAddr::new(base_vaddr + (i as u64) * 4096);
236 let frame = PhysFrame::<Size4KiB>::containing_address(phys);
237 map_fb_page_inner(pml4_phys, virt, frame, &mut allocator).expect("map should succeed");
238 });
239
240 (0..page_count).for_each(|i| {
241 let expected_phys = PhysAddr::new(phys_base + (i as u64) * 4096);
242 let virt = VirtAddr::new(base_vaddr + (i as u64) * 4096);
243 let returned_frame = unmap_user_page(pml4_phys, virt).expect("unmap should succeed");
244 assert!(
245 returned_frame.start_address() == expected_phys,
246 "unmap page {} returned phys {:#x}, expected {:#x}",
247 i,
248 returned_frame.start_address().as_u64(),
249 expected_phys.as_u64(),
250 );
251 });
252
253 let mut ptable = PROCESSES.lock();
254 ptable.destroy(pid, &mut BitmapFrameAllocator);
255 }
256);
257
258crate::kernel_test!(
259 fn pci_bar_unmap_nonexistent_fails() {
260 let mut allocator = BitmapFrameAllocator;
261 let mut ptable = PROCESSES.lock();
262 let created = ptable.allocate(&mut allocator).expect("alloc process");
263 ptable.start(created).expect("start");
264 let pid = created.pid();
265 let pml4_phys = ptable.exec(pid).unwrap().pml4_phys;
266 drop(ptable);
267
268 let virt = VirtAddr::new(0x0000_5000_0000_0000);
269 let result = unmap_user_page(pml4_phys, virt);
270 assert!(
271 result == Err(KernelError::InvalidAddress),
272 "unmapping a never-mapped page should fail"
273 );
274
275 let mut ptable = PROCESSES.lock();
276 ptable.destroy(pid, &mut BitmapFrameAllocator);
277 }
278);
279
280crate::kernel_test!(
281 fn pci_bar_map_double_map_fails() {
282 let idx = find_virtio_net_idx().expect("virtio-net must exist");
283 let (phys_base, _) = find_first_memory_bar(idx)
284 .map(|(_, base, sz)| (base, sz))
285 .expect("need a Memory BAR");
286 let (pid, _address) = setup_process_with_pci_cap(idx as u8);
287
288 let pml4_phys = {
289 let ptable = PROCESSES.lock();
290 ptable.exec(pid).unwrap().pml4_phys
291 };
292 let mut allocator = BitmapFrameAllocator;
293 let vaddr = VirtAddr::new(0x0000_4000_0000_0000);
294 let frame = PhysFrame::<Size4KiB>::containing_address(PhysAddr::new(phys_base));
295
296 map_fb_page_inner(pml4_phys, vaddr, frame, &mut allocator)
297 .expect("first map should succeed");
298
299 let second = map_fb_page_inner(pml4_phys, vaddr, frame, &mut allocator);
300 assert!(second.is_err(), "mapping the same vaddr twice should fail");
301
302 let _ = unmap_user_page(pml4_phys, vaddr);
303
304 let mut ptable = PROCESSES.lock();
305 ptable.destroy(pid, &mut BitmapFrameAllocator);
306 }
307);
308
309fn make_test_pci_device(
310 ranges: [Option<crate::pci::device::BlockedRange>; 4],
311) -> crate::pci::PciDeviceInfo {
312 crate::pci::PciDeviceInfo {
313 bus: 0,
314 device: 0,
315 function: 0,
316 vendor_id: 0,
317 device_id: 0,
318 class_code: 0,
319 subclass: 0,
320 prog_if: 0,
321 header_type: 0,
322 interrupt_line: 0,
323 interrupt_pin: 0,
324 bars: [BarInfo::None; 6],
325 blocked_config_ranges: ranges,
326 msix_cap: None,
327 }
328}
329
330crate::kernel_test!(
331 fn pci_config_write_rejects_command_register() {
332 let dev = make_test_pci_device([None; 4]);
333 assert!(
334 !crate::syscall::pci::is_config_write_safe(0x04, &dev),
335 "command register (0x04) must be blocked"
336 );
337 }
338);
339
340crate::kernel_test!(
341 fn pci_config_write_rejects_bar_offsets() {
342 let dev = make_test_pci_device([None; 4]);
343 [0x10u16, 0x14, 0x18, 0x1C, 0x20, 0x24]
344 .iter()
345 .for_each(|&offset| {
346 assert!(
347 !crate::syscall::pci::is_config_write_safe(offset, &dev),
348 "BAR offset {:#x} must be blocked",
349 offset
350 );
351 });
352 }
353);
354
355crate::kernel_test!(
356 fn pci_config_write_rejects_expansion_rom() {
357 let dev = make_test_pci_device([None; 4]);
358 assert!(
359 !crate::syscall::pci::is_config_write_safe(0x30, &dev),
360 "expansion ROM (0x30) must be blocked"
361 );
362 }
363);
364
365crate::kernel_test!(
366 fn pci_config_write_rejects_msi_range() {
367 use crate::pci::device::BlockedRange;
368 let dev = make_test_pci_device([
369 Some(BlockedRange {
370 start: 0x40,
371 end: 0x4E,
372 }),
373 None,
374 None,
375 None,
376 ]);
377 assert!(
378 !crate::syscall::pci::is_config_write_safe(0x40, &dev),
379 "MSI start (0x40) must be blocked"
380 );
381 assert!(
382 !crate::syscall::pci::is_config_write_safe(0x48, &dev),
383 "MSI mid (0x48) must be blocked"
384 );
385 assert!(
386 crate::syscall::pci::is_config_write_safe(0x38, &dev),
387 "offset 0x38 before MSI range should be allowed"
388 );
389 assert!(
390 crate::syscall::pci::is_config_write_safe(0x50, &dev),
391 "offset 0x50 after MSI range should be allowed"
392 );
393 }
394);
395
396crate::kernel_test!(
397 fn pci_config_write_allows_safe_offsets() {
398 let dev = make_test_pci_device([None; 4]);
399 assert!(
400 crate::syscall::pci::is_config_write_safe(0x3C, &dev),
401 "interrupt line (0x3C) should be allowed"
402 );
403 }
404);
405
406crate::kernel_test!(
407 fn pci_bar_mapping_tracks_duplicates() {
408 use crate::pci::{BAR_MAPPINGS, BarMappingEntry};
409
410 let pid = Pid::new(1);
411 let entry = BarMappingEntry {
412 pid,
413 device_idx: 5,
414 bar_idx: 2,
415 base_vaddr: 0x2000_0000,
416 };
417
418 let mut mappings = BAR_MAPPINGS.lock();
419 let _ = mappings.push(entry);
420
421 assert!(
422 mappings.iter().any(|e| e.matches(pid, 5, 2)),
423 "should find matching entry"
424 );
425 assert!(
426 !mappings.iter().any(|e| e.matches(pid, 5, 3)),
427 "different bar_idx should not match"
428 );
429 assert!(
430 !mappings.iter().any(|e| e.matches(pid, 6, 2)),
431 "different device_idx should not match"
432 );
433 assert!(
434 !mappings.iter().any(|e| e.matches(Pid::new(2), 5, 2)),
435 "different pid should not match"
436 );
437
438 let pos = mappings
439 .iter()
440 .enumerate()
441 .find(|(_, e)| e.matches(pid, 5, 2))
442 .map(|(i, _)| i);
443 if let Some(i) = pos {
444 mappings.swap_remove(i);
445 }
446 }
447);
448
449crate::kernel_test!(
450 fn pci_device_info_wire_roundtrip() {
451 use lancer_core::pci::*;
452 use zerocopy::IntoBytes;
453
454 let table = DEVICE_TABLE.lock();
455 table.iter().for_each(|dev| {
456 let wire = dev.to_wire();
457 assert!(wire.bus == dev.bus, "bus mismatch");
458 assert!(wire.device == dev.device, "device mismatch");
459 assert!(wire.function == dev.function, "function mismatch");
460 assert!(wire.vendor_id == dev.vendor_id, "vendor_id mismatch");
461 assert!(wire.device_id == dev.device_id, "device_id mismatch");
462 assert!(wire.class_code == dev.class_code, "class_code mismatch");
463 assert!(wire.subclass == dev.subclass, "subclass mismatch");
464 assert!(wire.prog_if == dev.prog_if, "prog_if mismatch");
465 assert!(wire.header_type == dev.header_type, "header_type mismatch");
466 assert!(
467 wire.interrupt_line == dev.interrupt_line,
468 "interrupt_line mismatch"
469 );
470 assert!(
471 wire.interrupt_pin == dev.interrupt_pin,
472 "interrupt_pin mismatch"
473 );
474
475 dev.bars.iter().zip(wire.bars.iter()).enumerate().for_each(
476 |(i, (orig, w))| match orig {
477 BarInfo::None => {
478 assert!(w.tag == BAR_TAG_NONE, "BAR{} tag should be None", i);
479 }
480 BarInfo::Memory {
481 phys_base,
482 size,
483 is_64bit,
484 prefetchable,
485 } => {
486 assert!(w.tag == BAR_TAG_MEMORY, "BAR{} tag should be Memory", i);
487 assert!(w.mem_phys_base == *phys_base, "BAR{} phys_base mismatch", i);
488 assert!(w.mem_size == *size, "BAR{} size mismatch", i);
489 assert!(
490 (w.flags & BAR_FLAG_64BIT != 0) == *is_64bit,
491 "BAR{} is_64bit mismatch",
492 i
493 );
494 assert!(
495 (w.flags & BAR_FLAG_PREFETCHABLE != 0) == *prefetchable,
496 "BAR{} prefetchable mismatch",
497 i
498 );
499 }
500 BarInfo::Io { port_base, size } => {
501 assert!(w.tag == BAR_TAG_IO, "BAR{} tag should be Io", i);
502 assert!(w.io_port_base == *port_base, "BAR{} port_base mismatch", i);
503 assert!(w.io_size == *size, "BAR{} io_size mismatch", i);
504 }
505 },
506 );
507
508 dev.blocked_config_ranges
509 .iter()
510 .zip(wire.blocked_config_ranges.iter())
511 .for_each(|(orig, w)| match orig {
512 Some(r) => {
513 assert!(w.start == r.start, "blocked range start mismatch");
514 assert!(w.end == r.end, "blocked range end mismatch");
515 }
516 None => {
517 assert!(w.start == 0 && w.end == 0, "None range should be zeroed");
518 }
519 });
520
521 let bytes = wire.as_bytes();
522 assert!(bytes.len() == 176, "wire bytes should be 176");
523 });
524 }
525);
526
527crate::kernel_test!(
528 fn pci_all_bar_types_valid() {
529 let table = DEVICE_TABLE.lock();
530 table.iter().for_each(|dev| {
531 dev.bars.iter().enumerate().for_each(|(i, bar)| match bar {
532 BarInfo::Memory {
533 phys_base, size, ..
534 } => {
535 assert!(
536 *size > 0,
537 "device {:04x}:{:04x} BAR{} Memory with size=0",
538 dev.vendor_id,
539 dev.device_id,
540 i
541 );
542 assert!(
543 *phys_base & 0xF == 0,
544 "device {:04x}:{:04x} BAR{} Memory phys_base not aligned",
545 dev.vendor_id,
546 dev.device_id,
547 i
548 );
549 }
550 BarInfo::Io { size, .. } => {
551 assert!(
552 *size > 0,
553 "device {:04x}:{:04x} BAR{} IO with size=0",
554 dev.vendor_id,
555 dev.device_id,
556 i
557 );
558 }
559 BarInfo::None => {}
560 });
561 });
562 }
563);
564
565crate::kernel_test!(
566 fn pci_virtio_net_has_msix_cap() {
567 let table = DEVICE_TABLE.lock();
568 let dev = table
569 .iter()
570 .find(|d| d.vendor_id == 0x1AF4 && d.class_code == 0x02)
571 .expect("virtio-net device must exist");
572
573 let msix = dev
574 .msix_cap
575 .expect("QEMU virtio-net should advertise MSI-X capability");
576
577 assert!(
578 msix.table_size > 0,
579 "MSI-X table_size should be > 0, got {}",
580 msix.table_size
581 );
582 assert!(
583 msix.table_bir < 6,
584 "MSI-X table BIR should be 0-5, got {}",
585 msix.table_bir
586 );
587 assert!(
588 msix.cap_offset >= 0x40,
589 "MSI-X cap_offset should be >= 0x40 (past standard header), got {:#x}",
590 msix.cap_offset
591 );
592 }
593);
594
595crate::kernel_test!(
596 fn pci_msix_table_map_and_configure() {
597 let idx = find_virtio_net_idx().expect("virtio-net must exist") as u8;
598
599 let has_msix = {
600 let table = DEVICE_TABLE.lock();
601 table.get(idx as usize).and_then(|d| d.msix_cap).is_some()
602 };
603 assert!(has_msix, "virtio-net should have MSI-X");
604
605 let hhdm_offset = crate::mem::addr::hhdm_offset();
606 let mut mapper = unsafe { crate::arch::paging::init(hhdm_offset) };
607 let mut alloc = BitmapFrameAllocator;
608
609 let map_result =
610 crate::pci::msix::ensure_table_mapped(idx, &mut mapper, &mut alloc, hhdm_offset);
611 assert!(
612 map_result.is_ok(),
613 "ensure_table_mapped should succeed, got {:?}",
614 map_result,
615 );
616
617 let vector = crate::arch::idt::IrqVector::new(38);
618 let cfg_result = crate::pci::msix::configure_entry(idx, 0, vector, 0);
619 assert!(
620 cfg_result.is_ok(),
621 "configure_entry should succeed, got {:?}",
622 cfg_result,
623 );
624
625 let unmask_result = crate::pci::msix::unmask_entry(idx, 0);
626 assert!(
627 unmask_result.is_ok(),
628 "unmask_entry should succeed, got {:?}",
629 unmask_result,
630 );
631 }
632);
633
634crate::kernel_test!(
635 fn pci_msix_invalid_device_idx_fails() {
636 let result = crate::pci::msix::unmask_entry(255, 0);
637 assert!(
638 result.is_err(),
639 "unmask_entry on non-existent device index should fail"
640 );
641 }
642);