Overview — eCosPro Support for VirtIO


The eCosPro VirtIO package supports access to VirtIO devices. It provides general management of the device and the buffer queues associated with it. Normally a next-level driver will use the facilities provided by this driver to then implement an eCos compliant driver for a particular class of device.

A VirtIO device is described by a cyg_vio_driver structure. Data transfers are described by a cyg_vio_tfr structure. Operations are handled through an API that is used by the class drivers.

VirtIO Device Structure

A VirtIO device is instantiated by creating one of these structures and populating its fields with suitable values. The following fields need to be set; other fields in the structure will be initialized by the VirtIO driver.

Base address of the device.
Device interrupt vector. Interrupts are not handled by the VirtIO driver, instead they need to be handled by the parent class driver. See the API description for details.
Interrupt vector priority.
A private pointer for the parent driver. This will usually be a pointer to a data structure that the driver uses to store state for this device.
If set to 1, this indicates that the device is a legacy device. At present only legacy devices are supported.
If set to 1 this indicates that this is a PCI device. At present PCI devices are not supported.
A bit mask corresponding to the driver feature bits defined for this class of driver. This will be set in the DRV_FEATURES field of the device during feature negotiation.
The number of queues in the queue array.
A pointer to an array of pointers to cyg_vio_queue structures. Queues are defined using the VIO_QUEUE(__name, __size) macro and then collected together into an array which is pointed to by this field.

The following example shows how a VirtIO console driver would be instantiated:

// Define driver feature set
                                        VIRTIO_F_NOTIFY_ON_EMPTY        | \
                                        VIRTIO_F_ANY_LAYOUT             | \
                                        VIRTIO_CONSOLE_F_MULTIPORT      | \

// Set queue size
#define CONSOLE_QUEUE_SIZE            128

// Define queues. Here we have two console channels and a control channel.
VIO_QUEUE( virtual_console_rxq0, CONSOLE_QUEUE_SIZE );
VIO_QUEUE( virtual_console_txq0, CONSOLE_QUEUE_SIZE );
VIO_QUEUE( virtual_control_rxq, CONSOLE_QUEUE_SIZE );
VIO_QUEUE( virtual_control_txq, CONSOLE_QUEUE_SIZE );
VIO_QUEUE( virtual_console_rxq1, CONSOLE_QUEUE_SIZE );
VIO_QUEUE( virtual_console_txq1, CONSOLE_QUEUE_SIZE );

// Collect queues together into an array
cyg_vio_queue *virtual_console_queues[] = { &virtual_console_rxq0, &virtual_console_txq0,
                                         &virtual_control_rxq, &virtual_control_txq,
                                         &virtual_console_rxq1, &virtual_console_txq1 };

// Declare the driver
cyg_vio_driver console_vio_driver =
     // Hardware parameters
    .base               = CYGHWR_HAL_CONSOLE0_BASE,
    .vector             = CYGNUM_HAL_INTERRUPT_CONSOLE0,
    .vector_pri         = 0xa0,

    // Console driver private data
    .priv               = &virtual_console,

    // This is a legacy, non PCI device
    .legacy             = 1,
    .pci                = 0,

    // Set driver features for use in negotiation
    .drv_features       = VIO_CONSOLE_FEATURES,

    // Attach queues.
    .queue_count        = 6,
    .queue              = virtual_console_queues,


VirtIO Transfer Structure

Data transfers are described using a cyg_vio_tfr structure. The parent driver prepares a transfer object, passes it to the VirtIO driver and receives it back via a callback when the transfer is complete.

A transfer object has the following structure:

#define VIO_IOV_MAX             8

struct cyg_vio_tfr
    cyg_uint16          queue;          // Queue number
    cyg_uint16          head;           // head descriptor index
    cyg_uint16          iov_len;        // IOV entry count
    cyg_uint32          actual;         // Total actual bytes transferred

    void                (*callback)( cyg_vio_tfr *tfr );
    void                *priv;          // Private data pointer

        void            *buffer;
        cyg_uint32      size;
        cyg_uint32      flags;
    } iov[VIO_IOV_MAX];



The fields are as follows:

The index in the queue array of the queue to apply the transfer to.
This is used to store the index of the head buffer descriptor associated with this transfer. It does not need to be set by the client, but may be monitored to detect transfer completion, it will be set to 0xFFFF when the transfer is done.
The number of entries in the iov field that are in use.
The actual number of bytes transferred. This is the value of the used queue element length field and therefore depends on the hypervisors device emulation to set it correctly.
A function that is called when the transfer is completed. The single argument is a pointer to the completed transfer. This will only be called from within the cyg_vio_poll().
A private pointer that can be used by the parent driver to supply context to the callback.

An array of buffer pointers with their sizes. The flags field for each will either be zero, or contain the VIO_IOV_FLAGS_WRITE flag. If the flag is zero, then this buffer is for transfer from the VM to the hypervisor, and if set, then the buffer is for transfer from the hypervisor to the VM.

While the default size of this array is 8, this is not a fixed limit. Larger arrays could be passed by treating a transfer object as an initial substructure of a larger object that contains space for a longer IOV array.