@samitouri / QOSamiQemu / commits / d72fb5e6b2

pc-bios/s390-ccw: Add support for virtio-blk-pci IPL

Add little-endian virt-queue configuration and support for virtio-blk-pci IPL devices. Reviewed-by: Eric Farman <farman@linux.ibm.com> Signed-off-by: Jared Rossi <jrossi@linux.ibm.com> Message-ID: <20260309003601.242634-13-jrossi@linux.ibm.com> [thuth: Fix coding style error & spelling warning reported by checkpatch.pl] Signed-off-by: Thomas Huth <thuth@redhat.com>

Jared Rossi committed Mar 8, 2026 at 20:35 UTC d72fb5e6b2e32b5214c0b6fff6f7b1d44e9e9792
7 files changed +400 -5
pc-bios/s390-ccw/main.c
+59 -2
@@ -18,6 +18,8 @@
18 #include "virtio.h"
19 #include "virtio-scsi.h"
20 #include "dasd-ipl.h"
21 +#include "clp.h"
22 +#include "virtio-pci.h"
23
24 static SubChannelId blk_schid = { .one = 1 };
25 static char loadparm_str[LOADPARM_LEN + 1];
@@ -151,6 +153,21 @@ static bool find_subch(int dev_no)
153 return false;
154 }
155
156 +static bool find_fid(uint32_t fid)
157 +{
158 + ClpFhListEntry entry;
159 + VDev *vdev = virtio_get_device();
160 +
161 + if (find_pci_function(fid, &entry)) {
162 + return false;
163 + }
164 +
165 + vdev->pci_fh = entry.fh;
166 + virtio_pci_id2type(vdev, entry.device_id);
167 +
168 + return vdev->dev_type != 0;
169 +}
170 +
171 static void menu_setup(VDev *vdev)
172 {
173 if (memcmp(loadparm_str, LOADPARM_PROMPT, LOADPARM_LEN) == 0) {
@@ -240,6 +257,9 @@ static bool find_boot_device(void)
257 blk_schid.ssid = iplb.scsi.ssid & 0x3;
258 found = find_subch(iplb.scsi.devno);
259 break;
260 + case S390_IPL_TYPE_PCI:
261 + found = find_fid(iplb.pci.fid);
262 + break;
263 default:
264 puts("Unsupported IPLB");
265 }
@@ -276,7 +296,7 @@ static int virtio_setup(void)
296 return ret;
297 }
298
279 -static void ipl_boot_device(void)
299 +static void ipl_ccw_device(void)
300 {
301 switch (cutype) {
302 case CU_TYPE_DASD_3990:
@@ -290,7 +310,44 @@ static void ipl_boot_device(void)
310 }
311 break;
312 default:
293 - printf("Attempting to boot from unexpected device type 0x%X\n", cutype);
313 + printf("Cannot boot CCW device with cu type 0x%X\n", cutype);
314 + }
315 +}
316 +
317 +static void ipl_pci_device(void)
318 +{
319 + VDev *vdev = virtio_get_device();
320 + vdev->is_cdrom = false;
321 + vdev->scsi_device_selected = false;
322 +
323 + if (virtio_pci_setup_device()) {
324 + return;
325 + }
326 +
327 + switch (vdev->dev_type) {
328 + case VIRTIO_ID_BLOCK:
329 + if (virtio_setup() == 0) {
330 + zipl_load(); /* only return on error */
331 + virtio_reset(virtio_get_device());
332 + }
333 + break;
334 + default:
335 + printf("Cannot boot PCI device type 0x%X\n", vdev->dev_type);
336 + }
337 +}
338 +
339 +static void ipl_boot_device(void)
340 +{
341 + switch (virtio_get_device()->ipl_type) {
342 + case S390_IPL_TYPE_QEMU_SCSI:
343 + case S390_IPL_TYPE_CCW:
344 + ipl_ccw_device();
345 + break;
346 + case S390_IPL_TYPE_PCI:
347 + ipl_pci_device();
348 + break;
349 + default:
350 + puts("Unrecognized IPL type!");
351 }
352 }
353
pc-bios/s390-ccw/pci.h
+3
@@ -26,8 +26,11 @@ union register_pair {
26 };
27
28 #define PCI_CFGBAR 0xF /* Base Address Register for config space */
29 +#define PCI_CMD_REG 0x4 /* Offset of command register */
30 #define PCI_CAPABILITY_LIST 0x34 /* Offset of first capability list entry */
31
32 +#define PCI_BUS_MASTER_MASK 0x0020 /* LE bit 3 of 16 bit register */
33 +
34 int pci_write(uint32_t fhandle, uint64_t offset, uint8_t pcias, uint64_t data,
35 uint8_t len);
36 int pci_read(uint32_t fhandle, uint64_t offset, uint8_t pcias, void *buf,
pc-bios/s390-ccw/virtio-blkdev.c
+18
@@ -13,10 +13,22 @@
13 #include "virtio.h"
14 #include "virtio-scsi.h"
15 #include "virtio-ccw.h"
16 +#include "virtio-pci.h"
17 +#include "bswap.h"
18
19 #define VIRTIO_BLK_F_GEOMETRY (1 << 4)
20 #define VIRTIO_BLK_F_BLK_SIZE (1 << 6)
21
22 +/*
23 + * Format header for little endian IPL
24 + */
25 +static void fmt_blk_hdr_le(VirtioBlkOuthdr *hdr)
26 +{
27 + hdr->type = bswap32(hdr->type);
28 + hdr->ioprio = bswap32(hdr->ioprio);
29 + hdr->sector = bswap64(hdr->sector);
30 +}
31 +
32 static int virtio_blk_read_many(VDev *vdev, unsigned long sector, void *load_addr,
33 int sec_num)
34 {
@@ -29,6 +41,10 @@ static int virtio_blk_read_many(VDev *vdev, unsigned long sector, void *load_add
41 out_hdr.ioprio = 99;
42 out_hdr.sector = virtio_sector_adjust(sector);
43
44 + if (!be_ipl()) {
45 + fmt_blk_hdr_le(&out_hdr);
46 + }
47 +
48 vring_send_buf(vr, &out_hdr, sizeof(out_hdr), VRING_DESC_F_NEXT);
49
50 /* This is where we want to receive data */
@@ -240,6 +256,8 @@ int virtio_blk_setup_device(VDev *vdev)
256 case S390_IPL_TYPE_QEMU_SCSI:
257 case S390_IPL_TYPE_CCW:
258 return virtio_ccw_setup(vdev);
259 + case S390_IPL_TYPE_PCI:
260 + return virtio_pci_setup(vdev);
261 default:
262 return 1;
263 }
pc-bios/s390-ccw/virtio-pci.c
+266
@@ -165,3 +165,269 @@ int vpci_read_flex(uint64_t offset, uint8_t pcias, void *buf, int len)
165
166 return 0;
167 }
168 +
169 +static int vpci_set_selected_vq(uint16_t queue_num)
170 +{
171 + return vpci_bswap16_write(c_cap.off + VPCI_C_OFFSET_Q_SELECT, c_cap.bar, queue_num);
172 +}
173 +
174 +static int vpci_set_queue_enable(uint16_t enabled)
175 +{
176 + return vpci_bswap16_write(c_cap.off + VPCI_C_OFFSET_Q_ENABLE, c_cap.bar, enabled);
177 +}
178 +
179 +static int set_pci_vq_addr(uint64_t config_off, void *addr)
180 +{
181 + return vpci_bswap64_write(c_cap.off + config_off, c_cap.bar, (uint64_t) addr);
182 +}
183 +
184 +static int virtio_pci_get_blk_config(void)
185 +{
186 + VirtioBlkConfig *cfg = &virtio_get_device()->config.blk;
187 + int rc = vpci_read_flex(d_cap.off, d_cap.bar, cfg, sizeof(VirtioBlkConfig));
188 +
189 + /* single byte fields are not touched */
190 + cfg->capacity = bswap64(cfg->capacity);
191 + cfg->size_max = bswap32(cfg->size_max);
192 + cfg->seg_max = bswap32(cfg->seg_max);
193 +
194 + cfg->geometry.cylinders = bswap16(cfg->geometry.cylinders);
195 +
196 + cfg->blk_size = bswap32(cfg->blk_size);
197 + cfg->min_io_size = bswap16(cfg->min_io_size);
198 + cfg->opt_io_size = bswap32(cfg->opt_io_size);
199 +
200 + return rc;
201 +}
202 +
203 +static int virtio_pci_negotiate(void)
204 +{
205 + int i, rc;
206 + VDev *vdev = virtio_get_device();
207 + struct VirtioFeatureDesc {
208 + uint32_t features;
209 + uint8_t index;
210 + } __attribute__((packed)) feats;
211 +
212 + for (i = 0; i < ARRAY_SIZE(vdev->guest_features); i++) {
213 + feats.features = 0;
214 + feats.index = i;
215 +
216 + rc = vpci_bswap32_write(c_cap.off + VPCI_C_OFFSET_DFSELECT, c_cap.bar,
217 + feats.index);
218 + rc |= vpci_read_flex(c_cap.off + VPCI_C_OFFSET_DF, c_cap.bar, &feats, 4);
219 +
220 + vdev->guest_features[i] &= bswap32(feats.features);
221 + feats.features = vdev->guest_features[i];
222 +
223 +
224 + rc |= vpci_bswap32_write(c_cap.off + VPCI_C_OFFSET_GFSELECT, c_cap.bar,
225 + feats.index);
226 + rc |= vpci_bswap32_write(c_cap.off + VPCI_C_OFFSET_GF, c_cap.bar,
227 + feats.features);
228 + }
229 +
230 + return rc;
231 +}
232 +
233 +/*
234 + * Find the position of the capability config within PCI configuration
235 + * space for a given cfg type. Return the position if found, otherwise 0.
236 + */
237 +static uint8_t virtio_pci_find_cap_pos(uint8_t cfg_type)
238 +{
239 + uint8_t next, cfg;
240 + int rc;
241 +
242 + rc = vpci_read_byte(PCI_CAPABILITY_LIST, PCI_CFGBAR, &next);
243 + rc |= vpci_read_byte(next + 3, PCI_CFGBAR, &cfg);
244 +
245 + while (!rc && (cfg != cfg_type) && next) {
246 + rc = vpci_read_byte(next + 1, PCI_CFGBAR, &next);
247 + rc |= vpci_read_byte(next + 3, PCI_CFGBAR, &cfg);
248 + }
249 +
250 + return rc ? 0 : next;
251 +}
252 +
253 +/*
254 + * Read PCI configuration space to find the offset of the Common, Device, and
255 + * Notification memory regions within the modern memory space.
256 + * Returns 0 if success, 1 if a capability could not be located, or a
257 + * negative RC if the configuration read failed.
258 + */
259 +static int virtio_pci_read_pci_cap_config(void)
260 +{
261 + uint8_t pos;
262 + int rc;
263 +
264 + /* Common capabilities */
265 + pos = virtio_pci_find_cap_pos(VPCI_CAP_COMMON_CFG);
266 + if (!pos) {
267 + puts("Failed to locate PCI common configuration");
268 + return 1;
269 + }
270 +
271 + rc = vpci_read_byte(pos + VPCI_CAP_BAR, PCI_CFGBAR, &c_cap.bar);
272 + if (rc || vpci_read_bswap32(pos + VPCI_CAP_OFFSET, PCI_CFGBAR, &c_cap.off)) {
273 + puts("Failed to read PCI common configuration");
274 + return -EIO;
275 + }
276 +
277 + /* Device capabilities */
278 + pos = virtio_pci_find_cap_pos(VPCI_CAP_DEVICE_CFG);
279 + if (!pos) {
280 + puts("Failed to locate PCI device configuration");
281 + return 1;
282 + }
283 +
284 + rc = vpci_read_byte(pos + VPCI_CAP_BAR, PCI_CFGBAR, &d_cap.bar);
285 + if (rc || vpci_read_bswap32(pos + VPCI_CAP_OFFSET, PCI_CFGBAR, &d_cap.off)) {
286 + puts("Failed to read PCI device configuration");
287 + return -EIO;
288 + }
289 +
290 + /* Notification capabilities */
291 + pos = virtio_pci_find_cap_pos(VPCI_CAP_NOTIFY_CFG);
292 + if (!pos) {
293 + puts("Failed to locate PCI notification configuration");
294 + return 1;
295 + }
296 +
297 + rc = vpci_read_byte(pos + VPCI_CAP_BAR, PCI_CFGBAR, &n_cap.bar);
298 + if (rc || vpci_read_bswap32(pos + VPCI_CAP_OFFSET, PCI_CFGBAR, &n_cap.off)) {
299 + puts("Failed to read PCI notification configuration");
300 + return -EIO;
301 + }
302 +
303 + rc = vpci_read_bswap32(pos + VPCI_N_CAP_MULT, PCI_CFGBAR, &notify_mult);
304 + if (rc || vpci_read_bswap16(c_cap.off + VPCI_C_OFFSET_Q_NOFF, c_cap.bar,
305 + &q_notify_offset)) {
306 + puts("Failed to read notification queue configuration");
307 + return -EIO;
308 + }
309 +
310 + return 0;
311 +}
312 +
313 +static int enable_pci_bus_master(void)
314 +{
315 + uint16_t cmd_reg;
316 +
317 + if (vpci_read_bswap16(PCI_CMD_REG, PCI_CFGBAR, &cmd_reg)) {
318 + puts("Failed to read PCI command register");
319 + return -EIO;
320 + }
321 +
322 + if (vpci_bswap16_write(PCI_CMD_REG, PCI_CFGBAR, cmd_reg | PCI_BUS_MASTER_MASK)) {
323 + puts("Failed to enable PCI bus mastering");
324 + return -EIO;
325 + }
326 +
327 + return 0;
328 +}
329 +
330 +int virtio_pci_setup(VDev *vdev)
331 +{
332 + VRing *vr;
333 + int rc;
334 + uint8_t status;
335 + uint16_t vq_size;
336 + int i = 0;
337 +
338 + vdev->guessed_disk_nature = VIRTIO_GDN_NONE;
339 + vdev->cmd_vr_idx = 0;
340 +
341 + if (virtio_pci_read_pci_cap_config()) {
342 + puts("Invalid virtio PCI capabilities");
343 + return -EIO;
344 + }
345 +
346 + if (enable_pci_bus_master()) {
347 + return -EIO;
348 + }
349 +
350 + if (virtio_reset(vdev)) {
351 + return -EIO;
352 + }
353 +
354 + status = VIRTIO_CONFIG_S_ACKNOWLEDGE;
355 + if (virtio_pci_set_status(status)) {
356 + puts("Virtio-pci device Failed to ACKNOWLEDGE");
357 + return -EIO;
358 + }
359 +
360 + vdev->guest_features[1] = VIRTIO_F_VERSION_1;
361 + if (virtio_pci_negotiate()) {
362 + panic("Virtio feature negotiation failed!");
363 + }
364 +
365 + switch (vdev->dev_type) {
366 + case VIRTIO_ID_BLOCK:
367 + vdev->nr_vqs = 1;
368 + vdev->cmd_vr_idx = 0;
369 + virtio_pci_get_blk_config();
370 + break;
371 + default:
372 + puts("Unsupported virtio device");
373 + return -ENODEV;
374 + }
375 +
376 + status |= VIRTIO_CONFIG_S_DRIVER;
377 + rc = virtio_pci_set_status(status);
378 + if (rc) {
379 + puts("Set status failed");
380 + return -EIO;
381 + }
382 +
383 + if (vpci_read_bswap16(VPCI_C_OFFSET_Q_SIZE, c_cap.bar, &vq_size)) {
384 + puts("Failed to read virt-queue configuration");
385 + return -EIO;
386 + }
387 +
388 + /* Configure virt-queues for pci */
389 + for (i = 0; i < vdev->nr_vqs; i++) {
390 + VqInfo info = {
391 + .queue = (unsigned long long) virtio_get_ring_area(i),
392 + .align = KVM_S390_VIRTIO_RING_ALIGN,
393 + .index = i,
394 + .num = vq_size,
395 + };
396 +
397 + vr = &vdev->vrings[i];
398 + vring_init(vr, &info);
399 +
400 + if (vpci_set_selected_vq(vr->id)) {
401 + puts("Failed to set selected virt-queue");
402 + return -EIO;
403 + }
404 +
405 + rc = set_pci_vq_addr(VPCI_C_OFFSET_Q_DESCLO, vr->desc);
406 + rc |= set_pci_vq_addr(VPCI_C_OFFSET_Q_AVAILLO, vr->avail);
407 + rc |= set_pci_vq_addr(VPCI_C_OFFSET_Q_USEDLO, vr->used);
408 + if (rc) {
409 + puts("Failed to configure virt-queue address");
410 + return -EIO;
411 + }
412 +
413 + if (vpci_set_queue_enable(true)) {
414 + puts("Failed to set virt-queue enabled");
415 + return -EIO;
416 + }
417 + }
418 +
419 + status |= VIRTIO_CONFIG_S_FEATURES_OK | VIRTIO_CONFIG_S_DRIVER_OK;
420 + return virtio_pci_set_status(status);
421 +}
422 +
423 +int virtio_pci_setup_device(void)
424 +{
425 + VDev *vdev = virtio_get_device();
426 +
427 + if (enable_pci_function(&vdev->pci_fh)) {
428 + puts("Failed to enable PCI function");
429 + return -ENODEV;
430 + }
431 +
432 + return 0;
433 +}
pc-bios/s390-ccw/virtio-pci.h
+2
@@ -65,6 +65,8 @@ typedef struct VirtioPciCap VirtioPciCap;
65 void virtio_pci_id2type(VDev *vdev, uint16_t device_id);
66 int virtio_pci_reset(VDev *vdev);
67 long virtio_pci_notify(int vq_id);
68 +int virtio_pci_setup(VDev *vdev);
69 +int virtio_pci_setup_device(void);
70
71 int vpci_read_flex(uint64_t offset, uint8_t pcias, void *buf, int len);
72 int vpci_read_bswap64(uint64_t offset, uint8_t pcias, uint64_t *buf);
pc-bios/s390-ccw/virtio.c
+51 -3
@@ -17,6 +17,7 @@
17 #include "virtio.h"
18 #include "virtio-scsi.h"
19 #include "virtio-ccw.h"
20 +#include "virtio-pci.h"
21 #include "bswap.h"
22 #include "helper.h"
23 #include "s390-time.h"
@@ -96,7 +97,7 @@ void vring_init(VRing *vr, VqInfo *info)
97 vr->avail->idx = 0;
98
99 /* We're running with interrupts off anyways, so don't bother */
99 - vr->used->flags = VRING_USED_F_NO_NOTIFY;
100 + vr->used->flags = be_ipl() ? VRING_USED_F_NO_NOTIFY : bswap16(VRING_USED_F_NO_NOTIFY);
101 vr->used->idx = 0;
102 vr->used_idx = 0;
103 vr->next_idx = 0;
@@ -112,6 +113,8 @@ bool vring_notify(VRing *vr)
113 case S390_IPL_TYPE_CCW:
114 vr->cookie = virtio_ccw_notify(vdev.schid, vr->id, vr->cookie);
115 break;
116 + case S390_IPL_TYPE_PCI:
117 + vr->cookie = virtio_pci_notify(vr->id);
118 default:
119 return 1;
120 }
@@ -119,11 +122,45 @@ bool vring_notify(VRing *vr)
122 return vr->cookie >= 0;
123 }
124
125 +/*
126 + * Get endienness of the IPL type
127 + * Return true for s390x native big-endian
128 + */
129 +bool be_ipl(void)
130 +{
131 + switch (virtio_get_device()->ipl_type) {
132 + case S390_IPL_TYPE_QEMU_SCSI:
133 + case S390_IPL_TYPE_CCW:
134 + return true;
135 + case S390_IPL_TYPE_PCI:
136 + return false;
137 + default:
138 + return true;
139 + }
140 +}
141 +
142 +/*
143 + * Format the virtio ring descriptor endianness
144 + * Return the available index increment in the appropriate endianness
145 + */
146 +static void vr_bswap_descriptor(VRingDesc *desc)
147 +{
148 + desc->addr = bswap64(desc->addr);
149 + desc->len = bswap32(desc->len);
150 + desc->flags = bswap16(desc->flags);
151 + desc->next = bswap16(desc->next);
152 +}
153 +
154 void vring_send_buf(VRing *vr, void *p, int len, int flags)
155 {
156 + if (!be_ipl()) {
157 + vr->avail->idx = bswap16(vr->avail->idx);
158 + }
159 +
160 /* For follow-up chains we need to keep the first entry point */
161 if (!(flags & VRING_HIDDEN_IS_CHAIN)) {
126 - vr->avail->ring[vr->avail->idx % vr->num] = vr->next_idx;
162 + vr->avail->ring[vr->avail->idx % vr->num] = be_ipl() ? vr->next_idx :
163 + bswap16(vr->next_idx);
164 }
165
166 vr->desc[vr->next_idx].addr = (unsigned long)p;
@@ -131,12 +168,21 @@ void vring_send_buf(VRing *vr, void *p, int len, int flags)
168 vr->desc[vr->next_idx].flags = flags & ~VRING_HIDDEN_IS_CHAIN;
169 vr->desc[vr->next_idx].next = vr->next_idx;
170 vr->desc[vr->next_idx].next++;
171 +
172 + if (!be_ipl()) {
173 + vr_bswap_descriptor(&vr->desc[vr->next_idx]);
174 + }
175 +
176 vr->next_idx++;
177
178 /* Chains only have a single ID */
179 if (!(flags & VRING_DESC_F_NEXT)) {
180 vr->avail->idx++;
181 }
182 +
183 + if (!be_ipl()) {
184 + vr->avail->idx = bswap16(vr->avail->idx);
185 + }
186 }
187
188 int vr_poll(VRing *vr)
@@ -147,7 +193,7 @@ int vr_poll(VRing *vr)
193 return 0;
194 }
195
150 - vr->used_idx = vr->used->idx;
196 + vr->used_idx = vr->used->idx; /* Endianness is preserved */
197 vr->next_idx = 0;
198 vr->desc[0].len = 0;
199 vr->desc[0].flags = 0;
@@ -187,6 +233,8 @@ int virtio_reset(VDev *vdev)
233 case S390_IPL_TYPE_QEMU_SCSI:
234 case S390_IPL_TYPE_CCW:
235 return virtio_ccw_reset(vdev);
236 + case S390_IPL_TYPE_PCI:
237 + return virtio_pci_reset(vdev);
238 default:
239 return -1;
240 }
pc-bios/s390-ccw/virtio.h
+1
@@ -273,6 +273,7 @@ struct VirtioCmd {
273 };
274 typedef struct VirtioCmd VirtioCmd;
275
276 +bool be_ipl(void);
277 void vring_init(VRing *vr, VqInfo *info);
278 bool virtio_is_supported(VDev *vdev);
279 bool vring_notify(VRing *vr);