使用Petalinux学习驱动开发时的一些经验。
部分图片和经验来源于网络,若有侵权麻烦联系我删除,主要是做笔记的时候忘记写来源了,做完笔记很久才写博客。
专栏目录:记录自己的嵌入式学习之路-CSDN博客
目录
6.2 platform_set_drvdata/platform_get_drvdata
7.1 将.c编译成模块时提示asm/types.h: 没有那个文件或目录
7.2 disagrees about version of symbol module_layout
7.4 linux/ide.h: No such file or directory
7.5 implicit declaration of function ‘copy_to_user’
7.6 ‘struct file_operations’ has no member named ‘owner’
7.7 function declaration isn’t a prototype
7.8 modpost: __aeabi_dmul/__aeabi_i2d/__aeabi_d2iz undefined
8.3 : loading out-of-tree module taints kernel
8.4 使用of_address_to_resource函数获取设备树资源的时候错误
1 基础——字符设备驱动
1.1 分配设备号(驱动入口使用)
1.2 字符设备创建和注册
1.3 删除字符设备
1.4 注销设备号(驱动出口使用)
1.5 设备类的创建
为了实现设备节点文件的自动创建,需要在驱动加载时创建设备类。
struct class *class_create (struct module *owner, const char *name)
其中,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。返回值是个指向结构体 class 的指针,也就是创建的类。
1.6 设备类的删除
有设备类的创建就得有设备类的删除,在设备驱动卸载时进行。
void class_destroy(struct class *cls);
其中,参数 cls就是要删除的类。
1.7 创建类下的设备
创建设备就是在/dev/下创建设备文件了
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
device_create 是个可变参数函数,参数 class 就是设备要创建哪个类下面;参数 parent 是父设备,一般为 NULL,也就是没有父设备;参数 devt 是设备号;参数 drvdata 是设备可能会使用的一些数据,一般为NULL;参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx 这个设备文件。返回值就是创建好的设备。
1.8 删除类下的设备
卸载驱动的时候需要删除掉创建的设备:
void device_destroy(struct class *class, dev_t devt)
2 初级——GPIO子系统
gpio子系统是linux内核当中用于管理GPIO资源的一套系统,它提供了很多GPIO相关的API接口。驱动程序中使用GPIO之前需要向gpio子系统申请,申请成功之后才可以使用。
有了GPIO子系统,GPIO 的输入、输出方向,设置GPIO输出高或低电平、读取GPIO输入电平就无需傻傻地手动修改寄存器实现了。
2.1 GPIO子系统使用的前提——设备树
gpio子系统虽然方便了驱动开发者使用gpio,但是最终还是得去操作硬件寄存器;所以在使用gpio子系统前,需要在设备树中描述硬件相关资源。
如上图所示,led-gpio属性用于描述该硬件由gpio0组的7号IO控制,且其有效电平是高电平。事实上这个led-gpio可以为xxx-gpio,可以理解为一个自定义属性,是用于驱动程序读取和gpio相关参数的。至于gpio0这个是怎么来的,就要看芯片厂商给出的设备树,对于我们来说,就是要看源码中的zynq-7000.dsti:
该节点描述了zynq的gpio设备。其中:
compatible:表明了要匹配的驱动,在源码中全文搜索其内容就能找到相应的驱动程序,不过用Linux的nautilus没办法搜索到,用Windows可以,其驱动在<源码>/drivers/gpio中;
#gpio-cells:表明了外部在引用该gpio外设的时候需要提供的额外参数数量,如上面之所以要<&gpi00 7 GPIO_ACTIVE_HIGH>,后面这两个参数就是#gpio-cells决定了的,至于参数具体含义是什么,可以参考<源码>/Documentation/devicetree/bindings/gpio下的gpio-zynq.txt;
gpio-controller:表示gpio0节点是个GPIO控制器,表示这个节点对应的驱动程序是gpio驱动;
reg:gpio的地址范围;
2.2 GPIO常用的OF函数
int of_gpio_named_count(*np, *propname):用于获取设备树某个属性里面定义了几个GPIO信息,要注意的是空的GPIO信息也会被统计到,其中np为设备节点,propname为要统计的GPIO属性;
intof_gpio_count(*np):此函数统计的是“gpios”这个属性的GPIO数量;
int of_get_named_gpio(*np, *propname, index):获取设备树中指定GPIO引脚的在内核中的编号;
2.3 GPIO子系统常用函数
int gpio_request(gpio, *label):向系统申请一个GPIO引脚,使用前必须申请;
void gpio_free(gpio):释放申请的GPIO引脚;
int gpio_direction_input(gpio):设置GPIO为输入;
int gpio_direction_output(gpio, value):设置GPIO为输出并设置其默认输出值;
int gpio_get_value(gpio):获取GPIO的值;
void gpio_set_value(gpio, value):设定GPIO的值;
int gpio_is_valid(gpio):判断GPIO编号是否是有效编号;
2.4 常规的GPIO子驱动的编写步骤
(1) of_get_named_gpio获取GPIO编号,并用gpio_is_valid验证编号是否有效。其中,需要的device_node参数由OF函数读取设备树或平台驱动提供(probe函数有);
(2) 使用gpio_request向系统申请使用指定的GPIO编号;
(3) 使用of_property_read_string读取设备树的default-state以确定GPIO的缺省状态;
(4) 使用gpio_direction_output/input设置GPIO为输出还是输入;
(5) 像字符设备驱动一样,进行设备号的分配、cdev的初始化、cdev的添加、创建类、创建设备等等;
(6) 在read/write函数中,利用gpio_get_value/gpio_set_value实现对GPIO的读写操作;
在驱动写卸载相关的函数(如驱动的exit或平台驱动的remove)中,使用gpio_free释放掉占用的GPIO编号;
2.5 类和设备操作示例
3 一切总线/框架的基础——平台总线框架
(1) 总线(Bus)负责维护注册(device)和驱动(driver);
(2) 注册进来一个device或者driver都会调用Bus->match函数将device与driver进行配对,并将它们加入链表,如果配对成功,调用Bus->probe或者driver->probe函数;
(3) 一个device只能配对一个driver;而一个driver可以对应多个device。
(4) 平台总线是一种虚拟的总线,其面向的是没有硬件总线,直接在CPU上取址的设备。
3.1 平台总线实例
(源码/include/linux/device.h)
系统中已经定义好的bus_type结构体变量platform_bus_type,其中定义了一些管理设备/驱动的函数。
3.2 平台总线的设备/驱动匹配
除driver_override匹配外,其他匹配方法都会按优先级依次尝试,直到匹配到或者所有方法都用完后才结束。一般需要支持有无设备树两种情况,即(2)必须支持,(4)和(5)至少得支持一种。
(1) driver_override(强制驱动匹配) : 优先级1
平台设备结构体(platform_device)中的driver_override成员被赋值,则强制使用与该值同名(.name)的驱动,完全不考虑其他驱动。(哪怕没有匹配的呀不考虑别的驱动)
(2) 设备