[PATCH 4/4] mmc: sdhci-brcmstb: rpmb sharing by claiming host for TZOS
Kamal Dasu
kamal.dasu at broadcom.com
Fri Jul 11 08:42:21 PDT 2025
From: Kamal Dasu <kdasu at broadcom.com>
Adding sdio rpmb partition sharing support in brcmstb host driver.
The sdhci-brcmstb controller driver uses SDIO_SHARE work registers
along with use of brcmstb hwspinlock framework to synchronize access
between linux and trusted zone firmware. The sdhci-brcmstb driver
claims and releases host for TZOS only when it is requested to do
via ipi0 interrupt.
Signed-off-by: Kamal Dasu <kamal.dasu at broadcom.com>
---
drivers/mmc/host/sdhci-brcmstb.c | 243 ++++++++++++++++++++++++++++++-
1 file changed, 242 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/sdhci-brcmstb.c b/drivers/mmc/host/sdhci-brcmstb.c
index 48cdcba0f39c..dd68fd512459 100644
--- a/drivers/mmc/host/sdhci-brcmstb.c
+++ b/drivers/mmc/host/sdhci-brcmstb.c
@@ -12,6 +12,10 @@
#include <linux/of.h>
#include <linux/bitops.h>
#include <linux/delay.h>
+#include <linux/hwspinlock.h>
+#include <linux/workqueue.h>
+#include <linux/kthread.h>
+#include "../core/core.h"
#include "sdhci-cqhci.h"
#include "sdhci-pltfm.h"
@@ -34,6 +38,11 @@
#define SDIO_CFG_CQ_CAPABILITY 0x4c
#define SDIO_CFG_CQ_CAPABILITY_FMUL GENMASK(13, 12)
+#define SDHCI_BRCMSTB_AGENT_LINUX 'L'
+#define SDHCI_BRCMSTB_AGENT_TZOS 'A'
+#define FLSHARE_IPIS0_INT_SEND_MASK BIT(17)
+#define HWSPINLOCK_TIMEOUT_MS 100
+
#define SDIO_CFG_CTRL 0x0
#define SDIO_CFG_CTRL_SDCD_N_TEST_EN BIT(31)
#define SDIO_CFG_CTRL_SDCD_N_TEST_LEV BIT(30)
@@ -46,9 +55,26 @@
/* Select all SD UHS type I SDR speed above 50MB/s */
#define MMC_CAP_UHS_I_SDR_MASK (MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104)
+#define BRCMSTB_SD_SHARE_REG_NEXT 0x0 /* Next Agent Register */
+#define BRCMSTB_SD_SHARE_REG_PMC 0x4 /* Work Agent1 Register */
+#define BRCMSTB_SD_SHARE_REG_TZOS 0x8 /* Work Agent2 Register */
+#define BRCMSTB_SD_SHARE_REG_LINUX 0xc /* Work Agent3 Register */
+
+struct brcmstb_sdio_share_info {
+ void __iomem *share_reg;
+ void __iomem *ipis0_reg;
+ struct hwspinlock *hwlock;
+ struct sdhci_host *host;
+ int irq_recv;
+ int host_claimed;
+ wait_queue_head_t wq;
+ struct task_struct *claim_thread;
+};
+
struct sdhci_brcmstb_priv {
void __iomem *cfg_regs;
unsigned int flags;
+ struct brcmstb_sdio_share_info *si;
struct clk *base_clk;
u32 base_freq_hz;
};
@@ -303,6 +329,218 @@ static const struct of_device_id __maybe_unused sdhci_brcm_of_match[] = {
{},
};
+static void sdhci_brcmstb_dump_shr_regs(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct brcmstb_sdio_share_info *si = priv->si;
+
+ dev_dbg(mmc_dev(host->mmc), "wn:0x%x wa:0x%x wl:0x%x\n",
+ readl(si->share_reg + BRCMSTB_SD_SHARE_REG_NEXT),
+ readl(si->share_reg + BRCMSTB_SD_SHARE_REG_TZOS),
+ readl(si->share_reg + BRCMSTB_SD_SHARE_REG_LINUX));
+}
+
+static bool sdhci_brcmstb_linux_host_is_next(struct brcmstb_sdio_share_info *si)
+{
+ u32 wn;
+
+ /* check if linux is next */
+ wn = readl(si->share_reg + BRCMSTB_SD_SHARE_REG_NEXT);
+ return ((wn == SDHCI_BRCMSTB_AGENT_LINUX) ? true : false);
+}
+
+static void sdhci_brcmstb_wait_for_linux_host(struct brcmstb_sdio_share_info *si)
+{
+ int ret;
+
+ while (1) {
+ /* put self in wait queue when host not available */
+ ret = wait_event_interruptible(si->wq,
+ sdhci_brcmstb_linux_host_is_next(si));
+ if (ret != -ERESTARTSYS)
+ break;
+ };
+}
+
+static bool sdhci_brcmstb_tzos_is_waiting(struct brcmstb_sdio_share_info *si)
+{
+ u32 wt;
+
+ /* check if TZOS has put itself in the work queue */
+ wt = readl(si->share_reg + BRCMSTB_SD_SHARE_REG_TZOS);
+ return ((wt == SDHCI_BRCMSTB_AGENT_TZOS) ? true : false);
+}
+
+static void sdhci_brcmstb_wait_for_tzos(struct brcmstb_sdio_share_info *si)
+{
+ int ret;
+
+ while (1) {
+ /* wait in queue when tzos cannot use controller */
+ ret = wait_event_interruptible(si->wq,
+ sdhci_brcmstb_tzos_is_waiting(si));
+ if (ret != -ERESTARTSYS)
+ break;
+ }
+}
+
+static void sdhci_brcmstb_aquire_hwsem(struct brcmstb_sdio_share_info *si)
+{
+ u32 wl = SDHCI_BRCMSTB_AGENT_LINUX;
+ struct mmc_host *mmc = si->host->mmc;
+ int ret;
+
+ /*
+ * aquire hw sem :
+ * 1. write linux agent id to work register WL
+ * 2. Aquire hw semaphore
+ * 2. clear next work register WN
+ */
+ writel(wl, si->share_reg + BRCMSTB_SD_SHARE_REG_LINUX);
+ /* try hw semaphore lock, we should never have to wait here */
+ ret = hwspin_lock_timeout(si->hwlock, HWSPINLOCK_TIMEOUT_MS);
+ WARN_ON(ret != 0);
+ /* clear next register when holding the semaphore */
+ writel(0, si->share_reg + BRCMSTB_SD_SHARE_REG_NEXT);
+ dev_dbg(mmc_dev(mmc), "hwsem aquire\n");
+ sdhci_brcmstb_dump_shr_regs(si->host);
+}
+
+static void sdhci_brcmstb_release_hwsem(struct brcmstb_sdio_share_info *si)
+{
+ u32 wt;
+ struct mmc_host *mmc = si->host->mmc;
+ int ret;
+
+ /*
+ * release hw semphore
+ * 1. set the next work agent register WN before releasing hw sem
+ * 2. Release hw semaphore
+ * 3. send ipi to TZOS
+ */
+ wt = readl(si->share_reg + BRCMSTB_SD_SHARE_REG_TZOS);
+ writel(wt, si->share_reg + BRCMSTB_SD_SHARE_REG_NEXT);
+
+ /* release hw semaphore if we hold it and send IPI */
+ ret = hwspin_trylock_raw(si->hwlock);
+ WARN_ON(ret != 0);
+ hwspin_unlock(si->hwlock);
+
+ if (wt == SDHCI_BRCMSTB_AGENT_TZOS)
+ writel(FLSHARE_IPIS0_INT_SEND_MASK, si->ipis0_reg);
+
+ dev_dbg(mmc_dev(mmc), "hwsem release\n");
+ sdhci_brcmstb_dump_shr_regs(si->host);
+}
+
+static irqreturn_t sdhci_brcmstb_recv_ipi0_irq(int irq, void *dev_id)
+{
+ struct sdhci_host *host = dev_id;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct brcmstb_sdio_share_info *si = priv->si;
+
+ dev_dbg(mmc_dev(host->mmc), "ipi irq %d next L:%d T:%d ch:%d\n",
+ irq, sdhci_brcmstb_linux_host_is_next(si),
+ sdhci_brcmstb_tzos_is_waiting(si),
+ si->host_claimed);
+ wake_up_interruptible(&si->wq);
+ return IRQ_HANDLED;
+}
+
+static int sdhci_brcmstb_host_claim_thread(void *data)
+{
+ struct brcmstb_sdio_share_info *si = data;
+ struct mmc_host *mmc = si->host->mmc;
+
+ do {
+ sdhci_brcmstb_wait_for_tzos(si);
+ /* claim host for TZOS */
+ mmc_claim_host(mmc);
+ si->host_claimed += 1;
+ sdhci_brcmstb_release_hwsem(si);
+ dev_dbg(mmc_dev(mmc), "host claimed %d\n", si->host_claimed);
+ sdhci_brcmstb_wait_for_linux_host(si);
+ sdhci_brcmstb_aquire_hwsem(si);
+ /* release host */
+ mmc_release_host(mmc);
+ si->host_claimed -= 1;
+ dev_dbg(mmc_dev(mmc), "host released %d\n", si->host_claimed);
+ } while (!kthread_should_stop());
+
+ return 0;
+}
+
+static int sdhci_brcmstb_sdio_share_init(struct platform_device *pdev)
+{
+ struct sdhci_host *host = dev_get_drvdata(&pdev->dev);
+ struct device_node *np = pdev->dev.of_node;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host);
+ struct brcmstb_sdio_share_info *si;
+ void __iomem *sdio_sh_regs;
+ int ret;
+
+ /* sdio_share block */
+ sdio_sh_regs = devm_platform_ioremap_resource_byname(pdev, "share");
+ if (IS_ERR(sdio_sh_regs))
+ return 0;
+
+ si = devm_kcalloc(&pdev->dev, 1, sizeof(struct brcmstb_sdio_share_info),
+ GFP_KERNEL);
+ if (!si)
+ return -ENOMEM;
+
+ si->share_reg = sdio_sh_regs;
+ ret = of_hwspin_lock_get_id(np, 0);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to get hwspinlock id %d\n", ret);
+ return ret;
+ }
+
+ si->hwlock = devm_hwspin_lock_request_specific(&pdev->dev, ret);
+ if (!si->hwlock) {
+ dev_err(&pdev->dev, "failed to request hwspinlock\n");
+ return -ENXIO;
+ }
+
+ si->irq_recv = platform_get_irq_byname_optional(pdev, "recv_ipi0");
+ if (si->irq_recv < 0) {
+ ret = si->irq_recv;
+ dev_err(&pdev->dev, "recv_ipi0 IRQ not found\n");
+ return ret;
+ }
+
+ ret = devm_request_irq(&pdev->dev, si->irq_recv,
+ sdhci_brcmstb_recv_ipi0_irq,
+ 0, "mmc_recv_ipi0", host);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "mmc_recv_ipi0 IRQ request_irq failed\n");
+ return ret;
+ }
+
+ si->ipis0_reg = devm_platform_ioremap_resource_byname(pdev, "flshr_ipis0");
+ if (IS_ERR(si->ipis0_reg))
+ return -ENXIO;
+
+ priv->si = si;
+ si->host = host;
+ init_waitqueue_head(&si->wq);
+ /* acquire hwsem */
+ sdhci_brcmstb_aquire_hwsem(si);
+ si->claim_thread =
+ kthread_run(sdhci_brcmstb_host_claim_thread, si,
+ "ksdshrthread/%s", mmc_hostname(host->mmc));
+ if (IS_ERR(si->claim_thread)) {
+ ret = PTR_ERR(si->claim_thread);
+ dev_err(&pdev->dev, "failed to run claim thread\n");
+ return -ENOEXEC;
+ }
+
+ return 0;
+}
+
static u32 sdhci_brcmstb_cqhci_irq(struct sdhci_host *host, u32 intmask)
{
int cmd_error = 0;
@@ -482,8 +720,11 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev)
goto err;
pltfm_host->clk = clk;
- return res;
+ res = sdhci_brcmstb_sdio_share_init(pdev);
+ if (res)
+ dev_warn(&pdev->dev, "sdio share unavailable\n");
+ return 0;
err:
sdhci_pltfm_free(pdev);
clk_disable_unprepare(base_clk);
--
2.34.1
More information about the linux-arm-kernel
mailing list