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