Vectorized IO to NAND media

With the basics of obtaining device information and constructing addresses in place one can dive into the task of constructing commands for doing vectorized IO.

As the section on background information describes, then there are handful of constraints to handle for IOs to NAND media to succeed.

Erase before write

The first constraint to handle is that a block must be erased before it can be written. We do so by constructing block-addresses for all blocks within a plane:

nvm_addr from_geo /dev/nvme0n1 0 0 0 10 0 0
nvm_addr from_geo /dev/nvme0n1 0 0 1 10 0 0

Yielding:

0x000000000000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 0}

0x000001000000000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 000, sec: 0}

With the addresses we can now construct a single command with two addresses and send of the erase:

NVM_CLI_PMODE="0" nvm_addr erase /dev/nvme0n1 0x000000000000000a 0x000001000000000a

On success, yielding:

# nvm_addr_erase: {pmode: SNGL}
0x000000000000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 0}
0x000001000000000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 000, sec: 0}

On error, yielding:

** nvm_addr_erase(...) : pmode(0x1)
(0x000000000000000a){ ch(00), lun(00), pl(0), blk(0010), pg(000), sec(0) }
(0x000001000000000a){ ch(00), lun(00), pl(1), blk(0010), pg(000), sec(0) }
nvm_addr_erase: Input/output error
nvm_ret { result(0xff), status(3) }

If an erase fails as above then it is because a block is bad. The bad-block-table interface (nvm_bbt) is provided to communicate and update media state. Introduction to the bad-block-table is given in a later section.

Note

C API for performing erases using block-adressing is done with nvm_addr_erase

Write

The two primary constraints for issuing writes are that they must be at the granularity of a full flash page and contiguous within a block.

Note

C API for performing write using vectorized IO with addressing at sector-level is done using nvm_addr_write, note that the payload must be aligned to sector size, the helper function nvm_buf_alloc is provided for convenience

Minimum write

For the geometry in figure X, a full flash page is four sectors of each 4096 bytes, a command satisfying the minimum-write constraint thus contains four addresses with a payload of 16384 bytes of data. The command can constructed via the CLI as:

nvm_addr write /dev/nvme0n1 \
0x000000000000000a 0x000000010000000a 0x000000020000000a 0x000000030000000a

The CLI creates an arbitrary payload, so we do not concern us with the payload at this point.

The result of the command is:

# nvm_addr_write : {pmode: DUAL}
0x000000000000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 0}
0x000000010000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 1}
0x000000020000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 2}
0x000000030000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 3}
nvm_ret: {result: 0x2, status: 0}
# nvm_addr_write: Input/output error
# write: Invalid argument

An unexpected write error occured, the constraints are satisfied, so what goes wrong? The issue in this case is that the command is constructed using plane-mode.

This introduces an additional constraint that writes must be performed to the block accross all planes. One can choose to disable the plane-mode, which is done by setting environment var NVM_CLI_PMODE="0" or by constructing a command satisfying the plane-mode constraint:

nvm_addr write /dev/nvme0n1 \
0x000000000000000a 0x000000010000000a 0x000000020000000a 0x000000030000000a \
0x000001000000000a 0x000001010000000a 0x000001020000000a 0x000001030000000a

Yielding without error:

# nvm_addr_write : {pmode: DUAL}
0x000000000000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 0}
0x000000010000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 1}
0x000000020000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 2}
0x000000030000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 3}
0x000001000000000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 000, sec: 0}
0x000001010000000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 000, sec: 1}
0x000001020000000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 000, sec: 2}
0x000001030000000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 000, sec: 3}

The plane-mode allows for multiple writes to be done in parallel across the planes.

Maximum write

An improvement of round-trip-time can be obtained by increasing the amount of work done by a single command, that is, increase the number of addresses.

There is an upper limit, write-naddrs-max, as can be seen in figure X, and retrieved from the device. In our case we can construct a command with 64 addresses.

Abiding to all of the above mentioned constraints a write command can be constructed as:

nvm_addr write /dev/nvme0n1 \
0x000000000001000a 0x000000010001000a 0x000000020001000a 0x000000030001000a \
0x000001000001000a 0x000001010001000a 0x000001020001000a 0x000001030001000a \
0x000000000002000a 0x000000010002000a 0x000000020002000a 0x000000030002000a \
0x000001000002000a 0x000001010002000a 0x000001020002000a 0x000001030002000a \
0x000000000003000a 0x000000010003000a 0x000000020003000a 0x000000030003000a \
0x000001000003000a 0x000001010003000a 0x000001020003000a 0x000001030003000a \
0x000000000004000a 0x000000010004000a 0x000000020004000a 0x000000030004000a \
0x000001000004000a 0x000001010004000a 0x000001020004000a 0x000001030004000a \
0x000000000005000a 0x000000010005000a 0x000000020005000a 0x000000030005000a \
0x000001000005000a 0x000001010005000a 0x000001020005000a 0x000001030005000a \
0x000000000006000a 0x000000010006000a 0x000000020006000a 0x000000030006000a \
0x000001000006000a 0x000001010006000a 0x000001020006000a 0x000001030006000a \
0x000000000007000a 0x000000010007000a 0x000000020007000a 0x000000030007000a \
0x000001000007000a 0x000001010007000a 0x000001020007000a 0x000001030007000a \
0x000000000008000a 0x000000010008000a 0x000000020008000a 0x000000030008000a \
0x000001000008000a 0x000001010008000a 0x000001020008000a 0x000001030008000a

Successfully yielding:

# nvm_addr_write : {pmode: DUAL}
0x000000000001000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 001, sec: 0}
0x000000010001000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 001, sec: 1}
0x000000020001000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 001, sec: 2}
0x000000030001000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 001, sec: 3}
0x000001000001000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 001, sec: 0}
0x000001010001000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 001, sec: 1}
0x000001020001000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 001, sec: 2}
0x000001030001000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 001, sec: 3}
0x000000000002000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 002, sec: 0}
0x000000010002000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 002, sec: 1}
0x000000020002000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 002, sec: 2}
0x000000030002000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 002, sec: 3}
0x000001000002000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 002, sec: 0}
0x000001010002000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 002, sec: 1}
0x000001020002000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 002, sec: 2}
0x000001030002000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 002, sec: 3}
0x000000000003000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 003, sec: 0}
0x000000010003000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 003, sec: 1}
0x000000020003000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 003, sec: 2}
0x000000030003000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 003, sec: 3}
0x000001000003000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 003, sec: 0}
0x000001010003000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 003, sec: 1}
0x000001020003000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 003, sec: 2}
0x000001030003000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 003, sec: 3}
0x000000000004000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 004, sec: 0}
0x000000010004000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 004, sec: 1}
0x000000020004000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 004, sec: 2}
0x000000030004000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 004, sec: 3}
0x000001000004000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 004, sec: 0}
0x000001010004000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 004, sec: 1}
0x000001020004000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 004, sec: 2}
0x000001030004000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 004, sec: 3}
0x000000000005000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 005, sec: 0}
0x000000010005000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 005, sec: 1}
0x000000020005000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 005, sec: 2}
0x000000030005000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 005, sec: 3}
0x000001000005000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 005, sec: 0}
0x000001010005000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 005, sec: 1}
0x000001020005000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 005, sec: 2}
0x000001030005000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 005, sec: 3}
0x000000000006000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 006, sec: 0}
0x000000010006000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 006, sec: 1}
0x000000020006000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 006, sec: 2}
0x000000030006000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 006, sec: 3}
0x000001000006000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 006, sec: 0}
0x000001010006000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 006, sec: 1}
0x000001020006000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 006, sec: 2}
0x000001030006000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 006, sec: 3}
0x000000000007000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 007, sec: 0}
0x000000010007000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 007, sec: 1}
0x000000020007000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 007, sec: 2}
0x000000030007000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 007, sec: 3}
0x000001000007000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 007, sec: 0}
0x000001010007000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 007, sec: 1}
0x000001020007000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 007, sec: 2}
0x000001030007000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 007, sec: 3}
0x000000000008000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 008, sec: 0}
0x000000010008000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 008, sec: 1}
0x000000020008000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 008, sec: 2}
0x000000030008000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 008, sec: 3}
0x000001000008000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 008, sec: 0}
0x000001010008000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 008, sec: 1}
0x000001020008000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 008, sec: 2}
0x000001030008000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 008, sec: 3}

Using vectorized IO we have with a single command successfully written a payload of 64 x 4096 bytes = 256 KB.

Read

Reads have fewer constraints than writes. The granularity of a read is a single sector (the smallest addressable unit) and can be performed non-contiguously.

The primary constraint for a read to adhere to is that the block which is read from must be closed. That is, all pages within the block must have been written. It might be that the constraint can be relaxed where only N pages ahead of the read must have been written instead of all pages in the block. The challenge with relaxing the constraint is that N is often an unknown size.

We have so far written a total of nine pages (across two planes), the first page in one command, the remaining eight pages in a second command. Thus we have 503 pages that need to be written before we can start reading.

Specifying the 503 x nplanes x nsectors = 4024 addresses via the CLI is tedious, we will therefore take a sneak peak at virtual blocks and execute:

nvm_vblk erase /dev/nvme0n1 0x000000000000000a
nvm_vblk write /dev/nvme0n1 0x000000000000000a
vblk:
  dev: {pmode: 'DUAL'}
  nbytes: 16777216
  nmbytes: 16
naddrs: 1
addrs:
  - 0x000000000000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 0}
nvm_vblk_erase: {elapsed: 0.005633}

vblk:
  dev: {pmode: 'DUAL'}
  nbytes: 16777216
  nmbytes: 16
naddrs: 1
addrs:
  - 0x000000000000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 0}
nvm_buf_alloc: {elapsed: 0.000012}
nvm_buf_fill: {elapsed: 0.019201}
nvm_vblk_write: {elapsed: 0.861263}

What these two commands actually do will be described in the following section on virtual blocks. For now all we need to know is that the block is now fully written / closed and we can start reading from it.

Note

C API for performing write using vectorized IO with addressing at sector-level is done using nvm_addr_read, the received payload must be stored in a sector-aligned buffer, the helper function nvm_buf_alloc is provided for convenience

Minimal Read

We can read a single sector:

nvm_addr read /dev/nvme0n1 0x000000000000000a
# nvm_addr_read: {pmode: DUAL}
0x000000000000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 0}

The data read from device can be written to a file system using the -o FILE option:

nvm_addr read /dev/nvme0n1 0x000000000000000a -o /tmp/dump.bin
# nvm_addr_read: {pmode: DUAL}
0x000000000000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 0}

The payload is then available by inspection, e.g. with hexdump:

hexdump /tmp/dump.bin -C -n 128
00000000  41 42 43 44 45 46 47 48  49 4a 4b 4c 4d 4e 4f 50  |ABCDEFGHIJKLMNOP|
00000010  51 52 53 54 55 56 57 58  59 5a 41 42 43 44 45 46  |QRSTUVWXYZABCDEF|
00000020  47 48 49 4a 4b 4c 4d 4e  4f 50 51 52 53 54 55 56  |GHIJKLMNOPQRSTUV|
00000030  57 58 59 5a 41 42 43 44  45 46 47 48 49 4a 4b 4c  |WXYZABCDEFGHIJKL|
00000040  4d 4e 4f 50 51 52 53 54  55 56 57 58 59 5a 41 42  |MNOPQRSTUVWXYZAB|
00000050  43 44 45 46 47 48 49 4a  4b 4c 4d 4e 4f 50 51 52  |CDEFGHIJKLMNOPQR|
00000060  53 54 55 56 57 58 59 5a  41 42 43 44 45 46 47 48  |STUVWXYZABCDEFGH|
00000070  49 4a 4b 4c 4d 4e 4f 50  51 52 53 54 55 56 57 58  |IJKLMNOPQRSTUVWX|
00000080

Maximum Read

Same as a write, a read has an upper bound on the number of addresses in a single command. Address-construction is equivalent.

Non-Contiguous Read

Reading pages 500, 200, 0, and 6 across planes:

nvm_addr read /dev/nvme0n1 \
0x0000000001f4000a 0x0000000101f4000a 0x0000000201f4000a 0x0000000301f4000a \
0x0000010001f4000a 0x0000010101f4000a 0x0000010201f4000a 0x0000010301f4000a \
0x0000000000c8000a 0x0000000100c8000a 0x0000000200c8000a 0x0000000300c8000a \
0x0000010000c8000a 0x0000010100c8000a 0x0000010200c8000a 0x0000010300c8000a \
0x000000000000000a 0x000000010000000a 0x000000020000000a 0x000000030000000a \
0x000001000000000a 0x000001010000000a 0x000001020000000a 0x000001030000000a \
0x000000000006000a 0x000000010006000a 0x000000020006000a 0x000000030006000a \
0x000001000006000a 0x000001010006000a 0x000001020006000a 0x000001030006000a

Successfully yielding:

# nvm_addr_read: {pmode: DUAL}
0x0000000001f4000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 500, sec: 0}
0x0000000101f4000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 500, sec: 1}
0x0000000201f4000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 500, sec: 2}
0x0000000301f4000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 500, sec: 3}
0x0000010001f4000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 500, sec: 0}
0x0000010101f4000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 500, sec: 1}
0x0000010201f4000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 500, sec: 2}
0x0000010301f4000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 500, sec: 3}
0x0000000000c8000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 200, sec: 0}
0x0000000100c8000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 200, sec: 1}
0x0000000200c8000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 200, sec: 2}
0x0000000300c8000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 200, sec: 3}
0x0000010000c8000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 200, sec: 0}
0x0000010100c8000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 200, sec: 1}
0x0000010200c8000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 200, sec: 2}
0x0000010300c8000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 200, sec: 3}
0x000000000000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 0}
0x000000010000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 1}
0x000000020000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 2}
0x000000030000000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 000, sec: 3}
0x000001000000000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 000, sec: 0}
0x000001010000000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 000, sec: 1}
0x000001020000000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 000, sec: 2}
0x000001030000000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 000, sec: 3}
0x000000000006000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 006, sec: 0}
0x000000010006000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 006, sec: 1}
0x000000020006000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 006, sec: 2}
0x000000030006000a: {ch: 00, lun: 00, pl: 0, blk: 0010, pg: 006, sec: 3}
0x000001000006000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 006, sec: 0}
0x000001010006000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 006, sec: 1}
0x000001020006000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 006, sec: 2}
0x000001030006000a: {ch: 00, lun: 00, pl: 1, blk: 0010, pg: 006, sec: 3}

It is worth mentioning that the vectorized reads can be non-contiguous not only within a block but also scattered across different different blocks, in different LUNs, and channels.

However, when using plane-mode ensure that addresses are constructed across planes and all sectors are read as in the example above.