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
40 of the scsi_$ calls that are needed for SANE. This is necessary because
41 Domain/OS will not allow a child process to access a parent's SCSI
42 device. The interface is through a common, mapped area. Mutex locks
43 are used to prevent concurrency problems, and eventcounts are used to
44 notify a waiting process that its request has completed.
45
46 This program is intended to support only one device at a time,
47 although multiple instances of this program may run concurrently. It is
48 intended that this program be forked/execd by a SANE application, and
49 that it will exit when the application exits.
50
51 Upon startup, the program is invoked with the path to an object that
52 needs to be mapped for communication. The parent process will have
53 already initialized the 'public' eventcounts and locks, and will be
54 waiting for the ResultReady eventcount to be incremented. After
55 initialization, the server will increment this eventcount, and wait for
56 an incoming request, which is signified by the CommandAvailable
57 eventcount. This EC will be incremented after another process has
58 filled in the parameter area.
59
60 DBG 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.
93 We are using 100 seconds here. */
94 #define DomainScsiTimeout 100000
95
96 /* Communication Area pointer */
97 struct DomainServerCommon *com;
98
99 /* Handle for fault handler */
100 pfm_$fh_handle_t FaultHandle;
101
102
103 static 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
114 don't know how to handle, or never expect to happen. */
DomainErrorCheck(status_$t status,const char * message)115 static 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
132 handles asynchronous faults. It always returns to the faulting code, but
133 it disables the handler, so that the server can be killed if the parent is
134 unable to do so. */
FaultHandler(pfm_$fault_rec_t * FaultStatusPtr)135 pfm_$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
DomainSCSIOpen(void)153 static 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
DomainSCSIClose(void)226 static 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
243 guarantee that it will generate a fault, and I can add support for it. */
DomainSCSIFlushAll(void)244 static 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
255 server architecture requires that the Wait immediately follow the Enter
256 command. */
DomainSCSIWait(void)257 static 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
DomainSCSIEnter(void)394 static 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. */
DomainSCSIReqWait(void)432 static void DomainSCSIReqWait(void)
433 {
434 DBG(1, "sanei_scsi_req_wait: (id=%p)\n", NULL);
435 return;
436 }
437
438
439 /* Startup the server */
sanei_DomainOS_init(char * path)440 static 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