打开APP
未登录
开通VIP,畅享免费电子书等14项超值服
开通VIP
首页
好书
留言交流
下载APP
联系客服
linux Platform设备驱动
云中月仙
>《待分类》
2018.04.06
关注
PlatForm设备驱动:
一、platform总线、设备与驱动
1
.
一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题,
但是在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于此类总线。
基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为 platform_driver。
2
.
注意,所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段,
例如,在 S3C6410处理器中,把内部集成的I2C、RTC、SPI、LCD、看门狗等控制器都归纳为platform_device,而它们本身就是字符设备。
3
.
基于Platform总线的驱动开发流程如下:
(1)定义初始化platform bus
(2)定义各种platform devices
(3)注册各种platform devices
(4)定义相关platform driver
(5)注册相关platform driver
(6)操作相关设备
4
.
平台相关结构
/
/
platform_device结构体
struct platform_device
{
const
char
*
name
;
/
*
设备名
*
/
u32 id
;
/
/
设备id,用于给插入给该总线并且具有相同name的设备编号,如果只有一个设备的话填
-
1。
struct device dev
;
/
/
结构体中内嵌的device结构体。
u32 num_resources
;
/
*
设备所使用各类资源数量
*
/
struct resource
*
resource
;
/
*
/
/
定义平台设备的资源
*
/
}
;
/
/
平台资源结构
struct resource
{
resource_size_t start
;
/
/
定义资源的起始地址
resource_size_t
end
;
/
/
定义资源的结束地址
const
char
*
name
;
/
/
定义资源的名称
unsigned long flags
;
/
/
定义资源的类型,比如MEM,IO,IRQ,DMA类型
struct resource
*
parent
,
*
sibling
,
*
child
;
}
;
/
/
设备的驱动:platform_driver这个结构体中包含probe
(
)
、remove
(
)
、shutdown
(
)
、suspend
(
)
、
resume
(
)
函数,通常也需要由驱动实现。
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
(
*
suspend_late
)
(
struct platform_device
*
,
pm_message_t state
)
;
int
(
*
resume_early
)
(
struct platform_device
*
)
;
int
(
*
resume
)
(
struct platform_device
*
)
;
struct pm_ext_ops
*
pm
;
struct device_driver driver
;
}
;
/
/
系统中为platform总线定义了一个bus_type的实例platform_bus_type,
struct bus_type platform_bus_type
=
{
.
name
=
“platform”
,
.
dev_attrs
=
platform_dev_attrs
,
.
match
=
platform_match
,
.
uevent
=
platform_uevent
,
.
pm
=
PLATFORM_PM_OPS_PTR
,
}
;
EXPORT_SYMBOL_GPL
(
platform_bus_type
)
;
/
/
这里要重点关注其match
(
)
成员函数,正是此成员表明了platform_device和platform_driver之间如何匹配。
static
int
platform_match
(
struct device
*
dev
,
struct device_driver
*
drv
)
{
struct platform_device
*
pdev
;
pdev
=
container_of
(
dev
,
struct platform_device
,
dev
)
;
return
(
strncmp
(
pdev
-
>
name
,
drv
-
>
name
,
BUS_ID_SIZE
)
=
=
0
)
;
}
/
/
匹配platform_device和platform_driver主要看二者的name字段是否相同。
/
/
对platform_device的定义通常在BSP的板文件中实现,在板文件中,将platform_device归纳为一个数组,最终通过platform_add_devices
(
)
函数统一注册。
/
/
platform_add_devices
(
)
函数可以将平台设备添加到系统中,这个函数的 原型为:
int
platform_add_devices
(
struct platform_device
*
*
devs
,
int
num
)
;
/
/
该函数的第一个参数为平台设备数组的指针,第二个参数为平台设备的数量,它内部调用了platform_device_register
(
)
函 数用于注册单个的平台设备。
1
.
platform bus总线先被kenrel注册。
2
.
系统初始化过程中调用platform_add_devices或者platform_device_register,将平台设备
(
platform devices
)
注册到平台总线中
(
platform bus
)
3
.
平台驱动
(
platform driver
)
与平台设备
(
platform device
)
的关联是在platform_driver_register或者driver_register中实现,一般这个函数在驱动的初始化过程调用。
通过这三步,就将平台总线,设备,驱动关联起来。
二.Platform初始化
系统启动时初始化时创建了platform_bus总线设备和platform_bus_type总线
,
platform总线是在内核初始化的时候就注册进了内核。
内核初始化函数kernel_init
(
)
中调用了do_basic_setup
(
)
,该函数中调用driver_init
(
)
,该函数中调用platform_bus_init
(
)
,我们看看platform_bus_init
(
)
函数:
int
__init platform_bus_init
(
void
)
{
int
error
;
early_platform_cleanup
(
)
;
/
/
清除platform设备链表
/
/
该函数把设备名为platform 的设备platform_bus注册到系统中,其他的platform的设备都会以它为parent。它在sysfs中目录下
.
即
/
sys
/
devices
/
platform。
/
/
platform_bus总线也是设备,所以也要进行设备的注册
/
/
struct device platform_bus
=
{
/
/
.
init_name
=
"platform"
,
/
/
}
;
error
=
device_register
(
&
platform_bus
)
;
/
/
将平台bus作为一个设备注册,出现在sys文件系统的device目录
if
(
error
)
return
error
;
/
/
接着bus_register
(
&
platform_bus_type
)
注册了platform_bus_type总线
.
/
*
struct bus_type platform_bus_type
=
{
.
name
=
“platform”
,
.
dev_attrs
=
platform_dev_attrs
,
.
match
=
platform_match
,
.
uevent
=
platform_uevent
,
.
pm
=
PLATFORM_PM_OPS_PTR
,
}
;
*
/
/
/
默认platform_bus_type中没有定义probe函数。
error
=
bus_register
(
&
platform_bus_type
)
;
/
/
注册平台类型的bus,将出现sys文件系统在bus目录下,创建一个platform的目录,以及相关属性文件
if
(
error
)
device_unregister
(
&
platform_bus
)
;
return
error
;
}
/
/
总线类型match函数是在设备匹配驱动时调用,uevent函数在产生事件时调用。
/
/
platform_match函数在当属于platform的设备或者驱动注册到内核时就会调用,完成设备与驱动的匹配工作。
static
int
platform_match
(
struct device
*
dev
,
struct device_driver
*
drv
)
{
struct platform_device
*
pdev
=
to_platform_device
(
dev
)
;
struct platform_driver
*
pdrv
=
to_platform_driver
(
drv
)
;
/
*
match against the id table first
*
/
if
(
pdrv
-
>
id_table
)
return platform_match_id
(
pdrv
-
>
id_table
,
pdev
)
!
=
NULL
;
/
*
fall
-
back
to
driver name match
*
/
return
(
strcmp
(
pdev
-
>
name
,
drv
-
>
name
)
=
=
0
)
;
/
/
比较设备和驱动的名称是否一样
}
static
const
struct platform_device_id
*
platform_match_id
(
struct platform_device_id
*
id
,
struct platform_device
*
pdev
)
{
while
(
id
-
>
name
[
0
]
)
{
if
(
strcmp
(
pdev
-
>
name
,
id
-
>
name
)
=
=
0
)
{
pdev
-
>
id_entry
=
id
;
return id
;
}
id
+
+
;
}
return
NULL
;
}
/
/
不难看出,如果pdrv的id_table数组中包含了pdev
-
>
name,或者drv
-
>
name和pdev
-
>
name名字相同,都会认为是匹配成功。
/
/
id_table数组是为了应对那些对应设备和驱动的drv
-
>
name和pdev
-
>
name名字不同的情况。
/
/
再看看platform_uevent()函数:platform_uevent 热插拔操作函数
static
int
platform_uevent
(
struct device
*
dev
,
struct kobj_uevent_env
*
env
)
{
struct platform_device
*
pdev
=
to_platform_device
(
dev
)
;
add_uevent_var
(
env
,
"MODALIAS=%s%s"
,
PLATFORM_MODULE_PREFIX
,
(
pdev
-
>
id_entry
)
?
pdev
-
>
id_entry
-
>
name
:
pdev
-
>
name
)
;
return 0
;
}
/
/
添加了MODALIAS环境变量,我们回顾一下:platform_bus
.
parent
-
>
kobj
-
>
kset
-
>
uevent_ops为device_uevent_ops,bus_uevent_ops的定义如下:
static struct kset_uevent_ops device_uevent_ops
=
{
.
filter
=
dev_uevent_filter
,
.
name
=
dev_uevent_name
,
.
uevent
=
dev_uevent
,
}
;
/
/
当调用device_add
(
)
时会调用kobject_uevent
(
&
dev
-
>
kobj
,
KOBJ_ADD
)
产生一个事件,这个函数中会调用相应的kset_uevent_ops的uevent函数,
三.Platform设备的注册
我们在设备模型的分析中知道了把设备添加到系统要调用device_initialize
(
)
和platform_device_add
(
pdev
)
函数。
Platform设备的注册分两种方式:
1
.
对于platform设备的初注册,内核源码提供了platform_device_add
(
)
函数,输入参数platform_device可以是静态的全局设备,它是进行一系列的操作后调用device_add
(
)
将设备注册到相应的总线(platform总线)上,
内核代码中platform设备的其他注册函数都是基于这个函数,如platform_device_register
(
)
、platform_device_register_simple
(
)
、platform_device_register_data
(
)
等。
2
.
另外一种机制就是动态申请platform_device_alloc
(
)
一个platform_device设备,然后通过platform_device_add_resources及platform_device_add_data等添加相关资源和属性。
无论哪一种platform_device,最终都将通过platform_device_add这册到platform总线上。
区别在于第二步:其实platform_device_add
(
)
包括device_add
(
)
,不过要先注册resources,然后将设备挂接到特定的platform总线。
3
.
第一种平台设备注册方式
/
/
platform_device是静态的全局设备,即platform_device结构的成员已经初始化完成
/
/
直接将平台设备注册到platform总线上
/
*
platform_device_register和device_register的区别
:
(
1
)
.
主要是有没有resource的区别,前者的结构体包含后面,并且增加了struct resource结构体成员,后者没有。
platform_device_register在device_register的基础上增加了struct resource部分的注册。
由此。可以看出,platform_device
-
-
-
paltform_driver_register机制与device
-
driver的主要区别就在于resource。
前者适合于具有独立资源设备的描述,后者则不是。
(
2
)
.
其实linux的各种其他驱动机制的基础都是device_driver。只不过是增加了部分功能,适合于不同的应用场合
.
*
/
int
platform_device_register
(
struct platform_device
*
pdev
)
{
device_initialize
(
&
pdev
-
>
dev
)
;
/
/
初始化platform_device内嵌的device
return platform_device_add
(
pdev
)
;
/
/
把它注册到platform_bus_type上
}
int
platform_device_add
(
struct platform_device
*
pdev
)
{
int
i
,
ret
=
0
;
if
(
!
pdev
)
return
-
EINVAL
;
if
(
!
pdev
-
>
dev
.
parent
)
pdev
-
>
dev
.
parent
=
&
platform_bus
;
/
/
设置父节点,即platform_bus作为总线设备的父节点,其余的platform设备都是它的子设备
/
/
platform_bus是一个设备,platform_bus_type才是真正的总线
pdev
-
>
dev
.
bus
=
&
platform_bus_type
;
/
/
设置platform总线
,
指定bus类型为platform_bus_type
/
/
设置pdev
-
>
dev内嵌的kobj的name字段
,
将platform下的名字传到内部device,最终会传到kobj
if
(
pdev
-
>
id
!
=
-
1
)
dev_set_name
(
&
pdev
-
>
dev
,
"%s.%d"
,
pdev
-
>
name
,
pdev
-
>
id
)
;
else
dev_set_name
(
&
pdev
-
>
dev
,
"%s"
,
pdev
-
>
name
)
;
/
/
初始化资源并将资源分配给它,每个资源的它的parent不存在则根据flags域设置parent,flags为IORESOURCE_MEM,
/
/
则所表示的资源为I
/
O映射内存,flags为IORESOURCE_IO,则所表示的资源为I
/
O端口。
for
(
i
=
0
;
i
<
pdev
-
>
num_resources
;
i
+
+
)
{
struct resource
*
p
,
*
r
=
&
pdev
-
>
resource
[
i
]
;
if
(
r
-
>
name
=
=
NULL
)
/
/
资源名称为NULL则把设备名称设置给它
r
-
>
name
=
dev_name
(
&
pdev
-
>
dev
)
;
p
=
r
-
>
parent
;
/
/
取得资源的父节点,资源在内核中也是层次安排的
if
(
!
p
)
{
if
(
resource_type
(
r
)
=
=
IORESOURCE_MEM
)
/
/
如果父节点为NULL,并且资源类型为IORESOURCE_MEM,则把父节点设置为iomem_resource
p
=
&
iomem_resource
;
else
if
(
resource_type
(
r
)
=
=
IORESOURCE_IO
)
/
/
否则如果类型为IORESOURCE_IO,则把父节点设置为ioport_resource
p
=
&
ioport_resource
;
}
/
/
从父节点申请资源,也就是出现在父节点目录层次下
if
(
p
&
&
insert_resource
(
p
,
r
)
)
{
printk
(
KERN_ERR
"%s: failed to claim resource %d\n"
,
dev_name
(
&
pdev
-
>
dev
)
,
i
)
;
ret
=
-
EBUSY
;
goto failed
;
}
}
pr_debug
(
"Registering platform device '%s'. Parent at %s\n"
,
dev_name
(
&
pdev
-
>
dev
)
,
dev_name
(
pdev
-
>
dev
.
parent
)
)
;
/
/
device_creat
(
)
创建一个设备并注册到内核驱动架构
.
.
.
/
/
device_add
(
)
注册一个设备到内核,少了一个创建设备
.
.
ret
=
device_add
(
&
pdev
-
>
dev
)
;
/
/
就在这里把设备注册到总线设备上
,
标准设备注册,即在sys文件系统中添加目录和各种属性文件
if
(
ret
=
=
0
)
return ret
;
failed
:
while
(
-
-
i
>
=
0
)
{
struct resource
*
r
=
&
pdev
-
>
resource
[
i
]
;
unsigned long type
=
resource_type
(
r
)
;
if
(
type
=
=
IORESOURCE_MEM
|
|
type
=
=
IORESOURCE_IO
)
release_resource
(
r
)
;
}
return ret
;
}
4
.
第二种平台设备注册方式
/
/
先分配一个platform_device结构,对其进行资源等的初始化
/
/
之后再对其进行注册,再调用platform_device_register
(
)
函数
struct platform_device
*
platform_device_alloc
(
const
char
*
name
,
int
id
)
{
struct platform_object
*
pa
;
/
*
struct platform_object
{
struct platform_device pdev
;
char name
[
1
]
;
}
;
*
/
pa
=
kzalloc
(
sizeof
(
struct platform_object
)
+
strlen
(
name
)
,
GFP_KERNEL
)
;
/
/
该函数首先为platform设备分配内存空间
if
(
pa
)
{
strcpy
(
pa
-
>
name
,
name
)
;
pa
-
>
pdev
.
name
=
pa
-
>
name
;
/
/
初始化platform_device设备的名称
pa
-
>
pdev
.
id
=
id
;
/
/
初始化platform_device设备的id
device_initialize
(
&
pa
-
>
pdev
.
dev
)
;
/
/
初始化platform_device内嵌的device
pa
-
>
pdev
.
dev
.
release
=
platform_device_release
;
}
return pa
?
&
pa
-
>
pdev
:
NULL
;
}
/
/
一个更好的方法是,通过下面的函数platform_device_register_simple
(
)
动态创建一个设备,并把这个设备注册到系统中:
struct platform_device
*
platform_device_register_simple
(
const
char
*
name
,
int
id
,
struct resource
*
res
,
unsigned
int
num
)
{
struct platform_device
*
pdev
;
int
retval
;
pdev
=
platform_device_alloc
(
name
,
id
)
;
if
(
!
pdev
)
{
retval
=
-
ENOMEM
;
goto
error
;
}
if
(
num
)
{
retval
=
platform_device_add_resources
(
pdev
,
res
,
num
)
;
if
(
retval
)
goto
error
;
}
retval
=
platform_device_add
(
pdev
)
;
if
(
retval
)
goto
error
;
return pdev
;
error
:
platform_device_put
(
pdev
)
;
return ERR_PTR
(
retval
)
;
}
/
/
该函数就是调用了platform_device_alloc
(
)
和platform_device_add
(
)
函数来创建的注册platform device,函数也根据res参数分配资源,看看platform_device_add_resources
(
)
函数:
int
platform_device_add_resources
(
struct platform_device
*
pdev
,
struct resource
*
res
,
unsigned
int
num
)
{
struct resource
*
r
;
r
=
kmalloc
(
sizeof
(
struct resource
)
*
num
,
GFP_KERNEL
)
;
/
/
为资源分配内存空间
if
(
r
)
{
memcpy
(
r
,
res
,
sizeof
(
struct resource
)
*
num
)
;
pdev
-
>
resource
=
r
;
/
/
并拷贝参数res中的内容,链接到device并设置其num_resources
pdev
-
>
num_resources
=
num
;
}
return r
?
0
:
-
ENOMEM
;
}
四.Platform设备驱动的注册
我们在设备驱动模型的分析中已经知道驱动在注册要调用driver_register
(
)
,
platform driver的注册函数platform_driver_register
(
)
同样也是进行其它的一些初始化后调用driver_register
(
)
将驱动注册到platform_bus_type总线上
.
int
platform_driver_register
(
struct platform_driver
*
drv
)
{
drv
-
>
driver
.
bus
=
&
platform_bus_type
;
/
/
它将要注册到的总线
/
*
设置成platform_bus_type这个很重要,因为driver和device是通过bus联系在一起的,
具体在本例中是通过 platform_bus_type中注册的回调例程和属性来是实现的
,
driver与device的匹配就是通过 platform_bus_type注册的回调例程platform_match
(
)
来完成的。
*
/
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
;
return driver_register
(
&
drv
-
>
driver
)
;
/
/
注册驱动
}
/
/
然后设定了platform_driver内嵌的driver的probe、remove、shutdown函数。
static
int
platform_drv_probe
(
struct device
*
_dev
)
{
struct platform_driver
*
drv
=
to_platform_driver
(
_dev
-
>
driver
)
;
struct platform_device
*
dev
=
to_platform_device
(
_dev
)
;
return drv
-
>
probe
(
dev
)
;
/
/
调用platform_driver的probe
(
)
函数,这个函数一般由用户自己实现
/
/
例如下边结构,回调的是serial8250_probe
(
)
函数
/
*
static struct platform_driver serial8250_isa_driver
=
{
.
probe
=
serial8250_probe
,
.
remove
=
__devexit_p
(
serial8250_remove
)
,
.
suspend
=
serial8250_suspend
,
.
resume
=
serial8250_resume
,
.
driver
=
{
.
name
=
"serial8250"
,
.
owner
=
THIS_MODULE
,
}
,
}
;
*
/
}
static
int
platform_drv_remove
(
struct device
*
_dev
)
{
struct platform_driver
*
drv
=
to_platform_driver
(
_dev
-
>
driver
)
;
struct platform_device
*
dev
=
to_platform_device
(
_dev
)
;
return drv
-
>
remove
(
dev
)
;
}
static void platform_drv_shutdown
(
struct device
*
_dev
)
{
struct platform_driver
*
drv
=
to_platform_driver
(
_dev
-
>
driver
)
;
struct platform_device
*
dev
=
to_platform_device
(
_dev
)
;
drv
-
>
shutdown
(
dev
)
;
}
/
/
总结:
1
.
从这三个函数的代码可以看到,又找到了相应的platform_driver和platform_device,然后调用platform_driver的probe、remove、shutdown函数。这是一种高明的做法:
在不针对某个驱动具体的probe、remove、shutdown指向的函数,而通过上三个过度函数来找到platform_driver,然后调用probe、remove、shutdown接口。
如果设备和驱动都注册了,就可以通过bus
-
>
match、bus
-
>
probe或driver
-
>
probe进行设备驱动匹配了。
2
.
驱动注册的时候platform_driver_register
(
)
-
>
driver_register
(
)
-
>
bus_add_driver
(
)
-
>
driver_attach
(
)
-
>
bus_for_each_dev
(
)
,
对每个挂在虚拟的platform bus的设备作__driver_attach
(
)
-
>
driver_probe_device
(
)
-
>
drv
-
>
bus
-
>
match
(
)
=
=
platform_match
(
)
-
>
比较strncmp
(
pdev
-
>
name
,
drv
-
>
name
,
BUS_ID_SIZE
)
,
如果相符就调用platform_drv_probe
(
)
-
>
driver
-
>
probe
(
)
,如果probe成功则绑定该设备到该驱动。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请
点击举报
。
打开APP,阅读全文并永久保存
查看更多类似文章
猜你喜欢
类似文章
【热】
打开小程序,算一算2024你的财运
详解platform_device_系列函数(页 1) - 文档专区 - 程序开发 - Linux论坛 - Powered by Discuz! Archiver
Linux驱动的platform机制
linux platform 驱动模型分析 (转)
Platform设备驱动机制分析
Linux设备模型(8)_platform设备
Linux Platform驱动程序框架解析
更多类似文章 >>
生活服务
热点新闻
留言交流
回顶部
联系我们
分享
收藏
点击这里,查看已保存的文章
导长图
关注
一键复制
下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!
联系客服
微信登录中...
请勿关闭此页面
先别划走!
送你5元优惠券,购买VIP限时立减!
5
元
优惠券
优惠券还有
10:00
过期
马上使用
×