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