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