SD card interface rewrite to improve compatibility with some SD cards.
[SCSI2SD.git] / software / SCSI2SD / src / scsi.c
1 //      Copyright (C) 2014 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 "scsiPhy.h"\r
21 #include "config.h"\r
22 #include "bits.h"\r
23 #include "diagnostic.h"\r
24 #include "disk.h"\r
25 #include "inquiry.h"\r
26 #include "led.h"\r
27 #include "mode.h"\r
28 #include "disk.h"\r
29 #include "time.h"\r
30 #include "cdrom.h"\r
31 \r
32 #include <string.h>\r
33 \r
34 // Global SCSI device state.\r
35 ScsiDevice scsiDev;\r
36 \r
37 static void enter_SelectionPhase(void);\r
38 static void process_SelectionPhase(void);\r
39 static void enter_BusFree(void);\r
40 static void enter_MessageIn(uint8 message);\r
41 static void enter_Status(uint8 status);\r
42 static void enter_DataIn(int len);\r
43 static void process_DataIn(void);\r
44 static void process_DataOut(void);\r
45 static void process_Command(void);\r
46 \r
47 static void doReserveRelease(void);\r
48 \r
49 static void enter_BusFree()\r
50 {\r
51         // This delay probably isn't needed for most SCSI hosts, but it won't\r
52         // hurt either. It's possible some of the samplers needed this delay.\r
53         CyDelayUs(2);\r
54 \r
55         SCSI_ClearPin(SCSI_Out_BSY);\r
56         // We now have a Bus Clear Delay of 800ns to release remaining signals.\r
57         SCSI_CTL_PHASE_Write(0);\r
58 \r
59         // Wait for the initiator to cease driving signals\r
60         // Bus settle delay + bus clear delay = 1200ns\r
61         CyDelayUs(2);\r
62 \r
63         ledOff();\r
64         scsiDev.phase = BUS_FREE;\r
65 }\r
66 \r
67 static void enter_MessageIn(uint8 message)\r
68 {\r
69         scsiDev.msgIn = message;\r
70         scsiDev.phase = MESSAGE_IN;\r
71 }\r
72 \r
73 void process_MessageIn()\r
74 {\r
75         scsiEnterPhase(MESSAGE_IN);\r
76         scsiWriteByte(scsiDev.msgIn);\r
77 \r
78         if (scsiDev.atnFlag)\r
79         {\r
80                 // If there was a parity error, we go\r
81                 // back to MESSAGE_OUT first, get out parity error message, then come\r
82                 // back here.\r
83         }\r
84         else if ((scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE) ||\r
85                 (scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG))\r
86         {\r
87                 // Go back to the command phase and start again.\r
88                 scsiDev.phase = COMMAND;\r
89                 scsiDev.parityError = 0;\r
90                 scsiDev.dataPtr = 0;\r
91                 scsiDev.savedDataPtr = 0;\r
92                 scsiDev.dataLen = 0;\r
93                 scsiDev.status = GOOD;\r
94                 transfer.blocks = 0;\r
95                 transfer.currentBlock = 0;\r
96         }\r
97         else /*if (scsiDev.msgIn == MSG_COMMAND_COMPLETE)*/\r
98         {\r
99                 enter_BusFree();\r
100         }\r
101 }\r
102 \r
103 static void messageReject()\r
104 {\r
105         scsiEnterPhase(MESSAGE_IN);\r
106         scsiWriteByte(MSG_REJECT);\r
107 }\r
108 \r
109 static void enter_Status(uint8 status)\r
110 {\r
111         scsiDev.status = status;\r
112         scsiDev.phase = STATUS;\r
113 \r
114         scsiDev.lastStatus = scsiDev.status;\r
115         scsiDev.lastSense = scsiDev.target->sense.code;\r
116         scsiDev.lastSenseASC = scsiDev.target->sense.asc;\r
117 }\r
118 \r
119 void process_Status()\r
120 {\r
121         scsiEnterPhase(STATUS);\r
122 \r
123         uint8 message;\r
124 \r
125         uint8 control = scsiDev.cdb[scsiDev.cdbLen - 1];\r
126         if ((scsiDev.status == GOOD) && (control & 0x01))\r
127         {\r
128                 // Linked command.\r
129                 scsiDev.status = INTERMEDIATE;\r
130                 if (control & 0x02)\r
131                 {\r
132                         message = MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG;\r
133                 }\r
134                 else\r
135                 {\r
136                         message = MSG_LINKED_COMMAND_COMPLETE;\r
137                 }\r
138         }\r
139         else\r
140         {\r
141                 message = MSG_COMMAND_COMPLETE;\r
142         }\r
143         scsiWriteByte(scsiDev.status);\r
144 \r
145         scsiDev.lastStatus = scsiDev.status;\r
146         scsiDev.lastSense = scsiDev.target->sense.code;\r
147         scsiDev.lastSenseASC = scsiDev.target->sense.asc;\r
148 \r
149 \r
150         // Command Complete occurs AFTER a valid status has been\r
151         // sent. then we go bus-free.\r
152         enter_MessageIn(message);\r
153 }\r
154 \r
155 static void enter_DataIn(int len)\r
156 {\r
157         scsiDev.dataLen = len;\r
158         scsiDev.phase = DATA_IN;\r
159 }\r
160 \r
161 static void process_DataIn()\r
162 {\r
163         uint32 len;\r
164 \r
165         if (scsiDev.dataLen > sizeof(scsiDev.data))\r
166         {\r
167                 scsiDev.dataLen = sizeof(scsiDev.data);\r
168         }\r
169 \r
170         len = scsiDev.dataLen - scsiDev.dataPtr;\r
171         if (len > 0)\r
172         {\r
173                 scsiEnterPhase(DATA_IN);\r
174                 scsiWrite(scsiDev.data + scsiDev.dataPtr, len);\r
175                 scsiDev.dataPtr += len;\r
176         }\r
177 \r
178         if ((scsiDev.dataPtr >= scsiDev.dataLen) &&\r
179                 (transfer.currentBlock == transfer.blocks))\r
180         {\r
181                 enter_Status(GOOD);\r
182         }\r
183 }\r
184 \r
185 static void process_DataOut()\r
186 {\r
187         uint32 len;\r
188 \r
189         if (scsiDev.dataLen > sizeof(scsiDev.data))\r
190         {\r
191                 scsiDev.dataLen = sizeof(scsiDev.data);\r
192         }\r
193 \r
194         scsiDev.parityError = 0;\r
195         len = scsiDev.dataLen - scsiDev.dataPtr;\r
196         if (len > 0)\r
197         {\r
198                 scsiEnterPhase(DATA_OUT);\r
199 \r
200                 scsiRead(scsiDev.data + scsiDev.dataPtr, len);\r
201                 scsiDev.dataPtr += len;\r
202 \r
203                 if (scsiDev.parityError &&\r
204                         (scsiDev.target->cfg->flags & CONFIG_ENABLE_PARITY) &&\r
205                         !scsiDev.compatMode)\r
206                 {\r
207                         scsiDev.target->sense.code = ABORTED_COMMAND;\r
208                         scsiDev.target->sense.asc = SCSI_PARITY_ERROR;\r
209                         enter_Status(CHECK_CONDITION);\r
210                 }\r
211         }\r
212 \r
213         if ((scsiDev.dataPtr >= scsiDev.dataLen) &&\r
214                 (transfer.currentBlock == transfer.blocks))\r
215         {\r
216                 if (scsiDev.postDataOutHook != NULL)\r
217                 {\r
218                         scsiDev.postDataOutHook();\r
219                 }\r
220                 else\r
221                 {\r
222                         enter_Status(GOOD);\r
223                 }\r
224         }\r
225 }\r
226 \r
227 static const uint8 CmdGroupBytes[8] = {6, 10, 10, 6, 6, 12, 6, 6};\r
228 static void process_Command()\r
229 {\r
230         int group;\r
231         uint8 command;\r
232         uint8 control;\r
233 \r
234         scsiEnterPhase(COMMAND);\r
235         scsiDev.parityError = 0;\r
236 \r
237         memset(scsiDev.cdb, 0, sizeof(scsiDev.cdb));\r
238         scsiDev.cdb[0] = scsiReadByte();\r
239 \r
240         group = scsiDev.cdb[0] >> 5;\r
241         scsiDev.cdbLen = CmdGroupBytes[group];\r
242         scsiRead(scsiDev.cdb + 1, scsiDev.cdbLen - 1);\r
243 \r
244         command = scsiDev.cdb[0];\r
245 \r
246         // Prefer LUN's set by IDENTIFY messages for newer hosts.\r
247         if (scsiDev.lun < 0)\r
248         {\r
249                 scsiDev.lun = scsiDev.cdb[1] >> 5;\r
250         }\r
251 \r
252         control = scsiDev.cdb[scsiDev.cdbLen - 1];\r
253 \r
254         scsiDev.cmdCount++;\r
255 \r
256         if (scsiDev.resetFlag)\r
257         {\r
258                 // Don't log bogus commands\r
259                 scsiDev.cmdCount--;\r
260                 memset(scsiDev.cdb, 0xff, sizeof(scsiDev.cdb));\r
261                 return;\r
262         }\r
263         else if (scsiDev.parityError &&\r
264                 (scsiDev.target->cfg->flags & CONFIG_ENABLE_PARITY) &&\r
265                 !scsiDev.compatMode)\r
266         {\r
267                 scsiDev.target->sense.code = ABORTED_COMMAND;\r
268                 scsiDev.target->sense.asc = SCSI_PARITY_ERROR;\r
269                 enter_Status(CHECK_CONDITION);\r
270         }\r
271         else if ((control & 0x02) && ((control & 0x01) == 0))\r
272         {\r
273                 // FLAG set without LINK flag.\r
274                 scsiDev.target->sense.code = ILLEGAL_REQUEST;\r
275                 scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;\r
276                 enter_Status(CHECK_CONDITION);\r
277         }\r
278         else if (command == 0x12)\r
279         {\r
280                 scsiInquiry();\r
281         }\r
282         else if (command == 0x03)\r
283         {\r
284                 // REQUEST SENSE\r
285                 uint32 allocLength = scsiDev.cdb[4];\r
286 \r
287                 // As specified by the SASI and SCSI1 standard.\r
288                 // Newer initiators won't be specifying 0 anyway.\r
289                 if (allocLength == 0) allocLength = 4;\r
290 \r
291                 memset(scsiDev.data, 0, 256); // Max possible alloc length\r
292                 scsiDev.data[0] = 0xF0;\r
293                 scsiDev.data[2] = scsiDev.target->sense.code & 0x0F;\r
294 \r
295                 scsiDev.data[3] = transfer.lba >> 24;\r
296                 scsiDev.data[4] = transfer.lba >> 16;\r
297                 scsiDev.data[5] = transfer.lba >> 8;\r
298                 scsiDev.data[6] = transfer.lba;\r
299 \r
300                 // Additional bytes if there are errors to report\r
301                 scsiDev.data[7] = 10; // additional length\r
302                 scsiDev.data[12] = scsiDev.target->sense.asc >> 8;\r
303                 scsiDev.data[13] = scsiDev.target->sense.asc;\r
304 \r
305                 // Silently truncate results. SCSI-2 spec 8.2.14.\r
306                 enter_DataIn(allocLength);\r
307 \r
308                 // This is a good time to clear out old sense information.\r
309                 scsiDev.target->sense.code = NO_SENSE;\r
310                 scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;\r
311         }\r
312         // Some old SCSI drivers do NOT properly support\r
313         // unitAttention. eg. the Mac Plus would trigger a SCSI reset\r
314         // on receiving the unit attention response on boot, thus\r
315         // triggering another unit attention condition.\r
316         else if (scsiDev.target->unitAttention &&\r
317                 (scsiDev.target->cfg->flags & CONFIG_ENABLE_UNIT_ATTENTION))\r
318         {\r
319                 scsiDev.target->sense.code = UNIT_ATTENTION;\r
320                 scsiDev.target->sense.asc = scsiDev.target->unitAttention;\r
321 \r
322                 // If initiator doesn't do REQUEST SENSE for the next command, then\r
323                 // data is lost.\r
324                 scsiDev.target->unitAttention = 0;\r
325 \r
326                 enter_Status(CHECK_CONDITION);\r
327         }\r
328         else if (scsiDev.lun)\r
329         {\r
330                 scsiDev.target->sense.code = ILLEGAL_REQUEST;\r
331                 scsiDev.target->sense.asc = LOGICAL_UNIT_NOT_SUPPORTED;\r
332                 enter_Status(CHECK_CONDITION);\r
333         }\r
334         else if (command == 0x17 || command == 0x16)\r
335         {\r
336                 doReserveRelease();\r
337         }\r
338         else if ((scsiDev.target->reservedId >= 0) &&\r
339                 (scsiDev.target->reservedId != scsiDev.initiatorId))\r
340         {\r
341                 enter_Status(CONFLICT);\r
342         }\r
343         else if (command == 0x1C)\r
344         {\r
345                 scsiReceiveDiagnostic();\r
346         }\r
347         else if (command == 0x1D)\r
348         {\r
349                 scsiSendDiagnostic();\r
350         }\r
351         else if (command == 0x3C)\r
352         {\r
353                 scsiReadBuffer();\r
354         }\r
355         else if (\r
356                 !scsiModeCommand() &&\r
357                 !scsiDiskCommand() &&\r
358                 !scsiCDRomCommand())\r
359         {\r
360                 scsiDev.target->sense.code = ILLEGAL_REQUEST;\r
361                 scsiDev.target->sense.asc = INVALID_COMMAND_OPERATION_CODE;\r
362                 enter_Status(CHECK_CONDITION);\r
363         }\r
364 \r
365         // Successful\r
366         if (scsiDev.phase == COMMAND) // No status set, and not in DATA_IN\r
367         {\r
368                 enter_Status(GOOD);\r
369         }\r
370 \r
371 }\r
372 \r
373 static void doReserveRelease()\r
374 {\r
375         int extentReservation = scsiDev.cdb[1] & 1;\r
376         int thirdPty = scsiDev.cdb[1] & 0x10;\r
377         int thirdPtyId = (scsiDev.cdb[1] >> 1) & 0x7;\r
378         uint8 command = scsiDev.cdb[0];\r
379 \r
380         int canRelease =\r
381                 (!thirdPty && (scsiDev.initiatorId == scsiDev.target->reservedId)) ||\r
382                         (thirdPty &&\r
383                                 (scsiDev.target->reserverId == scsiDev.initiatorId) &&\r
384                                 (scsiDev.target->reservedId == thirdPtyId)\r
385                         );\r
386 \r
387         if (extentReservation)\r
388         {\r
389                 // Not supported.\r
390                 scsiDev.target->sense.code = ILLEGAL_REQUEST;\r
391                 scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;\r
392                 enter_Status(CHECK_CONDITION);\r
393         }\r
394         else if (command == 0x17) // release\r
395         {\r
396                 if ((scsiDev.target->reservedId < 0) || canRelease)\r
397                 {\r
398                         scsiDev.target->reservedId = -1;\r
399                         scsiDev.target->reserverId = -1;\r
400                 }\r
401                 else\r
402                 {\r
403                         enter_Status(CONFLICT);\r
404                 }\r
405         }\r
406         else // assume reserve.\r
407         {\r
408                 if ((scsiDev.target->reservedId < 0) || canRelease)\r
409                 {\r
410                         scsiDev.target->reserverId = scsiDev.initiatorId;\r
411                         if (thirdPty)\r
412                         {\r
413                                 scsiDev.target->reservedId = thirdPtyId;\r
414                         }\r
415                         else\r
416                         {\r
417                                 scsiDev.target->reservedId = scsiDev.initiatorId;\r
418                         }\r
419                 }\r
420                 else\r
421                 {\r
422                         // Already reserved by someone else!\r
423                         enter_Status(CONFLICT);\r
424                 }\r
425         }\r
426 }\r
427 \r
428 static void scsiReset()\r
429 {\r
430         scsiDev.rstCount++;\r
431         ledOff();\r
432 \r
433         scsiPhyReset();\r
434         SCSI_Out_Ctl_Write(0);\r
435 \r
436         scsiDev.parityError = 0;\r
437         scsiDev.phase = BUS_FREE;\r
438         scsiDev.atnFlag = 0;\r
439         scsiDev.resetFlag = 0;\r
440         scsiDev.lun = -1;\r
441 \r
442         if (scsiDev.target)\r
443         {\r
444                 if (scsiDev.target->unitAttention != POWER_ON_RESET)\r
445                 {\r
446                         scsiDev.target->unitAttention = SCSI_BUS_RESET;\r
447                 }\r
448                 scsiDev.target->reservedId = -1;\r
449                 scsiDev.target->reserverId = -1;\r
450                 scsiDev.target->sense.code = NO_SENSE;\r
451                 scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;\r
452         }\r
453         scsiDev.target = NULL;\r
454         scsiDiskReset();\r
455 \r
456         scsiDev.postDataOutHook = NULL;\r
457 \r
458         // Sleep to allow the bus to settle down a bit.\r
459         // We must be ready again within the "Reset to selection time" of\r
460         // 250ms.\r
461         // There is no guarantee that the RST line will be negated by then.\r
462         // NOTE: We could be connected and powered by USB for configuration,\r
463         // in which case TERMPWR cannot be supplied, and reset will ALWAYS\r
464         // be true. Therefore, the sleep here must be slow to avoid slowing\r
465         // USB comms\r
466         CyDelay(1); // 1ms.\r
467 }\r
468 \r
469 static void enter_SelectionPhase()\r
470 {\r
471         // Ignore stale versions of this flag, but ensure we know the\r
472         // current value if the flag is still set.\r
473         scsiDev.atnFlag = 0;\r
474         scsiDev.parityError = 0;\r
475         scsiDev.dataPtr = 0;\r
476         scsiDev.savedDataPtr = 0;\r
477         scsiDev.dataLen = 0;\r
478         scsiDev.status = GOOD;\r
479         scsiDev.phase = SELECTION;\r
480         scsiDev.lun = -1;\r
481         scsiDev.discPriv = 0;\r
482         scsiDev.compatMode = 0;\r
483 \r
484         scsiDev.initiatorId = -1;\r
485         scsiDev.target = NULL;\r
486 \r
487         transfer.blocks = 0;\r
488         transfer.currentBlock = 0;\r
489 \r
490         scsiDev.postDataOutHook = NULL;\r
491 }\r
492 \r
493 static void process_SelectionPhase()\r
494 {\r
495         int sel = SCSI_ReadFilt(SCSI_Filt_SEL);\r
496         int bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
497 \r
498         // Only read these pins AFTER SEL and BSY - we don't want to catch them\r
499         // during a transition period.\r
500         uint8 mask = scsiReadDBxPins();\r
501         int maskBitCount = countBits(mask);\r
502         int goodParity = (Lookup_OddParity[mask] == SCSI_ReadPin(SCSI_In_DBP));\r
503         int atnFlag = SCSI_ReadFilt(SCSI_Filt_ATN);\r
504 \r
505         int tgtIndex;\r
506         TargetState* target = NULL;\r
507         for (tgtIndex = 0; tgtIndex < MAX_SCSI_TARGETS; ++tgtIndex)\r
508         {\r
509                 if (mask & (1 << scsiDev.targets[tgtIndex].targetId))\r
510                 {\r
511                         target = &scsiDev.targets[tgtIndex];\r
512                         break;\r
513                 }\r
514         }\r
515         if (!bsy && sel &&\r
516                 target &&\r
517                 (goodParity || !(target->cfg->flags & CONFIG_ENABLE_PARITY) || !atnFlag) &&\r
518                 (maskBitCount <= 2))\r
519         {\r
520                 scsiDev.target = target;\r
521 \r
522                 // Do we enter MESSAGE OUT immediately ? SCSI 1 and 2 standards says\r
523                 // move to MESSAGE OUT if ATN is true before we assert BSY.\r
524                 // The initiator should assert ATN with SEL.\r
525                 scsiDev.atnFlag = atnFlag;\r
526 \r
527                 // Unit attention breaks many older SCSI hosts. Disable it completely\r
528                 // for SCSI-1 (and older) hosts, regardless of our configured setting.\r
529                 // Enable the compatability mode also as many SASI and SCSI1\r
530                 // controllers don't generate parity bits.\r
531                 if (!scsiDev.atnFlag)\r
532                 {\r
533                         target->unitAttention = 0;\r
534                         scsiDev.compatMode = 1;\r
535                 }\r
536 \r
537                 // We've been selected!\r
538                 // Assert BSY - Selection success!\r
539                 // must happen within 200us (Selection abort time) of seeing our\r
540                 // ID + SEL.\r
541                 // (Note: the initiator will be waiting the "Selection time-out delay"\r
542                 // for our BSY response, which is actually a very generous 250ms)\r
543                 SCSI_SetPin(SCSI_Out_BSY);\r
544                 ledOn();\r
545 \r
546                 scsiDev.selCount++;\r
547 \r
548                 // Wait until the end of the selection phase.\r
549                 while (!scsiDev.resetFlag)\r
550                 {\r
551                         if (!SCSI_ReadFilt(SCSI_Filt_SEL))\r
552                         {\r
553                                 break;\r
554                         }\r
555                 }\r
556 \r
557                 // Save our initiator now that we're no longer in a time-critical\r
558                 // section.\r
559                 // SCSI1/SASI initiators may not set their own ID.\r
560                 {\r
561                         int i;\r
562                         uint8_t initiatorMask = mask ^ (1 << target->targetId);\r
563                         scsiDev.initiatorId = -1;\r
564                         for (i = 0; i < 8; ++i)\r
565                         {\r
566                                 if (initiatorMask & (1 << i))\r
567                                 {\r
568                                         scsiDev.initiatorId = i;\r
569                                         break;\r
570                                 }\r
571                         }\r
572                 }\r
573 \r
574                 scsiDev.phase = COMMAND;\r
575         }\r
576         else if (!sel)\r
577         {\r
578                 scsiDev.phase = BUS_BUSY;\r
579         }\r
580 }\r
581 \r
582 static void process_MessageOut()\r
583 {\r
584         scsiEnterPhase(MESSAGE_OUT);\r
585 \r
586         scsiDev.atnFlag = 0;\r
587         scsiDev.parityError = 0;\r
588         scsiDev.msgOut = scsiReadByte();\r
589         scsiDev.msgCount++;\r
590 \r
591         if (scsiDev.parityError &&\r
592                 (scsiDev.target->cfg->flags & CONFIG_ENABLE_PARITY) &&\r
593                 !scsiDev.compatMode)\r
594         {\r
595                 // Skip the remaining message bytes, and then start the MESSAGE_OUT\r
596                 // phase again from the start. The initiator will re-send the\r
597                 // same set of messages.\r
598                 while (SCSI_ReadFilt(SCSI_Filt_ATN) && !scsiDev.resetFlag)\r
599                 {\r
600                         scsiReadByte();\r
601                 }\r
602 \r
603                 // Go-back and try the message again.\r
604                 scsiDev.atnFlag = 1;\r
605                 scsiDev.parityError = 0;\r
606         }\r
607         else if (scsiDev.msgOut == 0x00)\r
608         {\r
609                 // COMMAND COMPLETE. but why would the target be receiving this ? nfi.\r
610                 enter_BusFree();\r
611         }\r
612         else if (scsiDev.msgOut == 0x06)\r
613         {\r
614                 // ABORT\r
615                 scsiDiskReset();\r
616                 enter_BusFree();\r
617         }\r
618         else if (scsiDev.msgOut == 0x0C)\r
619         {\r
620                 // BUS DEVICE RESET\r
621 \r
622                 scsiDiskReset();\r
623 \r
624                 scsiDev.target->unitAttention = SCSI_BUS_RESET;\r
625 \r
626                 // ANY initiator can reset the reservation state via this message.\r
627                 scsiDev.target->reservedId = -1;\r
628                 scsiDev.target->reserverId = -1;\r
629                 enter_BusFree();\r
630         }\r
631         else if (scsiDev.msgOut == 0x05)\r
632         {\r
633                 // Initiate Detected Error\r
634                 // Ignore for now\r
635         }\r
636         else if (scsiDev.msgOut == 0x0F)\r
637         {\r
638                 // INITIATE RECOVERY\r
639                 // Ignore for now\r
640         }\r
641         else if (scsiDev.msgOut == 0x10)\r
642         {\r
643                 // RELEASE RECOVERY\r
644                 // Ignore for now\r
645                 enter_BusFree();\r
646         }\r
647         else if (scsiDev.msgOut == MSG_REJECT)\r
648         {\r
649                 // Message Reject\r
650                 // Oh well.\r
651                 scsiDev.resetFlag = 1;\r
652         }\r
653         else if (scsiDev.msgOut == 0x08)\r
654         {\r
655                 // NOP\r
656         }\r
657         else if (scsiDev.msgOut == 0x09)\r
658         {\r
659                 // Message Parity Error\r
660                 // Go back and re-send the last message.\r
661                 scsiDev.phase = MESSAGE_IN;\r
662         }\r
663         else if (scsiDev.msgOut & 0x80) // 0x80 -> 0xFF\r
664         {\r
665                 // IDENTIFY\r
666                 if ((scsiDev.msgOut & 0x18) || // Reserved bits set.\r
667                         (scsiDev.msgOut & 0x20))  // We don't have any target routines!\r
668                 {\r
669                         messageReject();\r
670                 }\r
671 \r
672                 scsiDev.lun = scsiDev.msgOut & 0x7;\r
673                 scsiDev.discPriv = \r
674                         ((scsiDev.msgOut & 0x40) && (scsiDev.initiatorId >= 0))\r
675                                 ? 1 : 0;\r
676         }\r
677         else if (scsiDev.msgOut >= 0x20 && scsiDev.msgOut <= 0x2F)\r
678         {\r
679                 // Two byte message. We don't support these. read and discard.\r
680                 scsiReadByte();\r
681         }\r
682         else if (scsiDev.msgOut == 0x01)\r
683         {\r
684                 int i;\r
685 \r
686                 // Extended message.\r
687                 int msgLen = scsiReadByte();\r
688                 if (msgLen == 0) msgLen = 256;\r
689                 for (i = 0; i < msgLen && !scsiDev.resetFlag; ++i)\r
690                 {\r
691                         // Discard bytes.\r
692                         scsiReadByte();\r
693                 }\r
694 \r
695                 // We don't support ANY extended messages.\r
696                 // Modify Data Pointer:  We don't support reselection.\r
697                 // Wide Data Transfer Request: No. 8bit only.\r
698                 // Synchronous data transfer request. No, we can't do that.\r
699                 // We don't support any 2-byte messages either.\r
700                 // And we don't support any optional 1-byte messages.\r
701                 // In each case, the correct response is MESSAGE REJECT.\r
702                 messageReject();\r
703         }\r
704         else\r
705         {\r
706                 messageReject();\r
707         }\r
708 \r
709         // Re-check the ATN flag in case it stays asserted.\r
710         scsiDev.atnFlag |= SCSI_ReadFilt(SCSI_Filt_ATN);\r
711 }\r
712 \r
713 void scsiPoll(void)\r
714 {\r
715         if (scsiDev.resetFlag)\r
716         {\r
717                 scsiReset();\r
718                 if ((scsiDev.resetFlag = SCSI_ReadFilt(SCSI_Filt_RST)))\r
719                 {\r
720                         // Still in reset phase. Do not try and process any commands.\r
721                         return;\r
722                 }\r
723         }\r
724 \r
725         switch (scsiDev.phase)\r
726         {\r
727         case BUS_FREE:\r
728                 if (SCSI_ReadFilt(SCSI_Filt_BSY))\r
729                 {\r
730                         scsiDev.phase = BUS_BUSY;\r
731                 }\r
732                 // The Arbitration phase is optional for SCSI1/SASI hosts if there is only\r
733                 // one initiator in the chain. Support this by moving\r
734                 // straight to selection if SEL is asserted.\r
735                 // ie. the initiator won't assert BSY and it's own ID before moving to selection.\r
736                 else if (SCSI_ReadFilt(SCSI_Filt_SEL))\r
737                 {\r
738                         enter_SelectionPhase();\r
739                 }\r
740         break;\r
741 \r
742         case BUS_BUSY:\r
743                 // Someone is using the bus. Perhaps they are trying to\r
744                 // select us.\r
745                 if (SCSI_ReadFilt(SCSI_Filt_SEL))\r
746                 {\r
747                         enter_SelectionPhase();\r
748                 }\r
749                 else if (!SCSI_ReadFilt(SCSI_Filt_BSY))\r
750                 {\r
751                         scsiDev.phase = BUS_FREE;\r
752                 }\r
753         break;\r
754 \r
755         case ARBITRATION:\r
756                 // TODO Support reselection.\r
757                 break;\r
758 \r
759         case SELECTION:\r
760                 process_SelectionPhase();\r
761         break;\r
762 \r
763         case RESELECTION:\r
764                 // Not currently supported!\r
765         break;\r
766 \r
767         case COMMAND:\r
768                 // Do not check ATN here. SCSI 1 & 2 initiators must set ATN\r
769                 // and SEL together upon entering the selection phase if they\r
770                 // want to send a message (IDENTIFY) immediately.\r
771                 if (scsiDev.atnFlag)\r
772                 {\r
773                         process_MessageOut();\r
774                 }\r
775                 else\r
776                 {\r
777                         process_Command();\r
778                 }\r
779         break;\r
780 \r
781         case DATA_IN:\r
782                 scsiDev.atnFlag |= SCSI_ReadFilt(SCSI_Filt_ATN);\r
783                 if (scsiDev.atnFlag)\r
784                 {\r
785                         process_MessageOut();\r
786                 }\r
787                 else\r
788                 {\r
789                         process_DataIn();\r
790                 }\r
791         break;\r
792 \r
793         case DATA_OUT:\r
794                 scsiDev.atnFlag |= SCSI_ReadFilt(SCSI_Filt_ATN);\r
795                 if (scsiDev.atnFlag)\r
796                 {\r
797                         process_MessageOut();\r
798                 }\r
799                 else\r
800                 {\r
801                         process_DataOut();\r
802                 }\r
803         break;\r
804 \r
805         case STATUS:\r
806                 scsiDev.atnFlag |= SCSI_ReadFilt(SCSI_Filt_ATN);\r
807                 if (scsiDev.atnFlag)\r
808                 {\r
809                         process_MessageOut();\r
810                 }\r
811                 else\r
812                 {\r
813                         process_Status();\r
814                 }\r
815         break;\r
816 \r
817         case MESSAGE_IN:\r
818                 scsiDev.atnFlag |= SCSI_ReadFilt(SCSI_Filt_ATN);\r
819                 if (scsiDev.atnFlag)\r
820                 {\r
821                         process_MessageOut();\r
822                 }\r
823                 else\r
824                 {\r
825                         process_MessageIn();\r
826                 }\r
827 \r
828         break;\r
829 \r
830         case MESSAGE_OUT:\r
831                 process_MessageOut();\r
832         break;\r
833         }\r
834 }\r
835 \r
836 void scsiInit()\r
837 {\r
838         scsiDev.atnFlag = 0;\r
839         scsiDev.resetFlag = 1;\r
840         scsiDev.phase = BUS_FREE;\r
841         scsiDev.target = NULL;\r
842 \r
843         int i;\r
844         for (i = 0; i < MAX_SCSI_TARGETS; ++i)\r
845         {\r
846                 const TargetConfig* cfg = getConfigByIndex(i);\r
847                 if (cfg && (cfg->scsiId & CONFIG_TARGET_ENABLED))\r
848                 {\r
849                         scsiDev.targets[i].targetId = cfg->scsiId & CONFIG_TARGET_ID_BITS;\r
850                         scsiDev.targets[i].cfg = cfg;\r
851 \r
852                         scsiDev.targets[i].liveCfg.bytesPerSector = cfg->bytesPerSector;\r
853                 }\r
854                 else\r
855                 {\r
856                         scsiDev.targets[i].targetId = 0xff;\r
857                         scsiDev.targets[i].cfg = NULL;\r
858                 }\r
859                 scsiDev.targets[i].reservedId = -1;\r
860                 scsiDev.targets[i].reserverId = -1;\r
861                 scsiDev.targets[i].unitAttention = POWER_ON_RESET;\r
862                 scsiDev.targets[i].sense.code = NO_SENSE;\r
863                 scsiDev.targets[i].sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;\r
864         }\r
865 }\r
866 \r
867 void scsiDisconnect()\r
868 {\r
869         scsiEnterPhase(MESSAGE_IN);\r
870         scsiWriteByte(0x02); // save data pointer\r
871         scsiWriteByte(0x04); // disconnect msg.\r
872 \r
873         // For now, the caller is responsible for tracking the disconnected\r
874         // state, and calling scsiReconnect.\r
875         // Ideally the client would exit their loop and we'd implement this\r
876         // as part of scsiPoll\r
877         int phase = scsiDev.phase;\r
878         enter_BusFree();\r
879         scsiDev.phase = phase;\r
880 }\r
881 \r
882 int scsiReconnect()\r
883 {\r
884         int reconnected = 0;\r
885 \r
886         int sel = SCSI_ReadFilt(SCSI_Filt_SEL);\r
887         int bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
888         if (!sel && !bsy)\r
889         {\r
890                 CyDelayUs(1);\r
891                 sel = SCSI_ReadFilt(SCSI_Filt_SEL);\r
892                 bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
893         }\r
894 \r
895         if (!sel && !bsy)\r
896         {\r
897                 // Arbitrate.\r
898                 ledOn();\r
899                 uint8_t scsiIdMask = 1 << scsiDev.target->targetId;\r
900                 SCSI_Out_Bits_Write(scsiIdMask);\r
901                 SCSI_Out_Ctl_Write(1); // Write bits manually.\r
902                 SCSI_SetPin(SCSI_Out_BSY);\r
903 \r
904                 CyDelayUs(3); // arbitrate delay. 2.4us.\r
905 \r
906                 uint8_t dbx = scsiReadDBxPins();\r
907                 sel = SCSI_ReadFilt(SCSI_Filt_SEL);\r
908                 if (sel || ((dbx ^ scsiIdMask) > scsiIdMask))\r
909                 {\r
910                         // Lost arbitration.\r
911                         SCSI_Out_Ctl_Write(0);\r
912                         SCSI_ClearPin(SCSI_Out_BSY);\r
913                         ledOff();\r
914                 }\r
915                 else\r
916                 {\r
917                         // Won arbitration\r
918                         SCSI_SetPin(SCSI_Out_SEL);\r
919                         CyDelayUs(1); // Bus clear + Bus settle.\r
920 \r
921                         // Reselection phase\r
922                         SCSI_CTL_PHASE_Write(__scsiphase_io);\r
923                         SCSI_Out_Bits_Write(scsiIdMask | (1 << scsiDev.initiatorId));\r
924                         scsiDeskewDelay(); // 2 deskew delays\r
925                         scsiDeskewDelay(); // 2 deskew delays\r
926                         SCSI_ClearPin(SCSI_Out_BSY);\r
927                         CyDelayUs(1);  // Bus Settle Delay\r
928 \r
929                         uint32_t waitStart_ms = getTime_ms();\r
930                         bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
931                         // Wait for initiator.\r
932                         while (\r
933                                 !bsy &&\r
934                                 !scsiDev.resetFlag &&\r
935                                 (diffTime_ms(waitStart_ms, getTime_ms()) < 250))\r
936                         {\r
937                                 bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
938                         }\r
939 \r
940                         if (bsy)\r
941                         {\r
942                                 SCSI_SetPin(SCSI_Out_BSY);\r
943                                 scsiDeskewDelay(); // 2 deskew delays\r
944                                 scsiDeskewDelay(); // 2 deskew delays\r
945                                 SCSI_ClearPin(SCSI_Out_SEL);\r
946 \r
947                                 // Prepare for the initial IDENTIFY message.\r
948                                 SCSI_Out_Ctl_Write(0);\r
949                                 scsiEnterPhase(MESSAGE_IN);\r
950 \r
951                                 // Send identify command\r
952                                 scsiWriteByte(0x80);\r
953 \r
954                                 scsiEnterPhase(scsiDev.phase);\r
955                                 reconnected = 1;\r
956                         }\r
957                         else\r
958                         {\r
959                                 // reselect timeout.\r
960                                 SCSI_Out_Ctl_Write(0);\r
961                                 SCSI_ClearPin(SCSI_Out_SEL);\r
962                                 SCSI_CTL_PHASE_Write(0);\r
963                                 ledOff();\r
964                         }\r
965                 }\r
966         }\r
967         return reconnected;\r
968 }\r
969 \r