diff --git a/sys/dev/amdsmu/amdsmu.c b/sys/dev/amdsmu/amdsmu.c --- a/sys/dev/amdsmu/amdsmu.c +++ b/sys/dev/amdsmu/amdsmu.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,36 @@ SMU_MSG_GET_SUP_CONSTRAINTS = 0x09, }; +/* XXX Copied from Linux struct smu_metrics. */ +struct amdsmu_metrics { + uint32_t table_version; + uint32_t hint_count; + uint32_t s0i3_last_entry_status; + uint32_t time_last_in_s0i2; + uint64_t time_last_entering_s0i3; + uint64_t total_time_entering_s0i3; + uint64_t time_last_resuming; + uint64_t total_time_resuming; + uint64_t time_last_in_s0i3; + uint64_t total_time_in_s0i3; + uint64_t time_last_in_sw_drips; + uint64_t total_time_in_sw_drips; + /* + * This is how long each IP block was active for (us). In Linux, these + * are called "timecondition_notmet_*". I'm assuming this means "how + * long have the conditions for this IP block to deactivate not been + * met?" I'm not quite sure what these conditions are, however. + * + * XXX Total active time for IP blocks seems to be buggy and reporting + * garbage (at least on Phoenix), so it's disabled for now. The last + * active time for the USB4_0 IP block also seems to be buggy. + */ + uint64_t ip_block_last_active_time[32]; +#ifdef IP_BLOCK_TOTAL_ACTIVE_TIME + uint64_t ip_block_total_active_time[32]; +#endif +} __attribute__((packed)); + /* * TODO These are in common with amdtemp; should we find a way to factor these * out? Also, there are way more of these. I couldn't find a centralized place @@ -70,12 +101,37 @@ { VENDORID_AMD, CPUID_AMD_STRIX_POINT }, }; +static const char *const amdsmu_ip_blocks_names[] = { "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" }; + +CTASSERT(nitems(amdsmu_ip_blocks_names) <= 32); + struct amdsmu_softc { + struct sysctl_ctx_list *sysctlctx; + struct sysctl_oid *sysctlnode; + struct resource *res; bus_space_tag_t bus_tag; bus_space_handle_t smu_space; bus_space_handle_t reg_space; + + bool added_vers_sysctl; + uint8_t smu_program; + uint8_t smu_maj, smu_min, smu_rev; + + uint32_t active_ip_blocks; + struct sysctl_oid *ip_blocks_sysctlnode; + size_t ip_block_count; + struct sysctl_oid *ip_block_sysctlnodes[nitems(amdsmu_ip_blocks_names)]; + bool ip_blocks_active[nitems(amdsmu_ip_blocks_names)]; + + bool has_metrics; + bus_space_handle_t metrics_space; + bool added_metrics_sysctl; + struct amdsmu_metrics metrics; }; static bool @@ -186,31 +242,241 @@ } static void -amdsmu_print_vers(device_t dev) +amdsmu_get_vers(device_t dev) { - uint32_t smu_vers; - uint8_t smu_program; - uint8_t smu_maj, smu_min, smu_rev; + uint32_t smu_vers; + struct amdsmu_softc *sc = device_get_softc(dev); if (amdsmu_cmd(dev, SMU_MSG_GETSMUVERSION, 0, &smu_vers) != 0) { device_printf(dev, "failed to get SMU version\n"); return; } - smu_program = (smu_vers >> 24) & 0xFF; - smu_maj = (smu_vers >> 16) & 0xFF; - smu_min = (smu_vers >> 8) & 0xFF; - smu_rev = smu_vers & 0xFF; + sc->smu_program = (smu_vers >> 24) & 0xFF; + sc->smu_maj = (smu_vers >> 16) & 0xFF; + sc->smu_min = (smu_vers >> 8) & 0xFF; + sc->smu_rev = smu_vers & 0xFF; device_printf(dev, "SMU version: %d.%d.%d (program %d)\n", - smu_maj, smu_min, smu_rev, smu_program); + sc->smu_maj, sc->smu_min, sc->smu_rev, sc->smu_program); + + /* Add sysctl nodes for SMU version. */ + if (sc->added_vers_sysctl) + return; + sc->added_vers_sysctl = true; + + SYSCTL_ADD_U8(sc->sysctlctx, SYSCTL_CHILDREN(sc->sysctlnode), OID_AUTO, + "program", CTLFLAG_RD, &sc->smu_program, 0, "SMU program number"); + SYSCTL_ADD_U8(sc->sysctlctx, SYSCTL_CHILDREN(sc->sysctlnode), OID_AUTO, + "version_major", CTLFLAG_RD, &sc->smu_maj, 0, + "SMU firmware major version number"); + SYSCTL_ADD_U8(sc->sysctlctx, SYSCTL_CHILDREN(sc->sysctlnode), OID_AUTO, + "version_minor", CTLFLAG_RD, &sc->smu_min, 0, + "SMU firmware minor version number"); + SYSCTL_ADD_U8(sc->sysctlctx, SYSCTL_CHILDREN(sc->sysctlnode), OID_AUTO, + "version_revision", CTLFLAG_RD, &sc->smu_rev, 0, + "SMU firmware revision number"); +} + +static void +amdsmu_get_ip_blocks(device_t dev) +{ + struct amdsmu_softc *sc = device_get_softc(dev); + const uint16_t deviceid = pci_get_device(dev); + struct amdsmu_metrics *m = &sc->metrics; + bool active; + char sysctl_descr[32]; + + /* Get IP block count. */ + switch (deviceid) { + case CPUID_AMD_REMBRANDT: + sc->ip_block_count = 12; + break; + case CPUID_AMD_PHOENIX: + sc->ip_block_count = 21; + break; + /* TODO How many IP blocks does Strix Point (and the others) have? */ + case CPUID_AMD_STRIX_POINT: + default: + sc->ip_block_count = nitems(amdsmu_ip_blocks_names); + } + KASSERT(sc->ip_block_count <= nitems(amdsmu_ip_blocks_names), + ("too many IP blocks for array")); + + /* Get and print out IP blocks. */ + if (amdsmu_cmd(dev, SMU_MSG_GET_SUP_CONSTRAINTS, 0, &sc->active_ip_blocks) != 0) { + device_printf(dev, "failed to get IP blocks\n"); + return; + } + device_printf(dev, "Active IP blocks: "); + for (size_t i = 0; i < sc->ip_block_count; i++) { + active = (sc->active_ip_blocks & (1 << i)) != 0; + sc->ip_blocks_active[i] = active; + if (!active) + continue; + printf("%s%s", amdsmu_ip_blocks_names[i], + i + 1 < sc->ip_block_count ? " " : "\n"); + } + + /* + * Make sure we haven't already created the sysctl tree for IP blocks. + */ + if (sc->ip_blocks_sysctlnode != NULL) + return; + + /* Create a sysctl node for IP blocks. */ + sc->ip_blocks_sysctlnode = SYSCTL_ADD_NODE(sc->sysctlctx, + SYSCTL_CHILDREN(sc->sysctlnode), OID_AUTO, "ip_blocks", + CTLFLAG_RD, NULL, "SMU metrics"); + if (sc->ip_blocks_sysctlnode == NULL) { + device_printf(dev, "could not add sysctl node for IP blocks\n"); + return; + } + + /* Create a sysctl node for each IP block. */ + for (size_t i = 0; i < sc->ip_block_count; i++) { + /* Create the sysctl node itself for the IP block. */ + snprintf(sysctl_descr, sizeof sysctl_descr, + "Metrics about the %s AMD IP block", + amdsmu_ip_blocks_names[i]); + sc->ip_block_sysctlnodes[i] = SYSCTL_ADD_NODE(sc->sysctlctx, + SYSCTL_CHILDREN(sc->ip_blocks_sysctlnode), OID_AUTO, + amdsmu_ip_blocks_names[i], CTLFLAG_RD, NULL, sysctl_descr); + if (sc->ip_block_sysctlnodes[i] == NULL) { + device_printf(dev, + "could not add sysctl node for \"%s\"\n", sysctl_descr); + continue; + } + /* + * Create sysctls for if the IP block is currently active, last + * active time, and total active time. + */ + SYSCTL_ADD_BOOL(sc->sysctlctx, + SYSCTL_CHILDREN(sc->ip_block_sysctlnodes[i]), OID_AUTO, + "active", CTLFLAG_RD, &sc->ip_blocks_active[i], 0, + "IP block is currently active"); + SYSCTL_ADD_U64(sc->sysctlctx, + SYSCTL_CHILDREN(sc->ip_block_sysctlnodes[i]), OID_AUTO, + "last_time", CTLFLAG_RD, &m->ip_block_last_active_time[i], + 0, "How long the IP block was active for during the last" + "sleep (us)"); +#ifdef IP_BLOCK_TOTAL_ACTIVE_TIME + SYSCTL_ADD_U64(sc->sysctlctx, + SYSCTL_CHILDREN(sc->ip_block_sysctlnodes[i]), OID_AUTO, + "total_time", CTLFLAG_RD, &m->ip_block_total_active_time[i], + 0, "How long the IP block was active for during sleep in" + "total (us)"); +#endif + } +} + +static void +amdsmu_init_metrics(device_t dev) +{ + struct amdsmu_softc *sc = device_get_softc(dev); + uint32_t metrics_addr_lo, metrics_addr_hi; + uint64_t metrics_addr; + + sc->has_metrics = false; + + /* Get physical address of logging buffer. */ + if (amdsmu_cmd(dev, SMU_MSG_LOG_GETDRAM_ADDR_LO, 0, &metrics_addr_lo) + != 0) + return; + if (amdsmu_cmd(dev, SMU_MSG_LOG_GETDRAM_ADDR_HI, 0, &metrics_addr_hi) + != 0) + return; + metrics_addr = ((uint64_t) metrics_addr_hi << 32) | metrics_addr_lo; + + /* Map memory of logging buffer. */ + if (bus_space_map(sc->bus_tag, metrics_addr, + sizeof(struct amdsmu_metrics), 0, &sc->metrics_space) != 0) { + device_printf(dev, "could not map bus space for SMU metrics\n"); + return; + } + + /* Start logging for metrics. */ + amdsmu_cmd(dev, SMU_MSG_LOG_RESET, 0, NULL); + amdsmu_cmd(dev, SMU_MSG_LOG_START, 0, NULL); + + sc->has_metrics = true; +} + +static void +amdsmu_dump_metrics(device_t dev) +{ + struct amdsmu_softc *sc = device_get_softc(dev); + struct sysctl_oid *node; + + if (!sc->has_metrics) { + device_printf(dev, "can't dump metrics\n"); + return; + } + if (amdsmu_cmd(dev, SMU_MSG_LOG_DUMP_DATA, 0, NULL) != 0) { + device_printf(dev, "failed to dump metrics\n"); + return; + } + bus_space_read_region_4(sc->bus_tag, sc->metrics_space, 0, + (uint32_t *)&sc->metrics, sizeof(sc->metrics) / sizeof(uint32_t)); + + /* Add sysctl nodes for metrics. */ + if (sc->added_metrics_sysctl) + return; + sc->added_metrics_sysctl = true; + + node = SYSCTL_ADD_NODE(sc->sysctlctx, SYSCTL_CHILDREN(sc->sysctlnode), + OID_AUTO, "metrics", CTLFLAG_RD, NULL, "SMU metrics"); + if (node == NULL) { + device_printf(dev, "could not add sysctl node for metrics\n"); + return; + } + + SYSCTL_ADD_U32(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "table_version", CTLFLAG_RD, &sc->metrics.table_version, 0, + "SMU metrics table version"); + SYSCTL_ADD_U32(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "hint_count", CTLFLAG_RD, &sc->metrics.hint_count, 0, + "How many times the sleep hint was set"); + SYSCTL_ADD_U32(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "s0i3_last_entry_status", CTLFLAG_RD, + &sc->metrics.s0i3_last_entry_status, 0, + "1 if last S0i3 entry was successful"); + SYSCTL_ADD_U32(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "time_last_in_s0i2", CTLFLAG_RD, &sc->metrics.time_last_in_s0i2, 0, + "Time spent in S0i2 during last sleep (us)"); + SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "time_last_entering_s0i3", CTLFLAG_RD, + &sc->metrics.time_last_entering_s0i3, 0, + "Time spent entering S0i3 during last sleep (us)"); + SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "total_time_entering_s0i3", CTLFLAG_RD, + &sc->metrics.total_time_entering_s0i3, 0, + "Total time spent entering S0i3 (us)"); + SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "time_last_resuming", CTLFLAG_RD, &sc->metrics.time_last_resuming, + 0, "Time spent resuming from last sleep (us)"); + SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "total_time_resuming", CTLFLAG_RD, &sc->metrics.total_time_resuming, + 0, "Total time spent resuming from sleep (us)"); + SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "time_last_in_s0i3", CTLFLAG_RD, &sc->metrics.time_last_in_s0i3, 0, + "Time spent in S0i3 during last sleep (us)"); + SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "total_time_in_s0i3", CTLFLAG_RD, &sc->metrics.total_time_in_s0i3, + 0, "Total time spent in S0i3 (us)"); + SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "time_last_in_sw_drips", CTLFLAG_RD, &sc->metrics.time_last_in_sw_drips, + 0, "Time spent in software DRIPS (SW DRIPS) during last sleep (us)"); + SYSCTL_ADD_U64(sc->sysctlctx, SYSCTL_CHILDREN(node), OID_AUTO, + "total_time_in_sw_drips", CTLFLAG_RD, &sc->metrics.total_time_in_sw_drips, + 0, "Total time spent in software DRIPS (SW DRIPS) (us)"); } static int amdsmu_attach(device_t dev) { - struct amdsmu_softc *sc = device_get_softc(dev); - uint32_t physbase_addr_lo, physbase_addr_hi; - uint64_t physbase_addr; - int rid = 0; + struct amdsmu_softc *sc = device_get_softc(dev); + uint32_t physbase_addr_lo, physbase_addr_hi; + uint64_t physbase_addr; + int rid = 0; /* * Find physical base address for SMU. @@ -238,24 +504,38 @@ SMU_MEM_SIZE, 0, &sc->smu_space) != 0) { device_printf(dev, "could not map bus space for SMU\n"); bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res); + sc->res = NULL; return (ENXIO); } if (bus_space_map(sc->bus_tag, physbase_addr + SMU_REG_SPACE_OFF, SMU_MEM_SIZE, 0, &sc->reg_space) != 0) { device_printf(dev, "could not map bus space for SMU regs\n"); bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res); + sc->res = NULL; return (ENXIO); } - amdsmu_print_vers(dev); + /* sysctl stuff. */ + sc->sysctlctx = device_get_sysctl_ctx(dev); + sc->sysctlnode = device_get_sysctl_tree(dev); + + amdsmu_get_vers(dev); + + /* Get IP blocks. */ + amdsmu_get_ip_blocks(dev); + + /* Set up for getting metrics. */ + amdsmu_init_metrics(dev); + amdsmu_dump_metrics(dev); + return (0); } static int amdsmu_detach(device_t dev) { - struct amdsmu_softc *sc = device_get_softc(dev); - int rid = 0; + struct amdsmu_softc *sc = device_get_softc(dev); + int rid = 0; if (sc->res != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res);