Updated.
[wine] / dlls / winaspi / aspi.c
1 /**************************************************************************
2 ASPI routines
3 (C) 2000 David Elliott <dfe@infinite-internet.net>
4 Licensed under the WINE (X11) license
5 */
6
7 /* These routines are to be called from either WNASPI32 or WINASPI */
8
9 /* FIXME:
10  * - Registry format is stupid for now.. fix that later
11  * - No way to override automatic /proc detection, maybe provide an
12  *   HKEY_LOCAL_MACHINE\Software\Wine\Wine\Scsi regkey
13  * - Somewhat debating an #ifdef linux... technically all this code will
14  *   run on another UNIX.. it will fail nicely.
15  * - Please add support for mapping multiple channels on host adapters to
16  *   aspi controllers, e-mail me if you need help.
17  */
18
19 /*
20 Registry format is currently:
21 HKEY_DYN_DATA
22         WineScsi
23                 (default)=number of host adapters
24                 hHHcCCtTTdDD=linux device name
25 */
26 #include <stdio.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <sys/ioctl.h>
30 #include <fcntl.h>
31 #include <dirent.h>
32 #include <unistd.h>
33 #include <errno.h>
34 #include <string.h>
35
36 #include "debugtools.h"
37 #include "winreg.h"
38 #include "winerror.h"
39 #include "winescsi.h"
40
41 DEFAULT_DEBUG_CHANNEL(aspi);
42
43 /* Internal function prototypes */
44 static void
45 SCSI_GetProcinfo();
46
47 #ifdef linux
48 static void
49 SCSI_MapHCtoController();
50 #endif
51
52 static void set_last_error(void)
53 {
54     int save_errno = errno; /* errno gets overwritten by printf */
55     switch (errno)
56     {
57     case EAGAIN:
58         SetLastError( ERROR_SHARING_VIOLATION );
59         break;
60     case EBADF:
61         SetLastError( ERROR_INVALID_HANDLE );
62         break;
63     case ENOSPC:
64         SetLastError( ERROR_HANDLE_DISK_FULL );
65         break;
66     case EACCES:
67     case EPERM:
68     case EROFS:
69         SetLastError( ERROR_ACCESS_DENIED );
70         break;
71     case EBUSY:
72         SetLastError( ERROR_LOCK_VIOLATION );
73         break;
74     case ENOENT:
75         SetLastError( ERROR_FILE_NOT_FOUND );
76         break;
77     case EISDIR:
78         SetLastError( ERROR_CANNOT_MAKE );
79         break;
80     case ENFILE:
81     case EMFILE:
82         SetLastError( ERROR_NO_MORE_FILES );
83         break;
84     case EEXIST:
85         SetLastError( ERROR_FILE_EXISTS );
86         break;
87     case EINVAL:
88     case ESPIPE:
89         SetLastError( ERROR_SEEK );
90         break;
91     case ENOTEMPTY:
92         SetLastError( ERROR_DIR_NOT_EMPTY );
93         break;
94     case ENOEXEC:
95         SetLastError( ERROR_BAD_FORMAT );
96         break;
97     default:
98         WARN( "unknown file error: %s", strerror(save_errno) );
99         SetLastError( ERROR_GEN_FAILURE );
100         break;
101     }
102     errno = save_errno;
103 }
104
105 /* Exported functions */
106 void
107 SCSI_Init()
108 {
109         /* For now we just call SCSI_GetProcinfo */
110         SCSI_GetProcinfo();
111 #ifdef linux
112         SCSI_MapHCtoController();
113 #endif
114 }
115
116 int
117 ASPI_GetNumControllers()
118 {
119         HKEY hkeyScsi;
120         HKEY hkeyControllerMap;
121         DWORD error;
122         DWORD type = REG_DWORD;
123         DWORD num_ha = 0;
124         DWORD cbData = sizeof(num_ha);
125
126         if( RegOpenKeyExA(HKEY_DYN_DATA, KEYNAME_SCSI, 0, KEY_ALL_ACCESS, &hkeyScsi ) != ERROR_SUCCESS )
127         {
128                 ERR("Could not open HKEY_DYN_DATA\\%s\n",KEYNAME_SCSI);
129                 return 0;
130         }
131
132         if( (error=RegOpenKeyExA(hkeyScsi, KEYNAME_SCSI_CONTROLLERMAP, 0, KEY_ALL_ACCESS, &hkeyControllerMap )) != ERROR_SUCCESS )
133         {
134                 ERR("Could not open HKEY_DYN_DATA\\%s\\%s\n", KEYNAME_SCSI, KEYNAME_SCSI_CONTROLLERMAP);
135                 RegCloseKey(hkeyScsi);
136                 SetLastError(error);
137                 return 0;
138         }
139         if( RegQueryValueExA(hkeyControllerMap, NULL, NULL, &type, (LPBYTE)&num_ha, &cbData ) != ERROR_SUCCESS )
140         {
141                 ERR("Could not query value HKEY_DYN_DATA\\%s\n",KEYNAME_SCSI);
142                 num_ha=0;
143         }
144         RegCloseKey(hkeyControllerMap);
145         RegCloseKey(hkeyScsi);
146         TRACE("Returning %ld host adapters\n", num_ha );
147         return num_ha;
148 }
149
150 BOOL
151 SCSI_GetDeviceName( int h, int c, int t, int d, LPSTR devstr, LPDWORD lpcbData )
152 {
153
154         char idstr[20];
155         HKEY hkeyScsi;
156         DWORD type;
157
158         if( RegOpenKeyExA(HKEY_DYN_DATA, KEYNAME_SCSI, 0, KEY_ALL_ACCESS, &hkeyScsi ) != ERROR_SUCCESS )
159         {
160                 ERR("Could not open HKEY_DYN_DATA\\%s\n",KEYNAME_SCSI);
161                 return FALSE;
162         }
163
164
165         sprintf(idstr, "h%02dc%02dt%02dd%02d", h, c, t, d);
166
167         if( RegQueryValueExA(hkeyScsi, idstr, NULL, &type, devstr, lpcbData) != ERROR_SUCCESS )
168         {
169                 WARN("Could not query value HKEY_DYN_DATA\\%s\\%s\n",KEYNAME_SCSI, idstr);
170                 RegCloseKey(hkeyScsi);
171                 return FALSE;
172         }
173         RegCloseKey(hkeyScsi);
174
175         TRACE("scsi %s: Device name: %s\n",idstr,devstr);
176         return TRUE;
177 }
178
179 /* SCSI_GetHCforController
180  * RETURNS
181  *      HIWORD: Host Adapter
182  *      LOWORD: Channel
183  */
184 DWORD
185 ASPI_GetHCforController( int controller )
186 {
187         DWORD hc = 0xFFFFFFFF;
188         char cstr[20];
189         DWORD error;
190         HKEY hkeyScsi;
191         HKEY hkeyControllerMap;
192         DWORD type = REG_DWORD;
193         DWORD cbData = sizeof(DWORD);
194         DWORD disposition;
195 #if 0
196         FIXME("Please fix to map each channel of each host adapter to the proper ASPI controller number!\n");
197         hc = (controller << 16);
198         return hc;
199 #endif
200         if( (error=RegCreateKeyExA(HKEY_DYN_DATA, KEYNAME_SCSI, 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hkeyScsi, &disposition )) != ERROR_SUCCESS )
201         {
202                 ERR("Could not open HKEY_DYN_DATA\\%s\n",KEYNAME_SCSI);
203                 SetLastError(error);
204                 return hc;
205         }
206         if( disposition != REG_OPENED_EXISTING_KEY )
207         {
208                 WARN("Created HKEY_DYN_DATA\\%s\n",KEYNAME_SCSI);
209         }
210         if( (error=RegCreateKeyExA(hkeyScsi, KEYNAME_SCSI_CONTROLLERMAP, 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hkeyControllerMap, &disposition )) != ERROR_SUCCESS )
211         {
212                 ERR("Could not open HKEY_DYN_DATA\\%s\\%s\n", KEYNAME_SCSI, KEYNAME_SCSI_CONTROLLERMAP);
213                 RegCloseKey(hkeyScsi);
214                 SetLastError(error);
215                 return hc;
216         }
217         if( disposition != REG_OPENED_EXISTING_KEY )
218         {
219                 WARN("Created HKEY_DYN_DATA\\%s\\%s\n",KEYNAME_SCSI, KEYNAME_SCSI_CONTROLLERMAP);
220         }
221
222         sprintf(cstr, "c%02d", controller);
223         if( (error=RegQueryValueExA( hkeyControllerMap, cstr, 0, &type, (LPBYTE)&hc, &cbData)) != ERROR_SUCCESS )
224         {
225                 ERR("Could not open HKEY_DYN_DATA\\%s\\%s\\%s, error=%lx\n", KEYNAME_SCSI, KEYNAME_SCSI_CONTROLLERMAP, cstr, error );
226                 SetLastError( error );
227         }
228         RegCloseKey(hkeyControllerMap);
229         RegCloseKey(hkeyScsi);
230         return hc;
231
232 };
233
234 int
235 SCSI_OpenDevice( int h, int c, int t, int d )
236 {
237         char devstr[20];
238         DWORD cbData = 20;
239         int fd = -1;
240         char dainbread_linux_hack = 1;
241
242         if(!SCSI_GetDeviceName( h, c, t, d, devstr, &cbData ))
243         {
244                 WARN("Could not get device name for h%02dc%02dt%02dd%02d\n", h, c, t, d);
245                 return -1;
246         }
247
248 linux_hack:
249         TRACE("Opening device %s mode O_RDWR\n",devstr);
250         fd = open(devstr, O_RDWR);
251
252         if( fd < 0 )
253         {
254                 int len = strlen(devstr);
255                 set_last_error(); /* SetLastError() to errno */
256                 TRACE("Open failed (%s)\n", strerror(errno));
257
258                 /* in case of "/dev/sgX", convert from sga to sg0
259                  * and the other way around.
260                  * FIXME: remove it if the distributions
261                  * finally manage to agree on something.
262                  * The best would probably be a complete
263                  * rewrite of the Linux SCSI layer
264                  * to use CAM + devfs :-) */
265                 if ( (dainbread_linux_hack) &&
266                      (len >= 3) &&
267                      (devstr[len-3] == 's') && (devstr[len-2] == 'g') )
268                 {
269                         char *p = &devstr[len-1];
270                         *p = (*p >= 'a') ? *p - 'a' + '0' : *p - '0' + 'a';
271                         dainbread_linux_hack = 0;
272                         TRACE("Retry with \"equivalent\" Linux device '%s'\n", devstr);
273                         goto linux_hack;
274                 }
275         }
276         return fd;
277 }
278
279 #ifdef linux
280 /* SCSI_Fix_CMD_LEN
281  *      Checks to make sure the CMD_LEN is correct
282  */
283 void
284 SCSI_Fix_CMD_LEN(int fd, int cmd, int len)
285 {
286         int index=(cmd>>5)&7;
287
288         if (len!=scsi_command_size[index])
289         {
290                 TRACE("CDBLen for command %d claims to be %d, expected %d\n",
291                                 cmd, len, scsi_command_size[index]);
292                 ioctl(fd,SG_NEXT_CMD_LEN,&len);
293         }
294 }
295
296 int
297 SCSI_LinuxSetTimeout( int fd, int timeout )
298 {
299         int retval;
300         TRACE("Setting timeout to %d jiffies\n", timeout);
301         retval=ioctl(fd,SG_SET_TIMEOUT,&timeout);
302         if(retval)
303         {
304                 WARN("Could not set timeout ! (%s)\n", strerror(errno));
305         }
306         return retval;
307         
308 }
309
310 /* This function takes care of the write/read to the linux sg device.
311  * It returns TRUE or FALSE and uses set_last_error() to convert
312  * UNIX errno to Windows GetLastError().  The reason for that is that
313  * several programs will check that error and we might as well set
314  * it here.  We also return the value of the read call in
315  * lpcbBytesReturned.
316  */
317 BOOL /* NOTE: This function SHOULD BLOCK */
318 SCSI_LinuxDeviceIo( int fd,
319                 struct sg_header * lpInBuffer, DWORD cbInBuffer,
320                 struct sg_header * lpOutBuffer, DWORD cbOutBuffer,
321                 LPDWORD lpcbBytesReturned )
322 {
323         DWORD dwBytes;
324         DWORD save_error;
325
326         TRACE("Writing to Linux sg device\n");
327         dwBytes = write( fd, lpInBuffer, cbInBuffer );
328         if( dwBytes != cbInBuffer )
329         {
330                 set_last_error();
331                 save_error = GetLastError();
332                 WARN("Not enough bytes written to scsi device. bytes=%ld .. %ld\n", cbInBuffer, dwBytes );
333                 /* FIXME: set_last_error() never sets error to ERROR_NOT_ENOUGH_MEMORY... */
334                 if( save_error == ERROR_NOT_ENOUGH_MEMORY )
335                         MESSAGE("Your Linux kernel was not able to handle the amount of data sent to the scsi device. Try recompiling with a larger SG_BIG_BUFF value (kernel 2.0.x sg.h)");
336                 WARN("error= %ld\n", save_error );
337                 *lpcbBytesReturned = 0;
338                 return FALSE;
339         }
340         
341         TRACE("Reading reply from Linux sg device\n");
342         *lpcbBytesReturned = read( fd, lpOutBuffer, cbOutBuffer );
343         if( *lpcbBytesReturned != cbOutBuffer )
344         {
345                 set_last_error();
346                 save_error = GetLastError();
347                 WARN("Not enough bytes read from scsi device. bytes=%ld .. %ld\n", cbOutBuffer, *lpcbBytesReturned);
348                 WARN("error= %ld\n", save_error );
349                 return FALSE;
350         }
351         return TRUE;
352 }
353
354 /* Internal functions */
355 struct LinuxProcScsiDevice
356 {
357         int host;
358         int channel;
359         int target;
360         int lun;
361         char vendor[9];
362         char model[17];
363         char rev[5];
364         char type[33];
365         int ansirev;
366 };
367
368 /*
369  * we need to declare white spaces explicitly via %*1[ ],
370  * as there are very dainbread CD-ROM devices out there
371  * which have their manufacturer name blanked out via spaces,
372  * which confuses fscanf's parsing (skips all blank spaces)
373  */
374 static int
375 SCSI_getprocentry( FILE * procfile, struct LinuxProcScsiDevice * dev )
376 {
377         int result;
378         result = fscanf( procfile,
379                 "Host:%*1[ ]scsi%d%*1[ ]Channel:%*1[ ]%d%*1[ ]Id:%*1[ ]%d%*1[ ]Lun:%*1[ ]%d\n",
380                 &dev->host,
381                 &dev->channel,
382                 &dev->target,
383                 &dev->lun );
384         if( result == EOF )
385         {
386                 /* "end of entries" return, so no TRACE() here */
387                 return EOF;
388         }
389         if( result != 4 )
390         {
391                 ERR("bus id line scan count error\n");
392                 return 0;
393         }
394         result = fscanf( procfile,
395                 "  Vendor:%*1[ ]%8c%*1[ ]Model:%*1[ ]%16c%*1[ ]Rev:%*1[ ]%4c\n",
396                 dev->vendor,
397                 dev->model,
398                 dev->rev );
399         if( result != 3 )
400         {
401                 ERR("model line scan count error\n");
402                 return 0;
403         }
404
405         result = fscanf( procfile,
406                 "  Type:%*3[ ]%32c%*1[ ]ANSI%*1[ ]SCSI%*1[ ]revision:%*1[ ]%d\n",
407                 dev->type,
408                 &dev->ansirev );
409         if( result != 2 )
410         {
411                 ERR("SCSI type line scan count error\n");
412                 return 0;
413         }
414         /* Since we fscanf with %XXc instead of %s.. put a NULL at end */
415         dev->vendor[8] = 0;
416         dev->model[16] = 0;
417         dev->rev[4] = 0;
418         dev->type[32] = 0;
419
420         return 1;
421 }
422
423 static void
424 SCSI_printprocentry( const struct LinuxProcScsiDevice * dev )
425 {
426         TRACE( "Host: scsi%d Channel: %02d Id: %02d Lun: %02d\n",
427                 dev->host,
428                 dev->channel,
429                 dev->target,
430                 dev->lun );
431         TRACE( "  Vendor: %s Model: %s Rev: %s\n",
432                 dev->vendor,
433                 dev->model,
434                 dev->rev );
435         TRACE( "  Type:   %s ANSI SCSI revision: %02d\n",
436                 dev->type,
437                 dev->ansirev );
438 }
439
440 static BOOL
441 SCSI_PutRegControllerMap( HKEY hkeyControllerMap, int num_controller, int ha, int chan)
442 {
443         DWORD error;
444         char cstr[20];
445         DWORD hc;
446         hc = (ha << 16) + chan;
447         sprintf(cstr, "c%02d", num_controller);
448         if( (error=RegSetValueExA( hkeyControllerMap, cstr, 0, REG_DWORD, (LPBYTE)&hc, sizeof(DWORD))) != ERROR_SUCCESS )
449         {
450                 ERR("Could not create HKEY_DYN_DATA\\%s\\%s\\%s\n", KEYNAME_SCSI, KEYNAME_SCSI_CONTROLLERMAP, cstr );
451         }
452         return error;
453 }
454
455 static void
456 SCSI_MapHCtoController()
457 {
458         HKEY hkeyScsi;
459         HKEY hkeyControllerMap;
460         DWORD disposition;
461
462         char idstr[20];
463         DWORD cbIdStr;
464         int i = 0;
465         DWORD type = 0;
466         DWORD error;
467
468         DWORD num_controller = 0;
469         int last_ha = -1;
470         int last_chan = -1;
471
472         int ha = 0;
473         int chan = 0;
474
475         if( RegCreateKeyExA(HKEY_DYN_DATA, KEYNAME_SCSI, 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hkeyScsi, &disposition ) != ERROR_SUCCESS )
476         {
477                 ERR("Could not open HKEY_DYN_DATA\\%s\n",KEYNAME_SCSI);
478                 return;
479         }
480         if( disposition != REG_OPENED_EXISTING_KEY )
481         {
482                 WARN("Created HKEY_DYN_DATA\\%s\n",KEYNAME_SCSI);
483         }
484         if( RegCreateKeyExA(hkeyScsi, KEYNAME_SCSI_CONTROLLERMAP, 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hkeyControllerMap, &disposition ) != ERROR_SUCCESS )
485         {
486                 ERR("Could not create HKEY_DYN_DATA\\%s\\%s\n", KEYNAME_SCSI, KEYNAME_SCSI_CONTROLLERMAP);
487                 RegCloseKey(hkeyScsi);
488                 return;
489         }
490         
491         for( i=0; cbIdStr = sizeof(idstr), (error=RegEnumValueA( hkeyScsi, i, idstr, &cbIdStr, NULL, &type, NULL, NULL )) == ERROR_SUCCESS; i++ )
492         {
493                 if(idstr[0] == '\0') continue; /* skip the default value */
494
495                 if(sscanf(idstr, "h%02dc%02dt%*02dd%*02d", &ha, &chan) != 2) {
496                         ERR("incorrect reg. value %s\n", debugstr_a(idstr));
497                         continue;
498                 }
499
500                 if( last_ha < ha )
501                 {       /* Next HA */
502                         last_ha = ha;
503                         last_chan = chan;
504                         SCSI_PutRegControllerMap( hkeyControllerMap, num_controller, ha, chan);
505                         num_controller++;
506                 }
507                 else if( last_ha > ha )
508                 {
509                         FIXME("Expected registry to be sorted\n");
510                 }
511                 /* last_ha == ha */
512                 else if( last_chan < chan )
513                 {
514                         last_chan = chan;
515                         SCSI_PutRegControllerMap( hkeyControllerMap, num_controller, ha, chan);
516                         num_controller++;
517                 }
518                 else if( last_chan > chan )
519                 {
520                         FIXME("Expected registry to be sorted\n");
521                 }
522                 /* else last_ha == ha && last_chan == chan so do nothing */
523         }
524         /* Set (default) value to number of controllers */
525         if( RegSetValueExA(hkeyControllerMap, NULL, 0, REG_DWORD, (LPBYTE)&num_controller, sizeof(DWORD) ) != ERROR_SUCCESS )
526         {
527                 ERR("Could not set value HKEY_DYN_DATA\\%s\\%s\n",KEYNAME_SCSI, KEYNAME_SCSI_CONTROLLERMAP);
528         }
529         RegCloseKey(hkeyControllerMap);
530         RegCloseKey(hkeyScsi);
531         return;
532 }
533 #endif
534
535 int SCSI_Linux_CheckDevices(void)
536 {
537     DIR *devdir;
538     struct dirent *dent = NULL;
539
540     devdir = opendir("/dev");
541     for (dent=readdir(devdir);dent;dent=readdir(devdir))
542     {
543         if (!(strncmp(dent->d_name, "sg", 2)))
544             break;
545     }
546     closedir(devdir);
547
548     if (dent == NULL)
549     {
550         MESSAGE("WARNING: You don't have any /dev/sgX generic scsi devices ! \"man MAKEDEV\" !\n");
551         return 0;
552     }
553     return 1;
554 }
555
556 static void
557 SCSI_GetProcinfo()
558 /* I'll admit, this function is somewhat of a mess... it was originally
559  * designed to make some sort of linked list then I realized that
560  * HKEY_DYN_DATA would be a lot less messy
561  */
562 {
563 #ifdef linux
564         static const char procname[] = "/proc/scsi/scsi";
565         FILE * procfile = NULL;
566
567         char read_line[40], read1[10] = "\0", read2[10] = "\0";
568         int result = 0;
569
570         struct LinuxProcScsiDevice dev;
571
572         char idstr[20];
573         char devstr[20];
574
575         int devnum=0;
576         int num_ha = 0;
577
578         HKEY hkeyScsi;
579         DWORD disposition;
580
581         /* Check whether user has generic scsi devices at all */
582         if (!(SCSI_Linux_CheckDevices()))
583             return;
584         
585         procfile = fopen( procname, "r" );
586         if( !procfile )
587         {
588                 ERR("Could not open %s\n", procname);
589                 return;
590         }
591
592         fgets(read_line, 40, procfile);
593         sscanf( read_line, "Attached %9s %9s", read1, read2);
594
595         if(strcmp(read1, "devices:"))
596         {
597                 ERR("Incorrect %s format\n", procname);
598                 return;
599         }
600
601         if(!(strcmp(read2, "none")))
602         {
603                 ERR("No devices found in %s. Make sure you loaded your SCSI driver or set up ide-scsi emulation for your IDE device if this app needs it !\n", procname);
604                 return;
605         }
606
607         if( RegCreateKeyExA(HKEY_DYN_DATA, KEYNAME_SCSI, 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hkeyScsi, &disposition ) != ERROR_SUCCESS )
608         {
609                 ERR("Could not create HKEY_DYN_DATA\\%s\n",KEYNAME_SCSI);
610                 return;
611         }
612
613         /* Read info for one device */
614         while( (result = SCSI_getprocentry(procfile, &dev)) > 0 )
615         {
616                 /* Add to registry */
617
618                 sprintf(idstr, "h%02dc%02dt%02dd%02d", dev.host, dev.channel, dev.target, dev.lun);
619                 sprintf(devstr, "/dev/sg%c", 'a'+devnum);
620                 if( RegSetValueExA(hkeyScsi, idstr, 0, REG_SZ, devstr, strlen(devstr)+1 ) != ERROR_SUCCESS )
621                 {
622                         ERR("Could not set value HKEY_DYN_DATA\\%s\\%s\n",KEYNAME_SCSI, idstr);
623                 }
624
625                 /* Debug output */
626                 SCSI_printprocentry( &dev );
627
628                 /* FIXME: We *REALLY* need number of controllers.. not ha */
629                 /* num of hostadapters is highest ha + 1 */
630                 if( dev.host >= num_ha )
631                         num_ha = dev.host+1;
632                 devnum++;
633         } /* while(1) */
634         if( result != EOF )
635         {
636                 ERR("Sorry, incorrect %s format\n", procname);
637         }
638         fclose( procfile );
639         if( RegSetValueExA(hkeyScsi, NULL, 0, REG_DWORD, (LPBYTE)&num_ha, sizeof(num_ha) ) != ERROR_SUCCESS )
640         {
641                 ERR("Could not set value HKEY_DYN_DATA\\%s\n",KEYNAME_SCSI);
642         }
643         RegCloseKey(hkeyScsi);
644         return;
645 #endif
646 }