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