Kevin O'Connor | 76977b2 | 2010-02-17 01:01:32 -0500 | [diff] [blame] | 1 | // Support for several common scsi like command data block requests |
| 2 | // |
| 3 | // Copyright (C) 2010 Kevin O'Connor <kevin@koconnor.net> |
| 4 | // Copyright (C) 2002 MandrakeSoft S.A. |
| 5 | // |
| 6 | // This file may be distributed under the terms of the GNU LGPLv3 license. |
| 7 | |
Kevin O'Connor | 1902c94 | 2013-10-26 11:48:06 -0400 | [diff] [blame] | 8 | #include "biosvar.h" // GET_GLOBALFLAT |
Kevin O'Connor | 135f3f6 | 2013-09-14 23:57:26 -0400 | [diff] [blame] | 9 | #include "block.h" // struct disk_op_s |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 10 | #include "blockcmd.h" // struct cdb_request_sense |
Kevin O'Connor | b306459 | 2012-08-14 21:20:10 -0400 | [diff] [blame] | 11 | #include "byteorder.h" // be32_to_cpu |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 12 | #include "output.h" // dprintf |
Kevin O'Connor | 135f3f6 | 2013-09-14 23:57:26 -0400 | [diff] [blame] | 13 | #include "std/disk.h" // DISK_RET_EPARAM |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 14 | #include "string.h" // memset |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 15 | #include "util.h" // timer_calc |
Kevin O'Connor | 76977b2 | 2010-02-17 01:01:32 -0500 | [diff] [blame] | 16 | |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 17 | |
| 18 | /**************************************************************** |
| 19 | * Low level command requests |
| 20 | ****************************************************************/ |
| 21 | |
Kevin O'Connor | ef102af | 2014-12-29 09:58:48 -0500 | [diff] [blame] | 22 | static int |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 23 | cdb_get_inquiry(struct disk_op_s *op, struct cdbres_inquiry *data) |
| 24 | { |
| 25 | struct cdb_request_sense cmd; |
| 26 | memset(&cmd, 0, sizeof(cmd)); |
| 27 | cmd.command = CDB_CMD_INQUIRY; |
| 28 | cmd.length = sizeof(*data); |
Kevin O'Connor | 4dbe829 | 2015-07-07 09:53:54 -0400 | [diff] [blame] | 29 | op->command = CMD_SCSI; |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 30 | op->count = 1; |
| 31 | op->buf_fl = data; |
Kevin O'Connor | 4dbe829 | 2015-07-07 09:53:54 -0400 | [diff] [blame] | 32 | op->cdbcmd = &cmd; |
| 33 | op->blocksize = sizeof(*data); |
| 34 | return process_op(op); |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 35 | } |
| 36 | |
| 37 | // Request SENSE |
Kevin O'Connor | ef102af | 2014-12-29 09:58:48 -0500 | [diff] [blame] | 38 | static int |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 39 | cdb_get_sense(struct disk_op_s *op, struct cdbres_request_sense *data) |
| 40 | { |
| 41 | struct cdb_request_sense cmd; |
| 42 | memset(&cmd, 0, sizeof(cmd)); |
| 43 | cmd.command = CDB_CMD_REQUEST_SENSE; |
| 44 | cmd.length = sizeof(*data); |
Kevin O'Connor | 4dbe829 | 2015-07-07 09:53:54 -0400 | [diff] [blame] | 45 | op->command = CMD_SCSI; |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 46 | op->count = 1; |
| 47 | op->buf_fl = data; |
Kevin O'Connor | 4dbe829 | 2015-07-07 09:53:54 -0400 | [diff] [blame] | 48 | op->cdbcmd = &cmd; |
| 49 | op->blocksize = sizeof(*data); |
| 50 | return process_op(op); |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 51 | } |
| 52 | |
| 53 | // Test unit ready |
Kevin O'Connor | ef102af | 2014-12-29 09:58:48 -0500 | [diff] [blame] | 54 | static int |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 55 | cdb_test_unit_ready(struct disk_op_s *op) |
| 56 | { |
| 57 | struct cdb_request_sense cmd; |
| 58 | memset(&cmd, 0, sizeof(cmd)); |
| 59 | cmd.command = CDB_CMD_TEST_UNIT_READY; |
Kevin O'Connor | 4dbe829 | 2015-07-07 09:53:54 -0400 | [diff] [blame] | 60 | op->command = CMD_SCSI; |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 61 | op->count = 0; |
| 62 | op->buf_fl = NULL; |
Kevin O'Connor | 4dbe829 | 2015-07-07 09:53:54 -0400 | [diff] [blame] | 63 | op->cdbcmd = &cmd; |
| 64 | op->blocksize = 0; |
| 65 | return process_op(op); |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 66 | } |
| 67 | |
| 68 | // Request capacity |
Kevin O'Connor | ef102af | 2014-12-29 09:58:48 -0500 | [diff] [blame] | 69 | static int |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 70 | cdb_read_capacity(struct disk_op_s *op, struct cdbres_read_capacity *data) |
| 71 | { |
| 72 | struct cdb_read_capacity cmd; |
| 73 | memset(&cmd, 0, sizeof(cmd)); |
| 74 | cmd.command = CDB_CMD_READ_CAPACITY; |
Kevin O'Connor | 4dbe829 | 2015-07-07 09:53:54 -0400 | [diff] [blame] | 75 | op->command = CMD_SCSI; |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 76 | op->count = 1; |
| 77 | op->buf_fl = data; |
Kevin O'Connor | 4dbe829 | 2015-07-07 09:53:54 -0400 | [diff] [blame] | 78 | op->cdbcmd = &cmd; |
| 79 | op->blocksize = sizeof(*data); |
| 80 | return process_op(op); |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 81 | } |
| 82 | |
| 83 | // Mode sense, geometry page. |
Kevin O'Connor | ef102af | 2014-12-29 09:58:48 -0500 | [diff] [blame] | 84 | static int |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 85 | cdb_mode_sense_geom(struct disk_op_s *op, struct cdbres_mode_sense_geom *data) |
| 86 | { |
| 87 | struct cdb_mode_sense cmd; |
| 88 | memset(&cmd, 0, sizeof(cmd)); |
| 89 | cmd.command = CDB_CMD_MODE_SENSE; |
| 90 | cmd.flags = 8; /* DBD */ |
| 91 | cmd.page = MODE_PAGE_HD_GEOMETRY; |
| 92 | cmd.count = cpu_to_be16(sizeof(*data)); |
Kevin O'Connor | 4dbe829 | 2015-07-07 09:53:54 -0400 | [diff] [blame] | 93 | op->command = CMD_SCSI; |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 94 | op->count = 1; |
| 95 | op->buf_fl = data; |
Kevin O'Connor | 4dbe829 | 2015-07-07 09:53:54 -0400 | [diff] [blame] | 96 | op->cdbcmd = &cmd; |
| 97 | op->blocksize = sizeof(*data); |
| 98 | return process_op(op); |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 99 | } |
| 100 | |
Kevin O'Connor | feeb1c2 | 2014-12-29 09:40:46 -0500 | [diff] [blame] | 101 | |
| 102 | /**************************************************************** |
| 103 | * Main SCSI commands |
| 104 | ****************************************************************/ |
| 105 | |
Kevin O'Connor | fb25163 | 2015-07-07 10:53:12 -0400 | [diff] [blame] | 106 | // Create a scsi command request from a disk_op_s request |
Kevin O'Connor | c7fa789 | 2015-07-07 08:35:51 -0400 | [diff] [blame] | 107 | int |
Kevin O'Connor | fb25163 | 2015-07-07 10:53:12 -0400 | [diff] [blame] | 108 | scsi_fill_cmd(struct disk_op_s *op, void *cdbcmd, int maxcdb) |
Kevin O'Connor | f0a22eb | 2014-12-29 09:45:15 -0500 | [diff] [blame] | 109 | { |
| 110 | switch (op->command) { |
| 111 | case CMD_READ: |
Kevin O'Connor | fb25163 | 2015-07-07 10:53:12 -0400 | [diff] [blame] | 112 | case CMD_WRITE: ; |
| 113 | struct cdb_rwdata_10 *cmd = cdbcmd; |
| 114 | memset(cmd, 0, maxcdb); |
| 115 | cmd->command = (op->command == CMD_READ ? CDB_CMD_READ_10 |
| 116 | : CDB_CMD_WRITE_10); |
| 117 | cmd->lba = cpu_to_be32(op->lba); |
| 118 | cmd->count = cpu_to_be16(op->count); |
| 119 | return GET_GLOBALFLAT(op->drive_gf->blksize); |
Kevin O'Connor | 4dbe829 | 2015-07-07 09:53:54 -0400 | [diff] [blame] | 120 | case CMD_SCSI: |
Kevin O'Connor | fb25163 | 2015-07-07 10:53:12 -0400 | [diff] [blame] | 121 | memcpy(cdbcmd, op->cdbcmd, maxcdb); |
| 122 | return op->blocksize; |
Kevin O'Connor | f0a22eb | 2014-12-29 09:45:15 -0500 | [diff] [blame] | 123 | default: |
Kevin O'Connor | fb25163 | 2015-07-07 10:53:12 -0400 | [diff] [blame] | 124 | return -1; |
Kevin O'Connor | f0a22eb | 2014-12-29 09:45:15 -0500 | [diff] [blame] | 125 | } |
| 126 | } |
| 127 | |
Kevin O'Connor | 5dcd1ee | 2015-07-07 14:43:01 -0400 | [diff] [blame] | 128 | // Determine if the command is a request to pull data from the device |
| 129 | int |
| 130 | scsi_is_read(struct disk_op_s *op) |
| 131 | { |
| 132 | return op->command == CMD_READ || (op->command == CMD_SCSI && op->blocksize); |
| 133 | } |
| 134 | |
| 135 | // Check if a SCSI device is ready to receive commands |
Kevin O'Connor | 7149fc8 | 2010-02-17 23:24:42 -0500 | [diff] [blame] | 136 | int |
Paolo Bonzini | 8c976e3 | 2011-11-16 13:02:51 +0100 | [diff] [blame] | 137 | scsi_is_ready(struct disk_op_s *op) |
| 138 | { |
Kevin O'Connor | 1902c94 | 2013-10-26 11:48:06 -0400 | [diff] [blame] | 139 | dprintf(6, "scsi_is_ready (drive=%p)\n", op->drive_gf); |
Paolo Bonzini | 8c976e3 | 2011-11-16 13:02:51 +0100 | [diff] [blame] | 140 | |
| 141 | /* Retry TEST UNIT READY for 5 seconds unless MEDIUM NOT PRESENT is |
| 142 | * reported by the device. If the device reports "IN PROGRESS", |
| 143 | * 30 seconds is added. */ |
| 144 | int in_progress = 0; |
Kevin O'Connor | 018bdd7 | 2013-07-20 18:22:57 -0400 | [diff] [blame] | 145 | u32 end = timer_calc(5000); |
Paolo Bonzini | 8c976e3 | 2011-11-16 13:02:51 +0100 | [diff] [blame] | 146 | for (;;) { |
Kevin O'Connor | 018bdd7 | 2013-07-20 18:22:57 -0400 | [diff] [blame] | 147 | if (timer_check(end)) { |
Paolo Bonzini | 8c976e3 | 2011-11-16 13:02:51 +0100 | [diff] [blame] | 148 | dprintf(1, "test unit ready failed\n"); |
| 149 | return -1; |
| 150 | } |
| 151 | |
| 152 | int ret = cdb_test_unit_ready(op); |
| 153 | if (!ret) |
| 154 | // Success |
| 155 | break; |
| 156 | |
| 157 | struct cdbres_request_sense sense; |
| 158 | ret = cdb_get_sense(op, &sense); |
| 159 | if (ret) |
| 160 | // Error - retry. |
| 161 | continue; |
| 162 | |
| 163 | // Sense succeeded. |
| 164 | if (sense.asc == 0x3a) { /* MEDIUM NOT PRESENT */ |
| 165 | dprintf(1, "Device reports MEDIUM NOT PRESENT\n"); |
| 166 | return -1; |
| 167 | } |
| 168 | |
| 169 | if (sense.asc == 0x04 && sense.ascq == 0x01 && !in_progress) { |
| 170 | /* IN PROGRESS OF BECOMING READY */ |
Kevin O'Connor | 82f3279 | 2015-12-23 15:37:51 -0500 | [diff] [blame^] | 171 | dprintf(1, "Waiting for device to detect medium... "); |
Paolo Bonzini | 8c976e3 | 2011-11-16 13:02:51 +0100 | [diff] [blame] | 172 | /* Allow 30 seconds more */ |
Kevin O'Connor | 018bdd7 | 2013-07-20 18:22:57 -0400 | [diff] [blame] | 173 | end = timer_calc(30000); |
Paolo Bonzini | 8c976e3 | 2011-11-16 13:02:51 +0100 | [diff] [blame] | 174 | in_progress = 1; |
| 175 | } |
| 176 | } |
| 177 | return 0; |
| 178 | } |
| 179 | |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame] | 180 | // Validate drive, find block size / sector count, and register drive. |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 181 | int |
Kevin O'Connor | d83c87b | 2013-01-21 01:14:12 -0500 | [diff] [blame] | 182 | scsi_drive_setup(struct drive_s *drive, const char *s, int prio) |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 183 | { |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 184 | struct disk_op_s dop; |
| 185 | memset(&dop, 0, sizeof(dop)); |
Kevin O'Connor | 1902c94 | 2013-10-26 11:48:06 -0400 | [diff] [blame] | 186 | dop.drive_gf = drive; |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 187 | struct cdbres_inquiry data; |
| 188 | int ret = cdb_get_inquiry(&dop, &data); |
| 189 | if (ret) |
| 190 | return ret; |
| 191 | char vendor[sizeof(data.vendor)+1], product[sizeof(data.product)+1]; |
| 192 | char rev[sizeof(data.rev)+1]; |
| 193 | strtcpy(vendor, data.vendor, sizeof(vendor)); |
| 194 | nullTrailingSpace(vendor); |
| 195 | strtcpy(product, data.product, sizeof(product)); |
| 196 | nullTrailingSpace(product); |
| 197 | strtcpy(rev, data.rev, sizeof(rev)); |
| 198 | nullTrailingSpace(rev); |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame] | 199 | int pdt = data.pdt & 0x1f; |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 200 | int removable = !!(data.removable & 0x80); |
| 201 | dprintf(1, "%s vendor='%s' product='%s' rev='%s' type=%d removable=%d\n" |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame] | 202 | , s, vendor, product, rev, pdt, removable); |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 203 | drive->removable = removable; |
| 204 | |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame] | 205 | if (pdt == SCSI_TYPE_CDROM) { |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 206 | drive->blksize = CDROM_SECTOR_SIZE; |
| 207 | drive->sectors = (u64)-1; |
| 208 | |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame] | 209 | char *desc = znprintf(MAXDESCSIZE, "DVD/CD [%s Drive %s %s %s]" |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 210 | , s, vendor, product, rev); |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame] | 211 | boot_add_cd(drive, desc, prio); |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 212 | return 0; |
| 213 | } |
| 214 | |
| 215 | ret = scsi_is_ready(&dop); |
| 216 | if (ret) { |
| 217 | dprintf(1, "scsi_is_ready returned %d\n", ret); |
| 218 | return ret; |
| 219 | } |
| 220 | |
| 221 | struct cdbres_read_capacity capdata; |
| 222 | ret = cdb_read_capacity(&dop, &capdata); |
| 223 | if (ret) |
| 224 | return ret; |
| 225 | |
| 226 | // READ CAPACITY returns the address of the last block. |
| 227 | // We do not bother with READ CAPACITY(16) because BIOS does not support |
| 228 | // 64-bit LBA anyway. |
Kevin O'Connor | b306459 | 2012-08-14 21:20:10 -0400 | [diff] [blame] | 229 | drive->blksize = be32_to_cpu(capdata.blksize); |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame] | 230 | if (drive->blksize != DISK_SECTOR_SIZE) { |
| 231 | dprintf(1, "%s: unsupported block size %d\n", s, drive->blksize); |
| 232 | return -1; |
| 233 | } |
Kevin O'Connor | b306459 | 2012-08-14 21:20:10 -0400 | [diff] [blame] | 234 | drive->sectors = (u64)be32_to_cpu(capdata.sectors) + 1; |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 235 | dprintf(1, "%s blksize=%d sectors=%d\n" |
| 236 | , s, drive->blksize, (unsigned)drive->sectors); |
| 237 | |
Paolo Bonzini | cb72171 | 2012-03-05 12:29:12 +0100 | [diff] [blame] | 238 | // We do not recover from USB stalls, so try to be safe and avoid |
| 239 | // sending the command if the (obsolete, but still provided by QEMU) |
| 240 | // fixed disk geometry page may not be supported. |
| 241 | // |
| 242 | // We could also send the command only to small disks (e.g. <504MiB) |
| 243 | // but some old USB keys only support a very small subset of SCSI which |
| 244 | // does not even include the MODE SENSE command! |
| 245 | // |
Kevin O'Connor | 897fb11 | 2013-02-07 23:32:48 -0500 | [diff] [blame] | 246 | if (CONFIG_QEMU_HARDWARE && memcmp(vendor, "QEMU", 5) == 0) { |
Paolo Bonzini | cb72171 | 2012-03-05 12:29:12 +0100 | [diff] [blame] | 247 | struct cdbres_mode_sense_geom geomdata; |
| 248 | ret = cdb_mode_sense_geom(&dop, &geomdata); |
| 249 | if (ret == 0) { |
| 250 | u32 cylinders; |
| 251 | cylinders = geomdata.cyl[0] << 16; |
| 252 | cylinders |= geomdata.cyl[1] << 8; |
| 253 | cylinders |= geomdata.cyl[2]; |
| 254 | if (cylinders && geomdata.heads && |
| 255 | drive->sectors <= 0xFFFFFFFFULL && |
| 256 | ((u32)drive->sectors % (geomdata.heads * cylinders) == 0)) { |
Kevin O'Connor | 8ab9a34 | 2013-09-28 23:34:49 -0400 | [diff] [blame] | 257 | drive->pchs.cylinder = cylinders; |
| 258 | drive->pchs.head = geomdata.heads; |
| 259 | drive->pchs.sector = (u32)drive->sectors / (geomdata.heads * cylinders); |
Paolo Bonzini | cb72171 | 2012-03-05 12:29:12 +0100 | [diff] [blame] | 260 | } |
Paolo Bonzini | 000e084 | 2011-11-16 13:02:54 +0100 | [diff] [blame] | 261 | } |
| 262 | } |
| 263 | |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame] | 264 | char *desc = znprintf(MAXDESCSIZE, "%s Drive %s %s %s" |
| 265 | , s, vendor, product, rev); |
| 266 | boot_add_hd(drive, desc, prio); |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 267 | return 0; |
| 268 | } |