目录
1.基本概念
2.platform_device
3.platform_driver
4.platform_bus_type
5.平台驱动及相关函数
linux设备树
设备树的基本语法
节点名命名规范
默认意义的属性
1.基本概念
驱动模型
:由于计算机的外设越来越丰富,linux
内核中的驱动程序也越来越多,为了便于管理,
linux 内核2
.
6
版本采用设备模型来进行管理。在设备模型中包括总线,驱动,设备,类。
device:负责提供硬件资源
driver:负责初始化设备以及提供一些操作方式
bus:负责管理挂载对应总线的设备以及驱动,匹配设备和驱动,它维护着两个链表,里面记录着各个已 经注册的设备和驱动,可以物理存在,也可以虚拟。
linux设备中设备和驱动通常都需要挂接到一种总线上,例如
usb,pci,i2c
。但是嵌入式系统中
SoC
集 成了外设控制器或挂接到SoC
内存空间,基于这种背景,发明了一种虚拟总线称为
platform
总线用来连接那些没有物理总线的设备或一些不支持热插拔的设备DM9000网卡设备就是挂接在这条总线上的,相应的设备 称为platform_device
,
相应的驱动
platform_driver。
class
:
具有相同属性或特征的一类事物,例如将一些设备归为一类进行管理
(
/
sys
/
class
)
。
总线的工作原理:
总线管理着两个链表:设备链表 和 驱动链表。
当我们向内核注册一个驱动时,便插入到总线的驱动链表。
当我们向内核注册一个设备时,便插入到总线的设备链表。
在插入的同时,总线会执行一个 bus_type
结构体中的
match
方法对新插入的 设备
/
驱动 进行匹配
,匹配成功后,会调用驱动device_driver
结构体中的
probe 方法。 在移除设备或驱动时,会调用 device_driver 结构体中的
remove 方法。
Linux设备模型为这三种对象各自定义了对应的类:struct bus_type代表总线,struct device代表设备,struct device_driver代表驱动,这三者都内嵌了strcut kobject或struct
kset,于是会生成对应的总线,设备,驱动的目录。
设备专门用来描述设备所占有的资源信息,而驱动和设备绑定成功后,驱动负责从设备中动态获取这些资源信息,当设备的资源改变后,只是设备改变而已,驱动的代码可以不做任何修改,这就大大提高了驱动代码的通用性。总线就是联系两者的桥梁。
struct
platform_device
{
????????const
char *
name
;
//
设备名
????????int
id
;
????????bool
id_auto
;
????????struct
device dev
;
????????u32 num_resources
;
//
设备资源数量
????????struct
resource
*
resource
;
//
设备资源
????????const struct
platform_device_id
*
id_entry
;
????????struct
mfd_cell
*
mfd_cell
;
????????struct
pdev_archdata archdata
;
};
struct
platform_driver
{
????????int
(
*
probe
)(
struct
platform_device
*
);
//
匹配成功执行
probe
????????int
(
*
remove
)(
struct
platform_device
*
);
//
卸载时执行
remove
????????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
;
????????bool
prevent_deferred_probe
;
};
struct
of_device_id led_of_matches
[]
=
{
????????{.
compatible
=
"fs"
,
"myled"
},
//
通过设备树匹配
};
struct
platform_driver mydriver
=
{
????????.
probe
=
xxx_probe
,
????????.
remove
=
xxx_remove
,
????????.
driver
=
{
????????????????.
name
=
"mytest"
,
//
直接通过
name
匹配
????????????????.
of_match_table
=
of_match_ptr
(
led_of_matches
),
//
通过设备树进行匹配
????????},
};
struct
platform_device mydev
=
{
????????.
name
=
"mytest"
,
//
需要与驱动中的名字匹配
????????.
num_resources
=
ARRAY_SIZE
(
myresource
),
????????.
resource
=
myresource
,
};
5.平台驱动及相关函数
platform_driver_register(drv); //注册平台驱动
//drv:platform_driver对象
//返回值:成功返回0
void platform_driver_unregister(struct platform_driver *pdev); //注销平台驱动 //pdev:platform_driver对象
module_platform_driver(
platform_driver); //(注销+注册)
//platform_driver:platform_driver对象
int platform_device_register(struct platform_device *); //注册平台设备 //platform_device:platform_device对象
//返回值:成功返回0
void platform_device_unregister(struct platform_device *); //注销平台设备 //platform_device:platform_device对象
//
硬件资源描述
struct
resource xxxresouce
[]
=
{
????????[
0
]
=
{
????????.
start
=
0x11000c40
,
????????.
end
=
0x11000c40
+
3
,
? ? ? ? .
flags
=
IORESOURCE_MEM
,
???????},
????????[
1
]
=
{
????????.
start
=
168
,
????????.
flags
=
IORESOURCE_IRQ
,
????????}
};
获取硬件资源
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)?
//platform_device:platform_device对象
//type:资源类型
//num: resource index
//返回值:成功返回资源,失败返回NULL
创建设备文件
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char fmt, ...);
//class:该设备依附的类
//parent:父设备
//devt:设备号(此处的设备号为主次设备号)
//drvdata:私有数据
//fmt:设备名。 (自动创建在 /dev目录下) //返回值:成功返回struct device, 失败返回 ERR_PTR() on error
创建class
class_create(owner, name)
//owner通常为THIS_MODULE
//name为类名 (自动创建在sys/class目录下)
//返回值:成功返回struct class*, 失败返回 ERR_PTR() on error
void device_destroy(struct class *dev, dev_t devt); //销毁device
//dev: calss对象
//devt:设备号
extern void class_destroy(struct class *cls); //销毁class
//dev: calss对象
通过这个模型我们可以对之前的字符设备驱动框架进行修改
平台驱动代码:
...........................
//定义platform_driver对象
static struct platform_driver mydriver ={
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "mytest", //通过名字匹配
},
};
int my_probe(struct platform_device *pdev)
{
u32 val;
int ret;
//获取硬件资源
rescon = platform_get_resource(pdev,IORESOURCE_MEM,0);
//字符设备注册
ret = alloc_chrdev_region(&devnum,0,1,name);//1.申请设备号
if(ret!=0){
goto failed_alloc;
}
cdev_init(&mycdev,&myops); //2.cdev初始化
ret = cdev_add(&mycdev,devnum,1); //3.cdev添加到内核
if(ret!=0){
goto failed_add;
}
printk("register success %d,%d\n",MAJOR(devnum),MINOR(devnum));
myclass = class_create(THIS_MODULE,"myclass");
if(IS_ERR(myclass)){
goto failed_class;
}
mydevice = device_create(myclass,NULL,devnum,NULL,"led2");
if(IS_ERR(mydevice)){
goto failed_device;
}
//硬件操作
}
int my_remove(struct platform_device *pdev)
{
........
return 0;
}
...........
static int mod_init(void)
{
return platform_driver_register(&mydriver); //平台驱动注册
}
static void mod_exit(void)
{
platform_driver_unregister(&mydriver); //平台驱动注销
}
module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");
平台设备代码:
//描述硬件资源
struct resource myresource[]={
[0] = {
.start = 0x11000C20,
.end = 0x11000C20+3,
.flags = IORESOURCE_MEM
},
[1] = {
.start = 0x11000C24,
.end = 0x11000C24+3,
.flags = IORESOURCE_MEM
},
};
void my_release(struct device *dev)
{
printk("device release\n");
return ;
}
//定义platform_device对象
struct platform_device mydev = {
.name = "mytest", //通过名字匹配
.num_resources = ARRAY_SIZE(myresource),
.resource = myresource,
.dev = {
.release = my_release,
},
};
static int mod_init(void)
{
return platform_device_register(&mydev); //平台设备注册
}
static void mod_exit(void)
{
platform_device_unregister(&mydev); //平台设备注销
}
module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");
在后面引入了设备树的概念后我们平台设备代码这块就可以不再使用了。
linux设备树
在Linux 2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。
Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):
Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离。在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。
设备树的基本语法
设备树的基本单元是节点(node),这些node被组织成树状结构,除了root node,每个node都只有一个parent node,一个device tree文件中只能有一个root node。每个node中包含了若干的键值对(property/value)来描述该node的一些特性。每个node用节点名字标识。(类似于linux文件系统)
/{ ??????????????????????????????????????????//根节点
Property = value; ??????????????????????????//描述信息
<name>[@<unit-address>]{ ?????????????????//子节点
Property = value ??????????????????????//描述信息
};
……
};
节点名命名规范
节点名字的格式是[@]。如果该node没有reg属性,那么该节点名字中不能包括@unit-address。unit-address的具体格式是和设备挂在哪个bus上相关。例如对于CPU,其unit-address就是从0开始编址,以此加1,而具体的设备,例如以太网控制器,其unit-address就是寄存器地址。根节点的节点名是确定的,必须是“/”。
默认意义的属性
设备树语法中已经定义好的,具有通用规范意义的属性如果是设备信息和驱动分离框架的设备节点,则能够在内核初始化找到节点时候,自动解析生成相应的设备信息。
常见属性的有:?compatible、地址address、中断interrupt
简单举例:
compatible属性:用于匹配设备节点和设备驱动的属性,规则是驱动设备ID表中的compatible域的值(字符串),和设备树中设备节点中的compatible属性值完全一致。 compatible=“厂商名,设备名” ;
设备树键值对相关语法
[1]. 字符串信息
compatible = "随风,飘落";
[2]. 32位无符号整形数组
word-array = <32 45 67 89>;
reg = <0x10001000 0x24 0x20001000 0x24>;
[3]. 二进制数组
bi-array = [0c 20 11 24];
mac = [FE 02 11 CB 40 58];
[4]. 字符数组
string-list = "aaa" , "bbb" , "ccc";
在引入设备树的概念后,我们可以对前面的代码进行优化了,不再需要编写平台设备的代码。
那接下来我展示一段从设备树获取资源,来操控led灯的代码,和之前接口编程类似,我们需要先通过芯片手册找到控制led灯的管脚和操纵2该管脚的寄存器地址,然后修改对应的设备树内容:
?然后我们开始编写我们的平台驱动代码框架:
int my_probe(struct platform_device *pdev);
int my_remove(struct platform_device *pdev);
static struct resource *rescon;//接收状态寄存器的资源
static struct resource *resdata;//接收数据寄存器的资源
unsigned int *gpx1con;
unsigned int *gpx1data;
//定义platform_driver对象
struct of_device_id of_matches[]={
{.compatible="fs,myled"}, //名字要与设备树中的一样
{},
};
static struct platform_driver mydriver ={
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "myled",
.of_match_table = of_matches, //通过设备树匹配
},
};
int my_probe(struct platform_device *pdev)
{
u32 val;
int ret;
//通过设备树获取状态寄存器资源
rescon = platform_get_resource(pdev,IORESOURCE_MEM,0);
if(rescon==NULL){
goto failed_getcon;
}
printk("%#x\n",rescon->start);
gpx1con = ioremap(rescon->start,4);//映射
//通过设备树获取数据寄存器资源
resdata = platform_get_resource(pdev,IORESOURCE_MEM,1);
if(resdata==NULL){
goto failed_getdata;
}
printk("%#x\n",resdata->start);
gpx1data = ioremap(resdata->start,4);
//字符设备注册
//硬件操作
}
int my_remove(struct platform_device *pdev)
{
printk("driver remove\n");
//解除映射
iounmap(gpx1con);
iounmap(gpx1data);
.................
//注销字符设备
//。。。。。。
return 0;
}
static int mod_init(void)
{
return platform_driver_register(&mydriver); //平台驱动注册
}
static void mod_exit(void)
{
platform_driver_unregister(&mydriver); //平台驱动注销
}
module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");
Linux字符设备驱动编程步骤(更新)
1.实现入口函数 xxx_init() 和卸载函数 xxx_exit() 2.申请设备号 register_chrdev_region (与内核相关) 3.注册字符设备驱动 cdev_alloc,cdev_init,cdev_add (与内核相关) 4.利用 udev/mdev 机制创建设备文件(节点) class_create,device_create(与内核相关) 5.硬件部分初始化 ????????IO 资源映射 ioremap ,内核提供 gpio库函数(与硬件相关) ????????注册中断(与硬件相关) ????????初始化等待队列(与内核相关) ????????初始化定时器(与内核相关) 6.构建 file_operation 结构(与内核相关) 7.实现操作硬件的方法 xxx_open,xxx_read,xxx_write…(与硬件相关)
|