/* * LSI Fusion-MPT SAS 2.0 SCSI Host Adapter */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/error.h" #include "../port/sd.h" extern SDifc sdmpt2ifc; static char Ebadunit[] = "invalid unit"; static char Enoreqs[] = "no free requests"; static char Etimeout[] = "request timeout"; /* System Interface Register Set */ enum { Doorbell = 0x00, WriteSequence = 0x04, HostDiagnostic = 0x08, DiagRwData = 0x10, DiagRwAddressLow = 0x14, DiagRwAddressHigh = 0x18, HostInterruptStatus = 0x30, HostInterruptMask = 0x34, DcrData = 0x38, DcrAddress = 0x3c, ReplyFreeHostIndex = 0x48, ReplyPostHostIndex = 0x6c, HcbSize = 0x74, HcbAddressLow = 0x78, HcbAddressHigh = 0x7c, RequestDescriptorPostLow = 0xc0, RequestDescriptorPostHigh = 0xc4, }; /* Doorbell */ enum { WhoInitNone = 0x0<<24, WhoInitSystemBios = 0x1<<24, WhoInitRomBios = 0x2<<24, WhoInitPciPeer = 0x3<<24, WhoInitHostDriver = 0x4<<24, WhoInitManufacturing = 0x5<<24, IocMessageUnitReset = 0x40<<24, Handshake = 0x42<<24, DoorbellUsed = 1<<27, StateReset = 0x0<<28, StateReady = 0x1<<28, StateOperational = 0x2<<28, StateFault = 0x4<<28, StateMask = 0xf<<28, }; /* HostDiagnostic */ enum { HoldIocReset = 1<<1, ResetAdapter = 1<<2, DiagRwEnable = 1<<4, ResetHistory = 1<<5, FlashBadSignature = 1<<6, DiagWriteEnable = 1<<7, HcbMode = 1<<8, ForceHcbOnReset = 1<<9, ClearFlashBadSignature = 1<<10, BootDeviceFlash = 0<<11, BootDeviceHcdw = 1<<11, }; /* HostInterruptStatus */ enum { Ioc2SysDbStatus = 1<<0, ReplyDescriptorInterrupt = 1<<3, Sys2IocDbStatus = 1<<31, }; /* HostInterruptMask */ enum { Ioc2SysDbMask = 1<<0, ReplyIntMask = 1<<3, ResetIrqMask = 1<<30, }; /* Reply Descriptors */ enum { ScsiIoSuccess = 0x0, AddressReply = 0x1, }; /* Messages */ enum { DefaultReplyLength = 5, IocInit = 0x02, IocFacts = 0x03, PortEnable = 0x06, EventNotification = 0x07, EventAck = 0x08, ScsiIoRequest = 0x00, ScsiTaskManagement = 0x01, SasIoUnitControl = 0x1b, Config = 0x04, }; /* Events */ enum { LogData = 0x0001, StateChange = 0x0002, EventChange = 0x000a, LogEntryAdded = 0x0021, GpioInterrupt = 0x0023, TempThreshold = 0x0027, HostMessage = 0x0028, PowerPerformanceChange = 0x0029, HardResetReceived = 0x0005, SasDeviceStatusChange = 0x000f, IrOperationStatus = 0x0014, IrVolume = 0x001e, IrPhysicalDisk = 0x001f, IrConfigurationChangeList = 0x0020, SasDiscovery = 0x0016, SasBroadcastPrimitive = 0x0017, SasNotifyPrimitive = 0x0026, SasInitDeviceStatusChange = 0x0018, SasInitTableOverflow = 0x0019, SasTopologyChangeList = 0x001c, SasEnclDeviceStatusChange = 0x001d, SasPhyCounter = 0x0022, SasQuiesce = 0x0025, HbdPhyEvent = 0x0024, }; typedef struct Ctlr Ctlr; typedef struct Req Req; typedef struct Unit Unit; struct Unit { uchar status; uchar link; ushort devh; ulong info; ushort flags; uchar wwn[8]; }; struct Ctlr { Lock; int ioc; char name[8]; int port; Pcidev *pcidev; SDev *sdev; int enabled; Rendez reset; RWlock resetlock; Lock doorlock; RWlock tasklock; Unit unit[16]; uchar numports; ushort maxtargs; ulong *req; Req *reqfree; Lock reqfreelock; Lock reqpostlock; ushort reqcredit; ushort iocreqfsz; ushort reqfsz; ulong *reply; ulong *replyfree; ulong *replypost; uchar replyfsz; ushort replyq; ushort replyfreeq; ushort replypostq; ushort replypostmax; ulong replyfreep; ulong replypostp; Queue *eventq; }; struct Req { Rendez; ushort smid; ulong *req; SDreq *sdreq; int done; Req *next; }; static ulong iocread(Ctlr *ctlr, int reg) { return inl(ctlr->port+reg); } static void iocwrite(Ctlr *ctlr, int reg, ulong val) { outl(ctlr->port+reg, val); } static ulong iocwait(Ctlr *ctlr, int reg, ulong mask, int on) { ulong t, val; t = MACHP(0)->ticks; for(;;){ val = iocread(ctlr, reg); if(on) if(val & mask) return val; if(!on) if(!(val & mask)) return val; if(TK2MS(MACHP(0)->ticks - t) > 10*1000) /* PG §3.7.1 */ panic("iocwait: %s: wedge reg %#.2ux val %#.8ulx", ctlr->name, reg, val); microdelay(500); } } static void ckstate(Ctlr *ctlr, int state) { ulong val; val = iocread(ctlr, Doorbell); if(val & StateMask != state) panic("ckstate: %s: bad state %#.8ulx", ctlr->name, val); } static int ckstatus(Ctlr *ctlr, ulong *reply) { ushort status; /* * IOC Status is provided in every reply, which may be used * to determine the success of a given function independent of * message content. In the event an unexpected status is * returned, a panic is issued. */ status = reply[3]>>16 & 0x7fff; /* IOCStatus */ if(status == 0x0000) /* SUCCESS */ return 1; /* * Some functions support nominal failure modes; rather than * panic, we allow the caller to determine the severity of the * condition. */ switch(reply[0]>>24){ /* Function */ case ScsiIoRequest: case ScsiTaskManagement: switch(status){ case 0x0040: /* SCSI_RECOVERED_ERROR */ case 0x0042: /* SCSI_INVALID_DEVHANDLE */ case 0x0043: /* SCSI_DEVICE_NOT_THERE */ case 0x0044: /* SCSI_DATA_OVERRUN */ case 0x0045: /* SCSI_DATA_UNDERRUN */ case 0x0046: /* SCSI_IO_DATA_ERROR */ case 0x0047: /* SCSI_PROTOCOL_ERROR */ case 0x0048: /* SCSI_TASK_TERMINATED */ case 0x0049: /* SCSI_RESIDUAL_MISMATCH */ case 0x004a: /* SCSI_TASK_MGMT_FAILED */ case 0x004b: /* SCSI_IOC_TERMINATED */ case 0x004c: /* SCSI_EXT_TERMINATED */ return 0; } break; case Config: switch(status){ case 0x0020: /* CONFIG_INVALID_ACTION */ case 0x0021: /* CONFIG_INVALID_TYPE */ case 0x0022: /* CONFIG_INVALID_PAGE */ case 0x0023: /* CONFIG_INVALID_DATA */ case 0x0024: /* CONFIG_NO_DEFAULTS */ case 0x0025: /* CONFIG_CANT_COMMIT */ return 0; } break; } panic("ckstatus: %s: bad status %#.4ux", ctlr->name, status); return -1; /* not reached */ } static int doorbell(Ctlr *ctlr, ulong *req, int nwords) { ulong val; int i; ushort *reply; ilock(&ctlr->doorlock); iocwait(ctlr, Doorbell, DoorbellUsed, 0); iocwrite(ctlr, HostInterruptStatus, 0); val = Handshake | nwords<<16; iocwrite(ctlr, Doorbell, val); iocwait(ctlr, HostInterruptStatus, Ioc2SysDbStatus, 1); iocwrite(ctlr, HostInterruptStatus, 0); iocwait(ctlr, HostInterruptStatus, Sys2IocDbStatus, 0); for(i = 0; i < nwords; ++i){ iocwrite(ctlr, Doorbell, req[i]); iocwait(ctlr, HostInterruptStatus, Sys2IocDbStatus, 0); } /* * We do something sneaky here; replies are written back * into the request buffer during handshake. Buffers must be * sized to accomodate the larger of the two messages. * * Doorbell reads yield 16 bits at a time; upper bits are * considered reserved. The reply MsgLength is located in the * first 32-bit word; a reply will always contain at least * DefaultReplyLength words. */ reply = (ushort *)req; nwords = DefaultReplyLength; for(i = 0; i < nwords*2; ++i){ iocwait(ctlr, HostInterruptStatus, Ioc2SysDbStatus, 1); reply[i] = iocread(ctlr, Doorbell); iocwrite(ctlr, HostInterruptStatus, 0); if(i == 1) nwords = reply[i] & 0xff; /* MsgLength */ } iocwait(ctlr, HostInterruptStatus, Ioc2SysDbStatus, 1); iocwrite(ctlr, HostInterruptStatus, 0); iocwait(ctlr, Doorbell, DoorbellUsed, 0); iunlock(&ctlr->doorlock); return nwords; } #define UNIT(ctlr, n) ((ctlr)->unit + (n)) static int getunitcaps(Unit *u, char *p, int l) { char *o, *e; int i; static char *caps[] = { [4] "fua", /* SATA FUA */ [5] "ncq", /* SATA NCQ */ [6] "smart", /* SATA SMART */ [7] "lba48", /* SATA 48-bit LBA */ [9] "ssp", /* SATA Software Settings Preservation */ [10] "async", /* SATA Asynchronous Notification */ [11] "partial", /* Partial Power Management Mode */ [12] "slumber", /* Slumber Power Management Mode */ [13] "fp", /* Fast Path */ }; o = p; e = p + l; for(i = 0; i < nelem(caps); ++i) if(caps[i]) if(u->flags & 1<info & 1<<7) /* Device Type */ return "sata"; return "sas"; } static char * unitstatus(Unit *u) { switch(u->status & 0xf){ /* Reason Code */ case 0x1: case 0x5: return "online"; case 0x2: return "missing"; case 0x3: return "linkchange"; case 0x4: return "nolinkchange"; default: return "unknown"; } } static char * unitlink(Unit *u) { switch(u->link>>4){ /* Current Link Rate */ case 0x8: return "1.5Gb/s"; case 0x9: return "3.0Gb/s"; case 0xa: return "6.0Gb/s"; case 0xb: return "12.0Gb/s"; default: return "unknown"; } } static int unitonline(Unit *u) { switch(u->status & 0xf){ /* Reason Code */ case 0x1: case 0x5: return 1; default: return 0; } } #define REQ(ctlr, n) ((Req *)((ctlr)->req + (n)*(ctlr)->reqfsz + \ (ctlr)->iocreqfsz)) static ulong * reallocreq(Ctlr *ctlr) { ushort n; free(ctlr->req); /* * System Request Message Frames must be allocated * contiguously, aligned on a 16-byte boundary, and be a * multiple of 16 bytes in length. */ n = (ctlr->iocreqfsz + ROUNDUP(sizeof(Req), 16))*BY2WD; ctlr->reqfsz = n/BY2WD; ctlr->req = mallocalign(ctlr->reqcredit*n, 16, 0, 0); if(ctlr->req == nil) print("reallocreq: %s: out of memory\n", ctlr->name); return ctlr->req; } static Req * nextreq(Ctlr *ctlr) { Req *r; lock(&ctlr->reqfreelock); if(r = ctlr->reqfree) ctlr->reqfree = r->next; unlock(&ctlr->reqfreelock); return r; } static void freereq(Ctlr *ctlr, Req *r) { lock(&ctlr->reqfreelock); r->next = ctlr->reqfree; ctlr->reqfree = r; unlock(&ctlr->reqfreelock); } static int reqdone(void *arg) { Req *r; r = arg; return r->done; } static void postreq(Ctlr *ctlr, Req *r, ulong *desc, int ms) { r->done = 0; ilock(&ctlr->reqpostlock); iocwrite(ctlr, RequestDescriptorPostLow, desc[0]); iocwrite(ctlr, RequestDescriptorPostHigh, desc[1]); iunlock(&ctlr->reqpostlock); while(waserror()) ; tsleep(r, reqdone, r, ms); poperror(); if(!r->done) error(Etimeout); } static void postio(Ctlr *ctlr, Req *r, ulong *desc, int ms) { rlock(&ctlr->tasklock); if(waserror()){ runlock(&ctlr->tasklock); nexterror(); } postreq(ctlr, r, desc, ms); poperror(); runlock(&ctlr->tasklock); } static void posttask(Ctlr *ctlr, Req *r, ulong *desc, int ms) { wlock(&ctlr->tasklock); if(waserror()){ wunlock(&ctlr->tasklock); nexterror(); } postreq(ctlr, r, desc, ms); poperror(); wunlock(&ctlr->tasklock); } static void mksge(ulong *sgl, void *data, int len, int write) { int flags; flags = 1<<7; /* LastElement */ flags |= 1<<6; /* EndOfBuffer */ flags |= 1<<4; /* ElementType (Simple Element) */ if(write) flags |= 1<<2; /* Direction (Write) */ flags |= 0<<1; /* AddressSize (32-bit) */ flags |= 1<<0; /* EndOfList */ sgl[0] = flags<<24; /* Flags */ sgl[0] |= len & 0xffffff; /* Length */ if(data) sgl[1] = PCIWADDR(data); /* Address */ else sgl[1] = 0; } static void iocfacts(Ctlr *ctlr) { ulong buf[16]; memset(buf, 0, 3*BY2WD); buf[0] = IocFacts<<24; /* Function */ doorbell(ctlr, buf, 3); ckstatus(ctlr, buf); ctlr->numports = buf[5]>>16 & 0xff; /* NumberOfPorts */ ctlr->reqcredit = buf[6] & 0xffff; /* RequestCredit */ ctlr->iocreqfsz = buf[9] & 0xffff; /* IOCRequestFrameSize */ ctlr->maxtargs = buf[10]>>16; /* MaxTargets */ ctlr->replyfsz = buf[12]>>16 & 0xff; /* ReplyFrameSize */ ctlr->replypostmax = buf[13] & 0xffff; /* MaxReplyDescriptorPostQueueDepth */ } static void iocinit(Ctlr *ctlr) { ulong buf[18]; memset(buf, 0, 18*BY2WD); buf[0] = IocInit<<24; /* Function */ buf[6] = ctlr->reqfsz<<16; /* SystemRequestFrameSize */ buf[7] = ctlr->replyfreeq<<16; /* ReplyFreeQueueDepth */ buf[7] |= ctlr->replypostq; /* ReplyDescriptorPostQueueDepth */ buf[10] = PCIWADDR(ctlr->req); /* SystemRequestFrameBaseAddress */ buf[12] = PCIWADDR(ctlr->replypost); /* ReplyDescriptorPostQueueAddress */ buf[14] = PCIWADDR(ctlr->replyfree); /* ReplyFreeQueueAddress */ doorbell(ctlr, buf, 18); ckstatus(ctlr, buf); ckstate(ctlr, StateOperational); } #define EVENT(x) (1U<<((x) % 32)) static void eventnotify(Ctlr *ctlr) { ulong buf[11]; memset(buf, 0, 11*BY2WD); buf[0] = EventNotification<<24; /* Function */ /* * Event notifications are masked using the bit identified * by the value of the event; see MPI §8.4. PG §3.7.4 * suggests a number of SAS events required for proper host * mapping, however the SAS_TOPOLOGY_CHANGE_LIST event is * merely sufficient. */ buf[5] = ~EVENT(SasTopologyChangeList); buf[6] = ~0; buf[7] = ~0; buf[8] = ~0; buf[9] = ~0; doorbell(ctlr, buf, 11); ckstatus(ctlr, buf); } static void eventack(Ctlr *ctlr, ushort event, ulong context) { Req *r; ulong desc[2]; r = nextreq(ctlr); if(r == nil) error(Enoreqs); memset(r->req, 0, 5*BY2WD); r->req[0] = EventAck<<24; /* Function */ r->req[3] = event; /* Event */ r->req[4] = context; /* EventContext */ desc[0] = r->smid<<16 | 0x4<<1; /* Default Request */ desc[1] = 0; postreq(ctlr, r, desc, 5*1000); /* PG §3.7.4 */ freereq(ctlr, r); } static void portenable(Ctlr *ctlr) { Req *r; ulong desc[2]; /* * The Port Enable message is posted using the Request * Descriptor Post Queue for reliable delivery of events. * Use of the System Doorbell will miss events on a * uniprocessor. */ r = nextreq(ctlr); if(r == nil) error(Enoreqs); memset(r->req, 0, 3*BY2WD); r->req[0] = PortEnable<<24; /* Function */ desc[0] = r->smid<<16 | 0x4<<1; /* Default Request */ desc[1] = 0; postreq(ctlr, r, desc, 300*1000); /* PG §3.7.1 */ freereq(ctlr, r); } static void unitconfig(Ctlr *ctlr, Unit *u) { ulong buf[7+2], page[14]; /* * Unit configuration is pulled from SAS Device Page 0. The * DeviceInfo and Flags fields provide device interconnect and * capabilities. We obtain configuration via the System * Doorbell to simplify access to the page header. */ memset(buf, 0, 7*BY2WD); buf[0] = Config<<24; /* Function */ buf[0] |= 0x00; /* Action (PAGE_HEADER) */ buf[1] = 0x12<<16; /* ExtPageType (SAS_DEVICE) */ buf[5] = 0xf<<24; /* PageType (Extended) */ buf[5] |= 0<<16; /* PageNumber */ mksge(buf+7, nil, 0, 0); /* PageBufferSGE (empty) */ doorbell(ctlr, buf, 7+2); if(!ckstatus(ctlr, buf)) error(Ebadunit); buf[0] |= 0x01; /* Action (READ_CURRENT) */ buf[6] = 0x2<<28 | u->devh; /* PageAddress (HANDLE) */ mksge(buf+7, page, sizeof page, 0); /* PageBufferSGE */ doorbell(ctlr, buf, 7+2); if(!ckstatus(ctlr, buf)) error(Ebadunit); u->info = page[7]; /* DeviceInfo */ u->flags = page[8] & 0xffff; /* Flags */ memmove(u->wwn, page+9, 8); /* DeviceName */ } static void unitremove(Ctlr *ctlr, Unit *u) { Req *r; ulong desc[2]; r = nextreq(ctlr); if(r == nil) error(Enoreqs); memset(r->req, 0, 11*BY2WD); r->req[0] = SasIoUnitControl<<24; /* Function */ r->req[0] |= 0x0d; /* Operation (REMOVE_DEVICE) */ r->req[1] = u->devh; /* DevHandle */ desc[0] = r->smid<<16 | 0x4<<1; /* Default Request */ desc[1] = 0; postreq(ctlr, r, desc, 5*1000); /* PG §3.7.4 */ freereq(ctlr, r); } static void unitreset(Ctlr *ctlr, Unit *u) { Req *r; ulong desc[2]; r = nextreq(ctlr); if(r == nil) error(Enoreqs); memset(r->req, 0, 13*BY2WD); r->req[0] = ScsiTaskManagement<<24; /* Function */ r->req[0] |= u->devh; /* DevHandle */ r->req[1] = 0x03<<8; /* TaskType (Target Reset) */ desc[0] = r->smid<<16 | 0x3<<1; /* High Priority Request */ desc[1] = 0; posttask(ctlr, r, desc, 30*1000); /* PG §3.7.3 */ freereq(ctlr, r); } static void scsiabort(Ctlr *ctlr, Unit *u, Req *s) { Req *r; ulong desc[2]; r = nextreq(ctlr); if(r == nil) error(Enoreqs); memset(r->req, 0, 13*BY2WD); r->req[0] = ScsiTaskManagement<<24; /* Function */ r->req[0] |= u->devh; /* DevHandle */ r->req[1] = 0x01<<8; /* TaskType (Abort Task) */ r->req[12] = s->smid; /* TaskMID */ desc[0] = r->smid<<16 | 0x3<<1; /* High Priority Request */ desc[1] = 0; posttask(ctlr, r, desc, 5*1000); /* PG §3.7.3 */ freereq(ctlr, r); } static void scsiio(Ctlr *ctlr, Unit *u, SDreq *sdreq) { Req *r; ulong desc[2]; r = nextreq(ctlr); if(r == nil){ sdreq->status = SDbusy; return; } r->sdreq = sdreq; memset(r->req, 0, 24*BY2WD); r->req[0] = ScsiIoRequest<<24; /* Function */ r->req[0] |= u->devh; /* DevHandle */ r->req[3] = PCIWADDR(sdreq->sense); /* SenseBufferLowAddress */ r->req[4] = sizeof sdreq->sense<<16; /* SenseBufferLength */ r->req[5] = 24; /* SGLOffset0 */ r->req[7] = sdreq->dlen; /* DataLength */ r->req[9] = sdreq->clen; /* CDBLength */ if(sdreq->write) r->req[15] = 0x1<<24; /* Data Direction (Write) */ else r->req[15] = 0x2<<24; /* Data Direction (Read) */ memmove(r->req+16, sdreq->cmd, sdreq->clen); mksge(r->req+24, sdreq->data, sdreq->dlen, sdreq->write); desc[0] = r->smid<<16 | 0x0<<1; /* SCSI IO Request */ desc[1] = u->devh<<16; if(waserror()){ /* * SCSI Task Management Requests are guaranteed to * block until the IOC has replied to outstanding * messages; see MPI §9.5. */ if(!r->done) scsiabort(ctlr, u, r); if(!r->done) unitreset(ctlr, u); if(!r->done) nexterror(); }else{ postio(ctlr, r, desc, 10*1000); poperror(); } freereq(ctlr, r); } #define REPLY(ctlr, n) ((ctlr)->reply + (n)*(ctlr)->replyfsz) #define REPLYPOST(ctlr, n) ((ctlr)->replypost + (n)*2) static ulong * reallocreply(Ctlr *ctlr) { free(ctlr->reply); /* * System Reply Message Frames are less disciplined; they do * not have to be contiguous, must be aligned on a 4-byte * boundary, and must be a multiple of 4 bytes in length. */ ctlr->replyq = ctlr->reqcredit + 32; /* PG §3.1.3 */ ctlr->reply = mallocz(ctlr->replyq * ctlr->replyfsz*BY2WD, 0); if(ctlr->reply == nil) print("reallocreply: %s: out of memory\n", ctlr->name); return ctlr->reply; } static ulong * reallocreplyfree(Ctlr *ctlr) { free(ctlr->replyfree); /* * The Reply Free Queue must be allocated contiguously, * aligned on a 16-byte boundary, and must have a depth that * is a multiple of 16 bytes. */ ctlr->replyfreeq = ROUNDUP(ctlr->replyq + 1, 16); ctlr->replyfree = mallocalign(ctlr->replyfreeq * BY2WD, 16, 0, 0); if(ctlr->replyfree == nil) print("reallocreplyfree: %s: out of memory\n", ctlr->name); return ctlr->replyfree; } static ulong * reallocreplypost(Ctlr *ctlr) { free(ctlr->replypost); /* * The Reply Descriptor Post Queue must be allocated * contiguously and aligned on a 16-byte boundary. The depth * must not exceed MaxReplyDescriptorPostQueueDepth returned * in the IOC Facts reply. */ ctlr->replypostq = MIN(ctlr->replypostmax, ROUNDUP(ctlr->replyq + ctlr->reqcredit + 1, 16)); ctlr->replypost = mallocalign(ctlr->replypostq * 2*BY2WD, 16, 0, 0); if(ctlr->replypost == nil) print("reallocreplypost: %s: out of memory\n", ctlr->name); return ctlr->replypost; } static void freereply(Ctlr *ctlr, ulong *reply) { ctlr->replyfree[ctlr->replyfreep] = PCIWADDR(reply); ctlr->replyfreep++; if(ctlr->replyfreep == ctlr->replyfreeq) ctlr->replyfreep = 0; } static ulong * nextreplypost(Ctlr *ctlr) { ulong *desc; desc = REPLYPOST(ctlr, ctlr->replypostp); if(desc[0] == 0xffffffff) if(desc[1] == 0xffffffff) return nil; return desc; } static void freereplypost(Ctlr *ctlr, ulong *desc) { desc[0] = 0xffffffff; desc[1] = 0xffffffff; ctlr->replypostp++; if(ctlr->replypostp == ctlr->replypostq) ctlr->replypostp = 0; } static void scsiok(Ctlr *ctlr, ushort smid) { Req *r; r = REQ(ctlr, smid); r->sdreq->status = SDok; r->sdreq->rlen = r->sdreq->dlen; r->done++; wakeup(r); } static void scsierror(Ctlr *ctlr, ushort smid, ulong *reply) { Req *r; ckstatus(ctlr, reply); r = REQ(ctlr, smid); r->sdreq->status = reply[3] & 0xff; /* SCSIStatus */ if(reply[3]>>8 & 1<<0) /* SCSIState (Sense Valid) */ r->sdreq->flags |= SDvalidsense; r->sdreq->rlen = reply[5]; /* TransferCount */ r->done++; wakeup(r); } static void doreply(Ctlr *ctlr, ushort smid, ulong *reply) { Req *r; ckstatus(ctlr, reply); r = REQ(ctlr, smid); r->done++; wakeup(r); } static void topoevent(Ctlr *ctlr, ulong *data) { ulong *p, *e; int i; Unit *u; /* * Unfortunately, devsd limits us to 16 devices, which * essentially precludes support for SAS expanders and/or * enclosures. A one-to-one mapping exists between a direct * attached PHY and a device for simplicity. */ if(data[0]>>16 != 0x0) /* ExpanderDevHandle */ return; if((data[0] & 0xffff) != 0x1) /* EnclosureHandle */ return; /* * SAS topology changes are handled in two phases; we first * obtain identifying information from event data. New units * require additional configuration information and missing * units must have resources released. */ p = data + 3; e = p + (data[2] & 0xff); /* NumEntries */ i = data[2]>>8 & 0xff; /* StartPhyNum */ for(; p < e && i < nelem(ctlr->unit); ++p, ++i){ u = UNIT(ctlr, i); u->status = *p>>24 & 0xff; /* PhyStatus */ u->link = *p>>16 & 0xff; /* LinkRate */ switch(u->status & 0xf){ /* Reason Code */ case 0x1: u->devh = *p & 0xffff; /* AttachedDevHandle */ unitconfig(ctlr, u); break; case 0x2: unitreset(ctlr, u); unitremove(ctlr, u); break; } } } static void doevent(Ctlr *ctlr, ulong *reply) { ushort event; ulong context; ulong *data; event = reply[5] & 0xffff; /* Event */ context = reply[6]; /* EventContext */ data = reply + 7; /* EventData */ switch(event){ case SasTopologyChangeList: topoevent(ctlr, data); break; default: panic("doevent: %s: bad event %#.4ux", ctlr->name, event); } if(reply[1]>>16 & 0xff) /* AckRequired */ eventack(ctlr, event, context); } static void qevent(Ctlr *ctlr, ulong *reply) { int n; Block *bp; n = (reply[0]>>16 & 0xff)*BY2WD; /* MsgLength */ bp = iallocb(n); if(bp == nil) panic("qevent: %s: out of memory", ctlr->name); memmove(bp->wp, reply, n); bp->wp += n; qpassnolim(ctlr->eventq, bp); } static void addressreply(Ctlr *ctlr, ushort smid, ulong *reply) { uchar fn; fn = reply[0]>>24; /* Function */ switch(fn){ case PortEnable: case ScsiTaskManagement: case SasIoUnitControl: doreply(ctlr, smid, reply); break; case ScsiIoRequest: /* * Address Replies to SCSI IO Requests always * indicate an error; see MPI §9.4. */ scsierror(ctlr, smid, reply); break; case EventNotification: /* * We queue events for handling in a separate * process to ensure sanity when the IOC requires * synchronization via acknowledgement. */ qevent(ctlr, reply); break; default: panic("addressreply: %s: bad function %#.2ux", ctlr->name, fn); } /* * To simplify handing a System Reply Message Frame back to * the IOC, we update the ReplyFreeHostIndex register * immediately. Unfortunately, this requires additional * coherence. */ freereply(ctlr, reply); coherence(); iocwrite(ctlr, ReplyFreeHostIndex, ctlr->replyfreep); } static void freectlr(Ctlr *ctlr) { iofree(ctlr->port); qfree(ctlr->eventq); free(ctlr->req); free(ctlr->reply); free(ctlr->replyfree); free(ctlr->replypost); free(ctlr->sdev); free(ctlr); } static void resetctlr(Ctlr *ctlr) { ulong val; iocwrite(ctlr, WriteSequence, 0); /* flush */ iocwrite(ctlr, WriteSequence, 0xf); iocwrite(ctlr, WriteSequence, 0x4); iocwrite(ctlr, WriteSequence, 0xb); iocwrite(ctlr, WriteSequence, 0x2); iocwrite(ctlr, WriteSequence, 0x7); iocwrite(ctlr, WriteSequence, 0xd); val = iocread(ctlr, HostDiagnostic); val |= ResetAdapter; iocwrite(ctlr, HostDiagnostic, val); delay(50); /* don't touch! */ val = iocwait(ctlr, HostDiagnostic, ResetAdapter, 0); if(val & HcbMode) panic("resetctlr: %s: host boot not supported", ctlr->name); val &= ~HoldIocReset; iocwrite(ctlr, HostDiagnostic, val); iocwrite(ctlr, WriteSequence, 0xf); /* disable */ iocwait(ctlr, Doorbell, StateMask, 1); ckstate(ctlr, StateReady); } static int initctlr(Ctlr *ctlr) { int i; Req *r; ulong *p; memset(ctlr->unit, 0, sizeof ctlr->unit); /* * Each time the controller is reset, an IOC Facts reponse * may return different values. We err on the side of caution * and reallocate resources prior to issuing an IOC Init * request. */ iocfacts(ctlr); if(reallocreq(ctlr)) if(reallocreply(ctlr)) if(reallocreplyfree(ctlr)) if(reallocreplypost(ctlr)) goto init; return -1; init: iocinit(ctlr); /* * Initialize System Request Message Frames and associated * structures. A SMID is written once to avoid headaches * constructing messages in the I/O path. A SMID of 0 must be * initialized and is considered reserved; it may not be * placed on the free list or used by the host in any way. */ ctlr->reqfree = nil; for(i = 1; i < ctlr->reqcredit; ++i){ r = REQ(ctlr, i); r->smid = i; r->req = (ulong *)r - ctlr->iocreqfsz; freereq(ctlr, r); } /* * The Reply Descriptor Post Queue must be initialized with * the unused descriptor type for each entry. This is * slightly reordered to take advantage of coherence required * when updating the ReplyFreeHostIndex register below. */ ctlr->replypostp = 0; for(i = 0; i < ctlr->replypostq; ++i){ p = REPLYPOST(ctlr, i); freereplypost(ctlr, p); } /* * The Reply Free Queue is initialized with the lower 32 * bits of the PADDR for each System Reply Frame. The * ReplyFreeHostIndex register is initialized with the next * (unused) entry. */ ctlr->replyfreep = 0; for(i = 0; i < ctlr->replyq; ++i){ p = REPLY(ctlr, i); freereply(ctlr, p); } coherence(); iocwrite(ctlr, ReplyFreeHostIndex, ctlr->replyfreep); /* * Enable event notifications. */ qreopen(ctlr->eventq); eventnotify(ctlr); return 0; } static void enablectlr(Ctlr *ctlr) { ulong val; val = iocread(ctlr, HostInterruptMask); val &= ~ReplyIntMask; iocwrite(ctlr, HostInterruptMask, val); } static void disablectlr(Ctlr *ctlr) { ulong val; val = iocread(ctlr, HostInterruptMask); val |= ReplyIntMask; iocwrite(ctlr, HostInterruptMask, val); } static SDev * mpt2pnp(void) { Pcidev *p; SDev *sdev, *head, *tail; int ioc; char name[8]; Ctlr *ctlr; static int iocs; p = nil; head = tail = nil; while(p = pcimatch(p, 0x1000, 0)){ switch(p->did){ case 0x0070: /* LSISAS2004 */ case 0x0072: /* LSISAS2008 */ case 0x0074: /* LSISAS2108 */ case 0x0076: case 0x0077: case 0x0064: /* LSISAS2116 */ case 0x0065: case 0x0080: /* LSISAS2208 */ case 0x0081: case 0x0082: case 0x0083: case 0x0084: case 0x0085: case 0x0086: /* LSISAS2308 */ case 0x0087: break; default: continue; } ioc = iocs++; snprint(name, sizeof name, "ioc%d", ioc); ctlr = malloc(sizeof *ctlr); if(ctlr == nil){ print("mpt2pnp: %s: out of memory\n", name); continue; } ctlr->ioc = ioc; strncpy(ctlr->name, name, sizeof ctlr->name); ctlr->port = p->mem[0].bar & ~1; if(ioalloc(ctlr->port, p->mem[0].size, 0, "mpt2") < 0){ print("mpt2pnp: %s: port %ux in use\n", name, ctlr->port); freectlr(ctlr); continue; } pcisetbme(p); ctlr->pcidev = p; ctlr->eventq = qopen(0, Qmsg|Qclosed, nil, nil); if(ctlr->eventq == nil){ print("mpt2pnp: %s: qopen failed\n", name); freectlr(ctlr); continue; } resetctlr(ctlr); if(initctlr(ctlr) < 0){ print("mpt2pnp: %s: initctlr failed\n", name); freectlr(ctlr); continue; } sdev = malloc(sizeof *sdev); if(sdev == nil){ print("mpt2pnp: %s: out of memory\n", name); freectlr(ctlr); continue; } sdev->ifc = &sdmpt2ifc; sdev->ctlr = ctlr; sdev->idno = 'M' + ioc; sdev->nunit = nelem(ctlr->unit); ctlr->sdev = sdev; print("#S/sd%c: %s: mpt2 sas-2 with %d ports, %d max targets\n", sdev->idno, name, ctlr->numports, ctlr->maxtargs); if(head == nil) head = sdev; else tail->next = sdev; tail = sdev; } return head; } static void mpt2interrupt(Ureg *, void *arg) { Ctlr *ctlr; ulong val, *desc, *reply; ushort smid; ctlr = arg; ilock(ctlr); val = iocread(ctlr, HostInterruptStatus); if(!(val & ReplyDescriptorInterrupt)){ iunlock(ctlr); return; } while(desc = nextreplypost(ctlr)){ switch(desc[0] & 0xf){ /* ReplyFlags */ case ScsiIoSuccess: smid = desc[0]>>16; /* SMID */ scsiok(ctlr, smid); break; case AddressReply: smid = desc[0]>>16; /* SMID */ reply = KADDR(desc[1]); /* ReplyFrameAddress */ addressreply(ctlr, smid, reply); break; default: panic("mpt2interrupt: %s: bad reply %#.8ulx %#.8ulx", ctlr->name, desc[0], desc[1]); } freereplypost(ctlr, desc); } iunlock(ctlr); /* coherence */ iocwrite(ctlr, ReplyPostHostIndex, ctlr->replypostp); } static void mpt2reset(void *arg) { Ctlr *ctlr; ctlr = arg; for(;;){ while(waserror()) ; sleep(&ctlr->reset, return0, nil); poperror(); qclose(ctlr->eventq); wlock(&ctlr->resetlock); ilock(ctlr); disablectlr(ctlr); resetctlr(ctlr); if(initctlr(ctlr) < 0){ iunlock(ctlr); wunlock(&ctlr->resetlock); print("mpt2reset: %s: initctlr failed\n", ctlr->name); continue; } enablectlr(ctlr); iunlock(ctlr); wunlock(&ctlr->resetlock); if(waserror()) print("mpt2reset: %s: %s\n",ctlr->name, up->errstr); else{ portenable(ctlr); poperror(); } } } static void mpt2event(void *arg) { Ctlr *ctlr; Block *bp; ulong *reply; /* * For the unwary, a pending reset will first close the * eventq, which will cause qbread to eventually error. The * control structure below yields to the reset kproc until the * eventq is reopened and sanity is restored. */ ctlr = arg; while(waserror()) yield(); for(;;){ rlock(&ctlr->resetlock); if(waserror()){ runlock(&ctlr->resetlock); nexterror(); } if(bp = qbread(ctlr->eventq, 0)) if(waserror()) wakeup(&ctlr->reset); else{ reply = (ulong *)bp->rp; doevent(ctlr, reply); poperror(); } freeb(bp); poperror(); runlock(&ctlr->resetlock); } } static int mpt2enable(SDev *sdev) { Ctlr *ctlr; Pcidev *p; char name[32]; ctlr = sdev->ctlr; p = ctlr->pcidev; snprint(name, sizeof name, "%s (%s)", sdev->name, sdev->ifc->name); intrenable(p->intl, mpt2interrupt, ctlr, p->tbdf, name); ilock(ctlr); enablectlr(ctlr); iunlock(ctlr); if(!ctlr->enabled++){ kproc("mpt2reset", mpt2reset, ctlr); kproc("mpt2event", mpt2event, ctlr); } if(waserror()) print("mpt2enable: %s: %s\n", ctlr->name, up->errstr); else{ portenable(ctlr); poperror(); } return 0; } static int mpt2disable(SDev *sdev) { Ctlr *ctlr; Pcidev *p; char name[32]; ctlr = sdev->ctlr; p = ctlr->pcidev; ilock(ctlr); disablectlr(ctlr); iunlock(ctlr); snprint(name, sizeof name, "%s (%s)", sdev->name, sdev->ifc->name); intrdisable(p->intl, mpt2interrupt, ctlr, p->tbdf, name); return 0; } static int mpt2rio(SDreq *sdreq) { Ctlr *ctlr; Unit *u; ctlr = sdreq->unit->dev->ctlr; u = UNIT(ctlr, sdreq->unit->subno); rlock(&ctlr->resetlock); if(waserror()){ runlock(&ctlr->resetlock); return SDeio; } if(!unitonline(u)) error(Ebadunit); if(waserror()){ wakeup(&ctlr->reset); nexterror(); } scsiio(ctlr, u, sdreq); poperror(); poperror(); runlock(&ctlr->resetlock); return sdreq->status; } static int mpt2rctl(SDunit *unit, char *p, int l) { Ctlr *ctlr; Unit *u; char *o, *e; char buf[48]; ctlr = unit->dev->ctlr; if(ctlr == nil) return 0; o = p; e = p + l; u = UNIT(ctlr, unit->subno); getunitcaps(u, buf, sizeof buf); p = seprint(p, e, "capabilities %s\n", buf); p = seprint(p, e, "wwn %ullx\n", L64GET(u->wwn)); p = seprint(p, e, "type %s\n", unittype(u)); p = seprint(p, e, "status %s\n", unitstatus(u)); p = seprint(p, e, "link %s\n", unitlink(u)); p = seprint(p, e, "geometry %ulld %uld\n", unit->sectors, unit->secsize); return p - o; } static char * mpt2rtopctl(SDev *sdev, char *p, char *e) { Ctlr *ctlr; ctlr = sdev->ctlr; return seprint(p, e, "%s mpt2 %s port %ux irq %d\n", sdev->name, ctlr->name, ctlr->port, ctlr->pcidev->intl); } SDifc sdmpt2ifc = { "mpt2", /* name */ mpt2pnp, /* pnp */ nil, /* legacy */ mpt2enable, /* enable */ mpt2disable, /* disable */ scsiverify, /* verify */ scsionline, /* online */ mpt2rio, /* rio */ mpt2rctl, /* rctl */ nil, /* wctl */ scsibio, /* bio */ nil, /* probe */ nil, /* clear */ mpt2rtopctl, /* rtopctl */ nil, /* wtopctl */ };