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