bbb3e4843a5572f0e522f936d9f79348e7b8afe0
[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         // 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         scsiDev.parityError = 0;\r
205         len = scsiDev.dataLen - scsiDev.dataPtr;\r
206         if (len > 0)\r
207         {\r
208                 scsiEnterPhase(DATA_OUT);\r
209 \r
210                 scsiRead(scsiDev.data + scsiDev.dataPtr, len);\r
211                 scsiDev.dataPtr += len;\r
212 \r
213                 if (scsiDev.parityError &&\r
214                         (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY) &&\r
215                         (scsiDev.compatMode >= COMPAT_SCSI2))\r
216                 {\r
217                         scsiDev.target->sense.code = ABORTED_COMMAND;\r
218                         scsiDev.target->sense.asc = SCSI_PARITY_ERROR;\r
219                         enter_Status(CHECK_CONDITION);\r
220                 }\r
221         }\r
222 \r
223         if ((scsiDev.dataPtr >= scsiDev.dataLen) &&\r
224                 (transfer.currentBlock == transfer.blocks))\r
225         {\r
226                 if (scsiDev.postDataOutHook != NULL)\r
227                 {\r
228                         scsiDev.postDataOutHook();\r
229                 }\r
230                 else\r
231                 {\r
232                         enter_Status(GOOD);\r
233                 }\r
234         }\r
235 }\r
236 \r
237 static const uint8_t CmdGroupBytes[8] = {6, 10, 10, 6, 6, 12, 6, 6};\r
238 static void process_Command()\r
239 {\r
240         int group;\r
241         uint8_t command;\r
242         uint8_t control;\r
243 \r
244         scsiEnterPhase(COMMAND);\r
245         scsiDev.parityError = 0;\r
246 \r
247         memset(scsiDev.cdb + 6, 0, sizeof(scsiDev.cdb) - 6);\r
248         scsiRead(scsiDev.cdb, 6);\r
249 \r
250         group = scsiDev.cdb[0] >> 5;\r
251         scsiDev.cdbLen = CmdGroupBytes[group];\r
252         if (scsiDev.cdbLen - 6 > 0)\r
253         {\r
254                 scsiRead(scsiDev.cdb + 6, scsiDev.cdbLen - 6);\r
255         }\r
256 \r
257         command = scsiDev.cdb[0];\r
258 \r
259         // Prefer LUN's set by IDENTIFY messages for newer hosts.\r
260         if (scsiDev.lun < 0)\r
261         {\r
262                 scsiDev.lun = scsiDev.cdb[1] >> 5;\r
263         }\r
264 \r
265         control = scsiDev.cdb[scsiDev.cdbLen - 1];\r
266 \r
267         scsiDev.cmdCount++;\r
268         const S2S_TargetCfg* cfg = scsiDev.target->cfg;\r
269 \r
270         if (unlikely(scsiDev.resetFlag))\r
271         {\r
272                 // Don't log bogus commands\r
273                 scsiDev.cmdCount--;\r
274                 memset(scsiDev.cdb, 0xff, sizeof(scsiDev.cdb));\r
275                 return;\r
276         }\r
277         else if (scsiDev.parityError &&\r
278                 (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY) &&\r
279                 (scsiDev.compatMode >= COMPAT_SCSI2))\r
280         {\r
281                 scsiDev.target->sense.code = ABORTED_COMMAND;\r
282                 scsiDev.target->sense.asc = SCSI_PARITY_ERROR;\r
283                 enter_Status(CHECK_CONDITION);\r
284         }\r
285         else if ((control & 0x02) && ((control & 0x01) == 0))\r
286         {\r
287                 // FLAG set without LINK flag.\r
288                 scsiDev.target->sense.code = ILLEGAL_REQUEST;\r
289                 scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;\r
290                 enter_Status(CHECK_CONDITION);\r
291         }\r
292         else if (command == 0x12)\r
293         {\r
294                 s2s_scsiInquiry();\r
295         }\r
296         else if (command == 0x03)\r
297         {\r
298                 // REQUEST SENSE\r
299                 uint32_t allocLength = scsiDev.cdb[4];\r
300 \r
301                 // As specified by the SASI and SCSI1 standard.\r
302                 // Newer initiators won't be specifying 0 anyway.\r
303                 if (allocLength == 0) allocLength = 4;\r
304 \r
305                 memset(scsiDev.data, 0, 256); // Max possible alloc length\r
306                 scsiDev.data[0] = 0xF0;\r
307                 scsiDev.data[2] = scsiDev.target->sense.code & 0x0F;\r
308 \r
309                 scsiDev.data[3] = transfer.lba >> 24;\r
310                 scsiDev.data[4] = transfer.lba >> 16;\r
311                 scsiDev.data[5] = transfer.lba >> 8;\r
312                 scsiDev.data[6] = transfer.lba;\r
313 \r
314                 // Additional bytes if there are errors to report\r
315                 scsiDev.data[7] = 10; // additional length\r
316                 scsiDev.data[12] = scsiDev.target->sense.asc >> 8;\r
317                 scsiDev.data[13] = scsiDev.target->sense.asc;\r
318 \r
319                 // Silently truncate results. SCSI-2 spec 8.2.14.\r
320                 enter_DataIn(allocLength);\r
321 \r
322                 // This is a good time to clear out old sense information.\r
323                 scsiDev.target->sense.code = NO_SENSE;\r
324                 scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;\r
325         }\r
326         // Some old SCSI drivers do NOT properly support\r
327         // unitAttention. eg. the Mac Plus would trigger a SCSI reset\r
328         // on receiving the unit attention response on boot, thus\r
329         // triggering another unit attention condition.\r
330         else if (scsiDev.target->unitAttention &&\r
331                 (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_UNIT_ATTENTION))\r
332         {\r
333                 scsiDev.target->sense.code = UNIT_ATTENTION;\r
334                 scsiDev.target->sense.asc = scsiDev.target->unitAttention;\r
335 \r
336                 // If initiator doesn't do REQUEST SENSE for the next command, then\r
337                 // data is lost.\r
338                 scsiDev.target->unitAttention = 0;\r
339 \r
340                 enter_Status(CHECK_CONDITION);\r
341         }\r
342         else if (scsiDev.lun)\r
343         {\r
344                 scsiDev.target->sense.code = ILLEGAL_REQUEST;\r
345                 scsiDev.target->sense.asc = LOGICAL_UNIT_NOT_SUPPORTED;\r
346                 enter_Status(CHECK_CONDITION);\r
347         }\r
348         else if (command == 0x17 || command == 0x16)\r
349         {\r
350                 doReserveRelease();\r
351         }\r
352         else if ((scsiDev.target->reservedId >= 0) &&\r
353                 (scsiDev.target->reservedId != scsiDev.initiatorId))\r
354         {\r
355                 enter_Status(CONFLICT);\r
356         }\r
357         // Handle odd device types first that may override basic read and\r
358         // write commands. Will fall-through to generic disk handling.\r
359         else if (((cfg->deviceType == S2S_CFG_OPTICAL) && scsiCDRomCommand()) ||\r
360                 ((cfg->deviceType == S2S_CFG_SEQUENTIAL) && scsiTapeCommand()) ||\r
361                 ((cfg->deviceType == S2S_CFG_MO) && scsiMOCommand()))\r
362         {\r
363                 // Already handled.\r
364         }\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         int tgtIndex;\r
543         TargetState* target = NULL;\r
544         for (tgtIndex = 0; tgtIndex < S2S_MAX_TARGETS; ++tgtIndex)\r
545         {\r
546                 if (scsiDev.targets[tgtIndex].targetId == (selStatus & 7))\r
547                 {\r
548                         target = &scsiDev.targets[tgtIndex];\r
549                         break;\r
550                 }\r
551         }\r
552         if ((target != NULL) && (selStatus & 0x40))\r
553 // TODO         (goodParity || !(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY) || !atnFlag)\r
554         {\r
555                 // We've been selected!\r
556                 // Assert BSY - Selection success!\r
557                 // must happen within 200us (Selection abort time) of seeing our\r
558                 // ID + SEL.\r
559                 // (Note: the initiator will be waiting the "Selection time-out delay"\r
560                 // for our BSY response, which is actually a very generous 250ms)\r
561                 *SCSI_CTRL_BSY = 1;\r
562                 s2s_ledOn();\r
563 \r
564                 scsiDev.target = target;\r
565 \r
566                 // Do we enter MESSAGE OUT immediately ? SCSI 1 and 2 standards says\r
567                 // move to MESSAGE OUT if ATN is true before we assert BSY.\r
568                 // The initiator should assert ATN with SEL.\r
569                 scsiDev.atnFlag = selStatus & 0x80;\r
570 \r
571 \r
572                 // Unit attention breaks many older SCSI hosts. Disable it completely\r
573                 // for SCSI-1 (and older) hosts, regardless of our configured setting.\r
574                 // Enable the compatability mode also as many SASI and SCSI1\r
575                 // controllers don't generate parity bits.\r
576                 if (!scsiDev.atnFlag)\r
577                 {\r
578                         target->unitAttention = 0;\r
579                         scsiDev.compatMode = COMPAT_SCSI1;\r
580                 }\r
581                 else if (!(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_SCSI2))\r
582                 {\r
583                         scsiDev.compatMode = COMPAT_SCSI1;\r
584                 }\r
585                 else if (scsiDev.compatMode == COMPAT_UNKNOWN)\r
586                 {\r
587                         scsiDev.compatMode = COMPAT_SCSI2;\r
588                 }\r
589 \r
590                 scsiDev.selCount++;\r
591 \r
592 \r
593                 // Save our initiator now that we're no longer in a time-critical\r
594                 // section.\r
595                 // SCSI1/SASI initiators may not set their own ID.\r
596                 scsiDev.initiatorId = (selStatus >> 3) & 0x7;\r
597 \r
598                 while (likely(!scsiDev.resetFlag) && scsiStatusSEL())\r
599                 {\r
600                         // Wait until the end of the selection phase.\r
601                 }\r
602 \r
603                 scsiDev.phase = COMMAND;\r
604         }\r
605         else if (!selStatus)\r
606         {\r
607                 scsiDev.phase = BUS_BUSY;\r
608         }\r
609 }\r
610 \r
611 static void process_MessageOut()\r
612 {\r
613         scsiEnterPhase(MESSAGE_OUT);\r
614 \r
615         scsiDev.atnFlag = 0;\r
616         scsiDev.parityError = 0;\r
617         scsiDev.msgOut = scsiReadByte();\r
618         scsiDev.msgCount++;\r
619 \r
620         if (scsiDev.parityError &&\r
621                 (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY) &&\r
622                 (scsiDev.compatMode >= COMPAT_SCSI2))\r
623         {\r
624                 // Skip the remaining message bytes, and then start the MESSAGE_OUT\r
625                 // phase again from the start. The initiator will re-send the\r
626                 // same set of messages.\r
627                 while (scsiStatusATN() && !scsiDev.resetFlag)\r
628                 {\r
629                         scsiReadByte();\r
630                 }\r
631 \r
632                 // Go-back and try the message again.\r
633                 scsiDev.atnFlag = 1;\r
634                 scsiDev.parityError = 0;\r
635         }\r
636         else if (scsiDev.msgOut == 0x00)\r
637         {\r
638                 // COMMAND COMPLETE. but why would the target be receiving this ? nfi.\r
639                 enter_BusFree();\r
640         }\r
641         else if (scsiDev.msgOut == 0x06)\r
642         {\r
643                 // ABORT\r
644                 scsiDiskReset();\r
645                 enter_BusFree();\r
646         }\r
647         else if (scsiDev.msgOut == 0x0C)\r
648         {\r
649                 // BUS DEVICE RESET\r
650 \r
651                 scsiDiskReset();\r
652 \r
653                 scsiDev.target->unitAttention = SCSI_BUS_RESET;\r
654 \r
655                 // ANY initiator can reset the reservation state via this message.\r
656                 scsiDev.target->reservedId = -1;\r
657                 scsiDev.target->reserverId = -1;\r
658                 enter_BusFree();\r
659         }\r
660         else if (scsiDev.msgOut == 0x05)\r
661         {\r
662                 // Initiate Detected Error\r
663                 // Ignore for now\r
664         }\r
665         else if (scsiDev.msgOut == 0x0F)\r
666         {\r
667                 // INITIATE RECOVERY\r
668                 // Ignore for now\r
669         }\r
670         else if (scsiDev.msgOut == 0x10)\r
671         {\r
672                 // RELEASE RECOVERY\r
673                 // Ignore for now\r
674                 enter_BusFree();\r
675         }\r
676         else if (scsiDev.msgOut == MSG_REJECT)\r
677         {\r
678                 // Message Reject\r
679                 // Oh well.\r
680                 scsiDev.resetFlag = 1;\r
681         }\r
682         else if (scsiDev.msgOut == 0x08)\r
683         {\r
684                 // NOP\r
685         }\r
686         else if (scsiDev.msgOut == 0x09)\r
687         {\r
688                 // Message Parity Error\r
689                 // Go back and re-send the last message.\r
690                 scsiDev.phase = MESSAGE_IN;\r
691         }\r
692         else if (scsiDev.msgOut & 0x80) // 0x80 -> 0xFF\r
693         {\r
694                 // IDENTIFY\r
695                 if ((scsiDev.msgOut & 0x18) || // Reserved bits set.\r
696                         (scsiDev.msgOut & 0x20))  // We don't have any target routines!\r
697                 {\r
698                         messageReject();\r
699                 }\r
700 \r
701                 scsiDev.lun = scsiDev.msgOut & 0x7;\r
702                 scsiDev.discPriv = \r
703                         ((scsiDev.msgOut & 0x40) && (scsiDev.initiatorId >= 0))\r
704                                 ? 1 : 0;\r
705         }\r
706         else if (scsiDev.msgOut >= 0x20 && scsiDev.msgOut <= 0x2F)\r
707         {\r
708                 // Two byte message. We don't support these. read and discard.\r
709                 scsiReadByte();\r
710 \r
711                 if (scsiDev.msgOut == 0x23) {\r
712                         // Ignore Wide Residue. We're only 8 bit anyway.\r
713                 } else {\r
714                         messageReject();\r
715                 }\r
716         }\r
717         else if (scsiDev.msgOut == 0x01)\r
718         {\r
719                 int i;\r
720 \r
721                 // Extended message.\r
722                 int msgLen = scsiReadByte();\r
723                 if (msgLen == 0) msgLen = 256;\r
724                 uint8_t extmsg[256];\r
725                 for (i = 0; i < msgLen && !scsiDev.resetFlag; ++i)\r
726                 {\r
727                         // Discard bytes.\r
728                         extmsg[i] = scsiReadByte();\r
729                 }\r
730 \r
731                 if (extmsg[0] == 3 && msgLen == 2) // Wide Data Request\r
732                 {\r
733                         // Negotiate down to 8bit\r
734                         scsiEnterPhase(MESSAGE_IN);\r
735                         static const uint8_t WDTR[] = {0x01, 0x02, 0x03, 0x00};\r
736                         scsiWrite(WDTR, sizeof(WDTR));\r
737                 }\r
738                 else if (extmsg[0] == 1 && msgLen == 3) // Synchronous data request\r
739                 {\r
740                         // Negotiate back to async\r
741                         scsiEnterPhase(MESSAGE_IN);\r
742                         static const uint8_t SDTR[] = {0x01, 0x03, 0x01, 0x00, 0x00};\r
743                         scsiWrite(SDTR, sizeof(SDTR));\r
744                 }\r
745                 else\r
746                 {\r
747                         // Not supported\r
748                         messageReject();\r
749                 }\r
750         }\r
751         else\r
752         {\r
753                 messageReject();\r
754         }\r
755 \r
756         // Re-check the ATN flag in case it stays asserted.\r
757         scsiDev.atnFlag |= scsiStatusATN();\r
758 }\r
759 \r
760 void scsiPoll(void)\r
761 {\r
762         if (unlikely(scsiDev.resetFlag))\r
763         {\r
764                 scsiReset();\r
765                 if ((scsiDev.resetFlag = scsiStatusRST()))\r
766                 {\r
767                         // Still in reset phase. Do not try and process any commands.\r
768                         return;\r
769                 }\r
770         }\r
771 \r
772         switch (scsiDev.phase)\r
773         {\r
774         case BUS_FREE:\r
775                 if (scsiStatusBSY())\r
776                 {\r
777                         scsiDev.phase = BUS_BUSY;\r
778                 }\r
779                 // The Arbitration phase is optional for SCSI1/SASI hosts if there is only\r
780                 // one initiator in the chain. Support this by moving\r
781                 // straight to selection if SEL is asserted.\r
782                 // ie. the initiator won't assert BSY and it's own ID before moving to selection.\r
783                 else if (*SCSI_STS_SELECTED)\r
784                 {\r
785                         enter_SelectionPhase();\r
786                 }\r
787         break;\r
788 \r
789         case BUS_BUSY:\r
790                 // Someone is using the bus. Perhaps they are trying to\r
791                 // select us.\r
792                 if (*SCSI_STS_SELECTED)\r
793                 {\r
794                         enter_SelectionPhase();\r
795                 }\r
796                 else if (!scsiStatusBSY())\r
797                 {\r
798                         scsiDev.phase = BUS_FREE;\r
799                 }\r
800         break;\r
801 \r
802         case ARBITRATION:\r
803                 // TODO Support reselection.\r
804                 break;\r
805 \r
806         case SELECTION:\r
807                 process_SelectionPhase();\r
808         break;\r
809 \r
810         case RESELECTION:\r
811                 // Not currently supported!\r
812         break;\r
813 \r
814         case COMMAND:\r
815                 // Do not check ATN here. SCSI 1 & 2 initiators must set ATN\r
816                 // and SEL together upon entering the selection phase if they\r
817                 // want to send a message (IDENTIFY) immediately.\r
818                 if (scsiDev.atnFlag)\r
819                 {\r
820                         process_MessageOut();\r
821                 }\r
822                 else\r
823                 {\r
824                         process_Command();\r
825                 }\r
826         break;\r
827 \r
828         case DATA_IN:\r
829                 scsiDev.atnFlag |= scsiStatusATN();\r
830                 if (scsiDev.atnFlag)\r
831                 {\r
832                         process_MessageOut();\r
833                 }\r
834                 else\r
835                 {\r
836                         process_DataIn();\r
837                 }\r
838         break;\r
839 \r
840         case DATA_OUT:\r
841                 scsiDev.atnFlag |= scsiStatusATN();\r
842                 if (scsiDev.atnFlag)\r
843                 {\r
844                         process_MessageOut();\r
845                 }\r
846                 else\r
847                 {\r
848                         process_DataOut();\r
849                 }\r
850         break;\r
851 \r
852         case STATUS:\r
853                 scsiDev.atnFlag |= scsiStatusATN();\r
854                 if (scsiDev.atnFlag)\r
855                 {\r
856                         process_MessageOut();\r
857                 }\r
858                 else\r
859                 {\r
860                         process_Status();\r
861                 }\r
862         break;\r
863 \r
864         case MESSAGE_IN:\r
865                 scsiDev.atnFlag |= scsiStatusATN();\r
866                 if (scsiDev.atnFlag)\r
867                 {\r
868                         process_MessageOut();\r
869                 }\r
870                 else\r
871                 {\r
872                         process_MessageIn();\r
873                 }\r
874 \r
875         break;\r
876 \r
877         case MESSAGE_OUT:\r
878                 process_MessageOut();\r
879         break;\r
880         }\r
881 }\r
882 \r
883 void scsiInit()\r
884 {\r
885         static int firstInit = 1;\r
886 \r
887         scsiDev.atnFlag = 0;\r
888         scsiDev.resetFlag = 1;\r
889         scsiDev.phase = BUS_FREE;\r
890         scsiDev.target = NULL;\r
891         scsiDev.compatMode = COMPAT_UNKNOWN;\r
892 \r
893         int i;\r
894         for (i = 0; i < S2S_MAX_TARGETS; ++i)\r
895         {\r
896                 const S2S_TargetCfg* cfg = s2s_getConfigByIndex(i);\r
897                 if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))\r
898                 {\r
899                         scsiDev.targets[i].targetId = cfg->scsiId & S2S_CFG_TARGET_ID_BITS;\r
900                         scsiDev.targets[i].cfg = cfg;\r
901 \r
902                         scsiDev.targets[i].liveCfg.bytesPerSector = cfg->bytesPerSector;\r
903                 }\r
904                 else\r
905                 {\r
906                         scsiDev.targets[i].targetId = 0xff;\r
907                         scsiDev.targets[i].cfg = NULL;\r
908                 }\r
909                 scsiDev.targets[i].reservedId = -1;\r
910                 scsiDev.targets[i].reserverId = -1;\r
911                 if (firstInit)\r
912                 {\r
913                         scsiDev.targets[i].unitAttention = POWER_ON_RESET;\r
914                 }\r
915                 else\r
916                 {\r
917                         scsiDev.targets[i].unitAttention = PARAMETERS_CHANGED;\r
918                 }\r
919                 scsiDev.targets[i].sense.code = NO_SENSE;\r
920                 scsiDev.targets[i].sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;\r
921         }\r
922         firstInit = 0;\r
923 }\r
924 \r
925 /* TODO REENABLE\r
926 void scsiDisconnect()\r
927 {\r
928         scsiEnterPhase(MESSAGE_IN);\r
929         scsiWriteByte(0x02); // save data pointer\r
930         scsiWriteByte(0x04); // disconnect msg.\r
931 \r
932         // For now, the caller is responsible for tracking the disconnected\r
933         // state, and calling scsiReconnect.\r
934         // Ideally the client would exit their loop and we'd implement this\r
935         // as part of scsiPoll\r
936         int phase = scsiDev.phase;\r
937         enter_BusFree();\r
938         scsiDev.phase = phase;\r
939 }\r
940 */\r
941 \r
942 /* TODO REENABLE\r
943 int scsiReconnect()\r
944 {\r
945         int reconnected = 0;\r
946 \r
947         int sel = SCSI_ReadFilt(SCSI_Filt_SEL);\r
948         int bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
949         if (!sel && !bsy)\r
950         {\r
951                 s2s_delay_us(1);\r
952                 sel = SCSI_ReadFilt(SCSI_Filt_SEL);\r
953                 bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
954         }\r
955 \r
956         if (!sel && !bsy)\r
957         {\r
958                 // Arbitrate.\r
959                 s2s_ledOn();\r
960                 uint8_t scsiIdMask = 1 << scsiDev.target->targetId;\r
961                 SCSI_Out_Bits_Write(scsiIdMask);\r
962                 SCSI_Out_Ctl_Write(1); // Write bits manually.\r
963                 SCSI_SetPin(SCSI_Out_BSY);\r
964 \r
965                 s2s_delay_us(3); // arbitrate delay. 2.4us.\r
966 \r
967                 uint8_t dbx = scsiReadDBxPins();\r
968                 sel = SCSI_ReadFilt(SCSI_Filt_SEL);\r
969                 if (sel || ((dbx ^ scsiIdMask) > scsiIdMask))\r
970                 {\r
971                         // Lost arbitration.\r
972                         SCSI_Out_Ctl_Write(0);\r
973                         SCSI_ClearPin(SCSI_Out_BSY);\r
974                         s2s_ledOff();\r
975                 }\r
976                 else\r
977                 {\r
978                         // Won arbitration\r
979                         SCSI_SetPin(SCSI_Out_SEL);\r
980                         s2s_delay_us(1); // Bus clear + Bus settle.\r
981 \r
982                         // Reselection phase\r
983                         SCSI_CTL_PHASE_Write(__scsiphase_io);\r
984                         SCSI_Out_Bits_Write(scsiIdMask | (1 << scsiDev.initiatorId));\r
985                         scsiDeskewDelay(); // 2 deskew delays\r
986                         scsiDeskewDelay(); // 2 deskew delays\r
987                         SCSI_ClearPin(SCSI_Out_BSY);\r
988                         s2s_delay_us(1);  // Bus Settle Delay\r
989 \r
990                         uint32_t waitStart_ms = getTime_ms();\r
991                         bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
992                         // Wait for initiator.\r
993                         while (\r
994                                 !bsy &&\r
995                                 !scsiDev.resetFlag &&\r
996                                 (elapsedTime_ms(waitStart_ms) < 250))\r
997                         {\r
998                                 bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
999                         }\r
1000 \r
1001                         if (bsy)\r
1002                         {\r
1003                                 SCSI_SetPin(SCSI_Out_BSY);\r
1004                                 scsiDeskewDelay(); // 2 deskew delays\r
1005                                 scsiDeskewDelay(); // 2 deskew delays\r
1006                                 SCSI_ClearPin(SCSI_Out_SEL);\r
1007 \r
1008                                 // Prepare for the initial IDENTIFY message.\r
1009                                 SCSI_Out_Ctl_Write(0);\r
1010                                 scsiEnterPhase(MESSAGE_IN);\r
1011 \r
1012                                 // Send identify command\r
1013                                 scsiWriteByte(0x80);\r
1014 \r
1015                                 scsiEnterPhase(scsiDev.phase);\r
1016                                 reconnected = 1;\r
1017                         }\r
1018                         else\r
1019                         {\r
1020                                 // reselect timeout.\r
1021                                 SCSI_Out_Ctl_Write(0);\r
1022                                 SCSI_ClearPin(SCSI_Out_SEL);\r
1023                                 SCSI_CTL_PHASE_Write(0);\r
1024                                 s2s_ledOff();\r
1025                         }\r
1026                 }\r
1027         }\r
1028         return reconnected;\r
1029 }\r
1030 */\r
1031 \r