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