ff54cd2789d563cefe81a0a72a0e6f0b4d7b6cd4
[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 \r
484         for (int i = 0; i < S2S_MAX_TARGETS; ++i)\r
485         {\r
486                 scsiDev.target[i].syncOffset = 0;\r
487                 scsiDev.target[i].syncPeriod = 0;\r
488         }\r
489 \r
490         scsiDiskReset();\r
491 \r
492         scsiDev.postDataOutHook = NULL;\r
493 \r
494         // Sleep to allow the bus to settle down a bit.\r
495         // We must be ready again within the "Reset to selection time" of\r
496         // 250ms.\r
497         // There is no guarantee that the RST line will be negated by then.\r
498         // NOTE: We could be connected and powered by USB for configuration,\r
499         // in which case TERMPWR cannot be supplied, and reset will ALWAYS\r
500         // be true. Therefore, the sleep here must be slow to avoid slowing\r
501         // USB comms\r
502         s2s_delay_ms(1); // 1ms.\r
503 }\r
504 \r
505 static void enter_SelectionPhase()\r
506 {\r
507         // Ignore stale versions of this flag, but ensure we know the\r
508         // current value if the flag is still set.\r
509         scsiDev.atnFlag = 0;\r
510         scsiDev.parityError = 0;\r
511         scsiDev.dataPtr = 0;\r
512         scsiDev.savedDataPtr = 0;\r
513         scsiDev.dataLen = 0;\r
514         scsiDev.status = GOOD;\r
515         scsiDev.phase = SELECTION;\r
516         scsiDev.lun = -1;\r
517         scsiDev.discPriv = 0;\r
518 \r
519         scsiDev.initiatorId = -1;\r
520         scsiDev.target = NULL;\r
521 \r
522         transfer.blocks = 0;\r
523         transfer.currentBlock = 0;\r
524 \r
525         scsiDev.postDataOutHook = NULL;\r
526 \r
527         scsiDev.needSyncNegotiationAck = 0;\r
528 }\r
529 \r
530 static void process_SelectionPhase()\r
531 {\r
532         // Selection delays.\r
533         // Many SCSI1 samplers that use a 5380 chip need a delay of at least 1ms.\r
534         // The Mac Plus boot-time (ie. rom code) selection abort time\r
535         // is < 1ms and must have no delay (standard suggests 250ms abort time)\r
536         // Most newer SCSI2 hosts don't care either way.\r
537         if (scsiDev.boardCfg.selectionDelay == 255) // auto\r
538         {\r
539                 if (scsiDev.compatMode < COMPAT_SCSI2)\r
540                 {\r
541                         s2s_delay_ms(1);\r
542                 }\r
543         }\r
544         else if (scsiDev.boardCfg.selectionDelay != 0)\r
545         {\r
546                 s2s_delay_ms(scsiDev.boardCfg.selectionDelay);\r
547         }\r
548 \r
549         uint8_t selStatus = *SCSI_STS_SELECTED;\r
550 \r
551         int tgtIndex;\r
552         TargetState* target = NULL;\r
553         for (tgtIndex = 0; tgtIndex < S2S_MAX_TARGETS; ++tgtIndex)\r
554         {\r
555                 if (scsiDev.targets[tgtIndex].targetId == (selStatus & 7))\r
556                 {\r
557                         target = &scsiDev.targets[tgtIndex];\r
558                         break;\r
559                 }\r
560         }\r
561         if ((target != NULL) && (selStatus & 0x40))\r
562 // TODO         (goodParity || !(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY) || !atnFlag)\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 if (scsiDev.compatMode == COMPAT_UNKNOWN)\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.parityError = 0;\r
626         scsiDev.msgOut = scsiReadByte();\r
627         scsiDev.msgCount++;\r
628 \r
629         if (scsiDev.parityError &&\r
630                 (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY) &&\r
631                 (scsiDev.compatMode >= COMPAT_SCSI2))\r
632         {\r
633                 // Skip the remaining message bytes, and then start the MESSAGE_OUT\r
634                 // phase again from the start. The initiator will re-send the\r
635                 // same set of messages.\r
636                 while (scsiStatusATN() && !scsiDev.resetFlag)\r
637                 {\r
638                         scsiReadByte();\r
639                 }\r
640 \r
641                 // Go-back and try the message again.\r
642                 scsiDev.atnFlag = 1;\r
643                 scsiDev.parityError = 0;\r
644         }\r
645         else if (scsiDev.msgOut == 0x00)\r
646         {\r
647                 // COMMAND COMPLETE. but why would the target be receiving this ? nfi.\r
648                 enter_BusFree();\r
649         }\r
650         else if (scsiDev.msgOut == 0x06)\r
651         {\r
652                 // ABORT\r
653                 scsiDiskReset();\r
654                 enter_BusFree();\r
655         }\r
656         else if (scsiDev.msgOut == 0x0C)\r
657         {\r
658                 // BUS DEVICE RESET\r
659 \r
660                 scsiDiskReset();\r
661 \r
662                 scsiDev.target->unitAttention = SCSI_BUS_RESET;\r
663 \r
664                 // ANY initiator can reset the reservation state via this message.\r
665                 scsiDev.target->reservedId = -1;\r
666                 scsiDev.target->reserverId = -1;\r
667 \r
668                 // Cancel any sync negotiation\r
669                 scsiDev.target->syncOffset = 0;\r
670                 scsiDev.target->syncPeriod = 0;\r
671 \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 \r
695                 if (scsiDev.needSyncNegotiationAck)\r
696                 {\r
697                         scsiDev.target->syncOffset = 0;\r
698                         scsiDev.target->syncPeriod = 0;\r
699                         scsiDev.needSyncNegotiationAck = 0;\r
700                 }\r
701         }\r
702         else if (scsiDev.msgOut == 0x08)\r
703         {\r
704                 // NOP\r
705         }\r
706         else if (scsiDev.msgOut == 0x09)\r
707         {\r
708                 // Message Parity Error\r
709                 // Go back and re-send the last message.\r
710                 scsiDev.phase = MESSAGE_IN;\r
711         }\r
712         else if (scsiDev.msgOut & 0x80) // 0x80 -> 0xFF\r
713         {\r
714                 // IDENTIFY\r
715                 if ((scsiDev.msgOut & 0x18) || // Reserved bits set.\r
716                         (scsiDev.msgOut & 0x20))  // We don't have any target routines!\r
717                 {\r
718                         messageReject();\r
719                 }\r
720 \r
721                 scsiDev.lun = scsiDev.msgOut & 0x7;\r
722                 scsiDev.discPriv = \r
723                         ((scsiDev.msgOut & 0x40) && (scsiDev.initiatorId >= 0))\r
724                                 ? 1 : 0;\r
725         }\r
726         else if (scsiDev.msgOut >= 0x20 && scsiDev.msgOut <= 0x2F)\r
727         {\r
728                 // Two byte message. We don't support these. read and discard.\r
729                 scsiReadByte();\r
730 \r
731                 if (scsiDev.msgOut == 0x23) {\r
732                         // Ignore Wide Residue. We're only 8 bit anyway.\r
733                 } else {\r
734                         messageReject();\r
735                 }\r
736         }\r
737         else if (scsiDev.msgOut == 0x01)\r
738         {\r
739                 int i;\r
740 \r
741                 // Extended message.\r
742                 int msgLen = scsiReadByte();\r
743                 if (msgLen == 0) msgLen = 256;\r
744                 uint8_t extmsg[256];\r
745                 for (i = 0; i < msgLen && !scsiDev.resetFlag; ++i)\r
746                 {\r
747                         // Discard bytes.\r
748                         extmsg[i] = scsiReadByte();\r
749                 }\r
750 \r
751                 if (extmsg[0] == 3 && msgLen == 2) // Wide Data Request\r
752                 {\r
753                         // Negotiate down to 8bit\r
754                         scsiEnterPhase(MESSAGE_IN);\r
755                         static const uint8_t WDTR[] = {0x01, 0x02, 0x03, 0x00};\r
756                         scsiWrite(WDTR, sizeof(WDTR));\r
757                 }\r
758                 else if (extmsg[0] == 1 && msgLen == 3) // Synchronous data request\r
759                 {\r
760                         int transferPeriod = extmsg[1];\r
761                         int offset = extmsg[2];\r
762 \r
763                         if (transferPeriod > 50) // 200ns, 5MB/s\r
764                         {\r
765                                 scsiDev.target->syncOffset = 0;\r
766                                 scsiDev.target->syncPeriod = 0;\r
767                         } else {\r
768                                 scsiDev.target->syncOffset = offset < 15 ? offset : 15;\r
769                                 if (transferPeriod <= 12)\r
770                                 {\r
771                                         scsiDev.target->syncPeriod = 12; // 50ns, 20MB/s\r
772                                 }\r
773                                 else if (transferPeriod <= 25)\r
774                                 {\r
775                                         scsiDev.target->syncPeriod = 25; // 100ns, 10MB/s\r
776                                 } else {\r
777                                         scsiDev.target->syncPeriod = 50; // 200ns, 5MB/s\r
778                                 }\r
779                         }\r
780 \r
781                         scsiEnterPhase(MESSAGE_IN);\r
782                         uint8_t SDTR[] = {0x01, 0x03, 0x01, scsiDev.target->syncPeriod, scsiDev.target->syncOffset};\r
783                         scsiWrite(SDTR, sizeof(SDTR));\r
784                         scsiDev.needSyncNegotiationAck = 1; // Check if this message is rejected.\r
785                 }\r
786                 else\r
787                 {\r
788                         // Not supported\r
789                         messageReject();\r
790                 }\r
791         }\r
792         else\r
793         {\r
794                 messageReject();\r
795         }\r
796 \r
797         // Re-check the ATN flag in case it stays asserted.\r
798         scsiDev.atnFlag |= scsiStatusATN();\r
799 \r
800         if (!scsiDev.atnFlag)\r
801         {\r
802                 // Message wasn't rejected!\r
803                 scsiDev.needSyncNegotiationAck = 0;\r
804         }\r
805 }\r
806 \r
807 void scsiPoll(void)\r
808 {\r
809         if (unlikely(scsiDev.resetFlag))\r
810         {\r
811                 scsiReset();\r
812                 if ((scsiDev.resetFlag = scsiStatusRST()))\r
813                 {\r
814                         // Still in reset phase. Do not try and process any commands.\r
815                         return;\r
816                 }\r
817         }\r
818 \r
819         switch (scsiDev.phase)\r
820         {\r
821         case BUS_FREE:\r
822                 if (scsiStatusBSY())\r
823                 {\r
824                         scsiDev.phase = BUS_BUSY;\r
825                 }\r
826                 // The Arbitration phase is optional for SCSI1/SASI hosts if there is only\r
827                 // one initiator in the chain. Support this by moving\r
828                 // straight to selection if SEL is asserted.\r
829                 // ie. the initiator won't assert BSY and it's own ID before moving to selection.\r
830                 else if (*SCSI_STS_SELECTED)\r
831                 {\r
832                         enter_SelectionPhase();\r
833                 }\r
834         break;\r
835 \r
836         case BUS_BUSY:\r
837                 // Someone is using the bus. Perhaps they are trying to\r
838                 // select us.\r
839                 if (*SCSI_STS_SELECTED)\r
840                 {\r
841                         enter_SelectionPhase();\r
842                 }\r
843                 else if (!scsiStatusBSY())\r
844                 {\r
845                         scsiDev.phase = BUS_FREE;\r
846                 }\r
847         break;\r
848 \r
849         case ARBITRATION:\r
850                 // TODO Support reselection.\r
851                 break;\r
852 \r
853         case SELECTION:\r
854                 process_SelectionPhase();\r
855         break;\r
856 \r
857         case RESELECTION:\r
858                 // Not currently supported!\r
859         break;\r
860 \r
861         case COMMAND:\r
862                 // Do not check ATN here. SCSI 1 & 2 initiators must set ATN\r
863                 // and SEL together upon entering the selection phase if they\r
864                 // want to send a message (IDENTIFY) immediately.\r
865                 if (scsiDev.atnFlag)\r
866                 {\r
867                         process_MessageOut();\r
868                 }\r
869                 else\r
870                 {\r
871                         process_Command();\r
872                 }\r
873         break;\r
874 \r
875         case DATA_IN:\r
876                 scsiDev.atnFlag |= scsiStatusATN();\r
877                 if (scsiDev.atnFlag)\r
878                 {\r
879                         process_MessageOut();\r
880                 }\r
881                 else\r
882                 {\r
883                         process_DataIn();\r
884                 }\r
885         break;\r
886 \r
887         case DATA_OUT:\r
888                 scsiDev.atnFlag |= scsiStatusATN();\r
889                 if (scsiDev.atnFlag)\r
890                 {\r
891                         process_MessageOut();\r
892                 }\r
893                 else\r
894                 {\r
895                         process_DataOut();\r
896                 }\r
897         break;\r
898 \r
899         case STATUS:\r
900                 scsiDev.atnFlag |= scsiStatusATN();\r
901                 if (scsiDev.atnFlag)\r
902                 {\r
903                         process_MessageOut();\r
904                 }\r
905                 else\r
906                 {\r
907                         process_Status();\r
908                 }\r
909         break;\r
910 \r
911         case MESSAGE_IN:\r
912                 scsiDev.atnFlag |= scsiStatusATN();\r
913                 if (scsiDev.atnFlag)\r
914                 {\r
915                         process_MessageOut();\r
916                 }\r
917                 else\r
918                 {\r
919                         process_MessageIn();\r
920                 }\r
921 \r
922         break;\r
923 \r
924         case MESSAGE_OUT:\r
925                 process_MessageOut();\r
926         break;\r
927         }\r
928 }\r
929 \r
930 void scsiInit()\r
931 {\r
932         static int firstInit = 1;\r
933 \r
934         scsiDev.atnFlag = 0;\r
935         scsiDev.resetFlag = 1;\r
936         scsiDev.phase = BUS_FREE;\r
937         scsiDev.target = NULL;\r
938         scsiDev.compatMode = COMPAT_UNKNOWN;\r
939 \r
940         int i;\r
941         for (i = 0; i < S2S_MAX_TARGETS; ++i)\r
942         {\r
943                 const S2S_TargetCfg* cfg = s2s_getConfigByIndex(i);\r
944                 if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))\r
945                 {\r
946                         scsiDev.targets[i].targetId = cfg->scsiId & S2S_CFG_TARGET_ID_BITS;\r
947                         scsiDev.targets[i].cfg = cfg;\r
948 \r
949                         scsiDev.targets[i].liveCfg.bytesPerSector = cfg->bytesPerSector;\r
950                 }\r
951                 else\r
952                 {\r
953                         scsiDev.targets[i].targetId = 0xff;\r
954                         scsiDev.targets[i].cfg = NULL;\r
955                 }\r
956                 scsiDev.targets[i].reservedId = -1;\r
957                 scsiDev.targets[i].reserverId = -1;\r
958                 if (firstInit)\r
959                 {\r
960                         scsiDev.targets[i].unitAttention = POWER_ON_RESET;\r
961                 }\r
962                 else\r
963                 {\r
964                         scsiDev.targets[i].unitAttention = PARAMETERS_CHANGED;\r
965                 }\r
966                 scsiDev.targets[i].sense.code = NO_SENSE;\r
967                 scsiDev.targets[i].sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;\r
968 \r
969                 scsiDev.targets[i].syncOffset = 0;\r
970                 scsiDev.targets[i].syncPeriod = 0;\r
971         }\r
972         firstInit = 0;\r
973 }\r
974 \r
975 /* TODO REENABLE\r
976 void scsiDisconnect()\r
977 {\r
978         scsiEnterPhase(MESSAGE_IN);\r
979         scsiWriteByte(0x02); // save data pointer\r
980         scsiWriteByte(0x04); // disconnect msg.\r
981 \r
982         // For now, the caller is responsible for tracking the disconnected\r
983         // state, and calling scsiReconnect.\r
984         // Ideally the client would exit their loop and we'd implement this\r
985         // as part of scsiPoll\r
986         int phase = scsiDev.phase;\r
987         enter_BusFree();\r
988         scsiDev.phase = phase;\r
989 }\r
990 */\r
991 \r
992 /* TODO REENABLE\r
993 int scsiReconnect()\r
994 {\r
995         int reconnected = 0;\r
996 \r
997         int sel = SCSI_ReadFilt(SCSI_Filt_SEL);\r
998         int bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
999         if (!sel && !bsy)\r
1000         {\r
1001                 s2s_delay_us(1);\r
1002                 sel = SCSI_ReadFilt(SCSI_Filt_SEL);\r
1003                 bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
1004         }\r
1005 \r
1006         if (!sel && !bsy)\r
1007         {\r
1008                 // Arbitrate.\r
1009                 s2s_ledOn();\r
1010                 uint8_t scsiIdMask = 1 << scsiDev.target->targetId;\r
1011                 SCSI_Out_Bits_Write(scsiIdMask);\r
1012                 SCSI_Out_Ctl_Write(1); // Write bits manually.\r
1013                 SCSI_SetPin(SCSI_Out_BSY);\r
1014 \r
1015                 s2s_delay_us(3); // arbitrate delay. 2.4us.\r
1016 \r
1017                 uint8_t dbx = scsiReadDBxPins();\r
1018                 sel = SCSI_ReadFilt(SCSI_Filt_SEL);\r
1019                 if (sel || ((dbx ^ scsiIdMask) > scsiIdMask))\r
1020                 {\r
1021                         // Lost arbitration.\r
1022                         SCSI_Out_Ctl_Write(0);\r
1023                         SCSI_ClearPin(SCSI_Out_BSY);\r
1024                         s2s_ledOff();\r
1025                 }\r
1026                 else\r
1027                 {\r
1028                         // Won arbitration\r
1029                         SCSI_SetPin(SCSI_Out_SEL);\r
1030                         s2s_delay_us(1); // Bus clear + Bus settle.\r
1031 \r
1032                         // Reselection phase\r
1033                         SCSI_CTL_PHASE_Write(__scsiphase_io);\r
1034                         SCSI_Out_Bits_Write(scsiIdMask | (1 << scsiDev.initiatorId));\r
1035                         scsiDeskewDelay(); // 2 deskew delays\r
1036                         scsiDeskewDelay(); // 2 deskew delays\r
1037                         SCSI_ClearPin(SCSI_Out_BSY);\r
1038                         s2s_delay_us(1);  // Bus Settle Delay\r
1039 \r
1040                         uint32_t waitStart_ms = getTime_ms();\r
1041                         bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
1042                         // Wait for initiator.\r
1043                         while (\r
1044                                 !bsy &&\r
1045                                 !scsiDev.resetFlag &&\r
1046                                 (elapsedTime_ms(waitStart_ms) < 250))\r
1047                         {\r
1048                                 bsy = SCSI_ReadFilt(SCSI_Filt_BSY);\r
1049                         }\r
1050 \r
1051                         if (bsy)\r
1052                         {\r
1053                                 SCSI_SetPin(SCSI_Out_BSY);\r
1054                                 scsiDeskewDelay(); // 2 deskew delays\r
1055                                 scsiDeskewDelay(); // 2 deskew delays\r
1056                                 SCSI_ClearPin(SCSI_Out_SEL);\r
1057 \r
1058                                 // Prepare for the initial IDENTIFY message.\r
1059                                 SCSI_Out_Ctl_Write(0);\r
1060                                 scsiEnterPhase(MESSAGE_IN);\r
1061 \r
1062                                 // Send identify command\r
1063                                 scsiWriteByte(0x80);\r
1064 \r
1065                                 scsiEnterPhase(scsiDev.phase);\r
1066                                 reconnected = 1;\r
1067                         }\r
1068                         else\r
1069                         {\r
1070                                 // reselect timeout.\r
1071                                 SCSI_Out_Ctl_Write(0);\r
1072                                 SCSI_ClearPin(SCSI_Out_SEL);\r
1073                                 SCSI_CTL_PHASE_Write(0);\r
1074                                 s2s_ledOff();\r
1075                         }\r
1076                 }\r
1077         }\r
1078         return reconnected;\r
1079 }\r
1080 */\r
1081 \r