Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | // SPDX-License-Identifier: GPL-2.0+ /* * U-Boot driver for VirtIO SCSI host devices * * This driver implements the host-side interface for VirtIO SCSI devices, * allowing U-Boot to communicate with SCSI LUNs provided by a hypervisor. * * Based on the VirtIO v1.3 specification, Section 5.6: SCSI Host Device */ #define LOG_CATEGORY UCLASS_VIRTIO #include <blk.h> #include <dm.h> #include <malloc.h> #include <scsi.h> #include <virtio.h> #include <virtio_ring.h> #include "virtio_scsi.h" static_assert(CMD_BUF_SIZE >= VIRTIO_SCSI_CDB_SIZE); static_assert(SENSE_BUF_LEN >= VIRTIO_SCSI_SENSE_SIZE); /* * We need three virtqueues: controlq, eventq, and requestq. * The request queue is the one we will use for actual SCSI commands. */ enum { QUEUE_CONTROL, QUEUE_EVENT, QUEUE_REQUEST, QUEUE_COUNT, }; /* * struct virtio_scsi_cmd - represents a pending SCSI command * * The address of this struct is used as a 'cookie' to identify the request in * the virtqueue, although in practice there is only one pending request at a * time */ struct virtio_scsi_cmd { struct virtio_scsi_cmd_req req; struct virtio_scsi_cmd_resp resp; }; /** * struct virtio_scsi_priv - Per-device private data * * @vqs: Array of virtqueues for this device * @max_target: Maximum target ID supported by the device * @max_lun: Maximum LUN ID supported by the device * @v_cmd: Pre-allocated virtio-command struct to avoid allocation in the * I/O path * @id: ID number for the next transation */ struct virtio_scsi_priv { struct virtqueue *vqs[QUEUE_COUNT]; int max_target; int max_lun; int id; /* Pre-allocated command struct to avoid allocation in send_cmd */ struct virtio_scsi_cmd v_cmd; }; u32 features[] = { VIRTIO_SCSI_F_INOUT, }; static int virtio_scsi_exec(struct udevice *dev, struct scsi_cmd *cmd) { struct virtio_scsi_priv *priv = dev_get_priv(dev); struct virtqueue *vq = priv->vqs[QUEUE_REQUEST]; struct virtio_scsi_cmd *v_cmd = &priv->v_cmd; struct virtio_sg out_sgs[2], in_sgs[2]; struct virtio_sg *sgs[4]; uint out_count = 0, in_count = 0; int len, i, ret; uint dummy_len; /* Clear the pre-allocated command struct to avoid stale data */ memset(v_cmd, '\0', sizeof(*v_cmd)); /* * Fill in the request header. * The LUN is encoded in a specific way for virtio-scsi. * LUN 0 is encoded as 0x4000. LUNs 1-255 are encoded as themselves. */ v_cmd->req.lun[0] = 1; v_cmd->req.lun[1] = cmd->target; v_cmd->req.lun[2] = 0x40 | ((cmd->lun >> 8) & 0x3f); v_cmd->req.lun[3] = cmd->lun & 0xff; v_cmd->req.tag = priv->id++; v_cmd->req.task_attr = VIRTIO_SCSI_S_SIMPLE; /* Set the command CDB */ memcpy(v_cmd->req.cdb, cmd->cmd, cmd->cmdlen); log_debug("cmd %x\n", *cmd->cmd); /* * Set up scatter-gather lists for the request and response. * The device writes to IN buffers and reads from OUT buffers. */ out_sgs[out_count].addr = &v_cmd->req; out_sgs[out_count].length = sizeof(v_cmd->req); out_count++; in_sgs[in_count].addr = &v_cmd->resp; in_sgs[in_count].length = sizeof(v_cmd->resp); in_count++; if (cmd->datalen > 0) { switch (cmd->cmd[0]) { case SCSI_READ6: case SCSI_READ10: case SCSI_READ16: case SCSI_INQUIRY: in_sgs[in_count].addr = cmd->pdata; in_sgs[in_count].length = cmd->datalen; in_count++; break; case SCSI_WRITE6: case SCSI_WRITE10: out_sgs[out_count].addr = cmd->pdata; out_sgs[out_count].length = cmd->datalen; out_count++; break; case SCSI_REPORT_LUNS: case SCSI_RD_CAPAC: in_sgs[in_count].addr = cmd->pdata; in_sgs[in_count].length = cmd->datalen; in_count++; break; default: printf("Unsupported SCSI command %#02x\n", cmd->cmd[0]); return -ENOTSUPP; } } for (i = 0; i < out_count; i++) sgs[i] = &out_sgs[i]; for (i = 0; i < in_count; i++) sgs[out_count + i] = &in_sgs[i]; /* Add the buffers to the request virtqueue */ len = virtqueue_add(vq, sgs, out_count, in_count); if (len < 0) { printf("Failed to add buffer to virtqueue: %d\n", len); return len; } virtqueue_kick(vq); log_debug("wait..."); while (virtqueue_get_buf(vq, &dummy_len) != v_cmd) ; log_debug("done\n"); /* Process the response */ ret = 0; if (v_cmd->resp.response) { if (v_cmd->resp.response == VIRTIO_SCSI_S_BAD_TARGET) { /* * This is an expected result when scanning for a * non-existent device. Handle it silently. */ ret = -ENODEV; } else { log_err("virtio-scsi: response error: %#x\n", v_cmd->resp.response); ret = -EIO; } } else { ret = v_cmd->resp.status; if (ret) log_debug("status %x\n", v_cmd->resp.status); cmd->sensedatalen = min_t(u32, SENSE_BUF_LEN, v_cmd->resp.sense_len); if (cmd->sensedatalen) memcpy(cmd->sense_buf, v_cmd->resp.sense, cmd->sensedatalen); } return ret; } static int virtio_scsi_probe(struct udevice *dev) { struct virtio_scsi_priv *priv = dev_get_priv(dev); struct virtio_scsi_config config; struct scsi_plat *uc_plat; int ret; /* read the device-specific configuration space */ virtio_cread_bytes(dev, 0, &config, sizeof(config)); priv->max_target = config.max_target; /* U-Boot only supports up to 8 LUNs */ priv->max_lun = min(config.max_lun, 7u); log_debug("virtio-scsi: max_target=%d, max_lun=%d, sense_size=%d, cdb_size=%d\n", priv->max_target, priv->max_lun, config.sense_size, config.cdb_size); /* allocate the virtqueues */ ret = virtio_find_vqs(dev, QUEUE_COUNT, priv->vqs); if (ret) { printf("Failed to find vqs\n"); return -ENOENT; } uc_plat = dev_get_uclass_plat(dev); uc_plat->max_lun = priv->max_lun; uc_plat->max_id = priv->max_target; return 0; } static int virtio_scsi_bind(struct udevice *dev) { struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent); /* Indicate what driver features we support */ virtio_driver_features_init(uc_priv, features, ARRAY_SIZE(features), NULL, 0); return 0; } static const struct scsi_ops virtio_scsi_ops = { .exec = virtio_scsi_exec, }; static const struct udevice_id virtio_scsi_ids[] = { { .compatible = "virtio,scsi" }, { } }; U_BOOT_DRIVER(virtio_scsi) = { .name = VIRTIO_SCSI_DRV_NAME, .id = UCLASS_SCSI, .of_match = virtio_scsi_ids, .priv_auto = sizeof(struct virtio_scsi_priv), .ops = &virtio_scsi_ops, .probe = virtio_scsi_probe, .remove = virtio_reset, .bind = virtio_scsi_bind, .flags = DM_FLAG_ACTIVE_DMA, }; |