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