os/device/
virtio_hal.rs

1//! HAL (硬件抽象层) 实现,用于适配 virtio-drivers 0.12.0 库
2
3use crate::arch::mm::{paddr_to_vaddr, vaddr_to_paddr};
4use crate::mm::address::{ConvertablePaddr, PageNum, UsizeConvert};
5use crate::mm::frame_allocator::FrameRangeTracker;
6use crate::sync::SpinLock;
7use alloc::collections::btree_map::BTreeMap;
8use core::ptr::NonNull;
9use lazy_static::lazy_static;
10use virtio_drivers::{BufferDirection, Hal, PhysAddr};
11
12// 全局映射表,用于跟踪物理地址到分配的帧范围的映射
13lazy_static! {
14    static ref DMA_ALLOCATIONS: SpinLock<BTreeMap<PhysAddr, FrameRangeTracker>> =
15        SpinLock::new(BTreeMap::new());
16}
17
18/// virtio-drivers 0.12.0 库使用的 HAL 实现
19pub struct VirtIOHal;
20
21unsafe impl Hal for VirtIOHal {
22    /// 分配并清零指定数量的连续物理页用于DMA
23    fn dma_alloc(pages: usize, _direction: BufferDirection) -> (PhysAddr, NonNull<u8>) {
24        // 使用系统的连续物理帧分配器
25        let frame_range = match crate::mm::frame_allocator::alloc_contig_frames(pages) {
26            Some(range) => range,
27            None => {
28                // 返回空指针,让上层代码处理错误
29                return (PhysAddr::from(0u64), NonNull::dangling());
30            }
31        };
32
33        // 获取起始物理页号
34        let start_ppn = frame_range.start_ppn();
35
36        // 计算物理地址
37        let phys_addr = PhysAddr::from(start_ppn.start_addr().as_usize() as u64);
38
39        // 将物理地址转换为虚拟地址
40        let virt_addr = unsafe { start_ppn.start_addr().to_vaddr().as_mut_ptr::<u8>() };
41        let virt_ptr = NonNull::new(virt_addr).unwrap();
42
43        // 清零 DMA 缓冲区(VirtIO HAL trait 要求)
44        unsafe {
45            for page_idx in 0..pages {
46                let page_start = virt_addr.add(page_idx * crate::config::PAGE_SIZE);
47
48                // 逐字节清零整个页面
49                for offset in 0..crate::config::PAGE_SIZE {
50                    core::ptr::write_volatile(page_start.add(offset), 0);
51                }
52            }
53        }
54
55        // 将帧范围存储到全局映射表中
56        DMA_ALLOCATIONS.lock().insert(phys_addr, frame_range);
57
58        (phys_addr, virt_ptr)
59    }
60
61    /// 释放之前分配的DMA内存
62    unsafe fn dma_dealloc(paddr: PhysAddr, _vaddr: NonNull<u8>, _pages: usize) -> i32 {
63        // 从全局映射表中查找并移除对应的帧范围
64        // 注意:必须先释放DMA_ALLOCATIONS锁,再drop FrameRangeTracker
65        // 因为FrameRangeTracker::drop()会获取FRAME_ALLOCATOR锁
66        // 锁顺序要求:FRAME_ALLOCATOR(层级0) 必须在 DMA_ALLOCATIONS(层级7) 之前
67        let frame_range = DMA_ALLOCATIONS.lock().remove(&paddr);
68        // DMA_ALLOCATIONS锁已释放
69
70        // 现在可以安全地drop frame_range,它会获取FRAME_ALLOCATOR锁
71        if frame_range.is_some() {
72            0 // 成功释放
73        } else {
74            -1 // 未找到对应的分配记录
75        }
76    }
77
78    /// 将MMIO物理地址转换为虚拟地址
79    unsafe fn mmio_phys_to_virt(paddr: PhysAddr, _size: usize) -> NonNull<u8> {
80        // 提取物理地址值并使用架构特定的转换函数
81        let phys_addr = paddr as usize;
82        let virt = paddr_to_vaddr(phys_addr);
83
84        // 验证虚拟地址的合法性
85        let ptr = NonNull::new(virt as *mut u8).expect("mmio_phys_to_virt returned null pointer");
86        ptr
87    }
88
89    /// 共享内存区域给设备,并返回设备可访问的物理地址
90    unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> PhysAddr {
91        let vaddr = buffer.as_ptr() as *const u8 as usize;
92        let paddr = unsafe { vaddr_to_paddr(vaddr) };
93
94        let result = PhysAddr::from(paddr as u64);
95        result
96    }
97
98    /// 取消共享内存区域,并在必要时将数据复制回原始缓冲区
99    unsafe fn unshare(_paddr: PhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) {
100        // 简化实现,由于使用直接映射,不需要额外操作
101    }
102}
103
104impl VirtIOHal {
105    /// 创建新的 HAL 实例
106    pub fn new() -> Self {
107        Self
108    }
109}