QEMU虛擬板使用
- 編譯好驅動程序和應用程序後,複製到板子掛載的文件系統。如:
cp hello_drv.ko hello_drv_test /home/clay/linux/qemu/NFS
- 更新內核和設備樹
- 編譯成功後,可在
./arch/arm/boot/zImage
目錄下得到內核文件,在./arch/arm/boot/dts/100ask_imx6ull_qemu.dtb
得到設備樹文件 - 用上述2個文件去替換qemu中的zImage和100ask_imx6ull_qemu.dtb即可達到更新內核和設備樹的目的。
- qemu的內核和設備樹文件在其安裝目錄下的
./imx6ull-system-image
文件夾下。
- qemu的內核和設備樹文件在其安裝目錄下的
- 編譯成功後,可在
- 啓動QEMU(qemu終端操作)
./qemu-imx6ull-gui.sh
mount -t nfs -o nolock,vers=3 10.0.2.2:/home/clay/linux/qemu/NFS /mnt
cd /mnt && ls /mnt
insmod hello_drv.ko
- 加載驅動模塊lsmod
- 列出系統所有的驅動模塊cat /proc/devices
- 查看分配到的主設備號
- 關於qemu小燈狀態
- 低電平有效
- 打開-紅色-寫1
- 關閉-白色-寫0
GPIO電器屬性值
- GPIO爲輸入時,配置爲
0xF080
- GPIO爲輸出時,配置爲
0x10B0
查看終端是否註冊成功
cat /proc/interrupts
設備樹相關
查看設備樹節點:
ls /sys/firmware/devicetree/base
- 根節點對應base目錄, 每一個節點對應一個目錄, 每一個屬性對應一個文件
ls /proc/device-tree
- 鏈接文件, 指向 /sys/firmware/devicetree/base
platform平臺(name字段)
- 設備文件在:
/sys/bus/platform/devices/
- 驅動文件在:
/sys/bus/platform/drivers/
關於platform_device
- 系統中所有的platform_device, 有來自設備樹的, 也有來有.c文件中註冊的
ls /sys/devices/platform
- 對於來自設備樹的
platform_device
,可以進入/sys/devices/platform/<設備名>/of_node
中查看它的設備樹屬性- 例如進入/sys/devices/platform/led/後,若該目錄下有
of_node
節點,就表明該platform_device
來自設備樹 - 正常來說platform_device都在目錄
/sys/devices/platform
下,但是qemu的platform_device竟然沒有在,而是在目錄/sys/devices/soc0
!(這一點要特別注意!) - 進入myled節點裏,可以看到platform_divice的各種屬性,需要注意的是其中的
driver
目錄,這個是隻有在platform_device和platform_driver綁定後纔會生成的! - 另外通過
ls -l driver
,可以看到drivver也是個軟鏈接,qemu中指向/sys/bus/platform/drivers/100ask_led
目錄! - 關於設備樹節點與驅動配對查看方法,參考這篇
- 關於platform_device爲什麼叫100ask_led,是由驅動程序決定的,如下圖所示。
- 例如進入/sys/devices/platform/led/後,若該目錄下有
GCC
- 預處理(
.c/.cpp
)、編譯()、彙編、鏈接
頭文件
- 默認頭文件路徑(程序中用
<>
包含頭文件,會到這個默認路徑查找文件)- 系統gcc編譯器默認頭文件路徑:一般在
/usr/include
目錄下,進入該目錄,然後使用find -name "stdio.h"
表示在當前目錄下查找stdio.h
頭文件! ==find /usr/include/ -name "stdio.h"
- 交叉編譯gcc編譯器默認頭文件路徑:一般在交叉編譯工具的
bin
文件所在目錄,如/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf
,然後搜索find -name "stdio.h
此時找到的頭文件是交叉編譯gcc編譯器對應的頭文件!
- 系統gcc編譯器默認頭文件路徑:一般在
- 不在默認頭文件路徑(使用雙引號)
- 不指定路徑,則默認到當前路徑下查找對應頭文件
- 指定路徑,加上
-I ×××路徑
函數庫
- 默認路徑
- 系統gcc編譯器默認函數庫路徑:一般在
/lib
或者/usr/lib
目錄。 - 交叉編譯gcc編譯器默認函數庫路徑:一般在交叉編譯工具的
bin
文件所在目錄,如/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf
,然後搜索然後搜索find -name lib
即可看到對應的lib
目錄路徑,一般開發板使用的是/lib
或者/usr/lib
目錄
- 系統gcc編譯器默認函數庫路徑:一般在
- 不在默認頭文件路徑
- 編譯時使用
-L
選項指定目錄,用-l
選項指定庫。如-labc
,即想找到名字爲libabc.so
的庫!
- 編譯時使用
使用動態鏈接庫
- 生成.o文件:
arm-linux-gnueabihf-gcc -c -o sub.o sub.c
- 生成動態鏈接庫:
arm-linux-gnueabihf-gcc -shared -o libsub.so sub.o
- 使用動態鏈接庫生成可執行文件:
arm-linux-gnueabihf-gcc -o test main.o -lsub -L ./
- 注意
-l
和sub
之間沒有空格 - sub庫並沒有位於工具鏈的lib目錄,所以我們需要指定目錄,
-L ./
指定庫所在的目錄爲當前目錄
- 注意
- 注意:使用動態鏈接庫交叉編譯生成的可執行程序,傳輸到板子上時,需要把動態鏈接庫
libusb.so
放到板子對應的/lib
目錄- 如果不放到板子對應的/lib目錄,則需要重新運行以下程序(放到
/a
目錄爲例)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/a
然後運行./test
- 如果不放到板子對應的/lib目錄,則需要重新運行以下程序(放到
使用靜態鏈接庫
- 生成.o文件:
arm-linux-gnueabihf-gcc -c -o sub.o sub.c
- 生成靜態鏈接庫:
ar crs libsub.a sub.o
(可以使用多個.o生成靜態庫) - 使用靜態鏈接庫生成可執行文件
rm-linux-gnueabihf-gcc -o test main.o libsub.a
(如果.a不在當`前目錄下,需要指定它的絕對或相對路徑) - 注意:靜態鏈接庫不需要放到板子上!!!
GCC編譯一些很有用的選項
gcc -E main.c
// 查看預處理結果,比如頭文件是哪個gcc -E -dM main.c > 1.txt
// 把所有的宏展開,存在1.txt裏gcc -Wp,-MD,abc.dep -c -o main.o main.c
// 生成依賴文件abc.dep,後面Makefile會用
Makefile
$@
表示目標$^
表示所有依賴,$<
表示第一個依賴-Wp,-MD,[email protected]
,生成所有的目標依賴頭文件
驅動程序
- 驅動程序 = 驅動框架+硬件操作(單片機)
- 驅動程序書寫步驟
- ① 確定主設備號,也可以讓內核分配
- ② 定義自己的 file_operations 結構體
- ③ 實現對應的 drv_open/drv_read/drv_write 等函數,填入 file_operations 結構體
- ④ 把 file_operations 結構體告訴內核: register_chrdev
- ⑤ 誰來註冊驅動程序啊?得有一個入口函數:安裝驅動程序時,就會去調用這個入口函數
- ⑥ 有入口函數就應該有出口函數:卸載驅動程序時,出口函數調用 unregister_chrdev
- ⑦ 其他完善:提供設備信息,自動創建設備節點: class_create, device_create
- 函數查找參考內核中的其他程序
register_chrdev
第一個參數爲主設備號,傳入0,讓內核爲你分配一個主設備號。也可以指定主設備號(前提:這個主設備號沒被佔用!)- 加載的時候先創建類,再創建設備。
- 先摧毀設備,再摧毀類,最後取消註冊。
應用程序
- 函數使用,利用man手冊。如查找printf依賴的頭文件
man 3 printf
- 應用程序不能直接訪問內核數據,必須藉助其他數據。
設備樹基礎
- DTS - Device Tree Source
- DTC - Device Tree Compiler
- DTB - Device Tree Binary
.dtsi
是SOC的公共設備樹文件(i是include),.dts
是我們寫的設備樹文件,會追加到了.dtsi
文件中!/dts-v1/;
這個是.dts
設備樹文件開頭都要寫的- 命名
[label:] node-name[@unit-address]
中括號中內容可以省略。- label 的作用是爲了方便地引用 node,如
uart0: uart@fe001000
,可以有以下兩種訪問方式。&uart0
&{/uart@fe001000}
- label 的作用是爲了方便地引用 node,如
- 系統啓動後可以在根文件系統中看到設備樹的節點信息。
/proc/device-tree
- 編譯設備樹:
make dtbs
- 設備樹的處理過程是:
dtb -> device_node -> platform_device
設備樹屬性
- compatible
- 根節點下的
compatible
屬性- 用來選擇哪一個“machine desc”: 一個內核可以支持machine A,也支持 machine B,內核啓動後會根據根節點的 compatible 屬性找到對應的machine desc (機器描述)結構體,執行其中的初始化函數。
- compatible 的值,建議取這樣的形式: “manufacturer,model”,即“廠家名,模塊名”。
- 設備節點下的
compatible
屬性- 用來表示兼容的驅動。比如對於某個 LED,內核中可能有 A、 B、 C 三個驅動都支持它,那可以這樣寫。內核啓動時,就會爲這個 LED 按這樣的優先順序爲它找到驅動程序: A、 B、 C。
- 根節點下的
led {
compatible = “A”, “B”, “C”;
};
-
model
- model 用來準確地定義這個硬件是什麼
- 比如一個單板的設備文件的根節點下compatible屬性,可以兼容內核中的“smdk2440”,也兼容“mini2440”。但是它到底是什麼板?用 model 屬性來明確。
- 比如有 2 款板子配置基本一致, 它們的 compatible 是一樣的,那麼就通過 model 來分辨這 2 款板子
-
status
- 最後追加的值會覆蓋前面的值,即只有最後一次賦的值纔有效!
- dtsi 文件中定義了很多設備,但是在你的板子上某些設備是沒有的。這時你可以給這個設備節點添加一個 status 屬性,設置爲“disabled”。
- 通常使用的屬性值:“okay”和“disabled”
示例
//address-cells 爲 1,所以 reg 中用 1 個數來表示地址,即用 0x80000000 來表示地址
//size-cells 爲 1,所以 reg 中用 1 個數來表示大小,即用 0x20000000 表示大小
/ {
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x80000000 0x20000000>;
};
};
-
#address-cells、 #size-cells
- cell 指一個 32 位的數值
- address-cells: address 要用多少個 32 位數來表示;size-cells: size 要用多少個 32 位數來表示。
-
reg
- 本意是 register,用來表示寄存器地址。但在設備樹裏,它可以用來描述一段空間。
- reg 屬性的值,是一系列的“address size”,用多少個 32 位的數來表示 address 和 size,由其父節點的#address-cells、 #size-cells 決定。
示例:
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x80000000 0x20000000>;
};
};
對於 ARM 系統,寄存器和內存是統一編址的,即訪問寄存器時用某塊地址,訪問內存時用某塊地址,在訪問方法上沒有區別。
-
name(已過時)
- 它的值是字符串,用來表示節點的名字。在跟 platform_driver 匹配時,優先級最低。compatible 屬性在匹配過程中,優先級最高。
-
device_type(已過時)
- 它的值是字符串,用來表示節點的類型。在跟 platform_driver 匹配時,優先級爲中。compatible 屬性在匹配過程中,優先級最高。
OF函數【Open Firmware 開放固件】
- 內核源碼中 include/linux/目錄下有很多 of 開頭的頭文件。
- 驅動通過OF函數獲取設備樹節點信息
- 要獲取節點內容,需要先獲取節點