From: Don Slutz Don.Slutz@Gmail.com
Also known as Fusion MPT disk; this controller model is supported by VirtualBox and VMware, and QEMU support patches have been posted.
Signed-off-by: Don Slutz Don.Slutz@Gmail.com Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- Makefile | 2 +- src/Kconfig | 6 ++ src/block.c | 3 + src/block.h | 1 + src/hw/mpt-scsi.c | 307 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/hw/mpt-scsi.h | 8 ++ src/post.c | 2 + 7 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 src/hw/mpt-scsi.c create mode 100644 src/hw/mpt-scsi.h
diff --git a/Makefile b/Makefile index 3a0d2e8..fcbd88a 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ SRCBOTH=misc.c stacks.c output.c string.c block.c cdrom.c disk.c mouse.c kbd.c \ hw/usb.c hw/usb-uhci.c hw/usb-ohci.c hw/usb-ehci.c \ hw/usb-hid.c hw/usb-msc.c hw/usb-uas.c \ hw/blockcmd.c hw/floppy.c hw/ata.c hw/ramdisk.c \ - hw/lsi-scsi.c hw/esp-scsi.c hw/megasas.c + hw/lsi-scsi.c hw/esp-scsi.c hw/megasas.c hw/mpt-scsi.c SRC16=$(SRCBOTH) SRC32FLAT=$(SRCBOTH) post.c memmap.c malloc.c romfile.c x86.c optionroms.c \ pmm.c font.c boot.c bootsplash.c jpeg.c bmp.c tcgbios.c sha1.c \ diff --git a/src/Kconfig b/src/Kconfig index b873cd3..8250702 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -208,6 +208,12 @@ menu "Hardware support" default y help Support boot from LSI MegaRAID SAS scsi storage. + config MPT_SCSI + depends on DRIVES + bool "LSI MPT Fusion controllers" + default y + help + Support boot from LSI MPT Fusion scsi storage. config FLOPPY depends on DRIVES && HARDWARE_IRQ bool "Floppy controller" diff --git a/src/block.c b/src/block.c index 97e05fa..ef1cd9f 100644 --- a/src/block.c +++ b/src/block.c @@ -13,6 +13,7 @@ #include "hw/esp-scsi.h" // esp_scsi_process_op #include "hw/lsi-scsi.h" // lsi_scsi_process_op #include "hw/megasas.h" // megasas_process_op +#include "hw/mpt-scsi.h" // mpt_scsi_process_op #include "hw/pci.h" // pci_bdf_to_bus #include "hw/pvscsi.h" // pvscsi_process_op #include "hw/rtc.h" // rtc_read @@ -521,6 +522,8 @@ process_op_both(struct disk_op_s *op) return esp_scsi_process_op(op); case DTYPE_MEGASAS: return megasas_process_op(op); + case DTYPE_MPT_SCSI: + return mpt_scsi_process_op(op); default: if (!MODESEGMENT) return DISK_RET_EPARAM; diff --git a/src/block.h b/src/block.h index 2ff359f..7de6f54 100644 --- a/src/block.h +++ b/src/block.h @@ -80,6 +80,7 @@ struct drive_s { #define DTYPE_ESP_SCSI 0x81 #define DTYPE_MEGASAS 0x82 #define DTYPE_PVSCSI 0x83 +#define DTYPE_MPT_SCSI 0x84 #define DTYPE_SDCARD 0x90
#define MAXDESCSIZE 80 diff --git a/src/hw/mpt-scsi.c b/src/hw/mpt-scsi.c new file mode 100644 index 0000000..c3b227b --- /dev/null +++ b/src/hw/mpt-scsi.c @@ -0,0 +1,307 @@ +// MPT Fusion boot support. +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "biosvar.h" // GET_GLOBALFLAT +#include "block.h" // struct drive_s +#include "blockcmd.h" // scsi_drive_setup +#include "config.h" // CONFIG_* +#include "malloc.h" // free +#include "output.h" // dprintf +#include "pci.h" // foreachpci +#include "pci_ids.h" // PCI_DEVICE_ID +#include "pci_regs.h" // PCI_VENDOR_ID +#include "std/disk.h" // DISK_RET_SUCCESS +#include "string.h" // memset +#include "util.h" // usleep + +#define MPT_REG_DOORBELL 0x00 +#define MPT_REG_WRITE_SEQ 0x04 +#define MPT_REG_HOST_DIAG 0x08 +#define MPT_REG_TEST 0x0c +#define MPT_REG_DIAG_DATA 0x10 +#define MPT_REG_DIAG_ADDR 0x14 +#define MPT_REG_ISTATUS 0x30 +#define MPT_REG_IMASK 0x34 +#define MPT_REG_REQ_Q 0x40 +#define MPT_REG_REP_Q 0x44 + +#define MPT_DOORBELL_MSG_RESET 0x40 +#define MPT_DOORBELL_HANDSHAKE 0x42 + +#define MPT_IMASK_DOORBELL 0x01 +#define MPT_IMASK_REPLY 0x08 + +struct mpt_lun_s { + struct drive_s drive; + struct pci_device *pci; + u32 iobase; + u8 target; + u8 lun; +}; + +u8 reply_msg[4] __attribute((aligned(4))) VARFSEG; + +#define MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST (0x00) +#define MPT_MESSAGE_HDR_FUNCTION_IOC_INIT (0x02) + +static struct MptIOCInitRequest +{ + u8 WhoInit; /* Which system sent this init request. */ + u8 Reserved1; /* Reserved */ + u8 ChainOffset; /* Chain offset in the SG list. */ + u8 Function; /* Function to execute. */ + u8 Flags; /* Flags */ + u8 MaxDevices; /* Max devices the driver can handle. */ + u8 MaxBuses; /* Max buses the driver can handle. */ + u8 MessageFlags; /* Message flags. */ + u32 MessageContext; /* Message context ID. */ + u16 ReplyFrameSize; /* Reply frame size. */ + u16 Reserved2; /* Reserved */ + u32 HostMfaHighAddr; /* Upper 32bit of the message frames. */ + u32 SenseBufferHighAddr; /* Upper 32bit of the sense buffer. */ +} MptIOCInitRequest = { + .WhoInit = 2, + .Function = MPT_MESSAGE_HDR_FUNCTION_IOC_INIT, + .MaxDevices = 8, + .MaxBuses = 1, + .ReplyFrameSize = sizeof(reply_msg), + .HostMfaHighAddr = 0, + .SenseBufferHighAddr = 0 +}; + +struct MptIOCInitReply { + u8 WhoInit; /* Which subsystem sent this init request. */ + u8 Reserved1; /* Reserved */ + u8 MessageLength; /* Message length */ + u8 Function; /* Function. */ + u8 Flags; /* Flags */ + u8 MaxDevices; /* Maximum number of devices the driver can handle. */ + u8 MaxBuses; /* Maximum number of busses the driver can handle. */ + u8 MessageFlags; /* Message flags. */ + u32 MessageContext; /* Message context ID */ + u16 Reserved2; /* Reserved */ + u16 IOCStatus; /* IO controller status. */ + u32 IOCLogInfo; /* IO controller log information. */ +}; + +typedef struct MptSCSIIORequest { + u8 TargetID; /* Target ID */ + u8 Bus; /* Bus number */ + u8 ChainOffset; /* Chain offset */ + u8 Function; /* Function number. */ + u8 CDBLength; /* CDB length. */ + u8 SenseBufferLength; /* Sense buffer length. */ + u8 Reserved; /* Reserved */ + u8 MessageFlags; /* Message flags. */ + u32 MessageContext; /* Message context ID. */ + u8 LUN[8]; /* LUN */ + u32 Control; /* Control values. */ + u8 CDB[16]; /* The CDB. */ + u32 DataLength; /* Data length. */ + u32 SenseBufferLowAddr; /* Sense buffer low 32bit address. */ +} __attribute__((packed)) MptSCSIIORequest_t; + +#define MPT_CONTEXT_MAGIC 0x5555aaaa + +typedef struct MptSGEntrySimple32 { + u32 FlagsLength; + u32 DataBufferAddressLow; +} __attribute__((packed)) MptSGEntrySimple32_t; + +static int +mpt_scsi_cmd(u32 iobase, struct disk_op_s *op, + u8 *cdb, u16 target, u16 lun, u16 blocksize) +{ + if (lun != 0) + return DISK_RET_ENOTREADY; + + u8 sense_buf[18]; + struct scsi_req { + MptSCSIIORequest_t scsi_io; + MptSGEntrySimple32_t sge; + } __attribute__((packed, aligned(4))) req = { + .scsi_io = { + .TargetID = target, + .Bus = 0, + .Function = MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST, + .CDBLength = 16, + .SenseBufferLength = 18, + .MessageContext = MPT_CONTEXT_MAGIC, + .DataLength = op->count * blocksize, + .SenseBufferLowAddr = (u32)MAKE_FLATPTR(GET_SEG(SS), &sense_buf[0]), + }, + .sge = { + /* end of list, simple entry, end of buffer, last element */ + .FlagsLength = (op->count * blocksize) | 0xD1000000, + .DataBufferAddressLow = (u32)op->buf_fl, + } + }; + + req.scsi_io.LUN[1] = lun; + memcpy(req.scsi_io.CDB, cdb, 16); + if (blocksize) { + if (scsi_is_read(op)) { + req.scsi_io.Control = 2 << 24; + } else { + req.scsi_io.Control = 1 << 24; + req.sge.FlagsLength |= 0x04000000; + } + } + + outl((u32)MAKE_FLATPTR(GET_SEG(SS), &req), iobase + MPT_REG_REQ_Q); + + for (;;) { + u32 istatus = inl(iobase + MPT_REG_ISTATUS); + if (istatus & MPT_IMASK_REPLY) { + u32 resp = inl(iobase + MPT_REG_REP_Q); + /* another read to turn interrupt off */ + inl(iobase + MPT_REG_REP_Q); + if (resp == MPT_CONTEXT_MAGIC) { + return DISK_RET_SUCCESS; + } else if (resp & 0x80000000) { + outl((u32)&reply_msg[0], iobase + MPT_REG_REP_Q); + return DISK_RET_EBADTRACK; + } + } + usleep(50); + } +} + +int +mpt_scsi_process_op(struct disk_op_s *op) +{ + if (!CONFIG_MPT_SCSI) + return DISK_RET_EBADTRACK; + + u8 cdbcmd[16]; + int blocksize = scsi_fill_cmd(op, cdbcmd, sizeof(cdbcmd)); + if (blocksize < 0) + return default_process_op(op); + + struct mpt_lun_s *llun_gf = + container_of(op->drive_gf, struct mpt_lun_s, drive); + u16 target = GET_GLOBALFLAT(llun_gf->target); + u16 lun = GET_GLOBALFLAT(llun_gf->lun); + u32 iobase = GET_GLOBALFLAT(llun_gf->iobase); + return mpt_scsi_cmd(iobase, op, cdbcmd, target, lun, blocksize); +} + +static int +mpt_scsi_add_lun(struct pci_device *pci, u32 iobase, u8 target, u8 lun) +{ + struct mpt_lun_s *llun = malloc_fseg(sizeof(*llun)); + if (!llun) { + warn_noalloc(); + return -1; + } + memset(llun, 0, sizeof(*llun)); + llun->drive.type = DTYPE_MPT_SCSI; + llun->drive.cntl_id = pci->bdf; + llun->pci = pci; + llun->target = target; + llun->lun = lun; + llun->iobase = iobase; + + char *name = znprintf(16, "mpt %02x:%02x.%x %d:%d", + pci_bdf_to_bus(pci->bdf), pci_bdf_to_dev(pci->bdf), + pci_bdf_to_fn(pci->bdf), target, lun); + int prio = bootprio_find_scsi_device(pci, target, lun); + int ret = scsi_drive_setup(&llun->drive, name, prio); + free(name); + if (ret) { + goto fail; + } + return 0; + +fail: + free(llun); + return -1; +} + +static void +mpt_scsi_scan_target(struct pci_device *pci, u32 iobase, u8 target) +{ + /* TODO: send REPORT LUNS. For now, only LUN 0 is recognized. */ + mpt_scsi_add_lun(pci, iobase, target, 0); +} + +static inline void +mpt_out_doorbell(u8 func, u8 arg, u16 iobase) +{ + outl((func << 24) | (arg << 16), iobase + MPT_REG_DOORBELL); +} + +static void +init_mpt_scsi(struct pci_device *pci, const char *dev_name) +{ + u16 *msg_in_p; + u16 bdf = pci->bdf; + u32 iobase = pci_config_readl(pci->bdf, PCI_BASE_ADDRESS_0) + & PCI_BASE_ADDRESS_IO_MASK; + struct MptIOCInitReply MptIOCInitReply; + + pci_config_maskw(bdf, PCI_COMMAND, 0, + PCI_COMMAND_IO | PCI_COMMAND_MASTER); + + dprintf(1, "found %s at %02x:%02x.%x, io @ %x\n", dev_name, + pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf), + pci_bdf_to_fn(bdf), iobase); + + // reset + mpt_out_doorbell(MPT_DOORBELL_MSG_RESET, 0, iobase); + outl(MPT_IMASK_DOORBELL|MPT_IMASK_REPLY , iobase + MPT_REG_IMASK); + outl(0, iobase + MPT_REG_ISTATUS); + + // send IOC Init message through the doorbell + mpt_out_doorbell(MPT_DOORBELL_HANDSHAKE, + sizeof(MptIOCInitRequest)/sizeof(u32), + iobase); + + outsl(iobase + MPT_REG_DOORBELL, + (u32 *)&MptIOCInitRequest, + sizeof(MptIOCInitRequest)/sizeof(u32)); + + // Read the reply 16 bits at a time. Cannot use insl + // because the port is 32 bits wide. + msg_in_p = (u16 *)&MptIOCInitReply; + while(msg_in_p != (u16 *)(&MptIOCInitReply + 1)) + *msg_in_p++ = (u16)inl(iobase + MPT_REG_DOORBELL); + + // Eat doorbell interrupt + outl(0, iobase + MPT_REG_ISTATUS); + + // Post reply message used for SCSI errors + outl((u32)&reply_msg[0], iobase + MPT_REG_REP_Q); + + for (int i = 0; i < 7; i++) + mpt_scsi_scan_target(pci, iobase, i); + + return; +} + +void +mpt_scsi_setup(void) +{ + ASSERT32FLAT(); + if (!CONFIG_MPT_SCSI) { + return; + } + + dprintf(3, "init MPT\n"); + + struct pci_device *pci; + foreachpci(pci) { + if (pci->vendor == PCI_VENDOR_ID_LSI_LOGIC) { + if (pci->device == PCI_DEVICE_ID_LSI_53C1030) { + init_mpt_scsi(pci, "lsi53c1030"); + } + if (pci->device == PCI_DEVICE_ID_LSI_SAS1068) { + init_mpt_scsi(pci, "sas1068"); + } + if (pci->device == PCI_DEVICE_ID_LSI_SAS1068E) { + init_mpt_scsi(pci, "sas1068e"); + } + } + } +} diff --git a/src/hw/mpt-scsi.h b/src/hw/mpt-scsi.h new file mode 100644 index 0000000..6535dd8 --- /dev/null +++ b/src/hw/mpt-scsi.h @@ -0,0 +1,8 @@ +#ifndef __MPT_SCSI_H +#define __MPT_SCSI_H + +struct disk_op_s; +int mpt_scsi_process_op(struct disk_op_s *op); +void mpt_scsi_setup(void); + +#endif /* __MPT_SCSI_H */ diff --git a/src/post.c b/src/post.c index 6803585..1dd075e 100644 --- a/src/post.c +++ b/src/post.c @@ -14,6 +14,7 @@ #include "hw/ata.h" // ata_setup #include "hw/esp-scsi.h" // esp_scsi_setup #include "hw/lsi-scsi.h" // lsi_scsi_setup +#include "hw/mpt-scsi.h" // lsi_scsi_setup #include "hw/megasas.h" // megasas_setup #include "hw/pvscsi.h" // pvscsi_setup #include "hw/pic.h" // pic_setup @@ -153,6 +154,7 @@ device_hardware_setup(void) esp_scsi_setup(); megasas_setup(); pvscsi_setup(); + mpt_scsi_setup(); }
static void
On Wed, Jan 27, 2016 at 03:51:02PM +0100, Paolo Bonzini wrote:
From: Don Slutz Don.Slutz@Gmail.com
Also known as Fusion MPT disk; this controller model is supported by VirtualBox and VMware, and QEMU support patches have been posted.
Signed-off-by: Don Slutz Don.Slutz@Gmail.com Signed-off-by: Paolo Bonzini pbonzini@redhat.com
Thanks. Is the upstream support for this in QEMU now?
See my comments below.
Makefile | 2 +- src/Kconfig | 6 ++ src/block.c | 3 + src/block.h | 1 + src/hw/mpt-scsi.c | 307 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/hw/mpt-scsi.h | 8 ++ src/post.c | 2 + 7 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 src/hw/mpt-scsi.c create mode 100644 src/hw/mpt-scsi.h
diff --git a/Makefile b/Makefile index 3a0d2e8..fcbd88a 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ SRCBOTH=misc.c stacks.c output.c string.c block.c cdrom.c disk.c mouse.c kbd.c \ hw/usb.c hw/usb-uhci.c hw/usb-ohci.c hw/usb-ehci.c \ hw/usb-hid.c hw/usb-msc.c hw/usb-uas.c \ hw/blockcmd.c hw/floppy.c hw/ata.c hw/ramdisk.c \
- hw/lsi-scsi.c hw/esp-scsi.c hw/megasas.c
- hw/lsi-scsi.c hw/esp-scsi.c hw/megasas.c hw/mpt-scsi.c
SRC16=$(SRCBOTH) SRC32FLAT=$(SRCBOTH) post.c memmap.c malloc.c romfile.c x86.c optionroms.c \ pmm.c font.c boot.c bootsplash.c jpeg.c bmp.c tcgbios.c sha1.c \ diff --git a/src/Kconfig b/src/Kconfig index b873cd3..8250702 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -208,6 +208,12 @@ menu "Hardware support" default y help Support boot from LSI MegaRAID SAS scsi storage.
- config MPT_SCSI
depends on DRIVES
bool "LSI MPT Fusion controllers"
default y
help
Support boot from LSI MPT Fusion scsi storage.
Unless there is good reason to believe this will work on real hardware, I think this should depend on QEMU_HARDWARE and the mpt_scsi_setup() function should check runningOnQEMU().
config FLOPPY depends on DRIVES && HARDWARE_IRQ bool "Floppy controller"
diff --git a/src/block.c b/src/block.c index 97e05fa..ef1cd9f 100644 --- a/src/block.c +++ b/src/block.c @@ -13,6 +13,7 @@ #include "hw/esp-scsi.h" // esp_scsi_process_op #include "hw/lsi-scsi.h" // lsi_scsi_process_op #include "hw/megasas.h" // megasas_process_op +#include "hw/mpt-scsi.h" // mpt_scsi_process_op #include "hw/pci.h" // pci_bdf_to_bus #include "hw/pvscsi.h" // pvscsi_process_op #include "hw/rtc.h" // rtc_read @@ -521,6 +522,8 @@ process_op_both(struct disk_op_s *op) return esp_scsi_process_op(op); case DTYPE_MEGASAS: return megasas_process_op(op);
- case DTYPE_MPT_SCSI:
default: if (!MODESEGMENT) return DISK_RET_EPARAM;return mpt_scsi_process_op(op);
diff --git a/src/block.h b/src/block.h index 2ff359f..7de6f54 100644 --- a/src/block.h +++ b/src/block.h @@ -80,6 +80,7 @@ struct drive_s { #define DTYPE_ESP_SCSI 0x81 #define DTYPE_MEGASAS 0x82 #define DTYPE_PVSCSI 0x83 +#define DTYPE_MPT_SCSI 0x84 #define DTYPE_SDCARD 0x90
#define MAXDESCSIZE 80 diff --git a/src/hw/mpt-scsi.c b/src/hw/mpt-scsi.c new file mode 100644 index 0000000..c3b227b --- /dev/null +++ b/src/hw/mpt-scsi.c @@ -0,0 +1,307 @@ +// MPT Fusion boot support. +// +// This file may be distributed under the terms of the GNU LGPLv3 license.
I think there should be an explicit copyright statement - for example:
// Copyright (C) 2016 Some One someone@example.org
+#include "biosvar.h" // GET_GLOBALFLAT +#include "block.h" // struct drive_s +#include "blockcmd.h" // scsi_drive_setup +#include "config.h" // CONFIG_* +#include "malloc.h" // free +#include "output.h" // dprintf +#include "pci.h" // foreachpci +#include "pci_ids.h" // PCI_DEVICE_ID +#include "pci_regs.h" // PCI_VENDOR_ID +#include "std/disk.h" // DISK_RET_SUCCESS +#include "string.h" // memset +#include "util.h" // usleep
+#define MPT_REG_DOORBELL 0x00 +#define MPT_REG_WRITE_SEQ 0x04 +#define MPT_REG_HOST_DIAG 0x08 +#define MPT_REG_TEST 0x0c +#define MPT_REG_DIAG_DATA 0x10 +#define MPT_REG_DIAG_ADDR 0x14 +#define MPT_REG_ISTATUS 0x30 +#define MPT_REG_IMASK 0x34 +#define MPT_REG_REQ_Q 0x40 +#define MPT_REG_REP_Q 0x44
+#define MPT_DOORBELL_MSG_RESET 0x40 +#define MPT_DOORBELL_HANDSHAKE 0x42
+#define MPT_IMASK_DOORBELL 0x01 +#define MPT_IMASK_REPLY 0x08
+struct mpt_lun_s {
- struct drive_s drive;
- struct pci_device *pci;
- u32 iobase;
- u8 target;
- u8 lun;
+};
+u8 reply_msg[4] __attribute((aligned(4))) VARFSEG;
If I'm reading this correctly, this variable is a target of DMA. I don't think it is a good idea to attempt DMA to the f-segment (eg, the f-segment is read-only at runtime). If this needs to be a global variable (instead of stack allocated) then VARLOW should work.
+#define MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST (0x00) +#define MPT_MESSAGE_HDR_FUNCTION_IOC_INIT (0x02)
+static struct MptIOCInitRequest +{
- u8 WhoInit; /* Which system sent this init request. */
- u8 Reserved1; /* Reserved */
- u8 ChainOffset; /* Chain offset in the SG list. */
- u8 Function; /* Function to execute. */
- u8 Flags; /* Flags */
- u8 MaxDevices; /* Max devices the driver can handle. */
- u8 MaxBuses; /* Max buses the driver can handle. */
- u8 MessageFlags; /* Message flags. */
- u32 MessageContext; /* Message context ID. */
- u16 ReplyFrameSize; /* Reply frame size. */
- u16 Reserved2; /* Reserved */
- u32 HostMfaHighAddr; /* Upper 32bit of the message frames. */
- u32 SenseBufferHighAddr; /* Upper 32bit of the sense buffer. */
+} MptIOCInitRequest = {
- .WhoInit = 2,
- .Function = MPT_MESSAGE_HDR_FUNCTION_IOC_INIT,
- .MaxDevices = 8,
- .MaxBuses = 1,
- .ReplyFrameSize = sizeof(reply_msg),
- .HostMfaHighAddr = 0,
- .SenseBufferHighAddr = 0
+};
+struct MptIOCInitReply {
- u8 WhoInit; /* Which subsystem sent this init request. */
- u8 Reserved1; /* Reserved */
- u8 MessageLength; /* Message length */
- u8 Function; /* Function. */
- u8 Flags; /* Flags */
- u8 MaxDevices; /* Maximum number of devices the driver can handle. */
- u8 MaxBuses; /* Maximum number of busses the driver can handle. */
- u8 MessageFlags; /* Message flags. */
- u32 MessageContext; /* Message context ID */
- u16 Reserved2; /* Reserved */
- u16 IOCStatus; /* IO controller status. */
- u32 IOCLogInfo; /* IO controller log information. */
+};
+typedef struct MptSCSIIORequest {
- u8 TargetID; /* Target ID */
- u8 Bus; /* Bus number */
- u8 ChainOffset; /* Chain offset */
- u8 Function; /* Function number. */
- u8 CDBLength; /* CDB length. */
- u8 SenseBufferLength; /* Sense buffer length. */
- u8 Reserved; /* Reserved */
- u8 MessageFlags; /* Message flags. */
- u32 MessageContext; /* Message context ID. */
- u8 LUN[8]; /* LUN */
- u32 Control; /* Control values. */
- u8 CDB[16]; /* The CDB. */
- u32 DataLength; /* Data length. */
- u32 SenseBufferLowAddr; /* Sense buffer low 32bit address. */
+} __attribute__((packed)) MptSCSIIORequest_t;
+#define MPT_CONTEXT_MAGIC 0x5555aaaa
+typedef struct MptSGEntrySimple32 {
- u32 FlagsLength;
- u32 DataBufferAddressLow;
+} __attribute__((packed)) MptSGEntrySimple32_t;
+static int +mpt_scsi_cmd(u32 iobase, struct disk_op_s *op,
u8 *cdb, u16 target, u16 lun, u16 blocksize)
+{
- if (lun != 0)
return DISK_RET_ENOTREADY;
- u8 sense_buf[18];
- struct scsi_req {
MptSCSIIORequest_t scsi_io;
MptSGEntrySimple32_t sge;
- } __attribute__((packed, aligned(4))) req = {
.scsi_io = {
.TargetID = target,
.Bus = 0,
.Function = MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST,
.CDBLength = 16,
.SenseBufferLength = 18,
.MessageContext = MPT_CONTEXT_MAGIC,
.DataLength = op->count * blocksize,
.SenseBufferLowAddr = (u32)MAKE_FLATPTR(GET_SEG(SS), &sense_buf[0]),
},
.sge = {
/* end of list, simple entry, end of buffer, last element */
.FlagsLength = (op->count * blocksize) | 0xD1000000,
.DataBufferAddressLow = (u32)op->buf_fl,
}
- };
- req.scsi_io.LUN[1] = lun;
- memcpy(req.scsi_io.CDB, cdb, 16);
- if (blocksize) {
if (scsi_is_read(op)) {
req.scsi_io.Control = 2 << 24;
} else {
req.scsi_io.Control = 1 << 24;
req.sge.FlagsLength |= 0x04000000;
}
- }
- outl((u32)MAKE_FLATPTR(GET_SEG(SS), &req), iobase + MPT_REG_REQ_Q);
- for (;;) {
u32 istatus = inl(iobase + MPT_REG_ISTATUS);
if (istatus & MPT_IMASK_REPLY) {
u32 resp = inl(iobase + MPT_REG_REP_Q);
/* another read to turn interrupt off */
inl(iobase + MPT_REG_REP_Q);
if (resp == MPT_CONTEXT_MAGIC) {
return DISK_RET_SUCCESS;
} else if (resp & 0x80000000) {
outl((u32)&reply_msg[0], iobase + MPT_REG_REP_Q);
return DISK_RET_EBADTRACK;
}
}
usleep(50);
- }
This could loop forever on a hardware error. I know other SeaBIOS drivers do this as well, but I'd prefer if new code would have some eventual timeout (eg, 30 seconds) and fail the request.
-Kevin
On 27/01/2016 16:57, Kevin O'Connor wrote:
On Wed, Jan 27, 2016 at 03:51:02PM +0100, Paolo Bonzini wrote:
From: Don Slutz Don.Slutz@Gmail.com
Also known as Fusion MPT disk; this controller model is supported by VirtualBox and VMware, and QEMU support patches have been posted.
Signed-off-by: Don Slutz Don.Slutz@Gmail.com Signed-off-by: Paolo Bonzini pbonzini@redhat.com
Thanks. Is the upstream support for this in QEMU now?
Not yet, but it should be in 2.6.
diff --git a/src/Kconfig b/src/Kconfig index b873cd3..8250702 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -208,6 +208,12 @@ menu "Hardware support" default y help Support boot from LSI MegaRAID SAS scsi storage.
- config MPT_SCSI
depends on DRIVES
bool "LSI MPT Fusion controllers"
default y
help
Support boot from LSI MPT Fusion scsi storage.
Unless there is good reason to believe this will work on real hardware, I think this should depend on QEMU_HARDWARE and the mpt_scsi_setup() function should check runningOnQEMU().
It should work on real hardware; the submission of requests works more or less the same as for a full-blown driver. I wouldn't have problems with adding the dependency though, since real hardware would come with an option ROM.
+u8 reply_msg[4] __attribute((aligned(4))) VARFSEG;
If I'm reading this correctly, this variable is a target of DMA. I don't think it is a good idea to attempt DMA to the f-segment (eg, the f-segment is read-only at runtime).
It is the target of DMA, but we don't read it; because we are only sending out one request at a time, we know that the hardware will write 0x80000000|((u32)reply_msg >> 1). We still have to set up four free bytes for hardware to use.
But VARLOW is better indeed.
- for (;;) {
u32 istatus = inl(iobase + MPT_REG_ISTATUS);
if (istatus & MPT_IMASK_REPLY) {
u32 resp = inl(iobase + MPT_REG_REP_Q);
/* another read to turn interrupt off */
inl(iobase + MPT_REG_REP_Q);
if (resp == MPT_CONTEXT_MAGIC) {
return DISK_RET_SUCCESS;
} else if (resp & 0x80000000) {
outl((u32)&reply_msg[0], iobase + MPT_REG_REP_Q);
return DISK_RET_EBADTRACK;
}
}
usleep(50);
- }
This could loop forever on a hardware error. I know other SeaBIOS drivers do this as well, but I'd prefer if new code would have some eventual timeout (eg, 30 seconds) and fail the request.
It's a bit more complicated than that; the request is outstanding and I cannot send another one until it completes. So I would have to do one of two things:
1) reset the adapter, which would probably not have a timeout so that in the end there is no difference from the current code;
2) bring the disk offline entirely in the case of a timeout, which can possibly be a cure worse than the diseases.
So for something as simple as INT 13h I think anything more complex than such a loop has no actual benefit. Of course that would be different in a more complex environment.
Paolo
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
On 01/27/2016 10:38 AM, Paolo Bonzini wrote:
On 27/01/2016 16:57, Kevin O'Connor wrote:
On Wed, Jan 27, 2016 at 03:51:02PM +0100, Paolo Bonzini wrote:
From: Don Slutz Don.Slutz@Gmail.com
Also known as Fusion MPT disk; this controller model is supported by VirtualBox and VMware, and QEMU support patches have been posted.
Signed-off-by: Don Slutz Don.Slutz@Gmail.com Signed-off-by: Paolo Bonzini pbonzini@redhat.com
Thanks. Is the upstream support for this in QEMU now?
Not yet, but it should be in 2.6.
diff --git a/src/Kconfig b/src/Kconfig index b873cd3..8250702 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -208,6 +208,12 @@ menu "Hardware support" default y help Support boot from LSI MegaRAID SAS scsi storage.
- config MPT_SCSI
depends on DRIVES
bool "LSI MPT Fusion controllers"
default y
help
Support boot from LSI MPT Fusion scsi storage.
Unless there is good reason to believe this will work on real hardware, I think this should depend on QEMU_HARDWARE and the mpt_scsi_setup() function should check runningOnQEMU().
It should work on real hardware; the submission of requests works more or less the same as for a full-blown driver. I wouldn't have problems with adding the dependency though, since real hardware would come with an option ROM.
This I might be able to test directly on real hardware. Certain systems cannot use the LSI option ROM (coreboot-based systems in particular), so I am keen on seeing this work outside of QEMU.
I will test and report back, though it may be a few days before I can do that.
- -- Timothy Pearson Raptor Engineering +1 (415) 727-8645 (direct line) +1 (512) 690-0200 (switchboard) http://www.raptorengineeringinc.com
On Wed, Jan 27, 2016 at 10:45:13AM -0600, Timothy Pearson wrote:
On 01/27/2016 10:38 AM, Paolo Bonzini wrote:
On 27/01/2016 16:57, Kevin O'Connor wrote:
On Wed, Jan 27, 2016 at 03:51:02PM +0100, Paolo Bonzini wrote:
--- a/src/Kconfig +++ b/src/Kconfig @@ -208,6 +208,12 @@ menu "Hardware support" default y help Support boot from LSI MegaRAID SAS scsi storage.
- config MPT_SCSI
depends on DRIVES
bool "LSI MPT Fusion controllers"
default y
help
Support boot from LSI MPT Fusion scsi storage.
Unless there is good reason to believe this will work on real hardware, I think this should depend on QEMU_HARDWARE and the mpt_scsi_setup() function should check runningOnQEMU().
It should work on real hardware; the submission of requests works more or less the same as for a full-blown driver. I wouldn't have problems with adding the dependency though, since real hardware would come with an option ROM.
This I might be able to test directly on real hardware. Certain systems cannot use the LSI option ROM (coreboot-based systems in particular), so I am keen on seeing this work outside of QEMU.
I will test and report back, though it may be a few days before I can do that.
Thanks! FYI, there are two existing LSI drivers (CONFIG_LSI_SCSI and CONFIG_MEGASAS) as well. And, if you want to test CONFIG_LSI_SCSI support on real hardware, you'll need to remove its check for CONFIG_COREBOOT in Kconfig and remove the check for runningOnQEMU() in lsi_scsi_setup().
Also, if there are option roms on these cards, you'll probably want to set pci->have_driver in init_lsi_scsi(), init_megasas(), and/or init_mpt_scsi() to prevent SeaBIOS from running the optionrom once the driver has been found.
-Kevin
On Wed, Jan 27, 2016 at 05:38:28PM +0100, Paolo Bonzini wrote:
On 27/01/2016 16:57, Kevin O'Connor wrote:
On Wed, Jan 27, 2016 at 03:51:02PM +0100, Paolo Bonzini wrote:
- for (;;) {
u32 istatus = inl(iobase + MPT_REG_ISTATUS);
if (istatus & MPT_IMASK_REPLY) {
u32 resp = inl(iobase + MPT_REG_REP_Q);
/* another read to turn interrupt off */
inl(iobase + MPT_REG_REP_Q);
if (resp == MPT_CONTEXT_MAGIC) {
return DISK_RET_SUCCESS;
} else if (resp & 0x80000000) {
outl((u32)&reply_msg[0], iobase + MPT_REG_REP_Q);
return DISK_RET_EBADTRACK;
}
}
usleep(50);
- }
This could loop forever on a hardware error. I know other SeaBIOS drivers do this as well, but I'd prefer if new code would have some eventual timeout (eg, 30 seconds) and fail the request.
It's a bit more complicated than that; the request is outstanding and I cannot send another one until it completes. So I would have to do one of two things:
- reset the adapter, which would probably not have a timeout so that in
the end there is no difference from the current code;
- bring the disk offline entirely in the case of a timeout, which can
possibly be a cure worse than the diseases.
So for something as simple as INT 13h I think anything more complex than such a loop has no actual benefit. Of course that would be different in a more complex environment.
Oh, I understand and agree that recovery isn't worth while. My concern is that a hardware error here will appear as a silent hang. Breaking out of the loop eventually, calling warn_timeout(), and returning an error code has the benefit of some debugging from seabios and likely some strong error messages from the calling app.
As I'm more interested in the debugging then the recovery, a simple addition like this would be an improvement IMO:
u32 end = calc_timeout(30000); // 30 second max timeout for (;;) { ... if (timer_check(end)) { warn_timeout(); return DISK_RET_ECONTROLLER; } usleep(50); }
-Kevin
On 27/01/2016 18:15, Kevin O'Connor wrote:
Oh, I understand and agree that recovery isn't worth while. My concern is that a hardware error here will appear as a silent hang. Breaking out of the loop eventually, calling warn_timeout(), and returning an error code has the benefit of some debugging from seabios and likely some strong error messages from the calling app.
As I'm more interested in the debugging then the recovery, a simple addition like this would be an improvement IMO:
u32 end = calc_timeout(30000); // 30 second max timeout for (;;) { ... if (timer_check(end)) { warn_timeout(); return DISK_RET_ECONTROLLER; } usleep(50); }
I understood this to be your proposal. The problem is that I wouldn't be able to send any further requests later, because of the way the HBA is programmed.
The only alternative would be a bare bones recovery, which however (being bare bones) would not include a timeout and thus would have the same problem we're trying to fix.
Paolo
On Wed, Jan 27, 2016 at 06:19:43PM +0100, Paolo Bonzini wrote:
On 27/01/2016 18:15, Kevin O'Connor wrote:
Oh, I understand and agree that recovery isn't worth while. My concern is that a hardware error here will appear as a silent hang. Breaking out of the loop eventually, calling warn_timeout(), and returning an error code has the benefit of some debugging from seabios and likely some strong error messages from the calling app.
As I'm more interested in the debugging then the recovery, a simple addition like this would be an improvement IMO:
u32 end = calc_timeout(30000); // 30 second max timeout for (;;) { ... if (timer_check(end)) { warn_timeout(); return DISK_RET_ECONTROLLER; } usleep(50); }
I understood this to be your proposal. The problem is that I wouldn't be able to send any further requests later, because of the way the HBA is programmed.
I'm a bit confused. Without my proposal, the machine will silently busy loop forever and thus also wont be able to send any further requests.
The only difference, as I see it, is whether or not we report the problem.
The only alternative would be a bare bones recovery, which however (being bare bones) would not include a timeout and thus would have the same problem we're trying to fix.
I agree that is not worthwhile.
-Kevin
On 27/01/2016 18:30, Kevin O'Connor wrote:
On Wed, Jan 27, 2016 at 06:19:43PM +0100, Paolo Bonzini wrote:
On 27/01/2016 18:15, Kevin O'Connor wrote:
Oh, I understand and agree that recovery isn't worth while. My concern is that a hardware error here will appear as a silent hang. Breaking out of the loop eventually, calling warn_timeout(), and returning an error code has the benefit of some debugging from seabios and likely some strong error messages from the calling app.
As I'm more interested in the debugging then the recovery, a simple addition like this would be an improvement IMO:
u32 end = calc_timeout(30000); // 30 second max timeout for (;;) { ... if (timer_check(end)) { warn_timeout(); return DISK_RET_ECONTROLLER; } usleep(50); }
I understood this to be your proposal. The problem is that I wouldn't be able to send any further requests later, because of the way the HBA is programmed.
I'm a bit confused. Without my proposal, the machine will silently busy loop forever and thus also wont be able to send any further requests.
The only difference, as I see it, is whether or not we report the problem.
The difference is that with this proposal you would definitely not be able to recover; without it, you'll get an error (good) but you'll also get an error if the timeout was spurious. In that case, if you do not have a timeout the behavior is a little better.
I think the best would be to handle all drivers in the same way, and configure the timeout at Kconfig time. What do you think? Are there drivers that do read/write timeouts?
Paolo
On Wed, Jan 27, 2016 at 06:37:28PM +0100, Paolo Bonzini wrote:
On 27/01/2016 18:30, Kevin O'Connor wrote:
On Wed, Jan 27, 2016 at 06:19:43PM +0100, Paolo Bonzini wrote:
On 27/01/2016 18:15, Kevin O'Connor wrote:
Oh, I understand and agree that recovery isn't worth while. My concern is that a hardware error here will appear as a silent hang. Breaking out of the loop eventually, calling warn_timeout(), and returning an error code has the benefit of some debugging from seabios and likely some strong error messages from the calling app.
As I'm more interested in the debugging then the recovery, a simple addition like this would be an improvement IMO:
u32 end = calc_timeout(30000); // 30 second max timeout for (;;) { ... if (timer_check(end)) { warn_timeout(); return DISK_RET_ECONTROLLER; } usleep(50); }
I understood this to be your proposal. The problem is that I wouldn't be able to send any further requests later, because of the way the HBA is programmed.
I'm a bit confused. Without my proposal, the machine will silently busy loop forever and thus also wont be able to send any further requests.
The only difference, as I see it, is whether or not we report the problem.
The difference is that with this proposal you would definitely not be able to recover; without it, you'll get an error (good) but you'll also get an error if the timeout was spurious. In that case, if you do not have a timeout the behavior is a little better.
I think the best would be to handle all drivers in the same way, and configure the timeout at Kconfig time. What do you think? Are there drivers that do read/write timeouts?
Most of the SeaBIOS drivers do implement timeouts. I think the only ones that don't are lsi-scsi, pvscsi, and virtio.
The timeout should come from the hardware spec - a lot of specs do list a maximum transaction time. (It's also possible the SCSI standards may specify a maximum time.)
If there is no maximum time known, then I think using 30 seconds (which comes from the old PATA specs) is sufficient. The transaction size in SeaBIOS is limited to 64K, and if a drive can't read/write that in 30 seconds then I think it's fair to assume something is seriously broken and that the drive wont ever respond.
-Kevin
On 27/01/2016 18:55, Kevin O'Connor wrote:
Most of the SeaBIOS drivers do implement timeouts. I think the only ones that don't are lsi-scsi, pvscsi, and virtio.
The timeout should come from the hardware spec - a lot of specs do list a maximum transaction time. (It's also possible the SCSI standards may specify a maximum time.)
If there is no maximum time known, then I think using 30 seconds (which comes from the old PATA specs) is sufficient. The transaction size in SeaBIOS is limited to 64K, and if a drive can't read/write that in 30 seconds then I think it's fair to assume something is seriously broken and that the drive wont ever respond.
Okay, I'll use 60 seconds like src/hw/megasas.c does.
Paolo