| /* |
| * 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 |
| } |