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