From: Alex Ousherovitch <[email protected]>

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 <[email protected]>
Signed-off-by: Saravanakrishnan Krishnamoorthy <[email protected]>
Signed-off-by: Alex Ousherovitch <[email protected]>
Reviewed-by: Joel Wittenauer <[email protected]>
Reviewed-by: Thi Nguyen <[email protected]>
---
 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


Reply via email to