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