总线设备驱动模型
整体框架

无论先注册平台设备还是平台驱动,程序都会去bus里面找对应的链表,根据匹配规则进行设备和驱动的匹配,当成功后就会调用驱动的probe函数,在probe函数中自己可以注册设备分配自己的file_operotions结构体,并完成硬件的初始化工作。
匹配规则
比较
platform_device.driver_override和platform_driver.driver.name可以设置platform_device .driver_override,强制选择某个platform_driver。然后比较:
platform_ device.name和platform_driver.id_table[i].nameplatform_driver.id_table是struct platform_device_id类型的指针,表示该 drv 支持若干个 device ,它里面 列出了各个 device 的{ .name; //表示该 dv 支持的设备的名字 .driver_data; //提供给该 d evice 的私有数据 }
最后比较:
platform_device.name和platform_driver.driver.nameplatform_driver.id_table可能为空,这时可以根据platform_driver.driver.name来寻找同名的platform_device。
平台设备注册函数调用关系
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 // 调用 d rv 的 p robe
平台驱动注册函数调用关系
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 // 调用 d rv 的 p robe
常用函数
drivers/base/platform.c
设备相关
platform_device_register\platform_device_unregister
platform_add_devices// 注册多个 device
驱动相关
platform_driver_register\platform_driver_unregister
获取资源相关
返回该dev 某种类型(type)的资源中的第几个(num)资源
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
eg:获取中断内存资源
platform_get_resource(dev, IORESOURCE_MEM, i)
返回该dev所用的第几个(num) 中断:
int platform_get_irq(struct platform_device *dev, unsigned int num)
通过名字返回该dev的某类型的资源
struct resource *platform_get_resource_byname(struct platform_device *dev,
unsigned int type,
const char *name)
通过名字返回该dev的中断号
int platform_get_irq_byname(struct platform_device *dev, const char *name)
总线设备驱动模型步骤
平台设备
根据硬件,构建自己的设备资源信息resource,也就是LED0所使用的所有寄存器
构建自己的platform_device设备,把自己的设备资源信息resource,填充进平台设备
入口函数中,注册自己的平台设备platform_device到总线上的平台设备链表中注册的过程中会根据匹配规则 到【总线平台驱动链表】里面找,如果匹配成功会调用驱动里面的platform_driver下的probe函数 。
release函数:当卸载对端即【平台驱动】时候调用该函数,
出口函数:如果用户卸载该驱动的时候会,会到这里,把平台设备从总线中去掉
参见代码【03.led_driver_bus_dev_drv】工程下的【led_dev.c】文件,如下:
#define CCM_CCGR1_BASE (0x20C406C)
#define IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 (0x2290014)
#define GPIO5_DR_BASE (0x020AC000)
#define GPIO5_GDIR_BASE (0x020AC000+0x4)
#define REGISTER_LENGTH 4
static void led_release(struct device *dev)
{
/*
* 4:当卸载对端即【平台驱动】时候调用该函数,
*/
printk("led device released!\r\n");
}
/*
* 1:根据硬件,构建自己的设备资源信息resource,也就是LED0所使用的所有寄存器
*/
static struct resource led_resources[] = {
[0] = {
.start = CCM_CCGR1_BASE,
.end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3,
.end = (IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[2] = {
.start = GPIO5_DR_BASE,
.end = (GPIO5_DR_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[3] = {
.start = GPIO5_GDIR_BASE,
.end = (GPIO5_GDIR_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
};
/*
* 2:构建自己的platform_device设备,把自己的设备资源信息resource,填充进平台设备
*/
static struct platform_device leddevice = {
.name = "xym-led",
.id = -1,
.dev = {
.release = &led_release,
},
.num_resources = ARRAY_SIZE(led_resources),
.resource = led_resources,
};
static int __init leddevice_init(void)
{
/*
* 3:入口函数中,注册自己的平台设备platform_device到总线上的平台设备链表中
* 注册的过程中会根据匹配规则 到【总线平台驱动链表】里面找,
* 如果匹配成功会调用驱动里面的platform_driver下的probe函数
*/
return platform_device_register(&leddevice);
}
static void __exit leddevice_exit(void)
{
/*
* 5:如果用户卸载该驱动的时候会,会到这里,把平台设备从总线中去掉
*/
platform_device_unregister(&leddevice);
}
module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xyan_m@163.com");
平台驱动
构建自己的platform驱动结构体
在入口函数,注册自己的 platform_driver 到平台的总线驱动链表里面注册的过程中会根据匹配规则 到【总线平台设备链表】里面找,如果匹配成功会调用驱动里面的platform_driver下的probe函数
匹配成功后会调用probe,在probe首先获取平台设备驱动里面的platform_device资源
在probe函数里面,剩下的又回到新字符设备那一套了
自己创建struct file_operations结构体fp
在probe函数中,申请设备号,把fp占着该设备号对应的槽
初始化cdev 并添加到内核
创建类
在类下创建设备节点
当卸载对端即【平台设备】时候调用led_remove函数,倒着来,删除一遍
如果用户卸载该驱动的时候,会调用出口函数,把平台驱动从总线中去掉
参见代码【03.led_driver_bus_dev_drv】工程下的【led_drv.c】文件,如下:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "board_fire_imx6ull_pro.h"
struct led_char_dev {
dev_t devid; /* 字符ID */
struct cdev cdev; /* 字符设备 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct hw_led_res res[4]; /* led的平台资源 */
};
struct led_char_dev g_led_dev; /* 定义led设备 */
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)
{
unsigned long err;
uint8_t status = 0;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(&status, buf, 1);
/* 根据次设备号和status控制LED */
hw_led_write(0, status);
return 1;
}
static int led_drv_open (struct inode *node, struct file *file)
{
hw_led_init(g_led_dev.res);
file->private_data = &g_led_dev;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int led_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
hw_led_reinit();
return 0;
}
/*
* 4.1:自己创建struct file_operations结构体fp
*/
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
static int led_probe(struct platform_device *dev)
{
printk("led driver and device has matched,get res!\r\n");
int i;
/*
* 3:匹配成功后会调用,首先获取平台设备驱动里面的platform_device资源
*/
for (i = 0; i < 4; i++) {
g_led_dev.res[i].p_res = platform_get_resource(dev, IORESOURCE_MEM, i);
if (!g_led_dev.res[i].p_res) {
dev_err(&dev->dev, "No MEM resource for always on\n");
return -ENXIO;
}
g_led_dev.res[i].size = resource_size(g_led_dev.res[i].p_res);
}
/*
* 4:剩下的又回到新字符设备那一套了
*/
/*
* 4.2:在probe函数中,申请设备号,把fp占着该设备号对应的槽
*/
if(g_led_dev.major){
g_led_dev.devid = MKDEV(g_led_dev.major, 0);
register_chrdev_region(g_led_dev.devid, 1, "xym_led");
}else{
alloc_chrdev_region(&g_led_dev.devid, 0, 1, "xym_led"); /* 申请设备号 */
g_led_dev.major = MAJOR(g_led_dev.devid); /* 获取主设备号 */
g_led_dev.minor = MINOR(g_led_dev.devid); /* 获取次设备号 */
}
/*
* 4.3:初始化cdev 并添加到内核
*/
g_led_dev.cdev.owner = THIS_MODULE;
cdev_init(&g_led_dev.cdev, &led_fops);
cdev_add(&g_led_dev.cdev, g_led_dev.devid, 1);
/*
* 4.4:创建类
*/
g_led_dev.class = class_create(THIS_MODULE, "xym_led_class");
if (IS_ERR(g_led_dev.class)) {
return PTR_ERR(g_led_dev.class);
}
/*
* 4.5:在类下创建设备节点
*/
g_led_dev.device = device_create(g_led_dev.class, NULL, g_led_dev.devid, NULL, "xym_led");
return PTR_ERR(g_led_dev.device);
}
printk("%s %s line %d:led_probe !\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int led_remove(struct platform_device *dev)
{
/*
* 5:当卸载对端即【平台设备】时候调用该函数,倒着来,删除一遍
*/
hw_led_reinit();
cdev_del(&g_led_dev.cdev); /* 删除字符设备 */
unregister_chrdev_region(g_led_dev.devid, 1); /* 取消注册的字符设备释放槽 */
device_destroy(g_led_dev.class, g_led_dev.devid); /* 删除设备节点 */
class_destroy(g_led_dev.class); /* 删除类 */
return 0;
}
/*
* 1:构建自己的platform驱动结构体
*/
static struct platform_driver platform_driver_led = {
.driver = {
.name = "xym-led", /* 驱动名字,用于和设备匹配 */
},
.probe = led_probe,
.remove = led_remove,
};
static int __init led_init(void)
{
/*
* 2:注册自己的 platform_driver 到平台的总线驱动链表里面
* 注册的过程中会根据匹配规则 到【总线平台设备链表】里面找,
* 如果匹配成功会调用驱动里面的platform_driver下的probe函数
*/
printk("%s %s line %d:insmod !\n", __FILE__, __FUNCTION__, __LINE__);
return platform_driver_register(&platform_driver_led);
}
static void __exit led_exit(void)
{
/*
* 6:如果用户卸载该驱动的时候,会到这里,把平台驱动从总线中去掉
*/
printk("%s %s line %d: rmmod ! \n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&platform_driver_led);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xym_@163.com");