LSI Fusion-MPT SAS 2.0 SCSI Host Adapter Notes: Fri Aug 2 19:39:59 EDT 2013 geoff Needs revision, as communicated in mail. Reference: /n/sources/patch/sorry/sd-mpt2 Date: Tue Jul 23 07:20:38 CES 2013 Signed-off-by: sstallion@gmail.com Reviewed-by: geoff --- /sys/src/9/pc/sdmpt2.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/pc/sdmpt2.c Fri Jul 26 05:57:46 2013 @@ -0,0 +1,1518 @@ +/* + * 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; + } + for(;;){ + desc = nextreplypost(ctlr); + if(desc == nil) + break; + 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(); + } + bp = qbread(ctlr->eventq, 0); + if(bp) + 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 */ +}; --- /sys/src/9/pc/pc Tue Jul 23 07:19:45 2013 +++ /sys/src/9/pc/pc Tue Jul 23 07:19:43 2013 @@ -88,9 +88,10 @@ mtrr sdata pci sdscsi + sdiahci pci sdscsi + sdmpt2 pci sdscsi sd53c8xx pci sdscsi sdmylex pci sdscsi - sdiahci pci sdscsi uarti8250 uartpci pci --- /sys/src/9/pc/pccd Tue Jul 23 07:19:49 2013 +++ /sys/src/9/pc/pccd Tue Jul 23 07:19:47 2013 @@ -84,9 +84,10 @@ mtrr sdata pci sdscsi + sdiahci pci sdscsi + sdmpt2 pci sdscsi sd53c8xx pci sdscsi sdmylex pci sdscsi - sdiahci pci sdscsi uarti8250 uartpci pci --- /sys/src/9/pc/pccpu Tue Jul 23 07:19:53 2013 +++ /sys/src/9/pc/pccpu Tue Jul 23 07:19:51 2013 @@ -89,6 +89,7 @@ sdmv50xx pci sdscsi sdmylex pci sdscsi sdiahci pci sdscsi + sdmpt2 pci sdscsi sdaoe sdscsi ip --- /sys/src/9/pc/pccpuf Tue Jul 23 07:19:56 2013 +++ /sys/src/9/pc/pccpuf Tue Jul 23 07:19:54 2013 @@ -83,11 +83,12 @@ uarti8250 uartpci pci + sdaoe sdata pci sdscsi + sdiahci pci sdscsi + sdmpt2 pci sdscsi sd53c8xx pci sdscsi sdmylex pci sdscsi - sdiahci pci sdscsi - sdaoe vga3dfx +cur vgaark2000pv +cur --- /sys/src/9/pc/pcdisk Tue Jul 23 07:20:00 2013 +++ /sys/src/9/pc/pcdisk Tue Jul 23 07:19:58 2013 @@ -82,11 +82,12 @@ archmp mp apic mpacpi mtrr + sdaoe sdata pci sdscsi + sdiahci pci sdscsi + sdmpt2 pci sdscsi sd53c8xx pci sdscsi sdmylex pci sdscsi - sdiahci pci sdscsi - sdaoe uarti8250 uartpci pci --- /sys/src/9/pc/pcf Tue Jul 23 07:20:04 2013 +++ /sys/src/9/pc/pcf Tue Jul 23 07:20:02 2013 @@ -82,11 +82,12 @@ archmp mp apic mpacpi mtrr + sdaoe sdata pci sdscsi + sdiahci pci sdscsi + sdmpt2 pci sdscsi sd53c8xx pci sdscsi sdmylex pci sdscsi - sdiahci pci sdscsi - sdaoe uarti8250 uartpci pci --- /sys/src/9/pc/pcflop Tue Jul 23 07:20:07 2013 +++ /sys/src/9/pc/pcflop Tue Jul 23 07:20:05 2013 @@ -72,10 +72,12 @@ misc mtrr +# sdaoe sdata pci sdscsi +# sdiahci pci sdscsi +# sdmpt2 pci sdscsi sd53c8xx pci sdscsi sdmylex pci sdscsi -# sdiahci pci sdscsi uarti8250 --- /sys/src/9/pc/pcfs Tue Jul 23 07:20:11 2013 +++ /sys/src/9/pc/pcfs Tue Jul 23 07:20:09 2013 @@ -57,6 +57,7 @@ sdaoe sdata pci sdscsi sdiahci pci sdscsi + sdmpt2 pci sdscsi sd53c8xx pci sdscsi sdmylex pci sdscsi