备注:代码路径https://github.com/torvalds/linux/blob/v6.5-rc6/drivers/nvme/host/sysfs.c
nvme_sysfs_reset
static ssize_t nvme_sysfs_reset(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
struct nvme_ctrl *ctrl = dev_get_drvdata(dev);
int ret;
ret = nvme_reset_ctrl_sync(ctrl);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR(reset_controller, S_IWUSR, NULL, nvme_sysfs_reset);
这段代码是用于在 sysfs 中为 NVMe 控制器添加一个属性以执行重置操作的。让我解释一下这段代码的作用:
-
nvme_sysfs_reset
函数是一个用于执行控制器重置的回调函数。当用户在 sysfs 上写入reset_controller
属性时,该函数将被调用。它接收以下参数:dev
: 代表设备的指针,即 NVMe 控制器的设备结构体。attr
: 代表设备属性的指针,即要执行的属性(在此为reset_controller
)。buf
: 用户传递的数据,通常是用户写入属性的值。count
: 传递的数据的大小。
-
在
nvme_sysfs_reset
函数中,首先获取与设备相关的nvme_ctrl
结构体,然后调用nvme_reset_ctrl_sync
函数来同步地重置控制器。如果重置失败,函数将返回负数;如果成功,函数将返回非负数。无论重置是否成功,都返回count
,通常是传递的数据的大小。 -
DEVICE_ATTR(reset_controller, S_IWUSR, NULL, nvme_sysfs_reset);
这一行用于定义一个名为reset_controller
的设备属性。它的参数分别是属性名称、权限标志、显示函数和存储函数。在此代码中,S_IWUSR
标志表示用户具有写权限。NULL
表示没有显示函数,因此用户将无法读取此属性。nvme_sysfs_reset
函数是存储函数,它将在用户写入属性时被调用。
此代码段的效果是:在 sysfs 中为 NVMe 控制器创建一个名为 reset_controller
的属性,允许用户通过写入该属性来触发控制器的同步重置。写入属性后,nvme_sysfs_reset
函数将被调用以执行重置操作。无论是否成功,都会返回写入的数据大小。
nvme_sysfs_rescan
static ssize_t nvme_sysfs_rescan(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
struct nvme_ctrl *ctrl = dev_get_drvdata(dev);
nvme_queue_scan(ctrl);
return count;
}
static DEVICE_ATTR(rescan_controller, S_IWUSR, NULL, nvme_sysfs_rescan);
这段代码是用于在 sysfs 中为 NVMe 控制器添加一个属性以执行重新扫描操作的。让我解释一下这段代码的作用:
-
nvme_sysfs_rescan
函数是一个用于执行重新扫描操作的回调函数。当用户在 sysfs 上写入rescan_controller
属性时,该函数将被调用。它接收以下参数:dev
: 代表设备的指针,即 NVMe 控制器的设备结构体。attr
: 代表设备属性的指针,即要执行的属性(在此为rescan_controller
)。buf
: 用户传递的数据,通常是用户写入属性的值。count
: 传递的数据的大小。
-
在
nvme_sysfs_rescan
函数中,首先获取与设备相关的nvme_ctrl
结构体,然后调用nvme_queue_scan
函数来重新扫描控制器的队列。这将触发 NVMe 子系统对控制器进行重新扫描,以便发现和管理新添加的设备。 -
DEVICE_ATTR(rescan_controller, S_IWUSR, NULL, nvme_sysfs_rescan);
这一行用于定义一个名为rescan_controller
的设备属性。它的参数分别是属性名称、权限标志、显示函数和存储函数。在此代码中,S_IWUSR
标志表示用户具有写权限。NULL
表示没有显示函数,因此用户将无法读取此属性。nvme_sysfs_rescan
函数是存储函数,它将在用户写入属性时被调用。
此代码段的效果是:在 sysfs 中为 NVMe 控制器创建一个名为 rescan_controller
的属性,允许用户通过写入该属性来触发对控制器的重新扫描操作。写入属性后,nvme_sysfs_rescan
函数将被调用以执行重新扫描操作,并且总是返回写入的数据大小。
dev_to_ns_head
static inline struct nvme_ns_head *dev_to_ns_head(struct device *dev)
{
struct gendisk *disk = dev_to_disk(dev);
if (disk->fops == &nvme_bdev_ops)
return nvme_get_ns_from_dev(dev)->head;
else
return disk->private_data;
}
这段代码定义了一个名为 dev_to_ns_head
的内联函数,用于从设备指针中获取对应的 NVMe 命名空间头部结构体指针。
-
dev_to_ns_head
函数接受一个指向struct device
的指针作为参数。 -
首先,通过
dev_to_disk
宏将设备指针转换为关联的gendisk
结构体指针,即块设备表示。 -
然后,检查
gendisk
的操作函数指针fops
是否等于nvme_bdev_ops
。如果是,则说明该设备是由 NVMe 块设备驱动管理的,因此使用nvme_get_ns_from_dev(dev)->head
获取 NVMe 命名空间头部结构体指针。 -
如果
fops
不等于nvme_bdev_ops
,则表示该设备不是 NVMe 块设备,而是其他类型的块设备,因此可以直接从gendisk
的private_data
字段中获取 NVMe 命名空间头部结构体指针。
总之,dev_to_ns_head
函数根据设备类型来获取对应的 NVMe 命名空间头部结构体指针,以便进行后续操作。
wwid_show
static ssize_t wwid_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct nvme_ns_head *head = dev_to_ns_head(dev);
struct nvme_ns_ids *ids = &head->ids;
struct nvme_subsystem *subsys = head->subsys;
int serial_len = sizeof(subsys->serial);
int model_len = sizeof(subsys->model);
if (!uuid_is_null(&ids->uuid))
return sysfs_emit(buf, "uuid.%pU\n", &ids->uuid);
if (memchr_inv(ids->nguid, 0, sizeof(ids->nguid)))
return sysfs_emit(buf, "eui.%16phN\n", ids->nguid);
if (memchr_inv(ids->eui64, 0, sizeof(ids->eui64)))
return sysfs_emit(buf, "eui.%8phN\n", ids->eui64);
while (serial_len > 0 && (subsys->serial[serial_len - 1] == ' ' ||
subsys->serial[serial_len - 1] == '\0'))
serial_len--;
while (model_len > 0 && (subsys->model[model_len - 1] == ' ' ||
subsys->model[model_len - 1] == '\0'))
model_len--;
return sysfs_emit(buf, "nvme.%04x-%*phN-%*phN-%08x\n", subsys->vendor_id,
serial_len, subsys->serial, model_len, subsys->model,
head->ns_id);
}
static DEVICE_ATTR_RO(wwid);
这段代码定义了一个名为 wwid_show
的函数,用于在 sysfs 中显示命名空间的唯一标识符(wwid)。这个函数会在特定的 sysfs 属性被读取时调用。
-
wwid_show
函数接受一个指向struct device
的指针作为参数,以及一个指向struct device_attribute
的指针和一个指向字符缓冲区的指针。 -
首先,通过
dev_to_ns_head
函数将设备指针转换为对应的 NVMe 命名空间头部结构体指针。 -
接着,从命名空间头部结构体中获取命名空间的标识信息,如 UUID、NGUID、EUI64、子系统的厂商ID等。
-
判断标识信息是否存在,如果存在则分别格式化输出到缓冲区中,并返回输出的长度。
-
如果标识信息不存在,会对子系统的序列号(serial)和型号(model)进行处理,将末尾的空格或空字符去除。
-
最后,使用
sysfs_emit
函数格式化输出 NVMe 命名空间的唯一标识符,包括子系统的厂商ID、序列号、型号和命名空间ID,输出到缓冲区中。 -
通过
DEVICE_ATTR_RO
宏定义了一个只读的 sysfs 设备属性,其名称为wwid
,并将wwid_show
函数作为读取属性时的处理函数。
总之,通过这段代码,sysfs 中的 wwid
属性将根据命名空间的标识信息生成并显示唯一标识符。
nguid_show
static ssize_t nguid_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sysfs_emit(buf, "%pU\n", dev_to_ns_head(dev)->ids.nguid);
}
static DEVICE_ATTR_RO(nguid);
这段代码定义了一个名为 nguid_show
的函数,用于在 sysfs 中显示命名空间的 NGUID(Namespace Globally Unique Identifier)。这个函数会在特定的 sysfs 属性被读取时调用。
-
nguid_show
函数接受一个指向struct device
的指针作为参数,以及一个指向struct device_attribute
的指针和一个指向字符缓冲区的指针。 -
通过
dev_to_ns_head
函数将设备指针转换为对应的 NVMe 命名空间头部结构体指针。 -
通过命名空间头部结构体中的
ids.nguid
成员,使用%pU
格式化字符串输出 NGUID 到缓冲区中。 -
使用
sysfs_emit
函数格式化输出 NGUID,并将其输出到缓冲区中。 -
通过
DEVICE_ATTR_RO
宏定义了一个只读的 sysfs 设备属性,其名称为nguid
,并将nguid_show
函数作为读取属性时的处理函数。
总之,通过这段代码,sysfs 中的 nguid
属性将显示命名空间的 NGUID,这是一个全局唯一的标识符。
uuid_show
static ssize_t uuid_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct nvme_ns_ids *ids = &dev_to_ns_head(dev)->ids;
/* For backward compatibility expose the NGUID to userspace if
* we have no UUID set
*/
if (uuid_is_null(&ids->uuid)) {
dev_warn_once(dev,
"No UUID available providing old NGUID\n");
return sysfs_emit(buf, "%pU\n", ids->nguid);
}
return sysfs_emit(buf, "%pU\n", &ids->uuid);
}
static DEVICE_ATTR_RO(uuid);
这段代码定义了一个名为 uuid_show
的函数,用于在 sysfs 中显示命名空间的 UUID(Universally Unique Identifier)。这个函数会在特定的 sysfs 属性被读取时调用。
-
uuid_show
函数接受一个指向struct device
的指针作为参数,以及一个指向struct device_attribute
的指针和一个指向字符缓冲区的指针。 -
通过
dev_to_ns_head
函数将设备指针转换为对应的 NVMe 命名空间头部结构体指针。 -
通过命名空间头部结构体中的
ids
成员,使用&ids->uuid
来获取 UUID。 -
检查 UUID 是否为空(null)。如果 UUID 为空,则输出对应的 NGUID(用于向后兼容),并发出一条警告。这是为了向后兼容性考虑,在没有 UUID 的情况下使用 NGUID。
-
如果 UUID 不为空,使用
%pU
格式化字符串输出 UUID 到缓冲区中。 -
使用
sysfs_emit
函数格式化输出 UUID 或 NGUID,并将其输出到缓冲区中。 -
通过
DEVICE_ATTR_RO
宏定义了一个只读的 sysfs 设备属性,其名称为uuid
,并将uuid_show
函数作为读取属性时的处理函数。
总之,通过这段代码,sysfs 中的 uuid
属性将显示命名空间的 UUID。如果 UUID 为空,将显示相应的 NGUID,同时发出一条警告。这样可以在向后兼容性情况下提供相应的标识信息。
eui_show
static ssize_t eui_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sysfs_emit(buf, "%8ph\n", dev_to_ns_head(dev)->ids.eui64);
}
static DEVICE_ATTR_RO(eui);
这段代码定义了一个名为 eui_show
的函数,用于在 sysfs 中显示命名空间的 EUI64(Extended Unique Identifier)。
-
eui_show
函数接受一个指向struct device
的指针作为参数,以及一个指向struct device_attribute
的指针和一个指向字符缓冲区的指针。 -
通过
dev_to_ns_head
函数将设备指针转换为对应的 NVMe 命名空间头部结构体指针。 -
通过命名空间头部结构体中的
ids
成员,使用ids->eui64
来获取 EUI64。 -
使用
%8ph
格式化字符串输出 EUI64 到缓冲区中,其中%8ph
是一个格式化字符串,将 EUI64 格式化为 8 字节十六进制格式。 -
使用
sysfs_emit
函数格式化输出 EUI64,并将其输出到缓冲区中。 -
通过
DEVICE_ATTR_RO
宏定义了一个只读的 sysfs 设备属性,其名称为eui
,并将eui_show
函数作为读取属性时的处理函数。
总之,通过这段代码,sysfs 中的 eui
属性将显示命名空间的 EUI64,以 8 字节的十六进制格式。
nsid_show
static ssize_t nsid_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sysfs_emit(buf, "%d\n", dev_to_ns_head(dev)->ns_id);
}
static DEVICE_ATTR_RO(nsid);
这段代码定义了一个名为 nsid_show
的函数,用于在 sysfs 中显示命名空间的 ID(Namespace ID)。
-
nsid_show
函数接受一个指向struct device
的指针作为参数,以及一个指向struct device_attribute
的指针和一个指向字符缓冲区的指针。 -
通过
dev_to_ns_head
函数将设备指针转换为对应的 NVMe 命名空间头部结构体指针。 -
通过命名空间头部结构体中的
ns_id
成员获取命名空间的 ID。 -
使用
%d
格式化字符串输出命名空间的 ID 到缓冲区中。 -
使用
sysfs_emit
函数格式化输出命名空间的 ID,并将其输出到缓冲区中。 -
通过
DEVICE_ATTR_RO
宏定义了一个只读的 sysfs 设备属性,其名称为nsid
,并将nsid_show
函数作为读取属性时的处理函数。
总之,通过这段代码,sysfs 中的 nsid
属性将显示命名空间的 ID。
nvme_ns_id_attrs
static struct attribute *nvme_ns_id_attrs[] = {
&dev_attr_wwid.attr,
&dev_attr_uuid.attr,
&dev_attr_nguid.attr,
&dev_attr_eui.attr,
&dev_attr_nsid.attr,
#ifdef CONFIG_NVME_MULTIPATH
&dev_attr_ana_grpid.attr,
&dev_attr_ana_state.attr,
#endif
NULL,
};
static umode_t nvme_ns_id_attrs_are_visible(struct kobject *kobj,
struct attribute *a, int n)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct nvme_ns_ids *ids = &dev_to_ns_head(dev)->ids;
if (a == &dev_attr_uuid.attr) {
if (uuid_is_null(&ids->uuid) &&
!memchr_inv(ids->nguid, 0, sizeof(ids->nguid)))
return 0;
}
if (a == &dev_attr_nguid.attr) {
if (!memchr_inv(ids->nguid, 0, sizeof(ids->nguid)))
return 0;
}
if (a == &dev_attr_eui.attr) {
if (!memchr_inv(ids->eui64, 0, sizeof(ids->eui64)))
return 0;
}
#ifdef CONFIG_NVME_MULTIPATH
if (a == &dev_attr_ana_grpid.attr || a == &dev_attr_ana_state.attr) {
if (dev_to_disk(dev)->fops != &nvme_bdev_ops) /* per-path attr */
return 0;
if (!nvme_ctrl_use_ana(nvme_get_ns_from_dev(dev)->ctrl))
return 0;
}
#endif
return a->mode;
}
static const struct attribute_group nvme_ns_id_attr_group = {
.attrs = nvme_ns_id_attrs,
.is_visible = nvme_ns_id_attrs_are_visible,
};
const struct attribute_group *nvme_ns_id_attr_groups[] = {
&nvme_ns_id_attr_group,
NULL,
};
这段代码定义了 NVMe 命名空间的属性组和属性可见性的相关函数。
-
nvme_ns_id_attrs
数组定义了 NVMe 命名空间属性的指针集合,包括 WWID、UUID、NGUID、EUI 和命名空间 ID(nsid)。如果启用了 NVMe 多路径功能(CONFIG_NVME_MULTIPATH
),还包括 ANA(Asymmetric Namespace Access)相关的属性。 -
nvme_ns_id_attrs_are_visible
函数用于确定特定属性是否可见。它接受一个指向struct kobject
结构体的指针,一个指向属性的指针,以及属性在属性组中的索引。该函数的作用是根据属性的类型和属性组的可见性规则,决定是否显示属性。- 对于 UUID 属性,如果 UUID 是空的且 NGUID 不为空,则不显示。
- 对于 NGUID 属性,如果 NGUID 不为空,则不显示。
- 对于 EUI 属性,如果 EUI 不为空,则不显示。
- 对于 ANA 相关的属性,如果是针对每个路径的属性且控制器不使用 ANA,则不显示。
-
nvme_ns_id_attr_group
定义了 NVMe 命名空间属性组,包括属性集合nvme_ns_id_attrs
和属性可见性函数nvme_ns_id_attrs_are_visible
。 -
nvme_ns_id_attr_groups
数组定义了一个指向 NVMe 命名空间属性组的指针,其中包含nvme_ns_id_attr_group
。这个数组将用于为 NVMe 命名空间创建 sysfs 属性组。
总之,通过这段代码,为 NVMe 命名空间定义了一组属性,通过在 nvme_ns_id_attrs_are_visible
函数中进行可见性控制,只显示符合条件的属性,然后将这些属性集合组成一个 sysfs 属性组。
nvme_show_str_function & nvme_show_int_function
#define nvme_show_str_function(field) \
static ssize_t field##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
struct nvme_ctrl *ctrl = dev_get_drvdata(dev); \
return sysfs_emit(buf, "%.*s\n", \
(int)sizeof(ctrl->subsys->field), ctrl->subsys->field); \
} \
static DEVICE_ATTR(field, S_IRUGO, field##_show, NULL);
nvme_show_str_function(model);
nvme_show_str_function(serial);
nvme_show_str_function(firmware_rev);
#define nvme_show_int_function(field) \
static ssize_t field##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
struct nvme_ctrl *ctrl = dev_get_drvdata(dev); \
return sysfs_emit(buf, "%d\n", ctrl->field); \
} \
static DEVICE_ATTR(field, S_IRUGO, field##_show, NULL);
nvme_show_int_function(cntlid);
nvme_show_int_function(numa_node);
nvme_show_int_function(queue_count);
nvme_show_int_function(sqsize);
nvme_show_int_function(kato);
这段代码使用宏定义创建了一系列的设备属性的读取函数和对应的设备属性。
nvme_show_str_function
宏用于创建读取字符串类型属性的函数和对应的设备属性。它的参数是属性名(例如 “model”、“serial”、“firmware_rev”),根据属性名展开宏,定义了一个以属性名加上 _show
后缀的函数。这个函数接受一个指向 struct device
的指针,一个指向 struct device_attribute
的指针,以及一个字符缓冲区 buf
,并使用 sysfs_emit
函数将属性值写入到缓冲区中。
nvme_show_int_function
宏用于创建读取整数类型属性的函数和对应的设备属性。与字符串类型属性类似,这个宏的参数是属性名,然后根据属性名展开宏,定义了一个以属性名加上 _show
后缀的函数。这个函数同样接受一个指向 struct device
的指针,一个指向 struct device_attribute
的指针,以及一个字符缓冲区 buf
,然后使用 sysfs_emit
函数将整数属性值以格式化的方式写入缓冲区。
在每个宏的展开中,都使用了 dev_get_drvdata(dev)
获取了与设备关联的私有数据(通常是指向设备控制结构的指针)。然后使用 ctrl->subsys->field
或 ctrl->field
的方式获取具体的属性值。
最后,通过宏定义分别创建了各个属性的读取函数和对应的设备属性,这些设备属性可以在 sysfs 中访问,用于显示相关属性的信息。