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
37 #include <contrib/dev/acpica/include/acpi.h>
38 #include <dev/acpica/acpivar.h>
39
40 #include <linux/notifier.h>
41 #include <linux/suspend.h>
42 #include <linux/uuid.h>
43
44 #include <acpi/acpi_bus.h>
45 #include <acpi/video.h>
46
47 #define ACPI_AC_CLASS "ac_adapter"
48
49 ACPI_MODULE_NAME("linux_acpi")
50
51 enum {
52 LINUX_ACPI_ACAD,
53 LINUX_ACPI_VIDEO,
54 LINUX_ACPI_TAGS /* must be last */
55 };
56 _Static_assert(LINUX_ACPI_TAGS <= LINUX_NOTIFY_TAGS,
57 "Not enough space for tags in notifier_block structure");
58
59 #ifdef DEV_ACPI
60
61 suspend_state_t pm_suspend_target_state = PM_SUSPEND_ON;
62
63 static uint32_t linux_acpi_target_sleep_state = ACPI_STATE_S0;
64
65 static eventhandler_tag resume_tag;
66 static eventhandler_tag suspend_tag;
67
68 ACPI_HANDLE
bsd_acpi_get_handle(device_t bsddev)69 bsd_acpi_get_handle(device_t bsddev)
70 {
71 return (acpi_get_handle(bsddev));
72 }
73
74 bool
acpi_check_dsm(ACPI_HANDLE handle,const guid_t * uuid,int rev,uint64_t funcs)75 acpi_check_dsm(ACPI_HANDLE handle, const guid_t *uuid, int rev, uint64_t funcs)
76 {
77 UINT64 ret;
78
79 if (funcs == 0)
80 return (false);
81
82 /*
83 * From ACPI 6.3 spec 9.1.1:
84 * Bit 0 indicates whether there is support for any functions other
85 * than function 0 for the specified UUID and Revision ID. If set to
86 * zero, no functions are supported (other than function zero) for the
87 * specified UUID and Revision ID.
88 */
89 funcs |= 1 << 0;
90
91 ret = acpi_DSMQuery(handle, (const uint8_t *)uuid, rev);
92 return ((ret & funcs) == funcs);
93 }
94
95 ACPI_OBJECT *
acpi_evaluate_dsm_typed(ACPI_HANDLE handle,const guid_t * uuid,int rev,int func,ACPI_OBJECT * argv4,ACPI_OBJECT_TYPE type)96 acpi_evaluate_dsm_typed(ACPI_HANDLE handle, const guid_t *uuid, int rev,
97 int func, ACPI_OBJECT *argv4, ACPI_OBJECT_TYPE type)
98 {
99 ACPI_BUFFER buf;
100 ACPI_STATUS status;
101
102 status = acpi_EvaluateDSMTyped(handle, (const uint8_t *)uuid, rev, func,
103 argv4, &buf, type);
104 return (ACPI_SUCCESS(status) ? (ACPI_OBJECT *)buf.Pointer : NULL);
105 }
106
107 union linuxkpi_acpi_object *
acpi_evaluate_dsm(ACPI_HANDLE ObjHandle,const guid_t * guid,UINT64 rev,UINT64 func,union linuxkpi_acpi_object * pkg)108 acpi_evaluate_dsm(ACPI_HANDLE ObjHandle, const guid_t *guid,
109 UINT64 rev, UINT64 func, union linuxkpi_acpi_object *pkg)
110 {
111 ACPI_BUFFER buf;
112 ACPI_STATUS status;
113
114 status = acpi_EvaluateDSM(ObjHandle, (const uint8_t *)guid, rev, func,
115 (ACPI_OBJECT *)pkg, &buf);
116 return (ACPI_SUCCESS(status) ?
117 (union linuxkpi_acpi_object *)buf.Pointer : NULL);
118 }
119
120 static void
linux_handle_power_suspend_event(void * arg __unused)121 linux_handle_power_suspend_event(void *arg __unused)
122 {
123 /*
124 * Only support S3 for now.
125 * acpi_sleep_event isn't always called so we use power_suspend_early
126 * instead which means we don't know what state we're switching to.
127 * TODO: Make acpi_sleep_event consistent
128 */
129 linux_acpi_target_sleep_state = ACPI_STATE_S3;
130 pm_suspend_target_state = PM_SUSPEND_MEM;
131 }
132
133 static void
linux_handle_power_resume_event(void * arg __unused)134 linux_handle_power_resume_event(void *arg __unused)
135 {
136 linux_acpi_target_sleep_state = ACPI_STATE_S0;
137 pm_suspend_target_state = PM_SUSPEND_ON;
138 }
139
140 static void
linux_handle_acpi_acad_event(void * arg,int data)141 linux_handle_acpi_acad_event(void *arg, int data)
142 {
143 struct notifier_block *nb = arg;
144 /*
145 * Event type information is lost ATM in FreeBSD ACPI event handler.
146 * Fortunately, drm-kmod do not distinct AC event types too, so we can
147 * use any type e.g. ACPI_NOTIFY_BUS_CHECK that suits notifier handler.
148 */
149 struct acpi_bus_event abe = {
150 .device_class = ACPI_AC_CLASS,
151 .type = ACPI_NOTIFY_BUS_CHECK,
152 .data = data,
153 };
154
155 nb->notifier_call(nb, 0, &abe);
156 }
157
158 static void
linux_handle_acpi_video_event(void * arg,int type)159 linux_handle_acpi_video_event(void *arg, int type)
160 {
161 struct notifier_block *nb = arg;
162 struct acpi_bus_event abe = {
163 .device_class = ACPI_VIDEO_CLASS,
164 .type = type,
165 .data = 0,
166 };
167
168 nb->notifier_call(nb, 0, &abe);
169 }
170
171 int
register_acpi_notifier(struct notifier_block * nb)172 register_acpi_notifier(struct notifier_block *nb)
173 {
174 nb->tags[LINUX_ACPI_ACAD] = EVENTHANDLER_REGISTER(acpi_acad_event,
175 linux_handle_acpi_acad_event, nb, EVENTHANDLER_PRI_FIRST);
176 nb->tags[LINUX_ACPI_VIDEO] = EVENTHANDLER_REGISTER(acpi_video_event,
177 linux_handle_acpi_video_event, nb, EVENTHANDLER_PRI_FIRST);
178
179 return (0);
180 }
181
182 int
unregister_acpi_notifier(struct notifier_block * nb)183 unregister_acpi_notifier(struct notifier_block *nb)
184 {
185 EVENTHANDLER_DEREGISTER(acpi_acad_event, nb->tags[LINUX_ACPI_ACAD]);
186 EVENTHANDLER_DEREGISTER(acpi_video_event, nb->tags[LINUX_ACPI_VIDEO]);
187
188 return (0);
189 }
190
191 uint32_t
acpi_target_system_state(void)192 acpi_target_system_state(void)
193 {
194 return (linux_acpi_target_sleep_state);
195 }
196
197 struct acpi_dev_present_ctx {
198 const char *hid;
199 const char *uid;
200 int64_t hrv;
201 struct acpi_device *dev;
202 };
203
204 static ACPI_STATUS
acpi_dev_present_cb(ACPI_HANDLE handle,UINT32 level,void * context,void ** result)205 acpi_dev_present_cb(ACPI_HANDLE handle, UINT32 level, void *context,
206 void **result)
207 {
208 ACPI_DEVICE_INFO *devinfo;
209 struct acpi_device *dev;
210 struct acpi_dev_present_ctx *match = context;
211 bool present = false;
212 UINT32 sta, hrv;
213 int i;
214
215 if (handle == NULL)
216 return (AE_OK);
217
218 if (!ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) &&
219 !ACPI_DEVICE_PRESENT(sta))
220 return (AE_OK);
221
222 if (ACPI_FAILURE(AcpiGetObjectInfo(handle, &devinfo)))
223 return (AE_OK);
224
225 if ((devinfo->Valid & ACPI_VALID_HID) != 0 &&
226 strcmp(match->hid, devinfo->HardwareId.String) == 0) {
227 present = true;
228 } else if ((devinfo->Valid & ACPI_VALID_CID) != 0) {
229 for (i = 0; i < devinfo->CompatibleIdList.Count; i++) {
230 if (strcmp(match->hid,
231 devinfo->CompatibleIdList.Ids[i].String) == 0) {
232 present = true;
233 break;
234 }
235 }
236 }
237 if (present && match->uid != NULL &&
238 ((devinfo->Valid & ACPI_VALID_UID) == 0 ||
239 strcmp(match->uid, devinfo->UniqueId.String) != 0))
240 present = false;
241
242 AcpiOsFree(devinfo);
243 if (!present)
244 return (AE_OK);
245
246 if (match->hrv != -1) {
247 if (ACPI_FAILURE(acpi_GetInteger(handle, "_HRV", &hrv)))
248 return (AE_OK);
249 if (hrv != match->hrv)
250 return (AE_OK);
251 }
252
253 dev = acpi_get_device(handle);
254 if (dev == NULL)
255 return (AE_OK);
256 match->dev = dev;
257
258 return (AE_ERROR);
259 }
260
261 bool
lkpi_acpi_dev_present(const char * hid,const char * uid,int64_t hrv)262 lkpi_acpi_dev_present(const char *hid, const char *uid, int64_t hrv)
263 {
264 struct acpi_dev_present_ctx match;
265 int rv;
266
267 match.hid = hid;
268 match.uid = uid;
269 match.hrv = hrv;
270
271 rv = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
272 ACPI_UINT32_MAX, acpi_dev_present_cb, NULL, &match, NULL);
273
274 return (rv == AE_ERROR);
275 }
276
277 struct acpi_device *
lkpi_acpi_dev_get_first_match_dev(const char * hid,const char * uid,int64_t hrv)278 lkpi_acpi_dev_get_first_match_dev(const char *hid, const char *uid,
279 int64_t hrv)
280 {
281 struct acpi_dev_present_ctx match;
282 int rv;
283
284 match.hid = hid;
285 match.uid = uid;
286 match.hrv = hrv;
287 match.dev = NULL;
288
289 rv = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
290 ACPI_UINT32_MAX, acpi_dev_present_cb, NULL, &match, NULL);
291
292 return (rv == AE_ERROR ? match.dev : NULL);
293 }
294
295 static void
linux_register_acpi_event_handlers(void * arg __unused)296 linux_register_acpi_event_handlers(void *arg __unused)
297 {
298 /*
299 * XXX johalun: acpi_{sleep,wakeup}_event can't be trusted, use
300 * power_{suspend_early,resume} 'acpiconf -s 3' or 'zzz' will not
301 * generate acpi_sleep_event... Lid open or wake on button generates
302 * acpi_wakeup_event on one of my Dell laptops but not the other
303 * (but it does power on)... is this a general thing?
304 */
305 resume_tag = EVENTHANDLER_REGISTER(power_resume,
306 linux_handle_power_resume_event, NULL, EVENTHANDLER_PRI_FIRST);
307 suspend_tag = EVENTHANDLER_REGISTER(power_suspend_early,
308 linux_handle_power_suspend_event, NULL, EVENTHANDLER_PRI_FIRST);
309 }
310
311 static void
linux_deregister_acpi_event_handlers(void * arg __unused)312 linux_deregister_acpi_event_handlers(void *arg __unused)
313 {
314 EVENTHANDLER_DEREGISTER(power_resume, resume_tag);
315 EVENTHANDLER_DEREGISTER(power_suspend_early, suspend_tag);
316 }
317
318 SYSINIT(linux_acpi_events, SI_SUB_DRIVERS, SI_ORDER_ANY,
319 linux_register_acpi_event_handlers, NULL);
320 SYSUNINIT(linux_acpi_events, SI_SUB_DRIVERS, SI_ORDER_ANY,
321 linux_deregister_acpi_event_handlers, NULL);
322
323 #else /* !DEV_ACPI */
324
325 ACPI_HANDLE
bsd_acpi_get_handle(device_t bsddev)326 bsd_acpi_get_handle(device_t bsddev)
327 {
328 return (NULL);
329 }
330
331 bool
acpi_check_dsm(ACPI_HANDLE handle,const guid_t * uuid,int rev,uint64_t funcs)332 acpi_check_dsm(ACPI_HANDLE handle, const guid_t *uuid, int rev, uint64_t funcs)
333 {
334 return (false);
335 }
336
337 ACPI_OBJECT *
acpi_evaluate_dsm_typed(ACPI_HANDLE handle,const guid_t * uuid,int rev,int func,ACPI_OBJECT * argv4,ACPI_OBJECT_TYPE type)338 acpi_evaluate_dsm_typed(ACPI_HANDLE handle, const guid_t *uuid, int rev,
339 int func, ACPI_OBJECT *argv4, ACPI_OBJECT_TYPE type)
340 {
341 return (NULL);
342 }
343
344 union linuxkpi_acpi_object *
acpi_evaluate_dsm(ACPI_HANDLE ObjHandle,const guid_t * guid,UINT64 rev,UINT64 func,union linuxkpi_acpi_object * pkg)345 acpi_evaluate_dsm(ACPI_HANDLE ObjHandle, const guid_t *guid,
346 UINT64 rev, UINT64 func, union linuxkpi_acpi_object *pkg)
347 {
348 return (NULL);
349 }
350
351 int
register_acpi_notifier(struct notifier_block * nb)352 register_acpi_notifier(struct notifier_block *nb)
353 {
354 return (0);
355 }
356
357 int
unregister_acpi_notifier(struct notifier_block * nb)358 unregister_acpi_notifier(struct notifier_block *nb)
359 {
360 return (0);
361 }
362
363 uint32_t
acpi_target_system_state(void)364 acpi_target_system_state(void)
365 {
366 return (ACPI_STATE_S0);
367 }
368
369 bool
lkpi_acpi_dev_present(const char * hid,const char * uid,int64_t hrv)370 lkpi_acpi_dev_present(const char *hid, const char *uid, int64_t hrv)
371 {
372 return (false);
373 }
374
375 struct acpi_device *
lkpi_acpi_dev_get_first_match_dev(const char * hid,const char * uid,int64_t hrv)376 lkpi_acpi_dev_get_first_match_dev(const char *hid, const char *uid,
377 int64_t hrv)
378 {
379 return (NULL);
380 }
381
382 #endif /* !DEV_ACPI */
383