blob: cc5bbca5f87030a880599e093e64e8eb432f7d71 [file] [log] [blame]
/*
* ultrastor.c Copyright (C) 1992 David B. Gentzel
* Low-level SCSI driver for UltraStor 14F
* by David B. Gentzel, Whitfield Software Services, Carnegie, PA
* (gentzel@nova.enet.dec.com)
* scatter/gather added by Scott Taylor (n217cg@tamuts.tamu.edu)
* Thanks to UltraStor for providing the necessary documentation
*/
/*
* TODO:
* 1. Cleanup error handling & reporting.
* 2. Find out why scatter/gather is limited to 16 requests per command.
* 3. Add multiple outstanding requests.
* 4. See if we can make good use of having more than one command per lun.
* 5. Test/improve/fix abort & reset functions.
* 6. Look at command linking (mscp.command_link and
* mscp.command_link_id).
*/
/*
* NOTES:
* The UltraStor 14F is one of a family of intelligent, high performance
* SCSI-2 host adapters. They all support command queueing and
* scatter/gather I/O. Some of them can also emulate the standard
* WD1003 interface for use with OS's which don't support SCSI.
* Here is the scoop on the various models:
* 14F - ISA first-party DMA HA with floppy support and WD1003 emulation.
* 14N - ISA HA with floppy support. I think that this is a non-DMA
* HA. Nothing further known.
* 24F - EISA Bus Master HA with floppy support and WD1003 emulation.
* 34F - VL-Bus Bus Master HA with floppy support (no WD1003 emulation).
*
* The 14F is supported by this driver. An effort has been made to support
* the 34F. It should work, but is untested. The 24F does not work at
* present.
*
* Places flagged with a triple question-mark are things which are either
* unfinished, questionable, or wrong.
*/
#include <linux/stddef.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/dma.h>
#define ULTRASTOR_PRIVATE /* Get the private stuff from ultrastor.h */
#include "../blk.h"
#include "scsi.h"
#include "hosts.h"
#include "ultrastor.h"
#define ULTRASTOR_DEBUG 0
#define VERSION "1.1 alpha"
#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr)[0])
#define BYTE(num, n) ((unsigned char)((unsigned int)(num) >> ((n) * 8)))
/* Simply using "unsigned long" in these structures won't work as it causes
alignment. Perhaps the "aligned" attribute may be used in GCC 2.0 to get
around this, but for now I use this hack. */
typedef struct {
unsigned char bytes[4];
} Longword;
/* Used to fetch the configuration info from the config i/o registers. We
then store (in a friendlier format) in config. */
struct config_1 {
unsigned char bios_segment: 3;
unsigned char removable_disks_as_fixed: 1;
unsigned char interrupt: 2;
unsigned char dma_channel: 2;
};
struct config_2 {
unsigned char ha_scsi_id: 3;
unsigned char mapping_mode: 2;
unsigned char bios_drive_number: 1;
unsigned char tfr_port: 2;
};
/* Used to store configuration info read from config i/o registers. Most of
this is not used yet, but might as well save it. */
struct config {
const void *bios_segment;
unsigned short port_address;
unsigned char interrupt: 4;
unsigned char dma_channel: 3;
unsigned char bios_drive_number: 1;
unsigned char heads;
unsigned char sectors;
unsigned char ha_scsi_id: 3;
unsigned char subversion: 4;
};
/* MailBox SCSI Command Packet. Basic command structure for communicating
with controller. */
struct mscp {
unsigned char opcode: 3; /* type of command */
unsigned char xdir: 2; /* data transfer direction */
unsigned char dcn: 1; /* disable disconnect */
unsigned char ca: 1; /* use cache (if available) */
unsigned char sg: 1; /* scatter/gather operation */
unsigned char target_id: 3; /* target SCSI id */
unsigned char ch_no: 2; /* SCSI channel (always 0 for 14f) */
unsigned char lun: 3; /* logical unit number */
Longword transfer_data; /* transfer data pointer */
Longword transfer_data_length; /* length in bytes */
Longword command_link; /* for linking command chains */
unsigned char scsi_command_link_id; /* identifies command in chain */
unsigned char number_of_sg_list; /* (if sg is set) 8 bytes per list */
unsigned char length_of_sense_byte;
unsigned char length_of_scsi_cdbs; /* 6, 10, or 12 */
unsigned char scsi_cdbs[12]; /* SCSI commands */
unsigned char adapter_status; /* non-zero indicates HA error */
unsigned char target_status; /* non-zero indicates target error */
Longword sense_data;
};
/* The 14F uses an array of unaligned 4-byte ints for its scatter/gather list. */
typedef struct {
unsigned long address;
unsigned long num_bytes;
} ultrastor_sg_list;
/* This is our semaphore for mscp block availability */
int mscp_free = TRUE;
/* Allowed BIOS base addresses for 14f (NULL indicates reserved) */
static const void *const bios_segment_table_14f[8] = {
NULL, (void *)0xC4000, (void *)0xC8000, (void *)0xCC000,
(void *)0xD0000, (void *)0xD4000, (void *)0xD8000, (void *)0xDC000,
};
/* Allowed IRQs for 14f */
static const unsigned char interrupt_table_14f[4] = { 15, 14, 11, 10 };
/* Allowed DMA channels for 14f (0 indicates reserved) */
static const unsigned char dma_channel_table_14f[4] = { 5, 6, 7, 0 };
/* Head/sector mappings allowed by 14f */
static const struct {
unsigned char heads;
unsigned char sectors;
} mapping_table_14f[4] = { { 16, 63 }, { 64, 32 }, { 64, 63 }, { 0, 0 } };
/* Subversions of the 14F */
static const char *const subversion_names[] = { "14F", "34F" };
/* Config info */
static struct config config;
/* Our index in the host adapter array maintained by higher-level driver */
static int host_number;
/* PORT_ADDRESS is first port address used for i/o of messages. */
#ifdef PORT_OVERRIDE
# define PORT_ADDRESS PORT_OVERRIDE
#else
# define PORT_ADDRESS (config.port_address)
#endif
static volatile int aborted = 0;
#ifndef PORT_OVERRIDE
/* ??? A probe of address 0x310 screws up NE2000 cards */
static const unsigned short ultrastor_ports_14f[] = {
0x330, 0x340, /*0x310,*/ 0x230, 0x240, 0x210, 0x130, 0x140,
};
#endif
static void ultrastor_interrupt(int cpl);
static inline void build_sg_list(Scsi_Cmnd *SCpnt);
static void (*ultrastor_done)(Scsi_Cmnd *) = 0;
static Scsi_Cmnd *SCint = NULL;
int ultrastor_detect(int hostnum)
{
size_t i;
unsigned char in_byte, version_byte = 0;
struct config_1 config_1;
struct config_2 config_2;
#if (ULTRASTOR_DEBUG & UD_DETECT)
printk("US14F: detect: called\n");
#endif
#ifndef PORT_OVERRIDE
PORT_ADDRESS = 0;
for (i = 0; i < ARRAY_SIZE(ultrastor_ports_14f); i++) {
PORT_ADDRESS = ultrastor_ports_14f[i];
#endif
#if (ULTRASTOR_DEBUG & UD_DETECT)
printk("US14F: detect: testing port address %03X\n", PORT_ADDRESS);
#endif
in_byte = inb(PRODUCT_ID(PORT_ADDRESS + 0));
if (in_byte != US14F_PRODUCT_ID_0) {
#if (ULTRASTOR_DEBUG & UD_DETECT)
# ifdef PORT_OVERRIDE
printk("US14F: detect: wrong product ID 0 - %02X\n", in_byte);
# else
printk("US14F: detect: no adapter at port %03X\n", PORT_ADDRESS);
# endif
#endif
#ifdef PORT_OVERRIDE
return FALSE;
#else
continue;
#endif
}
in_byte = inb(PRODUCT_ID(PORT_ADDRESS + 1));
/* Only upper nibble is significant for Product ID 1 */
if ((in_byte & 0xF0) != US14F_PRODUCT_ID_1) {
#if (ULTRASTOR_DEBUG & UD_DETECT)
# ifdef PORT_OVERRIDE
printk("US14F: detect: wrong product ID 1 - %02X\n", in_byte);
# else
printk("US14F: detect: no adapter at port %03X\n", PORT_ADDRESS);
# endif
#endif
#ifdef PORT_OVERRIDE
return FALSE;
#else
continue;
#endif
}
version_byte = in_byte;
#ifndef PORT_OVERRIDE
break;
}
if (i == ARRAY_SIZE(ultrastor_ports_14f)) {
# if (ULTRASTOR_DEBUG & UD_DETECT)
printk("US14F: detect: no port address found!\n");
# endif
return FALSE;
}
#endif
#if (ULTRASTOR_DEBUG & UD_DETECT)
printk("US14F: detect: adapter found at port address %03X\n",
PORT_ADDRESS);
#endif
/* All above tests passed, must be the right thing. Get some useful
info. */
*(char *)&config_1 = inb(CONFIG(PORT_ADDRESS + 0));
*(char *)&config_2 = inb(CONFIG(PORT_ADDRESS + 1));
config.bios_segment = bios_segment_table_14f[config_1.bios_segment];
config.interrupt = interrupt_table_14f[config_1.interrupt];
config.ha_scsi_id = config_2.ha_scsi_id;
config.heads = mapping_table_14f[config_2.mapping_mode].heads;
config.sectors = mapping_table_14f[config_2.mapping_mode].sectors;
config.bios_drive_number = config_2.bios_drive_number;
config.subversion = (version_byte & 0x0F);
if (config.subversion == U34F)
config.dma_channel = 0;
else
config.dma_channel = dma_channel_table_14f[config_1.dma_channel];
if (!config.bios_segment) {
#if (ULTRASTOR_DEBUG & UD_DETECT)
printk("US14F: detect: not detected.\n");
#endif
return FALSE;
}
/* Final consistancy check, verify previous info. */
if (config.subversion != U34F)
if (!config.dma_channel || !(config_2.tfr_port & 0x2)) {
#if (ULTRASTOR_DEBUG & UD_DETECT)
printk("US14F: detect: consistancy check failed\n");
#endif
return FALSE;
}
/* If we were TRULY paranoid, we could issue a host adapter inquiry
command here and verify the data returned. But frankly, I'm
exhausted! */
/* Finally! Now I'm satisfied... */
#if (ULTRASTOR_DEBUG & UD_DETECT)
printk("US14F: detect: detect succeeded\n"
" Port address: %03X\n"
" BIOS segment: %05X\n"
" Interrupt: %u\n"
" DMA channel: %u\n"
" H/A SCSI ID: %u\n"
" Subversion: %u\n",
PORT_ADDRESS, config.bios_segment, config.interrupt,
config.dma_channel, config.ha_scsi_id, config.subversion);
#endif
host_number = hostnum;
scsi_hosts[hostnum].this_id = config.ha_scsi_id;
scsi_hosts[hostnum].unchecked_isa_dma = (config.subversion != U34F);
if (request_irq(config.interrupt, ultrastor_interrupt)) {
printk("Unable to allocate IRQ%u for UltraStor controller.\n",
config.interrupt);
return FALSE;
}
if (config.dma_channel && request_dma(config.dma_channel)) {
printk("Unable to allocate DMA channel %u for UltraStor controller.\n",
config.dma_channel);
free_irq(config.interrupt);
return FALSE;
}
scsi_hosts[hostnum].sg_tablesize = ULTRASTOR_14F_MAX_SG;
printk("UltraStor: scatter/gather enabled. Using %d SG lists.\n", ULTRASTOR_14F_MAX_SG);
return TRUE;
}
const char *ultrastor_info(void)
{
static char buf[64];
(void)sprintf(buf, "UltraStor %s SCSI @ Port %03X BIOS %05X IRQ%u DMA%u\n",
((config.subversion < ARRAY_SIZE(subversion_names))
? subversion_names[config.subversion] : "14F?"),
PORT_ADDRESS, (int)config.bios_segment, config.interrupt,
config.dma_channel);
return buf;
}
static struct mscp mscp = {
OP_SCSI, DTD_SCSI, 0, 1, 0 /* This stuff doesn't change */
};
static inline void build_sg_list(Scsi_Cmnd *SCpnt)
{
ultrastor_sg_list *sglist;
struct scatterlist *sl;
long transfer_length = 0;
int i;
sl = (struct scatterlist *) SCpnt->request_buffer;
SCpnt->host_scribble = (unsigned char *) scsi_malloc(512);
if (SCpnt->host_scribble == NULL)
/* Not sure what to do here; just panic for now */
panic("US14F: Can't allocate DMA buffer for scatter-gather list!\n");
/* Save ourselves some casts; can eliminate when we don't have to look at it anymore! */
sglist = (ultrastor_sg_list *) SCpnt->host_scribble;
for (i = 0; i < SCpnt->use_sg; i++) {
sglist[i].address = sl[i].address;
sglist[i].num_bytes = sl[i].length;
transfer_length += sl[i].length;
}
mscp.number_of_sg_list = (char) SCpnt->use_sg;
mscp.transfer_data = *(Longword *)&sglist;
/* ??? May not be necessary. Docs are unclear as to whether transfer length field is */
/* ignored or whether it should be set to the total number of bytes of the transfer. */
mscp.transfer_data_length = *(Longword *)&transfer_length;
}
int ultrastor_queuecommand(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
{
unsigned char in_byte;
#if (ULTRASTOR_DEBUG & UD_COMMAND)
printk("US14F: queuecommand: called\n");
#endif
/* We want to be sure that a command queued while another command */
/* is running doesn't overwrite the mscp block until the running */
/* command is finished. mscp_free is set in the interrupt handler. */
/* I'm not sure if the upper level driver will send another command */
/* with a command pending; this is just insurance. */
while (1) {
cli();
if (mscp_free) {
mscp_free = FALSE;
sti();
break;
}
sti();
}
mscp.opcode = OP_SCSI;
mscp.xdir = DTD_SCSI;
mscp.dcn = FALSE;
/* Tape drives don't work properly if the cache is used. The SCSI
READ command for a tape doesn't have a block offset, and the adapter
incorrectly assumes that all reads from the tape read the same
blocks. Results will depend on read buffer size and other disk
activity.
??? Which other device types should never use the cache? */
mscp.ca = scsi_devices[SCpnt->index].type != TYPE_TAPE;
mscp.target_id = SCpnt->target;
mscp.ch_no = 0;
mscp.lun = SCpnt->lun;
if (SCpnt->use_sg) {
/* Set scatter/gather flag in SCSI command packet */
mscp.sg = TRUE;
build_sg_list(SCpnt);
}
else {
/* Unset scatter/gather flag in SCSI command packet */
mscp.sg = FALSE;
mscp.transfer_data = *(Longword *)&SCpnt->request_buffer;
mscp.transfer_data_length = *(Longword *)&SCpnt->request_bufflen;
SCpnt->host_scribble = NULL;
}
memset(&mscp.command_link, 0, sizeof(mscp.command_link)); /*???*/
mscp.scsi_command_link_id = 0; /*???*/
mscp.length_of_sense_byte = 0; /*???*/
mscp.length_of_scsi_cdbs = COMMAND_SIZE(*(unsigned char *)SCpnt->cmnd);
memcpy(mscp.scsi_cdbs, SCpnt->cmnd, mscp.length_of_scsi_cdbs);
mscp.adapter_status = 0;
mscp.target_status = 0;
memset(&mscp.sense_data, 0, sizeof(mscp.sense_data)); /*???*/
/* Find free OGM slot (OGMINT bit is 0) */
do
in_byte = inb_p(LCL_DOORBELL_INTR(PORT_ADDRESS));
while (!aborted && (in_byte & 1));
if (aborted) {
#if (ULTRASTOR_DEBUG & (UD_COMMAND | UD_ABORT))
printk("US14F: queuecommand: aborted\n");
#endif
/* ??? is this right? */
return (aborted << 16);
}
/* Store pointer in OGM address bytes */
outb_p(BYTE(&mscp, 0), OGM_DATA_PTR(PORT_ADDRESS + 0));
outb_p(BYTE(&mscp, 1), OGM_DATA_PTR(PORT_ADDRESS + 1));
outb_p(BYTE(&mscp, 2), OGM_DATA_PTR(PORT_ADDRESS + 2));
outb_p(BYTE(&mscp, 3), OGM_DATA_PTR(PORT_ADDRESS + 3));
/* Issue OGM interrupt */
outb_p(0x1, LCL_DOORBELL_INTR(PORT_ADDRESS));
ultrastor_done = done;
SCint = SCpnt;
#if (ULTRASTOR_DEBUG & UD_COMMAND)
printk("US14F: queuecommand: returning\n");
#endif
return 0;
}
int ultrastor_abort(Scsi_Cmnd *SCpnt, int code)
{
#if (ULTRASTOR_DEBUG & UD_ABORT)
printk("US14F: abort: called\n");
#endif
aborted = (code ? code : DID_ABORT);
/* Free DMA buffer used for scatter/gather list */
if (SCpnt->host_scribble)
scsi_free(SCpnt->host_scribble, 512);
/* Free up mscp block for next command */
mscp_free = TRUE;
#if (ULTRASTOR_DEBUG & UD_ABORT)
printk("US14F: abort: returning\n");
#endif
return 0;
}
int ultrastor_reset(void)
{
#if 0
unsigned char in_byte;
#endif
#if (ULTRASTOR_DEBUG & UD_RESET)
printk("US14F: reset: called\n");
#endif
/* ??? SCSI bus reset causes problems on some systems. */
#if 0
/* Issue SCSI BUS reset */
outb_p(0x20, LCL_DOORBELL_INTR(PORT_ADDRESS));
/* Wait for completion... */
do
in_byte = inb_p(LCL_DOORBELL_INTR(PORT_ADDRESS));
while (in_byte & 0x20);
aborted = DID_RESET;
#endif
#if (ULTRASTOR_DEBUG & UD_RESET)
printk("US14F: reset: returning\n");
#endif
return 0;
}
int ultrastor_biosparam(int size, int dev, int *ip)
{
unsigned int s = config.heads * config.sectors;
ip[0] = config.heads;
ip[1] = config.sectors;
ip[2] = (size + (s - 1)) / s;
/* if (ip[2] > 1024)
ip[2] = 1024; */
return 0;
}
static void ultrastor_interrupt(int cpl)
{
#if (ULTRASTOR_DEBUG & UD_INTERRUPT)
printk("US14F: interrupt: called: status = %08X\n",
(mscp.adapter_status << 16) | mscp.target_status);
#endif
if (ultrastor_done == 0)
panic("US14F: interrupt: unexpected interrupt");
else {
void (*done)(Scsi_Cmnd *);
Scsi_Cmnd *SCtmp;
/* Save ultrastor_done locally and zero before calling. This is needed
as once we call done, we may get another command queued before this
interrupt service routine can return. */
done = ultrastor_done;
ultrastor_done = 0;
SCtmp = SCint;
/* Clean ICM slot (set ICMINT bit to 0) */
outb_p(0x1, SYS_DOORBELL_INTR(PORT_ADDRESS));
/* Let the higher levels know that we're done */
/* ??? status is wrong here... */
SCtmp->result = (mscp.adapter_status << 16) | mscp.target_status;
/* Free temp space used for scatter-gather list */
if (SCtmp->host_scribble)
scsi_free(SCtmp->host_scribble, 512);
/* Free up mscp block for next command */
mscp_free = TRUE;
done(SCtmp);
}
#if (ULTRASTOR_DEBUG & UD_INTERRUPT)
printk("US14F: interrupt: returning\n");
#endif
}