一、旧驱动缺陷
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/class
和 device_name
之间的那部分目录称为 subsystem
。也就是每个 dev
属性文件所在的路径都可表示为 /sys/class/subsystem/device_name/dev
),在 /dev
目录下创建相应的设备文件。
例如,cat /sys/class/tty/tty0/dev
会得到 4:0
,subsystem为tty,device_name
为 tty0
。
当 mdev
因 uevnet
事件(以前叫 hotplug
事件)被调用时,mdev
通过由 uevent
事件传递给它的环境变量获取到:引起该 uevent
事件的设备 action
及该设备所在的路径 device path
。然后判断引起该 uevent
事件的 action
是什么。若该 action
是 add
,即有新设备加入到系统中,不管该设备是虚拟设备还是实际物理 设备,mdev
都会通过 device path
路径下的 dev
属性文件获取到设备编号,然后以 device path
路径最后一个目录(即包含该 dev
属性文件的目录)作为设备名,在 /dev
目录下创建相应的设备文件。若该action
是 remote
,即设备已 从系统中移除,则删除 /dev
目录下以 device path
路径最后一个目录名称作为文件名的设备文件。如果该 action
既不是 add
也不是 remove
,mdev
则什么都不做。
由上面可知,如果我们想在设备加入到系统中或从系统中移除时,由 mdev
自动地创建和删除设备文件,那么就必须做到以下三点:
-
在
/sys/class
的某一subsystem
目录下。 -
创建一个以设备名
device_name
作为名称的目录。 -
并且在该
device_name
目录下还必须包含一个dev
属性文件,该dev属性文件以”major:minor\n”
形式输出设备编号。
思路有了,剩下的就是如何实现了。下面通过介绍 linux
设备驱动模型里两个重要的数据结构:class
和 class_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
函数里面设置好私有数据以后,在 write
、read
、close
等函数中直接读取 private_data
即可得到设备结构体
注:详细内容未深入分析。
六、异常处理
驱动编写过程中,函数会分配系统资源。当前函数执行失败后,需要对以前分配资源进行释放。如果每次失败后,在函数执行异常处理中进行释放,会重复调用函数,代码比较混乱。因此使用 goto
语句对异常进行处理,执行失败后,自动跳转到响应异常进行处理,异常代码可以重复使用。