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, ¬ify_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);