summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/PC/BIOS/virtio.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/PC/BIOS/virtio.c')
-rw-r--r--src/VBox/Devices/PC/BIOS/virtio.c701
1 files changed, 701 insertions, 0 deletions
diff --git a/src/VBox/Devices/PC/BIOS/virtio.c b/src/VBox/Devices/PC/BIOS/virtio.c
new file mode 100644
index 00000000..37560b5f
--- /dev/null
+++ b/src/VBox/Devices/PC/BIOS/virtio.c
@@ -0,0 +1,701 @@
+/* $Id: virtio.c $ */
+/** @file
+ * VirtIO-SCSI host adapter driver to boot from disks.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "biosint.h"
+#include "ebda.h"
+#include "inlines.h"
+#include "pciutil.h"
+#include "vds.h"
+#include "scsi.h"
+
+//#define DEBUG_VIRTIO 1
+#if DEBUG_VIRTIO
+# define DBG_VIRTIO(...) BX_INFO(__VA_ARGS__)
+#else
+# define DBG_VIRTIO(...)
+#endif
+
+/* The maximum CDB size. */
+#define VIRTIO_SCSI_CDB_SZ 16
+/** Maximum sense data to return. */
+#define VIRTIO_SCSI_SENSE_SZ 32
+
+#define VIRTIO_SCSI_RING_ELEM 3
+
+/**
+ * VirtIO queue descriptor.
+ */
+typedef struct
+{
+ /** 64bit guest physical address of the buffer, split into high and low part because we work in real mode. */
+ uint32_t GCPhysBufLow;
+ uint32_t GCPhysBufHigh;
+ /** Length of the buffer in bytes. */
+ uint32_t cbBuf;
+ /** Flags for the buffer. */
+ uint16_t fFlags;
+ /** Next field where the buffer is continued if _NEXT flag is set. */
+ uint16_t idxNext;
+} virtio_q_desc_t;
+
+#define VIRTIO_Q_DESC_F_NEXT 0x1
+#define VIRTIO_Q_DESC_F_WRITE 0x2
+#define VIRTIO_Q_DESC_F_INDIRECT 0x4
+
+/**
+ * VirtIO available ring.
+ */
+typedef struct
+{
+ /** Flags. */
+ volatile uint16_t fFlags;
+ /** Next index to write an available buffer by the driver. */
+ volatile uint16_t idxNextFree;
+ /** The ring - we only provide one entry. */
+ volatile uint16_t au16Ring[VIRTIO_SCSI_RING_ELEM];
+ /** Used event index. */
+ volatile uint16_t u16EvtUsed;
+} virtio_q_avail_t;
+
+/**
+ * VirtIO queue used element.
+ */
+typedef struct
+{
+ /** Index of the start of the descriptor chain. */
+ uint32_t u32Id;
+ /** Number of bytes used in the descriptor chain. */
+ uint32_t cbUsed;
+} virtio_q_used_elem_t;
+
+/**
+ * VirtIo used ring.
+ */
+typedef struct
+{
+ /** Flags. */
+ volatile uint16_t fFlags;
+ /** Index where the next entry would be written by the device. */
+ volatile uint16_t idxNextUsed;
+ /** The used ring. */
+ virtio_q_used_elem_t aRing[VIRTIO_SCSI_RING_ELEM];
+} virtio_q_used_t;
+
+/**
+ * VirtIO queue structure we are using, needs to be aligned on a 16byte boundary.
+ */
+typedef struct
+{
+ /** The descriptor table, using 3 max. */
+ virtio_q_desc_t aDescTbl[3];
+ /** Available ring. */
+ virtio_q_avail_t AvailRing;
+ /** Used ring. */
+ virtio_q_used_t UsedRing;
+ /** The notification offset for the queue. */
+ uint32_t offNotify;
+} virtio_q_t;
+
+/**
+ * VirtIO SCSI request structure passed in the queue.
+ */
+typedef struct
+{
+ /** The LUN to address. */
+ uint8_t au8Lun[8];
+ /** Request ID - split into low and high part. */
+ uint32_t u32IdLow;
+ uint32_t u32IdHigh;
+ /** Task attributes. */
+ uint8_t u8TaskAttr;
+ /** Priority. */
+ uint8_t u8Prio;
+ /** CRN value, usually 0. */
+ uint8_t u8Crn;
+ /** The CDB. */
+ uint8_t abCdb[VIRTIO_SCSI_CDB_SZ];
+} virtio_scsi_req_hdr_t;
+
+/**
+ * VirtIO SCSI status structure filled by the device.
+ */
+typedef struct
+{
+ /** Returned sense length. */
+ uint32_t cbSense;
+ /** Residual amount of bytes left. */
+ uint32_t cbResidual;
+ /** Status qualifier. */
+ uint16_t u16StatusQual;
+ /** Status code. */
+ uint8_t u8Status;
+ /** Response code. */
+ uint8_t u8Response;
+ /** Sense data. */
+ uint8_t abSense[VIRTIO_SCSI_SENSE_SZ];
+} virtio_scsi_req_sts_t;
+
+/**
+ * VirtIO config for the different data structures.
+ */
+typedef struct
+{
+ /** BAR where to find it. */
+ uint8_t u8Bar;
+ /** Padding. */
+ uint8_t abPad[3];
+ /** Offset within the bar. */
+ uint32_t u32Offset;
+ /** Length of the structure in bytes. */
+ uint32_t u32Length;
+} virtio_bar_cfg_t;
+
+/**
+ * VirtIO PCI capability structure.
+ */
+typedef struct
+{
+ /** Capability typem should always be PCI_CAP_ID_VNDR*/
+ uint8_t u8PciCapId;
+ /** Offset where to find the next capability or 0 if last capability. */
+ uint8_t u8PciCapNext;
+ /** Size of the capability in bytes. */
+ uint8_t u8PciCapLen;
+ /** VirtIO capability type. */
+ uint8_t u8VirtIoCfgType;
+ /** BAR where to find it. */
+ uint8_t u8Bar;
+ /** Padding. */
+ uint8_t abPad[3];
+ /** Offset within the bar. */
+ uint32_t u32Offset;
+ /** Length of the structure in bytes. */
+ uint32_t u32Length;
+} virtio_pci_cap_t;
+
+/**
+ * VirtIO-SCSI controller data.
+ */
+typedef struct
+{
+ /** The queue used - must be first for alignment reasons. */
+ virtio_q_t Queue;
+ /** The BAR configs read from the PCI configuration space, see VIRTIO_PCI_CAP_*_CFG,
+ * only use 4 because VIRTIO_PCI_CAP_PCI_CFG is not part of this. */
+ virtio_bar_cfg_t aBarCfgs[4];
+ /** The start offset in the PCI configuration space where to find the VIRTIO_PCI_CAP_PCI_CFG
+ * capability for the alternate access method to the registers. */
+ uint8_t u8PciCfgOff;
+ /** The notification offset multiplier. */
+ uint32_t u32NotifyOffMult;
+ /** PCI bus where the device is located. */
+ uint8_t u8Bus;
+ /** Device/Function number. */
+ uint8_t u8DevFn;
+ /** The current executed command structure. */
+ virtio_scsi_req_hdr_t ScsiReqHdr;
+ virtio_scsi_req_sts_t ScsiReqSts;
+} virtio_t;
+
+/* The VirtIO specific data must fit into 1KB (statically allocated). */
+ct_assert(sizeof(virtio_t) <= 1024);
+
+/** PCI configuration fields. */
+#define PCI_CONFIG_CAP 0x34
+
+#define PCI_CAP_ID_VNDR 0x09
+
+#define VBOX_VIRTIO_NIL_CFG 0xff
+
+#define VIRTIO_PCI_CAP_COMMON_CFG 0x01
+#define VIRTIO_PCI_CAP_NOTIFY_CFG 0x02
+#define VIRTIO_PCI_CAP_ISR_CFG 0x03
+#define VIRTIO_PCI_CAP_DEVICE_CFG 0x04
+#define VIRTIO_PCI_CAP_PCI_CFG 0x05
+
+#define RT_BIT_32(bit) ((uint32_t)(1L << (bit)))
+
+#define VIRTIO_COMMON_REG_DEV_FEAT_SLCT 0x00
+#define VIRTIO_COMMON_REG_DEV_FEAT 0x04
+# define VIRTIO_CMN_REG_DEV_FEAT_SCSI_INOUT 0x01
+#define VIRTIO_COMMON_REG_DRV_FEAT_SLCT 0x08
+#define VIRTIO_COMMON_REG_DRV_FEAT 0x0c
+#define VIRTIO_COMMON_REG_MSIX_CFG 0x10
+#define VIRTIO_COMMON_REG_NUM_QUEUES 0x12
+#define VIRTIO_COMMON_REG_DEV_STS 0x14
+# define VIRTIO_CMN_REG_DEV_STS_F_RST 0x00
+# define VIRTIO_CMN_REG_DEV_STS_F_ACK 0x01
+# define VIRTIO_CMN_REG_DEV_STS_F_DRV 0x02
+# define VIRTIO_CMN_REG_DEV_STS_F_DRV_OK 0x04
+# define VIRTIO_CMN_REG_DEV_STS_F_FEAT_OK 0x08
+# define VIRTIO_CMN_REG_DEV_STS_F_DEV_RST 0x40
+# define VIRTIO_CMN_REG_DEV_STS_F_FAILED 0x80
+#define VIRTIO_COMMON_REG_CFG_GEN 0x15
+
+#define VIRTIO_COMMON_REG_Q_SELECT 0x16
+#define VIRTIO_COMMON_REG_Q_SIZE 0x18
+#define VIRTIO_COMMON_REG_Q_MSIX_VEC 0x1a
+#define VIRTIO_COMMON_REG_Q_ENABLE 0x1c
+#define VIRTIO_COMMON_REG_Q_NOTIFY_OFF 0x1e
+#define VIRTIO_COMMON_REG_Q_DESC 0x20
+#define VIRTIO_COMMON_REG_Q_DRIVER 0x28
+#define VIRTIO_COMMON_REG_Q_DEVICE 0x30
+
+#define VIRTIO_DEV_CFG_REG_Q_NUM 0x00
+#define VIRTIO_DEV_CFG_REG_SEG_MAX 0x04
+#define VIRTIO_DEV_CFG_REG_SECT_MAX 0x08
+#define VIRTIO_DEV_CFG_REG_CMD_PER_LUN 0x0c
+#define VIRTIO_DEV_CFG_REG_EVT_INFO_SZ 0x10
+#define VIRTIO_DEV_CFG_REG_SENSE_SZ 0x14
+#define VIRTIO_DEV_CFG_REG_CDB_SZ 0x18
+#define VIRTIO_DEV_CFG_REG_MAX_CHANNEL 0x1c
+#define VIRTIO_DEV_CFG_REG_MAX_TGT 0x1e
+#define VIRTIO_DEV_CFG_REG_MAX_LUN 0x20
+
+#define VIRTIO_SCSI_Q_CONTROL 0x00
+#define VIRTIO_SCSI_Q_EVENT 0x01
+#define VIRTIO_SCSI_Q_REQUEST 0x02
+
+#define VIRTIO_SCSI_STS_RESPONSE_OK 0x00
+
+static void virtio_reg_set_bar_offset_length(virtio_t __far *virtio, uint8_t u8Bar, uint32_t offReg, uint32_t cb)
+{
+ pci_write_config_byte(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + 4, u8Bar);
+ pci_write_config_dword(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + 8, offReg);
+ pci_write_config_dword(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + 12, cb);
+}
+
+static void virtio_reg_common_access_prepare(virtio_t __far *virtio, uint16_t offReg, uint32_t cbAcc)
+{
+ virtio_reg_set_bar_offset_length(virtio,
+ virtio->aBarCfgs[VIRTIO_PCI_CAP_COMMON_CFG - 1].u8Bar,
+ virtio->aBarCfgs[VIRTIO_PCI_CAP_COMMON_CFG - 1].u32Offset + offReg,
+ cbAcc);
+}
+
+static void virtio_reg_dev_access_prepare(virtio_t __far *virtio, uint16_t offReg, uint32_t cbAcc)
+{
+ virtio_reg_set_bar_offset_length(virtio,
+ virtio->aBarCfgs[VIRTIO_PCI_CAP_DEVICE_CFG - 1].u8Bar,
+ virtio->aBarCfgs[VIRTIO_PCI_CAP_DEVICE_CFG - 1].u32Offset + offReg,
+ cbAcc);
+}
+
+static void virtio_reg_notify_access_prepare(virtio_t __far *virtio, uint16_t offReg, uint32_t cbAcc)
+{
+ virtio_reg_set_bar_offset_length(virtio,
+ virtio->aBarCfgs[VIRTIO_PCI_CAP_NOTIFY_CFG - 1].u8Bar,
+ virtio->aBarCfgs[VIRTIO_PCI_CAP_NOTIFY_CFG - 1].u32Offset + offReg,
+ cbAcc);
+}
+
+static void virtio_reg_isr_prepare(virtio_t __far *virtio, uint32_t cbAcc)
+{
+ virtio_reg_set_bar_offset_length(virtio,
+ virtio->aBarCfgs[VIRTIO_PCI_CAP_ISR_CFG - 1].u8Bar,
+ virtio->aBarCfgs[VIRTIO_PCI_CAP_ISR_CFG - 1].u32Offset,
+ cbAcc);
+}
+
+static uint8_t virtio_reg_common_read_u8(virtio_t __far *virtio, uint16_t offReg)
+{
+ virtio_reg_common_access_prepare(virtio, offReg, sizeof(uint8_t));
+ return pci_read_config_byte(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + sizeof(virtio_pci_cap_t));
+}
+
+static void virtio_reg_common_write_u8(virtio_t __far *virtio, uint16_t offReg, uint8_t u8Val)
+{
+ virtio_reg_common_access_prepare(virtio, offReg, sizeof(uint8_t));
+ pci_write_config_byte(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + sizeof(virtio_pci_cap_t), u8Val);
+}
+
+static uint16_t virtio_reg_common_read_u16(virtio_t __far *virtio, uint16_t offReg)
+{
+ virtio_reg_common_access_prepare(virtio, offReg, sizeof(uint16_t));
+ return pci_read_config_word(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + sizeof(virtio_pci_cap_t));
+}
+
+static void virtio_reg_common_write_u16(virtio_t __far *virtio, uint16_t offReg, uint16_t u16Val)
+{
+ virtio_reg_common_access_prepare(virtio, offReg, sizeof(uint16_t));
+ pci_write_config_word(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + sizeof(virtio_pci_cap_t), u16Val);
+}
+
+static void virtio_reg_common_write_u32(virtio_t __far *virtio, uint16_t offReg, uint32_t u32Val)
+{
+ virtio_reg_common_access_prepare(virtio, offReg, sizeof(uint32_t));
+ pci_write_config_dword(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + sizeof(virtio_pci_cap_t), u32Val);
+}
+
+static uint32_t virtio_reg_dev_cfg_read_u32(virtio_t __far *virtio, uint16_t offReg)
+{
+ virtio_reg_dev_access_prepare(virtio, offReg, sizeof(uint32_t));
+ return pci_read_config_dword(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + sizeof(virtio_pci_cap_t));
+}
+
+static void virtio_reg_dev_cfg_write_u32(virtio_t __far *virtio, uint16_t offReg, uint32_t u32Val)
+{
+ virtio_reg_dev_access_prepare(virtio, offReg, sizeof(uint32_t));
+ pci_write_config_dword(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + sizeof(virtio_pci_cap_t), u32Val);
+}
+
+static void virtio_reg_notify_write_u16(virtio_t __far *virtio, uint16_t offReg, uint16_t u16Val)
+{
+ virtio_reg_notify_access_prepare(virtio, offReg, sizeof(uint16_t));
+ pci_write_config_word(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + sizeof(virtio_pci_cap_t), u16Val);
+}
+
+static uint8_t virtio_reg_isr_read_u8(virtio_t __far *virtio)
+{
+ virtio_reg_isr_prepare(virtio, sizeof(uint8_t));
+ return pci_read_config_byte(virtio->u8Bus, virtio->u8DevFn, virtio->u8PciCfgOff + sizeof(virtio_pci_cap_t));
+}
+
+/**
+ * Converts a segment:offset pair into a 32bit physical address.
+ */
+static uint32_t virtio_addr_to_phys(void __far *ptr)
+{
+ return ((uint32_t)FP_SEG(ptr) << 4) + FP_OFF(ptr);
+}
+
+int virtio_scsi_cmd_data_out(void __far *pvHba, uint8_t idTgt, uint8_t __far *aCDB,
+ uint8_t cbCDB, uint8_t __far *buffer, uint32_t length)
+{
+ virtio_t __far *virtio = (virtio_t __far *)pvHba;
+ uint16_t idxUsedOld = virtio->Queue.UsedRing.idxNextUsed;
+
+ _fmemset(&virtio->ScsiReqHdr, 0, sizeof(virtio->ScsiReqHdr));
+ _fmemset(&virtio->ScsiReqSts, 0, sizeof(virtio->ScsiReqSts));
+
+ virtio->ScsiReqHdr.au8Lun[0] = 0x1;
+ virtio->ScsiReqHdr.au8Lun[1] = idTgt;
+ virtio->ScsiReqHdr.au8Lun[2] = 0;
+ virtio->ScsiReqHdr.au8Lun[3] = 0;
+ _fmemcpy(&virtio->ScsiReqHdr.abCdb[0], aCDB, cbCDB);
+
+ /* Fill in the descriptors. */
+ virtio->Queue.aDescTbl[0].GCPhysBufLow = virtio_addr_to_phys(&virtio->ScsiReqHdr);
+ virtio->Queue.aDescTbl[0].GCPhysBufHigh = 0;
+ virtio->Queue.aDescTbl[0].cbBuf = sizeof(virtio->ScsiReqHdr);
+ virtio->Queue.aDescTbl[0].fFlags = VIRTIO_Q_DESC_F_NEXT;
+ virtio->Queue.aDescTbl[0].idxNext = 1;
+
+ virtio->Queue.aDescTbl[1].GCPhysBufLow = virtio_addr_to_phys(buffer);
+ virtio->Queue.aDescTbl[1].GCPhysBufHigh = 0;
+ virtio->Queue.aDescTbl[1].cbBuf = length;
+ virtio->Queue.aDescTbl[1].fFlags = VIRTIO_Q_DESC_F_NEXT;
+ virtio->Queue.aDescTbl[1].idxNext = 2;
+
+ virtio->Queue.aDescTbl[2].GCPhysBufLow = virtio_addr_to_phys(&virtio->ScsiReqSts);
+ virtio->Queue.aDescTbl[2].GCPhysBufHigh = 0;
+ virtio->Queue.aDescTbl[2].cbBuf = sizeof(virtio->ScsiReqSts);
+ virtio->Queue.aDescTbl[2].fFlags = VIRTIO_Q_DESC_F_WRITE; /* End of chain. */
+ virtio->Queue.aDescTbl[2].idxNext = 0;
+
+ /* Put it into the queue. */
+ virtio->Queue.AvailRing.au16Ring[virtio->Queue.AvailRing.idxNextFree % VIRTIO_SCSI_RING_ELEM] = 0;
+ virtio->Queue.AvailRing.idxNextFree++;
+
+ /* Notify the device about the new command. */
+ DBG_VIRTIO("VirtIO: Submitting new request, Queue.offNotify=0x%x\n", virtio->Queue.offNotify);
+ virtio_reg_notify_write_u16(virtio, virtio->Queue.offNotify, VIRTIO_SCSI_Q_REQUEST);
+
+ /* Wait for it to complete. */
+ while (idxUsedOld == virtio->Queue.UsedRing.idxNextUsed);
+
+ DBG_VIRTIO("VirtIO: Request complete u8Response=%u\n", virtio->ScsiReqSts.u8Response);
+
+ /* Read ISR register to de-assert the interrupt, don't need to do anything with it. */
+ virtio_reg_isr_read_u8(virtio);
+
+ if (virtio->ScsiReqSts.u8Response != VIRTIO_SCSI_STS_RESPONSE_OK)
+ return 4;
+
+ return 0;
+}
+
+int virtio_scsi_cmd_data_in(void __far *pvHba, uint8_t idTgt, uint8_t __far *aCDB,
+ uint8_t cbCDB, uint8_t __far *buffer, uint32_t length)
+{
+ virtio_t __far *virtio = (virtio_t __far *)pvHba;
+ uint16_t idxUsedOld = virtio->Queue.UsedRing.idxNextUsed;
+
+ _fmemset(&virtio->ScsiReqHdr, 0, sizeof(virtio->ScsiReqHdr));
+ _fmemset(&virtio->ScsiReqSts, 0, sizeof(virtio->ScsiReqSts));
+
+ virtio->ScsiReqHdr.au8Lun[0] = 0x1;
+ virtio->ScsiReqHdr.au8Lun[1] = idTgt;
+ virtio->ScsiReqHdr.au8Lun[2] = 0;
+ virtio->ScsiReqHdr.au8Lun[3] = 0;
+ _fmemcpy(&virtio->ScsiReqHdr.abCdb[0], aCDB, cbCDB);
+
+ /* Fill in the descriptors. */
+ virtio->Queue.aDescTbl[0].GCPhysBufLow = virtio_addr_to_phys(&virtio->ScsiReqHdr);
+ virtio->Queue.aDescTbl[0].GCPhysBufHigh = 0;
+ virtio->Queue.aDescTbl[0].cbBuf = sizeof(virtio->ScsiReqHdr);
+ virtio->Queue.aDescTbl[0].fFlags = VIRTIO_Q_DESC_F_NEXT;
+ virtio->Queue.aDescTbl[0].idxNext = 1;
+
+ /* No data out buffer, the status comes right after this in the next descriptor. */
+ virtio->Queue.aDescTbl[1].GCPhysBufLow = virtio_addr_to_phys(&virtio->ScsiReqSts);
+ virtio->Queue.aDescTbl[1].GCPhysBufHigh = 0;
+ virtio->Queue.aDescTbl[1].cbBuf = sizeof(virtio->ScsiReqSts);
+ virtio->Queue.aDescTbl[1].fFlags = VIRTIO_Q_DESC_F_WRITE | VIRTIO_Q_DESC_F_NEXT;
+ virtio->Queue.aDescTbl[1].idxNext = 2;
+
+ virtio->Queue.aDescTbl[2].GCPhysBufLow = virtio_addr_to_phys(buffer);
+ virtio->Queue.aDescTbl[2].GCPhysBufHigh = 0;
+ virtio->Queue.aDescTbl[2].cbBuf = length;
+ virtio->Queue.aDescTbl[2].fFlags = VIRTIO_Q_DESC_F_WRITE; /* End of chain. */
+ virtio->Queue.aDescTbl[2].idxNext = 0;
+
+ /* Put it into the queue, the index is supposed to be free-running and clipped to the ring size
+ * internally. The free running index is what the driver sees. */
+ virtio->Queue.AvailRing.au16Ring[virtio->Queue.AvailRing.idxNextFree % VIRTIO_SCSI_RING_ELEM] = 0;
+ virtio->Queue.AvailRing.idxNextFree++;
+
+ /* Notify the device about the new command. */
+ DBG_VIRTIO("VirtIO: Submitting new request, Queue.offNotify=0x%x\n", virtio->Queue.offNotify);
+ virtio_reg_notify_write_u16(virtio, virtio->Queue.offNotify, VIRTIO_SCSI_Q_REQUEST);
+
+ /* Wait for it to complete. */
+ while (idxUsedOld == virtio->Queue.UsedRing.idxNextUsed);
+
+ DBG_VIRTIO("VirtIO: Request complete u8Response=%u\n", virtio->ScsiReqSts.u8Response);
+
+ /* Read ISR register to de-assert the interrupt, don't need to do anything with it. */
+ virtio_reg_isr_read_u8(virtio);
+
+ if (virtio->ScsiReqSts.u8Response != VIRTIO_SCSI_STS_RESPONSE_OK)
+ return 4;
+
+ return 0;
+}
+
+/**
+ * Initializes the VirtIO SCSI HBA and detects attached devices.
+ */
+static int virtio_scsi_hba_init(virtio_t __far *virtio, uint8_t u8Bus, uint8_t u8DevFn, uint8_t u8PciCapOffVirtIo)
+{
+ uint8_t u8PciCapOff;
+ uint8_t u8DevStat;
+
+ virtio->u8Bus = u8Bus;
+ virtio->u8DevFn = u8DevFn;
+
+ /*
+ * Go through the config space again, read the complete config capabilities
+ * this time and fill in the data.
+ */
+ u8PciCapOff = u8PciCapOffVirtIo;
+ while (u8PciCapOff != 0)
+ {
+ uint8_t u8PciCapId = pci_read_config_byte(u8Bus, u8DevFn, u8PciCapOff);
+ uint8_t cbPciCap = pci_read_config_byte(u8Bus, u8DevFn, u8PciCapOff + 2); /* Capability length. */
+
+ DBG_VIRTIO("Capability ID 0x%x at 0x%x\n", u8PciCapId, u8PciCapOff);
+
+ if ( u8PciCapId == PCI_CAP_ID_VNDR
+ && cbPciCap >= sizeof(virtio_pci_cap_t))
+ {
+ /* Read in the config type and see what we got. */
+ uint8_t u8PciVirtioCfg = pci_read_config_byte(u8Bus, u8DevFn, u8PciCapOff + 3);
+
+ DBG_VIRTIO("VirtIO: CFG ID 0x%x\n", u8PciVirtioCfg);
+ switch (u8PciVirtioCfg)
+ {
+ case VIRTIO_PCI_CAP_COMMON_CFG:
+ case VIRTIO_PCI_CAP_NOTIFY_CFG:
+ case VIRTIO_PCI_CAP_ISR_CFG:
+ case VIRTIO_PCI_CAP_DEVICE_CFG:
+ {
+ virtio_bar_cfg_t __far *pBarCfg = &virtio->aBarCfgs[u8PciVirtioCfg - 1];
+
+ pBarCfg->u8Bar = pci_read_config_byte(u8Bus, u8DevFn, u8PciCapOff + 4);
+ pBarCfg->u32Offset = pci_read_config_dword(u8Bus, u8DevFn, u8PciCapOff + 8);
+ pBarCfg->u32Length = pci_read_config_dword(u8Bus, u8DevFn, u8PciCapOff + 12);
+ if (u8PciVirtioCfg == VIRTIO_PCI_CAP_NOTIFY_CFG)
+ {
+ virtio->u32NotifyOffMult = pci_read_config_dword(u8Bus, u8DevFn, u8PciCapOff + 16);
+ DBG_VIRTIO("VirtIO: u32NotifyOffMult 0x%x\n", virtio->u32NotifyOffMult);
+ }
+ break;
+ }
+ case VIRTIO_PCI_CAP_PCI_CFG:
+ virtio->u8PciCfgOff = u8PciCapOff;
+ DBG_VIRTIO("VirtIO PCI CAP window offset: %x\n", u8PciCapOff);
+ break;
+ default:
+ DBG_VIRTIO("VirtIO SCSI HBA with unknown PCI capability type 0x%x\n", u8PciVirtioCfg);
+ break;
+ }
+ }
+
+ u8PciCapOff = pci_read_config_byte(u8Bus, u8DevFn, u8PciCapOff + 1);
+ }
+
+ /* Reset the device. */
+ u8DevStat = VIRTIO_CMN_REG_DEV_STS_F_RST;
+ virtio_reg_common_write_u8(virtio, VIRTIO_COMMON_REG_DEV_STS, u8DevStat);
+ /* Acknowledge presence. */
+ u8DevStat |= VIRTIO_CMN_REG_DEV_STS_F_ACK;
+ virtio_reg_common_write_u8(virtio, VIRTIO_COMMON_REG_DEV_STS, u8DevStat);
+ /* Our driver knows how to operate the device. */
+ u8DevStat |= VIRTIO_CMN_REG_DEV_STS_F_DRV;
+ virtio_reg_common_write_u8(virtio, VIRTIO_COMMON_REG_DEV_STS, u8DevStat);
+
+#if 0
+ /* Read the feature bits and only program the VIRTIO_CMN_REG_DEV_FEAT_SCSI_INOUT bit if available. */
+ fFeatures = virtio_reg_common_read_u32(virtio, VIRTIO_COMMON_REG_DEV_FEAT);
+ fFeatures &= VIRTIO_CMN_REG_DEV_FEAT_SCSI_INOUT;
+#endif
+
+ /* Check that the device is sane. */
+ if ( virtio_reg_dev_cfg_read_u32(virtio, VIRTIO_DEV_CFG_REG_Q_NUM) < 1
+ || virtio_reg_dev_cfg_read_u32(virtio, VIRTIO_DEV_CFG_REG_CDB_SZ) < 16
+ || virtio_reg_dev_cfg_read_u32(virtio, VIRTIO_DEV_CFG_REG_SENSE_SZ) < 32
+ || virtio_reg_dev_cfg_read_u32(virtio, VIRTIO_DEV_CFG_REG_SECT_MAX) < 1)
+ {
+ DBG_VIRTIO("VirtIO-SCSI: Invalid SCSI device configuration, ignoring device\n");
+ return 1;
+ }
+
+ virtio_reg_common_write_u32(virtio, VIRTIO_COMMON_REG_DRV_FEAT, VIRTIO_CMN_REG_DEV_FEAT_SCSI_INOUT);
+
+ /* Set the features OK bit. */
+ u8DevStat |= VIRTIO_CMN_REG_DEV_STS_F_FEAT_OK;
+ virtio_reg_common_write_u8(virtio, VIRTIO_COMMON_REG_DEV_STS, u8DevStat);
+
+ /* Read again and check the the okay bit is still set. */
+ if (!(virtio_reg_common_read_u8(virtio, VIRTIO_COMMON_REG_DEV_STS) & VIRTIO_CMN_REG_DEV_STS_F_FEAT_OK))
+ {
+ DBG_VIRTIO("VirtIO-SCSI: Device doesn't accept our feature set, ignoring device\n");
+ return 1;
+ }
+
+ /* Disable event and control queue. */
+ virtio_reg_common_write_u16(virtio, VIRTIO_COMMON_REG_Q_SELECT, VIRTIO_SCSI_Q_CONTROL);
+ virtio_reg_common_write_u16(virtio, VIRTIO_COMMON_REG_Q_SIZE, 0);
+ virtio_reg_common_write_u16(virtio, VIRTIO_COMMON_REG_Q_ENABLE, 0);
+
+ virtio_reg_common_write_u16(virtio, VIRTIO_COMMON_REG_Q_SELECT, VIRTIO_SCSI_Q_EVENT);
+ virtio_reg_common_write_u16(virtio, VIRTIO_COMMON_REG_Q_SIZE, 0);
+ virtio_reg_common_write_u16(virtio, VIRTIO_COMMON_REG_Q_ENABLE, 0);
+
+ /* Setup the request queue. */
+ virtio_reg_common_write_u16(virtio, VIRTIO_COMMON_REG_Q_SELECT, VIRTIO_SCSI_Q_REQUEST);
+ virtio_reg_common_write_u16(virtio, VIRTIO_COMMON_REG_Q_SIZE, VIRTIO_SCSI_RING_ELEM);
+ virtio_reg_common_write_u16(virtio, VIRTIO_COMMON_REG_Q_ENABLE, 1);
+
+ /* Set queue area addresses (only low part, leave high part 0). */
+ virtio_reg_common_write_u32(virtio, VIRTIO_COMMON_REG_Q_DESC, virtio_addr_to_phys(&virtio->Queue.aDescTbl[0]));
+ virtio_reg_common_write_u32(virtio, VIRTIO_COMMON_REG_Q_DESC + 4, 0);
+
+ virtio_reg_common_write_u32(virtio, VIRTIO_COMMON_REG_Q_DRIVER, virtio_addr_to_phys(&virtio->Queue.AvailRing));
+ virtio_reg_common_write_u32(virtio, VIRTIO_COMMON_REG_Q_DRIVER + 4, 0);
+
+ virtio_reg_common_write_u32(virtio, VIRTIO_COMMON_REG_Q_DEVICE, virtio_addr_to_phys(&virtio->Queue.UsedRing));
+ virtio_reg_common_write_u32(virtio, VIRTIO_COMMON_REG_Q_DEVICE + 4, 0);
+
+ virtio_reg_dev_cfg_write_u32(virtio, VIRTIO_DEV_CFG_REG_CDB_SZ, VIRTIO_SCSI_CDB_SZ);
+ virtio_reg_dev_cfg_write_u32(virtio, VIRTIO_DEV_CFG_REG_SENSE_SZ, VIRTIO_SCSI_SENSE_SZ);
+
+ DBG_VIRTIO("VirtIO: Q notify offset 0x%x\n", virtio_reg_common_read_u16(virtio, VIRTIO_COMMON_REG_Q_NOTIFY_OFF));
+ virtio->Queue.offNotify = virtio_reg_common_read_u16(virtio, VIRTIO_COMMON_REG_Q_NOTIFY_OFF) * virtio->u32NotifyOffMult;
+
+ /* Bring the device into operational mode. */
+ u8DevStat |= VIRTIO_CMN_REG_DEV_STS_F_DRV_OK;
+ virtio_reg_common_write_u8(virtio, VIRTIO_COMMON_REG_DEV_STS, u8DevStat);
+
+ return 0;
+}
+
+/**
+ * Init the VirtIO SCSI driver and detect attached disks.
+ */
+int virtio_scsi_init(void __far *pvHba, uint8_t u8Bus, uint8_t u8DevFn)
+{
+ virtio_t __far *virtio = (virtio_t __far *)pvHba;
+ uint8_t u8PciCapOff;
+ uint8_t u8PciCapOffVirtIo = VBOX_VIRTIO_NIL_CFG;
+ uint8_t u8PciCapVirtioSeen = 0;
+
+ /* Examine the capability list and search for the VirtIO specific capabilities. */
+ u8PciCapOff = pci_read_config_byte(u8Bus, u8DevFn, PCI_CONFIG_CAP);
+
+ while (u8PciCapOff != 0)
+ {
+ uint8_t u8PciCapId = pci_read_config_byte(u8Bus, u8DevFn, u8PciCapOff);
+ uint8_t cbPciCap = pci_read_config_byte(u8Bus, u8DevFn, u8PciCapOff + 2); /* Capability length. */
+
+ DBG_VIRTIO("Capability ID 0x%x at 0x%x\n", u8PciCapId, u8PciCapOff);
+
+ if ( u8PciCapId == PCI_CAP_ID_VNDR
+ && cbPciCap >= sizeof(virtio_pci_cap_t))
+ {
+ /* Read in the config type and see what we got. */
+ uint8_t u8PciVirtioCfg = pci_read_config_byte(u8Bus, u8DevFn, u8PciCapOff + 3);
+
+ if (u8PciCapOffVirtIo == VBOX_VIRTIO_NIL_CFG)
+ u8PciCapOffVirtIo = u8PciCapOff;
+
+ DBG_VIRTIO("VirtIO: CFG ID 0x%x\n", u8PciVirtioCfg);
+ switch (u8PciVirtioCfg)
+ {
+ case VIRTIO_PCI_CAP_COMMON_CFG:
+ case VIRTIO_PCI_CAP_NOTIFY_CFG:
+ case VIRTIO_PCI_CAP_ISR_CFG:
+ case VIRTIO_PCI_CAP_DEVICE_CFG:
+ case VIRTIO_PCI_CAP_PCI_CFG:
+ u8PciCapVirtioSeen |= 1 << (u8PciVirtioCfg - 1);
+ break;
+ default:
+ DBG_VIRTIO("VirtIO SCSI HBA with unknown PCI capability type 0x%x\n", u8PciVirtioCfg);
+ }
+ }
+
+ u8PciCapOff = pci_read_config_byte(u8Bus, u8DevFn, u8PciCapOff + 1);
+ }
+
+ /* Initialize the controller if all required PCI capabilities where found. */
+ if ( u8PciCapOffVirtIo != VBOX_VIRTIO_NIL_CFG
+ && u8PciCapVirtioSeen == 0x1f)
+ {
+ DBG_VIRTIO("VirtIO SCSI HBA with all required capabilities at 0x%x\n", u8PciCapOffVirtIo);
+
+ /* Enable PCI memory, I/O, bus mastering access in command register. */
+ pci_write_config_word(u8Bus, u8DevFn, 4, 0x7);
+ return virtio_scsi_hba_init(virtio, u8Bus, u8DevFn, u8PciCapOffVirtIo);
+ }
+ else
+ DBG_VIRTIO("VirtIO SCSI HBA with no usable PCI config access!\n");
+
+ return 1;
+}