← Documents Documentation/target/tcmu-design.rst GitHub 원문 ↗

Linux 6.18.37 · Target

TCM Userspace 설계

TCMU가 UIO와 offset 기반 shared memory를 이용해 LIO SCSI command를 userspace backstore로 전달하는 mailbox·command ring·data area 설계, device discovery, event, timeout과 handler 구현 절차를 설명합니다.

Source pathDocumentation/target/tcmu-design.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

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

1. 요약·해설

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

요약·해설

tcmu-design.rst:1-405

TCMU가 UIO와 offset 기반 shared memory를 이용해 LIO SCSI command를 userspace backstore로 전달하는 mailbox·command ring·data area 설계, device discovery, event, timeout과 handler 구현 절차를 설명합니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 ====================
2 TCM Userspace Design
3 ====================
4
5
6 .. Contents:
7
8 1) Design
9 a) Background
10 b) Benefits
11 c) Design constraints
12 d) Implementation overview
13 i. Mailbox
14 ii. Command ring
15 iii. Data Area
16 e) Device discovery
17 f) Device events
18 g) Other contingencies
19 2) Writing a user pass-through handler
20 a) Discovering and configuring TCMU uio devices
21 b) Waiting for events on the device(s)
22 c) Managing the command ring
23 3) A final note
24
25
26 Design
27 ======
28
29 TCM is another name for LIO, an in-kernel iSCSI target (server).
30 Existing TCM targets run in the kernel. TCMU (TCM in Userspace)
31 allows userspace programs to be written which act as iSCSI targets.
32 This document describes the design.
33
34 The existing kernel provides modules for different SCSI transport
35 protocols. TCM also modularizes the data storage. There are existing
36 modules for file, block device, RAM or using another SCSI device as
37 storage. These are called "backstores" or "storage engines". These
38 built-in modules are implemented entirely as kernel code.
39
40 Background
41 ----------
42
43 In addition to modularizing the transport protocol used for carrying
44 SCSI commands ("fabrics"), the Linux kernel target, LIO, also modularizes
45 the actual data storage as well. These are referred to as "backstores"
46 or "storage engines". The target comes with backstores that allow a
47 file, a block device, RAM, or another SCSI device to be used for the
48 local storage needed for the exported SCSI LUN. Like the rest of LIO,
49 these are implemented entirely as kernel code.
50
51 These backstores cover the most common use cases, but not all. One new
52 use case that other non-kernel target solutions, such as tgt, are able
53 to support is using Gluster's GLFS or Ceph's RBD as a backstore. The
54 target then serves as a translator, allowing initiators to store data
55 in these non-traditional networked storage systems, while still only
56 using standard protocols themselves.
57
58 If the target is a userspace process, supporting these is easy. tgt,
59 for example, needs only a small adapter module for each, because the
60 modules just use the available userspace libraries for RBD and GLFS.
61
62 Adding support for these backstores in LIO is considerably more
63 difficult, because LIO is entirely kernel code. Instead of undertaking
64 the significant work to port the GLFS or RBD APIs and protocols to the
65 kernel, another approach is to create a userspace pass-through
66 backstore for LIO, "TCMU".
67
68
69 Benefits
70 --------
71
72 In addition to allowing relatively easy support for RBD and GLFS, TCMU
73 will also allow easier development of new backstores. TCMU combines
74 with the LIO loopback fabric to become something similar to FUSE
75 (Filesystem in Userspace), but at the SCSI layer instead of the
76 filesystem layer. A SUSE, if you will.
77
78 The disadvantage is there are more distinct components to configure, and
79 potentially to malfunction. This is unavoidable, but hopefully not
80 fatal if we're careful to keep things as simple as possible.
81
82 Design constraints
83 ------------------
84
85 - Good performance: high throughput, low latency
86 - Cleanly handle if userspace:
87
88 1) never attaches
89 2) hangs
90 3) dies
91 4) misbehaves
92
93 - Allow future flexibility in user & kernel implementations
94 - Be reasonably memory-efficient
95 - Simple to configure & run
96 - Simple to write a userspace backend
97
98
99 Implementation overview
100 -----------------------
101
102 The core of the TCMU interface is a memory region that is shared
103 between kernel and userspace. Within this region is: a control area
104 (mailbox); a lockless producer/consumer circular buffer for commands
105 to be passed up, and status returned; and an in/out data buffer area.
106
107 TCMU uses the pre-existing UIO subsystem. UIO allows device driver
108 development in userspace, and this is conceptually very close to the
109 TCMU use case, except instead of a physical device, TCMU implements a
110 memory-mapped layout designed for SCSI commands. Using UIO also
111 benefits TCMU by handling device introspection (e.g. a way for
112 userspace to determine how large the shared region is) and signaling
113 mechanisms in both directions.
114
115 There are no embedded pointers in the memory region. Everything is
116 expressed as an offset from the region's starting address. This allows
117 the ring to still work if the user process dies and is restarted with
118 the region mapped at a different virtual address.
119
120 See target_core_user.h for the struct definitions.
121
122 The Mailbox
123 -----------
124
125 The mailbox is always at the start of the shared memory region, and
126 contains a version, details about the starting offset and size of the
127 command ring, and head and tail pointers to be used by the kernel and
128 userspace (respectively) to put commands on the ring, and indicate
129 when the commands are completed.
130
131 version - 1 (userspace should abort if otherwise)
132
133 flags:
134 - TCMU_MAILBOX_FLAG_CAP_OOOC:
135 indicates out-of-order completion is supported.
136 See "The Command Ring" for details.
137
138 cmdr_off
139 The offset of the start of the command ring from the start
140 of the memory region, to account for the mailbox size.
141 cmdr_size
142 The size of the command ring. This does *not* need to be a
143 power of two.
144 cmd_head
145 Modified by the kernel to indicate when a command has been
146 placed on the ring.
147 cmd_tail
148 Modified by userspace to indicate when it has completed
149 processing of a command.
150
151 The Command Ring
152 ----------------
153
154 Commands are placed on the ring by the kernel incrementing
155 mailbox.cmd_head by the size of the command, modulo cmdr_size, and
156 then signaling userspace via uio_event_notify(). Once the command is
157 completed, userspace updates mailbox.cmd_tail in the same way and
158 signals the kernel via a 4-byte write(). When cmd_head equals
159 cmd_tail, the ring is empty -- no commands are currently waiting to be
160 processed by userspace.
161
162 TCMU commands are 8-byte aligned. They start with a common header
163 containing "len_op", a 32-bit value that stores the length, as well as
164 the opcode in the lowest unused bits. It also contains cmd_id and
165 flags fields for setting by the kernel (kflags) and userspace
166 (uflags).
167
168 Currently only two opcodes are defined, TCMU_OP_CMD and TCMU_OP_PAD.
169
170 When the opcode is CMD, the entry in the command ring is a struct
171 tcmu_cmd_entry. Userspace finds the SCSI CDB (Command Data Block) via
172 tcmu_cmd_entry.req.cdb_off. This is an offset from the start of the
173 overall shared memory region, not the entry. The data in/out buffers
174 are accessible via the req.iov[] array. iov_cnt contains the number of
175 entries in iov[] needed to describe either the Data-In or Data-Out
176 buffers. For bidirectional commands, iov_cnt specifies how many iovec
177 entries cover the Data-Out area, and iov_bidi_cnt specifies how many
178 iovec entries immediately after that in iov[] cover the Data-In
179 area. Just like other fields, iov.iov_base is an offset from the start
180 of the region.
181
182 When completing a command, userspace sets rsp.scsi_status, and
183 rsp.sense_buffer if necessary. Userspace then increments
184 mailbox.cmd_tail by entry.hdr.length (mod cmdr_size) and signals the
185 kernel via the UIO method, a 4-byte write to the file descriptor.
186
187 If TCMU_MAILBOX_FLAG_CAP_OOOC is set for mailbox->flags, kernel is
188 capable of handling out-of-order completions. In this case, userspace can
189 handle command in different order other than original. Since kernel would
190 still process the commands in the same order it appeared in the command
191 ring, userspace need to update the cmd->id when completing the
192 command(a.k.a steal the original command's entry).
193
194 When the opcode is PAD, userspace only updates cmd_tail as above --
195 it's a no-op. (The kernel inserts PAD entries to ensure each CMD entry
196 is contiguous within the command ring.)
197
198 More opcodes may be added in the future. If userspace encounters an
199 opcode it does not handle, it must set UNKNOWN_OP bit (bit 0) in
200 hdr.uflags, update cmd_tail, and proceed with processing additional
201 commands, if any.
202
203 The Data Area
204 -------------
205
206 This is shared-memory space after the command ring. The organization
207 of this area is not defined in the TCMU interface, and userspace
208 should access only the parts referenced by pending iovs.
209
210
211 Device Discovery
212 ----------------
213
214 Other devices may be using UIO besides TCMU. Unrelated user processes
215 may also be handling different sets of TCMU devices. TCMU userspace
216 processes must find their devices by scanning sysfs
217 class/uio/uio*/name. For TCMU devices, these names will be of the
218 format::
219
220 tcm-user/<hba_num>/<device_name>/<subtype>/<path>
221
222 where "tcm-user" is common for all TCMU-backed UIO devices. <hba_num>
223 and <device_name> allow userspace to find the device's path in the
224 kernel target's configfs tree. Assuming the usual mount point, it is
225 found at::
226
227 /sys/kernel/config/target/core/user_<hba_num>/<device_name>
228
229 This location contains attributes such as "hw_block_size", that
230 userspace needs to know for correct operation.
231
232 <subtype> will be a userspace-process-unique string to identify the
233 TCMU device as expecting to be backed by a certain handler, and <path>
234 will be an additional handler-specific string for the user process to
235 configure the device, if needed. The name cannot contain ':', due to
236 LIO limitations.
237
238 For all devices so discovered, the user handler opens /dev/uioX and
239 calls mmap()::
240
241 mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)
242
243 where size must be equal to the value read from
244 /sys/class/uio/uioX/maps/map0/size.
245
246
247 Device Events
248 -------------
249
250 If a new device is added or removed, a notification will be broadcast
251 over netlink, using a generic netlink family name of "TCM-USER" and a
252 multicast group named "config". This will include the UIO name as
253 described in the previous section, as well as the UIO minor
254 number. This should allow userspace to identify both the UIO device and
255 the LIO device, so that after determining the device is supported
256 (based on subtype) it can take the appropriate action.
257
258
259 Other contingencies
260 -------------------
261
262 Userspace handler process never attaches:
263
264 - TCMU will post commands, and then abort them after a timeout period
265 (30 seconds.)
266
267 Userspace handler process is killed:
268
269 - It is still possible to restart and re-connect to TCMU
270 devices. Command ring is preserved. However, after the timeout period,
271 the kernel will abort pending tasks.
272
273 Userspace handler process hangs:
274
275 - The kernel will abort pending tasks after a timeout period.
276
277 Userspace handler process is malicious:
278
279 - The process can trivially break the handling of devices it controls,
280 but should not be able to access kernel memory outside its shared
281 memory areas.
282
283
284 Writing a user pass-through handler (with example code)
285 =======================================================
286
287 A user process handing a TCMU device must support the following:
288
289 a) Discovering and configuring TCMU uio devices
290 b) Waiting for events on the device(s)
291 c) Managing the command ring: Parsing operations and commands,
292 performing work as needed, setting response fields (scsi_status and
293 possibly sense_buffer), updating cmd_tail, and notifying the kernel
294 that work has been finished
295
296 First, consider instead writing a plugin for tcmu-runner. tcmu-runner
297 implements all of this, and provides a higher-level API for plugin
298 authors.
299
300 TCMU is designed so that multiple unrelated processes can manage TCMU
301 devices separately. All handlers should make sure to only open their
302 devices, based opon a known subtype string.
303
304 a) Discovering and configuring TCMU UIO devices::
305
306 /* error checking omitted for brevity */
307
308 int fd, dev_fd;
309 char buf[256];
310 unsigned long long map_len;
311 void *map;
312
313 fd = open("/sys/class/uio/uio0/name", O_RDONLY);
314 ret = read(fd, buf, sizeof(buf));
315 close(fd);
316 buf[ret-1] = '\0'; /* null-terminate and chop off the \n */
317
318 /* we only want uio devices whose name is a format we expect */
319 if (strncmp(buf, "tcm-user", 8))
320 exit(-1);
321
322 /* Further checking for subtype also needed here */
323
324 fd = open(/sys/class/uio/%s/maps/map0/size, O_RDONLY);
325 ret = read(fd, buf, sizeof(buf));
326 close(fd);
327 str_buf[ret-1] = '\0'; /* null-terminate and chop off the \n */
328
329 map_len = strtoull(buf, NULL, 0);
330
331 dev_fd = open("/dev/uio0", O_RDWR);
332 map = mmap(NULL, map_len, PROT_READ|PROT_WRITE, MAP_SHARED, dev_fd, 0);
333
334
335 b) Waiting for events on the device(s)
336
337 while (1) {
338 char buf[4];
339
340 int ret = read(dev_fd, buf, 4); /* will block */
341
342 handle_device_events(dev_fd, map);
343 }
344
345
346 c) Managing the command ring::
347
348 #include <linux/target_core_user.h>
349
350 int handle_device_events(int fd, void *map)
351 {
352 struct tcmu_mailbox *mb = map;
353 struct tcmu_cmd_entry *ent = (void *) mb + mb->cmdr_off + mb->cmd_tail;
354 int did_some_work = 0;
355
356 /* Process events from cmd ring until we catch up with cmd_head */
357 while (ent != (void *)mb + mb->cmdr_off + mb->cmd_head) {
358
359 if (tcmu_hdr_get_op(ent->hdr.len_op) == TCMU_OP_CMD) {
360 uint8_t *cdb = (void *)mb + ent->req.cdb_off;
361 bool success = true;
362
363 /* Handle command here. */
364 printf("SCSI opcode: 0x%x\n", cdb[0]);
365
366 /* Set response fields */
367 if (success)
368 ent->rsp.scsi_status = SCSI_NO_SENSE;
369 else {
370 /* Also fill in rsp->sense_buffer here */
371 ent->rsp.scsi_status = SCSI_CHECK_CONDITION;
372 }
373 }
374 else if (tcmu_hdr_get_op(ent->hdr.len_op) != TCMU_OP_PAD) {
375 /* Tell the kernel we didn't handle unknown opcodes */
376 ent->hdr.uflags |= TCMU_UFLAG_UNKNOWN_OP;
377 }
378 else {
379 /* Do nothing for PAD entries except update cmd_tail */
380 }
381
382 /* update cmd_tail */
383 mb->cmd_tail = (mb->cmd_tail + tcmu_hdr_get_len(&ent->hdr)) % mb->cmdr_size;
384 ent = (void *) mb + mb->cmdr_off + mb->cmd_tail;
385 did_some_work = 1;
386 }
387
388 /* Notify the kernel that work has been finished */
389 if (did_some_work) {
390 uint32_t buf = 0;
391
392 write(fd, &buf, 4);
393 }
394
395 return 0;
396 }
397
398
399 A final note
400 ============
401
402 Please be careful to return codes as defined by the SCSI
403 specifications. These are different than some values defined in the
404 scsi/scsi.h include file. For example, CHECK CONDITION's status code
405 is 2, not 1.
406

3. 한국어 전문 번역

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

문서 구성

1-24

이 문서는 TCMU 설계를 배경·효과·제약·구현 개요로 설명하고, shared-memory mailbox·command ring·data area, device discovery와 event, 예외 상황을 다룬다.

후반부는 userspace pass-through handler 작성법을 device 발견·설정, event 대기, command ring 관리로 나누어 예제 code와 함께 설명하며 마지막에는 SCSI status code 주의 사항을 덧붙인다.

문서의 두 축
부분내용
Design배경, 장점, 제약, shared memory, discovery, event, contingency
Userspace handlerUIO 발견, event 대기, command parsing과 response

설계 설명과 handler 구현 절차로 구성된다.

====================
TCM Userspace Design
====================


.. Contents:

   1) Design
     a) Background
     b) Benefits
     c) Design constraints
     d) Implementation overview
        i. Mailbox
        ii. Command ring
        iii. Data Area
     e) Device discovery
     f) Device events
     g) Other contingencies
   2) Writing a user pass-through handler
     a) Discovering and configuring TCMU uio devices
     b) Waiting for events on the device(s)
     c) Managing the command ring
   3) A final note

TCM·LIO와 userspace target

25-39

TCM은 in-kernel iSCSI target server인 LIO의 다른 이름이다. 기존 TCM target은 kernel에서 실행되지만 TCMU, 즉 TCM in Userspace는 userspace program이 iSCSI target 역할을 하게 한다.

kernel은 여러 SCSI transport protocol module을 제공하고 TCM은 실제 data storage도 modularize한다. file, block device, RAM, 다른 SCSI device를 storage로 쓰는 module을 backstore 또는 storage engine이라고 하며 기존 built-in module은 전부 kernel code로 구현된다.

LIO 구성
SCSI initiatorFabric or transport moduleLIO / TCM target core
LIO / TCM target coreBackstore or storage engineLocal or networked storage

transport와 storage를 각각 교체 가능한 module로 분리한다.


Design
======

TCM is another name for LIO, an in-kernel iSCSI target (server).
Existing TCM targets run in the kernel.  TCMU (TCM in Userspace)
allows userspace programs to be written which act as iSCSI targets.
This document describes the design.

The existing kernel provides modules for different SCSI transport
protocols.  TCM also modularizes the data storage.  There are existing
modules for file, block device, RAM or using another SCSI device as
storage.  These are called "backstores" or "storage engines".  These
built-in modules are implemented entirely as kernel code.

Networked userspace backstore가 필요한 이유

40-68

LIO는 SCSI command를 운반하는 transport protocol인 fabric뿐 아니라 실제 data storage인 backstore도 modularize한다. 기본 backstore는 exported SCSI LUN의 local storage로 file, block device, RAM, 다른 SCSI device를 사용할 수 있게 하며 모두 kernel code다.

이 module들이 일반적인 용도는 다루지만 전부는 아니다. userspace target인 `tgt` 같은 solution은 Gluster GLFS나 Ceph RBD를 backstore로 사용할 수 있다. target은 translator가 되어 initiator에는 표준 SCSI protocol만 보이게 하면서 data는 비전통적인 network storage에 저장한다.

target이 userspace process이면 RBD와 GLFS의 기존 userspace library를 호출하는 작은 adapter module만 작성하면 된다.

LIO는 kernel code이므로 GLFS나 RBD API와 protocol 전체를 kernel로 port하는 일은 훨씬 어렵다. 이를 피하는 대안이 LIO용 userspace pass-through backstore인 TCMU다.

TCMU의 번역 계층
SCSI initiatorStandard fabricLIO target
LIO targetTCMU pass-throughUserspace handler
Userspace handlerGLFS or RBD libraryNetworked storage

표준 SCSI LUN을 userspace storage library에 연결한다.

Background
----------

In addition to modularizing the transport protocol used for carrying
SCSI commands ("fabrics"), the Linux kernel target, LIO, also modularizes
the actual data storage as well. These are referred to as "backstores"
or "storage engines". The target comes with backstores that allow a
file, a block device, RAM, or another SCSI device to be used for the
local storage needed for the exported SCSI LUN. Like the rest of LIO,
these are implemented entirely as kernel code.

These backstores cover the most common use cases, but not all. One new
use case that other non-kernel target solutions, such as tgt, are able
to support is using Gluster's GLFS or Ceph's RBD as a backstore. The
target then serves as a translator, allowing initiators to store data
in these non-traditional networked storage systems, while still only
using standard protocols themselves.

If the target is a userspace process, supporting these is easy. tgt,
for example, needs only a small adapter module for each, because the
modules just use the available userspace libraries for RBD and GLFS.

Adding support for these backstores in LIO is considerably more
difficult, because LIO is entirely kernel code. Instead of undertaking
the significant work to port the GLFS or RBD APIs and protocols to the
kernel, another approach is to create a userspace pass-through
backstore for LIO, "TCMU".

새 backstore 개발의 이점과 비용

69-81

TCMU는 RBD와 GLFS를 비교적 쉽게 지원할 뿐 아니라 새 backstore 개발도 쉽게 한다. LIO loopback fabric과 결합하면 filesystem layer의 FUSE와 비슷한 구조를 SCSI layer에서 구현한다. 원문은 이를 말장난으로 SUSE라고 부른다.

단점은 설정해야 할 구성 요소와 고장날 가능성이 있는 구성 요소가 늘어난다는 점이다. 이는 피할 수 없지만 각 부분을 가능한 단순하게 유지하면 치명적 문제로 이어질 가능성을 낮출 수 있다.

TCMU tradeoff
이점비용
기존 userspace storage library 재사용kernel과 userspace 구성 요소를 함께 설정
새 backstore 개발 용이장애 지점 증가
SCSI layer의 FUSE 유사 모델단순한 interface 유지 필요

userspace 확장성과 운영 복잡성의 교환이다.

Benefits
--------

In addition to allowing relatively easy support for RBD and GLFS, TCMU
will also allow easier development of new backstores. TCMU combines
with the LIO loopback fabric to become something similar to FUSE
(Filesystem in Userspace), but at the SCSI layer instead of the
filesystem layer. A SUSE, if you will.

The disadvantage is there are more distinct components to configure, and
potentially to malfunction. This is unavoidable, but hopefully not
fatal if we're careful to keep things as simple as possible.

설계 제약

82-98

설계 목표는 높은 throughput과 낮은 latency, 합리적인 memory 효율, 단순한 설정과 실행, 쉬운 userspace backend 작성이다.

userspace가 전혀 attach하지 않거나 hang·종료·오동작하는 모든 상황을 깔끔하게 처리해야 한다. 또한 향후 kernel과 userspace 구현이 확장될 수 있는 유연성을 남겨야 한다.

TCMU 설계 제약
영역요구 사항
성능높은 throughput, 낮은 latency
userspace 장애미attach, hang, death, misbehavior 처리
확장성future user/kernel implementation 유연성
resource합리적인 memory 효율
사용성설정·실행·backend 작성이 단순

성능·복원력·확장성과 사용성을 함께 요구한다.

Design constraints
------------------

- Good performance: high throughput, low latency
- Cleanly handle if userspace:

   1) never attaches
   2) hangs
   3) dies
   4) misbehaves

- Allow future flexibility in user & kernel implementations
- Be reasonably memory-efficient
- Simple to configure & run
- Simple to write a userspace backend

Shared memory와 UIO 기반 구현

99-121

TCMU interface의 핵심은 kernel과 userspace가 공유하는 memory region이다. 이 region에는 control area인 mailbox, command와 status를 전달하는 lockless producer/consumer circular buffer, input/output data buffer area가 있다.

TCMU는 기존 UIO subsystem을 사용한다. UIO는 userspace device driver 개발을 지원하며, TCMU는 physical device 대신 SCSI command용 memory-mapped layout을 구현한다는 차이가 있다.

UIO를 사용하면 userspace가 shared region 크기를 알아내는 device introspection과 양방향 signaling mechanism도 재사용할 수 있다.

memory region 안에는 pointer를 넣지 않고 모든 위치를 region 시작 address로부터의 offset으로 표현한다. 따라서 user process가 죽었다가 다른 virtual address에 region을 mapping해 재시작해도 ring이 계속 동작한다. 구조체 정의는 `target_core_user.h`에 있다.

TCMU shared region
Region startMailbox
Mailbox cmdr_offCommand ring
After command ringData area
Offsets onlyRemap-safe userspace restart

하나의 mmap region 안에서 control, command, data를 offset으로 연결한다.

UIO가 제공하는 기능
기능TCMU 용도
mmapSCSI command shared layout
sysfs introspectionmap size와 device identity 발견
event signalingkernel→user와 user→kernel 통지

TCMU가 기존 subsystem에서 재사용하는 기능이다.

Implementation overview
-----------------------

The core of the TCMU interface is a memory region that is shared
between kernel and userspace. Within this region is: a control area
(mailbox); a lockless producer/consumer circular buffer for commands
to be passed up, and status returned; and an in/out data buffer area.

TCMU uses the pre-existing UIO subsystem. UIO allows device driver
development in userspace, and this is conceptually very close to the
TCMU use case, except instead of a physical device, TCMU implements a
memory-mapped layout designed for SCSI commands. Using UIO also
benefits TCMU by handling device introspection (e.g. a way for
userspace to determine how large the shared region is) and signaling
mechanisms in both directions.

There are no embedded pointers in the memory region. Everything is
expressed as an offset from the region's starting address. This allows
the ring to still work if the user process dies and is restarted with
the region mapped at a different virtual address.

See target_core_user.h for the struct definitions.

Mailbox field와 소유권

122-150

mailbox는 shared memory region 시작에 항상 놓이며 version, command ring 시작 offset과 크기, kernel과 userspace가 사용하는 head와 tail을 담는다. head는 command가 ring에 놓였음을, tail은 command 처리가 끝났음을 나타낸다.

version은 1이어야 하고 다른 값이면 userspace가 중단해야 한다. `TCMU_MAILBOX_FLAG_CAP_OOOC` flag는 out-of-order completion 지원을 뜻한다.

`cmdr_off`는 region 시작부터 command ring 시작까지의 offset으로 mailbox 크기를 반영한다. `cmdr_size`는 command ring 크기이며 2의 거듭제곱일 필요가 없다.

`cmd_head`는 kernel이 command를 추가할 때 수정하고 `cmd_tail`은 userspace가 command 처리를 완료할 때 수정한다.

Mailbox field
Field의미수정 주체
versioninterface version 1초기화
flagsOOOC capability 등kernel
cmdr_offregion 시작부터 command ring까지 offset초기화
cmdr_sizering byte size; power-of-two 불필요초기화
cmd_headkernel producer 위치kernel
cmd_tailuserspace consumer 완료 위치userspace

field별 의미와 writer를 구분한다.

The Mailbox
-----------

The mailbox is always at the start of the shared memory region, and
contains a version, details about the starting offset and size of the
command ring, and head and tail pointers to be used by the kernel and
userspace (respectively) to put commands on the ring, and indicate
when the commands are completed.

version - 1 (userspace should abort if otherwise)

flags:
    - TCMU_MAILBOX_FLAG_CAP_OOOC:
	indicates out-of-order completion is supported.
	See "The Command Ring" for details.

cmdr_off
	The offset of the start of the command ring from the start
	of the memory region, to account for the mailbox size.
cmdr_size
	The size of the command ring. This does *not* need to be a
	power of two.
cmd_head
	Modified by the kernel to indicate when a command has been
	placed on the ring.
cmd_tail
	Modified by userspace to indicate when it has completed
	processing of a command.

Command ring entry와 완료 protocol

151-202

kernel은 command size만큼 `mailbox.cmd_head`를 `cmdr_size` modulo로 증가시켜 ring에 command를 놓고 `uio_event_notify()`로 userspace에 알린다. userspace가 완료하면 같은 방식으로 `cmd_tail`을 증가시키고 file descriptor에 4 byte를 `write()`해 kernel에 알린다. `cmd_head == cmd_tail`이면 ring이 비었다.

TCMU command는 8-byte aligned이고 공통 header로 시작한다. 32-bit `len_op`는 length와 사용하지 않는 하위 bit의 opcode를 함께 저장한다. `cmd_id`, kernel이 설정하는 `kflags`, userspace가 설정하는 `uflags`도 있다.

현재 opcode는 `TCMU_OP_CMD`와 `TCMU_OP_PAD` 두 개다. CMD entry는 `struct tcmu_cmd_entry`이며 SCSI CDB는 `req.cdb_off` offset에서 찾는다. offset 기준은 entry가 아니라 shared region 시작이다.

Data-In과 Data-Out buffer는 `req.iov[]`로 접근한다. `iov_cnt`는 단방향 command의 buffer를 설명하는 entry 수다. bidirectional command에서는 먼저 `iov_cnt`개가 Data-Out을, 바로 뒤의 `iov_bidi_cnt`개가 Data-In을 설명한다. `iov_base`도 region 시작 기준 offset이다.

userspace는 완료할 때 `rsp.scsi_status`와 필요하면 `rsp.sense_buffer`를 설정한다. 이어 `entry.hdr.length`만큼 tail을 modulo 증가시키고 UIO fd에 4 byte를 써 kernel을 깨운다.

mailbox flag에 `TCMU_MAILBOX_FLAG_CAP_OOOC`가 있으면 kernel은 out-of-order completion을 처리할 수 있다. userspace는 원래 순서와 다르게 command를 처리할 수 있지만 kernel은 ring 순서로 완료를 보므로 완료할 original entry를 가져오는 방식으로 `cmd->id`를 갱신해야 한다.

PAD entry는 no-op이며 userspace는 tail만 갱신한다. kernel은 각 CMD entry가 ring 안에서 연속되도록 wrap 경계에 PAD를 삽입한다.

향후 opcode가 추가될 수 있다. 처리할 수 없는 opcode를 만나면 `hdr.uflags` bit 0인 `UNKNOWN_OP`를 설정하고 tail을 갱신한 뒤 다음 command를 계속 처리해야 한다.

Command producer/consumer
Kernel writes entrycmd_head += length mod cmdr_sizeuio_event_notify
Userspace parses CMD or PADSet response or UNKNOWN_OP
cmd_tail += length mod cmdr_sizewrite 4 bytes to UIO fdKernel completion

head와 tail은 서로 다른 주체가 갱신한다.

Command entry 핵심
Field해석
hdr.len_op8-byte aligned entry length와 opcode
req.cdb_offregion 시작 기준 SCSI CDB offset
req.iov[]Data-Out/Data-In buffer descriptor
iov_cnt단방향 수 또는 bidirectional Data-Out 수
iov_bidi_cntbidirectional Data-In 수
rsp.scsi_status완료 SCSI status
rsp.sense_buffer필요한 sense data

ring entry의 offset과 count 해석이다.

The Command Ring
----------------

Commands are placed on the ring by the kernel incrementing
mailbox.cmd_head by the size of the command, modulo cmdr_size, and
then signaling userspace via uio_event_notify(). Once the command is
completed, userspace updates mailbox.cmd_tail in the same way and
signals the kernel via a 4-byte write(). When cmd_head equals
cmd_tail, the ring is empty -- no commands are currently waiting to be
processed by userspace.

TCMU commands are 8-byte aligned. They start with a common header
containing "len_op", a 32-bit value that stores the length, as well as
the opcode in the lowest unused bits. It also contains cmd_id and
flags fields for setting by the kernel (kflags) and userspace
(uflags).

Currently only two opcodes are defined, TCMU_OP_CMD and TCMU_OP_PAD.

When the opcode is CMD, the entry in the command ring is a struct
tcmu_cmd_entry. Userspace finds the SCSI CDB (Command Data Block) via
tcmu_cmd_entry.req.cdb_off. This is an offset from the start of the
overall shared memory region, not the entry. The data in/out buffers
are accessible via the req.iov[] array. iov_cnt contains the number of
entries in iov[] needed to describe either the Data-In or Data-Out
buffers. For bidirectional commands, iov_cnt specifies how many iovec
entries cover the Data-Out area, and iov_bidi_cnt specifies how many
iovec entries immediately after that in iov[] cover the Data-In
area. Just like other fields, iov.iov_base is an offset from the start
of the region.

When completing a command, userspace sets rsp.scsi_status, and
rsp.sense_buffer if necessary. Userspace then increments
mailbox.cmd_tail by entry.hdr.length (mod cmdr_size) and signals the
kernel via the UIO method, a 4-byte write to the file descriptor.

If TCMU_MAILBOX_FLAG_CAP_OOOC is set for mailbox->flags, kernel is
capable of handling out-of-order completions. In this case, userspace can
handle command in different order other than original. Since kernel would
still process the commands in the same order it appeared in the command
ring, userspace need to update the cmd->id when completing the
command(a.k.a steal the original command's entry).

When the opcode is PAD, userspace only updates cmd_tail as above --
it's a no-op. (The kernel inserts PAD entries to ensure each CMD entry
is contiguous within the command ring.)

More opcodes may be added in the future. If userspace encounters an
opcode it does not handle, it must set UNKNOWN_OP bit (bit 0) in
hdr.uflags, update cmd_tail, and proceed with processing additional
commands, if any.

Data area 접근 규칙

203-210

data area는 command ring 뒤에 있는 shared-memory 공간이다. 이 영역의 내부 배치는 TCMU interface가 정의하지 않으며 userspace는 pending command의 iov가 참조하는 부분에만 접근해야 한다.

Data area 계약
규칙이유
command ring 뒤에 위치shared region 일부
고정 organization 없음implementation 유연성
pending iov 범위만 접근다른 command와 memory 격리

layout을 추측하지 않고 iov reference만 따른다.

The Data Area
-------------

This is shared-memory space after the command ring. The organization
of this area is not defined in the TCMU interface, and userspace
should access only the parts referenced by pending iovs.

sysfs UIO device 발견과 mmap

211-246

UIO는 TCMU 외의 device도 사용할 수 있고 서로 무관한 process가 서로 다른 TCMU device 집합을 처리할 수 있다. handler는 `sysfs class/uio/uio*/name`을 scan해 자신이 담당할 device를 찾아야 한다.

TCMU UIO name 형식은 `tcm-user/<hba_num>/<device_name>/<subtype>/<path>`다. `tcm-user`는 모든 TCMU-backed UIO device의 공통 prefix다.

`hba_num`과 `device_name`으로 kernel target ConfigFS tree의 device path를 찾는다. 일반 mount point라면 `/sys/kernel/config/target/core/user_<hba_num>/<device_name>`이며 이 위치에는 올바른 동작에 필요한 `hw_block_size` 같은 attribute가 있다.

`subtype`은 userspace process가 특정 handler를 기대하는 TCMU device를 식별하는 고유 string이다. `path`는 필요할 때 handler가 device를 설정하는 데 쓰는 추가 string이다. LIO 제한 때문에 name에 colon `:`을 넣을 수 없다.

발견한 device마다 `/dev/uioX`를 열고 `mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)`을 호출한다. size는 `/sys/class/uio/uioX/maps/map0/size`에서 읽은 값과 같아야 한다.

TCMU device 발견
Scan /sys/class/uio/uio*/nameMatch tcm-user prefix and subtype
hba_num + device_nameLocate ConfigFS attributes
Read map0/sizeOpen /dev/uioXMAP_SHARED mmap

UIO name에서 handler match와 ConfigFS path를 파생한다.

UIO name component
Component의미
tcm-userTCMU 공통 prefix
hba_numConfigFS user_<hba_num> 식별
device_nametarget core device name
subtype담당 userspace handler 선택
pathhandler-specific configuration string

각 path segment의 역할이다.

Device Discovery
----------------

Other devices may be using UIO besides TCMU. Unrelated user processes
may also be handling different sets of TCMU devices. TCMU userspace
processes must find their devices by scanning sysfs
class/uio/uio*/name. For TCMU devices, these names will be of the
format::

	tcm-user/<hba_num>/<device_name>/<subtype>/<path>

where "tcm-user" is common for all TCMU-backed UIO devices. <hba_num>
and <device_name> allow userspace to find the device's path in the
kernel target's configfs tree. Assuming the usual mount point, it is
found at::

	/sys/kernel/config/target/core/user_<hba_num>/<device_name>

This location contains attributes such as "hw_block_size", that
userspace needs to know for correct operation.

<subtype> will be a userspace-process-unique string to identify the
TCMU device as expecting to be backed by a certain handler, and <path>
will be an additional handler-specific string for the user process to
configure the device, if needed. The name cannot contain ':', due to
LIO limitations.

For all devices so discovered, the user handler opens /dev/uioX and
calls mmap()::

	mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)

where size must be equal to the value read from
/sys/class/uio/uioX/maps/map0/size.

Generic netlink device event

247-258

device가 추가되거나 제거되면 generic netlink family `TCM-USER`의 multicast group `config`로 notification을 broadcast한다.

event에는 앞 절의 UIO name과 UIO minor number가 들어 있다. userspace는 subtype으로 지원 여부를 판단하고 UIO device와 LIO device를 모두 식별해 적절한 동작을 수행할 수 있다.

Device event 처리
TCMU device add or removeTCM-USER/config multicast
UIO name + minorCheck subtype
Identify UIO and LIO objectsAttach, configure, or detach

netlink event를 지원 가능한 handler action으로 연결한다.

Device Events
-------------

If a new device is added or removed, a notification will be broadcast
over netlink, using a generic netlink family name of "TCM-USER" and a
multicast group named "config". This will include the UIO name as
described in the previous section, as well as the UIO minor
number. This should allow userspace to identify both the UIO device and
the LIO device, so that after determining the device is supported
(based on subtype) it can take the appropriate action.

Userspace 장애와 timeout

259-283

userspace handler가 attach하지 않으면 TCMU는 command를 post한 뒤 30초 timeout 후 abort한다.

handler process가 종료되어도 재시작해 TCMU device에 다시 연결할 수 있고 command ring은 보존된다. 다만 timeout이 지나면 kernel이 pending task를 abort한다.

handler가 hang한 경우에도 kernel은 timeout 뒤 pending task를 abort한다.

악의적인 process는 자신이 제어하는 device 처리를 쉽게 망가뜨릴 수 있지만, 자신에게 공유된 memory area 밖의 kernel memory에는 접근할 수 없어야 한다.

Userspace contingency
상황결과
attach하지 않음command post 후 30초 timeout으로 abort
process 종료ring 보존·재연결 가능, timeout 뒤 pending task abort
process hangtimeout 뒤 pending task abort
malicious담당 device는 손상 가능, shared area 밖 kernel memory는 차단

handler 상태별 kernel 동작이다.

Other contingencies
-------------------

Userspace handler process never attaches:

- TCMU will post commands, and then abort them after a timeout period
  (30 seconds.)

Userspace handler process is killed:

- It is still possible to restart and re-connect to TCMU
  devices. Command ring is preserved. However, after the timeout period,
  the kernel will abort pending tasks.

Userspace handler process hangs:

- The kernel will abort pending tasks after a timeout period.

Userspace handler process is malicious:

- The process can trivially break the handling of devices it controls,
  but should not be able to access kernel memory outside its shared
  memory areas.

Userspace handler의 세 책임

284-303

TCMU device를 처리하는 user process는 device 발견과 설정, device event 대기, command ring 관리를 지원해야 한다.

command ring 관리에는 operation과 command parsing, 필요한 work 수행, `scsi_status`와 필요 시 `sense_buffer` 설정, `cmd_tail` 갱신, 완료 사실을 kernel에 통지하는 절차가 모두 포함된다.

직접 구현하기 전에 `tcmu-runner` plugin 작성을 먼저 고려해야 한다. tcmu-runner는 이 low-level 절차 전체를 구현하고 plugin author에게 더 높은 수준의 API를 제공한다.

TCMU는 서로 무관한 여러 process가 device를 따로 관리할 수 있도록 설계되었다. 각 handler는 알려진 subtype string을 기준으로 자신이 담당하는 device만 열어야 한다.

Handler 책임
Discover UIO by subtypeConfigure and mmap
Wait for device eventParse command ring
Perform backend I/OSet SCSI response
Advance tailNotify kernel

device 소유권 선택부터 완료 통지까지의 반복 작업이다.

Writing a user pass-through handler (with example code)
=======================================================

A user process handing a TCMU device must support the following:

a) Discovering and configuring TCMU uio devices
b) Waiting for events on the device(s)
c) Managing the command ring: Parsing operations and commands,
   performing work as needed, setting response fields (scsi_status and
   possibly sense_buffer), updating cmd_tail, and notifying the kernel
   that work has been finished

First, consider instead writing a plugin for tcmu-runner. tcmu-runner
implements all of this, and provides a higher-level API for plugin
authors.

TCMU is designed so that multiple unrelated processes can manage TCMU
devices separately. All handlers should make sure to only open their
devices, based opon a known subtype string.

UIO 발견·mapping과 event 대기 예제

304-345

예제는 brevity를 위해 error checking을 생략한다. `/sys/class/uio/uio0/name`을 읽고 newline을 제거한 뒤 name이 `tcm-user`로 시작하는지 확인하며 실제 handler는 subtype도 추가로 검사해야 한다.

`/sys/class/uio/uioX/maps/map0/size`를 읽어 `strtoull()`로 mapping length를 얻는다. 이어 `/dev/uio0`을 read/write로 열고 shared read/write mapping을 만든다.

event loop는 UIO fd에서 4 byte를 blocking read한다. event가 도착하면 `handle_device_events(dev_fd, map)`을 호출해 command ring을 처리한다.

발견 예제 단계
단계경로 또는 호출결과
identity/sys/class/uio/uio0/nametcm-user prefix와 subtype 확인
map lengthmaps/map0/sizestrtoull로 byte length
device/dev/uio0O_RDWR fd
mappingmmap MAP_SHAREDmailbox와 ring address
eventread(fd, 4)blocking notification

code가 읽는 file과 결과다.

Event loop
Blocking 4-byte readUIO event
handle_device_eventsConsume entries until tail == head
Return to blocking read

UIO notification마다 mapped ring을 비울 때까지 처리한다.

a) Discovering and configuring TCMU UIO devices::

      /* error checking omitted for brevity */

      int fd, dev_fd;
      char buf[256];
      unsigned long long map_len;
      void *map;

      fd = open("/sys/class/uio/uio0/name", O_RDONLY);
      ret = read(fd, buf, sizeof(buf));
      close(fd);
      buf[ret-1] = '\0'; /* null-terminate and chop off the \n */

      /* we only want uio devices whose name is a format we expect */
      if (strncmp(buf, "tcm-user", 8))
	exit(-1);

      /* Further checking for subtype also needed here */

      fd = open(/sys/class/uio/%s/maps/map0/size, O_RDONLY);
      ret = read(fd, buf, sizeof(buf));
      close(fd);
      str_buf[ret-1] = '\0'; /* null-terminate and chop off the \n */

      map_len = strtoull(buf, NULL, 0);

      dev_fd = open("/dev/uio0", O_RDWR);
      map = mmap(NULL, map_len, PROT_READ|PROT_WRITE, MAP_SHARED, dev_fd, 0);


      b) Waiting for events on the device(s)

      while (1) {
        char buf[4];

        int ret = read(dev_fd, buf, 4); /* will block */

        handle_device_events(dev_fd, map);
      }

Command ring 처리 예제

346-398

예제는 `<linux/target_core_user.h>`를 include한다. mapped region 시작을 `struct tcmu_mailbox *mb`로 보고 `cmdr_off + cmd_tail`에서 첫 `struct tcmu_cmd_entry`를 계산한다.

entry 위치가 `cmdr_off + cmd_head`에 도달할 때까지 loop한다. opcode가 `TCMU_OP_CMD`이면 region 시작에 `req.cdb_off`를 더해 CDB를 찾고 예제에서는 첫 SCSI opcode를 출력한다.

backend work가 성공하면 `rsp.scsi_status = SCSI_NO_SENSE`로 설정한다. 실패하면 실제 구현에서 `sense_buffer`도 채우고 status를 `SCSI_CHECK_CONDITION`으로 설정한다.

opcode가 PAD가 아닌 알 수 없는 값이면 `TCMU_UFLAG_UNKNOWN_OP`를 `hdr.uflags`에 설정한다. PAD는 response work 없이 tail만 이동한다.

각 entry 후 `tcmu_hdr_get_len()` 길이를 더해 `cmdr_size` modulo로 `cmd_tail`을 갱신하고 다음 entry를 다시 계산한다. 하나라도 처리했다면 32-bit 0을 UIO fd에 4 byte 써 완료를 kernel에 통지한다.

handle_device_events
ent at cmd_tailtail != head
TCMU_OP_CMDRead CDBBackend workSet SCSI status
Unknown opcodeSet UNKNOWN_OP
TCMU_OP_PADNo response work
Advance cmd_tail modulo ringMore entries?
Work donewrite(fd, 4)Notify kernel

예제 code의 branch와 tail 갱신 순서다.

Opcode 처리
Opcode동작
TCMU_OP_CMDCDB 처리 후 scsi_status와 선택적 sense data 설정
TCMU_OP_PADwork 없이 tail 갱신
unknownTCMU_UFLAG_UNKNOWN_OP 설정 후 진행

예제 handler가 각 entry type에 수행하는 작업이다.

c) Managing the command ring::

      #include <linux/target_core_user.h>

      int handle_device_events(int fd, void *map)
      {
        struct tcmu_mailbox *mb = map;
        struct tcmu_cmd_entry *ent = (void *) mb + mb->cmdr_off + mb->cmd_tail;
        int did_some_work = 0;

        /* Process events from cmd ring until we catch up with cmd_head */
        while (ent != (void *)mb + mb->cmdr_off + mb->cmd_head) {

          if (tcmu_hdr_get_op(ent->hdr.len_op) == TCMU_OP_CMD) {
            uint8_t *cdb = (void *)mb + ent->req.cdb_off;
            bool success = true;

            /* Handle command here. */
            printf("SCSI opcode: 0x%x\n", cdb[0]);

            /* Set response fields */
            if (success)
              ent->rsp.scsi_status = SCSI_NO_SENSE;
            else {
              /* Also fill in rsp->sense_buffer here */
              ent->rsp.scsi_status = SCSI_CHECK_CONDITION;
            }
          }
          else if (tcmu_hdr_get_op(ent->hdr.len_op) != TCMU_OP_PAD) {
            /* Tell the kernel we didn't handle unknown opcodes */
            ent->hdr.uflags |= TCMU_UFLAG_UNKNOWN_OP;
          }
          else {
            /* Do nothing for PAD entries except update cmd_tail */
          }

          /* update cmd_tail */
          mb->cmd_tail = (mb->cmd_tail + tcmu_hdr_get_len(&ent->hdr)) % mb->cmdr_size;
          ent = (void *) mb + mb->cmdr_off + mb->cmd_tail;
          did_some_work = 1;
        }

        /* Notify the kernel that work has been finished */
        if (did_some_work) {
          uint32_t buf = 0;

          write(fd, &buf, 4);
        }

        return 0;
      }

SCSI specification status code 사용

399-405

반환 code는 반드시 SCSI specification에 정의된 값을 사용해야 한다. 이 값은 `scsi/scsi.h`에 정의된 일부 값과 다르다. 예를 들어 CHECK CONDITION의 status code는 1이 아니라 2다.

Status code 주의
상태SCSI specification 값
CHECK CONDITION2

header의 다른 상수와 혼동하면 안 되는 예다.

A final note
============

Please be careful to return codes as defined by the SCSI
specifications. These are different than some values defined in the
scsi/scsi.h include file. For example, CHECK CONDITION's status code
is 2, not 1.