summaryrefslogtreecommitdiff
path: root/sanei/sanei_DomainOS.c
blob: 5473151150ebdb600ee5393a7bae12da8cb242a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
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);
   }