bb16cf71df92da63a3d6b765ce67361875a30622
[SCSI2SD.git] / software / SCSI2SD / src / mode.c
1 //      Copyright (C) 2013 Michael McMaster <michael@codesrc.com>\r
2 //  Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com>\r
3 //  Copyright (C) 2019 Landon Rodgers <g.landon.rodgers@gmail.com>\r
4 //\r
5 //      This file is part of SCSI2SD.\r
6 //\r
7 //      SCSI2SD is free software: you can redistribute it and/or modify\r
8 //      it under the terms of the GNU General Public License as published by\r
9 //      the Free Software Foundation, either version 3 of the License, or\r
10 //      (at your option) any later version.\r
11 //\r
12 //      SCSI2SD is distributed in the hope that it will be useful,\r
13 //      but WITHOUT ANY WARRANTY; without even the implied warranty of\r
14 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
15 //      GNU General Public License for more details.\r
16 //\r
17 //      You should have received a copy of the GNU General Public License\r
18 //      along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.\r
19 #include "device.h"\r
20 #include "scsi.h"\r
21 #include "mode.h"\r
22 #include "disk.h"\r
23 #include "inquiry.h"\r
24 \r
25 #include <string.h>\r
26 \r
27 // "Vendor" defined page which was included by Seagate, and required for\r
28 // Amiga 500 using DKB SpitFire controller.\r
29 static const uint8 OperatingPage[] =\r
30 {\r
31 0x00, // Page code\r
32 0x02, // Page length\r
33 \r
34 // Bit 4 = unit attension (0 = on, 1 = off).\r
35 // Bit 7 = usage bit, EEPROM life exceeded warning = 1.\r
36 0x80, \r
37 \r
38 // Bit 7 = reserved.\r
39 // Bits 0:6: Device type qualifier, as per Inquiry data\r
40 0x00\r
41 };\r
42 \r
43 static const uint8 ReadWriteErrorRecoveryPage[] =\r
44 {\r
45 0x01, // Page code\r
46 0x0A, // Page length\r
47 \r
48 // VMS 5.5-2 is very particular regarding the mode page values.\r
49 // The required values for a SCSI2/NoTCQ device are:\r
50 // AWRE=0 ARRE=0 TB=1 RC=0 EER=? PER=1 DTE=1 DCR=?\r
51 // See ftp://www.digiater.nl/openvms/decus/vms94b/net94b/scsi_params_dkdriver.txt\r
52 // X-Newsgroups: comp.os.vms\r
53 // Subject: Re: VMS 6.1 vs. Seagate Disk Drives\r
54 // Message-Id: <32g87h$8q@nntpd.lkg.dec.com>\r
55 // From: weber@evms.enet.dec.com (Ralph O. Weber -- OpenVMS AXP)\r
56 // Date: 12 Aug 1994 16:32:49 GMT\r
57 0x26,\r
58 \r
59 0x00, // Don't try recovery algorithm during reads\r
60 0x00, // Correction span 0\r
61 0x00, // Head offset count 0,\r
62 0x00, // Data strobe offset count 0,\r
63 0x00, // Reserved\r
64 0x00, // Don't try recovery algorithm during writes\r
65 0x00, // Reserved\r
66 0x00, 0x00 // Recovery time limit 0 (use default)*/\r
67 };\r
68 \r
69 static const uint8 ReadWriteErrorRecoveryPage_SCSI1[] =\r
70 {\r
71 0x01, // Page code\r
72 0x06, // Page length\r
73 0x26,\r
74 0x00, // Don't try recovery algorithm during reads\r
75 0x00, // Correction span 0\r
76 0x00, // Head offset count 0,\r
77 0x00, // Data strobe offset count 0,\r
78 0xFF // Reserved\r
79 };\r
80 \r
81 static const uint8 DisconnectReconnectPage[] =\r
82 {\r
83 0x02, // Page code\r
84 0x0E, // Page length\r
85 0, // Buffer full ratio\r
86 0, // Buffer empty ratio\r
87 0x00, 10, // Bus inactivity limit, 100us increments. Allow 1ms.\r
88 0x00, 0x00, // Disconnect time limit\r
89 0x00, 0x00, // Connect time limit\r
90 0x00, 0x00, // Maximum burst size\r
91 0x00 ,// DTDC. Not used.\r
92 0x00, 0x00, 0x00 // Reserved\r
93 };\r
94 \r
95 static const uint8 DisconnectReconnectPage_SCSI1[] =\r
96 {\r
97 0x02, // Page code\r
98 0x0A, // Page length\r
99 0, // Buffer full ratio\r
100 0, // Buffer empty ratio\r
101 0x00, 10, // Bus inactivity limit, 100us increments. Allow 1ms.\r
102 0x00, 0x00, // Disconnect time limit\r
103 0x00, 0x00, // Connect time limit\r
104 0x00, 0x00 // Maximum burst size\r
105 };\r
106 \r
107 static const uint8 FormatDevicePage[] =\r
108 {\r
109 0x03 | 0x80, // Page code | PS (persist) bit.\r
110 0x16, // Page length\r
111 0x00, 0x00, // Single zone\r
112 0x00, 0x00, // No alternate sectors\r
113 0x00, 0x00, // No alternate tracks\r
114 0x00, 0x00, // No alternate tracks per lun\r
115 0x00, 0x00, // Sectors per track, configurable\r
116 0xFF, 0xFF, // Data bytes per physical sector. Configurable.\r
117 0x00, 0x01, // Interleave\r
118 0x00, 0x00, // Track skew factor\r
119 0x00, 0x00, // Cylinder skew factor\r
120 0xC0, // SSEC(set) HSEC(set) RMB SURF\r
121 0x00, 0x00, 0x00 // Reserved\r
122 };\r
123 \r
124 static const uint8 RigidDiskDriveGeometry[] =\r
125 {\r
126 0x04, // Page code\r
127 0x16, // Page length\r
128 0xFF, 0xFF, 0xFF, // Number of cylinders\r
129 0x00, // Number of heads (replaced by configured value)\r
130 0xFF, 0xFF, 0xFF, // Starting cylinder-write precompensation\r
131 0xFF, 0xFF, 0xFF, // Starting cylinder-reduced write current\r
132 0x00, 0x1, // Drive step rate (units of 100ns)\r
133 0x00, 0x00, 0x00, // Landing zone cylinder\r
134 0x00, // RPL\r
135 0x00, // Rotational offset\r
136 0x00, // Reserved\r
137 5400 >> 8, 5400 & 0xFF, // Medium rotation rate (RPM)\r
138 0x00, 0x00 // Reserved\r
139 };\r
140 \r
141 static const uint8 FlexibleDiskDriveGeometry[] =\r
142 {\r
143 0x05, // Page code\r
144 0x1E, // Page length\r
145 0x01, 0xF4, // Transfer Rate (500kbits)\r
146 0x01, // heads\r
147 18, // sectors per track\r
148 0x20,0x00, // bytes per sector\r
149 0x00, 80, // Cylinders\r
150 0x00, 0x80, // Write-precomp\r
151 0x00, 0x80, // reduced current,\r
152 0x00, 0x00, // Drive step rate\r
153 0x00, // pulse width\r
154 0x00, 0x00, // Head settle delay\r
155 0x00, // motor on delay\r
156 0x00,  // motor off delay\r
157 0x00,\r
158 0x00,\r
159 0x00,\r
160 0x00,\r
161 0x00,\r
162 0x00,\r
163 0x00,\r
164 0x00,\r
165 0x00,\r
166 0x00,\r
167 0x00\r
168 };\r
169 \r
170 static const uint8 RigidDiskDriveGeometry_SCSI1[] =\r
171 {\r
172 0x04, // Page code\r
173 0x12, // Page length\r
174 0xFF, 0xFF, 0xFF, // Number of cylinders\r
175 0x00, // Number of heads (replaced by configured value)\r
176 0xFF, 0xFF, 0xFF, // Starting cylinder-write precompensation\r
177 0xFF, 0xFF, 0xFF, // Starting cylinder-reduced write current\r
178 0x00, 0x1, // Drive step rate (units of 100ns)\r
179 0x00, 0x00, 0x00, // Landing zone cylinder\r
180 0x00, // RPL\r
181 0x00, // Rotational offset\r
182 0x00 // Reserved\r
183 };\r
184 \r
185 static const uint8 CachingPage[] =\r
186 {\r
187 0x08, // Page Code\r
188 0x0A, // Page length\r
189 0x01, // Read cache disable\r
190 0x00, // No useful rention policy.\r
191 0x00, 0x00, // Pre-fetch always disabled\r
192 0x00, 0x00, // Minimum pre-fetch\r
193 0x00, 0x00, // Maximum pre-fetch\r
194 0x00, 0x00, // Maximum pre-fetch ceiling\r
195 };\r
196 \r
197 static const uint8 ControlModePage[] =\r
198 {\r
199 0x0A, // Page code\r
200 0x06, // Page length\r
201 0x00, // No logging\r
202 0x01, // Disable tagged queuing\r
203 0x00, // No async event notifications\r
204 0x00, // Reserved\r
205 0x00, 0x00 // AEN holdoff period.\r
206 };\r
207 \r
208 static const uint8_t SequentialDeviceConfigPage[] =\r
209 {\r
210 0x10, // page code\r
211 0x0E, // Page length\r
212 0x00, // CAP, CAF, Active Format\r
213 0x00, // Active partition\r
214 0x00, // Write buffer full ratio\r
215 0x00, // Read buffer empty ratio\r
216 0x00,0x01, // Write delay time, in 100ms units\r
217 0x00, // Default gap size\r
218 0x10, // auto-generation of default eod (end of data)\r
219 0x00,0x00,0x00, // buffer-size at early warning\r
220 0x00, // No data compression\r
221 0x00 // reserved\r
222 };\r
223 \r
224 // Allow Apple 68k Drive Setup to format this drive.\r
225 // Code\r
226 static const uint8 AppleVendorPage[] =\r
227 {\r
228 0x30, // Page code\r
229 28, // Page length\r
230 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\r
231 'A','P','P','L','E',' ','C','O','M','P','U','T','E','R',',',' ','I','N','C','.'\r
232 };\r
233 \r
234 static void pageIn(int pc, int dataIdx, const uint8* pageData, int pageLen)\r
235 {\r
236         memcpy(&scsiDev.data[dataIdx], pageData, pageLen);\r
237 \r
238         if (pc == 0x01) // Mask out (un)changable values\r
239         {\r
240                 memset(&scsiDev.data[dataIdx+2], 0, pageLen - 2);\r
241         }\r
242 }\r
243 \r
244 static int useCustomPages(const TargetConfig* cfg, int pc, int pageCode, int* idx)\r
245 {\r
246         int found = 0;\r
247         int cfgIdx = 0;\r
248         while ((cfgIdx < sizeof(cfg->modePages) - 2) &&\r
249                 (cfg->modePages[cfgIdx + 1] != 0)\r
250                 )\r
251         {\r
252                 int pageSize = cfg->modePages[cfgIdx + 1] + 2;\r
253                 int dataPageCode = cfg->modePages[cfgIdx] & 0x3f;\r
254                 if ((dataPageCode == pageCode) ||\r
255                         (pageCode == 0x3f)\r
256                         )\r
257                 {\r
258                         pageIn(pc, *idx, &cfg->modePages[cfgIdx], pageSize);\r
259                         *idx += pageSize;\r
260                         found = 1;\r
261                         if (pageCode != 0x3f)\r
262                         {\r
263                                 break;\r
264                         }\r
265                 }\r
266                 cfgIdx += pageSize;\r
267         }\r
268         return found;\r
269 }\r
270 \r
271 static void doModeSense(\r
272         int sixByteCmd, int dbd, int pc, int pageCode, int allocLength)\r
273 {\r
274         ////////////// Mode Parameter Header\r
275         ////////////////////////////////////\r
276 \r
277         // Skip the Mode Data Length, we set that last.\r
278         int idx = 1;\r
279         if (!sixByteCmd) ++idx;\r
280 \r
281         uint8_t mediumType = 0;\r
282         uint8_t deviceSpecificParam = 0;\r
283         uint8_t density = 0;\r
284         switch (scsiDev.target->cfg->deviceType)\r
285         {\r
286         case CONFIG_FIXED:\r
287         case CONFIG_REMOVEABLE:\r
288                 mediumType = 0; // We should support various floppy types here!\r
289                 // Contains cache bits (0) and a Write-Protect bit.\r
290                 deviceSpecificParam =\r
291                         (blockDev.state & DISK_WP) ? 0x80 : 0;\r
292                 density = 0; // reserved for direct access\r
293                 break;\r
294 \r
295         case CONFIG_FLOPPY_14MB:\r
296                 mediumType = 0x1E; // 90mm/3.5"\r
297                 deviceSpecificParam =\r
298                         (blockDev.state & DISK_WP) ? 0x80 : 0;\r
299                 density = 0; // reserved for direct access\r
300                 break;\r
301 \r
302         case CONFIG_OPTICAL:\r
303                 mediumType = 0x02; // 120mm CDROM, data only.\r
304                 deviceSpecificParam = 0;\r
305                 density = 0x01; // User data only, 2048bytes per sector.\r
306                 break;\r
307 \r
308         case CONFIG_SEQUENTIAL:\r
309                 mediumType = 0; // reserved\r
310                 deviceSpecificParam =\r
311                         (blockDev.state & DISK_WP) ? 0x80 : 0;\r
312                 density = 0x13; // DAT Data Storage, X3B5/88-185A \r
313                 break;\r
314 \r
315         case CONFIG_MO:\r
316         mediumType = 0x03; // Optical reversible or erasable medium\r
317                 deviceSpecificParam =\r
318                         (blockDev.state & DISK_WP) ? 0x80 : 0;\r
319                 density = 0x00; // Default\r
320                 break;\r
321 \r
322         };\r
323 \r
324         scsiDev.data[idx++] = mediumType;\r
325         scsiDev.data[idx++] = deviceSpecificParam;\r
326 \r
327         if (sixByteCmd)\r
328         {\r
329                 if (dbd)\r
330                 {\r
331                         scsiDev.data[idx++] = 0; // No block descriptor\r
332                 }\r
333                 else\r
334                 {\r
335                         // One block descriptor of length 8 bytes.\r
336                         scsiDev.data[idx++] = 8;\r
337                 }\r
338         }\r
339         else\r
340         {\r
341                 scsiDev.data[idx++] = 0; // Reserved\r
342                 scsiDev.data[idx++] = 0; // Reserved\r
343                 if (dbd)\r
344                 {\r
345                         scsiDev.data[idx++] = 0; // No block descriptor\r
346                         scsiDev.data[idx++] = 0; // No block descriptor\r
347                 }\r
348                 else\r
349                 {\r
350                         // One block descriptor of length 8 bytes.\r
351                         scsiDev.data[idx++] = 0;\r
352                         scsiDev.data[idx++] = 8;\r
353                 }\r
354         }\r
355 \r
356         ////////////// Block Descriptor\r
357         ////////////////////////////////////\r
358         if (!dbd)\r
359         {\r
360                 scsiDev.data[idx++] = density;\r
361                 // Number of blocks\r
362                 // Zero == all remaining blocks shall have the medium\r
363                 // characteristics specified.\r
364                 scsiDev.data[idx++] = 0;\r
365                 scsiDev.data[idx++] = 0;\r
366                 scsiDev.data[idx++] = 0;\r
367 \r
368                 scsiDev.data[idx++] = 0; // reserved\r
369 \r
370                 // Block length\r
371                 uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;\r
372                 scsiDev.data[idx++] = bytesPerSector >> 16;\r
373                 scsiDev.data[idx++] = bytesPerSector >> 8;\r
374                 scsiDev.data[idx++] = bytesPerSector & 0xFF;\r
375         }\r
376 \r
377         int pageFound = 0;\r
378 \r
379         if (scsiDev.target->cfg->modePages[1] != 0)\r
380         {\r
381                 pageFound = useCustomPages(scsiDev.target->cfg, pc, pageCode, &idx);\r
382                 if (pageFound && pageCode != 0x3F) pageCode = 0xFF; // skip rest of logic\r
383         }\r
384 \r
385         if (pageCode == 0x01 || pageCode == 0x3F)\r
386         {\r
387                 pageFound = 1;\r
388                 if ((scsiDev.compatMode >= COMPAT_SCSI2))\r
389                 {\r
390                         pageIn(pc, idx, ReadWriteErrorRecoveryPage, sizeof(ReadWriteErrorRecoveryPage));\r
391                         idx += sizeof(ReadWriteErrorRecoveryPage);\r
392                 }\r
393                 else\r
394                 {\r
395                         pageIn(pc, idx, ReadWriteErrorRecoveryPage_SCSI1, sizeof(ReadWriteErrorRecoveryPage_SCSI1));\r
396                         idx += sizeof(ReadWriteErrorRecoveryPage_SCSI1);\r
397                 }\r
398         }\r
399 \r
400         if (pageCode == 0x02 || pageCode == 0x3F)\r
401         {\r
402                 pageFound = 1;\r
403                 if ((scsiDev.compatMode >= COMPAT_SCSI2))\r
404                 {\r
405                         pageIn(pc, idx, DisconnectReconnectPage, sizeof(DisconnectReconnectPage));\r
406                         idx += sizeof(DisconnectReconnectPage);\r
407                 }\r
408                 else\r
409                 {\r
410                         pageIn(pc, idx, DisconnectReconnectPage_SCSI1, sizeof(DisconnectReconnectPage_SCSI1));\r
411                         idx += sizeof(DisconnectReconnectPage_SCSI1);\r
412                 }\r
413         }\r
414 \r
415         if (pageCode == 0x03 || pageCode == 0x3F)\r
416         {\r
417                 pageFound = 1;\r
418                 pageIn(pc, idx, FormatDevicePage, sizeof(FormatDevicePage));\r
419                 if (pc != 0x01)\r
420                 {\r
421                         uint16_t sectorsPerTrack = scsiDev.target->cfg->sectorsPerTrack;\r
422                         scsiDev.data[idx+10] = sectorsPerTrack >> 8;\r
423                         scsiDev.data[idx+11] = sectorsPerTrack & 0xFF;\r
424 \r
425                         // Fill out the configured bytes-per-sector\r
426                         uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;\r
427                         scsiDev.data[idx+12] = bytesPerSector >> 8;\r
428                         scsiDev.data[idx+13] = bytesPerSector & 0xFF;\r
429                 }\r
430                 else\r
431                 {\r
432                         // Set a mask for the changeable values.\r
433                         scsiDev.data[idx+12] = 0xFF;\r
434                         scsiDev.data[idx+13] = 0xFF;\r
435                 }\r
436 \r
437                 idx += sizeof(FormatDevicePage);\r
438         }\r
439 \r
440         if (pageCode == 0x04 || pageCode == 0x3F)\r
441         {\r
442                 pageFound = 1;\r
443                 if ((scsiDev.compatMode >= COMPAT_SCSI2))\r
444                 {\r
445                         pageIn(pc, idx, RigidDiskDriveGeometry, sizeof(RigidDiskDriveGeometry));\r
446                 }\r
447                 else\r
448                 {\r
449                         pageIn(pc, idx, RigidDiskDriveGeometry_SCSI1, sizeof(RigidDiskDriveGeometry_SCSI1));\r
450                 }\r
451 \r
452                 if (pc != 0x01)\r
453                 {\r
454                         // Need to fill out the number of cylinders.\r
455                         uint32 cyl;\r
456                         uint8 head;\r
457                         uint32 sector;\r
458                         LBA2CHS(\r
459                                 getScsiCapacity(\r
460                                         scsiDev.target->cfg->sdSectorStart,\r
461                                         scsiDev.target->liveCfg.bytesPerSector,\r
462                                         scsiDev.target->cfg->scsiSectors),\r
463                                 &cyl,\r
464                                 &head,\r
465                                 &sector,\r
466                                 scsiDev.target->cfg->headsPerCylinder,\r
467                                 scsiDev.target->cfg->sectorsPerTrack);\r
468 \r
469                         scsiDev.data[idx+2] = cyl >> 16;\r
470                         scsiDev.data[idx+3] = cyl >> 8;\r
471                         scsiDev.data[idx+4] = cyl;\r
472 \r
473                         memcpy(&scsiDev.data[idx+6], &scsiDev.data[idx+2], 3);\r
474                         memcpy(&scsiDev.data[idx+9], &scsiDev.data[idx+2], 3);\r
475 \r
476                         scsiDev.data[idx+5] = scsiDev.target->cfg->headsPerCylinder;\r
477                 }\r
478 \r
479                 if ((scsiDev.compatMode >= COMPAT_SCSI2))\r
480                 {\r
481                         idx += sizeof(RigidDiskDriveGeometry);\r
482                 }\r
483                 else\r
484                 {\r
485                         idx += sizeof(RigidDiskDriveGeometry_SCSI1);\r
486                 }\r
487         }\r
488 \r
489         if ((pageCode == 0x05 || pageCode == 0x3F) &&\r
490                 (scsiDev.target->cfg->deviceType == CONFIG_FLOPPY_14MB))\r
491         {\r
492                 pageFound = 1;\r
493                 pageIn(pc, idx, FlexibleDiskDriveGeometry, sizeof(FlexibleDiskDriveGeometry));\r
494                 idx += sizeof(FlexibleDiskDriveGeometry);\r
495         }\r
496 \r
497         // DON'T output the following pages for SCSI1 hosts. They get upset when\r
498         // we have more data to send than the allocation length provided.\r
499         // (ie. Try not to output any more pages below this comment)\r
500 \r
501 \r
502         if ((scsiDev.compatMode >= COMPAT_SCSI2) &&\r
503                 (pageCode == 0x08 || pageCode == 0x3F))\r
504         {\r
505                 pageFound = 1;\r
506                 pageIn(pc, idx, CachingPage, sizeof(CachingPage));\r
507                 idx += sizeof(CachingPage);\r
508         }\r
509 \r
510         if ((scsiDev.compatMode >= COMPAT_SCSI2)\r
511                 && (pageCode == 0x0A || pageCode == 0x3F))\r
512         {\r
513                 pageFound = 1;\r
514                 pageIn(pc, idx, ControlModePage, sizeof(ControlModePage));\r
515                 idx += sizeof(ControlModePage);\r
516         }\r
517 \r
518         if ((scsiDev.target->cfg->deviceType == CONFIG_SEQUENTIAL) &&\r
519                 (pageCode == 0x10 || pageCode == 0x3F))\r
520         {\r
521                 pageFound = 1;\r
522                 pageIn(\r
523                         pc,\r
524                         idx,\r
525                         SequentialDeviceConfigPage,\r
526                         sizeof(SequentialDeviceConfigPage));\r
527                 idx += sizeof(SequentialDeviceConfigPage);\r
528         }\r
529 \r
530         if ((\r
531                         (scsiDev.target->cfg->quirks == CONFIG_QUIRKS_APPLE)\r
532                 ) &&\r
533                 (pageCode == 0x30 || pageCode == 0x3F))\r
534         {\r
535                 pageFound = 1;\r
536                 pageIn(pc, idx, AppleVendorPage, sizeof(AppleVendorPage));\r
537                 idx += sizeof(AppleVendorPage);\r
538         }\r
539 \r
540         // SCSI 2 standard says page 0 is always last.\r
541         if (pageCode == 0x00 || pageCode == 0x3F)\r
542         {\r
543                 pageFound = 1;\r
544                 pageIn(pc, idx, OperatingPage, sizeof(OperatingPage));\r
545 \r
546                 // Note inverted logic for the flag.\r
547                 scsiDev.data[idx+2] =\r
548                         (scsiDev.boardCfg.flags & CONFIG_ENABLE_UNIT_ATTENTION) ? 0x80 : 0x90;\r
549 \r
550                 scsiDev.data[idx+3] = getDeviceTypeQualifier();\r
551 \r
552                 idx += sizeof(OperatingPage);\r
553         }\r
554 \r
555         if (!pageFound)\r
556         {\r
557                 // Unknown Page Code\r
558                 pageFound = 0;\r
559                 scsiDev.status = CHECK_CONDITION;\r
560                 scsiDev.target->sense.code = ILLEGAL_REQUEST;\r
561                 scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;\r
562                 scsiDev.phase = STATUS;\r
563         }\r
564         else\r
565         {\r
566                 // Go back and fill out the mode data length\r
567                 if (sixByteCmd)\r
568                 {\r
569                         // Cannot currently exceed limits. yay\r
570                         scsiDev.data[0] = idx - 1;\r
571                 }\r
572                 else\r
573                 {\r
574                         scsiDev.data[0] = ((idx - 2) >> 8);\r
575                         scsiDev.data[1] = (idx - 2);\r
576                 }\r
577 \r
578                 scsiDev.dataLen = idx > allocLength ? allocLength : idx;\r
579                 scsiDev.phase = DATA_IN;\r
580         }\r
581 }\r
582 \r
583 \r
584 // Callback after the DATA OUT phase is complete.\r
585 static void doModeSelect(void)\r
586 {\r
587         if (scsiDev.status == GOOD) // skip if we've already encountered an error\r
588         {\r
589                 // scsiDev.dataLen bytes are in scsiDev.data\r
590 \r
591                 int idx;\r
592                 int blockDescLen;\r
593                 if (scsiDev.cdb[0] == 0x55)\r
594                 {\r
595                         blockDescLen =\r
596                                 (((uint16_t)scsiDev.data[6]) << 8) |scsiDev.data[7];\r
597                         idx = 8;\r
598                 }\r
599                 else\r
600                 {\r
601                         blockDescLen = scsiDev.data[3];\r
602                         idx = 4;\r
603                 }\r
604 \r
605                 // The unwritten rule.  Blocksizes are normally set using the\r
606                 // block descriptor value, not by changing page 0x03.\r
607                 if (blockDescLen >= 8)\r
608                 {\r
609                         uint32_t bytesPerSector =\r
610                                 (((uint32_t)scsiDev.data[idx+5]) << 16) |\r
611                                 (((uint32_t)scsiDev.data[idx+6]) << 8) |\r
612                                 scsiDev.data[idx+7];\r
613                         if ((bytesPerSector < MIN_SECTOR_SIZE) ||\r
614                                 (bytesPerSector > MAX_SECTOR_SIZE))\r
615                         {\r
616                                 goto bad;\r
617                         }\r
618                         else\r
619                         {\r
620                                 scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;\r
621                                 if (bytesPerSector != scsiDev.target->cfg->bytesPerSector)\r
622                                 {\r
623                                         configSave(scsiDev.target->targetId, bytesPerSector);\r
624                                 }\r
625                         }\r
626                 }\r
627                 idx += blockDescLen;\r
628 \r
629                 while (idx < scsiDev.dataLen)\r
630                 {\r
631                         int pageLen = scsiDev.data[idx + 1];\r
632                         if (idx + 2 + pageLen > scsiDev.dataLen) goto bad;\r
633 \r
634                         int pageCode = scsiDev.data[idx] & 0x3F;\r
635                         switch (pageCode)\r
636                         {\r
637                         case 0x03: // Format Device Page\r
638                         {\r
639                                 if (pageLen != 0x16) goto bad;\r
640 \r
641                                 // Fill out the configured bytes-per-sector\r
642                                 uint16_t bytesPerSector =\r
643                                         (((uint16_t)scsiDev.data[idx+12]) << 8) |\r
644                                         scsiDev.data[idx+13];\r
645 \r
646                                 // Sane values only, ok ?\r
647                                 if ((bytesPerSector < MIN_SECTOR_SIZE) ||\r
648                                         (bytesPerSector > MAX_SECTOR_SIZE))\r
649                                 {\r
650                                         goto bad;\r
651                                 }\r
652 \r
653                                 scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;\r
654                                 if (scsiDev.cdb[1] & 1) // SP Save Pages flag\r
655                                 {\r
656                                         configSave(scsiDev.target->targetId, bytesPerSector);\r
657                                 }\r
658                         }\r
659                         break;\r
660                         //default:\r
661 \r
662                                 // Easiest to just ignore for now. We'll get here when changing\r
663                                 // the SCSI block size via the descriptor header.\r
664                         }\r
665                         idx += 2 + pageLen;\r
666                 }\r
667         }\r
668 \r
669         goto out;\r
670 bad:\r
671         scsiDev.status = CHECK_CONDITION;\r
672         scsiDev.target->sense.code = ILLEGAL_REQUEST;\r
673         scsiDev.target->sense.asc = INVALID_FIELD_IN_PARAMETER_LIST;\r
674 \r
675 out:\r
676         scsiDev.phase = STATUS;\r
677 }\r
678 \r
679 int scsiModeCommand()\r
680 {\r
681         int commandHandled = 1;\r
682 \r
683         uint8 command = scsiDev.cdb[0];\r
684 \r
685         // We don't currently support the setting of any parameters.\r
686         // (ie. no MODE SELECT(6) or MODE SELECT(10) commands)\r
687 \r
688         if (command == 0x1A)\r
689         {\r
690                 // MODE SENSE(6)\r
691                 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors\r
692                 int pc = scsiDev.cdb[2] >> 6; // Page Control\r
693                 int pageCode = scsiDev.cdb[2] & 0x3F;\r
694                 int allocLength = scsiDev.cdb[4];\r
695 \r
696                 // SCSI1 standard: (CCS X3T9.2/86-52)\r
697                 // "An Allocation Length of zero indicates that no MODE SENSE data shall\r
698                 // be transferred. This condition shall not be considered as an error."\r
699                 doModeSense(1, dbd, pc, pageCode, allocLength);\r
700         }\r
701         else if (command == 0x5A)\r
702         {\r
703                 // MODE SENSE(10)\r
704                 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors\r
705                 int pc = scsiDev.cdb[2] >> 6; // Page Control\r
706                 int pageCode = scsiDev.cdb[2] & 0x3F;\r
707                 int allocLength =\r
708                         (((uint16) scsiDev.cdb[7]) << 8) +\r
709                         scsiDev.cdb[8];\r
710                 doModeSense(0, dbd, pc, pageCode, allocLength);\r
711         }\r
712         else if (command == 0x15)\r
713         {\r
714                 // MODE SELECT(6)\r
715                 int len = scsiDev.cdb[4];\r
716                 if (len == 0)\r
717                 {\r
718                         // If len == 0, then transfer no data. From the SCSI 2 standard:\r
719                         //      A parameter list length of zero indicates that no data shall\r
720                         //      be transferred. This condition shall not be considered as an\r
721                         //              error.\r
722                         scsiDev.phase = STATUS;\r
723                 }\r
724                 else\r
725                 {\r
726                         scsiDev.dataLen = len;\r
727                         scsiDev.phase = DATA_OUT;\r
728                         scsiDev.postDataOutHook = doModeSelect;\r
729                 }\r
730         }\r
731         else if (command == 0x55)\r
732         {\r
733                 // MODE SELECT(10)\r
734                 int allocLength = (((uint16) scsiDev.cdb[7]) << 8) + scsiDev.cdb[8];\r
735                 if (allocLength == 0)\r
736                 {\r
737                         // If len == 0, then transfer no data. From the SCSI 2 standard:\r
738                         //      A parameter list length of zero indicates that no data shall\r
739                         //      be transferred. This condition shall not be considered as an\r
740                         //              error.\r
741                         scsiDev.phase = STATUS;\r
742                 }\r
743                 else\r
744                 {\r
745                         scsiDev.dataLen = allocLength;\r
746                         scsiDev.phase = DATA_OUT;\r
747                         scsiDev.postDataOutHook = doModeSelect;\r
748                 }\r
749         }\r
750         else\r
751         {\r
752                 commandHandled = 0;\r
753         }\r
754 \r
755         return commandHandled;\r
756 }\r