现在的位置: 首页 > 综合 > 正文

Android Linux 驱动模型一

2014年02月16日 ⁄ 综合 ⁄ 共 16298字 ⁄ 字号 评论关闭

开篇灰常简单自我介绍下:本人在sz工作,做手机驱动开发一段时间了,硬件平台是高通的。今先来一篇驱动模型的,往后在写系列的。看别人博客写的简单,自己理解写出又是TM的另一回事。

对于驱动模型,我一开始只能从表面去填充driver等结构体,不懂实际驱动模型做了什么工作,这次有时间深入理解下这个Linux驱动模型。凡事最怕认真二字!哈哈。好,进入正题。

驱动也是Linux内核的一部分,应用层是如何跟内存的驱动做交互的呢?原来有一个名sys的fs做了基层的工作。在Linux,一切外设都当做文件,所以,应用层要操作外设硬件,如我们手机在菜单里调节LCD背光的亮度,是如何一层一层往下,最后传达给背光IC的呢?容易想到,应用层read回当前背光参数,然后在write一个不就完事了吗?Bingo!

那么,我们需要一个fs来write,why fs?因为你不是所一切外设都是文件吗,那得通过fs去操作文件。在Linxu 里,sys这个ram-based的fs ,就是我们需要的。这里,我们想放放这sysfs,后续讲到。

我们想以一个例子来开始。

本例为在高通平台上注册一个LCD的驱动。

**************************************************

文件 board-xxx-display.c 

**************************************************

static struct msm_panel_common_pdata mipi_hitachi_pdata
= {
.backlight_level = mipi_hitachi_backlight_level, // LCD的背光接口。关于LCD背光IC的注册,另讲解。
.power_on_set_1 = hitachi_power_on_set, //厂家给的初始化on代码,MIPI接口,要写成我们需要的方式
.power_off_set_1 = hitachi_power_off_set, //厂家给的初始化off代码
.power_on_set_size_1 = ARRAY_SIZE(hitachi_power_on_set),
.power_off_set_size_1 = ARRAY_SIZE(hitachi_power_off_set),
};

static struct platform_device mipi_dsi_hitachi_panel_device = {
.name = "mipi_hitachi", 
.id = 0, //这次我们这个id = 0 ,后面有个动态分配device的 id 不是等于0;一个driver对应多个device的。
.dev = {
.platform_data = &mipi_hitachi_pdata,
}
};

static struct platform_device *panel_devices[] __initdata = {

&mipi_dsi_hitachi_panel_device,

&mipi_dsi_ucit_panel_device,

&kcal_platrom_device,

};

void __init apq8064_init_fb(void)
{

platform_add_devices(panel_devices,ARRAY_SIZE(panel_devices));

}

**************************************************

文件 Board-8064.c

***************************************************

static void __init apq8064_cdp_init(void)

{

   apq8064_init_fb();

}

MACHINE_START(APQ8064_CDP, "QCT APQ8064 CDP")
.map_io = apq8064_map_io,
.reserve = apq8064_reserve,
.init_irq = apq8064_init_irq,
.handle_irq = gic_handle_irq,
.timer = &msm_timer,
.init_machine = apq8064_cdp_init,
.init_early = apq8064_allocate_memory_regions,
.init_very_early = apq8064_early_reserve,
.restart = msm_restart,
MACHINE_END

在这里我们先讲完platform的device和driver的注册,然后再大概讲下 boot跑到kernel后,如何加载上述出现过的MACHINE_START 和MACHINE_END之间的内容,要了解整个流程不能光靠一个device和driver匹配 就且过。

首先来讲下platform这条总线。linux下外设要么属于一个class,要么挂载在一条bus下。platform是一条虚拟总线,比其他那些I2C ,SPI等传统总线,最大区别的就是它吧平台一些资源给注册到系统,统一管理,从后面代码可看出。

LCD要挂在platform这虚拟总线,那首先,platform bus在start kernel时,要自己先注册自己platform总线。

***************************************************

文件kernel\drivers\base\init.c

***************************************************

void __init driver_init(void)
{
/* These are the core pieces */
devtmpfs_init();
devices_init();
buses_init();
classes_init();
firmware_init();
hypervisor_init();

/* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init();//这个和buses_init 分开,platform虚拟总线注册。
cpu_dev_init();
memory_dev_init();
}

***************************************************

文件andrroid\kernel\init\main.c

***************************************************

static void __init do_basic_setup(void)
{
cpuset_init_smp();
usermodehelper_init();
shmem_init();
driver_init();
init_irq_proc();
do_ctors();
usermodehelper_enable();
do_initcalls();
}

static int __init kernel_init(void * unused)
{

...

do_basic_setup();

...

}

static noinline void __init_refok rest_init(void)
{

...
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);//创建kernel的第一个线程

...

}

asmlinkage void __init start_kernel(void)
{

...

rest_init(); // start_kernel的最后一个函数就是你了!~~~
}

***************************************************

文件andrroid\kernel\drivers\base\platform.c

***************************************************

struct device platform_bus = {
.init_name
= "platform",
};

struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs
= platform_dev_attrs,
.match
= platform_match,
.uevent
= platform_uevent,
.pm = &platform_dev_pm_ops,
};

int __init platform_bus_init(void)
{
int error;

early_platform_cleanup();

error = device_register(&platform_bus);  //在sys/device/下注册一个名字为platform的设备,说明总线也是一种device.即sys/devies/platform
if (error)
return error;
error =  bus_register(&platform_bus_type);//在sys/bus/下注册一个名字为platform的总线,sys/bus/platform
if (error)
device_unregister(&platform_bus);
return error;
}

说到这里出现了device的注册,我们先来看看device相关的

好吧,想来一份驱动模型上层的基本组成结构:

linux设备模型的基本组成结构:

类型

所包含的内容

对应内核数据结构

对应/sys

设备(Devices)

设备是此模型中最基本的类型,

以设备本身的连接按层次组织

struct device

/sys/devices/*/*/.../

设备驱动(Device Drivers)

在一个系统中安装多个相同设备,只需要一份驱动程序的支持

struct device_driver

/sys/bus/pci/drivers/*/

总线类型(Bus Types)

在整个总线级别对此总线上连接的所有设备进行管理

struct bus_type

/sys/bus/*/

设备类别(Device Classes)

这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备

struct class

/sys/class/*/

首先还记得在上面init文件:driver_init()这里面调用了devices_init();下面进入code里面看。

***************************************************

文件andrroid\kernel\drivers\base\platform.c

***************************************************

int __init devices_init(void)
{
devices_kset
= kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
dev_kobj = kobject_create_and_add("dev", NULL);
if (!dev_kobj)
goto dev_kobj_err;
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
if (!sysfs_dev_block_kobj)
goto block_kobj_err;
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
if (!sysfs_dev_char_kobj)
goto char_kobj_err;

return 0;

。。。。

}

到这里,就出现了kobject和kset的概念。

bus  ,devices drivers 是这个驱动模型的上层容器------koject ,kset是驱动模型的底层实现。从上面代码可以看出,对上层容器的操作,其实就是对kobject 和kset的操作,进而 往下看会发现,就是对sysfs的操作,,,,有思路了吗?

这里想大概提下:

kobject的sysfs入口----->目录;

一个kobject----------->一个文件目录;

分配给kobject的名字 ------>目录的名字;

kset ------->同类kobject的集合;

kobject 的属性 ----->目录下文件

parent和kset是 驱动模型 底层实现的 层次结构的实现。

来看看kobject和kset的code吧

***************************************************

文件andrroid\kernel\lib\kobject.c

***************************************************

struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)

//初始化和add一个kobject,也有分开的init和add的函数
{
struct kobject *kobj;
int retval;

kobj = kobject_create();//(1)
if (!kobj)
return NULL;

retval = kobject_add(kobj, parent, "%s", name);(2)
if (retval) {
printk(KERN_WARNING "%s: kobject_add error: %d\n",
      __func__, retval);
kobject_put(kobj);
kobj = NULL;
}
return kobj;
}

start------------------------------------------kobj = kobject_create();//(1)-----------------------------------------------------------------------------------

(1)

struct kobject *kobject_create(void)
{
struct kobject *kobj;

kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);//给kobj分配内存空间
if (!kobj)
return NULL;

kobject_init(kobj, &dynamic_kobj_ktype);//init 一个kobject (1.1)
return kobj;
}

(1.1)

void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;

//检查kobj和ktpye,

//ktype里面包含一个release 的方法和 对属性的操作
if (!kobj) {
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) {
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if (kobj->state_initialized) { //如果没init就初始化
/* do not error out as sometimes we can recover */
printk(KERN_ERR "kobject (%p): tried to init an initialized "
      "object, something is seriously wrong.\n", kobj);
dump_stack();
}

kobject_init_internal(kobj);(1.1.1)
kobj->ktype = ktype; //
return;

}

(1.1.1)

static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref); //kref计数初始化,计数加1;一个kobject有效则这个kref为大于0。当这个计数为0时,需要再判断,然后有必要则release这kobject。
INIT_LIST_HEAD(&kobj->entry);
kobj->state_in_sysfs = 0; //有没挂载到sysfs
kobj->state_add_uevent_sent = 0;
kobj->state_remove_uevent_sent = 0;
kobj->state_initialized = 1; //kobject初始化 过的 标志
}

end------------------------------------------kobj = kobject_create();//(1)-----------------------------------------------------------------------------------

start------------------------------------------retval = kobject_add(kobj, parent, "%s", name);(2)-----------------------------------------------------

kobject_add()-->kobject_add_varg()

static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,
   const char *fmt, va_list vargs)
{
int retval;

retval = kobject_set_name_vargs(kobj, fmt, vargs);//set kobject name 也就是目录名字
if (retval) {
printk(KERN_ERR "kobject: can not set name properly!\n");
return retval;
}
kobj->parent = parent;//如果有parent则赋值。这里dev_kobj kobject_create_and_add("dev", NULL); 故parent= NULL 
return kobject_add_internal(kobj);(2.1)
}

(2.1)

static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;

if (!kobj)
return -ENOENT;

//如果没set name 就ERROR
if (!kobj->name || !kobj->name[0]) {
WARN(1, "kobject: (%p): attempted to be registered with empty "
"name!\n", kobj);
return -EINVAL;
}

parent = kobject_get(kobj->parent);

/* join kset if set, use it as parent if we do not already have one */
if (kobj->kset) { //有kset则
if (!parent)
parent = kobject_get(&kobj->kset->kobj);//如有kset 没parent则 kset就是parent .还记得前面说过的kset和partent是 底层分层的实现吗??
kobj_kset_join(kobj);
kobj->parent = parent;
}

pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : "<NULL>",
kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");

error = create_dir(kobj); 

//create_dir()就是在parent目录下创建一个名字为kobject_name的目录,并且创建一些默认属性文件。 //再往下就是fs的entry 和inode的东西。。。不讲先了~~~~
if (error) {
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;

/* be noisy on error issues */
if (error == -EEXIST)
WARN(1, "%s failed for %s with "
    "-EEXIST, don't try to register things with "
    "the same name in the same directory.\n",
    __func__, kobject_name(kobj));
else
WARN(1, "%s failed for %s (error: %d parent: %s)\n",
    __func__, kobject_name(kobj), error,
    parent ? kobject_name(parent) : "'none'");
} else
kobj->state_in_sysfs = 1; //else 就是创建目录成功,set标志位

return error;
}

end------------------------------------------retval = kobject_add(kobj, parent, "%s",
name);(2)
-----------------------------------------------------

~~~~~~~~~~~~~~~~~上面是初始化和创建一个kobject的过程,也就是一个创建一个目录过程,kset就kobject的集合容器,最后还是回到kobject的操作。

有两条链表来管理kobject kset的。

那kset的init和add或者register就不讲了。。。

到这里,kboject和kset的creat和add 和 上层的bus devices drivers有毛关系?想到了吧?if no ,往下吧

所以driver_init()这里面调用了devices_init()先在/sys下创建了 /sys/devices 这个devices_kset 目录,当注册一个device到系统时,就是在目录下add一个devices

以及一个/sys/dev 目录等。然后再到buses_init() ,platform_bus_init();的调用。为啥先注册/sys/devices/在注册总线呢??

先回到:int __init platform_bus_init(void)
{
int error;

early_platform_cleanup();

error = device_register(&platform_bus);
if (error)
return error;
error =
 bus_register
(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}

start------------------------------------------device_register(&platform_bus)-----------------------------------------------------

int device_register(struct device *dev)
{
device_initialize(dev);(1)
return device_add(dev);(2)
}

(1)void device_initialize(struct device *dev)
{

//这个就是在上面那个int __init devices_init(void)
//{
// devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);

//}
dev->kobj.kset = devices_kset; //说明两点:1)注册的device在/sys/devices/XXX 2)总线也是一种device

kobject_init(&dev->kobj, &device_ktype); //
kobject inti kref +1 ,ktype赋值

INIT_LIST_HEAD(&dev->deferred_probe);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
}

(2)int device_add(struct device *dev)
{
struct device *parent = NULL;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;

dev = get_device(dev);
if (!dev)
goto done;

if (!dev->p) {
error = device_private_init(dev); //相关链表
if (error)
goto done;
}

/*
* for statically allocated devices, which should all be converted
* some day, we need to initialize the name. We prevent reading back
* the name, and force the use of dev_name()
*/
if (dev->init_name) { //有init_name就用这个
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;//set 完init_name就set NULL
}

/* subsystems can specify simple device enumeration */
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

if (!dev_name(dev)) {
error = -EINVAL;
goto name_error;
}

pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent); //get parent
if (kobj)
dev->kobj.parent = kobj;

/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));

/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error =
kobject_add
(&dev->kobj, dev->kobj.parent, NULL); //这个是关键
if (error)
goto Error;

/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);

error = device_create_file(dev, &uevent_attr); //在/sys/devices/XXX/ uevent文件
if (error)
goto attrError;

if (MAJOR(dev->devt)) { //有必要在sys/dev下创建相关文件
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;

error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;

devtmpfs_create_node(dev);
}

error = device_add_class_symlinks(dev); 

//创建符号链接。符号链接文件(Symblic Link)和硬链接文件(Hard Link)区别:(1)Hard Link 在//某个目录下新增一个文件名链接到某个inode的关联记录。文件名与目录有关,文件内容与inode有关。其实就是多个文件名 对应同一个 inode而已。不会增加inode数量。//hard link有限制:不能夸fs,不能link目录。(2)Symblic link 其实就是建立一个新的文件,这个新文件会让数据的读写指向他link的那个文件。类似快捷方式。当源文件被删除时
//,link文件打不开。Symbolic有自己的inode和block,因为他是新建的文件。

if (error)
goto SymlinkError;
error = device_add_attrs(dev);/add class ,group属性文件
if (error)
goto AttrsError;
error = bus_add_device(dev);//这里其实就是把sys/devices/XXX的目录符号链接到在sys/bus/XXXbus/devices。
if (error)
goto BusError;
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev);

/* Notify clients of device addition.  This call must come
* after dpm_sysfs_add() and before kobject_uevent().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
    BUS_NOTIFY_ADD_DEVICE, dev);

kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_probe_device(dev);//这在是在总线上probe device ,这个后面与driver probe再讲。
if (parent)
klist_add_tail(&dev->p->knode_parent,
      &parent->p->klist_children);

if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
      &dev->class->p->klist_devices);

/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
   &dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}

end------------------------------------------device_register(&platform_bus)-----------------------------------------------------

start------------------------------------------ bus_register(&platform_bus_type);-----------------------------------------------------

int __bus_register(struct bus_type *bus, struct lock_class_key *key)
{
int retval;
struct subsys_private *priv;

priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;

priv->bus = bus;
bus->p = priv;

BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);//set 总线名字
if (retval)
goto out;

priv->subsys.kobj.kset =
bus_kset; //bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL); ----->buses_init(void) //创建/sys/bus ,说明platform总线要add到/sys/bus/platform/

priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1; //devices和driver匹配用到

retval = kset_register(&priv->subsys);
//创建了/sys/bus/platform这个目录

if (retval)
goto out;

retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;

//创建这个目录/sys/bus/platform/devices/ ,add device时会把sys/devices/xxx的 目录 符号链接到这 这目录下。所以看这个文件里面内容是一样的
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}

priv->drivers_kset = kset_create_and_add("drivers", NULL,//创建这个目录/sys/bus/platform/devices/ 
&priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}

INIT_LIST_HEAD(&priv->interfaces);
__mutex_init(&priv->mutex, "subsys mutex", key);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);

retval = add_probe_files(bus);//add  /sys/bus/platform/这目录下创建两个文件:drivers_probe ,drivers_autoprobe (retval = bus_create_file(bus, //&bus_attr_drivers_probe);retval = bus_create_file(bus, &bus_attr_drivers_autoprobe);)
if (retval)
goto bus_probe_files_fail;

retval = bus_add_attrs(bus);
if (retval)
goto bus_attrs_fail;

pr_debug("bus: '%s': registered\n", bus->name);
return 0;

end----------------------------------------- bus_register(&platform_bus_type);-----------------------------------------------------

*******************************************************************************************************

*好了,platform的总线注册讲完了。大致流程是 在start_kernel-->kernel_init()--->driver_init();

*void __init driver_init(void)
*{
* /* These are the core pieces */
* devtmpfs_init();
* devices_init();//先创建sys/devices/
* buses_init();//创建sys/bus/
* classes_init();
* firmware_init();

* hypervisor_init();
*
*
* /* These are also core pieces, but must come after the
* * core core pieces.

* */
* platform_bus_init();//注册platform总线;

*创建sys/devices/platform //总线也是devices的一样,这也是为啥要先调用devices_init()

*sys/bus/platform/

*sys/bus/platform/devices/

*sys/bus/platform/drivers/

*sys/bus/platform/drivers_autoprobe文件

*sys/bus/platform/drivers_probe文件

*sys/bus/platform/uevent文件

*
* cpu_dev_init();
* memory_dev_init();
*}

**********************************************************************************************************************

platform总线在启动kernel注册分析完毕,其实里面也包含bus 和devices的 creat 和add.

下面讲讲platform的devices 注册和 devices_driver的注册和匹配

***********************************************************************************************************************

抱歉!评论已关闭.