[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