[PATCH v2 16/19] crypto: cmh - add SLH-DSA/LMS/XMSS (HCQ)

Saravanakrishnan Krishnamoorthy skrishnamoorthy at rambus.com
Thu Jul 9 13:30:34 PDT 2026


From: Alex Ousherovitch <aousherovitch at rambus.com>

Register SLH-DSA, LMS, LMS-HSS, XMSS, and XMSS-MT algorithms
using the CMH HCQ core (core ID 0x08).  SLH-DSA is registered as a
sig algorithm with sign and verify support.  LMS, LMS-HSS, XMSS,
and XMSS-MT are registered as verify-only sig algorithms: their
stateful signing semantics (one-time-key private state the signer
must track) are not modeled by the kernel crypto API, so only
verification is exposed.

Co-developed-by: Saravanakrishnan Krishnamoorthy <skrishnamoorthy at rambus.com>
Signed-off-by: Saravanakrishnan Krishnamoorthy <skrishnamoorthy at rambus.com>
Signed-off-by: Alex Ousherovitch <aousherovitch at rambus.com>
Reviewed-by: Joel Wittenauer <Joel.Wittenauer at cryptography.com>
Reviewed-by: Thi Nguyen <thin at rambus.com>
---
 drivers/crypto/cmh/Makefile         |   6 +-
 drivers/crypto/cmh/cmh_hcq.c        | 313 +++++++++++++++++++++++
 drivers/crypto/cmh/cmh_main.c       |  24 ++
 drivers/crypto/cmh/cmh_pqc_lms.c    | 230 +++++++++++++++++
 drivers/crypto/cmh/cmh_pqc_slhdsa.c | 377 ++++++++++++++++++++++++++++
 drivers/crypto/cmh/cmh_pqc_xmss.c   | 230 +++++++++++++++++
 6 files changed, 1179 insertions(+), 1 deletion(-)
 create mode 100644 drivers/crypto/cmh/cmh_hcq.c
 create mode 100644 drivers/crypto/cmh/cmh_pqc_lms.c
 create mode 100644 drivers/crypto/cmh/cmh_pqc_slhdsa.c
 create mode 100644 drivers/crypto/cmh/cmh_pqc_xmss.c

diff --git a/drivers/crypto/cmh/Makefile b/drivers/crypto/cmh/Makefile
index 3425eb65d653..c3332804a9d7 100644
--- a/drivers/crypto/cmh/Makefile
+++ b/drivers/crypto/cmh/Makefile
@@ -36,7 +36,11 @@ cmh-y := \
 	cmh_pke_ecdh.o \
 	cmh_qse.o \
 	cmh_pqc_mldsa.o \
-	cmh_pqc_sizes.o
+	cmh_pqc_sizes.o \
+	cmh_hcq.o \
+	cmh_pqc_slhdsa.o \
+	cmh_pqc_lms.o \
+	cmh_pqc_xmss.o
 
 # Management ioctl device (/dev/cmh_mgmt): key lifecycle, PKE, PQC ioctls.
 cmh-$(CONFIG_CRYPTO_DEV_CMH_MGMT) += \
diff --git a/drivers/crypto/cmh/cmh_hcq.c b/drivers/crypto/cmh/cmh_hcq.c
new file mode 100644
index 000000000000..8fc3a5cb0f9f
--- /dev/null
+++ b/drivers/crypto/cmh/cmh_hcq.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Cryptography Research, Inc. (CRI).
+ * CMH LKM -- HCQ Core VCQ Builders
+ *
+ * VCQ builder functions for SLH-DSA, LMS, and XMSS commands.
+ * Each function populates a single vcq_cmd slot.  Callers assemble
+ * complete VCQs with header + command(s) + flush, then submit via
+ * cmh_tm_submit_sync().
+ */
+
+#include <linux/string.h>
+
+#include "cmh_sys.h"
+
+/* -- HCQ flush -- */
+
+/**
+ * vcq_add_hcq_flush() - Build an HCQ flush VCQ command
+ * @slot: VCQ command slot to populate
+ * @core_id: Hardware core ID for dispatch
+ */
+void vcq_add_hcq_flush(struct vcq_cmd *slot, u32 core_id)
+{
+	vcq_add_flush(slot, core_id);
+}
+
+/* -- SLH-DSA -- */
+
+/**
+ * vcq_add_hcq_slhdsa_keygen() - Build an SLH-DSA key generation VCQ command
+ * @slot: VCQ command slot to populate
+ * @core_id: Hardware core ID for dispatch
+ * @param_set: SLH-DSA parameter set identifier
+ * @seed_len: Length of seed buffer in bytes
+ * @pk_len: Length of public key buffer in bytes
+ * @sk_len: Length of secret key buffer in bytes
+ * @seed: DMA address of seed input buffer
+ * @pk: DMA address of public key output buffer
+ * @sk: DMA address of secret key output buffer
+ */
+void vcq_add_hcq_slhdsa_keygen(struct vcq_cmd *slot, u32 core_id, u32 param_set,
+			       u32 seed_len, u32 pk_len, u32 sk_len,
+			       u64 seed, u64 pk, u64 sk)
+{
+	memset(slot, 0, sizeof(*slot));
+	slot->magic = VCQ_CMD_MAGIC;
+	slot->id = VCQ_CMD_ID(core_id, 0, 1, HCQ_CMD_SLHDSA_KEYGEN);
+	slot->hwc.hcq.cmd_slhdsa_keygen.parameter_set = param_set;
+	slot->hwc.hcq.cmd_slhdsa_keygen.seed_len = seed_len;
+	slot->hwc.hcq.cmd_slhdsa_keygen.pk_len = pk_len;
+	slot->hwc.hcq.cmd_slhdsa_keygen.sk_len = sk_len;
+	slot->hwc.hcq.cmd_slhdsa_keygen.seed = seed;
+	slot->hwc.hcq.cmd_slhdsa_keygen.pk = pk;
+	slot->hwc.hcq.cmd_slhdsa_keygen.sk = sk;
+}
+
+/**
+ * vcq_add_hcq_slhdsa_sign() - Build an SLH-DSA signing VCQ command
+ * @slot: VCQ command slot to populate
+ * @core_id: Hardware core ID for dispatch
+ * @param_set: SLH-DSA parameter set identifier
+ * @msg_len: Length of message buffer in bytes
+ * @ctx_len: Length of context string in bytes
+ * @add_random: DMA address of additional randomness buffer
+ * @msg: DMA address of message buffer
+ * @ctx: DMA address of context string buffer
+ * @sk: DMA address of secret key buffer
+ * @sig: DMA address of signature output buffer
+ */
+void vcq_add_hcq_slhdsa_sign(struct vcq_cmd *slot, u32 core_id, u32 param_set,
+			     u32 msg_len, u32 ctx_len,
+			     u64 add_random, u64 msg, u64 ctx,
+			     u64 sk, u64 sig)
+{
+	memset(slot, 0, sizeof(*slot));
+	slot->magic = VCQ_CMD_MAGIC;
+	slot->id = VCQ_CMD_ID(core_id, 0, 1, HCQ_CMD_SLHDSA_SIGN);
+	slot->hwc.hcq.cmd_slhdsa_sign.parameter_set = param_set;
+	slot->hwc.hcq.cmd_slhdsa_sign.message_len = msg_len;
+	slot->hwc.hcq.cmd_slhdsa_sign.add_random = add_random;
+	slot->hwc.hcq.cmd_slhdsa_sign.message = msg;
+	slot->hwc.hcq.cmd_slhdsa_sign.context = ctx;
+	slot->hwc.hcq.cmd_slhdsa_sign.sk = sk;
+	slot->hwc.hcq.cmd_slhdsa_sign.sig = sig;
+	slot->hwc.hcq.cmd_slhdsa_sign.context_len = ctx_len;
+}
+
+/**
+ * vcq_add_hcq_slhdsa_sign_internal() - Build an SLH-DSA internal signing VCQ command
+ * @slot: VCQ command slot to populate
+ * @core_id: Hardware core ID for dispatch
+ * @param_set: SLH-DSA parameter set identifier
+ * @msg_len: Length of message buffer in bytes
+ * @add_random: DMA address of additional randomness buffer
+ * @msg: DMA address of message buffer
+ * @sk: DMA address of secret key buffer
+ * @sig: DMA address of signature output buffer
+ */
+void vcq_add_hcq_slhdsa_sign_internal(struct vcq_cmd *slot, u32 core_id, u32 param_set,
+				      u32 msg_len, u64 add_random,
+				      u64 msg, u64 sk, u64 sig)
+{
+	memset(slot, 0, sizeof(*slot));
+	slot->magic = VCQ_CMD_MAGIC;
+	slot->id = VCQ_CMD_ID(core_id, 0, 1, HCQ_CMD_SLHDSA_SIGN_INTERNAL);
+	slot->hwc.hcq.cmd_slhdsa_sign_internal.parameter_set = param_set;
+	slot->hwc.hcq.cmd_slhdsa_sign_internal.message_len = msg_len;
+	slot->hwc.hcq.cmd_slhdsa_sign_internal.add_random = add_random;
+	slot->hwc.hcq.cmd_slhdsa_sign_internal.message = msg;
+	slot->hwc.hcq.cmd_slhdsa_sign_internal.sk = sk;
+	slot->hwc.hcq.cmd_slhdsa_sign_internal.sig = sig;
+}
+
+/**
+ * vcq_add_hcq_slhdsa_verify() - Build an SLH-DSA verification VCQ command
+ * @slot: VCQ command slot to populate
+ * @core_id: Hardware core ID for dispatch
+ * @param_set: SLH-DSA parameter set identifier
+ * @msg_len: Length of message buffer in bytes
+ * @ctx_len: Length of context string in bytes
+ * @msg: DMA address of message buffer
+ * @ctx: DMA address of context string buffer
+ * @pk: DMA address of public key buffer
+ * @sig: DMA address of signature buffer to verify
+ */
+void vcq_add_hcq_slhdsa_verify(struct vcq_cmd *slot, u32 core_id, u32 param_set,
+			       u32 msg_len, u32 ctx_len,
+			       u64 msg, u64 ctx, u64 pk, u64 sig)
+{
+	memset(slot, 0, sizeof(*slot));
+	slot->magic = VCQ_CMD_MAGIC;
+	slot->id = VCQ_CMD_ID(core_id, 0, 1, HCQ_CMD_SLHDSA_VERIFY);
+	slot->hwc.hcq.cmd_slhdsa_verify.parameter_set = param_set;
+	slot->hwc.hcq.cmd_slhdsa_verify.message_len = msg_len;
+	slot->hwc.hcq.cmd_slhdsa_verify.message = msg;
+	slot->hwc.hcq.cmd_slhdsa_verify.context = ctx;
+	slot->hwc.hcq.cmd_slhdsa_verify.pk = pk;
+	slot->hwc.hcq.cmd_slhdsa_verify.sig = sig;
+	slot->hwc.hcq.cmd_slhdsa_verify.context_len = ctx_len;
+}
+
+/**
+ * vcq_add_hcq_slhdsa_sign_prehash() - Build an SLH-DSA prehash signing VCQ command
+ * @slot: VCQ command slot to populate
+ * @core_id: Hardware core ID for dispatch
+ * @cmd: VCQ command ID (sign-prehash variant)
+ * @param_set: SLH-DSA parameter set identifier
+ * @prehash_algo: Prehash algorithm identifier
+ * @msg_len: Length of message buffer in bytes
+ * @ctx_len: Length of context string in bytes
+ * @add_random: DMA address of additional randomness buffer
+ * @msg: DMA address of message buffer
+ * @ctx: DMA address of context string buffer
+ * @sk: DMA address of secret key buffer
+ * @sig: DMA address of signature output buffer
+ */
+void vcq_add_hcq_slhdsa_sign_prehash(struct vcq_cmd *slot, u32 core_id,
+				     u32 cmd, u32 param_set, u32 prehash_algo,
+				     u32 msg_len, u32 ctx_len,
+				     u64 add_random, u64 msg, u64 ctx,
+				     u64 sk, u64 sig)
+{
+	memset(slot, 0, sizeof(*slot));
+	slot->magic = VCQ_CMD_MAGIC;
+	slot->id = VCQ_CMD_ID(core_id, 0, 1, cmd);
+	slot->hwc.hcq.cmd_slhdsa_sign_prehash.parameter_set = param_set;
+	slot->hwc.hcq.cmd_slhdsa_sign_prehash.prehash_algo = prehash_algo;
+	slot->hwc.hcq.cmd_slhdsa_sign_prehash.message_len = msg_len;
+	slot->hwc.hcq.cmd_slhdsa_sign_prehash.context_len = ctx_len;
+	slot->hwc.hcq.cmd_slhdsa_sign_prehash.add_random = add_random;
+	slot->hwc.hcq.cmd_slhdsa_sign_prehash.message = msg;
+	slot->hwc.hcq.cmd_slhdsa_sign_prehash.context = ctx;
+	slot->hwc.hcq.cmd_slhdsa_sign_prehash.sk = sk;
+	slot->hwc.hcq.cmd_slhdsa_sign_prehash.sig = sig;
+}
+
+/**
+ * vcq_add_hcq_slhdsa_verify_prehash() - Build an SLH-DSA prehash verify VCQ command
+ * @slot: VCQ command slot to populate
+ * @core_id: Hardware core ID for dispatch
+ * @cmd: VCQ command ID (verify-prehash variant)
+ * @param_set: SLH-DSA parameter set identifier
+ * @prehash_algo: Prehash algorithm identifier
+ * @msg_len: Length of message buffer in bytes
+ * @ctx_len: Length of context string in bytes
+ * @msg: DMA address of message buffer
+ * @ctx: DMA address of context string buffer
+ * @pk: DMA address of public key buffer
+ * @sig: DMA address of signature buffer to verify
+ */
+void vcq_add_hcq_slhdsa_verify_prehash(struct vcq_cmd *slot, u32 core_id,
+				       u32 cmd, u32 param_set, u32 prehash_algo,
+				       u32 msg_len, u32 ctx_len,
+				       u64 msg, u64 ctx, u64 pk, u64 sig)
+{
+	memset(slot, 0, sizeof(*slot));
+	slot->magic = VCQ_CMD_MAGIC;
+	slot->id = VCQ_CMD_ID(core_id, 0, 1, cmd);
+	slot->hwc.hcq.cmd_slhdsa_verify_prehash.parameter_set = param_set;
+	slot->hwc.hcq.cmd_slhdsa_verify_prehash.prehash_algo = prehash_algo;
+	slot->hwc.hcq.cmd_slhdsa_verify_prehash.message_len = msg_len;
+	slot->hwc.hcq.cmd_slhdsa_verify_prehash.context_len = ctx_len;
+	slot->hwc.hcq.cmd_slhdsa_verify_prehash.message = msg;
+	slot->hwc.hcq.cmd_slhdsa_verify_prehash.context = ctx;
+	slot->hwc.hcq.cmd_slhdsa_verify_prehash.pk = pk;
+	slot->hwc.hcq.cmd_slhdsa_verify_prehash.sig = sig;
+}
+
+/**
+ * vcq_add_hcq_slhdsa_verify_internal() - Build an SLH-DSA internal verify VCQ command
+ * @slot: VCQ command slot to populate
+ * @core_id: Hardware core ID for dispatch
+ * @param_set: SLH-DSA parameter set identifier
+ * @msg_len: Length of message buffer in bytes
+ * @msg: DMA address of message buffer
+ * @pk: DMA address of public key buffer
+ * @sig: DMA address of signature buffer to verify
+ */
+void vcq_add_hcq_slhdsa_verify_internal(struct vcq_cmd *slot, u32 core_id, u32 param_set,
+					u32 msg_len, u64 msg, u64 pk, u64 sig)
+{
+	memset(slot, 0, sizeof(*slot));
+	slot->magic = VCQ_CMD_MAGIC;
+	slot->id = VCQ_CMD_ID(core_id, 0, 1,
+			      HCQ_CMD_SLHDSA_VERIFY_INTERNAL);
+	slot->hwc.hcq.cmd_slhdsa_verify_internal.parameter_set = param_set;
+	slot->hwc.hcq.cmd_slhdsa_verify_internal.message_len = msg_len;
+	slot->hwc.hcq.cmd_slhdsa_verify_internal.message = msg;
+	slot->hwc.hcq.cmd_slhdsa_verify_internal.pk = pk;
+	slot->hwc.hcq.cmd_slhdsa_verify_internal.sig = sig;
+}
+
+/**
+ * vcq_add_hcq_slhdsa_pubgen() - Build an SLH-DSA public key generation VCQ command
+ * @slot: VCQ command slot to populate
+ * @core_id: Hardware core ID for dispatch
+ * @param_set: SLH-DSA parameter set identifier
+ * @sk_len: Length of secret key buffer in bytes
+ * @sk: DMA address of secret key input buffer
+ * @pk: DMA address of public key output buffer
+ */
+void vcq_add_hcq_slhdsa_pubgen(struct vcq_cmd *slot, u32 core_id, u32 param_set,
+			       u32 sk_len, u64 sk, u64 pk)
+{
+	memset(slot, 0, sizeof(*slot));
+	slot->magic = VCQ_CMD_MAGIC;
+	slot->id = VCQ_CMD_ID(core_id, 0, 1, HCQ_CMD_SLHDSA_PUBGEN);
+	slot->hwc.hcq.cmd_slhdsa_pubgen.parameter_set = param_set;
+	slot->hwc.hcq.cmd_slhdsa_pubgen.sk_len = sk_len;
+	slot->hwc.hcq.cmd_slhdsa_pubgen.sk = sk;
+	slot->hwc.hcq.cmd_slhdsa_pubgen.pk = pk;
+}
+
+/* -- LMS -- */
+
+/**
+ * vcq_add_hcq_lms_verify() - Build an LMS/HSS signature verify VCQ command
+ * @slot: VCQ command slot to populate
+ * @core_id: Hardware core ID for dispatch
+ * @lms_hss: LMS/HSS mode flag (0 = LMS, 1 = HSS)
+ * @pk_len: Length of public key buffer in bytes
+ * @sig_len: Length of signature buffer in bytes
+ * @dig_len: Length of digest buffer in bytes
+ * @pk: DMA address of public key buffer
+ * @sig: DMA address of signature buffer
+ * @dig: DMA address of digest buffer
+ */
+void vcq_add_hcq_lms_verify(struct vcq_cmd *slot, u32 core_id, u32 lms_hss,
+			    u32 pk_len, u32 sig_len, u32 dig_len,
+			    u64 pk, u64 sig, u64 dig)
+{
+	memset(slot, 0, sizeof(*slot));
+	slot->magic = VCQ_CMD_MAGIC;
+	slot->id = VCQ_CMD_ID(core_id, 0, 1, HCQ_CMD_LMS_VERIFY);
+	slot->hwc.hcq.cmd_lms_verify.lms_hss = lms_hss;
+	slot->hwc.hcq.cmd_lms_verify.pk_len = pk_len;
+	slot->hwc.hcq.cmd_lms_verify.sig_len = sig_len;
+	slot->hwc.hcq.cmd_lms_verify.dig_len = dig_len;
+	slot->hwc.hcq.cmd_lms_verify.pk = pk;
+	slot->hwc.hcq.cmd_lms_verify.sig = sig;
+	slot->hwc.hcq.cmd_lms_verify.dig = dig;
+}
+
+/* -- XMSS -- */
+
+/**
+ * vcq_add_hcq_xmss_verify() - Build an XMSS/XMSS^MT signature verify VCQ command
+ * @slot: VCQ command slot to populate
+ * @core_id: Hardware core ID for dispatch
+ * @xmss_mt: XMSS/XMSS^MT mode flag (0 = XMSS, 1 = XMSS^MT)
+ * @pk_len: Length of public key buffer in bytes
+ * @sig_len: Length of signature buffer in bytes
+ * @dig_len: Length of digest buffer in bytes
+ * @pk: DMA address of public key buffer
+ * @sig: DMA address of signature buffer
+ * @dig: DMA address of digest buffer
+ */
+void vcq_add_hcq_xmss_verify(struct vcq_cmd *slot, u32 core_id, u32 xmss_mt,
+			     u32 pk_len, u32 sig_len, u32 dig_len,
+			     u64 pk, u64 sig, u64 dig)
+{
+	memset(slot, 0, sizeof(*slot));
+	slot->magic = VCQ_CMD_MAGIC;
+	slot->id = VCQ_CMD_ID(core_id, 0, 1, HCQ_CMD_XMSS_VERIFY);
+	slot->hwc.hcq.cmd_xmss_verify.xmss_mt = xmss_mt;
+	slot->hwc.hcq.cmd_xmss_verify.pk_len = pk_len;
+	slot->hwc.hcq.cmd_xmss_verify.sig_len = sig_len;
+	slot->hwc.hcq.cmd_xmss_verify.dig_len = dig_len;
+	slot->hwc.hcq.cmd_xmss_verify.pk = pk;
+	slot->hwc.hcq.cmd_xmss_verify.sig = sig;
+	slot->hwc.hcq.cmd_xmss_verify.dig = dig;
+}
diff --git a/drivers/crypto/cmh/cmh_main.c b/drivers/crypto/cmh/cmh_main.c
index bb81e2767974..0b5d22daec67 100644
--- a/drivers/crypto/cmh/cmh_main.c
+++ b/drivers/crypto/cmh/cmh_main.c
@@ -303,6 +303,21 @@ static int cmh_probe(struct platform_device *pdev)
 	if (ret)
 		goto err_pqc_mldsa_register;
 
+	/* Register PQC SLH-DSA */
+	ret = cmh_pqc_slhdsa_register();
+	if (ret)
+		goto err_pqc_slhdsa_register;
+
+	/* Register PQC LMS */
+	ret = cmh_pqc_lms_register();
+	if (ret)
+		goto err_pqc_lms_register;
+
+	/* Register PQC XMSS */
+	ret = cmh_pqc_xmss_register();
+	if (ret)
+		goto err_pqc_xmss_register;
+
 	/* Register key management device (/dev/cmh_mgmt) */
 	ret = cmh_mgmt_register();
 	if (ret)
@@ -315,6 +330,12 @@ static int cmh_probe(struct platform_device *pdev)
 	return 0;
 
 err_mgmt_register:
+	cmh_pqc_xmss_unregister();
+err_pqc_xmss_register:
+	cmh_pqc_lms_unregister();
+err_pqc_lms_register:
+	cmh_pqc_slhdsa_unregister();
+err_pqc_slhdsa_register:
 	cmh_pqc_mldsa_unregister();
 err_pqc_mldsa_register:
 	cmh_pke_ecdh_unregister();
@@ -379,6 +400,9 @@ static void cmh_remove(struct platform_device *pdev)
 	cfg = &dev->config;
 
 	cmh_mgmt_unregister();
+	cmh_pqc_xmss_unregister();
+	cmh_pqc_lms_unregister();
+	cmh_pqc_slhdsa_unregister();
 	cmh_pqc_mldsa_unregister();
 	cmh_pke_ecdh_unregister();
 	cmh_pke_ecdsa_unregister();
diff --git a/drivers/crypto/cmh/cmh_pqc_lms.c b/drivers/crypto/cmh/cmh_pqc_lms.c
new file mode 100644
index 000000000000..13b2e26aa7bd
--- /dev/null
+++ b/drivers/crypto/cmh/cmh_pqc_lms.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Cryptography Research, Inc. (CRI).
+ * CMH LKM -- LMS/HSS Signature Driver (verify-only, sig_alg, synchronous)
+ *
+ * Registers "lms" and "lms-hss" sig algorithms with verify-only
+ * callbacks.  Sign is not supported (stateful signature -- key
+ * management must happen externally).
+ *
+ * Verify: src = raw signature, digest = message bytes
+ * Public key: raw pk bytes (variable length, set via set_pub_key)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <crypto/sig.h>
+#include <crypto/internal/sig.h>
+
+#include "cmh_sys.h"
+#include "cmh_hcq_abi.h"
+#include "cmh_txn.h"
+#include "cmh_dma.h"
+#include "cmh_pqc.h"
+
+#define LMS_VCQ_CMDS	3	/* header + cmd + flush */
+
+struct cmh_lms_tfm_ctx {
+	u8 *pub_key;
+	u32 pub_key_len;
+	u32 lms_hss;		/* 0 = LMS, 1 = LMS-HSS */
+};
+
+static inline struct cmh_lms_tfm_ctx *cmh_lms_ctx(struct crypto_sig *tfm)
+{
+	return crypto_sig_ctx(tfm);
+}
+
+/*
+ * LMS/HSS verify (synchronous sig_alg)
+ *
+ * @src:    raw signature
+ * @slen:   signature length
+ * @digest: message bytes
+ * @dlen:   message length
+ *
+ * Returns 0 on successful verification, negative errno on failure.
+ */
+static int cmh_lms_verify(struct crypto_sig *tfm,
+			  const void *src, unsigned int slen,
+			  const void *digest, unsigned int dlen)
+{
+	struct cmh_lms_tfm_ctx *ctx = cmh_lms_ctx(tfm);
+	struct core_dispatch d = cmh_core_select_instance(CMH_CORE_HCQ);
+	struct vcq_cmd vcq[LMS_VCQ_CMDS];
+	u8 *sig_buf = NULL, *m_buf = NULL, *pk_buf = NULL;
+	dma_addr_t sig_dma = DMA_MAPPING_ERROR;
+	dma_addr_t m_dma = DMA_MAPPING_ERROR;
+	dma_addr_t pk_dma = DMA_MAPPING_ERROR;
+	int ret;
+
+	if (!ctx->pub_key)
+		return -EINVAL;
+	if (!slen || slen > LMS_MAX_SIG_LEN)
+		return -EINVAL;
+	if (!dlen || dlen > LMS_MAX_MSG_LEN)
+		return -EINVAL;
+
+	sig_buf = kmemdup(src, slen, GFP_KERNEL);
+	m_buf = kmemdup(digest, dlen, GFP_KERNEL);
+	pk_buf = kmemdup(ctx->pub_key, ctx->pub_key_len, GFP_KERNEL);
+	if (!sig_buf || !m_buf || !pk_buf) {
+		ret = -ENOMEM;
+		goto out_free;
+	}
+
+	sig_dma = cmh_dma_map_single(sig_buf, slen, DMA_TO_DEVICE);
+	m_dma = cmh_dma_map_single(m_buf, dlen, DMA_TO_DEVICE);
+	pk_dma = cmh_dma_map_single(pk_buf, ctx->pub_key_len, DMA_TO_DEVICE);
+
+	if (cmh_dma_map_error(sig_dma) || cmh_dma_map_error(m_dma) ||
+	    cmh_dma_map_error(pk_dma)) {
+		ret = -ENOMEM;
+		goto out_unmap;
+	}
+
+	vcq_set_header(&vcq[0], LMS_VCQ_CMDS);
+	vcq_add_hcq_lms_verify(&vcq[1], d.core_id, ctx->lms_hss,
+			       ctx->pub_key_len, slen, dlen,
+			       pk_dma, sig_dma, m_dma);
+	vcq_add_hcq_flush(&vcq[2], d.core_id);
+
+	/* LMS verify traverses Merkle hash chains -- inherently slow */
+	ret = cmh_tm_submit_sync_tmo(vcq, LMS_VCQ_CMDS, 1, d.mbx_idx,
+				     cmh_tm_slow_op_timeout_jiffies());
+
+out_unmap:
+	if (!cmh_dma_map_error(pk_dma))
+		cmh_dma_unmap_single(pk_dma, ctx->pub_key_len, DMA_TO_DEVICE);
+	if (!cmh_dma_map_error(m_dma))
+		cmh_dma_unmap_single(m_dma, dlen, DMA_TO_DEVICE);
+	if (!cmh_dma_map_error(sig_dma))
+		cmh_dma_unmap_single(sig_dma, slen, DMA_TO_DEVICE);
+
+out_free:
+	kfree(pk_buf);
+	kfree(m_buf);
+	kfree(sig_buf);
+	return ret;
+}
+
+static int cmh_lms_set_pub_key(struct crypto_sig *tfm,
+			       const void *key, unsigned int keylen)
+{
+	struct cmh_lms_tfm_ctx *ctx = cmh_lms_ctx(tfm);
+
+	if (!keylen || keylen > LMS_MAX_PK_LEN)
+		return -EINVAL;
+
+	kfree(ctx->pub_key);
+	ctx->pub_key = NULL;
+	ctx->pub_key_len = 0;
+
+	ctx->pub_key = kmemdup(key, keylen, GFP_KERNEL);
+	if (!ctx->pub_key)
+		return -ENOMEM;
+
+	ctx->pub_key_len = keylen;
+	return 0;
+}
+
+static unsigned int cmh_lms_key_size(struct crypto_sig *tfm)
+{
+	struct cmh_lms_tfm_ctx *ctx = cmh_lms_ctx(tfm);
+
+	return ctx->pub_key_len * 8;
+}
+
+static int cmh_lms_init(struct crypto_sig *tfm)
+{
+	struct cmh_lms_tfm_ctx *ctx = cmh_lms_ctx(tfm);
+
+	memset(ctx, 0, sizeof(*ctx));
+	return 0;
+}
+
+static int cmh_lms_hss_init(struct crypto_sig *tfm)
+{
+	struct cmh_lms_tfm_ctx *ctx = cmh_lms_ctx(tfm);
+
+	memset(ctx, 0, sizeof(*ctx));
+	ctx->lms_hss = 1;
+	return 0;
+}
+
+static void cmh_lms_exit(struct crypto_sig *tfm)
+{
+	struct cmh_lms_tfm_ctx *ctx = cmh_lms_ctx(tfm);
+
+	kfree(ctx->pub_key);
+	ctx->pub_key = NULL;
+}
+
+static struct sig_alg cmh_lms_algs[] = {
+	{
+		.verify		= cmh_lms_verify,
+		.set_pub_key	= cmh_lms_set_pub_key,
+		.key_size	= cmh_lms_key_size,
+		.init		= cmh_lms_init,
+		.exit		= cmh_lms_exit,
+		.base = {
+			.cra_name	  = "lms",
+			.cra_driver_name  = "cri-cmh-lms",
+			.cra_priority	  = 300,
+			.cra_module	  = THIS_MODULE,
+			.cra_ctxsize	  = sizeof(struct cmh_lms_tfm_ctx),
+		},
+	},
+	{
+		.verify		= cmh_lms_verify,
+		.set_pub_key	= cmh_lms_set_pub_key,
+		.key_size	= cmh_lms_key_size,
+		.init		= cmh_lms_hss_init,
+		.exit		= cmh_lms_exit,
+		.base = {
+			.cra_name	  = "lms-hss",
+			.cra_driver_name  = "cri-cmh-lms-hss",
+			.cra_priority	  = 300,
+			.cra_module	  = THIS_MODULE,
+			.cra_ctxsize	  = sizeof(struct cmh_lms_tfm_ctx),
+		},
+	},
+};
+
+/**
+ * cmh_pqc_lms_register() - Register LMS/LMS-HSS sig algorithms with the crypto framework
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int cmh_pqc_lms_register(void)
+{
+	int ret, i;
+
+	for (i = 0; i < ARRAY_SIZE(cmh_lms_algs); i++) {
+		ret = crypto_register_sig(&cmh_lms_algs[i]);
+		if (ret) {
+			dev_err(cmh_dev(), "cmh: failed to register %s (%d)\n",
+				cmh_lms_algs[i].base.cra_name, ret);
+			goto err_unregister;
+		}
+	}
+
+	return 0;
+
+err_unregister:
+	while (i--)
+		crypto_unregister_sig(&cmh_lms_algs[i]);
+	return ret;
+}
+
+/**
+ * cmh_pqc_lms_unregister() - Unregister LMS/LMS-HSS sig algorithms from the crypto framework
+ */
+void cmh_pqc_lms_unregister(void)
+{
+	int i = ARRAY_SIZE(cmh_lms_algs);
+
+	while (i--)
+		crypto_unregister_sig(&cmh_lms_algs[i]);
+}
diff --git a/drivers/crypto/cmh/cmh_pqc_slhdsa.c b/drivers/crypto/cmh/cmh_pqc_slhdsa.c
new file mode 100644
index 000000000000..9cc8cdb442b2
--- /dev/null
+++ b/drivers/crypto/cmh/cmh_pqc_slhdsa.c
@@ -0,0 +1,377 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Cryptography Research, Inc. (CRI).
+ * CMH LKM -- SLH-DSA Signature Driver (sig_alg, synchronous)
+ *
+ * Registers SLH-DSA sig algorithms for all 12 parameter sets
+ * (SHAKE/SHA2 x 128/192/256 x s/f) with sign and verify callbacks.
+ *
+ * Key format:
+ *   Public key  = raw pk bytes (2*n bytes)
+ *   Private key = raw sk bytes (4*n)
+ *
+ * Sign: src = message, dst = raw signature
+ * Verify: src = raw signature, digest = message bytes
+ *
+ * Private keys are raw (written to SYS_REF_TEMP per-operation).
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+#include <crypto/sig.h>
+#include <crypto/internal/sig.h>
+
+#include "cmh_sys.h"
+#include "cmh_qse_abi.h"
+#include "cmh_hcq_abi.h"
+#include "cmh_txn.h"
+#include "cmh_dma.h"
+#include "cmh_key.h"
+#include "cmh_pqc.h"
+
+struct cmh_slhdsa_tfm_ctx {
+	struct cmh_key_ctx key;		/* private key (raw sk bytes) */
+	u8 *pub_key;
+	u32 pub_key_len;
+	u32 param_set;			/* HCQ_SLHDSA_SHAKE_128S .. SHA2_256F */
+};
+
+static inline struct cmh_slhdsa_tfm_ctx *cmh_slhdsa_ctx(struct crypto_sig *tfm)
+{
+	return crypto_sig_ctx(tfm);
+}
+
+/*
+ * SLH-DSA sign (synchronous sig_alg)
+ *
+ * @src:  message bytes
+ * @slen: message length
+ * @dst:  signature output buffer
+ * @dlen: output buffer length
+ *
+ * Returns signature length on success, negative errno on failure.
+ * Uses raw private keys written to SYS_REF_TEMP per-operation.
+ */
+static int cmh_slhdsa_sign(struct crypto_sig *tfm,
+			   const void *src, unsigned int slen,
+			   void *dst, unsigned int dlen)
+{
+	struct cmh_slhdsa_tfm_ctx *ctx = cmh_slhdsa_ctx(tfm);
+	u32 sig_sz = slhdsa_get_sig_size(ctx->param_set);
+	u32 sk_sz = slhdsa_sk_size(ctx->param_set);
+	struct vcq_cmd vcq[HCQ_VCQ_CMDS_MAX]; /* raw: hdr+write+sign+flush */
+	u32 vcq_count;
+	u8 *m_buf = NULL, *sig_buf = NULL, *sk_buf = NULL;
+	dma_addr_t m_dma = DMA_MAPPING_ERROR;
+	dma_addr_t sig_dma = DMA_MAPPING_ERROR;
+	dma_addr_t sk_dma = DMA_MAPPING_ERROR;
+	int ret, idx;
+
+	if (ctx->key.mode == CMH_KEY_NONE)
+		return -EINVAL;
+	if (dlen < sig_sz)
+		return -EINVAL;
+	if (!slen || slen > SLHDSA_MAX_MSG_LEN)
+		return -EINVAL;
+
+	m_buf = kmemdup(src, slen, GFP_KERNEL);
+	sig_buf = kzalloc(sig_sz, GFP_KERNEL);
+	if (!m_buf || !sig_buf) {
+		ret = -ENOMEM;
+		goto out_free;
+	}
+
+	m_dma = cmh_dma_map_single(m_buf, slen, DMA_TO_DEVICE);
+	sig_dma = cmh_dma_map_single(sig_buf, sig_sz, DMA_FROM_DEVICE);
+	if (cmh_dma_map_error(m_dma) || cmh_dma_map_error(sig_dma)) {
+		ret = -ENOMEM;
+		goto out_unmap;
+	}
+
+	sk_dma = DMA_MAPPING_ERROR;
+	idx = 0;
+
+	struct core_dispatch d;
+
+	d = cmh_core_select_instance(CMH_CORE_HCQ);
+
+	if (ctx->key.raw.len != sk_sz) {
+		ret = -EINVAL;
+		goto out_unmap;
+	}
+	sk_buf = kmemdup(ctx->key.raw.data, ctx->key.raw.len,
+			 GFP_KERNEL);
+	if (!sk_buf) {
+		ret = -ENOMEM;
+		goto out_unmap;
+	}
+	sk_dma = cmh_dma_map_single(sk_buf, sk_sz, DMA_TO_DEVICE);
+	if (cmh_dma_map_error(sk_dma)) {
+		ret = -ENOMEM;
+		goto out_unmap;
+	}
+
+	vcq_count = HCQ_VCQ_CMDS_MIN + 1;
+	vcq_set_header(&vcq[idx++], vcq_count);
+	vcq_add_sys_write(&vcq[idx++], SYS_REF_TEMP, sk_dma,
+			  SYS_REF_NONE, sk_sz,
+			  ctx->key.raw.sys_type);
+	vcq_add_hcq_slhdsa_sign_internal(&vcq[idx++], d.core_id,
+					 ctx->param_set,
+					 slen, 0,
+					 m_dma, SYS_REF_TEMP,
+					 sig_dma);
+	vcq_add_hcq_flush(&vcq[idx++], d.core_id);
+
+	ret = cmh_tm_submit_sync_tmo(vcq, vcq_count, 1, d.mbx_idx,
+				     cmh_tm_slow_op_timeout_jiffies());
+
+	if (!ret) {
+		/* Sync bounce buffer so CPU sees the DMA-written signature */
+		cmh_dma_sync_for_cpu(sig_dma, sig_sz, DMA_FROM_DEVICE);
+		memcpy(dst, sig_buf, sig_sz);
+		ret = sig_sz;
+	}
+
+out_unmap:
+	if (sk_buf) {
+		if (!cmh_dma_map_error(sk_dma))
+			cmh_dma_unmap_single(sk_dma, sk_sz, DMA_TO_DEVICE);
+		kfree_sensitive(sk_buf);
+	}
+	if (!cmh_dma_map_error(sig_dma))
+		cmh_dma_unmap_single(sig_dma, sig_sz, DMA_FROM_DEVICE);
+	if (!cmh_dma_map_error(m_dma))
+		cmh_dma_unmap_single(m_dma, slen, DMA_TO_DEVICE);
+
+out_free:
+	kfree(sig_buf);
+	kfree(m_buf);
+	return ret;
+}
+
+/*
+ * SLH-DSA verify (synchronous sig_alg)
+ *
+ * @src:    raw signature
+ * @slen:   signature length
+ * @digest: message bytes
+ * @dlen:   message length
+ *
+ * Returns 0 on successful verification, negative errno on failure.
+ */
+static int cmh_slhdsa_verify(struct crypto_sig *tfm,
+			     const void *src, unsigned int slen,
+			     const void *digest, unsigned int dlen)
+{
+	struct cmh_slhdsa_tfm_ctx *ctx = cmh_slhdsa_ctx(tfm);
+	u32 sig_sz = slhdsa_get_sig_size(ctx->param_set);
+	u32 pk_sz = slhdsa_pk_size(ctx->param_set);
+	struct core_dispatch d = cmh_core_select_instance(CMH_CORE_HCQ);
+	struct vcq_cmd vcq[HCQ_VCQ_CMDS_MIN];
+	u8 *sig_buf = NULL, *m_buf = NULL, *pk_buf = NULL;
+	dma_addr_t sig_dma = DMA_MAPPING_ERROR;
+	dma_addr_t m_dma = DMA_MAPPING_ERROR;
+	dma_addr_t pk_dma = DMA_MAPPING_ERROR;
+	int ret;
+
+	if (!ctx->pub_key)
+		return -EINVAL;
+	if (slen != sig_sz)
+		return -EINVAL;
+	if (!dlen || dlen > SLHDSA_MAX_MSG_LEN)
+		return -EINVAL;
+
+	sig_buf = kmemdup(src, slen, GFP_KERNEL);
+	m_buf = kmemdup(digest, dlen, GFP_KERNEL);
+	pk_buf = kmemdup(ctx->pub_key, pk_sz, GFP_KERNEL);
+	if (!sig_buf || !m_buf || !pk_buf) {
+		ret = -ENOMEM;
+		goto out_free;
+	}
+
+	sig_dma = cmh_dma_map_single(sig_buf, sig_sz, DMA_TO_DEVICE);
+	m_dma = cmh_dma_map_single(m_buf, dlen, DMA_TO_DEVICE);
+	pk_dma = cmh_dma_map_single(pk_buf, pk_sz, DMA_TO_DEVICE);
+
+	if (cmh_dma_map_error(sig_dma) || cmh_dma_map_error(m_dma) ||
+	    cmh_dma_map_error(pk_dma)) {
+		ret = -ENOMEM;
+		goto out_unmap;
+	}
+
+	vcq_set_header(&vcq[0], HCQ_VCQ_CMDS_MIN);
+	vcq_add_hcq_slhdsa_verify_internal(&vcq[1], d.core_id, ctx->param_set,
+					   dlen, m_dma, pk_dma, sig_dma);
+	vcq_add_hcq_flush(&vcq[2], d.core_id);
+
+	/* SLH-DSA verify recomputes hyper-tree hashes -- inherently slow */
+	ret = cmh_tm_submit_sync_tmo(vcq, HCQ_VCQ_CMDS_MIN, 1, d.mbx_idx,
+				     cmh_tm_slow_op_timeout_jiffies());
+
+out_unmap:
+	if (!cmh_dma_map_error(pk_dma))
+		cmh_dma_unmap_single(pk_dma, pk_sz, DMA_TO_DEVICE);
+	if (!cmh_dma_map_error(m_dma))
+		cmh_dma_unmap_single(m_dma, dlen, DMA_TO_DEVICE);
+	if (!cmh_dma_map_error(sig_dma))
+		cmh_dma_unmap_single(sig_dma, sig_sz, DMA_TO_DEVICE);
+
+out_free:
+	kfree(pk_buf);
+	kfree(m_buf);
+	kfree(sig_buf);
+	return ret;
+}
+
+static int cmh_slhdsa_set_pub_key(struct crypto_sig *tfm,
+				  const void *key, unsigned int keylen)
+{
+	struct cmh_slhdsa_tfm_ctx *ctx = cmh_slhdsa_ctx(tfm);
+	u32 expected = slhdsa_pk_size(ctx->param_set);
+
+	if (keylen != expected)
+		return -EINVAL;
+
+	kfree(ctx->pub_key);
+	ctx->pub_key = NULL;
+	ctx->pub_key_len = 0;
+
+	ctx->pub_key = kmemdup(key, keylen, GFP_KERNEL);
+	if (!ctx->pub_key)
+		return -ENOMEM;
+
+	ctx->pub_key_len = keylen;
+	return 0;
+}
+
+static int cmh_slhdsa_set_priv_key(struct crypto_sig *tfm,
+				   const void *key, unsigned int keylen)
+{
+	struct cmh_slhdsa_tfm_ctx *ctx = cmh_slhdsa_ctx(tfm);
+
+	/* Raw sk (4*n bytes) */
+	if (keylen != slhdsa_sk_size(ctx->param_set))
+		return -EINVAL;
+
+	return cmh_key_setkey_raw(&ctx->key, key, keylen, CORE_ID_HCQ);
+}
+
+static unsigned int cmh_slhdsa_key_size(struct crypto_sig *tfm)
+{
+	struct cmh_slhdsa_tfm_ctx *ctx = cmh_slhdsa_ctx(tfm);
+
+	/* crypto_sig_keysize() returns bits, not bytes */
+	return slhdsa_pk_size(ctx->param_set) * 8;
+}
+
+static unsigned int cmh_slhdsa_max_size(struct crypto_sig *tfm)
+{
+	struct cmh_slhdsa_tfm_ctx *ctx = cmh_slhdsa_ctx(tfm);
+
+	return slhdsa_get_sig_size(ctx->param_set);
+}
+
+static void cmh_slhdsa_exit(struct crypto_sig *tfm)
+{
+	struct cmh_slhdsa_tfm_ctx *ctx = cmh_slhdsa_ctx(tfm);
+
+	cmh_key_destroy(&ctx->key);
+	kfree(ctx->pub_key);
+	ctx->pub_key = NULL;
+}
+
+/* Generate init functions for all 12 parameter sets */
+#define SLHDSA_INIT(ps_val)						\
+	static int cmh_slhdsa_init_##ps_val(struct crypto_sig *tfm)	\
+	{								\
+		struct cmh_slhdsa_tfm_ctx *ctx = cmh_slhdsa_ctx(tfm);	\
+		memset(ctx, 0, sizeof(*ctx));				\
+		ctx->param_set = ps_val;				\
+		return 0;						\
+	}
+
+SLHDSA_INIT(HCQ_SLHDSA_SHAKE_128S)
+SLHDSA_INIT(HCQ_SLHDSA_SHAKE_128F)
+SLHDSA_INIT(HCQ_SLHDSA_SHAKE_192S)
+SLHDSA_INIT(HCQ_SLHDSA_SHAKE_192F)
+SLHDSA_INIT(HCQ_SLHDSA_SHAKE_256S)
+SLHDSA_INIT(HCQ_SLHDSA_SHAKE_256F)
+SLHDSA_INIT(HCQ_SLHDSA_SHA2_128S)
+SLHDSA_INIT(HCQ_SLHDSA_SHA2_128F)
+SLHDSA_INIT(HCQ_SLHDSA_SHA2_192S)
+SLHDSA_INIT(HCQ_SLHDSA_SHA2_192F)
+SLHDSA_INIT(HCQ_SLHDSA_SHA2_256S)
+SLHDSA_INIT(HCQ_SLHDSA_SHA2_256F)
+
+#define SLHDSA_ALG(name, drv, ps_val) {					\
+		.sign		= cmh_slhdsa_sign,			\
+		.verify		= cmh_slhdsa_verify,			\
+		.set_pub_key	= cmh_slhdsa_set_pub_key,		\
+		.set_priv_key	= cmh_slhdsa_set_priv_key,		\
+		.key_size	= cmh_slhdsa_key_size,			\
+		.max_size	= cmh_slhdsa_max_size,			\
+		.init		= cmh_slhdsa_init_##ps_val,		\
+		.exit		= cmh_slhdsa_exit,			\
+		.base = {						\
+			.cra_name	  = name,			\
+			.cra_driver_name  = drv,			\
+			.cra_priority	  = 300,			\
+			.cra_module	  = THIS_MODULE,		\
+			.cra_ctxsize	  = sizeof(struct cmh_slhdsa_tfm_ctx), \
+		},							\
+	}
+
+static struct sig_alg cmh_slhdsa_algs[] = {
+	SLHDSA_ALG("slh-dsa-shake-128s", "cri-cmh-slh-dsa-shake-128s", HCQ_SLHDSA_SHAKE_128S),
+	SLHDSA_ALG("slh-dsa-shake-128f", "cri-cmh-slh-dsa-shake-128f", HCQ_SLHDSA_SHAKE_128F),
+	SLHDSA_ALG("slh-dsa-shake-192s", "cri-cmh-slh-dsa-shake-192s", HCQ_SLHDSA_SHAKE_192S),
+	SLHDSA_ALG("slh-dsa-shake-192f", "cri-cmh-slh-dsa-shake-192f", HCQ_SLHDSA_SHAKE_192F),
+	SLHDSA_ALG("slh-dsa-shake-256s", "cri-cmh-slh-dsa-shake-256s", HCQ_SLHDSA_SHAKE_256S),
+	SLHDSA_ALG("slh-dsa-shake-256f", "cri-cmh-slh-dsa-shake-256f", HCQ_SLHDSA_SHAKE_256F),
+	SLHDSA_ALG("slh-dsa-sha2-128s",  "cri-cmh-slh-dsa-sha2-128s",  HCQ_SLHDSA_SHA2_128S),
+	SLHDSA_ALG("slh-dsa-sha2-128f",  "cri-cmh-slh-dsa-sha2-128f",  HCQ_SLHDSA_SHA2_128F),
+	SLHDSA_ALG("slh-dsa-sha2-192s",  "cri-cmh-slh-dsa-sha2-192s",  HCQ_SLHDSA_SHA2_192S),
+	SLHDSA_ALG("slh-dsa-sha2-192f",  "cri-cmh-slh-dsa-sha2-192f",  HCQ_SLHDSA_SHA2_192F),
+	SLHDSA_ALG("slh-dsa-sha2-256s",  "cri-cmh-slh-dsa-sha2-256s",  HCQ_SLHDSA_SHA2_256S),
+	SLHDSA_ALG("slh-dsa-sha2-256f",  "cri-cmh-slh-dsa-sha2-256f",  HCQ_SLHDSA_SHA2_256F),
+};
+
+/**
+ * cmh_pqc_slhdsa_register() - Register SLH-DSA akcipher algorithms with the crypto framework
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int cmh_pqc_slhdsa_register(void)
+{
+	int ret, i;
+
+	for (i = 0; i < ARRAY_SIZE(cmh_slhdsa_algs); i++) {
+		ret = crypto_register_sig(&cmh_slhdsa_algs[i]);
+		if (ret) {
+			dev_err(cmh_dev(), "cmh: failed to register %s (%d)\n",
+				cmh_slhdsa_algs[i].base.cra_name, ret);
+			goto err_unregister;
+		}
+	}
+
+	return 0;
+
+err_unregister:
+	while (i--)
+		crypto_unregister_sig(&cmh_slhdsa_algs[i]);
+	return ret;
+}
+
+/**
+ * cmh_pqc_slhdsa_unregister() - Unregister SLH-DSA akcipher algorithms from the crypto framework
+ */
+void cmh_pqc_slhdsa_unregister(void)
+{
+	int i = ARRAY_SIZE(cmh_slhdsa_algs);
+
+	while (i--)
+		crypto_unregister_sig(&cmh_slhdsa_algs[i]);
+}
diff --git a/drivers/crypto/cmh/cmh_pqc_xmss.c b/drivers/crypto/cmh/cmh_pqc_xmss.c
new file mode 100644
index 000000000000..433ffcd6463d
--- /dev/null
+++ b/drivers/crypto/cmh/cmh_pqc_xmss.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Cryptography Research, Inc. (CRI).
+ * CMH LKM -- XMSS/XMSS-MT Signature Driver (verify-only, sig_alg, synchronous)
+ *
+ * Registers "xmss" and "xmss-mt" sig algorithms with verify-only
+ * callbacks.  Sign is not supported (stateful signature -- key
+ * management must happen externally).
+ *
+ * Verify: src = raw signature, digest = message bytes
+ * Public key: raw pk bytes (variable length, set via set_pub_key)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <crypto/sig.h>
+#include <crypto/internal/sig.h>
+
+#include "cmh_sys.h"
+#include "cmh_hcq_abi.h"
+#include "cmh_txn.h"
+#include "cmh_dma.h"
+#include "cmh_pqc.h"
+
+#define XMSS_VCQ_CMDS	3	/* header + cmd + flush */
+
+struct cmh_xmss_tfm_ctx {
+	u8 *pub_key;
+	u32 pub_key_len;
+	u32 xmss_mt;		/* 0 = XMSS, 1 = XMSS-MT */
+};
+
+static inline struct cmh_xmss_tfm_ctx *cmh_xmss_ctx(struct crypto_sig *tfm)
+{
+	return crypto_sig_ctx(tfm);
+}
+
+/*
+ * XMSS/XMSS-MT verify (synchronous sig_alg)
+ *
+ * @src:    raw signature
+ * @slen:   signature length
+ * @digest: message bytes
+ * @dlen:   message length
+ *
+ * Returns 0 on successful verification, negative errno on failure.
+ */
+static int cmh_xmss_verify(struct crypto_sig *tfm,
+			   const void *src, unsigned int slen,
+			   const void *digest, unsigned int dlen)
+{
+	struct cmh_xmss_tfm_ctx *ctx = cmh_xmss_ctx(tfm);
+	struct core_dispatch d = cmh_core_select_instance(CMH_CORE_HCQ);
+	struct vcq_cmd vcq[XMSS_VCQ_CMDS];
+	u8 *sig_buf = NULL, *m_buf = NULL, *pk_buf = NULL;
+	dma_addr_t sig_dma = DMA_MAPPING_ERROR;
+	dma_addr_t m_dma = DMA_MAPPING_ERROR;
+	dma_addr_t pk_dma = DMA_MAPPING_ERROR;
+	int ret;
+
+	if (!ctx->pub_key)
+		return -EINVAL;
+	if (!slen || slen > XMSS_MAX_SIG_LEN)
+		return -EINVAL;
+	if (!dlen || dlen > XMSS_MAX_MSG_LEN)
+		return -EINVAL;
+
+	sig_buf = kmemdup(src, slen, GFP_KERNEL);
+	m_buf = kmemdup(digest, dlen, GFP_KERNEL);
+	pk_buf = kmemdup(ctx->pub_key, ctx->pub_key_len, GFP_KERNEL);
+	if (!sig_buf || !m_buf || !pk_buf) {
+		ret = -ENOMEM;
+		goto out_free;
+	}
+
+	sig_dma = cmh_dma_map_single(sig_buf, slen, DMA_TO_DEVICE);
+	m_dma = cmh_dma_map_single(m_buf, dlen, DMA_TO_DEVICE);
+	pk_dma = cmh_dma_map_single(pk_buf, ctx->pub_key_len, DMA_TO_DEVICE);
+
+	if (cmh_dma_map_error(sig_dma) || cmh_dma_map_error(m_dma) ||
+	    cmh_dma_map_error(pk_dma)) {
+		ret = -ENOMEM;
+		goto out_unmap;
+	}
+
+	vcq_set_header(&vcq[0], XMSS_VCQ_CMDS);
+	vcq_add_hcq_xmss_verify(&vcq[1], d.core_id, ctx->xmss_mt,
+				ctx->pub_key_len, slen, dlen,
+				pk_dma, sig_dma, m_dma);
+	vcq_add_hcq_flush(&vcq[2], d.core_id);
+
+	/* XMSS verify traverses Merkle hash chains -- inherently slow */
+	ret = cmh_tm_submit_sync_tmo(vcq, XMSS_VCQ_CMDS, 1, d.mbx_idx,
+				     cmh_tm_slow_op_timeout_jiffies());
+
+out_unmap:
+	if (!cmh_dma_map_error(pk_dma))
+		cmh_dma_unmap_single(pk_dma, ctx->pub_key_len, DMA_TO_DEVICE);
+	if (!cmh_dma_map_error(m_dma))
+		cmh_dma_unmap_single(m_dma, dlen, DMA_TO_DEVICE);
+	if (!cmh_dma_map_error(sig_dma))
+		cmh_dma_unmap_single(sig_dma, slen, DMA_TO_DEVICE);
+
+out_free:
+	kfree(pk_buf);
+	kfree(m_buf);
+	kfree(sig_buf);
+	return ret;
+}
+
+static int cmh_xmss_set_pub_key(struct crypto_sig *tfm,
+				const void *key, unsigned int keylen)
+{
+	struct cmh_xmss_tfm_ctx *ctx = cmh_xmss_ctx(tfm);
+
+	if (!keylen || keylen > XMSS_MAX_PK_LEN)
+		return -EINVAL;
+
+	kfree(ctx->pub_key);
+	ctx->pub_key = NULL;
+	ctx->pub_key_len = 0;
+
+	ctx->pub_key = kmemdup(key, keylen, GFP_KERNEL);
+	if (!ctx->pub_key)
+		return -ENOMEM;
+
+	ctx->pub_key_len = keylen;
+	return 0;
+}
+
+static unsigned int cmh_xmss_key_size(struct crypto_sig *tfm)
+{
+	struct cmh_xmss_tfm_ctx *ctx = cmh_xmss_ctx(tfm);
+
+	return ctx->pub_key_len * 8;
+}
+
+static int cmh_xmss_init(struct crypto_sig *tfm)
+{
+	struct cmh_xmss_tfm_ctx *ctx = cmh_xmss_ctx(tfm);
+
+	memset(ctx, 0, sizeof(*ctx));
+	return 0;
+}
+
+static int cmh_xmss_mt_init(struct crypto_sig *tfm)
+{
+	struct cmh_xmss_tfm_ctx *ctx = cmh_xmss_ctx(tfm);
+
+	memset(ctx, 0, sizeof(*ctx));
+	ctx->xmss_mt = 1;
+	return 0;
+}
+
+static void cmh_xmss_exit(struct crypto_sig *tfm)
+{
+	struct cmh_xmss_tfm_ctx *ctx = cmh_xmss_ctx(tfm);
+
+	kfree(ctx->pub_key);
+	ctx->pub_key = NULL;
+}
+
+static struct sig_alg cmh_xmss_algs[] = {
+	{
+		.verify		= cmh_xmss_verify,
+		.set_pub_key	= cmh_xmss_set_pub_key,
+		.key_size	= cmh_xmss_key_size,
+		.init		= cmh_xmss_init,
+		.exit		= cmh_xmss_exit,
+		.base = {
+			.cra_name	  = "xmss",
+			.cra_driver_name  = "cri-cmh-xmss",
+			.cra_priority	  = 300,
+			.cra_module	  = THIS_MODULE,
+			.cra_ctxsize	  = sizeof(struct cmh_xmss_tfm_ctx),
+		},
+	},
+	{
+		.verify		= cmh_xmss_verify,
+		.set_pub_key	= cmh_xmss_set_pub_key,
+		.key_size	= cmh_xmss_key_size,
+		.init		= cmh_xmss_mt_init,
+		.exit		= cmh_xmss_exit,
+		.base = {
+			.cra_name	  = "xmss-mt",
+			.cra_driver_name  = "cri-cmh-xmss-mt",
+			.cra_priority	  = 300,
+			.cra_module	  = THIS_MODULE,
+			.cra_ctxsize	  = sizeof(struct cmh_xmss_tfm_ctx),
+		},
+	},
+};
+
+/**
+ * cmh_pqc_xmss_register() - Register XMSS/XMSS-MT sig algorithms with the crypto framework
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int cmh_pqc_xmss_register(void)
+{
+	int ret, i;
+
+	for (i = 0; i < ARRAY_SIZE(cmh_xmss_algs); i++) {
+		ret = crypto_register_sig(&cmh_xmss_algs[i]);
+		if (ret) {
+			dev_err(cmh_dev(), "cmh: failed to register %s (%d)\n",
+				cmh_xmss_algs[i].base.cra_name, ret);
+			goto err_unregister;
+		}
+	}
+
+	return 0;
+
+err_unregister:
+	while (i--)
+		crypto_unregister_sig(&cmh_xmss_algs[i]);
+	return ret;
+}
+
+/**
+ * cmh_pqc_xmss_unregister() - Unregister XMSS/XMSS-MT sig algorithms from the crypto framework
+ */
+void cmh_pqc_xmss_unregister(void)
+{
+	int i = ARRAY_SIZE(cmh_xmss_algs);
+
+	while (i--)
+		crypto_unregister_sig(&cmh_xmss_algs[i]);
+}
-- 
2.43.7




More information about the linux-riscv mailing list