// MegaRAID SAS boot support. // // Copyright (C) 2012 Hannes Reinecke, SUSE Linux Products GmbH // // Authors: // Hannes Reinecke // // based on virtio-scsi.c which is written by: // Paolo Bonzini // // 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" // pci_config_readl #include "pcidevice.h" // foreachpci #include "pci_ids.h" // PCI_DEVICE_ID_XXX #include "pci_regs.h" // PCI_VENDOR_ID #include "stacks.h" // yield #include "std/disk.h" // DISK_RET_SUCCESS #include "string.h" // memset #include "util.h" // timer_calc #define MFI_DB 0x0 // Doorbell #define MFI_OMSG0 0x18 // Outbound message 0 #define MFI_IDB 0x20 // Inbound doorbell #define MFI_ODB 0x2c // Outbound doorbell #define MFI_IQP 0x40 // Inbound queue port #define MFI_OSP0 0xb0 // Outbound scratch pad0 #define MFI_IQPL 0xc0 // Inbound queue port (low bytes) #define MFI_IQPH 0xc4 // Inbound queue port (high bytes) #define MFI_STATE_MASK 0xf0000000 #define MFI_STATE_WAIT_HANDSHAKE 0x60000000 #define MFI_STATE_BOOT_MESSAGE_PENDING 0x90000000 #define MFI_STATE_READY 0xb0000000 #define MFI_STATE_OPERATIONAL 0xc0000000 #define MFI_STATE_FAULT 0xf0000000 /* MFI Commands */ typedef enum { MFI_CMD_INIT = 0x00, MFI_CMD_LD_READ, MFI_CMD_LD_WRITE, MFI_CMD_LD_SCSI_IO, MFI_CMD_PD_SCSI_IO, MFI_CMD_DCMD, MFI_CMD_ABORT, MFI_CMD_SMP, MFI_CMD_STP } mfi_cmd_t; struct megasas_cmd_frame { u8 cmd; /*00h */ u8 sense_len; /*01h */ u8 cmd_status; /*02h */ u8 scsi_status; /*03h */ u8 target_id; /*04h */ u8 lun; /*05h */ u8 cdb_len; /*06h */ u8 sge_count; /*07h */ u32 context; /*08h */ u32 context_64; /*0Ch */ u16 flags; /*10h */ u16 timeout; /*12h */ u32 data_xfer_len; /*14h */ union { struct { u32 opcode; /*18h */ u8 mbox[12]; /*1Ch */ u32 sgl_addr; /*28h */ u32 sgl_len; /*32h */ u32 pad; /*34h */ } dcmd; struct { u32 sense_buf_lo; /*18h */ u32 sense_buf_hi; /*1Ch */ u8 cdb[16]; /*20h */ u32 sgl_addr; /*30h */ u32 sgl_len; /*34h */ } pthru; struct { u8 pad[22]; /*18h */ } gen; }; } __attribute__ ((packed)); struct mfi_ld_list_s { u32 count; u32 reserved_0; struct { u8 target; u8 lun; u16 seq; u8 state; u8 reserved_1[3]; u64 size; } lds[64]; } __attribute__ ((packed)); #define MEGASAS_POLL_TIMEOUT 60000 // 60 seconds polling timeout struct megasas_lun_s { struct drive_s drive; struct megasas_cmd_frame *frame; u32 iobase; u16 pci_id; u8 target; u8 lun; }; static int megasas_fire_cmd(u16 pci_id, u32 ioaddr, struct megasas_cmd_frame *frame) { u32 frame_addr = (u32)frame; int frame_count = 1; u8 cmd_state; dprintf(2, "Frame 0x%x\n", frame_addr); if (pci_id == PCI_DEVICE_ID_LSI_SAS2004 || pci_id == PCI_DEVICE_ID_LSI_SAS2008) { outl(0, ioaddr + MFI_IQPH); outl(frame_addr | frame_count << 1 | 1, ioaddr + MFI_IQPL); } else if (pci_id == PCI_DEVICE_ID_DELL_PERC5 || pci_id == PCI_DEVICE_ID_LSI_SAS1064R || pci_id == PCI_DEVICE_ID_LSI_VERDE_ZCR) { outl(frame_addr >> 3 | frame_count, ioaddr + MFI_IQP); } else { outl(frame_addr | frame_count << 1 | 1, ioaddr + MFI_IQP); } u32 end = timer_calc(MEGASAS_POLL_TIMEOUT); do { for (;;) { cmd_state = GET_LOWFLAT(frame->cmd_status); if (cmd_state != 0xff) break; if (timer_check(end)) { warn_timeout(); return -1; } yield(); } } while (cmd_state == 0xff); if (cmd_state == 0 || cmd_state == 0x2d) return 0; dprintf(1, "ERROR: Frame 0x%x, status 0x%x\n", frame_addr, cmd_state); return -1; } int megasas_process_op(struct disk_op_s *op) { if (!CONFIG_MEGASAS) return DISK_RET_EBADTRACK; u8 cdb[16]; int blocksize = scsi_fill_cmd(op, cdb, sizeof(cdb)); if (blocksize < 0) return default_process_op(op); struct megasas_lun_s *mlun_gf = container_of(op->drive_fl, struct megasas_lun_s, drive); struct megasas_cmd_frame *frame = GET_GLOBALFLAT(mlun_gf->frame); u16 pci_id = GET_GLOBALFLAT(mlun_gf->pci_id); int i; memset_fl(frame, 0, sizeof(*frame)); SET_LOWFLAT(frame->cmd, MFI_CMD_LD_SCSI_IO); SET_LOWFLAT(frame->cmd_status, 0xFF); SET_LOWFLAT(frame->target_id, GET_GLOBALFLAT(mlun_gf->target)); SET_LOWFLAT(frame->lun, GET_GLOBALFLAT(mlun_gf->lun)); SET_LOWFLAT(frame->flags, 0x0001); SET_LOWFLAT(frame->data_xfer_len, op->count * blocksize); SET_LOWFLAT(frame->cdb_len, 16); for (i = 0; i < 16; i++) { SET_LOWFLAT(frame->pthru.cdb[i], cdb[i]); } dprintf(2, "pthru cmd 0x%x count %d bs %d\n", cdb[0], op->count, blocksize); if (op->count) { SET_LOWFLAT(frame->pthru.sgl_addr, (u32)op->buf_fl); SET_LOWFLAT(frame->pthru.sgl_len, op->count * blocksize); SET_LOWFLAT(frame->sge_count, 1); } SET_LOWFLAT(frame->context, (u32)frame); if (megasas_fire_cmd(pci_id, GET_GLOBALFLAT(mlun_gf->iobase), frame) == 0) return DISK_RET_SUCCESS; dprintf(2, "pthru cmd 0x%x failed\n", cdb[0]); return DISK_RET_EBADTRACK; } static int megasas_add_lun(struct pci_device *pci, u32 iobase, u8 target, u8 lun) { struct megasas_lun_s *mlun = malloc_fseg(sizeof(*mlun)); char *name; int prio, ret = 0; if (!mlun) { warn_noalloc(); return -1; } memset(mlun, 0, sizeof(*mlun)); mlun->drive.type = DTYPE_MEGASAS; mlun->drive.cntl_id = pci->bdf; mlun->pci_id = pci->device; mlun->target = target; mlun->lun = lun; mlun->iobase = iobase; mlun->frame = memalign_low(256, sizeof(struct megasas_cmd_frame)); if (!mlun->frame) { warn_noalloc(); free(mlun); return -1; } boot_lchs_find_scsi_device(pci, target, lun, &(mlun->drive.lchs)); name = znprintf(MAXDESCSIZE, "MegaRAID SAS (PCI %pP) LD %d:%d" , pci, target, lun); prio = bootprio_find_scsi_device(pci, target, lun); ret = scsi_drive_setup(&mlun->drive, name, prio); free(name); if (ret) { free(mlun->frame); free(mlun); ret = -1; } return ret; } static void megasas_scan_target(struct pci_device *pci, u32 iobase) { struct mfi_ld_list_s ld_list; struct megasas_cmd_frame *frame = memalign_tmp(256, sizeof(*frame)); if (!frame) { warn_noalloc(); return; } memset(&ld_list, 0, sizeof(ld_list)); memset_fl(frame, 0, sizeof(*frame)); frame->cmd = MFI_CMD_DCMD; frame->cmd_status = 0xFF; frame->sge_count = 1; frame->flags = 0x0011; frame->data_xfer_len = sizeof(ld_list); frame->dcmd.opcode = 0x03010000; frame->dcmd.sgl_addr = (u32)MAKE_FLATPTR(GET_SEG(SS), &ld_list); frame->dcmd.sgl_len = sizeof(ld_list); frame->context = (u32)frame; if (megasas_fire_cmd(pci->device, iobase, frame) == 0) { dprintf(2, "%d LD found\n", ld_list.count); int i; for (i = 0; i < ld_list.count; i++) { dprintf(2, "LD %d:%d state 0x%x\n", ld_list.lds[i].target, ld_list.lds[i].lun, ld_list.lds[i].state); if (ld_list.lds[i].state != 0) { megasas_add_lun(pci, iobase, ld_list.lds[i].target, ld_list.lds[i].lun); } } } } static int megasas_transition_to_ready(struct pci_device *pci, u32 ioaddr) { u32 fw_state = 0, new_state, mfi_flags = 0; if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R || pci->device == PCI_DEVICE_ID_DELL_PERC5) new_state = inl(ioaddr + MFI_OMSG0) & MFI_STATE_MASK; else new_state = inl(ioaddr + MFI_OSP0) & MFI_STATE_MASK; while (fw_state != new_state) { switch (new_state) { case MFI_STATE_FAULT: dprintf(1, "ERROR: fw in fault state\n"); return -1; break; case MFI_STATE_WAIT_HANDSHAKE: mfi_flags = 0x08; /* fallthrough */ case MFI_STATE_BOOT_MESSAGE_PENDING: mfi_flags |= 0x10; if (pci->device == PCI_DEVICE_ID_LSI_SAS2004 || pci->device == PCI_DEVICE_ID_LSI_SAS2008 || pci->device == PCI_DEVICE_ID_LSI_SAS2208 || pci->device == PCI_DEVICE_ID_LSI_SAS3108) { outl(mfi_flags, ioaddr + MFI_DB); } else { outl(mfi_flags, ioaddr + MFI_IDB); } break; case MFI_STATE_OPERATIONAL: mfi_flags = 0x07; if (pci->device == PCI_DEVICE_ID_LSI_SAS2004 || pci->device == PCI_DEVICE_ID_LSI_SAS2008 || pci->device == PCI_DEVICE_ID_LSI_SAS2208 || pci->device == PCI_DEVICE_ID_LSI_SAS3108) { outl(mfi_flags, ioaddr + MFI_DB); if (pci->device == PCI_DEVICE_ID_LSI_SAS2208 || pci->device == PCI_DEVICE_ID_LSI_SAS3108) { int j = 0; u32 doorbell; while (j < MEGASAS_POLL_TIMEOUT) { doorbell = inl(ioaddr + MFI_DB) & 1; if (!doorbell) break; msleep(20); j++; } } } else { outl(mfi_flags, ioaddr + MFI_IDB); } break; case MFI_STATE_READY: dprintf(2, "MegaRAID SAS fw ready\n"); return 0; } // The current state should not last longer than poll timeout u32 end = timer_calc(MEGASAS_POLL_TIMEOUT); for (;;) { if (timer_check(end)) { break; } yield(); fw_state = new_state; if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R || pci->device == PCI_DEVICE_ID_DELL_PERC5) new_state = inl(ioaddr + MFI_OMSG0) & MFI_STATE_MASK; else new_state = inl(ioaddr + MFI_OSP0) & MFI_STATE_MASK; if (new_state != fw_state) { break; } } } dprintf(1, "ERROR: fw in state %x\n", new_state & MFI_STATE_MASK); return -1; } static void init_megasas(void *data) { struct pci_device *pci = data; u32 bar = PCI_BASE_ADDRESS_2; if (!(pci_config_readl(pci->bdf, bar) & PCI_BASE_ADDRESS_IO_MASK)) bar = PCI_BASE_ADDRESS_0; u32 iobase = pci_enable_iobar(pci, bar); if (!iobase) return; pci_enable_busmaster(pci); dprintf(1, "found MegaRAID SAS at %pP, io @ %x\n", pci, iobase); // reset if (megasas_transition_to_ready(pci, iobase) == 0) megasas_scan_target(pci, iobase); } void megasas_setup(void) { ASSERT32FLAT(); if (!CONFIG_MEGASAS) return; dprintf(3, "init megasas\n"); struct pci_device *pci; foreachpci(pci) { if (pci->vendor != PCI_VENDOR_ID_LSI_LOGIC && pci->vendor != PCI_VENDOR_ID_DELL) continue; if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R || pci->device == PCI_DEVICE_ID_LSI_SAS1078 || pci->device == PCI_DEVICE_ID_LSI_SAS1078DE || pci->device == PCI_DEVICE_ID_LSI_SAS2108 || pci->device == PCI_DEVICE_ID_LSI_SAS2108E || pci->device == PCI_DEVICE_ID_LSI_SAS2004 || pci->device == PCI_DEVICE_ID_LSI_SAS2008 || pci->device == PCI_DEVICE_ID_LSI_VERDE_ZCR || pci->device == PCI_DEVICE_ID_DELL_PERC5 || pci->device == PCI_DEVICE_ID_LSI_SAS2208 || pci->device == PCI_DEVICE_ID_LSI_SAS3108) run_thread(init_megasas, pci); } }