From: Jared Rossi <[email protected]> Add little-endian virt-queue configuration and support for virtio-blk-pci IPL devices.
Signed-off-by: Jared Rossi <[email protected]> --- 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
