1 /* 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2024-2025 The FreeBSD Foundation 5 * 6 * This software was developed by Aymeric Wibo <obiwac@freebsd.org> 7 * under sponsorship from the FreeBSD Foundation. 8 */ 9 10 #include <sys/param.h> 11 #include <sys/bus.h> 12 #include <sys/eventhandler.h> 13 #include <sys/kernel.h> 14 #include <sys/malloc.h> 15 #include <sys/module.h> 16 #include <sys/uuid.h> 17 18 #include <machine/_inttypes.h> 19 20 #include <contrib/dev/acpica/include/acpi.h> 21 #include <contrib/dev/acpica/include/accommon.h> 22 23 #include <dev/acpica/acpivar.h> 24 25 /* Hooks for the ACPI CA debugging infrastructure */ 26 #define _COMPONENT ACPI_SPMC 27 ACPI_MODULE_NAME("SPMC") 28 29 static SYSCTL_NODE(_debug_acpi, OID_AUTO, spmc, CTLFLAG_RD | CTLFLAG_MPSAFE, 30 NULL, "SPMC debugging"); 31 32 static char *spmc_ids[] = { 33 "PNP0D80", 34 NULL 35 }; 36 37 enum intel_dsm_index { 38 DSM_ENUM_FUNCTIONS = 0, 39 DSM_GET_DEVICE_CONSTRAINTS = 1, 40 DSM_GET_CRASH_DUMP_DEVICE = 2, 41 DSM_DISPLAY_OFF_NOTIF = 3, 42 DSM_DISPLAY_ON_NOTIF = 4, 43 DSM_ENTRY_NOTIF = 5, 44 DSM_EXIT_NOTIF = 6, 45 /* Only for Microsoft DSM set. */ 46 DSM_MODERN_ENTRY_NOTIF = 7, 47 DSM_MODERN_EXIT_NOTIF = 8, 48 }; 49 50 enum amd_dsm_index { 51 AMD_DSM_ENUM_FUNCTIONS = 0, 52 AMD_DSM_GET_DEVICE_CONSTRAINTS = 1, 53 AMD_DSM_ENTRY_NOTIF = 2, 54 AMD_DSM_EXIT_NOTIF = 3, 55 AMD_DSM_DISPLAY_OFF_NOTIF = 4, 56 AMD_DSM_DISPLAY_ON_NOTIF = 5, 57 }; 58 59 enum dsm_set_flags { 60 DSM_SET_INTEL = 1 << 0, 61 DSM_SET_MS = 1 << 1, 62 DSM_SET_AMD = 1 << 2, 63 }; 64 65 struct dsm_set { 66 enum dsm_set_flags flag; 67 const char *name; 68 int revision; 69 struct uuid uuid; 70 uint64_t dsms_expected; 71 }; 72 73 static struct dsm_set intel_dsm_set = { 74 .flag = DSM_SET_INTEL, 75 .name = "Intel", 76 /* 77 * XXX Linux uses 1 for the revision on Intel DSMs, but doesn't explain 78 * why. The commit that introduces this links to a document mentioning 79 * revision 0, so default this to 0. 80 * 81 * The debug.acpi.spmc.intel_dsm_revision sysctl may be used to configure 82 * this just in case. 83 */ 84 .revision = 0, 85 .uuid = { /* c4eb40a0-6cd2-11e2-bcfd-0800200c9a66 */ 86 0xc4eb40a0, 0x6cd2, 0x11e2, 0xbc, 0xfd, 87 {0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66}, 88 }, 89 .dsms_expected = DSM_GET_DEVICE_CONSTRAINTS | DSM_DISPLAY_OFF_NOTIF | 90 DSM_DISPLAY_ON_NOTIF | DSM_ENTRY_NOTIF | DSM_EXIT_NOTIF, 91 }; 92 93 SYSCTL_INT(_debug_acpi_spmc, OID_AUTO, intel_dsm_revision, CTLFLAG_RW, 94 &intel_dsm_set.revision, 0, 95 "Revision to use when evaluating Intel SPMC DSMs"); 96 97 static struct dsm_set ms_dsm_set = { 98 .flag = DSM_SET_MS, 99 .name = "Microsoft", 100 .revision = 0, 101 .uuid = { /* 11e00d56-ce64-47ce-837b-1f898f9aa461 */ 102 0x11e00d56, 0xce64, 0x47ce, 0x83, 0x7b, 103 {0x1f, 0x89, 0x8f, 0x9a, 0xa4, 0x61}, 104 }, 105 .dsms_expected = DSM_DISPLAY_OFF_NOTIF | DSM_DISPLAY_ON_NOTIF | 106 DSM_ENTRY_NOTIF | DSM_EXIT_NOTIF | DSM_MODERN_ENTRY_NOTIF | 107 DSM_MODERN_EXIT_NOTIF, 108 }; 109 110 static struct dsm_set amd_dsm_set = { 111 .flag = DSM_SET_AMD, 112 .name = "AMD", 113 /* 114 * XXX Linux uses 0 for the revision on AMD DSMs, but at least on the 115 * Framework 13 AMD 7040 series, the enum functions DSM only returns a 116 * function mask that covers all the DSMs we need to call when called 117 * with revision 2. 118 * 119 * The debug.acpi.spmc.amd_dsm_revision sysctl may be used to configure 120 * this just in case. 121 */ 122 .revision = 2, 123 .uuid = { /* e3f32452-febc-43ce-9039-932122d37721 */ 124 0xe3f32452, 0xfebc, 0x43ce, 0x90, 0x39, 125 {0x93, 0x21, 0x22, 0xd3, 0x77, 0x21}, 126 }, 127 .dsms_expected = AMD_DSM_GET_DEVICE_CONSTRAINTS | AMD_DSM_ENTRY_NOTIF | 128 AMD_DSM_EXIT_NOTIF | AMD_DSM_DISPLAY_OFF_NOTIF | 129 AMD_DSM_DISPLAY_ON_NOTIF, 130 }; 131 132 SYSCTL_INT(_debug_acpi_spmc, OID_AUTO, amd_dsm_revision, CTLFLAG_RW, 133 &amd_dsm_set.revision, 0, "Revision to use when evaluating AMD SPMC DSMs"); 134 135 union dsm_index { 136 int i; 137 enum intel_dsm_index regular; 138 enum amd_dsm_index amd; 139 }; 140 141 struct acpi_spmc_constraint { 142 bool enabled; 143 char *name; 144 int min_d_state; 145 ACPI_HANDLE handle; 146 147 /* Unused, spec only. */ 148 uint64_t lpi_uid; 149 uint64_t min_dev_specific_state; 150 151 /* Unused, AMD only. */ 152 uint64_t function_states; 153 }; 154 155 struct acpi_spmc_softc { 156 device_t dev; 157 ACPI_HANDLE handle; 158 ACPI_OBJECT *obj; 159 enum dsm_set_flags dsm_sets; 160 161 struct eventhandler_entry *eh_suspend; 162 struct eventhandler_entry *eh_resume; 163 164 bool constraints_populated; 165 size_t constraint_count; 166 struct acpi_spmc_constraint *constraints; 167 }; 168 169 static void acpi_spmc_check_dsm_set(struct acpi_spmc_softc *sc, 170 ACPI_HANDLE handle, struct dsm_set *dsm_set); 171 static int acpi_spmc_get_constraints(device_t dev); 172 static void acpi_spmc_free_constraints(struct acpi_spmc_softc *sc); 173 174 static void acpi_spmc_suspend(device_t dev, enum power_stype stype); 175 static void acpi_spmc_resume(device_t dev, enum power_stype stype); 176 177 static int 178 acpi_spmc_probe(device_t dev) 179 { 180 char *name; 181 ACPI_HANDLE handle; 182 struct acpi_spmc_softc *sc; 183 184 /* Check that this is an enabled device. */ 185 if (acpi_get_type(dev) != ACPI_TYPE_DEVICE || acpi_disabled("spmc")) 186 return (ENXIO); 187 188 if (ACPI_ID_PROBE(device_get_parent(dev), dev, spmc_ids, &name) > 0) 189 return (ENXIO); 190 191 handle = acpi_get_handle(dev); 192 if (handle == NULL) 193 return (ENXIO); 194 195 sc = device_get_softc(dev); 196 197 /* Check which sets of DSM's are supported. */ 198 sc->dsm_sets = 0; 199 200 acpi_spmc_check_dsm_set(sc, handle, &intel_dsm_set); 201 acpi_spmc_check_dsm_set(sc, handle, &ms_dsm_set); 202 acpi_spmc_check_dsm_set(sc, handle, &amd_dsm_set); 203 204 if (sc->dsm_sets == 0) 205 return (ENXIO); 206 207 device_set_descf(dev, "Low Power S0 Idle (DSM sets 0x%x)", 208 sc->dsm_sets); 209 210 return (0); 211 } 212 213 static int 214 acpi_spmc_attach(device_t dev) 215 { 216 struct acpi_spmc_softc *sc = device_get_softc(dev); 217 218 sc->dev = dev; 219 220 sc->handle = acpi_get_handle(dev); 221 if (sc->handle == NULL) 222 return (ENXIO); 223 224 sc->constraints_populated = false; 225 sc->constraint_count = 0; 226 sc->constraints = NULL; 227 228 /* Get device constraints. We can only call this once so do this now. */ 229 acpi_spmc_get_constraints(dev); 230 231 sc->eh_suspend = EVENTHANDLER_REGISTER(acpi_post_dev_suspend, 232 acpi_spmc_suspend, dev, 0); 233 sc->eh_resume = EVENTHANDLER_REGISTER(acpi_pre_dev_resume, 234 acpi_spmc_resume, dev, 0); 235 236 return (0); 237 } 238 239 static int 240 acpi_spmc_detach(device_t dev) 241 { 242 struct acpi_spmc_softc *sc = device_get_softc(dev); 243 244 EVENTHANDLER_DEREGISTER(acpi_post_dev_suspend, sc->eh_suspend); 245 EVENTHANDLER_DEREGISTER(acpi_pre_dev_resume, sc->eh_resume); 246 247 acpi_spmc_free_constraints(device_get_softc(dev)); 248 return (0); 249 } 250 251 static void 252 acpi_spmc_check_dsm_set(struct acpi_spmc_softc *sc, ACPI_HANDLE handle, 253 struct dsm_set *dsm_set) 254 { 255 const uint64_t dsms_supported = acpi_DSMQuery(handle, 256 (uint8_t *)&dsm_set->uuid, dsm_set->revision); 257 258 /* 259 * Check if DSM set supported at all. We do this by checking the 260 * existence of "enum functions". 261 */ 262 if ((dsms_supported & 1) == 0) 263 return; 264 if ((dsms_supported & dsm_set->dsms_expected) 265 != dsm_set->dsms_expected) { 266 device_printf(sc->dev, "DSM set %s does not support expected " 267 "DSMs (%#" PRIx64 " vs %#" PRIx64 "). " 268 "Some methods may fail.\n", 269 dsm_set->name, dsms_supported, dsm_set->dsms_expected); 270 } 271 sc->dsm_sets |= dsm_set->flag; 272 } 273 274 static void 275 acpi_spmc_free_constraints(struct acpi_spmc_softc *sc) 276 { 277 if (sc->constraints == NULL) 278 return; 279 280 for (size_t i = 0; i < sc->constraint_count; i++) { 281 if (sc->constraints[i].name != NULL) 282 free(sc->constraints[i].name, M_TEMP); 283 } 284 285 free(sc->constraints, M_TEMP); 286 sc->constraints = NULL; 287 } 288 289 static int 290 acpi_spmc_get_constraints_spec(struct acpi_spmc_softc *sc, ACPI_OBJECT *object) 291 { 292 struct acpi_spmc_constraint *constraint; 293 int revision; 294 ACPI_OBJECT *constraint_obj; 295 ACPI_OBJECT *name_obj; 296 ACPI_OBJECT *detail; 297 ACPI_OBJECT *constraint_package; 298 299 KASSERT(sc->constraints_populated == false, 300 ("constraints already populated")); 301 302 sc->constraint_count = object->Package.Count; 303 sc->constraints = malloc(sc->constraint_count * sizeof *sc->constraints, 304 M_TEMP, M_WAITOK | M_ZERO); 305 306 /* 307 * The value of sc->constraint_count can change during the loop, so 308 * iterate until object->Package.Count so we actually go over all 309 * elements in the package. 310 */ 311 for (size_t i = 0; i < object->Package.Count; i++) { 312 constraint_obj = &object->Package.Elements[i]; 313 constraint = &sc->constraints[i]; 314 315 constraint->enabled = 316 constraint_obj->Package.Elements[1].Integer.Value; 317 318 name_obj = &constraint_obj->Package.Elements[0]; 319 constraint->name = strdup(name_obj->String.Pointer, M_TEMP); 320 if (constraint->name == NULL) { 321 acpi_spmc_free_constraints(sc); 322 return (ENOMEM); 323 } 324 325 detail = &constraint_obj->Package.Elements[2]; 326 /* 327 * The first element in the device constraint detail package is 328 * the revision, and should always be zero. 329 */ 330 revision = detail->Package.Elements[0].Integer.Value; 331 if (revision != 0) { 332 device_printf(sc->dev, "Unknown revision %d for " 333 "device constraint detail package\n", revision); 334 sc->constraint_count--; 335 continue; 336 } 337 338 constraint_package = &detail->Package.Elements[1]; 339 340 constraint->lpi_uid = 341 constraint_package->Package.Elements[0].Integer.Value; 342 constraint->min_d_state = 343 constraint_package->Package.Elements[1].Integer.Value; 344 constraint->min_dev_specific_state = 345 constraint_package->Package.Elements[2].Integer.Value; 346 } 347 348 sc->constraints_populated = true; 349 return (0); 350 } 351 352 static int 353 acpi_spmc_get_constraints_amd(struct acpi_spmc_softc *sc, ACPI_OBJECT *object) 354 { 355 size_t constraint_count; 356 ACPI_OBJECT *constraint_obj; 357 ACPI_OBJECT *constraints; 358 struct acpi_spmc_constraint *constraint; 359 ACPI_OBJECT *name_obj; 360 361 KASSERT(sc->constraints_populated == false, 362 ("constraints already populated")); 363 364 /* 365 * First element in the package is unknown. 366 * Second element is the number of device constraints. 367 * Third element is the list of device constraints itself. 368 */ 369 constraint_count = object->Package.Elements[1].Integer.Value; 370 constraints = &object->Package.Elements[2]; 371 372 if (constraints->Package.Count != constraint_count) { 373 device_printf(sc->dev, "constraint count mismatch (%d to %zu)\n", 374 constraints->Package.Count, constraint_count); 375 return (ENXIO); 376 } 377 378 sc->constraint_count = constraint_count; 379 sc->constraints = malloc(constraint_count * sizeof *sc->constraints, 380 M_TEMP, M_WAITOK | M_ZERO); 381 382 for (size_t i = 0; i < constraint_count; i++) { 383 /* Parse the constraint package. */ 384 constraint_obj = &constraints->Package.Elements[i]; 385 if (constraint_obj->Package.Count != 4) { 386 device_printf(sc->dev, "constraint %zu has %d elements\n", 387 i, constraint_obj->Package.Count); 388 acpi_spmc_free_constraints(sc); 389 return (ENXIO); 390 } 391 392 constraint = &sc->constraints[i]; 393 constraint->enabled = 394 constraint_obj->Package.Elements[0].Integer.Value; 395 396 name_obj = &constraint_obj->Package.Elements[1]; 397 constraint->name = strdup(name_obj->String.Pointer, M_TEMP); 398 if (constraint->name == NULL) { 399 acpi_spmc_free_constraints(sc); 400 return (ENOMEM); 401 } 402 403 constraint->function_states = 404 constraint_obj->Package.Elements[2].Integer.Value; 405 constraint->min_d_state = 406 constraint_obj->Package.Elements[3].Integer.Value; 407 } 408 409 sc->constraints_populated = true; 410 return (0); 411 } 412 413 static int 414 acpi_spmc_get_constraints(device_t dev) 415 { 416 struct acpi_spmc_softc *sc; 417 union dsm_index dsm_index; 418 struct dsm_set *dsm_set; 419 ACPI_STATUS status; 420 ACPI_BUFFER result; 421 ACPI_OBJECT *object; 422 bool is_amd; 423 int rv; 424 struct acpi_spmc_constraint *constraint; 425 426 sc = device_get_softc(dev); 427 if (sc->constraints_populated) 428 return (0); 429 430 /* The Microsoft DSM set doesn't have this DSM. */ 431 is_amd = (sc->dsm_sets & DSM_SET_AMD) != 0; 432 if (is_amd) { 433 dsm_set = &amd_dsm_set; 434 dsm_index.amd = AMD_DSM_GET_DEVICE_CONSTRAINTS; 435 } else { 436 dsm_set = &intel_dsm_set; 437 dsm_index.regular = DSM_GET_DEVICE_CONSTRAINTS; 438 } 439 440 /* XXX It seems like this DSM fails if called more than once. */ 441 status = acpi_EvaluateDSMTyped(sc->handle, (uint8_t *)&dsm_set->uuid, 442 dsm_set->revision, dsm_index.i, NULL, &result, 443 ACPI_TYPE_PACKAGE); 444 if (ACPI_FAILURE(status)) { 445 device_printf(dev, "%s failed to call %s DSM %d (rev %d)\n", 446 __func__, dsm_set->name, dsm_index.i, dsm_set->revision); 447 return (ENXIO); 448 } 449 450 object = (ACPI_OBJECT *)result.Pointer; 451 if (is_amd) 452 rv = acpi_spmc_get_constraints_amd(sc, object); 453 else 454 rv = acpi_spmc_get_constraints_spec(sc, object); 455 AcpiOsFree(object); 456 if (rv != 0) 457 return (rv); 458 459 /* Get handles for each constraint device. */ 460 for (size_t i = 0; i < sc->constraint_count; i++) { 461 constraint = &sc->constraints[i]; 462 463 status = acpi_GetHandleInScope(sc->handle, 464 __DECONST(char *, constraint->name), &constraint->handle); 465 if (ACPI_FAILURE(status)) { 466 device_printf(dev, "failed to get handle for %s\n", 467 constraint->name); 468 constraint->handle = NULL; 469 } 470 } 471 return (0); 472 } 473 474 static void 475 acpi_spmc_check_constraints(struct acpi_spmc_softc *sc) 476 { 477 bool violation = false; 478 479 KASSERT(sc->constraints_populated, ("constraints not populated")); 480 for (size_t i = 0; i < sc->constraint_count; i++) { 481 struct acpi_spmc_constraint *constraint = &sc->constraints[i]; 482 483 if (!constraint->enabled) 484 continue; 485 if (constraint->handle == NULL) 486 continue; 487 488 ACPI_STATUS status = acpi_GetHandleInScope(sc->handle, 489 __DECONST(char *, constraint->name), &constraint->handle); 490 if (ACPI_FAILURE(status)) { 491 device_printf(sc->dev, "failed to get handle for %s\n", 492 constraint->name); 493 constraint->handle = NULL; 494 } 495 if (constraint->handle == NULL) 496 continue; 497 498 #ifdef notyet 499 int d_state; 500 if (ACPI_FAILURE(acpi_pwr_get_state(constraint->handle, &d_state))) 501 continue; 502 if (d_state < constraint->min_d_state) { 503 device_printf(sc->dev, "constraint for device %s" 504 " violated (minimum D-state required was %s, actual" 505 " D-state is %s), might fail to enter LPI state\n", 506 constraint->name, 507 acpi_d_state_to_str(constraint->min_d_state), 508 acpi_d_state_to_str(d_state)); 509 violation = true; 510 } 511 #endif 512 } 513 if (!violation) 514 device_printf(sc->dev, 515 "all device power constraints respected!\n"); 516 } 517 518 static void 519 acpi_spmc_run_dsm(device_t dev, struct dsm_set *dsm_set, int index) 520 { 521 struct acpi_spmc_softc *sc; 522 ACPI_STATUS status; 523 ACPI_BUFFER result; 524 525 sc = device_get_softc(dev); 526 527 status = acpi_EvaluateDSMTyped(sc->handle, (uint8_t *)&dsm_set->uuid, 528 dsm_set->revision, index, NULL, &result, ACPI_TYPE_ANY); 529 530 if (ACPI_FAILURE(status)) { 531 device_printf(dev, "%s failed to call %s DSM %d (rev %d)\n", 532 __func__, dsm_set->name, index, dsm_set->revision); 533 return; 534 } 535 536 AcpiOsFree(result.Pointer); 537 } 538 539 /* 540 * Try running the DSMs from all the DSM sets we have, as them failing costs us 541 * nothing, and it seems like on AMD platforms, both the AMD entry and Microsoft 542 * "modern" DSM's are required for it to enter modern standby. 543 * 544 * This is what Linux does too. 545 */ 546 static void 547 acpi_spmc_display_off_notif(device_t dev) 548 { 549 struct acpi_spmc_softc *sc = device_get_softc(dev); 550 551 if ((sc->dsm_sets & DSM_SET_INTEL) != 0) 552 acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_DISPLAY_OFF_NOTIF); 553 if ((sc->dsm_sets & DSM_SET_MS) != 0) 554 acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_DISPLAY_OFF_NOTIF); 555 if ((sc->dsm_sets & DSM_SET_AMD) != 0) 556 acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_DISPLAY_OFF_NOTIF); 557 } 558 559 static void 560 acpi_spmc_display_on_notif(device_t dev) 561 { 562 struct acpi_spmc_softc *sc = device_get_softc(dev); 563 564 if ((sc->dsm_sets & DSM_SET_INTEL) != 0) 565 acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_DISPLAY_ON_NOTIF); 566 if ((sc->dsm_sets & DSM_SET_MS) != 0) 567 acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_DISPLAY_ON_NOTIF); 568 if ((sc->dsm_sets & DSM_SET_AMD) != 0) 569 acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_DISPLAY_ON_NOTIF); 570 } 571 572 static void 573 acpi_spmc_entry_notif(device_t dev) 574 { 575 struct acpi_spmc_softc *sc = device_get_softc(dev); 576 577 acpi_spmc_check_constraints(sc); 578 579 if ((sc->dsm_sets & DSM_SET_AMD) != 0) 580 acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_ENTRY_NOTIF); 581 if ((sc->dsm_sets & DSM_SET_MS) != 0) { 582 acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_MODERN_ENTRY_NOTIF); 583 acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_ENTRY_NOTIF); 584 } 585 if ((sc->dsm_sets & DSM_SET_INTEL) != 0) 586 acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_ENTRY_NOTIF); 587 } 588 589 static void 590 acpi_spmc_exit_notif(device_t dev) 591 { 592 struct acpi_spmc_softc *sc = device_get_softc(dev); 593 594 if ((sc->dsm_sets & DSM_SET_INTEL) != 0) 595 acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_EXIT_NOTIF); 596 if ((sc->dsm_sets & DSM_SET_AMD) != 0) 597 acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_EXIT_NOTIF); 598 if ((sc->dsm_sets & DSM_SET_MS) != 0) { 599 acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_EXIT_NOTIF); 600 acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_MODERN_EXIT_NOTIF); 601 } 602 } 603 604 static void 605 acpi_spmc_suspend(device_t dev, enum power_stype stype) 606 { 607 if (stype != POWER_STYPE_SUSPEND_TO_IDLE) 608 return; 609 610 acpi_spmc_display_off_notif(dev); 611 acpi_spmc_entry_notif(dev); 612 } 613 614 static void 615 acpi_spmc_resume(device_t dev, enum power_stype stype) 616 { 617 if (stype != POWER_STYPE_SUSPEND_TO_IDLE) 618 return; 619 620 acpi_spmc_exit_notif(dev); 621 acpi_spmc_display_on_notif(dev); 622 } 623 624 static device_method_t acpi_spmc_methods[] = { 625 DEVMETHOD(device_probe, acpi_spmc_probe), 626 DEVMETHOD(device_attach, acpi_spmc_attach), 627 DEVMETHOD(device_detach, acpi_spmc_detach), 628 DEVMETHOD_END 629 }; 630 631 static driver_t acpi_spmc_driver = { 632 "acpi_spmc", 633 acpi_spmc_methods, 634 sizeof(struct acpi_spmc_softc), 635 }; 636 637 DRIVER_MODULE_ORDERED(acpi_spmc, acpi, acpi_spmc_driver, NULL, NULL, SI_ORDER_ANY); 638 MODULE_DEPEND(acpi_spmc, acpi, 1, 1, 1); 639