xref: /freebsd/sys/compat/linuxkpi/common/src/linux_acpi.c (revision fc2ed9d9680461937c7ffa4cc77fa38e656deb8b)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2018 Johannes Lundberg <johalun@FreeBSD.org>
5  * Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are
9  * met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #include "opt_acpi.h"
31 
32 #include <sys/types.h>
33 #include <sys/bus.h>
34 #include <sys/eventhandler.h>
35 #include <sys/kernel.h>
36 #include <sys/power.h>
37 
38 #include <contrib/dev/acpica/include/acpi.h>
39 #include <dev/acpica/acpivar.h>
40 
41 #include <linux/notifier.h>
42 #include <linux/suspend.h>
43 #include <linux/uuid.h>
44 
45 #include <acpi/acpi_bus.h>
46 #include <acpi/video.h>
47 
48 #define	ACPI_AC_CLASS	"ac_adapter"
49 
50 ACPI_MODULE_NAME("linux_acpi")
51 
52 enum {
53 	LINUX_ACPI_ACAD,
54 	LINUX_ACPI_VIDEO,
55 	LINUX_ACPI_TAGS			/* must be last */
56 };
57 _Static_assert(LINUX_ACPI_TAGS <= LINUX_NOTIFY_TAGS,
58     "Not enough space for tags in notifier_block structure");
59 
60 #ifdef DEV_ACPI
61 
62 suspend_state_t pm_suspend_target_state = PM_SUSPEND_ON;
63 
64 static uint32_t linux_acpi_target_sleep_state = ACPI_STATE_S0;
65 
66 static eventhandler_tag resume_tag;
67 static eventhandler_tag suspend_tag;
68 
69 ACPI_HANDLE
70 bsd_acpi_get_handle(device_t bsddev)
71 {
72 	return (acpi_get_handle(bsddev));
73 }
74 
75 bool
76 acpi_check_dsm(ACPI_HANDLE handle, const guid_t *uuid, int rev, uint64_t funcs)
77 {
78 	UINT64 ret;
79 
80 	if (funcs == 0)
81 		return (false);
82 
83 	/*
84 	 * From ACPI 6.3 spec 9.1.1:
85 	 * Bit 0 indicates whether there is support for any functions other
86 	 * than function 0 for the specified UUID and Revision ID. If set to
87 	 * zero, no functions are supported (other than function zero) for the
88 	 * specified UUID and Revision ID.
89 	 */
90 	funcs |= 1 << 0;
91 
92 	ret = acpi_DSMQuery(handle, (const uint8_t *)uuid, rev);
93 	return ((ret & funcs) == funcs);
94 }
95 
96 ACPI_OBJECT *
97 acpi_evaluate_dsm_typed(ACPI_HANDLE handle, const guid_t *uuid, int rev,
98     int func, ACPI_OBJECT *argv4, ACPI_OBJECT_TYPE type)
99 {
100 	ACPI_BUFFER buf;
101 	ACPI_STATUS status;
102 
103 	status = acpi_EvaluateDSMTyped(handle, (const uint8_t *)uuid, rev, func,
104 	    argv4, &buf, type);
105 	return (ACPI_SUCCESS(status) ? (ACPI_OBJECT *)buf.Pointer : NULL);
106 }
107 
108 union linuxkpi_acpi_object *
109 acpi_evaluate_dsm(ACPI_HANDLE ObjHandle, const guid_t *guid,
110     UINT64 rev, UINT64 func, union linuxkpi_acpi_object *pkg)
111 {
112 	ACPI_BUFFER buf;
113 	ACPI_STATUS status;
114 
115 	status = acpi_EvaluateDSM(ObjHandle, (const uint8_t *)guid, rev, func,
116 	    (ACPI_OBJECT *)pkg, &buf);
117 	return (ACPI_SUCCESS(status) ?
118 	    (union linuxkpi_acpi_object *)buf.Pointer : NULL);
119 }
120 
121 static void
122 linux_handle_power_suspend_event(void *arg __unused,
123     enum power_stype stype __unused)
124 {
125 	/*
126 	 * Only support S3 for now.
127 	 * acpi_sleep_event isn't always called so we use power_suspend_early
128 	 * instead which means we don't know what state we're switching to.
129 	 * TODO: Make acpi_sleep_event consistent
130 	 */
131 	linux_acpi_target_sleep_state = ACPI_STATE_S3;
132 	pm_suspend_target_state = PM_SUSPEND_MEM;
133 }
134 
135 static void
136 linux_handle_power_resume_event(void *arg __unused,
137     enum power_stype stype __unused)
138 {
139 	linux_acpi_target_sleep_state = ACPI_STATE_S0;
140 	pm_suspend_target_state = PM_SUSPEND_ON;
141 }
142 
143 static void
144 linux_handle_acpi_acad_event(void *arg, int data)
145 {
146 	struct notifier_block *nb = arg;
147 	/*
148 	 * Event type information is lost ATM in FreeBSD ACPI event handler.
149 	 * Fortunately, drm-kmod do not distinct AC event types too, so we can
150 	 * use any type e.g. ACPI_NOTIFY_BUS_CHECK that suits notifier handler.
151 	 */
152 	struct acpi_bus_event abe = {
153 	    .device_class = ACPI_AC_CLASS,
154 	    .type = ACPI_NOTIFY_BUS_CHECK,
155 	    .data = data,
156 	};
157 
158 	nb->notifier_call(nb, 0, &abe);
159 }
160 
161 static void
162 linux_handle_acpi_video_event(void *arg, int type)
163 {
164 	struct notifier_block *nb = arg;
165 	struct acpi_bus_event abe = {
166 	    .device_class = ACPI_VIDEO_CLASS,
167 	    .type = type,
168 	    .data = 0,
169 	};
170 
171 	nb->notifier_call(nb, 0, &abe);
172 }
173 
174 int
175 register_acpi_notifier(struct notifier_block *nb)
176 {
177 	nb->tags[LINUX_ACPI_ACAD] = EVENTHANDLER_REGISTER(acpi_acad_event,
178 	    linux_handle_acpi_acad_event, nb, EVENTHANDLER_PRI_FIRST);
179 	nb->tags[LINUX_ACPI_VIDEO] = EVENTHANDLER_REGISTER(acpi_video_event,
180 	    linux_handle_acpi_video_event, nb, EVENTHANDLER_PRI_FIRST);
181 
182 	return (0);
183 }
184 
185 int
186 unregister_acpi_notifier(struct notifier_block *nb)
187 {
188 	EVENTHANDLER_DEREGISTER(acpi_acad_event, nb->tags[LINUX_ACPI_ACAD]);
189 	EVENTHANDLER_DEREGISTER(acpi_video_event, nb->tags[LINUX_ACPI_VIDEO]);
190 
191 	return (0);
192 }
193 
194 uint32_t
195 acpi_target_system_state(void)
196 {
197 	return (linux_acpi_target_sleep_state);
198 }
199 
200 struct acpi_dev_present_ctx {
201 	const char *hid;
202 	const char *uid;
203 	int64_t hrv;
204 	struct acpi_device *dev;
205 };
206 
207 static ACPI_STATUS
208 acpi_dev_present_cb(ACPI_HANDLE handle, UINT32 level, void *context,
209     void **result)
210 {
211 	ACPI_DEVICE_INFO *devinfo;
212 	struct acpi_device *dev;
213 	struct acpi_dev_present_ctx *match = context;
214 	bool present = false;
215 	UINT32 sta, hrv;
216 	int i;
217 
218 	if (handle == NULL)
219 		return (AE_OK);
220 
221 	if (!ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) &&
222 	    !ACPI_DEVICE_PRESENT(sta))
223 		return (AE_OK);
224 
225 	if (ACPI_FAILURE(AcpiGetObjectInfo(handle, &devinfo)))
226 		return (AE_OK);
227 
228 	if ((devinfo->Valid & ACPI_VALID_HID) != 0 &&
229 	    strcmp(match->hid, devinfo->HardwareId.String) == 0) {
230 		present = true;
231 	} else if ((devinfo->Valid & ACPI_VALID_CID) != 0) {
232 		for (i = 0; i < devinfo->CompatibleIdList.Count; i++) {
233 			if (strcmp(match->hid,
234 			    devinfo->CompatibleIdList.Ids[i].String) == 0) {
235 				present = true;
236 				break;
237 			}
238 		}
239 	}
240 	if (present && match->uid != NULL &&
241 	    ((devinfo->Valid & ACPI_VALID_UID) == 0 ||
242 	      strcmp(match->uid, devinfo->UniqueId.String) != 0))
243 		present = false;
244 
245 	AcpiOsFree(devinfo);
246 	if (!present)
247 		return (AE_OK);
248 
249 	if (match->hrv != -1) {
250 		if (ACPI_FAILURE(acpi_GetInteger(handle, "_HRV", &hrv)))
251 			return (AE_OK);
252 		if (hrv != match->hrv)
253 			return (AE_OK);
254 	}
255 
256 	dev = acpi_get_device(handle);
257 	if (dev == NULL)
258 		return (AE_OK);
259 	match->dev = dev;
260 
261 	return (AE_ERROR);
262 }
263 
264 bool
265 lkpi_acpi_dev_present(const char *hid, const char *uid, int64_t hrv)
266 {
267 	struct acpi_dev_present_ctx match;
268 	int rv;
269 
270 	match.hid = hid;
271 	match.uid = uid;
272 	match.hrv = hrv;
273 
274 	rv = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
275 	    ACPI_UINT32_MAX, acpi_dev_present_cb, NULL, &match, NULL);
276 
277 	return (rv == AE_ERROR);
278 }
279 
280 struct acpi_device *
281 lkpi_acpi_dev_get_first_match_dev(const char *hid, const char *uid,
282     int64_t hrv)
283 {
284 	struct acpi_dev_present_ctx match;
285 	int rv;
286 
287 	match.hid = hid;
288 	match.uid = uid;
289 	match.hrv = hrv;
290 	match.dev = NULL;
291 
292 	rv = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
293 	    ACPI_UINT32_MAX, acpi_dev_present_cb, NULL, &match, NULL);
294 
295 	return (rv == AE_ERROR ? match.dev : NULL);
296 }
297 
298 static void
299 linux_register_acpi_event_handlers(void *arg __unused)
300 {
301 	/*
302 	 * XXX johalun: acpi_{sleep,wakeup}_event can't be trusted, use
303 	 * power_{suspend_early,resume} 'acpiconf -s 3' or 'zzz' will not
304 	 * generate acpi_sleep_event... Lid open or wake on button generates
305 	 * acpi_wakeup_event on one of my Dell laptops but not the other
306 	 * (but it does power on)... is this a general thing?
307 	 */
308 	resume_tag = EVENTHANDLER_REGISTER(power_resume,
309 	    linux_handle_power_resume_event, NULL, EVENTHANDLER_PRI_FIRST);
310 	suspend_tag = EVENTHANDLER_REGISTER(power_suspend_early,
311 	    linux_handle_power_suspend_event, NULL, EVENTHANDLER_PRI_FIRST);
312 }
313 
314 static void
315 linux_deregister_acpi_event_handlers(void *arg __unused)
316 {
317 	EVENTHANDLER_DEREGISTER(power_resume, resume_tag);
318 	EVENTHANDLER_DEREGISTER(power_suspend_early, suspend_tag);
319 }
320 
321 SYSINIT(linux_acpi_events, SI_SUB_DRIVERS, SI_ORDER_ANY,
322     linux_register_acpi_event_handlers, NULL);
323 SYSUNINIT(linux_acpi_events, SI_SUB_DRIVERS, SI_ORDER_ANY,
324     linux_deregister_acpi_event_handlers, NULL);
325 
326 #else	/* !DEV_ACPI */
327 
328 ACPI_HANDLE
329 bsd_acpi_get_handle(device_t bsddev)
330 {
331 	return (NULL);
332 }
333 
334 bool
335 acpi_check_dsm(ACPI_HANDLE handle, const guid_t *uuid, int rev, uint64_t funcs)
336 {
337 	return (false);
338 }
339 
340 ACPI_OBJECT *
341 acpi_evaluate_dsm_typed(ACPI_HANDLE handle, const guid_t *uuid, int rev,
342      int func, ACPI_OBJECT *argv4, ACPI_OBJECT_TYPE type)
343 {
344 	return (NULL);
345 }
346 
347 union linuxkpi_acpi_object *
348 acpi_evaluate_dsm(ACPI_HANDLE ObjHandle, const guid_t *guid,
349     UINT64 rev, UINT64 func, union linuxkpi_acpi_object *pkg)
350 {
351 	return (NULL);
352 }
353 
354 int
355 register_acpi_notifier(struct notifier_block *nb)
356 {
357 	return (0);
358 }
359 
360 int
361 unregister_acpi_notifier(struct notifier_block *nb)
362 {
363 	return (0);
364 }
365 
366 uint32_t
367 acpi_target_system_state(void)
368 {
369 	return (ACPI_STATE_S0);
370 }
371 
372 bool
373 lkpi_acpi_dev_present(const char *hid, const char *uid, int64_t hrv)
374 {
375 	return (false);
376 }
377 
378 struct acpi_device *
379 lkpi_acpi_dev_get_first_match_dev(const char *hid, const char *uid,
380     int64_t hrv)
381 {
382 	return (NULL);
383 }
384 
385 #endif	/* !DEV_ACPI */
386