[PATCH 3/3] nvme: Expose link rate switching tunables via sysfs

Liao Xuan liaoxuan at open-hieco.net
Wed Sep 23 23:15:13 PDT 2026


Add a speed attribute group under /sys/class/nvme/nvmeX/ that
exposes the adaptive link rate switching parameters for runtime tuning:

  enable           - toggle the feature on/off (0/1)
  monitor_interval - I/O monitoring window in milliseconds (>= 100)
  min_speed        - minimum PCIe generation to downgrade to (1-5)
  up_threshold     - windows above the threshold needed to upgrade
  down_threshold   - windows below the threshold needed to downgrade

The attributes are read/write and are removed when the controller is
torn down.

Signed-off-by: Liao Xuan <liaoxuan at open-hieco.net>
---
 drivers/nvme/host/nvme.h         |   3 +
 drivers/nvme/host/speed_switch.c |  15 +++-
 drivers/nvme/host/sysfs.c        | 143 +++++++++++++++++++++++++++++++
 3 files changed, 160 insertions(+), 1 deletion(-)

diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index 5d5034601..fb958ff13 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -1076,6 +1076,9 @@ extern const struct attribute_group nvme_dev_attrs_group;
 extern const struct attribute_group nvme_dev_diag_attrs_group;
 extern const struct attribute_group *nvme_subsys_attrs_groups[];
 extern const struct attribute_group *nvme_dev_attr_groups[];
+#ifdef CONFIG_NVME_SPEED_SWITCH
+extern const struct attribute_group nvme_speed_attr_group;
+#endif
 extern const struct block_device_operations nvme_bdev_ops;
 
 void nvme_delete_ctrl_sync(struct nvme_ctrl *ctrl);
diff --git a/drivers/nvme/host/speed_switch.c b/drivers/nvme/host/speed_switch.c
index 22f180c81..fce426845 100644
--- a/drivers/nvme/host/speed_switch.c
+++ b/drivers/nvme/host/speed_switch.c
@@ -325,7 +325,7 @@ void nvme_speed_switch_init(struct nvme_ctrl *ctrl)
 {
 	struct nvme_speed_switch *sw = &ctrl->speed_switch;
 	struct pci_dev *pdev;
-	int max_speed;
+	int max_speed, ret;
 
 	if (boot_cpu_data.x86_vendor != X86_VENDOR_HYGON) {
 		dev_dbg(ctrl->device,
@@ -376,6 +376,18 @@ void nvme_speed_switch_init(struct nvme_ctrl *ctrl)
 	timer_setup(&sw->timer, nvme_speed_switch_timer_fn, 0);
 	atomic_set(&sw->timer_active, NVME_SPEED_TIMER_INACTIVE);
 	WRITE_ONCE(sw->enabled, true);
+
+	ret = sysfs_create_group(&ctrl->device->kobj, &nvme_speed_attr_group);
+	if (ret) {
+		dev_err(ctrl->device,
+			"failed to create link rate switching sysfs group (%d)\n",
+			ret);
+		timer_delete_sync(&sw->timer);
+		free_percpu(sw->stats);
+		sw->stats = NULL;
+		WRITE_ONCE(sw->enabled, false);
+		return;
+	}
 	sw->initialized = true;
 
 	dev_info(ctrl->device,
@@ -405,6 +417,7 @@ void nvme_speed_switch_exit(struct nvme_ctrl *ctrl)
 	WRITE_ONCE(sw->enabled, false);
 	timer_delete_sync(&sw->timer);
 	cancel_work_sync(&sw->work);
+	sysfs_remove_group(&ctrl->device->kobj, &nvme_speed_attr_group);
 
 	free_percpu(sw->stats);
 	sw->stats = NULL;
diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c
index 75b2d69b5..cd899b87b 100644
--- a/drivers/nvme/host/sysfs.c
+++ b/drivers/nvme/host/sysfs.c
@@ -1308,3 +1308,146 @@ const struct attribute_group *nvme_subsys_attrs_groups[] = {
 	&nvme_subsys_attrs_group,
 	NULL,
 };
+
+#ifdef CONFIG_NVME_SPEED_SWITCH
+enum nvme_speed_attr_id {
+	NVME_SPEED_ATTR_ENABLE,
+	NVME_SPEED_ATTR_MONITOR_INTERVAL,
+	NVME_SPEED_ATTR_MIN_SPEED,
+	NVME_SPEED_ATTR_UP_THRESHOLD,
+	NVME_SPEED_ATTR_DOWN_THRESHOLD,
+};
+
+struct nvme_speed_attr {
+	struct device_attribute attr;
+	enum nvme_speed_attr_id id;
+};
+
+static ssize_t nvme_speed_attr_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct nvme_ctrl *ctrl = dev_get_drvdata(dev);
+	struct nvme_speed_switch *sw = &ctrl->speed_switch;
+	struct nvme_speed_attr *sa =
+		container_of(attr, struct nvme_speed_attr, attr);
+	u32 val;
+
+	switch (sa->id) {
+	case NVME_SPEED_ATTR_ENABLE:
+		val = READ_ONCE(sw->enabled);
+		break;
+	case NVME_SPEED_ATTR_MONITOR_INTERVAL:
+		val = READ_ONCE(sw->monitor_interval);
+		break;
+	case NVME_SPEED_ATTR_MIN_SPEED:
+		val = READ_ONCE(sw->min_speed);
+		break;
+	case NVME_SPEED_ATTR_UP_THRESHOLD:
+		val = READ_ONCE(sw->up_threshold);
+		break;
+	case NVME_SPEED_ATTR_DOWN_THRESHOLD:
+		val = READ_ONCE(sw->down_threshold);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return sysfs_emit(buf, "%u\n", val);
+}
+
+static ssize_t nvme_speed_attr_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct nvme_ctrl *ctrl = dev_get_drvdata(dev);
+	struct nvme_speed_switch *sw = &ctrl->speed_switch;
+	struct nvme_speed_attr *sa =
+		container_of(attr, struct nvme_speed_attr, attr);
+	u32 val;
+	int ret;
+
+	ret = kstrtou32(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	switch (sa->id) {
+	case NVME_SPEED_ATTR_ENABLE:
+		if (val > 1)
+			return -EINVAL;
+		if (!val && READ_ONCE(sw->enabled)) {
+			WRITE_ONCE(sw->enabled, false);
+			timer_delete_sync(&sw->timer);
+			cancel_work_sync(&sw->work);
+			atomic_set(&sw->timer_active, NVME_SPEED_TIMER_INACTIVE);
+		} else
+			WRITE_ONCE(sw->enabled, val);
+		break;
+	case NVME_SPEED_ATTR_MONITOR_INTERVAL:
+		if (val < 100)
+			return -EINVAL;
+		WRITE_ONCE(sw->monitor_interval, val);
+		break;
+	case NVME_SPEED_ATTR_MIN_SPEED:
+		if (val < 1 || val > 5)
+			return -EINVAL;
+		if (val > sw->max_speed)
+			val = sw->max_speed;
+		WRITE_ONCE(sw->min_speed, val);
+		break;
+	case NVME_SPEED_ATTR_UP_THRESHOLD:
+		WRITE_ONCE(sw->up_threshold, val);
+		break;
+	case NVME_SPEED_ATTR_DOWN_THRESHOLD:
+		WRITE_ONCE(sw->down_threshold, val);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static struct nvme_speed_attr nvme_speed_attr_enable = {
+	.attr = __ATTR(enable, 0644,
+		       nvme_speed_attr_show, nvme_speed_attr_store),
+	.id = NVME_SPEED_ATTR_ENABLE,
+};
+
+static struct nvme_speed_attr nvme_speed_attr_monitor_interval = {
+	.attr = __ATTR(monitor_interval, 0644,
+		       nvme_speed_attr_show, nvme_speed_attr_store),
+	.id = NVME_SPEED_ATTR_MONITOR_INTERVAL,
+};
+
+static struct nvme_speed_attr nvme_speed_attr_min_speed = {
+	.attr = __ATTR(min_speed, 0644,
+		       nvme_speed_attr_show, nvme_speed_attr_store),
+	.id = NVME_SPEED_ATTR_MIN_SPEED,
+};
+
+static struct nvme_speed_attr nvme_speed_attr_up_threshold = {
+	.attr = __ATTR(up_threshold, 0644,
+		       nvme_speed_attr_show, nvme_speed_attr_store),
+	.id = NVME_SPEED_ATTR_UP_THRESHOLD,
+};
+
+static struct nvme_speed_attr nvme_speed_attr_down_threshold = {
+	.attr = __ATTR(down_threshold, 0644,
+		       nvme_speed_attr_show, nvme_speed_attr_store),
+	.id = NVME_SPEED_ATTR_DOWN_THRESHOLD,
+};
+
+static struct attribute *nvme_speed_attrs[] = {
+	&nvme_speed_attr_enable.attr.attr,
+	&nvme_speed_attr_monitor_interval.attr.attr,
+	&nvme_speed_attr_min_speed.attr.attr,
+	&nvme_speed_attr_up_threshold.attr.attr,
+	&nvme_speed_attr_down_threshold.attr.attr,
+	NULL,
+};
+
+const struct attribute_group nvme_speed_attr_group = {
+	.name = "speed",
+	.attrs = nvme_speed_attrs,
+};
+#endif /* CONFIG_NVME_SPEED_SWITCH */
-- 
2.43.0




More information about the Linux-nvme mailing list