When a guest reads from or writes to a vdev, the vdev must perform actions analogous to the actions hardware performs in response to reads and writes.
When you write a vdev it is important to remember that you are creating a device that, from the perspective of the guest, is indistinguishable from hardware. Thus, when the guest reads, your vdev must write what it is trying to read; when the guest writes, you must read what it has written.
The diagram below illustrates how *_vread() and *_vwrite() functions call the appropriate corresponding guest_cpu_read() and guest_cpu_write() functions.
Remember that if the vdev acts as an intermediary between the guest and a hardware device (e.g., vdev ser8250), to the guest the vdev is indistinguishable from hardware, but in the hypervisor host (where vdevs run) the vdev is just another application interacting with a hardware driver.
The *_vread() and *_vwrite() functions in the source code examples that call guest_cpu_*() functions (see the guest.h chapter in the Virtual Device Developer's API Reference) use vopnd and oopnd in their arguments. For example, in the trace vdev's vdtrace_vwrite() function, you will find the following:
... if (vopnd->location == vdp->v_block.location) { const int err = guest_cpu_read(gcp, GXF_NONE, oopnd, 1, &counter, vopnd->length); if (err != EOK) { return guest_cpu_to_vrs(err); } ...
The vopnd and the oopnd arguments both point to arrays of qvm_state_block data structures: