mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 19:34:43 +01:00
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 <[email protected]> Reviewed-by: imp
This commit is contained in:
1 parent
c4c790949f
commit
35793364d7
2 files changed
+178
-10
No files matched your search
@@ -33,7 +33,7 @@
|
||||
.\"
|
||||
.\" Author: Jim Harris <[email protected]>
|
||||
.\"
|
||||
.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 .
|
||||
|
||||
+158
-9
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user