diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 21:39:35 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 21:39:35 +0000 |
| commit | 74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a (patch) | |
| tree | c6e220ba61db3a6ea4052e6841296d829654e664 /os/mpc/devata.c | |
| parent | 46439007cf417cbd9ac8049bb4122c890097a0fa (diff) | |
20060303
Diffstat (limited to 'os/mpc/devata.c')
| -rw-r--r-- | os/mpc/devata.c | 1194 |
1 files changed, 1194 insertions, 0 deletions
diff --git a/os/mpc/devata.c b/os/mpc/devata.c new file mode 100644 index 00000000..69665e38 --- /dev/null +++ b/os/mpc/devata.c @@ -0,0 +1,1194 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" + +#include "pcmcia.h" + +#define DPRINT if(0)print + +typedef struct Drive Drive; +typedef struct Ident Ident; +typedef struct Controller Controller; +typedef struct Partition Partition; +typedef struct Repl Repl; + +enum +{ + /* ports */ + Pbase= 0x1F0, + Pdata= 0, /* data port (16 bits) */ + Perror= 1, /* error port (read) */ + Pprecomp= 1, /* buffer mode port (write) */ + Pcount= 2, /* sector count port */ + Psector= 3, /* sector number port */ + Pcyllsb= 4, /* least significant byte cylinder # */ + Pcylmsb= 5, /* most significant byte cylinder # */ + Pdh= 6, /* drive/head port */ + Pstatus= 7, /* status port (read) */ + Sbusy= (1<<7), + Sready= (1<<6), + Sdrq= (1<<3), + Serr= (1<<0), + Pcmd= 7, /* cmd port (write) */ + + /* commands */ + Crecal= 0x10, + Cread= 0x20, + Cwrite= 0x30, + Cident= 0xEC, + Cident2= 0xFF, /* pseudo command for post Cident interrupt */ + Csetbuf= 0xEF, + Cinitparam= 0x91, + + /* conner specific commands */ + Cstandby= 0xE2, + Cidle= 0xE1, + Cpowerdown= 0xE3, + + /* disk states */ + Sspinning, + Sstandby, + Sidle, + Spowerdown, + + /* something we have to or into the drive/head reg */ + DHmagic= 0xA0, + + /* file types */ + Qdir= 0, + + Maxxfer= BY2PG, /* maximum transfer size/cmd */ + Npart= 8+2, /* 8 sub partitions, disk, and partition */ + Nrepl= 64, /* maximum replacement blocks */ +}; +#define PART(x) ((x)&0xF) +#define DRIVE(x) (((x)>>4)&0x7) +#define MKQID(d,p) (((d)<<4) | (p)) + +struct Partition +{ + ulong start; + ulong end; + char name[NAMELEN+1]; +}; + +struct Repl +{ + Partition *p; + int nrepl; + ulong blk[Nrepl]; +}; + +#define PARTMAGIC "plan9 partitions" +#define REPLMAGIC "block replacements" + +/* + * an ata drive + */ +struct Drive +{ + QLock; + + Controller *cp; + int drive; + int confused; /* needs to be recalibrated (or worse) */ + int online; + int npart; /* number of real partitions */ + Partition p[Npart]; + Repl repl; + ulong usetime; + int state; + char vol[NAMELEN]; + + ulong cap; /* total bytes */ + int bytes; /* bytes/sector */ + int sectors; /* sectors/track */ + int heads; /* heads/cyl */ + long cyl; /* cylinders/drive */ + + char lba; /* true if drive has logical block addressing */ + char multi; /* non-zero if drive does multiple block xfers */ +}; + +/* + * a controller for 2 drives + */ +struct Controller +{ + QLock; /* exclusive access to the controller */ + ISAConf; /* interface to pcmspecial */ + + Lock reglock; /* exclusive access to the registers */ + + int confused; /* needs to be recalibrated (or worse) */ + ulong pbase; /* base port (copied from ISAConf) */ + + /* + * current operation + */ + int cmd; /* current command */ + int lastcmd; /* debugging info */ + Rendez r; /* wait here for command termination */ + char *buf; /* xfer buffer */ + int nsecs; /* length of transfer (sectors) */ + int sofar; /* sectors transferred so far */ + int status; + int error; + Drive *dp; /* drive being accessed */ +}; + +Controller *atac; +Drive *ata; +static char* ataerr; +static int nhard; +static int spindowntime; + +static void ataintr(Ureg*, void*); +static long ataxfer(Drive*, Partition*, int, long, long, char*); +static void ataident(Drive*); +static void atasetbuf(Drive*, int); +static void ataparams(Drive*); +static void atapart(Drive*); +static int ataprobe(Drive*, int, int, int); + +static int +atagen(Chan *c, Dirtab*, int, int s, Dir *dirp) +{ + Qid qid; + int drive; + Drive *dp; + Partition *pp; + ulong l; + + qid.vers = 0; + drive = s/Npart; + s = s % Npart; + if(drive >= nhard) + return -1; + dp = &ata[drive]; + + if(dp->online == 0 || s >= dp->npart) + return 0; + + pp = &dp->p[s]; + sprint(up->genbuf, "%s%s", dp->vol, pp->name); + qid.path = MKQID(drive, s); + l = (pp->end - pp->start) * dp->bytes; + devdir(c, qid, up->genbuf, l, eve, 0660, dirp); + return 1; +} + +static void +atainit(void) +{ + Drive *dp; + Controller *cp; + uchar equip; + int pcmslot; + + if (atac) + return; /* already done */ + + equip = 0x10; /* hard coded */ + + print("ata init\n"); + cp = malloc(sizeof(*cp)); + if (!cp) + error(Enomem); + + cp->port = Pbase; + cp->irq = 14; + + if((pcmslot = pcmspecial("SunDisk", cp)) < 0) { + print("No ATA card\n"); + free(cp); + ataerr = Enoifc; + return; + } + ata = malloc(2 * sizeof(*ata)); + if(ata == nil) { + pcmspecialclose(pcmslot); + free(cp); + error(Enomem); + } + + atac = cp; + cp->buf = 0; + cp->lastcmd = cp->cmd; + cp->cmd = 0; + cp->pbase = cp->port; + pcmintrenable(pcmslot, ataintr, cp); + + dp = ata; + if(equip & 0xf0){ + dp->drive = 0; + dp->online = 0; + dp->cp = cp; + dp++; + } + if((equip & 0x0f)){ + dp->drive = 1; + dp->online = 0; + dp->cp = cp; + dp++; + } + nhard = dp - ata; + + spindowntime = 1; +} + + +/* + * Get the characteristics of each drive. Mark unresponsive ones + * off line. + */ +static Chan* +ataattach(char *spec) +{ + Drive *dp; + + atainit(); + if (!ata) + error(ataerr ? ataerr : Enoifc); + for(dp = ata; dp < &ata[nhard]; dp++){ + if(waserror()){ + dp->online = 0; + qunlock(dp); + continue; + } + qlock(dp); + if(!dp->online){ + /* + * Make sure ataclock() doesn't + * interfere. + */ + dp->usetime = m->ticks; + ataparams(dp); + dp->online = 1; + atasetbuf(dp, 1); + } + + /* + * read Plan 9 partition table + */ + atapart(dp); + qunlock(dp); + poperror(); + } + return devattach('H', spec); +} + +static int +atawalk(Chan *c, char *name) +{ + return devwalk(c, name, 0, 0, atagen); +} + +static void +atastat(Chan *c, char *dp) +{ + devstat(c, dp, 0, 0, atagen); +} + +static Chan* +ataopen(Chan *c, int omode) +{ + return devopen(c, omode, 0, 0, atagen); +} + +static void +ataclose(Chan *c) +{ + Drive *d; + Partition *p; + + if(c->mode != OWRITE && c->mode != ORDWR) + return; + + d = &ata[DRIVE(c->qid.path)]; + p = &d->p[PART(c->qid.path)]; + if(strcmp(p->name, "partition") != 0) + return; + + if(waserror()){ + qunlock(d); + nexterror(); + } + qlock(d); + atapart(d); + qunlock(d); + poperror(); +} + +static long +ataread(Chan *c, void *a, long n, vlong offset) +{ + Drive *dp; + long rv, i; + int skip; + uchar *aa = a; + Partition *pp; + char *buf; + + if(c->qid.path == CHDIR) + return devdirread(c, a, n, 0, 0, atagen); + + buf = smalloc(Maxxfer); + if(waserror()){ + free(buf); + nexterror(); + } + + dp = &ata[DRIVE(c->qid.path)]; + pp = &dp->p[PART(c->qid.path)]; + + skip = offset % dp->bytes; + for(rv = 0; rv < n; rv += i){ + i = ataxfer(dp, pp, Cread, offset+rv-skip, n-rv+skip, buf); + if(i == 0) + break; + i -= skip; + if(i > n - rv) + i = n - rv; + memmove(aa+rv, buf + skip, i); + skip = 0; + } + + free(buf); + poperror(); + + return rv; +} + +static long +atawrite(Chan *c, void *a, long n, vlong offset) +{ + Drive *dp; + long rv, i, partial; + uchar *aa = a; + Partition *pp; + char *buf; + + if(c->qid.path == CHDIR) + error(Eisdir); + + dp = &ata[DRIVE(c->qid.path)]; + pp = &dp->p[PART(c->qid.path)]; + buf = smalloc(Maxxfer); + if(waserror()){ + free(buf); + nexterror(); + } + + /* + * if not starting on a sector boundary, + * read in the first sector before writing + * it out. + */ + partial = offset % dp->bytes; + if(partial){ + ataxfer(dp, pp, Cread, offset-partial, dp->bytes, buf); + if(partial+n > dp->bytes) + rv = dp->bytes - partial; + else + rv = n; + memmove(buf+partial, aa, rv); + ataxfer(dp, pp, Cwrite, offset-partial, dp->bytes, buf); + } else + rv = 0; + + /* + * write out the full sectors + */ + partial = (n - rv) % dp->bytes; + n -= partial; + for(; rv < n; rv += i){ + i = n - rv; + if(i > Maxxfer) + i = Maxxfer; + memmove(buf, aa+rv, i); + i = ataxfer(dp, pp, Cwrite, offset+rv, i, buf); + if(i == 0) + break; + } + + /* + * if not ending on a sector boundary, + * read in the last sector before writing + * it out. + */ + if(partial){ + ataxfer(dp, pp, Cread, offset+rv, dp->bytes, buf); + memmove(buf, aa+rv, partial); + ataxfer(dp, pp, Cwrite, offset+rv, dp->bytes, buf); + rv += partial; + } + + free(buf); + poperror(); + + return rv; +} + +/* + * did an interrupt happen? + */ +static int +cmddone(void *a) +{ + Controller *cp = a; + + return cp->cmd == 0; +} + +/* + * Wait for the controller to be ready to accept a command. + * This is protected from intereference by ataclock() by + * setting dp->usetime before it is called. + */ +static void +cmdreadywait(Drive *dp) +{ + long start; + int period; + Controller *cp = dp->cp; + + /* give it 2 seconds to spin down and up */ + if(dp->state == Sspinning) + period = 10; + else + period = 2000; + + start = m->ticks; + while((inb(cp->pbase+Pstatus) & (Sready|Sbusy)) != Sready) + if(TK2MS(m->ticks - start) > period){ + DPRINT("cmdreadywait failed\n"); + error(Eio); + } +} + +static void +atarepl(Drive *dp, long bblk) +{ + int i; + + if(dp->repl.p == 0) + return; + for(i = 0; i < dp->repl.nrepl; i++){ + if(dp->repl.blk[i] == bblk) + DPRINT("found bblk %ld at offset %ld\n", bblk, i); + } +} + +static void +atasleep(Controller *cp, int ms) +{ + tsleep(&cp->r, cmddone, cp, ms); + if(cp->cmd && cp->cmd != Cident2){ + DPRINT("ata: cmd 0x%uX timeout, status=%lux\n", + cp->cmd, inb(cp->pbase+Pstatus)); + error("ata drive timeout"); + } +} + +/* + * transfer a number of sectors. ataintr will perform all the iterative + * parts. + */ +static long +ataxfer(Drive *dp, Partition *pp, int cmd, long start, long len, char *buf) +{ + Controller *cp; + long lblk; + int cyl, sec, head; + int loop, stat; + + if(dp->online == 0) + error(Eio); + + /* + * cut transfer size down to disk buffer size + */ + start = start / dp->bytes; + if(len > Maxxfer) + len = Maxxfer; + len = (len + dp->bytes - 1) / dp->bytes; + if(len == 0) + return 0; + + /* + * calculate physical address + */ + lblk = start + pp->start; + if(lblk >= pp->end) + return 0; + if(lblk+len > pp->end) + len = pp->end - lblk; + if(dp->lba){ + sec = lblk & 0xff; + cyl = (lblk>>8) & 0xffff; + head = (lblk>>24) & 0xf; + } else { + cyl = lblk/(dp->sectors*dp->heads); + sec = (lblk % dp->sectors) + 1; + head = ((lblk/dp->sectors) % dp->heads); + } + + DPRINT("<%s %d>", (cmd == Cwrite) ? "W" : "R", lblk); + cp = dp->cp; + qlock(cp); + if(waserror()){ + cp->buf = 0; + qunlock(cp); + nexterror(); + } + + /* + * Make sure hardclock() doesn't + * interfere. + */ + dp->usetime = m->ticks; + cmdreadywait(dp); + + ilock(&cp->reglock); + cp->sofar = 0; + cp->buf = buf; + cp->nsecs = len; + cp->cmd = cmd; + cp->dp = dp; + cp->status = 0; + + outb(cp->pbase+Pcount, cp->nsecs); + outb(cp->pbase+Psector, sec); + outb(cp->pbase+Pdh, DHmagic | (dp->drive<<4) | (dp->lba<<6) | head); + outb(cp->pbase+Pcyllsb, cyl); + outb(cp->pbase+Pcylmsb, cyl>>8); + outb(cp->pbase+Pcmd, cmd); + + if(cmd == Cwrite){ + loop = 0; + microdelay(1); + while((stat = inb(cp->pbase+Pstatus) & (Serr|Sdrq)) == 0) + if(++loop > 10000) + panic("ataxfer"); + outss(cp->pbase+Pdata, cp->buf, dp->bytes/2); + } else + stat = 0; + iunlock(&cp->reglock); + + if(stat & Serr) + error(Eio); + + /* + * wait for command to complete. if we get a note, + * remember it but keep waiting to let the disk finish + * the current command. + */ + loop = 0; + while(waserror()){ + DPRINT("interrupted ataxfer\n"); + if(loop++ > 10){ + print("ata disk error\n"); + nexterror(); + } + } + atasleep(cp, 3000); + dp->state = Sspinning; + dp->usetime = m->ticks; + poperror(); + if(loop) + nexterror(); + + if(cp->status & Serr){ + DPRINT("hd%d err: lblk %ld status %lux, err %lux\n", + dp-ata, lblk, cp->status, cp->error); + DPRINT("\tcyl %d, sec %d, head %d\n", cyl, sec, head); + DPRINT("\tnsecs %d, sofar %d\n", cp->nsecs, cp->sofar); + atarepl(dp, lblk+cp->sofar); + error(Eio); + } + cp->buf = 0; + len = cp->sofar*dp->bytes; + qunlock(cp); + poperror(); + + return len; +} + +/* + * set read ahead mode + */ +static void +atasetbuf(Drive *dp, int on) +{ + Controller *cp = dp->cp; + + qlock(cp); + if(waserror()){ + qunlock(cp); + nexterror(); + } + + cmdreadywait(dp); + + ilock(&cp->reglock); + cp->cmd = Csetbuf; + outb(cp->pbase+Pprecomp, on ? 0xAA : 0x55); /* read look ahead */ + outb(cp->pbase+Pdh, DHmagic | (dp->drive<<4)); + outb(cp->pbase+Pcmd, Csetbuf); + iunlock(&cp->reglock); + + atasleep(cp, 5000); + +/* if(cp->status & Serr) + DPRINT("hd%d setbuf err: status %lux, err %lux\n", + dp-ata, cp->status, cp->error);/**/ + + poperror(); + qunlock(cp); +} + +/* + * ident sector from drive. this is from ANSI X3.221-1994 + */ +struct Ident +{ + ushort config; /* general configuration info */ + ushort cyls; /* # of cylinders (default) */ + ushort reserved0; + ushort heads; /* # of heads (default) */ + ushort b2t; /* unformatted bytes/track */ + ushort b2s; /* unformated bytes/sector */ + ushort s2t; /* sectors/track (default) */ + ushort reserved1[3]; +/* 10 */ + ushort serial[10]; /* serial number */ + ushort type; /* buffer type */ + ushort bsize; /* buffer size/512 */ + ushort ecc; /* ecc bytes returned by read long */ + ushort firm[4]; /* firmware revision */ + ushort model[20]; /* model number */ +/* 47 */ + ushort s2i; /* number of sectors/interrupt */ + ushort dwtf; /* double word transfer flag */ + ushort capabilities; + ushort reserved2; + ushort piomode; + ushort dmamode; + ushort cvalid; /* (cvald&1) if next 4 words are valid */ + ushort ccyls; /* current # cylinders */ + ushort cheads; /* current # heads */ + ushort cs2t; /* current sectors/track */ + ushort ccap[2]; /* current capacity in sectors */ + ushort cs2i; /* current number of sectors/interrupt */ +/* 60 */ + ushort lbasecs[2]; /* # LBA user addressable sectors */ + ushort dmasingle; + ushort dmadouble; +/* 64 */ + ushort reserved3[64]; + ushort vendor[32]; /* vendor specific */ + ushort reserved4[96]; +}; + +/* + * get parameters from the drive + */ +static void +ataident(Drive *dp) +{ + Controller *cp; + char *buf; + Ident *ip; + char id[21]; + + cp = dp->cp; + buf = smalloc(Maxxfer); + qlock(cp); + if(waserror()){ + cp->buf = 0; + qunlock(cp); + free(buf); + nexterror(); + } + + cmdreadywait(dp); + + ilock(&cp->reglock); + cp->nsecs = 1; + cp->sofar = 0; + cp->cmd = Cident; + cp->dp = dp; + cp->buf = buf; + outb(cp->pbase+Pdh, DHmagic | (dp->drive<<4)); + outb(cp->pbase+Pcmd, Cident); + iunlock(&cp->reglock); + + atasleep(cp, 5000); + if(cp->status & Serr){ + DPRINT("bad disk ident status\n"); + error(Eio); + } + ip = (Ident*)buf; + + /* + * this function appears to respond with an extra interrupt after + * the ident information is read, except on the safari. The following + * delay gives this extra interrupt a chance to happen while we are quiet. + * Otherwise, the interrupt may come during a subsequent read or write, + * causing a panic and much confusion. + */ + if (cp->cmd == Cident2) + tsleep(&cp->r, return0, 0, 10); + + memmove(id, ip->model, sizeof(id)-1); + id[sizeof(id)-1] = 0; + + if(ip->capabilities & (1<<9)){ + dp->lba = 1; + dp->sectors = (ip->lbasecs[0]) | (ip->lbasecs[1]<<16); + dp->cap = dp->bytes * dp->sectors; +/*print("\nata%d model %s with %d lba sectors\n", dp->drive, id, dp->sectors);/**/ + } else { + dp->lba = 0; + + /* use default (unformatted) settings */ + dp->cyl = ip->cyls; + dp->heads = ip->heads; + dp->sectors = ip->s2t; +/*print("\nata%d model %s with default %d cyl %d head %d sec\n", dp->drive, + id, dp->cyl, dp->heads, dp->sectors);/**/ + + if(ip->cvalid&(1<<0)){ + /* use current settings */ + dp->cyl = ip->ccyls; + dp->heads = ip->cheads; + dp->sectors = ip->cs2t; +/*print("\tchanged to %d cyl %d head %d sec\n", dp->cyl, dp->heads, dp->sectors);/**/ + } + dp->cap = dp->bytes * dp->cyl * dp->heads * dp->sectors; + } + cp->lastcmd = cp->cmd; + cp->cmd = 0; + cp->buf = 0; + free(buf); + poperror(); + qunlock(cp); +} + +/* + * probe the given sector to see if it exists + */ +static int +ataprobe(Drive *dp, int cyl, int sec, int head) +{ + Controller *cp; + char *buf; + int rv; + + cp = dp->cp; + buf = smalloc(Maxxfer); + qlock(cp); + if(waserror()){ + free(buf); + qunlock(cp); + nexterror(); + } + + cmdreadywait(dp); + + ilock(&cp->reglock); + cp->cmd = Cread; + cp->dp = dp; + cp->status = 0; + cp->nsecs = 1; + cp->sofar = 0; + + outb(cp->pbase+Pcount, 1); + outb(cp->pbase+Psector, sec+1); + outb(cp->pbase+Pdh, DHmagic | head | (dp->lba<<6) | (dp->drive<<4)); + outb(cp->pbase+Pcyllsb, cyl); + outb(cp->pbase+Pcylmsb, cyl>>8); + outb(cp->pbase+Pcmd, Cread); + iunlock(&cp->reglock); + + atasleep(cp, 5000); + + if(cp->status & Serr) + rv = -1; + else + rv = 0; + + cp->buf = 0; + free(buf); + poperror(); + qunlock(cp); + return rv; +} + +/* + * figure out the drive parameters + */ +static void +ataparams(Drive *dp) +{ + int i, hi, lo; + + /* + * first try the easy way, ask the drive and make sure it + * isn't lying. + */ + dp->bytes = 512; + ataident(dp); + if(dp->lba){ + i = dp->sectors - 1; + if(ataprobe(dp, (i>>8)&0xffff, (i&0xff)-1, (i>>24)&0xf) == 0) + return; + } else { + if(ataprobe(dp, dp->cyl-1, dp->sectors-1, dp->heads-1) == 0) + return; + } + + /* + * the drive lied, determine parameters by seeing which ones + * work to read sectors. + */ + dp->lba = 0; + for(i = 0; i < 32; i++) + if(ataprobe(dp, 0, 0, i) < 0) + break; + dp->heads = i; + for(i = 0; i < 128; i++) + if(ataprobe(dp, 0, i, 0) < 0) + break; + dp->sectors = i; + for(i = 512; ; i += 512) + if(ataprobe(dp, i, dp->sectors-1, dp->heads-1) < 0) + break; + lo = i - 512; + hi = i; + for(; hi-lo > 1;){ + i = lo + (hi - lo)/2; + if(ataprobe(dp, i, dp->sectors-1, dp->heads-1) < 0) + hi = i; + else + lo = i; + } + dp->cyl = lo + 1; + dp->cap = dp->bytes * dp->cyl * dp->heads * dp->sectors; +} + +/* + * Read block replacement table. + * The table is just ascii block numbers. + */ +static void +atareplinit(Drive *dp) +{ + char *line[Nrepl+1]; + char *field[1]; + ulong n; + int i; + char *buf; + + /* + * check the partition is big enough + */ + if(dp->repl.p->end - dp->repl.p->start < Nrepl+1){ + dp->repl.p = 0; + return; + } + + buf = smalloc(Maxxfer); + if(waserror()){ + free(buf); + nexterror(); + } + + /* + * read replacement table from disk, null terminate + */ + ataxfer(dp, dp->repl.p, Cread, 0, dp->bytes, buf); + buf[dp->bytes-1] = 0; + + /* + * parse replacement table. + */ + n = getfields(buf, line, Nrepl+1, 1, "\n"); + if(strncmp(line[0], REPLMAGIC, sizeof(REPLMAGIC)-1)){ + dp->repl.p = 0; + } else { + for(dp->repl.nrepl = 0, i = 1; i < n; i++, dp->repl.nrepl++){ + if(getfields(line[i], field, 1, 1, " ") != 1) + break; + dp->repl.blk[dp->repl.nrepl] = strtoul(field[0], 0, 0); + if(dp->repl.blk[dp->repl.nrepl] <= 0) + break; + } + } + free(buf); + poperror(); +} + +/* + * read partition table. The partition table is just ascii strings. + */ +static void +atapart(Drive *dp) +{ + Partition *pp; + char *line[Npart+1]; + char *field[3]; + ulong n; + int i; + char *buf; + + sprint(dp->vol, "hd%d", dp - ata); + + /* + * we always have a partition for the whole disk + * and one for the partition table + */ + pp = &dp->p[0]; + strcpy(pp->name, "disk"); + pp->start = 0; + pp->end = dp->cap / dp->bytes; + pp++; + strcpy(pp->name, "partition"); + pp->start = dp->p[0].end - 1; + pp->end = dp->p[0].end; + pp++; + dp->npart = 2; + + /* + * initialise the bad-block replacement info + */ + dp->repl.p = 0; + + buf = smalloc(Maxxfer); + if(waserror()){ + free(buf); + nexterror(); + } + + /* + * read last sector from disk, null terminate. This used + * to be the sector we used for the partition tables. + * However, this sector is special on some PC's so we've + * started to use the second last sector as the partition + * table instead. To avoid reconfiguring all our old systems + * we first look to see if there is a valid partition + * table in the last sector. If so, we use it. Otherwise + * we switch to the second last. + */ + ataxfer(dp, dp->p+1, Cread, 0, dp->bytes, buf); + buf[dp->bytes-1] = 0; + n = getfields(buf, line, Npart+1, 1, "\n"); + if(n == 0 || strncmp(line[0], PARTMAGIC, sizeof(PARTMAGIC)-1)){ + dp->p[0].end--; + dp->p[1].start--; + dp->p[1].end--; + ataxfer(dp, dp->p+1, Cread, 0, dp->bytes, buf); + buf[dp->bytes-1] = 0; + n = getfields(buf, line, Npart+1, 1, "\n"); + } + + /* + * parse partition table. + */ + if(n > 0 && strncmp(line[0], PARTMAGIC, sizeof(PARTMAGIC)-1) == 0){ + for(i = 1; i < n; i++){ + switch(getfields(line[i], field, 3, 1, " ")) { + case 2: + if(strcmp(field[0], "unit") == 0) + strncpy(dp->vol, field[1], NAMELEN); + break; + case 3: + strncpy(pp->name, field[0], NAMELEN); + if(strncmp(pp->name, "repl", NAMELEN) == 0) + dp->repl.p = pp; + pp->start = strtoul(field[1], 0, 0); + pp->end = strtoul(field[2], 0, 0); + if(pp->start > pp->end || pp->end > dp->p[0].end) + break; + dp->npart++; + pp++; + } + } + } + free(buf); + poperror(); + + if(dp->repl.p) + atareplinit(dp); +} + +enum +{ + Maxloop= 10000, +}; + +/* + * we get an interrupt for every sector transferred + */ +static void +ataintr(Ureg*, void *arg) +{ + Controller *cp; + Drive *dp; + long loop; + char *addr; + + cp = arg; + dp = cp->dp; + + ilock(&cp->reglock); + + loop = 0; + while((cp->status = inb(cp->pbase+Pstatus)) & Sbusy){ + if(++loop > Maxloop) { + DPRINT("cmd=%lux status=%lux\n", + cp->cmd, inb(cp->pbase+Pstatus)); + panic("ataintr: wait busy"); + } + } + + switch(cp->cmd){ + case Cwrite: + if(cp->status & Serr){ + cp->lastcmd = cp->cmd; + cp->cmd = 0; + cp->error = inb(cp->pbase+Perror); + wakeup(&cp->r); + break; + } + cp->sofar++; + if(cp->sofar < cp->nsecs){ + loop = 0; + while(((cp->status = inb(cp->pbase+Pstatus)) & Sdrq) == 0) + if(++loop > Maxloop) { + DPRINT("cmd=%lux status=%lux\n", + cp->cmd, inb(cp->pbase+Pstatus)); + panic("ataintr: write"); + } + addr = cp->buf; + if(addr){ + addr += cp->sofar*dp->bytes; + outss(cp->pbase+Pdata, addr, dp->bytes/2); + } + } else{ + cp->lastcmd = cp->cmd; + cp->cmd = 0; + wakeup(&cp->r); + } + break; + case Cread: + case Cident: + loop = 0; + while((cp->status & (Serr|Sdrq)) == 0){ + if(++loop > Maxloop) { + DPRINT("cmd=%lux status=%lux\n", + cp->cmd, inb(cp->pbase+Pstatus)); + panic("ataintr: read/ident"); + } + cp->status = inb(cp->pbase+Pstatus); + } + if(cp->status & Serr){ + cp->lastcmd = cp->cmd; + cp->cmd = 0; + cp->error = inb(cp->pbase+Perror); + wakeup(&cp->r); + break; + } + addr = cp->buf; + if(addr){ + addr += cp->sofar*dp->bytes; + inss(cp->pbase+Pdata, addr, dp->bytes/2); + } + cp->sofar++; + if(cp->sofar > cp->nsecs) + print("ataintr %d %d\n", cp->sofar, cp->nsecs); + if(cp->sofar >= cp->nsecs){ + cp->lastcmd = cp->cmd; + if (cp->cmd == Cread) + cp->cmd = 0; + else + cp->cmd = Cident2; + wakeup(&cp->r); + } + break; + case Cinitparam: + case Csetbuf: + case Cidle: + case Cstandby: + case Cpowerdown: + cp->lastcmd = cp->cmd; + cp->cmd = 0; + wakeup(&cp->r); + break; + case Cident2: + cp->lastcmd = cp->cmd; + cp->cmd = 0; + break; + default: + print("weird disk interrupt, cmd=%.2ux, lastcmd= %.2ux status=%.2ux\n", + cp->cmd, cp->lastcmd, cp->status); + break; + } + + iunlock(&cp->reglock); +} + +void +hardclock(void) +{ + int drive; + Drive *dp; + Controller *cp; + int diff; + + if(spindowntime <= 0) + return; + + for(drive = 0; drive < nhard; drive++){ + dp = &ata[drive]; + cp = dp->cp; + + diff = TK2SEC(m->ticks - dp->usetime); + if((dp->state == Sspinning) && (diff >= spindowntime)){ + ilock(&cp->reglock); + cp->cmd = Cstandby; + outb(cp->pbase+Pcount, 0); + outb(cp->pbase+Pdh, DHmagic | (dp->drive<<4) | 0); + outb(cp->pbase+Pcmd, cp->cmd); + iunlock(&cp->reglock); + dp->state = Sstandby; + } + } +} + +Dev atadevtab = { + 'H', + "ata", + + devreset, + atainit, + ataattach, + devdetach, + devclone, + atawalk, + atastat, + ataopen, + devcreate, + ataclose, + ataread, + devbread, + atawrite, + devbwrite, + devremove, + devwstat, +}; |
