← Documents Documentation/networking/devmem.rst GitHub 원문 ↗

Linux 6.18.37 · Networking

Device Memory TCP

TCP payload를 dma-buf로 직접 송수신하는 RX·TX API, fragment 수명 주기와 제약을 설명합니다.

Source pathDocumentation/networking/devmem.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

devmem.rst:1-419

Device Memory TCP는 header만 호스트 TCP/IP 스택에서 처리하고 payload는 NIC와 dma-buf 사이에서 직접 이동시켜 호스트 메모리와 PCIe 대역폭을 절약합니다. RX에서는 queue와 dma-buf 바인딩, cmsg 해석, fragment 토큰 반환이 핵심이고, TX에서는 zero-copy 설정, dma-buf 오프셋 iovec, error queue 완료 통지가 핵심입니다. 커널이 payload를 읽을 수 없어 loopback·소프트웨어 checksum·TCP dump·BPF에 제약이 생깁니다.

devmem TCP 전체 경로
RXheader splitpayload → dma-bufSCM_DEVMEM_*토큰 반환
TXdma-buf bindSCM_DEVMEM_DMABUFMSG_ZEROCOPYMSG_ERRQUEUE 완료

RX와 TX 모두 netlink로 dma-buf를 NIC에 먼저 바인딩한 뒤 소켓 cmsg로 데이터를 참조합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0
2
3 =================
4 Device Memory TCP
5 =================
6
7
8 Intro
9 =====
10
11 Device memory TCP (devmem TCP) enables receiving data directly into device
12 memory (dmabuf). The feature is currently implemented for TCP sockets.
13
14
15 Opportunity
16 -----------
17
18 A large number of data transfers have device memory as the source and/or
19 destination. Accelerators drastically increased the prevalence of such
20 transfers. Some examples include:
21
22 - Distributed training, where ML accelerators, such as GPUs on different hosts,
23 exchange data.
24
25 - Distributed raw block storage applications transfer large amounts of data with
26 remote SSDs. Much of this data does not require host processing.
27
28 Typically the Device-to-Device data transfers in the network are implemented as
29 the following low-level operations: Device-to-Host copy, Host-to-Host network
30 transfer, and Host-to-Device copy.
31
32 The flow involving host copies is suboptimal, especially for bulk data transfers,
33 and can put significant strains on system resources such as host memory
34 bandwidth and PCIe bandwidth.
35
36 Devmem TCP optimizes this use case by implementing socket APIs that enable
37 the user to receive incoming network packets directly into device memory.
38
39 Packet payloads go directly from the NIC to device memory.
40
41 Packet headers go to host memory and are processed by the TCP/IP stack
42 normally. The NIC must support header split to achieve this.
43
44 Advantages:
45
46 - Alleviate host memory bandwidth pressure, compared to existing
47 network-transfer + device-copy semantics.
48
49 - Alleviate PCIe bandwidth pressure, by limiting data transfer to the lowest
50 level of the PCIe tree, compared to the traditional path which sends data
51 through the root complex.
52
53
54 More Info
55 ---------
56
57 slides, video
58 https://netdevconf.org/0x17/sessions/talk/device-memory-tcp.html
59
60 patchset
61 [PATCH net-next v24 00/13] Device Memory TCP
62 https://lore.kernel.org/netdev/[email protected]/
63
64
65 RX Interface
66 ============
67
68
69 Example
70 -------
71
72 ./tools/testing/selftests/drivers/net/hw/ncdevmem:do_server shows an example of
73 setting up the RX path of this API.
74
75
76 NIC Setup
77 ---------
78
79 Header split, flow steering, & RSS are required features for devmem TCP.
80
81 Header split is used to split incoming packets into a header buffer in host
82 memory, and a payload buffer in device memory.
83
84 Flow steering & RSS are used to ensure that only flows targeting devmem land on
85 an RX queue bound to devmem.
86
87 Enable header split & flow steering::
88
89 # enable header split
90 ethtool -G eth1 tcp-data-split on
91
92
93 # enable flow steering
94 ethtool -K eth1 ntuple on
95
96 Configure RSS to steer all traffic away from the target RX queue (queue 15 in
97 this example)::
98
99 ethtool --set-rxfh-indir eth1 equal 15
100
101
102 The user must bind a dmabuf to any number of RX queues on a given NIC using
103 the netlink API::
104
105 /* Bind dmabuf to NIC RX queue 15 */
106 struct netdev_queue *queues;
107 queues = malloc(sizeof(*queues) * 1);
108
109 queues[0]._present.type = 1;
110 queues[0]._present.idx = 1;
111 queues[0].type = NETDEV_RX_QUEUE_TYPE_RX;
112 queues[0].idx = 15;
113
114 *ys = ynl_sock_create(&ynl_netdev_family, &yerr);
115
116 req = netdev_bind_rx_req_alloc();
117 netdev_bind_rx_req_set_ifindex(req, 1 /* ifindex */);
118 netdev_bind_rx_req_set_dmabuf_fd(req, dmabuf_fd);
119 __netdev_bind_rx_req_set_queues(req, queues, n_queue_index);
120
121 rsp = netdev_bind_rx(*ys, req);
122
123 dmabuf_id = rsp->dmabuf_id;
124
125
126 The netlink API returns a dmabuf_id: a unique ID that refers to this dmabuf
127 that has been bound.
128
129 The user can unbind the dmabuf from the netdevice by closing the netlink socket
130 that established the binding. We do this so that the binding is automatically
131 unbound even if the userspace process crashes.
132
133 Note that any reasonably well-behaved dmabuf from any exporter should work with
134 devmem TCP, even if the dmabuf is not actually backed by devmem. An example of
135 this is udmabuf, which wraps user memory (non-devmem) in a dmabuf.
136
137
138 Socket Setup
139 ------------
140
141 The socket must be flow steered to the dmabuf bound RX queue::
142
143 ethtool -N eth1 flow-type tcp4 ... queue 15
144
145
146 Receiving data
147 --------------
148
149 The user application must signal to the kernel that it is capable of receiving
150 devmem data by passing the MSG_SOCK_DEVMEM flag to recvmsg::
151
152 ret = recvmsg(fd, &msg, MSG_SOCK_DEVMEM);
153
154 Applications that do not specify the MSG_SOCK_DEVMEM flag will receive an EFAULT
155 on devmem data.
156
157 Devmem data is received directly into the dmabuf bound to the NIC in 'NIC
158 Setup', and the kernel signals such to the user via the SCM_DEVMEM_* cmsgs::
159
160 for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
161 if (cm->cmsg_level != SOL_SOCKET ||
162 (cm->cmsg_type != SCM_DEVMEM_DMABUF &&
163 cm->cmsg_type != SCM_DEVMEM_LINEAR))
164 continue;
165
166 dmabuf_cmsg = (struct dmabuf_cmsg *)CMSG_DATA(cm);
167
168 if (cm->cmsg_type == SCM_DEVMEM_DMABUF) {
169 /* Frag landed in dmabuf.
170 *
171 * dmabuf_cmsg->dmabuf_id is the dmabuf the
172 * frag landed on.
173 *
174 * dmabuf_cmsg->frag_offset is the offset into
175 * the dmabuf where the frag starts.
176 *
177 * dmabuf_cmsg->frag_size is the size of the
178 * frag.
179 *
180 * dmabuf_cmsg->frag_token is a token used to
181 * refer to this frag for later freeing.
182 */
183
184 struct dmabuf_token token;
185 token.token_start = dmabuf_cmsg->frag_token;
186 token.token_count = 1;
187 continue;
188 }
189
190 if (cm->cmsg_type == SCM_DEVMEM_LINEAR)
191 /* Frag landed in linear buffer.
192 *
193 * dmabuf_cmsg->frag_size is the size of the
194 * frag.
195 */
196 continue;
197
198 }
199
200 Applications may receive 2 cmsgs:
201
202 - SCM_DEVMEM_DMABUF: this indicates the fragment landed in the dmabuf indicated
203 by dmabuf_id.
204
205 - SCM_DEVMEM_LINEAR: this indicates the fragment landed in the linear buffer.
206 This typically happens when the NIC is unable to split the packet at the
207 header boundary, such that part (or all) of the payload landed in host
208 memory.
209
210 Applications may receive no SO_DEVMEM_* cmsgs. That indicates non-devmem,
211 regular TCP data that landed on an RX queue not bound to a dmabuf.
212
213
214 Freeing frags
215 -------------
216
217 Frags received via SCM_DEVMEM_DMABUF are pinned by the kernel while the user
218 processes the frag. The user must return the frag to the kernel via
219 SO_DEVMEM_DONTNEED::
220
221 ret = setsockopt(client_fd, SOL_SOCKET, SO_DEVMEM_DONTNEED, &token,
222 sizeof(token));
223
224 The user must ensure the tokens are returned to the kernel in a timely manner.
225 Failure to do so will exhaust the limited dmabuf that is bound to the RX queue
226 and will lead to packet drops.
227
228 The user must pass no more than 128 tokens, with no more than 1024 total frags
229 among the token->token_count across all the tokens. If the user provides more
230 than 1024 frags, the kernel will free up to 1024 frags and return early.
231
232 The kernel returns the number of actual frags freed. The number of frags freed
233 can be less than the tokens provided by the user in case of:
234
235 (a) an internal kernel leak bug.
236 (b) the user passed more than 1024 frags.
237
238 TX Interface
239 ============
240
241
242 Example
243 -------
244
245 ./tools/testing/selftests/drivers/net/hw/ncdevmem:do_client shows an example of
246 setting up the TX path of this API.
247
248
249 NIC Setup
250 ---------
251
252 The user must bind a TX dmabuf to a given NIC using the netlink API::
253
254 struct netdev_bind_tx_req *req = NULL;
255 struct netdev_bind_tx_rsp *rsp = NULL;
256 struct ynl_error yerr;
257
258 *ys = ynl_sock_create(&ynl_netdev_family, &yerr);
259
260 req = netdev_bind_tx_req_alloc();
261 netdev_bind_tx_req_set_ifindex(req, ifindex);
262 netdev_bind_tx_req_set_fd(req, dmabuf_fd);
263
264 rsp = netdev_bind_tx(*ys, req);
265
266 tx_dmabuf_id = rsp->id;
267
268
269 The netlink API returns a dmabuf_id: a unique ID that refers to this dmabuf
270 that has been bound.
271
272 The user can unbind the dmabuf from the netdevice by closing the netlink socket
273 that established the binding. We do this so that the binding is automatically
274 unbound even if the userspace process crashes.
275
276 Note that any reasonably well-behaved dmabuf from any exporter should work with
277 devmem TCP, even if the dmabuf is not actually backed by devmem. An example of
278 this is udmabuf, which wraps user memory (non-devmem) in a dmabuf.
279
280 Socket Setup
281 ------------
282
283 The user application must use MSG_ZEROCOPY flag when sending devmem TCP. Devmem
284 cannot be copied by the kernel, so the semantics of the devmem TX are similar
285 to the semantics of MSG_ZEROCOPY::
286
287 setsockopt(socket_fd, SOL_SOCKET, SO_ZEROCOPY, &opt, sizeof(opt));
288
289 It is also recommended that the user binds the TX socket to the same interface
290 the dma-buf has been bound to via SO_BINDTODEVICE::
291
292 setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname) + 1);
293
294
295 Sending data
296 ------------
297
298 Devmem data is sent using the SCM_DEVMEM_DMABUF cmsg.
299
300 The user should create a msghdr where,
301
302 * iov_base is set to the offset into the dmabuf to start sending from
303 * iov_len is set to the number of bytes to be sent from the dmabuf
304
305 The user passes the dma-buf id to send from via the dmabuf_tx_cmsg.dmabuf_id.
306
307 The example below sends 1024 bytes from offset 100 into the dmabuf, and 2048
308 from offset 2000 into the dmabuf. The dmabuf to send from is tx_dmabuf_id::
309
310 char ctrl_data[CMSG_SPACE(sizeof(struct dmabuf_tx_cmsg))];
311 struct dmabuf_tx_cmsg ddmabuf;
312 struct msghdr msg = {};
313 struct cmsghdr *cmsg;
314 struct iovec iov[2];
315
316 iov[0].iov_base = (void*)100;
317 iov[0].iov_len = 1024;
318 iov[1].iov_base = (void*)2000;
319 iov[1].iov_len = 2048;
320
321 msg.msg_iov = iov;
322 msg.msg_iovlen = 2;
323
324 msg.msg_control = ctrl_data;
325 msg.msg_controllen = sizeof(ctrl_data);
326
327 cmsg = CMSG_FIRSTHDR(&msg);
328 cmsg->cmsg_level = SOL_SOCKET;
329 cmsg->cmsg_type = SCM_DEVMEM_DMABUF;
330 cmsg->cmsg_len = CMSG_LEN(sizeof(struct dmabuf_tx_cmsg));
331
332 ddmabuf.dmabuf_id = tx_dmabuf_id;
333
334 *((struct dmabuf_tx_cmsg *)CMSG_DATA(cmsg)) = ddmabuf;
335
336 sendmsg(socket_fd, &msg, MSG_ZEROCOPY);
337
338
339 Reusing TX dmabufs
340 ------------------
341
342 Similar to MSG_ZEROCOPY with regular memory, the user should not modify the
343 contents of the dma-buf while a send operation is in progress. This is because
344 the kernel does not keep a copy of the dmabuf contents. Instead, the kernel
345 will pin and send data from the buffer available to the userspace.
346
347 Just as in MSG_ZEROCOPY, the kernel notifies the userspace of send completions
348 using MSG_ERRQUEUE::
349
350 int64_t tstop = gettimeofday_ms() + waittime_ms;
351 char control[CMSG_SPACE(100)] = {};
352 struct sock_extended_err *serr;
353 struct msghdr msg = {};
354 struct cmsghdr *cm;
355 int retries = 10;
356 __u32 hi, lo;
357
358 msg.msg_control = control;
359 msg.msg_controllen = sizeof(control);
360
361 while (gettimeofday_ms() < tstop) {
362 if (!do_poll(fd)) continue;
363
364 ret = recvmsg(fd, &msg, MSG_ERRQUEUE);
365
366 for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
367 serr = (void *)CMSG_DATA(cm);
368
369 hi = serr->ee_data;
370 lo = serr->ee_info;
371
372 fprintf(stdout, "tx complete [%d,%d]\n", lo, hi);
373 }
374 }
375
376 After the associated sendmsg has been completed, the dmabuf can be reused by
377 the userspace.
378
379
380 Implementation & Caveats
381 ========================
382
383 Unreadable skbs
384 ---------------
385
386 Devmem payloads are inaccessible to the kernel processing the packets. This
387 results in a few quirks for payloads of devmem skbs:
388
389 - Loopback is not functional. Loopback relies on copying the payload, which is
390 not possible with devmem skbs.
391
392 - Software checksum calculation fails.
393
394 - TCP Dump and bpf can't access devmem packet payloads.
395
396
397 Testing
398 =======
399
400 More realistic example code can be found in the kernel source under
401 ``tools/testing/selftests/drivers/net/hw/ncdevmem.c``
402
403 ncdevmem is a devmem TCP netcat. It works very similarly to netcat, but
404 receives data directly into a udmabuf.
405
406 To run ncdevmem, you need to run it on a server on the machine under test, and
407 you need to run netcat on a peer to provide the TX data.
408
409 ncdevmem has a validation mode as well that expects a repeating pattern of
410 incoming data and validates it as such. For example, you can launch
411 ncdevmem on the server by::
412
413 ncdevmem -s <server IP> -c <client IP> -f <ifname> -l -p 5201 -v 7
414
415 On client side, use regular netcat to send TX data to ncdevmem process
416 on the server::
417
418 yes $(echo -e \\x01\\x02\\x03\\x04\\x05\\x06) | \
419 tr \\n \\0 | head -c 5G | nc <server IP> 5201 -p 5201
420

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

Device Memory TCP 소개

1-13

이 문서는 `GPL-2.0` 라이선스를 따릅니다.

Device Memory TCP(devmem TCP)는 데이터를 장치 메모리인 dma-buf로 직접 수신하게 합니다. 현재 이 기능은 TCP 소켓용으로 구현되어 있습니다.

.. SPDX-License-Identifier: GPL-2.0

=================
Device Memory TCP
=================


Intro
=====

Device memory TCP (devmem TCP) enables receiving data directly into device
memory (dmabuf). The feature is currently implemented for TCP sockets.

장치 간 전송의 최적화 기회

14-53

기회

많은 데이터 전송에서 장치 메모리가 출발지 또는 목적지로 쓰이며, 가속기의 보급으로 이런 전송이 크게 늘었습니다.

대표 사례로는 서로 다른 호스트의 GPU 같은 ML 가속기가 데이터를 교환하는 분산 학습과, 원격 SSD 사이에서 대량 데이터를 옮기는 분산 원시 블록 스토리지가 있습니다. 후자의 데이터 상당 부분은 호스트 처리가 필요하지 않습니다.

일반적인 네트워크 장치 간 전송은 장치에서 호스트로 복사하고, 호스트끼리 네트워크 전송한 다음, 호스트에서 장치로 다시 복사하는 저수준 동작으로 구현됩니다.

호스트 복사를 포함하는 이 흐름은 특히 대량 전송에서 비효율적이며 호스트 메모리 대역폭과 PCIe 대역폭에 상당한 부담을 줍니다.

devmem TCP는 수신 네트워크 패킷을 장치 메모리로 직접 받는 소켓 API를 제공해 이 사용 사례를 최적화합니다. 패킷 payload는 NIC에서 장치 메모리로 곧바로 가고, 패킷 header는 호스트 메모리로 이동해 일반 TCP/IP 스택에서 처리됩니다. 이를 위해 NIC가 header split을 지원해야 합니다.

devmem TCP의 이점
자원효과
호스트 메모리 대역폭네트워크 전송 뒤 장치로 복사하는 기존 의미론보다 호스트 메모리 대역폭 부담을 줄입니다.
PCIe 대역폭root complex를 통과하는 기존 경로와 달리 PCIe 트리의 가장 낮은 수준에 전송을 제한해 대역폭 부담을 줄입니다.

기존 호스트 경유 경로에서 중복되는 복사와 PCIe 상향 이동을 줄입니다.

devmem TCP 수신 경로
NICheader splitheader → host memoryTCP/IP stack
NICheader splitpayload → dma-bufaccelerator

header는 커널 TCP/IP 처리를 유지하고 payload만 장치 메모리로 직접 전달합니다.


Opportunity
-----------

A large number of data transfers have device memory as the source and/or
destination. Accelerators drastically increased the prevalence of such
transfers.  Some examples include:

- Distributed training, where ML accelerators, such as GPUs on different hosts,
  exchange data.

- Distributed raw block storage applications transfer large amounts of data with
  remote SSDs. Much of this data does not require host processing.

Typically the Device-to-Device data transfers in the network are implemented as
the following low-level operations: Device-to-Host copy, Host-to-Host network
transfer, and Host-to-Device copy.

The flow involving host copies is suboptimal, especially for bulk data transfers,
and can put significant strains on system resources such as host memory
bandwidth and PCIe bandwidth.

Devmem TCP optimizes this use case by implementing socket APIs that enable
the user to receive incoming network packets directly into device memory.

Packet payloads go directly from the NIC to device memory.

Packet headers go to host memory and are processed by the TCP/IP stack
normally. The NIC must support header split to achieve this.

Advantages:

- Alleviate host memory bandwidth pressure, compared to existing
  network-transfer + device-copy semantics.

- Alleviate PCIe bandwidth pressure, by limiting data transfer to the lowest
  level of the PCIe tree, compared to the traditional path which sends data
  through the root complex.

발표와 패치 세트

54-64

추가 정보

슬라이드와 영상은 `https://netdevconf.org/0x17/sessions/talk/device-memory-tcp.html`에서 볼 수 있습니다.

패치 세트는 `[PATCH net-next v24 00/13] Device Memory TCP`이며 `https://lore.kernel.org/netdev/[email protected]/`에 있습니다.

More Info
---------

  slides, video
    https://netdevconf.org/0x17/sessions/talk/device-memory-tcp.html

  patchset
    [PATCH net-next v24 00/13] Device Memory TCP
    https://lore.kernel.org/netdev/[email protected]/

RX 인터페이스 예제

65-74

RX 인터페이스

예제

`./tools/testing/selftests/drivers/net/hw/ncdevmem:do_server`는 이 API의 RX 경로를 설정하는 예를 보여 줍니다.

RX Interface
============


Example
-------

./tools/testing/selftests/drivers/net/hw/ncdevmem:do_server shows an example of
setting up the RX path of this API.

RX NIC 준비

75-101

NIC 설정

devmem TCP에는 header split, flow steering, RSS가 필요합니다. Header split은 수신 패킷을 호스트 메모리의 header buffer와 장치 메모리의 payload buffer로 나눕니다. Flow steering과 RSS는 devmem을 대상으로 하는 흐름만 devmem에 바인딩된 RX queue에 도착하도록 보장합니다.

header split과 flow steering을 활성화합니다.

# enable header split
ethtool -G eth1 tcp-data-split on

# enable flow steering
ethtool -K eth1 ntuple on

RSS를 구성해 대상 RX queue를 제외한 곳으로 모든 일반 트래픽을 보냅니다. 다음 예의 대상은 queue 15입니다.

ethtool --set-rxfh-indir eth1 equal 15

NIC Setup
---------

Header split, flow steering, & RSS are required features for devmem TCP.

Header split is used to split incoming packets into a header buffer in host
memory, and a payload buffer in device memory.

Flow steering & RSS are used to ensure that only flows targeting devmem land on
an RX queue bound to devmem.

Enable header split & flow steering::

        # enable header split
        ethtool -G eth1 tcp-data-split on


        # enable flow steering
        ethtool -K eth1 ntuple on

Configure RSS to steer all traffic away from the target RX queue (queue 15 in
this example)::

        ethtool --set-rxfh-indir eth1 equal 15

dma-buf를 RX queue에 바인딩

102-137

사용자는 netlink API로 dma-buf를 특정 NIC의 여러 RX queue에 바인딩해야 합니다.

/* Bind dmabuf to NIC RX queue 15 */
struct netdev_queue *queues;
queues = malloc(sizeof(*queues) * 1);

queues[0]._present.type = 1;
queues[0]._present.idx = 1;
queues[0].type = NETDEV_RX_QUEUE_TYPE_RX;
queues[0].idx = 15;

*ys = ynl_sock_create(&ynl_netdev_family, &yerr);

req = netdev_bind_rx_req_alloc();
netdev_bind_rx_req_set_ifindex(req, 1 /* ifindex */);
netdev_bind_rx_req_set_dmabuf_fd(req, dmabuf_fd);
__netdev_bind_rx_req_set_queues(req, queues, n_queue_index);

rsp = netdev_bind_rx(*ys, req);

dmabuf_id = rsp->dmabuf_id;

netlink API는 바인딩된 dma-buf를 가리키는 고유 ID인 `dmabuf_id`를 반환합니다.

바인딩을 만든 netlink 소켓을 닫으면 dma-buf를 netdevice에서 바인딩 해제할 수 있습니다. 이 설계는 사용자 공간 프로세스가 충돌해도 바인딩이 자동으로 해제되게 합니다.

어떤 exporter가 제공했든 합리적으로 동작하는 dma-buf라면 실제 장치 메모리 기반이 아니어도 devmem TCP와 함께 사용할 수 있습니다. 예를 들어 `udmabuf`는 사용자 메모리인 비장치 메모리를 dma-buf로 감쌉니다.

The user must bind a dmabuf to any number of RX queues on a given NIC using
the netlink API::

        /* Bind dmabuf to NIC RX queue 15 */
        struct netdev_queue *queues;
        queues = malloc(sizeof(*queues) * 1);

        queues[0]._present.type = 1;
        queues[0]._present.idx = 1;
        queues[0].type = NETDEV_RX_QUEUE_TYPE_RX;
        queues[0].idx = 15;

        *ys = ynl_sock_create(&ynl_netdev_family, &yerr);

        req = netdev_bind_rx_req_alloc();
        netdev_bind_rx_req_set_ifindex(req, 1 /* ifindex */);
        netdev_bind_rx_req_set_dmabuf_fd(req, dmabuf_fd);
        __netdev_bind_rx_req_set_queues(req, queues, n_queue_index);

        rsp = netdev_bind_rx(*ys, req);

        dmabuf_id = rsp->dmabuf_id;


The netlink API returns a dmabuf_id: a unique ID that refers to this dmabuf
that has been bound.

The user can unbind the dmabuf from the netdevice by closing the netlink socket
that established the binding. We do this so that the binding is automatically
unbound even if the userspace process crashes.

Note that any reasonably well-behaved dmabuf from any exporter should work with
devmem TCP, even if the dmabuf is not actually backed by devmem. An example of
this is udmabuf, which wraps user memory (non-devmem) in a dmabuf.

RX 소켓 흐름 조정

138-145

소켓 설정

소켓 흐름을 dma-buf에 바인딩한 RX queue로 조정해야 합니다.

ethtool -N eth1 flow-type tcp4 ... queue 15
Socket Setup
------------

The socket must be flow steered to the dmabuf bound RX queue::

        ethtool -N eth1 flow-type tcp4 ... queue 15

devmem 데이터 수신과 cmsg

146-213

데이터 수신

사용자 애플리케이션은 `recvmsg`에 `MSG_SOCK_DEVMEM` 플래그를 전달해 devmem 데이터를 받을 수 있음을 커널에 알려야 합니다.

ret = recvmsg(fd, &msg, MSG_SOCK_DEVMEM);

`MSG_SOCK_DEVMEM` 플래그를 지정하지 않은 애플리케이션은 devmem 데이터에서 `EFAULT`를 받습니다.

devmem 데이터는 NIC에 바인딩한 dma-buf로 직접 수신되고, 커널은 `SCM_DEVMEM_*` cmsg를 통해 이를 사용자에게 알립니다.

for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
	if (cm->cmsg_level != SOL_SOCKET ||
	    (cm->cmsg_type != SCM_DEVMEM_DMABUF &&
	     cm->cmsg_type != SCM_DEVMEM_LINEAR))
		continue;

	dmabuf_cmsg = (struct dmabuf_cmsg *)CMSG_DATA(cm);

	if (cm->cmsg_type == SCM_DEVMEM_DMABUF) {
		struct dmabuf_token token;
		token.token_start = dmabuf_cmsg->frag_token;
		token.token_count = 1;
		continue;
	}

	if (cm->cmsg_type == SCM_DEVMEM_LINEAR)
		continue;
}
수신 cmsg 의미
cmsg의미
`SCM_DEVMEM_DMABUF`fragment가 `dmabuf_id`가 가리키는 dma-buf에 도착했습니다. `frag_offset`은 시작 오프셋, `frag_size`는 크기, `frag_token`은 나중에 해제할 때 쓸 토큰입니다.
`SCM_DEVMEM_LINEAR`fragment가 선형 host buffer에 도착했습니다. NIC가 header 경계에서 패킷을 나누지 못해 payload 일부 또는 전부가 호스트 메모리에 놓일 때 일반적으로 발생합니다.
cmsg 없음dma-buf에 바인딩되지 않은 RX queue에 도착한 일반 비-devmem TCP 데이터입니다.

fragment가 놓인 메모리 위치와 후속 해제 정보를 전달합니다.

Receiving data
--------------

The user application must signal to the kernel that it is capable of receiving
devmem data by passing the MSG_SOCK_DEVMEM flag to recvmsg::

        ret = recvmsg(fd, &msg, MSG_SOCK_DEVMEM);

Applications that do not specify the MSG_SOCK_DEVMEM flag will receive an EFAULT
on devmem data.

Devmem data is received directly into the dmabuf bound to the NIC in 'NIC
Setup', and the kernel signals such to the user via the SCM_DEVMEM_* cmsgs::

                for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
                        if (cm->cmsg_level != SOL_SOCKET ||
                                (cm->cmsg_type != SCM_DEVMEM_DMABUF &&
                                 cm->cmsg_type != SCM_DEVMEM_LINEAR))
                                continue;

                        dmabuf_cmsg = (struct dmabuf_cmsg *)CMSG_DATA(cm);

                        if (cm->cmsg_type == SCM_DEVMEM_DMABUF) {
                                /* Frag landed in dmabuf.
                                 *
                                 * dmabuf_cmsg->dmabuf_id is the dmabuf the
                                 * frag landed on.
                                 *
                                 * dmabuf_cmsg->frag_offset is the offset into
                                 * the dmabuf where the frag starts.
                                 *
                                 * dmabuf_cmsg->frag_size is the size of the
                                 * frag.
                                 *
                                 * dmabuf_cmsg->frag_token is a token used to
                                 * refer to this frag for later freeing.
                                 */

                                struct dmabuf_token token;
                                token.token_start = dmabuf_cmsg->frag_token;
                                token.token_count = 1;
                                continue;
                        }

                        if (cm->cmsg_type == SCM_DEVMEM_LINEAR)
                                /* Frag landed in linear buffer.
                                 *
                                 * dmabuf_cmsg->frag_size is the size of the
                                 * frag.
                                 */
                                continue;

                }

Applications may receive 2 cmsgs:

- SCM_DEVMEM_DMABUF: this indicates the fragment landed in the dmabuf indicated
  by dmabuf_id.

- SCM_DEVMEM_LINEAR: this indicates the fragment landed in the linear buffer.
  This typically happens when the NIC is unable to split the packet at the
  header boundary, such that part (or all) of the payload landed in host
  memory.

Applications may receive no SO_DEVMEM_* cmsgs. That indicates non-devmem,
regular TCP data that landed on an RX queue not bound to a dmabuf.

수신 fragment 반환

214-237

fragment 해제

`SCM_DEVMEM_DMABUF`로 받은 fragment는 사용자가 처리하는 동안 커널이 고정합니다. 사용자는 `SO_DEVMEM_DONTNEED`를 통해 fragment를 커널에 반환해야 합니다.

ret = setsockopt(client_fd, SOL_SOCKET, SO_DEVMEM_DONTNEED, &token,
		 sizeof(token));

토큰은 제때 커널에 반환해야 합니다. 늦으면 RX queue에 바인딩된 제한된 dma-buf 공간이 고갈되어 패킷이 폐기됩니다.

한 번에 전달할 수 있는 토큰은 최대 128개이며, 모든 `token->token_count`를 합친 fragment는 최대 1024개입니다. 1024개를 넘기면 커널은 최대 1024개만 해제하고 일찍 반환합니다.

커널은 실제로 해제한 fragment 수를 반환합니다. 내부 커널 누수 버그가 있거나 사용자가 1024개보다 많은 fragment를 전달한 경우에는 해제 수가 제공한 토큰이 나타내는 수보다 적을 수 있습니다.

Freeing frags
-------------

Frags received via SCM_DEVMEM_DMABUF are pinned by the kernel while the user
processes the frag. The user must return the frag to the kernel via
SO_DEVMEM_DONTNEED::

        ret = setsockopt(client_fd, SOL_SOCKET, SO_DEVMEM_DONTNEED, &token,
                         sizeof(token));

The user must ensure the tokens are returned to the kernel in a timely manner.
Failure to do so will exhaust the limited dmabuf that is bound to the RX queue
and will lead to packet drops.

The user must pass no more than 128 tokens, with no more than 1024 total frags
among the token->token_count across all the tokens. If the user provides more
than 1024 frags, the kernel will free up to 1024 frags and return early.

The kernel returns the number of actual frags freed. The number of frags freed
can be less than the tokens provided by the user in case of:

(a) an internal kernel leak bug.
(b) the user passed more than 1024 frags.

TX 인터페이스 예제

238-247

TX 인터페이스

예제

`./tools/testing/selftests/drivers/net/hw/ncdevmem:do_client`는 이 API의 TX 경로를 설정하는 예를 보여 줍니다.

TX Interface
============


Example
-------

./tools/testing/selftests/drivers/net/hw/ncdevmem:do_client shows an example of
setting up the TX path of this API.

TX dma-buf 바인딩

248-279

NIC 설정

사용자는 netlink API로 TX dma-buf를 지정한 NIC에 바인딩해야 합니다.

struct netdev_bind_tx_req *req = NULL;
struct netdev_bind_tx_rsp *rsp = NULL;
struct ynl_error yerr;

*ys = ynl_sock_create(&ynl_netdev_family, &yerr);

req = netdev_bind_tx_req_alloc();
netdev_bind_tx_req_set_ifindex(req, ifindex);
netdev_bind_tx_req_set_fd(req, dmabuf_fd);

rsp = netdev_bind_tx(*ys, req);

tx_dmabuf_id = rsp->id;

netlink API는 바인딩된 dma-buf를 가리키는 고유 ID인 `dmabuf_id`를 반환합니다.

바인딩을 만든 netlink 소켓을 닫으면 dma-buf를 netdevice에서 바인딩 해제합니다. 사용자 공간 프로세스가 충돌해도 바인딩이 자동 해제됩니다.

RX와 마찬가지로 합리적으로 동작하는 어떤 exporter의 dma-buf도 실제 장치 메모리 기반이 아니어도 사용할 수 있습니다. `udmabuf`가 그 예입니다.


NIC Setup
---------

The user must bind a TX dmabuf to a given NIC using the netlink API::

        struct netdev_bind_tx_req *req = NULL;
        struct netdev_bind_tx_rsp *rsp = NULL;
        struct ynl_error yerr;

        *ys = ynl_sock_create(&ynl_netdev_family, &yerr);

        req = netdev_bind_tx_req_alloc();
        netdev_bind_tx_req_set_ifindex(req, ifindex);
        netdev_bind_tx_req_set_fd(req, dmabuf_fd);

        rsp = netdev_bind_tx(*ys, req);

        tx_dmabuf_id = rsp->id;


The netlink API returns a dmabuf_id: a unique ID that refers to this dmabuf
that has been bound.

The user can unbind the dmabuf from the netdevice by closing the netlink socket
that established the binding. We do this so that the binding is automatically
unbound even if the userspace process crashes.

Note that any reasonably well-behaved dmabuf from any exporter should work with
devmem TCP, even if the dmabuf is not actually backed by devmem. An example of
this is udmabuf, which wraps user memory (non-devmem) in a dmabuf.

TX 소켓의 zero-copy 설정

280-294

소켓 설정

devmem TCP를 송신할 때 애플리케이션은 `MSG_ZEROCOPY` 플래그를 사용해야 합니다. 커널은 devmem을 복사할 수 없으므로 devmem TX의 의미론은 `MSG_ZEROCOPY`와 유사합니다.

setsockopt(socket_fd, SOL_SOCKET, SO_ZEROCOPY, &opt, sizeof(opt));

`SO_BINDTODEVICE`를 사용해 TX 소켓을 dma-buf가 바인딩된 인터페이스와 같은 인터페이스에 바인딩하는 것도 권장합니다.

setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname) + 1);
Socket Setup
------------

The user application must use MSG_ZEROCOPY flag when sending devmem TCP. Devmem
cannot be copied by the kernel, so the semantics of the devmem TX are similar
to the semantics of MSG_ZEROCOPY::

        setsockopt(socket_fd, SOL_SOCKET, SO_ZEROCOPY, &opt, sizeof(opt));

It is also recommended that the user binds the TX socket to the same interface
the dma-buf has been bound to via SO_BINDTODEVICE::

        setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname) + 1);

SCM_DEVMEM_DMABUF 송신

295-338

데이터 송신

devmem 데이터는 `SCM_DEVMEM_DMABUF` cmsg를 사용해 보냅니다.

사용자는 `msghdr`를 만들고 `iov_base`를 dma-buf에서 송신을 시작할 오프셋으로, `iov_len`을 dma-buf에서 보낼 바이트 수로 설정합니다. 송신할 dma-buf ID는 `dmabuf_tx_cmsg.dmabuf_id`로 전달합니다.

다음 예는 `tx_dmabuf_id`가 가리키는 dma-buf에서 오프셋 100부터 1024바이트, 오프셋 2000부터 2048바이트를 보냅니다.

char ctrl_data[CMSG_SPACE(sizeof(struct dmabuf_tx_cmsg))];
struct dmabuf_tx_cmsg ddmabuf;
struct msghdr msg = {};
struct cmsghdr *cmsg;
struct iovec iov[2];

iov[0].iov_base = (void*)100;
iov[0].iov_len = 1024;
iov[1].iov_base = (void*)2000;
iov[1].iov_len = 2048;

msg.msg_iov = iov;
msg.msg_iovlen = 2;
msg.msg_control = ctrl_data;
msg.msg_controllen = sizeof(ctrl_data);

cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_DEVMEM_DMABUF;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct dmabuf_tx_cmsg));

ddmabuf.dmabuf_id = tx_dmabuf_id;
*((struct dmabuf_tx_cmsg *)CMSG_DATA(cmsg)) = ddmabuf;

sendmsg(socket_fd, &msg, MSG_ZEROCOPY);
Sending data
------------

Devmem data is sent using the SCM_DEVMEM_DMABUF cmsg.

The user should create a msghdr where,

* iov_base is set to the offset into the dmabuf to start sending from
* iov_len is set to the number of bytes to be sent from the dmabuf

The user passes the dma-buf id to send from via the dmabuf_tx_cmsg.dmabuf_id.

The example below sends 1024 bytes from offset 100 into the dmabuf, and 2048
from offset 2000 into the dmabuf. The dmabuf to send from is tx_dmabuf_id::

       char ctrl_data[CMSG_SPACE(sizeof(struct dmabuf_tx_cmsg))];
       struct dmabuf_tx_cmsg ddmabuf;
       struct msghdr msg = {};
       struct cmsghdr *cmsg;
       struct iovec iov[2];

       iov[0].iov_base = (void*)100;
       iov[0].iov_len = 1024;
       iov[1].iov_base = (void*)2000;
       iov[1].iov_len = 2048;

       msg.msg_iov = iov;
       msg.msg_iovlen = 2;

       msg.msg_control = ctrl_data;
       msg.msg_controllen = sizeof(ctrl_data);

       cmsg = CMSG_FIRSTHDR(&msg);
       cmsg->cmsg_level = SOL_SOCKET;
       cmsg->cmsg_type = SCM_DEVMEM_DMABUF;
       cmsg->cmsg_len = CMSG_LEN(sizeof(struct dmabuf_tx_cmsg));

       ddmabuf.dmabuf_id = tx_dmabuf_id;

       *((struct dmabuf_tx_cmsg *)CMSG_DATA(cmsg)) = ddmabuf;

       sendmsg(socket_fd, &msg, MSG_ZEROCOPY);

TX dma-buf 재사용

339-378

TX dma-buf 재사용

일반 메모리의 `MSG_ZEROCOPY`와 마찬가지로 송신 작업이 진행 중인 동안에는 dma-buf 내용을 수정하면 안 됩니다. 커널은 dma-buf 내용의 복사본을 보관하지 않고 사용자 공간에 보이는 buffer를 고정한 뒤 그곳에서 데이터를 송신하기 때문입니다.

커널은 `MSG_ZEROCOPY`와 마찬가지로 `MSG_ERRQUEUE`를 사용해 송신 완료를 사용자 공간에 알립니다.

int64_t tstop = gettimeofday_ms() + waittime_ms;
char control[CMSG_SPACE(100)] = {};
struct sock_extended_err *serr;
struct msghdr msg = {};
struct cmsghdr *cm;
__u32 hi, lo;

msg.msg_control = control;
msg.msg_controllen = sizeof(control);

while (gettimeofday_ms() < tstop) {
	if (!do_poll(fd)) continue;
	ret = recvmsg(fd, &msg, MSG_ERRQUEUE);
	for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
		serr = (void *)CMSG_DATA(cm);
		hi = serr->ee_data;
		lo = serr->ee_info;
		fprintf(stdout, "tx complete [%d,%d]\n", lo, hi);
	}
}

연결된 `sendmsg`가 완료된 뒤에는 사용자 공간에서 dma-buf를 재사용할 수 있습니다.

Reusing TX dmabufs
------------------

Similar to MSG_ZEROCOPY with regular memory, the user should not modify the
contents of the dma-buf while a send operation is in progress. This is because
the kernel does not keep a copy of the dmabuf contents. Instead, the kernel
will pin and send data from the buffer available to the userspace.

Just as in MSG_ZEROCOPY, the kernel notifies the userspace of send completions
using MSG_ERRQUEUE::

        int64_t tstop = gettimeofday_ms() + waittime_ms;
        char control[CMSG_SPACE(100)] = {};
        struct sock_extended_err *serr;
        struct msghdr msg = {};
        struct cmsghdr *cm;
        int retries = 10;
        __u32 hi, lo;

        msg.msg_control = control;
        msg.msg_controllen = sizeof(control);

        while (gettimeofday_ms() < tstop) {
                if (!do_poll(fd)) continue;

                ret = recvmsg(fd, &msg, MSG_ERRQUEUE);

                for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
                        serr = (void *)CMSG_DATA(cm);

                        hi = serr->ee_data;
                        lo = serr->ee_info;

                        fprintf(stdout, "tx complete [%d,%d]\n", lo, hi);
                }
        }

After the associated sendmsg has been completed, the dmabuf can be reused by
the userspace.

커널이 읽을 수 없는 skb payload

379-395

구현과 주의사항

읽을 수 없는 skb

패킷을 처리하는 커널은 devmem payload에 접근할 수 없습니다. 따라서 devmem skb payload에는 다음 제약이 있습니다.

devmem skb 제약
기능제약
Loopbackpayload 복사에 의존하므로 동작하지 않습니다.
소프트웨어 checksum계산에 실패합니다.
TCP dump와 BPFdevmem 패킷 payload에 접근할 수 없습니다.

payload가 장치 메모리에만 있어 호스트 커널의 복사·검사 기능 일부를 사용할 수 없습니다.


Implementation & Caveats
========================

Unreadable skbs
---------------

Devmem payloads are inaccessible to the kernel processing the packets. This
results in a few quirks for payloads of devmem skbs:

- Loopback is not functional. Loopback relies on copying the payload, which is
  not possible with devmem skbs.

- Software checksum calculation fails.

- TCP Dump and bpf can't access devmem packet payloads.

ncdevmem 검증

396-419

시험

보다 현실적인 예제 코드는 커널 소스의 `tools/testing/selftests/drivers/net/hw/ncdevmem.c`에 있습니다.

`ncdevmem`은 devmem TCP용 netcat이며 netcat과 비슷하게 동작하지만 데이터를 `udmabuf`로 직접 수신합니다.

실행하려면 시험 대상 시스템에서 `ncdevmem`을 서버로 실행하고, peer에서 netcat을 실행해 TX 데이터를 제공해야 합니다.

`ncdevmem`에는 반복되는 수신 데이터 패턴을 기대하고 검증하는 모드도 있습니다. 서버에서는 다음과 같이 시작합니다.

ncdevmem -s <server IP> -c <client IP> -f <ifname> -l -p 5201 -v 7

클라이언트에서는 일반 netcat으로 서버의 `ncdevmem` 프로세스에 TX 데이터를 보냅니다.

yes $(echo -e \x01\x02\x03\x04\x05\x06) | \
	tr \n \0 | head -c 5G | nc <server IP> 5201 -p 5201

Testing
=======

More realistic example code can be found in the kernel source under
``tools/testing/selftests/drivers/net/hw/ncdevmem.c``

ncdevmem is a devmem TCP netcat. It works very similarly to netcat, but
receives data directly into a udmabuf.

To run ncdevmem, you need to run it on a server on the machine under test, and
you need to run netcat on a peer to provide the TX data.

ncdevmem has a validation mode as well that expects a repeating pattern of
incoming data and validates it as such. For example, you can launch
ncdevmem on the server by::

        ncdevmem -s <server IP> -c <client IP> -f <ifname> -l -p 5201 -v 7

On client side, use regular netcat to send TX data to ncdevmem process
on the server::

        yes $(echo -e \\x01\\x02\\x03\\x04\\x05\\x06) | \
                tr \\n \\0 | head -c 5G | nc <server IP> 5201 -p 5201