shithub: riscv

Download patch

ref: c2fc2fad133d51bc7dc86af015a20aed11a1817f
parent: ae00ac74659e69a1aee9dc3e3ab20d5ec70b8126
author: cinap_lenrek <cinap_lenrek@localhost>
date: Sun Jul 10 10:14:23 EDT 2011

merge sd changes from 9atom

--- /dev/null
+++ b/sys/include/fis.h
@@ -1,0 +1,163 @@
+#pragma	lib	"libfis.a"
+#pragma	src	"/sys/src/libfis"
+
+/* ata errors */
+enum {
+	Emed	= 1<<0,		/* media error */
+	Enm	= 1<<1,		/* no media */
+	Eabrt	= 1<<2,		/* abort */
+	Emcr	= 1<<3,		/* media change request */
+	Eidnf	= 1<<4,		/* no user-accessible address */
+	Emc	= 1<<5,		/* media change */
+	Eunc	= 1<<6,		/* data error */
+	Ewp	= 1<<6,		/* write protect */
+	Eicrc	= 1<<7,		/* interface crc error */
+
+	Efatal	= Eidnf|Eicrc,	/* must sw reset */
+};
+
+/* ata status */
+enum {
+	ASerr	= 1<<0,		/* error */
+	ASdrq	= 1<<3,		/* request */
+	ASdf	= 1<<5,		/* fault */
+	ASdrdy	= 1<<6,		/* ready */
+	ASbsy	= 1<<7,		/* busy */
+
+	ASobs	= 1<<1|1<<2|1<<4,
+};
+
+enum {
+	/* fis types */
+	H2dev		= 0x27,
+	D2host		= 0x34,
+
+	/* fis flags bits */
+	Fiscmd		= 0x80,
+
+	/* ata bits */
+	Ataobs		= 0xa0,
+	Atalba		= 0x40,
+
+	/* nominal fis size (fits any fis) */
+	Fissize		= 0x20,
+};
+
+/* sata device-to-host (0x27) fis layout */
+enum {
+	Ftype,
+	Fflags,
+	Fcmd,
+	Ffeat,
+	Flba0,
+	Flba8,
+	Flba16,
+	Fdev,
+	Flba24,
+	Flba32,
+	Flba40,
+	Ffeat8,
+	Fsc,
+	Fsc8,
+	Ficc,		/* isochronous cmd completion */
+	Fcontrol,
+};
+
+/* sata host-to-device fis (0x34) differences */
+enum{
+	Fioport	= 1,
+	Fstatus,
+	Frerror,
+};
+
+/* ata protcol type */
+enum{
+	Pnd	= 0<<0,	/* data direction */
+	Pin	= 1<<0,
+	Pout	= 2<<0,
+	Pdatam	= 3<<0,
+
+	Ppio	= 1<<2,	/* ata protocol */
+	Pdma	= 2<<2,
+	Pdmq	= 3<<2,
+	Preset	= 4<<2,
+	Pdiag	= 5<<2,
+	Ppkt	= 6<<2,
+	Pprotom	= 7<<2,
+
+	P48	= 0<<5,	/* command “size” */
+	P28	= 1<<5,
+	Pcmdszm	= 1<<5,
+
+	Pssn	= 0<<6,	/* sector size */
+	P512	= 1<<6,
+	Pssm	= 1<<6,
+};
+
+typedef struct Sfis Sfis;
+struct Sfis {
+	ushort	feat;
+	uchar	udma;
+	uchar	speeds;
+	uint	sig;
+	uint	lsectsz;
+	uint	physshift;	/* log2(log/phys) */
+	uint	c;		/* disgusting, no? */
+	uint	h;
+	uint	s;
+};
+
+enum {
+	Dlba	= 1<<0,	/* required for sata */
+	Dllba 	= 1<<1,
+	Dsmart	= 1<<2,
+	Dpower	= 1<<3,
+	Dnop	= 1<<4,
+	Datapi	= 1<<5,
+	Datapi16= 1<<6,
+	Data8	= 1<<7,
+	Dsct	= 1<<8,
+	Dnflag	= 9,
+};
+
+enum {
+	Pspinup	= 1<<0,
+	Pidready	= 1<<1,
+};
+
+void	setfissig(Sfis*, uint);
+int	txmodefis(Sfis*, uchar*, uchar);
+int	atapirwfis(Sfis*, uchar*, uchar*, int, int);
+int	featfis(Sfis*, uchar*, uchar);
+int	flushcachefis(Sfis*, uchar*);
+int	identifyfis(Sfis*, uchar*);
+int	nopfis(Sfis*, uchar*, int);
+int	rwfis(Sfis*, uchar*, int, int, uvlong);
+void	skelfis(uchar*);
+void	sigtofis(Sfis*, uchar*);
+uvlong	fisrw(Sfis*, uchar*, int*);
+
+void	idmove(char*, ushort*, int);
+vlong	idfeat(Sfis*, ushort*);
+uvlong	idwwn(Sfis*, ushort*);
+int	idss(Sfis*, ushort*);
+int	idpuis(ushort*);
+ushort	id16(ushort*, int);
+uint	id32(ushort*, int);
+uvlong	id64(ushort*, int);
+char	*pflag(char*, char*, Sfis*);
+uint	fistosig(uchar*);
+
+/* scsi */
+typedef struct Cfis Cfis;
+struct Cfis {
+	uchar	phyid;
+	uchar	encid[8];
+	uchar	tsasaddr[8];
+	uchar	ssasaddr[8];
+	uchar	ict[2];
+};
+
+void	smpskelframe(Cfis*, uchar*, int);
+uint	sashash(uvlong);
+uchar	*sasbhash(uchar*, uchar*);
--- a/sys/src/9/pc/ahci.h
+++ b/sys/src/9/pc/ahci.h
@@ -1,34 +1,8 @@
 /*
  * advanced host controller interface (sata)
- * © 2007  coraid, inc
+ * © 2007-9  coraid, inc
  */
 
-/* ata errors */
-enum {
-	Emed	= 1<<0,		/* media error */
-	Enm	= 1<<1,		/* no media */
-	Eabrt	= 1<<2,		/* abort */
-	Emcr	= 1<<3,		/* media change request */
-	Eidnf	= 1<<4,		/* no user-accessible address */
-	Emc	= 1<<5,		/* media change */
-	Eunc	= 1<<6,		/* data error */
-	Ewp	= 1<<6,		/* write protect */
-	Eicrc	= 1<<7,		/* interface crc error */
-
-	Efatal	= Eidnf|Eicrc,	/* must sw reset */
-};
-
-/* ata status */
-enum {
-	ASerr	= 1<<0,		/* error */
-	ASdrq	= 1<<3,		/* request */
-	ASdf	= 1<<5,		/* fault */
-	ASdrdy	= 1<<6,		/* ready */
-	ASbsy	= 1<<7,		/* busy */
-
-	ASobs	= 1<<1|1<<2|1<<4,
-};
-
 /* pci configuration */
 enum {
 	Abar	= 5,
@@ -47,19 +21,18 @@
 
 /* cap bits: supported features */
 enum {
-	Hs64a	= 1<<31,	/* 64-bit addressing */
-	Hsncq	= 1<<30,	/* ncq */
-	Hssntf	= 1<<29,	/* snotification reg. */
-	Hsmps	= 1<<28,	/* mech pres switch */
-	Hsss	= 1<<27,	/* staggered spinup */
-	Hsalp	= 1<<26,	/* aggressive link pm */
-	Hsal	= 1<<25,	/* activity led */
-	Hsclo	= 1<<24,	/* command-list override */
+	H64a	= 1<<31,	/* 64-bit addressing */
+	Hncq	= 1<<30,	/* ncq */
+	Hsntf	= 1<<29,	/* snotification reg. */
+	Hmps	= 1<<28,	/* mech pres switch */
+	Hss	= 1<<27,	/* staggered spinup */
+	Halp	= 1<<26,	/* aggressive link pm */
+	Hal	= 1<<25,	/* activity led */
+	Hclo	= 1<<24,	/* command-list override */
 	Hiss	= 1<<20,	/* for interface speed */
-//	Hsnzo	= 1<<19,
-	Hsam	= 1<<18,	/* ahci-mode only */
-	Hspm	= 1<<17,	/* port multiplier */
-//	Hfbss	= 1<<16,
+	Ham	= 1<<18,	/* ahci-mode only */
+	Hpm	= 1<<17,	/* port multiplier */
+	Hfbs	= 1<<16,	/* fis-based switching */
 	Hpmb	= 1<<15,	/* multiple-block pio */
 	Hssc	= 1<<14,	/* slumber state */
 	Hpsc	= 1<<13,	/* partial-slumber state */
@@ -66,7 +39,7 @@
 	Hncs	= 1<<8,		/* n command slots */
 	Hcccs	= 1<<7,		/* coal */
 	Hems	= 1<<6,		/* enclosure mgmt. */
-	Hsxs	= 1<<5,		/* external sata */
+	Hxs	= 1<<5,		/* external sata */
 	Hnp	= 1<<0,		/* n ports */
 };
 
@@ -77,6 +50,29 @@
 	Hhr	= 1<<0,		/* hba reset */
 };
 
+/* cap2 bits */
+enum {
+	Apts	= 1<<2,	/* automatic partial to slumber */
+	Nvmp	= 1<<1,	/* nvmhci present; nvram */
+	Boh	= 1<<0,	/* bios/os handoff supported */
+};
+
+/* emctl bits */
+enum {
+	Pm	= 1<<27,	/* port multiplier support */
+	Alhd	= 1<<26,	/* activity led hardware driven */
+	Xonly	= 1<<25,	/* rx messages not supported */
+	Smb	= 1<<24,	/* single msg buffer; rx limited */
+	Esgpio	= 1<<19,	/* sgpio messages supported */
+	Eses2	= 1<<18,	/* ses-2 supported */
+	Esafte	= 1<<17,	/* saf-te supported */
+	Elmt	= 1<<16,	/* led msg types support */
+	Emrst	= 1<<9,	/* reset all em logic */
+	Tmsg	= 1<<8,	/* transmit message */
+	Mr	= 1<<0,	/* message rx'd */
+	Emtype	= Esgpio | Eses2 | Esafte | Elmt,
+};
+
 typedef struct {
 	ulong	cap;
 	ulong	ghc;
@@ -87,6 +83,8 @@
 	ulong	cccports;
 	ulong	emloc;
 	ulong	emctl;
+	ulong	cap2;
+	ulong	bios;
 } Ahba;
 
 enum {
@@ -147,6 +145,8 @@
 	Aalpe 	= 1<<26,	/* aggressive link pm enable */
 	Adlae	= 1<<25,	/* drive led on atapi */
 	Aatapi	= 1<<24,	/* device is atapi */
+	Apste	= 1<<23,	/* automatic slumber to partial cap */
+	Afbsc	= 1<<22,	/* fis-based switching capable */
 	Aesp	= 1<<21,	/* external sata port */
 	Acpd	= 1<<20,	/* cold presence detect */
 	Ampsp	= 1<<19,	/* mechanical pres. */
@@ -164,6 +164,7 @@
 	Ast	= 1<<0,		/* start */
 
 	Arun	= Ast|Acr|Afre|Afr,
+	Apwr	= Apod|Asud,
 };
 
 /* ctl register bits */
@@ -173,13 +174,41 @@
 	Adet	= 1<<0,		/* device detection */
 };
 
+/* sstatus register bits */
+enum{
+	/* sstatus det */
+	Smissing		= 0<<0,
+	Spresent		= 1<<0,
+	Sphylink		= 3<<0,
+	Sbist		= 4<<0,
+	Smask		= 7<<0,
+
+	/* sstatus speed */
+	Gmissing		= 0<<4,
+	Gi		= 1<<4,
+	Gii		= 2<<4,
+	Giii		= 3<<4,
+	Gmask		= 7<<4,
+
+	/* sstatus ipm */
+	Imissing		= 0<<8,
+	Iactive		= 1<<8,
+	Isleepy		= 2<<8,
+	Islumber		= 6<<8,
+	Imask		= 7<<8,
+
+	SImask		= Smask | Imask,
+	SSmask		= Smask | Isleepy,
+};
+
 #define	sstatus	scr0
 #define	sctl	scr2
 #define	serror	scr1
 #define	sactive	scr3
+#define	ntf	scr4
 
 typedef struct {
-	ulong	list;		/* PxCLB must be 1kb aligned. */
+	ulong	list;		/* PxCLB must be 1kb aligned */
 	ulong	listhi;
 	ulong	fis;		/* 256-byte aligned */
 	ulong	fishi;
@@ -194,9 +223,10 @@
 	ulong	scr1;
 	ulong	scr3;
 	ulong	ci;		/* command issue */
-	ulong	ntf;
-	uchar	res2[8];
-	ulong	vendor;
+	ulong	scr4;
+	ulong	fbs;
+	ulong	res2[11];
+	ulong	vendor[4];
 } Aport;
 
 /* in host's memory; not memory mapped */
@@ -244,26 +274,49 @@
 	Aprdt	prdt;
 } Actab;
 
+/* enclosure message header */
 enum {
-	Ferror	= 1,
-	Fdone	= 2,
+	Mled	= 0,
+	Msafte	= 1,
+	Mses2	= 2,
+	Msgpio	= 3,
 };
 
+typedef struct {
+	uchar	dummy;
+	uchar	msize;
+	uchar	dsize;
+	uchar	type;
+	uchar	hba;		/* bits 0:4 are the port */
+	uchar	pm;
+	uchar	led[2];
+} Aledmsg;
+
 enum {
-	Dllba 	= 1,
-	Dsmart	= 1<<1,
-	Dpower	= 1<<2,
-	Dnop	= 1<<3,
-	Datapi	= 1<<4,
-	Datapi16= 1<<5,
+	Aled	= 1<<0,
+	Locled	= 1<<3,
+	Errled	= 1<<6,
+
+	Ledoff	= 0,
+	Ledon	= 1,
 };
 
 typedef struct {
+	uint	encsz;
+	ulong	*enctx;
+	ulong	*encrx;
+} Aenc;
+
+enum {
+	Ferror	= 1,
+	Fdone	= 2,
+};
+
+typedef struct {
 	QLock;
 	Rendez;
 	uchar	flag;
-	uchar	feat;
-	uchar	smart;
+	Sfis;
 	Afis	fis;
 	Alist	*list;
 	Actab	*ctab;
--- a/sys/src/9/pc/mkfile
+++ b/sys/src/9/pc/mkfile
@@ -67,6 +67,7 @@
 	/$objtype/lib/libc.a\
 	/$objtype/lib/libsec.a\
 	/$objtype/lib/libmp.a\
+	/$objtype/lib/libfis.a\
 
 ETHER=`{echo devether.c ether*.c | sed 's/\.c/.'$O'/g'}
 VGA=`{echo devvga.c screen.c vga*.c | sed 's/\.c/.'$O'/g'}
--- a/sys/src/9/pc/pccpuf
+++ b/sys/src/9/pc/pccpuf
@@ -75,11 +75,13 @@
 	uarti8250
 	uartpci		pci
 
-	sdata		pci sdscsi
+	sdaoe
+	sdide		pci sdscsi
 	sd53c8xx	pci sdscsi
 	sdmylex		pci sdscsi
-	sdiahci		pci sdscsi
-	sdaoe
+	sdiahci		pci sdscsi led
+	sdodin		pci sdscsi led
+	sdloop
 
 	vga3dfx		+cur
 	vgaark2000pv	+cur
--- a/sys/src/9/pc/pcf
+++ b/sys/src/9/pc/pcf
@@ -81,11 +81,13 @@
 	archmp		mp apic
 	mtrr
 
-	sdata		pci sdscsi
+	sdaoe
+	sdide		pci sdscsi
 	sd53c8xx	pci sdscsi
 	sdmylex		pci sdscsi
-	sdiahci		pci sdscsi
-	sdaoe
+	sdiahci		pci sdscsi led
+	sdodin		pci sdscsi led
+	sdloop
 
 	uarti8250
 	uartpci		pci
--- a/sys/src/9/pc/sdata.c
+++ /dev/null
@@ -1,2312 +1,0 @@
-#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 "../port/sd.h"
-
-#define	HOWMANY(x, y)	(((x)+((y)-1))/(y))
-#define ROUNDUP(x, y)	(HOWMANY((x), (y))*(y))
-
-extern SDifc sdataifc;
-
-enum {
-	DbgCONFIG	= 0x0001,	/* detected drive config info */
-	DbgIDENTIFY	= 0x0002,	/* detected drive identify info */
-	DbgSTATE	= 0x0004,	/* dump state on panic */
-	DbgPROBE	= 0x0008,	/* trace device probing */
-	DbgDEBUG	= 0x0080,	/* the current problem... */
-	DbgINL		= 0x0100,	/* That Inil20+ message we hate */
-	Dbg48BIT	= 0x0200,	/* 48-bit LBA */
-	DbgBsy		= 0x0400,	/* interrupt but Bsy (shared IRQ) */
-};
-#define DEBUG		(DbgDEBUG|DbgSTATE)
-
-enum {					/* I/O ports */
-	Data		= 0,
-	Error		= 1,		/* (read) */
-	Features	= 1,		/* (write) */
-	Count		= 2,		/* sector count<7-0>, sector count<15-8> */
-	Ir		= 2,		/* interrupt reason (PACKET) */
-	Sector		= 3,		/* sector number */
-	Lbalo		= 3,		/* LBA<7-0>, LBA<31-24> */
-	Cyllo		= 4,		/* cylinder low */
-	Bytelo		= 4,		/* byte count low (PACKET) */
-	Lbamid		= 4,		/* LBA<15-8>, LBA<39-32> */
-	Cylhi		= 5,		/* cylinder high */
-	Bytehi		= 5,		/* byte count hi (PACKET) */
-	Lbahi		= 5,		/* LBA<23-16>, LBA<47-40> */
-	Dh		= 6,		/* Device/Head, LBA<27-24> */
-	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 */
-	Crs48		= 0x24,		/* Read Sectors Ext */
-	Crd48		= 0x25,		/* Read w/ DMA Ext */
-	Crdq48		= 0x26,		/* Read w/ DMA Queued Ext */
-	Crsm48		= 0x29,		/* Read Multiple Ext */
-	Cws		= 0x30,		/* Write Sectors */
-	Cws48		= 0x34,		/* Write Sectors Ext */
-	Cwd48		= 0x35,		/* Write w/ DMA Ext */
-	Cwdq48		= 0x36,		/* Write w/ DMA Queued Ext */
-	Cwsm48		= 0x39,		/* Write Multiple Ext */
-	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 */
-	Cstandby	= 0xE2,		/* Standby */
-	Cid		= 0xEC,		/* Identify Device */
-	Csf		= 0xEF,		/* Set Features */
-};
-
-enum {					/* Device Control */
-	Nien		= 0x02,		/* (not) Interrupt Enable */
-	Srst		= 0x04,		/* Software Reset */
-	Hob		= 0x80,		/* High Order Bit [sic] */
-};
-
-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,	/* End of Transfer */
-};
-
-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 */
-	Ilba		= 60,		/* 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 */
-	Icsfs		= 82,		/* command set/feature supported */
-	Icsfe		= 85,		/* command set/feature enabled */
-	Iudma		= 88,		/* ultra DMA mode */
-	Ierase		= 89,		/* time for security erase */
-	Ieerase		= 90,		/* time for enhanced security erase */
-	Ipower		= 91,		/* current advanced power management */
-	Ilba48		= 100,		/* 48-bit LBA size (64 bits in 100-103) */
-	Irmsn		= 127,		/* removable status notification */
-	Isecstat	= 128,		/* security status */
-	Icfapwr		= 160,		/* CFA power mode */
-	Imediaserial	= 176,		/* current media serial number */
-	Icksum		= 255,		/* checksum */
-};
-
-enum {					/* bit masks for config identify info */
-	Mpktsz		= 0x0003,	/* packet command size */
-	Mincomplete	= 0x0004,	/* incomplete information */
-	Mdrq		= 0x0060,	/* DRQ type */
-	Mrmdev		= 0x0080,	/* device is removable */
-	Mtype		= 0x1F00,	/* device type */
-	Mproto		= 0x8000,	/* command protocol */
-};
-
-enum {					/* bit masks for capabilities identify info */
-	Mdma		= 0x0100,	/* DMA supported */
-	Mlba		= 0x0200,	/* LBA supported */
-	Mnoiordy	= 0x0400,	/* IORDY may be disabled */
-	Miordy		= 0x0800,	/* IORDY supported */
-	Msoftrst	= 0x1000,	/* needs soft reset when Bsy */
-	Mstdby		= 0x2000,	/* standby supported */
-	Mqueueing	= 0x4000,	/* queueing overlap supported */
-	Midma		= 0x8000,	/* interleaved DMA supported */
-};
-
-enum {					/* bit masks for supported/enabled features */
-	Msmart		= 0x0001,
-	Msecurity	= 0x0002,
-	Mrmmedia	= 0x0004,
-	Mpwrmgmt	= 0x0008,
-	Mpkt		= 0x0010,
-	Mwcache		= 0x0020,
-	Mlookahead	= 0x0040,
-	Mrelirq		= 0x0080,
-	Msvcirq		= 0x0100,
-	Mreset		= 0x0200,
-	Mprotected	= 0x0400,
-	Mwbuf		= 0x1000,
-	Mrbuf		= 0x2000,
-	Mnop		= 0x4000,
-	Mmicrocode	= 0x0001,
-	Mqueued		= 0x0002,
-	Mcfa		= 0x0004,
-	Mapm		= 0x0008,
-	Mnotify		= 0x0010,
-	Mstandby	= 0x0020,
-	Mspinup		= 0x0040,
-	Mmaxsec		= 0x0100,
-	Mautoacoustic	= 0x0200,
-	Maddr48		= 0x0400,
-	Mdevconfov	= 0x0800,
-	Mflush		= 0x1000,
-	Mflush48	= 0x2000,
-	Msmarterror	= 0x0001,
-	Msmartselftest	= 0x0002,
-	Mmserial	= 0x0004,
-	Mmpassthru	= 0x0008,
-	Mlogging	= 0x0020,
-};
-
-typedef struct Ctlr Ctlr;
-typedef struct Drive Drive;
-
-typedef struct Prd {			/* Physical Region Descriptor */
-	ulong	pa;			/* Physical Base Address */
-	int	count;
-} Prd;
-
-enum {
-	BMspan		= 64*1024,	/* must be power of 2 <= 64*1024 */
-
-	Nprd		= SDmaxio/BMspan+2,
-};
-
-typedef struct Ctlr {
-	int	cmdport;
-	int	ctlport;
-	int	irq;
-	int	tbdf;
-	int	bmiba;			/* bus master interface base address */
-	int	maxio;			/* sector count transfer maximum */
-	int	span;			/* don't span this boundary with dma */
-
-	Pcidev*	pcidev;
-	void	(*ienable)(Ctlr*);
-	void	(*idisable)(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 */
-	vlong	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;
-	int	flags;			/* internal flags */
-} Drive;
-
-enum {					/* internal flags */
-	Lba48		= 0x1,		/* LBA48 mode */
-	Lba48always	= 0x2,		/* ... */
-};
-enum {
-	Last28		= (1<<28) - 1 - 1, /* all-ones mask is not addressible */
-};
-
-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, vlong 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("sdata: 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 -> %lld, 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 = vseprint(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){
-			/* nothing to do */
-		}
-		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
-atacsf(Drive* drive, vlong csf, int supported)
-{
-	ushort *info;
-	int cmdset, i, x;
-
-	if(supported)
-		info = &drive->info[Icsfs];
-	else
-		info = &drive->info[Icsfe];
-
-	for(i = 0; i < 3; i++){
-		x = (csf>>(16*i)) & 0xFFFF;
-		if(x == 0)
-			continue;
-		cmdset = info[i];
-		if(cmdset == 0 || cmdset == 0xFFFF)
-			return 0;
-		return cmdset & x;
-	}
-
-	return 0;
-}
-*/
-
-static int
-atadone(void* arg)
-{
-	return ((Ctlr*)arg)->done;
-}
-
-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] & 0x7F7F;
-		drive->dma = (dma>>8) & dma;
-		if(drive->dma)
-			drive->dma |= 'U'<<16;
-	}
-	if(!getconf("*nodma"))
-		drive->dmactl = drive->dma;
-	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, 400*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)
-{
-	Drive *drive;
-	int as, i, pkt;
-	uchar buf[512], *p;
-	ushort iconfig, *sp;
-
-	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;
-
-	/*
-	 * Beware the CompactFlash Association feature set.
-	 * Now, why this value in Iconfig just walks all over the bit
-	 * definitions used in the other parts of the ATA/ATAPI standards
-	 * is a mystery and a sign of true stupidity on someone's part.
-	 * Anyway, the standard says if this value is 0x848A then it's
-	 * CompactFlash and it's NOT a packet device.
-	 */
-	iconfig = drive->info[Iconfig];
-	if(iconfig != 0x848A && (iconfig & 0xC000) == 0x8000){
-		if(iconfig & 0x01)
-			drive->pkt = 16;
-		else
-			drive->pkt = 12;
-	}
-	else{
-		if(drive->info[Ivalid] & 0x0001){
-			drive->c = drive->info[Iccyl];
-			drive->h = drive->info[Ichead];
-			drive->s = drive->info[Icsec];
-		}
-		else{
-			drive->c = drive->info[Ilcyl];
-			drive->h = drive->info[Ilhead];
-			drive->s = drive->info[Ilsec];
-		}
-		if(drive->info[Icapabilities] & Mlba){
-			if(drive->info[Icsfs+1] & Maddr48){
-				drive->sectors = drive->info[Ilba48]
-					| (drive->info[Ilba48+1]<<16)
-					| ((vlong)drive->info[Ilba48+2]<<32);
-				drive->flags |= Lba48;
-			}
-			else{
-				drive->sectors = (drive->info[Ilba+1]<<16)
-					 |drive->info[Ilba];
-			}
-			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 port %uX config %4.4uX capabilities %4.4uX",
-			dev, cmdport, iconfig, drive->info[Icapabilities]);
-		print(" mwdma %4.4uX", drive->info[Imwdma]);
-		if(drive->info[Ivalid] & 0x04)
-			print(" udma %4.4uX", drive->info[Iudma]);
-		print(" dma %8.8uX rwm %ud", drive->dma, drive->rwm);
-		if(drive->flags&Lba48)
-			print("\tLLBA sectors %lld", drive->sectors);
-		print("\n");
-	}
-
-	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;
-	static int nonlegacy = 'C';
-	
-	if(ioalloc(cmdport, 8, 0, "atacmd") < 0) {
-		print("ataprobe: Cannot allocate %X\n", cmdport);
-		return nil;
-	}
-	if(ioalloc(ctlport+As, 1, 0, "atactl") < 0){
-		print("ataprobe: Cannot allocate %X\n", ctlport + As);
-		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;
-	}
-	memset(ctlr, 0, sizeof(Ctlr));
-	if((sdev = malloc(sizeof(SDev))) == nil){
-		free(ctlr);
-		free(drive);
-		goto release;
-	}
-	memset(sdev, 0, sizeof(SDev));
-	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 */
-	
-	switch(cmdport){
-	default:
-		sdev->idno = nonlegacy;
-		break;
-	case 0x1F0:
-		sdev->idno = 'C';
-		nonlegacy = 'E';
-		break;
-	case 0x170:
-		sdev->idno = 'D';
-		nonlegacy = 'E';
-		break;
-	}
-	sdev->ifc = &sdataifc;
-	sdev->ctlr = ctlr;
-	sdev->nunit = 2;
-	ctlr->sdev = sdev;
-
-	return sdev;
-}
-
-static void
-ataclear(SDev *sdev)
-{
-	Ctlr* ctlr;
-
-	ctlr = sdev->ctlr;
-	iofree(ctlr->cmdport);
-	iofree(ctlr->ctlport + As);
-
-	if (ctlr->drive[0])
-		free(ctlr->drive[0]);
-	if (ctlr->drive[1])
-		free(ctlr->drive[1]);
-	if (sdev->name)
-		free(sdev->name);
-	if (sdev->unitflg)
-		free(sdev->unitflg);
-	if (sdev->unit)
-		free(sdev->unit);
-	free(ctlr);
-	free(sdev);
-}
-
-static char *
-atastat(SDev *sdev, char *p, char *e)
-{
-	Ctlr *ctlr = sdev->ctlr;
-
-	return seprint(p, e, "%s ata port %X ctl %X irq %d\n", 
-		    	       sdev->name, ctlr->cmdport, ctlr->ctlport, ctlr->irq);
-}
-
-static SDev*
-ataprobew(DevConf *cf)
-{
-	char *p;
-	ISAConf isa;
-	
-	if (cf->nports != 2)
-		error(Ebadarg);
-
-	memset(&isa, 0, sizeof isa);
-	isa.port = cf->ports[0].port;
-	isa.irq = cf->intnum;
-	if((p=strchr(cf->type, '/')) == nil || pcmspecial(p+1, &isa) < 0)
-		error("cannot find controller");
-
-	return ataprobe(cf->ports[0].port, cf->ports[1].port, cf->intnum);
-}
-
-/*
- * These are duplicated with sdsetsense, etc., in devsd.c, but
- * those assume that the disk is not SCSI while in fact here
- * ata drives are not SCSI but ATAPI ones kind of are.
- */
-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 int
-atastandby(Drive* drive, int period)
-{
-	Ctlr* ctlr;
-	int cmdport, done;
-
-	ctlr = drive->ctlr;
-	drive->command = Cstandby;
-	qlock(ctlr);
-
-	cmdport = ctlr->cmdport;
-	ilock(ctlr);
-	outb(cmdport+Count, period);
-	outb(cmdport+Dh, drive->dev);
-	ctlr->done = 0;
-	ctlr->curdrive = drive;
-	ctlr->command = Cstandby;	/* debugging */
-	outb(cmdport+Command, Cstandby);
-	iunlock(ctlr);
-
-	while(waserror())
-		;
-	tsleep(ctlr, atadone, ctlr, 60*1000);
-	poperror();
-
-	done = ctlr->done;
-	qunlock(ctlr);
-
-	if(!done || (drive->status & Err))
-		return atasetsense(drive, SDcheck, 4, 8, drive->error);
-	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(drive->info[Icsfs] & Mnop)
-		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, i, span;
-
-	ctlr = drive->ctlr;
-	pa = PCIWADDR(drive->data);
-	if(pa & 0x03)
-		return -1;
-
-	/*
-	 * Sometimes drives identify themselves as being DMA capable
-	 * although they are not on a busmastering controller.
-	 */
-	prd = ctlr->prdt;
-	if(prd == nil){
-		drive->dmactl = 0;
-		print("disabling dma: not on a busmastering controller\n");
-		return -1;
-	}
-
-	for(i = 0; len && i < Nprd; i++){
-		prd->pa = pa;
-		span = ROUNDUP(pa, ctlr->span);
-		if(span == pa)
-			span += ctlr->span;
-		count = span - pa;
-		if(count >= len){
-			prd->count = PrdEOT|len;
-			break;
-		}
-		prd->count = count;
-		len -= count;
-		pa += count;
-		prd++;
-	}
-	if(i == Nprd)
-		(prd-1)->count |= PrdEOT;
-
-	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 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);
-
-	as = ataready(cmdport, ctlport, drive->dev, Bsy|Drq, Drdy, 107*1000);
-	/* used to test as&Chk as failure too, but some CD readers use that for media change */
-	if(as < 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] & Mdrq) != 0x0020){
-		microdelay(1);
-		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Chk, 4*1000);
-		if(as < 0 || (as & (Bsy|Chk))){
-			drive->status = as<0 ? 0 : as;
-			ctlr->curdrive = nil;
-			ctlr->done = 1;
-			r = SDtimeout;
-		}else
-			atapktinterrupt(drive);
-	}
-	iunlock(ctlr);
-
-	while(waserror())
-		;
-	if(!drive->pktdma)
-		sleep(ctlr, atadone, ctlr);
-	else for(timeo = 0; !ctlr->done; timeo++){
-		tsleep(ctlr, atadone, ctlr, 1000);
-		if(ctlr->done)
-			break;
-		ilock(ctlr);
-		atadmainterrupt(drive, 0);
-		if(!drive->error && timeo > 20){
-			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 uchar cmd48[256] = {
-	[Crs]	Crs48,
-	[Crd]	Crd48,
-	[Crdq]	Crdq48,
-	[Crsm]	Crsm48,
-	[Cws]	Cws48,
-	[Cwd]	Cwd48,
-	[Cwdq]	Cwdq48,
-	[Cwsm]	Cwsm48,
-};
-
-static int
-atageniostart(Drive* drive, uvlong lba)
-{
-	Ctlr *ctlr;
-	uchar cmd;
-	int as, c, cmdport, ctlport, h, len, s, use48;
-
-	use48 = 0;
-	if((drive->flags&Lba48always) || lba > Last28 || drive->count > 256){
-		if(!(drive->flags & Lba48))
-			return -1;
-		use48 = 1;
-		c = h = s = 0;
-	}
-	else 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, Drdy, 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;
-	cmd = drive->command;
-	if(use48){
-		outb(cmdport+Count, drive->count>>8);
-		outb(cmdport+Count, drive->count);
-		outb(cmdport+Lbalo, lba>>24);
-		outb(cmdport+Lbalo, lba);
-		outb(cmdport+Lbamid, lba>>32);
-		outb(cmdport+Lbamid, lba>>8);
-		outb(cmdport+Lbahi, lba>>40);
-		outb(cmdport+Lbahi, lba>>16);
-		outb(cmdport+Dh, drive->dev|Lba);
-		cmd = cmd48[cmd];
-
-		if(DEBUG & Dbg48BIT)
-			print("using 48-bit commands\n");
-	}
-	else{
-		outb(cmdport+Count, drive->count);
-		outb(cmdport+Sector, s);
-		outb(cmdport+Cyllo, c);
-		outb(cmdport+Cylhi, c>>8);
-		outb(cmdport+Dh, drive->dev|h);
-	}
-	ctlr->done = 0;
-	ctlr->curdrive = drive;
-	ctlr->command = drive->command;	/* debugging */
-	outb(cmdport+Command, cmd);
-
-	switch(drive->command){
-	case Cws:
-	case Cwsm:
-		microdelay(1);
-		/* 10*1000 for flash ide drives - maybe detect them? */
-		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 10*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;
-		print("atagenioretry: disabling dma\n");
-	}
-	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 clen)
-{
-	uchar *p;
-	Ctlr *ctlr;
-	vlong lba, len;
-	int count, maxio;
-
-	/*
-	 * 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 0x9E:			/* long 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>>56;
-		*p++ = len>>48;
-		*p++ = len>>40;
-		*p++ = len>>32;
-		*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 += 12;
-		return SDok;
-
-	case 0x28:			/* read */
-	case 0x88:
-	case 0x2a:			/* write */
-	case 0x8a:
-		break;
-
-	case 0x5A:
-		return atamodesense(drive, cmd);
-	}
-
-	ctlr = drive->ctlr;
-	if(clen == 16){
-		/* ata commands only go to 48-bit lba */
-		if(cmd[2] || cmd[3])
-			return atasetsense(drive, SDcheck, 3, 0xc, 2);
-		lba = (uvlong)cmd[4]<<40 | (uvlong)cmd[5]<<32;
-		lba |= cmd[6]<<24 | cmd[7]<<16 | cmd[8]<<8 | cmd[9];
-		count = cmd[10]<<24 | cmd[11]<<16 | cmd[12]<<8 | cmd[13];
-	}else{
-		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);
-	if(ctlr->maxio)
-		maxio = ctlr->maxio;
-	else if(drive->flags & Lba48)
-		maxio = 65536;
-	else
-		maxio = 256;
-	while(count){
-		if(count > maxio)
-			drive->count = maxio;
-		else
-			drive->count = count;
-		if(atageniostart(drive, lba)){
-			ilock(ctlr);
-			atanop(drive, 0);
-			iunlock(ctlr);
-			qunlock(ctlr);
-			return atagenioretry(drive);
-		}
-
-		while(waserror())
-			;
-		tsleep(ctlr, atadone, ctlr, 60*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);
-			qunlock(ctlr);
-			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 & DbgBsy)
-			print("IBsy+");
-		return;
-	}
-	cmdport = ctlr->cmdport;
-	status = inb(cmdport+Status);
-	if((drive = ctlr->curdrive) == nil){
-		iunlock(ctlr);
-		if((DEBUG & DbgINL) && 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;
-
-	case Cstandby:
-		ctlr->done = 1;
-		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;
-	SDev *legacy[2], *sdev, *head, *tail;
-	int channel, ispc87415, maxio, pi, r, span;
-
-	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).
-		 * Sub-class code 0x04 is 'RAID controller', e.g. VIA VT8237.
-		 */
-		if(p->ccrb != 0x01)
-			continue;
-		if(p->ccru != 0x01 && p->ccru != 0x04 && p->ccru != 0x80)
-			continue;
-		pi = p->ccrp;
-		ispc87415 = 0;
-		maxio = 0;
-		span = BMspan;
-
-		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 */
-		case (0x4D30<<16)|0x105A:	/* Promise PDC202xx */
-		case (0x4D68<<16)|0x105A:	/* Promise PDC20268 */
-		case (0x4D69<<16)|0x105A:	/* Promise Ultra/133 TX2 */
-		case (0x3373<<16)|0x105A:	/* Promise 20378 RAID */
-		case (0x3149<<16)|0x1106:	/* VIA VT8237 SATA/RAID */
-		case (0x3112<<16)|0x1095:	/* SiI 3112 SATA/RAID */
-			maxio = 15;
-			span = 8*1024;
-			/*FALLTHROUGH*/
-		case (0x0680<<16)|0x1095:	/* SiI 0680/680A PATA133 ATAPI/RAID */
-		case (0x3114<<16)|0x1095:	/* SiI 3114 SATA/RAID */
-			pi = 0x85;
-			break;
-		case (0x0004<<16)|0x1103:	/* HighPoint HPT366 */
-			pi = 0x85;
-			/*
-			 * Turn off fast interrupt prediction.
-			 */
-			if((r = pcicfgr8(p, 0x51)) & 0x80)
-				pcicfgw8(p, 0x51, r & ~0x80);
-			if((r = pcicfgr8(p, 0x55)) & 0x80)
-				pcicfgw8(p, 0x55, r & ~0x80);
-			break;
-		case (0x0640<<16)|0x1095:	/* CMD 640B */
-			/*
-			 * Bugfix code here...
-			 */
-			break;
-		case (0x7441<<16)|0x1022:	/* AMD 768 */
-			/*
-			 * Set:
-			 *	0x41	prefetch, postwrite;
-			 *	0x43	FIFO configuration 1/2 and 1/2;
-			 *	0x44	status register read retry;
-			 *	0x46	DMA read and end of sector flush.
-			 */
-			r = pcicfgr8(p, 0x41);
-			pcicfgw8(p, 0x41, r|0xF0);
-			r = pcicfgr8(p, 0x43);
-			pcicfgw8(p, 0x43, (r & 0x90)|0x2A);
-			r = pcicfgr8(p, 0x44);
-			pcicfgw8(p, 0x44, r|0x08);
-			r = pcicfgr8(p, 0x46);
-			pcicfgw8(p, 0x46, (r & 0x0C)|0xF0);
-			/*FALLTHROUGH*/
-		case (0x7401<<16)|0x1022:	/* AMD 755 Cobra */
-		case (0x7409<<16)|0x1022:	/* AMD 756 Viper */
-		case (0x7410<<16)|0x1022:	/* AMD 766 Viper Plus */
-		case (0x7469<<16)|0x1022:	/* AMD 3111 */
-			/*
-			 * This can probably be lumped in with the 768 above.
-			 */
-			/*FALLTHROUGH*/
-		case (0x209A<<16)|0x1022:	/* AMD CS5536 */
-		case (0x01BC<<16)|0x10DE:	/* nVidia nForce1 */
-		case (0x0065<<16)|0x10DE:	/* nVidia nForce2 */
-		case (0x0085<<16)|0x10DE:	/* nVidia nForce2 MCP */
-		case (0x00E3<<16)|0x10DE:	/* nVidia nForce2 250 SATA */
-		case (0x00D5<<16)|0x10DE:	/* nVidia nForce3 */
-		case (0x00E5<<16)|0x10DE:	/* nVidia nForce3 Pro */
-		case (0x00EE<<16)|0x10DE:	/* nVidia nForce3 250 SATA */
-		case (0x0035<<16)|0x10DE:	/* nVidia nForce3 MCP */
-		case (0x0053<<16)|0x10DE:	/* nVidia nForce4 */
-		case (0x0054<<16)|0x10DE:	/* nVidia nForce4 SATA */
-		case (0x0055<<16)|0x10DE:	/* nVidia nForce4 SATA */
-		case (0x0266<<16)|0x10DE:	/* nVidia nForce4 430 SATA */
-		case (0x0267<<16)|0x10DE:	/* nVidia nForce 55 MCP SATA */
-		case (0x03EC<<16)|0x10DE:	/* nVidia nForce 61 MCP SATA */
-		case (0x0448<<16)|0x10DE:	/* nVidia nForce 65 MCP SATA */
-		case (0x0560<<16)|0x10DE:	/* nVidia nForce 69 MCP SATA */
-			/*
-			 * Ditto, although it may have a different base
-			 * address for the registers (0x50?).
-			 */
-			/*FALLTHROUGH*/
-		case (0x4376<<16)|0x1002:	/* ATI SB400 PATA */
-		case (0x4379<<16)|0x1002:	/* ATI SB400 SATA */
-		case (0x437a<<16)|0x1002:	/* ATI SB400 SATA */
-		case (0x438c<<16)|0x1002:	/* ATI SB600 PATA */
-			break;
-		case (0x0211<<16)|0x1166:	/* ServerWorks IB6566 */
-			{
-				Pcidev *sb;
-
-				sb = pcimatch(nil, 0x1166, 0x0200);
-				if(sb == nil)
-					break;
-				r = pcicfgr32(sb, 0x64);
-				r &= ~0x2000;
-				pcicfgw32(sb, 0x64, r);
-			}
-			span = 32*1024;
-			break;
-		case (0x0502<<17)|0x100B:	/* NS SC1100/SCx200 */
-		case (0x5229<<16)|0x10B9:	/* ALi M1543 */
-		case (0x5288<<16)|0x10B9:	/* ALi M5288 SATA */
-		case (0x5513<<16)|0x1039:	/* SiS 962 */
-		case (0x0646<<16)|0x1095:	/* CMD 646 */
-		case (0x0571<<16)|0x1106:	/* VIA 82C686 */
-		case (0x9001<<16)|0x1106:	/* VIA chipset in VIA PV530 */
-		case (0x2363<<16)|0x197b:	/* JMicron SATA */
-		case (0x1230<<16)|0x8086:	/* 82371FB (PIIX) */
-		case (0x7010<<16)|0x8086:	/* 82371SB (PIIX3) */
-		case (0x7111<<16)|0x8086:	/* 82371[AE]B (PIIX4[E]) */
-		case (0x2411<<16)|0x8086:	/* 82801AA (ICH) */
-		case (0x2421<<16)|0x8086:	/* 82801AB (ICH0) */
-		case (0x244A<<16)|0x8086:	/* 82801BA (ICH2, Mobile) */
-		case (0x244B<<16)|0x8086:	/* 82801BA (ICH2, High-End) */
-		case (0x248A<<16)|0x8086:	/* 82801CA (ICH3, Mobile) */
-		case (0x248B<<16)|0x8086:	/* 82801CA (ICH3, High-End) */
-		case (0x24CA<<16)|0x8086:	/* 82801DBM (ICH4, Mobile) */
-		case (0x24CB<<16)|0x8086:	/* 82801DB (ICH4, High-End) */
-		case (0x24DB<<16)|0x8086:	/* 82801EB (ICH5) */
-		case (0x25A3<<16)|0x8086:	/* 6300ESB (E7210) */
-		case (0x2653<<16)|0x8086:	/* 82801FBM SATA */
-		case (0x266F<<16)|0x8086:	/* 82801FB (ICH6) */
-		case (0x27DF<<16)|0x8086:	/* 82801G SATA (ICH7) */
-		case (0x27C0<<16)|0x8086:	/* 82801GB SATA AHCI (ICH7) */
-//		case (0x27C4<<16)|0x8086:	/* 82801GBM SATA (ICH7) */
-		case (0x27C5<<16)|0x8086:	/* 82801GBM SATA AHCI (ICH7) */
-		case (0x2820<<16)|0x8086:	/* 82801HB/HR/HH/HO SATA IDE */
-		case (0x2850<<16)|0x8086:	/* 82801HBM/HEM PATA */
-		case (0x2828<<16)|0x8086:	/* 82801HBM SATA (ICH8-M) */
-		case (0x2829<<16)|0x8086:	/* 82801HBM SATA AHCI (ICH8-M) */
-		case (0x2920<<16)|0x8086:	/* 82801(IB)/IR/IH/IO SATA IDE (ICH9) */
-		case (0x3a20<<16)|0x8086:	/* 82801JI (ICH10) */
-		case (0x3a26<<16)|0x8086:	/* 82801JI (ICH10) */
-			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;
-					print("pc87415disable: not yet implemented\n");
-				}
-
-				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;
-			ctlr->maxio = maxio;
-			ctlr->span = span;
-			if(!(pi & 0x80))
-				continue;
-			ctlr->bmiba = (p->mem[4].bar & ~0x01) + channel*8;
-		}
-	}
-
-if(0){
-	int port;
-	ISAConf isa;
-
-	/*
-	 * Hack for PCMCIA drives.
-	 * This will be tidied once we figure out how the whole
-	 * removeable device thing is going to work.
-	 */
-	memset(&isa, 0, sizeof(isa));
-	isa.port = 0x180;		/* change this for your machine */
-	isa.irq = 11;			/* change this for your machine */
-
-	port = isa.port+0x0C;
-	channel = pcmspecial("MK2001MPL", &isa);
-	if(channel == -1)
-		channel = pcmspecial("SunDisk", &isa);
-	if(channel == -1){
-		isa.irq = 10;
-		channel = pcmspecial("CF", &isa);
-	}
-	if(channel == -1){
-		isa.irq = 10;
-		channel = pcmspecial("OLYMPUS", &isa);
-	}
-	if(channel == -1){
-		port = isa.port+0x204;
-		channel = pcmspecial("ATA/ATAPI", &isa);
-	}
-	if(channel >= 0 && (sdev = ataprobe(isa.port, port, isa.irq)) != nil){
-		if(head != nil)
-			tail->next = sdev;
-		else
-			head = sdev;
-	}
-}
-	return head;
-}
-
-static SDev*
-atalegacy(int port, int irq)
-{
-	return ataprobe(port, port+0x204, irq);
-}
-
-static int
-ataenable(SDev* sdev)
-{
-	Ctlr *ctlr;
-	char name[32];
-
-	ctlr = sdev->ctlr;
-
-	if(ctlr->bmiba){
-#define ALIGN	(4 * 1024)
-		if(ctlr->pcidev != nil)
-			pcisetbme(ctlr->pcidev);
-		ctlr->prdt = mallocalign(Nprd*sizeof(Prd), 4, 0, 4*1024);
-	}
-	snprint(name, sizeof(name), "%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
-atadisable(SDev *sdev)
-{
-	Ctlr *ctlr;
-	char name[32];
-
-	ctlr = sdev->ctlr;
-	outb(ctlr->ctlport+Dc, Nien);		/* disable interrupts */
-	if (ctlr->idisable)
-		ctlr->idisable(ctlr);
-	snprint(name, sizeof(name), "%s (%s)", sdev->name, sdev->ifc->name);
-	intrdisable(ctlr->irq, atainterrupt, ctlr, ctlr->tbdf, name);
-	if (ctlr->bmiba) {
-		if (ctlr->pcidev)
-			pciclrbme(ctlr->pcidev);
-		free(ctlr->prdt);
-	}
-	return 0;
-}
-
-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);
-	if(drive->flags&Lba48)
-		n += snprint(p+n, l-n, " lba48always %s",
-			(drive->flags&Lba48always) ? "on" : "off");
-	n += snprint(p+n, l-n, "\n");
-	if(drive->sectors){
-		n += snprint(p+n, l-n, "geometry %lld %d",
-			drive->sectors, drive->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)
-{
-	int period;
-	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 if(strcmp(cb->f[0], "standby") == 0){
-		switch(cb->nf){
-		default:
-			error(Ebadctl);
-		case 2:
-			period = strtol(cb->f[1], 0, 0);
-			if(period && (period < 30 || period > 240*5))
-				error(Ebadctl);
-			period /= 5;
-			break;
-		}
-		if(atastandby(drive, period) != SDok)
-			error(Ebadctl);
-	}
-	else if(strcmp(cb->f[0], "lba48always") == 0){
-		if(cb->nf != 2 || !(drive->flags&Lba48))
-			error(Ebadctl);
-		if(strcmp(cb->f[1], "on") == 0)
-			drive->flags |= Lba48always;
-		else if(strcmp(cb->f[1], "off") == 0)
-			drive->flags &= ~Lba48always;
-		else
-			error(Ebadctl);
-	}
-	else
-		error(Ebadctl);
-	qunlock(drive);
-	poperror();
-
-	return 0;
-}
-
-SDifc sdataifc = {
-	"ata",				/* name */
-
-	atapnp,				/* pnp */
-	atalegacy,			/* legacy */
-	ataenable,			/* enable */
-	atadisable,			/* disable */
-
-	scsiverify,			/* verify */
-	scsionline,			/* online */
-	atario,				/* rio */
-	atarctl,			/* rctl */
-	atawctl,			/* wctl */
-
-	scsibio,			/* bio */
-	ataprobew,			/* probe */
-	ataclear,			/* clear */
-	atastat,			/* rtopctl */
-	nil,				/* wtopctl */
-};
--- a/sys/src/9/pc/sdiahci.c
+++ b/sys/src/9/pc/sdiahci.c
@@ -1,6 +1,6 @@
 /*
  * intel/amd ahci sata controller
- * copyright © 2007-8 coraid, inc.
+ * copyright © 2007-10 coraid, inc.
  */
 
 #include "u.h"
@@ -11,12 +11,19 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/sd.h"
+#include <fis.h>
 #include "ahci.h"
+#include "../port/led.h"
 
+#pragma	varargck	type	"T"	int
 #define	dprint(...)	if(debug)	iprint(__VA_ARGS__); else USED(debug)
-#define	idprint(...)	if(prid)	iprint(__VA_ARGS__);  else USED(prid)
-#define	aprint(...)	if(datapi)	iprint(__VA_ARGS__);  else USED(datapi)
+#define	idprint(...)	if(prid)		print(__VA_ARGS__); else USED(prid)
+#define	aprint(...)	if(datapi)	print(__VA_ARGS__); else USED(datapi)
+#define	ledprint(...)	if(dled)		print(__VA_ARGS__); else USED(dled)
+#define	Pciwaddrh(a)	0
 #define Tname(c)	tname[(c)->type]
+#define	Ticks		MACHP(0)->ticks
+#define	MS2TK(t)	(((ulong)(t)*HZ)/1000)
 
 enum {
 	NCtlr	= 4,
@@ -23,8 +30,12 @@
 	NCtlrdrv= 32,
 	NDrive	= NCtlr*NCtlrdrv,
 
+	Fahdrs	= 4,
+
 	Read	= 0,
 	Write,
+
+	Eesb	= 1<<0,	/* must have (Eesb & Emtype) == 0 */
 };
 
 /* pci space configuration */
@@ -38,16 +49,16 @@
 	Tesb,
 	Tich,
 	Tsb600,
-	Tunk,
+	Tjmicron,
+	Tahci,
 };
 
-#define Intel(x)	((x)->pci->vid == 0x8086)
-
 static char *tname[] = {
 	"63xxesb",
 	"ich",
 	"sb600",
-	"unk",
+	"jmicron",
+	"ahci",
 };
 
 enum {
@@ -80,21 +91,21 @@
 	DMautoneg,
 	DMsatai,
 	DMsataii,
+	DMsataiii,
+	DMlast,
 };
 
-static char *modename[] = {
+static char *modes[DMlast] = {
 	"auto",
 	"satai",
 	"sataii",
+	"sataiii",
 };
 
-static char *flagname[] = {
-	"llba",
-	"smart",
-	"power",
-	"nop",
-	"atapi",
-	"atapi16",
+typedef struct Htab Htab;
+struct Htab {
+	ulong	bit;
+	char	*name;
 };
 
 typedef struct {
@@ -105,29 +116,35 @@
 	char	name[10];
 	Aport	*port;
 	Aportm	portm;
-	Aportc	portc;		/* redundant ptr to port and portm */
+	Aportc	portc;	/* redundant ptr to port and portm. */
+	Ledport;
 
-	uchar	mediachange;
+	uchar	drivechange;
 	uchar	state;
-	uchar	smartrs;
 
 	uvlong	sectors;
-	ulong	secsize;
-	ulong	intick;		/* start tick of current transfer */
+	uint	secsize;
+	ulong	totick;
 	ulong	lastseen;
-	int	wait;
-	uchar	mode;		/* DMautoneg, satai or sataii */
+	uint	wait;
+	uchar	mode;
 	uchar	active;
 
 	char	serial[20+1];
 	char	firmware[8+1];
 	char	model[40+1];
+	uvlong	wwn;
 
 	ushort	info[0x200];
 
-	int	driveno;	/* ctlr*NCtlrdrv + unit */
-	/* controller port # != driveno when not all ports are enabled */
-	int	portno;
+	/*
+	 * ahci allows non-sequential ports.
+	 * to avoid this hassle, we let
+	 * driveno	ctlr*NCtlrdrv + unit
+	 * portno	nth available port
+	 */
+	uint	driveno;
+	uint	portno;
 } Drive;
 
 struct Ctlr {
@@ -141,11 +158,12 @@
 	uchar	*mmio;
 	ulong	*lmmio;
 	Ahba	*hba;
+	Aenc;
+	uint	enctype;
 
 	Drive	rawdrive[NCtlrdrv];
 	Drive*	drive[NCtlrdrv];
 	int	ndrive;
-	int	mport;
 };
 
 static	Ctlr	iactlr[NCtlr];
@@ -155,10 +173,10 @@
 static	Drive	*iadrive[NDrive];
 static	int	niadrive;
 
-/* these are fiddled in iawtopctl() */
 static	int	debug;
 static	int	prid = 1;
 static	int	datapi;
+static	int	dled;
 
 static char stab[] = {
 [0]	'i', 'm',
@@ -186,13 +204,13 @@
 static void
 preg(uchar *reg, int n)
 {
-	int i;
 	char buf[25*3+1], *e;
+	int i;
 
 	e = buf;
 	for(i = 0; i < n; i++){
-		*e++ = ntab[reg[i]>>4];
-		*e++ = ntab[reg[i]&0xf];
+		*e++ = ntab[reg[i] >> 4];
+		*e++ = ntab[reg[i] & 0xf];
 		*e++ = ' ';
 	}
 	*e++ = '\n';
@@ -203,7 +221,7 @@
 static void
 dreg(char *s, Aport *p)
 {
-	dprint("ahci: %stask=%lux; cmd=%lux; ci=%lux; is=%lux\n",
+	dprint("%stask=%lux; cmd=%lux; ci=%lux; is=%lux\n",
 		s, p->task, p->cmd, p->ci, p->isr);
 }
 
@@ -219,7 +237,7 @@
 typedef struct {
 	Aport	*p;
 	int	i;
-}Asleep;
+} Asleep;
 
 static int
 ahciclear(void *v)
@@ -242,8 +260,8 @@
 static int
 ahciwait(Aportc *c, int ms)
 {
-	Asleep as;
 	Aport *p;
+	Asleep as;
 
 	p = c->p;
 	p->ci = 1;
@@ -250,94 +268,75 @@
 	as.p = p;
 	as.i = 1;
 	aesleep(c->m, &as, ms);
-	if((p->task&1) == 0 && p->ci == 0)
+	if((p->task & 1) == 0 && p->ci == 0)
 		return 0;
-	dreg("ahciwait timeout ", c->p);
+	dreg("ahciwait fail/timeout ", c->p);
 	return -1;
 }
 
-static int
-nop(Aportc *pc)
+static void
+mkalist(Aportm *m, uint flags, uchar *data, int len)
 {
-	uchar *c;
 	Actab *t;
 	Alist *l;
+	Aprdt *p;
 
-	if((pc->m->feat & Dnop) == 0)
-		return -1;
-
-	t = pc->m->ctab;
-	c = t->cfis;
-
-	memset(c, 0, 0x20);
-	c[0] = 0x27;
-	c[1] = 0x80;
-	c[2] = 0x00;
-	c[7] = 0xa0;		/* obsolete device bits */
-
-	l = pc->m->list;
-	l->flags = Lwrite | 0x5;
+	t = m->ctab;
+	l = m->list;
+	l->flags = flags | 0x5;
 	l->len = 0;
 	l->ctab = PCIWADDR(t);
-	l->ctabhi = 0;
-
-	return ahciwait(pc, 3*1000);
+	l->ctabhi = Pciwaddrh(t);
+	if(data){
+		l->flags |= 1<<16;
+		p = &t->prdt;
+		p->dba = PCIWADDR(data);
+		p->dbahi = Pciwaddrh(data);
+		p->count = 1<<31 | len - 2 | 1;
+	}
 }
 
 static int
-setfeatures(Aportc *pc, uchar f)
+nop(Aportc *pc)
 {
 	uchar *c;
-	Actab *t;
-	Alist *l;
 
-	t = pc->m->ctab;
-	c = t->cfis;
-
-	memset(c, 0, 0x20);
-	c[0] = 0x27;
-	c[1] = 0x80;
-	c[2] = 0xef;
-	c[3] = f;
-	c[7] = 0xa0;		/* obsolete device bits */
-
-	l = pc->m->list;
-	l->flags = Lwrite | 0x5;
-	l->len = 0;
-	l->ctab = PCIWADDR(t);
-	l->ctabhi = 0;
-
+	if((pc->m->feat & Dnop) == 0)
+		return -1;
+	c = pc->m->ctab->cfis;
+	nopfis(pc->m, c, 0);
+	mkalist(pc->m, Lwrite, 0, 0);
 	return ahciwait(pc, 3*1000);
 }
 
 static int
-setudmamode(Aportc *pc, uchar f)
+setfeatures(Aportc *pc, uchar f, uint w)
 {
 	uchar *c;
-	Actab *t;
-	Alist *l;
 
-	/* hack */
-	if((pc->p->sig >> 16) == 0xeb14)
-		return 0;
+	c = pc->m->ctab->cfis;
+	featfis(pc->m, c, f);
+	mkalist(pc->m, Lwrite, 0, 0);
+	return ahciwait(pc, w);
+}
 
-	t = pc->m->ctab;
-	c = t->cfis;
+/*
+ * ata 7, required for sata, requires that all devices "support"
+ * udma mode 5,   however sata:pata bridges allow older devices
+ * which may not.  the innodisk satadom, for example allows
+ * only udma mode 2.  on the assumption that actual udma is
+ * taking place on these bridges, we set the highest udma mode
+ * available, or pio if there is no udma mode available.
+ */
+static int
+settxmode(Aportc *pc, uchar f)
+{
+	uchar *c;
 
-	memset(c, 0, 0x20);
-	c[0] = 0x27;
-	c[1] = 0x80;
-	c[2] = 0xef;
-	c[3] = 3;		/* set transfer mode */
-	c[7] = 0xa0;		/* obsolete device bits */
-	c[12] = 0x40 | f;	/* sector count */
-
-	l = pc->m->list;
-	l->flags = Lwrite | 0x5;
-	l->len = 0;
-	l->ctab = PCIWADDR(t);
-	l->ctabhi = 0;
-
+	c = pc->m->ctab->cfis;
+	if(txmodefis(pc->m, c, f) == -1)
+		return 0;
+	mkalist(pc->m, Lwrite, 0, 0);
 	return ahciwait(pc, 3*1000);
 }
 
@@ -351,7 +350,7 @@
 }
 
 static int
-ahciportreset(Aportc *c)
+ahciportreset(Aportc *c, uint mode)
 {
 	ulong *cmd, i;
 	Aport *p;
@@ -360,229 +359,72 @@
 	cmd = &p->cmd;
 	*cmd &= ~(Afre|Ast);
 	for(i = 0; i < 500; i += 25){
-		if((*cmd&Acr) == 0)
+		if((*cmd & Acr) == 0)
 			break;
 		asleep(25);
 	}
-	p->sctl = 1|(p->sctl&~7);
+	p->sctl = 3*Aipm | 0*Aspd | Adet;
 	delay(1);
-	p->sctl &= ~7;
+	p->sctl = 3*Aipm | mode*Aspd;
 	return 0;
 }
 
 static int
-smart(Aportc *pc, int n)
+ahciflushcache(Aportc *pc)
 {
 	uchar *c;
-	Actab *t;
-	Alist *l;
 
-	if((pc->m->feat&Dsmart) == 0)
-		return -1;
+	c = pc->m->ctab->cfis;
+	flushcachefis(pc->m, c);
+	mkalist(pc->m, Lwrite, 0, 0);
 
-	t = pc->m->ctab;
-	c = t->cfis;
-
-	memset(c, 0, 0x20);
-	c[0] = 0x27;
-	c[1] = 0x80;
-	c[2] = 0xb0;
-	c[3] = 0xd8 + n;	/* able smart */
-	c[5] = 0x4f;
-	c[6] = 0xc2;
-	c[7] = 0xa0;
-
-	l = pc->m->list;
-	l->flags = Lwrite | 0x5;
-	l->len = 0;
-	l->ctab = PCIWADDR(t);
-	l->ctabhi = 0;
-
-	if(ahciwait(pc, 1000) == -1 || pc->p->task & (1|32)){
-		dprint("ahci: smart fail %lux\n", pc->p->task);
+	if(ahciwait(pc, 60000) == -1 || pc->p->task & (1|32)){
+		dprint("ahciflushcache fail %lux\n", pc->p->task);
 //		preg(pc->m->fis.r, 20);
 		return -1;
 	}
-	if(n)
-		return 0;
-	return 1;
-}
-
-static int
-smartrs(Aportc *pc)
-{
-	uchar *c;
-	Actab *t;
-	Alist *l;
-
-	t = pc->m->ctab;
-	c = t->cfis;
-
-	memset(c, 0, 0x20);
-	c[0] = 0x27;
-	c[1] = 0x80;
-	c[2] = 0xb0;
-	c[3] = 0xda;		/* return smart status */
-	c[5] = 0x4f;
-	c[6] = 0xc2;
-	c[7] = 0xa0;
-
-	l = pc->m->list;
-	l->flags = Lwrite | 0x5;
-	l->len = 0;
-	l->ctab = PCIWADDR(t);
-	l->ctabhi = 0;
-
-	c = pc->m->fis.r;
-	if(ahciwait(pc, 1000) == -1 || pc->p->task & (1|32)){
-		dprint("ahci: smart fail %lux\n", pc->p->task);
-		preg(c, 20);
-		return -1;
-	}
-	if(c[5] == 0x4f && c[6] == 0xc2)
-		return 1;
 	return 0;
 }
 
 static int
-ahciflushcache(Aportc *pc)
+ahciidentify0(Aportc *pc, void *id)
 {
-	uchar *c, llba;
-	Actab *t;
-	Alist *l;
-	static uchar tab[2] = {0xe7, 0xea};
-
-	llba = pc->m->feat&Dllba? 1: 0;
-	t = pc->m->ctab;
-	c = t->cfis;
-
-	memset(c, 0, 0x20);
-	c[0] = 0x27;
-	c[1] = 0x80;
-	c[2] = tab[llba];
-	c[7] = 0xa0;
-
-	l = pc->m->list;
-	l->flags = Lwrite | 0x5;
-	l->len = 0;
-	l->ctab = PCIWADDR(t);
-	l->ctabhi = 0;
-
-	if(ahciwait(pc, 60000) == -1 || pc->p->task & (1|32)){
-		dprint("ahciflushcache: fail %lux\n", pc->p->task);
-//		preg( pc->m->fis.r, 20);
-		return -1;
-	}
-	return 0;
-}
-
-static ushort
-gbit16(void *a)
-{
-	uchar *i;
-
-	i = a;
-	return i[1]<<8 | i[0];
-}
-
-static ulong
-gbit32(void *a)
-{
-	ulong j;
-	uchar *i;
-
-	i = a;
-	j  = i[3] << 24;
-	j |= i[2] << 16;
-	j |= i[1] << 8;
-	j |= i[0];
-	return j;
-}
-
-static uvlong
-gbit64(void *a)
-{
-	uchar *i;
-
-	i = a;
-	return (uvlong)gbit32(i+4) << 32 | gbit32(a);
-}
-
-static int
-ahciidentify0(Aportc *pc, void *id, int atapi)
-{
 	uchar *c;
 	Actab *t;
-	Alist *l;
-	Aprdt *p;
-	static uchar tab[] = { 0xec, 0xa1, };
 
 	t = pc->m->ctab;
 	c = t->cfis;
-
-	memset(c, 0, 0x20);
-	c[0] = 0x27;
-	c[1] = 0x80;
-	c[2] = tab[atapi];
-	c[7] = 0xa0;		/* obsolete device bits */
-
-	l = pc->m->list;
-	l->flags = 1<<16 | 0x5;
-	l->len = 0;
-	l->ctab = PCIWADDR(t);
-	l->ctabhi = 0;
-
-	memset(id, 0, 0x100);
-	p = &t->prdt;
-	p->dba = PCIWADDR(id);
-	p->dbahi = 0;
-	p->count = 1<<31 | (0x200-2) | 1;
-
+	memset(id, 0, 0x200);
+	identifyfis(pc->m, c);
+	mkalist(pc->m, 0, id, 0x200);
 	return ahciwait(pc, 3*1000);
 }
 
 static vlong
-ahciidentify(Aportc *pc, ushort *id)
+ahciidentify(Aportc *pc, ushort *id, uint *ss, char *d)
 {
-	int i, sig;
+	int i, n;
 	vlong s;
 	Aportm *m;
 
 	m = pc->m;
-	m->feat = 0;
-	m->smart = 0;
-	i = 0;
-	sig = pc->p->sig >> 16;
-	if(sig == 0xeb14){
-		m->feat |= Datapi;
-		i = 1;
+	for(i = 0;; i++){
+		if(i > 5 || ahciidentify0(pc, id) != 0)
+			return -1;
+		n = idpuis(id);
+		if(n & Pspinup && setfeatures(pc, 7, 20*1000) == -1)
+			print("%s: puis spinup fail\n", d);
+		if(n & Pidready)
+			break;
+		print("%s: puis waiting\n", d);
 	}
-	if(ahciidentify0(pc, id, i) == -1)
+	s = idfeat(m, id);
+	*ss = idss(m, id);
+	if(s == -1 || (m->feat&Dlba) == 0){
+		if((m->feat&Dlba) == 0)
+			dprint("%s: no lba support\n", d);
 		return -1;
-
-	i = gbit16(id+83) | gbit16(id+86);
-	if(i & (1<<10)){
-		m->feat |= Dllba;
-		s = gbit64(id+100);
-	}else
-		s = gbit32(id+60);
-
-	if(m->feat&Datapi){
-		i = gbit16(id+0);
-		if(i&1)
-			m->feat |= Datapi16;
 	}
-
-	i = gbit16(id+83);
-	if((i>>14) == 1) {
-		if(i & (1<<3))
-			m->feat |= Dpower;
-		i = gbit16(id+82);
-		if(i & 1)
-			m->feat |= Dsmart;
-		if(i & (1<<14))
-			m->feat |= Dnop;
-	}
 	return s;
 }
 
@@ -614,10 +456,10 @@
 	return -1;
 stop1:
 	/* extra check */
-	dprint("ahci: clo clear %lx\n", a->task);
+	dprint("ahci: clo clear %lux\n", a->task);
 	if(a->task & ASbsy)
 		return -1;
-	*p |= Ast;
+	*p |= Afre | Ast;
 	return 0;
 }
 
@@ -625,52 +467,28 @@
 ahcicomreset(Aportc *pc)
 {
 	uchar *c;
-	Actab *t;
-	Alist *l;
 
-	dprint("ahcicomreset\n");
-	dreg("ahci: comreset ", pc->p);
+	dreg("comreset ", pc->p);
 	if(ahciquiet(pc->p) == -1){
-		dprint("ahciquiet failed\n");
+		dprint("ahci: ahciquiet failed\n");
 		return -1;
 	}
 	dreg("comreset ", pc->p);
 
-	t = pc->m->ctab;
-	c = t->cfis;
-
-	memset(c, 0, 0x20);
-	c[0] = 0x27;
-	c[1] = 0x00;
-	c[7] = 0xa0;		/* obsolete device bits */
-	c[15] = 1<<2;		/* srst */
-
-	l = pc->m->list;
-	l->flags = Lclear | Lreset | 0x5;
-	l->len = 0;
-	l->ctab = PCIWADDR(t);
-	l->ctabhi = 0;
-
+	c = pc->m->ctab->cfis;
+	nopfis(pc->m, c, 1);
+	mkalist(pc->m, Lclear | Lreset, 0, 0);
 	if(ahciwait(pc, 500) == -1){
-		dprint("ahcicomreset: first command failed\n");
+		dprint("ahci: comreset1 failed\n");
 		return -1;
 	}
 	microdelay(250);
 	dreg("comreset ", pc->p);
 
-	memset(c, 0, 0x20);
-	c[0] = 0x27;
-	c[1] = 0x00;
-	c[7] = 0xa0;		/* obsolete device bits */
-
-	l = pc->m->list;
-	l->flags = Lwrite | 0x5;
-	l->len = 0;
-	l->ctab = PCIWADDR(t);
-	l->ctabhi = 0;
-
+	nopfis(pc->m, c, 0);
+	mkalist(pc->m, Lwrite, 0, 0);
 	if(ahciwait(pc, 150) == -1){
-		dprint("ahcicomreset: second command failed\n");
+		dprint("ahci: comreset2 failed\n");
 		return -1;
 	}
 	dreg("comreset ", pc->p);
@@ -706,7 +524,7 @@
 }
 
 /*
- * § 6.2.2.1  first part; comreset handled by reset disk.
+ * §6.2.2.1  first part; comreset handled by reset disk.
  *	- remainder is handled by configdisk.
  *	- ahcirecover is a quick recovery from a failed command.
  */
@@ -729,7 +547,7 @@
 {
 	ahciswreset(pc);
 	pc->p->cmd |= Ast;
-	if(setudmamode(pc, 5) == -1)
+	if(settxmode(pc, pc->m->udma) == -1)
 		return -1;
 	return 0;
 }
@@ -756,21 +574,19 @@
 }
 
 static void
-ahciwakeup(Aport *p)
+ahciwakeup(Aportc *c, uint mode)
 {
 	ushort s;
 
-	s = p->sstatus;
-	if((s & 0xF00) != 0x600)
+	s = c->p->sstatus;
+	if((s & Isleepy) == 0)
 		return;
-	if((s & 7) != 1){		/* not (device, no phy) */
-		iprint("ahci: slumbering drive unwakable %ux\n", s);
+	if((s & Smask) != Spresent){
+		print("ahci: slumbering drive missing %.3ux\n", s);
 		return;
 	}
-	p->sctl = 3*Aipm | 0*Aspd | Adet;
-	delay(1);
-	p->sctl &= ~7;
-//	iprint("ahci: wake %ux -> %ux\n", s, p->sstatus);
+	ahciportreset(c, mode);
+//	iprint("ahci: wake %.3ux -> %.3lux\n", s, c->p->sstatus);
 }
 
 static int
@@ -788,29 +604,34 @@
 		m->ctab = malign(sizeof *m->ctab, 128);
 	}
 
-	if(p->sstatus & 3 && h->cap & Hsss){
-		/* device connected & staggered spin-up */
-		dprint("ahci: configdrive:  spinning up ... [%lux]\n",
-			p->sstatus);
-		p->cmd |= Apod|Asud;
-		asleep(1400);
-	}
-
-	p->serror = SerrAll;
-
 	p->list = PCIWADDR(m->list);
-	p->listhi = 0;
+	p->listhi = Pciwaddrh(m->list);
 	p->fis = PCIWADDR(m->fis.base);
-	p->fishi = 0;
-	p->cmd |= Afre|Ast;
+	p->fishi = Pciwaddrh(m->fis.base);
 
-	if((p->sstatus & 0xF0F) == 0x601) /* drive coming up in slumbering? */
-		ahciwakeup(p);
+	p->cmd |= Afre;
 
+	if((p->sstatus & Sbist) == 0 && (p->cmd & Apwr) != Apwr)
+	if((p->sstatus & Sphylink) == 0 && h->cap & Hss){
+		/* staggered spin-up? */
+		dprint("ahci:  spin up ... [%.3lux]\n", p->sstatus);
+		p->cmd |= Apwr;
+		for(int i = 0; i < 1400; i += 50){
+			if(p->sstatus & (Sphylink | Sbist))
+				break;
+			asleep(50);
+		}
+	}
+
+	p->serror = SerrAll;
+
+	if((p->sstatus & SSmask) == (Isleepy | Spresent))
+		ahciwakeup(c, mode);
 	/* disable power managment sequence from book. */
-	p->sctl = (3*Aipm) | (mode*Aspd) | (0*Adet);
+	p->sctl = 3*Aipm | mode*Aspd | 0*Adet;
 	p->cmd &= ~Aalpe;
 
+	p->cmd |= Ast;
 	p->ie = IEM;
 
 	return 0;
@@ -843,22 +664,22 @@
 }
 
 static int
-ahciconf(Ctlr *ctlr)
+ahciconf(Ctlr *c)
 {
+	uint u;
 	Ahba *h;
-	ulong u;
 
-	h = ctlr->hba = (Ahba*)ctlr->mmio;
+	h = c->hba = (Ahba*)c->mmio;
 	u = h->cap;
 
-	if((u&Hsam) == 0)
+	if((u & Ham) == 0)
 		h->ghc |= Hae;
 
-	print("#S/sd%c: ahci %s port %#p: sss %ld ncs %ld coal %ld "
-		"mports %ld led %ld clo %ld ems %ld\n",
-		ctlr->sdev->idno, tname[ctlr->type], h,
-		(u>>27) & 1, (u>>8) & 0x1f, (u>>7) & 1, u & 0x1f, (u>>25) & 1,
-		(u>>24) & 1, (u>>6) & 1);
+//	print("#S/sd%c: ahci %s port %#p: sss %d ncs %d coal %d "
+//		"mport %d led %d clo %d ems %d\n",
+//		c->sdev->idno, Tname(c), h,
+//		(u>>27) & 1, (u>>8) & 0x1f, (u>>7) & 1, u & 0x1f, (u>>25) & 1,
+//		(u>>24) & 1, (u>>6) & 1);
 	return countbits(h->pi);
 }
 
@@ -867,7 +688,7 @@
 {
 	int wait;
 
-	h->ghc |= 1;
+	h->ghc |= Hhr;
 	for(wait = 0; wait < 1000; wait += 100){
 		if(h->ghc == 0)
 			return 0;
@@ -876,36 +697,27 @@
 	return -1;
 }
 
-static void
-idmove(char *p, ushort *a, int n)
+static char*
+dnam(Drive *d)
 {
-	int i;
-	char *op, *e;
+	char *s;
 
-	op = p;
-	for(i = 0; i < n/2; i++){
-		*p++ = a[i] >> 8;
-		*p++ = a[i];
-	}
-	*p = 0;
-	while(p > op && *--p == ' ')
-		*p = 0;
-	e = p;
-	for (p = op; *p == ' '; p++)
-		;
-	memmove(op, p, n - (e - p));
+	s = d->name;
+	if(d->unit && d->unit->name)
+		s = d->unit->name;
+	return s;
 }
 
 static int
 identify(Drive *d)
 {
+	uchar oserial[21];
 	ushort *id;
 	vlong osectors, s;
-	uchar oserial[21];
 	SDunit *u;
 
 	id = d->info;
-	s = ahciidentify(&d->portc, id);
+	s = ahciidentify(&d->portc, id, &d->secsize, dnam(d));
 	if(s == -1){
 		d->state = Derror;
 		return -1;
@@ -913,17 +725,14 @@
 	osectors = d->sectors;
 	memmove(oserial, d->serial, sizeof d->serial);
 
-	u = d->unit;
 	d->sectors = s;
-	d->secsize = u->secsize;
-	if(d->secsize == 0)
-		d->secsize = 512;		/* default */
-	d->smartrs = 0;
 
 	idmove(d->serial, id+10, 20);
 	idmove(d->firmware, id+23, 8);
 	idmove(d->model, id+27, 40);
+	d->wwn = idwwn(d->portc.m, id);
 
+	u = d->unit;
 	memset(u->inquiry, 0, sizeof u->inquiry);
 	u->inquiry[2] = 2;
 	u->inquiry[3] = 2;
@@ -930,8 +739,8 @@
 	u->inquiry[4] = sizeof u->inquiry - 4;
 	memmove(u->inquiry+8, d->model, 40);
 
-	if(osectors != s || memcmp(oserial, d->serial, sizeof oserial) != 0){
-		d->mediachange = 1;
+	if(osectors != s || memcmp(oserial, d->serial, sizeof oserial)){
+		d->drivechange = 1;
 		u->sectors = 0;
 	}
 	return 0;
@@ -940,88 +749,102 @@
 static void
 clearci(Aport *p)
 {
-	if(p->cmd & Ast) {
+	if(p->cmd & Ast){
 		p->cmd &= ~Ast;
 		p->cmd |=  Ast;
 	}
 }
 
+static int
+intel(Ctlr *c)
+{
+	return c->pci->vid == 0x8086;
+}
+
+static int
+ignoreahdrs(Drive *d)
+{
+	return d->portm.feat & Datapi && d->ctlr->type == Tsb600;
+}
+
 static void
 updatedrive(Drive *d)
 {
-	ulong cause, serr, s0, pr, ewake;
-	char *name;
+	ulong f, cause, serr, s0, pr, ewake;
 	Aport *p;
 	static ulong last;
 
 	pr = 1;
 	ewake = 0;
+	f = 0;
 	p = d->port;
 	cause = p->isr;
+	if(d->ctlr->type == Tjmicron)
+		cause &= ~Aifs;
 	serr = p->serror;
 	p->isr = cause;
-	name = "??";
-	if(d->unit && d->unit->name)
-		name = d->unit->name;
 
 	if(p->ci == 0){
-		d->portm.flag |= Fdone;
-		wakeup(&d->portm);
+		f |= Fdone;
 		pr = 0;
 	}else if(cause & Adps)
 		pr = 0;
 	if(cause & Ifatal){
 		ewake = 1;
-		dprint("ahci: updatedrive: fatal\n");
+		dprint("%s: fatal\n", dnam(d));
 	}
 	if(cause & Adhrs){
-		if(p->task & (1<<5|1)){
-			dprint("ahci: Adhrs cause %lux serr %lux task %lux\n",
-				cause, serr, p->task);
-			d->portm.flag |= Ferror;
+		if(p->task & 33){
+			if(ignoreahdrs(d) && serr & ErrE)
+				f |= Fahdrs;
+			dprint("%s: Adhrs cause %lux serr %lux task %lux\n",
+				dnam(d), cause, serr, p->task);
+			f |= Ferror;
 			ewake = 1;
 		}
 		pr = 0;
 	}
 	if(p->task & 1 && last != cause)
-		dprint("%s: err ca %lux serr %lux task %lux sstat %lux\n",
-			name, cause, serr, p->task, p->sstatus);
+		dprint("%s: err ca %lux serr %lux task %lux sstat %.3lux\n",
+			dnam(d), cause, serr, p->task, p->sstatus);
 	if(pr)
-		dprint("%s: upd %lux ta %lux\n", name, cause, p->task);
+		dprint("%s: upd %lux ta %lux\n", dnam(d), cause, p->task);
 
 	if(cause & (Aprcs|Aifs)){
 		s0 = d->state;
-		switch(p->sstatus & 7){
-		case 0:				/* no device */
+		switch(p->sstatus & Smask){
+		case Smissing:
 			d->state = Dmissing;
 			break;
-		case 1:				/* device but no phy comm. */
-			if((p->sstatus & 0xF00) == 0x600)
-				d->state = Dnew; /* slumbering */
+		case Spresent:
+			if((p->sstatus & Imask) == Islumber)
+				d->state = Dnew;
 			else
 				d->state = Derror;
 			break;
-		case 3:				/* device & phy comm. estab. */
-			/* power mgnt crap for surprise removal */
+		case Sphylink:
+			/* power mgnt crap for suprise removal */
 			p->ie |= Aprcs|Apcs;	/* is this required? */
 			d->state = Dreset;
 			break;
-		case 4:				/* phy off-line */
+		case Sbist:
 			d->state = Doffline;
 			break;
 		}
-		dprint("%s: %s → %s [Apcrs] %lux\n", name,
-			diskstates[s0], diskstates[d->state], p->sstatus);
-		/* print pulled message here. */
+		dprint("%s: %s → %s [Apcrs] %.3lux\n", dnam(d), diskstates[s0],
+			diskstates[d->state], p->sstatus);
 		if(s0 == Dready && d->state != Dready)
-			idprint("%s: pulled\n", name);
+			idprint("%s: pulled\n", dnam(d));
 		if(d->state != Dready)
-			d->portm.flag |= Ferror;
-		ewake = 1;
+			f |= Ferror;
+		if(d->state != Dready || p->ci)
+			ewake = 1;
 	}
 	p->serror = serr;
-	if(ewake){
+	if(ewake)
 		clearci(p);
+	if(f){
+		d->portm.flag = f;
 		wakeup(&d->portm);
 	}
 	last = cause;
@@ -1032,27 +855,23 @@
 {
 	/*
 	 * bogus code because the first interrupt is currently dropped.
-	 * likely my fault.  serror may be cleared at the wrong time.
+	 * likely my fault.  serror is maybe cleared at the wrong time.
 	 */
 	switch(s){
-	case 0:			/* no device */
+	default:
+		print("%s: pstatus: bad status %.3lux\n", dnam(d), s);
+	case Smissing:
 		d->state = Dmissing;
 		break;
-	case 1:			/* device but no phy. comm. */
+	case Spresent:
 		break;
-	case 2:			/* should this be missing?  need testcase. */
-		dprint("ahci: pstatus 2\n");
-		/* fallthrough */
-	case 3:			/* device & phy. comm. */
+	case Sphylink:
 		d->wait = 0;
 		d->state = Dnew;
 		break;
-	case 4:			/* offline */
+	case Sbist:
 		d->state = Doffline;
 		break;
-	case 6:			/* ? not sure this makes sense. TODO */
-		d->state = Dnew;
-		break;
 	}
 }
 
@@ -1062,7 +881,7 @@
 	if(ahciconfigdrive(d->ctlr->hba, &d->portc, d->mode) == -1)
 		return -1;
 	ilock(d);
-	pstatus(d, d->port->sstatus & 7);
+	pstatus(d, d->port->sstatus & Smask);
 	iunlock(d);
 	return 0;
 }
@@ -1075,9 +894,9 @@
 
 	p = d->port;
 	det = p->sctl & 7;
-	stat = p->sstatus & 7;
+	stat = p->sstatus & Smask;
 	state = (p->cmd>>28) & 0xf;
-	dprint("ahci: resetdisk: icc %ux  det %d sdet %d\n", state, det, stat);
+	dprint("%s: resetdisk: icc %ux  det %.3ux sdet %.3ux\n", dnam(d), state, det, stat);
 
 	ilock(d);
 	state = d->state;
@@ -1085,13 +904,15 @@
 		d->portm.flag |= Ferror;
 	clearci(p);			/* satisfy sleep condition. */
 	wakeup(&d->portm);
-	if(stat != 3){		/* device absent or phy not communicating? */
+	d->state = Derror;
+	iunlock(d);
+
+	if(stat != Sphylink){
+		ilock(d);
 		d->state = Dportreset;
 		iunlock(d);
 		return;
 	}
-	d->state = Derror;
-	iunlock(d);
 
 	qlock(&d->portm);
 	if(p->cmd&Ast && ahciswreset(&d->portc) == -1){
@@ -1098,15 +919,13 @@
 		ilock(d);
 		d->state = Dportreset;	/* get a bigger stick. */
 		iunlock(d);
-	} else {
+	}else{
 		ilock(d);
 		d->state = Dmissing;
 		iunlock(d);
-
 		configdrive(d);
 	}
-	dprint("ahci: resetdisk: %s → %s\n",
-		diskstates[state], diskstates[d->state]);
+	dprint("%s: resetdisk: %s → %s\n", dnam(d), diskstates[state], diskstates[d->state]);
 	qunlock(&d->portm);
 }
 
@@ -1113,7 +932,7 @@
 static int
 newdrive(Drive *d)
 {
-	char *name;
+	char *s;
 	Aportc *c;
 	Aportm *m;
 
@@ -1120,22 +939,17 @@
 	c = &d->portc;
 	m = &d->portm;
 
-	name = d->unit->name;
-	if(name == 0)
-		name = "??";
-
-	if(d->port->task == 0x80)
-		return -1;
 	qlock(c->m);
-	if(setudmamode(c, 5) == -1){
-		dprint("%s: can't set udma mode\n", name);
+	setfissig(m, c->p->sig);
+	if(identify(d) == -1){
+		dprint("%s: identify failure\n", dnam(d));
 		goto lose;
 	}
-	if(identify(d) == -1){
-		dprint("%s: identify failure\n", name);
+	if(settxmode(c, m->udma) == -1){
+		dprint("%s: can't set udma mode\n", dnam(d));
 		goto lose;
 	}
-	if(m->feat & Dpower && setfeatures(c, 0x85) == -1){
+	if(m->feat & Dpower && setfeatures(c, 0x85, 3*1000) == -1){
 		m->feat &= ~Dpower;
 		if(ahcirecover(c) == -1)
 			goto lose;
@@ -1147,13 +961,16 @@
 
 	qunlock(c->m);
 
-	idprint("%s: %sLBA %,llud sectors: %s %s %s %s\n", d->unit->name,
-		(m->feat & Dllba? "L": ""), d->sectors, d->model, d->firmware,
-		d->serial, d->mediachange? "[mediachange]": "");
+	s = "";
+	if(m->feat & Dllba)
+		s = "L";
+	idprint("%s: %sLBA %,lld sectors\n", dnam(d), s, d->sectors);
+	idprint("  %s %s %s %s\n", d->model, d->firmware, d->serial,
+		d->drivechange? "[newdrive]": "");
 	return 0;
 
 lose:
-	idprint("%s: can't be initialized\n", d->unit->name);
+	idprint("%s: can't be initialized\n", dnam(d));
 	ilock(d);
 	d->state = Dnull;
 	iunlock(d);
@@ -1169,12 +986,12 @@
 };
 
 static void
-westerndigitalhung(Drive *d)
+hangck(Drive *d)
 {
-	if((d->portm.feat&Datapi) == 0 && d->active &&
-	    TK2MS(MACHP(0)->ticks - d->intick) > 5000){
-		dprint("%s: drive hung; resetting [%lux] ci %lx\n",
-			d->unit->name, d->port->task, d->port->ci);
+	if((d->portm.feat & Datapi) == 0 && d->active &&
+	    d->totick != 0 && (long)(Ticks - d->totick) > 0){
+		dprint("%s: drive hung; resetting [%lux] ci %lux\n",
+			dnam(d), d->port->task, d->port->ci);
 		d->state = Dreset;
 	}
 }
@@ -1188,13 +1005,13 @@
 
 	i = -1;
 	qlock(&d->portm);
-	if(ahciportreset(&d->portc) == -1)
-		dprint("ahci: doportreset: fails\n");
+	if(ahciportreset(&d->portc, d->mode) == -1)
+		dprint("ahci: ahciportreset fails\n");
 	else
 		i = 0;
 	qunlock(&d->portm);
-	dprint("ahci: doportreset: portreset → %s  [task %lux]\n",
-		diskstates[d->state], d->port->task);
+	dprint("ahci: portreset → %s  [task %.4lux ss %.3lux]\n",
+		diskstates[d->state], d->port->task, d->port->sstatus);
 	return i;
 }
 
@@ -1205,46 +1022,38 @@
 	switch(d->state){
 	case Dnull:
 	case Doffline:
+		if(d->unit)
 		if(d->unit->sectors != 0){
 			d->sectors = 0;
-			d->mediachange = 1;
+			d->drivechange = 1;
 		}
-		/* fallthrough */
 	case Dready:
 		d->wait = 0;
-		break;
 	}
 }
 
+static uint
+maxmode(Ctlr *c)
+{
+	return (c->hba->cap & 0xf*Hiss)/Hiss;
+}
+
 static void
 checkdrive(Drive *d, int i)
 {
-	ushort s;
-	char *name;
+	ushort s, sig;
 
-	if(d == nil) {
-		print("checkdrive: nil d\n");
-		return;
-	}
 	ilock(d);
-	if(d->unit == nil || d->port == nil) {
-		if(0)
-			print("checkdrive: nil d->%s\n",
-				d->unit == nil? "unit": "port");
-		iunlock(d);
-		return;
-	}
-	name = d->unit->name;
 	s = d->port->sstatus;
 	if(s)
-		d->lastseen = MACHP(0)->ticks;
+		d->lastseen = Ticks;
 	if(s != olds[i]){
-		dprint("%s: status: %04ux -> %04ux: %s\n",
-			name, olds[i], s, diskstates[d->state]);
+		dprint("%s: status: %.3ux -> %.3ux: %s\n",
+			dnam(d), olds[i], s, diskstates[d->state]);
 		olds[i] = s;
 		d->wait = 0;
 	}
-	westerndigitalhung(d);
+	hangck(d);
 	switch(d->state){
 	case Dnull:
 	case Dready:
@@ -1251,40 +1060,44 @@
 		break;
 	case Dmissing:
 	case Dnew:
-		switch(s & 0x107){
-		case 1:		/* no device (pm), device but no phy. comm. */
-			ahciwakeup(d->port);
-			/* fall through */
-		case 0:		/* no device */
+		switch(s & (Iactive|Smask)){
+		case Spresent:
+			ahciwakeup(&d->portc, d->mode);
+		case Smissing:
 			break;
 		default:
-			dprint("%s: unknown status %04ux\n", name, s);
+			dprint("%s: unknown status %.3ux\n", dnam(d), s);
 			/* fall through */
-		case 0x100:		/* active, no device */
+		case Iactive:		/* active, no device */
 			if(++d->wait&Mphywait)
 				break;
 reset:
-			if(++d->mode > DMsataii)
-				d->mode = 0;
-			if(d->mode == DMsatai){	/* we tried everything */
+			if(d->mode == 0)
+				d->mode = maxmode(d->ctlr);
+			else
+				d->mode--;
+			if(d->mode == DMautoneg){
 				d->state = Dportreset;
 				goto portreset;
 			}
-			dprint("%s: reset; new mode %s\n", name,
-				modename[d->mode]);
+			dprint("%s: reset; new mode %s\n", dnam(d),
+				modes[d->mode]);
 			iunlock(d);
 			resetdisk(d);
 			ilock(d);
 			break;
-		case 0x103:		/* active, device, phy. comm. */
+		case Iactive | Sphylink:
+			if(d->unit == nil)
+				break;
 			if((++d->wait&Midwait) == 0){
-				dprint("%s: slow reset %04ux task=%lux; %d\n",
-					name, s, d->port->task, d->wait);
+				dprint("%s: slow reset %.3ux task=%lux; %d\n",
+					dnam(d), s, d->port->task, d->wait);
 				goto reset;
 			}
 			s = (uchar)d->port->task;
-			if(s == 0x7f || ((d->port->sig >> 16) != 0xeb14 &&
-			    (s & ~0x17) != (1<<6)))
+			sig = d->port->sig >> 16;
+			if(s == 0x7f || s&ASbsy ||
+			    (sig != 0xeb14 && (s & ASdrdy) == 0))
 				break;
 			iunlock(d);
 			newdrive(d);
@@ -1298,8 +1111,8 @@
 		/* fallthrough */
 	case Derror:
 	case Dreset:
-		dprint("%s: reset [%s]: mode %d; status %04ux\n",
-			name, diskstates[d->state], d->mode, s);
+		dprint("%s: reset [%s]: mode %d; status %.3ux\n",
+			dnam(d), diskstates[d->state], d->mode, s);
 		iunlock(d);
 		resetdisk(d);
 		ilock(d);
@@ -1306,15 +1119,14 @@
 		break;
 	case Dportreset:
 portreset:
-		if(d->wait++ & 0xff && (s & 0x100) == 0)
+		if(d->wait++ & 0xff && (s & Iactive) == 0)
 			break;
-		/* device is active */
-		dprint("%s: portreset [%s]: mode %d; status %04ux\n",
-			name, diskstates[d->state], d->mode, s);
+		dprint("%s: portreset [%s]: mode %d; status %.3ux\n",
+			dnam(d), diskstates[d->state], d->mode, s);
 		d->portm.flag |= Ferror;
 		clearci(d->port);
 		wakeup(&d->portm);
-		if((s & 7) == 0){	/* no device */
+		if((s & Smask) == 0){
 			d->state = Dmissing;
 			break;
 		}
@@ -1350,10 +1162,11 @@
 	c = a;
 	ilock(c);
 	cause = c->hba->isr;
-	for(i = 0; i < c->mport; i++){
+	for(i = 0; cause; i++){
 		m = 1 << i;
 		if((cause & m) == 0)
 			continue;
+		cause &= ~m;
 		d = c->rawdrive + i;
 		ilock(d);
 		if(d->port->isr && c->hba->pi & m)
@@ -1365,6 +1178,229 @@
 }
 
 static int
+ahciencreset(Ctlr *c)
+{
+	Ahba *h;
+
+	if(c->enctype == Eesb)
+		return 0;
+	h = c->hba;
+	h->emctl |= Emrst;
+	while(h->emctl & Emrst)
+		delay(1);
+	return 0;
+}
+
+/*
+ * from the standard: (http://en.wikipedia.org/wiki/IBPI)
+ * rebuild is preferred as locate+fail; alternate 1hz fail
+ * we're going to assume no locate led.
+ */
+
+enum {
+	Ledsleep	= 125,		/* 8hz */
+
+	N0	= Ledon*Aled,
+	L0	= Ledon*Aled | Ledon*Locled,
+	L1	= Ledon*Aled | Ledoff*Locled,
+	R0	= Ledon*Aled | Ledon*Locled |	Ledon*Errled,
+	R1	= Ledon*Aled | 			Ledoff*Errled,
+	S0	= Ledon*Aled |  Ledon*Locled /*|	Ledon*Errled*/,	/* botch */
+	S1	= Ledon*Aled | 			Ledoff*Errled,
+	P0	= Ledon*Aled | 			Ledon*Errled,
+	P1	= Ledon*Aled | 			Ledoff*Errled,
+	F0	= Ledon*Aled | 			Ledon*Errled,
+	C0	= Ledon*Aled | Ledon*Locled,
+	C1	= Ledon*Aled | Ledoff*Locled,
+
+};
+
+//static ushort led3[Ibpilast*8] = {
+//[Ibpinone*8]	0,	0,	0,	0,	0,	0,	0,	0,
+//[Ibpinormal*8]	N0,	N0,	N0,	N0,	N0,	N0,	N0,	N0,
+//[Ibpirebuild*8]	R0,	R0,	R0,	R0,	R1,	R1,	R1,	R1,
+//[Ibpilocate*8]	L0,	L1,	L0,	L1,	L0,	L1,	L0,	L1,
+//[Ibpispare*8]	S0,	S1,	S0,	S1,	S1,	S1,	S1,	S1,
+//[Ibpipfa*8]	P0,	P1,	P0,	P1,	P1,	P1,	P1,	P1,	/* first 1 sec */
+//[Ibpifail*8]	F0,	F0,	F0,	F0,	F0,	F0,	F0,	F0,
+//[Ibpicritarray*8]	C0,	C0,	C0,	C0,	C1,	C1,	C1,	C1,
+//[Ibpifailarray*8]	C0,	C1,	C0,	C1,	C0,	C1,	C0,	C1,
+//};
+
+static ushort led2[Ibpilast*8] = {
+[Ibpinone*8]	0,	0,	0,	0,	0,	0,	0,	0,
+[Ibpinormal*8]	N0,	N0,	N0,	N0,	N0,	N0,	N0,	N0,
+[Ibpirebuild*8]	R0,	R0,	R0,	R0,	R1,	R1,	R1,	R1,
+[Ibpilocate*8]	L0,	L0,	L0,	L0,	L0,	L0,	L0,	L0,
+[Ibpispare*8]	S0,	S0,	S0,	S0,	S1,	S1,	S1,	S1,
+[Ibpipfa*8]	P0,	P1,	P0,	P1,	P1,	P1,	P1,	P1,	/* first 1 sec */
+[Ibpifail*8]	F0,	F0,	F0,	F0,	F0,	F0,	F0,	F0,
+[Ibpicritarray*8]	C0,	C0,	C0,	C0,	C1,	C1,	C1,	C1,
+[Ibpifailarray*8]	C0,	C1,	C0,	C1,	C0,	C1,	C0,	C1,
+};
+
+static int
+ledstate(Ledport *p, uint seq)
+{
+	ushort i;
+
+	if(p->led == Ibpipfa && seq%32 >= 8)
+		i = P1;
+	else
+		i = led2[8*p->led + seq%8];
+	if(i != p->ledbits){
+		p->ledbits = i;
+		ledprint("ledstate %,.011ub %ud\n", p->ledbits, seq);
+		return 1;
+	}
+	return 0;
+}
+
+static int
+blink(Drive *d, ulong t)
+{
+	Ahba *h;
+	Ctlr *c;
+	Aledmsg msg;
+
+	if(ledstate(d, t) == 0)
+		return 0;
+	c = d->ctlr;
+	h = c->hba;
+	/* ensure last message has been transmitted */
+	while(h->emctl & Tmsg)
+		microdelay(1);
+	switch(c->enctype){
+	default:
+		panic("%s: bad led type %d\n", dnam(d), c->enctype);
+	case Elmt:
+		memset(&msg, 0, sizeof msg);
+		msg.type = Mled;
+		msg.dsize = 0;
+		msg.msize = sizeof msg - 4;
+		msg.led[0] = d->ledbits;
+		msg.led[1] = d->ledbits>>8;
+		msg.pm = 0;
+		msg.hba = d->driveno;
+		memmove(c->enctx, &msg, sizeof msg);
+		break;
+	}
+	h->emctl |= Tmsg;
+	return 1;
+}
+
+enum {
+	Esbdrv0	= 4,		/* start pos in bits */
+	Esbiota	= 3,		/* shift in bits */
+	Esbact	= 1,
+	Esbloc	= 2,
+	Esberr	= 4,
+};
+
+uint
+esbbits(uint s)
+{
+	uint i, e;				/* except after c */
+
+	e = 0;
+	for(i = 0; i < 3; i++)
+		e |= ((s>>3*i & 7) != 0)<<i;
+	return e;
+}
+
+static int
+blinkesb(Ctlr *c, ulong t)
+{
+	uint i, s, u[32/4];
+	uvlong v;
+	Drive *d;
+
+	s = 0;
+	for(i = 0; i < c->ndrive; i++){
+		d = c->drive[i];
+		s |= ledstate(d, t);		/* no port mapping */
+	}
+	if(s == 0)
+		return 0;
+	memset(u, 0, sizeof u);
+	for(i = 0; i < c->ndrive; i++){
+		d = c->drive[i];
+		s = Esbdrv0 + Esbiota*i;
+		v = esbbits(d->ledbits) * (1ull << s%32);
+		u[s/32 + 0] |= v;
+		u[s/32 + 1] |= v>>32;
+	}
+	for(i = 0; i < c->encsz; i++)
+		c->enctx[i] = u[i];
+	return 1;
+}
+
+static long
+ahciledr(SDunit *u, Chan *ch, void *a, long n, vlong off)
+{
+	Ctlr *c;
+	Drive *d;
+
+	c = u->dev->ctlr;
+	d = c->drive[u->subno];
+	return ledr(d, ch, a, n, off);
+}
+
+static long
+ahciledw(SDunit *u, Chan *ch, void *a, long n, vlong off)
+{
+	Ctlr *c;
+	Drive *d;
+
+	c = u->dev->ctlr;
+	d = c->drive[u->subno];
+	return ledw(d, ch, a, n, off);
+}
+
+static void
+ledkproc(void*)
+{
+	uchar map[NCtlr];
+	uint i, j, t0, t1;
+	Ctlr *c;
+	Drive *d;
+
+	j = 0;
+	memset(map, 0, sizeof map);
+	for(i = 0; i < niactlr; i++)
+		if(iactlr[i].enctype != 0){
+			ahciencreset(iactlr + i);
+			map[i] = 1;
+			j++;
+		}
+	if(j == 0)
+		pexit("no work", 1);
+	for(i = 0; i < niadrive; i++){
+		iadrive[i]->nled = 3;		/* hardcoded */
+		if(iadrive[i]->ctlr->enctype == Eesb)
+			iadrive[i]->nled = 3;
+		iadrive[i]->ledbits = -1;
+	}
+	for(i = 0; ; i++){
+		t0 = Ticks;
+		for(j = 0; j < niadrive; ){
+			c = iadrive[j]->ctlr;
+			if(map[j] == 0)
+				j += c->enctype;
+			else if(c->enctype == Eesb){
+				blinkesb(c, i);
+				j += c->ndrive;
+			}else{
+				d = iadrive[j++];
+				blink(d, i);
+			}
+		}
+		t1 = Ticks;
+		esleep(Ledsleep - TK2MS(t1 - t0));
+	}
+}
+
+static int
 iaverify(SDunit *u)
 {
 	Ctlr *c;
@@ -1374,7 +1410,11 @@
 	d = c->drive[u->subno];
 	ilock(c);
 	ilock(d);
-	d->unit = u;
+	if(d->unit == nil){
+		d->unit = u;
+		if(c->enctype != 0)
+			sdaddfile(u, "led", 0644, eve, ahciledr, ahciledw);
+	}
 	iunlock(d);
 	iunlock(c);
 	checkdrive(d, d->driveno);		/* c->d0 + d->driveno */
@@ -1390,11 +1430,9 @@
 
 	c = s->ctlr;
 	ilock(c);
-	if(!c->enabled) {
-		if(once == 0) {
-			once = 1;
+	if(!c->enabled){
+		if(once == 0)
 			kproc("iasata", satakproc, 0);
-		}
 		if(c->ndrive == 0)
 			panic("iaenable: zero s->ctlr->ndrive");
 		pcisetbme(c->pci);
@@ -1403,6 +1441,8 @@
 		/* supposed to squelch leftover interrupts here. */
 		ahcienable(c->hba);
 		c->enabled = 1;
+		if(++once == niactlr)
+			kproc("ialed", ledkproc, 0);
 	}
 	iunlock(c);
 	return 1;
@@ -1435,84 +1475,40 @@
 	d = c->drive[unit->subno];
 	r = 0;
 
-	if(d->portm.feat & Datapi && d->mediachange){
+	if(d->portm.feat & Datapi && d->drivechange){
 		r = scsionline(unit);
 		if(r > 0)
-			d->mediachange = 0;
+			d->drivechange = 0;
 		return r;
 	}
 
 	ilock(d);
-	if(d->mediachange){
+	if(d->drivechange){
 		r = 2;
-		d->mediachange = 0;
+		d->drivechange = 0;
 		/* devsd resets this after online is called; why? */
 		unit->sectors = d->sectors;
-		unit->secsize = 512;		/* default size */
-	} else if(d->state == Dready)
+		unit->secsize = d->secsize;
+	}else if(d->state == Dready)
 		r = 1;
 	iunlock(d);
 	return r;
 }
 
-/* returns locked list! */
 static Alist*
-ahcibuild(Drive *d, uchar *cmd, void *data, int n, vlong lba)
+ahcibuild(Aportm *m, int rw, void *data, uint n, vlong lba)
 {
-	uchar *c, acmd, dir, llba;
+	uchar *c;
+	uint flags;
 	Alist *l;
-	Actab *t;
-	Aportm *m;
-	Aprdt *p;
-	static uchar tab[2][2] = { 0xc8, 0x25, 0xca, 0x35, };
 
-	m = &d->portm;
-	dir = *cmd != 0x28;
-	llba = m->feat&Dllba? 1: 0;
-	acmd = tab[dir][llba];
-	qlock(m);
 	l = m->list;
-	t = m->ctab;
-	c = t->cfis;
-
-	c[0] = 0x27;
-	c[1] = 0x80;
-	c[2] = acmd;
-	c[3] = 0;
-
-	c[4] = lba;		/* sector		lba low	7:0 */
-	c[5] = lba >> 8;	/* cylinder low		lba mid	15:8 */
-	c[6] = lba >> 16;	/* cylinder hi		lba hi	23:16 */
-	c[7] = 0xa0 | 0x40;	/* obsolete device bits + lba */
-	if(llba == 0)
-		c[7] |= (lba>>24) & 7;
-
-	c[8] = lba >> 24;	/* sector (exp)		lba 	31:24 */
-	c[9] = lba >> 32;	/* cylinder low (exp)	lba	39:32 */
-	c[10] = lba >> 48;	/* cylinder hi (exp)	lba	48:40 */
-	c[11] = 0;		/* features (exp); */
-
-	c[12] = n;		/* sector count */
-	c[13] = n >> 8;		/* sector count (exp) */
-	c[14] = 0;		/* r */
-	c[15] = 0;		/* control */
-
-	*(ulong*)(c + 16) = 0;
-
-	l->flags = 1<<16 | Lpref | 0x5;	/* Lpref ?? */
-	if(dir == Write)
-		l->flags |= Lwrite;
-	l->len = 0;
-	l->ctab = PCIWADDR(t);
-	l->ctabhi = 0;
-
-	p = &t->prdt;
-	p->dba = PCIWADDR(data);
-	p->dbahi = 0;
-	if(d->unit == nil)
-		panic("ahcibuild: nil d->unit");
-	p->count = 1<<31 | (d->unit->secsize*n - 2) | 1;
-
+	c = m->ctab->cfis;
+	rwfis(m, c, rw, n, lba);
+	flags = Lpref;
+	if(rw == SDwrite)
+		flags |= Lwrite;
+	mkalist(m, flags, data, 512*n);
 	return l;
 }
 
@@ -1519,55 +1515,44 @@
 static Alist*
 ahcibuildpkt(Aportm *m, SDreq *r, void *data, int n)
 {
-	int fill, len;
+	uint flags;
 	uchar *c;
-	Alist *l;
 	Actab *t;
-	Aprdt *p;
+	Alist *l;
 
-	qlock(m);
 	l = m->list;
 	t = m->ctab;
 	c = t->cfis;
-
-	fill = m->feat&Datapi16? 16: 12;
-	if((len = r->clen) > fill)
-		len = fill;
-	memmove(t->atapi, r->cmd, len);
-	memset(t->atapi+len, 0, fill-len);
-
-	c[0] = 0x27;
-	c[1] = 0x80;
-	c[2] = 0xa0;
-	if(n != 0)
-		c[3] = 1;	/* dma */
-	else
-		c[3] = 0;	/* features (exp); */
-
-	c[4] = 0;		/* sector		lba low	7:0 */
-	c[5] = n;		/* cylinder low		lba mid	15:8 */
-	c[6] = n >> 8;		/* cylinder hi		lba hi	23:16 */
-	c[7] = 0xa0;		/* obsolete device bits */
-
-	*(ulong*)(c + 8) = 0;
-	*(ulong*)(c + 12) = 0;
-	*(ulong*)(c + 16) = 0;
-
-	l->flags = 1<<16 | Lpref | Latapi | 0x5;
+	atapirwfis(m, c, r->cmd, r->clen, n);
+	flags = 1<<16 | Lpref | Latapi;
 	if(r->write != 0 && data)
-		l->flags |= Lwrite;
-	l->len = 0;
-	l->ctab = PCIWADDR(t);
-	l->ctabhi = 0;
+		flags |= Lwrite;
+	mkalist(m, flags, data, n);
+	return l;
+}
 
-	if(data == 0)
-		return l;
+static Alist*
+ahcibuildfis(Aportm *m, SDreq *r, void *data, uint n)
+{
+	uchar *c;
+	uint flags;
+	Alist *l;
 
-	p = &t->prdt;
-	p->dba = PCIWADDR(data);
-	p->dbahi = 0;
-	p->count = 1<<31 | (n - 2) | 1;
-
+	l = m->list;
+	c = m->ctab->cfis;
+	if((r->ataproto & Pprotom) != Ppkt){
+		memmove(c, r->cmd, r->clen);
+		flags = Lpref;
+		if(r->write || n == 0)
+			flags |= Lwrite;
+		mkalist(m, flags, data, n);
+	}else{
+		atapirwfis(m, c, r->cmd, r->clen, n);
+		flags = 1<<16 | Lpref | Latapi;
+		if(r->write && data)
+			flags |= Lwrite;
+		mkalist(m, flags, data, n);
+	}
 	return l;
 }
 
@@ -1580,19 +1565,19 @@
 		if(d->state == Dreset || d->state == Dportreset ||
 		    d->state == Dnew)
 			return 1;
-		δ = MACHP(0)->ticks - d->lastseen;
+		δ = Ticks - d->lastseen;
 		if(d->state == Dnull || δ > 10*1000)
 			return -1;
 		ilock(d);
 		s = d->port->sstatus;
 		iunlock(d);
-		if((s & 0x700) == 0 && δ > 1500)
-			return -1;	/* no detect */
-		if(d->state == Dready && (s & 7) == 3)
-			return 0;	/* ready, present & phy. comm. */
+		if((s & Imask) == 0 && δ > 1500)
+			return -1;
+		if(d->state == Dready && (s & Smask) == Sphylink)
+			return 0;
 		esleep(250);
 	}
-	print("%s: not responding; offline\n", d->unit->name);
+	print("%s: not responding; offline\n", dnam(d));
 	ilock(d);
 	d->state = Doffline;
 	iunlock(d);
@@ -1626,55 +1611,43 @@
 }
 
 static int
-iariopkt(SDreq *r, Drive *d)
+io(Drive *d, uint proto, int to, int interrupt)
 {
-	int n, count, try, max, flag, task;
-	char *name;
-	uchar *cmd, *data;
+	uint task, flag, rv;
 	Aport *p;
 	Asleep as;
 
-	cmd = r->cmd;
-	name = d->unit->name;
-	p = d->port;
-
-	aprint("ahci: iariopkt: %02ux %02ux %c %d %p\n",
-		cmd[0], cmd[2], "rw"[r->write], r->dlen, r->data);
-	if(cmd[0] == 0x5a && (cmd[2] & 0x3f) == 0x3f)
-		return sdmodesense(r, cmd, d->info, sizeof d->info);
-	r->rlen = 0;
-	count = r->dlen;
-	max = 65536;
-
-	try = 0;
-retry:
-	data = r->data;
-	n = count;
-	if(n > max)
-		n = max;
-	ahcibuildpkt(&d->portm, r, data, n);
 	switch(waitready(d)){
 	case -1:
-		qunlock(&d->portm);
 		return SDeio;
 	case 1:
-		qunlock(&d->portm);
-		esleep(1);
-		goto retry;
+		return SDretry;
 	}
 
 	ilock(d);
 	d->portm.flag = 0;
 	iunlock(d);
+	p = d->port;
 	p->ci = 1;
 
 	as.p = p;
 	as.i = 1;
-	d->intick = MACHP(0)->ticks;
+	d->totick = 0;
+	if(to > 0)
+		d->totick = Ticks + MS2TK(to) | 1;	/* fix fencepost */
 	d->active++;
 
 	while(waserror())
-		;
+		if(interrupt){
+			d->active--;
+			d->port->ci = 0;
+			if(ahcicomreset(&d->portc) == -1){
+				ilock(d);
+				d->state = Dreset;
+				iunlock(d);
+			}
+			return SDtimeout;
+		}
 	sleep(&d->portm, ahciclear, &as);
 	poperror();
 
@@ -1684,45 +1657,117 @@
 	task = d->port->task;
 	iunlock(d);
 
-	if(task & (Efatal<<8) || task & (ASbsy|ASdrq) && d->state == Dready){
+	rv = SDok;
+	if(proto & Ppkt){
+		rv = task >> 8 + 4 & 0xf;
+		flag &= ~Fahdrs;
+		flag |= Fdone;
+	}else if(task & (Efatal<<8) || task & (ASbsy|ASdrq) && d->state == Dready){
 		d->port->ci = 0;
 		ahcirecover(&d->portc);
 		task = d->port->task;
 		flag &= ~Fdone;		/* either an error or do-over */
 	}
-	qunlock(&d->portm);
 	if(flag == 0){
-		if(++try == 10){
-			print("%s: bad disk\n", name);
-			r->status = SDcheck;
-			return SDcheck;
-		}
-		print("%s: retry\n", name);
-		goto retry;
+		print("%s: retry\n", dnam(d));
+		return SDretry;
 	}
-	if(flag & Ferror){
-		if((task&Eidnf) == 0)
-			print("%s: i/o error %ux\n", name, task);
-		r->status = SDcheck;
+	if(flag & (Fahdrs | Ferror)){
+		if((task & Eidnf) == 0)
+			print("%s: i/o error %ux\n", dnam(d), task);
 		return SDcheck;
 	}
+	return rv;
+}
 
-	data += n;
+static int
+iariopkt(SDreq *r, Drive *d)
+{
+	int n, count, try, max;
+	uchar *cmd;
 
-	r->rlen = data - (uchar*)r->data;
-	r->status = SDok;
-	return SDok;
+	cmd = r->cmd;
+	aprint("%s: %.2ux %.2ux %c %d %p\n", dnam(d), cmd[0], cmd[2],
+		"rw"[r->write], r->dlen, r->data);
+	r->rlen = 0;
+	count = r->dlen;
+	max = 65536;
+
+	for(try = 0; try < 10; try++){
+		n = count;
+		if(n > max)
+			n = max;
+		qlock(&d->portm);
+		ahcibuildpkt(&d->portm, r, r->data, n);
+		r->status = io(d, Ppkt, 5000, 0);
+		qunlock(&d->portm);
+		switch(r->status){
+		case SDeio:
+			return SDeio;
+		case SDretry:
+			continue;
+		}
+//		print("%.2ux :: %.2ux :: %.4ux\n", r->cmd[0], r->status, d->port->task);
+		r->rlen = d->portm.list->len;
+		return SDok;
+	}
+	print("%s: bad disk\n", dnam(d));
+	return r->status = SDcheck;
 }
 
+static long
+ahcibio(SDunit *u, int lun, int write, void *a, long count, uvlong lba)
+{
+	int n, rw, try, status, max;
+	uchar *data;
+	Ctlr *c;
+	Drive *d;
+
+	c = u->dev->ctlr;
+	d = c->drive[u->subno];
+	if(d->portm.feat & Datapi)
+		return scsibio(u, lun, write, a, count, lba);
+
+	max = 128;
+	if(d->portm.feat & Dllba){
+		max = 8192;		/* ahci maximum */
+		if(c->type == Tsb600)
+			max = 255;	/* errata */
+	}
+	rw = write? SDwrite: SDread;
+	data = a;
+	for(try = 0; try < 10;){
+		n = count;
+		if(n > max)
+			n = max;
+		qlock(&d->portm);
+		ahcibuild(&d->portm, rw, data, n, lba);
+		status = io(d, Pdma, 5000, 0);
+		qunlock(&d->portm);
+		switch(status){
+		case SDeio:
+			return -1;
+		case SDretry:
+			try++;
+			continue;
+		}
+		try = 0;
+		count -= n;
+		lba   += n;
+		data += n * u->secsize;
+		if(count == 0)
+			return data - (uchar*)a;
+	}
+	print("%s: bad disk\n", dnam(d));
+	return -1;
+}
+
 static int
 iario(SDreq *r)
 {
-	int i, n, count, try, max, flag, task;
-	vlong lba;
-	char *name;
-	uchar *cmd, *data;
-	Aport *p;
-	Asleep as;
+	int i, n, count, rw;
+	uchar *cmd;
+	uvlong lba;
 	Ctlr *c;
 	Drive *d;
 	SDunit *unit;
@@ -1733,101 +1778,123 @@
 	if(d->portm.feat & Datapi)
 		return iariopkt(r, d);
 	cmd = r->cmd;
-	name = d->unit->name;
-	p = d->port;
 
-	if(r->cmd[0] == 0x35 || r->cmd[0] == 0x91){
+	if(cmd[0] == 0x35 || cmd[0] == 0x91){
 		if(flushcache(d) == 0)
 			return sdsetsense(r, SDok, 0, 0, 0);
 		return sdsetsense(r, SDcheck, 3, 0xc, 2);
 	}
 
-	if((i = sdfakescsi(r, d->info, sizeof d->info)) != SDnostatus){
+	if((i = sdfakescsi(r)) != SDnostatus){
 		r->status = i;
 		return i;
 	}
 
-	if(*cmd != 0x28 && *cmd != 0x2a){
-		print("%s: bad cmd 0x%.2ux\n", name, cmd[0]);
-		r->status = SDcheck;
-		return SDcheck;
-	}
+	if((i = sdfakescsirw(r, &lba, &count, &rw)) != SDnostatus)
+		return i;
+	n = ahcibio(unit, r->lun, r->write, r->data, count, lba);
+	if(n == -1)
+		return SDeio;
+	r->rlen = n;
+	return SDok;
+}
 
-	lba   = cmd[2]<<24 | cmd[3]<<16 | cmd[4]<<8 | cmd[5];
-	count = cmd[7]<<8 | cmd[8];
-	if(r->data == nil)
-		return SDok;
-	if(r->dlen < count * unit->secsize)
-		count = r->dlen / unit->secsize;
-	max = 128;
+static uchar bogusrfis[16] = {
+[Ftype]		0x34,
+[Fioport]	0x40,
+[Fstatus]		0x50,
 
-	try = 0;
-retry:
-	data = r->data;
-	while(count > 0){
-		n = count;
-		if(n > max)
-			n = max;
-		ahcibuild(d, cmd, data, n, lba);
-		switch(waitready(d)){
-		case -1:
-			qunlock(&d->portm);
-			return SDeio;
-		case 1:
-			qunlock(&d->portm);
-			esleep(1);
-			goto retry;
-		}
-		ilock(d);
-		d->portm.flag = 0;
-		iunlock(d);
-		p->ci = 1;
+[Fdev]		0xa0,
+};
 
-		as.p = p;
-		as.i = 1;
-		d->intick = MACHP(0)->ticks;
-		d->active++;
+static void
+sdr0(Drive *d)
+{
+	uchar *c;
 
-		while(waserror())
-			;
-		sleep(&d->portm, ahciclear, &as);
-		poperror();
+	c = d->portm.fis.r;
+	memmove(c, bogusrfis, sizeof bogusrfis);
+	coherence();
+}
 
-		d->active--;
-		ilock(d);
-		flag = d->portm.flag;
-		task = d->port->task;
-		iunlock(d);
+static int
+sdr(SDreq *r, Drive *d, int st)
+{
+	uchar *c;
+	uint t;
 
-		if(task & (Efatal<<8) ||
-		    task & (ASbsy|ASdrq) && d->state == Dready){
-			d->port->ci = 0;
-			ahcirecover(&d->portc);
-			task = d->port->task;
-		}
+	if((r->ataproto & Pprotom) == Ppkt){
+		t = d->port->task;
+		if(t & ASerr)
+			st = t >> 8 + 4 & 0xf;
+	}
+	c = d->portm.fis.r;
+	memmove(r->cmd, c, 16);
+	r->status = st;
+	if(st == SDcheck)
+		st = SDok;
+	return st;
+}
+
+static int
+fisreqchk(Sfis *f, SDreq *r)
+{
+	if((r->ataproto & Pprotom) == Ppkt)
+		return SDnostatus;
+	/*
+	 * handle oob requests;
+	 *    restrict & sanitize commands
+	 */
+	if(r->clen != 16)
+		error(Eio);
+	if(r->cmd[0] == 0xf0){
+		sigtofis(f, r->cmd);
+		r->status = SDok;
+		return SDok;
+	}
+	r->cmd[0] = 0x27;
+	r->cmd[1] = 0x80;
+	r->cmd[7] |= 0xa0;
+	return SDnostatus;
+}
+
+static int
+iaataio(SDreq *r)
+{
+	int try;
+	Ctlr *c;
+	Drive *d;
+	SDunit *u;
+
+	u = r->unit;
+	c = u->dev->ctlr;
+	d = c->drive[u->subno];
+
+	if((r->status = fisreqchk(&d->portm, r)) != SDnostatus)
+		return r->status;
+	r->rlen = 0;
+	sdr0(d);
+	for(try = 0; try < 10; try++){
+		qlock(&d->portm);
+		ahcibuildfis(&d->portm, r, r->data, r->dlen);
+		r->status = io(d, r->ataproto & Pprotom, -1, 1);
 		qunlock(&d->portm);
-		if(flag == 0){
-			if(++try == 10){
-				print("%s: bad disk\n", name);
-				r->status = SDeio;
-				return SDeio;
-			}
-			iprint("%s: retry %lld\n", name, lba);
-			goto retry;
-		}
-		if(flag & Ferror){
-			iprint("%s: i/o error %ux @%,lld\n", name, task, lba);
-			r->status = SDeio;
+		switch(r->status){
+		case SDtimeout:
+			return sdsetsense(r, SDcheck, 11, 0, 6);
+		case SDeio:
 			return SDeio;
+		case SDretry:
+			continue;
 		}
-
-		count -= n;
-		lba   += n;
-		data += n * unit->secsize;
+		r->rlen = r->dlen;
+		if((r->ataproto & Pprotom) == Ppkt)
+			r->rlen = d->portm.list->len;
+		return sdr(r, d, r->status);
 	}
-	r->rlen = data - (uchar*)r->data;
-	r->status = SDok;
-	return SDok;
+	print("%s: bad disk\n", dnam(d));
+	r->status = SDeio;
+	return SDeio;
 }
 
 /*
@@ -1836,54 +1903,156 @@
 static int
 iaahcimode(Pcidev *p)
 {
-	dprint("iaahcimode: %ux %ux %ux\n", pcicfgr8(p, 0x91), pcicfgr8(p, 92),
-		pcicfgr8(p, 93));
-	pcicfgw16(p, 0x92, pcicfgr32(p, 0x92) | 0xf);	/* ports 0-3 */
-//	pcicfgw8(p, 0x93, pcicfgr32(p, 9x93) | 3);	/* ports 4-5 */
+	uint u;
+
+	u = pcicfgr16(p, 0x92);
+	dprint("ahci: %T: iaahcimode %.2ux %.4ux\n", p->tbdf, pcicfgr8(p, 0x91), u);
+	pcicfgw16(p, 0x92, u | 0xf);	/* ports 0-15 */
 	return 0;
 }
 
+enum{
+	Ghc	= 0x04/4,	/* global host control */
+	Pi	= 0x0c/4,	/* ports implemented */
+	Cmddec	= 1<<15,	/* enable command block decode */
+
+	/* Ghc bits */
+	Ahcien	= 1<<31,	/* ahci enable */
+};
+
 static void
 iasetupahci(Ctlr *c)
 {
-	/* disable cmd block decoding. */
-	pcicfgw16(c->pci, 0x40, pcicfgr16(c->pci, 0x40) & ~(1<<15));
-	pcicfgw16(c->pci, 0x42, pcicfgr16(c->pci, 0x42) & ~(1<<15));
+	pcicfgw16(c->pci, 0x40, pcicfgr16(c->pci, 0x40) & ~Cmddec);
+	pcicfgw16(c->pci, 0x42, pcicfgr16(c->pci, 0x42) & ~Cmddec);
 
-	c->lmmio[0x4/4] |= 1 << 31;	/* enable ahci mode (ghc register) */
-	c->lmmio[0xc/4] = (1 << 6) - 1;	/* 5 ports. (supposedly ro pi reg.) */
+	c->lmmio[Ghc] |= Ahcien;
+	c->lmmio[Pi] = (1 << 6) - 1;	/* 5 ports (supposedly ro pi reg) */
 
 	/* enable ahci mode; from ich9 datasheet */
 	pcicfgw16(c->pci, 0x90, 1<<6 | 1<<5);
 }
 
+static void
+sbsetupahci(Pcidev *p)
+{
+	print("sbsetupahci: tweaking %.4ux ccru %.2ux ccrp %.2ux\n",
+		p->did, p->ccru, p->ccrp);
+	pcicfgw8(p, 0x40, pcicfgr8(p, 0x40) | 1);
+	pcicfgw8(p, PciCCRu, 6);
+	pcicfgw8(p, PciCCRp, 1);
+	p->ccru = 6;
+	p->ccrp = 1;
+}
+
 static int
+esbenc(Ctlr *c)
+{
+	c->encsz = 1;
+	c->enctx = (ulong*)(c->mmio + 0xa0);
+	c->enctype = Eesb;
+	c->enctx[0] = 0;
+	return 0;
+}
+
+static int
+ahciencinit(Ctlr *c)
+{
+	ulong type, sz, o, *bar;
+	Ahba *h;
+
+	h = c->hba;
+	if(c->type == Tesb)
+		return esbenc(c);
+	if((h->cap & Hems) == 0)
+		return -1;
+	type = h->emctl & Emtype;
+	switch(type){
+	case Esgpio:
+	case Eses2:
+	case Esafte:
+		return -1;
+	case Elmt:
+		break;
+	default:
+		return -1;
+	}
+
+	sz = h->emloc & 0xffff;
+	o = h->emloc>>16;
+	if(sz == 0 || o == 0)
+		return -1;
+	bar = c->lmmio;
+	dprint("size = %.4lux; loc = %.4lux*4\n", sz, o);
+	c->encsz = sz;
+	c->enctx = bar + o;
+	if((h->emctl & Xonly) == 0){
+		if(h->emctl & Smb)
+			c->encrx = bar + o;
+		else
+			c->encrx = bar + o*2;
+	}
+	c->enctype = type;
+	return 0;
+}
+
+static int
 didtype(Pcidev *p)
 {
+	int type;
+
+	type = Tahci;
 	switch(p->vid){
+	default:
+		return -1;
 	case 0x8086:
 		if((p->did & 0xfffc) == 0x2680)
 			return Tesb;
+		if((p->did & 0xfffb) == 0x27c1)
+			return Tich;		/* 82801g[bh]m */
+		if((p->did & 0xffff) == 0x2821)
+			return Tich;		/* 82801h[roh] */
+		if((p->did & 0xfffe) == 0x2824)
+			return Tich;		/* 82801h[b] */
+		if((p->did & 0xfeff) == 0x2829)
+			return Tich;		/* ich8 */
+		if((p->did & 0xfffe) == 0x2922)
+			return Tich;		/* ich9 */
+		if((p->did & 0xffff)  == 0x3a02)
+			return Tich;		/* 82801jd/do */
+		if((p->did & 0xfefe)  == 0x3a22)
+			return Tich;		/* ich10, pch */
+		if((p->did & 0xfff7)  == 0x3b28)
+			return Tich;		/* pchm */
+		if((p->did & 0xfffe) == 0x3b22)
+			return Tich;		/* pch */
+		break;
+	case 0x1002:
+		if(p->ccru == 1 || p->ccrp != 1)
+		if(p->did == 0x4380 || p->did == 0x4390)
+			sbsetupahci(p);
+		type = Tsb600;
+		break;
+	case 0x1106:
 		/*
-		 * 0x27c4 is the intel 82801 in compatibility (not sata) mode.
+		 * unconfirmed report that the programming
+		 * interface is set incorrectly.
 		 */
-		if ((p->did & 0xfffb) == 0x27c1 ||	/* 82801g[bh]m ich7 */
-		    p->did == 0x2821 ||			/* 82801h[roh] */
-		    (p->did & 0xfffe) == 0x2824 ||	/* 82801h[b] */
-		    (p->did & 0xfeff) == 0x2829 ||	/* ich8/9m */
-		    (p->did & 0xfffe) == 0x2922 ||	/* ich9 */
-		    p->did == 0x3a02 ||			/* 82801jd/do */
-		    (p->did & 0xfefe) == 0x3a22 ||	/* ich10, pch */
-		    (p->did & 0xfff7) == 0x3b28)	/* pchm */
-			return Tich;
+		if(p->did == 0x3349)
+			return Tahci;
 		break;
-	case 0x1002:
-		if(p->did == 0x4380)
-			return Tsb600;
+	case 0x10de:
+	case 0x1039:
+	case 0x1b4b:
+	case 0x11ab:
 		break;
+	case 0x197b:
+	case 0x10b9:
+		type = Tjmicron;
+		break;
 	}
 	if(p->ccrb == Pcibcstore && p->ccru == 6 && p->ccrp == 1)
-		return Tunk;
+		return type;
 	return -1;
 }
 
@@ -1895,33 +2064,32 @@
 	Ctlr *c;
 	Drive *d;
 	Pcidev *p;
-	SDev *head, *tail, *s;
+	SDev *s;
 	static int done;
 
-	if(done++)
+	if(done)
 		return nil;
-
+	done = 1;
 	memset(olds, 0xff, sizeof olds);
 	p = nil;
-	head = tail = nil;
 loop:
 	while((p = pcimatch(p, 0, 0)) != nil){
-		type = didtype(p);
-		if (type == -1 || p->mem[Abar].bar == 0)
+		if((type = didtype(p)) == -1)
 			continue;
+		if(p->mem[Abar].bar == 0)
+			continue;
 		if(niactlr == NCtlr){
-			print("ahci: iapnp: %s: too many controllers\n",
-				tname[type]);
+			print("iapnp: %s: too many controllers\n", tname[type]);
 			break;
 		}
 		c = iactlr + niactlr;
-		s = sdevs  + niactlr;
+		s = sdevs + niactlr;
 		memset(c, 0, sizeof *c);
 		memset(s, 0, sizeof *s);
 		io = p->mem[Abar].bar & ~0xf;
 		c->mmio = vmap(io, p->mem[Abar].size);
 		if(c->mmio == 0){
-			print("ahci: %s: address 0x%luX in use did=%x\n",
+			print("%s: address %#p in use did %.4ux\n",
 				Tname(c), io, p->did);
 			continue;
 		}
@@ -1930,31 +2098,24 @@
 		c->type = type;
 
 		s->ifc = &sdiahciifc;
-		s->idno = 'E' + niactlr;
+		s->idno = 'E';
 		s->ctlr = c;
 		c->sdev = s;
 
-		if(Intel(c) && p->did != 0x2681)
+		if(intel(c) && p->did != 0x2681)
 			iasetupahci(c);
-		nunit = ahciconf(c);
 //		ahcihbareset((Ahba*)c->mmio);
-		if(Intel(c) && iaahcimode(p) == -1)
-			break;
-		if(nunit < 1){
+		nunit = ahciconf(c);
+		if(intel(c) && iaahcimode(p) == -1 || nunit < 1){
 			vunmap(c->mmio, p->mem[Abar].size);
 			continue;
 		}
 		c->ndrive = s->nunit = nunit;
-		c->mport = c->hba->cap & ((1<<5)-1);
 
-		i = (c->hba->cap >> 21) & 1;
-		print("#S/sd%c: %s: sata-%s with %d ports\n", s->idno,
-			Tname(c), "I\0II" + i*2, nunit);
-
 		/* map the drives -- they don't all need to be enabled. */
 		memset(c->rawdrive, 0, sizeof c->rawdrive);
 		n = 0;
-		for(i = 0; i < NCtlrdrv; i++) {
+		for(i = 0; i < NCtlrdrv; i++){
 			d = c->rawdrive + i;
 			d->portno = i;
 			d->driveno = -1;
@@ -1961,8 +2122,9 @@
 			d->sectors = 0;
 			d->serial[0] = ' ';
 			d->ctlr = c;
-			if((c->hba->pi & (1<<i)) == 0)
+			if((c->hba->pi & 1<<i) == 0)
 				continue;
+			snprint(d->name, sizeof d->name, "iahci%d.%d", niactlr, i);
 			d->port = (Aport*)(c->mmio + 0x80*i + 0x100);
 			d->portc.p = d->port;
 			d->portc.m = &d->portm;
@@ -1972,58 +2134,69 @@
 		}
 		for(i = 0; i < n; i++)
 			if(ahciidle(c->drive[i]->port) == -1){
-				dprint("ahci: %s: port %d wedged; abort\n",
+				print("%s: port %d wedged; abort\n",
 					Tname(c), i);
 				goto loop;
 			}
 		for(i = 0; i < n; i++){
-			c->drive[i]->mode = DMsatai;
+			c->drive[i]->mode = DMautoneg;
 			configdrive(c->drive[i]);
 		}
+		ahciencinit(c);
 
 		niadrive += n;
 		niactlr++;
-		if(head)
-			tail->next = s;
-		else
-			head = s;
-		tail = s;
+		sdadddevs(s);
+		i = (c->hba->cap >> 21) & 1;
+		print("#S/%s: %s: sata-%s with %d ports\n", s->name,
+			Tname(c), "I\0II" + i*2, nunit);
 	}
-	return head;
+	return nil;
 }
 
-static char* smarttab[] = {
-	"unset",
-	"error",
-	"threshold exceeded",
-	"normal"
+static Htab ctab[] = {
+	Aasp,	"asp",
+	Aalpe ,	"alpe ",
+	Adlae,	"dlae",
+	Aatapi,	"atapi",
+	Apste,	"pste",
+	Afbsc,	"fbsc",
+	Aesp,	"esp",
+	Acpd,	"cpd",
+	Ampsp,	"mpsp",
+	Ahpcp,	"hpcp",
+	Apma,	"pma",
+	Acps,	"cps",
+	Acr,	"cr",
+	Afr,	"fr",
+	Ampss,	"mpss",
+	Apod,	"pod",
+	Asud,	"sud",
+	Ast,	"st",
 };
 
-static char *
-pflag(char *s, char *e, uchar f)
+static char*
+capfmt(char *p, char *e, Htab *t, int n, ulong cap)
 {
-	uchar i;
+	uint i;
 
-	for(i = 0; i < 8; i++)
-		if(f & (1 << i))
-			s = seprint(s, e, "%s ", flagname[i]);
-	return seprint(s, e, "\n");
+	*p = 0;
+	for(i = 0; i < n; i++)
+		if(cap & t[i].bit)
+			p = seprint(p, e, "%s ", t[i].name);
+	return p;
 }
 
 static int
 iarctl(SDunit *u, char *p, int l)
 {
-	char buf[32];
-	char *e, *op;
+	char buf[32], *e, *op;
 	Aport *o;
 	Ctlr *c;
 	Drive *d;
 
-	c = u->dev->ctlr;
-	if(c == nil) {
-print("iarctl: nil u->dev->ctlr\n");
+	if((c = u->dev->ctlr) == nil)
 		return 0;
-	}
 	d = c->drive[u->subno];
 	o = d->port;
 
@@ -2033,47 +2206,34 @@
 		p = seprint(p, e, "model\t%s\n", d->model);
 		p = seprint(p, e, "serial\t%s\n", d->serial);
 		p = seprint(p, e, "firm\t%s\n", d->firmware);
-		if(d->smartrs == 0xff)
-			p = seprint(p, e, "smart\tenable error\n");
-		else if(d->smartrs == 0)
-			p = seprint(p, e, "smart\tdisabled\n");
-		else
-			p = seprint(p, e, "smart\t%s\n",
-				smarttab[d->portm.smart]);
+		if(d->wwn != 0)
+			p = seprint(p, e, "wwn\t%ullx\n", d->wwn);
 		p = seprint(p, e, "flag\t");
-		p = pflag(p, e, d->portm.feat);
+		p = pflag(p, e, &d->portm);
+		p = seprint(p, e, "udma\t%d\n", d->portm.udma);
 	}else
 		p = seprint(p, e, "no disk present [%s]\n", diskstates[d->state]);
 	serrstr(o->serror, buf, buf + sizeof buf - 1);
-	p = seprint(p, e, "reg\ttask %lux cmd %lux serr %lux %s ci %lux is %lux; "
-		"sig %lux sstatus %04lux\n", o->task, o->cmd, o->serror, buf,
+	p = seprint(p, e, "reg\ttask %lux cmd %lux serr %lux %s ci %lux is %lux "
+		"sig %lux sstatus %.3lux\n", o->task, o->cmd, o->serror, buf,
 		o->ci, o->isr, o->sig, o->sstatus);
-	if(d->unit == nil)
-		panic("iarctl: nil d->unit");
-	p = seprint(p, e, "geometry %llud %lud\n", d->sectors, d->unit->secsize);
+	p = seprint(p, e, "cmd\t");
+	p = capfmt(p, e, ctab, nelem(ctab), o->cmd);
+	p = seprint(p, e, "\n");
+	p = seprint(p, e, "mode\t%s %s\n", modes[d->mode], modes[maxmode(c)]);
+	p = seprint(p, e, "geometry %llud %lud\n", d->sectors, u->secsize);
 	return p - op;
 }
 
 static void
-runflushcache(Drive *d)
-{
-	long t0;
-
-	t0 = MACHP(0)->ticks;
-	if(flushcache(d) != 0)
-		error(Eio);
-	dprint("ahci: flush in %ld ms\n", MACHP(0)->ticks - t0);
-}
-
-static void
 forcemode(Drive *d, char *mode)
 {
 	int i;
 
-	for(i = 0; i < nelem(modename); i++)
-		if(strcmp(mode, modename[i]) == 0)
+	for(i = 0; i < nelem(modes); i++)
+		if(strcmp(mode, modes[i]) == 0)
 			break;
-	if(i == nelem(modename))
+	if(i == nelem(modes))
 		i = 0;
 	ilock(d);
 	d->mode = i;
@@ -2081,22 +2241,6 @@
 }
 
 static void
-runsmartable(Drive *d, int i)
-{
-	if(waserror()){
-		qunlock(&d->portm);
-		d->smartrs = 0;
-		nexterror();
-	}
-	if(lockready(d) == -1)
-		error(Eio);
-	d->smartrs = smart(&d->portc, i);
-	d->portm.smart = 0;
-	qunlock(&d->portm);
-	poperror();
-}
-
-static void
 forcestate(Drive *d, char *state)
 {
 	int i;
@@ -2108,10 +2252,31 @@
 		error(Ebadctl);
 	ilock(d);
 	d->state = i;
+//	statechange(d);
 	iunlock(d);
 }
 
+static int
+runsettxmode(Drive *d, char *s)
+{
+	int i;
+	Aportc *c;
+	Aportm *m;
 
+	c = &d->portc;
+	m = &d->portm;
+
+	i = 1;
+	if(lockready(d) == 0){
+		m->udma = atoi(s);
+		if(settxmode(c, m->udma) == 0)
+			i = 0;
+	}
+	qunlock(m);
+	return i;
+}
+
+
 static int
 iawctl(SDunit *u, Cmdbuf *cmd)
 {
@@ -2118,62 +2283,20 @@
 	char **f;
 	Ctlr *c;
 	Drive *d;
-	uint i;
 
 	c = u->dev->ctlr;
 	d = c->drive[u->subno];
 	f = cmd->f;
 
-	if(strcmp(f[0], "flushcache") == 0)
-		runflushcache(d);
-	else if(strcmp(f[0], "identify") ==  0){
-		i = strtoul(f[1]? f[1]: "0", 0, 0);
-		if(i > 0xff)
-			i = 0;
-		dprint("ahci: %04d %ux\n", i, d->info[i]);
-	}else if(strcmp(f[0], "mode") == 0)
+	if(strcmp(f[0], "mode") == 0)
 		forcemode(d, f[1]? f[1]: "satai");
-	else if(strcmp(f[0], "nop") == 0){
-		if((d->portm.feat & Dnop) == 0){
-			cmderror(cmd, "no drive support");
-			return -1;
-		}
-		if(waserror()){
-			qunlock(&d->portm);
-			nexterror();
-		}
-		if(lockready(d) == -1)
-			error(Eio);
-		nop(&d->portc);
-		qunlock(&d->portm);
-		poperror();
-	}else if(strcmp(f[0], "reset") == 0)
-		forcestate(d, "reset");
-	else if(strcmp(f[0], "smart") == 0){
-		if(d->smartrs == 0){
-			cmderror(cmd, "smart not enabled");
-			return -1;
-		}
-		if(waserror()){
-			qunlock(&d->portm);
-			d->smartrs = 0;
-			nexterror();
-		}
-		if(lockready(d) == -1)
-			error(Eio);
-		d->portm.smart = 2 + smartrs(&d->portc);
-		qunlock(&d->portm);
-		poperror();
-	}else if(strcmp(f[0], "smartdisable") == 0)
-		runsmartable(d, 1);
-	else if(strcmp(f[0], "smartenable") == 0)
-		runsmartable(d, 0);
 	else if(strcmp(f[0], "state") == 0)
 		forcestate(d, f[1]? f[1]: "null");
-	else{
+	else if(strcmp(f[0], "txmode") == 0){
+		if(runsettxmode(d, f[1]? f[1]: "0"))
+			cmderror(cmd, "bad txmode / stuck port");
+	}else
 		cmderror(cmd, Ebadctl);
-		return -1;
-	}
 	return 0;
 }
 
@@ -2202,42 +2325,63 @@
 	return p;
 }
 
-/* must emit exactly one line per controller (sd(3)) */
+static Htab htab[] = {
+	H64a,	"64a",
+	Hncq,	"ncq",
+	Hsntf,	"ntf",
+	Hmps,	"mps",
+	Hss,	"ss",
+	Halp,	"alp",
+	Hal,	"led",
+	Hclo,	"clo",
+	Ham,	"am",
+	Hpm,	"pm",
+	Hfbs,	"fbs",
+	Hpmb,	"pmb",
+	Hssc,	"slum",
+	Hpsc,	"pslum",
+	Hcccs,	"coal",
+	Hems,	"ems",
+	Hxs,	"xs",
+};
+
+static Htab htab2[] = {
+	Apts,	"apts",
+	Nvmp,	"nvmp",
+	Boh,	"boh",
+};
+
+static Htab emtab[] = {
+	Pm,	"pm",
+	Alhd,	"alhd",
+	Xonly,	"xonly",
+	Smb,	"smb",
+	Esgpio,	"esgpio",
+	Eses2,	"eses2",
+	Esafte,	"esafte",
+	Elmt,	"elmt",
+};
+
 static char*
-iartopctl(SDev *sdev, char *p, char *e)
+iartopctl(SDev *s, char *p, char *e)
 {
-	ulong cap;
 	char pr[25];
-	Ahba *hba;
-	Ctlr *ctlr;
+	ulong cap;
+	Ahba *h;
+	Ctlr *c;
 
-#define has(x, str) if(cap & (x)) p = seprint(p, e, "%s ", (str))
-
-	ctlr = sdev->ctlr;
-	hba = ctlr->hba;
-	p = seprint(p, e, "sd%c ahci port %#p: ", sdev->idno, hba);
-	cap = hba->cap;
-	has(Hs64a, "64a");
-	has(Hsalp, "alp");
-	has(Hsam, "am");
-	has(Hsclo, "clo");
-	has(Hcccs, "coal");
-	has(Hems, "ems");
-	has(Hsal, "led");
-	has(Hsmps, "mps");
-	has(Hsncq, "ncq");
-	has(Hssntf, "ntf");
-	has(Hspm, "pm");
-	has(Hpsc, "pslum");
-	has(Hssc, "slum");
-	has(Hsss, "ss");
-	has(Hsxs, "sxs");
-	portr(pr, pr + sizeof pr, hba->pi);
+	c = s->ctlr;
+	h = c->hba;
+	cap = h->cap;
+	p = seprint(p, e, "sd%c ahci %s port %#p: ", s->idno, Tname(c), h);
+	p = capfmt(p, e, htab, nelem(htab), cap);
+	p = capfmt(p, e, htab2, nelem(htab2), h->cap2);
+	p = capfmt(p, e, emtab, nelem(emtab), h->emctl);
+	portr(pr, pr + sizeof pr, h->pi);
 	return seprint(p, e,
-		"iss %ld ncs %ld np %ld; ghc %lux isr %lux pi %lux %s ver %lux\n",
+		"iss %ld ncs %ld np %ld ghc %lux isr %lux pi %lux %s ver %lux\n",
 		(cap>>20) & 0xf, (cap>>8) & 0x1f, 1 + (cap & 0x1f),
-		hba->ghc, hba->isr, hba->pi, pr, hba->ver);
-#undef has
+		h->ghc, h->isr, h->pi, pr, h->ver);
 }
 
 static int
@@ -2249,8 +2393,6 @@
 	f = cmd->f;
 	v = 0;
 
-	if (f[0] == nil)
-		return 0;
 	if(strcmp(f[0], "debug") == 0)
 		v = &debug;
 	else if(strcmp(f[0], "idprint") == 0)
@@ -2257,6 +2399,8 @@
 		v = &prid;
 	else if(strcmp(f[0], "aprint") == 0)
 		v = &datapi;
+	else if(strcmp(f[0], "ledprint") == 0)
+		v = &dled;
 	else
 		cmderror(cmd, Ebadctl);
 
@@ -2277,7 +2421,7 @@
 }
 
 SDifc sdiahciifc = {
-	"iahci",
+	"ahci",
 
 	iapnp,
 	nil,		/* legacy */
@@ -2290,9 +2434,10 @@
 	iarctl,
 	iawctl,
 
-	scsibio,
+	ahcibio,
 	nil,		/* probe */
 	nil,		/* clear */
 	iartopctl,
 	iawtopctl,
+	iaataio,
 };
--- /dev/null
+++ b/sys/src/9/pc/sdide.c
@@ -1,0 +1,2524 @@
+#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 "../port/sd.h"
+#include <fis.h>
+
+#define	HOWMANY(x, y)	(((x)+((y)-1))/(y))
+#define ROUNDUP(x, y)	(HOWMANY((x), (y))*(y))
+#define uprint(...)	snprint(up->genbuf, sizeof up->genbuf, __VA_ARGS__);
+#pragma	varargck	argpos	atadebug		3
+
+extern SDifc sdideifc;
+
+enum {
+	DbgCONFIG	= 0x0001,	/* detected drive config info */
+	DbgIDENTIFY	= 0x0002,	/* detected drive identify info */
+	DbgSTATE	= 0x0004,	/* dump state on panic */
+	DbgPROBE	= 0x0008,	/* trace device probing */
+	DbgDEBUG	= 0x0080,	/* the current problem... */
+	DbgINL		= 0x0100,	/* That Inil20+ message we hate */
+	Dbg48BIT	= 0x0200,	/* 48-bit LBA */
+	DbgBsy		= 0x0400,	/* interrupt but Bsy (shared IRQ) */
+	DbgAtazz	= 0x0800,	/* debug raw ata io */
+};
+#define DEBUG		(DbgDEBUG|DbgSTATE)
+
+enum {					/* I/O ports */
+	Data		= 0,
+	Error		= 1,		/* (read) */
+	Features	= 1,		/* (write) */
+	Count		= 2,		/* sector count<7-0>, sector count<15-8> */
+	Ir		= 2,		/* interrupt reason (PACKET) */
+	Sector		= 3,		/* sector number */
+	Lbalo		= 3,		/* LBA<7-0>, LBA<31-24> */
+	Cyllo		= 4,		/* cylinder low */
+	Bytelo		= 4,		/* byte count low (PACKET) */
+	Lbamid		= 4,		/* LBA<15-8>, LBA<39-32> */
+	Cylhi		= 5,		/* cylinder high */
+	Bytehi		= 5,		/* byte count hi (PACKET) */
+	Lbahi		= 5,		/* LBA<23-16>, LBA<47-40> */
+	Dh		= 6,		/* Device/Head, LBA<27-24> */
+	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 */
+	Devs		= Dev0 | Dev1,
+	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 */
+	Crs		= 0x20,		/* Read Sectors */
+	Crs48		= 0x24,		/* Read Sectors Ext */
+	Crd48		= 0x25,		/* Read w/ DMA Ext */
+	Crsm48		= 0x29,		/* Read Multiple Ext */
+	Cws		= 0x30,		/* Write Sectors */
+	Cws48		= 0x34,		/* Write Sectors Ext */
+	Cwd48		= 0x35,		/* Write w/ DMA Ext */
+	Cwsm48		= 0x39,		/* Write Multiple Ext */
+	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 */
+	Crd		= 0xC8,		/* Read DMA */
+	Cwd		= 0xCA,		/* Write DMA */
+	Cid		= 0xEC,		/* Identify Device */
+};
+
+enum {					/* Device Control */
+	Nien		= 0x02,		/* (not) Interrupt Enable */
+	Srst		= 0x04,		/* Software Reset */
+	Hob		= 0x80,		/* High Order Bit [sic] */
+};
+
+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,	/* End of Transfer */
+};
+
+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 */
+	Ilba		= 60,		/* 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 */
+	Icsfs		= 82,		/* command set/feature supported */
+	Icsfe		= 85,		/* command set/feature enabled */
+	Iudma		= 88,		/* ultra DMA mode */
+	Ierase		= 89,		/* time for security erase */
+	Ieerase		= 90,		/* time for enhanced security erase */
+	Ipower		= 91,		/* current advanced power management */
+	Ilba48		= 100,		/* 48-bit LBA size (64 bits in 100-103) */
+	Irmsn		= 127,		/* removable status notification */
+	Isecstat	= 128,		/* security status */
+	Icfapwr		= 160,		/* CFA power mode */
+	Imediaserial	= 176,		/* current media serial number */
+	Icksum		= 255,		/* checksum */
+};
+
+enum {					/* bit masks for config identify info */
+	Mpktsz		= 0x0003,	/* packet command size */
+	Mincomplete	= 0x0004,	/* incomplete information */
+	Mdrq		= 0x0060,	/* DRQ type */
+	Mrmdev		= 0x0080,	/* device is removable */
+	Mtype		= 0x1F00,	/* device type */
+	Mproto		= 0x8000,	/* command protocol */
+};
+
+enum {					/* bit masks for capabilities identify info */
+	Mdma		= 0x0100,	/* DMA supported */
+	Mlba		= 0x0200,	/* LBA supported */
+	Mnoiordy	= 0x0400,	/* IORDY may be disabled */
+	Miordy		= 0x0800,	/* IORDY supported */
+	Msoftrst	= 0x1000,	/* needs soft reset when Bsy */
+	Mqueueing	= 0x4000,	/* queueing overlap supported */
+	Midma		= 0x8000,	/* interleaved DMA supported */
+};
+
+enum {					/* bit masks for supported/enabled features */
+	Msmart		= 0x0001,
+	Msecurity	= 0x0002,
+	Mrmmedia	= 0x0004,
+	Mpwrmgmt	= 0x0008,
+	Mpkt		= 0x0010,
+	Mwcache		= 0x0020,
+	Mlookahead	= 0x0040,
+	Mrelirq		= 0x0080,
+	Msvcirq		= 0x0100,
+	Mreset		= 0x0200,
+	Mprotected	= 0x0400,
+	Mwbuf		= 0x1000,
+	Mrbuf		= 0x2000,
+	Mnop		= 0x4000,
+	Mmicrocode	= 0x0001,
+	Mqueued		= 0x0002,
+	Mcfa		= 0x0004,
+	Mapm		= 0x0008,
+	Mnotify		= 0x0010,
+	Mspinup		= 0x0040,
+	Mmaxsec		= 0x0100,
+	Mautoacoustic	= 0x0200,
+	Maddr48		= 0x0400,
+	Mdevconfov	= 0x0800,
+	Mflush		= 0x1000,
+	Mflush48	= 0x2000,
+	Msmarterror	= 0x0001,
+	Msmartselftest	= 0x0002,
+	Mmserial	= 0x0004,
+	Mmpassthru	= 0x0008,
+	Mlogging	= 0x0020,
+};
+
+typedef struct Ctlr Ctlr;
+typedef struct Drive Drive;
+
+typedef struct Prd {			/* Physical Region Descriptor */
+	ulong	pa;			/* Physical Base Address */
+	int	count;
+} Prd;
+
+enum {
+	BMspan		= 64*1024,	/* must be power of 2 <= 64*1024 */
+
+	Nprd		= SDmaxio/BMspan+2,
+};
+
+typedef struct Ctlr {
+	int	cmdport;
+	int	ctlport;
+	int	irq;
+	int	tbdf;
+	int	bmiba;			/* bus master interface base address */
+	int	maxio;			/* sector count transfer maximum */
+	int	span;			/* don't span this boundary with dma */
+
+	Pcidev*	pcidev;
+	void	(*ienable)(Ctlr*);
+	void	(*idisable)(Ctlr*);
+	SDev*	sdev;
+
+	Drive*	drive[2];
+
+	Prd*	prdt;			/* physical region descriptor table */
+	void	(*irqack)(Ctlr*);
+
+	QLock;				/* current command */
+	Drive*	curdrive;
+	int	command;		/* last command issued (debugging) */
+	Rendez;
+	int	done;
+	uint	nrq;
+	uint	nildrive;
+	uint	bsy;
+
+	Lock;				/* register access */
+} Ctlr;
+
+typedef struct Drive {
+	Ctlr*	ctlr;
+	SDunit	*unit;
+
+	int	dev;
+	ushort	info[256];
+	Sfis;
+
+	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 */
+
+	uvlong	sectors;
+	uint	secsize;
+	char	serial[20+1];
+	char	firmware[8+1];
+	char	model[40+1];
+
+	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;
+	int	flags;			/* internal flags */
+	uint	missirq;
+	uint	spurloop;
+	uint	irq;
+	uint	bsy;
+} Drive;
+
+enum {					/* internal flags */
+	Lba48always	= 0x2,		/* ... */
+	Online		= 0x4,		/* drive onlined */
+};
+
+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, SDreq *r, uvlong lba, int count)
+{
+	Prd *prd;
+	Pcidev *p;
+	Ctlr *ctlr;
+	int i, bmiba, ccnt;
+	uvlong clba;
+
+	if(!(DEBUG & DbgSTATE))
+		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(r->clen == -16)
+		clba = fisrw(nil, r->cmd, &ccnt);
+	else 
+		sdfakescsirw(r, &clba, &ccnt, 0);
+	print("lba %llud -> %llud, count %d -> %d (%d)\n",
+		clba, lba, ccnt, 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
+	|| drive->command == (Pdma|Pin) || drive->command == (Pdma|Pout)){
+		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 void
+atadebug(int cmdport, int ctlport, char* fmt, ...)
+{
+	char *p, *e, buf[PRINTSIZE];
+	int i;
+	va_list arg;
+
+	if(!(DEBUG & DbgPROBE))
+		return;
+
+	p = buf;
+	e = buf + sizeof buf;
+	va_start(arg, fmt);
+	p = vseprint(p, e, fmt, arg);
+	va_end(arg);
+
+	if(cmdport){
+		if(p > buf && p[-1] == '\n')
+			p--;
+		p = seprint(p, e, " ataregs 0x%uX:", cmdport);
+		for(i = Features; i < Command; i++)
+			p = seprint(p, e, " 0x%2.2uX", inb(cmdport+i));
+		if(ctlport)
+			p = seprint(p, e, " 0x%2.2uX", inb(ctlport+As));
+		p = seprint(p, e, "\n");
+	}
+	putstrn(buf, p - buf);
+}
+
+static int
+ataready(int cmdport, int ctlport, int dev, int reset, int ready, int m)
+{
+	int as, m0;
+
+	atadebug(cmdport, ctlport, "ataready: dev %ux:%ux reset %ux ready %ux",
+		cmdport, dev, reset, ready);
+	m0 = m;
+	do{
+		/*
+		 * 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){
+			/* nothing to do */
+		}
+		else if(dev){
+			outb(cmdport+Dh, dev);
+			dev = 0;
+		}
+		else if(ready == 0 || (as & ready)){
+			atadebug(0, 0, "ataready: %d:%d %#.2ux\n", m, m0, as);
+			return as;
+		}
+		microdelay(1);
+	}while(m-- > 0);
+	atadebug(0, 0, "ataready: timeout %d %#.2ux\n", m0, as);
+	return -1;
+}
+
+static int
+atadone(void* arg)
+{
+	return ((Ctlr*)arg)->done;
+}
+
+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(SDunit *unit, Drive* drive)
+{
+	char buf[32], *s;
+	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] & 0x7F7F;
+		drive->dma = (dma>>8) & dma;
+		if(drive->dma)
+			drive->dma |= 'U'<<16;
+	}
+	if(unit != nil){
+		snprint(buf, sizeof buf, "*%sdma", unit->name);
+		if((s = getconf(buf)) && strcmp(s, "on") == 0){
+			print("set %s dma\n", unit->name);
+			drive->dmactl = drive->dma;
+		}
+	}
+	return dma;
+}
+
+static int
+ataidentify(Ctlr*, 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;
+	}
+	dev &= ~Lba;
+	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, 400*1000);
+	if(as < 0)
+		return -1;
+	if(as & Err)
+		return as;
+
+	memset(info, 0, 512);
+	inss(cmdport+Data, info, 256);
+	ataready(cmdport, ctlport, dev, Bsy|Drq, Drdy, 3*1000);
+	inb(cmdport+Status);
+
+	return 0;
+}
+
+static Drive*
+atadrive(SDunit *unit, Drive *drive, int cmdport, int ctlport, int dev)
+{
+	int as, pkt;
+	uchar buf[512], oserial[21];
+	uvlong osectors;
+	Ctlr *ctlr;
+
+	if(DEBUG & DbgIDENTIFY)
+		print("identify: port %ux dev %.2ux\n", cmdport, dev & ~Lba);
+	atadebug(0, 0, "identify: port 0x%uX dev 0x%2.2uX\n", cmdport, dev);
+	pkt = 1;
+	if(drive != nil){
+		osectors = drive->sectors;
+		memmove(oserial, drive->serial, sizeof drive->serial);
+		ctlr = drive->ctlr;
+	}else{
+		osectors = 0;
+		memset(oserial, 0, sizeof drive->serial);
+		ctlr = nil;
+	}
+retry:
+	as = ataidentify(ctlr, cmdport, ctlport, dev, pkt, buf);
+	if(as < 0)
+		return nil;
+	if(as & Err){
+		if(pkt == 0)
+			return nil;
+		pkt = 0;
+		goto retry;
+	}
+
+	if(drive == 0){
+		if((drive = malloc(sizeof(Drive))) == nil)
+			return nil;
+		drive->serial[0] = ' ';
+		drive->dev = dev;
+	}
+
+	memmove(drive->info, buf, sizeof(drive->info));
+
+	setfissig(drive, pkt? 0xeb140000: 0x0101);
+	drive->sectors = idfeat(drive, drive->info);
+	drive->secsize = idss(drive, drive->info);
+
+	idmove(drive->serial, drive->info+10, 20);
+	idmove(drive->firmware, drive->info+23, 8);
+	idmove(drive->model, drive->info+27, 40);
+	if(unit != nil){
+		memset(unit->inquiry, 0, sizeof unit->inquiry);
+		unit->inquiry[2] = 2;
+		unit->inquiry[3] = 2;
+		unit->inquiry[4] = sizeof unit->inquiry - 4;
+		memmove(unit->inquiry+8, drive->model, 40);
+	}
+
+	if(pkt){
+		drive->pkt = 12;
+		if(drive->feat & Datapi16)
+			drive->pkt = 16;
+	}else{
+		if(drive->feat & Dlba)
+			drive->dev |= Lba;
+		atarwmmode(drive, cmdport, ctlport, dev);
+	}
+	atadmamode(unit, drive);	
+
+	if(osectors != 0 && memcmp(oserial, drive->serial, sizeof oserial) != 0)
+		if(unit)
+			unit->sectors = 0;
+	drive->unit = unit;
+	if(DEBUG & DbgCONFIG){
+		print("dev %2.2uX port %uX config %4.4uX capabilities %4.4uX",
+			dev, cmdport, drive->info[Iconfig], drive->info[Icapabilities]);
+		print(" mwdma %4.4uX", drive->info[Imwdma]);
+		if(drive->info[Ivalid] & 0x04)
+			print(" udma %4.4uX", drive->info[Iudma]);
+		print(" dma %8.8uX rwm %ud", drive->dma, drive->rwm);
+		if(drive->feat&Dllba)
+			print("\tLLBA sectors %llud", drive->sectors);
+		print("\n");
+	}
+
+	return drive;
+}
+
+static void
+atasrst(int ctlport)
+{
+	int dc0;
+
+	/*
+	 * 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.
+	 */
+	dc0 = inb(ctlport+Dc);
+	microdelay(5);
+	outb(ctlport+Dc, Srst|dc0);
+	microdelay(5);
+	outb(ctlport+Dc, dc0);
+	microdelay(2*1000);
+}
+
+static int
+seldev(int dev, int map)
+{
+	if((dev & Devs) == Dev0 && map&1)
+		return dev;
+	if((dev & Devs) == Dev1 && map&2)
+		return dev;
+	return -1;
+}
+
+static SDev*
+ataprobe(int cmdport, int ctlport, int irq, int map)
+{
+	Ctlr* ctlr;
+	SDev *sdev;
+	Drive *drive;
+	int dev, error, rhi, rlo;
+	static int nonlegacy = 'C';
+
+	if(ioalloc(cmdport, 8, 0, "atacmd") < 0) {
+		print("ataprobe: Cannot allocate %X\n", cmdport);
+		return nil;
+	}
+	if(ioalloc(ctlport+As, 1, 0, "atactl") < 0){
+		print("ataprobe: Cannot allocate %X\n", ctlport + As);
+		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.
+	 */
+	if((dev = seldev(Dev0, map)) == -1)
+	if((dev = seldev(Dev1, map)) == -1)
+		goto release;
+	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 || (dev = seldev(Dev1, map)) == -1){
+release:
+				outb(cmdport+Dc, Nien);
+				inb(cmdport+Status);
+				/* further measures to prevent irqs? */
+				iofree(cmdport);
+				iofree(ctlport+As);
+				return nil;
+			}
+			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;
+		if((dev = seldev(Dev1, map)) == -1)
+			goto release;
+		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(0, 0, 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(0, 0, 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 */
+	
+	switch(cmdport){
+	default:
+		sdev->idno = nonlegacy;
+		break;
+	case 0x1F0:
+		sdev->idno = 'C';
+		nonlegacy = 'E';
+		break;
+	case 0x170:
+		sdev->idno = 'D';
+		nonlegacy = 'E';
+		break;
+	}
+	sdev->ifc = &sdideifc;
+	sdev->ctlr = ctlr;
+	sdev->nunit = 2;
+	ctlr->sdev = sdev;
+
+	return sdev;
+}
+
+static void
+ataclear(SDev *sdev)
+{
+	Ctlr* ctlr;
+
+	ctlr = sdev->ctlr;
+	iofree(ctlr->cmdport);
+	iofree(ctlr->ctlport + As);
+
+	if (ctlr->drive[0])
+		free(ctlr->drive[0]);
+	if (ctlr->drive[1])
+		free(ctlr->drive[1]);
+	if (sdev->name)
+		free(sdev->name);
+	if (sdev->unitflg)
+		free(sdev->unitflg);
+	if (sdev->unit)
+		free(sdev->unit);
+	free(ctlr);
+	free(sdev);
+}
+
+static char *
+atastat(SDev *sdev, char *p, char *e)
+{
+	Ctlr *ctlr;
+
+	ctlr = sdev->ctlr;
+//	return seprint(p, e, "%s ata port %X ctl %X irq %d %T\n", 
+//		    sdev->name, ctlr->cmdport, ctlr->ctlport, ctlr->irq, ctlr->tbdf);
+	return seprint(p, e, "%s ata port %X ctl %X irq %d\n", 
+		    sdev->name, ctlr->cmdport, ctlr->ctlport, ctlr->irq);
+}
+
+static SDev*
+ataprobew(DevConf *cf)
+{
+	char *p;
+	ISAConf isa;
+	
+	if (cf->nports != 2)
+		error(Ebadarg);
+
+	memset(&isa, 0, sizeof isa);
+	isa.port = cf->ports[0].port;
+	isa.irq = cf->intnum;
+	if((p=strchr(cf->type, '/')) == nil || pcmspecial(p+1, &isa) < 0)
+		error("cannot find controller");
+
+	return ataprobe(cf->ports[0].port, cf->ports[1].port, cf->intnum, 3);
+}
+
+static void atainterrupt(Ureg*, void*);
+
+static int
+iowait(Drive *drive, int ms, int interrupt)
+{
+	int msec, step;
+	Ctlr *ctlr;
+
+	step = 1000;
+	if(drive->missirq > 10)
+		step = 50;
+	ctlr = drive->ctlr;
+	for(msec = 0; msec < ms; msec += step){
+		while(waserror())
+			if(interrupt)
+				return -1;
+		tsleep(ctlr, atadone, ctlr, step);
+		poperror();
+		if(ctlr->done)
+			break;
+		atainterrupt(nil, ctlr);
+		if(ctlr->done){
+			if(drive->missirq++ < 3)
+				print("ide: caught missed irq\n");
+			break;
+		}else
+			drive->spurloop++;
+	}
+	return ctlr->done;
+}
+
+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 use it otherwise
+	 * must try a software reset.
+	 */
+	if(dolock)
+		ilock(drive->ctlr);
+	if(drive->feat & Dnop)
+		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, i, span;
+
+	ctlr = drive->ctlr;
+	pa = PCIWADDR(drive->data);
+	if(pa & 0x03)
+		return -1;
+
+	/*
+	 * Sometimes drives identify themselves as being DMA capable
+	 * although they are not on a busmastering controller.
+	 */
+	prd = ctlr->prdt;
+	if(prd == nil){
+		drive->dmactl = 0;
+		print("disabling dma: not on a busmastering controller\n");
+		return -1;
+	}
+
+	for(i = 0; len && i < Nprd; i++){
+		prd->pa = pa;
+		span = ROUNDUP(pa, ctlr->span);
+		if(span == pa)
+			span += ctlr->span;
+		count = span - pa;
+		if(count >= len){
+			prd->count = PrdEOT|len;
+			break;
+		}
+		prd->count = count;
+		len -= count;
+		pa += count;
+		prd++;
+	}
+	if(i == Nprd)
+		(prd-1)->count |= PrdEOT;
+
+	bmiba = ctlr->bmiba;
+	outl(bmiba+Bmidtpx, PCIWADDR(ctlr->prdt));
+	if(drive->write)
+		outb(bmiba+Bmicx, 0);
+	else
+		outb(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 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
+atapktio0(Drive *drive, SDreq *r)
+{
+	uchar *cmd;
+	int as, cmdport, ctlport, len, rv, timeo;
+	Ctlr *ctlr;
+
+	rv = SDok;
+	cmd = r->cmd;
+	drive->command = Cpkt;
+	memmove(drive->pktcmd, cmd, r->clen);
+	memset(drive->pktcmd+r->clen, 0, drive->pkt-r->clen);
+	drive->limit = drive->data+drive->dlen;
+
+	ctlr = drive->ctlr;
+	cmdport = ctlr->cmdport;
+	ctlport = ctlr->ctlport;
+
+	as = ataready(cmdport, ctlport, drive->dev, Bsy|Drq, Drdy, 107*1000);
+	/* used to test as&Chk as failure too, but some CD readers use that for media change */
+	if(as < 0)
+		return SDnostatus;
+
+	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] & Mdrq) != 0x0020){
+		microdelay(1);
+		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Chk, 4*1000);
+		if(as < 0 || (as & (Bsy|Chk))){
+			drive->status = as<0 ? 0 : as;
+			ctlr->curdrive = nil;
+			ctlr->done = 1;
+			rv = SDtimeout;
+		}else
+			atapktinterrupt(drive);
+	}
+	iunlock(ctlr);
+
+	while(waserror())
+		;
+	if(!drive->pktdma)
+		sleep(ctlr, atadone, ctlr);
+	else for(timeo = 0; !ctlr->done; timeo++){
+		tsleep(ctlr, atadone, ctlr, 1000);
+		if(ctlr->done)
+			break;
+		ilock(ctlr);
+		atadmainterrupt(drive, 0);
+		if(!drive->error && timeo > 20){
+			ataabort(drive, 0);
+			atadmastop(ctlr);
+			drive->dmactl = 0;
+			drive->error |= Abrt;
+		}
+		if(drive->error){
+			drive->status |= Chk;
+			ctlr->curdrive = nil;
+		}
+		iunlock(ctlr);
+	}
+	poperror();
+
+	if(drive->status & Chk)
+		rv = SDcheck;
+	return rv;
+}
+
+static int
+atapktio(Drive* drive, SDreq *r)
+{
+	int n;
+	Ctlr *ctlr;
+
+	ctlr = drive->ctlr;
+	qlock(ctlr);
+	n = atapktio0(drive, r);
+	qunlock(ctlr);
+	return n;
+}
+
+static uchar cmd48[256] = {
+	[Crs]	Crs48,
+	[Crd]	Crd48,
+	[Crsm]	Crsm48,
+	[Cws]	Cws48,
+	[Cwd]	Cwd48,
+	[Cwsm]	Cwsm48,
+};
+
+enum{
+	Last28	= (1<<28) - 1 - 1,
+};
+
+static int
+atageniostart(Drive* drive, uvlong lba)
+{
+	Ctlr *ctlr;
+	uchar cmd;
+	int as, c, cmdport, ctlport, h, len, s, use48;
+
+	use48 = 0;
+	if((drive->flags&Lba48always) || lba > Last28 || drive->count > 256){
+		if((drive->feat & Dllba) == 0)
+			return -1;
+		use48 = 1;
+		c = h = s = 0;
+	}else if(drive->dev & Lba){
+		c = (lba>>8) & 0xFFFF;
+		h = (lba>>24) & 0x0F;
+		s = lba & 0xFF;
+	}else{
+		if (drive->s == 0 || drive->h == 0){
+			print("sdide: chs address botch");
+			return -1;
+		}
+		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, Drdy, 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;
+	cmd = drive->command;
+	if(use48){
+		outb(cmdport+Count, drive->count>>8);
+		outb(cmdport+Count, drive->count);
+		outb(cmdport+Lbalo, lba>>24);
+		outb(cmdport+Lbalo, lba);
+		outb(cmdport+Lbamid, lba>>32);
+		outb(cmdport+Lbamid, lba>>8);
+		outb(cmdport+Lbahi, lba>>40);
+		outb(cmdport+Lbahi, lba>>16);
+		outb(cmdport+Dh, drive->dev|Lba);
+		cmd = cmd48[cmd];
+
+		if(DEBUG & Dbg48BIT)
+			print("using 48-bit commands\n");
+	}else{
+		outb(cmdport+Count, drive->count);
+		outb(cmdport+Sector, s);
+		outb(cmdport+Cyllo, c);
+		outb(cmdport+Cylhi, c>>8);
+		outb(cmdport+Dh, drive->dev|h);
+	}
+	ctlr->done = 0;
+	ctlr->curdrive = drive;
+	ctlr->command = drive->command;	/* debugging */
+	outb(cmdport+Command, cmd);
+
+	switch(drive->command){
+	case Cws:
+	case Cwsm:
+		microdelay(1);
+		/* 10*1000 for flash ide drives - maybe detect them? */
+		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 10*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, SDreq *r, uvlong lba, int count)
+{
+	char *s;
+	int rv, count0, rw;
+	uvlong lba0;
+
+	if(drive->dmactl){
+		drive->dmactl = 0;
+		s = "disabling dma";
+		rv = SDretry;
+	}else if(drive->rwmctl){
+		drive->rwmctl = 0;
+		s = "disabling rwm";
+		rv = SDretry;
+	}else{
+		s = "nondma";
+		rv = sdsetsense(r, SDcheck, 4, 8, drive->error);
+	}
+	sdfakescsirw(r, &lba0, &count0, &rw);
+	print("atagenioretry: %s %c:%llud:%d @%llud:%d\n",
+		s, "rw"[rw], lba0, count0, lba, count);
+	return rv;
+}
+
+static int
+atagenio(Drive* drive, SDreq *r)
+{
+	Ctlr *ctlr;
+	uvlong lba;
+	int i, rw, count, maxio;
+
+	if((i = sdfakescsi(r)) != SDnostatus)
+		return i;
+	if((i = sdfakescsirw(r, &lba, &count, &rw)) != SDnostatus)
+		return i;
+	ctlr = drive->ctlr;
+	if(drive->data == nil)
+		return SDok;
+	if(drive->dlen < count*drive->secsize)
+		count = drive->dlen/drive->secsize;
+	qlock(ctlr);
+	if(ctlr->maxio)
+		maxio = ctlr->maxio;
+	else if(drive->feat & Dllba)
+		maxio = 65536;
+	else
+		maxio = 256;
+	while(count){
+		if(count > maxio)
+			drive->count = maxio;
+		else
+			drive->count = count;
+		if(atageniostart(drive, lba)){
+			ilock(ctlr);
+			atanop(drive, 0);
+			iunlock(ctlr);
+			qunlock(ctlr);
+			return atagenioretry(drive, r, lba, count);
+		}
+		iowait(drive, 60*1000, 0);
+		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, r, lba, count);
+			ataabort(drive, 1);
+			qunlock(ctlr);
+			return atagenioretry(drive, r, lba, count);
+		}
+
+		if(drive->status & Err){
+			qunlock(ctlr);
+print("atagenio: %llud:%d\n", lba, drive->count);
+			return sdsetsense(r, SDcheck, 4, 8, drive->error);
+		}
+		count -= drive->count;
+		lba += drive->count;
+	}
+	qunlock(ctlr);
+
+	return SDok;
+}
+
+static int
+atario(SDreq* r)
+{
+	uchar *p;
+	int status;
+	Ctlr *ctlr;
+	Drive *drive;
+	SDunit *unit;
+
+	unit = r->unit;
+	if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil){
+		r->status = SDtimeout;
+		return SDtimeout;
+	}
+	drive = ctlr->drive[unit->subno];
+	qlock(drive);
+	for(;;){
+		drive->write = r->write;
+		drive->data = r->data;
+		drive->dlen = r->dlen;
+		drive->status = 0;
+		drive->error = 0;
+		if(drive->pkt)
+			status = atapktio(drive, r);
+		else
+			status = atagenio(drive, r);
+		if(status != SDretry)
+			break;
+		if(DbgDEBUG)
+			print("%s: retry: dma %8.8uX rwm %4.4uX\n",
+				unit->name, drive->dmactl, drive->rwmctl);
+	}
+	if(status == SDok && r->rlen == 0 && (r->flags & SDvalidsense) == 0){
+		sdsetsense(r, SDok, 0, 0, 0);
+		if(drive->data){
+			p = r->data;
+			r->rlen = drive->data - p;
+		}
+		else
+			r->rlen = 0;
+	}
+	qunlock(drive);
+	return status;
+}
+
+/**/
+static int
+isdmacmd(Drive *d, SDreq *r)
+{
+	switch(r->ataproto & Pprotom){
+	default:
+		return 0;
+	case Pdmq:
+		error("no queued support");
+	case Pdma:
+		if(!(d->dmactl || d->rwmctl))
+			error("dma in non dma mode\n");
+		return 1;
+	}
+}
+
+static int
+atagenatastart(Drive* d, SDreq *r)
+{
+	uchar u;
+	int as, cmdport, ctlport, len, pr, isdma;
+	Ctlr *ctlr;
+
+	isdma = isdmacmd(d, r);
+	ctlr = d->ctlr;
+	cmdport = ctlr->cmdport;
+	ctlport = ctlr->ctlport;
+	if(ataready(cmdport, ctlport, d->dev, Bsy|Drq, d->pkt? 0: Drdy, 101*1000) < 0)
+		return -1;
+
+	ilock(ctlr);
+	if(isdma && atadmasetup(d, d->block)){
+		iunlock(ctlr);
+		return -1;
+	
+	}
+	if(d->feat & Dllba && (r->ataproto & P28) == 0){
+		outb(cmdport+Features, r->cmd[Ffeat8]);
+		outb(cmdport+Features, r->cmd[Ffeat]);
+		outb(cmdport+Count, r->cmd[Fsc8]);
+		outb(cmdport+Count, r->cmd[Fsc]);
+		outb(cmdport+Lbalo, r->cmd[Flba24]);
+		outb(cmdport+Lbalo, r->cmd[Flba0]);
+		outb(cmdport+Lbamid, r->cmd[Flba32]);
+		outb(cmdport+Lbamid, r->cmd[Flba8]);
+		outb(cmdport+Lbahi, r->cmd[Flba40]);
+		outb(cmdport+Lbahi, r->cmd[Flba16]);
+		u = r->cmd[Fdev] & ~0xb0;
+		outb(cmdport+Dh, d->dev|u);
+	}else{
+		outb(cmdport+Features, r->cmd[Ffeat]);
+		outb(cmdport+Count, r->cmd[Fsc]);
+		outb(cmdport+Lbalo, r->cmd[Flba0]);
+		outb(cmdport+Lbamid, r->cmd[Flba8]);
+		outb(cmdport+Lbahi, r->cmd[Flba16]);
+		u = r->cmd[Fdev] & ~0xb0;
+		outb(cmdport+Dh, d->dev|u);
+	}
+	ctlr->done = 0;
+	ctlr->curdrive = d;
+	d->command = r->ataproto & (Pprotom|Pdatam);
+	ctlr->command = r->cmd[Fcmd];
+	outb(cmdport+Command, r->cmd[Fcmd]);
+
+	pr = r->ataproto & Pprotom;
+	if(pr == Pnd || pr == Preset)
+		USED(d);
+	else if(!isdma){
+		microdelay(1);
+		/* 10*1000 for flash ide drives - maybe detect them? */
+		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 10*1000);
+		if(as < 0 || (as & Err)){
+			iunlock(ctlr);
+			return -1;
+		}
+		len = d->block;
+		if(r->write && len > 0)
+			outss(cmdport+Data, d->data, len/2);
+	}else
+		atadmastart(ctlr, d->write);
+	iunlock(ctlr);
+	return 0;
+}
+
+static void
+mkrfis(Drive *d, SDreq *r)
+{
+	uchar *u;
+	int cmdport;
+	Ctlr *ctlr;
+
+	ctlr = d->ctlr;
+	cmdport = ctlr->cmdport;
+	u = r->cmd;
+
+	ilock(ctlr);
+	u[Ftype] = 0x34;
+	u[Fioport] = 0;
+	if((d->feat & Dllba) && (r->ataproto & P28) == 0){
+		u[Frerror] = inb(cmdport+Error);
+		u[Fsc8] = inb(cmdport+Count);
+		u[Fsc] = inb(cmdport+Count);
+		u[Flba24] = inb(cmdport+Lbalo);
+		u[Flba0] = inb(cmdport+Lbalo);
+		u[Flba32] = inb(cmdport+Lbamid);
+		u[Flba8] = inb(cmdport+Lbamid);
+		u[Flba40] = inb(cmdport+Lbahi);
+		u[Flba16] = inb(cmdport+Lbahi);
+		u[Fdev] = inb(cmdport+Dh);
+		u[Fstatus] = inb(cmdport+Status);
+	}else{
+		u[Frerror] = inb(cmdport+Error);
+		u[Fsc] = inb(cmdport+Count);
+		u[Flba0] = inb(cmdport+Lbalo);
+		u[Flba8] = inb(cmdport+Lbamid);
+		u[Flba16] = inb(cmdport+Lbahi);
+		u[Fdev] = inb(cmdport+Dh);
+		u[Fstatus] = inb(cmdport+Status);
+	}
+	iunlock(ctlr);
+}
+
+static int
+atarstdone(Drive *d)
+{
+	int as;
+	Ctlr *c;
+
+	c = d->ctlr;
+	as = ataready(c->cmdport, c->ctlport, 0, Bsy|Drq, 0, 5*1000);
+	c->done = as >= 0;
+	return c->done;
+}
+
+static uint
+cmdss(Drive *d, SDreq *r)
+{
+	switch(r->cmd[Fcmd]){
+	case Cid:
+	case Cidpkt:
+		return 512;
+	default:
+		return d->secsize;
+	}
+}
+
+/*
+ * various checks.  we should be craftier and
+ * avoid figuring out how big stuff is supposed to be.
+ */
+static uint
+patasizeck(Drive *d, SDreq *r)
+{
+	uint count, maxio, secsize;
+	Ctlr *ctlr;
+
+	secsize = cmdss(d, r);		/* BOTCH */
+	if(secsize == 0)
+		error(Eio);
+	count = r->dlen / secsize;
+	ctlr = d->ctlr;
+	if(ctlr->maxio)
+		maxio = ctlr->maxio;
+	else if((d->feat & Dllba) && (r->ataproto & P28) == 0)
+		maxio = 65536;
+	else
+		maxio = 256;
+	if(count > maxio){
+		uprint("i/o too large, lim %d", maxio);
+		error(up->genbuf);
+	}
+	if(r->ataproto&Ppio && count > 1)
+		error("invalid # of sectors");
+	return count;
+}
+
+static int
+atapataio(Drive *d, SDreq *r)
+{
+	int rv;
+	Ctlr *ctlr;
+
+	d->count = 0;
+	if(r->ataproto & Pdatam)
+		d->count = patasizeck(d, r);
+	d->block = r->dlen;
+	d->limit = d->data + r->dlen;
+
+	ctlr = d->ctlr;
+	qlock(ctlr);
+	if(waserror()){
+		qunlock(ctlr);
+		nexterror();
+	}
+	rv = atagenatastart(d, r);
+	poperror();
+	if(rv){
+		if(DEBUG & DbgAtazz)
+			print("sdide: !atageatastart\n");
+		ilock(ctlr);
+		atanop(d, 0);
+		iunlock(ctlr);
+		qunlock(ctlr);
+		return sdsetsense(r, SDcheck, 4, 8, d->error);
+	}
+
+	if((r->ataproto & Pprotom) == Preset)
+		atarstdone(d);
+	else
+		while(iowait(d, 30*1000, 1) == 0)
+			;
+	if(!ctlr->done){
+		if(DEBUG & DbgAtazz){
+			print("sdide: !done\n");
+			atadumpstate(d, r, 0, d->count);
+		}
+		ataabort(d, 1);
+		qunlock(ctlr);
+		return sdsetsense(r, SDcheck, 11, 0, 6);	/* aborted; i/o process terminated */
+	}
+	mkrfis(d, r);
+	if(d->status & Err){
+		if(DEBUG & DbgAtazz)
+			print("sdide: status&Err\n");
+		qunlock(ctlr);
+		return sdsetsense(r, SDcheck, 4, 8, d->error);
+	}
+	qunlock(ctlr);
+	return SDok;
+}
+
+static int
+ataataio0(Drive *d, SDreq *r)
+{
+	int i;
+
+	if((r->ataproto & Pprotom) == Ppkt){
+		if(r->clen > d->pkt)
+			error(Eio);
+		qlock(d->ctlr);
+		i = atapktio0(d, r);
+		d->block = d->data - (uchar*)r->data;
+		mkrfis(d, r);
+		qunlock(d->ctlr);
+		return i;
+	}else
+		return atapataio(d, r);
+}
+
+/*
+ * hack to allow udma mode to be set or unset
+ * via direct ata command.  it would be better
+ * to move the assumptions about dma mode out
+ * of some of the helper functions.
+ */
+static int
+isudm(SDreq *r)
+{
+	uchar *c;
+
+	c = r->cmd;
+	if(c[Fcmd] == 0xef && c[Ffeat] == 0x03){
+		if(c[Fsc]&0x40)
+			return 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+fisreqchk(Sfis *f, SDreq *r)
+{
+	if((r->ataproto & Pprotom) == Ppkt)
+		return SDnostatus;
+	/*
+	 * handle oob requests;
+	 *    restrict & sanitize commands
+	 */
+	if(r->clen != 16)
+		error(Eio);
+	if(r->cmd[0] == 0xf0){
+		sigtofis(f, r->cmd);
+		r->status = SDok;
+		return SDok;
+	}
+	r->cmd[0] = 0x27;
+	r->cmd[1] = 0x80;
+	r->cmd[7] |= 0xa0;
+	return SDnostatus;
+}
+
+static int
+ataataio(SDreq *r)
+{
+	int status, udm;
+	Ctlr *c;
+	Drive *d;
+	SDunit *u;
+
+	u = r->unit;
+	if((c = u->dev->ctlr) == nil || (d = c->drive[u->subno]) == nil){
+		r->status = SDtimeout;
+		return SDtimeout;
+	}
+	if((status = fisreqchk(d, r)) != SDnostatus)
+		return status;
+	udm = isudm(r);
+
+	qlock(d);
+	if(waserror()){
+		qunlock(d);
+		nexterror();
+	}
+retry:
+	d->write = r->write;
+	d->data = r->data;
+	d->dlen = r->dlen;
+	d->status = 0;
+	d->error = 0;
+
+	switch(status = ataataio0(d, r)){
+	case SDretry:
+		if(DbgDEBUG)
+			print("%s: retry: dma %.8ux rwm %.4ux\n",
+				u->name, d->dmactl, d->rwmctl);
+		goto retry;
+	case SDok:
+		if(udm == 1)
+			d->dmactl = d->dma;
+		else if(udm == -1)
+			d->dmactl = 0;
+		sdsetsense(r, SDok, 0, 0, 0);
+		r->rlen = d->block;
+		break;
+	}
+	poperror();
+	qunlock(d);
+	r->status = status;
+	return status;
+}
+/**/
+
+static void
+ichirqack(Ctlr *ctlr)
+{
+	int bmiba;
+
+	if(bmiba = ctlr->bmiba)
+		outb(bmiba+Bmisx, inb(bmiba+Bmisx));
+}
+
+static void
+atainterrupt(Ureg*, void* arg)
+{
+	Ctlr *ctlr;
+	Drive *drive;
+	int cmdport, len, status;
+
+	ctlr = arg;
+
+	ilock(ctlr);
+	ctlr->nrq++;
+	if(ctlr->curdrive)
+		ctlr->curdrive->irq++;
+	if(inb(ctlr->ctlport+As) & Bsy){
+		ctlr->bsy++;
+		if(ctlr->curdrive)
+			ctlr->curdrive->bsy++;
+		iunlock(ctlr);
+		if(DEBUG & DbgBsy)
+			print("IBsy+");
+		return;
+	}
+	cmdport = ctlr->cmdport;
+	status = inb(cmdport+Status);
+	if((drive = ctlr->curdrive) == nil){
+		ctlr->nildrive++;
+		if(ctlr->irqack != nil)
+			ctlr->irqack(ctlr);
+		iunlock(ctlr);
+		if((DEBUG & DbgINL) && 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:
+	case Ppio|Pin:
+		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:
+	case Ppio|Pout:
+		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:
+	case Ppkt|Pin:
+	case Ppkt|Pout:
+		atapktinterrupt(drive);
+		break;
+
+	case Crd:
+	case Cwd:
+	case Pdma|Pin:
+	case Pdma|Pout:
+		atadmainterrupt(drive, drive->count*drive->secsize);
+		break;
+
+	case Pnd:
+	case Preset:
+		ctlr->done = 1;
+		break;
+	}
+	if(ctlr->irqack != nil)
+		ctlr->irqack(ctlr);
+	iunlock(ctlr);
+
+	if(drive->error){
+		status |= Err;
+		ctlr->done = 1;
+	}
+
+	if(ctlr->done){
+		ctlr->curdrive = nil;
+		drive->status = status;
+		wakeup(ctlr);
+	}
+}
+
+typedef struct Lchan Lchan;
+struct Lchan {
+	int	cmdport;
+	int	ctlport;
+	int	irq;
+	int	probed;
+};
+static Lchan lchan[2] = {
+	0x1f0,	0x3f4,	IrqATA0,	0,
+	0x170,	0x374,	IrqATA1,	0,
+};
+
+static int
+badccru(Pcidev *p)
+{
+	switch(p->did<<16 | p->did){
+	case 0x439c<<16 | 0x1002:
+	case 0x438c<<16 | 0x1002:
+print("hi, anothy\n");
+print("%T: allowing bad ccru %.2ux for suspected ide controller\n", p->tbdf, p->ccru);
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static SDev*
+atapnp(void)
+{
+	char *s;
+	int channel, map, ispc87415, maxio, pi, r, span, tbdf;
+	Ctlr *ctlr;
+	Pcidev *p;
+	SDev *sdev, *head, *tail;
+	void (*irqack)(Ctlr*);
+
+	head = tail = nil;
+	for(p = nil; 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).
+		 * Sub-class code 0x04 is 'RAID controller', e.g. VIA VT8237.
+		 */
+		if(p->ccrb != 0x01)
+			continue;
+		if(!badccru(p))
+		if(p->ccru != 0x01 && p->ccru != 0x04 && p->ccru != 0x80)
+			continue;
+		pi = p->ccrp;
+		map = 3;
+		ispc87415 = 0;
+		maxio = 0;
+		if(s = getconf("*idemaxio"))
+			maxio = atoi(s);
+		span = BMspan;
+		irqack = nil;
+
+		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 */
+		case (0x4D30<<16)|0x105A:	/* Promise PDC202xx */
+		case (0x4D68<<16)|0x105A:	/* Promise PDC20268 */
+		case (0x4D69<<16)|0x105A:	/* Promise Ultra/133 TX2 */
+		case (0x3373<<16)|0x105A:	/* Promise 20378 RAID */
+		case (0x3149<<16)|0x1106:	/* VIA VT8237 SATA/RAID */
+		case (0x3112<<16)|0x1095:	/* SiL 3112 SATA/RAID */
+			maxio = 15;
+			span = 8*1024;
+			/*FALLTHROUGH*/
+		case (0x3114<<16)|0x1095:	/* SiL 3114 SATA/RAID */
+		case (0x0680<<16)|0x1095:	/* SiI 0680/680A PATA133 ATAPI/RAID */
+			pi = 0x85;
+			break;
+		case (0x0004<<16)|0x1103:	/* HighPoint HPT366 */
+			pi = 0x85;
+			/*
+			 * Turn off fast interrupt prediction.
+			 */
+			if((r = pcicfgr8(p, 0x51)) & 0x80)
+				pcicfgw8(p, 0x51, r & ~0x80);
+			if((r = pcicfgr8(p, 0x55)) & 0x80)
+				pcicfgw8(p, 0x55, r & ~0x80);
+			break;
+		case (0x0640<<16)|0x1095:	/* CMD 640B */
+			/*
+			 * Bugfix code here...
+			 */
+			break;
+		case (0x7441<<16)|0x1022:	/* AMD 768 */
+			/*
+			 * Set:
+			 *	0x41	prefetch, postwrite;
+			 *	0x43	FIFO configuration 1/2 and 1/2;
+			 *	0x44	status register read retry;
+			 *	0x46	DMA read and end of sector flush.
+			 */
+			r = pcicfgr8(p, 0x41);
+			pcicfgw8(p, 0x41, r|0xF0);
+			r = pcicfgr8(p, 0x43);
+			pcicfgw8(p, 0x43, (r & 0x90)|0x2A);
+			r = pcicfgr8(p, 0x44);
+			pcicfgw8(p, 0x44, r|0x08);
+			r = pcicfgr8(p, 0x46);
+			pcicfgw8(p, 0x46, (r & 0x0C)|0xF0);
+			/*FALLTHROUGH*/
+		case (0x01BC<<16)|0x10DE:	/* nVidia nForce1 */
+		case (0x0065<<16)|0x10DE:	/* nVidia nForce2 */
+		case (0x0085<<16)|0x10DE:	/* nVidia nForce2 MCP */
+		case (0x00E3<<16)|0x10DE:	/* nVidia nForce2 250 SATA */
+		case (0x00D5<<16)|0x10DE:	/* nVidia nForce3 */
+		case (0x00E5<<16)|0x10DE:	/* nVidia nForce3 Pro */
+		case (0x00EE<<16)|0x10DE:	/* nVidia nForce3 250 SATA */
+		case (0x0035<<16)|0x10DE:	/* nVidia nForce3 MCP */
+		case (0x0053<<16)|0x10DE:	/* nVidia nForce4 */
+		case (0x0054<<16)|0x10DE:	/* nVidia nForce4 SATA */
+		case (0x0055<<16)|0x10DE:	/* nVidia nForce4 SATA */
+		case (0x0266<<16)|0x10DE:	/* nVidia nForce4 430 SATA */
+		case (0x0265<<16)|0x10DE:	/* nVidia nForce 51 MCP */
+		case (0x0267<<16)|0x10DE:	/* nVidia nForce 55 MCP SATA */
+		case (0x03ec<<16)|0x10DE:	/* nVidia nForce 61 MCP SATA */
+		case (0x03f6<<16)|0x10DE:	/* nVidia nForce 61 MCP PATA */
+		case (0x0448<<16)|0x10DE:	/* nVidia nForce 65 MCP SATA */
+		case (0x0560<<16)|0x10DE:	/* nVidia nForce 69 MCP SATA */
+			/*
+			 * Ditto, although it may have a different base
+			 * address for the registers (0x50?).
+			 */
+			/*FALLTHROUGH*/
+		case (0x209A<<16)|0x1022:	/* AMD CS5536 */
+		case (0x7401<<16)|0x1022:	/* AMD 755 Cobra */
+		case (0x7409<<16)|0x1022:	/* AMD 756 Viper */
+		case (0x7410<<16)|0x1022:	/* AMD 766 Viper Plus */
+		case (0x7469<<16)|0x1022:	/* AMD 3111 */
+		case (0x4376<<16)|0x1002:	/* SB4xx pata */
+		case (0x4379<<16)|0x1002:	/* SB4xx sata */
+		case (0x437a<<16)|0x1002:	/* SB4xx sata ctlr #2 */
+		case (0x437c<<16)|0x1002:	/* Rx6xx pata */
+		case (0x439c<<16)|0x1002:	/* SB7xx pata */
+			break;
+		case (0x0211<<16)|0x1166:	/* ServerWorks IB6566 */
+			{
+				Pcidev *sb;
+
+				sb = pcimatch(nil, 0x1166, 0x0200);
+				if(sb == nil)
+					break;
+				r = pcicfgr32(sb, 0x64);
+				r &= ~0x2000;
+				pcicfgw32(sb, 0x64, r);
+			}
+			span = 32*1024;
+			break;
+		case (0x5229<<16)|0x10B9:	/* ALi M1543 */
+		case (0x5288<<16)|0x10B9:	/* ALi M5288 SATA */
+			/*FALLTHROUGH*/
+		case (0x5513<<16)|0x1039:	/* SiS 962 */
+		case (0x0646<<16)|0x1095:	/* CMD 646 */
+		case (0x0571<<16)|0x1106:	/* VIA 82C686 */
+		case (0x0502<<16)|0x100b:	/* National Semiconductor SC1100/SCx200 */
+			break;
+		case (0x2360<<16)|0x197b:	/* jmicron jmb360 */
+		case (0x2361<<16)|0x197b:	/* jmicron jmb361 */
+		case (0x2363<<16)|0x197b:	/* jmicron jmb363 */
+		case (0x2365<<16)|0x197b:	/* jmicron jmb365 */
+		case (0x2366<<16)|0x197b:	/* jmicron jmb366 */
+		case (0x2368<<16)|0x197b:	/* jmicron jmb368 */
+			break;
+		case (0x1230<<16)|0x8086:	/* 82371FB (PIIX) */
+		case (0x7010<<16)|0x8086:	/* 82371SB (PIIX3) */
+		case (0x7111<<16)|0x8086:	/* 82371[AE]B (PIIX4[E]) */
+			break;
+		case (0x2411<<16)|0x8086:	/* 82801AA (ICH) */
+		case (0x2421<<16)|0x8086:	/* 82801AB (ICH0) */
+		case (0x244A<<16)|0x8086:	/* 82801BA (ICH2, Mobile) */
+		case (0x244B<<16)|0x8086:	/* 82801BA (ICH2, High-End) */
+		case (0x248A<<16)|0x8086:	/* 82801CA (ICH3, Mobile) */
+		case (0x248B<<16)|0x8086:	/* 82801CA (ICH3, High-End) */
+		case (0x24CA<<16)|0x8086:	/* 82801DBM (ICH4, Mobile) */
+		case (0x24CB<<16)|0x8086:	/* 82801DB (ICH4, High-End) */
+		case (0x24D1<<16)|0x8086:	/* 82801er (ich5) */
+		case (0x24DB<<16)|0x8086:	/* 82801EB (ICH5) */
+		case (0x25A2<<16)|0x8086:	/* 6300ESB pata */
+		case (0x25A3<<16)|0x8086:	/* 6300ESB (E7210) */
+		case (0x266F<<16)|0x8086:	/* 82801FB (ICH6) */
+		case (0x2653<<16)|0x8086:	/* 82801FBM (ICH6, Mobile) */
+		case (0x269e<<16)|0x8086:	/* 63xxESB (intel 5000) */
+		case (0x27DF<<16)|0x8086:	/* 82801G PATA (ICH7) */
+		case (0x27C0<<16)|0x8086:	/* 82801GB SATA (ICH7) */
+		case (0x27C4<<16)|0x8086:	/* 82801GBM SATA (ICH7) */
+		case (0x27C5<<16)|0x8086:	/* 82801GBM SATA AHCI (ICH7) */
+		case (0x2820<<16)|0x8086:	/* 82801HB/HR/HH/HO SATA IDE */
+		case (0x2828<<16)|0x8086:	/* 82801HBM SATA (ICH8-M) */
+		case (0x2920<<16)|0x8086:	/* 82801(IB)/IR/IH/IO SATA (ICH9) port 0-3 */
+		case (0x2921<<16)|0x8086:	/* 82801(IB)/IR/IH/IO SATA (ICH9) port 0-1 */
+		case (0x2926<<16)|0x8086:	/* 82801(IB)/IR/IH/IO SATA (ICH9) port 4-5 */
+		case (0x2928<<16)|0x8086:	/* 82801(IB)/IR/IH/IO SATA (ICH9m) port 0-1 */
+		case (0x2929<<16)|0x8086:	/* 82801(IB)/IR/IH/IO SATA (ICH9m) port 0-1, 4-5 */
+		case (0x292d<<16)|0x8086:	/* 82801(IB)/IR/IH/IO SATA (ICH9m) port 4-5*/
+		case (0x3a20<<16)|0x8086:	/* 82801ji (ich10) */
+		case (0x3a26<<16)|0x8086:	/* 82801ji (ich10) */
+		case (0x3b20<<16)|0x8086:	/* 34x0 (pch) port 0-3 */
+		case (0x3b21<<16)|0x8086:	/* 34x0 (pch) port 4-5 */
+		case (0x3b28<<16)|0x8086:	/* 34x0pm (pch) port 0-1, 4-5 */
+		case (0x3b2e<<16)|0x8086:	/* 34x0pm (pch) port 0-3 */
+			map = 0;
+			if(pcicfgr16(p, 0x40) & 0x8000)
+				map |= 1;
+			if(pcicfgr16(p, 0x42) & 0x8000)
+				map |= 2;
+			irqack = ichirqack;
+			break;
+		}
+		for(channel = 0; channel < 2; channel++){
+			if((map & 1<<channel) == 0)
+				continue;
+			if(pi & 1<<2*channel){
+				sdev = ataprobe(p->mem[0+2*channel].bar & ~0x01,
+						p->mem[1+2*channel].bar & ~0x01,
+						p->intl, 3);
+				tbdf = p->tbdf;
+			}
+			else if(lchan[channel].probed == 0){
+				sdev = ataprobe(lchan[channel].cmdport,
+					lchan[channel].ctlport, lchan[channel].irq, 3);
+				lchan[channel].probed = 1;
+				tbdf = BUSUNKNOWN;
+			}
+			else
+				continue;
+			if(sdev == nil)
+				continue;
+			ctlr = sdev->ctlr;
+			if(ispc87415) {
+				ctlr->ienable = pc87415ienable;
+				print("pc87415disable: not yet implemented\n");
+			}
+			ctlr->tbdf = tbdf;
+			ctlr->pcidev = p;
+			ctlr->maxio = maxio;
+			ctlr->span = span;
+			ctlr->irqack = irqack;
+			if(pi & 0x80)
+				ctlr->bmiba = (p->mem[4].bar & ~0x01) + channel*8;
+			if(head != nil)
+				tail->next = sdev;
+			else
+				head = sdev;
+			tail = sdev;
+		}
+	}
+
+	if(lchan[0].probed + lchan[1].probed == 0)
+		for(channel = 0; channel < 2; channel++){
+			sdev = nil;
+			if(lchan[channel].probed == 0){
+	//			print("sdide: blind probe %.3ux\n", lchan[channel].cmdport);
+				sdev = ataprobe(lchan[channel].cmdport,
+					lchan[channel].ctlport, lchan[channel].irq, 3);
+				lchan[channel].probed = 1;
+			}
+			if(sdev == nil)
+				continue;
+			if(head != nil)
+				tail->next = sdev;
+			else
+				head = sdev;
+			tail = sdev;
+		}
+
+if(0){
+	int port;
+	ISAConf isa;
+
+	/*
+	 * Hack for PCMCIA drives.
+	 * This will be tidied once we figure out how the whole
+	 * removeable device thing is going to work.
+	 */
+	memset(&isa, 0, sizeof(isa));
+	isa.port = 0x180;		/* change this for your machine */
+	isa.irq = 11;			/* change this for your machine */
+
+	port = isa.port+0x0C;
+	channel = pcmspecial("MK2001MPL", &isa);
+	if(channel == -1)
+		channel = pcmspecial("SunDisk", &isa);
+	if(channel == -1){
+		isa.irq = 10;
+		channel = pcmspecial("CF", &isa);
+	}
+	if(channel == -1){
+		isa.irq = 10;
+		channel = pcmspecial("OLYMPUS", &isa);
+	}
+	if(channel == -1){
+		port = isa.port+0x204;
+		channel = pcmspecial("ATA/ATAPI", &isa);
+	}
+	if(channel >= 0 && (sdev = ataprobe(isa.port, port, isa.irq, 3)) != nil){
+		if(head != nil)
+			tail->next = sdev;
+		else
+			head = sdev;
+	}
+}
+	return head;
+}
+
+static void
+atadmaclr(Ctlr *ctlr)
+{
+	int bmiba, bmisx;
+
+	if(ctlr->curdrive)
+		ataabort(ctlr->curdrive, 1);
+	bmiba = ctlr->bmiba;
+	if(bmiba == 0)
+		return;
+	atadmastop(ctlr);
+ 	outl(bmiba+Bmidtpx, 0);
+	bmisx = inb(bmiba+Bmisx) & ~Bmidea;
+	outb(bmiba+Bmisx, bmisx|Ideints|Idedmae);
+//	pciintst(ctlr->pcidev);
+}
+
+static int
+ataenable(SDev* sdev)
+{
+	Ctlr *ctlr;
+	char name[32];
+
+	ctlr = sdev->ctlr;
+	if(ctlr->bmiba){
+		atadmaclr(ctlr);
+		if(ctlr->pcidev != nil)
+			pcisetbme(ctlr->pcidev);
+		ctlr->prdt = mallocalign(Nprd*sizeof(Prd), 4, 0, 64*1024);
+	}
+	snprint(name, sizeof(name), "%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
+atadisable(SDev *sdev)
+{
+	Ctlr *ctlr;
+	char name[32];
+
+	ctlr = sdev->ctlr;
+	outb(ctlr->ctlport+Dc, Nien);		/* disable interrupts */
+	if (ctlr->idisable)
+		ctlr->idisable(ctlr);
+	snprint(name, sizeof(name), "%s (%s)", sdev->name, sdev->ifc->name);
+	intrdisable(ctlr->irq, atainterrupt, ctlr, ctlr->tbdf, name);
+	if(ctlr->bmiba) {
+//		atadmaclr(ctlr);
+		if (ctlr->pcidev)
+			pciclrbme(ctlr->pcidev);
+		free(ctlr->prdt);
+	}
+	return 0;
+}
+
+static int
+ataonline(SDunit *unit)
+{
+	Ctlr *ctlr;
+	Drive *drive;
+
+	if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil)
+		return 0;
+	drive = ctlr->drive[unit->subno];
+	if((drive->flags & Online) == 0){
+		drive->flags |= Online;
+		atadrive(unit, drive, ctlr->cmdport, ctlr->ctlport, drive->dev);
+	}
+	unit->sectors = drive->sectors;
+	unit->secsize = drive->secsize;
+	if(drive->feat & Datapi)
+		return scsionline(unit);
+	return 1;
+}
+
+static int
+atarctl(SDunit* unit, char* p, int l)
+{
+	Ctlr *ctlr;
+	Drive *drive;
+	char *e, *op;
+
+	if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil)
+		return 0;
+	drive = ctlr->drive[unit->subno];
+
+	e = p+l;
+	op = p;
+	qlock(drive);
+	p = seprint(p, e, "config %4.4uX capabilities %4.4uX", drive->info[Iconfig], drive->info[Icapabilities]);
+	if(drive->dma)
+		p = seprint(p, e, " dma %8.8uX dmactl %8.8uX", drive->dma, drive->dmactl);
+	if(drive->rwm)
+		p = seprint(p, e, " rwm %ud rwmctl %ud", drive->rwm, drive->rwmctl);
+	if(drive->feat & Dllba)
+		p = seprint(p, e, " lba48always %s", (drive->flags&Lba48always) ? "on" : "off");
+	p = seprint(p, e, "\n");
+	p = seprint(p, e, "model	%s\n", drive->model);
+	p = seprint(p, e, "serial	%s\n", drive->serial);
+	p = seprint(p, e, "firm	%s\n", drive->firmware);
+	p = seprint(p, e, "feat	");
+	p = pflag(p, e, drive);
+	if(drive->sectors){
+		p = seprint(p, e, "geometry %llud %d", drive->sectors, drive->secsize);
+		if(drive->pkt == 0 && (drive->feat & Dlba) == 0)
+			p = seprint(p, e, " %d %d %d", drive->c, drive->h, drive->s);
+		p = seprint(p, e, "\n");
+	}
+	p = seprint(p, e, "missirq	%ud\n", drive->missirq);
+	p = seprint(p, e, "sloop	%ud\n", drive->spurloop);
+	p = seprint(p, e, "irq	%ud %ud\n", ctlr->nrq, drive->irq);
+	p = seprint(p, e, "bsy	%ud %ud\n", ctlr->bsy, drive->bsy);
+	p = seprint(p, e, "nildrive	%ud\n", ctlr->nildrive);
+	qunlock(drive);
+
+	return p - op;
+}
+
+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 if(strcmp(cb->f[0], "lba48always") == 0){
+		if(cb->nf != 2 || !(drive->feat & Dllba))
+			error(Ebadctl);
+		if(strcmp(cb->f[1], "on") == 0)
+			drive->flags |= Lba48always;
+		else if(strcmp(cb->f[1], "off") == 0)
+			drive->flags &= ~Lba48always;
+		else
+			error(Ebadctl);
+	}
+	else if(strcmp(cb->f[0], "identify") == 0){
+		atadrive(unit, drive, ctlr->cmdport, ctlr->ctlport, drive->dev);
+	}
+	else
+		error(Ebadctl);
+	qunlock(drive);
+	poperror();
+
+	return 0;
+}
+
+SDifc sdideifc = {
+	"ide",				/* name */
+
+	atapnp,				/* pnp */
+	nil,			/* legacy */
+	ataenable,			/* enable */
+	atadisable,			/* disable */
+
+	scsiverify,			/* verify */
+	ataonline,			/* online */
+	atario,				/* rio */
+	atarctl,			/* rctl */
+	atawctl,			/* wctl */
+
+	scsibio,			/* bio */
+	ataprobew,			/* probe */
+	ataclear,			/* clear */
+	atastat,			/* rtopctl */
+	nil,				/* wtopctl */
+	ataataio,
+};
--- a/sys/src/9/pc/sdmv50xx.c
+++ b/sys/src/9/pc/sdmv50xx.c
@@ -1,5 +1,5 @@
 /*
- * Marvell 88SX[56]0[48][01] fileserver Serial ATA (SATA) driver
+ * Marvell 88SX[56]0[48][01] Serial ATA (SATA) driver
  *
  * See MV-S101357-00 Rev B Marvell PCI/PCI-X to 8-Port/4-Port
  * SATA Host Controller, ATA-5 ANSI NCITS 340-2000.
@@ -16,25 +16,18 @@
 #include "fns.h"
 #include "io.h"
 #include "../port/error.h"
+#include	"../port/sd.h"
+#include <fis.h>
 
-#include "../port/sd.h"
+#define	dprint(...)	// print(__VA_ARGS__)
+#define	idprint(...)	print(__VA_ARGS__)
+#define	Ticks		MACHP(0)->ticks
 
-#define dprint if(!0){}else iprint
-#define idprint if(!0){}else iprint
-#define ioprint if(!0){}else iprint
-
 enum {
 	NCtlr		= 4,
-	NCtlrdrv	= 8,
+	NCtlrdrv		= 8,
 	NDrive		= NCtlr*NCtlrdrv,
 
-	Read 		= 0,
-	Write,
-
-	Coraiddebug	= 0,
-};
-
-enum {
 	SrbRing = 32,
 
 	/* Addresses of ATA register */
@@ -48,63 +41,58 @@
 	ARseccnt	= 022,
 	ARstat		= 027,
 
-	ATAerr		= (1<<0),
-	ATAdrq		= (1<<3),
-	ATAdf 		= (1<<5),
-	ATAdrdy 	= (1<<6),
-	ATAbusy 	= (1<<7),
-	ATAabort	= (1<<2),
-	ATAobs		= (1<<1 | 1<<2 | 1<<4),
-	ATAeIEN		= (1<<1),
-	ATAsrst		= (1<<2),
-	ATAhob		= (1<<7),
-	ATAbad		= (ATAbusy|ATAdf|ATAdrq|ATAerr),
+	ATAerr		= 1<<0,
+	ATAdrq		= 1<<3,
+	ATAdf 		= 1<<5,
+	ATAdrdy 	= 1<<6,
+	ATAbusy 	= 1<<7,
+	ATAabort	= 1<<2,
+	ATAobs		= 1<<1 | 1<<2 | 1<<4,
+	ATAeIEN	= 1<<1,
+	ATAbad		= ATAbusy|ATAdf|ATAdrq|ATAerr,
 
-	SFdone 		= (1<<0),
-	SFerror 	= (1<<1),
+	SFdone 		= 1<<0,
+	SFerror 		= 1<<1,
 
-	SRBident 	= 0,
-	SRBread,
-	SRBwrite,
-	SRBsmart,
+	PRDeot		= 1<<15,
 
-	SRBnodata = 0,
-	SRBdatain,
-	SRBdataout,
-
-	RQread		= 1,		/* data coming IN from device */
-
-	PRDeot		= (1<<15),
-
 	/* EDMA interrupt error cause register */
+	ePrtDataErr	= 1<<0,
+	ePrtPRDErr	= 1<<1,
+	eDevErr		= 1<<2,
+	eDevDis		= 1<<3,
+	eDevCon	= 1<<4,
+	eOverrun	= 1<<5,
+	eUnderrun	= 1<<6,
+	eSelfDis		= 1<<8,
+	ePrtCRQBErr	= 1<<9,
+	ePrtCRPBErr	= 1<<10,
+	ePrtIntErr	= 1<<11,
+	eIORdyErr	= 1<<12,
 
-	ePrtDataErr	= (1<<0),
-	ePrtPRDErr	= (1<<1),
-	eDevErr		= (1<<2),
-	eDevDis		= (1<<3),
-	eDevCon		= (1<<4),
-	eOverrun	= (1<<5),
-	eUnderrun	= (1<<6),
-	eSelfDis	= (1<<8),
-	ePrtCRQBErr	= (1<<9),
-	ePrtCRPBErr	= (1<<10),
-	ePrtIntErr	= (1<<11),
-	eIORdyErr	= (1<<12),
-
 	/* flags for sata 2 version */
-	eSelfDis2	= (1<<7),
-	SerrInt		= (1<<5),
+	eSelfDis2	= 1<<7,
+	SerrInt		= 1<<5,
 
 	/* EDMA Command Register */
+	eEnEDMA	= 1<<0,
+	eDsEDMA 	= 1<<1,
+	eAtaRst 		= 1<<2,
 
-	eEnEDMA	= (1<<0),
-	eDsEDMA 	= (1<<1),
-	eAtaRst 	= (1<<2),
-
 	/* Interrupt mask for errors we care about */
-	IEM		= (eDevDis | eDevCon | eSelfDis),
-	IEM2		= (eDevDis | eDevCon | eSelfDis2),
+	IEM		= eDevDis | eDevCon | eSelfDis,
+	IEM2		= eDevDis | eDevCon | eSelfDis2,
 
+	/* phyerrata magic */
+	Mpreamp	= 0x7e0,
+	Dpreamp	= 0x720,
+
+	REV60X1B2	= 0x7,
+	REV60X1C0	= 0x9,
+
+	/* general mmio registers */
+	Portswtch	= 0x1d64/4,
+
 	/* drive states */
 	Dnull 		= 0,
 	Dnew,
@@ -114,33 +102,12 @@
 	Dreset,
 	Dlast,
 
-	/* drive flags */
-	Dext	 	= (1<<0),	/* use ext commands */
-	Dpio		= (1<<1),	/* doing pio */
-	Dwanted		= (1<<2),	/* someone wants an srb entry */
-	Dedma		= (1<<3),	/* device in edma mode */
-	Dpiowant	= (1<<4),	/* some wants to use the pio mode */
-
-	/* phyerrata magic crap */
-	Mpreamp	= 0x7e0,
-	Dpreamp	= 0x720,
-
-	REV60X1B2	= 0x7,
-	REV60X1C0	= 0x9,
-
+	/* sata mode */
+	DMautoneg	= 0,
+	DMsatai,
+	DMsataii,
 };
 
-static char* diskstates[Dlast] = {
-	"null",
-	"new",
-	"ready",
-	"error",
-	"missing",
-	"reset",
-};
-
-extern SDifc sdmv50xxifc;
-
 typedef struct Arb Arb;
 typedef struct Bridge Bridge;
 typedef struct Chip Chip;
@@ -162,12 +129,6 @@
 	Edma	*edma;
 };
 
-enum {
-	DMautoneg,
-	DMsatai,
-	DMsataii,
-};
-
 struct Drive
 {
 	Lock;
@@ -175,7 +136,7 @@
 	Ctlr	*ctlr;
 	SDunit	*unit;
 	char	name[10];
-	ulong	magic;
+	Sfis;
 
 	Bridge	*bridge;
 	Edma	*edma;
@@ -182,12 +143,12 @@
 	Chip	*chip;
 	int	chipx;
 
-	int	mediachange;
+	int	drivechange;
 	int	state;
-	int	flag;
 	uvlong	sectors;
+	uint	secsize;
 	ulong	pm2;		/* phymode 2 init state */
-	ulong	intick;		/* check for hung western digital drives. */
+	ulong	intick;		/* check for hung drives. */
 	int	wait;
 	int	mode;		/* DMautoneg, satai or sataii. */
 
@@ -194,6 +155,7 @@
 	char	serial[20+1];
 	char	firmware[8+1];
 	char	model[40+1];
+	uvlong	wwn;
 
 	ushort	info[256];
 
@@ -205,7 +167,7 @@
 
 	Srb	*srbhead;
 	Srb	*srbtail;
-	int	driveno;	/* ctlr*NCtlrdrv + unit */
+	int	driveno;		/* ctlr*NCtlrdrv + unit */
 };
 
 struct Ctlr
@@ -252,7 +214,7 @@
 /*
  * Memory-mapped I/O registers in many forms.
  */
-struct Bridge			/* memory-mapped per-Drive registers */
+struct Bridge			/* memory-mapped per-drive registers */
 {
 	ulong	status;
 	ulong	serror;
@@ -268,9 +230,9 @@
 	char	fill2[0x34];
 	ulong	phymode;
 	char	fill3[0x88];
-};				/* length must be 0x100 */
+};				/* must be 0x100 hex in length */
 
-struct Arb			/* memory-mapped per-Chip registers */
+struct Arb			/* memory-mapped per-chip registers */
 {
 	ulong	config;		/* satahc configuration register (sata2 only) */
 	ulong	rqop;		/* request queue out-pointer */
@@ -285,7 +247,7 @@
 	Bridge	bridge[4];
 };
 
-struct Edma			/* memory-mapped per-Drive DMA-related registers */
+struct Edma			/* memory-mapped per-drive DMA-related registers */
 {
 	ulong	config;		/* configuration register */
 	ulong	timer;
@@ -357,56 +319,28 @@
 	ulong	ts;		/* time stamp */
 };
 
-static Drive 	*mvsatadrive[NDrive];
-static int	nmvsatadrive;
+static	Ctlr 	*mvsatactlr[NCtlr];
+static	Drive 	*mvsatadrive[NDrive];
+static	int	nmvsatadrive;
+static	char	*diskstates[Dlast] = {
+	"null",
+	"new",
+	"ready",
+	"error",
+	"missing",
+	"reset",
+};
 
-/*
- * Little-endian parsing for drive data.
- */
-static ushort
-lhgets(void *p)
-{
-	uchar *a = p;
-	return ((ushort) a[1] << 8) | a[0];
-}
+extern SDifc sdmv50xxifc;
 
-static ulong
-lhgetl(void *p)
-{
-	uchar *a = p;
-	return ((ulong) lhgets(a+2) << 16) | lhgets(a);
-}
-
-static uvlong
-lhgetv(void *p)
-{
-	uchar *a = p;
-	return ((uvlong) lhgetl(a+4) << 32) | lhgetl(a);
-}
-
-static void
-idmove(char *p, ushort *a, int n)
-{
-	char *op;
-	int i;
-
-	op = p;
-	for(i=0; i<n/2; i++){
-		*p++ = a[i]>>8;
-		*p++ = a[i];
-	}
-	while(p>op && *--p == ' ')
-		*p = 0;
-}
-
 /*
  * Request buffers.
  */
-struct
+static struct
 {
 	Lock;
-	Srb *freechain;
-	int nalloc;
+	Srb	*freechain;
+	int	nalloc;
 } srblist;
 
 static Srb*
@@ -435,9 +369,6 @@
 	iunlock(&srblist);
 }
 
-/*
- * Wait for a byte to be a particular value.
- */
 static int
 satawait(uchar *p, uchar mask, uchar v, int ms)
 {
@@ -448,32 +379,27 @@
 	return (*p & mask) == v;
 }
 
-/*
- * Drive initialization
- */
 /* unmask in the pci registers err done */
 static void
-unmask(ulong *mmio, int port, int coal)
+portswitch(ulong *mmio, int port, uint coal, uint on)
 {
-	port &= 7;
-	if(coal)
-		coal = 1;
-	if (port < 4)
-		mmio[0x1d64/4] |= (3 << (((port&3)*2)) | (coal<<8));
+	ulong m;
+
+	m = 3<<(port&3)*2 | coal<<8;
+	if((port&7) >= 4)
+		m <<= 9;
+	if(on)
+		mmio[Portswtch] |= m;
 	else
-		mmio[0x1d64/4] |= (3 << (((port&3)*2+9)) | (coal<<17));
+		mmio[Portswtch] &= m;
 }
 
-static void
-mask(ulong *mmio, int port, int coal)
+static char*
+dnam(Drive *d)
 {
-	port &= 7;
-	if(coal)
-		coal = 1;
-	if (port < 4)
-		mmio[0x1d64/4] &= ~(3 << (((port&3)*2)) | (coal<<8));
-	else
-		mmio[0x1d64/4] &= ~(3 << (((port&3)*2+9)) | (coal<<17));
+	if(d->unit)
+		return d->unit->name;
+	return d->name;
 }
 
 /* I give up, marvell.  You win. */
@@ -483,17 +409,22 @@
 	ulong n, m;
 	enum { BadAutoCal = 0xf << 26, };
 
-	if (d->ctlr->type == 1)
+	if(d->ctlr->type == 1){
+		/* set phyctrl bits [0:1] to 01 per MV-S102013-00 Rev C. */
+		n = d->bridge->phyctrl;
+		n &= ~3;
+		d->bridge->phyctrl = n | 1;
 		return;
+	}
 	microdelay(200);
 	n = d->bridge->phymode2;
 	while ((n & BadAutoCal) == BadAutoCal) {
-		dprint("%s: badautocal\n", d->unit->name);
+		dprint("%s: badautocal\n", dnam(d));
 		n &= ~(1<<16);
-		n |= (1<<31);
+		n |= 1<<31;
 		d->bridge->phymode2 = n;
 		microdelay(200);
-		d->bridge->phymode2 &= ~((1<<16) | (1<<31));
+		d->bridge->phymode2 &= ~(1<<16 | 1<<31);
 		microdelay(200);
 		n = d->bridge->phymode2;
 	}
@@ -552,6 +483,28 @@
 	}
 }
 
+static int
+edmadisable(Drive *d, int reset)
+{
+	Edma *e;
+
+	e = d->edma;
+	if(!reset && (e->ctl & eEnEDMA) == 0)
+		return 0;
+	e->ctl = eDsEDMA;
+	microdelay(1);
+	if(reset)
+		e->ctl = eAtaRst;
+	microdelay(25);
+	e->ctl = 0;
+	if (satawait((uchar *)&e->ctl, eEnEDMA, 0, 3*1000) == 0){
+		print("%s: eEnEDMA never cleared on reset\n", dnam(d));
+		return -1;
+	}
+	edmacleanout(d);
+	return 0;
+}
+
 static void
 resetdisk(Drive *d)
 {
@@ -571,16 +524,10 @@
 		n = d->edma->sataconfig;	/* flush */
 		USED(n);
 	}
-	d->edma->ctl = eDsEDMA;
-	microdelay(1);
-	d->edma->ctl = eAtaRst;
-	microdelay(25);
-	d->edma->ctl = 0;
-	if (satawait((uchar *)&d->edma->ctl, eEnEDMA, 0, 3*1000) == 0)
-		print("%s: eEnEDMA never cleared on reset\n", d->unit->name);
-	edmacleanout(d);
+	if(edmadisable(d, 1) == -1){
+	}
 	phyerrata(d);
-	d->bridge->sctrl = 0x301 | (d->mode << 4);
+	d->bridge->sctrl = 0x301 | d->mode<<4;
 	d->state = Dmissing;
 }
 
@@ -596,7 +543,7 @@
 	d->rx = xspanalloc(32*sizeof(Rx), 256, 0);
 	d->prd = xspanalloc(32*sizeof(Prd), 32, 0);
 	for(i = 0; i < 32; i++)
-		d->tx[i].prdpa = PADDR(&d->prd[i]);
+		d->tx[i].prdpa = PCIWADDR(&d->prd[i]);
 	coherence();
 }
 
@@ -604,27 +551,12 @@
 configdrive(Ctlr *ctlr, Drive *d, SDunit *unit)
 {
 	dprint("%s: configdrive\n", unit->name);
-	if(d->driveno < 0)
-		panic("mv50xx: configdrive: unset driveno\n");
 	d->unit = unit;
-	edmainit(d);
-	d->mode = DMsatai;
-	if(d->ctlr->type == 1){
-		d->edma->iem = IEM;
-		d->bridge = &d->chip->arb->bridge[d->chipx];
-	}else{
-		d->edma->iem = IEM2;
-		d->bridge = &d->chip->edma[d->chipx].port;
-		d->edma->iem = ~(1<<6);
-		d->pm2 = Dpreamp;
-		if(d->ctlr->lmmio[0x180d8/4] & 1)
-			d->pm2 = d->bridge->phymode2 & Mpreamp;
-	}
 	resetdisk(d);
-	unmask(ctlr->lmmio, d->driveno, 0);
+	portswitch(ctlr->lmmio, d->driveno, 0, 1);
 	delay(100);
 	if(d->bridge->status){
-		dprint("%s: configdrive: found drive %lx\n", unit->name, d->bridge->status);
+		dprint("%s: configdrive: found drive %lux\n", unit->name, d->bridge->status);
 		return 0;
 	}
 	return -1;
@@ -631,21 +563,19 @@
 }
 
 static int
-enabledrive(Drive *d)
+edmaenable(Drive *d)
 {
 	Edma *edma;
 
-	dprint("%s: enabledrive..", d->unit->name);
+	dprint("%s: enabledrive..", dnam(d));
 
 	if((d->bridge->status & 0xf) != 3){
-		dprint("%s: not present\n", d->unit->name);
-		d->state = Dmissing;
+		dprint("%s: not present\n", dnam(d));
 		return -1;
 	}
 	edma = d->edma;
 	if(satawait(&edma->cmdstat, ATAbusy, 0, 5*1000) == 0){
-		dprint("%s: busy timeout\n", d->unit->name);
-		d->state = Dmissing;
+		dprint("%s: busy timeout\n", dnam(d));
 		return -1;
 	}
 	edma->iec = 0;
@@ -653,40 +583,40 @@
 	edma->config = 0x51f;
 	if (d->ctlr->type == 2)
 		edma->config |= 7<<11;
-	edma->txi = PADDR(d->tx);
+	edma->txi = PCIWADDR(d->tx);
 	edma->txo = (ulong)d->tx & 0x3e0;
 	edma->rxi = (ulong)d->rx & 0xf8;
-	edma->rxo = PADDR(d->rx);
+	edma->rxo = PCIWADDR(d->rx);
 	edma->ctl |= 1;		/* enable dma */
-
-	if(d->bridge->status = 0x113){
-		dprint("%s: new\n", d->unit->name);
-		d->state = Dnew;
-	}else
-		print("%s: status not forced (should be okay)\n", d->unit->name);
 	return 0;
 }
 
+static int
+enabledrive(Drive *d)
+{
+	dprint("%s: enabledrive..", dnam(d));
+	if(edmaenable(d) == 0){
+		switch(d->bridge->status){
+		case 0x113:
+		case 0x123:
+			d->state = Dnew;
+			break;
+		}
+		return 0;
+	}
+	print("mv50: enable reset\n");
+	d->state = Dreset;
+	return -1;
+}
+
 static void
 disabledrive(Drive *d)
 {
-	int i;
-	ulong *r;
-
-	dprint("%s: disabledrive\n", d->unit->name);
-
 	if(d->tx == nil)	/* never enabled */
 		return;
-
 	d->edma->ctl = 0;
 	d->edma->iem = 0;
-
-	r = (ulong*)(d->ctlr->mmio + 0x1d64);
-	i = d->chipx;
-	if(d->chipx < 4)
-		*r &= ~(3 << (i*2));
-	else
-		*r |= ~(3 << (i*2+9));
+	portswitch(d->ctlr->lmmio, d->driveno, 0, 0);
 }
 
 static int
@@ -694,15 +624,14 @@
 {
 	Edma *edma;
 
-	dprint("%s: setudmamode %d\n", d->unit->name, mode);
-
+	dprint("%s: setudmamode %d\n", dnam(d), mode);
 	edma = d->edma;
-	if (edma == nil) {
+	if(edma == nil) {
 		iprint("setudamode(m%d): zero d->edma\m", d->driveno);
 		return 0;
 	}
-	if(satawait(&edma->cmdstat, ~ATAobs, ATAdrdy, 9*1000) == 0){
-		iprint("%s: cmdstat 0x%.2ux ready timeout\n", d->unit->name, edma->cmdstat);
+	if(satawait(&edma->cmdstat, ~ATAobs, ATAdrdy, 250) == 0){
+		iprint("%s: cmdstat 0x%.2ux ready timeout\n", dnam(d), edma->cmdstat);
 		return 0;
 	}
 	edma->altstat = ATAeIEN;
@@ -711,7 +640,7 @@
 	edma->cmdstat = 0xef;
 	microdelay(1);
 	if(satawait(&edma->cmdstat, ATAbusy, 0, 5*1000) == 0){
-		iprint("%s: cmdstat 0x%.2ux busy timeout\n", d->unit->name, edma->cmdstat);
+		iprint("%s: cmdstat 0x%.2ux busy timeout\n", dnam(d), edma->cmdstat);
 		return 0;
 	}
 	return 1;
@@ -720,14 +649,15 @@
 static int
 identifydrive(Drive *d)
 {
+	char *s;
 	int i;
 	ushort *id;
 	Edma *edma;
-	SDunit *unit;
+	SDunit *u;
 
-	dprint("%s: identifydrive\n", d->unit->name);
-
-	if(setudmamode(d, 5) == 0)	/* do all SATA support 5? */
+	dprint("%s: identifydrive\n", dnam(d));
+	setfissig(d, 0);			/* BOTCH; need to find and set signature */
+	if(setudmamode(d, 5) == 0)	/* BOTCH; run after identify */
 		goto Error;
 
 	id = d->info;
@@ -745,29 +675,27 @@
 		id[i] = edma->pio;
 	if(edma->cmdstat & ATAbad)
 		goto Error;
-	i = lhgets(id+83) | lhgets(id+86);
-	if(i & (1<<10)){
-		d->flag |= Dext;
-		d->sectors = lhgetv(id+100);
-	}else{
-		d->flag &= ~Dext;
-		d->sectors = lhgetl(id+60);
-	}
+	d->sectors = idfeat(d, id);
+	d->secsize = idss(d, id);
 	idmove(d->serial, id+10, 20);
 	idmove(d->firmware, id+23, 8);
 	idmove(d->model, id+27, 40);
+	d->wwn = idwwn(d, id);
 
-	unit = d->unit;
-	memset(unit->inquiry, 0, sizeof unit->inquiry);
-	unit->inquiry[2] = 2;
-	unit->inquiry[3] = 2;
-	unit->inquiry[4] = sizeof(unit->inquiry)-4;
-	idmove((char*)unit->inquiry+8, id+27, 40);
+	u = d->unit;
+	memset(u->inquiry, 0, sizeof u->inquiry);
+	u->inquiry[2] = 2;
+	u->inquiry[3] = 2;
+	u->inquiry[4] = sizeof u->inquiry - 4;
+	idmove((char*)u->inquiry+8, id+27, 40);
 
 	if(enabledrive(d) == 0) {
 		d->state = Dready;
-		d->mediachange = 1;
-		idprint("%s: LLBA %lld sectors\n", d->unit->name, d->sectors);
+		d->drivechange = 1;
+		s = nil;
+		if(d->feat & Dllba)
+			s = "L";
+		idprint("%s: %sLBA %llud sectors\n", dnam(d), s, d->sectors);
 	} else
 		d->state = Derror;
 	if(d->state == Dready)
@@ -779,20 +707,21 @@
 	return -1;
 }
 
-/* p. 163:
-	M	recovered error
-	P	protocol error
-	N	PhyRdy change
-	W	CommWake
-	B	8-to-10 encoding error
-	D	disparity error
-	C	crc error
-	H	handshake error
-	S	link sequence error
-	T	transport state transition error
-	F	unrecognized fis type
-	X	device changed
-*/
+/*
+ * p. 163:
+ *	M	recovered error
+ *	P	protocol error
+ *	N	PhyRdy change
+ *	W	CommWake
+ *	B	8-to-10 encoding error
+ *	D	disparity error
+ *	C	crc error
+ *	H	handshake error
+ *	S	link sequence error
+ *	T	transport state transition error
+ *	F	unrecognized fis type
+ *	X	device changed
+ */
 
 static char stab[] = {
 [1]	'M',
@@ -800,7 +729,7 @@
 [16]	'N',
 [18]	'W', 'B', 'D', 'C', 'H', 'S', 'T', 'F', 'X'
 };
-static ulong sbad = (7<<20)|(3<<23);
+static ulong sbad = 7<<20 | 3<<23;
 
 static void
 serrdecode(ulong r, char *s, char *e)
@@ -808,17 +737,16 @@
 	int i;
 
 	e -= 3;
-	for(i = 0; i < nelem(stab) && s < e; i++){
-		if((r&(1<<i)) && stab[i]){
+	for(i = 0; i < nelem(stab) && s < e; i++)
+		if(r & 1<<i && stab[i]){
 			*s++ = stab[i];
-			if(sbad&(1<<i))
+			if(sbad & 1<<i)
 				*s++ = '*';
 		}
-	}
 	*s = 0;
 }
 
-char *iectab[] = {
+static char *iectab[] = {
 	"ePrtDataErr",
 	"ePrtPRDErr",
 	"eDevErr",
@@ -846,7 +774,9 @@
 }
 
 enum{
-	Cerror = ePrtDataErr|ePrtPRDErr|eDevErr|eSelfDis2|ePrtCRPBErr|ePrtIntErr,
+	Cerror1	= ePrtDataErr|ePrtPRDErr|eOverrun|ePrtCRQBErr|ePrtCRPBErr|ePrtIntErr,
+	Cerror2	= ePrtDataErr|ePrtPRDErr|ePrtCRQBErr|
+			ePrtCRPBErr|ePrtIntErr|eDevErr|eSelfDis2,
 };
 
 static void
@@ -858,7 +788,7 @@
 	char buf[32+4+1];
 
 	edma = d->edma;
-	if((edma->ctl&eEnEDMA) == 0){
+	if((edma->ctl & eEnEDMA) == 0){
 		/* FEr SATA#4 40xx */
 		x = d->edma->cmdstat;
 		USED(x);
@@ -866,24 +796,46 @@
 	cause = edma->iec;
 	if(cause == 0)
 		return;
-	dprint("%s: cause %08ulx [%s]\n", d->unit->name, cause, iecdecode(cause));
+	dprint("%s: cause %.8lux [%s]\n", dnam(d), cause, iecdecode(cause));
 	if(cause & eDevCon)
 		d->state = Dnew;
-	if(cause&eDevDis && d->state == Dready)
-		iprint("%s: pulled: st=%08ulx\n", d->unit->name, cause);
+	if(cause & eDevDis && d->state == Dready)
+		iprint("%s: pulled: st=%.8lux\n", dnam(d), cause);
 	switch(d->ctlr->type){
 	case 1:
-		if(cause&eSelfDis)
-			d->state = Derror;
+		if(cause & eUnderrun){
+			/* FEr SATA#5 50xx for revs A0, B0 */
+			if(d->ctlr->rid < 2)
+				d->state = Dreset;
+			else{
+				d->state = Derror;
+				dprint("%s: underrun\n", dnam(d));
+			}
+		}
+		if(cause & (eDevErr | eSelfDis)){
+			/*
+			 * FEr SATA#7 60xx for refs A0, B0
+			 * check for IRC error.  we only check the
+			 * ABORT flag as we don't get the upper nibble
+			 */
+			if(d->ctlr->rid < 2)
+			if(edma->altstat & ATAerr && edma->err & ATAabort)
+				d->state = Dreset;
+			else
+				d->state = Derror;
+		}
+		if(cause & Cerror1)
+			d->state = Dreset;
 		break;
 	case 2:
-		if(cause&Cerror)
-			d->state = Derror;
-		if(cause&SerrInt){
+		if(cause & Cerror2)
+			d->state = Dreset;
+		if(cause & SerrInt){
 			serrdecode(d->bridge->serror, buf, buf+sizeof buf);
-			dprint("%s: serror %08ulx [%s]\n", d->unit->name, (ulong)d->bridge->serror, buf);
-			d->bridge->serror = d->bridge->serror;
+			dprint("%s: serror %.8lux [%s]\n", dnam(d), d->bridge->serror, buf);
+			d->bridge->serror = ~0; /*d->bridge->serror;*/
 		}
+		break;
 	}
 	edma->iec = ~cause;
 }
@@ -892,7 +844,7 @@
  * Requests
  */
 static Srb*
-srbrw(int req, Drive *d, uchar *data, uint sectors, uvlong lba)
+srbrw(int rw, Drive *d, uchar *data, uint sectors, uvlong lba)
 {
 	int i;
 	Srb *srb;
@@ -899,38 +851,28 @@
 	static uchar cmd[2][2] = { 0xC8, 0x25, 0xCA, 0x35 };
 
 	srb = allocsrb();
-	srb->req = req;
+	srb->req = rw;
 	srb->drive = d;
 	srb->blockno = lba;
 	srb->sectors = sectors;
-	srb->count = sectors*512;
+	srb->count = sectors * d->secsize;
 	srb->flag = 0;
 	srb->data = data;
 
 	for(i=0; i<6; i++)
-		srb->lba[i] = lba >> (8*i);
-	srb->cmd = cmd[srb->req!=SRBread][(d->flag&Dext)!=0];
+		srb->lba[i] = lba >> 8*i;
+	srb->cmd = cmd[srb->req!=SDread][(d->feat&Dllba)!=0];
 	return srb;
 }
 
-static uintptr
-advance(uintptr pa, int shift)
-{
-	int n, mask;
-
-	mask = 0x1F<<shift;
-	n = (pa & mask) + (1<<shift);
-	return (pa & ~mask) | (n & mask);
-}
-
-#define CMD(r, v) (((r)<<8) | ((v)&0xFF))
+#define CMD(r, v) (((r)<<8) | (v))
 static void
-mvsatarequest(ushort *cmd, Srb *srb, int ext)
+mvsatarequest(ushort *cmd, Srb *srb, int llba)
 {
 	*cmd++ = CMD(ARseccnt, 0);
 	*cmd++ = CMD(ARseccnt, srb->sectors);
 	*cmd++ = CMD(ARfea, 0);
-	if(ext){
+	if(llba){
 		*cmd++ = CMD(ARlba0, srb->lba[3]);
 		*cmd++ = CMD(ARlba0, srb->lba[0]);
 		*cmd++ = CMD(ARlba1, srb->lba[4]);
@@ -944,9 +886,19 @@
 		*cmd++ = CMD(ARlba2, srb->lba[2]);
 		*cmd++ = CMD(ARdev, srb->lba[3] | 0xe0);
 	}
-	*cmd = CMD(ARcmd, srb->cmd) | (1<<15);
+	*cmd = CMD(ARcmd, srb->cmd) | 1<<15;
 }
 
+static uintptr
+advance(uintptr pa, int shift)
+{
+	int n, mask;
+
+	mask = 0x1F<<shift;
+	n = (pa & mask) + (1<<shift);
+	return (pa & ~mask) | (n & mask);
+}
+
 static void
 startsrb(Drive *d, Srb *srb)
 {
@@ -971,19 +923,19 @@
 			break;
 	if(i == nelem(d->srb))
 		panic("sdmv50xx: no free srbs");
-	d->intick = MACHP(0)->ticks;
+	d->intick = Ticks;
 	d->srb[i] = srb;
 	edma = d->edma;
 	tx = (Tx*)KADDR(edma->txi);
-	tx->flag = (i<<1) | (srb->req == SRBread);
+	tx->flag = i<<1 | (srb->req == SDread);
 	prd = KADDR(tx->prdpa);
-	prd->pa = PADDR(srb->data);
+	prd->pa = PCIWADDR(srb->data);
 	prd->count = srb->count;
 	prd->flag = PRDeot;
-	mvsatarequest(tx->regs, srb, d->flag&Dext);
+	mvsatarequest(tx->regs, srb, d->feat&Dllba);
 	coherence();
 	edma->txi = advance(edma->txi, 5);
-	d->intick = MACHP(0)->ticks;
+	d->intick = Ticks;
 }
 
 enum{
@@ -1036,18 +988,20 @@
  * Interrupts
  */
 static void
-mv50interrupt(Ureg*, void *a)
+mv50interrupt(Ureg*, void *v)
 {
 	int i;
-	ulong cause;
+	ulong cause, tk0, m;
+	Arb *a;
 	Ctlr *ctlr;
 	Drive *drive;
+	static uint st;
 
-	ctlr = a;
+	ctlr = v;
 	ilock(ctlr);
 	cause = ctlr->lmmio[0x1d60/4];
-//	dprint("sd%c: mv50interrupt: 0x%lux\n", ctlr->sdev->idno, cause);
-	for(i=0; i<ctlr->ndrive; i++)
+//	dprint("sd%c: mv50interrupt: %.8lux\n", ctlr->sdev->idno, cause);
+	for(i=0; cause && i<ctlr->ndrive; i++)
 		if(cause & (3<<(i*2+i/4))){
 			drive = &ctlr->drive[i];
 			if(drive->edma == 0)
@@ -1054,9 +1008,17 @@
 				continue;	/* not ready yet. */
 			ilock(drive);
 			updatedrive(drive);
-			while(ctlr->chip[i/4].arb->ic & (0x0101 << (i%4))){
-				ctlr->chip[i/4].arb->ic = ~(0x101 << (i%4));
+			tk0 = Ticks;
+			a = ctlr->chip[i/4].arb;
+			m = 0x0101 << i%4;
+			while(a->ic & m){
+				a->ic = ~m;
 				completesrb(drive);
+				if(TK2MS(Ticks - tk0) > 3000){
+					print("%s: irq wedge\n", dnam(drive));
+					drive->state = Dreset;
+					break;
+				}
 			}
 			iunlock(drive);
 		}
@@ -1065,20 +1027,20 @@
 
 enum{
 	Nms		= 256,
-	Midwait		= 16*1024/Nms-1,
-	Mphywait	= 512/Nms-1,
+	Midwait		= 16*1024/Nms - 1,
+	Mphywait	= 512/Nms - 1,
 };
 
 static void
-westerndigitalhung(Drive *d)
+hangck(Drive *d)
 {
 	Edma *e;
 
 	e = d->edma;
-	if(d->srb
-	&& TK2MS(MACHP(0)->ticks-d->intick) > 5*1000
+	if(d->nsrb > 0
+	&& TK2MS(Ticks - d->intick) > 5*1000
 	&& (e->rxo&Rpidx) == (e->rxi&Rpidx)){
-		dprint("westerndigital drive hung; resetting\n");
+		print("%s: drive hung; resetting\n", dnam(d));
 		d->state = Dreset;
 	}
 }
@@ -1087,16 +1049,14 @@
 checkdrive(Drive *d, int i)
 {
 	static ulong s, olds[NCtlr*NCtlrdrv];
-	char *name;
 
 	ilock(d);
-	name = d->unit->name;
 	s = d->bridge->status;
 	if(s != olds[i]){
-		dprint("%s: status: %08lx -> %08lx: %s\n", name, olds[i], s, diskstates[d->state]);
+		dprint("%s: status: %.8lux -> %.8lux: %s\n", dnam(d), olds[i], s, diskstates[d->state]);
 		olds[i] = s;
 	}
-	/* westerndigitalhung(d); */
+	hangck(d);
 	switch(d->state){
 	case Dnew:
 	case Dmissing:
@@ -1104,12 +1064,12 @@
 		case 0x000:
 			break;
 		default:
-			dprint("%s: unknown state %8lx\n", name, s);
+			dprint("%s: unknown state %.8lux\n", dnam(d), s);
 		case 0x100:
 			if(++d->wait&Mphywait)
 				break;
 		reset:	d->mode ^= 1;
-			dprint("%s: reset; new mode %d\n", name, d->mode);
+			dprint("%s: reset; new mode %d\n", dnam(d), d->mode);
 			resetdisk(d);
 			break;
 		case 0x123:
@@ -1125,10 +1085,10 @@
 	case Dready:
 		if(s != 0)
 			break;
-		iprint("%s: pulled: st=%08ulx\n", name, s);	/* never happens */
+		iprint("%s: pulled: st=%.8lux\n", dnam(d), s); /* never happens */
 	case Dreset:
 	case Derror:
-		dprint("%s reset: mode %d\n", name, d->mode);
+		dprint("%s reset: mode %d\n", dnam(d), d->mode);
 		resetdisk(d);
 		break;
 	}
@@ -1140,9 +1100,6 @@
 {
 	int i;
 
-	while(waserror())
-		;
-
 	for(;;){
 		tsleep(&up->sleep, return0, 0, Nms);
 		for(i = 0; i < nmvsatadrive; i++)
@@ -1150,9 +1107,24 @@
 	}
 }
 
-/*
- * Device discovery
- */
+static void
+initdrive(Drive *d)
+{
+	edmainit(d);
+	d->mode = DMsatai;
+	if(d->ctlr->type == 1){
+		d->edma->iem = IEM;
+		d->bridge = &d->chip->arb->bridge[d->chipx];
+	}else{
+		d->edma->iem = IEM2;
+		d->bridge = &d->chip->edma[d->chipx].port;
+//		d->edma->iem = ~(1<<6);
+		d->pm2 = Dpreamp;
+		if(d->ctlr->lmmio[0x180d8/4] & 1)
+			d->pm2 = d->bridge->phymode2 & Mpreamp;
+	}
+}
+
 static SDev*
 mv50pnp(void)
 {
@@ -1160,12 +1132,11 @@
 	uchar *base;
 	ulong io, n, *mem;
 	Ctlr *ctlr;
+	Drive *d;
 	Pcidev *p;
 	SDev *head, *tail, *sdev;
-	Drive *d;
 	static int ctlrno, done;
 
-	dprint("mv50pnp\n");
 	if(done++)
 		return nil;
 
@@ -1173,6 +1144,8 @@
 	head = nil;
 	tail = nil;
 	while((p = pcimatch(p, 0x11ab, 0)) != nil){
+		if(p->ccrb != Pcibcstore || p->ccru + p->ccrp || p->did&0x0f00)
+			continue;
 		switch(p->did){
 		case 0x5040:
 		case 0x5041:
@@ -1190,19 +1163,16 @@
 			break;
 		}
 		nunit = (p->did&0xf0) >> 4;
-		print("Marvell 88SX%ux: %d SATA-%s ports with%s flash\n",
-			(ushort)p->did, nunit,
+		print("#S/sd%c: Marvell 88sx%ux: %d sata-%s ports with%s flash\n",
+			'E' + ctlrno, (ushort)p->did, nunit,
 			((p->did&0xf000)==0x6000? "II": "I"),
 			(p->did&1? "": "out"));
-		if((sdev = malloc(sizeof(SDev))) == nil)
+		if((sdev = malloc(sizeof *sdev)) == nil)
 			continue;
-		if((ctlr = malloc(sizeof(Ctlr))) == nil){
+		if((ctlr = malloc(sizeof *ctlr)) == nil){
 			free(sdev);
 			continue;
 		}
-		memset(sdev, 0, sizeof *sdev);
-		memset(ctlr, 0, sizeof *ctlr);
-
 		io = p->mem[0].bar & ~0x0F;
 		mem = (ulong*)vmap(io, p->mem[0].size);
 		if(mem == 0){
@@ -1242,6 +1212,7 @@
 		}
 		for (i = 0; i < nunit; i++) {
 			d = &ctlr->drive[i];
+			snprint(d->name, sizeof d->name, "mv50%d.%d", ctlrno, i);
 			d->sectors = 0;
 			d->ctlr = ctlr;
 			d->driveno = ctlrno*NCtlrdrv + i;
@@ -1249,7 +1220,9 @@
 			d->chip = &ctlr->chip[i/4];
 			d->edma = &d->chip->edma[d->chipx];
 			mvsatadrive[d->driveno] = d;
+			initdrive(d);
 		}
+		mvsatactlr[ctlrno] = ctlr;
 		nmvsatadrive += nunit;
 		ctlrno++;
 		if(head)
@@ -1261,10 +1234,6 @@
 	return head;
 }
 
-/*
- * Enable the controller.  Each disk has its own interrupt mask,
- * and those get enabled as the disks are brought online.
- */
 static int
 mv50enable(SDev *sdev)
 {
@@ -1276,15 +1245,13 @@
 	ctlr = sdev->ctlr;
 	if (ctlr->enabled)
 		return 1;
+	ctlr->enabled = 1;
+	kproc("mvsata", satakproc, 0);
 	snprint(name, sizeof name, "%s (%s)", sdev->name, sdev->ifc->name);
 	intrenable(ctlr->irq, mv50interrupt, ctlr, ctlr->tbdf, name);
-	ctlr->enabled = 1;
 	return 1;
 }
 
-/*
- * Disable the controller.
- */
 static int
 mv50disable(SDev *sdev)
 {
@@ -1310,30 +1277,6 @@
 }
 
 /*
- * Clean up all disk structures.  Already disabled.
- * Could keep count of number of allocated controllers
- * and free the srblist when it drops to zero.
- */
-static void
-mv50clear(SDev *sdev)
-{
-	int i;
-	Ctlr *ctlr;
-	Drive *d;
-
-	dprint("sd%c: clear\n", sdev->idno);
-
-	ctlr = sdev->ctlr;
-	for(i=0; i<ctlr->ndrive; i++){
-		d = &ctlr->drive[i];
-		free(d->tx);
-		free(d->rx);
-		free(d->prd);
-	}
-	free(ctlr);
-}
-
-/*
  * Check that there is a disk or at least a hot swap bay in the drive.
  */
 static int
@@ -1354,20 +1297,19 @@
 
 	/*
 	 * If ctlr->type == 1, then the drives spin up whenever
-	 * the controller feels like it; if ctlr->type != 1, then
+	 * the controller feels like it; if ctlr->type == 2, then
 	 * they spin up as a result of configdrive.
 	 *
-	 * If there is a drive in the slot, give it 1.5s to spin up
+	 * If there is a drive in the slot, give it 1.4s to spin up
 	 * before returning.  There is a noticeable drag on the
 	 * power supply when spinning up fifteen drives
 	 * all at once (like in the Coraid enclosures).
 	 */
-	if(ctlr->type != 1 && i == 0){
+	if(ctlr->type == 2 && i == 0)
 		if(!waserror()){
-			tsleep(&up->sleep, return0, 0, 1500);
+			tsleep(&up->sleep, return0, 0, 1400);
 			poperror();
 		}
-	}
 	return 1;
 }
 
@@ -1382,9 +1324,6 @@
 	int r, s0;
 	static int once;
 
-	if(once++ == 0)
-		kproc("mvsata", satakproc, 0);
-
 	ctlr = unit->dev->ctlr;
 	d = &ctlr->drive[unit->subno];
 	r = 0;
@@ -1393,12 +1332,12 @@
 	USED(s0);
 	if(d->state == Dnew)
 		identifydrive(d);
-	if(d->mediachange){
+	if(d->drivechange){
 		idprint("%s: online: %s -> %s\n", unit->name, diskstates[s0], diskstates[d->state]);
 		r = 2;
 		unit->sectors = d->sectors;
-		unit->secsize = 512;
-		d->mediachange = 0;
+		unit->secsize = d->secsize;
+		d->drivechange = 0;
 	} else if(d->state == Dready)
 		r = 1;
 	iunlock(d);
@@ -1483,25 +1422,13 @@
 {
 	int i;
 
-	for(i = 0; i < n; i++)
-		p = seprint(p, e, "%s%s%-19s %.8lux\n",
-			prefix? prefix: "", prefix? ": ": "",
-			r[i].name, *(ulong *)((uchar*)base + r[i].offset));
+	for(i=0; i<n; i++)
+		p = seprint(p, e, "%s%s%-19s %.8ux\n",
+			prefix ? prefix : "", prefix ? ": " : "",
+			r[i].name, *(u32int*)((uchar*)base+r[i].offset));
 	return p;
 }
 
-static char*
-rdinfo(char *p, char *e, ushort *info)
-{
-	int i;
-
-	p = seprint(p, e, "info");
-	for(i = 0; i < 256; i++)
-		p = seprint(p, e, "%s%.4ux%s", i%8 == 0? "\t": "", info[i],
-			i%8 == 7? "\n": "");
-	return p;
-}
-
 static int
 mv50rctl(SDunit *unit, char *p, int l)
 {
@@ -1519,15 +1446,18 @@
 		p = seprint(p, e, "model    %s\n", drive->model);
 		p = seprint(p, e, "serial   %s\n", drive->serial);
 		p = seprint(p, e, "firmware %s\n", drive->firmware);
+		p = seprint(p, e, "wwn\t%llux\n", drive->wwn);
+		p = seprint(p, e, "flag\t");
+		p = pflag(p, e, drive);
 	}else
 		p = seprint(p, e, "no disk present\n");
-	p = seprint(p, e, "geometry %llud 512\n", drive->sectors);
-	p = rdinfo(p, e, drive->info);
-
-	p = rdregs(p, e, drive->chip->arb, regsarb, nelem(regsarb), nil);
+	p = seprint(p, e, "geometry %llud %ud\n", drive->sectors, drive->secsize);
 	p = rdregs(p, e, drive->bridge, regsbridge, nelem(regsbridge), nil);
-	p = rdregs(p, e, drive->edma, regsedma, nelem(regsedma), nil);
-
+	if(0){
+		p = rdregs(p, e, drive->chip->arb, regsarb, nelem(regsarb), nil);
+		p = rdregs(p, e, drive->bridge, regsbridge, nelem(regsbridge), nil);
+		p = rdregs(p, e, drive->edma, regsedma, nelem(regsedma), nil);
+	}
 	return p-op;
 }
 
@@ -1537,7 +1467,6 @@
 	Ctlr *ctlr;
 	Drive *drive;
 
-	USED(unit);
 	if(strcmp(cb->f[0], "reset") == 0){
 		ctlr = unit->dev->ctlr;
 		drive = &ctlr->drive[unit->subno];
@@ -1550,34 +1479,6 @@
 	return -1;
 }
 
-/*
- * sd(3): ``Reading /dev/sdctl yields information about each controller,
- * one line per controller.''
- */
-static char*
-mv50rtopctl(SDev *sdev, char *p, char *e)
-{
-	char name[10];
-	Ctlr *ctlr;
-
-	ctlr = sdev->ctlr;
-	if(ctlr == nil)
-		return p;
-
-	snprint(name, sizeof name, "sd%c", sdev->idno);
-	p = rdregs(p, e, ctlr->mmio, regsctlr, nelem(regsctlr), name);
-	if (Coraiddebug) {
-		/* info for first disk.  BUG: this shouldn't be here. */
-		p = rdregs(p, e, ctlr->chip[0].arb,
-			regsarb, nelem(regsarb), name);
-		p = rdregs(p, e, &ctlr->chip[0].arb->bridge[0],
-			regsbridge, nelem(regsbridge), name);
-		p = rdregs(p, e, &ctlr->chip[0].edma[0],
-			regsedma, nelem(regsedma), name);
-	}
-	return p;
-}
-
 static int
 waitready(Drive *d)
 {
@@ -1589,9 +1490,9 @@
 		iunlock(d);
 		if(s == 0)
 			return SDeio;
-		if (d->state == Dready)
+		if(d->state == Dready)
 			return SDok;
-		if ((i+1)%60 == 0){
+		if((i+1)%60 == 0){
 			ilock(d);
 			resetdisk(d);
 			iunlock(d);
@@ -1601,87 +1502,42 @@
 			poperror();
 		}
 	}
-	print("%s: not responding after 2 minutes\n", d->unit->name);
+	print("%s: not responding; error\n", dnam(d));
 	return SDeio;
 }
 
-static int
-mv50rio(SDreq *r)
+static long
+mv50bio(SDunit *u, int /*lun*/, int write, void *a, long count, uvlong lba)
 {
-	int count, max, n, status, try, flag;
-	uchar *cmd, *data;
-	uvlong lba;
+	int n, try, flag;
+	uchar *data;
 	Ctlr *ctlr;
-	Drive *drive;
-	SDunit *unit;
+	Drive *d;
 	Srb *srb;
 
-	unit = r->unit;
-	ctlr = unit->dev->ctlr;
-	drive = &ctlr->drive[unit->subno];
-	cmd = r->cmd;
-
-	if((status = sdfakescsi(r, drive->info, sizeof drive->info)) != SDnostatus){
-		/* XXX check for SDcheck here */
-		r->status = status;
-		return status;
-	}
-
-	switch(cmd[0]){
-	case 0x28:	/* read */
-	case 0x2A:	/* write */
-		break;
-	default:
-		iprint("%s: bad cmd 0x%.2ux\n", drive->unit->name, cmd[0]);
-		r->status = SDcheck;
-		return SDcheck;
-	}
-
-	lba = (cmd[2]<<24)|(cmd[3]<<16)|(cmd[4]<<8)|cmd[5];
-	count = (cmd[7]<<8)|cmd[8];
-	if(r->data == nil)
-		return SDok;
-	if(r->dlen < count*unit->secsize)
-		count = r->dlen/unit->secsize;
-
+	ctlr = u->dev->ctlr;
+	d = ctlr->drive + u->subno;
 	try = 0;
+	data = a;
 retry:
-	if(waitready(drive) != SDok)
-		return SDeio;
-	/*
-	 * Could arrange here to have an Srb always outstanding:
-	 *
-	 *	lsrb = nil;
-	 *	while(count > 0 || lsrb != nil){
-	 *		srb = nil;
-	 *		if(count > 0){
-	 *			srb = issue next srb;
-	 *		}
-	 *		if(lsrb){
-	 *			sleep on lsrb and handle it
-	 *		}
-	 *	}
-	 *
-	 * On the disks I tried, this didn't help.  If anything,
-	 * it's a little slower.		-rsc
-	 */
-	data = r->data;
+	if(waitready(d) != SDok)
+		return -1;
 	while(count > 0){
 		/*
 		 * Max is 128 sectors (64kB) because prd->count is 16 bits.
 		 */
-		max = 128;
 		n = count;
-		if(n > max)
-			n = max;
-		if((drive->edma->ctl&eEnEDMA) == 0)
+		if(n > 128)
+			n = 128;
+		ilock(d);
+		if((d->edma->ctl&eEnEDMA) == 0 && edmaenable(d) == -1){
+			iunlock(d);
 			goto tryagain;
-		srb = srbrw(cmd[0]==0x28 ? SRBread : SRBwrite, drive, data, n, lba);
-		ilock(drive);
-		startsrb(drive, srb);
-		iunlock(drive);
+		}
+		srb = srbrw(write, d, data, n, lba);
+		startsrb(d, srb);
+		iunlock(d);
 
-		/* Don't let user interrupt DMA. */
 		while(waserror())
 			;
 		sleep(srb, srbdone, srb);
@@ -1690,30 +1546,279 @@
 		flag = srb->flag;
 		freesrb(srb);
 		if(flag == 0){
-tryagain:
-			if(++try == 10){
-				print("%s: bad disk\n", drive->unit->name);
-				return SDeio;
+	tryagain:		if(++try == 10){
+				print("%s: bad disk\n", dnam(d));
+				return -1;
 			}
-			dprint("%s: retry\n", drive->unit->name);
-			if(!waserror()){
-				tsleep(&up->sleep, return0, 0, 1000);
-				poperror();
-			}
+			dprint("%s: retry\n", dnam(d));
 			goto retry;
 		}
 		if(flag & SFerror){
-			print("%s: i/o error\n", drive->unit->name);
-			return SDeio;
+			print("%s: i/o error\n", dnam(d));
+			return -1;
 		}
 		count -= n;
 		lba += n;
-		data += n*unit->secsize;
+		data += n*u->secsize;
 	}
-	r->rlen = data - (uchar*)r->data;
+	return data - (uchar*)a;
+}
+
+static int
+mv50rio(SDreq *r)
+{
+	int count, n, status, rw;
+	uvlong lba;
+	Ctlr *ctlr;
+	Drive *d;
+	SDunit *unit;
+
+	unit = r->unit;
+	ctlr = unit->dev->ctlr;
+	d = &ctlr->drive[unit->subno];
+
+	if((status = sdfakescsi(r)) != SDnostatus)
+		return r->status = status;
+	if((status = sdfakescsirw(r, &lba, &count, &rw)) == SDcheck)
+		return status;
+	n = mv50bio(r->unit, r->lun, rw, r->data, count, lba);
+	if(n == -1)
+		return SDeio;
+	r->rlen = n;
 	return SDok;
 }
 
+static void
+mkrfis(SDreq *r, Drive *d, Edma *e)
+{
+	uchar *u;
+
+	u = r->cmd;
+	u[Ftype] = 0x34;
+	u[Fioport] = 0;
+	if((d->feat & Dllba) && (r->ataproto & P28) == 0){
+		u[Frerror] = e->err;
+		u[Fsc8] = e->seccnt;
+		u[Fsc] = e->seccnt;
+		u[Flba24] = e->lba0;
+		u[Flba0] = e->lba0;
+		u[Flba32] = e->lba1;
+		u[Flba8] = e->lba1;
+		u[Flba40] = e->lba2;
+		u[Flba16] = e->lba2;
+		u[Fdev] = e->lba3;
+		u[Fstatus] = e->cmdstat;
+	}else{
+		u[Frerror] = e->err;
+		u[Fsc] = e->seccnt;
+		u[Flba0] = e->lba0;
+		u[Flba8] = e->lba1;
+		u[Flba16] = e->lba2;
+		u[Fdev] = e->lba3;
+		u[Fstatus] = e->cmdstat;
+	}
+}
+
+static int
+piocmd(SDreq *r, Drive *d)
+{
+	uchar *p, *c;
+	int n, nsec, i, err;
+	Edma *e;
+	SDunit *u;
+
+	u = r->unit;
+
+	if(waitready(d) != SDok)
+		return SDeio;
+	nsec = 0;
+	if(u->secsize != 0)
+		nsec = r->dlen / u->secsize;
+	if(r->dlen < nsec*u->secsize)
+		nsec = r->dlen/u->secsize;
+	if(nsec > 256)
+		error("can't do more than 256 sectors");
+
+	ilock(d);
+	e = d->edma;
+	if(edmadisable(d, 0) == -1) {
+		iunlock(d);
+		error("can't disable edma");
+	}
+	n = satawait(&e->cmdstat, ATAdrdy|ATAbusy, ATAdrdy, 3*1000);
+	if(n == 0) {
+print("piocmd: notready %.2ux\n", e->cmdstat);
+		iunlock(d);
+		return sdsetsense(r, SDcheck, 4, 8, 0);
+	}
+	c = r->cmd;
+	if(r->ataproto & P28){
+		e->altstat = ATAeIEN;
+		e->seccnt = c[Fsc];
+		e->err = c[Ffeat];
+		e->lba0 = c[Flba0];
+		e->lba1 = c[Flba8];
+		e->lba2 = c[Flba16];
+		e->lba3 = c[Fdev];
+		e->cmdstat = c[Fcmd];
+	}else{
+		e->altstat = ATAeIEN;
+		e->seccnt = c[Fsc8];
+		e->seccnt = c[Fsc];
+		e->err = c[Ffeat];
+		e->lba0 = c[Flba24];
+		e->lba0 = c[Flba0];
+		e->lba1 = c[Flba32];
+		e->lba1 = c[Flba8];
+		e->lba1 = c[Flba40];
+		e->lba2 = c[Flba16];
+		e->lba3 = c[Fdev];
+		e->cmdstat = c[Fcmd];
+	}
+	err = 0;
+
+	if((r->ataproto & Pdatam) == Pnd)
+		n = satawait(&e->cmdstat, ATAbusy, 0, 3*1000);
+	else
+		n = satawait(&e->cmdstat, ATAbusy|ATAdrq, ATAdrq, 3*1000);
+	if(n == 0 || e->cmdstat & ATAerr){
+		err = 1;
+		goto lose;
+	}
+	p = r->data;
+	for(; nsec > 0; nsec--)
+		for (i = 0; i < u->secsize; i += 2) {
+			n = satawait(&e->cmdstat, ATAbusy|ATAdrq, ATAdrq, 300);
+			if (n == 0) {
+				d->state = Dreset;
+				err = 1;
+				goto lose;
+			}
+			if(r->ataproto & Pout){
+				n = (ushort)p[i + 1] << 8;
+				e->pio = n | p[i];
+			} else {
+				n = e->pio;
+				p[i] = n;
+				p[i + 1] = n >> 8;
+			}
+			microdelay(1);
+		}
+lose:
+	if(nsec == 0)
+		r->rlen = r->dlen;
+	mkrfis(r, d, e);
+	iunlock(d);
+	if(err)
+		return sdsetsense(r, SDcheck, 4, 8, 0);
+	else
+		return sdsetsense(r, SDok, 0, 0, 0);
+}
+
+/*
+ * hack to allow udma mode to be set or unset
+ * via direct ata command.  it would be better
+ * to move the assumptions about dma mode out
+ * of some of the helper functions.
+ */
+static int
+isudm(SDreq *r)
+{
+	uchar *c;
+
+	c = r->cmd;
+	if(c[Fcmd] == 0xef && c[Ffeat] == 0x03){
+		if(c[Fsc]&0x40)
+			return 1;
+		return -1;
+	}
+	return 0;
+}
+static int
+fisreqchk(Sfis *f, SDreq *r)
+{
+	if((r->ataproto & Pprotom) == Ppkt)
+		return SDnostatus;
+	/*
+	 * handle oob requests;
+	 *    restrict & sanitize commands
+	 */
+	if(r->clen != 16)
+		error(Eio);
+	if(r->cmd[0] == 0xf0){
+		sigtofis(f, r->cmd);
+		r->status = SDok;
+		return SDok;
+	}
+	r->cmd[0] = 0x27;
+	r->cmd[1] = 0x80;
+	r->cmd[7] |= 0xa0;
+	return SDnostatus;
+}
+
+static int
+badf(SDreq *r, Drive*)
+{
+print("badf %.2ux %2ux\n", r->cmd[2], r->ataproto);
+	return sdsetsense(r, SDcheck, 2, 24, 0);
+}
+
+static int
+ataio0(SDreq *r, Drive *d)
+{
+	int (*f)(SDreq*, Drive*);
+
+	f = badf;
+	switch(r->ataproto & Pprotom){
+	default:
+		break;
+	case Ppio:
+	case Pnd:
+		f = piocmd;
+		break;
+	}
+	return f(r, d);
+}
+
+static int
+mv50ata(SDreq *r)
+{
+	int status, udm;
+	Ctlr *c;
+	Drive *d;
+	SDunit *u;
+
+	u = r->unit;
+	c = u->dev->ctlr;
+	d = c->drive + u->subno;
+	if((status = fisreqchk(d, r)) != SDnostatus)
+		return status;
+	udm = isudm(r);
+	USED(udm);		/* botch */
+
+//	qlock(d);
+	if(waserror()){
+//		qunlock(d);
+		nexterror();
+	}
+retry:
+	switch(status = ataio0(r, d)){
+	default:
+		dprint("%s: status %d\n", dnam(d), status);
+		break;
+	case SDretry:
+		dprint("%s: retry\n", dnam(d));
+		goto retry;
+	case SDok:
+		sdsetsense(r, SDok, 0, 0, 0);
+		break;
+	}
+	poperror();
+//	qunlock(d);
+	return r->status = status;
+}
+
+
 SDifc sdmv50xxifc = {
 	"mv50xx",			/* name */
 
@@ -1725,13 +1830,15 @@
 	mv50verify,			/* verify */
 	mv50online,			/* online */
 	mv50rio,				/* rio */
-	mv50rctl,			/* rctl */
+	mv50rctl,				/* rctl */
 	mv50wctl,			/* wctl */
 
-	scsibio,			/* bio */
+	mv50bio,				/* bio */
 	nil,				/* probe */
-	mv50clear,			/* clear */
-	mv50rtopctl,			/* rtopctl */
+	nil,				/* clear */
+	nil,				/* rtopctl */
+	nil,
+	mv50ata,
 };
 
 /*
--- a/sys/src/9/pc/sdmylex.c
+++ b/sys/src/9/pc/sdmylex.c
@@ -840,7 +840,6 @@
 	Ctlr *ctlr;
 	uchar cmd[6], data[256];
 	int clen, dlen, timeo;
-	static int count;
 
 	if(ioalloc(port, 0x3, 0, "mylex") < 0)
 		return nil;
@@ -894,18 +893,13 @@
 	if(issue(ctlr, cmd, clen, data, dlen)){
 		if(data[0] == 'E')
 			ctlr->bus = 32;
+		print("mylex ctlr @ port 0x%ux: 32-bit ", ctlr->port);
 		ctlr->wide = data[0x0D] & 0x01;
-		/*
-		 * devsd doesn't pass us the `spec' argument, so
-		 * we'll assume that sd0 goes to the first scsi host
-		 * adapter found, etc.
-		 */
-		print("#S/sd%d: mylex SCSI: port 0x%ux: %d-bit, ",
-			count++, ctlr->port, ctlr->bus);
 		if (ctlr->wide)
-			print("wide\n");
+			print("wide ");
 		else
-			print("narrow\n");
+			print("narrow ");
+		print("SCSI host adapter\n");
 	}
 	else{
 		/*
@@ -1187,8 +1181,9 @@
 		cmd[1] = 1;
 		if(!issue(ctlr, cmd, 2, 0, 0)) {
 			ctlr->wide = 0;
-			print("mylex32enable: port 0x%ux: scsi wide-mode setup "
-				"failed on wide host adapter", ctlr->port);
+			print(
+"mylex32enable: ctlr @ port 0x%ux: scsi wide-mode setup failed on wide host adapter",
+				ctlr->port);
 		}
 	}
 
--- /dev/null
+++ b/sys/src/9/pc/sdodin.c
@@ -1,0 +1,2841 @@
+/*
+ * marvell odin ii 88se64xx sata/sas controller
+ * copyright © 2009 erik quanstrom
+ * coraid, inc.
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/sd.h"
+#include <fis.h>
+#include "../port/led.h"
+
+#define	dprint(...)	if(debug)	print(__VA_ARGS__); else USED(debug)
+#define	idprint(...)	if(idebug)	print(__VA_ARGS__); else USED(idebug)
+#define	aprint(...)	if(adebug)	print(__VA_ARGS__); else USED(adebug)
+#define	Pciwaddrh(a)	0
+#define	Pciw64(x)	(uvlong)PCIWADDR(x)
+#define	Ticks		MACHP(0)->ticks
+
+/* copied from sdiahci */
+enum {
+	Dnull		= 0,
+	Dmissing		= 1<<0,
+	Dnopower	= 1<<1,
+	Dnew		= 1<<2,
+	Dready		= 1<<3,
+	Derror		= 1<<4,
+	Dreset		= 1<<5,
+	Doffline		= 1<<6,
+	Dportreset	= 1<<7,
+	Dlast		= 9,
+};
+
+static char *diskstates[Dlast] = {
+	"null",
+	"missing",
+	"nopower",
+	"new",
+	"ready",
+	"error",
+	"reset",
+	"offline",
+	"portreset",
+};
+
+static char *type[] = {
+	"offline",
+	"sas",
+	"sata",
+};
+
+enum{
+	Nctlr		= 4,
+	Nctlrdrv		= 8,
+	Ndrive		= Nctlr*Nctlrdrv,
+	Mbar		= 2,
+	Mebar		= 4,
+	Nqueue		= 32,		/* cmd queue size */
+	Qmask		= Nqueue - 1,
+	Nregset		= 8,
+	Rmask		= 0xffff,
+	Nms		= 256,		/* drive check rate */
+
+	Sas		= 1,
+	Sata,
+
+	/* cmd bits */
+	Error		= 1<<31,
+	Done		= 1<<30,
+	Noverdict	= 1<<29,
+	Creset		= 1<<28,
+	Atareset		= 1<<27,
+	Sense		= 1<<26,
+	Timeout		= 1<<25,
+	Response	= 1<<24,
+	Active		= 1<<23,
+
+	/* pci registers */
+	Phy0		= 0x40,
+	Gpio		= 0x44,
+	Phy1		= 0x90,
+	Gpio1		= 0x94,
+	Dctl		= 0xe8,
+
+	/* phy offests */
+	Phydisable	= 1<<12,
+	Phyrst		= 1<<16,
+	Phypdwn		= 1<<20,
+	Phyen		= 1<<24,
+
+	/* bar4 registers */
+	Gctl		= 0x004/4,
+	Gis		= 0x008/4,	/* global interrupt status */
+	Pi		= 0x00c/4,	/* ports implemented */
+	Flashctl		= 0x030/4,	/* spi flash control */
+	Flashcmd	= 0x034/4,	/* flash wormhole */
+	Flashdata	= 0x038/4,
+	I²cctl		= 0x040/4,	/* i²c control */
+	I²ccmd		= 0x044/4,
+	I²cdata		= 0x048/4,
+	Ptype		= 0x0a0/4,	/* 15:8 auto detect enable; 7:0 sas=1. sata=0 */
+	Portcfg0		= 0x100/4,	/* 31:16 register sets 31:16 */
+	Portcfg1		= 0x104/4,	/* 31:16 register sets 15:8 tx enable; 7 rx enable */
+	Clbase		= 0x108/4,	/* cmd list base; 64 bits */
+	Fisbase		= 0x110/4,	/* 64 bits */
+	Dqcfg		= 0x120/4,	/* bits 11:0 specify size */
+	Dqbase		= 0x124/4,
+	Dqwp		= 0x12c/4,	/* delivery queue write pointer */
+	Dqrp		= 0x130/4,
+	Cqcfg		= 0x134/4,
+	Cqbase		= 0x138/4,
+	Cqwp		= 0x140/4,	/* hw */
+	Coal		= 0x148/4,
+	Coalto		= 0x14c/4,	/* coal timeout µs */
+	Cis		= 0x150/4,	/* centeral irq status */
+	Cie		= 0x154/4,	/* centeral irq enable */
+	Csis		= 0x158/4,	/* cmd set irq status */
+	Csie		= 0x15c/4,
+	Cmda		= 0x1b8/4,
+	Cmdd		= 0x1bc/4,
+	Gpioa		= 0x270/4,
+	Gpiod		= 0x274/4,
+	Gpiooff		= 0x100,		/* second gpio offset */
+
+	/* port conf registers; mapped through wormhole */
+	Pinfo		= 0x000,
+	Paddr		= 0x004,
+	Painfo		= 0x00c,		/* attached device info */
+	Pawwn		= 0x010,
+	Psatactl		= 0x018,
+	Pphysts		= 0x01c,
+	Psig		= 0x020,		/* 16 bytes */
+	Perr		= 0x030,
+	Pcrcerr		= 0x034,
+	Pwidecfg		= 0x038,
+	Pwwn		= 0x080,		/* 12 wwn + ict */
+
+	/* port cmd registers; mapped through “cmd” wormhole */
+	Ci		= 0x040,		/* cmd active (16) */
+	Task		= 0x080,
+	Rassoc		= 0x0c0,
+	Pfifo0		= 0x1a8,
+	Pfifo1		= 0x1c4,
+	Pwdtimer	= 0x13c,
+
+	/* “vendor specific” wormhole */
+	Phymode	= 0x001,
+
+	/* gpio wormhole */
+	Sgconf0		= 0x000,
+	Sgconf1		= 0x004,
+	Sgclk		= 0x008,
+	Sgconf3		= 0x00c,
+	Sgis		= 0x010,		/* interrupt set */
+	Sgie		= 0x014,		/* interrupt enable */
+	Drivesrc		= 0x020,		/* 4 drives/register; 4 bits/drive */
+	Drivectl		= 0x038,		/* same deal */
+
+	/* Gctl bits */
+	Reset		= 1<<0,
+	Intenable	= 1<<1,
+
+	/* Portcfg0/1 bits */
+	Regen		= 1<<16,	/* enable sata regsets 31:16 or 15:0 */
+	Xmten		= 1<<8,	/* enable port n transmission */
+	Dataunke	= 1<<3,
+	Rsple		= 1<<2,	/* response frames in le format */
+	Oabe		= 1<<1,	/* oa frame in big endian format */
+	Framele		= 1<<0,	/* frame contents in le format */
+
+	Allresrx		= 1<<7,	/* receive all responses */
+	Stpretry		= 1<<6,
+	Cmdirq		= 1<<5,	/* 1 == self clearing */
+	Fisen		= 1<<4,	/* enable fis rx */
+	Errstop		= 1<<3,	/* set -> stop on ssp/smp error */
+	Resetiss		= 1<<1,	/* reset cmd issue; self clearing */
+	Issueen		= 1<<0,
+
+	/* Dqcfg bits */
+	Dqen		= 1<<16,
+
+	/* Cqcfg bits */
+	Noattn		= 1<<17,	/* don't post entries with attn bit */
+	Cqen		= 1<<16,
+
+	/* Cis bits */
+	I2cirq		= 1<<31,
+	Swirq1		= 1<<30,
+	Swirq0		= 1<<29,
+	Prderr		= 1<<28,
+	Dmato		= 1<<27,
+	Parity		= 1<<28,	/* parity error; fatal */
+	Slavei2c		= 1<<25,
+	Portstop		= 1<<16,	/* bitmapped */
+	Portirq		= 1<<8,	/* bitmapped */
+	Srsirq		= 1<<3,
+	Issstop		= 1<<1,
+	Cdone		= 1<<0,
+	Iclr		= Swirq1 | Swirq0,
+
+	/* Pis bits */
+	Caf		= 1<<29,	/* clear affiliation fail */
+	Sync		= 1<<25,	/* sync during fis rx */
+	Phyerr		= 1<<24,
+	Stperr		= 1<<23,
+	Crcerr		= 1<<22,
+	Linktx		= 1<<21,
+	Linkrx		= 1<<20,
+	Martianfis	= 1<<19,
+	Anot		= 1<<18,	/* async notification */
+	Bist		= 1<<17,
+	Sigrx		= 1<<16,	/* native sata signature rx */
+	Phyunrdy	= 1<<12,	/* phy went offline*/
+	Uilong		= 1<<11,
+	Uishort		= 1<<10,
+	Martiantag	= 1<<9,
+	Bnot		= 1<<8,	/* broadcast noticication */
+	Comw		= 1<<7,
+	Portsel		= 1<<6,
+	Hreset		= 1<<5,
+	Phyidto		= 1<<4,
+	Phyidfail		= 1<<3,
+	Phyidok		= 1<<2,
+	Hresetok		= 1<<1,
+	Phyrdy		= 1<<0,
+
+	Pisataup		= Phyrdy | Comw | Sigrx,
+	Pisasup		= Phyrdy | Comw | Phyidok,
+	Piburp		= Sync | Phyerr | Stperr | Crcerr | Linktx |
+				Linkrx | Martiantag,
+	Pireset		= Phyidfail | Bnot | Phyunrdy | Bist |
+				Anot | Martianfis | Bist | Phyidto |
+				Hreset,
+	Piunsupp	= Portsel,
+
+	/* Psc bits */
+	Sphyrdy		= 1<<20,
+	Linkrate		= 1<<18,	/* 4 bits */
+	Maxrate		= 1<<12,
+	Minrate		= 1<<8,
+	Sreset		= 1<<3,
+	Sbnote		= 1<<2,
+	Shreset		= 1<<1,
+	Sphyrst		= 1<<0,
+
+	/* Painfo bits */
+	Issp		= 1<<19,
+	Ismp		= 1<<18,
+	Istp		= 1<<17,
+	Itype		= 1<<0,	/* two bits */
+
+	/* Psatactl bits */
+	Powerctl		= 1<<30,	/* 00 wake; 10 partial 01 slumb */
+	Srst		= 1<<29,	/* soft reset */
+	Power		= 1<<28,
+	Sportsel		= 1<<24,
+	Dmahon		= 1<<22,
+	Srsten		= 1<<20,
+	Dmaxfr		= 1<<18,
+
+	/* phy status bits */
+	Phylock		= 1<<9,
+	Nspeed		= 1<<4,
+	Psphyrdy		= 1<<2,
+
+	/* Task bits; modeled after ahci */
+	Eestatus		= 0xff<<24,
+	Asdbs		= 1<<18,
+	Apio		= 1<<17,
+	Adhrs		= 1<<16,
+	Eerror		= 0xff<<8,
+	Estatus		= 0xff,
+
+	/* Phymode bits */
+	Pmnotify		= 1<<24,
+	Pmnotifyen	= 1<<23,
+
+	/* Sgconf0 bits */
+	Autolen		= 1<<24,	/* 8 bits */
+	Manlen		= 1<<16,	/* 8 bits */
+	Sdincapt		= 1<<8,	/* capture sdatain on + edge */
+	Sdoutch		= 1<<7,	/* change sdataout on - edge
+	Sldch		= 1<<6,	/* change sload on - edge
+	Sdoutivt		= 1<<5,	/* invert sdataout polarity */
+	Ldivt		= 1<<4,
+	Sclkivt		= 1<<3,
+	Blinkben		= 1<<2,	/* enable blink b */
+	Blinkaen		= 1<<1,	/* enable blink a */
+	Sgpioen		= 1<<0,
+
+	/* Sgconf1 bits; 4 bits each */
+	Sactoff		= 1<<28,	/* stretch activity off; 0/64 - 15/64 */
+	Sacton		= 1<<24,	/* 1/64th - 16/64 */
+	Factoff		= 1<<20,	/* 0/8 - 15/8; default 1 */
+	Facton		= 1<<16,	/* 0/4 - 15/4; default 2 */
+	Bhi		= 1<<12,	/* 1/8 - 16/8 */
+	Blo		= 1<<8,	/* 1/8 - 16/8 */
+	Ahi		= 1<<4,	/* 1/8 - 16/8 */
+	Alo		= 1<<0,	/* 1/8 - 16/8 */
+
+	/* Sgconf3 bits */
+	Autopat		= 1<<20,	/* 4 bits of start pattern */
+	Manpat		= 1<<16,
+	Manrep		= 1<<4,	/* repeats; 7ff ≡ ∞ */
+	Sdouthalt	= 0<<2,
+	Sdoutman	= 1<<2,
+	Sdoutauto	= 2<<2,
+	Sdoutma		= 3<<2,
+	Sdincapoff	= 0<<0,
+	Sdinoneshot	= 1<<0,
+	Sdinrep		= 2<<0,
+
+	/* Sgie Sgis bits */
+	Sgreprem	= 1<<8,	/* 12 bits; not irq related */
+	Manrep0		= 1<<1,	/* write 1 to clear */
+	Capdone	= 1<<0,	/* capture done */
+
+	/* drive control bits (repeated 4x per drive) */
+	Aled		= 1<<5,	/* 3 bits */
+	Locled		= 1<<3,	/* 2 bits */
+	Errled		= 1<<0,	/* 3 bits */
+	Llow		= 0,
+	Lhigh		= 1,
+	Lblinka		= 2,
+	Lblinkaneg	= 3,
+	Lsof		= 4,
+	Leof		= 5,
+	Lblinkb		= 6,
+	Lblinkbneg	= 7,
+
+	/* cmd queue bits */
+	Dssp		= 1<<29,
+	Dsmp		= 2<<29,
+	Dsata		= 3<<29,	/* also stp */
+	Ditor		= 1<<28,	/* initiator */
+	Dsatareg		= 1<<20,
+	Dphyno		= 1<<12,
+	Dcslot		= 1,
+
+	/* completion queue bits */
+	Cgood		= 1<<23,	/* ssp only */
+	Cresetdn		= 1<<21,
+	Crx		= 1<<20,	/* target mode */
+	Cattn		= 1<<19,
+	Crxfr		= 1<<18,
+	Cerr		= 1<<17,
+	Cqdone		= 1<<16,
+	Cslot		= 1<<0,	/* 12 bits */
+
+	/* error bits — first word */
+	Eissuestp	= 1<<31,	/* cmd issue stopped */
+	Epi		= 1<<30,	/* protection info error */
+	Eoflow		= 1<<29,	/* buffer overflow */
+	Eretry		= 1<<28,	/* retry limit exceeded */
+	Eufis		= 1<<27,
+	Edmat		= 1<<26,	/* dma terminate */
+	Esync		= 1<<25,	/* sync rx during tx */
+	Etask		= 1<<24,
+	Ererr		= 1<<23,	/* r error received */
+
+	Eroff		= 1<<20,	/* read data offset error */
+	Exoff		= 1<<19,	/* xfer rdy offset error */
+	Euxr		= 1<<18,	/* unexpected xfer rdy */
+	Exflow		= 1<<16,	/* buffer over/underflow */
+	Elock		= 1<<15,	/* interlock error */
+	Enak		= 1<<14,
+	Enakto		= 1<<13,
+	Enoak		= 1<<12,	/* conn closed wo nak */
+	Eopento		= 1<<11,	/* open conn timeout */
+	Epath		= 1<<10,	/* open reject - path blocked */
+	Enodst		= 1<<9,	/* open reject - no dest */
+	Estpbsy		= 1<<8,	/* stp resource busy */
+	Ebreak		= 1<<7,	/* break while sending */
+	Ebaddst		= 1<<6,	/* open reject - bad dest */
+	Ebadprot	= 1<<5,	/* open reject - proto not supp */
+	Erate		= 1<<4,	/* open reject - rate not supp */
+	Ewdest		= 1<<3,	/* open reject - wrong dest */
+	Ecreditto	= 1<<2,	/* credit timeout */
+	Edog		= 1<<1,	/* watchdog timeout */
+	Eparity		= 1<<0,	/* buffer parity error */
+
+	/* sas ctl cmd header bits */
+	Ssptype		= 1<<5,	/* 3 bits */
+	Ssppt		= 1<<4,	/* build your own header *.
+	Firstburst	= 1<<3,	/* first burst */
+	Vrfylen		= 1<<2,	/* verify length */
+	Tlretry		= 1<<1,	/* transport layer retry */
+	Piren		= 1<<0,	/* pir present */
+
+	/* sata ctl cmd header bits */
+	Lreset		= 1<<7,
+	Lfpdma		= 1<<6,	/* first-party dma.  (what's that?) */
+	Latapi		= 1<<5,
+	Lpm		= 1<<0,	/* 4 bits */
+
+	Sspcmd		= 0*Ssptype,
+	Ssptask		= 1*Ssptype,
+	Sspxfrdy		= 4*Ssptype,
+	Ssprsp		= 5*Ssptype,
+	Sspread		= 6*Ssptype,
+	Sspwrite		= 7*Ssptype,
+};
+
+/* following ahci */
+typedef struct {
+	ulong	dba;
+	ulong	dbahi;
+	ulong	pad;
+	ulong	count;
+} Aprdt;
+
+typedef struct {
+	union{
+		struct{
+			uchar	cfis[0x40];
+			uchar	atapi[0x20];
+		};
+		struct{
+			uchar	mfis[0x40];
+		};
+		struct{
+			uchar	sspfh[0x18];
+			uchar	sasiu[0x40];
+		};
+	};
+} Ctab;
+
+/* protection information record */
+typedef struct {
+	uchar	ctl;
+	uchar	pad;
+	uchar	size[2];
+	uchar	rtag[4];
+	uchar	atag[2];
+	uchar	mask[2];
+} Pir;
+
+/* open address frame */
+typedef struct {
+	uchar	oaf[0x28];
+	uchar	fb[4];
+} Oaf;
+
+/* status buffer */
+typedef struct {
+	uchar	error[8];
+	uchar	rsp[0x400];
+} Statb;
+
+typedef struct {
+	uchar	satactl;
+	uchar	sasctl;
+	uchar	len[2];
+
+	uchar	fislen[2];
+	uchar	maxrsp;
+	uchar	d0;
+
+	uchar	tag[2];
+	uchar	ttag[2];
+
+	uchar	dlen[4];
+	uchar	ctab[8];
+	uchar	oaf[8];
+	uchar	statb[8];
+	uchar	prd[8];
+
+	uchar	d3[16];
+} Cmdh;
+
+typedef struct Cmd Cmd;
+struct Cmd {
+	Rendez;
+	uint	cflag;
+
+	Cmdh	*cmdh;
+	Ctab;
+	Oaf;
+	Statb;
+	Aprdt;
+};
+
+typedef struct Drive Drive;
+typedef struct Ctlr Ctlr;
+
+struct Drive {
+	Lock;
+	QLock;
+	Ctlr	*ctlr;
+	SDunit	*unit;
+	char	name[16];
+
+	Cmd	*cmd;
+
+	/* sdscsi doesn't differentiate drivechange/mediachange */
+	uchar	drivechange;
+	uchar	state;
+	uchar	type;
+	ushort	info[0x100];
+
+	Sfis;	/* sata and media info*/
+	Cfis;	/* sas and media info */
+	Ledport;	/* led */
+
+	/* hotplug info */
+	uint	lastseen;
+	uint	intick;
+	uint	wait;
+
+	char	serial[20+1];
+	char	firmware[8+1];
+	char	model[40+1];
+	uvlong	wwn;
+	uvlong	sectors;
+	uint	secsize;
+
+	uint	driveno;
+};
+
+struct Ctlr {
+	Lock;
+	uchar	enabled;
+	SDev	*sdev;
+	Pcidev	*pci;
+	uint	*reg;
+
+	uint	dq[Nqueue];
+	uint	dqwp;
+	uint	cq[Nqueue + 1];
+	uint	cqrp;
+	Cmdh	*cl;
+	uchar	*fis;
+	Cmd	*cmdtab;
+
+	Drive	drive[Nctlrdrv];
+	uint	ndrive;
+};
+
+static	Ctlr	msctlr[Nctlr];
+static	SDev	sdevs[Nctlr];
+static	uint	nmsctlr;
+static	Drive	*msdrive[Ndrive];
+static	uint	nmsdrive;
+static	int	debug=0;
+static	int	idebug=1;
+static	int	adebug;
+static	uint	 olds[Nctlr*Nctlrdrv];
+	SDifc	sdodinifc;
+
+/* a good register is hard to find */
+static	int	pis[] = {
+	0x160/4, 0x168/4, 0x170/4, 0x178/4,
+	0x200/4, 0x208/4, 0x210/4, 0x218/4,
+};
+static	int	pcfg[] = {
+	0x1c0/4, 0x1c8/4, 0x1d0/4, 0x1d8/4,
+	0x230/4, 0x238/4, 0x240/4, 0x248/4,
+};
+static	int	psc[] = {
+	0x180/4, 0x184/4, 0x188/4, 0x18c/4,
+	0x220/4, 0x224/4, 0x228/4, 0x22c/4,
+};
+static	int	vscfg[] = {
+	0x1e0/4, 0x1e8/4, 0x1f0/4, 0x1f8/4,
+	0x250/4, 0x258/4, 0x260/4, 0x268/4,
+};
+#define	sstatus(d)	(d)->ctlr->reg[psc[(d)->driveno]]
+
+static char*
+dstate(uint s)
+{
+	int i;
+
+	for(i = 0; s; i++)
+		s >>= 1;
+	return diskstates[i];
+}
+
+static char*
+dnam(Drive *d)
+{
+	if(d->unit)
+		return d->unit->name;
+	return d->name;
+}
+
+static uvlong border = 0x0001020304050607ull;
+static uvlong lorder = 0x0706050403020100ull;
+
+static uvlong
+getle(uchar *t, int w)
+{
+	uint i;
+	uvlong r;
+
+	r = 0;
+	for(i = w; i != 0; )
+		r = r<<8 | t[--i];
+	return r;
+}
+
+static void
+putle(uchar *t, uvlong r, int w)
+{
+	uchar *o, *f;
+	uint i;
+
+	f = (uchar*)&r;
+	o = (uchar*)&lorder;
+	for(i = 0; i < w; i++)
+		t[o[i]] = f[i];
+}
+
+static uvlong
+getbe(uchar *t, int w)
+{
+	uint i;
+	uvlong r;
+
+	r = 0;
+	for(i = 0; i < w; i++)
+		r = r<<8 | t[i];
+	return r;
+}
+
+static void
+putbe(uchar *t, uvlong r, int w)
+{
+	uchar *o, *f;
+	uint i;
+
+	f = (uchar*)&r;
+	o = (uchar*)&border + (sizeof border-w);
+	for(i = 0; i < w; i++)
+		t[i] = f[o[i]];
+}
+
+static int phyrtab[] = {Phy0, Phy1};
+static void
+phyenable(Ctlr *c, Drive *d)
+{
+	uint i, u, reg, m;
+
+	i = d->driveno;
+	reg = phyrtab[i > 3];
+	i &= 3;
+	i = 1<<i;
+	u = pcicfgr32(c->pci, reg);
+	m = i*(Phypdwn | Phydisable | Phyen);
+	if((u & m) == Phyen)
+		return;
+	m = i*(Phypdwn | Phydisable);
+	u &= ~m;
+	u |= i*Phyen;
+	pcicfgw32(c->pci, reg, u);
+}
+
+static void
+regtxreset(Drive *d)
+{
+	uint i, u, m;
+	Ctlr *c = d->ctlr;
+
+	i = d->driveno;
+	u = c->reg[Portcfg1];
+	m = (Regen|Xmten)<<i;
+	u &= ~m;
+	c->reg[Portcfg1] = u;
+	delay(1);
+	c->reg[Portcfg1] = u | m;
+}
+
+/* aka comreset? */
+static void
+phyreset(Drive *d)
+{
+	uint i, u, reg;
+	Ctlr *c;
+
+	c = d->ctlr;
+	phyenable(c, d);
+
+	i = d->driveno;
+	reg = phyrtab[i > 3];
+	i &= 3;
+	i = 1<<i;
+	u = pcicfgr32(c->pci, reg);
+	pcicfgw32(c->pci, reg, u | i*Phyrst);
+	delay(5);
+	pcicfgw32(c->pci, reg, u);
+
+	sstatus(d) |= Shreset;
+	while(sstatus(d) & Shreset);
+		;
+}
+
+static void
+reset(Drive *d)
+{
+	regtxreset(d);
+	phyreset(d);
+}
+
+/*
+ * sata/sas register reads through wormhole
+ */
+static uint
+ssread(Ctlr *c, int port, uint r)
+{
+	c->reg[Cmda] = r + 4*port;
+	return c->reg[Cmdd];
+}
+
+static void
+sswrite(Ctlr *c, int port, int r, uint u)
+{
+	c->reg[Cmda] = r + 4*port;
+	c->reg[Cmdd] = u;
+}
+
+/*
+ * port configuration r/w through wormhole
+ */
+static uint
+pcread(Ctlr *c, uint port, uint r)
+{
+	c->reg[pcfg[port]] = r;
+	return c->reg[pcfg[port] + 1];
+}
+
+static void
+pcwrite(Ctlr *c, uint port, uint r, uint u)
+{
+	c->reg[pcfg[port] + 0] = r;
+	c->reg[pcfg[port] + 1] = u;
+}
+
+/*
+ * vendor specific r/w through wormhole
+ */
+static uint
+vsread(Ctlr *c, uint port, uint r)
+{
+	c->reg[vscfg[port]] = r;
+	return c->reg[vscfg[port] + 1];
+}
+
+static void
+vswrite(Ctlr *c, uint port, uint r, uint u)
+{
+	c->reg[vscfg[port] + 0] = r;
+	c->reg[vscfg[port] + 1] = u;
+}
+
+/*
+ * gpio wormhole
+ */
+static uint
+gpread(Ctlr *c, uint r)
+{
+	c->reg[Gpioa] = r;
+	return c->reg[Gpiod];
+}
+
+static void
+gpwrite(Ctlr *c, uint r, uint u)
+{
+	c->reg[Gpioa] = r;
+	c->reg[Gpiod] = u;
+}
+
+static uint*
+getsigfis(Drive *d, uint *fis)
+{
+	uint i;
+
+	for(i = 0; i < 4; i++)
+		fis[i] = pcread(d->ctlr, d->driveno, Psig + 4*i);
+	return fis;
+}
+
+static uint
+getsig(Drive *d)
+{
+	uint fis[4];
+
+	return fistosig((uchar*)getsigfis(d, fis));
+}
+
+static uint
+ci(Drive *d)
+{
+	return ssread(d->ctlr, d->driveno, Ci);
+}
+
+static void
+unsetci(Drive *d)
+{
+	uint i;
+
+	i = 1<<d->driveno;
+	sswrite(d->ctlr, d->driveno, Ci, i);
+	while(ci(d) & i)
+		microdelay(1);
+}
+
+static uint
+gettask(Drive *d)
+{
+	return ssread(d->ctlr, d->driveno, Task);
+}
+
+static void
+tprint(Drive *d, uint t)
+{
+	uint s;
+
+	s = sstatus(d);
+	dprint("%s: err task %ux sstat %ux\n", dnam(d), t, s);
+}
+
+static int
+cmdactive(void *v)
+{
+	Cmd *x;
+
+	x = v;
+	return (x->cflag & Done) != 0;
+}
+
+static int
+mswait(Cmd *x, int ms)
+{
+	uint u, tk0;
+
+	if(up){
+		tk0 = Ticks;
+		while(waserror())
+			;
+		tsleep(x, cmdactive, x, ms);
+		poperror();
+		ms -= TK2MS(Ticks - tk0);
+	}else
+		while(ms-- && cmdactive(x))
+			delay(1);
+//	ilock(cmd->d);
+	u = x->cflag;
+	x->cflag = 0;
+//	iunlock(cmd->d)
+
+	if(u == (Done | Active))
+		return 0;
+	if((u & Done) == 0){
+		u |= Noverdict | Creset | Timeout;
+		print("cmd timeout ms:%d %ux\n", ms, u);
+	}
+	return u;
+}
+
+static void
+setstate(Drive *d, int state)
+{
+	ilock(d);
+	d->state = state;
+	iunlock(d);
+}
+
+static void
+esleep(int ms)
+{
+	if(waserror())
+		return;
+	tsleep(&up->sleep, return0, 0, ms);
+	poperror();
+}
+
+static int
+waitready(Drive *d)
+{
+	ulong s, i, δ;
+
+	for(i = 0; i < 15000; i += 250){
+		if(d->state & (Dreset | Dportreset | Dnew))
+			return 1;
+		δ = Ticks - d->lastseen;
+		if(d->state == Dnull || δ > 10*1000)
+			return -1;
+		ilock(d);
+		s = sstatus(d);
+		iunlock(d);
+		if((s & Sphyrdy) == 0 && δ > 1500)
+			return -1;
+		if(d->state == Dready && (s & Sphyrdy))
+			return 0;
+		esleep(250);
+	}
+	print("%s: not responding; offline: %.8ux\n", dnam(d), sstatus(d));
+	setstate(d, Doffline);
+	return -1;
+}
+
+static int
+lockready(Drive *d)
+{
+	int i, r;
+
+	for(i = 0; ; i++){
+		qlock(d);
+		if((r = waitready(d)) != 1)
+			return r;
+		qunlock(d);
+		if(i == Nms*10)
+			break;
+		esleep(1);
+	}
+	return -1;
+}
+
+static int
+command(Drive *d, uint cmd, int ms)
+{
+	uint s, n, m, i;
+	Ctlr *c;
+
+	c = d->ctlr;
+	i = d->driveno;
+	m = 1<<i;
+	n = cmd | Ditor | i*Dsatareg | m*Dphyno | i*Dcslot;
+//	print("cqwp\t%.8ux : n %ux : d%d; \n", c->cq[0], n, i);
+	/*
+	 * xinc doesn't return the previous value and i can't
+	 * figure out how to do this without a lock
+	 *	s = _xinc(&c->dqwp);
+	 */
+	d->cmd->cflag = Active;
+	ilock(c);
+	s = c->dqwp++;
+	c->dq[s&Qmask] = n;
+	c->reg[Dqwp] = s&Qmask;
+	iunlock(c);
+//	print("	dq slot %d\n", s);
+	d->intick = Ticks;		/* move to mswait? */
+	return mswait(d->cmd, ms);
+}
+
+static int
+buildfis(Drive *d, SDreq *r, void *data, int n)
+{
+	Aprdt *p;
+	Cmd *x;
+	Cmdh *h;
+
+	x = d->cmd;
+	memmove(x->cfis, r->cmd, r->clen);
+
+	h = x->cmdh;
+	memset(h, 0, 16);
+	h->fislen[0] = 5;
+	h->len[0] = 0;
+
+	if(data != nil){
+		h->len[0] = 1;
+		p = x;
+		p->dba = PCIWADDR(data);
+		p->dbahi = Pciwaddrh(data);
+		p->count = n;
+	}
+	return command(d, Dsata, 10*1000);
+}
+
+static int
+build(Drive *d, int rw, void *data, int n, vlong lba)
+{
+	Aprdt *p;
+	Cmd *x;
+	Cmdh *h;
+
+	x = d->cmd;
+	rwfis(d, x->cfis, rw, n, lba);
+
+	h = x->cmdh;
+	memset(h, 0, 16);
+	h->fislen[0] = 5;
+	h->len[0] = 1;			/* one prdt entry */
+
+	p = x;
+	p->dba = PCIWADDR(data);
+	p->dbahi = Pciwaddrh(data);
+	p->count = d->secsize*n;
+
+	return command(d, Dsata, 10*1000);
+}
+
+enum{
+	Rnone	= 1,
+	Rdma	= 0x00,		/* dma setup; length 0x1b */
+	Rpio	= 0x20,		/* pio setup; length 0x13 */
+	Rd2h	= 0x40,		/* d2h register;length 0x13 */
+	Rsdb	= 0x58,		/* set device bits; length 0x08 */
+};
+
+static uint fisotab[8] = {
+[0]	Rnone,
+[1]	Rd2h,
+[2]	Rpio,
+[3]	Rnone,
+[4]	Rsdb,
+[5]	Rnone,
+[6]	Rnone,
+[7]	Rnone,
+};
+
+static uint
+fisoffset(Drive *d, int mustbe)
+{
+	uint t, r;
+
+	t = gettask(d) & 0x70000;
+	r = fisotab[t >> 16];
+	if(r == Rnone || (mustbe != 0 && r != mustbe))
+		return 0;
+	return 0x800 + 0x100*d->driveno + r;
+}
+
+/* need to find a non-atapi-specific way of doing this */
+static uint
+atapixfer(Drive *d, uint n)
+{
+	uchar *u;
+	uint i, x;
+
+	if((i = fisoffset(d, Rd2h)) == 0)
+		return 0;
+	u = d->ctlr->fis + i;
+	x = u[Flba16]<<8 | u[Flba8];
+	if(x > n){
+		x = n;
+		print("%s: atapixfer %ux %ux\n", dnam(d), x, n);
+	}
+	return x;
+}
+
+static int
+buildpkt(Drive *d, SDreq *r, void *data, int n)
+{
+	int rv;
+	Aprdt *p;
+	Cmd *x;
+	Cmdh *h;
+
+	x = d->cmd;
+	atapirwfis(d, x->cfis, r->cmd, r->clen, n);
+
+	h = x->cmdh;
+	memset(h, 0, 16);
+	h->satactl = Latapi;
+	h->fislen[0] = 5;
+	h->len[0] = 1;		/* one prdt entry */
+
+	if(data != nil){
+		p = x;
+		p->dba = PCIWADDR(data);
+		p->dbahi = Pciwaddrh(data);
+		p->count = n;
+	}
+	rv = command(d, Dsata, 10*1000);
+	if(rv == 0)
+		r->rlen = atapixfer(d, n);
+	return rv;
+}
+
+/*
+ * ata 7, required for sata, requires that all devices "support"
+ * udma mode 5,   however sata:pata bridges allow older devices
+ * which may not.  the innodisk satadom, for example allows
+ * only udma mode 2.  on the assumption that actual udma is
+ * taking place on these bridges, we set the highest udma mode
+ * available, or pio if there is no udma mode available.
+ */
+static int
+settxmode(Drive *d, uchar f)
+{
+	Cmd *x;
+	Cmdh *h;
+
+	x = d->cmd;
+	if(txmodefis(d, x->cfis, f) == -1)
+		return 0;
+
+	h = x->cmdh;
+	memset(h, 0, 16);
+	h->fislen[0] = 5;
+
+	return command(d, Dsata, 3*1000);
+}
+
+static int
+setfeatures(Drive *d, uchar f, uint w)
+{
+	Cmd *x;
+	Cmdh *h;
+
+	x = d->cmd;
+	featfis(d, x->cfis, f);
+
+	h = x->cmdh;
+	memset(h, 0, 16);
+	h->fislen[0] = 5;
+
+	return command(d, Dsata, w);
+}
+
+static int
+mvflushcache(Drive *d)
+{
+	Cmd *x;
+	Cmdh *h;
+
+	x = d->cmd;
+	flushcachefis(d, x->cfis);
+
+	h = x->cmdh;
+	memset(h, 0, 16);
+	h->fislen[0] = 5;
+
+	return command(d, Dsata, 60*1000);
+}
+
+static int
+identify0(Drive *d, void *id)
+{
+	Aprdt *p;
+	Cmd *x;
+	Cmdh *h;
+
+	x = d->cmd;
+	identifyfis(d, x->cfis);
+
+	h = x->cmdh;
+	memset(h, 0, 16);
+	h->fislen[0] = 5;
+	h->len[0] = 1;		/* one prdt entry */
+
+	memset(id, 0, 0x200);
+	p = x;
+	p->dba = PCIWADDR(id);
+	p->dbahi = Pciwaddrh(id);
+	p->count = 0x200;
+
+	return command(d, Dsata, 3*1000);
+}
+
+static int
+identify(Drive *d)
+{
+	int i, n;
+	vlong osectors, s;
+	uchar oserial[21];
+	ushort *id;
+	SDunit *u;
+
+	id = d->info;
+	for(i = 0;; i++){
+		if(i > 5 || identify0(d, id) != 0)
+			return -1;
+		n = idpuis(id);
+		if(n & Pspinup && setfeatures(d, 7, 20*1000) == -1)
+			dprint("%s: puis spinup fail\n", dnam(d));
+		if(n & Pidready)
+			break;
+	}
+
+	s = idfeat(d, id);
+	if(s == -1)
+		return -1;
+	if((d->feat&Dlba) == 0){
+		dprint("%s: no lba support\n", dnam(d));
+		return -1;
+	}
+	osectors = d->sectors;
+	memmove(oserial, d->serial, sizeof d->serial);
+
+	d->sectors = s;
+	d->secsize = idss(d, id);
+
+	idmove(d->serial, id+10, 20);
+	idmove(d->firmware, id+23, 8);
+	idmove(d->model, id+27, 40);
+	d->wwn = idwwn(d, id);
+
+	u = d->unit;
+	memset(u->inquiry, 0, sizeof u->inquiry);
+	u->inquiry[2] = 2;
+	u->inquiry[3] = 2;
+	u->inquiry[4] = sizeof u->inquiry - 4;
+	memmove(u->inquiry+8, d->model, 40);
+
+	if(osectors != s || memcmp(oserial, d->serial, sizeof oserial)){
+		d->drivechange = 1;
+		u->sectors = 0;
+	}
+	return 0;
+}
+
+/* open address fises */
+enum{
+	Initiator		= 0x80,
+	Openaddr	= 1,
+	Awms		= 0x8000,
+	Smp		= 0,
+	Ssp		= 1,
+	Stp		= 2,
+	Spd15		= 8,
+	Spd30		= 9,
+};
+
+static void
+oafis(Cfis *f, uchar *c, int type)
+{
+	c[0] = Initiator | type<<4 | Openaddr;
+	c[1] = Spd30;				/* botch; just try 3gbps */
+	if(type == Smp)
+		memset(c + 2, 0xff, 2);
+	else
+		memmove(c + 2, f->ict, 2);
+	memmove(c + 4, f->tsasaddr, 8);		/* dest "port identifier" §4.2.6 */
+	memmove(c + 12, f->ssasaddr, 8);
+}
+
+/* sas fises */
+static int
+sasfis(Cfis*, uchar *c, SDreq *r)
+{
+	memmove(c, r->cmd, r->clen);
+	if(r->clen < 16)
+		memset(c + r->clen, 0, 16 - r->clen);
+	return 0;
+}
+
+/* sam3 §4.9.4 single-level lun structure */
+static void
+scsilun8(uchar *c, int l)
+{
+	memset(c, 0, 8);
+	if(l < 255)
+		c[1] = l;
+	else if(l < 16384){
+		c[0] = 1<<6 | l>>8;
+		c[1] = l;
+	}else
+		print("bad lun %d\n", l);
+}
+
+static void
+iuhdr(SDreq *r, uchar *c, int fburst)
+{
+	scsilun8(c, r->lun);
+	c[8] = 0;
+	c[9] = 0;
+	if(fburst)
+		c[9] = 0x80;
+}
+
+static void
+ssphdr(Cfis *x, uchar *c, int ftype)
+{
+	memset(c, 0, 0x18);
+	c[0] = ftype;
+	sasbhash(c + 1, x->tsasaddr);
+	sasbhash(c + 5, x->ssasaddr);
+}
+
+/* debugging */
+static void
+dump(uchar *u, uint n)
+{
+	uint i;
+
+	if(n > 100)
+		n = 100;
+	for(i = 0; i < n; i += 4){
+		print("%.2d  %.2ux%.2ux%.2ux%.2ux", i, u[i], u[i + 1], u[i + 2], u[i + 3]);
+		print("\n");
+	}
+}
+
+static void
+prsense(uchar *u, uint n)
+{
+	print("sense data %d: \n", n);
+	dump(u, n);
+}
+
+static void
+priu(uchar *u, uint n)
+{
+	print("iu %d: \n", n);
+	dump(u, n);
+}
+
+/*
+ * other suspects:
+ * key	asc/q
+ * 02	0401	becoming ready
+ * 	040b	target port in standby state
+ * 	0b01	overtemp
+ * 	0b0[345]	background *
+ * 	0c01	write error - recovered with auto reallocation
+ * 	0c02	write error - auto reallocation failed
+ * 	0c03	write error - recommend reassignment
+ * 	17*	recovered data
+ * 	18*	recovered data
+ * 	5d*	smart-style reporting (disk/smart handles)
+ * 	5e*	power state change
+ */
+
+static int
+classifykey(int asckey)
+{
+	if(asckey == 0x062901 || asckey == 0x062900){
+		/* power on */
+		dprint("power on sense\n");
+		return SDretry;
+	}
+	return SDcheck;
+}
+
+/* spc3 §4.5 */
+static int
+sasrspck(Drive *d, SDreq *r, int min)
+{
+	char *p;
+	int rv;
+	uchar *u, *s;
+	uint l, fmt, n, keyasc;
+
+	u = d->cmd->rsp;
+	s = u + 24;
+	dprint("status %d datapres %d\n", u[11], u[10]);
+	switch(u[10]){
+	case 1:
+		l = getbe(u + 20, 4);
+		/*
+		 * this is always a bug because we don't do
+		 * task mgmt
+		 */
+		print("%s: bug: task data %d min %d\n", dnam(d), l, min);
+		return SDcheck;
+	case 2:
+		l = getbe(u + 16, 4);
+		n = sizeof r->sense;
+		if(l < n)
+			n = l;
+		memmove(r->sense, s, n);
+		fmt = s[0] & 0x7f;
+		keyasc = (s[2] & 0xf)<<16 | s[12]<<8 | s[13];
+		rv = SDcheck;
+		/* spc3 §4.5.3; 0x71 is deferred. */
+		if(n >= 18 && (fmt == 0x70 || fmt == 0x71)){
+			rv = classifykey(keyasc);
+			p = "";
+			if(rv == SDcheck){
+				r->flags |= SDvalidsense;
+				p = "valid";
+			}
+			dprint("sense %.6ux %s\n", keyasc, p);
+		}else
+			prsense(s, l);
+		return rv;
+	default:
+		print("%s: sasrspck: spurious\n", dnam(d));
+		priu(u, 24);
+		prsense(s, 0x30);
+		return SDcheck;
+	}
+}
+
+static int
+buildsas(Drive *d, SDreq *r, uchar *data, int n)
+{
+	int w, try, fburst;
+	Aprdt *p;
+	Cmd *x;
+	Cmdh *h;
+
+	try = 0;
+top:
+	fburst = 0;		/* Firstburst? */
+	x = d->cmd;
+	/* ssphdr(d, x->sspfh, 6); */
+	iuhdr(r, x->sasiu, fburst);
+	w = 0;
+	if(r->clen > 16)
+		w = r->clen - 16 + 3>> 2;
+	x->sasiu[11] = w;
+	sasfis(d, x->sasiu + 12, r);
+
+	h = x->cmdh;
+	memset(h, 0, 16);
+	h->sasctl = Tlretry | /*Vrfylen |*/ Sspcmd | fburst;
+	h->fislen[0] = sizeof x->sspfh + 12 + 16 + 4*w >> 2;
+	h->maxrsp = 0xff;
+	if(n)
+		h->len[0] = 1;
+	h->ttag[0] = 1;
+	*(uint*)h->dlen = n;
+
+	if(n){
+		p = x;
+		p->dba = PCIWADDR(data);
+		p->dbahi = Pciwaddrh(data);
+		p->count = n;
+	}
+	switch(w = command(d, Dssp, 10*1000)){
+	case 0:
+		r->status = sdsetsense(r, SDok, 0, 0, 0);
+		return 0;
+	case Response | Done | Active:
+		r->status = sasrspck(d, r, 0);
+		if(r->status == SDok)
+			return 0;
+		if(r->status == SDretry){
+			if(try++ < 2)
+				goto top;
+			r->status |= SDvalidsense;
+		}
+		return w | Sense;
+	default:
+		r->status = sdsetsense(r, SDcheck, 4, 24, 0);
+		return w;
+	}
+}
+
+static uint
+analyze(Drive *d, Statb *b)
+{
+	uint u, r, t;
+
+	r = 0;
+	u = *(uint*)b->error;
+	if(u & Eissuestp){
+		r |= Error;
+		unsetci(d);
+	}
+	if(u & Etask && (d->feat & Datapi) == 0){
+		t = gettask(d);
+		if(t & 1)
+			tprint(d, t);
+		if(t & Efatal<<8 || t & (ASbsy|ASdrq))
+			r |= Noverdict|Atareset;
+		if(t&Adhrs && t&33)
+			r |= Noverdict|Atareset;
+		else
+			r |= Error;
+	}
+	if(u & (Ererr | Ebadprot)){
+		/* sas thing */
+		print("%s: sas error %.8ux\n", dnam(d), u);
+		r |= Error;
+	}
+	if(u & ~(Ebadprot | Ererr | Etask | Eissuestp))
+		print("%s: analyze %.8ux\n", dnam(d), u);
+
+	return r;
+}
+
+static void
+updatedone(Ctlr *c)
+{
+	uint a, e, i, u, slot;
+	Cmd *x;
+	Drive *d;
+
+	e = c->cq[0];
+	if(e == 0xfff)
+		return;
+	if(e > Qmask)
+		print("sdodin: bug: bad cqrp %ux\n", e);
+	e = e+1 & Qmask;
+	for(i = c->cqrp; i != e; i = i+1 & Qmask){
+		u = c->cq[1 + i];
+		c->cq[1 + i] = 0;
+		slot = u & 0xfff;
+		u &= ~slot;
+		d = c->drive + slot;
+  		x = d->cmd;
+		if(u & Cqdone){
+			x->cflag |= Done;
+			u &= ~Cqdone;
+		}
+		if(u & (Crxfr | Cgood)){
+			if((u & Cgood) == 0)
+				x->cflag |= Response;
+			u &= ~(Crxfr | Cgood);
+		}
+		if(u & Cerr){
+			dprint("%s: Cerr ..\n", dnam(d));
+			a = analyze(d, x);
+			x->cflag |= Done | a;
+			u &= ~Cerr;
+		}
+		if(x->cflag & Done)
+			wakeup(x);
+		if(u)
+			print("%s: odd bits %.8ux\n", dnam(d), u);
+	}
+if(i == c->cqrp)print("odin: spur done\n");
+	c->cqrp = i;
+}
+
+static void
+updatedrive(Drive *d)
+{
+	uint cause, s0, ewake;
+	char *name;
+	Cmd *x;
+	static uint last, tk;
+
+	ewake = 0;
+	cause = d->ctlr->reg[pis[d->driveno]];
+	d->ctlr->reg[pis[d->driveno]] = cause;
+	x = d->cmd;
+	name = dnam(d);
+
+	if(last != cause || Ticks - tk > 5*1000){
+		dprint("%s: ca %ux ta %ux\n", name, cause, gettask(d));
+		tk = Ticks;
+	}
+	if(cause & (Phyunrdy | Phyidto | Pisataup | Pisasup)){
+		s0 = d->state;
+		if(cause == (Phyrdy | Comw)){
+			d->type = 0;
+			d->state = Dnopower;
+		}
+		switch(cause & (Phyunrdy | Phyidto | Phyidok | Sigrx)){
+		case Phyunrdy:
+			d->state = Dmissing;
+			if(sstatus(d) & Sphyrdy){
+				if(d->type != 0)
+					d->state = Dnew;
+				else
+					d->state = Dreset;
+			}
+			break;
+		case Phyidto:
+			d->type = 0;
+			d->state = Dmissing;
+			break;
+		case Phyidok:
+			d->type = Sas;
+			d->state = Dnew;
+			break;
+		case Sigrx:
+			d->type = Sata;
+			d->state = Dnew;
+			break;
+		}
+		dprint("%s: %s → %s [Apcrs] %s %ux\n", name, dstate(s0),
+			dstate(d->state), type[d->type], sstatus(d));
+		if(s0 == Dready && d->state != Dready)
+			idprint("%s: pulled\n", name);
+		if(d->state != Dready || ci(d))
+			ewake |= Done | Noverdict;
+	}else if(cause & Piburp)
+		ewake |= Done | Noverdict;
+	else if(cause & Pireset)
+		ewake |= Done | Noverdict | Creset;
+	else if(cause & Piunsupp){
+		print("%s: unsupported h/w: %.8ux\n", name, cause);
+		ewake |= Done | Error;
+		d->type = 0;
+		d->state = Doffline;
+	}
+	if(ewake){
+		dprint("%s: ewake %.8ux\n", name, ewake);
+		unsetci(d);
+		x->cflag |= ewake;
+		wakeup(x);
+	}
+	last = cause;
+}
+
+static int
+satareset(Drive *d)
+{
+	ilock(d->ctlr);
+	unsetci(d);
+	iunlock(d->ctlr);
+	if(gettask(d) & (ASdrq|ASbsy))
+		return -1;
+	if(settxmode(d, d->udma) != 0)
+		return -1;
+	return 0;
+}
+
+static int
+msriopkt(SDreq *r, Drive *d)
+{
+	int n, count, try, max, flag, task;
+	uchar *cmd;
+
+	cmd = r->cmd;
+	aprint("%02ux %02ux %c %d %p\n", cmd[0], cmd[2], "rw"[r->write],
+		r->dlen, r->data);
+	r->rlen = 0;
+	count = r->dlen;
+	max = 65536;
+
+	for(try = 0; try < 10; try++){
+		n = count;
+		if(n > max)
+			n = max;
+		if(lockready(d) == -1)
+			return SDeio;
+		flag = buildpkt(d, r, r->data, n);
+		task = gettask(d);
+		if(flag & Atareset && satareset(d) == -1)
+			setstate(d, Dreset);
+		qunlock(d);
+		if(flag & Noverdict){
+			if(flag & Creset)
+				setstate(d, Dreset);
+			print("%s: retry\n", dnam(d));
+			continue;
+		}
+		if(flag & Error){
+			if((task & Eidnf) == 0)
+				print("%s: i/o error %ux\n", dnam(d), task);
+			return r->status = SDcheck;
+		}
+		return r->status = SDok;
+	}
+	print("%s: bad disk\n", dnam(d));
+	return r->status = SDcheck;
+}
+
+static int
+msriosas(SDreq *r, Drive *d)
+{
+	int try, flag;
+
+	for(try = 0; try < 10; try++){
+		if(lockready(d) == -1)
+			return SDeio;
+		flag = buildsas(d, r, r->data, r->dlen);
+		qunlock(d);
+		if(flag & Noverdict){
+			if(flag & Creset)
+				setstate(d, Dreset);
+			print("%s: retry\n", dnam(d));
+			continue;
+		}
+		if(flag & Error){
+			print("%s: i/o error\n", dnam(d));
+			return r->status = SDcheck;
+		}
+		r->rlen = r->dlen;	/* fishy */
+		return r->status;		/* set in sasrspck */
+
+	}
+	print("%s: bad disk\n", dnam(d));
+	sdsetsense(r, SDcheck, 3, r->write? 0xc00: 0x11, 0);
+	return r->status = SDcheck;
+}
+
+static int
+flushcache(Drive *d)
+{
+	int i;
+
+	i = -1;
+	if(lockready(d) == 0)
+		i = mvflushcache(d);
+	qunlock(d);
+	return i;
+}
+
+static int
+msriosata(SDreq *r, Drive *d)
+{
+	char *name;
+	int i, n, count, try, max, flag, task;
+	uvlong lba;
+	uchar *cmd, *data;
+	SDunit *unit;
+
+	unit = r->unit;
+	cmd = r->cmd;
+	name = dnam(d);
+
+	if(cmd[0] == 0x35 || cmd[0] == 0x91){
+		if(flushcache(d) == 0)
+			return sdsetsense(r, SDok, 0, 0, 0);
+		return sdsetsense(r, SDcheck, 3, 0xc, 2);
+	}
+	if((i = sdfakescsi(r)) != SDnostatus){
+		r->status = i;
+		return i;
+	}
+	if((i = sdfakescsirw(r, &lba, &count, nil)) != SDnostatus)
+		return i;
+	max = 128;
+	if(d->feat & Dllba)
+		max = 65536;
+	try = 0;
+	data = r->data;
+	while(count > 0){
+		n = count;
+		if(n > max)
+			n = max;
+		if(lockready(d) == -1)
+			return SDeio;
+		flag = build(d, r->write, data, n, lba);
+		task = gettask(d);
+		if(flag & Atareset && satareset(d) == -1)
+			setstate(d, Dreset);
+		qunlock(d);
+		if(flag & Noverdict){
+			if(flag & Creset)
+				setstate(d, Dreset);
+			if(++try == 2){
+				print("%s: bad disk\n", name);
+				return r->status = SDeio;
+			}
+			iprint("%s: retry %lld [%.8ux]\n", name, lba, task);
+			continue;
+		}
+		if(flag & Error){
+			iprint("%s: i/o error %ux @%,lld\n", name, task, lba);
+			return r->status = SDeio;
+		}
+		count -= n;
+		lba   += n;
+		data += n*unit->secsize;
+	}
+	r->rlen = data - (uchar*)r->data;
+	r->status = SDok;
+	return SDok;
+}
+
+static int
+msrio(SDreq *r)
+{
+	Ctlr *c;
+	Drive *d;
+	SDunit *u;
+
+	u = r->unit;
+	c = u->dev->ctlr;
+	d = c->drive + u->subno;
+	if(d->feat & Datapi)
+		return msriopkt(r, d);
+	if(d->type == Sas)
+		return msriosas(r, d);
+	if(d->type == Sata)
+		return msriosata(r, d);
+	return sdsetsense(r, SDcheck, 3, 0x04, 0x24);
+}
+
+/*
+ * §6.1.9.5
+ * not clear that this is necessary
+ * we should know that it's a d2h from the status.
+ * pio returns pio setup fises.  hw bug?
+ */
+static int
+sdr(SDreq *r, Drive *d, int st)
+{
+	uint i;
+
+	if(i = fisoffset(d, 0/*Rd2h*/))
+		memmove(r->cmd, d->ctlr->fis + i, 16);
+	else
+		memset(r->cmd, 0xff, 16);
+	r->status = st;
+	return st;
+}
+
+/*
+ * handle oob requests;
+ *    restrict & sanitize commands
+ */
+static int
+fisreqchk(Sfis *f, SDreq *r)
+{
+	uchar *c;
+
+	if((r->ataproto & Pprotom) == Ppkt)
+		return SDnostatus;
+	if(r->clen != 16)
+		error("bad command length"); //error(Eio);
+	c = r->cmd;
+	if(c[0] == 0xf0){
+		sigtofis(f, r->cmd);
+		return r->status = SDok;
+	}
+	c[0] = H2dev;
+	c[1] = Fiscmd;
+	c[7] |= Ataobs;
+	return SDnostatus;
+}
+
+static int
+msataio(SDreq *r)
+{
+	char *name;
+	int try, flag, task;
+	Ctlr *c;
+	Drive *d;
+	SDunit *u;
+	int (*build)(Drive*, SDreq*, void*, int);
+
+	u = r->unit;
+	c = u->dev->ctlr;
+	d = c->drive + u->subno;
+	name = dnam(d);
+
+	if(d->type != Sata)
+		error("not sata");
+	if(r->cmd[0] == 0xf1){
+		d->state = Dreset;
+		return r->status = SDok;
+	}
+	if((r->status = fisreqchk(d, r)) != SDnostatus)
+		return r->status;
+	build = buildfis;
+	if((r->ataproto & Pprotom) == Ppkt)
+		build = buildpkt;
+
+	for(try = 0; try < 10; try++){
+		if(lockready(d) == -1)
+			return SDeio;
+		flag = build(d, r, r->data, r->dlen);
+		task = gettask(d);
+		if(flag & Atareset && satareset(d) == -1)
+			setstate(d, Dreset);
+		qunlock(d);
+		if(flag & Noverdict){
+			if(flag & (Timeout | Creset))
+				setstate(d, Dreset);
+			else if(task & Eabrt<<8){
+				/* assume bad cmd */
+				r->status = SDeio;
+				return SDeio;
+			}
+			print("%s: retry\n", name);
+			continue;
+		}
+		if(flag & Error){
+			print("%s: i/o error %.8ux\n", name, task);
+			r->status = SDeio;
+			return SDeio;
+		}
+		if(build != buildpkt)
+			r->rlen = r->dlen;
+		return sdr(r, d, SDok);
+	}
+	print("%s: bad disk\n", name);
+	return sdr(r, d, SDeio);
+}
+
+static void
+msinterrupt(Ureg *, void *a)
+{
+	Ctlr *c;
+	uint u, i;
+	static uint cnt;
+
+	c = a;
+	ilock(c);
+	u = c->reg[Cis];
+	if(u == 0){
+		iunlock(c);
+		return;
+	}
+	c->reg[Cis] = u & ~Iclr;
+	if(u != Cdone && cnt++ < 15)
+		print("sdodin: irq %s %.8ux\n", c->sdev->ifc->name, u);
+	for(i = 0; i < 8; i++)
+		if(u & (1<<i)*(Portirq|Portstop))
+			updatedrive(c->drive + i);
+	if(u & Srsirq){
+		u = c->reg[Csis];
+		c->reg[Csis] = u;
+		for(i = 0; i < 8; i++)
+			if(u & 1<<i)
+				updatedrive(c->drive + i);
+	}
+	if(u & Cdone){
+		updatedone(c);
+		c->reg[Cis] = Cdone;
+	}
+	iunlock(c);
+}
+
+static char*
+mc(Drive *d)
+{
+	char *s;
+
+	s = "";
+	if(d->drivechange)
+		s = "[newdrive]";
+	return s;
+}
+
+static int
+newsatadrive(Drive *d)
+{
+	uint task;
+
+	task = gettask(d);
+	if((task & 0xffff) == 0x80)
+		return SDretry;
+	setfissig(d, getsig(d));
+	if(identify(d) != 0){
+		dprint("%s: identify failure\n", dnam(d));
+		return SDeio;
+	}
+	if(d->feat & Dpower && setfeatures(d, 0x85, 3*1000)  != 0){
+		d->feat &= ~Dpower;
+		if(satareset(d) == -1)
+			return SDeio;
+	}
+	if(settxmode(d, d->udma)  != 0){
+		dprint("%s: can't set tx mode\n", dnam(d));
+		return SDeio;
+	}
+	return SDok;
+}
+
+static void
+newoaf(Drive *d, int type)
+{
+	uint ict, i;
+	uvlong sa;
+	Ctlr *c;
+
+	i = d->driveno;
+	c = d->ctlr;
+
+	sa = pcread(c, i, Pawwn + 0);
+	sa |= (uvlong)pcread(c, i, Pawwn + 4)<<32;
+	putbe(d->tsasaddr, sa, 8);
+	memmove(d->ssasaddr, d->ssasaddr, 8);
+	ict = pcread(c, i, Pwwn + 8);
+	putbe(d->ict, ict, 2);
+	oafis(d, d->cmd->oaf, type);
+}
+
+static int
+sasinquiry(Drive *d)
+{
+	SDreq r;
+	SDunit *u;
+
+	u = d->unit;
+	memset(&r, 0, sizeof r);
+	r.cmd[0] = 0x12;
+	r.cmd[4] = 0xff;
+	r.clen = 6;
+	r.unit = u;
+
+	return buildsas(d, &r, u->inquiry, sizeof u->inquiry);
+}
+
+static int
+sastur(Drive *d)
+{
+	SDreq r;
+	SDunit *u;
+
+	u = d->unit;
+	memset(&r, 0, sizeof r);
+	r.clen = 6;
+	r.unit = u;
+	return buildsas(d, &r, 0, 0);
+}
+
+static int
+sasvpd(Drive *d, uchar *buf, int l)
+{
+	SDreq r;
+	SDunit *u;
+
+	u = d->unit;
+	memset(&r, 0, sizeof r);
+	r.cmd[0] = 0x12;
+	r.cmd[1] = 1;
+	r.cmd[2] = 0x80;
+	r.cmd[4] = l;
+	r.clen = 6;
+	r.unit = u;
+	return buildsas(d, &r, buf, l);
+}
+
+static int
+sascapacity10(Drive *d, uchar *buf, int l)
+{
+	SDreq r;
+	SDunit *u;
+
+	u = d->unit;
+	memset(&r, 0, sizeof r);
+	r.cmd[0] = 0x25;
+	r.clen = 10;
+	r.unit = u;
+	return buildsas(d, &r, buf, l);
+}
+
+static int
+sascapacity16(Drive *d, uchar *buf, int l)
+{
+	SDreq r;
+	SDunit *u;
+
+	u = d->unit;
+	memset(&r, 0, sizeof r);
+	r.cmd[0] = 0x9e;
+	r.cmd[1] = 0x10;
+	r.cmd[13] = l;
+	r.clen = 16;
+	r.unit = u;
+	return buildsas(d, &r, buf, l);
+}
+
+static void
+frmove(char *p, uchar *c, int n)
+{
+	char *op, *e;
+
+	memmove(p, c, n);
+	op = p;
+	p[n] = 0;
+	for(p = p + n - 1; p > op && *p == ' '; p--)
+		*p = 0;
+	e = p;
+	p = op;
+	while(*p == ' ')
+		p++;
+	memmove(op, p, n - (e - p));
+}
+
+static void
+chkinquiry(Drive *d, uchar *c)
+{
+	char buf[32], buf2[32], omod[sizeof d->model];
+
+	memmove(omod, d->model, sizeof d->model);
+	frmove(buf, c + 8, 8);
+	frmove(buf2, c + 16, 16);
+	snprint(d->model, sizeof d->model, "%s %s", buf, buf2);
+	frmove(d->firmware, c + 23, 4);
+	if(memcmp(omod, d->model, sizeof omod) != 0)
+		d->drivechange = 1;
+}
+
+static void
+chkvpd(Drive *d, uchar *c, int n)
+{
+	char buf[sizeof d->serial];
+	int l;
+
+	l = c[3];
+	if(l > n)
+		l = n;
+	frmove(buf, c + 4, l);
+	if(strcmp(buf, d->serial) != 0)
+		d->drivechange = 1;
+	memmove(d->serial, buf, sizeof buf);
+}
+
+static int
+adjcapacity(Drive *d, uvlong ns, uint nss)
+{
+	if(ns != 0)
+		ns++;
+	if(nss == 2352)
+		nss = 2048;
+	if(d->sectors != ns || d->secsize != nss){
+		d->drivechange = 1;
+		d->sectors = ns;
+		d->secsize = nss;
+	}
+	return 0;
+}
+
+static int
+chkcapacity10(uchar *p, uvlong *ns, uint *nss)
+{
+	*ns = getbe(p, 4);
+	*nss = getbe(p + 4, 4);
+	return 0;
+}
+
+static int
+chkcapacity16(uchar *p, uvlong *ns, uint *nss)
+{
+	*ns = getbe(p, 8);
+	*nss = getbe(p + 8, 4);
+	return 0;
+}
+
+static int
+sasprobe(Drive *d)
+{
+	uchar buf[0x40];
+	int r;
+	uint nss;
+	uvlong ns;
+
+	if((r = sastur(d)) != 0)
+		return r;
+	if((r = sasinquiry(d)) != 0)
+		return r;
+	chkinquiry(d, d->unit->inquiry);
+	/* vpd 0x80 (unit serial) is not mandatory */
+	if((r = sasvpd(d, buf, sizeof buf)) == 0)
+		chkvpd(d, buf, sizeof buf);
+	else if(r & (Error | Timeout))
+		return r;
+	else{
+		if(d->serial[0])
+			d->drivechange = 1;
+		d->serial[0] = 0;
+	}
+	if((r = sascapacity10(d, buf, sizeof buf)) != 0)
+		return r;
+	chkcapacity10(buf, &ns, &nss);
+	if(ns == 0xffffffff){
+		if((r = sascapacity16(d, buf, sizeof buf)) != 0)
+			return r;
+		chkcapacity16(buf, &ns, &nss);
+	}
+	adjcapacity(d, ns, nss);
+
+	return 0;
+}
+
+static int
+newsasdrive(Drive *d)
+{
+	memset(d->cmd->rsp, 0, sizeof d->cmd->rsp);
+	newoaf(d, Ssp);
+	switch(sasprobe(d) & (Error | Noverdict | Timeout | Sense)){
+	case Error:
+	case Timeout:
+		return SDeio;
+	case Sense:
+	case Noverdict:
+		return SDretry;
+	}
+	return SDok;
+}
+
+static int
+newdrive(Drive *d)
+{
+	char *t;
+	int r;
+
+	memset(&d->Sfis, 0, sizeof d->Sfis);
+	memset(&d->Cfis, 0, sizeof d->Cfis);
+	qlock(d);
+	switch(d->type){
+	case Sata:
+		r = newsatadrive(d);
+		break;
+	case Sas:
+		r = newsasdrive(d);
+		break;
+	default:
+		print("%s: bug: martian drive %d\n", dnam(d), d->type);
+		qunlock(d);
+		return -1;
+	}
+	t = type[d->type];
+	switch(r){
+	case SDok:
+		idprint("%s: %s %,lld sectors\n", dnam(d), t, d->sectors);
+		idprint("  %s %s %s %s\n", d->model, d->firmware, d->serial, mc(d));
+		setstate(d, Dready);
+		break;
+	case SDeio:
+		idprint("%s: %s can't be initialized\n", dnam(d), t);
+		setstate(d, Derror);
+	case SDretry:
+		break;
+	}
+	qunlock(d);
+	return r;
+}
+
+static void
+statechange(Drive *d)
+{
+	switch(d->state){
+	case Dmissing:
+	case Dnull:
+	case Doffline:
+		d->drivechange = 1;
+		d->unit->sectors = 0;
+		break;
+	case Dready:
+		d->wait = 0;
+		break;
+	}
+}
+
+/*
+ * we don't respect running commands.  botch?
+ */
+static void
+checkdrive(Drive *d, int i)
+{
+	uint s;
+
+	if(d->unit == nil)
+		return;
+	ilock(d);
+	s = sstatus(d);
+	d->wait++;
+	if(s & Sphyrdy)
+		d->lastseen = Ticks;
+	if(s != olds[i]){
+		dprint("%s: status: %.6ux -> %.6ux: %s\n",
+			dnam(d), olds[i], s, dstate(d->state));
+		olds[i] = s;
+		statechange(d);
+	}
+	switch(d->state){
+	case Dnull:
+	case Dmissing:
+		if(d->type != 0 && s & Sphyrdy)
+			d->state = Dnew;
+		break;
+	case Dnopower:
+		phyreset(d);	/* spinup */
+		break;
+	case Dnew:
+		if(d->wait % 6 != 0)
+			break;
+		iunlock(d);
+		newdrive(d);
+		ilock(d);
+		break;
+	case Dready:
+		d->wait = 0;
+		break;
+	case Derror:
+		d->wait = 0;
+		d->state = Dreset;
+	case Dreset:
+		if(d->wait % 40 != 0)
+			break;
+		reset(d);
+		break;
+	case Doffline:
+	case Dportreset:
+		break;
+	}
+	iunlock(d);
+}
+
+static void
+mskproc(void*)
+{
+	int i;
+
+	for(;;){
+		tsleep(&up->sleep, return0, 0, Nms);
+		for(i = 0; i < nmsdrive; i++)
+			checkdrive(msdrive[i], i);
+	}
+}
+
+static void
+ledcfg(Ctlr *c, int port, uint cfg)
+{
+	uint u, r, s;
+
+	r = Drivectl + (port>>2)*Gpiooff;
+	s = 15 - port & 3;
+	s *= 8;
+	u = gpread(c, r);
+	u &= ~(0xff << s);
+	u |= cfg<<s;
+	gpwrite(c, r, u);
+}
+
+static uchar ses2ledstd[Ibpilast] = {
+[Ibpinone]	Lhigh*Aled,
+[Ibpinormal]	Lsof*Aled | Llow*Locled | Llow*Errled,
+[Ibpirebuild]	Lsof*Aled | Llow*Locled | Llow*Errled,
+[Ibpilocate]	Lsof*Aled | Lblinka*Locled | Llow*Errled,
+[Ibpispare]	Lsof*Aled | Llow*Locled| Lblinka*Errled,
+[Ibpipfa]		Lsof*Aled | Lblinkb*Locled | Llow*Errled,
+[Ibpifail]		Lsof*Aled | Llow*Locled | Lhigh*Errled,
+[Ibpicritarray]	Lsof*Aled,
+[Ibpifailarray]	Lsof*Aled,
+};
+
+static uchar ses2led[Ibpilast] = {
+[Ibpinone]	Lhigh*Aled,
+[Ibpinormal]	Lsof*Aled | Llow*Locled | Llow*Errled,
+[Ibpirebuild]	Lsof*Aled | Lblinkaneg*Locled | Llow*Errled,
+[Ibpilocate]	Lsof*Aled | Lhigh*Locled | Llow*Errled,
+[Ibpispare]	Lsof*Aled | Lblinka*Locled| Llow*Errled,
+[Ibpipfa]		Lsof*Aled | Lblinkb*Locled | Llow*Errled,
+[Ibpifail]		Lsof*Aled | Llow*Locled | Lhigh*Errled,
+[Ibpicritarray]	Lsof*Aled,
+[Ibpifailarray]	Lsof*Aled,
+};
+
+static void
+setupled(Ctlr *c)
+{
+	int i, l, blen;
+	pcicfgw32(c->pci, Gpio, pcicfgr32(c->pci, Gpio) | 1<<7);
+
+	/*
+	 * configure a for 4hz (1/8s on and 1/8s off)
+	 * configure b for 1hz (2/8s on and 6/8s off)
+	 */
+	l = 3 + c->ndrive >> 2;
+	blen = 3*24 - 1;
+	for(i = 0; i < l*Gpiooff; i += Gpiooff){
+		gpwrite(c, Sgconf0 + i, blen*Autolen | Blinkben | Blinkaen | Sgpioen);
+		gpwrite(c, Sgconf1 + i, 1*Bhi | 1*Blo | 1*Ahi | 7*Alo);
+		gpwrite(c, Sgconf3 + i, 7<<20 | Sdoutauto);
+	}
+}
+
+static void
+trebuild(Ctlr *c, Drive *d, int dno, uint i)
+{
+	uchar bits;
+
+	if(0 && d->led == Ibpirebuild){
+		switch(i%19){
+		case 0:
+			bits = 0;
+			break;
+		case 1:
+			bits = ses2led[Ibpirebuild] | Lblinka*Locled;
+			break;
+		case 3:
+			bits = ses2led[Ibpirebuild] | Lblinkb*Locled;
+			break;
+		}
+	}else
+		bits =  ses2led[d->led];
+	if(d->ledbits != bits)
+		ledcfg(c, dno, bits);
+}
+
+static long
+odinledr(SDunit *u, Chan *ch, void *a, long n, vlong off)
+{
+	Ctlr *c;
+	Drive *d;
+
+	c = u->dev->ctlr;
+	d = c->drive + u->subno;
+	return ledr(d, ch, a, n, off);
+}
+
+static long
+odinledw(SDunit *u, Chan *ch, void *a, long n, vlong off)
+{
+	Ctlr *c;
+	Drive *d;
+
+	c = u->dev->ctlr;
+	d = c->drive + u->subno;
+	return ledw(d, ch, a, n, off);
+}
+
+/*
+ * this kproc can probablly go when i figure out
+ * how to program the manual blinker
+ */
+static void
+ledkproc(void*)
+{
+	uint i, j;
+	Drive *d;
+
+	for(i = 0; i < nmsdrive; i++){
+		d = msdrive[i];
+		d->nled = 2;		/* how to know? */
+	}
+	for(i = 0; i < nmsctlr; i++)
+		pcicfgw32(msctlr[i].pci, Gpio, pcicfgr32(msctlr[i].pci, Gpio) | 1<<7);
+	for(i = 0; i < nmsctlr; i++)
+		setupled(msctlr + i);
+	for(i = 0; ; i++){
+		esleep(Nms);
+		for(j = 0; j < nmsdrive; j++){
+			d = msdrive[j];
+			trebuild(d->ctlr, d, j, i);
+		}
+	}
+}
+
+static int
+msenable(SDev *s)
+{
+	char buf[32];
+	Ctlr *c;
+	static int once;
+
+	c = s->ctlr;
+	ilock(c);
+	if(!c->enabled){
+		if(once++ == 0)
+			kproc("odin", mskproc, 0);
+		pcisetbme(c->pci);
+		snprint(buf, sizeof buf, "%s (%s)", s->name, s->ifc->name);
+		intrenable(c->pci->intl, msinterrupt, c, c->pci->tbdf, buf);
+//		c->reg[Cis] |= Swirq1;		/* force initial interrupt. */
+		c->enabled = 1;
+	}
+	iunlock(c);
+	return 1;
+}
+
+static int
+msdisable(SDev *s)
+{
+	char buf[32];
+	Ctlr *c;
+
+	c = s->ctlr;
+	ilock(c);
+//	disable(c->hba);
+	snprint(buf, sizeof buf, "%s (%s)", s->name, s->ifc->name);
+	intrdisable(c->pci->intl, msinterrupt, c, c->pci->tbdf, buf);
+	c->enabled = 0;
+	iunlock(c);
+	return 1;
+}
+
+static int
+scsiish(Drive *d)
+{
+	return d->type == Sas || d->feat & Datapi;
+}
+
+static int
+msonline(SDunit *u)
+{
+	int r;
+	Ctlr *c;
+	Drive *d;
+
+	c = u->dev->ctlr;
+	d = c->drive + u->subno;
+	r = 0;
+
+	if(scsiish(d)){
+		if(!d->drivechange)
+			return r;
+		r = scsionline(u);
+		if(r > 0)
+			d->drivechange = 0;
+		return r;
+	}
+	ilock(d);
+	if(d->drivechange){
+		r = 2;
+		d->drivechange = 0;
+		u->sectors = d->sectors;
+		u->secsize = d->secsize;
+	} else if(d->state == Dready)
+		r = 1;
+	iunlock(d);
+	return r;
+}
+
+static void
+verifychk(Drive *d)
+{
+	int w;
+
+	if(!up)
+		checkdrive(d, d->driveno);
+	for(w = 0; w < 12000; w += 210){
+		if(d->state == Dready)
+			break;
+		if(w > 2000 && d->state != Dnew)
+			break;
+		if((sstatus(d) & Sphyrdy) == 0)
+			break;
+		if(!up)
+			checkdrive(d, d->driveno);
+		esleep(210);
+	}
+}
+
+static int
+msverify(SDunit *u)
+{
+	int chk;
+	Ctlr *c;
+	Drive *d;
+	static int once;
+
+	c = u->dev->ctlr;
+	d = c->drive + u->subno;
+	ilock(c);
+	ilock(d);
+	chk = 0;
+	if(d->unit == nil){
+		d->unit = u;
+		sdaddfile(u, "led", 0644, eve, odinledr, odinledw);
+		once++;
+		if(once == nmsctlr)
+			kproc("mvled", ledkproc, 0);
+		chk = 1;
+	}
+	iunlock(d);
+	iunlock(c);
+
+	/*
+	 * since devsd doesn't know much about hot-plug drives,
+	 * we need to give detected drives a chance.
+	 */
+	if(chk){
+		reset(d);
+		verifychk(d);
+	}
+	return 1;
+}
+
+static uint*
+map(Pcidev *p, int bar)
+{
+	uintptr io;
+
+	io = p->mem[bar].bar & ~0xf;
+	return (uint*)vmap(io, p->mem[bar].size);
+}
+
+/* §5.1.3 */
+static void
+initmem(Ctlr *c)
+{
+	c->fis = smalloc(0x800 + 0x100*16);	/* §6.1.9.3 */
+	c->reg[Fisbase + 0] = PCIWADDR(c->fis);
+	c->reg[Fisbase + 1] = Pciwaddrh(c->fis);
+	c->reg[Cqbase + 0] = PCIWADDR(c->cq);
+	c->reg[Cqbase + 1] = Pciwaddrh(c->cq);
+	c->reg[Cqcfg] = Cqen | Noattn | nelem(c->cq) - 1;
+	c->reg[Dqbase + 0] = PCIWADDR(c->dq);
+	c->reg[Dqbase + 1] = Pciwaddrh(c->dq);
+	c->reg[Dqcfg] = Dqen | nelem(c->dq);
+	c->cl = smalloc(nelem(c->cq)*sizeof *c->cl);
+	c->reg[Clbase + 0] = PCIWADDR(c->cl);
+	c->reg[Clbase + 1] = Pciwaddrh(c->cl);
+	c->cmdtab = smalloc(Nctlrdrv*sizeof *c->cmdtab);
+}
+
+/* §5.1.2 */
+static void
+startup(Ctlr *c)
+{
+	c->reg[Gctl] |= Reset;
+	while(c->reg[Gctl] & Reset)
+		;
+	initmem(c);
+	c->reg[Cie] = Swirq1 | 0xff*Portstop | 0xff*Portirq | Srsirq | Issstop | Cdone;
+	c->reg[Gctl] |= Intenable;
+	c->reg[Portcfg0] = Rmask*Regen | Dataunke | Rsple | Framele;
+	c->reg[Portcfg1] = Rmask*Regen | 0xff*Xmten | /*Cmdirq |*/ Fisen | Resetiss | Issueen;
+	c->reg[Csie] = ~0;
+	sswrite(c, 0, Pwdtimer, 0x7fffff);
+}
+
+static void
+forcetype(Ctlr*)
+{
+	/*
+	 * if we want to force sas/sata, here's where to do it.
+	 */
+}
+
+static void
+setupcmd(Drive *d)
+{
+	int i;
+	Ctlr *c;
+	Cmd *cmd;
+	Cmdh *h;
+
+	i = d->driveno;
+	c = d->ctlr;
+	d->cmd = c->cmdtab + i;
+	d->cmd->cmdh = c->cl + i;
+	cmd = d->cmd;
+	h = cmd->cmdh;
+
+	/* prep the precomputable bits in the cmd hdr §6.1.4 */
+	putle(h->ctab, Pciw64(&cmd->Ctab), sizeof h->ctab);
+	putle(h->oaf, Pciw64(&cmd->Oaf), sizeof h->oaf);
+	putle(h->statb, Pciw64(&cmd->Statb), sizeof h->statb);
+	putle(h->prd, Pciw64(&cmd->Aprdt), sizeof h->prd);
+
+	/* finally, set up the wide-port participating bit */
+	pcwrite(c, i, Pwidecfg, 1<<i);
+}
+
+static void
+phychk(Ctlr *c, Drive *d)
+{
+	int i;
+	uvlong u;
+	static uchar src[8] = {0x50, 0x03, 0x04, 0x80};
+
+	i = d->driveno;
+	memmove(d->ssasaddr, src, 8);
+	u = getbe(d->ssasaddr, 8);
+	pcwrite(c, i, Paddr + 0, u);
+	pcwrite(c, i, Paddr + 4, u>>32);
+}
+
+static SDev*
+mspnp(void)
+{
+	int i, nunit;
+	Ctlr *c;
+	Drive *d;
+	Pcidev *p;
+	SDev **ll, *s, *s0;
+	static int done;
+
+	if(done++)
+		return nil;
+	s0 = nil;
+	ll = &s0;
+	for(p = nil; (p = pcimatch(p, 0x11ab, 0x6485)) != nil; ){
+		if(nmsctlr == Nctlr){
+			print("sdodin: too many controllers\n");
+			break;
+		}
+		c = msctlr + nmsctlr;
+		s = sdevs + nmsctlr;
+		memset(c, 0, sizeof *c);
+		memset(s, 0, sizeof *s);
+		if((c->reg = map(p, Mebar)) == 0){
+			print("sdodin: bar %#p in use\n", c->reg);
+			continue;
+		}
+		nunit = p->did>>4 & 0xf;
+		s->ifc = &sdodinifc;
+		s->idno = 'a' + nmsctlr;
+		s->ctlr = c;
+		c->sdev = s;
+		c->pci = p;
+		c->ndrive = s->nunit = nunit;
+		i = pcicfgr32(p, Dctl) & ~(7<<12);
+		pcicfgw32(p, Dctl, i | 4<<12);
+
+		print("#S/sd%c: odin ii sata/sas with %d ports\n", s->idno, nunit);
+		startup(c);
+		forcetype(c);
+		for(i = 0; i < nunit; i++){
+			d = c->drive + i;
+			d->driveno = i;
+			d->sectors = 0;
+			d->ctlr = c;
+			setupcmd(d);
+			snprint(d->name, sizeof d->name, "odin%d.%d", nmsctlr, i);
+			msdrive[nmsdrive + i] = d;
+//			phychk(c, d);
+			c->reg[pis[i] + 1] =
+				Sync | Phyerr | Stperr | Crcerr |
+				Linkrx | Martianfis | Anot | Bist | Sigrx |
+				Phyunrdy | Martiantag | Bnot | Comw |
+				Portsel | Hreset | Phyidto | Phyidok |
+				Hresetok | Phyrdy;
+		}
+		nmsdrive += nunit;
+		nmsctlr++;
+		*ll = s;
+		ll = &s->next;
+	}
+	return s0;
+}
+
+static char*
+msrctlsata(Drive *d, char *p, char *e)
+{
+	p = seprint(p, e, "flag\t");
+	p = pflag(p, e, d);
+	p = seprint(p, e, "udma\t%d\n", d->udma);
+	return p;
+}
+
+static char*
+rctldebug(char *p, char *e, Ctlr *c, Drive *d)
+{
+	int i;
+	uvlong sasid;
+
+	i = d->driveno;
+	p = seprint(p, e, "sstatus\t%.8ux\n", sstatus(d));
+//	p = seprint(p, e, "cis\t%.8ux %.8ux\n", c->reg[Cis], c->reg[Cie]);
+//	p = seprint(p, e, "gis\t%.8ux\n", c->reg[Gis]);
+	p = seprint(p, e, "pis\t%.8ux %.8ux\n", c->reg[pis[i]], c->reg[pis[i] + 1]);
+	p = seprint(p, e, "sis\t%.8ux\n", c->reg[Csis]);
+	p = seprint(p, e, "cqwp\t%.8ux\n", c->cq[0]);
+	p = seprint(p, e, "cerror\t%.8ux %.8ux\n", *(uint*)d->cmd->error, *(uint*)(d->cmd->error+4));
+	p = seprint(p, e, "task\t%.8ux\n", gettask(d));
+	p = seprint(p, e, "ptype\t%.8ux\n", c->reg[Ptype]);
+	p = seprint(p, e, "satactl\t%.8ux\n", pcread(c, i, Psatactl));	/* appears worthless */
+	p = seprint(p, e, "info	%.8ux %.8ux\n", pcread(c, i, Pinfo), pcread(c, i, Painfo));
+	p = seprint(p, e, "physts	%.8ux\n", pcread(c, i, Pphysts));
+	p = seprint(p, e, "widecfg	%.8ux\n", pcread(c, i, Pwidecfg));
+	sasid = pcread(c, i, Pwwn + 0);
+	sasid |= (uvlong)pcread(c, i, Pwwn + 4)<<32;
+	p = seprint(p, e, "wwn	%.16llux %.8ux\n", sasid, pcread(c, i, Pwwn + 8));
+	sasid = pcread(c, i, Pawwn + 0);
+	sasid |= (uvlong)pcread(c, i, Pawwn + 4)<<32;
+	p = seprint(p, e, "awwn	%.16llux\n", sasid);
+	sasid = pcread(c, i, Paddr + 0);
+	sasid |= (uvlong)pcread(c, i, Paddr + 4)<<32;
+	p = seprint(p, e, "sasid	%.16llux\n", sasid);
+	return p;
+}
+
+static int
+msrctl(SDunit *u, char *p, int l)
+{
+	char *e, *op;
+	Ctlr *c;
+	Drive *d;
+
+	if((c = u->dev->ctlr) == nil)
+		return 0;
+	d = c->drive + u->subno;
+	e = p + l;
+	op = p;
+	p = seprint(p, e, "state\t%s\n", dstate(d->state));
+	p = seprint(p, e, "type\t%s", type[d->type]);
+	if(d->type == Sata)
+		p = seprint(p, e, " sig %.8ux", getsig(d));
+	p = seprint(p, e, "\n");
+	if(d->state == Dready){
+		p = seprint(p, e, "model\t%s\n", d->model);
+		p = seprint(p, e, "serial\t%s\n", d->serial);
+		p = seprint(p, e, "firm\t%s\n", d->firmware);
+		p = seprint(p, e, "wwn\t%llux\n", d->wwn);
+		p = msrctlsata(d, p, e);
+	}
+	p = rctldebug(p, e, c, d);
+	p = seprint(p, e, "geometry %llud %lud\n", d->sectors, u->secsize);
+	return p - op;
+}
+
+static void
+forcestate(Drive *d, char *state)
+{
+	int i;
+
+	for(i = 1; i < nelem(diskstates); i++)
+		if(strcmp(state, diskstates[i]) == 0)
+			break;
+	if(i == nelem(diskstates))
+		error(Ebadctl);
+	ilock(d);
+	d->state = 1 << i - 1;
+	statechange(d);
+	iunlock(d);
+}
+
+static int
+mswctl(SDunit *u, Cmdbuf *cmd)
+{
+	char **f;
+	Ctlr *c;
+	Drive *d;
+
+	c = u->dev->ctlr;
+	d = c->drive + u->subno;
+	f = cmd->f;
+	if(strcmp(f[0], "state") == 0)
+		forcestate(d, f[1]? f[1]: "null");
+	else
+		cmderror(cmd, Ebadctl);
+	return 0;
+}
+
+static int
+mswtopctl(SDev*, Cmdbuf *cmd)
+{
+	char **f;
+	int *v;
+
+	f = cmd->f;
+	v = 0;
+	if(strcmp(f[0], "debug") == 0)
+		v = &debug;
+	else if(strcmp(f[0], "idprint") == 0)
+		v = &idebug;
+	else if(strcmp(f[0], "aprint") == 0)
+		v = &adebug;
+	else
+		cmderror(cmd, Ebadctl);
+	if(cmd->nf == 1)
+		*v ^= 1;
+	else if(cmd->nf == 2)
+		*v = strcmp(f[1], "on") == 0;
+	else
+		cmderror(cmd, Ebadarg);
+	return 0;
+}
+
+SDifc sdodinifc = {
+	"odin",
+	mspnp,
+	nil,
+	msenable,
+	msdisable,
+	msverify,
+	msonline,
+	msrio,
+	msrctl,
+	mswctl,
+	scsibio,
+	nil,		/* probe */
+	nil,		/* clear */
+	nil,
+	mswtopctl,
+	msataio,
+};
--- a/sys/src/9/port/aoe.h
+++ b/sys/src/9/port/aoe.h
@@ -1,9 +1,8 @@
-/*
- * ATA-over-Ethernet (AoE) protocol
- */
 enum {
 	ACata,
 	ACconfig,
+	ACmask,
+	ACres,
 };
 
 enum {
@@ -15,25 +14,57 @@
 };
 
 enum {
-	AEcmd	= 1,
-	AEarg,
-	AEdev,
-	AEcfg,
-	AEver,
+	AEunk,
+	AEcmd,				/* bad command */
+	AEarg,				/* bad argument */
+	AEoff,				/* device offline */
+	AEcfg,				/* config string already set */
+	AEver,				/* unsupported version */
+	AEres,				/* target reserved */
 };
 
 enum {
-	Aoetype	= 0x88a2,
-	Aoesectsz = 512,			/* standard sector size */
-	Aoever	= 1,
+	/* mask commands */
+	Mread		= 0,
+	Medit,
 
-	AFerr	= 1<<2,
-	AFrsp	= 1<<3,
+	/* mask directives */
+	MDnop		= 0,
+	MDadd,
+	MDdel,
 
-	AAFwrite= 1,
-	AAFext	= 1<<6,
+	/* mask errors */
+	MEunk		= 1,
+	MEbad,
+	MEfull,
+
+	/* reserve / release */
+	Rrread		= 0,
+	Rrset,
+	Rrforce,
 };
 
+enum {
+	Aoetype		= 0x88a2,
+	Aoesectsz 	= 512,
+	Aoemaxcfg	= 1024,
+
+	Aoehsz		= 24,
+	Aoeatasz	= 12,
+	Aoecfgsz		= 8,
+	Aoerrsz		= 2,
+	Aoemsz		= 4,
+	Aoemdsz	= 8,
+
+	Aoever		= 1,
+
+	AFerr		= 1<<2,
+	AFrsp		= 1<<3,
+
+	AAFwrite	= 1,
+	AAFext		= 1<<6,
+};
+
 typedef struct {
 	uchar	dst[Eaddrlen];
 	uchar	src[Eaddrlen];
@@ -44,13 +75,9 @@
 	uchar	minor;
 	uchar	cmd;
 	uchar	tag[4];
-	uchar	payload[];
 } Aoehdr;
 
-#define AOEHDRSZ	offsetof(Aoehdr, payload[0])
-
 typedef struct {
-	Aoehdr;
 	uchar	aflag;
 	uchar	errfeat;
 	uchar	scnt;
@@ -57,22 +84,34 @@
 	uchar	cmdstat;
 	uchar	lba[6];
 	uchar	res[2];
-	uchar	payload[];
 } Aoeata;
 
-#define AOEATASZ	offsetof(Aoeata, payload[0])
-
 typedef struct {
-	Aoehdr;
 	uchar	bufcnt[2];
 	uchar	fwver[2];
 	uchar	scnt;
 	uchar	verccmd;
 	uchar	cslen[2];
-	uchar	payload[];
-} Aoeqc;
+} Aoecfg;
 
-#define AOEQCSZ		offsetof(Aoeqc, payload[0])
+typedef struct {
+	uchar	dres;
+	uchar	dcmd;
+	uchar	ea[Eaddrlen];
+} Aoemd;
+
+typedef struct {
+	uchar	mres;
+	uchar	mcmd;
+	uchar	merr;
+	uchar	mcnt;
+} Aoem;
+
+typedef struct {
+	uchar	rcmd;
+	uchar	nea;
+	uchar	ea0[];
+} Aoerr;
 
 extern char Echange[];
 extern char Enotup[];
--- a/sys/src/9/port/devaoe.c
+++ b/sys/src/9/port/devaoe.c
@@ -1,5 +1,5 @@
 /*
- *	© 2005-2010 coraid
+ *	© 2005-10 coraid
  *	aoe storage initiator
  */
 
@@ -15,6 +15,7 @@
 #include "etherif.h"
 #include "../ip/ip.h"
 #include "../port/aoe.h"
+#include <fis.h>
 
 #pragma	varargck argpos	eventlog	1
 
@@ -24,6 +25,7 @@
 enum {
 	Maxunits	= 0xff,
 	Maxframes	= 128,
+	Maxmtu		= 100000,
 	Ndevlink	= 6,
 	Nea		= 6,
 	Nnetlink	= 6,
@@ -35,13 +37,10 @@
 #define QID(u, t) 	((u)<<4 | (t))
 #define Q3(l, u, t)	((l)<<8 | QID(u, t))
 #define UP(d)		((d)->flag & Dup)
-/*
- * would like this to depend on the chan (srb).
- * not possible in the current structure.
- */
-#define Nofail(d, s)	((d)->flag & Dnofail)
 
-#define	MS2TK(t)	((t)/MS2HZ)
+#define	Ticks		MACHP(0)->ticks
+#define	Ms2tk(t)	(((t)*HZ)/1000)
+#define	Tk2ms(t)	(((t)*1000)/HZ)
 
 enum {
 	Qzero,
@@ -67,7 +66,7 @@
 	Qdevlinkfiles	= Qdevlinkend-Qdevlinkbase,
 
 	Eventlen 	= 256,
-	Nevents 	= 64,			/* must be power of 2 */
+	Nevents 	= 64,
 
 	Fread		= 0,
 	Fwrite,
@@ -74,15 +73,10 @@
 	Tfree		= -1,
 	Tmgmt,
 
-	/*
-	 * round trip bounds, timeouts, in ticks.
-	 * timeouts should be long enough that rebooting
-	 * the coraid (which usually takes under two minutes)
-	 * doesn't trigger a timeout.
-	 */
-	Rtmax		= MS2TK(320),
-	Rtmin		= MS2TK(20),
-	Maxreqticks	= 4*60*HZ,		/* was 45*HZ */
+	/* round trip bounds, timeouts, in ticks */
+	Rtmax		= Ms2tk(320),
+	Rtmin		= Ms2tk(20),
+	Srbtimeout	= 45*HZ,
 
 	Dbcnt		= 1024,
 
@@ -91,6 +85,9 @@
 	Cwr		= 0x30,
 	Cwrext		= 0x34,
 	Cid		= 0xec,
+
+	Alloc		= 0x01234567,
+	Free		= 0x89abcdef,
 };
 
 enum {
@@ -104,28 +101,12 @@
  * to send jumbograms to that interface.
  */
 enum {
-	/* sync with ahci.h */
-	Dllba 	= 1<<0,
-	Dsmart	= 1<<1,
-	Dpower	= 1<<2,
-	Dnop	= 1<<3,
-	Datapi	= 1<<4,
-	Datapi16= 1<<5,
-
-	/* aoe specific */
-	Dup	= 1<<6,
-	Djumbo	= 1<<7,
-	Dnofail	= 1<<8,
+	Dup	= 1<<0,
+	Djumbo	= 1<<1,
+	Dnofail	= 1<<2,
 };
 
 static char *flagname[] = {
-	"llba",
-	"smart",
-	"power",
-	"nop",
-	"atapi",
-	"atapi16",
-
 	"up",
 	"jumbo",
 	"nofail",
@@ -132,9 +113,8 @@
 };
 
 typedef struct {
-	ushort	flag;
+	uchar	flag;
 	uint	lostjumbo;
-	int	datamtu;
 
 	Chan	*cc;
 	Chan	*dc;
@@ -148,9 +128,10 @@
 	int	nea;
 	ulong	eaidx;
 	uchar	eatab[Nea][Eaddrlen];
+	int	datamtu;
 	ulong	npkt;
 	ulong	resent;
-	ushort	flag;
+	uchar	flag;
 
 	ulong	rttavg;
 	ulong	mintimer;
@@ -159,6 +140,7 @@
 typedef struct Srb Srb;
 struct Srb {
 	Rendez;
+	uint	state;
 	Srb	*next;
 	ulong	ticksent;
 	ulong	len;
@@ -197,11 +179,11 @@
 	Devlink	*dl;
 	Devlink	dltab[Ndevlink];
 
+	uchar	flag;
 	ushort	fwver;
-	ushort	flag;
 	int	nopen;
-	int	major;
-	int	minor;
+	uint	major;
+	uint	minor;
 	int	unit;
 	int	lasttag;
 	int	nframes;
@@ -210,6 +192,8 @@
 	vlong	realbsize;
 
 	uint	maxbcnt;
+	uint	maxmtu;
+	ulong	lostjumbo;
 	ushort	nout;
 	ushort	maxout;
 	ulong	lastwadj;
@@ -217,7 +201,7 @@
 	Srb	*tail;
 	Srb	*inprocess;
 
-	/* magic numbers 'R' us */
+	Sfis;
 	char	serial[20+1];
 	char	firmware[8+1];
 	char	model[40+1];
@@ -250,16 +234,15 @@
 	Netlink	nl[Nnetlink];
 } netlinks;
 
-extern Dev 	aoedevtab;
-static Ref 	units;
-static Ref	drivevers;
-static int	debug;
-static int	autodiscover	= 1;
-static int	rediscover;
+extern	Dev 	aoedevtab;
+static	Ref 	units;
+static	Ref	drivevers;
+static	int	debug;
+static	int	autodiscover	= 1;
+static	int	rediscover;
+extern	char 	Enotup[] 	= "aoe device is down";
+extern	char	Echange[]	= "media or partition has changed";
 
-char 	Enotup[] 	= "aoe device is down";
-char	Echange[]	= "media or partition has changed";
-
 static Srb*
 srballoc(ulong sz)
 {
@@ -266,8 +249,9 @@
 	Srb *srb;
 
 	srb = malloc(sizeof *srb+sz);
+	srb->state = Alloc;
 	srb->dp = srb->data = srb+1;
-	srb->ticksent = MACHP(0)->ticks;
+	srb->ticksent = Ticks;
 	return srb;
 }
 
@@ -277,33 +261,79 @@
 	Srb *srb;
 
 	srb = malloc(sizeof *srb);
+	srb->state = Alloc;
 	srb->dp = srb->data = db;
-	srb->ticksent = MACHP(0)->ticks;
+	srb->ticksent = Ticks;
 	return srb;
 }
 
-#define srbfree(srb) free(srb)
+static int
+srbready(void *v)
+{
+	Srb *s;
 
+	s = v;
+	return s->nout == 0 && (s->len == 0 || s->error != nil);
+}
+
 static void
-srberror(Srb *srb, char *s)
+srbfree(Srb *srb)
 {
-	srb->error = s;
-	srb->nout--;
-	if (srb->nout == 0)
+	int n;
+
+	for(n = 0; srb->state != Free; n++)
+		sched();
+	free(srb);
+}
+
+/* under Aoedev qlock() so setting of srb->state is safe */
+static void
+srbwakeup(Srb *srb)
+{
+	if(srbready(srb)){
+		assert(srb->state == Alloc);
 		wakeup(srb);
+		srb->state = Free;
+	}
 }
 
 static void
+srbcleanout(Aoedev *d, Srb *srb)
+{
+	Srb *x, **ll;
+
+	if(srb == d->inprocess)
+		d->inprocess = nil;
+	else
+		for(ll = &d->head; x = *ll; ll = &x->next){
+			d->tail = x;
+			if(x == srb)
+				*ll = x->next;
+		}
+}
+
+static void
+srberror(Aoedev *d, Srb *srb, char *s)
+{
+	srbcleanout(d, srb);
+	srb->error = s;
+	srbwakeup(srb);
+}
+
+static void
 frameerror(Aoedev *d, Frame *f, char *s)
 {
 	Srb *srb;
 
-	srb = f->srb;
-	if(f->tag == Tfree || !srb)
+	if(f->tag == Tfree)
 		return;
+	srb = f->srb;
 	f->srb = nil;
 	f->tag = Tfree;		/* don't get fooled by way-slow responses */
-	srberror(srb, s);
+	if(!srb)
+		return;
+	srb->nout--;
+	srberror(d, srb, s);
 	d->nout--;
 }
 
@@ -310,16 +340,10 @@
 static char*
 unitname(Aoedev *d)
 {
-	uprint("%d.%d", d->major, d->minor);
+	uprint("%ud.%ud", d->major, d->minor);
 	return up->genbuf;
 }
 
-static int
-eventlogready(void*)
-{
-	return *events.rp;
-}
-
 static long
 eventlogread(void *a, long n)
 {
@@ -385,13 +409,13 @@
 static int
 eventcount(void)
 {
-	int n;
+	uint n;
 
 	lock(&events);
 	if(*events.rp == 0)
 		n = 0;
 	else
-		n = (events.wp - events.rp) & (Nevents - 1);
+		n = events.wp - events.rp & Nevents - 1;
 	unlock(&events);
 	return n/Eventlen;
 }
@@ -401,7 +425,7 @@
 {
 	int n;
 
-	n = MACHP(0)->ticks & 0xffff;
+	n = Ticks & 0xffff;
 	n -= tag & 0xffff;
 	if(n < 0)
 		n += 1<<16;
@@ -415,7 +439,7 @@
 
 	do {
 		t = ++d->lasttag << 16;
-		t |= MACHP(0)->ticks & 0xffff;
+		t |= Ticks & 0xffff;
 	} while (t == Tfree || t == Tmgmt);
 	return t;
 }
@@ -428,7 +452,7 @@
 	d->flag &= ~Dup;
 	f = d->frames;
 	e = f + d->nframes;
-	for(; f < e; f->tag = Tfree, f->srb = nil, f++)
+	for(; f < e; f++)
 		frameerror(d, f, Enotup);
 	d->inprocess = nil;
 	eventlog("%æ: removed; %s\n", d, err);
@@ -490,6 +514,12 @@
 	return l->eaidx++ % l->nea;
 }
 
+/*
+ * would like this to depend on the chan (srb).
+ * not possible in the current structure.
+ */
+#define Nofail(d, s)	(((d)->flag&Dnofail) == Dnofail)
+
 static int
 hset(Aoedev *d, Frame *f, Aoehdr *h, int cmd)
 {
@@ -496,10 +526,11 @@
 	int i;
 	Devlink *l;
 
-	if(f->srb && MACHP(0)->ticks - f->srb->ticksent > Maxreqticks){
+	if(f->srb)
+	if((long)(Ticks-f->srb->ticksent) > Srbtimeout){
 		eventlog("%æ: srb timeout\n", d);
-		if(cmd == ACata && f->srb && Nofail(d, s))
-			f->srb->ticksent = MACHP(0)->ticks;
+		if(cmd == ACata && Nofail(d, s))
+			f->srb->ticksent = Ticks;
 		else
 			frameerror(d, f, Etimedout);
 		return -1;
@@ -507,7 +538,7 @@
 	l = pickdevlink(d);
 	i = pickea(l);
 	if(i == -1){
-		if(cmd != ACata || f->srb == nil || !Nofail(d, s))
+		if(!(cmd == ACata && f->srb && Nofail(d, s)))
 			downdev(d, "resend fails; no netlink/ea");
 		return -1;
 	}
@@ -524,7 +555,7 @@
 	f->dl = l;
 	f->nl = l->nl;
 	f->eaidx = i;
-	f->ticksent = MACHP(0)->ticks;
+	f->ticksent = Ticks;
 
 	return f->tag;
 }
@@ -534,10 +565,12 @@
 {
 	ulong n;
 	Aoeata *a;
+	Aoehdr *h;
 
-	a = (Aoeata*)f->hdr;
-	if(hset(d, f, a, a->cmd) == -1)
+	h = (Aoehdr*)f->hdr;
+	if(hset(d, f, h, h->cmd) == -1)
 		return -1;
+	a = (Aoeata*)(f->hdr + Aoehsz);
 	n = f->bcnt;
 	if(n > d->maxbcnt){
 		n = d->maxbcnt;		/* mtu mismatch (jumbo fail?) */
@@ -548,6 +581,7 @@
 	f->dl->resent++;
 	f->dl->npkt++;
 	if(waserror())
+		/* should remove the netlink */
 		return -1;
 	devtab[f->nl->dc->type]->bwrite(f->nl->dc, allocfb(f), 0);
 	poperror();
@@ -555,7 +589,7 @@
 }
 
 static void
-discover(int major, int minor)
+discover(uint major, uint minor)
 {
 	Aoehdr *h;
 	Block *b;
@@ -582,7 +616,6 @@
 		h->minor = minor;
 		h->cmd = ACconfig;
 		poperror();
-		/* send b down the queue */
 		devtab[nl->dc->type]->bwrite(nl->dc, b, 0);
 	}
 }
@@ -596,7 +629,7 @@
 {
 	ulong i, tx, timeout, nbc;
 	vlong starttick;
-	enum { Nms = 100, Nbcms = 30*1000, };		/* magic */
+	enum { Nms = 100, Nbcms = 30*1000, };
 	uchar *ea;
 	Aoeata *a;
 	Aoedev *d;
@@ -612,7 +645,7 @@
 		}
 		nbc = Nbcms/Nms;
 	}
-	starttick = MACHP(0)->ticks;
+	starttick = Ticks;
 	rlock(&devs);
 	for(d = devs.d; d; d = d->next){
 		if(!canqlock(d))
@@ -635,13 +668,13 @@
 			if(d->nout == d->maxout){
 				if(d->maxout > 1)
 					d->maxout--;
-				d->lastwadj = MACHP(0)->ticks;
+				d->lastwadj = Ticks;
 			}
-			a = (Aoeata*)f->hdr;
+			a = (Aoeata*)(f->hdr + Aoehsz);
 			if(a->scnt > Dbcnt / Aoesectsz &&
 			   ++f->nl->lostjumbo > (d->nframes << 1)){
 				ea = f->dl->eatab[f->eaidx];
-				eventlog("%æ: jumbo failure on %s:%E; lba%lld\n",
+				eventlog("%æ: jumbo failure on %s:%E; %llud\n",
 					d, f->nl->path, ea, f->lba);
 				d->maxbcnt = Dbcnt;
 				d->flag &= ~Djumbo;
@@ -650,18 +683,18 @@
 			if(tx++ == 0){
 				if((l->rttavg <<= 1) > Rtmax)
 					l->rttavg = Rtmax;
-				eventlog("%æ: rtt %ldms\n", d, TK2MS(l->rttavg));
+				eventlog("%æ: rtt %ldms\n", d, Tk2ms(l->rttavg));
 			}
 		}
 		if(d->nout == d->maxout && d->maxout < d->nframes &&
-		   TK2MS(MACHP(0)->ticks - d->lastwadj) > 10*1000){ /* more magic */
+		   TK2MS(Ticks-d->lastwadj) > 10*1000){
 			d->maxout++;
-			d->lastwadj = MACHP(0)->ticks;
+			d->lastwadj = Ticks;
 		}
 		qunlock(d);
 	}
 	runlock(&devs);
-	i = Nms - TK2MS(MACHP(0)->ticks - starttick);
+	i = Nms - TK2MS(Ticks - starttick);
 	if(i > 0)
 		tsleep(&up->sleep, return0, 0, i);
 	goto loop;
@@ -674,7 +707,7 @@
 	Aoedev *d;
 
 	d = va_arg(f->args, Aoedev*);
-	snprint(buf, sizeof buf, "aoe%d.%d", d->major, d->minor);
+	snprint(buf, sizeof buf, "aoe%ud.%ud", d->major, d->minor);
 	return fmtstrcpy(f, buf);
 }
 
@@ -683,11 +716,14 @@
 static void
 aoecfg(void)
 {
+	char *p, *f[32], buf[24], ifbuf[64];
 	int n, i;
-	char *p, *f[32], buf[24];
 
-	if((p = getconf("aoeif")) == nil || (n = tokenize(p, f, nelem(f))) < 1)
+	if((p = getconf("aoeif")) == nil)
 		return;
+	strncpy(ifbuf, p, sizeof buf);
+	if((n = tokenize(ifbuf, f, nelem(f))) < 1)
+		return;
 	/* goo! */
 	for(i = 0; i < n; i++){
 		p = f[i];
@@ -736,21 +772,33 @@
 }
 
 static Aoedev*
-unit2dev(ulong unit)
+unitseq(ulong unit)
 {
 	int i;
 	Aoedev *d;
 
-	rlock(&devs);
 	i = 0;
+	rlock(&devs);
 	for(d = devs.d; d; d = d->next)
-		if(i++ == unit){
+		if(i++ == unit)
+			break;
+	runlock(&devs);
+	return d;
+}
+
+static Aoedev*
+unit2dev(ulong unit)
+{
+	Aoedev *d;
+
+	rlock(&devs);
+	for(d = devs.d; d; d = d->next)
+		if(d->unit == unit){
 			runlock(&devs);
 			return d;
 		}
 	runlock(&devs);
-	uprint("unit lookup failure: %lux pc %#p", unit, getcallerpc(&unit));
-	error(up->genbuf);
+	error("unit lookup failure");
 	return nil;
 }
 
@@ -867,11 +915,9 @@
 		if(s < Qtopfiles)
 			return topgen(c, Qtopbase + s, dp);
 		s -= Qtopfiles;
-		if(s >= units.ref)
+		if((d = unitseq(s)) == 0)
 			return -1;
-		mkqid(&q, QID(s, Qunitdir), 0, QTDIR);
-		d = unit2dev(s);
-		assert(d != nil);
+		mkqid(&q, QID(d->unit, Qunitdir), 0, QTDIR);
 		devdir(c, q, unitname(d), 0, eve, 0555, dp);
 		return 1;
 	case Qtopctl:
@@ -972,6 +1018,7 @@
 	ulong bcnt;
 	char extbit, writebit;
 	Aoeata *ah;
+	Aoehdr *h;
 	Srb *srb;
 
 	extbit = 0x4;
@@ -981,13 +1028,14 @@
 	bcnt = d->maxbcnt;
 	if(bcnt > srb->len)
 		bcnt = srb->len;
-	f->nhdr = AOEATASZ;
+	f->nhdr = Aoehsz + Aoeatasz;
 	memset(f->hdr, 0, f->nhdr);
-	ah = (Aoeata*)f->hdr;
-	if(hset(d, f, ah, ACata) == -1) {
+	h = (Aoehdr*)f->hdr;
+	if(hset(d, f, h, ACata) == -1){
 		d->inprocess = nil;
 		return;
 	}
+	ah = (Aoeata*)(f->hdr + Aoehsz);
 	f->dp = srb->dp;
 	f->bcnt = bcnt;
 	f->lba = srb->sector;
@@ -995,7 +1043,7 @@
 
 	ah->scnt = bcnt / Aoesectsz;
 	putlba(ah, f->lba);
-	if(d->flag & Dllba)
+	if(d->feat & Dllba)
 		ah->aflag |= AAFext;
 	else {
 		extbit = 0;
@@ -1020,13 +1068,12 @@
 		d->inprocess = nil;
 	d->nout++;
 	f->dl->npkt++;
-	if(waserror()){
-		f->tag = Tfree;
-		d->inprocess = nil;
-		nexterror();
+	if(waserror())
+		frameerror(d, f, "write error");
+	else{
+		devtab[f->nl->dc->type]->bwrite(f->nl->dc, allocfb(f), 0);
+		poperror();
 	}
-	devtab[f->nl->dc->type]->bwrite(f->nl->dc, allocfb(f), 0);
-	poperror();
 }
 
 static char*
@@ -1074,15 +1121,6 @@
 	l->rttavg += n >> 2;
 }
 
-static int
-srbready(void *v)
-{
-	Srb *s;
-
-	s = v;
-	return s->error || (!s->nout && !s->len);
-}
-
 static Frame*
 getframe(Aoedev *d, int tag)
 {
@@ -1109,7 +1147,7 @@
 {
 	Frame *f;
 
-	while ((f = freeframe(d)) != nil) {
+	while(f = freeframe(d)) {
 		if(d->inprocess == nil){
 			if(d->head == nil)
 				return;
@@ -1130,6 +1168,8 @@
 		qunlock(d);
 		nexterror();
 	}
+	if(!UP(d))
+		error(Eio);
 	srb->next = nil;
 	if(d->tail)
 		d->tail->next = srb;
@@ -1140,8 +1180,11 @@
 	poperror();
 	qunlock(d);
 
-	while(waserror())
-		;
+	while(waserror()){
+		qlock(d);
+		srberror(d, srb, "interrupted");
+		qunlock(d);
+	}
 	sleep(srb, srbready, srb);
 	poperror();
 }
@@ -1152,12 +1195,14 @@
 rw(Aoedev *d, int write, uchar *db, long len, uvlong off)
 {
 	long n, nlen, copy;
-	enum { Srbsz = 1<<19, };	/* magic allocation */
+	enum { Srbsz = 1<<19, };
 	Srb *srb;
 
 	if((off|len) & (Aoesectsz-1))
 		error("offset and length must be sector multiple.\n");
-	if(off > d->bsize || len == 0)
+	if(!UP(d))
+		error(Eio);
+	if(off >= d->bsize)
 		return 0;
 	if(off + len > d->bsize)
 		len = d->bsize - off;
@@ -1171,11 +1216,8 @@
 		srbfree(srb);
 		nexterror();
 	}
-	nlen = len;
 	srb->write = write;
-	do {
-		if(!UP(d))
-			error(Eio);
+	for(nlen = len; nlen; nlen -= n){
 		srb->sector = off / Aoesectsz;
 		srb->dp = srb->data;
 		n = nlen;
@@ -1189,10 +1231,9 @@
 			error(srb->error);
 		if(!write && !copy)
 			memmove(db, srb->data, n);
-		nlen -= n;
 		db += n;
 		off += n;
-	} while (nlen > 0);
+	}
 	poperror();
 	srbfree(srb);
 	return len;
@@ -1209,16 +1250,14 @@
 	return n;
 }
 
-static char *
-pflag(char *s, char *e, uchar f)
+static char*
+aoeflag(char *s, char *e, uchar f)
 {
-	uchar i, m;
+	uchar i;
 
-	for(i = 0; i < 8; i++){
-		m = 1 << i;
-		if(f & m)
-			s = seprint(s, e, "%s ", flagname[i]? flagname[i]: "oops");
-	}
+	for(i = 0; i < nelem(flagname); i++)
+		if(f & 1 << i)
+			s = seprint(s, e, "%s ", flagname[i]);
 	return seprint(s, e, "\n");
 }
 
@@ -1237,15 +1276,17 @@
 
 	p = seprint(p, e,
 		"state: %s\n"	"nopen: %d\n"	"nout: %d\n"
-		"nmaxout: %d\n"	"nframes: %d\n"	"maxbcnt: %d\n"
+		"nmaxout: %d\n"	"nframes: %d\n"	"maxbcnt: %d [maxmtu %d]\n"
 		"fw: %.4ux\n"
 		"model: %s\n"	"serial: %s\n"	"firmware: %s\n",
 		state,		d->nopen,	d->nout,
-		d->maxout, 	d->nframes,	d->maxbcnt,
+		d->maxout, 	d->nframes,	d->maxbcnt, d->maxmtu,
 		d->fwver,
 		d->model, 	d->serial, 	d->firmware);
 	p = seprint(p, e, "flag: ");
-	p = pflag(p, e, d->flag);
+	p = pflag(p, e, d);
+	p[-1] = ' ';	/* horrid */
+	p = aoeflag(p, e, d->flag);
 
 	if(p - s < len)
 		len = p - s;
@@ -1270,11 +1311,11 @@
 	case Qdata:
 		return rw(d, Read, db, len, off);
 	case Qconfig:
-		if (!UP(d))
+		if(!UP(d))
 			error(Enotup);
 		return readmem(off, db, len, d->config, d->nconfig);
 	case Qident:
-		if (!UP(d))
+		if(!UP(d))
 			error(Enotup);
 		return readmem(off, db, len, d->ident, sizeof d->ident);
 	}
@@ -1281,6 +1322,24 @@
 }
 
 static int
+getmtu(Chan *m)
+{
+	int n, mtu;
+	char buf[36];
+
+	mtu = 1514;
+	if(m == nil || waserror())
+		return mtu;
+	n = devtab[m->type]->read(m, buf, sizeof buf - 1, 0);
+	poperror();
+	if(n > 12){
+		buf[n] = 0;
+		mtu = strtoul(buf + 12, 0, 0);
+	}
+	return mtu;
+}
+
+static int
 devlinkread(Chan *c, void *db, int len, int off)
 {
 	int i;
@@ -1303,15 +1362,18 @@
 	p = seprint(p, e, "\n");
 	p = seprint(p, e, "npkt: %uld\n", l->npkt);
 	p = seprint(p, e, "resent: %uld\n", l->resent);
-	p = seprint(p, e, "flag: "); p = pflag(p, e, l->flag);
-	p = seprint(p, e, "rttavg: %uld\n", TK2MS(l->rttavg));
-	p = seprint(p, e, "mintimer: %uld\n", TK2MS(l->mintimer));
+	p = seprint(p, e, "flag: ");
+	p = aoeflag(p, e, l->flag);
+	p = seprint(p, e, "rttavg: %uld\n", Tk2ms(l->rttavg));
+	p = seprint(p, e, "mintimer: %uld\n", Tk2ms(l->mintimer));
+	p = seprint(p, e, "datamtu: %d\n", l->datamtu);
 
 	p = seprint(p, e, "nl path: %s\n", l->nl->path);
 	p = seprint(p, e, "nl ea: %E\n", l->nl->ea);
-	p = seprint(p, e, "nl flag: "); p = pflag(p, e, l->flag);
+	p = seprint(p, e, "nl flag: ");
+	p = aoeflag(p, e, l->flag);
 	p = seprint(p, e, "nl lostjumbo: %d\n", l->nl->lostjumbo);
-	p = seprint(p, e, "nl datamtu: %d\n", l->nl->datamtu);
+	p = seprint(p, e, "nl datamtu: %d\n", getmtu(l->nl->mtu));
 
 	if(p - s < len)
 		len = p - s;
@@ -1340,9 +1402,10 @@
 			continue;
 		p = seprint(p, e, "if%d path: %s\n", i, n->path);
 		p = seprint(p, e, "if%d ea: %E\n", i, n->ea);
-		p = seprint(p, e, "if%d flag: ", i); p = pflag(p, e, n->flag);
+		p = seprint(p, e, "if%d flag: ", i);
+		p = aoeflag(p, e, n->flag);
 		p = seprint(p, e, "if%d lostjumbo: %d\n", i, n->lostjumbo);
-		p = seprint(p, e, "if%d datamtu: %d\n", i, n->datamtu);
+		p = seprint(p, e, "if%d datamtu: %d\n", i, getmtu(n->mtu));
 	}
 
 	if(p - s < len)
@@ -1381,13 +1444,14 @@
 configwrite(Aoedev *d, void *db, long len)
 {
 	char *s;
-	Aoeqc *ch;
+	Aoehdr *h;
+	Aoecfg *ch;
 	Frame *f;
 	Srb *srb;
 
 	if(!UP(d))
 		error(Enotup);
-	if(len > ETHERMAXTU - AOEQCSZ)
+	if(len > sizeof d->config)
 		error(Etoobig);
 	srb = srballoc(len);
 	s = malloc(len);
@@ -1413,11 +1477,12 @@
 		tsleep(&up->sleep, return0, 0, 100);
 		poperror();
 	}
-	f->nhdr = AOEQCSZ;
+	f->nhdr = Aoehsz + Aoecfgsz;
 	memset(f->hdr, 0, f->nhdr);
-	ch = (Aoeqc*)f->hdr;
-	if(hset(d, f, ch, ACconfig) == -1)
+	h = (Aoehdr*)f->hdr;
+	if(hset(d, f, h, ACconfig) == -1)
 		return 0;
+	ch = (Aoecfg*)(f->hdr + Aoehsz);
 	f->srb = srb;
 	f->dp = s;
 	ch->verccmd = AQCfset;
@@ -1456,16 +1521,15 @@
 	return len;
 }
 
-static int getmtu(Chan*);
-
 static int
 devmaxdata(Aoedev *d)
 {
-	int i, m, mtu;
+	int i, m, mtu, datamtu;
 	Devlink *l;
 	Netlink *n;
 
 	mtu = 100000;
+	datamtu = 100000;
 	for(i = 0; i < d->ndl; i++){
 		l = d->dl + i;
 		n = l->nl;
@@ -1472,21 +1536,30 @@
 		if((l->flag & Dup) == 0 || (n->flag & Dup) == 0)
 			continue;
 		m = getmtu(n->mtu);
+		if(l->datamtu < datamtu)
+			datamtu = l->datamtu;
 		if(m < mtu)
 			mtu = m;
 	}
 	if(mtu == 100000)
-		mtu = 0;
-	mtu -= AOEATASZ;
+		mtu = 1514;
+	mtu -= Aoehsz + Aoeatasz;
+	mtu -= mtu % Aoesectsz;
+	if(mtu > datamtu)
+		mtu = datamtu;
 	return mtu;
 }
 
 static int
-toggle(char *s, int init)
+toggle(char *s, uint f, uint bit)
 {
 	if(s == nil)
-		return init ^ 1;
-	return strcmp(s, "on") == 0;
+		f = f^bit;
+	else if(strcmp(s, "on") == 0)
+		f |= bit;
+	else
+		f &= ~bit;
+	return f;
 }
 
 static void ataident(Aoedev*);
@@ -1513,7 +1586,7 @@
 		{Jumbo, 	"jumbo", 	0 },
 		{Maxbno,	"maxbno",	0 },
 		{Mtu,		"mtu",		0 },
-		{Nofailf,	"nofail",	0 },
+		{Nofailf,		"nofail",		0 },
 		{Setsize, 	"setsize", 	0 },
 	};
 
@@ -1533,14 +1606,7 @@
 		ataident(d);
 		break;
 	case Jumbo:
-		m = 0;
-		if(d->flag & Djumbo)
-			m = 1;
-		toggle(cb->f[1], m);
-		if(m)
-			d->flag |= Djumbo;
-		else
-			d->flag &= ~Djumbo;
+		d->flag = toggle(cb->f[1], d->flag, Djumbo);
 		break;
 	case Maxbno:
 	case Mtu:
@@ -1552,20 +1618,19 @@
 			if(ct->index == Maxbno)
 				m *= Aoesectsz;
 			else{
-				m -= AOEATASZ;
+				m -= Aoehsz + Aoeatasz;
 				m &= ~(Aoesectsz-1);
 			}
 			if(m == 0 || m > maxbcnt)
-				cmderror(cb, "mtu out of legal range");
+				cmderror(cb, "invalid mtu");
 			maxbcnt = m;
-		}
+			d->maxmtu = m;
+		} else
+			d->maxmtu = Maxmtu;
 		d->maxbcnt = maxbcnt;
 		break;
 	case Nofailf:
-		if (toggle(cb->f[1], (d->flag & Dnofail) != 0))
-			d->flag |= Dnofail;
-		else
-			d->flag &= ~Dnofail;
+		d->flag = toggle(cb->f[1], d->flag, Dnofail);
 		break;
 	case Setsize:
 		bsize = d->realbsize;
@@ -1578,8 +1643,6 @@
 		}
 		d->bsize = bsize;
 		break;
-	default:
-		cmderror(cb, "unknown aoe control message");
 	}
 	poperror();
 	qunlock(d);
@@ -1635,7 +1698,7 @@
 	e = nl + nelem(netlinks.nl);
 	for(; nl < e && nl->cc; nl++)
 		continue;
-	if (nl >= e)
+	if(nl == e)
 		error("out of netlink structures");
 	nl->cc = cc;
 	nl->dc = dc;
@@ -1677,14 +1740,14 @@
  * always allocate max frames.  maxout may change.
  */
 static Aoedev*
-newdev(long major, long minor, int n)
+newdev(uint major, uint minor, int n)
 {
 	Aoedev *d;
 	Frame *f, *e;
 
-	d = mallocz(sizeof *d, 1);
-	f = mallocz(sizeof *f * Maxframes, 1);
-	if (!d || !f) {
+	d = malloc(sizeof *d);
+	f = malloc(sizeof *f*Maxframes);
+	if(!d || !f) {
 		free(d);
 		free(f);
 		error("aoe device allocation failure");
@@ -1691,7 +1754,7 @@
 	}
 	d->nframes = n;
 	d->frames = f;
-	for (e = f + n; f < e; f++)
+	for (e = f + Maxframes; f < e; f++)
 		f->tag = Tfree;
 	d->maxout = n;
 	d->major = major;
@@ -1698,6 +1761,7 @@
 	d->minor = minor;
 	d->maxbcnt = Dbcnt;
 	d->flag = Djumbo;
+	d->maxmtu = Maxmtu;
 	d->unit = newunit();		/* bzzt.  inaccurate if units removed */
 	if(d->unit == -1){
 		free(d);
@@ -1709,7 +1773,7 @@
 }
 
 static Aoedev*
-mm2dev(int major, int minor)
+mm2dev(uint major, uint minor)
 {
 	Aoedev *d;
 
@@ -1720,13 +1784,13 @@
 			return d;
 		}
 	runlock(&devs);
-	eventlog("mm2dev: %d.%d not found\n", major, minor);
+	eventlog("mm2dev: %ud.%ud not found\n", major, minor);
 	return nil;
 }
 
 /* Find the device in our list.  If not known, add it */
 static Aoedev*
-getdev(long major, long minor, int n)
+getdev(uint major, uint minor, int n)
 {
 	Aoedev *d;
 
@@ -1740,7 +1804,7 @@
 	for(d = devs.d; d; d = d->next)
 		if(d->major == major && d->minor == minor)
 			break;
-	if (d == nil) {
+	if(d == nil) {
 		d = newdev(major, minor, n);
 		d->next = devs.d;
 		devs.d = d;
@@ -1750,53 +1814,23 @@
 	return d;
 }
 
-static ushort
-gbit16(void *a)
-{
-	uchar *i;
-
-	i = a;
-	return i[1] << 8 | i[0];
-}
-
-static ulong
-gbit32(void *a)
-{
-	ulong j;
-	uchar *i;
-
-	i = a;
-	j  = i[3] << 24;
-	j |= i[2] << 16;
-	j |= i[1] << 8;
-	j |= i[0];
-	return j;
-}
-
-static uvlong
-gbit64(void *a)
-{
-	uchar *i;
-
-	i = a;
-	return (uvlong)gbit32(i+4) << 32 | gbit32(a);
-}
-
 static void
 ataident(Aoedev *d)
 {
 	Aoeata *a;
-	Block *b;
+	Aoehdr *h;
 	Frame *f;
 
 	f = freeframe(d);
 	if(f == nil)
 		return;
-	f->nhdr = AOEATASZ;
+	f->nhdr = Aoehsz + Aoeatasz;
 	memset(f->hdr, 0, f->nhdr);
-	a = (Aoeata*)f->hdr;
-	if(hset(d, f, a, ACata) == -1)
+	h = (Aoehdr*)f->hdr;
+	if(hset(d, f, h, ACata) == -1)
 		return;
+	a = (Aoeata*)(f->hdr + Aoehsz);
+	f->srb = srbkalloc(0, 0);
 	a->cmdstat = Cid;	/* ata 6, page 110 */
 	a->scnt = 1;
 	a->lba[3] = 0xa0;
@@ -1804,26 +1838,14 @@
 	f->dl->npkt++;
 	f->bcnt = 512;
 	f->dlen = 0;
-	b = allocfb(f);
-	devtab[f->nl->dc->type]->bwrite(f->nl->dc, b, 0);
-}
-
-static int
-getmtu(Chan *m)
-{
-	int n, mtu;
-	char buf[36];
-
-	mtu = 1514;
-	if(m == nil || waserror())
-		return mtu;
-	n = devtab[m->type]->read(m, buf, sizeof buf - 1, 0);
-	if(n > 12){
-		buf[n] = 0;
-		mtu = strtoul(buf + 12, 0, 0);
+	if(waserror()){
+		srbfree(f->srb);
+		d->nout--;
+		f->tag = Tfree;
+	}else{
+		devtab[f->nl->dc->type]->bwrite(f->nl->dc, allocfb(f), 0);
+		poperror();
 	}
-	poperror();
-	return mtu;
 }
 
 static int
@@ -1845,16 +1867,19 @@
 }
 
 static Devlink*
-newdevlink(Aoedev *d, Netlink *n, Aoeqc *c)
+newdevlink(Aoedev *d, Netlink *n, Aoehdr *h)
 {
 	int i;
+	Aoecfg *c;
 	Devlink *l;
 
+	c = (Aoecfg*)((uchar*)h + Aoehsz);
 	for(i = 0; i < Ndevlink; i++){
 		l = d->dl + i;
 		if(i == d->ndl){
 			d->ndl++;
-			newdlea(l, c->src);
+			newdlea(l, h->src);
+			l->datamtu = c->scnt*Aoesectsz;
 			l->nl = n;
 			l->flag |= Dup;
 			l->mintimer = Rtmin;
@@ -1861,13 +1886,14 @@
 			l->rttavg = Rtmax;
 			return l;
 		}
-		if(l->nl == n) {
-			newdlea(l, c->src);
+		if(l->nl == n){
+			newdlea(l, h->src);
+			l->datamtu = c->scnt*Aoesectsz;
 			l->flag |= Dup;
 			return l;
 		}
 	}
-	eventlog("%æ: out of links: %s:%E to %E\n", d, n->path, n->ea, c->src);
+	eventlog("%æ: out of links: %s:%E to %E\n", d, n->path, n->ea, h->src);
 	return 0;
 }
 
@@ -1893,18 +1919,21 @@
 static void
 qcfgrsp(Block *b, Netlink *nl)
 {
-	int major, cmd, cslen, blen;
-	unsigned n;
+	int cmd, cslen, blen;
+	uint n, major;
 	Aoedev *d;
-	Aoeqc *ch;
+	Aoehdr *h;
+	Aoecfg *ch;
 	Devlink *l;
 	Frame *f;
+	Srb *srb;
 
-	ch = (Aoeqc*)b->rp;
-	major = nhgets(ch->major);
-	n = nhgetl(ch->tag);
+	h = (Aoehdr*)b->rp;
+	ch = (Aoecfg*)(b->rp + Aoehsz);
+	major = nhgets(h->major);
+	n = nhgetl(h->tag);
 	if(n != Tmgmt){
-		d = mm2dev(major, ch->minor);
+		d = mm2dev(major, h->minor);
 		if(d == nil)
 			return;
 		qlock(d);
@@ -1915,7 +1944,7 @@
 			return;
 		}
 		cslen = nhgets(ch->cslen);
-		blen = BLEN(b) - AOEQCSZ;
+		blen = BLEN(b) - (Aoehsz + Aoecfgsz);
 		if(cslen < blen && BLEN(b) > 60)
 			eventlog("%æ: cfgrsp: tag %.8ux oversized %d %d\n",
 				d, n, cslen, blen);
@@ -1924,12 +1953,16 @@
 				d, n, cslen, blen);
 			cslen = blen;
 		}
-		memmove(f->dp, ch + 1, cslen);
-		f->srb->nout--;
-		wakeup(f->srb);
-		d->nout--;
+		memmove(f->dp, (uchar*)ch + Aoehsz + Aoecfgsz, cslen);
+		srb = f->srb;
+		f->dp = nil;
 		f->srb = nil;
-		f->tag = Tfree;
+		if(srb){
+			srb->nout--;
+			srbwakeup(srb);
+			d->nout--;
+			f->tag = Tfree;
+		}
 		qunlock(d);
 		return;
 	}
@@ -1936,7 +1969,7 @@
 
 	cmd = ch->verccmd & 0xf;
 	if(cmd != 0){
-		eventlog("aoe%d.%d: cfgrsp: bad command %d\n", major, ch->minor, cmd);
+		eventlog("aoe%ud.%ud: cfgrsp: bad command %d\n", major, h->minor, cmd);
 		return;
 	}
 	n = nhgets(ch->bufcnt);
@@ -1944,10 +1977,10 @@
 		n = Maxframes;
 
 	if(waserror()){
-		eventlog("getdev: %d.%d ignored: %s\n", major, ch->minor, up->errstr);
+		eventlog("getdev: %ud.%ud ignored: %s\n", major, h->minor, up->errstr);
 		return;
 	}
-	d = getdev(major, ch->minor, n);
+	d = getdev(major, h->minor, n);
 	poperror();
 	if(d == 0)
 		return;
@@ -1960,7 +1993,7 @@
 		nexterror();
 	}
 
-	l = newdevlink(d, nl, ch);		/* add this interface. */
+	l = newdevlink(d, nl, h);		/* add this interface. */
 
 	d->fwver = nhgets(ch->fwver);
 	n = nhgets(ch->cslen);
@@ -1967,15 +2000,17 @@
 	if(n > sizeof d->config)
 		n = sizeof d->config;
 	d->nconfig = n;
-	memmove(d->config, ch + 1, n);
-	if(l != 0 && d->flag & Djumbo){
-		n = getmtu(nl->mtu) - AOEATASZ;
-		n /= Aoesectsz;
-		if(n > ch->scnt)
-			n = ch->scnt;
-		n = n? n * Aoesectsz: Dbcnt;
+	memmove(d->config, (uchar*)ch + Aoehsz + Aoecfgsz, n);
+
+	/* manually set mtu may be reset lower if conditions warrant */
+	if(l){
+		n = devmaxdata(d);
+		if((d->flag & Djumbo) == 0)
+			n = Dbcnt;
+		if(n > d->maxmtu)
+			n = d->maxmtu;
 		if(n != d->maxbcnt){
-			eventlog("%æ: setting %d byte data frames on %s:%E\n",
+			eventlog("%æ: setting %d byte mtu on %s:%E\n",
 				d, n, nl->path, nl->ea);
 			d->maxbcnt = n;
 		}
@@ -1986,59 +2021,18 @@
 	qunlock(d);
 }
 
-void
-aoeidmove(char *p, ushort *u, unsigned n)
-{
-	int i;
-	char *op, *e, *s;
-
-	op = p;
-	/*
-	 * the ushort `*u' is sometimes not aligned on a short boundary,
-	 * so dereferencing u[i] causes an alignment exception on
-	 * some machines.
-	 */
-	s = (char *)u;
-	for(i = 0; i < n; i += 2){
-		*p++ = s[i + 1];
-		*p++ = s[i];
-	}
-	*p = 0;
-	while(p > op && *--p == ' ')
-		*p = 0;
-	e = p;
-	p = op;
-	while(*p == ' ')
-		p++;
-	memmove(op, p, n - (e - p));
-}
-
 static vlong
 aoeidentify(Aoedev *d, ushort *id)
 {
-	int i;
 	vlong s;
 
-	d->flag &= ~(Dllba|Dpower|Dsmart|Dnop|Dup);
-
-	i = gbit16(id+83) | gbit16(id+86);
-	if(i & (1<<10)){
-		d->flag |= Dllba;
-		s = gbit64(id+100);
-	}else
-		s = gbit32(id+60);
-
-	i = gbit16(id+83);
-	if((i>>14) == 1) {
-		if(i & (1<<3))
-			d->flag  |= Dpower;
-		i = gbit16(id+82);
-		if(i & 1)
-			d->flag  |= Dsmart;
-		if(i & (1<<14))
-			d->flag  |= Dnop;
+	s = idfeat(d, id);
+	if(s == -1)
+		return -1;
+	if((d->feat&Dlba) == 0){
+		dprint("%æ: no lba support\n", d);
+		return -1;
 	}
-//	eventlog("%æ up\n", d);
 	d->flag |= Dup;
 	memmove(d->ident, id, sizeof d->ident);
 	return s;
@@ -2064,13 +2058,14 @@
 	osectors = d->realbsize;
 	memmove(oserial, d->serial, sizeof d->serial);
 
-	aoeidmove(d->serial, id+10, 20);
-	aoeidmove(d->firmware, id+23, 8);
-	aoeidmove(d->model, id+27, 40);
+	idmove(d->serial, id+10, 20);
+	idmove(d->firmware, id+23, 8);
+	idmove(d->model, id+27, 40);
+	/* idss() */
+	/* d->wwn = idwwn(d, id); */
 
 	s *= Aoesectsz;
-	if((osectors == 0 || osectors != s) &&
-	    memcmp(oserial, d->serial, sizeof oserial) != 0){
+	if(osectors != s || memcmp(oserial, d->serial, sizeof oserial)){
 		d->bsize = s;
 		d->realbsize = s;
 //		d->mediachange = 1;
@@ -2082,24 +2077,26 @@
 static void
 atarsp(Block *b)
 {
-	unsigned n;
-	short major;
+	uint n;
+	ushort major;
 	Aoeata *ahin, *ahout;
+	Aoehdr *h;
 	Aoedev *d;
 	Frame *f;
 	Srb *srb;
 
-	ahin = (Aoeata*)b->rp;
-	major = nhgets(ahin->major);
-	d = mm2dev(major, ahin->minor);
+	h = (Aoehdr*)b->rp;
+	major = nhgets(h->major);
+	d = mm2dev(major, h->minor);
 	if(d == nil)
 		return;
+	ahin = (Aoeata*)(b->rp + Aoehsz);
 	qlock(d);
 	if(waserror()){
 		qunlock(d);
 		nexterror();
 	}
-	n = nhgetl(ahin->tag);
+	n = nhgetl(h->tag);
 	f = getframe(d, n);
 	if(f == nil){
 		dprint("%æ: unexpected response; tag %ux\n", d, n);
@@ -2106,7 +2103,7 @@
 		goto bail;
 	}
 	rtupdate(f->dl, tsince(f->tag));
-	ahout = (Aoeata*)f->hdr;
+	ahout = (Aoeata*)(f->hdr + Aoehsz);
 	srb = f->srb;
 
 	if(ahin->cmdstat & 0xa9){
@@ -2119,12 +2116,12 @@
 		switch(ahout->cmdstat){
 		case Crd:
 		case Crdext:
-			if(BLEN(b) - AOEATASZ < n){
-				eventlog("%æ: runt read blen %ld expect %d\n",
+			if(BLEN(b) - (Aoehsz + Aoeatasz) != n){
+				eventlog("%æ: misread blen %ld expect %d\n",
 					d, BLEN(b), n);
 				goto bail;
 			}
-			memmove(f->dp, (uchar *)ahin + AOEATASZ, n);
+			memmove(f->dp, b->rp + Aoehsz + Aoeatasz, n);
 		case Cwr:
 		case Cwrext:
 			if(n > Dbcnt)
@@ -2137,12 +2134,14 @@
 			}
 			break;
 		case Cid:
-			if(BLEN(b) - AOEATASZ < 512){
+			if(BLEN(b) - (Aoehsz + Aoeatasz) < 512){
 				eventlog("%æ: runt identify blen %ld expect %d\n",
-					d, BLEN(b), n);
+					d, BLEN(b), 512 + Aoehsz + Aoeatasz);
 				goto bail;
 			}
-			identify(d, (ushort*)((uchar *)ahin + AOEATASZ));
+			identify(d, (ushort*)(b->rp + Aoehsz + Aoeatasz));
+			free(srb);		/* BOTCH */
+			srb = nil;
 			break;
 		default:
 			eventlog("%æ: unknown ata command %.2ux \n",
@@ -2150,9 +2149,11 @@
 		}
 	}
 
-	if(srb && --srb->nout == 0 && srb->len == 0)
-		wakeup(srb);
 	f->srb = nil;
+	if(srb){
+		srb->nout--;
+		srbwakeup(srb);
+	}
 	f->tag = Tfree;
 	d->nout--;
 
@@ -2177,7 +2178,7 @@
 	kstrcpy(name, nl->path, Maxpath);
 
 	if(waserror()){
-		eventlog("netrdaoe exiting: %s\n", up->errstr);
+		eventlog("netrdaoe@%s: exiting: %s\n", name, up->errstr);
 		netlinks.reader[idx] = 0;
 		wakeup(netlinks.rendez + idx);
 		pexit(up->errstr, 1);
@@ -2185,38 +2186,27 @@
 	if(autodiscover)
 		discover(0xffff, 0xff);
 	for (;;) {
-		if(!(nl->flag & Dup)) {
-			uprint("%s: netlink is down", name);
-			error(up->genbuf);
-		}
-		if (nl->dc == nil)
+		if((nl->flag & Dup) == 0)
+			error("netlink is down");
+		if(nl->dc == nil)
 			panic("netrdaoe: nl->dc == nil");
 		b = devtab[nl->dc->type]->bread(nl->dc, 1<<16, 0);
-		if(b == nil) {
-			uprint("%s: nil read from network", name);
-			error(up->genbuf);
-		}
+		if(b == nil)
+			error("network read");
 		h = (Aoehdr*)b->rp;
 		if(h->verflag & AFrsp)
 			if(s = aoeerror(h)){
-				eventlog("%s: %s\n", nl->path, up->errstr);
+				eventlog("%s: %s\n", nl->path, s);
 				errrsp(b, s);
-			}else
-				switch(h->cmd){
-				case ACata:
-					atarsp(b);
-					break;
-				case ACconfig:
-					qcfgrsp(b, nl);
-					break;
-				default:
-					if((h->cmd & 0xf0) == 0){
-						eventlog("%s: unknown cmd %d\n",
-							nl->path, h->cmd);
-						errrsp(b, "unknown command");
-					}
-					break;
-				}
+			}else if(h->cmd == ACata)
+				atarsp(b);
+			else if(h->cmd == ACconfig)
+				qcfgrsp(b, nl);
+			else if((h->cmd & 0xf0) == 0){
+				eventlog("%s: unknown cmd %d\n",
+					nl->path, h->cmd);
+				errrsp(b, "unknown command");
+			}
 		freeb(b);
 	}
 }
@@ -2234,7 +2224,7 @@
 		cclose(c);
 		nexterror();
 	}
-	if (c == nil)
+	if(c == nil)
 		panic("æ: getaddr: c == nil");
 	n = devtab[c->type]->read(c, buf, sizeof buf-1, 0);
 	poperror();
@@ -2252,7 +2242,7 @@
 	Chan *dc, *cc, *mtu;
 	Netlink *nl;
 
-	snprint(addr, sizeof addr, "%s!%#x", path, Aoetype);
+	snprint(addr, sizeof addr, "%s!0x%x", path, Aoetype);
 	dc = chandial(addr, nil, nil, &cc);
 	snprint(addr, sizeof addr, "%s/mtu", path);
 	if(waserror())
@@ -2302,7 +2292,7 @@
 		if(n->dc && strcmp(n->path, path) == 0)
 			break;
 	unlock(&netlinks);
-	if (n >= e)
+	if(n == e)
 		error("device not bound");
 
 	/*
@@ -2371,7 +2361,7 @@
 	wlock(&devs);
 	for(p = d = devs.d; d; d = next){
 		next = d->next;
-		if(d->ndl > 0) {
+		if(d->ndl > 0){
 			p = d;
 			continue;
 		}
@@ -2392,8 +2382,37 @@
 }
 
 static void
-removeaoedev(Aoedev *d)
+strtoss(char *f, uint *shelf, uint *slot)
 {
+	char *s;
+
+	*shelf = 0xffff;
+	*slot = 0xff;
+	if(!f)
+		return;
+	*shelf = strtol(f, &s, 0);
+	if(s == f || *shelf > 0xffff)
+		error("bad shelf");
+	f = s;
+	if(*f++ == '.'){
+		*slot = strtol(f, &s, 0);
+		if(s == f || *slot > 0xff)
+			error("bad slot");
+	}
+}
+
+static void
+discoverstr(char *f)
+{
+	uint shelf, slot;
+
+	strtoss(f, &shelf, &slot);
+	discover(shelf, slot);
+}
+
+static void
+removedev(Aoedev *d)
+{
 	int i;
 	Aoedev *p;
 
@@ -2400,9 +2419,9 @@
 	wlock(&devs);
 	p = 0;
 	if(d != devs.d)
-		for(p = devs.d; p; p = p->next)
-			if(p->next == d)
-				break;
+	for(p = devs.d; p; p = p->next)
+		if(p->next == d)
+			break;
 	qlock(d);
 	d->flag &= ~Dup;
 	newvers(d);
@@ -2421,60 +2440,45 @@
 	wunlock(&devs);
 }
 
-static void
-removedev(char *name)
-{
-	Aoedev *d, *p;
 
-	wlock(&devs);
-	for(p = d = devs.d; d; p = d, d = d->next)
-		if(strcmp(name, unitname(d)) == 0) {
-			wunlock(&devs);
-			removeaoedev(p);
-			return;
-		}
-	wunlock(&devs);
-	error("device not bound");
-}
-
 static void
-discoverstr(char *f)
-{
-	ushort shelf, slot;
-	ulong sh;
-	char *s;
-
-	if(f == 0){
-		discover(0xffff, 0xff);
-		return;
-	}
-
-	shelf = sh = strtol(f, &s, 0);
-	if(s == f || sh > 0xffff)
-		error("bad shelf");
-	f = s;
-	if(*f++ == '.'){
-		slot = strtol(f, &s, 0);
-		if(s == f || slot > 0xff)
-			error("bad shelf");
-	}else
-		slot = 0xff;
-	discover(shelf, slot);
-}
-
-
-static void
 aoeremove(Chan *c)
 {
 	switch(TYPE(c->qid)){
 	default:
+	case Qzero:
+	case Qtopdir:
+	case Qtoplog:
+	case Qtopctl:
+	case Qctl:
+	case Qdata:
+	case Qconfig:
+	case Qident:
 		error(Eperm);
 	case Qunitdir:
-		removeaoedev(unit2dev(UNIT(c->qid)));
+		removedev(unit2dev(UNIT(c->qid)));
 		break;
 	}
 }
 
+static void
+removestr(char *f)
+{
+	uint shelf, slot;
+	Aoedev *d;
+
+	strtoss(f, &shelf, &slot);
+	wlock(&devs);
+	for(d = devs.d; d; d = d->next)
+		if(shelf == d->major && slot == d->minor){
+			wunlock(&devs);	/* BOTCH */
+			removedev(d);
+			return;
+		}
+	wunlock(&devs);
+	error("device not bound");
+}
+
 static long
 topctlwrite(void *db, long n)
 {
@@ -2509,28 +2513,26 @@
 	f = cb->f[1];
 	switch(ct->index){
 	case Autodiscover:
-		autodiscover = toggle(f, autodiscover);
+		autodiscover = toggle(f, autodiscover, 1);
 		break;
 	case Bind:
 		netbind(f);
 		break;
 	case Debug:
-		debug = toggle(f, debug);
+		debug = toggle(f, debug, 1);
 		break;
 	case Discover:
 		discoverstr(f);
 		break;
 	case Rediscover:
-		rediscover = toggle(f, rediscover);
+		rediscover = toggle(f, rediscover, 1);
 		break;
 	case Remove:
-		removedev(f);
+		removestr(f);	/* depricated */
 		break;
 	case Unbind:
 		netunbind(f);
 		break;
-	default:
-		cmderror(cb, "unknown aoe control message");
 	}
 	poperror();
 	free(cb);
--- a/sys/src/9/port/devsd.c
+++ b/sys/src/9/port/devsd.c
@@ -15,7 +15,9 @@
 extern Dev sddevtab;
 extern SDifc* sdifc[];
 
-static char Echange[] = "media or partition has changed";
+static	char	Echange[]	= "media or partition has changed";
+static	char	Enoata[]		= "raw ata commands not supported";
+static	char	Enoscsi[]	= "raw scsi commands not supported";
 
 static char devletters[] = "0123456789"
 	"abcdefghijklmnopqrstuvwxyz"
@@ -23,8 +25,13 @@
 
 static SDev *devs[sizeof devletters-1];
 static QLock devslock;
+static SDunit topctlunit;
 
 enum {
+	Ahdrsz		= 2,
+};
+
+enum {
 	Rawcmd,
 	Rawdata,
 	Rawstatus,
@@ -40,6 +47,7 @@
 	Qctl		= Qunitbase,
 	Qraw,
 	Qpart,
+	Qextra,
 
 	TypeLOG		= 4,
 	NType		= (1<<TypeLOG),
@@ -292,7 +300,7 @@
 		}
 		sdev->unitflg[subno] = 1;
 
-		snprint(buf, sizeof(buf), "%s%d", sdev->name, subno);
+		snprint(buf, sizeof buf, "%s%x", sdev->name, subno);
 		kstrdup(&unit->name, buf);
 		kstrdup(&unit->user, eve);
 		unit->perm = 0555;
@@ -323,15 +331,14 @@
 sdreset(void)
 {
 	int i;
-	SDev *sdev;
 
 	/*
 	 * Probe all known controller types and register any devices found.
 	 */
 	for(i = 0; sdifc[i] != nil; i++){
-		if(sdifc[i]->pnp == nil || (sdev = sdifc[i]->pnp()) == nil)
+		if(sdifc[i]->pnp == nil)
 			continue;
-		sdadddevs(sdev);
+		sdadddevs(sdifc[i]->pnp());
 	}
 }
 
@@ -344,8 +351,8 @@
 	for(; sdev; sdev=next){
 		next = sdev->next;
 
-		sdev->unit = (SDunit**)malloc(sdev->nunit * sizeof(SDunit*));
-		sdev->unitflg = (int*)malloc(sdev->nunit * sizeof(int));
+		sdev->unit = malloc(sdev->nunit * sizeof(SDunit*));
+		sdev->unitflg = malloc(sdev->nunit * sizeof(int));
 		if(sdev->unit == nil || sdev->unitflg == nil){
 			print("sdadddevs: out of memory\n");
 		giveup:
@@ -392,11 +399,12 @@
 {
 	Qid q;
 	uvlong l;
+	SDfile *e;
 	SDpart *pp;
 	SDperm *perm;
 	SDunit *unit;
 	SDev *sdev;
-	int rv;
+	int rv, t;
 
 	sdev = sdgetdev(DEV(c->qid));
 	assert(sdev);
@@ -438,6 +446,18 @@
 		devdir(c, q, pp->name, l, pp->user, pp->perm, dp);
 		rv = 1;
 		break;
+	case Qextra:
+		t = PART(c->qid);
+		if(t >= unit->nefile)
+			break;
+		mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), PART(c->qid), Qextra),
+			unit->vers, QTFILE);
+		e = unit->efile + t;
+		if(emptystr(e->user))
+			kstrdup(&e->user, eve);
+		devdir(c, q, e->name, 0, e->user, e->perm, dp);
+		rv = 1;
+		break;
 	}
 
 	decref(&sdev->r);
@@ -448,11 +468,20 @@
 sd1gen(Chan* c, int i, Dir* dp)
 {
 	Qid q;
+	SDperm *p;
 
 	switch(i){
 	case Qtopctl:
 		mkqid(&q, QID(0, 0, 0, Qtopctl), 0, QTFILE);
-		devdir(c, q, "sdctl", 0, eve, 0640, dp);
+		qlock(&topctlunit.ctl);
+		p = &topctlunit.ctlperm;
+		if(p->user == nil || p->user[0] == 0){
+			kstrdup(&p->name, "sdctl");
+			kstrdup(&p->user, eve);
+			p->perm = 0640;
+		}
+		devdir(c, q, p->name, 0, p->user, p->perm, dp);
+		qunlock(&topctlunit.ctl);
 		return 1;
 	}
 	return -1;
@@ -459,6 +488,26 @@
 }
 
 static int
+efilegen(Chan *c, SDunit *unit, int i, Dir *dp)
+{
+	Qid q;
+	SDfile *e;
+
+	i -= SDnpart;
+	if(unit->nefile == 0 || i >= unit->nefile)
+		return -1;
+	if(i < 0)
+		return 0;
+	e = unit->efile + i;
+	if(emptystr(e->user))
+		kstrdup(&e->user, eve);
+	mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), i, Qextra),
+		unit->vers, QTFILE);
+	devdir(c, q, e->name, 0, e->user, e->perm, dp);
+	return 1;
+}
+
+static int
 sdgen(Chan* c, char*, Dirtab*, int, int s, Dir* dp)
 {
 	Qid q;
@@ -553,17 +602,18 @@
 		}
 		i -= Qpart;
 		if(unit->part == nil || i >= unit->npart){
+			r = efilegen(c, unit, i, dp);
 			qunlock(&unit->ctl);
 			decref(&sdev->r);
-			break;
+			return r;
 		}
 		pp = &unit->part[i];
-		if(!pp->valid){
+		if(!pp->valid || unit->sectors == 0){
 			qunlock(&unit->ctl);
 			decref(&sdev->r);
 			return 0;
 		}
-		l = (pp->end - pp->start) * unit->secsize;
+		l = (pp->end - pp->start) * (uvlong)unit->secsize;
 		mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), i, Qpart),
 			unit->vers+pp->vers, QTFILE);
 		if(emptystr(pp->user))
@@ -575,6 +625,7 @@
 	case Qraw:
 	case Qctl:
 	case Qpart:
+	case Qextra:
 		if((sdev = sdgetdev(DEV(c->qid))) == nil){
 			devdir(c, q, "unavailable", 0, eve, 0, dp);
 			return 1;
@@ -715,10 +766,12 @@
 	}
 }
 
+#define iskaddr(a)	((uintptr)(a) > KZERO)
+
 static long
 sdbio(Chan* c, int write, char* a, long len, uvlong off)
 {
-	int nchange;
+	int nchange, hard, allocd;
 	long l;
 	uchar *b;
 	SDpart *pp;
@@ -785,21 +838,30 @@
 		poperror();
 	}
 
-	b = sdmalloc(nb*unit->secsize);
-	if(b == nil)
-		error(Enomem);
+	offset = off%unit->secsize;
+	if(offset+len > nb*unit->secsize)
+		len = nb*unit->secsize - offset;
+	hard = offset || write && len%unit->secsize;
+
+	if(iskaddr(a) && !hard) {
+		b = (uchar*)a;
+		allocd = 0;
+	}else{
+		b = sdmalloc(nb*unit->secsize);
+		if(b == nil)
+			error(Enomem);
+		allocd = 1;
+	}
 	if(waserror()){
-		sdfree(b);
+		if(allocd)
+			sdfree(b);
 		if(!(unit->inquiry[1] & 0x80))
 			decref(&sdev->r);		/* gadverdamme! */
 		nexterror();
 	}
 
-	offset = off%unit->secsize;
-	if(offset+len > nb*unit->secsize)
-		len = nb*unit->secsize - offset;
 	if(write){
-		if(offset || (len%unit->secsize)){
+		if(hard){
 			l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno);
 			if(l < 0)
 				error(Eio);
@@ -810,7 +872,8 @@
 					len = l;
 			}
 		}
-		memmove(b+offset, a, len);
+		if(allocd)
+			memmove(b+offset, a, len);
 		l = unit->dev->ifc->bio(unit, 0, 1, b, nb, bno);
 		if(l < 0)
 			error(Eio);
@@ -827,9 +890,11 @@
 			len = 0;
 		else if(len > l - offset)
 			len = l - offset;
-		memmove(a, b+offset, len);
+		if(allocd)
+			memmove(a, b+offset, len);
 	}
-	sdfree(b);
+	if(allocd)
+		sdfree(b);
 	poperror();
 
 	if(unit->inquiry[1] & 0x80){
@@ -844,35 +909,60 @@
 static long
 sdrio(SDreq* r, void* a, long n)
 {
+	char *errstr;
+	int rv;
 	void *data;
+	SDunit *u;
+	int (*f)(SDreq*);
 
 	if(n >= SDmaxio || n < 0)
 		error(Etoobig);
-
-	data = nil;
-	if(n){
-		if((data = sdmalloc(n)) == nil)
-			error(Enomem);
-		if(r->write)
-			memmove(data, a, n);
+	u = r->unit;
+	if(u->haversense && r->cmd[0] == 0x03){
+		u->haversense = 0;
+		r->rlen = sizeof u->rsense;
+		if(r->rlen > n)
+			r->rlen = n;
+		memmove(a, u->rsense, r->rlen);
+		r->status = SDok;
+		return r->rlen;
 	}
-	r->data = data;
-	r->dlen = n;
 
+	data = nil;
+	if(n > 0 && (data = sdmalloc(n)) == nil)
+		error(Enomem);
 	if(waserror()){
 		sdfree(data);
 		r->data = nil;
 		nexterror();
 	}
+	if(r->write && n > 0)
+		memmove(data, a, n);
+	r->data = data;
+	r->dlen = n;
 
-	if(r->unit->dev->ifc->rio(r) != SDok)
+	if(r->proto == SData){
+		f = u->dev->ifc->ataio;
+		errstr = Enoata;
+	}else{
+		f = u->dev->ifc->rio;
+		errstr = Enoscsi;
+	}
+	if(f == nil)
+		error(errstr);
+	rv = f(r);
+	if(r->flags & SDvalidsense){
+		memmove(u->rsense, r->sense, sizeof u->rsense);
+		u->haversense = 1;
+	}
+	if(rv != SDok)
 		error(Eio);
 
 	if(!r->write && r->rlen > 0)
 		memmove(a, data, r->rlen);
+	poperror();
 	sdfree(data);
 	r->data = nil;
-	poperror();
 
 	return r->rlen;
 }
@@ -879,6 +969,9 @@
 
 /*
  * SCSI simulation for non-SCSI devices
+ *
+ * see /sys/src/cmd/scuzz/sense.c for information on key.
+ * see /sys/lib/scsicodes for asc:ascq codes
  */
 int
 sdsetsense(SDreq *r, int status, int key, int asc, int ascq)
@@ -908,37 +1001,8 @@
 }
 
 int
-sdmodesense(SDreq *r, uchar *cmd, void *info, int ilen)
+sdfakescsi(SDreq *r)
 {
-	int len;
-	uchar *data;
-
-	/*
-	 * Fake a vendor-specific request with page code 0,
-	 * return the drive info.
-	 */
-	if((cmd[2] & 0x3F) != 0 && (cmd[2] & 0x3F) != 0x3F)
-		return sdsetsense(r, SDcheck, 0x05, 0x24, 0);
-	len = (cmd[7]<<8)|cmd[8];
-	if(len == 0)
-		return SDok;
-	if(len < 8+ilen)
-		return sdsetsense(r, SDcheck, 0x05, 0x1A, 0);
-	if(r->data == nil || r->dlen < len)
-		return sdsetsense(r, SDcheck, 0x05, 0x20, 1);
-	data = r->data;
-	memset(data, 0, 8);
-	data[0] = ilen>>8;
-	data[1] = ilen;
-	if(ilen)
-		memmove(data+8, info, ilen);
-	r->rlen = 8+ilen;
-	return sdsetsense(r, SDok, 0, 0, 0);
-}
-
-int
-sdfakescsi(SDreq *r, void *info, int ilen)
-{
 	uchar *cmd, *p;
 	uvlong len;
 	SDunit *unit;
@@ -948,25 +1012,6 @@
 	unit = r->unit;
 
 	/*
-	 * Rewrite read(6)/write(6) into read(10)/write(10).
-	 */
-	switch(cmd[0]){
-	case 0x08:	/* read */
-	case 0x0A:	/* write */
-		cmd[9] = 0;
-		cmd[8] = cmd[4];
-		cmd[7] = 0;
-		cmd[6] = 0;
-		cmd[5] = cmd[3];
-		cmd[4] = cmd[2];
-		cmd[3] = cmd[1] & 0x0F;
-		cmd[2] = 0;
-		cmd[1] &= 0xE0;
-		cmd[0] |= 0x20;
-		break;
-	}
-
-	/*
 	 * Map SCSI commands into ATA commands for discs.
 	 * Fail any command with a LUN except INQUIRY which
 	 * will return 'logical unit not supported'.
@@ -1018,13 +1063,15 @@
 		/*
 		 * Read capacity returns the LBA of the last sector.
 		 */
-		len = unit->sectors - 1;
+		len = unit->sectors;
+		if(len > 0)
+			len--;
 		p = r->data;
 		*p++ = len>>24;
 		*p++ = len>>16;
 		*p++ = len>>8;
 		*p++ = len;
-		len = 512;
+		len = unit->secsize;
 		*p++ = len>>24;
 		*p++ = len>>16;
 		*p++ = len>>8;
@@ -1040,7 +1087,9 @@
 		/*
 		 * Read capcity returns the LBA of the last sector.
 		 */
-		len = unit->sectors - 1;
+		len = unit->sectors;
+		if(len > 0)
+			len--;
 		p = r->data;
 		*p++ = len>>56;
 		*p++ = len>>48;
@@ -1050,7 +1099,7 @@
 		*p++ = len>>16;
 		*p++ = len>>8;
 		*p++ = len;
-		len = 512;
+		len = unit->secsize;
 		*p++ = len>>24;
 		*p++ = len>>16;
 		*p++ = len>>8;
@@ -1057,12 +1106,12 @@
 		*p++ = len;
 		r->rlen = p - (uchar*)r->data;
 		return sdsetsense(r, SDok, 0, 0, 0);
-
-	case 0x5A:	/* mode sense */
-		return sdmodesense(r, cmd, info, ilen);
-
-	case 0x28:	/* read */
-	case 0x2A:	/* write */
+	case 0x08:	/* read6 */
+	case 0x0a:	/* write6 */
+	case 0x28:	/* read10 */
+	case 0x2a:	/* write10 */
+	case 0xa8:	/* read12 */
+	case 0xaa:	/* write12 */
 	case 0x88:	/* read16 */
 	case 0x8a:	/* write16 */
 		return SDnostatus;
@@ -1069,13 +1118,105 @@
 	}
 }
 
+int
+sdfakescsirw(SDreq *r, uvlong *llba, int *nsec, int *rwp)
+{
+	uchar *c;
+	int rw, count;
+	uvlong lba;
+
+	c = r->cmd;
+	rw = SDread;
+	if((c[0] & 0xf) == 0xa)
+		rw = SDwrite;
+	switch(c[0]){
+	case 0x08:	/* read6 */
+	case 0x0a:
+		lba = (c[1] & 0xf)<<16 | c[2]<<8 | c[3];
+		count = c[4];
+		break;
+	case 0x28:	/* read10 */
+	case 0x2a:
+		lba = c[2]<<24 | c[3]<<16 | c[4]<<8 | c[5];
+		count = c[7]<<8 | c[8];
+		break;
+	case 0xa8:	/* read12 */
+	case 0xaa:
+		lba = c[2]<<24 | c[3]<<16 | c[4]<<8 | c[5];
+		count = c[6]<<24 | c[7]<<16 | c[8]<<8 | c[9];
+		break;
+	case 0x88:	/* read16 */
+	case 0x8a:
+		/* ata commands only go to 48-bit lba */
+		if(c[2] || c[3])
+			return sdsetsense(r, SDcheck, 3, 0xc, 2);
+		lba = (uvlong)c[4]<<40 | (uvlong)c[5]<<32;
+		lba |= c[6]<<24 | c[7]<<16 | c[8]<<8 | c[9];
+		count = c[10]<<24 | c[11]<<16 | c[12]<<8 | c[13];
+		break;
+	default:
+		print("%s: bad cmd 0x%.2ux\n", r->unit->name, c[0]);
+		r->status  = sdsetsense(r, SDcheck, 0x05, 0x20, 0);
+		return SDcheck;
+	}
+	if(r->data == nil)
+		return SDok;
+	if(r->dlen < count * r->unit->secsize)
+		count = r->dlen/r->unit->secsize;
+	if(rwp)
+		*rwp = rw;
+	*llba = lba;
+	*nsec = count;
+	return SDnostatus;
+}
+
 static long
+extrarw(int write, Chan *c, void *a, long n, vlong off)
+{
+	int i;
+	SDrw *f;
+	SDev *sdev;
+	SDunit *unit;
+
+	sdev = sdgetdev(DEV(c->qid));
+	if(sdev == nil)
+		error(Enonexist);
+	if(waserror()){
+		decref(&sdev->r);
+		nexterror();
+	}
+	unit = sdev->unit[UNIT(c->qid)];
+	if(unit->vers != c->qid.vers)
+		error(Echange);
+	unit = sdev->unit[UNIT(c->qid)];
+	i = PART(c->qid);
+	if(i >= unit->nefile)
+		error(Enonexist);
+	f = unit->efile[i].r;
+	if(write)
+		f = unit->efile[i].w;
+	if(i >= unit->nefile || f == nil)
+		error(Eperm);
+	n = f(unit, c, a, n, off);
+	poperror();
+	decref(&sdev->r);
+	return n;
+}
+
+static char*
+deftopctl(SDev *s, char *p, char *e)
+{
+	return seprint(p, e, "sd%c %s %d units\n", s->idno, s->ifc->name, s->nunit);
+}
+
+static long
 sdread(Chan *c, void *a, long n, vlong off)
 {
 	char *p, *e, *buf;
+	SDev *sdev;
 	SDpart *pp;
+	SDreq *r;
 	SDunit *unit;
-	SDev *sdev;
 	ulong offset;
 	int i, l, m, status;
 
@@ -1093,6 +1234,8 @@
 			sdev = devs[i];
 			if(sdev && sdev->ifc->rtopctl)
 				p = sdev->ifc->rtopctl(sdev, p, e);
+			else if(sdev)
+				p = deftopctl(sdev, p, e);
 		}
 		qunlock(&devslock);
 		n = readstr(off, a, n, buf);
@@ -1157,23 +1300,38 @@
 		}
 		if(unit->state == Rawdata){
 			unit->state = Rawstatus;
-			i = sdrio(unit->req, a, n);
+			r = unit->req;
+			r->timeout = 0;
+			i = sdrio(r, a, n);
 		}
 		else if(unit->state == Rawstatus){
-			status = unit->req->status;
-			unit->state = Rawcmd;
-			free(unit->req);
+			r = unit->req;
 			unit->req = nil;
-			i = readnum(0, a, n, status, NUMSIZE);
+			unit->state = Rawcmd;
+			status = r->status;
+			if(r->proto == SData){
+				p = a;
+				i = 16 + Ahdrsz;
+				if(n < i)
+					i = n;
+				if(i > 0)
+					p[0] = status;
+				if(i > Ahdrsz)
+					memmove(p + Ahdrsz, r->cmd, i - Ahdrsz);
+			}else
+				i = readnum(0, a, n, status, NUMSIZE);
+			free(r);
 		} else
 			i = 0;
+		poperror();
 		qunlock(&unit->raw);
 		decref(&sdev->r);
-		poperror();
 		return i;
 
 	case Qpart:
 		return sdbio(c, 0, a, n, off);
+	case Qextra:
+		return extrarw(0, c, a, n, off);
 	}
 }
 
@@ -1183,7 +1341,8 @@
 sdwrite(Chan* c, void* a, long n, vlong off)
 {
 	char *f0;
-	int i;
+	int i, atacdb, proto, ataproto;
+	uchar *u;
 	uvlong end, start;
 	Cmdbuf *cb;
 	SDifc *ifc;
@@ -1250,7 +1409,7 @@
 			error(Ebadctl);
 		poperror();
 		poperror();
-		if (sdev)
+		if(sdev)
 			decref(&sdev->r);
 		free(cb);
 		break;
@@ -1299,6 +1458,9 @@
 		break;
 
 	case Qraw:
+		proto = SDcdb;
+		ataproto = 0;
+		atacdb = 0;
 		sdev = sdgetdev(DEV(c->qid));
 		if(sdev == nil)
 			error(Enonexist);
@@ -1311,18 +1473,34 @@
 		}
 		switch(unit->state){
 		case Rawcmd:
+			/* sneaky ata commands */
+			u = a;
+			if(n > 1 && *u == 0xff){
+				proto = SData;
+				ataproto = u[1];
+				a = u + 2;
+				atacdb = Ahdrsz;
+				n -= Ahdrsz;
+			}		
 			if(n < 6 || n > sizeof(req->cmd))
 				error(Ebadarg);
 			if((req = malloc(sizeof(SDreq))) == nil)
 				error(Enomem);
 			req->unit = unit;
+			if(waserror()){
+				free(req);
+				nexterror();
+			}
 			memmove(req->cmd, a, n);
+			poperror();
 			req->clen = n;
-			req->flags = SDnosense;
+		/*	req->flags = SDnosense;	*/
 			req->status = ~0;
-
+			req->proto = proto;
+			req->ataproto = ataproto;
 			unit->req = req;
 			unit->state = Rawdata;
+			n += atacdb;
 			break;
 
 		case Rawstatus:
@@ -1333,15 +1511,18 @@
 
 		case Rawdata:
 			unit->state = Rawstatus;
-			unit->req->write = 1;
-			n = sdrio(unit->req, a, n);
+			req = unit->req;
+			req->write = 1;
+			n = sdrio(req, a, n);
 		}
+		poperror();
 		qunlock(&unit->raw);
 		decref(&sdev->r);
-		poperror();
 		break;
 	case Qpart:
 		return sdbio(c, 1, a, n, off);
+	case Qextra:
+		return extrarw(1, c, a, n, off);
 	}
 
 	return n;
@@ -1358,17 +1539,23 @@
 
 	if(c->qid.type & QTDIR)
 		error(Eperm);
-
-	sdev = sdgetdev(DEV(c->qid));
-	if(sdev == nil)
-		error(Enonexist);
-	unit = sdev->unit[UNIT(c->qid)];
+	if(TYPE(c->qid) == Qtopctl){
+		unit = &topctlunit;
+		sdev = nil;
+	}else{
+		sdev = sdgetdev(DEV(c->qid));
+		if(sdev == nil)
+			error(Enonexist);
+		unit = sdev->unit[UNIT(c->qid)];
+	}
 	qlock(&unit->ctl);
+
 	d = nil;
 	if(waserror()){
 		free(d);
 		qunlock(&unit->ctl);
-		decref(&sdev->r);
+		if(sdev != nil)
+			decref(&sdev->r);
 		nexterror();
 	}
 
@@ -1375,6 +1562,7 @@
 	switch(TYPE(c->qid)){
 	default:
 		error(Eperm);
+	case Qtopctl:
 	case Qctl:
 		perm = &unit->ctlperm;
 		break;
@@ -1396,14 +1584,22 @@
 	n = convM2D(dp, n, &d[0], (char*)&d[1]);
 	if(n == 0)
 		error(Eshortstat);
+	if(d->atime != ~0 ||  d->mtime  != ~0 ||  d->length  != ~0)
+		error(Eperm);
+	if(!emptystr(d[0].muid) || !emptystr(d[0].name))
+		error(Eperm);
 	if(!emptystr(d[0].uid))
 		kstrdup(&perm->user, d[0].uid);
+	if(!emptystr(d[0].gid) && strcmp(d[0].gid, eve) != 0)
+		error(Eperm);
 	if(d[0].mode != ~0UL)
 		perm->perm = (perm->perm & ~0777) | (d[0].mode & 0777);
 
 	free(d);
+	d = nil; USED(d);
 	qunlock(&unit->ctl);
-	decref(&sdev->r);
+	if(sdev != nil)
+		decref(&sdev->r);
 	poperror();
 	return n;
 }
@@ -1487,6 +1683,55 @@
 	return unconfigure(spec);
 }
 
+int
+sdaddfile(SDunit *unit, char *s, int perm, char *u, SDrw *r, SDrw *w)
+{
+	int i;
+	SDfile *e;
+	static Lock lk;
+
+	if(unit == nil)
+		return -1;
+	lock(&lk);
+	for(i = 0; i < unit->nefile; i++)
+		if(strcmp(unit->efile[i].name, s) == 0)
+			break;
+	if(i >= nelem(unit->efile)){
+		unlock(&lk);
+		return -1;
+	}
+	if(i >= unit->nefile)
+		unit->nefile = i + 1;
+	e = unit->efile + i;
+	if(e->name == nil)
+		kstrdup(&e->name, s);
+	if(e->user == nil)
+		 kstrdup(&e->user, u);
+	e->perm = perm;
+	e->r = r;
+	e->w = w;
+	unlock(&lk);
+	return 0;
+}
+
+static void
+sdshutdown(void)
+{
+	int i;
+	SDev *sd;
+
+	for(i = 0; i < nelem(devs); i++){
+		sd = devs[i];
+		if(sd == nil)
+			continue;
+		if(sd->ifc->disable == nil){
+			print("#S/sd%c: no disable function\n", devletters[i]);
+			continue;
+		}
+		sd->ifc->disable(sd);
+	}
+}
+
 Dev sddevtab = {
 	'S',
 	"sd",
@@ -1493,7 +1738,7 @@
 
 	sdreset,
 	devinit,
-	devshutdown,
+	sdshutdown,
 	sdattach,
 	sdwalk,
 	sdstat,
@@ -1544,7 +1789,7 @@
 {
 	Devport *p;
 
-	p = (Devport *)malloc((dc->nports + 1) * sizeof(Devport));
+	p = malloc((dc->nports + 1) * sizeof(Devport));
 	if(dc->nports > 0){
 		memmove(p, dc->ports, dc->nports * sizeof(Devport));
 		free(dc->ports);
@@ -1635,7 +1880,6 @@
 		if(j == nelem(options))
 			error(Ebadarg);
 	}
-	/* this has been rewritten to accomodate sdaoe */
 	if(cd.on < 0 || cd.spec == 0)
 		error(Ebadarg);
 	if(cd.on && cd.cf.type == nil)
--- /dev/null
+++ b/sys/src/9/port/led.c
@@ -1,0 +1,62 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "../port/error.h"
+#include "fns.h"
+#include "led.h"
+
+static char *ibpinames[Ibpilast] = {
+[Ibpinone]	"none",
+[Ibpinormal]	"normal",
+[Ibpilocate]	"locate",
+[Ibpifail]		"fail",
+[Ibpirebuild]	"rebuild",
+[Ibpipfa]		"pfa",
+[Ibpispare]	"spare",
+[Ibpicritarray]	"critarray",
+[Ibpifailarray]	"failarray",
+};
+
+char*
+ledname(int c)
+{
+	if(c >= 0 && c < Ibpilast)
+		return ibpinames[c];
+	return "bad index";
+}
+
+ int
+name2led(char *s)
+{
+	int i;
+
+	for(i = 0; i < nelem(ibpinames); i++)
+		if(strcmp(ibpinames[i], s) == 0)
+			return i;
+	return -1;
+}
+
+long
+ledr(Ledport *p, Chan*, void *a, long n, vlong off)
+{
+	char buf[64];
+
+	snprint(buf, sizeof buf, "%s\n", ledname(p->led));
+	return readstr(off, a, n, buf);
+}
+
+long
+ledw(Ledport *p, Chan*, void *a, long n, vlong)
+{
+	int i;
+	Cmdbuf *cb;
+
+	cb = parsecmd(a, n);
+	i = name2led(cb->f[0]);
+	free(cb);
+	if(i == -1)
+		error(Ebadarg);
+	p->led = i;
+	return n;
+}
--- /dev/null
+++ b/sys/src/9/port/led.h
@@ -1,0 +1,26 @@
+typedef struct Ledport Ledport;
+
+struct Ledport {
+	uchar	nled;
+	uchar	led;
+	ushort	ledbits;		/* implementation dependent */
+};
+
+/* http://en.wikipedia.org/wiki/IBPI */
+enum {
+	Ibpinone,
+	Ibpinormal,
+	Ibpilocate,
+	Ibpifail,
+	Ibpirebuild,
+	Ibpipfa,
+	Ibpispare,
+	Ibpicritarray,
+	Ibpifailarray,
+	Ibpilast,
+};
+
+char	*ledname(int);
+int	name2led(char*);
+long	ledr(Ledport*, Chan*, void*, long, vlong);
+long	ledw(Ledport*, Chan*, void*, long, vlong);
--- a/sys/src/9/port/lib.h
+++ b/sys/src/9/port/lib.h
@@ -85,18 +85,24 @@
 #pragma	varargck	argpos	snprint		3
 #pragma	varargck	argpos	sprint		2
 
+#pragma	varargck	type	"llb"	vlong
 #pragma	varargck	type	"lld"	vlong
 #pragma	varargck	type	"llx"	vlong
+#pragma	varargck	type	"llb"	uvlong
 #pragma	varargck	type	"lld"	uvlong
 #pragma	varargck	type	"llx"	uvlong
+#pragma	varargck	type	"lb"	long
 #pragma	varargck	type	"ld"	long
 #pragma	varargck	type	"lx"	long
+#pragma	varargck	type	"lb"	ulong
 #pragma	varargck	type	"ld"	ulong
 #pragma	varargck	type	"lx"	ulong
+#pragma	varargck	type	"b"	int
 #pragma	varargck	type	"d"	int
 #pragma	varargck	type	"x"	int
 #pragma	varargck	type	"c"	int
 #pragma	varargck	type	"C"	int
+#pragma	varargck	type	"b"	uint
 #pragma	varargck	type	"d"	uint
 #pragma	varargck	type	"x"	uint
 #pragma	varargck	type	"c"	uint
--- a/sys/src/9/port/sd.h
+++ b/sys/src/9/port/sd.h
@@ -2,6 +2,7 @@
  * Storage Device.
  */
 typedef struct SDev SDev;
+typedef struct SDfile SDfile;
 typedef struct SDifc SDifc;
 typedef struct SDpart SDpart;
 typedef struct SDperm SDperm;
@@ -22,11 +23,20 @@
 	ulong	vers;
 };
 
+typedef long SDrw(SDunit*, Chan*, void*, long, vlong);
+struct SDfile {
+	SDperm;
+	SDrw	*r;
+	SDrw	*w;
+};
+
 struct SDunit {
 	SDev*	dev;
 	int	subno;
 	uchar	inquiry[255];		/* format follows SCSI spec */
 	uchar	sense[18];		/* format follows SCSI spec */
+	uchar	rsense[18];		/* support seperate rq sense and inline return */
+	uchar	haversense;
 	SDperm;
 
 	QLock	ctl;
@@ -42,6 +52,8 @@
 	int	state;
 	SDreq*	req;
 	SDperm	rawperm;
+	SDfile	efile[5];
+	int	nefile;
 };
 
 /*
@@ -67,7 +79,7 @@
 	char*	name;
 
 	SDev*	(*pnp)(void);
-	SDev*	(*legacy)(int, int);
+	SDev*	(*xxlegacy)(int, int);		/* unused.  remove me */
 	int	(*enable)(SDev*);
 	int	(*disable)(SDev*);
 
@@ -82,22 +94,26 @@
 	void	(*clear)(SDev*);
 	char*	(*rtopctl)(SDev*, char*, char*);
 	int	(*wtopctl)(SDev*, Cmdbuf*);
+	int	(*ataio)(SDreq*);
 };
 
 struct SDreq {
 	SDunit*	unit;
 	int	lun;
-	int	write;
-	uchar	cmd[16];
+	char	write;
+	char	proto;
+	char	ataproto;
+	uchar	cmd[0x20];
 	int	clen;
 	void*	data;
 	int	dlen;
 
 	int	flags;
+	ulong	timeout;		/* in ticks */
 
 	int	status;
 	long	rlen;
-	uchar	sense[256];
+	uchar	sense[32];
 };
 
 enum {
@@ -119,6 +135,12 @@
 
 	SDmaxio		= 2048*1024,
 	SDnpart		= 16,
+
+	SDread	= 0,
+	SDwrite,
+
+	SData		= 1,
+	SDcdb		= 2,
 };
 
 #define sdmalloc(n)	malloc(n)
@@ -127,11 +149,11 @@
 /* devsd.c */
 extern void sdadddevs(SDev*);
 extern int sdsetsense(SDreq*, int, int, int, int);
-extern int sdmodesense(SDreq*, uchar*, void*, int);
-extern int sdfakescsi(SDreq*, void*, int);
+extern int sdfakescsi(SDreq*);
+extern int sdfakescsirw(SDreq*, uvlong*, int*, int*);
+extern int sdaddfile(SDunit*, char*, int, char*, SDrw*, SDrw*);
 
 /* sdscsi.c */
 extern int scsiverify(SDunit*);
 extern int scsionline(SDunit*);
 extern long scsibio(SDunit*, int, int, void*, long, uvlong);
-extern SDev* scsiid(SDev*, SDifc*);
--- a/sys/src/9/port/sdaoe.c
+++ b/sys/src/9/port/sdaoe.c
@@ -1,5 +1,5 @@
 /*
- * aoe sd driver, copyright © 2007 coraid
+ * aoe sd driver, copyright © 2007-9 coraid
  */
 
 #include "u.h"
@@ -12,6 +12,7 @@
 #include "../port/sd.h"
 #include "../port/netif.h"
 #include "../port/aoe.h"
+#include <fis.h>
 
 extern	char	Echange[];
 extern	char	Enotup[];
@@ -19,34 +20,14 @@
 #define uprint(...)	snprint(up->genbuf, sizeof up->genbuf, __VA_ARGS__);
 
 enum {
-	Nctlr	= 32,
-	Maxpath	= 128,
+	Maxpath		= 128,
 
 	Probeintvl	= 100,		/* ms. between probes */
-	Probemax	= 20,		/* max probes */
+	Probemax	= 10*1000,	/* max ms. to wait */
 };
 
-enum {
-	/* sync with ahci.h */
-	Dllba 	= 1<<0,
-	Dsmart	= 1<<1,
-	Dpower	= 1<<2,
-	Dnop	= 1<<3,
-	Datapi	= 1<<4,
-	Datapi16= 1<<5,
-};
-
-static char *flagname[] = {
-	"llba",
-	"smart",
-	"power",
-	"nop",
-	"atapi",
-	"atapi16",
-};
-
 typedef struct Ctlr Ctlr;
-struct Ctlr{
+struct Ctlr {
 	QLock;
 
 	Ctlr	*next;
@@ -56,11 +37,8 @@
 	Chan	*c;
 
 	ulong	vers;
-	uchar	mediachange;
-	uchar	flag;
-	uchar	smart;
-	uchar	smartrs;
-	uchar	feat;
+	uchar	drivechange;
+	Sfis;
 
 	uvlong	sectors;
 	char	serial[20+1];
@@ -69,8 +47,6 @@
 	char	ident[0x100];
 };
 
-void	aoeidmove(char *p, ushort *a, unsigned n);
-
 static	Lock	ctlrlock;
 static	Ctlr	*head;
 static	Ctlr	*tail;
@@ -77,80 +53,54 @@
 
 SDifc sdaoeifc;
 
-static ushort
-gbit16(void *a)
-{
-	uchar *i;
-
-	i = a;
-	return i[1] << 8 | i[0];
-}
-
-static ulong
-gbit32(void *a)
-{
-	ulong j;
-	uchar *i;
-
-	i = a;
-	j  = i[3] << 24;
-	j |= i[2] << 16;
-	j |= i[1] << 8;
-	j |= i[0];
-	return j;
-}
-
-static uvlong
-gbit64(void *a)
-{
-	uchar *i;
-
-	i = a;
-	return (uvlong)gbit32(i+4)<<32 | gbit32(i);
-}
-
 static int
 identify(Ctlr *c, ushort *id)
 {
-	int i;
 	uchar oserial[21];
-	uvlong osectors, s;
+	vlong osectors, s;
 
 	osectors = c->sectors;
 	memmove(oserial, c->serial, sizeof c->serial);
-
-	c->feat &= ~(Dllba|Dpower|Dsmart|Dnop);
-	i = gbit16(id+83) | gbit16(id+86);
-	if(i & (1<<10)){
-		c->feat |= Dllba;
-		s = gbit64(id+100);
-	}else
-		s = gbit32(id+60);
-
-	i = gbit16(id+83);
-	if((i>>14) == 1) {
-		if(i & (1<<3))
-			c->feat |= Dpower;
-		i = gbit16(id+82);
-		if(i & 1)
-			c->feat |= Dsmart;
-		if(i & (1<<14))
-			c->feat |= Dnop;
+	s = idfeat(c, id);
+	if(s == -1){
+		uprint("%s: identify fails", c->unit->name);
+		print("%s\n", up->genbuf);
+		error(up->genbuf);
 	}
+	idmove(c->serial, id+10, 20);
+	idmove(c->firmware, id+23, 8);
+	idmove(c->model, id+27, 40);
 
-	aoeidmove(c->serial, id+10, 20);
-	aoeidmove(c->firmware, id+23, 8);
-	aoeidmove(c->model, id+27, 40);
-
 	if((osectors == 0 || osectors != s) &&
 	    memcmp(oserial, c->serial, sizeof oserial) != 0){
 		c->sectors = s;
-		c->mediachange = 1;
+		c->drivechange = 1;
 		c->vers++;
 	}
 	return 0;
 }
 
+static void
+aoectl(Ctlr *d, char *s)
+{
+	Chan *c;
+
+	c = nil;
+	if(waserror()){
+		if(c)
+			cclose(c);
+		print("sdaoectl: %s\n", up->errstr);
+		nexterror();
+	}
+
+	uprint("%s/ctl", d->path);
+	c = namec(up->genbuf, Aopen, OWRITE, 0);
+	devtab[c->type]->write(c, s, strlen(s), 0);
+
+	poperror();
+	cclose(c);
+}
+
 /* must call with d qlocked */
 static int
 aoeidentify(Ctlr *d, SDunit *u)
@@ -173,7 +123,6 @@
 	cclose(c);
 
 	d->feat = 0;
-	d->smart = 0;
 	identify(d, (ushort*)d->ident);
 
 	memset(u->inquiry, 0, sizeof u->inquiry);
@@ -203,7 +152,6 @@
 {
 	Ctlr *c;
 
-	/* race? */
 	if(ctlrlookup(path))
 		error(Eexist);
 
@@ -248,7 +196,6 @@
 	free(x);
 }
 
-/* don't call aoeprobe from within a loop; it loops internally retrying open. */
 static SDev*
 aoeprobe(char *path, SDev *s)
 {
@@ -273,21 +220,24 @@
 	poperror();
 	cclose(c);
 
-	for(i = 0; i < Probemax; i++){
+	for(i = 0;; i += Probeintvl){
+		if(i > Probemax || waserror())
+			error(Etimedout);
 		tsleep(&up->sleep, return0, 0, Probeintvl);
+		poperror();
+
 		uprint("%s/ident", path);
-		if(!waserror()) {
-			c = namec(up->genbuf, Aopen, OREAD, 0);
-			poperror();
-			cclose(c);
-			break;
-		}
+		if(waserror())
+			continue;
+		c = namec(up->genbuf, Aopen, OREAD, 0);
+		poperror();
+		cclose(c);
+
+		ctlr = newctlr(path);
+		break;
 	}
-	if(i >= Probemax)
-		error(Etimedout);
-	uprint("%s/ident", path);
-	ctlr = newctlr(path);
-	if(ctlr == nil || s == nil && (s = malloc(sizeof *s)) == nil)
+
+	if(s == nil && (s = malloc(sizeof *s)) == nil)
 		return nil;
 	s->ctlr = ctlr;
 	s->ifc = &sdaoeifc;
@@ -296,14 +246,20 @@
 }
 
 static char 	*probef[32];
+static char	*probebuf;
 static int 	nprobe;
 
 static int
 pnpprobeid(char *s)
 {
+	int id;
+
 	if(strlen(s) < 2)
 		return 0;
-	return s[1] == '!'? s[0]: 'e';
+	id = 'e';
+	if(s[1] == '!')
+		id = s[0];
+	return id;
 }
 
 static SDev*
@@ -315,7 +271,8 @@
 
 	if((p = getconf("aoedev")) == 0)
 		return 0;
-	nprobe = tokenize(p, probef, nelem(probef));
+	kstrdup(&probebuf, p);
+	nprobe = tokenize(probebuf, probef, nelem(probef));
 	h = t = 0;
 	for(i = 0; i < nprobe; i++){
 		id = pnpprobeid(probef[i]);
@@ -341,7 +298,7 @@
 static Ctlr*
 pnpprobe(SDev *sd)
 {
-	ulong start;
+	int j;
 	char *p;
 	static int i;
 
@@ -353,17 +310,21 @@
 	if(p[1] == '!')
 		p += 2;
 
-	start = TK2MS(MACHP(0)->ticks);
-	if(waserror()){
-		print("#æ: pnpprobe failed in %lud ms: %s: %s\n",
-			TK2MS(MACHP(0)->ticks) - start, probef[i-1],
-			up->errstr);
-		return nil;
+	for(j = 0;; j += Probeintvl){
+		if(j > Probemax){
+			print("#æ: pnpprobe: %s: %s\n", probef[i-1], up->errstr);
+			return 0;
+		}
+		if(waserror()){
+			tsleep(&up->sleep, return0, 0, Probeintvl);
+			continue;
+		}
+		sd = aoeprobe(p, sd);
+		poperror();
+		break;
 	}
-	sd = aoeprobe(p, sd);			/* does a round of probing */
-	poperror();
-	print("#æ: pnpprobe established %s in %lud ms\n",
-		probef[i-1], TK2MS(MACHP(0)->ticks) - start);
+	print("#æ: pnpprobe establishes %s in %dms\n", probef[i-1], j);
+	aoectl(sd->ctlr, "nofail on");
 	return sd->ctlr;
 }
 
@@ -378,7 +339,7 @@
 	c = s->ctlr;
 	if(c == nil && (s->ctlr = c = pnpprobe(s)) == nil)
 		return 0;
-	c->mediachange = 1;
+	c->drivechange = 1;
 	return 1;
 }
 
@@ -412,17 +373,17 @@
 	c = u->dev->ctlr;
 	r = 0;
 
-	if((c->feat&Datapi) && c->mediachange){
+	if((c->feat&Datapi) && c->drivechange){
 		if(aoeconnect(u, c) == 0 && (r = scsionline(u)) > 0)
-			c->mediachange = 0;
+			c->drivechange = 0;
 		return r;
 	}
 
-	if(c->mediachange){
+	if(c->drivechange){
 		if(aoeconnect(u, c) == -1)
 			return 0;
 		r = 2;
-		c->mediachange = 0;
+		c->drivechange = 0;
 		u->sectors = c->sectors;
 		u->secsize = Aoesectsz;
 	} else
@@ -431,103 +392,70 @@
 	return r;
 }
 
-static int
-aoerio(SDreq *r)
+static long
+aoebio(SDunit *u, int, int write, void *a, long count, uvlong lba)
 {
-	int i, count;
-	uvlong lba;
-	char *name;
-	uchar *cmd;
+	uchar *data;
+	int n;
 	long (*rio)(Chan*, void*, long, vlong);
 	Ctlr *c;
-	SDunit *unit;
 
-	unit = r->unit;
-	c = unit->dev->ctlr;
+	c = u->dev->ctlr;
 //	if(c->feat & Datapi)
-//		return aoeriopkt(r, d);
-
-	cmd = r->cmd;
-	name = unit->name;
-
-	if(r->cmd[0] == 0x35 || r->cmd[0] == 0x91){
-//		qlock(c);
-//		i = flushcache();
-//		qunlock(c);
-//		if(i == 0)
-//			return sdsetsense(r, SDok, 0, 0, 0);
-		return sdsetsense(r, SDcheck, 3, 0xc, 2);
-	}
-
-	if((i = sdfakescsi(r, c->ident, sizeof c->ident)) != SDnostatus){
-		r->status = i;
-		return i;
-	}
-
-	switch(*cmd){
-	case 0x88:
-	case 0x28:
-		rio = devtab[c->c->type]->read;
-		break;
-	case 0x8a:
-	case 0x2a:
+//		return scsibio(u, lun, write, a, count, lba);
+	data = a;
+	if(write)
 		rio = devtab[c->c->type]->write;
-		break;
-	default:
-		print("%s: bad cmd %#.2ux\n", name, cmd[0]);
-		r->status = SDcheck;
-		return SDcheck;
-	}
+	else
+		rio = devtab[c->c->type]->read;
 
-	if(r->data == nil)
-		return SDok;
-
-	if(r->clen == 16){
-		if(cmd[2] || cmd[3])
-			return sdsetsense(r, SDcheck, 3, 0xc, 2);
-		lba = (uvlong)cmd[4]<<40 | (uvlong)cmd[5]<<32;
-		lba |=   cmd[6]<<24 |  cmd[7]<<16 |  cmd[8]<<8 | cmd[9];
-		count = cmd[10]<<24 | cmd[11]<<16 | cmd[12]<<8 | cmd[13];
-	}else{
-		lba  = cmd[2]<<24 | cmd[3]<<16 | cmd[4]<<8 | cmd[5];
-		count = cmd[7]<<8 | cmd[8];
-	}
-
-	count *= Aoesectsz;
-
-	if(r->dlen < count)
-		count = r->dlen & ~0x1ff;
-
 	if(waserror()){
 		if(strcmp(up->errstr, Echange) == 0 ||
 		    strcmp(up->errstr, Enotup) == 0)
-			unit->sectors = 0;
+			u->sectors = 0;
 		nexterror();
 	}
-	r->rlen = rio(c->c, r->data, count, Aoesectsz * lba);
+	n = rio(c->c, data, Aoesectsz * count, Aoesectsz * lba);
 	poperror();
-	r->status = SDok;
-	return SDok;
+	return n;
 }
 
-static char *smarttab[] = {
-	"unset",
-	"error",
-	"threshold exceeded",
-	"normal"
-};
+static int
+flushcache(Ctlr *)
+{
+	return -1;
+}
 
-static char *
-pflag(char *s, char *e, uchar f)
+static int
+aoerio(SDreq *r)
 {
-	uchar i, m;
+	int i, count, rw;
+	uvlong lba;
+	Ctlr *c;
+	SDunit *u;
 
-	for(i = 0; i < 8; i++){
-		m = 1 << i;
-		if(f & m)
-			s = seprint(s, e, "%s ", flagname[i]);
+	u = r->unit;
+	c = u->dev->ctlr;
+//	if(c->feat & Datapi)
+//		return aoeriopkt(r, d);
+
+	if(r->cmd[0] == 0x35 || r->cmd[0] == 0x91){
+		qlock(c);
+		i = flushcache(c);
+		qunlock(c);
+		if(i == 0)
+			return sdsetsense(r, SDok, 0, 0, 0);
+		return sdsetsense(r, SDcheck, 3, 0xc, 2);
 	}
-	return seprint(s, e, "\n");
+
+	if((i = sdfakescsi(r)) != SDnostatus){
+		r->status = i;
+		return i;
+	}
+	if((i = sdfakescsirw(r, &lba, &count, &rw)) != SDnostatus)
+		return i;
+	r->rlen = aoebio(u, r->lun, rw == SDwrite, r->data, count, lba);
+	return r->status = SDok;
 }
 
 static int
@@ -544,14 +472,8 @@
 	p = seprint(p, e, "model\t%s\n", c->model);
 	p = seprint(p, e, "serial\t%s\n", c->serial);
 	p = seprint(p, e, "firm	%s\n", c->firmware);
-	if(c->smartrs == 0xff)
-		p = seprint(p, e, "smart\tenable error\n");
-	else if(c->smartrs == 0)
-		p = seprint(p, e, "smart\tdisabled\n");
-	else
-		p = seprint(p, e, "smart\t%s\n", smarttab[c->smart]);
 	p = seprint(p, e, "flag	");
-	p = pflag(p, e, c->feat);
+	p = pflag(p, e, c);
 	p = seprint(p, e, "geometry %llud %d\n", c->sectors, Aoesectsz);
 	return p-op;
 }
@@ -590,7 +512,7 @@
 	Ctlr *c;
 
 	c = s->ctlr;
-	return seprint(p, e, "%s aoe %s\n", s->name, c->path);
+	return seprint(p, e, "%s aoe %s\n", s->name, c? c->path: "");
 }
 
 static int
@@ -617,7 +539,7 @@
 	aoerctl,
 	aoewctl,
 
-	scsibio,
+	aoebio,
 	aoeprobew,	/* probe */
 	aoeclear,	/* clear */
 	aoertopctl,
--- /dev/null
+++ b/sys/src/9/port/sdloop.c
@@ -1,0 +1,415 @@
+/*
+ * sd loopback driver,
+ * copyright © 2009-10 erik quanstrom
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/sd.h"
+#include "../port/netif.h"
+
+extern	char	Echange[];
+extern	char	Enotup[];
+
+#define uprint(...)	snprint(up->genbuf, sizeof up->genbuf, __VA_ARGS__);
+
+enum {
+	Maxpath		= 256,
+	Devsectsize	= 512,
+};
+
+typedef struct Ctlr Ctlr;
+struct Ctlr {
+	QLock;
+
+	Ctlr	*next;
+	SDunit	*unit;
+
+	char	path[Maxpath];
+	Chan	*c;
+
+	uint	vers;
+	uchar	drivechange;
+
+	uvlong	sectors;
+	uint	sectsize;
+};
+
+static	Lock	ctlrlock;
+static	Ctlr	*head;
+static	Ctlr	*tail;
+
+SDifc sdloopifc;
+
+/* must call with c qlocked */
+static void
+identify(Ctlr *c, SDunit *u)
+{
+	int n;
+	uvlong s, osectors;
+	uchar buf[sizeof(Dir) + 100];
+	Dir dir;
+
+	if(waserror()){
+		iprint("sdloop: identify: %s\n", up->errstr);
+		nexterror();
+	}
+	osectors = c->sectors;
+	n = devtab[c->c->type]->stat(c->c, buf, sizeof buf);
+	if(convM2D(buf, n, &dir, nil) == 0)
+		error("internal error: stat error in seek");
+	s = dir.length / c->sectsize;
+	poperror();
+
+	memset(u->inquiry, 0, sizeof u->inquiry);
+	u->inquiry[2] = 2;
+	u->inquiry[3] = 2;
+	u->inquiry[4] = sizeof u->inquiry - 4;
+	memmove(u->inquiry+8, c->path, 40);
+
+	if(osectors == 0 || osectors != s){
+		c->sectors = s;
+		c->drivechange = 1;
+		c->vers++;
+	}
+}
+
+static Ctlr*
+ctlrlookup(char *path)
+{
+	Ctlr *c;
+
+	lock(&ctlrlock);
+	for(c = head; c; c = c->next)
+		if(strcmp(c->path, path) == 0)
+			break;
+	unlock(&ctlrlock);
+	return c;
+}
+
+static Ctlr*
+newctlr(char *path)
+{
+	Ctlr *c;
+
+	if(ctlrlookup(path))
+		error(Eexist);
+	if((c = malloc(sizeof *c)) == nil)
+		error(Enomem);
+	if(waserror()){
+		free(c);
+		nexterror();
+	}
+	c->c = namec(path, Aopen, ORDWR, 0);
+	poperror();
+	kstrcpy(c->path, path, sizeof c->path);
+	lock(&ctlrlock);
+	if(head != nil)
+		tail->next = c;
+	else
+		head = c;
+	tail = c;
+	unlock(&ctlrlock);
+	return c;
+}
+
+static void
+delctlr(Ctlr *c)
+{
+	Ctlr *x, *prev;
+
+	lock(&ctlrlock);
+
+	for(prev = 0, x = head; x; prev = x, x = c->next)
+		if(strcmp(c->path, x->path) == 0)
+			break;
+	if(x == 0){
+		unlock(&ctlrlock);
+		error(Enonexist);
+	}
+
+	if(prev)
+		prev->next = x->next;
+	else
+		head = x->next;
+	if(x->next == nil)
+		tail = prev;
+	unlock(&ctlrlock);
+
+	if(x->c)
+		cclose(x->c);
+	free(x);
+}
+
+static SDev*
+probe(char *path, SDev *s)
+{
+	char *p;
+	uint sectsize;
+	Ctlr *c;
+
+	sectsize = 0;
+	if(p = strchr(path, '!')){
+		*p = 0;
+		sectsize = strtoul(p + 1, 0, 0);
+	}
+	c = newctlr(path);
+	c->sectsize = sectsize? sectsize: Devsectsize;
+	if(s == nil && (s = malloc(sizeof *s)) == nil)
+		return nil;
+	s->ctlr = c;
+	s->ifc = &sdloopifc;
+	s->nunit = 1;
+	return s;
+}
+
+static char 	*probef[32];
+static int 	nprobe;
+
+static int
+pnpprobeid(char *s)
+{
+	int id;
+
+	if(strlen(s) < 2)
+		return 0;
+	id = 'l';
+	if(s[1] == '!')
+		id = s[0];
+	return id;
+}
+
+static SDev*
+pnp(void)
+{
+	int i, id;
+	char *p;
+	SDev *h, *t, *s;
+
+	if((p = getconf("loopdev")) == 0)
+		return 0;
+	nprobe = tokenize(p, probef, nelem(probef));
+	h = t = 0;
+	for(i = 0; i < nprobe; i++){
+		id = pnpprobeid(probef[i]);
+		if(id == 0)
+			continue;
+		s = malloc(sizeof *s);
+		if(s == nil)
+			break;
+		s->ctlr = 0;
+		s->idno = id;
+		s->ifc = &sdloopifc;
+		s->nunit = 1;
+
+		if(h)
+			t->next = s;
+		else
+			h = s;
+		t = s;
+	}
+	return h;
+}
+
+static Ctlr*
+pnpprobe(SDev *s)
+{
+	char *p;
+	static int i;
+
+	if(i > nprobe)
+		return 0;
+	p = probef[i++];
+	if(strlen(p) < 2)
+		return 0;
+	if(p[1] == '!')
+		p += 2;
+	s = probe(p, s);
+	return s->ctlr;
+}
+
+
+static int
+loopverify(SDunit *u)
+{
+	SDev *s;
+	Ctlr *c;
+
+	s = u->dev;
+	c = s->ctlr;
+	if(c == nil){
+		if(waserror())
+			return 0;
+		s->ctlr = c = pnpprobe(s);
+		poperror();
+	}
+	c->drivechange = 1;
+	return 1;
+}
+
+static int
+connect(SDunit *u, Ctlr *c)
+{
+	qlock(c);
+	if(waserror()){
+		qunlock(c);
+		return -1;
+	}
+	identify(u->dev->ctlr, u);
+	qunlock(c);
+	poperror();
+	return 0;
+}
+
+static int
+looponline(SDunit *u)
+{
+	Ctlr *c;
+	int r;
+
+	c = u->dev->ctlr;
+	if(c->drivechange){
+		if(connect(u, c) == -1)
+			return 0;
+		r = 2;
+		c->drivechange = 0;
+		u->sectors = c->sectors;
+		u->secsize = c->sectsize;
+	} else
+		r = 1;
+	return r;
+}
+
+static long
+loopbio(SDunit *u, int, int write, void *a, long count, uvlong lba)
+{
+	uchar *data;
+	int n;
+	long (*rio)(Chan*, void*, long, vlong);
+	Ctlr *c;
+
+	c = u->dev->ctlr;
+	data = a;
+	if(write)
+		rio = devtab[c->c->type]->write;
+	else
+		rio = devtab[c->c->type]->read;
+
+	if(waserror()){
+		if(strcmp(up->errstr, Echange) == 0 ||
+		    strcmp(up->errstr, Enotup) == 0)
+			u->sectors = 0;
+		nexterror();
+	}
+	n = rio(c->c, data, c->sectsize * count, c->sectsize * lba);
+	poperror();
+	return n;
+}
+
+static int
+looprio(SDreq *r)
+{
+	int i, count, rw;
+	uvlong lba;
+	SDunit *u;
+
+	u = r->unit;
+
+	if(r->cmd[0] == 0x35 || r->cmd[0] == 0x91)
+		return sdsetsense(r, SDok, 0, 0, 0);
+
+	if((i = sdfakescsi(r)) != SDnostatus)
+		return r->status = i;
+	if((i = sdfakescsirw(r, &lba, &count, &rw)) != SDnostatus)
+		return i;
+	r->rlen = loopbio(u, r->lun, rw == SDwrite, r->data, count, lba);
+	return r->status = SDok;
+}
+
+static int
+looprctl(SDunit *u, char *p, int l)
+{
+	Ctlr *c;
+	char *e, *op;
+
+	if((c = u->dev->ctlr) == nil)
+		return 0;
+	e = p+l;
+	op = p;
+
+	p = seprint(p, e, "path\t%s\n", c->path);
+	p = seprint(p, e, "geometry %llud %d\n", c->sectors, c->sectsize);
+	return p - op;
+}
+
+static int
+loopwctl(SDunit *, Cmdbuf *cmd)
+{
+	cmderror(cmd, Ebadarg);
+	return 0;
+}
+
+static SDev*
+loopprobew(DevConf *c)
+{
+	char *p;
+
+	p = strchr(c->type, '/');
+	if(p == nil || strlen(p) > Maxpath - 1)
+		error(Ebadarg);
+	p++;
+	if(ctlrlookup(p))
+		error(Einuse);
+	return probe(p, 0);
+}
+
+static void
+loopclear(SDev *s)
+{
+	delctlr((Ctlr *)s->ctlr);
+}
+
+static char*
+looprtopctl(SDev *s, char *p, char *e)
+{
+	Ctlr *c;
+
+	c = s->ctlr;
+	return seprint(p, e, "%s loop %s\n", s->name, c? c->path: "");
+}
+
+static int
+loopwtopctl(SDev *, Cmdbuf *cmd)
+{
+	switch(cmd->nf){
+	default:
+		cmderror(cmd, Ebadarg);
+	}
+	return 0;
+}
+
+SDifc sdloopifc = {
+	"loop",
+
+	pnp,
+	nil,		/* legacy */
+	nil,		/* enable */
+	nil,		/* disable */
+
+	loopverify,
+	looponline,
+	looprio,
+	looprctl,
+	loopwctl,
+
+	loopbio,
+	loopprobew,	/* probe */
+	loopclear,	/* clear */
+	looprtopctl,
+	loopwtopctl,
+};
--- a/sys/src/9/port/sdscsi.c
+++ b/sys/src/9/port/sdscsi.c
@@ -153,7 +153,8 @@
 			/*
 			 * If no medium present, bail out.
 			 * If unit is becoming ready, rather than not
-			 * not ready, wait a little then poke it again. 				 */
+			 * not ready, wait a little then poke it again.
+			 */
 			if(r->sense[12] == 0x3A)
 				break;
 			if(r->sense[12] != 0x04 || r->sense[13] != 0x01)
@@ -175,6 +176,74 @@
 	return -1;
 }
 
+static void
+cap10(SDreq *r)
+{
+	r->cmd[0] = 0x25;
+	r->cmd[1] = r->lun<<5;
+	r->clen = 10;
+	r->dlen = 8;
+}
+
+static void
+cap16(SDreq *r)
+{
+	uint i;
+
+	i = 32;
+	r->cmd[0] = 0x9e;
+	r->cmd[1] = 0x10;
+	r->cmd[10] = i>>24;
+	r->cmd[11] = i>>16;
+	r->cmd[12] = i>>8;
+	r->cmd[13] = i;
+	r->clen = 16;
+	r->dlen = i;
+}
+
+static uint
+belong(uchar *u)
+{
+	return u[0]<<24 | u[1]<<16 | u[2]<<8 | u[3];
+}
+
+static uvlong
+capreply(SDreq *r, ulong *secsize)
+{
+	uchar *u;
+	ulong ss;
+	uvlong s;
+
+	*secsize = 0;
+	u = r->data;
+	if(r->clen == 16){
+		s = (uvlong)belong(u)<<32 | belong(u + 4);
+		ss = belong(u + 8);
+	}else{
+		s = belong(u);
+		ss = belong(u + 4);
+	}
+	/*
+	 * 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(ss == 2352)
+		ss = 2048;
+	/*
+	 * Devices with removable media may return 0 sectors
+	 * when they have empty media (e.g. sata dvd writers);
+	 * if so, keep the count zero.
+	 *
+	 * Read-capacity returns the LBA of the last sector,
+	 * therefore the number of sectors must be incremented.
+	 */
+	if(s != 0)
+		s++;
+	*secsize = ss;
+	return s;
+}
+
 int
 scsionline(SDunit* unit)
 {
@@ -181,16 +250,17 @@
 	SDreq *r;
 	uchar *p;
 	int ok, retries;
+	void (*cap)(SDreq*);
 
-	if((r = malloc(sizeof(SDreq))) == nil)
+	if((r = malloc(sizeof *r)) == nil)
 		return 0;
-	if((p = sdmalloc(8)) == nil){
+	if((p = sdmalloc(32)) == nil){
 		free(r);
 		return 0;
 	}
 
 	ok = 0;
-
+	cap = cap10;
 	r->unit = unit;
 	r->lun = 0;				/* ??? */
 	for(retries = 0; retries < 10; retries++){
@@ -201,13 +271,10 @@
 		 * 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;
+		memset(r->cmd, 0, sizeof r->cmd);
+		cap(r);
 
 		r->status = ~0;
 		switch(scsirio(r)){
@@ -214,26 +281,11 @@
 		default:
 			break;
 		case 0:
-			unit->sectors = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3];
-			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;
-			/*
-			 * Devices with removable media may return 0 sectors
-			 * when they have empty media (e.g. sata dvd writers);
-			 * if so, keep the count zero.
-			 *
-			 * Read-capacity returns the LBA of the last sector,
-			 * therefore the number of sectors must be incremented.
-			 */
-			if(unit->sectors != 0)
-				unit->sectors++;
+			unit->sectors = capreply(r, &unit->secsize);
+			if(unit->sectors == 0xffffffff && cap == cap10){
+				cap = cap16;
+				continue;
+			}
 			ok = 1;
 			break;
 		case 1:
@@ -253,56 +305,6 @@
 		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;
-}
-
 static void
 scsifmt10(SDreq *r, int write, int lun, ulong nb, uvlong bno)
 {
@@ -367,7 +369,7 @@
 	r->lun = lun;
 again:
 	r->write = write;
-	if(bno >= (1ULL<<32))
+	if(bno > 0xffffffff)
 		scsifmt16(r, write, lun, nb, bno);
 	else
 		scsifmt10(r, write, lun, nb, bno);
@@ -381,8 +383,19 @@
 		rlen = -1;
 		break;
 	case 0:
-		rlen = r->rlen;
-		break;
+		/*
+		 * scsi allows commands to return successfully
+		 * but return sense data, indicating that the
+		 * operation didn't proceed as expected.
+		 * (confusing, no).  this allows the raw commands
+		 * to successfully return errors.  but any sense
+		 * data bio sees must be an error.  bomb out.
+		 */
+		if(r->status == SDok && r->rlen > 0
+		&& ((r->flags & SDvalidsense) == 0 || r->sense[2] == 0)){
+			rlen = r->rlen;
+			break;
+		}
 	case 2:
 		rlen = -1;
 		if(!(r->flags & SDvalidsense))
@@ -415,6 +428,10 @@
 				goto again;
 			break;
 		}
+		snprint(up->genbuf, sizeof up->genbuf, "%s %.2ux%.2ux%.2ux %lld",
+			Eio, r->sense[2], r->sense[12], r->sense[13], bno);
+		free(r);
+		error(up->genbuf);
 		break;
 	}
 	free(r);
--- /dev/null
+++ b/sys/src/libfis/fis.c
@@ -1,0 +1,545 @@
+/*
+ * sata fises and sas frames
+ * copyright © 2009-2010 erik quanstrom
+ */
+#include <u.h>
+#include <libc.h>
+#include <fis.h>
+
+static char *flagname[9] = {
+	"lba",
+	"llba",
+	"smart",
+	"power",
+	"nop",
+	"atapi",
+	"atapi16",
+	"ata8",
+	"sct",
+};
+
+/*
+ * ata8 standard (llba) cmd layout
+ *
+ * feature	16 bits
+ * count		16 bits
+ * lba		48 bits
+ * device		8 bits
+ * command	8 bits
+ *
+ * response:
+ *
+ * status		8 bits
+ * error		8 bits
+ * reason		8 bits
+ * count		8 bits
+ * sstatus		8 bits
+ * sactive		8 bits
+*/
+
+/*
+ * sata fis layout for fistype 0x27: host-to-device:
+ *
+ * 0	fistype
+ * 1	fis flags
+ * 2	ata command
+ * 3	features
+ * 4	sector	lba low	7:0
+ * 5	cyl low	lba mid	15:8
+ * 6	cyl hi	lba hi	23:16
+ * 7	device / head
+ * 8	sec exp	lba	31:24
+ * 9	cy low e	lba	39:32
+ * 10	cy hi e	lba	48:40
+ * 11	features (exp)
+ * 12	sector count
+ * 13	sector count (exp)
+ * 14	r
+ * 15	control
+ */
+
+void
+setfissig(Sfis *x, uint sig)
+{
+	x->sig = sig;
+}
+
+void
+skelfis(uchar *c)
+{
+	memset(c, 0, Fissize);
+	c[Ftype] = H2dev;
+	c[Fflags] = Fiscmd;
+	c[Fdev] = Ataobs;
+}
+
+int
+nopfis(Sfis*, uchar *c, int srst)
+{
+	skelfis(c);
+	if(srst){
+		c[Fflags] &= ~Fiscmd;
+		c[Fcontrol] = 1<<2;
+		return Preset|P28;
+	}
+	return Pnd|P28;
+}
+
+int
+txmodefis(Sfis *f, uchar *c, uchar d)
+{
+	int m;
+
+	/* hack */
+	if((f->sig >> 16) == 0xeb14)
+		return -1;
+	m = 0x40;
+	if(d == 0xff){
+		d = 0;
+		m = 0;
+	}
+	skelfis(c);
+	c[Fcmd] = 0xef;
+	c[Ffeat] = 3;			/* set transfer mode */
+	c[Fsc] = m | d;			/* sector count */
+	return Pnd|P28;
+}
+
+int
+featfis(Sfis*, uchar *c, uchar f)
+{
+	skelfis(c);
+	c[Fcmd] = 0xef;
+	c[Ffeat] = f;
+	return Pnd|P28;
+}
+
+int
+identifyfis(Sfis *f, uchar *c)
+{
+	static uchar tab[] = { 0xec, 0xa1, };
+
+	skelfis(c);
+	c[Fcmd] = tab[f->sig>>16 == 0xeb14];
+	return Pin|Ppio|P28|P512;
+}
+
+int
+flushcachefis(Sfis *f, uchar *c)
+{
+	static uchar tab[2] = {0xe7, 0xea};
+	static uchar ptab[2] = {Pnd|P28, Pnd|P48};
+	int llba;
+
+	llba = (f->feat & Dllba) != 0;
+	skelfis(c);
+	c[Fcmd] = tab[llba];
+	return ptab[llba];
+}
+
+static ushort
+gbit16(void *a)
+{
+	ushort j;
+	uchar *i;
+
+	i = a;
+	j  = i[1] << 8;
+	j |= i[0];
+	return j;
+}
+
+static uint
+gbit32(void *a)
+{
+	uint j;
+	uchar *i;
+
+	i = a;
+	j  = i[3] << 24;
+	j |= i[2] << 16;
+	j |= i[1] << 8;
+	j |= i[0];
+	return j;
+}
+
+static uvlong
+gbit64(void *a)
+{
+	uchar *i;
+
+	i = a;
+	return (uvlong)gbit32(i+4) << 32 | gbit32(a);
+}
+
+ushort
+id16(ushort *id, int i)
+{
+	return gbit16(id+i);
+}
+
+uint
+id32(ushort *id, int i)
+{
+	return gbit32(id+i);
+}
+
+uvlong
+id64(ushort *id, int i)
+{
+	return gbit64(id+i);
+}
+
+/* acs-2 §7.18.7.4 */
+static ushort puistab[] = {
+	0x37c8,	Pspinup,
+	0x738c,	Pspinup | Pidready,
+	0x8c73,	0,
+	0xc837,	Pidready,
+};
+
+int
+idpuis(ushort *id)
+{
+	ushort u, i;
+
+	u = gbit16(id + 2);
+	for(i = 0; i < nelem(puistab); i += 2)
+		if(u == puistab[i])
+			return puistab[i + 1];
+	return Pidready;	/* annoying cdroms */
+}
+
+static ushort
+onesc(ushort *id)
+{
+	ushort u;
+
+	u = gbit16(id);
+	if(u == 0xffff)
+		u = 0;
+	return u;
+}
+
+enum{
+	Idmasp	= 1<<8,
+	Ilbasp	= 1<<9,
+	Illba	= 1<<10,
+};
+
+vlong
+idfeat(Sfis *f, ushort *id)
+{
+	int i, j;
+	vlong s;
+
+	f->feat = 0;
+	if(f->sig>>16 == 0xeb14)
+		f->feat |= Datapi;
+	i = gbit16(id + 49);
+	if((i & Ilbasp) == 0){
+		if(gbit16(id + 53) & 1){
+			f->c = gbit16(id + 1);
+			f->h = gbit16(id + 3);
+			f->s = gbit16(id + 6);
+		}else{
+			f->c = gbit16(id + 54);
+			f->h = gbit16(id + 55);
+			f->s = gbit16(id + 56);
+		}
+		s = f->c*f->h*f->s;
+	}else{
+		f->c = f->h = f->s = 0;
+		f->feat |= Dlba;
+		j = gbit16(id + 83) | gbit16(id + 86);
+		if(j & Illba){
+			f->feat |= Dllba;
+			s = gbit64(id + 100);
+		}else
+			s = gbit32(id + 60);
+	}
+	f->udma = 0xff;
+	if(i & Idmasp)
+	if(gbit16(id + 53) & 4)
+		for(i = gbit16(id + 88) & 0x7f; i; i >>= 1)
+			f->udma++;
+
+	if(f->feat & Datapi){
+		i = gbit16(id + 0);
+		if(i & 1)
+			f->feat |= Datapi16;
+	}
+
+	i = gbit16(id+83);
+	if((i>>14) == 1){
+		if(i & (1<<3))
+			f->feat |= Dpower;
+		i = gbit16(id + 82);
+		if(i & 1)
+			f->feat |= Dsmart;
+		if(i & (1<<14))
+			f->feat |= Dnop;
+	}
+	i = onesc(id + 80);
+	if(i & 1<<8){
+		f->feat |= Data8;
+		i = onesc(id + 222);			/* sata? */
+		j = onesc(id + 76);
+		if(i != 0 && i >> 12 == 1 && j != 0){
+			j >>= 1;
+			f->speeds = j & 7;
+			i = gbit16(id + 78) & gbit16(id + 79);
+			/*
+			 * not acceptable for comreset to
+			 * wipe out device configuration.
+			 * reject drive.
+			 */
+			if((i & 1<<6) == 0)
+				return -1;
+		}
+	}
+	if(gbit16(id + 206) & 1)
+		f->feat |= Dsct;
+	idss(f, id);
+	return s;
+}
+
+int
+idss(Sfis *f, ushort *id)
+{
+	uint sw, i;
+
+	if(f->sig>>16 == 0xeb14)
+		return 0;
+	f->lsectsz = 512;
+	f->physshift = 0;
+	i = gbit16(id + 106);
+	if(i >> 14 != 1)
+		return f->lsectsz;
+	if((sw = gbit32(id + 117)) >= 256)
+		f->lsectsz = sw * 2;
+	if(i & 1<<13)
+		f->physshift = i & 7;
+	return f->lsectsz * (1<<f->physshift);
+}
+
+uvlong
+idwwn(Sfis*, ushort *id)
+{
+	uvlong u;
+
+	u = 0;
+	if(id[108]>>12 == 5){
+		u |= (uvlong)gbit16(id + 108) << 48;
+		u |= (uvlong)gbit16(id + 109) << 32;
+		u |= gbit16(id + 110) << 16;
+		u |= gbit16(id + 111) << 0;
+	}
+	return u;
+}
+
+void
+idmove(char *p, ushort *u, int n)
+{
+	int i;
+	char *op, *e, *s;
+
+	op = p;
+	s = (char*)u;
+	for(i = 0; i < n; i += 2){
+		*p++ = s[i + 1];
+		*p++ = s[i + 0];
+	}
+	*p = 0;
+	while(p > op && *--p == ' ')
+		*p = 0;
+	e = p;
+	p = op;
+	while(*p == ' ')
+		p++;
+	memmove(op, p, n - (e - p));
+}
+
+char*
+pflag(char *s, char *e, Sfis *f)
+{
+	ushort i, u;
+
+	u = f->feat;
+	for(i = 0; i < Dnflag; i++)
+		if(u & (1 << i))
+			s = seprint(s, e, "%s ", flagname[i]);
+	return seprint(s, e, "\n");
+}
+
+int
+atapirwfis(Sfis *f, uchar *c, uchar *cdb, int cdblen, int ndata)
+{
+	int fill, len;
+
+	fill = f->feat&Datapi16? 16: 12;
+	if((len = cdblen) > fill)
+		len = fill;
+	memmove(c + 0x40, cdb, len);
+	memset(c + 0x40 + len, 0, fill - len);
+
+	c[Ftype] = H2dev;
+	c[Fflags] = Fiscmd;
+	c[Fcmd] = Ataobs;
+	if(ndata != 0)
+		c[Ffeat] = 1;	/* dma */
+	else
+		c[Ffeat] = 0;	/* features (exp); */
+	c[Flba0] = 0;	
+	c[Flba8] = ndata;
+	c[Flba16] = ndata >> 8;
+	c[Fdev] = Ataobs;
+	memset(c + 8, 0, Fissize - 8);
+	return P28|Ppkt;
+}
+
+int
+rwfis(Sfis *f, uchar *c, int rw, int nsect, uvlong lba)
+{
+	uchar acmd, llba, udma;
+	static uchar tab[2][2][2] = { 0x20, 0x24, 0x30, 0x34, 0xc8, 0x25, 0xca, 0x35, };
+	static uchar ptab[2][2][2] = {
+		Pin|Ppio|P28,	Pin|Ppio|P48,
+		Pout|Ppio|P28,	Pout|Ppio|P48,
+		Pin|Pdma|P28,	Pin|Pdma|P48,
+		Pout|Pdma|P28,	Pout|Pdma|P48,
+	};
+
+	nsect >>= f->physshift;
+	lba >>= f->physshift;
+
+	udma = f->udma != 0xff;
+	llba = (f->feat & Dllba) != 0;
+	acmd = tab[udma][rw][llba];
+
+	c[Ftype] = 0x27;
+	c[Fflags] = 0x80;
+	c[Fcmd] = acmd;
+	c[Ffeat] = 0;
+
+	c[Flba0] = lba;
+	c[Flba8] = lba >> 8;
+	c[Flba16] = lba >> 16;
+	c[Fdev] = Ataobs | Atalba;
+	if(llba == 0)
+		c[Fdev] |= (lba>>24) & 0xf;
+
+	c[Flba24] = lba >> 24;
+	c[Flba32] = lba >> 32;
+	c[Flba40] = lba >> 48;
+	c[Ffeat8] = 0;
+
+	c[Fsc] = nsect;
+	c[Fsc8] = nsect >> 8;
+	c[Ficc] = 0;
+	c[Fcontrol] = 0;
+
+	memset(c + 16, 0, Fissize - 16);
+	return ptab[udma][rw][llba];
+}
+
+uvlong
+fisrw(Sfis *f, uchar *c, int *n)
+{
+	uvlong lba;
+
+	lba = c[Flba0];
+	lba |= c[Flba8] << 8;
+	lba |= c[Flba16] << 16;
+	lba |= c[Flba24] << 24;
+	lba |= (uvlong)(c[Flba32] | c[Flba40]<<8) << 32;
+
+	*n = c[Fsc];
+	*n |= c[Fsc8] << 8;
+
+	*n >>= f->physshift;
+	lba >>= f->physshift;
+
+	return lba;
+}
+
+void
+sigtofis(Sfis *f, uchar *c)
+{
+	uint u;
+
+	u = f->sig;
+	memset(c, 0, Fissize);
+	c[Ftype] = 0x34;
+	c[Fflags] = 0x00;
+	c[Fcmd] = 0x50;
+	c[Ffeat] = 0x01;
+	c[Flba0] = u >> 8;
+	c[Flba8] = u >> 16;
+	c[Flba16] = u >> 24;
+	c[Fdev] = Ataobs;
+	c[Fsc] = u;
+}
+
+uint
+fistosig(uchar *u)
+{
+	return u[Fsc] | u[Flba0]<<8 | u[Flba8]<<16 | u[Flba16]<<24;
+}
+
+
+/* sas smp */
+void
+smpskelframe(Cfis *f, uchar *c, int m)
+{
+	memset(c, 0, Fissize);
+	c[Ftype] = 0x40;
+	c[Fflags] = m;
+	if(f->phyid)
+		c[Flba32] = f->phyid;
+}
+
+uint
+sashash(uvlong u)
+{
+	uint poly, msb, l, r;
+	uvlong m;
+
+	r = 0;
+	poly = 0x01db2777;
+	msb = 0x01000000;
+	for(m = 1ull<<63; m > 0; m >>= 1){
+		l = 0;
+		if(m & u)
+			l = msb;
+		r <<= 1;
+		r ^= l;
+		if(r & msb)
+			r ^= poly;
+	}
+	return r & 0xffffff;
+}
+
+uchar*
+sasbhash(uchar *t, uchar *s)
+{
+	uint poly, msb, l, r, i, j;
+
+	r = 0;
+	poly = 0x01db2777;
+	msb = 0x01000000;
+	for(i = 0; i < 8; i++)
+		for(j = 0x80; j != 0; j >>= 1){
+			l = 0;
+			if(s[i] & j)
+				l = msb;
+			r <<= 1;
+			r ^= l;
+			if(r & msb)
+				r ^= poly;
+		}
+	t[0] = r>>16;
+	t[1] = r>>8;
+	t[2] = r;
+	return t;
+}
--- /dev/null
+++ b/sys/src/libfis/mkfile
@@ -1,0 +1,15 @@
+</$objtype/mkfile
+
+LIB=/$objtype/lib/libfis.a
+OFILES=\
+	fis.$O\
+
+HFILES=/sys/include/fis.h
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	${LIB:/$objtype/%=/386/%}\
+
+</sys/src/cmd/mksyslib