~kris/9p

9hist

1ab3f5e46b66d2a6220fe84fce7c6dbdbf462cd3 — David du Colombier 30 years ago 441c4ce
Plan 9 from Bell Labs 1995-12-06
2 files changed, 1029 insertions(+), 268 deletions(-)

M pc/devata.c
M pc/pci.c
M pc/devata.c => pc/devata.c +987 -265
@@ 1,3 1,7 @@
/*
 * This has gotten a bit messy with the addition of multiple controller
 * and ATAPI support; needs a rewrite before adding any 'ctl' functions.
 */
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"


@@ 9,6 13,9 @@
#include	"devtab.h"

#define DPRINT if(0)print
#define XPRINT if(0)print
#define ILOCK(x)
#define IUNLOCK(x)

typedef	struct Drive		Drive;
typedef	struct Ident		Ident;


@@ 19,55 26,62 @@ typedef struct Repl		Repl;
enum
{
	/* ports */
	Pbase0=		0x1F0,
	Pbase1=		0x170,
	Pbase0=		0x1F0,	/* primary */
	Pbase1=		0x170,	/* secondary */
	Pbase2=		0x1E8,	/* tertiary */
	Pbase3=		0x168,	/* quaternary */
	Pdata=		0,	/* data port (16 bits) */
	Perror=		1,	/* error port (read) */
	Pprecomp=	1,	/* buffer mode port (write) */
	 Eabort=	(1<<2),
	Pfeature=	1,	/* buffer mode port (write) */
	Pcount=		2,	/* sector count port */
	Psector=	3,	/* sector number port */
	Pcyllsb=	4,	/* least significant byte cylinder # */
	Pcylmsb=	5,	/* most significant byte cylinder # */
	Pdh=		6,	/* drive/head port */
	 DHmagic=	0xA0,
	 DHslave=	0x10,
	Pstatus=	7,	/* status port (read) */
	 Sbusy=		 (1<<7),
	 Sready=	 (1<<6),
	 Sdf=		 (1<<5),
	 Sdrq=		 (1<<3),
	 Serr=		 (1<<0),
	Pcmd=		7,	/* cmd port (write) */

	/* commands */
	Crecal=		0x10,
	Cfirst=		0xFF,	/* pseudo command for initialisation */
	Cread=		0x20,
	Cwrite=		0x30,
	Cedd=		0x90,	/* execute device diagnostics */
	Cident=		0xEC,
	Cident2=	0xFF,	/* pseudo command for post Cident interrupt */
	Csetbuf=	0xEF,
	Cinitparam=	0x91,
	Cident2=	0xFE,	/* pseudo command for post Cident interrupt */
	Cfeature=	0xEF,

	/* conner specific commands */
	Cstandby=	0xE2,
	Cidle=		0xE1,
	Cpowerdown=	0xE3,

	Cpktcmd=	0xA0,
	Cidentd=	0xA1,
	Ccapacity=	0x25,
	Cread2=		0x28,

	/* disk states */
	Sspinning,
	Sstandby,
	Sidle,
	Spowerdown,

	/* something we have to or into the drive/head reg */
	DHmagic=	0xA0,

	/* file types */
	Qdir=		0,

	Maxxfer=	BY2PG,		/* maximum transfer size/cmd */
	Npart=		8+2,		/* 8 sub partitions, disk, and partition */
	Npart=		20+2,		/* 8 sub partitions, disk, and partition */
	Nrepl=		64,		/* maximum replacement blocks */

	Hardtimeout=	4000,		/* disk access timeout */
	Hardtimeout=	30000,		/* disk access timeout (ms) */

	NCtlr=		4,
	NDrive=		NCtlr*2,
};

#define PART(x)		((x)&0xF)
#define DRIVE(x)	(((x)>>4)&0x7)
#define MKQID(d,p)	(((d)<<4) | (p))


@@ 97,10 111,13 @@ struct Drive
	QLock;

	Controller *cp;
	int	drive;
	int	confused;	/* needs to be recalibrated (or worse) */
	int	online;
	uchar	driveno;
	uchar	dh;		/* DHmagic|Am-I-A-Slave */
	uchar	atapi;
	uchar	online;

	int	npart;		/* number of real partitions */
	int	partok;
	Partition p[Npart];
	Repl	repl;
	ulong	usetime;


@@ 112,9 129,14 @@ struct Drive
	int	sectors;	/* sectors/track */
	int	heads;		/* heads/cyl */
	long	cyl;		/* cylinders/drive */
	ulong	lbasecs;

	uchar	lba;		/* true if drive has logical block addressing */
	uchar	multi;		/* true if drive can do multiple block xfers (unused) */
	uchar	drqintr;	/* ATAPI */
	ulong	vers;		/* ATAPI */

	char	lba;		/* true if drive has logical block addressing */
	char	multi;		/* non-zero if drive does multiple block xfers */
	int	spindown;
};

/*


@@ 122,41 144,61 @@ struct Drive
 */
struct Controller
{
	QLock;			/* exclusive access to the controller */
	QLock*	ctlrlock;	/* exclusive access to the controller */

	Lock	reglock;	/* exclusive access to the registers */

	int	confused;	/* needs to be recalibrated (or worse) */
	int	pbase;		/* base port */
	uchar	ctlrno;
	uchar	noatapi;

	/*
	 *  current operation
	 */
	int	cmd;		/* current command */
	int	lastcmd;	/* debugging info */
	Rendez	r;		/* wait here for command termination */
	char	*buf;		/* xfer buffer */
	uchar	cmd;		/* current command */
	uchar	cmdblk[12];	/* ATAPI */
	int	len;		/* ATAPI */
	int	count;		/* ATAPI */
	uchar	lastcmd;	/* debugging info */
	uchar	status;
	uchar	error;
	uchar*	buf;		/* xfer buffer */
	int	nsecs;		/* length of transfer (sectors) */
	int	sofar;		/* sectors transferred so far */
	int	status;
	int	error;
	Drive	*dp;		/* drive being accessed */
	Drive*	dp;		/* drive being accessed */
};

Controller	*atac;
Drive		*ata;
static int	spindowntime;
static QLock ataprobelock;
static int ataprobedone;
static Controller *atactlr[NCtlr];
static QLock atactlrlock[NCtlr];
static Drive *atadrive[NDrive];
static int spindownmask;
static int have640b = -1;
static int pbase[NCtlr] = {
	Pbase0, Pbase1, Pbase2, Pbase3,
};
static int defirq[NCtlr] = {
	14, 15, 0, 0,
};

static void	ataintr(Ureg*, void*);
static long	ataxfer(Drive*, Partition*, int, long, long, char*);
static long	ataxfer(Drive*, Partition*, int, long, long, uchar*);
static void	ataident(Drive*);
static void	atasetbuf(Drive*, int);
static void	atafeature(Drive*, uchar);
static void	ataparams(Drive*);
static void	atapart(Drive*);
static int	ataprobe(Drive*, int, int, int);
static void	atasleep(Controller*);

static int	isatapi(Drive*);
static long	atapirwio(Chan*, char*, ulong, ulong);
static void	atapipart(Drive*);
static void	atapiintr(Controller*);

static int
atagen(Chan *c, Dirtab *tab, long ntab, long s, Dir *dirp)
atagen(Chan *c, Dirtab*, long, long s, Dir *dirp)
{
	Qid qid;
	int drive;


@@ 165,13 207,15 @@ atagen(Chan *c, Dirtab *tab, long ntab, long s, Dir *dirp)
	Partition *pp;
	ulong l;

	USED(tab, ntab);
	qid.vers = 0;
	drive = s/Npart;
	s = s % Npart;
	if(drive >= conf.nhard)

	if(drive >= NDrive)
		return -1;
	dp = &ata[drive];
	if(atadrive[drive] == 0)
		return 0;
	dp = atadrive[drive];

	if(dp->online == 0 || s >= dp->npart)
		return 0;


@@ 185,45 229,294 @@ atagen(Chan *c, Dirtab *tab, long ntab, long s, Dir *dirp)
	return 1;
}

void
atareset(void)
static void
cmd640b(void)
{
	Drive *dp;
	Controller *cp;
	uchar equip;
	char *p;
	PCIcfg* pcicfg;
	uchar r50[12];
	int devno;
	extern void pcicfgw8(int, int, int, int, void*, int);

	equip = nvramread(0x12);
	if(equip == 0)
		equip = 0x10;		/* the Globalyst 250 lies */
	/*
	 * Look for CMD640B dual PCI controllers. Amongst other
	 * bugs only one of the controllers can be active at a time.
	 * Unfortunately there's no way to tell which pair of
	 * controllers this is, so if one is found then all controller
	 * pairs are synchronised.
	 */
	pcicfg = malloc(sizeof(PCIcfg));
	pcicfg->vid = 0x1095;
	pcicfg->did = 0x0640;
	devno = 0;
	while((devno = pcimatch(0, devno, pcicfg)) != -1){
		have640b = devno-1;
		/*
		 * If one is found, make sure read-ahead is disabled on all
		 * drives and that the 2nd controller is enabled:
		 *   reg 0x51:	bit 7 - drive 1 read ahead disable
		 *  		bit 6 - drive 0 read ahead disable
		 *  		bit 3 - 2nd controller enable
		 *   reg 0x57:	bit 3 - drive 1 read ahead disable
		 *  		bit 2 - drive 0 read ahead disable
		 * Doing byte-writes to PCI configuration space is not in the
		 * spec...
		 */
		pcicfgr(0, have640b, 0, 0x50, r50, sizeof(r50));
		r50[0x01] |= 0xC8;
		pcicfgw8(0, have640b, 0, 0x51, &r50[0x01], sizeof(r50[0x01]));
		r50[0x07] |= 0x0C;
		pcicfgw8(0, have640b, 0, 0x57, &r50[0x07], sizeof(r50[0x07]));
	}
	free(pcicfg);
}

	ata = xalloc(2 * sizeof(Drive));
	atac = xalloc(sizeof(Controller));
static void
rz1000(void)
{
	PCIcfg* pcicfg;
	ulong r40;
	int devno;

	cp = atac;
	cp->buf = 0;
	cp->lastcmd = cp->cmd;
	cp->cmd = 0;
	cp->pbase = Pbase0;
	setvec(ATAvec0, ataintr, 0);

	dp = ata;
	if(equip & 0xf0){
		dp->drive = 0;
		dp->online = 0;
		dp->cp = cp;
		dp++;
	/*
	 * Look for PC-Tech RZ1000 controllers and turn off prefetch.
	 * This is overkill, but cheap.
	 */
	pcicfg = malloc(sizeof(PCIcfg));
	pcicfg->vid = 0x1042;
	pcicfg->did = 0;
	devno = 0;
	while((devno = pcimatch(0, devno, pcicfg)) != -1){
		if(pcicfg->did != 0x1000 && pcicfg->did != 0x1001)
			continue;
		pcicfgr(0, devno-1, 0, 0x40, &r40, sizeof(r40));
		r40 &= ~0x2000;
		pcicfgw(0, devno-1, 0, 0x40, &r40, sizeof(r40));
	}
	if((equip & 0x0f)){
		dp->drive = 1;
		dp->online = 0;
		dp->cp = cp;
		dp++;
	free(pcicfg);
}

static int
atactlrwait(Controller* ctlr, uchar pdh, uchar ready, ulong ticks)
{
	int port;
	uchar dh, status;

	port = ctlr->pbase;
	dh = (inb(port+Pdh) & DHslave)^(pdh & DHslave);
	ticks += m->ticks+1;

	do{
		status = inb(port+Pstatus);
		if(status & Sbusy)
			continue;
		if(dh){
			outb(port+Pdh, pdh);
			dh = 0;
			continue;
		}
		if((status & ready) == ready)
			return 0;
	}while(m->ticks < ticks);

	DPRINT("ata%d: ctlrwait failed %uX\n", ctlr->ctlrno, status);
	outb(port+Pdh, DHmagic);
	return 1;
}

static void
atadrivealloc(Controller* ctlr, int driveno, int atapi)
{
	Drive *drive;

	if((drive = xalloc(sizeof(Drive))) == 0){
		DPRINT("ata%d: can't xalloc drive0\n", ctlr->ctlrno);
		return;
	}
	drive->cp = ctlr;
	drive->driveno = driveno;
	sprint(drive->vol, "hd%d", drive->driveno);
	drive->dh = DHmagic;
	if(driveno & 0x01)
		drive->dh |= DHslave;
	drive->vers = 1;
	if(atapi)
		drive->atapi = 1;

	atadrive[driveno] = drive;
}

static int
atactlrprobe(int ctlrno, int irq)
{
	Controller *ctlr;
	int atapi, mask, port;
	uchar error, status;

	/*
	 * Check the existence of a controller by verifying a sensible
	 * value can be written to and read from the drive/head register.
	 * We define the primary/secondary/tertiary and quaternary controller
	 * port addresses to be at fixed values.
	 * If it's OK, allocate and initialise a Controller structure.
	 */
	port = pbase[ctlrno];
	outb(port+Pdh, DHmagic);
	microdelay(1);
	if((inb(port+Pdh) & 0xFF) != DHmagic){
		DPRINT("ata%d: DHmagic not ok\n", ctlrno);
		return -1;
	}
	DPRINT("ata%d: DHmagic ok\n", ctlrno);
	if((ctlr = xalloc(sizeof(Controller))) == 0)
		return -1;
	ctlr->pbase = port;
	ctlr->ctlrno = ctlrno;
	ctlr->lastcmd = 0xFF;

	/*
	 * Attempt to check the existence of drives on the controller
	 * by issuing a 'check device diagnostics' command.
	 * Issuing a device reset here would possibly destroy any BIOS
	 * drive remapping and, anyway, some controllers (Vibra16) don't
	 * seem to implement the control-block registers.
	 * Unfortunately the vector must be set at this point as the Cedd
	 * command will generate an interrupt, which means the ataintr routine
	 * will be left on the interrupt call chain even if there are no
	 * drives found.
	 * At least one controller/ATAPI-drive combination doesn't respond
	 * to the Edd (Micronics M54Li + Sanyo CDR-XXX) so let's check for the
	 * ATAPI signature straight off. If we find it there will be no probe
	 * done for a slave. Tough.
	 */
	atapi = 0;
	mask = 0;
	status = inb(port+Pstatus);
	DPRINT("ata%d: ATAPI %uX %uX %uX\n", ctlrno, status,
		inb(port+Pcylmsb), inb(port+Pcyllsb));
	if(status == 0 && inb(port+Pcylmsb) == 0xEB && inb(port+Pcyllsb) == 0x14){
		DPRINT("ata%d: ATAPI ok\n", ctlrno);
		setvec(irq, ataintr, ctlr);
		atapi |= 0x01;
		mask |= 0x01;
		goto skipedd;
	}
	if(atactlrwait(ctlr, DHmagic, 0, MS2TK(1)) || waserror()){
		xfree(ctlr);
		return -1;
	}
	setvec(irq, ataintr, ctlr);
	ctlr->cmd = Cedd;
	outb(port+Pcmd, Cedd);
	atasleep(ctlr);
	poperror();

	/*
	 * The diagnostic returns a code in the error register, good
	 * status is bits 6-0 == 0x01.
	 * The existence of the slave is more difficult to determine,
	 * different generations of controllers may respond in different
	 * ways. The standards here offer little light but only more and
	 * more heat:
	 *   1) the slave must be done and have dropped Sbusy by now (six
	 *	seconds for the master, 5 seconds for the slave). If it
	 *	hasn't, then it has either failed or the controller is
	 *	broken in some way (e.g. Vibra16 returns status of 0xFF);
	 *   2) theory says the status of a non-existent slave should be 0.
	 *	Of course, it's valid for all the bits to be 0 for a slave
	 *	that exists too...
	 *   3) a valid ATAPI drive can have status 0 and the ATAPI signature
	 *	in the cylinder registers after reset. Of course, if the drive
	 *	has been messed about by the BIOS or some other O/S then the
	 *	signature may be gone.
	 */
	error = inb(port+Perror);
	DPRINT("ata%d: master diag error %ux\n", ctlr->ctlrno, error);
	if((error & ~0x80) == 0x01)
		mask |= 0x01;

	outb(port+Pdh, DHmagic|DHslave);
	microdelay(1);
	status = inb(port+Pstatus);
	error = inb(port+Perror);
	DPRINT("ata%d: slave diag status %ux, error %ux\n", ctlr->ctlrno, status, error);
	if(status && (status & (Sbusy|Serr)) == 0 && (error & ~0x80) == 0x01)
		mask |= 0x02;
	else if(status == 0 && inb(port+Pcylmsb) == 0xEB && inb(port+Pcyllsb) == 0x14){
		atapi |= 0x02;
		mask |= 0x02;
	}
	conf.nhard = dp - ata;

skipedd:
	if(mask == 0){
		xfree(ctlr);
		return -1;
	}
	atactlr[ctlrno] = ctlr;

	if(have640b >= 0 && (ctlrno & 0x01))
		ctlr->ctlrlock = &atactlrlock[ctlrno-1];
	else
		ctlr->ctlrlock = &atactlrlock[ctlrno];

	if(mask & 0x01)
		atadrivealloc(ctlr, ctlrno*2, atapi & 0x01);
	if(mask & 0x02)
		atadrivealloc(ctlr, ctlrno*2+1, atapi & 0x02);

	return 0;
}

void
atactlrreset(void)
{
	int ctlrno, driveno, i, slave, spindown;
	ISAConf isa;

	cmd640b();
	rz1000();

	for(ctlrno = 0; ctlrno < NCtlr; ctlrno++){
		memset(&isa, 0, sizeof(ISAConf));
		if(isaconfig("ata", ctlrno, &isa) == 0 && ctlrno)
			continue;
		if(isa.irq == 0 && (isa.irq = defirq[ctlrno]) == 0)
			continue;

		if(atactlrprobe(ctlrno, Int0vec+isa.irq))
			continue;
	
	if(conf.nhard && (p = getconf("spindowntime")))
		spindowntime = atoi(p);
		for(i = 0; i < isa.nopt; i++){
			DPRINT("ata%d: opt %s\n", ctlrno, isa.opt[i]);
			if(strncmp(isa.opt[i], "spindown", 8) == 0){
				if(isa.opt[i][9] != '=')
					continue;
				if(isa.opt[i][8] == '0')
					slave = 0;
				else if(isa.opt[i][8] == '1')
					slave = 1;
				else
					continue;
				driveno = ctlrno*2+slave;
				if(atadrive[driveno] == 0)
					continue;
				if((spindown = strtol(&isa.opt[i][10], 0, 0)) == 0)
					continue;
				if(spindown < (Hardtimeout+2000)/1000)
					spindown = (Hardtimeout+2000)/1000;
				atadrive[driveno]->spindown = spindown;
				spindownmask |= (1<<driveno);
				DPRINT("ata%d: opt spindownmask %ux\n",
					ctlrno, spindownmask);
			}
			else if(strcmp(isa.opt[i], "noatapi") == 0)
				atactlr[ctlrno]->noatapi = 1;
		}
	}
}

void
atareset(void)
{
}

void


@@ 238,9 531,21 @@ atainit(void)
Chan*
ataattach(char *spec)
{
	int driveno;
	Drive *dp;

	for(dp = ata; dp < &ata[conf.nhard]; dp++){
	DPRINT("ataattach\n");

	qlock(&ataprobelock);
	if(ataprobedone == 0){
		atactlrreset();
		ataprobedone = 1;
	}
	qunlock(&ataprobelock);

	for(driveno = 0; driveno < NDrive; driveno++){
		if((dp = atadrive[driveno]) == 0)
			continue;
		if(waserror()){
			dp->online = 0;
			qunlock(dp);


@@ 248,20 553,20 @@ ataattach(char *spec)
		}
		qlock(dp);
		if(!dp->online){
			/*
			 * Make sure ataclock() doesn't
			 * interfere.
			 */
			dp->usetime = m->ticks;
			ataparams(dp);
			dp->online = 1;
			atasetbuf(dp, 1);
			atafeature(dp, 0xAA);	/* read look ahead */
		}

		/*
		 *  read Plan 9 partition table
		 */
		atapart(dp);
		if(dp->partok == 0){
			if(dp->atapi)
				atapipart(dp);
			else
				atapart(dp);
		}
		qunlock(dp);
		poperror();
	}


@@ 293,47 598,45 @@ ataopen(Chan *c, int omode)
}

void
atacreate(Chan *c, char *name, int omode, ulong perm)
atacreate(Chan*, char*, int, ulong)
{
	USED(c, name, omode, perm);
	error(Eperm);
}

void
ataclose(Chan *c)
{
	Drive *d;
	Drive *dp;
	Partition *p;

	if(c->mode != OWRITE && c->mode != ORDWR)
		return;

	d = &ata[DRIVE(c->qid.path)];
	p = &d->p[PART(c->qid.path)];
	dp = atadrive[DRIVE(c->qid.path)];
	p = &dp->p[PART(c->qid.path)];
	if(strcmp(p->name, "partition") != 0)
		return;

	if(waserror()){
		qunlock(d);
		qunlock(dp);
		nexterror();
	}
	qlock(d);
	atapart(d);
	qunlock(d);
	qlock(dp);
	dp->partok = 0;
	atapart(dp);
	qunlock(dp);
	poperror();
}

void
ataremove(Chan *c)
ataremove(Chan*)
{
	USED(c);
	error(Eperm);
}

void
atawstat(Chan *c, char *dp)
atawstat(Chan*, char*)
{
	USED(c, dp);
	error(Eperm);
}



@@ 345,20 648,34 @@ ataread(Chan *c, void *a, long n, ulong offset)
	int skip;
	uchar *aa = a;
	Partition *pp;
	char *buf;
	uchar *buf;

	if(c->qid.path == CHDIR)
		return devdirread(c, a, n, 0, 0, atagen);

	dp = atadrive[DRIVE(c->qid.path)];
	if(dp->atapi){
		if(dp->online == 0)
			error(Eio);
		if(waserror()){
			qunlock(dp);
			nexterror();
		}
		qlock(dp);
		if(dp->partok == 0)
			atapipart(dp);
		qunlock(dp);
		poperror();
		return atapirwio(c, a, n, offset);
	}
	pp = &dp->p[PART(c->qid.path)];

	buf = smalloc(Maxxfer);
	if(waserror()){
		free(buf);
		nexterror();
	}

	dp = &ata[DRIVE(c->qid.path)];
	pp = &dp->p[PART(c->qid.path)];

	skip = offset % dp->bytes;
	for(rv = 0; rv < n; rv += i){
		i = ataxfer(dp, pp, Cread, offset+rv-skip, n-rv+skip, buf);


@@ 390,13 707,16 @@ atawrite(Chan *c, void *a, long n, ulong offset)
	long rv, i, partial;
	uchar *aa = a;
	Partition *pp;
	char *buf;
	uchar *buf;

	if(c->qid.path == CHDIR)
		error(Eisdir);

	dp = &ata[DRIVE(c->qid.path)];
	dp = atadrive[DRIVE(c->qid.path)];
	if(dp->atapi)
		error(Eperm);
	pp = &dp->p[PART(c->qid.path)];

	buf = smalloc(Maxxfer);
	if(waserror()){
		free(buf);


@@ 472,28 792,26 @@ cmddone(void *a)

/*
 * Wait for the controller to be ready to accept a command.
 * This is protected from intereference by ataclock() by
 * setting dp->usetime before it is called.
 */
static void
static int
cmdreadywait(Drive *dp)
{
	long start;
	int period;
	Controller *cp = dp->cp;
	ulong ticks;
	uchar ready;

	/* give it 2 seconds to spin down and up */
	dp->usetime = m->ticks;
	if(dp->state == Sspinning)
		period = 10;
		ticks = MS2TK(10);
	else
		period = 2000;
		ticks = MS2TK(2000);

	start = m->ticks;
	while((inb(cp->pbase+Pstatus) & (Sready|Sbusy)) != Sready)
		if(TK2MS(m->ticks - start) > period){
			DPRINT("cmdreadywait failed\n");
			error(Eio);
		}
	if(dp->atapi)
		ready = 0;
	else
		ready = Sready;

	return atactlrwait(dp->cp, dp->dh, ready, ticks);
}

static void


@@ 505,7 823,7 @@ atarepl(Drive *dp, long bblk)
		return;
	for(i = 0; i < dp->repl.nrepl; i++){
		if(dp->repl.blk[i] == bblk)
			DPRINT("found bblk %ld at offset %ld\n", bblk, i);
			DPRINT("%s: found bblk %ld at offset %ld\n", dp->vol, bblk, i);
	}
}



@@ 514,7 832,7 @@ atasleep(Controller *cp)
{
	tsleep(&cp->r, cmddone, cp, Hardtimeout);
	if(cp->cmd && cp->cmd != Cident2){
		DPRINT("hard drive timeout\n");
		DPRINT("ata%d: cmd 0x%uX timeout\n", cp->ctlrno, cp->cmd);
		error("ata drive timeout");
	}
}


@@ 525,7 843,7 @@ atasleep(Controller *cp)
 *  parts.
 */
static long
ataxfer(Drive *dp, Partition *pp, int cmd, long start, long len, char *buf)
ataxfer(Drive *dp, Partition *pp, int cmd, long start, long len, uchar *buf)
{
	Controller *cp;
	long lblk;


@@ 563,24 881,22 @@ ataxfer(Drive *dp, Partition *pp, int cmd, long start, long len, char *buf)
		head = ((lblk/dp->sectors) % dp->heads);
	}

/*print("ataxfer cyl %d sec %d head %d\n", cyl, sec, head);/**/
	XPRINT("%s: ataxfer cyl %d sec %d head %d len %d\n", dp->vol, cyl, sec, head, len);

	cp = dp->cp;
	qlock(cp);
	qlock(cp->ctlrlock);
	if(waserror()){
		cp->dp = 0;
		cp->buf = 0;
		qunlock(cp);
		qunlock(cp->ctlrlock);
		nexterror();
	}

	/*
	 * Make sure hardclock() doesn't
	 * interfere.
	 */
	dp->usetime = m->ticks;
	cmdreadywait(dp);
	if(cmdreadywait(dp)){
		error(Eio);
	}

	ilock(&cp->reglock);
	ILOCK(&cp->reglock);
	cp->sofar = 0;
	cp->buf = buf;
	cp->nsecs = len;


@@ 590,20 906,21 @@ ataxfer(Drive *dp, Partition *pp, int cmd, long start, long len, char *buf)

	outb(cp->pbase+Pcount, cp->nsecs);
	outb(cp->pbase+Psector, sec);
	outb(cp->pbase+Pdh, DHmagic | (dp->drive<<4) | (dp->lba<<6) | head);
	outb(cp->pbase+Pdh, dp->dh | (dp->lba<<6) | head);
	outb(cp->pbase+Pcyllsb, cyl);
	outb(cp->pbase+Pcylmsb, cyl>>8);
	outb(cp->pbase+Pcmd, cmd);

	if(cmd == Cwrite){
		loop = 0;
		microdelay(1);
		while((stat = inb(cp->pbase+Pstatus) & (Serr|Sdrq)) == 0)
			if(++loop > 10000)
				panic("ataxfer");
				panic("%s: ataxfer", dp->vol);
		outss(cp->pbase+Pdata, cp->buf, dp->bytes/2);
	} else
		stat = 0;
	iunlock(&cp->reglock);
	IUNLOCK(&cp->reglock);

	if(stat & Serr)
		error(Eio);


@@ 615,30 932,31 @@ ataxfer(Drive *dp, Partition *pp, int cmd, long start, long len, char *buf)
	 */
	loop = 0;
	while(waserror()){
		DPRINT("interrupted ataxfer\n");
		DPRINT("%s: interrupted ataxfer\n", dp->vol);
		if(loop++ > 10){
			print("ata disk error\n");
			print("%s: disk error\n", dp->vol);
			nexterror();
		}
	}
	atasleep(cp);
	dp->state = Sspinning;
	dp->usetime = m->ticks;
	dp->state = Sspinning;
	poperror();
	if(loop)
		nexterror();

	if(cp->status & Serr){
		DPRINT("hd%d err: lblk %ld status %lux, err %lux\n",
			dp-ata, lblk, cp->status, cp->error);
		DPRINT("%s err: lblk %ld status %lux, err %lux\n",
			dp->vol, lblk, cp->status, cp->error);
		DPRINT("\tcyl %d, sec %d, head %d\n", cyl, sec, head);
		DPRINT("\tnsecs %d, sofar %d\n", cp->nsecs, cp->sofar);
		atarepl(dp, lblk+cp->sofar);
		error(Eio);
	}
	cp->dp = 0;
	cp->buf = 0;
	len = cp->sofar*dp->bytes;
	qunlock(cp);
	qunlock(cp->ctlrlock);
	poperror();

	return len;


@@ 648,33 966,41 @@ ataxfer(Drive *dp, Partition *pp, int cmd, long start, long len, char *buf)
 *  set read ahead mode
 */
static void
atasetbuf(Drive *dp, int on)
atafeature(Drive *dp, uchar arg)
{
	Controller *cp = dp->cp;

	qlock(cp);
	if(dp->atapi)
		return;

	qlock(cp->ctlrlock);
	if(waserror()){
		qunlock(cp);
		cp->dp = 0;
		qunlock(cp->ctlrlock);
		nexterror();
	}

	cmdreadywait(dp);
	if(cmdreadywait(dp)){
		error(Eio);
	}

	ilock(&cp->reglock);
	cp->cmd = Csetbuf;
	outb(cp->pbase+Pprecomp, on ? 0xAA : 0x55);	/* read look ahead */
	outb(cp->pbase+Pdh, DHmagic | (dp->drive<<4));
	outb(cp->pbase+Pcmd, Csetbuf);
	iunlock(&cp->reglock);
	ILOCK(&cp->reglock);
	cp->cmd = Cfeature;
	cp->dp = dp;
	outb(cp->pbase+Pfeature, arg);
	outb(cp->pbase+Pdh, dp->dh);
	outb(cp->pbase+Pcmd, Cfeature);
	IUNLOCK(&cp->reglock);

	atasleep(cp);

/*	if(cp->status & Serr)
		DPRINT("hd%d setbuf err: status %lux, err %lux\n",
			dp-ata, cp->status, cp->error);/**/
	if(cp->status & Serr)
		DPRINT("%s: setbuf err: status %lux, err %lux\n",
			dp->vol, cp->status, cp->error);

	cp->dp = 0;
	poperror();
	qunlock(cp);
	qunlock(cp->ctlrlock);
}

/*


@@ 727,35 1053,56 @@ static void
ataident(Drive *dp)
{
	Controller *cp;
	char *buf;
	uchar *buf;
	Ident *ip;
	char id[21];
	ulong lbasecs;
	uchar cmd;

	cp = dp->cp;
	buf = smalloc(Maxxfer);
	qlock(cp);
	qlock(cp->ctlrlock);
	if(waserror()){
		qunlock(cp);
		cp->dp = 0;
		free(buf);
		qunlock(cp->ctlrlock);
		nexterror();
	}

	cmdreadywait(dp);
	if(dp->atapi || isatapi(dp))
		cmd = Cidentd;
	else{
		cmd = Cident;
		if(cmdreadywait(dp)){
			dp->atapi = 1;
			if(isatapi(dp) == 0)
				error(Eio);
			cmd = Cidentd;
		}
	}

	ilock(&cp->reglock);
retryatapi:
	ILOCK(&cp->reglock);
	cp->nsecs = 1;
	cp->sofar = 0;
	cp->cmd = Cident;
	cp->cmd = cmd;
	cp->dp = dp;
	cp->buf = buf;
	outb(cp->pbase+Pdh, DHmagic | (dp->drive<<4));
	outb(cp->pbase+Pcmd, Cident);
	iunlock(&cp->reglock);
	outb(cp->pbase+Pdh, dp->dh);
	outb(cp->pbase+Pcmd, cmd);
	IUNLOCK(&cp->reglock);

	atasleep(cp);

	if(cp->status & Serr){
		DPRINT("bad disk ident status\n");
		DPRINT("%s: bad disk ident status\n", dp->vol);
		if(dp->atapi == 0 && (cp->error & Eabort)){
			dp->atapi = 1;
			if(isatapi(dp)){
				cmd = Cidentd;
				goto retryatapi;
			}
		}
		error(Eio);
	}
	ip = (Ident*)buf;


@@ 773,37 1120,54 @@ ataident(Drive *dp)
	memmove(id, ip->model, sizeof(id)-1);
	id[sizeof(id)-1] = 0;

	DPRINT("%s: config 0x%uX capabilities 0x%uX\n",
		dp->vol, ip->config, ip->capabilities);
	if(dp->atapi){
		dp->bytes = 2048;
		if((ip->config & 0x0060) == 0x0020)
			dp->drqintr = 1;
	}
	if(dp->spindown && (ip->capabilities & (1<<13)))
		dp->spindown /= 5;

	/* use default (unformatted) settings */
	dp->cyl = ip->cyls;
	dp->heads = ip->heads;
	dp->sectors = ip->s2t;
	DPRINT("%s: %s %d/%d/%d CHS %d bytes\n",
		dp->vol, id, dp->cyl, dp->heads, dp->sectors, dp->cap);

	if(ip->cvalid&(1<<0)){
		/* use current settings */
		dp->cyl = ip->ccyls;
		dp->heads = ip->cheads;
		dp->sectors = ip->cs2t;
		DPRINT("%s: changed to %d cyl %d head %d sec\n",
			dp->vol, dp->cyl, dp->heads, dp->sectors);
	}

	lbasecs = (ip->lbasecs[0]) | (ip->lbasecs[1]<<16);
	if((ip->capabilities & (1<<9)) && (lbasecs & 0xf0000000) == 0){
		dp->lba = 1;
		dp->sectors = lbasecs;
		dp->cap = dp->bytes * dp->sectors;
/*print("\nata%d model %s with %d lba sectors\n", dp->drive, id, dp->sectors);/**/
		dp->lbasecs = lbasecs;
		dp->cap = dp->bytes * dp->lbasecs;
		DPRINT("%s: LBA: %s %d sectors %d bytes\n",
			dp->vol, id, dp->lbasecs, dp->cap);
	} else {
		dp->lba = 0;

		/* use default (unformatted) settings */
		dp->cyl = ip->cyls;
		dp->heads = ip->heads;
		dp->sectors = ip->s2t;
/*print("\nata%d model %s with default %d cyl %d head %d sec\n", dp->drive,
			id, dp->cyl, dp->heads, dp->sectors);/**/

		if(ip->cvalid&(1<<0)){
			/* use current settings */
			dp->cyl = ip->ccyls;
			dp->heads = ip->cheads;
			dp->sectors = ip->cs2t;
/*print("\tchanged to %d cyl %d head %d sec\n", dp->cyl, dp->heads, dp->sectors);/**/
		}
		dp->lbasecs = 0;
		dp->cap = dp->bytes * dp->cyl * dp->heads * dp->sectors;
	}
	cp->lastcmd = cp->cmd;
	cp->cmd = 0;

	if(cp->cmd){
		cp->lastcmd = cp->cmd;
		cp->cmd = 0;
	}
	cp->dp = 0;
	cp->buf = 0;
	free(buf);
	poperror();
	qunlock(cp);
	qunlock(cp->ctlrlock);
}

/*


@@ 813,21 1177,24 @@ static int
ataprobe(Drive *dp, int cyl, int sec, int head)
{
	Controller *cp;
	char *buf;
	uchar *buf;
	int rv;

	cp = dp->cp;
	buf = smalloc(Maxxfer);
	qlock(cp);
	qlock(cp->ctlrlock);
	if(waserror()){
		cp->dp = 0;
		free(buf);
		qunlock(cp);
		qunlock(cp->ctlrlock);
		nexterror();
	}

	cmdreadywait(dp);
	if(cmdreadywait(dp)){
		error(Eio);
	}

	ilock(&cp->reglock);
	ILOCK(&cp->reglock);
	cp->cmd = Cread;
	cp->dp = dp;
	cp->status = 0;


@@ 837,23 1204,27 @@ ataprobe(Drive *dp, int cyl, int sec, int head)

	outb(cp->pbase+Pcount, 1);
	outb(cp->pbase+Psector, sec+1);
	outb(cp->pbase+Pdh, DHmagic | head | (dp->lba<<6) | (dp->drive<<4));
	outb(cp->pbase+Pdh, dp->dh | (dp->lba<<6) | head);
	outb(cp->pbase+Pcyllsb, cyl);
	outb(cp->pbase+Pcylmsb, cyl>>8);
	outb(cp->pbase+Pcmd, Cread);
	iunlock(&cp->reglock);
	IUNLOCK(&cp->reglock);

	atasleep(cp);

	if(cp->status & Serr)
	if(cp->status & Serr){
		DPRINT("%s: probe err: status %lux, err %lux\n",
			dp->vol, cp->status, cp->error);
		rv = -1;
	}
	else
		rv = 0;

	cp->dp = 0;
	cp->buf = 0;
	free(buf);
	poperror();
	qunlock(cp);
	qunlock(cp->ctlrlock);
	return rv;
}



@@ 871,8 1242,10 @@ ataparams(Drive *dp)
	 */
	dp->bytes = 512;
	ataident(dp);
	if(dp->atapi)
		return;
	if(dp->lba){
		i = dp->sectors - 1;
		i = dp->lbasecs - 1;
		if(ataprobe(dp, (i>>8)&0xffff, (i&0xff)-1, (i>>24)&0xf) == 0)
			return;
	} else {


@@ 907,6 1280,10 @@ ataparams(Drive *dp)
	}
	dp->cyl = lo + 1;
	dp->cap = dp->bytes * dp->cyl * dp->heads * dp->sectors;
	DPRINT("%s: probed: %d/%d/%d CHS %d bytes\n",
		dp->vol, dp->cyl, dp->heads, dp->sectors, dp->cap);
	if(dp->cyl == 0 || dp->heads == 0 || dp->sectors == 0)
		error(Eio);
}

/*


@@ 920,7 1297,7 @@ atareplinit(Drive *dp)
	char *field[1];
	ulong n;
	int i;
	char *buf;
	uchar *buf;

	/*
	 *  check the partition is big enough


@@ 945,7 1322,7 @@ atareplinit(Drive *dp)
	/*
	 *  parse replacement table.
	 */
	n = getfields(buf, line, Nrepl+1, "\n");
	n = getfields((char*)buf, line, Nrepl+1, "\n");
	if(strncmp(line[0], REPLMAGIC, sizeof(REPLMAGIC)-1)){
		dp->repl.p = 0;
	} else {


@@ 969,12 1346,15 @@ atapart(Drive *dp)
{
	Partition *pp;
	char *line[Npart+1];
	char *field[3];
	char *field[3], namebuf[NAMELEN], *p;
	ulong n;
	int i;
	char *buf;
	uchar *buf;

	sprint(dp->vol, "hd%d", dp - ata);
	DPRINT("%s: partok %d\n", dp->vol, dp->partok);

	if(dp->partok)
		return;

	/*
	 *  we always have a partition for the whole disk


@@ 988,6 1368,7 @@ atapart(Drive *dp)
	strcpy(pp->name, "partition");
	pp->start = dp->p[0].end - 1;
	pp->end = dp->p[0].end;
	pp++;
	dp->npart = 2;

	/*


@@ 997,58 1378,70 @@ atapart(Drive *dp)

	buf = smalloc(Maxxfer);
	if(waserror()){
		DPRINT("%s: atapart error\n", dp->vol);
		free(buf);
		nexterror();
	}


	/*
	 *  read last sector from disk, null terminate.  This used
	 *  to be the sector we used for the partition tables.
	 *  However, this sector is special on some PC's so we've
	 *  started to use the second last sector as the partition
	 *  table instead.  To avoid reconfiguring all our old systems
	 *  we first look to see if there is a valid partition
	 *  table in the last sector.  If so, we use it.  Otherwise
	 *  we switch to the second last.
	 * Check if the partitions are described in plan9.ini.
	 * If not, read the disc.
	 */
	ataxfer(dp, pp, Cread, 0, dp->bytes, buf);
	buf[dp->bytes-1] = 0;
	n = getfields(buf, line, Npart+1, "\n");
	if(n == 0 || strncmp(line[0], PARTMAGIC, sizeof(PARTMAGIC)-1)){
		dp->p[0].end--;
		dp->p[1].start--;
		dp->p[1].end--;
		ataxfer(dp, pp, Cread, 0, dp->bytes, buf);
	sprint(namebuf, "%spartition", dp->vol);
	if((p = getconf(namebuf)) == 0){	
		/*
		 *  read last sector from disk, null terminate.  This used
		 *  to be the sector we used for the partition tables.
		 *  However, this sector is special on some PC's so we've
		 *  started to use the second last sector as the partition
		 *  table instead.  To avoid reconfiguring all our old systems
		 *  we first look to see if there is a valid partition
		 *  table in the last sector.  If so, we use it.  Otherwise
		 *  we switch to the second last.
		 */
		ataxfer(dp, &dp->p[1], Cread, 0, dp->bytes, buf);
		buf[dp->bytes-1] = 0;
		n = getfields(buf, line, Npart+1, "\n");
		n = getfields((char*)buf, line, Npart+1, "\n");
		if(n == 0 || strncmp(line[0], PARTMAGIC, sizeof(PARTMAGIC)-1)){
			dp->p[0].end--;
			dp->p[1].start--;
			dp->p[1].end--;
			ataxfer(dp, &dp->p[1], Cread, 0, dp->bytes, buf);
			buf[dp->bytes-1] = 0;
			n = getfields((char*)buf, line, Npart+1, "\n");
		}
	}
	else{
		strcpy((char*)buf, p);
		n = getfields((char*)buf, line, Npart+1, "\n");
	}

	/*
	 *  parse partition table.
	 */
	if(n && strncmp(line[0], PARTMAGIC, sizeof(PARTMAGIC)-1) == 0){
	if(n > 0 && strncmp(line[0], PARTMAGIC, sizeof(PARTMAGIC)-1) == 0){
		for(i = 1; i < n; i++){
			if(getfields(line[i], field, 3, " ") != 3)
				break;
			if(pp >= &dp->p[Npart])
				break;
			strncpy(pp->name, field[0], NAMELEN);
			if(strncmp(pp->name, "repl", NAMELEN) == 0)
				dp->repl.p = pp;
			pp->start = strtoul(field[1], 0, 0);
			pp->end = strtoul(field[2], 0, 0);
			if(pp->start > pp->end || pp->start >= dp->p[0].end)
				break;
			pp++;
			switch(getfields(line[i], field, 3, " ")) {
			case 2:
				if(strcmp(field[0], "unit") == 0)
					strncpy(dp->vol, field[1], NAMELEN);
				break;	
			case 3:
				strncpy(pp->name, field[0], NAMELEN);
				if(strncmp(pp->name, "repl", NAMELEN) == 0)
					dp->repl.p = pp;
				pp->start = strtoul(field[1], 0, 0);
				pp->end = strtoul(field[2], 0, 0);
				if(pp->start > pp->end || pp->start >= dp->p[0].end)
					break;
				dp->npart++;
			}
		}
	}
	dp->npart = pp - dp->p;
	free(buf);
	poperror();

	dp->partok = 1;

	if(dp->repl.p)
		atareplinit(dp);
}


@@ 1062,29 1455,26 @@ enum
 *  we get an interrupt for every sector transferred
 */
static void
ataintr(Ureg *ur, void *arg)
ataintr(Ureg*, void* arg)
{
	Controller *cp;
	Drive *dp;
	long loop;
	char *addr;
	int loop;
	uchar *addr;

	USED(ur, arg);

	/*
 	 *  BUG!! if there is ever more than one controller, we need a way to
	 *	  distinguish which interrupted (use arg).
	 */
	cp = &atac[0];
	dp = cp->dp;
	cp = arg;
	if((dp = cp->dp) == 0 && cp->cmd != Cedd)
		return;

	ilock(&cp->reglock);
	ILOCK(&cp->reglock);

	loop = 0;
	while((cp->status = inb(cp->pbase+Pstatus)) & Sbusy){
		if(++loop > Maxloop)
			panic("ataintr: wait busy cmd=%lux status=%lux",
				cp->cmd, inb(cp->pbase+Pstatus));
		if(++loop > Maxloop){
			print("ata%d: cmd=%lux, lastcmd=%lux status=%lux\n",
				cp->ctlrno, cp->cmd, cp->lastcmd, inb(cp->pbase+Pstatus));
			panic("%s: wait busy\n", dp->vol);
		}
	}

	switch(cp->cmd){


@@ 1101,8 1491,8 @@ ataintr(Ureg *ur, void *arg)
			loop = 0;
			while(((cp->status = inb(cp->pbase+Pstatus)) & Sdrq) == 0)
				if(++loop > Maxloop)
					panic("ataintr: write cmd=%lux status=%lux\n",
						cp->cmd, inb(cp->pbase+Pstatus));
					panic("%s: write cmd=%lux status=%lux\n",
						dp->vol, cp->cmd, inb(cp->pbase+Pstatus));
			addr = cp->buf;
			if(addr){
				addr += cp->sofar*dp->bytes;


@@ 1116,11 1506,12 @@ ataintr(Ureg *ur, void *arg)
		break;
	case Cread:
	case Cident:
	case Cidentd:
		loop = 0;
		while((cp->status & (Serr|Sdrq)) == 0){
			if(++loop > Maxloop){
				print("ataintr: read/ident cmd=%lux status=%lux\n",
					cp->cmd, inb(cp->pbase+Pstatus));
				print("%s: read/ident cmd=%lux status=%lux\n",
					dp->vol, cp->cmd, inb(cp->pbase+Pstatus));
				cp->status |= Serr;
				break;
			}


@@ 1140,21 1531,24 @@ ataintr(Ureg *ur, void *arg)
		}
		cp->sofar++;
		if(cp->sofar > cp->nsecs)
			print("ataintr %d %d\n", cp->sofar, cp->nsecs);
			print("%s: intr %d %d\n", dp->vol, cp->sofar, cp->nsecs);
		if(cp->sofar >= cp->nsecs){
			cp->lastcmd = cp->cmd;
			if (cp->cmd == Cread)
				cp->cmd = 0;
			else
			if(cp->cmd == Cidentd)
				cp->cmd = Cident2;
			else if(cp->cmd == Cident && (cp->status & Sready) == 0)
				cp->cmd = Cident2;
			else
				cp->cmd = 0;
			inb(cp->pbase+Pstatus);
			DPRINT("status %uX, alt %uX\n",
				inb(cp->pbase+Pstatus), inb(cp->pbase+0x206));
			wakeup(&cp->r);
		}
		break;
	case Cinitparam:
	case Csetbuf:
	case Cidle:
	case Cfeature:
	case Cstandby:
	case Cpowerdown:
	case Cedd:
		cp->lastcmd = cp->cmd;
		cp->cmd = 0;
		wakeup(&cp->r);


@@ 1163,39 1557,367 @@ ataintr(Ureg *ur, void *arg)
		cp->lastcmd = cp->cmd;
		cp->cmd = 0;
		break;
	case Cpktcmd:
		atapiintr(cp);
		break;
	default:
		print("weird disk interrupt, cmd=%.2ux, lastcmd= %.2ux status=%.2ux\n",
			cp->cmd, cp->lastcmd, cp->status);
		if(cp->cmd == 0 && cp->lastcmd == Cpktcmd && cp->cmdblk[0] == Ccapacity)
			break;
		if(cp->status & Serr)
			cp->error = inb(cp->pbase+Perror);
		print("%s: weird interrupt, cmd=%.2ux, lastcmd=%.2ux, ",
			dp->vol, cp->cmd, cp->lastcmd);
		print("status=%.2ux, error=%.2ux, count=%.2ux\n",
			cp->ctlrno, cp->error, inb(cp->pbase+Pcount));
		break;
	}

	iunlock(&cp->reglock);
	IUNLOCK(&cp->reglock);
}

void
hardclock(void)
{
	int drive;
	int driveno, mask;
	Drive *dp;
	Controller *cp;
	int diff;

	if(spindowntime <= 0)
	if((mask = spindownmask) == 0)
		return;

	for(drive = 0; drive < conf.nhard; drive++){
		dp = &ata[drive];
	for(driveno = 0; driveno < NDrive && mask; driveno++){
		mask &= ~(1<<driveno);
		if((dp = atadrive[driveno]) == 0)
			continue;
		cp = dp->cp;

		diff = TK2SEC(m->ticks - dp->usetime);
		if((dp->state == Sspinning) && (diff >= spindowntime)){
			ilock(&cp->reglock);
		if((dp->state == Sspinning) && (diff >= dp->spindown)){
			DPRINT("%s: spindown\n", dp->vol);
			ILOCK(&cp->reglock);
			cp->cmd = Cstandby;
			outb(cp->pbase+Pcount, 0);
			outb(cp->pbase+Pdh, DHmagic | (dp->drive<<4) | 0);
			outb(cp->pbase+Pdh, dp->dh);
			outb(cp->pbase+Pcmd, cp->cmd);
			iunlock(&cp->reglock);
			IUNLOCK(&cp->reglock);
			dp->state = Sstandby;
		}
	}
}

static int
isatapi(Drive *dp)
{
	Controller *cp;

	cp = dp->cp;
	outb(cp->pbase+Pdh, dp->dh);
	DPRINT("%s: isatapi %d\n", dp->vol, dp->atapi);
	if(dp->atapi){
		outb(cp->pbase+Pcmd, 0x08);
		delay(20);
	}
	dp->atapi = 0;
	dp->bytes = 512;
	microdelay(1);
	if(inb(cp->pbase+Pstatus)){
		DPRINT("%s: isatapi status %ux\n", dp->vol, inb(cp->pbase+Pstatus));
		return 0;
	}
	if(inb(cp->pbase+Pcylmsb) != 0xEB || inb(cp->pbase+Pcyllsb) != 0x14){
		DPRINT("%s: isatapi cyl %ux %ux\n",
			dp->vol, inb(cp->pbase+Pcylmsb), inb(cp->pbase+Pcyllsb));
		return 0;
	}
	dp->atapi = 1;
	sprint(dp->vol, "atapi%d", dp->driveno);
	dp->spindown = 0;
	spindownmask &= ~(1<<dp->driveno);
	return 1;
}

static void
atapiexec(Drive *dp)
{
	Controller *cp;
	int loop;

	cp = dp->cp;

	if(cmdreadywait(dp)){
		error(Eio);
	}
	
	ILOCK(&cp->reglock);
	cp->nsecs = 1;
	cp->sofar = 0;
	cp->error = 0;
	cp->cmd = Cpktcmd;
	outb(cp->pbase+Pcount, 0);
	outb(cp->pbase+Psector, 0);
	outb(cp->pbase+Pfeature, 0);
	outb(cp->pbase+Pcyllsb, cp->len);
	outb(cp->pbase+Pcylmsb, cp->len>>8);
	outb(cp->pbase+Pdh, dp->dh);
	outb(cp->pbase+Pcmd, cp->cmd);

	if(dp->drqintr == 0){
		microdelay(1);
		for(loop = 0; (inb(cp->pbase+Pstatus) & (Serr|Sdrq)) == 0; loop++){
			if(loop < 10000)
				continue;
			panic("%s: cmddrqwait: cmd=%lux status=%lux\n",
				dp->vol, cp->cmd, inb(cp->pbase+Pstatus));
		}
		outss(cp->pbase+Pdata, cp->cmdblk, sizeof(cp->cmdblk)/2);
	}
	IUNLOCK(&cp->reglock);

	loop = 0;
	while(waserror()){
		DPRINT("%s: interrupted atapiexec\n", dp->vol);
		if(loop++ > 10){
			print("%s: disk error\n", dp->vol);
			nexterror();
		}
	}
	atasleep(cp);
	poperror();
	if(loop)
		nexterror();

	if(cp->status & Serr){
		DPRINT("%s: Bad packet command %ux\n", dp->vol, cp->error);
		error(Eio);
	}
}

static long
atapiio(Drive *dp, char *a, ulong len, ulong offset)
{
	ulong bn, n, o, m;
	Controller *cp;
	uchar *buf;
	int retrycount;

	cp = dp->cp;

	buf = smalloc(Maxxfer);
	qlock(cp->ctlrlock);
	retrycount = 1;
retry:
	if(waserror()){
		dp->partok = 0;
		if((cp->status & Serr) && (cp->error & 0xF0) == 0x60){
			dp->vers++;
			if(retrycount){
				retrycount--;
				goto retry;
			}
		}
		cp->dp = 0;
		free(buf);
		qunlock(cp->ctlrlock);
		nexterror();
	}

	cp->buf = buf;
	cp->dp = dp;
	cp->len = dp->bytes;

	n = len;
	while(n > 0){
		bn = offset / dp->bytes;
		if(offset > dp->cap-dp->bytes)
			break;
		o = offset % dp->bytes;
		m = dp->bytes - o;
		if(m > n)
			m = n;
		memset(cp->cmdblk, 0, 12);
		cp->cmdblk[0] = Cread2;
		cp->cmdblk[2] = bn >> 24;
		cp->cmdblk[3] = bn >> 16;
		cp->cmdblk[4] = bn >> 8;
		cp->cmdblk[5] = bn;
		cp->cmdblk[7] = 0;
		cp->cmdblk[8] = 1;
		atapiexec(dp);
		if(cp->count != dp->bytes){
			print("short read\n");
			break;
		}
		memmove(a, cp->buf + o, m);
		n -= m;
		offset += m;
		a += m;
	}
	poperror();
	free(buf);
	cp->dp = 0;
	qunlock(cp->ctlrlock);
	return len-n;
}

static long
atapirwio(Chan *c, char *a, ulong len, ulong offset)
{
	Drive *dp;
	ulong vers;
	long rv;

	dp = atadrive[DRIVE(c->qid.path)];

	qlock(dp);
	if(waserror()){
		qunlock(dp);
		nexterror();
	}

	vers = c->qid.vers;
	c->qid.vers = dp->vers;
	if(vers && vers != dp->vers)
		error(Eio);
	rv = atapiio(dp, a, len, offset);

	poperror();
	qunlock(dp);
	return rv;
}

static void
atapipart(Drive *dp)
{
	Controller *cp;
	uchar *buf, err;
	Partition *pp;
	int retrycount;

	cp = dp->cp;

	pp = &dp->p[0];
	strcpy(pp->name, "disk");
	pp->start = 0;
	pp->end = 0;
	dp->npart = 1;

	buf = smalloc(Maxxfer);
	qlock(cp->ctlrlock);
	retrycount = 1;
retry:
	if(waserror()){
		if((cp->status & Serr) && (cp->error & 0xF0) == 0x60){
			dp->vers++;
			if(retrycount){
				retrycount--;
				goto retry;
			}
		}
		cp->dp = 0;
		free(buf);
		if((cp->status & Serr) && (cp->error & 0xF0) == 0x20)
			err = cp->error;
		else
			err = 0;
		qunlock(cp->ctlrlock);
		if(err == 0x20)
			return;
		nexterror();
	}

	cp->buf = buf;
	cp->dp = dp;

	cp->len = 8;
	cp->count = 0;
	memset(cp->cmdblk, 0, sizeof(cp->cmdblk));
	cp->cmdblk[0] = Ccapacity;
	atapiexec(dp);
	if(cp->count != 8){
		print("cmd=%2.2uX, lastcmd=%2.2uX ", cp->cmd, cp->lastcmd);
		print("cdsize count %d, status 0x%2.2uX, error 0x%2.2uX\n",
			cp->count, cp->status, cp->error);
		error(Eio);
	}
	dp->lbasecs = (cp->buf[0]<<24)|(cp->buf[1]<<16)|(cp->buf[2]<<8)|cp->buf[3];
	dp->cap = dp->lbasecs*dp->bytes;
	cp->dp = 0;
	free(cp->buf);
	poperror();
	qunlock(cp->ctlrlock);

	pp->end = dp->cap / dp->bytes;
	dp->partok = 1;
}

static void
atapiintr(Controller *cp)
{
	uchar cause;
	int count, loop, pbase;
	uchar *addr;

	pbase = cp->pbase;
	cause = inb(pbase+Pcount) & 0x03;
	DPRINT("%s: atapiintr %uX\n", cp->dp->vol, cause);
	switch(cause){

	case 0:						/* data out */
		cp->status |= Serr;
		/*FALLTHROUGH*/
	case 1:						/* command */
		if(cp->status & Serr){
			cp->lastcmd = cp->cmd;
			cp->cmd = 0;
			cp->error = inb(pbase+Perror);
			wakeup(&cp->r); 
			break;
		}
		outss(pbase+Pdata, cp->cmdblk, sizeof(cp->cmdblk)/2);
		break;

	case 2:						/* data in */
		addr = cp->buf;
		if(addr == 0){
			cp->lastcmd = cp->cmd;
			cp->cmd = 0;
			if(cp->status & Serr)
				cp->error = inb(pbase+Perror);
			wakeup(&cp->r);	 
			break;	
		}
		loop = 0;
		while((cp->status & (Serr|Sdrq)) == 0){
			if(++loop > Maxloop){
				cp->status |= Serr;
				break;
			}
			cp->status = inb(pbase+Pstatus);
		}
		if(cp->status & Serr){
			cp->lastcmd = cp->cmd;
			cp->cmd = 0;
			cp->error = inb(pbase+Perror);
			print("%s: Cpktcmd status=%uX, error=%uX\n",
				cp->dp->vol, cp->status, cp->error);
			wakeup(&cp->r);
			break;
		}
		count = inb(pbase+Pcyllsb)|(inb(pbase+Pcylmsb)<<8);
		if (count > Maxxfer) 
			count = Maxxfer;
		inss(pbase+Pdata, addr, count/2);
		cp->count = count;
		cp->lastcmd = cp->cmd; 
		break;

	case 3:						/* status */
		cp->lastcmd = cp->cmd;
		cp->cmd = 0;
		if(cp->status & Serr)
			cp->error = inb(cp->pbase+Perror);
		wakeup(&cp->r);	
		break;
	}
}

M pc/pci.c => pc/pci.c +42 -3
@@ 106,11 106,11 @@ pcicfgw(int busno, int devno, int funcno, int regno, void* data, int nbytes)
	case 1:
		addr = 0x80000000|((busno & 0xFF)<<16)|((devno & 0x1F)<<11)|((funcno & 0x03)<<8);
		p = data;
		for(len = nbytes/sizeof(ulong); len > 0; len--){
		for(len = nbytes/sizeof(*p); len > 0; len--){
			outl(PCIaddr, addr|(regno & 0xFF));
			outl(PCIdata, *p);
			p++;
			regno += sizeof(ulong);
			regno += sizeof(*p);
		}
	
		outl(PCIaddr, 0);


@@ 124,7 124,7 @@ pcicfgw(int busno, int devno, int funcno, int regno, void* data, int nbytes)
	
		base = (0xC000|(devno<<8)) + regno;
		p = data;
		for(len = nbytes/sizeof(ulong); len > 0; len--){
		for(len = nbytes/sizeof(*p); len > 0; len--){
			outl(base, *p);
			p++;
			base += sizeof(*p);


@@ 136,6 136,45 @@ pcicfgw(int busno, int devno, int funcno, int regno, void* data, int nbytes)
	unlock(&pcicfglock);
}

/*
 * This is not in the spec, but at least the CMD640B requires it.
 */
void
pcicfgw8(int busno, int devno, int funcno, int regno, void* data, int nbytes)
{
	uchar *p;
	int base, len;

	lock(&pcicfglock);
	if(pcicfgmode == -1)
		pcicfginit(busno);

	switch(pcicfgmode){

	default:
		panic("pcicfgw8: pcicfgmode %d\n", pcicfgmode);
		break;

	case 2:
		if(devno >= 16)
			break;
		outb(PCIcse, 0x80|((funcno & 0x07)<<1));
		outb(PCIforward, busno);
	
		base = (0xC000|(devno<<8)) + regno;
		p = data;
		for(len = nbytes/sizeof(*p); len > 0; len--){
			outb(base, *p);
			p++;
			base += sizeof(*p);
		}
	
		outb(PCIcse, 0);
	}

	unlock(&pcicfglock);
}

int
pcimatch(int busno, int devno, PCIcfg* pcicfg)
{