# Virtual Driver Testing Framework [Github repo](https://github.com/t0matoOtk/VDTF) ## Keywords `QEMU` `Device Driver` `DTS / DTB` `Ftrace` `GDB` `dmesg` ## Abstract 本專題目標是建立一個可量測 Linux 裝置驅動效能的實驗平台,用來比較不同 I/O 設計對系統表現的影響, 最後Deploy到硬體上,減少開發測試成本。 系統使用 QEMU 模擬硬體環境,並在 Linux kernel 中開發 I2C / SPI driver,搭配 Device Tree 完成裝置綁定。 最終達成可重現的效能量測結果,可比較 latency、throughput 與 CPU 使用率,並分析不同 bus 與 driver 設計帶來的效能差異。 --- ## Overview * QEMU Resberry Linux 編譯 * QEMU 虛擬硬體製作 * Linux kernel driver 開發 * Device Tree 綁定 * I2C / SPI bus framework * 系統效能量測與分析 ## Detailed Implementation ### 1:基礎環境 系統開機時的流程,大致可分成三步驟。 ``` 1. Bootloader 負責把 Kernel 搬進記憶體。 2. Kernel 負責初始化硬體(點亮螢幕、偵測記憶體)。 3. RootFS Kernel 完成硬體初始化後,會去掛載硬碟上的 RootFS。 若掛載成功,Kernel 就會去 RootFS 裡面尋找一個叫 init 的程式並執行它。 ``` 我使用 Qemu 來模擬硬體,在虛擬的環境中,我們沒有模擬bootloader,而是直接把kernel載入虛擬記憶體裡面執行。 * 建立 QEMU ARM Linux 環境 ```=bash sudo apt install qemu-system qemu-utils ``` * [編譯 kernel](/Sevf9DLiSJWZiFIoR82_JQ) * [準備 rootfs](/sdG_FDsyQrigvgKFVbtguA) * 建立執行檔 `start_qemu.sh` 並給予權限 ```bash cat << 'EOF' > start_qemu.sh #!/bin/bash qemu-system-aarch64 \ -M raspi3b \ -cpu cortex-a53 \ -m 1G \ -kernel ./Image \ -dtb ./bcm2711-rpi-3-b.dtb \ -drive file=rootfs.img,format=raw,if=sd \ -append "rw console=tty1 root=/dev/mmcblk0 rootwait devtmpfs.mount=1" \ -device usb-kbd EOF ``` ```bash chmod +x start_qemu.sh ``` 完成後整理一下檔案 ``` ~/.../VDTF/ ├── busybox/ # BusyBox (編譯區) ├── linux/ # Linux Kernel (編譯區) └── deploy/ # QEMU 要跑的地方 ├── Image # Kernel: 從 linux/arch/arm64/boot/ 複製過來 ├── bcm2710...dtb # Device Tree Blob: 從 linux/arch/arm64/boot/dts/broadcom/ 複製過來 ├── rootfs.img # Root Filesystem: 封裝好的 100MB 映像檔 └── start_qemu.sh # 方便執行的腳本 ``` 最後執行 start_qemu.sh 看看是否能正常開機, 如果能正常開機你應該要能看到一個 shell,並能在視窗內輸入指令 --- ### 2:QEMU Fake Device(Memory Mapped I/O) **為什麼需要 Fake Device?** 如果你今天要在 Linux 寫一個驅動程式來控制一個 LED 燈或感測器, 你的程式碼最後一定會去存取某個物理位址, 但在 QEMU 的環境裡, 我們並沒有真實的硬體負責接收資料, 因此我們會需要定義一個虛擬硬體來模擬硬體行為。 硬體是利用 Memory Mapped I/O 的方式來接收訊息。 所以 Fake Device 要做的工作就是,在 QEMU 啟動時跟系統說, 從 0x3F900000 開始的 1KB 空間歸我管。以後 CPU 只要存取這段位址,請通報我 --- ### 3.硬體裝置模擬 **如何定義 Fake Device?** 要所需的工作可以分成三項 - 虛擬硬體行為定義: 定義當 Driver 讀或寫這區塊時,QEMU 應該執行哪段 C 程式碼。 - Device Tree宣告:告訴 linux Driver 在虛擬記憶體上的哪裡,有你的設備。 - Device Driver撰寫: Device Driver 是作業系統核心中的一個專屬模組,它的任務是將軟體的抽象請求(如寫入檔案)轉譯為硬體的物理操作 (如改變電壓訊號)。 我們這裡使用 BME280 設備作為虛擬設備的實做對象,參考 datasheet 進行實做, 因為它同時支援 I2C 和 SPI 通訊協定,方便我們進行後續的分析。 [BME280 DataSheet](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf) [如何閱讀DataSheet?](/UIuxp0bbR8yCtSYivvPpwg) [ctf-wiki 編寫Qemu設備](https://ctf-wiki.org/zh-tw/pwn/virtualization/qemu/environment/build-qemu-dev/) #### (0) GPIO [如何在QEMU定義MMIO](/6Cs4Ve2eRdCCqrSdG3gixQ) #### (1) I2C Device [如何在QEMU定義I2C Device](/caRl5CUiQW2ua_tfz0DMbw) [如何撰寫I2C kernel driver](/E9E3taMyTFGmE_QwCtlDLg) * QEMU 實作 I2C slave device * Linux I2C driver(i2c_transfer) 驗證:可完成 read/write transaction #### (2) SPI Device * QEMU SPI peripheral * SPI driver(spi_sync) 驗證:SPI/I2C 可並存操作 --- ## Experiments ### 測試環境 - 平台:QEMU raspi3b 模擬環境 - 裝置:BME280 溫濕度氣壓感測器 - I2C:bus 1,位址 0x76,clock 100kHz - SPI:bus 0,CS0,最大頻率 10MHz - 測試次數:10000 次讀取 --- ### 測試結果 ![image](https://hackmd.io/_uploads/r1fQLk-h-x.png) ![image](https://hackmd.io/_uploads/B1w381b3Wl.png) | 指標 | I2C | SPI | 倍數 | |---|---|---|---| | 平均延遲 | 269 us | 82 us | 3.3x | | 最小延遲 | 93 us | 48 us | 1.9x | | p50 | 203 us | 55 us | 3.7x | | p90 | 522 us | 146 us | 3.6x | | p95 | 640 us | 185 us | 3.5x | | p99 | 897 us | 288 us | 3.1x | | p99.9 | 1197 us | 465 us | 2.6x | | 吞吐量 | 3717 reads/sec | 12195 reads/sec | 3.3x | | CPU 使用率 | 8.8% | 28.2% | 3.2x | --- ## 實驗一: 比較SPI 和 I2C 效能 #### 延遲 **SPI 整體比 I2C 快約 3.3 倍** 1. **I2C 有 ACK 機制**:每個 byte 傳輸完 master 需要等待 slave 回應 ACK,造成額外等待時間。 2. **I2C 半雙工**:同一條線上傳送和接收需要切換方向,SPI 是全雙工可以同時收發。 3. **I2C 有位址階段**:每次傳輸需要先送 slave 位址,SPI 靠 CS 腳位直接選裝置。 #### CPU 使用率 SPI 的 CPU 使用率(28.2%)是 I2C(8.8%)的 3.2 倍,所以SPI 佔用 CPU 比較多嗎? 我們計算 **每單位讀取的 CPU 成本** ``` I2C: 8.8% / 3717 reads/sec ≈ 0.00237% per read SPI: 28.2% / 12195 reads/sec ≈ 0.00231% per read ``` 兩者每次讀取消耗的 CPU 成本幾乎相同,SPI 的高 CPU 使用率來自於它更高的吞吐量,而不是每次傳輸本身更耗 CPU。 ### 實驗2 比較 自製driver 和 現有driver 的效能 | 指標 | I2C kernel | I2C simple | SPI kernel | SPI simple | |---|---|---|---|---| | avg | 264 us | 761 us | 90 us | 140 us | | p50 | 209 us | 731 us | 67 us | 96 us | | p99 | 832 us | 1784 us | 334 us | 421 us | | throughput | 3787 /sec | 1314 /sec | 11111 /sec | 7142 /sec | | cpu_usage | 10.7% | 5.8% | 28.1% | 28.6% | **1. Kernel driver 比 simple driver 快很多** - I2C:kernel 比 simple 快 **2.9x** - SPI:kernel 比 simple 快 **1.6x** **2. 為什麼 kernel driver 比較快?** - kernel driver 用 **regmap** 框架,有快取機制 - IIO framework 對讀取有優化 - 我們的 simple driver 每次都直接做 I2C/SPI 傳輸,沒有任何快取 **3. CPU 使用率** - I2C simple 的 cpu_usage(5.8%)比 kernel(10.7%)低 - 原因是 simple driver 速度慢,單位時間內完成的工作少 --- ### QEMU 模擬的限制 1. **時序不精確**:QEMU 的 I2C/SPI 模擬跳過了實體層的時序,實際硬體上的差異可能更大,因為 I2C 100kHz 對比 SPI 10MHz 的時脈差距會更明顯。 2. **排程干擾**:QEMU 跑在 host OS 上,host 的排程器偶爾會搶佔 CPU,造成 tail latency 偏高。這解釋了 p99.9 的數值比 p99 高出很多的現象。 3. **CPU 計數方式**:我們用 `/proc/stat` 測量整個系統的 CPU 使用率,不是只有測試程式本身,所以數值包含了 kernel driver 和 QEMU 模擬的開銷。 4. **在真實硬體上預期**:SPI 的優勢會更明顯,因為實體層的時脈差距(100kHz vs 10MHz)會直接反映在傳輸速度上,而不會被 QEMU 的模擬平滑掉。 --- ## 結論 | | I2C | SPI | |---|---|---| | 適合場景 | 多裝置、低速、線材簡單 | 高速資料傳輸、單一裝置 | | 優點 | 只需兩條線、支援多裝置 | 速度快、全雙工、無 ACK overhead | | 缺點 | 速度慢、有 ACK overhead | 需要 CS 腳位、線材較多 | **在感測器讀取的場景下,SPI 的吞吐量約為 I2C 的 3.3 倍,延遲約為 I2C 的 30%。** 如果應用需要高頻率讀取感測器資料(例如 IMU、高速 ADC),SPI 是明顯更好的選擇。 如果只需要低頻率讀取(例如環境監控),I2C 的簡單接線和多裝置支援是更大的優勢。