1/*-
2 * Copyright (c) 2016 Netflix, Inc.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26#include <sys/cdefs.h>
27__FBSDID("$FreeBSD$");
28
29#include <sys/param.h>
30#include <sys/ioccom.h>
31
32#include <ctype.h>
33#include <err.h>
34#include <fcntl.h>
35#include <stdbool.h>
36#include <stddef.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <sysexits.h>
41#include <unistd.h>
42
43#include "nvmecontrol.h"
44
45_Static_assert(sizeof(struct nvme_power_state) == 256 / NBBY,
46	       "nvme_power_state size wrong");
47
48#define POWER_NONE 0xffffffffu
49
50static struct options {
51	bool		list;
52	uint32_t	power;
53	uint32_t	workload;
54	const char	*dev;
55} opt = {
56	.list = false,
57	.power = POWER_NONE,
58	.workload = 0,
59	.dev = NULL,
60};
61
62static void
63power_list_one(int i, struct nvme_power_state *nps)
64{
65	int mpower, apower, ipower;
66	uint8_t mps, nops, aps, apw;
67
68	mps = (nps->mps_nops >> NVME_PWR_ST_MPS_SHIFT) &
69		NVME_PWR_ST_MPS_MASK;
70	nops = (nps->mps_nops >> NVME_PWR_ST_NOPS_SHIFT) &
71		NVME_PWR_ST_NOPS_MASK;
72	apw = (nps->apw_aps >> NVME_PWR_ST_APW_SHIFT) &
73		NVME_PWR_ST_APW_MASK;
74	aps = (nps->apw_aps >> NVME_PWR_ST_APS_SHIFT) &
75		NVME_PWR_ST_APS_MASK;
76
77	mpower = nps->mp;
78	if (mps == 0)
79		mpower *= 100;
80	ipower = nps->idlp;
81	if (nps->ips == 1)
82		ipower *= 100;
83	apower = nps->actp;
84	if (aps == 1)
85		apower *= 100;
86	printf("%2d: %2d.%04dW%c %3d.%03dms %3d.%03dms %2d %2d %2d %2d %2d.%04dW %2d.%04dW %d\n",
87	       i, mpower / 10000, mpower % 10000,
88	       nops ? '*' : ' ', nps->enlat / 1000, nps->enlat % 1000,
89	       nps->exlat / 1000, nps->exlat % 1000, nps->rrt, nps->rrl,
90	       nps->rwt, nps->rwl, ipower / 10000, ipower % 10000,
91	       apower / 10000, apower % 10000, apw);
92}
93
94static void
95power_list(struct nvme_controller_data *cdata)
96{
97	int i;
98
99	printf("\nPower States Supported: %d\n\n", cdata->npss + 1);
100	printf(" #   Max pwr  Enter Lat  Exit Lat RT RL WT WL Idle Pwr  Act Pwr Workloadd\n");
101	printf("--  --------  --------- --------- -- -- -- -- -------- -------- --\n");
102	for (i = 0; i <= cdata->npss; i++)
103		power_list_one(i, &cdata->power_state[i]);
104}
105
106static void
107power_set(int fd, int power_val, int workload, int perm)
108{
109	struct nvme_pt_command	pt;
110	uint32_t p;
111
112	p = perm ? (1u << 31) : 0;
113	memset(&pt, 0, sizeof(pt));
114	pt.cmd.opc = NVME_OPC_SET_FEATURES;
115	pt.cmd.cdw10 = htole32(NVME_FEAT_POWER_MANAGEMENT | p);
116	pt.cmd.cdw11 = htole32(power_val | (workload << 5));
117
118	if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
119		err(EX_IOERR, "set feature power mgmt request failed");
120
121	if (nvme_completion_is_error(&pt.cpl))
122		errx(EX_IOERR, "set feature power mgmt request returned error");
123}
124
125static void
126power_show(int fd)
127{
128	struct nvme_pt_command	pt;
129
130	memset(&pt, 0, sizeof(pt));
131	pt.cmd.opc = NVME_OPC_GET_FEATURES;
132	pt.cmd.cdw10 = htole32(NVME_FEAT_POWER_MANAGEMENT);
133
134	if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
135		err(EX_IOERR, "set feature power mgmt request failed");
136
137	if (nvme_completion_is_error(&pt.cpl))
138		errx(EX_IOERR, "set feature power mgmt request returned error");
139
140	printf("Current Power Mode is %d\n", pt.cpl.cdw0);
141}
142
143static void
144power(const struct cmd *f, int argc, char *argv[])
145{
146	struct nvme_controller_data	cdata;
147	int				fd;
148	char				*path;
149	uint32_t			nsid;
150
151	if (arg_parse(argc, argv, f))
152		return;
153
154	if (opt.list && opt.power != POWER_NONE) {
155		fprintf(stderr, "Can't set power and list power states\n");
156		arg_help(argc, argv, f);
157	}
158
159	open_dev(opt.dev, &fd, 1, 1);
160	get_nsid(fd, &path, &nsid);
161	if (nsid != 0) {
162		close(fd);
163		open_dev(path, &fd, 1, 1);
164	}
165	free(path);
166
167	if (opt.list) {
168		if (read_controller_data(fd, &cdata))
169			errx(EX_IOERR, "Identify request failed");
170		power_list(&cdata);
171		goto out;
172	}
173
174	if (opt.power != POWER_NONE) {
175		power_set(fd, opt.power, opt.workload, 0);
176		goto out;
177	}
178	power_show(fd);
179
180out:
181	close(fd);
182	exit(0);
183}
184
185static const struct opts power_opts[] = {
186#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }
187	OPT("list", 'l', arg_none, opt, list,
188	    "List the valid power states"),
189	OPT("power", 'p', arg_uint32, opt, power,
190	    "Set the power state"),
191	OPT("workload", 'w', arg_uint32, opt, workload,
192	    "Set the workload"),
193	{ NULL, 0, arg_none, NULL, NULL }
194};
195#undef OPT
196
197static const struct args power_args[] = {
198	{ arg_string, &opt.dev, "controller-id|namespace-id" },
199	{ arg_none, NULL, NULL },
200};
201
202static struct cmd power_cmd = {
203	.name = "power",
204	.fn = power,
205	.descr = "Manage power states for the drive",
206	.ctx_size = sizeof(opt),
207	.opts = power_opts,
208	.args = power_args,
209};
210
211CMD_COMMAND(power_cmd);
212