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