1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
|
@page page_device_scsi SCSI host and commands
# SCSI subsystem
RT-Thread provides a **small SCSI core** for storage backends: a **host** (`rt_scsi_host`) scans targets, issues commands through **`rt_scsi_ops::transfer`**, and attaches **type-specific upper drivers** (disk `sd*`, CD-ROM, …) that register **`rt_blk_disk`** devices.
The core is **not** a device-model bus (see comment in `scsi.c`) — there is no `rt_scmi`-style match/probe bus. Host adapters call **`rt_scsi_host_register()`** after filling **`rt_scsi_host`**.
| Piece | Path |
| --- | --- |
| API / CDB layouts | `components/drivers/include/drivers/scsi.h` |
| Core | `components/drivers/scsi/scsi.c` |
| Block disk (type 0) | `components/drivers/scsi/scsi_sd.c` |
| CD-ROM (type 5) | `components/drivers/scsi/scsi_cdrom.c` |
**Kconfig**: `RT_USING_SCSI` (needs `RT_USING_DM`). Options `RT_SCSI_SD` / `RT_SCSI_CDROM` (need `RT_USING_BLK`).
---
## End-to-end flow
```
Host adapter (AHCI / UFS / VirtIO-SCSI / …)
fills rt_scsi_host { dev, ops, max_id, max_lun, parallel_io }
calls rt_scsi_host_register()
↓
For each (id, lun): INQUIRY → devtype / removable
unknown type or inquiry fail → skip LUN
devtype >= SCSI_DEVICE_TYPE_MAX → shrink scan, stop
↓
scsi_device_setup(sdev): optional reset → TEST UNIT READY (5 s) → driver_table[devtype].probe
↓
scsi_sd_probe / scsi_cdrom_probe: READ CAPACITY → rt_hw_blk_disk_register ("sd*a", …)
↓
Applications use blk API (@ref page_device_blk, @ref page_device_disk)
```
**Unregister**: `rt_scsi_host_unregister()` walks **`lun_nodes`**, optional **`ops->reset`**, type **`remove`**, frees each **`rt_scsi_device`**.
---
## Data structures (`scsi.h`)
### Host and device
```c
struct rt_scsi_host {
struct rt_device *dev; /* required; DMA / naming */
const struct rt_scsi_ops *ops;
rt_size_t max_id; /* both must be non-zero */
rt_size_t max_lun;
rt_bool_t parallel_io; /* hint for blk layer */
rt_list_t lun_nodes; /* filled by register */
};
struct rt_scsi_device {
struct rt_scsi_host *host;
rt_list_t list;
rt_size_t id;
rt_size_t lun;
rt_uint32_t devtype; /* from INQUIRY */
rt_uint32_t removable;
rt_size_t last_block; /* filled by READ CAPACITY helpers */
rt_size_t block_size;
void *priv; /* scsi_sd / scsi_cdrom */
};
struct rt_scsi_ops {
rt_err_t (*reset)(struct rt_scsi_device *sdev); /* optional */
rt_err_t (*transfer)(struct rt_scsi_device *sdev, struct rt_scsi_cmd *cmd); /* required */
};
```
### Command envelope
```c
struct rt_scsi_cmd {
union { /* CDB: inquiry, read10, write16, … */ } op;
rt_size_t op_size;
union {
struct { /* fixed response structs */ } ;
struct { void *ptr; rt_size_t size; } data; /* DMA buffer for read/write */
} data;
};
```
Build CDBs with packed structs in **`scsi.h`** (`rt_scsi_inquiry`, `rt_scsi_read10`, …) or fill **`cmd.op`** manually. Set **`op_size`** to the CDB length your transport accepts.
High-level helpers in **`scsi.c`** (`rt_scsi_inquiry`, `rt_scsi_read10`, `rt_scsi_write16`, `rt_scsi_read_capacity10`, …) assemble **`rt_scsi_cmd`** and call **`host->ops->transfer`**. On failure they often issue **`rt_scsi_request_sense`**.
**`rt_scsi_cmd_is_write(cmd)`** — used by VirtIO-SCSI to pick IN vs OUT virtqueue buffers.
---
## `rt_scsi_host_register` / `unregister`
### Before register (caller)
| Field | Rule |
| --- | --- |
| `dev` | Non-NULL |
| `ops` | Non-NULL; **`transfer`** required |
| `max_id` / `max_lun` | Both non-zero; define scan grid |
| `parallel_io` | Set if adapter can pipeline multiple LUN commands |
| `lun_nodes` | Do not pre-fill; core initializes the list |
### Scan behavior (`scsi.c`)
1. Nested loop **`id ∈ [0, max_id)`**, **`lun ∈ [0, max_lun)`**.
2. **`rt_scsi_inquiry(&tmp_sdev, NULL)`** — failure skips the LUN.
3. If **`devtype >= SCSI_DEVICE_TYPE_MAX`**, scan **stops** and **`max_id` / `max_lun`** are clipped to the current index (simple bus assumption).
4. Allocated **`rt_scsi_device`** is copied from the temp device (id, lun, devtype, removable).
5. **`scsi_device_setup`**: optional **`reset`** → wait **TEST UNIT READY** (up to ~5 s) → **`driver_table[devtype].probe`**.
Returns **`RT_EOK`** if at least one LUN probed; **`-RT_EEMPTY`** if none; **`-RT_EINVAL`** / **`-RT_ENOMEM`** on bad args or OOM (partial LUNs may remain on ENOMEM mid-scan).
### `transfer` contract
- Fill or consume **`cmd->data`** (`ptr` + `size` for READ/WRITE; embedded structs for INQUIRY / capacity).
- Return **`RT_EOK`** on command completion; RT error codes on transport failure.
- Sense: adapters may copy sense into **`cmd->data.request_sense`**; helpers already call **REQUEST SENSE** after some failures.
---
## Upper-layer drivers (`driver_table`)
| `devtype` (`SCSI_DEVICE_TYPE_*`) | Kconfig | Probe | Block device |
| --- | --- | --- | --- |
| `0x00` DIRECT | `RT_SCSI_SD` | `scsi_sd_probe` | `sd*a` — read/write/sync/erase |
| `0x05` CDROM | `RT_SCSI_CDROM` | `scsi_cdrom_probe` | `sr*a` — read-only |
Other inquiry types log **not supported** and are skipped.
**`scsi_sd_probe`**: READ CAPACITY 10; if last LBA is `0xffffffff`, READ CAPACITY 16 and use READ/WRITE **16** ops; sets geometry from **`sdev->block_size`** / **`last_block`**; **`rt_hw_blk_disk_register`**. Honors **`host->parallel_io`** on the disk.
**`scsi_cdrom_probe`**: capacity + read-only **`rt_blk_disk`** (uses READ 10/12/16 by LBA width).
---
## Built-in SCSI hosts (examples)
These embed **`struct rt_scsi_host parent`** and call **`rt_scsi_host_register`** after hardware init:
| Backend | Source | Notes |
| --- | --- | --- |
| **AHCI** | `ata/ahci.c` | SATA/ATAPI → `ahci_scsi_ops.transfer`; see @ref page_device_ata |
| **UFS** | `ufs/ufs.c` | UTP carries SCSI CDBs; `ufs_host_ops`, often `parallel_io = RT_TRUE` |
| **VirtIO SCSI** | `virtio/virtio-scsi.c` | `RT_VIRTIO_DRIVER_EXPORT`; 3 queues; hotplug via `config_changed` + work queue re-register |
Typical host setup (pattern):
```c
struct my_hba {
struct rt_scsi_host parent;
/* private */
};
static rt_err_t my_scsi_transfer(struct rt_scsi_device *sdev, struct rt_scsi_cmd *cmd)
{
/* map id/lun, push CDB + data to hardware */
return RT_EOK;
}
static const struct rt_scsi_ops my_scsi_ops = {
.reset = my_scsi_reset, /* optional */
.transfer = my_scsi_transfer,
};
/* after controller ready */
hba->parent.dev = &controller_dev;
hba->parent.ops = &my_scsi_ops;
hba->parent.max_id = 1;
hba->parent.max_lun = 8;
hba->parent.parallel_io = RT_FALSE;
rt_scsi_host_register(&hba->parent);
```
Teardown: **`rt_scsi_host_unregister(&hba->parent)`** before freeing the controller.
---
## Command reference (`scsi.h`)
Opcodes and structs are defined for common storage commands:
| Opcode macro | Struct | Helper |
| --- | --- | --- |
| `RT_SCSI_CMD_INQUIRY` | `rt_scsi_inquiry` | `rt_scsi_inquiry()` |
| `RT_SCSI_CMD_TEST_UNIT_READY` | `rt_scsi_test_unit_ready` | `rt_scsi_test_unit_ready()` |
| `RT_SCSI_CMD_READ_CAPACITY10/16` | capacity structs | `rt_scsi_read_capacity10/16()` |
| `RT_SCSI_CMD_READ10/12/16` | read structs | `rt_scsi_read10/12/16()` |
| `RT_SCSI_CMD_WRITE10/12/16` | write structs | `rt_scsi_write10/12/16()` |
| `RT_SCSI_CMD_SYNCHRONIZE_CACHE10/16` | sync cache | `rt_scsi_synchronize_cache10/16()` |
| `RT_SCSI_CMD_WRITE_SAME10/16` | write same | `rt_scsi_write_same10/16()` (erase path in `scsi_sd`) |
| Mode select/sense | mode structs | `rt_scsi_mode_*` (auto-refresh in `scsi_sd`) |
Use these when implementing **`transfer`** for a new HBA or when extending **`driver_table`** for another device type.
---
## Debugging
| Symptom | Checks |
| --- | --- |
| `-RT_EEMPTY` | No LUN answered INQUIRY — wiring, power, `max_id`/`max_lun`, firmware |
| `-RT_ETIMEOUT` in setup | Media not ready — TEST UNIT READY loop |
| `-RT_ENOSYS` | Unsupported `devtype` — enable `RT_SCSI_SD` / `RT_SCSI_CDROM` or add `driver_table` entry |
| READ/WRITE fail | Sense via `rt_scsi_request_sense`; verify `cmd->data.size` is in **sectors** for READ/WRITE helpers |
| VirtIO hotplug | `virtio_scsi_config_changed` → rescan; log `virtio.dev.scsi` |
---
## See also
- @ref page_device_ata — AHCI port → SCSI → `sd*`
- @ref page_device_blk — block layer API
- @ref page_device_disk — `rt_blk_disk` registration
- `components/drivers/include/drivers/scsi.h`
- `components/drivers/scsi/scsi.c`
- `components/drivers/virtio/virtio-scsi.c`
|