1/* sane - Scanner Access Now Easy. 2 Copyright (C) 1996, 1997 David Mosberger-Tang 3 This file is part of the SANE package. 4 5 This program is free software; you can redistribute it and/or 6 modify it under the terms of the GNU General Public License as 7 published by the Free Software Foundation; either version 2 of the 8 License, or (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 As a special exception, the authors of SANE give permission for 19 additional uses of the libraries contained in this release of SANE. 20 21 The exception is that, if you link a SANE library with other files 22 to produce an executable, this does not by itself cause the 23 resulting executable to be covered by the GNU General Public 24 License. Your use of that executable is in no way restricted on 25 account of linking the SANE library code into it. 26 27 This exception does not, however, invalidate any other reasons why 28 the executable file might be covered by the GNU General Public 29 License. 30 31 If you submit changes to SANE to the maintainers to be included in 32 a subsequent release, you agree by submitting the changes that 33 those changes may be distributed with this exception intact. 34 35 If you write modifications of your own for SANE, it is your choice 36 whether to permit this exception to apply to your modifications. 37 If you do not wish that, delete this exception notice. 38 39 This file defines a server for Apollo Domain/OS systems. It does all 40of the scsi_$ calls that are needed for SANE. This is necessary because 41Domain/OS will not allow a child process to access a parent's SCSI 42device. The interface is through a common, mapped area. Mutex locks 43are used to prevent concurrency problems, and eventcounts are used to 44notify a waiting process that its request has completed. 45 46 This program is intended to support only one device at a time, 47although multiple instances of this program may run concurrently. It is 48intended that this program be forked/execd by a SANE application, and 49that it will exit when the application exits. 50 51 Upon startup, the program is invoked with the path to an object that 52needs to be mapped for communication. The parent process will have 53already initialized the 'public' eventcounts and locks, and will be 54waiting for the ResultReady eventcount to be incremented. After 55initialization, the server will increment this eventcount, and wait for 56an incoming request, which is signified by the CommandAvailable 57eventcount. This EC will be incremented after another process has 58filled in the parameter area. 59 60DBG levels: 61 0 Error - always printed. 62 1 Basic monitor - print entry to main functions, or errors that are 63 normally suppressed because they are reported at a higher level. 64 2 Medium monitor - show intermediate steps in functions 65 3 Detailed monitor - if its there, print it 66 67*/ 68 69#include <assert.h> 70#include <ctype.h> 71#include <stdio.h> 72#include <stdlib.h> 73#include <string.h> 74 75#include <apollo/base.h> 76#include <apollo/ec2.h> 77#include <apollo/error.h> 78#include <apollo/fault.h> 79#include <apollo/ms.h> 80#include <apollo/mutex.h> 81#include <apollo/pfm.h> 82#include <apollo/scsi.h> 83 84#include "../include/sane/config.h" 85 86#include "../include/sane/sanei_scsi.h" 87 88#include "../include/sane/sanei_debug.h" 89 90#include "sanei_DomainOS.h" 91 92/* Timeout period for SCSI wait, in milliseconds. 93We are using 100 seconds here. */ 94#define DomainScsiTimeout 100000 95 96/* Communication Area pointer */ 97struct DomainServerCommon *com; 98 99/* Handle for fault handler */ 100pfm_$fh_handle_t FaultHandle; 101 102 103static struct 104 { 105 void *DomainSCSIPtr; /* Pointer to the data block for this device */ 106 void *DomainSensePtr; /* Pointer to the sense area for this device */ 107 u_int in_use : 1; /* is this DomainFdInfo in use? */ 108 u_int fake_fd : 1; /* is this a fake file descriptor? */ 109 scsi_$handle_t scsi_handle; /* SCSI handle */ 110 scsi_$operation_id_t op_id; /* op_id of current request */ 111 } *DomainFdInfo; 112 113/* This function is called error might have occurred, but it would be one that I 114don't know how to handle, or never expect to happen. */ 115static void DomainErrorCheck(status_$t status, const char *message) 116 { 117 char *subsystem, *module, *code; 118 short subsystem_length, module_length, code_length; 119 120 if (status.all) 121 { 122 DBG(0, "Unrecoverable Domain/OS Error 0x%08x: %s\n", status.all, message); 123 error_$find_text(status, &subsystem, &subsystem_length, &module, &module_length, &code, &code_length); 124 if (subsystem_length && module_length && code_length) 125 DBG(0, "%.*s (%.*s/%.*s)\n", code_length, code, subsystem_length, subsystem, module_length, module); 126 exit(EXIT_FAILURE); 127 } 128 } 129 130 131/* This function is the fault handler for the server. Currently, it only 132handles asynchronous faults. It always returns to the faulting code, but 133it disables the handler, so that the server can be killed if the parent is 134unable to do so. */ 135pfm_$fh_func_val_t FaultHandler(pfm_$fault_rec_t *FaultStatusPtr) 136 { 137 status_$t status; 138 139 DBG(1, "In fault handler, status is %08x\n", FaultStatusPtr->status.all); 140 switch (FaultStatusPtr->status.all) 141 { 142 case fault_$quit: 143 pfm_$release_fault_handler(FaultHandle, &status); 144 DomainErrorCheck(status, "Can't release fault handler"); 145 return pfm_$return_to_faulting_code; 146 default: 147 DBG(0, "Unrecognized fault type %08x, exiting\n", FaultStatusPtr->status.all); 148 exit(EXIT_FAILURE); 149 } 150 } 151 152 153static void DomainSCSIOpen(void) 154 { 155 static int num_alloced = 0; 156 int fd; 157 scsi_$handle_t scsi_handle; 158 pinteger len; 159 void *DataBasePtr; 160 161 /* Find fake fd. */ 162 for (fd = 0; fd < num_alloced; ++fd) 163 if (!DomainFdInfo[fd].in_use) 164 break; 165 166 /* Acquire the device */ 167 DBG(1, "DomainSCSIOpen: dev='%s', fd=%d\n", com->open_path, fd); 168 len = strlen(com->open_path); 169 scsi_$acquire((char *)com->open_path, len, &scsi_handle, &com->CommandStatus); 170 if (com->CommandStatus.all != status_$ok) 171 { 172 /* we have a failure, return an error code, and generate debug output */ 173 DBG(1, "DomainSCSIOpen: acquire failed, Domain/OS status is %08x\n", com->CommandStatus.all); 174 error_$print(com->CommandStatus); 175 return; 176 } 177 else 178 { 179 /* device acquired, setup buffers and buffer pointers */ 180 DBG(2, "DomainSCSIOpen: acquire OK, handle is %x\n", scsi_handle); 181 /* Create/map the data area */ 182 tmpnam(com->open_path); 183 DBG(2, "DomainSCSIOpen: Data block name will be '%s'\n", com->open_path); 184 DataBasePtr = ms_$crmapl(com->open_path, strlen(com->open_path), 0, DomainMaxDataSize + DomainSenseSize, ms_$cowriters, &com->CommandStatus); 185 DomainErrorCheck(com->CommandStatus, "Creating Data Area"); 186 assert((((int)DataBasePtr) & 0x3ff) == 0); /* Relies on Domain/OS mapping new objects on page boundary */ 187 DBG(2, "Data Buffer block created at %p, length = 0x%lx\n", DataBasePtr, DomainMaxDataSize + DomainSenseSize); 188 /* Wire the buffer */ 189 scsi_$wire(scsi_handle, (void *)DataBasePtr, DomainMaxDataSize + DomainSenseSize, &com->CommandStatus); 190 if (com->CommandStatus.all == status_$ok) 191 { 192 /* success, indicate status */ 193 DBG(2, "Buffer wire was successful\n"); 194 } 195 else 196 { 197 /* failure, print detail and return code */ 198 DBG(1, "Buffer wire failed, Domain/OS status is %08x\n", com->CommandStatus.all); 199 error_$print(com->CommandStatus); 200 return; 201 } 202 } 203 204 if (fd >= num_alloced) 205 { 206 size_t new_size, old_size; 207 208 old_size = num_alloced * sizeof (DomainFdInfo[0]); 209 num_alloced = fd + 8; 210 new_size = num_alloced * sizeof (DomainFdInfo[0]); 211 if (DomainFdInfo) 212 DomainFdInfo = realloc (DomainFdInfo, new_size); 213 else 214 DomainFdInfo = malloc (new_size); 215 memset ((char *) DomainFdInfo + old_size, 0, new_size - old_size); 216 assert(DomainFdInfo); 217 } 218 DomainFdInfo[fd].in_use = 1; 219 DomainFdInfo[fd].scsi_handle = scsi_handle; 220 DomainFdInfo[fd].DomainSCSIPtr = DataBasePtr; 221 DomainFdInfo[fd].DomainSensePtr = ((char *)DataBasePtr) + DomainMaxDataSize; 222 com->fd = fd; 223 } 224 225 226static void DomainSCSIClose(void) 227 { 228 DomainFdInfo[com->fd].in_use = 0; 229 DBG(1, "sanei_scsi_close: fd=%d\n", com->fd); 230 /* Unwire the buffer */ 231 scsi_$unwire(DomainFdInfo[com->fd].scsi_handle, DomainFdInfo[com->fd].DomainSCSIPtr, DomainMaxDataSize + DomainSenseSize, true, &com->CommandStatus); 232 DomainErrorCheck(com->CommandStatus, "Unwiring SCSI buffers"); 233 /* Release the device */ 234 scsi_$release(DomainFdInfo[com->fd].scsi_handle, &com->CommandStatus); 235 DomainErrorCheck(com->CommandStatus, "Releasing device"); 236 /* Unmap the buffer area */ 237 ms_$unmap(DomainFdInfo[com->fd].DomainSCSIPtr, DomainMaxDataSize + DomainSenseSize, &com->CommandStatus); 238 DomainErrorCheck(com->CommandStatus, "Unmapping device data area"); 239 } 240 241 242/* I have never seen this called, and I'm not sure what to do with it, so I 243guarantee that it will generate a fault, and I can add support for it. */ 244static void DomainSCSIFlushAll(void) 245 { 246 status_$t status; 247 248 DBG(1, "DomainSCSIFlushAll: ()\n"); 249 DBG(0, "Error - unimplemented feature in module" "BACKEND_NAME"); 250 assert(1==0); 251 } 252 253 254/* This function must only be called from DomainSCSIEnter. The current 255server architecture requires that the Wait immediately follow the Enter 256command. */ 257static void DomainSCSIWait(void) 258 { 259 int count; 260 char *ascii_wait_status, *ascii_op_status; 261 pinteger return_count; 262 scsi_$op_status_t status_list[1]; 263 scsi_$wait_index_t wait_index; 264 265 /* wait for the command completion */ 266 wait_index = scsi_$wait(DomainFdInfo[com->fd].scsi_handle, DomainScsiTimeout, true, DomainFdInfo[com->fd].op_id, 1, status_list, &return_count, &com->CommandStatus); 267 DBG(2, " scsi_$wait returned status = %08x\n", com->CommandStatus.all); 268 if (com->CommandStatus.all == status_$ok) 269 { 270 switch (wait_index) 271 { 272 case scsi_device_advance: ascii_wait_status = "scsi_device_advance"; break; 273 case scsi_timeout: ascii_wait_status = "scsi_timeout"; break; 274 case scsi_async_fault: ascii_wait_status = "scsi_async_fault"; break; 275 default: ascii_wait_status = "unknown"; break; 276 } 277 DBG(2, " scsi_$wait status is %s, return_count is %d\n", ascii_wait_status, return_count); 278 if (wait_index != scsi_device_advance) 279 { 280 DBG(1, "Error - SCSI timeout, or async fault\n"); 281 com->CommandStatus.all = scsi_$operation_timeout; 282 } 283 else for (count = 0; count < return_count; count++) 284 { 285 switch (status_list[count].op_status) 286 { 287 case scsi_good_status: ascii_op_status = "scsi_good_status"; break; 288 case scsi_check_condition: ascii_op_status = "scsi_check_condition"; break; 289 case scsi_condition_met: ascii_op_status = "scsi_condition_met"; break; 290 case scsi_rsv1: ascii_op_status = "scsi_rsv1"; break; 291 case scsi_busy: ascii_op_status = "scsi_busy"; break; 292 case scsi_rsv2: ascii_op_status = "scsi_rsv2"; break; 293 case scsi_rsv3: ascii_op_status = "scsi_rsv3"; break; 294 case scsi_rsv4: ascii_op_status = "scsi_rsv4"; break; 295 case scsi_intermediate_good: ascii_op_status = "scsi_intermediate_good"; break; 296 case scsi_rsv5: ascii_op_status = "scsi_rsv5"; break; 297 case scsi_intermediate_condition_met: ascii_op_status = "scsi_intermediate_condition_met"; break; 298 case scsi_rsv6: ascii_op_status = "scsi_rsv6"; break; 299 case scsi_reservation_conflict: ascii_op_status = "scsi_reservation_conflict"; break; 300 case scsi_rsv7: ascii_op_status = "scsi_rsv7"; break; 301 case scsi_rsv8: ascii_op_status = "scsi_rsv8"; break; 302 case scsi_rsv9: ascii_op_status = "scsi_rsv9"; break; 303 case scsi_undefined_status: ascii_op_status = "scsi_undefined_status"; break; 304 default: ascii_op_status = "unknown"; break; 305 } 306 DBG(2, " list[%d]: op=%x cmd_status=%08x, status=%s\n", count, status_list[count].op, status_list[count].cmd_status.all, ascii_op_status); 307 switch (status_list[count].cmd_status.all) 308 { 309 case status_$ok: 310 switch (status_list[count].op_status) 311 { 312 case scsi_good_status: 313 break; 314 case scsi_busy: 315 com->CommandStatus.all = status_$ok | 0x80000000; 316 com->SCSIStatus = scsi_busy; 317 break; 318 case scsi_check_condition: 319 { 320 static unsigned char scanner_sense_cdb[] = {3, 0, 0, 0, DomainSenseSize, 0}; 321 static scsi_$cdb_t sense_cdb; 322 static linteger sense_cdb_size; 323 static scsi_$operation_id_t sense_op_id; 324 static status_$t sense_status; 325 static pinteger sense_return_count; 326 static int temp; 327 328 /* Issue the sense command (wire, issue, wait, unwire */ 329 sense_cdb_size = sizeof(scanner_sense_cdb); 330 memcpy(&sense_cdb, scanner_sense_cdb, sense_cdb_size); 331 scsi_$do_command_2(DomainFdInfo[com->fd].scsi_handle, sense_cdb, sense_cdb_size, DomainFdInfo[com->fd].DomainSensePtr, DomainSenseSize, scsi_read, &sense_op_id, &sense_status); 332 DomainErrorCheck(sense_status, "Executing sense command"); 333 scsi_$wait(DomainFdInfo[com->fd].scsi_handle, DomainScsiTimeout, true, sense_op_id, 1, status_list, &sense_return_count, &sense_status); 334 /* The following debug output is scanner specific */ 335 DBG(2, "Sense information: Error code=%02x, ASC=%02x, ASCQ=%02x\n", ((u_char *)DomainFdInfo[com->fd].DomainSensePtr)[0], ((char *)DomainFdInfo[com->fd].DomainSensePtr)[0xc], ((char *)DomainFdInfo[com->fd].DomainSensePtr)[0xd]); 336 DBG(2, " Sense dump:\n"); 337 for (temp = 0; temp < DomainSenseSize; temp++) 338 DBG(2, " %02x", ((u_char *)DomainFdInfo[com->fd].DomainSensePtr)[temp]); 339 DBG(2, "\n"); 340 /* see if buffer underrun - ILI/Valid are set, and command was a read */ 341 /* Warning - this might be UMAX specific */ 342 if ((((char *)DomainFdInfo[com->fd].DomainSensePtr)[0] == 0xf0) && (((char *)DomainFdInfo[com->fd].DomainSensePtr)[2] & 0x20) && (com->cdb.g0.cmd == 0x28)) 343 { 344 /* Warning - the following code is specific to endianness and int size */ 345 /* Its also very ugly */ 346 DBG(2, "Shortening destination length by %x bytes\n", *(int *)(((char *)DomainFdInfo[com->fd].DomainSensePtr)+3)); 347 com->dst_size -= *(int *)(((char *)DomainFdInfo[com->fd].DomainSensePtr)+3); 348 DBG(2, "Final dest size is %x\n", com->dst_size); 349 } 350 else 351 { 352 /* Set this status so that the sense handler can be called */ 353 com->CommandStatus.all = status_$ok | 0x80000000; 354 com->SCSIStatus = scsi_check_condition; 355 } 356 } 357 break; 358 default: 359 /* I fault out in this case because I want to know about this error, 360 and this guarantees that it will get attention. */ 361 DBG(0, "Unrecoverable Domain/OS scsi handler error: status=%08x\n", status_list[count].op_status); 362 exit(EXIT_FAILURE); 363 } 364 break; 365 /* Handle recognized error conditions by copying the error code over */ 366 case scsi_$operation_timeout: 367 case scsi_$dma_underrun: /* received by some backend code */ 368 case scsi_$hdwr_failure: /* received when both scanners were active */ 369 com->CommandStatus = status_list[count].cmd_status; 370 break; 371 default: 372 DBG(0, "Unrecoverable DomainOS scsi handler error: status=%08x\n", status_list[count].cmd_status.all); 373 error_$print(status_list[count].cmd_status); 374 exit(EXIT_FAILURE); 375 } 376 } 377 /* Dump the buffer contents */ 378 if (com->direction == scsi_read) 379 { 380 DBG(3, "first words of buffer are:\n"); 381 for (return_count = 0; return_count < com->dst_size; return_count++) 382 DBG(3, "%02X%c", ((unsigned char *)DomainFdInfo[com->fd].DomainSCSIPtr)[return_count], (return_count % 16) == 15 ? '\n' : ' '); 383 DBG(3, "\n"); 384 } 385 } 386 else 387 { 388 /* scsi_$wait failed */ 389 DBG(1, "scsi_$wait failed, status is %08x\n", com->CommandStatus.all); 390 } 391 } 392 393 394static void DomainSCSIEnter(void) 395 { 396 static int count; 397 398 /* Give some debug info */ 399 DBG(1, "Entering DomainSCSIEnter, fd=%d, opcode=%02X\n", com->fd, com->cdb.all[0]); 400 DBG(2, " CDB Contents: "); 401 for (count = 0; count < com->cdb_size; count++) 402 DBG(2, " %02X", com->cdb.all[count]); 403 DBG(2, "\n"); 404 DBG(2, "Buffer address is 0x%08x\n", DomainFdInfo[com->fd].DomainSCSIPtr); 405 DBG(2, "Buffer size is %x\n", com->buf_size); 406 DBG(2, "Direction is %s\n", (com->direction == scsi_read) ? "READ" : "WRITE"); 407 /* now queue the command */ 408 scsi_$do_command_2(DomainFdInfo[com->fd].scsi_handle, com->cdb, com->cdb_size, DomainFdInfo[com->fd].DomainSCSIPtr, com->buf_size, com->direction, &DomainFdInfo[com->fd].op_id, &com->CommandStatus); 409 if (com->CommandStatus.all == status_$ok) 410 { 411 /* success, indicate status */ 412 DBG(2, " scsi_$do_command_2 was successful, op_id is %x\n", DomainFdInfo[com->fd].op_id); 413 414 /* If we supported multiple outstanding requests for one device, this would be 415 a good breakpoint. We would store the op_id in a private place, and construct 416 a queue for each device. This complicates things, and SANE doesn't seem to need 417 it, so it won't be implemented. The current server architecture does the wait 418 automatically, and status for the entire operation is returned. This means that 419 the sanei_scsi_req_enter and sanei_scsi_req_wait calls don't make sense, and 420 should generate an error. */ 421 DomainSCSIWait(); 422 } 423 else 424 { 425 /* failure, print detail and return code */ 426 DBG(1, " scsi_$do_command_2 failed, status is %08x\n", com->CommandStatus.all); 427 } 428 } 429 430 431/* This function is not currently used. */ 432static void DomainSCSIReqWait(void) 433 { 434 DBG(1, "sanei_scsi_req_wait: (id=%p)\n", NULL); 435 return; 436 } 437 438 439/* Startup the server */ 440static void sanei_DomainOS_init(char *path) 441 { 442 int done, index; 443 long CommandTriggerValue; 444 ec2_$ptr_t CommandAvailablePtr[1]; 445 status_$t status; 446 unsigned long length_mapped; 447 448 DBG(1, "Starting Domain SANE Server, common area path = '%s'\n", path); 449 com = ms_$mapl(path, strlen(path), 0, sizeof(struct DomainServerCommon), ms_$cowriters, ms_$wr, true, &length_mapped, &status); 450 DomainErrorCheck(status, "Can't open common area"); 451 if (length_mapped < sizeof(struct DomainServerCommon)) 452 { 453 DBG(0, "Error - can't open common area '%s' to required length\n", path); 454 DBG(0, " Required length = %lx, returned length = %lx\n", sizeof(struct DomainServerCommon), length_mapped); 455 exit(EXIT_FAILURE); 456 } 457 /* Make the file temporary, so it will disappear when it is closed */ 458 ms_$mk_temporary(com, &status); 459 DomainErrorCheck(status, "Can't make common file temporary"); 460 DBG(2, "Domain Server common area mapped, length is %lx\n", length_mapped); 461 /* The communication area is open, give the initial response */ 462 ec2_$advance(&com->ResultReady, &status); 463 DomainErrorCheck(status, "Can't advance ResultReady EC after startup"); 464 /* Enter the command loop */ 465 CommandAvailablePtr[0] = &com->CommandAvailable; 466 CommandTriggerValue = ec2_$read(com->CommandAvailable) + 1; 467 /* Inhibit asynchronous faults */ 468/* pfm_$inhibit();*/ 469 /* Establish the fault handler */ 470 FaultHandle = pfm_$establish_fault_handler(pfm_$all_faults, 0, FaultHandler, &status); 471 DomainErrorCheck(status, "Can't establish fault handler"); 472 done = 0; 473 do 474 { 475 /* Wait for the command */ 476 DBG(2, "Waiting for incoming command\n"); 477 do 478 { 479 index = ec2_$wait_svc(CommandAvailablePtr, &CommandTriggerValue, 1, &status); 480 } 481 while (status.all == ec2_$wait_quit); 482 DomainErrorCheck(status, "Error waiting on CommandAvailable EC"); 483 assert (index == 1); 484 /* Get the trigger value for next time - this avoids a race/deadlock */ 485 CommandTriggerValue = ec2_$read(com->CommandAvailable) + 1; 486 /* decode/execute the command */ 487 DBG(2, "Received a command - opcode is %x\n", com->opcode); 488 switch(com->opcode) 489 { 490 case Open: 491 DomainSCSIOpen(); 492 ec2_$advance(&com->CommandAccepted, &status); 493 DomainErrorCheck(status, "Can't advance CommandAccepted EC on open"); 494 break; 495 case Close: 496 DomainSCSIClose(); 497 ec2_$advance(&com->CommandAccepted, &status); 498 DomainErrorCheck(status, "Can't advance CommandAccepted EC on close"); 499 break; 500 case Enter: 501 DomainSCSIEnter(); 502 ec2_$advance(&com->CommandAccepted, &status); 503 DomainErrorCheck(status, "Can't advance CommandAccepted EC on enter"); 504 break; 505 case Exit: 506 done = 1; 507 /* This lets the parent know that the command was accepted. It can be 508 used to avoid sending a signal. */ 509 ec2_$advance(&com->CommandAccepted, &status); 510 DomainErrorCheck(status, "Can't advance CommandAccepted EC on exit"); 511 break; 512 default: 513 DBG(1, "Invalid command %x received\n", com->opcode); 514 } 515 DBG(2, "Command processing complete\n"); 516 } 517 while (!done); 518 /* This would be a good place to close all devices, but for now we'll assume 519 they have already been closed by a well-behaved program */ 520 /* Unmap the common area */ 521 ms_$unmap(com, sizeof(struct DomainServerCommon), &status); 522 DomainErrorCheck(status, "Error unmapping common area"); 523 DBG(1, "Exiting Domain SANE Server\n"); 524/* pfm_$enable();*/ 525 exit(EXIT_SUCCESS); 526 } 527