Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 1 | // Virtio SCSI boot support. |
| 2 | // |
| 3 | // Copyright (C) 2012 Red Hat Inc. |
| 4 | // |
| 5 | // Authors: |
| 6 | // Paolo Bonzini <pbonzini@redhat.com> |
| 7 | // |
| 8 | // This file may be distributed under the terms of the GNU LGPLv3 license. |
| 9 | |
Kevin O'Connor | 1902c94 | 2013-10-26 11:48:06 -0400 | [diff] [blame] | 10 | #include "biosvar.h" // GET_GLOBALFLAT |
Kevin O'Connor | 135f3f6 | 2013-09-14 23:57:26 -0400 | [diff] [blame] | 11 | #include "block.h" // struct drive_s |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 12 | #include "blockcmd.h" // scsi_drive_setup |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 13 | #include "config.h" // CONFIG_* |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 14 | #include "malloc.h" // free |
| 15 | #include "output.h" // dprintf |
| 16 | #include "pci.h" // foreachpci |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 17 | #include "pci_ids.h" // PCI_DEVICE_ID_VIRTIO_BLK |
| 18 | #include "pci_regs.h" // PCI_VENDOR_ID |
Kevin O'Connor | 135f3f6 | 2013-09-14 23:57:26 -0400 | [diff] [blame] | 19 | #include "std/disk.h" // DISK_RET_SUCCESS |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 20 | #include "string.h" // memset |
| 21 | #include "util.h" // usleep |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 22 | #include "virtio-pci.h" |
| 23 | #include "virtio-ring.h" |
| 24 | #include "virtio-scsi.h" |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 25 | |
| 26 | struct virtio_lun_s { |
| 27 | struct drive_s drive; |
| 28 | struct pci_device *pci; |
| 29 | struct vring_virtqueue *vq; |
| 30 | u16 ioaddr; |
| 31 | u16 target; |
| 32 | u16 lun; |
| 33 | }; |
| 34 | |
| 35 | static int |
| 36 | virtio_scsi_cmd(u16 ioaddr, struct vring_virtqueue *vq, struct disk_op_s *op, |
Kevin O'Connor | 1fd9a89 | 2012-03-14 21:27:45 -0400 | [diff] [blame] | 37 | void *cdbcmd, u16 target, u16 lun, u16 blocksize) |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 38 | { |
| 39 | struct virtio_scsi_req_cmd req; |
| 40 | struct virtio_scsi_resp_cmd resp; |
| 41 | struct vring_list sg[3]; |
| 42 | |
| 43 | memset(&req, 0, sizeof(req)); |
| 44 | req.lun[0] = 1; |
| 45 | req.lun[1] = target; |
| 46 | req.lun[2] = (lun >> 8) | 0x40; |
| 47 | req.lun[3] = (lun & 0xff); |
| 48 | memcpy(req.cdb, cdbcmd, 16); |
| 49 | |
Kevin O'Connor | 1fd9a89 | 2012-03-14 21:27:45 -0400 | [diff] [blame] | 50 | u32 len = op->count * blocksize; |
| 51 | int datain = cdb_is_read(cdbcmd, blocksize); |
Paolo Bonzini | 8c31307 | 2012-03-16 18:54:46 +0100 | [diff] [blame] | 52 | int in_num = (datain ? 2 : 1); |
| 53 | int out_num = (len ? 3 : 2) - in_num; |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 54 | |
| 55 | sg[0].addr = MAKE_FLATPTR(GET_SEG(SS), &req); |
| 56 | sg[0].length = sizeof(req); |
| 57 | |
| 58 | sg[out_num].addr = MAKE_FLATPTR(GET_SEG(SS), &resp); |
| 59 | sg[out_num].length = sizeof(resp); |
| 60 | |
Paolo Bonzini | 8c31307 | 2012-03-16 18:54:46 +0100 | [diff] [blame] | 61 | if (len) { |
| 62 | int data_idx = (datain ? 2 : 1); |
| 63 | sg[data_idx].addr = op->buf_fl; |
| 64 | sg[data_idx].length = len; |
| 65 | } |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 66 | |
| 67 | /* Add to virtqueue and kick host */ |
| 68 | vring_add_buf(vq, sg, out_num, in_num, 0, 0); |
| 69 | vring_kick(ioaddr, vq, 1); |
| 70 | |
| 71 | /* Wait for reply */ |
| 72 | while (!vring_more_used(vq)) |
| 73 | usleep(5); |
| 74 | |
| 75 | /* Reclaim virtqueue element */ |
| 76 | vring_get_buf(vq, NULL); |
| 77 | |
| 78 | /* Clear interrupt status register. Avoid leaving interrupts stuck if |
| 79 | * VRING_AVAIL_F_NO_INTERRUPT was ignored and interrupts were raised. |
| 80 | */ |
| 81 | vp_get_isr(ioaddr); |
| 82 | |
| 83 | if (resp.response == VIRTIO_SCSI_S_OK && resp.status == 0) { |
| 84 | return DISK_RET_SUCCESS; |
| 85 | } |
| 86 | return DISK_RET_EBADTRACK; |
| 87 | } |
| 88 | |
| 89 | int |
| 90 | virtio_scsi_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize) |
| 91 | { |
Kevin O'Connor | 1902c94 | 2013-10-26 11:48:06 -0400 | [diff] [blame] | 92 | struct virtio_lun_s *vlun_gf = |
| 93 | container_of(op->drive_gf, struct virtio_lun_s, drive); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 94 | |
Kevin O'Connor | 1902c94 | 2013-10-26 11:48:06 -0400 | [diff] [blame] | 95 | return virtio_scsi_cmd(GET_GLOBALFLAT(vlun_gf->ioaddr), |
| 96 | GET_GLOBALFLAT(vlun_gf->vq), op, cdbcmd, |
| 97 | GET_GLOBALFLAT(vlun_gf->target), |
| 98 | GET_GLOBALFLAT(vlun_gf->lun), |
Kevin O'Connor | 1fd9a89 | 2012-03-14 21:27:45 -0400 | [diff] [blame] | 99 | blocksize); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 100 | } |
| 101 | |
| 102 | static int |
| 103 | virtio_scsi_add_lun(struct pci_device *pci, u16 ioaddr, |
| 104 | struct vring_virtqueue *vq, u16 target, u16 lun) |
| 105 | { |
| 106 | struct virtio_lun_s *vlun = malloc_fseg(sizeof(*vlun)); |
| 107 | if (!vlun) { |
| 108 | warn_noalloc(); |
| 109 | return -1; |
| 110 | } |
| 111 | memset(vlun, 0, sizeof(*vlun)); |
| 112 | vlun->drive.type = DTYPE_VIRTIO_SCSI; |
| 113 | vlun->drive.cntl_id = pci->bdf; |
| 114 | vlun->pci = pci; |
| 115 | vlun->ioaddr = ioaddr; |
| 116 | vlun->vq = vq; |
| 117 | vlun->target = target; |
| 118 | vlun->lun = lun; |
| 119 | |
| 120 | int prio = bootprio_find_scsi_device(pci, target, lun); |
Kevin O'Connor | d83c87b | 2013-01-21 01:14:12 -0500 | [diff] [blame] | 121 | int ret = scsi_drive_setup(&vlun->drive, "virtio-scsi", prio); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 122 | if (ret) |
| 123 | goto fail; |
| 124 | return 0; |
| 125 | |
| 126 | fail: |
| 127 | free(vlun); |
| 128 | return -1; |
| 129 | } |
| 130 | |
| 131 | static int |
| 132 | virtio_scsi_scan_target(struct pci_device *pci, u16 ioaddr, |
| 133 | struct vring_virtqueue *vq, u16 target) |
| 134 | { |
| 135 | /* TODO: send REPORT LUNS. For now, only LUN 0 is recognized. */ |
| 136 | int ret = virtio_scsi_add_lun(pci, ioaddr, vq, target, 0); |
| 137 | return ret < 0 ? 0 : 1; |
| 138 | } |
| 139 | |
| 140 | static void |
| 141 | init_virtio_scsi(struct pci_device *pci) |
| 142 | { |
| 143 | u16 bdf = pci->bdf; |
| 144 | dprintf(1, "found virtio-scsi at %x:%x\n", pci_bdf_to_bus(bdf), |
| 145 | pci_bdf_to_dev(bdf)); |
| 146 | struct vring_virtqueue *vq = NULL; |
| 147 | u16 ioaddr = vp_init_simple(bdf); |
| 148 | if (vp_find_vq(ioaddr, 2, &vq) < 0 ) { |
| 149 | dprintf(1, "fail to find vq for virtio-scsi %x:%x\n", |
| 150 | pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf)); |
| 151 | goto fail; |
| 152 | } |
| 153 | |
Asias He | 5a7730d | 2013-03-15 09:45:15 +0800 | [diff] [blame] | 154 | vp_set_status(ioaddr, VIRTIO_CONFIG_S_ACKNOWLEDGE | |
| 155 | VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_DRIVER_OK); |
| 156 | |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 157 | int i, tot; |
| 158 | for (tot = 0, i = 0; i < 256; i++) |
| 159 | tot += virtio_scsi_scan_target(pci, ioaddr, vq, i); |
| 160 | |
Eric Northup | 5e03881 | 2014-03-12 13:42:35 -0700 | [diff] [blame^] | 161 | if (!tot) |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 162 | goto fail; |
| 163 | |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 164 | return; |
| 165 | |
| 166 | fail: |
Eric Northup | 5e03881 | 2014-03-12 13:42:35 -0700 | [diff] [blame^] | 167 | vp_reset(ioaddr); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 168 | free(vq); |
| 169 | } |
| 170 | |
| 171 | void |
| 172 | virtio_scsi_setup(void) |
| 173 | { |
| 174 | ASSERT32FLAT(); |
Kevin O'Connor | 897fb11 | 2013-02-07 23:32:48 -0500 | [diff] [blame] | 175 | if (! CONFIG_VIRTIO_SCSI) |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 176 | return; |
| 177 | |
| 178 | dprintf(3, "init virtio-scsi\n"); |
| 179 | |
| 180 | struct pci_device *pci; |
| 181 | foreachpci(pci) { |
| 182 | if (pci->vendor != PCI_VENDOR_ID_REDHAT_QUMRANET |
| 183 | || pci->device != PCI_DEVICE_ID_VIRTIO_SCSI) |
| 184 | continue; |
| 185 | init_virtio_scsi(pci); |
| 186 | } |
| 187 | } |