All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] spapr_vscsi: fix silent data corruption above 128 KiB
@ 2026-08-20 12:26 Bernard Ladenthin
  2026-08-20 12:26 ` [PATCH 1/2] spapr_vscsi: fix data corruption on transfers " Bernard Ladenthin
  2026-08-20 12:26 ` [PATCH 2/2] tests/spapr: add bare-metal reproducers for pseries machine behaviour Bernard Ladenthin
  0 siblings, 2 replies; 3+ messages in thread
From: Bernard Ladenthin @ 2026-08-20 12:26 UTC (permalink / raw)
  To: qemu-devel; +Cc: npiggin, harshpb, qemu-ppc, Bernard Ladenthin

struct vscsi_req::cur_desc_offset is uint16_t. scsi-disk delivers a command
in SCSI_DMA_BUF_SIZE (128 KiB) chunks, so vscsi_srp_direct_data() adds
0x20000 to it on the first chunk and it wraps back to zero. Every following
chunk of the same command is then DMA'd over the start of the guest buffer:
the tail of the transfer overwrites the head, and the rest is never written
at all. The command completes with status 0 and no sense data, so the guest
has no way to notice.

Linux never hits it. Its ibmvscsi initiator uses INDIRECT descriptors for
anything multi-page, and that path resets cur_desc_offset at every
descriptor boundary, so the counter never reaches 65536. A guest that
describes the whole transfer with one DIRECT descriptor and issues 256 KiB
commands hits it on the first command. The field has been uint16_t since it
was introduced in 2013.

Patch 1 widens it. struct srp_direct_buf.len is already uint32_t and
vscsi_fetch_desc() already takes buf_offset as unsigned, so that is the
whole change. The VMSTATE entries for cur_desc_num/cur_desc_offset are
commented out upstream, so the migration format is unaffected.

Patch 2 adds the reproducer. It is a bare-metal PowerPC payload booted with
-kernel on -M pseries that speaks CRQ and SRP directly, with no guest
operating system involved, so a failure is attributable to one function in
QEMU rather than to something a kernel did on the way there. The fix comes
first, so the tree is never left with a failing test, but the test applies
on its own if a reviewer wants to put it first and watch it fail.

Testing. Against unpatched master the test reports 2 passed, 2 failed: the
two controls hold -- the CRQ registers and the 256 KiB READ_10 completes
with SRP status 0 -- and exactly the two assertions about where the data
landed fail. With patch 1 it reports 4 passed, 0 failed. Both runs use the
same payload and the same script; only qemu-system-ppc64 differs. Each
patch was also confirmed to apply and compile on its own against master.

The committed ELF is 920 bytes and its source rebuilds to it byte for byte;
"make check-reproducible" is there so the .S and the .elf cannot drift apart
unnoticed. tests/multiboot is the precedent for committing a built payload
and running it from a standalone script rather than a meson target.

The bug was found while trying to boot AIX 5.3 on -M pseries, where liblvm
reads a volume group descriptor area in one 256 KiB command and gets the
second half of it at offset 0. That is not needed to see the problem and is
not part of this series; the reproducer needs no guest at all.

Bernard Ladenthin (2):
  spapr_vscsi: fix data corruption on transfers above 128 KiB
  tests/spapr: add bare-metal reproducers for pseries machine behaviour

 MAINTAINERS              |   1 +
 hw/scsi/spapr_vscsi.c    |   9 +-
 tests/spapr/Makefile     |  62 ++++++++++
 tests/spapr/link.ld      |   3 +
 tests/spapr/run_test.sh  | 108 ++++++++++++++++
 tests/spapr/vscsibig.S   | 285 +++++++++++++++++++++++++++++++++++++++++++
 tests/spapr/vscsibig.elf | Bin 0 -> 920 bytes
 7 files changed, 467 insertions(+), 1 deletion(-)
 create mode 100644 tests/spapr/Makefile
 create mode 100644 tests/spapr/link.ld
 create mode 100755 tests/spapr/run_test.sh
 create mode 100644 tests/spapr/vscsibig.S
 create mode 100644 tests/spapr/vscsibig.elf

--
2.47.3


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 1/2] spapr_vscsi: fix data corruption on transfers above 128 KiB
  2026-08-20 12:26 [PATCH 0/2] spapr_vscsi: fix silent data corruption above 128 KiB Bernard Ladenthin
@ 2026-08-20 12:26 ` Bernard Ladenthin
  2026-08-20 12:26 ` [PATCH 2/2] tests/spapr: add bare-metal reproducers for pseries machine behaviour Bernard Ladenthin
  1 sibling, 0 replies; 3+ messages in thread
From: Bernard Ladenthin @ 2026-08-20 12:26 UTC (permalink / raw)
  To: qemu-devel; +Cc: npiggin, harshpb, qemu-ppc, Bernard Ladenthin, qemu-stable

scsi-disk hands the HBA at most SCSI_DMA_BUF_SIZE (128 KiB) per
vscsi_transfer_data() callback, so a single SCSI command carrying more
than that arrives in several chunks.  vscsi_srp_direct_data() remembers
how far into the descriptor it already is in req->cur_desc_offset -- but
that field is uint16_t, so the very first 128 KiB chunk does

    req->cur_desc_offset += 0x20000;      /* 0x20000 & 0xFFFF == 0 */

and wraps straight back to zero.  Every following chunk of the same
command is then fetched with buf_offset 0 and DMA'd over the START of
the guest buffer: the tail of the transfer overwrites the head, and the
rest of the buffer is never written at all.  The command still completes
with status 0 and no sense data, so the guest cannot notice.

Linux never hits this.  Its ibmvscsi initiator uses INDIRECT descriptors
for anything multi-page, and the indirect path resets cur_desc_offset to
0 at every descriptor boundary, so the counter never reaches 65536.  A
guest that describes the whole transfer with a single DIRECT descriptor
and issues 256 KiB commands does hit it on the first command.

Measured with a READ_10 of 2098 sectors, traced with
spapr_vscsi_transfer_data and spapr_vscsi_fetch_desc_done:

    queue_cmd      tag 0x2 CMD 0x28=READ_10 LUN 0 ret: 262144
    transfer_data  tag=0x2 len=0x20000
    fetch_desc     cur=0 offs=0x0 ret { va=0x84d28 len=0x40000 }
    transfer_data  tag=0x2 len=0x20000
    fetch_desc     cur=0 offs=0x0 ret { va=0x84d28 len=0x40000 }
    cmd_complete   tag=0x2 status=0x0

Same tag, same request, same target address twice.  The guest buffer
ends up holding the second half of the transfer at offset 0 and zeroes
above it.

struct srp_direct_buf.len is already uint32_t and vscsi_fetch_desc()
already takes buf_offset as unsigned, so widening the field is all that
is required.  The VMSTATE entries for cur_desc_num/cur_desc_offset are
commented out upstream, so the migration format is unaffected.

Fixes: 8ca8a17c9a16 ("pseries: rework PAPR virtual SCSI")
Cc: qemu-stable@nongnu.org
Signed-off-by: Bernard Ladenthin <bernard.ladenthin@gmail.com>
---
This is my first QEMU submission. Corrections on anything I got wrong in
the process are welcome.

diff --git a/hw/scsi/spapr_vscsi.c b/hw/scsi/spapr_vscsi.c
index 978eb856d7..1f0c2a3b64 100644
--- a/hw/scsi/spapr_vscsi.c
+++ b/hw/scsi/spapr_vscsi.c
@@ -86,7 +86,14 @@ typedef struct vscsi_req {
     uint16_t                total_desc;
     uint16_t                cdb_offset;
     uint16_t                cur_desc_num;
-    uint16_t                cur_desc_offset;
+    /*
+     * Byte offset reached inside the current descriptor.  Must be at
+     * least 32 bit: scsi-disk delivers a command in SCSI_DMA_BUF_SIZE
+     * (128 KiB) chunks, so a DIRECT descriptor larger than 64 KiB would
+     * wrap a uint16_t back to zero and make every chunk after the first
+     * overwrite the head of the guest buffer.
+     */
+    uint32_t                cur_desc_offset;
 } vscsi_req;
 
 #define TYPE_VIO_SPAPR_VSCSI_DEVICE "spapr-vscsi"


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 2/2] tests/spapr: add bare-metal reproducers for pseries machine behaviour
  2026-08-20 12:26 [PATCH 0/2] spapr_vscsi: fix silent data corruption above 128 KiB Bernard Ladenthin
  2026-08-20 12:26 ` [PATCH 1/2] spapr_vscsi: fix data corruption on transfers " Bernard Ladenthin
@ 2026-08-20 12:26 ` Bernard Ladenthin
  1 sibling, 0 replies; 3+ messages in thread
From: Bernard Ladenthin @ 2026-08-20 12:26 UTC (permalink / raw)
  To: qemu-devel; +Cc: npiggin, harshpb, qemu-ppc, Bernard Ladenthin

These are small PowerPC payloads booted with -kernel on -M pseries.  They
drive the hypervisor interface directly -- hypercalls, CRQ, SRP -- so no
guest operating system is involved and a failure is attributable to one
function in QEMU rather than to something a kernel did on the way there.

vscsibig.S is the first.  It registers a CRQ, identity-maps its pages with
H_PUT_TCE, clears the power-on unit attention, then issues a single 256 KiB
READ_10 described by one DIRECT descriptor and checks where the data landed.
The buffer is poisoned with 0xeeeeeeee first, which separates "the DMA never
happened" from "the disk really holds zeros".

Following tests/multiboot, the built payload is committed so the test can be
run without a PowerPC cross toolchain, and run_test.sh is a standalone script
rather than a meson target.  The ELF is 920 bytes: linking with -N keeps the
loader from page-aligning the load segment, which would otherwise pad a
584-byte payload out to 65 KiB of zeros.

"make check-reproducible" rebuilds from source and compares against the
committed binary, so the .S and the .elf cannot drift apart unnoticed.
Signed-off-by: Bernard Ladenthin <bernard.ladenthin@gmail.com>
---
 MAINTAINERS              |   1 +
 tests/spapr/Makefile     |  62 +++++++++
 tests/spapr/link.ld      |   3 +
 tests/spapr/run_test.sh  | 108 +++++++++++++++
 tests/spapr/vscsibig.S   | 285 +++++++++++++++++++++++++++++++++++++++
 tests/spapr/vscsibig.elf | Bin 0 -> 920 bytes
 6 files changed, 459 insertions(+)
 create mode 100644 tests/spapr/Makefile
 create mode 100644 tests/spapr/link.ld
 create mode 100644 tests/spapr/run_test.sh
 create mode 100644 tests/spapr/vscsibig.S
 create mode 100644 tests/spapr/vscsibig.elf

diff --git a/MAINTAINERS b/MAINTAINERS
index 902db77218..e226d254b3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1658,6 +1658,7 @@ F: pc-bios/slof.bin
 F: docs/system/ppc/pseries.rst
 F: docs/specs/ppc-spapr-*
 F: tests/qtest/spapr*
+F: tests/spapr/
 F: tests/qtest/libqos/*spapr*
 F: tests/qtest/rtas*
 F: tests/qtest/libqos/rtas*
diff --git a/tests/spapr/Makefile b/tests/spapr/Makefile
new file mode 100644
index 0000000000..60af7f903f
--- /dev/null
+++ b/tests/spapr/Makefile
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Bare-metal reproducers for pseries (sPAPR) machine behaviour.
+#
+# These are tiny PowerPC payloads booted with -kernel on -M pseries.  They
+# talk to the hypervisor interface directly -- hypercalls, CRQ, SRP -- so
+# they exercise QEMU's sPAPR implementation with no guest operating system
+# anywhere in the picture.  That makes a failure attributable to one
+# function in QEMU rather than to something a kernel did on the way there.
+#
+# The built payloads are committed so the tests can be run without a
+# PowerPC cross toolchain, following tests/multiboot.  They are small:
+# linking with -N keeps the loader from page-aligning the load segment,
+# which would otherwise pad a 584-byte payload out to 65 KiB of zeros.
+#
+# Rebuild with:
+#     make CROSS=powerpc64-linux-gnu-
+#
+# and confirm the committed binary still matches its source with:
+#     make check-reproducible
+
+CROSS ?= powerpc64-linux-gnu-
+AS     = $(CROSS)as
+LD     = $(CROSS)ld
+STRIP  = $(CROSS)strip
+
+ASFLAGS = -many
+LDFLAGS = -N -T link.ld
+
+PAYLOADS = vscsibig
+ELFS     = $(PAYLOADS:%=%.elf)
+
+all: $(ELFS)
+
+%.o: %.S
+	$(AS) $(ASFLAGS) -o $@ $<
+
+%.elf: %.o link.ld
+	$(LD) $(LDFLAGS) -o $@ $<
+	$(STRIP) $@
+
+# The payloads are position-fixed and self-contained, so rebuilding from
+# the same source must produce the same bytes.  If this fails, the
+# committed ELF and the committed .S have drifted apart and one of them is
+# lying about what was tested.
+check-reproducible: $(ELFS)
+	@for p in $(PAYLOADS); do \
+	    cp $$p.elf $$p.elf.committed; \
+	    $(MAKE) --no-print-directory -B $$p.elf CROSS=$(CROSS) >/dev/null; \
+	    if cmp -s $$p.elf $$p.elf.committed; then \
+	        echo "  $$p.elf: reproducible"; \
+	    else \
+	        echo "  $$p.elf: DIFFERS from the committed binary"; \
+	        mv $$p.elf.committed $$p.elf; exit 1; \
+	    fi; \
+	    mv $$p.elf.committed $$p.elf; \
+	done
+
+clean:
+	rm -f *.o *.elf.committed vscsidisk.raw *.monitor.txt
+
+.PHONY: all clean check-reproducible
diff --git a/tests/spapr/link.ld b/tests/spapr/link.ld
new file mode 100644
index 0000000000..e901962834
--- /dev/null
+++ b/tests/spapr/link.ld
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+ENTRY(_start)
+SECTIONS { . = 0x400000; .text : { *(.text) } }
diff --git a/tests/spapr/run_test.sh b/tests/spapr/run_test.sh
new file mode 100644
index 0000000000..935b0fc2e4
--- /dev/null
+++ b/tests/spapr/run_test.sh
@@ -0,0 +1,108 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Run the bare-metal sPAPR reproducers and check the words each leaves
+# behind.
+#
+# Each payload boots with -kernel on -M pseries, drives the hypervisor
+# interface directly, and writes its results to physical 0x01000000.  This
+# script reads that buffer back through the QEMU monitor and compares it
+# against the expected words.
+#
+# Not wired into meson or CI, the same as tests/multiboot: it needs a
+# machine, it takes a few seconds per payload, and its value is in being
+# runnable by hand when someone doubts a claim.
+#
+# Usage:
+#     ./run_test.sh [/path/to/qemu-system-ppc64]
+#
+# Exit status is 0 only if every assertion held.
+
+set -u
+
+QEMU=${1:-${QEMU:-../../build/qemu-system-ppc64}}
+SETTLE=${SETTLE:-15}
+
+if [ ! -x "$QEMU" ]; then
+    echo "qemu-system-ppc64 not found at: $QEMU" >&2
+    echo "pass the path as \$1 or set \$QEMU" >&2
+    exit 2
+fi
+
+fail=0
+pass=0
+
+# Backing disk for vscsibig: sector s starts with the big-endian word
+# 0xA5000000|s, so a chunk that lands at the wrong offset names the sector
+# it really came from.  Generated rather than committed -- it is 2 MiB of
+# almost nothing.
+make_disk() {
+    perl -e '
+        open(my $f, ">", "vscsidisk.raw") or die;
+        binmode $f;
+        for my $s (0 .. 4095) {
+            print $f pack("N", 0xA5000000 | $s), (chr(0) x 508);
+        }
+        close $f;
+    '
+}
+
+# $1 payload  $2 words to dump  $3.. "index:expected:description"
+run_payload() {
+    local name=$1 words=$2; shift 2
+    local out="$name.monitor.txt"
+
+    ( sleep "$SETTLE"; printf 'x /%dxw 0x1000000\nquit\n' "$words" ) | \
+        "$QEMU" -M pseries -cpu POWER7 -m 512 \
+            -display none -vga none -nodefaults -serial none \
+            -kernel "$name.elf" \
+            -device spapr-vscsi,id=scsi0 \
+            -drive file=vscsidisk.raw,if=none,id=d0,format=raw \
+            -device scsi-hd,bus=scsi0.0,drive=d0 \
+            -monitor stdio >"$out" 2>/dev/null
+
+    # The monitor prints "0000000001000000: 0x00000000 0x00000000 ..." and,
+    # being a readline monitor on a pipe, echoes every typed character
+    # wrapped in cursor-control escapes.  Pulling out each 0x-prefixed
+    # 8-digit token sidesteps both: the echoed command contains 0x1000000,
+    # which is seven digits and does not match.
+    local got n
+    got=$(grep -oE '0x[0-9a-f]{8}' "$out" | sed 's/^0x//')
+    n=$(printf '%s\n' "$got" | grep -c .)
+
+    if [ "$n" -lt "$words" ]; then
+        echo "  $name: NOTHING MEASURED -- expected $words words, got $n"
+        echo "        A short read means the payload did not finish or the"
+        echo "        dump failed.  Raise \$SETTLE and look at $out."
+        fail=$((fail + 1))
+        return
+    fi
+
+    local spec idx expect desc actual
+    for spec in "$@"; do
+        idx=${spec%%:*}; spec=${spec#*:}
+        expect=${spec%%:*}; desc=${spec#*:}
+        actual=$(echo "$got" | sed -n "$((idx + 1))p")
+        if [ "$actual" = "$expect" ]; then
+            echo "  PASS  $desc"
+            pass=$((pass + 1))
+        else
+            echo "  FAIL  $desc"
+            echo "        expected $expect, got ${actual:-<nothing>}"
+            fail=$((fail + 1))
+        fi
+    done
+}
+
+echo "=== vscsibig: a >128 KiB transfer must not be folded onto the"
+echo "    start of the guest buffer ==="
+make_disk
+run_payload vscsibig 20 \
+    "1:00000000:H_REG_CRQ registered the queue on the vSCSI adapter" \
+    "11:00000000:the 256 KiB READ_10 reports SRP status 0 either way" \
+    "13:a5000000:sector 0 landed at buffer offset 0" \
+    "15:a5000100:sector 256 landed at buffer offset 0x20000"
+
+echo
+echo "$pass passed, $fail failed"
+[ "$fail" -eq 0 ]
diff --git a/tests/spapr/vscsibig.S b/tests/spapr/vscsibig.S
new file mode 100644
index 0000000000..6474d31f0e
--- /dev/null
+++ b/tests/spapr/vscsibig.S
@@ -0,0 +1,285 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * vscsibig.S -- guest-free reproducer for defect 7:
+ *   spapr_vscsi silently drops data on any transfer larger than 128 KiB.
+ *
+ * struct vscsi_req::cur_desc_offset is uint16_t.  scsi-disk delivers a
+ * command in SCSI_DMA_BUF_SIZE (128 KiB) chunks, so vscsi_srp_direct_data()
+ * does
+ *     req->cur_desc_offset += 0x20000;      /  0x20000 & 0xFFFF == 0  /
+ * and wraps back to zero: every chunk after the first is DMA'd over the
+ * START of the guest buffer.  The command still completes with status 0 and
+ * no sense data.
+ *
+ * This payload speaks CRQ + SRP directly -- no guest OS, no SLOF client
+ * interface. QEMU's vscsi_process_srp_iu() dispatches SRP_CMD without
+ * requiring a prior LOGIN, and SPAPR_VIO_LIOBN(reg) == reg, so the whole
+ * thing is: free any CRQ SLOF left behind, register our own, identity-map
+ * the pages we touch with H_PUT_TCE, clear the power-on UNIT ATTENTION with
+ * a TEST UNIT READY, then send one READ_10 of 512 blocks (= 256 KiB)
+ * described by a single DIRECT descriptor.
+ *
+ * The backing disk is written by mkvscsidisk.py so that sector s starts with
+ * the big-endian word 0xA5000000|s.  Therefore:
+ *
+ *   +0x30  data[0]        RED 0xa5000100 (the 2nd chunk landed at offset 0)
+ *                       GREEN 0xa5000000 (sector 0, where it belongs)
+ *   +0x38  data[0x20000]  RED 0xeeeeeeee (poison -- never written at all)
+ *                       GREEN 0xa5000100 (sector 256)
+ *
+ * Poison rather than zero matters: it separates "the DMA never happened"
+ * from "the disk really holds zeros", which is exactly the distinction that
+ * cost two measurement rounds when this was first chased through AIX.
+ *
+ * Results at physical 0x01000000; read with the monitor: x /10xg 0x1000000
+ *   +0x00  H_REG_CRQ return code (0 = registered)
+ *   +0x08  the VIO unit address that answered (0x71000000 + n)
+ *   +0x10  return code of the last H_PUT_TCE
+ *   +0x18  H_SEND_CRQ return code for the READ
+ *   +0x20  poll iterations spent waiting for the READ response
+ *          (0x04000000 = the cap: it timed out, all below is void)
+ *   +0x28  SRP RSP status byte of the READ  -- must be 0; 2 = CHECK CONDITION
+ *   +0x30  data[0]
+ *   +0x38  data[0x20000]
+ *   +0x40  SRP RSP status byte of the TEST UNIT READY (2 is normal: it eats the
+ *          power-on UNIT ATTENTION, which is exactly why it is sent first)
+ *   +0x48  poll iterations spent waiting for the TEST UNIT READY response
+ *
+ * Physical map: payload 0x00400000 (relocated by spapr, harmless -- every
+ * address here is absolute), results 0x01000000, CRQ queue 0x02000000, TUR
+ * IU 0x02002000, READ IU 0x02001000, data buffer 0x02100000 + 256 KiB.  Run
+ * at -m 512 or more.
+ */
+    .section .text
+    .globl  _start
+_start:
+    /* r11 = result buffer  0x01000000      */
+    lis     11, 0x0100
+    /* r20 = CRQ queue      0x02000000      */
+    lis     20, 0x0200
+    /* r21 = READ IU        0x02001000      */
+    addi    21, 20, 0x1000
+    /* r19 = TUR IU         0x02002000      */
+    addi    19, 20, 0x2000
+    /* r22 = data buffer    0x02100000      */
+    lis     22, 0x0210
+
+    /* ---- clear the result buffer (80 bytes) ---- */
+    li      0, 0
+    li      9, 0
+1:  stdx    0, 11, 9
+    addi    9, 9, 8
+    cmpwi   9, 80
+    blt     1b
+
+    /* ---- clear the CRQ queue page: we poll it, so it must start empty ---- */
+    li      0, 0
+    li      9, 0
+2:  stdx    0, 20, 9
+    addi    9, 9, 8
+    cmpwi   9, 0x1000
+    blt     2b
+
+    /* ---- poison the whole 256 KiB data buffer ---- */
+    lis     8, 0xEEEE
+    ori     8, 8, 0xEEEE
+    li      9, 0
+    /* 0x00040000 bytes                     */
+    lis     10, 0x0004
+3:  stwx    8, 22, 9
+    addi    9, 9, 4
+    cmpw    9, 10
+    blt     3b
+
+    /* ---- find the vSCSI unit: H_FREE_CRQ, then H_REG_CRQ ----
+     * H_REG_CRQ gives H_PARAMETER for a unit that does not exist,
+     * H_NOT_FOUND for one without a CRQ (a vty), so the first success
+     * is the vSCSI adapter.  The unit that answered is recorded, so a
+     * wrong guess is visible rather than silent. */
+    /* candidate unit 0x71000000            */
+    lis     23, 0x7100
+    li      24, 0
+/* H_FREE_CRQ                           */
+4:  li      3, 0x100
+    mr      4, 23
+    sc      1
+    /* H_REG_CRQ                            */
+    li      3, 0xFC
+    mr      4, 23
+    /* queue address (IOVA)                 */
+    mr      5, 20
+    li      6, 0
+    /* queue length 4096                    */
+    ori     6, 6, 0x1000
+    sc      1
+    cmpdi   3, 0
+    beq     5f
+    addi    23, 23, 1
+    addi    24, 24, 1
+    cmpwi   24, 8
+    blt     4b
+    /* no unit answered -- record and stop  */
+    std     3, 0(11)
+    b       99f
+/* +0x00 H_REG_CRQ rc                   */
+5:  std     3, 0(11)
+    /* +0x08 unit that answered             */
+    std     23, 8(11)
+
+    /* ---- identity-map 0x02000000..0x02200000 into the TCE window ----
+     * SPAPR_VIO_LIOBN(reg) == reg, page shift 12, window base 0,
+     * so ioba == phys. */
+    lis     25, 0x0200
+    li      26, 0
+/* H_PUT_TCE                            */
+6:  li      3, 0x20
+    /* liobn == unit                        */
+    mr      4, 23
+    /* ioba                                 */
+    mr      5, 25
+    /* tce = page | read | write            */
+    ori     6, 25, 3
+    sc      1
+    addi    25, 25, 0x1000
+    addi    26, 26, 1
+    cmpwi   26, 512
+    blt     6b
+    /* +0x10 last H_PUT_TCE rc              */
+    std     3, 16(11)
+
+    /* ================= 1. TEST UNIT READY, to eat the UNIT ATTENTION =========
+     * A freshly reset SCSI device answers its first command with
+     * sense key 0x06 / ASC 0x29 (power on, reset or bus device reset).  Without
+     * it the READ below fails with CHECK CONDITION and transfers
+     * nothing. */
+    li      0, 0
+    li      9, 0
+7:  stdx    0, 19, 9
+    addi    9, 9, 8
+    cmpwi   9, 48
+    blt     7b
+    li      0, 0x02
+    /* opcode = SRP_CMD                     */
+    stb     0, 0(19)
+                                    /* buf_fmt 0 = no data          */
+    lis     0, 0x8000
+    /* lun = 0x8000000000000000             */
+    stw     0, 20(19)
+                                    /* cdb[0] +32 = TEST UNIT READY */
+    /* H_SEND_CRQ (0x104 is H_VIO_SIGNAL!)  */
+    li      3, 0x108
+    mr      4, 23
+    lis     5, 0x8001
+    sldi    5, 5, 32
+    /* 80 01 00 00 0000 0030 (IU_length 48) */
+    ori     5, 5, 0x0030
+    mr      6, 19
+    sc      1
+
+    li      27, 0
+    /* poll cap ~67M                        */
+    lis     28, 0x0400
+/* response lands in queue slot 0       */
+8:  lbz     0, 0(20)
+    cmpwi   0, 0
+    bne     9f
+    addi    27, 27, 1
+    cmpd    27, 28
+    blt     8b
+/* +0x48 TUR poll iterations            */
+9:  std     27, 72(11)
+    /* srp_rsp.status                       */
+    lbz     0, 19(19)
+    /* +0x40 TUR status (2 = the UA)        */
+    std     0, 64(11)
+
+    /* ============ 2. the READ_10 that reproduces the defect ======== */
+    li      0, 0
+    li      9, 0
+10: stdx    0, 21, 9
+    addi    9, 9, 8
+    cmpwi   9, 64
+    blt     10b
+
+    li      0, 0x02
+    /* opcode = SRP_CMD                     */
+    stb     0, 0(21)
+    li      0, 0x01
+    /* buf_fmt: data-in = DIRECT            */
+    stb     0, 5(21)
+    li      0, 0
+    /* data_out_desc_cnt = 0                */
+    stb     0, 6(21)
+    li      0, 1
+    /* data_in_desc_cnt  = 1                */
+    stb     0, 7(21)
+    lis     0, 0x1122
+    ori     0, 0, 0x3344
+    /* tag (any)                            */
+    stw     0, 8(21)
+    lis     0, 0x5566
+    ori     0, 0, 0x7788
+    stw     0, 12(21)
+    lis     0, 0x8000
+    /* lun = 0x8000000000000000 (SAM-5 LU)  */
+    stw     0, 20(21)
+    li      0, 0
+    stw     0, 24(21)
+                                    /* add_cdb_len at +31 stays 0   */
+    li      0, 0x28
+    /* cdb[0] = READ(10)                    */
+    stb     0, 32(21)
+    li      0, 0x02
+    /* cdb[7..8] = 0x0200 = 512 blocks      */
+    stb     0, 39(21)
+
+    /* srp_direct_buf { va, key, len } at add_data (+48) */
+    li      0, 0
+    stw     0, 48(21)
+    /* va  = 0x02100000                     */
+    stw     22, 52(21)
+    /* key = 0                              */
+    stw     0, 56(21)
+    lis     0, 0x0004
+    /* len = 0x00040000 = 256 KiB           */
+    stw     0, 60(21)
+
+    /* H_SEND_CRQ                           */
+    li      3, 0x108
+    mr      4, 23
+    lis     5, 0x8001
+    sldi    5, 5, 32
+    /* 80 01 00 00 0000 0040 (IU_length 64) */
+    ori     5, 5, 0x0040
+    mr      6, 21
+    sc      1
+    /* +0x18 H_SEND_CRQ rc                  */
+    std     3, 24(11)
+
+    /* the TUR response took slot 0, so this one lands in slot 1 (offset 16) */
+    addi    29, 20, 16
+    li      27, 0
+    lis     28, 0x0400
+11: lbz     0, 0(29)
+    cmpwi   0, 0
+    bne     12f
+    addi    27, 27, 1
+    cmpd    27, 28
+    blt     11b
+/* +0x20 READ poll iterations           */
+12: std     27, 32(11)
+    /* srp_rsp.status -- must be 0          */
+    lbz     0, 19(21)
+    /* +0x28                                */
+    std     0, 40(11)
+
+    /* ---- the two witnesses ---- */
+    lwz     0, 0(22)
+    /* +0x30 data[0]                        */
+    std     0, 48(11)
+    lis     8, 0x0002
+    lwzx    0, 22, 8
+    /* +0x38 data[0x20000]                  */
+    std     0, 56(11)
+
+99: b       99b
diff --git a/tests/spapr/vscsibig.elf b/tests/spapr/vscsibig.elf
new file mode 100644
index 0000000000000000000000000000000000000000..b0d472adca3298a9e78f64c701e71156bdcc3e96
GIT binary patch
literal 920
zcmb_bKWGzC9RA)*YOciL%H={3g`?71C4@jbT=FjID^y9LLzga{t`&>WNqgXdX?he2
z8H-cEMUc9<c5}&81ZTl9gPW2`u-HQTd-vx_p>DqA`~AM(-|yYK{l}|MIA_p@!zMc+
zq-Il#e~TFjmog-B;N+{^Ld*kU8c$rAvf!P>$`GvOGqfX)=81`B;GjL^rb|f;2j~`8
z9yK1qs7_*SDZ2cn`BKK4P<#vH@uo2ztD2TXey_Yzqq=-|axIRxM=v0#r%SVX-#>-R
zz_jb*LfO5;_n|uk{8JN(uigii4sf^m8%$KdsudVC<MY`L&;c7L{PP3GqQfDi>Ip~n
z*`N6mKAlBQ^$-mcVuNb$a5aBKHGef(+3u*Pn)tv1JVZFMP@)|T)z5nuvGBNX>K~)t
z+kPAGEPP-w%A;Qbs-#CAz!a~+x?-f}{5;=5r*FYJD?;kKvh_Tv=h2s?I2+iQP<e#6
zQorC~YTC#A@{tY0!}_+5SADW?$NSl|ALWx@A)KQ+hmaFKNcsAkuUMeC>j8N}?*=`x
z_u*r#hwh~!PDn$h>)t<gIek0UH5Ziu(pPWM+-_Oc$adpLRoi{O8@voQw@?e7g#oSk
nzfO@FWj{vpzNKy*CYFBBSs=?-zOr*!;5zbQ>Q7zg^*mewP}Q64

literal 0
HcmV?d00001

-- 
2.49.0.windows.1



^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-08-20 12:41 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-20 12:26 [PATCH 0/2] spapr_vscsi: fix silent data corruption above 128 KiB Bernard Ladenthin
2026-08-20 12:26 ` [PATCH 1/2] spapr_vscsi: fix data corruption on transfers " Bernard Ladenthin
2026-08-20 12:26 ` [PATCH 2/2] tests/spapr: add bare-metal reproducers for pseries machine behaviour Bernard Ladenthin

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.