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