mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-11-30 07:16:43 +07:00
7d12e780e0
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
868 lines
26 KiB
C
868 lines
26 KiB
C
/*
|
|
* sym53c416.c
|
|
* Low-level SCSI driver for sym53c416 chip.
|
|
* Copyright (C) 1998 Lieven Willems (lw_linux@hotmail.com)
|
|
*
|
|
* Changes :
|
|
*
|
|
* Marcelo Tosatti <marcelo@conectiva.com.br> : Added io_request_lock locking
|
|
* Alan Cox <alan@redhat.com> : Cleaned up code formatting
|
|
* Fixed an irq locking bug
|
|
* Added ISAPnP support
|
|
* Bjoern A. Zeeb <bzeeb@zabbadoz.net> : Initial irq locking updates
|
|
* Added another card with ISAPnP support
|
|
*
|
|
* LILO command line usage: sym53c416=<PORTBASE>[,<IRQ>]
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2, or (at your option) any
|
|
* later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/types.h>
|
|
#include <linux/init.h>
|
|
#include <linux/string.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/spinlock.h>
|
|
#include <asm/dma.h>
|
|
#include <asm/system.h>
|
|
#include <asm/io.h>
|
|
#include <linux/blkdev.h>
|
|
#include <linux/isapnp.h>
|
|
#include "scsi.h"
|
|
#include <scsi/scsi_host.h>
|
|
#include "sym53c416.h"
|
|
|
|
#define VERSION_STRING "Version 1.0.0-ac"
|
|
|
|
#define TC_LOW 0x00 /* Transfer counter low */
|
|
#define TC_MID 0x01 /* Transfer counter mid */
|
|
#define SCSI_FIFO 0x02 /* SCSI FIFO register */
|
|
#define COMMAND_REG 0x03 /* Command Register */
|
|
#define STATUS_REG 0x04 /* Status Register (READ) */
|
|
#define DEST_BUS_ID 0x04 /* Destination Bus ID (WRITE) */
|
|
#define INT_REG 0x05 /* Interrupt Register (READ) */
|
|
#define TOM 0x05 /* Time out multiplier (WRITE) */
|
|
#define STP 0x06 /* Synchronous Transfer period */
|
|
#define SYNC_OFFSET 0x07 /* Synchronous Offset */
|
|
#define CONF_REG_1 0x08 /* Configuration register 1 */
|
|
#define CONF_REG_2 0x0B /* Configuration register 2 */
|
|
#define CONF_REG_3 0x0C /* Configuration register 3 */
|
|
#define CONF_REG_4 0x0D /* Configuration register 4 */
|
|
#define TC_HIGH 0x0E /* Transfer counter high */
|
|
#define PIO_FIFO_1 0x10 /* PIO FIFO register 1 */
|
|
#define PIO_FIFO_2 0x11 /* PIO FIFO register 2 */
|
|
#define PIO_FIFO_3 0x12 /* PIO FIFO register 3 */
|
|
#define PIO_FIFO_4 0x13 /* PIO FIFO register 4 */
|
|
#define PIO_FIFO_CNT 0x14 /* PIO FIFO count */
|
|
#define PIO_INT_REG 0x15 /* PIO interrupt register */
|
|
#define CONF_REG_5 0x16 /* Configuration register 5 */
|
|
#define FEATURE_EN 0x1D /* Feature Enable register */
|
|
|
|
/* Configuration register 1 entries: */
|
|
/* Bits 2-0: SCSI ID of host adapter */
|
|
#define SCM 0x80 /* Slow Cable Mode */
|
|
#define SRID 0x40 /* SCSI Reset Interrupt Disable */
|
|
#define PTM 0x20 /* Parity Test Mode */
|
|
#define EPC 0x10 /* Enable Parity Checking */
|
|
#define CTME 0x08 /* Special Test Mode */
|
|
|
|
/* Configuration register 2 entries: */
|
|
#define FE 0x40 /* Features Enable */
|
|
#define SCSI2 0x08 /* SCSI 2 Enable */
|
|
#define TBPA 0x04 /* Target Bad Parity Abort */
|
|
|
|
/* Configuration register 3 entries: */
|
|
#define IDMRC 0x80 /* ID Message Reserved Check */
|
|
#define QTE 0x40 /* Queue Tag Enable */
|
|
#define CDB10 0x20 /* Command Descriptor Block 10 */
|
|
#define FSCSI 0x10 /* FastSCSI */
|
|
#define FCLK 0x08 /* FastClock */
|
|
|
|
/* Configuration register 4 entries: */
|
|
#define RBS 0x08 /* Register bank select */
|
|
#define EAN 0x04 /* Enable Active Negotiation */
|
|
|
|
/* Configuration register 5 entries: */
|
|
#define LPSR 0x80 /* Lower Power SCSI Reset */
|
|
#define IE 0x20 /* Interrupt Enable */
|
|
#define LPM 0x02 /* Low Power Mode */
|
|
#define WSE0 0x01 /* 0WS Enable */
|
|
|
|
/* Interrupt register entries: */
|
|
#define SRST 0x80 /* SCSI Reset */
|
|
#define ILCMD 0x40 /* Illegal Command */
|
|
#define DIS 0x20 /* Disconnect */
|
|
#define BS 0x10 /* Bus Service */
|
|
#define FC 0x08 /* Function Complete */
|
|
#define RESEL 0x04 /* Reselected */
|
|
#define SI 0x03 /* Selection Interrupt */
|
|
|
|
/* Status Register Entries: */
|
|
#define SCI 0x80 /* SCSI Core Int */
|
|
#define GE 0x40 /* Gross Error */
|
|
#define PE 0x20 /* Parity Error */
|
|
#define TC 0x10 /* Terminal Count */
|
|
#define VGC 0x08 /* Valid Group Code */
|
|
#define PHBITS 0x07 /* Phase bits */
|
|
|
|
/* PIO Interrupt Register Entries: */
|
|
#define SCI 0x80 /* SCSI Core Int */
|
|
#define PFI 0x40 /* PIO FIFO Interrupt */
|
|
#define FULL 0x20 /* PIO FIFO Full */
|
|
#define EMPTY 0x10 /* PIO FIFO Empty */
|
|
#define CE 0x08 /* Collision Error */
|
|
#define OUE 0x04 /* Overflow / Underflow error */
|
|
#define FIE 0x02 /* Full Interrupt Enable */
|
|
#define EIE 0x01 /* Empty Interrupt Enable */
|
|
|
|
/* SYM53C416 SCSI phases (lower 3 bits of SYM53C416_STATUS_REG) */
|
|
#define PHASE_DATA_OUT 0x00
|
|
#define PHASE_DATA_IN 0x01
|
|
#define PHASE_COMMAND 0x02
|
|
#define PHASE_STATUS 0x03
|
|
#define PHASE_RESERVED_1 0x04
|
|
#define PHASE_RESERVED_2 0x05
|
|
#define PHASE_MESSAGE_OUT 0x06
|
|
#define PHASE_MESSAGE_IN 0x07
|
|
|
|
/* SYM53C416 core commands */
|
|
#define NOOP 0x00
|
|
#define FLUSH_FIFO 0x01
|
|
#define RESET_CHIP 0x02
|
|
#define RESET_SCSI_BUS 0x03
|
|
#define DISABLE_SEL_RESEL 0x45
|
|
#define RESEL_SEQ 0x40
|
|
#define SEL_WITHOUT_ATN_SEQ 0x41
|
|
#define SEL_WITH_ATN_SEQ 0x42
|
|
#define SEL_WITH_ATN_AND_STOP_SEQ 0x43
|
|
#define ENABLE_SEL_RESEL 0x44
|
|
#define SEL_WITH_ATN3_SEQ 0x46
|
|
#define RESEL3_SEQ 0x47
|
|
#define SND_MSG 0x20
|
|
#define SND_STAT 0x21
|
|
#define SND_DATA 0x22
|
|
#define DISCONNECT_SEQ 0x23
|
|
#define TERMINATE_SEQ 0x24
|
|
#define TARGET_COMM_COMPLETE_SEQ 0x25
|
|
#define DISCONN 0x27
|
|
#define RECV_MSG_SEQ 0x28
|
|
#define RECV_CMD 0x29
|
|
#define RECV_DATA 0x2A
|
|
#define RECV_CMD_SEQ 0x2B
|
|
#define TARGET_ABORT_PIO 0x04
|
|
#define TRANSFER_INFORMATION 0x10
|
|
#define INIT_COMM_COMPLETE_SEQ 0x11
|
|
#define MSG_ACCEPTED 0x12
|
|
#define TRANSFER_PAD 0x18
|
|
#define SET_ATN 0x1A
|
|
#define RESET_ATN 0x1B
|
|
#define ILLEGAL 0xFF
|
|
|
|
#define PIO_MODE 0x80
|
|
|
|
#define IO_RANGE 0x20 /* 0x00 - 0x1F */
|
|
#define ID "sym53c416" /* Attention: copied to the sym53c416.h */
|
|
#define PIO_SIZE 128 /* Size of PIO fifo is 128 bytes */
|
|
|
|
#define READ_TIMEOUT 150
|
|
#define WRITE_TIMEOUT 150
|
|
|
|
#ifdef MODULE
|
|
|
|
#define sym53c416_base sym53c416
|
|
#define sym53c416_base_1 sym53c416_1
|
|
#define sym53c416_base_2 sym53c416_2
|
|
#define sym53c416_base_3 sym53c416_3
|
|
|
|
static unsigned int sym53c416_base[2] = {0,0};
|
|
static unsigned int sym53c416_base_1[2] = {0,0};
|
|
static unsigned int sym53c416_base_2[2] = {0,0};
|
|
static unsigned int sym53c416_base_3[2] = {0,0};
|
|
|
|
#endif
|
|
|
|
#define MAXHOSTS 4
|
|
|
|
#define SG_ADDRESS(buffer) ((char *) (page_address((buffer)->page)+(buffer)->offset))
|
|
|
|
enum phases
|
|
{
|
|
idle,
|
|
data_out,
|
|
data_in,
|
|
command_ph,
|
|
status_ph,
|
|
message_out,
|
|
message_in
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
int base;
|
|
int irq;
|
|
int scsi_id;
|
|
} host;
|
|
|
|
static host hosts[MAXHOSTS] = {
|
|
{0, 0, SYM53C416_SCSI_ID},
|
|
{0, 0, SYM53C416_SCSI_ID},
|
|
{0, 0, SYM53C416_SCSI_ID},
|
|
{0, 0, SYM53C416_SCSI_ID}
|
|
};
|
|
|
|
static int host_index = 0;
|
|
static char info[120];
|
|
static Scsi_Cmnd *current_command = NULL;
|
|
static int fastpio = 1;
|
|
|
|
static int probeaddrs[] = {0x200, 0x220, 0x240, 0};
|
|
|
|
static void sym53c416_set_transfer_counter(int base, unsigned int len)
|
|
{
|
|
/* Program Transfer Counter */
|
|
outb(len & 0x0000FF, base + TC_LOW);
|
|
outb((len & 0x00FF00) >> 8, base + TC_MID);
|
|
outb((len & 0xFF0000) >> 16, base + TC_HIGH);
|
|
}
|
|
|
|
static DEFINE_SPINLOCK(sym53c416_lock);
|
|
|
|
/* Returns the number of bytes read */
|
|
static __inline__ unsigned int sym53c416_read(int base, unsigned char *buffer, unsigned int len)
|
|
{
|
|
unsigned int orig_len = len;
|
|
unsigned long flags = 0;
|
|
unsigned int bytes_left;
|
|
unsigned long i;
|
|
int timeout = READ_TIMEOUT;
|
|
|
|
/* Do transfer */
|
|
spin_lock_irqsave(&sym53c416_lock, flags);
|
|
while(len && timeout)
|
|
{
|
|
bytes_left = inb(base + PIO_FIFO_CNT); /* Number of bytes in the PIO FIFO */
|
|
if(fastpio && bytes_left > 3)
|
|
{
|
|
insl(base + PIO_FIFO_1, buffer, bytes_left >> 2);
|
|
buffer += bytes_left & 0xFC;
|
|
len -= bytes_left & 0xFC;
|
|
}
|
|
else if(bytes_left > 0)
|
|
{
|
|
len -= bytes_left;
|
|
for(; bytes_left > 0; bytes_left--)
|
|
*(buffer++) = inb(base + PIO_FIFO_1);
|
|
}
|
|
else
|
|
{
|
|
i = jiffies + timeout;
|
|
spin_unlock_irqrestore(&sym53c416_lock, flags);
|
|
while(time_before(jiffies, i) && (inb(base + PIO_INT_REG) & EMPTY) && timeout)
|
|
if(inb(base + PIO_INT_REG) & SCI)
|
|
timeout = 0;
|
|
spin_lock_irqsave(&sym53c416_lock, flags);
|
|
if(inb(base + PIO_INT_REG) & EMPTY)
|
|
timeout = 0;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&sym53c416_lock, flags);
|
|
return orig_len - len;
|
|
}
|
|
|
|
/* Returns the number of bytes written */
|
|
static __inline__ unsigned int sym53c416_write(int base, unsigned char *buffer, unsigned int len)
|
|
{
|
|
unsigned int orig_len = len;
|
|
unsigned long flags = 0;
|
|
unsigned int bufferfree;
|
|
unsigned long i;
|
|
unsigned int timeout = WRITE_TIMEOUT;
|
|
|
|
/* Do transfer */
|
|
spin_lock_irqsave(&sym53c416_lock, flags);
|
|
while(len && timeout)
|
|
{
|
|
bufferfree = PIO_SIZE - inb(base + PIO_FIFO_CNT);
|
|
if(bufferfree > len)
|
|
bufferfree = len;
|
|
if(fastpio && bufferfree > 3)
|
|
{
|
|
outsl(base + PIO_FIFO_1, buffer, bufferfree >> 2);
|
|
buffer += bufferfree & 0xFC;
|
|
len -= bufferfree & 0xFC;
|
|
}
|
|
else if(bufferfree > 0)
|
|
{
|
|
len -= bufferfree;
|
|
for(; bufferfree > 0; bufferfree--)
|
|
outb(*(buffer++), base + PIO_FIFO_1);
|
|
}
|
|
else
|
|
{
|
|
i = jiffies + timeout;
|
|
spin_unlock_irqrestore(&sym53c416_lock, flags);
|
|
while(time_before(jiffies, i) && (inb(base + PIO_INT_REG) & FULL) && timeout)
|
|
;
|
|
spin_lock_irqsave(&sym53c416_lock, flags);
|
|
if(inb(base + PIO_INT_REG) & FULL)
|
|
timeout = 0;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&sym53c416_lock, flags);
|
|
return orig_len - len;
|
|
}
|
|
|
|
static irqreturn_t sym53c416_intr_handle(int irq, void *dev_id)
|
|
{
|
|
struct Scsi_Host *dev = dev_id;
|
|
int base = 0;
|
|
int i;
|
|
unsigned long flags = 0;
|
|
unsigned char status_reg, pio_int_reg, int_reg;
|
|
struct scatterlist *sglist;
|
|
unsigned int sgcount;
|
|
unsigned int tot_trans = 0;
|
|
|
|
/* We search the base address of the host adapter which caused the interrupt */
|
|
/* FIXME: should pass dev_id sensibly as hosts[i] */
|
|
for(i = 0; i < host_index && !base; i++)
|
|
if(irq == hosts[i].irq)
|
|
base = hosts[i].base;
|
|
/* If no adapter found, we cannot handle the interrupt. Leave a message */
|
|
/* and continue. This should never happen... */
|
|
if(!base)
|
|
{
|
|
printk(KERN_ERR "sym53c416: No host adapter defined for interrupt %d\n", irq);
|
|
return IRQ_NONE;
|
|
}
|
|
/* Now we have the base address and we can start handling the interrupt */
|
|
|
|
spin_lock_irqsave(dev->host_lock,flags);
|
|
status_reg = inb(base + STATUS_REG);
|
|
pio_int_reg = inb(base + PIO_INT_REG);
|
|
int_reg = inb(base + INT_REG);
|
|
spin_unlock_irqrestore(dev->host_lock, flags);
|
|
|
|
/* First, we handle error conditions */
|
|
if(int_reg & SCI) /* SCSI Reset */
|
|
{
|
|
printk(KERN_DEBUG "sym53c416: Reset received\n");
|
|
current_command->SCp.phase = idle;
|
|
current_command->result = DID_RESET << 16;
|
|
spin_lock_irqsave(dev->host_lock, flags);
|
|
current_command->scsi_done(current_command);
|
|
spin_unlock_irqrestore(dev->host_lock, flags);
|
|
goto out;
|
|
}
|
|
if(int_reg & ILCMD) /* Illegal Command */
|
|
{
|
|
printk(KERN_WARNING "sym53c416: Illegal Command: 0x%02x.\n", inb(base + COMMAND_REG));
|
|
current_command->SCp.phase = idle;
|
|
current_command->result = DID_ERROR << 16;
|
|
spin_lock_irqsave(dev->host_lock, flags);
|
|
current_command->scsi_done(current_command);
|
|
spin_unlock_irqrestore(dev->host_lock, flags);
|
|
goto out;
|
|
}
|
|
if(status_reg & GE) /* Gross Error */
|
|
{
|
|
printk(KERN_WARNING "sym53c416: Controller reports gross error.\n");
|
|
current_command->SCp.phase = idle;
|
|
current_command->result = DID_ERROR << 16;
|
|
spin_lock_irqsave(dev->host_lock, flags);
|
|
current_command->scsi_done(current_command);
|
|
spin_unlock_irqrestore(dev->host_lock, flags);
|
|
goto out;
|
|
}
|
|
if(status_reg & PE) /* Parity Error */
|
|
{
|
|
printk(KERN_WARNING "sym53c416:SCSI parity error.\n");
|
|
current_command->SCp.phase = idle;
|
|
current_command->result = DID_PARITY << 16;
|
|
spin_lock_irqsave(dev->host_lock, flags);
|
|
current_command->scsi_done(current_command);
|
|
spin_unlock_irqrestore(dev->host_lock, flags);
|
|
goto out;
|
|
}
|
|
if(pio_int_reg & (CE | OUE))
|
|
{
|
|
printk(KERN_WARNING "sym53c416: PIO interrupt error.\n");
|
|
current_command->SCp.phase = idle;
|
|
current_command->result = DID_ERROR << 16;
|
|
spin_lock_irqsave(dev->host_lock, flags);
|
|
current_command->scsi_done(current_command);
|
|
spin_unlock_irqrestore(dev->host_lock, flags);
|
|
goto out;
|
|
}
|
|
if(int_reg & DIS) /* Disconnect */
|
|
{
|
|
if(current_command->SCp.phase != message_in)
|
|
current_command->result = DID_NO_CONNECT << 16;
|
|
else
|
|
current_command->result = (current_command->SCp.Status & 0xFF) | ((current_command->SCp.Message & 0xFF) << 8) | (DID_OK << 16);
|
|
current_command->SCp.phase = idle;
|
|
spin_lock_irqsave(dev->host_lock, flags);
|
|
current_command->scsi_done(current_command);
|
|
spin_unlock_irqrestore(dev->host_lock, flags);
|
|
goto out;
|
|
}
|
|
/* Now we handle SCSI phases */
|
|
|
|
switch(status_reg & PHBITS) /* Filter SCSI phase out of status reg */
|
|
{
|
|
case PHASE_DATA_OUT:
|
|
{
|
|
if(int_reg & BS)
|
|
{
|
|
current_command->SCp.phase = data_out;
|
|
outb(FLUSH_FIFO, base + COMMAND_REG);
|
|
sym53c416_set_transfer_counter(base, current_command->request_bufflen);
|
|
outb(TRANSFER_INFORMATION | PIO_MODE, base + COMMAND_REG);
|
|
if(!current_command->use_sg)
|
|
tot_trans = sym53c416_write(base, current_command->request_buffer, current_command->request_bufflen);
|
|
else
|
|
{
|
|
sgcount = current_command->use_sg;
|
|
sglist = current_command->request_buffer;
|
|
while(sgcount--)
|
|
{
|
|
tot_trans += sym53c416_write(base, SG_ADDRESS(sglist), sglist->length);
|
|
sglist++;
|
|
}
|
|
}
|
|
if(tot_trans < current_command->underflow)
|
|
printk(KERN_WARNING "sym53c416: Underflow, wrote %d bytes, request for %d bytes.\n", tot_trans, current_command->underflow);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PHASE_DATA_IN:
|
|
{
|
|
if(int_reg & BS)
|
|
{
|
|
current_command->SCp.phase = data_in;
|
|
outb(FLUSH_FIFO, base + COMMAND_REG);
|
|
sym53c416_set_transfer_counter(base, current_command->request_bufflen);
|
|
outb(TRANSFER_INFORMATION | PIO_MODE, base + COMMAND_REG);
|
|
if(!current_command->use_sg)
|
|
tot_trans = sym53c416_read(base, current_command->request_buffer, current_command->request_bufflen);
|
|
else
|
|
{
|
|
sgcount = current_command->use_sg;
|
|
sglist = current_command->request_buffer;
|
|
while(sgcount--)
|
|
{
|
|
tot_trans += sym53c416_read(base, SG_ADDRESS(sglist), sglist->length);
|
|
sglist++;
|
|
}
|
|
}
|
|
if(tot_trans < current_command->underflow)
|
|
printk(KERN_WARNING "sym53c416: Underflow, read %d bytes, request for %d bytes.\n", tot_trans, current_command->underflow);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PHASE_COMMAND:
|
|
{
|
|
current_command->SCp.phase = command_ph;
|
|
printk(KERN_ERR "sym53c416: Unknown interrupt in command phase.\n");
|
|
break;
|
|
}
|
|
|
|
case PHASE_STATUS:
|
|
{
|
|
current_command->SCp.phase = status_ph;
|
|
outb(FLUSH_FIFO, base + COMMAND_REG);
|
|
outb(INIT_COMM_COMPLETE_SEQ, base + COMMAND_REG);
|
|
break;
|
|
}
|
|
|
|
case PHASE_RESERVED_1:
|
|
case PHASE_RESERVED_2:
|
|
{
|
|
printk(KERN_ERR "sym53c416: Reserved phase occurred.\n");
|
|
break;
|
|
}
|
|
|
|
case PHASE_MESSAGE_OUT:
|
|
{
|
|
current_command->SCp.phase = message_out;
|
|
outb(SET_ATN, base + COMMAND_REG);
|
|
outb(MSG_ACCEPTED, base + COMMAND_REG);
|
|
break;
|
|
}
|
|
|
|
case PHASE_MESSAGE_IN:
|
|
{
|
|
current_command->SCp.phase = message_in;
|
|
current_command->SCp.Status = inb(base + SCSI_FIFO);
|
|
current_command->SCp.Message = inb(base + SCSI_FIFO);
|
|
if(current_command->SCp.Message == SAVE_POINTERS || current_command->SCp.Message == DISCONNECT)
|
|
outb(SET_ATN, base + COMMAND_REG);
|
|
outb(MSG_ACCEPTED, base + COMMAND_REG);
|
|
break;
|
|
}
|
|
}
|
|
out:
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void sym53c416_init(int base, int scsi_id)
|
|
{
|
|
outb(RESET_CHIP, base + COMMAND_REG);
|
|
outb(NOOP, base + COMMAND_REG);
|
|
outb(0x99, base + TOM); /* Time out of 250 ms */
|
|
outb(0x05, base + STP);
|
|
outb(0x00, base + SYNC_OFFSET);
|
|
outb(EPC | scsi_id, base + CONF_REG_1);
|
|
outb(FE | SCSI2 | TBPA, base + CONF_REG_2);
|
|
outb(IDMRC | QTE | CDB10 | FSCSI | FCLK, base + CONF_REG_3);
|
|
outb(0x83 | EAN, base + CONF_REG_4);
|
|
outb(IE | WSE0, base + CONF_REG_5);
|
|
outb(0, base + FEATURE_EN);
|
|
}
|
|
|
|
static int sym53c416_probeirq(int base, int scsi_id)
|
|
{
|
|
int irq, irqs;
|
|
unsigned long i;
|
|
|
|
/* Clear interrupt register */
|
|
inb(base + INT_REG);
|
|
/* Start probing for irq's */
|
|
irqs = probe_irq_on();
|
|
/* Reinit chip */
|
|
sym53c416_init(base, scsi_id);
|
|
/* Cause interrupt */
|
|
outb(NOOP, base + COMMAND_REG);
|
|
outb(ILLEGAL, base + COMMAND_REG);
|
|
outb(0x07, base + DEST_BUS_ID);
|
|
outb(0x00, base + DEST_BUS_ID);
|
|
/* Wait for interrupt to occur */
|
|
i = jiffies + 20;
|
|
while(time_before(jiffies, i) && !(inb(base + STATUS_REG) & SCI))
|
|
barrier();
|
|
if(time_before_eq(i, jiffies)) /* timed out */
|
|
return 0;
|
|
/* Get occurred irq */
|
|
irq = probe_irq_off(irqs);
|
|
sym53c416_init(base, scsi_id);
|
|
return irq;
|
|
}
|
|
|
|
/* Setup: sym53c416=base,irq */
|
|
void sym53c416_setup(char *str, int *ints)
|
|
{
|
|
int i;
|
|
|
|
if(host_index >= MAXHOSTS)
|
|
{
|
|
printk(KERN_WARNING "sym53c416: Too many hosts defined\n");
|
|
return;
|
|
}
|
|
if(ints[0] < 1 || ints[0] > 2)
|
|
{
|
|
printk(KERN_ERR "sym53c416: Wrong number of parameters:\n");
|
|
printk(KERN_ERR "sym53c416: usage: sym53c416=<base>[,<irq>]\n");
|
|
return;
|
|
}
|
|
for(i = 0; i < host_index && i >= 0; i++)
|
|
if(hosts[i].base == ints[1])
|
|
i = -2;
|
|
if(i >= 0)
|
|
{
|
|
hosts[host_index].base = ints[1];
|
|
hosts[host_index].irq = (ints[0] == 2)? ints[2] : 0;
|
|
host_index++;
|
|
}
|
|
}
|
|
|
|
static int sym53c416_test(int base)
|
|
{
|
|
outb(RESET_CHIP, base + COMMAND_REG);
|
|
outb(NOOP, base + COMMAND_REG);
|
|
if(inb(base + COMMAND_REG) != NOOP)
|
|
return 0;
|
|
if(!inb(base + TC_HIGH) || inb(base + TC_HIGH) == 0xFF)
|
|
return 0;
|
|
if((inb(base + PIO_INT_REG) & (FULL | EMPTY | CE | OUE | FIE | EIE)) != EMPTY)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
static struct isapnp_device_id id_table[] __devinitdata = {
|
|
{ ISAPNP_ANY_ID, ISAPNP_ANY_ID,
|
|
ISAPNP_VENDOR('S','L','I'), ISAPNP_FUNCTION(0x4161), 0 },
|
|
{ ISAPNP_ANY_ID, ISAPNP_ANY_ID,
|
|
ISAPNP_VENDOR('S','L','I'), ISAPNP_FUNCTION(0x4163), 0 },
|
|
{ ISAPNP_DEVICE_SINGLE_END }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(isapnp, id_table);
|
|
|
|
static void sym53c416_probe(void)
|
|
{
|
|
int *base = probeaddrs;
|
|
int ints[2];
|
|
|
|
ints[0] = 1;
|
|
for(; *base; base++) {
|
|
if (request_region(*base, IO_RANGE, ID)) {
|
|
if (sym53c416_test(*base)) {
|
|
ints[1] = *base;
|
|
sym53c416_setup(NULL, ints);
|
|
}
|
|
release_region(*base, IO_RANGE);
|
|
}
|
|
}
|
|
}
|
|
|
|
int __init sym53c416_detect(struct scsi_host_template *tpnt)
|
|
{
|
|
unsigned long flags;
|
|
struct Scsi_Host * shpnt = NULL;
|
|
int i;
|
|
int count;
|
|
struct pnp_dev *idev = NULL;
|
|
|
|
#ifdef MODULE
|
|
int ints[3];
|
|
|
|
ints[0] = 2;
|
|
if(sym53c416_base)
|
|
{
|
|
ints[1] = sym53c416_base[0];
|
|
ints[2] = sym53c416_base[1];
|
|
sym53c416_setup(NULL, ints);
|
|
}
|
|
if(sym53c416_base_1)
|
|
{
|
|
ints[1] = sym53c416_base_1[0];
|
|
ints[2] = sym53c416_base_1[1];
|
|
sym53c416_setup(NULL, ints);
|
|
}
|
|
if(sym53c416_base_2)
|
|
{
|
|
ints[1] = sym53c416_base_2[0];
|
|
ints[2] = sym53c416_base_2[1];
|
|
sym53c416_setup(NULL, ints);
|
|
}
|
|
if(sym53c416_base_3)
|
|
{
|
|
ints[1] = sym53c416_base_3[0];
|
|
ints[2] = sym53c416_base_3[1];
|
|
sym53c416_setup(NULL, ints);
|
|
}
|
|
#endif
|
|
printk(KERN_INFO "sym53c416.c: %s\n", VERSION_STRING);
|
|
|
|
for (i=0; id_table[i].vendor != 0; i++) {
|
|
while((idev=pnp_find_dev(NULL, id_table[i].vendor,
|
|
id_table[i].function, idev))!=NULL)
|
|
{
|
|
int i[3];
|
|
|
|
if(pnp_device_attach(idev)<0)
|
|
{
|
|
printk(KERN_WARNING "sym53c416: unable to attach PnP device.\n");
|
|
continue;
|
|
}
|
|
if(pnp_activate_dev(idev) < 0)
|
|
{
|
|
printk(KERN_WARNING "sym53c416: unable to activate PnP device.\n");
|
|
pnp_device_detach(idev);
|
|
continue;
|
|
|
|
}
|
|
|
|
i[0] = 2;
|
|
i[1] = pnp_port_start(idev, 0);
|
|
i[2] = pnp_irq(idev, 0);
|
|
|
|
printk(KERN_INFO "sym53c416: ISAPnP card found and configured at 0x%X, IRQ %d.\n",
|
|
i[1], i[2]);
|
|
sym53c416_setup(NULL, i);
|
|
}
|
|
}
|
|
sym53c416_probe();
|
|
|
|
/* Now we register and set up each host adapter found... */
|
|
for(count = 0, i = 0; i < host_index; i++) {
|
|
if (!request_region(hosts[i].base, IO_RANGE, ID))
|
|
continue;
|
|
if (!sym53c416_test(hosts[i].base)) {
|
|
printk(KERN_WARNING "No sym53c416 found at address 0x%03x\n", hosts[i].base);
|
|
goto fail_release_region;
|
|
}
|
|
|
|
/* We don't have an irq yet, so we should probe for one */
|
|
if (!hosts[i].irq)
|
|
hosts[i].irq = sym53c416_probeirq(hosts[i].base, hosts[i].scsi_id);
|
|
if (!hosts[i].irq)
|
|
goto fail_release_region;
|
|
|
|
shpnt = scsi_register(tpnt, 0);
|
|
if (!shpnt)
|
|
goto fail_release_region;
|
|
/* Request for specified IRQ */
|
|
if (request_irq(hosts[i].irq, sym53c416_intr_handle, 0, ID, shpnt))
|
|
goto fail_free_host;
|
|
|
|
spin_lock_irqsave(&sym53c416_lock, flags);
|
|
shpnt->unique_id = hosts[i].base;
|
|
shpnt->io_port = hosts[i].base;
|
|
shpnt->n_io_port = IO_RANGE;
|
|
shpnt->irq = hosts[i].irq;
|
|
shpnt->this_id = hosts[i].scsi_id;
|
|
sym53c416_init(hosts[i].base, hosts[i].scsi_id);
|
|
count++;
|
|
spin_unlock_irqrestore(&sym53c416_lock, flags);
|
|
continue;
|
|
|
|
fail_free_host:
|
|
scsi_unregister(shpnt);
|
|
fail_release_region:
|
|
release_region(hosts[i].base, IO_RANGE);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
const char *sym53c416_info(struct Scsi_Host *SChost)
|
|
{
|
|
int i;
|
|
int base = SChost->io_port;
|
|
int irq = SChost->irq;
|
|
int scsi_id = 0;
|
|
int rev = inb(base + TC_HIGH);
|
|
|
|
for(i = 0; i < host_index; i++)
|
|
if(hosts[i].base == base)
|
|
scsi_id = hosts[i].scsi_id;
|
|
sprintf(info, "Symbios Logic 53c416 (rev. %d) at 0x%03x, irq %d, SCSI-ID %d, %s pio", rev, base, irq, scsi_id, (fastpio)? "fast" : "slow");
|
|
return info;
|
|
}
|
|
|
|
int sym53c416_queuecommand(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
|
|
{
|
|
int base;
|
|
unsigned long flags = 0;
|
|
int i;
|
|
|
|
/* Store base register as we can have more than one controller in the system */
|
|
base = SCpnt->device->host->io_port;
|
|
current_command = SCpnt; /* set current command */
|
|
current_command->scsi_done = done; /* set ptr to done function */
|
|
current_command->SCp.phase = command_ph; /* currect phase is the command phase */
|
|
current_command->SCp.Status = 0;
|
|
current_command->SCp.Message = 0;
|
|
|
|
spin_lock_irqsave(&sym53c416_lock, flags);
|
|
outb(scmd_id(SCpnt), base + DEST_BUS_ID); /* Set scsi id target */
|
|
outb(FLUSH_FIFO, base + COMMAND_REG); /* Flush SCSI and PIO FIFO's */
|
|
/* Write SCSI command into the SCSI fifo */
|
|
for(i = 0; i < SCpnt->cmd_len; i++)
|
|
outb(SCpnt->cmnd[i], base + SCSI_FIFO);
|
|
/* Start selection sequence */
|
|
outb(SEL_WITHOUT_ATN_SEQ, base + COMMAND_REG);
|
|
/* Now an interrupt will be generated which we will catch in out interrupt routine */
|
|
spin_unlock_irqrestore(&sym53c416_lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static int sym53c416_host_reset(Scsi_Cmnd *SCpnt)
|
|
{
|
|
int base;
|
|
int scsi_id = -1;
|
|
int i;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&sym53c416_lock, flags);
|
|
|
|
/* printk("sym53c416_reset\n"); */
|
|
base = SCpnt->device->host->io_port;
|
|
/* search scsi_id - fixme, we shouldnt need to iterate for this! */
|
|
for(i = 0; i < host_index && scsi_id == -1; i++)
|
|
if(hosts[i].base == base)
|
|
scsi_id = hosts[i].scsi_id;
|
|
outb(RESET_CHIP, base + COMMAND_REG);
|
|
outb(NOOP | PIO_MODE, base + COMMAND_REG);
|
|
outb(RESET_SCSI_BUS, base + COMMAND_REG);
|
|
sym53c416_init(base, scsi_id);
|
|
|
|
spin_unlock_irqrestore(&sym53c416_lock, flags);
|
|
return SUCCESS;
|
|
}
|
|
|
|
static int sym53c416_release(struct Scsi_Host *shost)
|
|
{
|
|
if (shost->irq)
|
|
free_irq(shost->irq, shost);
|
|
if (shost->io_port && shost->n_io_port)
|
|
release_region(shost->io_port, shost->n_io_port);
|
|
return 0;
|
|
}
|
|
|
|
static int sym53c416_bios_param(struct scsi_device *sdev,
|
|
struct block_device *dev,
|
|
sector_t capacity, int *ip)
|
|
{
|
|
int size;
|
|
|
|
size = capacity;
|
|
ip[0] = 64; /* heads */
|
|
ip[1] = 32; /* sectors */
|
|
if((ip[2] = size >> 11) > 1024) /* cylinders, test for big disk */
|
|
{
|
|
ip[0] = 255; /* heads */
|
|
ip[1] = 63; /* sectors */
|
|
ip[2] = size / (255 * 63); /* cylinders */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Loadable module support */
|
|
#ifdef MODULE
|
|
|
|
MODULE_AUTHOR("Lieven Willems");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_param_array(sym53c416, uint, NULL, 0);
|
|
module_param_array(sym53c416_1, uint, NULL, 0);
|
|
module_param_array(sym53c416_2, uint, NULL, 0);
|
|
module_param_array(sym53c416_3, uint, NULL, 0);
|
|
|
|
#endif
|
|
|
|
static struct scsi_host_template driver_template = {
|
|
.proc_name = "sym53c416",
|
|
.name = "Symbios Logic 53c416",
|
|
.detect = sym53c416_detect,
|
|
.info = sym53c416_info,
|
|
.queuecommand = sym53c416_queuecommand,
|
|
.eh_host_reset_handler =sym53c416_host_reset,
|
|
.release = sym53c416_release,
|
|
.bios_param = sym53c416_bios_param,
|
|
.can_queue = 1,
|
|
.this_id = SYM53C416_SCSI_ID,
|
|
.sg_tablesize = 32,
|
|
.cmd_per_lun = 1,
|
|
.unchecked_isa_dma = 1,
|
|
.use_clustering = ENABLE_CLUSTERING,
|
|
};
|
|
#include "scsi_module.c"
|