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