基於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裝置提供了一組記憶體對映的控制暫存器,後跟一個裝置特定的配置空間,在形式上是位於一個特定地址上的記憶體區域。一旦作業系統找到了這個記憶體區域,就可以獲得與這個裝置相關的各種暫存器資訊。
- 根據傳入的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