Small compatibility improvements, and added scsi2sd-monitor test program
[SCSI2SD-V6.git] / software / SCSI2SD / src / mode.c
1 // Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
2 // Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com>
3 //
4 // This file is part of SCSI2SD.
5 //
6 // SCSI2SD is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // SCSI2SD is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with SCSI2SD. If not, see <http://www.gnu.org/licenses/>.
18 #pragma GCC push_options
19 #pragma GCC optimize("-flto")
20
21 #include "device.h"
22 #include "scsi.h"
23 #include "mode.h"
24 #include "disk.h"
25
26 #include <string.h>
27
28 static const uint8 ReadWriteErrorRecoveryPage[] =
29 {
30 0x01, // Page code
31 0x0A, // Page length
32
33 // VMS 5.5-2 is very particular regarding the mode page values.
34 // The required values for a SCSI2/NoTCQ device are:
35 // AWRE=0 ARRE=0 TB=1 RC=0 EER=? PER=1 DTE=1 DCR=?
36 // See ftp://www.digiater.nl/openvms/decus/vms94b/net94b/scsi_params_dkdriver.txt
37 // X-Newsgroups: comp.os.vms
38 // Subject: Re: VMS 6.1 vs. Seagate Disk Drives
39 // Message-Id: <32g87h$8q@nntpd.lkg.dec.com>
40 // From: weber@evms.enet.dec.com (Ralph O. Weber -- OpenVMS AXP)
41 // Date: 12 Aug 1994 16:32:49 GMT
42 0x26,
43
44 0x00, // Don't try recovery algorithm during reads
45 0x00, // Correction span 0
46 0x00, // Head offset count 0,
47 0x00, // Data strobe offset count 0,
48 0x00, // Reserved
49 0x00, // Don't try recovery algorithm during writes
50 0x00, // Reserved
51 0x00, 0x00 // Recovery time limit 0 (use default)*/
52 };
53
54 static const uint8 DisconnectReconnectPage[] =
55 {
56 0x02, // Page code
57 0x0E, // Page length
58 0, // Buffer full ratio
59 0, // Buffer empty ratio
60 0x00, 10, // Bus inactivity limit, 100us increments. Allow 1ms.
61 0x00, 0x00, // Disconnect time limit
62 0x00, 0x00, // Connect time limit
63 0x00, 0x00, // Maximum burst size
64 0x00 ,// DTDC. Not used.
65 0x00, 0x00, 0x00 // Reserved
66 };
67
68 static const uint8 FormatDevicePage[] =
69 {
70 0x03 | 0x80, // Page code | PS (persist) bit.
71 0x16, // Page length
72 0x00, 0x00, // Single zone
73 0x00, 0x00, // No alternate sectors
74 0x00, 0x00, // No alternate tracks
75 0x00, 0x00, // No alternate tracks per lun
76 0x00, SCSI_SECTORS_PER_TRACK, // Sectors per track
77 0xFF, 0xFF, // Data bytes per physical sector. Configurable.
78 0x00, 0x01, // Interleave
79 0x00, 0x00, // Track skew factor
80 0x00, 0x00, // Cylinder skew factor
81 0xC0, // SSEC(set) HSEC(set) RMB SURF
82 0x00, 0x00, 0x00 // Reserved
83 };
84
85 static const uint8 RigidDiskDriveGeometry[] =
86 {
87 0x04, // Page code
88 0x16, // Page length
89 0xFF, 0xFF, 0xFF, // Number of cylinders
90 SCSI_HEADS_PER_CYLINDER, // Number of heads
91 0xFF, 0xFF, 0xFF, // Starting cylinder-write precompensation
92 0xFF, 0xFF, 0xFF, // Starting cylinder-reduced write current
93 0x00, 0x1, // Drive step rate (units of 100ns)
94 0x00, 0x00, 0x00, // Landing zone cylinder
95 0x00, // RPL
96 0x00, // Rotational offset
97 0x00, // Reserved
98 5400 >> 8, 5400 & 0xFF, // Medium rotation rate (RPM)
99 0x00, 0x00 // Reserved
100 };
101
102 static const uint8 CachingPage[] =
103 {
104 0x08, // Page Code
105 0x0A, // Page length
106 0x01, // Read cache disable
107 0x00, // No useful rention policy.
108 0x00, 0x00, // Pre-fetch always disabled
109 0x00, 0x00, // Minimum pre-fetch
110 0x00, 0x00, // Maximum pre-fetch
111 0x00, 0x00, // Maximum pre-fetch ceiling
112 };
113
114 static const uint8 ControlModePage[] =
115 {
116 0x0A, // Page code
117 0x06, // Page length
118 0x00, // No logging
119 0x01, // Disable tagged queuing
120 0x00, // No async event notifications
121 0x00, // Reserved
122 0x00, 0x00 // AEN holdoff period.
123 };
124
125 // Allow Apple 68k Drive Setup to format this drive.
126 // Code
127 static const uint8 AppleVendorPage[] =
128 {
129 0x30, // Page code
130 28, // Page length
131 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
132 'A','P','P','L','E',' ','C','O','M','P','U','T','E','R',',',' ','I','N','C','.'
133 };
134
135 static void pageIn(int pc, int dataIdx, const uint8* pageData, int pageLen)
136 {
137 memcpy(&scsiDev.data[dataIdx], pageData, pageLen);
138
139 if (pc == 0x01) // Mask out (un)changable values
140 {
141 memset(&scsiDev.data[dataIdx+2], 0, pageLen - 2);
142 }
143 }
144
145 static void doModeSense(
146 int sixByteCmd, int dbd, int pc, int pageCode, int allocLength)
147 {
148 ////////////// Mode Parameter Header
149 ////////////////////////////////////
150
151 // Skip the Mode Data Length, we set that last.
152 int idx = 1;
153 if (!sixByteCmd) ++idx;
154
155 uint8_t mediumType = 0;
156 uint8_t deviceSpecificParam = 0;
157 uint8_t density = 0;
158 switch (scsiDev.target->cfg->deviceType == CONFIG_OPTICAL)
159 {
160 case CONFIG_FIXED:
161 case CONFIG_REMOVEABLE:
162 mediumType = 0; // We should support various floppy types here!
163 // Contains cache bits (0) and a Write-Protect bit.
164 deviceSpecificParam =
165 (blockDev.state & DISK_WP) ? 0x80 : 0;
166 density = 0; // reserved for direct access
167 break;
168
169 case CONFIG_FLOPPY_14MB:
170 mediumType = 0x1E; // 90mm/3.5"
171 deviceSpecificParam =
172 (blockDev.state & DISK_WP) ? 0x80 : 0;
173 density = 0; // reserved for direct access
174 break;
175
176 case CONFIG_OPTICAL:
177 mediumType = 0x02; // 120mm CDROM, data only.
178 deviceSpecificParam = 0;
179 density = 0x01; // User data only, 2048bytes per sector.
180 break;
181
182 };
183
184 scsiDev.data[idx++] = mediumType;
185 scsiDev.data[idx++] = deviceSpecificParam;
186
187 if (sixByteCmd)
188 {
189 if (dbd)
190 {
191 scsiDev.data[idx++] = 0; // No block descriptor
192 }
193 else
194 {
195 // One block descriptor of length 8 bytes.
196 scsiDev.data[idx++] = 8;
197 }
198 }
199 else
200 {
201 scsiDev.data[idx++] = 0; // Reserved
202 scsiDev.data[idx++] = 0; // Reserved
203 if (dbd)
204 {
205 scsiDev.data[idx++] = 0; // No block descriptor
206 scsiDev.data[idx++] = 0; // No block descriptor
207 }
208 else
209 {
210 // One block descriptor of length 8 bytes.
211 scsiDev.data[idx++] = 0;
212 scsiDev.data[idx++] = 8;
213 }
214 }
215
216 ////////////// Block Descriptor
217 ////////////////////////////////////
218 if (!dbd)
219 {
220 scsiDev.data[idx++] = density;
221 // Number of blocks
222 // Zero == all remaining blocks shall have the medium
223 // characteristics specified.
224 scsiDev.data[idx++] = 0;
225 scsiDev.data[idx++] = 0;
226 scsiDev.data[idx++] = 0;
227
228 scsiDev.data[idx++] = 0; // reserved
229
230 // Block length
231 uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
232 scsiDev.data[idx++] = bytesPerSector >> 16;
233 scsiDev.data[idx++] = bytesPerSector >> 8;
234 scsiDev.data[idx++] = bytesPerSector & 0xFF;
235 }
236
237 int pageFound = 0;
238
239 if (pageCode == 0x01 || pageCode == 0x3F)
240 {
241 pageFound = 1;
242 pageIn(pc, idx, ReadWriteErrorRecoveryPage, sizeof(ReadWriteErrorRecoveryPage));
243 idx += sizeof(ReadWriteErrorRecoveryPage);
244 }
245
246 if (pageCode == 0x02 || pageCode == 0x3F)
247 {
248 pageFound = 1;
249 pageIn(pc, idx, DisconnectReconnectPage, sizeof(DisconnectReconnectPage));
250 idx += sizeof(DisconnectReconnectPage);
251 }
252
253 if (pageCode == 0x03 || pageCode == 0x3F)
254 {
255 pageFound = 1;
256 pageIn(pc, idx, FormatDevicePage, sizeof(FormatDevicePage));
257 if (pc != 0x01)
258 {
259 // Fill out the configured bytes-per-sector
260 uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
261 scsiDev.data[idx+12] = bytesPerSector >> 8;
262 scsiDev.data[idx+13] = bytesPerSector & 0xFF;
263 }
264 else
265 {
266 // Set a mask for the changeable values.
267 scsiDev.data[idx+12] = 0xFF;
268 scsiDev.data[idx+13] = 0xFF;
269 }
270
271 idx += sizeof(FormatDevicePage);
272 }
273
274 if (pageCode == 0x04 || pageCode == 0x3F)
275 {
276 pageFound = 1;
277 pageIn(pc, idx, RigidDiskDriveGeometry, sizeof(RigidDiskDriveGeometry));
278
279 if (pc != 0x01)
280 {
281 // Need to fill out the number of cylinders.
282 uint32 cyl;
283 uint8 head;
284 uint32 sector;
285 LBA2CHS(
286 getScsiCapacity(
287 scsiDev.target->cfg->sdSectorStart,
288 scsiDev.target->liveCfg.bytesPerSector,
289 scsiDev.target->cfg->scsiSectors),
290 &cyl,
291 &head,
292 &sector);
293
294 scsiDev.data[idx+2] = cyl >> 16;
295 scsiDev.data[idx+3] = cyl >> 8;
296 scsiDev.data[idx+4] = cyl;
297
298 memcpy(&scsiDev.data[idx+6], &scsiDev.data[idx+2], 3);
299 memcpy(&scsiDev.data[idx+9], &scsiDev.data[idx+2], 3);
300 }
301
302 idx += sizeof(RigidDiskDriveGeometry);
303 }
304
305 // DON'T output the following pages for SCSI1 hosts. They get upset when
306 // we have more data to send than the allocation length provided.
307 // (ie. Try not to output any more pages below this comment)
308
309
310 if ((scsiDev.compatMode >= COMPAT_SCSI2) &&
311 (pageCode == 0x08 || pageCode == 0x3F))
312 {
313 pageFound = 1;
314 pageIn(pc, idx, CachingPage, sizeof(CachingPage));
315 idx += sizeof(CachingPage);
316 }
317
318 if ((scsiDev.compatMode >= COMPAT_SCSI2)
319 && (pageCode == 0x0A || pageCode == 0x3F))
320 {
321 pageFound = 1;
322 pageIn(pc, idx, ControlModePage, sizeof(ControlModePage));
323 idx += sizeof(ControlModePage);
324 }
325
326 if ((
327 (scsiDev.target->cfg->quirks == CONFIG_QUIRKS_APPLE) ||
328 (idx + sizeof(AppleVendorPage) <= allocLength)
329 ) &&
330 (pageCode == 0x30 || pageCode == 0x3F))
331 {
332 pageFound = 1;
333 pageIn(pc, idx, AppleVendorPage, sizeof(AppleVendorPage));
334 idx += sizeof(AppleVendorPage);
335 }
336
337 if (!pageFound)
338 {
339 // Unknown Page Code
340 pageFound = 0;
341 scsiDev.status = CHECK_CONDITION;
342 scsiDev.target->sense.code = ILLEGAL_REQUEST;
343 scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
344 scsiDev.phase = STATUS;
345 }
346 else
347 {
348 // Go back and fill out the mode data length
349 if (sixByteCmd)
350 {
351 // Cannot currently exceed limits. yay
352 scsiDev.data[0] = idx - 1;
353 }
354 else
355 {
356 scsiDev.data[0] = ((idx - 2) >> 8);
357 scsiDev.data[1] = (idx - 2);
358 }
359
360 scsiDev.dataLen = idx > allocLength ? allocLength : idx;
361 scsiDev.phase = DATA_IN;
362 }
363 }
364
365
366 // Callback after the DATA OUT phase is complete.
367 static void doModeSelect(void)
368 {
369 if (scsiDev.status == GOOD) // skip if we've already encountered an error
370 {
371 // scsiDev.dataLen bytes are in scsiDev.data
372
373 int idx;
374 int blockDescLen;
375 if (scsiDev.cdb[0] == 0x55)
376 {
377 blockDescLen =
378 (((uint16_t)scsiDev.data[6]) << 8) |scsiDev.data[7];
379 idx = 8;
380 }
381 else
382 {
383 blockDescLen = scsiDev.data[3];
384 idx = 4;
385 }
386
387 // The unwritten rule. Blocksizes are normally set using the
388 // block descriptor value, not by changing page 0x03.
389 if (blockDescLen >= 8)
390 {
391 uint32_t bytesPerSector =
392 (((uint32_t)scsiDev.data[idx+5]) << 16) |
393 (((uint32_t)scsiDev.data[idx+6]) << 8) |
394 scsiDev.data[idx+7];
395 if ((bytesPerSector < MIN_SECTOR_SIZE) ||
396 (bytesPerSector > MAX_SECTOR_SIZE))
397 {
398 goto bad;
399 }
400 else
401 {
402 scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;
403 if (bytesPerSector != scsiDev.target->cfg->bytesPerSector)
404 {
405 configSave(scsiDev.target->targetId, bytesPerSector);
406 }
407 }
408 }
409 idx += blockDescLen;
410
411 while (idx < scsiDev.dataLen)
412 {
413 int pageLen = scsiDev.data[idx + 1];
414 if (idx + 2 + pageLen > scsiDev.dataLen) goto bad;
415
416 int pageCode = scsiDev.data[idx] & 0x3F;
417 switch (pageCode)
418 {
419 case 0x03: // Format Device Page
420 {
421 if (pageLen != 0x16) goto bad;
422
423 // Fill out the configured bytes-per-sector
424 uint16_t bytesPerSector =
425 (((uint16_t)scsiDev.data[idx+12]) << 8) |
426 scsiDev.data[idx+13];
427
428 // Sane values only, ok ?
429 if ((bytesPerSector < MIN_SECTOR_SIZE) ||
430 (bytesPerSector > MAX_SECTOR_SIZE))
431 {
432 goto bad;
433 }
434
435 scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;
436 if (scsiDev.cdb[1] & 1) // SP Save Pages flag
437 {
438 configSave(scsiDev.target->targetId, bytesPerSector);
439 }
440 }
441 break;
442 //default:
443
444 // Easiest to just ignore for now. We'll get here when changing
445 // the SCSI block size via the descriptor header.
446 }
447 idx += 2 + pageLen;
448 }
449 }
450
451 goto out;
452 bad:
453 scsiDev.status = CHECK_CONDITION;
454 scsiDev.target->sense.code = ILLEGAL_REQUEST;
455 scsiDev.target->sense.asc = INVALID_FIELD_IN_PARAMETER_LIST;
456
457 out:
458 scsiDev.phase = STATUS;
459 }
460
461 int scsiModeCommand()
462 {
463 int commandHandled = 1;
464
465 uint8 command = scsiDev.cdb[0];
466
467 // We don't currently support the setting of any parameters.
468 // (ie. no MODE SELECT(6) or MODE SELECT(10) commands)
469
470 if (command == 0x1A)
471 {
472 // MODE SENSE(6)
473 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors
474 int pc = scsiDev.cdb[2] >> 6; // Page Control
475 int pageCode = scsiDev.cdb[2] & 0x3F;
476 int allocLength = scsiDev.cdb[4];
477 if (allocLength == 0) allocLength = 256;
478 doModeSense(1, dbd, pc, pageCode, allocLength);
479 }
480 else if (command == 0x5A)
481 {
482 // MODE SENSE(10)
483 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors
484 int pc = scsiDev.cdb[2] >> 6; // Page Control
485 int pageCode = scsiDev.cdb[2] & 0x3F;
486 int allocLength =
487 (((uint16) scsiDev.cdb[7]) << 8) +
488 scsiDev.cdb[8];
489 doModeSense(0, dbd, pc, pageCode, allocLength);
490 }
491 else if (command == 0x15)
492 {
493 // MODE SELECT(6)
494 int len = scsiDev.cdb[4];
495 if (len == 0)
496 {
497 // If len == 0, then transfer no data. From the SCSI 2 standard:
498 // A parameter list length of zero indicates that no data shall
499 // be transferred. This condition shall not be considered as an
500 // error.
501 scsiDev.phase = STATUS;
502 }
503 else
504 {
505 scsiDev.dataLen = len;
506 scsiDev.phase = DATA_OUT;
507 scsiDev.postDataOutHook = doModeSelect;
508 }
509 }
510 else if (command == 0x55)
511 {
512 // MODE SELECT(10)
513 int allocLength = (((uint16) scsiDev.cdb[7]) << 8) + scsiDev.cdb[8];
514 if (allocLength == 0)
515 {
516 // If len == 0, then transfer no data. From the SCSI 2 standard:
517 // A parameter list length of zero indicates that no data shall
518 // be transferred. This condition shall not be considered as an
519 // error.
520 scsiDev.phase = STATUS;
521 }
522 else
523 {
524 scsiDev.dataLen = allocLength;
525 scsiDev.phase = DATA_OUT;
526 scsiDev.postDataOutHook = doModeSelect;
527 }
528 }
529 else
530 {
531 commandHandled = 0;
532 }
533
534 return commandHandled;
535 }
536
537 #pragma GCC pop_options