1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2001 Mitsuru IWASAKI
5 * All rights reserved.
6 * Copyright (c) 2025 The FreeBSD Foundation
7 *
8 * Portions of this software were developed by Aymeric Wibo
9 * <obiwac@freebsd.org> under sponsorship from the FreeBSD Foundation.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33 #include <sys/param.h>
34 #include <sys/conf.h>
35 #include <sys/eventhandler.h>
36 #include <sys/fcntl.h>
37 #include <sys/kernel.h>
38 #include <sys/power.h>
39 #include <sys/proc.h>
40 #include <sys/sbuf.h>
41 #include <sys/sysctl.h>
42 #include <sys/systm.h>
43 #include <sys/taskqueue.h>
44
45 enum power_stype power_standby_stype = POWER_STYPE_UNKNOWN;
46 enum power_stype power_suspend_stype = POWER_STYPE_UNKNOWN;
47 enum power_stype power_hibernate_stype = POWER_STYPE_UNKNOWN;
48
49 static u_int power_pm_type = POWER_PM_TYPE_NONE;
50 static power_pm_fn_t power_pm_fn = NULL;
51 static void *power_pm_arg = NULL;
52 static bool power_pm_supported[POWER_STYPE_COUNT] = {0};
53 static struct task power_pm_task;
54
55 static d_ioctl_t power_ioctl;
56
57 static struct cdevsw power_cdevsw = {
58 .d_version = D_VERSION,
59 .d_ioctl = power_ioctl,
60 .d_name = "power",
61 };
62
63 static void
power_init(void * unused)64 power_init(void *unused)
65 {
66 struct make_dev_args args;
67 struct cdev *dev;
68
69 make_dev_args_init(&args);
70 args.mda_devsw = &power_cdevsw;
71 args.mda_uid = UID_ROOT;
72 args.mda_gid = GID_OPERATOR;
73 args.mda_mode = 0660;
74 if (make_dev_s(&args, &dev, "power") != 0)
75 printf("Failed to create power device");
76 }
77 SYSINIT(powerdev, SI_SUB_PSEUDO, SI_ORDER_ANY, power_init, NULL);
78
79 static int
power_ioctl(struct cdev * dev,u_long cmd,caddr_t data,int fflag,struct thread * td)80 power_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
81 struct thread *td)
82 {
83 int err = 0;
84 uint32_t trans;
85
86 if ((fflag & FWRITE) == 0)
87 return (EPERM);
88
89 switch (cmd) {
90 case PIOTRANSITION:
91 trans = *(uint32_t *)data;
92 /* Check for overflow */
93 if ((enum power_transition)trans != trans)
94 return (EINVAL);
95 err = power_pm_suspend((enum power_transition)trans);
96 break;
97 default:
98 err = EINVAL;
99 }
100 return (err);
101 }
102
103 enum power_stype
power_name_to_stype(const char * name)104 power_name_to_stype(const char *name)
105 {
106 enum power_stype stype;
107
108 for (stype = 0; stype < POWER_STYPE_COUNT; stype++) {
109 if (strcasecmp(name, power_stype_names[stype]) == 0)
110 return (stype);
111 }
112 return (POWER_STYPE_UNKNOWN);
113 }
114
115 const char *
power_stype_to_name(enum power_stype stype)116 power_stype_to_name(enum power_stype stype)
117 {
118 if (stype == POWER_STYPE_UNKNOWN)
119 return ("NONE");
120 if (stype < POWER_STYPE_AWAKE || stype >= POWER_STYPE_COUNT)
121 return (NULL);
122 return (power_stype_names[stype]);
123 }
124
125 static int
sysctl_supported_stypes(SYSCTL_HANDLER_ARGS)126 sysctl_supported_stypes(SYSCTL_HANDLER_ARGS)
127 {
128 int error;
129 struct sbuf sb;
130 enum power_stype stype;
131
132 sbuf_new(&sb, NULL, 32, SBUF_AUTOEXTEND);
133 for (stype = 0; stype < POWER_STYPE_COUNT; stype++) {
134 if (power_pm_supported[stype])
135 sbuf_printf(&sb, "%s ", power_stype_to_name(stype));
136 }
137 sbuf_trim(&sb);
138 sbuf_finish(&sb);
139 error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req);
140 sbuf_delete(&sb);
141
142 return (error);
143 }
144
145 static int
power_sysctl_stype(SYSCTL_HANDLER_ARGS)146 power_sysctl_stype(SYSCTL_HANDLER_ARGS)
147 {
148 char name[POWER_STYPE_NAME_LEN];
149 int err;
150 enum power_stype new_stype, old_stype;
151
152 old_stype = *(enum power_stype *)oidp->oid_arg1;
153 strlcpy(name, power_stype_to_name(old_stype), sizeof(name));
154 err = sysctl_handle_string(oidp, name, sizeof(name), req);
155 if (err != 0 || req->newptr == NULL)
156 return (err);
157
158 new_stype = power_name_to_stype(name);
159 if (new_stype == POWER_STYPE_UNKNOWN)
160 return (EINVAL);
161 if (!power_pm_supported[new_stype])
162 return (EOPNOTSUPP);
163 if (new_stype != old_stype)
164 *(enum power_stype *)oidp->oid_arg1 = new_stype;
165 return (0);
166 }
167
168 static SYSCTL_NODE(_kern, OID_AUTO, power, CTLFLAG_RW, 0,
169 "Generic power management related sysctls");
170
171 SYSCTL_PROC(_kern_power, OID_AUTO, supported_stype,
172 CTLTYPE_STRING | CTLFLAG_RD, 0, 0, sysctl_supported_stypes, "A",
173 "List supported sleep types");
174 SYSCTL_PROC(_kern_power, OID_AUTO, standby, CTLTYPE_STRING | CTLFLAG_RW,
175 &power_standby_stype, 0, power_sysctl_stype, "A",
176 "Sleep type to enter on standby");
177 SYSCTL_PROC(_kern_power, OID_AUTO, suspend, CTLTYPE_STRING | CTLFLAG_RW,
178 &power_suspend_stype, 0, power_sysctl_stype, "A",
179 "Sleep type to enter on suspend");
180 SYSCTL_PROC(_kern_power, OID_AUTO, hibernate, CTLTYPE_STRING | CTLFLAG_RW,
181 &power_hibernate_stype, 0, power_sysctl_stype, "A",
182 "Sleep type to enter on hibernate");
183
184 static void
power_pm_deferred_fn(void * arg,int pending)185 power_pm_deferred_fn(void *arg, int pending)
186 {
187 enum power_stype stype = (intptr_t)arg;
188
189 power_pm_fn(POWER_CMD_SUSPEND, power_pm_arg, stype);
190 }
191
192 int
power_pm_register(u_int pm_type,power_pm_fn_t pm_fn,void * pm_arg,const bool pm_supported[static POWER_STYPE_COUNT])193 power_pm_register(u_int pm_type, power_pm_fn_t pm_fn, void *pm_arg,
194 const bool pm_supported[static POWER_STYPE_COUNT])
195 {
196 int error;
197
198 if (power_pm_type == POWER_PM_TYPE_NONE ||
199 power_pm_type == pm_type) {
200 power_pm_type = pm_type;
201 power_pm_fn = pm_fn;
202 power_pm_arg = pm_arg;
203 memcpy(power_pm_supported, pm_supported,
204 sizeof(power_pm_supported));
205 if (power_pm_supported[POWER_STYPE_STANDBY])
206 power_standby_stype = POWER_STYPE_STANDBY;
207 if (power_pm_supported[POWER_STYPE_FW_SUSPEND])
208 power_suspend_stype = POWER_STYPE_FW_SUSPEND;
209 else if (power_pm_supported[POWER_STYPE_SUSPEND_TO_IDLE])
210 power_suspend_stype = POWER_STYPE_SUSPEND_TO_IDLE;
211 if (power_pm_supported[POWER_STYPE_FW_HIBERNATE])
212 power_hibernate_stype = POWER_STYPE_FW_HIBERNATE;
213 error = 0;
214 TASK_INIT(&power_pm_task, 0, power_pm_deferred_fn, NULL);
215 } else {
216 error = ENXIO;
217 }
218
219 return (error);
220 }
221
222 u_int
power_pm_get_type(void)223 power_pm_get_type(void)
224 {
225
226 return (power_pm_type);
227 }
228
229 int
power_pm_suspend(enum power_transition trans)230 power_pm_suspend(enum power_transition trans)
231 {
232 enum power_stype stype;
233
234 if (power_pm_fn == NULL)
235 return (ENXIO);
236
237 switch (trans) {
238 case POWER_TRANSITION_STANDBY:
239 stype = power_standby_stype;
240 break;
241 case POWER_TRANSITION_SUSPEND:
242 stype = power_suspend_stype;
243 break;
244 case POWER_TRANSITION_HIBERNATE:
245 stype = power_hibernate_stype;
246 break;
247 default:
248 printf("%s: unknown sleep state transition %d\n", __func__,
249 trans);
250 return (EINVAL);
251 }
252
253 power_pm_task.ta_context = (void *)(intptr_t)stype;
254 taskqueue_enqueue(taskqueue_thread, &power_pm_task);
255
256 return (0);
257 }
258
259 /*
260 * Power profile.
261 */
262
263 static int power_profile_state = POWER_PROFILE_PERFORMANCE;
264
265 int
power_profile_get_state(void)266 power_profile_get_state(void)
267 {
268 return (power_profile_state);
269 }
270
271 void
power_profile_set_state(int state)272 power_profile_set_state(int state)
273 {
274 int changed;
275
276 if (state != power_profile_state) {
277 power_profile_state = state;
278 changed = 1;
279 if (bootverbose) {
280 printf("system power profile changed to '%s'\n",
281 (state == POWER_PROFILE_PERFORMANCE) ?
282 "performance" : "economy");
283 }
284 } else {
285 changed = 0;
286 }
287
288 if (changed)
289 EVENTHANDLER_INVOKE(power_profile_change, 0);
290 }
291