/* * Virtio driver bits * * Copyright (c) 2013 Alexander Graf * * 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"); } }