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 | |
| 8 | #include "biosvar.h" // GET_GLOBAL |
| 9 | #include "util.h" // htonl |
| 10 | #include "disk.h" // struct disk_op_s |
| 11 | #include "blockcmd.h" // struct cdb_request_sense |
| 12 | #include "ata.h" // atapi_cmd_data |
Gerd Hoffmann | d52fdf6 | 2010-11-29 09:42:13 +0100 | [diff] [blame] | 13 | #include "ahci.h" // atapi_cmd_data |
Kevin O'Connor | 7149fc8 | 2010-02-17 23:24:42 -0500 | [diff] [blame] | 14 | #include "usb-msc.h" // usb_cmd_data |
Kevin O'Connor | 76977b2 | 2010-02-17 01:01:32 -0500 | [diff] [blame] | 15 | |
Kevin O'Connor | 7149fc8 | 2010-02-17 23:24:42 -0500 | [diff] [blame] | 16 | // Route command to low-level handler. |
Kevin O'Connor | 76977b2 | 2010-02-17 01:01:32 -0500 | [diff] [blame] | 17 | static int |
| 18 | cdb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize) |
| 19 | { |
| 20 | u8 type = GET_GLOBAL(op->drive_g->type); |
| 21 | switch (type) { |
| 22 | case DTYPE_ATAPI: |
| 23 | return atapi_cmd_data(op, cdbcmd, blocksize); |
Kevin O'Connor | 7149fc8 | 2010-02-17 23:24:42 -0500 | [diff] [blame] | 24 | case DTYPE_USB: |
| 25 | return usb_cmd_data(op, cdbcmd, blocksize); |
Gerd Hoffmann | d52fdf6 | 2010-11-29 09:42:13 +0100 | [diff] [blame] | 26 | case DTYPE_AHCI: |
| 27 | return ahci_cmd_data(op, cdbcmd, blocksize); |
Kevin O'Connor | 76977b2 | 2010-02-17 01:01:32 -0500 | [diff] [blame] | 28 | default: |
| 29 | op->count = 0; |
| 30 | return DISK_RET_EPARAM; |
| 31 | } |
| 32 | } |
| 33 | |
Kevin O'Connor | 7149fc8 | 2010-02-17 23:24:42 -0500 | [diff] [blame] | 34 | int |
Paolo Bonzini | 8c976e3 | 2011-11-16 13:02:51 +0100 | [diff] [blame] | 35 | scsi_is_ready(struct disk_op_s *op) |
| 36 | { |
| 37 | dprintf(6, "scsi_is_ready (drive=%p)\n", op->drive_g); |
| 38 | |
| 39 | /* Retry TEST UNIT READY for 5 seconds unless MEDIUM NOT PRESENT is |
| 40 | * reported by the device. If the device reports "IN PROGRESS", |
| 41 | * 30 seconds is added. */ |
| 42 | int in_progress = 0; |
| 43 | u64 end = calc_future_tsc(5000); |
| 44 | for (;;) { |
| 45 | if (check_tsc(end)) { |
| 46 | dprintf(1, "test unit ready failed\n"); |
| 47 | return -1; |
| 48 | } |
| 49 | |
| 50 | int ret = cdb_test_unit_ready(op); |
| 51 | if (!ret) |
| 52 | // Success |
| 53 | break; |
| 54 | |
| 55 | struct cdbres_request_sense sense; |
| 56 | ret = cdb_get_sense(op, &sense); |
| 57 | if (ret) |
| 58 | // Error - retry. |
| 59 | continue; |
| 60 | |
| 61 | // Sense succeeded. |
| 62 | if (sense.asc == 0x3a) { /* MEDIUM NOT PRESENT */ |
| 63 | dprintf(1, "Device reports MEDIUM NOT PRESENT\n"); |
| 64 | return -1; |
| 65 | } |
| 66 | |
| 67 | if (sense.asc == 0x04 && sense.ascq == 0x01 && !in_progress) { |
| 68 | /* IN PROGRESS OF BECOMING READY */ |
| 69 | printf("Waiting for device to detect medium... "); |
| 70 | /* Allow 30 seconds more */ |
| 71 | end = calc_future_tsc(30000); |
| 72 | in_progress = 1; |
| 73 | } |
| 74 | } |
| 75 | return 0; |
| 76 | } |
| 77 | |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame^] | 78 | // Validate drive, find block size / sector count, and register drive. |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 79 | int |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame^] | 80 | scsi_init_drive(struct drive_s *drive, const char *s, int prio) |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 81 | { |
| 82 | if (!CONFIG_USB_MSC) |
| 83 | return 0; |
| 84 | |
| 85 | struct disk_op_s dop; |
| 86 | memset(&dop, 0, sizeof(dop)); |
| 87 | dop.drive_g = drive; |
| 88 | struct cdbres_inquiry data; |
| 89 | int ret = cdb_get_inquiry(&dop, &data); |
| 90 | if (ret) |
| 91 | return ret; |
| 92 | char vendor[sizeof(data.vendor)+1], product[sizeof(data.product)+1]; |
| 93 | char rev[sizeof(data.rev)+1]; |
| 94 | strtcpy(vendor, data.vendor, sizeof(vendor)); |
| 95 | nullTrailingSpace(vendor); |
| 96 | strtcpy(product, data.product, sizeof(product)); |
| 97 | nullTrailingSpace(product); |
| 98 | strtcpy(rev, data.rev, sizeof(rev)); |
| 99 | nullTrailingSpace(rev); |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame^] | 100 | int pdt = data.pdt & 0x1f; |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 101 | int removable = !!(data.removable & 0x80); |
| 102 | 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^] | 103 | , s, vendor, product, rev, pdt, removable); |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 104 | drive->removable = removable; |
| 105 | |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame^] | 106 | if (pdt == SCSI_TYPE_CDROM) { |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 107 | drive->blksize = CDROM_SECTOR_SIZE; |
| 108 | drive->sectors = (u64)-1; |
| 109 | |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame^] | 110 | char *desc = znprintf(MAXDESCSIZE, "DVD/CD [%s Drive %s %s %s]" |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 111 | , s, vendor, product, rev); |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame^] | 112 | boot_add_cd(drive, desc, prio); |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 113 | return 0; |
| 114 | } |
| 115 | |
| 116 | ret = scsi_is_ready(&dop); |
| 117 | if (ret) { |
| 118 | dprintf(1, "scsi_is_ready returned %d\n", ret); |
| 119 | return ret; |
| 120 | } |
| 121 | |
| 122 | struct cdbres_read_capacity capdata; |
| 123 | ret = cdb_read_capacity(&dop, &capdata); |
| 124 | if (ret) |
| 125 | return ret; |
| 126 | |
| 127 | // READ CAPACITY returns the address of the last block. |
| 128 | // We do not bother with READ CAPACITY(16) because BIOS does not support |
| 129 | // 64-bit LBA anyway. |
| 130 | drive->blksize = ntohl(capdata.blksize); |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame^] | 131 | if (drive->blksize != DISK_SECTOR_SIZE) { |
| 132 | dprintf(1, "%s: unsupported block size %d\n", s, drive->blksize); |
| 133 | return -1; |
| 134 | } |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 135 | drive->sectors = (u64)ntohl(capdata.sectors) + 1; |
| 136 | dprintf(1, "%s blksize=%d sectors=%d\n" |
| 137 | , s, drive->blksize, (unsigned)drive->sectors); |
| 138 | |
Paolo Bonzini | 000e084 | 2011-11-16 13:02:54 +0100 | [diff] [blame] | 139 | struct cdbres_mode_sense_geom geomdata; |
| 140 | ret = cdb_mode_sense_geom(&dop, &geomdata); |
| 141 | if (ret == 0) { |
| 142 | u32 cylinders; |
| 143 | cylinders = geomdata.cyl[0] << 16; |
| 144 | cylinders |= geomdata.cyl[1] << 8; |
| 145 | cylinders |= geomdata.cyl[2]; |
| 146 | if (cylinders && geomdata.heads && |
| 147 | drive->sectors <= 0xFFFFFFFFULL && |
| 148 | ((u32)drive->sectors % (geomdata.heads * cylinders) == 0)) { |
| 149 | drive->pchs.cylinders = cylinders; |
| 150 | drive->pchs.heads = geomdata.heads; |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame^] | 151 | drive->pchs.spt = (u32)drive->sectors / (geomdata.heads * cylinders); |
Paolo Bonzini | 000e084 | 2011-11-16 13:02:54 +0100 | [diff] [blame] | 152 | } |
| 153 | } |
| 154 | |
Kevin O'Connor | 279dcb1 | 2012-02-18 11:02:27 -0500 | [diff] [blame^] | 155 | char *desc = znprintf(MAXDESCSIZE, "%s Drive %s %s %s" |
| 156 | , s, vendor, product, rev); |
| 157 | boot_add_hd(drive, desc, prio); |
Paolo Bonzini | ded04a3 | 2011-11-17 10:23:02 +0100 | [diff] [blame] | 158 | return 0; |
| 159 | } |
| 160 | |
Paolo Bonzini | 8c976e3 | 2011-11-16 13:02:51 +0100 | [diff] [blame] | 161 | int |
Kevin O'Connor | 7149fc8 | 2010-02-17 23:24:42 -0500 | [diff] [blame] | 162 | cdb_get_inquiry(struct disk_op_s *op, struct cdbres_inquiry *data) |
| 163 | { |
| 164 | struct cdb_request_sense cmd; |
| 165 | memset(&cmd, 0, sizeof(cmd)); |
| 166 | cmd.command = CDB_CMD_INQUIRY; |
| 167 | cmd.length = sizeof(*data); |
| 168 | op->count = 1; |
| 169 | op->buf_fl = data; |
| 170 | return cdb_cmd_data(op, &cmd, sizeof(*data)); |
| 171 | } |
| 172 | |
Kevin O'Connor | 76977b2 | 2010-02-17 01:01:32 -0500 | [diff] [blame] | 173 | // Request SENSE |
| 174 | int |
| 175 | cdb_get_sense(struct disk_op_s *op, struct cdbres_request_sense *data) |
| 176 | { |
| 177 | struct cdb_request_sense cmd; |
| 178 | memset(&cmd, 0, sizeof(cmd)); |
| 179 | cmd.command = CDB_CMD_REQUEST_SENSE; |
| 180 | cmd.length = sizeof(*data); |
| 181 | op->count = 1; |
| 182 | op->buf_fl = data; |
| 183 | return cdb_cmd_data(op, &cmd, sizeof(*data)); |
| 184 | } |
| 185 | |
Paolo Bonzini | 0082374 | 2011-11-16 13:02:42 +0100 | [diff] [blame] | 186 | // Test unit ready |
| 187 | int |
| 188 | cdb_test_unit_ready(struct disk_op_s *op) |
| 189 | { |
| 190 | struct cdb_request_sense cmd; |
| 191 | memset(&cmd, 0, sizeof(cmd)); |
| 192 | cmd.command = CDB_CMD_TEST_UNIT_READY; |
| 193 | op->count = 0; |
| 194 | op->buf_fl = NULL; |
| 195 | return cdb_cmd_data(op, &cmd, 0); |
| 196 | } |
| 197 | |
Kevin O'Connor | 76977b2 | 2010-02-17 01:01:32 -0500 | [diff] [blame] | 198 | // Request capacity |
| 199 | int |
| 200 | cdb_read_capacity(struct disk_op_s *op, struct cdbres_read_capacity *data) |
| 201 | { |
| 202 | struct cdb_read_capacity cmd; |
| 203 | memset(&cmd, 0, sizeof(cmd)); |
| 204 | cmd.command = CDB_CMD_READ_CAPACITY; |
| 205 | op->count = 1; |
| 206 | op->buf_fl = data; |
| 207 | return cdb_cmd_data(op, &cmd, sizeof(*data)); |
| 208 | } |
| 209 | |
Paolo Bonzini | 000e084 | 2011-11-16 13:02:54 +0100 | [diff] [blame] | 210 | // Mode sense, geometry page. |
| 211 | int |
| 212 | cdb_mode_sense_geom(struct disk_op_s *op, struct cdbres_mode_sense_geom *data) |
| 213 | { |
| 214 | struct cdb_mode_sense cmd; |
| 215 | memset(&cmd, 0, sizeof(cmd)); |
| 216 | cmd.command = CDB_CMD_MODE_SENSE; |
| 217 | cmd.flags = 8; /* DBD */ |
| 218 | cmd.page = MODE_PAGE_HD_GEOMETRY; |
| 219 | cmd.count = htons(sizeof(*data)); |
| 220 | op->count = 1; |
| 221 | op->buf_fl = data; |
| 222 | return cdb_cmd_data(op, &cmd, sizeof(*data)); |
| 223 | } |
| 224 | |
Kevin O'Connor | 76977b2 | 2010-02-17 01:01:32 -0500 | [diff] [blame] | 225 | // Read sectors. |
| 226 | int |
| 227 | cdb_read(struct disk_op_s *op) |
| 228 | { |
| 229 | struct cdb_rwdata_10 cmd; |
| 230 | memset(&cmd, 0, sizeof(cmd)); |
| 231 | cmd.command = CDB_CMD_READ_10; |
| 232 | cmd.lba = htonl(op->lba); |
| 233 | cmd.count = htons(op->count); |
Kevin O'Connor | 7149fc8 | 2010-02-17 23:24:42 -0500 | [diff] [blame] | 234 | return cdb_cmd_data(op, &cmd, GET_GLOBAL(op->drive_g->blksize)); |
Kevin O'Connor | 76977b2 | 2010-02-17 01:01:32 -0500 | [diff] [blame] | 235 | } |
Paolo Bonzini | ddb8ceb | 2011-11-16 13:02:47 +0100 | [diff] [blame] | 236 | |
| 237 | // Write sectors. |
| 238 | int |
| 239 | cdb_write(struct disk_op_s *op) |
| 240 | { |
| 241 | struct cdb_rwdata_10 cmd; |
| 242 | memset(&cmd, 0, sizeof(cmd)); |
| 243 | cmd.command = CDB_CMD_WRITE_10; |
| 244 | cmd.lba = htonl(op->lba); |
| 245 | cmd.count = htons(op->count); |
| 246 | return cdb_cmd_data(op, &cmd, GET_GLOBAL(op->drive_g->blksize)); |
| 247 | } |