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