# 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 次讀取
---
### 測試結果


| 指標 | 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 的簡單接線和多裝置支援是更大的優勢。