Add scsi mode page 0 support
[SCSI2SD.git] / software / SCSI2SD / src / inquiry.c
1 //      Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
2 //
3 //      This file is part of SCSI2SD.
4 //
5 //      SCSI2SD is free software: you can redistribute it and/or modify
6 //      it under the terms of the GNU General Public License as published by
7 //      the Free Software Foundation, either version 3 of the License, or
8 //      (at your option) any later version.
9 //
10 //      SCSI2SD is distributed in the hope that it will be useful,
11 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //      GNU General Public License for more details.
14 //
15 //      You should have received a copy of the GNU General Public License
16 //      along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
17 #include "device.h"
18 #include "scsi.h"
19 #include "config.h"
20 #include "inquiry.h"
21
22 #include <string.h>
23
24 static uint8 StandardResponse[] =
25 {
26 0x00, // "Direct-access device". AKA standard hard disk
27 0x00, // device type modifier
28 0x02, // Complies with ANSI SCSI-2.
29 0x01, // Response format is compatible with the old CCS format.
30 0x1f, // standard length.
31 0, 0, // Reserved
32 0x08 // Enable linked commands
33 };
34 // Vendor set by config 'c','o','d','e','s','r','c',' ',
35 // prodId set by config'S','C','S','I','2','S','D',' ',' ',' ',' ',' ',' ',' ',' ',' ',
36 // Revision set by config'2','.','0','a'
37
38 /* For reference, here's a dump from an Apple branded 500Mb drive from 1994.
39 $ sudo sg_inq -H /dev/sdd --len 255
40 standard INQUIRY:
41  00     00 00 02 01 31 00 00 18  51 55 41 4e 54 55 4d 20    ....1...QUANTUM 
42  10     4c 50 53 32 37 30 20 20  20 20 20 20 20 20 20 20    LPS270          
43  20     30 39 30 30 00 00 00 d9  b0 27 34 01 04 b3 01 1b    0900.....'4.....
44  30     07 00 a0 00 00 ff                                   ......
45  Vendor identification: QUANTUM 
46  Product identification: LPS270          
47  Product revision level: 0900
48 */
49
50
51 static const uint8 SupportedVitalPages[] =
52 {
53 0x00, // "Direct-access device". AKA standard hard disk
54 0x00, // Page Code
55 0x00, // Reserved
56 0x04, // Page length
57 0x00, // Support "Supported vital product data pages"
58 0x80, // Support "Unit serial number page"
59 0x81, // Support "Implemented operating definition page"
60 0x82 // Support "ASCII Implemented operating definition page"
61 };
62
63 static const uint8 UnitSerialNumber[] =
64 {
65 0x00, // "Direct-access device". AKA standard hard disk
66 0x80, // Page Code
67 0x00, // Reserved
68 0x10, // Page length
69 'c','o','d','e','s','r','c','-','1','2','3','4','5','6','7','8'
70 };
71
72 static const uint8 ImpOperatingDefinition[] =
73 {
74 0x00, // "Direct-access device". AKA standard hard disk
75 0x81, // Page Code
76 0x00, // Reserved
77 0x03, // Page length
78 0x03, // Current: SCSI-2 operating definition
79 0x03, // Default: SCSI-2 operating definition
80 0x03 // Supported (list): SCSI-2 operating definition.
81 };
82
83 static const uint8 AscImpOperatingDefinition[] =
84 {
85 0x00, // "Direct-access device". AKA standard hard disk
86 0x82, // Page Code
87 0x00, // Reserved
88 0x07, // Page length
89 0x06, // Ascii length
90 'S','C','S','I','-','2'
91 };
92
93 static void useCustomVPD(const TargetConfig* cfg, int pageCode)
94 {
95         int cfgIdx = 0;
96         int found = 0;
97         while ((cfgIdx < sizeof(cfg->vpd) - 4) &&
98                 (cfg->vpd[cfgIdx + 3] != 0)
99                 )
100         {
101                 int pageSize = cfg->vpd[cfgIdx + 3] + 4;
102                 int dataPageCode = cfg->vpd[cfgIdx + 1];
103                 if (dataPageCode == pageCode)
104                 {
105                         memcpy(scsiDev.data, &(cfg->vpd[cfgIdx]), pageSize);
106                         scsiDev.dataLen = pageSize;
107                         scsiDev.phase = DATA_IN;
108                         found = 1;
109                         break;
110                 }
111                 cfgIdx += pageSize;
112         }
113
114         if (!found)
115         {
116                 // error.
117                 scsiDev.status = CHECK_CONDITION;
118                 scsiDev.target->sense.code = ILLEGAL_REQUEST;
119                 scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
120                 scsiDev.phase = STATUS;
121         }
122 }
123
124 void scsiInquiry()
125 {
126         uint8 evpd = scsiDev.cdb[1] & 1; // enable vital product data.
127         uint8 pageCode = scsiDev.cdb[2];
128         uint32 allocationLength = scsiDev.cdb[4];
129
130         // SASI standard, X3T9.3_185_RevE  states that 0 == 256 bytes
131         // BUT SCSI 2 standard says 0 == 0.
132         if (scsiDev.compatMode <= COMPAT_SCSI1) // excludes COMPAT_SCSI2_DISABLED
133         {
134                 if (allocationLength == 0) allocationLength = 256;
135         }
136
137         if (!evpd)
138         {
139                 if (pageCode)
140                 {
141                         // error.
142                         scsiDev.status = CHECK_CONDITION;
143                         scsiDev.target->sense.code = ILLEGAL_REQUEST;
144                         scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
145                         scsiDev.phase = STATUS;
146                 }
147                 else
148                 {
149                         const TargetConfig* config = scsiDev.target->cfg;
150                         memcpy(scsiDev.data, StandardResponse, sizeof(StandardResponse));
151                         scsiDev.data[1] = scsiDev.target->cfg->deviceTypeModifier;
152
153                         if (scsiDev.compatMode >= COMPAT_SCSI2)
154                         {
155                                 scsiDev.data[3] = 2; // SCSI 2 response format.
156                         }
157                         memcpy(&scsiDev.data[8], config->vendor, sizeof(config->vendor));
158                         memcpy(&scsiDev.data[16], config->prodId, sizeof(config->prodId));
159                         memcpy(&scsiDev.data[32], config->revision, sizeof(config->revision));
160
161                         scsiDev.dataLen = sizeof(StandardResponse) +
162                                 sizeof(config->vendor) +
163                                 sizeof(config->prodId) +
164                                 sizeof(config->revision);
165                         scsiDev.phase = DATA_IN;
166                 }
167         }
168         else if (scsiDev.target->cfg->vpd[3] != 0)
169         {
170                 useCustomVPD(scsiDev.target->cfg, pageCode);
171         }
172         else if (pageCode == 0x00)
173         {
174                 memcpy(scsiDev.data, SupportedVitalPages, sizeof(SupportedVitalPages));
175                 scsiDev.dataLen = sizeof(SupportedVitalPages);
176                 scsiDev.phase = DATA_IN;
177         }
178         else if (pageCode == 0x80)
179         {
180                 memcpy(scsiDev.data, UnitSerialNumber, sizeof(UnitSerialNumber));
181                 scsiDev.dataLen = sizeof(UnitSerialNumber);
182                 const TargetConfig* config = scsiDev.target->cfg;
183                 memcpy(&scsiDev.data[4], config->serial, sizeof(config->serial));
184                 scsiDev.phase = DATA_IN;
185         }
186         else if (pageCode == 0x81)
187         {
188                 memcpy(
189                         scsiDev.data,
190                         ImpOperatingDefinition,
191                         sizeof(ImpOperatingDefinition));
192                 scsiDev.dataLen = sizeof(ImpOperatingDefinition);
193                 scsiDev.phase = DATA_IN;
194         }
195         else if (pageCode == 0x82)
196         {
197                 memcpy(
198                         scsiDev.data,
199                         AscImpOperatingDefinition,
200                         sizeof(AscImpOperatingDefinition));
201                 scsiDev.dataLen = sizeof(AscImpOperatingDefinition);
202                 scsiDev.phase = DATA_IN;
203         }
204         else
205         {
206                 // error.
207                 scsiDev.status = CHECK_CONDITION;
208                 scsiDev.target->sense.code = ILLEGAL_REQUEST;
209                 scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
210                 scsiDev.phase = STATUS;
211         }
212
213
214         if (scsiDev.phase == DATA_IN)
215         {
216                 // "real" hard drives send back exactly allocationLenth bytes, padded
217                 // with zeroes. This only seems to happen for Inquiry responses, and not
218                 // other commands that also supply an allocation length such as Mode Sense or
219                 // Request Sense.
220                 // (See below for exception to this rule when 0 allocation length)
221                 if (scsiDev.dataLen < allocationLength)
222                 {
223                         memset(
224                                 &scsiDev.data[scsiDev.dataLen],
225                                 0,
226                                 allocationLength - scsiDev.dataLen);
227                 }
228                 // Spec 8.2.5 requires us to simply truncate the response if it's
229                 // too big.
230                 scsiDev.dataLen = allocationLength;
231
232                 // Set the device type as needed.
233                 scsiDev.data[0] = getDeviceTypeQualifier();
234
235                 switch (scsiDev.target->cfg->deviceType)
236                 {
237                 case CONFIG_OPTICAL:
238                         scsiDev.data[1] |= 0x80; // Removable bit.
239                         break;
240
241                 case CONFIG_SEQUENTIAL:
242                         scsiDev.data[1] |= 0x80; // Removable bit.
243                         break;
244                         
245                 case CONFIG_MO:
246                         scsiDev.data[1] |= 0x80; // Removable bit.
247                         break;
248
249                 case CONFIG_FLOPPY_14MB:
250                 case CONFIG_REMOVEABLE:
251                         scsiDev.data[1] |= 0x80; // Removable bit.
252                         break;
253                  default:
254                         // Accept defaults for a fixed disk.
255                         break;
256                 }
257
258         }
259
260         // Set the first byte to indicate LUN presence.
261         if (scsiDev.lun) // We only support lun 0
262         {
263                 scsiDev.data[0] = 0x7F;
264         }
265 }
266
267 uint8_t getDeviceTypeQualifier()
268 {
269         // Set the device type as needed.
270         switch (scsiDev.target->cfg->deviceType)
271         {
272         case CONFIG_OPTICAL:
273                 return 0x05;
274                 break;
275
276         case CONFIG_SEQUENTIAL:
277                 return 0x01;
278                 break;
279
280         case CONFIG_MO:
281                 return 0x07;
282                 break;
283
284         case CONFIG_FLOPPY_14MB:
285         case CONFIG_REMOVEABLE:
286                 return 0;
287                 break;
288
289         default:
290                 // Accept defaults for a fixed disk.
291                 return 0;
292         }
293 }
294