]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/iommu/arm-smmu-v3.c
iommu/arm-smmu: Fix polling of command queue
[karo-tx-linux.git] / drivers / iommu / arm-smmu-v3.c
index ce801170d5f2342f1b2c750750bea720d9fc0ff2..c040e246bc590bcd747f15ab506f3108fa0936c6 100644 (file)
@@ -713,19 +713,15 @@ static void queue_inc_prod(struct arm_smmu_queue *q)
        writel(q->prod, q->prod_reg);
 }
 
-static bool __queue_cons_before(struct arm_smmu_queue *q, u32 until)
-{
-       if (Q_WRP(q, q->cons) == Q_WRP(q, until))
-               return Q_IDX(q, q->cons) < Q_IDX(q, until);
-
-       return Q_IDX(q, q->cons) >= Q_IDX(q, until);
-}
-
-static int queue_poll_cons(struct arm_smmu_queue *q, u32 until, bool wfe)
+/*
+ * Wait for the SMMU to consume items. If drain is true, wait until the queue
+ * is empty. Otherwise, wait until there is at least one free slot.
+ */
+static int queue_poll_cons(struct arm_smmu_queue *q, bool drain, bool wfe)
 {
        ktime_t timeout = ktime_add_us(ktime_get(), ARM_SMMU_POLL_TIMEOUT_US);
 
-       while (queue_sync_cons(q), __queue_cons_before(q, until)) {
+       while (queue_sync_cons(q), (drain ? !queue_empty(q) : queue_full(q))) {
                if (ktime_compare(ktime_get(), timeout) > 0)
                        return -ETIMEDOUT;
 
@@ -879,7 +875,7 @@ static void arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu)
         * 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, idx), q->ent_dwords);
+       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]);
@@ -890,13 +886,12 @@ static void arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu)
                return;
        }
 
-       queue_write(cmd, Q_ENT(q, idx), q->ent_dwords);
+       queue_write(Q_ENT(q, cons), cmd, q->ent_dwords);
 }
 
 static void arm_smmu_cmdq_issue_cmd(struct arm_smmu_device *smmu,
                                    struct arm_smmu_cmdq_ent *ent)
 {
-       u32 until;
        u64 cmd[CMDQ_ENT_DWORDS];
        bool wfe = !!(smmu->features & ARM_SMMU_FEAT_SEV);
        struct arm_smmu_queue *q = &smmu->cmdq.q;
@@ -908,17 +903,12 @@ static void arm_smmu_cmdq_issue_cmd(struct arm_smmu_device *smmu,
        }
 
        spin_lock(&smmu->cmdq.lock);
-       while (until = q->prod + 1, queue_insert_raw(q, cmd) == -ENOSPC) {
-               /*
-                * Keep the queue locked, otherwise the producer could wrap
-                * twice and we could see a future consumer pointer that looks
-                * like it's behind us.
-                */
-               if (queue_poll_cons(q, until, wfe))
+       while (queue_insert_raw(q, cmd) == -ENOSPC) {
+               if (queue_poll_cons(q, false, wfe))
                        dev_err_ratelimited(smmu->dev, "CMDQ timeout\n");
        }
 
-       if (ent->opcode == CMDQ_OP_CMD_SYNC && queue_poll_cons(q, until, wfe))
+       if (ent->opcode == CMDQ_OP_CMD_SYNC && queue_poll_cons(q, true, wfe))
                dev_err_ratelimited(smmu->dev, "CMD_SYNC timeout\n");
        spin_unlock(&smmu->cmdq.lock);
 }
@@ -1034,6 +1024,9 @@ static void arm_smmu_write_strtab_ent(struct arm_smmu_device *smmu, u32 sid,
                case STRTAB_STE_0_CFG_S2_TRANS:
                        ste_live = true;
                        break;
+               case STRTAB_STE_0_CFG_ABORT:
+                       if (disable_bypass)
+                               break;
                default:
                        BUG(); /* STE corruption */
                }
@@ -1158,36 +1151,66 @@ static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
        struct arm_smmu_queue *q = &smmu->evtq.q;
        u64 evt[EVTQ_ENT_DWORDS];
 
-       while (!queue_remove_raw(q, evt)) {
-               u8 id = evt[0] >> EVTQ_0_ID_SHIFT & EVTQ_0_ID_MASK;
+       do {
+               while (!queue_remove_raw(q, evt)) {
+                       u8 id = evt[0] >> EVTQ_0_ID_SHIFT & EVTQ_0_ID_MASK;
 
-               dev_info(smmu->dev, "event 0x%02x received:\n", id);
-               for (i = 0; i < ARRAY_SIZE(evt); ++i)
-                       dev_info(smmu->dev, "\t0x%016llx\n",
-                                (unsigned long long)evt[i]);
-       }
+                       dev_info(smmu->dev, "event 0x%02x received:\n", id);
+                       for (i = 0; i < ARRAY_SIZE(evt); ++i)
+                               dev_info(smmu->dev, "\t0x%016llx\n",
+                                        (unsigned long long)evt[i]);
+
+               }
+
+               /*
+                * Not much we can do on overflow, so scream and pretend we're
+                * trying harder.
+                */
+               if (queue_sync_prod(q) == -EOVERFLOW)
+                       dev_err(smmu->dev, "EVTQ overflow detected -- events lost\n");
+       } while (!queue_empty(q));
 
        /* Sync our overflow flag, as we believe we're up to speed */
        q->cons = Q_OVF(q, q->prod) | Q_WRP(q, q->cons) | Q_IDX(q, q->cons);
        return IRQ_HANDLED;
 }
 
-static irqreturn_t arm_smmu_evtq_handler(int irq, void *dev)
-{
-       irqreturn_t ret = IRQ_WAKE_THREAD;
-       struct arm_smmu_device *smmu = dev;
-       struct arm_smmu_queue *q = &smmu->evtq.q;
-
-       /*
-        * Not much we can do on overflow, so scream and pretend we're
-        * trying harder.
-        */
-       if (queue_sync_prod(q) == -EOVERFLOW)
-               dev_err(smmu->dev, "EVTQ overflow detected -- events lost\n");
-       else if (queue_empty(q))
-               ret = IRQ_NONE;
+static void arm_smmu_handle_ppr(struct arm_smmu_device *smmu, u64 *evt)
+{
+       u32 sid, ssid;
+       u16 grpid;
+       bool ssv, last;
+
+       sid = evt[0] >> PRIQ_0_SID_SHIFT & PRIQ_0_SID_MASK;
+       ssv = evt[0] & PRIQ_0_SSID_V;
+       ssid = ssv ? evt[0] >> PRIQ_0_SSID_SHIFT & PRIQ_0_SSID_MASK : 0;
+       last = evt[0] & PRIQ_0_PRG_LAST;
+       grpid = evt[1] >> PRIQ_1_PRG_IDX_SHIFT & PRIQ_1_PRG_IDX_MASK;
+
+       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 << PRIQ_1_ADDR_SHIFT);
+
+       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,
+                       },
+               };
 
-       return ret;
+               arm_smmu_cmdq_issue_cmd(smmu, &cmd);
+       }
 }
 
 static irqreturn_t arm_smmu_priq_thread(int irq, void *dev)
@@ -1196,63 +1219,19 @@ static irqreturn_t arm_smmu_priq_thread(int irq, void *dev)
        struct arm_smmu_queue *q = &smmu->priq.q;
        u64 evt[PRIQ_ENT_DWORDS];
 
-       while (!queue_remove_raw(q, evt)) {
-               u32 sid, ssid;
-               u16 grpid;
-               bool ssv, last;
-
-               sid = evt[0] >> PRIQ_0_SID_SHIFT & PRIQ_0_SID_MASK;
-               ssv = evt[0] & PRIQ_0_SSID_V;
-               ssid = ssv ? evt[0] >> PRIQ_0_SSID_SHIFT & PRIQ_0_SSID_MASK : 0;
-               last = evt[0] & PRIQ_0_PRG_LAST;
-               grpid = evt[1] >> PRIQ_1_PRG_IDX_SHIFT & PRIQ_1_PRG_IDX_MASK;
-
-               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 << PRIQ_1_ADDR_SHIFT);
-
-               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,
-                               },
-                       };
+       do {
+               while (!queue_remove_raw(q, evt))
+                       arm_smmu_handle_ppr(smmu, evt);
 
-                       arm_smmu_cmdq_issue_cmd(smmu, &cmd);
-               }
-       }
+               if (queue_sync_prod(q) == -EOVERFLOW)
+                       dev_err(smmu->dev, "PRIQ overflow detected -- requests lost\n");
+       } while (!queue_empty(q));
 
        /* Sync our overflow flag, as we believe we're up to speed */
        q->cons = Q_OVF(q, q->prod) | Q_WRP(q, q->cons) | Q_IDX(q, q->cons);
        return IRQ_HANDLED;
 }
 
-static irqreturn_t arm_smmu_priq_handler(int irq, void *dev)
-{
-       irqreturn_t ret = IRQ_WAKE_THREAD;
-       struct arm_smmu_device *smmu = dev;
-       struct arm_smmu_queue *q = &smmu->priq.q;
-
-       /* PRIQ overflow indicates a programming error */
-       if (queue_sync_prod(q) == -EOVERFLOW)
-               dev_err(smmu->dev, "PRIQ overflow detected -- requests lost\n");
-       else if (queue_empty(q))
-               ret = IRQ_NONE;
-
-       return ret;
-}
-
 static irqreturn_t arm_smmu_cmdq_sync_handler(int irq, void *dev)
 {
        /* We don't actually use CMD_SYNC interrupts for anything */
@@ -1285,15 +1264,11 @@ static irqreturn_t arm_smmu_gerror_handler(int irq, void *dev)
        if (active & GERROR_MSI_GERROR_ABT_ERR)
                dev_warn(smmu->dev, "GERROR MSI write aborted\n");
 
-       if (active & GERROR_MSI_PRIQ_ABT_ERR) {
+       if (active & GERROR_MSI_PRIQ_ABT_ERR)
                dev_warn(smmu->dev, "PRIQ MSI write aborted\n");
-               arm_smmu_priq_handler(irq, smmu->dev);
-       }
 
-       if (active & GERROR_MSI_EVTQ_ABT_ERR) {
+       if (active & GERROR_MSI_EVTQ_ABT_ERR)
                dev_warn(smmu->dev, "EVTQ MSI write aborted\n");
-               arm_smmu_evtq_handler(irq, smmu->dev);
-       }
 
        if (active & GERROR_MSI_CMDQ_ABT_ERR) {
                dev_warn(smmu->dev, "CMDQ MSI write aborted\n");
@@ -2232,10 +2207,10 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
        /* Request interrupt lines */
        irq = smmu->evtq.q.irq;
        if (irq) {
-               ret = devm_request_threaded_irq(smmu->dev, irq,
-                                               arm_smmu_evtq_handler,
+               ret = devm_request_threaded_irq(smmu->dev, irq, NULL,
                                                arm_smmu_evtq_thread,
-                                               0, "arm-smmu-v3-evtq", smmu);
+                                               IRQF_ONESHOT,
+                                               "arm-smmu-v3-evtq", smmu);
                if (ret < 0)
                        dev_warn(smmu->dev, "failed to enable evtq irq\n");
        }
@@ -2260,10 +2235,10 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
        if (smmu->features & ARM_SMMU_FEAT_PRI) {
                irq = smmu->priq.q.irq;
                if (irq) {
-                       ret = devm_request_threaded_irq(smmu->dev, irq,
-                                                       arm_smmu_priq_handler,
+                       ret = devm_request_threaded_irq(smmu->dev, irq, NULL,
                                                        arm_smmu_priq_thread,
-                                                       0, "arm-smmu-v3-priq",
+                                                       IRQF_ONESHOT,
+                                                       "arm-smmu-v3-priq",
                                                        smmu);
                        if (ret < 0)
                                dev_warn(smmu->dev,