本系列的项目代码主要参考文献^1,建议读者熟悉了^1之后再详细阅读^2,^2详细介绍了Linux驱动模型。需要注意的是文献^1主要面向的是嵌入式设备驱动编写,与个人计算机驱动编写有一定不同。比如,设备树一般不在个人计算机中使用,而是依靠各种自动配置协议来识别硬件。
首先需要在驱动中定义接收数据的变量。
static int a = 0;
static int b[5];// 如果传入的参数超过五个会报错 could not insert module parameter/parameter.ko: Invalid parameters
static int count; // 记录用户在安装驱动时传入给b的参数的个数
module_param(a, int, S_IRUSR);
module_param_array(b, int, &count, S_IRUSR);
传参方法是:
insmod modulename a=3 b=1,2,3,4,5
# b 可以传入少于5个的参数但是不能多于5个
使用misc_register
函数注册杂项设备,注册成功后会自动在/dev/
路径下创建与一个定义在struct miscdevice
中的name
字段同名的节点。
@startmindmap
+ 杂项设备
++ 1. 注册杂项设备
+++ misc_register(&misc)
++ 2. 构建杂项设备结构体
***:
<code>
static struct miscdevice misc = {
.minor=12,
.name="name",// /dev/下的名称
.fops = &
}
</code>
;
++ 3. 构建file_operations
++ 4. 卸载杂项设备
+++ misc_deregister(&misc)
@endmindmap
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = MODULENAME,
.fops = &misc_fops,
};
字符设备与杂项设备不同的是,在注册字符设备之后并不会自动在/dev/
路径下创建节点。需要使用mknod
命令创建节点。
格式:
mknod 名称 类型 主设备号 次设备号
例如:
mknod /dev/test c 499 0
{ // 创建设备文件
// 在/sys/class/目录下创建一个类,类名为就是第二个参数
cls = class_create(hellocdev.owner, MODULENAME);
// 在/dev/目录下创建设备文件,文件名为最后一个参数
device_create(cls,
NULL,
MKDEV(major_num, minor_num),
NULL,
MODULENAME);
}
{ // 删除设备文件
device_destroy(cls, MKDEV(major_num, minor_num));
class_destroy(cls);
}
注意在4registerchrdev
项目中,文献^1中的代码的释放顺序有问题。应当秉承先创建者后释放的原则。
@startmindmap
* 字符设备驱动框架
** 1.驱动初始化
*** 分配设备号
**** 静态分配
***** register_chrdev_region
**** 动态分配
***** alloc_chrdev_region
**** 操作设备号
***** dev_t本质上就是一个无符号32位整数
***** MAJOR宏从dev_t中获取主设备号,占12bit
***** MINOR宏从dev_t中获取次设备号,占20bit
***** MKDEV(major, minor)获取dev_t
*** cdev_init
*** cdev_add
** 2.构建file_operations
** 3.生成设备节点
*** 自动生成设备节点
**** a. class_create创建一个class
**** b. device_create创建一个设备节点
*** 手动生成设备节点
**** mknod命令
** 4. 驱动卸载
*** device_destroy 销毁设备节点
*** class_destroy 销毁class
*** cdev_del 卸载设备
*** unregister_chrdev_region 释放设备号
@endmindmap
建议详细阅读文献^2。
概括来说,Platform设备包括:基于端口的设备(已不推荐使用,保留下来只为兼容旧设备,legacy);连接物理总线的桥设备;集成在SOC平台上面的控制器;连接在其它bus上的设备(很少见)。等等。^2
这些设备有一个基本的特征:可以通过CPU bus直接寻址(例如在嵌入式系统常见的“寄存器”)。因此,由于这个共性,内核在设备模型的基础上(device和device_driver),对这些设备进行了更进一步的封装,抽象出paltform bus、platform device和platform driver,以便驱动开发人员可以方便的开发这类设备的驱动。^2
可以说,paltform设备对Linux驱动工程师是非常重要的,因为我们编写的大多数设备驱动,都是为了驱动plaftom设备。本文我们就来看看Platform设备在内核中的实现。^2
在计算机中有这样一类设备,它们通过各自的设备控制器,直接和CPU连接,CPU可以通过常规的寻址操作访问它们(或者说访问它们的控制器)。这种连接方式,并不属于传统意义上的总线连接。但设备模型应该具备普适性,因此Linux就虚构了一条Platform Bus,供这些设备挂靠。^2
在实现时就需要实现两个驱动,一个是device
驱动,一个是driver
驱动。前者实现与设备紧密相关的代码,后者实现若干种设备的相同代码。
platform_driver.driver.name
用于和platform_device.name
进行匹配,当platform_driver
与platform_device
匹配成功时,就会自动调用platform_driver.probe
函数;
当platform_driver
与platform_device
任意一个卸载时,就会调用platform_driver.remove
函数;
当设备收到shutdown命令时,调用platform_driver.shutdown
函数;
当设备休眠时,调用platform_driver.suspend
函数;
当设备被唤醒时,调用platform_driver.resume
函数。
在platform_device
和platform_driver
进行匹配时,如果platform_driver.id_table
和platform_driver.driver.name
都有值时,那么就会忽略platform_driver.driver.name
,只拿platform_driver.id_table
中的名字与platform_driver
进行匹配,无论是否匹配。
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
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;
bool prevent_deferred_probe;
};
前面讲了,如果平台设备名称与平台驱动名称匹配那么就会调用platform_driver.probe
函数,那么如何实现该函数呢?
int (*probe)(struct platform_device *pdev)
传入的pdev
获取,struct resource* res = pdev->res;
(不安全,不推荐);
b. 使用函数访问,struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int)
。struct resource* request_mem_region(start,n,name)
,这里的目的是判断该IO端口的地址是否已经被请求使用了,如果被请求使用了或者部分地址重叠,那么就会请求失败,请求失败就不能进行IO映射。如果请求成功,那么就会将IO端口地址记录下来,如果后续有其它驱动尝试请求就会请求失败。参见[^3][^4]
b. 端口映射 ioremap
,参见[^3][^4]。
c. 注册杂项设备此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。