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