diff --git a/include/chid.h b/include/chid.h
index 5746ba88c32..0ea2053b08c 100644
--- a/include/chid.h
+++ b/include/chid.h
@@ -50,6 +50,31 @@ enum chid_field_t {
 	CHID_COUNT,
 };
 
+/*
+ * enum chid_variant_id - Microsoft CHID hardware ID variants
+ *
+ * This covers HardwareID-00 through HardwareID-14
+ */
+enum chid_variant_id {
+	CHID_00,	/* Most specific */
+	CHID_01,
+	CHID_02,
+	CHID_03,
+	CHID_04,
+	CHID_05,
+	CHID_06,
+	CHID_07,
+	CHID_08,
+	CHID_09,
+	CHID_10,
+	CHID_11,
+	CHID_12,
+	CHID_13,
+	CHID_14,	/* Least specific */
+	
+	CHID_VARIANT_COUNT
+};
+
 /**
  * struct chid_variant - defines which fields are used in each CHID variant
  *
@@ -103,4 +128,33 @@ struct chid_data {
  */
 int chid_from_smbios(struct chid_data *chid);
 
+/**
+ * chid_generate() - Generate a specific CHID variant
+ *
+ * @variant: Which CHID variant to generate (0-14)
+ * @data: SMBIOS data to use for generation
+ * @chid: Output buffer for the generated CHID (16 bytes)
+ *
+ * Return: 0 if OK, -ve error code on failure
+ */
+int chid_generate(int variant, const struct chid_data *data, u8 chid[16]);
+
+/**
+ * chid_get_field_name() - Get display name of a specific CHID field
+ *
+ * @field: Which CHID field
+ *
+ * Return: String containing the field name
+ */
+const char *chid_get_field_name(enum chid_field_t field);
+
+/**
+ * chid_get_variant_fields() - Get the fields mask for a CHID variant
+ *
+ * @variant: Which CHID variant (0-14)
+ *
+ * Return: Bitmask of fields used by this variant
+ */
+u32 chid_get_variant_fields(int variant);
+
 #endif
diff --git a/lib/chid.c b/lib/chid.c
index 10e9b8fecae..1522ae04c79 100644
--- a/lib/chid.c
+++ b/lib/chid.c
@@ -9,6 +9,9 @@
  * See: https://docs.microsoft.com/en-us/windows-hardware/drivers/install/specifying-hardware-ids-for-a-computer
  *
  * Copyright 2025 Simon Glass <sjg@chromium.org>
+ *
+ * Credit: Richard Hughes
+ * https://blogs.gnome.org/hughsie/2017/04/25/reverse-engineering-computerhardwareids-exe-with-winedbg/
  */
 
 #define LOG_CATEGORY	LOGC_ACPI
@@ -17,9 +20,111 @@
 #include <errno.h>
 #include <smbios.h>
 #include <asm/global_data.h>
+#include <linux/bitops.h>
+#include <linux/utf.h>
+#include <u-boot/uuid.h>
 
 DECLARE_GLOBAL_DATA_PTR;
 
+/* field names for display purposes */
+static const char *fields[CHID_COUNT] = {
+	[CHID_MANUF] = "Manufacturer",
+	[CHID_FAMILY] = "Family",
+	[CHID_PRODUCT_NAME] = "ProductName",
+	[CHID_PRODUCT_SKU] = "ProductSku",
+	[CHID_BOARD_MANUF] = "BaseboardManufacturer",
+	[CHID_BOARD_PRODUCT] = "BaseboardProduct",
+	[CHID_BIOS_VENDOR] = "BiosVendor",
+	[CHID_BIOS_VERSION] = "BiosVersion",
+	[CHID_BIOS_MAJOR] = "BiosMajorRelease",
+	[CHID_BIOS_MINOR] = "BiosMinorRelease",
+	[CHID_ENCLOSURE_TYPE] = "EnclosureKind",
+};
+
+/*
+ * Microsoft CHID variants table
+ *
+ * Each entry defines which SMBIOS fields are combined to create
+ * a specific Hardware ID variant. The variants are ordered from
+ * most specific (HardwareID-00) to least specific (HardwareID-14).
+ */
+static const struct chid_variant variants[CHID_VARIANT_COUNT] = {{
+	/* HardwareID-00: Most specific - includes all identifying fields */
+	.name = "HardwareID-00",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_PRODUCT_NAME) |
+		BIT(CHID_PRODUCT_SKU) | BIT(CHID_BIOS_VENDOR) |
+		BIT(CHID_BIOS_VERSION) | BIT(CHID_BIOS_MAJOR) |
+		BIT(CHID_BIOS_MINOR),
+	}, {
+	/* HardwareID-01: Without SKU */
+	.name = "HardwareID-01",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_PRODUCT_NAME) |
+		BIT(CHID_BIOS_VENDOR) | BIT(CHID_BIOS_VERSION) |
+		BIT(CHID_BIOS_MAJOR) | BIT(CHID_BIOS_MINOR),
+	}, {
+	/* HardwareID-02: Without family */
+	.name = "HardwareID-02",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_PRODUCT_NAME) |
+		BIT(CHID_BIOS_VENDOR) | BIT(CHID_BIOS_VERSION) |
+		BIT(CHID_BIOS_MAJOR) | BIT(CHID_BIOS_MINOR),
+	}, {
+	/* HardwareID-03: With baseboard info, no BIOS version */
+	.name = "HardwareID-03",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_PRODUCT_NAME) |
+		BIT(CHID_PRODUCT_SKU) | BIT(CHID_BOARD_MANUF) |
+		BIT(CHID_BOARD_PRODUCT),
+	}, {
+	/* HardwareID-04: Basic product identification */
+	.name = "HardwareID-04",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_PRODUCT_NAME) |
+		BIT(CHID_PRODUCT_SKU),
+	}, {
+	/* HardwareID-05: Without SKU */
+	.name = "HardwareID-05",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_PRODUCT_NAME),
+	}, {
+	/* HardwareID-06: SKU with baseboard */
+	.name = "HardwareID-06",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_PRODUCT_SKU) |
+		BIT(CHID_BOARD_MANUF) | BIT(CHID_BOARD_PRODUCT),
+	}, {
+	/* HardwareID-07: Just manufacturer and SKU */
+	.name = "HardwareID-07",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_PRODUCT_SKU),
+	}, {
+	/* HardwareID-08: Product name with baseboard */
+	.name = "HardwareID-08",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_PRODUCT_NAME) |
+		BIT(CHID_BOARD_MANUF) | BIT(CHID_BOARD_PRODUCT),
+	}, {
+	/* HardwareID-09: Just manufacturer and product name */
+	.name = "HardwareID-09",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_PRODUCT_NAME),
+	}, {
+	/* HardwareID-10: Family with baseboard */
+	.name = "HardwareID-10",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_BOARD_MANUF) |
+		BIT(CHID_BOARD_PRODUCT),
+	}, {
+	/* HardwareID-11: Just manufacturer and family */
+	.name = "HardwareID-11",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY),
+	}, {
+	/* HardwareID-12: Manufacturer and enclosure type */
+	.name = "HardwareID-12",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_ENCLOSURE_TYPE),
+	}, {
+	/* HardwareID-13: Manufacturer with baseboard only */
+	.name = "HardwareID-13",
+	.fields = BIT(CHID_MANUF) | BIT(CHID_BOARD_MANUF) |
+		BIT(CHID_BOARD_PRODUCT),
+	}, {
+	/* HardwareID-14: Least specific - manufacturer only */
+	.name = "HardwareID-14",
+	.fields = BIT(CHID_MANUF),
+	}
+};
+
 int chid_from_smbios(struct chid_data *chid)
 {
 	const struct smbios_type0 *bios;
@@ -71,3 +176,123 @@ int chid_from_smbios(struct chid_data *chid)
 
 	return 0;
 }
+
+/**
+ * add_item() - Add a string to a buffer if the field is enabled
+ *
+ * Adds a string and then an '&', but only if the field is enabled in the mask
+ *
+ * @ptr: Current position in the buffer
+ * @end: Pointer to the end of the buffer (one past the last byte)
+ * @fields: Bitmask of enabled fields
+ * @field: Which field this is (CHID_xxx)
+ * @str: String to add, or NULL if none, in which case nothing is added
+ * Return: Pointer updated to after the written string and '&' (non-terminated),
+ * or @end if out of space
+ */
+static char *add_item(char *ptr, char *end, u32 fields, enum chid_field_t field,
+		      const char *str)
+{
+	char trimmed[256];
+	int len;
+
+	if (!(fields & BIT(field)) || !str)
+		return ptr;
+
+	/* Copy string to temporary buffer and trim spaces */
+	strlcpy(trimmed, str, sizeof(trimmed));
+	str = strim(trimmed);
+	if (!*str)
+		return ptr;
+
+	len = strlen(str);
+	if (end - ptr <= len + 1)
+		return end;
+	memcpy(ptr, str, len);
+	ptr += len;
+	*ptr++ = '&';
+
+	return ptr;
+}
+
+int chid_generate(int variant, const struct chid_data *data, u8 chid[16])
+{
+	const struct chid_variant *var;
+	struct uuid namespace = {
+		.time_low = cpu_to_be32(0x70ffd812),
+		.time_mid = cpu_to_be16(0x4c7f),
+		.time_hi_and_version = cpu_to_be16(0x4c7d),
+	};
+	__le16 utf16_data[1024];
+	char *ptr, *end;
+	int utf16_chars;
+	char str[512];
+	u32 fields;
+
+	/* Validate input parameters */
+	if (variant < 0 || variant >= CHID_VARIANT_COUNT || !data || !chid)
+		return -EINVAL;
+
+	var = &variants[variant];
+	fields = var->fields;
+	ptr = str;
+	end = str + sizeof(str) - 1;
+
+	/* build the input string based on the variant's field mask */
+	ptr = add_item(ptr, end, fields, CHID_MANUF, data->manuf);
+	ptr = add_item(ptr, end, fields, CHID_FAMILY, data->family);
+	ptr = add_item(ptr, end, fields, CHID_PRODUCT_NAME, data->product_name);
+	ptr = add_item(ptr, end, fields, CHID_PRODUCT_SKU, data->product_sku);
+	ptr = add_item(ptr, end, fields, CHID_BOARD_MANUF, data->board_manuf);
+	ptr = add_item(ptr, end, fields, CHID_BOARD_PRODUCT,
+		       data->board_product);
+	ptr = add_item(ptr, end, fields, CHID_BIOS_VENDOR, data->bios_vendor);
+	ptr = add_item(ptr, end, fields, CHID_BIOS_VERSION, data->bios_version);
+	ptr = add_item(ptr, end, fields, CHID_BIOS_MAJOR,
+		       simple_itoa(data->bios_major));
+	ptr = add_item(ptr, end, fields, CHID_BIOS_MINOR,
+		       simple_itoa(data->bios_minor));
+	ptr = add_item(ptr, end, fields, CHID_ENCLOSURE_TYPE,
+		       simple_itoa(data->enclosure_type));
+
+	/* Check if we ran out of buffer space */
+	if (ptr == end)
+		return log_msg_ret("cgs", -ENOSPC);
+
+	/* If no fields were added, we can't generate a CHID */
+	if (ptr == str)
+		return log_msg_ret("cgn", -ENODATA);
+
+	/* remove the trailing '&' and nul-terminate the string */
+	ptr--;
+	*ptr = '\0';
+
+	/*
+	 * convert to UTF-16LE and generate v5 UUID using Microsoft's namespace
+	 * This matches Microsoft's ComputerHardwareIds.exe implementation
+	 */
+	utf16_chars = utf8_to_utf16le(str, utf16_data, ARRAY_SIZE(utf16_data));
+	if (utf16_chars < 0)
+		return log_msg_ret("cgu", -ENOMEM);
+
+	gen_v5_guid_be(&namespace, (struct efi_guid *)chid, utf16_data,
+		       utf16_chars * 2, NULL);
+
+	return 0;
+}
+
+const char *chid_get_field_name(enum chid_field_t field)
+{
+	if (field >= CHID_COUNT)
+		return "Unknown";
+
+	return fields[field];
+}
+
+u32 chid_get_variant_fields(int variant)
+{
+	if (variant < 0 || variant >= CHID_VARIANT_COUNT)
+		return 0;
+
+	return variants[variant].fields;
+}
diff --git a/test/lib/Makefile b/test/lib/Makefile
index c53ceab955e..5c89421918b 100644
--- a/test/lib/Makefile
+++ b/test/lib/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_UT_LIB_CRYPT) += test_crypt.o
 obj-$(CONFIG_UT_TIME) += time.o
 obj-$(CONFIG_$(PHASE_)UT_UNICODE) += unicode.o
 obj-$(CONFIG_LIB_UUID) += uuid.o
+obj-$(CONFIG_CHID) += chid.o
 else
 obj-$(CONFIG_SANDBOX) += kconfig_spl.o
 endif
diff --git a/test/lib/chid.c b/test/lib/chid.c
new file mode 100644
index 00000000000..3250d4eb624
--- /dev/null
+++ b/test/lib/chid.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Tests for Computer Hardware Identifiers (Windows CHID) support
+ *
+ * Copyright 2025 Simon Glass <sjg@chromium.org>
+ */
+
+#include <chid.h>
+#include <test/lib.h>
+#include <test/test.h>
+#include <test/ut.h>
+#include <u-boot/uuid.h>
+#include <string.h>
+
+static int chid_basic(struct unit_test_state *uts)
+{
+	struct chid_data data = {
+		.manuf = "Test Manufacturer",
+		.product_name = "Test Product",
+		.family = "Test Family",
+		.product_sku = "Test SKU",
+		.bios_vendor = "Test BIOS Vendor",
+		.bios_version = "1.0.0",
+		.bios_major = 1,
+		.bios_minor = 0,
+	};
+	u8 chid[UUID_LEN];
+
+	/* Test HardwareID-00 (most specific) */
+	ut_assertok(chid_generate(CHID_00, &data, chid));
+
+	/* The CHID should not be all zeros */
+	u8 zero_chid[UUID_LEN] = {0};
+	ut_assert(memcmp(chid, zero_chid, UUID_LEN));
+
+	return 0;
+}
+LIB_TEST(chid_basic, 0);
+
+static int chid_variants(struct unit_test_state *uts)
+{
+	struct chid_data data = {
+		.manuf = "Dell Inc.",
+		.product_name = "OptiPlex 7090",
+		.family = "OptiPlex",
+		.product_sku = "0A5C",
+		.bios_vendor = "Dell Inc.",
+		.bios_version = "1.12.0",
+		.bios_major = 1,
+		.bios_minor = 12,
+		.enclosure_type = 3,
+	};
+	u8 chid0[UUID_LEN], chid1[UUID_LEN], chid14[UUID_LEN];
+
+	/* Test different variants produce different CHIDs */
+	ut_assertok(chid_generate(CHID_00, &data, chid0));
+	ut_assertok(chid_generate(CHID_01, &data, chid1));
+	ut_assertok(chid_generate(CHID_14, &data, chid14));
+
+	/* All CHIDs should be different */
+	ut_assert(memcmp(chid0, chid1, UUID_LEN));
+	ut_assert(memcmp(chid0, chid14, UUID_LEN));
+	ut_assert(memcmp(chid1, chid14, UUID_LEN));
+
+	return 0;
+}
+LIB_TEST(chid_variants, 0);
+
+static int chid_missing_fields(struct unit_test_state *uts)
+{
+	struct chid_data data = {
+		.manuf = "Test Manufacturer",
+		/* Missing other fields */
+	};
+	struct chid_data empty_data = {0};
+	u8 chid[UUID_LEN];
+
+	/* Test HardwareID-14 (manufacturer only) should work */
+	ut_assertok(chid_generate(CHID_14, &data, chid));
+
+	/*
+	 * Test HardwareID-05 (requires string fields only) with completely
+	 * empty data should fail
+	 */
+	ut_asserteq(-ENODATA, chid_generate(CHID_05, &empty_data, chid));
+
+	/* Test HardwareID-14 with empty data should also fail */
+	ut_asserteq(-ENODATA, chid_generate(CHID_14, &empty_data, chid));
+
+	return 0;
+}
+LIB_TEST(chid_missing_fields, 0);
+
+static int chid_invalid_params(struct unit_test_state *uts)
+{
+	struct chid_data data = {
+		.manuf = "Test Manufacturer",
+	};
+	u8 chid[UUID_LEN];
+
+	/* Test invalid variant number */
+	ut_asserteq(-EINVAL, chid_generate(-1, &data, chid));
+	ut_asserteq(-EINVAL, chid_generate(15, &data, chid));
+
+	/* Test NULL data */
+	ut_asserteq(-EINVAL, chid_generate(CHID_00, NULL, chid));
+
+	/* Test NULL chid output buffer */
+	ut_asserteq(-EINVAL, chid_generate(CHID_00, &data, NULL));
+
+	return 0;
+}
+LIB_TEST(chid_invalid_params, 0);
+
+static int chid_consistent(struct unit_test_state *uts)
+{
+	struct chid_data data = {
+		.manuf = "ACME Corp",
+		.product_name = "Widget Pro",
+		.bios_vendor = "ACME BIOS",
+		.bios_version = "2.1.0",
+		.bios_major = 2,
+		.bios_minor = 1,
+	};
+	u8 chid1[UUID_LEN], chid2[UUID_LEN];
+	char chid1_str[UUID_STR_LEN + 1], chid2_str[UUID_STR_LEN + 1];
+
+	/* Generate the same CHID twice - should be identical */
+	ut_assertok(chid_generate(CHID_02, &data, chid1));
+	ut_assertok(chid_generate(CHID_02, &data, chid2));
+
+	/* CHIDs should be identical for same input */
+	uuid_bin_to_str(chid1, chid1_str, UUID_STR_FORMAT_STD);
+	uuid_bin_to_str(chid2, chid2_str, UUID_STR_FORMAT_STD);
+	ut_asserteq_str(chid1_str, chid2_str);
+
+	return 0;
+}
+LIB_TEST(chid_consistent, 0);
+
+static int chid_numeric(struct unit_test_state *uts)
+{
+	struct chid_data data = {
+		.manuf = "Test Corp",
+		.bios_major = 255,
+		.bios_minor = 127,
+		.enclosure_type = 99,
+	};
+	u8 zero_chid[UUID_LEN] = {0};
+	u8 chid[UUID_LEN];
+
+	/* Test with numeric fields only (manufacturer + numeric values) */
+	/* HardwareID-12: Manufacturer + Enclosure Type */
+	ut_assertok(chid_generate(CHID_12, &data, chid));
+
+	/* CHID should be generated successfully */
+	ut_assert(memcmp(chid, zero_chid, UUID_LEN));
+
+	return 0;
+}
+LIB_TEST(chid_numeric, 0);
+
+static int chid_real(struct unit_test_state *uts)
+{
+	/*
+	 * Real data from Lenovo ThinkPad X13s Gen 1 (21BXCTO1WW)
+	 * Test against actual CHIDs from Microsoft's ComputerHardwareIds.exe
+	 * output
+	 */
+	struct chid_data data = {
+		.manuf = "LENOVO",
+		.family = "ThinkPad X13s Gen 1",
+		.product_name = "21BXCTO1WW",
+		.product_sku = "LENOVO_MT_21BX_BU_Think_FM_ThinkPad X13s Gen 1",
+		.board_manuf = "LENOVO",
+		.board_product = "21BXCTO1WW",
+		.bios_vendor = "LENOVO",
+		.bios_version = "N3HET88W (1.60 )",
+		.bios_major = 1,
+		.bios_minor = 60,
+		.enclosure_type = 0x0a,
+	};
+	u8 chid[UUID_LEN];
+	char chid_str[UUID_STR_LEN + 1];
+
+	/* Test HardwareID-14 (Manufacturer only) */
+	ut_assertok(chid_generate(CHID_14, &data, chid));
+	uuid_bin_to_str(chid, chid_str, UUID_STR_FORMAT_STD);
+	ut_asserteq_str("6de5d951-d755-576b-bd09-c5cf66b27234", chid_str);
+
+	/* Test HardwareID-11 (Manufacturer + Family) */
+	ut_assertok(chid_generate(CHID_11, &data, chid));
+	uuid_bin_to_str(chid, chid_str, UUID_STR_FORMAT_STD);
+	ut_asserteq_str("f249803d-0d95-54f3-a28f-f26c14a03f3b", chid_str);
+
+	/* Test HardwareID-12 (Manufacturer + EnclosureKind) */
+	ut_assertok(chid_generate(CHID_12, &data, chid));
+	uuid_bin_to_str(chid, chid_str, UUID_STR_FORMAT_STD);
+	ut_asserteq_str("5e820764-888e-529d-a6f9-dfd12bacb160", chid_str);
+
+	/*
+	 * Test HardwareID-13 (Manufacturer + BaseboardManufacturer +
+	 * BaseboardProduct)
+	 */
+	ut_assertok(chid_generate(CHID_13, &data, chid));
+	uuid_bin_to_str(chid, chid_str, UUID_STR_FORMAT_STD);
+	ut_asserteq_str("156c9b34-bedb-5bfd-ae1f-ef5d2a994967", chid_str);
+
+	return 0;
+}
+LIB_TEST(chid_real, 0);
+
+static int chid_exact(struct unit_test_state *uts)
+{
+	/*
+	 * Test exact CHID matching against Microsoft's ComputerHardwareIds.exe
+	 * Using Lenovo ThinkPad X13s Gen 1 data from reference file
+	 * Expected CHID for HardwareID-14 (Manufacturer only):
+	 * {6de5d951-d755-576b-bd09-c5cf66b27234}
+	 */
+	struct chid_data data = {
+		.manuf = "LENOVO",
+		.family = "ThinkPad X13s Gen 1",
+		.product_name = "21BXCTO1WW",
+		.product_sku = "LENOVO_MT_21BX_BU_Think_FM_ThinkPad X13s Gen 1",
+		.board_manuf = "LENOVO",
+		.board_product = "21BXCTO1WW",
+		.bios_vendor = "LENOVO",
+		.bios_version = "N3HET88W (1.60 )",
+		.bios_major = 1,
+		.bios_minor = 60,
+		.enclosure_type = 0x0a,
+	};
+	char chid_str[UUID_STR_LEN + 1];
+	u8 chid[UUID_LEN];
+
+	/* Test HardwareID-14 (Manufacturer only) */
+	ut_assertok(chid_generate(CHID_14, &data, chid));
+
+	/* Convert CHID to string and compare with expected GUID string */
+	uuid_bin_to_str(chid, chid_str, UUID_STR_FORMAT_STD);
+	ut_asserteq_str("6de5d951-d755-576b-bd09-c5cf66b27234", chid_str);
+
+	return 0;
+}
+LIB_TEST(chid_exact, 0);
