[PATCH] drm/amdgpu: add Component Unified ID (CUID) sysfs interface
"Galantsev, Dmitrii" <[email protected]>
| Newsgroups | org.freedesktop.lists.amd-gfx |
|---|---|
| Message-ID | <[email protected]> |
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