The branch main has been updated by obiwac:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=e4e44f69f963dad9e25d589c18b3e9a279e49249

commit e4e44f69f963dad9e25d589c18b3e9a279e49249
Author:     Aymeric Wibo <obi...@freebsd.org>
AuthorDate: 2025-06-14 15:33:05 +0000
Commit:     Aymeric Wibo <obi...@freebsd.org>
CommitDate: 2025-07-26 22:26:46 +0000

    amdsmu: Expose sysctls for metrics about last sleep
    
    Get "log" address with the `SMU_MSG_LOG_GETDRAM_ADDR_HI/LO` SMU
    commands. From this, we dump & read metrics about residency in various
    sleep states (none, S0i2, and S0i3), as well as active durations for
    the IP blocks on the CPU during the last sleep. All these metrics are
    exposed under the `dev.amdsmu.0.metrics` node.
    
    This is useful for debugging sleep as the residency registers in the
    `_LPI` object are not populated on some systems (e.g. AMD Phoenix).
    
    A `dev.amdsmu.0.ip_blocks.XXX` node is also created for each IP block,
    with children for the current active state and time spent active during
    the last sleep.
    
    Differential Revision: https://reviews.freebsd.org/D48714
    
    Reviewed by:    cem, kib, mckusick
    Approved by:    kib, mckusick
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D48714
---
 sys/dev/amdsmu/amdsmu.c     | 264 +++++++++++++++++++++++++++++++++++++++++---
 sys/dev/amdsmu/amdsmu.h     |  46 +++++++-
 sys/dev/amdsmu/amdsmu_reg.h |  30 +++++
 3 files changed, 322 insertions(+), 18 deletions(-)

diff --git a/sys/dev/amdsmu/amdsmu.c b/sys/dev/amdsmu/amdsmu.c
index 84e6c749cca7..d46b0fc4f5ec 100644
--- a/sys/dev/amdsmu/amdsmu.c
+++ b/sys/dev/amdsmu/amdsmu.c
@@ -12,6 +12,7 @@
 #include <sys/kernel.h>
 #include <sys/module.h>
 #include <sys/rman.h>
+#include <sys/sysctl.h>
 
 #include <dev/pci/pcivar.h>
 #include <dev/amdsmu/amdsmu.h>
@@ -120,32 +121,175 @@ amdsmu_cmd(device_t dev, enum amdsmu_msg msg, uint32_t 
arg, uint32_t *ret)
        return (EINVAL);
 }
 
-static void
-amdsmu_print_vers(device_t dev)
+static int
+amdsmu_get_vers(device_t dev)
 {
+       int err;
        uint32_t smu_vers;
-       uint8_t smu_program;
-       uint8_t smu_maj, smu_min, smu_rev;
+       struct amdsmu_softc *sc = device_get_softc(dev);
 
-       if (amdsmu_cmd(dev, SMU_MSG_GETSMUVERSION, 0, &smu_vers) != 0) {
+       err = amdsmu_cmd(dev, SMU_MSG_GETSMUVERSION, 0, &smu_vers);
+       if (err != 0) {
                device_printf(dev, "failed to get SMU version\n");
-               return;
+               return (err);
        }
-       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);
+
+       return (0);
+}
+
+static int
+amdsmu_get_ip_blocks(device_t dev)
+{
+       struct amdsmu_softc *sc = device_get_softc(dev);
+       const uint16_t deviceid = pci_get_device(dev);
+       int err;
+       struct amdsmu_metrics *m = &sc->metrics;
+       bool active;
+       char sysctl_descr[32];
+
+       /* Get IP block count. */
+       switch (deviceid) {
+       case PCI_DEVICEID_AMD_REMBRANDT_ROOT:
+               sc->ip_block_count = 12;
+               break;
+       case PCI_DEVICEID_AMD_PHOENIX_ROOT:
+               sc->ip_block_count = 21;
+               break;
+       /* TODO How many IP blocks does Strix Point (and the others) have? */
+       case PCI_DEVICEID_AMD_STRIX_POINT_ROOT:
+       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. */
+       err = amdsmu_cmd(dev, SMU_MSG_GET_SUP_CONSTRAINTS, 0,
+           &sc->active_ip_blocks);
+       if (err != 0) {
+               device_printf(dev, "failed to get IP blocks\n");
+               return (err);
+       }
+       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");
+       }
+
+       /* 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 (ENOMEM);
+       }
+
+       /* 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
+       }
+       return (0);
+}
+
+static int
+amdsmu_init_metrics(device_t dev)
+{
+       struct amdsmu_softc *sc = device_get_softc(dev);
+       int err;
+       uint32_t metrics_addr_lo, metrics_addr_hi;
+       uint64_t metrics_addr;
+
+       /* Get physical address of logging buffer. */
+       err = amdsmu_cmd(dev, SMU_MSG_LOG_GETDRAM_ADDR_LO, 0, &metrics_addr_lo);
+       if (err != 0)
+               return (err);
+       err = amdsmu_cmd(dev, SMU_MSG_LOG_GETDRAM_ADDR_HI, 0, &metrics_addr_hi);
+       if (err != 0)
+               return (err);
+       metrics_addr = ((uint64_t) metrics_addr_hi << 32) | metrics_addr_lo;
+
+       /* Map memory of logging buffer. */
+       err = bus_space_map(sc->bus_tag, metrics_addr,
+           sizeof(struct amdsmu_metrics), 0, &sc->metrics_space);
+       if (err != 0) {
+               device_printf(dev, "could not map bus space for SMU metrics\n");
+               return (err);
+       }
+
+       /* Start logging for metrics. */
+       amdsmu_cmd(dev, SMU_MSG_LOG_RESET, 0, NULL);
+       amdsmu_cmd(dev, SMU_MSG_LOG_START, 0, NULL);
+       return (0);
+}
+
+static int
+amdsmu_dump_metrics(device_t dev)
+{
+       struct amdsmu_softc *sc = device_get_softc(dev);
+       int err;
+
+       err = amdsmu_cmd(dev, SMU_MSG_LOG_DUMP_DATA, 0, NULL);
+       if (err != 0) {
+               device_printf(dev, "failed to dump metrics\n");
+               return (err);
+       }
+       bus_space_read_region_4(sc->bus_tag, sc->metrics_space, 0,
+           (uint32_t *)&sc->metrics, sizeof(sc->metrics) / sizeof(uint32_t));
+
+       return (0);
 }
 
 static int
 amdsmu_attach(device_t dev)
 {
        struct amdsmu_softc *sc = device_get_softc(dev);
+       int err;
        uint32_t physbase_addr_lo, physbase_addr_hi;
        uint64_t physbase_addr;
        int rid = 0;
+       struct sysctl_oid *node;
 
        /*
         * Find physical base address for SMU.
@@ -172,19 +316,105 @@ amdsmu_attach(device_t dev)
        if (bus_space_map(sc->bus_tag, physbase_addr,
            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);
-               return (ENXIO);
+               err = ENXIO;
+               goto err_smu_space;
        }
        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_space_unmap(sc->bus_tag, sc->smu_space, SMU_MEM_SIZE);
-               bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res);
-               return (ENXIO);
+               err = ENXIO;
+               goto err_reg_space;
        }
 
-       amdsmu_print_vers(dev);
+       /* sysctl stuff. */
+       sc->sysctlctx = device_get_sysctl_ctx(dev);
+       sc->sysctlnode = device_get_sysctl_tree(dev);
+
+       /* Get version & add sysctls. */
+       if ((err = amdsmu_get_vers(dev)) != 0)
+               goto err_dump;
+
+       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");
+
+       /* Set up for getting metrics & add sysctls. */
+       if ((err = amdsmu_init_metrics(dev)) != 0)
+               goto err_dump;
+       if ((err = amdsmu_dump_metrics(dev)) != 0)
+               goto err_dump;
+
+       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");
+               err = ENOMEM;
+               goto err_dump;
+       }
+
+       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 awake 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 awake (us)");
+
+       /* Get IP blocks & add sysctls. */
+       err = amdsmu_get_ip_blocks(dev);
+       if (err != 0)
+               goto err_dump;
+
        return (0);
+err_dump:
+       bus_space_unmap(sc->bus_tag, sc->reg_space, SMU_MEM_SIZE);
+err_reg_space:
+       bus_space_unmap(sc->bus_tag, sc->smu_space, SMU_MEM_SIZE);
+err_smu_space:
+       bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res);
+       return (err);
 }
 
 static int
diff --git a/sys/dev/amdsmu/amdsmu.h b/sys/dev/amdsmu/amdsmu.h
index 5e76c8e93062..0ec15d003984 100644
--- a/sys/dev/amdsmu/amdsmu.h
+++ b/sys/dev/amdsmu/amdsmu.h
@@ -9,7 +9,9 @@
 #ifndef _AMDSMU_H_
 #define        _AMDSMU_H_
 
-#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
 #include <machine/bus.h>
 #include <x86/cputypes.h>
 
@@ -27,12 +29,54 @@ static const struct amdsmu_product {
        { CPU_VENDOR_AMD,       PCI_DEVICEID_AMD_STRIX_POINT_ROOT },
 };
 
+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;
+
+       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)];
+
+       bus_space_handle_t      metrics_space;
+       struct amdsmu_metrics   metrics;
 };
 
 static inline uint32_t
diff --git a/sys/dev/amdsmu/amdsmu_reg.h b/sys/dev/amdsmu/amdsmu_reg.h
index 5e73cea19028..721c5ac17bd3 100644
--- a/sys/dev/amdsmu/amdsmu_reg.h
+++ b/sys/dev/amdsmu/amdsmu_reg.h
@@ -9,6 +9,8 @@
 #ifndef _AMDSMU_REG_H_
 #define        _AMDSMU_REG_H_
 
+#include <sys/types.h>
+
 /*
  * 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
@@ -50,4 +52,32 @@ enum amdsmu_msg {
        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), i.e., blocking
+        * entry to S0i3.  In Linux, these are called "timecondition_notmet_*".
+        *
+        * 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));
+
 #endif /* _AMDSMU_REG_H_ */

Reply via email to