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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
|
@page page_device_blk Block layer (`blk`)
# Block storage subsystem
The **blk** layer turns a single physical storage backend into one or more **RT-Thread block devices** that applications and **DFS** can open, read/write by **sector**, and control with standard IOCTLs. Hardware drivers implement **`struct rt_blk_disk_ops`** on an embedded **`struct rt_blk_disk`**; the core registers the disk, scans partition tables, and creates child volumes (**`struct rt_blk_device`**) such as **`sda1`** or **`mmcsd0p1`**.
Header: `components/drivers/include/drivers/blk.h`. IOCTLs and geometry: `components/drivers/include/drivers/classes/block.h`. Core: `components/drivers/block/blk.c`, `blk_dev.c`, `blk_partition.c`.
## Architecture
```
[NVMe / SDIO / SCSI SD / … driver]
│ rt_blk_disk_ops (read/write/getgeome/…)
▼
struct rt_blk_disk ← rt_hw_blk_disk_register()
│ rt_blk_disk_probe_partition() (inside register)
▼
struct rt_blk_device × N ← disk_add_blk_dev() / blk_dev.c
│ rt_device_read/write (sector-relative)
▼
DFS / applications / POSIX devio
```
| Layer | Struct | Document |
| --- | --- | --- |
| **Physical disk** | `struct rt_blk_disk` | @ref page_device_disk |
| **Partition table** | (on-disk metadata) | @ref page_device_partitions |
| **Volume (partition)** | `struct rt_blk_device` | **This page** — application-facing nodes |
Both disk and volume nodes use **`RT_Device_Class_Block`**. Applications normally open **volumes** (`sda1`), not the raw disk device, unless probing created a single full-disk volume with **`partno == 0`**.
## Kconfig
| Option | Role |
| --- | --- |
| **`RT_USING_BLK`** | Master switch for `components/drivers/block/` |
| **`RT_BLK_PARTITION_EFI`** | GPT / protective MBR parser (`partitions/efi.c`) |
| **`RT_BLK_PARTITION_DFS`** | Legacy DFS superblock layout on sector 0 (`partitions/dfs.c`, needs **`RT_USING_DFS`**) |
**`rt_hw_blk_disk_register`** always calls **`rt_blk_disk_probe_partition`**; partition scan errors are **ignored** (register still succeeds).
---
# Access block devices (applications)
Same usage model as other char/block devices: **find → open → read/write → control → close**. Sector index is **0-based within the opened device** (for a partition device, sector 0 is the start of that partition).
## Find device
```c
rt_device_t rt_device_find(const char *name);
```
| Parameter | Description |
| --- | --- |
| `name` | Volume name, e.g. **`sda0`**, **`sda1`**, **`mmcsd0`**, **`nvme0n1`** (from DM naming — see @ref page_device_disk) |
| **Return** | Device handle, or **`RT_NULL`** if not found |
```c
#define BLK_DEV_NAME "sda1"
rt_device_t blk = rt_device_find(BLK_DEV_NAME);
```
## Open / close
```c
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag);
rt_err_t rt_device_close(rt_device_t dev);
```
| `oflag` | Notes |
| --- | --- |
| **`RT_DEVICE_OFLAG_RDONLY`** | Always valid |
| **`RT_DEVICE_OFLAG_WRONLY`** | Fails on disk/volume if backend is read-only (**`ops->write == NULL`** sets **`read_only`**) |
Opening a **partition** device (`rt_blk_device`) internally **opens the parent disk** (`blk_dev_open` → `rt_device_open(&blk->disk->parent)`).
## Read / write (sectors)
```c
rt_ssize_t rt_device_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
rt_ssize_t rt_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
```
For block class devices, **`pos`** is the **starting sector number**, **`size`** is **sector count** (not bytes).
| Parameter | Partition volume | Whole disk |
| --- | --- | --- |
| `pos` | 0 … `sector_count - 1` (relative to partition) | 0 … disk capacity − 1 |
| `size` | Sectors to transfer | Same |
| **Return** | Sectors actually transferred, or negative error | Same |
```c
#define SECTOR_SIZE 512
rt_uint8_t buf[SECTOR_SIZE];
if (rt_device_read(blk, 0, buf, 1) == 1)
{
/* one sector at LBA 0 of this partition */
}
```
## Control (IOCTL)
```c
rt_err_t rt_device_control(rt_device_t dev, int cmd, void *args);
```
### Standard commands (`classes/block.h`)
| Command | `args` type | Partition volume | Raw disk |
| --- | --- | --- | --- |
| **`RT_DEVICE_CTRL_BLK_GETGEOME`** | `struct rt_device_blk_geometry *` | **`sector_count`** = partition size; **`bytes_per_sector`** from disk | Full disk geometry |
| **`RT_DEVICE_CTRL_BLK_SYNC`** | ignored | Flushes parent disk | **`ops->sync`** under lock |
| **`RT_DEVICE_CTRL_BLK_ERASE`** | ignored | Only if **`disk->partitions <= 1`**; else **`-RT_EIO`** | May remove all partitions then **`ops->erase`** |
| **`RT_DEVICE_CTRL_BLK_AUTOREFRESH`** | `rt_bool_t *` / pointer treated as enable flag | Delegates to disk if single partition | **`ops->autorefresh`** |
| **`RT_DEVICE_CTRL_BLK_PARTITION`** | `struct dfs_partition *` | Copies **`blk->partition`** | **`-RT_EINVAL`** on disk node |
### DFS helpers (`blk_dfs.h`)
| Command | Role |
| --- | --- |
| **`RT_DEVICE_CTRL_BLK_SSIZEGET`** | Fills `*(rt_off_t *)args` with **bytes per sector** |
| **`RT_DEVICE_CTRL_ALL_BLK_SSIZEGET`** | Total byte size = sectors × bytes_per_sector (used by DFS v2 POSIX layer) |
Vendor-specific IOCTLs pass through **`disk->ops->control(disk, blk, cmd, args)`** on the volume path; on the disk path **`blk` is NULL**.
### Geometry structure
```c
struct rt_device_blk_geometry {
rt_uint64_t sector_count;
rt_uint32_t bytes_per_sector;
rt_uint32_t block_size; /* erase block size for flash-style media */
};
```
---
# Partition volumes (`rt_blk_device`)
Defined in **`blk.h`** when DFS headers are available.
| Field | Meaning |
| --- | --- |
| `disk` | Parent **`rt_blk_disk`** |
| `sector_start` / `sector_count` | Slice on the physical disk (LBA range) |
| `partno` | Index used in device name (`%sp%d` / `%s%d`) |
| `partition` | **`struct dfs_partition`** for DFS mount; **`partition.lock`** → **`disk->usr_lock`** |
| `list` | Node on **`disk->part_nodes`** |
### `blk_dev.c` forwarding
| Operation | Behavior |
| --- | --- |
| **read/write** | Translates to **`disk->parent`** at **`sector_start + pos`** |
| **GETGEOME** | Disk geometry with **`sector_count`** replaced by partition length |
| **SYNC** | **`rt_device_control(&disk->parent, SYNC)`** |
| **ERASE / AUTOREFRESH** | Allowed only when **`disk->partitions <= 1`** |
### Device naming (DM)
**`disk_add_blk_dev`** (`blk_dev.c`):
- If disk name’s last character is **`'a'`–`'z'`** (e.g. **`sda`**): volume name **`sdap1`** (`%sp%d`).
- Otherwise (e.g. **`mmcsd0`**, **`nvme0n1`**): **`mmcsd0p1`**, **`nvme0n1p2`** (`%s%d`).
Each volume gets its own **`rt_dm_ida_alloc(disk->ida)`** slot under the same **`master_id`** as the disk.
---
# DFS integration
When **`RT_USING_DFS`** and **`RT_USING_POSIX_DEVIO`** (and DFS v2 as implemented in-tree), **`blk_dfs.c`** registers file operations that:
1. On **open**: **`GETGEOME`** + **`ALL_BLK_SSIZEGET`** for file size.
2. On **read/write**: byte-oriented I/O with unaligned head/tail sector handling via a bounce sector buffer.
3. **ioctl**: forwards to **`rt_device_control`**.
Mount the **volume** device name (not the disk) with the appropriate filesystem driver after partition probe.
---
# Drivers already using blk
These drivers embed **`struct rt_blk_disk`**, set **`ops`**, **`ida`**, and call **`rt_hw_blk_disk_register`** when media is present:
| Driver | File | Typical name | Notable `rt_blk_disk` fields |
| --- | --- | --- | --- |
| **NVMe** | `nvme/nvme.c` | `nvme0n1`, … | **`parallel_io = RT_TRUE`**, per-namespace disk, **`max_partitions = RT_BLK_PARTITION_MAX`** |
| **SDIO/MMC** | `sdio/dev_block.c` | host name / `mmcsd0p*` | **`removable`**, **`max_partitions = RT_MMCSD_MAX_PARTITION`**, sets geometry before register |
| **SCSI disk** | `scsi/scsi_sd.c` | `sda`, `sdb`, … | **`parallel_io`** from host, READ CAPACITY 10/16, **`sd_ida`** |
| **SCSI CD-ROM** | `scsi/scsi_cdrom.c` | optical media | Read-only style backend |
**AHCI/SATA** path: **`ata/ahci.c`** exposes SCSI devices; **SD card** goes through **`scsi_sd`** or **MMC `dev_block`**, then blk.
### Minimal driver pattern
```c
static const struct rt_blk_disk_ops my_blk_ops = {
.read = my_read,
.write = my_write,
.getgeome = my_getgeome,
.sync = my_sync,
};
static struct rt_dm_ida my_ida = RT_DM_IDA_INIT(MY_DISK);
void my_disk_register(struct my_hw *hw)
{
struct rt_blk_disk *disk = &hw->blk;
disk->ops = &my_blk_ops;
disk->ida = &my_ida;
disk->parallel_io = RT_FALSE; /* or RT_TRUE if ops are reentrant */
rt_dm_dev_set_name(&disk->parent.parent, "sda"); /* example */
rt_hw_blk_disk_register(disk); /* also runs partition probe */
}
```
Details: @ref page_device_disk.
---
# Locks and concurrency
| Mechanism | Scope |
| --- | --- |
| **`disk->usr_lock`** (semaphore) | Serializes disk **`read`/`write`/`sync`** when **`parallel_io == 0`** |
| **`disk->lock`** (spinlock) | Partition list, unregister, erase path |
| **`parallel_io == 1`** | Disk **`read`/`write`** skip **`usr_lock`** — backend must be safe for concurrent I/O |
Partition volume I/O still goes through the disk node and follows the disk’s **`parallel_io`** / lock rules.
---
# Typical bring-up sequence
1. Allocate **`struct rt_blk_disk`** (often embedded in driver private data).
2. Implement **`rt_blk_disk_ops`**; set **`ida`**, name, flags (**`read_only`**, **`removable`**, **`parallel_io`**, **`max_partitions`**).
3. **`rt_hw_blk_disk_register(disk)`** — partition probe runs automatically.
4. Application or init code **`rt_device_find("sda1")`**, mount DFS, or run filesystem code.
5. **`rt_hw_blk_disk_unregister(disk)`** after unmount — syncs, removes all volumes, checks **`ref_count`**.
To **re-scan** partitions after media change, unregister, update geometry, register again (or call **`rt_blk_disk_probe_partition`** only if **`disk->partitions == 0`**).
---
# Driver / BSP quick notes
| Topic | Guidance |
| --- | --- |
| **Name before register** | First character of **`disk->parent.parent.name`** must be non-NUL |
| **`ida`** | Mandatory with **`RT_USING_DM`**; disk and each volume consume IDA slots |
| **Probe errors** | Ignored at register — check **`disk->partitions`** or open volumes explicitly |
| **`max_partitions`** | Set **`RT_BLK_PARTITION_NONE`** to skip table parsing |
| **Read-only** | Omit **`write`** in **`ops`** — core forces **`read_only`** |
| **Unmount first** | **`unregister`** returns **`-RT_EBUSY`** if **`ref_count > 0`** |
| **Erase** | Disk erase IOCTL removes **all** partition devices first |
## See also
- @ref page_device_disk — `rt_blk_disk` / `rt_blk_disk_ops` / register API
- @ref page_device_partitions — GPT/EFI and DFS parsers
- @ref page_device_ata — AHCI → SCSI → blk path
- @ref page_device_nvme
- @ref page_device_sdio
- @ref page_device_scsi
- `components/drivers/block/blk.c`
- `components/drivers/block/blk_dev.c`
- `components/drivers/block/blk_partition.c`
|