xref: /freebsd/sys/kern/subr_power.c (revision 554a459ff59969e0f66b1b1c6695d249e2eecd84)
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