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