Ugly workaround for lack of multiple HID device support on OSX.
[SCSI2SD-V6.git] / software / scsi2sd-config / scsi2sd-config.cc
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
18 #include "SCSI2SD_HID.hh"
19
20 #if __cplusplus >= 201103L
21 #include <memory>
22 using std::shared_ptr;
23 #else
24 #include <tr1/memory>
25 using std::tr1::shared_ptr;
26 #endif
27
28 #include <iomanip>
29 #include <iostream>
30 #include <sstream>
31
32 // Request extended stdio format macros.
33 #define __STDC_FORMAT_MACROS
34 #include <inttypes.h>
35
36 #include <getopt.h>
37 #include <stdint.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42
43 // htonl/ntohl includes.
44 #ifdef _WIN32
45 #include <winsock2.h>
46 #else
47 #include <arpa/inet.h>
48 #endif
49
50 #include "hidapi.h"
51
52 #define MIN(a,b) (a < b ? a : b)
53
54 using namespace SCSI2SD;
55
56 enum
57 {
58         PARAM_ID,
59         PARAM_PARITY,
60         PARAM_NOPARITY,
61         PARAM_UNITATT,
62         PARAM_NOUNITATT,
63         PARAM_MAXBLOCKS,
64         PARAM_APPLE,
65         PARAM_VENDOR,
66         PARAM_PRODID,
67         PARAM_REV,
68         PARAM_BYTESPERSECTOR,
69         PARAM_RESET
70 };
71
72 // Must be consistent with the structure defined in the SCSI2SD config.h header.
73 // We always transfer data in network byte order.
74 struct __attribute__((packed)) ConfigPacket 
75 {
76         uint8_t scsiId;
77         char vendor[8];
78         char prodId[16];
79         char revision[4];
80         uint8_t enableParity;
81         uint8_t enableUnitAttention;
82         uint8_t reserved1; // Unused. Ensures maxBlocks is aligned.
83         uint32_t maxSectors;
84         uint16_t bytesPerSector;
85
86
87         // Pad to 64 bytes, which is what we can fit into a USB HID packet.
88         char reserved[26];
89
90         void fromNet()
91         {
92                 maxSectors = ntohl(maxSectors);
93                 bytesPerSector = ntohs(bytesPerSector);
94         }
95         void toNet()
96         {
97                 maxSectors = htonl(maxSectors);
98                 bytesPerSector = htons(bytesPerSector);
99         }
100         void reset()
101         {
102                 scsiId = 0;
103                 strcpy(vendor, " codesrc");
104                 strcpy(prodId, "         SCSI2SD");
105                 strcpy(revision, " 3.5");
106                 enableParity = 1;
107                 enableUnitAttention = 1;
108                 reserved1 = 0;
109                 maxSectors = 0;
110                 bytesPerSector = 512;
111         }
112 };
113
114 static void printConfig(ConfigPacket* packet)
115 {
116         std::cout <<
117                 "SCSI ID:\t\t\t" << packet->scsiId << "\n" <<
118                 "Vendor:\t\t\t\t\"" << std::string(packet->vendor, 8) << "\"\n" <<
119                 "Product ID:\t\t\t\"" << std::string(packet->prodId, 16) << "\"\n" <<
120                 "Revision:\t\t\t\"" << std::string(packet->revision, 4) << "\"\n" <<
121                 "\n" <<
122                 "Parity Checking:\t\t" <<
123                         (packet->enableParity ? "enabled" : "disabled") << "\n" <<
124                 "Unit Attention Condition:\t" <<
125                         (packet->enableUnitAttention ? "enabled" : "disabled") << "\n" <<
126                 "Bytes per sector:\t\t" << packet->bytesPerSector << std::endl;
127
128         if (packet->maxSectors)
129         {
130                 char sizeBuf[64];
131                 uint64_t maxBytes = packet->maxSectors * (uint64_t) packet->bytesPerSector;
132                 if (maxBytes > (1024*1024*1024))
133                 {
134                         sprintf(sizeBuf, "%.02fGB", maxBytes / (1024.0*1024.0*1024.0));
135                 }
136                 else if (maxBytes > (1024*1024))
137                 {
138                         sprintf(sizeBuf, "%.02fMB", maxBytes / (1024.0*1024.0));
139                 }
140                 else if (maxBytes > (1024))
141                 {
142                         sprintf(sizeBuf, "%.02fKB", maxBytes / (1024.0));
143                 }
144                 else
145                 {
146                         sprintf(sizeBuf, "%" PRIu64 " bytes", maxBytes);
147                 }
148
149                 std::cout <<"Maximum Size:\t\t\t" << sizeBuf <<
150                         " (" << packet->maxSectors << " sectors)" << std::endl;
151         }
152         else
153         {
154                 std::cout << "Maximum Size:\t\t\tUnlimited" << std::endl;
155         }
156 }
157
158 static void usage()
159 {
160         printf("Usage: scsi2sd-config [options...]\n");
161         printf("\n");
162         printf("--id={0-7}\tSCSI device ID.\n\n");
163         printf("--parity\tCheck the SCSI parity signal, and reject data where\n");
164         printf("\t\tthe parity is bad.\n\n");
165         printf("--no-parity\tDon't check the SCSI parity signal.\n");
166         printf("\t\tThis is required for SCSI host controllers that do not provide\n");
167         printf("\t\tparity.\n\n");
168         printf("--attention\tRespond with a Unit Attention status on device reset.\n");
169         printf("\t\tSome systems will fail on this response, even though it is\n");
170         printf("\t\trequired by the SCSI-2 standard.\n\n");
171         printf("--no-attention\tDisable Unit Attention responses.\n\n");
172         printf("--blocks={0-4294967295}\n\t\tSet a limit to the reported device size.\n");
173         printf("\t\tThe size of each block/sector is set by the --sector parameter.\n");
174         printf("\t\tThe reported size will be the lower of this value and the SD\n");
175         printf("\t\tcard size. 0 disables the limit.\n");
176         printf("\t\tThe maximum possible size is 2TB.\n\n");
177         printf("--sector={64-8192}\n\t\tSet the bytes-per-sector. Normally 512 bytes.\n");
178         printf("\t\tCan also be set with a SCSI MODE SELECT command.\n\n");
179         printf("--apple\t\tSet the vendor, product ID and revision fields to simulate an \n");
180         printf("\t\tapple-suppled disk. Provides support for the Apple Drive Setup\n");
181         printf("\t\tutility.\n\n");
182         printf("--vendor={vendor}\tSets the reported device vendor. Up to 8 characters.\n\n");
183         printf("--prod-id={prod-id}\tSets the reported product ID. Up to 16 characters.\n\n");
184         printf("--rev={revision}\tSets the reported device revision. Up to 4 characters.\n\n");
185         printf("--reset\tRevert all settings to factory defaults.\n\n");
186         printf("\n");
187         printf("\nThe current configuration settings are displayed if no options are supplied");
188         printf("\n\n");
189         exit(1);
190 }
191
192 int main(int argc, char* argv[])
193 {
194         printf("SCSI2SD Configuration Utility.\n");
195         printf("Copyright (C) 2013 Michael McMaster <michael@codesrc.com>\n\n");
196
197         printf(
198                 "USB device parameters\n\tVendor ID:\t0x%04X\n\tProduct ID:\t0x%04X\n",
199                 HID::VENDOR_ID,
200                 HID::PRODUCT_ID);
201
202         // Enumerate and print the HID devices on the system
203         shared_ptr<HID> scsi2sdHID(HID::Open());
204
205         if (!scsi2sdHID)
206         {
207                 fprintf(stderr, "ERROR: SCSI2SD USB device not found.\n");
208                 exit(1);
209         }
210
211         std::stringstream foundMsg;
212         foundMsg <<
213                 "Device Found\n" <<
214                 "  Firmware Version:\t" << scsi2sdHID->getFirmwareVersionStr();
215
216         std::cout << foundMsg.str() << std::endl;
217
218         ConfigPacket packet;
219         try
220         {
221                 scsi2sdHID->readConfig(
222                         reinterpret_cast<uint8_t*>(&packet),
223                         sizeof(packet)
224                         );
225                 packet.fromNet();
226         }
227         catch (std::exception& e)
228         {
229                 std::cerr << "ERROR: Invalid data received from device.\n" <<
230                         e.what() << std::endl;
231                 exit(1);
232         }
233
234         struct option options[] =
235         {
236                 {
237                         "id", required_argument, NULL, PARAM_ID
238                 },
239                 {
240                         "parity", no_argument, NULL, PARAM_PARITY
241                 },
242                 {
243                         "no-parity", no_argument, NULL, PARAM_NOPARITY
244                 },
245                 {
246                         "attention", no_argument, NULL, PARAM_UNITATT
247                 },
248                 {
249                         "no-attention", no_argument, NULL, PARAM_NOUNITATT
250                 },
251                 {
252                         "blocks", required_argument, NULL, PARAM_MAXBLOCKS
253                 },
254                 {
255                         "apple", no_argument, NULL, PARAM_APPLE
256                 },
257                 {
258                         "vendor", required_argument, NULL, PARAM_VENDOR
259                 },
260                 {
261                         "prod-id", required_argument, NULL, PARAM_PRODID
262                 },
263                 {
264                         "rev", required_argument, NULL, PARAM_REV
265                 },
266                 {
267                         "sector", required_argument, NULL, PARAM_BYTESPERSECTOR
268                 },
269                 {
270                         "reset", no_argument, NULL, PARAM_RESET
271                 },
272                 {
273                         NULL, 0, NULL, 0
274                 }
275         };
276
277         int doWrite = 0;
278         int optIdx = 0;
279         int c;
280         while ((c = getopt_long(argc, argv, "", options, &optIdx)) != -1)
281         {
282                 doWrite = 1;
283                 switch (c)
284                 {
285                 case PARAM_ID:
286                 {
287                         int id = -1;
288                         if (sscanf(optarg, "%d", &id) == 1 && id >= 0 && id <= 7)
289                         {
290                                 packet.scsiId = id;
291                         }
292                         else
293                         {
294                                 usage();
295                         }
296                         break;
297                 }
298
299                 case PARAM_PARITY:
300                         packet.enableParity = 1;
301                         break;
302
303                 case PARAM_NOPARITY:
304                         packet.enableParity = 0;
305                         break;
306
307                 case PARAM_UNITATT:
308                         packet.enableUnitAttention = 1;
309                         break;
310
311                 case PARAM_NOUNITATT:
312                         packet.enableUnitAttention = 0;
313                         break;
314
315                 case PARAM_MAXBLOCKS:
316                 {
317                         int64_t maxSectors = -1;
318                         if (sscanf(optarg, "%" PRId64, &maxSectors) == 1 &&
319                                 maxSectors >= 0 && maxSectors <= 0xffffffff)
320                         {
321                                 packet.maxSectors = maxSectors;
322                         }
323                         else
324                         {
325                                 usage();
326                         }
327                         break;
328                 }
329
330                 case PARAM_APPLE:
331                         memcpy(packet.vendor, " SEAGATE", 8);
332                         memcpy(packet.prodId, "          ST225N", 16);
333                         memcpy(packet.revision, "1.0 ", 4);
334                         break;
335
336                 case PARAM_VENDOR:
337                         memset(packet.vendor, ' ', 8);
338                         memcpy(packet.vendor, optarg, MIN(strlen(optarg), 8));
339                         break;
340
341                 case PARAM_PRODID:
342                         memset(packet.prodId, ' ', 16);
343                         memcpy(packet.prodId, optarg, MIN(strlen(optarg), 16));
344                         break;
345
346                 case PARAM_REV:
347                         memset(packet.revision, ' ', 4);
348                         memcpy(packet.revision, optarg, MIN(strlen(optarg), 4));
349                         break;
350
351                 case PARAM_BYTESPERSECTOR:
352                 {
353                         int64_t bytesPerSector = -1;
354                         if (sscanf(optarg, "%" PRId64, &bytesPerSector) == 1 &&
355                                 bytesPerSector >= 64 && bytesPerSector <= 8192)
356                         {
357                                 packet.bytesPerSector = bytesPerSector;
358                         }
359                         else
360                         {
361                                 usage();
362                         }
363                         break;
364                 }
365                 case PARAM_RESET:
366                         packet.reset();
367                         break;
368
369                 case '?':
370                         doWrite = 0;
371                         usage();
372                 }
373         }
374
375         if (doWrite)
376         {
377                 printf("\nSaving configuration...");
378                 try
379                 {
380                         packet.toNet();
381                         scsi2sdHID->saveConfig(
382                                 reinterpret_cast<uint8_t*>(&packet),
383                                 sizeof(packet));
384                 }
385                 catch (std::exception& e)
386                 {
387                         printf(" Fail.\n");
388                         std::cerr << "ERROR: Failed to save config.\n" << e.what() << std::endl;
389                         exit(1);
390                 }
391                 printf(" Done.\n");
392
393                 sleep(1); // Wait for the data to be saved to eeprom
394
395                 // Clear outstanding stale data
396                 scsi2sdHID->readConfig(
397                         reinterpret_cast<uint8_t*>(&packet),
398                         sizeof(packet));
399
400                 // Proper update
401                 scsi2sdHID->readConfig(
402                         reinterpret_cast<uint8_t*>(&packet),
403                         sizeof(packet));
404                 packet.fromNet();
405         }
406
407         printf("\nCurrent Device Settings:\n");
408         printConfig(&packet);
409
410         return 0;
411 }
412