1. 程式人生 > 其它 >基於Qemu初始化裝置驅動程式

基於Qemu初始化裝置驅動程式

基於Qemu初始化裝置驅動程式

裝置樹

QEMU 可以把它模擬的機器細節資訊全都匯出到dtb格式的二進位制檔案中,並可通過 dtc (Device Tree Compiler)工具轉成可理解的文字檔案。

$ qemu-system-riscv64 -machine virt -machine dumpdtb=riscv64-virt.dtb -bios default
$ dtc -I dtb -O dts -o riscv64-virt.dts riscv64-virt.dtb
$ less riscv64-virt.dts
#就可以看到QEMU RV64 virt計算機的詳細硬體(包括各種外設)細節,包括CPU,記憶體,串列埠,時鐘和各種virtio裝置的資訊。

裝置樹的每個節點上都描述了對應裝置的資訊,如支援的協議是什麼型別等等。而作業系統就是通過這些節點上的資訊來實現對裝置的識別的。具體而言,一個裝置節點上會有幾個標準屬性,這裡簡要介紹我們需要用到的幾個:

  • compatible:該屬性指的是該裝置的程式設計模型,一般格式為 "manufacturer,model",分別指一個出廠標籤和具體模型。如 "virtio,mmio" 指的是這個裝置通過 virtio 協議、MMIO(記憶體對映 I/O)方式來驅動。
  • model:指的是裝置生產商給裝置的型號。
  • reg:當一些很長的資訊或者資料無法用其他標準屬性來定義時,可以用 reg 段來自定義儲存一些資訊。

傳遞裝置樹資訊

作業系統在啟動後需要了解計算機系統中所有接入的裝置,這就要有一個讀取全部已接入裝置資訊的能力,而裝置資訊放在哪裡,又是誰幫我們來做的呢?在 RISC-V 中,這個一般是由 bootloader,即 OpenSBI or RustSBI 韌體完成的。它來完成對於包括實體記憶體在內的各外設的探測,將探測結果以 裝置樹二進位制物件(DTB,Device Tree Blob) 的格式儲存在實體記憶體中的某個地方。然後bootloader會啟動作業系統,即把放置DTB的實體地址將放在 a1 暫存器中,而將會把 HART ID (HART,Hardware Thread,硬體執行緒,可以理解為執行的 CPU 核

)放在 a0 暫存器上,然後跳轉到作業系統的入口地址處繼續執行。

extern "C" fn main(_hartid: usize, device_tree_paddr: usize) {
   ...
   init_dt(device_tree_paddr);
   ...
}

解析裝置資訊

Qemu中Virtio Over MMIO方式沒有基於匯流排的裝置探測機制。 所以作業系統採用Device Tree的方式來探測各種基於MMIO方式的virtio裝置,從而作業系統能知道與裝置相關的暫存器和所用的中斷。基於MMIO方式的virtio裝置提供了一組記憶體對映的控制暫存器,後跟一個裝置特定的配置空間,在形式上是位於一個特定地址上的記憶體區域。一旦作業系統找到了這個記憶體區域,就可以獲得與這個裝置相關的各種暫存器資訊。

  1. 根據傳入的DTB的實體地址獲取裝置樹資訊
  2. 驗證 Magic Number,這是為了保證系統可靠性,驗證這段記憶體是否存放了裝置樹資訊。
  3. 載入dbt資料,遍歷dbt資料
  4. 在遍歷過程中,一旦發現了一個支援 "virtio,mmio" 的裝置(其實就是 QEMU 模擬的各種virtio裝置),就進入下一步載入驅動的邏輯。
  5. virtio驅動程式的執行過程可參考:https://gitee.com/rcore-os/rCore-Tutorial-Book-v3/blob/main/source/chapter9/2device-driver-2.rst
fn init_dt(dtb: usize) {
    info!("device tree @ {:#x}", dtb);
    #[repr(C)]
    struct DtbHeader {
        be_magic: u32,
        be_size: u32,
    }
    let header = unsafe { &*(dtb as *const DtbHeader) };
    let magic = u32::from_be(header.be_magic);
    const DEVICE_TREE_MAGIC: u32 = 0xd00dfeed;
    assert_eq!(magic, DEVICE_TREE_MAGIC);
    let size = u32::from_be(header.be_size);
    let dtb_data = unsafe { core::slice::from_raw_parts(dtb as *const u8, size as usize) };
    let dt = DeviceTree::load(dtb_data).expect("failed to parse device tree");
    walk_dt_node(&dt.root); //遍歷資料
}

//發現了一個支援 "virtio,mmio" 的裝置,就進入下一步載入驅動的邏輯
fn walk_dt_node(dt: &Node) {
    if let Ok(compatible) = dt.prop_str("compatible") {
        if compatible == "virtio,mmio" {
            virtio_probe(dt);
        }
    }
    for child in dt.children.iter() {
        walk_dt_node(child);
    }
}
//對不同型別裝置的處理
fn virtio_probe(node: &Node) {
    if let Some(reg) = node.prop_raw("reg") {
        let paddr = reg.as_slice().read_be_u64(0).unwrap();
        let size = reg.as_slice().read_be_u64(8).unwrap();
        let vaddr = paddr;
        info!("walk dt addr={:#x}, size={:#x}", paddr, size);
        let header = unsafe { &mut *(vaddr as *mut VirtIOHeader) };
        info!(
            "Detected virtio device with vendor id {:#X}",
            header.vendor_id()
        );
        info!("Device tree node {:?}", node);
        match header.device_type() {
            DeviceType::Block => virtio_blk(header),
            DeviceType::GPU => virtio_gpu(header),
            DeviceType::Input => virtio_input(header),
            DeviceType::Network => virtio_net(header),
            t => warn!("Unrecognized virtio device: {:?}",t),
        }
    }
}

Qemu啟動引數設定

qemu-system-riscv64 \
		-machine virt \
		-serial mon:stdio \
		-bios default \
		-kernel $(kernel) \
		-drive file=$(img),if=none,format=raw,id=x0 \
		-device virtio-blk-device,drive=x0 \
		-device virtio-gpu-device \
		-device virtio-mouse-device \
		-device virtio-net-device,netdev=net0\
		-netdev tap,id=net0,"helper=/usr/lib/qemu/qemu-bridge-helper"

當新增網路裝置時(-netdev),網路配置常見的有兩種模式,一種是user模式,一種是tap模式。

user模式的客戶機可以連通宿主機及外部網路。使用者模式網路完全由QEMU模擬實現整個TCP/IP協議棧,並且使用這個協議棧提供一個虛擬的NAT網路,負責將qemu所模擬的系統網路請求轉發到外部網絡卡上,從而實現網路通訊。

tap模式表明在主機上增加一塊虛擬網路裝置,然後就可以象真實網絡卡一樣配置它這種方式要比user mode複雜一些,但是設定好後 虛擬機器<-->網際網路虛擬機器<-->主機通訊都很容易。預設的網路配置指令碼是 /etc/qemu-ifup,預設的網路解除配置指令碼是 /etc/qemu-ifdown。 使用 script=no 或 downscript=no 禁用指令碼執行。也可使用網路助手配置 TAP 介面並將其附加到網橋。 預設的網路助手可執行檔案是 /path/to/qemu-bridge-helper,預設的網橋裝置是 br0。

  1. 安裝網橋工具

    sudo apt install bridge-utils
    sudo apt install uml-utilities
    
  2. 建立相應檔案(*/bridge.conf),否則會出現以下錯誤

    failed to parse default acl file `/etc/qemu/bridge.conf'
    qemu-system-riscv64: bridge helper failed
    
  3. echo "allow br0" > /etc/qemu/bridge.conf否則出現以下錯誤

    access denied by acl file
    qemu-system-riscv64: bridge helper failed
    
  4. failed to create tun device: Operation not permitted
    qemu-system-riscv64: bridge helper failed
    
    //解決方案
    sudo chmod u+s /usr/lib/qemu/qemu-bridge-helper
    
  5. failed to get mtu of bridge `br0': No such device
    qemu-system-riscv64: bridge helper failed
    
    //解決方案
    sudo brctl addbr br0
    sudo ip link set br0 up
    

參考連結:https://gitee.com/rcore-os/rCore-Tutorial-Book-v3/blob/main/source/chapter9/2device-driver-1.rst