加油加油坚持住!
1、
Linux驱动模型:驱动模型即将各模型中共有的部分抽象成C结构体。Linux2.4版本前无驱动模型的概念,每个驱动写的代码因人而异,随后为规范书写方式,发明了驱动模型,即提取公共信息组成一个个类,为添加设备和驱动提供统一的接口,使得开发变得简单化、规范化,但驱动本身仍然存在开发难度,设备(触摸屏、蓝牙等)的最底层代码不需要驱动工程师搞,Linux设备模型也不需要驱动工程师搞,驱动工程师需要做的是把二者结合。早期的驱动是需要手动inmod的,有驱动模型后直接注册即可。
驱动模型分为四类:类、总线、设备、驱动、Kobject、sysfs、udev。
深入理解Linux设备模型与驱动开发-优快云博客 (五星推荐)
比如led,rtc,beep,key这类结构简单的设备,它们的控制不需要时序,它们没有相应的物理总线。所以linux内核不会为它们创建驱动总线。为了是这部分设备的驱动开发也能够遵循设备驱动模型,linux内核引入了一种虚拟的总线——平台总线(platform bus)。
2、Linux的底层模型(主要是写内核的人写的,搞驱动的一般不用)
基本结构体Kobject:各类对象的最小单元,对象引用计数(kref)、维护对象链表(entry、parent)、对象上锁(kset)、对用户空间的描述(ktype)。
kobj_type:提供在sysfs下的操作。attribute:sysfs下的属性;sysfs_ops:对象在sysfs下的操作方法。
kset:描述sysfs下的目录关系。
驱动开发注意包含和引用的关系,包含是结构体成员,绑定是指针。如
struct kobject {
const char *name;
struct list_head entry; // 上下节点 包含关系
struct kobject *parent; // 上下层之间 绑定关系
struct kset *kset; // 上锁 绑定关系
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref; // 对象引用计数
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
3、Linux的上层模型
设备:
struct device是硬件设备在内核驱动框架中的抽象。
device_register用于向内核驱动框架注册一个设备。
通常device不会单独使用,而是被包含在一个具体设备结构体中,如struct usb_device。
驱动:
device_driver是驱动程序在内核驱动框架中的抽象。
name:驱动程序的名字,用于驱动与对应设备的匹配。
probe:用于检测此设备是否可以用本驱动,毕竟不同厂家的设备对应的驱动是不同的。
类:
相关结构体:struct class 和 struct class_device。
class的真正意义在于作为同属于一个class的多个设备的容器
内核中驱动会被类和总线双重管理。
模型思想即面向对象的思想,结构体包一层结构体即类继承类,有一个基类(device_driver),然后创建子类(usb_device)。驱动开发要有思想复杂度!一层套一层。
4、Linux设备总线开发目的是方便管理。
CPU与外部通信的方式:
32位地址总线寻址:片上外设可以通过SOC的内存寻址,因为设备是硬编码进SOC的,硬编码一般是数字前端工程师设计的,使用Verilog HDL语言。(在Linux中设计为平台总线形式,大部分设备均为平台总线)
通过IIC、SPI、USB总线与外部通信。
EXPORT_SYMBOL_GPL的应用:
// 如下代码表示函数允许被其他仅支持GPL的内核模块使用,而不是使用.h声明的方式
void platform_driver_unregister(struct platform_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_unregister);
platfrom设备驱动分析:
// device驱动源码分析
static struct platform_driver pm860x_backlight_driver = { //
.driver = {
.name = "88pm860x-backlight", // name为驱动唯一标识符
.owner = THIS_MODULE,
},
.probe = pm860x_backlight_probe, // 驱动的侦测函数
.remove = pm860x_backlight_remove,// 驱动的卸载函数
};
static int __init pm860x_backlight_init(void)
{
return platform_driver_register(&pm860x_backlight_driver);
}
module_init(pm860x_backlight_init); // 模块注册
static void __exit pm860x_backlight_exit(void)
{
platform_driver_unregister(&pm860x_backlight_driver);
}
module_exit(pm860x_backlight_exit); // 模块卸载
static struct platform_device this_device = {
.name = "tv_ntsc", // 设备名,用于内核设备树和其他机制中标识这个设备
.id = 0,
.dev = {
.platform_data = &ntsc_panel_data,
}
};
static int __init ntsc_init(void) // __init阶段执行,执行完毕后释放该数据段
{
int ret;
ret = platform_driver_register(&this_driver); // 注册驱动
if (!ret) {
ret = platform_device_register(&this_device);// 注册成功则注册该设备
if (ret)
platform_driver_unregister(&this_driver);// 否则,卸载设备,以保持内核稳定性。
}
return ret;
}
module_init(ntsc_init); // 模块注册
5、
一个驱动可以对应多个设备。当设备加载进来时,会通过platform_match函数匹配对应的驱动(根据驱动名)。若存在,则调用driver的probe函数完成驱动的安装。
内核提供platform_device,写驱动的人提供platform_driver,可以全局检索name的形式检索驱动对应的设备。
设备于内核中的执行顺序:
s3c24xx_led -->mini2440_led1 -->mini2440_devices-->platform_add_devices-->platform_device_register 完成设备添加,可从/sys目录找到。
然后内核根据设备名s3c24xx_led查找对应的驱动,若存在,则执行。以此实现platform框架下的设备和驱动匹配。
static struct platform_device mini2440_led1 = {
.name = "s3c24xx_led",
.id = 1,
.dev = {
.platform_data = &mini2440_led1_pdata, // 这段绑定关系交由各驱动模块自己执行
},
};
struct device {
struct device *parent;
.......
.......
void *platform_data; /* Platform specific data, device
core doesn't touch it */ // 此段指针交由各驱动自行绑定。
.......
.......
}
// driver部分,将
static int s3c24xx_led_probe(struct platform_device *dev)
{
struct s3c24xx_led_platdata *pdata = dev->dev.platform_data; // 此处实现自定义数据的赋值操作
struct s3c24xx_gpio_led *led;
int ret;
led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL);
if (led == NULL) {
dev_err(&dev->dev, "No memory for device\n");
return -ENOMEM;
}
platform_set_drvdata(dev, led);
......
......
}
6、
驱动的核心思考点:
能想清楚数据谁写的?给谁了?从哪里传递到哪里?谁接受到数据干啥了?
想想这么设计的优势、必要性是什么?
自己写一个出来。
以上三条就是驱动开发核心部分,区别是不同驱动对应的不同。