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