Linux 驱动开发 七:新字符设备驱动框架构思

一、旧驱动缺陷

1、使用 register_chrdev 函数注册字符设备会带来如下 2 个缺陷

  • 必须指定主设备号,也就需要事先确定没有使用的设备号。
  • 一个主设备号下所有次设备号都会被注册,造成此设备号浪费。

2、需要手动使用 mknod 命令创建设备节点。

二、解决方法

1、register_chrdev 缺陷解决方案

使用 Linux 内核提供的新字符设备注册函数完成设备注册,新函数如下:

  • int register_chrdev_region(dev_t from,unsigned count,const char *name):从指定的 from 设备号开始分配 count 个设备。
  • int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count, const char *name):从指定的 baseminor 次设备号开始动态分配 count 个设备,并将申请的设备号从 dev 字段返回。

特别注意:以上 2 个函数都不会调用 cdev_init 函数和 cdev_add 函数

2、手动创建设备节点解决方案

1、mdev 机制

https://blog.csdn.net/yangguoyu8023/article/details/70768703
执行 mdev -s 命令时,mdev 扫描 /sys/block(块设备保存在 /sys/block 目录下,内核 2.6.25版本以后,块设备也保存在 /sys/class/block 目录下。mdev 扫描 /sys/block 是为了实现向后兼容)和 /sys/class 两个目录下的 dev 属性文件,从该 dev属性文件中获取到设备编号(dev 属性文件以 “major:minor\n” 形式保存设备编号),并以包含该 dev 属性文件的目录名称作为设备名 device_name(即包含 dev 属性文件的目录称为 device_name,而 /sys/classdevice_name 之间的那部分目录称为 subsystem。也就是每个 dev 属性文件所在的路径都可表示为 /sys/class/subsystem/device_name/dev),在 /dev 目录下创建相应的设备文件。

例如,cat /sys/class/tty/tty0/dev 会得到 4:0subsystem为tty,device_nametty0

mdevuevnet 事件(以前叫 hotplug 事件)被调用时,mdev 通过由 uevent 事件传递给它的环境变量获取到:引起该 uevent 事件的设备 action 及该设备所在的路径 device path。然后判断引起该 uevent 事件的 action 是什么。若该 actionadd,即有新设备加入到系统中,不管该设备是虚拟设备还是实际物理 设备,mdev 都会通过 device path 路径下的 dev 属性文件获取到设备编号,然后以 device path 路径最后一个目录(即包含该 dev 属性文件的目录)作为设备名,在 /dev 目录下创建相应的设备文件。若该actionremote,即设备已 从系统中移除,则删除 /dev 目录下以 device path 路径最后一个目录名称作为文件名的设备文件。如果该 action 既不是 add 也不是 removemdev 则什么都不做。

由上面可知,如果我们想在设备加入到系统中或从系统中移除时,由 mdev 自动地创建和删除设备文件,那么就必须做到以下三点:

  • /sys/class 的某一 subsystem 目录下。

  • 创建一个以设备名 device_name 作为名称的目录。

  • 并且在该 device_name 目录下还必须包含一个 dev 属性文件,该dev属性文件以 ”major:minor\n” 形式输出设备编号。

思路有了,剩下的就是如何实现了。下面通过介绍 linux 设备驱动模型里两个重要的数据结构:classclass_device

2、class

一个类是一个设备的高层视图,它抽象掉了底层的实现细节。例如,在驱动层面时,你可能会见到 SCSI 磁盘或者 ATA 磁盘;但在类层面时,它们都是磁盘。允许用户空间基于它们做什么来使用设备,而不是它们如何被连接或者它们如何工作

class表示一类设备,所有 class 都属于 class_subsys (class 子系统),即出现在 /sys/class 目录下,除了块设备(可能出现在 /sys/block//sys/class/block)。

/**
 * struct class - device classes
 * @name:	Name of the class.
 * @owner:	The module owner.
 * @class_attrs: Default attributes of this class.
 * @dev_groups:	Default attributes of the devices that belong to the class.
 * @dev_kobj:	The kobject that represents this class and links it into the hierarchy.
 * @dev_uevent:	Called when a device is added, removed from this class, or a
 *		few other things that generate uevents to add the environment
 *		variables.
 * @devnode:	Callback to provide the devtmpfs.
 * @class_release: Called to release this class.
 * @dev_release: Called to release the device.
 * @suspend:	Used to put the device to sleep mode, usually to a low power
 *		state.
 * @resume:	Used to bring the device from the sleep mode.
 * @ns_type:	Callbacks so sysfs can detemine namespaces.
 * @namespace:	Namespace of the device belongs to this class.
 * @pm:		The default device power management operations of this class.
 * @p:		The private data of the driver core, no one other than the
 *		driver core can touch this.
 *
 * A class is a higher-level view of a device that abstracts out low-level
 * implementation details. Drivers may see a SCSI disk or an ATA disk, but,
 * at the class level, they are all simply disks. Classes allow user space
 * to work with devices based on what they do, rather than how they are
 * connected or how they work.
 */
struct class {
	const char		*name;	/* class的名称 */
	struct module		*owner;	/* 拥有该class的模块 */

	struct class_attribute		*class_attrs;
	const struct attribute_group	**dev_groups;
	struct kobject			*dev_kobj;

    /* 该函数提供在产生热插拔class_device事件时,添加环境变量的能力 */
	int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
	char *(*devnode)(struct device *dev, umode_t *mode);
    
	/* class被移除时,调用该函数进行必要的清理工作 */ 
	void (*class_release)(struct class *class);
	void (*dev_release)(struct device *dev);

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

	const struct kobj_ns_type_operations *ns_type;
	const void *(*namespace)(struct device *dev);

	const struct dev_pm_ops *pm;

	struct subsys_private *p;
};

3、class_device\device

一个 class 可以看成是一个容器(一个子系统 subsystem),包含了很多的 class_device,这些 class_device 是由 class 这个大的容器来管理的,而每个 class_device 都对应着一个具体的设备

每个 class 对象包括一个 class_device 链表,每个 class_device 对象表示一个逻辑设备并通过 struct class_device 中的dev成员(一个指向 struct device 的指针)关联一个物理设备。一个逻辑设备总是对应一个物理设备,而一个物理设备却可以对应多个逻辑设备。

实际上,class_device/sys/class/subsystem 生成的目录就是上面提到的 class_device。这样第 2 点也有了。

说明:在使用 linux 内核中为找到 class_device 结构体类型,但找到 device 结构体类型。

/**
 * struct device - The basic device structure
 * @parent:	The device's "parent" device, the device to which it is attached.
 * 		In most cases, a parent device is some sort of bus or host
 * 		controller. If parent is NULL, the device, is a top-level device,
 * 		which is not usually what you want.
 * @p:		Holds the private data of the driver core portions of the device.
 * 		See the comment of the struct device_private for detail.
 * @kobj:	A top-level, abstract class from which other classes are derived.
 * @init_name:	Initial name of the device.
 * @type:	The type of device.
 * 		This identifies the device type and carries type-specific
 * 		information.
 * @mutex:	Mutex to synchronize calls to its driver.
 * @bus:	Type of bus device is on.
 * @driver:	Which driver has allocated this
 * @platform_data: Platform data specific to the device.
 * 		Example: For devices on custom boards, as typical of embedded
 * 		and SOC based hardware, Linux often uses platform_data to point
 * 		to board-specific structures describing devices and how they
 * 		are wired.  That can include what ports are available, chip
 * 		variants, which GPIO pins act in what additional roles, and so
 * 		on.  This shrinks the "Board Support Packages" (BSPs) and
 * 		minimizes board-specific #ifdefs in drivers.
 * @driver_data: Private pointer for driver specific info.
 * @power:	For device power management.
 * 		See Documentation/power/devices.txt for details.
 * @pm_domain:	Provide callbacks that are executed during system suspend,
 * 		hibernation, system resume and during runtime PM transitions
 * 		along with subsystem-level and driver-level callbacks.
 * @pins:	For device pin management.
 *		See Documentation/pinctrl.txt for details.
 * @numa_node:	NUMA node this device is close to.
 * @dma_mask:	Dma mask (if dma'ble device).
 * @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all
 * 		hardware supports 64-bit addresses for consistent allocations
 * 		such descriptors.
 * @dma_pfn_offset: offset of DMA memory range relatively of RAM
 * @dma_parms:	A low level driver may set these to teach IOMMU code about
 * 		segment limitations.
 * @dma_pools:	Dma pools (if dma'ble device).
 * @dma_mem:	Internal for coherent mem override.
 * @cma_area:	Contiguous memory area for dma allocations
 * @archdata:	For arch-specific additions.
 * @of_node:	Associated device tree node.
 * @fwnode:	Associated device node supplied by platform firmware.
 * @devt:	For creating the sysfs "dev".
 * @id:		device instance
 * @devres_lock: Spinlock to protect the resource of the device.
 * @devres_head: The resources list of the device.
 * @knode_class: The node used to add the device to the class list.
 * @class:	The class of the device.
 * @groups:	Optional attribute groups.
 * @release:	Callback to free the device after all references have
 * 		gone away. This should be set by the allocator of the
 * 		device (i.e. the bus driver that discovered the device).
 * @iommu_group: IOMMU group the device belongs to.
 *
 * @offline_disabled: If set, the device is permanently online.
 * @offline:	Set after successful invocation of bus type's .offline().
 *
 * At the lowest level, every device in a Linux system is represented by an
 * instance of struct device. The device structure contains the information
 * that the device model core needs to model the system. Most subsystems,
 * however, track additional information about the devices they host. As a
 * result, it is rare for devices to be represented by bare device structures;
 * instead, that structure, like kobject structures, is usually embedded within
 * a higher-level representation of the device.
 */
struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	const struct device_type *type;

	struct mutex		mutex;	/* mutex to synchronize calls to
					 * its driver.
					 */

	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	void		*driver_data;	/* Driver data, set and get with
					   dev_set/get_drvdata */
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;

#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */
	unsigned long	dma_pfn_offset;

	struct device_dma_parameters *dma_parms;

	struct list_head	dma_pools;	/* dma pools (if dma'ble) */

	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
#ifdef CONFIG_DMA_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#endif
	/* arch specific additions */
	struct dev_archdata	archdata;

	struct device_node	*of_node; /* associated device tree node */
	struct fwnode_handle	*fwnode; /* firmware device node */

	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
	u32			id;	/* device instance */

	spinlock_t		devres_lock;
	struct list_head	devres_head;

	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */

	void	(*release)(struct device *dev);
	struct iommu_group	*iommu_group;

	bool			offline_disabled:1;
	bool			offline:1;
};

4、说明

使用 busybox 构建根文件 系统的时候,busybox 会创建一个 udev 的简化版本— mdev,所以在嵌入式 Linux 中我们使用 mdev 来实现设备节点文件的自动创建与删除。

三、设备文件节点的自动创建与删除

1、创建类

自动创建设备节点的工作是在驱动程序的入口函数中完成的。首先要创建一个 class 类,class 是个结构体,定义在文件 include/linux/device.h 里面。class_create 是类创建函数,class_create 是个宏定义,内容如下:

/* This is a #define to keep the compiler from merging different
 * instances of the __key variable */
#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})
/**
 * class_create - create a struct class structure
 * @owner: pointer to the module that is to "own" this struct class
 * @name: pointer to a string for the name of this class.
 * @key: the lock_class_key for this class; used by mutex lock debugging
 *
 * This is used to create a struct class pointer that can then be used
 * in calls to device_create().
 *
 * Returns &struct class pointer on success, or ERR_PTR() on error.
 *
 * Note, the pointer created here is to be destroyed when finished by
 * making a call to class_destroy().
 */
struct class *__class_create(struct module *owner, const char *name,
			     struct lock_class_key *key)
{
	struct class *cls;
	int retval;

	cls = kzalloc(sizeof(*cls), GFP_KERNEL);
	if (!cls) {
		retval = -ENOMEM;
		goto error;
	}

	cls->name = name;
	cls->owner = owner;
	cls->class_release = class_create_release;

	retval = __class_register(cls, key);
	if (retval)
		goto error;

	return cls;

error:
	kfree(cls);
	return ERR_PTR(retval);
}
EXPORT_SYMBOL_GPL(__class_create);

根据上述代码,将宏 class_create 展开以后内容如下:

struct class *class_create (struct module *owner, const char *name)

class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。返回值是个指向结构体 class 的指针,也就是创建的类。

2、删除类

卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:

/**
 * class_destroy - destroys a struct class structure
 * @cls: pointer to the struct class that is to be destroyed
 *
 * Note, the pointer to be destroyed must have been created with a call
 * to class_create().
 */
void class_destroy(struct class *cls)
{
	if ((cls == NULL) || (IS_ERR(cls)))
		return;

	class_unregister(cls);
}

参数 cls 就是要删除的类。

3、创建设备

上一小节创建好类以后还不能实现自动创建设备节点,我们还需要在这个类下创建一个设备。使用 device_create 函数在类下面创建设备,device_create 函数原型如下:

/**
 * device_create - creates a device and registers it with sysfs
 * @class: pointer to the struct class that this device should be registered to
 * @parent: pointer to the parent struct device of this new device, if any
 * @devt: the dev_t for the char device to be added
 * @drvdata: the data to be added to the device for callbacks
 * @fmt: string for the device's name
 *
 * This function can be used by char device classes.  A struct device
 * will be created in sysfs, registered to the specified class.
 *
 * A "dev" file will be created, showing the dev_t for the device, if
 * the dev_t is not 0,0.
 * If a pointer to a parent struct device is passed in, the newly created
 * struct device will be a child of that device in sysfs.
 * The pointer to the struct device will be returned from the call.
 * Any further sysfs files that might be required can be created using this
 * pointer.
 *
 * Returns &struct device pointer on success, or ERR_PTR() on error.
 *
 * Note: the struct class passed to this function must have previously
 * been created with a call to class_create().
 */
struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
{
	va_list vargs;
	struct device *dev;

	va_start(vargs, fmt);
	dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
	va_end(vargs);
	return dev;
}
EXPORT_SYMBOL_GPL(device_create);

device_create 是个可变参数函数,参数说明:

  • 参数 class 就是设备要创建哪个类下面。

  • 参数 parent 是父设备,一般为NULL,也就是没有父设备。

  • 参数 devt 是设备号。

  • 参数 drvdata 是设备可能会使用 的一些数据,一般为 NULL

  • 参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成 /dev/xxx 这个设备文件。

返回值就是创建好的设备。

4、删除设备

设备删除函数为 device_destroy,函数原型如下:

/**
 * device_destroy - removes a device that was created with device_create()
 * @class: pointer to the struct class that this device was registered with
 * @devt: the dev_t of the device that was previously registered
 *
 * This call unregisters and cleans up a device that was created with a
 * call to device_create().
 */
void device_destroy(struct class *class, dev_t devt)
{
	struct device *dev;

	dev = class_find_device(class, NULL, &devt, __match_devt);
	if (dev) {
		put_device(dev);
		device_unregister(dev);
	}
}
EXPORT_SYMBOL_GPL(device_destroy);

参数 classs 是要删除的设备所处的类,参数 devt 是要删除的设备号。

5、示例

示例由正点原子提供:

struct class *class;	/* 类 */
struct device *device;	/* 设备 */
dev_t devid;			/* 设备号 */

/* 驱动入口函数 */
static int __init xxx_init(void)
{
    /* 创建类 */
	class = class_create(THIS_MODULE, "xxx");
    /* 创建设备 */
    device = device_create(class, NULL, devid, NULL, "xxx");
    
    return 0;
}

/* 驱动出口函数 */
static void __exit led_exit(void)
{
	/* 删除设备 */
    device_destroy(newchrled.class, newchrled.devid);
    /* 删除类 */
    class_destroy(newchrled.class);
}

module_init(led_init);
module_exit(led_exit);

四、驱动中数据管理

驱动中自定义一个结构体类型,驱动中所有数据都通过自定义结构体进行管理。

五、私有数据

为了方便调用自定义数据结构体,我们将自定义结构体变量首地址保存到 file 结构体中 private_data 成员变量中。

open 函数里面设置好私有数据以后,在 writereadclose 等函数中直接读取 private_data 即可得到设备结构体

注:详细内容未深入分析。

六、异常处理

驱动编写过程中,函数会分配系统资源。当前函数执行失败后,需要对以前分配资源进行释放。如果每次失败后,在函数执行异常处理中进行释放,会重复调用函数,代码比较混乱。因此使用 goto 语句对异常进行处理,执行失败后,自动跳转到响应异常进行处理,异常代码可以重复使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值