/* $OpenBSD: amdpmc.c,v 1.3 2026/01/19 21:13:36 kettenis Exp $ */ /* * Copyright (c) 2025 Mark Kettenis * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #define SMN_ADDR 0x60 #define SMN_PMC_BASE_ADDR_LO 0x13b102e8 #define SMN_PMC_BASE_ADDR_HI 0x13b102ec #define SMN_PMC_BASE_ADDR_LO_MASK 0x0000ffff #define SMN_PMC_BASE_ADDR_HI_MASK 0xfff00000 #define SMN_PMC_SIZE 0x1000 #define SMN_PMC_BASE_ADDR_OFFSET 0x00010000 #define SMN_DATA 0x64 #define PMC_MSG 0x538 #define PMC_MSG_1A 0x938 #define PMC_RESP 0x980 #define PMC_RESP_OK 0x01 #define PMC_RESP_BUSY 0xfc #define PMC_ARG 0x9bc #define SMU_MSG_GETVERSION 0x02 #define SMU_MSG_OS_HINT 0x03 #define SMU_MSG_LOG_GETDRAM_ADDR_HI 0x04 #define SMU_MSG_LOG_GETDRAM_ADDR_LO 0x05 #define SMU_MSG_LOG_START 0x06 #define SMU_MSG_LOG_RESET 0x07 #define SMU_MSG_LOG_DUMP_DATA 0x08 #define SMU_MSG_GET_SUP_CONSTRAINTS 0x09 struct smu_metrics { uint32_t table_version; uint32_t hint_count; uint32_t s0i3_last_entry_status; uint32_t timein_s0i2; uint64_t timeentering_s0i3_lastcapture; uint64_t timeentering_s0i3_totaltime; uint64_t timeto_resume_to_os_lastcapture; uint64_t timeto_resume_to_os_totaltime; uint64_t timein_s0i3_lastcapture; uint64_t timein_s0i3_totaltime; uint64_t timein_swdrips_lastcapture; uint64_t timein_swdrips_totaltime; uint64_t timecondition_notmet_lastcapture[32]; uint64_t timecondition_notmet_totaltime[32]; }; const char *smu_blocks[] = { "DISPLAY", "CPU", "GFX", "VDD", "ACP", "VCN", "ISP", "NBIO", "DF", "USB3_0", "USB3_1", "LAPIC", "USB3_2", "USB3_3", "USB3_4", "USB4_0", "USB4_1", "MPM", "JPEG", "IPU", "UMSCH", "VPE", }; const char *smu_blocks_1a_7x[] = { "DISPLAY", "CPU", "GFX", "VDD", "VDD_CTX", "ACP", "VCN_0", "VCN_1", "ISP", "NBIO", "DF", "USB3_0", "USB3_1", "LAPIC", "USB3_2", "USB4_RT0", "USB4_RT1", "USB4_0", "USB4_1", "MPM", "JPEG_0", "JPEG_1", "IPU", "UMSCH", "VPE", }; /* Low Power S0 Idle DSM methods */ #define ACPI_LPS0_ENUM_FUNCTIONS 0 #define ACPI_LPS0_GET_CONSTRAINTS 1 #define ACPI_LPS0_ENTRY 2 #define ACPI_LPS0_EXIT 3 #define ACPI_LPS0_SCREEN_OFF 4 #define ACPI_LPS0_SCREEN_ON 5 struct amdpmc_softc { struct device sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; struct acpi_softc *sc_acpi; struct aml_node *sc_node; bus_addr_t sc_pmc_msg; const char **sc_smu_blocks; u_int sc_smu_nblocks; struct smu_metrics *sc_metrics; uint32_t sc_active_blocks; }; int amdpmc_match(struct device *, void *, void *); void amdpmc_attach(struct device *, struct device *, void *); int amdpmc_activate(struct device *, int); const struct cfattach amdpmc_ca = { sizeof (struct amdpmc_softc), amdpmc_match, amdpmc_attach, NULL, amdpmc_activate }; struct cfdriver amdpmc_cd = { NULL, "amdpmc", DV_DULL }; const char *amdpmc_hids[] = { "AMDI0005", "AMDI0006", "AMDI0007", "AMDI0008", "AMDI0009", "AMDI000A", "AMDI000B", NULL }; void amdpmc_suspend(void *); void amdpmc_resume(void *); void amdpmc_smu_log_print(struct amdpmc_softc *); int amdpmc_send_msg(struct amdpmc_softc *, uint8_t, uint32_t, uint32_t *); int amdpmc_match(struct device *parent, void *match, void *aux) { struct acpi_attach_args *aaa = aux; struct cfdata *cf = match; return acpi_matchhids(aaa, amdpmc_hids, cf->cf_driver->cd_name); } void amdpmc_attach(struct device *parent, struct device *self, void *aux) { struct amdpmc_softc *sc = (struct amdpmc_softc *)self; struct acpi_attach_args *aaa = aux; pci_chipset_tag_t pc; pcitag_t tag; pcireg_t reg; bus_space_handle_t ioh; uint32_t addr_lo, addr_hi; bus_addr_t addr; uint32_t version; int error; sc->sc_acpi = (struct acpi_softc *)parent; sc->sc_node = aaa->aaa_node; printf(": %s\n", aaa->aaa_node->name); sc->sc_acpi->sc_pmc_suspend = amdpmc_suspend; sc->sc_acpi->sc_pmc_resume = amdpmc_resume; sc->sc_acpi->sc_pmc_cookie = sc; pc = pci_lookup_segment(0, 0); tag = pci_make_tag(pc, 0, 0, 0); reg = pci_conf_read(pc, tag, PCI_ID_REG); KASSERT(PCI_VENDOR(reg) == PCI_VENDOR_AMD); switch (PCI_PRODUCT(reg)) { case PCI_PRODUCT_AMD_17_6X_RC: /* RN/CZN */ case PCI_PRODUCT_AMD_19_4X_RC: /* YC */ case PCI_PRODUCT_AMD_19_6X_RC: /* CB */ case PCI_PRODUCT_AMD_19_7X_RC: /* PS */ sc->sc_pmc_msg = PMC_MSG; sc->sc_smu_blocks = smu_blocks; sc->sc_smu_nblocks = nitems(smu_blocks); break; case PCI_PRODUCT_AMD_1A_2X_RC: /* Strix Point */ case PCI_PRODUCT_AMD_1A_6X_RC: /* Krackan Point */ sc->sc_pmc_msg = PMC_MSG_1A; if (curcpu()->ci_model == 0x70) { sc->sc_smu_blocks = smu_blocks_1a_7x; sc->sc_smu_nblocks = nitems(smu_blocks_1a_7x); } else { sc->sc_smu_blocks = smu_blocks; sc->sc_smu_nblocks = nitems(smu_blocks); } break; case PCI_PRODUCT_AMD_17_1X_RC: /* RV/PCO */ case PCI_PRODUCT_AMD_19_1X_RC: /* SP */ default: /* Unsupported */ return; } pci_conf_write(pc, tag, SMN_ADDR, SMN_PMC_BASE_ADDR_LO); addr_lo = pci_conf_read(pc, tag, SMN_DATA); pci_conf_write(pc, tag, SMN_ADDR, SMN_PMC_BASE_ADDR_HI); addr_hi = pci_conf_read(pc, tag, SMN_DATA); if (addr_lo == 0xffffffff || addr_hi == 0xffffffff) return; addr_lo &= SMN_PMC_BASE_ADDR_HI_MASK; addr_hi &= SMN_PMC_BASE_ADDR_LO_MASK; addr = (uint64_t)addr_hi << 32 | addr_lo; sc->sc_iot = aaa->aaa_memt; if (bus_space_map(sc->sc_iot, addr + SMN_PMC_BASE_ADDR_OFFSET, SMN_PMC_SIZE, 0, &sc->sc_ioh)) { printf("%s: can't map SMU registers\n", sc->sc_dev.dv_xname); return; } error = amdpmc_send_msg(sc, SMU_MSG_GETVERSION, 0, &version); if (error) { printf("%s: can't read SMU version\n", sc->sc_dev.dv_xname); return; } printf("%s: SMU program %u version %u.%u.%u\n", sc->sc_dev.dv_xname, (version >> 24) & 0xff, (version >> 16) & 0xff, (version >> 8) & 0xff, (version >> 0) & 0xff); error = amdpmc_send_msg(sc, SMU_MSG_GET_SUP_CONSTRAINTS, 0, &sc->sc_active_blocks); if (error) { printf("%s: can't read active blocks\n", sc->sc_dev.dv_xname); return; } error = amdpmc_send_msg(sc, SMU_MSG_LOG_GETDRAM_ADDR_LO, 0, &addr_lo); if (error) { printf("%s: can't read SMU DRAM address (%d)\n", sc->sc_dev.dv_xname, error); return; } error = amdpmc_send_msg(sc, SMU_MSG_LOG_GETDRAM_ADDR_HI, 0, &addr_hi); if (error) { printf("%s: can't read SMU DRAM address (%d)\n", sc->sc_dev.dv_xname, error); return; } addr = (uint64_t)addr_hi << 32 | addr_lo; if (bus_space_map(sc->sc_iot, addr, sizeof(struct smu_metrics), 0, &ioh)) { printf("%s: can't map SMU metrics\n", sc->sc_dev.dv_xname); return; } sc->sc_metrics = bus_space_vaddr(sc->sc_iot, ioh); memset(sc->sc_metrics, 0, sizeof(struct smu_metrics)); error = amdpmc_send_msg(sc, SMU_MSG_LOG_RESET, 0, NULL); if (error) { printf("%s: can't reset SMU message log (%d)\n", sc->sc_dev.dv_xname, error); return; } error = amdpmc_send_msg(sc, SMU_MSG_LOG_START, 0, NULL); if (error) { printf("%s: can't start SMU message log (%d)\n", sc->sc_dev.dv_xname, error); return; } } void amdpmc_smu_log_dump(struct amdpmc_softc *sc) { int error; error = amdpmc_send_msg(sc, SMU_MSG_LOG_DUMP_DATA, 0, NULL); if (error) { printf("%s: can't dump SMU message log (%d)\n", sc->sc_dev.dv_xname, error); return; } } void amdpmc_smu_log_print(struct amdpmc_softc *sc) { const char *devname = sc->sc_dev.dv_xname; int i; printf("%s: SMU table_version %u\n", devname, sc->sc_metrics->table_version); printf("%s: SMU hint_count %u\n", devname, sc->sc_metrics->hint_count); printf("%s: SMU s0i3_last_entry_status %u\n", devname, sc->sc_metrics->s0i3_last_entry_status); printf("%s: SMU timeentering_s0i3_lastcapture %llu\n", devname, sc->sc_metrics->timeentering_s0i3_lastcapture); printf("%s: SMU timein_s0i3_lastcapture %llu\n", devname, sc->sc_metrics->timein_s0i3_lastcapture); printf("%s: SMU timeto_resume_to_os_lastcapture %llu\n", devname, sc->sc_metrics->timeto_resume_to_os_lastcapture); for (i = 0; i < sc->sc_smu_nblocks; i++) { if ((sc->sc_active_blocks & (1U << i)) == 0) continue; if (sc->sc_metrics->timecondition_notmet_lastcapture[i] == 0) continue; printf("%s: SMU %s: %llu\n", devname, sc->sc_smu_blocks[i], sc->sc_metrics->timecondition_notmet_lastcapture[i]); } } int amdpmc_activate(struct device *self, int act) { struct amdpmc_softc *sc = (struct amdpmc_softc *)self; switch (act) { case DVACT_SUSPEND: break; case DVACT_RESUME: if (sc->sc_metrics) amdpmc_smu_log_print(sc); break; } return 0; } int amdpmc_dsm(struct acpi_softc *sc, struct aml_node *node, int func) { struct aml_value cmd[4]; struct aml_value res; /* e3f32452-febc-43ce-9039-932122d37721 */ static uint8_t lps0_dsm_guid[] = { 0x52, 0x24, 0xF3, 0xE3, 0xBC, 0xFE, 0xCE, 0x43, 0x90, 0x39, 0x93, 0x21, 0x22, 0xD3, 0x77, 0x21, }; bzero(&cmd, sizeof(cmd)); cmd[0].type = AML_OBJTYPE_BUFFER; cmd[0].v_buffer = (uint8_t *)&lps0_dsm_guid; cmd[0].length = sizeof(lps0_dsm_guid); /* rev */ cmd[1].type = AML_OBJTYPE_INTEGER; cmd[1].v_integer = 0; cmd[1].length = 1; /* func */ cmd[2].type = AML_OBJTYPE_INTEGER; cmd[2].v_integer = func; cmd[2].length = 1; /* not used */ cmd[3].type = AML_OBJTYPE_PACKAGE; cmd[3].length = 0; if (aml_evalname(sc, node, "_DSM", 4, cmd, &res)) { printf("%s: eval of _DSM at %s failed\n", sc->sc_dev.dv_xname, aml_nodename(node)); return 1; } aml_freevalue(&res); return 0; } void amdpmc_suspend(void *cookie) { struct amdpmc_softc *sc = cookie; if (sc->sc_acpi->sc_state != ACPI_STATE_S0) return; if (sc->sc_metrics) { memset(sc->sc_metrics, 0, sizeof(struct smu_metrics)); amdpmc_send_msg(sc, SMU_MSG_LOG_RESET, 0, NULL); amdpmc_send_msg(sc, SMU_MSG_LOG_START, 0, NULL); amdpmc_send_msg(sc, SMU_MSG_OS_HINT, 1, NULL); } amdpmc_dsm(sc->sc_acpi, sc->sc_node, ACPI_LPS0_SCREEN_OFF); amdpmc_dsm(sc->sc_acpi, sc->sc_node, ACPI_LPS0_ENTRY); } void amdpmc_resume(void *cookie) { struct amdpmc_softc *sc = cookie; if (sc->sc_acpi->sc_state != ACPI_STATE_S0) return; amdpmc_dsm(sc->sc_acpi, sc->sc_node, ACPI_LPS0_EXIT); amdpmc_dsm(sc->sc_acpi, sc->sc_node, ACPI_LPS0_SCREEN_ON); if (sc->sc_metrics) { amdpmc_send_msg(sc, SMU_MSG_OS_HINT, 0, NULL); amdpmc_smu_log_dump(sc); } } int amdpmc_send_msg(struct amdpmc_softc *sc, uint8_t msg, uint32_t arg, uint32_t *data) { uint32_t val; int timo; for (timo = 1000000; timo > 0; timo -= 50) { val = bus_space_read_4(sc->sc_iot, sc->sc_ioh, PMC_RESP); if (val) break; delay(50); } if (timo == 0) return ETIMEDOUT; bus_space_write_4(sc->sc_iot, sc->sc_ioh, PMC_RESP, 0); bus_space_write_4(sc->sc_iot, sc->sc_ioh, PMC_ARG, arg); bus_space_write_4(sc->sc_iot, sc->sc_ioh, sc->sc_pmc_msg, msg); for (timo = 1000000; timo > 0; timo -= 50) { val = bus_space_read_4(sc->sc_iot, sc->sc_ioh, PMC_RESP); if (val) break; delay(50); } if (timo == 0) return ETIMEDOUT; switch (val) { case PMC_RESP_OK: if (data) { delay(50); *data = bus_space_read_4(sc->sc_iot, sc->sc_ioh, PMC_ARG); } return 0; case PMC_RESP_BUSY: return EBUSY; default: return EIO; } }