目录
- 前言
- 一、平台设备总线
- 1.是个啥?
- 2.API函数
- 二、设备端+驱动端
- 1. 匹配机制
- 2. 实现代码
- 三、设备树+驱动端
- 1.匹配机制
- 2.代码实现
前言
本文主要介绍了一下两种驱动编写方法:
1.比较原始的设备端+驱动端编写方法。
2.效率较高的设备树+驱动端编写方法。
最后,使用LED闪烁实战验证了一下。
一、平台设备总线
1.是个啥?
在Linux驱动开发中,平台设备总线(Platform Device Bus)是一个重要的概念,用于管理与特定平台紧密集成的设备。这些设备通常不是通过传统的总线(如PCI、USB)连接的,而是直接连接到系统的硬件平台上。
说人话就是:平台设备总线(Platform Device Bus)是Linux内核虚拟出来的一条总线,不是真实的导线。它将底层驱动分为了设备端和驱动端。设备端主要就是负责提供稳定不变的东西。例如设备的信息等,大部分就是和硬件相关的(该部分可以选择用设备树写)。驱动端主要就是负责先从设备端获取稳定不变的信息,然后再去驱动一些需要变动的东西。
平台设备总线也分为两种编写方式:1.设备端+驱动端。2.设备树+驱动端。本质上的区别就是硬件配置的管理方式不同,前者是要单独写一个驱动程序,里面包含了有关设备的所有配置信息。后者的硬件配置信息通过挂载到设备树上来管理。
注意:无论是设备端+驱动端还是设备树+驱动端他们在加载时都不用分先后。
2.API函数
注册平台设备函数:
int platform_device_register(struct platform_device *pdev);
函数参数:指向 platform_device
结构体的指针(核心结构体)
void xyd_release(struct device *dev)//虽然不用填东西,但必须要有
{
}
struct platform_device {
const char *name;
int id;
struct device dev;
u32 num_resources;
struct resource *resource;
}
核心结构体里的参数有很多,需要填写的部分如上所示。
name:就是和驱动端所要匹配的名字。
id:也是匹配方式的一种,这里一般填-1即可
dev:存放设备信息的核心结构体(void (*release)(struct device *dev);
)
num_resources:提供资源的数量,一般用ARRAY_SIZE()计算。
resource:提供的具体资源
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
};
start:资源设备的起始地址
end:资源设备的结束地址
name:给这个设备资源取的名字
flags:资源的类型 — 使用宏即可,内核已经帮你定义好了
注销平台设备函数:
void platform_device_unregister(struct platform_device *pdev)
函数参数:平台设备的核心结构体
接下来是驱动端的API函数:
platform_driver_register(struct platform_driver *prv)
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
struct device_driver driver;
};
probe:探测函数。 设备端和驱动端匹配成功时就会进入到该函数中去执行,从而去获取资源以及注册相关的设备资源。
remove:移除函数,有一端被卸载时就会进入该移除函数中执行。一般存放卸载的API函数。
driver:设备资源的核心结构体
struct device_driver {
const char *name;
const struct of_device_id *of_match_table;
};
name:驱动端和设备端进行匹配的名字
of_match_table:他是用于驱动端和设备树进行匹配的参数
struct of_device_id {
char compatible[128];//他要和设备树里的 compatible 保持一致
};
如果你使用的是驱动端+设备树的编写方法,那么此时你需要填写如下结构体:
struct device_driver {
const char *name,
}
否则你去加载内核驱动时,内核会出问题。
二、设备端+驱动端
这种方法,现在用的已经较少了,因为比较麻烦。设备信息通常在代码中直接定义,通常适合简单的工作场景。
1. 匹配机制
因为平台设备总线将驱动分为设备端和驱动端,那么驱动运行时,驱动端是如何找到对应的设备端,去寻找设备信息的呢?
关键就在于:名字,设备端+驱动端的匹配是由名字来匹配的,本质上就是利用strcmp函数进行字符串比较。
设备端和驱动端都有一个属于自己的核心结构体,而各自的核心结构体里都有一个名为name的成员变量,他们就是通过该变量进行匹配的。
在编写时一定要注意两端的name变量要保持一致
2. 实现代码
设备端:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
//定义结构体表明存放的设备信息
struct resource Led_s[2]={
[0]={
.start = 21,
.end = 22,
.name = "my_leds",
.flags = IORESOURCE_MEM,
},
[1]={
.start = 0,
.end = 1,
.name = "my_leds",
.flags = IORESOURCE_MEM,
}
};
void xyd_release(struct device *dev)
{
}
struct platform_device pdev={
.name="my_leds",
.id= -1,
.dev={
.release = xyd_release,
},
.num_resources = ARRAY_SIZE(Led_s),
.resource = Led_s,
};
static int __init mydevice_init(void)
{
int a=0;
printk("设备端加载函数运行!\n");
a=platform_device_register(&pdev);
if(a < 0)
{
printk("platform_device_register error\n");
return -1;
}
printk("设备端注册成功\n");
return 0;
}
static void __exit mydevice_exit(void)
{
printk("设备端卸载函数运行!\n");
platform_device_unregister(&pdev);
printk("设备端卸载完成!\n");
}
module_init(mydevice_init);
module_exit(mydevice_exit);
MODULE_LICENSE("GPL");
驱动端
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include<linux/device.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/platform_device.h>
struct resource *led[2];
dev_t dev;
struct cdev mydev;
struct class *myclass = NULL;
int my_open (struct inode *inode, struct file *fp)
{
int i;
for(i=led[0]->start;i<=led[0]->end;i++)
{
gpio_set_value(i,led[1]->end);//21,1/22,1
}
printk("led灯:亮!\n");
return 0;
}
int my_release (struct inode *inode, struct file *fp)
{
int i;
for(i=led[0]->start;i<=led[0]->end;i++)
{
gpio_set_value(i,led[1]->start);//21,0/22,0
}
printk("led灯:灭!\n");
return 0;
}
struct file_operations my_file = {
.open = my_open,
.release = my_release,
};
int my_probe(struct platform_device *pdev)
{
int i;
for(i=0;i<=1;i++)
{
led[i] = platform_get_resource(pdev,IORESOURCE_MEM,i);
}
gpio_request(led[0]->start, "led1");
gpio_request(led[0]->end, "led2");
gpio_direction_output(led[0] -> start, 0);
gpio_direction_output(led[0] -> end, 0);
//动态申请设备号
alloc_chrdev_region(&dev, 0, 1, "myled");
//初始化设备核心结构体
cdev_init(&mydev,&my_file);
//向内核申请Linux2.6字符设备
cdev_add(&mydev, dev, 1);
myclass = class_create(THIS_MODULE,"LED_CLASS");
device_create(myclass, NULL, dev, NULL, "myleds");
printk("探测函数:设备端和驱动端匹配成功\n");
return 0;
}
int myled_remove(struct platform_device *pdev)
{
printk("移除函数运行\n");
device_destroy(myclass,dev);//销毁设备节点
class_destroy(myclass);//销毁设备类
cdev_del(&mydev);//删除字符设备
unregister_chrdev_region(dev,1);//释放设备号
printk("设备注销成功\n");
gpio_free(led[0]->start);
gpio_free(led[1]->start);
return 0;
}
struct platform_driver drv = {
.probe = my_probe,
.remove = myled_remove,
.driver = {
.name = "my_leds",
},
};
static int __init mydriver_init(void)
{
platform_driver_register(&drv);
return 0;
}
static void __exit mydriver_exit(void)
{
platform_driver_unregister(&drv);
}
module_init(mydriver_init);
module_exit(mydriver_exit);
MODULE_LICENSE("GPL");
应用端
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
int fd =0;
char buffer[100];
const char *data = "Hello, this is a test write!";
if(argc<2)
{
printf("请输入正确的参数\n");
return -1;
}
fd = open(argv[1],O_RDWR);
if(fd<0)
{
perror("open");
return -1;
}
while(1)
{
fd = open(argv[1],O_RDWR); // --- 底层的open函数
sleep(1);
close(fd);//底层的close
sleep(1);
}
return 0;
}
三、设备树+驱动端
使用设备树+驱动端的编写方法是当前最常用且效率最高的方式。通过设备树,我们能够通过几行简洁的代码完成硬件信息的挂载,从而将主要精力集中在编写驱动端的代码上。
1.匹配机制
和设备端+驱动端的匹配方法不同,设备树+驱动端的匹配机制主要是通过设备树里的 Compatible属性来匹配的。所以在编写时,驱动端代码要和设备树上的compatible(兼容性标识符)保持一致。
设备树:
驱动端:
2.代码实现
设备树:
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2021 Rockchip Electronics Co., Ltd.
*
*/
/dts-v1/;
#include "rk3588s-evb4-lp4x-yyt.dtsi"
#include "rk3588-linux.dtsi"
/ {
model = "Rockchip RK3588S EVB4 LP4X V10 Board";
compatible = "rockchip,rk3588s-evb4-lp4x-v10", "rockchip,rk3588";
xydled:xyd_leds {
compatible = "xyd_led";
leds-gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,
<&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>,
<&gpio0 RK_PD0 GPIO_ACTIVE_HIGH>,
<&gpio1 RK_PD5 GPIO_ACTIVE_HIGH>;
status = "okay";
};
xydkey:xyd_keys{
compatible = "xyd_keys";
keys-gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,
<&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio1>;
interrupts = <RK_PA7 IRQ_TYPE_LEVEL_LOW>,
<RK_PB1 IRQ_TYPE_LEVEL_LOW>;
status = "okay";
};
xydbeep:xyd_beep{
compatible = "xyd-beep";
beeps-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;
status = "okay";
};
xydinfo:xyd_device{
compatible = "xyd-device";
devices-gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,
<&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>,
<&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,
<&gpio1 RK_PB1 GPIO_ACTIVE_LOW>,
<&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;
status = "okay";
};
xydnod:xyd_devs{
compatible = "xyd-devs";
xydled1: xyd_leds {
leds-gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,
<&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;
status = "okay";
};
xydkey1: xyd_keys {
keys-gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,
<&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;
status = "okay";
};
xydbeep1: xyd_beep {
beeps-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;
status = "okay";
};
};
};
驱动端:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include<linux/device.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/platform_device.h>
int led[2]={0};
dev_t dev;
struct cdev mydev;
struct class *myclass = NULL;
int my_open (struct inode *inode, struct file *fp)
{
int i;
for(i=0;i<=1;i++)
{
gpio_set_value(led[i],1);//21,1/22,1
}
printk("led灯:亮!\n");
return 0;
}
int my_release (struct inode *inode, struct file *fp)
{
int i;
for(i=0;i<=1;i++)
{
gpio_set_value(led[i],0);//21,0/22,0
}
printk("led灯:灭!\n");
return 0;
}
struct file_operations my_file = {
.open = my_open,
.release = my_release,
};
int my_probe(struct platform_device *pdev)
{
//获取编号
led[0] = of_get_named_gpio(pdev->dev.of_node,"leds-gpios",0);
led[1] = of_get_named_gpio(pdev->dev.of_node,"leds-gpios",1);
gpio_request(led[0], "led1");
gpio_request(led[1], "led2");
gpio_direction_output(led[0], 0);
gpio_direction_output(led[1], 0);
//动态申请设备号
alloc_chrdev_region(&dev, 0, 1, "myled");
//初始化设备核心结构体
cdev_init(&mydev,&my_file);
//向内核申请Linux2.6字符设备
cdev_add(&mydev, dev, 1);
myclass = class_create(THIS_MODULE,"LED_CLASS");
device_create(myclass, NULL, dev, NULL, "my_leds");
printk("探测函数:设备端和驱动端匹配成功\n");
return 0;
}
int myled_remove(struct platform_device *pdev)
{
printk("移除函数运行\n");
device_destroy(myclass,dev);//销毁设备节点
class_destroy(myclass);//销毁设备类
cdev_del(&mydev);//删除字符设备
unregister_chrdev_region(dev,1);//释放设备号
printk("设备注销成功\n");
gpio_free(led[0]);
gpio_free(led[1]);
return 0;
}
const struct of_device_id mydev_node={
.compatible = "xyd_led",
};
struct platform_driver drv = {
.probe = my_probe,
.remove = myled_remove,
.driver = {
.name = "my_leds",
.of_match_table = &mydev_node
},
};
static int __init mydriver_init(void)
{
platform_driver_register(&drv);
return 0;
}
static void __exit mydriver_exit(void)
{
platform_driver_unregister(&drv);
}
module_init(mydriver_init);
module_exit(mydriver_exit);
MODULE_LICENSE("GPL");
应用端:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
int fd =0;
char buffer[100];
const char *data = "Hello, this is a test write!";
if(argc<2)
{
printf("请输入正确的参数\n");
return -1;
}
fd = open(argv[1],O_RDWR);
if(fd<0)
{
perror("open");
return -1;
}
while(1)
{
fd = open(argv[1],O_RDWR); // --- 底层的open函数
sleep(1);
close(fd);//底层的close
sleep(1);
}
return 0;
}