Multi-sector writes, increased SPI clock to 24MHz, Added support for SDHC/SDXC.
[SCSI2SD-V6.git] / software / SCSI2SD / SCSI2SD.cydsn / mode.c
1 //      Copyright (C) 2013 Michael McMaster <michael@codesrc.com>\r
2 //\r
3 //      This file is part of SCSI2SD.\r
4 //\r
5 //      SCSI2SD is free software: you can redistribute it and/or modify\r
6 //      it under the terms of the GNU General Public License as published by\r
7 //      the Free Software Foundation, either version 3 of the License, or\r
8 //      (at your option) any later version.\r
9 //\r
10 //      SCSI2SD is distributed in the hope that it will be useful,\r
11 //      but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 //      GNU General Public License for more details.\r
14 //\r
15 //      You should have received a copy of the GNU General Public License\r
16 //      along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.\r
17 \r
18 #include "device.h"\r
19 #include "scsi.h"\r
20 #include "mode.h"\r
21 #include "disk.h"\r
22 \r
23 #include <string.h>\r
24 \r
25 static const uint8 DisconnectReconnectPage[] =\r
26 {\r
27 0x02, // Page code\r
28 0x0E, // Page length\r
29 0, // Buffer full ratio\r
30 0, // Buffer empty ratio\r
31 0x00, 10, // Bus inactivity limit, 100us increments. Allow 1ms.\r
32 0x00, 0x00, // Disconnect time limit\r
33 0x00, 0x00, // Connect time limit\r
34 0x00, 0x00, // Maximum burst size\r
35 0x00 ,// DTDC. Not used.\r
36 0x00, 0x00, 0x00 // Reserved\r
37 };\r
38 \r
39 static const uint8 FormatDevicePage[] =\r
40 {\r
41 0x03, // Page code \r
42 0x16, // Page length\r
43 0x00, 0x00, // Single zone\r
44 0x00, 0x00, // No alternate sectors\r
45 0x00, 0x00, // No alternate tracks\r
46 0x00, 0x00, // No alternate tracks per lun\r
47 0x00, SCSI_SECTORS_PER_TRACK, // Sectors per track\r
48 SCSI_SECTOR_SIZE >> 8, SCSI_SECTOR_SIZE & 0xFF, // Data bytes per physical sector\r
49 0x00, 0x01, // Interleave\r
50 0x00, 0x00, // Track skew factor\r
51 0x00, 0x00, // Cylinder skew factor\r
52 0xC0, // SSEC(set) HSEC(set) RMB SURF\r
53 0x00, 0x00, 0x00 // Reserved\r
54 };\r
55 \r
56 static const uint8 RigidDiskDriveGeometry[] =\r
57 {\r
58 0x04, // Page code\r
59 0x16, // Page length\r
60 0xFF, 0xFF, 0xFF, // Number of cylinders\r
61 SCSI_HEADS_PER_CYLINDER, // Number of heads\r
62 0xFF, 0xFF, 0xFF, // Starting cylinder-write precompensation\r
63 0xFF, 0xFF, 0xFF, // Starting cylinder-reduced write current\r
64 0x00, 0x1, // Drive step rate (units of 100ns)\r
65 0x00, 0x00, 0x00, // Landing zone cylinder\r
66 0x00, // RPL\r
67 0x00, // Rotational offset\r
68 0x00, // Reserved\r
69 5400 >> 8, 5400 & 0xFF, // Medium rotation rate (RPM)\r
70 0x00, 0x00 // Reserved\r
71 };\r
72 \r
73 static const uint8 CachingPage[] =\r
74 {\r
75 0x08, // Page Code\r
76 0x0A, // Page length\r
77 0x01, // Read cache disable\r
78 0x00, // No useful rention policy.\r
79 0x00, 0x00, // Pre-fetch always disabled\r
80 0x00, 0x00, // Minimum pre-fetch\r
81 0x00, 0x00, // Maximum pre-fetch\r
82 0x00, 0x00, // Maximum pre-fetch ceiling\r
83 };\r
84 \r
85 static const uint8 ControlModePage[] =\r
86 {\r
87 0x0A, // Page code\r
88 0x06, // Page length\r
89 0x00, // No logging\r
90 0x01, // Disable tagged queuing\r
91 0x00, // No async event notifications\r
92 0x00, // Reserved\r
93 0x00, 0x00 // AEN holdoff period.\r
94 };\r
95 \r
96 static void pageIn(int pc, int dataIdx, const uint8* pageData, int pageLen)\r
97 {\r
98         memcpy(&scsiDev.data[dataIdx], pageData, pageLen);\r
99 \r
100         if (pc == 0x01) // Mask out (un)changable values\r
101         {\r
102                 memset(&scsiDev.data[dataIdx+2], 0, pageLen - 2);\r
103         }\r
104 }\r
105 \r
106 static void doModeSense(\r
107         int sixByteCmd, int dbd, int pc, int pageCode, int allocLength)\r
108 {\r
109         // TODO Apple HD SC Drive Setup requests Page 3 (FormatDevicePage) with an\r
110         // allocLength of 0x20. We need 0x24 if we include a block descriptor, and\r
111         // thus return CHECK CONDITION. A block descriptor is optional, so we\r
112         // chose to ignore it.\r
113         // TODO make configurable\r
114         dbd = 1;\r
115         \r
116         if (pc == 0x03) // Saved Values not supported.\r
117         {\r
118                 scsiDev.status = CHECK_CONDITION;\r
119                 scsiDev.sense.code = ILLEGAL_REQUEST;\r
120                 scsiDev.sense.asc = SAVING_PARAMETERS_NOT_SUPPORTED;\r
121                 scsiDev.phase = STATUS;\r
122         }\r
123         else\r
124         {\r
125                 ////////////// Mode Parameter Header\r
126                 ////////////////////////////////////\r
127 \r
128                 // Skip the Mode Data Length, we set that last.\r
129                 int idx = 1;\r
130                 if (!sixByteCmd) ++idx;\r
131 \r
132                 scsiDev.data[idx++] = 0; // Medium type. 0 = default\r
133 \r
134                 // Device-specific parameter. Contains cache bits (0) and\r
135                 // a Write-Protect bit.\r
136                 scsiDev.data[idx++] = (blockDev.state & DISK_WP) ? 0x80 : 0;\r
137 \r
138                 if (sixByteCmd)\r
139                 {\r
140                         if (dbd)\r
141                         {\r
142                                 scsiDev.data[idx++] = 0; // No block descriptor\r
143                         }\r
144                         else\r
145                         {\r
146                                 // One block descriptor of length 8 bytes.\r
147                                 scsiDev.data[idx++] = 8;\r
148                         }\r
149                 }\r
150                 else\r
151                 {\r
152                         scsiDev.data[idx++] = 0; // Reserved\r
153                         scsiDev.data[idx++] = 0; // Reserved\r
154                         if (dbd)\r
155                         {\r
156                                 scsiDev.data[idx++] = 0; // No block descriptor\r
157                                 scsiDev.data[idx++] = 0; // No block descriptor\r
158                         }\r
159                         else\r
160                         {\r
161                                 // One block descriptor of length 8 bytes.\r
162                                 scsiDev.data[idx++] = 0;\r
163                                 scsiDev.data[idx++] = 8;\r
164                         }\r
165                 }\r
166 \r
167                 ////////////// Block Descriptor\r
168                 ////////////////////////////////////\r
169                 if (!dbd)\r
170                 {\r
171                         scsiDev.data[idx++] = 0; // Density code. Reserved for direct-access\r
172                         // Number of blocks\r
173                         // Zero == all remaining blocks shall have the medium\r
174                         // characteristics specified.\r
175                         scsiDev.data[idx++] = 0;\r
176                         scsiDev.data[idx++] = 0;\r
177                         scsiDev.data[idx++] = 0;\r
178 \r
179                         scsiDev.data[idx++] = 0; // reserved\r
180 \r
181                         // Block length\r
182                         scsiDev.data[idx++] = SCSI_BLOCK_SIZE >> 16;\r
183                         scsiDev.data[idx++] = SCSI_BLOCK_SIZE >> 8;\r
184                         scsiDev.data[idx++] = SCSI_BLOCK_SIZE & 0xFF;\r
185                 }\r
186 \r
187                 int pageFound = 1;\r
188 \r
189                 switch (pageCode)\r
190                 {\r
191                 case 0x3F:\r
192                         // EVERYTHING\r
193 \r
194                 case 0x02:\r
195                         pageIn(pc, idx, DisconnectReconnectPage, sizeof(DisconnectReconnectPage));\r
196                         idx += sizeof(DisconnectReconnectPage);\r
197                         if (pageCode != 0x3f) break;\r
198 \r
199                 case 0x03:\r
200                         pageIn(pc, idx, FormatDevicePage, sizeof(FormatDevicePage));\r
201                         idx += sizeof(FormatDevicePage);\r
202                         if (pageCode != 0x3f) break;\r
203 \r
204                 case 0x04:\r
205                 {\r
206                         pageIn(pc, idx, RigidDiskDriveGeometry, sizeof(RigidDiskDriveGeometry));\r
207 \r
208                         if (pc != 0x01)\r
209                         {\r
210                                 // Need to fill out the number of cylinders.\r
211                                 uint32 cyl;\r
212                                 uint8 head;\r
213                                 uint32 sector;\r
214                                 LBA2CHS(blockDev.capacity, &cyl, &head, &sector);\r
215 \r
216                                 scsiDev.data[idx+2] = cyl >> 16;\r
217                                 scsiDev.data[idx+3] = cyl >> 8;\r
218                                 scsiDev.data[idx+4] = cyl;\r
219 \r
220                                 memcpy(&scsiDev.data[idx+6], &scsiDev.data[idx+2], 3);\r
221                                 memcpy(&scsiDev.data[idx+9], &scsiDev.data[idx+2], 3);\r
222                         }\r
223 \r
224                         idx += sizeof(RigidDiskDriveGeometry);\r
225                         if (pageCode != 0x3f) break;\r
226                 }\r
227 \r
228                 case 0x08:\r
229                         pageIn(pc, idx, CachingPage, sizeof(CachingPage));\r
230                         idx += sizeof(CachingPage);\r
231                         if (pageCode != 0x3f) break;\r
232 \r
233                 case 0x0A:\r
234                         pageIn(pc, idx, ControlModePage, sizeof(ControlModePage));\r
235                         idx += sizeof(ControlModePage);\r
236                         break;\r
237 \r
238                 default:\r
239                         // Unknown Page Code\r
240                         pageFound = 0;\r
241                         scsiDev.status = CHECK_CONDITION;\r
242                         scsiDev.sense.code = ILLEGAL_REQUEST;\r
243                         scsiDev.sense.asc = INVALID_FIELD_IN_CDB;\r
244                         scsiDev.phase = STATUS;\r
245                 }\r
246 \r
247 \r
248                 if (idx > allocLength)\r
249                 {\r
250                         // Initiator may not have space to receive results.\r
251                         scsiDev.status = CHECK_CONDITION;\r
252                         scsiDev.sense.code = ILLEGAL_REQUEST;\r
253                         scsiDev.sense.asc = INVALID_FIELD_IN_CDB;\r
254                         scsiDev.phase = STATUS;\r
255                 }\r
256                 else if (pageFound)\r
257                 {\r
258                         // Go back and fill out the mode data length\r
259                         if (sixByteCmd)\r
260                         {\r
261                                 // Cannot currently exceed limits. yay\r
262                                 scsiDev.data[0] = idx - 1;\r
263                         }\r
264                         else\r
265                         {\r
266                                 scsiDev.data[0] = ((idx - 2) >> 8);\r
267                                 scsiDev.data[1] = (idx - 2);\r
268                         }\r
269 \r
270                         scsiDev.dataLen = idx;\r
271                         scsiDev.phase = DATA_IN;\r
272                 }\r
273                 else\r
274                 {\r
275                         // Initiator may not have space to receive results.\r
276                         scsiDev.status = CHECK_CONDITION;\r
277                         scsiDev.sense.code = ILLEGAL_REQUEST;\r
278                         scsiDev.sense.asc = INVALID_FIELD_IN_CDB;\r
279                         scsiDev.phase = STATUS;\r
280                 }\r
281         }\r
282 }\r
283 \r
284 int scsiModeCommand()\r
285 {\r
286         int commandHandled = 1;\r
287 \r
288         uint8 command = scsiDev.cdb[0];\r
289 \r
290         // We don't currently support the setting of any parameters.\r
291         // (ie. no MODE SELECT(6) or MODE SELECT(10) commands)\r
292 \r
293         if (command == 0x1A)\r
294         {\r
295                 // MODE SENSE(6)\r
296                 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors\r
297                 int pc = scsiDev.cdb[2] >> 6; // Page Control\r
298                 int pageCode = scsiDev.cdb[2] & 0x3F;\r
299                 int allocLength = scsiDev.cdb[4];\r
300                 if (allocLength == 0) allocLength = 256;\r
301                 doModeSense(1, dbd, pc, pageCode, allocLength);\r
302         }\r
303         else if (command == 0x5A)\r
304         {\r
305                 // MODE SENSE(10)\r
306                 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors\r
307                 int pc = scsiDev.cdb[2] >> 6; // Page Control\r
308                 int pageCode = scsiDev.cdb[2] & 0x3F;\r
309                 int allocLength =\r
310                         (((uint16) scsiDev.cdb[7]) << 8) +\r
311                         scsiDev.cdb[8];\r
312                 doModeSense(0, dbd, pc, pageCode, allocLength);\r
313         }\r
314         else\r
315         {\r
316                 commandHandled = 0;\r
317         }\r
318 \r
319         return commandHandled;\r
320 }\r
321 \r
322 \r