Thread (62 messages) 62 messages, 5 authors, 2023-08-21

Re: [PATCH v12 07/26] nvme-tcp: Add DDP offload control path

From: Sagi Grimberg <sagi@grimberg.me>
Date: 2023-08-14 18:55:11
Also in: linux-nvme

quoted
quoted
+static inline bool is_netdev_ulp_offload_active(struct net_device *netdev,
+                                             struct nvme_tcp_queue *queue)
+{
+     if (!netdev || !queue)
+             return false;
Is it reasonable to be called here with !netdev or !queue ?
The check is needed only for the IO queue case but we can move it
earlier in nvme_tcp_start_queue().
I still don't understand even on io queues how this can happen.
quoted
quoted
+
+     /* If we cannot query the netdev limitations, do not offload */
+     if (!nvme_tcp_ddp_query_limits(netdev, queue))
+             return false;
+
+     /* If netdev supports nvme-tcp ddp offload, we can offload */
+     if (test_bit(ULP_DDP_C_NVME_TCP_BIT, netdev->ulp_ddp_caps.active))
+             return true;
This should be coming from the API itself, have the limits query
api fail if this is off.
We can move the function to the ULP DDP layer.
quoted
btw, what is the active thing? is this driven from ethtool enable?
what happens if the user disables it while there is a ulp using it?
The active bits are indeed driven by ethtool according to the design
Jakub suggested.
The nvme-tcp connection will have to be reconnected to see the effect of
changing the bit.
It should move inside the api as well. Don't want to care about it in
nvme.
quoted
quoted
+
+     return false;
This can be folded to the above function.
We won't be able to check for TLS in a common wrapper. We think this
should be kept.
Why? any tcp ddp need to be able to support tls. Nothing specific to
nvme here.
quoted
quoted
+static int nvme_tcp_offload_socket(struct nvme_tcp_queue *queue)
+{
+     struct net_device *netdev = queue->ctrl->offloading_netdev;
+     struct ulp_ddp_config config = {.type = ULP_DDP_NVME};
+     int ret;
+
+     config.nvmeotcp.pfv = NVME_TCP_PFV_1_0;
Question, what happens if the pfv changes, is the ddp guaranteed to
work?
The existing HW supports only NVME_TCP_PFV_1_0.
Once a new version will be used, the device driver should fail the
sk_add().
OK.
quoted
quoted
+/* In presence of packet drops or network packet reordering, the device may lose
+ * synchronization between the TCP stream and the L5P framing, and require a
+ * resync with the kernel's TCP stack.
+ *
+ * - NIC HW identifies a PDU header at some TCP sequence number,
+ *   and asks NVMe-TCP to confirm it.
+ * - When NVMe-TCP observes the requested TCP sequence, it will compare
+ *   it with the PDU header TCP sequence, and report the result to the
+ *   NIC driver
+ */
+static void nvme_tcp_resync_response(struct nvme_tcp_queue *queue,
+                                  struct sk_buff *skb, unsigned int offset)
+{
+     u64 pdu_seq = TCP_SKB_CB(skb)->seq + offset - queue->pdu_offset;
+     struct net_device *netdev = queue->ctrl->offloading_netdev;
+     u64 pdu_val = (pdu_seq << 32) | ULP_DDP_RESYNC_PENDING;
+     u64 resync_val;
+     u32 resync_seq;
+
+     resync_val = atomic64_read(&queue->resync_req);
+     /* Lower 32 bit flags. Check validity of the request */
+     if ((resync_val & ULP_DDP_RESYNC_PENDING) == 0)
+             return;
+
+     /*
+      * Obtain and check requested sequence number: is this PDU header
+      * before the request?
+      */
+     resync_seq = resync_val >> 32;
+     if (before(pdu_seq, resync_seq))
+             return;
+
+     /*
+      * The atomic operation guarantees that we don't miss any NIC driver
+      * resync requests submitted after the above checks.
+      */
+     if (atomic64_cmpxchg(&queue->resync_req, pdu_val,
+                          pdu_val & ~ULP_DDP_RESYNC_PENDING) !=
+                          atomic64_read(&queue->resync_req))
+             netdev->netdev_ops->ulp_ddp_ops->resync(netdev,
+                                                     queue->sock->sk,
+                                                     pdu_seq);
Who else is doing an atomic on this value? and what happens
if the cmpxchg fails?
The driver thread can set queue->resync_req concurrently in patch
"net/mlx5e: NVMEoTCP, data-path for DDP+DDGST offload" in function
nvmeotcp_update_resync().

If the cmpxchg fails it means a new resync request was triggered by the
HW, the old request will be dropped and the new one will be processed by
a later PDU.
So resync_req is actually the current tcp sequence number or something?
The name resync_req is very confusing.
quoted
quoted
+}
+
+static bool nvme_tcp_resync_request(struct sock *sk, u32 seq, u32 flags)
+{
+     struct nvme_tcp_queue *queue = sk->sk_user_data;
+
+     /*
+      * "seq" (TCP seq number) is what the HW assumes is the
+      * beginning of a PDU.  The nvme-tcp layer needs to store the
+      * number along with the "flags" (ULP_DDP_RESYNC_PENDING) to
+      * indicate that a request is pending.
+      */
+     atomic64_set(&queue->resync_req, (((uint64_t)seq << 32) | flags));
Question, is this coming from multiple contexts? what contexts are
competing here that make it an atomic operation? It is unclear what is
going on here tbh.
The driver could get a resync request and set queue->resync_req
concurrently while processing HW CQEs as you can see in patch
"net/mlx5e: NVMEoTCP, data-path for DDP+DDGST offload" in function
nvmeotcp_update_resync().

The resync flow is:

      nvme-tcp                           mlx5                     hw
         |                                |                        |
         |                                |                      sends CQE with
         |                                |                      resync request
         |                                | <----------------------'
         |                         nvmeotcp_update_resync()
   nvme_tcp_resync_request() <-----------'|
   we store the request

Later, while receiving PDUs we check for pending requests.
If there is one, we send call nvme_tcp_resync_response() which calls
into mlx5 to send the response to the HW.
...
quoted
quoted
+                     ret = nvme_tcp_offload_socket(queue);
+                     if (ret) {
+                             dev_info(nctrl->device,
+                                      "failed to setup offload on queue %d ret=%d\n",
+                                      idx, ret);
+                     }
+             }
+     } else {
               ret = nvmf_connect_admin_queue(nctrl);
+             if (ret)
+                     goto err;

-     if (!ret) {
-             set_bit(NVME_TCP_Q_LIVE, &queue->flags);
-     } else {
-             if (test_bit(NVME_TCP_Q_ALLOCATED, &queue->flags))
-                     __nvme_tcp_stop_queue(queue);
-             dev_err(nctrl->device,
-                     "failed to connect queue: %d ret=%d\n", idx, ret);
+             netdev = get_netdev_for_sock(queue->sock->sk);
Is there any chance that this is a different netdev than what is
already recorded? doesn't make sense to me.
The idea is that we are first starting the admin queue, which looks up
the netdev associated with the socket and stored in the queue. Later,
when the IO queues are started, we use the recorded netdev.

In cases of bonding or vlan, a netdev can have lower device links, which
get_netdev_for_sock() will look up.
I think the code should in high level do:
	if (idx) {
		ret = nvmf_connect_io_queue(nctrl, idx);
		if (ret)
			goto err;
		if (nvme_tcp_ddp_query_limits(queue))
			nvme_tcp_offload_socket(queue);

	} else {
		ret = nvmf_connect_admin_queue(nctrl);
		if (ret)
			goto err;
		ctrl->ddp_netdev = get_netdev_for_sock(queue->sock->sk);
		if (nvme_tcp_ddp_query_limits(queue))
			nvme_tcp_offload_apply_limits(queue);
	}

ctrl->ddp_netdev should be cleared and put when the admin queue
is stopped/freed, similar to how async_req is handled.
quoted
quoted
+                     goto done;
+             }
+             if (is_netdev_ulp_offload_active(netdev, queue))
+                     nvme_tcp_offload_apply_limits(queue, netdev);
+             /*
+              * release the device as no offload context is
+              * established yet.
+              */
+             dev_put(netdev);
the put is unclear, what does it pair with? the get_netdev_for_sock?
Yes, get_netdev_for_sock() takes a reference, which we don't need at
that point so we put it.
Well, you store a pointer to it, what happens if it goes away while
the controller is being set up?
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help