That reminds me quite strongly of VirtIO (block) devices... and yet the actual command format is, of course, different. Why can't we stop re-inveting things over and over?
Because different use cases require different designs? If you try to create a protocol that can work for all purposes, it'll be a poor fit for any of them and will be out-competed by more specialized alternatives.
There's a reason emulators design their virtual devices to resemble real hardware (PCI, SCSI, USB) -- there's already going to be a bunch of code in the hypervisor to create fake hardware. It's also more practical to piggy-back on PCI (etc) when the spec needs to be implemented by competing vendors, since there's no kernel and no OS idioms involved. Not to mention various pre-kernel code such as EFI and bootloaders.
Conversely, userspace developers really do not want to be coding up a fake PCI device with registers and interrupts and so on just to get some bytes into the kernel. They want to invoke system calls (ioctl, mmap, io_uring) and let the OS handle the details.
The basis of virtio is literally just a ring queue, with its request descriptors looking almost exactly like structs suitable for passing to readv(2) or writev(2); the PCI shim is built on top of that and is completely optional (you can have a purely MMIO virtio device, after all). It was built this way so that KVM would not have to mimic the idiosyncrasies of real hardware: passing data as-is to the physical devices can't work for obvious reasons (even if you disregard security completely); instead in can, after minimal processing, shove it into the Linux kernel and let it take care of the rest.
The next sub-section of the MMIO section is a datasheet of control registers.
My point about PCI isn't strictly about PCI, it applies equally to VirtIO over MMIO. I do not ever want to have my userspace code poke at memory-mapped registers or do interrupt handling just to do the equivalent of an ioctl.
---
OK, fine, maybe PCI and MMIO are irrelevant but there could be opportunities to share struct layouts. In the section describing block devices[1], there's some code listings for the request packets. A representative example is the request struct:
Take a look at that request, then look at struct ublksrv_ctrl_cmd in ublk_cmd.h[2]. There is very little the two protocols have in common. Yes, they're both doing some sort of packetized data transfer, but all of the details are different.
Also, just ... just look at the size of the VirtIO specification. There is a lot there. I haven't run a `wc -l` but it would not surprise me if just the spec for VirtIO is longer than the entire patch series for ublk. Out of all that, the sum total of the virtio-blk struct layouts is something like 100, 200 lines.
Is it worth going through the trouble of trying to unify these two unrelated specs just so that we can satisfy some bizarre philosophical goal of carefully avoiding new ideas?
Like, if you're going to go that far, why does virtio-blk need to exist instead of continuing to emulate SCSI? Or using iSCSI for host<-> device transfer? The obvious answer is, again, because different use cases have different requirements. It's silly to cook two soups in the same bowl.
There's a reason emulators design their virtual devices to resemble real hardware (PCI, SCSI, USB) -- there's already going to be a bunch of code in the hypervisor to create fake hardware. It's also more practical to piggy-back on PCI (etc) when the spec needs to be implemented by competing vendors, since there's no kernel and no OS idioms involved. Not to mention various pre-kernel code such as EFI and bootloaders.
Conversely, userspace developers really do not want to be coding up a fake PCI device with registers and interrupts and so on just to get some bytes into the kernel. They want to invoke system calls (ioctl, mmap, io_uring) and let the OS handle the details.