plan 9 kernel history: overview | file list | diff list

2000/0607/alphapc/sdata.c (diff list | history)

2000/0515/sys/src/9/alphapc/sdata.c:11,182000/0607/sys/src/9/alphapc/sdata.c:11,24 (short | long)
2000/0515    
 
extern SDifc sdataifc; 
 
#define DEBUG		0 
#define DPRINT		if(DEBUG)print 
2000/0607    
enum { 
	DbgCONFIG	= 0x01,		/* detected drive config info */ 
	DbgIDENTIFY	= 0x02,		/* detected drive identify info */ 
	DbgSTATE	= 0x04,		/* dump state on panic */ 
	DbgPROBE	= 0x08,		/* trace device probing */ 
	DbgDEBUG	= 0x80,		/* the current problem... */ 
}; 
#define DEBUG		(DbgDEBUG|DbgSTATE|DbgCONFIG) 
2000/0515    
 
enum {					/* I/O ports */ 
	Data		= 0, 
2000/0515/sys/src/9/alphapc/sdata.c:86,912000/0607/sys/src/9/alphapc/sdata.c:92,98
2000/0515    
	Cidpkt		= 0xA1,		/* Identify Packet Device */ 
	Crsm		= 0xC4,		/* Read Multiple */ 
	Cwsm		= 0xC5,		/* Write Multiple */ 
2000/0607    
	Csm		= 0xC6,		/* Set Multiple */ 
2000/0515    
	Crdq		= 0xC7,		/* Read DMA queued */ 
	Crd		= 0xC8,		/* Read DMA */ 
	Cwd		= 0xCA,		/* Write DMA */ 
2000/0515/sys/src/9/alphapc/sdata.c:190,1952000/0607/sys/src/9/alphapc/sdata.c:197,203
2000/0515    
	int	cmdport; 
	int	ctlport; 
	int	irq; 
2000/0607    
	int	tbdf; 
2000/0515    
	int	bmiba;			/* bus master interface base address */ 
 
	Pcidev*	pcidev; 
2000/0515/sys/src/9/alphapc/sdata.c:202,2072000/0607/sys/src/9/alphapc/sdata.c:210,216
2000/0515    
 
	QLock;				/* current command */ 
	Drive*	curdrive; 
2000/0607    
	int	command;		/* last command issued (debugging) */ 
2000/0515    
	Rendez; 
	int	done; 
 
2000/0515/sys/src/9/alphapc/sdata.c:218,2282000/0607/sys/src/9/alphapc/sdata.c:227,241
2000/0515    
	int	s;			/* sector */ 
	int	sectors;		/* total */ 
	int	secsize;		/* sector size */ 
	int	block;			/* R/W multiple size */ 
	int	pior;			/* PIO read command */ 
	int	piow;			/* PIO write command */ 
2000/0607    
 
2000/0515    
	int	dma;			/* DMA R/W possible */ 
2000/0607    
	int	dmactl; 
	int	rwm;			/* read/write multiple possible */ 
	int	rwmctl; 
 
2000/0515    
	int	pkt;			/* PACKET device, length of pktcmd */ 
2000/0607    
	uchar	pktcmd[16]; 
	int	pktdma;			/* this PACKET command using dma */ 
2000/0515    
 
	uchar	sense[18]; 
	uchar	inquiry[48]; 
2000/0515/sys/src/9/alphapc/sdata.c:234,2442000/0607/sys/src/9/alphapc/sdata.c:247,255
2000/0515    
	int	dlen; 
	uchar*	limit; 
	int	count;			/* sectors */ 
2000/0607    
	int	block;			/* R/W bytes per block */ 
2000/0515    
	int	status; 
	int	error; 
                 
	uchar	pktcmd[16]; 
	int	pktdma; 
} Drive; 
 
static void 
2000/0515/sys/src/9/alphapc/sdata.c:259,2642000/0607/sys/src/9/alphapc/sdata.c:270,325
2000/0515    
	pcicfgw32(p, 0x40, x); 
} 
 
2000/0607    
static void 
atadumpstate(Drive* drive, uchar* cmd, int lba, int count) 
{ 
	Prd *prd; 
	Pcidev *p; 
	Ctlr *ctlr; 
	int i, bmiba; 
 
	if(!(DEBUG & DbgSTATE)){ 
		USED(drive, cmd, lba, count); 
		return; 
	} 
 
	ctlr = drive->ctlr; 
	print("command %2.2uX\n", ctlr->command); 
	print("data %8.8p limit %8.8p dlen %d status %uX error %uX\n", 
		drive->data, drive->limit, drive->dlen, 
		drive->status, drive->error); 
	if(cmd != nil){ 
		print("lba %d -> %d, count %d -> %d (%d)\n", 
			(cmd[2]<<24)|(cmd[3]<<16)|(cmd[4]<<8)|cmd[5], lba, 
			(cmd[7]<<8)|cmd[8], count, drive->count); 
	} 
	if(!(inb(ctlr->ctlport+As) & Bsy)){ 
		for(i = 1; i < 7; i++) 
			print(" 0x%2.2uX", inb(ctlr->cmdport+i)); 
		print(" 0x%2.2uX\n", inb(ctlr->ctlport+As)); 
	} 
	if(drive->command == Cwd || drive->command == Crd){ 
		bmiba = ctlr->bmiba; 
		prd = ctlr->prdt; 
		print("bmicx %2.2uX bmisx %2.2uX prdt %8.8p\n", 
			inb(bmiba+Bmicx), inb(bmiba+Bmisx), prd); 
		for(;;){ 
			print("pa 0x%8.8luX count %8.8uX\n", 
				prd->pa, prd->count); 
			if(prd->count & PrdEOT) 
				break; 
			prd++; 
		} 
	} 
	if(ctlr->pcidev && ctlr->pcidev->vid == 0x8086){ 
		p = ctlr->pcidev; 
		print("0x40: %4.4uX 0x42: %4.4uX", 
			pcicfgr16(p, 0x40), pcicfgr16(p, 0x42)); 
		print("0x48: %2.2uX\n", pcicfgr8(p, 0x48)); 
		print("0x4A: %4.4uX\n", pcicfgr16(p, 0x4A)); 
	} 
} 
 
2000/0515    
static int 
atadebug(int cmdport, int ctlport, char* fmt, ...) 
{ 
2000/0515/sys/src/9/alphapc/sdata.c:266,2722000/0607/sys/src/9/alphapc/sdata.c:327,333
2000/0515    
	va_list arg; 
	char buf[PRINTSIZE]; 
 
	if(!DEBUG){ 
2000/0607    
	if(!(DEBUG & DbgPROBE)){ 
2000/0515    
		USED(cmdport, ctlport, fmt); 
		return 0; 
	} 
2000/0515/sys/src/9/alphapc/sdata.c:280,2862000/0607/sys/src/9/alphapc/sdata.c:341,347
2000/0515    
			n--; 
		n += snprint(buf+n, PRINTSIZE-n, " ataregs 0x%uX:", 
			cmdport); 
		for(i = 1; i < 7; i++) 
2000/0607    
		for(i = Features; i < Command; i++) 
2000/0515    
			n += snprint(buf+n, PRINTSIZE-n, " 0x%2.2uX", 
				inb(cmdport+i)); 
		if(ctlport) 
2000/0515/sys/src/9/alphapc/sdata.c:306,3322000/0607/sys/src/9/alphapc/sdata.c:367,390
2000/0515    
		 * Wait for the controller to become not busy and 
		 * possibly for a status bit to become true (usually 
		 * Drdy). Must change to the appropriate device 
		 * register set before testing for ready. 
2000/0607    
		 * register set if necessary before testing for ready. 
2000/0515    
		 * Always run through the loop at least once so it 
		 * can be used as a test for !Bsy. 
		 */ 
		as = inb(ctlport+As); 
		if(dev && !(as & (Bsy|Drq))){ 
2000/0607    
		if(as & reset) 
			; 
		else if(dev){ 
2000/0515    
			outb(cmdport+Dh, dev); 
			dev = 0; 
			continue; 
		} 
		else if(!(as & reset)){ 
			if(ready == 0 || (as & ready)){ 
				atadebug(0, 0, "ataready: %d 0x%2.2uX\n", 
					micro, as); 
				return as; 
			} 
2000/0607    
		else if(ready == 0 || (as & ready)){ 
			atadebug(0, 0, "ataready: %d 0x%2.2uX\n", micro, as); 
			return as; 
2000/0515    
		} 
 
		if(micro-- <= 0){ 
			atadebug(0, 0, "ataready: %d 0x%2.2uX\n", 
				micro, as); 
2000/0607    
			atadebug(0, 0, "ataready: %d 0x%2.2uX\n", micro, as); 
2000/0515    
			break; 
		} 
		microdelay(1); 
2000/0515/sys/src/9/alphapc/sdata.c:354,3942000/0607/sys/src/9/alphapc/sdata.c:412,529
2000/0515    
	return 0; 
} 
 
static Drive* 
ataidentify(int cmdport, int ctlport, int dev) 
2000/0607    
static int 
atasf(int cmdport, int ctlport, int dev, uchar* command) 
2000/0515    
{ 
	Drive *drive; 
	uchar buf[512], *p; 
	int command, dma, i, as; 
	ushort *sp; 
2000/0607    
	int as, i; 
2000/0515    
 
	atadebug(0, 0, "identify: port 0x%uX dev 0x%2.2uX\n", cmdport, dev); 
	command = Cidpkt; 
retry: 
	as = ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 100*1000); 
2000/0607    
	if(ataready(cmdport, ctlport, dev, Bsy|Drq, Drdy, 108*1000) < 0) 
		return -1; 
 
	for(i = Features; i < Dh; i++) 
		outb(cmdport+i, command[i]); 
	outb(cmdport+Command, Csf); 
	microdelay(100); 
	as = ataready(cmdport, ctlport, 0, Bsy, Drdy|Df|Err, 109*1000); 
	if(as < 0 || (as & (Df|Err))) 
		return -1; 
	return 0; 
} 
 
static int 
atarwmmode(Drive* drive, int cmdport, int ctlport, int dev) 
{ 
	int as, maxrwm, rwm; 
 
	maxrwm = (drive->info[Imaxrwm] & 0xFF); 
	if(maxrwm == 0) 
		return 0; 
 
	/* 
	 * Sometimes drives come up with the current count set 
	 * to 0; if so, set a suitable value, otherwise believe 
	 * the value in Irwm if the 0x100 bit is set. 
	 */ 
	if(drive->info[Irwm] & 0x100) 
		rwm = (drive->info[Irwm] & 0xFF); 
	else 
		rwm = 0; 
	if(rwm == 0) 
		rwm = maxrwm; 
	if(rwm > 16) 
		rwm = 16; 
	if(ataready(cmdport, ctlport, dev, Bsy|Drq, Drdy, 102*1000) < 0) 
		return 0; 
	outb(cmdport+Count, rwm); 
	outb(cmdport+Command, Csm); 
	microdelay(1); 
	as = ataready(cmdport, ctlport, 0, Bsy, Drdy|Df|Err, 1000); 
	inb(cmdport+Status); 
	if(as < 0 || (as & (Df|Err))) 
		return 0; 
 
	drive->rwm = rwm; 
 
	return rwm; 
} 
 
static int 
atadmamode(Drive* drive) 
{ 
	int dma; 
 
	/* 
	 * Check if any DMA mode enabled. 
	 * Assumes the BIOS has picked and enabled the best. 
	 * This is completely passive at the moment, no attempt is 
	 * made to ensure the hardware is correctly set up. 
	 */ 
	dma = drive->info[Imwdma] & 0x0707; 
	drive->dma = (dma>>8) & dma; 
	if(drive->dma == 0 && (drive->info[Ivalid] & 0x04)){ 
		dma = drive->info[Iudma] & 0x1F1F; 
		drive->dma = (dma>>8) & dma; 
		if(drive->dma) 
			drive->dma |= 'U'<<16; 
	} 
 
	return dma; 
} 
 
static int 
ataidentify(int cmdport, int ctlport, int dev, int pkt, void* info) 
{ 
	int as, command, drdy; 
 
	if(pkt){ 
		command = Cidpkt; 
		drdy = 0; 
	} 
	else{ 
		command = Cid; 
		drdy = Drdy; 
	} 
	as = ataready(cmdport, ctlport, dev, Bsy|Drq, drdy, 103*1000); 
2000/0515    
	if(as < 0) 
		return nil; 
2000/0607    
		return as; 
2000/0515    
	outb(cmdport+Command, command); 
	microdelay(1); 
 
	as = ataready(cmdport, ctlport, dev, Bsy, Drq|Err, 100*1000); 
2000/0607    
	as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 104*1000); 
2000/0515    
	if(as < 0) 
		return nil; 
	if(as & Err){ 
		if(command == Cid) 
			return nil; 
		command = Cid; 
		goto retry; 
	} 
	memset(buf, 0, sizeof(buf)); 
	inss(cmdport+Data, buf, 256); 
2000/0607    
		return -1; 
	if(as & Err) 
		return as; 
 
	memset(info, 0, 512); 
	inss(cmdport+Data, info, 256); 
2000/0515    
	inb(cmdport+Status); 
 
	if(DEBUG > 1){ 
2000/0607    
	if(DEBUG & DbgIDENTIFY){ 
2000/0515    
		int i; 
		ushort *sp; 
 
		sp = (ushort*)buf; 
2000/0607    
		sp = (ushort*)info; 
2000/0515    
		for(i = 0; i < 256; i++){ 
			if(i && (i%16) == 0) 
				print("\n"); 
2000/0515/sys/src/9/alphapc/sdata.c:398,4032000/0607/sys/src/9/alphapc/sdata.c:533,562
2000/0515    
		print("\n"); 
	} 
 
2000/0607    
	return 0; 
} 
 
static Drive* 
atadrive(int cmdport, int ctlport, int dev) 
{ 
	ushort *sp; 
	Drive *drive; 
	int as, i, pkt; 
	uchar buf[512], *p; 
 
	atadebug(0, 0, "identify: port 0x%uX dev 0x%2.2uX\n", cmdport, dev); 
	pkt = 1; 
retry: 
	as = ataidentify(cmdport, ctlport, dev, pkt, buf); 
	if(as < 0) 
		return nil; 
	if(as & Err){ 
		if(pkt == 0) 
			return nil; 
		pkt = 0; 
		goto retry; 
	} 
 
2000/0515    
	if((drive = malloc(sizeof(Drive))) == nil) 
		return nil; 
	drive->dev = dev; 
2000/0515/sys/src/9/alphapc/sdata.c:414,4192000/0607/sys/src/9/alphapc/sdata.c:573,580
2000/0515    
		*p++ = *sp>>8; 
		*p++ = *sp++; 
	} 
2000/0607    
 
	drive->secsize = 512; 
2000/0515    
	if((drive->info[Iconfig] & 0xC000) == 0x8000){ 
		if(drive->info[Iconfig] & 0x01) 
			drive->pkt = 16; 
2000/0515/sys/src/9/alphapc/sdata.c:438,4732000/0607/sys/src/9/alphapc/sdata.c:599,617
2000/0515    
		} 
		else 
			drive->sectors = drive->c*drive->h*drive->s; 
	}	 
	drive->secsize = 512; 
                 
	if((drive->info[Imaxrwm] & 0xFF) && (drive->info[Irwm] & 0x0100)){ 
		drive->block = drive->info[Imaxrwm] & 0x00FF; 
		drive->pior = Crsm; 
		drive->piow = Cwsm; 
2000/0607    
		atarwmmode(drive, cmdport, ctlport, dev); 
2000/0515    
	} 
	else{ 
		drive->block = 1; 
		drive->pior = Crs; 
		drive->piow = Cws; 
	} 
	drive->block *= drive->secsize; 
2000/0607    
	atadmamode(drive);	 
2000/0515    
 
	/* 
	 * Check if any DMA mode enabled. 
	 * Assumes the BIOS has picked and enabled the best. 
	 */ 
	dma = drive->info[Imwdma] & 0x0707; 
	drive->dma = (dma>>8) & dma; 
	if(drive->dma == 0 && (drive->info[Ivalid] & 0x04)){ 
		dma = drive->info[Iudma] & 0x1F1F; 
		drive->dma = (dma>>8) & dma; 
		if(drive->dma) 
			drive->dma |= 'U'<<16; 
2000/0607    
	if(DEBUG & DbgCONFIG){ 
		print("dev %2.2uX config %4.4uX capabilities %4.4uX", 
			dev, drive->info[Iconfig], drive->info[Icapabilities]); 
		print(" mwdma %4.4uX dma %8.8uX",  
			drive->info[Imwdma], drive->dma); 
		if(drive->info[Ivalid] & 0x04) 
			print(" udma %4.4uX", drive->info[Iudma]); 
		print(" rwm %ud\n", drive->rwm); 
2000/0515    
	} 
print("dev %4.4uX capabilities %4.4uX config %4.4uX mwdma %4.4uX dma %8.8uX\n", 
    dev, drive->info[Icapabilities], drive->info[Iconfig], 
    drive->info[Imwdma], drive->dma); 
 
	return drive; 
} 
2000/0515/sys/src/9/alphapc/sdata.c:519,5242000/0607/sys/src/9/alphapc/sdata.c:663,669
2000/0515    
	dev = Dev0; 
	if(inb(ctlport+As) & Bsy){ 
		outb(cmdport+Dh, dev); 
2000/0607    
		microdelay(1); 
2000/0515    
trydev1: 
		atadebug(cmdport, ctlport, "ataprobe bsy"); 
		outb(cmdport+Cyllo, 0xAA); 
2000/0515/sys/src/9/alphapc/sdata.c:544,5502000/0607/sys/src/9/alphapc/sdata.c:689,695
2000/0515    
	 */ 
	outb(ctlport+Dc, Nien); 
tryedd1: 
	if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 100*1000) < 0){ 
2000/0607    
	if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 105*1000) < 0){ 
2000/0515    
		/* 
		 * There's something there, but it didn't come up clean, 
		 * so try hitting it with a big stick. The timing here is 
2000/0515/sys/src/9/alphapc/sdata.c:552,5582000/0607/sys/src/9/alphapc/sdata.c:697,703
2000/0515    
		 * gets some marginal hardware back online. 
		 */ 
		atasrst(ctlport); 
		if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 100*1000) < 0) 
2000/0607    
		if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 106*1000) < 0) 
2000/0515    
			goto release; 
	} 
 
2000/0515/sys/src/9/alphapc/sdata.c:591,5972000/0607/sys/src/9/alphapc/sdata.c:736,742
2000/0515    
	 * If the one drive found is Dev0 and the EDD command 
	 * didn't indicate Dev1 doesn't exist, check for it. 
	 */ 
	if((drive = ataidentify(cmdport, ctlport, dev)) == nil) 
2000/0607    
	if((drive = atadrive(cmdport, ctlport, dev)) == nil) 
2000/0515    
		goto release; 
	if((ctlr = malloc(sizeof(Ctlr))) == nil){ 
		free(drive); 
2000/0515/sys/src/9/alphapc/sdata.c:613,6252000/0607/sys/src/9/alphapc/sdata.c:758,772
2000/0515    
			 * Ataprobe is the only place possibly invalid 
			 * drives should be selected. 
			 */ 
			drive = ataidentify(cmdport, ctlport, Dev1); 
2000/0607    
			drive = atadrive(cmdport, ctlport, Dev1); 
2000/0515    
			if(drive != nil){ 
				drive->ctlr = ctlr; 
				ctlr->drive[1] = drive; 
			} 
			else 
2000/0607    
			else{ 
2000/0515    
				outb(cmdport+Dh, Dev0); 
2000/0607    
				microdelay(1); 
			} 
2000/0515    
		} 
	} 
	else 
2000/0515/sys/src/9/alphapc/sdata.c:628,6332000/0607/sys/src/9/alphapc/sdata.c:775,782
2000/0515    
	ctlr->cmdport = cmdport; 
	ctlr->ctlport = ctlport; 
	ctlr->irq = irq; 
2000/0607    
	ctlr->tbdf = BUSUNKNOWN; 
	ctlr->command = Cedd;		/* debugging */ 
2000/0515    
 
	sdev->ifc = &sdataifc; 
	sdev->ctlr = ctlr; 
2000/0515/sys/src/9/alphapc/sdata.c:693,6992000/0607/sys/src/9/alphapc/sdata.c:842,849
2000/0515    
	cmdport = ctlr->cmdport; 
	outb(cmdport+Features, subcommand); 
	outb(cmdport+Dh, drive->dev); 
	outb(cmdport+Command, 0); 
2000/0607    
	ctlr->command = Cnop;		/* debugging */ 
	outb(cmdport+Command, Cnop); 
2000/0515    
 
	microdelay(1); 
	ctlport = ctlr->ctlport; 
2000/0515/sys/src/9/alphapc/sdata.c:707,7182000/0607/sys/src/9/alphapc/sdata.c:857,870
2000/0515    
} 
 
static void 
ataabort(Drive* drive) 
2000/0607    
ataabort(Drive* drive, int dolock) 
2000/0515    
{ 
	/* 
	 * If NOP is available (packet commands) use it otherwise 
	 * must try a software reset. 
	 */ 
2000/0607    
	if(dolock) 
		ilock(drive->ctlr); 
2000/0515    
	if(atacsfenabled(drive, 0x0000000000004000LL)) 
		atanop(drive, 0); 
	else{ 
2000/0515/sys/src/9/alphapc/sdata.c:719,7242000/0607/sys/src/9/alphapc/sdata.c:871,878
2000/0515    
		atasrst(drive->ctlr->ctlport); 
		drive->error |= Abrt; 
	} 
2000/0607    
	if(dolock) 
		iunlock(drive->ctlr); 
2000/0515    
} 
 
static int 
2000/0515/sys/src/9/alphapc/sdata.c:740,7462000/0607/sys/src/9/alphapc/sdata.c:894,900
2000/0515    
	 * although they are not on a busmastering controller. 
	 */ 
	if(prd == nil){ 
		drive->dma = 0; 
2000/0607    
		drive->dmactl = 0; 
2000/0515    
		return -1; 
	} 
 
2000/0515/sys/src/9/alphapc/sdata.c:778,7842000/0607/sys/src/9/alphapc/sdata.c:932,938
2000/0515    
		outb(ctlr->bmiba+Bmicx, Rwcon|Ssbm); 
} 
 
static void 
2000/0607    
static int 
2000/0515    
atadmastop(Ctlr* ctlr) 
{ 
	int bmiba; 
2000/0515/sys/src/9/alphapc/sdata.c:785,7912000/0607/sys/src/9/alphapc/sdata.c:939,946
2000/0515    
 
	bmiba = ctlr->bmiba; 
	outb(bmiba+Bmicx, inb(bmiba+Bmicx) & ~Ssbm); 
	outb(bmiba+Bmisx, inb(bmiba+Bmisx)|Ideints|Idedmae); 
2000/0607    
 
	return inb(bmiba+Bmisx); 
2000/0515    
} 
 
static void 
2000/0515/sys/src/9/alphapc/sdata.c:820,8262000/0607/sys/src/9/alphapc/sdata.c:975,981
2000/0515    
		 * exhausted. For both cases must somehow tell the 
		 * drive to abort. 
		 */ 
		ataabort(drive); 
2000/0607    
		ataabort(drive, 0); 
2000/0515    
		break; 
	} 
	atadmastop(ctlr); 
2000/0515/sys/src/9/alphapc/sdata.c:897,9092000/0607/sys/src/9/alphapc/sdata.c:1052,1064
2000/0515    
 
	qlock(ctlr); 
 
	if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, 0, 100*1000) < 0){ 
2000/0607    
	if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, 0, 107*1000) < 0){ 
2000/0515    
		qunlock(ctlr); 
		return -1; 
	} 
 
	ilock(ctlr); 
	if(drive->dlen && drive->dma && !atadmasetup(drive, drive->dlen)) 
2000/0607    
	if(drive->dlen && drive->dmactl && !atadmasetup(drive, drive->dlen)) 
2000/0515    
		drive->pktdma = Dma; 
	else 
		drive->pktdma = 0; 
2000/0515/sys/src/9/alphapc/sdata.c:916,9282000/0607/sys/src/9/alphapc/sdata.c:1071,1085
2000/0515    
	outb(cmdport+Bytehi, len>>8); 
	outb(cmdport+Dh, drive->dev); 
	ctlr->done = 0; 
2000/0607    
	ctlr->curdrive = drive; 
	ctlr->command = Cpkt;		/* debugging */ 
2000/0515    
	if(drive->pktdma) 
		atadmastart(ctlr, drive->write); 
	outb(cmdport+Command, Cpkt); 
 
	if((drive->info[Iconfig] & 0x0060) != 0x0020){ 
		as = ataready(cmdport, ctlport, 
			drive->dev, Bsy, Drq|Chk, 4*1000); 
2000/0607    
		microdelay(1); 
		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Chk, 4*1000); 
2000/0515    
		if(as < 0) 
			r = SDtimeout; 
		else if(as & Chk) 
2000/0515/sys/src/9/alphapc/sdata.c:930,9362000/0607/sys/src/9/alphapc/sdata.c:1087,1092
2000/0515    
		else 
			atapktinterrupt(drive); 
	} 
	ctlr->curdrive = drive; 
	iunlock(ctlr); 
 
	while(waserror()) 
2000/0515/sys/src/9/alphapc/sdata.c:944,9522000/0607/sys/src/9/alphapc/sdata.c:1100,1108
2000/0515    
		ilock(ctlr); 
		atadmainterrupt(drive, 0); 
		if(!drive->error && timeo > 10){ 
			ataabort(drive); 
2000/0607    
			ataabort(drive, 0); 
2000/0515    
			atadmastop(ctlr); 
			drive->dma = 0; 
2000/0607    
			drive->dmactl = 0; 
2000/0515    
			drive->error |= Abrt; 
		} 
		if(drive->error){ 
2000/0515/sys/src/9/alphapc/sdata.c:991,10102000/0607/sys/src/9/alphapc/sdata.c:1147,1177
2000/0515    
	ctlr = drive->ctlr; 
	cmdport = ctlr->cmdport; 
	ctlport = ctlr->ctlport; 
	if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, 0, 100*1000) < 0) 
2000/0607    
	if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, 0, 101*1000) < 0) 
2000/0515    
		return -1; 
 
	ilock(ctlr); 
	if(drive->dma && !atadmasetup(drive, drive->count*drive->secsize)){ 
2000/0607    
	if(drive->dmactl && !atadmasetup(drive, drive->count*drive->secsize)){ 
2000/0515    
		if(drive->write) 
			drive->command = Cwd; 
		else 
			drive->command = Crd; 
	} 
	else if(drive->write) 
		drive->command = drive->piow; 
	else 
		drive->command = drive->pior; 
2000/0607    
	else if(drive->rwmctl){ 
		drive->block = drive->rwm*drive->secsize; 
		if(drive->write) 
			drive->command = Cwsm; 
		else 
			drive->command = Crsm; 
	} 
	else{ 
		drive->block = drive->secsize; 
		if(drive->write) 
			drive->command = Cws; 
		else 
			drive->command = Crs; 
	} 
	drive->limit = drive->data + drive->count*drive->secsize; 
2000/0515    
 
	outb(cmdport+Count, drive->count); 
	outb(cmdport+Sector, s); 
2000/0515/sys/src/9/alphapc/sdata.c:1012,10242000/0607/sys/src/9/alphapc/sdata.c:1179,1193
2000/0515    
	outb(cmdport+Cyllo, c); 
	outb(cmdport+Cylhi, c>>8); 
	ctlr->done = 0; 
2000/0607    
	ctlr->curdrive = drive; 
	ctlr->command = drive->command;	/* debugging */ 
2000/0515    
	outb(cmdport+Command, drive->command); 
	microdelay(1); 
 
	switch(drive->command){ 
	case Cws: 
	case Cwsm: 
		as = ataready(cmdport, ctlport, drive->dev, Bsy, Drq|Err, 1000); 
2000/0607    
		microdelay(1); 
		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 1000); 
2000/0515    
		if(as < 0 || (as & Err)){ 
			iunlock(ctlr); 
			return -1; 
2000/0515/sys/src/9/alphapc/sdata.c:1034,10402000/0607/sys/src/9/alphapc/sdata.c:1203,1208
2000/0515    
		atadmastart(ctlr, drive->write); 
		break; 
	} 
	ctlr->curdrive = drive; 
	iunlock(ctlr); 
 
	return 0; 
2000/0515/sys/src/9/alphapc/sdata.c:1041,10462000/0607/sys/src/9/alphapc/sdata.c:1209,1227
2000/0515    
} 
 
static int 
2000/0607    
atagenioretry(Drive* drive) 
{ 
	if(drive->dmactl) 
		drive->dmactl = 0; 
	else if(drive->rwmctl) 
		drive->rwmctl = 0; 
	else 
		return atasetsense(drive, SDcheck, 4, 8, drive->error); 
 
	return SDretry; 
} 
 
static int 
2000/0515    
atagenio(Drive* drive, uchar* cmd, int) 
{ 
	uchar *p; 
2000/0515/sys/src/9/alphapc/sdata.c:1134,11452000/0607/sys/src/9/alphapc/sdata.c:1315,1327
2000/0515    
			drive->count = 256; 
		else 
			drive->count = count; 
		drive->limit += drive->count*drive->secsize; 
		if(atageniostart(drive, lba)){ 
2000/0607    
			ilock(ctlr); 
			atanop(drive, 0); 
			iunlock(ctlr); 
2000/0515    
			qunlock(ctlr); 
			return atasetsense(drive, SDcheck, 2, 5, 0); 
2000/0607    
			return atagenioretry(drive); 
2000/0515    
		} 
		lba += drive->count; 
 
		while(waserror()) 
			; 
2000/0515/sys/src/9/alphapc/sdata.c:1151,11592000/0607/sys/src/9/alphapc/sdata.c:1333,1342
2000/0515    
			 * standby and sleep modes it could take as 
			 * long as 30 seconds for a drive to respond. 
			 * Very hard to get out of this cleanly. 
			 * Let's see if it ever happens first... 
			 */ 
			panic("atagenio"); 
2000/0607    
			atadumpstate(drive, cmd, lba, count); 
			ataabort(drive, 1); 
			return atagenioretry(drive); 
2000/0515    
		} 
 
		if(drive->status & Err){ 
2000/0515/sys/src/9/alphapc/sdata.c:1160,11702000/0607/sys/src/9/alphapc/sdata.c:1343,1350
2000/0515    
			qunlock(ctlr); 
			return atasetsense(drive, SDcheck, 4, 8, drive->error); 
		} 
		if(drive->data != drive->limit) 
			print("%s: atagenio: %p != %p\n", 
				ctlr->sdev->name, 
				drive->data, drive->limit); 
		count -= drive->count; 
2000/0607    
		lba += drive->count; 
2000/0515    
	} 
	qunlock(ctlr); 
 
2000/0515/sys/src/9/alphapc/sdata.c:1215,12242000/0607/sys/src/9/alphapc/sdata.c:1395,1404
2000/0515    
	} 
 
	qlock(drive); 
2000/0607    
retry: 
2000/0515    
	drive->write = r->write; 
	drive->data = r->data; 
	drive->dlen = r->dlen; 
	drive->limit = r->data; 
 
	drive->status = 0; 
	drive->error = 0; 
2000/0515/sys/src/9/alphapc/sdata.c:1226,12312000/0607/sys/src/9/alphapc/sdata.c:1406,1417
2000/0515    
		status = atapktio(drive, cmdp, clen); 
	else 
		status = atagenio(drive, cmdp, clen); 
2000/0607    
	if(status == SDretry){ 
		if(DbgDEBUG) 
			print("%s: retry: dma %8.8uX rwm %4.4uX\n", 
				unit->name, drive->dmactl, drive->rwmctl); 
		goto retry; 
	} 
2000/0515    
	if(status == SDok){ 
		atasetsense(drive, SDok, 0, 0, 0); 
		if(drive->data){ 
2000/0515/sys/src/9/alphapc/sdata.c:1243,12492000/0607/sys/src/9/alphapc/sdata.c:1429,1434
2000/0515    
		cmd10[4] = sizeof(r->sense)-1; 
		drive->data = r->sense; 
		drive->dlen = sizeof(r->sense)-1; 
		drive->limit = r->sense; 
		drive->status = 0; 
		drive->error = 0; 
		if(drive->pkt) 
2000/0515/sys/src/9/alphapc/sdata.c:1288,12972000/0607/sys/src/9/alphapc/sdata.c:1473,1484
2000/0515    
	int cmdport, len, status; 
 
	ctlr = arg; 
	                 
2000/0607    
 
2000/0515    
	ilock(ctlr); 
	if(inb(ctlr->ctlport+As) & Bsy){ 
		iunlock(ctlr); 
2000/0607    
		if(DEBUG & DbgDEBUG) 
			print("IBsy+"); 
2000/0515    
		return; 
	} 
	cmdport = ctlr->cmdport; 
2000/0515/sys/src/9/alphapc/sdata.c:1298,13032000/0607/sys/src/9/alphapc/sdata.c:1485,1492
2000/0515    
	status = inb(cmdport+Status); 
	if((drive = ctlr->curdrive) == nil){ 
		iunlock(ctlr); 
2000/0607    
		if((DEBUG & DbgDEBUG) && ctlr->command != Cedd) 
			print("Inil%2.2uX+", ctlr->command); 
2000/0515    
		return; 
	} 
 
2000/0515/sys/src/9/alphapc/sdata.c:1371,13772000/0607/sys/src/9/alphapc/sdata.c:1560,1566
2000/0515    
{ 
	Ctlr *ctlr; 
	Pcidev *p; 
	int channel, ispc87415, pi; 
2000/0607    
	int channel, ispc87415, pi, r; 
2000/0515    
	SDev *legacy[2], *sdev, *head, *tail; 
 
	legacy[0] = legacy[1] = head = tail = nil; 
2000/0515/sys/src/9/alphapc/sdata.c:1425,14412000/0607/sys/src/9/alphapc/sdata.c:1614,1641
2000/0515    
			ispc87415 = 1; 
			pcicfgw32(p, 0x40, 0x00000300); 
			break; 
                 
2000/0607    
		case (0x1000<<16)|0x1042:	/* PC-Tech RZ1000 */ 
			/* 
			 * Turn off prefetch. Overkill, but cheap. 
			 */ 
			r = pcicfgr32(p, 0x40); 
			r &= ~0x2000; 
			pcicfgw32(p, 0x40, r); 
			break; 
2000/0515    
		case (0x4D38<<16)|0x105A:	/* Promise PDC20262 */ 
			pi = 0x85; 
			break; 
2000/0607    
		case (0x0640<<16)|0x1095:	/* CMD 640B */ 
			/* 
			 * Bugfix code here... 
			 */ 
			break; 
		case (0x0646<<16)|0x1095:	/* CMD 646 */ 
		case (0x0571<<16)|0x1106:	/* VIA 82C686 */ 
2000/0515    
		case (0x1230<<16)|0x8086:	/* 82371FB (PIIX) */ 
		case (0x7010<<16)|0x8086:	/* 82371SB (PIIX3) */ 
		case (0x7111<<16)|0x8086:	/* 82371[AE]B (PIIX4[E]) */ 
		case (0x0646<<16)|0x1095:	/* CMD 646 */ 
			break; 
		case (0x0571<<16)|0x1106:	/* VIA 82C686 */ 
			break; 
		} 
 
		for(channel = 0; channel < 2; channel++){ 
2000/0515/sys/src/9/alphapc/sdata.c:1455,14612000/0607/sys/src/9/alphapc/sdata.c:1655,1661
2000/0515    
				else 
					head = sdev; 
				tail = sdev; 
				ctlr->pcidev = p; 
2000/0607    
				ctlr->tbdf = p->tbdf; 
2000/0515    
			} 
			else if((sdev = legacy[channel]) == nil) 
				continue; 
2000/0515/sys/src/9/alphapc/sdata.c:1462,14672000/0607/sys/src/9/alphapc/sdata.c:1662,1668
2000/0515    
			else 
				ctlr = sdev->ctlr; 
 
2000/0607    
			ctlr->pcidev = p; 
2000/0515    
			if(!(pi & 0x80)) 
				continue; 
			ctlr->bmiba = (p->mem[4].bar & ~0x01) + channel*8; 
2000/0515/sys/src/9/alphapc/sdata.c:1518,15242000/0607/sys/src/9/alphapc/sdata.c:1719,1724
2000/0515    
static int 
ataenable(SDev* sdev) 
{ 
	int tbdf; 
	Ctlr *ctlr; 
	char name[NAMELEN]; 
 
2000/0515/sys/src/9/alphapc/sdata.c:1529,15402000/0607/sys/src/9/alphapc/sdata.c:1729,1736
2000/0515    
			pcisetbme(ctlr->pcidev); 
		ctlr->prdt = xspanalloc(Nprd*sizeof(Prd), 4, 4*1024); 
	} 
	if(ctlr->pcidev) 
		tbdf = ctlr->pcidev->tbdf; 
	else 
		tbdf = BUSUNKNOWN; 
	snprint(name, NAMELEN, "%s (%s)", sdev->name, sdev->ifc->name); 
	intrenable(ctlr->irq, atainterrupt, ctlr, tbdf, name); 
2000/0607    
	intrenable(ctlr->irq, atainterrupt, ctlr, ctlr->tbdf, name); 
2000/0515    
	outb(ctlr->ctlport+Dc, 0); 
	if(ctlr->ienable) 
		ctlr->ienable(ctlr); 
2000/0515/sys/src/9/alphapc/sdata.c:1553,15672000/0607/sys/src/9/alphapc/sdata.c:1749,1827
2000/0515    
		return 0; 
	drive = ctlr->drive[unit->subno]; 
 
	n = snprint(p, l, "geometry %ld %ld", unit->sectors, unit->secsize); 
	if(drive->pkt == 0) 
		n += snprint(p+n, l-n, " %d %d %d", 
			drive->c, drive->h, drive->s); 
2000/0607    
	qlock(drive); 
	n = snprint(p, l, "config %4.4uX capabilities %4.4uX", 
		drive->info[Iconfig], drive->info[Icapabilities]); 
	if(drive->dma) 
		n += snprint(p+n, l-n, " dma %8.8uX dmactl %8.8uX", 
			drive->dma, drive->dmactl); 
	if(drive->rwm) 
		n += snprint(p+n, l-n, " rwm %ud rwmctl %ud", 
			drive->rwm, drive->rwmctl); 
2000/0515    
	n += snprint(p+n, l-n, "\n"); 
2000/0607    
	if(!unit->changed && unit->sectors){ 
		n += snprint(p+n, l-n, "geometry %ld %ld", 
			unit->sectors, unit->secsize); 
		if(drive->pkt == 0) 
			n += snprint(p+n, l-n, " %d %d %d", 
				drive->c, drive->h, drive->s); 
		n += snprint(p+n, l-n, "\n"); 
	} 
	qunlock(drive); 
2000/0515    
 
	return n; 
} 
 
2000/0607    
static int 
atawctl(SDunit* unit, Cmdbuf* cb) 
{ 
	Ctlr *ctlr; 
	Drive *drive; 
 
	if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil) 
		return 0; 
	drive = ctlr->drive[unit->subno]; 
 
	qlock(drive); 
	if(waserror()){ 
		qunlock(drive); 
		nexterror(); 
	} 
 
	/* 
	 * Dma and rwm control is passive at the moment, 
	 * i.e. it is assumed that the hardware is set up 
	 * correctly already either by the BIOS or when 
	 * the drive was initially identified. 
	 */ 
	if(strcmp(cb->f[0], "dma") == 0){ 
		if(cb->nf != 2 || drive->dma == 0) 
			error(Ebadctl); 
		if(strcmp(cb->f[1], "on") == 0) 
			drive->dmactl = drive->dma; 
		else if(strcmp(cb->f[1], "off") == 0) 
			drive->dmactl = 0; 
		else 
			error(Ebadctl); 
	} 
	else if(strcmp(cb->f[0], "rwm") == 0){ 
		if(cb->nf != 2 || drive->rwm == 0) 
			error(Ebadctl); 
		if(strcmp(cb->f[1], "on") == 0) 
			drive->rwmctl = drive->rwm; 
		else if(strcmp(cb->f[1], "off") == 0) 
			drive->rwmctl = 0; 
		else 
			error(Ebadctl); 
	} 
	else 
		error(Ebadctl); 
	qunlock(drive); 
	poperror(); 
 
	return 0; 
} 
 
2000/0515    
SDifc sdataifc = { 
	"ata",				/* name */ 
 
2000/0515/sys/src/9/alphapc/sdata.c:1575,15812000/0607/sys/src/9/alphapc/sdata.c:1835,1841
2000/0515    
	scsionline,			/* online */ 
	atario,				/* rio */ 
	atarctl,			/* rctl */ 
	nil,				/* wctl */ 
2000/0607    
	atawctl,			/* wctl */ 
2000/0515    
 
	scsibio,			/* bio */ 
}; 
2000/0607/sys/src/9/alphapc/sdata.c:1,18412000/0706/sys/src/9/alphapc/sdata.c:1 (short | long)
2000/0515    
#include "u.h" 
#include "../port/lib.h" 
#include "mem.h" 
#include "dat.h" 
#include "fns.h" 
#include "io.h" 
#include "ureg.h" 
#include "../port/error.h" 
                 
#include "sd.h" 
                 
extern SDifc sdataifc; 
                 
2000/0607    
enum { 
	DbgCONFIG	= 0x01,		/* detected drive config info */ 
	DbgIDENTIFY	= 0x02,		/* detected drive identify info */ 
	DbgSTATE	= 0x04,		/* dump state on panic */ 
	DbgPROBE	= 0x08,		/* trace device probing */ 
	DbgDEBUG	= 0x80,		/* the current problem... */ 
}; 
#define DEBUG		(DbgDEBUG|DbgSTATE|DbgCONFIG) 
2000/0515    
                 
enum {					/* I/O ports */ 
	Data		= 0, 
	Error		= 1,		/* (read) */ 
	Features	= 1,		/* (write) */ 
	Count		= 2,		/* sector count */ 
	Ir		= 2,		/* interrupt reason (PACKET) */ 
	Sector		= 3,		/* sector number, LBA<7-0> */ 
	Cyllo		= 4,		/* cylinder low, LBA<15-8> */ 
	Bytelo		= 4,		/* byte count low (PACKET) */ 
	Cylhi		= 5,		/* cylinder high, LBA<23-16> */ 
	Bytehi		= 5,		/* byte count hi (PACKET) */ 
	Dh		= 6,		/* Device/Head, LBA<32-14> */ 
	Status		= 7,		/* (read) */ 
	Command		= 7,		/* (write) */ 
                 
	As		= 2,		/* Alternate Status (read) */ 
	Dc		= 2,		/* Device Control (write) */ 
}; 
                 
enum {					/* Error */ 
	Med		= 0x01,		/* Media error */ 
	Ili		= 0x01,		/* command set specific (PACKET) */ 
	Nm		= 0x02,		/* No Media */ 
	Eom		= 0x02,		/* command set specific (PACKET) */ 
	Abrt		= 0x04,		/* Aborted command */ 
	Mcr		= 0x08,		/* Media Change Request */ 
	Idnf		= 0x10,		/* no user-accessible address */ 
	Mc		= 0x20,		/* Media Change */ 
	Unc		= 0x40,		/* Uncorrectable data error */ 
	Wp		= 0x40,		/* Write Protect */ 
	Icrc		= 0x80,		/* Interface CRC error */ 
}; 
                 
enum {					/* Features */ 
	Dma		= 0x01,		/* data transfer via DMA (PACKET) */ 
	Ovl		= 0x02,		/* command overlapped (PACKET) */ 
}; 
                 
enum {					/* Interrupt Reason */ 
	Cd		= 0x01,		/* Command/Data */ 
	Io		= 0x02,		/* I/O direction */ 
	Rel		= 0x04,		/* Bus Release */ 
}; 
                 
enum {					/* Device/Head */ 
	Dev0		= 0xA0,		/* Master */ 
	Dev1		= 0xB0,		/* Slave */ 
	Lba		= 0x40,		/* LBA mode */ 
}; 
                 
enum {					/* Status, Alternate Status */ 
	Err		= 0x01,		/* Error */ 
	Chk		= 0x01,		/* Check error (PACKET) */ 
	Drq		= 0x08,		/* Data Request */ 
	Dsc		= 0x10,		/* Device Seek Complete */ 
	Serv		= 0x10,		/* Service */ 
	Df		= 0x20,		/* Device Fault */ 
	Dmrd		= 0x20,		/* DMA ready (PACKET) */ 
	Drdy		= 0x40,		/* Device Ready */ 
	Bsy		= 0x80,		/* Busy */ 
}; 
                 
enum {					/* Command */ 
	Cnop		= 0x00,		/* NOP */ 
	Cdr		= 0x08,		/* Device Reset */ 
	Crs		= 0x20,		/* Read Sectors */ 
	Cws		= 0x30,		/* Write Sectors */ 
	Cedd		= 0x90,		/* Execute Device Diagnostics */ 
	Cpkt		= 0xA0,		/* Packet */ 
	Cidpkt		= 0xA1,		/* Identify Packet Device */ 
	Crsm		= 0xC4,		/* Read Multiple */ 
	Cwsm		= 0xC5,		/* Write Multiple */ 
2000/0607    
	Csm		= 0xC6,		/* Set Multiple */ 
2000/0515    
	Crdq		= 0xC7,		/* Read DMA queued */ 
	Crd		= 0xC8,		/* Read DMA */ 
	Cwd		= 0xCA,		/* Write DMA */ 
	Cwdq		= 0xCC,		/* Write DMA queued */ 
	Cid		= 0xEC,		/* Identify Device */ 
	Csf		= 0xEF,		/* Set Features */ 
}; 
                 
enum {					/* Device Control */ 
	Nien		= 0x02,		/* (not) Interrupt Enable */ 
	Srst		= 0x04,		/* Software Reset */ 
}; 
                 
enum {					/* PCI Configuration Registers */ 
	Bmiba		= 0x20,		/* Bus Master Interface Base Address */ 
	Idetim		= 0x40,		/* IE Timing */ 
	Sidetim		= 0x44,		/* Slave IE Timing */ 
	Udmactl		= 0x48,		/* Ultra DMA/33 Control */ 
	Udmatim		= 0x4A,		/* Ultra DMA/33 Timing */ 
}; 
                 
enum {					/* Bus Master IDE I/O Ports */ 
	Bmicx		= 0,		/* Command */ 
	Bmisx		= 2,		/* Status */ 
	Bmidtpx		= 4,		/* Descriptor Table Pointer */ 
}; 
                 
enum {					/* Bmicx */ 
	Ssbm		= 0x01,		/* Start/Stop Bus Master */ 
	Rwcon		= 0x08,		/* Read/Write Control */ 
}; 
                 
enum {					/* Bmisx */ 
	Bmidea		= 0x01,		/* Bus Master IDE Active */ 
	Idedmae		= 0x02,		/* IDE DMA Error  (R/WC) */ 
	Ideints		= 0x04,		/* IDE Interrupt Status (R/WC) */ 
	Dma0cap		= 0x20,		/* Drive 0 DMA Capable */ 
	Dma1cap		= 0x40,		/* Drive 0 DMA Capable */ 
}; 
enum {					/* Physical Region Descriptor */ 
	PrdEOT		= 0x80000000,	/* Bus Master IDE Active */ 
}; 
                 
enum {					/* offsets into the identify info. */ 
	Iconfig		= 0,		/* general configuration */ 
	Ilcyl		= 1,		/* logical cylinders */ 
	Ilhead		= 3,		/* logical heads */ 
	Ilsec		= 6,		/* logical sectors per logical track */ 
	Iserial		= 10,		/* serial number */ 
	Ifirmware	= 23,		/* firmware revision */ 
	Imodel		= 27,		/* model number */ 
	Imaxrwm		= 47,		/* max. read/write multiple sectors */ 
	Icapabilities	= 49,		/* capabilities */ 
	Istandby	= 50,		/* device specific standby timer */ 
	Ipiomode	= 51,		/* PIO data transfer mode number */ 
	Ivalid		= 53, 
	Iccyl		= 54,		/* cylinders if (valid&0x01) */ 
	Ichead		= 55,		/* heads if (valid&0x01) */ 
	Icsec		= 56,		/* sectors if (valid&0x01) */ 
	Iccap		= 57,		/* capacity if (valid&0x01) */ 
	Irwm		= 59,		/* read/write multiple */ 
	Ilba0		= 60,		/* LBA size */ 
	Ilba1		= 61,		/* LBA size */ 
	Imwdma		= 63,		/* multiword DMA mode */ 
	Iapiomode	= 64,		/* advanced PIO modes supported */ 
	Iminmwdma	= 65,		/* min. multiword DMA cycle time */ 
	Irecmwdma	= 66,		/* rec. multiword DMA cycle time */ 
	Iminpio		= 67,		/* min. PIO cycle w/o flow control */ 
	Iminiordy	= 68,		/* min. PIO cycle with IORDY */ 
	Ipcktbr		= 71,		/* time from PACKET to bus release */ 
	Iserbsy		= 72,		/* time from SERVICE to !Bsy */ 
	Iqdepth		= 75,		/* max. queue depth */ 
	Imajor		= 80,		/* major version number */ 
	Iminor		= 81,		/* minor version number */ 
	Icmdset0	= 82,		/* command sets supported */ 
	Icmdset1	= 83,		/* command sets supported */ 
	Icmdset2	= 84,		/* command sets supported extension */ 
	Icmdset3	= 85,		/* command sets enabled */ 
	Icmdset4	= 86,		/* command sets enabled */ 
	Icmdset5	= 87,		/* command sets enabled extension */ 
	Iudma		= 88,		/* ultra DMA mode */ 
	Ierase		= 89,		/* time for security erase */ 
	Ieerase		= 90,		/* time for enhanced security erase */ 
	Ipower		= 91,		/* current advanced power management */ 
	Irmsn		= 127,		/* removable status notification */ 
	Istatus		= 128,		/* security status */ 
}; 
                 
typedef struct Ctlr Ctlr; 
typedef struct Drive Drive; 
                 
typedef struct Prd { 
	ulong	pa;			/* Physical Base Address */ 
	int	count; 
} Prd; 
                 
enum { 
	Nprd		= SDmaxio/(64*1024)+2, 
}; 
                 
typedef struct Ctlr { 
	int	cmdport; 
	int	ctlport; 
	int	irq; 
2000/0607    
	int	tbdf; 
2000/0515    
	int	bmiba;			/* bus master interface base address */ 
                 
	Pcidev*	pcidev; 
	void	(*ienable)(Ctlr*); 
	SDev*	sdev; 
                 
	Drive*	drive[2]; 
                 
	Prd*	prdt;			/* physical region descriptor table */ 
                 
	QLock;				/* current command */ 
	Drive*	curdrive; 
2000/0607    
	int	command;		/* last command issued (debugging) */ 
2000/0515    
	Rendez; 
	int	done; 
                 
	Lock;				/* register access */ 
} Ctlr; 
                 
typedef struct Drive { 
	Ctlr*	ctlr; 
                 
	int	dev; 
	ushort	info[256]; 
	int	c;			/* cylinder */ 
	int	h;			/* head */ 
	int	s;			/* sector */ 
	int	sectors;		/* total */ 
	int	secsize;		/* sector size */ 
2000/0607    
                 
2000/0515    
	int	dma;			/* DMA R/W possible */ 
2000/0607    
	int	dmactl; 
	int	rwm;			/* read/write multiple possible */ 
	int	rwmctl; 
                 
2000/0515    
	int	pkt;			/* PACKET device, length of pktcmd */ 
2000/0607    
	uchar	pktcmd[16]; 
	int	pktdma;			/* this PACKET command using dma */ 
2000/0515    
                 
	uchar	sense[18]; 
	uchar	inquiry[48]; 
                 
	QLock;				/* drive access */ 
	int	command;		/* current command */ 
	int	write; 
	uchar*	data; 
	int	dlen; 
	uchar*	limit; 
	int	count;			/* sectors */ 
2000/0607    
	int	block;			/* R/W bytes per block */ 
2000/0515    
	int	status; 
	int	error; 
} Drive; 
                 
static void 
pc87415ienable(Ctlr* ctlr) 
{ 
	Pcidev *p; 
	int x; 
                 
	p = ctlr->pcidev; 
	if(p == nil) 
		return; 
                 
	x = pcicfgr32(p, 0x40); 
	if(ctlr->cmdport == p->mem[0].bar) 
		x &= ~0x00000100; 
	else 
		x &= ~0x00000200; 
	pcicfgw32(p, 0x40, x); 
} 
                 
2000/0607    
static void 
atadumpstate(Drive* drive, uchar* cmd, int lba, int count) 
{ 
	Prd *prd; 
	Pcidev *p; 
	Ctlr *ctlr; 
	int i, bmiba; 
                 
	if(!(DEBUG & DbgSTATE)){ 
		USED(drive, cmd, lba, count); 
		return; 
	} 
                 
	ctlr = drive->ctlr; 
	print("command %2.2uX\n", ctlr->command); 
	print("data %8.8p limit %8.8p dlen %d status %uX error %uX\n", 
		drive->data, drive->limit, drive->dlen, 
		drive->status, drive->error); 
	if(cmd != nil){ 
		print("lba %d -> %d, count %d -> %d (%d)\n", 
			(cmd[2]<<24)|(cmd[3]<<16)|(cmd[4]<<8)|cmd[5], lba, 
			(cmd[7]<<8)|cmd[8], count, drive->count); 
	} 
	if(!(inb(ctlr->ctlport+As) & Bsy)){ 
		for(i = 1; i < 7; i++) 
			print(" 0x%2.2uX", inb(ctlr->cmdport+i)); 
		print(" 0x%2.2uX\n", inb(ctlr->ctlport+As)); 
	} 
	if(drive->command == Cwd || drive->command == Crd){ 
		bmiba = ctlr->bmiba; 
		prd = ctlr->prdt; 
		print("bmicx %2.2uX bmisx %2.2uX prdt %8.8p\n", 
			inb(bmiba+Bmicx), inb(bmiba+Bmisx), prd); 
		for(;;){ 
			print("pa 0x%8.8luX count %8.8uX\n", 
				prd->pa, prd->count); 
			if(prd->count & PrdEOT) 
				break; 
			prd++; 
		} 
	} 
	if(ctlr->pcidev && ctlr->pcidev->vid == 0x8086){ 
		p = ctlr->pcidev; 
		print("0x40: %4.4uX 0x42: %4.4uX", 
			pcicfgr16(p, 0x40), pcicfgr16(p, 0x42)); 
		print("0x48: %2.2uX\n", pcicfgr8(p, 0x48)); 
		print("0x4A: %4.4uX\n", pcicfgr16(p, 0x4A)); 
	} 
} 
                 
2000/0515    
static int 
atadebug(int cmdport, int ctlport, char* fmt, ...) 
{ 
	int i, n; 
	va_list arg; 
	char buf[PRINTSIZE]; 
                 
2000/0607    
	if(!(DEBUG & DbgPROBE)){ 
2000/0515    
		USED(cmdport, ctlport, fmt); 
		return 0; 
	} 
                 
	va_start(arg, fmt); 
	n = doprint(buf, buf+sizeof(buf), fmt, arg) - buf; 
	va_end(arg); 
                 
	if(cmdport){ 
		if(buf[n-1] == '\n') 
			n--; 
		n += snprint(buf+n, PRINTSIZE-n, " ataregs 0x%uX:", 
			cmdport); 
2000/0607    
		for(i = Features; i < Command; i++) 
2000/0515    
			n += snprint(buf+n, PRINTSIZE-n, " 0x%2.2uX", 
				inb(cmdport+i)); 
		if(ctlport) 
			n += snprint(buf+n, PRINTSIZE-n, " 0x%2.2uX", 
				inb(ctlport+As)); 
		n += snprint(buf+n, PRINTSIZE-n, "\n"); 
	} 
	putstrn(buf, n); 
                 
	return n; 
} 
                 
static int 
ataready(int cmdport, int ctlport, int dev, int reset, int ready, int micro) 
{ 
	int as; 
                 
	atadebug(cmdport, ctlport, "ataready: dev %uX reset %uX ready %uX", 
		dev, reset, ready); 
                 
	for(;;){ 
		/* 
		 * Wait for the controller to become not busy and 
		 * possibly for a status bit to become true (usually 
		 * Drdy). Must change to the appropriate device 
2000/0607    
		 * register set if necessary before testing for ready. 
2000/0515    
		 * Always run through the loop at least once so it 
		 * can be used as a test for !Bsy. 
		 */ 
		as = inb(ctlport+As); 
2000/0607    
		if(as & reset) 
			; 
		else if(dev){ 
2000/0515    
			outb(cmdport+Dh, dev); 
			dev = 0; 
		} 
2000/0607    
		else if(ready == 0 || (as & ready)){ 
			atadebug(0, 0, "ataready: %d 0x%2.2uX\n", micro, as); 
			return as; 
2000/0515    
		} 
                 
		if(micro-- <= 0){ 
2000/0607    
			atadebug(0, 0, "ataready: %d 0x%2.2uX\n", micro, as); 
2000/0515    
			break; 
		} 
		microdelay(1); 
	} 
	atadebug(cmdport, ctlport, "ataready: timeout"); 
                 
	return -1; 
} 
                 
static int 
atacsfenabled(Drive* drive, vlong csf) 
{ 
	int cmdset, i, x; 
                 
	for(i = 0; i < 3; i++){ 
		x = (csf>>(16*i)) & 0xFFFF; 
		if(x == 0) 
			continue; 
		cmdset = drive->info[Icmdset3+i]; 
		if(cmdset == 0 || cmdset == 0xFFFF) 
			return 0; 
		return cmdset & x; 
	} 
                 
	return 0; 
} 
                 
2000/0607    
static int 
atasf(int cmdport, int ctlport, int dev, uchar* command) 
2000/0515    
{ 
2000/0607    
	int as, i; 
2000/0515    
                 
2000/0607    
	if(ataready(cmdport, ctlport, dev, Bsy|Drq, Drdy, 108*1000) < 0) 
		return -1; 
                 
	for(i = Features; i < Dh; i++) 
		outb(cmdport+i, command[i]); 
	outb(cmdport+Command, Csf); 
	microdelay(100); 
	as = ataready(cmdport, ctlport, 0, Bsy, Drdy|Df|Err, 109*1000); 
	if(as < 0 || (as & (Df|Err))) 
		return -1; 
	return 0; 
} 
                 
static int 
atarwmmode(Drive* drive, int cmdport, int ctlport, int dev) 
{ 
	int as, maxrwm, rwm; 
                 
	maxrwm = (drive->info[Imaxrwm] & 0xFF); 
	if(maxrwm == 0) 
		return 0; 
                 
	/* 
	 * Sometimes drives come up with the current count set 
	 * to 0; if so, set a suitable value, otherwise believe 
	 * the value in Irwm if the 0x100 bit is set. 
	 */ 
	if(drive->info[Irwm] & 0x100) 
		rwm = (drive->info[Irwm] & 0xFF); 
	else 
		rwm = 0; 
	if(rwm == 0) 
		rwm = maxrwm; 
	if(rwm > 16) 
		rwm = 16; 
	if(ataready(cmdport, ctlport, dev, Bsy|Drq, Drdy, 102*1000) < 0) 
		return 0; 
	outb(cmdport+Count, rwm); 
	outb(cmdport+Command, Csm); 
	microdelay(1); 
	as = ataready(cmdport, ctlport, 0, Bsy, Drdy|Df|Err, 1000); 
	inb(cmdport+Status); 
	if(as < 0 || (as & (Df|Err))) 
		return 0; 
                 
	drive->rwm = rwm; 
                 
	return rwm; 
} 
                 
static int 
atadmamode(Drive* drive) 
{ 
	int dma; 
                 
	/* 
	 * Check if any DMA mode enabled. 
	 * Assumes the BIOS has picked and enabled the best. 
	 * This is completely passive at the moment, no attempt is 
	 * made to ensure the hardware is correctly set up. 
	 */ 
	dma = drive->info[Imwdma] & 0x0707; 
	drive->dma = (dma>>8) & dma; 
	if(drive->dma == 0 && (drive->info[Ivalid] & 0x04)){ 
		dma = drive->info[Iudma] & 0x1F1F; 
		drive->dma = (dma>>8) & dma; 
		if(drive->dma) 
			drive->dma |= 'U'<<16; 
	} 
                 
	return dma; 
} 
                 
static int 
ataidentify(int cmdport, int ctlport, int dev, int pkt, void* info) 
{ 
	int as, command, drdy; 
                 
	if(pkt){ 
		command = Cidpkt; 
		drdy = 0; 
	} 
	else{ 
		command = Cid; 
		drdy = Drdy; 
	} 
	as = ataready(cmdport, ctlport, dev, Bsy|Drq, drdy, 103*1000); 
2000/0515    
	if(as < 0) 
2000/0607    
		return as; 
2000/0515    
	outb(cmdport+Command, command); 
	microdelay(1); 
                 
2000/0607    
	as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 104*1000); 
2000/0515    
	if(as < 0) 
2000/0607    
		return -1; 
	if(as & Err) 
		return as; 
                 
	memset(info, 0, 512); 
	inss(cmdport+Data, info, 256); 
2000/0515    
	inb(cmdport+Status); 
                 
2000/0607    
	if(DEBUG & DbgIDENTIFY){ 
2000/0515    
		int i; 
		ushort *sp; 
                 
2000/0607    
		sp = (ushort*)info; 
2000/0515    
		for(i = 0; i < 256; i++){ 
			if(i && (i%16) == 0) 
				print("\n"); 
			print("%4.4uX ", *sp); 
			sp++; 
		} 
		print("\n"); 
	} 
                 
2000/0607    
	return 0; 
} 
                 
static Drive* 
atadrive(int cmdport, int ctlport, int dev) 
{ 
	ushort *sp; 
	Drive *drive; 
	int as, i, pkt; 
	uchar buf[512], *p; 
                 
	atadebug(0, 0, "identify: port 0x%uX dev 0x%2.2uX\n", cmdport, dev); 
	pkt = 1; 
retry: 
	as = ataidentify(cmdport, ctlport, dev, pkt, buf); 
	if(as < 0) 
		return nil; 
	if(as & Err){ 
		if(pkt == 0) 
			return nil; 
		pkt = 0; 
		goto retry; 
	} 
                 
2000/0515    
	if((drive = malloc(sizeof(Drive))) == nil) 
		return nil; 
	drive->dev = dev; 
	memmove(drive->info, buf, sizeof(drive->info)); 
	drive->sense[0] = 0x70; 
	drive->sense[7] = sizeof(drive->sense)-7; 
                 
	drive->inquiry[2] = 2; 
	drive->inquiry[3] = 2; 
	drive->inquiry[4] = sizeof(drive->inquiry)-4; 
	p = &drive->inquiry[8]; 
	sp = &drive->info[Imodel]; 
	for(i = 0; i < 20; i++){ 
		*p++ = *sp>>8; 
		*p++ = *sp++; 
	} 
2000/0607    
                 
	drive->secsize = 512; 
2000/0515    
	if((drive->info[Iconfig] & 0xC000) == 0x8000){ 
		if(drive->info[Iconfig] & 0x01) 
			drive->pkt = 16; 
		else 
			drive->pkt = 12; 
	} 
	else{ 
		if(drive->info[Ivalid] & 0x0001){ 
			drive->c = drive->info[Ilcyl]; 
			drive->h = drive->info[Ilhead]; 
			drive->s = drive->info[Ilsec]; 
		} 
		else{ 
			drive->c = drive->info[Iccyl]; 
			drive->h = drive->info[Ichead]; 
			drive->s = drive->info[Icsec]; 
		} 
		if(drive->info[Icapabilities] & 0x0200){ 
			drive->sectors = (drive->info[Ilba1]<<16) 
					 |drive->info[Ilba0]; 
			drive->dev |= Lba; 
		} 
		else 
			drive->sectors = drive->c*drive->h*drive->s; 
2000/0607    
		atarwmmode(drive, cmdport, ctlport, dev); 
2000/0515    
	} 
2000/0607    
	atadmamode(drive);	 
2000/0515    
                 
2000/0607    
	if(DEBUG & DbgCONFIG){ 
		print("dev %2.2uX config %4.4uX capabilities %4.4uX", 
			dev, drive->info[Iconfig], drive->info[Icapabilities]); 
		print(" mwdma %4.4uX dma %8.8uX",  
			drive->info[Imwdma], drive->dma); 
		if(drive->info[Ivalid] & 0x04) 
			print(" udma %4.4uX", drive->info[Iudma]); 
		print(" rwm %ud\n", drive->rwm); 
2000/0515    
	} 
                 
	return drive; 
} 
                 
static void 
atasrst(int ctlport) 
{ 
	/* 
	 * Srst is a big stick and may cause problems if further 
	 * commands are tried before the drives become ready again. 
	 * Also, there will be problems here if overlapped commands 
	 * are ever supported. 
	 */ 
	microdelay(5); 
	outb(ctlport+Dc, Srst); 
	microdelay(5); 
	outb(ctlport+Dc, 0); 
	microdelay(2*1000); 
} 
                 
static SDev* 
ataprobe(int cmdport, int ctlport, int irq) 
{ 
	Ctlr* ctlr; 
	SDev *sdev; 
	Drive *drive; 
	int dev, error, rhi, rlo; 
                 
	if(ioalloc(cmdport, 8, 0, "atacmd") < 0) 
		return nil; 
	if(ioalloc(ctlport+As, 1, 0, "atactl") < 0){ 
		iofree(cmdport); 
		return nil; 
	} 
                 
	/* 
	 * Try to detect a floating bus. 
	 * Bsy should be cleared. If not, see if the cylinder registers 
	 * are read/write capable. 
	 * If the master fails, try the slave to catch slave-only 
	 * configurations. 
	 * There's no need to restore the tested registers as they will 
	 * be reset on any detected drives by the Cedd command. 
	 * All this indicates is that there is at least one drive on the 
	 * controller; when the non-existent drive is selected in a 
	 * single-drive configuration the registers of the existing drive 
	 * are often seen, only command execution fails. 
	 */ 
	dev = Dev0; 
	if(inb(ctlport+As) & Bsy){ 
		outb(cmdport+Dh, dev); 
2000/0607    
		microdelay(1); 
2000/0515    
trydev1: 
		atadebug(cmdport, ctlport, "ataprobe bsy"); 
		outb(cmdport+Cyllo, 0xAA); 
		outb(cmdport+Cylhi, 0x55); 
		outb(cmdport+Sector, 0xFF); 
		rlo = inb(cmdport+Cyllo); 
		rhi = inb(cmdport+Cylhi); 
		if(rlo != 0xAA && (rlo == 0xFF || rhi != 0x55)){ 
			if(dev == Dev1){ 
release: 
				iofree(cmdport); 
				iofree(ctlport+As); 
				return nil; 
			} 
			dev = Dev1; 
			if(ataready(cmdport, ctlport, dev, Bsy, 0, 20*1000) < 0) 
				goto trydev1; 
		} 
	} 
                 
	/* 
	 * Disable interrupts on any detected controllers. 
	 */ 
	outb(ctlport+Dc, Nien); 
tryedd1: 
2000/0607    
	if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 105*1000) < 0){ 
2000/0515    
		/* 
		 * There's something there, but it didn't come up clean, 
		 * so try hitting it with a big stick. The timing here is 
		 * wrong but this is a last-ditch effort and it sometimes 
		 * gets some marginal hardware back online. 
		 */ 
		atasrst(ctlport); 
2000/0607    
		if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 106*1000) < 0) 
2000/0515    
			goto release; 
	} 
                 
	/* 
	 * Can only get here if controller is not busy. 
	 * If there are drives Bsy will be set within 400nS, 
	 * must wait 2mS before testing Status. 
	 * Wait for the command to complete (6 seconds max). 
	 */ 
	outb(cmdport+Command, Cedd); 
	delay(2); 
	if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 6*1000*1000) < 0) 
		goto release; 
                 
	/* 
	 * If bit 0 of the error register is set then the selected drive 
	 * exists. This is enough to detect single-drive configurations. 
	 * However, if the master exists there is no way short of executing 
	 * a command to determine if a slave is present. 
	 * It appears possible to get here testing Dev0 although it doesn't 
	 * exist and the EDD won't take, so try again with Dev1. 
	 */ 
	error = inb(cmdport+Error); 
	atadebug(cmdport, ctlport, "ataprobe: dev %uX", dev); 
	if((error & ~0x80) != 0x01){ 
		if(dev == Dev1) 
			goto release; 
		dev = Dev1; 
		goto tryedd1; 
	} 
                 
	/* 
	 * At least one drive is known to exist, try to 
	 * identify it. If that fails, don't bother checking 
	 * any further. 
	 * If the one drive found is Dev0 and the EDD command 
	 * didn't indicate Dev1 doesn't exist, check for it. 
	 */ 
2000/0607    
	if((drive = atadrive(cmdport, ctlport, dev)) == nil) 
2000/0515    
		goto release; 
	if((ctlr = malloc(sizeof(Ctlr))) == nil){ 
		free(drive); 
		goto release; 
	} 
	if((sdev = malloc(sizeof(SDev))) == nil){ 
		free(ctlr); 
		free(drive); 
		goto release; 
	} 
	drive->ctlr = ctlr; 
	if(dev == Dev0){ 
		ctlr->drive[0] = drive; 
		if(!(error & 0x80)){ 
			/* 
			 * Always leave Dh pointing to a valid drive, 
			 * otherwise a subsequent call to ataready on 
			 * this controller may try to test a bogus Status. 
			 * Ataprobe is the only place possibly invalid 
			 * drives should be selected. 
			 */ 
2000/0607    
			drive = atadrive(cmdport, ctlport, Dev1); 
2000/0515    
			if(drive != nil){ 
				drive->ctlr = ctlr; 
				ctlr->drive[1] = drive; 
			} 
2000/0607    
			else{ 
2000/0515    
				outb(cmdport+Dh, Dev0); 
2000/0607    
				microdelay(1); 
			} 
2000/0515    
		} 
	} 
	else 
		ctlr->drive[1] = drive; 
                 
	ctlr->cmdport = cmdport; 
	ctlr->ctlport = ctlport; 
	ctlr->irq = irq; 
2000/0607    
	ctlr->tbdf = BUSUNKNOWN; 
	ctlr->command = Cedd;		/* debugging */ 
2000/0515    
                 
	sdev->ifc = &sdataifc; 
	sdev->ctlr = ctlr; 
	sdev->nunit = 2; 
	ctlr->sdev = sdev; 
                 
	return sdev; 
} 
                 
static int 
atasetsense(Drive* drive, int status, int key, int asc, int ascq) 
{ 
	drive->sense[2] = key; 
	drive->sense[12] = asc; 
	drive->sense[13] = ascq; 
                 
	return status; 
} 
                 
static int 
atamodesense(Drive* drive, uchar* cmd) 
{ 
	int len; 
                 
	/* 
	 * Fake a vendor-specific request with page code 0, 
	 * return the drive info. 
	 */ 
	if((cmd[2] & 0x3F) != 0 && (cmd[2] & 0x3F) != 0x3F) 
		return atasetsense(drive, SDcheck, 0x05, 0x24, 0); 
	len = (cmd[7]<<8)|cmd[8]; 
	if(len == 0) 
		return SDok; 
	if(len < 8+sizeof(drive->info)) 
		return atasetsense(drive, SDcheck, 0x05, 0x1A, 0); 
	if(drive->data == nil || drive->dlen < len) 
		return atasetsense(drive, SDcheck, 0x05, 0x20, 1); 
	memset(drive->data, 0, 8); 
	drive->data[0] = sizeof(drive->info)>>8; 
	drive->data[1] = sizeof(drive->info); 
	memmove(drive->data+8, drive->info, sizeof(drive->info)); 
	drive->data += 8+sizeof(drive->info); 
                 
	return SDok; 
} 
                 
static void 
atanop(Drive* drive, int subcommand) 
{ 
	Ctlr* ctlr; 
	int as, cmdport, ctlport, timeo; 
                 
	/* 
	 * Attempt to abort a command by using NOP. 
	 * In response, the drive is supposed to set Abrt 
	 * in the Error register, set (Drdy|Err) in Status 
	 * and clear Bsy when done. However, some drives 
	 * (e.g. ATAPI Zip) just go Bsy then clear Status 
	 * when done, hence the timeout loop only on Bsy 
	 * and the forced setting of drive->error. 
	 */ 
	ctlr = drive->ctlr; 
	cmdport = ctlr->cmdport; 
	outb(cmdport+Features, subcommand); 
	outb(cmdport+Dh, drive->dev); 
2000/0607    
	ctlr->command = Cnop;		/* debugging */ 
	outb(cmdport+Command, Cnop); 
2000/0515    
                 
	microdelay(1); 
	ctlport = ctlr->ctlport; 
	for(timeo = 0; timeo < 1000; timeo++){ 
		as = inb(ctlport+As); 
		if(!(as & Bsy)) 
			break; 
		microdelay(1); 
	} 
	drive->error |= Abrt; 
} 
                 
static void 
2000/0607    
ataabort(Drive* drive, int dolock) 
2000/0515    
{ 
	/* 
	 * If NOP is available (packet commands) use it otherwise 
	 * must try a software reset. 
	 */ 
2000/0607    
	if(dolock) 
		ilock(drive->ctlr); 
2000/0515    
	if(atacsfenabled(drive, 0x0000000000004000LL)) 
		atanop(drive, 0); 
	else{ 
		atasrst(drive->ctlr->ctlport); 
		drive->error |= Abrt; 
	} 
2000/0607    
	if(dolock) 
		iunlock(drive->ctlr); 
2000/0515    
} 
                 
static int 
atadmasetup(Drive* drive, int len) 
{ 
	Prd *prd; 
	ulong pa; 
	Ctlr *ctlr; 
	int bmiba, bmisx, count; 
                 
	pa = PCIWADDR(drive->data); 
	if(pa & 0x03) 
		return -1; 
	ctlr = drive->ctlr; 
	prd = ctlr->prdt; 
                 
	/* 
	 * Sometimes drives identify themselves as being DMA capable 
	 * although they are not on a busmastering controller. 
	 */ 
	if(prd == nil){ 
2000/0607    
		drive->dmactl = 0; 
2000/0515    
		return -1; 
	} 
                 
	for(;;){ 
		prd->pa = pa; 
		count = 64*1024 - (pa & 0xFFFF); 
		if(count >= len){ 
			prd->count = PrdEOT|(len & 0xFFFF); 
			break; 
		} 
		prd->count = count; 
		len -= count; 
		pa += count; 
		prd++; 
	} 
                 
	bmiba = ctlr->bmiba; 
	outl(bmiba+Bmidtpx, PCIWADDR(ctlr->prdt)); 
	if(drive->write) 
		outb(ctlr->bmiba+Bmicx, 0); 
	else 
		outb(ctlr->bmiba+Bmicx, Rwcon); 
	bmisx = inb(bmiba+Bmisx); 
	outb(bmiba+Bmisx, bmisx|Ideints|Idedmae); 
                 
	return 0; 
} 
                 
static void 
atadmastart(Ctlr* ctlr, int write) 
{ 
	if(write) 
		outb(ctlr->bmiba+Bmicx, Ssbm); 
	else 
		outb(ctlr->bmiba+Bmicx, Rwcon|Ssbm); 
} 
                 
2000/0607    
static int 
2000/0515    
atadmastop(Ctlr* ctlr) 
{ 
	int bmiba; 
                 
	bmiba = ctlr->bmiba; 
	outb(bmiba+Bmicx, inb(bmiba+Bmicx) & ~Ssbm); 
2000/0607    
                 
	return inb(bmiba+Bmisx); 
2000/0515    
} 
                 
static void 
atadmainterrupt(Drive* drive, int count) 
{ 
	Ctlr* ctlr; 
	int bmiba, bmisx; 
                 
	ctlr = drive->ctlr; 
	bmiba = ctlr->bmiba; 
	bmisx = inb(bmiba+Bmisx); 
	switch(bmisx & (Ideints|Idedmae|Bmidea)){ 
	case Bmidea: 
		/* 
		 * Data transfer still in progress, nothing to do 
		 * (this should never happen). 
		 */ 
		return; 
                 
	case Ideints: 
	case Ideints|Bmidea: 
		/* 
		 * Normal termination, tidy up. 
		 */ 
		drive->data += count; 
		break; 
                 
	default: 
		/* 
		 * What's left are error conditions (memory transfer 
		 * problem) and the device is not done but the PRD is 
		 * exhausted. For both cases must somehow tell the 
		 * drive to abort. 
		 */ 
2000/0607    
		ataabort(drive, 0); 
2000/0515    
		break; 
	} 
	atadmastop(ctlr); 
	ctlr->done = 1; 
} 
                 
static int 
atapktiodone(void* arg) 
{ 
	return ((Ctlr*)arg)->done; 
} 
                 
static void 
atapktinterrupt(Drive* drive) 
{ 
	Ctlr* ctlr; 
	int cmdport, len; 
                 
	ctlr = drive->ctlr; 
	cmdport = ctlr->cmdport; 
	switch(inb(cmdport+Ir) & (/*Rel|*/Io|Cd)){ 
	case Cd: 
		outss(cmdport+Data, drive->pktcmd, drive->pkt/2); 
		break; 
                 
	case 0: 
		len = (inb(cmdport+Bytehi)<<8)|inb(cmdport+Bytelo); 
		if(drive->data+len > drive->limit){ 
			atanop(drive, 0); 
			break; 
		} 
		outss(cmdport+Data, drive->data, len/2); 
		drive->data += len; 
		break; 
                 
	case Io: 
		len = (inb(cmdport+Bytehi)<<8)|inb(cmdport+Bytelo); 
		if(drive->data+len > drive->limit){ 
			atanop(drive, 0); 
			break; 
		} 
		inss(cmdport+Data, drive->data, len/2); 
		drive->data += len; 
		break; 
                 
	case Io|Cd: 
		if(drive->pktdma) 
			atadmainterrupt(drive, drive->dlen); 
		else 
			ctlr->done = 1; 
		break; 
	} 
} 
                 
static int 
atapktio(Drive* drive, uchar* cmd, int clen) 
{ 
	Ctlr *ctlr; 
	int as, cmdport, ctlport, len, r, timeo; 
                 
	if(cmd[0] == 0x5A && (cmd[2] & 0x3F) == 0) 
		return atamodesense(drive, cmd); 
                 
	r = SDok; 
                 
	drive->command = Cpkt; 
	memmove(drive->pktcmd, cmd, clen); 
	memset(drive->pktcmd+clen, 0, drive->pkt-clen); 
	drive->limit = drive->data+drive->dlen; 
                 
	ctlr = drive->ctlr; 
	cmdport = ctlr->cmdport; 
	ctlport = ctlr->ctlport; 
                 
	qlock(ctlr); 
                 
2000/0607    
	if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, 0, 107*1000) < 0){ 
2000/0515    
		qunlock(ctlr); 
		return -1; 
	} 
                 
	ilock(ctlr); 
2000/0607    
	if(drive->dlen && drive->dmactl && !atadmasetup(drive, drive->dlen)) 
2000/0515    
		drive->pktdma = Dma; 
	else 
		drive->pktdma = 0; 
                 
	outb(cmdport+Features, drive->pktdma); 
	outb(cmdport+Count, 0); 
	outb(cmdport+Sector, 0); 
	len = 16*drive->secsize; 
	outb(cmdport+Bytelo, len); 
	outb(cmdport+Bytehi, len>>8); 
	outb(cmdport+Dh, drive->dev); 
	ctlr->done = 0; 
2000/0607    
	ctlr->curdrive = drive; 
	ctlr->command = Cpkt;		/* debugging */ 
2000/0515    
	if(drive->pktdma) 
		atadmastart(ctlr, drive->write); 
	outb(cmdport+Command, Cpkt); 
                 
	if((drive->info[Iconfig] & 0x0060) != 0x0020){ 
2000/0607    
		microdelay(1); 
		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Chk, 4*1000); 
2000/0515    
		if(as < 0) 
			r = SDtimeout; 
		else if(as & Chk) 
			r = SDcheck; 
		else 
			atapktinterrupt(drive); 
	} 
	iunlock(ctlr); 
                 
	while(waserror()) 
		; 
	if(!drive->pktdma) 
		sleep(ctlr, atapktiodone, ctlr); 
	else for(timeo = 0; !ctlr->done; timeo++){ 
		tsleep(ctlr, atapktiodone, ctlr, 1000); 
		if(ctlr->done) 
			break; 
		ilock(ctlr); 
		atadmainterrupt(drive, 0); 
		if(!drive->error && timeo > 10){ 
2000/0607    
			ataabort(drive, 0); 
2000/0515    
			atadmastop(ctlr); 
2000/0607    
			drive->dmactl = 0; 
2000/0515    
			drive->error |= Abrt; 
		} 
		if(drive->error){ 
			drive->status |= Chk; 
			ctlr->curdrive = nil; 
		} 
		iunlock(ctlr); 
	} 
	poperror(); 
                 
	qunlock(ctlr); 
                 
	if(drive->status & Chk) 
		r = SDcheck; 
                 
	return r; 
} 
                 
static int 
atageniodone(void* arg) 
{ 
	return ((Ctlr*)arg)->done; 
} 
                 
static int 
atageniostart(Drive* drive, int lba) 
{ 
	Ctlr *ctlr; 
	int as, c, cmdport, ctlport, h, len, s; 
                 
	if(drive->dev & Lba){ 
		c = (lba>>8) & 0xFFFF; 
		h = (lba>>24) & 0x0F; 
		s = lba & 0xFF; 
	} 
	else{ 
		c = lba/(drive->s*drive->h); 
		h = ((lba/drive->s) % drive->h); 
		s = (lba % drive->s) + 1; 
	} 
                 
	ctlr = drive->ctlr; 
	cmdport = ctlr->cmdport; 
	ctlport = ctlr->ctlport; 
2000/0607    
	if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, 0, 101*1000) < 0) 
2000/0515    
		return -1; 
                 
	ilock(ctlr); 
2000/0607    
	if(drive->dmactl && !atadmasetup(drive, drive->count*drive->secsize)){ 
2000/0515    
		if(drive->write) 
			drive->command = Cwd; 
		else 
			drive->command = Crd; 
	} 
2000/0607    
	else if(drive->rwmctl){ 
		drive->block = drive->rwm*drive->secsize; 
		if(drive->write) 
			drive->command = Cwsm; 
		else 
			drive->command = Crsm; 
	} 
	else{ 
		drive->block = drive->secsize; 
		if(drive->write) 
			drive->command = Cws; 
		else 
			drive->command = Crs; 
	} 
	drive->limit = drive->data + drive->count*drive->secsize; 
2000/0515    
                 
	outb(cmdport+Count, drive->count); 
	outb(cmdport+Sector, s); 
	outb(cmdport+Dh, drive->dev|h); 
	outb(cmdport+Cyllo, c); 
	outb(cmdport+Cylhi, c>>8); 
	ctlr->done = 0; 
2000/0607    
	ctlr->curdrive = drive; 
	ctlr->command = drive->command;	/* debugging */ 
2000/0515    
	outb(cmdport+Command, drive->command); 
                 
	switch(drive->command){ 
	case Cws: 
	case Cwsm: 
2000/0607    
		microdelay(1); 
		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 1000); 
2000/0515    
		if(as < 0 || (as & Err)){ 
			iunlock(ctlr); 
			return -1; 
		} 
		len = drive->block; 
		if(drive->data+len > drive->limit) 
			len = drive->limit-drive->data; 
		outss(cmdport+Data, drive->data, len/2); 
		break; 
                 
	case Crd: 
	case Cwd: 
		atadmastart(ctlr, drive->write); 
		break; 
	} 
	iunlock(ctlr); 
                 
	return 0; 
} 
                 
static int 
2000/0607    
atagenioretry(Drive* drive) 
{ 
	if(drive->dmactl) 
		drive->dmactl = 0; 
	else if(drive->rwmctl) 
		drive->rwmctl = 0; 
	else 
		return atasetsense(drive, SDcheck, 4, 8, drive->error); 
                 
	return SDretry; 
} 
                 
static int 
2000/0515    
atagenio(Drive* drive, uchar* cmd, int) 
{ 
	uchar *p; 
	Ctlr *ctlr; 
	int count, lba, len; 
                 
	/* 
	 * Map SCSI commands into ATA commands for discs. 
	 * Fail any command with a LUN except INQUIRY which 
	 * will return 'logical unit not supported'. 
	 */ 
	if((cmd[1]>>5) && cmd[0] != 0x12) 
		return atasetsense(drive, SDcheck, 0x05, 0x25, 0); 
                 
	switch(cmd[0]){ 
	default: 
		return atasetsense(drive, SDcheck, 0x05, 0x20, 0); 
                 
	case 0x00:			/* test unit ready */ 
		return SDok; 
                 
	case 0x03:			/* request sense */ 
		if(cmd[4] < sizeof(drive->sense)) 
			len = cmd[4]; 
		else 
			len = sizeof(drive->sense); 
		if(drive->data && drive->dlen >= len){ 
			memmove(drive->data, drive->sense, len); 
			drive->data += len; 
		} 
		return SDok; 
                 
	case 0x12:			/* inquiry */ 
		if(cmd[4] < sizeof(drive->inquiry)) 
			len = cmd[4]; 
		else 
			len = sizeof(drive->inquiry); 
		if(drive->data && drive->dlen >= len){ 
			memmove(drive->data, drive->inquiry, len); 
			drive->data += len; 
		} 
		return SDok; 
                 
	case 0x1B:			/* start/stop unit */ 
		/* 
		 * NOP for now, can use the power management feature 
		 * set later. 
		 */ 
		return SDok; 
                 
	case 0x25:			/* read capacity */ 
		if((cmd[1] & 0x01) || cmd[2] || cmd[3]) 
			return atasetsense(drive, SDcheck, 0x05, 0x24, 0); 
		if(drive->data == nil || drive->dlen < 8) 
			return atasetsense(drive, SDcheck, 0x05, 0x20, 1); 
		/* 
		 * Read capacity returns the LBA of the last sector. 
		 */ 
		len = drive->sectors-1; 
		p = drive->data; 
		*p++ = len>>24; 
		*p++ = len>>16; 
		*p++ = len>>8; 
		*p++ = len; 
		len = drive->secsize; 
		*p++ = len>>24; 
		*p++ = len>>16; 
		*p++ = len>>8; 
		*p = len; 
		drive->data += 8; 
		return SDok; 
                 
	case 0x28:			/* read */ 
	case 0x2A:			/* write */ 
		break; 
                 
	case 0x5A: 
		return atamodesense(drive, cmd); 
	} 
                 
	ctlr = drive->ctlr; 
	lba = (cmd[2]<<24)|(cmd[3]<<16)|(cmd[4]<<8)|cmd[5]; 
	count = (cmd[7]<<8)|cmd[8]; 
	if(drive->data == nil) 
		return SDok; 
	if(drive->dlen < count*drive->secsize) 
		count = drive->dlen/drive->secsize; 
	qlock(ctlr); 
	while(count){ 
		if(count > 256) 
			drive->count = 256; 
		else 
			drive->count = count; 
		if(atageniostart(drive, lba)){ 
2000/0607    
			ilock(ctlr); 
			atanop(drive, 0); 
			iunlock(ctlr); 
2000/0515    
			qunlock(ctlr); 
2000/0607    
			return atagenioretry(drive); 
2000/0515    
		} 
                 
		while(waserror()) 
			; 
		tsleep(ctlr, atageniodone, ctlr, 10*1000); 
		poperror(); 
		if(!ctlr->done){ 
			/* 
			 * What should the above timeout be? In 
			 * standby and sleep modes it could take as 
			 * long as 30 seconds for a drive to respond. 
			 * Very hard to get out of this cleanly. 
			 */ 
2000/0607    
			atadumpstate(drive, cmd, lba, count); 
			ataabort(drive, 1); 
			return atagenioretry(drive); 
2000/0515    
		} 
                 
		if(drive->status & Err){ 
			qunlock(ctlr); 
			return atasetsense(drive, SDcheck, 4, 8, drive->error); 
		} 
		count -= drive->count; 
2000/0607    
		lba += drive->count; 
2000/0515    
	} 
	qunlock(ctlr); 
                 
	return SDok; 
} 
                 
static int 
atario(SDreq* r) 
{ 
	Ctlr *ctlr; 
	Drive *drive; 
	SDunit *unit; 
	uchar cmd10[10], *cmdp, *p; 
	int clen, reqstatus, status; 
                 
	unit = r->unit; 
	if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil){ 
		r->status = SDtimeout; 
		return SDtimeout; 
	} 
	drive = ctlr->drive[unit->subno]; 
                 
	/* 
	 * Most SCSI commands can be passed unchanged except for 
	 * the padding on the end. The few which require munging 
	 * are not used internally. Mode select/sense(6) could be 
	 * converted to the 10-byte form but it's not worth the 
	 * effort. Read/write(6) are easy. 
	 */ 
	switch(r->cmd[0]){ 
	case 0x08:			/* read */ 
	case 0x0A:			/* write */ 
		cmdp = cmd10; 
		memset(cmdp, 0, sizeof(cmd10)); 
		cmdp[0] = r->cmd[0]|0x20; 
		cmdp[1] = r->cmd[1] & 0xE0; 
		cmdp[5] = r->cmd[3]; 
		cmdp[4] = r->cmd[2]; 
		cmdp[3] = r->cmd[1] & 0x0F; 
		cmdp[8] = r->cmd[4]; 
		clen = sizeof(cmd10); 
		break; 
                 
	default: 
		cmdp = r->cmd; 
		clen = r->clen; 
		break; 
	} 
                 
	qlock(drive); 
2000/0607    
retry: 
2000/0515    
	drive->write = r->write; 
	drive->data = r->data; 
	drive->dlen = r->dlen; 
                 
	drive->status = 0; 
	drive->error = 0; 
	if(drive->pkt) 
		status = atapktio(drive, cmdp, clen); 
	else 
		status = atagenio(drive, cmdp, clen); 
2000/0607    
	if(status == SDretry){ 
		if(DbgDEBUG) 
			print("%s: retry: dma %8.8uX rwm %4.4uX\n", 
				unit->name, drive->dmactl, drive->rwmctl); 
		goto retry; 
	} 
2000/0515    
	if(status == SDok){ 
		atasetsense(drive, SDok, 0, 0, 0); 
		if(drive->data){ 
			p = r->data; 
			r->rlen = drive->data - p; 
		} 
		else 
			r->rlen = 0; 
	} 
	else if(status == SDcheck && !(r->flags & SDnosense)){ 
		drive->write = 0; 
		memset(cmd10, 0, sizeof(cmd10)); 
		cmd10[0] = 0x03; 
		cmd10[1] = r->lun<<5; 
		cmd10[4] = sizeof(r->sense)-1; 
		drive->data = r->sense; 
		drive->dlen = sizeof(r->sense)-1; 
		drive->status = 0; 
		drive->error = 0; 
		if(drive->pkt) 
			reqstatus = atapktio(drive, cmd10, 6); 
		else 
			reqstatus = atagenio(drive, cmd10, 6); 
		if(reqstatus == SDok){ 
			r->flags |= SDvalidsense; 
			atasetsense(drive, SDok, 0, 0, 0); 
		} 
	} 
	qunlock(drive); 
	r->status = status; 
	if(status != SDok) 
		return status; 
                 
	/* 
	 * Fix up any results. 
	 * Many ATAPI CD-ROMs ignore the LUN field completely and 
	 * return valid INQUIRY data. Patch the response to indicate 
	 * 'logical unit not supported' if the LUN is non-zero. 
	 */ 
	switch(cmdp[0]){ 
	case 0x12:			/* inquiry */ 
		if((p = r->data) == nil) 
			break; 
		if((cmdp[1]>>5) && (!drive->pkt || (p[0] & 0x1F) == 0x05)) 
			p[0] = 0x7F; 
		/*FALLTHROUGH*/ 
	default: 
		break; 
	} 
                 
	return SDok; 
} 
                 
static void 
atainterrupt(Ureg*, void* arg) 
{ 
	Ctlr *ctlr; 
	Drive *drive; 
	int cmdport, len, status; 
                 
	ctlr = arg; 
2000/0607    
                 
2000/0515    
	ilock(ctlr); 
	if(inb(ctlr->ctlport+As) & Bsy){ 
		iunlock(ctlr); 
2000/0607    
		if(DEBUG & DbgDEBUG) 
			print("IBsy+"); 
2000/0515    
		return; 
	} 
	cmdport = ctlr->cmdport; 
	status = inb(cmdport+Status); 
	if((drive = ctlr->curdrive) == nil){ 
		iunlock(ctlr); 
2000/0607    
		if((DEBUG & DbgDEBUG) && ctlr->command != Cedd) 
			print("Inil%2.2uX+", ctlr->command); 
2000/0515    
		return; 
	} 
                 
	if(status & Err) 
		drive->error = inb(cmdport+Error); 
	else switch(drive->command){ 
	default: 
		drive->error = Abrt; 
		break; 
                 
	case Crs: 
	case Crsm: 
		if(!(status & Drq)){ 
			drive->error = Abrt; 
			break; 
		} 
		len = drive->block; 
		if(drive->data+len > drive->limit) 
			len = drive->limit-drive->data; 
		inss(cmdport+Data, drive->data, len/2); 
		drive->data += len; 
		if(drive->data >= drive->limit) 
			ctlr->done = 1; 
		break; 
                 
	case Cws: 
	case Cwsm: 
		len = drive->block; 
		if(drive->data+len > drive->limit) 
			len = drive->limit-drive->data; 
		drive->data += len; 
		if(drive->data >= drive->limit){ 
			ctlr->done = 1; 
			break; 
		} 
		if(!(status & Drq)){ 
			drive->error = Abrt; 
			break; 
		} 
		len = drive->block; 
		if(drive->data+len > drive->limit) 
			len = drive->limit-drive->data; 
		outss(cmdport+Data, drive->data, len/2); 
		break; 
                 
	case Cpkt: 
		atapktinterrupt(drive); 
		break; 
                 
	case Crd: 
	case Cwd: 
		atadmainterrupt(drive, drive->count*drive->secsize); 
		break; 
	} 
	iunlock(ctlr); 
                 
	if(drive->error){ 
		status |= Err; 
		ctlr->done = 1; 
	} 
                 
	if(ctlr->done){ 
		ctlr->curdrive = nil; 
		drive->status = status; 
		wakeup(ctlr); 
	} 
} 
                 
static SDev* 
atapnp(void) 
{ 
	Ctlr *ctlr; 
	Pcidev *p; 
2000/0607    
	int channel, ispc87415, pi, r; 
2000/0515    
	SDev *legacy[2], *sdev, *head, *tail; 
                 
	legacy[0] = legacy[1] = head = tail = nil; 
	if(sdev = ataprobe(0x1F0, 0x3F4, IrqATA0)){ 
		head = tail = sdev; 
		legacy[0] = sdev; 
	} 
	if(sdev = ataprobe(0x170, 0x374, IrqATA1)){ 
		if(head != nil) 
			tail->next = sdev; 
		else 
			head = sdev; 
		tail = sdev; 
		legacy[1] = sdev; 
	} 
                 
	p = nil; 
	while(p = pcimatch(p, 0, 0)){ 
		/* 
		 * Look for devices with the correct class and sub-class 
		 * code and known device and vendor ID; add native-mode 
		 * channels to the list to be probed, save info for the 
		 * compatibility mode channels. 
		 * Note that the legacy devices should not be considered 
		 * PCI devices by the interrupt controller. 
		 * For both native and legacy, save info for busmastering 
		 * if capable. 
		 * Promise Ultra ATA/66 (PDC20262) appears to 
		 * 1) give a sub-class of 'other mass storage controller' 
		 *    instead of 'IDE controller', regardless of whether it's 
		 *    the only controller or not; 
		 * 2) put 0 in the programming interface byte (probably 
		 *    as a consequence of 1) above). 
		 */ 
		if(p->ccrb != 0x01 || (p->ccru != 0x01 && p->ccru != 0x80)) 
			continue; 
		pi = p->ccrp; 
		ispc87415 = 0; 
                 
		switch((p->did<<16)|p->vid){ 
		default: 
			continue; 
                 
		case (0x0002<<16)|0x100B:	/* NS PC87415 */ 
			/* 
			 * Disable interrupts on both channels until 
			 * after they are probed for drives. 
			 * This must be called before interrupts are 
			 * enabled because the IRQ may be shared. 
			 */ 
			ispc87415 = 1; 
			pcicfgw32(p, 0x40, 0x00000300); 
			break; 
2000/0607    
		case (0x1000<<16)|0x1042:	/* PC-Tech RZ1000 */ 
			/* 
			 * Turn off prefetch. Overkill, but cheap. 
			 */ 
			r = pcicfgr32(p, 0x40); 
			r &= ~0x2000; 
			pcicfgw32(p, 0x40, r); 
			break; 
2000/0515    
		case (0x4D38<<16)|0x105A:	/* Promise PDC20262 */ 
			pi = 0x85; 
			break; 
2000/0607    
		case (0x0640<<16)|0x1095:	/* CMD 640B */ 
			/* 
			 * Bugfix code here... 
			 */ 
			break; 
		case (0x0646<<16)|0x1095:	/* CMD 646 */ 
		case (0x0571<<16)|0x1106:	/* VIA 82C686 */ 
2000/0515    
		case (0x1230<<16)|0x8086:	/* 82371FB (PIIX) */ 
		case (0x7010<<16)|0x8086:	/* 82371SB (PIIX3) */ 
		case (0x7111<<16)|0x8086:	/* 82371[AE]B (PIIX4[E]) */ 
			break; 
		} 
                 
		for(channel = 0; channel < 2; channel++){ 
			if(pi & (1<<(2*channel))){ 
				sdev = ataprobe(p->mem[0+2*channel].bar & ~0x01, 
						p->mem[1+2*channel].bar & ~0x01, 
						p->intl); 
				if(sdev == nil) 
					continue; 
                 
				ctlr = sdev->ctlr; 
				if(ispc87415) 
					ctlr->ienable = pc87415ienable; 
                 
				if(head != nil) 
					tail->next = sdev; 
				else 
					head = sdev; 
				tail = sdev; 
2000/0607    
				ctlr->tbdf = p->tbdf; 
2000/0515    
			} 
			else if((sdev = legacy[channel]) == nil) 
				continue; 
			else 
				ctlr = sdev->ctlr; 
                 
2000/0607    
			ctlr->pcidev = p; 
2000/0515    
			if(!(pi & 0x80)) 
				continue; 
			ctlr->bmiba = (p->mem[4].bar & ~0x01) + channel*8; 
		} 
	} 
                 
	return head; 
} 
                 
static SDev* 
atalegacy(int port, int irq) 
{ 
	return ataprobe(port, port+0x204, irq); 
} 
                 
static SDev* 
ataid(SDev* sdev) 
{ 
	int i; 
	Ctlr *ctlr; 
                 
	/* 
	 * Legacy controllers are always 'C' and 'D' and if 
	 * they exist and have drives will be first in the list. 
	 * If there are no active legacy controllers, native 
	 * controllers start at 'C'. 
	 */ 
	if(sdev == nil) 
		return nil; 
	ctlr = sdev->ctlr; 
	if(ctlr->cmdport == 0x1F0 || ctlr->cmdport == 0x170) 
		i = 2; 
	else 
		i = 0; 
	while(sdev){ 
		if(sdev->ifc == &sdataifc){ 
			ctlr = sdev->ctlr; 
			if(ctlr->cmdport == 0x1F0) 
				sdev->idno = 'C'; 
			else if(ctlr->cmdport == 0x170) 
				sdev->idno = 'D'; 
			else{ 
				sdev->idno = 'C'+i; 
				i++; 
			} 
			snprint(sdev->name, NAMELEN, "sd%c", sdev->idno); 
		} 
		sdev = sdev->next; 
	} 
                 
	return nil; 
} 
                 
static int 
ataenable(SDev* sdev) 
{ 
	Ctlr *ctlr; 
	char name[NAMELEN]; 
                 
	ctlr = sdev->ctlr; 
                 
	if(ctlr->bmiba){ 
		if(ctlr->pcidev != nil) 
			pcisetbme(ctlr->pcidev); 
		ctlr->prdt = xspanalloc(Nprd*sizeof(Prd), 4, 4*1024); 
	} 
	snprint(name, NAMELEN, "%s (%s)", sdev->name, sdev->ifc->name); 
2000/0607    
	intrenable(ctlr->irq, atainterrupt, ctlr, ctlr->tbdf, name); 
2000/0515    
	outb(ctlr->ctlport+Dc, 0); 
	if(ctlr->ienable) 
		ctlr->ienable(ctlr); 
                 
	return 1; 
} 
                 
static int 
atarctl(SDunit* unit, char* p, int l) 
{ 
	int n; 
	Ctlr *ctlr; 
	Drive *drive; 
                 
	if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil) 
		return 0; 
	drive = ctlr->drive[unit->subno]; 
                 
2000/0607    
	qlock(drive); 
	n = snprint(p, l, "config %4.4uX capabilities %4.4uX", 
		drive->info[Iconfig], drive->info[Icapabilities]); 
	if(drive->dma) 
		n += snprint(p+n, l-n, " dma %8.8uX dmactl %8.8uX", 
			drive->dma, drive->dmactl); 
	if(drive->rwm) 
		n += snprint(p+n, l-n, " rwm %ud rwmctl %ud", 
			drive->rwm, drive->rwmctl); 
2000/0515    
	n += snprint(p+n, l-n, "\n"); 
2000/0607    
	if(!unit->changed && unit->sectors){ 
		n += snprint(p+n, l-n, "geometry %ld %ld", 
			unit->sectors, unit->secsize); 
		if(drive->pkt == 0) 
			n += snprint(p+n, l-n, " %d %d %d", 
				drive->c, drive->h, drive->s); 
		n += snprint(p+n, l-n, "\n"); 
	} 
	qunlock(drive); 
2000/0515    
                 
	return n; 
} 
                 
2000/0607    
static int 
atawctl(SDunit* unit, Cmdbuf* cb) 
{ 
	Ctlr *ctlr; 
	Drive *drive; 
                 
	if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil) 
		return 0; 
	drive = ctlr->drive[unit->subno]; 
                 
	qlock(drive); 
	if(waserror()){ 
		qunlock(drive); 
		nexterror(); 
	} 
                 
	/* 
	 * Dma and rwm control is passive at the moment, 
	 * i.e. it is assumed that the hardware is set up 
	 * correctly already either by the BIOS or when 
	 * the drive was initially identified. 
	 */ 
	if(strcmp(cb->f[0], "dma") == 0){ 
		if(cb->nf != 2 || drive->dma == 0) 
			error(Ebadctl); 
		if(strcmp(cb->f[1], "on") == 0) 
			drive->dmactl = drive->dma; 
		else if(strcmp(cb->f[1], "off") == 0) 
			drive->dmactl = 0; 
		else 
			error(Ebadctl); 
	} 
	else if(strcmp(cb->f[0], "rwm") == 0){ 
		if(cb->nf != 2 || drive->rwm == 0) 
			error(Ebadctl); 
		if(strcmp(cb->f[1], "on") == 0) 
			drive->rwmctl = drive->rwm; 
		else if(strcmp(cb->f[1], "off") == 0) 
			drive->rwmctl = 0; 
		else 
			error(Ebadctl); 
	} 
	else 
		error(Ebadctl); 
	qunlock(drive); 
	poperror(); 
                 
	return 0; 
} 
                 
2000/0515    
SDifc sdataifc = { 
	"ata",				/* name */ 
                 
	atapnp,				/* pnp */ 
	atalegacy,			/* legacy */ 
	ataid,				/* id */ 
	ataenable,			/* enable */ 
	nil,				/* disable */ 
                 
	scsiverify,			/* verify */ 
	scsionline,			/* online */ 
	atario,				/* rio */ 
	atarctl,			/* rctl */ 
2000/0607    
	atawctl,			/* wctl */ 
2000/0515    
                 
	scsibio,			/* bio */ 
}; 
2000/0706    
#include "../pc/sdata.c" 


source code copyright © 1990-2005 Lucent Technologies; see license
Plan 9 distribution
comments to russ cox (rsc@swtch.com)