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