/*
 * Copyright (c) 2014 ARM Limited
 * All rights reserved
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Andreas Sandberg
 */

#ifndef __DEV_VIRTIO_BASE_HH__
#define __DEV_VIRTIO_BASE_HH__

#include "arch/isa_traits.hh"
#include "base/bitunion.hh"
#include "base/callback.hh"
#include "dev/virtio/virtio_ring.h"
#include "mem/port_proxy.hh"
#include "sim/sim_object.hh"

struct VirtIODeviceBaseParams;
class VirtQueue;

/** @{
 * @name VirtIO endian conversion helpers
 *
 * VirtIO prior to version 1.0 (legacy versions) normally send values
 * to the host in the guest systems native byte order. This is going
 * to change in version 1.0 which mandates little endian. We currently
 * only support the legacy version of VirtIO (the new and shiny
 * standard is still in a draft state and not implemented by the
 * kernel). Once we support the new standard, we should negotiate the
 * VirtIO version with the guest and automatically use the right type
 * of byte swapping.
 */

/** Convert legacy VirtIO endianness to host endianness. */
template <typename T> inline T
vtoh_legacy(T v) {
    return TheISA::gtoh(v);
}

/** Convert host endianness to legacy VirtIO endianness. */
template <typename T> inline T
htov_legacy(T v) {
    return TheISA::htog(v);
}


template <> inline vring_used_elem
vtoh_legacy(vring_used_elem v) {
    v.id = vtoh_legacy(v.id);
    v.len = vtoh_legacy(v.len);
    return v;
}

template <> inline vring_used_elem
htov_legacy(vring_used_elem v) {
    v.id = htov_legacy(v.id);
    v.len = htov_legacy(v.len);
    return v;
}

template <> inline vring_desc
vtoh_legacy(vring_desc v) {
    v.addr = vtoh_legacy(v.addr);
    v.len = vtoh_legacy(v.len);
    v.flags = vtoh_legacy(v.flags);
    v.next = vtoh_legacy(v.next);
    return v;
}

template <> inline vring_desc
htov_legacy(vring_desc v) {
    v.addr = htov_legacy(v.addr);
    v.len = htov_legacy(v.len);
    v.flags = htov_legacy(v.flags);
    v.next = htov_legacy(v.next);
    return v;
}

/** @} */

/**
 * VirtIO descriptor (chain) wrapper
 *
 * Communication in VirtIO takes place by sending and receiving chains
 * of so called descriptors using device queues. The queue is
 * responsible for sending a descriptor chain from the guest to the
 * host and later sending it back to the guest. The descriptor chain
 * itself can be thought of as a linked list of buffers (descriptors)
 * that are read only (isIncoming() is true) or write only
 * (isOutgoing() is true). A single chain may contain any mix of input
 * and output buffers.
 *
 * The descriptor wrapper is normally <i>only</i> instantiated by the
 * virtqueue wrapper (VirtQueue) and should never be instantiated in
 * device models. The VirtQueue also ensures that the descriptor
 * wrapper is re-populated with new data from the guest by calling
 * updateChain() whenever a new descriptor chain is passed to the host
 * (VirtQueue::consumeDescriptor()). The updateChain() method
 * automatically does some sanity checks on the descriptor chain to
 * detect loops.
 */
class VirtDescriptor
{
  public:
    /** Descriptor index in virtqueue */
    typedef uint16_t Index;

    /** @{
     * @name VirtIO Descriptor <-> Queue Interface
     */
    /**
     * Create a descriptor wrapper.
     *
     * @param memProxy Proxy to the guest physical memory.
     * @param queue Queue owning this descriptor.
     * @param index Index within the queue.
     */
    VirtDescriptor(PortProxy &memProxy, VirtQueue &queue, Index index);
    // WORKAROUND: The noexcept declaration works around a bug where
    // gcc 4.7 tries to call the wrong constructor when emplacing
    // something into a vector.
    VirtDescriptor(VirtDescriptor &&other) noexcept;
    ~VirtDescriptor() noexcept;

    VirtDescriptor &operator=(VirtDescriptor &&rhs) noexcept;

    /** Get the descriptor's index into the virtqueue. */
    Index index() const { return _index; }

    /** Populate this descriptor with data from the guest. */
    void update();

    /** Populate this descriptor chain with data from the guest. */
    void updateChain();
    /** @} */

    /** @{
     * @name Debug interfaces
     */
    /**
     * Dump the contents of a descriptor
     */
    void dump() const;
    /**
     * Dump the contents of a descriptor chain starting at this
     * descriptor.
     */
    void dumpChain() const;
    /** @} */


    /** @{
     * @name Device Model Interfaces
     */
    /**
     * Read the contents of a descriptor.
     *
     * This method copies the contents of a descriptor into a buffer
     * within gem5. Devices should typically use chainRead() instead
     * as it automatically follows the descriptor chain to read the
     * desired number of bytes.
     *
     * @see chainRead
     *
     * @param offset Offset into the descriptor.
     * @param dst Destination buffer.
     * @param size Amount of data to read (in bytes).
     */
    void read(size_t offset, uint8_t *dst, size_t size) const;
    /**
     * Write to the contents of a descriptor.
     *
     * This method copies the contents of a descriptor into a buffer
     * within gem5. Devices should typically use chainWrite() instead
     * as it automatically follows the descriptor chain to read the
     * desired number of bytes.
     *
     * @see chainWrite
     *
     * @param offset Offset into the descriptor.
     * @param src Source buffer.
     * @param size Amount of data to read (in bytes).
     */
    void write(size_t offset, const uint8_t *src, size_t size);
    /**
     * Retrieve the size of this descriptor.
     *
     * This method gets the size of a single descriptor. For incoming
     * data, it corresponds to the amount of data that can be read
     * from the descriptor. For outgoing data, it corresponds to the
     * amount of data that can be written to it.
     *
     * @see chainSize
     *
     * @return Size of descriptor in bytes.
     */
    size_t size() const { return desc.len; }

    /**
     * Is this descriptor chained to another descriptor?
     *
     * @return true if there is a next pointer, false otherwise.
     */
    bool hasNext() const { return desc.flags & VRING_DESC_F_NEXT; }
    /**
     * Get the pointer to the next descriptor in a chain.
     *
     * @return Pointer to the next descriptor or NULL if this is the
     * last element in a chain.
     */
    VirtDescriptor *next() const;

    /** Check if this is a read-only descriptor (incoming data). */
    bool isIncoming() const { return !isOutgoing(); }
    /** Check if this is a write-only descriptor (outgoing data). */
    bool isOutgoing() const { return desc.flags & VRING_DESC_F_WRITE; }


    /**
     * Read the contents of a descriptor chain.
     *
     * This method reads the specified number of bytes from a
     * descriptor chain starting at the this descriptor plus an offset
     * in bytes. The method automatically follows the links in the
     * descriptor chain.
     *
     * @param offset Offset into the chain (in bytes).
     * @param dst Pointer to destination buffer.
     * @param size Size (in bytes).
     */
    void chainRead(size_t offset, uint8_t *dst, size_t size) const;
    /**
     * Write to a descriptor chain.
     *
     * This method writes the specified number of bytes to a
     * descriptor chain starting at the this descriptor plus an offset
     * in bytes. The method automatically follows the links in the
     * descriptor chain.
     *
     * @param offset Offset into the chain (in bytes).
     * @param src Pointer to source buffer.
     * @param size Size (in bytes).
     */
    void chainWrite(size_t offset, const uint8_t *src, size_t size);
    /**
     * Retrieve the size of this descriptor chain.
     *
     * This method gets the size of a descriptor chain starting at
     * this descriptor.
     *
     * @return Size of descriptor chain in bytes.
     */
    size_t chainSize() const;
    /** @} */

  private:
    // Remove default constructor
    VirtDescriptor();
    // Prevent copying
    VirtDescriptor(const VirtDescriptor &other);

    /** Pointer to memory proxy */
    PortProxy *memProxy;
    /** Pointer to virtqueue owning this descriptor */
    VirtQueue *queue;

    /** Index in virtqueue */
    Index _index;

    /** Underlying descriptor */
    vring_desc desc;
};

/**
 * Base wrapper around a virtqueue.
 *
 * VirtIO device models typically need to extend this class to
 * implement their own device queues.
 *
 * @note Queues must be registered with
 * VirtIODeviceBase::registerQueue() to be active.
 */
class VirtQueue : public Serializable {
public:
    virtual ~VirtQueue() {};

    /** @{
     * @name Checkpointing Interface
     */
    void serialize(CheckpointOut &cp) const override;
    void unserialize(CheckpointIn &cp) override;

    /** @{
     * @name Low-level Device Interface
     */
    /**
     * Set the base address of this queue.
     *
     * @param address Guest physical base address of the queue.
     */
    void setAddress(Addr address);
    /**
     * Get the guest physical address of this queue.
     *
     * @return Physical address in guest where this queue resides.
     */
    Addr getAddress() const { return _address; }

    /**
     * Get the number of descriptors available in this queue.
     *
     * @return Size of queue in descriptors.
     */
     uint16_t getSize() const { return _size; }

    /**
     * Get a pointer to a specific descriptor in the queue.
     *
     * @note This interfaces is normally only used by VirtDescriptor
     * to follow descriptor chains. Device models typically don't need
     * to use it.
     *
     * @return Pointer to a VirtDescriptor.
     */
    VirtDescriptor *getDescriptor(VirtDescriptor::Index index) {
        return &descriptors[index];
    }
    /** @} */

    /** @{
     * @name Device Model Interfaces
     */
    /**
     * Get an incoming descriptor chain from the queue.
     *
     * @return Pointer to descriptor on success, NULL if no pending
     * descriptors are available.
     */
    VirtDescriptor *consumeDescriptor();
    /**
     * Send a descriptor chain to the guest.
     *
     * This method posts a descriptor chain to the guest after a
     * device model has finished processing it. The device model
     * typically needs to call VirtIODeviceBase::kick() to deliver
     * notify tell the guest that the queue has been updated.
     *
     * @note The desc parameter must refer to the first descriptor in
     * a chain that has been retrieved using consumeDescriptor().
     *
     * @note The len parameter specified the amount of data produced
     * by the device model. It seems to be ignored by Linux and it is
     * not well defined.
     *
     * @param desc Start of descriptor chain.
     * @param len Length of the produced data.
     */
    void produceDescriptor(VirtDescriptor *desc, uint32_t len);
    /** @} */

    /** @{
     * @name Device Model Callbacks
     */
    /**
     * Notify queue of pending events.
     *
     * This method is called by VirtIODeviceBase::onNotify() to notify
     * the device model of pending data in a virtqueue. The default
     * implementation of this method iterates over the available
     * descriptor chains and calls onNotifyDescriptor() for every new
     * incoming chain.
     *
     * Device models should normally overload one of onNotify() and
     * onNotifyDescriptor().
     */
    virtual void onNotify();
    /**
     * Notify queue of pending incoming descriptor.
     *
     * This method is called by the default implementation of
     * onNotify() to notify the device model of pending data in a
     * descriptor chain.
     *
     * Device models should normally overload one of onNotify() and
     * onNotifyDescriptor().
     */
    virtual void onNotifyDescriptor(VirtDescriptor *desc) {};
    /** @} */

    /** @{
     * @name Debug interfaces
     */
    /** Dump the contents of a queue */
    void dump() const;
    /** @} */

    /** @{ */
    /**
     * Page size used by VirtIO.\ It's hard-coded to 4096 bytes in
     * the spec for historical reasons.
     */
    static const unsigned ALIGN_BITS = 12;
    static const unsigned ALIGN_SIZE = 1 << ALIGN_BITS;
    /** @} */

  protected:
    /**
     * Instantiate a new virtqueue.
     *
     * Instantiate a virtqueue with a fixed size. The size is
     * specified in descriptors which are defined as 4096 bytes each.
     *
     * @param proxy Proxy to the guest physical memory.
     * @param size Size in descriptors/pages.
     */
    VirtQueue(PortProxy &proxy, uint16_t size);

  private:
    VirtQueue();

    /** Queue size in terms of number of descriptors */
    const uint16_t _size;
    /** Base address of the queue */
    Addr _address;
    /** Guest physical memory proxy */
    PortProxy &memProxy;

  private:
    /**
     * VirtIO ring buffer wrapper.
     *
     * This class wraps a VirtIO ring buffer. The template parameter T
     * is used to select the data type for the items in the ring (used
     * or available descriptors).
     */
    template<typename T>
    class VirtRing
    {
      public:
        typedef uint16_t Flags;
        typedef uint16_t Index;

        struct Header {
            Flags flags;
            Index index;
        } M5_ATTR_PACKED;

        VirtRing<T>(PortProxy &proxy, uint16_t size)
        : header{0, 0}, ring(size), _proxy(proxy), _base(0) {}

        /**
         * Set the base address of the VirtIO ring buffer.
         *
         * @param addr New host physical address
         */
        void setAddress(Addr addr) { _base = addr; }

        /** Update the ring buffer header with data from the guest. */
        void readHeader() {
            assert(_base != 0);
            _proxy.readBlob(_base, (uint8_t *)&header, sizeof(header));
            header.flags = vtoh_legacy(header.flags);
            header.index = vtoh_legacy(header.index);
        }

        void writeHeader() {
            Header out;
            assert(_base != 0);
            out.flags = htov_legacy(header.flags);
            out.index = htov_legacy(header.index);
            _proxy.writeBlob(_base, (uint8_t *)&out, sizeof(out));
        }

        void read() {
            readHeader();

            /* Read and byte-swap the elements in the ring */
            T temp[ring.size()];
            _proxy.readBlob(_base + sizeof(header),
                            (uint8_t *)temp, sizeof(T) * ring.size());
            for (int i = 0; i < ring.size(); ++i)
                ring[i] = vtoh_legacy(temp[i]);
        }

        void write() {
            assert(_base != 0);
            /* Create a byte-swapped copy of the ring and write it to
             * guest memory. */
            T temp[ring.size()];
            for (int i = 0; i < ring.size(); ++i)
                temp[i] = htov_legacy(ring[i]);
            _proxy.writeBlob(_base + sizeof(header),
                             (uint8_t *)temp, sizeof(T) * ring.size());
            writeHeader();
        }

        /** Ring buffer header in host byte order */
        Header header;
        /** Elements in ring in host byte order */
        std::vector<T> ring;

      private:
        // Remove default constructor
        VirtRing<T>();

        /** Guest physical memory proxy */
        PortProxy &_proxy;
        /** Guest physical base address of the ring buffer */
        Addr _base;
    };

    /** Ring of available (incoming) descriptors */
    VirtRing<VirtDescriptor::Index> avail;
    /** Ring of used (outgoing) descriptors */
    VirtRing<struct vring_used_elem> used;

    /** Offset of last consumed descriptor in the VirtQueue::avail
     * ring */
    uint16_t _last_avail;

    /** Vector of pre-created descriptors indexed by their index into
     * the queue. */
    std::vector<VirtDescriptor> descriptors;
};

/**
 * Base class for all VirtIO-based devices.
 *
 * This class implements the functionality of the VirtIO 0.9.5
 * specification. This version of VirtIO is also known as "legacy" in
 * the VirtIO 1.0 specification from OASIS.
 *
 * @see https://github.com/rustyrussell/virtio-spec
 * @see http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html
 */
class VirtIODeviceBase : public SimObject
{
  public:
    typedef uint16_t QueueID;
    typedef uint32_t FeatureBits;
    /** This is a VirtQueue address as exposed through the low-level
     * interface.\ The address needs to be multiplied by the page size
     * (seems to be hardcoded to 4096 in the spec) to get the real
     * physical address.
     */
    typedef uint16_t VirtAddress;
    /** Device Type (sometimes known as subsystem ID) */
    typedef uint16_t DeviceId;

    BitUnion8(DeviceStatus)
        Bitfield<7> failed;
        Bitfield<2> driver_ok;
        Bitfield<1> driver;
        Bitfield<0> acknowledge;
    EndBitUnion(DeviceStatus)

    typedef VirtIODeviceBaseParams Params;
    VirtIODeviceBase(Params *params, DeviceId id, size_t config_size,
                     FeatureBits features);
    virtual ~VirtIODeviceBase();

  public:
    /** @{
     * @name SimObject Interfaces
     */
    void serialize(CheckpointOut &cp) const override;
    void unserialize(CheckpointIn &cp) override;
    /** @} */


  protected:
    /** @{
     * @name Device Model Interfaces
     */

    /**
     * Inform the guest of available buffers.
     *
     * When a device model has finished processing incoming buffers
     * (after onNotify has been called), it typically needs to inform
     * the guest that there are new pending outgoing buffers. The
     * method used to inform the guest is transport dependent, but is
     * typically through an interrupt. Device models call this method
     * to tell the transport interface to notify the guest.
     */
    void kick() {
        assert(transKick);
        transKick->process();
    };

    /**
     * Register a new VirtQueue with the device model.
     *
     * Devices typically register at least one VirtQueue to use for
     * communication with the guest. This <i>must</i> be done from the
     * constructor since the number of queues are assumed to be
     * constant throughout the lifetime of the device.
     *
     * @warning This method may only be called from the device model
     * constructor.
     */
    void registerQueue(VirtQueue &queue);


    /**
     * Feature set accepted by the guest.
     *
     * When the guest starts the driver for the device, it starts by
     * negotiating features. The device first offers a set of features
     * (see deviceFeatures), the driver then notifies the device of
     * which features it accepted. The base class will automatically
     * accept any feature set that is a subset of the features offered
     * by the device.
     */
    FeatureBits guestFeatures;
    /** @} */

  public:
    /** @{
     * @name Optional VirtIO Interfaces
     */
    /**
     * Read from the configuration space of a device.
     *
     * This method is called by the transport interface to read data
     * from a device model's configuration space. The device model
     * should use the cfgOffset parameter as the offset into its
     * configuration space.
     *
     * @warning The address in the packet should not be used to
     * determine the offset into a device's configuration space.
     *
     * @param pkt Read request packet.
     * @param cfgOffset Offset into the device's configuration space.
     */
    virtual void readConfig(PacketPtr pkt, Addr cfgOffset);
    /**
     * Write to the configuration space of a device.
     *
     * This method is called by the transport interface to write data
     * into a device model's configuration space. The device model
     * should use the cfgOffset parameter as the offset into its
     * configuration space.
     *
     * @warning The address in the packet should not be used to
     * determine the offset into a device's configuration space.
     *
     * @param pkt Write request packet.
     * @param cfgOffset Offset into the device's configuration space.
     */
    virtual void writeConfig(PacketPtr pkt, Addr cfgOffset);

    /**
     * Driver-request device reset.
     *
     * The device driver may reset a device by writing zero to the
     * device status register (using setDeviceStatus()), which causes
     * this method to be called. Device models overriding this method
     * <i>must</i> ensure that the reset method of the base class is
     * called when the device is reset.
     *
     * @note Always call the reset method of the base class from
     * device-specific reset methods.
     */
    virtual void reset();
    /** @} */

  protected:
    /** @{
     * @name Device Model Helpers
     */

    /**
     * Read configuration data from a device structure.
     *
     * @param pkt Read request packet.
     * @param cfgOffset Offset into the device's configuration space.
     * @param cfg Device configuration
     */
    void readConfigBlob(PacketPtr pkt, Addr cfgOffset, const uint8_t *cfg);

    /**
     * Write configuration data to a device structure.
     *
     * @param pkt Write request packet.
     * @param cfgOffset Offset into the device's configuration space.
     * @param cfg Device configuration
     */
    void writeConfigBlob(PacketPtr pkt, Addr cfgOffset, uint8_t *cfg);

    /** @} */

  public:
    /** @{
     * @name VirtIO Transport Interfaces
     */
     /**
      * Register a callback to kick the guest through the transport
      * interface.
      *
      * @param c Callback into transport interface.
      */
    void registerKickCallback(Callback *c) {
        assert(!transKick);
        transKick = c;
    }


    /**
     * Driver is requesting service.
     *
     * This method is called by the underlying hardware interface
     * (e.g., PciVirtIO or MmmioVirtIO) to notify a device of pending
     * incoming descriptors.
     *
     * @param index ID of the queue with pending actions.
     */
    void onNotify(QueueID index);


    /**
     * Change currently active queue.
     *
     * The transport interface works on a queue at a time. The
     * currently active queue is decided by the value of the queue
     * select field in a device.
     *
     * @param idx ID of the queue to select.
     */
    void setQueueSelect(QueueID idx) { _queueSelect = idx; }
    /**
     * Get the currently active queue.
     *
     * The transport interface works on a queue at a time. The
     * currently active queue is decided by the value of the queue
     * select field in a device.
     *
     * @return The ID of the currently active queue.
     */
    QueueID getQueueSelect() const { return _queueSelect; }

    /**
     * Change the host physical address of the currently active queue.
     *
     * @note The new address is specified in multiples of the page
     * size (fixed to 4096 bytes in the standard). For example, if the
     * address 10 is selected, the actual host physical address will
     * be 40960.
     *
     * @see setQueueSelect
     * @see getQueueSelect
     *
     * @param address New address of the currently active queue (in
     * pages).
     */
    void setQueueAddress(uint32_t address);
    /**
     * Get the host physical address of the currently active queue.
     *
     * @note The new address is specified in multiples of the page
     * size (fixed to 4096 bytes in the standard). For example, if the
     * address 10 is selected, the actual host physical address will
     * be 40960.
     *
     * @see setQueueSelect
     * @see getQueueSelect
     *
     * @return Address of the currently active queue (in pages).
     */
    uint32_t getQueueAddress() const;

    /**
     * Get the size (descriptors) of the currently active queue.
     *
     * @return Size of the currently active queue in number of
     * descriptors.
     */
    uint16_t getQueueSize() const { return getCurrentQueue().getSize(); }

    /**
     * Update device status and optionally reset device.
     *
     * The special device status of 0 is used to reset the device by
     * calling reset().
     *
     * @param status New device status.
     */
    void setDeviceStatus(DeviceStatus status);

    /**
     * Retrieve the device status.
     *
     * @return Device status.
     */
    DeviceStatus getDeviceStatus() const { return _deviceStatus; }

    /**
     * Set feature bits accepted by the guest driver.
     *
     * This enables a subset of the features offered by the device
     * model through the getGuestFeatures() interface.
     */
    void setGuestFeatures(FeatureBits features);

    /**
     * Get features accepted by the guest driver.
     *
     * @return Currently active features.
     */
    FeatureBits getGuestFeatures() const { return guestFeatures; }

    /** Device ID (sometimes known as subsystem ID) */
    const DeviceId deviceId;

    /** Size of the device's configuration space */
    const size_t configSize;

    /** Feature set offered by the device */
    const FeatureBits deviceFeatures;
    /** @} */

  private:
    /** Convenience method to get the currently selected queue */
    const VirtQueue &getCurrentQueue() const;
    /** Convenience method to get the currently selected queue */
    VirtQueue &getCurrentQueue();

    /**
     * Status of the device
     *
     * @see getDeviceStatus
     * @see setDeviceStatus
     */
    DeviceStatus _deviceStatus;

    /** Queue select register (set by guest) */
    QueueID _queueSelect;

    /** List of virtual queues supported by this device */
    std::vector<VirtQueue *> _queues;

    /** Callbacks to kick the guest through the transport layer  */
    Callback *transKick;
};

#endif // __DEV_VIRTIO_BASE_HH__
