diff options
Diffstat (limited to 'src/dev/virtio/base.hh')
-rw-r--r-- | src/dev/virtio/base.hh | 881 |
1 files changed, 881 insertions, 0 deletions
diff --git a/src/dev/virtio/base.hh b/src/dev/virtio/base.hh new file mode 100644 index 000000000..4695f2b96 --- /dev/null +++ b/src/dev/virtio/base.hh @@ -0,0 +1,881 @@ +/* + * 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: + virtual ~VirtQueue() {}; + + /** @{ + * @name Checkpointing Interface + */ + virtual void serialize(std::ostream &os); + virtual void unserialize(Checkpoint *cp, const std::string §ion); + /** @} */ + + /** @{ + * @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) + : 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(std::ostream &os); + void unserialize(Checkpoint *cp, const std::string §ion); + + /** @} */ + + + 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__ |