Home Linux设备模型
Post
Cancel

Linux设备模型

Linux中引入了设备驱动模型分层的概念,将设备驱动代码分成了两部分:设备驱动。设备负责提供硬件资源, 驱动负责去使用这些设备提供的硬件资源。并由总线将它们联系起来。大致关系:

1
2
3
  设备----总线---驱动
   |             |
   ---------------

几个基本概念

  • 设备 (device) :物理设备,挂载在某总线上的,或是虚拟的总线如平台总线上。
  • 驱动 (driver) :物理设备的驱动程序,提供设备的操作方式等。
  • 总线(bus) :用于联系物理设备和驱动程序。物理设备和驱动程序都需要注册到相关总线上,包括真实的总线(i2c,spi,pci,usb等)或虚拟的总线(platform)
  • 类 (class) :对于具有相类似功能的设备,归到一种类别,进行分类管理。

相关的文件夹主要是 /sys/ 文件夹,这个文件夹是内核导出的虚拟的,主要用来管理控制设备,驱动模块等。很大部分是设备相关的,和 /proc/区分开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 查看系统中注册的总线
$ ls /sys/bus/
acpi         clocksource  edac          hid           mdio_bus  mmc    parport      platform  sdio    usb         wmi
auxiliary    container    eisa          i2c           mei       nd     pci          pnp       serial  usb-serial  workqueue
cec          cpu          event_source  isa           memory    node   pci-epf      rapidio   serio   virtio      xen
clockevents  dax          gpio          machinecheck  mipi-dsi  nvmem  pci_express  scsi      spi     vme         xen-backend

# 查看总线(如i2c,spi,pci)下注册的设备和驱动
$ ls /sys/bus/i2c/ -l
total 0
drwxr-xr-x  2 root root    0 3月  29 16:44 devices
drwxr-xr-x 37 root root    0 3月  29 16:44 drivers
-rw-r--r--  1 root root 4096 3月  31 15:41 drivers_autoprobe
--w-------  1 root root 4096 3月  31 15:41 drivers_probe
--w-------  1 root root 4096 3月  29 16:44 uevent

$ ls /sys/bus/spi/ -l
total 0
drwxr-xr-x 2 root root    0 3月  29 16:44 devices
drwxr-xr-x 7 root root    0 3月  29 16:44 drivers
-rw-r--r-- 1 root root 4096 3月  31 15:41 drivers_autoprobe
--w------- 1 root root 4096 3月  31 15:41 drivers_probe
--w------- 1 root root 4096 3月  29 16:44 uevent

$ ls /sys/bus/pci/ -l
total 0
drwxr-xr-x  2 root root    0 3月  29 16:44 devices
drwxr-xr-x 37 root root    0 3月  29 16:44 drivers
-rw-r--r--  1 root root 4096 3月  31 15:41 drivers_autoprobe
--w-------  1 root root 4096 3月  31 15:41 drivers_probe
--w-------  1 root root 4096 3月  31 15:41 rescan
-rw-r--r--  1 root root 4096 3月  31 15:41 resource_alignment
drwxr-xr-x  7 root root    0 3月  29 16:44 slots
--w-------  1 root root 4096 3月  29 16:44 uevent

# 查看class分类,不同种类的功能文件夹,里面的设备主要都是软链接
$ ls /sys/class/
ata_device  devcoredump     extcon       intel_scu_ipc  mmc_host        phy           rapidio_port  scsi_host   vfio
ata_link    devfreq         firmware     iommu          msr             powercap      rc            spi_master  virtio-ports
ata_port    devfreq-event   gpio         ipmi           nd              power_supply  regulator     spi_slave   vtconsole
backlight   devlink         graphics     leds           net             ppdev         remoteproc    thermal     wakeup
bdi         dma             hidraw       lirc           nvme            ppp           rfkill        tpm         watchdog
block       dma_heap        hwmon        mdio_bus       nvme-generic    pps           rtc           tpmrm       wmi_bus
bsg         dmi             i2c-adapter  mei            nvme-subsystem  printer       scsi_device   tty         wwan
dax         drm             i2c-dev      mem            pci_bus         ptp           scsi_disk     usb_role
dca         drm_dp_aux_dev  input        misc           pci_epc         pwm           scsi_generic  vc

总线

总线在内核中的定义位于 /include/linux/device.h 中的 struct bus_type 。实例化到具体的总线(包括虚拟总线),会设置一些关键的函数回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
 * struct bus_type - The bus type of the device
 *
 * @name:	The name of the bus.
 * @dev_name:	Used for subsystems to enumerate devices like ("foo%u", dev->id).
 * @dev_root:	Default device to use as the parent.
 * @bus_groups:	Default attributes of the bus.
 * @dev_groups:	Default attributes of the devices on the bus.
 * @drv_groups: Default attributes of the device drivers on the bus.
 * @match:	Called, perhaps multiple times, whenever a new device or driver
 *		is added for this bus. It should return a positive value if the
 *		given device can be handled by the given driver and zero
 *		otherwise. It may also return error code if determining that
 *		the driver supports the device is not possible. In case of
 *		-EPROBE_DEFER it will queue the device for deferred probing.
 * @uevent:	Called when a device is added, removed, or a few other things
 *		that generate uevents to add the environment variables.
 * @probe:	Called when a new device or driver add to this bus, and callback
 *		the specific driver's probe to initial the matched device.
 * @remove:	Called when a device removed from this bus.
 * @shutdown:	Called at shut-down time to quiesce the device.
 *
 * @online:	Called to put the device back online (after offlining it).
 * @offline:	Called to put the device offline for hot-removal. May fail.
 *
 * @suspend:	Called when a device on this bus wants to go to sleep mode.
 * @resume:	Called to bring a device on this bus out of sleep mode.
 * @num_vf:	Called to find out how many virtual functions a device on this
 *		bus supports.
 * @dma_configure:	Called to setup DMA configuration on a device on
 *			this bus.
 * @pm:		Power management operations of this bus, callback the specific
 *		device driver's pm-ops.
 * @iommu_ops:  IOMMU specific operations for this bus, used to attach IOMMU
 *              driver implementations to a bus and allow the driver to do
 *              bus-specific setup
 * @p:		The private data of the driver core, only the driver core can
 *		touch this.
 * @lock_key:	Lock class key for use by the lock validator
 * @need_parent_lock:	When probing or removing a device on this bus, the
 *			device core should lock the device's parent.
 *
 * A bus is a channel between the processor and one or more devices. For the
 * purposes of the device model, all devices are connected via a bus, even if
 * it is an internal, virtual, "platform" bus. Buses can plug into each other.
 * A USB controller is usually a PCI device, for example. The device model
 * represents the actual connections between buses and the devices they control.
 * A bus is represented by the bus_type structure. It contains the name, the
 * default attributes, the bus' methods, PM operations, and the driver core's
 * private data.
 */
struct bus_type {
	const char		*name;          //总线名称,/sys/bus/ 会有对应名称的目录
	const char		*dev_name;
	struct device		*dev_root;
	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);    //判断驱动和设备是否匹配的回调,每种总线自行具体定义,像acpi,设备树,id,名称等机制
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	int (*probe)(struct device *dev);   //match匹配成功后调用,最终会调用驱动中的probe进行初始化
	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);

	int (*num_vf)(struct device *dev);

	int (*dma_configure)(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;

	bool need_parent_lock;
};

该结构中最关键的是 match回调函数,每当有设备或总线注册到该总线上时,会执行match函数,遍历一次所有的设备和驱动,看是否有能匹配的,如果匹配了,就将对应的 设备和驱动绑定,并调用 probe 回调函数,该函数最终会进一步调用驱动中定义的probe 函数。

典型函数,总线的注册和注销

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int __must_check bus_register(struct bus_type *bus);
void bus_unregister(struct bus_type *bus);

//很多总线在初始化时,就会使用这个来进行总线注册,如
//平台总线
struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.dma_configure	= platform_dma_configure,
	.pm		= &platform_dev_pm_ops,
};
bus_register(&platform_bus_type);

//I2C
struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};
bus_register(&i2c_bus_type);

//PCI
struct bus_type pci_bus_type = {
	.name		= "pci",
	.match		= pci_bus_match,
	.uevent		= pci_uevent,
	.probe		= pci_device_probe,
	.remove		= pci_device_remove,
	.shutdown	= pci_device_shutdown,
	.dev_groups	= pci_dev_groups,
	.bus_groups	= pci_bus_groups,
	.drv_groups	= pci_drv_groups,
	.pm		= PCI_PM_OPS_PTR,
	.num_vf		= pci_bus_num_vf,
	.dma_configure	= pci_dma_configure,
};
bus_register(&pci_bus_type);

在总线注册成功后,系统会在/sys/bus/目录下创建一个新注册总线名的目录,里面有两个重要的子目录,drivers和devices,对应挂在该总线上的驱动和设备。

/sys/bus/目录是在系统启动时创建的,内核在启动过程中,对于设备模型,首先调用driver_init函数来初始化设备模型,该驱动模型初始化函数中 包括调用buses_init创建/sys/bus目录。

设备

设备在内核中的定义位于 /include/linux/device.h 中的 struct device 。实例化到具体的设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/**
 * 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.
 * @links:	Links to suppliers and consumers of this device.
 * @power:	For device power management.
 *		See Documentation/driver-api/pm/devices.rst 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/driver-api/pinctl.rst for details.
 * @msi_list:	Hosts MSI descriptors
 * @msi_domain: The generic MSI domain this device is using.
 * @numa_node:	NUMA node this device is close to.
 * @dma_ops:    DMA mapping operations for this device.
 * @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.
 * @bus_dma_mask: Mask of an upstream bridge or bus which imposes a smaller DMA
 *		limit than the device itself supports.
 * @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.
 * @iommu_fwspec: IOMMU-specific properties supplied by firmware.
 *
 * @offline_disabled: If set, the device is permanently online.
 * @offline:	Set after successful invocation of bus type's .offline().
 * @of_node_reused: Set if the device-tree node is shared with an ancestor
 *              device.
 *
 * 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_links_info	links;
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
	struct irq_domain	*msi_domain;
#endif
#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
	raw_spinlock_t		msi_lock;
	struct list_head	msi_list;
#endif

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	const struct dma_map_ops *dma_ops;
	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. */
	u64		bus_dma_mask;	/* upstream dma_mask constraint */
	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;
	struct iommu_fwspec	*iommu_fwspec;

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

设备结构体较大,列举一些常用的,如 init_name指定设备名称,可以用于总线匹配。parent指向父设备,Linux设备模型中设备之间是树状结构管理的。 bus指向挂载的总线结构体,of_node指向关联的设备树节点,driver_data记录驱动用的设备相关数据,class指向所属的classrelease设备 注销时的回调函数。等等。

典型相关函数

1
2
int __must_check device_register(struct device *dev);
void device_unregister(struct device *dev);

设备注册成功后,系统会在 /sys/devices 下创建对应设备名称的目录,即代表了一个设备,里面有各种属性信息等。 /sys/下的其他目录中的设备一般都是最终软链接到这个目录中。

驱动

驱动,在内核中的基本代表结构体是 struct device_driver ,用于驱动匹配的设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
 * struct device_driver - The basic device driver structure
 * @name:	Name of the device driver.
 * @bus:	The bus which the device of this driver belongs to.
 * @owner:	The module owner.
 * @mod_name:	Used for built-in modules.
 * @suppress_bind_attrs: Disables bind/unbind via sysfs.
 * @probe_type:	Type of the probe (synchronous or asynchronous) to use.
 * @of_match_table: The open firmware table.
 * @acpi_match_table: The ACPI match table.
 * @probe:	Called to query the existence of a specific device,
 *		whether this driver can work with it, and bind the driver
 *		to a specific device.
 * @remove:	Called when the device is removed from the system to
 *		unbind a device from this driver.
 * @shutdown:	Called at shut-down time to quiesce the device.
 * @suspend:	Called to put the device to sleep mode. Usually to a
 *		low power state.
 * @resume:	Called to bring a device from sleep mode.
 * @groups:	Default attributes that get created by the driver core
 *		automatically.
 * @pm:		Power management operations of the device which matched
 *		this driver.
 * @coredump:	Called when sysfs entry is written to. The device driver
 *		is expected to call the dev_coredump API resulting in a
 *		uevent.
 * @p:		Driver core's private data, no one other than the driver
 *		core can touch this.
 *
 * The device driver-model tracks all of the drivers known to the system.
 * The main reason for this tracking is to enable the driver core to match
 * up drivers with new devices. Once drivers are known objects within the
 * system, however, a number of other things become possible. Device drivers
 * can export information and configuration variables that are independent
 * of any specific device.
 */
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;
	void (*coredump) (struct device *dev);

	struct driver_private *p;
};

一些常见成员:name表示驱动名称,同时可以用作与设备名进行比较匹配。bus表示该驱动需要挂载的对应总线, of_match_table用于指定该驱动支持的设备,在使用设备树时,会使用该成员中的compatible与设备树中的compatible进行比较匹配。 proberemove是驱动匹配成功后 及 设备卸载驱动时进行的回调函数,一般是初始化和反初始化操作。

驱动的基本注册函数:

1
2
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

驱动注册成功后,系统会在 /sys/bus/<bus>/drivers 目录中出现对应名称的驱动目录。此外,通常不会直接调用驱动的基本注册函数,内核的 各个子系统通常提供了对应的封装后的注册函数,直观上,不需要再手动指定bus类型了。如pci_register_driver即注册pci总线的设备驱动, 它内部帮助用户自动配置了一些成员(主要包括bus类型),并在最后调用driver_register完成驱动注册;同时,其对应的驱动结构体也是被进一步继承的, 如struct pci_driver里面包含了struct device_driver基本成员。其他的总线(i2c,spi)也是类似的继承思想。通常驱动程序中直接使用对应总线 的驱动注册。以上这些基本接口函数 是linux的设备驱动模型。

属性文件

/sys/ 目录下的各个目录,如总线,设备,驱动目录,其中的文件都是有内核导出到用户空间的属性,是不占用物理磁盘空间的,可以查看和设置对应 对象的各种属性。在内核代码中(/include/linux/sysfs.h),使用struct attributestruct attribute_group来表示一个和一组属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct attribute {
	const char		*name;
	umode_t			mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	bool			ignore_lockdep:1;
	struct lock_class_key	*key;
	struct lock_class_key	skey;
#endif
};

/**
 * struct attribute_group - data structure used to declare an attribute group.
 * @name:	Optional: Attribute group name
 *		If specified, the attribute group will be created in
 *		a new subdirectory with this name.
 * @is_visible:	Optional: Function to return permissions associated with an
 *		attribute of the group. Will be called repeatedly for each
 *		non-binary attribute in the group. Only read/write
 *		permissions as well as SYSFS_PREALLOC are accepted. Must
 *		return 0 if an attribute is not visible. The returned value
 *		will replace static permissions defined in struct attribute.
 * @is_bin_visible:
 *		Optional: Function to return permissions associated with a
 *		binary attribute of the group. Will be called repeatedly
 *		for each binary attribute in the group. Only read/write
 *		permissions as well as SYSFS_PREALLOC are accepted. Must
 *		return 0 if a binary attribute is not visible. The returned
 *		value will replace static permissions defined in
 *		struct bin_attribute.
 * @attrs:	Pointer to NULL terminated list of attributes.
 * @bin_attrs:	Pointer to NULL terminated list of binary attributes.
 *		Either attrs or bin_attrs or both must be provided.
 */
struct attribute_group {
	const char		*name;
	umode_t			(*is_visible)(struct kobject *,
					      struct attribute *, int);
	umode_t			(*is_bin_visible)(struct kobject *,
						  struct bin_attribute *, int);
	struct attribute	**attrs;
	struct bin_attribute	**bin_attrs;
};

属性结构体中,主要是两个成员,一个属性名称,一个属性读写权限,没有属性值,所以该结构更像是一个接口。 属性文件的使用,一般会被进一步继承,扩展,主要提供读取和写入的回调,以实现对特定对象属性的实例化读写。如总线属性文件,设备属性文件,驱动属性文件。 属性值的实现通常使用基于struct attribute的文本形式,也有二进制的形式struct bin_attribute

设备属性文件

头文件:/include/linux/device.h ,设备属性结构如下,提供了读写功能

1
2
3
4
5
6
7
8
/* interface for exporting device attributes */
struct device_attribute {
	struct attribute	attr;
	ssize_t (*show)(struct device *dev, struct device_attribute *attr,
			char *buf);
	ssize_t (*store)(struct device *dev, struct device_attribute *attr,
			 const char *buf, size_t count);
};

基本使用方式,定义属性,实现读写回调函数,创建属性文件,参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//(1) 定义属性,一般可以用这几个宏
#define DEVICE_ATTR_RW(_name) \
	struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
#define DEVICE_ATTR_RO(_name) \
	struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
#define DEVICE_ATTR_WO(_name) \
	struct device_attribute dev_attr_##_name = __ATTR_WO(_name)
#define DEVICE_ATTR(_name, _mode, _show, _store) \
	struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

//假设创建timeout属性(rw,0644)
DEVICE_ATTR_RW(timeout);

//(2)针对定义的属性,提供对应的读写函数,函数声明应当能够被定义处找到,(因为定义属性时,需要相关的属性读写函数回调的声明)
//如果使用了上述前三个宏定义,属性的读写函数名是固定的(名称+ _show/_store),因为宏定义就是这样固定实现的,无法修改
ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,const char *buf, size_t count);

//假定有属性 timeout (RW权限),声明并定义好
ssize_t timeout_show(struct device *dev, struct device_attribute *attr,char *buf);
ssize_t timeout_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count);


//(3)在内核代码(驱动代码)中,进行属性导出,使用API,
// 需要注意:设备属性entry 参数的变量名也是固定的,在使用宏定义 来定义属性时定死的,设备属性变量名为 dev_attr_+属性名,
extern int device_create_file(struct device *device,
			      const struct device_attribute *entry);
extern void device_remove_file(struct device *dev,
			       const struct device_attribute *attr);

// 假设创建 timeout 属性
device_create_file(&my_xxx_dev->dev,&dev_attr_timeout);

参考示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//全局定义一个 warn_cnt 属性
ssize_t warn_cnt_show(struct device *dev, struct device_attribute *attr,char *buf){
    xt_qrng_t *hdev;
    int data = 0;
    hdev = dev_get_drvdata(dev);
    // data = ... ; // read regs to get 
    return sprintf(buf,"%d\n",data);
}
DEVICE_ATTR_RO(warn_cnt);

//对于每个具体的device,probe中创建设备相关文件。
int qrng_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
	//...
    device_create_file(&hdev->pcidev->dev,&dev_attr_warn_cnt);
    device_create_file(&hdev->pcidev->dev,&dev_attr_fail_cnt);
	//...
}

//设备卸载驱动时移除相关属性文件
void qrng_remove(struct pci_dev *pdev)
{
	//...
    device_remove_file(&hdev->pcidev->dev,&dev_attr_warn_cnt);
	//...
}

宏定义展开参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
#define DEVICE_ATTR(_name, _mode, _show, _store) \
	struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define DEVICE_ATTR_RW(_name) \
	struct device_attribute dev_attr_##_name = __ATTR_RW(_name)

#define __ATTR(_name, _mode, _show, _store) {				\
	.attr = {.name = __stringify(_name),				\
		 .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },		\
	.show	= _show,						\
	.store	= _store,						\
}

#define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)

驱动属性文件

驱动属性文件和设备属性文件使用基本一致。相关结构体和API:(/include/linux/device.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct driver_attribute {
	struct attribute attr;
	ssize_t (*show)(struct device_driver *driver, char *buf);
	ssize_t (*store)(struct device_driver *driver, const char *buf,
			 size_t count);
};

#define DRIVER_ATTR_RW(_name) \
	struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
#define DRIVER_ATTR_RO(_name) \
	struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR_WO(_name) \
	struct driver_attribute driver_attr_##_name = __ATTR_WO(_name)

extern int __must_check driver_create_file(struct device_driver *driver,
					const struct driver_attribute *attr);
extern void driver_remove_file(struct device_driver *driver,
			       const struct driver_attribute *attr);

驱动属性是针对驱动,是一类设备的共有属性。所以注册驱动属性文件通常是在注册完驱动后,就注册驱动属性文件。example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ssize_t card_cnt_show(struct device_driver *driver, char *buf){
    return sprintf(buf,"%d\n",gb_get_card_count());
}
DRIVER_ATTR_RO(card_cnt);

int __init module_init(void){
	//...
    ret = pci_register_driver(&qrng_drv);
    if(ret != 0){
        pr_err("err pci_register_driver:%d\n",ret);
        goto err_reg_drv;
    }

    ret = driver_create_file(&qrng_drv.driver,&driver_attr_card_cnt);
    if(ret != 0){
        goto errout;
    }
	//...
}

void __exit module_exit(void){
	//...
	driver_remove_file(&qrng_drv.driver,&driver_attr_card_cnt);
    pci_unregister_driver(&qrng_drv);
	//...
}

总线属性文件

总线也有相似的属性文件和API接口。实际通常不会用到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct bus_attribute {
	struct attribute	attr;
	ssize_t (*show)(struct bus_type *bus, char *buf);
	ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};

#define BUS_ATTR(_name, _mode, _show, _store)	\
	struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define BUS_ATTR_RW(_name) \
	struct bus_attribute bus_attr_##_name = __ATTR_RW(_name)
#define BUS_ATTR_RO(_name) \
	struct bus_attribute bus_attr_##_name = __ATTR_RO(_name)

extern int __must_check bus_create_file(struct bus_type *,
					struct bus_attribute *);
extern void bus_remove_file(struct bus_type *, struct bus_attribute *);

补充记录

属性导出文件和模块参数
以上的这些模块属性导出文件,不仅可以查看导出的属性,还可以进行模块的运行时调整,如可以在.store 中更新完值后, 进一步对模块进行一些调整,(流程需要根据实际情况编写)。比如一些功能,可以实现为动态开关,提供到用户空间,这样后, 用户空间可以方面的控制驱动,相当于是 除了/dev 下设备节点的另一种方便的控制方式。 .show 一般用户查看状态, 可以直接将关键变量导出,也可以实现为读取时真实进行一系列操作以读取实时的状态。

相比于模块参数,模块参数更多是用于用户模块初始化时的配置,虽然运行中也可以修改,但不如属性导出文件可以提供回调, 功能受限。

This post is licensed under CC BY 4.0 by the author.

内核内存分配接口

内核-互斥与同步机制