1.平台总线模型
Linux设备驱动模型有三大实体:
(1)设备:管理设备自身的属性信息(寄存地址或者led个数),对应的数据结构随类型而变,但有共同的属性成员 struct device。
(2)驱动:实现设备的具体硬件操作(驱动方法),对应的数据结构随类型而变,但有共同的属性成员 struct device_driver;
(3)总线:绑定了符合该总线要求(匹配词)的设备端和驱动端,将设备和驱动匹配起来,对应的数据结构是struct bus_type。
补充:多个类似的设备可以匹配同一个驱动。也有些设备与驱动时一对一匹配的。
- Linux内核维护着一个全局的设备链表--加载的设备均会挂载到这个链表上,挂载了所有的已安装的设备;
- Linux内核维护着一个全局的驱动链表--指的就是对应的驱动程序,挂载了所有的已安装的驱动程序。
- 每当安装一个驱动时,相应的总线把该驱动和设备链表上的每个设备的匹配词进行比较,如果匹配则绑定该驱动,并把设备的信息(要传递的参数)传递给驱动。
- 每当安装一个设备时,相应的总线把该设备和驱动链表上的每个驱动的匹配词进行比较,如果匹配则绑定该设备,并把设备的信息(要传递参数)传递给驱动。
常见的专用总线:I2C、SPI、USB、PCI、CAN.....
2. platform_device
2.1 相关结构体
#include <linux/platform_device.h>
struct platform_device{
const char *name; //自定义的设备名,用于和驱动匹配的匹配词(关键词)。设备端的匹配词
int id; //当设备和驱动一对一匹配时,设为-1
//注册成功后内核会自动创建目录 /sys/bus/platform/devices/name/
struct device dev; //所有设备的公共抽象模型(类似基类),很重要
struct resource *resources; //设备所用资源数组的首地址
u32 num_resources; //设备所用资源的个数,即资源数组的元素个数
struct platform_device_id *id_entry; //设备id表入口,由内核实现
struct pdev_archdata archdata;
};
struct device{
//平台数据指针,指向描述设备本身的属性信息(platform_device一级成员无法表示的情形),
platform_driver模块中可以获取该数据
void *plarform_data;
//指向描述驱动的数据,通常在platform_device模块中设置该数据,用于告知platform_driver要服务的设备类型,在platform_driver模块中获取该数据,提供相应对应的驱动服务
void *driver_data;
.....
void (*release)(struct device *dev); //指向释放device对象的函数,必须实现(哪怕是空操作也行)
//否则卸载模块时候报错
//当device对象应用计数为0时会自动调用该函数
}
struct resource{
resource_size_t start; //所用资源的起始物理地址或编号 例如:GPIO0--0xFF72 0000
resource_size_t end; //所用资源的结束物理地址或编号 66536
const char *name; //自定义的资源名称
unsigned long flags; //资源类型
struct resource *parent,*sibling,*child; //父节点、兄节点、子节点,内核实现
};
资源类型:
#define IORESOURCE_IO 0x00000100 //X86为所有的I/O外设设置独立的内存空间隔离
#define IORESOURCE_MEM 0x00000200 //内存空间
#define IORESOURCE_IRQ 0x00000400 //中断编号
#define IORESOURCE_DMA 0x00000800 //DMA通道编号
2.2 相关函数或者宏
1.platform_device_register()
头文件:#include <linux/platform_device.h>
原型:int platform_device_register(struct platform_device *pdev); //用在函数的入口
功能:注册指定的platform_device
参数:pdev:指定的platform_device对象的地址
返回值:成功:0
失败:负的错误码
2.platform_device_unregister()
头文件:#include <linux/platform_device.h>
原型:void platform_device_unregister(struct platform_device *pdev); //用在函数的出口
功能:注销指定的platform_device
参数:Pdev:指定的platform_device对象的地址
返回值:无
2.3 编程
平台设备驱动代码的思路:
- 如果用到资源,则定义并初始化一个struct resource类型的数组。否则忽略该步骤。
- 定义并初始化一个struct platform_device类型的对象,填充其中的name、id、resource(不用则填NULL)、num_resources(不用填0)成员 struct device-->release成员 dev->platform_data根据实际情况进行实现。
- 通常在模块的加载函数中调用platform_device_register()注册指定的platform_device对象。
- 通常在模块的卸载函数中调用platform_device_unregister()注销指定的platform_device对象。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#define rGPIO1 0xFF730000 //GPIO1的物理地址 GPIO0的物理地址0xFF720000
#define rGPIO1SIZE 0xFFFF //GPIO1占用的大小
//释放led的资源
static void led_release(struct device *dev)
{
printk(KERN_EMERG "%s is call\r\n", __FUNCTION__);
}
//定义设备资源的结构体的实现
static struct resource led_resource[] = {
[0] = {
.start = rGPIO1,
.end = rGPIO1 + rGPIO1SIZE - 1,
.flags = IORESOURCE_MEM,
},
};
//设备端的核心结构体的实现--配置的具体实现
static struct platform_device led_dev = {
.name = "rk3399", //匹配词
.id = -1,
.num_resources = ARRAY_SIZE(led_resource),
.resource = led_resource,
.dev = {
.release = led_release,
},
};
//模块的入口
static int __init xxx_init(void)
{
printk(KERN_EMERG "%s is call\n",__FUNCTION__);
//第一注册设备端代码实现
platform_device_register(&led_dev);
return 0;
}
//模块的出口
static void __exit xxx_exit(void)
{
printk(KERN_EMERG "%s is call\n",__FUNCTION__);
//注销指定的设备
platform_device_unregister(&led_dev);
}
//注册一下
module_init(xxx_init);
module_exit(xxx_exit);
//开源协议
MODULE_LICENSE("GPL");
3. platform_driver
3.1相关结构体
#include <linux/platform_device.h>
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; //所有驱动的公共抽象模型(类似基类),很重要
struct platform_device_id *id_table; //匹配设备的id数组地址,该数组最末元素必须为{};
//一个驱动可以匹配一个或多个设备,描述可以匹配哪些设备
};
struct device_driver{
const char *name; //自定义的驱动名,用于和设备一对一匹配的匹配词--module模块。
//注册成功后内核会自动创建目录/sys/bus/platform/drivers/name
const struct of_device_id *of_match_table; //用于设备侧以设备树方式实现一对多匹配
//匹配设备的数组首地址,该数组最末元素必须为{};
//一个驱动可以匹配一个或多个设备,描述可以匹配哪些设备
}
struct of_device_id{
char name[32]; //用于以设备树方式实现设备进行一对一或一对多匹配的关键词
// 前提条件:platform_device是以设备树的方式实现
char type[32];
char compatible[128];
const void *data;
};
struct platform_device_id{
char name[PLATFORM_NAME_SIZE]; //用于和设备进行一对一或一对多匹配的匹配词
//前提:platform_device是以module的方式实现
kernel_ulong_t driver_data;
};
3.2 相关函数或者宏
1.platform_driver_register()
头文件:#include <linux/platform_device.h>
原型:int platform_driver_register(struct platform_driver *drv);
功能:注册指定的platform_driver--核心结构体对象的名字
参数:Drv:指定的platform_driver对象的地址
返回值:成功:0
失败:负的错误码
内核源码的基本形式
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
2.platform_driver_unregister()
头文件:#include <linux/platform_device.h>
原型:void platform_driver_unregister(struct platform_driver *drv);
功能:注销指定的platform_driver
参数:drv:指定的platform_driver对象的地址
返回值:无
内核源码的基本形式:
/**
* platform_driver_unregister - unregister a driver for platform-level devices
* @drv: platform driver structure
*/
void platform_driver_unregister(struct platform_driver *drv)
{
driver_unregister(&drv->driver);
}
3.platform_get_resource()
头文件:#include <linux/platform_device.h>
原型:struct resource* platform_get_resource(struct platform_device *dev,unsigned int type,unsigned int num);
功能:查看指定的platform_device所使用的资源(能查到什么资源--(一般情况下传递是寄存器的地址))
参数:Dev:指定的platform_device对象的地址
type:查看资源的类型
num:查看的资源在所用的同类型资源中的编号(排号),从0开始编号
返回值:成功:查到的资源对象的首地址
失败:NULL
内核源码的基本形式:
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type
* @num: resource index
*/
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
{
int i;
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
if (type == resource_type(r) && num-- == 0)
return r;
}
return NULL;
}
4.platform_get_irq()
头文件:#include <linux/platform_device.h>
原型:Int platform_get_irq(struct platform_device *dev,unsigned int num);
功能:查看指定的platform_device所使用的IRQ号资源
参数:dev:指定的platform_device对象的地址
num:查看的资源在所用的IRQ号资源的编号(排号),从0开始编号
返回值:成功 :>=0 查到的IRQ号
失败:负的错误码
5.platform_get_resource_byname()
头文件:#include <linux/platform_device.h>
原型:stuct resource *platform_get_resource_byname(struct platform_device *dev,unsigned int type,const char *name);
功能:根据资源名称查看指定platform_device所使用的资源。
参数:dev:指定的platform_device对象的地址
type:查看资源的类型
name:查看资源的名称
返回值:成功:查到的资源对象的地址
失败:NULL
内核源码的基本形式:
/**
* platform_get_resource_byname - get a resource for a device by name
* @dev: platform device
* @type: resource type
* @name: resource name
*/
struct resource *platform_get_resource_byname(struct platform_device *dev,
unsigned int type,
const char *name)
{
int i;
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
if (unlikely(!r->name))
continue;
if (type == resource_type(r) && !strcmp(r->name, name))
return r;
}
return NULL;
}
6.platform_get_irq_byname()
头文件:#include <linux/platform_device.h>
原型:int platform_get_irq_byname(struct platform_device *dev,const char *name);
功能:根据资源名称查看指定的platform_device所使用的IRQ号的资源
参数:dev:指定的platform_device对象的地址
name:查看的IRQ号资源的名称
返回值:成功 :>=0 查看到的IRQ号
失败:负的错误码
7.devm_request_mem_region()
头文件:#include <linux/ioport.h>
宏原型:devm_request_mem_region(dev,start,n,name);
功能:向内核申请连续的内存资源,通告其他驱动,这段内存已经被使用。
参数:dev:当前申请资源的设备的struct device成员的地址
start:申请内存资源的起始物理地址,类型是resource_size_t
n:申请内存大小(字节数)
name:自定义的名称,不重要
返回值:成功:struct resource *类型的有效地址
失败:NULL
注意:
/proc/iomem文件中记录了被注册的外设和物理地址
8.devm_release_mem_region()
头文件:#include <linux/ioport.h>
宏原型:devm_release_mem_region(dev,start,n);
功能:在内核释放之前申请内存资源
参数:dev:当前申请资源的设备的struct device成员的地址
start:申请内存资源的起始物理地址,类型是resource_size_t
n:申请内存大小(字节数)
返回值:无