Many bug fixes, including selection fixes.
[SCSI2SD.git] / software / SCSI2SD / SCSI2SD.cydsn / disk.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 "config.h"\r
22 #include "disk.h"\r
23 #include "sd.h"\r
24 \r
25 #include <string.h>\r
26 \r
27 // Global\r
28 BlockDevice blockDev;\r
29 Transfer transfer;\r
30 \r
31 static int doSdInit()\r
32 {\r
33         int result = sdInit();\r
34         if (result)\r
35         {\r
36                 blockDev.state = blockDev.state | DISK_INITIALISED;\r
37 \r
38                 // artificially limit this value according to EEPROM config.\r
39                 blockDev.capacity =\r
40                         (config->maxBlocks && (sdDev.capacity > config->maxBlocks))\r
41                                 ? config->maxBlocks : sdDev.capacity;\r
42         }\r
43         return result;\r
44 }\r
45 \r
46 \r
47 static void doFormatUnit()\r
48 {\r
49         // Low-level formatting is not required.\r
50         // Nothing left to do.\r
51 }\r
52 \r
53 static void doReadCapacity()\r
54 {\r
55         uint32 lba = (((uint32) scsiDev.cdb[2]) << 24) +\r
56                 (((uint32) scsiDev.cdb[3]) << 16) +\r
57                 (((uint32) scsiDev.cdb[4]) << 8) +\r
58                 scsiDev.cdb[5];\r
59         int pmi = scsiDev.cdb[8] & 1;\r
60 \r
61         if (!pmi && lba)\r
62         {\r
63                 // error.\r
64                 // We don't do anything with the "partial medium indicator", and\r
65                 // assume that delays are constant across each block. But the spec\r
66                 // says we must return this error if pmi is specified incorrectly.\r
67                 scsiDev.status = CHECK_CONDITION;\r
68                 scsiDev.sense.code = ILLEGAL_REQUEST;\r
69                 scsiDev.sense.asc = INVALID_FIELD_IN_CDB;\r
70                 scsiDev.phase = STATUS;\r
71         }\r
72         else if (blockDev.capacity > 0)\r
73         {\r
74                 uint32 highestBlock = blockDev.capacity - 1;\r
75 \r
76                 scsiDev.data[0] = highestBlock >> 24;\r
77                 scsiDev.data[1] = highestBlock >> 16;\r
78                 scsiDev.data[2] = highestBlock >> 8;\r
79                 scsiDev.data[3] = highestBlock;\r
80 \r
81                 scsiDev.data[4] = blockDev.bs >> 24;\r
82                 scsiDev.data[5] = blockDev.bs >> 16;\r
83                 scsiDev.data[6] = blockDev.bs >> 8;\r
84                 scsiDev.data[7] = blockDev.bs;\r
85                 scsiDev.dataLen = 8;\r
86                 scsiDev.phase = DATA_IN;\r
87         }\r
88         else\r
89         {\r
90                 scsiDev.status = CHECK_CONDITION;\r
91                 scsiDev.sense.code = NOT_READY;\r
92                 scsiDev.sense.asc = MEDIUM_NOT_PRESENT;\r
93                 scsiDev.phase = STATUS;\r
94         }\r
95 }\r
96 \r
97 static void doWrite(uint32 lba, uint32 blocks)\r
98 {\r
99         if (blockDev.state & DISK_WP)\r
100         {\r
101                 scsiDev.status = CHECK_CONDITION;\r
102                 scsiDev.sense.code = ILLEGAL_REQUEST;\r
103                 scsiDev.sense.asc = WRITE_PROTECTED;\r
104                 scsiDev.phase = STATUS;\r
105         }\r
106         else if (((uint64) lba) + blocks > blockDev.capacity)\r
107         {\r
108                 scsiDev.status = CHECK_CONDITION;\r
109                 scsiDev.sense.code = ILLEGAL_REQUEST;\r
110                 scsiDev.sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;\r
111                 scsiDev.phase = STATUS;\r
112         }\r
113         else\r
114         {\r
115                 transfer.dir = TRANSFER_WRITE;\r
116                 transfer.lba = lba;\r
117                 transfer.blocks = blocks;\r
118                 transfer.currentBlock = 0;\r
119                 scsiDev.phase = DATA_OUT;\r
120                 scsiDev.dataLen = SCSI_BLOCK_SIZE;\r
121                 scsiDev.dataPtr = SCSI_BLOCK_SIZE; // TODO FIX scsiDiskPoll()\r
122 \r
123                 // No need for single-block reads atm.  Overhead of the\r
124                 // multi-block read is minimal.\r
125                 transfer.multiBlock = 1;\r
126                 sdPrepareWrite();\r
127         }\r
128 }\r
129 \r
130 \r
131 static void doRead(uint32 lba, uint32 blocks)\r
132 {\r
133         if (((uint64) lba) + blocks > blockDev.capacity)\r
134         {\r
135                 scsiDev.status = CHECK_CONDITION;\r
136                 scsiDev.sense.code = ILLEGAL_REQUEST;\r
137                 scsiDev.sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;\r
138                 scsiDev.phase = STATUS;\r
139         }\r
140         else\r
141         {\r
142                 transfer.dir = TRANSFER_READ;\r
143                 transfer.lba = lba;\r
144                 transfer.blocks = blocks;\r
145                 transfer.currentBlock = 0;\r
146                 scsiDev.phase = DATA_IN;\r
147                 scsiDev.dataLen = 0; // No data yet\r
148 \r
149                 if ((blocks == 1) ||\r
150                         (((uint64) lba) + blocks == blockDev.capacity)\r
151                         )\r
152                 {\r
153                         // We get errors on reading the last sector using a multi-sector\r
154                         // read :-(\r
155                         transfer.multiBlock = 0;\r
156                 }\r
157                 else\r
158                 {\r
159                         transfer.multiBlock = 1;\r
160                         sdPrepareRead();\r
161                 }\r
162         }\r
163 }\r
164 \r
165 static void doSeek(uint32 lba)\r
166 {\r
167         if (lba >= blockDev.capacity)\r
168         {\r
169                 scsiDev.status = CHECK_CONDITION;\r
170                 scsiDev.sense.code = ILLEGAL_REQUEST;\r
171                 scsiDev.sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;\r
172                 scsiDev.phase = STATUS;\r
173         }\r
174 }\r
175 \r
176 static int doTestUnitReady()\r
177 {\r
178         int ready = 1;\r
179         if (!(blockDev.state & DISK_STARTED))\r
180         {\r
181                 ready = 0;\r
182                 scsiDev.status = CHECK_CONDITION;\r
183                 scsiDev.sense.code = NOT_READY;\r
184                 scsiDev.sense.asc = LOGICAL_UNIT_NOT_READY_INITIALIZING_COMMAND_REQUIRED;\r
185                 scsiDev.phase = STATUS;\r
186         }\r
187         else if (!(blockDev.state & DISK_PRESENT))\r
188         {\r
189                 ready = 0;\r
190                 scsiDev.status = CHECK_CONDITION;\r
191                 scsiDev.sense.code = NOT_READY;\r
192                 scsiDev.sense.asc = MEDIUM_NOT_PRESENT;\r
193                 scsiDev.phase = STATUS;\r
194         }\r
195         else if (!(blockDev.state & DISK_INITIALISED))\r
196         {\r
197                 ready = 0;\r
198                 scsiDev.status = CHECK_CONDITION;\r
199                 scsiDev.sense.code = NOT_READY;\r
200                 scsiDev.sense.asc = LOGICAL_UNIT_NOT_READY_CAUSE_NOT_REPORTABLE;\r
201                 scsiDev.phase = STATUS;\r
202         }\r
203         return ready;\r
204 }\r
205 \r
206 // Handle direct-access scsi device commands\r
207 int scsiDiskCommand()\r
208 {\r
209         int commandHandled = 1;\r
210 \r
211         uint8 command = scsiDev.cdb[0];\r
212         if (command == 0x1B)\r
213         {\r
214                 // START STOP UNIT\r
215                 // Enable or disable media access operations.\r
216                 // Ignore load/eject requests. We can't do that.\r
217                 //int immed = scsiDev.cdb[1] & 1;\r
218                 int start = scsiDev.cdb[4] & 1;\r
219 \r
220                 if (start)\r
221                 {\r
222                         blockDev.state = blockDev.state | DISK_STARTED;\r
223                         if (!(blockDev.state & DISK_INITIALISED))\r
224                         {\r
225                                 doSdInit();\r
226                         }\r
227                 }\r
228                 else\r
229                 {\r
230                         blockDev.state &= ~DISK_STARTED;\r
231                 }\r
232         }\r
233         else if (command == 0x00)\r
234         {\r
235                 // TEST UNIT READY\r
236                 doTestUnitReady();\r
237         }\r
238         else if (!doTestUnitReady())\r
239         {\r
240                 // Status and sense codes already set by doTestUnitReady\r
241         }\r
242         else if (command == 0x04)\r
243         {\r
244                 // FORMAT UNIT\r
245                 doFormatUnit();\r
246         }\r
247         else if (command == 0x08)\r
248         {\r
249                 // READ(6)\r
250                 uint32 lba =\r
251                         (((uint32) scsiDev.cdb[1] & 0x1F) << 16) +\r
252                         (((uint32) scsiDev.cdb[2]) << 8) +\r
253                         scsiDev.cdb[3];\r
254                 uint32 blocks = scsiDev.cdb[4];\r
255                 if (blocks == 0) blocks = 256;\r
256                 doRead(lba, blocks);\r
257         }\r
258 \r
259         else if (command == 0x28)\r
260         {\r
261                 // READ(10)\r
262                 // Ignore all cache control bits - we don't support a memory cache.\r
263 \r
264                 uint32 lba =\r
265                         (((uint32) scsiDev.cdb[2]) << 24) +\r
266                         (((uint32) scsiDev.cdb[3]) << 16) +\r
267                         (((uint32) scsiDev.cdb[4]) << 8) +\r
268                         scsiDev.cdb[5];\r
269                 uint32 blocks =\r
270                         (((uint32) scsiDev.cdb[7]) << 8) +\r
271                         scsiDev.cdb[8];\r
272 \r
273                 doRead(lba, blocks);\r
274         }\r
275 \r
276         else if (command == 0x25)\r
277         {\r
278                 // READ CAPACITY\r
279                 doReadCapacity();\r
280         }\r
281 \r
282         else if (command == 0x0B)\r
283         {\r
284                 // SEEK(6)\r
285                 uint32 lba =\r
286                         (((uint32) scsiDev.cdb[1] & 0x1F) << 16) +\r
287                         (((uint32) scsiDev.cdb[2]) << 8) +\r
288                         scsiDev.cdb[3];\r
289 \r
290                 doSeek(lba);\r
291         }\r
292 \r
293         else if (command == 0x2B)\r
294         {\r
295                 // SEEK(10)\r
296                 uint32 lba =\r
297                         (((uint32) scsiDev.cdb[2]) << 24) +\r
298                         (((uint32) scsiDev.cdb[3]) << 16) +\r
299                         (((uint32) scsiDev.cdb[4]) << 8) +\r
300                         scsiDev.cdb[5];\r
301 \r
302                 doSeek(lba);\r
303         }\r
304         else if (command == 0x0A)\r
305         {\r
306                 // WRITE(6)\r
307                 uint32 lba =\r
308                         (((uint32) scsiDev.cdb[1] & 0x1F) << 16) +\r
309                         (((uint32) scsiDev.cdb[2]) << 8) +\r
310                         scsiDev.cdb[3];\r
311                 uint32 blocks = scsiDev.cdb[4];\r
312                 if (blocks == 0) blocks = 256;\r
313                 doWrite(lba, blocks);\r
314         }\r
315 \r
316         else if (command == 0x2A)\r
317         {\r
318                 // WRITE(10)\r
319                 // Ignore all cache control bits - we don't support a memory cache.\r
320 \r
321                 uint32 lba =\r
322                         (((uint32) scsiDev.cdb[2]) << 24) +\r
323                         (((uint32) scsiDev.cdb[3]) << 16) +\r
324                         (((uint32) scsiDev.cdb[4]) << 8) +\r
325                         scsiDev.cdb[5];\r
326                 uint32 blocks =\r
327                         (((uint32) scsiDev.cdb[7]) << 8) +\r
328                         scsiDev.cdb[8];\r
329 \r
330                 doWrite(lba, blocks);\r
331         }\r
332         else if (command == 0x36)\r
333         {\r
334                 // LOCK UNLOCK CACHE\r
335                 // We don't have a cache to lock data into. do nothing.\r
336         }\r
337         else if (command == 0x34)\r
338         {\r
339                 // PRE-FETCH.\r
340                 // We don't have a cache to pre-fetch into. do nothing.\r
341         }\r
342         else if (command == 0x1E)\r
343         {\r
344                 // PREVENT ALLOW MEDIUM REMOVAL\r
345                 // Not much we can do to prevent the user removing the SD card.\r
346                 // do nothing.\r
347         }\r
348         else if (command == 0x01)\r
349         {\r
350                 // REZERO UNIT\r
351                 // Set the lun to a vendor-specific state. Ignore.\r
352         }\r
353         else if (command == 0x35)\r
354         {\r
355                 // SYNCHRONIZE CACHE\r
356                 // We don't have a cache. do nothing.\r
357         }\r
358         else if (command == 0x2F)\r
359         {\r
360                 // VERIFY\r
361                 // TODO: When they supply data to verify, we should read the data and\r
362                 // verify it. If they don't supply any data, just say success.\r
363                 if ((scsiDev.cdb[1] & 0x02) == 0)\r
364                 {\r
365                         // They are asking us to do a medium verification with no data\r
366                         // comparison. Assume success, do nothing.\r
367                 }\r
368                 else\r
369                 {\r
370                         // TODO. This means they are supplying data to verify against.\r
371                         // Technically we should probably grab the data and compare it.\r
372                         scsiDev.status = CHECK_CONDITION;\r
373                         scsiDev.sense.code = ILLEGAL_REQUEST;\r
374                         scsiDev.sense.asc = INVALID_FIELD_IN_CDB;\r
375                         scsiDev.phase = STATUS;\r
376                 }\r
377         }\r
378         else\r
379         {\r
380                 commandHandled = 0;\r
381         }\r
382 \r
383         return commandHandled;\r
384 }\r
385 \r
386 void scsiDiskPoll()\r
387 {\r
388         if (scsiDev.phase == DATA_IN &&\r
389                 transfer.currentBlock != transfer.blocks)\r
390         {\r
391                 if (scsiDev.dataLen == 0)\r
392                 {\r
393                         if (transfer.multiBlock)\r
394                         {\r
395                                 sdReadSectorMulti();\r
396                         }\r
397                         else\r
398                         {\r
399                                 sdReadSectorSingle();\r
400                         }\r
401                 }\r
402                 else if (scsiDev.dataPtr == scsiDev.dataLen)\r
403                 {\r
404                         scsiDev.dataLen = 0;\r
405                         scsiDev.dataPtr = 0;\r
406                         transfer.currentBlock++;\r
407                         if (transfer.currentBlock >= transfer.blocks)\r
408                         {\r
409                                 int needComplete = transfer.multiBlock;\r
410                                 scsiDev.phase = STATUS;\r
411                                 scsiDiskReset();\r
412                                 if (needComplete)\r
413                                 {\r
414                                         sdCompleteRead();\r
415                                 }\r
416                         }\r
417                 }\r
418         }\r
419         else if (scsiDev.phase == DATA_OUT &&\r
420                 transfer.currentBlock != transfer.blocks)\r
421         {\r
422                 int writeOk = sdWriteSector();\r
423                 // TODO FIX scsiDiskPoll() scsiDev.dataPtr = 0;\r
424                 transfer.currentBlock++;\r
425                 if (transfer.currentBlock >= transfer.blocks)\r
426                 {\r
427                         scsiDev.dataLen = 0;\r
428                         scsiDev.dataPtr = 0;\r
429                         scsiDev.phase = STATUS;\r
430 \r
431                         scsiDiskReset();\r
432 \r
433                         if (writeOk)\r
434                         {\r
435                                 sdCompleteWrite();\r
436                         }\r
437                 }\r
438         }\r
439 }\r
440 \r
441 void scsiDiskReset()\r
442 {\r
443  // todo if SPI command in progress, cancel it.\r
444         scsiDev.dataPtr = 0;\r
445         scsiDev.savedDataPtr = 0;\r
446         scsiDev.dataLen = 0;\r
447         transfer.lba = 0;\r
448         transfer.blocks = 0;\r
449         transfer.currentBlock = 0;\r
450         transfer.multiBlock = 0;\r
451 }\r
452 \r
453 void scsiDiskInit()\r
454 {\r
455         blockDev.bs = SCSI_BLOCK_SIZE;\r
456         blockDev.capacity = 0;\r
457         scsiDiskReset();\r
458 \r
459         // Don't require the host to send us a START STOP UNIT command\r
460         blockDev.state = DISK_STARTED;\r
461         // WP pin not available for micro-sd\r
462         // TODO read card WP register\r
463         #if 0\r
464         if (SD_WP_Read())\r
465         {\r
466                 blockDev.state = blockDev.state | DISK_WP;\r
467         }\r
468         #endif\r
469 \r
470         if (SD_CD_Read() == 1)\r
471         {\r
472                 int retry;\r
473                 blockDev.state = blockDev.state | DISK_PRESENT;\r
474 \r
475                 // Wait up to 5 seconds for the SD card to wake up.\r
476                 for (retry = 0; retry < 5; ++retry)\r
477                 {\r
478                         if (doSdInit())\r
479                         {\r
480                                 break;\r
481                         }\r
482                         else\r
483                         {\r
484                                 CyDelay(1000);\r
485                         }\r
486                 }\r
487         }\r
488 }\r
489 \r