1 Star 2 Fork 0

basic-learn/linux-drivers

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
README.md 8.79 KB
一键复制 编辑 原始数据 按行查看 历史
wangzhankun 提交于 2年前 . init

前言

本系列的项目代码主要参考文献^1,建议读者熟悉了^1之后再详细阅读^2^2详细介绍了Linux驱动模型。需要注意的是文献^1主要面向的是嵌入式设备驱动编写,与个人计算机驱动编写有一定不同。比如,设备树一般不在个人计算机中使用,而是依靠各种自动配置协议来识别硬件。

  • ch34x usb转串口驱动,不是自己写的,从网上下载的
  • helloworld 第一个驱动文件
  • micsdevice 杂项设备
  • characterdevice 字符设备,这里介绍了静态和动态申请设备号
  • registerchrdev 注册字符设备
  • parameter 介绍了如何在安装module时传入参数

驱动安装时传参

首先需要在驱动中定义接收数据的变量。

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

平台总线模型 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

什么是platform bus

在计算机中有这样一类设备,它们通过各自的设备控制器,直接和CPU连接,CPU可以通过常规的寻址操作访问它们(或者说访问它们的控制器)。这种连接方式,并不属于传统意义上的总线连接。但设备模型应该具备普适性,因此Linux就虚构了一条Platform Bus,供这些设备挂靠。^2

platform 设备实现

在实现时就需要实现两个驱动,一个是device驱动,一个是driver驱动。前者实现与设备紧密相关的代码,后者实现若干种设备的相同代码。

platform_driver.driver.name用于和platform_device.name进行匹配,当platform_driverplatform_device匹配成功时,就会自动调用platform_driver.probe函数;

platform_driverplatform_device任意一个卸载时,就会调用platform_driver.remove函数;

当设备收到shutdown命令时,调用platform_driver.shutdown函数;

当设备休眠时,调用platform_driver.suspend函数;

当设备被唤醒时,调用platform_driver.resume函数。

platform_deviceplatform_driver进行匹配时,如果platform_driver.id_tableplatform_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;
};

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. 注册杂项设备

参考信息

[^3]: Linux 字符设备驱动开发基础(五)—— ioremap() 函数解析

[^4]: 内核request_mem_region 和 ioremap的理解

Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/basic-learn/linux-drivers.git
git@gitee.com:basic-learn/linux-drivers.git
basic-learn
linux-drivers
linux-drivers
master

搜索帮助