Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/dev/amdsmu/amdsmu.c
39535 views
1
/*
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 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/kernel.h>
13
#include <sys/module.h>
14
#include <sys/rman.h>
15
#include <sys/sysctl.h>
16
17
#include <dev/pci/pcivar.h>
18
#include <dev/amdsmu/amdsmu.h>
19
20
static bool
21
amdsmu_match(device_t dev, const struct amdsmu_product **product_out)
22
{
23
const uint16_t vendorid = pci_get_vendor(dev);
24
const uint16_t deviceid = pci_get_device(dev);
25
26
for (size_t i = 0; i < nitems(amdsmu_products); i++) {
27
const struct amdsmu_product *prod = &amdsmu_products[i];
28
29
if (vendorid == prod->amdsmu_vendorid &&
30
deviceid == prod->amdsmu_deviceid) {
31
if (product_out != NULL)
32
*product_out = prod;
33
return (true);
34
}
35
}
36
return (false);
37
}
38
39
static void
40
amdsmu_identify(driver_t *driver, device_t parent)
41
{
42
if (device_find_child(parent, "amdsmu", -1) != NULL)
43
return;
44
45
if (amdsmu_match(parent, NULL)) {
46
if (device_add_child(parent, "amdsmu", -1) == NULL)
47
device_printf(parent, "add amdsmu child failed\n");
48
}
49
}
50
51
static int
52
amdsmu_probe(device_t dev)
53
{
54
if (resource_disabled("amdsmu", 0))
55
return (ENXIO);
56
if (!amdsmu_match(device_get_parent(dev), NULL))
57
return (ENXIO);
58
device_set_descf(dev, "AMD System Management Unit");
59
60
return (BUS_PROBE_GENERIC);
61
}
62
63
static enum amdsmu_res
64
amdsmu_wait_res(device_t dev)
65
{
66
struct amdsmu_softc *sc = device_get_softc(dev);
67
enum amdsmu_res res;
68
69
/*
70
* The SMU has a response ready for us when the response register is
71
* set. Otherwise, we must wait.
72
*/
73
for (size_t i = 0; i < SMU_RES_READ_MAX; i++) {
74
res = amdsmu_read4(sc, SMU_REG_RESPONSE);
75
if (res != SMU_RES_WAIT)
76
return (res);
77
pause_sbt("amdsmu", ustosbt(SMU_RES_READ_PERIOD_US), 0,
78
C_HARDCLOCK);
79
}
80
device_printf(dev, "timed out waiting for response from SMU\n");
81
return (SMU_RES_WAIT);
82
}
83
84
static int
85
amdsmu_cmd(device_t dev, enum amdsmu_msg msg, uint32_t arg, uint32_t *ret)
86
{
87
struct amdsmu_softc *sc = device_get_softc(dev);
88
enum amdsmu_res res;
89
90
/* Wait for SMU to be ready. */
91
if (amdsmu_wait_res(dev) == SMU_RES_WAIT)
92
return (ETIMEDOUT);
93
94
/* Clear previous response. */
95
amdsmu_write4(sc, SMU_REG_RESPONSE, SMU_RES_WAIT);
96
97
/* Write out command to registers. */
98
amdsmu_write4(sc, SMU_REG_MESSAGE, msg);
99
amdsmu_write4(sc, SMU_REG_ARGUMENT, arg);
100
101
/* Wait for SMU response and handle it. */
102
res = amdsmu_wait_res(dev);
103
104
switch (res) {
105
case SMU_RES_WAIT:
106
return (ETIMEDOUT);
107
case SMU_RES_OK:
108
if (ret != NULL)
109
*ret = amdsmu_read4(sc, SMU_REG_ARGUMENT);
110
return (0);
111
case SMU_RES_REJECT_BUSY:
112
device_printf(dev, "SMU is busy\n");
113
return (EBUSY);
114
case SMU_RES_REJECT_PREREQ:
115
case SMU_RES_UNKNOWN:
116
case SMU_RES_FAILED:
117
device_printf(dev, "SMU error: %02x\n", res);
118
return (EIO);
119
}
120
121
return (EINVAL);
122
}
123
124
static int
125
amdsmu_get_vers(device_t dev)
126
{
127
int err;
128
uint32_t smu_vers;
129
struct amdsmu_softc *sc = device_get_softc(dev);
130
131
err = amdsmu_cmd(dev, SMU_MSG_GETSMUVERSION, 0, &smu_vers);
132
if (err != 0) {
133
device_printf(dev, "failed to get SMU version\n");
134
return (err);
135
}
136
sc->smu_program = (smu_vers >> 24) & 0xFF;
137
sc->smu_maj = (smu_vers >> 16) & 0xFF;
138
sc->smu_min = (smu_vers >> 8) & 0xFF;
139
sc->smu_rev = smu_vers & 0xFF;
140
device_printf(dev, "SMU version: %d.%d.%d (program %d)\n",
141
sc->smu_maj, sc->smu_min, sc->smu_rev, sc->smu_program);
142
143
return (0);
144
}
145
146
static int
147
amdsmu_get_ip_blocks(device_t dev)
148
{
149
struct amdsmu_softc *sc = device_get_softc(dev);
150
const uint16_t deviceid = pci_get_device(dev);
151
int err;
152
struct amdsmu_metrics *m = &sc->metrics;
153
bool active;
154
char sysctl_descr[32];
155
156
/* Get IP block count. */
157
switch (deviceid) {
158
case PCI_DEVICEID_AMD_REMBRANDT_ROOT:
159
sc->ip_block_count = 12;
160
break;
161
case PCI_DEVICEID_AMD_PHOENIX_ROOT:
162
sc->ip_block_count = 21;
163
break;
164
/* TODO How many IP blocks does Strix Point (and the others) have? */
165
case PCI_DEVICEID_AMD_STRIX_POINT_ROOT:
166
default:
167
sc->ip_block_count = nitems(amdsmu_ip_blocks_names);
168
}
169
KASSERT(sc->ip_block_count <= nitems(amdsmu_ip_blocks_names),
170
("too many IP blocks for array"));
171
172
/* Get and print out IP blocks. */
173
err = amdsmu_cmd(dev, SMU_MSG_GET_SUP_CONSTRAINTS, 0,
174
&sc->active_ip_blocks);
175
if (err != 0) {
176
device_printf(dev, "failed to get IP blocks\n");
177
return (err);
178
}
179
device_printf(dev, "Active IP blocks: ");
180
for (size_t i = 0; i < sc->ip_block_count; i++) {
181
active = (sc->active_ip_blocks & (1 << i)) != 0;
182
sc->ip_blocks_active[i] = active;
183
if (!active)
184
continue;
185
printf("%s%s", amdsmu_ip_blocks_names[i],
186
i + 1 < sc->ip_block_count ? " " : "\n");
187
}
188
189
/* Create a sysctl node for IP blocks. */
190
sc->ip_blocks_sysctlnode = SYSCTL_ADD_NODE(sc->sysctlctx,
191
SYSCTL_CHILDREN(sc->sysctlnode), OID_AUTO, "ip_blocks",
192
CTLFLAG_RD, NULL, "SMU metrics");
193
if (sc->ip_blocks_sysctlnode == NULL) {
194
device_printf(dev, "could not add sysctl node for IP blocks\n");
195
return (ENOMEM);
196
}
197
198
/* Create a sysctl node for each IP block. */
199
for (size_t i = 0; i < sc->ip_block_count; i++) {
200
/* Create the sysctl node itself for the IP block. */
201
snprintf(sysctl_descr, sizeof sysctl_descr,
202
"Metrics about the %s AMD IP block",
203
amdsmu_ip_blocks_names[i]);
204
sc->ip_block_sysctlnodes[i] = SYSCTL_ADD_NODE(sc->sysctlctx,
205
SYSCTL_CHILDREN(sc->ip_blocks_sysctlnode), OID_AUTO,
206
amdsmu_ip_blocks_names[i], CTLFLAG_RD, NULL, sysctl_descr);
207
if (sc->ip_block_sysctlnodes[i] == NULL) {
208
device_printf(dev,
209
"could not add sysctl node for \"%s\"\n", sysctl_descr);
210
continue;
211
}
212
/*
213
* Create sysctls for if the IP block is currently active, last
214
* active time, and total active time.
215
*/
216
SYSCTL_ADD_BOOL(sc->sysctlctx,
217
SYSCTL_CHILDREN(sc->ip_block_sysctlnodes[i]), OID_AUTO,
218
"active", CTLFLAG_RD, &sc->ip_blocks_active[i], 0,
219
"IP block is currently active");
220
SYSCTL_ADD_U64(sc->sysctlctx,
221
SYSCTL_CHILDREN(sc->ip_block_sysctlnodes[i]), OID_AUTO,
222
"last_time", CTLFLAG_RD, &m->ip_block_last_active_time[i],
223
0, "How long the IP block was active for during the last"
224
" sleep (us)");
225
#ifdef IP_BLOCK_TOTAL_ACTIVE_TIME
226
SYSCTL_ADD_U64(sc->sysctlctx,
227
SYSCTL_CHILDREN(sc->ip_block_sysctlnodes[i]), OID_AUTO,
228
"total_time", CTLFLAG_RD, &m->ip_block_total_active_time[i],
229
0, "How long the IP block was active for during sleep in"
230
" total (us)");
231
#endif
232
}
233
return (0);
234
}
235
236
static int
237
amdsmu_init_metrics(device_t dev)
238
{
239
struct amdsmu_softc *sc = device_get_softc(dev);
240
int err;
241
uint32_t metrics_addr_lo, metrics_addr_hi;
242
uint64_t metrics_addr;
243
244
/* Get physical address of logging buffer. */
245
err = amdsmu_cmd(dev, SMU_MSG_LOG_GETDRAM_ADDR_LO, 0, &metrics_addr_lo);
246
if (err != 0)
247
return (err);
248
err = amdsmu_cmd(dev, SMU_MSG_LOG_GETDRAM_ADDR_HI, 0, &metrics_addr_hi);
249
if (err != 0)
250
return (err);
251
metrics_addr = ((uint64_t) metrics_addr_hi << 32) | metrics_addr_lo;
252
253
/* Map memory of logging buffer. */
254
err = bus_space_map(sc->bus_tag, metrics_addr,
255
sizeof(struct amdsmu_metrics), 0, &sc->metrics_space);
256
if (err != 0) {
257
device_printf(dev, "could not map bus space for SMU metrics\n");
258
return (err);
259
}
260
261
/* Start logging for metrics. */
262
amdsmu_cmd(dev, SMU_MSG_LOG_RESET, 0, NULL);
263
amdsmu_cmd(dev, SMU_MSG_LOG_START, 0, NULL);
264
return (0);
265
}
266
267
static int
268
amdsmu_dump_metrics(device_t dev)
269
{
270
struct amdsmu_softc *sc = device_get_softc(dev);
271
int err;
272
273
err = amdsmu_cmd(dev, SMU_MSG_LOG_DUMP_DATA, 0, NULL);
274
if (err != 0) {
275
device_printf(dev, "failed to dump metrics\n");
276
return (err);
277
}
278
bus_space_read_region_4(sc->bus_tag, sc->metrics_space, 0,
279
(uint32_t *)&sc->metrics, sizeof(sc->metrics) / sizeof(uint32_t));
280
281
return (0);
282
}
283
284
static void
285
amdsmu_fetch_idlemask(device_t dev)
286
{
287
struct amdsmu_softc *sc = device_get_softc(dev);
288
289
sc->idlemask = amdsmu_read4(sc, SMU_REG_IDLEMASK);
290
}
291
292
static int
293
amdsmu_attach(device_t dev)
294
{
295
struct amdsmu_softc *sc = device_get_softc(dev);
296
int err;
297
uint32_t physbase_addr_lo, physbase_addr_hi;
298
uint64_t physbase_addr;
299
int rid = 0;
300
struct sysctl_oid *node;
301
302
/*
303
* Find physical base address for SMU.
304
* XXX I am a little confused about the masks here. I'm just copying
305
* what Linux does in the amd-pmc driver to get the base address.
306
*/
307
pci_write_config(dev, SMU_INDEX_ADDRESS, SMU_PHYSBASE_ADDR_LO, 4);
308
physbase_addr_lo = pci_read_config(dev, SMU_INDEX_DATA, 4) & 0xFFF00000;
309
310
pci_write_config(dev, SMU_INDEX_ADDRESS, SMU_PHYSBASE_ADDR_HI, 4);
311
physbase_addr_hi = pci_read_config(dev, SMU_INDEX_DATA, 4) & 0x0000FFFF;
312
313
physbase_addr = (uint64_t)physbase_addr_hi << 32 | physbase_addr_lo;
314
315
/* Map memory for SMU and its registers. */
316
sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
317
if (sc->res == NULL) {
318
device_printf(dev, "could not allocate resource\n");
319
return (ENXIO);
320
}
321
322
sc->bus_tag = rman_get_bustag(sc->res);
323
324
if (bus_space_map(sc->bus_tag, physbase_addr,
325
SMU_MEM_SIZE, 0, &sc->smu_space) != 0) {
326
device_printf(dev, "could not map bus space for SMU\n");
327
err = ENXIO;
328
goto err_smu_space;
329
}
330
if (bus_space_map(sc->bus_tag, physbase_addr + SMU_REG_SPACE_OFF,
331
SMU_MEM_SIZE, 0, &sc->reg_space) != 0) {
332
device_printf(dev, "could not map bus space for SMU regs\n");
333
err = ENXIO;
334
goto err_reg_space;
335
}
336
337
/* sysctl stuff. */
338
sc->sysctlctx = device_get_sysctl_ctx(dev);
339
sc->sysctlnode = device_get_sysctl_tree(dev);
340
341
/* Get version & add sysctls. */
342
if ((err = amdsmu_get_vers(dev)) != 0)
343
goto err_dump;
344
345
SYSCTL_ADD_U8(sc->sysctlctx, SYSCTL_CHILDREN(sc->sysctlnode), OID_AUTO,
346
"program", CTLFLAG_RD, &sc->smu_program, 0, "SMU program number");
347
SYSCTL_ADD_U8(sc->sysctlctx, SYSCTL_CHILDREN(sc->sysctlnode), OID_AUTO,
348
"version_major", CTLFLAG_RD, &sc->smu_maj, 0,
349
"SMU firmware major version number");
350
SYSCTL_ADD_U8(sc->sysctlctx, SYSCTL_CHILDREN(sc->sysctlnode), OID_AUTO,
351
"version_minor", CTLFLAG_RD, &sc->smu_min, 0,
352
"SMU firmware minor version number");
353
SYSCTL_ADD_U8(sc->sysctlctx, SYSCTL_CHILDREN(sc->sysctlnode), OID_AUTO,
354
"version_revision", CTLFLAG_RD, &sc->smu_rev, 0,
355
"SMU firmware revision number");
356
357
/* Set up for getting metrics & add sysctls. */
358
if ((err = amdsmu_init_metrics(dev)) != 0)
359
goto err_dump;
360
if ((err = amdsmu_dump_metrics(dev)) != 0)
361
goto err_dump;
362
363
node = SYSCTL_ADD_NODE(sc->sysctlctx, SYSCTL_CHILDREN(sc->sysctlnode),
364
OID_AUTO, "metrics", CTLFLAG_RD, NULL, "SMU metrics");
365
if (node == NULL) {
366
device_printf(dev, "could not add sysctl node for metrics\n");
367
err = ENOMEM;
368
goto err_dump;
369
}
370
371
SYSCTL_ADD_U32(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
372
"table_version", CTLFLAG_RD, &sc->metrics.table_version, 0,
373
"SMU metrics table version");
374
SYSCTL_ADD_U32(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
375
"hint_count", CTLFLAG_RD, &sc->metrics.hint_count, 0,
376
"How many times the sleep hint was set");
377
SYSCTL_ADD_U32(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
378
"s0i3_last_entry_status", CTLFLAG_RD,
379
&sc->metrics.s0i3_last_entry_status, 0,
380
"1 if last S0i3 entry was successful");
381
SYSCTL_ADD_U32(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
382
"time_last_in_s0i2", CTLFLAG_RD, &sc->metrics.time_last_in_s0i2, 0,
383
"Time spent in S0i2 during last sleep (us)");
384
SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
385
"time_last_entering_s0i3", CTLFLAG_RD,
386
&sc->metrics.time_last_entering_s0i3, 0,
387
"Time spent entering S0i3 during last sleep (us)");
388
SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
389
"total_time_entering_s0i3", CTLFLAG_RD,
390
&sc->metrics.total_time_entering_s0i3, 0,
391
"Total time spent entering S0i3 (us)");
392
SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
393
"time_last_resuming", CTLFLAG_RD, &sc->metrics.time_last_resuming,
394
0, "Time spent resuming from last sleep (us)");
395
SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
396
"total_time_resuming", CTLFLAG_RD, &sc->metrics.total_time_resuming,
397
0, "Total time spent resuming from sleep (us)");
398
SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
399
"time_last_in_s0i3", CTLFLAG_RD, &sc->metrics.time_last_in_s0i3, 0,
400
"Time spent in S0i3 during last sleep (us)");
401
SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
402
"total_time_in_s0i3", CTLFLAG_RD, &sc->metrics.total_time_in_s0i3,
403
0, "Total time spent in S0i3 (us)");
404
SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
405
"time_last_in_sw_drips", CTLFLAG_RD,
406
&sc->metrics.time_last_in_sw_drips, 0,
407
"Time spent in awake during last sleep (us)");
408
SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO,
409
"total_time_in_sw_drips", CTLFLAG_RD,
410
&sc->metrics.total_time_in_sw_drips, 0,
411
"Total time spent awake (us)");
412
413
/* Get IP blocks & add sysctls. */
414
err = amdsmu_get_ip_blocks(dev);
415
if (err != 0)
416
goto err_dump;
417
418
/* Get idlemask & add sysctl. */
419
amdsmu_fetch_idlemask(dev);
420
SYSCTL_ADD_U32(sc->sysctlctx, SYSCTL_CHILDREN(sc->sysctlnode), OID_AUTO,
421
"idlemask", CTLFLAG_RD, &sc->idlemask, 0, "SMU idlemask. This "
422
"value is not documented - only used to help AMD internally debug "
423
"issues");
424
425
return (0);
426
err_dump:
427
bus_space_unmap(sc->bus_tag, sc->reg_space, SMU_MEM_SIZE);
428
err_reg_space:
429
bus_space_unmap(sc->bus_tag, sc->smu_space, SMU_MEM_SIZE);
430
err_smu_space:
431
bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res);
432
return (err);
433
}
434
435
static int
436
amdsmu_detach(device_t dev)
437
{
438
struct amdsmu_softc *sc = device_get_softc(dev);
439
int rid = 0;
440
441
bus_space_unmap(sc->bus_tag, sc->smu_space, SMU_MEM_SIZE);
442
bus_space_unmap(sc->bus_tag, sc->reg_space, SMU_MEM_SIZE);
443
444
bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res);
445
return (0);
446
}
447
448
static device_method_t amdsmu_methods[] = {
449
DEVMETHOD(device_identify, amdsmu_identify),
450
DEVMETHOD(device_probe, amdsmu_probe),
451
DEVMETHOD(device_attach, amdsmu_attach),
452
DEVMETHOD(device_detach, amdsmu_detach),
453
DEVMETHOD_END
454
};
455
456
static driver_t amdsmu_driver = {
457
"amdsmu",
458
amdsmu_methods,
459
sizeof(struct amdsmu_softc),
460
};
461
462
DRIVER_MODULE(amdsmu, hostb, amdsmu_driver, NULL, NULL);
463
MODULE_VERSION(amdsmu, 1);
464
MODULE_DEPEND(amdsmu, amdsmn, 1, 1, 1);
465
MODULE_PNP_INFO("U16:vendor;U16:device", pci, amdsmu, amdsmu_products,
466
nitems(amdsmu_products));
467
468