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