~kris/9p

9hist

e60967e8e7a82404ab9c2a0f5c5dab40ec217e7d — David du Colombier 26 years ago 92fc4b8
Plan 9 from Bell Labs 2000-07-06
M alphapc/sd.h => alphapc/sd.h +12 -5
@@ 4,15 4,20 @@
typedef struct SDev SDev;
typedef struct SDifc SDifc;
typedef struct SDpart SDpart;
typedef struct SDperm SDperm;
typedef struct SDreq SDreq;
typedef struct SDunit SDunit;

typedef struct SDpart {
	ulong	start;
	ulong	end;
typedef struct SDperm {
	char	name[NAMELEN];
	char	user[NAMELEN];
	ulong	perm;
} SDperm;

typedef struct SDpart {
	ulong	start;
	ulong	end;
	SDperm;
	int	valid;
	ulong	vers;
} SDpart;


@@ 21,18 26,20 @@ typedef struct SDunit {
	SDev*	dev;
	int	subno;
	uchar	inquiry[256];		/* format follows SCSI spec */
	char	name[NAMELEN];
	SDperm;
	Rendez	rendez;

	QLock	ctl;
	ulong	sectors;
	ulong	secsize;
	SDpart*	part;		/* either nil or points at array of size SDnpart */
	SDpart*	part;			/* nil or array of size SDnpart */
	ulong	vers;
	SDperm	ctlperm;

	Lock	rawinuse;		/* really just a test-and-set */
	int	state;
	SDreq*	req;
	SDperm	rawperm;
} SDunit;

typedef struct SDev {

M alphapc/sdata.c => alphapc/sdata.c +1 -1841
@@ 1,1841 1,1 @@
#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;

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)

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 */
	Csm		= 0xC6,		/* Set Multiple */
	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;
	int	tbdf;
	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;
	int	command;		/* last command issued (debugging) */
	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 */

	int	dma;			/* DMA R/W possible */
	int	dmactl;
	int	rwm;			/* read/write multiple possible */
	int	rwmctl;

	int	pkt;			/* PACKET device, length of pktcmd */
	uchar	pktcmd[16];
	int	pktdma;			/* this PACKET command using dma */

	uchar	sense[18];
	uchar	inquiry[48];

	QLock;				/* drive access */
	int	command;		/* current command */
	int	write;
	uchar*	data;
	int	dlen;
	uchar*	limit;
	int	count;			/* sectors */
	int	block;			/* R/W bytes per block */
	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);
}

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));
	}
}

static int
atadebug(int cmdport, int ctlport, char* fmt, ...)
{
	int i, n;
	va_list arg;
	char buf[PRINTSIZE];

	if(!(DEBUG & DbgPROBE)){
		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);
		for(i = Features; i < Command; i++)
			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
		 * register set if necessary before testing for ready.
		 * Always run through the loop at least once so it
		 * can be used as a test for !Bsy.
		 */
		as = inb(ctlport+As);
		if(as & reset)
			;
		else if(dev){
			outb(cmdport+Dh, dev);
			dev = 0;
		}
		else if(ready == 0 || (as & ready)){
			atadebug(0, 0, "ataready: %d 0x%2.2uX\n", micro, as);
			return as;
		}

		if(micro-- <= 0){
			atadebug(0, 0, "ataready: %d 0x%2.2uX\n", micro, as);
			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;
}

static int
atasf(int cmdport, int ctlport, int dev, uchar* command)
{
	int as, i;

	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);
	if(as < 0)
		return as;
	outb(cmdport+Command, command);
	microdelay(1);

	as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 104*1000);
	if(as < 0)
		return -1;
	if(as & Err)
		return as;

	memset(info, 0, 512);
	inss(cmdport+Data, info, 256);
	inb(cmdport+Status);

	if(DEBUG & DbgIDENTIFY){
		int i;
		ushort *sp;

		sp = (ushort*)info;
		for(i = 0; i < 256; i++){
			if(i && (i%16) == 0)
				print("\n");
			print("%4.4uX ", *sp);
			sp++;
		}
		print("\n");
	}

	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;
	}

	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++;
	}

	drive->secsize = 512;
	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;
		atarwmmode(drive, cmdport, ctlport, dev);
	}
	atadmamode(drive);	

	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);
	}

	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);
		microdelay(1);
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:
	if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 105*1000) < 0){
		/*
		 * 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);
		if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 106*1000) < 0)
			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.
	 */
	if((drive = atadrive(cmdport, ctlport, dev)) == nil)
		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.
			 */
			drive = atadrive(cmdport, ctlport, Dev1);
			if(drive != nil){
				drive->ctlr = ctlr;
				ctlr->drive[1] = drive;
			}
			else{
				outb(cmdport+Dh, Dev0);
				microdelay(1);
			}
		}
	}
	else
		ctlr->drive[1] = drive;

	ctlr->cmdport = cmdport;
	ctlr->ctlport = ctlport;
	ctlr->irq = irq;
	ctlr->tbdf = BUSUNKNOWN;
	ctlr->command = Cedd;		/* debugging */

	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);
	ctlr->command = Cnop;		/* debugging */
	outb(cmdport+Command, Cnop);

	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
ataabort(Drive* drive, int dolock)
{
	/*
	 * If NOP is available (packet commands) use it otherwise
	 * must try a software reset.
	 */
	if(dolock)
		ilock(drive->ctlr);
	if(atacsfenabled(drive, 0x0000000000004000LL))
		atanop(drive, 0);
	else{
		atasrst(drive->ctlr->ctlport);
		drive->error |= Abrt;
	}
	if(dolock)
		iunlock(drive->ctlr);
}

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){
		drive->dmactl = 0;
		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);
}

static int
atadmastop(Ctlr* ctlr)
{
	int bmiba;

	bmiba = ctlr->bmiba;
	outb(bmiba+Bmicx, inb(bmiba+Bmicx) & ~Ssbm);

	return inb(bmiba+Bmisx);
}

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.
		 */
		ataabort(drive, 0);
		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);

	if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, 0, 107*1000) < 0){
		qunlock(ctlr);
		return -1;
	}

	ilock(ctlr);
	if(drive->dlen && drive->dmactl && !atadmasetup(drive, drive->dlen))
		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;
	ctlr->curdrive = drive;
	ctlr->command = Cpkt;		/* debugging */
	if(drive->pktdma)
		atadmastart(ctlr, drive->write);
	outb(cmdport+Command, Cpkt);

	if((drive->info[Iconfig] & 0x0060) != 0x0020){
		microdelay(1);
		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Chk, 4*1000);
		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){
			ataabort(drive, 0);
			atadmastop(ctlr);
			drive->dmactl = 0;
			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;
	if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, 0, 101*1000) < 0)
		return -1;

	ilock(ctlr);
	if(drive->dmactl && !atadmasetup(drive, drive->count*drive->secsize)){
		if(drive->write)
			drive->command = Cwd;
		else
			drive->command = Crd;
	}
	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;

	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;
	ctlr->curdrive = drive;
	ctlr->command = drive->command;	/* debugging */
	outb(cmdport+Command, drive->command);

	switch(drive->command){
	case Cws:
	case Cwsm:
		microdelay(1);
		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 1000);
		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
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
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)){
			ilock(ctlr);
			atanop(drive, 0);
			iunlock(ctlr);
			qunlock(ctlr);
			return atagenioretry(drive);
		}

		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.
			 */
			atadumpstate(drive, cmd, lba, count);
			ataabort(drive, 1);
			return atagenioretry(drive);
		}

		if(drive->status & Err){
			qunlock(ctlr);
			return atasetsense(drive, SDcheck, 4, 8, drive->error);
		}
		count -= drive->count;
		lba += drive->count;
	}
	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);
retry:
	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);
	if(status == SDretry){
		if(DbgDEBUG)
			print("%s: retry: dma %8.8uX rwm %4.4uX\n",
				unit->name, drive->dmactl, drive->rwmctl);
		goto retry;
	}
	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;

	ilock(ctlr);
	if(inb(ctlr->ctlport+As) & Bsy){
		iunlock(ctlr);
		if(DEBUG & DbgDEBUG)
			print("IBsy+");
		return;
	}
	cmdport = ctlr->cmdport;
	status = inb(cmdport+Status);
	if((drive = ctlr->curdrive) == nil){
		iunlock(ctlr);
		if((DEBUG & DbgDEBUG) && ctlr->command != Cedd)
			print("Inil%2.2uX+", ctlr->command);
		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;
	int channel, ispc87415, pi, r;
	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;
		case (0x1000<<16)|0x1042:	/* PC-Tech RZ1000 */
			/*
			 * Turn off prefetch. Overkill, but cheap.
			 */
			r = pcicfgr32(p, 0x40);
			r &= ~0x2000;
			pcicfgw32(p, 0x40, r);
			break;
		case (0x4D38<<16)|0x105A:	/* Promise PDC20262 */
			pi = 0x85;
			break;
		case (0x0640<<16)|0x1095:	/* CMD 640B */
			/*
			 * Bugfix code here...
			 */
			break;
		case (0x0646<<16)|0x1095:	/* CMD 646 */
		case (0x0571<<16)|0x1106:	/* VIA 82C686 */
		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;
				ctlr->tbdf = p->tbdf;
			}
			else if((sdev = legacy[channel]) == nil)
				continue;
			else
				ctlr = sdev->ctlr;

			ctlr->pcidev = p;
			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);
	intrenable(ctlr->irq, atainterrupt, ctlr, ctlr->tbdf, name);
	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];

	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);
	n += snprint(p+n, l-n, "\n");
	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);

	return n;
}

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;
}

SDifc sdataifc = {
	"ata",				/* name */

	atapnp,				/* pnp */
	atalegacy,			/* legacy */
	ataid,				/* id */
	ataenable,			/* enable */
	nil,				/* disable */

	scsiverify,			/* verify */
	scsionline,			/* online */
	atario,				/* rio */
	atarctl,			/* rctl */
	atawctl,			/* wctl */

	scsibio,			/* bio */
};
#include "../pc/sdata.c"

M alphapc/sdscsi.c => alphapc/sdscsi.c +1 -386
@@ 1,386 1,1 @@
#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"

static int
scsitest(SDreq* r)
{
	r->write = 0;
	memset(r->cmd, 0, sizeof(r->cmd));
	r->cmd[1] = r->lun<<5;
	r->clen = 6;
	r->data = nil;
	r->dlen = 0;
	r->flags = 0;

	r->status = ~0;

	return r->unit->dev->ifc->rio(r);
}

int
scsiverify(SDunit* unit)
{
	SDreq *r;
	int i, status;
	uchar *inquiry;

	if((r = malloc(sizeof(SDreq))) == nil)
		return 0;
	if((inquiry = sdmalloc(sizeof(unit->inquiry))) == nil){
		free(r);
		return 0;
	}
	r->unit = unit;
	r->lun = 0;		/* ??? */

	memset(unit->inquiry, 0, sizeof(unit->inquiry));
	r->write = 0;
	r->cmd[0] = 0x12;
	r->cmd[1] = r->lun<<5;
	r->cmd[4] = sizeof(unit->inquiry)-1;
	r->clen = 6;
	r->data = inquiry;
	r->dlen = sizeof(unit->inquiry)-1;
	r->flags = 0;

	r->status = ~0;
	if(unit->dev->ifc->rio(r) != SDok){
		free(r);
		return 0;
	}
	memmove(unit->inquiry, inquiry, r->dlen);
	free(inquiry); 

	SET(status);
	for(i = 0; i < 3; i++){
		while((status = scsitest(r)) == SDbusy)
			;
		if(status == SDok || status != SDcheck)
			break;
		if(!(r->flags & SDvalidsense))
			break;
		if((r->sense[2] & 0x0F) != 0x02)
			continue;
		/*
		 * Unit is 'not ready'.
		 * If it needs an initialising command, set status
		 * so it will be spun-up below.
		 * If there's no medium, that's OK too, but don't
		 * try to spin it up.
		 */
		if(r->sense[12] == 0x04 && r->sense[13] == 0x02){
			status = SDok;
			break;
		}
		if(r->sense[12] == 0x3A)
			break;
	}

	if(status == SDok){
		/*
		 * Try to ensure a direct-access device is spinning.
		 * Don't wait for completion, ignore the result.
		 */
		if((unit->inquiry[0] & 0x1F) == 0){
			memset(r->cmd, 0, sizeof(r->cmd));
			r->write = 0;
			r->cmd[0] = 0x1B;
			r->cmd[1] = (r->lun<<5)|0x01;
			r->cmd[4] = 1;
			r->clen = 6;
			r->data = nil;
			r->dlen = 0;
			r->flags = 0;

			r->status = ~0;
			unit->dev->ifc->rio(r);
		}
	}
	free(r);

	if(status == SDok || status == SDcheck)
		return 1;
	return 0;
}

static int
scsirio(SDreq* r)
{
	/*
	 * Perform an I/O request, returning
	 *	-1	failure
	 *	 0	ok
	 *	 1	no medium present
	 *	 2	retry
	 * The contents of r may be altered so the
	 * caller should re-initialise if necesary.
	 */
	r->status = ~0;
	switch(r->unit->dev->ifc->rio(r)){
	default:
		return -1;
	case SDcheck:
		if(!(r->flags & SDvalidsense))
			return -1;
		switch(r->sense[2] & 0x0F){
		case 0x00:		/* no sense */
		case 0x01:		/* recovered error */
			return 2;
		case 0x06:		/* check condition */
			/*
			 * 0x28 - not ready to ready transition,
			 *	  medium may have changed.
			 * 0x29 - power on or some type of reset.
			 */
			if(r->sense[12] == 0x28 && r->sense[13] == 0)
				return 2;
			if(r->sense[12] == 0x29)
				return 2;
			return -1;
		case 0x02:		/* not ready */
			/*
			 * If no medium present, bail out.
			 * If unit is becoming ready, rather than not
			 * not ready, wait a little then poke it again. 				 */
			if(r->sense[12] == 0x3A)
				return 1;
			if(r->sense[12] != 0x04 || r->sense[13] != 0x01)
				return -1;

			while(waserror())
				;
			tsleep(&r->unit->rendez, return0, 0, 500);
			poperror();
			scsitest(r);
			return 2;
		default:
			return -1;
		}
		return -1;
	case SDok:
		return 0;
	}
	return -1;
}

int
scsionline(SDunit* unit)
{
	SDreq *r;
	uchar *p;
	int ok, retries;

	if((r = malloc(sizeof(SDreq))) == nil)
		return 0;
	if((p = sdmalloc(8)) == nil){
		free(r);
		return 0;
	}

	ok = 0;

	r->unit = unit;
	r->lun = 0;				/* ??? */
	for(retries = 0; retries < 10; retries++){
		/*
		 * Read-capacity is mandatory for DA, WORM, CD-ROM and
		 * MO. It may return 'not ready' if type DA is not
		 * spun up, type MO or type CD-ROM are not loaded or just
		 * plain slow getting their act together after a reset.
		 */
		r->write = 0;
		memset(r->cmd, 0, sizeof(r->cmd));
		r->cmd[0] = 0x25;
		r->cmd[1] = r->lun<<5;
		r->clen = 10;
		r->data = p;
		r->dlen = 8;
		r->flags = 0;
	
		r->status = ~0;
		switch(scsirio(r)){
		default:
			break;
		case 0:
			unit->sectors = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3];
			/*
			 * Read-capacity returns the LBA of the last sector,
			 * therefore the number of sectors must be incremented.
			 */
			unit->sectors++;
			unit->secsize = (p[4]<<24)|(p[5]<<16)|(p[6]<<8)|p[7];

			/*
			 * Some ATAPI CD readers lie about the block size.
			 * Since we don't read audio via this interface
			 * it's okay to always fudge this.
			 */
			if(unit->secsize == 2352)
				unit->secsize = 2048;
			ok = 1;
			break;
		case 1:
			ok = 1;
			break;
		case 2:
			continue;
		}
		break;
	}
	free(p);
	free(r);

	if(ok)
		return ok+retries;
	else
		return 0;
}

int
scsiexec(SDunit* unit, int write, uchar* cmd, int clen, void* data, int* dlen)
{
	SDreq *r;
	int status;

	if((r = malloc(sizeof(SDreq))) == nil)
		return SDmalloc;
	r->unit = unit;
	r->lun = cmd[1]>>5;		/* ??? */
	r->write = write;
	memmove(r->cmd, cmd, clen);
	r->clen = clen;
	r->data = data;
	if(dlen)
		r->dlen = *dlen;
	r->flags = 0;

	r->status = ~0;

	/*
	 * Call the device-specific I/O routine.
	 * There should be no calls to 'error()' below this
	 * which percolate back up.
	 */
	switch(status = unit->dev->ifc->rio(r)){
	case SDok:
		if(dlen)
			*dlen = r->rlen;
		/*FALLTHROUGH*/
	case SDcheck:
		/*FALLTHROUGH*/
	default:
		/*
		 * It's more complicated than this. There are conditions
		 * which are 'ok' but for which the returned status code
		 * is not 'SDok'.
		 * Also, not all conditions require a reqsense, might
		 * need to do a reqsense here and make it available to the
		 * caller somehow.
		 *
		 * Mañana.
		 */
		break;
	}
	sdfree(r);

	return status;
}

long
scsibio(SDunit* unit, int lun, int write, void* data, long nb, long bno)
{
	SDreq *r;
	long rlen;

	if((r = malloc(sizeof(SDreq))) == nil)
		error(Enomem);
	r->unit = unit;
	r->lun = lun;
again:
	r->write = write;
	if(write == 0)
		r->cmd[0] = 0x28;
	else
		r->cmd[0] = 0x2A;
	r->cmd[1] = (lun<<5);
	r->cmd[2] = bno>>24;
	r->cmd[3] = bno>>16;
	r->cmd[4] = bno>>8;
	r->cmd[5] = bno;
	r->cmd[6] = 0;
	r->cmd[7] = nb>>8;
	r->cmd[8] = nb;
	r->cmd[9] = 0;
	r->clen = 10;
	r->data = data;
	r->dlen = nb*unit->secsize;
	r->flags = 0;

	r->status = ~0;
	switch(scsirio(r)){
	default:
		rlen = -1;
		break;
	case 0:
		rlen = r->rlen;
		break;
	case 2:
		rlen = -1;
		if(!(r->flags & SDvalidsense))
			break;
		switch(r->sense[2] & 0x0F){
		default:
			break;
		case 0x06:		/* check condition */
			/*
			 * Check for a removeable media change.
			 * If so, mark it by zapping the geometry info
			 * to force an online request.
			 */
			if(r->sense[12] != 0x28 || r->sense[13] != 0)
				break;
			if(unit->inquiry[1] & 0x80)
				unit->sectors = 0;
			break;
		case 0x02:		/* not ready */
			/*
			 * If unit is becoming ready,
			 * rather than not not ready, try again.
			 */
			if(r->sense[12] == 0x04 && r->sense[13] == 0x01)
				goto again;
			break;
		}
		break;
	}
	free(r);

	return rlen;
}

SDev*
scsiid(SDev* sdev, SDifc* ifc)
{
	static char idno[16] = "0123456789abcdef";
	static char *p = idno;

	while(sdev){
		if(sdev->ifc == ifc){
			sdev->idno = *p++;
			snprint(sdev->name, NAMELEN, "sd%c", sdev->idno);
			if(p >= &idno[sizeof(idno)])
				break;
		}
		sdev = sdev->next;
	}

	return nil;
}
#include "../pc/sdscsi.c"

M ip/icmp.c => ip/icmp.c +66 -78
@@ 30,7 30,7 @@ enum {			/* Packet Types */
	EchoReply	= 0,
	Unreachable	= 3,
	SrcQuench	= 4,
	Redirect	= 4,
	Redirect	= 5,
	EchoRequest	= 8,
	TimeExceed	= 11,
	InParmProblem	= 12,


@@ 40,56 40,61 @@ enum {			/* Packet Types */
	InfoReply	= 16,
	AddrMaskRequest = 17,
	AddrMaskReply   = 18,

	Maxtype		= 18,
};

char *icmpnames[Maxtype+1] =
{
[EchoReply]		"EchoReply",
[Unreachable]		"Unreachable",
[SrcQuench]		"SrcQuench",
[Redirect]		"Redirect",
[EchoRequest]		"EchoRequest",
[TimeExceed]		"TimeExceed",
[InParmProblem]		"InParmProblem",
[Timestamp]		"Timestamp",
[TimestampReply]	"TimestampReply",
[InfoRequest]		"InfoRequest",
[InfoReply]		"InfoReply",
[AddrMaskRequest]	"AddrMaskRequest",
[AddrMaskReply  ]	"AddrMaskReply  ",
};

enum {
	IP_ICMPPROTO	= 1,
	ICMP_IPSIZE	= 20,
	ICMP_HDRSIZE	= 8,
};

typedef struct Icmpstats Icmpstats;
struct Icmpstats 
enum
{
	InMsgs,
	InErrors,
	OutMsgs,
	CsumErrs,
	LenErrs,
	HlenErrs,

	Nstats,
};

static char *statnames[Nstats] =
{
	ulong	icmpInMsgs;
	ulong	icmpInErrors;
//	ulong	icmpInDestUnreachs;
//	ulong	icmpInTimeExcds;
//	ulong	icmpInParmProbs;
//	ulong	icmpInSrcQuenchs;
//	ulong	icmpInRedirects;
//	ulong	icmpInEchos;
//	ulong	icmpInEchoReps;
//	ulong	icmpInTimestamps;
//	ulong	icmpInTimestampReps;
//	ulong	icmpInAddrMasks;
//	ulong	icmpInAddrMaskReps;
	ulong	icmpOutMsgs;
	ulong	icmpOutErrors;
//	ulong	icmpOutDestUnreachs;
//	ulong	icmpOutTimeExcds;
//	ulong	icmpOutParmProbs;
//	ulong	icmpOutSrcQuenchs;
//	ulong	icmpOutRedirects;
//	ulong	icmpOutEchos;
//	ulong	icmpOutEchoReps;
//	ulong	icmpOutTimestamps;
//	ulong	icmpOutTimestampReps;
//	ulong	icmpOutAddrMasks;
//	ulong	icmpOutAddrMaskReps;
[InMsgs]	"InMsgs",
[InErrors]	"InErrors",
[OutMsgs]	"OutMsgs",
[CsumErrs]	"CsumErrs",
[LenErrs]	"LenErrs",
[HlenErrs]	"HlenErrs",
};

typedef struct Icmppriv Icmppriv;
struct Icmppriv
{
	/* MIB Counters */
	Icmpstats	istats;
	ulong	stats[Nstats];

	/* non-MIB stats */
	ulong	csumerr;		/* checksum errors */
	ulong	lenerr;			/* short packet */
	ulong	hlenerr;		/* short header */
	/* message counts */
	ulong	in[Maxtype+1];
	ulong	out[Maxtype+1];
};


@@ 170,7 175,7 @@ icmpkick(Conv *c, int l)
	hnputs(p->icmpid, c->lport);
	memset(p->cksum, 0, sizeof(p->cksum));
	hnputs(p->cksum, ptclcsum(bp, ICMP_IPSIZE, blocklen(bp) - ICMP_IPSIZE));
	ipriv->istats.icmpOutMsgs++;
	ipriv->stats[OutMsgs]++;
	ipoput(c->p->f, bp, 0, c->ttl, c->tos);
}



@@ 196,7 201,6 @@ icmpttlexceeded(Fs *f, uchar *ia, Block *bp)
	hnputs(np->seq, 0);
	memset(np->cksum, 0, sizeof(np->cksum));
	hnputs(np->cksum, ptclcsum(nbp, ICMP_IPSIZE, blocklen(nbp) - ICMP_IPSIZE));
/*	stats.out[TimeExceed]++; */
	ipoput(f, nbp, 0, MAXTTL, DFLTTOS);

}


@@ 235,7 239,6 @@ icmpnoconv(Fs *f, Block *bp)
	hnputs(np->seq, 0);
	memset(np->cksum, 0, sizeof(np->cksum));
	hnputs(np->cksum, ptclcsum(nbp, ICMP_IPSIZE, blocklen(nbp) - ICMP_IPSIZE));
/*	stats.out[Unreachable]++; */
	ipoput(f, nbp, 0, MAXTTL, DFLTTOS);
}



@@ 304,27 307,27 @@ icmpiput(Proto *icmp, uchar*, Block *bp)

	ipriv = icmp->priv;
	
	ipriv->istats.icmpInMsgs++;
	ipriv->stats[InMsgs]++;

	p = (Icmp *)bp->rp;
	netlog(icmp->f, Logicmp, "icmpiput %d %d\n", p->type, p->code);
	n = blocklen(bp);
	if(n < ICMP_IPSIZE+ICMP_HDRSIZE){
		ipriv->istats.icmpInErrors++;
		ipriv->hlenerr++;
		ipriv->stats[InErrors]++;
		ipriv->stats[HlenErrs]++;
		netlog(icmp->f, Logicmp, "icmp hlen %d\n", n);
		goto raise;
	}
	iplen = nhgets(p->length);
	if(iplen > n || (iplen % 1)){
		ipriv->lenerr++;
		ipriv->istats.icmpInErrors++;
		ipriv->stats[LenErrs]++;
		ipriv->stats[InErrors]++;
		netlog(icmp->f, Logicmp, "icmp length %d\n", iplen);
		goto raise;
	}
	if(ptclcsum(bp, ICMP_IPSIZE, iplen - ICMP_IPSIZE)){
		ipriv->istats.icmpInErrors++;
		ipriv->csumerr++;
		ipriv->stats[InErrors]++;
		ipriv->stats[CsumErrs]++;
		netlog(icmp->f, Logicmp, "icmp checksum error\n");
		goto raise;
	}


@@ 345,7 348,7 @@ icmpiput(Proto *icmp, uchar*, Block *bp)

		bp->rp += ICMP_IPSIZE+ICMP_HDRSIZE;
		if(blocklen(bp) < 8){
			ipriv->lenerr++;
			ipriv->stats[LenErrs]++;
			goto raise;
		}
		p = (Icmp *)bp->rp;


@@ 364,7 367,7 @@ icmpiput(Proto *icmp, uchar*, Block *bp)

			bp->rp += ICMP_IPSIZE+ICMP_HDRSIZE;
			if(blocklen(bp) < 8){
				ipriv->lenerr++;
				ipriv->stats[LenErrs]++;
				goto raise;
			}
			p = (Icmp *)bp->rp;


@@ 414,37 417,22 @@ icmpadvise(Proto *icmp, Block *bp, char *msg)
int
icmpstats(Proto *icmp, char *buf, int len)
{
	Icmppriv *ipriv;

	ipriv = icmp->priv;

	return snprint(buf,len,"%lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud",
			ipriv->istats.icmpInMsgs,
			ipriv->istats.icmpInErrors,
			ipriv->in[Unreachable],
			ipriv->in[TimeExceed],
			ipriv->in[InParmProblem],
			ipriv->in[SrcQuench],
			ipriv->in[Redirect],
			ipriv->in[EchoRequest],
			ipriv->in[EchoReply],
			ipriv->in[Timestamp],
			ipriv->in[TimestampReply],
			ipriv->in[AddrMaskRequest],
			ipriv->in[AddrMaskReply],
			ipriv->istats.icmpOutMsgs,
			ipriv->istats.icmpOutErrors,
			ipriv->out[Unreachable],
			ipriv->out[TimeExceed],
			ipriv->out[InParmProblem],
			ipriv->out[SrcQuench],
			ipriv->out[Redirect],
			ipriv->out[EchoRequest],
			ipriv->out[EchoReply],
			ipriv->out[Timestamp],
			ipriv->out[TimestampReply],
			ipriv->out[AddrMaskRequest],
			ipriv->out[AddrMaskReply]);
	Icmppriv *priv;
	char *p, *e;
	int i;

	priv = icmp->priv;
	p = buf;
	e = p+len;
	for(i = 0; i < Nstats; i++)
		p = seprint(p, e, "%s: %lud\n", statnames[i], priv->stats[i]);
	for(i = 0; i <= Maxtype; i++){
		if(icmpnames[i])
			p = seprint(p, e, "%s: %lud %lud\n", icmpnames[i], priv->in[i], priv->out[i]);
		else
			p = seprint(p, e, "%d: %lud %lud\n", i, priv->in[i], priv->out[i]);
	}
	return p - buf;
}
	
void

M ip/il.c => ip/il.c +50 -17
@@ 149,12 149,39 @@ struct Ilhdr
	uchar	ilack[4];	/* Acked sequence */
};

enum
{
	InMsgs,
	OutMsgs,
	CsumErrs,		/* checksum errors */
	HlenErrs,		/* header length error */
	LenErrs,		/* short packet */
	OutOfOrder,		/* out of order */
	Retrans,		/* retransmissions */
	DupMsg,
	DupBytes,

	Nstats,
};

static char *statnames[] =
{
[InMsgs]	"InMsgs",
[OutMsgs]	"OutMsgs",
[CsumErrs]	"CsumErrs",
[HlenErrs]	"HlenErr",
[LenErrs]	"LenErrs",
[OutOfOrder]	"OutOfOrder",
[Retrans]	"Retrans",
[DupMsg]	"DupMsg",
[DupBytes]	"DupBytes",
};

typedef struct Ilpriv Ilpriv;
struct Ilpriv
{
	ulong	stats[Nstats];

	/* non-MIB stats */
	ulong		csumerr;		/* checksum errors */
	ulong		hlenerr;		/* header length error */
	ulong		lenerr;			/* short packet */


@@ 289,10 316,13 @@ ilkick(Conv *c, int l)
	ulong id, ack;
	Block *bp;
	Fs *f;
	Ilpriv *priv;


	USED(l);

	f = c->p->f;
	priv = c->p->priv;
	ic = (Ilcb*)c->ptcl;

	bp = qget(c->wq);


@@ 355,6 385,7 @@ ilkick(Conv *c, int l)
	if(later(msec, ic->timeout, nil))
		ilsettimeout(ic);
	ipoput(f, bp, 0, c->ttl, c->tos);
	priv->stats[OutMsgs]++;
}

static void


@@ 367,17 398,16 @@ ilcreate(Conv *c)
int
ilxstats(Proto *il, char *buf, int len)
{
	int n;
	Ilpriv *ipriv;

	ipriv = il->priv;
	Ilpriv *priv;
	char *p, *e;
	int i;

	n = snprint(buf, len,
		"il: csum %lud hlen %lud len %lud order %lud rexmit %lud",
		ipriv->csumerr, ipriv->hlenerr, ipriv->lenerr, ipriv->order, ipriv->rexmit);
	n += snprint(buf+n, len-n, " dupp %lud dupb %lud\n",
		ipriv->dup, ipriv->dupb);
	return n;
	priv = il->priv;
	p = buf;
	e = p+len;
	for(i = 0; i < Nstats; i++)
		p = seprint(p, e, "%s: %lud\n", statnames[i], priv->stats[i]);
	return p - buf;
}

void


@@ 492,14 522,14 @@ iliput(Proto *il, uchar*, Block *bp)
	plen = blocklen(bp);
	if(plen < IL_IPSIZE+IL_HDRSIZE){
		netlog(il->f, Logil, "il: hlenerr\n");
		ipriv->hlenerr++;
		ipriv->stats[HlenErrs]++;
		goto raise;
	}

	illen = nhgets(ih->illen);
	if(illen+IL_IPSIZE > plen){
		netlog(il->f, Logil, "il: lenerr\n");
		ipriv->lenerr++;
		ipriv->stats[LenErrs]++;
		goto raise;
	}



@@ 512,7 542,7 @@ iliput(Proto *il, uchar*, Block *bp)
			st = "?";
		else
			st = iltype[ih->iltype];
		ipriv->csumerr++;
		ipriv->stats[CsumErrs]++;
		netlog(il->f, Logil, "il: cksum %ux %ux, pkt(%s id %lud ack %lud %I/%d->%d)\n",
			csum, st, nhgetl(ih->ilid), nhgetl(ih->ilack), raddr, sp, dp);
		goto raise;


@@ 616,6 646,7 @@ _ilprocess(Conv *s, Ilhdr *h, Block *bp)
{
	Ilcb *ic;
	ulong id, ack;
	Ilpriv *priv;

	id = nhgetl(h->ilid);
	ack = nhgetl(h->ilack);


@@ 624,6 655,8 @@ _ilprocess(Conv *s, Ilhdr *h, Block *bp)

	ic->lastrecv = msec;
	ic->querytime = msec + QueryTime;
	priv = s->p->priv;
	priv->stats[InMsgs]++;

	switch(ic->state) {
	default:


@@ 865,7 898,7 @@ ilpullup(Conv *s)
		}
		if(oid != ic->recvd+1){
			ipriv = s->p->priv;
			ipriv->order++;
			ipriv->stats[OutOfOrder]++;
			break;
		}



@@ 923,8 956,8 @@ iloutoforder(Conv *s, Ilhdr *h, Block *bp)
			newid = nhgetl(lid);
			if(id <= newid) {
				if(id == newid) {
					ipriv->dup++;
					ipriv->dupb += blocklen(bp);
					ipriv->stats[DupMsg]++;
					ipriv->stats[DupBytes] += blocklen(bp);
					qunlock(&ic->outo);
					freeblist(bp);
					return;

M ip/ip.c => ip/ip.c +82 -62
@@ 52,34 52,58 @@ struct Ipfrag
};

/* MIB II counters */
typedef struct Ipstats Ipstats;
struct Ipstats
enum
{
	ulong	ipForwarding;
	ulong	ipDefaultTTL;
	ulong	ipInReceives;
	ulong	ipInHdrErrors;
	ulong	ipInAddrErrors;
	ulong	ipForwDatagrams;
	ulong	ipInUnknownProtos;
	ulong	ipInDiscards;
	ulong	ipInDelivers;
	ulong	ipOutRequests;
	ulong	ipOutDiscards;
	ulong	ipOutNoRoutes;
	ulong	ipReasmTimeout;
	ulong	ipReasmReqds;
	ulong	ipReasmOKs;
	ulong	ipReasmFails;
	ulong	ipFragOKs;
	ulong	ipFragFails;
	ulong	ipFragCreates;
	Forwarding,
	DefaultTTL,
	InReceives,
	InHdrErrors,
	InAddrErrors,
	ForwDatagrams,
	InUnknownProtos,
	InDiscards,
	InDelivers,
	OutRequests,
	OutDiscards,
	OutNoRoutes,
	ReasmTimeout,
	ReasmReqds,
	ReasmOKs,
	ReasmFails,
	FragOKs,
	FragFails,
	FragCreates,

	Nstats,
};

static char *statnames[] =
{
[Forwarding]	"Forwarding",
[DefaultTTL]	"DefaultTTL",
[InReceives]	"InReceives",
[InHdrErrors]	"InHdrErrors",
[InAddrErrors]	"InAddrErrors",
[ForwDatagrams]	"ForwDatagrams",
[InUnknownProtos]	"InUnknownProtos",
[InDiscards]	"InDiscards",
[InDelivers]	"InDelivers",
[OutRequests]	"OutRequests",
[OutDiscards]	"OutDiscards",
[OutNoRoutes]	"OutNoRoutes",
[ReasmTimeout]	"ReasmTimeout",
[ReasmReqds]	"ReasmReqds",
[ReasmOKs]	"ReasmOKs",
[ReasmFails]	"ReasmFails",
[FragOKs]	"FragOKs",
[FragFails]	"FragFails",
[FragCreates]	"FragCreates",
};

/* an instance of IP */
struct IP
{
	Ipstats		istats;
	ulong		stats[Nstats];

	QLock		fraglock;
	Fragment*	flisthead;


@@ 116,9 140,9 @@ iprouting(Fs *f, int on)
{
	f->ip->iprouting = on;
	if(f->ip->iprouting==0)
		f->ip->istats.ipForwarding = 2;
		f->ip->stats[Forwarding] = 2;
	else
		f->ip->istats.ipForwarding = 1;	
		f->ip->stats[Forwarding] = 1;	
}

void


@@ 138,7 162,7 @@ ipoput(Fs *f, Block *bp, int gating, int ttl, int tos)
	/* Fill out the ip header */
	eh = (Iphdr*)(bp->rp);

	ip->istats.ipOutRequests++;
	ip->stats[OutRequests]++;

	/* Number of uchars in data and ip header to write */
	len = blocklen(bp);


@@ 146,7 170,7 @@ ipoput(Fs *f, Block *bp, int gating, int ttl, int tos)
	if(gating){
		chunk = nhgets(eh->length);
		if(chunk > len){
			ip->istats.ipOutDiscards++;
			ip->stats[OutDiscards]++;
			netlog(f, Logip, "short gated packet\n");
			goto free;
		}


@@ 154,14 178,14 @@ ipoput(Fs *f, Block *bp, int gating, int ttl, int tos)
			len = chunk;
	}
	if(len >= IP_MAX){
		ip->istats.ipOutDiscards++;
		ip->stats[OutDiscards]++;
		netlog(f, Logip, "exceeded ip max size %V\n", eh->dst);
		goto free;
	}

	r = v4lookup(f, eh->dst);
	if(r == nil){
		ip->istats.ipOutNoRoutes++;
		ip->stats[OutNoRoutes]++;
		netlog(f, Logip, "no interface %V\n", eh->dst);
		goto free;
	}


@@ 213,16 237,16 @@ ipoput(Fs *f, Block *bp, int gating, int ttl, int tos)
	}

	if(eh->frag[0] & (IP_DF>>8)){
		ip->istats.ipFragFails++;
		ip->istats.ipOutDiscards++;
		ip->stats[FragFails]++;
		ip->stats[OutDiscards]++;
		netlog(f, Logip, "%V: eh->frag[0] & (IP_DF>>8)\n", eh->dst);
		goto raise;
	}

	seglen = (medialen - IPHDR) & ~7;
	if(seglen < 8){
		ip->istats.ipFragFails++;
		ip->istats.ipOutDiscards++;
		ip->stats[FragFails]++;
		ip->stats[OutDiscards]++;
		netlog(f, Logip, "%V seglen < 8\n", eh->dst);
		goto raise;
	}


@@ 262,8 286,8 @@ ipoput(Fs *f, Block *bp, int gating, int ttl, int tos)
		chunk = seglen;
		while(chunk) {
			if(!xp) {
				ip->istats.ipOutDiscards++;
				ip->istats.ipFragFails++;
				ip->stats[OutDiscards]++;
				ip->stats[FragFails]++;
				freeblist(nb);
				netlog(f, Logip, "!xp: chunk %d\n", chunk);
				goto raise;


@@ 283,9 307,9 @@ ipoput(Fs *f, Block *bp, int gating, int ttl, int tos)
		feh->cksum[1] = 0;
		hnputs(feh->cksum, ipcsum(&feh->vihl));
		ifc->m->bwrite(ifc, nb, V4, gate);
		ip->istats.ipFragCreates++;
		ip->stats[FragCreates]++;
	}
	ip->istats.ipFragOKs++;
	ip->stats[FragOKs]++;
raise:
	runlock(ifc);
	poperror();


@@ 324,7 348,7 @@ ipiput(Fs *f, uchar *ia, Block *bp)
	Route *r, *sr;

	ip = f->ip;
	ip->istats.ipInReceives++;
	ip->stats[InReceives]++;

//	h = (Iphdr *)(bp->rp);
//	DBG(nhgetl(h->src))(Logipmsg, "ipiput %V %V len %d proto %d\n",


@@ 349,7 373,7 @@ ipiput(Fs *f, uchar *ia, Block *bp)

	/* dump anything that whose header doesn't checksum */
	if(ipcsum(&h->vihl)) {
		ip->istats.ipInHdrErrors++;
		ip->stats[InHdrErrors]++;
		netlog(f, Logip, "ip: checksum error %V\n", h->src);
		freeblist(bp);
		return;


@@ 362,7 386,7 @@ ipiput(Fs *f, uchar *ia, Block *bp)
	if(h->vihl != (IP_VER|IP_HLEN)) {
		hl = (h->vihl&0xF)<<2;
		if((h->vihl&0xF0) != IP_VER || hl < (IP_HLEN<<2)) {
			ip->istats.ipInHdrErrors++;
			ip->stats[InHdrErrors]++;
			netlog(f, Logip, "ip: %V bad hivl %ux\n", h->src, h->vihl);
			freeblist(bp);
			return;


@@ 388,20 412,20 @@ ipiput(Fs *f, uchar *ia, Block *bp)
		sr = v4lookup(f, h->src);
		r = v4lookup(f, h->dst);
		if(r == nil || sr == r){
			ip->istats.ipOutDiscards++;
			ip->stats[OutDiscards]++;
			freeblist(bp);
			return;
		}

		/* don't forward if packet has timed out */
		if(h->ttl <= 1){
			ip->istats.ipInHdrErrors++;
			ip->stats[InHdrErrors]++;
			icmpttlexceeded(f, ia, bp);
			freeblist(bp);
			return;
		}

		ip->istats.ipForwDatagrams++;
		ip->stats[ForwDatagrams]++;
		ipoput(f, bp, 1, h->ttl - 1, h->tos);

		return;


@@ 422,12 446,12 @@ ipiput(Fs *f, uchar *ia, Block *bp)

	p = Fsrcvpcol(f, h->proto);
	if(p != nil && p->rcv != nil) {
		ip->istats.ipInDelivers++;
		ip->stats[InDelivers]++;
		(*p->rcv)(p, ia, bp);
		return;
	}
	ip->istats.ipInDiscards++;
	ip->istats.ipInUnknownProtos++;
	ip->stats[InDiscards]++;
	ip->stats[InUnknownProtos]++;
	freeblist(bp);
}



@@ 435,21 459,17 @@ int
ipstats(Fs *f, char *buf, int len)
{
	IP *ip;
	char *p, *e;
	int i;

	ip = f->ip;
	ip->istats.ipDefaultTTL = MAXTTL;
	return snprint(buf, len, "%lud %lud %lud %lud %lud %lud %lud %lud %lud %lud "
				 "%lud %lud %lud %lud %lud %lud %lud %lud %lud",
		ip->istats.ipForwarding, ip->istats.ipDefaultTTL,
		ip->istats.ipInReceives, ip->istats.ipInHdrErrors,
		ip->istats.ipInAddrErrors, ip->istats.ipForwDatagrams,
		ip->istats.ipInUnknownProtos, ip->istats.ipInDiscards,
		ip->istats.ipInDelivers, ip->istats.ipOutRequests,
		ip->istats.ipOutDiscards, ip->istats.ipOutNoRoutes,
		ip->istats.ipReasmTimeout, ip->istats.ipReasmReqds,
		ip->istats.ipReasmOKs, ip->istats.ipReasmFails,
		ip->istats.ipFragOKs, ip->istats.ipFragFails,
		ip->istats.ipFragCreates);
	ip->stats[DefaultTTL] = MAXTTL;

	p = buf;
	e = p+len;
	for(i = 0; i < Nstats; i++)
		p = seprint(p, e, "%s: %lud\n", statnames[i], ip->stats[i]);
	return p - buf;
}

Block*


@@ 484,7 504,7 @@ ipreassemble(IP *ip, int offset, Block *bp, Iphdr *ih)
		if(f->src == src && f->dst == dst && f->id == id)
			break;
		if(f->age < msec){
			ip->istats.ipReasmTimeout++;
			ip->stats[ReasmTimeout]++;
			ipfragfree(ip, f);
		}
	}


@@ 497,7 517,7 @@ ipreassemble(IP *ip, int offset, Block *bp, Iphdr *ih)
	if(!ih->tos && (offset & ~(IP_MF|IP_DF)) == 0) {
		if(f != nil) {
			ipfragfree(ip, f);
			ip->istats.ipReasmFails++;
			ip->stats[ReasmFails]++;
		}
		qunlock(&ip->fraglock);
		return bp;


@@ 521,7 541,7 @@ ipreassemble(IP *ip, int offset, Block *bp, Iphdr *ih)
		f->blist = bp;

		qunlock(&ip->fraglock);
		ip->istats.ipReasmReqds++;
		ip->stats[ReasmReqds]++;
		return nil;
	}



@@ 607,7 627,7 @@ ipreassemble(IP *ip, int offset, Block *bp, Iphdr *ih)
			ih = BLKIP(bl);
			hnputs(ih->length, len);
			qunlock(&ip->fraglock);
			ip->istats.ipReasmOKs++;
			ip->stats[ReasmOKs]++;
			return bl;		
		}
		pktposn += BKFG(bl)->flen;

M ip/netdevmedium.c => ip/netdevmedium.c +4 -4
@@ 24,10 24,10 @@ struct Netdevrock
Medium netdevmedium =
{
.name=		"netdev",
.hsize=		14,		//ZZZ what should these be?
.minmtu=	60,
.maxmtu=	1514,
.maclen=	6,
.hsize=		0,
.minmtu=	0,
.maxmtu=	64000,
.maclen=	0,
.bind=		netdevbind,
.unbind=	netdevunbind,
.bwrite=	netdevbwrite,

M ip/tcp.c => ip/tcp.c +67 -62
@@ 196,25 196,46 @@ struct Tcpctl
int	tcp_irtt = DEF_RTT;	/* Initial guess at round trip time */
ushort	tcp_mss  = DEF_MSS;	/* Maximum segment size to be sent */

/* MIB II counters */
typedef struct Tcpstats Tcpstats;
struct Tcpstats
enum {
	/* MIB stats */
	MaxConn,
	ActiveOpens,
	PassiveOpens,
	EstabResets,
	CurrEstab,
	InSegs,
	OutSegs,
	RetransSegs,
	RetransTimeouts,
	InErrs,
	OutRsts,

	/* non-MIB stats */
	CsumErrs,
	HlenErrs,
	LenErrs,
	OutOfOrder,

	Nstats
};

static char *statnames[] =
{
	ulong	tcpRtoAlgorithm;
	ulong	tcpRtoMin;
	ulong	tcpRtoMax;
	ulong	tcpMaxConn;
	ulong	tcpActiveOpens;
	ulong	tcpPassiveOpens;
	ulong	tcpAttemptFails;
	ulong	tcpEstabResets;
	ulong	tcpCurrEstab;
	ulong	tcpInSegs;
	ulong	tcpOutSegs;
	ulong	tcpRetransSegs;
        ulong   tcpRetransTimeouts;
	ulong	InErrs;
	ulong	OutRsts;
[MaxConn]	"MaxConn",
[ActiveOpens]	"ActiveOpens",
[PassiveOpens]	"PassiveOpens",
[EstabResets]	"EstabResets",
[CurrEstab]	"CurrEstab",
[InSegs]	"InSegs",
[OutSegs]	"OutSegs",
[RetransSegs]	"RetransSegs",
[RetransTimeouts]	"RetransTimeouts",
[InErrs]	"InErrs",
[OutRsts]	"OutRsts",
[CsumErrs]	"CsumErrs",
[HlenErrs]	"HlenErrs",
[LenErrs]	"LenErrs",
[OutOfOrder]	"OutOfOrder",
};

typedef struct Tcppriv Tcppriv;


@@ 224,14 245,7 @@ struct Tcppriv
	QLock 	tl;			/* Protect timer list */
	Rendez	tcpr;			/* used by tcpackproc */

	/* MIB stats */
	Tcpstats tstats;

	/* non-MIB stats */
	ulong		csumerr;		/* checksum errors */
	ulong		hlenerr;		/* header length error */
	ulong		lenerr;			/* short packet */
	ulong		order;			/* out of order */
	ulong	stats[Nstats];

	/* for keeping track of tcpackproc */
	int	ackprocstarted;


@@ 270,9 284,9 @@ tcpsetstate(Conv *s, uchar newstate)
		return;

	if(oldstate == Established)
		tpriv->tstats.tcpCurrEstab--;
		tpriv->stats[CurrEstab]--;
	if(newstate == Established)
		tpriv->tstats.tcpCurrEstab++;
		tpriv->stats[CurrEstab]++;

	/**
	print( "%d/%d %s->%s CurrEstab=%d\n", s->lport, s->rport,


@@ 691,13 705,13 @@ tcpstart(Conv *s, int mode, ushort window)

	switch(mode) {
	case TCP_LISTEN:
		tpriv->tstats.tcpPassiveOpens++;
		tpriv->stats[PassiveOpens]++;
		tcb->flags |= CLONE;
		tcpsetstate(s, Listen);
		break;

	case TCP_CONNECT:
		tpriv->tstats.tcpActiveOpens++;
		tpriv->stats[ActiveOpens]++;
		/* Send SYN, go into SYN_SENT state */
		qlock(s);
		if(waserror()){


@@ 895,7 909,7 @@ sndrst(Proto *tcp, uchar *source, uchar *dest, ushort length, Tcp *seg)
	hnputs(ph.tcpsport, seg->dest);
	hnputs(ph.tcpdport, seg->source);

	tpriv->tstats.OutRsts++;
	tpriv->stats[OutRsts]++;
	rflags = RST;

	/* convince the other end that this reset is in band */


@@ 1207,7 1221,7 @@ tcpiput(Proto *tcp, uchar*, Block *bp)
	f = tcp->f;
	tpriv = tcp->priv;
	
	tpriv->tstats.tcpInSegs++;
	tpriv->stats[InSegs]++;

	h = (Tcphdr*)(bp->rp);



@@ 1218,7 1232,8 @@ tcpiput(Proto *tcp, uchar*, Block *bp)
	h->Unused = 0;
	hnputs(h->tcplen, length-TCP_PKT);
	if(ptclcsum(bp, TCP_IPLEN, length-TCP_IPLEN)) {
		tpriv->csumerr++;
		tpriv->stats[CsumErrs]++;
		tpriv->stats[InErrs]++;
		netlog(f, Logtcp, "bad tcp proto cksum\n");
		freeblist(bp);
		return;


@@ 1226,7 1241,8 @@ tcpiput(Proto *tcp, uchar*, Block *bp)

	hdrlen = ntohtcp(&seg, &bp);
	if(hdrlen < 0){
		tpriv->hlenerr++;
		tpriv->stats[HlenErrs]++;
		tpriv->stats[InErrs]++;
		netlog(f, Logtcp, "bad tcp hdr len\n");
		return;
	}


@@ 1235,7 1251,8 @@ tcpiput(Proto *tcp, uchar*, Block *bp)
	length -= hdrlen+TCP_PKT;
	bp = trimblock(bp, hdrlen+TCP_PKT, length);
	if(bp == nil){
		tpriv->lenerr++;
		tpriv->stats[LenErrs]++;
		tpriv->stats[InErrs]++;
		netlog(f, Logtcp, "tcp len < 0 after trim\n");
		return;
	}


@@ 1424,7 1441,7 @@ tcpiput(Proto *tcp, uchar*, Block *bp)
	if(seg.seq != tcb->rcv.nxt)
	if(length != 0 || (seg.flags & (SYN|FIN))) {
		update(s, &seg);
		tpriv->order++;
		tpriv->stats[OutOfOrder]++;
		if(addreseq(tcb, &seg, bp, length) < 0)
			print("reseq %I.%d -> %I.%d\n", s->raddr, s->rport, s->laddr, s->lport);
		tcb->flags |= FORCE;


@@ 1438,7 1455,7 @@ tcpiput(Proto *tcp, uchar*, Block *bp)
	for(;;) {
		if(seg.flags & RST) {
			if(tcb->state == Established)
				tpriv->tstats.tcpEstabResets++;
				tpriv->stats[EstabResets]++;
			localclose(s, Econrefused);
			goto raise;
		}


@@ 1771,7 1788,7 @@ tcpoutput(Conv *s)
			if(ssize < n)
				n = ssize;
			tcb->resent += n;
			tpriv->tstats.tcpRetransSegs++;
			tpriv->stats[RetransSegs]++;
		}

		tcb->snd.ptr += ssize;


@@ 1820,7 1837,7 @@ tcpoutput(Conv *s)
			}
		}

		tpriv->tstats.tcpOutSegs++;
		tpriv->stats[OutSegs]++;
		if(tcb->kacounter > 0)
			tcpgo(tpriv, &tcb->katimer);
		ipoput(f, hbp, 0, s->ttl, s->tos);


@@ 1982,7 1999,7 @@ tcptimeout(void *arg)
			break;
		}
		tcprxmit(s);
		tpriv->tstats.tcpRetransTimeouts++;
		tpriv->stats[RetransTimeouts]++;
		tcb->snd.dupacks = 0;
		break;
	case Time_wait:


@@ 2219,28 2236,16 @@ tcpctl(Conv* c, char** f, int n)
int
tcpstats(Proto *tcp, char *buf, int len)
{
	Tcppriv *tpriv;

	tpriv = tcp->priv;


	Tcppriv *priv;
	char *p, *e;
	int i;

	return snprint(buf, len, "%lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud",
		tpriv->tstats.tcpRtoAlgorithm,
		tpriv->tstats.tcpRtoMin,
		tpriv->tstats.tcpRtoMax,
		tpriv->tstats.tcpMaxConn,
		tpriv->tstats.tcpActiveOpens,
		tpriv->tstats.tcpPassiveOpens,
		tpriv->tstats.tcpAttemptFails,
		tpriv->tstats.tcpEstabResets,
		tpriv->tstats.tcpCurrEstab,
		tpriv->tstats.tcpInSegs,
		tpriv->tstats.tcpOutSegs,
		tpriv->tstats.tcpRetransSegs,
		tpriv->tstats.tcpRetransTimeouts,
		tpriv->tstats.InErrs,
		tpriv->tstats.OutRsts);
	priv = tcp->priv;
	p = buf;
	e = p+len;
	for(i = 0; i < Nstats; i++)
		p = seprint(p, e, "%s: %lud\n", statnames[i], priv->stats[i]);
	return p - buf;
}

/*


@@ 2312,7 2317,7 @@ tcpinit(Fs *fs)
	tcp->ipproto = IP_TCPPROTO;
	tcp->nc = Nchans;
	tcp->ptclsize = sizeof(Tcpctl);
	tpriv->tstats.tcpMaxConn = Nchans;
	tpriv->stats[MaxConn] = Nchans;

	Fsproto(fs, tcp);
}

M ip/udp.c => ip/udp.c +40 -26
@@ 45,25 45,35 @@ struct Udphdr
	uchar	udpcksum[2];	/* Checksum */
};

/* MIB II counters */
typedef struct Udpstats Udpstats;
struct Udpstats
enum
{
	ulong	udpInDatagrams;
	ulong	udpNoPorts;
	ulong	udpInErrors;
	ulong	udpOutDatagrams;
	/* MIB II counters */
	InDatagrams,
	NoPorts,
	InErrors,
	OutDatagrams,

	/* non-MIB counters */
	CsumErrs,
	LenErrs,

	Nstats,
};

static char *statnames[] = {
[InDatagrams]	"InDatagrams",
[NoPorts]	"NoPorts",
[InErrors]	"InErrors",
[OutDatagrams]	"OutDatagrams",
[CsumErrs]	"CsumErrs",
[LenErrs]	"LenErrs",
};


typedef struct Udppriv Udppriv;
struct Udppriv
{
	/* MIB counters */
	Udpstats	ustats;

	/* non-MIB stats */
	ulong		csumerr;		/* checksum errors */
	ulong		lenerr;			/* short packet */
	ulong	stats[Nstats];
};

/*


@@ 226,7 236,7 @@ udpkick(Conv *c, int)

	hnputs(uh->udpcksum, ptclcsum(bp, UDP_IPHDR, dlen+UDP_HDRSIZE));

	upriv->ustats.udpOutDatagrams++;
	upriv->stats[OutDatagrams]++;
	ipoput(f, bp, 0, c->ttl, c->tos);
}



@@ 245,7 255,7 @@ udpiput(Proto *udp, uchar *ia, Block *bp)
	upriv = udp->priv;
	f = udp->f;

	upriv->ustats.udpInDatagrams++;
	upriv->stats[InDatagrams]++;

	uh = (Udphdr*)(bp->rp);



@@ 263,7 273,8 @@ udpiput(Proto *udp, uchar *ia, Block *bp)

	if(nhgets(uh->udpcksum)) {
		if(ptclcsum(bp, UDP_IPHDR, len+UDP_PHDRSIZE)) {
			upriv->ustats.udpInErrors++;
			upriv->stats[InErrors]++;
			upriv->stats[CsumErrs]++;
			netlog(f, Logudp, "udp: checksum error %I\n", raddr);
			DPRINT("udp: checksum error %I\n", raddr);
			freeblist(bp);


@@ 319,7 330,7 @@ found:
			}
			c->state = Connected;
		} else {
			upriv->ustats.udpNoPorts++;
			upriv->stats[NoPorts]++;
			qunlock(udp);
			netlog(f, Logudp, "udp: no conv %I!%d -> %I!%d\n", raddr, rport,
				laddr, lport);


@@ 344,7 355,8 @@ found:
		qunlock(c);
		netlog(f, Logudp, "udp: len err %I.%d -> %I.%d\n", raddr, rport,
			laddr, lport);
		upriv->lenerr++;
		upriv->stats[LenErrs]++;
		upriv->stats[InErrors]++;
		return;
	}



@@ 469,14 481,16 @@ udpadvise(Proto *udp, Block *bp, char *msg)
int
udpstats(Proto *udp, char *buf, int len)
{
	Udppriv *upriv;

	upriv = udp->priv;
	return snprint(buf, len, "%lud %lud %lud %lud",
		upriv->ustats.udpInDatagrams,
		upriv->ustats.udpNoPorts,
		upriv->ustats.udpInErrors,
		upriv->ustats.udpOutDatagrams);
	Udppriv *priv;
	char *p, *e;
	int i;

	priv = udp->priv;
	p = buf;
	e = p+len;
	for(i = 0; i < Nstats; i++)
		p = seprint(p, e, "%s: %lud\n", statnames[i], priv->stats[i]);
	return p - buf;
}

void

M pc/i8253.c => pc/i8253.c +4 -2
@@ 47,6 47,8 @@ enum

	Freq=	1193182,	/* Real clock frequency */

	Minusec=20,		/* minimum cycles for a clock interrupt */

	FreqMul=16,		/* extra accuracy in fastticks/Freq calculation; ok up to ~8ghz */
};



@@ 187,8 189,8 @@ clockintrsched0(vlong next)
			i8253.mode = Trigger;
			set--;
		}
		if(set < 3)
			set = 5;
		if(set < Minusec)
			set = Minusec;
		outb(T0cntr, set);	/* low byte */
		outb(T0cntr, set>>8);	/* high byte */
	}else if(i8253.mode != Square){

M port/devcons.c => port/devcons.c +12 -0
@@ 139,6 139,18 @@ sprint(char *s, char *fmt, ...)
	return n;
}

char*
seprint(char *buf, char *e, char *fmt, ...)
{
	char *out;
	va_list arg;

	va_start(arg, fmt);
	out = doprint(buf, e, fmt, arg);
	va_end(arg);
	return out;
}

int noprint;

int

M port/devsd.c => port/devsd.c +47 -14
@@ 211,6 211,8 @@ sdgetunit(SDev* sdev, int subno)
		sdev->enabled = 1;

		snprint(unit->name, NAMELEN, "%s%d", sdev->name, subno);
		strncpy(unit->user, eve, NAMELEN);
		unit->perm = 0555;
		unit->subno = subno;
		unit->dev = sdev;



@@ 314,17 316,28 @@ sd2gen(Chan* c, int i, Dir* dp)
	Qid q;
	vlong l;
	SDpart *pp;
	SDperm *perm;
	SDunit *unit;

	unit = sdunit[UNIT(c->qid)];
	switch(i){
	case Qctl:
		q = (Qid){QID(UNIT(c->qid), PART(c->qid), Qctl), unit->vers};
		devdir(c, q, "ctl", 0, eve, 0640, dp);
		perm = &unit->ctlperm;
		if(perm->user[0] == '\0'){
			strncpy(perm->user, eve, NAMELEN);
			perm->perm = 0640;
		}
		devdir(c, q, "ctl", 0, perm->user, perm->perm, dp);
		return 1;
	case Qraw:
		q = (Qid){QID(UNIT(c->qid), PART(c->qid), Qraw), unit->vers};
		devdir(c, q, "raw", 0, eve, CHEXCL|0600, dp);
		perm = &unit->rawperm;
		if(perm->user[0] == '\0'){
			strncpy(perm->user, eve, NAMELEN);
			perm->perm = CHEXCL|0600;
		}
		devdir(c, q, "raw", 0, perm->user, perm->perm, dp);
		return 1;
	case Qpart:
		pp = &unit->part[PART(c->qid)];


@@ 367,10 380,14 @@ sdgen(Chan* c, Dirtab*, int, int s, Dir* dp)
			return 1;
		}
		if(s < sdnunit){
			if(sdunit[s] == nil && sdindex2unit(s) == nil)
				return 0;
			if((unit = sdunit[s]) == nil){
				if((unit = sdindex2unit(s)) == nil)
					return 0;
			}
			q = (Qid){QID(s, 0, Qunitdir)|CHDIR, 0};
			devdir(c, q, sdunit[s]->name, 0, eve, 0555, dp);
			if(unit->user[0] == '\0')
				strncpy(unit->user, eve, NAMELEN);
			devdir(c, q, unit->name, 0, unit->user, unit->perm, dp);
			return 1;
		}
		s -= sdnunit;


@@ 388,10 405,12 @@ sdgen(Chan* c, Dirtab*, int, int s, Dir* dp)
		/*
		 * Check for media change.
		 * If one has already been detected, sectors will be zero.
		 * If there is one waiting to be detected, online will return > 1.
		 * If there is one waiting to be detected, online
		 * will return > 1.
		 * Online is a bit of a large hammer but does the job.
		 */
		if(unit->sectors == 0 || (unit->dev->ifc->online && unit->dev->ifc->online(unit) > 1))
		if(unit->sectors == 0
		|| (unit->dev->ifc->online && unit->dev->ifc->online(unit) > 1))
			sdinitpart(unit);

		i = s+Qunitbase;


@@ 858,9 877,10 @@ sdwstat(Chan* c, char* dp)
{
	Dir d;
	SDpart *pp;
	SDperm *perm;
	SDunit *unit;

	if((c->qid.path & CHDIR) || TYPE(c->qid) != Qpart)
	if(c->qid.path & CHDIR)
		error(Eperm);

	unit = sdunit[UNIT(c->qid)];


@@ 870,15 890,28 @@ sdwstat(Chan* c, char* dp)
		nexterror();
	}

	pp = &unit->part[PART(c->qid)];
	if(unit->vers+pp->vers != c->qid.vers)
		error(Enonexist);
	if(strncmp(up->user, pp->user, NAMELEN) && !iseve())
	switch(TYPE(c->qid)){
	default:
		error(Eperm);
	case Qctl:
		perm = &unit->ctlperm;
		break;
	case Qraw:
		perm = &unit->rawperm;
		break;
	case Qpart:
		pp = &unit->part[PART(c->qid)];
		if(unit->vers+pp->vers != c->qid.vers)
			error(Enonexist);
		perm = &pp->SDperm;
		break;
	}

	if(strncmp(up->user, perm->user, NAMELEN) && !iseve())
		error(Eperm);
	convM2D(dp, &d);
	strncpy(pp->user, d.uid, NAMELEN);
	pp->perm = d.mode&0777;
	strncpy(perm->user, d.uid, NAMELEN);
	perm->perm = (perm->perm & ~0777) | (d.mode & 0777);

	qunlock(&unit->ctl);
	poperror();

M port/lib.h => port/lib.h +1 -0
@@ 62,6 62,7 @@ extern	int	numbconv(va_list*, Fconv*);
extern	char	*doprint(char*, char*, char*, va_list);
extern	int	fmtinstall(int, int (*)(va_list*, Fconv*));
extern	int	sprint(char*, char*, ...);
extern	char*	seprint(char*, char*, char*, ...);
extern	int	snprint(char*, int, char*, ...);
extern	int	print(char*, ...);


M port/sd.h => port/sd.h +12 -5
@@ 4,15 4,20 @@
typedef struct SDev SDev;
typedef struct SDifc SDifc;
typedef struct SDpart SDpart;
typedef struct SDperm SDperm;
typedef struct SDreq SDreq;
typedef struct SDunit SDunit;

typedef struct SDpart {
	ulong	start;
	ulong	end;
typedef struct SDperm {
	char	name[NAMELEN];
	char	user[NAMELEN];
	ulong	perm;
} SDperm;

typedef struct SDpart {
	ulong	start;
	ulong	end;
	SDperm;
	int	valid;
	ulong	vers;
} SDpart;


@@ 21,18 26,20 @@ typedef struct SDunit {
	SDev*	dev;
	int	subno;
	uchar	inquiry[256];		/* format follows SCSI spec */
	char	name[NAMELEN];
	SDperm;
	Rendez	rendez;

	QLock	ctl;
	ulong	sectors;
	ulong	secsize;
	SDpart*	part;		/* either nil or points at array of size SDnpart */
	SDpart*	part;			/* nil or array of size SDnpart */
	ulong	vers;
	SDperm	ctlperm;

	Lock	rawinuse;		/* really just a test-and-set */
	int	state;
	SDreq*	req;
	SDperm	rawperm;
} SDunit;

typedef struct SDev {