Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/iommu/arm/arm-smmu-v3/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 135 kB image not shown  

Quelle  arm-smmu-v3.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * IOMMU API for ARM architected SMMUv3 implementations.
 *
 * Copyright (C) 2015 ARM Limited
 *
 * Author: Will Deacon <will.deacon@arm.com>
 *
 * This driver is powered by bad coffee and bombay mix.
 */


#include <linux/acpi.h>
#include <linux/acpi_iort.h>
#include <linux/bitops.h>
#include <linux/crash_dump.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io-pgtable.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/msi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/pci.h>
#include <linux/pci-ats.h>
#include <linux/platform_device.h>
#include <linux/string_choices.h>
#include <kunit/visibility.h>
#include <uapi/linux/iommufd.h>

#include "arm-smmu-v3.h"
#include "../../dma-iommu.h"

static bool disable_msipolling;
module_param(disable_msipolling, bool, 0444);
MODULE_PARM_DESC(disable_msipolling,
 "Disable MSI-based polling for CMD_SYNC completion.");

static const struct iommu_ops arm_smmu_ops;
static struct iommu_dirty_ops arm_smmu_dirty_ops;

enum arm_smmu_msi_index {
 EVTQ_MSI_INDEX,
 GERROR_MSI_INDEX,
 PRIQ_MSI_INDEX,
 ARM_SMMU_MAX_MSIS,
};

#define NUM_ENTRY_QWORDS 8
static_assert(sizeof(struct arm_smmu_ste) == NUM_ENTRY_QWORDS * sizeof(u64));
static_assert(sizeof(struct arm_smmu_cd) == NUM_ENTRY_QWORDS * sizeof(u64));

static phys_addr_t arm_smmu_msi_cfg[ARM_SMMU_MAX_MSIS][3] = {
 [EVTQ_MSI_INDEX] = {
  ARM_SMMU_EVTQ_IRQ_CFG0,
  ARM_SMMU_EVTQ_IRQ_CFG1,
  ARM_SMMU_EVTQ_IRQ_CFG2,
 },
 [GERROR_MSI_INDEX] = {
  ARM_SMMU_GERROR_IRQ_CFG0,
  ARM_SMMU_GERROR_IRQ_CFG1,
  ARM_SMMU_GERROR_IRQ_CFG2,
 },
 [PRIQ_MSI_INDEX] = {
  ARM_SMMU_PRIQ_IRQ_CFG0,
  ARM_SMMU_PRIQ_IRQ_CFG1,
  ARM_SMMU_PRIQ_IRQ_CFG2,
 },
};

struct arm_smmu_option_prop {
 u32 opt;
 const char *prop;
};

DEFINE_XARRAY_ALLOC1(arm_smmu_asid_xa);
DEFINE_MUTEX(arm_smmu_asid_lock);

static struct arm_smmu_option_prop arm_smmu_options[] = {
 { ARM_SMMU_OPT_SKIP_PREFETCH, "hisilicon,broken-prefetch-cmd" },
 { ARM_SMMU_OPT_PAGE0_REGS_ONLY, "cavium,cn9900-broken-page1-regspace"},
 { 0, NULL},
};

static const char * const event_str[] = {
 [EVT_ID_BAD_STREAMID_CONFIG] = "C_BAD_STREAMID",
 [EVT_ID_STE_FETCH_FAULT] = "F_STE_FETCH",
 [EVT_ID_BAD_STE_CONFIG] = "C_BAD_STE",
 [EVT_ID_STREAM_DISABLED_FAULT] = "F_STREAM_DISABLED",
 [EVT_ID_BAD_SUBSTREAMID_CONFIG] = "C_BAD_SUBSTREAMID",
 [EVT_ID_CD_FETCH_FAULT] = "F_CD_FETCH",
 [EVT_ID_BAD_CD_CONFIG] = "C_BAD_CD",
 [EVT_ID_TRANSLATION_FAULT] = "F_TRANSLATION",
 [EVT_ID_ADDR_SIZE_FAULT] = "F_ADDR_SIZE",
 [EVT_ID_ACCESS_FAULT] = "F_ACCESS",
 [EVT_ID_PERMISSION_FAULT] = "F_PERMISSION",
 [EVT_ID_VMS_FETCH_FAULT] = "F_VMS_FETCH",
};

static const char * const event_class_str[] = {
 [0] = "CD fetch",
 [1] = "Stage 1 translation table fetch",
 [2] = "Input address caused fault",
 [3] = "Reserved",
};

static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master);

static void parse_driver_options(struct arm_smmu_device *smmu)
{
 int i = 0;

 do {
  if (of_property_read_bool(smmu->dev->of_node,
      arm_smmu_options[i].prop)) {
   smmu->options |= arm_smmu_options[i].opt;
   dev_notice(smmu->dev, "option %s\n",
    arm_smmu_options[i].prop);
  }
 } while (arm_smmu_options[++i].opt);
}

/* Low-level queue manipulation functions */
static bool queue_has_space(struct arm_smmu_ll_queue *q, u32 n)
{
 u32 space, prod, cons;

 prod = Q_IDX(q, q->prod);
 cons = Q_IDX(q, q->cons);

 if (Q_WRP(q, q->prod) == Q_WRP(q, q->cons))
  space = (1 << q->max_n_shift) - (prod - cons);
 else
  space = cons - prod;

 return space >= n;
}

static bool queue_full(struct arm_smmu_ll_queue *q)
{
 return Q_IDX(q, q->prod) == Q_IDX(q, q->cons) &&
        Q_WRP(q, q->prod) != Q_WRP(q, q->cons);
}

static bool queue_empty(struct arm_smmu_ll_queue *q)
{
 return Q_IDX(q, q->prod) == Q_IDX(q, q->cons) &&
        Q_WRP(q, q->prod) == Q_WRP(q, q->cons);
}

static bool queue_consumed(struct arm_smmu_ll_queue *q, u32 prod)
{
 return ((Q_WRP(q, q->cons) == Q_WRP(q, prod)) &&
  (Q_IDX(q, q->cons) > Q_IDX(q, prod))) ||
        ((Q_WRP(q, q->cons) != Q_WRP(q, prod)) &&
  (Q_IDX(q, q->cons) <= Q_IDX(q, prod)));
}

static void queue_sync_cons_out(struct arm_smmu_queue *q)
{
 /*
 * Ensure that all CPU accesses (reads and writes) to the queue
 * are complete before we update the cons pointer.
 */

 __iomb();
 writel_relaxed(q->llq.cons, q->cons_reg);
}

static void queue_inc_cons(struct arm_smmu_ll_queue *q)
{
 u32 cons = (Q_WRP(q, q->cons) | Q_IDX(q, q->cons)) + 1;
 q->cons = Q_OVF(q->cons) | Q_WRP(q, cons) | Q_IDX(q, cons);
}

static void queue_sync_cons_ovf(struct arm_smmu_queue *q)
{
 struct arm_smmu_ll_queue *llq = &q->llq;

 if (likely(Q_OVF(llq->prod) == Q_OVF(llq->cons)))
  return;

 llq->cons = Q_OVF(llq->prod) | Q_WRP(llq, llq->cons) |
        Q_IDX(llq, llq->cons);
 queue_sync_cons_out(q);
}

static int queue_sync_prod_in(struct arm_smmu_queue *q)
{
 u32 prod;
 int ret = 0;

 /*
 * We can't use the _relaxed() variant here, as we must prevent
 * speculative reads of the queue before we have determined that
 * prod has indeed moved.
 */

 prod = readl(q->prod_reg);

 if (Q_OVF(prod) != Q_OVF(q->llq.prod))
  ret = -EOVERFLOW;

 q->llq.prod = prod;
 return ret;
}

static u32 queue_inc_prod_n(struct arm_smmu_ll_queue *q, int n)
{
 u32 prod = (Q_WRP(q, q->prod) | Q_IDX(q, q->prod)) + n;
 return Q_OVF(q->prod) | Q_WRP(q, prod) | Q_IDX(q, prod);
}

static void queue_poll_init(struct arm_smmu_device *smmu,
       struct arm_smmu_queue_poll *qp)
{
 qp->delay = 1;
 qp->spin_cnt = 0;
 qp->wfe = !!(smmu->features & ARM_SMMU_FEAT_SEV);
 qp->timeout = ktime_add_us(ktime_get(), ARM_SMMU_POLL_TIMEOUT_US);
}

static int queue_poll(struct arm_smmu_queue_poll *qp)
{
 if (ktime_compare(ktime_get(), qp->timeout) > 0)
  return -ETIMEDOUT;

 if (qp->wfe) {
  wfe();
 } else if (++qp->spin_cnt < ARM_SMMU_POLL_SPIN_COUNT) {
  cpu_relax();
 } else {
  udelay(qp->delay);
  qp->delay *= 2;
  qp->spin_cnt = 0;
 }

 return 0;
}

static void queue_write(__le64 *dst, u64 *src, size_t n_dwords)
{
 int i;

 for (i = 0; i < n_dwords; ++i)
  *dst++ = cpu_to_le64(*src++);
}

static void queue_read(u64 *dst, __le64 *src, size_t n_dwords)
{
 int i;

 for (i = 0; i < n_dwords; ++i)
  *dst++ = le64_to_cpu(*src++);
}

static int queue_remove_raw(struct arm_smmu_queue *q, u64 *ent)
{
 if (queue_empty(&q->llq))
  return -EAGAIN;

 queue_read(ent, Q_ENT(q, q->llq.cons), q->ent_dwords);
 queue_inc_cons(&q->llq);
 queue_sync_cons_out(q);
 return 0;
}

/* High-level queue accessors */
static int arm_smmu_cmdq_build_cmd(u64 *cmd, struct arm_smmu_cmdq_ent *ent)
{
 memset(cmd, 0, 1 << CMDQ_ENT_SZ_SHIFT);
 cmd[0] |= FIELD_PREP(CMDQ_0_OP, ent->opcode);

 switch (ent->opcode) {
 case CMDQ_OP_TLBI_EL2_ALL:
 case CMDQ_OP_TLBI_NSNH_ALL:
  break;
 case CMDQ_OP_PREFETCH_CFG:
  cmd[0] |= FIELD_PREP(CMDQ_PREFETCH_0_SID, ent->prefetch.sid);
  break;
 case CMDQ_OP_CFGI_CD:
  cmd[0] |= FIELD_PREP(CMDQ_CFGI_0_SSID, ent->cfgi.ssid);
  fallthrough;
 case CMDQ_OP_CFGI_STE:
  cmd[0] |= FIELD_PREP(CMDQ_CFGI_0_SID, ent->cfgi.sid);
  cmd[1] |= FIELD_PREP(CMDQ_CFGI_1_LEAF, ent->cfgi.leaf);
  break;
 case CMDQ_OP_CFGI_CD_ALL:
  cmd[0] |= FIELD_PREP(CMDQ_CFGI_0_SID, ent->cfgi.sid);
  break;
 case CMDQ_OP_CFGI_ALL:
  /* Cover the entire SID range */
  cmd[1] |= FIELD_PREP(CMDQ_CFGI_1_RANGE, 31);
  break;
 case CMDQ_OP_TLBI_NH_VA:
  cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_VMID, ent->tlbi.vmid);
  fallthrough;
 case CMDQ_OP_TLBI_EL2_VA:
  cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_NUM, ent->tlbi.num);
  cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_SCALE, ent->tlbi.scale);
  cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_ASID, ent->tlbi.asid);
  cmd[1] |= FIELD_PREP(CMDQ_TLBI_1_LEAF, ent->tlbi.leaf);
  cmd[1] |= FIELD_PREP(CMDQ_TLBI_1_TTL, ent->tlbi.ttl);
  cmd[1] |= FIELD_PREP(CMDQ_TLBI_1_TG, ent->tlbi.tg);
  cmd[1] |= ent->tlbi.addr & CMDQ_TLBI_1_VA_MASK;
  break;
 case CMDQ_OP_TLBI_S2_IPA:
  cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_NUM, ent->tlbi.num);
  cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_SCALE, ent->tlbi.scale);
  cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_VMID, ent->tlbi.vmid);
  cmd[1] |= FIELD_PREP(CMDQ_TLBI_1_LEAF, ent->tlbi.leaf);
  cmd[1] |= FIELD_PREP(CMDQ_TLBI_1_TTL, ent->tlbi.ttl);
  cmd[1] |= FIELD_PREP(CMDQ_TLBI_1_TG, ent->tlbi.tg);
  cmd[1] |= ent->tlbi.addr & CMDQ_TLBI_1_IPA_MASK;
  break;
 case CMDQ_OP_TLBI_NH_ASID:
  cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_ASID, ent->tlbi.asid);
  fallthrough;
 case CMDQ_OP_TLBI_NH_ALL:
 case CMDQ_OP_TLBI_S12_VMALL:
  cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_VMID, ent->tlbi.vmid);
  break;
 case CMDQ_OP_TLBI_EL2_ASID:
  cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_ASID, ent->tlbi.asid);
  break;
 case CMDQ_OP_ATC_INV:
  cmd[0] |= FIELD_PREP(CMDQ_0_SSV, ent->substream_valid);
  cmd[0] |= FIELD_PREP(CMDQ_ATC_0_GLOBAL, ent->atc.global);
  cmd[0] |= FIELD_PREP(CMDQ_ATC_0_SSID, ent->atc.ssid);
  cmd[0] |= FIELD_PREP(CMDQ_ATC_0_SID, ent->atc.sid);
  cmd[1] |= FIELD_PREP(CMDQ_ATC_1_SIZE, ent->atc.size);
  cmd[1] |= ent->atc.addr & CMDQ_ATC_1_ADDR_MASK;
  break;
 case CMDQ_OP_PRI_RESP:
  cmd[0] |= FIELD_PREP(CMDQ_0_SSV, ent->substream_valid);
  cmd[0] |= FIELD_PREP(CMDQ_PRI_0_SSID, ent->pri.ssid);
  cmd[0] |= FIELD_PREP(CMDQ_PRI_0_SID, ent->pri.sid);
  cmd[1] |= FIELD_PREP(CMDQ_PRI_1_GRPID, ent->pri.grpid);
  switch (ent->pri.resp) {
  case PRI_RESP_DENY:
  case PRI_RESP_FAIL:
  case PRI_RESP_SUCC:
   break;
  default:
   return -EINVAL;
  }
  cmd[1] |= FIELD_PREP(CMDQ_PRI_1_RESP, ent->pri.resp);
  break;
 case CMDQ_OP_RESUME:
  cmd[0] |= FIELD_PREP(CMDQ_RESUME_0_SID, ent->resume.sid);
  cmd[0] |= FIELD_PREP(CMDQ_RESUME_0_RESP, ent->resume.resp);
  cmd[1] |= FIELD_PREP(CMDQ_RESUME_1_STAG, ent->resume.stag);
  break;
 case CMDQ_OP_CMD_SYNC:
  if (ent->sync.msiaddr) {
   cmd[0] |= FIELD_PREP(CMDQ_SYNC_0_CS, CMDQ_SYNC_0_CS_IRQ);
   cmd[1] |= ent->sync.msiaddr & CMDQ_SYNC_1_MSIADDR_MASK;
  } else {
   cmd[0] |= FIELD_PREP(CMDQ_SYNC_0_CS, CMDQ_SYNC_0_CS_SEV);
  }
  cmd[0] |= FIELD_PREP(CMDQ_SYNC_0_MSH, ARM_SMMU_SH_ISH);
  cmd[0] |= FIELD_PREP(CMDQ_SYNC_0_MSIATTR, ARM_SMMU_MEMATTR_OIWB);
  break;
 default:
  return -ENOENT;
 }

 return 0;
}

static struct arm_smmu_cmdq *arm_smmu_get_cmdq(struct arm_smmu_device *smmu,
            struct arm_smmu_cmdq_ent *ent)
{
 struct arm_smmu_cmdq *cmdq = NULL;

 if (smmu->impl_ops && smmu->impl_ops->get_secondary_cmdq)
  cmdq = smmu->impl_ops->get_secondary_cmdq(smmu, ent);

 return cmdq ?: &smmu->cmdq;
}

static bool arm_smmu_cmdq_needs_busy_polling(struct arm_smmu_device *smmu,
          struct arm_smmu_cmdq *cmdq)
{
 if (cmdq == &smmu->cmdq)
  return false;

 return smmu->options & ARM_SMMU_OPT_TEGRA241_CMDQV;
}

static void arm_smmu_cmdq_build_sync_cmd(u64 *cmd, struct arm_smmu_device *smmu,
      struct arm_smmu_cmdq *cmdq, u32 prod)
{
 struct arm_smmu_queue *q = &cmdq->q;
 struct arm_smmu_cmdq_ent ent = {
  .opcode = CMDQ_OP_CMD_SYNC,
 };

 /*
 * Beware that Hi16xx adds an extra 32 bits of goodness to its MSI
 * payload, so the write will zero the entire command on that platform.
 */

 if (smmu->options & ARM_SMMU_OPT_MSIPOLL) {
  ent.sync.msiaddr = q->base_dma + Q_IDX(&q->llq, prod) *
       q->ent_dwords * 8;
 }

 arm_smmu_cmdq_build_cmd(cmd, &ent);
 if (arm_smmu_cmdq_needs_busy_polling(smmu, cmdq))
  u64p_replace_bits(cmd, CMDQ_SYNC_0_CS_NONE, CMDQ_SYNC_0_CS);
}

void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu,
         struct arm_smmu_cmdq *cmdq)
{
 static const char * const cerror_str[] = {
  [CMDQ_ERR_CERROR_NONE_IDX] = "No error",
  [CMDQ_ERR_CERROR_ILL_IDX] = "Illegal command",
  [CMDQ_ERR_CERROR_ABT_IDX] = "Abort on command fetch",
  [CMDQ_ERR_CERROR_ATC_INV_IDX] = "ATC invalidate timeout",
 };
 struct arm_smmu_queue *q = &cmdq->q;

 int i;
 u64 cmd[CMDQ_ENT_DWORDS];
 u32 cons = readl_relaxed(q->cons_reg);
 u32 idx = FIELD_GET(CMDQ_CONS_ERR, cons);
 struct arm_smmu_cmdq_ent cmd_sync = {
  .opcode = CMDQ_OP_CMD_SYNC,
 };

 dev_err(smmu->dev, "CMDQ error (cons 0x%08x): %s\n", cons,
  idx < ARRAY_SIZE(cerror_str) ?  cerror_str[idx] : "Unknown");

 switch (idx) {
 case CMDQ_ERR_CERROR_ABT_IDX:
  dev_err(smmu->dev, "retrying command fetch\n");
  return;
 case CMDQ_ERR_CERROR_NONE_IDX:
  return;
 case CMDQ_ERR_CERROR_ATC_INV_IDX:
  /*
 * ATC Invalidation Completion timeout. CONS is still pointing
 * at the CMD_SYNC. Attempt to complete other pending commands
 * by repeating the CMD_SYNC, though we might well end up back
 * here since the ATC invalidation may still be pending.
 */

  return;
 case CMDQ_ERR_CERROR_ILL_IDX:
 default:
  break;
 }

 /*
 * We may have concurrent producers, so we need to be careful
 * not to touch any of the shadow cmdq state.
 */

 queue_read(cmd, Q_ENT(q, cons), q->ent_dwords);
 dev_err(smmu->dev, "skipping command in error state:\n");
 for (i = 0; i < ARRAY_SIZE(cmd); ++i)
  dev_err(smmu->dev, "\t0x%016llx\n", (unsigned long long)cmd[i]);

 /* Convert the erroneous command into a CMD_SYNC */
 arm_smmu_cmdq_build_cmd(cmd, &cmd_sync);
 if (arm_smmu_cmdq_needs_busy_polling(smmu, cmdq))
  u64p_replace_bits(cmd, CMDQ_SYNC_0_CS_NONE, CMDQ_SYNC_0_CS);

 queue_write(Q_ENT(q, cons), cmd, q->ent_dwords);
}

static void arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu)
{
 __arm_smmu_cmdq_skip_err(smmu, &smmu->cmdq);
}

/*
 * Command queue locking.
 * This is a form of bastardised rwlock with the following major changes:
 *
 * - The only LOCK routines are exclusive_trylock() and shared_lock().
 *   Neither have barrier semantics, and instead provide only a control
 *   dependency.
 *
 * - The UNLOCK routines are supplemented with shared_tryunlock(), which
 *   fails if the caller appears to be the last lock holder (yes, this is
 *   racy). All successful UNLOCK routines have RELEASE semantics.
 */

static void arm_smmu_cmdq_shared_lock(struct arm_smmu_cmdq *cmdq)
{
 int val;

 /*
 * We can try to avoid the cmpxchg() loop by simply incrementing the
 * lock counter. When held in exclusive state, the lock counter is set
 * to INT_MIN so these increments won't hurt as the value will remain
 * negative.
 */

 if (atomic_fetch_inc_relaxed(&cmdq->lock) >= 0)
  return;

 do {
  val = atomic_cond_read_relaxed(&cmdq->lock, VAL >= 0);
 } while (atomic_cmpxchg_relaxed(&cmdq->lock, val, val + 1) != val);
}

static void arm_smmu_cmdq_shared_unlock(struct arm_smmu_cmdq *cmdq)
{
 (void)atomic_dec_return_release(&cmdq->lock);
}

static bool arm_smmu_cmdq_shared_tryunlock(struct arm_smmu_cmdq *cmdq)
{
 if (atomic_read(&cmdq->lock) == 1)
  return false;

 arm_smmu_cmdq_shared_unlock(cmdq);
 return true;
}

#define arm_smmu_cmdq_exclusive_trylock_irqsave(cmdq, flags)  \
({         \
 bool __ret;       \
 local_irq_save(flags);      \
 __ret = !atomic_cmpxchg_relaxed(&cmdq->lock, 0, INT_MIN); \
 if (!__ret)       \
  local_irq_restore(flags);    \
 __ret;        \
})

#define arm_smmu_cmdq_exclusive_unlock_irqrestore(cmdq, flags)  \
({         \
 atomic_set_release(&cmdq->lock, 0);    \
 local_irq_restore(flags);     \
})


/*
 * Command queue insertion.
 * This is made fiddly by our attempts to achieve some sort of scalability
 * since there is one queue shared amongst all of the CPUs in the system.  If
 * you like mixed-size concurrency, dependency ordering and relaxed atomics,
 * then you'll *love* this monstrosity.
 *
 * The basic idea is to split the queue up into ranges of commands that are
 * owned by a given CPU; the owner may not have written all of the commands
 * itself, but is responsible for advancing the hardware prod pointer when
 * the time comes. The algorithm is roughly:
 *
 *  1. Allocate some space in the queue. At this point we also discover
 *    whether the head of the queue is currently owned by another CPU,
 *    or whether we are the owner.
 *
 * 2. Write our commands into our allocated slots in the queue.
 *
 * 3. Mark our slots as valid in arm_smmu_cmdq.valid_map.
 *
 * 4. If we are an owner:
 * a. Wait for the previous owner to finish.
 * b. Mark the queue head as unowned, which tells us the range
 *    that we are responsible for publishing.
 * c. Wait for all commands in our owned range to become valid.
 * d. Advance the hardware prod pointer.
 * e. Tell the next owner we've finished.
 *
 * 5. If we are inserting a CMD_SYNC (we may or may not have been an
 *    owner), then we need to stick around until it has completed:
 * a. If we have MSIs, the SMMU can write back into the CMD_SYNC
 *    to clear the first 4 bytes.
 * b. Otherwise, we spin waiting for the hardware cons pointer to
 *    advance past our command.
 *
 * The devil is in the details, particularly the use of locking for handling
 * SYNC completion and freeing up space in the queue before we think that it is
 * full.
 */

static void __arm_smmu_cmdq_poll_set_valid_map(struct arm_smmu_cmdq *cmdq,
            u32 sprod, u32 eprod, bool set)
{
 u32 swidx, sbidx, ewidx, ebidx;
 struct arm_smmu_ll_queue llq = {
  .max_n_shift = cmdq->q.llq.max_n_shift,
  .prod  = sprod,
 };

 ewidx = BIT_WORD(Q_IDX(&llq, eprod));
 ebidx = Q_IDX(&llq, eprod) % BITS_PER_LONG;

 while (llq.prod != eprod) {
  unsigned long mask;
  atomic_long_t *ptr;
  u32 limit = BITS_PER_LONG;

  swidx = BIT_WORD(Q_IDX(&llq, llq.prod));
  sbidx = Q_IDX(&llq, llq.prod) % BITS_PER_LONG;

  ptr = &cmdq->valid_map[swidx];

  if ((swidx == ewidx) && (sbidx < ebidx))
   limit = ebidx;

  mask = GENMASK(limit - 1, sbidx);

  /*
 * The valid bit is the inverse of the wrap bit. This means
 * that a zero-initialised queue is invalid and, after marking
 * all entries as valid, they become invalid again when we
 * wrap.
 */

  if (set) {
   atomic_long_xor(mask, ptr);
  } else { /* Poll */
   unsigned long valid;

   valid = (ULONG_MAX + !!Q_WRP(&llq, llq.prod)) & mask;
   atomic_long_cond_read_relaxed(ptr, (VAL & mask) == valid);
  }

  llq.prod = queue_inc_prod_n(&llq, limit - sbidx);
 }
}

/* Mark all entries in the range [sprod, eprod) as valid */
static void arm_smmu_cmdq_set_valid_map(struct arm_smmu_cmdq *cmdq,
     u32 sprod, u32 eprod)
{
 __arm_smmu_cmdq_poll_set_valid_map(cmdq, sprod, eprod, true);
}

/* Wait for all entries in the range [sprod, eprod) to become valid */
static void arm_smmu_cmdq_poll_valid_map(struct arm_smmu_cmdq *cmdq,
      u32 sprod, u32 eprod)
{
 __arm_smmu_cmdq_poll_set_valid_map(cmdq, sprod, eprod, false);
}

/* Wait for the command queue to become non-full */
static int arm_smmu_cmdq_poll_until_not_full(struct arm_smmu_device *smmu,
          struct arm_smmu_cmdq *cmdq,
          struct arm_smmu_ll_queue *llq)
{
 unsigned long flags;
 struct arm_smmu_queue_poll qp;
 int ret = 0;

 /*
 * Try to update our copy of cons by grabbing exclusive cmdq access. If
 * that fails, spin until somebody else updates it for us.
 */

 if (arm_smmu_cmdq_exclusive_trylock_irqsave(cmdq, flags)) {
  WRITE_ONCE(cmdq->q.llq.cons, readl_relaxed(cmdq->q.cons_reg));
  arm_smmu_cmdq_exclusive_unlock_irqrestore(cmdq, flags);
  llq->val = READ_ONCE(cmdq->q.llq.val);
  return 0;
 }

 queue_poll_init(smmu, &qp);
 do {
  llq->val = READ_ONCE(cmdq->q.llq.val);
  if (!queue_full(llq))
   break;

  ret = queue_poll(&qp);
 } while (!ret);

 return ret;
}

/*
 * Wait until the SMMU signals a CMD_SYNC completion MSI.
 * Must be called with the cmdq lock held in some capacity.
 */

static int __arm_smmu_cmdq_poll_until_msi(struct arm_smmu_device *smmu,
       struct arm_smmu_cmdq *cmdq,
       struct arm_smmu_ll_queue *llq)
{
 int ret = 0;
 struct arm_smmu_queue_poll qp;
 u32 *cmd = (u32 *)(Q_ENT(&cmdq->q, llq->prod));

 queue_poll_init(smmu, &qp);

 /*
 * The MSI won't generate an event, since it's being written back
 * into the command queue.
 */

 qp.wfe = false;
 smp_cond_load_relaxed(cmd, !VAL || (ret = queue_poll(&qp)));
 llq->cons = ret ? llq->prod : queue_inc_prod_n(llq, 1);
 return ret;
}

/*
 * Wait until the SMMU cons index passes llq->prod.
 * Must be called with the cmdq lock held in some capacity.
 */

static int __arm_smmu_cmdq_poll_until_consumed(struct arm_smmu_device *smmu,
            struct arm_smmu_cmdq *cmdq,
            struct arm_smmu_ll_queue *llq)
{
 struct arm_smmu_queue_poll qp;
 u32 prod = llq->prod;
 int ret = 0;

 queue_poll_init(smmu, &qp);
 llq->val = READ_ONCE(cmdq->q.llq.val);
 do {
  if (queue_consumed(llq, prod))
   break;

  ret = queue_poll(&qp);

  /*
 * This needs to be a readl() so that our subsequent call
 * to arm_smmu_cmdq_shared_tryunlock() can fail accurately.
 *
 * Specifically, we need to ensure that we observe all
 * shared_lock()s by other CMD_SYNCs that share our owner,
 * so that a failing call to tryunlock() means that we're
 * the last one out and therefore we can safely advance
 * cmdq->q.llq.cons. Roughly speaking:
 *
 * CPU 0 CPU1 CPU2 (us)
 *
 * if (sync)
 *  shared_lock();
 *
 * dma_wmb();
 * set_valid_map();
 *
 *  if (owner) {
 * poll_valid_map();
 * <control dependency>
 * writel(prod_reg);
 *
 * readl(cons_reg);
 * tryunlock();
 *
 * Requires us to see CPU 0's shared_lock() acquisition.
 */

  llq->cons = readl(cmdq->q.cons_reg);
 } while (!ret);

 return ret;
}

static int arm_smmu_cmdq_poll_until_sync(struct arm_smmu_device *smmu,
      struct arm_smmu_cmdq *cmdq,
      struct arm_smmu_ll_queue *llq)
{
 if (smmu->options & ARM_SMMU_OPT_MSIPOLL &&
     !arm_smmu_cmdq_needs_busy_polling(smmu, cmdq))
  return __arm_smmu_cmdq_poll_until_msi(smmu, cmdq, llq);

 return __arm_smmu_cmdq_poll_until_consumed(smmu, cmdq, llq);
}

static void arm_smmu_cmdq_write_entries(struct arm_smmu_cmdq *cmdq, u64 *cmds,
     u32 prod, int n)
{
 int i;
 struct arm_smmu_ll_queue llq = {
  .max_n_shift = cmdq->q.llq.max_n_shift,
  .prod  = prod,
 };

 for (i = 0; i < n; ++i) {
  u64 *cmd = &cmds[i * CMDQ_ENT_DWORDS];

  prod = queue_inc_prod_n(&llq, i);
  queue_write(Q_ENT(&cmdq->q, prod), cmd, CMDQ_ENT_DWORDS);
 }
}

/*
 * This is the actual insertion function, and provides the following
 * ordering guarantees to callers:
 *
 * - There is a dma_wmb() before publishing any commands to the queue.
 *   This can be relied upon to order prior writes to data structures
 *   in memory (such as a CD or an STE) before the command.
 *
 * - On completion of a CMD_SYNC, there is a control dependency.
 *   This can be relied upon to order subsequent writes to memory (e.g.
 *   freeing an IOVA) after completion of the CMD_SYNC.
 *
 * - Command insertion is totally ordered, so if two CPUs each race to
 *   insert their own list of commands then all of the commands from one
 *   CPU will appear before any of the commands from the other CPU.
 */

int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu,
    struct arm_smmu_cmdq *cmdq, u64 *cmds, int n,
    bool sync)
{
 u64 cmd_sync[CMDQ_ENT_DWORDS];
 u32 prod;
 unsigned long flags;
 bool owner;
 struct arm_smmu_ll_queue llq, head;
 int ret = 0;

 llq.max_n_shift = cmdq->q.llq.max_n_shift;

 /* 1. Allocate some space in the queue */
 local_irq_save(flags);
 llq.val = READ_ONCE(cmdq->q.llq.val);
 do {
  u64 old;

  while (!queue_has_space(&llq, n + sync)) {
   local_irq_restore(flags);
   if (arm_smmu_cmdq_poll_until_not_full(smmu, cmdq, &llq))
    dev_err_ratelimited(smmu->dev, "CMDQ timeout\n");
   local_irq_save(flags);
  }

  head.cons = llq.cons;
  head.prod = queue_inc_prod_n(&llq, n + sync) |
          CMDQ_PROD_OWNED_FLAG;

  old = cmpxchg_relaxed(&cmdq->q.llq.val, llq.val, head.val);
  if (old == llq.val)
   break;

  llq.val = old;
 } while (1);
 owner = !(llq.prod & CMDQ_PROD_OWNED_FLAG);
 head.prod &= ~CMDQ_PROD_OWNED_FLAG;
 llq.prod &= ~CMDQ_PROD_OWNED_FLAG;

 /*
 * 2. Write our commands into the queue
 * Dependency ordering from the cmpxchg() loop above.
 */

 arm_smmu_cmdq_write_entries(cmdq, cmds, llq.prod, n);
 if (sync) {
  prod = queue_inc_prod_n(&llq, n);
  arm_smmu_cmdq_build_sync_cmd(cmd_sync, smmu, cmdq, prod);
  queue_write(Q_ENT(&cmdq->q, prod), cmd_sync, CMDQ_ENT_DWORDS);

  /*
 * In order to determine completion of our CMD_SYNC, we must
 * ensure that the queue can't wrap twice without us noticing.
 * We achieve that by taking the cmdq lock as shared before
 * marking our slot as valid.
 */

  arm_smmu_cmdq_shared_lock(cmdq);
 }

 /* 3. Mark our slots as valid, ensuring commands are visible first */
 dma_wmb();
 arm_smmu_cmdq_set_valid_map(cmdq, llq.prod, head.prod);

 /* 4. If we are the owner, take control of the SMMU hardware */
 if (owner) {
  /* a. Wait for previous owner to finish */
  atomic_cond_read_relaxed(&cmdq->owner_prod, VAL == llq.prod);

  /* b. Stop gathering work by clearing the owned flag */
  prod = atomic_fetch_andnot_relaxed(CMDQ_PROD_OWNED_FLAG,
         &cmdq->q.llq.atomic.prod);
  prod &= ~CMDQ_PROD_OWNED_FLAG;

  /*
 * c. Wait for any gathered work to be written to the queue.
 * Note that we read our own entries so that we have the control
 * dependency required by (d).
 */

  arm_smmu_cmdq_poll_valid_map(cmdq, llq.prod, prod);

  /*
 * d. Advance the hardware prod pointer
 * Control dependency ordering from the entries becoming valid.
 */

  writel_relaxed(prod, cmdq->q.prod_reg);

  /*
 * e. Tell the next owner we're done
 * Make sure we've updated the hardware first, so that we don't
 * race to update prod and potentially move it backwards.
 */

  atomic_set_release(&cmdq->owner_prod, prod);
 }

 /* 5. If we are inserting a CMD_SYNC, we must wait for it to complete */
 if (sync) {
  llq.prod = queue_inc_prod_n(&llq, n);
  ret = arm_smmu_cmdq_poll_until_sync(smmu, cmdq, &llq);
  if (ret) {
   dev_err_ratelimited(smmu->dev,
         "CMD_SYNC timeout at 0x%08x [hwprod 0x%08x, hwcons 0x%08x]\n",
         llq.prod,
         readl_relaxed(cmdq->q.prod_reg),
         readl_relaxed(cmdq->q.cons_reg));
  }

  /*
 * Try to unlock the cmdq lock. This will fail if we're the last
 * reader, in which case we can safely update cmdq->q.llq.cons
 */

  if (!arm_smmu_cmdq_shared_tryunlock(cmdq)) {
   WRITE_ONCE(cmdq->q.llq.cons, llq.cons);
   arm_smmu_cmdq_shared_unlock(cmdq);
  }
 }

 local_irq_restore(flags);
 return ret;
}

static int __arm_smmu_cmdq_issue_cmd(struct arm_smmu_device *smmu,
         struct arm_smmu_cmdq_ent *ent,
         bool sync)
{
 u64 cmd[CMDQ_ENT_DWORDS];

 if (unlikely(arm_smmu_cmdq_build_cmd(cmd, ent))) {
  dev_warn(smmu->dev, "ignoring unknown CMDQ opcode 0x%x\n",
    ent->opcode);
  return -EINVAL;
 }

 return arm_smmu_cmdq_issue_cmdlist(
  smmu, arm_smmu_get_cmdq(smmu, ent), cmd, 1, sync);
}

static int arm_smmu_cmdq_issue_cmd(struct arm_smmu_device *smmu,
       struct arm_smmu_cmdq_ent *ent)
{
 return __arm_smmu_cmdq_issue_cmd(smmu, ent, false);
}

static int arm_smmu_cmdq_issue_cmd_with_sync(struct arm_smmu_device *smmu,
          struct arm_smmu_cmdq_ent *ent)
{
 return __arm_smmu_cmdq_issue_cmd(smmu, ent, true);
}

static void arm_smmu_cmdq_batch_init(struct arm_smmu_device *smmu,
         struct arm_smmu_cmdq_batch *cmds,
         struct arm_smmu_cmdq_ent *ent)
{
 cmds->num = 0;
 cmds->cmdq = arm_smmu_get_cmdq(smmu, ent);
}

static void arm_smmu_cmdq_batch_add(struct arm_smmu_device *smmu,
        struct arm_smmu_cmdq_batch *cmds,
        struct arm_smmu_cmdq_ent *cmd)
{
 bool unsupported_cmd = !arm_smmu_cmdq_supports_cmd(cmds->cmdq, cmd);
 bool force_sync = (cmds->num == CMDQ_BATCH_ENTRIES - 1) &&
     (smmu->options & ARM_SMMU_OPT_CMDQ_FORCE_SYNC);
 int index;

 if (force_sync || unsupported_cmd) {
  arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmdq, cmds->cmds,
         cmds->num, true);
  arm_smmu_cmdq_batch_init(smmu, cmds, cmd);
 }

 if (cmds->num == CMDQ_BATCH_ENTRIES) {
  arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmdq, cmds->cmds,
         cmds->num, false);
  arm_smmu_cmdq_batch_init(smmu, cmds, cmd);
 }

 index = cmds->num * CMDQ_ENT_DWORDS;
 if (unlikely(arm_smmu_cmdq_build_cmd(&cmds->cmds[index], cmd))) {
  dev_warn(smmu->dev, "ignoring unknown CMDQ opcode 0x%x\n",
    cmd->opcode);
  return;
 }

 cmds->num++;
}

static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu,
          struct arm_smmu_cmdq_batch *cmds)
{
 return arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmdq, cmds->cmds,
        cmds->num, true);
}

static void arm_smmu_page_response(struct device *dev, struct iopf_fault *unused,
       struct iommu_page_response *resp)
{
 struct arm_smmu_cmdq_ent cmd = {0};
 struct arm_smmu_master *master = dev_iommu_priv_get(dev);
 int sid = master->streams[0].id;

 if (WARN_ON(!master->stall_enabled))
  return;

 cmd.opcode  = CMDQ_OP_RESUME;
 cmd.resume.sid  = sid;
 cmd.resume.stag  = resp->grpid;
 switch (resp->code) {
 case IOMMU_PAGE_RESP_INVALID:
 case IOMMU_PAGE_RESP_FAILURE:
  cmd.resume.resp = CMDQ_RESUME_0_RESP_ABORT;
  break;
 case IOMMU_PAGE_RESP_SUCCESS:
  cmd.resume.resp = CMDQ_RESUME_0_RESP_RETRY;
  break;
 default:
  break;
 }

 arm_smmu_cmdq_issue_cmd(master->smmu, &cmd);
 /*
 * Don't send a SYNC, it doesn't do anything for RESUME or PRI_RESP.
 * RESUME consumption guarantees that the stalled transaction will be
 * terminated... at some point in the future. PRI_RESP is fire and
 * forget.
 */

}

/* Context descriptor manipulation functions */
void arm_smmu_tlb_inv_asid(struct arm_smmu_device *smmu, u16 asid)
{
 struct arm_smmu_cmdq_ent cmd = {
  .opcode = smmu->features & ARM_SMMU_FEAT_E2H ?
   CMDQ_OP_TLBI_EL2_ASID : CMDQ_OP_TLBI_NH_ASID,
  .tlbi.asid = asid,
 };

 arm_smmu_cmdq_issue_cmd_with_sync(smmu, &cmd);
}

/*
 * Based on the value of ent report which bits of the STE the HW will access. It
 * would be nice if this was complete according to the spec, but minimally it
 * has to capture the bits this driver uses.
 */

VISIBLE_IF_KUNIT
void arm_smmu_get_ste_used(const __le64 *ent, __le64 *used_bits)
{
 unsigned int cfg = FIELD_GET(STRTAB_STE_0_CFG, le64_to_cpu(ent[0]));

 used_bits[0] = cpu_to_le64(STRTAB_STE_0_V);
 if (!(ent[0] & cpu_to_le64(STRTAB_STE_0_V)))
  return;

 used_bits[0] |= cpu_to_le64(STRTAB_STE_0_CFG);

 /* S1 translates */
 if (cfg & BIT(0)) {
  used_bits[0] |= cpu_to_le64(STRTAB_STE_0_S1FMT |
         STRTAB_STE_0_S1CTXPTR_MASK |
         STRTAB_STE_0_S1CDMAX);
  used_bits[1] |=
   cpu_to_le64(STRTAB_STE_1_S1DSS | STRTAB_STE_1_S1CIR |
        STRTAB_STE_1_S1COR | STRTAB_STE_1_S1CSH |
        STRTAB_STE_1_S1STALLD | STRTAB_STE_1_STRW |
        STRTAB_STE_1_EATS | STRTAB_STE_1_MEV);
  used_bits[2] |= cpu_to_le64(STRTAB_STE_2_S2VMID);

  /*
 * See 13.5 Summary of attribute/permission configuration fields
 * for the SHCFG behavior.
 */

  if (FIELD_GET(STRTAB_STE_1_S1DSS, le64_to_cpu(ent[1])) ==
      STRTAB_STE_1_S1DSS_BYPASS)
   used_bits[1] |= cpu_to_le64(STRTAB_STE_1_SHCFG);
 }

 /* S2 translates */
 if (cfg & BIT(1)) {
  used_bits[1] |=
   cpu_to_le64(STRTAB_STE_1_S2FWB | STRTAB_STE_1_EATS |
        STRTAB_STE_1_SHCFG | STRTAB_STE_1_MEV);
  used_bits[2] |=
   cpu_to_le64(STRTAB_STE_2_S2VMID | STRTAB_STE_2_VTCR |
        STRTAB_STE_2_S2AA64 | STRTAB_STE_2_S2ENDI |
        STRTAB_STE_2_S2PTW | STRTAB_STE_2_S2S |
        STRTAB_STE_2_S2R);
  used_bits[3] |= cpu_to_le64(STRTAB_STE_3_S2TTB_MASK);
 }

 if (cfg == STRTAB_STE_0_CFG_BYPASS)
  used_bits[1] |= cpu_to_le64(STRTAB_STE_1_SHCFG);
}
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_get_ste_used);

/*
 * Figure out if we can do a hitless update of entry to become target. Returns a
 * bit mask where 1 indicates that qword needs to be set disruptively.
 * unused_update is an intermediate value of entry that has unused bits set to
 * their new values.
 */

static u8 arm_smmu_entry_qword_diff(struct arm_smmu_entry_writer *writer,
        const __le64 *entry, const __le64 *target,
        __le64 *unused_update)
{
 __le64 target_used[NUM_ENTRY_QWORDS] = {};
 __le64 cur_used[NUM_ENTRY_QWORDS] = {};
 u8 used_qword_diff = 0;
 unsigned int i;

 writer->ops->get_used(entry, cur_used);
 writer->ops->get_used(target, target_used);

 for (i = 0; i != NUM_ENTRY_QWORDS; i++) {
  /*
 * Check that masks are up to date, the make functions are not
 * allowed to set a bit to 1 if the used function doesn't say it
 * is used.
 */

  WARN_ON_ONCE(target[i] & ~target_used[i]);

  /* Bits can change because they are not currently being used */
  unused_update[i] = (entry[i] & cur_used[i]) |
       (target[i] & ~cur_used[i]);
  /*
 * Each bit indicates that a used bit in a qword needs to be
 * changed after unused_update is applied.
 */

  if ((unused_update[i] & target_used[i]) != target[i])
   used_qword_diff |= 1 << i;
 }
 return used_qword_diff;
}

static bool entry_set(struct arm_smmu_entry_writer *writer, __le64 *entry,
        const __le64 *target, unsigned int start,
        unsigned int len)
{
 bool changed = false;
 unsigned int i;

 for (i = start; len != 0; len--, i++) {
  if (entry[i] != target[i]) {
   WRITE_ONCE(entry[i], target[i]);
   changed = true;
  }
 }

 if (changed)
  writer->ops->sync(writer);
 return changed;
}

/*
 * Update the STE/CD to the target configuration. The transition from the
 * current entry to the target entry takes place over multiple steps that
 * attempts to make the transition hitless if possible. This function takes care
 * not to create a situation where the HW can perceive a corrupted entry. HW is
 * only required to have a 64 bit atomicity with stores from the CPU, while
 * entries are many 64 bit values big.
 *
 * The difference between the current value and the target value is analyzed to
 * determine which of three updates are required - disruptive, hitless or no
 * change.
 *
 * In the most general disruptive case we can make any update in three steps:
 *  - Disrupting the entry (V=0)
 *  - Fill now unused qwords, execpt qword 0 which contains V
 *  - Make qword 0 have the final value and valid (V=1) with a single 64
 *    bit store
 *
 * However this disrupts the HW while it is happening. There are several
 * interesting cases where a STE/CD can be updated without disturbing the HW
 * because only a small number of bits are changing (S1DSS, CONFIG, etc) or
 * because the used bits don't intersect. We can detect this by calculating how
 * many 64 bit values need update after adjusting the unused bits and skip the
 * V=0 process. This relies on the IGNORED behavior described in the
 * specification.
 */

VISIBLE_IF_KUNIT
void arm_smmu_write_entry(struct arm_smmu_entry_writer *writer, __le64 *entry,
     const __le64 *target)
{
 __le64 unused_update[NUM_ENTRY_QWORDS];
 u8 used_qword_diff;

 used_qword_diff =
  arm_smmu_entry_qword_diff(writer, entry, target, unused_update);
 if (hweight8(used_qword_diff) == 1) {
  /*
 * Only one qword needs its used bits to be changed. This is a
 * hitless update, update all bits the current STE/CD is
 * ignoring to their new values, then update a single "critical
 * qword" to change the STE/CD and finally 0 out any bits that
 * are now unused in the target configuration.
 */

  unsigned int critical_qword_index = ffs(used_qword_diff) - 1;

  /*
 * Skip writing unused bits in the critical qword since we'll be
 * writing it in the next step anyways. This can save a sync
 * when the only change is in that qword.
 */

  unused_update[critical_qword_index] =
   entry[critical_qword_index];
  entry_set(writer, entry, unused_update, 0, NUM_ENTRY_QWORDS);
  entry_set(writer, entry, target, critical_qword_index, 1);
  entry_set(writer, entry, target, 0, NUM_ENTRY_QWORDS);
 } else if (used_qword_diff) {
  /*
 * At least two qwords need their inuse bits to be changed. This
 * requires a breaking update, zero the V bit, write all qwords
 * but 0, then set qword 0
 */

  unused_update[0] = 0;
  entry_set(writer, entry, unused_update, 0, 1);
  entry_set(writer, entry, target, 1, NUM_ENTRY_QWORDS - 1);
  entry_set(writer, entry, target, 0, 1);
 } else {
  /*
 * No inuse bit changed. Sanity check that all unused bits are 0
 * in the entry. The target was already sanity checked by
 * compute_qword_diff().
 */

  WARN_ON_ONCE(
   entry_set(writer, entry, target, 0, NUM_ENTRY_QWORDS));
 }
}
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_write_entry);

static void arm_smmu_sync_cd(struct arm_smmu_master *master,
        int ssid, bool leaf)
{
 size_t i;
 struct arm_smmu_cmdq_batch cmds;
 struct arm_smmu_device *smmu = master->smmu;
 struct arm_smmu_cmdq_ent cmd = {
  .opcode = CMDQ_OP_CFGI_CD,
  .cfgi = {
   .ssid = ssid,
   .leaf = leaf,
  },
 };

 arm_smmu_cmdq_batch_init(smmu, &cmds, &cmd);
 for (i = 0; i < master->num_streams; i++) {
  cmd.cfgi.sid = master->streams[i].id;
  arm_smmu_cmdq_batch_add(smmu, &cmds, &cmd);
 }

 arm_smmu_cmdq_batch_submit(smmu, &cmds);
}

static void arm_smmu_write_cd_l1_desc(struct arm_smmu_cdtab_l1 *dst,
          dma_addr_t l2ptr_dma)
{
 u64 val = (l2ptr_dma & CTXDESC_L1_DESC_L2PTR_MASK) | CTXDESC_L1_DESC_V;

 /* The HW has 64 bit atomicity with stores to the L2 CD table */
 WRITE_ONCE(dst->l2ptr, cpu_to_le64(val));
}

static dma_addr_t arm_smmu_cd_l1_get_desc(const struct arm_smmu_cdtab_l1 *src)
{
 return le64_to_cpu(src->l2ptr) & CTXDESC_L1_DESC_L2PTR_MASK;
}

struct arm_smmu_cd *arm_smmu_get_cd_ptr(struct arm_smmu_master *master,
     u32 ssid)
{
 struct arm_smmu_cdtab_l2 *l2;
 struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;

 if (!arm_smmu_cdtab_allocated(cd_table))
  return NULL;

 if (cd_table->s1fmt == STRTAB_STE_0_S1FMT_LINEAR)
  return &cd_table->linear.table[ssid];

 l2 = cd_table->l2.l2ptrs[arm_smmu_cdtab_l1_idx(ssid)];
 if (!l2)
  return NULL;
 return &l2->cds[arm_smmu_cdtab_l2_idx(ssid)];
}

static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master,
       u32 ssid)
{
 struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
 struct arm_smmu_device *smmu = master->smmu;

 might_sleep();
 iommu_group_mutex_assert(master->dev);

 if (!arm_smmu_cdtab_allocated(cd_table)) {
  if (arm_smmu_alloc_cd_tables(master))
   return NULL;
 }

 if (cd_table->s1fmt == STRTAB_STE_0_S1FMT_64K_L2) {
  unsigned int idx = arm_smmu_cdtab_l1_idx(ssid);
  struct arm_smmu_cdtab_l2 **l2ptr = &cd_table->l2.l2ptrs[idx];

  if (!*l2ptr) {
   dma_addr_t l2ptr_dma;

   *l2ptr = dma_alloc_coherent(smmu->dev, sizeof(**l2ptr),
          &l2ptr_dma, GFP_KERNEL);
   if (!*l2ptr)
    return NULL;

   arm_smmu_write_cd_l1_desc(&cd_table->l2.l1tab[idx],
        l2ptr_dma);
   /* An invalid L1CD can be cached */
   arm_smmu_sync_cd(master, ssid, false);
  }
 }
 return arm_smmu_get_cd_ptr(master, ssid);
}

struct arm_smmu_cd_writer {
 struct arm_smmu_entry_writer writer;
 unsigned int ssid;
};

VISIBLE_IF_KUNIT
void arm_smmu_get_cd_used(const __le64 *ent, __le64 *used_bits)
{
 used_bits[0] = cpu_to_le64(CTXDESC_CD_0_V);
 if (!(ent[0] & cpu_to_le64(CTXDESC_CD_0_V)))
  return;
 memset(used_bits, 0xFF, sizeof(struct arm_smmu_cd));

 /*
 * If EPD0 is set by the make function it means
 * T0SZ/TG0/IR0/OR0/SH0/TTB0 are IGNORED
 */

 if (ent[0] & cpu_to_le64(CTXDESC_CD_0_TCR_EPD0)) {
  used_bits[0] &= ~cpu_to_le64(
   CTXDESC_CD_0_TCR_T0SZ | CTXDESC_CD_0_TCR_TG0 |
   CTXDESC_CD_0_TCR_IRGN0 | CTXDESC_CD_0_TCR_ORGN0 |
   CTXDESC_CD_0_TCR_SH0);
  used_bits[1] &= ~cpu_to_le64(CTXDESC_CD_1_TTB0_MASK);
 }
}
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_get_cd_used);

static void arm_smmu_cd_writer_sync_entry(struct arm_smmu_entry_writer *writer)
{
 struct arm_smmu_cd_writer *cd_writer =
  container_of(writer, struct arm_smmu_cd_writer, writer);

 arm_smmu_sync_cd(writer->master, cd_writer->ssid, true);
}

static const struct arm_smmu_entry_writer_ops arm_smmu_cd_writer_ops = {
 .sync = arm_smmu_cd_writer_sync_entry,
 .get_used = arm_smmu_get_cd_used,
};

void arm_smmu_write_cd_entry(struct arm_smmu_master *master, int ssid,
        struct arm_smmu_cd *cdptr,
        const struct arm_smmu_cd *target)
{
 bool target_valid = target->data[0] & cpu_to_le64(CTXDESC_CD_0_V);
 bool cur_valid = cdptr->data[0] & cpu_to_le64(CTXDESC_CD_0_V);
 struct arm_smmu_cd_writer cd_writer = {
  .writer = {
   .ops = &arm_smmu_cd_writer_ops,
   .master = master,
  },
  .ssid = ssid,
 };

 if (ssid != IOMMU_NO_PASID && cur_valid != target_valid) {
  if (cur_valid)
   master->cd_table.used_ssids--;
  else
   master->cd_table.used_ssids++;
 }

 arm_smmu_write_entry(&cd_writer.writer, cdptr->data, target->data);
}

void arm_smmu_make_s1_cd(struct arm_smmu_cd *target,
    struct arm_smmu_master *master,
    struct arm_smmu_domain *smmu_domain)
{
 struct arm_smmu_ctx_desc *cd = &smmu_domain->cd;
 const struct io_pgtable_cfg *pgtbl_cfg =
  &io_pgtable_ops_to_pgtable(smmu_domain->pgtbl_ops)->cfg;
 typeof(&pgtbl_cfg->arm_lpae_s1_cfg.tcr) tcr =
  &pgtbl_cfg->arm_lpae_s1_cfg.tcr;

 memset(target, 0, sizeof(*target));

 target->data[0] = cpu_to_le64(
  FIELD_PREP(CTXDESC_CD_0_TCR_T0SZ, tcr->tsz) |
  FIELD_PREP(CTXDESC_CD_0_TCR_TG0, tcr->tg) |
  FIELD_PREP(CTXDESC_CD_0_TCR_IRGN0, tcr->irgn) |
  FIELD_PREP(CTXDESC_CD_0_TCR_ORGN0, tcr->orgn) |
  FIELD_PREP(CTXDESC_CD_0_TCR_SH0, tcr->sh) |
#ifdef __BIG_ENDIAN
  CTXDESC_CD_0_ENDI |
#endif
  CTXDESC_CD_0_TCR_EPD1 |
  CTXDESC_CD_0_V |
  FIELD_PREP(CTXDESC_CD_0_TCR_IPS, tcr->ips) |
  CTXDESC_CD_0_AA64 |
  (master->stall_enabled ? CTXDESC_CD_0_S : 0) |
  CTXDESC_CD_0_R |
  CTXDESC_CD_0_A |
  CTXDESC_CD_0_ASET |
  FIELD_PREP(CTXDESC_CD_0_ASID, cd->asid)
  );

 /* To enable dirty flag update, set both Access flag and dirty state update */
 if (pgtbl_cfg->quirks & IO_PGTABLE_QUIRK_ARM_HD)
  target->data[0] |= cpu_to_le64(CTXDESC_CD_0_TCR_HA |
            CTXDESC_CD_0_TCR_HD);

 target->data[1] = cpu_to_le64(pgtbl_cfg->arm_lpae_s1_cfg.ttbr &
          CTXDESC_CD_1_TTB0_MASK);
 target->data[3] = cpu_to_le64(pgtbl_cfg->arm_lpae_s1_cfg.mair);
}
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_make_s1_cd);

void arm_smmu_clear_cd(struct arm_smmu_master *master, ioasid_t ssid)
{
 struct arm_smmu_cd target = {};
 struct arm_smmu_cd *cdptr;

 if (!arm_smmu_cdtab_allocated(&master->cd_table))
  return;
 cdptr = arm_smmu_get_cd_ptr(master, ssid);
 if (WARN_ON(!cdptr))
  return;
 arm_smmu_write_cd_entry(master, ssid, cdptr, &target);
}

static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master)
{
 int ret;
 size_t l1size;
 size_t max_contexts;
 struct arm_smmu_device *smmu = master->smmu;
 struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;

 cd_table->s1cdmax = master->ssid_bits;
 max_contexts = 1 << cd_table->s1cdmax;

 if (!(smmu->features & ARM_SMMU_FEAT_2_LVL_CDTAB) ||
     max_contexts <= CTXDESC_L2_ENTRIES) {
  cd_table->s1fmt = STRTAB_STE_0_S1FMT_LINEAR;
  cd_table->linear.num_ents = max_contexts;

  l1size = max_contexts * sizeof(struct arm_smmu_cd);
  cd_table->linear.table = dma_alloc_coherent(smmu->dev, l1size,
           &cd_table->cdtab_dma,
           GFP_KERNEL);
  if (!cd_table->linear.table)
   return -ENOMEM;
 } else {
  cd_table->s1fmt = STRTAB_STE_0_S1FMT_64K_L2;
  cd_table->l2.num_l1_ents =
   DIV_ROUND_UP(max_contexts, CTXDESC_L2_ENTRIES);

  cd_table->l2.l2ptrs = kcalloc(cd_table->l2.num_l1_ents,
          sizeof(*cd_table->l2.l2ptrs),
          GFP_KERNEL);
  if (!cd_table->l2.l2ptrs)
   return -ENOMEM;

  l1size = cd_table->l2.num_l1_ents * sizeof(struct arm_smmu_cdtab_l1);
  cd_table->l2.l1tab = dma_alloc_coherent(smmu->dev, l1size,
       &cd_table->cdtab_dma,
       GFP_KERNEL);
  if (!cd_table->l2.l2ptrs) {
   ret = -ENOMEM;
   goto err_free_l2ptrs;
  }
 }
 return 0;

err_free_l2ptrs:
 kfree(cd_table->l2.l2ptrs);
 cd_table->l2.l2ptrs = NULL;
 return ret;
}

static void arm_smmu_free_cd_tables(struct arm_smmu_master *master)
{
 int i;
 struct arm_smmu_device *smmu = master->smmu;
 struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;

 if (cd_table->s1fmt != STRTAB_STE_0_S1FMT_LINEAR) {
  for (i = 0; i < cd_table->l2.num_l1_ents; i++) {
   if (!cd_table->l2.l2ptrs[i])
    continue;

   dma_free_coherent(smmu->dev,
       sizeof(*cd_table->l2.l2ptrs[i]),
       cd_table->l2.l2ptrs[i],
       arm_smmu_cd_l1_get_desc(&cd_table->l2.l1tab[i]));
  }
  kfree(cd_table->l2.l2ptrs);

  dma_free_coherent(smmu->dev,
      cd_table->l2.num_l1_ents *
       sizeof(struct arm_smmu_cdtab_l1),
      cd_table->l2.l1tab, cd_table->cdtab_dma);
 } else {
  dma_free_coherent(smmu->dev,
      cd_table->linear.num_ents *
       sizeof(struct arm_smmu_cd),
      cd_table->linear.table, cd_table->cdtab_dma);
 }
}

/* Stream table manipulation functions */
static void arm_smmu_write_strtab_l1_desc(struct arm_smmu_strtab_l1 *dst,
       dma_addr_t l2ptr_dma)
{
 u64 val = 0;

 val |= FIELD_PREP(STRTAB_L1_DESC_SPAN, STRTAB_SPLIT + 1);
 val |= l2ptr_dma & STRTAB_L1_DESC_L2PTR_MASK;

 /* The HW has 64 bit atomicity with stores to the L2 STE table */
 WRITE_ONCE(dst->l2ptr, cpu_to_le64(val));
}

struct arm_smmu_ste_writer {
 struct arm_smmu_entry_writer writer;
 u32 sid;
};

static void arm_smmu_ste_writer_sync_entry(struct arm_smmu_entry_writer *writer)
{
 struct arm_smmu_ste_writer *ste_writer =
  container_of(writer, struct arm_smmu_ste_writer, writer);
 struct arm_smmu_cmdq_ent cmd = {
  .opcode = CMDQ_OP_CFGI_STE,
  .cfgi = {
   .sid = ste_writer->sid,
   .leaf = true,
  },
 };

 arm_smmu_cmdq_issue_cmd_with_sync(writer->master->smmu, &cmd);
}

static const struct arm_smmu_entry_writer_ops arm_smmu_ste_writer_ops = {
 .sync = arm_smmu_ste_writer_sync_entry,
 .get_used = arm_smmu_get_ste_used,
};

static void arm_smmu_write_ste(struct arm_smmu_master *master, u32 sid,
          struct arm_smmu_ste *ste,
          const struct arm_smmu_ste *target)
{
 struct arm_smmu_device *smmu = master->smmu;
 struct arm_smmu_ste_writer ste_writer = {
  .writer = {
   .ops = &arm_smmu_ste_writer_ops,
   .master = master,
  },
  .sid = sid,
 };

 arm_smmu_write_entry(&ste_writer.writer, ste->data, target->data);

 /* It's likely that we'll want to use the new STE soon */
 if (!(smmu->options & ARM_SMMU_OPT_SKIP_PREFETCH)) {
  struct arm_smmu_cmdq_ent
   prefetch_cmd = { .opcode = CMDQ_OP_PREFETCH_CFG,
      .prefetch = {
       .sid = sid,
      } };

  arm_smmu_cmdq_issue_cmd(smmu, &prefetch_cmd);
 }
}

void arm_smmu_make_abort_ste(struct arm_smmu_ste *target)
{
 memset(target, 0, sizeof(*target));
 target->data[0] = cpu_to_le64(
  STRTAB_STE_0_V |
  FIELD_PREP(STRTAB_STE_0_CFG, STRTAB_STE_0_CFG_ABORT));
}
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_make_abort_ste);

VISIBLE_IF_KUNIT
void arm_smmu_make_bypass_ste(struct arm_smmu_device *smmu,
         struct arm_smmu_ste *target)
{
 memset(target, 0, sizeof(*target));
 target->data[0] = cpu_to_le64(
  STRTAB_STE_0_V |
  FIELD_PREP(STRTAB_STE_0_CFG, STRTAB_STE_0_CFG_BYPASS));

 if (smmu->features & ARM_SMMU_FEAT_ATTR_TYPES_OVR)
  target->data[1] = cpu_to_le64(FIELD_PREP(STRTAB_STE_1_SHCFG,
        STRTAB_STE_1_SHCFG_INCOMING));
}
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_make_bypass_ste);

VISIBLE_IF_KUNIT
void arm_smmu_make_cdtable_ste(struct arm_smmu_ste *target,
          struct arm_smmu_master *master, bool ats_enabled,
          unsigned int s1dss)
{
 struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
 struct arm_smmu_device *smmu = master->smmu;

 memset(target, 0, sizeof(*target));
 target->data[0] = cpu_to_le64(
  STRTAB_STE_0_V |
  FIELD_PREP(STRTAB_STE_0_CFG, STRTAB_STE_0_CFG_S1_TRANS) |
  FIELD_PREP(STRTAB_STE_0_S1FMT, cd_table->s1fmt) |
  (cd_table->cdtab_dma & STRTAB_STE_0_S1CTXPTR_MASK) |
  FIELD_PREP(STRTAB_STE_0_S1CDMAX, cd_table->s1cdmax));

 target->data[1] = cpu_to_le64(
  FIELD_PREP(STRTAB_STE_1_S1DSS, s1dss) |
  FIELD_PREP(STRTAB_STE_1_S1CIR, STRTAB_STE_1_S1C_CACHE_WBRA) |
  FIELD_PREP(STRTAB_STE_1_S1COR, STRTAB_STE_1_S1C_CACHE_WBRA) |
  FIELD_PREP(STRTAB_STE_1_S1CSH, ARM_SMMU_SH_ISH) |
  ((smmu->features & ARM_SMMU_FEAT_STALLS &&
    !master->stall_enabled) ?
    STRTAB_STE_1_S1STALLD :
    0) |
  FIELD_PREP(STRTAB_STE_1_EATS,
      ats_enabled ? STRTAB_STE_1_EATS_TRANS : 0));

 if ((smmu->features & ARM_SMMU_FEAT_ATTR_TYPES_OVR) &&
     s1dss == STRTAB_STE_1_S1DSS_BYPASS)
  target->data[1] |= cpu_to_le64(FIELD_PREP(
   STRTAB_STE_1_SHCFG, STRTAB_STE_1_SHCFG_INCOMING));

 if (smmu->features & ARM_SMMU_FEAT_E2H) {
  /*
 * To support BTM the streamworld needs to match the
 * configuration of the CPU so that the ASID broadcasts are
 * properly matched. This means either S/NS-EL2-E2H (hypervisor)
 * or NS-EL1 (guest). Since an SVA domain can be installed in a
 * PASID this should always use a BTM compatible configuration
 * if the HW supports it.
 */

  target->data[1] |= cpu_to_le64(
   FIELD_PREP(STRTAB_STE_1_STRW, STRTAB_STE_1_STRW_EL2));
 } else {
  target->data[1] |= cpu_to_le64(
   FIELD_PREP(STRTAB_STE_1_STRW, STRTAB_STE_1_STRW_NSEL1));

  /*
 * VMID 0 is reserved for stage-2 bypass EL1 STEs, see
 * arm_smmu_domain_alloc_id()
 */

  target->data[2] =
   cpu_to_le64(FIELD_PREP(STRTAB_STE_2_S2VMID, 0));
 }
}
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_make_cdtable_ste);

void arm_smmu_make_s2_domain_ste(struct arm_smmu_ste *target,
     struct arm_smmu_master *master,
     struct arm_smmu_domain *smmu_domain,
     bool ats_enabled)
{
 struct arm_smmu_s2_cfg *s2_cfg = &smmu_domain->s2_cfg;
 const struct io_pgtable_cfg *pgtbl_cfg =
  &io_pgtable_ops_to_pgtable(smmu_domain->pgtbl_ops)->cfg;
 typeof(&pgtbl_cfg->arm_lpae_s2_cfg.vtcr) vtcr =
  &pgtbl_cfg->arm_lpae_s2_cfg.vtcr;
 u64 vtcr_val;
 struct arm_smmu_device *smmu = master->smmu;

 memset(target, 0, sizeof(*target));
 target->data[0] = cpu_to_le64(
  STRTAB_STE_0_V |
  FIELD_PREP(STRTAB_STE_0_CFG, STRTAB_STE_0_CFG_S2_TRANS));

 target->data[1] = cpu_to_le64(
  FIELD_PREP(STRTAB_STE_1_EATS,
      ats_enabled ? STRTAB_STE_1_EATS_TRANS : 0));

 if (pgtbl_cfg->quirks & IO_PGTABLE_QUIRK_ARM_S2FWB)
  target->data[1] |= cpu_to_le64(STRTAB_STE_1_S2FWB);
 if (smmu->features & ARM_SMMU_FEAT_ATTR_TYPES_OVR)
  target->data[1] |= cpu_to_le64(FIELD_PREP(STRTAB_STE_1_SHCFG,
         STRTAB_STE_1_SHCFG_INCOMING));

 vtcr_val = FIELD_PREP(STRTAB_STE_2_VTCR_S2T0SZ, vtcr->tsz) |
     FIELD_PREP(STRTAB_STE_2_VTCR_S2SL0, vtcr->sl) |
     FIELD_PREP(STRTAB_STE_2_VTCR_S2IR0, vtcr->irgn) |
     FIELD_PREP(STRTAB_STE_2_VTCR_S2OR0, vtcr->orgn) |
     FIELD_PREP(STRTAB_STE_2_VTCR_S2SH0, vtcr->sh) |
     FIELD_PREP(STRTAB_STE_2_VTCR_S2TG, vtcr->tg) |
     FIELD_PREP(STRTAB_STE_2_VTCR_S2PS, vtcr->ps);
 target->data[2] = cpu_to_le64(
  FIELD_PREP(STRTAB_STE_2_S2VMID, s2_cfg->vmid) |
  FIELD_PREP(STRTAB_STE_2_VTCR, vtcr_val) |
  STRTAB_STE_2_S2AA64 |
#ifdef __BIG_ENDIAN
  STRTAB_STE_2_S2ENDI |
#endif
  STRTAB_STE_2_S2PTW |
  (master->stall_enabled ? STRTAB_STE_2_S2S : 0) |
  STRTAB_STE_2_S2R);

 target->data[3] = cpu_to_le64(pgtbl_cfg->arm_lpae_s2_cfg.vttbr &
          STRTAB_STE_3_S2TTB_MASK);
}
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_make_s2_domain_ste);

/*
 * This can safely directly manipulate the STE memory without a sync sequence
 * because the STE table has not been installed in the SMMU yet.
 */

static void arm_smmu_init_initial_stes(struct arm_smmu_ste *strtab,
           unsigned int nent)
{
 unsigned int i;

 for (i = 0; i < nent; ++i) {
  arm_smmu_make_abort_ste(strtab);
  strtab++;
 }
}

static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid)
{
 dma_addr_t l2ptr_dma;
 struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
 struct arm_smmu_strtab_l2 **l2table;

 l2table = &cfg->l2.l2ptrs[arm_smmu_strtab_l1_idx(sid)];
 if (*l2table)
  return 0;

 *l2table = dmam_alloc_coherent(smmu->dev, sizeof(**l2table),
           &l2ptr_dma, GFP_KERNEL);
 if (!*l2table) {
  dev_err(smmu->dev,
   "failed to allocate l2 stream table for SID %u\n",
   sid);
  return -ENOMEM;
 }

 arm_smmu_init_initial_stes((*l2table)->stes,
       ARRAY_SIZE((*l2table)->stes));
 arm_smmu_write_strtab_l1_desc(&cfg->l2.l1tab[arm_smmu_strtab_l1_idx(sid)],
          l2ptr_dma);
 return 0;
}

static int arm_smmu_streams_cmp_key(const void *lhs, const struct rb_node *rhs)
{
 struct arm_smmu_stream *stream_rhs =
  rb_entry(rhs, struct arm_smmu_stream, node);
 const u32 *sid_lhs = lhs;

 if (*sid_lhs < stream_rhs->id)
  return -1;
 if (*sid_lhs > stream_rhs->id)
  return 1;
 return 0;
}

static int arm_smmu_streams_cmp_node(struct rb_node *lhs,
         const struct rb_node *rhs)
{
 return arm_smmu_streams_cmp_key(
  &rb_entry(lhs, struct arm_smmu_stream, node)->id, rhs);
}

static struct arm_smmu_master *
arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid)
{
 struct rb_node *node;

 lockdep_assert_held(&smmu->streams_mutex);

 node = rb_find(&sid, &smmu->streams, arm_smmu_streams_cmp_key);
 if (!node)
  return NULL;
 return rb_entry(node, struct arm_smmu_stream, node)->master;
}

/* IRQ and event handlers */
static void arm_smmu_decode_event(struct arm_smmu_device *smmu, u64 *raw,
      struct arm_smmu_event *event)
{
 struct arm_smmu_master *master;

 event->id = FIELD_GET(EVTQ_0_ID, raw[0]);
 event->sid = FIELD_GET(EVTQ_0_SID, raw[0]);
 event->ssv = FIELD_GET(EVTQ_0_SSV, raw[0]);
 event->ssid = event->ssv ? FIELD_GET(EVTQ_0_SSID, raw[0]) : IOMMU_NO_PASID;
 event->privileged = FIELD_GET(EVTQ_1_PnU, raw[1]);
 event->instruction = FIELD_GET(EVTQ_1_InD, raw[1]);
 event->s2 = FIELD_GET(EVTQ_1_S2, raw[1]);
 event->read = FIELD_GET(EVTQ_1_RnW, raw[1]);
 event->stag = FIELD_GET(EVTQ_1_STAG, raw[1]);
 event->stall = FIELD_GET(EVTQ_1_STALL, raw[1]);
 event->class = FIELD_GET(EVTQ_1_CLASS, raw[1]);
 event->iova = FIELD_GET(EVTQ_2_ADDR, raw[2]);
 event->ipa = raw[3] & EVTQ_3_IPA;
 event->fetch_addr = raw[3] & EVTQ_3_FETCH_ADDR;
 event->ttrnw = FIELD_GET(EVTQ_1_TT_READ, raw[1]);
 event->class_tt = false;
 event->dev = NULL;

 if (event->id == EVT_ID_PERMISSION_FAULT)
  event->class_tt = (event->class == EVTQ_1_CLASS_TT);

 mutex_lock(&smmu->streams_mutex);
 master = arm_smmu_find_master(smmu, event->sid);
 if (master)
  event->dev = get_device(master->dev);
 mutex_unlock(&smmu->streams_mutex);
}

static int arm_smmu_handle_event(struct arm_smmu_device *smmu, u64 *evt,
     struct arm_smmu_event *event)
{
 int ret = 0;
 u32 perm = 0;
 struct arm_smmu_master *master;
 struct iopf_fault fault_evt = { };
 struct iommu_fault *flt = &fault_evt.fault;

 switch (event->id) {
 case EVT_ID_BAD_STE_CONFIG:
 case EVT_ID_STREAM_DISABLED_FAULT:
 case EVT_ID_BAD_SUBSTREAMID_CONFIG:
 case EVT_ID_BAD_CD_CONFIG:
 case EVT_ID_TRANSLATION_FAULT:
 case EVT_ID_ADDR_SIZE_FAULT:
 case EVT_ID_ACCESS_FAULT:
 case EVT_ID_PERMISSION_FAULT:
  break;
 default:
  return -EOPNOTSUPP;
 }

 if (event->stall) {
  if (event->read)
   perm |= IOMMU_FAULT_PERM_READ;
  else
   perm |= IOMMU_FAULT_PERM_WRITE;

  if (event->instruction)
   perm |= IOMMU_FAULT_PERM_EXEC;

  if (event->privileged)
   perm |= IOMMU_FAULT_PERM_PRIV;

  flt->type = IOMMU_FAULT_PAGE_REQ;
  flt->prm = (struct iommu_fault_page_request){
   .flags = IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE,
   .grpid = event->stag,
   .perm = perm,
   .addr = event->iova,
  };

  if (event->ssv) {
   flt->prm.flags |= IOMMU_FAULT_PAGE_REQUEST_PASID_VALID;
   flt->prm.pasid = event->ssid;
  }
 }

 mutex_lock(&smmu->streams_mutex);
 master = arm_smmu_find_master(smmu, event->sid);
 if (!master) {
  ret = -EINVAL;
  goto out_unlock;
 }

 if (event->stall)
  ret = iommu_report_device_fault(master->dev, &fault_evt);
 else if (master->vmaster && !event->s2)
  ret = arm_vmaster_report_event(master->vmaster, evt);
 else
  ret = -EOPNOTSUPP; /* Unhandled events should be pinned */
out_unlock:
 mutex_unlock(&smmu->streams_mutex);
 return ret;
}

static void arm_smmu_dump_raw_event(struct arm_smmu_device *smmu, u64 *raw,
        struct arm_smmu_event *event)
{
 int i;

 dev_err(smmu->dev, "event 0x%02x received:\n", event->id);

 for (i = 0; i < EVTQ_ENT_DWORDS; ++i)
  dev_err(smmu->dev, "\t0x%016llx\n", raw[i]);
}

#define ARM_SMMU_EVT_KNOWN(e) ((e)->id < ARRAY_SIZE(event_str) && event_str[(e)->id])
#define ARM_SMMU_LOG_EVT_STR(e) ARM_SMMU_EVT_KNOWN(e) ? event_str[(e)->id] : "UNKNOWN"
#define ARM_SMMU_LOG_CLIENT(e) (e)->dev ? dev_name((e)->dev) : "(unassigned sid)"

static void arm_smmu_dump_event(struct arm_smmu_device *smmu, u64 *raw,
    struct arm_smmu_event *evt,
    struct ratelimit_state *rs)
{
 if (!__ratelimit(rs))
  return;

 arm_smmu_dump_raw_event(smmu, raw, evt);

 switch (evt->id) {
 case EVT_ID_TRANSLATION_FAULT:
 case EVT_ID_ADDR_SIZE_FAULT:
 case EVT_ID_ACCESS_FAULT:
 case EVT_ID_PERMISSION_FAULT:
  dev_err(smmu->dev, "event: %s client: %s sid: %#x ssid: %#x iova: %#llx ipa: %#llx",
   ARM_SMMU_LOG_EVT_STR(evt), ARM_SMMU_LOG_CLIENT(evt),
   evt->sid, evt->ssid, evt->iova, evt->ipa);

  dev_err(smmu->dev, "%s %s %s %s \"%s\"%s%s stag: %#x",
   evt->privileged ? "priv" : "unpriv",
   evt->instruction ? "inst" : "data",
   str_read_write(evt->read),
   evt->s2 ? "s2" : "s1", event_class_str[evt->class],
   evt->class_tt ? (evt->ttrnw ? " ttd_read" : " ttd_write") : "",
   evt->stall ? " stall" : "", evt->stag);

  break;

 case EVT_ID_STE_FETCH_FAULT:
 case EVT_ID_CD_FETCH_FAULT:
 case EVT_ID_VMS_FETCH_FAULT:
  dev_err(smmu->dev, "event: %s client: %s sid: %#x ssid: %#x fetch_addr: %#llx",
   ARM_SMMU_LOG_EVT_STR(evt), ARM_SMMU_LOG_CLIENT(evt),
   evt->sid, evt->ssid, evt->fetch_addr);

  break;

 default:
  dev_err(smmu->dev, "event: %s client: %s sid: %#x ssid: %#x",
   ARM_SMMU_LOG_EVT_STR(evt), ARM_SMMU_LOG_CLIENT(evt),
   evt->sid, evt->ssid);
 }
}

static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
{
 u64 evt[EVTQ_ENT_DWORDS];
 struct arm_smmu_event event = {0};
 struct arm_smmu_device *smmu = dev;
 struct arm_smmu_queue *q = &smmu->evtq.q;
 struct arm_smmu_ll_queue *llq = &q->llq;
 static DEFINE_RATELIMIT_STATE(rs, DEFAULT_RATELIMIT_INTERVAL,
          DEFAULT_RATELIMIT_BURST);

 do {
  while (!queue_remove_raw(q, evt)) {
   arm_smmu_decode_event(smmu, evt, &event);
   if (arm_smmu_handle_event(smmu, evt, &event))
    arm_smmu_dump_event(smmu, evt, &event, &rs);

   put_device(event.dev);
   cond_resched();
  }

  /*
 * Not much we can do on overflow, so scream and pretend we're
 * trying harder.
 */

  if (queue_sync_prod_in(q) == -EOVERFLOW)
   dev_err(smmu->dev, "EVTQ overflow detected -- events lost\n");
 } while (!queue_empty(llq));

 /* Sync our overflow flag, as we believe we're up to speed */
 queue_sync_cons_ovf(q);
 return IRQ_HANDLED;
}

static void arm_smmu_handle_ppr(struct arm_smmu_device *smmu, u64 *evt)
{
 u32 sid, ssid;
 u16 grpid;
 bool ssv, last;

 sid = FIELD_GET(PRIQ_0_SID, evt[0]);
 ssv = FIELD_GET(PRIQ_0_SSID_V, evt[0]);
 ssid = ssv ? FIELD_GET(PRIQ_0_SSID, evt[0]) : IOMMU_NO_PASID;
 last = FIELD_GET(PRIQ_0_PRG_LAST, evt[0]);
 grpid = FIELD_GET(PRIQ_1_PRG_IDX, evt[1]);

 dev_info(smmu->dev, "unexpected PRI request received:\n");
 dev_info(smmu->dev,
   "\tsid 0x%08x.0x%05x: [%u%s] %sprivileged %s%s%s access at iova 0x%016llx\n",
   sid, ssid, grpid, last ? "L" : "",
   evt[0] & PRIQ_0_PERM_PRIV ? "" : "un",
   evt[0] & PRIQ_0_PERM_READ ? "R" : "",
   evt[0] & PRIQ_0_PERM_WRITE ? "W" : "",
   evt[0] & PRIQ_0_PERM_EXEC ? "X" : "",
   evt[1] & PRIQ_1_ADDR_MASK);

 if (last) {
  struct arm_smmu_cmdq_ent cmd = {
   .opcode   = CMDQ_OP_PRI_RESP,
   .substream_valid = ssv,
   .pri   = {
    .sid = sid,
    .ssid = ssid,
    .grpid = grpid,
    .resp = PRI_RESP_DENY,
   },
  };

  arm_smmu_cmdq_issue_cmd(smmu, &cmd);
 }
}

static irqreturn_t arm_smmu_priq_thread(int irq, void *dev)
{
 struct arm_smmu_device *smmu = dev;
 struct arm_smmu_queue *q = &smmu->priq.q;
 struct arm_smmu_ll_queue *llq = &q->llq;
 u64 evt[PRIQ_ENT_DWORDS];

 do {
  while (!queue_remove_raw(q, evt))
   arm_smmu_handle_ppr(smmu, evt);

  if (queue_sync_prod_in(q) == -EOVERFLOW)
   dev_err(smmu->dev, "PRIQ overflow detected -- requests lost\n");
 } while (!queue_empty(llq));

 /* Sync our overflow flag, as we believe we're up to speed */
 queue_sync_cons_ovf(q);
 return IRQ_HANDLED;
}

static int arm_smmu_device_disable(struct arm_smmu_device *smmu);

static irqreturn_t arm_smmu_gerror_handler(int irq, void *dev)
{
 u32 gerror, gerrorn, active;
 struct arm_smmu_device *smmu = dev;

 gerror = readl_relaxed(smmu->base + ARM_SMMU_GERROR);
 gerrorn = readl_relaxed(smmu->base + ARM_SMMU_GERRORN);

 active = gerror ^ gerrorn;
 if (!(active & GERROR_ERR_MASK))
  return IRQ_NONE; /* No errors pending */

 dev_warn(smmu->dev,
   "unexpected global error reported (0x%08x), this could be serious\n",
   active);

 if (active & GERROR_SFM_ERR) {
  dev_err(smmu->dev, "device has entered Service Failure Mode!\n");
  arm_smmu_device_disable(smmu);
 }

 if (active & GERROR_MSI_GERROR_ABT_ERR)
  dev_warn(smmu->dev, "GERROR MSI write aborted\n");

 if (active & GERROR_MSI_PRIQ_ABT_ERR)
  dev_warn(smmu->dev, "PRIQ MSI write aborted\n");

 if (active & GERROR_MSI_EVTQ_ABT_ERR)
  dev_warn(smmu->dev, "EVTQ MSI write aborted\n");

 if (active & GERROR_MSI_CMDQ_ABT_ERR)
  dev_warn(smmu->dev, "CMDQ MSI write aborted\n");

 if (active & GERROR_PRIQ_ABT_ERR)
  dev_err(smmu->dev, "PRIQ write aborted -- events may have been lost\n");

 if (active & GERROR_EVTQ_ABT_ERR)
  dev_err(smmu->dev, "EVTQ write aborted -- events may have been lost\n");

 if (active & GERROR_CMDQ_ERR)
  arm_smmu_cmdq_skip_err(smmu);

 writel(gerror, smmu->base + ARM_SMMU_GERRORN);
 return IRQ_HANDLED;
}

static irqreturn_t arm_smmu_combined_irq_thread(int irq, void *dev)
{
 struct arm_smmu_device *smmu = dev;

 arm_smmu_evtq_thread(irq, dev);
 if (smmu->features & ARM_SMMU_FEAT_PRI)
  arm_smmu_priq_thread(irq, dev);

 return IRQ_HANDLED;
}

static irqreturn_t arm_smmu_combined_irq_handler(int irq, void *dev)
{
 arm_smmu_gerror_handler(irq, dev);
 return IRQ_WAKE_THREAD;
}

static void
arm_smmu_atc_inv_to_cmd(int ssid, unsigned long iova, size_t size,
   struct arm_smmu_cmdq_ent *cmd)
{
 size_t log2_span;
 size_t span_mask;
 /* ATC invalidates are always on 4096-bytes pages */
 size_t inval_grain_shift = 12;
 unsigned long page_start, page_end;

 /*
 * ATS and PASID:
 *
 * If substream_valid is clear, the PCIe TLP is sent without a PASID
 * prefix. In that case all ATC entries within the address range are
 * invalidated, including those that were requested with a PASID! There
 * is no way to invalidate only entries without PASID.
 *
 * When using STRTAB_STE_1_S1DSS_SSID0 (reserving CD 0 for non-PASID
 * traffic), translation requests without PASID create ATC entries
 * without PASID, which must be invalidated with substream_valid clear.
 * This has the unpleasant side-effect of invalidating all PASID-tagged
 * ATC entries within the address range.
 */

 *cmd = (struct arm_smmu_cmdq_ent) {
  .opcode   = CMDQ_OP_ATC_INV,
  .substream_valid = (ssid != IOMMU_NO_PASID),
  .atc.ssid  = ssid,
 };

 if (!size) {
  cmd->atc.size = ATC_INV_SIZE_ALL;
  return;
 }

 page_start = iova >> inval_grain_shift;
 page_end = (iova + size - 1) >> inval_grain_shift;

 /*
 * In an ATS Invalidate Request, the address must be aligned on the
 * range size, which must be a power of two number of page sizes. We
 * thus have to choose between grossly over-invalidating the region, or
 * splitting the invalidation into multiple commands. For simplicity
 * we'll go with the first solution, but should refine it in the future
 * if multiple commands are shown to be more efficient.
 *
 * Find the smallest power of two that covers the range. The most
 * significant differing bit between the start and end addresses,
 * fls(start ^ end), indicates the required span. For example:
 *
 * We want to invalidate pages [8; 11]. This is already the ideal range:
 * x = 0b1000 ^ 0b1011 = 0b11
 * span = 1 << fls(x) = 4
 *
 * To invalidate pages [7; 10], we need to invalidate [0; 15]:
 * x = 0b0111 ^ 0b1010 = 0b1101
 * span = 1 << fls(x) = 16
 */

 log2_span = fls_long(page_start ^ page_end);
 span_mask = (1ULL << log2_span) - 1;

 page_start &= ~span_mask;

 cmd->atc.addr = page_start << inval_grain_shift;
 cmd->atc.size = log2_span;
}

static int arm_smmu_atc_inv_master(struct arm_smmu_master *master,
       ioasid_t ssid)
{
 int i;
 struct arm_smmu_cmdq_ent cmd;
 struct arm_smmu_cmdq_batch cmds;

 arm_smmu_atc_inv_to_cmd(ssid, 0, 0, &cmd);

 arm_smmu_cmdq_batch_init(master->smmu, &cmds, &cmd);
 for (i = 0; i < master->num_streams; i++) {
  cmd.atc.sid = master->streams[i].id;
  arm_smmu_cmdq_batch_add(master->smmu, &cmds, &cmd);
 }

 return arm_smmu_cmdq_batch_submit(master->smmu, &cmds);
}

int arm_smmu_atc_inv_domain(struct arm_smmu_domain *smmu_domain,
       unsigned long iova, size_t size)
{
 struct arm_smmu_master_domain *master_domain;
 int i;
 unsigned long flags;
 struct arm_smmu_cmdq_ent cmd = {
  .opcode = CMDQ_OP_ATC_INV,
 };
 struct arm_smmu_cmdq_batch cmds;

 if (!(smmu_domain->smmu->features & ARM_SMMU_FEAT_ATS))
  return 0;

 /*
 * Ensure that we've completed prior invalidation of the main TLBs
 * before we read 'nr_ats_masters' in case of a concurrent call to
 * arm_smmu_enable_ats():
 *
 * // unmap() // arm_smmu_enable_ats()
 * TLBI+SYNC atomic_inc(&nr_ats_masters);
 * smp_mb(); [...]
 * atomic_read(&nr_ats_masters); pci_enable_ats() // writel()
 *
 * Ensures that we always see the incremented 'nr_ats_masters' count if
 * ATS was enabled at the PCI device before completion of the TLBI.
 */

 smp_mb();
 if (!atomic_read(&smmu_domain->nr_ats_masters))
  return 0;

 arm_smmu_cmdq_batch_init(smmu_domain->smmu, &cmds, &cmd);

 spin_lock_irqsave(&smmu_domain->devices_lock, flags);
 list_for_each_entry(master_domain, &smmu_domain->devices,
       devices_elm) {
  struct arm_smmu_master *master = master_domain->master;

  if (!master->ats_enabled)
   continue;

  if (master_domain->nested_ats_flush) {
   /*
 * If a S2 used as a nesting parent is changed we have
 * no option but to completely flush the ATC.
 */

   arm_smmu_atc_inv_to_cmd(IOMMU_NO_PASID, 0, 0, &cmd);
  } else {
   arm_smmu_atc_inv_to_cmd(master_domain->ssid, iova, size,
      &cmd);
  }

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=97 H=93 G=94

¤ Dauer der Verarbeitung: 0.11 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.