0a4e361254
Because of the rudimentary design of the chip, it is necessary to poll the SCSI bus signals during PIO and this tends to hog the CPU. The driver will accept new commands while others execute, and this causes a soft lockup because the workqueue item will not terminate until the issue queue is emptied. When exercising dmx3191d using sequential IO from dd, the driver is sent 512 KiB WRITE commands and 128 KiB READs. For a PIO transfer, the rate is is only about 300 KiB/s, so these are long-running commands. And although PDMA may run at several MiB/s, interrupts are disabled for the duration of the transfer. Fix the unresponsiveness and soft lockup issues by calling cond_resched() after each command is completed and by limiting max_sectors for drivers that don't implement real DMA. Signed-off-by: Finn Thain <fthain@telegraphics.com.au> Reviewed-by: Hannes Reinecke <hare@suse.com> Tested-by: Ondrej Zary <linux@rainbow-software.org> Tested-by: Michael Schmitz <schmitzmic@gmail.com> Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
442 lines
12 KiB
C
442 lines
12 KiB
C
/*
|
|
* Generic Macintosh NCR5380 driver
|
|
*
|
|
* Copyright 1998, Michael Schmitz <mschmitz@lbl.gov>
|
|
*
|
|
* derived in part from:
|
|
*/
|
|
/*
|
|
* Generic Generic NCR5380 driver
|
|
*
|
|
* Copyright 1995, Russell King
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/module.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/init.h>
|
|
#include <linux/blkdev.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <asm/hwtest.h>
|
|
#include <asm/io.h>
|
|
#include <asm/macints.h>
|
|
#include <asm/setup.h>
|
|
|
|
#include <scsi/scsi_host.h>
|
|
|
|
/* Definitions for the core NCR5380 driver. */
|
|
|
|
#define PSEUDO_DMA
|
|
|
|
#define NCR5380_implementation_fields unsigned char *pdma_base
|
|
|
|
#define NCR5380_read(reg) macscsi_read(instance, reg)
|
|
#define NCR5380_write(reg, value) macscsi_write(instance, reg, value)
|
|
|
|
#define NCR5380_pread macscsi_pread
|
|
#define NCR5380_pwrite macscsi_pwrite
|
|
#define NCR5380_dma_xfer_len(instance, cmd, phase) (cmd->transfersize)
|
|
|
|
#define NCR5380_intr macscsi_intr
|
|
#define NCR5380_queue_command macscsi_queue_command
|
|
#define NCR5380_abort macscsi_abort
|
|
#define NCR5380_bus_reset macscsi_bus_reset
|
|
#define NCR5380_info macscsi_info
|
|
#define NCR5380_show_info macscsi_show_info
|
|
#define NCR5380_write_info macscsi_write_info
|
|
|
|
#include "NCR5380.h"
|
|
|
|
static int setup_can_queue = -1;
|
|
module_param(setup_can_queue, int, 0);
|
|
static int setup_cmd_per_lun = -1;
|
|
module_param(setup_cmd_per_lun, int, 0);
|
|
static int setup_sg_tablesize = -1;
|
|
module_param(setup_sg_tablesize, int, 0);
|
|
static int setup_use_pdma = -1;
|
|
module_param(setup_use_pdma, int, 0);
|
|
static int setup_use_tagged_queuing = -1;
|
|
module_param(setup_use_tagged_queuing, int, 0);
|
|
static int setup_hostid = -1;
|
|
module_param(setup_hostid, int, 0);
|
|
static int setup_toshiba_delay = -1;
|
|
module_param(setup_toshiba_delay, int, 0);
|
|
|
|
/*
|
|
* NCR 5380 register access functions
|
|
*/
|
|
|
|
static inline char macscsi_read(struct Scsi_Host *instance, int reg)
|
|
{
|
|
return in_8(instance->base + (reg << 4));
|
|
}
|
|
|
|
static inline void macscsi_write(struct Scsi_Host *instance, int reg, int value)
|
|
{
|
|
out_8(instance->base + (reg << 4), value);
|
|
}
|
|
|
|
#ifndef MODULE
|
|
static int __init mac_scsi_setup(char *str)
|
|
{
|
|
int ints[8];
|
|
|
|
(void)get_options(str, ARRAY_SIZE(ints), ints);
|
|
|
|
if (ints[0] < 1) {
|
|
pr_err("Usage: mac5380=<can_queue>[,<cmd_per_lun>[,<sg_tablesize>[,<hostid>[,<use_tags>[,<use_pdma>[,<toshiba_delay>]]]]]]\n");
|
|
return 0;
|
|
}
|
|
if (ints[0] >= 1)
|
|
setup_can_queue = ints[1];
|
|
if (ints[0] >= 2)
|
|
setup_cmd_per_lun = ints[2];
|
|
if (ints[0] >= 3)
|
|
setup_sg_tablesize = ints[3];
|
|
if (ints[0] >= 4)
|
|
setup_hostid = ints[4];
|
|
if (ints[0] >= 5)
|
|
setup_use_tagged_queuing = ints[5];
|
|
if (ints[0] >= 6)
|
|
setup_use_pdma = ints[6];
|
|
if (ints[0] >= 7)
|
|
setup_toshiba_delay = ints[7];
|
|
return 1;
|
|
}
|
|
|
|
__setup("mac5380=", mac_scsi_setup);
|
|
#endif /* !MODULE */
|
|
|
|
#ifdef PSEUDO_DMA
|
|
/*
|
|
Pseudo-DMA: (Ove Edlund)
|
|
The code attempts to catch bus errors that occur if one for example
|
|
"trips over the cable".
|
|
XXX: Since bus errors in the PDMA routines never happen on my
|
|
computer, the bus error code is untested.
|
|
If the code works as intended, a bus error results in Pseudo-DMA
|
|
being disabled, meaning that the driver switches to slow handshake.
|
|
If bus errors are NOT extremely rare, this has to be changed.
|
|
*/
|
|
|
|
#define CP_IO_TO_MEM(s,d,len) \
|
|
__asm__ __volatile__ \
|
|
(" cmp.w #4,%2\n" \
|
|
" bls 8f\n" \
|
|
" move.w %1,%%d0\n" \
|
|
" neg.b %%d0\n" \
|
|
" and.w #3,%%d0\n" \
|
|
" sub.w %%d0,%2\n" \
|
|
" bra 2f\n" \
|
|
" 1: move.b (%0),(%1)+\n" \
|
|
" 2: dbf %%d0,1b\n" \
|
|
" move.w %2,%%d0\n" \
|
|
" lsr.w #5,%%d0\n" \
|
|
" bra 4f\n" \
|
|
" 3: move.l (%0),(%1)+\n" \
|
|
"31: move.l (%0),(%1)+\n" \
|
|
"32: move.l (%0),(%1)+\n" \
|
|
"33: move.l (%0),(%1)+\n" \
|
|
"34: move.l (%0),(%1)+\n" \
|
|
"35: move.l (%0),(%1)+\n" \
|
|
"36: move.l (%0),(%1)+\n" \
|
|
"37: move.l (%0),(%1)+\n" \
|
|
" 4: dbf %%d0,3b\n" \
|
|
" move.w %2,%%d0\n" \
|
|
" lsr.w #2,%%d0\n" \
|
|
" and.w #7,%%d0\n" \
|
|
" bra 6f\n" \
|
|
" 5: move.l (%0),(%1)+\n" \
|
|
" 6: dbf %%d0,5b\n" \
|
|
" and.w #3,%2\n" \
|
|
" bra 8f\n" \
|
|
" 7: move.b (%0),(%1)+\n" \
|
|
" 8: dbf %2,7b\n" \
|
|
" moveq.l #0, %2\n" \
|
|
" 9: \n" \
|
|
".section .fixup,\"ax\"\n" \
|
|
" .even\n" \
|
|
"90: moveq.l #1, %2\n" \
|
|
" jra 9b\n" \
|
|
".previous\n" \
|
|
".section __ex_table,\"a\"\n" \
|
|
" .align 4\n" \
|
|
" .long 1b,90b\n" \
|
|
" .long 3b,90b\n" \
|
|
" .long 31b,90b\n" \
|
|
" .long 32b,90b\n" \
|
|
" .long 33b,90b\n" \
|
|
" .long 34b,90b\n" \
|
|
" .long 35b,90b\n" \
|
|
" .long 36b,90b\n" \
|
|
" .long 37b,90b\n" \
|
|
" .long 5b,90b\n" \
|
|
" .long 7b,90b\n" \
|
|
".previous" \
|
|
: "=a"(s), "=a"(d), "=d"(len) \
|
|
: "0"(s), "1"(d), "2"(len) \
|
|
: "d0")
|
|
|
|
static int macscsi_pread(struct Scsi_Host *instance,
|
|
unsigned char *dst, int len)
|
|
{
|
|
struct NCR5380_hostdata *hostdata = shost_priv(instance);
|
|
unsigned char *d;
|
|
unsigned char *s;
|
|
|
|
s = hostdata->pdma_base + (INPUT_DATA_REG << 4);
|
|
d = dst;
|
|
|
|
/* These conditions are derived from MacOS */
|
|
|
|
while (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_DRQ) &&
|
|
!(NCR5380_read(STATUS_REG) & SR_REQ))
|
|
;
|
|
|
|
if (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_DRQ) &&
|
|
(NCR5380_read(BUS_AND_STATUS_REG) & BASR_PHASE_MATCH)) {
|
|
pr_err("Error in macscsi_pread\n");
|
|
return -1;
|
|
}
|
|
|
|
CP_IO_TO_MEM(s, d, len);
|
|
|
|
if (len != 0) {
|
|
pr_notice("Bus error in macscsi_pread\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define CP_MEM_TO_IO(s,d,len) \
|
|
__asm__ __volatile__ \
|
|
(" cmp.w #4,%2\n" \
|
|
" bls 8f\n" \
|
|
" move.w %0,%%d0\n" \
|
|
" neg.b %%d0\n" \
|
|
" and.w #3,%%d0\n" \
|
|
" sub.w %%d0,%2\n" \
|
|
" bra 2f\n" \
|
|
" 1: move.b (%0)+,(%1)\n" \
|
|
" 2: dbf %%d0,1b\n" \
|
|
" move.w %2,%%d0\n" \
|
|
" lsr.w #5,%%d0\n" \
|
|
" bra 4f\n" \
|
|
" 3: move.l (%0)+,(%1)\n" \
|
|
"31: move.l (%0)+,(%1)\n" \
|
|
"32: move.l (%0)+,(%1)\n" \
|
|
"33: move.l (%0)+,(%1)\n" \
|
|
"34: move.l (%0)+,(%1)\n" \
|
|
"35: move.l (%0)+,(%1)\n" \
|
|
"36: move.l (%0)+,(%1)\n" \
|
|
"37: move.l (%0)+,(%1)\n" \
|
|
" 4: dbf %%d0,3b\n" \
|
|
" move.w %2,%%d0\n" \
|
|
" lsr.w #2,%%d0\n" \
|
|
" and.w #7,%%d0\n" \
|
|
" bra 6f\n" \
|
|
" 5: move.l (%0)+,(%1)\n" \
|
|
" 6: dbf %%d0,5b\n" \
|
|
" and.w #3,%2\n" \
|
|
" bra 8f\n" \
|
|
" 7: move.b (%0)+,(%1)\n" \
|
|
" 8: dbf %2,7b\n" \
|
|
" moveq.l #0, %2\n" \
|
|
" 9: \n" \
|
|
".section .fixup,\"ax\"\n" \
|
|
" .even\n" \
|
|
"90: moveq.l #1, %2\n" \
|
|
" jra 9b\n" \
|
|
".previous\n" \
|
|
".section __ex_table,\"a\"\n" \
|
|
" .align 4\n" \
|
|
" .long 1b,90b\n" \
|
|
" .long 3b,90b\n" \
|
|
" .long 31b,90b\n" \
|
|
" .long 32b,90b\n" \
|
|
" .long 33b,90b\n" \
|
|
" .long 34b,90b\n" \
|
|
" .long 35b,90b\n" \
|
|
" .long 36b,90b\n" \
|
|
" .long 37b,90b\n" \
|
|
" .long 5b,90b\n" \
|
|
" .long 7b,90b\n" \
|
|
".previous" \
|
|
: "=a"(s), "=a"(d), "=d"(len) \
|
|
: "0"(s), "1"(d), "2"(len) \
|
|
: "d0")
|
|
|
|
static int macscsi_pwrite(struct Scsi_Host *instance,
|
|
unsigned char *src, int len)
|
|
{
|
|
struct NCR5380_hostdata *hostdata = shost_priv(instance);
|
|
unsigned char *s;
|
|
unsigned char *d;
|
|
|
|
s = src;
|
|
d = hostdata->pdma_base + (OUTPUT_DATA_REG << 4);
|
|
|
|
/* These conditions are derived from MacOS */
|
|
|
|
while (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_DRQ) &&
|
|
(!(NCR5380_read(STATUS_REG) & SR_REQ) ||
|
|
(NCR5380_read(BUS_AND_STATUS_REG) & BASR_PHASE_MATCH)))
|
|
;
|
|
|
|
if (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_DRQ)) {
|
|
pr_err("Error in macscsi_pwrite\n");
|
|
return -1;
|
|
}
|
|
|
|
CP_MEM_TO_IO(s, d, len);
|
|
|
|
if (len != 0) {
|
|
pr_notice("Bus error in macscsi_pwrite\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#include "NCR5380.c"
|
|
|
|
#define DRV_MODULE_NAME "mac_scsi"
|
|
#define PFX DRV_MODULE_NAME ": "
|
|
|
|
static struct scsi_host_template mac_scsi_template = {
|
|
.module = THIS_MODULE,
|
|
.proc_name = DRV_MODULE_NAME,
|
|
.show_info = macscsi_show_info,
|
|
.write_info = macscsi_write_info,
|
|
.name = "Macintosh NCR5380 SCSI",
|
|
.info = macscsi_info,
|
|
.queuecommand = macscsi_queue_command,
|
|
.eh_abort_handler = macscsi_abort,
|
|
.eh_bus_reset_handler = macscsi_bus_reset,
|
|
.can_queue = 16,
|
|
.this_id = 7,
|
|
.sg_tablesize = SG_ALL,
|
|
.cmd_per_lun = 2,
|
|
.use_clustering = DISABLE_CLUSTERING,
|
|
.cmd_size = NCR5380_CMD_SIZE,
|
|
.max_sectors = 128,
|
|
};
|
|
|
|
static int __init mac_scsi_probe(struct platform_device *pdev)
|
|
{
|
|
struct Scsi_Host *instance;
|
|
int error;
|
|
int host_flags = 0;
|
|
struct resource *irq, *pio_mem, *pdma_mem = NULL;
|
|
|
|
pio_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!pio_mem)
|
|
return -ENODEV;
|
|
|
|
#ifdef PSEUDO_DMA
|
|
pdma_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
#endif
|
|
|
|
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
|
|
if (!hwreg_present((unsigned char *)pio_mem->start +
|
|
(STATUS_REG << 4))) {
|
|
pr_info(PFX "no device detected at %pap\n", &pio_mem->start);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (setup_can_queue > 0)
|
|
mac_scsi_template.can_queue = setup_can_queue;
|
|
if (setup_cmd_per_lun > 0)
|
|
mac_scsi_template.cmd_per_lun = setup_cmd_per_lun;
|
|
if (setup_sg_tablesize >= 0)
|
|
mac_scsi_template.sg_tablesize = setup_sg_tablesize;
|
|
if (setup_hostid >= 0)
|
|
mac_scsi_template.this_id = setup_hostid & 7;
|
|
if (setup_use_pdma < 0)
|
|
setup_use_pdma = 0;
|
|
|
|
instance = scsi_host_alloc(&mac_scsi_template,
|
|
sizeof(struct NCR5380_hostdata));
|
|
if (!instance)
|
|
return -ENOMEM;
|
|
|
|
instance->base = pio_mem->start;
|
|
if (irq)
|
|
instance->irq = irq->start;
|
|
else
|
|
instance->irq = NO_IRQ;
|
|
|
|
if (pdma_mem && setup_use_pdma) {
|
|
struct NCR5380_hostdata *hostdata = shost_priv(instance);
|
|
|
|
hostdata->pdma_base = (unsigned char *)pdma_mem->start;
|
|
} else
|
|
host_flags |= FLAG_NO_PSEUDO_DMA;
|
|
|
|
#ifdef SUPPORT_TAGS
|
|
host_flags |= setup_use_tagged_queuing > 0 ? FLAG_TAGGED_QUEUING : 0;
|
|
#endif
|
|
host_flags |= setup_toshiba_delay > 0 ? FLAG_TOSHIBA_DELAY : 0;
|
|
|
|
error = NCR5380_init(instance, host_flags);
|
|
if (error)
|
|
goto fail_init;
|
|
|
|
if (instance->irq != NO_IRQ) {
|
|
error = request_irq(instance->irq, macscsi_intr, IRQF_SHARED,
|
|
"NCR5380", instance);
|
|
if (error)
|
|
goto fail_irq;
|
|
}
|
|
|
|
NCR5380_maybe_reset_bus(instance);
|
|
|
|
error = scsi_add_host(instance, NULL);
|
|
if (error)
|
|
goto fail_host;
|
|
|
|
platform_set_drvdata(pdev, instance);
|
|
|
|
scsi_scan_host(instance);
|
|
return 0;
|
|
|
|
fail_host:
|
|
if (instance->irq != NO_IRQ)
|
|
free_irq(instance->irq, instance);
|
|
fail_irq:
|
|
NCR5380_exit(instance);
|
|
fail_init:
|
|
scsi_host_put(instance);
|
|
return error;
|
|
}
|
|
|
|
static int __exit mac_scsi_remove(struct platform_device *pdev)
|
|
{
|
|
struct Scsi_Host *instance = platform_get_drvdata(pdev);
|
|
|
|
scsi_remove_host(instance);
|
|
if (instance->irq != NO_IRQ)
|
|
free_irq(instance->irq, instance);
|
|
NCR5380_exit(instance);
|
|
scsi_host_put(instance);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver mac_scsi_driver = {
|
|
.remove = __exit_p(mac_scsi_remove),
|
|
.driver = {
|
|
.name = DRV_MODULE_NAME,
|
|
},
|
|
};
|
|
|
|
module_platform_driver_probe(mac_scsi_driver, mac_scsi_probe);
|
|
|
|
MODULE_ALIAS("platform:" DRV_MODULE_NAME);
|
|
MODULE_LICENSE("GPL");
|