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