Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/dev/acpica/acpi_spmc.c
96295 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/kernel.h>
12
#include <sys/module.h>
13
#include <sys/bus.h>
14
#include <sys/malloc.h>
15
#include <sys/uuid.h>
16
#include <sys/kdb.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
bool constraints_populated;
162
size_t constraint_count;
163
struct acpi_spmc_constraint *constraints;
164
};
165
166
static void acpi_spmc_check_dsm_set(struct acpi_spmc_softc *sc,
167
ACPI_HANDLE handle, struct dsm_set *dsm_set);
168
static int acpi_spmc_get_constraints(device_t dev);
169
static void acpi_spmc_free_constraints(struct acpi_spmc_softc *sc);
170
171
static int
172
acpi_spmc_probe(device_t dev)
173
{
174
char *name;
175
ACPI_HANDLE handle;
176
struct acpi_spmc_softc *sc;
177
178
/* Check that this is an enabled device. */
179
if (acpi_get_type(dev) != ACPI_TYPE_DEVICE || acpi_disabled("spmc"))
180
return (ENXIO);
181
182
if (ACPI_ID_PROBE(device_get_parent(dev), dev, spmc_ids, &name) > 0)
183
return (ENXIO);
184
185
handle = acpi_get_handle(dev);
186
if (handle == NULL)
187
return (ENXIO);
188
189
sc = device_get_softc(dev);
190
191
/* Check which sets of DSM's are supported. */
192
sc->dsm_sets = 0;
193
194
acpi_spmc_check_dsm_set(sc, handle, &intel_dsm_set);
195
acpi_spmc_check_dsm_set(sc, handle, &ms_dsm_set);
196
acpi_spmc_check_dsm_set(sc, handle, &amd_dsm_set);
197
198
if (sc->dsm_sets == 0)
199
return (ENXIO);
200
201
device_set_descf(dev, "Low Power S0 Idle (DSM sets 0x%x)",
202
sc->dsm_sets);
203
204
return (0);
205
}
206
207
static int
208
acpi_spmc_attach(device_t dev)
209
{
210
struct acpi_spmc_softc *sc;
211
212
sc = device_get_softc(dev);
213
sc->dev = dev;
214
215
sc->handle = acpi_get_handle(dev);
216
if (sc->handle == NULL)
217
return (ENXIO);
218
219
sc->constraints_populated = false;
220
sc->constraint_count = 0;
221
sc->constraints = NULL;
222
223
/* Get device constraints. We can only call this once so do this now. */
224
acpi_spmc_get_constraints(sc->dev);
225
226
return (0);
227
}
228
229
static int
230
acpi_spmc_detach(device_t dev)
231
{
232
acpi_spmc_free_constraints(device_get_softc(dev));
233
return (0);
234
}
235
236
static void
237
acpi_spmc_check_dsm_set(struct acpi_spmc_softc *sc, ACPI_HANDLE handle,
238
struct dsm_set *dsm_set)
239
{
240
const uint64_t dsms_supported = acpi_DSMQuery(handle,
241
(uint8_t *)&dsm_set->uuid, dsm_set->revision);
242
243
/*
244
* Check if DSM set supported at all. We do this by checking the
245
* existence of "enum functions".
246
*/
247
if ((dsms_supported & 1) == 0)
248
return;
249
if ((dsms_supported & dsm_set->dsms_expected)
250
!= dsm_set->dsms_expected) {
251
device_printf(sc->dev, "DSM set %s does not support expected "
252
"DSMs (%#" PRIx64 " vs %#" PRIx64 "). "
253
"Some methods may fail.\n",
254
dsm_set->name, dsms_supported, dsm_set->dsms_expected);
255
}
256
sc->dsm_sets |= dsm_set->flag;
257
}
258
259
static void
260
acpi_spmc_free_constraints(struct acpi_spmc_softc *sc)
261
{
262
if (sc->constraints == NULL)
263
return;
264
265
for (size_t i = 0; i < sc->constraint_count; i++) {
266
if (sc->constraints[i].name != NULL)
267
free(sc->constraints[i].name, M_TEMP);
268
}
269
270
free(sc->constraints, M_TEMP);
271
sc->constraints = NULL;
272
}
273
274
static int
275
acpi_spmc_get_constraints_spec(struct acpi_spmc_softc *sc, ACPI_OBJECT *object)
276
{
277
struct acpi_spmc_constraint *constraint;
278
int revision;
279
ACPI_OBJECT *constraint_obj;
280
ACPI_OBJECT *name_obj;
281
ACPI_OBJECT *detail;
282
ACPI_OBJECT *constraint_package;
283
284
KASSERT(sc->constraints_populated == false,
285
("constraints already populated"));
286
287
sc->constraint_count = object->Package.Count;
288
sc->constraints = malloc(sc->constraint_count * sizeof *sc->constraints,
289
M_TEMP, M_WAITOK | M_ZERO);
290
291
/*
292
* The value of sc->constraint_count can change during the loop, so
293
* iterate until object->Package.Count so we actually go over all
294
* elements in the package.
295
*/
296
for (size_t i = 0; i < object->Package.Count; i++) {
297
constraint_obj = &object->Package.Elements[i];
298
constraint = &sc->constraints[i];
299
300
constraint->enabled =
301
constraint_obj->Package.Elements[1].Integer.Value;
302
303
name_obj = &constraint_obj->Package.Elements[0];
304
constraint->name = strdup(name_obj->String.Pointer, M_TEMP);
305
if (constraint->name == NULL) {
306
acpi_spmc_free_constraints(sc);
307
return (ENOMEM);
308
}
309
310
/*
311
* The first element in the device constraint detail package is
312
* the revision, and should always be zero.
313
*/
314
revision = constraint_obj->Package.Elements[0].Integer.Value;
315
if (revision != 0) {
316
device_printf(sc->dev, "Unknown revision %d for "
317
"device constraint detail package\n", revision);
318
sc->constraint_count--;
319
continue;
320
}
321
322
detail = &constraint_obj->Package.Elements[2];
323
constraint_package = &detail->Package.Elements[1];
324
325
constraint->lpi_uid =
326
constraint_package->Package.Elements[0].Integer.Value;
327
constraint->min_d_state =
328
constraint_package->Package.Elements[1].Integer.Value;
329
constraint->min_dev_specific_state =
330
constraint_package->Package.Elements[2].Integer.Value;
331
}
332
333
sc->constraints_populated = true;
334
return (0);
335
}
336
337
static int
338
acpi_spmc_get_constraints_amd(struct acpi_spmc_softc *sc, ACPI_OBJECT *object)
339
{
340
size_t constraint_count;
341
ACPI_OBJECT *constraint_obj;
342
ACPI_OBJECT *constraints;
343
struct acpi_spmc_constraint *constraint;
344
ACPI_OBJECT *name_obj;
345
346
KASSERT(sc->constraints_populated == false,
347
("constraints already populated"));
348
349
/*
350
* First element in the package is unknown.
351
* Second element is the number of device constraints.
352
* Third element is the list of device constraints itself.
353
*/
354
constraint_count = object->Package.Elements[1].Integer.Value;
355
constraints = &object->Package.Elements[2];
356
357
if (constraints->Package.Count != constraint_count) {
358
device_printf(sc->dev, "constraint count mismatch (%d to %zu)\n",
359
constraints->Package.Count, constraint_count);
360
return (ENXIO);
361
}
362
363
sc->constraint_count = constraint_count;
364
sc->constraints = malloc(constraint_count * sizeof *sc->constraints,
365
M_TEMP, M_WAITOK | M_ZERO);
366
367
for (size_t i = 0; i < constraint_count; i++) {
368
/* Parse the constraint package. */
369
constraint_obj = &constraints->Package.Elements[i];
370
if (constraint_obj->Package.Count != 4) {
371
device_printf(sc->dev, "constraint %zu has %d elements\n",
372
i, constraint_obj->Package.Count);
373
acpi_spmc_free_constraints(sc);
374
return (ENXIO);
375
}
376
377
constraint = &sc->constraints[i];
378
constraint->enabled =
379
constraint_obj->Package.Elements[0].Integer.Value;
380
381
name_obj = &constraint_obj->Package.Elements[1];
382
constraint->name = strdup(name_obj->String.Pointer, M_TEMP);
383
if (constraint->name == NULL) {
384
acpi_spmc_free_constraints(sc);
385
return (ENOMEM);
386
}
387
388
constraint->function_states =
389
constraint_obj->Package.Elements[2].Integer.Value;
390
constraint->min_d_state =
391
constraint_obj->Package.Elements[3].Integer.Value;
392
}
393
394
sc->constraints_populated = true;
395
return (0);
396
}
397
398
static int
399
acpi_spmc_get_constraints(device_t dev)
400
{
401
struct acpi_spmc_softc *sc;
402
union dsm_index dsm_index;
403
struct dsm_set *dsm_set;
404
ACPI_STATUS status;
405
ACPI_BUFFER result;
406
ACPI_OBJECT *object;
407
bool is_amd;
408
int rv;
409
struct acpi_spmc_constraint *constraint;
410
411
sc = device_get_softc(dev);
412
if (sc->constraints_populated)
413
return (0);
414
415
/* The Microsoft DSM set doesn't have this DSM. */
416
is_amd = (sc->dsm_sets & DSM_SET_AMD) != 0;
417
if (is_amd) {
418
dsm_set = &amd_dsm_set;
419
dsm_index.amd = AMD_DSM_GET_DEVICE_CONSTRAINTS;
420
} else {
421
dsm_set = &intel_dsm_set;
422
dsm_index.regular = DSM_GET_DEVICE_CONSTRAINTS;
423
}
424
425
/* XXX It seems like this DSM fails if called more than once. */
426
status = acpi_EvaluateDSMTyped(sc->handle, (uint8_t *)&dsm_set->uuid,
427
dsm_set->revision, dsm_index.i, NULL, &result,
428
ACPI_TYPE_PACKAGE);
429
if (ACPI_FAILURE(status)) {
430
device_printf(dev, "%s failed to call %s DSM %d (rev %d)\n",
431
__func__, dsm_set->name, dsm_index.i, dsm_set->revision);
432
return (ENXIO);
433
}
434
435
object = (ACPI_OBJECT *)result.Pointer;
436
if (is_amd)
437
rv = acpi_spmc_get_constraints_amd(sc, object);
438
else
439
rv = acpi_spmc_get_constraints_spec(sc, object);
440
AcpiOsFree(object);
441
if (rv != 0)
442
return (rv);
443
444
/* Get handles for each constraint device. */
445
for (size_t i = 0; i < sc->constraint_count; i++) {
446
constraint = &sc->constraints[i];
447
448
status = acpi_GetHandleInScope(sc->handle,
449
__DECONST(char *, constraint->name), &constraint->handle);
450
if (ACPI_FAILURE(status)) {
451
device_printf(dev, "failed to get handle for %s\n",
452
constraint->name);
453
constraint->handle = NULL;
454
}
455
}
456
return (0);
457
}
458
459
static void
460
acpi_spmc_check_constraints(struct acpi_spmc_softc *sc)
461
{
462
bool violation = false;
463
464
KASSERT(sc->constraints_populated, ("constraints not populated"));
465
for (size_t i = 0; i < sc->constraint_count; i++) {
466
struct acpi_spmc_constraint *constraint = &sc->constraints[i];
467
468
if (!constraint->enabled)
469
continue;
470
if (constraint->handle == NULL)
471
continue;
472
473
ACPI_STATUS status = acpi_GetHandleInScope(sc->handle,
474
__DECONST(char *, constraint->name), &constraint->handle);
475
if (ACPI_FAILURE(status)) {
476
device_printf(sc->dev, "failed to get handle for %s\n",
477
constraint->name);
478
constraint->handle = NULL;
479
}
480
if (constraint->handle == NULL)
481
continue;
482
483
#ifdef notyet
484
int d_state;
485
if (ACPI_FAILURE(acpi_pwr_get_state(constraint->handle, &d_state)))
486
continue;
487
if (d_state < constraint->min_d_state) {
488
device_printf(sc->dev, "constraint for device %s"
489
" violated (minimum D-state required was %s, actual"
490
" D-state is %s), might fail to enter LPI state\n",
491
constraint->name,
492
acpi_d_state_to_str(constraint->min_d_state),
493
acpi_d_state_to_str(d_state));
494
violation = true;
495
}
496
#endif
497
}
498
if (!violation)
499
device_printf(sc->dev,
500
"all device power constraints respected!\n");
501
}
502
503
static void
504
acpi_spmc_run_dsm(device_t dev, struct dsm_set *dsm_set, int index)
505
{
506
struct acpi_spmc_softc *sc;
507
ACPI_STATUS status;
508
ACPI_BUFFER result;
509
510
sc = device_get_softc(dev);
511
512
status = acpi_EvaluateDSMTyped(sc->handle, (uint8_t *)&dsm_set->uuid,
513
dsm_set->revision, index, NULL, &result, ACPI_TYPE_ANY);
514
515
if (ACPI_FAILURE(status)) {
516
device_printf(dev, "%s failed to call %s DSM %d (rev %d)\n",
517
__func__, dsm_set->name, index, dsm_set->revision);
518
return;
519
}
520
521
AcpiOsFree(result.Pointer);
522
}
523
524
/*
525
* Try running the DSMs from all the DSM sets we have, as them failing costs us
526
* nothing, and it seems like on AMD platforms, both the AMD entry and Microsoft
527
* "modern" DSM's are required for it to enter modern standby.
528
*
529
* This is what Linux does too.
530
*/
531
static void
532
acpi_spmc_display_off_notif(device_t dev)
533
{
534
struct acpi_spmc_softc *sc = device_get_softc(dev);
535
536
if ((sc->dsm_sets & DSM_SET_INTEL) != 0)
537
acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_DISPLAY_OFF_NOTIF);
538
if ((sc->dsm_sets & DSM_SET_MS) != 0)
539
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_DISPLAY_OFF_NOTIF);
540
if ((sc->dsm_sets & DSM_SET_AMD) != 0)
541
acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_DISPLAY_OFF_NOTIF);
542
}
543
544
static void
545
acpi_spmc_display_on_notif(device_t dev)
546
{
547
struct acpi_spmc_softc *sc = device_get_softc(dev);
548
549
if ((sc->dsm_sets & DSM_SET_INTEL) != 0)
550
acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_DISPLAY_ON_NOTIF);
551
if ((sc->dsm_sets & DSM_SET_MS) != 0)
552
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_DISPLAY_ON_NOTIF);
553
if ((sc->dsm_sets & DSM_SET_AMD) != 0)
554
acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_DISPLAY_ON_NOTIF);
555
}
556
557
static void
558
acpi_spmc_entry_notif(device_t dev)
559
{
560
struct acpi_spmc_softc *sc = device_get_softc(dev);
561
562
acpi_spmc_check_constraints(sc);
563
564
if ((sc->dsm_sets & DSM_SET_AMD) != 0)
565
acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_ENTRY_NOTIF);
566
if ((sc->dsm_sets & DSM_SET_MS) != 0) {
567
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_MODERN_ENTRY_NOTIF);
568
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_ENTRY_NOTIF);
569
}
570
if ((sc->dsm_sets & DSM_SET_INTEL) != 0)
571
acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_ENTRY_NOTIF);
572
}
573
574
static void
575
acpi_spmc_exit_notif(device_t dev)
576
{
577
struct acpi_spmc_softc *sc = device_get_softc(dev);
578
579
if ((sc->dsm_sets & DSM_SET_INTEL) != 0)
580
acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_EXIT_NOTIF);
581
if ((sc->dsm_sets & DSM_SET_AMD) != 0)
582
acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_EXIT_NOTIF);
583
if ((sc->dsm_sets & DSM_SET_MS) != 0) {
584
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_EXIT_NOTIF);
585
acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_MODERN_EXIT_NOTIF);
586
}
587
}
588
589
static int
590
acpi_spmc_suspend(device_t dev)
591
{
592
acpi_spmc_display_off_notif(dev);
593
acpi_spmc_entry_notif(dev);
594
595
return (0);
596
}
597
598
static int
599
acpi_spmc_resume(device_t dev)
600
{
601
acpi_spmc_exit_notif(dev);
602
acpi_spmc_display_on_notif(dev);
603
604
return (0);
605
}
606
607
static device_method_t acpi_spmc_methods[] = {
608
DEVMETHOD(device_probe, acpi_spmc_probe),
609
DEVMETHOD(device_attach, acpi_spmc_attach),
610
DEVMETHOD(device_detach, acpi_spmc_detach),
611
DEVMETHOD_END
612
};
613
614
static driver_t acpi_spmc_driver = {
615
"acpi_spmc",
616
acpi_spmc_methods,
617
sizeof(struct acpi_spmc_softc),
618
};
619
620
DRIVER_MODULE_ORDERED(acpi_spmc, acpi, acpi_spmc_driver, NULL, NULL, SI_ORDER_ANY);
621
MODULE_DEPEND(acpi_spmc, acpi, 1, 1, 1);
622
623