c66608acbbe951d0a5282e088e2320c0b5141c7d
[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 #pragma GCC push_options\r
19 #pragma GCC optimize("-flto")\r
20 \r
21 #include "device.h"\r
22 #include "scsi.h"\r
23 #include "mode.h"\r
24 #include "disk.h"\r
25 \r
26 #include <string.h>\r
27 \r
28 static const uint8 ReadWriteErrorRecoveryPage[] =\r
29 {\r
30 0x01, // Page code\r
31 0x0A, // Page length\r
32 \r
33 // VMS 5.5-2 is very particular regarding the mode page values.\r
34 // The required values for a SCSI2/NoTCQ device are:\r
35 // AWRE=0 ARRE=0 TB=1 RC=0 EER=? PER=1 DTE=1 DCR=?\r
36 // See ftp://www.digiater.nl/openvms/decus/vms94b/net94b/scsi_params_dkdriver.txt\r
37 // X-Newsgroups: comp.os.vms\r
38 // Subject: Re: VMS 6.1 vs. Seagate Disk Drives\r
39 // Message-Id: <32g87h$8q@nntpd.lkg.dec.com>\r
40 // From: weber@evms.enet.dec.com (Ralph O. Weber -- OpenVMS AXP)\r
41 // Date: 12 Aug 1994 16:32:49 GMT\r
42 0x26,\r
43 \r
44 0x00, // Don't try recovery algorithm during reads\r
45 0x00, // Correction span 0\r
46 0x00, // Head offset count 0,\r
47 0x00, // Data strobe offset count 0,\r
48 0x00, // Reserved\r
49 0x00, // Don't try recovery algorithm during writes\r
50 0x00, // Reserved\r
51 0x00, 0x00 // Recovery time limit 0 (use default)*/\r
52 };\r
53 \r
54 static const uint8 DisconnectReconnectPage[] =\r
55 {\r
56 0x02, // Page code\r
57 0x0E, // Page length\r
58 0, // Buffer full ratio\r
59 0, // Buffer empty ratio\r
60 0x00, 10, // Bus inactivity limit, 100us increments. Allow 1ms.\r
61 0x00, 0x00, // Disconnect time limit\r
62 0x00, 0x00, // Connect time limit\r
63 0x00, 0x00, // Maximum burst size\r
64 0x00 ,// DTDC. Not used.\r
65 0x00, 0x00, 0x00 // Reserved\r
66 };\r
67 \r
68 static const uint8 FormatDevicePage[] =\r
69 {\r
70 0x03 | 0x80, // Page code | PS (persist) bit.\r
71 0x16, // Page length\r
72 0x00, 0x00, // Single zone\r
73 0x00, 0x00, // No alternate sectors\r
74 0x00, 0x00, // No alternate tracks\r
75 0x00, 0x00, // No alternate tracks per lun\r
76 0x00, SCSI_SECTORS_PER_TRACK, // Sectors per track\r
77 0xFF, 0xFF, // Data bytes per physical sector. Configurable.\r
78 0x00, 0x01, // Interleave\r
79 0x00, 0x00, // Track skew factor\r
80 0x00, 0x00, // Cylinder skew factor\r
81 0xC0, // SSEC(set) HSEC(set) RMB SURF\r
82 0x00, 0x00, 0x00 // Reserved\r
83 };\r
84 \r
85 static const uint8 RigidDiskDriveGeometry[] =\r
86 {\r
87 0x04, // Page code\r
88 0x16, // Page length\r
89 0xFF, 0xFF, 0xFF, // Number of cylinders\r
90 SCSI_HEADS_PER_CYLINDER, // Number of heads\r
91 0xFF, 0xFF, 0xFF, // Starting cylinder-write precompensation\r
92 0xFF, 0xFF, 0xFF, // Starting cylinder-reduced write current\r
93 0x00, 0x1, // Drive step rate (units of 100ns)\r
94 0x00, 0x00, 0x00, // Landing zone cylinder\r
95 0x00, // RPL\r
96 0x00, // Rotational offset\r
97 0x00, // Reserved\r
98 5400 >> 8, 5400 & 0xFF, // Medium rotation rate (RPM)\r
99 0x00, 0x00 // Reserved\r
100 };\r
101 \r
102 static const uint8 CachingPage[] =\r
103 {\r
104 0x08, // Page Code\r
105 0x0A, // Page length\r
106 0x01, // Read cache disable\r
107 0x00, // No useful rention policy.\r
108 0x00, 0x00, // Pre-fetch always disabled\r
109 0x00, 0x00, // Minimum pre-fetch\r
110 0x00, 0x00, // Maximum pre-fetch\r
111 0x00, 0x00, // Maximum pre-fetch ceiling\r
112 };\r
113 \r
114 static const uint8 ControlModePage[] =\r
115 {\r
116 0x0A, // Page code\r
117 0x06, // Page length\r
118 0x00, // No logging\r
119 0x01, // Disable tagged queuing\r
120 0x00, // No async event notifications\r
121 0x00, // Reserved\r
122 0x00, 0x00 // AEN holdoff period.\r
123 };\r
124 \r
125 // Allow Apple 68k Drive Setup to format this drive.\r
126 // Code\r
127 static const uint8 AppleVendorPage[] =\r
128 {\r
129 0x30, // Page code\r
130 28, // Page length\r
131 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\r
132 'A','P','P','L','E',' ','C','O','M','P','U','T','E','R',',',' ','I','N','C','.'\r
133 };\r
134 \r
135 static void pageIn(int pc, int dataIdx, const uint8* pageData, int pageLen)\r
136 {\r
137         memcpy(&scsiDev.data[dataIdx], pageData, pageLen);\r
138 \r
139         if (pc == 0x01) // Mask out (un)changable values\r
140         {\r
141                 memset(&scsiDev.data[dataIdx+2], 0, pageLen - 2);\r
142         }\r
143 }\r
144 \r
145 static void doModeSense(\r
146         int sixByteCmd, int dbd, int pc, int pageCode, int allocLength)\r
147 {\r
148         if (pc == 0x03) // Saved Values not supported.\r
149         {\r
150                 scsiDev.status = CHECK_CONDITION;\r
151                 scsiDev.target->sense.code = ILLEGAL_REQUEST;\r
152                 scsiDev.target->sense.asc = SAVING_PARAMETERS_NOT_SUPPORTED;\r
153                 scsiDev.phase = STATUS;\r
154         }\r
155         else\r
156         {\r
157                 ////////////// Mode Parameter Header\r
158                 ////////////////////////////////////\r
159 \r
160                 // Skip the Mode Data Length, we set that last.\r
161                 int idx = 1;\r
162                 if (!sixByteCmd) ++idx;\r
163 \r
164                 uint8_t mediumType = 0;\r
165                 uint8_t deviceSpecificParam = 0;\r
166                 uint8_t density = 0;\r
167                 switch (scsiDev.target->cfg->deviceType == CONFIG_OPTICAL)\r
168                 {\r
169                 case CONFIG_FIXED:\r
170                 case CONFIG_REMOVEABLE:\r
171                         mediumType = 0; // We should support various floppy types here!\r
172                         // Contains cache bits (0) and a Write-Protect bit.\r
173                         deviceSpecificParam =\r
174                                 (blockDev.state & DISK_WP) ? 0x80 : 0;\r
175                         density = 0; // reserved for direct access\r
176                         break;\r
177 \r
178                 case CONFIG_FLOPPY_14MB:\r
179                         mediumType = 0x1E; // 90mm/3.5"\r
180                         deviceSpecificParam =\r
181                                 (blockDev.state & DISK_WP) ? 0x80 : 0;\r
182                         density = 0; // reserved for direct access\r
183                         break;\r
184 \r
185                 case CONFIG_OPTICAL:\r
186                         mediumType = 0x02; // 120mm CDROM, data only.\r
187                         deviceSpecificParam = 0;\r
188                         density = 0x01; // User data only, 2048bytes per sector.\r
189                         break;\r
190 \r
191                 };\r
192 \r
193                 scsiDev.data[idx++] = mediumType;\r
194                 scsiDev.data[idx++] = deviceSpecificParam;\r
195 \r
196                 if (sixByteCmd)\r
197                 {\r
198                         if (dbd)\r
199                         {\r
200                                 scsiDev.data[idx++] = 0; // No block descriptor\r
201                         }\r
202                         else\r
203                         {\r
204                                 // One block descriptor of length 8 bytes.\r
205                                 scsiDev.data[idx++] = 8;\r
206                         }\r
207                 }\r
208                 else\r
209                 {\r
210                         scsiDev.data[idx++] = 0; // Reserved\r
211                         scsiDev.data[idx++] = 0; // Reserved\r
212                         if (dbd)\r
213                         {\r
214                                 scsiDev.data[idx++] = 0; // No block descriptor\r
215                                 scsiDev.data[idx++] = 0; // No block descriptor\r
216                         }\r
217                         else\r
218                         {\r
219                                 // One block descriptor of length 8 bytes.\r
220                                 scsiDev.data[idx++] = 0;\r
221                                 scsiDev.data[idx++] = 8;\r
222                         }\r
223                 }\r
224 \r
225                 ////////////// Block Descriptor\r
226                 ////////////////////////////////////\r
227                 if (!dbd)\r
228                 {\r
229                         scsiDev.data[idx++] = density;\r
230                         // Number of blocks\r
231                         // Zero == all remaining blocks shall have the medium\r
232                         // characteristics specified.\r
233                         scsiDev.data[idx++] = 0;\r
234                         scsiDev.data[idx++] = 0;\r
235                         scsiDev.data[idx++] = 0;\r
236 \r
237                         scsiDev.data[idx++] = 0; // reserved\r
238 \r
239                         // Block length\r
240                         uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;\r
241                         scsiDev.data[idx++] = bytesPerSector >> 16;\r
242                         scsiDev.data[idx++] = bytesPerSector >> 8;\r
243                         scsiDev.data[idx++] = bytesPerSector & 0xFF;\r
244                 }\r
245 \r
246                 int pageFound = 0;\r
247 \r
248                 if (pageCode == 0x01 || pageCode == 0x3F)\r
249                 {\r
250                         pageFound = 1;\r
251                         pageIn(pc, idx, ReadWriteErrorRecoveryPage, sizeof(ReadWriteErrorRecoveryPage));\r
252                         idx += sizeof(ReadWriteErrorRecoveryPage);\r
253                 }\r
254 \r
255                 if (pageCode == 0x02 || pageCode == 0x3F)\r
256                 {\r
257                         pageFound = 1;\r
258                         pageIn(pc, idx, DisconnectReconnectPage, sizeof(DisconnectReconnectPage));\r
259                         idx += sizeof(DisconnectReconnectPage);\r
260                 }\r
261 \r
262                 if (pageCode == 0x03 || pageCode == 0x3F)\r
263                 {\r
264                         pageFound = 1;\r
265                         pageIn(pc, idx, FormatDevicePage, sizeof(FormatDevicePage));\r
266                         if (pc != 0x01)\r
267                         {\r
268                                 // Fill out the configured bytes-per-sector\r
269                                 uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;\r
270                                 scsiDev.data[idx+12] = bytesPerSector >> 8;\r
271                                 scsiDev.data[idx+13] = bytesPerSector & 0xFF;\r
272                         }\r
273                         else\r
274                         {\r
275                                 // Set a mask for the changeable values.\r
276                                 scsiDev.data[idx+12] = 0xFF;\r
277                                 scsiDev.data[idx+13] = 0xFF;\r
278                         }\r
279 \r
280                         idx += sizeof(FormatDevicePage);\r
281                 }\r
282 \r
283                 if (pageCode == 0x04 || pageCode == 0x3F)\r
284                 {\r
285                         pageFound = 1;\r
286                         pageIn(pc, idx, RigidDiskDriveGeometry, sizeof(RigidDiskDriveGeometry));\r
287 \r
288                         if (pc != 0x01)\r
289                         {\r
290                                 // Need to fill out the number of cylinders.\r
291                                 uint32 cyl;\r
292                                 uint8 head;\r
293                                 uint32 sector;\r
294                                 LBA2CHS(\r
295                                         getScsiCapacity(\r
296                                                 scsiDev.target->cfg->sdSectorStart,\r
297                                                 scsiDev.target->liveCfg.bytesPerSector,\r
298                                                 scsiDev.target->cfg->scsiSectors),\r
299                                         &cyl,\r
300                                         &head,\r
301                                         &sector);\r
302 \r
303                                 scsiDev.data[idx+2] = cyl >> 16;\r
304                                 scsiDev.data[idx+3] = cyl >> 8;\r
305                                 scsiDev.data[idx+4] = cyl;\r
306 \r
307                                 memcpy(&scsiDev.data[idx+6], &scsiDev.data[idx+2], 3);\r
308                                 memcpy(&scsiDev.data[idx+9], &scsiDev.data[idx+2], 3);\r
309                         }\r
310 \r
311                         idx += sizeof(RigidDiskDriveGeometry);\r
312                 }\r
313 \r
314                 // DON'T output the following pages for SCSI1 hosts. They get upset when\r
315                 // we have more data to send than the allocation length provided.\r
316                 // (ie. Try not to output any more pages below this comment)\r
317 \r
318 \r
319                 if (!scsiDev.compatMode && (pageCode == 0x08 || pageCode == 0x3F))\r
320                 {\r
321                         pageFound = 1;\r
322                         pageIn(pc, idx, CachingPage, sizeof(CachingPage));\r
323                         idx += sizeof(CachingPage);\r
324                 }\r
325 \r
326                 if (!scsiDev.compatMode && (pageCode == 0x0A || pageCode == 0x3F))\r
327                 {\r
328                         pageFound = 1;\r
329                         pageIn(pc, idx, ControlModePage, sizeof(ControlModePage));\r
330                         idx += sizeof(ControlModePage);\r
331                 }\r
332 \r
333                 if ((\r
334                                 (scsiDev.target->cfg->quirks == CONFIG_QUIRKS_APPLE) ||\r
335                                 (idx + sizeof(AppleVendorPage) <= allocLength)\r
336                         ) &&\r
337                         (pageCode == 0x30 || pageCode == 0x3F))\r
338                 {\r
339                         pageFound = 1;\r
340                         pageIn(pc, idx, AppleVendorPage, sizeof(AppleVendorPage));\r
341                         idx += sizeof(AppleVendorPage);\r
342                 }\r
343 \r
344                 if (!pageFound)\r
345                 {\r
346                         // Unknown Page Code\r
347                         pageFound = 0;\r
348                         scsiDev.status = CHECK_CONDITION;\r
349                         scsiDev.target->sense.code = ILLEGAL_REQUEST;\r
350                         scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;\r
351                         scsiDev.phase = STATUS;\r
352                 }\r
353                 else\r
354                 {\r
355                         // Go back and fill out the mode data length\r
356                         if (sixByteCmd)\r
357                         {\r
358                                 // Cannot currently exceed limits. yay\r
359                                 scsiDev.data[0] = idx - 1;\r
360                         }\r
361                         else\r
362                         {\r
363                                 scsiDev.data[0] = ((idx - 2) >> 8);\r
364                                 scsiDev.data[1] = (idx - 2);\r
365                         }\r
366 \r
367                         scsiDev.dataLen = idx > allocLength ? allocLength : idx;\r
368                         scsiDev.phase = DATA_IN;\r
369                 }\r
370         }\r
371 }\r
372 \r
373 // Callback after the DATA OUT phase is complete.\r
374 static void doModeSelect(void)\r
375 {\r
376         if (scsiDev.status == GOOD) // skip if we've already encountered an error\r
377         {\r
378                 // scsiDev.dataLen bytes are in scsiDev.data\r
379 \r
380                 int idx;\r
381                 int blockDescLen;\r
382                 if (scsiDev.cdb[0] == 0x55)\r
383                 {\r
384                         blockDescLen =\r
385                                 (((uint16_t)scsiDev.data[6]) << 8) |scsiDev.data[7];\r
386                         idx = 8;\r
387                 }\r
388                 else\r
389                 {\r
390                         blockDescLen = scsiDev.data[3];\r
391                         idx = 4;\r
392                 }\r
393 \r
394                 // The unwritten rule.  Blocksizes are normally set using the\r
395                 // block descriptor value, not by changing page 0x03.\r
396                 if (blockDescLen >= 8)\r
397                 {\r
398                         uint32_t bytesPerSector =\r
399                                 (((uint32_t)scsiDev.data[idx+5]) << 16) |\r
400                                 (((uint32_t)scsiDev.data[idx+6]) << 8) |\r
401                                 scsiDev.data[idx+7];\r
402                         if ((bytesPerSector < MIN_SECTOR_SIZE) ||\r
403                                 (bytesPerSector > MAX_SECTOR_SIZE))\r
404                         {\r
405                                 goto bad;\r
406                         }\r
407                         else\r
408                         {\r
409                                 scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;\r
410                                 if (bytesPerSector != scsiDev.target->cfg->bytesPerSector)\r
411                                 {\r
412                                         configSave(scsiDev.target->targetId, bytesPerSector);\r
413                                 }\r
414                         }\r
415                 }\r
416                 idx += blockDescLen;\r
417 \r
418                 while (idx < scsiDev.dataLen)\r
419                 {\r
420                         int pageLen = scsiDev.data[idx + 1];\r
421                         if (idx + 2 + pageLen > scsiDev.dataLen) goto bad;\r
422 \r
423                         int pageCode = scsiDev.data[idx] & 0x3F;\r
424                         switch (pageCode)\r
425                         {\r
426                         case 0x03: // Format Device Page\r
427                         {\r
428                                 if (pageLen != 0x16) goto bad;\r
429 \r
430                                 // Fill out the configured bytes-per-sector\r
431                                 uint16_t bytesPerSector =\r
432                                         (((uint16_t)scsiDev.data[idx+12]) << 8) |\r
433                                         scsiDev.data[idx+13];\r
434 \r
435                                 // Sane values only, ok ?\r
436                                 if ((bytesPerSector < MIN_SECTOR_SIZE) ||\r
437                                         (bytesPerSector > MAX_SECTOR_SIZE))\r
438                                 {\r
439                                         goto bad;\r
440                                 }\r
441 \r
442                                 scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;\r
443                                 if (scsiDev.cdb[1] & 1) // SP Save Pages flag\r
444                                 {\r
445                                         configSave(scsiDev.target->targetId, bytesPerSector);\r
446                                 }\r
447                         }\r
448                         break;\r
449                         //default:\r
450 \r
451                                 // Easiest to just ignore for now. We'll get here when changing\r
452                                 // the SCSI block size via the descriptor header.\r
453                         }\r
454                         idx += 2 + pageLen;\r
455                 }\r
456         }\r
457 \r
458         goto out;\r
459 bad:\r
460         scsiDev.status = CHECK_CONDITION;\r
461         scsiDev.target->sense.code = ILLEGAL_REQUEST;\r
462         scsiDev.target->sense.asc = INVALID_FIELD_IN_PARAMETER_LIST;\r
463 \r
464 out:\r
465         scsiDev.phase = STATUS;\r
466 }\r
467 \r
468 int scsiModeCommand()\r
469 {\r
470         int commandHandled = 1;\r
471 \r
472         uint8 command = scsiDev.cdb[0];\r
473 \r
474         // We don't currently support the setting of any parameters.\r
475         // (ie. no MODE SELECT(6) or MODE SELECT(10) commands)\r
476 \r
477         if (command == 0x1A)\r
478         {\r
479                 // MODE SENSE(6)\r
480                 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors\r
481                 int pc = scsiDev.cdb[2] >> 6; // Page Control\r
482                 int pageCode = scsiDev.cdb[2] & 0x3F;\r
483                 int allocLength = scsiDev.cdb[4];\r
484                 if (allocLength == 0) allocLength = 256;\r
485                 doModeSense(1, dbd, pc, pageCode, allocLength);\r
486         }\r
487         else if (command == 0x5A)\r
488         {\r
489                 // MODE SENSE(10)\r
490                 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors\r
491                 int pc = scsiDev.cdb[2] >> 6; // Page Control\r
492                 int pageCode = scsiDev.cdb[2] & 0x3F;\r
493                 int allocLength =\r
494                         (((uint16) scsiDev.cdb[7]) << 8) +\r
495                         scsiDev.cdb[8];\r
496                 doModeSense(0, dbd, pc, pageCode, allocLength);\r
497         }\r
498         else if (command == 0x15)\r
499         {\r
500                 // MODE SELECT(6)\r
501                 int len = scsiDev.cdb[4];\r
502                 if (len == 0)\r
503                 {\r
504                         // If len == 0, then transfer no data. From the SCSI 2 standard:\r
505                         //      A parameter list length of zero indicates that no data shall\r
506                         //      be transferred. This condition shall not be considered as an\r
507                         //              error.\r
508                         scsiDev.phase = STATUS;\r
509                 }\r
510                 else\r
511                 {\r
512                         scsiDev.dataLen = len;\r
513                         scsiDev.phase = DATA_OUT;\r
514                         scsiDev.postDataOutHook = doModeSelect;\r
515                 }\r
516         }\r
517         else if (command == 0x55)\r
518         {\r
519                 // MODE SELECT(10)\r
520                 int allocLength = (((uint16) scsiDev.cdb[7]) << 8) + scsiDev.cdb[8];\r
521                 if (allocLength == 0)\r
522                 {\r
523                         // If len == 0, then transfer no data. From the SCSI 2 standard:\r
524                         //      A parameter list length of zero indicates that no data shall\r
525                         //      be transferred. This condition shall not be considered as an\r
526                         //              error.\r
527                         scsiDev.phase = STATUS;\r
528                 }\r
529                 else\r
530                 {\r
531                         scsiDev.dataLen = allocLength;\r
532                         scsiDev.phase = DATA_OUT;\r
533                         scsiDev.postDataOutHook = doModeSelect;\r
534                 }\r
535         }\r
536         else\r
537         {\r
538                 commandHandled = 0;\r
539         }\r
540 \r
541         return commandHandled;\r
542 }\r
543 \r
544 #pragma GCC pop_options\r