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