编写LED驱动-设备树
写LED驱动
首先,我们要清楚总线设备驱动相比于传统驱动模型而言,在操作上做了精简。传统驱动模型针对不同的board或者change gpio,都需要对相应drv.c进行修改,这样操作相对比较复杂,也不容易后期的维护,总线驱动将所有的设备全部注册在platform_dev结构体内,每个设备不同的在于他们相应的resource不同,如果change gpio,只需修改platform_dev 结构体即可。
linux kernel不能直接访问register的物理地址,需要采用ioremap的映射映射到对应的物理地址: led_cnt = ioremap(addr, size)
后面在通过分配/设置/注册来设计相应的硬件操作。
首先为了方便维护与扩展,首先我们定义了两个文件:platform_devices-----存储相应的寄存器信息;platform_driver----寄存器的相应操作
怎么去理解上面两个文件呢?
platform_devices就相当于一个仓库,里面存放了很多的物品(硬件资源),他把物品的地址信息告诉你。
platform_driver 就相当于一个操作员,你从仓库中拿到这个物品,接下来你需要如何操作他,该文件进行了定义,也同传统方式一样,分配file_operations/设置/注册。
当然,一个仓库里面这么多物品,你如何去管理他呢?
接下来引入了总线,总线分为两部分:platform_devices,platform_driver,在内核内定义了两个结构体,相比于传统驱动模式,只需要修改platform_devices内的GPIO配置即可,不需要在修改drv的部分,相对来说扩展性更强一点。但是也存在一个问题,每个硬件资源修改后,都需要重新编译,并且随着驱动设备的增多,重复性的platform-devices随之增加,浪费了内核资源,这也就是后面为什么会引入设备树的原因,只需要修改dts文件即可,内核只负责解析这些配置不需要在重新编译。
下面是韦东山老师的笔记,从中可以看出BUS将总线划分了两个部分:platform_devices, platform_driver.
resource: 用于指定硬件资源,flags用于指定硬件资源类型:GPIO,IRQ,IIC等。start指定硬件资源位置。
那么如何匹配platform_devices, platform_driver, 换句话怎么知道platform_devices对应的是那个platform_driver? name/idtable-----platform_devices, platform_driver两者拥有相同的name
在dev与driver定义了两个链表,首先先找到对应的dev,然后在driver链表中查找name/idtable匹配的对应的driver
probe: 定义了主要的driver驱动函数。write/read/open等。
两个结构体都被注册到了内核?即注册了两个字符设备驱动? 其实是我们将之前的字符驱动拆分为了:硬件资源与驱动资源两部分,所以需要同时将两个都注册到内核内。
下方是注册到匹配drv/dev的流程,这里不需要太过深入理解,通过下面流程可以了解在注册platform_device/platform_driver时,会自动匹配对应的drv
platform_device_register platform_device_add device_add bus_add_device // 放入链表 bus_probe_device // probe 枚举设备,即找到匹配的(dev, drv) device_initial_probe __device_attach bus_for_each_drv(...,__device_attach_driver,...) __device_attach_driver driver_match_device(drv, dev) // 是否匹配 driver_probe_device // 调用 drv 的 probe platform_driver_register __platform_driver_register driver_register bus_add_driver // 放入链表 driver_attach(drv) bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); __driver_attach driver_match_device(drv, dev) // 是否匹配 driver_probe_device // 调用 drv 的 probe
常用的函数:
通过名字(name)返回该 dev 的某类型(type)资源:
通过名字(name)返回该 dev 的中断号:
① 分配/设置/注册 platform_device 结构体 在里面定义所用资源,指定设备名字。
② 分配/设置/注册 platform_driver 结构体 在其中的 probe 函数里,分配/设置/注册 file_operations 结构体, 并从 platform_device 中确实所用硬件资源。 指定 platform_driver 的名字。
首先,我们要建立一个file_operator结构体:
/* 定义自己的file_operations结构体 */
static struct file_operations led_drv = {
.owner = THIS_MODULE, //主设备号
.open = led_drv_open, // driver function
.read = l
ed_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
编写相应的四个driver function:
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
//struct inode *inode = file_inode(file);
//int minor = iminor(inode);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(&status, buf, 1);
/* 根据次设备号和status控制LED */
gpiod_set_value(led_gpio, status);
return 1;
}
static int led_drv_open (struct inode *node, struct file *file)
{
//int minor = iminor(node);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 根据次设备号初始化LED */
gpiod_direction_output(led_gpio, 0);
return 0;
}
static int led_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
注册filer_operator: 告诉CPU:这里采用设备树的方式:
b
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)