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 |
Kevin O'Connor | 4d8510c | 2016-02-03 01:28:20 -0500 | [diff] [blame] | 16 | #include "pcidevice.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 | b35ea54 | 2016-04-01 20:05:45 -0400 | [diff] [blame] | 19 | #include "stacks.h" // run_thread |
Kevin O'Connor | 135f3f6 | 2013-09-14 23:57:26 -0400 | [diff] [blame] | 20 | #include "std/disk.h" // DISK_RET_SUCCESS |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 21 | #include "string.h" // memset |
| 22 | #include "util.h" // usleep |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 23 | #include "virtio-pci.h" |
| 24 | #include "virtio-ring.h" |
| 25 | #include "virtio-scsi.h" |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 26 | |
| 27 | struct virtio_lun_s { |
| 28 | struct drive_s drive; |
| 29 | struct pci_device *pci; |
| 30 | struct vring_virtqueue *vq; |
Gerd Hoffmann | daf5cc9 | 2015-06-25 09:36:16 +0200 | [diff] [blame] | 31 | struct vp_device *vp; |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 32 | u16 target; |
| 33 | u16 lun; |
| 34 | }; |
| 35 | |
Kevin O'Connor | e0eb5a0 | 2015-07-07 12:19:53 -0400 | [diff] [blame] | 36 | int |
| 37 | virtio_scsi_process_op(struct disk_op_s *op) |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 38 | { |
Kevin O'Connor | e0eb5a0 | 2015-07-07 12:19:53 -0400 | [diff] [blame] | 39 | if (! CONFIG_VIRTIO_SCSI) |
| 40 | return 0; |
| 41 | struct virtio_lun_s *vlun = |
| 42 | container_of(op->drive_gf, struct virtio_lun_s, drive); |
| 43 | struct vp_device *vp = vlun->vp; |
| 44 | struct vring_virtqueue *vq = vlun->vq; |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 45 | struct virtio_scsi_req_cmd req; |
| 46 | struct virtio_scsi_resp_cmd resp; |
| 47 | struct vring_list sg[3]; |
| 48 | |
| 49 | memset(&req, 0, sizeof(req)); |
Kevin O'Connor | e0eb5a0 | 2015-07-07 12:19:53 -0400 | [diff] [blame] | 50 | int blocksize = scsi_fill_cmd(op, req.cdb, 16); |
| 51 | if (blocksize < 0) |
| 52 | return default_process_op(op); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 53 | req.lun[0] = 1; |
Kevin O'Connor | e0eb5a0 | 2015-07-07 12:19:53 -0400 | [diff] [blame] | 54 | req.lun[1] = vlun->target; |
| 55 | req.lun[2] = (vlun->lun >> 8) | 0x40; |
| 56 | req.lun[3] = (vlun->lun & 0xff); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 57 | |
Kevin O'Connor | 1fd9a89 | 2012-03-14 21:27:45 -0400 | [diff] [blame] | 58 | u32 len = op->count * blocksize; |
Kevin O'Connor | 5dcd1ee | 2015-07-07 14:43:01 -0400 | [diff] [blame] | 59 | int datain = scsi_is_read(op); |
Paolo Bonzini | 8c31307 | 2012-03-16 18:54:46 +0100 | [diff] [blame] | 60 | int in_num = (datain ? 2 : 1); |
| 61 | int out_num = (len ? 3 : 2) - in_num; |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 62 | |
Gerd Hoffmann | b7dd90f | 2015-07-01 14:38:59 +0200 | [diff] [blame] | 63 | sg[0].addr = (void*)(&req); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 64 | sg[0].length = sizeof(req); |
| 65 | |
Gerd Hoffmann | b7dd90f | 2015-07-01 14:38:59 +0200 | [diff] [blame] | 66 | sg[out_num].addr = (void*)(&resp); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 67 | sg[out_num].length = sizeof(resp); |
| 68 | |
Paolo Bonzini | 8c31307 | 2012-03-16 18:54:46 +0100 | [diff] [blame] | 69 | if (len) { |
| 70 | int data_idx = (datain ? 2 : 1); |
| 71 | sg[data_idx].addr = op->buf_fl; |
| 72 | sg[data_idx].length = len; |
| 73 | } |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 74 | |
| 75 | /* Add to virtqueue and kick host */ |
| 76 | vring_add_buf(vq, sg, out_num, in_num, 0, 0); |
Gerd Hoffmann | daf5cc9 | 2015-06-25 09:36:16 +0200 | [diff] [blame] | 77 | vring_kick(vp, vq, 1); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 78 | |
| 79 | /* Wait for reply */ |
| 80 | while (!vring_more_used(vq)) |
| 81 | usleep(5); |
| 82 | |
| 83 | /* Reclaim virtqueue element */ |
| 84 | vring_get_buf(vq, NULL); |
| 85 | |
| 86 | /* Clear interrupt status register. Avoid leaving interrupts stuck if |
| 87 | * VRING_AVAIL_F_NO_INTERRUPT was ignored and interrupts were raised. |
| 88 | */ |
Gerd Hoffmann | daf5cc9 | 2015-06-25 09:36:16 +0200 | [diff] [blame] | 89 | vp_get_isr(vp); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 90 | |
| 91 | if (resp.response == VIRTIO_SCSI_S_OK && resp.status == 0) { |
| 92 | return DISK_RET_SUCCESS; |
| 93 | } |
| 94 | return DISK_RET_EBADTRACK; |
| 95 | } |
| 96 | |
Roman Kagan | 69ebdef | 2017-04-26 17:18:03 +0300 | [diff] [blame] | 97 | static void |
| 98 | virtio_scsi_init_lun(struct virtio_lun_s *vlun, struct pci_device *pci, |
| 99 | struct vp_device *vp, struct vring_virtqueue *vq, |
| 100 | u16 target, u16 lun) |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 101 | { |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 102 | memset(vlun, 0, sizeof(*vlun)); |
| 103 | vlun->drive.type = DTYPE_VIRTIO_SCSI; |
| 104 | vlun->drive.cntl_id = pci->bdf; |
| 105 | vlun->pci = pci; |
Gerd Hoffmann | daf5cc9 | 2015-06-25 09:36:16 +0200 | [diff] [blame] | 106 | vlun->vp = vp; |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 107 | vlun->vq = vq; |
| 108 | vlun->target = target; |
| 109 | vlun->lun = lun; |
Roman Kagan | 69ebdef | 2017-04-26 17:18:03 +0300 | [diff] [blame] | 110 | } |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 111 | |
Roman Kagan | 69ebdef | 2017-04-26 17:18:03 +0300 | [diff] [blame] | 112 | static int |
| 113 | virtio_scsi_add_lun(u32 lun, struct drive_s *tmpl_drv) |
| 114 | { |
| 115 | struct virtio_lun_s *tmpl_vlun = |
| 116 | container_of(tmpl_drv, struct virtio_lun_s, drive); |
| 117 | struct virtio_lun_s *vlun = malloc_fseg(sizeof(*vlun)); |
| 118 | if (!vlun) { |
| 119 | warn_noalloc(); |
| 120 | return -1; |
| 121 | } |
| 122 | virtio_scsi_init_lun(vlun, tmpl_vlun->pci, tmpl_vlun->vp, tmpl_vlun->vq, |
| 123 | tmpl_vlun->target, lun); |
| 124 | |
| 125 | int prio = bootprio_find_scsi_device(vlun->pci, vlun->target, vlun->lun); |
Kevin O'Connor | d83c87b | 2013-01-21 01:14:12 -0500 | [diff] [blame] | 126 | int ret = scsi_drive_setup(&vlun->drive, "virtio-scsi", prio); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 127 | if (ret) |
| 128 | goto fail; |
| 129 | return 0; |
| 130 | |
| 131 | fail: |
| 132 | free(vlun); |
| 133 | return -1; |
| 134 | } |
| 135 | |
| 136 | static int |
Gerd Hoffmann | daf5cc9 | 2015-06-25 09:36:16 +0200 | [diff] [blame] | 137 | virtio_scsi_scan_target(struct pci_device *pci, struct vp_device *vp, |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 138 | struct vring_virtqueue *vq, u16 target) |
| 139 | { |
Roman Kagan | 69ebdef | 2017-04-26 17:18:03 +0300 | [diff] [blame] | 140 | |
| 141 | struct virtio_lun_s vlun0; |
| 142 | |
| 143 | virtio_scsi_init_lun(&vlun0, pci, vp, vq, target, 0); |
| 144 | |
| 145 | int ret = scsi_rep_luns_scan(&vlun0.drive, virtio_scsi_add_lun); |
| 146 | return ret < 0 ? 0 : ret; |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 147 | } |
| 148 | |
| 149 | static void |
Kevin O'Connor | b35ea54 | 2016-04-01 20:05:45 -0400 | [diff] [blame] | 150 | init_virtio_scsi(void *data) |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 151 | { |
Kevin O'Connor | b35ea54 | 2016-04-01 20:05:45 -0400 | [diff] [blame] | 152 | struct pci_device *pci = data; |
Kevin O'Connor | 7b67300 | 2016-02-03 03:03:15 -0500 | [diff] [blame] | 153 | dprintf(1, "found virtio-scsi at %pP\n", pci); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 154 | struct vring_virtqueue *vq = NULL; |
Gerd Hoffmann | daf5cc9 | 2015-06-25 09:36:16 +0200 | [diff] [blame] | 155 | struct vp_device *vp = malloc_high(sizeof(*vp)); |
| 156 | if (!vp) { |
| 157 | warn_noalloc(); |
| 158 | return; |
| 159 | } |
Gerd Hoffmann | 74d0cdc | 2015-06-25 10:24:27 +0200 | [diff] [blame] | 160 | vp_init_simple(vp, pci); |
Gerd Hoffmann | 604a6f1 | 2015-06-26 10:22:55 +0200 | [diff] [blame] | 161 | u8 status = VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER; |
| 162 | |
| 163 | if (vp->use_modern) { |
| 164 | u64 features = vp_get_features(vp); |
| 165 | u64 version1 = 1ull << VIRTIO_F_VERSION_1; |
Jason Wang | dd9bba5 | 2017-07-05 15:49:51 +0800 | [diff] [blame^] | 166 | u64 iommu_platform = 1ull << VIRTIO_F_IOMMU_PLATFORM; |
Gerd Hoffmann | 604a6f1 | 2015-06-26 10:22:55 +0200 | [diff] [blame] | 167 | if (!(features & version1)) { |
Kevin O'Connor | 7b67300 | 2016-02-03 03:03:15 -0500 | [diff] [blame] | 168 | dprintf(1, "modern device without virtio_1 feature bit: %pP\n", pci); |
Gerd Hoffmann | 604a6f1 | 2015-06-26 10:22:55 +0200 | [diff] [blame] | 169 | goto fail; |
| 170 | } |
| 171 | |
Jason Wang | dd9bba5 | 2017-07-05 15:49:51 +0800 | [diff] [blame^] | 172 | vp_set_features(vp, features & (version1 | iommu_platform)); |
Gerd Hoffmann | 604a6f1 | 2015-06-26 10:22:55 +0200 | [diff] [blame] | 173 | status |= VIRTIO_CONFIG_S_FEATURES_OK; |
| 174 | vp_set_status(vp, status); |
| 175 | if (!(vp_get_status(vp) & VIRTIO_CONFIG_S_FEATURES_OK)) { |
Kevin O'Connor | 7b67300 | 2016-02-03 03:03:15 -0500 | [diff] [blame] | 176 | dprintf(1, "device didn't accept features: %pP\n", pci); |
Gerd Hoffmann | 604a6f1 | 2015-06-26 10:22:55 +0200 | [diff] [blame] | 177 | goto fail; |
| 178 | } |
| 179 | } |
| 180 | |
Gerd Hoffmann | daf5cc9 | 2015-06-25 09:36:16 +0200 | [diff] [blame] | 181 | if (vp_find_vq(vp, 2, &vq) < 0 ) { |
Kevin O'Connor | 7b67300 | 2016-02-03 03:03:15 -0500 | [diff] [blame] | 182 | dprintf(1, "fail to find vq for virtio-scsi %pP\n", pci); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 183 | goto fail; |
| 184 | } |
| 185 | |
Gerd Hoffmann | 604a6f1 | 2015-06-26 10:22:55 +0200 | [diff] [blame] | 186 | status |= VIRTIO_CONFIG_S_DRIVER_OK; |
| 187 | vp_set_status(vp, status); |
Asias He | 5a7730d | 2013-03-15 09:45:15 +0800 | [diff] [blame] | 188 | |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 189 | int i, tot; |
| 190 | for (tot = 0, i = 0; i < 256; i++) |
Gerd Hoffmann | daf5cc9 | 2015-06-25 09:36:16 +0200 | [diff] [blame] | 191 | tot += virtio_scsi_scan_target(pci, vp, vq, i); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 192 | |
Eric Northup | 5e03881 | 2014-03-12 13:42:35 -0700 | [diff] [blame] | 193 | if (!tot) |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 194 | goto fail; |
| 195 | |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 196 | return; |
| 197 | |
| 198 | fail: |
Gerd Hoffmann | daf5cc9 | 2015-06-25 09:36:16 +0200 | [diff] [blame] | 199 | vp_reset(vp); |
| 200 | free(vp); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 201 | free(vq); |
| 202 | } |
| 203 | |
| 204 | void |
| 205 | virtio_scsi_setup(void) |
| 206 | { |
| 207 | ASSERT32FLAT(); |
Kevin O'Connor | 897fb11 | 2013-02-07 23:32:48 -0500 | [diff] [blame] | 208 | if (! CONFIG_VIRTIO_SCSI) |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 209 | return; |
| 210 | |
| 211 | dprintf(3, "init virtio-scsi\n"); |
| 212 | |
| 213 | struct pci_device *pci; |
| 214 | foreachpci(pci) { |
Gerd Hoffmann | 71acef8 | 2015-06-26 09:50:22 +0200 | [diff] [blame] | 215 | if (pci->vendor != PCI_VENDOR_ID_REDHAT_QUMRANET || |
| 216 | (pci->device != PCI_DEVICE_ID_VIRTIO_SCSI_09 && |
| 217 | pci->device != PCI_DEVICE_ID_VIRTIO_SCSI_10)) |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 218 | continue; |
Kevin O'Connor | b35ea54 | 2016-04-01 20:05:45 -0400 | [diff] [blame] | 219 | run_thread(init_virtio_scsi, pci); |
Paolo Bonzini | c5c488f | 2012-02-27 17:22:23 +0100 | [diff] [blame] | 220 | } |
| 221 | } |