summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRanjan Kumar <ranjan.kumar@broadcom.com>2026-09-16 13:57:01 +0530
committerMartin K. Petersen (Oracle) <mkp@kernel.org>2026-09-16 22:14:22 -0400
commitea8dfd05a0fcabe3ea8bb8a3b9e45b5170a210a2 (patch)
tree813252028b546c1e6641b347de21e3999ebdc316
parent7342b9f0e3065c086605f075a204972d270b267b (diff)
downloadlinux-next-ea8dfd05a0fcabe3ea8bb8a3b9e45b5170a210a2.tar.gz
linux-next-ea8dfd05a0fcabe3ea8bb8a3b9e45b5170a210a2.zip
scsi: mpi3mr: Fix memory leak on operational queue creation failure
When operational queue creation fails after one or more queues have been created, the error path frees the queue information arrays but does not release the DMA memory segments associated with the created queues, resulting in a memory leak. Fix this by ensuring that partially allocated segments are freed immediately if a queue fails to create. Additionally, resolve the following issues in the queue segment free/alloc paths: 1. Clear mrioc->intr_info[].op_reply_q with WRITE_ONCE() and follow it with synchronize_irq() before freeing segments, and have the ISR paths read it once via READ_ONCE() into a local, to close a race where the ISR could use the pointer while it is being freed. 2. Free q_segment_list before checking q_segments in both free functions, since a kzalloc_objs() failure on q_segments left q_segment_list leaked via the early return. 3. The threaded poll handler returned without re-enabling the interrupt when the reply queue was already gone, leaving that interrupt line permanently masked. It now re-enables it before returning. 4. Two other callers read the same pointer without a NULL check, which could now be reached with a NULL value. Add the check at the single point they both call through, and read the pointer consistently with the writer above. Reported-by: Sashiko <sashiko-bot@kernel.org> Closes: https://sashiko.dev/#/patchset/20260626114109.43685-1-ranjan.kumar@broadcom.com?part=6 Closes: https://sashiko.dev/#/patchset/20260708183305.244485-1-ranjan.kumar@broadcom.com?part=6 Closes: https://sashiko.dev/#/patchset/20260724102505.115136-1-ranjan.kumar@broadcom.com?part=6 Closes: https://sashiko.dev/#/patchset/20260805110634.346670-1-ranjan.kumar@broadcom.com?part=6 Co-developed-by: Chandrakanth Patil <chandrakanth.patil@broadcom.com> Signed-off-by: Chandrakanth Patil <chandrakanth.patil@broadcom.com> Signed-off-by: Ranjan Kumar <ranjan.kumar@broadcom.com> Link: https://patch.msgid.link/20260916082705.44712-7-ranjan.kumar@broadcom.com Signed-off-by: Martin K. Petersen (Oracle) <mkp@kernel.org>
-rw-r--r--drivers/scsi/mpi3mr/mpi3mr_fw.c91
-rw-r--r--drivers/scsi/mpi3mr/mpi3mr_os.c2
2 files changed, 68 insertions, 25 deletions
diff --git a/drivers/scsi/mpi3mr/mpi3mr_fw.c b/drivers/scsi/mpi3mr/mpi3mr_fw.c
index 51ddcc1008c2..d63870b087bc 100644
--- a/drivers/scsi/mpi3mr/mpi3mr_fw.c
+++ b/drivers/scsi/mpi3mr/mpi3mr_fw.c
@@ -582,6 +582,9 @@ int mpi3mr_process_op_reply_q(struct mpi3mr_ioc *mrioc,
struct mpi3_default_reply_descriptor *reply_desc;
u16 req_q_idx = 0, reply_qidx, threshold_comps = 0;
+ if (!op_reply_q)
+ return 0;
+
reply_qidx = op_reply_q->qid - 1;
if (!atomic_add_unless(&op_reply_q->in_use, 1, 1))
@@ -714,6 +717,7 @@ static irqreturn_t mpi3mr_isr_primary(int irq, void *privdata)
{
struct mpi3mr_intr_info *intr_info = privdata;
struct mpi3mr_ioc *mrioc;
+ struct op_reply_qinfo *op_reply_q;
u16 midx;
u32 num_admin_replies = 0, num_op_reply = 0;
@@ -729,9 +733,9 @@ static irqreturn_t mpi3mr_isr_primary(int irq, void *privdata)
if (!midx)
num_admin_replies = mpi3mr_process_admin_reply_q(mrioc);
- if (intr_info->op_reply_q)
- num_op_reply = mpi3mr_process_op_reply_q(mrioc,
- intr_info->op_reply_q);
+ op_reply_q = READ_ONCE(intr_info->op_reply_q);
+ if (op_reply_q)
+ num_op_reply = mpi3mr_process_op_reply_q(mrioc, op_reply_q);
if (num_admin_replies || num_op_reply)
return IRQ_HANDLED;
@@ -744,6 +748,7 @@ static irqreturn_t mpi3mr_isr_primary(int irq, void *privdata)
static irqreturn_t mpi3mr_isr(int irq, void *privdata)
{
struct mpi3mr_intr_info *intr_info = privdata;
+ struct op_reply_qinfo *op_reply_q;
int ret;
if (!intr_info)
@@ -756,11 +761,12 @@ static irqreturn_t mpi3mr_isr(int irq, void *privdata)
* If more IOs are expected, schedule IRQ polling thread.
* Otherwise exit from ISR.
*/
- if ((threaded_isr_poll == false) || !intr_info->op_reply_q)
+ op_reply_q = READ_ONCE(intr_info->op_reply_q);
+ if ((threaded_isr_poll == false) || !op_reply_q)
return ret;
- if (!intr_info->op_reply_q->enable_irq_poll ||
- !atomic_read(&intr_info->op_reply_q->pend_ios))
+ if (!op_reply_q->enable_irq_poll ||
+ !atomic_read(&op_reply_q->pend_ios))
return ret;
disable_irq_nosync(intr_info->os_irq);
@@ -782,12 +788,19 @@ static irqreturn_t mpi3mr_isr_poll(int irq, void *privdata)
{
struct mpi3mr_intr_info *intr_info = privdata;
struct mpi3mr_ioc *mrioc;
+ struct op_reply_qinfo *op_reply_q;
u16 midx;
u32 num_op_reply = 0;
- if (!intr_info || !intr_info->op_reply_q)
+ if (!intr_info)
return IRQ_NONE;
+ op_reply_q = READ_ONCE(intr_info->op_reply_q);
+ if (!op_reply_q) {
+ enable_irq(intr_info->os_irq);
+ return IRQ_HANDLED;
+ }
+
mrioc = intr_info->mrioc;
midx = intr_info->msix_index;
@@ -796,20 +809,23 @@ static irqreturn_t mpi3mr_isr_poll(int irq, void *privdata)
if (!mrioc->intr_enabled || mrioc->unrecoverable)
break;
+ op_reply_q = READ_ONCE(intr_info->op_reply_q);
+ if (!op_reply_q)
+ break;
+
if (!midx)
mpi3mr_process_admin_reply_q(mrioc);
- if (intr_info->op_reply_q)
- num_op_reply +=
- mpi3mr_process_op_reply_q(mrioc,
- intr_info->op_reply_q);
- if (!atomic_read(&intr_info->op_reply_q->pend_ios))
+ num_op_reply +=
+ mpi3mr_process_op_reply_q(mrioc, op_reply_q);
+ if (!atomic_read(&op_reply_q->pend_ios))
break;
usleep_range(MPI3MR_IRQ_POLL_SLEEP, 10 * MPI3MR_IRQ_POLL_SLEEP);
} while (num_op_reply < mrioc->max_host_ios);
- intr_info->op_reply_q->enable_irq_poll = false;
+ if (op_reply_q)
+ op_reply_q->enable_irq_poll = false;
enable_irq(intr_info->os_irq);
return IRQ_HANDLED;
@@ -1993,10 +2009,6 @@ static void mpi3mr_free_op_req_q_segments(struct mpi3mr_ioc *mrioc, u16 q_idx)
int size;
struct segments *segments;
- segments = mrioc->req_qinfo[q_idx].q_segments;
- if (!segments)
- return;
-
if (mrioc->enable_segqueue) {
size = MPI3MR_OP_REQ_Q_SEG_SIZE;
if (mrioc->req_qinfo[q_idx].q_segment_list) {
@@ -2010,6 +2022,10 @@ static void mpi3mr_free_op_req_q_segments(struct mpi3mr_ioc *mrioc, u16 q_idx)
size = mrioc->req_qinfo[q_idx].segment_qd *
mrioc->facts.op_req_sz;
+ segments = mrioc->req_qinfo[q_idx].q_segments;
+ if (!segments)
+ return;
+
for (j = 0; j < mrioc->req_qinfo[q_idx].num_segments; j++) {
if (!segments[j].segment)
continue;
@@ -2036,10 +2052,17 @@ static void mpi3mr_free_op_reply_q_segments(struct mpi3mr_ioc *mrioc, u16 q_idx)
u16 j;
int size;
struct segments *segments;
+ u16 midx = REPLY_QUEUE_IDX_TO_MSIX_IDX(q_idx, mrioc->op_reply_q_offset);
- segments = mrioc->op_reply_qinfo[q_idx].q_segments;
- if (!segments)
- return;
+ /*
+ * Stop the ISR/poll thread from picking up this queue before its
+ * segments are freed below, and wait for any in-flight handler
+ * that already has the old pointer to finish using it.
+ */
+ if (midx < mrioc->intr_info_count) {
+ WRITE_ONCE(mrioc->intr_info[midx].op_reply_q, NULL);
+ synchronize_irq(pci_irq_vector(mrioc->pdev, midx));
+ }
if (mrioc->enable_segqueue) {
size = MPI3MR_OP_REP_Q_SEG_SIZE;
@@ -2054,6 +2077,10 @@ static void mpi3mr_free_op_reply_q_segments(struct mpi3mr_ioc *mrioc, u16 q_idx)
size = mrioc->op_reply_qinfo[q_idx].segment_qd *
mrioc->op_reply_desc_sz;
+ segments = mrioc->op_reply_qinfo[q_idx].q_segments;
+ if (!segments)
+ return;
+
for (j = 0; j < mrioc->op_reply_qinfo[q_idx].num_segments; j++) {
if (!segments[j].segment)
continue;
@@ -2520,7 +2547,7 @@ out:
static int mpi3mr_create_op_queues(struct mpi3mr_ioc *mrioc)
{
int retval = 0;
- u16 num_queues = 0, i = 0, msix_count_op_q = 1;
+ u16 num_queues = 0, i = 0, j = 0, msix_count_op_q = 1;
u32 ioc_status;
enum mpi3mr_iocstate ioc_state;
@@ -2572,6 +2599,13 @@ static int mpi3mr_create_op_queues(struct mpi3mr_ioc *mrioc)
}
}
+ if (i < num_queues) {
+ for (j = i; j < num_queues; j++) {
+ mpi3mr_free_op_req_q_segments(mrioc, j);
+ mpi3mr_free_op_reply_q_segments(mrioc, j);
+ }
+ }
+
if (i == 0) {
/* Not even one queue is created successfully*/
retval = -1;
@@ -2593,11 +2627,19 @@ static int mpi3mr_create_op_queues(struct mpi3mr_ioc *mrioc)
return retval;
out_failed:
- kfree(mrioc->req_qinfo);
- mrioc->req_qinfo = NULL;
+ if (mrioc->req_qinfo) {
+ for (j = 0; j < i; j++) {
+ mpi3mr_free_op_req_q_segments(mrioc, j);
+ mpi3mr_free_op_reply_q_segments(mrioc, j);
+ }
+ kfree(mrioc->req_qinfo);
+ mrioc->req_qinfo = NULL;
+ }
+ mrioc->num_op_req_q = 0;
kfree(mrioc->op_reply_qinfo);
mrioc->op_reply_qinfo = NULL;
+ mrioc->num_op_reply_q = 0;
return retval;
}
@@ -2641,7 +2683,8 @@ int mpi3mr_op_request_post(struct mpi3mr_ioc *mrioc,
if (mpi3mr_check_req_qfull(op_req_q)) {
midx = REPLY_QUEUE_IDX_TO_MSIX_IDX(
reply_qidx, mrioc->op_reply_q_offset);
- mpi3mr_process_op_reply_q(mrioc, mrioc->intr_info[midx].op_reply_q);
+ mpi3mr_process_op_reply_q(mrioc,
+ READ_ONCE(mrioc->intr_info[midx].op_reply_q));
if (mpi3mr_check_req_qfull(op_req_q)) {
diff --git a/drivers/scsi/mpi3mr/mpi3mr_os.c b/drivers/scsi/mpi3mr/mpi3mr_os.c
index 7e59773c0276..3412e1e0e8ce 100644
--- a/drivers/scsi/mpi3mr/mpi3mr_os.c
+++ b/drivers/scsi/mpi3mr/mpi3mr_os.c
@@ -3993,7 +3993,7 @@ inline void mpi3mr_poll_pend_io_completions(struct mpi3mr_ioc *mrioc)
for (i = mrioc->op_reply_q_offset; i < num_of_reply_queues; i++)
mpi3mr_process_op_reply_q(mrioc,
- mrioc->intr_info[i].op_reply_q);
+ READ_ONCE(mrioc->intr_info[i].op_reply_q));
}
/**