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