-
把驱动程序编译进内核方法:
-
把驱动源码放到内核源码树中:根据设备类型不同,把源文件放在linux源码/drivers中相应的目录下。字符设备,通常放进
drivers/char/目录下 -
修改
Kconfig文件(让图形配置菜单能看到它):-
用文本编辑器打开
drivers/char/Kconfig -
合适的位置(或者文件末尾
endmenu之前),添加以下配置文本:
config CONFIG_HELLO_DRV
tristate "100ask Hello Driver Support"
default y
help
This is a hello world driver for learning Linux device model. If you say Y here, the driver will be compiled into the kernel. -
tristate(三态) :表示这个驱动可以有三种选择:Y(编译进内核)、M(编译成模块)、N(不编译)。 -
default y:这一步是关键! 它意味着默认直接硬编码编译进内核(对应*号)。
-
-
修改
Makefile文件-
打开
drivers/char/Makefile。 -
在文件里添加下面这一行:
obj-$(CONFIG_HELLO_DRV) += hello_drv.o
-
-
配置并编译内核
-
进入内核源码的根目录。
-
启动图形配置界面:make menuconfig
-
顺着路径找到刚才添加的菜单。
-
保存并退出 (会生成新的
.config文件)。开始编译内核
-
-
-
把驱动程序编译成模块的另外一种方法:
-
在上面的章节(把驱动程序编译进内核方法)中,
Kconfig中CONFIG_HELLO_DRV是三态。当选择m时:1. 确保你当前处于内核源码的根目录。
cd /home/book/linux-4.4/
2. 在根目录下编译内核镜像(可选)
make Image -j4
3. 在根目录下编译所有的驱动模块(这一步会生成 hello_drv.ko)
make modules -j4
4. 在根目录下执行模块安装命令(这一步会把 hello_drv.ko 提取并打包到你的 rootfs 中)
make modules_install INSTALL_MOD_PATH=/home/book/rootfs
#5. 当你刚运行完
make modules编译成功时,.ko文件并不会跑到什么神秘的发布文件夹里,而是直接生成在你的驱动源码.c文件所在的相同目录下。
-
-
linux中,/dev目录中的内容:
/dev目录下的每个文件,都代表了一个实打实的硬件接口或者是内核虚拟出来的设备。-
如果你在终端输入
ls -l /dev,你会发现它的输出与普通目录很不一样:例如crw-rw---- 1 root dialout 4, 64 May 28 20:00 ttyS0
brw-rw---- 1 root disk 8, 0 May 28 20:00 sda
首字母
c或b:普通文件是-,目录是d。而/dev下绝大多数文件开头是c(字符设备,Character Device) 或b(块设备,Block Device)。
没有文件大小,只有主次设备号 :在原本显示文件大小(Byte)的位置,它们显示的是两个逗号隔开的数字(如4, 64)。这两个数字就是主设备号 和次设备号。 -
/dev目录不是存放在硬盘上的实际文件夹,它是一个由内核(通过devtmpfs文件系统)在内存中动态维护的硬件映射表 。它就像是一个硬件插线板,把复杂的底层驱动接口,整整齐齐地转换成了一个个可读写的"文件文件",供上层应用程序调用。
-
-
驱动源代码放入linux源码/drivers目录下编译驱动的方法:
放置源码
在
drivers目录下找到适合你驱动类型的子目录(例如网络驱动放net,块设备放block,字符设备放char)。如果属于自定义类别,也可以直接在drivers/下新建一个目录。-
示例路径 :
drivers/char/my_driver/ -
创建文件 :将你的驱动源码(如
my_driver.c、my_driver.h)放入该目录。 -
创建或修改子目录的 Makefile
内核编译系统依赖各自目录下的
Makefile来决定编译哪些文件。在你的驱动目录(例如drivers/char/my_driver/)下创建一个新的Makefile,并添加以下内容:drivers/char/my_driver/Makefile
CONFIG_MY_DRIVER 是你在 Kconfig 中定义的配置项
obj-$(CONFIG_MY_DRIVER) += my_driver.o
如果驱动由多个源文件组成,按如下方式编写:
obj-$(CONFIG_MY_DRIVER) += my_driver_mod.o
my_driver_mod-y := my_driver_main.o my_driver_utils.o
-
创建或修改子目录的 Kconfig
为了能在内核的菜单配置(如
make menuconfig)中看到你的驱动,需要配置Kconfig文件。在你的驱动目录下创建一个Kconfig文件:drivers/char/my_driver/Kconfig
menu "My Custom Driver Support"
config MY_DRIVER
tristate "Enable My Custom Driver"
default n
help
This is a description for my custom driver.
If unsure, say N.
endmenu
注意 :
tristate表示支持三种状态:编译进内核(Y)、编译为模块(M)、不编译(N)。如果只想支持内联或关闭,可以使用bool。 -
挂接至上层目录的 Makefile 和 Kconfig
必须修改你驱动目录的上一级目录 中的文件,让内核编译系统能够递归查找到你的新目录。以挂载在
drivers/char/下为例:修改
drivers/char/Kconfig打开该文件,在适当的位置(通常是文件末尾
endmenu之前)引入你的Kconfig:source "drivers/char/my_driver/Kconfig"修改
drivers/char/Makefile打开该文件,在适当位置添加整套驱动目录的编译引用:
Makefileobj-$(CONFIG_MY_DRIVER) += my_driver/ -
配置与编译
-
完成上述文件修改后,返回内核源码的根目录执行以下步骤:
-
打开配置菜单:
make menuconfig -
激活驱动 : 在菜单中根据你设置的路径找到
My Custom Driver Support,按键盘Space键将其配置为<*>(编译进内核)或<M>(编译为独立模块.ko)。保存并退出。 -
开始编译:
-
如果配置为
<M>,可以单独编译该目录:make M=drivers/char/my_driver -
如果配置为
<*>,则需要编译整个内核:make -j$(nproc)
-
-
-
-
总线设备驱动模型:
1.模型的核心三大要素
整个模型可以简单概括为一个公式:
Bus (总线) = Device (设备) + Driver (驱动)。+-----------------------------------------+ | Bus (总线) | +-----------------------------------------+ | | v v +-----------------------+ +-----------------------+ | Device (设备) | | Driver (驱动) | | (描述硬件: 资源/中断) | | (描述软件: 操作方法) | +-----------------------+ +-----------------------+❶ Bus (总线)
总线是连接设备和驱动的桥梁。在 Linux 内核中,用
struct bus_type结构体表示。-
物理总线:真实存在的硬件总线,如 PCI、USB、I2C、SPI 等。
-
虚拟总线(Platform Bus):为了让那些没有物理总线的设备(如 SoC 内部集成的内存映射外设:GPIO、RTC、UART 等)也能复用这套模型,Linux 虚拟出来了一条平台总线(platform_bus)。
-
核心职责:负责匹配(Match)设备和驱动。当有新的设备或驱动注册到总线上时,总线会调用它的
match函数来检查两者是否匹配。
-
❷ Device (设备)。类似于stm32中的用户程序,负责确定使用哪个引脚(资源),并且利用HAL库操作引脚。设备负责提供硬件参数(干活需要的数据)。在内核中用 struct device(或其派生的 struct platform_device)表示。
- 包含内容:寄存器基地址、中断号(IRQ)、DMA 通道、时钟等硬件资源。
- 特点:不包含控制硬件的代码逻辑,只纯粹描述"这个硬件长什么样、在哪里"。
❸ Driver (驱动),类似于stm32中HAL库的HAL_GPIO_TogglePin函数。这个函数可以操作所有的GPIO,是操作硬件的通用代码。驱动负责提供行为逻辑(怎么干活的代码)。在内核中用 struct device_driver(或其派生的 struct platform_driver)表示。
- 包含内容 :
probe(探针函数,用于初始化硬件)、remove(卸载函数)、suspend/resume(电源管理)以及标准的字符设备/块设备操作接口(open,read,write等)。 - 特点:同一份驱动代码可以支持多个相同的硬件设备。驱动本身不包含硬件死绑定的寄存器地址,而是等总线分发设备给自己时,去读取设备里的资源。
2. 核心工作流程:匹配与初始化
总线设备驱动模型最精妙的地方在于它的动态匹配机制 ,通常被称为 match 和 probe。
[设备注册] [驱动注册]
platform_device_register() platform_driver_register()
\ /
v v
+---------------------------------------------+
| 进入总线设备/驱动链表 |
+---------------------------------------------+
|
v
总线调用:bus_type.match()
(匹配名字/ID)
|
+-----+-----+
| 匹配成功? |
+-----+-----+
| 是
v
总线调用:driver.probe()
(分配资源、注册字符设备、运行)
步骤一:注册(Register)
-
当系统启动或热插拔时,设备端通过
platform_device_register()将硬件信息注册进内核,挂在总线的设备链表上。 -
驱动程序加载时(如
insmod),通过platform_driver_register()将驱动注册进内核,挂在总线的驱动链表上。
步骤二:匹配(Match)
每当链表里新增了成员,总线就会遍历对侧的链表进行一一比对。常见的匹配方式有:
-
设备树匹配(最常用) :比对驱动中
of_match_table的.compatible属性与设备树(DTS)节点的compatible属性是否一致。 -
ACPI 匹配:基于 ACPI 表。
-
ID 列表匹配 :比对驱动的
id_table内部支持的 ID。 -
名字匹配 :直接比对
device.name和driver.name字符串是否相同。
步骤三:探针(Probe)
如果 match 函数返回 true,总线就会调用驱动中定义的 probe 函数,并将匹配成功的设备指针 作为参数传进去。 在 probe 函数中,驱动会完成以下事情:
-
从设备结构体中提取寄存器地址、中断号等资源。
-
使用
ioremap映射寄存器物理地址。 -
申请中断(
request_irq)。 -
注册具体的设备类型(如字符设备
cdev_add),生成/dev/xxx节点,供用户空间调用。 -
platform_device_register和platform_driver_register作用:
-
platform_device_register:
-
加入设备链表 :将传入的
struct platform_device结构体指针挂到平台总线(platform_bus_type)的 设备链表(Devices List) 末尾。 -
创建 sysfs 节点 :在虚拟文件系统
/sys/devices/platform/下创建一个以该设备名字命名的文件夹,暴露硬件拓扑结构。
- platform_driver_register:
-
加入驱动链表 :将传入的
struct platform_driver结构体指针挂到平台总线(platform_bus_type)的 驱动链表(Drivers List) 末尾。 -
创建 sysfs 节点 :在虚拟文件系统
/sys/bus/platform/drivers/下创建一个以该驱动名字命名的文件夹。