From: Dmitrii Galantsev <[email protected]>

Expose a stable, per-GPU identifier so fleet and inventory tooling can name
a device without reading the raw serial number.

A 122-bit primary payload (serial, PCI vendor/device/revision, unit and
component type) is packed LSB-first and rendered as an RFC 9562 UUIDv8.
Three sysfs attributes sit beside unique_id:

  - cuid_primary (0400): raw identity; root-only as it embeds the serial.
  - cuid_secondary (0444): HMAC-SHA256(seed, primary), world-readable as it
    leaks neither the serial nor the seed. A public default seed is used
    until a secret is provisioned via cuid_seed.
  - cuid_seed (0600): the secret seed, raw bytes (at most 32).

Derivation uses the synchronous library HMAC-SHA256 (crypto/sha2.h) on the
read path; the only persistent state is the seed, guarded by a mutex. CUID
init is best-effort and does not fail device attribute init.

Signed-off-by: Dmitrii Galantsev <[email protected]>
---
 Documentation/gpu/amdgpu/driver-misc.rst   |  21 ++
 drivers/gpu/drm/amd/amdgpu/Kconfig         |   2 +
 drivers/gpu/drm/amd/amdgpu/Makefile        |   1 +
 drivers/gpu/drm/amd/amdgpu/amdgpu.h        |   5 +
 drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.c   | 278 +++++++++++++++++++++
 drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.h   |  45 ++++
 drivers/gpu/drm/amd/amdgpu/amdgpu_device.c |  13 +-
 7 files changed, 364 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.c
 create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.h

diff --git a/Documentation/gpu/amdgpu/driver-misc.rst 
b/Documentation/gpu/amdgpu/driver-misc.rst
index e1a964c3add2..983358ab4c4c 100644
--- a/Documentation/gpu/amdgpu/driver-misc.rst
+++ b/Documentation/gpu/amdgpu/driver-misc.rst
@@ -154,3 +154,24 @@ uma/carveout
 
 .. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
    :doc: uma/carveout
+
+Component Unified ID (CUID) Information
+======================================
+
+cuid_primary
+------------
+
+.. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.c
+   :doc: cuid_primary
+
+cuid_secondary
+--------------
+
+.. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.c
+   :doc: cuid_secondary
+
+cuid_seed
+---------
+
+.. kernel-doc:: drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.c
+   :doc: cuid_seed
diff --git a/drivers/gpu/drm/amd/amdgpu/Kconfig 
b/drivers/gpu/drm/amd/amdgpu/Kconfig
index 12e4a41bf1f0..1430096412ec 100644
--- a/drivers/gpu/drm/amd/amdgpu/Kconfig
+++ b/drivers/gpu/drm/amd/amdgpu/Kconfig
@@ -26,6 +26,8 @@ config DRM_AMDGPU
        select DRM_SUBALLOC_HELPER
        select DRM_EXEC
        select DRM_PANEL_BACKLIGHT_QUIRKS
+       # CUID derives its secondary ID with library HMAC-SHA256
+       select CRYPTO_LIB_SHA256
        # amdgpu depends on ACPI_VIDEO when ACPI is enabled, for select to work
        # ACPI_VIDEO's dependencies must also be selected.
        select INPUT if ACPI
diff --git a/drivers/gpu/drm/amd/amdgpu/Makefile 
b/drivers/gpu/drm/amd/amdgpu/Makefile
index 105b9dcc19a2..66f4476966cc 100644
--- a/drivers/gpu/drm/amd/amdgpu/Makefile
+++ b/drivers/gpu/drm/amd/amdgpu/Makefile
@@ -52,6 +52,7 @@ amdgpu-y := amdgpu_drv.o
 
 # add KMS driver
 amdgpu-y += amdgpu_device.o amdgpu_reg_access.o amdgpu_doorbell_mgr.o 
amdgpu_kms.o \
+       amdgpu_cuid.o \
        amdgpu_atombios.o atombios_crtc.o amdgpu_connectors.o \
        atom.o amdgpu_fence.o amdgpu_ttm.o amdgpu_object.o amdgpu_gart.o \
        amdgpu_encoders.o amdgpu_display.o amdgpu_i2c.o \
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h 
b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
index f5d65bd0ac25..90abbdfcec5b 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
@@ -872,6 +872,11 @@ struct amdgpu_device {
 
        uint64_t                        unique_id;
        uint8_t                         unitid;
+
+       /* CUID: secret seed for HMAC-derived secondary CUID */
+       struct mutex                    cuid_seed_lock;
+       u8                              cuid_seed[32];
+       unsigned int                    cuid_seed_len;
        uint64_t        df_perfmon_config_assign_mask[AMDGPU_MAX_DF_PERFMONS];
 
        /* enable runtime pm on the device */
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.c 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.c
new file mode 100644
index 000000000000..a365f8ab4591
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CUID (Canonical Unique ID) support for amdgpu.
+ *
+ * Builds a primary identifier from the GPU identity (vendor/device/revision 
and
+ * the serial in adev->unique_id) and exposes it, plus an HMAC-derived 
secondary
+ * value, as UUIDv8 sysfs files beside unique_id.
+ *
+ * The packing, UUIDv8 framing and HMAC derivation form a frozen ABI: the
+ * payload is packed LSB-first into 16 little-endian bytes, framed as an
+ * RFC 9562 UUIDv8, and derived as HMAC-SHA256(key, primary). Derivation uses
+ * the synchronous library HMAC-SHA256 (crypto/sha2.h), so it cannot fail and 
is
+ * callable from the sysfs read path. The only persistent state is the secret
+ * seed (adev->cuid_seed*, guarded by adev->cuid_seed_lock).
+ */
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/capability.h>
+#include <linux/pci.h>
+#include <linux/string.h>
+#include <linux/uuid.h>
+#include <linux/mutex.h>
+#include <linux/unaligned.h>
+#include <crypto/sha2.h>
+
+#include <drm/drm_drv.h>
+
+#include "amdgpu.h"
+#include "amdgpu_cuid.h"
+
+/* Public default seed for cuid_secondary until a secret is provisioned. */
+static const u8 CUID_DEFAULT_SEED[] = "AMD-CUID-DEFAULT-SEED-v1";
+
+/* Pack the 122-bit primary payload into 16 little-endian bytes. */
+static void cuid_pack(const struct cuid_primary *p, u8 raw[16])
+{
+       put_unaligned_le64(p->dsn, raw);        /* 0:63 DSN */
+
+       raw[8]  = p->unit_id & 0xFF;            /* 64:71 UnitID lo */
+       raw[9]  = p->revision_id;               /* 72:79 revision */
+       put_unaligned_le16(p->device_id, &raw[10]);     /* 80:95 device */
+       put_unaligned_le16(p->vendor_id, &raw[12]);     /* 96:111 vendor */
+
+       /* 112:116 UnitID hi | 117 aux | 118:121 component type */
+       raw[14] = ((p->unit_id >> 8) & 0x1F) |
+                 ((p->aux & 0x1) << 5) |
+                 ((p->component_type & 0x03) << 6);
+       raw[15] = (p->component_type >> 2) & 0x03;
+}
+
+/* UUIDv8 framing bits: version nibble (76:79), variant bits (62:63). */
+static bool cuid_is_framing_bit(int pos)
+{
+       return pos == 62 || pos == 63 || (pos >= 76 && pos <= 79);
+}
+
+/* Insert RFC 9562 UUIDv8 version(8)/variant(10b) framing losslessly. */
+static void cuid_to_uuidv8(const u8 raw[16], u8 uuid[16])
+{
+       int src = 0, pos;
+
+       memset(uuid, 0, 16);
+       for (pos = 0; pos < 128; pos++) {
+               if (cuid_is_framing_bit(pos))
+                       continue;
+               if ((raw[src >> 3] >> (src & 7)) & 1)
+                       uuid[15 - (pos >> 3)] |= 1u << (pos & 7);
+               src++;
+       }
+       uuid[6] = (uuid[6] & 0x0F) | 0x80;      /* version 8 */
+       uuid[8] = (uuid[8] & 0x3F) | 0x80;      /* variant 10b */
+}
+
+/*
+ * Derive the secondary payload from HMAC-SHA256(key, raw_primary):
+ *   0:63    = hash[0:63]
+ *   72:116  = hash[64:108]    (45 bits)
+ *   117     = aux (from primary); all other bits zero.
+ */
+static void cuid_derive(const u8 *key, unsigned int keylen,
+                       const u8 raw_primary[16], u8 derived_raw[16])
+{
+       u8 digest[SHA256_DIGEST_SIZE];
+       int i;
+
+       hmac_sha256_usingrawkey(key, keylen, raw_primary, 16, digest);
+
+       memset(derived_raw, 0, 16);
+
+       /* hash[0:63] -> derived[0:63] */
+       memcpy(derived_raw, digest, 8);
+
+       /* hash[64:108] -> derived[72:116] */
+       for (i = 0; i < 45; i++) {
+               int hb = 64 + i, db = 72 + i;
+               u8 bit = (digest[hb >> 3] >> (hb & 7)) & 1;
+
+               derived_raw[db >> 3] |= bit << (db & 7);
+       }
+
+       /* aux (bit 117) copied from primary; same position in both buffers */
+       derived_raw[14] |= raw_primary[14] & BIT(5);
+
+       memzero_explicit(digest, sizeof(digest));
+}
+
+static void amdgpu_cuid_build_primary(struct amdgpu_device *adev,
+                                     struct cuid_primary *p)
+{
+       u64 dsn = 0;
+
+       memset(p, 0, sizeof(*p));
+
+       if (adev->pdev) {
+               p->vendor_id = adev->pdev->vendor;
+               p->device_id = adev->pdev->device;
+               p->revision_id = adev->pdev->revision;
+
+               /* Prefer the PCIe DSN capability, else the SMU serial. */
+               dsn = pci_get_dsn(adev->pdev);
+       }
+       if (!dsn)
+               dsn = adev->unique_id;
+
+       p->dsn = dsn;
+       /* aux (bit 117): set when no genuine serial was found (dsn == 0). */
+       p->aux = (dsn == 0);
+       p->component_type = CUID_COMPONENT_GPU;
+}
+
+/*
+ * DOC: cuid_primary
+ *
+ * Primary CUID as a UUIDv8. Embeds the raw serial, so it is root-only (0400).
+ */
+static ssize_t cuid_primary_show(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       struct drm_device *ddev = dev_get_drvdata(dev);
+       struct amdgpu_device *adev = drm_to_adev(ddev);
+       struct cuid_primary primary;
+       u8 raw[16], uuid[16];
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       amdgpu_cuid_build_primary(adev, &primary);
+       cuid_pack(&primary, raw);
+       cuid_to_uuidv8(raw, uuid);
+
+       return sysfs_emit(buf, "%pUb\n", uuid);
+}
+
+static DEVICE_ATTR(cuid_primary, 0400, cuid_primary_show, NULL);
+
+/*
+ * DOC: cuid_secondary
+ *
+ * HMAC-SHA256(seed, primary) as a UUIDv8. Leaks neither the serial nor the
+ * seed, so it is world-readable (0444). Without a provisioned seed a public
+ * default is used: stable but not fleet-unique, so treat it as a placeholder.
+ */
+static ssize_t cuid_secondary_show(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct drm_device *ddev = dev_get_drvdata(dev);
+       struct amdgpu_device *adev = drm_to_adev(ddev);
+       struct cuid_primary primary;
+       u8 raw[16], derived[16], uuid[16];
+       u8 seed[32];
+       unsigned int seedlen;
+
+       amdgpu_cuid_build_primary(adev, &primary);
+       cuid_pack(&primary, raw);
+
+       mutex_lock(&adev->cuid_seed_lock);
+       if (adev->cuid_seed_len) {
+               seedlen = adev->cuid_seed_len;
+               memcpy(seed, adev->cuid_seed, sizeof(seed));
+       } else {
+               /* default seed, excluding the string's NUL terminator */
+               seedlen = sizeof(CUID_DEFAULT_SEED) - 1;
+               memcpy(seed, CUID_DEFAULT_SEED, seedlen);
+       }
+       mutex_unlock(&adev->cuid_seed_lock);
+
+       cuid_derive(seed, seedlen, raw, derived);
+       memzero_explicit(seed, sizeof(seed));
+
+       cuid_to_uuidv8(derived, uuid);
+       return sysfs_emit(buf, "%pUb\n", uuid);
+}
+
+static DEVICE_ATTR_RO(cuid_secondary);
+
+/*
+ * DOC: cuid_seed
+ *
+ * Root-only (0600) secret seed for cuid_secondary, as raw bytes (at most 32).
+ * Reading returns the seed in use (the provisioned secret, or the default);
+ * read and write use the same encoding, so the value round-trips.
+ */
+static ssize_t cuid_seed_show(struct device *dev,
+                             struct device_attribute *attr, char *buf)
+{
+       struct drm_device *ddev = dev_get_drvdata(dev);
+       struct amdgpu_device *adev = drm_to_adev(ddev);
+       unsigned int seedlen;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       mutex_lock(&adev->cuid_seed_lock);
+       if (adev->cuid_seed_len) {
+               seedlen = adev->cuid_seed_len;
+               memcpy(buf, adev->cuid_seed, seedlen);
+       } else {
+               /* default seed, excluding the string's NUL terminator */
+               seedlen = sizeof(CUID_DEFAULT_SEED) - 1;
+               memcpy(buf, CUID_DEFAULT_SEED, seedlen);
+       }
+       mutex_unlock(&adev->cuid_seed_lock);
+
+       /* Raw bytes so the value round-trips through a write. */
+       return seedlen;
+}
+
+static ssize_t cuid_seed_store(struct device *dev,
+                              struct device_attribute *attr,
+                              const char *buf, size_t count)
+{
+       struct drm_device *ddev = dev_get_drvdata(dev);
+       struct amdgpu_device *adev = drm_to_adev(ddev);
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+       if (count > sizeof(adev->cuid_seed))
+               return -EINVAL;
+
+       mutex_lock(&adev->cuid_seed_lock);
+       memset(adev->cuid_seed, 0, sizeof(adev->cuid_seed));
+       memcpy(adev->cuid_seed, buf, count);
+       adev->cuid_seed_len = count;
+       mutex_unlock(&adev->cuid_seed_lock);
+
+       return count;
+}
+
+static DEVICE_ATTR(cuid_seed, 0600, cuid_seed_show, cuid_seed_store);
+
+static struct attribute *amdgpu_cuid_attrs[] = {
+       &dev_attr_cuid_primary.attr,
+       &dev_attr_cuid_secondary.attr,
+       &dev_attr_cuid_seed.attr,
+       NULL,
+};
+
+static const struct attribute_group amdgpu_cuid_group = {
+       .attrs = amdgpu_cuid_attrs,
+};
+
+int amdgpu_cuid_sysfs_init(struct amdgpu_device *adev)
+{
+       mutex_init(&adev->cuid_seed_lock);
+       adev->cuid_seed_len = 0;
+
+       /* On failure the lock is torn down by amdgpu_cuid_sysfs_fini(). */
+       return sysfs_create_group(&adev->dev->kobj, &amdgpu_cuid_group);
+}
+
+void amdgpu_cuid_sysfs_fini(struct amdgpu_device *adev)
+{
+       sysfs_remove_group(&adev->dev->kobj, &amdgpu_cuid_group);
+
+       memzero_explicit(adev->cuid_seed, sizeof(adev->cuid_seed));
+       adev->cuid_seed_len = 0;
+       mutex_destroy(&adev->cuid_seed_lock);
+}
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.h 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.h
new file mode 100644
index 000000000000..473ba3a4ab8f
--- /dev/null
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cuid.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * CUID (Canonical Unique ID) support for amdgpu.
+ *
+ * Builds a 122-bit primary identifier from the per-GPU hardware identity and
+ * exposes it as a UUIDv8, together with an HMAC-derived secondary CUID.
+ */
+#ifndef __AMDGPU_CUID_H__
+#define __AMDGPU_CUID_H__
+
+#include <linux/types.h>
+
+struct amdgpu_device;
+
+/*
+ * 122-bit primary CUID layout (frozen ABI):
+ *
+ *   0:63     Device Serial Number (amdgpu: adev->unique_id)
+ *   64:71    UnitID low 8 bits
+ *   72:79    RevisionID
+ *   80:95    DeviceID
+ *   96:111   VendorID
+ *   112:116  UnitID high 5 bits
+ *   117      Auxiliary Indicator
+ *   118:121  Component Type
+ *
+ * Packed with explicit shift/mask (not C bitfields) for a stable ABI.
+ */
+struct cuid_primary {
+       u64 dsn;
+       u16 vendor_id;
+       u16 device_id;
+       u16 unit_id;            /* 13 bits */
+       u8 revision_id;
+       u8 component_type;      /* 4 bits */
+       u8 aux;                 /* bit 117 */
+} __packed;
+
+/* Component Type values (bits 118:121). */
+#define CUID_COMPONENT_GPU     0x2
+
+int amdgpu_cuid_sysfs_init(struct amdgpu_device *adev);
+void amdgpu_cuid_sysfs_fini(struct amdgpu_device *adev);
+
+#endif /* __AMDGPU_CUID_H__ */
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c 
b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
index 472e96ae884e..760103641592 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
@@ -48,6 +48,7 @@
 #include <linux/vga_switcheroo.h>
 #include <linux/efi.h>
 #include "amdgpu.h"
+#include "amdgpu_cuid.h"
 #include "amdgpu_trace.h"
 #include "amdgpu_i2c.h"
 #include "atom.h"
@@ -246,8 +247,16 @@ static int amdgpu_device_attr_sysfs_init(struct 
amdgpu_device *adev)
        if (amdgpu_nbio_is_replay_cnt_supported(adev))
                ret = sysfs_create_file(&adev->dev->kobj,
                                        &dev_attr_pcie_replay_count.attr);
+       if (ret)
+               return ret;
 
-       return ret;
+       /* CUID is informational; a failure here should not fail device init. */
+       ret = amdgpu_cuid_sysfs_init(adev);
+       if (ret)
+               dev_warn(adev->dev, "failed to create CUID sysfs attributes: 
%d\n",
+                        ret);
+
+       return 0;
 }
 
 static void amdgpu_device_attr_sysfs_fini(struct amdgpu_device *adev)
@@ -255,6 +264,8 @@ static void amdgpu_device_attr_sysfs_fini(struct 
amdgpu_device *adev)
        if (amdgpu_nbio_is_replay_cnt_supported(adev))
                sysfs_remove_file(&adev->dev->kobj,
                                  &dev_attr_pcie_replay_count.attr);
+
+       amdgpu_cuid_sysfs_fini(adev);
 }
 
 static ssize_t amdgpu_sysfs_reg_state_get(struct file *f, struct kobject *kobj,
-- 
2.54.0

Reply via email to