[PATCH] nvme-tcp: fix usage of page_frag_cache
Daniel Wagner <[email protected]>
| Newsgroups | org.infradead.lists.linux-nvme,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Dmitry Bogdanov <[email protected]> nvme uses page_frag_cache to preallocate PDU for each preallocated request of block device. Block devices are created in parallel threads, consequently page_frag_cache is used in not thread-safe manner. That leads to incorrect refcounting of backstore pages and premature free. That can be catched by !sendpage_ok inside network stack: WARNING: CPU: 7 PID: 467 at ../net/core/skbuff.c:6931 skb_splice_from_iter+0xfa/0x310. tcp_sendmsg_locked+0x782/0xce0 tcp_sendmsg+0x27/0x40 sock_sendmsg+0x8b/0xa0 nvme_tcp_try_send_cmd_pdu+0x149/0x2a0 Then random panic may occur. Fix that by serializing the usage of page_frag_cache. Fixes: 4e893ca81170 ("nvme_core: scan namespaces asynchronously") Signed-off-by: Dmitry Bogdanov <[email protected]> Signed-off-by: Daniel Wagner <[email protected]> --- If the target exposes many namespaces (>1000) and the host has many CPUs (>80), it is trivial to trigger the allocation race condition in nvme_tcp_init_request which results in the logs below: WARNING: CPU: XX PID: XXXX at net/core/skbuff.c:XXXX skb_splice_from_iter+0xfa/0x310 nvme nvme22: failed to send request -5 nvme nvme23: failed to send request -5 nvme nvme24: failed to send request -5 [... repeating for all controllers, thousands of times ...] RDX: 00000000000005e8 RSI: 0000000000000010 RDI: 0000000000000000 RBP: 000000000000004c R08: ff57ae906085bd78 R09: 000000000000004c R10: 000000000000004c R11: 00000000000003ef R12: 0000000000000000 R13: ff4f98b626e30c00 R14: ff57ae906085bbf0 R15: ff4f98b626e30c00 FS: 0000000000000000(0000) GS:ff4f98e2abc00000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f0f167ff000 CR3: 0000000382a18003 CR4: 0000000000f71ee0 Call Trace: <TASK> ? __warn+0x86/0x150 ? skb_splice_from_iter+0xfa/0x310 ? report_bug+0xfb/0x1e0 ? handle_bug+0x44/0x80 ? exc_invalid_op+0x13/0x60 ? asm_exc_invalid_op+0x16/0x20 ? skb_splice_from_iter+0xfa/0x310 ? __alloc_skb+0xd5/0x190 tcp_sendmsg_locked+0x782/0xcd0 tcp_sendmsg+0x27/0x40 sock_sendmsg+0x98/0xc0 nvme_tcp_try_send_cmd_pdu+0x149/0x2a0 [nvme_tcp] nvme_tcp_try_send+0xbb/0x2c0 [nvme_tcp] nvme_tcp_io_work+0x37/0xb0 [nvme_tcp] process_one_work+0x223/0x460 ? __pfx_worker_thread+0x10/0x10 worker_thread+0x2a/0x3b0 ? __pfx_worker_thread+0x10/0x10 kthread+0xdf/0x120 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x29/0x50 </TASK> The above excerpt is from our customers log. I was able to reproduce this on the latest nvme-7.3 branch. Dmitry's patch [1] looks correct to me. All the allocation happens in the slow path and the context can sleep, thus a mutex seems to be a good choice. [1] https://lore.kernel.org/linux-nvme/[email protected]/ --- drivers/nvme/host/tcp.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index ba5c7b3e2a7c..dd40798bc248 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -108,6 +108,7 @@ struct nvme_tcp_queue { struct mutex queue_lock; struct mutex send_mutex; + struct mutex pf_cache_lock; struct llist_head req_list; struct list_head send_list; @@ -550,9 +551,11 @@ static int nvme_tcp_init_request(struct blk_mq_tag_set *set, struct nvme_tcp_queue *queue = &ctrl->queues[queue_idx]; u8 hdgst = nvme_tcp_hdgst_len(queue); + mutex_lock(&queue->pf_cache_lock); req->pdu = page_frag_alloc(&queue->pf_cache, sizeof(struct nvme_tcp_cmd_pdu) + hdgst, GFP_KERNEL | __GFP_ZERO); + mutex_unlock(&queue->pf_cache_lock); if (!req->pdu) return -ENOMEM; @@ -1417,9 +1420,11 @@ static int nvme_tcp_alloc_async_req(struct nvme_tcp_ctrl *ctrl) struct nvme_tcp_request *async = &ctrl->async_req; u8 hdgst = nvme_tcp_hdgst_len(queue); + mutex_lock(&queue->pf_cache_lock); async->pdu = page_frag_alloc(&queue->pf_cache, sizeof(struct nvme_tcp_cmd_pdu) + hdgst, GFP_KERNEL | __GFP_ZERO); + mutex_unlock(&queue->pf_cache_lock); if (!async->pdu) return -ENOMEM; @@ -1461,6 +1466,7 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid) kfree(queue->pdu); mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); + mutex_destroy(&queue->pf_cache_lock); #ifdef CONFIG_DEBUG_LOCK_ALLOC lockdep_unregister_key(&queue->nvme_tcp_sk_key); @@ -1788,6 +1794,7 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, INIT_LIST_HEAD(&queue->send_list); mutex_init(&queue->send_mutex); INIT_WORK(&queue->io_work, nvme_tcp_io_work); + mutex_init(&queue->pf_cache_lock); if (qid > 0) queue->cmnd_capsule_len = nctrl->ioccsz * 16; @@ -1928,6 +1935,7 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, err_destroy_mutex: mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); + mutex_destroy(&queue->pf_cache_lock); return ret; } --- base-commit: 29261f8bb41662f2a660c479e5cf592942b53f78 change-id: 20260716-nvme-tcp-page_fraq_cache-d04f6226f927 Best regards, -- Daniel Wagner <[email protected]>