/* * ultrastor.c (C) 1991 David B. Gentzel * Low-level SCSI driver for UltraStor 14F * by David B. Gentzel, Whitfield Software Services, Carnegie, PA * (gentzel@nova.enet.dec.com) * Thanks to UltraStor for providing the necessary documentation */ /* * NOTES: * The UltraStor 14F is an intelligent, high performance ISA SCSI-2 host * adapter. It is essentially an ISA version of the UltraStor 24F EISA * adapter. It supports first-party DMA, command queueing, and * scatter/gather I/O. It can also emulate the standard AT MFM/RLL/IDE * interface for use with OS's which don't support SCSI. * * This driver may also work (with some small changes) with the UltraStor * 24F. I have no way of confirming this... * * Places flagged with a triple question-mark are things which are either * unfinished, questionable, or wrong. */ /* * CAVEATS: ??? * This driver is VERY stupid. It takes no advantage of much of the power * of the UltraStor controller. We just sit-and-spin while waiting for * commands to complete. I hope to go back and beat it into shape, but * PLEASE, anyone else who would like to, please make improvements! * * By defining USE_QUEUECOMMAND as TRUE in ultrastor.h, you enable the * queueing feature of the mid-level SCSI driver. This should improve * performance somewhat. However, it does not seem to work. I believe * this is due to a bug in the mid-level driver, but I haven't looked * too closely. */ #include <linux/config.h> #ifdef CONFIG_SCSI_ULTRASTOR #include <stddef.h> #include <linux/string.h> #include <linux/sched.h> #include <linux/kernel.h> #include <asm/io.h> #include <asm/system.h> #define ULTRASTOR_PRIVATE /* Get the private stuff from ultrastor.h */ #include "ultrastor.h" #include "scsi.h" #include "hosts.h" #define VERSION "1.0 beta" #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr)[0]) #define BIT(n) (1ul << (n)) #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 reserved: 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 { unsigned short port_address; const void *bios_segment; unsigned char interrupt: 4; unsigned char dma_channel: 3; unsigned char ha_scsi_id: 3; unsigned char heads: 6; unsigned char sectors: 6; unsigned char bios_drive_number: 1; }; /* 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; }; /* Allowed BIOS base addresses for 14f (NULL indicates reserved) */ static const void *const bios_segment_table[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[4] = { 15, 14, 11, 10 }; /* Allowed DMA channels for 14f (0 indicates reserved) */ static const unsigned char dma_channel_table[4] = { 5, 6, 7, 0 }; /* Head/sector mappings allowed by 14f */ static const struct { unsigned char heads; unsigned char sectors; } mapping_table[4] = { { 16, 63 }, { 64, 32 }, { 64, 63 }, { 0, 0 } }; /* 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 static const unsigned short ultrastor_ports[] = { 0x330, 0x340, 0x310, 0x230, 0x240, 0x210, 0x130, 0x140, }; #endif void ultrastor_interrupt(void); static void (*ultrastor_done)(int, int) = 0; static const struct { const char *signature; size_t offset; size_t length; } signatures[] = { { "SBIOS 1.01 COPYRIGHT (C) UltraStor Corporation,1990-1992.", 0x10, 57 }, }; int ultrastor_14f_detect(int hostnum) { size_t i; unsigned char in_byte; 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); i++) { PORT_ADDRESS = ultrastor_ports[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", PORT_ADDRESS); # endif #endif #ifdef PORT_OVERRIDE return FALSE; #else continue; #endif } in_byte = inb(PRODUCT_ID(PORT_ADDRESS + 1)); /* Only upper nibble is defined 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", PORT_ADDRESS); # endif #endif #ifdef PORT_OVERRIDE return FALSE; #else continue; #endif } #ifndef PORT_OVERRIDE break; } if (i == ARRAY_SIZE(ultrastor_ports)) { # 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[config_1.bios_segment]; config.interrupt = interrupt_table[config_1.interrupt]; config.dma_channel = dma_channel_table[config_1.dma_channel]; config.ha_scsi_id = config_2.ha_scsi_id; config.heads = mapping_table[config_2.mapping_mode].heads; config.sectors = mapping_table[config_2.mapping_mode].sectors; config.bios_drive_number = config_2.bios_drive_number; /* To verify this card, we simply look for the UltraStor SCSI from the BIOS version notice. */ if (config.bios_segment != NULL) { int found = 0; for (i = 0; !found && i < ARRAY_SIZE(signatures); i++) if (memcmp((char *)config.bios_segment + signatures[i].offset, signatures[i].signature, signatures[i].length)) found = 1; if (!found) config.bios_segment = NULL; } 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.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", PORT_ADDRESS, config.bios_segment, config.interrupt, config.dma_channel, config.ha_scsi_id); #endif host_number = hostnum; scsi_hosts[hostnum].this_id = config.ha_scsi_id; #if USE_QUEUECOMMAND set_intr_gate(0x20 + config.interrupt, ultrastor_interrupt); /* gate to PIC 2 */ outb_p(inb_p(0x21) & ~BIT(2), 0x21); /* enable the interrupt */ outb(inb_p(0xA1) & ~BIT(config.interrupt - 8), 0xA1); #endif return TRUE; } const char *ultrastor_14f_info(void) { return "UltraStor 14F SCSI driver version " VERSION " by David B. Gentzel\n"; } static struct mscp mscp = { OP_SCSI, DTD_SCSI, FALSE, TRUE, FALSE /* This stuff doesn't change */ }; int ultrastor_14f_queuecommand(unsigned char target, const void *cmnd, void *buff, int bufflen, void (*done)(int, int)) { unsigned char in_byte; #if (ULTRASTOR_DEBUG & UD_COMMAND) printk("US14F: queuecommand: called\n"); #endif /* Skip first (constant) byte */ memset((char *)&mscp + 1, 0, sizeof (struct mscp) - 1); mscp.target_id = target; /* mscp.lun = ???; */ mscp.transfer_data = *(Longword *)&buff; mscp.transfer_data_length = *(Longword *)&bufflen, mscp.length_of_scsi_cdbs = ((*(unsigned char *)cmnd <= 0x1F) ? 6 : 10); memcpy(mscp.scsi_cdbs, cmnd, mscp.length_of_scsi_cdbs); /* 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) /* ??? 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; #if (ULTRASTOR_DEBUG & UD_COMMAND) printk("US14F: queuecommand: returning\n"); #endif return 0; } #if !USE_QUEUECOMMAND int ultrastor_14f_command(unsigned char target, const void *cmnd, void *buff, int bufflen) { unsigned char in_byte; #if (ULTRASTOR_DEBUG & UD_COMMAND) printk("US14F: command: called\n"); #endif (void)ultrastor_14f_queuecommand(target, cmnd, buff, bufflen, 0); /* Wait for ICM interrupt */ do in_byte = inb_p(SYS_DOORBELL_INTR(PORT_ADDRESS)); while (!aborted && !(in_byte & 1)); if (aborted) /* ??? is this right? */ return (aborted << 16); /* Clean ICM slot (set ICMINT bit to 0) */ outb_p(0x1, SYS_DOORBELL_INTR(PORT_ADDRESS)); #if (ULTRASTOR_DEBUG & UD_COMMAND) printk("US14F: command: returning %08X\n", (mscp.adapter_status << 16) | mscp.target_status); #endif /* ??? not right, but okay for now? */ return (mscp.adapter_status << 16) | mscp.target_status; } #endif int ultrastor_14f_abort(int code) { aborted = (code ? code : DID_ABORT); return 0; } int ultrastor_14f_reset(void) { unsigned char in_byte; #if (ULTRASTOR_DEBUG & UD_RESET) printk("US14F: reset: called\n"); #endif /* 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; #if (ULTRASTOR_DEBUG & UD_RESET) printk("US14F: reset: returning\n"); #endif return 0; } #if USE_QUEUECOMMAND void ultrastor_interrupt_service(void) { if (ultrastor_done == 0) { printk("US14F: unexpected ultrastor interrupt\n\r"); /* ??? Anything else we should do here? Reset? */ return; } printk("US14F: got an ultrastor interrupt: %u\n\r", (mscp.adapter_status << 16) | mscp.target_status); ultrastor_done(host_number, (mscp.adapter_status << 16) | mscp.target_status); ultrastor_done = 0; } __asm__(" _ultrastor_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 outb %al,$0x80 # give port chance to breathe outb %al,$0x80 outb %al,$0x80 outb %al,$0x80 outb %al,$0x20 call _ultrastor_interrupt_service pop %fs pop %es pop %ds popl %edx popl %ecx popl %eax iret "); #endif #endif