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