RDMA/efa: Fix PBL chunk length computation

On register MR, when creating the PBL, if it's an indirect PBL we create
a chunk list to hold the PBL pages pointers. Each chunk is 4KB in size
and can hold 510 addresses (EFA_PTRS_PER_CHUNK) and has a 12-byte
control buffer at the end of it holding the next chunk's pointer and its
length.

If the PBL number of pages is a multiple of EFA_PTRS_PER_CHUNK, the
calculated last chunk length is wrongly computed as 0, even though that
chunk is fully populated with 510 real page pointers. This wrong length
is used both to DMA map the chunk and is propagated to the device,
causing the device to see the chunk as empty and reject the memory
registration.

Fix the calculation so it will be performed only if the number of pages
isn't a multiple of EFA_PTRS_PER_CHUNK, if it is, its already handled in
the above loop correctly.
Also prevent out-of-bounds reach in the chunks array in such scenario.

Fixes: 40909f664d ("RDMA/efa: Add EFA verbs implementation")
Reviewed-by: Firas Jahjah <firasj@amazon.com>
Reviewed-by: Michael Margolin <mrgolin@amazon.com>
Signed-off-by: Yonatan Nachum <ynachum@amazon.com>
Link: https://patch.msgid.link/20260727090255.1175120-1-ynachum@amazon.com
Signed-off-by: Leon Romanovsky <leon@kernel.org>
This commit is contained in:
Yonatan Nachum 2026-07-27 09:02:55 +00:00 committed by Leon Romanovsky
parent 51f2c8d2c9
commit 229b42d745

View File

@ -1362,9 +1362,11 @@ static int pbl_chunk_list_create(struct efa_dev *dev, struct pbl_context *pbl)
chunk_list->chunks[i].length = EFA_CHUNK_USED_SIZE;
}
chunk_list->chunks[chunk_list_size - 1].length =
((page_cnt % EFA_PTRS_PER_CHUNK) * EFA_CHUNK_PAYLOAD_PTR_SIZE) +
EFA_CHUNK_PTR_SIZE;
if (page_cnt % EFA_PTRS_PER_CHUNK != 0)
chunk_list->chunks[chunk_list_size - 1].length =
((page_cnt % EFA_PTRS_PER_CHUNK) * EFA_CHUNK_PAYLOAD_PTR_SIZE) +
EFA_CHUNK_PTR_SIZE;
/* fill the dma addresses of sg list pages to chunks: */
chunk_idx = 0;
@ -1376,9 +1378,12 @@ static int pbl_chunk_list_create(struct efa_dev *dev, struct pbl_context *pbl)
rdma_block_iter_dma_address(&biter);
if (payload_idx == EFA_PTRS_PER_CHUNK) {
chunk_idx++;
cur_chunk_buf = chunk_list->chunks[chunk_idx].buf;
payload_idx = 0;
chunk_idx++;
if (chunk_idx >= chunk_list_size)
break;
cur_chunk_buf = chunk_list->chunks[chunk_idx].buf;
}
}