c66608acbbe951d0a5282e088e2320c0b5141c7d
[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 if (pc == 0x03) // Saved Values not supported.
149 {
150 scsiDev.status = CHECK_CONDITION;
151 scsiDev.target->sense.code = ILLEGAL_REQUEST;
152 scsiDev.target->sense.asc = SAVING_PARAMETERS_NOT_SUPPORTED;
153 scsiDev.phase = STATUS;
154 }
155 else
156 {
157 ////////////// Mode Parameter Header
158 ////////////////////////////////////
159
160 // Skip the Mode Data Length, we set that last.
161 int idx = 1;
162 if (!sixByteCmd) ++idx;
163
164 uint8_t mediumType = 0;
165 uint8_t deviceSpecificParam = 0;
166 uint8_t density = 0;
167 switch (scsiDev.target->cfg->deviceType == CONFIG_OPTICAL)
168 {
169 case CONFIG_FIXED:
170 case CONFIG_REMOVEABLE:
171 mediumType = 0; // We should support various floppy types here!
172 // Contains cache bits (0) and a Write-Protect bit.
173 deviceSpecificParam =
174 (blockDev.state & DISK_WP) ? 0x80 : 0;
175 density = 0; // reserved for direct access
176 break;
177
178 case CONFIG_FLOPPY_14MB:
179 mediumType = 0x1E; // 90mm/3.5"
180 deviceSpecificParam =
181 (blockDev.state & DISK_WP) ? 0x80 : 0;
182 density = 0; // reserved for direct access
183 break;
184
185 case CONFIG_OPTICAL:
186 mediumType = 0x02; // 120mm CDROM, data only.
187 deviceSpecificParam = 0;
188 density = 0x01; // User data only, 2048bytes per sector.
189 break;
190
191 };
192
193 scsiDev.data[idx++] = mediumType;
194 scsiDev.data[idx++] = deviceSpecificParam;
195
196 if (sixByteCmd)
197 {
198 if (dbd)
199 {
200 scsiDev.data[idx++] = 0; // No block descriptor
201 }
202 else
203 {
204 // One block descriptor of length 8 bytes.
205 scsiDev.data[idx++] = 8;
206 }
207 }
208 else
209 {
210 scsiDev.data[idx++] = 0; // Reserved
211 scsiDev.data[idx++] = 0; // Reserved
212 if (dbd)
213 {
214 scsiDev.data[idx++] = 0; // No block descriptor
215 scsiDev.data[idx++] = 0; // No block descriptor
216 }
217 else
218 {
219 // One block descriptor of length 8 bytes.
220 scsiDev.data[idx++] = 0;
221 scsiDev.data[idx++] = 8;
222 }
223 }
224
225 ////////////// Block Descriptor
226 ////////////////////////////////////
227 if (!dbd)
228 {
229 scsiDev.data[idx++] = density;
230 // Number of blocks
231 // Zero == all remaining blocks shall have the medium
232 // characteristics specified.
233 scsiDev.data[idx++] = 0;
234 scsiDev.data[idx++] = 0;
235 scsiDev.data[idx++] = 0;
236
237 scsiDev.data[idx++] = 0; // reserved
238
239 // Block length
240 uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
241 scsiDev.data[idx++] = bytesPerSector >> 16;
242 scsiDev.data[idx++] = bytesPerSector >> 8;
243 scsiDev.data[idx++] = bytesPerSector & 0xFF;
244 }
245
246 int pageFound = 0;
247
248 if (pageCode == 0x01 || pageCode == 0x3F)
249 {
250 pageFound = 1;
251 pageIn(pc, idx, ReadWriteErrorRecoveryPage, sizeof(ReadWriteErrorRecoveryPage));
252 idx += sizeof(ReadWriteErrorRecoveryPage);
253 }
254
255 if (pageCode == 0x02 || pageCode == 0x3F)
256 {
257 pageFound = 1;
258 pageIn(pc, idx, DisconnectReconnectPage, sizeof(DisconnectReconnectPage));
259 idx += sizeof(DisconnectReconnectPage);
260 }
261
262 if (pageCode == 0x03 || pageCode == 0x3F)
263 {
264 pageFound = 1;
265 pageIn(pc, idx, FormatDevicePage, sizeof(FormatDevicePage));
266 if (pc != 0x01)
267 {
268 // Fill out the configured bytes-per-sector
269 uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
270 scsiDev.data[idx+12] = bytesPerSector >> 8;
271 scsiDev.data[idx+13] = bytesPerSector & 0xFF;
272 }
273 else
274 {
275 // Set a mask for the changeable values.
276 scsiDev.data[idx+12] = 0xFF;
277 scsiDev.data[idx+13] = 0xFF;
278 }
279
280 idx += sizeof(FormatDevicePage);
281 }
282
283 if (pageCode == 0x04 || pageCode == 0x3F)
284 {
285 pageFound = 1;
286 pageIn(pc, idx, RigidDiskDriveGeometry, sizeof(RigidDiskDriveGeometry));
287
288 if (pc != 0x01)
289 {
290 // Need to fill out the number of cylinders.
291 uint32 cyl;
292 uint8 head;
293 uint32 sector;
294 LBA2CHS(
295 getScsiCapacity(
296 scsiDev.target->cfg->sdSectorStart,
297 scsiDev.target->liveCfg.bytesPerSector,
298 scsiDev.target->cfg->scsiSectors),
299 &cyl,
300 &head,
301 &sector);
302
303 scsiDev.data[idx+2] = cyl >> 16;
304 scsiDev.data[idx+3] = cyl >> 8;
305 scsiDev.data[idx+4] = cyl;
306
307 memcpy(&scsiDev.data[idx+6], &scsiDev.data[idx+2], 3);
308 memcpy(&scsiDev.data[idx+9], &scsiDev.data[idx+2], 3);
309 }
310
311 idx += sizeof(RigidDiskDriveGeometry);
312 }
313
314 // DON'T output the following pages for SCSI1 hosts. They get upset when
315 // we have more data to send than the allocation length provided.
316 // (ie. Try not to output any more pages below this comment)
317
318
319 if (!scsiDev.compatMode && (pageCode == 0x08 || pageCode == 0x3F))
320 {
321 pageFound = 1;
322 pageIn(pc, idx, CachingPage, sizeof(CachingPage));
323 idx += sizeof(CachingPage);
324 }
325
326 if (!scsiDev.compatMode && (pageCode == 0x0A || pageCode == 0x3F))
327 {
328 pageFound = 1;
329 pageIn(pc, idx, ControlModePage, sizeof(ControlModePage));
330 idx += sizeof(ControlModePage);
331 }
332
333 if ((
334 (scsiDev.target->cfg->quirks == CONFIG_QUIRKS_APPLE) ||
335 (idx + sizeof(AppleVendorPage) <= allocLength)
336 ) &&
337 (pageCode == 0x30 || pageCode == 0x3F))
338 {
339 pageFound = 1;
340 pageIn(pc, idx, AppleVendorPage, sizeof(AppleVendorPage));
341 idx += sizeof(AppleVendorPage);
342 }
343
344 if (!pageFound)
345 {
346 // Unknown Page Code
347 pageFound = 0;
348 scsiDev.status = CHECK_CONDITION;
349 scsiDev.target->sense.code = ILLEGAL_REQUEST;
350 scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
351 scsiDev.phase = STATUS;
352 }
353 else
354 {
355 // Go back and fill out the mode data length
356 if (sixByteCmd)
357 {
358 // Cannot currently exceed limits. yay
359 scsiDev.data[0] = idx - 1;
360 }
361 else
362 {
363 scsiDev.data[0] = ((idx - 2) >> 8);
364 scsiDev.data[1] = (idx - 2);
365 }
366
367 scsiDev.dataLen = idx > allocLength ? allocLength : idx;
368 scsiDev.phase = DATA_IN;
369 }
370 }
371 }
372
373 // Callback after the DATA OUT phase is complete.
374 static void doModeSelect(void)
375 {
376 if (scsiDev.status == GOOD) // skip if we've already encountered an error
377 {
378 // scsiDev.dataLen bytes are in scsiDev.data
379
380 int idx;
381 int blockDescLen;
382 if (scsiDev.cdb[0] == 0x55)
383 {
384 blockDescLen =
385 (((uint16_t)scsiDev.data[6]) << 8) |scsiDev.data[7];
386 idx = 8;
387 }
388 else
389 {
390 blockDescLen = scsiDev.data[3];
391 idx = 4;
392 }
393
394 // The unwritten rule. Blocksizes are normally set using the
395 // block descriptor value, not by changing page 0x03.
396 if (blockDescLen >= 8)
397 {
398 uint32_t bytesPerSector =
399 (((uint32_t)scsiDev.data[idx+5]) << 16) |
400 (((uint32_t)scsiDev.data[idx+6]) << 8) |
401 scsiDev.data[idx+7];
402 if ((bytesPerSector < MIN_SECTOR_SIZE) ||
403 (bytesPerSector > MAX_SECTOR_SIZE))
404 {
405 goto bad;
406 }
407 else
408 {
409 scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;
410 if (bytesPerSector != scsiDev.target->cfg->bytesPerSector)
411 {
412 configSave(scsiDev.target->targetId, bytesPerSector);
413 }
414 }
415 }
416 idx += blockDescLen;
417
418 while (idx < scsiDev.dataLen)
419 {
420 int pageLen = scsiDev.data[idx + 1];
421 if (idx + 2 + pageLen > scsiDev.dataLen) goto bad;
422
423 int pageCode = scsiDev.data[idx] & 0x3F;
424 switch (pageCode)
425 {
426 case 0x03: // Format Device Page
427 {
428 if (pageLen != 0x16) goto bad;
429
430 // Fill out the configured bytes-per-sector
431 uint16_t bytesPerSector =
432 (((uint16_t)scsiDev.data[idx+12]) << 8) |
433 scsiDev.data[idx+13];
434
435 // Sane values only, ok ?
436 if ((bytesPerSector < MIN_SECTOR_SIZE) ||
437 (bytesPerSector > MAX_SECTOR_SIZE))
438 {
439 goto bad;
440 }
441
442 scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;
443 if (scsiDev.cdb[1] & 1) // SP Save Pages flag
444 {
445 configSave(scsiDev.target->targetId, bytesPerSector);
446 }
447 }
448 break;
449 //default:
450
451 // Easiest to just ignore for now. We'll get here when changing
452 // the SCSI block size via the descriptor header.
453 }
454 idx += 2 + pageLen;
455 }
456 }
457
458 goto out;
459 bad:
460 scsiDev.status = CHECK_CONDITION;
461 scsiDev.target->sense.code = ILLEGAL_REQUEST;
462 scsiDev.target->sense.asc = INVALID_FIELD_IN_PARAMETER_LIST;
463
464 out:
465 scsiDev.phase = STATUS;
466 }
467
468 int scsiModeCommand()
469 {
470 int commandHandled = 1;
471
472 uint8 command = scsiDev.cdb[0];
473
474 // We don't currently support the setting of any parameters.
475 // (ie. no MODE SELECT(6) or MODE SELECT(10) commands)
476
477 if (command == 0x1A)
478 {
479 // MODE SENSE(6)
480 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors
481 int pc = scsiDev.cdb[2] >> 6; // Page Control
482 int pageCode = scsiDev.cdb[2] & 0x3F;
483 int allocLength = scsiDev.cdb[4];
484 if (allocLength == 0) allocLength = 256;
485 doModeSense(1, dbd, pc, pageCode, allocLength);
486 }
487 else if (command == 0x5A)
488 {
489 // MODE SENSE(10)
490 int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors
491 int pc = scsiDev.cdb[2] >> 6; // Page Control
492 int pageCode = scsiDev.cdb[2] & 0x3F;
493 int allocLength =
494 (((uint16) scsiDev.cdb[7]) << 8) +
495 scsiDev.cdb[8];
496 doModeSense(0, dbd, pc, pageCode, allocLength);
497 }
498 else if (command == 0x15)
499 {
500 // MODE SELECT(6)
501 int len = scsiDev.cdb[4];
502 if (len == 0)
503 {
504 // If len == 0, then transfer no data. From the SCSI 2 standard:
505 // A parameter list length of zero indicates that no data shall
506 // be transferred. This condition shall not be considered as an
507 // error.
508 scsiDev.phase = STATUS;
509 }
510 else
511 {
512 scsiDev.dataLen = len;
513 scsiDev.phase = DATA_OUT;
514 scsiDev.postDataOutHook = doModeSelect;
515 }
516 }
517 else if (command == 0x55)
518 {
519 // MODE SELECT(10)
520 int allocLength = (((uint16) scsiDev.cdb[7]) << 8) + scsiDev.cdb[8];
521 if (allocLength == 0)
522 {
523 // If len == 0, then transfer no data. From the SCSI 2 standard:
524 // A parameter list length of zero indicates that no data shall
525 // be transferred. This condition shall not be considered as an
526 // error.
527 scsiDev.phase = STATUS;
528 }
529 else
530 {
531 scsiDev.dataLen = allocLength;
532 scsiDev.phase = DATA_OUT;
533 scsiDev.postDataOutHook = doModeSelect;
534 }
535 }
536 else
537 {
538 commandHandled = 0;
539 }
540
541 return commandHandled;
542 }
543
544 #pragma GCC pop_options