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