2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* dec_esp.c: Driver for SCSI chips on IOASIC based TURBOchannel DECstations
|
|
|
|
* and TURBOchannel PMAZ-A cards
|
|
|
|
*
|
|
|
|
* TURBOchannel changes by Harald Koerfgen
|
|
|
|
* PMAZ-A support by David Airlie
|
|
|
|
*
|
|
|
|
* based on jazz_esp.c:
|
|
|
|
* Copyright (C) 1997 Thomas Bogendoerfer (tsbogend@alpha.franken.de)
|
|
|
|
*
|
|
|
|
* jazz_esp is based on David S. Miller's ESP driver and cyber_esp
|
|
|
|
*
|
|
|
|
* 20000819 - Small PMAZ-AA fixes by Florian Lohoff <flo@rfc822.org>
|
|
|
|
* Be warned the PMAZ-AA works currently as a single card.
|
|
|
|
* Dont try to put multiple cards in one machine - They are
|
|
|
|
* both detected but it may crash under high load garbling your
|
|
|
|
* data.
|
|
|
|
* 20001005 - Initialization fixes for 2.4.0-test9
|
|
|
|
* Florian Lohoff <flo@rfc822.org>
|
|
|
|
*
|
2005-10-15 09:26:31 +08:00
|
|
|
* Copyright (C) 2002, 2003, 2005 Maciej W. Rozycki
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/blkdev.h>
|
|
|
|
#include <linux/proc_fs.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/stat.h>
|
|
|
|
|
|
|
|
#include <asm/dma.h>
|
|
|
|
#include <asm/irq.h>
|
|
|
|
#include <asm/pgtable.h>
|
|
|
|
#include <asm/system.h>
|
|
|
|
|
|
|
|
#include <asm/dec/interrupts.h>
|
|
|
|
#include <asm/dec/ioasic.h>
|
|
|
|
#include <asm/dec/ioasic_addrs.h>
|
|
|
|
#include <asm/dec/ioasic_ints.h>
|
|
|
|
#include <asm/dec/machtype.h>
|
2005-10-15 09:26:31 +08:00
|
|
|
#include <asm/dec/system.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <asm/dec/tc.h>
|
|
|
|
|
|
|
|
#define DEC_SCSI_SREG 0
|
|
|
|
#define DEC_SCSI_DMAREG 0x40000
|
|
|
|
#define DEC_SCSI_SRAM 0x80000
|
|
|
|
#define DEC_SCSI_DIAG 0xC0000
|
|
|
|
|
|
|
|
#include "scsi.h"
|
|
|
|
#include <scsi/scsi_host.h>
|
|
|
|
#include "NCR53C9x.h"
|
|
|
|
|
|
|
|
static int dma_bytes_sent(struct NCR_ESP *esp, int fifo_count);
|
|
|
|
static void dma_drain(struct NCR_ESP *esp);
|
|
|
|
static int dma_can_transfer(struct NCR_ESP *esp, struct scsi_cmnd *sp);
|
|
|
|
static void dma_dump_state(struct NCR_ESP *esp);
|
|
|
|
static void dma_init_read(struct NCR_ESP *esp, u32 vaddress, int length);
|
|
|
|
static void dma_init_write(struct NCR_ESP *esp, u32 vaddress, int length);
|
|
|
|
static void dma_ints_off(struct NCR_ESP *esp);
|
|
|
|
static void dma_ints_on(struct NCR_ESP *esp);
|
|
|
|
static int dma_irq_p(struct NCR_ESP *esp);
|
|
|
|
static int dma_ports_p(struct NCR_ESP *esp);
|
|
|
|
static void dma_setup(struct NCR_ESP *esp, u32 addr, int count, int write);
|
|
|
|
static void dma_mmu_get_scsi_one(struct NCR_ESP *esp, struct scsi_cmnd * sp);
|
|
|
|
static void dma_mmu_get_scsi_sgl(struct NCR_ESP *esp, struct scsi_cmnd * sp);
|
|
|
|
static void dma_advance_sg(struct scsi_cmnd * sp);
|
|
|
|
|
|
|
|
static void pmaz_dma_drain(struct NCR_ESP *esp);
|
|
|
|
static void pmaz_dma_init_read(struct NCR_ESP *esp, u32 vaddress, int length);
|
|
|
|
static void pmaz_dma_init_write(struct NCR_ESP *esp, u32 vaddress, int length);
|
|
|
|
static void pmaz_dma_ints_off(struct NCR_ESP *esp);
|
|
|
|
static void pmaz_dma_ints_on(struct NCR_ESP *esp);
|
|
|
|
static void pmaz_dma_setup(struct NCR_ESP *esp, u32 addr, int count, int write);
|
|
|
|
static void pmaz_dma_mmu_get_scsi_one(struct NCR_ESP *esp, struct scsi_cmnd * sp);
|
|
|
|
|
|
|
|
#define TC_ESP_RAM_SIZE 0x20000
|
|
|
|
#define ESP_TGT_DMA_SIZE ((TC_ESP_RAM_SIZE/7) & ~(sizeof(int)-1))
|
|
|
|
#define ESP_NCMD 7
|
|
|
|
|
|
|
|
#define TC_ESP_DMAR_MASK 0x1ffff
|
|
|
|
#define TC_ESP_DMAR_WRITE 0x80000000
|
|
|
|
#define TC_ESP_DMA_ADDR(x) ((unsigned)(x) & TC_ESP_DMAR_MASK)
|
|
|
|
|
|
|
|
u32 esp_virt_buffer;
|
|
|
|
int scsi_current_length;
|
|
|
|
|
|
|
|
volatile unsigned char cmd_buffer[16];
|
|
|
|
volatile unsigned char pmaz_cmd_buffer[16];
|
|
|
|
/* This is where all commands are put
|
|
|
|
* before they are trasfered to the ESP chip
|
|
|
|
* via PIO.
|
|
|
|
*/
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
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)
2006-10-05 21:55:46 +08:00
|
|
|
static irqreturn_t scsi_dma_merr_int(int, void *);
|
|
|
|
static irqreturn_t scsi_dma_err_int(int, void *);
|
|
|
|
static irqreturn_t scsi_dma_int(int, void *);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
static int dec_esp_detect(struct scsi_host_template * tpnt);
|
|
|
|
|
|
|
|
static int dec_esp_release(struct Scsi_Host *shost)
|
|
|
|
{
|
|
|
|
if (shost->irq)
|
|
|
|
free_irq(shost->irq, NULL);
|
|
|
|
if (shost->io_port && shost->n_io_port)
|
|
|
|
release_region(shost->io_port, shost->n_io_port);
|
|
|
|
scsi_unregister(shost);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct scsi_host_template driver_template = {
|
|
|
|
.proc_name = "dec_esp",
|
|
|
|
.proc_info = esp_proc_info,
|
|
|
|
.name = "NCR53C94",
|
|
|
|
.detect = dec_esp_detect,
|
|
|
|
.slave_alloc = esp_slave_alloc,
|
|
|
|
.slave_destroy = esp_slave_destroy,
|
|
|
|
.release = dec_esp_release,
|
|
|
|
.info = esp_info,
|
|
|
|
.queuecommand = esp_queue,
|
|
|
|
.eh_abort_handler = esp_abort,
|
|
|
|
.eh_bus_reset_handler = esp_reset,
|
|
|
|
.can_queue = 7,
|
|
|
|
.this_id = 7,
|
|
|
|
.sg_tablesize = SG_ALL,
|
|
|
|
.cmd_per_lun = 1,
|
|
|
|
.use_clustering = DISABLE_CLUSTERING,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#include "scsi_module.c"
|
|
|
|
|
|
|
|
/***************************************************************** Detection */
|
2005-11-01 01:31:40 +08:00
|
|
|
static int dec_esp_detect(struct scsi_host_template * tpnt)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct NCR_ESP *esp;
|
|
|
|
struct ConfigDev *esp_dev;
|
|
|
|
int slot;
|
|
|
|
unsigned long mem_start;
|
|
|
|
|
|
|
|
if (IOASIC) {
|
|
|
|
esp_dev = 0;
|
|
|
|
esp = esp_allocate(tpnt, (void *) esp_dev);
|
|
|
|
|
|
|
|
/* Do command transfer with programmed I/O */
|
|
|
|
esp->do_pio_cmds = 1;
|
|
|
|
|
|
|
|
/* Required functions */
|
|
|
|
esp->dma_bytes_sent = &dma_bytes_sent;
|
|
|
|
esp->dma_can_transfer = &dma_can_transfer;
|
|
|
|
esp->dma_dump_state = &dma_dump_state;
|
|
|
|
esp->dma_init_read = &dma_init_read;
|
|
|
|
esp->dma_init_write = &dma_init_write;
|
|
|
|
esp->dma_ints_off = &dma_ints_off;
|
|
|
|
esp->dma_ints_on = &dma_ints_on;
|
|
|
|
esp->dma_irq_p = &dma_irq_p;
|
|
|
|
esp->dma_ports_p = &dma_ports_p;
|
|
|
|
esp->dma_setup = &dma_setup;
|
|
|
|
|
|
|
|
/* Optional functions */
|
|
|
|
esp->dma_barrier = 0;
|
|
|
|
esp->dma_drain = &dma_drain;
|
|
|
|
esp->dma_invalidate = 0;
|
|
|
|
esp->dma_irq_entry = 0;
|
|
|
|
esp->dma_irq_exit = 0;
|
|
|
|
esp->dma_poll = 0;
|
|
|
|
esp->dma_reset = 0;
|
|
|
|
esp->dma_led_off = 0;
|
|
|
|
esp->dma_led_on = 0;
|
|
|
|
|
|
|
|
/* virtual DMA functions */
|
|
|
|
esp->dma_mmu_get_scsi_one = &dma_mmu_get_scsi_one;
|
|
|
|
esp->dma_mmu_get_scsi_sgl = &dma_mmu_get_scsi_sgl;
|
|
|
|
esp->dma_mmu_release_scsi_one = 0;
|
|
|
|
esp->dma_mmu_release_scsi_sgl = 0;
|
|
|
|
esp->dma_advance_sg = &dma_advance_sg;
|
|
|
|
|
|
|
|
|
|
|
|
/* SCSI chip speed */
|
|
|
|
esp->cfreq = 25000000;
|
|
|
|
|
|
|
|
esp->dregs = 0;
|
|
|
|
|
|
|
|
/* ESP register base */
|
2005-10-15 09:26:31 +08:00
|
|
|
esp->eregs = (void *)CKSEG1ADDR(dec_kn_slot_base +
|
|
|
|
IOASIC_SCSI);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Set the command buffer */
|
|
|
|
esp->esp_command = (volatile unsigned char *) cmd_buffer;
|
|
|
|
|
|
|
|
/* get virtual dma address for command buffer */
|
|
|
|
esp->esp_command_dvma = virt_to_phys(cmd_buffer);
|
|
|
|
|
|
|
|
esp->irq = dec_interrupt[DEC_IRQ_ASC];
|
|
|
|
|
|
|
|
esp->scsi_id = 7;
|
|
|
|
|
|
|
|
/* Check for differential SCSI-bus */
|
|
|
|
esp->diff = 0;
|
|
|
|
|
|
|
|
esp_initialize(esp);
|
|
|
|
|
2006-07-02 10:29:42 +08:00
|
|
|
if (request_irq(esp->irq, esp_intr, IRQF_DISABLED,
|
2005-04-17 06:20:36 +08:00
|
|
|
"ncr53c94", esp->ehost))
|
|
|
|
goto err_dealloc;
|
|
|
|
if (request_irq(dec_interrupt[DEC_IRQ_ASC_MERR],
|
2006-07-02 10:29:42 +08:00
|
|
|
scsi_dma_merr_int, IRQF_DISABLED,
|
2005-04-17 06:20:36 +08:00
|
|
|
"ncr53c94 error", esp->ehost))
|
|
|
|
goto err_free_irq;
|
|
|
|
if (request_irq(dec_interrupt[DEC_IRQ_ASC_ERR],
|
2006-07-02 10:29:42 +08:00
|
|
|
scsi_dma_err_int, IRQF_DISABLED,
|
2005-04-17 06:20:36 +08:00
|
|
|
"ncr53c94 overrun", esp->ehost))
|
|
|
|
goto err_free_irq_merr;
|
|
|
|
if (request_irq(dec_interrupt[DEC_IRQ_ASC_DMA],
|
2006-07-02 10:29:42 +08:00
|
|
|
scsi_dma_int, IRQF_DISABLED,
|
2005-04-17 06:20:36 +08:00
|
|
|
"ncr53c94 dma", esp->ehost))
|
|
|
|
goto err_free_irq_err;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (TURBOCHANNEL) {
|
|
|
|
while ((slot = search_tc_card("PMAZ-AA")) >= 0) {
|
|
|
|
claim_tc_card(slot);
|
|
|
|
|
|
|
|
esp_dev = 0;
|
|
|
|
esp = esp_allocate(tpnt, (void *) esp_dev);
|
|
|
|
|
|
|
|
mem_start = get_tc_base_addr(slot);
|
|
|
|
|
|
|
|
/* Store base addr into esp struct */
|
2005-06-14 03:55:42 +08:00
|
|
|
esp->slot = CPHYSADDR(mem_start);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
esp->dregs = 0;
|
2005-10-15 09:26:31 +08:00
|
|
|
esp->eregs = (void *)CKSEG1ADDR(mem_start +
|
|
|
|
DEC_SCSI_SREG);
|
2005-04-17 06:20:36 +08:00
|
|
|
esp->do_pio_cmds = 1;
|
|
|
|
|
|
|
|
/* Set the command buffer */
|
|
|
|
esp->esp_command = (volatile unsigned char *) pmaz_cmd_buffer;
|
|
|
|
|
|
|
|
/* get virtual dma address for command buffer */
|
|
|
|
esp->esp_command_dvma = virt_to_phys(pmaz_cmd_buffer);
|
|
|
|
|
|
|
|
esp->cfreq = get_tc_speed();
|
|
|
|
|
|
|
|
esp->irq = get_tc_irq_nr(slot);
|
|
|
|
|
|
|
|
/* Required functions */
|
|
|
|
esp->dma_bytes_sent = &dma_bytes_sent;
|
|
|
|
esp->dma_can_transfer = &dma_can_transfer;
|
|
|
|
esp->dma_dump_state = &dma_dump_state;
|
|
|
|
esp->dma_init_read = &pmaz_dma_init_read;
|
|
|
|
esp->dma_init_write = &pmaz_dma_init_write;
|
|
|
|
esp->dma_ints_off = &pmaz_dma_ints_off;
|
|
|
|
esp->dma_ints_on = &pmaz_dma_ints_on;
|
|
|
|
esp->dma_irq_p = &dma_irq_p;
|
|
|
|
esp->dma_ports_p = &dma_ports_p;
|
|
|
|
esp->dma_setup = &pmaz_dma_setup;
|
|
|
|
|
|
|
|
/* Optional functions */
|
|
|
|
esp->dma_barrier = 0;
|
|
|
|
esp->dma_drain = &pmaz_dma_drain;
|
|
|
|
esp->dma_invalidate = 0;
|
|
|
|
esp->dma_irq_entry = 0;
|
|
|
|
esp->dma_irq_exit = 0;
|
|
|
|
esp->dma_poll = 0;
|
|
|
|
esp->dma_reset = 0;
|
|
|
|
esp->dma_led_off = 0;
|
|
|
|
esp->dma_led_on = 0;
|
|
|
|
|
|
|
|
esp->dma_mmu_get_scsi_one = pmaz_dma_mmu_get_scsi_one;
|
|
|
|
esp->dma_mmu_get_scsi_sgl = 0;
|
|
|
|
esp->dma_mmu_release_scsi_one = 0;
|
|
|
|
esp->dma_mmu_release_scsi_sgl = 0;
|
|
|
|
esp->dma_advance_sg = 0;
|
|
|
|
|
2006-07-02 10:29:42 +08:00
|
|
|
if (request_irq(esp->irq, esp_intr, IRQF_DISABLED,
|
2005-04-17 06:20:36 +08:00
|
|
|
"PMAZ_AA", esp->ehost)) {
|
|
|
|
esp_deallocate(esp);
|
|
|
|
release_tc_card(slot);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
esp->scsi_id = 7;
|
|
|
|
esp->diff = 0;
|
|
|
|
esp_initialize(esp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(nesps) {
|
|
|
|
printk("ESP: Total of %d ESP hosts found, %d actually in use.\n", nesps, esps_in_use);
|
|
|
|
esps_running = esps_in_use;
|
|
|
|
return esps_in_use;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_free_irq_err:
|
|
|
|
free_irq(dec_interrupt[DEC_IRQ_ASC_ERR], scsi_dma_err_int);
|
|
|
|
err_free_irq_merr:
|
|
|
|
free_irq(dec_interrupt[DEC_IRQ_ASC_MERR], scsi_dma_merr_int);
|
|
|
|
err_free_irq:
|
|
|
|
free_irq(esp->irq, esp_intr);
|
|
|
|
err_dealloc:
|
|
|
|
esp_deallocate(esp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************* DMA Functions */
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
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)
2006-10-05 21:55:46 +08:00
|
|
|
static irqreturn_t scsi_dma_merr_int(int irq, void *dev_id)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
printk("Got unexpected SCSI DMA Interrupt! < ");
|
|
|
|
printk("SCSI_DMA_MEMRDERR ");
|
|
|
|
printk(">\n");
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
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)
2006-10-05 21:55:46 +08:00
|
|
|
static irqreturn_t scsi_dma_err_int(int irq, void *dev_id)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
/* empty */
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
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)
2006-10-05 21:55:46 +08:00
|
|
|
static irqreturn_t scsi_dma_int(int irq, void *dev_id)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
u32 scsi_next_ptr;
|
|
|
|
|
|
|
|
scsi_next_ptr = ioasic_read(IO_REG_SCSI_DMA_P);
|
|
|
|
|
|
|
|
/* next page */
|
|
|
|
scsi_next_ptr = (((scsi_next_ptr >> 3) + PAGE_SIZE) & PAGE_MASK) << 3;
|
|
|
|
ioasic_write(IO_REG_SCSI_DMA_BP, scsi_next_ptr);
|
|
|
|
fast_iob();
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dma_bytes_sent(struct NCR_ESP *esp, int fifo_count)
|
|
|
|
{
|
|
|
|
return fifo_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dma_drain(struct NCR_ESP *esp)
|
|
|
|
{
|
|
|
|
u32 nw, data0, data1, scsi_data_ptr;
|
|
|
|
u16 *p;
|
|
|
|
|
|
|
|
nw = ioasic_read(IO_REG_SCSI_SCR);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Is there something in the dma buffers left?
|
|
|
|
*/
|
|
|
|
if (nw) {
|
|
|
|
scsi_data_ptr = ioasic_read(IO_REG_SCSI_DMA_P) >> 3;
|
|
|
|
p = phys_to_virt(scsi_data_ptr);
|
|
|
|
switch (nw) {
|
|
|
|
case 1:
|
|
|
|
data0 = ioasic_read(IO_REG_SCSI_SDR0);
|
|
|
|
p[0] = data0 & 0xffff;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
data0 = ioasic_read(IO_REG_SCSI_SDR0);
|
|
|
|
p[0] = data0 & 0xffff;
|
|
|
|
p[1] = (data0 >> 16) & 0xffff;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
data0 = ioasic_read(IO_REG_SCSI_SDR0);
|
|
|
|
data1 = ioasic_read(IO_REG_SCSI_SDR1);
|
|
|
|
p[0] = data0 & 0xffff;
|
|
|
|
p[1] = (data0 >> 16) & 0xffff;
|
|
|
|
p[2] = data1 & 0xffff;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
printk("Strange: %d words in dma buffer left\n", nw);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dma_can_transfer(struct NCR_ESP *esp, struct scsi_cmnd * sp)
|
|
|
|
{
|
|
|
|
return sp->SCp.this_residual;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dma_dump_state(struct NCR_ESP *esp)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dma_init_read(struct NCR_ESP *esp, u32 vaddress, int length)
|
|
|
|
{
|
|
|
|
u32 scsi_next_ptr, ioasic_ssr;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
if (vaddress & 3)
|
|
|
|
panic("dec_esp.c: unable to handle partial word transfers, yet...");
|
|
|
|
|
|
|
|
dma_cache_wback_inv((unsigned long) phys_to_virt(vaddress), length);
|
|
|
|
|
|
|
|
spin_lock_irqsave(&ioasic_ssr_lock, flags);
|
|
|
|
|
|
|
|
fast_mb();
|
|
|
|
ioasic_ssr = ioasic_read(IO_REG_SSR);
|
|
|
|
|
|
|
|
ioasic_ssr &= ~IO_SSR_SCSI_DMA_EN;
|
|
|
|
ioasic_write(IO_REG_SSR, ioasic_ssr);
|
|
|
|
|
|
|
|
fast_wmb();
|
|
|
|
ioasic_write(IO_REG_SCSI_SCR, 0);
|
|
|
|
ioasic_write(IO_REG_SCSI_DMA_P, vaddress << 3);
|
|
|
|
|
|
|
|
/* prepare for next page */
|
|
|
|
scsi_next_ptr = ((vaddress + PAGE_SIZE) & PAGE_MASK) << 3;
|
|
|
|
ioasic_write(IO_REG_SCSI_DMA_BP, scsi_next_ptr);
|
|
|
|
|
|
|
|
ioasic_ssr |= (IO_SSR_SCSI_DMA_DIR | IO_SSR_SCSI_DMA_EN);
|
|
|
|
fast_wmb();
|
|
|
|
ioasic_write(IO_REG_SSR, ioasic_ssr);
|
|
|
|
|
|
|
|
fast_iob();
|
|
|
|
spin_unlock_irqrestore(&ioasic_ssr_lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dma_init_write(struct NCR_ESP *esp, u32 vaddress, int length)
|
|
|
|
{
|
|
|
|
u32 scsi_next_ptr, ioasic_ssr;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
if (vaddress & 3)
|
|
|
|
panic("dec_esp.c: unable to handle partial word transfers, yet...");
|
|
|
|
|
|
|
|
dma_cache_wback_inv((unsigned long) phys_to_virt(vaddress), length);
|
|
|
|
|
|
|
|
spin_lock_irqsave(&ioasic_ssr_lock, flags);
|
|
|
|
|
|
|
|
fast_mb();
|
|
|
|
ioasic_ssr = ioasic_read(IO_REG_SSR);
|
|
|
|
|
|
|
|
ioasic_ssr &= ~(IO_SSR_SCSI_DMA_DIR | IO_SSR_SCSI_DMA_EN);
|
|
|
|
ioasic_write(IO_REG_SSR, ioasic_ssr);
|
|
|
|
|
|
|
|
fast_wmb();
|
|
|
|
ioasic_write(IO_REG_SCSI_SCR, 0);
|
|
|
|
ioasic_write(IO_REG_SCSI_DMA_P, vaddress << 3);
|
|
|
|
|
|
|
|
/* prepare for next page */
|
|
|
|
scsi_next_ptr = ((vaddress + PAGE_SIZE) & PAGE_MASK) << 3;
|
|
|
|
ioasic_write(IO_REG_SCSI_DMA_BP, scsi_next_ptr);
|
|
|
|
|
|
|
|
ioasic_ssr |= IO_SSR_SCSI_DMA_EN;
|
|
|
|
fast_wmb();
|
|
|
|
ioasic_write(IO_REG_SSR, ioasic_ssr);
|
|
|
|
|
|
|
|
fast_iob();
|
|
|
|
spin_unlock_irqrestore(&ioasic_ssr_lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dma_ints_off(struct NCR_ESP *esp)
|
|
|
|
{
|
|
|
|
disable_irq(dec_interrupt[DEC_IRQ_ASC_DMA]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dma_ints_on(struct NCR_ESP *esp)
|
|
|
|
{
|
|
|
|
enable_irq(dec_interrupt[DEC_IRQ_ASC_DMA]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dma_irq_p(struct NCR_ESP *esp)
|
|
|
|
{
|
|
|
|
return (esp->eregs->esp_status & ESP_STAT_INTR);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dma_ports_p(struct NCR_ESP *esp)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* FIXME: what's this good for?
|
|
|
|
*/
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dma_setup(struct NCR_ESP *esp, u32 addr, int count, int write)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* DMA_ST_WRITE means "move data from device to memory"
|
|
|
|
* so when (write) is true, it actually means READ!
|
|
|
|
*/
|
|
|
|
if (write)
|
|
|
|
dma_init_read(esp, addr, count);
|
|
|
|
else
|
|
|
|
dma_init_write(esp, addr, count);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dma_mmu_get_scsi_one(struct NCR_ESP *esp, struct scsi_cmnd * sp)
|
|
|
|
{
|
|
|
|
sp->SCp.ptr = (char *)virt_to_phys(sp->request_buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dma_mmu_get_scsi_sgl(struct NCR_ESP *esp, struct scsi_cmnd * sp)
|
|
|
|
{
|
|
|
|
int sz = sp->SCp.buffers_residual;
|
|
|
|
struct scatterlist *sg = sp->SCp.buffer;
|
|
|
|
|
|
|
|
while (sz >= 0) {
|
|
|
|
sg[sz].dma_address = page_to_phys(sg[sz].page) + sg[sz].offset;
|
|
|
|
sz--;
|
|
|
|
}
|
|
|
|
sp->SCp.ptr = (char *)(sp->SCp.buffer->dma_address);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dma_advance_sg(struct scsi_cmnd * sp)
|
|
|
|
{
|
|
|
|
sp->SCp.ptr = (char *)(sp->SCp.buffer->dma_address);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pmaz_dma_drain(struct NCR_ESP *esp)
|
|
|
|
{
|
|
|
|
memcpy(phys_to_virt(esp_virt_buffer),
|
2005-10-15 09:44:26 +08:00
|
|
|
(void *)CKSEG1ADDR(esp->slot + DEC_SCSI_SRAM +
|
|
|
|
ESP_TGT_DMA_SIZE),
|
|
|
|
scsi_current_length);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void pmaz_dma_init_read(struct NCR_ESP *esp, u32 vaddress, int length)
|
|
|
|
{
|
|
|
|
volatile u32 *dmareg =
|
2005-10-15 09:44:26 +08:00
|
|
|
(volatile u32 *)CKSEG1ADDR(esp->slot + DEC_SCSI_DMAREG);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (length > ESP_TGT_DMA_SIZE)
|
|
|
|
length = ESP_TGT_DMA_SIZE;
|
|
|
|
|
|
|
|
*dmareg = TC_ESP_DMA_ADDR(ESP_TGT_DMA_SIZE);
|
|
|
|
|
|
|
|
iob();
|
|
|
|
|
|
|
|
esp_virt_buffer = vaddress;
|
|
|
|
scsi_current_length = length;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pmaz_dma_init_write(struct NCR_ESP *esp, u32 vaddress, int length)
|
|
|
|
{
|
|
|
|
volatile u32 *dmareg =
|
2005-10-15 09:44:26 +08:00
|
|
|
(volatile u32 *)CKSEG1ADDR(esp->slot + DEC_SCSI_DMAREG);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-10-15 09:44:26 +08:00
|
|
|
memcpy((void *)CKSEG1ADDR(esp->slot + DEC_SCSI_SRAM +
|
|
|
|
ESP_TGT_DMA_SIZE),
|
2005-04-17 06:20:36 +08:00
|
|
|
phys_to_virt(vaddress), length);
|
|
|
|
|
|
|
|
wmb();
|
|
|
|
*dmareg = TC_ESP_DMAR_WRITE | TC_ESP_DMA_ADDR(ESP_TGT_DMA_SIZE);
|
|
|
|
|
|
|
|
iob();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pmaz_dma_ints_off(struct NCR_ESP *esp)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pmaz_dma_ints_on(struct NCR_ESP *esp)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pmaz_dma_setup(struct NCR_ESP *esp, u32 addr, int count, int write)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* DMA_ST_WRITE means "move data from device to memory"
|
|
|
|
* so when (write) is true, it actually means READ!
|
|
|
|
*/
|
|
|
|
if (write)
|
|
|
|
pmaz_dma_init_read(esp, addr, count);
|
|
|
|
else
|
|
|
|
pmaz_dma_init_write(esp, addr, count);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pmaz_dma_mmu_get_scsi_one(struct NCR_ESP *esp, struct scsi_cmnd * sp)
|
|
|
|
{
|
|
|
|
sp->SCp.ptr = (char *)virt_to_phys(sp->request_buffer);
|
|
|
|
}
|