# linux-drivers **Repository Path**: basic-learn/linux-drivers ## Basic Information - **Project Name**: linux-drivers - **Description**: 这是一个记录了若干种Linux驱动样例的仓库 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2023-01-17 - **Last Updated**: 2025-03-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 前言 本系列的项目代码主要参考文献[^1],建议读者熟悉了[^1]之后再详细阅读[^2],[^2]详细介绍了Linux驱动模型。需要注意的是文献[^1]主要面向的是嵌入式设备驱动编写,与个人计算机驱动编写有一定不同。比如,设备树一般不在个人计算机中使用,而是依靠各种自动配置协议来识别硬件。 * ch34x usb转串口驱动,不是自己写的,从网上下载的 * helloworld 第一个驱动文件 * micsdevice 杂项设备 * characterdevice 字符设备,这里介绍了静态和动态申请设备号 * registerchrdev 注册字符设备 * parameter 介绍了如何在安装module时传入参数 # 驱动安装时传参 首先需要在驱动中定义接收数据的变量。 ```cpp 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); ``` 传参方法是: ```shell insmod modulename a=3 b=1,2,3,4,5 # b 可以传入少于5个的参数但是不能多于5个 ``` # 杂项设备 使用`misc_register`函数注册杂项设备,注册成功后会自动在`/dev/`路径下创建与一个定义在`struct miscdevice`中的`name`字段同名的节点。 ```puml @startmindmap + 杂项设备 ++ 1. 注册杂项设备 +++ misc_register(&misc) ++ 2. 构建杂项设备结构体 ***: static struct miscdevice misc = { .minor=12, .name="name",// /dev/下的名称 .fops = & } ; ++ 3. 构建file_operations ++ 4. 卸载杂项设备 +++ misc_deregister(&misc) @endmindmap ``` ```cpp struct miscdevice misc_dev = { .minor = MISC_DYNAMIC_MINOR, .name = MODULENAME, .fops = &misc_fops, }; ``` # 字符设备 ## 如果驱动没有自动创建设备节点 字符设备与杂项设备不同的是,在注册字符设备之后并不会自动在`/dev/`路径下创建节点。需要使用`mknod`命令创建节点。 格式: ```shell mknod 名称 类型 主设备号 次设备号 ``` 例如: ```shell mknod /dev/test c 499 0 ``` ## 驱动创建设备节点 ```cpp { // 创建设备文件 // 在/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]中的代码的释放顺序有问题。应当秉承先创建者后释放的原则。 ```puml @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 ``` # 平台总线模型 platform bus model 建议详细阅读文献[^2]。 ## 什么是platform设备? 概括来说,Platform设备包括:基于端口的设备(已不推荐使用,保留下来只为兼容旧设备,legacy);连接物理总线的桥设备;集成在SOC平台上面的控制器;连接在其它bus上的设备(很少见)。等等。[^2] 这些设备有一个基本的特征:可以通过CPU bus直接寻址(例如在嵌入式系统常见的“寄存器”)。因此,由于这个共性,内核在设备模型的基础上(device和device_driver),对这些设备进行了更进一步的封装,***抽象出paltform bus、platform device和platform driver***,以便驱动开发人员可以方便的开发这类设备的驱动。[^2] 可以说,paltform设备对Linux驱动工程师是非常重要的,因为我们编写的大多数设备驱动,都是为了驱动plaftom设备。本文我们就来看看Platform设备在内核中的实现。[^2] ![](asserts/platform-device.png) ## 什么是platform bus 在计算机中有这样一类设备,它们通过各自的设备控制器,直接和CPU连接,CPU可以通过常规的寻址操作访问它们(或者说访问它们的控制器)。这种连接方式,并不属于传统意义上的总线连接。但设备模型应该具备普适性,因此Linux就虚构了一条Platform Bus,供这些设备挂靠。[^2] ## platform 设备实现 在实现时就需要实现两个驱动,一个是`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`进行匹配,无论是否匹配。 ```cpp 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; }; ``` ### probe函数的实现 前面讲了,如果平台设备名称与平台驱动名称匹配那么就会调用`platform_driver.probe`函数,那么如何实现该函数呢? 1. 需要从平台设备中获取硬件资源 a. 直接从`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)`。 2. 注册杂项/字符设备,完善file_operation结构体,并生成设备节点 a. 请求IO端口。 `struct resource* request_mem_region(start,n,name)`,这里的目的是判断该IO端口的地址是否已经被请求使用了,如果被请求使用了或者部分地址重叠,那么就会请求失败,请求失败就不能进行IO映射。如果请求成功,那么就会将IO端口地址记录下来,如果后续有其它驱动尝试请求就会请求失败。参见[^3][^4] b. 端口映射 `ioremap`,参见[^3][^4]。 c. 注册杂项设备 # 参考信息 [^1]: [【北京迅为】嵌入式学习之Linux驱动篇](https://www.bilibili.com/video/BV1Vy4y1B7ta/?p=18&spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=47bbcc428387a807dfb9a0a62d6b09d1) [^2]: [Linux设备模型系列文章](http://www.wowotech.net/sort/device_model/page/2) [^3]: [Linux 字符设备驱动开发基础(五)—— ioremap() 函数解析](https://blog.csdn.net/zqixiao_09/article/details/50859505) [^4]: [内核request_mem_region 和 ioremap的理解](https://blog.csdn.net/skyflying2012/article/details/8672011)