hw/nvme: fix pin-based interrupt behavior (again)

Jakub noticed[1] that, when using pin-based interrupts, the device will
unconditionally deasssert when any CQEs are acknowledged. However, the
pin should not be deasserted if other completion queues still holds
unacknowledged CQEs.

The bug is an artifact of commit ca247d3509 ("hw/block/nvme: fix
pin-based interrupt behavior") which fixed one bug but introduced
another. This is the third time someone tries to fix pin-based
interrupts (see commit 5e9aa92eb1 ("hw/block: Fix pin-based interrupt
behaviour of NVMe"))...

Third time's the charm, so fix it, again, by keeping track of how many
CQs have unacknowledged CQEs and only deassert when all are cleared.

  [1]: <20210610114624.304681-1-jakub.jermar@kernkonzept.com>

Cc: qemu-stable@nongnu.org
Fixes: ca247d3509 ("hw/block/nvme: fix pin-based interrupt behavior")
Reported-by: Jakub Jermář <jakub.jermar@kernkonzept.com>
Signed-off-by: Klaus Jensen <k.jensen@samsung.com>
Reviewed-by: Keith Busch <kbusch@kernel.org>
This commit is contained in:
Klaus Jensen 2021-06-17 20:55:42 +02:00
parent 2b02aabc9d
commit 83d7ed5c57
2 changed files with 18 additions and 1 deletions

View File

@ -473,7 +473,9 @@ static void nvme_irq_deassert(NvmeCtrl *n, NvmeCQueue *cq)
return; return;
} else { } else {
assert(cq->vector < 32); assert(cq->vector < 32);
n->irq_status &= ~(1 << cq->vector); if (!n->cq_pending) {
n->irq_status &= ~(1 << cq->vector);
}
nvme_irq_check(n); nvme_irq_check(n);
} }
} }
@ -1253,6 +1255,7 @@ static void nvme_post_cqes(void *opaque)
NvmeCQueue *cq = opaque; NvmeCQueue *cq = opaque;
NvmeCtrl *n = cq->ctrl; NvmeCtrl *n = cq->ctrl;
NvmeRequest *req, *next; NvmeRequest *req, *next;
bool pending = cq->head != cq->tail;
int ret; int ret;
QTAILQ_FOREACH_SAFE(req, &cq->req_list, entry, next) { QTAILQ_FOREACH_SAFE(req, &cq->req_list, entry, next) {
@ -1282,6 +1285,10 @@ static void nvme_post_cqes(void *opaque)
QTAILQ_INSERT_TAIL(&sq->req_list, req, entry); QTAILQ_INSERT_TAIL(&sq->req_list, req, entry);
} }
if (cq->tail != cq->head) { if (cq->tail != cq->head) {
if (cq->irq_enabled && !pending) {
n->cq_pending++;
}
nvme_irq_assert(n, cq); nvme_irq_assert(n, cq);
} }
} }
@ -4297,6 +4304,11 @@ static uint16_t nvme_del_cq(NvmeCtrl *n, NvmeRequest *req)
trace_pci_nvme_err_invalid_del_cq_notempty(qid); trace_pci_nvme_err_invalid_del_cq_notempty(qid);
return NVME_INVALID_QUEUE_DEL; return NVME_INVALID_QUEUE_DEL;
} }
if (cq->irq_enabled && cq->tail != cq->head) {
n->cq_pending--;
}
nvme_irq_deassert(n, cq); nvme_irq_deassert(n, cq);
trace_pci_nvme_del_cq(qid); trace_pci_nvme_del_cq(qid);
nvme_free_cq(cq, n); nvme_free_cq(cq, n);
@ -6039,6 +6051,10 @@ static void nvme_process_db(NvmeCtrl *n, hwaddr addr, int val)
} }
if (cq->tail == cq->head) { if (cq->tail == cq->head) {
if (cq->irq_enabled) {
n->cq_pending--;
}
nvme_irq_deassert(n, cq); nvme_irq_deassert(n, cq);
} }
} else { } else {

View File

@ -410,6 +410,7 @@ typedef struct NvmeCtrl {
uint32_t max_q_ents; uint32_t max_q_ents;
uint8_t outstanding_aers; uint8_t outstanding_aers;
uint32_t irq_status; uint32_t irq_status;
int cq_pending;
uint64_t host_timestamp; /* Timestamp sent by the host */ uint64_t host_timestamp; /* Timestamp sent by the host */
uint64_t timestamp_set_qemu_clock_ms; /* QEMU clock time */ uint64_t timestamp_set_qemu_clock_ms; /* QEMU clock time */
uint64_t starttime_ms; uint64_t starttime_ms;