On Thu, Jul 16, 2026 at 1:49 AM Galantsev, Dmitrii
<[email protected]> wrote:
>
> 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;
I'd move this into its own struct defined in cuid.h and then include
that here. I think you probably want to store the cached primary and
secondary ids as well.
> 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
Please use MIT instead.
> +/*
> + * 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";
Check with Tony. I think he had proposed a default seed value.
> +
> +/* 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;
> +}
I'd like to try and keep the amdgpu details out of the cuid library.
Ideally this library could be made into a standalone module eventually
that could be used by other drivers that want to implement cuid. For
example, make the function take an optional serial number as a
parameter which will get used if the caller specifies it, otherwise,
it will use the DSN.
I'd also suggest caching the primary and secondary CUIDs and only
regenerating them on driver load or if the seed changes. For storing
the seed value, I think we had talked about using
efi.get_variable()/efi.set_variable()
> +
> +/*
> + * 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);
> +}
Make these amdgpu independent. Then any device can call them.
> 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 */
MIT please.
Thanks,
Alex
> +/*
> + * 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
>