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