1 // Virtio SCSI boot support.
3 // Copyright (C) 2012 Red Hat Inc.
6 // Paolo Bonzini <pbonzini@redhat.com>
8 // This file may be distributed under the terms of the GNU LGPLv3 license.
10 #include "biosvar.h" // GET_GLOBALFLAT
11 #include "block.h" // struct drive_s
12 #include "blockcmd.h" // scsi_drive_setup
13 #include "config.h" // CONFIG_*
14 #include "malloc.h" // free
15 #include "output.h" // dprintf
16 #include "pci.h" // foreachpci
17 #include "pci_ids.h" // PCI_DEVICE_ID_VIRTIO_BLK
18 #include "pci_regs.h" // PCI_VENDOR_ID
19 #include "std/disk.h" // DISK_RET_SUCCESS
20 #include "string.h" // memset
21 #include "util.h" // usleep
22 #include "virtio-pci.h"
23 #include "virtio-ring.h"
24 #include "virtio-scsi.h"
28 struct pci_device *pci;
29 struct vring_virtqueue *vq;
36 virtio_scsi_process_op(struct disk_op_s *op)
38 if (! CONFIG_VIRTIO_SCSI)
40 struct virtio_lun_s *vlun =
41 container_of(op->drive_gf, struct virtio_lun_s, drive);
42 struct vp_device *vp = vlun->vp;
43 struct vring_virtqueue *vq = vlun->vq;
44 struct virtio_scsi_req_cmd req;
45 struct virtio_scsi_resp_cmd resp;
46 struct vring_list sg[3];
48 memset(&req, 0, sizeof(req));
49 int blocksize = scsi_fill_cmd(op, req.cdb, 16);
51 return default_process_op(op);
53 req.lun[1] = vlun->target;
54 req.lun[2] = (vlun->lun >> 8) | 0x40;
55 req.lun[3] = (vlun->lun & 0xff);
57 u32 len = op->count * blocksize;
58 int datain = scsi_is_read(op);
59 int in_num = (datain ? 2 : 1);
60 int out_num = (len ? 3 : 2) - in_num;
62 sg[0].addr = (void*)(&req);
63 sg[0].length = sizeof(req);
65 sg[out_num].addr = (void*)(&resp);
66 sg[out_num].length = sizeof(resp);
69 int data_idx = (datain ? 2 : 1);
70 sg[data_idx].addr = op->buf_fl;
71 sg[data_idx].length = len;
74 /* Add to virtqueue and kick host */
75 vring_add_buf(vq, sg, out_num, in_num, 0, 0);
76 vring_kick(vp, vq, 1);
79 while (!vring_more_used(vq))
82 /* Reclaim virtqueue element */
83 vring_get_buf(vq, NULL);
85 /* Clear interrupt status register. Avoid leaving interrupts stuck if
86 * VRING_AVAIL_F_NO_INTERRUPT was ignored and interrupts were raised.
90 if (resp.response == VIRTIO_SCSI_S_OK && resp.status == 0) {
91 return DISK_RET_SUCCESS;
93 return DISK_RET_EBADTRACK;
97 virtio_scsi_add_lun(struct pci_device *pci, struct vp_device *vp,
98 struct vring_virtqueue *vq, u16 target, u16 lun)
100 struct virtio_lun_s *vlun = malloc_fseg(sizeof(*vlun));
105 memset(vlun, 0, sizeof(*vlun));
106 vlun->drive.type = DTYPE_VIRTIO_SCSI;
107 vlun->drive.cntl_id = pci->bdf;
111 vlun->target = target;
114 int prio = bootprio_find_scsi_device(pci, target, lun);
115 int ret = scsi_drive_setup(&vlun->drive, "virtio-scsi", prio);
126 virtio_scsi_scan_target(struct pci_device *pci, struct vp_device *vp,
127 struct vring_virtqueue *vq, u16 target)
129 /* TODO: send REPORT LUNS. For now, only LUN 0 is recognized. */
130 int ret = virtio_scsi_add_lun(pci, vp, vq, target, 0);
131 return ret < 0 ? 0 : 1;
135 init_virtio_scsi(struct pci_device *pci)
138 dprintf(1, "found virtio-scsi at %x:%x\n", pci_bdf_to_bus(bdf),
139 pci_bdf_to_dev(bdf));
140 struct vring_virtqueue *vq = NULL;
141 struct vp_device *vp = malloc_high(sizeof(*vp));
146 vp_init_simple(vp, pci);
147 u8 status = VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER;
149 if (vp->use_modern) {
150 u64 features = vp_get_features(vp);
151 u64 version1 = 1ull << VIRTIO_F_VERSION_1;
152 if (!(features & version1)) {
153 dprintf(1, "modern device without virtio_1 feature bit: %x:%x\n",
154 pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf));
158 vp_set_features(vp, version1);
159 status |= VIRTIO_CONFIG_S_FEATURES_OK;
160 vp_set_status(vp, status);
161 if (!(vp_get_status(vp) & VIRTIO_CONFIG_S_FEATURES_OK)) {
162 dprintf(1, "device didn't accept features: %x:%x\n",
163 pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf));
168 if (vp_find_vq(vp, 2, &vq) < 0 ) {
169 dprintf(1, "fail to find vq for virtio-scsi %x:%x\n",
170 pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf));
174 status |= VIRTIO_CONFIG_S_DRIVER_OK;
175 vp_set_status(vp, status);
178 for (tot = 0, i = 0; i < 256; i++)
179 tot += virtio_scsi_scan_target(pci, vp, vq, i);
193 virtio_scsi_setup(void)
196 if (! CONFIG_VIRTIO_SCSI)
199 dprintf(3, "init virtio-scsi\n");
201 struct pci_device *pci;
203 if (pci->vendor != PCI_VENDOR_ID_REDHAT_QUMRANET ||
204 (pci->device != PCI_DEVICE_ID_VIRTIO_SCSI_09 &&
205 pci->device != PCI_DEVICE_ID_VIRTIO_SCSI_10))
207 init_virtio_scsi(pci);