https://www.cnblogs.com/lifexy/p/7569371.html
自己理解:
主要是把设备和驱动进行分离:
device:主要是硬件描述相关。地址,中断等等;
driver:主要是驱动硬件工作,连接硬件与上层应用的桥梁;
device与driver就通过某种机制进行连接起来。就是通过platform_bus_type,把驱动和设备联系起来。
platform_bus_type是bus type的一种,其定义如下:
struct bus_type platform_bus_type={
.name = "platform",
.dev_attrs = platfrom_dev_attrs,
.uevent = platfrom_uevent,
.match = platform_match,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume = platform_resume,
.resume_early = platform_resume_early,
}
只要有一方注册,就会调用platform_bus_type的.match匹配函数,来找对方,成功就调用driver驱动结构体里的.probe函数来使总线将设备和驱动联系起来
device:先声明(定义),再注册
driver:先声明(定义),在注册
platform_driver_register()函数,主要是注册到platforn_bus_type虚拟总线上,如下示例:
int platform_driver_register(struct platform_driver *drv) {
drv->driver.bus = &platform_bus_type; //(1)挂接到虚拟总线
platform_bus_type上 if (drv->probe) drv->driver.probe = platform_drv_probe;
if (drv->remove) drv->driver.remove = platform_drv_remove;
if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend) drv->driver.suspend = platform_drv_suspend;
if (drv->resume) drv->driver.resume = platform_drv_resume;
return driver_register(&drv->driver); //(2) 注册到driver目录下 }
挂接到虚拟总线platform_bus_type上,然后会调用platform_bus_type下的platform_match匹配函数,来匹配device和driver的名字
若名字匹配成功,则调用driver的.probe成员函数,然后放到/sys/bus/platform/driver目录下,其中driver_register()函数就是用来创建dirver目录的
定义:
platform_device{
name //设备名称,要与platform_driver的name一样,这样总线才能匹配成功
id //id号,插入总线下相同name的设备编号(一个驱动可以有多个设备),如果只有一个设备填-1
dev //内嵌的具体的device结构体,其中成员platform_data,是个void *类型,可以给平台driver提供各种数据(比如:GPIO引脚等等)
num_resource //资源数量,
resource //资源结构体,保存设备的信息
}
resource{
start //起始资源,如果是地址的话,必须是物理地址
end //结束资源,如果是地址的话,必须是物理地址
name //资源名
flag //资源的标志,比如IORESOURCE_MEM,表示地址资源, IORESOURCE_IRQ表示中断引脚... ...
struct resource *parent, *sibling, *child; //资源拓扑指针父、兄、子,可以构成链表
}
完成定义后,进行实例填充,比如
led_resource[]={
[0] = {
.start = 0x56000050, //led的寄存器GPFCON起始地址
.end = 0x56000050 + 8 - 1, // led的寄存器GPFDAT结束地址
.flags = IORESOURCE_MEM, //表示地址资源
},
[1] = {
.start = 5, //表示GPF第几个引脚开始
.end = 5, //结束引脚
flags = IORESOURCE_IRQ, //表示中断资源
}
}
led_device={
}
梳理清晰以上信息,我们就可以编写代码把上面的信息给关联起来运用。
led_device.c设备代码的结构如下:
device_init()
device_exit()
module_init()
module_exit()
MODULE_LICENCE()
下面我们再看看driver:
同样需要先定义:驱动主要工作会包括匹配卸载,休眠唤醒;
platform_driver{
.probe
.remove
.suspend
.resume
.driver
}
定义清楚后就需要填充实体:
led_driver={
}
填充完成后就同样编码对上面的信息进行关联起来运用,代码结构如下:
led_driver.c:
driver_init()
driver_exit()
module_init()
module_exit()
MODULE_LICTENCE()
下面请看完整代码:
led_device.c
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
static struct resource led_resource[] = { //资源数组
[0] = {
.start = 0x56000050, //led的寄存器GPFCON起始地址
.end = 0x56000050 + 8 - 1, // led的寄存器GPFDAT结束地址
.flags = IORESOURCE_MEM, //表示地址资源
},
[1] = {
.start = 5, //表示GPF第几个引脚开始
.end = 5, //结束引脚
.flags = IORESOURCE_IRQ, //表示中断资源
}
};
static void led_release(struct device * dev) //释放函数
{}
static struct platform_device led_dev = {
.name = "myled", //对应的platform_driver驱动的名字
.id = -1, //表示只有一个设备
.num_resources = ARRAY_SIZE(led_resource), //资源数量,ARRAY_SIZE()函数:获取数量
.resource = led_resource, //资源数组led_resource
.dev = {
.release = led_release, //释放函数,必须向内核提供一个release函数, 、
//否则卸载时,内核找不到该函数会报错
},
};
static int led_dev_init(void) //入口函数,注册dev设备
{
platform_device_register(&led_dev);
return 0;
}
static void led_dev_exit(void) //出口函数,注销dev设备
{
platform_device_unregister(&led_dev);
}
module_init(led_dev_init); //修饰入口函数
module_exit(led_dev_exit); //修饰出口函数
MODULE_LICENSE("GPL"); //声明函数
led_driver.c
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
/*函数声明*/
static int led_remove(struct platform_device *led_dev);
static int led_probe(struct platform_device *led_dev);
struct platform_driver led_drv = {
.probe = led_probe, //当与设备匹配,则调用该函数
.remove = led_remove, //删除设备
.driver = {
.name = "myled", //与设备名称一样
}
};
static struct class *cls; //类,用来注册,和注销
static volatile unsigned long *gpio_con; //被file_operations的.open函数用
static volatile unsigned long *gpio_dat; //被file_operations的.write函数用
static int pin; //LED位于的引脚值
static int led_open(struct inode *inode, struct file *file)
{
*GPFcon&=~(0x03<<(LED_PIN*2));
*GPFcon|=(0x01<<(LED_PIN*2));
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val=0;
if(count!=1)
return -EINAL;
copy_from_user(&val,buf,count); //从用户(应用层)拷贝数据
if(val) //开灯
{
*GPFdat&=~(0x1<<LED_PIN);
}
else
{
*GPFdat |= (0x1<<LED_PIN);
}
return 0 ;
}
static struct file_operations led_fops= {
.owner = THIS_MODULE, //被使用时阻止模块被卸载
.open = led_open,
.write = led_write,
};
static int led_probe(struct platform_device *pdev)
{
struct resource *res;
printk("enter probe\n");
/* 根据platform_device的资源进行ioremap */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取寄存器地址
gpio_con = ioremap(res->start, res->end - res->start + 1); //获取虚拟地址
gpio_dat = gpio_con + 1;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //获取引脚值
pin = res->start;
/* 注册字符设备驱动程序 */
major = register_chrdev(0, "myled", &led_fops); //赋入file_operations结构体
cls = class_create(THIS_MODULE, "myled");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
return 0;
}
static int led_remove(struct platform_device *pdev)
{
/* 卸载字符设备驱动程序 */
printk("enter remove\n");
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "myled");
iounmap(gpio_con); //注销虚拟地址
return 0;
}
static int led_drv_init(void) //入口函数,注册驱动
{
platform_driver_register(&led_drv);
return 0;
}
static void led_drv_exit(void) //出口函数,卸载驱动
{
platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");