/VSI_Ext

Primary LanguageCApache License 2.0Apache-2.0

  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);