* [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support
@ 2026-10-02 5:25 uverma
2026-10-02 5:25 ` [RFC PATCH v2 1/8] ppc/spapr: add PReP boot partition detection uverma
` (8 more replies)
0 siblings, 9 replies; 18+ messages in thread
From: uverma @ 2026-10-02 5:25 UTC (permalink / raw)
To: qemu-devel, qemu-ppc, aik, nnmlinux, avnish
Cc: pbonzini, th.huth, sbhat, harshpb, amachhiw, rathc, balaton,
philmd, npiggin, marcandre.lureau, fam, alex.bennee,
richard.henderson, Utkarsh Verma
From: Utkarsh Verma <uverma@linux.ibm.com>
This series adds support for booting from a disk image under the Virtual Open
Firmware (VOF) path for sPAPR/pseries machines in QEMU.
The most common use case for pseries VMs is booting from a distro qcow2 disk
image. These images carry a PReP boot partition containing GRUB2, which in turn
loads the kernel and initrd via IEEE 1275 client interface calls.
VOF currently only supports direct -kernel/-initrd boot; there is no
firmware-mediated path to hand off to a bootloader on disk. This series
implements that missing path, making VOF capable of handling the standard
distro qcow2 boot flow without requiring SLOF.
This series takes reference from an earlier implementation by Alexey
Kardashevskiy [1] which explored the same concept but was not carried forward at
the time.
The existing load_elf function works on a file, whereas in the VOF the PReP
partition (GRUB) is read from the disk image and loaded in the memory, also
on loading the PReP partition in the guest memory the /memory@0/available FDT
needs to be properly updated for each PT_LOAD segment of the core.elf file
of the GRUB (PReP partition).
Implemented a new public elf loader API load_elf_ram_sym_buf() to resolve both
these issues.
- load_elf_ram_sym_buf() accepts a caller-supplied memory buffer instead of a
filename, using g_file_open_tmp() to obtain an anonymous file descriptor for
the caller-supplied buffer. This means the ELF image has 2 copies, the
caller's heap buffer and the temporary file. For typical PReP partition sizes
(~4 MB) this is acceptable, but if a more memory-efficient approach is
preferred we are open to alternatives. Feedback on the preferred direction is
welcome.
- Also, added optional segment_fn_t callback in load_elf_ram_sym_buf() through
the internal elf_ops.h.inc template; it is invoked once per PT_LOAD segment
after the load address is resolved, allowing callers to perform per-segment
work such as VOF memory claims.
When no -kernel is given, spapr_vof_reset() calls a newly implemented function
spapr_vof_try_prep_boot() which iterates attached block backends, scans
partitions for a PReP boot partition, reads the partition contents into a
host buffer, calls load_elf_ram_sym_buf() to load the GRUB in the guest memory,
and on success stores the ELF entry point in spapr->kernel_addr.
spapr_vof_client_dt_finalize() then writes the entry point into
/chosen/qemu,boot-kernel so VOF then transfers control to GRUB.
- Partition scanner (spapr_vof_partition.c): scans MBR and GPT partition tables
on a BlockBackend to locate the PReP boot partition.
- Core loader extension (hw/core/loader.c): new load_elf_ram_sym_buf() API loads
a 32/64-bit ELF binary from a caller-supplied memory buffer, reusing the
existing load_elf32/load_elf64 paths. A new segment_fn_t callback in
elf_ops.h.inc lets callers act on each PT_LOAD segment as it is resolved.
- read, seek OF services: two previously unimplemented IEEE 1275 client
services, covering block device I/O needed by GRUB.
- vscsi-report-luns method: walks the SCSIBus and builds the LUN report table
in guest memory in the format expected by GRUB's ofdisk driver, matching
SLOF's dev-generate-srplun encoding.
- VTY write/read routing: routes console I/O through the VTY chardev backend;
promotes vty_getchars to external linkage. Also opens a stdin ihandle in
addition to stdout so GRUB can read from the console.
- FDT "disk" wildcard child node (spapr_vscsi.c): emits a single generic "disk"
child FDT node under the vSCSI adapter (without a per-LUN suffix), matching
the actual Open Firmware behaviour. The path_offset() wildcard fallback
resolves any "disk@<srp-lun>" open request to this shared node; the LUN is
extracted from the path string at runtime in vof_do_open().
We used AI to build prototype with manual changes incorporated as identified
during debugging multiple issues faced while making it work, hence using below
tag as being discussed in the ongoing AI policy patch:
[PATCH v4 0/4] docs/devel, AGENTS.md: a completely new policy on AI-generated contributions
Link to the patch [2].
AI-used-for: Code
Testing:
The series has been tested end-to-end by booting a RHEL9.7 qcow2 image on a
pseries machine with VOF enabled (no SLOF).
build$ ./qemu-system-ppc64 -M pseries,x-vof=on -cpu power9 -m 2G \
-hda ../../rhel-guest-image-9.7-20251021.0.ppc64le.qcow2 -nographic
qemu-system-ppc64: warning: TCG doesn't support requested feature, cap-cfpc=workaround
qemu-system-ppc64: warning: TCG doesn't support requested feature, cap-ibs=workaround
qemu-system-ppc64: warning: TCG doesn't support requested feature, cap-ccf-assist=on
Welcome to GRUB!
<snip>
GRUB version 2.06
+----------------------------------------------------------------------------+
|*Red Hat Enterprise Linux (5.14.0-611.5.1.el9_7.ppc64le) 9.7 (Plow) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
+----------------------------------------------------------------------------+
Use the ^ and v keys to select which entry is highlighted.
Press enter to boot the selected OS, `e' to edit the commands
before booting or `c' for a command-line.
<snip>
Red Hat Enterprise Linux 9.7 (Plow)
Kernel 5.14.0-611.5.1.el9_7.ppc64le on an ppc64le
Activate the web console with: systemctl enable --now cockpit.socket
localhost login: root
Password:
Last login: Fri Jul 10 01:45:44 on hvc0
[root@localhost ~]#
[root@localhost ~]# ls /
afs boot etc lib media opt root sbin sys usr
bin dev home lib64 mnt proc run srv tmp var
[root@localhost ~]#
[root@localhost ~]# echo "Hello, World!" > hello.txt
[root@localhost ~]# cat hello.txt
Hello, World!
[root@localhost ~]#
[1] https://lore.kernel.org/qemu-devel/20200203032943.121178-1-aik@ozlabs.ru/#t
[2] https://lore.kernel.org/qemu-devel/20260930085433.830743-1-pbonzini@redhat.com/
Utkarsh Verma (8):
ppc/spapr: add PReP boot partition detection
hw/loader: add load_elf_ram_sym_buf() for in-memory ELF loading
ppc/spapr: add baseline VOF disk boot support
ppc/spapr: add VTY backend support to OF read/write/open services
ppc/spapr: add block device backend to VOF open/read/write/seek
services
spapr_vscsi: add VOF disk nodes to the device tree
ppc/spapr: strip OF path argument suffix in path_offset
ppc/spapr: implement vscsi-report-luns call-method for PAPR vSCSI
hw/char/spapr_vty.c | 2 +-
hw/core/loader.c | 81 +++++-
hw/ppc/meson.build | 5 +-
hw/ppc/spapr_vof.c | 164 +++++++++++-
hw/ppc/spapr_vof_partition.c | 272 ++++++++++++++++++++
hw/ppc/trace-events | 4 +
hw/ppc/vof.c | 475 ++++++++++++++++++++++++++++++++++-
hw/scsi/spapr_vscsi.c | 23 ++
include/hw/core/loader.h | 30 +++
include/hw/elf_ops.h.inc | 12 +-
include/hw/ppc/spapr_vio.h | 1 +
include/hw/ppc/spapr_vof.h | 14 ++
include/hw/ppc/vof.h | 2 +
13 files changed, 1070 insertions(+), 15 deletions(-)
create mode 100644 hw/ppc/spapr_vof_partition.c
create mode 100644 include/hw/ppc/spapr_vof.h
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [RFC PATCH v2 1/8] ppc/spapr: add PReP boot partition detection
2026-10-02 5:25 [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support uverma
@ 2026-10-02 5:25 ` uverma
2026-10-02 5:25 ` [RFC PATCH v2 2/8] hw/loader: add load_elf_ram_sym_buf() for in-memory ELF loading uverma
` (7 subsequent siblings)
8 siblings, 0 replies; 18+ messages in thread
From: uverma @ 2026-10-02 5:25 UTC (permalink / raw)
To: qemu-devel, qemu-ppc, aik, nnmlinux, avnish
Cc: pbonzini, th.huth, sbhat, harshpb, amachhiw, rathc, balaton,
philmd, npiggin, marcandre.lureau, fam, alex.bennee,
richard.henderson, Utkarsh Verma
From: Utkarsh Verma <uverma@linux.ibm.com>
Add spapr_vof_partition.c which implements basic MBR and GPT partition
table scanning to locate the PReP boot partition during VOF enabled boot.
AI-used-for: code
Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
---
Chaneglog:
v2:
- Added CRC validations for GPT.
- Added bounds checking GPT header size.
- Added new GPT_MAX_PARTITION to cap max partitions at 128
- Added bounds checking for the partition entry array against disk size
for GPT.
- Switched GPT entry to use a fixed-size buffer instead of g_malloc per
entry.
- Added validation for first_lba and last_lba in GPT before returning
size.
- Added validation for MBR PReP partition entries(start_lba/num_sectors)
---
hw/ppc/meson.build | 5 +-
hw/ppc/spapr_vof_partition.c | 272 +++++++++++++++++++++++++++++++++++
include/hw/ppc/spapr_vof.h | 14 ++
3 files changed, 290 insertions(+), 1 deletion(-)
create mode 100644 hw/ppc/spapr_vof_partition.c
create mode 100644 include/hw/ppc/spapr_vof.h
diff --git a/hw/ppc/meson.build b/hw/ppc/meson.build
index 37aa535db2..68a5d2622f 100644
--- a/hw/ppc/meson.build
+++ b/hw/ppc/meson.build
@@ -93,6 +93,9 @@ ppc_ss.add(when: 'CONFIG_AMIGAONE', if_true: files('amigaone.c'))
ppc_ss.add(when: 'CONFIG_PEGASOS', if_true: files('pegasos.c'))
ppc_ss.add(when: 'CONFIG_VOF', if_true: files('vof.c'))
-ppc_ss.add(when: ['CONFIG_VOF', 'CONFIG_PSERIES'], if_true: files('spapr_vof.c'))
+ppc_ss.add(when: ['CONFIG_VOF', 'CONFIG_PSERIES'], if_true: files(
+ 'spapr_vof.c',
+ 'spapr_vof_partition.c',
+))
hw_arch += {'ppc': ppc_ss}
diff --git a/hw/ppc/spapr_vof_partition.c b/hw/ppc/spapr_vof_partition.c
new file mode 100644
index 0000000000..b495a53d9d
--- /dev/null
+++ b/hw/ppc/spapr_vof_partition.c
@@ -0,0 +1,272 @@
+/*
+ * QEMU PowerPC sPAPR VOF Partition Table Support.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This implements partition table detection for VOF boot,
+ * supporting MBR and GPT partition tables with focus on PReP boot partition.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bswap.h"
+#include "qemu/error-report.h"
+#include "system/block-backend-io.h"
+#include "hw/ppc/spapr_vof.h"
+#include <zlib.h>
+
+#define SECTOR_SIZE 512
+#define MBR_PARTITION_ENTRY_SIZE 16
+#define MBR_PARTITION_TABLE_OFFSET 446
+#define MBR_NUM_PARTITIONS 4
+
+#define PARTITION_TYPE_PREP 0x41 /* PReP Boot partition */
+#define PARTITION_TYPE_GPT 0xEE
+
+#define GPT_MAX_PARTITION 128
+#define GPT_SIGNATURE "EFI PART"
+#define GPT_SIGNATURE_SIZE 8
+
+/* PReP Boot: 9E1A2D38-C612-4316-AA26-8B49521E5A8B */
+static const uint8_t GUID_PREP_BOOT[16] = {
+ 0x38, 0x2D, 0x1A, 0x9E, 0x12, 0xC6, 0x16, 0x43,
+ 0xAA, 0x26, 0x8B, 0x49, 0x52, 0x1E, 0x5A, 0x8B
+};
+
+typedef struct MBRPartitionEntry {
+ uint8_t boot_flag;
+ uint8_t start_chs[3];
+ uint8_t type;
+ uint8_t end_chs[3];
+ uint32_t start_lba;
+ uint32_t num_sectors;
+} QEMU_PACKED MBRPartitionEntry;
+
+typedef struct GPTHeader {
+ char signature[8];
+ uint32_t revision;
+ uint32_t header_size;
+ uint32_t header_crc32;
+ uint32_t reserved;
+ uint64_t current_lba;
+ uint64_t backup_lba;
+ uint64_t first_usable_lba;
+ uint64_t last_usable_lba;
+ uint8_t disk_guid[16];
+ uint64_t partition_entries_lba;
+ uint32_t num_partition_entries;
+ uint32_t partition_entry_size;
+ uint32_t partition_array_crc32;
+} QEMU_PACKED GPTHeader;
+
+typedef struct GPTPartitionEntry {
+ uint8_t type_guid[16];
+ uint8_t partition_guid[16];
+ uint64_t first_lba;
+ uint64_t last_lba;
+ uint64_t attributes;
+ uint16_t name[36];
+} QEMU_PACKED GPTPartitionEntry;
+
+static bool find_prep_partition_gpt(BlockBackend *blk,
+ uint64_t *offset, uint64_t *size)
+{
+ uint8_t lba1[SECTOR_SIZE];
+ GPTHeader *header;
+ uint32_t header_size;
+ GPTHeader header_copy;
+ uint32_t crc_stored;
+ uint32_t crc_computed;
+ uint64_t entries_lba;
+ uint32_t num_entries;
+ uint32_t entry_size;
+ int64_t disk_size;
+ uint64_t disk_last_lba;
+ uint32_t array_bytes;
+ g_autofree uint8_t *array_buf = NULL;
+ uint8_t buf[sizeof(GPTPartitionEntry)];
+ GPTPartitionEntry *current_entry;
+ uint32_t i;
+ int ret;
+
+ ret = blk_pread(blk, SECTOR_SIZE, SECTOR_SIZE, lba1, 0);
+ if (ret < 0) {
+ warn_report("GPT: failed to read LBA1 (ret=%d)", ret);
+ return false;
+ }
+
+ header = (GPTHeader *)lba1;
+
+ /* EFI PART signature validation */
+ if (memcmp(header->signature, GPT_SIGNATURE, GPT_SIGNATURE_SIZE) != 0) {
+ return false;
+ }
+
+ /* GPT header size validation */
+ header_size = le32_to_cpu(header->header_size);
+ if (header_size < sizeof(GPTHeader) || header_size > SECTOR_SIZE) {
+ warn_report("GPT: invalid header size %u (expected %zu..%u)",
+ header_size, sizeof(GPTHeader), SECTOR_SIZE);
+ return false;
+ }
+
+ /* CRC32 validation of GPT header */
+ header_copy = *header;
+ header_copy.header_crc32 = 0;
+ crc_stored = le32_to_cpu(header->header_crc32);
+ crc_computed = crc32(0, (uint8_t *)&header_copy, header_size);
+ if (crc_computed != crc_stored) {
+ warn_report("GPT: header CRC32 mismatch (computed=0x%08x "
+ "stored=0x%08x)", crc_computed, crc_stored);
+ return false;
+ }
+
+ entries_lba = le64_to_cpu(header->partition_entries_lba);
+ num_entries = le32_to_cpu(header->num_partition_entries);
+ entry_size = le32_to_cpu(header->partition_entry_size);
+
+ /*
+ * Usually the PReP is the first partition and most systems commonly have
+ * 128 max partitions, so cap the scan at 128
+ */
+ if (num_entries > GPT_MAX_PARTITION) {
+ warn_report("GPT: num_partition_entries %u exceeds %u, "
+ "scanning only the first %u partitions", num_entries,
+ GPT_MAX_PARTITION, GPT_MAX_PARTITION);
+ num_entries = GPT_MAX_PARTITION;
+ }
+
+ if (num_entries == 0) {
+ warn_report("GPT: partition table has no entries");
+ return false;
+ }
+
+ if (entry_size < sizeof(GPTPartitionEntry)) {
+ warn_report("GPT: partition entry size %u is too small", entry_size);
+ return false;
+ }
+
+ disk_size = blk_getlength(blk);
+ if (disk_size <= 0) {
+ warn_report("GPT: failed to get disk size");
+ return false;
+ }
+ disk_last_lba = (uint64_t)disk_size / SECTOR_SIZE - 1;
+
+ if (entries_lba >= UINT64_MAX / SECTOR_SIZE ||
+ entries_lba >= (uint64_t)disk_size / SECTOR_SIZE ||
+ num_entries > ((uint64_t)disk_size - entries_lba * SECTOR_SIZE)
+ / entry_size) {
+ warn_report("GPT: partition entry array extends beyond disk");
+ return false;
+ }
+
+ /* CRC32 validation of GPT partition array */
+ array_bytes = num_entries * entry_size;
+ array_buf = g_malloc(array_bytes);
+ crc_stored = le32_to_cpu(header->partition_array_crc32);
+
+ ret = blk_pread(blk, entries_lba * SECTOR_SIZE, array_bytes, array_buf, 0);
+ if (ret < 0) {
+ warn_report("GPT: failed to read partition entry array for CRC "
+ "(ret=%d)", ret);
+ return false;
+ }
+ crc_computed = crc32(0, array_buf, array_bytes);
+ if (crc_computed != crc_stored) {
+ warn_report("GPT: partition array CRC32 mismatch (computed=0x%08x "
+ "stored=0x%08x)", crc_computed, crc_stored);
+ return false;
+ }
+
+ for (i = 0; i < num_entries; i++) {
+ uint64_t current_entry_offset = entries_lba * SECTOR_SIZE +
+ (uint64_t)i * entry_size;
+ ret = blk_pread(blk, current_entry_offset, sizeof(buf), buf, 0);
+ if (ret < 0) {
+ warn_report("GPT: failed to read partition entry %u (ret=%d)",
+ i, ret);
+ return false;
+ }
+
+ current_entry = (GPTPartitionEntry *)buf;
+ if (memcmp(current_entry->type_guid, GUID_PREP_BOOT,
+ sizeof(GUID_PREP_BOOT)) == 0) {
+ uint64_t first_lba = le64_to_cpu(current_entry->first_lba);
+ uint64_t last_lba = le64_to_cpu(current_entry->last_lba);
+
+ if (last_lba < first_lba) {
+ warn_report("GPT: PReP entry has "
+ "last_lba (%"PRIu64") < first_lba (%"PRIu64"), "
+ "skipping", last_lba, first_lba);
+ continue;
+ }
+ if (first_lba > disk_last_lba || last_lba > disk_last_lba) {
+ warn_report("GPT: PReP entry LBA range [%"PRIu64", %"PRIu64"] "
+ "exceeds disk (%"PRIu64"), skipping",
+ first_lba, last_lba, disk_last_lba);
+ continue;
+ }
+
+ *offset = first_lba * SECTOR_SIZE;
+ *size = (last_lba - first_lba + 1) * SECTOR_SIZE;
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool find_prep_partition_mbr(BlockBackend *blk,
+ uint64_t *offset, uint64_t *size)
+{
+ uint8_t mbr[SECTOR_SIZE];
+ MBRPartitionEntry *entry;
+ int ret;
+ int i;
+
+ ret = blk_pread(blk, 0, SECTOR_SIZE, mbr, 0);
+ if (ret < 0) {
+ warn_report("MBR: failed to read MBR sector (ret=%d)", ret);
+ return false;
+ }
+
+ /* MBR boot signature: byte 510 = 0x55, byte 511 = 0xAA */
+ if (mbr[510] != 0x55 || mbr[511] != 0xAA) {
+ warn_report("MBR: invalid boot signature (0x%02x 0x%02x), "
+ "expected 0x55 0xAA", mbr[510], mbr[511]);
+ return false;
+ }
+
+ for (i = 0; i < MBR_NUM_PARTITIONS; i++) {
+ entry = (MBRPartitionEntry *)&mbr[MBR_PARTITION_TABLE_OFFSET +
+ i * MBR_PARTITION_ENTRY_SIZE];
+
+ if (entry->type == PARTITION_TYPE_GPT) {
+ return find_prep_partition_gpt(blk, offset, size);
+ }
+
+ if (entry->type == PARTITION_TYPE_PREP) {
+ uint32_t start_lba = le32_to_cpu(entry->start_lba);
+ uint32_t num_sectors = le32_to_cpu(entry->num_sectors);
+
+ if (start_lba == 0 || num_sectors == 0) {
+ warn_report("MBR: PReP partition entry %d has invalid "
+ "start_lba=%u or num_sectors=%u, skipping",
+ i, start_lba, num_sectors);
+ continue;
+ }
+ *offset = (uint64_t)start_lba * SECTOR_SIZE;
+ *size = (uint64_t)num_sectors * SECTOR_SIZE;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool spapr_vof_find_prep_partition(BlockBackend *blk,
+ uint64_t *offset, uint64_t *size)
+{
+ if (!blk) {
+ return false;
+ }
+ return find_prep_partition_mbr(blk, offset, size);
+}
diff --git a/include/hw/ppc/spapr_vof.h b/include/hw/ppc/spapr_vof.h
new file mode 100644
index 0000000000..08787d2aeb
--- /dev/null
+++ b/include/hw/ppc/spapr_vof.h
@@ -0,0 +1,14 @@
+/*
+ * QEMU PowerPC sPAPR VOF Support
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef HW_SPAPR_VOF_H
+#define HW_SPAPR_VOF_H
+
+typedef struct BlockBackend BlockBackend;
+
+bool spapr_vof_find_prep_partition(BlockBackend *blk,
+ uint64_t *offset, uint64_t *size);
+
+#endif /* HW_SPAPR_VOF_H */
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC PATCH v2 2/8] hw/loader: add load_elf_ram_sym_buf() for in-memory ELF loading
2026-10-02 5:25 [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support uverma
2026-10-02 5:25 ` [RFC PATCH v2 1/8] ppc/spapr: add PReP boot partition detection uverma
@ 2026-10-02 5:25 ` uverma
2026-10-06 8:10 ` Utkarsh Verma
2026-10-02 5:25 ` [RFC PATCH v2 3/8] ppc/spapr: add baseline VOF disk boot support uverma
` (6 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: uverma @ 2026-10-02 5:25 UTC (permalink / raw)
To: qemu-devel, qemu-ppc, aik, nnmlinux, avnish
Cc: pbonzini, th.huth, sbhat, harshpb, amachhiw, rathc, balaton,
philmd, npiggin, marcandre.lureau, fam, alex.bennee,
richard.henderson, Utkarsh Verma
From: Utkarsh Verma <uverma@linux.ibm.com>
Add a new public API load_elf_ram_sym_buf() that loads an ELF image
from a caller-supplied memory buffer rather than a file path.
The implementation creates a temporary file via g_file_open_tmp(), then
reuses the existing load_elf32/load_elf64 paths.
To support per-segment inspection by callers (e.g. detecting VoF
firmware segments), extend the internal load_elf{32,64} template
(elf_ops.h.inc) with an optional segment_fn_t callback that is
invoked once for every PT_LOAD segment after its load address is
resolved. Returning false from the callback aborts the load with
ELF_LOAD_FAILED.
AI-used-for: code
Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
---
Chaneglog:
v2:
- Replaced memfd_create() with g_file_open_tmp()
- Replaced write() call with qemu_write_full()
---
hw/core/loader.c | 81 +++++++++++++++++++++++++++++++++++++++-
include/hw/core/loader.h | 30 +++++++++++++++
include/hw/elf_ops.h.inc | 12 +++++-
3 files changed, 120 insertions(+), 3 deletions(-)
diff --git a/hw/core/loader.c b/hw/core/loader.c
index 5cbfba0a86..fa04c59ed0 100644
--- a/hw/core/loader.c
+++ b/hw/core/loader.c
@@ -510,12 +510,14 @@ ssize_t load_elf_ram_sym(const char *filename,
ret = load_elf64(filename, fd, elf_note_fn,
translate_fn, translate_opaque, must_swab,
pentry, lowaddr, highaddr, pflags, elf_machine,
- clear_lsb, data_swab, as, load_rom, sym_cb);
+ clear_lsb, data_swab, as, load_rom, sym_cb,
+ NULL, NULL);
} else {
ret = load_elf32(filename, fd, elf_note_fn,
translate_fn, translate_opaque, must_swab,
pentry, lowaddr, highaddr, pflags, elf_machine,
- clear_lsb, data_swab, as, load_rom, sym_cb);
+ clear_lsb, data_swab, as, load_rom, sym_cb,
+ NULL, NULL);
}
if (ret > 0) {
@@ -527,6 +529,81 @@ ssize_t load_elf_ram_sym(const char *filename,
return ret;
}
+ssize_t load_elf_ram_sym_buf(const uint8_t *buf, size_t buflen,
+ uint64_t (*elf_note_fn)(void *, void *, bool),
+ uint64_t (*translate_fn)(void *, uint64_t),
+ void *translate_opaque, uint64_t *pentry,
+ uint64_t *lowaddr, uint64_t *highaddr,
+ uint32_t *pflags, int elf_data_order,
+ int elf_machine, int clear_lsb, int data_swab,
+ AddressSpace *as, bool load_rom,
+ symbol_fn_t sym_cb,
+ segment_fn_t segment_fn, void *segment_opaque)
+{
+ const int host_data_order = HOST_BIG_ENDIAN ? ELFDATA2MSB : ELFDATA2LSB;
+ int fd, must_swab;
+ ssize_t ret = ELF_LOAD_FAILED;
+ uint8_t e_ident[EI_NIDENT];
+ gchar *tmp_path = NULL;
+ GError *gerr = NULL;
+
+ fd = g_file_open_tmp("qemu-elf-buf-XXXXXX", &tmp_path, &gerr);
+ if (fd < 0) {
+ error_report("load_elf_ram_sym_buf: %s", gerr->message);
+ g_error_free(gerr);
+ return ELF_LOAD_FAILED;
+ }
+ g_unlink(tmp_path);
+ g_free(tmp_path);
+
+ if (qemu_write_full(fd, buf, buflen) != buflen) {
+ error_report("load_elf_ram_sym_buf: write: %s", strerror(errno));
+ goto fail;
+ }
+
+ lseek(fd, 0, SEEK_SET);
+ if (read(fd, e_ident, sizeof(e_ident)) != sizeof(e_ident)) {
+ goto fail;
+ }
+ if (e_ident[0] != ELFMAG0 ||
+ e_ident[1] != ELFMAG1 ||
+ e_ident[2] != ELFMAG2 ||
+ e_ident[3] != ELFMAG3) {
+ ret = ELF_LOAD_NOT_ELF;
+ goto fail;
+ }
+
+ if (elf_data_order != ELFDATANONE && elf_data_order != e_ident[EI_DATA]) {
+ ret = ELF_LOAD_WRONG_ENDIAN;
+ goto fail;
+ }
+
+ must_swab = host_data_order != e_ident[EI_DATA];
+
+ lseek(fd, 0, SEEK_SET);
+ if (e_ident[EI_CLASS] == ELFCLASS64) {
+ ret = load_elf64("(buffer)", fd, elf_note_fn,
+ translate_fn, translate_opaque, must_swab,
+ pentry, lowaddr, highaddr, pflags, elf_machine,
+ clear_lsb, data_swab, as, load_rom, sym_cb,
+ segment_fn, segment_opaque);
+ } else {
+ ret = load_elf32("(buffer)", fd, elf_note_fn,
+ translate_fn, translate_opaque, must_swab,
+ pentry, lowaddr, highaddr, pflags, elf_machine,
+ clear_lsb, data_swab, as, load_rom, sym_cb,
+ segment_fn, segment_opaque);
+ }
+
+ if (ret > 0) {
+ debuginfo_report_elf("(buffer)", fd, 0);
+ }
+
+ fail:
+ close(fd);
+ return ret;
+}
+
static void bswap_uboot_header(uboot_image_header_t *hdr)
{
#if !HOST_BIG_ENDIAN
diff --git a/include/hw/core/loader.h b/include/hw/core/loader.h
index d9431e8a8d..b67b2ca013 100644
--- a/include/hw/core/loader.h
+++ b/include/hw/core/loader.h
@@ -156,6 +156,36 @@ ssize_t load_elf_ram_sym(const char *filename,
int clear_lsb, int data_swab,
AddressSpace *as, bool load_rom, symbol_fn_t sym_cb);
+/*
+ * segment_fn_t:
+ * Per-PT_LOAD segment callback for load_elf_ram_sym_buf().
+ */
+typedef bool (*segment_fn_t)(void *opaque,
+ uint64_t paddr, uint64_t vaddr,
+ uint64_t filesz, uint64_t memsz);
+
+/*
+ * load_elf_ram_sym_buf:
+ * @buf: pointer to an in-memory ELF image
+ * @buflen: size of @buf in bytes
+ * @segment_fn: optional per-PT_LOAD callback
+ * @segment_opaque: opaque data passed to @segment_fn
+ *
+ * Identical to load_elf_ram_sym() but loads from a caller-supplied
+ * memory buffer instead of a file. All other parameters have the
+ * same meaning as load_elf_ram_sym().
+ */
+ssize_t load_elf_ram_sym_buf(const uint8_t *buf, size_t buflen,
+ uint64_t (*elf_note_fn)(void *, void *, bool),
+ uint64_t (*translate_fn)(void *, uint64_t),
+ void *translate_opaque, uint64_t *pentry,
+ uint64_t *lowaddr, uint64_t *highaddr,
+ uint32_t *pflags, int elf_data_order,
+ int elf_machine, int clear_lsb, int data_swab,
+ AddressSpace *as, bool load_rom,
+ symbol_fn_t sym_cb,
+ segment_fn_t segment_fn, void *segment_opaque);
+
/** load_elf_as:
* Same as load_elf_ram_sym(), but always loads the elf as ROM
*/
diff --git a/include/hw/elf_ops.h.inc b/include/hw/elf_ops.h.inc
index 044e72de2a..a2c9b52bf9 100644
--- a/include/hw/elf_ops.h.inc
+++ b/include/hw/elf_ops.h.inc
@@ -321,7 +321,9 @@ static ssize_t glue(load_elf, SZ)(const char *name, int fd,
uint32_t *pflags, int elf_machine,
int clear_lsb, int data_swab,
AddressSpace *as, bool load_rom,
- symbol_fn_t sym_cb)
+ symbol_fn_t sym_cb,
+ segment_fn_t segment_fn,
+ void *segment_opaque)
{
struct elfhdr ehdr;
struct elf_phdr *phdr = NULL, *ph;
@@ -504,6 +506,14 @@ static ssize_t glue(load_elf, SZ)(const char *name, int fd,
addr = ph->p_paddr;
}
+ if (segment_fn) {
+ if (!segment_fn(segment_opaque, addr, ph->p_vaddr,
+ file_size, mem_size)) {
+ ret = ELF_LOAD_FAILED;
+ goto fail;
+ }
+ }
+
if (data_swab) {
elf_word j;
for (j = 0; j < file_size; j += (1 << data_swab)) {
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC PATCH v2 3/8] ppc/spapr: add baseline VOF disk boot support
2026-10-02 5:25 [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support uverma
2026-10-02 5:25 ` [RFC PATCH v2 1/8] ppc/spapr: add PReP boot partition detection uverma
2026-10-02 5:25 ` [RFC PATCH v2 2/8] hw/loader: add load_elf_ram_sym_buf() for in-memory ELF loading uverma
@ 2026-10-02 5:25 ` uverma
2026-10-02 5:25 ` [RFC PATCH v2 4/8] ppc/spapr: add VTY backend support to OF read/write/open services uverma
` (5 subsequent siblings)
8 siblings, 0 replies; 18+ messages in thread
From: uverma @ 2026-10-02 5:25 UTC (permalink / raw)
To: qemu-devel, qemu-ppc, aik, nnmlinux, avnish
Cc: pbonzini, th.huth, sbhat, harshpb, amachhiw, rathc, balaton,
philmd, npiggin, marcandre.lureau, fam, alex.bennee,
richard.henderson, Utkarsh Verma
From: Utkarsh Verma <uverma@linux.ibm.com>
Add initial support for disk boot via VOF.
Scan all the block devices, and for each device search for PReP partition,
load the ELF payload (GRUB), and set the '/chosen' node in FDT with boot kernel
and bootpath properties.
AI-used-for: code
Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
---
Chaneglog:
v2:
- Added bounds checking for partition size before doing g_malloc.
- Added a roll back logic for cleaning the memory map FDT for paritial
claims.
---
hw/ppc/spapr_vof.c | 162 ++++++++++++++++++++++++++++++++++++++++---
hw/ppc/vof.c | 3 +
include/hw/ppc/vof.h | 2 +
3 files changed, 159 insertions(+), 8 deletions(-)
diff --git a/hw/ppc/spapr_vof.c b/hw/ppc/spapr_vof.c
index 46d78756e6..78d5b7d5b5 100644
--- a/hw/ppc/spapr_vof.c
+++ b/hw/ppc/spapr_vof.c
@@ -10,10 +10,19 @@
#include "hw/ppc/spapr_cpu_core.h"
#include "hw/ppc/fdt.h"
#include "hw/ppc/vof.h"
+#include "hw/ppc/spapr_vof.h"
+#include "hw/core/qdev.h"
+#include "hw/core/loader.h"
#include "system/system.h"
+#include "system/block-backend.h"
+#include "system/block-backend-global-state.h"
#include "qom/qom-qobject.h"
+#include "target/ppc/cpu.h"
+#include "elf.h"
#include "trace.h"
+#define SPAPR_VOF_PREP_MAX_SIZE (10 * 1024 * 1024)
+
target_ulong spapr_h_vof_client(PowerPCCPU *cpu, SpaprMachineState *spapr,
target_ulong opcode, target_ulong *_args)
{
@@ -32,10 +41,10 @@ void spapr_vof_client_dt_finalize(SpaprMachineState *spapr, void *fdt)
vof_build_dt(fdt, spapr->vof);
- if (spapr->vof->bootargs) {
- int chosen;
+ int chosen;
+ _FDT(chosen = fdt_path_offset(fdt, "/chosen"));
- _FDT(chosen = fdt_path_offset(fdt, "/chosen"));
+ if (spapr->vof->bootargs) {
/*
* If the client did not change "bootargs", spapr_dt_chosen() must have
* stored machine->kernel_cmdline in it before getting here.
@@ -43,6 +52,22 @@ void spapr_vof_client_dt_finalize(SpaprMachineState *spapr, void *fdt)
_FDT(fdt_setprop_string(fdt, chosen, "bootargs", spapr->vof->bootargs));
}
+ if (spapr->vof->disk_boot) {
+ /*
+ * If disk boot is detected change the "qemu,boot-kernel" to hold
+ * kernel_addr/kernel_size which contain the GRUB entry point and size
+ */
+ uint64_t kern[2];
+ kern[0] = cpu_to_be64(spapr->kernel_addr);
+ kern[1] = cpu_to_be64(spapr->kernel_size);
+ _FDT(fdt_setprop(fdt, chosen, "qemu,boot-kernel", &kern, sizeof(kern)));
+
+ if (spapr->vof->bootpath) {
+ _FDT(fdt_setprop_string(fdt, chosen, "bootpath",
+ spapr->vof->bootpath));
+ }
+ }
+
/*
* SLOF-less setup requires an open instance of stdout for early
* kernel printk. By now all phandles are settled so we can open
@@ -54,11 +79,116 @@ void spapr_vof_client_dt_finalize(SpaprMachineState *spapr, void *fdt)
}
}
+static bool vof_elf_segment_cb(void *opaque,
+ uint64_t paddr, uint64_t vaddr,
+ uint64_t filesz, uint64_t memsz)
+{
+ Vof *vof = opaque;
+
+ if (memsz == 0) {
+ return true;
+ }
+
+ if (paddr != vaddr) {
+ error_report("spapr_vof_load_elf: segment paddr/vaddr mismatch "
+ "(paddr=0x%" PRIx64 " vaddr=0x%" PRIx64 ")",
+ paddr, vaddr);
+ return false;
+ }
+
+ if (vof_claim(vof, paddr, memsz, 0) == -1) {
+ error_report("spapr_vof_load_elf: vof_claim failed for "
+ "paddr=0x%" PRIx64 " size=0x%" PRIx64, paddr, memsz);
+ return false;
+ }
+
+ return true;
+}
+
+static bool spapr_vof_try_prep_boot(SpaprMachineState *spapr, Vof *vof)
+{
+ BlockBackend *blk;
+ DeviceState *dev;
+ uint64_t partition_offset;
+ uint64_t partition_size;
+ uint8_t *prep_data;
+ uint64_t entry_point;
+ uint64_t load_size;
+ int64_t blk_len;
+
+ for (blk = blk_next(NULL); blk; blk = blk_next(blk)) {
+
+ if (!blk_is_inserted(blk)) {
+ continue;
+ }
+
+ partition_offset = 0;
+ partition_size = 0;
+
+ if (!spapr_vof_find_prep_partition(blk, &partition_offset,
+ &partition_size)) {
+ continue;
+ }
+
+ blk_len = blk_getlength(blk);
+ if (blk_len < 0 ||
+ partition_size == 0 ||
+ partition_size > SPAPR_VOF_PREP_MAX_SIZE ||
+ partition_offset > (uint64_t)blk_len ||
+ partition_size > (uint64_t)blk_len - partition_offset) {
+ continue;
+ }
+
+ prep_data = g_malloc(partition_size);
+ if (blk_pread(blk, partition_offset, partition_size,
+ prep_data, 0) < 0) {
+ g_free(prep_data);
+ continue;
+ }
+
+ guint saved_claimed_len = vof->claimed->len;
+ uint64_t saved_claimed_base = vof->claimed_base;
+
+ uint64_t lowaddr = UINT64_MAX, highaddr = 0;
+ entry_point = 0;
+ ssize_t ret = load_elf_ram_sym_buf(prep_data, partition_size,
+ NULL, NULL, NULL,
+ &entry_point, &lowaddr, &highaddr,
+ NULL, ELFDATA2MSB,
+ PPC_ELF_MACHINE, 0, 0,
+ NULL, false, NULL,
+ vof_elf_segment_cb, vof);
+ if (ret <= 0) {
+ error_report("spapr_vof_try_prep_boot: %s", load_elf_strerror(ret));
+ /* Roll back any partial claims made during the failed load. */
+ g_array_set_size(vof->claimed, saved_claimed_len);
+ vof->claimed_base = saved_claimed_base;
+ g_free(prep_data);
+ continue;
+ }
+ load_size = highaddr - lowaddr;
+
+ g_free(prep_data);
+
+ spapr->kernel_addr = entry_point;
+ spapr->kernel_size = load_size;
+
+ vof->disk_boot = true;
+ dev = blk_get_attached_dev(blk);
+ if (dev) {
+ vof->bootpath = qdev_get_fw_dev_path(dev);
+ }
+ return true;
+ }
+ return false;
+}
+
void spapr_vof_reset(SpaprMachineState *spapr, void *fdt, Error **errp)
{
target_ulong stack_ptr;
Vof *vof = spapr->vof;
PowerPCCPU *first_ppc_cpu = POWERPC_CPU(first_cpu);
+ MachineState *machine = MACHINE(spapr);
vof_init(vof, spapr->rma_size, errp);
@@ -70,7 +200,7 @@ void spapr_vof_reset(SpaprMachineState *spapr, void *fdt, Error **errp)
/* Stack grows downwards plus reserve space for the minimum stack frame */
stack_ptr += VOF_STACK_SIZE - 0x20;
- if (spapr->kernel_size &&
+ if (machine->kernel_filename && spapr->kernel_size &&
vof_claim(vof, spapr->kernel_addr, spapr->kernel_size, 0) == -1) {
error_setg(errp, "Memory for kernel is in use");
return;
@@ -82,6 +212,14 @@ void spapr_vof_reset(SpaprMachineState *spapr, void *fdt, Error **errp)
return;
}
+ /*
+ * Disk boot: load GRUB from the PReP boot partition on the block device, if
+ * no kernel/initrd are provided
+ */
+ if (!machine->kernel_filename) {
+ spapr_vof_try_prep_boot(spapr, vof);
+ }
+
spapr_vof_client_dt_finalize(spapr, fdt);
spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT,
@@ -91,10 +229,18 @@ void spapr_vof_reset(SpaprMachineState *spapr, void *fdt, Error **errp)
/*
* At this point the expected allocation map is:
*
- * 0..c38 - the initial firmware
- * 8000..10000 - stack
- * 400000.. - kernel
- * 3ea0000.. - initramdisk
+ * Kernel + initrd boot:
+ * 0..d80 - the initial firmware
+ * 8000..10000 - stack
+ * 10000..20000 - vscsi-report-luns table (up to 64 KB, if needed)
+ * 400000.. - kernel
+ * 3ea0000.. - initramdisk
+ *
+ * Disk (GRUB) boot:
+ * 0..d80 - the initial firmware
+ * 8000..10000 - stack
+ * 10000..20000 - vscsi-report-luns table (up to 64 KB, if needed)
+ * 400000.. - GRUB (loaded from PReP partition)
*
* We skip writing FDT as nothing expects it; OF client interface is
* going to be used for reading the device tree.
diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
index 873e967f1e..9ad1aa7626 100644
--- a/hw/ppc/vof.c
+++ b/hw/ppc/vof.c
@@ -1042,6 +1042,9 @@ void vof_cleanup(Vof *vof)
vof->of_instances = NULL;
vof->of_instance_last = 0;
vof->claimed_base = 0;
+ g_free(vof->bootpath);
+ vof->bootpath = NULL;
+ vof->disk_boot = false;
}
void vof_build_dt(void *fdt, Vof *vof)
diff --git a/include/hw/ppc/vof.h b/include/hw/ppc/vof.h
index 3a0fbffe54..e17779ee8a 100644
--- a/include/hw/ppc/vof.h
+++ b/include/hw/ppc/vof.h
@@ -18,6 +18,8 @@ typedef struct Vof {
GHashTable *of_instances; /* ihandle -> SpaprOfInstance */
uint32_t of_instance_last;
char *bootargs;
+ char *bootpath;
+ bool disk_boot;
long fw_size;
} Vof;
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC PATCH v2 4/8] ppc/spapr: add VTY backend support to OF read/write/open services
2026-10-02 5:25 [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support uverma
` (2 preceding siblings ...)
2026-10-02 5:25 ` [RFC PATCH v2 3/8] ppc/spapr: add baseline VOF disk boot support uverma
@ 2026-10-02 5:25 ` uverma
2026-10-07 14:35 ` BALATON Zoltan
2026-10-02 5:25 ` [RFC PATCH v2 5/8] ppc/spapr: add block device backend to VOF open/read/write/seek services uverma
` (4 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: uverma @ 2026-10-02 5:25 UTC (permalink / raw)
To: qemu-devel, qemu-ppc, aik, nnmlinux, avnish
Cc: pbonzini, th.huth, sbhat, harshpb, amachhiw, rathc, balaton,
philmd, npiggin, marcandre.lureau, fam, alex.bennee,
richard.henderson, Utkarsh Verma
From: Utkarsh Verma <uverma@linux.ibm.com>
Extend the VOF client interface to support console I/O through the sPAPR VTY
device, which is needed by bootloaders such as GRUB during disk boot.
Add vof_read() to implement the OpenFirmware "read" client service, which was
previously missing.
Route the OF read and write services backed by vty_getchars() and vty_putchars()
Make vty_getchars() public similar to vty_putchars().
AI-used-for: code
Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
---
hw/char/spapr_vty.c | 2 +-
hw/ppc/spapr_vof.c | 2 +
hw/ppc/trace-events | 2 +
hw/ppc/vof.c | 84 ++++++++++++++++++++++++++++++++++++++
include/hw/ppc/spapr_vio.h | 1 +
5 files changed, 90 insertions(+), 1 deletion(-)
diff --git a/hw/char/spapr_vty.c b/hw/char/spapr_vty.c
index 1dd9fb155c..2c97e0e027 100644
--- a/hw/char/spapr_vty.c
+++ b/hw/char/spapr_vty.c
@@ -52,7 +52,7 @@ static void vty_receive(void *opaque, const uint8_t *buf, int size)
}
}
-static int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max)
+int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max)
{
SpaprVioVty *dev = VIO_SPAPR_VTY_DEVICE(sdev);
int n = 0;
diff --git a/hw/ppc/spapr_vof.c b/hw/ppc/spapr_vof.c
index 78d5b7d5b5..3195c859a4 100644
--- a/hw/ppc/spapr_vof.c
+++ b/hw/ppc/spapr_vof.c
@@ -76,6 +76,8 @@ void spapr_vof_client_dt_finalize(SpaprMachineState *spapr, void *fdt)
if (stdout_path) {
_FDT(vof_client_open_store(fdt, spapr->vof, "/chosen", "stdout",
stdout_path));
+ _FDT(vof_client_open_store(fdt, spapr->vof, "/chosen", "stdin",
+ stdout_path));
}
}
diff --git a/hw/ppc/trace-events b/hw/ppc/trace-events
index 1f125ce841..dbdcfada26 100644
--- a/hw/ppc/trace-events
+++ b/hw/ppc/trace-events
@@ -79,6 +79,7 @@ vof_error_unknown_method(const char *method) "\"%s\""
vof_error_unknown_ihandle_close(uint32_t ih) "ih=0x%x"
vof_error_unknown_path(const char *path) "\"%s\""
vof_error_write(uint32_t ih) "ih=0x%x"
+vof_error_read(uint32_t ih) "ih=0x%x"
vof_finddevice(const char *path, uint32_t ph) "\"%s\" => ph=0x%x"
vof_claim(uint32_t virt, uint32_t size, uint32_t align, uint32_t ret) "virt=0x%x size=0x%x align=0x%x => 0x%x"
vof_release(uint32_t virt, uint32_t size, uint32_t ret) "virt=0x%x size=0x%x => 0x%x"
@@ -92,6 +93,7 @@ vof_package_to_path(uint32_t ph, const char *tmp, int ret) "ph=0x%x => %s len=%d
vof_instance_to_path(uint32_t ih, uint32_t ph, const char *tmp, int ret) "ih=0x%x ph=0x%x => %s len=%d"
vof_instance_to_package(uint32_t ih, uint32_t ph) "ih=0x%x => ph=0x%x"
vof_write(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\""
+vof_read(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\""
vof_avail(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
vof_claimed(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
index 9ad1aa7626..6f6d424347 100644
--- a/hw/ppc/vof.c
+++ b/hw/ppc/vof.c
@@ -9,6 +9,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
+#include CONFIG_DEVICES /* CONFIG_PSERIES */
#include "qemu/osdep.h"
#include "qemu/ctype.h"
#include "qemu/timer.h"
@@ -23,6 +24,7 @@
#include "qom/qom-qobject.h"
#include "trace.h"
+#include "hw/ppc/spapr_vio.h"
#include <libfdt.h>
/*
@@ -45,6 +47,7 @@ typedef struct {
typedef struct {
char *path; /* the path used to open the instance */
uint32_t phandle;
+ void *vty;
} OfInstance;
static int readstr(hwaddr pa, char *buf, int size)
@@ -459,6 +462,28 @@ static uint32_t vof_do_open(void *fdt, Vof *vof, int offset, const char *path)
++vof->of_instance_last;
inst->path = g_strdup(path);
+ inst->vty = NULL;
+
+#ifdef CONFIG_PSERIES
+ const char *node_name = fdt_get_name(fdt, offset, NULL);
+ if (node_name && strncmp(node_name, "vty", 3) == 0) {
+ uint8_t discard_buf[VOF_VTY_BUF_SIZE];
+ MachineState *ms = MACHINE(qdev_get_machine());
+ SpaprMachineState *spapr = SPAPR_MACHINE(ms);
+
+ if (spapr && spapr->vio_bus) {
+ inst->vty = spapr_vty_get_default(spapr->vio_bus);
+ if (inst->vty) {
+ /* Flush any stale data from the VTY input buffer */
+ while (vty_getchars(inst->vty, discard_buf,
+ sizeof(discard_buf)) > 0) {
+ /* discard */
+ }
+ }
+ }
+ }
+#endif
+
g_hash_table_insert(vof->of_instances,
GINT_TO_POINTER(vof->of_instance_last),
inst);
@@ -577,6 +602,23 @@ static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
return PROM_ERROR;
}
+#ifdef CONFIG_PSERIES
+ if (inst->vty) {
+ uint32_t total_written = 0;
+
+ for ( ; len > 0; len -= cb) {
+ cb = MIN(len, sizeof(tmp));
+ if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) {
+ return PROM_ERROR;
+ }
+ vty_putchars(inst->vty, (uint8_t *)tmp, cb);
+ buf += cb;
+ total_written += cb;
+ }
+ return total_written;
+ }
+#endif
+
for ( ; len > 0; len -= cb) {
cb = MIN(len, sizeof(tmp) - 1);
if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) {
@@ -594,6 +636,46 @@ static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
return len;
}
+static uint32_t vof_read(Vof *vof, uint32_t ihandle, uint32_t buf,
+ uint32_t len)
+{
+ OfInstance *inst = (OfInstance *)
+ g_hash_table_lookup(vof->of_instances, GINT_TO_POINTER(ihandle));
+
+ if (!inst) {
+ trace_vof_error_read(ihandle);
+ return PROM_ERROR;
+ }
+
+#ifdef CONFIG_PSERIES
+ if (inst->vty) {
+ uint8_t tmp[VOF_VTY_BUF_SIZE];
+ unsigned cb = MIN(len, sizeof(tmp));
+ uint32_t bytes_read = vty_getchars(inst->vty, tmp, cb);
+ if (bytes_read > 0) {
+ if (VOF_MEM_WRITE(buf, tmp, bytes_read) != MEMTX_OK) {
+ trace_vof_error_read(ihandle);
+ return PROM_ERROR;
+ }
+ }
+ if (trace_event_get_state(TRACE_VOF_READ) &&
+ qemu_loglevel_mask(LOG_TRACE)) {
+ char trace_buf[VOF_VTY_BUF_SIZE + 1];
+ memcpy(trace_buf, tmp, bytes_read);
+ trace_buf[bytes_read] = '\0';
+ trace_vof_read(ihandle, bytes_read, trace_buf);
+ }
+ return bytes_read;
+ }
+#endif
+
+ /*
+ * For other devices, return 0 to indicate no data available.
+ * This allows GRUB to continue without blocking on input.
+ */
+ return 0;
+}
+
static void vof_claimed_dump(GArray *claimed)
{
int i;
@@ -906,6 +988,8 @@ static uint32_t vof_client_handle(MachineState *ms, void *fdt, Vof *vof,
ret = vof_instance_to_path(fdt, vof, args[0], args[1], args[2]);
} else if (cmpserv("write", 3, 1)) {
ret = vof_write(vof, args[0], args[1], args[2]);
+ } else if (cmpserv("read", 3, 1)) {
+ ret = vof_read(vof, args[0], args[1], args[2]);
} else if (cmpserv("claim", 3, 1)) {
uint64_t ret64 = vof_claim(vof, args[0], args[1], args[2]);
diff --git a/include/hw/ppc/spapr_vio.h b/include/hw/ppc/spapr_vio.h
index 0ea0dbae8b..81e7c0b91b 100644
--- a/include/hw/ppc/spapr_vio.h
+++ b/include/hw/ppc/spapr_vio.h
@@ -136,6 +136,7 @@ static inline int spapr_vio_dma_set(SpaprVioDevice *dev, uint64_t taddr,
int spapr_vio_send_crq(SpaprVioDevice *dev, uint8_t *crq);
SpaprVioDevice *vty_lookup(SpaprMachineState *spapr, target_ulong reg);
+int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max);
void vty_putchars(SpaprVioDevice *sdev, uint8_t *buf, int len);
void spapr_vty_create(SpaprVioBus *bus, Chardev *chardev);
void spapr_vlan_create(SpaprVioBus *bus, NICInfo *nd);
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC PATCH v2 5/8] ppc/spapr: add block device backend to VOF open/read/write/seek services
2026-10-02 5:25 [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support uverma
` (3 preceding siblings ...)
2026-10-02 5:25 ` [RFC PATCH v2 4/8] ppc/spapr: add VTY backend support to OF read/write/open services uverma
@ 2026-10-02 5:25 ` uverma
2026-10-07 18:20 ` BALATON Zoltan
2026-10-02 5:25 ` [RFC PATCH v2 6/8] spapr_vscsi: add VOF disk nodes to the device tree uverma
` (3 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: uverma @ 2026-10-02 5:25 UTC (permalink / raw)
To: qemu-devel, qemu-ppc, aik, nnmlinux, avnish
Cc: pbonzini, th.huth, sbhat, harshpb, amachhiw, rathc, balaton,
philmd, npiggin, marcandre.lureau, fam, alex.bennee,
richard.henderson, Utkarsh Verma
From: Utkarsh Verma <uverma@linux.ibm.com>
Extend the VOF client interface to support block device I/O through the
sPAPR SCSI disk, which is needed by bootloaders such as GRUB during VOF
disk boot.
Add BlockBackend and position tracking fields to OfInstance and use them
in vof_seek(), vof_write() and vof_read() to extend their functionality
to handle block devices as well.
In vof_do_open(), detect "disk" FDT nodes, extract the SRP LUN from the
path string, and use scsi_device_find() + blk_by_dev() to obtain the
matching BlockBackend.
Add vof_seek() to implement the OpenFirmware "seek" client service,
which was previously missing.
AI-used-for: code
Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
---
Changelog:
v2:
- vof_do_open(): replaced brute force blk_next() loop with
vof_find_vscsi_bus() + scsi_device_find() + blk_by_dev() for a SCSI
disk discovery.
- vof_write() block path: capped I/O to VOF_BLK_BUF_SIZE (64 KB) per
iteration, checked blk_flush() return value, added bdrv_is_read_only()
guard before any write attempt.
- vof_write() serial path: fixed an existing bug, which did not advance
the buffer after each iteration and returned the remaining len instead
of bytes written.
- vof_read() block path: added the same loop pattern as vof_write(),
advancing buf and position correctly per iteration.
- path_offset(): added wildcard fallback so "disk@<srp-lun>" paths
resolve to the parent's generic "disk" FDT subnode.
---
hw/ppc/trace-events | 2 +
hw/ppc/vof.c | 217 +++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 215 insertions(+), 4 deletions(-)
diff --git a/hw/ppc/trace-events b/hw/ppc/trace-events
index dbdcfada26..a61398ecb2 100644
--- a/hw/ppc/trace-events
+++ b/hw/ppc/trace-events
@@ -78,6 +78,7 @@ vof_error_unknown_service(const char *service, int nargs, int nret) "\"%s\" args
vof_error_unknown_method(const char *method) "\"%s\""
vof_error_unknown_ihandle_close(uint32_t ih) "ih=0x%x"
vof_error_unknown_path(const char *path) "\"%s\""
+vof_error_seek(uint32_t ih) "ih=0x%x"
vof_error_write(uint32_t ih) "ih=0x%x"
vof_error_read(uint32_t ih) "ih=0x%x"
vof_finddevice(const char *path, uint32_t ph) "\"%s\" => ph=0x%x"
@@ -92,6 +93,7 @@ vof_interpret(const char *cmd, uint32_t param1, uint32_t param2, uint32_t ret, u
vof_package_to_path(uint32_t ph, const char *tmp, int ret) "ph=0x%x => %s len=%d"
vof_instance_to_path(uint32_t ih, uint32_t ph, const char *tmp, int ret) "ih=0x%x ph=0x%x => %s len=%d"
vof_instance_to_package(uint32_t ih, uint32_t ph) "ih=0x%x => ph=0x%x"
+vof_seek(uint32_t ih, uint64_t pos) "ih=0x%x pos=0x%"PRIx64
vof_write(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\""
vof_read(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\""
vof_avail(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
index 6f6d424347..d4b57b3b46 100644
--- a/hw/ppc/vof.c
+++ b/hw/ppc/vof.c
@@ -11,6 +11,7 @@
#include CONFIG_DEVICES /* CONFIG_PSERIES */
#include "qemu/osdep.h"
+#include "qemu/cutils.h"
#include "qemu/ctype.h"
#include "qemu/timer.h"
#include "qemu/range.h"
@@ -25,6 +26,9 @@
#include "trace.h"
#include "hw/ppc/spapr_vio.h"
+#include "hw/scsi/scsi.h"
+#include "system/block-backend.h"
+#include "block/block-io.h"
#include <libfdt.h>
/*
@@ -38,6 +42,7 @@
#define VOF_MAX_METHODLEN 256
#define VOF_MAX_FORTHCODE 256
#define VOF_VTY_BUF_SIZE 256
+#define VOF_BLK_BUF_SIZE (64 * 1024)
typedef struct {
uint64_t start;
@@ -47,6 +52,8 @@ typedef struct {
typedef struct {
char *path; /* the path used to open the instance */
uint32_t phandle;
+ BlockBackend *blk;
+ uint64_t pos; /* current position for seek operations */
void *vty;
} OfInstance;
@@ -138,6 +145,8 @@ static int path_offset(const void *fdt, const char *path)
{
g_autofree char *p = NULL;
char *at;
+ char *last_slash;
+ int offset;
/*
* https://www.devicetree.org/open-firmware/bindings/ppc/release/ppc-2_1.html#HDR16
@@ -156,7 +165,76 @@ static int path_offset(const void *fdt, const char *path)
}
}
- return fdt_path_offset(fdt, p);
+ offset = fdt_path_offset(fdt, p);
+ if (offset >= 0) {
+ return offset;
+ }
+
+ /*
+ * Apply the OF wildcard rule for vSCSI disk paths. A path ending in
+ * disk@<srp-lun> has no per-LUN FDT node, instead the FDT contains a single
+ * generic "disk" child (no srp-lun appended to the path). The phandle is
+ * shared by all LUNs on the adapter, matching the Open Firmware behaviour.
+ */
+ last_slash = strrchr(p, '/');
+ if (last_slash && strncmp(last_slash + 1, "disk@", 5) == 0) {
+ int parent_off;
+
+ *last_slash = '\0';
+ parent_off = fdt_path_offset(fdt, p);
+ if (parent_off >= 0) {
+ int child_off = fdt_subnode_offset(fdt, parent_off, "disk");
+ if (child_off >= 0) {
+ return child_off;
+ }
+ }
+ }
+
+ return offset;
+}
+
+/*
+ * Return the SCSIBus for a v-scsi@<reg> path, or NULL if not found.
+ */
+static SCSIBus *vof_find_vscsi_bus(MachineState *ms, const char *path)
+{
+#ifdef CONFIG_PSERIES
+ SpaprMachineState *spapr = SPAPR_MACHINE(ms);
+ const char *at;
+ const char *endptr;
+ unsigned long reg;
+ SpaprVioDevice *vdev;
+ BusState *bus;
+
+ if (!spapr || !spapr->vio_bus) {
+ return NULL;
+ }
+
+ at = strstr(path, "v-scsi@");
+ if (!at) {
+ return NULL;
+ }
+ at = strchr(at, '@');
+ if (!at) {
+ return NULL;
+ }
+
+ if (qemu_strtoul(at + 1, &endptr, 16, ®) || endptr == at + 1) {
+ return NULL;
+ }
+
+ vdev = spapr_vio_find_by_reg(spapr->vio_bus, (uint32_t)reg);
+ if (!vdev) {
+ return NULL;
+ }
+
+ QLIST_FOREACH(bus, &vdev->qdev.child_bus, sibling) {
+ if (object_dynamic_cast(OBJECT(bus), TYPE_SCSI_BUS)) {
+ return SCSI_BUS(bus);
+ }
+ }
+#endif /* CONFIG_PSERIES */
+ return NULL;
}
static uint32_t vof_finddevice(const void *fdt, uint32_t nodeaddr)
@@ -450,6 +528,8 @@ static uint32_t vof_do_open(void *fdt, Vof *vof, int offset, const char *path)
{
uint32_t ret = PROM_ERROR;
OfInstance *inst = NULL;
+ const char *node_name;
+ MachineState *ms = MACHINE(qdev_get_machine());
if (vof->of_instance_last == 0xFFFFFFFF) {
/* We do not recycle ihandles yet */
@@ -462,13 +542,43 @@ static uint32_t vof_do_open(void *fdt, Vof *vof, int offset, const char *path)
++vof->of_instance_last;
inst->path = g_strdup(path);
+ inst->blk = NULL;
+ inst->pos = 0;
inst->vty = NULL;
+ node_name = fdt_get_name(fdt, offset, NULL);
+
+ /*
+ * vSCSI disk open. The FDT contains only a generic "disk" child node (no
+ * srp-lun appended to the path), matching Open Firmware behaviour.
+ * Extract the LUN from the "disk@<hex>" component of the path string
+ * and use it to find the corresponding QEMU block backend.
+ */
+ if (node_name && strcmp(node_name, "disk") == 0) {
+ const char *disk_at = strstr(path, "/disk@");
+ const char *endptr;
+ uint64_t srp_lun;
+
+ if (disk_at &&
+ qemu_strtou64(disk_at + 6, &endptr, 16, &srp_lun) == 0) {
+ SCSIBus *sbus = vof_find_vscsi_bus(ms, path);
+
+ if (sbus) {
+ uint32_t target = (srp_lun >> 56) & 0x3f;
+ uint32_t channel = (srp_lun >> 53) & 0x7;
+ uint32_t lun = (srp_lun >> 48) & 0x1f;
+ SCSIDevice *sdev = scsi_device_find(sbus, (int)channel,
+ (int)target, (int)lun);
+ if (sdev) {
+ inst->blk = blk_by_dev(sdev);
+ }
+ }
+ }
+ }
+
#ifdef CONFIG_PSERIES
- const char *node_name = fdt_get_name(fdt, offset, NULL);
if (node_name && strncmp(node_name, "vty", 3) == 0) {
uint8_t discard_buf[VOF_VTY_BUF_SIZE];
- MachineState *ms = MACHINE(qdev_get_machine());
SpaprMachineState *spapr = SPAPR_MACHINE(ms);
if (spapr && spapr->vio_bus) {
@@ -602,6 +712,38 @@ static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
return PROM_ERROR;
}
+ if (inst->blk) {
+ if (bdrv_is_read_only(blk_bs(inst->blk))) {
+ trace_vof_error_write(ihandle);
+ return PROM_ERROR;
+ }
+
+ g_autofree uint8_t *blkbuf = g_malloc(MIN(len, (uint32_t)VOF_BLK_BUF_SIZE));
+ uint32_t total = 0;
+
+ while (len > 0) {
+ cb = MIN(len, (uint32_t)VOF_BLK_BUF_SIZE);
+ if (VOF_MEM_READ(buf, blkbuf, cb) != MEMTX_OK) {
+ trace_vof_error_write(ihandle);
+ return PROM_ERROR;
+ }
+ if (blk_pwrite(inst->blk, inst->pos, cb, blkbuf, 0) < 0) {
+ trace_vof_error_write(ihandle);
+ return PROM_ERROR;
+ }
+ inst->pos += cb;
+ buf += cb;
+ len -= cb;
+ total += cb;
+ }
+ if (blk_flush(inst->blk) < 0) {
+ trace_vof_error_write(ihandle);
+ return PROM_ERROR;
+ }
+ trace_vof_write(ihandle, total, "(disk)");
+ return total;
+ }
+
#ifdef CONFIG_PSERIES
if (inst->vty) {
uint32_t total_written = 0;
@@ -619,6 +761,7 @@ static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
}
#endif
+ uint32_t total_traced = 0;
for ( ; len > 0; len -= cb) {
cb = MIN(len, sizeof(tmp) - 1);
if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) {
@@ -631,9 +774,11 @@ static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
tmp[cb] = '\0';
trace_vof_write(ihandle, cb, tmp);
}
+ buf += cb;
+ total_traced += cb;
}
- return len;
+ return total_traced;
}
static uint32_t vof_read(Vof *vof, uint32_t ihandle, uint32_t buf,
@@ -647,6 +792,33 @@ static uint32_t vof_read(Vof *vof, uint32_t ihandle, uint32_t buf,
return PROM_ERROR;
}
+ if (inst->blk) {
+ g_autofree uint8_t *tmp = g_malloc(MIN(len, (uint32_t)VOF_BLK_BUF_SIZE));
+ uint32_t total = 0;
+
+ while (len > 0) {
+ unsigned cb = MIN(len, (uint32_t)VOF_BLK_BUF_SIZE);
+ int ret = blk_pread(inst->blk, inst->pos, cb, tmp, 0);
+ if (ret < 0) {
+ trace_vof_error_read(ihandle);
+ return PROM_ERROR;
+ }
+
+ if (VOF_MEM_WRITE(buf, tmp, cb) != MEMTX_OK) {
+ trace_vof_error_read(ihandle);
+ return PROM_ERROR;
+ }
+
+ inst->pos += cb;
+ buf += cb;
+ len -= cb;
+ total += cb;
+ }
+
+ trace_vof_read(ihandle, total, "(disk)");
+ return total;
+ }
+
#ifdef CONFIG_PSERIES
if (inst->vty) {
uint8_t tmp[VOF_VTY_BUF_SIZE];
@@ -676,6 +848,41 @@ static uint32_t vof_read(Vof *vof, uint32_t ihandle, uint32_t buf,
return 0;
}
+static uint32_t vof_seek(Vof *vof, uint32_t ihandle, uint32_t pos_hi,
+ uint32_t pos_lo)
+{
+ OfInstance *inst = (OfInstance *)
+ g_hash_table_lookup(vof->of_instances, GINT_TO_POINTER(ihandle));
+ uint64_t pos = ((uint64_t)pos_hi << 32) | pos_lo;
+
+ if (!inst) {
+ trace_vof_error_seek(ihandle);
+ return PROM_ERROR;
+ }
+
+ if (inst->blk) {
+ int64_t size = blk_getlength(inst->blk);
+
+ if (size < 0) {
+ trace_vof_error_seek(ihandle);
+ return PROM_ERROR;
+ }
+
+ if (pos > (uint64_t)size) {
+ trace_vof_error_seek(ihandle);
+ return PROM_ERROR;
+ }
+
+ inst->pos = pos;
+ trace_vof_seek(ihandle, pos);
+ return 0;
+ }
+
+ /* VTY and other devices don't support seek */
+ trace_vof_error_seek(ihandle);
+ return PROM_ERROR;
+}
+
static void vof_claimed_dump(GArray *claimed)
{
int i;
@@ -986,6 +1193,8 @@ static uint32_t vof_client_handle(MachineState *ms, void *fdt, Vof *vof,
ret = vof_package_to_path(fdt, args[0], args[1], args[2]);
} else if (cmpserv("instance-to-path", 3, 1)) {
ret = vof_instance_to_path(fdt, vof, args[0], args[1], args[2]);
+ } else if (cmpserv("seek", 3, 1)) {
+ ret = vof_seek(vof, args[0], args[1], args[2]);
} else if (cmpserv("write", 3, 1)) {
ret = vof_write(vof, args[0], args[1], args[2]);
} else if (cmpserv("read", 3, 1)) {
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC PATCH v2 6/8] spapr_vscsi: add VOF disk nodes to the device tree
2026-10-02 5:25 [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support uverma
` (4 preceding siblings ...)
2026-10-02 5:25 ` [RFC PATCH v2 5/8] ppc/spapr: add block device backend to VOF open/read/write/seek services uverma
@ 2026-10-02 5:25 ` uverma
2026-10-02 5:25 ` [RFC PATCH v2 7/8] ppc/spapr: strip OF path argument suffix in path_offset uverma
` (2 subsequent siblings)
8 siblings, 0 replies; 18+ messages in thread
From: uverma @ 2026-10-02 5:25 UTC (permalink / raw)
To: qemu-devel, qemu-ppc, aik, nnmlinux, avnish
Cc: pbonzini, th.huth, sbhat, harshpb, amachhiw, rathc, balaton,
philmd, npiggin, marcandre.lureau, fam, alex.bennee,
richard.henderson, Utkarsh Verma
From: Utkarsh Verma <uverma@linux.ibm.com>
In VOF mode, add a single generic "disk" child node under the vSCSI
FDT node instead of one node per attached LUN, matching the Open
Firmware behaviour.
The node carries device_type = "block" and a name property. The
path_offset() wildcard fallback introduced alongside this change
resolves any "disk@<srp-lun>" open request to this shared node,
from which vof_do_open() extracts the LUN from the path string
at runtime.
AI-used-for: code
Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
---
Changelog:
v2:
- Replaced one FDT child node per attached LUN ("disk@<srp-lun>" with
reg + device_type) with a single generic "disk" wildcard node shared
by all LUNs, matching the Open Firmware behaviour.
---
hw/scsi/spapr_vscsi.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/hw/scsi/spapr_vscsi.c b/hw/scsi/spapr_vscsi.c
index b4c8f94d22..a799181d0f 100644
--- a/hw/scsi/spapr_vscsi.c
+++ b/hw/scsi/spapr_vscsi.c
@@ -1255,6 +1255,29 @@ static int spapr_vscsi_devnode(SpaprVioDevice *dev, void *fdt, int node_off)
return ret;
}
+ /*
+ * In VOF mode add a generic "disk" wildcard child node without appending
+ * srp-lun to it's path name, matching the actual Open Firmware behaviour.
+ * Its phandle and device_type covers any disk@<srp-lun> path.
+ */
+ SpaprMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
+ if (spapr->vof) {
+ int disk_off = fdt_add_subnode(fdt, node_off, "disk");
+ if (disk_off < 0) {
+ return disk_off;
+ }
+
+ ret = fdt_setprop_string(fdt, disk_off, "name", "disk");
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = fdt_setprop_string(fdt, disk_off, "device_type", "block");
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC PATCH v2 7/8] ppc/spapr: strip OF path argument suffix in path_offset
2026-10-02 5:25 [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support uverma
` (5 preceding siblings ...)
2026-10-02 5:25 ` [RFC PATCH v2 6/8] spapr_vscsi: add VOF disk nodes to the device tree uverma
@ 2026-10-02 5:25 ` uverma
2026-10-02 5:25 ` [RFC PATCH v2 8/8] ppc/spapr: implement vscsi-report-luns call-method for PAPR vSCSI uverma
2026-10-06 16:49 ` [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support Avnish Chouhan
8 siblings, 0 replies; 18+ messages in thread
From: uverma @ 2026-10-02 5:25 UTC (permalink / raw)
To: qemu-devel, qemu-ppc, aik, nnmlinux, avnish
Cc: pbonzini, th.huth, sbhat, harshpb, amachhiw, rathc, balaton,
philmd, npiggin, marcandre.lureau, fam, alex.bennee,
richard.henderson, Utkarsh Verma
From: Utkarsh Verma <uverma@linux.ibm.com>
OF paths can include a ":argument" suffix on the last component (e.g.
"disk@8000000000000000:0"), which is an OF path argument such as a
partition number. This is not part of the FDT node name and causes
fdt_path_offset() to fail.
Strip any ":<suffix>" from the last path component only, leaving
intermediate components untouched.
AI-used-for: code
Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
---
hw/ppc/vof.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
index d4b57b3b46..75b2398ae7 100644
--- a/hw/ppc/vof.c
+++ b/hw/ppc/vof.c
@@ -146,6 +146,7 @@ static int path_offset(const void *fdt, const char *path)
g_autofree char *p = NULL;
char *at;
char *last_slash;
+ char *colon;
int offset;
/*
@@ -156,6 +157,20 @@ static int path_offset(const void *fdt, const char *path)
* suppressing leading zeros".
*/
p = g_strdup(path);
+
+ /*
+ * Strip any ":argument" suffix from the last path component (e.g.
+ * "disk@8000000000000000:0") — it is an OF path argument, not part of
+ * the FDT node name.
+ */
+ last_slash = strrchr(p, '/');
+ if (last_slash) {
+ colon = strchr(last_slash, ':');
+ if (colon) {
+ *colon = '\0';
+ }
+ }
+
for (at = strchr(p, '@'); at && *at; ) {
if (*at == '/') {
at = strchr(at, '@');
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC PATCH v2 8/8] ppc/spapr: implement vscsi-report-luns call-method for PAPR vSCSI
2026-10-02 5:25 [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support uverma
` (6 preceding siblings ...)
2026-10-02 5:25 ` [RFC PATCH v2 7/8] ppc/spapr: strip OF path argument suffix in path_offset uverma
@ 2026-10-02 5:25 ` uverma
2026-10-07 18:31 ` BALATON Zoltan
2026-10-06 16:49 ` [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support Avnish Chouhan
8 siblings, 1 reply; 18+ messages in thread
From: uverma @ 2026-10-02 5:25 UTC (permalink / raw)
To: qemu-devel, qemu-ppc, aik, nnmlinux, avnish
Cc: pbonzini, th.huth, sbhat, harshpb, amachhiw, rathc, balaton,
philmd, npiggin, marcandre.lureau, fam, alex.bennee,
richard.henderson, Utkarsh Verma
From: Utkarsh Verma <uverma@linux.ibm.com>
GRUB (grub-core/disk/ieee1275/ofdisk.c) calls "vscsi-report-luns" on
any PAPR virtual SCSI controller ihandle to enumerate attached LUNs
before attempting to boot from disk. Without this method VOF returns
PROM_ERROR and GRUB cannot find vSCSI disks.
Add vof_vscsi_report_luns() to build the LUN response table in guest
memory and wire it into vof_call_method().
Place the table at a fixed address VSCSI_TABLE_BASE (0x10000), in the
free gap between the OF stack top and KERNEL_LOAD_ADDR, and cap its
size at VSCSI_TABLE_MAXSZ (64 KB).
The table layout and SRP LUN encoding follow SLOF (vio-vscsi.fs
dev-generate-srplun) each entry is an 8-byte big-endian cell whose
low 4 bytes hold a guest pointer to a null-terminated list of SRP LUNs
for that target; GRUB reads the pointer at offset
table + 4 + 8*i.
AI-used-for: code
Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
---
Chaneglog:
v2:
- vof_find_vscsi_bus() moved to patch 5/8; this patch reuses it.
- Table placement changed from the VOF firmware region to the free gap
between the OF stack top and KERNEL_LOAD_ADDR. Added VSCSI_TABLE_MAXSZ
(64 KB) cap for table size.
- Rewritten the SRP LUN encoding in simplified manner.
---
hw/ppc/vof.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 160 insertions(+)
diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
index 75b2398ae7..6fb39d62a9 100644
--- a/hw/ppc/vof.c
+++ b/hw/ppc/vof.c
@@ -31,6 +31,9 @@
#include "block/block-io.h"
#include <libfdt.h>
+#define VSCSI_TABLE_BASE 0x10000U
+#define VSCSI_TABLE_MAXSZ 0x10000U
+
/*
* OF 1275 "nextprop" description suggests is it 32 bytes max but
* LoPAPR defines "ibm,query-interrupt-source-number" which is 33 chars long.
@@ -1086,6 +1089,143 @@ static void vof_instantiate_rtas(Error **errp)
error_setg(errp, "The firmware should have instantiated RTAS");
}
+#ifdef CONFIG_PSERIES
+/* Combined (channel<<6)|id index space, matches SLOF dev-max-target. */
+#define VSCSI_MAX_TARGETS 512
+
+/*
+ * Build the vscsi-report-luns response in guest memory.
+ *
+ * Returns catch_result (0 = success). On success, *nentries_out and
+ * *table_addr_out describe the result table written into the free gap
+ * between the stack top (0x10000) and KERNEL_LOAD_ADDR (0x400000).
+ *
+ * Table layout (big-endian, 8 bytes per entry):
+ * [table_addr + 8*i + 0..3] : 0x00000000
+ * [table_addr + 8*i + 4..7] : guest pointer to null-terminated SRP LUN list
+ *
+ * GRUB reads each pointer as: *(uint32_t *)(table + 4 + 8 * i)
+ */
+static uint32_t vof_vscsi_report_luns(SCSIBus *sbus,
+ uint32_t *nentries_out,
+ uint32_t *table_addr_out)
+{
+ uint64_t *lun_lists[VSCSI_MAX_TARGETS];
+ int lun_counts[VSCSI_MAX_TARGETS];
+ uint32_t ptr_table_size;
+ uint32_t lun_data_size;
+ uint32_t total_size;
+ uint32_t base_addr;
+ uint32_t lun_data_base;
+ uint32_t lun_data_off;
+ uint32_t table_idx;
+ uint32_t table_addr;
+ BusChild *kid;
+ int nentries = 0;
+ int t;
+
+ memset(lun_lists, 0, sizeof(lun_lists));
+ memset(lun_counts, 0, sizeof(lun_counts));
+
+ QTAILQ_FOREACH(kid, &sbus->qbus.children, sibling) {
+ SCSIDevice *dev = SCSI_DEVICE(kid->child);
+ int combined_target;
+ uint64_t srplun;
+ int cnt;
+
+ combined_target = ((dev->channel & 0x7) << 6) | (dev->id & 0x3f);
+
+ /* SRP LUN: 0x8000 | (id << 8) | (channel << 5) | lun, in top 16 bits */
+ srplun = (0x8000ULL | ((dev->id & 0x3fULL) << 8)
+ | ((dev->channel & 0x7ULL) << 5)
+ | (dev->lun & 0x1fULL)) << 48;
+
+ cnt = lun_counts[combined_target];
+ lun_lists[combined_target] = g_realloc(lun_lists[combined_target],
+ (cnt + 2) * sizeof(uint64_t));
+ lun_lists[combined_target][cnt] = cpu_to_be64(srplun);
+ lun_lists[combined_target][cnt + 1] = 0;
+ lun_counts[combined_target]++;
+ if (cnt == 0) {
+ nentries++;
+ }
+ }
+
+ if (nentries == 0) {
+ *nentries_out = 0;
+ *table_addr_out = 0;
+ return 0;
+ }
+
+ ptr_table_size = nentries * sizeof(uint64_t);
+ lun_data_size = 0;
+ for (t = 0; t < VSCSI_MAX_TARGETS; t++) {
+ if (lun_lists[t]) {
+ lun_data_size += (lun_counts[t] + 1) * sizeof(uint64_t);
+ }
+ }
+ total_size = ptr_table_size + lun_data_size;
+
+ /*
+ * Max table size capped at 64 KB, which comfortably covers
+ * 512 targets × ~16 LUNs each.
+ */
+ if (total_size > VSCSI_TABLE_MAXSZ) {
+ for (t = 0; t < VSCSI_MAX_TARGETS; t++) {
+ g_free(lun_lists[t]);
+ }
+ return PROM_ERROR;
+ }
+ base_addr = VSCSI_TABLE_BASE;
+
+ table_addr = base_addr;
+ lun_data_base = base_addr + ptr_table_size;
+ lun_data_off = 0;
+ table_idx = 0;
+
+ for (t = 0; t < VSCSI_MAX_TARGETS; t++) {
+ uint32_t lun_buf_size;
+ uint32_t lun_buf_addr;
+ uint64_t cell_be64;
+
+ if (!lun_lists[t]) {
+ continue;
+ }
+
+ lun_buf_size = (lun_counts[t] + 1) * sizeof(uint64_t);
+ lun_buf_addr = lun_data_base + lun_data_off;
+
+ if (VOF_MEM_WRITE(lun_buf_addr, lun_lists[t], lun_buf_size)
+ != MEMTX_OK) {
+ goto write_err;
+ }
+
+ cell_be64 = cpu_to_be64((uint64_t)lun_buf_addr);
+ if (VOF_MEM_WRITE(table_addr + table_idx * sizeof(uint64_t),
+ &cell_be64, sizeof(cell_be64)) != MEMTX_OK) {
+ goto write_err;
+ }
+
+ lun_data_off += lun_buf_size;
+ table_idx++;
+ }
+
+ *nentries_out = table_idx;
+ *table_addr_out = table_addr;
+
+ for (t = 0; t < VSCSI_MAX_TARGETS; t++) {
+ g_free(lun_lists[t]);
+ }
+ return 0;
+
+write_err:
+ for (t = 0; t < VSCSI_MAX_TARGETS; t++) {
+ g_free(lun_lists[t]);
+ }
+ return PROM_ERROR;
+}
+#endif /* CONFIG_PSERIES */
+
static uint32_t vof_call_method(MachineState *ms, Vof *vof, uint32_t methodaddr,
uint32_t ihandle, uint32_t param1,
uint32_t param2, uint32_t param3,
@@ -1109,6 +1249,26 @@ static uint32_t vof_call_method(MachineState *ms, Vof *vof, uint32_t methodaddr,
goto trace_exit;
}
+#ifdef CONFIG_PSERIES
+ /* vscsi-report-luns: enumerate LUNs for GRUB */
+ if (strcmp(method, "vscsi-report-luns") == 0) {
+ SCSIBus *sbus = vof_find_vscsi_bus(ms, inst->path);
+
+ if (sbus) {
+ uint32_t nentries = 0, table_addr = 0;
+ ret = vof_vscsi_report_luns(sbus, &nentries, &table_addr);
+ ret2[0] = nentries;
+ ret2[1] = table_addr;
+ } else {
+ ret = 1;
+ ret2[0] = 0;
+ ret2[1] = 0;
+ }
+ trace_vof_method(ihandle, method, param1, ret, ret2[0]);
+ goto trace_exit;
+ }
+#endif /* CONFIG_PSERIES */
+
if (strcmp(inst->path, "/") == 0) {
if (strcmp(method, "ibm,client-architecture-support") == 0) {
Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF);
--
2.55.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [RFC PATCH v2 2/8] hw/loader: add load_elf_ram_sym_buf() for in-memory ELF loading
2026-10-02 5:25 ` [RFC PATCH v2 2/8] hw/loader: add load_elf_ram_sym_buf() for in-memory ELF loading uverma
@ 2026-10-06 8:10 ` Utkarsh Verma
0 siblings, 0 replies; 18+ messages in thread
From: Utkarsh Verma @ 2026-10-06 8:10 UTC (permalink / raw)
To: qemu-devel, qemu-ppc, aik, nnmlinux, avnish, philmd, alistair
Cc: pbonzini, th.huth, sbhat, harshpb, amachhiw, rathc, balaton,
philmd, npiggin, marcandre.lureau, fam, alex.bennee,
richard.henderson
+ Alistair and Philippe
Hi Alistair and Philippe,
Could you please share your feedback on this new ELF loader from buffer
function. Or suggest someone who may help to review this.
Also, hw/core/loader.c file has no entry in the MAINTAINERS file. So,
should we add the file under Generic loader section only?
Regards,
Utkarsh
On 10/2/26 10:55 AM, uverma@linux.ibm.com wrote:
> From: Utkarsh Verma <uverma@linux.ibm.com>
>
> Add a new public API load_elf_ram_sym_buf() that loads an ELF image
> from a caller-supplied memory buffer rather than a file path.
> The implementation creates a temporary file via g_file_open_tmp(), then
> reuses the existing load_elf32/load_elf64 paths.
>
> To support per-segment inspection by callers (e.g. detecting VoF
> firmware segments), extend the internal load_elf{32,64} template
> (elf_ops.h.inc) with an optional segment_fn_t callback that is
> invoked once for every PT_LOAD segment after its load address is
> resolved. Returning false from the callback aborts the load with
> ELF_LOAD_FAILED.
>
> AI-used-for: code
> Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
> ---
> Chaneglog:
> v2:
> - Replaced memfd_create() with g_file_open_tmp()
> - Replaced write() call with qemu_write_full()
> ---
> hw/core/loader.c | 81 +++++++++++++++++++++++++++++++++++++++-
> include/hw/core/loader.h | 30 +++++++++++++++
> include/hw/elf_ops.h.inc | 12 +++++-
> 3 files changed, 120 insertions(+), 3 deletions(-)
>
> diff --git a/hw/core/loader.c b/hw/core/loader.c
> index 5cbfba0a86..fa04c59ed0 100644
> --- a/hw/core/loader.c
> +++ b/hw/core/loader.c
> @@ -510,12 +510,14 @@ ssize_t load_elf_ram_sym(const char *filename,
> ret = load_elf64(filename, fd, elf_note_fn,
> translate_fn, translate_opaque, must_swab,
> pentry, lowaddr, highaddr, pflags, elf_machine,
> - clear_lsb, data_swab, as, load_rom, sym_cb);
> + clear_lsb, data_swab, as, load_rom, sym_cb,
> + NULL, NULL);
> } else {
> ret = load_elf32(filename, fd, elf_note_fn,
> translate_fn, translate_opaque, must_swab,
> pentry, lowaddr, highaddr, pflags, elf_machine,
> - clear_lsb, data_swab, as, load_rom, sym_cb);
> + clear_lsb, data_swab, as, load_rom, sym_cb,
> + NULL, NULL);
> }
>
> if (ret > 0) {
> @@ -527,6 +529,81 @@ ssize_t load_elf_ram_sym(const char *filename,
> return ret;
> }
>
> +ssize_t load_elf_ram_sym_buf(const uint8_t *buf, size_t buflen,
> + uint64_t (*elf_note_fn)(void *, void *, bool),
> + uint64_t (*translate_fn)(void *, uint64_t),
> + void *translate_opaque, uint64_t *pentry,
> + uint64_t *lowaddr, uint64_t *highaddr,
> + uint32_t *pflags, int elf_data_order,
> + int elf_machine, int clear_lsb, int data_swab,
> + AddressSpace *as, bool load_rom,
> + symbol_fn_t sym_cb,
> + segment_fn_t segment_fn, void *segment_opaque)
> +{
> + const int host_data_order = HOST_BIG_ENDIAN ? ELFDATA2MSB : ELFDATA2LSB;
> + int fd, must_swab;
> + ssize_t ret = ELF_LOAD_FAILED;
> + uint8_t e_ident[EI_NIDENT];
> + gchar *tmp_path = NULL;
> + GError *gerr = NULL;
> +
> + fd = g_file_open_tmp("qemu-elf-buf-XXXXXX", &tmp_path, &gerr);
> + if (fd < 0) {
> + error_report("load_elf_ram_sym_buf: %s", gerr->message);
> + g_error_free(gerr);
> + return ELF_LOAD_FAILED;
> + }
> + g_unlink(tmp_path);
> + g_free(tmp_path);
> +
> + if (qemu_write_full(fd, buf, buflen) != buflen) {
> + error_report("load_elf_ram_sym_buf: write: %s", strerror(errno));
> + goto fail;
> + }
> +
> + lseek(fd, 0, SEEK_SET);
> + if (read(fd, e_ident, sizeof(e_ident)) != sizeof(e_ident)) {
> + goto fail;
> + }
> + if (e_ident[0] != ELFMAG0 ||
> + e_ident[1] != ELFMAG1 ||
> + e_ident[2] != ELFMAG2 ||
> + e_ident[3] != ELFMAG3) {
> + ret = ELF_LOAD_NOT_ELF;
> + goto fail;
> + }
> +
> + if (elf_data_order != ELFDATANONE && elf_data_order != e_ident[EI_DATA]) {
> + ret = ELF_LOAD_WRONG_ENDIAN;
> + goto fail;
> + }
> +
> + must_swab = host_data_order != e_ident[EI_DATA];
> +
> + lseek(fd, 0, SEEK_SET);
> + if (e_ident[EI_CLASS] == ELFCLASS64) {
> + ret = load_elf64("(buffer)", fd, elf_note_fn,
> + translate_fn, translate_opaque, must_swab,
> + pentry, lowaddr, highaddr, pflags, elf_machine,
> + clear_lsb, data_swab, as, load_rom, sym_cb,
> + segment_fn, segment_opaque);
> + } else {
> + ret = load_elf32("(buffer)", fd, elf_note_fn,
> + translate_fn, translate_opaque, must_swab,
> + pentry, lowaddr, highaddr, pflags, elf_machine,
> + clear_lsb, data_swab, as, load_rom, sym_cb,
> + segment_fn, segment_opaque);
> + }
> +
> + if (ret > 0) {
> + debuginfo_report_elf("(buffer)", fd, 0);
> + }
> +
> + fail:
> + close(fd);
> + return ret;
> +}
> +
> static void bswap_uboot_header(uboot_image_header_t *hdr)
> {
> #if !HOST_BIG_ENDIAN
> diff --git a/include/hw/core/loader.h b/include/hw/core/loader.h
> index d9431e8a8d..b67b2ca013 100644
> --- a/include/hw/core/loader.h
> +++ b/include/hw/core/loader.h
> @@ -156,6 +156,36 @@ ssize_t load_elf_ram_sym(const char *filename,
> int clear_lsb, int data_swab,
> AddressSpace *as, bool load_rom, symbol_fn_t sym_cb);
>
> +/*
> + * segment_fn_t:
> + * Per-PT_LOAD segment callback for load_elf_ram_sym_buf().
> + */
> +typedef bool (*segment_fn_t)(void *opaque,
> + uint64_t paddr, uint64_t vaddr,
> + uint64_t filesz, uint64_t memsz);
> +
> +/*
> + * load_elf_ram_sym_buf:
> + * @buf: pointer to an in-memory ELF image
> + * @buflen: size of @buf in bytes
> + * @segment_fn: optional per-PT_LOAD callback
> + * @segment_opaque: opaque data passed to @segment_fn
> + *
> + * Identical to load_elf_ram_sym() but loads from a caller-supplied
> + * memory buffer instead of a file. All other parameters have the
> + * same meaning as load_elf_ram_sym().
> + */
> +ssize_t load_elf_ram_sym_buf(const uint8_t *buf, size_t buflen,
> + uint64_t (*elf_note_fn)(void *, void *, bool),
> + uint64_t (*translate_fn)(void *, uint64_t),
> + void *translate_opaque, uint64_t *pentry,
> + uint64_t *lowaddr, uint64_t *highaddr,
> + uint32_t *pflags, int elf_data_order,
> + int elf_machine, int clear_lsb, int data_swab,
> + AddressSpace *as, bool load_rom,
> + symbol_fn_t sym_cb,
> + segment_fn_t segment_fn, void *segment_opaque);
> +
> /** load_elf_as:
> * Same as load_elf_ram_sym(), but always loads the elf as ROM
> */
> diff --git a/include/hw/elf_ops.h.inc b/include/hw/elf_ops.h.inc
> index 044e72de2a..a2c9b52bf9 100644
> --- a/include/hw/elf_ops.h.inc
> +++ b/include/hw/elf_ops.h.inc
> @@ -321,7 +321,9 @@ static ssize_t glue(load_elf, SZ)(const char *name, int fd,
> uint32_t *pflags, int elf_machine,
> int clear_lsb, int data_swab,
> AddressSpace *as, bool load_rom,
> - symbol_fn_t sym_cb)
> + symbol_fn_t sym_cb,
> + segment_fn_t segment_fn,
> + void *segment_opaque)
> {
> struct elfhdr ehdr;
> struct elf_phdr *phdr = NULL, *ph;
> @@ -504,6 +506,14 @@ static ssize_t glue(load_elf, SZ)(const char *name, int fd,
> addr = ph->p_paddr;
> }
>
> + if (segment_fn) {
> + if (!segment_fn(segment_opaque, addr, ph->p_vaddr,
> + file_size, mem_size)) {
> + ret = ELF_LOAD_FAILED;
> + goto fail;
> + }
> + }
> +
> if (data_swab) {
> elf_word j;
> for (j = 0; j < file_size; j += (1 << data_swab)) {
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support
2026-10-02 5:25 [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support uverma
` (7 preceding siblings ...)
2026-10-02 5:25 ` [RFC PATCH v2 8/8] ppc/spapr: implement vscsi-report-luns call-method for PAPR vSCSI uverma
@ 2026-10-06 16:49 ` Avnish Chouhan
8 siblings, 0 replies; 18+ messages in thread
From: Avnish Chouhan @ 2026-10-06 16:49 UTC (permalink / raw)
To: uverma
Cc: qemu-devel, qemu-ppc, aik, nnmlinux, pbonzini, th.huth, sbhat,
harshpb, amachhiw, rathc, balaton, philmd, npiggin,
marcandre.lureau, fam, alex.bennee, richard.henderson
On 2026-10-02 10:55, uverma@linux.ibm.com wrote:
Reviewed the complete series, looks good!
Reviewed-by: Avnish Chouhan <avnish@linux.ibm.com>
Regards,
Avnish Chouhan
> From: Utkarsh Verma <uverma@linux.ibm.com>
>
> This series adds support for booting from a disk image under the
> Virtual Open
> Firmware (VOF) path for sPAPR/pseries machines in QEMU.
>
> The most common use case for pseries VMs is booting from a distro qcow2
> disk
> image. These images carry a PReP boot partition containing GRUB2, which
> in turn
> loads the kernel and initrd via IEEE 1275 client interface calls.
> VOF currently only supports direct -kernel/-initrd boot; there is no
> firmware-mediated path to hand off to a bootloader on disk. This series
> implements that missing path, making VOF capable of handling the
> standard
> distro qcow2 boot flow without requiring SLOF.
>
> This series takes reference from an earlier implementation by Alexey
> Kardashevskiy [1] which explored the same concept but was not carried
> forward at
> the time.
>
> The existing load_elf function works on a file, whereas in the VOF the
> PReP
> partition (GRUB) is read from the disk image and loaded in the memory,
> also
> on loading the PReP partition in the guest memory the
> /memory@0/available FDT
> needs to be properly updated for each PT_LOAD segment of the core.elf
> file
> of the GRUB (PReP partition).
>
> Implemented a new public elf loader API load_elf_ram_sym_buf() to
> resolve both
> these issues.
> - load_elf_ram_sym_buf() accepts a caller-supplied memory buffer
> instead of a
> filename, using g_file_open_tmp() to obtain an anonymous file
> descriptor for
> the caller-supplied buffer. This means the ELF image has 2 copies,
> the
> caller's heap buffer and the temporary file. For typical PReP
> partition sizes
> (~4 MB) this is acceptable, but if a more memory-efficient approach
> is
> preferred we are open to alternatives. Feedback on the preferred
> direction is
> welcome.
> - Also, added optional segment_fn_t callback in load_elf_ram_sym_buf()
> through
> the internal elf_ops.h.inc template; it is invoked once per PT_LOAD
> segment
> after the load address is resolved, allowing callers to perform
> per-segment
> work such as VOF memory claims.
>
> When no -kernel is given, spapr_vof_reset() calls a newly implemented
> function
> spapr_vof_try_prep_boot() which iterates attached block backends, scans
> partitions for a PReP boot partition, reads the partition contents into
> a
> host buffer, calls load_elf_ram_sym_buf() to load the GRUB in the guest
> memory,
> and on success stores the ELF entry point in spapr->kernel_addr.
> spapr_vof_client_dt_finalize() then writes the entry point into
> /chosen/qemu,boot-kernel so VOF then transfers control to GRUB.
>
> - Partition scanner (spapr_vof_partition.c): scans MBR and GPT
> partition tables
> on a BlockBackend to locate the PReP boot partition.
>
> - Core loader extension (hw/core/loader.c): new load_elf_ram_sym_buf()
> API loads
> a 32/64-bit ELF binary from a caller-supplied memory buffer, reusing
> the
> existing load_elf32/load_elf64 paths. A new segment_fn_t callback in
> elf_ops.h.inc lets callers act on each PT_LOAD segment as it is
> resolved.
>
> - read, seek OF services: two previously unimplemented IEEE 1275 client
> services, covering block device I/O needed by GRUB.
>
> - vscsi-report-luns method: walks the SCSIBus and builds the LUN report
> table
> in guest memory in the format expected by GRUB's ofdisk driver,
> matching
> SLOF's dev-generate-srplun encoding.
>
> - VTY write/read routing: routes console I/O through the VTY chardev
> backend;
> promotes vty_getchars to external linkage. Also opens a stdin ihandle
> in
> addition to stdout so GRUB can read from the console.
>
> - FDT "disk" wildcard child node (spapr_vscsi.c): emits a single
> generic "disk"
> child FDT node under the vSCSI adapter (without a per-LUN suffix),
> matching
> the actual Open Firmware behaviour. The path_offset() wildcard
> fallback
> resolves any "disk@<srp-lun>" open request to this shared node; the
> LUN is
> extracted from the path string at runtime in vof_do_open().
>
> We used AI to build prototype with manual changes incorporated as
> identified
> during debugging multiple issues faced while making it work, hence
> using below
> tag as being discussed in the ongoing AI policy patch:
> [PATCH v4 0/4] docs/devel, AGENTS.md: a completely new policy on
> AI-generated contributions
> Link to the patch [2].
> AI-used-for: Code
>
> Testing:
>
> The series has been tested end-to-end by booting a RHEL9.7 qcow2 image
> on a
> pseries machine with VOF enabled (no SLOF).
>
> build$ ./qemu-system-ppc64 -M pseries,x-vof=on -cpu power9 -m 2G \
> -hda ../../rhel-guest-image-9.7-20251021.0.ppc64le.qcow2 -nographic
> qemu-system-ppc64: warning: TCG doesn't support requested feature,
> cap-cfpc=workaround
> qemu-system-ppc64: warning: TCG doesn't support requested feature,
> cap-ibs=workaround
> qemu-system-ppc64: warning: TCG doesn't support requested feature,
> cap-ccf-assist=on
> Welcome to GRUB!
> <snip>
> GRUB version 2.06
>
>
> +----------------------------------------------------------------------------+
> |*Red Hat Enterprise Linux (5.14.0-611.5.1.el9_7.ppc64le) 9.7 (Plow)
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
> |
>
> +----------------------------------------------------------------------------+
>
> Use the ^ and v keys to select which entry is highlighted.
> Press enter to boot the selected OS, `e' to edit the commands
> before booting or `c' for a command-line.
>
> <snip>
>
> Red Hat Enterprise Linux 9.7 (Plow)
> Kernel 5.14.0-611.5.1.el9_7.ppc64le on an ppc64le
>
> Activate the web console with: systemctl enable --now cockpit.socket
>
> localhost login: root
> Password:
> Last login: Fri Jul 10 01:45:44 on hvc0
> [root@localhost ~]#
> [root@localhost ~]# ls /
> afs boot etc lib media opt root sbin sys usr
> bin dev home lib64 mnt proc run srv tmp var
> [root@localhost ~]#
> [root@localhost ~]# echo "Hello, World!" > hello.txt
> [root@localhost ~]# cat hello.txt
> Hello, World!
> [root@localhost ~]#
>
> [1]
> https://lore.kernel.org/qemu-devel/20200203032943.121178-1-aik@ozlabs.ru/#t
> [2]
> https://lore.kernel.org/qemu-devel/20260930085433.830743-1-pbonzini@redhat.com/
>
> Utkarsh Verma (8):
> ppc/spapr: add PReP boot partition detection
> hw/loader: add load_elf_ram_sym_buf() for in-memory ELF loading
> ppc/spapr: add baseline VOF disk boot support
> ppc/spapr: add VTY backend support to OF read/write/open services
> ppc/spapr: add block device backend to VOF open/read/write/seek
> services
> spapr_vscsi: add VOF disk nodes to the device tree
> ppc/spapr: strip OF path argument suffix in path_offset
> ppc/spapr: implement vscsi-report-luns call-method for PAPR vSCSI
>
> hw/char/spapr_vty.c | 2 +-
> hw/core/loader.c | 81 +++++-
> hw/ppc/meson.build | 5 +-
> hw/ppc/spapr_vof.c | 164 +++++++++++-
> hw/ppc/spapr_vof_partition.c | 272 ++++++++++++++++++++
> hw/ppc/trace-events | 4 +
> hw/ppc/vof.c | 475 ++++++++++++++++++++++++++++++++++-
> hw/scsi/spapr_vscsi.c | 23 ++
> include/hw/core/loader.h | 30 +++
> include/hw/elf_ops.h.inc | 12 +-
> include/hw/ppc/spapr_vio.h | 1 +
> include/hw/ppc/spapr_vof.h | 14 ++
> include/hw/ppc/vof.h | 2 +
> 13 files changed, 1070 insertions(+), 15 deletions(-)
> create mode 100644 hw/ppc/spapr_vof_partition.c
> create mode 100644 include/hw/ppc/spapr_vof.h
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [RFC PATCH v2 4/8] ppc/spapr: add VTY backend support to OF read/write/open services
2026-10-02 5:25 ` [RFC PATCH v2 4/8] ppc/spapr: add VTY backend support to OF read/write/open services uverma
@ 2026-10-07 14:35 ` BALATON Zoltan
2026-10-08 9:17 ` Utkarsh Verma
0 siblings, 1 reply; 18+ messages in thread
From: BALATON Zoltan @ 2026-10-07 14:35 UTC (permalink / raw)
To: Utkarsh Verma
Cc: qemu-devel, qemu-ppc, aik, nnmlinux, avnish, pbonzini, th.huth,
sbhat, harshpb, amachhiw, rathc, philmd, npiggin,
marcandre.lureau, fam, alex.bennee, richard.henderson
On Fri, 2 Oct 2026, uverma@linux.ibm.com wrote:
> From: Utkarsh Verma <uverma@linux.ibm.com>
>
> Extend the VOF client interface to support console I/O through the sPAPR VTY
> device, which is needed by bootloaders such as GRUB during disk boot.
>
> Add vof_read() to implement the OpenFirmware "read" client service, which was
> previously missing.
>
> Route the OF read and write services backed by vty_getchars() and vty_putchars()
>
> Make vty_getchars() public similar to vty_putchars().
>
> AI-used-for: code
> Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
> ---
> hw/char/spapr_vty.c | 2 +-
> hw/ppc/spapr_vof.c | 2 +
> hw/ppc/trace-events | 2 +
> hw/ppc/vof.c | 84 ++++++++++++++++++++++++++++++++++++++
> include/hw/ppc/spapr_vio.h | 1 +
> 5 files changed, 90 insertions(+), 1 deletion(-)
>
> diff --git a/hw/char/spapr_vty.c b/hw/char/spapr_vty.c
> index 1dd9fb155c..2c97e0e027 100644
> --- a/hw/char/spapr_vty.c
> +++ b/hw/char/spapr_vty.c
> @@ -52,7 +52,7 @@ static void vty_receive(void *opaque, const uint8_t *buf, int size)
> }
> }
>
> -static int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max)
> +int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max)
> {
> SpaprVioVty *dev = VIO_SPAPR_VTY_DEVICE(sdev);
> int n = 0;
> diff --git a/hw/ppc/spapr_vof.c b/hw/ppc/spapr_vof.c
> index 78d5b7d5b5..3195c859a4 100644
> --- a/hw/ppc/spapr_vof.c
> +++ b/hw/ppc/spapr_vof.c
> @@ -76,6 +76,8 @@ void spapr_vof_client_dt_finalize(SpaprMachineState *spapr, void *fdt)
> if (stdout_path) {
> _FDT(vof_client_open_store(fdt, spapr->vof, "/chosen", "stdout",
> stdout_path));
> + _FDT(vof_client_open_store(fdt, spapr->vof, "/chosen", "stdin",
> + stdout_path));
> }
> }
>
> diff --git a/hw/ppc/trace-events b/hw/ppc/trace-events
> index 1f125ce841..dbdcfada26 100644
> --- a/hw/ppc/trace-events
> +++ b/hw/ppc/trace-events
> @@ -79,6 +79,7 @@ vof_error_unknown_method(const char *method) "\"%s\""
> vof_error_unknown_ihandle_close(uint32_t ih) "ih=0x%x"
> vof_error_unknown_path(const char *path) "\"%s\""
> vof_error_write(uint32_t ih) "ih=0x%x"
> +vof_error_read(uint32_t ih) "ih=0x%x"
> vof_finddevice(const char *path, uint32_t ph) "\"%s\" => ph=0x%x"
> vof_claim(uint32_t virt, uint32_t size, uint32_t align, uint32_t ret) "virt=0x%x size=0x%x align=0x%x => 0x%x"
> vof_release(uint32_t virt, uint32_t size, uint32_t ret) "virt=0x%x size=0x%x => 0x%x"
> @@ -92,6 +93,7 @@ vof_package_to_path(uint32_t ph, const char *tmp, int ret) "ph=0x%x => %s len=%d
> vof_instance_to_path(uint32_t ih, uint32_t ph, const char *tmp, int ret) "ih=0x%x ph=0x%x => %s len=%d"
> vof_instance_to_package(uint32_t ih, uint32_t ph) "ih=0x%x => ph=0x%x"
> vof_write(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\""
> +vof_read(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\""
> vof_avail(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
> vof_claimed(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
>
> diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
> index 9ad1aa7626..6f6d424347 100644
> --- a/hw/ppc/vof.c
> +++ b/hw/ppc/vof.c
> @@ -9,6 +9,7 @@
> * SPDX-License-Identifier: GPL-2.0-or-later
> */
>
> +#include CONFIG_DEVICES /* CONFIG_PSERIES */
> #include "qemu/osdep.h"
> #include "qemu/ctype.h"
> #include "qemu/timer.h"
> @@ -23,6 +24,7 @@
> #include "qom/qom-qobject.h"
> #include "trace.h"
>
> +#include "hw/ppc/spapr_vio.h"
> #include <libfdt.h>
>
> /*
> @@ -45,6 +47,7 @@ typedef struct {
> typedef struct {
> char *path; /* the path used to open the instance */
> uint32_t phandle;
> + void *vty;
> } OfInstance;
>
> static int readstr(hwaddr pa, char *buf, int size)
> @@ -459,6 +462,28 @@ static uint32_t vof_do_open(void *fdt, Vof *vof, int offset, const char *path)
> ++vof->of_instance_last;
>
> inst->path = g_strdup(path);
> + inst->vty = NULL;
> +
> +#ifdef CONFIG_PSERIES
> + const char *node_name = fdt_get_name(fdt, offset, NULL);
> + if (node_name && strncmp(node_name, "vty", 3) == 0) {
> + uint8_t discard_buf[VOF_VTY_BUF_SIZE];
> + MachineState *ms = MACHINE(qdev_get_machine());
> + SpaprMachineState *spapr = SPAPR_MACHINE(ms);
> +
> + if (spapr && spapr->vio_bus) {
> + inst->vty = spapr_vty_get_default(spapr->vio_bus);
> + if (inst->vty) {
> + /* Flush any stale data from the VTY input buffer */
> + while (vty_getchars(inst->vty, discard_buf,
> + sizeof(discard_buf)) > 0) {
> + /* discard */
> + }
> + }
> + }
> + }
> +#endif
> +
> g_hash_table_insert(vof->of_instances,
> GINT_TO_POINTER(vof->of_instance_last),
> inst);
> @@ -577,6 +602,23 @@ static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
> return PROM_ERROR;
> }
>
> +#ifdef CONFIG_PSERIES
> + if (inst->vty) {
> + uint32_t total_written = 0;
> +
> + for ( ; len > 0; len -= cb) {
> + cb = MIN(len, sizeof(tmp));
> + if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) {
> + return PROM_ERROR;
> + }
> + vty_putchars(inst->vty, (uint8_t *)tmp, cb);
> + buf += cb;
> + total_written += cb;
> + }
> + return total_written;
> + }
> +#endif
> +
Instead of adding #ifdefs in this file could these be implemented as some
functions exported by spapr_vof.c and called here without needing #ifdefs?
Maybe it could be part of VofMachineIfClass similar to setprop.
Regards,
BALATON Zoltan
> for ( ; len > 0; len -= cb) {
> cb = MIN(len, sizeof(tmp) - 1);
> if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) {
> @@ -594,6 +636,46 @@ static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
> return len;
> }
>
> +static uint32_t vof_read(Vof *vof, uint32_t ihandle, uint32_t buf,
> + uint32_t len)
> +{
> + OfInstance *inst = (OfInstance *)
> + g_hash_table_lookup(vof->of_instances, GINT_TO_POINTER(ihandle));
> +
> + if (!inst) {
> + trace_vof_error_read(ihandle);
> + return PROM_ERROR;
> + }
> +
> +#ifdef CONFIG_PSERIES
> + if (inst->vty) {
> + uint8_t tmp[VOF_VTY_BUF_SIZE];
> + unsigned cb = MIN(len, sizeof(tmp));
> + uint32_t bytes_read = vty_getchars(inst->vty, tmp, cb);
> + if (bytes_read > 0) {
> + if (VOF_MEM_WRITE(buf, tmp, bytes_read) != MEMTX_OK) {
> + trace_vof_error_read(ihandle);
> + return PROM_ERROR;
> + }
> + }
> + if (trace_event_get_state(TRACE_VOF_READ) &&
> + qemu_loglevel_mask(LOG_TRACE)) {
> + char trace_buf[VOF_VTY_BUF_SIZE + 1];
> + memcpy(trace_buf, tmp, bytes_read);
> + trace_buf[bytes_read] = '\0';
> + trace_vof_read(ihandle, bytes_read, trace_buf);
> + }
> + return bytes_read;
> + }
> +#endif
> +
> + /*
> + * For other devices, return 0 to indicate no data available.
> + * This allows GRUB to continue without blocking on input.
> + */
> + return 0;
> +}
> +
> static void vof_claimed_dump(GArray *claimed)
> {
> int i;
> @@ -906,6 +988,8 @@ static uint32_t vof_client_handle(MachineState *ms, void *fdt, Vof *vof,
> ret = vof_instance_to_path(fdt, vof, args[0], args[1], args[2]);
> } else if (cmpserv("write", 3, 1)) {
> ret = vof_write(vof, args[0], args[1], args[2]);
> + } else if (cmpserv("read", 3, 1)) {
> + ret = vof_read(vof, args[0], args[1], args[2]);
> } else if (cmpserv("claim", 3, 1)) {
> uint64_t ret64 = vof_claim(vof, args[0], args[1], args[2]);
>
> diff --git a/include/hw/ppc/spapr_vio.h b/include/hw/ppc/spapr_vio.h
> index 0ea0dbae8b..81e7c0b91b 100644
> --- a/include/hw/ppc/spapr_vio.h
> +++ b/include/hw/ppc/spapr_vio.h
> @@ -136,6 +136,7 @@ static inline int spapr_vio_dma_set(SpaprVioDevice *dev, uint64_t taddr,
> int spapr_vio_send_crq(SpaprVioDevice *dev, uint8_t *crq);
>
> SpaprVioDevice *vty_lookup(SpaprMachineState *spapr, target_ulong reg);
> +int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max);
> void vty_putchars(SpaprVioDevice *sdev, uint8_t *buf, int len);
> void spapr_vty_create(SpaprVioBus *bus, Chardev *chardev);
> void spapr_vlan_create(SpaprVioBus *bus, NICInfo *nd);
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [RFC PATCH v2 5/8] ppc/spapr: add block device backend to VOF open/read/write/seek services
2026-10-02 5:25 ` [RFC PATCH v2 5/8] ppc/spapr: add block device backend to VOF open/read/write/seek services uverma
@ 2026-10-07 18:20 ` BALATON Zoltan
2026-10-08 9:24 ` Utkarsh Verma
0 siblings, 1 reply; 18+ messages in thread
From: BALATON Zoltan @ 2026-10-07 18:20 UTC (permalink / raw)
To: Utkarsh Verma
Cc: qemu-devel, qemu-ppc, aik, nnmlinux, avnish, pbonzini, th.huth,
sbhat, harshpb, amachhiw, rathc, philmd, npiggin,
marcandre.lureau, fam, alex.bennee, richard.henderson
On Fri, 2 Oct 2026, uverma@linux.ibm.com wrote:
> From: Utkarsh Verma <uverma@linux.ibm.com>
>
> Extend the VOF client interface to support block device I/O through the
> sPAPR SCSI disk, which is needed by bootloaders such as GRUB during VOF
> disk boot.
>
> Add BlockBackend and position tracking fields to OfInstance and use them
> in vof_seek(), vof_write() and vof_read() to extend their functionality
> to handle block devices as well.
>
> In vof_do_open(), detect "disk" FDT nodes, extract the SRP LUN from the
> path string, and use scsi_device_find() + blk_by_dev() to obtain the
> matching BlockBackend.
>
> Add vof_seek() to implement the OpenFirmware "seek" client service,
> which was previously missing.
>
> AI-used-for: code
> Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
> ---
> Changelog:
> v2:
> - vof_do_open(): replaced brute force blk_next() loop with
> vof_find_vscsi_bus() + scsi_device_find() + blk_by_dev() for a SCSI
> disk discovery.
> - vof_write() block path: capped I/O to VOF_BLK_BUF_SIZE (64 KB) per
> iteration, checked blk_flush() return value, added bdrv_is_read_only()
> guard before any write attempt.
> - vof_write() serial path: fixed an existing bug, which did not advance
> the buffer after each iteration and returned the remaining len instead
> of bytes written.
> - vof_read() block path: added the same loop pattern as vof_write(),
> advancing buf and position correctly per iteration.
> - path_offset(): added wildcard fallback so "disk@<srp-lun>" paths
> resolve to the parent's generic "disk" FDT subnode.
> ---
> hw/ppc/trace-events | 2 +
> hw/ppc/vof.c | 217 +++++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 215 insertions(+), 4 deletions(-)
>
> diff --git a/hw/ppc/trace-events b/hw/ppc/trace-events
> index dbdcfada26..a61398ecb2 100644
> --- a/hw/ppc/trace-events
> +++ b/hw/ppc/trace-events
> @@ -78,6 +78,7 @@ vof_error_unknown_service(const char *service, int nargs, int nret) "\"%s\" args
> vof_error_unknown_method(const char *method) "\"%s\""
> vof_error_unknown_ihandle_close(uint32_t ih) "ih=0x%x"
> vof_error_unknown_path(const char *path) "\"%s\""
> +vof_error_seek(uint32_t ih) "ih=0x%x"
> vof_error_write(uint32_t ih) "ih=0x%x"
> vof_error_read(uint32_t ih) "ih=0x%x"
> vof_finddevice(const char *path, uint32_t ph) "\"%s\" => ph=0x%x"
> @@ -92,6 +93,7 @@ vof_interpret(const char *cmd, uint32_t param1, uint32_t param2, uint32_t ret, u
> vof_package_to_path(uint32_t ph, const char *tmp, int ret) "ph=0x%x => %s len=%d"
> vof_instance_to_path(uint32_t ih, uint32_t ph, const char *tmp, int ret) "ih=0x%x ph=0x%x => %s len=%d"
> vof_instance_to_package(uint32_t ih, uint32_t ph) "ih=0x%x => ph=0x%x"
> +vof_seek(uint32_t ih, uint64_t pos) "ih=0x%x pos=0x%"PRIx64
> vof_write(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\""
> vof_read(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\""
> vof_avail(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
> diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
> index 6f6d424347..d4b57b3b46 100644
> --- a/hw/ppc/vof.c
> +++ b/hw/ppc/vof.c
> @@ -11,6 +11,7 @@
>
> #include CONFIG_DEVICES /* CONFIG_PSERIES */
> #include "qemu/osdep.h"
> +#include "qemu/cutils.h"
> #include "qemu/ctype.h"
> #include "qemu/timer.h"
> #include "qemu/range.h"
> @@ -25,6 +26,9 @@
> #include "trace.h"
>
> #include "hw/ppc/spapr_vio.h"
> +#include "hw/scsi/scsi.h"
> +#include "system/block-backend.h"
> +#include "block/block-io.h"
> #include <libfdt.h>
>
> /*
> @@ -38,6 +42,7 @@
> #define VOF_MAX_METHODLEN 256
> #define VOF_MAX_FORTHCODE 256
> #define VOF_VTY_BUF_SIZE 256
> +#define VOF_BLK_BUF_SIZE (64 * 1024)
* KiB instead of 1024?
>
> typedef struct {
> uint64_t start;
> @@ -47,6 +52,8 @@ typedef struct {
> typedef struct {
> char *path; /* the path used to open the instance */
> uint32_t phandle;
> + BlockBackend *blk;
> + uint64_t pos; /* current position for seek operations */
> void *vty;
> } OfInstance;
>
> @@ -138,6 +145,8 @@ static int path_offset(const void *fdt, const char *path)
> {
> g_autofree char *p = NULL;
> char *at;
> + char *last_slash;
> + int offset;
>
> /*
> * https://www.devicetree.org/open-firmware/bindings/ppc/release/ppc-2_1.html#HDR16
> @@ -156,7 +165,76 @@ static int path_offset(const void *fdt, const char *path)
> }
> }
>
> - return fdt_path_offset(fdt, p);
> + offset = fdt_path_offset(fdt, p);
> + if (offset >= 0) {
> + return offset;
> + }
> +
> + /*
> + * Apply the OF wildcard rule for vSCSI disk paths. A path ending in
> + * disk@<srp-lun> has no per-LUN FDT node, instead the FDT contains a single
> + * generic "disk" child (no srp-lun appended to the path). The phandle is
> + * shared by all LUNs on the adapter, matching the Open Firmware behaviour.
> + */
> + last_slash = strrchr(p, '/');
> + if (last_slash && strncmp(last_slash + 1, "disk@", 5) == 0) {
> + int parent_off;
> +
> + *last_slash = '\0';
> + parent_off = fdt_path_offset(fdt, p);
> + if (parent_off >= 0) {
> + int child_off = fdt_subnode_offset(fdt, parent_off, "disk");
I'm not sure how this should work but what if there are multiple disk
nodes such as disk@0 disk@1 or similar? Should this look for the original
ending of the path instead of just "disk"?
> + if (child_off >= 0) {
> + return child_off;
> + }
> + }
> + }
> +
> + return offset;
> +}
> +
> +/*
> + * Return the SCSIBus for a v-scsi@<reg> path, or NULL if not found.
> + */
> +static SCSIBus *vof_find_vscsi_bus(MachineState *ms, const char *path)
> +{
> +#ifdef CONFIG_PSERIES
> + SpaprMachineState *spapr = SPAPR_MACHINE(ms);
> + const char *at;
> + const char *endptr;
> + unsigned long reg;
> + SpaprVioDevice *vdev;
> + BusState *bus;
> +
> + if (!spapr || !spapr->vio_bus) {
> + return NULL;
> + }
> +
> + at = strstr(path, "v-scsi@");
> + if (!at) {
> + return NULL;
> + }
> + at = strchr(at, '@');
> + if (!at) {
> + return NULL;
> + }
> +
> + if (qemu_strtoul(at + 1, &endptr, 16, ®) || endptr == at + 1) {
> + return NULL;
> + }
> +
> + vdev = spapr_vio_find_by_reg(spapr->vio_bus, (uint32_t)reg);
> + if (!vdev) {
> + return NULL;
> + }
> +
> + QLIST_FOREACH(bus, &vdev->qdev.child_bus, sibling) {
> + if (object_dynamic_cast(OBJECT(bus), TYPE_SCSI_BUS)) {
> + return SCSI_BUS(bus);
> + }
> + }
> +#endif /* CONFIG_PSERIES */
No #ifdefs. This vof.c file is the generic machine independent parts, so
this function should go to spapr_vof.c and maybe exported through the VOF
machine class interface. Pegasos2 has IDE disks so if I want to later add
support for this for that machine I should implement similar function
there and this one here should call the implementation supplied by the
machine not hard code spapr here. So this should be some generic find
block device for device node function that the machine implements.
> + return NULL;
> }
>
> static uint32_t vof_finddevice(const void *fdt, uint32_t nodeaddr)
> @@ -450,6 +528,8 @@ static uint32_t vof_do_open(void *fdt, Vof *vof, int offset, const char *path)
> {
> uint32_t ret = PROM_ERROR;
> OfInstance *inst = NULL;
> + const char *node_name;
> + MachineState *ms = MACHINE(qdev_get_machine());
>
> if (vof->of_instance_last == 0xFFFFFFFF) {
> /* We do not recycle ihandles yet */
> @@ -462,13 +542,43 @@ static uint32_t vof_do_open(void *fdt, Vof *vof, int offset, const char *path)
> ++vof->of_instance_last;
>
> inst->path = g_strdup(path);
> + inst->blk = NULL;
> + inst->pos = 0;
> inst->vty = NULL;
>
> + node_name = fdt_get_name(fdt, offset, NULL);
> +
> + /*
> + * vSCSI disk open. The FDT contains only a generic "disk" child node (no
> + * srp-lun appended to the path), matching Open Firmware behaviour.
> + * Extract the LUN from the "disk@<hex>" component of the path string
> + * and use it to find the corresponding QEMU block backend.
> + */
> + if (node_name && strcmp(node_name, "disk") == 0) {
> + const char *disk_at = strstr(path, "/disk@");
> + const char *endptr;
> + uint64_t srp_lun;
> +
> + if (disk_at &&
> + qemu_strtou64(disk_at + 6, &endptr, 16, &srp_lun) == 0) {
> + SCSIBus *sbus = vof_find_vscsi_bus(ms, path);
> +
> + if (sbus) {
> + uint32_t target = (srp_lun >> 56) & 0x3f;
> + uint32_t channel = (srp_lun >> 53) & 0x7;
> + uint32_t lun = (srp_lun >> 48) & 0x1f;
> + SCSIDevice *sdev = scsi_device_find(sbus, (int)channel,
> + (int)target, (int)lun);
> + if (sdev) {
> + inst->blk = blk_by_dev(sdev);
> + }
> + }
> + }
> + }
> +
This should be in spapr_vof.c too as it's specific to spapr. Just call the
machine function that returns the blk for the node here. The rest below
seems generic so those are OK to be here in vof.c.
Regards,
BALATON Zoltan
> #ifdef CONFIG_PSERIES
> - const char *node_name = fdt_get_name(fdt, offset, NULL);
> if (node_name && strncmp(node_name, "vty", 3) == 0) {
> uint8_t discard_buf[VOF_VTY_BUF_SIZE];
> - MachineState *ms = MACHINE(qdev_get_machine());
> SpaprMachineState *spapr = SPAPR_MACHINE(ms);
>
> if (spapr && spapr->vio_bus) {
> @@ -602,6 +712,38 @@ static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
> return PROM_ERROR;
> }
>
> + if (inst->blk) {
> + if (bdrv_is_read_only(blk_bs(inst->blk))) {
> + trace_vof_error_write(ihandle);
> + return PROM_ERROR;
> + }
> +
> + g_autofree uint8_t *blkbuf = g_malloc(MIN(len, (uint32_t)VOF_BLK_BUF_SIZE));
> + uint32_t total = 0;
> +
> + while (len > 0) {
> + cb = MIN(len, (uint32_t)VOF_BLK_BUF_SIZE);
> + if (VOF_MEM_READ(buf, blkbuf, cb) != MEMTX_OK) {
> + trace_vof_error_write(ihandle);
> + return PROM_ERROR;
> + }
> + if (blk_pwrite(inst->blk, inst->pos, cb, blkbuf, 0) < 0) {
> + trace_vof_error_write(ihandle);
> + return PROM_ERROR;
> + }
> + inst->pos += cb;
> + buf += cb;
> + len -= cb;
> + total += cb;
> + }
> + if (blk_flush(inst->blk) < 0) {
> + trace_vof_error_write(ihandle);
> + return PROM_ERROR;
> + }
> + trace_vof_write(ihandle, total, "(disk)");
> + return total;
> + }
> +
> #ifdef CONFIG_PSERIES
> if (inst->vty) {
> uint32_t total_written = 0;
> @@ -619,6 +761,7 @@ static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
> }
> #endif
>
> + uint32_t total_traced = 0;
> for ( ; len > 0; len -= cb) {
> cb = MIN(len, sizeof(tmp) - 1);
> if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) {
> @@ -631,9 +774,11 @@ static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf,
> tmp[cb] = '\0';
> trace_vof_write(ihandle, cb, tmp);
> }
> + buf += cb;
> + total_traced += cb;
> }
>
> - return len;
> + return total_traced;
> }
>
> static uint32_t vof_read(Vof *vof, uint32_t ihandle, uint32_t buf,
> @@ -647,6 +792,33 @@ static uint32_t vof_read(Vof *vof, uint32_t ihandle, uint32_t buf,
> return PROM_ERROR;
> }
>
> + if (inst->blk) {
> + g_autofree uint8_t *tmp = g_malloc(MIN(len, (uint32_t)VOF_BLK_BUF_SIZE));
> + uint32_t total = 0;
> +
> + while (len > 0) {
> + unsigned cb = MIN(len, (uint32_t)VOF_BLK_BUF_SIZE);
> + int ret = blk_pread(inst->blk, inst->pos, cb, tmp, 0);
> + if (ret < 0) {
> + trace_vof_error_read(ihandle);
> + return PROM_ERROR;
> + }
> +
> + if (VOF_MEM_WRITE(buf, tmp, cb) != MEMTX_OK) {
> + trace_vof_error_read(ihandle);
> + return PROM_ERROR;
> + }
> +
> + inst->pos += cb;
> + buf += cb;
> + len -= cb;
> + total += cb;
> + }
> +
> + trace_vof_read(ihandle, total, "(disk)");
> + return total;
> + }
> +
> #ifdef CONFIG_PSERIES
> if (inst->vty) {
> uint8_t tmp[VOF_VTY_BUF_SIZE];
> @@ -676,6 +848,41 @@ static uint32_t vof_read(Vof *vof, uint32_t ihandle, uint32_t buf,
> return 0;
> }
>
> +static uint32_t vof_seek(Vof *vof, uint32_t ihandle, uint32_t pos_hi,
> + uint32_t pos_lo)
> +{
> + OfInstance *inst = (OfInstance *)
> + g_hash_table_lookup(vof->of_instances, GINT_TO_POINTER(ihandle));
> + uint64_t pos = ((uint64_t)pos_hi << 32) | pos_lo;
> +
> + if (!inst) {
> + trace_vof_error_seek(ihandle);
> + return PROM_ERROR;
> + }
> +
> + if (inst->blk) {
> + int64_t size = blk_getlength(inst->blk);
> +
> + if (size < 0) {
> + trace_vof_error_seek(ihandle);
> + return PROM_ERROR;
> + }
> +
> + if (pos > (uint64_t)size) {
> + trace_vof_error_seek(ihandle);
> + return PROM_ERROR;
> + }
> +
> + inst->pos = pos;
> + trace_vof_seek(ihandle, pos);
> + return 0;
> + }
> +
> + /* VTY and other devices don't support seek */
> + trace_vof_error_seek(ihandle);
> + return PROM_ERROR;
> +}
> +
> static void vof_claimed_dump(GArray *claimed)
> {
> int i;
> @@ -986,6 +1193,8 @@ static uint32_t vof_client_handle(MachineState *ms, void *fdt, Vof *vof,
> ret = vof_package_to_path(fdt, args[0], args[1], args[2]);
> } else if (cmpserv("instance-to-path", 3, 1)) {
> ret = vof_instance_to_path(fdt, vof, args[0], args[1], args[2]);
> + } else if (cmpserv("seek", 3, 1)) {
> + ret = vof_seek(vof, args[0], args[1], args[2]);
> } else if (cmpserv("write", 3, 1)) {
> ret = vof_write(vof, args[0], args[1], args[2]);
> } else if (cmpserv("read", 3, 1)) {
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [RFC PATCH v2 8/8] ppc/spapr: implement vscsi-report-luns call-method for PAPR vSCSI
2026-10-02 5:25 ` [RFC PATCH v2 8/8] ppc/spapr: implement vscsi-report-luns call-method for PAPR vSCSI uverma
@ 2026-10-07 18:31 ` BALATON Zoltan
2026-10-08 9:25 ` Utkarsh Verma
0 siblings, 1 reply; 18+ messages in thread
From: BALATON Zoltan @ 2026-10-07 18:31 UTC (permalink / raw)
To: Utkarsh Verma
Cc: qemu-devel, qemu-ppc, aik, nnmlinux, avnish, pbonzini, th.huth,
sbhat, harshpb, amachhiw, rathc, philmd, npiggin,
marcandre.lureau, fam, alex.bennee, richard.henderson
On Fri, 2 Oct 2026, uverma@linux.ibm.com wrote:
> From: Utkarsh Verma <uverma@linux.ibm.com>
>
> GRUB (grub-core/disk/ieee1275/ofdisk.c) calls "vscsi-report-luns" on
> any PAPR virtual SCSI controller ihandle to enumerate attached LUNs
> before attempting to boot from disk. Without this method VOF returns
> PROM_ERROR and GRUB cannot find vSCSI disks.
>
> Add vof_vscsi_report_luns() to build the LUN response table in guest
> memory and wire it into vof_call_method().
These should go to spapr_vof.c too as they are spapr specific.
Regards,
BALATON Zoltan
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [RFC PATCH v2 4/8] ppc/spapr: add VTY backend support to OF read/write/open services
2026-10-07 14:35 ` BALATON Zoltan
@ 2026-10-08 9:17 ` Utkarsh Verma
0 siblings, 0 replies; 18+ messages in thread
From: Utkarsh Verma @ 2026-10-08 9:17 UTC (permalink / raw)
To: BALATON Zoltan
Cc: qemu-devel, qemu-ppc, aik, nnmlinux, avnish, pbonzini, th.huth,
sbhat, harshpb, amachhiw, rathc, philmd, npiggin,
marcandre.lureau, fam, alex.bennee, richard.henderson
Hi Balaton,
Thank you for the review.
On 10/7/26 8:05 PM, BALATON Zoltan wrote:
> On Fri, 2 Oct 2026, uverma@linux.ibm.com wrote:
>> From: Utkarsh Verma <uverma@linux.ibm.com>
>>
>> Extend the VOF client interface to support console I/O through the
>> sPAPR VTY
>> device, which is needed by bootloaders such as GRUB during disk boot.
>>
>> Add vof_read() to implement the OpenFirmware "read" client service,
>> which was
>> previously missing.
>>
>> Route the OF read and write services backed by vty_getchars() and
>> vty_putchars()
>>
>> Make vty_getchars() public similar to vty_putchars().
>>
>> AI-used-for: code
>> Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
>> ---
>> hw/char/spapr_vty.c | 2 +-
>> hw/ppc/spapr_vof.c | 2 +
>> hw/ppc/trace-events | 2 +
>> hw/ppc/vof.c | 84 ++++++++++++++++++++++++++++++++++++++
>> include/hw/ppc/spapr_vio.h | 1 +
>> 5 files changed, 90 insertions(+), 1 deletion(-)
>>
>> diff --git a/hw/char/spapr_vty.c b/hw/char/spapr_vty.c
>> index 1dd9fb155c..2c97e0e027 100644
>> --- a/hw/char/spapr_vty.c
>> +++ b/hw/char/spapr_vty.c
>> @@ -52,7 +52,7 @@ static void vty_receive(void *opaque, const uint8_t
>> *buf, int size)
>> }
>> }
>>
>> -static int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max)
>> +int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max)
>> {
>> SpaprVioVty *dev = VIO_SPAPR_VTY_DEVICE(sdev);
>> int n = 0;
>> diff --git a/hw/ppc/spapr_vof.c b/hw/ppc/spapr_vof.c
>> index 78d5b7d5b5..3195c859a4 100644
>> --- a/hw/ppc/spapr_vof.c
>> +++ b/hw/ppc/spapr_vof.c
>> @@ -76,6 +76,8 @@ void spapr_vof_client_dt_finalize(SpaprMachineState
>> *spapr, void *fdt)
>> if (stdout_path) {
>> _FDT(vof_client_open_store(fdt, spapr->vof, "/chosen", "stdout",
>> stdout_path));
>> + _FDT(vof_client_open_store(fdt, spapr->vof, "/chosen", "stdin",
>> + stdout_path));
>> }
>> }
>>
>> diff --git a/hw/ppc/trace-events b/hw/ppc/trace-events
>> index 1f125ce841..dbdcfada26 100644
>> --- a/hw/ppc/trace-events
>> +++ b/hw/ppc/trace-events
>> @@ -79,6 +79,7 @@ vof_error_unknown_method(const char *method) "\"%s\""
>> vof_error_unknown_ihandle_close(uint32_t ih) "ih=0x%x"
>> vof_error_unknown_path(const char *path) "\"%s\""
>> vof_error_write(uint32_t ih) "ih=0x%x"
>> +vof_error_read(uint32_t ih) "ih=0x%x"
>> vof_finddevice(const char *path, uint32_t ph) "\"%s\" => ph=0x%x"
>> vof_claim(uint32_t virt, uint32_t size, uint32_t align, uint32_t ret)
>> "virt=0x%x size=0x%x align=0x%x => 0x%x"
>> vof_release(uint32_t virt, uint32_t size, uint32_t ret) "virt=0x%x
>> size=0x%x => 0x%x"
>> @@ -92,6 +93,7 @@ vof_package_to_path(uint32_t ph, const char *tmp,
>> int ret) "ph=0x%x => %s len=%d
>> vof_instance_to_path(uint32_t ih, uint32_t ph, const char *tmp, int
>> ret) "ih=0x%x ph=0x%x => %s len=%d"
>> vof_instance_to_package(uint32_t ih, uint32_t ph) "ih=0x%x => ph=0x%x"
>> vof_write(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u]
>> \"%s\""
>> +vof_read(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u]
>> \"%s\""
>> vof_avail(uint64_t start, uint64_t end, uint64_t size)
>> "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
>> vof_claimed(uint64_t start, uint64_t end, uint64_t size)
>> "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
>>
>> diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
>> index 9ad1aa7626..6f6d424347 100644
>> --- a/hw/ppc/vof.c
>> +++ b/hw/ppc/vof.c
>> @@ -9,6 +9,7 @@
>> * SPDX-License-Identifier: GPL-2.0-or-later
>> */
>>
>> +#include CONFIG_DEVICES /* CONFIG_PSERIES */
>> #include "qemu/osdep.h"
>> #include "qemu/ctype.h"
>> #include "qemu/timer.h"
>> @@ -23,6 +24,7 @@
>> #include "qom/qom-qobject.h"
>> #include "trace.h"
>>
>> +#include "hw/ppc/spapr_vio.h"
>> #include <libfdt.h>
>>
>> /*
>> @@ -45,6 +47,7 @@ typedef struct {
>> typedef struct {
>> char *path; /* the path used to open the instance */
>> uint32_t phandle;
>> + void *vty;
>> } OfInstance;
>>
>> static int readstr(hwaddr pa, char *buf, int size)
>> @@ -459,6 +462,28 @@ static uint32_t vof_do_open(void *fdt, Vof *vof,
>> int offset, const char *path)
>> ++vof->of_instance_last;
>>
>> inst->path = g_strdup(path);
>> + inst->vty = NULL;
>> +
>> +#ifdef CONFIG_PSERIES
>> + const char *node_name = fdt_get_name(fdt, offset, NULL);
>> + if (node_name && strncmp(node_name, "vty", 3) == 0) {
>> + uint8_t discard_buf[VOF_VTY_BUF_SIZE];
>> + MachineState *ms = MACHINE(qdev_get_machine());
>> + SpaprMachineState *spapr = SPAPR_MACHINE(ms);
>> +
>> + if (spapr && spapr->vio_bus) {
>> + inst->vty = spapr_vty_get_default(spapr->vio_bus);
>> + if (inst->vty) {
>> + /* Flush any stale data from the VTY input buffer */
>> + while (vty_getchars(inst->vty, discard_buf,
>> + sizeof(discard_buf)) > 0) {
>> + /* discard */
>> + }
>> + }
>> + }
>> + }
>> +#endif
>> +
>> g_hash_table_insert(vof->of_instances,
>> GINT_TO_POINTER(vof->of_instance_last),
>> inst);
>> @@ -577,6 +602,23 @@ static uint32_t vof_write(Vof *vof, uint32_t
>> ihandle, uint32_t buf,
>> return PROM_ERROR;
>> }
>>
>> +#ifdef CONFIG_PSERIES
>> + if (inst->vty) {
>> + uint32_t total_written = 0;
>> +
>> + for ( ; len > 0; len -= cb) {
>> + cb = MIN(len, sizeof(tmp));
>> + if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) {
>> + return PROM_ERROR;
>> + }
>> + vty_putchars(inst->vty, (uint8_t *)tmp, cb);
>> + buf += cb;
>> + total_written += cb;
>> + }
>> + return total_written;
>> + }
>> +#endif
>> +
>
> Instead of adding #ifdefs in this file could these be implemented as
> some functions exported by spapr_vof.c and called here without needing
> #ifdefs? Maybe it could be part of VofMachineIfClass similar to setprop.
>
Agreed. In v3 I will add new callbacks in VofMachineIfClass for
vty_open, vty_read and vty_write and implement all those in spapr_vof.c
and just call the hook from here without needing any ifdefs.
> Regards,
> BALATON Zoltan
>
>> for ( ; len > 0; len -= cb) {
>> cb = MIN(len, sizeof(tmp) - 1);
>> if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) {
>> @@ -594,6 +636,46 @@ static uint32_t vof_write(Vof *vof, uint32_t
>> ihandle, uint32_t buf,
>> return len;
>> }
>>
>> +static uint32_t vof_read(Vof *vof, uint32_t ihandle, uint32_t buf,
>> + uint32_t len)
>> +{
>> + OfInstance *inst = (OfInstance *)
>> + g_hash_table_lookup(vof->of_instances,
>> GINT_TO_POINTER(ihandle));
>> +
>> + if (!inst) {
>> + trace_vof_error_read(ihandle);
>> + return PROM_ERROR;
>> + }
>> +
>> +#ifdef CONFIG_PSERIES
>> + if (inst->vty) {
>> + uint8_t tmp[VOF_VTY_BUF_SIZE];
>> + unsigned cb = MIN(len, sizeof(tmp));
>> + uint32_t bytes_read = vty_getchars(inst->vty, tmp, cb);
>> + if (bytes_read > 0) {
>> + if (VOF_MEM_WRITE(buf, tmp, bytes_read) != MEMTX_OK) {
>> + trace_vof_error_read(ihandle);
>> + return PROM_ERROR;
>> + }
>> + }
>> + if (trace_event_get_state(TRACE_VOF_READ) &&
>> + qemu_loglevel_mask(LOG_TRACE)) {
>> + char trace_buf[VOF_VTY_BUF_SIZE + 1];
>> + memcpy(trace_buf, tmp, bytes_read);
>> + trace_buf[bytes_read] = '\0';
>> + trace_vof_read(ihandle, bytes_read, trace_buf);
>> + }
>> + return bytes_read;
>> + }
>> +#endif
>> +
>> + /*
>> + * For other devices, return 0 to indicate no data available.
>> + * This allows GRUB to continue without blocking on input.
>> + */
>> + return 0;
>> +}
>> +
>> static void vof_claimed_dump(GArray *claimed)
>> {
>> int i;
>> @@ -906,6 +988,8 @@ static uint32_t vof_client_handle(MachineState
>> *ms, void *fdt, Vof *vof,
>> ret = vof_instance_to_path(fdt, vof, args[0], args[1], args[2]);
>> } else if (cmpserv("write", 3, 1)) {
>> ret = vof_write(vof, args[0], args[1], args[2]);
>> + } else if (cmpserv("read", 3, 1)) {
>> + ret = vof_read(vof, args[0], args[1], args[2]);
>> } else if (cmpserv("claim", 3, 1)) {
>> uint64_t ret64 = vof_claim(vof, args[0], args[1], args[2]);
>>
>> diff --git a/include/hw/ppc/spapr_vio.h b/include/hw/ppc/spapr_vio.h
>> index 0ea0dbae8b..81e7c0b91b 100644
>> --- a/include/hw/ppc/spapr_vio.h
>> +++ b/include/hw/ppc/spapr_vio.h
>> @@ -136,6 +136,7 @@ static inline int spapr_vio_dma_set(SpaprVioDevice
>> *dev, uint64_t taddr,
>> int spapr_vio_send_crq(SpaprVioDevice *dev, uint8_t *crq);
>>
>> SpaprVioDevice *vty_lookup(SpaprMachineState *spapr, target_ulong reg);
>> +int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max);
>> void vty_putchars(SpaprVioDevice *sdev, uint8_t *buf, int len);
>> void spapr_vty_create(SpaprVioBus *bus, Chardev *chardev);
>> void spapr_vlan_create(SpaprVioBus *bus, NICInfo *nd);
>>
>
Regards,
Utkarsh
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [RFC PATCH v2 5/8] ppc/spapr: add block device backend to VOF open/read/write/seek services
2026-10-07 18:20 ` BALATON Zoltan
@ 2026-10-08 9:24 ` Utkarsh Verma
2026-10-08 12:42 ` BALATON Zoltan
0 siblings, 1 reply; 18+ messages in thread
From: Utkarsh Verma @ 2026-10-08 9:24 UTC (permalink / raw)
To: BALATON Zoltan
Cc: qemu-devel, qemu-ppc, aik, nnmlinux, avnish, pbonzini, th.huth,
sbhat, harshpb, amachhiw, rathc, philmd, npiggin,
marcandre.lureau, fam, alex.bennee, richard.henderson
On 10/7/26 11:50 PM, BALATON Zoltan wrote:
> On Fri, 2 Oct 2026, uverma@linux.ibm.com wrote:
>> From: Utkarsh Verma <uverma@linux.ibm.com>
>>
>> Extend the VOF client interface to support block device I/O through the
>> sPAPR SCSI disk, which is needed by bootloaders such as GRUB during VOF
>> disk boot.
>>
>> Add BlockBackend and position tracking fields to OfInstance and use them
>> in vof_seek(), vof_write() and vof_read() to extend their functionality
>> to handle block devices as well.
>>
>> In vof_do_open(), detect "disk" FDT nodes, extract the SRP LUN from the
>> path string, and use scsi_device_find() + blk_by_dev() to obtain the
>> matching BlockBackend.
>>
>> Add vof_seek() to implement the OpenFirmware "seek" client service,
>> which was previously missing.
>>
>> AI-used-for: code
>> Signed-off-by: Utkarsh Verma <uverma@linux.ibm.com>
>> ---
>> Changelog:
>> v2:
>> - vof_do_open(): replaced brute force blk_next() loop with
>> vof_find_vscsi_bus() + scsi_device_find() + blk_by_dev() for a SCSI
>> disk discovery.
>> - vof_write() block path: capped I/O to VOF_BLK_BUF_SIZE (64 KB) per
>> iteration, checked blk_flush() return value, added bdrv_is_read_only()
>> guard before any write attempt.
>> - vof_write() serial path: fixed an existing bug, which did not advance
>> the buffer after each iteration and returned the remaining len instead
>> of bytes written.
>> - vof_read() block path: added the same loop pattern as vof_write(),
>> advancing buf and position correctly per iteration.
>> - path_offset(): added wildcard fallback so "disk@<srp-lun>" paths
>> resolve to the parent's generic "disk" FDT subnode.
>> ---
>> hw/ppc/trace-events | 2 +
>> hw/ppc/vof.c | 217 +++++++++++++++++++++++++++++++++++++++++++-
>> 2 files changed, 215 insertions(+), 4 deletions(-)
>>
>> diff --git a/hw/ppc/trace-events b/hw/ppc/trace-events
>> index dbdcfada26..a61398ecb2 100644
>> --- a/hw/ppc/trace-events
>> +++ b/hw/ppc/trace-events
>> @@ -78,6 +78,7 @@ vof_error_unknown_service(const char *service, int
>> nargs, int nret) "\"%s\" args
>> vof_error_unknown_method(const char *method) "\"%s\""
>> vof_error_unknown_ihandle_close(uint32_t ih) "ih=0x%x"
>> vof_error_unknown_path(const char *path) "\"%s\""
>> +vof_error_seek(uint32_t ih) "ih=0x%x"
>> vof_error_write(uint32_t ih) "ih=0x%x"
>> vof_error_read(uint32_t ih) "ih=0x%x"
>> vof_finddevice(const char *path, uint32_t ph) "\"%s\" => ph=0x%x"
>> @@ -92,6 +93,7 @@ vof_interpret(const char *cmd, uint32_t param1,
>> uint32_t param2, uint32_t ret, u
>> vof_package_to_path(uint32_t ph, const char *tmp, int ret) "ph=0x%x =>
>> %s len=%d"
>> vof_instance_to_path(uint32_t ih, uint32_t ph, const char *tmp, int
>> ret) "ih=0x%x ph=0x%x => %s len=%d"
>> vof_instance_to_package(uint32_t ih, uint32_t ph) "ih=0x%x => ph=0x%x"
>> +vof_seek(uint32_t ih, uint64_t pos) "ih=0x%x pos=0x%"PRIx64
>> vof_write(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u]
>> \"%s\""
>> vof_read(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\""
>> vof_avail(uint64_t start, uint64_t end, uint64_t size)
>> "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64
>> diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
>> index 6f6d424347..d4b57b3b46 100644
>> --- a/hw/ppc/vof.c
>> +++ b/hw/ppc/vof.c
>> @@ -11,6 +11,7 @@
>>
>> #include CONFIG_DEVICES /* CONFIG_PSERIES */
>> #include "qemu/osdep.h"
>> +#include "qemu/cutils.h"
>> #include "qemu/ctype.h"
>> #include "qemu/timer.h"
>> #include "qemu/range.h"
>> @@ -25,6 +26,9 @@
>> #include "trace.h"
>>
>> #include "hw/ppc/spapr_vio.h"
>> +#include "hw/scsi/scsi.h"
>> +#include "system/block-backend.h"
>> +#include "block/block-io.h"
>> #include <libfdt.h>
>>
>> /*
>> @@ -38,6 +42,7 @@
>> #define VOF_MAX_METHODLEN 256
>> #define VOF_MAX_FORTHCODE 256
>> #define VOF_VTY_BUF_SIZE 256
>> +#define VOF_BLK_BUF_SIZE (64 * 1024)
>
> * KiB instead of 1024?
>
Sure, will use KiB and fix it for SPAPR_VOF_PREP_MAX_SIZE in spapr_vof.c
as well in v3.
>>
>> typedef struct {
>> uint64_t start;
>> @@ -47,6 +52,8 @@ typedef struct {
>> typedef struct {
>> char *path; /* the path used to open the instance */
>> uint32_t phandle;
>> + BlockBackend *blk;
>> + uint64_t pos; /* current position for seek operations */
>> void *vty;
>> } OfInstance;
>>
>> @@ -138,6 +145,8 @@ static int path_offset(const void *fdt, const char
>> *path)
>> {
>> g_autofree char *p = NULL;
>> char *at;
>> + char *last_slash;
>> + int offset;
>>
>> /*
>> * https://www.devicetree.org/open-firmware/bindings/ppc/release/
>> ppc-2_1.html#HDR16
>> @@ -156,7 +165,76 @@ static int path_offset(const void *fdt, const
>> char *path)
>> }
>> }
>>
>> - return fdt_path_offset(fdt, p);
>> + offset = fdt_path_offset(fdt, p);
>> + if (offset >= 0) {
>> + return offset;
>> + }
>> +
>> + /*
>> + * Apply the OF wildcard rule for vSCSI disk paths. A path ending in
>> + * disk@<srp-lun> has no per-LUN FDT node, instead the FDT
>> contains a single
>> + * generic "disk" child (no srp-lun appended to the path). The
>> phandle is
>> + * shared by all LUNs on the adapter, matching the Open Firmware
>> behaviour.
>> + */
>> + last_slash = strrchr(p, '/');
>> + if (last_slash && strncmp(last_slash + 1, "disk@", 5) == 0) {
>> + int parent_off;
>> +
>> + *last_slash = '\0';
>> + parent_off = fdt_path_offset(fdt, p);
>> + if (parent_off >= 0) {
>> + int child_off = fdt_subnode_offset(fdt, parent_off, "disk");
>
> I'm not sure how this should work but what if there are multiple disk
> nodes such as disk@0 disk@1 or similar? Should this look for the
> original ending of the path instead of just "disk"?
>
There is only one "disk" child node under the vSCSI adapter regardless
of how many physical disks are attached. It seems a little weird that
only one disk node is created even for multiple vSCSI disks, but that is
the actual open firmware behaviour, I cross checked with the device tree
data on a pseries LPAR machine and with the SLOF implementation of it as
well.
So, the GRUB only searches for a "disk" child node under the vscsi
adapter and then calls the vscsi-report-luns method to get the full path
of the individual disks and passes the same path to firmware while
calling open. So, GRUB doesn't rely on FDT disk data to search the disk,
it calls the vscs-report-luns method to get the full disk path.
This is why I strip the @<srp-lun> part in the path before searching the
FDT, the node it needs to resolve to is always the shared "disk" child node.
>> + if (child_off >= 0) {
>> + return child_off;
>> + }
>> + }
>> + }
>> +
>> + return offset;
>> +}
>> +
>> +/*
>> + * Return the SCSIBus for a v-scsi@<reg> path, or NULL if not found.
>> + */
>> +static SCSIBus *vof_find_vscsi_bus(MachineState *ms, const char *path)
>> +{
>> +#ifdef CONFIG_PSERIES
>> + SpaprMachineState *spapr = SPAPR_MACHINE(ms);
>> + const char *at;
>> + const char *endptr;
>> + unsigned long reg;
>> + SpaprVioDevice *vdev;
>> + BusState *bus;
>> +
>> + if (!spapr || !spapr->vio_bus) {
>> + return NULL;
>> + }
>> +
>> + at = strstr(path, "v-scsi@");
>> + if (!at) {
>> + return NULL;
>> + }
>> + at = strchr(at, '@');
>> + if (!at) {
>> + return NULL;
>> + }
>> +
>> + if (qemu_strtoul(at + 1, &endptr, 16, ®) || endptr == at + 1) {
>> + return NULL;
>> + }
>> +
>> + vdev = spapr_vio_find_by_reg(spapr->vio_bus, (uint32_t)reg);
>> + if (!vdev) {
>> + return NULL;
>> + }
>> +
>> + QLIST_FOREACH(bus, &vdev->qdev.child_bus, sibling) {
>> + if (object_dynamic_cast(OBJECT(bus), TYPE_SCSI_BUS)) {
>> + return SCSI_BUS(bus);
>> + }
>> + }
>> +#endif /* CONFIG_PSERIES */
>
> No #ifdefs. This vof.c file is the generic machine independent parts, so
> this function should go to spapr_vof.c and maybe exported through the
> VOF machine class interface. Pegasos2 has IDE disks so if I want to
> later add support for this for that machine I should implement similar
> function there and this one here should call the implementation supplied
> by the machine not hard code spapr here. So this should be some generic
> find block device for device node function that the machine implements.
>
Agreed, I will move this vof_find_vscsi_bus function in spapr_vof.c
>> + return NULL;
>> }
>>
>> static uint32_t vof_finddevice(const void *fdt, uint32_t nodeaddr)
>> @@ -450,6 +528,8 @@ static uint32_t vof_do_open(void *fdt, Vof *vof,
>> int offset, const char *path)
>> {
>> uint32_t ret = PROM_ERROR;
>> OfInstance *inst = NULL;
>> + const char *node_name;
>> + MachineState *ms = MACHINE(qdev_get_machine());
>>
>> if (vof->of_instance_last == 0xFFFFFFFF) {
>> /* We do not recycle ihandles yet */
>> @@ -462,13 +542,43 @@ static uint32_t vof_do_open(void *fdt, Vof *vof,
>> int offset, const char *path)
>> ++vof->of_instance_last;
>>
>> inst->path = g_strdup(path);
>> + inst->blk = NULL;
>> + inst->pos = 0;
>> inst->vty = NULL;
>>
>> + node_name = fdt_get_name(fdt, offset, NULL);
>> +
>> + /*
>> + * vSCSI disk open. The FDT contains only a generic "disk" child
>> node (no
>> + * srp-lun appended to the path), matching Open Firmware behaviour.
>> + * Extract the LUN from the "disk@<hex>" component of the path
>> string
>> + * and use it to find the corresponding QEMU block backend.
>> + */
>> + if (node_name && strcmp(node_name, "disk") == 0) {
>> + const char *disk_at = strstr(path, "/disk@");
>> + const char *endptr;
>> + uint64_t srp_lun;
>> +
>> + if (disk_at &&
>> + qemu_strtou64(disk_at + 6, &endptr, 16, &srp_lun) == 0) {
>> + SCSIBus *sbus = vof_find_vscsi_bus(ms, path);
>> +
>> + if (sbus) {
>> + uint32_t target = (srp_lun >> 56) & 0x3f;
>> + uint32_t channel = (srp_lun >> 53) & 0x7;
>> + uint32_t lun = (srp_lun >> 48) & 0x1f;
>> + SCSIDevice *sdev = scsi_device_find(sbus, (int)channel,
>> + (int)target,
>> (int)lun);
>> + if (sdev) {
>> + inst->blk = blk_by_dev(sdev);
>> + }
>> + }
>> + }
>> + }
>> +
>
> This should be in spapr_vof.c too as it's specific to spapr. Just call
> the machine function that returns the blk for the node here. The rest
> below seems generic so those are OK to be here in vof.c.
>
Will add a new find_blk kind of a callback function in
VofMachineIfClass, and move all this code inside that function in
spapr_vof.c and just call hook from here.
> Regards,
> BALATON Zoltan
>
>> #ifdef CONFIG_PSERIES
>> - const char *node_name = fdt_get_name(fdt, offset, NULL);
>> if (node_name && strncmp(node_name, "vty", 3) == 0) {
>> uint8_t discard_buf[VOF_VTY_BUF_SIZE];
>> - MachineState *ms = MACHINE(qdev_get_machine());
>> SpaprMachineState *spapr = SPAPR_MACHINE(ms);
>>
>> if (spapr && spapr->vio_bus) {
>> @@ -602,6 +712,38 @@ static uint32_t vof_write(Vof *vof, uint32_t
>> ihandle, uint32_t buf,
>> return PROM_ERROR;
>> }
>>
>> + if (inst->blk) {
>> + if (bdrv_is_read_only(blk_bs(inst->blk))) {
>> + trace_vof_error_write(ihandle);
>> + return PROM_ERROR;
>> + }
>> +
>> + g_autofree uint8_t *blkbuf = g_malloc(MIN(len,
>> (uint32_t)VOF_BLK_BUF_SIZE));
>> + uint32_t total = 0;
>> +
>> + while (len > 0) {
>> + cb = MIN(len, (uint32_t)VOF_BLK_BUF_SIZE);
>> + if (VOF_MEM_READ(buf, blkbuf, cb) != MEMTX_OK) {
>> + trace_vof_error_write(ihandle);
>> + return PROM_ERROR;
>> + }
>> + if (blk_pwrite(inst->blk, inst->pos, cb, blkbuf, 0) < 0) {
>> + trace_vof_error_write(ihandle);
>> + return PROM_ERROR;
>> + }
>> + inst->pos += cb;
>> + buf += cb;
>> + len -= cb;
>> + total += cb;
>> + }
>> + if (blk_flush(inst->blk) < 0) {
>> + trace_vof_error_write(ihandle);
>> + return PROM_ERROR;
>> + }
>> + trace_vof_write(ihandle, total, "(disk)");
>> + return total;
>> + }
>> +
>> #ifdef CONFIG_PSERIES
>> if (inst->vty) {
>> uint32_t total_written = 0;
>> @@ -619,6 +761,7 @@ static uint32_t vof_write(Vof *vof, uint32_t
>> ihandle, uint32_t buf,
>> }
>> #endif
>>
>> + uint32_t total_traced = 0;
>> for ( ; len > 0; len -= cb) {
>> cb = MIN(len, sizeof(tmp) - 1);
>> if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) {
>> @@ -631,9 +774,11 @@ static uint32_t vof_write(Vof *vof, uint32_t
>> ihandle, uint32_t buf,
>> tmp[cb] = '\0';
>> trace_vof_write(ihandle, cb, tmp);
>> }
>> + buf += cb;
>> + total_traced += cb;
>> }
>>
>> - return len;
>> + return total_traced;
>> }
>>
>> static uint32_t vof_read(Vof *vof, uint32_t ihandle, uint32_t buf,
>> @@ -647,6 +792,33 @@ static uint32_t vof_read(Vof *vof, uint32_t
>> ihandle, uint32_t buf,
>> return PROM_ERROR;
>> }
>>
>> + if (inst->blk) {
>> + g_autofree uint8_t *tmp = g_malloc(MIN(len,
>> (uint32_t)VOF_BLK_BUF_SIZE));
>> + uint32_t total = 0;
>> +
>> + while (len > 0) {
>> + unsigned cb = MIN(len, (uint32_t)VOF_BLK_BUF_SIZE);
>> + int ret = blk_pread(inst->blk, inst->pos, cb, tmp, 0);
>> + if (ret < 0) {
>> + trace_vof_error_read(ihandle);
>> + return PROM_ERROR;
>> + }
>> +
>> + if (VOF_MEM_WRITE(buf, tmp, cb) != MEMTX_OK) {
>> + trace_vof_error_read(ihandle);
>> + return PROM_ERROR;
>> + }
>> +
>> + inst->pos += cb;
>> + buf += cb;
>> + len -= cb;
>> + total += cb;
>> + }
>> +
>> + trace_vof_read(ihandle, total, "(disk)");
>> + return total;
>> + }
>> +
>> #ifdef CONFIG_PSERIES
>> if (inst->vty) {
>> uint8_t tmp[VOF_VTY_BUF_SIZE];
>> @@ -676,6 +848,41 @@ static uint32_t vof_read(Vof *vof, uint32_t
>> ihandle, uint32_t buf,
>> return 0;
>> }
>>
>> +static uint32_t vof_seek(Vof *vof, uint32_t ihandle, uint32_t pos_hi,
>> + uint32_t pos_lo)
>> +{
>> + OfInstance *inst = (OfInstance *)
>> + g_hash_table_lookup(vof->of_instances,
>> GINT_TO_POINTER(ihandle));
>> + uint64_t pos = ((uint64_t)pos_hi << 32) | pos_lo;
>> +
>> + if (!inst) {
>> + trace_vof_error_seek(ihandle);
>> + return PROM_ERROR;
>> + }
>> +
>> + if (inst->blk) {
>> + int64_t size = blk_getlength(inst->blk);
>> +
>> + if (size < 0) {
>> + trace_vof_error_seek(ihandle);
>> + return PROM_ERROR;
>> + }
>> +
>> + if (pos > (uint64_t)size) {
>> + trace_vof_error_seek(ihandle);
>> + return PROM_ERROR;
>> + }
>> +
>> + inst->pos = pos;
>> + trace_vof_seek(ihandle, pos);
>> + return 0;
>> + }
>> +
>> + /* VTY and other devices don't support seek */
>> + trace_vof_error_seek(ihandle);
>> + return PROM_ERROR;
>> +}
>> +
>> static void vof_claimed_dump(GArray *claimed)
>> {
>> int i;
>> @@ -986,6 +1193,8 @@ static uint32_t vof_client_handle(MachineState
>> *ms, void *fdt, Vof *vof,
>> ret = vof_package_to_path(fdt, args[0], args[1], args[2]);
>> } else if (cmpserv("instance-to-path", 3, 1)) {
>> ret = vof_instance_to_path(fdt, vof, args[0], args[1], args[2]);
>> + } else if (cmpserv("seek", 3, 1)) {
>> + ret = vof_seek(vof, args[0], args[1], args[2]);
>> } else if (cmpserv("write", 3, 1)) {
>> ret = vof_write(vof, args[0], args[1], args[2]);
>> } else if (cmpserv("read", 3, 1)) {
>>
>
Regards,
Utkarsh
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [RFC PATCH v2 8/8] ppc/spapr: implement vscsi-report-luns call-method for PAPR vSCSI
2026-10-07 18:31 ` BALATON Zoltan
@ 2026-10-08 9:25 ` Utkarsh Verma
0 siblings, 0 replies; 18+ messages in thread
From: Utkarsh Verma @ 2026-10-08 9:25 UTC (permalink / raw)
To: BALATON Zoltan
Cc: qemu-devel, qemu-ppc, aik, nnmlinux, avnish, pbonzini, th.huth,
sbhat, harshpb, amachhiw, rathc, philmd, npiggin,
marcandre.lureau, fam, alex.bennee, richard.henderson
On 10/8/26 12:01 AM, BALATON Zoltan wrote:
> On Fri, 2 Oct 2026, uverma@linux.ibm.com wrote:
>> From: Utkarsh Verma <uverma@linux.ibm.com>
>>
>> GRUB (grub-core/disk/ieee1275/ofdisk.c) calls "vscsi-report-luns" on
>> any PAPR virtual SCSI controller ihandle to enumerate attached LUNs
>> before attempting to boot from disk. Without this method VOF returns
>> PROM_ERROR and GRUB cannot find vSCSI disks.
>>
>> Add vof_vscsi_report_luns() to build the LUN response table in guest
>> memory and wire it into vof_call_method().
>
> These should go to spapr_vof.c too as they are spapr specific.
>
> Regards,
> BALATON Zoltan
>
Sure, will move all this implementation to spapr_vof.c and just add a
vscsi_report_luns kind of callback function in VofMachineIfClass and
just call that hook from here.
Regards,
Utkarsh
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [RFC PATCH v2 5/8] ppc/spapr: add block device backend to VOF open/read/write/seek services
2026-10-08 9:24 ` Utkarsh Verma
@ 2026-10-08 12:42 ` BALATON Zoltan
0 siblings, 0 replies; 18+ messages in thread
From: BALATON Zoltan @ 2026-10-08 12:42 UTC (permalink / raw)
To: Utkarsh Verma
Cc: qemu-devel, qemu-ppc, aik, nnmlinux, avnish, pbonzini, th.huth,
sbhat, harshpb, amachhiw, rathc, philmd, npiggin,
marcandre.lureau, fam, alex.bennee, richard.henderson
[-- Attachment #1: Type: text/plain, Size: 5977 bytes --]
Hello,
On Thu, 8 Oct 2026, Utkarsh Verma wrote:
[...]
>>> diff --git a/hw/ppc/vof.c b/hw/ppc/vof.c
>>> index 6f6d424347..d4b57b3b46 100644
>>> --- a/hw/ppc/vof.c
>>> +++ b/hw/ppc/vof.c
>>> @@ -11,6 +11,7 @@
>>>
>>> #include CONFIG_DEVICES /* CONFIG_PSERIES */
>>> #include "qemu/osdep.h"
>>> +#include "qemu/cutils.h"
>>> #include "qemu/ctype.h"
>>> #include "qemu/timer.h"
>>> #include "qemu/range.h"
>>> @@ -25,6 +26,9 @@
>>> #include "trace.h"
>>>
>>> #include "hw/ppc/spapr_vio.h"
>>> +#include "hw/scsi/scsi.h"
>>> +#include "system/block-backend.h"
>>> +#include "block/block-io.h"
>>> #include <libfdt.h>
>>>
>>> /*
>>> @@ -38,6 +42,7 @@
>>> #define VOF_MAX_METHODLEN 256
>>> #define VOF_MAX_FORTHCODE 256
>>> #define VOF_VTY_BUF_SIZE 256
>>> +#define VOF_BLK_BUF_SIZE (64 * 1024)
>>
>> * KiB instead of 1024?
>>
> Sure, will use KiB and fix it for SPAPR_VOF_PREP_MAX_SIZE in spapr_vof.c as
> well in v3.
Sorry for taking that long to review which caused you needing to prepare
another version but I have little time for QEMU now and generally don't
like to be first to review AI generated patches so I hoped somebody else
could do a review. Since the list is already high volume, if you agree
with a change it's OK to just include it in next version and you don't
have to reply for those with "OK, I'll do that" here, unless you have a
comment or question about it or answering a question asked.
>>>
>>> typedef struct {
>>> uint64_t start;
>>> @@ -47,6 +52,8 @@ typedef struct {
>>> typedef struct {
>>> char *path; /* the path used to open the instance */
>>> uint32_t phandle;
>>> + BlockBackend *blk;
>>> + uint64_t pos; /* current position for seek operations */
>>> void *vty;
>>> } OfInstance;
>>>
>>> @@ -138,6 +145,8 @@ static int path_offset(const void *fdt, const char
>>> *path)
>>> {
>>> g_autofree char *p = NULL;
>>> char *at;
>>> + char *last_slash;
>>> + int offset;
>>>
>>> /*
>>> * https://www.devicetree.org/open-firmware/bindings/ppc/release/
>>> ppc-2_1.html#HDR16
>>> @@ -156,7 +165,76 @@ static int path_offset(const void *fdt, const char
>>> *path)
>>> }
>>> }
>>>
>>> - return fdt_path_offset(fdt, p);
>>> + offset = fdt_path_offset(fdt, p);
>>> + if (offset >= 0) {
>>> + return offset;
>>> + }
>>> +
>>> + /*
>>> + * Apply the OF wildcard rule for vSCSI disk paths. A path ending in
>>> + * disk@<srp-lun> has no per-LUN FDT node, instead the FDT contains a
>>> single
>>> + * generic "disk" child (no srp-lun appended to the path). The
>>> phandle is
>>> + * shared by all LUNs on the adapter, matching the Open Firmware
>>> behaviour.
>>> + */
>>> + last_slash = strrchr(p, '/');
>>> + if (last_slash && strncmp(last_slash + 1, "disk@", 5) == 0) {
>>> + int parent_off;
>>> +
>>> + *last_slash = '\0';
>>> + parent_off = fdt_path_offset(fdt, p);
>>> + if (parent_off >= 0) {
>>> + int child_off = fdt_subnode_offset(fdt, parent_off, "disk");
>>
>> I'm not sure how this should work but what if there are multiple disk nodes
>> such as disk@0 disk@1 or similar? Should this look for the original ending
>> of the path instead of just "disk"?
>>
> There is only one "disk" child node under the vSCSI adapter regardless of how
> many physical disks are attached. It seems a little weird that only one disk
> node is created even for multiple vSCSI disks, but that is the actual open
> firmware behaviour, I cross checked with the device tree data on a pseries
> LPAR machine and with the SLOF implementation of it as well.
> So, the GRUB only searches for a "disk" child node under the vscsi adapter
> and then calls the vscsi-report-luns method to get the full path of the
> individual disks and passes the same path to firmware while calling open. So,
> GRUB doesn't rely on FDT disk data to search the disk, it calls the
> vscs-report-luns method to get the full disk path.
> This is why I strip the @<srp-lun> part in the path before searching the FDT,
> the node it needs to resolve to is always the shared "disk" child node.
We're in in the common vof.c file here which is a generic OpenFirmware
implementation and is shared with pegasos (and later could be used by
other machines needing OpenFirmware). On pegasos2 for example we can have
/pci/ide/disk@0,0, /pci/ide/disk@0,1, /pci/ide/disk@1,0 and
/pci/ide/disk@1,1 for 4 IDE disks so this change may break clients
accessing those device nodes. So likely you need to avoid changing this
function but maybe by moving all the other stuff to spapr_vof.c this will
not be needed here any more and can be handled in spapr_vof.c. If you find
you're trying to change vof.c for something specific to spapr then think
again because anything spapr specific should be in spapr_vof.c.
I've also noticed pegasos1 sets boot-device to /pci/ide/disk@0,0:0 where
:0 is showing the partition so that is a generic OF thing that could be
handled here in vof.c but that could be fixed later as well. To make it
easy to use this block device support by other machines in the future, the
functions to be added to VofMachineIfClass should be chosen to do the
machine specific mapping between OpenFirmware nodes to generic QEMU
constructs and then the code here can work on those without needing to be
machine specific. E.g. I'm not sure requiring machines to implement
vty_open/read/write is necessary and if it could be done more generically
with just one function mapping an OF device node to something that can be
used here like for block device the mapping function returns a blk device
for an OF device node. That would also make machine code simpler as it
does not have to talk to the device just return the QEMU struct to vof and
reduce code duplication between machines. But I don't know what would be
the right QEMU construct for a vty, maybe a chardev?
Regards,
BALATON Zoltan
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-10-08 12:43 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-02 5:25 [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support uverma
2026-10-02 5:25 ` [RFC PATCH v2 1/8] ppc/spapr: add PReP boot partition detection uverma
2026-10-02 5:25 ` [RFC PATCH v2 2/8] hw/loader: add load_elf_ram_sym_buf() for in-memory ELF loading uverma
2026-10-06 8:10 ` Utkarsh Verma
2026-10-02 5:25 ` [RFC PATCH v2 3/8] ppc/spapr: add baseline VOF disk boot support uverma
2026-10-02 5:25 ` [RFC PATCH v2 4/8] ppc/spapr: add VTY backend support to OF read/write/open services uverma
2026-10-07 14:35 ` BALATON Zoltan
2026-10-08 9:17 ` Utkarsh Verma
2026-10-02 5:25 ` [RFC PATCH v2 5/8] ppc/spapr: add block device backend to VOF open/read/write/seek services uverma
2026-10-07 18:20 ` BALATON Zoltan
2026-10-08 9:24 ` Utkarsh Verma
2026-10-08 12:42 ` BALATON Zoltan
2026-10-02 5:25 ` [RFC PATCH v2 6/8] spapr_vscsi: add VOF disk nodes to the device tree uverma
2026-10-02 5:25 ` [RFC PATCH v2 7/8] ppc/spapr: strip OF path argument suffix in path_offset uverma
2026-10-02 5:25 ` [RFC PATCH v2 8/8] ppc/spapr: implement vscsi-report-luns call-method for PAPR vSCSI uverma
2026-10-07 18:31 ` BALATON Zoltan
2026-10-08 9:25 ` Utkarsh Verma
2026-10-06 16:49 ` [RFC PATCH v2 0/8] ppc/spapr: VOF disk image (qcow2) boot support Avnish Chouhan
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.