Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/dev/acpica/acpi_spmc.c
104817 views
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 <[email protected]>
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
/*
326
* The first element in the device constraint detail package is
327
* the revision, and should always be zero.
328
*/
329
revision = constraint_obj->Package.Elements[0].Integer.Value;
330
if (revision != 0) {
331
device_printf(sc->dev, "Unknown revision %d for "
332
"device constraint detail package\n", revision);
333
sc->constraint_count--;
334
continue;
335
}
336
337
detail = &constraint_obj->Package.Elements[2];
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
640