diff options
Diffstat (limited to 'pc-bios/s390-ccw/virtio-blkdev.c')
-rw-r--r-- | pc-bios/s390-ccw/virtio-blkdev.c | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/pc-bios/s390-ccw/virtio-blkdev.c b/pc-bios/s390-ccw/virtio-blkdev.c new file mode 100644 index 0000000000..11c56261ca --- /dev/null +++ b/pc-bios/s390-ccw/virtio-blkdev.c @@ -0,0 +1,296 @@ +/* + * Virtio driver bits + * + * Copyright (c) 2013 Alexander Graf <agraf@suse.de> + * + * 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 + * directory. + */ + +#include "libc.h" +#include "s390-ccw.h" +#include "virtio.h" +#include "virtio-scsi.h" + +static int virtio_blk_read_many(VDev *vdev, ulong sector, void *load_addr, + int sec_num) +{ + VirtioBlkOuthdr out_hdr; + u8 status; + VRing *vr = &vdev->vrings[vdev->cmd_vr_idx]; + + /* Tell the host we want to read */ + out_hdr.type = VIRTIO_BLK_T_IN; + out_hdr.ioprio = 99; + out_hdr.sector = virtio_sector_adjust(sector); + + vring_send_buf(vr, &out_hdr, sizeof(out_hdr), VRING_DESC_F_NEXT); + + /* This is where we want to receive data */ + vring_send_buf(vr, load_addr, virtio_get_block_size() * sec_num, + VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN | + VRING_DESC_F_NEXT); + + /* status field */ + vring_send_buf(vr, &status, sizeof(u8), + VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN); + + /* Now we can tell the host to read */ + vring_wait_reply(); + + if (drain_irqs(vr->schid)) { + /* Well, whatever status is supposed to contain... */ + status = 1; + } + return status; +} + +int virtio_read_many(ulong sector, void *load_addr, int sec_num) +{ + VDev *vdev = virtio_get_device(); + + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return virtio_blk_read_many(vdev, sector, load_addr, sec_num); + case VIRTIO_ID_SCSI: + return virtio_scsi_read_many(vdev, sector, load_addr, sec_num); + } + panic("\n! No readable IPL device !\n"); + return -1; +} + +unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2, + ulong subchan_id, void *load_addr) +{ + u8 status; + int sec = rec_list1; + int sec_num = ((rec_list2 >> 32) & 0xffff) + 1; + int sec_len = rec_list2 >> 48; + ulong addr = (ulong)load_addr; + + if (sec_len != virtio_get_block_size()) { + return -1; + } + + sclp_print("."); + status = virtio_read_many(sec, (void *)addr, sec_num); + if (status) { + panic("I/O Error"); + } + addr += sec_num * virtio_get_block_size(); + + return addr; +} + +int virtio_read(ulong sector, void *load_addr) +{ + return virtio_read_many(sector, load_addr, 1); +} + +/* + * Other supported value pairs, if any, would need to be added here. + * Note: head count is always 15. + */ +static inline u8 virtio_eckd_sectors_for_block_size(int size) +{ + switch (size) { + case 512: + return 49; + case 1024: + return 33; + case 2048: + return 21; + case 4096: + return 12; + } + return 0; +} + +VirtioGDN virtio_guessed_disk_nature(void) +{ + return virtio_get_device()->guessed_disk_nature; +} + +void virtio_assume_scsi(void) +{ + VDev *vdev = virtio_get_device(); + + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + vdev->guessed_disk_nature = VIRTIO_GDN_SCSI; + vdev->config.blk.blk_size = VIRTIO_SCSI_BLOCK_SIZE; + vdev->config.blk.physical_block_exp = 0; + vdev->blk_factor = 1; + break; + case VIRTIO_ID_SCSI: + vdev->scsi_block_size = VIRTIO_SCSI_BLOCK_SIZE; + break; + } +} + +void virtio_assume_iso9660(void) +{ + VDev *vdev = virtio_get_device(); + + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + vdev->guessed_disk_nature = VIRTIO_GDN_SCSI; + vdev->config.blk.blk_size = VIRTIO_ISO_BLOCK_SIZE; + vdev->config.blk.physical_block_exp = 0; + vdev->blk_factor = VIRTIO_ISO_BLOCK_SIZE / VIRTIO_SECTOR_SIZE; + break; + case VIRTIO_ID_SCSI: + vdev->scsi_block_size = VIRTIO_ISO_BLOCK_SIZE; + break; + } +} + +void virtio_assume_eckd(void) +{ + VDev *vdev = virtio_get_device(); + + vdev->guessed_disk_nature = VIRTIO_GDN_DASD; + vdev->blk_factor = 1; + vdev->config.blk.physical_block_exp = 0; + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + vdev->config.blk.blk_size = 4096; + break; + case VIRTIO_ID_SCSI: + vdev->config.blk.blk_size = vdev->scsi_block_size; + break; + } + vdev->config.blk.geometry.heads = 15; + vdev->config.blk.geometry.sectors = + virtio_eckd_sectors_for_block_size(vdev->config.blk.blk_size); +} + +bool virtio_disk_is_scsi(void) +{ + VDev *vdev = virtio_get_device(); + + if (vdev->guessed_disk_nature == VIRTIO_GDN_SCSI) { + return true; + } + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return (vdev->config.blk.geometry.heads == 255) + && (vdev->config.blk.geometry.sectors == 63) + && (virtio_get_block_size() == VIRTIO_SCSI_BLOCK_SIZE); + case VIRTIO_ID_SCSI: + return true; + } + return false; +} + +bool virtio_disk_is_eckd(void) +{ + VDev *vdev = virtio_get_device(); + const int block_size = virtio_get_block_size(); + + if (vdev->guessed_disk_nature == VIRTIO_GDN_DASD) { + return true; + } + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return (vdev->config.blk.geometry.heads == 15) + && (vdev->config.blk.geometry.sectors == + virtio_eckd_sectors_for_block_size(block_size)); + case VIRTIO_ID_SCSI: + return false; + } + return false; +} + +bool virtio_ipl_disk_is_valid(void) +{ + return virtio_disk_is_scsi() || virtio_disk_is_eckd(); +} + +int virtio_get_block_size(void) +{ + VDev *vdev = virtio_get_device(); + + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return vdev->config.blk.blk_size << vdev->config.blk.physical_block_exp; + case VIRTIO_ID_SCSI: + return vdev->scsi_block_size; + } + return 0; +} + +uint8_t virtio_get_heads(void) +{ + VDev *vdev = virtio_get_device(); + + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return vdev->config.blk.geometry.heads; + case VIRTIO_ID_SCSI: + return vdev->guessed_disk_nature == VIRTIO_GDN_DASD + ? vdev->config.blk.geometry.heads : 255; + } + return 0; +} + +uint8_t virtio_get_sectors(void) +{ + VDev *vdev = virtio_get_device(); + + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return vdev->config.blk.geometry.sectors; + case VIRTIO_ID_SCSI: + return vdev->guessed_disk_nature == VIRTIO_GDN_DASD + ? vdev->config.blk.geometry.sectors : 63; + } + return 0; +} + +uint64_t virtio_get_blocks(void) +{ + VDev *vdev = virtio_get_device(); + const uint64_t factor = virtio_get_block_size() / VIRTIO_SECTOR_SIZE; + + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + return vdev->config.blk.capacity / factor; + case VIRTIO_ID_SCSI: + return vdev->scsi_last_block / factor; + } + return 0; +} + +void virtio_blk_setup_device(SubChannelId schid) +{ + VDev *vdev = virtio_get_device(); + + vdev->schid = schid; + virtio_setup_ccw(vdev); + + switch (vdev->senseid.cu_model) { + case VIRTIO_ID_BLOCK: + sclp_print("Using virtio-blk.\n"); + if (!virtio_ipl_disk_is_valid()) { + /* make sure all getters but blocksize return 0 for + * invalid IPL disk + */ + memset(&vdev->config.blk, 0, sizeof(vdev->config.blk)); + virtio_assume_scsi(); + } + break; + case VIRTIO_ID_SCSI: + IPL_assert(vdev->config.scsi.sense_size == VIRTIO_SCSI_SENSE_SIZE, + "Config: sense size mismatch"); + IPL_assert(vdev->config.scsi.cdb_size == VIRTIO_SCSI_CDB_SIZE, + "Config: CDB size mismatch"); + + sclp_print("Using virtio-scsi.\n"); + virtio_scsi_setup(vdev); + break; + default: + panic("\n! No IPL device available !\n"); + } +} |