blob: 635c08059c9db68501725457c7a8f74a4cdfc251 [file] [log] [blame]
/* $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
");