Kernel-设备驱动注册

设备驱动

​ Linux设备驱动分为三个实体总线、设备(device)、驱动(driver), 平台总线将设备和驱动匹配。

2.6 内核加入了platform虚拟总线,platform机制将设备本身的资源注册进内核,由内核统一管理。

  • 在系统注册任意一个驱动时, 都会寻找对应的设备。
  • 当系统注册设备时,系统也会寻找对应的驱动进行匹配。

总线-设备-驱动 关系

Linux设备

Linux将设备分为三大类: 字符设备、块设备、网络设备。

  • 字符设备, 字符设备是能够像字节流一样被访问的设备。常见led,蜂鸣器,串口,键盘等等。
  • 块设备, 块设备通过内存缓冲区访问,可以随机存取的设备。通过传输固定大小的数据(一般为512或1k)来访问设备。一般性的理解就是存储介质类的设备,常见字符设备有u盘,TF卡,eMMC,电脑硬盘,光盘等
  • 网络设备,可以和其他主机交换数据的设备。常见的以太网设备,wifi,蓝牙等

cat /proc/devices 可以看到不同的设备都有编号。

主设备号 256, 从设备号 256。 故理论上就有256 * 256 个设备号。


主设备号、从设备号

在设备管理中,除了设备类型外,内核还需要一对称为主从设备号的参数, 才能唯一标识一个设备。

主设备号

​ 用于标识驱动程序, 相同的主设备号使用相同的驱动程序。

从设备号

​ 用于标识同一驱动程序的不同硬件。


Linux 驱动 和 设备 注册过程

Linux内核会要求每出现一个设备就要向总线汇报, 或者 出现一个驱动,也要向总线汇报, 或者叫做注册。

系统初始化的时候,会扫描链接了哪些设备,并为每一个设备建立一个struct platform_device的变量,然后将设备的变量插入到devices链表中。如下图所示:

Linux_device链表

系统初始化任意一个驱动程序的时候,也要准备一个struct platform_driver 结构变量, 然后将驱动的变量插入到drivers链表,如下图所示:

Linux_drivers链表

Linux总线为了将设备 与 驱动绑定,方便管理。 在注册设备,或者注册驱动时,都会寻找与之匹配的设备,而匹配由总线platform_match函数完成。

注册设备的结构体platform_device, 注册驱动的结构体为platform_driver。设备和驱动的结构体成员name字段,相同则匹配。 并且匹配成功,则会调用platform_driver中的probe函数,注册驱动。

platform_match

platform_match() 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* platform_match - bind platform device to platform driver.
* @dev: device.
* @drv: driver.
*
* Platform device IDs are assumed to be encoded like this:
* "<name><instance>", where <name> is a short description of the type of
* device, like "pci" or "floppy", and <instance> is the enumerated
* instance of the device, like '0' or '42'. Driver IDs are simply
* "<name>". So, extract the <name> from the platform_device structure,
* and compare it against the name of the driver. Return whether they match
* or not.
*/
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);

/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;

/* Then try to match against the id table */
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);
}


驱动 platform_driver

  • 头文件:

    • 结构体: platform_driver

    • 位于 include/linux/platform_device.h 头文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      extern int platform_driver_register(struct platform_driver *)
      extern void platform_driver_unregister(struct platform_driver *);

      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 (*resume)(struct platform_device *);
      struct device_driver driver;
      const struct platform_device_id *id_table;
      }

  • 驱动常见状态:

    • 初始化

    • 移除

    • 休眠

    • 复位

  1. probe函数:

    platform_match 函数匹配之后, 驱动调用的初始化函数

  2. remove函数:

    移除驱动函数

  3. suspend函数

    悬挂(休眠)驱动函数

  4. resume函数:

    休眠后恢复驱动

  5. shudown 函数:

  6. device_driver 数据结构

    name 和 注册设备name 一致

    owner 一般赋值THIS_MODULE


设备 platform_device

头文件:

  • 结构体: platform_device

  • 位于 include/linux/platform_device.h 头文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    struct platform_device {
    const char * name; //设备名称, 在 /sys/devices 会显示。
    int id; //设备id,用于插入总线并且具有相同name的设备编号,如果只有一个设备则为-1
    struct device dev; //结构体中内嵌的device结构体
    u32 num_resources;//设备使用资源的数量
    struct resource * resource;//设备使用的资源数组

    const struct platform_device_id *id_entry;

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata archdata;
    };
  • 第二个参数”id” 表示子设备编号, 一个设备如果有多个子设备,则需要写入子设备号数量, 只有一个则为 -1

    定义于: $kernel/drivers/base/platform.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    /**
    * platform_device_register - add a platform-level device
    * @pdev: platform device we're adding
    */
    int platform_device_register(struct platform_device *pdev)
    {
    device_initialize(&pdev->dev);
    return platform_device_add(pdev);
    }
    EXPORT_SYMBOL_GPL(platform_device_register);

    /**
    * platform_device_unregister - unregister a platform-level device
    * @pdev: platform device we're unregistering
    *
    * Unregistration is done in 2 steps. First we release all resources
    * and remove it from the subsystem, then we drop reference count by
    * calling platform_device_put().
    */
    void platform_device_unregister(struct platform_device *pdev)
    {
    platform_device_del(pdev);
    platform_device_put(pdev);
    }
    EXPORT_SYMBOL_GPL(platform_device_unregister);

注册设备到平台总线:

以三星itop-4412为例:

方法适用于源码:(静态注册)

1
vi $itop-kernel/arch/arm/mach-exynos/mach-itop4412.c

Hello_module_src

platform_device_初始化数组

注: s3c_device_hello_ctl 即注册的platform_device 设备变量信息.

按照其他模块方式编写。 hello模块即可, 同时确保 宏定义 ,变量未重复。

对应在 $itop-kernel/drivers/char/Kconfig 添加对应模块选择配置

1
2
3
4
5
config HEOLL_CTL
bool "Enable hello config"
default y
help
Enable hello config

make menuconfig 所示如下图:

hello_module

即保存退出, 重新编译 make zImage , 烧写至开发板。

ls -ls /sys/devices/platform 即可看到新注册的hello驱动。

ls /sys/class/misc/


方法二:(动态注册)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static  void hello_dev_release(struct device *dev)
{
printk("hello_dev release");
}

static struct platform_device hello_dev = {
.name = "hello_name", // device`s name
.id = -1,
.dev = {
.release = hello_dev_release,
},
};

//注册设备;
static int __init device_init(void)
{
int ret = 0;
ret = platform_device_register(&hello_dev);
printk(KERN_WARNING "platform_device_register ret = %d\n", ret);

return 0;
}

static void __exit device_exit(void)
{
platform_device_unregister(&hello_dev);
printk(KERN_WARNING "platform_device_register exit\n");
}

module_init(device_init);
module_exit(device_exit);

完整源码


方法三:(mknod)

1
2
3
4
5
6
7
8
9
10
11
12
13
 创建特殊文件。  

  mknod Name { b | c } Major Minor

  创建 FIFO(已命名的管道)

  mknod Name { p }


TYPE may be:
b create a block (buffered) special file
c, u create a character (unbuffered) special file
p create a FIFO

示例:

1
mknod /dev/input/mouse0  c  12  32


注册驱动到平台总线

  • platform_driver_register函数platform_driver_unregister函数 用于注册 和 卸载驱动。
  • platform_driver结构体
  1. 定义宏变量 DRIVER_NAME "hello_ctl" (注: 与注册hello设备时的名称相同。)

  2. 编写模块 module_init module_exit 分别在其中注册 和 卸载驱动platform_driver_register platform_driver_unregister

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    static int __init hello_init(void)
    {

    int DriverState = 0;

    printk(KERN_WARNING "Welcome hello-module init\n");

    DriverState = platform_driver_register(&hello_driver);

    printk(DRIVER_NAME "state = \t", DriverState);

    return 0;
    }

    static void __exit hello_exit(void)
    {

    printk(KERN_WARNING "byebye hello-module exit\n");

    platform_driver_unregister(&hello_driver);
    }

    //注册 并初始化模块
    module_init(hello_init)

    //模块退出, 并关闭内存;
    module_exit(hello_exit)

  3. 其中注册 和 卸载驱动函数的传入值为 platform_driver 类型, 即我们编写,将要实现的驱动程序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    struct platform_driver hello_driver = 
    {
    .probe = &hello_probe,
    .remove = &hello_remove,
    .shutdown = &hello_shutdown,
    .suspend = &hello_suspend,
    .resume = &hello_resume,
    .driver =
    {
    .name = DRIVER_NAME, //设备注册- 查看 $itop_kernel/
    .owner = THIS_MODULE,
    }
    };

    其次定义并且实现 hello_driver; 此类型可查看 驱动 platform_driver

问题: probe_hello: disagrees about version of symbol module_layout

解决办法:

uname -r 查看系统内核版本, 同时查看 makefile 提供内核源码版本, 提供与系统内核版本对应一致的版本源码。

注意:

  1. 先注册platform_driver结构体, 使用probe 注册杂项设备, 设置两个 name.

或者

  1. 在 module_init 中直接使用 misc_register 注册杂项设备, 其效果相同。

如下:

  1. probe 示例源码
  2. misc 示例源码


设备节点

杂项设备的主设备号是10, 在任何Linux系统中它都是固定的。

杂项设备注册函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
include/linux/miscdevice.h


struct miscdevice {
int minor; // 设备号, 赋值为 MISC_DYNAMIC_MINOR
const char *name; // 设备名称。
const struct file_operations *fops; //file_operations 结构体。
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};

extern int misc_register(struct miscdevice * misc);
extern int misc_deregister(struct miscdevice *misc);
1
extern int misc_register(struct miscdevice *misc);

杂项设备注册函数; 一般在probe中调用,参数为struct miscdevice

1
extern int misc_deregister(struct miscdevice *misc);

杂项设备卸载函数;一般是在remove函数中用于卸载驱动。

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int __init hello_init(void)
{
int miscstate = 0;
//杂项设备;
miscstate = misc_register(&hello_device);// 区别: 不进行platform_match 匹配调用,直接注册设备。
printk(KERN_WARNING "misc_register = %d\n", miscstate);
}

static void __exit hello_exit(void)
{
printk(KERN_WARNING "hello_device exit\n");
misc_deregister(&hello_device); //删除杂项设备;
return 0;
}

module_init(hello_init);
module_exit(hello_exit);

ls /dev/ 即可看到我们创建的杂项设备节点:

1
0 crw------- 1 root root 10, 55 Dec 20 00:00 hello_device_name

即: 我们便可理解:设备节点,驱动名,设备名其实并无任何关系。


Linux file_operations结构体

file_operations 结构体的成员函数属于驱动设计的主体内容, 里面的函数和Linux系统给应用程序提供系统接口一一对应。

file_operations 结构体位于 include/linux/fs.h, 定义为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
* NOTE:
* all file operations except setlease can be called without
* the big kernel lock held in all filesystems.
*/
struct file_operations {
struct module *owner; //一般是 THIS_MODULE
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
/* remove by cym 20130408 support for MT660.ko */
#if 0
//#ifdef CONFIG_SMM6260_MODEM
#if 1// liang, Pixtree also need to use ioctl interface...
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
#endif
#endif
/* end remove */
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//与写功能稍微重合,主要针对IO口的控制。
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *); //对应上层的open函数。打开文件。
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *); //对应上层close函数,打开文件操作之后需要关闭。
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
/* add by cym 20130408 support for MT6260 and Pixtree */
#if defined(CONFIG_SMM6260_MODEM) || defined(CONFIG_USE_GPIO_AS_I2C)
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
#endif
/* end add */
};

iTop4412_leds.c 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include <linux/init.h>
#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/fs.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
//#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#include <linux/regulator/consumer.h>
//#include "gps.h"
#include <linux/delay.h>

#define LEDS_DEBUG
#ifdef LEDS_DEBUG
#define DPRINTK(x...) printk("LEDS_CTL DEBUG:" x)
#else
#define DPRINTK(x...)
#endif

#define DRIVER_NAME "leds"


#if defined(CONFIG_CPU_TYPE_SCP_ELITE) || defined(CONFIG_CPU_TYPE_POP_ELITE) || defined(CONFIG_CPU_TYPE_POP2G_ELITE)
static int led_gpios[] = {
EXYNOS4_GPL2(0),
EXYNOS4_GPK1(1),
};

#elif defined(CONFIG_CPU_TYPE_SCP_SUPPER) || defined(CONFIG_CPU_TYPE_POP_SUPPER) || defined(CONFIG_CPU_TYPE_POP2G_SUPPER)


static int led_gpios[] = {
#if defined(CONFIG_MTK_COMBO_COMM) || defined(CONFIG_MTK_COMBO_COMM_MODULE)
EXYNOS4_GPC0(2),
#else
EXYNOS4_GPX2(5),
#endif
EXYNOS4_GPX0(1),
};


#endif
#define LED_NUM ARRAY_SIZE(led_gpios)

int leds_open(struct inode *inode,struct file *filp)
{
DPRINTK("Device Opened Success!\n");
return nonseekable_open(inode,filp);
}

int leds_release(struct inode *inode,struct file *filp)
{
DPRINTK("Device Closed Success!\n");
return 0;
}

int leds_pm(bool enable)
{
int ret = 0;
printk("debug: LEDS PM return %d\r\n" , ret);
return ret;
};

long leds_ioctl(struct file *file,unsigned int cmd,unsigned long arg)
{
printk("debug: leds_ioctl cmd is %d\n" , cmd);

switch(cmd)
{
case 0:
case 1:
if (arg > LED_NUM) { //LED_NUM = 2
return -EINVAL;
}

gpio_set_value(led_gpios[arg], cmd);
break;

default:
return -EINVAL;
}

return 0;
}

//rokcy 将系统调用 和 驱动程序关联起来的关键数据结构;
static struct file_operations leds_ops = {
.owner = THIS_MODULE, // 指向拥有这个结构的模块指针;
.open = leds_open, //对设备文件进行的第一个操作;
.release= leds_release, //文件结构被释放时引用这个操作:
.unlocked_ioctl = leds_ioctl,//ioctl 系统调用提供了发出设备特定命令的方法.
};

static struct miscdevice leds_dev = {
.minor = MISC_DYNAMIC_MINOR,
.fops = &leds_ops,
.name = "leds",
};


static int leds_probe(struct platform_device *pdev)
{
int ret, i;
char *banner = "leds Initialize\n";

printk(banner);//

for(i=0; i<LED_NUM; i++)
{
ret = gpio_request(led_gpios[i], "LED");
if (ret) {
printk("%s: request GPIO %d for LED failed, ret = %d\n", DRIVER_NAME,
led_gpios[i], ret);
return ret;
}

s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
gpio_set_value(led_gpios[i], 1);
}

ret = misc_register(&leds_dev);
if(ret<0)
{
printk("leds:register device failed!\n");
goto exit;
}

return 0;

exit:
misc_deregister(&leds_dev);
return ret;
}

static int leds_remove (struct platform_device *pdev)
{
misc_deregister(&leds_dev);

return 0;
}

static int leds_suspend (struct platform_device *pdev, pm_message_t state)
{
DPRINTK("leds suspend:power off!\n");
return 0;
}

static int leds_resume (struct platform_device *pdev)
{
DPRINTK("leds resume:power on!\n");
return 0;
}

//rokcy 此结构体 即函数定义; -- 针对不同信号响应信息;
static struct platform_driver leds_driver = { //驱动结构体;
.probe = leds_probe, //rokcy 驱动调用初始化;
.remove = leds_remove, //rocky 移除驱动函数
.suspend = leds_suspend,//rocky 休眠函数;
.resume = leds_resume, //休眠后恢复驱动
.driver = {
.name = DRIVER_NAME, // 与 注册设备 name 一致;
.owner = THIS_MODULE,// 赋值 THIS_MODULE
},
};

static void __exit leds_exit(void)
{
platform_driver_unregister(&leds_driver);
}

static int __init leds_init(void)
{
return platform_driver_register(&leds_driver); //[2] 驱动注册;
}

module_init(leds_init); //rocky 初始化加载;
module_exit(leds_exit);

MODULE_LICENSE("Dual BSD/GPL");


导出模块 以及符号的相互作用

Linux2.6 内核的/proc/kallsyms 文件对应内核符号表,它记录了符号以及符号所在的内存地址,模块可以使用下列宏导到内核符号表中。

1
2
EXPORT_SYMBOL(符号名)                      任意模块均可
EXPORT_SYMBOL_GPL(符号名) 只使用包含GPL许可权的模块

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <linux/module.h>    /*module_init()*/  
#include <linux/kernel.h> /* printk() */
#include <linux/init.h> /* __init __exit */

int add_test(int a ,int b)
{
return a + b;
}

int sub_test(int a,int b)
{
return a - b;
}

EXPORT_SYMBOL(add_test);
EXPORT_SYMBOL(sub_test);

MODULE_AUTHOR("Rocky");
MODULE_LICENSE("GPL");

执行cat /proc/kallsyms | grep test 即可找到以下信息,表示模块确实加载到内核表中。

1
2
3
4
5
6
7
8
9
10
f88c9008 r __ksymtab_sub_integar        [export_symb]  
f88c9020 r __kstrtab_sub_integar [export_symb]
f88c9018 r __kcrctab_sub_integar [export_symb]
f88c9010 r __ksymtab_add_integar [export_symb]
f88c902c r __kstrtab_add_integar [export_symb]
f88c901c r __kcrctab_add_integar [export_symb]
f88c9000 T add_tes [export_symb]
f88c9004 T sub_tes [export_symb]
13db98c9 a __crc_sub_integar [export_symb]
e1626dee a __crc_add_integar [export_symb]

在其它模块中可以引用此符号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <linux/module.h>    /*module_init()*/  
#include <linux/kernel.h> /* printk() */
#include <linux/init.h> /* __init __exit */

#define DEBUG //open debug message

#ifdef DEBUG
#define PRINTK(fmt, arg...) printk(KERN_WARNING fmt, ##arg)
#else
#define PRINTK(fmt, arg...) printk(KERN_DEBUG fmt, ##arg)
#endif

extern int add_test(int a ,int b);
extern int sub_test(int a,int b);

static int __init hello_init(void)
{
int a,b;

a = add_test(10,20);
b = sub_test(30,20);
PRINTK("the add test result is %d",a);
PRINTK("the sub test result is %d\n",b);
return 0;
}

static void __exit hello_exit(void)
{
PRINTK(" Hello World exit\n ");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_AUTHOR("Rocky");
MODULE_LICENSE("GPL");


platform 机制:

  1. 总线注册机制

    1. Kernel_init()

    2. do_basic_setupo()

    3. driver_init()

    4. platform_bus_init()

    5. bus_register()

      注册系统platform总线. 内核启动初始化,自动维护。

  2. 添加设备阶段:

    1. Platform_device_register()
    2. Platform_device_add()
    3. pdev->dev.bus = &platform_bus_type && device_add()

    此步骤操作具体可以查看 $kernel/arch/arm/mach-exynos/mach-itop4412.c

  3. 驱动注册阶段:

    1. Platform_driver_register()
    2. driver_register()
    3. bus_add_driver()
    4. driver_attach()
    5. bus_for_each_dev()

    对在每个挂载虚拟的platform bus的设备做 driver_attach()-> driver_probe_device()

    判断drv->bus->match() 是否执行成功, 此时通过指针执行platform_match() 对比name。


知识点:

  1. proc 目录是虚拟文件系统, 可以为Linux用户空间和内核空间提供交互, 它存在于内存中, 而不占用实际的flash或者硬盘空间。
  2. /proc/devices/ 里面的设备是加载驱动程序生成的, 即现有驱动创建信息。
  3. /dev/ 下的设备是通过创建设备节点生成的。用户通过此设备节点访问内核驱动。 即系统挂载的实际设备。
  4. /sys/devices/ 里面的设备都是系统初始化, make zImage 编译后注册的新设备。
  5. /proc/misc 查看pc机Ubuntu系统的杂项设备。
  6. /sys/module 查看模块信息, 即lsmod信息

Linux_设备驱动

platform设备驱动

欣赏此文? 求鼓励,求支持!