文章目录
1 平台总线式驱动开发引入。
硬编码式的驱动开发带来的问题:
- 垃圾代码太多
- 结构不清晰
- 一些统一设备功能难以支持
- 开发效率低下
所以要解决这些问题需要将设备和驱动分离
struct device来表示一个具体设备,主要提供具体设备相关的资源(如寄存器地址、GPIO、中断等等)
struct device_driver来表示一个设备驱动,一个驱动可以支持多个操作逻辑相同的设备
带来的问题-------怎样将二者进行关联(匹配)?
硬件上同一总线上的设备遵循一致的时序通信,在其基础上增加管理设备和驱动的软件功能 于是引入总线(bus),各种总线的核心框架由内核来实现,通信时序一般由SOC供应商支持内核中用struct bus_type来表示一种总线,总线可以是实际存在的总线,也可以是虚拟总线:
- 实际总线:提供时序通信方式 + 管理设备和驱动
- 虚拟总线:仅用来管理设备和驱动(最核心的作用之一就是完成设备和驱动的匹配)
其次根据设备树,在系统启动时自动产生每个节点对应的设备。
2 基本数据类型
2.1 struct device
struct device
{
struct bus_type *bus; //总线类型
dev_t devt; //设备号
struct device_driver *driver; //设备驱动
struct device_node *of_node;//设备树中的节点,重要
void (*release)(struct device *dev);//删除设备,重要
//.......
};
2.2 struct device_driver
struct device_driver
{
const char *name; //驱动名称,匹配device用,重要
struct bus_type *bus; //总线类型
struct module *owner; //模块THIS_MODULE
const struct of_device_id *of_match_table;//用于设备树匹配 of_match_ptr(某struct of_device_id对象地址) 重要
//......
};
struct of_device_id
{
char name[32];//设备名
char type[32];//设备类型
char compatible[128]; //用于device和driver的match,重点
};
3 platform总线驱动
platform是一种虚拟总线,主要用来管理那些不需要时序通信的设备
3.1 核心数据类型之platform_device
struct platform_device
{
const char *name; //匹配用的名字
int id;//设备id,用于在该总线上同名的设备进行编号,如果只有一个设备,则为-1
struct device dev; //设备模块必须包含该结构体
struct resource *resource;//资源结构体 指向资源数组
u32 num_resources;//资源的数量 资源数组的元素个数
const struct platform_device_id *id_entry;//设备八字
};
struct platform_device_id
{
char name[20];//匹配用名称
kernel_ulong_t driver_data;//需要向驱动传输的其它数据
};
struct resource
{
resource_size_t start; //资源起始位置
resource_size_t end; //资源结束位置
const char *name;
unsigned long flags; //区分资源是什么类型的
};
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_IRQ 0x00000400
/*
flags 指资源类型,我们常用的是 IORESOURCE_MEM、IORESOURCE_IRQ 这两种。start 和 end 的含义会随着 flags而变更,如
a -- flags为IORESOURCE_MEM 时,start 、end 分别表示该platform_device占据的内存的开始地址和结束值;注意不同MEM的地址值不能重叠
b -- flags为 IORESOURCE_IRQ 时,start 、end 分别表示该platform_device使用的中断号的开始地址和结束值
*/
/**
*注册:把指定设备添加到内核中平台总线的设备列表,等待匹配,匹配成功则回调驱动中probe;
*/
int platform_device_register(struct platform_device *);
/**
*注销:把指定设备从设备列表中删除,如果驱动已匹配则回调驱动方法和设备信息中的release;
*/
void platform_device_unregister(struct platform_device *);
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
/*
功能:获取设备资源
参数:dev:平台驱动
type:获取的资源类型
num:对应类型资源的序号(如第0个MEM、第2个IRQ等,不是数组下标)
返回值:成功:资源结构体首地址,失败:NULL
*/
3.2 核心数据类型之platform_driver
struct platform_driver
{
int (*probe)(struct platform_device *);//设备和驱动匹配成功之后调用该函数
int (*remove)(struct platform_device *);//设备卸载了调用该函数
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;//内核里所有的驱动必须包含该结构体
const struct platform_device_id *id_table; //能够支持的设备八字数组,用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束
};
int platform_driver_register(struct platform_driver*pdrv);
/*
功能:注册平台设备驱动
参数:pdrv:平台设备驱动结构体
返回值:成功:0
失败:错误码
*/
void platform_driver_unregister(struct platform_driver*pdrv);
4 platform的三种匹配方式
4.1 名称匹配
一个驱动只对应一个设备 ----- 优先级最低
4.1.2名称匹配驱动基础框架
hello_device.c (设备)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
//做清理工作
void hello_dev_release(struct device *pdev)
{
printk("hello_dev_release is called\n");
}
//资源
struct resource hello_dev_res[]=
{
[0]={.start=0x1000,.end=0x1003,.name="reg1",.flags=IORESOURCE_MEM},
[1]={.start=0x2000,.end=0x2003,.name="reg2",.flags=IORESOURCE_MEM},
[2]={.start=10,.end=10,.name="irq1",.flags=IORESOURCE_IRQ},
[3]={.start=0x3000,.end=0x3003,.name="reg3",.flags=IORESOURCE_MEM},
[4]={.start=100,.end=100,.name="irq2",.flags=IORESOURCE_IRQ},
[5]={.start=62,.end=62,.name="irq3",.flags=IORESOURCE_IRQ},
};
struct platform_device hello_device=
{
//用于名称匹配
.name="hello",
//设备回收时会被调用
.dev.release=hello_dev_release,
.resource=hello_dev_res,
//计算资源个数
.num_resources=ARRAY_SIZE(hello_dev_res),
};
//初始化
int __init hello_device_init(void)
{
platform_device_register(&hello_device);
return 0;
}
//退出
void __exit hello_device_exit(void)
{
platform_device_unregister(&hello_device);
}
MODULE_LICENSE("GPL");
module_init(hello_device_init);
module_exit(hello_device_exit);
hello_driver.c(驱动代码)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
//匹配成功将会被调用
int hello_driver_probe(struct platform_device *p_pltdev)
{
struct resource *pres =NULL;
printk("hello_driver_probe is called\n");
//取第3个IORESOURCE_MEM资源
pres=platform_get_resource(p_pltdev,IORESOURCE_MEM,2);
printk("res.start=0x%d\n",(unsigned int)pres->start);
//取第2个IORESOURCE_IRQ资源
pres=platform_get_resource(p_pltdev,IORESOURCE_IRQ,1);
printk("res.start=0x%d\n",(unsigned int)pres->start);
return 0;
}
//匹配失败会被调用
int hello_driver_remove(struct platform_device *p_pltdev)
{
printk("hello_driver_remove is called\n");
return 0;
}
struct platform_driver hello_driver=
{
.driver.name="hello",
.probe=hello_driver_probe,
.remove=hello_driver_remove,
};
int __init hello_driver_init(void)
{
platform_driver_register(&hello_driver);
return 0;
}
void __exit hello_driver_exit(void)
{
platform_driver_unregister(&hello_driver);
}
MODULE_LICENSE("GPL");
module_init(hello_driver_init);
module_exit(hello_driver_exit);
4.2 id匹配(可想象成八字匹配)
一个驱动可以对应多个设备 ------优先级次低 device模块中,id的name成员必须与struct platform_device中的name成员内容一致 因此device模块中,struct platform_device中的name成员必须指定 driver模块中,struct platform_driver成员driver的name成员必须指定,但与device模块中name可以不相同。
4.3 设备树匹配
内核启动时根据设备树自动产生的设备 ------ 优先级最高 使用compatible属性进行匹配,注意设备树中compatible属性值不要包含空白字符 id_table可不设置,但struct platform_driver成员driver的name成员必须设置。
5 led驱动名称匹配版
fs4412_led.h
#ifndef MY_CHAR_H
#define MY_CHAR_H
#include <asm/ioctl.h>
#define LED_DEV_MAGIC 'g'
//#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
//#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)
#define MY_LED_OFF _IO(LED_DEV_MAGIC,0)
#define MY_LED_ON _IO(LED_DEV_MAGIC,1)
// led2
#define GPX2CON 0x11000C40
#define GPX2DAT 0x11000C44
// led3
#define GPX1CON 0x11000C20
#define GPX1DAT 0x11000C24
//led4,5
#define GPF3CON 0x114001E0
#define GPF3DAT 0x114001E4
#endif
// 这个文件编写的是设备信息(资源等等)
fs4412leds_device.c(设备)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include "fs4412_led.h"
//做清理工作,设备回收时被调用
void fs4412led_dev_release(struct device *pdev)
{
printk("fs4412led_dev_release is called\n");
}
//资源,这里是各个led的各个GPIO的控制寄存器的地址和读写寄存器的地址
struct resource fs4412led_dev_res[]=
{
[0]={.start=GPX1CON,.end=GPX1CON+3,.name="GPX1CON",.flags=IORESOURCE_MEM},
[1]={.start=GPX1DAT,.end=GPX1DAT+3,.name="GPX1DAT",.flags=IORESOURCE_MEM},
[2]={.start=GPX2CON,.end=GPX2CON+3,.name="GPX2CON",.flags=IORESOURCE_MEM},
[3]={.start=GPX2DAT,.end=GPX2DAT+3,.name="GPX2DAT",.flags=IORESOURCE_MEM},
[4]={.start=GPF3CON,.end=GPF3CON+3,.name="GPF3CON",.flags=IORESOURCE_MEM},
[5]={.start=GPF3DAT,.end=GPF3DAT+3,.name="GPF3DAT",.flags=IORESOURCE_MEM},
};
struct platform_device fs4412led_device=
{
//用于名称匹配
.name="fs4412leds",
//设备回收时会被调用
.dev.release=fs4412led_dev_release,
.resource=fs4412led_dev_res,
//计算资源个数
.num_resources=ARRAY_SIZE(fs4412led_dev_res),
};
int __init fs4412led_device_init(void)
{
platform_device_register(&fs4412led_device);
return 0;
}
void __exit fs4412led_device_exit(void)
{
platform_device_unregister(&fs4412led_device);
}
MODULE_LICENSE("GPL");
module_init(fs4412led_device_init);
module_exit(fs4412led_device_exit);
//这个是驱动代码
fs4412leds_driver.c(驱动)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/atomic.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include "fs4412_led.h"
#define fs4412leds_driver_DEV_CNT 1
int major =11;//主设备号
int minor = 0;//次设备号
int fs4412leds_driver_num =fs4412leds_driver_DEV_CNT;//设备数量
struct fs4412leds_driver_dev
{
struct cdev mydev;//建立一个设备对象
//volatile为不做优化
volatile unsigned long * led2con;
volatile unsigned long * led2dat;
volatile unsigned long * led3con;
volatile unsigned long * led3dat;
volatile unsigned long * led4con;
volatile unsigned long * led4dat;
volatile unsigned long * led5con;
volatile unsigned long * led5dat;
struct class *pcls;
struct device *pdev;
};
struct fs4412leds_driver_dev *gmydev_arr[fs4412leds_driver_DEV_CNT];
//打开
int fs4412leds_driver_open(struct inode *pnode,struct file *pfile)
{
/*为避免使用全局变量*/
/*
*container_of是一个宏,可以用来求全局结构体变量的地址
*已知其成员的地址和成员名字,就可以求出其地址
*pnode->i_cdev:指向插入系统的设备对象(这里也是gmydev的成员mydev的地址)
*pnode->private_data:本次打开文件的私有数据,驱动中常来在几个操作函数间传递共用数据
*/
pfile->private_data=(void *)(container_of(pnode->i_cdev,struct fs4412leds_driver_dev,mydev));
return 0;
}
//关闭
int fs4412leds_driver_close(struct inode *pnode,struct file *pfile)
{
return 0;
}
void led_on(struct fs4412leds_driver_dev * pmydev,int ledno)
{
switch(ledno)
{
case 2:
printk("2\n");
writel(readl(pmydev->led2dat) | (0x1<<7),pmydev->led2dat);//关闭led2
break;
case 3:
printk("3\n");
writel(readl(pmydev->led3dat) | (0x1<<0),pmydev->led3dat);//关闭led2
break;
case 4:
printk("4\n");
writel(readl(pmydev->led4dat) | (0x1<<4),pmydev->led4dat);//关闭led2
break;
case 5:
printk("5\n");
writel(readl(pmydev->led5dat) | (0x1<<5),pmydev->led5dat);//关闭led2
break;
}
}
void led_off(struct fs4412leds_driver_dev * pmydev,int ledno)
{
switch(ledno)
{
case 2:
writel(readl(pmydev->led2dat) & (~(1<<7)),pmydev->led2dat);//关闭led2
break;
case 3:
writel(readl(pmydev->led3dat) & (~(1<<0)),pmydev->led3dat);//关闭led3
break;
case 4:
writel(readl(pmydev->led4dat) & (~(1<<4)),pmydev->led4dat);//关闭led4
break;
case 5:
writel(readl(pmydev->led5dat) & (~(1<<5)),pmydev->led5dat);//关闭led5
break;
}
}
long fs4412leds_driver_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)
{
struct fs4412leds_driver_dev * pmydev = (struct fs4412leds_driver_dev*)pfile->private_data;
if(arg<2||arg>5)
{
return -1;
}
switch(cmd)
{
case MY_LED_ON:
led_on(pmydev,arg);
break;
case MY_LED_OFF:
led_off(pmydev,arg);
break;
default:
return -1;
}
return 0;
}
struct file_operations myops={
.owner=THIS_MODULE,
.open=fs4412leds_driver_open,
.unlocked_ioctl=fs4412leds_driver_ioctl,
.release =fs4412leds_driver_close,
};
/**
* @brief 将寄存器物理地址映射为虚拟地址
* @param[in] pmydev:设备对象结构体
* @retval NULL
*
*/
void ioremap_ledreg(struct fs4412leds_driver_dev *pmydev,struct platform_device *p_pltdev)
{
/*得到虚拟内存地址*/
struct resource *pres=NULL;
//获取相应资源
pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,2);
pmydev->led2con=ioremap(pres->start,4);
pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,3);
pmydev->led2dat=ioremap(pres->start,4);
pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,0);
pmydev->led3con=ioremap(pres->start,4);
pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,1);
pmydev->led3dat=ioremap(pres->start,4);
pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,4);
pmydev->led4con=ioremap(pres->start,4);
pres =platform_get_resource(p_pltdev,IORESOURCE_MEM,5);
pmydev->led4dat=ioremap(pres->start,4);
pmydev->led5con=pmydev->led4con;
pmydev->led5dat=pmydev->led4dat;
}
/**
* @brief 将寄存器物理地址映射为虚拟地址
* @param[in] pmydev:设备对象结构体
* @retval NULL
*
*/
void set_output_ledconreg(struct fs4412leds_driver_dev *pmydev)
{
writel((readl(pmydev->led2con) & (~(0xF<<28))) | (0x1<<28),pmydev->led2con);//设置GPX2_7为输出模式
writel((readl(pmydev->led3con) & (~(0xF<<0))) | (0x1<<0),pmydev->led3con);//设置GPX1_0为输出模式
writel((readl(pmydev->led4con) & (~(0xF<<16))) | (0x1<<16),pmydev->led4con);//设置GPF3_4为输出模式
writel((readl(pmydev->led5con) & (~(0xF<<20))) | (0x1<<20),pmydev->led5con);//设置GPF3_5为输出模式
writel(readl(pmydev->led2dat) & (~(1<<7)),pmydev->led2dat);//关闭led2
writel(readl(pmydev->led3dat) & (~(1<<0)),pmydev->led3dat);//关闭led3
writel(readl(pmydev->led4dat) & (~(1<<4)),pmydev->led4dat);//关闭led4
writel(readl(pmydev->led5dat) & (~(1<<5)),pmydev->led5dat);//关闭led5
}
/**
* @brief 解除寄存器物理地址和虚拟地址映射
* @param[in] pmydev:设备对象结构体
* @retval NULL
*
*/
void iounremap_ledreg(struct fs4412leds_driver_dev *pmydev)
{
iounmap(pmydev->led2con);
pmydev->led2con=NULL;
iounmap(pmydev->led2dat);
pmydev->led2dat=NULL;
iounmap(pmydev->led3con);
pmydev->led3con=NULL;
iounmap(pmydev->led3dat);
pmydev->led3dat=NULL;
iounmap(pmydev->led4con);
pmydev->led4con=NULL;
iounmap(pmydev->led4dat);
pmydev->led4dat=NULL;
pmydev->led5con=NULL;
pmydev->led5dat=NULL;
}
//初始化
int fs4412leds_driver_probe(struct platform_device *p_pltdev)
{
int ret = 0;
int i = 0;
dev_t devno =MKDEV(major,minor);//组合成一个完整的设备号
/*
* 手动申请设备号
*int register_chrdev_region(dev_t from, unsigned count, const char *name)
*参数分别为 设备号,设备数量,设备名字
*/
ret=register_chrdev_region(devno,fs4412leds_driver_num,"fs4412leds");
if(ret)
{
/*
*自动申请设备号
*int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count, const char *name)
* dev:分配设备号成功后用来存放分配到的设备号
* baseminior:起始的次设备号,一般为0
* count:申请的设备数量
* name:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号
*/
ret = alloc_chrdev_region(&devno,minor,fs4412leds_driver_num,"fs4412leds");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离主设备号(手动申请失败需要)
}
for(i=0;i<fs4412leds_driver_DEV_CNT;i++)
{
gmydev_arr[i]=(struct fs4412leds_driver_dev*)kmalloc(sizeof(struct fs4412leds_driver_dev),GFP_KERNEL);
if(NULL==gmydev_arr[i])
{
printk("kmalloc failed\n");
unregister_chrdev_region(devno,fs4412leds_driver_num);//注销设备号
return -1;
}
memset(gmydev_arr[i],0,sizeof(struct fs4412leds_driver_dev));//初始化
}
/*指定操作函数集*/
for(i=0;i<fs4412leds_driver_DEV_CNT;i++)
{
devno =MKDEV(major,minor+i);
cdev_init(&gmydev_arr[i]->mydev,&myops);//初始化设备对象
/*将struct_cdev对象添加到内核对应的数据结构里*/
gmydev_arr[i]->mydev.owner=THIS_MODULE;
cdev_add(&gmydev_arr[i]->mydev,devno,1);//添加设备
/*ioremap*/
ioremap_ledreg(gmydev_arr[i],p_pltdev);//映射地址
set_output_ledconreg(gmydev_arr[i]);//led初始化
/*con_register set output*/
gmydev_arr[i]->pcls=class_create(THIS_MODULE,"fs4412led");
if(IS_ERR(gmydev_arr[i]->pcls))
{
printk("class_create failed\n");
cdev_del(&gmydev_arr[i]->mydev);
iounremap_ledreg(gmydev_arr[i]);//取消映射
kfree(gmydev_arr[i]);
unregister_chrdev_region(devno,fs4412leds_driver_num);//注销设备号
gmydev_arr[i]=NULL;
return -1;
}
gmydev_arr[i]->pdev=device_create(gmydev_arr[i]->pcls,NULL,devno,NULL,"fs4412led");
if(NULL==gmydev_arr[i]->pdev)
{
printk("device_create failed\n");
cdev_del(&gmydev_arr[i]->mydev);
iounremap_ledreg(gmydev_arr[i]);//取消映射
kfree(gmydev_arr[i]);
unregister_chrdev_region(devno,fs4412leds_driver_num);//注销设备号
gmydev_arr[i]=NULL;
class_destroy(gmydev_arr[i]->pcls);
return -1;
}
}
return 0;
}
int fs4412leds_driver_remove(struct platform_device *p_pltdev)
{
dev_t devno = MKDEV(major,minor);
int i=0;
/*iounremap*/
for(i=0;i<fs4412leds_driver_DEV_CNT;i++)
{
iounremap_ledreg(gmydev_arr[i]);//取消映射
device_destroy(gmydev_arr[i]->pcls,devno);
class_destroy(gmydev_arr[i]->pcls);
cdev_del(&gmydev_arr[i]->mydev);
}
for(i=0;i<fs4412leds_driver_DEV_CNT;i++)
{
kfree(gmydev_arr[i]);
gmydev_arr[i]=NULL;
}
unregister_chrdev_region(devno,fs4412leds_driver_num);//注销设备号
return 0;
}
struct platform_driver fs4412leds_driver =
{
.driver.name="fs4412leds",
.probe=fs4412leds_driver_probe,
.remove=fs4412leds_driver_remove,
};
int __init fs4412leds_driver_init(void)
{
platform_driver_register(&fs4412leds_driver);
return 0;
}
void __exit fs4412leds_driver_exit(void)
{
platform_driver_unregister(&fs4412leds_driver);
}
MODULE_LICENSE("GPL");
module_init(fs4412leds_driver_init);
module_exit(fs4412leds_driver_exit);
注意事项:.name=“fs4412leds” 和.driver.name=“fs4412leds”,需要的值要相同不然设备不能被成功建立。
本文介绍了Linux平台总线式驱动开发,包括为了解决硬编码驱动问题而引入的struct device和struct device_driver,以及platform总线的核心数据类型。讨论了platform的三种设备与驱动匹配方式:名称匹配、id匹配和设备树匹配,并通过led驱动名称匹配版举例说明。
&spm=1001.2101.3001.5002&articleId=126344890&d=1&t=3&u=e6bcff3a4698426183b9aadfa6afc5dd)

被折叠的 条评论
为什么被折叠?



