blob: 4ab1a7795ee85e09d0cc916f497a8bf21df3d4d8 [file]
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 Process Mission
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/drivers/disk.h>
#include <zephyr/drivers/virtio.h>
#include <zephyr/drivers/virtio/virtqueue.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#define DT_DRV_COMPAT virtio_blk
#define VIRTIO_BLK_QUEUE_IDX 0
/*
* The single request virtqueue holds one descriptor for the request header, up
* to VIRTIO_BLK_MAX_DATA_SEGS scatter-gather data descriptors, and one for the
* status byte. VIRTIO_BLK_MAX_DATA_SEGS also bounds the size of the on-stack
* scatter-gather segment array.
*/
#if defined(CONFIG_MMU)
#define VIRTIO_BLK_MAX_DATA_SEGS CONFIG_DISK_VIRTIO_BLK_MAX_SEGMENTS
#else
#define VIRTIO_BLK_MAX_DATA_SEGS 1
#endif
#define VIRTIO_BLK_REQUEST_QUEUE_SIZE Z_POW2_CEIL(VIRTIO_BLK_MAX_DATA_SEGS + 2)
BUILD_ASSERT(VIRTIO_BLK_REQUEST_QUEUE_SIZE <= 256,
"VIRTIO block request virtqueue must not exceed 256 entries");
/* Feature bits from VirtIO 1.3, section 5.2.3 */
#define VIRTIO_BLK_F_SIZE_MAX 1
#define VIRTIO_BLK_F_SEG_MAX 2
#define VIRTIO_BLK_F_GEOMETRY 4
#define VIRTIO_BLK_F_RO 5
#define VIRTIO_BLK_F_BLK_SIZE 6
#define VIRTIO_BLK_F_FLUSH 9
#define VIRTIO_BLK_F_TOPOLOGY 10
#define VIRTIO_BLK_F_CONFIG_WCE 11
#define VIRTIO_BLK_F_MQ 12
#define VIRTIO_BLK_F_DISCARD 13
#define VIRTIO_BLK_F_WRITE_ZEROES 14
#define VIRTIO_BLK_F_LIFETIME 15
#define VIRTIO_BLK_F_SECURE_ERASE 16
#define VIRTIO_BLK_F_ZONED 17
#define VIRTIO_BLK_T_IN 0
#define VIRTIO_BLK_T_OUT 1
#define VIRTIO_BLK_T_FLUSH 4
#define VIRTIO_BLK_S_OK 0
#define VIRTIO_BLK_S_IOERR 1
#define VIRTIO_BLK_S_UNSUPP 2
/* The VirtIO block protocol always addresses the device in 512-byte sectors. */
#define VIRTIO_BLK_SECTOR_SIZE 512U
BUILD_ASSERT((CONFIG_DISK_VIRTIO_BLK_SECTOR_SIZE % 512) == 0,
"DISK_VIRTIO_BLK_SECTOR_SIZE must be a multiple of 512");
#if defined(CONFIG_MMU)
BUILD_ASSERT(CONFIG_DISK_VIRTIO_BLK_SECTOR_SIZE <= CONFIG_MMU_PAGE_SIZE,
"DISK_VIRTIO_BLK_SECTOR_SIZE must not exceed the MMU page size");
#endif
LOG_MODULE_REGISTER(disk_virtio_blk, CONFIG_DISK_VIRTIO_BLK_LOG_LEVEL);
/*
* Header prefix of struct virtio_blk_req (VirtIO 1.3, section 5.2.6).
* The data payload and the status byte are placed in separate virtqueue
* descriptors, so only the first three fields are represented here.
*/
struct virtio_blk_req_hdr {
uint32_t type;
uint32_t reserved;
uint64_t sector;
};
/*
* Partial device-specific configuration layout from VirtIO 1.3, section 5.2.
* Fields beyond blk_size are omitted because the driver does not implement
* the corresponding features.
*/
struct virtio_blk_config {
uint64_t capacity;
uint32_t size_max;
uint32_t seg_max;
struct virtio_blk_geometry {
uint16_t cylinders;
uint8_t heads;
uint8_t sectors;
} geometry;
uint32_t blk_size;
} __packed;
struct virtio_blk_drv_config {
const struct device *vdev;
const char *disk_name;
};
struct virtio_blk_data {
const struct device *vdev;
struct disk_info info;
struct k_sem sem;
struct k_mutex lock;
uint64_t capacity;
uint32_t sector_size;
uint32_t sectors_per_block;
uint16_t max_data_segs;
bool read_only;
bool flush;
uint8_t status;
struct virtio_blk_req_hdr req_hdr;
};
/*
* A chunk is a physically-contiguous piece of a caller buffer within a single
* MMU page. One sector may span two pages, so it can produce up to two chunks;
* physically-adjacent chunks are coalesced into a scatter-gather segment.
*/
struct virtio_blk_chunk {
uintptr_t phys;
size_t len;
};
static int virtio_blk_disk_ioctl(struct disk_info *disk, uint8_t cmd, void *buff);
static const struct disk_operations virtio_blk_ops;
static uint16_t virtio_blk_enum_queues_cb(uint16_t q_index, uint16_t q_size_max, void *opaque)
{
struct virtio_blk_data *data = opaque;
if (q_index == VIRTIO_BLK_QUEUE_IDX) {
uint16_t request_queue_size = Z_POW2_CEIL(data->max_data_segs + 2U);
return MIN(request_queue_size, q_size_max);
}
return 0;
}
static void virtio_blk_complete(void *priv, uint32_t used_len)
{
struct virtio_blk_data *data = priv;
ARG_UNUSED(used_len);
k_sem_give(&data->sem);
}
/*
* The start_sector/num_sector arguments are in logical-block units (the sector
* size reported to disk_access); the device capacity is in 512-byte VirtIO
* sectors, so scale by sectors_per_block before comparing.
*/
static inline bool virtio_blk_sector_range_valid(struct virtio_blk_data *data, uint32_t start,
uint32_t count)
{
uint64_t end = ((uint64_t)start + count) * data->sectors_per_block;
return end <= data->capacity;
}
static int virtio_blk_negotiate_feature(const struct device *vdev, int bit, bool *negotiated)
{
int ret;
*negotiated = false;
if (!virtio_read_device_feature_bit(vdev, bit)) {
return 0;
}
ret = virtio_write_driver_feature_bit(vdev, bit, true);
if (ret != 0) {
LOG_ERR("failed to enable feature bit %d: %d", bit, ret);
return ret;
}
*negotiated = true;
return 0;
}
/*
* Submit a single descriptor chain: request header, zero or more caller data
* segments, and the status byte. Serialized by data->lock because req_hdr,
* status and the completion semaphore are shared per device (single in-flight
* request); disk_access does not serialize concurrent read/write callers.
*
* sector is expressed in 512-byte VirtIO sectors, as required by the protocol.
*/
static int virtio_blk_submit(struct virtio_blk_data *data, uint32_t type, uint64_t sector,
struct virtq_buf *data_segs, uint16_t n_segs, bool is_write)
{
struct virtq *vq = virtio_get_virtqueue(data->vdev, VIRTIO_BLK_QUEUE_IDX);
struct virtq_buf bufs[VIRTIO_BLK_MAX_DATA_SEGS + 2];
uint16_t dev_readable_count;
uint16_t bufs_count = 0;
uint8_t status;
int ret;
if (vq == NULL) {
return -ENODEV;
}
k_mutex_lock(&data->lock, K_FOREVER);
data->req_hdr.type = sys_cpu_to_le32(type);
data->req_hdr.reserved = 0;
data->req_hdr.sector = sys_cpu_to_le64(sector);
data->status = 0xff;
bufs[bufs_count].addr = &data->req_hdr;
bufs[bufs_count].len = sizeof(data->req_hdr);
bufs_count++;
for (uint16_t i = 0; i < n_segs; i++) {
bufs[bufs_count++] = data_segs[i];
}
bufs[bufs_count].addr = &data->status;
bufs[bufs_count].len = sizeof(data->status);
bufs_count++;
/*
* The header, and for a write all data segments, are device-readable;
* the status byte (and, for a read, the data) is device-writable.
*/
dev_readable_count = is_write ? (uint16_t)(1U + n_segs) : 1U;
ret = virtq_add_buffer_chain(vq, bufs, bufs_count, dev_readable_count, virtio_blk_complete,
data, K_FOREVER);
if (ret != 0) {
LOG_ERR("failed to submit request: %d", ret);
k_mutex_unlock(&data->lock);
return ret;
}
virtio_notify_virtqueue(data->vdev, VIRTIO_BLK_QUEUE_IDX);
k_sem_take(&data->sem, K_FOREVER);
if (data->status != VIRTIO_BLK_S_OK) {
status = data->status;
k_mutex_unlock(&data->lock);
LOG_ERR("request failed, status=%u", status);
return (status == VIRTIO_BLK_S_UNSUPP) ? -ENOTSUP : -EIO;
}
k_mutex_unlock(&data->lock);
return 0;
}
#if defined(CONFIG_MMU)
/*
* The virtqueue layer translates buffer addresses with k_mem_phys_addr(), which
* is only valid for the kernel's linear RAM mapping. A buffer outside that
* mapping (e.g. the thread-stack work area FatFS passes to f_mkfs()) is not
* necessarily physically contiguous, so it cannot be handed over as a single
* descriptor.
*
* Walk the sectors starting at buf, translate each page with
* arch_page_phys_get() to its real physical address, and coalesce physically
* adjacent chunks into scatter-gather segments. Each segment is expressed as its
* linear-mapping alias k_mem_virt_addr(phys); the virtqueue layer's
* k_mem_phys_addr() then reproduces the correct physical address. This is
* zero-copy and needs no shared bounce buffer.
*
* At most max_segs segments are produced. When the budget is exhausted the
* function stops on a sector boundary, so the caller can split a too-fragmented
* transfer across several device requests. Returns the number of sectors
* covered (>= 1) and stores the segment count in *n_out, or a negative errno.
*/
/*
* Translate one page to its physical address, using a one-page cache to avoid
* repeated lookups when a single sector spans two pages.
*/
static int virtio_blk_page_phys(uintptr_t page, uintptr_t *cached_page, uintptr_t *cached_phys,
bool *have_cache, uintptr_t *phys)
{
if (*have_cache && page == *cached_page) {
*phys = *cached_phys;
return 0;
}
if (arch_page_phys_get(UINT_TO_POINTER(page), phys) != 0) {
return -EIO;
}
*cached_page = page;
*cached_phys = *phys;
*have_cache = true;
return 0;
}
/*
* Split one sector, starting at sector offset sector_idx within buf, into at most two
* physically-contiguous chunks. A sector may cross a page boundary, producing two
* chunks; chunks that happen to be physically adjacent are merged into one.
* Returns the number of chunks produced, or a negative errno.
*/
static int virtio_blk_sector_to_chunks(const uint8_t *buf, uint32_t sector_size,
uint32_t sector_idx, struct virtio_blk_chunk *chunks,
uintptr_t *cached_page, uintptr_t *cached_phys,
bool *have_cache)
{
int nchunks = 0;
size_t sec_off = 0;
while (sec_off < sector_size) {
/* Virtual address of the next byte to be covered in this sector. */
uintptr_t va = POINTER_TO_UINT(buf) + (size_t)sector_idx * sector_size + sec_off;
/* Page containing that byte, and the offset within the page. */
uintptr_t page = ROUND_DOWN(va, CONFIG_MMU_PAGE_SIZE);
size_t page_off = va - page;
/* Length remaining in the sector or to the end of the page, whichever is smaller.
*/
size_t contig_len =
MIN((size_t)sector_size - sec_off, (size_t)CONFIG_MMU_PAGE_SIZE - page_off);
uintptr_t phys;
int ret;
ret = virtio_blk_page_phys(page, cached_page, cached_phys, have_cache, &phys);
if (ret != 0) {
return ret;
}
phys += page_off;
/* Merge with the previous chunk if the two are physically adjacent. */
if (nchunks > 0 && chunks[nchunks - 1].phys + chunks[nchunks - 1].len == phys) {
chunks[nchunks - 1].len += contig_len;
} else {
chunks[nchunks].phys = phys;
chunks[nchunks].len = contig_len;
nchunks++;
}
sec_off += contig_len;
}
return nchunks;
}
/*
* Append or coalesce one physical chunk into the output segment array.
*
* If the new chunk is physically adjacent to the previous segment, extend the
* previous segment instead of consuming a new descriptor. Otherwise start a
* new segment at segs[*n_segs]. The segment address is expressed via its
* linear-mapping alias so the virtqueue layer can translate it back.
*/
static void virtio_blk_commit_chunk(struct virtq_buf *segs, uint16_t *n_segs,
uintptr_t *prev_end_phys, uintptr_t chunk_phys,
size_t chunk_len)
{
/* Coalesce with the previous segment when the new chunk is contiguous. */
if (*n_segs > 0 && chunk_phys == *prev_end_phys) {
segs[*n_segs - 1].len += (uint32_t)chunk_len;
} else {
segs[*n_segs].addr = k_mem_virt_addr(chunk_phys);
segs[*n_segs].len = (uint32_t)chunk_len;
(*n_segs)++;
}
/* Record the end of the (possibly extended) last segment. */
*prev_end_phys = chunk_phys + chunk_len;
}
static int virtio_blk_build_segs(const uint8_t *buf, uint32_t sector_size, uint32_t num_sector,
struct virtq_buf *segs, uint16_t max_segs, uint16_t *n_out)
{
struct virtio_blk_chunk chunks[2];
uintptr_t prev_end_phys = 0;
uintptr_t cached_page = 0;
uintptr_t cached_phys = 0;
bool have_cache = false;
uint16_t n_segs = 0;
int nchunks;
int new_segs;
/*
* Walk the caller buffer sector by sector, translate each page to its
* physical address, and coalesce physically adjacent chunks into segments.
* Stop on a sector boundary when the descriptor budget is exhausted so the
* caller can submit what we have built and continue with a new request.
*/
for (uint32_t sector_idx = 0; sector_idx < num_sector; sector_idx++) {
nchunks = virtio_blk_sector_to_chunks(buf, sector_size, sector_idx, chunks,
&cached_page, &cached_phys, &have_cache);
if (nchunks < 0) {
return nchunks;
}
/*
* new_segs is the number of additional segment slots this sector would
* consume after coalescing with the previous segment (if any).
*/
new_segs = nchunks;
if (n_segs > 0 && chunks[0].phys == prev_end_phys) {
new_segs -= 1;
}
/* Not enough descriptors left for this sector: stop here. */
if ((uint32_t)n_segs + (uint32_t)new_segs > max_segs) {
if (sector_idx == 0) {
/* A single sector needs more segments than the budget. */
return -ENOBUFS;
}
*n_out = n_segs;
return (int)sector_idx;
}
for (int c = 0; c < nchunks; c++) {
virtio_blk_commit_chunk(segs, &n_segs, &prev_end_phys, chunks[c].phys,
chunks[c].len);
}
}
*n_out = n_segs;
return (int)num_sector;
}
static int virtio_blk_transfer_mmu(struct virtio_blk_data *data, uint32_t type,
uint32_t start_sector, const uint8_t *buf, uint32_t num_sector,
bool is_write)
{
struct virtq *vq = virtio_get_virtqueue(data->vdev, VIRTIO_BLK_QUEUE_IDX);
struct virtq_buf segs[VIRTIO_BLK_MAX_DATA_SEGS];
uint32_t sector_size = data->sector_size;
uint16_t max_data_descs;
uint32_t done = 0;
if (vq == NULL) {
return -ENODEV;
}
/* Reserve one descriptor for the header and one for the status byte. */
max_data_descs = (uint16_t)MIN((uint32_t)data->max_data_segs,
(vq->num > 2U) ? (uint32_t)(vq->num - 2U) : 1U);
while (done < num_sector) {
uint16_t n_segs = 0;
int n_sectors;
int ret;
n_sectors = virtio_blk_build_segs(buf + (size_t)done * sector_size, sector_size,
num_sector - done, segs, max_data_descs, &n_segs);
if (n_sectors < 0) {
return n_sectors;
}
ret = virtio_blk_submit(data, type,
(uint64_t)(start_sector + done) * data->sectors_per_block,
segs, n_segs, is_write);
if (ret != 0) {
return ret;
}
done += (uint32_t)n_sectors;
}
return 0;
}
#endif /* CONFIG_MMU */
/*
* Run a read or write transfer. The caller's buffer is passed to the device
* directly (zero copy) as one or more scatter-gather segments; a buffer outside
* the linear RAM mapping is translated page-by-page rather than copied.
*
* start_sector/num_sector are in logical-block units; the device is addressed
* in 512-byte VirtIO sectors, so scale by sectors_per_block on submission.
*/
static int virtio_blk_transfer(struct virtio_blk_data *data, uint32_t type, uint32_t start_sector,
const uint8_t *buf, uint32_t num_sector, bool is_write)
{
if (num_sector == 0U) {
return 0;
}
#if defined(CONFIG_MMU)
return virtio_blk_transfer_mmu(data, type, start_sector, buf, num_sector, is_write);
#else
struct virtq_buf seg;
uint32_t sector_size = data->sector_size;
/* Without an MMU every buffer is identity-mapped and contiguous. */
seg.addr = (void *)buf;
seg.len = (uint32_t)((size_t)num_sector * sector_size);
return virtio_blk_submit(data, type, (uint64_t)start_sector * data->sectors_per_block, &seg,
1, is_write);
#endif
}
static int virtio_blk_dev_init(const struct device *dev)
{
const struct virtio_blk_drv_config *cfg = dev->config;
struct virtio_blk_data *data = dev->data;
const uint8_t *dev_cfg_bytes;
struct virtq *vq;
bool have_blk_size = false;
bool have_seg_max = false;
uint32_t sector_size = CONFIG_DISK_VIRTIO_BLK_SECTOR_SIZE;
uint32_t seg_max = 0;
int ret;
if (!device_is_ready(cfg->vdev)) {
LOG_ERR("parent VirtIO device not ready");
return -ENODEV;
}
data->vdev = cfg->vdev;
data->info.name = cfg->disk_name;
data->info.ops = &virtio_blk_ops;
data->info.dev = dev;
k_sem_init(&data->sem, 0, 1);
k_mutex_init(&data->lock);
/*
* Negotiate feature bits before committing them; the device-specific
* configuration space must only be read once FEATURES_OK is set (see
* VirtIO 1.3, section 3.1.1).
*/
ret = virtio_blk_negotiate_feature(cfg->vdev, VIRTIO_BLK_F_RO, &data->read_only);
if (ret != 0) {
return ret;
}
ret = virtio_blk_negotiate_feature(cfg->vdev, VIRTIO_BLK_F_SEG_MAX, &have_seg_max);
if (ret != 0) {
return ret;
}
ret = virtio_blk_negotiate_feature(cfg->vdev, VIRTIO_BLK_F_FLUSH, &data->flush);
if (ret != 0) {
return ret;
}
ret = virtio_blk_negotiate_feature(cfg->vdev, VIRTIO_BLK_F_BLK_SIZE, &have_blk_size);
if (ret != 0) {
return ret;
}
ret = virtio_commit_feature_bits(cfg->vdev);
if (ret != 0) {
return ret;
}
dev_cfg_bytes = virtio_get_device_specific_config(cfg->vdev);
if (dev_cfg_bytes == NULL) {
LOG_ERR("missing device-specific configuration");
return -ENODEV;
}
data->capacity = sys_get_le64(dev_cfg_bytes + offsetof(struct virtio_blk_config, capacity));
/*
* seg_max is only valid when VIRTIO_BLK_F_SEG_MAX was negotiated. It
* caps the number of data descriptors in one request; the request header
* and status byte use separate descriptors.
*/
if (have_seg_max) {
seg_max = sys_get_le32(dev_cfg_bytes + offsetof(struct virtio_blk_config, seg_max));
if (seg_max == 0U) {
LOG_ERR("unsupported seg_max %u", seg_max);
return -ENOTSUP;
}
data->max_data_segs = (uint16_t)MIN((uint32_t)VIRTIO_BLK_MAX_DATA_SEGS, seg_max);
} else {
data->max_data_segs = VIRTIO_BLK_MAX_DATA_SEGS;
}
/*
* The VirtIO block protocol always addresses the device in 512-byte
* sectors and reports capacity in those units (VirtIO 1.3, section 5.2).
* The logical block size exposed to disk_access is the device-advertised
* blk_size when VIRTIO_BLK_F_BLK_SIZE was negotiated, otherwise the
* configured fallback. Transfers are converted to 512-byte VirtIO
* sectors via sectors_per_block.
*/
if (have_blk_size) {
sector_size =
sys_get_le32(dev_cfg_bytes + offsetof(struct virtio_blk_config, blk_size));
}
if (sector_size < VIRTIO_BLK_SECTOR_SIZE || (sector_size % VIRTIO_BLK_SECTOR_SIZE) != 0U) {
LOG_ERR("unsupported block size %u (must be a multiple of 512)", sector_size);
return -ENOTSUP;
}
#if defined(CONFIG_MMU)
if (sector_size > CONFIG_MMU_PAGE_SIZE) {
LOG_ERR("block size %u exceeds MMU page size %u", sector_size,
(uint32_t)CONFIG_MMU_PAGE_SIZE);
return -ENOTSUP;
}
#endif
data->sector_size = sector_size;
data->sectors_per_block = sector_size / VIRTIO_BLK_SECTOR_SIZE;
ret = virtio_init_virtqueues(cfg->vdev, 1, virtio_blk_enum_queues_cb, data);
if (ret != 0) {
return ret;
}
/*
* A normal request needs at least one header, one data and one status
* descriptor. Reject a virtqueue that cannot hold the minimum chain.
*/
vq = virtio_get_virtqueue(cfg->vdev, VIRTIO_BLK_QUEUE_IDX);
if (vq == NULL || vq->num < 3U) {
LOG_ERR("request virtqueue missing or too small");
return -ENOTSUP;
}
virtio_finalize_init(cfg->vdev);
LOG_INF("capacity %llu bytes, block size %u bytes", data->capacity * VIRTIO_BLK_SECTOR_SIZE,
data->sector_size);
return disk_access_register(&data->info);
}
static int virtio_blk_disk_init(struct disk_info *disk)
{
return virtio_blk_disk_ioctl(disk, DISK_IOCTL_CTRL_INIT, NULL);
}
static int virtio_blk_disk_status(struct disk_info *disk)
{
struct virtio_blk_data *data = CONTAINER_OF(disk, struct virtio_blk_data, info);
return data->read_only ? DISK_STATUS_WR_PROTECT : DISK_STATUS_OK;
}
static int virtio_blk_disk_read(struct disk_info *disk, uint8_t *data_buf, uint32_t start_sector,
uint32_t num_sector)
{
struct virtio_blk_data *data = CONTAINER_OF(disk, struct virtio_blk_data, info);
if (!virtio_blk_sector_range_valid(data, start_sector, num_sector)) {
return -EINVAL;
}
return virtio_blk_transfer(data, VIRTIO_BLK_T_IN, start_sector, data_buf, num_sector,
false);
}
static int virtio_blk_disk_write(struct disk_info *disk, const uint8_t *data_buf,
uint32_t start_sector, uint32_t num_sector)
{
struct virtio_blk_data *data = CONTAINER_OF(disk, struct virtio_blk_data, info);
if (data->read_only) {
return -EROFS;
}
if (!virtio_blk_sector_range_valid(data, start_sector, num_sector)) {
return -EINVAL;
}
return virtio_blk_transfer(data, VIRTIO_BLK_T_OUT, start_sector, data_buf, num_sector,
true);
}
static int virtio_blk_disk_ioctl(struct disk_info *disk, uint8_t cmd, void *buff)
{
struct virtio_blk_data *data = CONTAINER_OF(disk, struct virtio_blk_data, info);
if (buff == NULL &&
(cmd == DISK_IOCTL_GET_SECTOR_COUNT || cmd == DISK_IOCTL_GET_SECTOR_SIZE ||
cmd == DISK_IOCTL_GET_ERASE_BLOCK_SZ)) {
return -EINVAL;
}
switch (cmd) {
case DISK_IOCTL_GET_SECTOR_COUNT: {
uint64_t count = data->capacity / data->sectors_per_block;
if (count > UINT32_MAX) {
LOG_WRN("capacity exceeds 32-bit range");
}
*(uint32_t *)buff = (uint32_t)MIN(count, (uint64_t)UINT32_MAX);
break;
}
case DISK_IOCTL_GET_SECTOR_SIZE:
*(uint32_t *)buff = data->sector_size;
break;
case DISK_IOCTL_GET_ERASE_BLOCK_SZ:
*(uint32_t *)buff = 1U;
break;
case DISK_IOCTL_CTRL_INIT:
case DISK_IOCTL_CTRL_DEINIT:
break;
case DISK_IOCTL_CTRL_SYNC:
/*
* Only issue a flush when the device advertised a writeback cache via
* VIRTIO_BLK_F_FLUSH. Without it, there is nothing to flush; per
* VirtIO 1.3 ยง5.2.5.1 we may assume a writethrough cache and report
* success without sending a FLUSH request.
*/
if (!data->flush) {
return 0;
}
return virtio_blk_submit(data, VIRTIO_BLK_T_FLUSH, 0, NULL, 0, false);
default:
return -EINVAL;
}
return 0;
}
static const struct disk_operations virtio_blk_ops = {
.init = virtio_blk_disk_init,
.status = virtio_blk_disk_status,
.read = virtio_blk_disk_read,
.write = virtio_blk_disk_write,
.ioctl = virtio_blk_disk_ioctl,
};
#define VIRTIO_BLK_DEFINE(inst) \
static struct virtio_blk_data virtio_blk_data_##inst; \
static const struct virtio_blk_drv_config virtio_blk_drv_config_##inst = { \
.vdev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
.disk_name = DT_INST_PROP(inst, disk_name), \
}; \
DEVICE_DT_INST_DEFINE(inst, virtio_blk_dev_init, NULL, &virtio_blk_data_##inst, \
&virtio_blk_drv_config_##inst, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &virtio_blk_ops);
DT_INST_FOREACH_STATUS_OKAY(VIRTIO_BLK_DEFINE)