summaryrefslogtreecommitdiff
path: root/sanei/sanei_DomainOS.c
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2014-10-06 14:00:40 +0200
committerJörg Frings-Fürst <debian@jff-webhosting.net>2014-10-06 14:00:40 +0200
commit6e9c41a892ed0e0da326e0278b3221ce3f5713b8 (patch)
tree2e301d871bbeeb44aa57ff9cc070fcf3be484487 /sanei/sanei_DomainOS.c
Initial import of sane-backends version 1.0.24-1.2
Diffstat (limited to 'sanei/sanei_DomainOS.c')
-rwxr-xr-xsanei/sanei_DomainOS.c528
1 files changed, 528 insertions, 0 deletions
diff --git a/sanei/sanei_DomainOS.c b/sanei/sanei_DomainOS.c
new file mode 100755
index 0000000..5473151
--- /dev/null
+++ b/sanei/sanei_DomainOS.c
@@ -0,0 +1,528 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1996, 1997 David Mosberger-Tang
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+
+ This file defines a server for Apollo Domain/OS systems. It does all
+of the scsi_$ calls that are needed for SANE. This is necessary because
+Domain/OS will not allow a child process to access a parent's SCSI
+device. The interface is through a common, mapped area. Mutex locks
+are used to prevent concurrency problems, and eventcounts are used to
+notify a waiting process that its request has completed.
+
+ This program is intended to support only one device at a time,
+although multiple instances of this program may run concurrently. It is
+intended that this program be forked/execd by a SANE application, and
+that it will exit when the application exits.
+
+ Upon startup, the program is invoked with the path to an object that
+needs to be mapped for communication. The parent process will have
+already initialized the 'public' eventcounts and locks, and will be
+waiting for the ResultReady eventcount to be incremented. After
+initialization, the server will increment this eventcount, and wait for
+an incoming request, which is signified by the CommandAvailable
+eventcount. This EC will be incremented after another process has
+filled in the parameter area.
+
+DBG levels:
+ 0 Error - always printed.
+ 1 Basic monitor - print entry to main functions, or errors that are
+ normally suppressed because they are reported at a higher level.
+ 2 Medium monitor - show intermediate steps in functions
+ 3 Detailed monitor - if its there, print it
+
+*/
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <apollo/base.h>
+#include <apollo/ec2.h>
+#include <apollo/error.h>
+#include <apollo/fault.h>
+#include <apollo/ms.h>
+#include <apollo/mutex.h>
+#include <apollo/pfm.h>
+#include <apollo/scsi.h>
+
+#include "../include/sane/config.h"
+
+#include "../include/sane/sanei_scsi.h"
+
+#include "../include/sane/sanei_debug.h"
+
+#include "sanei_DomainOS.h"
+
+/* Timeout period for SCSI wait, in milliseconds.
+We are using 100 seconds here. */
+#define DomainScsiTimeout 100000
+
+/* Communication Area pointer */
+struct DomainServerCommon *com;
+
+/* Handle for fault handler */
+pfm_$fh_handle_t FaultHandle;
+
+
+static struct
+ {
+ void *DomainSCSIPtr; /* Pointer to the data block for this device */
+ void *DomainSensePtr; /* Pointer to the sense area for this device */
+ u_int in_use : 1; /* is this DomainFdInfo in use? */
+ u_int fake_fd : 1; /* is this a fake file descriptor? */
+ scsi_$handle_t scsi_handle; /* SCSI handle */
+ scsi_$operation_id_t op_id; /* op_id of current request */
+ } *DomainFdInfo;
+
+/* This function is called error might have occured, but it would be one that I
+don't know how to handle, or never expect to happen. */
+static void DomainErrorCheck(status_$t status, const char *message)
+ {
+ char *subsystem, *module, *code;
+ short subsystem_length, module_length, code_length;
+
+ if (status.all)
+ {
+ DBG(0, "Unrecoverable Domain/OS Error 0x%08x: %s\n", status.all, message);
+ error_$find_text(status, &subsystem, &subsystem_length, &module, &module_length, &code, &code_length);
+ if (subsystem_length && module_length && code_length)
+ DBG(0, "%.*s (%.*s/%.*s)\n", code_length, code, subsystem_length, subsystem, module_length, module);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+
+/* This function is the fault handler for the server. Currently, it only
+handles asynchronous faults. It always returns to the faulting code, but
+it disables the handler, so that the server can be killed if the parent is
+unable to do so. */
+pfm_$fh_func_val_t FaultHandler(pfm_$fault_rec_t *FaultStatusPtr)
+ {
+ status_$t status;
+
+ DBG(1, "In fault handler, status is %08x\n", FaultStatusPtr->status.all);
+ switch (FaultStatusPtr->status.all)
+ {
+ case fault_$quit:
+ pfm_$release_fault_handler(FaultHandle, &status);
+ DomainErrorCheck(status, "Can't release fault handler");
+ return pfm_$return_to_faulting_code;
+ default:
+ DBG(0, "Unrecognized fault type %08x, exiting\n", FaultStatusPtr->status.all);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+
+static void DomainSCSIOpen(void)
+ {
+ static int num_alloced = 0;
+ int fd;
+ scsi_$handle_t scsi_handle;
+ pinteger len;
+ void *DataBasePtr;
+
+ /* Find fake fd. */
+ for (fd = 0; fd < num_alloced; ++fd)
+ if (!DomainFdInfo[fd].in_use)
+ break;
+
+ /* Acquire the device */
+ DBG(1, "DomainSCSIOpen: dev='%s', fd=%d\n", com->open_path, fd);
+ len = strlen(com->open_path);
+ scsi_$acquire((char *)com->open_path, len, &scsi_handle, &com->CommandStatus);
+ if (com->CommandStatus.all != status_$ok)
+ {
+ /* we have a failure, return an error code, and generate debug output */
+ DBG(1, "DomainSCSIOpen: acquire failed, Domain/OS status is %08x\n", com->CommandStatus.all);
+ error_$print(com->CommandStatus);
+ return;
+ }
+ else
+ {
+ /* device acquired, setup buffers and buffer pointers */
+ DBG(2, "DomainSCSIOpen: acquire OK, handle is %x\n", scsi_handle);
+ /* Create/map the data area */
+ tmpnam(com->open_path);
+ DBG(2, "DomainSCSIOpen: Data block name will be '%s'\n", com->open_path);
+ DataBasePtr = ms_$crmapl(com->open_path, strlen(com->open_path), 0, DomainMaxDataSize + DomainSenseSize, ms_$cowriters, &com->CommandStatus);
+ DomainErrorCheck(com->CommandStatus, "Creating Data Area");
+ assert((((int)DataBasePtr) & 0x3ff) == 0); /* Relies on Domain/OS mapping new objects on page boundary */
+ DBG(2, "Data Buffer block created at %p, length = 0x%lx\n", DataBasePtr, DomainMaxDataSize + DomainSenseSize);
+ /* Wire the buffer */
+ scsi_$wire(scsi_handle, (void *)DataBasePtr, DomainMaxDataSize + DomainSenseSize, &com->CommandStatus);
+ if (com->CommandStatus.all == status_$ok)
+ {
+ /* success, indicate status */
+ DBG(2, "Buffer wire was successful\n");
+ }
+ else
+ {
+ /* failure, print detail and return code */
+ DBG(1, "Buffer wire failed, Domain/OS status is %08x\n", com->CommandStatus.all);
+ error_$print(com->CommandStatus);
+ return;
+ }
+ }
+
+ if (fd >= num_alloced)
+ {
+ size_t new_size, old_size;
+
+ old_size = num_alloced * sizeof (DomainFdInfo[0]);
+ num_alloced = fd + 8;
+ new_size = num_alloced * sizeof (DomainFdInfo[0]);
+ if (DomainFdInfo)
+ DomainFdInfo = realloc (DomainFdInfo, new_size);
+ else
+ DomainFdInfo = malloc (new_size);
+ memset ((char *) DomainFdInfo + old_size, 0, new_size - old_size);
+ assert(DomainFdInfo);
+ }
+ DomainFdInfo[fd].in_use = 1;
+ DomainFdInfo[fd].scsi_handle = scsi_handle;
+ DomainFdInfo[fd].DomainSCSIPtr = DataBasePtr;
+ DomainFdInfo[fd].DomainSensePtr = ((char *)DataBasePtr) + DomainMaxDataSize;
+ com->fd = fd;
+ }
+
+
+static void DomainSCSIClose(void)
+ {
+ DomainFdInfo[com->fd].in_use = 0;
+ DBG(1, "sanei_scsi_close: fd=%d\n", com->fd);
+ /* Unwire the buffer */
+ scsi_$unwire(DomainFdInfo[com->fd].scsi_handle, DomainFdInfo[com->fd].DomainSCSIPtr, DomainMaxDataSize + DomainSenseSize, true, &com->CommandStatus);
+ DomainErrorCheck(com->CommandStatus, "Unwiring SCSI buffers");
+ /* Release the device */
+ scsi_$release(DomainFdInfo[com->fd].scsi_handle, &com->CommandStatus);
+ DomainErrorCheck(com->CommandStatus, "Releasing device");
+ /* Unmap the buffer area */
+ ms_$unmap(DomainFdInfo[com->fd].DomainSCSIPtr, DomainMaxDataSize + DomainSenseSize, &com->CommandStatus);
+ DomainErrorCheck(com->CommandStatus, "Unmapping device data area");
+ }
+
+
+/* I have never seen this called, and I'm not sure what to do with it, so I
+guarantee that it will generate a fault, and I can add support for it. */
+static void DomainSCSIFlushAll(void)
+ {
+ status_$t status;
+
+ DBG(1, "DomainSCSIFlushAll: ()\n");
+ DBG(0, "Error - unimplemented feature in module" "BACKEND_NAME");
+ assert(1==0);
+ }
+
+
+/* This function must only be called from DomainSCSIEnter. The current
+server architecture requires that the Wait immediately follow the Enter
+command. */
+static void DomainSCSIWait(void)
+ {
+ int count;
+ char *ascii_wait_status, *ascii_op_status;
+ pinteger return_count;
+ scsi_$op_status_t status_list[1];
+ scsi_$wait_index_t wait_index;
+
+ /* wait for the command completion */
+ wait_index = scsi_$wait(DomainFdInfo[com->fd].scsi_handle, DomainScsiTimeout, true, DomainFdInfo[com->fd].op_id, 1, status_list, &return_count, &com->CommandStatus);
+ DBG(2, " scsi_$wait returned status = %08x\n", com->CommandStatus.all);
+ if (com->CommandStatus.all == status_$ok)
+ {
+ switch (wait_index)
+ {
+ case scsi_device_advance: ascii_wait_status = "scsi_device_advance"; break;
+ case scsi_timeout: ascii_wait_status = "scsi_timeout"; break;
+ case scsi_async_fault: ascii_wait_status = "scsi_async_fault"; break;
+ default: ascii_wait_status = "unknown"; break;
+ }
+ DBG(2, " scsi_$wait status is %s, return_count is %d\n", ascii_wait_status, return_count);
+ if (wait_index != scsi_device_advance)
+ {
+ DBG(1, "Error - SCSI timeout, or async fault\n");
+ com->CommandStatus.all = scsi_$operation_timeout;
+ }
+ else for (count = 0; count < return_count; count++)
+ {
+ switch (status_list[count].op_status)
+ {
+ case scsi_good_status: ascii_op_status = "scsi_good_status"; break;
+ case scsi_check_condition: ascii_op_status = "scsi_check_condition"; break;
+ case scsi_condition_met: ascii_op_status = "scsi_condition_met"; break;
+ case scsi_rsv1: ascii_op_status = "scsi_rsv1"; break;
+ case scsi_busy: ascii_op_status = "scsi_busy"; break;
+ case scsi_rsv2: ascii_op_status = "scsi_rsv2"; break;
+ case scsi_rsv3: ascii_op_status = "scsi_rsv3"; break;
+ case scsi_rsv4: ascii_op_status = "scsi_rsv4"; break;
+ case scsi_intermediate_good: ascii_op_status = "scsi_intermediate_good"; break;
+ case scsi_rsv5: ascii_op_status = "scsi_rsv5"; break;
+ case scsi_intermediate_condition_met: ascii_op_status = "scsi_intermediate_condition_met"; break;
+ case scsi_rsv6: ascii_op_status = "scsi_rsv6"; break;
+ case scsi_reservation_conflict: ascii_op_status = "scsi_reservation_conflict"; break;
+ case scsi_rsv7: ascii_op_status = "scsi_rsv7"; break;
+ case scsi_rsv8: ascii_op_status = "scsi_rsv8"; break;
+ case scsi_rsv9: ascii_op_status = "scsi_rsv9"; break;
+ case scsi_undefined_status: ascii_op_status = "scsi_undefined_status"; break;
+ default: ascii_op_status = "unknown"; break;
+ }
+ 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);
+ switch (status_list[count].cmd_status.all)
+ {
+ case status_$ok:
+ switch (status_list[count].op_status)
+ {
+ case scsi_good_status:
+ break;
+ case scsi_busy:
+ com->CommandStatus.all = status_$ok | 0x80000000;
+ com->SCSIStatus = scsi_busy;
+ break;
+ case scsi_check_condition:
+ {
+ static unsigned char scanner_sense_cdb[] = {3, 0, 0, 0, DomainSenseSize, 0};
+ static scsi_$cdb_t sense_cdb;
+ static linteger sense_cdb_size;
+ static scsi_$operation_id_t sense_op_id;
+ static status_$t sense_status;
+ static pinteger sense_return_count;
+ static int temp;
+
+ /* Issue the sense command (wire, issue, wait, unwire */
+ sense_cdb_size = sizeof(scanner_sense_cdb);
+ memcpy(&sense_cdb, scanner_sense_cdb, sense_cdb_size);
+ 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);
+ DomainErrorCheck(sense_status, "Executing sense command");
+ scsi_$wait(DomainFdInfo[com->fd].scsi_handle, DomainScsiTimeout, true, sense_op_id, 1, status_list, &sense_return_count, &sense_status);
+ /* The following debug output is scanner specific */
+ 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]);
+ DBG(2, " Sense dump:\n");
+ for (temp = 0; temp < DomainSenseSize; temp++)
+ DBG(2, " %02x", ((u_char *)DomainFdInfo[com->fd].DomainSensePtr)[temp]);
+ DBG(2, "\n");
+ /* see if buffer underrun - ILI/Valid are set, and command was a read */
+ /* Warning - this might be UMAX specific */
+ if ((((char *)DomainFdInfo[com->fd].DomainSensePtr)[0] == 0xf0) && (((char *)DomainFdInfo[com->fd].DomainSensePtr)[2] & 0x20) && (com->cdb.g0.cmd == 0x28))
+ {
+ /* Warning - the following code is specific to endianness and int size */
+ /* Its also very ugly */
+ DBG(2, "Shortening destination length by %x bytes\n", *(int *)(((char *)DomainFdInfo[com->fd].DomainSensePtr)+3));
+ com->dst_size -= *(int *)(((char *)DomainFdInfo[com->fd].DomainSensePtr)+3);
+ DBG(2, "Final dest size is %x\n", com->dst_size);
+ }
+ else
+ {
+ /* Set this status so that the sense handler can be called */
+ com->CommandStatus.all = status_$ok | 0x80000000;
+ com->SCSIStatus = scsi_check_condition;
+ }
+ }
+ break;
+ default:
+ /* I fault out in this case because I want to know about this error,
+ and this guarantees that it will get attention. */
+ DBG(0, "Unrecoverable Domain/OS scsi handler error: status=%08x\n", status_list[count].op_status);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ /* Handle recognized error conditions by copying the error code over */
+ case scsi_$operation_timeout:
+ case scsi_$dma_underrun: /* received by some backend code */
+ case scsi_$hdwr_failure: /* received when both scanners were active */
+ com->CommandStatus = status_list[count].cmd_status;
+ break;
+ default:
+ DBG(0, "Unrecoverable DomainOS scsi handler error: status=%08x\n", status_list[count].cmd_status.all);
+ error_$print(status_list[count].cmd_status);
+ exit(EXIT_FAILURE);
+ }
+ }
+ /* Dump the buffer contents */
+ if (com->direction == scsi_read)
+ {
+ DBG(3, "first words of buffer are:\n");
+ for (return_count = 0; return_count < com->dst_size; return_count++)
+ DBG(3, "%02X%c", ((unsigned char *)DomainFdInfo[com->fd].DomainSCSIPtr)[return_count], (return_count % 16) == 15 ? '\n' : ' ');
+ DBG(3, "\n");
+ }
+ }
+ else
+ {
+ /* scsi_$wait failed */
+ DBG(1, "scsi_$wait failed, status is %08x\n", com->CommandStatus.all);
+ }
+ }
+
+
+static void DomainSCSIEnter(void)
+ {
+ static int count;
+
+ /* Give some debug info */
+ DBG(1, "Entering DomainSCSIEnter, fd=%d, opcode=%02X\n", com->fd, com->cdb.all[0]);
+ DBG(2, " CDB Contents: ");
+ for (count = 0; count < com->cdb_size; count++)
+ DBG(2, " %02X", com->cdb.all[count]);
+ DBG(2, "\n");
+ DBG(2, "Buffer address is 0x%08x\n", DomainFdInfo[com->fd].DomainSCSIPtr);
+ DBG(2, "Buffer size is %x\n", com->buf_size);
+ DBG(2, "Direction is %s\n", (com->direction == scsi_read) ? "READ" : "WRITE");
+ /* now queue the command */
+ 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);
+ if (com->CommandStatus.all == status_$ok)
+ {
+ /* success, indicate status */
+ DBG(2, " scsi_$do_command_2 was successful, op_id is %x\n", DomainFdInfo[com->fd].op_id);
+
+ /* If we supported multiple outstanding requests for one device, this would be
+ a good breakpoint. We would store the op_id in a private place, and construct
+ a queue for each device. This complicates things, and SANE doesn't seem to need
+ it, so it won't be implemented. The current server architecture does the wait
+ automatically, and status for the entire operation is returned. This means that
+ the sanei_scsi_req_enter and sanei_scsi_req_wait calls don't make sense, and
+ should generate an error. */
+ DomainSCSIWait();
+ }
+ else
+ {
+ /* failure, print detail and return code */
+ DBG(1, " scsi_$do_command_2 failed, status is %08x\n", com->CommandStatus.all);
+ }
+ }
+
+
+/* This function is not currently used. */
+static void DomainSCSIReqWait(void)
+ {
+ DBG(1, "sanei_scsi_req_wait: (id=%p)\n", NULL);
+ return;
+ }
+
+
+/* Startup the server */
+static void sanei_DomainOS_init(char *path)
+ {
+ int done, index;
+ long CommandTriggerValue;
+ ec2_$ptr_t CommandAvailablePtr[1];
+ status_$t status;
+ unsigned long length_mapped;
+
+ DBG(1, "Starting Domain SANE Server, common area path = '%s'\n", path);
+ com = ms_$mapl(path, strlen(path), 0, sizeof(struct DomainServerCommon), ms_$cowriters, ms_$wr, true, &length_mapped, &status);
+ DomainErrorCheck(status, "Can't open common area");
+ if (length_mapped < sizeof(struct DomainServerCommon))
+ {
+ DBG(0, "Error - can't open common area '%s' to required length\n", path);
+ DBG(0, " Required length = %lx, returned length = %lx\n", sizeof(struct DomainServerCommon), length_mapped);
+ exit(EXIT_FAILURE);
+ }
+ /* Make the file temporary, so it will disappear when it is closed */
+ ms_$mk_temporary(com, &status);
+ DomainErrorCheck(status, "Can't make common file temporary");
+ DBG(2, "Domain Server common area mapped, length is %lx\n", length_mapped);
+ /* The communication area is open, give the initial response */
+ ec2_$advance(&com->ResultReady, &status);
+ DomainErrorCheck(status, "Can't advance ResultReady EC after startup");
+ /* Enter the command loop */
+ CommandAvailablePtr[0] = &com->CommandAvailable;
+ CommandTriggerValue = ec2_$read(com->CommandAvailable) + 1;
+ /* Inhibit asynchronous faults */
+/* pfm_$inhibit();*/
+ /* Establish the fault handler */
+ FaultHandle = pfm_$establish_fault_handler(pfm_$all_faults, 0, FaultHandler, &status);
+ DomainErrorCheck(status, "Can't establish fault handler");
+ done = 0;
+ do
+ {
+ /* Wait for the command */
+ DBG(2, "Waiting for incoming command\n");
+ do
+ {
+ index = ec2_$wait_svc(CommandAvailablePtr, &CommandTriggerValue, 1, &status);
+ }
+ while (status.all == ec2_$wait_quit);
+ DomainErrorCheck(status, "Error waiting on CommandAvailable EC");
+ assert (index == 1);
+ /* Get the trigger value for next time - this avoids a race/deadlock */
+ CommandTriggerValue = ec2_$read(com->CommandAvailable) + 1;
+ /* decode/execute the command */
+ DBG(2, "Received a command - opcode is %x\n", com->opcode);
+ switch(com->opcode)
+ {
+ case Open:
+ DomainSCSIOpen();
+ ec2_$advance(&com->CommandAccepted, &status);
+ DomainErrorCheck(status, "Can't advance CommandAccepted EC on open");
+ break;
+ case Close:
+ DomainSCSIClose();
+ ec2_$advance(&com->CommandAccepted, &status);
+ DomainErrorCheck(status, "Can't advance CommandAccepted EC on close");
+ break;
+ case Enter:
+ DomainSCSIEnter();
+ ec2_$advance(&com->CommandAccepted, &status);
+ DomainErrorCheck(status, "Can't advance CommandAccepted EC on enter");
+ break;
+ case Exit:
+ done = 1;
+ /* This lets the parent know that the command was accepted. It can be
+ used to avoid sending a signal. */
+ ec2_$advance(&com->CommandAccepted, &status);
+ DomainErrorCheck(status, "Can't advance CommandAccepted EC on exit");
+ break;
+ default:
+ DBG(1, "Invalid command %x received\n", com->opcode);
+ }
+ DBG(2, "Command processing complete\n");
+ }
+ while (!done);
+ /* This would be a good place to close all devices, but for now we'll assume
+ they have already been closed by a well-behaved program */
+ /* Unmap the common area */
+ ms_$unmap(com, sizeof(struct DomainServerCommon), &status);
+ DomainErrorCheck(status, "Error unmapping common area");
+ DBG(1, "Exiting Domain SANE Server\n");
+/* pfm_$enable();*/
+ exit(EXIT_SUCCESS);
+ }