The branch main has been updated by obiwac:

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

commit f261b63307fca34f27e4d12384d19cb543b4867a
Author:     Aymeric Wibo <obi...@freebsd.org>
AuthorDate: 2025-06-14 15:31:22 +0000
Commit:     Aymeric Wibo <obi...@freebsd.org>
CommitDate: 2025-07-26 19:17:06 +0000

    amdsmu: Initial work on a driver for the AMD SMU
    
    Start work on a driver for the AMD SMU (system management unit), which
    will eventually be used for getting S0ix statistics (e.g. how long the
    CPU has spent in the deepest - S0i3 - sleep state during the last
    sleep) as well as letting PMFW (power management firmware, running on
    the SMU) know when we intend to enter and exit sleep. It is what's
    responsible for actually turning off the VDD line to the CPU.
    
    With this commit, amdsmu is just able to get the SMU's firmware version
    on AMD Rembrandt, Phoenix, and Strix Point CPUs.
    
    This is the equivalent to amd-pmc on Linux.
    
    Reviewed by:    cem, kib, mckusick
    Approved by:    kib, mckusick
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D48683
---
 sys/conf/files.x86          |   1 +
 sys/dev/amdsmu/amdsmu.c     | 221 ++++++++++++++++++++++++++++++++++++++++++++
 sys/dev/amdsmu/amdsmu.h     |  50 ++++++++++
 sys/dev/amdsmu/amdsmu_reg.h |  53 +++++++++++
 sys/modules/Makefile        |   2 +
 sys/modules/amdsmu/Makefile |  14 +++
 6 files changed, 341 insertions(+)

diff --git a/sys/conf/files.x86 b/sys/conf/files.x86
index df206b314b38..9976e9cfec5d 100644
--- a/sys/conf/files.x86
+++ b/sys/conf/files.x86
@@ -62,6 +62,7 @@ dev/acpi_support/acpi_wmi_if.m        standard
 dev/agp/agp_amd64.c            optional        agp
 dev/agp/agp_i810.c             optional        agp
 dev/agp/agp_via.c              optional        agp
+dev/amdsmu/amdsmu.c            optional        amdsmu pci
 dev/amdsbwd/amdsbwd.c          optional        amdsbwd
 dev/amdsmn/amdsmn.c            optional        amdsmn | amdtemp
 dev/amdtemp/amdtemp.c          optional        amdtemp
diff --git a/sys/dev/amdsmu/amdsmu.c b/sys/dev/amdsmu/amdsmu.c
new file mode 100644
index 000000000000..84e6c749cca7
--- /dev/null
+++ b/sys/dev/amdsmu/amdsmu.c
@@ -0,0 +1,221 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 The FreeBSD Foundation
+ *
+ * This software was developed by Aymeric Wibo <obi...@freebsd.org>
+ * under sponsorship from the FreeBSD Foundation.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+
+#include <dev/pci/pcivar.h>
+#include <dev/amdsmu/amdsmu.h>
+
+static bool
+amdsmu_match(device_t dev, const struct amdsmu_product **product_out)
+{
+       const uint16_t vendorid = pci_get_vendor(dev);
+       const uint16_t deviceid = pci_get_device(dev);
+
+       for (size_t i = 0; i < nitems(amdsmu_products); i++) {
+               const struct amdsmu_product *prod = &amdsmu_products[i];
+
+               if (vendorid == prod->amdsmu_vendorid &&
+                   deviceid == prod->amdsmu_deviceid) {
+                       if (product_out != NULL)
+                               *product_out = prod;
+                       return (true);
+               }
+       }
+       return (false);
+}
+
+static void
+amdsmu_identify(driver_t *driver, device_t parent)
+{
+       if (device_find_child(parent, "amdsmu", -1) != NULL)
+               return;
+
+       if (amdsmu_match(parent, NULL)) {
+               if (device_add_child(parent, "amdsmu", -1) == NULL)
+                       device_printf(parent, "add amdsmu child failed\n");
+       }
+}
+
+static int
+amdsmu_probe(device_t dev)
+{
+       if (resource_disabled("amdsmu", 0))
+               return (ENXIO);
+       if (!amdsmu_match(device_get_parent(dev), NULL))
+               return (ENXIO);
+       device_set_descf(dev, "AMD System Management Unit");
+
+       return (BUS_PROBE_GENERIC);
+}
+
+static enum amdsmu_res
+amdsmu_wait_res(device_t dev)
+{
+       struct amdsmu_softc *sc = device_get_softc(dev);
+       enum amdsmu_res res;
+
+       /*
+        * The SMU has a response ready for us when the response register is
+        * set.  Otherwise, we must wait.
+        */
+       for (size_t i = 0; i < SMU_RES_READ_MAX; i++) {
+               res = amdsmu_read4(sc, SMU_REG_RESPONSE);
+               if (res != SMU_RES_WAIT)
+                       return (res);
+               pause_sbt("amdsmu", ustosbt(SMU_RES_READ_PERIOD_US), 0,
+                   C_HARDCLOCK);
+       }
+       device_printf(dev, "timed out waiting for response from SMU\n");
+       return (SMU_RES_WAIT);
+}
+
+static int
+amdsmu_cmd(device_t dev, enum amdsmu_msg msg, uint32_t arg, uint32_t *ret)
+{
+       struct amdsmu_softc *sc = device_get_softc(dev);
+       enum amdsmu_res res;
+
+       /* Wait for SMU to be ready. */
+       if (amdsmu_wait_res(dev) == SMU_RES_WAIT)
+               return (ETIMEDOUT);
+
+       /* Clear previous response. */
+       amdsmu_write4(sc, SMU_REG_RESPONSE, SMU_RES_WAIT);
+
+       /* Write out command to registers. */
+       amdsmu_write4(sc, SMU_REG_MESSAGE, msg);
+       amdsmu_write4(sc, SMU_REG_ARGUMENT, arg);
+
+       /* Wait for SMU response and handle it. */
+       res = amdsmu_wait_res(dev);
+
+       switch (res) {
+       case SMU_RES_WAIT:
+               return (ETIMEDOUT);
+       case SMU_RES_OK:
+               if (ret != NULL)
+                       *ret = amdsmu_read4(sc, SMU_REG_ARGUMENT);
+               return (0);
+       case SMU_RES_REJECT_BUSY:
+               device_printf(dev, "SMU is busy\n");
+               return (EBUSY);
+       case SMU_RES_REJECT_PREREQ:
+       case SMU_RES_UNKNOWN:
+       case SMU_RES_FAILED:
+               device_printf(dev, "SMU error: %02x\n", res);
+               return (EIO);
+       }
+
+       return (EINVAL);
+}
+
+static void
+amdsmu_print_vers(device_t dev)
+{
+       uint32_t smu_vers;
+       uint8_t smu_program;
+       uint8_t smu_maj, smu_min, smu_rev;
+
+       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;
+       device_printf(dev, "SMU version: %d.%d.%d (program %d)\n",
+           smu_maj, smu_min, smu_rev, smu_program);
+}
+
+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;
+
+       /*
+        * Find physical base address for SMU.
+        * XXX I am a little confused about the masks here.  I'm just copying
+        * what Linux does in the amd-pmc driver to get the base address.
+        */
+       pci_write_config(dev, SMU_INDEX_ADDRESS, SMU_PHYSBASE_ADDR_LO, 4);
+       physbase_addr_lo = pci_read_config(dev, SMU_INDEX_DATA, 4) & 0xFFF00000;
+
+       pci_write_config(dev, SMU_INDEX_ADDRESS, SMU_PHYSBASE_ADDR_HI, 4);
+       physbase_addr_hi = pci_read_config(dev, SMU_INDEX_DATA, 4) & 0x0000FFFF;
+
+       physbase_addr = (uint64_t)physbase_addr_hi << 32 | physbase_addr_lo;
+
+       /* Map memory for SMU and its registers. */
+       sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+       if (sc->res == NULL) {
+               device_printf(dev, "could not allocate resource\n");
+               return (ENXIO);
+       }
+
+       sc->bus_tag = rman_get_bustag(sc->res);
+
+       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);
+       }
+       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);
+       }
+
+       amdsmu_print_vers(dev);
+       return (0);
+}
+
+static int
+amdsmu_detach(device_t dev)
+{
+       struct amdsmu_softc *sc = device_get_softc(dev);
+       int rid = 0;
+
+       bus_space_unmap(sc->bus_tag, sc->smu_space, SMU_MEM_SIZE);
+       bus_space_unmap(sc->bus_tag, sc->reg_space, SMU_MEM_SIZE);
+
+       bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res);
+       return (0);
+}
+
+static device_method_t amdsmu_methods[] = {
+       DEVMETHOD(device_identify,      amdsmu_identify),
+       DEVMETHOD(device_probe,         amdsmu_probe),
+       DEVMETHOD(device_attach,        amdsmu_attach),
+       DEVMETHOD(device_detach,        amdsmu_detach),
+       DEVMETHOD_END
+};
+
+static driver_t amdsmu_driver = {
+       "amdsmu",
+       amdsmu_methods,
+       sizeof(struct amdsmu_softc),
+};
+
+DRIVER_MODULE(amdsmu, hostb, amdsmu_driver, NULL, NULL);
+MODULE_VERSION(amdsmu, 1);
+MODULE_DEPEND(amdsmu, amdsmn, 1, 1, 1);
+MODULE_PNP_INFO("U16:vendor;U16:device", pci, amdsmu, amdsmu_products,
+    nitems(amdsmu_products));
diff --git a/sys/dev/amdsmu/amdsmu.h b/sys/dev/amdsmu/amdsmu.h
new file mode 100644
index 000000000000..5e76c8e93062
--- /dev/null
+++ b/sys/dev/amdsmu/amdsmu.h
@@ -0,0 +1,50 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 The FreeBSD Foundation
+ *
+ * This software was developed by Aymeric Wibo <obi...@freebsd.org>
+ * under sponsorship from the FreeBSD Foundation.
+ */
+#ifndef _AMDSMU_H_
+#define        _AMDSMU_H_
+
+#include <sys/types.h>
+#include <machine/bus.h>
+#include <x86/cputypes.h>
+
+#include <dev/amdsmu/amdsmu_reg.h>
+
+#define SMU_RES_READ_PERIOD_US 50
+#define SMU_RES_READ_MAX       20000
+
+static const struct amdsmu_product {
+       uint16_t        amdsmu_vendorid;
+       uint16_t        amdsmu_deviceid;
+} amdsmu_products[] = {
+       { CPU_VENDOR_AMD,       PCI_DEVICEID_AMD_REMBRANDT_ROOT },
+       { CPU_VENDOR_AMD,       PCI_DEVICEID_AMD_PHOENIX_ROOT },
+       { CPU_VENDOR_AMD,       PCI_DEVICEID_AMD_STRIX_POINT_ROOT },
+};
+
+struct amdsmu_softc {
+       struct resource         *res;
+       bus_space_tag_t         bus_tag;
+
+       bus_space_handle_t      smu_space;
+       bus_space_handle_t      reg_space;
+};
+
+static inline uint32_t
+amdsmu_read4(const struct amdsmu_softc *sc, bus_size_t reg)
+{
+       return (bus_space_read_4(sc->bus_tag, sc->reg_space, reg));
+}
+
+static inline void
+amdsmu_write4(const struct amdsmu_softc *sc, bus_size_t reg, uint32_t val)
+{
+       bus_space_write_4(sc->bus_tag, sc->reg_space, reg, val);
+}
+
+#endif /* _AMDSMU_H_ */
diff --git a/sys/dev/amdsmu/amdsmu_reg.h b/sys/dev/amdsmu/amdsmu_reg.h
new file mode 100644
index 000000000000..5e73cea19028
--- /dev/null
+++ b/sys/dev/amdsmu/amdsmu_reg.h
@@ -0,0 +1,53 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 The FreeBSD Foundation
+ *
+ * This software was developed by Aymeric Wibo <obi...@freebsd.org>
+ * under sponsorship from the FreeBSD Foundation.
+ */
+#ifndef _AMDSMU_REG_H_
+#define        _AMDSMU_REG_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
+ * which lists them though.
+ */
+#define PCI_DEVICEID_AMD_REMBRANDT_ROOT                0x14B5
+#define PCI_DEVICEID_AMD_PHOENIX_ROOT          0x14E8
+#define PCI_DEVICEID_AMD_STRIX_POINT_ROOT      0x14A4
+
+#define SMU_INDEX_ADDRESS      0xB8
+#define SMU_INDEX_DATA         0xBC
+
+#define SMU_PHYSBASE_ADDR_LO   0x13B102E8
+#define SMU_PHYSBASE_ADDR_HI   0x13B102EC
+
+#define SMU_MEM_SIZE           0x1000
+#define SMU_REG_SPACE_OFF      0x10000
+
+#define SMU_REG_MESSAGE                0x538
+#define SMU_REG_RESPONSE       0x980
+#define SMU_REG_ARGUMENT       0x9BC
+
+enum amdsmu_res {
+       SMU_RES_WAIT            = 0x00,
+       SMU_RES_OK              = 0x01,
+       SMU_RES_REJECT_BUSY     = 0xFC,
+       SMU_RES_REJECT_PREREQ   = 0xFD,
+       SMU_RES_UNKNOWN         = 0xFE,
+       SMU_RES_FAILED          = 0xFF,
+};
+
+enum amdsmu_msg {
+       SMU_MSG_GETSMUVERSION           = 0x02,
+       SMU_MSG_LOG_GETDRAM_ADDR_HI     = 0x04,
+       SMU_MSG_LOG_GETDRAM_ADDR_LO     = 0x05,
+       SMU_MSG_LOG_START               = 0x06,
+       SMU_MSG_LOG_RESET               = 0x07,
+       SMU_MSG_LOG_DUMP_DATA           = 0x08,
+       SMU_MSG_GET_SUP_CONSTRAINTS     = 0x09,
+};
+
+#endif /* _AMDSMU_REG_H_ */
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
index 7cb6e2124326..99c9ec9dcd01 100644
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -34,6 +34,7 @@ SUBDIR=       \
        alq \
        ${_amd_ecc_inject} \
        ${_amdgpio} \
+       ${_amdsmu} \
        ${_amdsbwd} \
        ${_amdsmn} \
        ${_amdtemp} \
@@ -772,6 +773,7 @@ _acpi=              acpi
 _aesni=                aesni
 .endif
 _amd_ecc_inject=amd_ecc_inject
+_amdsmu=       amdsmu
 _amdsbwd=      amdsbwd
 _amdsmn=       amdsmn
 _amdtemp=      amdtemp
diff --git a/sys/modules/amdsmu/Makefile b/sys/modules/amdsmu/Makefile
new file mode 100644
index 000000000000..752f57173d61
--- /dev/null
+++ b/sys/modules/amdsmu/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2025 The FreeBSD Foundation
+#
+# This software was developed by Aymeric Wibo <obi...@freebsd.org>
+# under sponsorship from the FreeBSD Foundation.
+
+.PATH: ${SRCTOP}/sys/dev/amdsmu
+
+KMOD=  amdsmu
+SRCS=  amdsmu.c
+SRCS+= bus_if.h device_if.h pci_if.h
+
+.include <bsd.kmod.mk>

Reply via email to