在Linux内核中所有总线驱动都遵从设备驱动的模型,总线驱动的模型如下图:
内核在设计这些总线驱动模型的时候将一个驱动分为了三个部分device、bus、driver。
device是用来描述硬件设备的,bus是总线用来链接device和driver,driver是用来描述
驱动的对象。在内核中所有的device放在内核的klist_devices的链表中管理,而内核中
所有的driver放在klist_driver中管理。内核中的device和driver通过bus完成关联。
当device和driver匹配成功之后执行驱动的probe函数,在probe函数中就可以完成
操作硬件了。当卸载任何一方驱动的时候都会执行驱动中的remove函数。
platform总线驱动遵从设备模型,platform是Linux内核抽象出来的软件代码,并没
有真实的总线协议与之对应。platform总线驱动的思想就是要将设备信息和设备驱动
进行分离。platform_device和platform_driver通过总线匹配成功之后会执行驱动中
设备端:
1.分配并初始化对象
struct platform_device {
const char *name; //用于匹配的名字
int id; //总线号 PLATFORM_DEVID_AUTO 自动分配总线号
struct device dev; //父类
u32 num_resources; //资源的个数
struct resource *resource; //资源的首地址
};
struct device { //父类
void (*release)(struct device *dev); //释放资源
}
struct resource { //设备信息结构体
resource_size_t start; //资源起始值 0x50006000 0xc0008000 71
resource_size_t end; //资源的结束值 0x50006000+3 0xc0008000+30 71
unsigned long flags; //资源的类型 IORESOURCE_IO IORESOURCE_MEM IORESOURCE_IRQ
};
2.注册、注销
platform_device_register(struct platform_device *); //注册
platform_device_unregister(struct platform_device *); //注销
驱动端:
1.分配并初始化对象
struct platform_driver {
int (*probe)(struct platform_device *);
//匹配成功执行的函数
int (*remove)(struct platform_device *);
//分离的时候执行的函数
struct device_driver driver;
//父类
const struct platform_device_id *id_table;
//2.idtable匹配
};
struct device_driver {
const char *name; //1.名字匹配
const struct of_device_id *of_match_table; //3.设备树匹配
}
2.注册、注销
platform_driver_register(drv); //注册
platform_driver_unregister(struct platform_driver *);//注销
3.驱动中一键注册注销的宏
module_platform_driver(__platform_driver)
module_driver(__platform_driver, platform_driver_register,platform_driver_unregister)
#define module_driver(pdrv, __register, __unregister, ...)
static int __init pdrv_init(void)
{
return platform_driver_register(&pdrv );
}
module_init(pdrv_init);
static void __exit pdrv_exit(void)
{
platform_driver_unregister(&pdrv);
}
module_exit(pdrv_exit);
pdev.c
#include
#include
#include
struct resource res[] = {
[0] = {
.start = 0x12345678,
.end = 0x12345678+49,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 71,
.end = 71,
.flags = IORESOURCE_IRQ,
},
};
void pdev_release(struct device *dev)
{
printk("%s:%d\n",__func__,__LINE__);
}
struct platform_device pdev = {
.name = "hahahaha",
.id = PLATFORM_DEVID_AUTO,
.dev = {
.release = pdev_release,
},
.num_resources = ARRAY_SIZE(res),
.resource = res,
};
static int __init pdev_init(void)
{
return platform_device_register(&pdev);
}
static void __exit pdev_exit(void)
{
platform_device_unregister(&pdev);
}
module_init(pdev_init);
module_exit(pdev_exit);
MODULE_LICENSE("GPL");
pdrv.c
#include
#include
#include
int pdrv_probe(struct platform_device* pdev)
{
printk("%s:%d\n", __func__, __LINE__);
return 0;
}
int pdrv_remove(struct platform_device* pdev)
{
printk("%s:%d\n", __func__, __LINE__);
return 0;
}
struct platform_driver pdrv = {
.probe = pdrv_probe,
.remove = pdrv_remove,
.driver = {
.name = "hahahaha",
},
};
module_platform_driver(pdrv);
MODULE_LICENSE("GPL");
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int index)
功能:在设备驱动中获取设备信息的函数
参数:
@dev:设备端的结构体指针
@type:资源的类型
@index:同类型资源的编号
返回值:成功返回结构体指针,失败返回NULL
int platform_get_irq(struct platform_device *dev, unsigned int index)
功能:获取中断类型的资源
参数:
@dev:设备端的结构体指针
@index:中断类型资源的编号
返回值:成功返回中断号,失败返回错误码
pdrv.c
#include
#include
#include
struct resource *res;
int irqno;
int pdrv_probe(struct platform_device* pdev)
{
printk("%s:%d\n", __func__, __LINE__);
//获取IORESOURCE_MEM类型的资源
res = platform_get_resource(pdev,IORESOURCE_MEM,0);
if(res == NULL){
printk("platform get resource error\n");
return -ENODATA;
}
//获取中断类型的资源
irqno = platform_get_irq(pdev,0);
if(irqno < 0){
printk("get irq resource error\n");
return irqno;
}
printk("addr = %#llx,irqno = %d\n",res->start,irqno);
return 0;
}
int pdrv_remove(struct platform_device* pdev)
{
printk("%s:%d\n", __func__, __LINE__);
return 0;
}
struct platform_driver pdrv = {
.probe = pdrv_probe,
.remove = pdrv_remove,
.driver = {
.name = "hahahaha",
},
};
module_platform_driver(pdrv);
MODULE_LICENSE("GPL");
内核中的驱动有的时候需要支持一系列设备,这一系列设备的名就可以放在idtable中。
不管用户使用的那个设备只要和idtable中的名字匹配成功都需要执行驱动中的probe函数。
如果驱动中写了idtable和name.先按照idtable匹配,如果匹配不上在按照name匹配。
name可以不用于匹配,但必须填充。
struct platform_device_id {
char name[PLATFORM_NAME_SIZE]; //名字
kernel_ulong_t driver_data; //给驱动传递的数据
};
struct platform_device_id idtable[] = {
{"hello0",0},
{"hello1",1},
{"hello2",2},
{"hello3",3},
{}, //如果前面的成员都没有匹配上从这里退出
};
struct platform_driver pdrv = {
.probe = pdrv_probe,
.remove = pdrv_remove,
.driver = {
.name = "hahahaha",
},
.id_table = idtable,
};
#include
#include
#include
#include
struct resource *res, *res1;
int irqno;
int pdrv_probe(struct platform_device* pdev)
{
printk("%s:%d\n", __func__, __LINE__);
printk("data = %d\n",pdev->id_entry->driver_data);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
printk("platform get resource error\n");
return -ENODATA;
}
res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (res1 == NULL) {
printk("platform get resource error\n");
return -ENODATA;
}
irqno = platform_get_irq(pdev, 0);
if (irqno < 0) {
printk("get irq resource error\n");
return irqno;
}
printk("addr = %#llx,addr1=%#llx,irqno = %d\n", res->start,res1->start,irqno);
return 0;
}
int pdrv_remove(struct platform_device* pdev)
{
printk("%s:%d\n", __func__, __LINE__);
return 0;
}
struct platform_device_id idtable[] = {
{"hello0",0},
{"hello1",1},
{"hello2",2},
{"hello3",3},
{}, //如果前面的成员都没有匹配上从这里退出
};
struct platform_driver pdrv = {
.probe = pdrv_probe,
.remove = pdrv_remove,
.driver = {
.name = "hahahaha",
},
.id_table = idtable,
};
module_platform_driver(pdrv);
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(总线类型, idtable数组名);
完成热插拔效果的宏
效果演示:
1.将pdev.ko 和pdrv.ko放到如下目录
/lib/modules/5.4.0-104-generic/kernel/drivers/platform
2.让系统重新扫描目录
sudo depmod -a
3.重启ubuntu系统
sudo reboot
在带设备树的内核版本中platform_device描述的设备信息都被放到了设备树中。
所以platform_device的驱动就不用写了。如果使用platform的设备树的匹配的方式,
只需要填写设备树,并编写platform_driver的代码即可。
myplatform{
compatible = "hqyj,myplatform"; //<====必须填充
reg = <0x12345678 0x31>;
interrupt-parent = <&gpiof>;
interrupts = <9 0>;
};
struct of_device_id {
char name[32];
char type[32];
char compatible[128]; //一般选择这种匹配方式
const void *data;
};
struct of_device_id oftable[] = {
{.compatible = "hqyj,myplatform",},
{} //不能省略
};
MODULE_DEVICE_TABLE(of, oftable);
struct platform_driver pdrv = {
.probe = pdrv_probe,
.remove = pdrv_remove,
.driver = {
.name = "hahahaha",
.of_match_table = oftable,
},
};
#include
#include
#include
#include
#include
// myplatform{
// compatible = "hqyj,myplatform"; //<====必须填充
// reg = <0x12345678 0x31>;
// interrupt-parent = <&gpiof>;
// interrupts = <9 0>;
// led1 = <&gpioe 10 0>; //自己的键值对
// };
struct resource *res;
int irqno;
int pdrv_probe(struct platform_device* pdev)
{
printk("%s:%d\n", __func__, __LINE__);
//解析自己的属性
//gpiod_get_from_of_node(pdev->dev.of_node,"led1",0,GPIOD_OUT_LOW,NULL);
//IORESOURCE_MEM获取设备树中的reg
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
printk("platform get resource error\n");
return -ENODATA;
}
//获取设备树中的中断号
irqno = platform_get_irq(pdev, 0);
if (irqno < 0) {
printk("get irq resource error\n");
return irqno;
}
printk("addr = %#x,irqno = %d\n", res->start,irqno);
return 0;
}
int pdrv_remove(struct platform_device* pdev)
{
printk("%s:%d\n", __func__, __LINE__);
return 0;
}
struct of_device_id oftable[] = {
{.compatible = "hqyj,myplatform",},
{} //不能省略
};
MODULE_DEVICE_TABLE(of, oftable);
struct platform_driver pdrv = {
.probe = pdrv_probe,
.remove = pdrv_remove,
.driver = {
.name = "hahahaha",
.of_match_table = oftable,
},
};
module_platform_driver(pdrv);
MODULE_LICENSE("GPL");
1.基于platform驱动实现如下要求 //platform总线驱动
a.应用程序通过阻塞的io模型来读取status变量的值 //等待队列
b.status是内核驱动中的一个变量,代表LED1的状态
c.status的值随着按键按下而改变(按键中断) //按键中断
例如status=0 按下按键status=1 ,再次按下按键status=0
d.在按下按键的时候需要同时将led1的状态取反 //gpio子系统
e.驱动中需要编写字符设备驱动 //字符设备驱动
f.驱动中需要自动创建设备节点 //自动创建设备节点
g.这个驱动需要的所有设备信息放在设备树的同一个节点中 //设备树
platform_irq_led.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// myplatform{
// compatible = "hqyj,myplatform"; //<====必须填充
// reg = <0x12345678 0x31>;
// interrupt-parent = <&gpiof>;
// interrupts = <9 0>;
// led1 = <&gpioe 10 0>; //自己的键值对
// };
#define CNAME "platform_irq_led"
struct device_node* node;
struct gpio_desc* desc;
unsigned int irqno;
int major;
const int count = 1;
struct class* cls;
struct device* dev;
wait_queue_head_t wq;
int condition = 0;
int status = 0;
irqreturn_t platform_irq_led_handle(int irq, void* dev)
{
//取反灯的状态
status = gpiod_get_value(desc);
status = !status;
gpiod_set_value(desc, status);
//唤醒阻塞
condition = 1;
wake_up_interruptible(&wq);
return IRQ_HANDLED;
}
int gpio_irq_init(struct device_node* node)
{
int ret;
//获取并初始化gpio
desc = gpiod_get_from_of_node(node, "led1", 0, GPIOD_OUT_LOW, NULL);
if (IS_ERR(desc)) {
printk("get gpio error\n");
ret = PTR_ERR(desc);
goto ERR1;
}
//获取并注册中断
// irqno = platform_get_irq(pdev, 0);
irqno = irq_of_parse_and_map(node, 0);
if (irqno == 0) {
printk("parse irqno error\n");
ret = -EINVAL;
goto ERR2;
}
ret = request_irq(irqno, platform_irq_led_handle,
IRQF_TRIGGER_FALLING, CNAME, NULL);
if (ret) {
printk("request irq error\n");
goto ERR2;
}
return 0;
ERR2:
gpiod_put(desc);
ERR1:
return ret;
}
void gpio_irq_deinit(void)
{
free_irq(irqno, NULL);
gpiod_put(desc);
}
int platform_irq_led_open(struct inode* inode, struct file* file)
{
return 0;
}
ssize_t platform_irq_led_read(struct file* filp,
char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
// 1.判断用户是否是阻塞打开
if (filp->f_flags & O_NONBLOCK) {
return -EINVAL;
} else {
// 2.阻塞
ret = wait_event_interruptible(wq, condition);
if (ret) {
printk("receive signal....\n");
return ret;
}
}
// 3.将灯的状态返回到用户空间
if (size > sizeof(status))
size = sizeof(status);
ret = copy_to_user(ubuf, &status, size);
if (ret) {
printk("copy data to user error\n");
return -EIO;
}
//4.条件清零
condition = 0;
return size;
}
int platform_irq_led_close(struct inode* inode, struct file* file)
{
return 0;
}
static struct file_operations fops = {
.open = platform_irq_led_open,
.read = platform_irq_led_read,
.release = platform_irq_led_close,
};
int platform_irq_led_probe(struct platform_device* pdev)
{
int ret;
printk("%s:%d\n", __func__, __LINE__);
// 1.初始化led和irq
if ((ret = gpio_irq_init(pdev->dev.of_node)) != 0)
return ret;
// 2.注册字符设备驱动
major = register_chrdev(0, CNAME, &fops);
if (major < 0) {
printk("register chrdev error\n");
ret = -EAGAIN;
goto ERR1;
}
// 3.创建设备节点
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls)) {
printk("class create error\n");
ret = PTR_ERR(cls);
goto ERR2;
}
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, CNAME);
if (IS_ERR(dev)) {
printk("device create error\n");
ret = PTR_ERR(dev);
goto ERR3;
}
//初始化等待队列头
init_waitqueue_head(&wq);
return 0;
ERR3:
class_destroy(cls);
ERR2:
unregister_chrdev(major, CNAME);
ERR1:
gpio_irq_deinit();
return ret;
return 0;
}
int platform_irq_led_remove(struct platform_device* pdev)
{
printk("%s:%d\n", __func__, __LINE__);
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, CNAME);
gpio_irq_deinit();
return 0;
}
struct of_device_id oftable[] = {
{
.compatible = "hqyj,myplatform",
},
{} //不能省略
};
MODULE_DEVICE_TABLE(of, oftable);
struct platform_driver platform_irq_led = {
.probe = platform_irq_led_probe,
.remove = platform_irq_led_remove,
.driver = {
.name = "hahahaha",
.of_match_table = oftable,
},
};
module_platform_driver(platform_irq_led);
MODULE_LICENSE("GPL");
test.c
#include
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
int fd,status;
if((fd = open("/dev/platform_irq_led",O_RDWR)) < 0){
perror("open error");
exit(EXIT_FAILURE);
}
while(1){
read(fd,&status,sizeof(status));
printf("status = %d\n",status);
}
close(fd);
return 0;
}