aboutsummaryrefslogtreecommitdiff
path: root/pc-bios/s390-ccw/virtio-blkdev.c
diff options
context:
space:
mode:
Diffstat (limited to 'pc-bios/s390-ccw/virtio-blkdev.c')
-rw-r--r--pc-bios/s390-ccw/virtio-blkdev.c296
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");
+ }
+}