/* $Id: aha1542.c,v 1.1 1992/04/24 18:01:50 root Exp root $ * linux/kernel/aha1542.c * * (C) 1992 Tommy Thorn */ #include <linux/config.h> #include <linux/kernel.h> #include <linux/head.h> #include <linux/string.h> #include <asm/system.h> #include <asm/io.h> #include <sys/types.h> #include "scsi.h" #include "hosts.h" #include "aha1542.h" #ifdef DEBUG #define DEB(x) x #else #define DEB(x) #endif /* static const char RCSid[] = "$Header: /usr/src/linux/kernel/blk_drv/scsi/RCS/aha1542.c,v 1.1 1992/04/24 18:01:50 root Exp root $"; */ #define base 0x330 #define intr_chan 11 static struct mailbox mb[2]; static struct ccb ccb; long WAITtimeout, WAITnexttimeout = 3000000; void (*do_done)() = NULL; extern void aha1542_interrupt(); #define aha1542_intr_reset() outb(IRST, CONTROL) #define aha1542_enable_intr() outb(inb_p(0xA1) & ~8, 0xA1) #define aha1542_disable_intr() outb(inb_p(0xA1) | 8, 0xA1) #define WAIT(port, mask, allof, noneof) \ { register WAITbits; \ register WAITtimeout = WAITnexttimeout; \ while (1) { \ WAITbits = inb(port) & (mask); \ if ((WAITbits & (allof)) == (allof) && ((WAITbits & (noneof)) == 0)) \ break; \ if (--WAITtimeout == 0) goto fail; \ } \ } static void aha1542_stat(void) { int s = inb(STATUS), i = inb(INTRFLAGS); /* printk("status = %x, intrflags = %x served %d last %x timeout %d\n", s, i, intr_flag, intr_last, WAITtimeout); */ printk("status=%x intrflags=%x\n", s, i, WAITnexttimeout-WAITtimeout); } static int aha1542_out(unchar *cmdp, int len) { while (len--) { WAIT(STATUS, CDF, 0, CDF); outb(*cmdp++, DATA); } return 0; fail: printk("aha1542_out failed(%d): ", len+1); aha1542_stat(); return 1; } int makecode(unsigned hosterr, unsigned scsierr) { switch (hosterr) { case 0x0: case 0xa: /* Linked command complete without error and linked normally */ case 0xb: /* Linked command complete without error, interrupt generated */ hosterr = 0; break; case 0x11: /* Selection time out-The initiator selection or target reselection was not complete within the SCSI Time out period */ hosterr = DID_TIME_OUT; break; case 0x12: /* Data overrun/underrun-The target attempted to transfer more data thean was allocated by the Data Length field or the sum of the Scatter / Gather Data Length fields. */ case 0x13: /* Unexpected bus free-The target dropped the SCSI BSY at an unexpected time. */ case 0x15: /* MBO command was not 00, 01 or 02-The first byte of the CB was invalid. This usually indicates a software failure. */ case 0x16: /* Invalid CCB Operation Code-The first byte of the CCB was invalid. This usually indicates a software failure. */ case 0x17: /* Linked CCB does not have the same LUN-A subsequent CCB of a set of linked CCB's does not specify the same logical unit number as the first. */ case 0x18: /* Invalid Target Direction received from Host-The direction of a Target Mode CCB was invalid. */ case 0x19: /* Duplicate CCB Received in Target Mode-More than once CCB was received to service data transfer between the same target LUN and initiator SCSI ID in the same direction. */ case 0x1a: /* Invalid CCB or Segment List Parameter-A segment list with a zero length segment or invalid segment list boundaries was received. A CCB parameter was invalid. */ hosterr = DID_ERROR; /* Couldn't find any better */ break; case 0x14: /* Target bus phase sequence failure-An invalid bus phase or bus phase sequence was requested by the target. The host adapter will generate a SCSI Reset Condition, notifying the host with a SCRD interrupt */ hosterr = DID_RESET; break; default: printk("makecode: unknown hoststatus %x\n", hosterr); break; } return scsierr|(hosterr << 16); } int aha1542_test_port(void) { volatile int debug = 0; /* Reset the adapter. I ought to make a hard reset, but it's not really nessesary */ /* DEB(printk("aha1542_test_port called \n")); */ outb(SRST|IRST/*|SCRST*/, CONTROL); debug = 1; /* Expect INIT and IDLE, any of the others are bad */ WAIT(STATUS, STATMASK, INIT|IDLE, STST|DIAGF|INVDCMD|DF|CDF); debug = 2; /* Shouldn't have generated any interrupts during reset */ if (inb(INTRFLAGS)&INTRMASK) goto fail; debug = 3; /* Test the basic ECHO command */ outb(CMD_ECHO, DATA); debug = 4; /* Wait for CDF=0. If any of the others are set, it's bad */ WAIT(STATUS, STATMASK, 0, STST|DIAGF|INVDCMD|DF|CDF); debug = 5; /* The meaning of life */ outb(42, DATA); debug = 6; /* Expect only DF, that is, data ready */ WAIT(STATUS, STATMASK, DF, STST|DIAGF|CDF|INVDCMD); debug = 7; /* Is the answer correct? */ if (inb(DATA) != 42) goto fail; debug = 8; /* Reading port should reset DF */ if (inb(STATUS) & DF) goto fail; debug = 9; /* When HACC, command is completed, and we're though testing */ WAIT(INTRFLAGS, HACC, HACC, 0); /* now initialize adapter */ debug = 10; /* Clear interrupts */ outb(IRST, CONTROL); debug = 11; return debug; /* 1 = ok */ fail: return 0; /* 0 = not ok */ } /* What's this little function for? */ char *aha1542_info(void) { static char buffer[] = "Adaptec 1542"; return buffer; } /* A "high" level interrupt handler */ void aha1542_intr_handle(void) { int flag = inb(INTRFLAGS); void (*my_done)() = do_done; int errstatus; do_done = NULL; #ifdef DEBUG printk("aha1542_intr_handle: "); if (!(flag&ANYINTR)) printk("no interrupt?"); if (flag&MBIF) printk("MBIF "); if (flag&MBOA) printk("MBOF "); if (flag&HACC) printk("HACC "); if (flag&SCRD) printk("SCRD "); printk("status %02x\n", inb(STATUS)); if (ccb.tarstat|ccb.hastat) printk("aha1542_command: returning %x (status %d)\n", ccb.tarstat + ((int) ccb.hastat << 16), mb[1].status); #endif aha1542_intr_reset(); if (!my_done) { printk("aha1542_intr_handle: Unexpected interrupt\n"); return; } /* is there mail :-) */ if (!mb[1].status) { DEB(printk("aha1542_intr_handle: strange: mbif but no mail!\n")); my_done(DID_TIME_OUT << 16); return; } /* more error checking left out here */ if (mb[1].status != 1) /* This is surely wrong, but I don't know what's right */ errstatus = makecode(ccb.hastat, ccb.tarstat); else errstatus = 0; mb[1].status = 0; if (ccb.tarstat == 2) { int i; DEB(printk("aha1542_intr_handle: sense:")); for (i = 0; i < 12; i++) printk("%02x ", ccb.cdb[ccb.cdblen+i]); printk("\n"); /* DEB(printk("aha1542_intr_handle: buf:")); for (i = 0; i < bufflen; i++) printk("%02x ", ((unchar *)buff)[i]); printk("\n"); */ } DEB(if (errstatus) printk("aha1542_intr_handle: returning %6x\n", errstatus)); my_done(errstatus); return; } int aha1542_queuecommand(unchar target, const void *cmnd, void *buff, int bufflen, void (*done)(int)) { unchar ahacmd = CMD_START_SCSI; int i; unchar *cmd = (unchar *) cmnd; DEB(if (target > 1) {done(DID_TIME_OUT << 16); return 0;}); #ifdef DEBUG if (*cmd == READ_10 || *cmd == WRITE_10) i = xscsi2int(cmd+2); else if (*cmd == READ_6 || *cmd == WRITE_6) i = scsi2int(cmd+2); else i = -1; if (done) printk("aha1542_queuecommand: dev %d cmd %02x pos %d len %d ", target, *cmd, i, bufflen); else printk("aha1542_command: dev %d cmd %02x pos %d len %d ", target, *cmd, i, bufflen); aha1542_stat(); printk("aha1542_queuecommand: dumping scsi cmd:"); for (i = 0; i < (*cmd<=0x1f?6:10); i++) printk("%02x ", cmd[i]); printk("\n"); if (*cmd == WRITE_10 || *cmd == WRITE_6) return 0; /* we are still testing, so *don't* write */ #endif memset(&ccb, 0, sizeof ccb); ccb.cdblen = (*cmd<=0x1f)?6:10; /* SCSI Command Descriptor Block Length */ memcpy(ccb.cdb, cmd, ccb.cdblen); ccb.op = 0; /* SCSI Initiator Command */ ccb.idlun = (target&7)<<5; /* SCSI Target Id */ ccb.rsalen = 12; any2scsi(ccb.datalen, bufflen); any2scsi(ccb.dataptr, buff); ccb.linkptr[0] = ccb.linkptr[1] = ccb.linkptr[2] = 0; ccb.commlinkid = 0; mb[0].status = 1; mb[1].status = 0; #ifdef DEBUGd printk("aha1542_command: sending.. "); for (i = 0; i < sizeof(ccb)-10; i++) printk("%02x ", ((unchar *)&ccb)[i]); #endif if (done) { DEB(printk("aha1542_queuecommand: now waiting for interrupt "); aha1542_stat()); if (do_done) printk("aha1542_queuecommand: Two concurrent queuecommand?\n"); else do_done = done; aha1542_out(&ahacmd, 1); /* start scsi command */ DEB(aha1542_stat()); aha1542_enable_intr(); } else printk("aha1542_queuecommand: done can't be NULL\n"); return 0; } volatile static int internal_done_flag = 0; volatile static int internal_done_errcode = 0; static void internal_done(int errcode) { internal_done_errcode = errcode; ++internal_done_flag; } int aha1542_command(unchar target, const void *cmnd, void *buff, int bufflen) { DEB(printk("aha1542_command: ..calling aha1542_queuecommand\n")); aha1542_queuecommand(target, cmnd, buff, bufflen, internal_done); while (!internal_done_flag); internal_done_flag = 0; return internal_done_errcode; } /* Initialize mailboxes */ static void setup_mailboxes() { static unchar cmd[5] = {CMD_MBINIT, 1}; mb[0].status = mb[1].status = 0; aha1542_intr_reset(); /* reset interrupts, so they don't block */ any2scsi((cmd+2), mb); any2scsi(mb[0].ccbptr, &ccb); aha1542_out(cmd, 5); WAIT(INTRFLAGS, INTRMASK, HACC, 0); while (0) { fail: printk("aha1542_detect: failed setting up mailboxes\n"); } aha1542_intr_reset(); } /* a hack to avoid a strange compilation error */ void call_buh() { set_intr_gate(0x2b,&aha1542_interrupt); } /* return non-zero on detection */ int aha1542_detect(int hostnum) /* hostnum ignored for now */ { int i; DEB(printk("aha1542_detect: \n")); if (!(i = aha1542_test_port())) { return 0; } /* Set the Bus on/off-times as not to ruin floppy performens */ { static unchar oncmd[] = {CMD_BUSON_TIME, 5}; static unchar offcmd[] = {CMD_BUSOFF_TIME, 9}; aha1542_intr_reset(); aha1542_out(oncmd, 2); WAIT(INTRFLAGS, INTRMASK, HACC, 0); aha1542_intr_reset(); aha1542_out(offcmd, 2); WAIT(INTRFLAGS, INTRMASK, HACC, 0); while (0) { fail: printk("aha1542_detect: setting bus on/off-time failed\n"); } aha1542_intr_reset(); } aha1542_stat(); setup_mailboxes(); aha1542_stat(); DEB(printk("aha1542_detect: enable interrupt channel %d\n", intr_chan)); call_buh(); if (intr_chan >= 8) outb(inb_p(0x21)&0xfb,0x21); /* open for slave ?? */ DEB(printk("aha1542_detect: enabling interrupts\n")); aha1542_enable_intr(); #ifdef DEBUG DEB(printk(" *** READ CAPACITY ***\n")); { unchar buf[8]; static unchar cmd[] = { READ_CAPACITY, 0, 0, 0, 0, 0, 0, 0, 0, 0}; int i; for (i = 0; i < sizeof(buf); ++i) buf[i] = 0x87; for (i = 0; i < 2; ++i) if (!aha1542_command(i, cmd, buf, sizeof(buf))) { printk("aha_detect: LU %d sector_size %d device_size %d\n", i, xscsi2int(buf+4), xscsi2int(buf)); } } DEB(printk(" *** NOW RUNNING MY OWN TEST *** \n")); for (i = 0; i < 4; ++i) { unsigned char cmd[10]; static buffer[512]; cmd[0] = READ_10; cmd[1] = 0; xany2scsi(cmd+2, i); cmd[6] = 0; cmd[7] = 0; cmd[8] = 1; cmd[9] = 0; aha1542_command(0, cmd, buffer, 512); } #endif return 1; } int aha1542_abort(int i) { DEB(printk("aha1542_abort\n")); return 0; } int aha1542_reset(void) { DEB(printk("aha1542_reset called\n")); return 0; } __asm__(" _aha1542_interrupt: cld pushl %eax pushl %ecx pushl %edx push %ds push %es push %fs movl $0x10,%eax mov %ax,%ds mov %ax,%es movl $0x17,%eax mov %ax,%fs movb $0x20,%al outb %al,$0xA0 # EOI to interrupt controller #1 jmp 1f # give port chance to breathe 1: jmp 1f 1: outb %al,$0x20 # Please, someone, change this to use the timer # andl $0xfffeffff,_timer_active movl $_aha1542_intr_handle,%edx call *%edx # ``interesting'' way of handling intr. pop %fs pop %es pop %ds popl %edx popl %ecx popl %eax iret ");