diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/PC/BIOS/virtio.c | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/PC/BIOS/virtio.c')
-rw-r--r-- | src/VBox/Devices/PC/BIOS/virtio.c | 701 |
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; +} |