* [PATCH] scsi: arm: Convert acornscsi-io.S to C
2026-10-22 18:36 [PATCH 0/1] scsi: arm: Convert acornscsi-io.S to C Breno Rodrigues Alves
@ 2026-10-22 18:36 ` Breno Rodrigues Alves
2026-10-22 18:52 ` [PATCH v2 0/1] " Breno Rodrigues Alves
` (2 subsequent siblings)
3 siblings, 0 replies; 11+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-22 18:36 UTC (permalink / raw)
To: linux
Cc: James.Bottomley, mkp, linux-arm-kernel, linux-scsi, linux-kernel,
Breno Rodrigues Alves
The Acorn SCSI expansion card presents each 16-bit SCSI data halfword
on the low half of a 32-bit card access. The hand-written assembly
in acornscsi-io.S implements the two directions of the data path:
- __acornscsi_in() : packs pairs of 16-bit card reads into 32-bit
memory words
- __acornscsi_out() : expands 16-bit memory halfwords into 32-bit
card writes (duplicating the halfword in both
halves for the bulk of the transfer)
Both routines are simple pointer walks that a modern compiler handles
just as well as hand-written assembly, so replace the .S file with an
equivalent C implementation. The conversion also happens to handle
odd transfer lengths correctly: the original assembly drops the final
odd byte.
The C version takes its arguments as (void __iomem *, char *, int),
matching the prototype actually used by acornscsi.c. The assembly
version had no C prototype, so the original comment declaring the
first argument as unsigned int was never enforced; using the caller's
type removes that latent inconsistency and lets the MMIO accessors
do the right thing on any architecture.
No functional change is intended for the normal case of even-length
transfers, which is what all current callers of these helpers use.
No Makefile change is required: acornscsi_mod-objs already lists
acornscsi-io.o, and the build system will pick up the new .c file in
preference to the removed .S file.
Signed-off-by: Breno Rodrigues Alves <breno3011alves@gmail.com>
---
drivers/scsi/arm/acornscsi-io.S | 135 -------------------------
drivers/scsi/arm/acornscsi-io.c | 168 ++++++++++++++++++++++++++++++++
drivers/scsi/arm/acornscsi-io.h | 14 +++
drivers/scsi/arm/acornscsi.c | 4 +-
4 files changed, 184 insertions(+), 137 deletions(-)
delete mode 100644 drivers/scsi/arm/acornscsi-io.S
create mode 100644 drivers/scsi/arm/acornscsi-io.c
create mode 100644 drivers/scsi/arm/acornscsi-io.h
diff --git a/drivers/scsi/arm/acornscsi-io.S b/drivers/scsi/arm/acornscsi-io.S
deleted file mode 100644
index fdd7237bb..000000000
--- a/drivers/scsi/arm/acornscsi-io.S
+++ /dev/null
@@ -1,135 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * linux/drivers/acorn/scsi/acornscsi-io.S: Acorn SCSI card IO
- */
-#include <linux/linkage.h>
-
-#include <asm/assembler.h>
-#include <mach/hardware.h>
-
-#if defined(__APCS_32__)
-#define LOADREGS(t,r,l...) ldm##t r, l
-#elif defined(__APCS_26__)
-#define LOADREGS(t,r,l...) ldm##t r, l##^
-#endif
-
-@ Purpose: transfer a block of data from the acorn scsi card to memory
-@ Proto : void acornscsi_in(unsigned int addr_start, char *buffer, int length)
-@ Returns: nothing
-
- .align
-ENTRY(__acornscsi_in)
- stmfd sp!, {r4 - r7, lr}
- bic r0, r0, #3
- mov lr, #0xff
- orr lr, lr, #0xff00
-acornscsi_in16lp:
- subs r2, r2, #16
- bmi acornscsi_in8
- ldmia r0!, {r3, r4, r5, r6}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- and r4, r5, lr
- orr r4, r4, r6, lsl #16
- ldmia r0!, {r5, r6, r7, ip}
- and r5, r5, lr
- orr r5, r5, r6, lsl #16
- and r6, r7, lr
- orr r6, r6, ip, lsl #16
- stmia r1!, {r3 - r6}
- bne acornscsi_in16lp
- LOADREGS(fd, sp!, {r4 - r7, pc})
-
-acornscsi_in8: adds r2, r2, #8
- bmi acornscsi_in4
- ldmia r0!, {r3, r4, r5, r6}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- and r4, r5, lr
- orr r4, r4, r6, lsl #16
- stmia r1!, {r3 - r4}
- LOADREGS(eqfd, sp!, {r4 - r7, pc})
- sub r2, r2, #8
-
-acornscsi_in4: adds r2, r2, #4
- bmi acornscsi_in2
- ldmia r0!, {r3, r4}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- str r3, [r1], #4
- LOADREGS(eqfd, sp!, {r4 - r7, pc})
- sub r2, r2, #4
-
-acornscsi_in2: adds r2, r2, #2
- ldr r3, [r0], #4
- and r3, r3, lr
- strb r3, [r1], #1
- mov r3, r3, lsr #8
- strplb r3, [r1], #1
- LOADREGS(fd, sp!, {r4 - r7, pc})
-
-@ Purpose: transfer a block of data from memory to the acorn scsi card
-@ Proto : void acornscsi_in(unsigned int addr_start, char *buffer, int length)
-@ Returns: nothing
-
-ENTRY(__acornscsi_out)
- stmfd sp!, {r4 - r6, lr}
- bic r0, r0, #3
-acornscsi_out16lp:
- subs r2, r2, #16
- bmi acornscsi_out8
- ldmia r1!, {r4, r6, ip, lr}
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- mov r5, r6, lsl #16
- orr r5, r5, r5, lsr #16
- mov r6, r6, lsr #16
- orr r6, r6, r6, lsl #16
- stmia r0!, {r3, r4, r5, r6}
- mov r3, ip, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, ip, lsr #16
- orr r4, r4, r4, lsl #16
- mov ip, lr, lsl #16
- orr ip, ip, ip, lsr #16
- mov lr, lr, lsr #16
- orr lr, lr, lr, lsl #16
- stmia r0!, {r3, r4, ip, lr}
- bne acornscsi_out16lp
- LOADREGS(fd, sp!, {r4 - r6, pc})
-
-acornscsi_out8: adds r2, r2, #8
- bmi acornscsi_out4
- ldmia r1!, {r4, r6}
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- mov r5, r6, lsl #16
- orr r5, r5, r5, lsr #16
- mov r6, r6, lsr #16
- orr r6, r6, r6, lsl #16
- stmia r0!, {r3, r4, r5, r6}
- LOADREGS(eqfd, sp!, {r4 - r6, pc})
-
- sub r2, r2, #8
-acornscsi_out4: adds r2, r2, #4
- bmi acornscsi_out2
- ldr r4, [r1], #4
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- stmia r0!, {r3, r4}
- LOADREGS(eqfd, sp!, {r4 - r6, pc})
-
- sub r2, r2, #4
-acornscsi_out2: adds r2, r2, #2
- ldr r3, [r1], #2
- strb r3, [r0], #1
- mov r3, r3, lsr #8
- strplb r3, [r0], #1
- LOADREGS(fd, sp!, {r4 - r6, pc})
-
diff --git a/drivers/scsi/arm/acornscsi-io.c b/drivers/scsi/arm/acornscsi-io.c
new file mode 100644
index 000000000..ad16d984d
--- /dev/null
+++ b/drivers/scsi/arm/acornscsi-io.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Acorn SCSI card I/O
+ *
+ * The Acorn SCSI expansion card exposes each 16-bit SCSI data
+ * halfword on the low half of a 32-bit card access:
+ *
+ * - on reads, one 16-bit value is returned per 32-bit card load;
+ * consecutive card reads are packed into 32-bit memory words
+ *
+ * - on writes, a 16-bit memory halfword is expanded into a 32-bit
+ * card word. The original assembly duplicates the halfword into
+ * both halves of the card word for the bulk of the transfer and
+ * writes only the low 16 bits of the trailing partial word,
+ * since the hardware samples only the low half.
+ */
+
+#include <linux/align.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include "acornscsi-io.h"
+
+/*
+ * Transfer a block of data from the Acorn SCSI card into memory.
+ *
+ * The card address is rounded down to a 32-bit boundary. Each
+ * 32-bit read from the card yields one valid 16-bit halfword in
+ * the low half; pairs of card reads are packed into single 32-bit
+ * memory words.
+ */
+void __acornscsi_in(void __iomem *addr, char *buffer, int length)
+{
+ const u32 __iomem *card = PTR_ALIGN((const u32 __iomem *)addr, 4);
+ u8 *buf = (u8 *)buffer;
+
+ while (length >= 16) {
+ u32 w0 = readl(card + 0), w1 = readl(card + 1);
+ u32 w2 = readl(card + 2), w3 = readl(card + 3);
+ u32 w4 = readl(card + 4), w5 = readl(card + 5);
+ u32 w6 = readl(card + 6), w7 = readl(card + 7);
+
+ card += 8;
+
+ ((u32 *)buf)[0] = (w0 & 0xffff) | (w1 << 16);
+ ((u32 *)buf)[1] = (w2 & 0xffff) | (w3 << 16);
+ ((u32 *)buf)[2] = (w4 & 0xffff) | (w5 << 16);
+ ((u32 *)buf)[3] = (w6 & 0xffff) | (w7 << 16);
+
+ buf += 16;
+ length -= 16;
+ }
+
+ while (length >= 8) {
+ u32 w0 = readl(card + 0), w1 = readl(card + 1);
+ u32 w2 = readl(card + 2), w3 = readl(card + 3);
+
+ card += 4;
+
+ ((u32 *)buf)[0] = (w0 & 0xffff) | (w1 << 16);
+ ((u32 *)buf)[1] = (w2 & 0xffff) | (w3 << 16);
+
+ buf += 8;
+ length -= 8;
+ }
+
+ while (length >= 4) {
+ u32 w0 = readl(card + 0), w1 = readl(card + 1);
+
+ card += 2;
+
+ ((u32 *)buf)[0] = (w0 & 0xffff) | (w1 << 16);
+
+ buf += 4;
+ length -= 4;
+ }
+
+ while (length >= 2) {
+ u32 w0 = readl(card);
+
+ card++;
+
+ buf[0] = w0 & 0xff;
+ buf[1] = (w0 >> 8) & 0xff;
+
+ buf += 2;
+ length -= 2;
+ }
+
+ if (length >= 1) {
+ u32 w0 = readl(card);
+
+ buf[0] = w0 & 0xff;
+ }
+}
+
+/*
+ * Transfer a block of data from memory to the Acorn SCSI card.
+ *
+ * Each 16-bit value read from memory is expanded into a 32-bit card
+ * word, with the halfword duplicated in both halves (matching the
+ * original assembly). For the trailing partial halfword the card
+ * only samples the low 16 bits, so two byte stores are sufficient.
+ */
+void __acornscsi_out(void __iomem *addr, char *buffer, int length)
+{
+ u32 __iomem *card = PTR_ALIGN((u32 __iomem *)addr, 4);
+ const u8 *buf = (const u8 *)buffer;
+
+ while (length >= 16) {
+ u32 v0 = ((const u32 *)buf)[0];
+ u32 v1 = ((const u32 *)buf)[1];
+ u32 v2 = ((const u32 *)buf)[2];
+ u32 v3 = ((const u32 *)buf)[3];
+
+ writel((v0 & 0xffff) | ((v0 & 0xffff) << 16), card + 0);
+ writel((v0 >> 16) | ((v0 >> 16) << 16), card + 1);
+ writel((v1 & 0xffff) | ((v1 & 0xffff) << 16), card + 2);
+ writel((v1 >> 16) | ((v1 >> 16) << 16), card + 3);
+ writel((v2 & 0xffff) | ((v2 & 0xffff) << 16), card + 4);
+ writel((v2 >> 16) | ((v2 >> 16) << 16), card + 5);
+ writel((v3 & 0xffff) | ((v3 & 0xffff) << 16), card + 6);
+ writel((v3 >> 16) | ((v3 >> 16) << 16), card + 7);
+
+ card += 8;
+ buf += 16;
+ length -= 16;
+ }
+
+ while (length >= 8) {
+ u32 v0 = ((const u32 *)buf)[0];
+ u32 v1 = ((const u32 *)buf)[1];
+
+ writel((v0 & 0xffff) | ((v0 & 0xffff) << 16), card + 0);
+ writel((v0 >> 16) | ((v0 >> 16) << 16), card + 1);
+ writel((v1 & 0xffff) | ((v1 & 0xffff) << 16), card + 2);
+ writel((v1 >> 16) | ((v1 >> 16) << 16), card + 3);
+
+ card += 4;
+ buf += 8;
+ length -= 8;
+ }
+
+ while (length >= 4) {
+ u32 v0 = ((const u32 *)buf)[0];
+
+ writel((v0 & 0xffff) | ((v0 & 0xffff) << 16), card + 0);
+ writel((v0 >> 16) | ((v0 >> 16) << 16), card + 1);
+
+ card += 2;
+ buf += 4;
+ length -= 4;
+ }
+
+ while (length >= 2) {
+ u16 v = *(const u16 *)buf;
+ void __iomem *cp = (void __iomem *)card;
+
+ writeb(v & 0xff, cp);
+ writeb(v >> 8, cp + 1);
+
+ card = (u32 __iomem *)(cp + 2);
+ buf += 2;
+ length -= 2;
+ }
+
+ if (length >= 1)
+ writeb(*buf, (void __iomem *)card);
+}
diff --git a/drivers/scsi/arm/acornscsi-io.h b/drivers/scsi/arm/acornscsi-io.h
new file mode 100644
index 000000000..64acfb8bb
--- /dev/null
+++ b/drivers/scsi/arm/acornscsi-io.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Acorn SCSI card I/O
+ */
+
+#ifndef _ACORNSCSI_IO_H
+#define _ACORNSCSI_IO_H
+
+#include <linux/io.h>
+
+void __acornscsi_in(void __iomem *addr, char *buffer, int length);
+void __acornscsi_out(void __iomem *addr, char *buffer, int length);
+
+#endif /* _ACORNSCSI_IO_H */
diff --git a/drivers/scsi/arm/acornscsi.c b/drivers/scsi/arm/acornscsi.c
index 79d7d7336..263a1bf24 100644
--- a/drivers/scsi/arm/acornscsi.c
+++ b/drivers/scsi/arm/acornscsi.c
@@ -140,6 +140,8 @@
#include <scsi/scsicam.h>
+#include "acornscsi-io.h"
+
#define VER_MAJOR 2
#define VER_MINOR 0
#define VER_PATCH 6
@@ -886,7 +888,6 @@ static
void acornscsi_data_read(AS_Host *host, char *ptr,
unsigned int start_addr, unsigned int length)
{
- extern void __acornscsi_in(void __iomem *, char *buf, int len);
unsigned int page, offset, len = length;
page = (start_addr >> 12);
@@ -931,7 +932,6 @@ static
void acornscsi_data_write(AS_Host *host, char *ptr,
unsigned int start_addr, unsigned int length)
{
- extern void __acornscsi_out(void __iomem *, char *buf, int len);
unsigned int page, offset, len = length;
page = (start_addr >> 12);
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v2 0/1] scsi: arm: Convert acornscsi-io.S to C
2026-10-22 18:36 [PATCH 0/1] scsi: arm: Convert acornscsi-io.S to C Breno Rodrigues Alves
2026-10-22 18:36 ` [PATCH] " Breno Rodrigues Alves
@ 2026-10-22 18:52 ` Breno Rodrigues Alves
2026-10-22 18:52 ` [PATCH v2] " Breno Rodrigues Alves
2026-10-22 20:30 ` [PATCH v3] " Breno Rodrigues Alves
2026-10-22 21:02 ` [PATCH v4] " Breno Rodrigues Alves
3 siblings, 1 reply; 11+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-22 18:52 UTC (permalink / raw)
To: linux
Cc: James.Bottomley, mkp, linux-arm-kernel, linux-scsi, linux-kernel,
Breno Rodrigues Alves
v2 addresses two issues raised by the sashiko bot:
- PTR_ALIGN() rounds up, but the assembly's "bic r0, r0, #3"
rounds down. Use PTR_ALIGN_DOWN() to match, otherwise an odd
on-card offset shifts the entire transfer by one 16-bit halfword
and can walk past the 4 KiB card window.
- In __acornscsi_out(), the trailing byte loop advanced by 2 bytes
into the unused upper half of the current card word. Advance by
a full 32-bit word (card++) so the trailing byte lands in the
low half of the next card word, which is where the hardware
actually samples it.
No functional change for even-length transfers. The odd-length
behaviour is now correct for both directions.
Breno (1):
scsi: arm: Convert acornscsi-io.S to C
drivers/scsi/arm/acornscsi-io.S | 116 ------------
drivers/scsi/arm/acornscsi-io.c | 168 ++++++++++++++++++++++
drivers/scsi/arm/acornscsi-io.h | 15 +++
drivers/scsi/arm/acornscsi.c | 3 +-
4 files changed, 184 insertions(+), 137 deletions(-)
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v2] scsi: arm: Convert acornscsi-io.S to C
2026-10-22 18:52 ` [PATCH v2 0/1] " Breno Rodrigues Alves
@ 2026-10-22 18:52 ` Breno Rodrigues Alves
2026-10-22 19:22 ` Bart Van Assche
0 siblings, 1 reply; 11+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-22 18:52 UTC (permalink / raw)
To: linux
Cc: James.Bottomley, mkp, linux-arm-kernel, linux-scsi, linux-kernel,
Breno Rodrigues Alves
The Acorn SCSI expansion card presents each 16-bit SCSI data halfword
on the low half of a 32-bit card access. The hand-written assembly
in acornscsi-io.S implements the two directions of the data path:
- __acornscsi_in() : packs pairs of 16-bit card reads into 32-bit
memory words
- __acornscsi_out() : expands 16-bit memory halfwords into 32-bit
card writes (duplicating the halfword in both
halves for the bulk of the transfer)
Both routines are simple pointer walks that a modern compiler handles
just as well as hand-written assembly, so replace the .S file with an
equivalent C implementation.
The C version takes its arguments as (void __iomem *, char *, int),
matching the prototype actually used by acornscsi.c. The assembly
version had no C prototype, so the original comment declaring the
first argument as unsigned int was never enforced; using the caller's
type removes that latent inconsistency and lets the MMIO accessors
do the right thing on any architecture.
Note on odd transfer lengths: the original assembly dropped the final
odd byte when the tail reached the two-byte handler, leaving the byte
unwritten (out) or unread (in). The C version advances the MMIO
pointer by a full 32-bit card word in the trailing loop, so the
remaining odd byte is transferred through the low half of the next
card word, which is where the hardware actually samples it. Callers
in acornscsi.c pass even lengths in practice, so this is a latent-bug
fix, not a behaviour change that affects existing systems.
No Makefile change is required: acornscsi_mod-objs already lists
acornscsi-io.o, and the build system will pick up the new .c file in
preference to the removed .S file.
Signed-off-by: Breno Rodrigues Alves <breno3011alves@gmail.com>
---
drivers/scsi/arm/acornscsi-io.S | 135 -------------------------
drivers/scsi/arm/acornscsi-io.c | 168 ++++++++++++++++++++++++++++++++
drivers/scsi/arm/acornscsi-io.h | 14 +++
drivers/scsi/arm/acornscsi.c | 4 +-
4 files changed, 184 insertions(+), 137 deletions(-)
delete mode 100644 drivers/scsi/arm/acornscsi-io.S
create mode 100644 drivers/scsi/arm/acornscsi-io.c
create mode 100644 drivers/scsi/arm/acornscsi-io.h
diff --git a/drivers/scsi/arm/acornscsi-io.S b/drivers/scsi/arm/acornscsi-io.S
deleted file mode 100644
index fdd7237bb..000000000
--- a/drivers/scsi/arm/acornscsi-io.S
+++ /dev/null
@@ -1,135 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * linux/drivers/acorn/scsi/acornscsi-io.S: Acorn SCSI card IO
- */
-#include <linux/linkage.h>
-
-#include <asm/assembler.h>
-#include <mach/hardware.h>
-
-#if defined(__APCS_32__)
-#define LOADREGS(t,r,l...) ldm##t r, l
-#elif defined(__APCS_26__)
-#define LOADREGS(t,r,l...) ldm##t r, l##^
-#endif
-
-@ Purpose: transfer a block of data from the acorn scsi card to memory
-@ Proto : void acornscsi_in(unsigned int addr_start, char *buffer, int length)
-@ Returns: nothing
-
- .align
-ENTRY(__acornscsi_in)
- stmfd sp!, {r4 - r7, lr}
- bic r0, r0, #3
- mov lr, #0xff
- orr lr, lr, #0xff00
-acornscsi_in16lp:
- subs r2, r2, #16
- bmi acornscsi_in8
- ldmia r0!, {r3, r4, r5, r6}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- and r4, r5, lr
- orr r4, r4, r6, lsl #16
- ldmia r0!, {r5, r6, r7, ip}
- and r5, r5, lr
- orr r5, r5, r6, lsl #16
- and r6, r7, lr
- orr r6, r6, ip, lsl #16
- stmia r1!, {r3 - r6}
- bne acornscsi_in16lp
- LOADREGS(fd, sp!, {r4 - r7, pc})
-
-acornscsi_in8: adds r2, r2, #8
- bmi acornscsi_in4
- ldmia r0!, {r3, r4, r5, r6}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- and r4, r5, lr
- orr r4, r4, r6, lsl #16
- stmia r1!, {r3 - r4}
- LOADREGS(eqfd, sp!, {r4 - r7, pc})
- sub r2, r2, #8
-
-acornscsi_in4: adds r2, r2, #4
- bmi acornscsi_in2
- ldmia r0!, {r3, r4}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- str r3, [r1], #4
- LOADREGS(eqfd, sp!, {r4 - r7, pc})
- sub r2, r2, #4
-
-acornscsi_in2: adds r2, r2, #2
- ldr r3, [r0], #4
- and r3, r3, lr
- strb r3, [r1], #1
- mov r3, r3, lsr #8
- strplb r3, [r1], #1
- LOADREGS(fd, sp!, {r4 - r7, pc})
-
-@ Purpose: transfer a block of data from memory to the acorn scsi card
-@ Proto : void acornscsi_in(unsigned int addr_start, char *buffer, int length)
-@ Returns: nothing
-
-ENTRY(__acornscsi_out)
- stmfd sp!, {r4 - r6, lr}
- bic r0, r0, #3
-acornscsi_out16lp:
- subs r2, r2, #16
- bmi acornscsi_out8
- ldmia r1!, {r4, r6, ip, lr}
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- mov r5, r6, lsl #16
- orr r5, r5, r5, lsr #16
- mov r6, r6, lsr #16
- orr r6, r6, r6, lsl #16
- stmia r0!, {r3, r4, r5, r6}
- mov r3, ip, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, ip, lsr #16
- orr r4, r4, r4, lsl #16
- mov ip, lr, lsl #16
- orr ip, ip, ip, lsr #16
- mov lr, lr, lsr #16
- orr lr, lr, lr, lsl #16
- stmia r0!, {r3, r4, ip, lr}
- bne acornscsi_out16lp
- LOADREGS(fd, sp!, {r4 - r6, pc})
-
-acornscsi_out8: adds r2, r2, #8
- bmi acornscsi_out4
- ldmia r1!, {r4, r6}
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- mov r5, r6, lsl #16
- orr r5, r5, r5, lsr #16
- mov r6, r6, lsr #16
- orr r6, r6, r6, lsl #16
- stmia r0!, {r3, r4, r5, r6}
- LOADREGS(eqfd, sp!, {r4 - r6, pc})
-
- sub r2, r2, #8
-acornscsi_out4: adds r2, r2, #4
- bmi acornscsi_out2
- ldr r4, [r1], #4
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- stmia r0!, {r3, r4}
- LOADREGS(eqfd, sp!, {r4 - r6, pc})
-
- sub r2, r2, #4
-acornscsi_out2: adds r2, r2, #2
- ldr r3, [r1], #2
- strb r3, [r0], #1
- mov r3, r3, lsr #8
- strplb r3, [r0], #1
- LOADREGS(fd, sp!, {r4 - r6, pc})
-
diff --git a/drivers/scsi/arm/acornscsi-io.c b/drivers/scsi/arm/acornscsi-io.c
new file mode 100644
index 000000000..d882441ea
--- /dev/null
+++ b/drivers/scsi/arm/acornscsi-io.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Acorn SCSI card I/O
+ *
+ * The Acorn SCSI expansion card exposes each 16-bit SCSI data
+ * halfword on the low half of a 32-bit card access:
+ *
+ * - on reads, one 16-bit value is returned per 32-bit card load;
+ * consecutive card reads are packed into 32-bit memory words
+ *
+ * - on writes, a 16-bit memory halfword is expanded into a 32-bit
+ * card word. The original assembly duplicates the halfword into
+ * both halves of the card word for the bulk of the transfer and
+ * writes only the low 16 bits of the trailing partial word,
+ * since the hardware samples only the low half.
+ */
+
+#include <linux/align.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include "acornscsi-io.h"
+
+/*
+ * Transfer a block of data from the Acorn SCSI card into memory.
+ *
+ * The card address is rounded down to a 32-bit boundary. Each
+ * 32-bit read from the card yields one valid 16-bit halfword in
+ * the low half; pairs of card reads are packed into single 32-bit
+ * memory words.
+ */
+void __acornscsi_in(void __iomem *addr, char *buffer, int length)
+{
+ const u32 __iomem *card = PTR_ALIGN_DOWN((const u32 __iomem *)addr, 4);
+ u8 *buf = (u8 *)buffer;
+
+ while (length >= 16) {
+ u32 w0 = readl(card + 0), w1 = readl(card + 1);
+ u32 w2 = readl(card + 2), w3 = readl(card + 3);
+ u32 w4 = readl(card + 4), w5 = readl(card + 5);
+ u32 w6 = readl(card + 6), w7 = readl(card + 7);
+
+ card += 8;
+
+ ((u32 *)buf)[0] = (w0 & 0xffff) | (w1 << 16);
+ ((u32 *)buf)[1] = (w2 & 0xffff) | (w3 << 16);
+ ((u32 *)buf)[2] = (w4 & 0xffff) | (w5 << 16);
+ ((u32 *)buf)[3] = (w6 & 0xffff) | (w7 << 16);
+
+ buf += 16;
+ length -= 16;
+ }
+
+ while (length >= 8) {
+ u32 w0 = readl(card + 0), w1 = readl(card + 1);
+ u32 w2 = readl(card + 2), w3 = readl(card + 3);
+
+ card += 4;
+
+ ((u32 *)buf)[0] = (w0 & 0xffff) | (w1 << 16);
+ ((u32 *)buf)[1] = (w2 & 0xffff) | (w3 << 16);
+
+ buf += 8;
+ length -= 8;
+ }
+
+ while (length >= 4) {
+ u32 w0 = readl(card + 0), w1 = readl(card + 1);
+
+ card += 2;
+
+ ((u32 *)buf)[0] = (w0 & 0xffff) | (w1 << 16);
+
+ buf += 4;
+ length -= 4;
+ }
+
+ while (length >= 2) {
+ u32 w0 = readl(card);
+
+ card++;
+
+ buf[0] = w0 & 0xff;
+ buf[1] = (w0 >> 8) & 0xff;
+
+ buf += 2;
+ length -= 2;
+ }
+
+ if (length >= 1) {
+ u32 w0 = readl(card);
+
+ buf[0] = w0 & 0xff;
+ }
+}
+
+/*
+ * Transfer a block of data from memory to the Acorn SCSI card.
+ *
+ * Each 16-bit value read from memory is expanded into a 32-bit card
+ * word, with the halfword duplicated in both halves (matching the
+ * original assembly). For the trailing partial halfword the card
+ * only samples the low 16 bits, so two byte stores are sufficient.
+ */
+void __acornscsi_out(void __iomem *addr, char *buffer, int length)
+{
+ u32 __iomem *card = PTR_ALIGN_DOWN((u32 __iomem *)addr, 4);
+ const u8 *buf = (const u8 *)buffer;
+
+ while (length >= 16) {
+ u32 v0 = ((const u32 *)buf)[0];
+ u32 v1 = ((const u32 *)buf)[1];
+ u32 v2 = ((const u32 *)buf)[2];
+ u32 v3 = ((const u32 *)buf)[3];
+
+ writel((v0 & 0xffff) | ((v0 & 0xffff) << 16), card + 0);
+ writel((v0 >> 16) | ((v0 >> 16) << 16), card + 1);
+ writel((v1 & 0xffff) | ((v1 & 0xffff) << 16), card + 2);
+ writel((v1 >> 16) | ((v1 >> 16) << 16), card + 3);
+ writel((v2 & 0xffff) | ((v2 & 0xffff) << 16), card + 4);
+ writel((v2 >> 16) | ((v2 >> 16) << 16), card + 5);
+ writel((v3 & 0xffff) | ((v3 & 0xffff) << 16), card + 6);
+ writel((v3 >> 16) | ((v3 >> 16) << 16), card + 7);
+
+ card += 8;
+ buf += 16;
+ length -= 16;
+ }
+
+ while (length >= 8) {
+ u32 v0 = ((const u32 *)buf)[0];
+ u32 v1 = ((const u32 *)buf)[1];
+
+ writel((v0 & 0xffff) | ((v0 & 0xffff) << 16), card + 0);
+ writel((v0 >> 16) | ((v0 >> 16) << 16), card + 1);
+ writel((v1 & 0xffff) | ((v1 & 0xffff) << 16), card + 2);
+ writel((v1 >> 16) | ((v1 >> 16) << 16), card + 3);
+
+ card += 4;
+ buf += 8;
+ length -= 8;
+ }
+
+ while (length >= 4) {
+ u32 v0 = ((const u32 *)buf)[0];
+
+ writel((v0 & 0xffff) | ((v0 & 0xffff) << 16), card + 0);
+ writel((v0 >> 16) | ((v0 >> 16) << 16), card + 1);
+
+ card += 2;
+ buf += 4;
+ length -= 4;
+ }
+
+ while (length >= 2) {
+ u16 v = *(const u16 *)buf;
+ void __iomem *cp = (void __iomem *)card;
+
+ writeb(v & 0xff, cp);
+ writeb(v >> 8, cp + 1);
+
+ card++;
+ buf += 2;
+ length -= 2;
+ }
+
+ if (length >= 1)
+ writeb(*buf, (void __iomem *)card);
+}
diff --git a/drivers/scsi/arm/acornscsi-io.h b/drivers/scsi/arm/acornscsi-io.h
new file mode 100644
index 000000000..64acfb8bb
--- /dev/null
+++ b/drivers/scsi/arm/acornscsi-io.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Acorn SCSI card I/O
+ */
+
+#ifndef _ACORNSCSI_IO_H
+#define _ACORNSCSI_IO_H
+
+#include <linux/io.h>
+
+void __acornscsi_in(void __iomem *addr, char *buffer, int length);
+void __acornscsi_out(void __iomem *addr, char *buffer, int length);
+
+#endif /* _ACORNSCSI_IO_H */
diff --git a/drivers/scsi/arm/acornscsi.c b/drivers/scsi/arm/acornscsi.c
index 79d7d7336..263a1bf24 100644
--- a/drivers/scsi/arm/acornscsi.c
+++ b/drivers/scsi/arm/acornscsi.c
@@ -140,6 +140,8 @@
#include <scsi/scsicam.h>
+#include "acornscsi-io.h"
+
#define VER_MAJOR 2
#define VER_MINOR 0
#define VER_PATCH 6
@@ -886,7 +888,6 @@ static
void acornscsi_data_read(AS_Host *host, char *ptr,
unsigned int start_addr, unsigned int length)
{
- extern void __acornscsi_in(void __iomem *, char *buf, int len);
unsigned int page, offset, len = length;
page = (start_addr >> 12);
@@ -931,7 +932,6 @@ static
void acornscsi_data_write(AS_Host *host, char *ptr,
unsigned int start_addr, unsigned int length)
{
- extern void __acornscsi_out(void __iomem *, char *buf, int len);
unsigned int page, offset, len = length;
page = (start_addr >> 12);
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH v2] scsi: arm: Convert acornscsi-io.S to C
2026-10-22 18:52 ` [PATCH v2] " Breno Rodrigues Alves
@ 2026-10-22 19:22 ` Bart Van Assche
2026-10-22 20:30 ` Breno Rodrigues Alves
2026-10-22 21:02 ` Breno Rodrigues Alves
0 siblings, 2 replies; 11+ messages in thread
From: Bart Van Assche @ 2026-10-22 19:22 UTC (permalink / raw)
To: Breno Rodrigues Alves, linux
Cc: James.Bottomley, mkp, linux-arm-kernel, linux-scsi, linux-kernel
On 10/22/26 11:52 AM, Breno Rodrigues Alves wrote:
> + while (length >= 16) {
> + u32 w0 = readl(card + 0), w1 = readl(card + 1);
> + u32 w2 = readl(card + 2), w3 = readl(card + 3);
> + u32 w4 = readl(card + 4), w5 = readl(card + 5);
> + u32 w6 = readl(card + 6), w7 = readl(card + 7);
> +
> + card += 8;
> +
> + ((u32 *)buf)[0] = (w0 & 0xffff) | (w1 << 16);
> + ((u32 *)buf)[1] = (w2 & 0xffff) | (w3 << 16);
> + ((u32 *)buf)[2] = (w4 & 0xffff) | (w5 << 16);
> + ((u32 *)buf)[3] = (w6 & 0xffff) | (w7 << 16);
> +
> + buf += 16;
> + length -= 16;
> + }
Why eight readl() calls instead of a loop? Additionally, please use
put_unaligned_be32() instead of open-coding it. Is the code below
equivalent to the loop above?
while (length >= 16) {
for (int i = 0; i < 4; i++) {
u16 w0 = readl(card + 0);
u32 w1 = readl(card + 1);
put_unaligned_be32(w0 | (w1 << 16), buf);
card += 2;
buf += 4;
}
length -= 16;
}
Thanks,
Bart.
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH v2] scsi: arm: Convert acornscsi-io.S to C
2026-10-22 19:22 ` Bart Van Assche
@ 2026-10-22 20:30 ` Breno Rodrigues Alves
2026-10-22 21:02 ` Breno Rodrigues Alves
1 sibling, 0 replies; 11+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-22 20:30 UTC (permalink / raw)
To: Bart Van Assche
Cc: linux, James.Bottomley, mkp, linux-arm-kernel, linux-scsi,
linux-kernel, Breno Rodrigues Alves
Hi Bart,
Thanks for the review.
> Why eight readl() calls instead of a loop?
The eight readl() calls mirror the assembly's unrolled 16-byte body
(4x ldmia + 4x stmia over 16 bytes). Collapsed into a loop in v3 —
the compiler unrolls it for the aligned case.
> Additionally, please use put_unaligned_be32() instead of open-coding
> it. Is the code below equivalent to the loop above?
Almost, but not quite — two subtleties:
1. w0 is declared as u16, which truncates the readl() result. It
happens to work because the compose step only uses the low 16
bits, but u32 is less surprising. Fixed in v3.
2. put_unaligned_be32() would byte-swap relative to the original
assembly. The asm did a plain 32-bit store:
and r3, r3, lr ; r3 = w0 & 0xffff
orr r3, r3, r4, lsl #16 ; r3 |= w1 << 16
str r3, [r1] ; plain 32-bit store
On little-endian ARM that writes [w0_lo, w0_hi, w1_lo, w1_hi],
i.e. put_unaligned_le32() semantics. put_unaligned_be32()
writes [w1_hi, w1_lo, w0_hi, w0_lo] — reversed.
acornscsi_data_read() feeds SCSI payload bytes verbatim into the
upper layer, so that reversal would corrupt the transferred data.
v3 uses put_unaligned_le32() / get_unaligned_le32() / le16().
Note: <asm/unaligned.h> was removed in 6.12; the include is now
<linux/unaligned.h>.
v3 follows as a reply to this message. The 16-, 8- and 4-byte bodies
are now a single 4-byte loop per direction.
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH v2] scsi: arm: Convert acornscsi-io.S to C
2026-10-22 19:22 ` Bart Van Assche
2026-10-22 20:30 ` Breno Rodrigues Alves
@ 2026-10-22 21:02 ` Breno Rodrigues Alves
1 sibling, 0 replies; 11+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-22 21:02 UTC (permalink / raw)
To: Bart Van Assche
Cc: linux, James.Bottomley, mkp, linux-arm-kernel, linux-scsi,
linux-kernel, Breno Rodrigues Alves
Hi Bart,
> Shouldn't this "while" be changed into "if"?
Yes. After the preceding while (length >= 4) loop, length is in
{0, 1, 2, 3}, so the trailing loop runs at most once. The same
applies to the corresponding loop in __acornscsi_in(); both are now
'if' in v4.
> The above code can be simplified as shown below, isn't it?
>
> void __iomem *cp = (void __iomem *)card;
>
> writeb(buf[0], cp);
> writeb(buf[1], cp + 1);
Agreed. v4 drops get_unaligned_le16() in __acornscsi_out() and
writes the two bytes directly. Note that __acornscsi_in() still
needs put_unaligned_le16() because the bytes come from the low
half of the 32-bit card word and buf may be unaligned.
> Isn't the above (void __iomem *) cast superfluous?
Yes. u32 __iomem * converts implicitly to void __iomem *. Removed
in v4. The cast on the earlier 'cp' variable is kept because cp is
used for byte-level pointer arithmetic (cp + 1 must advance one
byte, not one 32-bit word).
v4 follows.
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v3] scsi: arm: Convert acornscsi-io.S to C
2026-10-22 18:36 [PATCH 0/1] scsi: arm: Convert acornscsi-io.S to C Breno Rodrigues Alves
2026-10-22 18:36 ` [PATCH] " Breno Rodrigues Alves
2026-10-22 18:52 ` [PATCH v2 0/1] " Breno Rodrigues Alves
@ 2026-10-22 20:30 ` Breno Rodrigues Alves
2026-10-22 20:49 ` Bart Van Assche
2026-10-22 21:02 ` [PATCH v4] " Breno Rodrigues Alves
3 siblings, 1 reply; 11+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-22 20:30 UTC (permalink / raw)
To: linux
Cc: Bart Van Assche, James.Bottomley, mkp, linux-arm-kernel,
linux-scsi, linux-kernel, Breno Rodrigues Alves
The Acorn SCSI expansion card presents each 16-bit SCSI data halfword
on the low half of a 32-bit card access. The hand-written assembly
in acornscsi-io.S implements the two directions of the data path:
- __acornscsi_in() : packs pairs of 16-bit card reads into 32-bit
memory words
- __acornscsi_out() : expands 16-bit memory halfwords into 32-bit
card writes (duplicating the halfword in both
halves for the bulk of the transfer)
Both routines are simple pointer walks that a modern compiler handles
just as well as hand-written assembly, so replace the .S file with an
equivalent C implementation.
The C version takes its arguments as (void __iomem *, char *, int),
matching the prototype actually used by acornscsi.c. The assembly
version had no C prototype, so the original comment declaring the
first argument as unsigned int was never enforced; using the caller's
type removes that latent inconsistency and lets the MMIO accessors
do the right thing on any architecture.
The 16-, 8- and 4-byte unrolled bodies from the assembly are folded
into a single 4-byte loop per direction and expressed with
put_unaligned_le32() / get_unaligned_le32(), which the compiler
unrolls exactly as before. The LE variants are used because the
assembly performed a plain 32-bit store on little-endian ARM, which
emits bytes in little-endian order and is what the SCSI payload
expects.
Note on odd transfer lengths: the original assembly dropped the final
odd byte when the tail reached the two-byte handler, leaving the byte
unwritten (out) or unread (in). The C version advances the MMIO
pointer by a full 32-bit card word in the trailing loop, so the
remaining odd byte is transferred through the low half of the next
card word, which is where the hardware actually samples it. Callers
in acornscsi.c pass even lengths in practice, so this is a latent-bug
fix, not a behaviour change that affects existing systems.
No Makefile change is required: acornscsi_mod-objs already lists
acornscsi-io.o, and the build system will pick up the new .c file in
preference to the removed .S file.
Signed-off-by: Breno Rodrigues Alves <breno3011alves@gmail.com>
---
drivers/scsi/arm/acornscsi-io.S | 135 --------------------------------
drivers/scsi/arm/acornscsi-io.c | 106 +++++++++++++++++++++++++
drivers/scsi/arm/acornscsi-io.h | 14 ++++
drivers/scsi/arm/acornscsi.c | 4 +-
4 files changed, 122 insertions(+), 137 deletions(-)
delete mode 100644 drivers/scsi/arm/acornscsi-io.S
create mode 100644 drivers/scsi/arm/acornscsi-io.c
create mode 100644 drivers/scsi/arm/acornscsi-io.h
diff --git a/drivers/scsi/arm/acornscsi-io.S b/drivers/scsi/arm/acornscsi-io.S
deleted file mode 100644
index fdd7237bb..000000000
--- a/drivers/scsi/arm/acornscsi-io.S
+++ /dev/null
@@ -1,135 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * linux/drivers/acorn/scsi/acornscsi-io.S: Acorn SCSI card IO
- */
-#include <linux/linkage.h>
-
-#include <asm/assembler.h>
-#include <mach/hardware.h>
-
-#if defined(__APCS_32__)
-#define LOADREGS(t,r,l...) ldm##t r, l
-#elif defined(__APCS_26__)
-#define LOADREGS(t,r,l...) ldm##t r, l##^
-#endif
-
-@ Purpose: transfer a block of data from the acorn scsi card to memory
-@ Proto : void acornscsi_in(unsigned int addr_start, char *buffer, int length)
-@ Returns: nothing
-
- .align
-ENTRY(__acornscsi_in)
- stmfd sp!, {r4 - r7, lr}
- bic r0, r0, #3
- mov lr, #0xff
- orr lr, lr, #0xff00
-acornscsi_in16lp:
- subs r2, r2, #16
- bmi acornscsi_in8
- ldmia r0!, {r3, r4, r5, r6}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- and r4, r5, lr
- orr r4, r4, r6, lsl #16
- ldmia r0!, {r5, r6, r7, ip}
- and r5, r5, lr
- orr r5, r5, r6, lsl #16
- and r6, r7, lr
- orr r6, r6, ip, lsl #16
- stmia r1!, {r3 - r6}
- bne acornscsi_in16lp
- LOADREGS(fd, sp!, {r4 - r7, pc})
-
-acornscsi_in8: adds r2, r2, #8
- bmi acornscsi_in4
- ldmia r0!, {r3, r4, r5, r6}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- and r4, r5, lr
- orr r4, r4, r6, lsl #16
- stmia r1!, {r3 - r4}
- LOADREGS(eqfd, sp!, {r4 - r7, pc})
- sub r2, r2, #8
-
-acornscsi_in4: adds r2, r2, #4
- bmi acornscsi_in2
- ldmia r0!, {r3, r4}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- str r3, [r1], #4
- LOADREGS(eqfd, sp!, {r4 - r7, pc})
- sub r2, r2, #4
-
-acornscsi_in2: adds r2, r2, #2
- ldr r3, [r0], #4
- and r3, r3, lr
- strb r3, [r1], #1
- mov r3, r3, lsr #8
- strplb r3, [r1], #1
- LOADREGS(fd, sp!, {r4 - r7, pc})
-
-@ Purpose: transfer a block of data from memory to the acorn scsi card
-@ Proto : void acornscsi_in(unsigned int addr_start, char *buffer, int length)
-@ Returns: nothing
-
-ENTRY(__acornscsi_out)
- stmfd sp!, {r4 - r6, lr}
- bic r0, r0, #3
-acornscsi_out16lp:
- subs r2, r2, #16
- bmi acornscsi_out8
- ldmia r1!, {r4, r6, ip, lr}
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- mov r5, r6, lsl #16
- orr r5, r5, r5, lsr #16
- mov r6, r6, lsr #16
- orr r6, r6, r6, lsl #16
- stmia r0!, {r3, r4, r5, r6}
- mov r3, ip, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, ip, lsr #16
- orr r4, r4, r4, lsl #16
- mov ip, lr, lsl #16
- orr ip, ip, ip, lsr #16
- mov lr, lr, lsr #16
- orr lr, lr, lr, lsl #16
- stmia r0!, {r3, r4, ip, lr}
- bne acornscsi_out16lp
- LOADREGS(fd, sp!, {r4 - r6, pc})
-
-acornscsi_out8: adds r2, r2, #8
- bmi acornscsi_out4
- ldmia r1!, {r4, r6}
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- mov r5, r6, lsl #16
- orr r5, r5, r5, lsr #16
- mov r6, r6, lsr #16
- orr r6, r6, r6, lsl #16
- stmia r0!, {r3, r4, r5, r6}
- LOADREGS(eqfd, sp!, {r4 - r6, pc})
-
- sub r2, r2, #8
-acornscsi_out4: adds r2, r2, #4
- bmi acornscsi_out2
- ldr r4, [r1], #4
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- stmia r0!, {r3, r4}
- LOADREGS(eqfd, sp!, {r4 - r6, pc})
-
- sub r2, r2, #4
-acornscsi_out2: adds r2, r2, #2
- ldr r3, [r1], #2
- strb r3, [r0], #1
- mov r3, r3, lsr #8
- strplb r3, [r0], #1
- LOADREGS(fd, sp!, {r4 - r6, pc})
-
diff --git a/drivers/scsi/arm/acornscsi-io.c b/drivers/scsi/arm/acornscsi-io.c
new file mode 100644
index 000000000..c907090bd
--- /dev/null
+++ b/drivers/scsi/arm/acornscsi-io.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Acorn SCSI card I/O
+ *
+ * The Acorn SCSI expansion card exposes each 16-bit SCSI data
+ * halfword on the low half of a 32-bit card access:
+ *
+ * - on reads, one 16-bit value is returned per 32-bit card load;
+ * consecutive card reads are packed into 32-bit memory words
+ *
+ * - on writes, a 16-bit memory halfword is expanded into a 32-bit
+ * card word. The original assembly duplicates the halfword into
+ * both halves of the card word for the bulk of the transfer and
+ * writes only the low 16 bits of the trailing partial word,
+ * since the hardware samples only the low half.
+ */
+
+#include <linux/unaligned.h>
+#include <linux/align.h>
+#include <linux/io.h>
+#include <linux/types.h>
+
+#include "acornscsi-io.h"
+
+/*
+ * Transfer a block of data from the Acorn SCSI card into memory.
+ *
+ * The card address is rounded down to a 32-bit boundary. Each
+ * 32-bit read from the card yields one valid 16-bit halfword in
+ * the low half; pairs of card reads are packed into single 32-bit
+ * memory words.
+ */
+void __acornscsi_in(void __iomem *addr, char *buffer, int length)
+{
+ const u32 __iomem *card = PTR_ALIGN_DOWN((const u32 __iomem *)addr, 4);
+ u8 *buf = (u8 *)buffer;
+
+ while (length >= 4) {
+ u32 w0 = readl(card + 0);
+ u32 w1 = readl(card + 1);
+
+ put_unaligned_le32((w0 & 0xffff) | (w1 << 16), buf);
+
+ card += 2;
+ buf += 4;
+ length -= 4;
+ }
+
+ while (length >= 2) {
+ u32 w0 = readl(card);
+
+ put_unaligned_le16(w0 & 0xffff, buf);
+
+ card++;
+ buf += 2;
+ length -= 2;
+ }
+
+ if (length >= 1) {
+ u32 w0 = readl(card);
+
+ *buf = w0 & 0xff;
+ }
+}
+
+/*
+ * Transfer a block of data from memory to the Acorn SCSI card.
+ *
+ * Each 16-bit value read from memory is expanded into a 32-bit card
+ * word, with the halfword duplicated in both halves (matching the
+ * original assembly). For the trailing partial halfword the card
+ * only samples the low 16 bits, so two byte stores are sufficient.
+ */
+void __acornscsi_out(void __iomem *addr, char *buffer, int length)
+{
+ u32 __iomem *card = PTR_ALIGN_DOWN((u32 __iomem *)addr, 4);
+ const u8 *buf = (const u8 *)buffer;
+
+ while (length >= 4) {
+ u32 v = get_unaligned_le32(buf);
+ u32 lo = v & 0xffff;
+ u32 hi = v >> 16;
+
+ writel(lo | (lo << 16), card + 0);
+ writel(hi | (hi << 16), card + 1);
+
+ card += 2;
+ buf += 4;
+ length -= 4;
+ }
+
+ while (length >= 2) {
+ u16 v = get_unaligned_le16(buf);
+ void __iomem *cp = (void __iomem *)card;
+
+ writeb(v & 0xff, cp);
+ writeb((v >> 8) & 0xff, cp + 1);
+
+ card++;
+ buf += 2;
+ length -= 2;
+ }
+
+ if (length >= 1)
+ writeb(*buf, (void __iomem *)card);
+}
diff --git a/drivers/scsi/arm/acornscsi-io.h b/drivers/scsi/arm/acornscsi-io.h
new file mode 100644
index 000000000..64acfb8bb
--- /dev/null
+++ b/drivers/scsi/arm/acornscsi-io.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Acorn SCSI card I/O
+ */
+
+#ifndef _ACORNSCSI_IO_H
+#define _ACORNSCSI_IO_H
+
+#include <linux/io.h>
+
+void __acornscsi_in(void __iomem *addr, char *buffer, int length);
+void __acornscsi_out(void __iomem *addr, char *buffer, int length);
+
+#endif /* _ACORNSCSI_IO_H */
diff --git a/drivers/scsi/arm/acornscsi.c b/drivers/scsi/arm/acornscsi.c
index 79d7d7336..263a1bf24 100644
--- a/drivers/scsi/arm/acornscsi.c
+++ b/drivers/scsi/arm/acornscsi.c
@@ -140,6 +140,8 @@
#include <scsi/scsicam.h>
+#include "acornscsi-io.h"
+
#define VER_MAJOR 2
#define VER_MINOR 0
#define VER_PATCH 6
@@ -886,7 +888,6 @@ static
void acornscsi_data_read(AS_Host *host, char *ptr,
unsigned int start_addr, unsigned int length)
{
- extern void __acornscsi_in(void __iomem *, char *buf, int len);
unsigned int page, offset, len = length;
page = (start_addr >> 12);
@@ -931,7 +932,6 @@ static
void acornscsi_data_write(AS_Host *host, char *ptr,
unsigned int start_addr, unsigned int length)
{
- extern void __acornscsi_out(void __iomem *, char *buf, int len);
unsigned int page, offset, len = length;
page = (start_addr >> 12);
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH v3] scsi: arm: Convert acornscsi-io.S to C
2026-10-22 20:30 ` [PATCH v3] " Breno Rodrigues Alves
@ 2026-10-22 20:49 ` Bart Van Assche
0 siblings, 0 replies; 11+ messages in thread
From: Bart Van Assche @ 2026-10-22 20:49 UTC (permalink / raw)
To: Breno Rodrigues Alves, linux
Cc: James.Bottomley, mkp, linux-arm-kernel, linux-scsi, linux-kernel
On 10/22/26 1:30 PM, Breno Rodrigues Alves wrote:
> + while (length >= 2) {
Shouldn't this "while" be changed into "if"?
> + u16 v = get_unaligned_le16(buf);
> + void __iomem *cp = (void __iomem *)card;
> +
> + writeb(v & 0xff, cp);
> + writeb((v >> 8) & 0xff, cp + 1);
The above code can be simplified as shown below, isn't it?
void __iomem *cp = (void __iomem *)card;
writeb(buf[0], cp);
writeb(buf[1], cp + 1);
> + if (length >= 1)
> + writeb(*buf, (void __iomem *)card);
Isn't the above (void __iomem *) cast superfluous?
Thanks,
Bart.
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4] scsi: arm: Convert acornscsi-io.S to C
2026-10-22 18:36 [PATCH 0/1] scsi: arm: Convert acornscsi-io.S to C Breno Rodrigues Alves
` (2 preceding siblings ...)
2026-10-22 20:30 ` [PATCH v3] " Breno Rodrigues Alves
@ 2026-10-22 21:02 ` Breno Rodrigues Alves
2026-10-22 21:36 ` Bart Van Assche
3 siblings, 1 reply; 11+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-22 21:02 UTC (permalink / raw)
To: linux
Cc: Bart Van Assche, James.Bottomley, mkp, linux-arm-kernel,
linux-scsi, linux-kernel, Breno Rodrigues Alves
The Acorn SCSI expansion card presents each 16-bit SCSI data halfword
on the low half of a 32-bit card access. The hand-written assembly
in acornscsi-io.S implements the two directions of the data path:
- __acornscsi_in() : packs pairs of 16-bit card reads into 32-bit
memory words
- __acornscsi_out() : expands 16-bit memory halfwords into 32-bit
card writes (duplicating the halfword in both
halves for the bulk of the transfer)
Both routines are simple pointer walks that a modern compiler handles
just as well as hand-written assembly, so replace the .S file with an
equivalent C implementation.
The C version takes its arguments as (void __iomem *, char *, int),
matching the prototype actually used by acornscsi.c. The assembly
version had no C prototype, so the original comment declaring the
first argument as unsigned int was never enforced; using the caller's
type removes that latent inconsistency and lets the MMIO accessors
do the right thing on any architecture.
The 16-, 8- and 4-byte unrolled bodies from the assembly are folded
into a single 4-byte loop per direction and expressed with
put_unaligned_le32() / get_unaligned_le32(), which the compiler
unrolls exactly as before. The LE variants are used because the
assembly performed a plain 32-bit store on little-endian ARM, which
emits bytes in little-endian order and is what the SCSI payload
expects.
Note on odd transfer lengths: the original assembly dropped the final
odd byte when the tail reached the two-byte handler, leaving the byte
unwritten (out) or unread (in). The C version advances the MMIO
pointer by a full 32-bit card word in the trailing loop, so the
remaining odd byte is transferred through the low half of the next
card word, which is where the hardware actually samples it. Callers
in acornscsi.c pass even lengths in practice, so this is a latent-bug
fix, not a behaviour change that affects existing systems.
No Makefile change is required: acornscsi_mod-objs already lists
acornscsi-io.o, and the build system will pick up the new .c file in
preference to the removed .S file.
Signed-off-by: Breno Rodrigues Alves <breno3011alves@gmail.com>
---
drivers/scsi/arm/acornscsi-io.S | 135 --------------------------------
drivers/scsi/arm/acornscsi-io.c | 105 +++++++++++++++++++++++++
drivers/scsi/arm/acornscsi-io.h | 14 ++++
drivers/scsi/arm/acornscsi.c | 4 +-
4 files changed, 121 insertions(+), 137 deletions(-)
delete mode 100644 drivers/scsi/arm/acornscsi-io.S
create mode 100644 drivers/scsi/arm/acornscsi-io.c
create mode 100644 drivers/scsi/arm/acornscsi-io.h
diff --git a/drivers/scsi/arm/acornscsi-io.S b/drivers/scsi/arm/acornscsi-io.S
deleted file mode 100644
index fdd7237bb..000000000
--- a/drivers/scsi/arm/acornscsi-io.S
+++ /dev/null
@@ -1,135 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * linux/drivers/acorn/scsi/acornscsi-io.S: Acorn SCSI card IO
- */
-#include <linux/linkage.h>
-
-#include <asm/assembler.h>
-#include <mach/hardware.h>
-
-#if defined(__APCS_32__)
-#define LOADREGS(t,r,l...) ldm##t r, l
-#elif defined(__APCS_26__)
-#define LOADREGS(t,r,l...) ldm##t r, l##^
-#endif
-
-@ Purpose: transfer a block of data from the acorn scsi card to memory
-@ Proto : void acornscsi_in(unsigned int addr_start, char *buffer, int length)
-@ Returns: nothing
-
- .align
-ENTRY(__acornscsi_in)
- stmfd sp!, {r4 - r7, lr}
- bic r0, r0, #3
- mov lr, #0xff
- orr lr, lr, #0xff00
-acornscsi_in16lp:
- subs r2, r2, #16
- bmi acornscsi_in8
- ldmia r0!, {r3, r4, r5, r6}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- and r4, r5, lr
- orr r4, r4, r6, lsl #16
- ldmia r0!, {r5, r6, r7, ip}
- and r5, r5, lr
- orr r5, r5, r6, lsl #16
- and r6, r7, lr
- orr r6, r6, ip, lsl #16
- stmia r1!, {r3 - r6}
- bne acornscsi_in16lp
- LOADREGS(fd, sp!, {r4 - r7, pc})
-
-acornscsi_in8: adds r2, r2, #8
- bmi acornscsi_in4
- ldmia r0!, {r3, r4, r5, r6}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- and r4, r5, lr
- orr r4, r4, r6, lsl #16
- stmia r1!, {r3 - r4}
- LOADREGS(eqfd, sp!, {r4 - r7, pc})
- sub r2, r2, #8
-
-acornscsi_in4: adds r2, r2, #4
- bmi acornscsi_in2
- ldmia r0!, {r3, r4}
- and r3, r3, lr
- orr r3, r3, r4, lsl #16
- str r3, [r1], #4
- LOADREGS(eqfd, sp!, {r4 - r7, pc})
- sub r2, r2, #4
-
-acornscsi_in2: adds r2, r2, #2
- ldr r3, [r0], #4
- and r3, r3, lr
- strb r3, [r1], #1
- mov r3, r3, lsr #8
- strplb r3, [r1], #1
- LOADREGS(fd, sp!, {r4 - r7, pc})
-
-@ Purpose: transfer a block of data from memory to the acorn scsi card
-@ Proto : void acornscsi_in(unsigned int addr_start, char *buffer, int length)
-@ Returns: nothing
-
-ENTRY(__acornscsi_out)
- stmfd sp!, {r4 - r6, lr}
- bic r0, r0, #3
-acornscsi_out16lp:
- subs r2, r2, #16
- bmi acornscsi_out8
- ldmia r1!, {r4, r6, ip, lr}
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- mov r5, r6, lsl #16
- orr r5, r5, r5, lsr #16
- mov r6, r6, lsr #16
- orr r6, r6, r6, lsl #16
- stmia r0!, {r3, r4, r5, r6}
- mov r3, ip, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, ip, lsr #16
- orr r4, r4, r4, lsl #16
- mov ip, lr, lsl #16
- orr ip, ip, ip, lsr #16
- mov lr, lr, lsr #16
- orr lr, lr, lr, lsl #16
- stmia r0!, {r3, r4, ip, lr}
- bne acornscsi_out16lp
- LOADREGS(fd, sp!, {r4 - r6, pc})
-
-acornscsi_out8: adds r2, r2, #8
- bmi acornscsi_out4
- ldmia r1!, {r4, r6}
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- mov r5, r6, lsl #16
- orr r5, r5, r5, lsr #16
- mov r6, r6, lsr #16
- orr r6, r6, r6, lsl #16
- stmia r0!, {r3, r4, r5, r6}
- LOADREGS(eqfd, sp!, {r4 - r6, pc})
-
- sub r2, r2, #8
-acornscsi_out4: adds r2, r2, #4
- bmi acornscsi_out2
- ldr r4, [r1], #4
- mov r3, r4, lsl #16
- orr r3, r3, r3, lsr #16
- mov r4, r4, lsr #16
- orr r4, r4, r4, lsl #16
- stmia r0!, {r3, r4}
- LOADREGS(eqfd, sp!, {r4 - r6, pc})
-
- sub r2, r2, #4
-acornscsi_out2: adds r2, r2, #2
- ldr r3, [r1], #2
- strb r3, [r0], #1
- mov r3, r3, lsr #8
- strplb r3, [r0], #1
- LOADREGS(fd, sp!, {r4 - r6, pc})
-
diff --git a/drivers/scsi/arm/acornscsi-io.c b/drivers/scsi/arm/acornscsi-io.c
new file mode 100644
index 000000000..08071963d
--- /dev/null
+++ b/drivers/scsi/arm/acornscsi-io.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Acorn SCSI card I/O
+ *
+ * The Acorn SCSI expansion card exposes each 16-bit SCSI data
+ * halfword on the low half of a 32-bit card access:
+ *
+ * - on reads, one 16-bit value is returned per 32-bit card load;
+ * consecutive card reads are packed into 32-bit memory words
+ *
+ * - on writes, a 16-bit memory halfword is expanded into a 32-bit
+ * card word. The original assembly duplicates the halfword into
+ * both halves of the card word for the bulk of the transfer and
+ * writes only the low 16 bits of the trailing partial word,
+ * since the hardware samples only the low half.
+ */
+
+#include <linux/align.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include "acornscsi-io.h"
+
+/*
+ * Transfer a block of data from the Acorn SCSI card into memory.
+ *
+ * The card address is rounded down to a 32-bit boundary. Each
+ * 32-bit read from the card yields one valid 16-bit halfword in
+ * the low half; pairs of card reads are packed into single 32-bit
+ * memory words.
+ */
+void __acornscsi_in(void __iomem *addr, char *buffer, int length)
+{
+ const u32 __iomem *card = PTR_ALIGN_DOWN((const u32 __iomem *)addr, 4);
+ u8 *buf = (u8 *)buffer;
+
+ while (length >= 4) {
+ u32 w0 = readl(card + 0);
+ u32 w1 = readl(card + 1);
+
+ put_unaligned_le32((w0 & 0xffff) | (w1 << 16), buf);
+
+ card += 2;
+ buf += 4;
+ length -= 4;
+ }
+
+ if (length >= 2) {
+ u32 w0 = readl(card);
+
+ put_unaligned_le16(w0 & 0xffff, buf);
+
+ card++;
+ buf += 2;
+ length -= 2;
+ }
+
+ if (length >= 1) {
+ u32 w0 = readl(card);
+
+ *buf = w0 & 0xff;
+ }
+}
+
+/*
+ * Transfer a block of data from memory to the Acorn SCSI card.
+ *
+ * Each 16-bit value read from memory is expanded into a 32-bit card
+ * word, with the halfword duplicated in both halves (matching the
+ * original assembly). For the trailing partial halfword the card
+ * only samples the low 16 bits, so two byte stores are sufficient.
+ */
+void __acornscsi_out(void __iomem *addr, char *buffer, int length)
+{
+ u32 __iomem *card = PTR_ALIGN_DOWN((u32 __iomem *)addr, 4);
+ const u8 *buf = (const u8 *)buffer;
+
+ while (length >= 4) {
+ u32 v = get_unaligned_le32(buf);
+ u32 lo = v & 0xffff;
+ u32 hi = v >> 16;
+
+ writel(lo | (lo << 16), card + 0);
+ writel(hi | (hi << 16), card + 1);
+
+ card += 2;
+ buf += 4;
+ length -= 4;
+ }
+
+ if (length >= 2) {
+ void __iomem *cp = (void __iomem *)card;
+
+ writeb(buf[0], cp);
+ writeb(buf[1], cp + 1);
+
+ card++;
+ buf += 2;
+ length -= 2;
+ }
+
+ if (length >= 1)
+ writeb(*buf, card);
+}
diff --git a/drivers/scsi/arm/acornscsi-io.h b/drivers/scsi/arm/acornscsi-io.h
new file mode 100644
index 000000000..64acfb8bb
--- /dev/null
+++ b/drivers/scsi/arm/acornscsi-io.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Acorn SCSI card I/O
+ */
+
+#ifndef _ACORNSCSI_IO_H
+#define _ACORNSCSI_IO_H
+
+#include <linux/io.h>
+
+void __acornscsi_in(void __iomem *addr, char *buffer, int length);
+void __acornscsi_out(void __iomem *addr, char *buffer, int length);
+
+#endif /* _ACORNSCSI_IO_H */
diff --git a/drivers/scsi/arm/acornscsi.c b/drivers/scsi/arm/acornscsi.c
index 79d7d7336..263a1bf24 100644
--- a/drivers/scsi/arm/acornscsi.c
+++ b/drivers/scsi/arm/acornscsi.c
@@ -140,6 +140,8 @@
#include <scsi/scsicam.h>
+#include "acornscsi-io.h"
+
#define VER_MAJOR 2
#define VER_MINOR 0
#define VER_PATCH 6
@@ -886,7 +888,6 @@ static
void acornscsi_data_read(AS_Host *host, char *ptr,
unsigned int start_addr, unsigned int length)
{
- extern void __acornscsi_in(void __iomem *, char *buf, int len);
unsigned int page, offset, len = length;
page = (start_addr >> 12);
@@ -931,7 +932,6 @@ static
void acornscsi_data_write(AS_Host *host, char *ptr,
unsigned int start_addr, unsigned int length)
{
- extern void __acornscsi_out(void __iomem *, char *buf, int len);
unsigned int page, offset, len = length;
page = (start_addr >> 12);
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH v4] scsi: arm: Convert acornscsi-io.S to C
2026-10-22 21:02 ` [PATCH v4] " Breno Rodrigues Alves
@ 2026-10-22 21:36 ` Bart Van Assche
0 siblings, 0 replies; 11+ messages in thread
From: Bart Van Assche @ 2026-10-22 21:36 UTC (permalink / raw)
To: Breno Rodrigues Alves, linux
Cc: James.Bottomley, mkp, linux-arm-kernel, linux-scsi, linux-kernel
On 10/22/26 2:02 PM, Breno Rodrigues Alves wrote:
> Both routines are simple pointer walks that a modern compiler handles
> just as well as hand-written assembly, so replace the .S file with an
> equivalent C implementation.
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
^ permalink raw reply [flat|nested] 11+ messages in thread