Plan 9 from Bell Labs’s /usr/web/sources/contrib/stallion/src/sdmpt2.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * 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<<i){
			if(p != o)
				p = seprint(p, e, " ");
			p = seprint(p, e, "%s", caps[i]);
		}
	if(p == o)
		p = seprint(p, e, "none");
	return p - o;
}

static char *
unittype(Unit *u)
{
	if(u->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 */
};

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].