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