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