基於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設備提供了一組內存映射的控制寄存器,后跟一個設備特定的配置空間,在形式上是位於一個特定地址上的內存區域。一旦操作系統找到了這個內存區域,就可以獲得與這個設備相關的各種寄存器信息。
- 根據傳入的DTB的物理地址獲取設備樹信息
- 驗證 Magic Number,這是為了保證系統可靠性,驗證這段內存是否存放了設備樹信息。
- 加載dbt數據,遍歷dbt數據
- 在遍歷過程中,一旦發現了一個支持 "virtio,mmio" 的設備(其實就是 QEMU 模擬的各種virtio設備),就進入下一步加載驅動的邏輯。
- 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。
-
安裝網橋工具
sudo apt install bridge-utils sudo apt install uml-utilities
-
創建相應文件(
*/bridge.conf
),否則會出現以下錯誤failed to parse default acl file `/etc/qemu/bridge.conf' qemu-system-riscv64: bridge helper failed
-
echo "allow br0" > /etc/qemu/bridge.conf
否則出現以下錯誤access denied by acl file qemu-system-riscv64: bridge helper failed
-
failed to create tun device: Operation not permitted qemu-system-riscv64: bridge helper failed //解決方案 sudo chmod u+s /usr/lib/qemu/qemu-bridge-helper
-
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