Re: [PATCH v3 02/19] crypto: cmh - add core platform driver

Krzysztof Kozlowski <[email protected]>
Newsgroups org.kernel.vger.linux-api,org.infradead.lists.linux-riscv,org.kernel.vger.linux-crypto,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest
Message-ID <20260811-meerkat-of-excellent-satiation-d4c32f@quoll>
On Thu, Aug 06, 2026 at 12:55:01PM -0700, Saravanakrishnan Krishnamoorthy wrote:
> These are the single-block cipher API used for software-fallback paths:
> CCM empty-input tag computation (2 ECB encryptions + XOR) and XCBC(SM4)
> empty-message workaround (3 ECB encryptions + XOR).  No public wrapper
> exists; this is the same pattern used by in-tree crypto/ccm.c,
> crypto/cmac.c, and crypto/xcbc.c.
> 
> 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]>

These two reviews did not happen, drop the tags. The code has trivial
mistakes from downstream code. Internal review would point these trivial
issues, so I do not believe you received valid, meaningful internal
review which would justify the tags.

> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +
> +#include "cmh.h"
> +#include "cmh_dma.h"
> +#include "cmh_mqi.h"
> +#include "cmh_txn.h"
> +#include "cmh_rh.h"
> +#include "cmh_registers.h"
> +#include "cmh_debugfs.h"
> +#include "cmh_sysfs.h"
> +
> +#include <linux/iopoll.h>
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Alex Ousherovitch <[email protected]>");
> +MODULE_AUTHOR("Saravanakrishnan Krishnamoorthy <[email protected]>");
> +MODULE_AUTHOR("Joel Wittenauer <[email protected]>");
> +MODULE_DESCRIPTION("Rambus CryptoManager Hub (CMH) hardware crypto accelerator");
> +MODULE_ALIAS("platform:cmh");

Why?

> +MODULE_IMPORT_NS("CRYPTO_INTERNAL");

Why all this is in completely odd place, not next to the module init
code?

> +
> +#ifdef CONFIG_CRYPTO_DEV_CMH_DEBUG
> +static bool skip_fw_check;
> +module_param(skip_fw_check, bool, 0444);
> +MODULE_PARM_DESC(skip_fw_check,
> +		 "[debug] Skip eSW boot status check at probe (default: false)");
> +#else
> +#define skip_fw_check false
> +#endif
> +
> +/* Global device state (single-instance module) */
> +
> +static struct cmh_device *g_cmh_dev;

Eh, no. Don't implement singletons.

> +
> +/* SIC Sanity Check */
> +
> +static int cmh_check_sic(struct cmh_config *cfg)
> +{
> +	const u32 ready = SIC_SW_BOOT_STATUS_MISSION |
> +			  SIC_SW_BOOT_STATUS_MISSION2;
> +	u32 boot_status;
> +	u32 hw_version;
> +	u32 sw_boot;
> +	int ret;
> +
> +	boot_status = cmh_reg_read32(cfg->sic_mapped, R_SIC_BOOT_STATUS);
> +	hw_version  = cmh_reg_read32(cfg->sic_mapped, R_SIC_HW_VERSION0);
> +
> +	dev_info(cmh_dev(), "SIC boot_status=0x%08x hw_version=0x%08x\n",
> +		 boot_status, hw_version);
> +
> +	if ((boot_status & SIC_BOOT_STATUS_MASK) != SIC_BOOT_STATUS_PASS) {
> +		dev_err(cmh_dev(), "SIC boot status check failed (0x%02x != 0x%02x)\n",
> +			boot_status & SIC_BOOT_STATUS_MASK, SIC_BOOT_STATUS_PASS);
> +		return -EIO;
> +	}
> +
> +	/*
> +	 * Wait for eSW readiness: MISSION signals the primary VCQ engine,
> +	 * MISSION2 the sidecar engine (set asynchronously).  The driver
> +	 * uses both, so require both bits.
> +	 */
> +	ret = read_poll_timeout(ioread32, sw_boot,
> +				(sw_boot & ready) == ready,
> +				1000,
> +				(unsigned long)cfg->fw_ready_timeout_ms * 1000UL,
> +				false,
> +				cfg->sic_mapped + R_SIC_SW_BOOT_STATUS);
> +	if (ret) {
> +		sw_boot = cmh_reg_read32(cfg->sic_mapped, R_SIC_SW_BOOT_STATUS);
> +		dev_err(cmh_dev(), "CMH eSW not ready (sw_boot_status=0x%08x, timeout=%ums)\n",
> +			sw_boot, cfg->fw_ready_timeout_ms);
> +		return -ETIMEDOUT;
> +	}
> +
> +	dev_info(cmh_dev(), "CMH eSW fully operational (sw_boot_status=0x%08x)\n",
> +		 sw_boot);

Drop

> +
> +	return 0;
> +}
> +
> +/* Module Init -- platform driver probe */
> +
> +static int cmh_probe(struct platform_device *pdev)
> +{
> +	struct cmh_device *dev;
> +	struct cmh_config *cfg;
> +	struct clk_bulk_data *clks;
> +	struct gpio_desc *reset;
> +	unsigned int i;
> +	int ret;
> +
> +	/* Single-instance guard: reject if already probed */
> +	if (g_cmh_dev)
> +		return -EBUSY;

NAK

> +
> +	dev_info(&pdev->dev, "loading v%s\n", CMH_VERSION);

NAK and internal review should tell you that

> +
> +	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
> +	if (!dev)
> +		return -ENOMEM;
> +
> +	dev->dev = &pdev->dev;
> +	cfg = &dev->config;
> +
> +	/* Declare DMA addressing capability */
> +	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
> +	if (ret) {
> +		dev_err(&pdev->dev, "dma_set_mask_and_coherent failed (rc=%d)\n",
> +			ret);
> +		goto err_free_dev;
> +	}
> +
> +	/* Initialize DMA backend (standard API or FPGA pool) */
> +	ret = cmh_dma_init(pdev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "DMA init failed (rc=%d)\n", ret);
> +		goto err_free_dev;
> +	}
> +
> +	/* Step 1: Parse and validate configuration (DT + module params) */
> +	ret = cmh_config_init(cfg, pdev);
> +	if (ret)
> +		goto err_dma_init;
> +
> +	dev_info(cmh_dev(), "sic_base=0x%llx size=0x%zx mbx_count=%u\n",
> +		 (unsigned long long)cfg->sic_base, cfg->sic_size,
> +		 cfg->mbx_count);

How many dev_info are you going to print?

> +
> +	/*
> +	 * Enable functional clocks and release reset.  Both are optional --
> +	 * integrations where a separate management/power controller owns the
> +	 * clock and reset lines describe neither, and these calls are
> +	 * no-ops.  The hub gates its clocks internally, but
> +	 * clk_disable_unused() would otherwise gate an always-on input the
> +	 * driver never claimed, so the driver enables whatever clocks the
> +	 * device tree provides.  Clocks come up before reset is released (a
> +	 * hard reset requires an active clock) and before any SIC register
> +	 * access.  The reset line is acquired already deasserted; the driver
> +	 * does not drive a reset pulse -- the eSW boots independently and its
> +	 * mission-mode readiness is verified separately below, and no in-tree
> +	 * platform wires this line for a reset sequence to be exercised.
> +	 * devm unwinds both on remove or probe error.
> +	 */
> +	ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &clks);
> +	if (ret < 0) {
> +		dev_err(cmh_dev(), "failed to enable clocks (rc=%d)\n", ret);
> +		goto err_dma_init;
> +	}
> +	if (ret > 0)
> +		dev_info(cmh_dev(), "enabled %d clock(s)\n", ret);

Again?

> +
> +	reset = devm_gpiod_get_optional(&pdev->dev, "reset", GPIOD_OUT_LOW);
> +	if (IS_ERR(reset)) {
> +		ret = PTR_ERR(reset);
> +		dev_err(cmh_dev(), "failed to acquire reset GPIO (rc=%d)\n",
> +			ret);

Syntax is ret = dev_err_probe and internal review should tell you that.

> +		goto err_dma_init;
> +	}
> +
> +	/* Step 2: ioremap the SIC region */
> +	cfg->sic_mapped = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(cfg->sic_mapped)) {
> +		ret = PTR_ERR(cfg->sic_mapped);
> +		cfg->sic_mapped = NULL;
> +		dev_err(cmh_dev(), "ioremap failed for SIC region (rc=%d)\n",
> +			ret);
> +		goto err_dma_init;
> +	}
> +
> +	/* Step 3: Verify CMH is alive */
> +	if (skip_fw_check) {
> +		dev_info(cmh_dev(), "skipping eSW boot check (skip_fw_check=1)\n");
> +	} else {
> +		ret = cmh_check_sic(cfg);
> +		if (ret)
> +			goto err_dma_init;
> +	}
> +
> +	/* Step 3.5: Discover crypto cores from the SIC CORE_ENABLE register */
> +	ret = cmh_config_discover_cores(cfg);
> +	if (ret)
> +		goto err_dma_init;
> +
> +	/* Step 4: Compute per-instance register bases */
> +	for (i = 0; i < cfg->mbx_count; i++) {
> +		struct cmh_mbx_config *m = &cfg->mailboxes[i];
> +
> +		m->reg_base = cmh_mbx_instance_base(cfg->sic_mapped,
> +						    m->instance);
> +
> +		dev_dbg(cmh_dev(), "mbx[%u] instance=%u reg_base=%p\n",
> +			i, m->instance, m->reg_base);
> +	}
> +
> +	(void)cmh_debugfs_init(cfg);

Why the cast?

> +
> +	/* Initialise mailbox queue interface */
> +	ret = cmh_mqi_init(cfg);
> +	if (ret)
> +		goto err_mqi_init;
> +
> +	/* Initialise transaction manager */
> +	ret = cmh_tm_init(cfg);
> +	if (ret)
> +		goto err_tm_init;
> +
> +	/* Initialise response handler */
> +	ret = cmh_rh_init(cfg);
> +	if (ret)
> +		goto err_rh_init;
> +
> +	g_cmh_dev = dev;

Nope (and we do not call things "g" from globals)

> +	platform_set_drvdata(pdev, dev);
> +
> +	dev_info(cmh_dev(), "initialized successfully\n");

NAK

> +	return 0;
> +
> +err_rh_init:
> +	cmh_tm_cleanup();
> +err_tm_init:
> +	cmh_mqi_cleanup(cfg);
> +err_mqi_init:
> +	cmh_debugfs_cleanup();
> +err_dma_init:
> +	cmh_dma_cleanup();
> +err_free_dev:
> +	return ret;
> +}
> +
> +/* Module Exit -- platform driver remove */
> +
> +static void cmh_remove(struct platform_device *pdev)
> +{
> +	struct cmh_device *dev = platform_get_drvdata(pdev);
> +	struct cmh_config *cfg;
> +
> +	if (!dev)
> +		return;
> +
> +	cfg = &dev->config;
> +
> +	cmh_rh_cleanup(cfg);
> +	cmh_tm_cleanup();
> +	cmh_mqi_cleanup(cfg);
> +	cmh_debugfs_cleanup();
> +	cmh_dma_cleanup();
> +
> +	dev_info(&pdev->dev, "unloaded successfully\n");

NAK

> +
> +	g_cmh_dev = NULL;
> +}
> +
> +static const struct of_device_id cmh_of_match[] = {
> +	{ .compatible = "rambus,cmh-v1030" },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, cmh_of_match);
> +
> +/*
> + * PM suspend/resume.
> + *
> + * Suspend: drain the TM first (while the RH is still active and can
> + * deliver completions for in-flight transactions), then quiesce the
> + * RH (cancel watchdog, mask HW interrupts).  This ordering ensures
> + * the drain_timeout_ms wait in cmh_tm_quiesce() can actually succeed
> + * -- if we suspended RH first, no completions would be delivered and
> + * the drain would always hit the force-cancel path.
> + *
> + * IRQ handlers remain registered (standard PM pattern: the kernel
> + * disables the IRQ lines during suspend, no need to free/re-request).
> + *
> + * Resume: re-check the SIC/SW boot status, re-synchronise the RH
> + * with hardware (head positions, interrupt masks, watchdog), then
> + * restart the TM kthread.
> + */
> +
> +static int cmh_suspend(struct device *dev)
> +{
> +	struct cmh_device *cmh = dev_get_drvdata(dev);
> +
> +	if (!cmh)
> +		return 0;
> +
> +	dev_info(dev, "suspending\n");

Really, how many dev_info do you intend to have?

> +	cmh_tm_quiesce();
> +	cmh_rh_suspend(&cmh->config);
> +	return 0;
> +}
> +
> +static int cmh_resume(struct device *dev)
> +{
> +	struct cmh_device *cmh = dev_get_drvdata(dev);
> +	int ret;
> +
> +	if (!cmh)
> +		return 0;
> +
> +	ret = cmh_check_sic(&cmh->config);
> +	if (ret) {
> +		dev_err(dev, "resume: CMH eSW health check failed (%d)\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	/*
> +	 * cmh_rh_resume() is void: it only re-syncs MMIO head pointers,
> +	 * clears stale interrupt status bits (W1C), re-enables interrupt
> +	 * masks, and re-arms the watchdog timer -- none of which can fail
> +	 * after the SIC health check above has confirmed HW accessibility.
> +	 */
> +	cmh_rh_resume(&cmh->config);
> +
> +	ret = cmh_tm_resume();
> +	if (ret) {
> +		dev_err(dev, "resume: TM restart failed (%d)\n", ret);
> +		return ret;
> +	}
> +	dev_info(dev, "resumed successfully\n");

NAK

> +	return 0;
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(cmh_pm_ops,
> +				cmh_suspend,
> +				cmh_resume);
> +
> +/*
> + * Runtime PM is intentionally not implemented.  The CMH hardware does
> + * not expose HLOS-accessible clock gates or power domains -- the eSW
> + * firmware manages HW power state independently.  There is no mechanism
> + * for the kernel to idle, gate clocks, or power down the accelerator
> + * block from HLOS.  If a future platform variant exposes power control
> + * to HLOS (e.g. via a SCMI power domain), runtime PM support can be
> + * added at that time using SET_RUNTIME_PM_OPS and pm_runtime_get/put
> + * around VCQ submission paths.
> + *
> + * System sleep (suspend/resume) is supported via DEFINE_SIMPLE_DEV_PM_OPS
> + * above: suspend quiesces the TM and masks IRQs; resume re-verifies
> + * eSW health (SIC status) and restarts the TM thread.
> + */
> +
> +static struct platform_driver cmh_driver = {
> +	.probe      = cmh_probe,
> +	.remove     = cmh_remove,
> +	.driver = {
> +		.name           = CMH_DRV_NAME,

Use name directly.

> +		.of_match_table = cmh_of_match,
> +		.dev_groups     = cmh_sysfs_groups,
> +		.pm             = pm_sleep_ptr(&cmh_pm_ops),
> +	},
> +};
> +
> +static int __init cmh_init(void)
> +{
> +	/*
> +	 * Register the platform driver and let the driver core drive
> +	 * probing.  Probe failures are logged by probe() itself; a probe
> +	 * that returns -EPROBE_DEFER (e.g. its interrupt controller is not
> +	 * yet ready) is retried by the core.  Do not gate module load on
> +	 * g_cmh_dev -- that would defeat deferred probe.
> +	 */

Why do you need this? Why your driver is done differently than all
others?

> +	return platform_driver_register(&cmh_driver);
> +}
> +
> +static void __exit cmh_exit(void)
> +{
> +	platform_driver_unregister(&cmh_driver);
> +}
> +
> +module_init(cmh_init);
> +module_exit(cmh_exit);

And this is not module_platform_driver because...?

> diff --git a/drivers/crypto/cmh/cmh_mqi.c b/drivers/crypto/cmh/cmh_mqi.c
> new file mode 100644
> index 000000000000..99ba44695d3c
> --- /dev/null
> +++ b/drivers/crypto/cmh/cmh_mqi.c
> @@ -0,0 +1,347 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2026 Cryptography Research, Inc. (CRI).
> + * CMH LKM -- Mailbox Queue Initializer
> + *
> + * Responsibilities:
> + *   - Allocate queue buffers for each configured mailbox
> + *   - Execute the MBX lock/setup/enable register sequence
> + *   - Readback-verify all critical register writes
> + *   - Hold lock for MBX lifetime (CMH eSW requires it for host access)
> + *   - Clean up (flush + unlock + free) on exit or error
> + *
> + * Register sequence per instance (per CMH MBX hardware specification):
> + *   1. Read R_MBX_LOCK -> non-zero = ownership token acquired
> + *   2. W1C stale R_MBX_INTERRUPT bits (avoids spurious error cascade)
> + *   3. Set R_MBX_INTERRUPT_MASK = MBX_IRQ_MASK
> + *   4. Write QUEUE_LO/HI, SLOTS, STRIDE (queue address + geometry)
> + *   5. Sync TAIL = HEAD (CMH eSW owns HEAD; avoids stale-queue parse)
> + *   6. Readback verify QUEUE_LO/HI/SLOTS/STRIDE
> + *   7. Write COMMAND = MBX_COMMAND_RUN
> + *   8. Lock stays held -- released only in teardown
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/jiffies.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>


...


> +/**
> + * cmh_mqi_init() - Initialize all mailbox queues
> + * @cfg: CMH configuration describing the mailboxes to set up
> + *
> + * Allocates DMA queue buffers for each configured mailbox, then executes
> + * the MBX lock/setup/enable register sequence.  On failure, all
> + * successfully initialized mailboxes are torn down and buffers freed.
> + *
> + * Return: 0 on success, negative errno on failure.
> + */
> +int cmh_mqi_init(struct cmh_config *cfg)
> +{
> +	unsigned int i, j;
> +	int ret;
> +
> +	/* Allocate queue buffers */
> +	for (i = 0; i < cfg->mbx_count; i++) {
> +		struct cmh_mbx_config *m = &cfg->mailboxes[i];
> +
> +		m->virt_addr = cmh_dma_alloc(m->queue_size, &m->dma_handle,
> +					     GFP_KERNEL);
> +		if (!m->virt_addr) {
> +			ret = -ENOMEM;
> +			goto err_free_bufs;
> +		}
> +
> +		dev_dbg(cmh_dev(), "mqi[%u] alloc %zu bytes @ virt=%pK dma=%pad\n",
> +			i, m->queue_size, m->virt_addr, &m->dma_handle);
> +	}
> +
> +	/* Lock/setup/enable each mailbox */
> +	for (i = 0; i < cfg->mbx_count; i++) {
> +		ret = cmh_mbx_setup_one(&cfg->mailboxes[i]);
> +		if (ret) {
> +			dev_err(cmh_dev(), "mqi[%u] setup failed (rc=%d)\n",
> +				i, ret);
> +			goto err_teardown;
> +		}
> +	}
> +
> +	dev_info(cmh_dev(), "MQI init complete (%u mailboxes)\n", cfg->mbx_count);

NAK

> +	return 0;
> +
> +err_teardown:
> +	for (j = 0; j < i; j++)
> +		cmh_mbx_teardown_one(&cfg->mailboxes[j]);
> +err_free_bufs:
> +	for (j = 0; j < cfg->mbx_count; j++) {
> +		if (cfg->mailboxes[j].virt_addr)
> +			cmh_dma_free(cfg->mailboxes[j].queue_size,
> +				     cfg->mailboxes[j].virt_addr,
> +				     cfg->mailboxes[j].dma_handle);
> +		cfg->mailboxes[j].virt_addr = NULL;
> +		cfg->mailboxes[j].dma_handle = 0;
> +	}
> +	return ret;
> +}
> +
> +/**
> + * cmh_mqi_cleanup() - Clean up all mailbox queues
> + * @cfg: CMH configuration describing the mailboxes to tear down
> + *
> + * Tears down each mailbox (flush + unlock) and frees the DMA queue
> + * buffers allocated by cmh_mqi_init().
> + */
> +void cmh_mqi_cleanup(struct cmh_config *cfg)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < cfg->mbx_count; i++) {
> +		struct cmh_mbx_config *m = &cfg->mailboxes[i];
> +
> +		cmh_mbx_teardown_one(m);
> +
> +		if (m->virt_addr)
> +			cmh_dma_free(m->queue_size, m->virt_addr,
> +				     m->dma_handle);
> +		m->virt_addr = NULL;
> +		m->dma_handle = 0;
> +	}
> +
> +	dev_info(cmh_dev(), "MQI cleanup complete\n");

NAK

...

> +
> +/*
> + * Resolve per-MBX Linux virqs for the CMH interrupt lines.
> + *
> + * Each mailbox declares its own completion interrupt in its device-tree
> + * child node; cmh_config_init() resolves these to Linux virqs and stores
> + * them in cfg->mailboxes[i].irq (-1 when the mailbox has no interrupt).
> + * IRQ mode requires every configured mailbox to have an interrupt; if
> + * none do (or only some), the response handler uses watchdog polling.
> + *
> + * Populates rh.irqs[] and rh.nirqs.  Returns 0 on success, or a
> + * negative errno if no IRQs could be resolved (polling-only mode).
> + */
> +static int cmh_rh_resolve_irqs(struct cmh_config *cfg)
> +{
> +	u32 i, nwith = 0;
> +
> +	rh.nirqs = 0;
> +
> +	for (i = 0; i < cfg->mbx_count; i++)
> +		if (cfg->mailboxes[i].irq >= 0)
> +			nwith++;
> +
> +	if (nwith == 0) {
> +		dev_info(cmh_dev(), "rh: no mailbox IRQs -- polling mode\n");

So a warning or info?

> +		return -ENODEV;
> +	}
> +
> +	if (nwith != cfg->mbx_count) {
> +		dev_warn(cmh_dev(),
> +			 "rh: only %u/%u mailboxes have IRQs -- falling back to polling\n",
> +			 nwith, cfg->mbx_count);
> +		return -ENODEV;
> +	}
> +
> +	for (i = 0; i < cfg->mbx_count; i++) {
> +		rh.irqs[i] = cfg->mailboxes[i].irq;
> +		dev_dbg(cmh_dev(), "rh: MBX%u -> IRQ %d\n", i, rh.irqs[i]);
> +	}
> +
> +	rh.nirqs = cfg->mbx_count;
> +	return 0;
> +}
> +
> +/**
> + * cmh_rh_init() - Initialize the response handler
> + * @cfg: Device configuration (mailbox count, MMIO bases, IRQ info)
> + *
> + * Resolve per-mailbox IRQs from the device tree (or module parameter
> + * override), register threaded IRQ handlers (hardirq + kthread), and
> + * arm the missed-IRQ software watchdog timer.  If no IRQs can be
> + * resolved, falls back to watchdog-only polling mode.
> + *
> + * Return: 0 on success, negative errno on failure.
> + */
> +int cmh_rh_init(struct cmh_config *cfg)
> +{
> +	int ret;
> +	u32 i;
> +
> +	rh.cfg = cfg;
> +	rh.nirqs = 0;
> +	rh.active = false;
> +	atomic_set(&rh.irq_count, 0);
> +
> +	/* Allocate per-MBX tracking */
> +	rh.mbx = kcalloc(cfg->mbx_count, sizeof(*rh.mbx), GFP_KERNEL);
> +	if (!rh.mbx)
> +		return -ENOMEM;
> +
> +	/* Resolve per-MBX IRQs */
> +	if (cmh_rh_resolve_irqs(cfg) < 0) {
> +		/*
> +		 * No IRQs available.  The watchdog timer provides
> +		 * a polling fallback: it reads MBX head registers
> +		 * periodically and processes completions.  This is
> +		 * slower than IRQ-driven completion but functional.
> +		 *
> +		 * Completion latency in polling-only mode is bounded
> +		 * by the watchdog interval (default 200 ms, tunable
> +		 * via debugfs config/watchdog_ms).
> +		 */
> +		dev_warn(cmh_dev(),
> +			 "rh: no IRQs -- using watchdog polling (interval %u ms)\n",
> +			 watchdog_ms);

And here it is a warning?

> +
> +		/* Seed last_head from HW before first watchdog tick */
> +		for (i = 0; i < cfg->mbx_count; i++)
> +			rh.mbx[i].last_head =
> +				cmh_reg_read32(cfg->mailboxes[i].reg_base,
> +					       R_MBX_QUEUE_HEAD);
> +
> +		rh.active = true;
> +		timer_setup(&rh_watchdog, cmh_rh_watchdog_fn, 0);
> +		mod_timer(&rh_watchdog, jiffies +
> +			  msecs_to_jiffies(max(watchdog_ms,
> +					       CMH_RH_WATCHDOG_MS_MIN)));
> +		return 0;
> +	}
> +
> +	/* Initialize per-MBX state: read current head positions */
> +	for (i = 0; i < cfg->mbx_count; i++)
> +		rh.mbx[i].last_head = cmh_reg_read32(rh.cfg->mailboxes[i].reg_base,
> +						     R_MBX_QUEUE_HEAD);
> +
> +	/*
> +	 * Register threaded IRQ handlers.
> +	 *
> +	 * DT per-MBX path: one distinct virq per MBX, nirqs == mbx_count.
> +	 * DT single-IRQ path: one shared IRQ, nirqs == 1.  The handler
> +	 * scans all mailboxes unconditionally, so a single registration
> +	 * suffices.
> +	 *
> +	 * Use IRQF_SHARED only for the single-IRQ path where one line
> +	 * is shared across all MBXes.  Dedicated per-MBX virqs need no
> +	 * sharing flag.
> +	 */
> +	{

What is with this indentation?

> +		unsigned long irqflags = (rh.nirqs == 1 && cfg->mbx_count > 1)
> +					  ? IRQF_SHARED : 0;
> +
> +		for (i = 0; i < rh.nirqs; i++) {
> +			ret = request_threaded_irq(rh.irqs[i],
> +						   cmh_rh_hardirq,
> +						   cmh_rh_thread,
> +						   irqflags,
> +						   "cmh", cfg);
> +			if (ret) {
> +				dev_err(cmh_dev(), "rh: request_threaded_irq(%d) for MBX%u failed (rc=%d)\n",
> +					rh.irqs[i], i, ret);
> +				/* Unwind previously registered IRQs */
> +				while (i--)
> +					free_irq(rh.irqs[i], cfg);
> +				rh.nirqs = 0;
> +				kfree(rh.mbx);
> +				rh.mbx = NULL;
> +				return ret;
> +			}
> +		}
> +	}
> +
> +	rh.active = true;
> +
> +	/* Enable MBX completion interrupts (DONE + ERROR) */
> +	for (i = 0; i < cfg->mbx_count; i++) {
> +		u32 stale;
> +
> +		/*
> +		 * W1C any interrupt bits that accumulated between
> +		 * MQI setup and now (e.g. CMH eSW processing stale
> +		 * commands) before enabling the mask.
> +		 */
> +		stale = cmh_reg_read32(cfg->mailboxes[i].reg_base,
> +				       R_MBX_INTERRUPT);
> +		if (stale)
> +			cmh_reg_write32(stale, cfg->mailboxes[i].reg_base,
> +					R_MBX_INTERRUPT);
> +
> +		cmh_reg_write32(MBX_IRQ_MASK,
> +				cfg->mailboxes[i].reg_base,
> +				R_MBX_INTERRUPT_MASK);
> +	}
> +
> +	dev_info(cmh_dev(), "rh: initialized (%u IRQs, %u mailboxes, watchdog %u ms)\n",
> +		 rh.nirqs, cfg->mbx_count, watchdog_ms);

One more dev_info....


> +
> +	/* Arm missed-IRQ watchdog timer */
> +	timer_setup(&rh_watchdog, cmh_rh_watchdog_fn, 0);
> +	mod_timer(&rh_watchdog, jiffies +
> +		  msecs_to_jiffies(max(watchdog_ms,
> +				       CMH_RH_WATCHDOG_MS_MIN)));
> +
> +	return 0;
> +}
> +
> +/**
> + * cmh_rh_suspend() - Suspend the response handler
> + * @cfg: Device configuration
> + *
> + * Stop the watchdog timer and mask mailbox interrupts at the hardware
> + * level.  The IRQ handlers remain registered so that resume can
> + * re-enable them without re-requesting.
> + */
> +void cmh_rh_suspend(struct cmh_config *cfg)
> +{
> +	u32 i;
> +
> +	if (!rh.active)
> +		return;
> +
> +	/* Stop the watchdog before masking HW interrupts */
> +	timer_delete_sync(&rh_watchdog);
> +
> +	/* Mask MBX interrupts at the hardware level */
> +	for (i = 0; i < cfg->mbx_count; i++)
> +		cmh_reg_write32(0, cfg->mailboxes[i].reg_base,
> +				R_MBX_INTERRUPT_MASK);
> +
> +	/*
> +	 * Ensure no threaded IRQ handler is still in-flight.
> +	 * After masking, a handler may already have been scheduled.
> +	 * synchronize_irq() waits for it to complete before we
> +	 * proceed with suspend (which tears down TM state).
> +	 */
> +	for (i = 0; i < rh.nirqs; i++)
> +		synchronize_irq(rh.irqs[i]);
> +
> +	rh.active = false;
> +	dev_dbg(cmh_dev(), "rh: suspended\n");

No, you cannot have even debug messages which re-implement and
duplicated standard tracing and standard debugging code.


...


> +static unsigned int cmq_max_depth = 256;
> +module_param(cmq_max_depth, uint, 0444);
> +MODULE_PARM_DESC(cmq_max_depth,
> +		 "Max pending commands in the Command Message Queue (default: 256)");
> +
> +static unsigned int backlog_max_depth = 1024;
> +module_param(backlog_max_depth, uint, 0444);

Drop all module params, not really right way to do things.

> +MODULE_PARM_DESC(backlog_max_depth,
> +		 "Max pending commands in the backlog queue (0 = disable backlog, default: 1024)");
> +
> +static unsigned int async_timeout_ms = 2000;
> +
> +#define CMH_TM_BACKOFF_MIN_US   100  /* queue-full backoff range (us) */
> +#define CMH_TM_BACKOFF_MAX_US   500
> +static unsigned int cmq_depth;       /* current CMQ depth, protected by tm.cmq_lock */

...


> diff --git a/drivers/crypto/cmh/include/cmh.h b/drivers/crypto/cmh/include/cmh.h
> new file mode 100644
> index 000000000000..18150ba39129
> --- /dev/null
> +++ b/drivers/crypto/cmh/include/cmh.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2026 Cryptography Research, Inc. (CRI).
> + * CMH LKM -- Top-level Device Structure
> + */
> +
> +#ifndef CMH_H
> +#define CMH_H
> +
> +#include <linux/device.h>
> +
> +#include "cmh_config.h"
> +
> +#define CMH_DRV_NAME   "cmh"
> +#define CMH_VERSION    "1.0.0"

NAK, this is not allowed in Linux kernel code. Do you see drivers
(except complete junk from AMD) doing this?

Best regards,
Krzysztof
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.