【Linux】Petalinux驱动开发基础

基于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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值