scsi: mac_scsi: Fix pseudo DMA implementation, take 2

commit 78ff751f8e6a9446e9fb26b2bff0b8d3f8974cbd upstream.

A system bus error during a PDMA transfer can mess up the calculation of
the transfer residual (the PDMA handshaking hardware lacks a byte
counter). This results in data corruption.

The algorithm in this patch anticipates a bus error by starting each
transfer with a MOVE.B instruction. If a bus error is caught the transfer
will be retried. If a bus error is caught later in the transfer (for a
MOVE.W instruction) the transfer gets failed and subsequent requests for
that target will use PIO instead of PDMA.

This avoids the "!REQ and !ACK" error so the severity level of that message
is reduced to KERN_DEBUG.

Cc: Michael Schmitz <schmitzmic@gmail.com>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: stable@vger.kernel.org # v4.14+
Fixes: 3a0f64bfa9 ("mac_scsi: Fix pseudo DMA implementation")
Signed-off-by: Finn Thain <fthain@telegraphics.com.au>
Reported-by: Chris Jones <chris@martin-jones.com>
Tested-by: Stan Johnson <userm57@yahoo.com>
Tested-by: Michael Schmitz <schmitzmic@gmail.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Finn Thain 2019-06-09 11:19:11 +10:00 committed by Greg Kroah-Hartman
parent de769c7626
commit ce7ec07aba

View file

@ -3,6 +3,8 @@
* *
* Copyright 1998, Michael Schmitz <mschmitz@lbl.gov> * Copyright 1998, Michael Schmitz <mschmitz@lbl.gov>
* *
* Copyright 2019 Finn Thain
*
* derived in part from: * derived in part from:
*/ */
/* /*
@ -11,6 +13,7 @@
* Copyright 1995, Russell King * Copyright 1995, Russell King
*/ */
#include <linux/delay.h>
#include <linux/types.h> #include <linux/types.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/ioport.h> #include <linux/ioport.h>
@ -89,101 +92,217 @@ static int __init mac_scsi_setup(char *str)
__setup("mac5380=", mac_scsi_setup); __setup("mac5380=", mac_scsi_setup);
#endif /* !MODULE */ #endif /* !MODULE */
/* Pseudo DMA asm originally by Ove Edlund */ /*
* According to "Inside Macintosh: Devices", Mac OS requires disk drivers to
* specify the number of bytes between the delays expected from a SCSI target.
* This allows the operating system to "prevent bus errors when a target fails
* to deliver the next byte within the processor bus error timeout period."
* Linux SCSI drivers lack knowledge of the timing behaviour of SCSI targets
* so bus errors are unavoidable.
*
* If a MOVE.B instruction faults, we assume that zero bytes were transferred
* and simply retry. That assumption probably depends on target behaviour but
* seems to hold up okay. The NOP provides synchronization: without it the
* fault can sometimes occur after the program counter has moved past the
* offending instruction. Post-increment addressing can't be used.
*/
#define CP_IO_TO_MEM(s,d,n) \ #define MOVE_BYTE(operands) \
__asm__ __volatile__ \ asm volatile ( \
(" cmp.w #4,%2\n" \ "1: moveb " operands " \n" \
" bls 8f\n" \ "11: nop \n" \
" move.w %1,%%d0\n" \ " addq #1,%0 \n" \
" neg.b %%d0\n" \ " subq #1,%1 \n" \
" and.w #3,%%d0\n" \ "40: \n" \
" sub.w %%d0,%2\n" \ " \n" \
" bra 2f\n" \ ".section .fixup,\"ax\" \n" \
" 1: move.b (%0),(%1)+\n" \ ".even \n" \
" 2: dbf %%d0,1b\n" \ "90: movel #1, %2 \n" \
" move.w %2,%%d0\n" \ " jra 40b \n" \
" lsr.w #5,%%d0\n" \ ".previous \n" \
" bra 4f\n" \ " \n" \
" 3: move.l (%0),(%1)+\n" \ ".section __ex_table,\"a\" \n" \
"31: move.l (%0),(%1)+\n" \ ".align 4 \n" \
"32: move.l (%0),(%1)+\n" \ ".long 1b,90b \n" \
"33: move.l (%0),(%1)+\n" \ ".long 11b,90b \n" \
"34: move.l (%0),(%1)+\n" \ ".previous \n" \
"35: move.l (%0),(%1)+\n" \ : "+a" (addr), "+r" (n), "+r" (result) : "a" (io))
"36: move.l (%0),(%1)+\n" \
"37: move.l (%0),(%1)+\n" \ /*
" 4: dbf %%d0,3b\n" \ * If a MOVE.W (or MOVE.L) instruction faults, it cannot be retried because
" move.w %2,%%d0\n" \ * the residual byte count would be uncertain. In that situation the MOVE_WORD
" lsr.w #2,%%d0\n" \ * macro clears n in the fixup section to abort the transfer.
" and.w #7,%%d0\n" \ */
" bra 6f\n" \
" 5: move.l (%0),(%1)+\n" \ #define MOVE_WORD(operands) \
" 6: dbf %%d0,5b\n" \ asm volatile ( \
" and.w #3,%2\n" \ "1: movew " operands " \n" \
" bra 8f\n" \ "11: nop \n" \
" 7: move.b (%0),(%1)+\n" \ " subq #2,%1 \n" \
" 8: dbf %2,7b\n" \ "40: \n" \
" moveq.l #0, %2\n" \ " \n" \
" 9: \n" \ ".section .fixup,\"ax\" \n" \
".section .fixup,\"ax\"\n" \ ".even \n" \
" .even\n" \ "90: movel #0, %1 \n" \
"91: moveq.l #1, %2\n" \ " movel #2, %2 \n" \
" jra 9b\n" \ " jra 40b \n" \
"94: moveq.l #4, %2\n" \ ".previous \n" \
" jra 9b\n" \ " \n" \
".previous\n" \ ".section __ex_table,\"a\" \n" \
".section __ex_table,\"a\"\n" \ ".align 4 \n" \
" .align 4\n" \ ".long 1b,90b \n" \
" .long 1b,91b\n" \ ".long 11b,90b \n" \
" .long 3b,94b\n" \ ".previous \n" \
" .long 31b,94b\n" \ : "+a" (addr), "+r" (n), "+r" (result) : "a" (io))
" .long 32b,94b\n" \
" .long 33b,94b\n" \ #define MOVE_16_WORDS(operands) \
" .long 34b,94b\n" \ asm volatile ( \
" .long 35b,94b\n" \ "1: movew " operands " \n" \
" .long 36b,94b\n" \ "2: movew " operands " \n" \
" .long 37b,94b\n" \ "3: movew " operands " \n" \
" .long 5b,94b\n" \ "4: movew " operands " \n" \
" .long 7b,91b\n" \ "5: movew " operands " \n" \
".previous" \ "6: movew " operands " \n" \
: "=a"(s), "=a"(d), "=d"(n) \ "7: movew " operands " \n" \
: "0"(s), "1"(d), "2"(n) \ "8: movew " operands " \n" \
: "d0") "9: movew " operands " \n" \
"10: movew " operands " \n" \
"11: movew " operands " \n" \
"12: movew " operands " \n" \
"13: movew " operands " \n" \
"14: movew " operands " \n" \
"15: movew " operands " \n" \
"16: movew " operands " \n" \
"17: nop \n" \
" subl #32,%1 \n" \
"40: \n" \
" \n" \
".section .fixup,\"ax\" \n" \
".even \n" \
"90: movel #0, %1 \n" \
" movel #2, %2 \n" \
" jra 40b \n" \
".previous \n" \
" \n" \
".section __ex_table,\"a\" \n" \
".align 4 \n" \
".long 1b,90b \n" \
".long 2b,90b \n" \
".long 3b,90b \n" \
".long 4b,90b \n" \
".long 5b,90b \n" \
".long 6b,90b \n" \
".long 7b,90b \n" \
".long 8b,90b \n" \
".long 9b,90b \n" \
".long 10b,90b \n" \
".long 11b,90b \n" \
".long 12b,90b \n" \
".long 13b,90b \n" \
".long 14b,90b \n" \
".long 15b,90b \n" \
".long 16b,90b \n" \
".long 17b,90b \n" \
".previous \n" \
: "+a" (addr), "+r" (n), "+r" (result) : "a" (io))
#define MAC_PDMA_DELAY 32
static inline int mac_pdma_recv(void __iomem *io, unsigned char *start, int n)
{
unsigned char *addr = start;
int result = 0;
if (n >= 1) {
MOVE_BYTE("%3@,%0@");
if (result)
goto out;
}
if (n >= 1 && ((unsigned long)addr & 1)) {
MOVE_BYTE("%3@,%0@");
if (result)
goto out;
}
while (n >= 32)
MOVE_16_WORDS("%3@,%0@+");
while (n >= 2)
MOVE_WORD("%3@,%0@+");
if (result)
return start - addr; /* Negated to indicate uncertain length */
if (n == 1)
MOVE_BYTE("%3@,%0@");
out:
return addr - start;
}
static inline int mac_pdma_send(unsigned char *start, void __iomem *io, int n)
{
unsigned char *addr = start;
int result = 0;
if (n >= 1) {
MOVE_BYTE("%0@,%3@");
if (result)
goto out;
}
if (n >= 1 && ((unsigned long)addr & 1)) {
MOVE_BYTE("%0@,%3@");
if (result)
goto out;
}
while (n >= 32)
MOVE_16_WORDS("%0@+,%3@");
while (n >= 2)
MOVE_WORD("%0@+,%3@");
if (result)
return start - addr; /* Negated to indicate uncertain length */
if (n == 1)
MOVE_BYTE("%0@,%3@");
out:
return addr - start;
}
static inline int macscsi_pread(struct NCR5380_hostdata *hostdata, static inline int macscsi_pread(struct NCR5380_hostdata *hostdata,
unsigned char *dst, int len) unsigned char *dst, int len)
{ {
u8 __iomem *s = hostdata->pdma_io + (INPUT_DATA_REG << 4); u8 __iomem *s = hostdata->pdma_io + (INPUT_DATA_REG << 4);
unsigned char *d = dst; unsigned char *d = dst;
int n = len;
int transferred; hostdata->pdma_residual = len;
while (!NCR5380_poll_politely(hostdata, BUS_AND_STATUS_REG, while (!NCR5380_poll_politely(hostdata, BUS_AND_STATUS_REG,
BASR_DRQ | BASR_PHASE_MATCH, BASR_DRQ | BASR_PHASE_MATCH,
BASR_DRQ | BASR_PHASE_MATCH, HZ / 64)) { BASR_DRQ | BASR_PHASE_MATCH, HZ / 64)) {
CP_IO_TO_MEM(s, d, n); int bytes;
transferred = d - dst - n; bytes = mac_pdma_recv(s, d, min(hostdata->pdma_residual, 512));
hostdata->pdma_residual = len - transferred;
/* No bus error. */ if (bytes > 0) {
if (n == 0) d += bytes;
hostdata->pdma_residual -= bytes;
}
if (hostdata->pdma_residual == 0)
return 0; return 0;
/* Target changed phase early? */
if (NCR5380_poll_politely2(hostdata, STATUS_REG, SR_REQ, SR_REQ, if (NCR5380_poll_politely2(hostdata, STATUS_REG, SR_REQ, SR_REQ,
BUS_AND_STATUS_REG, BASR_ACK, BASR_ACK, HZ / 64) < 0) BUS_AND_STATUS_REG, BASR_ACK,
scmd_printk(KERN_ERR, hostdata->connected, BASR_ACK, HZ / 64) < 0)
scmd_printk(KERN_DEBUG, hostdata->connected,
"%s: !REQ and !ACK\n", __func__); "%s: !REQ and !ACK\n", __func__);
if (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_PHASE_MATCH)) if (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_PHASE_MATCH))
return 0; return 0;
if (bytes == 0)
udelay(MAC_PDMA_DELAY);
if (bytes >= 0)
continue;
dsprintk(NDEBUG_PSEUDO_DMA, hostdata->host, dsprintk(NDEBUG_PSEUDO_DMA, hostdata->host,
"%s: bus error (%d/%d)\n", __func__, transferred, len); "%s: bus error (%d/%d)\n", __func__, d - dst, len);
NCR5380_dprint(NDEBUG_PSEUDO_DMA, hostdata->host); NCR5380_dprint(NDEBUG_PSEUDO_DMA, hostdata->host);
d = dst + transferred; return -1;
n = len - transferred;
} }
scmd_printk(KERN_ERR, hostdata->connected, scmd_printk(KERN_ERR, hostdata->connected,
@ -192,93 +311,27 @@ static inline int macscsi_pread(struct NCR5380_hostdata *hostdata,
return -1; return -1;
} }
#define CP_MEM_TO_IO(s,d,n) \
__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" \
"91: moveq.l #1, %2\n" \
" jra 9b\n" \
"94: moveq.l #4, %2\n" \
" jra 9b\n" \
".previous\n" \
".section __ex_table,\"a\"\n" \
" .align 4\n" \
" .long 1b,91b\n" \
" .long 3b,94b\n" \
" .long 31b,94b\n" \
" .long 32b,94b\n" \
" .long 33b,94b\n" \
" .long 34b,94b\n" \
" .long 35b,94b\n" \
" .long 36b,94b\n" \
" .long 37b,94b\n" \
" .long 5b,94b\n" \
" .long 7b,91b\n" \
".previous" \
: "=a"(s), "=a"(d), "=d"(n) \
: "0"(s), "1"(d), "2"(n) \
: "d0")
static inline int macscsi_pwrite(struct NCR5380_hostdata *hostdata, static inline int macscsi_pwrite(struct NCR5380_hostdata *hostdata,
unsigned char *src, int len) unsigned char *src, int len)
{ {
unsigned char *s = src; unsigned char *s = src;
u8 __iomem *d = hostdata->pdma_io + (OUTPUT_DATA_REG << 4); u8 __iomem *d = hostdata->pdma_io + (OUTPUT_DATA_REG << 4);
int n = len;
int transferred; hostdata->pdma_residual = len;
while (!NCR5380_poll_politely(hostdata, BUS_AND_STATUS_REG, while (!NCR5380_poll_politely(hostdata, BUS_AND_STATUS_REG,
BASR_DRQ | BASR_PHASE_MATCH, BASR_DRQ | BASR_PHASE_MATCH,
BASR_DRQ | BASR_PHASE_MATCH, HZ / 64)) { BASR_DRQ | BASR_PHASE_MATCH, HZ / 64)) {
CP_MEM_TO_IO(s, d, n); int bytes;
transferred = s - src - n; bytes = mac_pdma_send(s, d, min(hostdata->pdma_residual, 512));
hostdata->pdma_residual = len - transferred;
/* Target changed phase early? */ if (bytes > 0) {
if (NCR5380_poll_politely2(hostdata, STATUS_REG, SR_REQ, SR_REQ, s += bytes;
BUS_AND_STATUS_REG, BASR_ACK, BASR_ACK, HZ / 64) < 0) hostdata->pdma_residual -= bytes;
scmd_printk(KERN_ERR, hostdata->connected, }
"%s: !REQ and !ACK\n", __func__);
if (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_PHASE_MATCH))
return 0;
/* No bus error. */ if (hostdata->pdma_residual == 0) {
if (n == 0) {
if (NCR5380_poll_politely(hostdata, TARGET_COMMAND_REG, if (NCR5380_poll_politely(hostdata, TARGET_COMMAND_REG,
TCR_LAST_BYTE_SENT, TCR_LAST_BYTE_SENT,
TCR_LAST_BYTE_SENT, HZ / 64) < 0) TCR_LAST_BYTE_SENT, HZ / 64) < 0)
@ -287,17 +340,29 @@ static inline int macscsi_pwrite(struct NCR5380_hostdata *hostdata,
return 0; return 0;
} }
if (NCR5380_poll_politely2(hostdata, STATUS_REG, SR_REQ, SR_REQ,
BUS_AND_STATUS_REG, BASR_ACK,
BASR_ACK, HZ / 64) < 0)
scmd_printk(KERN_DEBUG, hostdata->connected,
"%s: !REQ and !ACK\n", __func__);
if (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_PHASE_MATCH))
return 0;
if (bytes == 0)
udelay(MAC_PDMA_DELAY);
if (bytes >= 0)
continue;
dsprintk(NDEBUG_PSEUDO_DMA, hostdata->host, dsprintk(NDEBUG_PSEUDO_DMA, hostdata->host,
"%s: bus error (%d/%d)\n", __func__, transferred, len); "%s: bus error (%d/%d)\n", __func__, s - src, len);
NCR5380_dprint(NDEBUG_PSEUDO_DMA, hostdata->host); NCR5380_dprint(NDEBUG_PSEUDO_DMA, hostdata->host);
s = src + transferred; return -1;
n = len - transferred;
} }
scmd_printk(KERN_ERR, hostdata->connected, scmd_printk(KERN_ERR, hostdata->connected,
"%s: phase mismatch or !DRQ\n", __func__); "%s: phase mismatch or !DRQ\n", __func__);
NCR5380_dprint(NDEBUG_PSEUDO_DMA, hostdata->host); NCR5380_dprint(NDEBUG_PSEUDO_DMA, hostdata->host);
return -1; return -1;
} }