RT-Thread RTOS
An open source embedded real-time operating system
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 Physical disk (rt_blk_disk)
Partition table (on-disk metadata) Disk partition probing
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

rt_device_t rt_device_find(const char *name);
Definition: rtdef.h:1369
Parameter Description
name Volume name, e.g. **sda0**, **sda1**, **mmcsd0**, **nvme0n1** (from DM naming — see Physical disk (rt_blk_disk))
Return Device handle, or **RT_NULL** if not found
#define BLK_DEV_NAME "sda1"
rt_device_t blk = rt_device_find(BLK_DEV_NAME);

Open / close

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_openrt_device_open(&blk->disk->parent)).

Read / write (sectors)

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
#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)

rt_err_t rt_device_control(rt_device_t dev, int cmd, void *args);

Standard commands (<tt>classes/block.h</tt>)

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 (<tt>blk_dfs.h</tt>)

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

rt_uint64_t sector_count;
rt_uint32_t bytes_per_sector;
rt_uint32_t block_size; /* erase block size for flash-style media */
};
Definition: block.h:27
rt_uint32_t block_size
Definition: block.h:30
rt_uint64_t sector_count
Definition: block.h:28
rt_uint32_t bytes_per_sector
Definition: block.h:29

Partition volumes (<tt>rt_blk_device</tt>)

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 (spd / sd)
partition **struct dfs_partition** for DFS mount; **partition.lock** → **disk->usr_lock**
list Node on **disk->part_nodes**

<tt>blk_dev.c</tt> 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** (spd).
  • Otherwise (e.g. **mmcsd0**, **nvme0n1**): **mmcsd0p1**, **nvme0n1p2** (sd`).

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

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 */
}
rt_err_t rt_hw_blk_disk_register(struct rt_blk_disk *disk)
Register the blk disk device.
Configure the blk device.
Definition: blk.h:63
Physical blk device.
Definition: blk.h:35
struct rt_object parent
Definition: rtdef.h:1370

Details: Physical disk (rt_blk_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