| /* |
| * 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"); |
| } |
| } |