CoreIO library ~~~~~~~~~~~~~~~ The CoreIO library allows external programs to act as MMIO responders, send interrupt requests to the VM, and perform DMA. To use the library in your C program, you need to connect to a VM: int coreio_connect(const char *target); Connect to a VM. target string like "10.10.0.3:23456" Returns error flag. If the result was zero, you can continue using the CoreIO API. The API is based around a few concepts. First, there's the vMMIO range. Each range is a 4k-aligned block of address space that a CoreIO client can claim. Ranges are indexed from 0, in the order they were specified during VM creation. To claim a vMMIO range, create and fill out a callback structure: #define COREIO_FLAGS_IFETCH 2 #define COREIO_FLAGS_PRIV 4 #define COREIO_FLAGS_SECURE 8 typedef struct { /* MMIO operations. */ int (*read)(void *priv, uint64_t addr, size_t len, void *buf, unsigned flags); int (*write)(void *priv, uint64_t addr, size_t len, void *buf, unsigned flags); /* MMIO pair-wise operations. If not supplied, read/write are used twice. */ int (*readp)(void *priv, uint64_t addr, size_t len, void *buf1, void *buf2, unsigned flags); int (*writep)(void *priv, uint64_t addr, size_t len, void *buf1, void *buf2, unsigned flags); } coreio_func_t; The read/write functions are called with your private pointer (supplied when registering as funcp). They also receive the full address (VM's physical address of the access), access length in bytes, the actual data buffer to read or write to, and a few flags describing access mode. The functions can return 1 to signal failure, which will become a bus error when it hits the VM. After you have your handlers and coreio_func_t filled out, register it: int coreio_register(unsigned rid, const coreio_func_t *func, void *funcp); Register a MMIO handler. rid MMIO range ID to register func io handler function table funcp io handler function parameter (passed to callbacks) Returns error flag. Then, you must construct a main loop of your program. The main loop support is intended to work with select(). You call coreio_preparefds before select() to add CoreIO's sockets to select wait list, and coreio_processfds after select to process the socket events: int coreio_preparefds(int nfds, fd_set *readfds, fd_set *writefds); Prepare fd_sets for select(2). nfds current index of maximum fd in sets + 1 readfds readfds to update writefds writefds to update Returns new nfds. int coreio_processfds(fd_set *readfds, fd_set *writefds); Process fd_sets after select(2). readfds readfds to process writefds writefds to process Returns error flag (for instance, connection to VM lost). A simple implementation of a main loop, without any other event processing, looks like this: /* ... */ fd_set readfds, writefds; int res; while(1) { FD_ZERO(&writefds); FD_ZERO(&readfds); int nfds = coreio_preparefds(0, &readfds, &writefds); if(nfds < 0) /* error in prepare */ break; select(nfds, &readfds, &writefds, NULL, NULL); res = coreio_processfds(&readfds, &writefds); if(res) /* error in process */ break; } /* ... */ This kind of main loop, with a timeout, is provided as a convenience by the library: int coreio_mainloop(long long usec); Simple implementation of a main loop. usec time to spend in loop, in microseconds; negative means forever Returns error flag. In addition to processing MMIO events, your program can also send interrupts. Note that in the VMs, all interrupt lines are level (the interrupt controller can interpret them as edge, but to trigger an edge interrupt, you must send two updates: one from 0 to 1, one from 1 to 0 - it's not enough to just send 1 every time: there'll be no edge). int coreio_irq_update(unsigned rid, unsigned iid, unsigned state); Send an IRQ update. rid MMIO range ID associated with IRQ iid IRQ index state IRQ line state (1 - active, 0 - inactive) There are two ways to perform DMA access, blocking and non-blocking. Blocking access is simpler. You just call a read/write function, and it returns when it's done. Non-blocking access requires you to pass a completion function (and its opaque parameter pointer, which will be passed to it without change). The function will be called at a future time, during coreio_processfds(). int coreio_dma_read(unsigned mid, unsigned sid, uint64_t addr, size_t len, void *buf, unsigned flags, void (*cpl)(void *, int), void *cplp); Start a DMA read. mid IOMMU ID (0 = direct) sid IOMMU region ID addr target address size size of read to perform buf memory buffer to fill with data flags bus access flags cpl completion function; if NULL, call is blocking cplp completion function parameter Returns error flag. int coreio_dma_write(unsigned mid, unsigned sid, uint64_t addr, size_t len, void *buf, unsigned flags, void (*cpl)(void *, int), void *cplp); Start a DMA write. mid IOMMU ID (0 = direct) sid IOMMU region ID addr target address size size of write to perform buf memory buffer with data flags bus access flags cpl completion function; if NULL, call is blocking cplp completion function parameter Returns error flag. On most small VMs, you can pass 0 as IOMMU ID and region ID. That specifies that the access should access VM physical memory directly. Finally, to cleanly shut down the CoreIO library, use: void coreio_disconnect(void);