字符设备驱动程序—1

编写步骤

  1. 确定主设备号,也可以设为0让内核自动分配
  2. 定义 file_operations 结构体
  3. 实现 file_operations 结构体中的 open、read、write等函数
  4. register_chrdev 注册字符设备驱动
  5. 完成入口函数、出口函数(安装驱动时调用入口函数、卸载驱动时调用出口函数)
  6. 自动创建设备节点 class_create、device_create

注意:

  1. 驱动程序操作硬件:

    ioremap 映射寄存器的物理地址得到虚拟地址

  2. 驱动程序与应用程序之间传递信息:

    copy_to_user、copy_from_user

class_create、device_create

内核中定义有 struct class 结构体,顾名思义,一个 struct class 结构体对应一个类,内核同时提供了 class_create 函数,用它创建 class 类放在 /sys/class 目录下,一旦创建好这个类,再调用 device_create 就可以在 /dev 目录下创建设备节点。

eg:

hello_class = class_create(THIS_MODULE, "hello_class");
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");

上述代码的 class_create 会在 /sys/class 下创建 hello_class 目录,这个目录下有一个链接文件 hello,在它所指向的文件中包含着更多的设备信息(比如:dev文件中有主设备号、次设备号信息)

image-20240527204444231

image-20240527204719547

image-20240527205114116

/sys/devices:系统中所有设备的存放目录

/sys/class:系统中的所有设备按照其功能放置的目录结构,同时该目录下的文件也链接到了 /sys/devices 目录 (eg:/sys/class/leds 存放所有的led设备、/sys/class/input 存放所有的输入类设备)

驱动设计思想

面向对象

字符设备驱动程序抽象出一个 file_operations 结构体;

分层

最简单的驱动程序:以操作一盏 led 灯为例(./ledtest /dev/myled on ./ledtest /dev/myled off)

​ 在驱动程序中只需要:(1)在open函数中初始化 led

​ (2)在write函数中根据传进来的值控制 led

​ 这两步都是直接对相应的寄存器进行操作

​ 缺点:将驱动程序和硬件资源绑定死,更换硬件资源时需要重新编写驱动程序

分层思想:上下分层,将 led 驱动程序分为两层,将驱动程序和硬件相分离

image-20240528102704602

leddrv.c 中并没有具体的对于寄存器的操作,只负责注册 file_operations 结构体,它的 open/write 成员会调用 board_X.c 中提供的硬件 led_opr 中的对应函数

board_X.c 中实现对硬件的具体操作,构造各自的 led_operations 硬件操作结构体

缺点:实现了硬件操作与驱动程序的相分离,但是在硬件操作中,对于某一个 led,它的寄存器操作是写死的,那么如果这个 led 的引脚发生改变,board_X.c 文件就需要重新编写编译

分离

对于分层的改进,不再将 led 与某个引脚相固定,对于分层中的硬件操作层,再分离为引脚资源(led使用那个引脚)、引脚操作(对于引脚的初始化和控制)

image-20240528104657699

board_A.c 文件中指定引脚资源,实现 led_resource 结构体

static struct led_resource board_A_led = {
	.pin = GROUP_PIN(3,1),
};

chipY_gpio.c 文件中完成硬件操作,定义 led_operations 结构体,并实现 init、ctl 函数

通过分离的思想进而演变出了总线设备驱动模型和设备树

驱动的三种编写方法

传统写法

image-20240528110945001

使用哪个引脚,怎么操作引脚,都写死在代码中。最简单,不考虑扩展性,可以快速实现功能。修改引脚时,需要重新编译。

总线设备驱动模型

image-20240528111036298

引入平台设备(platform_device)、平台驱动(platform_driver),将 “ 驱动 ” 与 “ 硬件 ” 相分离

更换引脚时,led_drv.c 不需要更改,但是 led_dev.c 需要更改

缺点:资源文件 led_dev.c 是要存放在Linux内核中的,当资源过多时,就导致了更多的资源文件,及其占用内核空间

设备树

image-20240528111900327

通过配置文件来定义资源

设备树中的节点通过一定条件,内核可以将它转换为 platform_device 结构体

设备树文件DTS —> 设备树文件DTB —> platform_device

修改引脚时,只需要修改 dts 文件,并编译得到 dtb 文件,把它传给内核,无需重新编译内核、驱动

总线设备驱动模型

概念

image-20240528174605150

**总线(bus) ** :负责管理挂载对应总线的设备以及驱动;
**设备(device) ** :挂载在某个总线的物理设备;
驱动(driver) :与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;

工作原理

总线管理着两个链表:设备链表、驱动链表

当我们向内核注册一个驱动时,它会加入到驱动链表

当我们往内核加入一个设备时,它会加入到设备链表

若安装一个 platform_device 结构体,即将这个结构体加入总线设备链表中,它会与总线驱动链表从前到后进行匹配(总线结构体 bus_type 中的 match 函数),若匹配成功,则调用 platform_driver 中的 probe 函数

重要结构体

平台总线结构体
struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	struct device_attribute	*dev_attrs;	/* use dev_groups instead */
	const struct attribute_group **bus_groups;
	const struct attribute_group **dev_groups;
	const struct attribute_group **drv_groups;

	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	int (*probe)(struct device *dev);
	int (*remove)(struct device *dev);
	void (*shutdown)(struct device *dev);

	int (*online)(struct device *dev);
	int (*offline)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;
};
平台设备结构体
struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	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 resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	unsigned long desc;
	struct resource *parent, *sibling, *child;
};
平台驱动结构体
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;
}
struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data;
};
struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;
	const char		*mod_name;	/* used for built-in modules */

	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */
	enum probe_type probe_type;

	const struct of_device_id	*of_match_table;
	const struct acpi_device_id	*acpi_match_table;

	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;

	const struct dev_pm_ops *pm;

	struct driver_private *p;
};

匹配规则

image-20240528173115117

设备与驱动的匹配是 platform_driver 结构体中的 match 函数做的

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);
 
    /* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);
 
	/* Attempt an OF style match first */
    //第1种匹配方式,OF类型匹配,设备树采用的匹配方式
	if (of_driver_match_device(dev, drv))
		return 1;
 
    /* Then try ACPI style match */
    //第2种匹配方式,ACPI匹配
	if (acpi_driver_match_device(dev, drv))
		return 1;
 
	/* Then try to match against the id table */
    //第3种匹配方式,id_table匹配
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;
 
    	/* fall-back to driver name match */
    //第4种匹配方式,直接比较驱动和设备的name字段
	return (strcmp(pdev->name, drv->name) == 0);
}
第二种

platform_device.driver_override 和 platform_driver.driver.name可以设置 platform_device 的 driver_override,强制选择某个 platform_driver

struct platform_device {
	...
	char *driver_override; 
	...
}

struct platform_driver {
	...
	struct device_driver driver;
	...
}
struct device_driver {
	...
	const char		*name;
	...
}
第三种

platform_device. name 和 platform_driver.id_table[i].name

Platform_driver.id_table 是“platform_device_id”指针,表示该 drv 支持若干个 device,它里面列出了各个 device 的 {.name, .driver_data} ,其中的 “ name ” 表示该drv 支持的设备的名字,driver_data 是些提供给该 device 的私有数据。

struct platform_device {
	...
	const char	*name;
	...
}

struct platform_driver {
	...
	const struct platform_device_id *id_table;
	...
}
struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data;
};
第四种

platform_device.name 和 platform_driver.driver.name

platform_driver.id_table 可能为空,这时可以根据 platform_driver.driver.name 来寻找同名的 platform_device。

struct platform_driver {
	...
	struct device_driver driver;
	...
}
struct device_driver {
	const char		*name;
}

struct platform_device {
	const char	*name;
}

代码示例

static int major = 0;
struct led_operations *p_led_opr;

void led_class_create_device(int minor)
{
	//   /dev/100ask_led0,1,...
	device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); 
}
void led_class_destroy_device(int minor)
{
	device_destroy(led_class, MKDEV(major, minor));
}
void register_led_operations(struct led_operations *opr)
{
	p_led_opr = opr;
}

static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};
static struct led_operations board_demo_led_opr = {
    .init = board_demo_led_init,
    .ctl  = board_demo_led_ctl,
};

// 平台驱动有入口函数和出口函数
// chip_demo_gpio_probe 中获取硬件资源,并通过 led_class_create_device 自动创建设备节点
static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "100ask_led",
    },
};
static struct resource resources[] = {
        {
                .start = GROUP_PIN(3,1),
                .flags = IORESOURCE_IRQ,
                .name = "100ask_led_pin",
        },
        {
                .start = GROUP_PIN(5,8),
                .flags = IORESOURCE_IRQ,
                .name = "100ask_led_pin",
        },
};
// 平台硬件也有入口函数、出口函数
static struct platform_device board_A_led_dev = {
        .name = "100ask_led",
        .num_resources = ARRAY_SIZE(resources),
        .resource = resources,
        .dev = {
                .release = led_dev_release,
         },
};
  • 53
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值