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