| // MegaRAID SAS boot support. |
| // |
| // Copyright (C) 2012 Hannes Reinecke, SUSE Linux Products GmbH |
| // |
| // Authors: |
| // Hannes Reinecke <hare@suse.de> |
| // |
| // based on virtio-scsi.c which is written by: |
| // Paolo Bonzini <pbonzini@redhat.com> |
| // |
| // 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); |
| } |
| } |