pc-bios/s390-ccw: Split virtio-ccw and generic virtio

Separate the CCW specific virtio routines and create generic wrappers for easier
reuse of existing virtio functions with non-CCW devices.

Reviewed-by: Thomas Huth <thuth@redhat.com>
Reviewed-by: Eric Farman <farman@linux.ibm.com>
Signed-off-by: Jared Rossi <jrossi@linux.ibm.com>
Message-ID: <20260309003601.242634-7-jrossi@linux.ibm.com>
[thuth: Use SPDX license identifier in virtio-ccw.c]
Signed-off-by: Thomas Huth <thuth@redhat.com>
This commit is contained in:
Jared Rossi
2026-03-08 20:35:52 -04:00
committed by Thomas Huth
parent ada7a60056
commit 4ff1499ada
12 changed files with 330 additions and 221 deletions

View File

@@ -34,7 +34,8 @@ QEMU_DGFLAGS = -MMD -MP -MT $@ -MF $(@D)/$(*F).d
.PHONY : all clean build-all distclean
OBJECTS = start.o main.o bootmap.o jump2ipl.o sclp.o menu.o netmain.o \
virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o
virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o \
virtio-ccw.o
SLOF_DIR := $(SRC_PATH)/../../roms/SLOF

View File

@@ -71,6 +71,7 @@ static int is_dev_possibly_bootable(int dev_no, int sch_no)
bool is_virtio;
Schib schib;
int r;
VDev *vdev = virtio_get_device();
blk_schid.sch_no = sch_no;
r = stsch_err(blk_schid, &schib);
@@ -91,7 +92,8 @@ static int is_dev_possibly_bootable(int dev_no, int sch_no)
* Note: we always have to run virtio_is_supported() here to make
* sure that the vdev.senseid data gets pre-initialized correctly
*/
is_virtio = virtio_is_supported(blk_schid);
vdev->schid = blk_schid;
is_virtio = virtio_is_supported(vdev);
/* No specific devno given, just return whether the device is possibly bootable */
if (dev_no < 0) {
@@ -256,10 +258,10 @@ static int virtio_setup(void)
puts("Network boot device detected");
return 0;
case VIRTIO_ID_BLOCK:
ret = virtio_blk_setup_device(blk_schid);
ret = virtio_blk_setup_device(vdev);
break;
case VIRTIO_ID_SCSI:
ret = virtio_scsi_setup_device(blk_schid);
ret = virtio_scsi_setup_device(vdev);
break;
default:
puts("\n! No IPL device available !\n");

View File

@@ -500,7 +500,7 @@ static bool find_net_dev(Schib *schib, int dev_no)
continue;
}
enable_subchannel(net_schid);
if (!virtio_is_supported(net_schid)) {
if (!virtio_is_supported(virtio_get_device())) {
continue;
}
if (virtio_get_device_type() != VIRTIO_ID_NET) {

View File

@@ -66,9 +66,6 @@ void sclp_setup(void);
void sclp_get_loadparm_ascii(char *loadparm);
int sclp_read(char *str, size_t count);
/* virtio.c */
bool virtio_is_supported(SubChannelId schid);
/* bootmap.c */
void zipl_load(void);

View File

@@ -12,6 +12,7 @@
#include "s390-ccw.h"
#include "virtio.h"
#include "virtio-scsi.h"
#include "virtio-ccw.h"
#define VIRTIO_BLK_F_GEOMETRY (1 << 4)
#define VIRTIO_BLK_F_BLK_SIZE (1 << 6)
@@ -229,15 +230,17 @@ uint64_t virtio_get_blocks(void)
}
}
int virtio_blk_setup_device(SubChannelId schid)
int virtio_blk_setup_device(VDev *vdev)
{
VDev *vdev = virtio_get_device();
vdev->guest_features[0] = VIRTIO_BLK_F_GEOMETRY | VIRTIO_BLK_F_BLK_SIZE;
vdev->schid = schid;
virtio_setup_ccw(vdev);
puts("Using virtio-blk.");
return 0;
switch (vdev->ipl_type) {
case S390_IPL_TYPE_QEMU_SCSI:
case S390_IPL_TYPE_CCW:
return virtio_ccw_setup(vdev);
default:
return 1;
}
}

View File

@@ -0,0 +1,239 @@
/*
* Virtio functionality for CCW devices
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Copyright (c) 2013 Alexander Graf <agraf@suse.de>
* Copyright 2025 IBM Corp.
*
* Author(s): Jared Rossi <jrossi@linux.ibm.com>
*/
#include <string.h>
#include "s390-ccw.h"
#include "cio.h"
#include "virtio.h"
#include "virtio-ccw.h"
#include "virtio-scsi.h"
#include "bswap.h"
#include "helper.h"
#include "s390-time.h"
/* virtio spec v1.0 para 4.3.3.2 */
static long kvm_hypercall(unsigned long nr, unsigned long param1,
unsigned long param2, unsigned long param3)
{
register unsigned long r_nr asm("1") = nr;
register unsigned long r_param1 asm("2") = param1;
register unsigned long r_param2 asm("3") = param2;
register unsigned long r_param3 asm("4") = param3;
register long retval asm("2");
asm volatile ("diag %%r2,%%r4,0x500"
: "=d" (retval)
: "d" (r_nr), "0" (r_param1), "r"(r_param2), "d"(r_param3)
: "memory", "cc");
return retval;
}
static int run_ccw(VDev *vdev, int cmd, void *ptr, int len, bool sli)
{
Ccw1 ccw = {};
ccw.cmd_code = cmd;
ccw.cda = (long)ptr;
ccw.count = len;
if (sli) {
ccw.flags |= CCW_FLAG_SLI;
}
return do_cio(vdev->schid, vdev->senseid.cu_type, ptr2u32(&ccw), CCW_FMT1);
}
bool virtio_ccw_is_supported(VDev *vdev)
{
memset(&vdev->senseid, 0, sizeof(vdev->senseid));
/*
* Run sense id command.
* The size of the senseid data differs between devices (notably,
* between virtio devices and dasds), so specify the largest possible
* size and suppress the incorrect length indication for smaller sizes.
*/
if (run_ccw(vdev, CCW_CMD_SENSE_ID, &vdev->senseid, sizeof(vdev->senseid),
true)) {
return false;
}
vdev->dev_type = vdev->senseid.cu_model;
if (vdev->senseid.cu_type == 0x3832) {
switch (vdev->dev_type) {
case VIRTIO_ID_BLOCK:
case VIRTIO_ID_SCSI:
case VIRTIO_ID_NET:
return true;
default:
return false;
}
}
return false;
}
int drain_irqs_ccw(SubChannelId schid)
{
Irb irb = {};
int r = 0;
while (1) {
/* FIXME: make use of TPI, for that enable subchannel and isc */
if (tsch(schid, &irb)) {
/* Might want to differentiate error codes later on. */
if (irb.scsw.cstat) {
r = -EIO;
} else if (irb.scsw.dstat != 0xc) {
r = -EIO;
}
return r;
}
}
}
long virtio_ccw_notify(SubChannelId schid, int vq_idx, long cookie)
{
return kvm_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, *(u32 *)&schid,
vq_idx, cookie);
}
int virtio_ccw_run(VDev *vdev, int vqid, VirtioCmd *cmd)
{
VRing *vr = &vdev->vrings[vqid];
int i = 0;
do {
vring_send_buf(vr, cmd[i].data, cmd[i].size,
cmd[i].flags | (i ? VRING_HIDDEN_IS_CHAIN : 0));
} while (cmd[i++].flags & VRING_DESC_F_NEXT);
vring_wait_reply();
if (drain_irqs()) {
return -1;
}
return 0;
}
int virtio_ccw_reset(VDev *vdev)
{
return run_ccw(vdev, CCW_CMD_VDEV_RESET, NULL, 0, false);
}
int virtio_ccw_setup(VDev *vdev)
{
int i, cfg_size = 0;
uint8_t status;
struct VirtioFeatureDesc {
uint32_t features;
uint8_t index;
} __attribute__((packed)) feats;
if (!virtio_ccw_is_supported(vdev)) {
puts("Virtio unsupported for this device ID");
return -ENODEV;
}
/* device ID has been established now */
vdev->config.blk.blk_size = 0; /* mark "illegal" - setup started... */
vdev->guessed_disk_nature = VIRTIO_GDN_NONE;
virtio_reset(vdev);
status = VIRTIO_CONFIG_S_ACKNOWLEDGE;
if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) {
puts("Could not write ACKNOWLEDGE status to host");
return -EIO;
}
switch (vdev->dev_type) {
case VIRTIO_ID_NET:
vdev->nr_vqs = 2;
vdev->cmd_vr_idx = 0;
cfg_size = sizeof(vdev->config.net);
break;
case VIRTIO_ID_BLOCK:
vdev->nr_vqs = 1;
vdev->cmd_vr_idx = 0;
cfg_size = sizeof(vdev->config.blk);
break;
case VIRTIO_ID_SCSI:
vdev->nr_vqs = 3;
vdev->cmd_vr_idx = VR_REQUEST;
cfg_size = sizeof(vdev->config.scsi);
break;
default:
puts("Unsupported virtio device");
return -ENODEV;
}
status |= VIRTIO_CONFIG_S_DRIVER;
if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) {
puts("Could not write DRIVER status to host");
return -EIO;
}
/* Feature negotiation */
for (i = 0; i < ARRAY_SIZE(vdev->guest_features); i++) {
feats.features = 0;
feats.index = i;
if (run_ccw(vdev, CCW_CMD_READ_FEAT, &feats, sizeof(feats), false)) {
puts("Could not get features bits");
return -EIO;
}
vdev->guest_features[i] &= bswap32(feats.features);
feats.features = bswap32(vdev->guest_features[i]);
if (run_ccw(vdev, CCW_CMD_WRITE_FEAT, &feats, sizeof(feats), false)) {
puts("Could not set features bits");
return -EIO;
}
}
if (run_ccw(vdev, CCW_CMD_READ_CONF, &vdev->config, cfg_size, false)) {
puts("Could not get virtio device configuration");
return -EIO;
}
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,
};
VqConfig config = {
.index = i,
.num = 0,
};
if (run_ccw(vdev, CCW_CMD_READ_VQ_CONF, &config, sizeof(config),
false)) {
puts("Could not get virtio device VQ config");
return -EIO;
}
info.num = config.num;
vring_init(&vdev->vrings[i], &info);
if (run_ccw(vdev, CCW_CMD_SET_VQ, &info, sizeof(info), false)) {
puts("Cannot set VQ info");
return -EIO;
}
}
status |= VIRTIO_CONFIG_S_DRIVER_OK;
if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) {
puts("Could not write DRIVER_OK status to host");
return -EIO;
}
return 0;
}

View File

@@ -0,0 +1,24 @@
/*
* Virtio definitions for CCW devices
*
* Copyright 2025 IBM Corp.
* Author(s): Jared Rossi <jrossi@linux.ibm.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef VIRTIO_CCW_H
#define VIRTIO_CCW_H
/* main.c */
extern SubChannelId blk_schid;
/* virtio-ccw.c */
int drain_irqs_ccw(SubChannelId schid);
bool virtio_ccw_is_supported(VDev *vdev);
int virtio_ccw_run(VDev *vdev, int vqid, VirtioCmd *cmd);
long virtio_ccw_notify(SubChannelId schid, int vq_idx, long cookie);
int virtio_ccw_setup(VDev *vdev);
int virtio_ccw_reset(VDev *vdev);
#endif

View File

@@ -19,6 +19,7 @@
#include <ethernet.h>
#include "s390-ccw.h"
#include "virtio.h"
#include "virtio-ccw.h"
#include "s390-time.h"
#include "helper.h"
@@ -54,7 +55,7 @@ int virtio_net_init(void *mac_addr)
rx_last_idx = 0;
vdev->guest_features[0] = VIRTIO_NET_F_MAC_BIT;
virtio_setup_ccw(vdev);
virtio_ccw_setup(vdev);
if (!(vdev->guest_features[0] & VIRTIO_NET_F_MAC_BIT)) {
puts("virtio-net device does not support the MAC address feature");

View File

@@ -15,6 +15,7 @@
#include "virtio.h"
#include "scsi.h"
#include "virtio-scsi.h"
#include "virtio-ccw.h"
#include "s390-time.h"
#include "helper.h"
@@ -476,12 +477,9 @@ static int virtio_scsi_setup(VDev *vdev)
return 0;
}
int virtio_scsi_setup_device(SubChannelId schid)
int virtio_scsi_setup_device(VDev *vdev)
{
VDev *vdev = virtio_get_device();
vdev->schid = schid;
virtio_setup_ccw(vdev);
virtio_ccw_setup(vdev);
if (vdev->config.scsi.sense_size != VIRTIO_SCSI_SENSE_SIZE) {
puts("Config: sense size mismatch");

View File

@@ -69,6 +69,6 @@ static inline bool virtio_scsi_response_ok(const VirtioScsiCmdResp *r)
int virtio_scsi_read_many(VDev *vdev,
unsigned long sector, void *load_addr, int sec_num);
int virtio_scsi_setup_device(SubChannelId schid);
int virtio_scsi_setup_device(VDev *vdev);
#endif /* VIRTIO_SCSI_H */

View File

@@ -2,6 +2,9 @@
* Virtio driver bits
*
* Copyright (c) 2013 Alexander Graf <agraf@suse.de>
* Copyright 2025 IBM Corp.
*
* Author(s): Jared Rossi <jrossi@linux.ibm.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or (at
* your option) any later version. See the COPYING file in the top-level
@@ -13,6 +16,7 @@
#include "cio.h"
#include "virtio.h"
#include "virtio-scsi.h"
#include "virtio-ccw.h"
#include "bswap.h"
#include "helper.h"
#include "s390-time.h"
@@ -44,28 +48,9 @@ VirtioDevType virtio_get_device_type(void)
return vdev.dev_type;
}
/* virtio spec v1.0 para 4.3.3.2 */
static long kvm_hypercall(unsigned long nr, unsigned long param1,
unsigned long param2, unsigned long param3)
char *virtio_get_ring_area(int ring_num)
{
register unsigned long r_nr asm("1") = nr;
register unsigned long r_param1 asm("2") = param1;
register unsigned long r_param2 asm("3") = param2;
register unsigned long r_param3 asm("4") = param3;
register long retval asm("2");
asm volatile ("diag %%r2,%%r4,0x500"
: "=d" (retval)
: "d" (r_nr), "0" (r_param1), "r"(r_param2), "d"(r_param3)
: "memory", "cc");
return retval;
}
static long virtio_notify(SubChannelId schid, int vq_idx, long cookie)
{
return kvm_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, *(u32 *)&schid,
vq_idx, cookie);
return ring_area + ring_num * VIRTIO_RING_SIZE;
}
/***********************************************
@@ -74,39 +59,27 @@ static long virtio_notify(SubChannelId schid, int vq_idx, long cookie)
int drain_irqs(void)
{
Irb irb = {};
int r = 0;
while (1) {
/* FIXME: make use of TPI, for that enable subchannel and isc */
if (tsch(vdev.schid, &irb)) {
/* Might want to differentiate error codes later on. */
if (irb.scsw.cstat) {
r = -EIO;
} else if (irb.scsw.dstat != 0xc) {
r = -EIO;
}
return r;
}
switch (vdev.ipl_type) {
case S390_IPL_TYPE_QEMU_SCSI:
case S390_IPL_TYPE_CCW:
return drain_irqs_ccw(vdev.schid);
default:
return 0;
}
}
static int run_ccw(VDev *vdev, int cmd, void *ptr, int len, bool sli)
int virtio_run(VDev *vdev, int vqid, VirtioCmd *cmd)
{
Ccw1 ccw = {};
ccw.cmd_code = cmd;
ccw.cda = (long)ptr;
ccw.count = len;
if (sli) {
ccw.flags |= CCW_FLAG_SLI;
switch (vdev->ipl_type) {
case S390_IPL_TYPE_QEMU_SCSI:
case S390_IPL_TYPE_CCW:
return virtio_ccw_run(vdev, vqid, cmd);
default:
return -1;
}
return do_cio(vdev->schid, vdev->senseid.cu_type, ptr2u32(&ccw), CCW_FMT1);
}
static void vring_init(VRing *vr, VqInfo *info)
void vring_init(VRing *vr, VqInfo *info)
{
void *p = (void *) info->queue;
@@ -134,7 +107,15 @@ static void vring_init(VRing *vr, VqInfo *info)
bool vring_notify(VRing *vr)
{
vr->cookie = virtio_notify(vdev.schid, vr->id, vr->cookie);
switch (vdev.ipl_type) {
case S390_IPL_TYPE_QEMU_SCSI:
case S390_IPL_TYPE_CCW:
vr->cookie = virtio_ccw_notify(vdev.schid, vr->id, vr->cookie);
break;
default:
return 1;
}
return vr->cookie >= 0;
}
@@ -200,164 +181,24 @@ int vring_wait_reply(void)
return 1;
}
int virtio_run(VDev *vdev, int vqid, VirtioCmd *cmd)
{
VRing *vr = &vdev->vrings[vqid];
int i = 0;
do {
vring_send_buf(vr, cmd[i].data, cmd[i].size,
cmd[i].flags | (i ? VRING_HIDDEN_IS_CHAIN : 0));
} while (cmd[i++].flags & VRING_DESC_F_NEXT);
vring_wait_reply();
if (drain_irqs()) {
return -1;
}
return 0;
}
int virtio_reset(VDev *vdev)
{
return run_ccw(vdev, CCW_CMD_VDEV_RESET, NULL, 0, false);
}
int virtio_setup_ccw(VDev *vdev)
{
int i, cfg_size = 0;
uint8_t status;
struct VirtioFeatureDesc {
uint32_t features;
uint8_t index;
} __attribute__((packed)) feats;
if (!virtio_is_supported(vdev->schid)) {
puts("Virtio unsupported for this device ID");
return -ENODEV;
}
/* device ID has been established now */
vdev->config.blk.blk_size = 0; /* mark "illegal" - setup started... */
vdev->guessed_disk_nature = VIRTIO_GDN_NONE;
virtio_reset(vdev);
status = VIRTIO_CONFIG_S_ACKNOWLEDGE;
if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) {
puts("Could not write ACKNOWLEDGE status to host");
return -EIO;
}
switch (vdev->dev_type) {
case VIRTIO_ID_NET:
vdev->nr_vqs = 2;
vdev->cmd_vr_idx = 0;
cfg_size = sizeof(vdev->config.net);
break;
case VIRTIO_ID_BLOCK:
vdev->nr_vqs = 1;
vdev->cmd_vr_idx = 0;
cfg_size = sizeof(vdev->config.blk);
break;
case VIRTIO_ID_SCSI:
vdev->nr_vqs = 3;
vdev->cmd_vr_idx = VR_REQUEST;
cfg_size = sizeof(vdev->config.scsi);
break;
switch (vdev->ipl_type) {
case S390_IPL_TYPE_QEMU_SCSI:
case S390_IPL_TYPE_CCW:
return virtio_ccw_reset(vdev);
default:
puts("Unsupported virtio device");
return -ENODEV;
return -1;
}
status |= VIRTIO_CONFIG_S_DRIVER;
if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) {
puts("Could not write DRIVER status to host");
return -EIO;
}
/* Feature negotiation */
for (i = 0; i < ARRAY_SIZE(vdev->guest_features); i++) {
feats.features = 0;
feats.index = i;
if (run_ccw(vdev, CCW_CMD_READ_FEAT, &feats, sizeof(feats), false)) {
puts("Could not get features bits");
return -EIO;
}
vdev->guest_features[i] &= bswap32(feats.features);
feats.features = bswap32(vdev->guest_features[i]);
if (run_ccw(vdev, CCW_CMD_WRITE_FEAT, &feats, sizeof(feats), false)) {
puts("Could not set features bits");
return -EIO;
}
}
if (run_ccw(vdev, CCW_CMD_READ_CONF, &vdev->config, cfg_size, false)) {
puts("Could not get virtio device configuration");
return -EIO;
}
for (i = 0; i < vdev->nr_vqs; i++) {
VqInfo info = {
.queue = (unsigned long long) ring_area + (i * VIRTIO_RING_SIZE),
.align = KVM_S390_VIRTIO_RING_ALIGN,
.index = i,
.num = 0,
};
VqConfig config = {
.index = i,
.num = 0,
};
if (run_ccw(vdev, CCW_CMD_READ_VQ_CONF, &config, sizeof(config),
false)) {
puts("Could not get virtio device VQ config");
return -EIO;
}
info.num = config.num;
vring_init(&vdev->vrings[i], &info);
if (run_ccw(vdev, CCW_CMD_SET_VQ, &info, sizeof(info), false)) {
puts("Cannot set VQ info");
return -EIO;
}
}
status |= VIRTIO_CONFIG_S_DRIVER_OK;
if (run_ccw(vdev, CCW_CMD_WRITE_STATUS, &status, sizeof(status), false)) {
puts("Could not write DRIVER_OK status to host");
return -EIO;
}
return 0;
}
bool virtio_is_supported(SubChannelId schid)
bool virtio_is_supported(VDev *vdev)
{
vdev.schid = schid;
memset(&vdev.senseid, 0, sizeof(vdev.senseid));
/*
* Run sense id command.
* The size of the senseid data differs between devices (notably,
* between virtio devices and dasds), so specify the largest possible
* size and suppress the incorrect length indication for smaller sizes.
*/
if (run_ccw(&vdev, CCW_CMD_SENSE_ID, &vdev.senseid, sizeof(vdev.senseid),
true)) {
switch (vdev->ipl_type) {
case S390_IPL_TYPE_QEMU_SCSI:
case S390_IPL_TYPE_CCW:
return virtio_ccw_is_supported(vdev);
default:
return false;
}
vdev.dev_type = vdev.senseid.cu_model;
if (vdev.senseid.cu_type == 0x3832) {
switch (vdev.dev_type) {
case VIRTIO_ID_BLOCK:
case VIRTIO_ID_SCSI:
case VIRTIO_ID_NET:
return true;
default:
return false;
}
}
return false;
}

View File

@@ -108,6 +108,7 @@ struct VRing {
};
typedef struct VRing VRing;
char *virtio_get_ring_area(int ring_num);
/***********************************************
* Virtio block *
@@ -269,6 +270,8 @@ struct VirtioCmd {
};
typedef struct VirtioCmd VirtioCmd;
void vring_init(VRing *vr, VqInfo *info);
bool virtio_is_supported(VDev *vdev);
bool vring_notify(VRing *vr);
int drain_irqs(void);
void vring_send_buf(VRing *vr, void *p, int len, int flags);
@@ -283,7 +286,7 @@ int virtio_net_init(void *mac_addr);
void virtio_net_deinit(void);
/* virtio-blkdev.c */
int virtio_blk_setup_device(SubChannelId schid);
int virtio_blk_setup_device(VDev *vdev);
int virtio_read(unsigned long sector, void *load_addr);
unsigned long virtio_load_direct(unsigned long rec_list1, unsigned long rec_list2,
void *load_addr);