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