1 // Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
\r
2 // Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com>
\r
4 // This file is part of SCSI2SD.
\r
6 // SCSI2SD is free software: you can redistribute it and/or modify
\r
7 // it under the terms of the GNU General Public License as published by
\r
8 // the Free Software Foundation, either version 3 of the License, or
\r
9 // (at your option) any later version.
\r
11 // SCSI2SD is distributed in the hope that it will be useful,
\r
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
14 // GNU General Public License for more details.
\r
16 // You should have received a copy of the GNU General Public License
\r
17 // along with SCSI2SD. If not, see <http://www.gnu.org/licenses/>.
\r
25 static const uint8_t ReadWriteErrorRecoveryPage[] =
\r
28 0x0A, // Page length
\r
30 // VMS 5.5-2 is very particular regarding the mode page values.
\r
31 // The required values for a SCSI2/NoTCQ device are:
\r
32 // AWRE=0 ARRE=0 TB=1 RC=0 EER=? PER=1 DTE=1 DCR=?
\r
33 // See ftp://www.digiater.nl/openvms/decus/vms94b/net94b/scsi_params_dkdriver.txt
\r
34 // X-Newsgroups: comp.os.vms
\r
35 // Subject: Re: VMS 6.1 vs. Seagate Disk Drives
\r
36 // Message-Id: <32g87h$8q@nntpd.lkg.dec.com>
\r
37 // From: weber@evms.enet.dec.com (Ralph O. Weber -- OpenVMS AXP)
\r
38 // Date: 12 Aug 1994 16:32:49 GMT
\r
41 0x00, // Don't try recovery algorithm during reads
\r
42 0x00, // Correction span 0
\r
43 0x00, // Head offset count 0,
\r
44 0x00, // Data strobe offset count 0,
\r
46 0x00, // Don't try recovery algorithm during writes
\r
48 0x00, 0x00 // Recovery time limit 0 (use default)*/
\r
51 static const uint8_t ReadWriteErrorRecoveryPage_SCSI1[] =
\r
54 0x06, // Page length
\r
56 0x00, // Don't try recovery algorithm during reads
\r
57 0x00, // Correction span 0
\r
58 0x00, // Head offset count 0,
\r
59 0x00, // Data strobe offset count 0,
\r
63 static const uint8_t DisconnectReconnectPage[] =
\r
66 0x0E, // Page length
\r
67 0, // Buffer full ratio
\r
68 0, // Buffer empty ratio
\r
69 0x00, 10, // Bus inactivity limit, 100us increments. Allow 1ms.
\r
70 0x00, 0x00, // Disconnect time limit
\r
71 0x00, 0x00, // Connect time limit
\r
72 0x00, 0x00, // Maximum burst size
\r
73 0x00 ,// DTDC. Not used.
\r
74 0x00, 0x00, 0x00 // Reserved
\r
77 static const uint8_t DisconnectReconnectPage_SCSI1[] =
\r
80 0x0A, // Page length
\r
81 0, // Buffer full ratio
\r
82 0, // Buffer empty ratio
\r
83 0x00, 10, // Bus inactivity limit, 100us increments. Allow 1ms.
\r
84 0x00, 0x00, // Disconnect time limit
\r
85 0x00, 0x00, // Connect time limit
\r
86 0x00, 0x00 // Maximum burst size
\r
89 static const uint8_t FormatDevicePage[] =
\r
91 0x03 | 0x80, // Page code | PS (persist) bit.
\r
92 0x16, // Page length
\r
93 0x00, 0x00, // Single zone
\r
94 0x00, 0x00, // No alternate sectors
\r
95 0x00, 0x00, // No alternate tracks
\r
96 0x00, 0x00, // No alternate tracks per lun
\r
97 0x00, 0x00, // Sectors per track, configurable
\r
98 0xFF, 0xFF, // Data bytes per physical sector. Configurable.
\r
99 0x00, 0x01, // Interleave
\r
100 0x00, 0x00, // Track skew factor
\r
101 0x00, 0x00, // Cylinder skew factor
\r
102 0xC0, // SSEC(set) HSEC(set) RMB SURF
\r
103 0x00, 0x00, 0x00 // Reserved
\r
106 static const uint8_t RigidDiskDriveGeometry[] =
\r
109 0x16, // Page length
\r
110 0xFF, 0xFF, 0xFF, // Number of cylinders
\r
111 0x00, // Number of heads (replaced by configured value)
\r
112 0xFF, 0xFF, 0xFF, // Starting cylinder-write precompensation
\r
113 0xFF, 0xFF, 0xFF, // Starting cylinder-reduced write current
\r
114 0x00, 0x1, // Drive step rate (units of 100ns)
\r
115 0x00, 0x00, 0x00, // Landing zone cylinder
\r
117 0x00, // Rotational offset
\r
119 5400 >> 8, 5400 & 0xFF, // Medium rotation rate (RPM)
\r
120 0x00, 0x00 // Reserved
\r
123 static const uint8_t FlexibleDiskDriveGeometry[] =
\r
126 0x1E, // Page length
\r
127 0x01, 0xF4, // Transfer Rate (500kbits)
\r
129 18, // sectors per track
\r
130 0x20,0x00, // bytes per sector
\r
131 0x00, 80, // Cylinders
\r
132 0x00, 0x80, // Write-precomp
\r
133 0x00, 0x80, // reduced current,
\r
134 0x00, 0x00, // Drive step rate
\r
135 0x00, // pulse width
\r
136 0x00, 0x00, // Head settle delay
\r
137 0x00, // motor on delay
\r
138 0x00, // motor off delay
\r
152 static const uint8_t RigidDiskDriveGeometry_SCSI1[] =
\r
155 0x12, // Page length
\r
156 0xFF, 0xFF, 0xFF, // Number of cylinders
\r
157 0x00, // Number of heads (replaced by configured value)
\r
158 0xFF, 0xFF, 0xFF, // Starting cylinder-write precompensation
\r
159 0xFF, 0xFF, 0xFF, // Starting cylinder-reduced write current
\r
160 0x00, 0x1, // Drive step rate (units of 100ns)
\r
161 0x00, 0x00, 0x00, // Landing zone cylinder
\r
163 0x00, // Rotational offset
\r
167 static const uint8_t CachingPage[] =
\r
170 0x0A, // Page length
\r
171 0x01, // Read cache disable
\r
172 0x00, // No useful rention policy.
\r
173 0x00, 0x00, // Pre-fetch always disabled
\r
174 0x00, 0x00, // Minimum pre-fetch
\r
175 0x00, 0x00, // Maximum pre-fetch
\r
176 0x00, 0x00, // Maximum pre-fetch ceiling
\r
179 // Old CCS SCSI-1 cache page
\r
180 static const uint8_t CCSCachingPage[] =
\r
183 0x0E, // Page length
\r
184 0x00, // Read cache disable
\r
185 0x00, // Prefetch threshold
\r
186 0x00, 0x00, // Max threshold / multiplier
\r
187 0x00, 0x00, // Min threshold / multiplier
\r
188 0x00, 0x00, // Reserved
\r
194 static const uint8_t ControlModePage[] =
\r
197 0x06, // Page length
\r
198 0x00, // No logging
\r
199 0x01, // Disable tagged queuing
\r
200 0x00, // No async event notifications
\r
202 0x00, 0x00 // AEN holdoff period.
\r
205 static const uint8_t SequentialDeviceConfigPage[] =
\r
208 0x0E, // Page length
\r
209 0x00, // CAP, CAF, Active Format
\r
210 0x00, // Active partition
\r
211 0x00, // Write buffer full ratio
\r
212 0x00, // Read buffer empty ratio
\r
213 0x00,0x01, // Write delay time, in 100ms units
\r
214 0x00, // Default gap size
\r
215 0x10, // auto-generation of default eod (end of data)
\r
216 0x00,0x00,0x00, // buffer-size at early warning
\r
217 0x00, // No data compression
\r
221 // Allow Apple 68k Drive Setup to format this drive.
\r
223 static const uint8_t AppleVendorPage[] =
\r
227 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
\r
228 'A','P','P','L','E',' ','C','O','M','P','U','T','E','R',',',' ','I','N','C','.'
\r
231 static void pageIn(int pc, int dataIdx, const uint8_t* pageData, int pageLen)
\r
233 memcpy(&scsiDev.data[dataIdx], pageData, pageLen);
\r
235 if (pc == 0x01) // Mask out (un)changable values
\r
237 memset(&scsiDev.data[dataIdx+2], 0, pageLen - 2);
\r
241 static void doModeSense(
\r
242 int sixByteCmd, int dbd, int pc, int pageCode, int allocLength)
\r
244 ////////////// Mode Parameter Header
\r
245 ////////////////////////////////////
\r
247 // Skip the Mode Data Length, we set that last.
\r
249 if (!sixByteCmd) ++idx;
\r
251 uint8_t mediumType = 0;
\r
252 uint8_t deviceSpecificParam = 0;
\r
253 uint8_t density = 0;
\r
254 switch (scsiDev.target->cfg->deviceType)
\r
256 case S2S_CFG_FIXED:
\r
257 case S2S_CFG_REMOVEABLE:
\r
258 mediumType = 0; // We should support various floppy types here!
\r
259 // Contains cache bits (0) and a Write-Protect bit.
\r
260 deviceSpecificParam =
\r
261 (blockDev.state & DISK_WP) ? 0x80 : 0;
\r
262 density = 0; // reserved for direct access
\r
265 case S2S_CFG_FLOPPY_14MB:
\r
266 mediumType = 0x1E; // 90mm/3.5"
\r
267 deviceSpecificParam =
\r
268 (blockDev.state & DISK_WP) ? 0x80 : 0;
\r
269 density = 0; // reserved for direct access
\r
272 case S2S_CFG_OPTICAL:
\r
273 mediumType = 0x02; // 120mm CDROM, data only.
\r
274 deviceSpecificParam = 0;
\r
275 density = 0x01; // User data only, 2048bytes per sector.
\r
278 case S2S_CFG_SEQUENTIAL:
\r
279 mediumType = 0; // reserved
\r
280 deviceSpecificParam =
\r
281 (blockDev.state & DISK_WP) ? 0x80 : 0;
\r
282 density = 0x13; // DAT Data Storage, X3B5/88-185A
\r
286 mediumType = 0x03; // Optical reversible or erasable medium
\r
287 deviceSpecificParam =
\r
288 (blockDev.state & DISK_WP) ? 0x80 : 0;
\r
289 density = 0x00; // Default
\r
294 scsiDev.data[idx++] = mediumType;
\r
295 scsiDev.data[idx++] = deviceSpecificParam;
\r
301 scsiDev.data[idx++] = 0; // No block descriptor
\r
305 // One block descriptor of length 8 bytes.
\r
306 scsiDev.data[idx++] = 8;
\r
311 scsiDev.data[idx++] = 0; // Reserved
\r
312 scsiDev.data[idx++] = 0; // Reserved
\r
315 scsiDev.data[idx++] = 0; // No block descriptor
\r
316 scsiDev.data[idx++] = 0; // No block descriptor
\r
320 // One block descriptor of length 8 bytes.
\r
321 scsiDev.data[idx++] = 0;
\r
322 scsiDev.data[idx++] = 8;
\r
326 ////////////// Block Descriptor
\r
327 ////////////////////////////////////
\r
330 scsiDev.data[idx++] = density;
\r
331 // Number of blocks
\r
332 // Zero == all remaining blocks shall have the medium
\r
333 // characteristics specified.
\r
334 scsiDev.data[idx++] = 0;
\r
335 scsiDev.data[idx++] = 0;
\r
336 scsiDev.data[idx++] = 0;
\r
338 scsiDev.data[idx++] = 0; // reserved
\r
341 uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
\r
342 scsiDev.data[idx++] = bytesPerSector >> 16;
\r
343 scsiDev.data[idx++] = bytesPerSector >> 8;
\r
344 scsiDev.data[idx++] = bytesPerSector & 0xFF;
\r
349 if (pageCode == 0x01 || pageCode == 0x3F)
\r
352 if ((scsiDev.compatMode >= COMPAT_SCSI2))
\r
354 pageIn(pc, idx, ReadWriteErrorRecoveryPage, sizeof(ReadWriteErrorRecoveryPage));
\r
355 idx += sizeof(ReadWriteErrorRecoveryPage);
\r
359 pageIn(pc, idx, ReadWriteErrorRecoveryPage_SCSI1, sizeof(ReadWriteErrorRecoveryPage_SCSI1));
\r
360 idx += sizeof(ReadWriteErrorRecoveryPage_SCSI1);
\r
364 if (pageCode == 0x02 || pageCode == 0x3F)
\r
367 if ((scsiDev.compatMode >= COMPAT_SCSI2))
\r
369 pageIn(pc, idx, DisconnectReconnectPage, sizeof(DisconnectReconnectPage));
\r
370 idx += sizeof(DisconnectReconnectPage);
\r
374 pageIn(pc, idx, DisconnectReconnectPage_SCSI1, sizeof(DisconnectReconnectPage_SCSI1));
\r
375 idx += sizeof(DisconnectReconnectPage_SCSI1);
\r
379 if (pageCode == 0x03 || pageCode == 0x3F)
\r
382 pageIn(pc, idx, FormatDevicePage, sizeof(FormatDevicePage));
\r
385 uint16_t sectorsPerTrack = scsiDev.target->cfg->sectorsPerTrack;
\r
386 scsiDev.data[idx+10] = sectorsPerTrack >> 8;
\r
387 scsiDev.data[idx+11] = sectorsPerTrack & 0xFF;
\r
389 // Fill out the configured bytes-per-sector
\r
390 uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
\r
391 scsiDev.data[idx+12] = bytesPerSector >> 8;
\r
392 scsiDev.data[idx+13] = bytesPerSector & 0xFF;
\r
396 // Set a mask for the changeable values.
\r
397 scsiDev.data[idx+12] = 0xFF;
\r
398 scsiDev.data[idx+13] = 0xFF;
\r
401 idx += sizeof(FormatDevicePage);
\r
404 if (pageCode == 0x04 || pageCode == 0x3F)
\r
407 if ((scsiDev.compatMode >= COMPAT_SCSI2))
\r
409 pageIn(pc, idx, RigidDiskDriveGeometry, sizeof(RigidDiskDriveGeometry));
\r
413 pageIn(pc, idx, RigidDiskDriveGeometry_SCSI1, sizeof(RigidDiskDriveGeometry_SCSI1));
\r
418 // Need to fill out the number of cylinders.
\r
424 scsiDev.target->cfg->sdSectorStart,
\r
425 scsiDev.target->liveCfg.bytesPerSector,
\r
426 scsiDev.target->cfg->scsiSectors),
\r
430 scsiDev.target->cfg->headsPerCylinder,
\r
431 scsiDev.target->cfg->sectorsPerTrack);
\r
433 scsiDev.data[idx+2] = cyl >> 16;
\r
434 scsiDev.data[idx+3] = cyl >> 8;
\r
435 scsiDev.data[idx+4] = cyl;
\r
437 memcpy(&scsiDev.data[idx+6], &scsiDev.data[idx+2], 3);
\r
438 memcpy(&scsiDev.data[idx+9], &scsiDev.data[idx+2], 3);
\r
440 scsiDev.data[idx+5] = scsiDev.target->cfg->headsPerCylinder;
\r
443 if ((scsiDev.compatMode >= COMPAT_SCSI2))
\r
445 idx += sizeof(RigidDiskDriveGeometry);
\r
449 idx += sizeof(RigidDiskDriveGeometry_SCSI1);
\r
453 if (pageCode == 0x05 || pageCode == 0x3F)
\r
456 pageIn(pc, idx, FlexibleDiskDriveGeometry, sizeof(FlexibleDiskDriveGeometry));
\r
457 idx += sizeof(FlexibleDiskDriveGeometry);
\r
460 // DON'T output the following pages for SCSI1 hosts. They get upset when
\r
461 // we have more data to send than the allocation length provided.
\r
462 // (ie. Try not to output any more pages below this comment)
\r
465 if ((scsiDev.compatMode >= COMPAT_SCSI2) &&
\r
466 (pageCode == 0x08 || pageCode == 0x3F))
\r
469 pageIn(pc, idx, CachingPage, sizeof(CachingPage));
\r
470 idx += sizeof(CachingPage);
\r
473 if ((scsiDev.compatMode >= COMPAT_SCSI2)
\r
474 && (pageCode == 0x0A || pageCode == 0x3F))
\r
477 pageIn(pc, idx, ControlModePage, sizeof(ControlModePage));
\r
478 idx += sizeof(ControlModePage);
\r
481 if ((scsiDev.target->cfg->deviceType == S2S_CFG_SEQUENTIAL) &&
\r
482 (pageCode == 0x10 || pageCode == 0x3F))
\r
488 SequentialDeviceConfigPage,
\r
489 sizeof(SequentialDeviceConfigPage));
\r
490 idx += sizeof(SequentialDeviceConfigPage);
\r
494 (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_APPLE) ||
\r
495 (idx + sizeof(AppleVendorPage) <= allocLength)
\r
497 (pageCode == 0x30 || pageCode == 0x3F))
\r
500 pageIn(pc, idx, AppleVendorPage, sizeof(AppleVendorPage));
\r
501 idx += sizeof(AppleVendorPage);
\r
504 if (pageCode == 0x38) // Don't send unless requested
\r
507 pageIn(pc, idx, CCSCachingPage, sizeof(CCSCachingPage));
\r
508 idx += sizeof(CCSCachingPage);
\r
513 // Unknown Page Code
\r
515 scsiDev.status = CHECK_CONDITION;
\r
516 scsiDev.target->sense.code = ILLEGAL_REQUEST;
\r
517 scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
\r
518 scsiDev.phase = STATUS;
\r
522 // Go back and fill out the mode data length
\r
525 // Cannot currently exceed limits. yay
\r
526 scsiDev.data[0] = idx - 1;
\r
530 scsiDev.data[0] = ((idx - 2) >> 8);
\r
531 scsiDev.data[1] = (idx - 2);
\r
534 scsiDev.dataLen = idx > allocLength ? allocLength : idx;
\r
535 scsiDev.phase = DATA_IN;
\r
540 // Callback after the DATA OUT phase is complete.
\r
541 static void doModeSelect(void)
\r
543 if (scsiDev.status == GOOD) // skip if we've already encountered an error
\r
545 // scsiDev.dataLen bytes are in scsiDev.data
\r
549 if (scsiDev.cdb[0] == 0x55)
\r
552 (((uint16_t)scsiDev.data[6]) << 8) |scsiDev.data[7];
\r
557 blockDescLen = scsiDev.data[3];
\r
561 // The unwritten rule. Blocksizes are normally set using the
\r
562 // block descriptor value, not by changing page 0x03.
\r
563 if (blockDescLen >= 8)
\r
565 uint32_t bytesPerSector =
\r
566 (((uint32_t)scsiDev.data[idx+5]) << 16) |
\r
567 (((uint32_t)scsiDev.data[idx+6]) << 8) |
\r
568 scsiDev.data[idx+7];
\r
569 if ((bytesPerSector < MIN_SECTOR_SIZE) ||
\r
570 (bytesPerSector > MAX_SECTOR_SIZE))
\r
576 scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;
\r
577 if (bytesPerSector != scsiDev.target->cfg->bytesPerSector)
\r
579 s2s_configSave(scsiDev.target->targetId, bytesPerSector);
\r
583 idx += blockDescLen;
\r
585 while (idx < scsiDev.dataLen)
\r
587 int pageLen = scsiDev.data[idx + 1];
\r
588 if (idx + 2 + pageLen > scsiDev.dataLen) goto bad;
\r
590 int pageCode = scsiDev.data[idx] & 0x3F;
\r
593 case 0x03: // Format Device Page
\r
595 if (pageLen != 0x16) goto bad;
\r
597 // Fill out the configured bytes-per-sector
\r
598 uint16_t bytesPerSector =
\r
599 (((uint16_t)scsiDev.data[idx+12]) << 8) |
\r
600 scsiDev.data[idx+13];
\r
602 // Sane values only, ok ?
\r
603 if ((bytesPerSector < MIN_SECTOR_SIZE) ||
\r
604 (bytesPerSector > MAX_SECTOR_SIZE))
\r
609 scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;
\r
610 if (scsiDev.cdb[1] & 1) // SP Save Pages flag
\r
612 s2s_configSave(scsiDev.target->targetId, bytesPerSector);
\r
618 // Easiest to just ignore for now. We'll get here when changing
\r
619 // the SCSI block size via the descriptor header.
\r
621 idx += 2 + pageLen;
\r
627 scsiDev.status = CHECK_CONDITION;
\r
628 scsiDev.target->sense.code = ILLEGAL_REQUEST;
\r
629 scsiDev.target->sense.asc = INVALID_FIELD_IN_PARAMETER_LIST;
\r
632 scsiDev.phase = STATUS;
\r
635 int scsiModeCommand()
\r
637 int commandHandled = 1;
\r
639 uint8_t command = scsiDev.cdb[0];
\r
641 // We don't currently support the setting of any parameters.
\r
642 // (ie. no MODE SELECT(6) or MODE SELECT(10) commands)
\r
644 if (command == 0x1A)
\r
647 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors
\r
648 int pc = scsiDev.cdb[2] >> 6; // Page Control
\r
649 int pageCode = scsiDev.cdb[2] & 0x3F;
\r
650 int allocLength = scsiDev.cdb[4];
\r
652 // SCSI1 standard: (CCS X3T9.2/86-52)
\r
653 // "An Allocation Length of zero indicates that no MODE SENSE data shall
\r
654 // be transferred. This condition shall not be considered as an error."
\r
655 doModeSense(1, dbd, pc, pageCode, allocLength);
\r
657 else if (command == 0x5A)
\r
660 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors
\r
661 int pc = scsiDev.cdb[2] >> 6; // Page Control
\r
662 int pageCode = scsiDev.cdb[2] & 0x3F;
\r
664 (((uint16_t) scsiDev.cdb[7]) << 8) +
\r
666 doModeSense(0, dbd, pc, pageCode, allocLength);
\r
668 else if (command == 0x15)
\r
671 int len = scsiDev.cdb[4];
\r
674 // If len == 0, then transfer no data. From the SCSI 2 standard:
\r
675 // A parameter list length of zero indicates that no data shall
\r
676 // be transferred. This condition shall not be considered as an
\r
678 scsiDev.phase = STATUS;
\r
682 scsiDev.dataLen = len;
\r
683 scsiDev.phase = DATA_OUT;
\r
684 scsiDev.postDataOutHook = doModeSelect;
\r
687 else if (command == 0x55)
\r
690 int allocLength = (((uint16_t) scsiDev.cdb[7]) << 8) + scsiDev.cdb[8];
\r
691 if (allocLength == 0)
\r
693 // If len == 0, then transfer no data. From the SCSI 2 standard:
\r
694 // A parameter list length of zero indicates that no data shall
\r
695 // be transferred. This condition shall not be considered as an
\r
697 scsiDev.phase = STATUS;
\r
701 scsiDev.dataLen = allocLength;
\r
702 scsiDev.phase = DATA_OUT;
\r
703 scsiDev.postDataOutHook = doModeSelect;
\r
708 commandHandled = 0;
\r
711 return commandHandled;
\r