这几天在为s3c2440添加TVP5150驱动时,首次理解多了一些Linux驱动模型。在这里共享一下学习心得。
总线
说总线之前,先想想,计算机里的设备很多都是寄生在一个“总线”上的。比如USB键盘和鼠标是属于USB总路线上的设备,网卡是在PCI总线上的。于是Linux中每一个设备都可以属于一个总线。
对于一个总线而言,可以通过定义一个struct bus_type来描述一个总线:
struct bus_type {
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct bus_type_private *p;
};
当检测到一个设备“插入”时(谁去检测暂且不关心),便会枚举所有存在的驱动,挨个以它为参数调用bus_type->match 函数,match函数的作用就是判断这个刚“插入”的设备是否与当前驱动吻合(也就是这个驱动是不是这个设备的驱动),如果是的话,就加载这个驱动,剩下的设备(struct device)初始化等事情,交给设备驱动(struct device_driver)来做。
设备
一个设备可以有两种方式添加,一种就是现在流行的热拔插(HOTPLUG),一种是固定的设备。一个设备添加时,便会导致内核枚举所有驱动以寻找该设备的驱动(如上一章所说),匹配设备由总线驱动完成。
在Linux中设备以struct device来表示。
设备驱动
设备驱动在Linux中以struct device_driver来表示。如总线一章所说设备驱动是在总线驱动匹配成功设备和驱动之后调用的。
举例
这里以在s3c2440中添加一个tvp5150视频解码芯片作为例子,说明这个驱动模型的工作。首先,tvp5150使用的是I2C总线,Linux的S3C2440已经有了I2C总线的驱动。然而,I2C总线不具备自动探测设备的功能,由于情景限制,我们也不需要自动探测设备,于是我们手动添加一个struct device。这里提到静态的设备,即不能自动探测的,系统中固定有的设备,用struct platform_device表示。这个struct platform_device字面上的意思就是“平台设备”,它和struct device的关系很微妙,从C++的角度来看,struct platform_device是struct device的子类,只不过在这儿用了C语言来实现。
对于静态的设备(“平台设备”),我们在架构初始化的函数中添加,对于 mini2440而言,这个函数位于 arch/arm/mach-s3c2440/mach-mini2440.c中。我们添加一个描述设备的静态函数:
static struct i2c_board_info mini2440_tvp5150_info = {
I2C_BOARD_INFO("tvp5150",0xba),
.irq = IRQ_EINT20,
};
static struct platform_device s3c_device_tvp5150 = {
.name = "s3c2440_tvp5150_adapter",
.id = -1,
.dev = {
.platform_data = &mini2440_tvp5150_info
},
};
最重要的参数就是platform_device中的name和dev了。name指明了这个平台设备的名称,在platform总线(该总线为一虚拟的总线,用于管理静态设备的)中匹配驱动要靠这个名称,只有这个名称和驱动的名称一样时,才能调用相应的驱动。platform_data可以作为在调用驱动时,设备给予驱动的一些数据信息。在这里为i2c_board_info的,其中指明了i2c设备的名称和地址。稍候在驱动程序中,我们将使用这个数据来创建一个i2c设备。
其实我们在这是绕一个圈子,本来可以直接添加一个i2c设备到i2c总线上,i2c总线自然会加载这个设备驱动(Linux中已经包含TVP5150的驱动)。这里这么做的原因是,我们还要为我们自己的情景,自定义这个tvp5150的参数,为了不破坏内核原有的驱动,我们只能自己增加驱动来完成我们的工作。
在一个标准的Linux内核模块里,我们要添加一个驱动,即有:
static struct platform_driver s3c2440_tvp5150_driver = {
.probe = s3c2440_tvp5150_probe,
.remove = s3c2440_tvp5150_remove,
.driver = {
.name = "s3c2440_tvp5150_adapter",
.owner = THIS_MODULE,
}
};
static int __init s3c2440_tvp5150_init (void){
return platform_driver_register (&s3c2440_tvp5150_driver);
}
static void __exit s3c2440_tvp5150_cleanup (void){
platform_driver_unregister (&s3c2440_tvp5150_driver);
}
module_init (s3c2440_tvp5150_init);
module_exit (s3c2440_tvp5150_cleanup);
这里就声明并初始化了一个platform_driver,它是struct device_driver的“子类”,里面的name参数必须和设备里的name一致才能匹配成功。在模块初始化函数里,我们调用了platform_driver_register来注册这个驱动。当设备添加,总线匹配成功的时候,调用s3c2440_tvp5150_probe函数,在这个函数里面,我们添加了一个i2c设备到i2c总线上,于是tvp5150的驱动便被调用起来了。
总线
说总线之前,先想想,计算机里的设备很多都是寄生在一个“总线”上的。比如USB键盘和鼠标是属于USB总路线上的设备,网卡是在PCI总线上的。于是Linux中每一个设备都可以属于一个总线。
对于一个总线而言,可以通过定义一个struct bus_type来描述一个总线:
struct bus_type {
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct bus_type_private *p;
};
当检测到一个设备“插入”时(谁去检测暂且不关心),便会枚举所有存在的驱动,挨个以它为参数调用bus_type->match 函数,match函数的作用就是判断这个刚“插入”的设备是否与当前驱动吻合(也就是这个驱动是不是这个设备的驱动),如果是的话,就加载这个驱动,剩下的设备(struct device)初始化等事情,交给设备驱动(struct device_driver)来做。
设备
一个设备可以有两种方式添加,一种就是现在流行的热拔插(HOTPLUG),一种是固定的设备。一个设备添加时,便会导致内核枚举所有驱动以寻找该设备的驱动(如上一章所说),匹配设备由总线驱动完成。
在Linux中设备以struct device来表示。
设备驱动
设备驱动在Linux中以struct device_driver来表示。如总线一章所说设备驱动是在总线驱动匹配成功设备和驱动之后调用的。
举例
这里以在s3c2440中添加一个tvp5150视频解码芯片作为例子,说明这个驱动模型的工作。首先,tvp5150使用的是I2C总线,Linux的S3C2440已经有了I2C总线的驱动。然而,I2C总线不具备自动探测设备的功能,由于情景限制,我们也不需要自动探测设备,于是我们手动添加一个struct device。这里提到静态的设备,即不能自动探测的,系统中固定有的设备,用struct platform_device表示。这个struct platform_device字面上的意思就是“平台设备”,它和struct device的关系很微妙,从C++的角度来看,struct platform_device是struct device的子类,只不过在这儿用了C语言来实现。
对于静态的设备(“平台设备”),我们在架构初始化的函数中添加,对于 mini2440而言,这个函数位于 arch/arm/mach-s3c2440/mach-mini2440.c中。我们添加一个描述设备的静态函数:
static struct i2c_board_info mini2440_tvp5150_info = {
I2C_BOARD_INFO("tvp5150",0xba),
.irq = IRQ_EINT20,
};
static struct platform_device s3c_device_tvp5150 = {
.name = "s3c2440_tvp5150_adapter",
.id = -1,
.dev = {
.platform_data = &mini2440_tvp5150_info
},
};
最重要的参数就是platform_device中的name和dev了。name指明了这个平台设备的名称,在platform总线(该总线为一虚拟的总线,用于管理静态设备的)中匹配驱动要靠这个名称,只有这个名称和驱动的名称一样时,才能调用相应的驱动。platform_data可以作为在调用驱动时,设备给予驱动的一些数据信息。在这里为i2c_board_info的,其中指明了i2c设备的名称和地址。稍候在驱动程序中,我们将使用这个数据来创建一个i2c设备。
其实我们在这是绕一个圈子,本来可以直接添加一个i2c设备到i2c总线上,i2c总线自然会加载这个设备驱动(Linux中已经包含TVP5150的驱动)。这里这么做的原因是,我们还要为我们自己的情景,自定义这个tvp5150的参数,为了不破坏内核原有的驱动,我们只能自己增加驱动来完成我们的工作。
在一个标准的Linux内核模块里,我们要添加一个驱动,即有:
static struct platform_driver s3c2440_tvp5150_driver = {
.probe = s3c2440_tvp5150_probe,
.remove = s3c2440_tvp5150_remove,
.driver = {
.name = "s3c2440_tvp5150_adapter",
.owner = THIS_MODULE,
}
};
static int __init s3c2440_tvp5150_init (void){
return platform_driver_register (&s3c2440_tvp5150_driver);
}
static void __exit s3c2440_tvp5150_cleanup (void){
platform_driver_unregister (&s3c2440_tvp5150_driver);
}
module_init (s3c2440_tvp5150_init);
module_exit (s3c2440_tvp5150_cleanup);
这里就声明并初始化了一个platform_driver,它是struct device_driver的“子类”,里面的name参数必须和设备里的name一致才能匹配成功。在模块初始化函数里,我们调用了platform_driver_register来注册这个驱动。当设备添加,总线匹配成功的时候,调用s3c2440_tvp5150_probe函数,在这个函数里面,我们添加了一个i2c设备到i2c总线上,于是tvp5150的驱动便被调用起来了。