git: 35793364d722 - main - nvmecontrol: Add APST feature support
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 11 Sep 2026 05:32:42 UTC
The branch main has been updated by imp:
URL: https://cgit.FreeBSD.org/src/commit/?id=35793364d722fcba73a7dd0c76ddadaa0038fa10
commit 35793364d722fcba73a7dd0c76ddadaa0038fa10
Author: Alexey Sukhoguzov <sap@eseipi.net>
AuthorDate: 2026-09-11 05:13:40 +0000
Commit: Warner Losh <imp@FreeBSD.org>
CommitDate: 2026-09-11 05:13:40 +0000
nvmecontrol: Add APST feature support
Add -a, -d and -m options to the power subcommand to manage APST,
along with additional status output when invoked without args.
Signed-off-by: Alexey Sukhoguzov <sap@eseipi.net>
Reviewed-by: imp
---
sbin/nvmecontrol/nvmecontrol.8 | 21 +++++-
sbin/nvmecontrol/power.c | 167 ++++++++++++++++++++++++++++++++++++++---
2 files changed, 178 insertions(+), 10 deletions(-)
diff --git a/sbin/nvmecontrol/nvmecontrol.8 b/sbin/nvmecontrol/nvmecontrol.8
index 6499da9f72e7..913ce1a42cd5 100644
--- a/sbin/nvmecontrol/nvmecontrol.8
+++ b/sbin/nvmecontrol/nvmecontrol.8
@@ -33,7 +33,7 @@
.\"
.\" Author: Jim Harris <jimharris@FreeBSD.org>
.\"
-.Dd July 31, 2026
+.Dd September 10, 2026
.Dt NVMECONTROL 8
.Os
.Sh NAME
@@ -172,7 +172,10 @@
.Aq Ar device-id
.Nm
.Ic power
+.Op Fl a Ar apst_state
+.Op Fl d Ar apst_data
.Op Fl l
+.Op Fl m Ar apst_limit
.Op Fl p power_state
.Op Fl w workload_hint
.Aq Ar device-id | Ar namespace-id
@@ -628,11 +631,27 @@ This will report status on a sanitize that is already running on the drive.
.Ss power
Manage the power modes of the NVMe controller.
.Bl -tag -width 6n
+.It Fl a Ar state
+Enable (1) or disable (0) Autonomous Power State Transition (APST).
+When enabled, the controller will autonomously switch power states
+according to configuration data.
+.It Fl d Ar target:idle[,target:idle ...]
+Set the APST data.
+Each target:idle pair corresponds to a specific available power
+state starting from the lowest, and defines the target state to
+transition to, as well as the idle time in milliseconds to wait
+before that transition.
+Setting both parameters to zero disables switching from that state.
.It Fl l
List all supported power modes.
Modes marked with an asterisk
.Pq Dq *
are non-operational power modes.
+.It Fl m Ar limit
+Configure APST to use all available non-operational power states
+with total latency below the specified
+.Ar limit
+(in milliseconds).
.It Fl p Ar mode
Set the power mode to
.Ar mode .
diff --git a/sbin/nvmecontrol/power.c b/sbin/nvmecontrol/power.c
index dd6d8bcdca0a..fca152c3924a 100644
--- a/sbin/nvmecontrol/power.c
+++ b/sbin/nvmecontrol/power.c
@@ -28,6 +28,7 @@
#include <sys/param.h>
#include <sys/ioccom.h>
+#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
@@ -44,17 +45,23 @@
_Static_assert(sizeof(struct nvme_power_state) == 256 / NBBY,
"nvme_power_state size wrong");
-#define POWER_NONE 0xffffffffu
+#define NONE 0xffffffffu
static struct options {
bool list;
+ uint32_t apst;
+ uint32_t apst_limit;
uint32_t power;
uint32_t workload;
+ const char *apst_data;
const char *dev;
} opt = {
.list = false,
- .power = POWER_NONE,
+ .apst = NONE,
+ .apst_limit = NONE,
+ .power = NONE,
.workload = 0,
+ .apst_data = NULL,
.dev = NULL,
};
@@ -117,10 +124,132 @@ power_set(int fd, int power_val, int workload, int perm)
errx(EX_IOERR, "set feature power mgmt request returned error");
}
+enum feat_opc { GET, SET };
+
+static int
+power_apst_cmd(int fd, enum feat_opc opc, bool enable, uint64_t *data,
+ int size)
+{
+ struct nvme_pt_command pt;
+
+ memset(&pt, 0, sizeof(pt));
+ pt.cmd.opc = (opc == SET) ?
+ NVME_OPC_SET_FEATURES : NVME_OPC_GET_FEATURES;
+ pt.cmd.cdw10 = htole32(NVME_FEAT_AUTONOMOUS_POWER_STATE_TRANSITION);
+ pt.cmd.cdw11 = htole32(enable);
+ pt.buf = data;
+ pt.len = size;
+ pt.is_read = (opc == GET) ? 1 : 0;
+
+ if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) == -1)
+ err(EX_IOERR, "APST %s command failed",
+ (opc == SET) ? "set" : "get");
+
+ if (nvme_completion_is_error(&pt.cpl))
+ errx(EX_IOERR, "APST %s command returned error",
+ (opc == SET) ? "set" : "get");
+
+ return (pt.cpl.cdw0);
+}
+
static void
-power_show(int fd)
+power_apst_data_generate(struct nvme_controller_data *cdata,
+ uint64_t *data, int num, int limit)
+{
+ int i, itpt, latency;
+
+ assert(cdata->npss < num);
+
+ for (i = cdata->npss; i > 0; --i) {
+ if (!NVMEV(NVME_PWR_ST_NOPS,
+ cdata->power_state[i].mps_nops)) {
+ data[i - 1] = data[i];
+ continue;
+ }
+
+ latency = (cdata->power_state[i].enlat +
+ cdata->power_state[i].exlat) / 1000;
+ if (latency > limit)
+ continue;
+
+ /* Wait 50x the latency before each transition. */
+ itpt = MIN(latency * 50, (1 << 24) - 1);
+ data[i - 1] = htole64(itpt << 8 | i << 3);
+ }
+}
+
+static void
+power_apst_data_parse(struct nvme_controller_data *cdata,
+ uint64_t *data, int num, const char *dstr)
+{
+ int i, itps, itpt;
+ char *str, *token;
+
+ str = strdup(dstr);
+
+ for (i = 0; (token = strsep(&str, " ,")) != NULL && i < num; ++i) {
+ if (sscanf(token, "%i:%i", &itps, &itpt) != 2)
+ errx(EX_USAGE, "cannot parse provided configuration");
+
+ if (itps < 0 || itps >= cdata->npss)
+ errx(EX_USAGE, "invalid ITPS=%d (must be 0..%d)",
+ itps, cdata->npss);
+ if (itpt < 0 || itpt >= 1 << 24)
+ errx(EX_USAGE, "invalid ITPT=%d (must be 0..%d)",
+ itpt, 1 << 24);
+
+ data[i] = htole64(itpt << 8 | itps << 3);
+ }
+}
+
+static void
+power_apst_show(uint64_t *data, int num, bool enabled)
+{
+ int entry, i;
+
+ while (num > 0 && data[num - 1] == 0)
+ --num;
+
+ printf("APST %s\n", enabled ? "enabled" : "disabled");
+ printf("\n # ITPS ITPT Hex\n");
+ printf("-- ---- ------ --------\n");
+ for (i = 0; i < num || i == 0; ++i) {
+ entry = letoh(data[i]);
+ printf("%2d: %4d %4dms %#8x\n",
+ i, (entry & 0xF8) >> 3, entry >> 8, entry);
+ }
+}
+
+static void
+power_apst(int fd, struct nvme_controller_data *cdata,
+ uint32_t enable, const char *dstr, uint32_t limit)
+{
+ uint64_t data[32];
+
+ if (cdata->apsta == 0)
+ errx(EX_UNAVAILABLE, "Not supported by the controller");
+
+ if (enable == NONE)
+ enable = power_apst_cmd(fd, GET, 0, NULL, 0);
+
+ memset(&data, 0, sizeof(data));
+
+ if (dstr != NULL)
+ power_apst_data_parse(cdata, data, nitems(data), dstr);
+ else if (limit != NONE)
+ power_apst_data_generate(cdata, data, nitems(data), limit);
+ else
+ power_apst_cmd(fd, GET, 0, data, sizeof(data));
+
+ power_apst_cmd(fd, SET, enable, data, sizeof(data));
+}
+
+static void
+power_show(int fd, struct nvme_controller_data *cdata)
{
struct nvme_pt_command pt;
+ uint64_t data[32];
+ int status;
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_GET_FEATURES;
@@ -134,6 +263,11 @@ power_show(int fd)
printf("Current Power State is %d\n", pt.cpl.cdw0 & 0x1F);
printf("Current Workload Hint is %d\n", pt.cpl.cdw0 >> 5);
+
+ if (cdata->apsta != 0) {
+ status = power_apst_cmd(fd, GET, 0, data, sizeof(data));
+ power_apst_show(data, nitems(data), status);
+ }
}
static void
@@ -147,7 +281,7 @@ power(const struct cmd *f, int argc, char *argv[])
if (arg_parse(argc, argv, f))
return;
- if (opt.list && opt.power != POWER_NONE) {
+ if (opt.list && opt.power != NONE) {
fprintf(stderr, "Can't set power and list power states\n");
arg_help(argc, argv, f);
}
@@ -160,18 +294,27 @@ power(const struct cmd *f, int argc, char *argv[])
}
free(path);
+ if (opt.power != NONE) {
+ power_set(fd, opt.power, opt.workload, 0);
+ goto out;
+ }
+
+ if (read_controller_data(fd, &cdata))
+ errx(EX_IOERR, "Identify request failed");
+
if (opt.list) {
- if (read_controller_data(fd, &cdata))
- errx(EX_IOERR, "Identify request failed");
power_list(&cdata);
goto out;
}
- if (opt.power != POWER_NONE) {
- power_set(fd, opt.power, opt.workload, 0);
+ if (opt.apst != NONE || opt.apst_limit != NONE ||
+ opt.apst_data != NULL) {
+ power_apst(fd, &cdata, opt.apst,
+ opt.apst_data, opt.apst_limit);
goto out;
}
- power_show(fd);
+
+ power_show(fd, &cdata);
out:
close(fd);
@@ -180,8 +323,14 @@ out:
static const struct opts power_opts[] = {
#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }
+ OPT("apst", 'a', arg_uint32, opt, apst,
+ "Enable or disable APST"),
+ OPT("data", 'd', arg_string, opt, apst_data,
+ "Set the APST configuration"),
OPT("list", 'l', arg_none, opt, list,
"List the valid power states"),
+ OPT("limit", 'm', arg_uint32, opt, apst_limit,
+ "Set the APST latency limit"),
OPT("power", 'p', arg_uint32, opt, power,
"Set the power state"),
OPT("workload", 'w', arg_uint32, opt, workload,