Plan 9 from Bell Labs’s /usr/web/sources/contrib/quanstro/root/sys/src/fs/pc/mv50xx.c

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


/*
 * fileserver mv50xx driver.
 */

#include "all.h"
#include "io.h"
#include "mem.h"

enum {
	NCtlr		= 2,
	NCtlrdrv		= 8,
	NDrive		= NCtlr*NCtlrdrv,

	Read 		= 0,
	Write,
};

#define dprint(...)	//print(__VA_ARGS__)
#define idprint(...)	
#define ioprint(...)

enum {
	Rbufps		= RBUFSIZE/512,

	/* Addresses of ATA register */
	ARcmd		= 027,
	ARdev		= 026,
	ARerr		= 021,
	ARfea		= 021,
	ARlba2		= 025,
	ARlba1		= 024,
	ARlba0		= 023,
	ARseccnt	= 022,
	ARstat		= 027,

	ATAerr		= 1<<0,
	ATAdrq		= 1<<3,
	ATAdf 		= 1<<5,
	ATAdrdy 	= 1<<6,
	ATAbusy 	= 1<<7,
	ATAabort	= 1<<2,
	ATAobs		= 1<<1 | 1<<2 | 1<<4,
	ATAeIEN	= 1<<1,
	ATAsrst		= 1<<2,
	ATAhob		= 1<<7,
	ATAbad		= ATAbusy|ATAdf|ATAdrq|ATAerr,

	SFrun		= 1<<0,
	SFdone 		= 1<<1,
	SFerror 		= 1<<2,
	SFretry		= 1<<3,

	PRDeot		= 1<<15,

	/* EDMA interrupt error cause register */

	ePrtDataErr	= 1<<0,
	ePrtPRDErr	= 1<<1,
	eDevErr		= 1<<2,
	eDevDis		= 1<<3,	
	eDevCon	= 1<<4,
	eOverrun	= 1<<5,
	eUnderrun	= 1<<6,
	eSelfDis		= 1<<8,
	ePrtCRQBErr	= 1<<9,
	ePrtCRPBErr	= 1<<10,
	ePrtIntErr	= 1<<11,
	eIORdyErr	= 1<<12,

	// flags for sata 2 version
	eSelfDis2	= 1<<7,
	SerrInt		= 1<<5,

	/* EDMA Command Register */

	eEnEDMA	= 1<<0,
	eDsEDMA 	= 1<<1,
	eAtaRst 		= 1<<2,

	/* Interrupt mask for errors we care about */
	IEM		= (eDevDis | eDevCon | eSelfDis),
	IEM2		= (eDevDis | eDevCon | eSelfDis2),

	/* drive states */
	Dnull 		= 0,
	Dnew,
	Dready,
	Derror,
	Dmissing,
	Dreset,
	Dlast,

	/* drive flags */
	Dllba	 	= 1<<0,

	// phyerrata magic crap
	Mpreamp	= 0x7e0,
	Dpreamp	= 0x720,

	REV60X1B2	= 0x7,
	REV60X1C0	= 0x9,

};

static char* diskstates[Dlast] = {
	"null",
	"new",
	"ready",
	"error",
	"missing",
};

typedef struct Ctlr Ctlr;

typedef struct{
	ulong	status;
	ulong	serror;
	ulong	sctrl;
	ulong	phyctrl;
	ulong	phymode3;
	ulong	phymode4;
	uchar	fill0[0x14];
	ulong	phymode1;
	ulong	phymode2;
	char	fill1[8];
	ulong	ctrl;
	char	fill2[0x34];
	ulong	phymode;
	char	fill3[0x88];
}Bridge;				// must be 0x100 hex in length

typedef struct{
	ulong	config;		/* satahc configuration register (sata2 only) */
	ulong	rqop;		/* request queue out-pointer */
	ulong	rqip;		/* response queue in pointer */
	ulong	ict;		/* inerrupt caolescing threshold */
	ulong	itt;		/* interrupt timer threshold */
	ulong	ic;		/* interrupt cause */
	ulong	btc;		/* bridges test control */
	ulong	bts;		/* bridges test status */
	ulong	bpc;		/* bridges pin configuration */
	char	fill1[0xdc];
	Bridge	bridge[4];
}Arb;

typedef struct{
	ulong	config;		/* configuration register */
	ulong	timer;
	ulong	iec;		/* interrupt error cause */
	ulong	iem;		/* interrupt error mask */

	ulong	txbasehi;		/* request queue base address high */
	ulong	txi;		/* request queue in pointer */
	ulong	txo;		/* request queue out pointer */

	ulong	rxbasehi;		/* response queue base address high */
	ulong	rxi;		/* response queue in pointer */
	ulong	rxo;		/* response queue out pointer */

	ulong	ctl;		/* command register */
	ulong	testctl;		/* test control */
	ulong	status;
	ulong	iordyto;		/* IORDY timeout */
	char	fill[0x18];
	ulong	sataconfig;	/* sata 2 */
	char	fill[0xac];
	ushort	pio;		/* data register */
	char	pad0[2];
	uchar	err;		/* features and error */
	char	pad1[3];
	uchar	seccnt;		/* sector count */
	char	pad2[3];
	uchar	lba0;
	char	pad3[3];
	uchar	lba1;
	char	pad4[3];
	uchar	lba2;
	char	pad5[3];
	uchar	lba3;
	char	pad6[3];
	uchar	cmdstat;		/* cmd/status */
	char	pad7[3];
	uchar	altstat;		/* alternate status */
	uchar	fill2[0x1df];
	Bridge	port;
	char	fill3[0x1c00];	/* pad to 0x2000 bytes */
}Edma;

// there are 4 drives per chip.  thus an 8-port
// card has two chips.
typedef struct{
	Arb	*arb;
	Edma	*edma;
}Chip;

typedef struct{
	ulong	pa;		/* byte address of physical memory */
	ushort	count;		/* byte count (bit0 must be 0) */
	ushort	flag;
	ulong	zero;		/* high long of 64 bit address */
	ulong	reserved;
}Prd;

typedef struct{
	ulong	prdpa;		/* physical region descriptor table structures */
	ulong	zero;		/* must be zero (high long of prd address) */
	ushort	flag;		/* control flags */
	ushort	regs[11];
}Tx;

typedef struct{
	ushort	cid;		/* cID of response */
	uchar	cEdmaSts;	/* EDMA status */
	uchar	cDevSts;		/* status from disk */
	ulong	ts;		/* time stamp */
}Rx;

enum{
	DMautoneg,
	DMsatai,
	DMsataii,
};

typedef struct{
	Lock;

	Ctlr	*ctlr;
	ulong	magic;

	Bridge	*bridge;
	Edma	*edma;
	Chip	*chip;
	int	chipx;

	int	state;
	int	flag;
	uvlong	sectors;
	ulong	pm2;		// phymode 2 init state
	ulong	intick;		// check for hung westerdigital drives.
	int	wait;
	int	mode;		// DMautoneg, satai or sataii.

	char	serial[20+1];
	char	firmware[8+1];
	char	model[40+1];

	ushort	info[256];

	Prd	*prd;
	Tx	*tx;
	Rx	*rx;

	QLock;
	Rendez;
	int	cmdflag;

	int	driveno;		/* ctlr*NCtlrdrv + unit */
	int	fflag;
	Filter	rate[2];
	int	init;
}Drive;
#pragma	varargck	type	"m"	Drive*

struct Ctlr{
	Lock;

	int	irq;
	int	tbdf;
	int	rid;
	int	type;
	Pcidev	*pcidev;

	uchar	*mmio;
	ulong	*lmmio;
	Chip	chip[2];
	int	nchip;
	Drive	drive[NCtlrdrv];
	int	ndrive;
};

static Ctlr	mvctlr[NCtlr];
static int		nmvctlr;


static ushort
gbit16(void *a)
{
	uchar *i;
	ushort j;

	i = a;
	j = i[1]<<8;
	j |= i[0];
	return j;
}

static u32int
gbit32(void *a)
{
	uchar *i;
	u32int j;

	i = a;
	j = i[3]<<24;
	j |= i[2]<<16;
	j |= i[1]<<8;
	j |= i[0];
	return j;
}

static uvlong
gbit64(void *a)
{
	uchar *i;

	i = a;
	return (uvlong) gbit32(i+4)<<32|gbit32(a);
}


static void
idmove(char *p, ushort *a, int n)
{
	char *op, *e;
	int i;

	op = p;
	for(i = 0; i < n/2; i++){
		*p++ = a[i]>>8;
		*p++ = a[i];
	}
	*p = 0;
	while(p > op && *--p == ' ')
		*p = 0;
	e = p;
	p = op;
	while(*p == ' ')
		p++;
	memmove(op, p, n-(e-p));
}

/*
 * Wait for a byte to be a particular value.
 */
static int
satawait(volatile uchar *p, uchar mask, uchar v, int ms)
{
	int i;

	for(i=0; i<ms && (*p & mask) != v; i++)
		microdelay(1000);
	return (*p & mask) == v;
}

/*
 * Drive initialization
 */

// unmask in the pci registers err done
static void
unmask(ulong *mmio, int port, int coal)
{
	port &= 7;
	if(coal)
		coal = 1;
	if (port < 4)
		mmio[0x1d64/4] |= (3 << (((port&3)*2)) | (coal<<8));
	else
		mmio[0x1d64/4] |= (3 << (((port&3)*2+9)) | (coal<<17));
}

static void
mask(ulong *mmio, int port, int coal)
{
	port &= 7;
	if(coal)
		coal = 1;
	if (port < 4)
		mmio[0x1d64/4] &= ~(3 << (((port&3)*2)) | (coal<<8));
	else
		mmio[0x1d64/4] &= ~(3 << (((port&3)*2+9)) | (coal<<17));
}

/* I give up, marvell.  You win. */
static void
phyerrata(Drive *d)
{
	ulong n, m;
	enum { BadAutoCal = 0xf << 26, };

	if (d->ctlr->type == 1)
		return;
	microdelay(200);
	n = d->bridge->phymode2;
	while ((n & BadAutoCal) == BadAutoCal) {
		dprint("%m: badautocal\n", d);
		n &= ~(1<<16);
		n |= (1<<31);
		d->bridge->phymode2 = n;
		microdelay(200);
		d->bridge->phymode2 &= ~((1<<16) | (1<<31));
		microdelay(200);
		n = d->bridge->phymode2;
	}
	n &= ~(1<<31);
	d->bridge->phymode2 = n;
	microdelay(200);

	/* abra cadabra!  (random magic) */
	m = d->bridge->phymode3;
	m &= ~0x7f800000;
	m |= 0x2a800000;
	d->bridge->phymode3 = m;

	/* fix phy mode 4 */
	m = d->bridge->phymode3;
	n = d->bridge->phymode4;
	n &= ~(1<<1);
	n |= 1;
	switch(d->ctlr->rid){
	case REV60X1B2:
	default:
		d->bridge->phymode4 = n;
		d->bridge->phymode3 = m;
		break;
	case REV60X1C0:
		d->bridge->phymode4 = n;
		break;
	}

	/* revert values of pre-emphasis and signal amps to the saved ones */
	n = d->bridge->phymode2;
	n &= ~Mpreamp;
	n |= d->pm2;
	n &= ~(1<<16);
	d->bridge->phymode2 = n;
}

static void
edmacleanout(Drive *d)
{
	if(d->cmdflag&SFrun){
		d->cmdflag |= SFerror|SFdone;
		wakeup(d);
	}
}

static void
resetdisk(Drive *d)
{
	ulong n;

	dprint("%m: resetdisk\n", d);
	d->sectors = 0;
	if (d->ctlr->type == 2) {
		// without bit 8 we can boot without disks, but
		// inserted disks will never appear.  :-X
		n = d->edma->sataconfig;
		n &= 0xff;
		n |= 0x9b1100;
		d->edma->sataconfig = n;
		n = d->edma->sataconfig;	//flush
		USED(n);
	}
	d->edma->ctl = eDsEDMA;
	microdelay(1);
	d->edma->ctl = eAtaRst;
	microdelay(25);
	d->edma->ctl = 0;
	if (satawait((uchar *)&d->edma->ctl, eEnEDMA, 0, 3*1000) == 0)
		print("%m: eEnEDMA never cleared on reset\n", d);
	edmacleanout(d);
	phyerrata(d);
	d->bridge->sctrl = 0x301 | (d->mode << 4);
	d->state = Dmissing;
}

static void
edmainit(Drive *d)
{
	if(d->tx != nil)
		return;

	d->tx = ialloc(32*sizeof(Tx), 1024);
	d->rx = ialloc(32*sizeof(Rx), 256);
	d->prd = ialloc(32*sizeof(Prd), 32);
	for(int i = 0; i < 32; i++)
		d->tx[i].prdpa = PADDR(d->prd+i);
	coherence();
}

static int
configdrive(Ctlr *ctlr, Drive *d)
{
	dprint("%m: configdrive\n", d);
	edmainit(d);
	d->mode = DMsatai;
	if(d->ctlr->type == 1){
		d->edma->iem = IEM;
		d->bridge = &d->chip->arb->bridge[d->chipx];
	}else{
		d->edma->iem = IEM2;
		d->bridge = &d->chip->edma[d->chipx].port;
		d->edma->iem = ~(1<<6);
		d->pm2 = Dpreamp;
		if(d->ctlr->lmmio[0x180d8/4] & 1)
			d->pm2 = d->bridge->phymode2 & Mpreamp;
	}
	resetdisk(d);
	unmask(ctlr->lmmio, d->driveno, 0);
	delay(100);
	if(d->bridge->status){
		dprint("\t" "bridge %lx\n", d->bridge->status);
		delay(1400);		// don't burn out the power supply.
	}
	return 0;
}

static int
enabledrive(Drive *d)
{
	Edma *edma;

	dprint("%m: enabledrive..", d);

	if((d->bridge->status & 0xf) != 3){
		dprint("%m: not present\n", d);
		d->state = Dmissing;
		return -1;
	}
	edma = d->edma;
	if(satawait(&edma->cmdstat, ATAbusy, 0, 5*1000) == 0){
		dprint("%m: busy timeout\n", d);
		d->state = Dmissing;
		return -1;
	}
	edma->iec = 0;
	d->chip->arb->ic &= ~(0x101 << d->chipx);
	edma->config = 0x51f;
	if (d->ctlr->type == 2)
		edma->config |= 7<<11;
	edma->txi = PADDR(d->tx);
	edma->txo = (ulong)d->tx & 0x3e0;
	edma->rxi = (ulong)d->rx & 0xf8;
	edma->rxo = PADDR(d->rx);
	edma->ctl |= 1;		/* enable dma */

	if(d->bridge->status == 0x113){
		dprint("%m: new\n", d);
		d->state = Dnew;
	}else
		print("%m: status not forced (should be okay)\n", d);
	return 0;
}

static int
setudmamode(Drive *d, uchar mode)
{
	Edma *edma;

	dprint("%m: setudmamode %d\n", d, mode);

	edma = d->edma;
	if(edma == nil)
		panic("%m: setudamode: zero d->edma\m", d);
	if(satawait(&edma->cmdstat, ~ATAobs, ATAdrdy, 9*1000) == 0){
		print("%m: cmdstat 0x%.2ux ready timeout\n", d, edma->cmdstat);
		return 0;
	}
	edma->altstat = ATAeIEN;
	edma->err = 3;
	edma->seccnt = 0x40 | mode;
	edma->cmdstat = 0xef;
	microdelay(1);
	if(satawait(&edma->cmdstat, ATAbusy, 0, 5*1000) == 0){
		print("%m: cmdstat 0x%.2ux busy timeout\n", d, edma->cmdstat);
		return 0;
	}
	return 1;
}

static int
identifydrive(Drive *d)
{
	int i;
	ushort *id;
	Edma *edma;

	dprint("%m: identifydrive\n", d);

	if(setudmamode(d, 5) == 0)
		goto Error;

	id = d->info;
	memset(d->info, 0, sizeof d->info);
	edma = d->edma;
	if(satawait(&edma->cmdstat, ~ATAobs, ATAdrdy, 5*1000) == 0)
		goto Error;

	edma->altstat = ATAeIEN;	/* no interrupts */
	edma->cmdstat = 0xec;
	microdelay(1);
	if(satawait(&edma->cmdstat, ATAbusy, 0, 5*1000) == 0)
		goto Error;
	for(i = 0; i < 256; i++)
		id[i] = edma->pio;
	if(edma->cmdstat & ATAbad)
		goto Error;
	i = gbit16(id+83) | gbit16(id+86);
	if(i & (1<<10)){
		d->flag |= Dllba;
		d->sectors = gbit64(id+100);
	}else{
		d->flag &= ~Dllba;
		d->sectors = gbit32(id+60);
	}
	idmove(d->serial, id+10, 20);
	idmove(d->firmware, id+23, 8);
	idmove(d->model, id+27, 40);

	if(enabledrive(d) == 0) {
		d->state = Dready;
		print("%m: LLBA %lld sectors\n", d, d->sectors);
		return 0;
	}
	d->state = Derror;
	return -1;
Error:
	dprint("error...");
	d->state = Derror;
	return -1;
}

/* p. 163:
	M	recovered error
	P	protocol error
	N	PhyRdy change
	W	CommWake
	B	8-to-10 encoding error
	D	disparity error
	C	crc error
	H	handshake error
	S	link sequence error
	T	transport state transition error
	F	unrecognized fis type
	X	device changed
*/

static char stab[] = {
[1]	'M',
[10]	'P',
[16]	'N',
[18]	'W', 'B', 'D', 'C', 'H', 'S', 'T', 'F', 'X'
};
static ulong sbad = (7<<20)|(3<<23);

static void
serrdecode(ulong r, char *s, char *e)
{
	int i;

	e -=3;
	for(i = 0; i < nelem(stab) && s < e; i++){
		if((r&(1<<i)) && stab[i]){
			*s++ = stab[i];
			if(sbad&(1<<i))
				*s++ = '*';
		}
	}
	*s = 0;
}

char *iectab[] = {
	"ePrtDataErr",
	"ePrtPRDErr",
	"eDevErr",
	"eDevDis",
	"eDevCon",
	"SerrInt",
	"eUnderrun",
	"eSelfDis2",
	"eSelfDis",
	"ePrtCRQBErr",
	"ePrtCRPBErr",
	"ePrtIntErr",
	"eIORdyErr",
};

static char*
iecdecode(ulong cause)
{
	int i;

	for(i = 0; i < nelem(iectab); i++)
		if(cause&(1<<i))
			return iectab[i];
	return "";
}

enum{
	Cerror	= ePrtDataErr|ePrtPRDErr|eDevErr|eSelfDis2|ePrtCRPBErr|ePrtIntErr,
};

static void
updatedrive(Drive *d)
{
	ulong cause;
	Edma *edma;
	char buf[32+4+1];

	edma = d->edma;
	if((edma->ctl&eEnEDMA) == 0){
		// FEr SATA#4 50xx
		cause = edma->cmdstat;
		USED(cause);
	}
	cause = edma->iec;
	if(cause == 0)
		return;
	dprint("%m: cause %08ulx [%s]\n", d, cause, iecdecode(cause));
	if(cause & eDevCon)
		d->state = Dnew;
	if(cause&eDevDis && d->state == Dready)
		print("%m: pulled: st=%08ulx\n", d, cause);
	switch(d->ctlr->type){
	case 1:
		if(cause&eSelfDis)
			d->state = Derror;
		break;
	case 2:
		if(cause&Cerror)
			d->state = Derror;
		if(cause&SerrInt){
			serrdecode(d->bridge->serror, buf, buf+sizeof buf);
			dprint("%m: serror %08ulx [%s]\n", d, (ulong)d->bridge->serror, buf);
			d->bridge->serror = d->bridge->serror;
		}
	}
	edma->iec = ~cause;
}

#define Cmd(r, v) (((r)<<8) | ((v)&0xff))

static void
mvsatarequest(ushort *cmd, int atacmd, uvlong lba, int sectors, int llba)
{
	*cmd++ = Cmd(ARseccnt, 0);
	*cmd++ = Cmd(ARseccnt, sectors);
	*cmd++ = Cmd(ARfea, 0);
	if(llba){
		*cmd++ = Cmd(ARlba0, lba>>24);
		*cmd++ = Cmd(ARlba0, lba);
		*cmd++ = Cmd(ARlba1, lba>>32);
		*cmd++ = Cmd(ARlba1, lba>>8);
		*cmd++ = Cmd(ARlba2, lba>>40);
		*cmd++ = Cmd(ARlba2, lba>>16);
		*cmd++ = Cmd(ARdev, 0xe0);
	}else{
		*cmd++ = Cmd(ARlba0, lba);
		*cmd++ = Cmd(ARlba1, lba>>8);
		*cmd++ = Cmd(ARlba2, lba>>16);
		*cmd++ = Cmd(ARdev, lba>>24 | 0xe0);
	}
	*cmd = Cmd(ARcmd, atacmd) | 1<<15;
}

static uintptr
advance(uintptr pa, int shift)
{
	int n, mask;

	mask = 0x1F<<shift;
	n = (pa & mask) + (1<<shift);
	return (pa & ~mask) | (n & mask);
}

static void
start(Drive *d, int write, int atacmd, uchar *data, uvlong lba, int sectors, int ext)
{
	Edma *edma;
	Prd *prd;
	Tx *tx;

	d->intick = Ticks;
	edma = d->edma;
	tx = (Tx*)KADDR(edma->txi);
	tx->flag = (write == 0);
	prd = KADDR(tx->prdpa);
	prd->pa = PADDR(data);
	prd->zero = 0;
	prd->count = sectors*512;
	prd->flag = PRDeot;
	mvsatarequest(tx->regs, atacmd, lba, sectors, ext);
	coherence();
	edma->txi = advance(edma->txi, 5);
}

#define Rpidx(rpreg) (((rpreg)>>3) & 0x1f)

static void
complete(Drive *d)
{
	Edma *edma;
	Rx *rx;

	edma = d->edma;
	if((edma->ctl & eEnEDMA) == 0)
		return;
//	if(Rpidx(edma->rxi) == Rpidx(edma->rxo))
//		return;

	rx = (Rx*)KADDR(edma->rxo);
	if(rx->cDevSts & ATAbad)
		d->cmdflag |= SFerror;
	if (rx->cEdmaSts)
		print("cEdmaSts: %02ux\n", rx->cEdmaSts);
	d->cmdflag |= SFdone;
	edma->rxo = advance(edma->rxo, 3);
	ioprint("%m: complete: wakeup\n", d);
	wakeup(d);
}

static int
done(void *v)
{
	Drive *d;

	d = v;
	return d->cmdflag&SFdone;
}

static void
mv50interrupt(Ureg*, void *a)
{
	int i;
	ulong cause;
	Ctlr *c;
	Drive *d;

	c = a;
	ilock(c);
	cause = c->lmmio[0x1d60/4];
	for(i = 0; i < c->ndrive; i++)
		if(cause & (3<<(i*2+i/4))){
			d = c->drive+i;
//			print("%m: int: %lux\n", d, cause);
			ilock(d);
			updatedrive(d);
			while(c->chip[i/4].arb->ic & (0x0101 << (i%4))){
				c->chip[i/4].arb->ic = ~(0x101 << (i%4));
				complete(d);
			}
			iunlock(d);
		}
	iunlock(c);
}

enum{
	Nms		= 512,
	Midwait		= 16*1024/Nms-1,
	Mphywait	= 512/Nms-1,
};

static void
westerndigitalhung(Drive *d)
{
	if(d->cmdflag&SFrun)
	if(TK2MS(Ticks-d->intick) > 5*1000){
		dprint("%m: westerndigital drive hung; resetting\n", d);
		d->state = Dreset;
	}
}

static void
checkdrive(Drive *d, int i)
{
	static ulong s, olds[NCtlr*NCtlrdrv];

	ilock(d);
	s = d->bridge->status;
	if(s != olds[i]){
		dprint("%m: status: %08lx -> %08lx: %s\n", d, olds[i], s, diskstates[d->state]);
		olds[i] = s;
	}
	// westerndigitalhung(d);
	switch(d->state){
	case Dnew:
	case Dmissing:
		switch(s){
		case 0x000:
		case 0x023:
		case 0x013:
			break;
		default:
			dprint("%m: unknown state %8lx\n", d, s);
		case 0x100:
			if(++d->wait&Mphywait)
				break;
		reset:	d->mode ^= 1;
			dprint("%m: reset; new mode %d\n", d, d->mode);
			resetdisk(d);
			break;
		case 0x123:
		case 0x113:
			s = d->edma->cmdstat;
			if(s == 0x7f || (s&~ATAobs) != ATAdrdy){
				if((++d->wait&Midwait) == 0)
					goto reset;
			}else if(identifydrive(d) == -1)
				goto reset;
		}
		break;
	case Dready:
		if(s != 0)
			break;
		print("%m: pulled: st=%08ulx\n", d, s);
	case Dreset:
	case Derror:
		dprint("%m: reset: mode %d\n", d, d->mode);
		resetdisk(d);
		break;
	}
	iunlock(d);
}

static void
satakproc(void)
{
	int i, j;
	Drive *d;
	Ctlr *c;
	static Rendez r;

	for(;;){
		tsleep(&r, no, 0, Nms);
		for(i = 0; i < nmvctlr; i++){
			c = mvctlr+i;
			for(j = 0; j < c->ndrive; j++){
				d = c->drive+j;
				checkdrive(d, i*NCtlrdrv+j);
			}
		}
	}
}

void
mv50pnp(void)
{
	int i, nunit;
	uchar *base;
	ulong io, n, *mem;
	Ctlr *ctlr;
	Pcidev *p;
	Drive *d;

	dprint("mv50pnp\n");

	p = nil;
	while((p = pcimatch(p, 0x11ab, 0)) != nil){
		switch(p->did){
		case 0x5040:
		case 0x5041:
		case 0x5080:
		case 0x5081:
		case 0x6041:
		case 0x6081:
			break;
		default:
			print("mv50pnp: unknown did %ux ignored\n", (ushort)p->did);
			continue;
		}
		if(nmvctlr == NCtlr){
			print("mv50pnp: too many controllers\n");
			break;
		}
		nunit = (p->did&0xf0) >> 4;
		print("marvell 88sx%ux: %d sata-%s ports with%s flash, irq %d\n",
			(ushort)p->did, nunit,
			((p->did&0xf000)==0x6000? "ii": "i"),
			(p->did&1? "": "out"), p->intl);

		ctlr = mvctlr+nmvctlr;
		memset(ctlr, 0, sizeof *ctlr);

		io = p->mem[0].bar & ~0x0F;
		mem = (ulong*)vmap(io, p->mem[0].size);
		if(mem == 0){
			print("mv50xx: address 0x%luX in use\n", io);
			continue;
		}
		ctlr->rid = p->rid;
		ctlr->type = (p->did >> 12) & 3;

		// avert thine eyes!  (what does this do?)
		mem[0x104f0/4] = 0;

		// disable r/w combining
		if(ctlr->type == 1){
			n = mem[0xc00/4];
			n &= ~(3<<4);
			mem[0xc00/4] = n;
		}
		setvec(p->intl+24, mv50interrupt, ctlr);
		pcisetbme(p);

		ctlr->irq = p->intl;
		ctlr->tbdf = p->tbdf;
		ctlr->pcidev = p;
		ctlr->lmmio = mem;
		ctlr->mmio = (uchar*)mem;
		ctlr->nchip = (nunit+3)/4;
		ctlr->ndrive = nunit;
		for(i = 0; i < ctlr->nchip; i++){
			base = ctlr->mmio+0x20000+0x10000*i;
			ctlr->chip[i].arb = (Arb*)base;
			if(ctlr->type == 2)
				ctlr->chip[i].arb->config |= 0xf<<24;
			ctlr->chip[i].edma = (Edma*)(base + 0x2000);
		}
		for (i = 0; i < nunit; i++) {
			d = &ctlr->drive[i];
			memset(d, 0, sizeof *d);
			d->sectors = 0;
			d->ctlr = ctlr;
			d->driveno = nmvctlr*NCtlrdrv + i;
			d->chipx = i%4;
			d->chip =&ctlr->chip[i/4];
			d->edma = &d->chip->edma[d->chipx];
			configdrive(ctlr, d);
		}
		nmvctlr++;
	}

	userinit(satakproc, 0, "mvsata");
}

static int
waitready(Drive *d)
{
	ulong s, i;
	Rendez r;

	for(i = 0; i < 120; i++){
		ilock(d);
		s = d->bridge->status;
		iunlock(d);
		if(s == 0)
			return  -1;
		if (d->state == Dready)
		if((d->edma->ctl&eEnEDMA) != 0)
			return 0;
		if ((i+1)%60 == 0){
			ilock(d);
			resetdisk(d);
			iunlock(d);
		}
		memset(&r, 0, sizeof r);
		tsleep(&r, no, 0, 1000);
	}
	print("%m: not responding after 2 minutes\n", d);
	return -1;
}

static uint
getatacmd(int write, int e)
{
	static uchar cmd[2][2] = { 0xC8, 0x25, 0xCA, 0x35 };

	return cmd[write][e!=0];
}

static int
rw(Drive *d, int write, uchar *db, long len, uvlong lba)
{
	uchar *data;
	char *msg;
	uint count, try, ext, atacmd;

	ext = d->flag&Dllba;
	atacmd = getatacmd(write, ext);

	data = db;
	if(len > 64*1024)
		len = 64*1024;
	count = len/512;
	msg = "%m: bad disk\n";

	qlock(d);
	for(try = 0; try < 10; try++){
		if(waitready(d) == -1){
			qunlock(d);
			return -1;
		}

		start(d, write, atacmd, data, lba, count, ext);
		d->cmdflag = SFrun;
		sleep(d, done, d);
		d->cmdflag &= ~SFrun;

		if(d->cmdflag&SFretry){
			dprint("%m: retry\n", d);
			tsleep(d, no, 0, 1000);
			continue;
		}
		if(d->cmdflag&SFerror){
			msg = "%m: i/o error\n";
			break;
		}
		qunlock(d);
		return count*512;
	}
	qunlock(d);
	print(msg, d); 
	return -1;
}

static int
fmtm(Fmt *f)
{
	Drive *d;
	char buf[8];

	d = va_arg(f->args, Drive*);
	if(d == nil)
		snprint(buf, sizeof buf, "m[nil]");
	else
		snprint(buf, sizeof buf, "m%d", d->driveno);
	return fmtstrcpy(f, buf);
}

static Drive*
mvdev(Device *d)
{
	int i, j;
	Drive *dr;

	i = d->wren.ctrl;
	j = d->wren.targ;

	for(; i < nmvctlr; i++){
		if(j < mvctlr[i].ndrive){
			dr = mvctlr[i].drive+j;
			if(dr->state&Dready)
				return dr;
			return 0;
		}
		j -= mvctlr[i].ndrive;
	}
	panic("mv: bad drive %Z\n", d);
	return 0;
}

static void
cmd_stat(int, char*[])
{
	Ctlr *c;
	Drive *d;
	int i, j;

	for(i = 0; i < nmvctlr; i++){
		c = mvctlr+i;
		for(j = 0; j < c->ndrive; j++){
			d = c->drive+j;
			if(d->fflag == 0)
				continue;
			print("%m:\n", d);
			print("  r\t%W\n", d->rate+Read);
			print("  w\t%W\n", d->rate+Write);
		}
	}
}

void
mvinit0(void)
{
	fmtinstall('m', fmtm);
	mv50pnp();
	if(nmvctlr > 0){
		cmd_install("statm", "-- marvell sata stats", cmd_stat);
	}
}

void
mvinit(Device *dv)
{
	Drive *d;
	vlong s;
	char *lba;
	static int once;

	if(once++ == 0)
		mvinit0();

top:
	d = mvdev(dv);
	if(d == 0){
		print("\t\t" "%d.%d.%d not ready yet\n", dv->wren.ctrl, dv->wren.targ, dv->wren.lun);
		delay(500);
		goto top;
	}

	if(d->init++== 0){
		dofilter(d->rate+Read);
		dofilter(d->rate+Write);
	}

	s = d->sectors;
	lba = "";
	if(d->flag&Dllba)
		lba = "L";
	print("\t\t" "%lld sectors/%lld blocks %sLBA\n", s, s/Rbufps, lba);
}

Devsize
mvsize(Device *dv)
{
	Drive *d;

	d = mvdev(dv);
	if(d == 0)
		return 0;
	dprint("%m: mvsize %lld\n", d, d->sectors/(RBUFSIZE/512));
	return d->sectors/(RBUFSIZE/512);
}

typedef struct{
	QLock;
	uchar	*buf;
	uvlong	lba;
}Cbuf;

Cbuf cbuftab[NDrive];

enum{
	Cbad = 1LL<<63
};

Cbuf*
getcbuf(int i)
{
	Cbuf *c;

	c = cbuftab+i;
	qlock(c);
	if(c->buf == 0){
		c->buf = ialloc(64*1024, 0);
		c->lba = Cbad;
	}
	return c;
}

void
putcbuf(Cbuf *c)
{
	qunlock(c);
}

static int
crw0(Drive *d, int write, uchar *buf, uvlong lba, Cbuf *c)
{
	uvlong off;
	int rv;

	if(write == 0){
		if(lba >= c->lba && lba < c->lba+128){
			off = lba-c->lba;
			memmove(buf, c->buf+off*512, 8192);
			return 8192;
		}
		if(lba+128 > d->sectors)
			return rw(d, 0, buf, 8192, lba);
		rv = rw(d, 0, c->buf, 64*1024, lba);
		if(rv != 64*1024){
			c->lba = Cbad;
			return rv;
		}
		memmove(buf, c->buf, 8192);
		c->lba = lba;
		return 8192;
	}else{
		if(lba >= c->lba && lba < c->lba+128)
			c->lba = Cbad;
		return rw(d, 1, buf, 8192, lba);
	}
}

static int
crw(Drive *d, int write, uchar *buf, uvlong lba)
{
	Cbuf *c;
	int rv;

	c = getcbuf(d->driveno);
	rv = crw0(d, write, buf, lba, c);
	putcbuf(c);
	return rv;
}

int
mvread(Device *dv, Devsize b, void *c)
{
	Drive *d;
	int rv;

	d = mvdev(dv);
	if(d == 0)
		return 1;

//	rv = rw(d, 0, c, RBUFSIZE, b*Rbufps);
	rv = crw(d, 0, c, b*Rbufps);
	if(rv != RBUFSIZE)
		return 1;
	d->rate[Read].count++;
	d->fflag = 1;
	return 0;
}

int
mvwrite(Device *dv, Devsize b, void *c)
{
	Drive *d;
	int rv;

	d = mvdev(dv);
	if(d == 0)
		return 1;

//	rv = rw(d, 1, c, RBUFSIZE, b*Rbufps);
	rv = crw(d, 1, c, b*Rbufps);
	if(rv != RBUFSIZE)
		return 1;
	d->rate[Write].count++;
	d->fflag = 1;
	return 0;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.