From 9fans-admin@cse.psu.edu Fri Sep 07 15:21:30 2001
Return-Path: <9fans-admin@cse.psu.edu>
Delivered-To: dpx@acl.lanl.gov
Received: (qmail 304752 invoked from network); 7 Sep 2001 09:21:30 -0600
Received: from mailrelay1.lanl.gov (128.165.3.6)
  by acl.lanl.gov with SMTP; 7 Sep 2001 09:21:30 -0600
Received: from mailproxy1.lanl.gov (localhost.localdomain [127.0.0.1])
	by mailrelay1.lanl.gov (8.11.4/8.11.4/(ccn-5)) with ESMTP id f87FLTE22503;
	Fri, 7 Sep 2001 09:21:29 -0600
Received: from mail.cse.psu.edu (psuvax1.cse.psu.edu [130.203.4.6])
	by mailproxy1.lanl.gov (8.11.6/8.11.6/(ccn-5)) with ESMTP id f87FLTQ30417;
	Fri, 7 Sep 2001 09:21:29 -0600
Received: from psuvax1.cse.psu.edu (psuvax1.cse.psu.edu [130.203.30.6])
	by mail.cse.psu.edu (CSE Mail Server) with ESMTP
	id 2883E19A0C; Fri, 7 Sep 2001 11:21:15 -0400 (EDT)
Delivered-To: 9fans@cse.psu.edu
Received: from nautilus.escet.urjc.es (nautilus.escet.urjc.es [212.128.4.207])
	by mail.cse.psu.edu (CSE Mail Server) with SMTP id 31D17199E8
	for <9fans@cse.psu.edu>; Fri, 7 Sep 2001 11:20:30 -0400 (EDT)
To: 9fans@cse.psu.edu
From: "Fco.J.Ballesteros" <nemo@gsyc.escet.urjc.es>
MIME-Version: 1.0
Content-Type: multipart/mixed;
	boundary="upas-cefrnfuwhwfqcaxqtnsykuzaqu"
Message-Id: <20010907152030.31D17199E8@mail.cse.psu.edu>
Subject: [9fans] sd on the bitsy
Sender: 9fans-admin@cse.psu.edu
Errors-To: 9fans-admin@cse.psu.edu
X-BeenThere: 9fans@cse.psu.edu
X-Mailman-Version: 2.0.6
Precedence: bulk
Reply-To: 9fans@cse.psu.edu
List-Id: Fans of the OS Plan 9 from Bell Labs <9fans.cse.psu.edu>
List-Archive: <http://lists.cse.psu.edu/archives/9fans/>
Date: Fri, 7 Sep 2001 17:42:47 +0200
Status: RO
X-Status:


This is a multi-part message in MIME format.
--upas-cefrnfuwhwfqcaxqtnsykuzaqu
Content-Disposition: inline
Content-Type: text/plain; charset="US-ASCII"
Content-Transfer-Encoding: 7bit

Hi,

  I'm now using kfs on the bitsy using a 2G pcmcia disk. This is just
the pc sdata driver adapted a little bit to work on the bitsy, together
with some fixes for cis that jmk kindly sent to me. I only have used it
with a MK2001MPL 2G HDD, so it might fail with other drives.

The files are
bitsy/dat.h
bitsy/sdata.c
port/cis.c
port/devsd.c
port/sd.h
Since I don't trust my network and the files are not too big, I attach
them here.

The change in port/sd is to allow it to be configured in the cpurc
like this:

# start sd
if(grep -s MK2001MPL /dev/pcm0ctl){
	echo -n 'configure #S0 sd 1'>/dev/pcm0ctl
	bind -a '#S' /dev
}


The bitsy is a cool plan9 mpeg player, not to talk about
using it as an emergency file&cpu server...
so thanks you all


--upas-cefrnfuwhwfqcaxqtnsykuzaqu
Content-Disposition: attachment; filename=dat.h
Content-Type: text/plain; charset="US-ASCII"
Content-Transfer-Encoding: 7bit

typedef struct Cisdat Cisdat;
typedef struct Conf Conf;
typedef struct Cycintr Cycintr;
typedef struct FPU FPU;
typedef struct FPenv FPenv;
typedef struct FPsave FPsave;
typedef struct DevConf DevConf;
typedef struct Label Label;
typedef struct Lock Lock;
typedef struct MMU MMU;
typedef struct Mach Mach;
typedef struct Notsave Notsave;
typedef struct Page Page;
typedef struct PCMmap PCMmap;
typedef struct PCMslot PCMslot;
typedef struct PCMconftab PCMconftab;
typedef struct PhysUart PhysUart;
typedef struct PMMU PMMU;
typedef struct Proc Proc;
typedef struct Uart Uart;
typedef struct Ureg Ureg;
typedef struct Vctl Vctl;
typedef struct Uart Uart;

typedef void IntrHandler(Ureg*, void*);

/*
 * parameters for sysproc.c
 */
#define AOUT_MAGIC (E_MAGIC)

struct Lock
{
	ulong key;
	ulong sr;
	ulong pc;
	Proc *p;
	ushort isilock;
};

struct Label
{
	ulong sp;
	ulong pc;
};

/*
 * FPsave.status
 */
enum
{
	FPinit,
	FPactive,
	FPinactive,
};
struct FPsave
{
	ulong status;
	ulong control;
	ulong regs[8][3]; /* emulated fp */
};

struct Conf
{
	ulong nmach; /* processors */
	ulong nproc; /* processes */
	ulong npage0; /* total physical pages of memory */
	ulong npage1; /* total physical pages of memory */
	ulong npage; /* total physical pages of memory */
	ulong upages; /* user page pool */
	ulong nimage; /* number of page cache image headers */
	ulong nswap; /* number of swap pages */
	int nswppo; /* max # of pageouts per segment pass */
	ulong base0; /* base of bank 0 */
	ulong base1; /* base of bank 1 */
	ulong copymode; /* 0 is copy on write, 1 is copy on reference */
	int monitor;
	ulong ialloc; /* bytes available for interrupt time allocation */
	ulong pipeqsize; /* size in bytes of pipe queues */
	ulong hz; /* processor cycle freq */
	ulong mhz;
};

/*
 * MMU stuff in proc
 */
enum
{
	NCOLOR= 1, /* 1 level cache, don't worry about VCE's */
	Nmeg= 32, /* maximum size of user space */
};

struct PMMU
{
	Page *l1page[Nmeg]; /* this's process' level 1 entries */
	ulong l1table[Nmeg]; /* ... */
	Page *mmufree; /* free mmu pages */
};

/*
 * things saved in the Proc structure during a notify
 */
struct Notsave
{
	int dummy;
};

#include "../port/portdat.h"

struct Mach
{
	int machno; /* physical id of processor */
	ulong splpc; /* pc of last caller to splhi */

	Proc *proc; /* current process */
	ulong mmupid; /* process id currently in mmu & cache */

	ulong ticks; /* of the clock since boot time */
	Label sched; /* scheduler wakeup */
	Lock alarmlock; /* access to alarm list */
	void* alarm; /* alarms bound to this clock */
	int inclockintr;

	ulong fairness; /* for runproc */

	/* stats */
	int tlbfault;
	int tlbpurge;
	int pfault;
	int cs;
	int syscall;
	int load;
	int intr;
	vlong fastclock; /* last sampled value */
	vlong intrts; /* time stamp of last interrupt */
	ulong spuriousintr;
	int lastintr;

	int flushmmu; /* make current proc flush it's mmu state */
	Proc *pid2proc[31]; /* what proc holds what pid */
	int lastpid; /* highest assigned pid slot */

	int cpumhz; /* speed of cpu */
	int cpuhz; /* ... *

	/* save areas for exceptions */
	ulong sfiq[5];
	ulong sirq[5];
	ulong sund[5];
	ulong sabt[5];

	int stack[1];
};

/*
 * fasttick timer interrupts
 */
struct Cycintr
{
	vlong when; /* fastticks when f should be called */
	void (*f)(Ureg*, Cycintr*);
	void *a;
	Cycintr *next;
};

/*
 * Fake kmap since we direct map dram
 */
typedef void KMap;
#define VA(k) ((ulong)(k))
#define kmap(p) (KMap*)((p)->pa)
#define kunmap(k)

struct
{
	Lock;
	int machs; /* bitmap of active CPUs */
	int exiting; /* shutdown */
	int ispanic; /* shutdown in response to a panic */
}active;

#define MACHP(n) ((Mach *)(MACHADDR+(n)*BY2PG))

extern Mach *m;
extern Proc *up;

enum
{
	OneMeg= 1024*1024,
};

/*
 * routines to access UART hardware
 */
struct PhysUart
{
	void (*enable)(Uart*, int);
	void (*disable)(Uart*);
	void (*kick)(Uart*);
	void (*intr)(Ureg*, void*);
	void (*dobreak)(Uart*, int);
	void (*baud)(Uart*, int);
	void (*bits)(Uart*, int);
	void (*stop)(Uart*, int);
	void (*parity)(Uart*, int);
	void (*modemctl)(Uart*, int);
	void (*rts)(Uart*, int);
	void (*dtr)(Uart*, int);
	long (*status)(Uart*, void*, long, long);
};

enum {
	Stagesize= 1024
};

/*
 * software UART
 */
struct Uart
{
	QLock;
	int type;
	int dev;
	int opens;
	void *regs;
	PhysUart *phys;

	int enabled;
	Uart *elist; /* next enabled interface */
	char name[NAMELEN];

	uchar sticky[4]; /* sticky write register values */
	ulong freq; /* clock frequency */
	uchar mask; /* bits/char */
	int baud; /* baud rate */

	int parity; /* parity errors */
	int frame; /* framing errors */
	int overrun; /* rcvr overruns */

	/* buffers */
	int (*putc)(Queue*, int);
	Queue *iq;
	Queue *oq;

	uchar istage[Stagesize];
	uchar *iw;
	uchar *ir;
	uchar *ie;

	Lock tlock; /* transmit */
	uchar ostage[Stagesize];
	uchar *op;
	uchar *oe;

	int modem; /* hardware flow control on */
	int xonoff; /* software flow control on */
	int blocked;
	int cts, dsr, dcd, dcdts; /* keep track of modem status */
	int ctsbackoff;
	int hup_dsr, hup_dcd; /* send hangup upstream? */
	int dohup;

	int kinuse; /* device in use by kernel */

	Rendez r;
};

/*
 * PCMCIA structures known by both port/cis.c and the pcmcia driver
 */

/*
 * Map between ISA memory space and PCMCIA card memory space.
 */
struct PCMmap {
	ulong ca; /* card address */
	ulong cea; /* card end address */
	ulong isa; /* local virtual address */
	int len; /* length of the ISA area */
	int attr; /* attribute memory */
};

/*
 * a PCMCIA configuration entry
 */
struct PCMconftab
{
	int index;
	ushort irqs; /* legal irqs */
	uchar irqtype;
	uchar bit16; /* true for 16 bit access */
	struct {
		ulong start;
		ulong len;
	} io[16];
	int nio;
	uchar vpp1;
	uchar vpp2;
	uchar memwait;
	ulong maxwait;
	ulong readywait;
	ulong otherwait;
};


/*
 * PCMCIA card slot
 */
struct PCMslot
{
	RWlock;

	Ref ref;

	long memlen; /* memory length */
	uchar slotno; /* slot number */
	void *regs; /* i/o registers */
	void *mem; /* memory */
	void *attr; /* attribute memory */

	/* status */
	uchar occupied; /* card in the slot */
	uchar configed; /* card configured */

	/* cis info */
	int cisread; /* set when the cis has been read */
	char verstr[512]; /* version string */
	uchar cpresent; /* config registers present */
	ulong caddr; /* relative address of config registers */
	int nctab; /* number of config table entries */
	PCMconftab ctab[8];
	PCMconftab *def; /* default conftab */


	/* maps are fixed */
	PCMmap memmap;
	PCMmap attrmap;
};

/*
 * hardware info about a device
 */
struct DevConf
{
	ulong mem; /* mapped memory address */
	ulong port; /* mapped i/o regs */
	int size; /* access size */
	int itype; /* type of interrupt */
	ulong interrupt; /* interrupt number */
	char type[NAMELEN]; /* card type */
};

--upas-cefrnfuwhwfqcaxqtnsykuzaqu
Content-Disposition: attachment; filename=sdata.c
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: 8bit

#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"


extern SDifc sdataifc;

//BUG?
#define PCIWADDR(x) ((ulong)(x))

enum {
	DbgCONFIG = 0x01, /* detected drive config info */
	DbgIDENTIFY = 0x02, /* detected drive identify info */
	DbgSTATE = 0x04, /* dump state on panic */
	DbgPROBE = 0x08, /* trace device probing */
	DbgDEBUG = 0x80, /* the current problem... */
};
#define DEBUG (DbgDEBUG|DbgSTATE|DbgCONFIG)

enum { /* I/O ports */
	Data = 0,
	Error = 1, /* (read) */
	Features = 1, /* (write) */
	Count = 2, /* sector count */
	Ir = 2, /* interrupt reason (PACKET) */
	Sector = 3, /* sector number, LBA<7-0> */
	Cyllo = 4, /* cylinder low, LBA<15-8> */
	Bytelo = 4, /* byte count low (PACKET) */
	Cylhi = 5, /* cylinder high, LBA<23-16> */
	Bytehi = 5, /* byte count hi (PACKET) */
	Dh = 6, /* Device/Head, LBA<32-14> */
	Status = 7, /* (read) */
	Command = 7, /* (write) */

	As = 2, /* Alternate Status (read) */
	Dc = 2, /* Device Control (write) */
};

enum { /* Error */
	Med = 0x01, /* Media error */
	Ili = 0x01, /* command set specific (PACKET) */
	Nm = 0x02, /* No Media */
	Eom = 0x02, /* command set specific (PACKET) */
	Abrt = 0x04, /* Aborted command */
	Mcr = 0x08, /* Media Change Request */
	Idnf = 0x10, /* no user-accessible address */
	Mc = 0x20, /* Media Change */
	Unc = 0x40, /* Uncorrectable data error */
	Wp = 0x40, /* Write Protect */
	Icrc = 0x80, /* Interface CRC error */
};

enum { /* Features */
	Dma = 0x01, /* data transfer via DMA (PACKET) */
	Ovl = 0x02, /* command overlapped (PACKET) */
};

enum { /* Interrupt Reason */
	Cd = 0x01, /* Command/Data */
	Io = 0x02, /* I/O direction */
	Rel = 0x04, /* Bus Release */
};

enum { /* Device/Head */
	Dev0 = 0xA0, /* Master */
	Dev1 = 0xB0, /* Slave */
	Lba = 0x40, /* LBA mode */
};

enum { /* Status, Alternate Status */
	Err = 0x01, /* Error */
	Chk = 0x01, /* Check error (PACKET) */
	Drq = 0x08, /* Data Request */
	Dsc = 0x10, /* Device Seek Complete */
	Serv = 0x10, /* Service */
	Df = 0x20, /* Device Fault */
	Dmrd = 0x20, /* DMA ready (PACKET) */
	Drdy = 0x40, /* Device Ready */
	Bsy = 0x80, /* Busy */
};

enum { /* Command */
	Cnop = 0x00, /* NOP */
	Cdr = 0x08, /* Device Reset */
	Crs = 0x20, /* Read Sectors */
	Cws = 0x30, /* Write Sectors */
	Cedd = 0x90, /* Execute Device Diagnostics */
	Cpkt = 0xA0, /* Packet */
	Cidpkt = 0xA1, /* Identify Packet Device */
	Crsm = 0xC4, /* Read Multiple */
	Cwsm = 0xC5, /* Write Multiple */
	Csm = 0xC6, /* Set Multiple */
	Crdq = 0xC7, /* Read DMA queued */
	Crd = 0xC8, /* Read DMA */
	Cwd = 0xCA, /* Write DMA */
	Cwdq = 0xCC, /* Write DMA queued */
	Cstandby = 0xE2, /* Standby */
	Cid = 0xEC, /* Identify Device */
	Csf = 0xEF, /* Set Features */
};

enum { /* Device Control */
	Nien = 0x02, /* (not) Interrupt Enable */
	Srst = 0x04, /* Software Reset */
};

enum { /* PCI Configuration Registers */
	Bmiba = 0x20, /* Bus Master Interface Base Address */
	Idetim = 0x40, /* IE Timing */
	Sidetim = 0x44, /* Slave IE Timing */
	Udmactl = 0x48, /* Ultra DMA/33 Control */
	Udmatim = 0x4A, /* Ultra DMA/33 Timing */
};

enum { /* Bus Master IDE I/O Ports */
	Bmicx = 0, /* Command */
	Bmisx = 2, /* Status */
	Bmidtpx = 4, /* Descriptor Table Pointer */
};

enum { /* Bmicx */
	Ssbm = 0x01, /* Start/Stop Bus Master */
	Rwcon = 0x08, /* Read/Write Control */
};

enum { /* Bmisx */
	Bmidea = 0x01, /* Bus Master IDE Active */
	Idedmae = 0x02, /* IDE DMA Error (R/WC) */
	Ideints = 0x04, /* IDE Interrupt Status (R/WC) */
	Dma0cap = 0x20, /* Drive 0 DMA Capable */
	Dma1cap = 0x40, /* Drive 0 DMA Capable */
};
enum { /* Physical Region Descriptor */
	PrdEOT = 0x80000000, /* Bus Master IDE Active */
};

enum { /* offsets into the identify info. */
	Iconfig = 0, /* general configuration */
	Ilcyl = 1, /* logical cylinders */
	Ilhead = 3, /* logical heads */
	Ilsec = 6, /* logical sectors per logical track */
	Iserial = 10, /* serial number */
	Ifirmware = 23, /* firmware revision */
	Imodel = 27, /* model number */
	Imaxrwm = 47, /* max. read/write multiple sectors */
	Icapabilities = 49, /* capabilities */
	Istandby = 50, /* device specific standby timer */
	Ipiomode = 51, /* PIO data transfer mode number */
	Ivalid = 53,
	Iccyl = 54, /* cylinders if (valid&0x01) */
	Ichead = 55, /* heads if (valid&0x01) */
	Icsec = 56, /* sectors if (valid&0x01) */
	Iccap = 57, /* capacity if (valid&0x01) */
	Irwm = 59, /* read/write multiple */
	Ilba0 = 60, /* LBA size */
	Ilba1 = 61, /* LBA size */
	Imwdma = 63, /* multiword DMA mode */
	Iapiomode = 64, /* advanced PIO modes supported */
	Iminmwdma = 65, /* min. multiword DMA cycle time */
	Irecmwdma = 66, /* rec. multiword DMA cycle time */
	Iminpio = 67, /* min. PIO cycle w/o flow control */
	Iminiordy = 68, /* min. PIO cycle with IORDY */
	Ipcktbr = 71, /* time from PACKET to bus release */
	Iserbsy = 72, /* time from SERVICE to !Bsy */
	Iqdepth = 75, /* max. queue depth */
	Imajor = 80, /* major version number */
	Iminor = 81, /* minor version number */
	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 */
	Irmsn = 127, /* removable status notification */
	Istatus = 128, /* security status */
};

typedef struct Ctlr Ctlr;
typedef struct Drive Drive;

typedef struct Prd {
	ulong pa; /* Physical Base Address */
	int count;
} Prd;

enum {
	Nprd = SDmaxio/(64*1024)+2,
};

typedef struct Ctlr {
	int cmdport;
	int ctlport;
	int irq;
	int tbdf;
	int bmiba; /* bus master interface base address */

	void (*ienable)(Ctlr*);
	SDev* sdev;

	Drive* drive[2];

	Prd* prdt; /* physical region descriptor table */

	QLock; /* current command */
	Drive* curdrive;
	int command; /* last command issued (debugging) */
	Rendez;
	int done;

	Lock; /* register access */
} Ctlr;

typedef struct Drive {
	Ctlr* ctlr;

	int dev;
	ushort info[256];
	int c; /* cylinder */
	int h; /* head */
	int s; /* sector */
	int sectors; /* total */
	int secsize; /* sector size */

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

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

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

	QLock; /* drive access */
	int command; /* current command */
	int write;
	uchar* data;
	int dlen;
	uchar* limit;
	int count; /* sectors */
	int block; /* R/W bytes per block */
	int status;
	int error;
} Drive;


char*
getconf(char*)
{
	return nil;
}

static void
atadumpstate(Drive* drive, uchar* cmd, int lba, int count)
{
	Prd *prd;
	Ctlr *ctlr;
	int i, bmiba;

	if(!(DEBUG & DbgSTATE)){
		USED(drive, cmd, lba, count);
		return;
	}

	ctlr = drive->ctlr;
	print("command %2.2uX\n", ctlr->command);
	print("data %8.8p limit %8.8p dlen %d status %uX error %uX\n",
		drive->data, drive->limit, drive->dlen,
		drive->status, drive->error);
	if(cmd != nil){
		print("lba %d -> %d, count %d -> %d (%d)\n",
			(cmd[2]<<24)|(cmd[3]<<16)|(cmd[4]<<8)|cmd[5],
			lba,
			(cmd[7]<<8)|cmd[8], count, drive->count);
	}
	if(!(inb(ctlr->ctlport+As) & Bsy)){
		for(i = 1; i < 7; i++)
			print(" 0x%2.2uX", inb(ctlr->cmdport+i));
		print(" 0x%2.2uX\n", inb(ctlr->ctlport+As));
	}
	if(drive->command == Cwd || drive->command == Crd){
		bmiba = ctlr->bmiba;
		prd = ctlr->prdt;
		print("bmicx %2.2uX bmisx %2.2uX prdt %8.8p\n",
			inb(bmiba+Bmicx), inb(bmiba+Bmisx), prd);
		for(;;){
			print("pa 0x%8.8luX count %8.8uX\n",
				prd->pa, prd->count);
			if(prd->count & PrdEOT)
				break;
			prd++;
		}
	}
}

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

	if(!(DEBUG & DbgPROBE)){
		USED(cmdport, ctlport, fmt);
		return 0;
	}

	va_start(arg, fmt);
	n = doprint(buf, buf+sizeof(buf), fmt, arg) - buf;
	va_end(arg);

	if(cmdport){
		if(buf[n-1] == '\n')
			n--;
		n += snprint(buf+n, PRINTSIZE-n, " ataregs 0x%uX:",
			cmdport);
		for(i = Features; i < Command; i++)
			n += snprint(buf+n, PRINTSIZE-n, " 0x%2.2uX",
				inb(cmdport+i));
		if(ctlport)
			n += snprint(buf+n, PRINTSIZE-n, " 0x%2.2uX",
				inb(ctlport+As));
		n += snprint(buf+n, PRINTSIZE-n, "\n");
	}
	putstrn(buf, n);

	return n;
}

static int
ataready(int cmdport, int ctlport, int dev, int reset, int ready, int micro)
{
	int as;

	atadebug(cmdport, ctlport, "ataready: dev %uX reset %uX ready %uX",
		dev, reset, ready);

	for(;;){
		/*
		 * Wait for the controller to become not busy and
		 * possibly for a status bit to become true (usually
		 * Drdy). Must change to the appropriate device
		 * register set if necessary before testing for ready.
		 * Always run through the loop at least once so it
		 * can be used as a test for !Bsy.
		 */
		as = inb(ctlport+As);
		if(as & reset)
			;
		else if(dev){
			outb(cmdport+Dh, dev);
			dev = 0;
		}
		else if(ready == 0 || (as & ready)){
			atadebug(0, 0, "ataready: %d 0x%2.2uX\n", micro, as);
			return as;
		}

		if(micro-- <= 0){
			atadebug(0, 0, "ataready: %d 0x%2.2uX\n", micro, as);
			break;
		}
		µdelay(4);
	}
	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);
	µdelay(4);
	as = ataready(cmdport, ctlport, 0, Bsy, Drdy|Df|Err, 1000);
	inb(cmdport+Status);
	if(as < 0 || (as & (Df|Err)))
		return 0;

	drive->rwm = rwm;

	return rwm;
}

static int
atadmamode(Drive* drive)
{
	int dma;

	/*
	 * Check if any DMA mode enabled.
	 * Assumes the BIOS has picked and enabled the best.
	 * This is completely passive at the moment, no attempt is
	 * made to ensure the hardware is correctly set up.
	 */
	dma = drive->info[Imwdma] & 0x0707;
	drive->dma = (dma>>8) & dma;
	if(drive->dma == 0 && (drive->info[Ivalid] & 0x04)){
		dma = drive->info[Iudma] & 0x1F1F;
		drive->dma = (dma>>8) & dma;
		if(drive->dma)
			drive->dma |= 'U'<<16;
	}

	return dma;
}

static int
ataidentify(int cmdport, int ctlport, int dev, int pkt, void* info)
{
	int as, command, drdy;

	if(pkt){
		command = Cidpkt;
		drdy = 0;
	}
	else{
		command = Cid;
		drdy = Drdy;
	}
	as = ataready(cmdport, ctlport, dev, Bsy|Drq, drdy, 103*1000);
	if(as < 0)
		return as;
	outb(cmdport+Command, command);
	µdelay(4);

	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 < 32; i++){
			if(i && (i%16) == 0)
				print("\n");
			print(" %4.4uX", *sp);
			sp++;
		}
		print("\n");
	}

	return 0;
}

static Drive*
atadrive(int cmdport, int ctlport, int dev)
{
	ushort *sp;
	Drive *drive;
	int as, i, pkt;
	uchar buf[512], *p;

	atadebug(0, 0, "identify: port 0x%uX dev 0x%2.2uX\n", cmdport, dev);
	pkt = 1;
retry:
	as = ataidentify(cmdport, ctlport, dev, pkt, buf);
	if(as < 0)
		return nil;
	if(as & Err){
		if(pkt == 0)
			return nil;
		pkt = 0;
		goto retry;
	}

	if((drive = malloc(sizeof(Drive))) == nil)
		return nil;
	drive->dev = dev;
	memmove(drive->info, buf, sizeof(drive->info));
	drive->sense[0] = 0x70;
	drive->sense[7] = sizeof(drive->sense)-7;

	drive->inquiry[2] = 2;
	drive->inquiry[3] = 2;
	drive->inquiry[4] = sizeof(drive->inquiry)-4;
	p = &drive->inquiry[8];
	sp = &drive->info[Imodel];
	for(i = 0; i < 20; i++){
		*p++ = *sp>>8;
		*p++ = *sp++;
	}

	drive->secsize = 512;
	if(drive->info[Iconfig] != 0x848A && (drive->info[Iconfig] & 0xC000)
	== 0x8000){
		if(drive->info[Iconfig] & 0x01)
			drive->pkt = 16;
		else
			drive->pkt = 12;
	}
	else{
		if(drive->info[Ivalid] & 0x0001){
			drive->c = drive->info[Ilcyl];
			drive->h = drive->info[Ilhead];
			drive->s = drive->info[Ilsec];
		}
		else{
			drive->c = drive->info[Iccyl];
			drive->h = drive->info[Ichead];
			drive->s = drive->info[Icsec];
		}
		if(drive->info[Icapabilities] & 0x0200){
			drive->sectors = (drive->info[Ilba1]<<16)
					 |drive->info[Ilba0];
			drive->dev |= Lba;
		}
		else
			drive->sectors = drive->c*drive->h*drive->s;
		atarwmmode(drive, cmdport, ctlport, dev);
	}
	atadmamode(drive);

	if(DEBUG & DbgCONFIG){
		print("dev %2.2uX 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\n", drive->dma, drive->rwm);
	}

	return drive;
}

static void
atasrst(int ctlport)
{
	/*
	 * Srst is a big stick and may cause problems if further
	 * commands are tried before the drives become ready again.
	 * Also, there will be problems here if overlapped commands
	 * are ever supported.
	 */
	µdelay(20);
	outb(ctlport+Dc, Srst);
	µdelay(20);
	outb(ctlport+Dc, 0);
	µdelay(4*1000);
}

static SDev*
ataprobe(int cmdport, int ctlport, int irq)
{
	Ctlr* ctlr;
	SDev *sdev;
	Drive *drive;
	int dev, error, rhi, rlo;

	/*
	 * 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);
		µdelay(5);
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:
				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(5);
	if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 6*1000*1000) < 0)
		goto release;

	/*
	 * If bit 0 of the error register is set then the selected drive
	 * exists. This is enough to detect single-drive configurations.
	 * However, if the master exists there is no way short of executing
	 * a command to determine if a slave is present.
	 * It appears possible to get here testing Dev0 although it doesn't
	 * exist and the EDD won't take, so try again with Dev1.
	 */
	error = inb(cmdport+Error);
	atadebug(cmdport, ctlport, "ataprobe: dev %uX", dev);
	if((error & ~0x80) != 0x01){
		if(dev == Dev1)
			goto release;
		dev = Dev1;
		goto tryedd1;
	}

	/*
	 * At least one drive is known to exist, try to
	 * identify it. If that fails, don't bother checking
	 * any further.
	 * If the one drive found is Dev0 and the EDD command
	 * didn't indicate Dev1 doesn't exist, check for it.
	 */
	if((drive = atadrive(cmdport, ctlport, dev)) == nil)
		goto release;
	if((ctlr = malloc(sizeof(Ctlr))) == nil){
		free(drive);
		goto release;
	}
	if((sdev = malloc(sizeof(SDev))) == nil){
		free(ctlr);
		free(drive);
		goto release;
	}
	drive->ctlr = ctlr;
	if(dev == Dev0){
		ctlr->drive[0] = drive;
#ifdef notdef
		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);
				µdelay(1);
			}
		}
#endif
	}
	else
		ctlr->drive[1] = drive;

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

	sdev->ifc = &sdataifc;
	sdev->ctlr = ctlr;
	sdev->nunit = 1;
	ctlr->sdev = sdev;

	return sdev;
}

static int
atasetsense(Drive* drive, int status, int key, int asc, int ascq)
{
	drive->sense[2] = key;
	drive->sense[12] = asc;
	drive->sense[13] = ascq;

	return status;
}

static int
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, 30*1000);
	poperror();

	done = ctlr->done;
	qunlock(ctlr);

	if(!done || (drive->status & Err))
		return atasetsense(drive, SDcheck, 4, 8, drive->error);
	return SDok;
}

static int
atamodesense(Drive* drive, uchar* cmd)
{
	int len;

	/*
	 * Fake a vendor-specific request with page code 0,
	 * return the drive info.
	 */
	if((cmd[2] & 0x3F) != 0 && (cmd[2] & 0x3F) != 0x3F)
		return atasetsense(drive, SDcheck, 0x05, 0x24, 0);
	len = (cmd[7]<<8)|cmd[8];
	if(len == 0)
		return SDok;
	if(len < 8+sizeof(drive->info))
		return atasetsense(drive, SDcheck, 0x05, 0x1A, 0);
	if(drive->data == nil || drive->dlen < len)
		return atasetsense(drive, SDcheck, 0x05, 0x20, 1);
	memset(drive->data, 0, 8);
	drive->data[0] = sizeof(drive->info)>>8;
	drive->data[1] = sizeof(drive->info);
	memmove(drive->data+8, drive->info, sizeof(drive->info));
	drive->data += 8+sizeof(drive->info);

	return SDok;
}

static void
atanop(Drive* drive, int subcommand)
{
	Ctlr* ctlr;
	int as, cmdport, ctlport, timeo;

	/*
	 * Attempt to abort a command by using NOP.
	 * In response, the drive is supposed to set Abrt
	 * in the Error register, set (Drdy|Err) in Status
	 * and clear Bsy when done. However, some drives
	 * (e.g. ATAPI Zip) just go Bsy then clear Status
	 * when done, hence the timeout loop only on Bsy
	 * and the forced setting of drive->error.
	 */
	ctlr = drive->ctlr;
	cmdport = ctlr->cmdport;
	outb(cmdport+Features, subcommand);
	outb(cmdport+Dh, drive->dev);
	ctlr->command = Cnop; /* debugging */
	outb(cmdport+Command, Cnop);

	µdelay(1);
	ctlport = ctlr->ctlport;
	for(timeo = 0; timeo < 1000; timeo++){
		as = inb(ctlport+As);
		if(!(as & Bsy))
			break;
		µdelay(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(atacsf(drive, 0x0000000000004000LL, 0))
		atanop(drive, 0);
	else{
		atasrst(drive->ctlr->ctlport);
		drive->error |= Abrt;
	}
	if(dolock)
		iunlock(drive->ctlr);
}

static int
atadmasetup(Drive* drive, int )
{
	drive->dmactl = 0;
	return -1;

#ifdef notdef
	Prd *prd;
	ulong pa;
	Ctlr *ctlr;
	int bmiba, bmisx, count;

	pa = PCIWADDR(drive->data);
	if(pa & 0x03)
		return -1;
	ctlr = drive->ctlr;
	prd = ctlr->prdt;

	/*
	 * Sometimes drives identify themselves as being DMA capable
	 * although they are not on a busmastering controller.
	 */
	if(prd == nil){
		drive->dmactl = 0;
		return -1;
	}

	for(;;){
		prd->pa = pa;
		count = 64*1024 - (pa & 0xFFFF);
		if(count >= len){
			prd->count = PrdEOT|(len & 0xFFFF);
			break;
		}
		prd->count = count;
		len -= count;
		pa += count;
		prd++;
	}

	bmiba = ctlr->bmiba;
	outl(bmiba+Bmidtpx, PCIWADDR(ctlr->prdt));
	if(drive->write)
		outb(ctlr->bmiba+Bmicx, 0);
	else
		outb(ctlr->bmiba+Bmicx, Rwcon);
	bmisx = inb(bmiba+Bmisx);
	outb(bmiba+Bmisx, bmisx|Ideints|Idedmae);

	return 0;
#endif
}

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

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

	ilock(ctlr);
	if(drive->dlen && drive->dmactl && !atadmasetup(drive,
	drive->dlen))
		drive->pktdma = Dma;
	else
		drive->pktdma = 0;

	outb(cmdport+Features, drive->pktdma);
	outb(cmdport+Count, 0);
	outb(cmdport+Sector, 0);
	len = 16*drive->secsize;
	outb(cmdport+Bytelo, len);
	outb(cmdport+Bytehi, len>>8);
	outb(cmdport+Dh, drive->dev);
	ctlr->done = 0;
	ctlr->curdrive = drive;
	ctlr->command = Cpkt; /* debugging */
	if(drive->pktdma)
		atadmastart(ctlr, drive->write);
	outb(cmdport+Command, Cpkt);

	if((drive->info[Iconfig] & 0x0060) != 0x0020){
		µdelay(1);
		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Chk, 4*1000);
		if(as < 0)
			r = SDtimeout;
		else if(as & Chk)
			r = SDcheck;
		else
			atapktinterrupt(drive);
	}
	iunlock(ctlr);

	while(waserror())
		;
	if(!drive->pktdma)
		sleep(ctlr, 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 > 10){
			ataabort(drive, 0);
			atadmastop(ctlr);
			drive->dmactl = 0;
			drive->error |= Abrt;
		}
		if(drive->error){
			drive->status |= Chk;
			ctlr->curdrive = nil;
		}
		iunlock(ctlr);
	}
	poperror();

	qunlock(ctlr);

	if(drive->status & Chk)
		r = SDcheck;

	return r;
}

static int
atageniostart(Drive* drive, int lba)
{
	Ctlr *ctlr;
	int as, c, cmdport, ctlport, h, len, s;

	if(drive->dev & Lba){
		c = (lba>>8) & 0xFFFF;
		h = (lba>>24) & 0x0F;
		s = lba & 0xFF;
	}
	else{
		c = lba/(drive->s*drive->h);
		h = ((lba/drive->s) % drive->h);
		s = (lba % drive->s) + 1;
	}

	ctlr = drive->ctlr;
	cmdport = ctlr->cmdport;
	ctlport = ctlr->ctlport;
	if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, 0, 101*1000) < 0)
		return -1;

	ilock(ctlr);
	if(drive->dmactl && !atadmasetup(drive,
	drive->count*drive->secsize)){
		if(drive->write)
			drive->command = Cwd;
		else
			drive->command = Crd;
	}
	else if(drive->rwmctl){
		drive->block = drive->rwm*drive->secsize;
		if(drive->write)
			drive->command = Cwsm;
		else
			drive->command = Crsm;
	}
	else{
		drive->block = drive->secsize;
		if(drive->write)
			drive->command = Cws;
		else
			drive->command = Crs;
	}
	drive->limit = drive->data + drive->count*drive->secsize;

	outb(cmdport+Count, drive->count);
	outb(cmdport+Sector, s);
	outb(cmdport+Dh, drive->dev|h);
	outb(cmdport+Cyllo, c);
	outb(cmdport+Cylhi, c>>8);
	ctlr->done = 0;
	ctlr->curdrive = drive;
	ctlr->command = drive->command; /* debugging */
	outb(cmdport+Command, drive->command);

	switch(drive->command){
	case Cws:
	case Cwsm:
		µdelay(1);
		as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 1000);
		if(as < 0 || (as & Err)){
			iunlock(ctlr);
			return -1;
		}
		len = drive->block;
		if(drive->data+len > drive->limit)
			len = drive->limit-drive->data;
		outss(cmdport+Data, drive->data, len/2);
		break;

	case Crd:
	case Cwd:
		atadmastart(ctlr, drive->write);
		break;
	}
	iunlock(ctlr);

	return 0;
}

static int
atagenioretry(Drive* drive)
{
	if(drive->dmactl)
		drive->dmactl = 0;
	else if(drive->rwmctl)
		drive->rwmctl = 0;
	else
		return atasetsense(drive, SDcheck, 4, 8, drive->error);

	return SDretry;
}

static int
atagenio(Drive* drive, uchar* cmd, int)
{
	uchar *p;
	Ctlr *ctlr;
	int count, lba, len;

	/*
	 * Map SCSI commands into ATA commands for discs.
	 * Fail any command with a LUN except INQUIRY which
	 * will return 'logical unit not supported'.
	 */
	if((cmd[1]>>5) && cmd[0] != 0x12)
		return atasetsense(drive, SDcheck, 0x05, 0x25, 0);

	switch(cmd[0]){
	default:
		return atasetsense(drive, SDcheck, 0x05, 0x20, 0);

	case 0x00: /* test unit ready */
		return SDok;

	case 0x03: /* request sense */
		if(cmd[4] < sizeof(drive->sense))
			len = cmd[4];
		else
			len = sizeof(drive->sense);
		if(drive->data && drive->dlen >= len){
			memmove(drive->data, drive->sense, len);
			drive->data += len;
		}
		return SDok;

	case 0x12: /* inquiry */
		if(cmd[4] < sizeof(drive->inquiry))
			len = cmd[4];
		else
			len = sizeof(drive->inquiry);
		if(drive->data && drive->dlen >= len){
			memmove(drive->data, drive->inquiry, len);
			drive->data += len;
		}
		return SDok;

	case 0x1B: /* start/stop unit */
		/*
		 * NOP for now, can use the power management feature
		 * set later.
		 */
		return SDok;

	case 0x25: /* read capacity */
		if((cmd[1] & 0x01) || cmd[2] || cmd[3])
			return atasetsense(drive, SDcheck, 0x05, 0x24, 0);
		if(drive->data == nil || drive->dlen < 8)
			return atasetsense(drive, SDcheck, 0x05, 0x20, 1);
		/*
		 * Read capacity returns the LBA of the last sector.
		 */
		len = drive->sectors-1;
		p = drive->data;
		*p++ = len>>24;
		*p++ = len>>16;
		*p++ = len>>8;
		*p++ = len;
		len = drive->secsize;
		*p++ = len>>24;
		*p++ = len>>16;
		*p++ = len>>8;
		*p = len;
		drive->data += 8;
		return SDok;

	case 0x28: /* read */
	case 0x2A: /* write */
		break;

	case 0x5A:
		return atamodesense(drive, cmd);
	}

	ctlr = drive->ctlr;
	lba = (cmd[2]<<24)|(cmd[3]<<16)|(cmd[4]<<8)|cmd[5];
	count = (cmd[7]<<8)|cmd[8];
	if(drive->data == nil)
		return SDok;
	if(drive->dlen < count*drive->secsize)
		count = drive->dlen/drive->secsize;
	qlock(ctlr);
	while(count){
		if(count > 256)
			drive->count = 256;
		else
			drive->count = count;
		if(atageniostart(drive, lba)){
			ilock(ctlr);
			atanop(drive, 0);
			iunlock(ctlr);
			qunlock(ctlr);
			return atagenioretry(drive);
		}

		while(waserror())
			;
		tsleep(ctlr, atadone, ctlr, 30*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 & DbgDEBUG)
			print("IBsy+");
		return;
	}
	cmdport = ctlr->cmdport;
	status = inb(cmdport+Status);
	if((drive = ctlr->curdrive) == nil){
		iunlock(ctlr);
		if((DEBUG & DbgDEBUG) && ctlr->command != Cedd)
			print("Inil%2.2uX/%2.2uX+", ctlr->command, status);
		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);
	}
}

#ifdef notdef
static SDev*
atapnp(void)
{
	int cmdport;
	int ctlport;
	int irq;

	cmdport = 0x200;
	ctlport = cmdport + 0x0C;
	irq = 10;
	return ataprobe(cmdport, ctlport, irq);
}
#endif


static SDev*
atalegacy(int port, int irq)
{
	return ataprobe(port, port+0x204, irq);
}

static SDev*
ataid(SDev* sdev)
{
	int i;
	Ctlr *ctlr;

	if(sdev == nil)
		return nil;
	ctlr = sdev->ctlr;
	i = 0;
	while(sdev){
		if(sdev->ifc == &sdataifc){
			ctlr = sdev->ctlr;
			sdev->idno = 'C'+i;
			i++;
			snprint(sdev->name, NAMELEN, "sd%c", sdev->idno);
		}
		sdev = sdev->next;
	}

	return nil;
}

static int ataitype;
static int atairq;
static int
ataenable(SDev* sdev)
{
	Ctlr *ctlr;
	char name[NAMELEN];

	ctlr = sdev->ctlr;

	if(ctlr->bmiba){
		ctlr->prdt = xspanalloc(Nprd*sizeof(Prd), 4, 4*1024);
	}
	snprint(name, NAMELEN, "%s (%s)", sdev->name, sdev->ifc->name);
// intrenable(ctlr->irq, atainterrupt, ctlr, ctlr->tbdf, name);
	outb(ctlr->ctlport+Dc, 0);
	intrenable(ataitype, atairq, atainterrupt, ctlr, name);
	if(ctlr->ienable)
		ctlr->ienable(ctlr);

	return 1;
}

static int
atarctl(SDunit* unit, char* p, int l)
{
	int n;
	Ctlr *ctlr;
	Drive *drive;

	if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno]
	== nil)
		return 0;
	drive = ctlr->drive[unit->subno];

	qlock(drive);
	n = snprint(p, l, "config %4.4uX capabilities %4.4uX",
		drive->info[Iconfig], drive->info[Icapabilities]);
	if(drive->dma)
		n += snprint(p+n, l-n, " dma %8.8uX dmactl %8.8uX",
			drive->dma, drive->dmactl);
	if(drive->rwm)
		n += snprint(p+n, l-n, " rwm %ud rwmctl %ud",
			drive->rwm, drive->rwmctl);
	n += snprint(p+n, l-n, "\n");
	if(unit->sectors){
		n += snprint(p+n, l-n, "geometry %ld %ld",
			unit->sectors, unit->secsize);
		if(drive->pkt == 0)
			n += snprint(p+n, l-n, " %d %d %d",
				drive->c, drive->h, drive->s);
		n += snprint(p+n, l-n, "\n");
	}
	qunlock(drive);

	return n;
}

static int
atawctl(SDunit* unit, Cmdbuf* cb)
{
	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
		error(Ebadctl);
	qunlock(drive);
	poperror();

	return 0;
}

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

	r->status = ~0;

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

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

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


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

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

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

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

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

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

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

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

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

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

	ok = 0;

	r->unit = unit;
	r->lun = 0; /* ??? */
	for(retries = 0; retries < 10; retries++){
		/*
		 * Read-capacity is mandatory for DA, WORM, CD-ROM and
		 * MO. It may return 'not ready' if type DA is not
		 * spun up, type MO or type CD-ROM are not loaded or just
		 * plain slow getting their act together after a reset.
		 */
		r->write = 0;
		memset(r->cmd, 0, sizeof(r->cmd));
		r->cmd[0] = 0x25;
		r->cmd[1] = r->lun<<5;
		r->clen = 10;
		r->data = p;
		r->dlen = 8;
		r->flags = 0;

		r->status = ~0;
		switch(scsirio(r)){
		default:
			break;
		case 0:
			unit->sectors =
			(p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3];
			/*
			 * Read-capacity returns the LBA of the last sector,
			 * therefore the number of sectors must be incremented.
			 */
			unit->sectors++;
			unit->secsize =
			(p[4]<<24)|(p[5]<<16)|(p[6]<<8)|p[7];

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

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

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

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

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

	return rlen;
}


struct Try {
	int p;
	int c;
} tries[] = {
		   { 0, 0x0c },
		   { 0, 0 },
};

static SDev*
ataconfig(int on, char *, void *pf)
{
	DevConf* cf = pf;
	int cmdport;
	int ctlport;
	int irq;
	SDev* rc;
	struct Try *try;

	if(on == 0)
		return nil;
	rc = nil;
	for (try = &tries[0]; try->p != 0 || try->c != 0; try++){
		ataitype = cf->itype;
		atairq = cf->interrupt;
		cmdport = cf->port + try->p;
		ctlport = cmdport + try->c;
		irq = cf->interrupt;
		rc = ataprobe(cmdport, ctlport, irq);
		if (rc)
			break;
	}
	return rc;
}

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

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

	ataverify, /* verify */
	ataonline, /* online */
	atario, /* rio */
	atarctl, /* rctl */
	atawctl, /* wctl */

	atabio, /* bio */
	ataconfig, /* config */
};

--upas-cefrnfuwhwfqcaxqtnsykuzaqu
Content-Disposition: attachment; filename=cis.c
Content-Type: text/plain; charset="US-ASCII"
Content-Transfer-Encoding: 7bit

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "io.h"

/*
 * read and crack the card information structure enough to set
 * important parameters like power
 */
/* cis memory walking */
typedef struct Cisdat {
	uchar *cisbase;
	int cispos;
	int cisskip;
	int cislen;
} Cisdat;

static void tcfig(PCMslot*, Cisdat*, int);
static void tentry(PCMslot*, Cisdat*, int);
static void tvers1(PCMslot*, Cisdat*, int);

static int
readc(Cisdat *cis, uchar *x)
{
	if(cis->cispos >= cis->cislen)
		return 0;
	*x = cis->cisbase[cis->cisskip*cis->cispos];
	cis->cispos++;
	return 1;
}

static int
xcistuple(int slotno, int tuple, int subtuple, void *v, int nv, int attr)
{
	PCMmap *m;
	Cisdat cis;
	int i, l;
	uchar *p;
	uchar type, link, n, c;
	int this, subtype;

	m = pcmmap(slotno, 0, 0, attr);
	if(m == 0)
		return -1;

	cis.cisbase = KADDR(m->isa);
	cis.cispos = 0;
	cis.cisskip = attr ? 2 : 1;
	cis.cislen = m->len;

	/* loop through all the tuples */
	for(i = 0; i < 1000; i++){
		this = cis.cispos;
		if(readc(&cis, &type) != 1)
			break;
		if(type == 0xFF)
			break;
		if(readc(&cis, &link) != 1)
			break;
		if(link == 0xFF)
			break;

		n = link;
		if (link > 1 && subtuple != -1) {
			if (readc(&cis, &c) != 1)
				break;
			subtype = c;
			n--;
		} else
			subtype = -1;

		if(type == tuple && subtype == subtuple) {
			p = v;
			for(l=0; l<nv && l<n; l++)
				if(readc(&cis, p++) != 1)
					break;
			pcmunmap(slotno, m);
			return nv;
		}
		cis.cispos = this + (2+link);
	}
	pcmunmap(slotno, m);
	return -1;
}

int
pcmcistuple(int slotno, int tuple, int subtuple, void *v, int nv)
{
	int n;

	/* try attribute space, then memory */
	if((n = xcistuple(slotno, tuple, subtuple, v, nv, 1)) >= 0)
		return n;
	return xcistuple(slotno, tuple, subtuple, v, nv, 0);
}

void
pcmcisread(PCMslot *pp)
{
	int this;
	Cisdat cis;
	PCMmap *m;
	uchar type, link;

	memset(pp->ctab, 0, sizeof(pp->ctab));
	pp->caddr = 0;
	pp->cpresent = 0;
	pp->configed = 0;
	pp->nctab = 0;
	pp->verstr[0] = 0;

	/*
	 * Read all tuples in attribute space.
	 */
	m = pcmmap(pp->slotno, 0, 0, 1);
	if(m == 0)
		return;

	cis.cisbase = KADDR(m->isa);
	cis.cispos = 0;
	cis.cisskip = 2;
	cis.cislen = m->len;
	/* loop through all the tuples */
	for(;;){
		this = cis.cispos;
		if(readc(&cis, &type) != 1)
			break;
		if(type == 0xFF)
			break;
		if(readc(&cis, &link) != 1)
			break;

		switch(type){
		default:
			break;
		case 0x15:
			tvers1(pp, &cis, type);
			break;
		case 0x1A:
			tcfig(pp, &cis, type);
			break;
		case 0x1B:
			tentry(pp, &cis, type);
			break;
		}

		if(link == 0xFF)
			break;
		cis.cispos = this + (2+link);
	}
	pcmunmap(pp->slotno, m);
}

static ulong
getlong(Cisdat *cis, int size)
{
	uchar c;
	int i;
	ulong x;

	x = 0;
	for(i = 0; i < size; i++){
		if(readc(cis, &c) != 1)
			break;
		x |= c<<(i*8);
	}
	return x;
}

static void
tcfig(PCMslot *pp, Cisdat *cis, int )
{
	uchar size, rasize, rmsize;
	uchar last;

	if(readc(cis, &size) != 1)
		return;
	rasize = (size&0x3) + 1;
	rmsize = ((size>>2)&0xf) + 1;
	if(readc(cis, &last) != 1)
		return;
	pp->caddr = getlong(cis, rasize);
	pp->cpresent = getlong(cis, rmsize);
}

static ulong vexp[8] =
{
	1, 10, 100, 1000, 10000, 100000, 1000000, 10000000
};
static ulong vmant[16] =
{
	10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, 90,
};

static ulong
microvolt(Cisdat *cis)
{
	uchar c;
	ulong microvolts;
	ulong exp;

	if(readc(cis, &c) != 1)
		return 0;
	exp = vexp[c&0x7];
	microvolts = vmant[(c>>3)&0xf]*exp;
	while(c & 0x80){
		if(readc(cis, &c) != 1)
			return 0;
		switch(c){
		case 0x7d:
			break; /* high impedence when sleeping */
		case 0x7e:
		case 0x7f:
			microvolts = 0; /* no connection */
			break;
		default:
			exp /= 10;
			microvolts += exp*(c&0x7f);
		}
	}
	return microvolts;
}

static ulong
nanoamps(Cisdat *cis)
{
	uchar c;
	ulong nanoamps;

	if(readc(cis, &c) != 1)
		return 0;
	nanoamps = vexp[c&0x7]*vmant[(c>>3)&0xf];
	while(c & 0x80){
		if(readc(cis, &c) != 1)
			return 0;
		if(c == 0x7d || c == 0x7e || c == 0x7f)
			nanoamps = 0;
	}
	return nanoamps;
}

/*
 * only nominal voltage (feature 1) is important for config,
 * other features must read card to stay in sync.
 */
static ulong
power(Cisdat *cis)
{
	uchar feature;
	ulong mv;

	mv = 0;
	if(readc(cis, &feature) != 1)
		return 0;
	if(feature & 1)
		mv = microvolt(cis);
	if(feature & 2)
		microvolt(cis);
	if(feature & 4)
		microvolt(cis);
	if(feature & 8)
		nanoamps(cis);
	if(feature & 0x10)
		nanoamps(cis);
	if(feature & 0x20)
		nanoamps(cis);
	if(feature & 0x40)
		nanoamps(cis);
	return mv/1000000;
}

static ulong mantissa[16] =
{ 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, };

static ulong exponent[8] =
{ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, };

static ulong
ttiming(Cisdat *cis, int scale)
{
	uchar unscaled;
	ulong nanosecs;

	if(readc(cis, &unscaled) != 1)
		return 0;
	nanosecs = (mantissa[(unscaled>>3)&0xf]*exponent[unscaled&7])/10;
	nanosecs = nanosecs * vexp[scale];
	return nanosecs;
}

static void
timing(Cisdat *cis, PCMconftab *ct)
{
	uchar c, i;

	if(readc(cis, &c) != 1)
		return;
	i = c&0x3;
	if(i != 3)
		ct->maxwait = ttiming(cis, i); /* max wait */
	i = (c>>2)&0x7;
	if(i != 7)
		ct->readywait = ttiming(cis, i); /* max ready/busy wait */
	i = (c>>5)&0x7;
	if(i != 7)
		ct->otherwait = ttiming(cis, i); /* reserved wait */
}

static void
iospaces(Cisdat *cis, PCMconftab *ct)
{
	uchar c;
	int i, nio;

	ct->nio = 0;
	if(readc(cis, &c) != 1)
		return;

	ct->bit16 = ((c>>5)&3) >= 2;
	if(!(c & 0x80)){
		ct->io[0].start = 0;
		ct->io[0].len = 1<<(c&0x1f);
		ct->nio = 1;
		return;
	}

	if(readc(cis, &c) != 1)
		return;

	/*
	 * For each of the range descriptions read the
	 * start address and the length (value is length-1).
	 */
	nio = (c&0xf)+1;
	for(i = 0; i < nio; i++){
		ct->io[i].start = getlong(cis, (c>>4)&0x3);
		ct->io[i].len = getlong(cis, (c>>6)&0x3)+1;
	}
	ct->nio = nio;
}

static void
irq(Cisdat *cis, PCMconftab *ct)
{
	uchar c;

	if(readc(cis, &c) != 1)
		return;
	ct->irqtype = c & 0xe0;
	if(c & 0x10)
		ct->irqs = getlong(cis, 2);
	else
		ct->irqs = 1<<(c&0xf);
	ct->irqs &= 0xDEB8; /* levels available to card */
}

static void
memspace(Cisdat *cis, int asize, int lsize, int host)
{
	ulong haddress, address, len;

	len = getlong(cis, lsize)*256;
	address = getlong(cis, asize)*256;
	USED(len, address);
	if(host){
		haddress = getlong(cis, asize)*256;
		USED(haddress);
	}
}

static void
tentry(PCMslot *pp, Cisdat *cis, int )
{
	uchar c, i, feature;
	PCMconftab *ct;

	if(pp->nctab >= nelem(pp->ctab))
		return;
	if(readc(cis, &c) != 1)
		return;
	ct = &pp->ctab[pp->nctab++];

	/* copy from last default config */
	if(pp->def)
		*ct = *pp->def;

	ct->index = c & 0x3f;

	/* is this the new default? */
	if(c & 0x40)
		pp->def = ct;

	/* memory wait specified? */
	if(c & 0x80){
		if(readc(cis, &i) != 1)
			return;
		if(i&0x80)
			ct->memwait = 1;
	}

	if(readc(cis, &feature) != 1)
		return;
	switch(feature&0x3){
	case 1:
		ct->vpp1 = ct->vpp2 = power(cis);
		break;
	case 2:
		power(cis);
		ct->vpp1 = ct->vpp2 = power(cis);
		break;
	case 3:
		power(cis);
		ct->vpp1 = power(cis);
		ct->vpp2 = power(cis);
		break;
	default:
		break;
	}
	if(feature&0x4)
		timing(cis, ct);
	if(feature&0x8)
		iospaces(cis, ct);
	if(feature&0x10)
		irq(cis, ct);
	switch((feature>>5)&0x3){
	case 1:
		memspace(cis, 0, 2, 0);
		break;
	case 2:
		memspace(cis, 2, 2, 0);
		break;
	case 3:
		if(readc(cis, &c) != 1)
			return;
		for(i = 0; i <= (c&0x7); i++)
			memspace(cis, (c>>5)&0x3, (c>>3)&0x3, c&0x80);
		break;
	}
	pp->configed++;
}

static void
tvers1(PCMslot *pp, Cisdat *cis, int )
{
	uchar c, major, minor, last;
	int i;

	if(readc(cis, &major) != 1)
		return;
	if(readc(cis, &minor) != 1)
		return;
	last = 0;
	for(i = 0; i < sizeof(pp->verstr)-1; i++){
		if(readc(cis, &c) != 1)
			return;
		if(c == 0)
			c = ';';
		if(c == '\n')
			c = ';';
		if(c == 0xff)
			break;
		if(c == ';' && last == ';')
			continue;
		pp->verstr[i] = c;
		last = c;
	}
	pp->verstr[i] = 0;
}

--upas-cefrnfuwhwfqcaxqtnsykuzaqu
Content-Disposition: attachment; filename=devsd.c
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: 8bit

/*
 * Storage Device.
 */
#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"

extern Dev sddevtab;
extern SDifc* sdifc[];

static QLock sdqlock;
static SDev* sdlist;
static SDunit** sdunit;
static int* sdunitflg;
static int sdnunit;

enum {
	Rawcmd,
	Rawdata,
	Rawstatus,
};

enum {
	Qtopdir = 1, /* top level directory */
	Qtopbase,

	Qunitdir, /* directory per unit */
	Qunitbase,
	Qctl = Qunitbase,
	Qlog,
	Qraw,
	Qpart,
};

#define TYPE(q) ((q).path & 0x0F)
#define PART(q) (((q).path>>4) & 0x0F)
#define UNIT(q) (((q).path>>8) & 0xFF)
#define QID(u, p, t) (((u)<<8)|((p)<<4)|(t))

static void
sdaddpart(SDunit* unit, char* name, ulong start, ulong end)
{
	SDpart *pp;
	int i, partno;

	/*
	 * Check name not already used
	 * and look for a free slot.
	 */
	if(unit->part != nil){
		partno = -1;
		for(i = 0; i < unit->npart; i++){
			pp = &unit->part[i];
			if(!pp->valid){
				if(partno == -1)
					partno = i;
				break;
			}
			if(strncmp(name, pp->name, NAMELEN) == 0){
				if(pp->start == start && pp->end == end)
					return;
				error(Ebadctl);
			}
		}
	}
	else{
		if((unit->part = malloc(sizeof(SDpart)*SDnpart)) == nil)
			error(Enomem);
		unit->npart = SDnpart;
		partno = 0;
	}

	/*
	 * If no free slot found then increase the
	 * array size (can't get here with unit->part == nil).
	 */
	if(partno == -1){
		if((pp = malloc(sizeof(SDpart)*(unit->npart+SDnpart))) == nil)
			error(Enomem);
		memmove(pp, unit->part, sizeof(SDpart)*unit->npart);
		free(unit->part);
		unit->part = pp;
		partno = unit->npart;
		unit->npart += SDnpart;
	}

	/*
	 * Check size and extent are valid.
	 */
	if(start > end || end > unit->sectors)
		error(Eio);
	pp = &unit->part[partno];
	pp->start = start;
	pp->end = end;
	strncpy(pp->name, name, NAMELEN);
	strncpy(pp->user, eve, NAMELEN);
	pp->perm = 0640;
	pp->valid = 1;
}

static void
sddelpart(SDunit* unit, char* name)
{
	int i;
	SDpart *pp;

	/*
	 * Look for the partition to delete.
	 * Can't delete if someone still has it open.
	 */
	pp = unit->part;
	for(i = 0; i < unit->npart; i++){
		if(strncmp(name, pp->name, NAMELEN) == 0)
			break;
		pp++;
	}
	if(i >= unit->npart)
		error(Ebadctl);
	pp->valid = 0;
	pp->vers++;
}

static int
sdinitpart(SDunit* unit)
{
	int i, nf;
	ulong start, end;
	char *f[4], *p, *q, buf[10];

	unit->vers++;
	unit->sectors = unit->secsize = 0;
	if(unit->part){
		for(i = 0; i < unit->npart; i++){
			unit->part[i].valid = 0;
			unit->part[i].vers++;
		}
	}

	if(unit->inquiry[0] & 0xC0)
		return 0;
	switch(unit->inquiry[0] & 0x1F){
	case 0x00: /* DA */
	case 0x04: /* WORM */
	case 0x05: /* CD-ROM */
	case 0x07: /* MO */
		break;
	default:
		return 0;
	}

	if(unit->dev->ifc->online)
		unit->dev->ifc->online(unit);
	if(unit->sectors){
		sdaddpart(unit, "data", 0, unit->sectors);

		/*
		 * Use partitions passed from boot program,
		 * e.g.
		 * sdC0part=dos 63 123123/plan9 123123 456456
		 * This happens before /boot sets hostname so the
		 * partitions will have the null-string for user.
		 * The gen functions patch it up.
		 */
		snprint(buf, sizeof buf, "%spart", unit->name);
		for(p = getconf(buf); p != nil; p = q){
			if(q = strchr(p, '/'))
				*q++ = '\0';
			nf = getfields(p, f, nelem(f), 1, " \t\r");
			if(nf < 3)
				continue;

			start = strtoul(f[1], 0, 0);
			end = strtoul(f[2], 0, 0);
			if(!waserror()){
				sdaddpart(unit, f[0], start, end);
				poperror();
			}
		}
	}

	return 1;
}

static SDunit*
sdgetunit(SDev* sdev, int subno)
{
	int index;
	SDunit *unit;

	/*
	 * Associate a unit with a given device and sub-unit
	 * number on that device.
	 * The device will be probed if it has not already been
	 * successfully accessed.
	 */
	qlock(&sdqlock);
	index = sdev->index+subno;
	unit = sdunit[index];
	if(unit == nil){
		/*
		 * Probe the unit only once. This decision
		 * may be a little severe and reviewed later.
		 */
		if(sdunitflg[index]){
			qunlock(&sdqlock);
			return nil;
		}
		if((unit = malloc(sizeof(SDunit))) == nil){
			qunlock(&sdqlock);
			return nil;
		}
		sdunitflg[index] = 1;

		if(sdev->enabled == 0 && sdev->ifc->enable)
			sdev->ifc->enable(sdev);
		sdev->enabled = 1;
		µdelay(5);
		snprint(unit->name, NAMELEN, "%s%d", sdev->name, subno);
		strncpy(unit->user, eve, NAMELEN);
		unit->perm = 0555;
		unit->subno = subno;
		unit->dev = sdev;

		unit->log.logmask = ~0;
		unit->log.nlog = 16*1024;
		unit->log.minread = 4*1024;

		/*
		 * No need to lock anything here as this is only
		 * called before the unit is made available in the
		 * sdunit[] array.
		 */
		if(unit->dev->ifc->verify(unit) == 0){
			qunlock(&sdqlock);
			free(unit);
			return nil;
		}
		sdunit[index] = unit;
	}
	qunlock(&sdqlock);

	return unit;
}

static SDunit*
sdindex2unit(int index)
{
	SDev *sdev;

	/*
	 * Associate a unit with a given index into the top-level
	 * device directory.
	 * The device will be probed if it has not already been
	 * successfully accessed.
	 */
	for(sdev = sdlist; sdev != nil; sdev = sdev->next){
		if(index >= sdev->index && index <
		sdev->index+sdev->nunit)
			return sdgetunit(sdev, index-sdev->index);
	}

	return nil;

}

static void
sdreset(void)
{
	int i;
	SDev *sdev, *tail;

	/*
	 * Probe all configured controllers and make a list
	 * of devices found, accumulating a possible maximum number
	 * of units attached and marking each device with an index
	 * into the linear top-level directory array of units.
	 */
	tail = nil;
	for(i = 0; sdifc[i] != nil; i++){
		if(sdifc[i]->pnp == nil || (sdev = sdifc[i]->pnp()) == nil)
			continue;
		if(sdlist != nil)
			tail->next = sdev;
		else
			sdlist = sdev;
		for(tail = sdev; tail->next != nil; tail = tail->next){
			sdev->index = sdnunit;
			sdnunit += tail->nunit;
		}
		tail->index = sdnunit;
		sdnunit += tail->nunit;
	}

	/*
	 * Legacy and option code goes here. This will be hard...
	 */

	/*
	 * The maximum number of possible units is known, allocate
	 * placeholders for their datastructures; the units will be
	 * probed and structures allocated when attached.
	 * Allocate controller names for the different types.
	 */
	if(sdnunit == 0)
		return;
	if((sdunit = malloc(sdnunit*sizeof(SDunit*))) == nil)
		return;
	if((sdunitflg = malloc(sdnunit*sizeof(int))) == nil){
		free(sdunit);
		sdunit = nil;
		return;
	}
	for(i = 0; sdifc[i] != nil; i++){
		/*
		 * BUG: no check is made here or later when a
		 * unit is attached that the id and name are set.
		 */
		if(sdifc[i]->id)
			sdifc[i]->id(sdlist);
	}
}

static int
sdconfig(int on, char *spec, DevConf *cf)
{
	int i;
	SDev *sdev, *tail;

	/* can't unconfigure */
	if(on == 0)
		return -1;

	/*
	 * Config all configured controllers and make a list
	 * of devices found, accumulating a possible maximum number
	 * of units attached and marking each device with an index
	 * into the linear top-level directory array of units.
	 */
	tail = nil;
	for(i = 0; sdifc[i] != nil; i++){
		if(sdifc[i]->config == nil)
			break;
		if((sdev = sdifc[i]->config(on, spec, cf)) == nil)
			break;
		if(sdlist != nil)
			tail->next = sdev;
		else
			sdlist = sdev;
		for(tail = sdev; tail->next != nil; tail = tail->next){
			sdev->index = sdnunit;
			sdnunit += tail->nunit;
		}
		tail->index = sdnunit;
		sdnunit += tail->nunit;
	}

	/*
	 * The maximum number of possible units is known, allocate
	 * placeholders for their datastructures; the units will be
	 * probed and structures allocated when attached.
	 * Allocate controller names for the different types.
	 */
	if(sdnunit == 0)
		return -1;
	if((sdunit = malloc(sdnunit*sizeof(SDunit*))) == nil)
		return -1;
	if((sdunitflg = malloc(sdnunit*sizeof(int))) == nil){
		free(sdunit);
		sdunit = nil;
		return -1;
	}
	for(i = 0; sdifc[i] != nil; i++){
		/*
		 * BUG: no check is made here or later when a
		 * unit is attached that the id and name are set.
		 */
		if(sdifc[i]->id)
			sdifc[i]->id(sdlist);
	}
	return 0;

}

static int
sd2gen(Chan* c, int i, Dir* dp)
{
	Qid q;
	vlong l;
	SDpart *pp;
	SDperm *perm;
	SDunit *unit;

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

static int
sd1gen(Chan*, int i, Dir*)
{
	switch(i){
	default:
		return -1;
	}
	return -1;
}

static int
sdgen(Chan* c, Dirtab*, int, int s, Dir* dp)
{
	Qid q;
	vlong l;
	int i, r;
	SDpart *pp;
	SDunit *unit;
	char name[NAMELEN];

	switch(TYPE(c->qid)){
	case Qtopdir:
		if(s == DEVDOTDOT){
			q = (Qid){QID(0, 0, Qtopdir)|CHDIR, 0};
			snprint(name, NAMELEN, "#%C", sddevtab.dc);
			devdir(c, q, name, 0, eve, 0555, dp);
			return 1;
		}
		if(s < sdnunit){
			if((unit = sdunit[s]) == nil){
				if((unit = sdindex2unit(s)) == nil)
					return 0;
			}
			q = (Qid){QID(s, 0, Qunitdir)|CHDIR, 0};
			if(unit->user[0] == '\0')
				strncpy(unit->user, eve, NAMELEN);
			devdir(c, q, unit->name, 0, unit->user,
			unit->perm, dp);
			return 1;
		}
		s -= sdnunit;
		return sd1gen(c, s+Qtopbase, dp);
	case Qunitdir:
		if(s == DEVDOTDOT){
			q = (Qid){QID(0, 0, Qtopdir)|CHDIR, 0};
			snprint(name, NAMELEN, "#%C", sddevtab.dc);
			devdir(c, q, name, 0, eve, 0555, dp);
			return 1;
		}
		unit = sdunit[UNIT(c->qid)];
		qlock(&unit->ctl);

		/*
		 * Check for media change.
		 * If one has already been detected, sectors will be zero.
		 * If there is one waiting to be detected, online
		 * will return > 1.
		 * Online is a bit of a large hammer but does the job.
		 */
		if(unit->sectors == 0
		|| (unit->dev->ifc->online &&
		unit->dev->ifc->online(unit) > 1))
			sdinitpart(unit);

		i = s+Qunitbase;
		if(i < Qpart){
			r = sd2gen(c, i, dp);
			qunlock(&unit->ctl);
			return r;
		}
		i -= Qpart;
		if(unit->part == nil || i >= unit->npart){
			qunlock(&unit->ctl);
			break;
		}
		pp = &unit->part[i];
		if(!pp->valid){
			qunlock(&unit->ctl);
			return 0;
		}
		l = (pp->end - pp->start) * (vlong)unit->secsize;
		q = (Qid){QID(UNIT(c->qid), i, Qpart),
		unit->vers+pp->vers};
		if(pp->user[0] == '\0')
			strncpy(pp->user, eve, NAMELEN);
		devdir(c, q, pp->name, l, pp->user, pp->perm, dp);
		qunlock(&unit->ctl);
		return 1;
	case Qraw:
	case Qctl:
	case Qpart:
	case Qlog:
		unit = sdunit[UNIT(c->qid)];
		qlock(&unit->ctl);
		r = sd2gen(c, TYPE(c->qid), dp);
		qunlock(&unit->ctl);
		return r;
	default:
		break;
	}

	return -1;
}

static Chan*
sdattach(char* spec)
{
	Chan *c;
	char *p;
	SDev *sdev;
	int idno, subno;

	if(sdnunit == 0 || *spec == '\0'){
		c = devattach(sddevtab.dc, spec);
		c->qid = (Qid){QID(0, 0, Qtopdir)|CHDIR, 0};
		return c;
	}

	if(spec[0] != 's' || spec[1] != 'd')
		error(Ebadspec);
	idno = spec[2];
	subno = strtol(&spec[3], &p, 0);
	if(p == &spec[3])
		error(Ebadspec);
	for(sdev = sdlist; sdev != nil; sdev = sdev->next){
		if(sdev->idno == idno && subno < sdev->nunit)
			break;
	}
	if(sdev == nil || sdgetunit(sdev, subno) == nil)
		error(Enonexist);

	c = devattach(sddevtab.dc, spec);
	c->qid = (Qid){QID(sdev->index+subno, 0, Qunitdir)|CHDIR, 0};
	c->dev = sdev->index+subno;
	return c;
}

static Chan*
sdclone(Chan* c, Chan* nc)
{
	return devclone(c, nc);
}

static int
sdwalk(Chan* c, char* name)
{
	return devwalk(c, name, nil, 0, sdgen);
}

static void
sdstat(Chan* c, char* db)
{
	devstat(c, db, nil, 0, sdgen);
}

static Chan*
sdopen(Chan* c, int omode)
{
	SDpart *pp;
	SDunit *unit;

	c = devopen(c, omode, 0, 0, sdgen);
	switch(TYPE(c->qid)){
	default:
		break;
	case Qlog:
		unit = sdunit[UNIT(c->qid)];
		logopen(&unit->log);
		break;
	case Qctl:
		unit = sdunit[UNIT(c->qid)];
		c->qid.vers = unit->vers;
		break;
	case Qraw:
		unit = sdunit[UNIT(c->qid)];
		c->qid.vers = unit->vers;
		if(!canlock(&unit->rawinuse)){
			c->flag &= ~COPEN;
			error(Einuse);
		}
		unit->state = Rawcmd;
		break;
	case Qpart:
		unit = sdunit[UNIT(c->qid)];
		qlock(&unit->ctl);
		if(waserror()){
			qunlock(&unit->ctl);
			c->flag &= ~COPEN;
			nexterror();
		}
		pp = &unit->part[PART(c->qid)];
		c->qid.vers = unit->vers+pp->vers;
		qunlock(&unit->ctl);
		poperror();
		break;
	}
	return c;
}

static void
sdclose(Chan* c)
{
	SDunit *unit;

	if(c->qid.path & CHDIR)
		return;
	if(!(c->flag & COPEN))
		return;

	switch(TYPE(c->qid)){
	default:
		break;
	case Qlog:
		unit = sdunit[UNIT(c->qid)];
		logclose(&unit->log);
		break;
	case Qraw:
		unit = sdunit[UNIT(c->qid)];
		unlock(&unit->rawinuse);
		break;
	}
}

static long
sdbio(Chan* c, int write, char* a, long len, vlong off)
{
	int nchange;
	long l;
	uchar *b;
	SDpart *pp;
	SDunit *unit;
	ulong bno, max, nb, offset;

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

	nchange = 0;
	qlock(&unit->ctl);
	while(waserror()){
		/* notification of media change; go around again */
		if(strcmp(up->error, Eio) == 0 && unit->sectors == 0 &&
		nchange++ == 0){
			sdinitpart(unit);
			continue;
		}

		/* other errors; give up */
		qunlock(&unit->ctl);
		nexterror();
	}
	pp = &unit->part[PART(c->qid)];
	if(unit->vers+pp->vers != c->qid.vers)
		error(Eio);

	/*
	 * Check the request is within bounds.
	 * Removeable drives are locked throughout the I/O
	 * in case the media changes unexpectedly.
	 * Non-removeable drives are not locked during the I/O
	 * to allow the hardware to optimise if it can; this is
	 * a little fast and loose.
	 * It's assumed that non-removeable media parameters
	 * (sectors, secsize) can't change once the drive has
	 * been brought online.
	 */
	bno = (off/unit->secsize) + pp->start;
	nb = ((off+len+unit->secsize-1)/unit->secsize) + pp->start - bno;
	max = SDmaxio/unit->secsize;
	if(nb > max)
		nb = max;
	if(bno+nb > pp->end)
		nb = pp->end - bno;
	if(bno >= pp->end || nb == 0){
		if(write)
			error(Eio);
		qunlock(&unit->ctl);
		poperror();
		return 0;
	}
	if(!(unit->inquiry[1] & 0x80)){
		qunlock(&unit->ctl);
		poperror();
	}

	if(unit->log.opens) {
		int i;
		uchar lbuf[1+4+8], *p;
		ulong x[3];

		p = lbuf;
		*p++ = write ? 'w' : 'r';
		x[0] = off>>32;
		x[1] = off;
		x[2] = len;
		for(i=0; i<3; i++) {
			*p++ = x[i]>>24;
			*p++ = x[i]>>16;
			*p++ = x[i]>>8;
			*p++ = x[i];
		}

		logn(&unit->log, 1, lbuf, 1+4+8);
	}

	b = sdmalloc(nb*unit->secsize);
	if(b == nil)
		error(Enomem);
	if(waserror()){
		sdfree(b);
		nexterror();
	}

	offset = off%unit->secsize;
	if(write){
		if(offset || (len%unit->secsize)){
			l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno);
			if(l < 0)
				error(Eio);
			if(l < (nb*unit->secsize)){
				nb = l/unit->secsize;
				l = nb*unit->secsize - offset;
				if(len > l)
					len = l;
			}
		}
		memmove(b+offset, a, len);
		l = unit->dev->ifc->bio(unit, 0, 1, b, nb, bno);
		if(l < 0)
			error(Eio);
		if(l < offset)
			len = 0;
		else if(len > l - offset)
			len = l - offset;
	}
	else {
		l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno);
		if(l < 0)
			error(Eio);
		if(l < offset)
			len = 0;
		else if(len > l - offset)
			len = l - offset;
		memmove(a, b+offset, len);
	}
	sdfree(b);
	poperror();

	if(unit->inquiry[1] & 0x80){
		qunlock(&unit->ctl);
		poperror();
	}

	return len;
}

static long
sdrio(SDreq* r, void* a, long n)
{
	void *data;

	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);
	}
	r->data = data;
	r->dlen = n;

	if(waserror()){
		if(data != nil){
			sdfree(data);
			r->data = nil;
		}
		nexterror();
	}

	if(r->unit->dev->ifc->rio(r) != SDok)
		error(Eio);

	if(!r->write && r->rlen > 0)
		memmove(a, data, r->rlen);
	if(data != nil){
		sdfree(data);
		r->data = nil;
	}
	poperror();

	return r->rlen;
}

static long
sdread(Chan *c, void *a, long n, vlong off)
{
	char *p;
	SDpart *pp;
	SDunit *unit;
	ulong offset;
	int i, l, status;

	offset = off;
	switch(TYPE(c->qid)){
	default:
		error(Eperm);
	case Qtopdir:
	case Qunitdir:
		return devdirread(c, a, n, 0, 0, sdgen);
	case Qlog:
		unit = sdunit[UNIT(c->qid)];
		return logread(&unit->log, a, 0, n);
	case Qctl:
		unit = sdunit[UNIT(c->qid)];
		p = malloc(READSTR);
		l = snprint(p, READSTR, "inquiry %.48s\n",
			(char*)unit->inquiry+8);
		qlock(&unit->ctl);
		/*
		 * If there's a device specific routine it must
		 * provide all information pertaining to night geometry
		 * and the garscadden trains.
		 */
		if(unit->dev->ifc->rctl)
			l += unit->dev->ifc->rctl(unit, p+l, READSTR-l);
		if(unit->sectors == 0)
			sdinitpart(unit);
		if(unit->sectors){
			if(unit->dev->ifc->rctl == nil)
				l += snprint(p+l, READSTR-l,
					"geometry %ld %ld\n",
					unit->sectors, unit->secsize);
			pp = unit->part;
			for(i = 0; i < unit->npart; i++){
				if(pp->valid)
					l += snprint(p+l, READSTR-l,
						"part %.*s %lud %lud\n",
						NAMELEN, pp->name,
						pp->start, pp->end);
				pp++;
			}
		}
		qunlock(&unit->ctl);
		l = readstr(offset, a, n, p);
		free(p);
		return l;
	case Qraw:
		unit = sdunit[UNIT(c->qid)];
		qlock(&unit->raw);
		if(waserror()){
			qunlock(&unit->raw);
			nexterror();
		}
		if(unit->state == Rawdata){
			unit->state = Rawstatus;
			i = sdrio(unit->req, a, n);
		}
		else if(unit->state == Rawstatus){
			status = unit->req->status;
			unit->state = Rawcmd;
			free(unit->req);
			unit->req = nil;
			i = readnum(0, a, n, status, NUMSIZE);
		} else
			i = 0;
		qunlock(&unit->raw);
		poperror();
		return i;
	case Qpart:
		return sdbio(c, 0, a, n, off);
	}

	return 0;
}

static long
sdwrite(Chan *c, void *a, long n, vlong off)
{
	Cmdbuf *cb;
	SDreq *req;
	SDunit *unit;
	ulong end, start;

	switch(TYPE(c->qid)){
	default:
		error(Eperm);

	case Qlog:
		error(Ebadctl);

	case Qctl:
		cb = parsecmd(a, n);
		unit = sdunit[UNIT(c->qid)];

		qlock(&unit->ctl);
		if(waserror()){
			qunlock(&unit->ctl);
			free(cb);
			nexterror();
		}
		if(unit->vers != c->qid.vers)
			error(Eio);

		if(cb->nf < 1)
			error(Ebadctl);
		if(strcmp(cb->f[0], "part") == 0){
			if(cb->nf != 4)
				error(Ebadctl);
			if(unit->sectors == 0 && !sdinitpart(unit))
				error(Eio);
			start = strtoul(cb->f[2], 0, 0);
			end = strtoul(cb->f[3], 0, 0);
			sdaddpart(unit, cb->f[1], start, end);
		}
		else if(strcmp(cb->f[0], "delpart") == 0){
			if(cb->nf != 2 || unit->part == nil)
				error(Ebadctl);
			sddelpart(unit, cb->f[1]);
		}
		else if(unit->dev->ifc->wctl)
			unit->dev->ifc->wctl(unit, cb);
		else
			error(Ebadctl);
		qunlock(&unit->ctl);
		poperror();
		free(cb);
		break;

	case Qraw:
		unit = sdunit[UNIT(c->qid)];
		qlock(&unit->raw);
		if(waserror()){
			qunlock(&unit->raw);
			nexterror();
		}
		switch(unit->state){
		case Rawcmd:
			if(n < 6 || n > sizeof(req->cmd))
				error(Ebadarg);
			if((req = malloc(sizeof(SDreq))) == nil)
				error(Enomem);
			req->unit = unit;
			memmove(req->cmd, a, n);
			req->clen = n;
			req->flags = SDnosense;
			req->status = ~0;

			unit->req = req;
			unit->state = Rawdata;
			break;

		case Rawstatus:
			unit->state = Rawcmd;
			free(unit->req);
			unit->req = nil;
			error(Ebadusefd);

		case Rawdata:
			if(unit->state != Rawdata)
				error(Ebadusefd);
			unit->state = Rawstatus;

			unit->req->write = 1;
			n = sdrio(unit->req, a, n);
		}
		qunlock(&unit->raw);
		poperror();
		return n;

	case Qpart:
		return sdbio(c, 1, a, n, off);
	}

	return n;
}

static void
sdwstat(Chan* c, char* dp)
{
	Dir d;
	SDpart *pp;
	SDperm *perm;
	SDunit *unit;

	if(c->qid.path & CHDIR)
		error(Eperm);

	unit = sdunit[UNIT(c->qid)];
	qlock(&unit->ctl);
	if(waserror()){
		qunlock(&unit->ctl);
		nexterror();
	}

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

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

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



Dev sddevtab = {
	'S',
	"sd",

	sdreset,
	devinit,
	sdattach,
	sdclone,
	sdwalk,
	sdstat,
	sdopen,
	devcreate,
	sdclose,
	sdread,
	devbread,
	sdwrite,
	devbwrite,
	devremove,
	sdwstat,
	nil, /* power management */
	sdconfig,
};

--upas-cefrnfuwhwfqcaxqtnsykuzaqu
Content-Disposition: attachment; filename=sd.h
Content-Type: text/plain; charset="US-ASCII"
Content-Transfer-Encoding: 7bit

/*
 * Storage Device.
 */
typedef struct SDev SDev;
typedef struct SDifc SDifc;
typedef struct SDpart SDpart;
typedef struct SDperm SDperm;
typedef struct SDreq SDreq;
typedef struct SDunit SDunit;

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

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

typedef struct SDunit {
	SDev* dev;
	int subno;
	uchar inquiry[256]; /* format follows SCSI spec */
	SDperm;
	Rendez rendez;

	QLock ctl;
	ulong sectors;
	ulong secsize;
	SDpart* part; /* nil or array of size npart */
	int npart;
	ulong vers;
	SDperm ctlperm;

	QLock raw; /* raw read or write in progress */
	Lock rawinuse; /* really just a test-and-set */
	int state;
	SDreq* req;
	SDperm rawperm;

	Log log;
} SDunit;

typedef struct SDev {
	SDifc* ifc; /* pnp/legacy */
	void *ctlr;
	int idno;
	char name[NAMELEN];
	int index; /* into unit space */
	int nunit;
	SDev* next;

	QLock; /* enable/disable */
	int enabled;
} SDev;

typedef struct SDifc {
	char* name;

	SDev* (*pnp)(void);
	SDev* (*legacy)(int, int);
	SDev* (*id)(SDev*);
	int (*enable)(SDev*);
	int (*disable)(SDev*);

	int (*verify)(SDunit*);
	int (*online)(SDunit*);
	int (*rio)(SDreq*);
	int (*rctl)(SDunit*, char*, int);
	int (*wctl)(SDunit*, Cmdbuf*);

	long (*bio)(SDunit*, int, int, void*, long, long);
	SDev* (*config)(int on, char *spec, void *cf);
} SDifc;

typedef struct SDreq {
	SDunit* unit;
	int lun;
	int write;
	uchar cmd[16];
	int clen;
	void* data;
	int dlen;

	int flags;

	int status;
	long rlen;
	uchar sense[256];
} SDreq;

enum {
	SDnosense = 0x00000001,
	SDvalidsense = 0x00010000,
};

enum {
	SDretry = -5, /* internal to controllers */
	SDmalloc = -4,
	SDeio = -3,
	SDtimeout = -2,
	SDnostatus = -1,

	SDok = 0,

	SDcheck = 0x02, /* check condition */
	SDbusy = 0x08, /* busy */

	SDmaxio = 2048*1024,
	SDnpart = 16,
};

#define sdmalloc(n) malloc(n)
#define sdfree(p) free(p)

/* sdscsi.c */
extern int scsiverify(SDunit*);
extern int scsionline(SDunit*);
extern long scsibio(SDunit*, int, int, void*, long, long);
extern SDev* scsiid(SDev*, SDifc*);
--upas-cefrnfuwhwfqcaxqtnsykuzaqu--