diff options
Diffstat (limited to 'drivers/nvme/host/sysfs.c')
| -rw-r--r-- | drivers/nvme/host/sysfs.c | 668 | 
1 files changed, 668 insertions, 0 deletions
diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c new file mode 100644 index 000000000000..45e91811f905 --- /dev/null +++ b/drivers/nvme/host/sysfs.c @@ -0,0 +1,668 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sysfs interface for the NVMe core driver. + * + * Copyright (c) 2011-2014, Intel Corporation. + */ + +#include <linux/nvme-auth.h> + +#include "nvme.h" +#include "fabrics.h" + +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); + +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); + +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; +} + +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); + +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); + +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_ratelimited(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); + +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); + +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); + +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, +}; + +#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); + +static ssize_t nvme_sysfs_delete(struct device *dev, +				struct device_attribute *attr, const char *buf, +				size_t count) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + +	if (!test_bit(NVME_CTRL_STARTED_ONCE, &ctrl->flags)) +		return -EBUSY; + +	if (device_remove_file_self(dev, attr)) +		nvme_delete_ctrl_sync(ctrl); +	return count; +} +static DEVICE_ATTR(delete_controller, S_IWUSR, NULL, nvme_sysfs_delete); + +static ssize_t nvme_sysfs_show_transport(struct device *dev, +					 struct device_attribute *attr, +					 char *buf) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + +	return sysfs_emit(buf, "%s\n", ctrl->ops->name); +} +static DEVICE_ATTR(transport, S_IRUGO, nvme_sysfs_show_transport, NULL); + +static ssize_t nvme_sysfs_show_state(struct device *dev, +				     struct device_attribute *attr, +				     char *buf) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); +	static const char *const state_name[] = { +		[NVME_CTRL_NEW]		= "new", +		[NVME_CTRL_LIVE]	= "live", +		[NVME_CTRL_RESETTING]	= "resetting", +		[NVME_CTRL_CONNECTING]	= "connecting", +		[NVME_CTRL_DELETING]	= "deleting", +		[NVME_CTRL_DELETING_NOIO]= "deleting (no IO)", +		[NVME_CTRL_DEAD]	= "dead", +	}; + +	if ((unsigned)ctrl->state < ARRAY_SIZE(state_name) && +	    state_name[ctrl->state]) +		return sysfs_emit(buf, "%s\n", state_name[ctrl->state]); + +	return sysfs_emit(buf, "unknown state\n"); +} + +static DEVICE_ATTR(state, S_IRUGO, nvme_sysfs_show_state, NULL); + +static ssize_t nvme_sysfs_show_subsysnqn(struct device *dev, +					 struct device_attribute *attr, +					 char *buf) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + +	return sysfs_emit(buf, "%s\n", ctrl->subsys->subnqn); +} +static DEVICE_ATTR(subsysnqn, S_IRUGO, nvme_sysfs_show_subsysnqn, NULL); + +static ssize_t nvme_sysfs_show_hostnqn(struct device *dev, +					struct device_attribute *attr, +					char *buf) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + +	return sysfs_emit(buf, "%s\n", ctrl->opts->host->nqn); +} +static DEVICE_ATTR(hostnqn, S_IRUGO, nvme_sysfs_show_hostnqn, NULL); + +static ssize_t nvme_sysfs_show_hostid(struct device *dev, +					struct device_attribute *attr, +					char *buf) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + +	return sysfs_emit(buf, "%pU\n", &ctrl->opts->host->id); +} +static DEVICE_ATTR(hostid, S_IRUGO, nvme_sysfs_show_hostid, NULL); + +static ssize_t nvme_sysfs_show_address(struct device *dev, +					 struct device_attribute *attr, +					 char *buf) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + +	return ctrl->ops->get_address(ctrl, buf, PAGE_SIZE); +} +static DEVICE_ATTR(address, S_IRUGO, nvme_sysfs_show_address, NULL); + +static ssize_t nvme_ctrl_loss_tmo_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); +	struct nvmf_ctrl_options *opts = ctrl->opts; + +	if (ctrl->opts->max_reconnects == -1) +		return sysfs_emit(buf, "off\n"); +	return sysfs_emit(buf, "%d\n", +			  opts->max_reconnects * opts->reconnect_delay); +} + +static ssize_t nvme_ctrl_loss_tmo_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); +	struct nvmf_ctrl_options *opts = ctrl->opts; +	int ctrl_loss_tmo, err; + +	err = kstrtoint(buf, 10, &ctrl_loss_tmo); +	if (err) +		return -EINVAL; + +	if (ctrl_loss_tmo < 0) +		opts->max_reconnects = -1; +	else +		opts->max_reconnects = DIV_ROUND_UP(ctrl_loss_tmo, +						opts->reconnect_delay); +	return count; +} +static DEVICE_ATTR(ctrl_loss_tmo, S_IRUGO | S_IWUSR, +	nvme_ctrl_loss_tmo_show, nvme_ctrl_loss_tmo_store); + +static ssize_t nvme_ctrl_reconnect_delay_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + +	if (ctrl->opts->reconnect_delay == -1) +		return sysfs_emit(buf, "off\n"); +	return sysfs_emit(buf, "%d\n", ctrl->opts->reconnect_delay); +} + +static ssize_t nvme_ctrl_reconnect_delay_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); +	unsigned int v; +	int err; + +	err = kstrtou32(buf, 10, &v); +	if (err) +		return err; + +	ctrl->opts->reconnect_delay = v; +	return count; +} +static DEVICE_ATTR(reconnect_delay, S_IRUGO | S_IWUSR, +	nvme_ctrl_reconnect_delay_show, nvme_ctrl_reconnect_delay_store); + +static ssize_t nvme_ctrl_fast_io_fail_tmo_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + +	if (ctrl->opts->fast_io_fail_tmo == -1) +		return sysfs_emit(buf, "off\n"); +	return sysfs_emit(buf, "%d\n", ctrl->opts->fast_io_fail_tmo); +} + +static ssize_t nvme_ctrl_fast_io_fail_tmo_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); +	struct nvmf_ctrl_options *opts = ctrl->opts; +	int fast_io_fail_tmo, err; + +	err = kstrtoint(buf, 10, &fast_io_fail_tmo); +	if (err) +		return -EINVAL; + +	if (fast_io_fail_tmo < 0) +		opts->fast_io_fail_tmo = -1; +	else +		opts->fast_io_fail_tmo = fast_io_fail_tmo; +	return count; +} +static DEVICE_ATTR(fast_io_fail_tmo, S_IRUGO | S_IWUSR, +	nvme_ctrl_fast_io_fail_tmo_show, nvme_ctrl_fast_io_fail_tmo_store); + +static ssize_t cntrltype_show(struct device *dev, +			      struct device_attribute *attr, char *buf) +{ +	static const char * const type[] = { +		[NVME_CTRL_IO] = "io\n", +		[NVME_CTRL_DISC] = "discovery\n", +		[NVME_CTRL_ADMIN] = "admin\n", +	}; +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + +	if (ctrl->cntrltype > NVME_CTRL_ADMIN || !type[ctrl->cntrltype]) +		return sysfs_emit(buf, "reserved\n"); + +	return sysfs_emit(buf, type[ctrl->cntrltype]); +} +static DEVICE_ATTR_RO(cntrltype); + +static ssize_t dctype_show(struct device *dev, +			   struct device_attribute *attr, char *buf) +{ +	static const char * const type[] = { +		[NVME_DCTYPE_NOT_REPORTED] = "none\n", +		[NVME_DCTYPE_DDC] = "ddc\n", +		[NVME_DCTYPE_CDC] = "cdc\n", +	}; +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + +	if (ctrl->dctype > NVME_DCTYPE_CDC || !type[ctrl->dctype]) +		return sysfs_emit(buf, "reserved\n"); + +	return sysfs_emit(buf, type[ctrl->dctype]); +} +static DEVICE_ATTR_RO(dctype); + +#ifdef CONFIG_NVME_AUTH +static ssize_t nvme_ctrl_dhchap_secret_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); +	struct nvmf_ctrl_options *opts = ctrl->opts; + +	if (!opts->dhchap_secret) +		return sysfs_emit(buf, "none\n"); +	return sysfs_emit(buf, "%s\n", opts->dhchap_secret); +} + +static ssize_t nvme_ctrl_dhchap_secret_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); +	struct nvmf_ctrl_options *opts = ctrl->opts; +	char *dhchap_secret; + +	if (!ctrl->opts->dhchap_secret) +		return -EINVAL; +	if (count < 7) +		return -EINVAL; +	if (memcmp(buf, "DHHC-1:", 7)) +		return -EINVAL; + +	dhchap_secret = kzalloc(count + 1, GFP_KERNEL); +	if (!dhchap_secret) +		return -ENOMEM; +	memcpy(dhchap_secret, buf, count); +	nvme_auth_stop(ctrl); +	if (strcmp(dhchap_secret, opts->dhchap_secret)) { +		struct nvme_dhchap_key *key, *host_key; +		int ret; + +		ret = nvme_auth_generate_key(dhchap_secret, &key); +		if (ret) { +			kfree(dhchap_secret); +			return ret; +		} +		kfree(opts->dhchap_secret); +		opts->dhchap_secret = dhchap_secret; +		host_key = ctrl->host_key; +		mutex_lock(&ctrl->dhchap_auth_mutex); +		ctrl->host_key = key; +		mutex_unlock(&ctrl->dhchap_auth_mutex); +		nvme_auth_free_key(host_key); +	} else +		kfree(dhchap_secret); +	/* Start re-authentication */ +	dev_info(ctrl->device, "re-authenticating controller\n"); +	queue_work(nvme_wq, &ctrl->dhchap_auth_work); + +	return count; +} + +static DEVICE_ATTR(dhchap_secret, S_IRUGO | S_IWUSR, +	nvme_ctrl_dhchap_secret_show, nvme_ctrl_dhchap_secret_store); + +static ssize_t nvme_ctrl_dhchap_ctrl_secret_show(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); +	struct nvmf_ctrl_options *opts = ctrl->opts; + +	if (!opts->dhchap_ctrl_secret) +		return sysfs_emit(buf, "none\n"); +	return sysfs_emit(buf, "%s\n", opts->dhchap_ctrl_secret); +} + +static ssize_t nvme_ctrl_dhchap_ctrl_secret_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); +	struct nvmf_ctrl_options *opts = ctrl->opts; +	char *dhchap_secret; + +	if (!ctrl->opts->dhchap_ctrl_secret) +		return -EINVAL; +	if (count < 7) +		return -EINVAL; +	if (memcmp(buf, "DHHC-1:", 7)) +		return -EINVAL; + +	dhchap_secret = kzalloc(count + 1, GFP_KERNEL); +	if (!dhchap_secret) +		return -ENOMEM; +	memcpy(dhchap_secret, buf, count); +	nvme_auth_stop(ctrl); +	if (strcmp(dhchap_secret, opts->dhchap_ctrl_secret)) { +		struct nvme_dhchap_key *key, *ctrl_key; +		int ret; + +		ret = nvme_auth_generate_key(dhchap_secret, &key); +		if (ret) { +			kfree(dhchap_secret); +			return ret; +		} +		kfree(opts->dhchap_ctrl_secret); +		opts->dhchap_ctrl_secret = dhchap_secret; +		ctrl_key = ctrl->ctrl_key; +		mutex_lock(&ctrl->dhchap_auth_mutex); +		ctrl->ctrl_key = key; +		mutex_unlock(&ctrl->dhchap_auth_mutex); +		nvme_auth_free_key(ctrl_key); +	} else +		kfree(dhchap_secret); +	/* Start re-authentication */ +	dev_info(ctrl->device, "re-authenticating controller\n"); +	queue_work(nvme_wq, &ctrl->dhchap_auth_work); + +	return count; +} + +static DEVICE_ATTR(dhchap_ctrl_secret, S_IRUGO | S_IWUSR, +	nvme_ctrl_dhchap_ctrl_secret_show, nvme_ctrl_dhchap_ctrl_secret_store); +#endif + +static struct attribute *nvme_dev_attrs[] = { +	&dev_attr_reset_controller.attr, +	&dev_attr_rescan_controller.attr, +	&dev_attr_model.attr, +	&dev_attr_serial.attr, +	&dev_attr_firmware_rev.attr, +	&dev_attr_cntlid.attr, +	&dev_attr_delete_controller.attr, +	&dev_attr_transport.attr, +	&dev_attr_subsysnqn.attr, +	&dev_attr_address.attr, +	&dev_attr_state.attr, +	&dev_attr_numa_node.attr, +	&dev_attr_queue_count.attr, +	&dev_attr_sqsize.attr, +	&dev_attr_hostnqn.attr, +	&dev_attr_hostid.attr, +	&dev_attr_ctrl_loss_tmo.attr, +	&dev_attr_reconnect_delay.attr, +	&dev_attr_fast_io_fail_tmo.attr, +	&dev_attr_kato.attr, +	&dev_attr_cntrltype.attr, +	&dev_attr_dctype.attr, +#ifdef CONFIG_NVME_AUTH +	&dev_attr_dhchap_secret.attr, +	&dev_attr_dhchap_ctrl_secret.attr, +#endif +	NULL +}; + +static umode_t nvme_dev_attrs_are_visible(struct kobject *kobj, +		struct attribute *a, int n) +{ +	struct device *dev = container_of(kobj, struct device, kobj); +	struct nvme_ctrl *ctrl = dev_get_drvdata(dev); + +	if (a == &dev_attr_delete_controller.attr && !ctrl->ops->delete_ctrl) +		return 0; +	if (a == &dev_attr_address.attr && !ctrl->ops->get_address) +		return 0; +	if (a == &dev_attr_hostnqn.attr && !ctrl->opts) +		return 0; +	if (a == &dev_attr_hostid.attr && !ctrl->opts) +		return 0; +	if (a == &dev_attr_ctrl_loss_tmo.attr && !ctrl->opts) +		return 0; +	if (a == &dev_attr_reconnect_delay.attr && !ctrl->opts) +		return 0; +	if (a == &dev_attr_fast_io_fail_tmo.attr && !ctrl->opts) +		return 0; +#ifdef CONFIG_NVME_AUTH +	if (a == &dev_attr_dhchap_secret.attr && !ctrl->opts) +		return 0; +	if (a == &dev_attr_dhchap_ctrl_secret.attr && !ctrl->opts) +		return 0; +#endif + +	return a->mode; +} + +const struct attribute_group nvme_dev_attrs_group = { +	.attrs		= nvme_dev_attrs, +	.is_visible	= nvme_dev_attrs_are_visible, +}; +EXPORT_SYMBOL_GPL(nvme_dev_attrs_group); + +const struct attribute_group *nvme_dev_attr_groups[] = { +	&nvme_dev_attrs_group, +	NULL, +}; + +#define SUBSYS_ATTR_RO(_name, _mode, _show)			\ +	struct device_attribute subsys_attr_##_name = \ +		__ATTR(_name, _mode, _show, NULL) + +static ssize_t nvme_subsys_show_nqn(struct device *dev, +				    struct device_attribute *attr, +				    char *buf) +{ +	struct nvme_subsystem *subsys = +		container_of(dev, struct nvme_subsystem, dev); + +	return sysfs_emit(buf, "%s\n", subsys->subnqn); +} +static SUBSYS_ATTR_RO(subsysnqn, S_IRUGO, nvme_subsys_show_nqn); + +static ssize_t nvme_subsys_show_type(struct device *dev, +				    struct device_attribute *attr, +				    char *buf) +{ +	struct nvme_subsystem *subsys = +		container_of(dev, struct nvme_subsystem, dev); + +	switch (subsys->subtype) { +	case NVME_NQN_DISC: +		return sysfs_emit(buf, "discovery\n"); +	case NVME_NQN_NVME: +		return sysfs_emit(buf, "nvm\n"); +	default: +		return sysfs_emit(buf, "reserved\n"); +	} +} +static SUBSYS_ATTR_RO(subsystype, S_IRUGO, nvme_subsys_show_type); + +#define nvme_subsys_show_str_function(field)				\ +static ssize_t subsys_##field##_show(struct device *dev,		\ +			    struct device_attribute *attr, char *buf)	\ +{									\ +	struct nvme_subsystem *subsys =					\ +		container_of(dev, struct nvme_subsystem, dev);		\ +	return sysfs_emit(buf, "%.*s\n",				\ +			   (int)sizeof(subsys->field), subsys->field);	\ +}									\ +static SUBSYS_ATTR_RO(field, S_IRUGO, subsys_##field##_show); + +nvme_subsys_show_str_function(model); +nvme_subsys_show_str_function(serial); +nvme_subsys_show_str_function(firmware_rev); + +static struct attribute *nvme_subsys_attrs[] = { +	&subsys_attr_model.attr, +	&subsys_attr_serial.attr, +	&subsys_attr_firmware_rev.attr, +	&subsys_attr_subsysnqn.attr, +	&subsys_attr_subsystype.attr, +#ifdef CONFIG_NVME_MULTIPATH +	&subsys_attr_iopolicy.attr, +#endif +	NULL, +}; + +static const struct attribute_group nvme_subsys_attrs_group = { +	.attrs = nvme_subsys_attrs, +}; + +const struct attribute_group *nvme_subsys_attrs_groups[] = { +	&nvme_subsys_attrs_group, +	NULL, +};  |