Menu
On this page

UBLK_U_CMD_SET_PARAMS gives the kernel everything it needs to build the block device at START_DEV: capacity, block sizes, request-size limits, attributes, and optional blocks for discard, zones, DMA alignment, segments and integrity. The kernel turns them into the disk’s queue_limits, which is what you later see under /sys/block/ublkbN/queue/.

Layout and versioning#

struct ublk_params {
    __u32 len;     /* bytes of this structure the caller is passing */
    __u32 types;   /* UBLK_PARAM_TYPE_* bits: which blocks below are valid */
    struct ublk_param_basic     basic;      /* offset   8, 32 bytes */
    struct ublk_param_discard   discard;    /* offset  40, 20 bytes */
    struct ublk_param_devt      devt;       /* offset  60, 16 bytes */
    struct ublk_param_zoned     zoned;      /* offset  76, 32 bytes */
    struct ublk_param_dma_align dma;        /* offset 108,  8 bytes */
    struct ublk_param_segment   seg;        /* offset 120, 16 bytes (4 bytes padding before) */
    struct ublk_param_integrity integrity;  /* offset 136, 16 bytes */
};                                          /* 152 bytes as of 7.0 */
Type bitBlockSinceSettable
UBLK_PARAM_TYPE_BASIC (1 « 0)basic6.0yes, and mandatory
UBLK_PARAM_TYPE_DISCARD (1 « 1)discard6.0yes
UBLK_PARAM_TYPE_DEVT (1 « 2)devt6.3no: read-only, filled by GET_PARAMS
UBLK_PARAM_TYPE_ZONED (1 « 3)zoned6.6yes, with UBLK_F_ZONED
UBLK_PARAM_TYPE_DMA_ALIGN (1 « 4)dma6.15yes
UBLK_PARAM_TYPE_SEGMENT (1 « 5)seg6.15yes
UBLK_PARAM_TYPE_INTEGRITY (1 « 6)integrity7.0yes, with UBLK_F_INTEGRITY

The structure only grows at the end, so a server compiled against an older header sends a smaller len and a newer kernel reads what it sent. The other direction is the trap: the kernel copies at most its own sizeof(struct ublk_params) and then clears every types bit it does not know, without an error. On a 6.14 kernel, a SET_PARAMS with UBLK_PARAM_TYPE_SEGMENT succeeds and the segment limits are silently ignored. If a block matters, read the parameters back with GET_PARAMS and check types.

len must be non-zero, no larger than the control header’s len, and types must be non-zero. SET_PARAMS is accepted only before the disk exists; after START_DEV it fails with EACCES. A validation failure returns EINVAL and clears every parameter, including ones that were valid.

basic#

struct ublk_param_basic {
    __u32 attrs;               /* UBLK_ATTR_* */
    __u8  logical_bs_shift;
    __u8  physical_bs_shift;
    __u8  io_opt_shift;
    __u8  io_min_shift;
    __u32 max_sectors;
    __u32 chunk_sectors;
    __u64 dev_sectors;
    __u64 virt_boundary_mask;
};
FieldMeaningRule
logical_bs_shiftlog2 of the logical block size: the smallest addressable unit9 to PAGE_SHIFT (512 bytes to the page size)
physical_bs_shiftlog2 of the physical block size: the unit the backend writes atomically or efficientlyat least logical_bs_shift
io_min_shift, io_opt_shiftlog2 of the minimum and optimal I/O sizes; hints exported to filesystems and toolsnone (an io_min_shift below the physical block size is raised to it; io_opt_shift 0 is reported as no optimal size from 6.13, and as 1 byte in optimal_io_size before)
max_sectorslargest request, in 512-byte sectorsat most max_io_buf_bytes >> 9 from ADD_DEV
chunk_sectorsboundary requests must not cross, in sectors; 0 for nonerequired (the zone size) on zoned devices
dev_sectorscapacity in 512-byte sectorsshould be a multiple of the logical block size
virt_boundary_maskrequests’ scatter-gather segments must not cross this boundary (NVMe-style PRP constraints); 0 for none
attrsUBLK_ATTR_*, below

dev_sectors and max_sectors count 512-byte sectors even on a 4Kn device. A 1 GiB device with 4096-byte blocks has dev_sectors = 2097152 and logical_bs_shift = 12.

Attributes#

AttributeEffect
UBLK_ATTR_READ_ONLY (1 « 0)The disk is created read-only (set_disk_ro); writes fail in the block layer
UBLK_ATTR_ROTATIONAL (1 « 1)Marks the device rotational (/sys/block/ublkbN/queue/rotational = 1), which changes I/O scheduler and filesystem heuristics
UBLK_ATTR_VOLATILE_CACHE (1 « 2)The device has a write-back cache: the kernel will send FLUSH
UBLK_ATTR_FUA (1 « 3)The device honors per-write FUA. Ignored without UBLK_ATTR_VOLATILE_CACHE

The last two define the durability contract. If a completed write can be lost in a power failure, set UBLK_ATTR_VOLATILE_CACHE and implement flush.

discard#

struct ublk_param_discard {
    __u32 discard_alignment;
    __u32 discard_granularity;
    __u32 max_discard_sectors;
    __u32 max_write_zeroes_sectors;
    __u16 max_discard_segments;
    __u16 reserved0;
};

This one block enables two independent operations. max_discard_sectors > 0 enables UBLK_IO_OP_DISCARD; max_write_zeroes_sectors > 0 enables UBLK_IO_OP_WRITE_ZEROES. Without the block, or with both at zero, the block layer reports neither, and blkdiscard fails with “operation not supported”.

FieldMeaningRule
discard_granularitythe backend’s allocation unit in bytes; discards are aligned and sized to itnon-zero whenever the block is present, even for write-zeroes only
discard_alignmentoffset in bytes of the first granularity-aligned unit
max_discard_sectorslargest discard in 512-byte sectors; 0 disables discard0xffffffff means “as large as the block layer allows”
max_write_zeroes_sectorslargest write-zeroes; 0 disables it
max_discard_segmentsranges per discard requestmust be exactly 1 when discard is enabled

Advertise only what the backend implements. A server that advertises discard and then drops discards is honest (discard is advisory); one that advertises write-zeroes and does not zero the range corrupts data.

devt#

struct ublk_param_devt {
    __u32 char_major, char_minor;   /* /dev/ublkcN */
    __u32 disk_major, disk_minor;   /* /dev/ublkbN; 0 until START_DEV */
};

Read-only. Including UBLK_PARAM_TYPE_DEVT in a SET_PARAMS fails with EINVAL. GET_PARAMS always fills it. Useful for finding the device nodes inside a container or without udev.

zoned#

struct ublk_param_zoned {
    __u32 max_open_zones;
    __u32 max_active_zones;
    __u32 max_zone_append_sectors;
    __u8  reserved[20];
};

Valid only on a device created with UBLK_F_ZONED, and required on one. max_zone_append_sectors must be non-zero; the open and active limits may not exceed the number of zones (capacity divided by basic.chunk_sectors). See Zoned devices.

dma_align#

struct ublk_param_dma_align {
    __u32 alignment;   /* a mask: required alignment minus one */
    __u8  pad[4];
};

The memory alignment the block layer must guarantee for request buffers, as a mask (alignment + 1 must be a power of two, and alignment smaller than the page size). Without the block, the kernel uses a mask of 3 (4-byte alignment). It matters for zero copy, where request pages are handed straight to the backend: a backend file opened O_DIRECT typically needs 512-byte alignment (mask 511).

segment#

struct ublk_param_segment {
    __u64 seg_boundary_mask;   /* segments must not cross (mask + 1)-byte boundaries */
    __u32 max_segment_size;
    __u16 max_segments;
    __u8  pad[2];
};

Scatter-gather limits for the request, mostly to make zero-copy requests acceptable to the backend without being split again. seg_boundary_mask + 1 must be a power of two and at least 4096 (UBLK_MIN_SEGMENT_SIZE); max_segment_size must be at least 4096. The header warns that setting any of the three to 0 is undefined. Without the block, the kernel allows up to 65535 segments of unlimited size.

integrity#

struct ublk_param_integrity {
    __u32 flags;                   /* LBMD_PI_CAP_* from linux/fs.h */
    __u16 max_integrity_segments;  /* 0 means no limit */
    __u8  interval_exp;
    __u8  metadata_size;           /* must be non-zero */
    __u8  pi_offset;
    __u8  csum_type;               /* LBMD_PI_CSUM_* */
    __u8  tag_size;
    __u8  pad[5];
};

Valid only with UBLK_F_INTEGRITY (Linux 7.0). Describes per-block metadata and protection information; see Integrity metadata.

A worked example#

A 64 GiB device with 4096-byte logical and physical blocks, 1 MiB maximum requests, a write-back cache, discard and write-zeroes on a 4 KiB allocation unit:

struct ublk_params p = {
    .len   = sizeof(p),
    .types = UBLK_PARAM_TYPE_BASIC | UBLK_PARAM_TYPE_DISCARD,
    .basic = {
        .attrs              = UBLK_ATTR_VOLATILE_CACHE,
        .logical_bs_shift   = 12,
        .physical_bs_shift  = 12,
        .io_min_shift       = 12,
        .io_opt_shift       = 20,
        .max_sectors        = (1 << 20) >> 9,             /* 2048 */
        .dev_sectors        = (64ULL << 30) >> 9,         /* 134217728 */
    },
    .discard = {
        .discard_granularity      = 4096,
        .max_discard_sectors      = 0xffffffff,
        .max_write_zeroes_sectors = 0xffffffff,
        .max_discard_segments     = 1,
    },
};

max_sectors must not exceed what ADD_DEV returned in max_io_buf_bytes, so compute it from the negotiated value, not the requested one.