From 83d1816c0edf887f86a55b6cf2cb1f9c14f3c433 Mon Sep 17 00:00:00 2001 From: Ozan Durgut Date: Tue, 26 May 2026 01:00:41 +0200 Subject: [PATCH 1/5] soc: adi: add SC59x SMPU driver Add a driver for the SC59x System Memory Protection Unit and export a small API for configuring protection regions from other ADI drivers. The driver maps the SMPU instances described in DT, handles the shared violation interrupt, supports optional static region setup at probe time, and exposes debugfs state for inspection. Also add the required Kconfig/Makefile plumbing and the public header for SMPU clients. Signed-off-by: Ozan Durgut --- drivers/soc/adi/Kconfig | 8 + drivers/soc/adi/mach-sc59x/Makefile | 1 + drivers/soc/adi/mach-sc59x/smpu.c | 1326 +++++++++++++++++++++++++++ include/linux/soc/adi/smpu.h | 174 ++++ 4 files changed, 1509 insertions(+) create mode 100644 drivers/soc/adi/mach-sc59x/smpu.c create mode 100644 include/linux/soc/adi/smpu.h diff --git a/drivers/soc/adi/Kconfig b/drivers/soc/adi/Kconfig index cdd2a79057de4f..b44856751ba2c4 100644 --- a/drivers/soc/adi/Kconfig +++ b/drivers/soc/adi/Kconfig @@ -16,3 +16,11 @@ config ADI_PADS_SYSTEM help This enables support for the ADI PADS System Config driver, which manages pin configuration and other system settings. + +config ADI_SC59X_SMPU + tristate "ADI SC59x System Memory Protection Unit" + depends on ARCH_SC59X_64 || COMPILE_TEST + default ARCH_SC59X_64 + help + This enables support for the SMPU (System Memory Protection Unit) + on Analog Devices SC59x processors (SC595/SC596/SC598). diff --git a/drivers/soc/adi/mach-sc59x/Makefile b/drivers/soc/adi/mach-sc59x/Makefile index 44baa95f0256de..93cb0c52c117e1 100644 --- a/drivers/soc/adi/mach-sc59x/Makefile +++ b/drivers/soc/adi/mach-sc59x/Makefile @@ -3,3 +3,4 @@ # todo modularize with kconfigs obj-y := core.o +obj-$(CONFIG_ADI_SC59X_SMPU) += smpu.o diff --git a/drivers/soc/adi/mach-sc59x/smpu.c b/drivers/soc/adi/mach-sc59x/smpu.c new file mode 100644 index 00000000000000..57a8c203652c6a --- /dev/null +++ b/drivers/soc/adi/mach-sc59x/smpu.c @@ -0,0 +1,1326 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * System Memory Protection Unit (SMPU) for ADI SC59x processors + * + * Copyright (C) 2026 - Analog Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register offsets */ +#define ADI_SMPU_REG_CTL 0x00 +#define ADI_SMPU_REG_STAT 0x04 +#define ADI_SMPU_REG_IADDR 0x08 +#define ADI_SMPU_REG_IDTLS 0x0C +#define ADI_SMPU_REG_BADDR 0x10 +#define ADI_SMPU_REG_BDTLS 0x14 + +/* Region configuration registers (stride = 0x18 per region) */ +#define ADI_SMPU_RCTL(n) (0x20 + (n) * 0x18) +#define ADI_SMPU_RADDR(n) (0x24 + (n) * 0x18) +#define ADI_SMPU_RIDA(n) (0x28 + (n) * 0x18) +#define ADI_SMPU_RIDMSKA(n) (0x2C + (n) * 0x18) +#define ADI_SMPU_RIDB(n) (0x30 + (n) * 0x18) +#define ADI_SMPU_RIDMSKB(n) (0x34 + (n) * 0x18) + +/* Security registers */ +#define ADI_SMPU_REG_SECURECTL 0x800 +#define ADI_SMPU_SECURERCTL(n) (0x820 + (n) * 0x04) +#define ADI_SMPU_REG_REVID 0x220 + +/* SMPU_SECURECTL register bits */ +#define ADI_SMPU_SECURECTL_LOCK BIT(31) +#define ADI_SMPU_SECURECTL_WSECDIS BIT(11) +#define ADI_SMPU_SECURECTL_WNSEN BIT(10) +#define ADI_SMPU_SECURECTL_RSECDIS BIT(9) +#define ADI_SMPU_SECURECTL_RNSEN BIT(8) +#define ADI_SMPU_SECURECTL_RLOCK BIT(3) +#define ADI_SMPU_SECURECTL_SINTEN BIT(2) +#define ADI_SMPU_SECURECTL_SBETYPE BIT(1) +#define ADI_SMPU_SECURECTL_SBEDIS BIT(0) + +/* SMPU_SECURERCTL[n] register bits */ +#define ADI_SMPU_SECURERCTL_WSECDIS BIT(3) +#define ADI_SMPU_SECURERCTL_WNSEN BIT(2) +#define ADI_SMPU_SECURERCTL_RSECDIS BIT(1) +#define ADI_SMPU_SECURERCTL_RNSEN BIT(0) + +/* SMPU_CTL register bits */ +#define ADI_SMPU_CTL_LOCK BIT(31) +#define ADI_SMPU_CTL_RLOCK BIT(4) +#define ADI_SMPU_CTL_PINTEN BIT(3) +#define ADI_SMPU_CTL_PBETYPE BIT(2) +#define ADI_SMPU_CTL_PBEDIS BIT(1) +#define ADI_SMPU_CTL_RSDIS BIT(0) + +/* SMPU_STAT register bits (W1C) */ +#define ADI_SMPU_STAT_LWERR BIT(17) +#define ADI_SMPU_STAT_ADRERR BIT(16) +#define ADI_SMPU_STAT_BEOVR BIT(3) +#define ADI_SMPU_STAT_BERR BIT(2) +#define ADI_SMPU_STAT_IOVR BIT(1) +#define ADI_SMPU_STAT_IRQ BIT(0) +#define ADI_SMPU_STAT_W1C_MASK 0x3002F + +/* SMPU_RCTL register bits */ +#define ADI_SMPU_RCTL_WIDCINV BIT(11) +#define ADI_SMPU_RCTL_WPROTEN BIT(10) +#define ADI_SMPU_RCTL_RIDCINV BIT(9) +#define ADI_SMPU_RCTL_RPROTEN BIT(8) +#define ADI_SMPU_RCTL_SIZE_SHIFT 1 +#define ADI_SMPU_RCTL_SIZE_MASK GENMASK(5, 1) +#define ADI_SMPU_RCTL_EN BIT(0) + +/* SMPU_IDTLS/BDTLS register bits */ +#define ADI_SMPU_DTLS_ID_SHIFT 8 +#define ADI_SMPU_DTLS_ID_MASK GENMASK(20, 8) +#define ADI_SMPU_DTLS_RW BIT(1) +#define ADI_SMPU_DTLS_SECURE BIT(0) + +/* Maximum number of SMPU instances */ +#define ADI_SMPU_MAX_INSTANCES 9 + +/* Violation history buffer size per instance */ +#define ADI_SMPU_VIOL_HISTORY_SIZE 32 + +/** + * struct adi_smpu_violation - SMPU violation record + * @timestamp: Timestamp of violation from jiffies + * @addr: Violated address + * @id: Transaction ID that caused violation + * @is_write: True if write violation, false if read + * @is_secure: True if secure transaction + */ +struct adi_smpu_violation { + unsigned long timestamp; + u32 addr; + u16 id; + bool is_write; + bool is_secure; +}; + +/** + * struct adi_smpu_instance - Per-instance SMPU data + * @base: Memory-mapped I/O base address + * @instance_id: SMPU instance ID + * @name: Instance name + * @region_bitmap: Bitmap of allocated regions + * @lock: Spinlock for register access + * @smpu: Back pointer to main SMPU structure + * @violations: Circular buffer of violations + * @viol_head: Head index of violation buffer + * @viol_count: Total violation count + * @debugfs_dir: Debugfs directory for this instance + */ +struct adi_smpu_instance { + void __iomem *base; + int instance_id; + const char *name; + unsigned long region_bitmap; + spinlock_t lock; + struct adi_smpu *smpu; + struct adi_smpu_violation violations[ADI_SMPU_VIOL_HISTORY_SIZE]; + unsigned int viol_head; + unsigned long viol_count; + struct dentry *debugfs_dir; +}; + +/** + * struct adi_smpu - Main SMPU driver data + * @dev: Device pointer + * @instances: Array of SMPU instances + * @num_instances: Number of active instances + * @debugfs_root: Root debugfs directory + * @secure_regs_available: Can access 0x800+ registers (Errata, 20000003) + */ +struct adi_smpu { + struct device *dev; + struct adi_smpu_instance instances[ADI_SMPU_MAX_INSTANCES]; + int num_instances; + struct dentry *debugfs_root; + bool secure_regs_available; +}; + +/* Register access helpers */ +static inline u32 adi_smpu_readl(struct adi_smpu_instance *inst, u32 offset) +{ + return readl(inst->base + offset); +} + +static inline void adi_smpu_writel(struct adi_smpu_instance *inst, + u32 val, u32 offset) +{ + writel(val, inst->base + offset); +} + +/** + * adi_smpu_write_secure_reg - Write to secure register + * @inst: SMPU instance + * @val: Value to write + * @offset: Register offset (must be >= 0x800) + * + * Writes to secure registers. + * + * Return: 0 on success, -ENOTSUPP if secure registers unavailable, -EINVAL on error + */ +static int adi_smpu_write_secure_reg(struct adi_smpu_instance *inst, + u32 val, u32 offset) +{ + if (!inst->smpu->secure_regs_available) { + dev_warn_once(inst->smpu->dev, + "Secure register access blocked (Anomaly 20000003)\n"); + return -ENOTSUPP; + } + + if (offset < 0x800) { + dev_err(inst->smpu->dev, "Invalid secure register offset 0x%x\n", offset); + return -EINVAL; + } + + adi_smpu_writel(inst, val, offset); + return 0; +} + +/** + * adi_smpu_read_secure_reg - Read from secure register + * @inst: SMPU instance + * @val: Pointer to store read value + * @offset: Register offset (must be >= 0x800) + * + * Reads from secure registers. + * + * Return: 0 on success, -ENOTSUPP if secure registers unavailable, -EINVAL on error + */ +static int adi_smpu_read_secure_reg(struct adi_smpu_instance *inst, + u32 *val, u32 offset) +{ + if (!inst->smpu->secure_regs_available) + return -ENOTSUPP; + + if (offset < 0x800) + return -EINVAL; + + *val = adi_smpu_readl(inst, offset); + return 0; +} + +/** + * adi_smpu_get_instance - Get SMPU instance by ID + * @smpu: SMPU driver data + * @instance_id: Instance ID to find + * + * Return: Pointer to instance or NULL if not found + */ +static struct adi_smpu_instance * +adi_smpu_get_instance(struct adi_smpu *smpu, int instance_id) +{ + int i; + + for (i = 0; i < smpu->num_instances; i++) { + if (smpu->instances[i].instance_id == instance_id) + return &smpu->instances[i]; + } + + return NULL; +} + +/* Forward declarations */ +static int adi_smpu_configure_region_secure(struct adi_smpu *smpu, + int instance_id, int region_num, + const struct adi_smpu_region_config *config); + +/** + * adi_smpu_calc_size_bits - Calculate size bits for region size + * @size: Region size in bytes + * + * SMPU hardware encodes region size as power-of-2 bits: + * Size = 4KB -> bits = 0 (1 << 12) + * Size = 4GB -> bits = 20 (1 << 32) + * + * Return: Size bits value, or negative error + */ +static int adi_smpu_calc_size_bits(size_t size) +{ + int bits; + + if (size < SZ_4K || size > SZ_4G) + return -EINVAL; + + if (!is_power_of_2(size)) + return -EINVAL; + + bits = ilog2(size) - 12; + if (bits < 0 || bits > 20) + return -EINVAL; + + return bits; +} + +/** + * adi_smpu_validate_region - Validate region configuration + * @config: Region configuration to validate + * + * Return: 0 if valid, negative error otherwise + */ +static int adi_smpu_validate_region(const struct adi_smpu_region_config *config) +{ + int size_bits; + + if (!config) + return -EINVAL; + + /* Validate size */ + size_bits = adi_smpu_calc_size_bits(config->size); + if (size_bits < 0) + return -EINVAL; + + /* Validate alignment */ + if (!IS_ALIGNED(config->base, config->size)) + return -EINVAL; + + return 0; +} + +/** + * adi_smpu_store_violation - Store violation in history buffer + * @inst: SMPU instance + * @addr: Violation address + * @details: Violation details register value + */ +static void adi_smpu_store_violation(struct adi_smpu_instance *inst, + u32 addr, u32 details) +{ + struct adi_smpu_violation *viol; + unsigned int idx; + + idx = inst->viol_head % ADI_SMPU_VIOL_HISTORY_SIZE; + viol = &inst->violations[idx]; + + viol->timestamp = jiffies; + viol->addr = addr; + viol->id = (details & ADI_SMPU_DTLS_ID_MASK) >> ADI_SMPU_DTLS_ID_SHIFT; + viol->is_write = !!(details & ADI_SMPU_DTLS_RW); + viol->is_secure = !!(details & ADI_SMPU_DTLS_SECURE); + + inst->viol_head++; + inst->viol_count++; +} + +/** + * adi_smpu_log_violation - Log protection violation + * @inst: SMPU instance + * @addr: Violation address + * @details: Violation details + */ +static void adi_smpu_log_violation(struct adi_smpu_instance *inst, + u32 addr, u32 details) +{ + u16 trans_id = (details & ADI_SMPU_DTLS_ID_MASK) >> ADI_SMPU_DTLS_ID_SHIFT; + bool is_write = !!(details & ADI_SMPU_DTLS_RW); + bool is_secure = !!(details & ADI_SMPU_DTLS_SECURE); + + dev_err(inst->smpu->dev, + "%s violation: addr=0x%08x id=0x%03x %s %s\n", + inst->name, addr, trans_id, + is_write ? "write" : "read", + is_secure ? "secure" : "non-secure"); + + adi_smpu_store_violation(inst, addr, details); +} + +/** + * adi_smpu_irq_handler - SMPU interrupt handler + * @irq: IRQ number + * @dev_id: Device data (struct adi_smpu pointer) + * + * Handles protection violations for all SMPU instances. + * Multiple instances share the same IRQ line (SECID 217). + * + * Return: IRQ_HANDLED if any violation processed, IRQ_NONE otherwise + */ +static irqreturn_t adi_smpu_irq_handler(int irq, void *dev_id) +{ + struct adi_smpu *smpu = dev_id; + irqreturn_t ret = IRQ_NONE; + int i; + + for (i = 0; i < smpu->num_instances; i++) { + struct adi_smpu_instance *inst = &smpu->instances[i]; + unsigned long flags; + u32 status, iaddr, idtls; + + spin_lock_irqsave(&inst->lock, flags); + + status = adi_smpu_readl(inst, ADI_SMPU_REG_STAT); + + if (status & ADI_SMPU_STAT_IRQ) { + iaddr = adi_smpu_readl(inst, ADI_SMPU_REG_IADDR); + idtls = adi_smpu_readl(inst, ADI_SMPU_REG_IDTLS); + + adi_smpu_log_violation(inst, iaddr, idtls); + + /* Check for overrun conditions */ + if (status & ADI_SMPU_STAT_IOVR) + dev_warn(inst->smpu->dev, + "%s: interrupt overrun\n", inst->name); + + if (status & ADI_SMPU_STAT_BERR) { + u32 baddr = adi_smpu_readl(inst, ADI_SMPU_REG_BADDR); + dev_err(inst->smpu->dev, + "%s: bus error at 0x%08x\n", + inst->name, baddr); + } + + /* Clear W1C status bits */ + adi_smpu_writel(inst, status & ADI_SMPU_STAT_W1C_MASK, + ADI_SMPU_REG_STAT); + + ret = IRQ_HANDLED; + } + + spin_unlock_irqrestore(&inst->lock, flags); + } + + return ret; +} + +/** + * adi_smpu_reset_instance - Reset SMPU instance to safe state + * @inst: SMPU instance + * + * Disables all regions and clears pending violations. + */ +static void adi_smpu_reset_instance(struct adi_smpu_instance *inst) +{ + unsigned long flags; + u32 val; + int i; + + spin_lock_irqsave(&inst->lock, flags); + + /* Disable all regions */ + for (i = 0; i < ADI_SMPU_NUM_REGIONS; i++) + adi_smpu_writel(inst, 0, ADI_SMPU_RCTL(i)); + + /* Clear any pending violations */ + adi_smpu_writel(inst, ADI_SMPU_STAT_W1C_MASK, ADI_SMPU_REG_STAT); + + /* Enable violation interrupts */ + val = adi_smpu_readl(inst, ADI_SMPU_REG_CTL); + val |= ADI_SMPU_CTL_PINTEN; + adi_smpu_writel(inst, val, ADI_SMPU_REG_CTL); + + spin_unlock_irqrestore(&inst->lock, flags); +} + +/* Public API implementation */ + +struct adi_smpu *adi_smpu_get_from_node(struct device *dev) +{ + struct platform_device *smpu_pdev; + struct device_node *smpu_node; + struct adi_smpu *ret = NULL; + + smpu_node = of_parse_phandle(dev->of_node, "adi,smpu", 0); + if (!smpu_node) { + dev_err(dev, "Missing adi,smpu phandle in device tree\n"); + return ERR_PTR(-ENODEV); + } + + smpu_pdev = of_find_device_by_node(smpu_node); + if (!smpu_pdev) { + ret = ERR_PTR(-EPROBE_DEFER); + goto cleanup; + } + + ret = dev_get_drvdata(&smpu_pdev->dev); + if (!ret) + ret = ERR_PTR(-EPROBE_DEFER); + +cleanup: + of_node_put(smpu_node); + return ret; +} +EXPORT_SYMBOL(adi_smpu_get_from_node); + +void adi_smpu_put(struct adi_smpu *smpu) +{ + if (smpu) + put_device(smpu->dev); +} +EXPORT_SYMBOL(adi_smpu_put); + +int adi_smpu_configure_region(struct adi_smpu *smpu, int instance_id, + int region_num, + const struct adi_smpu_region_config *config) +{ + struct adi_smpu_instance *inst; + unsigned long flags; + u32 rctl, raddr; + int size_bits; + int ret; + + if (!smpu || !config) + return -EINVAL; + + if (region_num < 0 || region_num >= ADI_SMPU_NUM_REGIONS) + return -EINVAL; + + ret = adi_smpu_validate_region(config); + if (ret) + return ret; + + inst = adi_smpu_get_instance(smpu, instance_id); + if (!inst) + return -EINVAL; + + size_bits = adi_smpu_calc_size_bits(config->size); + + spin_lock_irqsave(&inst->lock, flags); + + /* Disable region during configuration */ + adi_smpu_writel(inst, 0, ADI_SMPU_RCTL(region_num)); + + /* Configure base address (bits 31:12) */ + raddr = (config->base >> 12) & 0xFFFFF; + adi_smpu_writel(inst, raddr, ADI_SMPU_RADDR(region_num)); + + /* Configure transaction ID filters */ + adi_smpu_writel(inst, config->allowed_ids[0] & 0x1FFF, + ADI_SMPU_RIDA(region_num)); + adi_smpu_writel(inst, config->id_masks[0] & 0x1FFF, + ADI_SMPU_RIDMSKA(region_num)); + adi_smpu_writel(inst, config->allowed_ids[1] & 0x1FFF, + ADI_SMPU_RIDB(region_num)); + adi_smpu_writel(inst, config->id_masks[1] & 0x1FFF, + ADI_SMPU_RIDMSKB(region_num)); + + /* Configure control register */ + rctl = (size_bits << ADI_SMPU_RCTL_SIZE_SHIFT) & ADI_SMPU_RCTL_SIZE_MASK; + + if (config->permissions & ADI_SMPU_PERM_READ) + rctl |= ADI_SMPU_RCTL_RPROTEN; + if (config->permissions & ADI_SMPU_PERM_WRITE) + rctl |= ADI_SMPU_RCTL_WPROTEN; + if (config->id_invert[0]) + rctl |= ADI_SMPU_RCTL_RIDCINV; + if (config->id_invert[1]) + rctl |= ADI_SMPU_RCTL_WIDCINV; + + /* Write control register (region still disabled) */ + adi_smpu_writel(inst, rctl, ADI_SMPU_RCTL(region_num)); + + spin_unlock_irqrestore(&inst->lock, flags); + + dev_dbg(smpu->dev, "%s: configured region %d: base=0x%llx size=0x%zx\n", + inst->name, region_num, (u64)config->base, config->size); + + /* Configure secure access if requested and available */ + if (config->secure_mode != ADI_SMPU_SEC_BOTH) { + ret = adi_smpu_configure_region_secure(smpu, instance_id, + region_num, config); + /* Errors are logged but don't fail configuration */ + } + + return 0; +} +EXPORT_SYMBOL(adi_smpu_configure_region); + +/** + * adi_smpu_configure_region_secure - Configure per-region secure access + * @smpu: SMPU handle + * @instance_id: SMPU instance + * @region_num: Region number + * @config: Region configuration with secure_mode field + * + * Configures SECURERCTL[n] register for the specified region. + * Only works if secure_regs_available flag is set. + * + * Return: 0 on success, -ENOTSUPP if secure regs unavailable, -EINVAL on error + */ +static int adi_smpu_configure_region_secure(struct adi_smpu *smpu, + int instance_id, int region_num, + const struct adi_smpu_region_config *config) +{ + struct adi_smpu_instance *inst; + u32 securerctl = 0; + int ret; + + inst = adi_smpu_get_instance(smpu, instance_id); + if (!inst) + return -EINVAL; + + if (region_num < 0 || region_num >= ADI_SMPU_NUM_REGIONS) + return -EINVAL; + + /* Build SECURERCTL value based on mode */ + switch (config->secure_mode) { + case ADI_SMPU_SEC_BOTH: + /* Allow both secure and non-secure */ + securerctl = ADI_SMPU_SECURERCTL_RNSEN | /* Non-secure reads */ + ADI_SMPU_SECURERCTL_WNSEN; /* Non-secure writes */ + /* Secure enabled by default (RSECDIS=0, WSECDIS=0) */ + break; + + case ADI_SMPU_SEC_SECURE_ONLY: + /* Allow only secure transactions */ + securerctl = 0; /* Non-secure enabled by default */ + /* Secure enabled by default */ + break; + + case ADI_SMPU_SEC_NONSECURE_ONLY: + /* Allow only non-secure transactions */ + securerctl = ADI_SMPU_SECURERCTL_RNSEN | + ADI_SMPU_SECURERCTL_WNSEN | + ADI_SMPU_SECURERCTL_RSECDIS | /* Block secure reads */ + ADI_SMPU_SECURERCTL_WSECDIS; /* Block secure writes */ + break; + + case ADI_SMPU_SEC_NONE: + /* Block all */ + securerctl = ADI_SMPU_SECURERCTL_RSECDIS | + ADI_SMPU_SECURERCTL_WSECDIS; + /* RNSEN=0, WNSEN=0 already blocks non-secure */ + break; + + default: + return -EINVAL; + } + + /* Write to SECURERCTL[n] register */ + ret = adi_smpu_write_secure_reg(inst, securerctl, + ADI_SMPU_SECURERCTL(region_num)); + if (ret == -ENOTSUPP) { + /* Just warning */ + dev_warn_once(smpu->dev, + "Secure mode not available, using TID filtering only\n"); + return 0; + } + + dev_dbg(smpu->dev, "%s region %d: secure_mode=%d (SECURERCTL=0x%x)\n", + inst->name, region_num, config->secure_mode, securerctl); + + return ret; +} + +int adi_smpu_enable_region(struct adi_smpu *smpu, int instance_id, + int region_num) +{ + struct adi_smpu_instance *inst; + unsigned long flags; + u32 rctl; + + if (!smpu) + return -EINVAL; + + if (region_num < 0 || region_num >= ADI_SMPU_NUM_REGIONS) + return -EINVAL; + + inst = adi_smpu_get_instance(smpu, instance_id); + if (!inst) + return -EINVAL; + + spin_lock_irqsave(&inst->lock, flags); + + rctl = adi_smpu_readl(inst, ADI_SMPU_RCTL(region_num)); + rctl |= ADI_SMPU_RCTL_EN; + adi_smpu_writel(inst, rctl, ADI_SMPU_RCTL(region_num)); + + spin_unlock_irqrestore(&inst->lock, flags); + + dev_dbg(smpu->dev, "%s: enabled region %d\n", inst->name, region_num); + + return 0; +} +EXPORT_SYMBOL(adi_smpu_enable_region); + +int adi_smpu_disable_region(struct adi_smpu *smpu, int instance_id, + int region_num) +{ + struct adi_smpu_instance *inst; + unsigned long flags; + u32 rctl; + + if (!smpu) + return -EINVAL; + + if (region_num < 0 || region_num >= ADI_SMPU_NUM_REGIONS) + return -EINVAL; + + inst = adi_smpu_get_instance(smpu, instance_id); + if (!inst) + return -EINVAL; + + spin_lock_irqsave(&inst->lock, flags); + + rctl = adi_smpu_readl(inst, ADI_SMPU_RCTL(region_num)); + rctl &= ~ADI_SMPU_RCTL_EN; + adi_smpu_writel(inst, rctl, ADI_SMPU_RCTL(region_num)); + + spin_unlock_irqrestore(&inst->lock, flags); + + dev_dbg(smpu->dev, "%s: disabled region %d\n", inst->name, region_num); + + return 0; +} +EXPORT_SYMBOL(adi_smpu_disable_region); + +int adi_smpu_lock_config(struct adi_smpu *smpu, int instance_id) +{ + struct adi_smpu_instance *inst; + unsigned long flags; + u32 val; + + if (!smpu) + return -EINVAL; + + inst = adi_smpu_get_instance(smpu, instance_id); + if (!inst) + return -EINVAL; + + spin_lock_irqsave(&inst->lock, flags); + + val = adi_smpu_readl(inst, ADI_SMPU_REG_CTL); + val |= ADI_SMPU_CTL_LOCK | ADI_SMPU_CTL_RLOCK; + adi_smpu_writel(inst, val, ADI_SMPU_REG_CTL); + + spin_unlock_irqrestore(&inst->lock, flags); + + dev_info(smpu->dev, "%s: configuration locked\n", inst->name); + + return 0; +} +EXPORT_SYMBOL(adi_smpu_lock_config); + +int adi_smpu_allocate_region(struct adi_smpu *smpu, int instance_id) +{ + struct adi_smpu_instance *inst; + unsigned long flags; + int region; + + if (!smpu) + return -EINVAL; + + inst = adi_smpu_get_instance(smpu, instance_id); + if (!inst) + return -EINVAL; + + spin_lock_irqsave(&inst->lock, flags); + + region = find_first_zero_bit(&inst->region_bitmap, ADI_SMPU_NUM_REGIONS); + if (region >= ADI_SMPU_NUM_REGIONS) { + spin_unlock_irqrestore(&inst->lock, flags); + return -ENOSPC; + } + + set_bit(region, &inst->region_bitmap); + + spin_unlock_irqrestore(&inst->lock, flags); + + return region; +} +EXPORT_SYMBOL(adi_smpu_allocate_region); + +void adi_smpu_free_region(struct adi_smpu *smpu, int instance_id, + int region_num) +{ + struct adi_smpu_instance *inst; + unsigned long flags; + + if (!smpu || region_num < 0 || region_num >= ADI_SMPU_NUM_REGIONS) + return; + + inst = adi_smpu_get_instance(smpu, instance_id); + if (!inst) + return; + + spin_lock_irqsave(&inst->lock, flags); + + /* Disable region */ + adi_smpu_writel(inst, 0, ADI_SMPU_RCTL(region_num)); + + /* Mark as free */ + clear_bit(region_num, &inst->region_bitmap); + + spin_unlock_irqrestore(&inst->lock, flags); +} +EXPORT_SYMBOL(adi_smpu_free_region); + +/* Debugfs interface */ + +static int smpu_status_show(struct seq_file *s, void *data) +{ + struct adi_smpu_instance *inst = s->private; + unsigned long flags; + u32 ctl, stat; + + spin_lock_irqsave(&inst->lock, flags); + ctl = adi_smpu_readl(inst, ADI_SMPU_REG_CTL); + stat = adi_smpu_readl(inst, ADI_SMPU_REG_STAT); + spin_unlock_irqrestore(&inst->lock, flags); + + seq_printf(s, "Control: 0x%08x\n", ctl); + seq_printf(s, " Locked: %s\n", ctl & ADI_SMPU_CTL_LOCK ? "yes" : "no"); + seq_printf(s, " Region locked: %s\n", ctl & ADI_SMPU_CTL_RLOCK ? "yes" : "no"); + seq_printf(s, " IRQ enabled: %s\n", ctl & ADI_SMPU_CTL_PINTEN ? "yes" : "no"); + seq_printf(s, "\nStatus: 0x%08x\n", stat); + seq_printf(s, " IRQ pending: %s\n", stat & ADI_SMPU_STAT_IRQ ? "yes" : "no"); + seq_printf(s, " Bus error: %s\n", stat & ADI_SMPU_STAT_BERR ? "yes" : "no"); + + /* Show secure control (if available) */ + if (inst->smpu->secure_regs_available) { + u32 securectl; + int ret = adi_smpu_read_secure_reg(inst, &securectl, ADI_SMPU_REG_SECURECTL); + + if (ret == 0) { + seq_printf(s, "\nSECURECTL: 0x%08x\n", securectl); + seq_printf(s, " Secure reads: %s\n", + (securectl & ADI_SMPU_SECURECTL_RSECDIS) ? "disabled" : "enabled"); + seq_printf(s, " Secure writes: %s\n", + (securectl & ADI_SMPU_SECURECTL_WSECDIS) ? "disabled" : "enabled"); + seq_printf(s, " Non-secure reads: %s\n", + (securectl & ADI_SMPU_SECURECTL_RNSEN) ? "enabled" : "disabled"); + seq_printf(s, " Non-secure writes: %s\n", + (securectl & ADI_SMPU_SECURECTL_WNSEN) ? "enabled" : "disabled"); + seq_printf(s, " Security IRQ: %s\n", + (securectl & ADI_SMPU_SECURECTL_SINTEN) ? "enabled" : "disabled"); + } + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(smpu_status); + +static int smpu_regions_show(struct seq_file *s, void *data) +{ + struct adi_smpu_instance *inst = s->private; + unsigned long flags; + int i; + + seq_printf(s, "%-6s %-10s %-12s %-10s %-6s %-8s %-8s\n", + "Region", "Enabled", "Base", "Size", "Perms", "IDA", "IDB"); + seq_puts(s, "------------------------------------------------------------\n"); + + spin_lock_irqsave(&inst->lock, flags); + + for (i = 0; i < ADI_SMPU_NUM_REGIONS; i++) { + u32 rctl = adi_smpu_readl(inst, ADI_SMPU_RCTL(i)); + u32 raddr = adi_smpu_readl(inst, ADI_SMPU_RADDR(i)); + u32 rida = adi_smpu_readl(inst, ADI_SMPU_RIDA(i)); + u32 ridb = adi_smpu_readl(inst, ADI_SMPU_RIDB(i)); + + if (rctl & ADI_SMPU_RCTL_EN) { + int size_bits = (rctl & ADI_SMPU_RCTL_SIZE_MASK) >> + ADI_SMPU_RCTL_SIZE_SHIFT; + size_t size = 1UL << (size_bits + 12); + phys_addr_t base = (phys_addr_t)raddr << 12; + char perms[4] = "---"; + + if (rctl & ADI_SMPU_RCTL_RPROTEN) + perms[0] = 'R'; + if (rctl & ADI_SMPU_RCTL_WPROTEN) + perms[1] = 'W'; + + seq_printf(s, "%-6d %-10s 0x%08llx 0x%-8zx %-6s 0x%04x 0x%04x", + i, "yes", (u64)base, size, perms, + rida & 0x1FFF, ridb & 0x1FFF); + + /* Show secure control for this region (if available) */ + if (inst->smpu->secure_regs_available) { + u32 securerctl; + int ret = adi_smpu_read_secure_reg(inst, &securerctl, + ADI_SMPU_SECURERCTL(i)); + if (ret == 0) { + seq_printf(s, " | Sec:%c%c NS:%c%c", + (securerctl & ADI_SMPU_SECURERCTL_RSECDIS) ? '-' : 'R', + (securerctl & ADI_SMPU_SECURERCTL_WSECDIS) ? '-' : 'W', + (securerctl & ADI_SMPU_SECURERCTL_RNSEN) ? 'R' : '-', + (securerctl & ADI_SMPU_SECURERCTL_WNSEN) ? 'W' : '-'); + } + } + seq_puts(s, "\n"); + } else { + seq_printf(s, "%-6d %-10s\n", i, "no"); + } + } + + spin_unlock_irqrestore(&inst->lock, flags); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(smpu_regions); + +static int smpu_violations_show(struct seq_file *s, void *data) +{ + struct adi_smpu_instance *inst = s->private; + unsigned long flags; + unsigned int i, count, start; + + seq_printf(s, "Total violations: %lu\n\n", inst->viol_count); + + if (inst->viol_count == 0) { + seq_puts(s, "No violations recorded\n"); + return 0; + } + + seq_printf(s, "%-10s %-12s %-8s %-6s %-8s\n", + "Time", "Address", "ID", "Type", "Secure"); + seq_puts(s, "---------------------------------------------------\n"); + + spin_lock_irqsave(&inst->lock, flags); + + count = min(inst->viol_count, (unsigned long)ADI_SMPU_VIOL_HISTORY_SIZE); + if (inst->viol_count > ADI_SMPU_VIOL_HISTORY_SIZE) + start = inst->viol_head % ADI_SMPU_VIOL_HISTORY_SIZE; + else + start = 0; + + for (i = 0; i < count; i++) { + unsigned int idx = (start + i) % ADI_SMPU_VIOL_HISTORY_SIZE; + struct adi_smpu_violation *viol = &inst->violations[idx]; + unsigned long age = jiffies - viol->timestamp; + + seq_printf(s, "%-10lu 0x%08x 0x%04x %-6s %-8s\n", + age / HZ, viol->addr, viol->id, + viol->is_write ? "write" : "read", + viol->is_secure ? "yes" : "no"); + } + + spin_unlock_irqrestore(&inst->lock, flags); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(smpu_violations); + +static int smpu_stats_show(struct seq_file *s, void *data) +{ + struct adi_smpu_instance *inst = s->private; + + seq_printf(s, "Total violations: %lu\n", inst->viol_count); + seq_printf(s, "History size: %u\n", ADI_SMPU_VIOL_HISTORY_SIZE); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(smpu_stats); + +static int smpu_instances_show(struct seq_file *s, void *data) +{ + struct adi_smpu *smpu = s->private; + int i; + + seq_printf(s, "Total instances: %d\n\n", smpu->num_instances); + seq_printf(s, "%-10s %-12s %-10s\n", "Name", "Base", "ID"); + seq_puts(s, "------------------------------------\n"); + + for (i = 0; i < smpu->num_instances; i++) { + struct adi_smpu_instance *inst = &smpu->instances[i]; + seq_printf(s, "%-10s 0x%08lx %-10d\n", + inst->name, (unsigned long)inst->base, + inst->instance_id); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(smpu_instances); + +static int smpu_summary_show(struct seq_file *s, void *data) +{ + struct adi_smpu *smpu = s->private; + unsigned long total_violations = 0; + int i; + + seq_printf(s, "ADSP-SC59x System Memory Protection Unit\n"); + seq_printf(s, "Instances: %d\n", smpu->num_instances); + seq_printf(s, "Secure registers: %s\n", + smpu->secure_regs_available ? "available" : "unavailable (Errata, 20000003)"); + seq_puts(s, "\n"); + + for (i = 0; i < smpu->num_instances; i++) + total_violations += smpu->instances[i].viol_count; + + seq_printf(s, "Total violations: %lu\n", total_violations); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(smpu_summary); + +static void adi_smpu_debugfs_init(struct adi_smpu *smpu) +{ + struct dentry *root; + int i; + + root = debugfs_create_dir("smpu", NULL); + smpu->debugfs_root = root; + + /* Everybody can read, nobody can write */ + debugfs_create_file("instances", 0444, root, smpu, + &smpu_instances_fops); + debugfs_create_file("summary", 0444, root, smpu, + &smpu_summary_fops); + + for (i = 0; i < smpu->num_instances; i++) { + struct adi_smpu_instance *inst = &smpu->instances[i]; + struct dentry *inst_dir; + + inst_dir = debugfs_create_dir(inst->name, root); + inst->debugfs_dir = inst_dir; + + debugfs_create_file("status", 0444, inst_dir, inst, + &smpu_status_fops); + debugfs_create_file("regions", 0444, inst_dir, inst, + &smpu_regions_fops); + debugfs_create_file("violations", 0444, inst_dir, inst, + &smpu_violations_fops); + debugfs_create_file("stats", 0444, inst_dir, inst, + &smpu_stats_fops); + } +} + +/* Static region parsing from device tree */ + +static int adi_smpu_parse_static_regions(struct adi_smpu *smpu) +{ + struct device *dev = smpu->dev; + struct device_node *np = dev->of_node; + struct device_node *regions_node, *child; + int region_count = 0; + + /* Look for "static-regions" child node */ + regions_node = of_get_child_by_name(np, "static-regions"); + if (!regions_node) { + dev_dbg(dev, "No static-regions defined in device tree\n"); + return 0; /* static regions are optional so no error */ + } + + dev_info(dev, "Parsing static protection regions from device tree...\n"); + + /* Iterate through each region definition */ + for_each_child_of_node(regions_node, child) { + struct adi_smpu_region_config config = {0}; + struct device_node *mem_node; + struct resource res; + u32 instance, permissions; + int region_num, ret; + u32 allowed_ids[2] = {0}; + u32 id_masks[2] = {0}; + int id_count; + + /* Parse instance ID */ + ret = of_property_read_u32(child, "instance", &instance); + if (ret) { + dev_err(dev, "Missing 'instance' property in region '%s'\n", + child->name); + of_node_put(child); + of_node_put(regions_node); + return ret; + } + + /* Parse memory-region phandle to get base and size */ + mem_node = of_parse_phandle(child, "memory-region", 0); + if (mem_node) { + ret = of_address_to_resource(mem_node, 0, &res); + of_node_put(mem_node); + if (ret) { + dev_err(dev, "Failed to parse memory-region for '%s'\n", + child->name); + of_node_put(child); + of_node_put(regions_node); + return ret; + } + config.base = res.start; + config.size = resource_size(&res); + } else { + /* Fallback: direct base/size properties */ + u64 base, size; + + ret = of_property_read_u64(child, "base", &base); + if (ret) { + dev_err(dev, "Missing 'base' or 'memory-region' in '%s'\n", + child->name); + of_node_put(child); + of_node_put(regions_node); + return ret; + } + + ret = of_property_read_u64(child, "size", &size); + if (ret) { + dev_err(dev, "Missing 'size' in '%s'\n", child->name); + of_node_put(child); + of_node_put(regions_node); + return ret; + } + + config.base = base; + config.size = size; + } + + /* Parse permissions (default to RW if not specified) */ + ret = of_property_read_u32(child, "permissions", &permissions); + if (ret) + permissions = ADI_SMPU_PERM_RW; + config.permissions = permissions; + + /* Parse allowed transaction IDs (max 2) */ + id_count = of_property_count_u32_elems(child, "allowed-ids"); + if (id_count > 0) { + if (id_count > 2) + id_count = 2; + of_property_read_u32_array(child, "allowed-ids", allowed_ids, id_count); + config.allowed_ids[0] = (u16)allowed_ids[0]; + if (id_count > 1) + config.allowed_ids[1] = (u16)allowed_ids[1]; + } + + /* Parse ID masks (default to exact match(0x1FFF)) */ + id_count = of_property_count_u32_elems(child, "id-masks"); + if (id_count > 0) { + if (id_count > 2) + id_count = 2; + of_property_read_u32_array(child, "id-masks", id_masks, id_count); + config.id_masks[0] = (u16)id_masks[0]; + if (id_count > 1) + config.id_masks[1] = (u16)id_masks[1]; + } else { + /* Default to exact match */ + config.id_masks[0] = 0x1FFF; + config.id_masks[1] = 0x1FFF; + } + + /* Parse ID inversion (default to false(no inversion)) */ + config.id_invert[0] = of_property_read_bool(child, "id-invert-a"); + config.id_invert[1] = of_property_read_bool(child, "id-invert-b"); + + /* Parse secure mode */ + if (of_property_read_bool(child, "secure-only")) { + config.secure_mode = ADI_SMPU_SEC_SECURE_ONLY; + } else if (of_property_read_bool(child, "non-secure-only")) { + config.secure_mode = ADI_SMPU_SEC_NONSECURE_ONLY; + } else { + config.secure_mode = ADI_SMPU_SEC_BOTH; /* Default */ + } + + /* Allocate a region for this configuration */ + region_num = adi_smpu_allocate_region(smpu, instance); + if (region_num < 0) { + dev_err(dev, "Failed to allocate region for '%s' on SMPU%d: %d\n", + child->name, instance, region_num); + of_node_put(child); + of_node_put(regions_node); + return region_num; + } + + /* Configure the region */ + ret = adi_smpu_configure_region(smpu, instance, region_num, &config); + if (ret) { + dev_err(dev, "Failed to configure region %d on SMPU%d: %d\n", + region_num, instance, ret); + adi_smpu_free_region(smpu, instance, region_num); + of_node_put(child); + of_node_put(regions_node); + return ret; + } + + /* Enable the region */ + ret = adi_smpu_enable_region(smpu, instance, region_num); + if (ret) { + dev_err(dev, "Failed to enable region %d on SMPU%d: %d\n", + region_num, instance, ret); + adi_smpu_free_region(smpu, instance, region_num); + of_node_put(child); + of_node_put(regions_node); + return ret; + } + + dev_info(dev, "Static region %d: SMPU%d [0x%llx-0x%llx] perm=0x%x ids=[0x%03x,0x%03x]\n", + region_num, instance, + (unsigned long long)config.base, + (unsigned long long)(config.base + config.size - 1), + config.permissions, + config.allowed_ids[0], config.allowed_ids[1]); + + region_count++; + } + + of_node_put(regions_node); + + if (region_count > 0) + dev_info(dev, "Configured %d static protection regions\n", region_count); + + return 0; +} + +/* Platform driver */ + +static int adi_smpu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct adi_smpu *smpu; + int ret, irq, i; + + smpu = devm_kzalloc(dev, sizeof(*smpu), GFP_KERNEL); + if (!smpu) + return -ENOMEM; + + smpu->dev = dev; + dev_set_drvdata(dev, smpu); + + /* Check if secure registers are accessible (Errata, 20000003) */ + smpu->secure_regs_available = + of_property_read_bool(np, "adi,secure-access"); + + /* Parse and map all SMPU instances from device tree */ + for (i = 0; i < ADI_SMPU_MAX_INSTANCES; i++) { + struct adi_smpu_instance *inst = &smpu->instances[i]; + struct resource *res; + const char *name; + void __iomem *base; + int instance_id; + + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!res) + break; + + ret = of_property_read_string_index(np, "reg-names", i, &name); + if (ret) { + dev_err(dev, "Missing reg-names entry %d\n", i); + return ret; + } + + /* Extract instance ID from name (like, "smpu0" -> 0) */ + ret = sscanf(name, "smpu%d", &instance_id); + if (ret != 1) { + dev_err(dev, "Invalid reg-name: %s\n", name); + return -EINVAL; + } + + base = devm_ioremap(dev, res->start, resource_size(res)); + if (IS_ERR(base)) { + dev_err(dev, "Failed to map %s\n", name); + return PTR_ERR(base); + } + + inst->base = base; + inst->instance_id = instance_id; + inst->name = devm_kasprintf(dev, GFP_KERNEL, "smpu%d", instance_id); + inst->smpu = smpu; + inst->region_bitmap = 0; + inst->viol_head = 0; + inst->viol_count = 0; + spin_lock_init(&inst->lock); + + dev_dbg(dev, "Mapped %s at 0x%08lx\n", inst->name, + (unsigned long)inst->base); + } + + smpu->num_instances = i; + + if (smpu->num_instances == 0) { + dev_err(dev, "No SMPU instances found\n"); + return -ENODEV; + } + + /* Reset all instances to safe state */ + for (i = 0; i < smpu->num_instances; i++) + adi_smpu_reset_instance(&smpu->instances[i]); + + /* Configure global secure control if available */ + if (smpu->secure_regs_available) { + for (i = 0; i < smpu->num_instances; i++) { + struct adi_smpu_instance *inst = &smpu->instances[i]; + u32 securectl; + + /* Enable both secure and non-secure by default, + secure is enabled as default already */ + securectl = ADI_SMPU_SECURECTL_RNSEN | + ADI_SMPU_SECURECTL_WNSEN | + ADI_SMPU_SECURECTL_SINTEN; + + ret = adi_smpu_write_secure_reg(inst, securectl, + ADI_SMPU_REG_SECURECTL); + if (ret) { + dev_warn(dev, "%s: Failed to init SECURECTL: %d\n", + inst->name, ret); + smpu->secure_regs_available = false; + break; + } + + dev_dbg(dev, "%s: SECURECTL initialized (0x%x)\n", + inst->name, securectl); + } + } + + /* Request IRQ for violations (shared across all instances) */ + irq = platform_get_irq(pdev, 0); + if (irq > 0) { + ret = devm_request_irq(dev, irq, adi_smpu_irq_handler, + IRQF_SHARED, dev_name(dev), smpu); + if (ret) { + dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); + return ret; + } + dev_info(dev, "Registered IRQ %d for violations\n", irq); + } else { + dev_warn(dev, "No IRQ specified, violations will not be reported\n"); + } + + /* Parse and configure static regions from device tree */ + ret = adi_smpu_parse_static_regions(smpu); + if (ret) { + dev_err(dev, "Failed to parse static regions: %d\n", ret); + return ret; + } + + /* Create debugfs interface */ + adi_smpu_debugfs_init(smpu); + + dev_info(dev, "Initialized %d SMPU instances\n", smpu->num_instances); + + if (!smpu->secure_regs_available) + dev_warn(dev, "Security registers unavailable (Errata, 20000003)\n"); + + return 0; +} + +static void adi_smpu_remove(struct platform_device *pdev) +{ + struct adi_smpu *smpu = dev_get_drvdata(&pdev->dev); + + debugfs_remove_recursive(smpu->debugfs_root); +} + +static const struct of_device_id adi_smpu_match[] = { + { .compatible = "adi,sc59x-smpu" }, + { } +}; +MODULE_DEVICE_TABLE(of, adi_smpu_match); + +static struct platform_driver adi_smpu_driver = { + .probe = adi_smpu_probe, + .remove = adi_smpu_remove, + .driver = { + .name = "adi-sc59x-smpu", + .of_match_table = of_match_ptr(adi_smpu_match) + }, +}; + +module_platform_driver(adi_smpu_driver); + +MODULE_DESCRIPTION("System Memory Protection Unit for ADI SC59x SoCs"); +MODULE_AUTHOR("OZAN DURGUT"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/soc/adi/smpu.h b/include/linux/soc/adi/smpu.h new file mode 100644 index 00000000000000..b664ef2e27721e --- /dev/null +++ b/include/linux/soc/adi/smpu.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Analog Devices SC59x System Memory Protection Unit + * + * Copyright (C) 2025 - Analog Devices, Inc. + * Author: Ozan Durgutt + */ + +#ifndef SOC_ADI_SMPU_H +#define SOC_ADI_SMPU_H + +#include +#include + +/* SMPU instance IDs (sparse numbering: 0, 2-6, 9, 11-12) */ +#define ADI_SMPU0 0 +#define ADI_SMPU2 2 +#define ADI_SMPU3 3 +#define ADI_SMPU4 4 +#define ADI_SMPU5 5 +#define ADI_SMPU6 6 +#define ADI_SMPU9 9 +#define ADI_SMPU11 11 +#define ADI_SMPU12 12 + +/* Number of regions per SMPU instance */ +#define ADI_SMPU_NUM_REGIONS 8 + +/* Forward declaration */ +struct adi_smpu; + +/** + * enum adi_smpu_secure_mode - Secure access modes (TrustZone) + * @ADI_SMPU_SEC_BOTH: Allow both secure & non-secure transactions (default) + * @ADI_SMPU_SEC_SECURE_ONLY: Allow only secure transactions + * @ADI_SMPU_SEC_NONSECURE_ONLY: Allow only non-secure transactions + * @ADI_SMPU_SEC_NONE: Block all transactions (for debugging) + * + * Only used if secure registers available (adi,secure-access property). + */ +enum adi_smpu_secure_mode { + ADI_SMPU_SEC_BOTH = 0, + ADI_SMPU_SEC_SECURE_ONLY, + ADI_SMPU_SEC_NONSECURE_ONLY, + ADI_SMPU_SEC_NONE, +}; + +/** + * struct adi_smpu_region_config - SMPU region configuration + * @base: Physical base address of the protected region (must be aligned to size) + * @size: Size of the region (must be power of 2, minimum 4KB, maximum 4GB) + * @permissions: Access permissions (ADI_SMPU_PERM_* flags) + * @allowed_ids: Transaction IDs allowed to access (up to 2 ID ranges) + * @id_masks: Masks for ID matching (0x0000 = match all, 0x1FFF = exact match) + * @id_invert: Invert ID match logic (true = block specified IDs) + * @secure_mode: Secure/non-secure access control (only if secure regs available) + */ +struct adi_smpu_region_config { + phys_addr_t base; + size_t size; + u32 permissions; + u16 allowed_ids[2]; + u16 id_masks[2]; + bool id_invert[2]; + enum adi_smpu_secure_mode secure_mode; +}; + +/* Permission flags */ +#define ADI_SMPU_PERM_READ BIT(0) +#define ADI_SMPU_PERM_WRITE BIT(1) +#define ADI_SMPU_PERM_EXECUTE BIT(2) +#define ADI_SMPU_PERM_RW (ADI_SMPU_PERM_READ | ADI_SMPU_PERM_WRITE) +#define ADI_SMPU_PERM_RWX (ADI_SMPU_PERM_RW | ADI_SMPU_PERM_EXECUTE) + +/** + * adi_smpu_get_from_node - Get SMPU driver instance from device tree + * @dev: Device requesting SMPU access + * + * Parses "adi,smpu" phandle from device tree and returns the SMPU driver + * instance. Call adi_smpu_put() when done. + * + * Return: Pointer to SMPU instance or ERR_PTR on error + */ +struct adi_smpu *adi_smpu_get_from_node(struct device *dev); + +/** + * adi_smpu_put - Release SMPU driver instance + * @smpu: SMPU instance to release + */ +void adi_smpu_put(struct adi_smpu *smpu); + +/** + * adi_smpu_configure_region - Configure an SMPU protection region + * @smpu: SMPU driver instance + * @instance_id: SMPU instance ID (0, 2-6, 9, 11-12) + * @region_num: Region number (0-7) + * @config: Region configuration + * + * Configures the specified protection region. The region will be disabled + * during configuration. Call adi_smpu_enable_region() to activate. + * + * Return: 0 on success, negative error code on failure + */ +int adi_smpu_configure_region(struct adi_smpu *smpu, int instance_id, + int region_num, + const struct adi_smpu_region_config *config); + +/** + * adi_smpu_enable_region - Enable an SMPU protection region + * @smpu: SMPU driver instance + * @instance_id: SMPU instance ID (0, 2-6, 9, 11-12) + * @region_num: Region number (0-7) + * + * Enables a previously configured protection region. The region must be + * configured via adi_smpu_configure_region() first. + * + * Return: 0 on success, negative error code on failure + */ +int adi_smpu_enable_region(struct adi_smpu *smpu, int instance_id, + int region_num); + +/** + * adi_smpu_disable_region - Disable an SMPU protection region + * @smpu: SMPU driver instance + * @instance_id: SMPU instance ID (0, 2-6, 9, 11-12) + * @region_num: Region number (0-7) + * + * Disables an active protection region, removing all access restrictions. + * + * Return: 0 on success, negative error code on failure + */ +int adi_smpu_disable_region(struct adi_smpu *smpu, int instance_id, + int region_num); + +/** + * adi_smpu_lock_config - Lock SMPU configuration + * @smpu: SMPU driver instance + * @instance_id: SMPU instance ID (0, 2-6, 9, 11-12) + * + * Locks the SMPU configuration, preventing further changes until system reset. + * This is a security feature to prevent malicious reconfiguration. + * + * WARNING: This is irreversible until system reset! + * + * Return: 0 on success, negative error code on failure + */ +int adi_smpu_lock_config(struct adi_smpu *smpu, int instance_id); + +/** + * adi_smpu_allocate_region - Allocate an unused SMPU region + * @smpu: SMPU driver instance + * @instance_id: SMPU instance ID (0, 2-6, 9, 11-12) + * + * Finds and allocates an unused region. The region is marked as in-use + * but not configured or enabled. Call adi_smpu_configure_region() and + * adi_smpu_enable_region() to activate. + * + * Return: Region number (0-7) on success, negative error code on failure + */ +int adi_smpu_allocate_region(struct adi_smpu *smpu, int instance_id); + +/** + * adi_smpu_free_region - Free an allocated SMPU region + * @smpu: SMPU driver instance + * @instance_id: SMPU instance ID (0, 2-6, 9, 11-12) + * @region_num: Region number (0-7) + * + * Frees a previously allocated region, making it available for reuse. + * The region will be disabled automatically. + */ +void adi_smpu_free_region(struct adi_smpu *smpu, int instance_id, + int region_num); + +#endif /* SOC_ADI_SMPU_H */ From 266481a0ecb3a1331f6caeb272a77900348497fb Mon Sep 17 00:00:00 2001 From: Ozan Durgut Date: Tue, 26 May 2026 01:01:59 +0200 Subject: [PATCH 2/5] dt-bindings: soc: adi: add SMPU binding Add a devicetree binding for the SC59x System Memory Protection Unit. Include examples for both a basic SMPU description and a setup with boot-time static regions. Signed-off-by: Ozan Durgut --- .../bindings/soc/adi/adi,sc59x-smpu.yaml | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/adi/adi,sc59x-smpu.yaml diff --git a/Documentation/devicetree/bindings/soc/adi/adi,sc59x-smpu.yaml b/Documentation/devicetree/bindings/soc/adi/adi,sc59x-smpu.yaml new file mode 100644 index 00000000000000..1b13898e60f13f --- /dev/null +++ b/Documentation/devicetree/bindings/soc/adi/adi,sc59x-smpu.yaml @@ -0,0 +1,250 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/soc/adi/adi,sc59x-smpu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices SC59x System Memory Protection Unit + +maintainers: + - Ozan Durgut + - Analog Devices + +description: | + The System Memory Protection Unit (SMPU) provides hardware-based memory + protection for ADSP-SC59x processors. Each SMPU instance can configure up + to 8 protection regions with transaction ID-based access control. + + There are 9 SMPU instances in SC59x processors protecting different memory + domains: + - SMPU0 (0x31007000): L2 SRAM Banks 0-3, Boot ROM0 + - SMPU2 (0x31083000): L2 SRAM Banks 4-7, Boot ROM1-2 + - SMPU3 (0x31084000): L2 SRAM Banks 0-7 (Instruction Port) + - SMPU4 (0x31085000): L2 DMA Port 0 + - SMPU5 (0x31086000): L2 DMA Port 1 + - SMPU6 (0x31087000): Reserved + - SMPU9 (0x310A0000): DDR3 Memory Controller + - SMPU11 (0x310A1000): SPI2/OSPI Flash + - SMPU12 (0x31012000): OTP Memory + + Each region can be configured with: + - Base address and size (power-of-2, 4KB to 4GB) + - Read/write protection independently + - Transaction ID filtering with dual match units and masks + - Inversion logic for blocking specific IDs + + Silicon Anomaly 20000003: Non-secure accesses to SMPU registers in range + 0x800-0xFFF cause erroneous bus errors. The driver handles this by avoiding + these registers unless the adi,secure-access property indicates secure mode. + +properties: + compatible: + const: adi,sc59x-smpu + + reg: + description: Register regions for each SMPU instance + minItems: 1 + maxItems: 9 + + reg-names: + description: Names for each SMPU instance (sparse numbering) + minItems: 1 + maxItems: 9 + items: + enum: + - smpu0 + - smpu2 + - smpu3 + - smpu4 + - smpu5 + - smpu6 + - smpu9 + - smpu11 + - smpu12 + + interrupts: + description: Protection violation interrupt (SEC ID 217) + maxItems: 1 + + adi,secure-access: + type: boolean + description: | + Driver has secure-mode access and can program registers at offset + 0x800-0xFFF (SMPU_SECURECTL and SMPU_SECURERCTL). + + If absent, driver avoids these registers to work around Anomaly 20000003 + (non-secure access causes bus errors). Basic protection still works via + registers at 0x00-0x7FF. + + static-regions: + type: object + description: | + Optional static protection regions to be configured at boot time. + Each child node defines a region with base address, size, permissions, + and allowed transaction IDs. These regions are automatically configured + and enabled during driver probe. + patternProperties: + "^region-[0-9]+$": + type: object + properties: + instance: + $ref: /schemas/types.yaml#/definitions/uint32 + description: SMPU instance ID (0, 2-6, 9, 11-12) + + memory-region: + $ref: /schemas/types.yaml#/definitions/phandle + description: | + Reference to reserved-memory node. Preferred method for defining + base and size. + + base: + $ref: /schemas/types.yaml#/definitions/uint64 + description: | + Physical base address (alternative to memory-region). Must be + aligned to region size. + + size: + $ref: /schemas/types.yaml#/definitions/uint64 + description: | + Region size (alternative to memory-region). Must be power of 2, + minimum 4KB, maximum 4GB. + + permissions: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Access permissions bitmap: + bit 0: Read enable + bit 1: Write enable + bit 2: Execute enable + Default is 0x3 (read/write). + + allowed-ids: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 2 + description: | + Transaction IDs allowed to access this region (up to 2). + 13-bit IDs identifying bus masters (CPU cores, DMA, etc). + + id-masks: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 2 + description: | + Masks for transaction ID matching (0x0000 = match all, + 0x1FFF = exact match). Default is 0x1FFF for exact matching. + + id-invert-a: + type: boolean + description: Invert ID match logic for first ID (block instead of allow) + + id-invert-b: + type: boolean + description: Invert ID match logic for second ID (block instead of allow) + + secure-only: + type: boolean + description: | + Allow only secure transactions. Blocks all non-secure accesses. + Only effective if adi,secure-access property is set. + Cannot be combined with non-secure-only. + + non-secure-only: + type: boolean + description: | + Allow only non-secure transactions. Blocks all secure accesses. + Useful for non-secure peripherals in TrustZone systems. + Cannot be combined with secure-only. + + required: + - instance + + oneOf: + - required: + - memory-region + - required: + - base + - size + +required: + - compatible + - reg + - reg-names + +additionalProperties: false + +examples: + - | + #include + #include + + /* Basic configuration without static regions */ + smpu: smpu@31007000 { + compatible = "adi,sc59x-smpu"; + reg = <0x31007000 0x1000>, /* SMPU0 */ + <0x31083000 0x1000>, /* SMPU2 */ + <0x31084000 0x1000>, /* SMPU3 */ + <0x31085000 0x1000>, /* SMPU4 */ + <0x31086000 0x1000>, /* SMPU5 */ + <0x31087000 0x1000>, /* SMPU6 */ + <0x310A0000 0x1000>, /* SMPU9 */ + <0x310A1000 0x1000>, /* SMPU11 */ + <0x31012000 0x1000>; /* SMPU12 */ + reg-names = "smpu0", "smpu2", "smpu3", "smpu4", "smpu5", + "smpu6", "smpu9", "smpu11", "smpu12"; + interrupts = ; + }; + + - | + #include + + /* Example with static protection regions */ + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + dsp_memory: dsp@20000000 { + reg = <0x20000000 0x100000>; /* 1MB for DSP */ + no-map; + }; + + sharc_memory: sharc@20100000 { + reg = <0x20100000 0x200000>; /* 2MB for SHARC+ */ + no-map; + }; + }; + + smpu: smpu@31007000 { + compatible = "adi,sc59x-smpu"; + reg = <0x31007000 0x1000>, /* SMPU0 */ + <0x310A0000 0x1000>; /* SMPU9 (DDR) */ + reg-names = "smpu0", "smpu9"; + interrupts = ; + + /* Static protection regions configured at boot */ + static-regions { + region-0 { + instance = <0>; /* SMPU0: L2 SRAM */ + memory-region = <&dsp_memory>; + permissions = <0x3>; /* Read/Write */ + allowed-ids = <0x100>; /* DSP core transaction ID */ + }; + + region-1 { + instance = <0>; /* SMPU0: L2 SRAM */ + memory-region = <&sharc_memory>; + permissions = <0x3>; /* Read/Write */ + allowed-ids = <0x200 0x201>; /* SHARC+ cores */ + }; + + region-2 { + instance = <9>; /* SMPU9: DDR protection */ + base = <0x80000000>; + size = <0x40000000>; /* 1GB DDR */ + permissions = <0x7>; /* Read/Write/Execute */ + allowed-ids = <0x100 0x200>; /* DSP and SHARC */ + id-masks = <0x1FFF 0x1FF0>; /* Exact and range match */ + }; + }; + }; From eae9bb49409a95f2feb8a9603a472638ac710e0b Mon Sep 17 00:00:00 2001 From: Ozan Durgut Date: Tue, 26 May 2026 01:05:13 +0200 Subject: [PATCH 3/5] arm64: dts: adi: sc59x: add SMPU node Describe the SC59x SMPU block in the common device tree for sc59x. Signed-off-by: Ozan Durgut --- arch/arm64/boot/dts/adi/sc59x-64.dtsi | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/arch/arm64/boot/dts/adi/sc59x-64.dtsi b/arch/arm64/boot/dts/adi/sc59x-64.dtsi index ef5aee84fc129a..2c823923e8e188 100644 --- a/arch/arm64/boot/dts/adi/sc59x-64.dtsi +++ b/arch/arm64/boot/dts/adi/sc59x-64.dtsi @@ -198,6 +198,11 @@ reg = <0x20005000 0x20000>; /*128KiB*/ no-map; }; + + smpu_test: smpu-test@9e000000 { + reg = <0x9e000000 0x100000>; + no-map; + }; }; scb { @@ -248,6 +253,35 @@ status = "okay"; }; + smpu: smpu@31007000 { + compatible = "adi,sc59x-smpu"; + reg = <0x31007000 0x1000>, /* SMPU0 - L2 SRAM Port 0 */ + <0x31083000 0x1000>, /* SMPU2 - L2 SRAM Port 1 */ + <0x31084000 0x1000>, /* SMPU3 - L2 SRAM Port 2 */ + <0x31085000 0x1000>, /* SMPU4 - L2 DMA Port 0 */ + <0x31086000 0x1000>, /* SMPU5 - L2 DMA Port 1 */ + /* <0x31087000 0x1000>, SMPU6 - Reserved */ + <0x310A0000 0x1000>, /* SMPU9 - DDR Memory Controller */ + <0x310A1000 0x1000>; /* SMPU11 - SPI2/OSPI Flash */ + /* <0x31012000 0x1000>; SMPU12 - OTP Memory */ + reg-names = "smpu0", "smpu2", "smpu3", "smpu4", "smpu5", + "smpu9", "smpu11"; + interrupts = ; + status = "okay"; + + static-regions { + /* Test region on SMPU0 */ + region-test-l2 { + instance = <0>; /* SMPU0: L2 SRAM Port 0 */ + base = <0x20040000>; /* L2 SRAM bank 1 */ + size = <0x40000>; /* 256KB */ + permissions = <0x1>; /* Read-only */ + allowed-ids = <0x0029 0>; /* ARM CPU */ + id-masks = <0x1FFF 0>; /* Exact match */ + }; + }; + }; + tru: tru@3108a000 { compatible = "adi,trigger-routing-unit"; reg = <0x3108a000 0x1000>; From ae91a1624d45dd299758eed982ff57a87b5d9149 Mon Sep 17 00:00:00 2001 From: Ozan Durgut Date: Tue, 26 May 2026 13:59:42 +0200 Subject: [PATCH 4/5] fixup! soc: adi: add SC59x SMPU driver Signed-off-by: Ozan Durgut --- drivers/soc/adi/mach-sc59x/smpu.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/drivers/soc/adi/mach-sc59x/smpu.c b/drivers/soc/adi/mach-sc59x/smpu.c index 57a8c203652c6a..b473ea3af66256 100644 --- a/drivers/soc/adi/mach-sc59x/smpu.c +++ b/drivers/soc/adi/mach-sc59x/smpu.c @@ -420,9 +420,9 @@ static void adi_smpu_reset_instance(struct adi_smpu_instance *inst) /* Clear any pending violations */ adi_smpu_writel(inst, ADI_SMPU_STAT_W1C_MASK, ADI_SMPU_REG_STAT); - /* Enable violation interrupts */ + /* Leave violation interrupts disabled until an IRQ is registered. */ val = adi_smpu_readl(inst, ADI_SMPU_REG_CTL); - val |= ADI_SMPU_CTL_PINTEN; + val &= ~ADI_SMPU_CTL_PINTEN; adi_smpu_writel(inst, val, ADI_SMPU_REG_CTL); spin_unlock_irqrestore(&inst->lock, flags); @@ -1210,14 +1210,16 @@ static int adi_smpu_probe(struct platform_device *pdev) } base = devm_ioremap(dev, res->start, resource_size(res)); - if (IS_ERR(base)) { + if (!base) { dev_err(dev, "Failed to map %s\n", name); - return PTR_ERR(base); + return -ENOMEM; } inst->base = base; inst->instance_id = instance_id; inst->name = devm_kasprintf(dev, GFP_KERNEL, "smpu%d", instance_id); + if (!inst->name) + return -ENOMEM; inst->smpu = smpu; inst->region_bitmap = 0; inst->viol_head = 0; @@ -1266,7 +1268,10 @@ static int adi_smpu_probe(struct platform_device *pdev) } /* Request IRQ for violations (shared across all instances) */ - irq = platform_get_irq(pdev, 0); + irq = platform_get_irq_optional(pdev, 0); + if (irq < 0 && irq != -ENXIO) + return irq; + if (irq > 0) { ret = devm_request_irq(dev, irq, adi_smpu_irq_handler, IRQF_SHARED, dev_name(dev), smpu); @@ -1274,6 +1279,15 @@ static int adi_smpu_probe(struct platform_device *pdev) dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); return ret; } + + for (i = 0; i < smpu->num_instances; i++) { + struct adi_smpu_instance *inst = &smpu->instances[i]; + u32 val = adi_smpu_readl(inst, ADI_SMPU_REG_CTL); + + val |= ADI_SMPU_CTL_PINTEN; + adi_smpu_writel(inst, val, ADI_SMPU_REG_CTL); + } + dev_info(dev, "Registered IRQ %d for violations\n", irq); } else { dev_warn(dev, "No IRQ specified, violations will not be reported\n"); From d4a85535ff8ac5d53207d5047cc3d6d23af151d3 Mon Sep 17 00:00:00 2001 From: Ozan Durgut Date: Tue, 26 May 2026 15:37:13 +0200 Subject: [PATCH 5/5] fixup! arm64: dts: adi: sc59x: add SMPU node Signed-off-by: Ozan Durgut --- arch/arm64/boot/dts/adi/sc59x-64.dtsi | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/arch/arm64/boot/dts/adi/sc59x-64.dtsi b/arch/arm64/boot/dts/adi/sc59x-64.dtsi index 2c823923e8e188..8cb77bc73176eb 100644 --- a/arch/arm64/boot/dts/adi/sc59x-64.dtsi +++ b/arch/arm64/boot/dts/adi/sc59x-64.dtsi @@ -199,10 +199,6 @@ no-map; }; - smpu_test: smpu-test@9e000000 { - reg = <0x9e000000 0x100000>; - no-map; - }; }; scb { @@ -253,30 +249,29 @@ status = "okay"; }; - smpu: smpu@31007000 { + smpu: smpu@31083000 { compatible = "adi,sc59x-smpu"; - reg = <0x31007000 0x1000>, /* SMPU0 - L2 SRAM Port 0 */ - <0x31083000 0x1000>, /* SMPU2 - L2 SRAM Port 1 */ + reg = <0x31083000 0x1000>, /* SMPU2 - L2 SRAM Port 1 */ <0x31084000 0x1000>, /* SMPU3 - L2 SRAM Port 2 */ <0x31085000 0x1000>, /* SMPU4 - L2 DMA Port 0 */ <0x31086000 0x1000>, /* SMPU5 - L2 DMA Port 1 */ /* <0x31087000 0x1000>, SMPU6 - Reserved */ <0x310A0000 0x1000>, /* SMPU9 - DDR Memory Controller */ - <0x310A1000 0x1000>; /* SMPU11 - SPI2/OSPI Flash */ + <0x310A1000 0x1000>, /* SMPU11 - SPI2/OSPI Flash */ /* <0x31012000 0x1000>; SMPU12 - OTP Memory */ - reg-names = "smpu0", "smpu2", "smpu3", "smpu4", "smpu5", + reg-names = "smpu2", "smpu3", "smpu4", "smpu5", "smpu9", "smpu11"; interrupts = ; status = "okay"; static-regions { - /* Test region on SMPU0 */ + /* Test region on SMPU2 */ region-test-l2 { - instance = <0>; /* SMPU0: L2 SRAM Port 0 */ + instance = <2>; /* SMPU2: L2 SRAM Port 1 */ base = <0x20040000>; /* L2 SRAM bank 1 */ size = <0x40000>; /* 256KB */ permissions = <0x1>; /* Read-only */ - allowed-ids = <0x0029 0>; /* ARM CPU */ + allowed-ids = <0x0289 0>; /* ARM A55 TID */ id-masks = <0x1FFF 0>; /* Exact match */ }; };