From: Jared Rossi <jrossi@linux.ibm.com>
Add little-endian virt-queue configuration and support for virtio-blk-pci IPL
devices.
Signed-off-by: Jared Rossi <jrossi@linux.ibm.com>
---
pc-bios/s390-ccw/main.c | 60 +++++++-
pc-bios/s390-ccw/virtio-blkdev.c | 18 +++
pc-bios/s390-ccw/virtio-pci.c | 256 +++++++++++++++++++++++++++++++
pc-bios/s390-ccw/virtio-pci.h | 2 +
pc-bios/s390-ccw/virtio.c | 46 +++++-
pc-bios/s390-ccw/virtio.h | 1 +
6 files changed, 379 insertions(+), 4 deletions(-)
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index 2ffce743bd..7d4a5b39d3 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -18,6 +18,8 @@
#include "virtio.h"
#include "virtio-scsi.h"
#include "dasd-ipl.h"
+#include "clp.h"
+#include "virtio-pci.h"
static SubChannelId blk_schid = { .one = 1 };
static char loadparm_str[LOADPARM_LEN + 1];
@@ -151,6 +153,21 @@ static bool find_subch(int dev_no)
return false;
}
+static bool find_fid(uint32_t fid)
+{
+ ClpFhListEntry entry;
+ VDev *vdev = virtio_get_device();
+
+ if (find_pci_function(fid, &entry)) {
+ return false;
+ }
+
+ vdev->pci_fh = entry.fh;
+ virtio_pci_id2type(vdev, entry.device_id);
+
+ return vdev->dev_type != 0;
+}
+
static void menu_setup(void)
{
if (memcmp(loadparm_str, LOADPARM_PROMPT, LOADPARM_LEN) == 0) {
@@ -239,6 +256,9 @@ static bool find_boot_device(void)
blk_schid.ssid = iplb.scsi.ssid & 0x3;
found = find_subch(iplb.scsi.devno);
break;
+ case S390_IPL_TYPE_PCI:
+ found = find_fid(iplb.pci.fid);
+ break;
default:
puts("Unsupported IPLB");
}
@@ -275,7 +295,7 @@ static int virtio_setup(void)
return ret;
}
-static void ipl_boot_device(void)
+static void ipl_ccw_device(void)
{
switch (cutype) {
case CU_TYPE_DASD_3990:
@@ -288,7 +308,43 @@ static void ipl_boot_device(void)
}
break;
default:
- printf("Attempting to boot from unexpected device type 0x%X\n", cutype);
+ printf("Cannot boot CCW device with cu type 0x%X\n", cutype);
+ }
+}
+
+static void ipl_pci_device(void)
+{
+ VDev *vdev = virtio_get_device();
+ vdev->is_cdrom = false;
+ vdev->scsi_device_selected = false;
+
+ if (virtio_pci_setup_device()) {
+ return;
+ }
+
+ switch (vdev->dev_type) {
+ case VIRTIO_ID_BLOCK:
+ if (virtio_setup() == 0) {
+ zipl_load();
+ }
+ break;
+ default:
+ printf("Cannot boot PCI device type 0x%X\n", vdev->dev_type);
+ }
+}
+
+static void ipl_boot_device(void)
+{
+ switch (virtio_get_device()->ipl_type) {
+ case S390_IPL_TYPE_QEMU_SCSI:
+ case S390_IPL_TYPE_CCW:
+ ipl_ccw_device();
+ break;
+ case S390_IPL_TYPE_PCI:
+ ipl_pci_device();
+ break;
+ default:
+ puts("Unrecognized IPL type!");
}
}
diff --git a/pc-bios/s390-ccw/virtio-blkdev.c b/pc-bios/s390-ccw/virtio-blkdev.c
index e14bcf0382..c517a9eef3 100644
--- a/pc-bios/s390-ccw/virtio-blkdev.c
+++ b/pc-bios/s390-ccw/virtio-blkdev.c
@@ -13,10 +13,22 @@
#include "virtio.h"
#include "virtio-scsi.h"
#include "virtio-ccw.h"
+#include "virtio-pci.h"
+#include "bswap.h"
#define VIRTIO_BLK_F_GEOMETRY (1 << 4)
#define VIRTIO_BLK_F_BLK_SIZE (1 << 6)
+/*
+ * Format header for little endian IPL
+ */
+static void fmt_blk_hdr_le(VirtioBlkOuthdr *hdr)
+{
+ hdr->type = bswap32(hdr->type);
+ hdr->ioprio = bswap32(hdr->ioprio);
+ hdr->sector = bswap64(hdr->sector);
+}
+
static int virtio_blk_read_many(VDev *vdev, unsigned long sector, void *load_addr,
int sec_num)
{
@@ -29,6 +41,10 @@ static int virtio_blk_read_many(VDev *vdev, unsigned long sector, void *load_add
out_hdr.ioprio = 99;
out_hdr.sector = virtio_sector_adjust(sector);
+ if (!be_ipl()) {
+ fmt_blk_hdr_le(&out_hdr);
+ }
+
vring_send_buf(vr, &out_hdr, sizeof(out_hdr), VRING_DESC_F_NEXT);
/* This is where we want to receive data */
@@ -242,6 +258,8 @@ int virtio_blk_setup_device(void)
case S390_IPL_TYPE_QEMU_SCSI:
case S390_IPL_TYPE_CCW:
return virtio_ccw_setup(vdev);
+ case S390_IPL_TYPE_PCI:
+ return virtio_pci_setup(vdev);
default:
return 1;
}
diff --git a/pc-bios/s390-ccw/virtio-pci.c b/pc-bios/s390-ccw/virtio-pci.c
index 70abac0256..464cfd510b 100644
--- a/pc-bios/s390-ccw/virtio-pci.c
+++ b/pc-bios/s390-ccw/virtio-pci.c
@@ -166,3 +166,259 @@ int vpci_read_flex(uint64_t offset, uint8_t pcias, void *buf, int len)
return 0;
}
+
+static int vpci_set_selected_vq(uint16_t queue_num)
+{
+ return vpci_bswap16_write(c_cap.off + VPCI_C_OFFSET_Q_SELECT, c_cap.bar, queue_num);
+}
+
+static int vpci_set_queue_size(uint16_t queue_size)
+{
+ return vpci_bswap16_write(c_cap.off + VPCI_C_OFFSET_Q_SIZE, c_cap.bar, queue_size);
+}
+
+static int vpci_set_queue_enable(uint16_t enabled)
+{
+ return vpci_bswap16_write(c_cap.off + VPCI_C_OFFSET_Q_ENABLE, c_cap.bar, enabled);
+}
+
+static int set_pci_vq_addr(uint64_t config_off, void *addr)
+{
+ return vpci_bswap64_write(c_cap.off + config_off, c_cap.bar, (uint64_t) addr);
+}
+
+static int virtio_pci_get_blk_config(void)
+{
+ VirtioBlkConfig *cfg = &virtio_get_device()->config.blk;
+ int rc = vpci_read_flex(d_cap.off, d_cap.bar, cfg, sizeof(VirtioBlkConfig));
+
+ /* single byte fields are not touched */
+ cfg->capacity = bswap64(cfg->capacity);
+ cfg->size_max = bswap32(cfg->size_max);
+ cfg->seg_max = bswap32(cfg->seg_max);
+
+ cfg->geometry.cylinders = bswap16(cfg->geometry.cylinders);
+
+ cfg->blk_size = bswap32(cfg->blk_size);
+ cfg->min_io_size = bswap16(cfg->min_io_size);
+ cfg->opt_io_size = bswap32(cfg->opt_io_size);
+
+ return rc;
+}
+
+static int virtio_pci_negotiate(void)
+{
+ int i, rc;
+ VDev *vdev = virtio_get_device();
+ struct VirtioFeatureDesc {
+ uint32_t features;
+ uint8_t index;
+ } __attribute__((packed)) feats;
+
+ for (i = 0; i < ARRAY_SIZE(vdev->guest_features); i++) {
+ feats.features = 0;
+ feats.index = i;
+
+ rc = vpci_bswap32_write(c_cap.off + VPCI_C_OFFSET_DFSELECT, c_cap.bar,
+ feats.index);
+ rc |= vpci_read_flex(c_cap.off + VPCI_C_OFFSET_DF, c_cap.bar, &feats, 4);
+
+ vdev->guest_features[i] &= bswap32(feats.features);
+ feats.features = vdev->guest_features[i];
+
+
+ rc |= vpci_bswap32_write(c_cap.off + VPCI_C_OFFSET_GFSELECT, c_cap.bar,
+ feats.index);
+ rc |= vpci_bswap32_write(c_cap.off + VPCI_C_OFFSET_GF, c_cap.bar,
+ feats.features);
+ }
+
+ return rc;
+}
+
+/*
+ * Find the position of the capability config within PCI configuration
+ * space for a given cfg type. Return the position if found, otherwise 0.
+ */
+static uint8_t virtio_pci_find_cap_pos(uint8_t cfg_type)
+{
+ uint8_t next, cfg;
+ int rc;
+
+ rc = vpci_read_byte(PCI_CAPABILITY_LIST, PCI_CFGBAR, &next);
+ rc |= vpci_read_byte(next + 3, PCI_CFGBAR, &cfg);
+
+ while (!rc && (cfg != cfg_type) && next) {
+ rc = vpci_read_byte(next + 1, PCI_CFGBAR, &next);
+ rc |= vpci_read_byte(next + 3, PCI_CFGBAR, &cfg);
+ }
+
+ return rc ? 0 : next;
+}
+
+/*
+ * Read PCI configuration space to find the offset of the Common, Device, and
+ * Notification memory regions within the modern memory space.
+ * Returns 0 if success, 1 if a capability could not be located, or a
+ * negative RC if the configuration read failed.
+ */
+static int virtio_pci_read_pci_cap_config(void)
+{
+ uint8_t pos;
+ int rc;
+
+ /* Common capabilities */
+ pos = virtio_pci_find_cap_pos(VPCI_CAP_COMMON_CFG);
+ if (!pos) {
+ puts("Failed to locate PCI common configuration");
+ return 1;
+ }
+
+ rc = vpci_read_byte(pos + VPCI_CAP_BAR, PCI_CFGBAR, &c_cap.bar);
+ if (rc || vpci_read_bswap32(pos + VPCI_CAP_OFFSET, PCI_CFGBAR, &c_cap.off)) {
+ puts("Failed to read PCI common configuration");
+ return -EIO;
+ }
+
+ /* Device capabilities */
+ pos = virtio_pci_find_cap_pos(VPCI_CAP_DEVICE_CFG);
+ if (!pos) {
+ puts("Failed to locate PCI device configuration");
+ return 1;
+ }
+
+ rc = vpci_read_byte(pos + VPCI_CAP_BAR, PCI_CFGBAR, &d_cap.bar);
+ if (rc || vpci_read_bswap32(pos + VPCI_CAP_OFFSET, PCI_CFGBAR, &d_cap.off)) {
+ puts("Failed to read PCI device configuration");
+ return -EIO;
+ }
+
+ /* Notification capabilities */
+ pos = virtio_pci_find_cap_pos(VPCI_CAP_NOTIFY_CFG);
+ if (!pos) {
+ puts("Failed to locate PCI notification configuration");
+ return 1;
+ }
+
+ rc = vpci_read_byte(pos + VPCI_CAP_BAR, PCI_CFGBAR, &n_cap.bar);
+ if (rc || vpci_read_bswap32(pos + VPCI_CAP_OFFSET, PCI_CFGBAR, &n_cap.off)) {
+ puts("Failed to read PCI notification configuration");
+ return -EIO;
+ }
+
+ rc = vpci_read_bswap32(pos + VPCI_N_CAP_MULT, PCI_CFGBAR, ¬ify_mult);
+ if (rc || vpci_read_bswap16(c_cap.off + VPCI_C_OFFSET_Q_NOFF, c_cap.bar,
+ &q_notify_offset)) {
+ puts("Failed to read notification queue configuration");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int virtio_pci_setup(VDev *vdev)
+{
+ VRing *vr;
+ int rc;
+ uint8_t status;
+ int i = 0;
+
+ vdev->guessed_disk_nature = VIRTIO_GDN_NONE;
+ vdev->cmd_vr_idx = 0;
+
+ if (virtio_reset(vdev)) {
+ return -EIO;
+ }
+
+ status = VPCI_S_ACKNOWLEDGE;
+ if (virtio_pci_set_status(status)) {
+ puts("Virtio-pci device Failed to ACKNOWLEDGE");
+ return -EIO;
+ }
+
+ rc = virtio_pci_read_pci_cap_config();
+ if (rc) {
+ puts("Invalid virtio PCI capabilities");
+ return -EIO;
+ }
+
+ vdev->guest_features[1] = VIRTIO_F_VERSION_1;
+ if (virtio_pci_negotiate()) {
+ panic("Virtio feature negotation failed!");
+ }
+
+ switch (vdev->dev_type) {
+ case VIRTIO_ID_BLOCK:
+ vdev->nr_vqs = 1;
+ vdev->cmd_vr_idx = 0;
+ vdev->config.blk.blk_size = 0;
+ virtio_pci_get_blk_config();
+ break;
+ default:
+ puts("Unsupported virtio device");
+ return -ENODEV;
+ }
+
+ status |= VPCI_S_DRIVER;
+ rc = virtio_pci_set_status(status);
+ if (rc) {
+ puts("Set status failed");
+ return -EIO;
+ }
+
+ /* Configure virt-queues for pci */
+ for (i = 0; i < vdev->nr_vqs; i++) {
+ VqInfo info = {
+ .queue = (unsigned long long) virtio_get_ring_area(i),
+ .align = KVM_S390_VIRTIO_RING_ALIGN,
+ .index = i,
+ .num = 0,
+ };
+
+ vr = &vdev->vrings[i];
+
+ if (vpci_read_flex(VPCI_C_COMMON_NUMQ, c_cap.bar, &info.num, 2)) {
+ return -EIO;
+ }
+
+ vring_init(vr, &info);
+
+ if (vpci_set_selected_vq(vr->id)) {
+ puts("Failed to set selected virt-queue");
+ return -EIO;
+ }
+
+ if (vpci_set_queue_size(VIRTIO_RING_SIZE)) {
+ puts("Failed to set virt-queue size");
+ return -EIO;
+ }
+
+ rc = set_pci_vq_addr(VPCI_C_OFFSET_Q_DESCLO, vr->desc);
+ rc |= set_pci_vq_addr(VPCI_C_OFFSET_Q_AVAILLO, vr->avail);
+ rc |= set_pci_vq_addr(VPCI_C_OFFSET_Q_USEDLO, vr->used);
+ if (rc) {
+ puts("Failed to configure virt-queue address");
+ return -EIO;
+ }
+
+ if (vpci_set_queue_enable(true)) {
+ puts("Failed to set virt-queue enabled");
+ return -EIO;
+ }
+ }
+
+ status |= VPCI_S_FEATURES_OK | VPCI_S_DRIVER_OK;
+ return virtio_pci_set_status(status);
+}
+
+int virtio_pci_setup_device(void)
+{
+ VDev *vdev = virtio_get_device();
+
+ if (enable_pci_function(&vdev->pci_fh)) {
+ puts("Failed to enable PCI function");
+ return -ENODEV;
+ }
+
+ return 0;
+}
diff --git a/pc-bios/s390-ccw/virtio-pci.h b/pc-bios/s390-ccw/virtio-pci.h
index 96c17ac3c7..883b00e0c6 100644
--- a/pc-bios/s390-ccw/virtio-pci.h
+++ b/pc-bios/s390-ccw/virtio-pci.h
@@ -71,6 +71,8 @@ typedef struct VirtioPciCap VirtioPciCap;
void virtio_pci_id2type(VDev *vdev, uint16_t device_id);
int virtio_pci_reset(VDev *vdev);
long virtio_pci_notify(int vq_id);
+int virtio_pci_setup(VDev *vdev);
+int virtio_pci_setup_device(void);
int vpci_read_flex(uint64_t offset, uint8_t pcias, void *buf, int len);
int vpci_read_bswap64(uint64_t offset, uint8_t pcias, uint64_t *buf);
diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c
index 956b34ff33..f65571a920 100644
--- a/pc-bios/s390-ccw/virtio.c
+++ b/pc-bios/s390-ccw/virtio.c
@@ -17,6 +17,7 @@
#include "virtio.h"
#include "virtio-scsi.h"
#include "virtio-ccw.h"
+#include "virtio-pci.h"
#include "bswap.h"
#include "helper.h"
#include "s390-time.h"
@@ -112,6 +113,8 @@ bool vring_notify(VRing *vr)
case S390_IPL_TYPE_CCW:
vr->cookie = virtio_ccw_notify(vdev.schid, vr->id, vr->cookie);
break;
+ case S390_IPL_TYPE_PCI:
+ vr->cookie = virtio_pci_notify(vr->id);
default:
return 1;
}
@@ -119,8 +122,43 @@ bool vring_notify(VRing *vr)
return vr->cookie >= 0;
}
+/*
+ * Get endienness of the IPL type
+ * Return true for s390x native big-endian
+ */
+bool be_ipl(void)
+{
+ switch (virtio_get_device()->ipl_type) {
+ case S390_IPL_TYPE_QEMU_SCSI:
+ case S390_IPL_TYPE_CCW:
+ return true;
+ case S390_IPL_TYPE_PCI:
+ return false;
+ default:
+ return true;
+ }
+}
+
+/*
+ * Format the virtio ring descriptor endianness
+ * Return the available index increment in the appropriate endianness
+ */
+static uint16_t vr_fmt_descriptor(VRingDesc *desc)
+{
+ if (!be_ipl()) {
+ desc->addr = bswap64(desc->addr);
+ desc->len = bswap32(desc->len);
+ desc->flags = bswap16(desc->flags);
+ desc->next = bswap16(desc->next);
+ }
+
+ return be_ipl() ? 1 : bswap16(1);
+}
+
void vring_send_buf(VRing *vr, void *p, int len, int flags)
{
+ uint16_t increment;
+
/* For follow-up chains we need to keep the first entry point */
if (!(flags & VRING_HIDDEN_IS_CHAIN)) {
vr->avail->ring[vr->avail->idx % vr->num] = vr->next_idx;
@@ -131,11 +169,13 @@ void vring_send_buf(VRing *vr, void *p, int len, int flags)
vr->desc[vr->next_idx].flags = flags & ~VRING_HIDDEN_IS_CHAIN;
vr->desc[vr->next_idx].next = vr->next_idx;
vr->desc[vr->next_idx].next++;
+
+ increment = vr_fmt_descriptor(&vr->desc[vr->next_idx]);
vr->next_idx++;
/* Chains only have a single ID */
if (!(flags & VRING_DESC_F_NEXT)) {
- vr->avail->idx++;
+ vr->avail->idx += increment;
}
}
@@ -147,7 +187,7 @@ int vr_poll(VRing *vr)
return 0;
}
- vr->used_idx = vr->used->idx;
+ vr->used_idx = vr->used->idx; /* Endianness is preserved */
vr->next_idx = 0;
vr->desc[0].len = 0;
vr->desc[0].flags = 0;
@@ -187,6 +227,8 @@ int virtio_reset(VDev *vdev)
case S390_IPL_TYPE_QEMU_SCSI:
case S390_IPL_TYPE_CCW:
return virtio_ccw_reset(vdev);
+ case S390_IPL_TYPE_PCI:
+ return virtio_pci_reset(vdev);
default:
return -1;
}
diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h
index 0c34d59be2..4705c8f456 100644
--- a/pc-bios/s390-ccw/virtio.h
+++ b/pc-bios/s390-ccw/virtio.h
@@ -271,6 +271,7 @@ struct VirtioCmd {
};
typedef struct VirtioCmd VirtioCmd;
+bool be_ipl(void);
void vring_init(VRing *vr, VqInfo *info);
bool virtio_is_supported(VDev *vdev);
bool vring_notify(VRing *vr);
--
2.52.0
<...snip...>
On 1/27/2026 8:15 AM, jrossi@linux.ibm.com wrote:
> int virtio_pci_setup(VDev *vdev)
> +{
> + VRing *vr;
> + int rc;
> + uint8_t status;
> + int i = 0;
> +
> + vdev->guessed_disk_nature = VIRTIO_GDN_NONE;
> + vdev->cmd_vr_idx = 0;
> +
> + if (virtio_reset(vdev)) {
> + return -EIO;
> + }
> +
> + status = VPCI_S_ACKNOWLEDGE;
> + if (virtio_pci_set_status(status)) {
> + puts("Virtio-pci device Failed to ACKNOWLEDGE");
> + return -EIO;
> + }
> +
> + rc = virtio_pci_read_pci_cap_config();
> + if (rc) {
> + puts("Invalid virtio PCI capabilities");
> + return -EIO;
> + }
> +
> + vdev->guest_features[1] = VIRTIO_F_VERSION_1;
> + if (virtio_pci_negotiate()) {
> + panic("Virtio feature negotation failed!");
> + }
> +
> + switch (vdev->dev_type) {
> + case VIRTIO_ID_BLOCK:
> + vdev->nr_vqs = 1;
> + vdev->cmd_vr_idx = 0;
> + vdev->config.blk.blk_size = 0;
> + virtio_pci_get_blk_config();
> + break;
> + default:
> + puts("Unsupported virtio device");
> + return -ENODEV;
> + }
> +
> + status |= VPCI_S_DRIVER;
> + rc = virtio_pci_set_status(status);
> + if (rc) {
> + puts("Set status failed");
> + return -EIO;
> + }
> +
> + /* Configure virt-queues for pci */
> + for (i = 0; i < vdev->nr_vqs; i++) {
> + VqInfo info = {
> + .queue = (unsigned long long) virtio_get_ring_area(i),
> + .align = KVM_S390_VIRTIO_RING_ALIGN,
> + .index = i,
> + .num = 0,
> + };
> +
> + vr = &vdev->vrings[i];
> +
> + if (vpci_read_flex(VPCI_C_COMMON_NUMQ, c_cap.bar, &info.num, 2)) {
> + return -EIO;
> + }
> +
> + vring_init(vr, &info);
> +
> + if (vpci_set_selected_vq(vr->id)) {
> + puts("Failed to set selected virt-queue");
> + return -EIO;
> + }
> +
> + if (vpci_set_queue_size(VIRTIO_RING_SIZE)) {
> + puts("Failed to set virt-queue size");
> + return -EIO;
> + }
> +
> + rc = set_pci_vq_addr(VPCI_C_OFFSET_Q_DESCLO, vr->desc);
> + rc |= set_pci_vq_addr(VPCI_C_OFFSET_Q_AVAILLO, vr->avail);
> + rc |= set_pci_vq_addr(VPCI_C_OFFSET_Q_USEDLO, vr->used);
> + if (rc) {
> + puts("Failed to configure virt-queue address");
> + return -EIO;
> + }
> +
> + if (vpci_set_queue_enable(true)) {
> + puts("Failed to set virt-queue enabled");
> + return -EIO;
> + }
> + }
> +
> + status |= VPCI_S_FEATURES_OK | VPCI_S_DRIVER_OK;
> + return virtio_pci_set_status(status);
Should we also enable Bus master bit as part of the device setup? QEMU
though has a workaround where if we have setup the ACKNOWLEDGE and
DRIVER status flags set then it would enable the Bus master by default
since older Linux virtio-pci drivers didn't do it
(https://github.com/qemu/qemu/blob/cd5a79dc98e3087e7658e643bdbbb0baec77ac8a/hw/virtio/virtio-pci.c#L480-L487).
But latest virtio-pci drivers seems to be doing the right thing, so not
sure how long this workaround would be supported :) .
> +}
> +
<..snip..>
> diff --git a/pc-bios/s390-ccw/virtio-pci.h b/pc-bios/s390-ccw/virtio-pci.h
> index 96c17ac3c7..883b00e0c6 100644
> --- a/pc-bios/s390-ccw/virtio-pci.h
> +++ b/pc-bios/s390-ccw/virtio-pci.h
> @@ -71,6 +71,8 @@ typedef struct VirtioPciCap VirtioPciCap;
> void virtio_pci_id2type(VDev *vdev, uint16_t device_id);
> int virtio_pci_reset(VDev *vdev);
> long virtio_pci_notify(int vq_id);
> +int virtio_pci_setup(VDev *vdev);
> +int virtio_pci_setup_device(void);
>
> int vpci_read_flex(uint64_t offset, uint8_t pcias, void *buf, int len);
> int vpci_read_bswap64(uint64_t offset, uint8_t pcias, uint64_t *buf);
> diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c
> index 956b34ff33..f65571a920 100644
> --- a/pc-bios/s390-ccw/virtio.c
> +++ b/pc-bios/s390-ccw/virtio.c
> @@ -17,6 +17,7 @@
> #include "virtio.h"
> #include "virtio-scsi.h"
> #include "virtio-ccw.h"
> +#include "virtio-pci.h"
> #include "bswap.h"
> #include "helper.h"
> #include "s390-time.h"
> @@ -112,6 +113,8 @@ bool vring_notify(VRing *vr)
> case S390_IPL_TYPE_CCW:
> vr->cookie = virtio_ccw_notify(vdev.schid, vr->id, vr->cookie);
> break;
> + case S390_IPL_TYPE_PCI:
> + vr->cookie = virtio_pci_notify(vr->id);
> default:
> return 1;
> }
> @@ -119,8 +122,43 @@ bool vring_notify(VRing *vr)
> return vr->cookie >= 0;
> }
>
> +/*
> + * Get endienness of the IPL type
> + * Return true for s390x native big-endian
> + */
> +bool be_ipl(void)
> +{
> + switch (virtio_get_device()->ipl_type) {
> + case S390_IPL_TYPE_QEMU_SCSI:
> + case S390_IPL_TYPE_CCW:
> + return true;
> + case S390_IPL_TYPE_PCI:
> + return false;
> + default:
> + return true;
> + }
> +}
> +
> +/*
> + * Format the virtio ring descriptor endianness
> + * Return the available index increment in the appropriate endianness
> + */
> +static uint16_t vr_fmt_descriptor(VRingDesc *desc)
> +{
> + if (!be_ipl()) {
> + desc->addr = bswap64(desc->addr);
> + desc->len = bswap32(desc->len);
> + desc->flags = bswap16(desc->flags);
> + desc->next = bswap16(desc->next);
> + }
> +
> + return be_ipl() ? 1 : bswap16(1);
> +}
> +
> void vring_send_buf(VRing *vr, void *p, int len, int flags)
> {
> + uint16_t increment;
> +
> /* For follow-up chains we need to keep the first entry point */
> if (!(flags & VRING_HIDDEN_IS_CHAIN)) {
> vr->avail->ring[vr->avail->idx % vr->num] = vr->next_idx;
> @@ -131,11 +169,13 @@ void vring_send_buf(VRing *vr, void *p, int len, int flags)
> vr->desc[vr->next_idx].flags = flags & ~VRING_HIDDEN_IS_CHAIN;
> vr->desc[vr->next_idx].next = vr->next_idx;
> vr->desc[vr->next_idx].next++;
> +
> + increment = vr_fmt_descriptor(&vr->desc[vr->next_idx]);
If we are only returning 1 from the function vr_fmt_descriptor(), why
not just set increment=1 or remove it completely and keep
vr->avail->idx++? That way we don't need to do any endian conversion?
Thanks
Farhan
> vr->next_idx++;
>
> /* Chains only have a single ID */
> if (!(flags & VRING_DESC_F_NEXT)) {
> - vr->avail->idx++;
> + vr->avail->idx += increment;
> }
> }
On Thu, Feb 05, 2026 at 03:43:10PM -0800, Farhan Ali wrote:
> <...snip...>
>
> On 1/27/2026 8:15 AM, jrossi@linux.ibm.com wrote:
> > int virtio_pci_setup(VDev *vdev)
> > +{
> > + VRing *vr;
> > + int rc;
> > + uint8_t status;
> > + int i = 0;
> > +
> > + vdev->guessed_disk_nature = VIRTIO_GDN_NONE;
> > + vdev->cmd_vr_idx = 0;
> > +
> > + if (virtio_reset(vdev)) {
> > + return -EIO;
> > + }
> > +
> > + status = VPCI_S_ACKNOWLEDGE;
> > + if (virtio_pci_set_status(status)) {
> > + puts("Virtio-pci device Failed to ACKNOWLEDGE");
> > + return -EIO;
> > + }
> > +
> > + rc = virtio_pci_read_pci_cap_config();
> > + if (rc) {
> > + puts("Invalid virtio PCI capabilities");
> > + return -EIO;
> > + }
> > +
> > + vdev->guest_features[1] = VIRTIO_F_VERSION_1;
> > + if (virtio_pci_negotiate()) {
> > + panic("Virtio feature negotation failed!");
> > + }
> > +
> > + switch (vdev->dev_type) {
> > + case VIRTIO_ID_BLOCK:
> > + vdev->nr_vqs = 1;
> > + vdev->cmd_vr_idx = 0;
> > + vdev->config.blk.blk_size = 0;
> > + virtio_pci_get_blk_config();
> > + break;
> > + default:
> > + puts("Unsupported virtio device");
> > + return -ENODEV;
> > + }
> > +
> > + status |= VPCI_S_DRIVER;
> > + rc = virtio_pci_set_status(status);
> > + if (rc) {
> > + puts("Set status failed");
> > + return -EIO;
> > + }
> > +
> > + /* Configure virt-queues for pci */
> > + for (i = 0; i < vdev->nr_vqs; i++) {
> > + VqInfo info = {
> > + .queue = (unsigned long long) virtio_get_ring_area(i),
> > + .align = KVM_S390_VIRTIO_RING_ALIGN,
> > + .index = i,
> > + .num = 0,
> > + };
> > +
> > + vr = &vdev->vrings[i];
> > +
> > + if (vpci_read_flex(VPCI_C_COMMON_NUMQ, c_cap.bar, &info.num, 2)) {
> > + return -EIO;
> > + }
> > +
> > + vring_init(vr, &info);
> > +
> > + if (vpci_set_selected_vq(vr->id)) {
> > + puts("Failed to set selected virt-queue");
> > + return -EIO;
> > + }
> > +
> > + if (vpci_set_queue_size(VIRTIO_RING_SIZE)) {
> > + puts("Failed to set virt-queue size");
> > + return -EIO;
> > + }
> > +
> > + rc = set_pci_vq_addr(VPCI_C_OFFSET_Q_DESCLO, vr->desc);
> > + rc |= set_pci_vq_addr(VPCI_C_OFFSET_Q_AVAILLO, vr->avail);
> > + rc |= set_pci_vq_addr(VPCI_C_OFFSET_Q_USEDLO, vr->used);
> > + if (rc) {
> > + puts("Failed to configure virt-queue address");
> > + return -EIO;
> > + }
> > +
> > + if (vpci_set_queue_enable(true)) {
> > + puts("Failed to set virt-queue enabled");
> > + return -EIO;
> > + }
> > + }
> > +
> > + status |= VPCI_S_FEATURES_OK | VPCI_S_DRIVER_OK;
> > + return virtio_pci_set_status(status);
>
> Should we also enable Bus master bit as part of the device setup? QEMU
> though has a workaround where if we have setup the ACKNOWLEDGE and DRIVER
> status flags set then it would enable the Bus master by default since older
> Linux virtio-pci drivers didn't do it (https://github.com/qemu/qemu/blob/cd5a79dc98e3087e7658e643bdbbb0baec77ac8a/hw/virtio/virtio-pci.c#L480-L487).
> But latest virtio-pci drivers seems to be doing the right thing, so not sure
> how long this workaround would be supported :) .
>
Please do. linux 2.6.x is ancient and there's no guarantee we will keep
supporting this indefinitely.
> > +}
> > +
>
> <..snip..>
>
>
> > diff --git a/pc-bios/s390-ccw/virtio-pci.h b/pc-bios/s390-ccw/virtio-pci.h
> > index 96c17ac3c7..883b00e0c6 100644
> > --- a/pc-bios/s390-ccw/virtio-pci.h
> > +++ b/pc-bios/s390-ccw/virtio-pci.h
> > @@ -71,6 +71,8 @@ typedef struct VirtioPciCap VirtioPciCap;
> > void virtio_pci_id2type(VDev *vdev, uint16_t device_id);
> > int virtio_pci_reset(VDev *vdev);
> > long virtio_pci_notify(int vq_id);
> > +int virtio_pci_setup(VDev *vdev);
> > +int virtio_pci_setup_device(void);
> > int vpci_read_flex(uint64_t offset, uint8_t pcias, void *buf, int len);
> > int vpci_read_bswap64(uint64_t offset, uint8_t pcias, uint64_t *buf);
> > diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c
> > index 956b34ff33..f65571a920 100644
> > --- a/pc-bios/s390-ccw/virtio.c
> > +++ b/pc-bios/s390-ccw/virtio.c
> > @@ -17,6 +17,7 @@
> > #include "virtio.h"
> > #include "virtio-scsi.h"
> > #include "virtio-ccw.h"
> > +#include "virtio-pci.h"
> > #include "bswap.h"
> > #include "helper.h"
> > #include "s390-time.h"
> > @@ -112,6 +113,8 @@ bool vring_notify(VRing *vr)
> > case S390_IPL_TYPE_CCW:
> > vr->cookie = virtio_ccw_notify(vdev.schid, vr->id, vr->cookie);
> > break;
> > + case S390_IPL_TYPE_PCI:
> > + vr->cookie = virtio_pci_notify(vr->id);
> > default:
> > return 1;
> > }
> > @@ -119,8 +122,43 @@ bool vring_notify(VRing *vr)
> > return vr->cookie >= 0;
> > }
> > +/*
> > + * Get endienness of the IPL type
> > + * Return true for s390x native big-endian
> > + */
> > +bool be_ipl(void)
> > +{
> > + switch (virtio_get_device()->ipl_type) {
> > + case S390_IPL_TYPE_QEMU_SCSI:
> > + case S390_IPL_TYPE_CCW:
> > + return true;
> > + case S390_IPL_TYPE_PCI:
> > + return false;
> > + default:
> > + return true;
> > + }
> > +}
> > +
> > +/*
> > + * Format the virtio ring descriptor endianness
> > + * Return the available index increment in the appropriate endianness
> > + */
> > +static uint16_t vr_fmt_descriptor(VRingDesc *desc)
> > +{
> > + if (!be_ipl()) {
> > + desc->addr = bswap64(desc->addr);
> > + desc->len = bswap32(desc->len);
> > + desc->flags = bswap16(desc->flags);
> > + desc->next = bswap16(desc->next);
> > + }
> > +
> > + return be_ipl() ? 1 : bswap16(1);
> > +}
> > +
> > void vring_send_buf(VRing *vr, void *p, int len, int flags)
> > {
> > + uint16_t increment;
> > +
> > /* For follow-up chains we need to keep the first entry point */
> > if (!(flags & VRING_HIDDEN_IS_CHAIN)) {
> > vr->avail->ring[vr->avail->idx % vr->num] = vr->next_idx;
> > @@ -131,11 +169,13 @@ void vring_send_buf(VRing *vr, void *p, int len, int flags)
> > vr->desc[vr->next_idx].flags = flags & ~VRING_HIDDEN_IS_CHAIN;
> > vr->desc[vr->next_idx].next = vr->next_idx;
> > vr->desc[vr->next_idx].next++;
> > +
> > + increment = vr_fmt_descriptor(&vr->desc[vr->next_idx]);
>
> If we are only returning 1 from the function vr_fmt_descriptor(), why not
> just set increment=1 or remove it completely and keep vr->avail->idx++? That
> way we don't need to do any endian conversion?
>
> Thanks
>
> Farhan
>
>
> > vr->next_idx++;
> > /* Chains only have a single ID */
> > if (!(flags & VRING_DESC_F_NEXT)) {
> > - vr->avail->idx++;
> > + vr->avail->idx += increment;
> > }
> > }
I found the following mistake after posting...
> +int virtio_pci_setup(VDev *vdev)
> +{
> + VRing *vr;
> + int rc;
> + uint8_t status;
> + int i = 0;
> +
> + vdev->guessed_disk_nature = VIRTIO_GDN_NONE;
> + vdev->cmd_vr_idx = 0;
> +
> + if (virtio_reset(vdev)) {
> + return -EIO;
> + }
> +
> + status = VPCI_S_ACKNOWLEDGE;
> + if (virtio_pci_set_status(status)) {
> + puts("Virtio-pci device Failed to ACKNOWLEDGE");
> + return -EIO;
> + }
> +
> + rc = virtio_pci_read_pci_cap_config();
> + if (rc) {
> + puts("Invalid virtio PCI capabilities");
> + return -EIO;
> + }
Reading the capabilities configuration must be done before writing
status to the device.
I have already fixed it for the next version.
Regards,
Jared Rossi
© 2016 - 2026 Red Hat, Inc.