基于Petalinux做Linux驅動開發。
部分圖片和經驗來源于網絡,若有侵權麻煩聯系我刪除,主要是做筆記的時候忘記寫來源了,做完筆記很久才寫博客。
專欄目錄:記錄自己的嵌入式學習之路-CSDN博客
目錄
1 一個完整的Linux系統(針對Zynq)
1.1 PS部分
1.2 PL部分(若沒用到PL就不需要這個部分):
2 Petalinux的內核源碼獲取
3 系統編譯過程
4 NFS掛載根文件系統(Rootfs)的條件
5 交叉編譯
6 驅動模塊的開發
6.1 驅動的運行方式
6.2 file_operations結構體
6.3 驅動模塊的加載和卸載
6.4 一個驅動程序必須有的東西
6.5 字符設備的注冊和注銷
6.6 一個字符設備驅動必須有的東西
6.7 設備號
6.8 內核空間與用戶空間
7 地址映射
7.1 相關函數
8 設備樹
8.1 是什么
8.2 為什么
8.3 設備樹相關概念
8.4 一個典型的設備樹文件
8.5 設備樹節點的基本格式
8.6 節點屬性
8.7 特殊節點
8.8 如何定位一個節點
8.9 內核啟動過程中設備樹的解析過程
8.10 如何添加一個設備樹節點
8.11 如何引用一個節點
8.12 驅動與設備樹交互的函數
8.13 設備樹使用注意事項
9 內核的內存申請
9.1 常見的作用域
9.2 驅動程序中常使用static的原因
9.3 動態內存申請
9.4 kmalloc函數
9.5 kzalloc函數
9.6 vmalloc函數
9.7 devm_kmalloc/devm_kzalloc函數
9.8 devm_kmalloc_array/kmalloc_array
10 驅動與用戶空間的交互函數
10.1 read函數
10.2 write函數
10.3 unlocked_ioctl
10.4 對比
11 ioctl詳解
11.1 ioctl協議的命令組成
11.2 ioctl的宏
11.3 用于輸入輸出的時候需要注意的點
12 Linux開發常用的頭文件
12.1 驅動開發頭文件
12.2 Linux應用開發頭文件
13 Linux驅動開發常用的宏
1 一個完整的Linux系統(針對Zynq)
1.1 PS部分
五大要素:
(1)fsbl(First Stage Boot Loader) -> zynq_fsbl.elf
負責初始化PS部分的硬件并加載第二階段的引導加載程序。
(2)uboot(Universal Boot Loader) -> u-boot.elf、boot.scr(boot引導)
Bootloader 是在操作系統運行之前執行的一段小程序。通過這段小程序,可以初始化硬件設備、建立內存空間的映射表,從而建立適當的系統軟硬件環境,為最終調用操作系統內核做好準備。
boot.scr文件則是用于給uboot執行和啟動相關的行為的腳本,其中包含了加載內核、設備樹、根文件系統等操作,我個人感覺和uboot中的bootm、bootz等命令類似。而這個文件整體又和uboot中bootargs、bootcmd這兩個環境變量的作用類似。
(3)設備樹文件 -> system.dtb
一種描述硬件的數據結構。
(4)linux內核 -> image.ub、(zImage)、(uImage)
內核是Linux系統的核心,負責管理系統的硬件和軟件資源。其中,image.ub可以直接通過uboot上的bootm命令進行Linux的引導啟動,也是放在SD卡BOOT分區就能自動引導的內核鏡像文件;zImage是一種經過gzip壓縮的Linux內核鏡像格式;uImage是U-Boot引導加載程序專用的內核鏡像格式。它是在zImage或Image(不加壓縮的內核鏡像)的基礎上加上一個U-Boot頭部信息(U-BootHeader),使U-Boot能夠識別并加載內核鏡像。
注:image.ub其實包含了system.bit,zImage,system.dtb三者。
(5)根文件系統 -> rootfs.tar.gz
根文件系統提供了操作系統運行所需的文件和程序。
1.2 PL部分(若沒用到PL就不需要這個部分):
(1)比特流(Bitstream)文件 -> system.bit
FPGA的配置文件,用于初始化PL部分的硬件。
2 Petalinux的內核源碼獲取
進行驅動開發時往往需要Linux的源碼,但在Xilinx的Github中不一定有指定版本(petalinux編譯時的版本)的Linux內核源碼,例petalinux 2023.1的6.1.5版本的內核就沒有打包在那里。
而petalinux編譯的過程中,默認是會刪除掉其解壓出來的源碼的。要拿到它編譯的源碼,可以修改<plnx_proj>/project-spec/meta-user/conf/petalinuxbsp.conf,加上RM_WORK_EXCLUDE += "linux-xlnx",編譯后可以在<plnx_proj>/build/tmp/work-shared/zynq-generic-7z020/kernel-source中獲取源碼。
若需要uboot源碼,則加上RM_WORK_EXCLUDE += “u-boot-xlnx”。
3 系統編譯過程
普通的petalinux開發,最后會將fsbl、比特流文件、uboot、設備樹這四個部分都打包到一個BOOT.BIN的文件中去。常用命令是:
petalinux-package --boot --fsbl --fpga --u-boot –-force
若進行Linux驅動開發,設備樹文件、linux內核、根文件系統以及比特流文件都是有可能經常發生改動的,因此要盡量將這些分離出來。
(1)首先,是在編譯時,僅將fsbl和uboot編譯并打包進BOOT.BIN中,因為這兩個文件基本不會變:
petalinux-build -c bootloader //編譯fsbl
petalinux-build -c u-boot //編譯uboot
petalinux-package --boot --fsbl --u-boot --dtb no –force//打包兩者進BOOT.BIN,并放過其他成員。
(2)其次,是petalinux-build后修改boot.scr,因為以這樣的方式編譯出來的boot.scr腳本的內容是不對的,具體就是:
將部分對uImage的操作改為對zImage的操作(這里主要就是不使用image.ub改用zImage了);
并添加對system.bit的操作(添加關于比特流的內容是因為沒有將比特流打包進BOOT.BIN,過程中生成的boot.sc也因此沒有與其相關的操作,所以要手動添加);
具體可以看正點原子的教程或看正常用petalinux整合編譯時的boot.scr文件,這里其實就是將其修改為正常boot.scr該有的樣子。最后是將原版改名為boot.cmd.default,并刪掉第一行(包含亂碼的行)再使用以下命令重新生成為boot.scr:
mkimage -c none -A arm -T script -d boot.cmd.default boot.scr
要重新生成一個boot.scr的理由也很簡單,就是因為boot.scr文件的頭部帶有一些生成的二進制數據,直接修改boot.scr是不行的。
(3)接著,給Linux源碼增加設備樹文件,從上面編譯到的文件中取(路徑為<petalinux項目根目錄>/components/plnx_workspace/device-tree/device-tree),具體需要pcw.dtsi,pl.dtsi,system-top.dts,zynq-7000.dtsi和system-conf.dtsi這五個。將其放置到源碼/arch/arm/boot/dts目錄中并根據需要對system-user.dtsi和該目錄下的MAKEFILE進行修改。
(4)然后,利用make xilinx_zynq_defconfig命令設置內核配置;并使用make -j8編譯內核。該步會在arch/arm/boot中生成所需的內核鏡像文件zImage,在arch/arm/boot/dts生成設備樹二進制文件system-top.dtb。若僅修改了設備樹文件,可以僅編譯它,用命令make dts。
(5)其后,回到petalinux項目中,用petalinux-config -c rootfs命令和petalinux-build -c rootfs命令配置并編譯根文件系統得到rootfs.tar.gz。
(6)最后,在SD卡的BOOT分區,放入文件。用BOOT.BIN,boot.scr,system.bit,zImage,system.dtb五個文件代替普通Petalinux開發的BOOT.BIN,boot.scr和image.ub三個文件的方案:
BOOT.BIN :來自步驟(1),是僅包含fsbl和uboot的啟動引導文件;
boot.scr :來自步驟(2),是添加比特流操作行為且更改boot內核行為后的boot腳本;
system.bit :來自步驟(1),是petalinux項目生成的比特流文件;
zImage :來自步驟(4),是Linux內核;
system.dtb :來自步驟(4),由system-top.dtb文件改名獲得,是設備樹二進制文件。
(7)最最后,將根文件系統解壓后放入SD卡的rootfs分區:
rootfs.tar.gz :來自步驟(5),根文件系統的壓縮包。
(附加)使用NFS掛載根文件系統會更好,因為不用經常將SD卡拿出來修改rootfs分區,而boot分區則是進入Linux后隨便改,反正進系統后該分區就沒有實際的用處了。
4 NFS掛載根文件系統(Rootfs)的條件
(1)Ubuntu安裝NFS
sudo apt install nfs-kernel-server
(2)Ubuntu一個創建NFS的掛載路徑
假設為/home/xxx/workspace/nfs
(3)Ubuntu修改NFS配置文件
sudo vi /etc/exports
在文件末添加如下內容:
/home/xxx/workspace/nfs *(rw,sync,no_root_squash)
(4)Ubuntu重啟rpcbind服務和NFS服務(其實重啟系統最好)
sudo /etc/init.d/rpcbind restart
sudo systemctl start nfs-kernel-server.service
(5)網絡硬件設置
Zynq開發板(用網線PS口)與Ubuntu(用網絡橋接)連接到同一路由器上。
(6)Ubuntu網絡軟件設置
通過ifconfig命令查看Ubuntu的局域網IP地址、網關地址、掩碼等。
(7)Zynq網絡軟件設置
開機并在倒計時前回車進入uboot,使用dhcp命令自動獲取IP,依次使用以下命令將相關配置寫入開發板,具體IP要根據實際情況更改:
setenv ipaddr 192.168.100.10 //開發板 ip 地址
setenv gatewayip 192.168.100.1 //開發板網關
setenv netmask 255.255.255.0 //開發板 ip 地址掩碼
setenv serverip 192.168.100.2 //ubuntu ip地址
最后使用saveenv命令將其保存。
(8)Zynq boot命令參數設置
使用以下命令設置boot要執行的參數:
setenv bootargs 'console=ttyPS0,115200 root=/dev/nfs rw nfsroot=192.168.100.2:/home/XXX/RootFS_NFS,nfsvers=3 ip=192.168.100.10:192.168.100.2:192.168.100.1:255.255.255.0::eth0:off'
最后使用saveenv命令保存。
至此,設置完畢。后續調試時,只有boot分區需要提前放好在SD卡中,rootfs直接解壓在Ubuntu的/home/xxx/workspace/nfs/rootfs目錄后,打開Zynq開發板就能自動掛載根文件系統。
5 交叉編譯
當主機平臺(運行編譯器的平臺)和目標平臺(產生的程序將在其上運行的平臺)不兼容時,該過程就叫做交叉編譯。
petalinux裝好后的編譯環境:
由none可以看出,這些編譯器是沒有編譯linux的能力的。而Petalinux在構建linux系統過程中會編譯生成linux交叉編譯工具鏈,然后使用其構建 linux 系統,可以在Petalinux工程下使用“find . -name "arm-xilinx-linux-gnueabi-gcc"”命令找到相應的痕跡:
需要拿到交叉編譯的SDK,需要對某一個petalinux項目執行petalinux-build --sdk,編譯好的sdk安裝腳本是/項目/image/linux中的sdk.sh,安裝到某一地方后安裝程序會提示后續需要配置sdk環境的命令,可以寫個別名放到~/.bashrc中方便使用。
安裝了SDK后的arm包:
6 驅動模塊的開發
6.1 驅動的運行方式
(1)編譯進內核;
(2)編譯成模塊,在內核啟動后使用insmod命令加載驅動模塊;
一般在調試開發時是編譯成模塊,開發完成后就可以在編譯成模塊和編譯進內核中選擇了。
6.2 file_operations結構體
位于Linux內核/include/linux/fs.h中,其中fs是file system的縮寫。該結構體是Linux內核驅動操作函數集合。
6.3 驅動模塊的加載和卸載
(1)加載
insmod:加載指定的.ko模塊,但不能解決模塊的依賴關系,比如 drv.ko依賴 first.ko這個模塊,就必須先使用 insmod命令加載 first.ko這個模塊,然后再加載 drv.ko 這個模塊;
modprobe(推薦):modprobe 會分析模塊的依賴關系,然后將所有依賴的模塊都加載到內核中,因此 modprobe命令相比 insmod 要智能一些。其次modprobe還有錯誤檢查、錯誤報告等功能;
注意:modprobe命令默認會去/lib/modules/<kernel-version>目錄中查找模塊,如果沒有的話需要自行創建,如petalinux2020.2就需要創建/lib/modules/5.4.0-150-generic目錄。
注意:使用modprobe命令前,需先運行depmod建立系統的模塊依賴關系。
(2)卸載
rmmod(推薦):卸載某個模塊;
modprobe:卸載某個模塊及其依賴的模塊,而因為該模塊依賴的模塊也有可能在被其他模塊所使用,卸載會出問題,所以不推薦使用modprobe來卸載模塊;
6.4 一個驅動程序必須有的東西
(1) 驅動入口函數:static int __init xxx_init(void)
(2) 驅動出口函數:static void __exit xxx_exit(void)
(3) 指定驅動入口函數的語句:module_init(xxx_init); // 指定后加載模塊就會自動運行驅動入口函數
(4) 指定驅動出口函數的語句:module_exit(xxx_exit); // 指定后卸載模塊就會自動運行驅動出口函數
(5) 開源許可證類型:MODULE_LICENSE(str) //添加模塊LICENSE信息(必須)
(6) 作者信息:MODULE_AUTHOR(str