/* vim: ts=4 noexpandtab : */ /* SANE SCSI interface for AmigaOS Copyright (C) 2000 by Ingo Wilken 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 provides a SANE SCSI interface for AmigaOS. It should work with any OS version (even pre-V36). This implementation supports almost all nice features that are possible with SANE's API: multiple asynchronous commands per device, out-of-order completion for commands, any buffer size for "*_extended" commands. *+*+*+*+*+*+*+* WARNING: COMPLETELY UNTESTED CODE! *+*+*+*+*+*+*+* It compiles with DICE 2.07.56R under UAE but has never been tried on a real machine. My Amiga is non-functional and I wrote it just for fun. Use at your own risk. -------------------------------------------------------------------- Amiga devices are specified by ":::", e.g.: "scsi.device:0:3:2" for ID 3 LUN 2 on controller board 0, using the standard scsi.device. The Exec device can be omitted, in this case it is taken from the environment variable SANE_AMIGA_SCSI_DEVICE, or defaults to "scsi.device". A full SANE device name would be something like st400::0:3:2 ST400 on ID 3, LUN 2, using board 0 of scsi.device epson:foo.device:1:4:0 Epson on ID 4, LUN 0, using board 1 of foo.device The SANE "scsi" configfile option can also be used, in this case the bus number is used as the board number; channel is ignored. The Exec device cannot be specified in this case, it is taken from the environment variable or defaults to "scsi.device". */ #include #include #include #include #include #include #include #include /* probably not needed */ #include #include #include #include #include #include #define BACKEND_NAME sanei_scsi #include #define SCSI_DEVICE "scsi.device" #define NULL ((void *)0) static struct MsgPort *msgport; /* reply port for Exec I/O messages */ /* The Amiga scsi.device can transfer more than 16MB per command, but * this probably exceeds available memory :-) We limit this to 128KB. */ #define MAX_DATA (128*1024) int sanei_scsi_max_request_size = MAX_DATA; typedef struct { struct IOStdReq *ioreq; struct MinList cmdlist; SANEI_SCSI_Sense_Handler handler; void *handler_arg; } descriptor; #define MAX_DES 8 static descriptor des[MAX_DES]; #define SENSE_LEN 254 typedef struct { struct MinNode node; /* must be first element */ descriptor *des; struct IOStdReq ioreq; struct SCSICmd cmd; size_t *dst_size; UBYTE sense[SENSE_LEN]; } command; /* prototypes */ static SANE_Status amiga_open_device( char *dname, int board, int id, int lun, struct IOStdReq **ioP ); static void amiga_close_device( struct IOStdReq *ioreq ); static void amiga_flush_cmds( descriptor *d ); static void amiga_cleanup_one( descriptor *d ); static void amiga_cleanup_all(void); static void amiga_finish(void); static SANE_Status amiga_init(void); static command * amiga_create_command(struct IOStdReq *ioreq, void *src, size_t src_size, void *dst, size_t dst_size); static int amiga_inquiry(struct IOStdReq *ioreq, char *res, size_t reslen); static void amiga_close_device( struct IOStdReq *ioreq ) { CloseDevice((struct IORequest *)ioreq); DeleteExtIO((struct IORequest *)ioreq); } static void amiga_flush_cmds( descriptor *d ) { command *c; if( !d->ioreq ) return; while( (c = RemHead((struct List *)&d->cmdlist)) != NULL ) { AbortIO((struct IORequest *)&c->ioreq); WaitIO((struct IORequest *)&c->ioreq); FreeMem(c, sizeof(*c)); } } static void amiga_cleanup_one( descriptor *d ) { if( !d->ioreq ) return; amiga_flush_cmds(d); amiga_close_device(d->ioreq); d->ioreq = NULL; /* mark as free */ } static void amiga_cleanup_all(void) { int i; for( i = 0; i < MAX_DES; i++ ) amiga_cleanup_one(des); } static void amiga_finish(void) { amiga_cleanup_all(); DeletePort(msgport); msgport = NULL; } static SANE_Status amiga_init(void) { if( !msgport ) { msgport = CreatePort(NULL, 0); if( !msgport ) return SANE_STATUS_NO_MEM; /* out of resources */ atexit(amiga_finish); } return SANE_STATUS_GOOD; } static SANE_Status amiga_open_device( char *dname, int board, int id, int lun, struct IOStdReq **ioP ) { struct IOStdReq *ioreq; SANE_Status status; ULONG unit; *ioP = NULL; if( !dname || !dname[0] ) { dname = getenv("SANE_AMIGA_SCSI_DEVICE"); if( !dname ) dname = SCSI_DEVICE; } status = SANE_STATUS_NO_MEM; ioreq = CreateExtIO(msgport, sizeof(struct IOStdReq)); if( ioreq ) { status = SANE_STATUS_GOOD; unit = 100 * board + 10 * lun + id; if( OpenDevice(dname, unit, (struct IORequest *)ioreq, 0L) != 0 ) { DeleteExtIO((struct IORequest *)ioreq); ioreq = NULL; status = SANE_STATUS_INVAL; } } *ioP = ioreq; return status; } SANE_Status sanei_scsi_open_extended( const char *dev, int *fdP, SANEI_SCSI_Sense_Handler handler, void *handler_arg, int *buffersize ) { int board = -1, id = -1, lun = -1; char *dname, *p; size_t len; int i; SANE_Status status; *fdP = -1; status = amiga_init(); if( status != SANE_STATUS_GOOD ) return status; /* find an empty descriptor slot */ for( i = 0; i < MAX_DES; i++ ) { if( des[i].ioreq == NULL ) break; } if( i >= MAX_DES ) return SANE_STATUS_NO_MEM; /* Get device name. dev could be an immutable string constant, * so we have to copy the device name (after parsing the rest * of the string to simplify error checking). */ p = strchr(dev, ':'); if( !p ) return SANE_STATUS_INVAL; len = p-dev; /* get board number */ board = strtol(p+1, &p, 10); if( *p != ':' ) return SANE_STATUS_INVAL; /* get unit ID */ id = strtol(p+1, &p, 10); if( *p != ':' ) return SANE_STATUS_INVAL; /* get LUN */ lun = strtol(p+1, &p, 10); if( *p != '\0' ) return SANE_STATUS_INVAL; if( board < 0 || id < 0 || id > 6 || lun < 0 || lun > 7 ) return SANE_STATUS_INVAL; /* dev string seems to be ok. Now copy device name. */ dname = AllocMem(len+1, MEMF_PUBLIC); if( !dname ) return SANE_STATUS_NO_MEM; strncpy(dname, dev, len); dname[len] = '\0'; status = amiga_open_device(dname, board, id, lun, &des[i].ioreq); FreeMem(dname, len+1); if( status != SANE_STATUS_GOOD ) return status; NewList((struct List *)&des[i].cmdlist); des[i].handler = handler; des[i].handler_arg = handler_arg; *fdP = i; return SANE_STATUS_GOOD; } SANE_Status sanei_scsi_open( const char *dev, int *fdP, SANEI_SCSI_Sense_Handler handler, void *handler_arg ) { int bs = sanei_scsi_max_request_size; return sanei_scsi_open_extended(dev, fdP, handler, handler_arg, &bs); } void sanei_scsi_close( int fd ) { if( fd < 0 || fd >= MAX_DES ) return; amiga_cleanup_one(&des[fd]); } static command * amiga_create_command( struct IOStdReq *ioreq, void *src, size_t src_size, void *dst, size_t dst_size ) { command *c; c = AllocMem(sizeof(*c), MEMF_PUBLIC|MEMF_CLEAR); if( c ) { memcpy(&c->ioreq, ioreq, sizeof(c->ioreq)); c->ioreq.io_Length = sizeof(struct SCSICmd); c->ioreq.io_Data = &c->cmd; c->ioreq.io_Command = HD_SCSICMD; c->cmd.scsi_Data = dst; c->cmd.scsi_Length = dst_size; c->cmd.scsi_Command = src; c->cmd.scsi_CmdLength = src_size; c->cmd.scsi_Flags = SCSIF_AUTOSENSE; c->cmd.scsi_SenseData = c->sense; c->cmd.scsi_SenseLength = SENSE_LEN; c->cmd.scsi_SenseActual = 0; } return c; } SANE_Status sanei_scsi_req_enter( int fd, const void * src, size_t src_size, void * dst, size_t *dst_size, void **idP ) { command *c; descriptor *d; SANE_Status status; if( fd < 0 || fd >= MAX_DES ) return SANE_STATUS_INVAL; d = &des[fd]; if( d->ioreq == NULL ) return SANE_STATUS_INVAL; status = SANE_STATUS_NO_MEM; c = amiga_create_command(d->ioreq, src, src_size, dst, dst_size ? *dst_size : 0); if( c ) { AddTail((struct List *)&d->cmdlist, (struct Node *)c); c->des = d; c->dst_size = dst_size; SendIO((struct IORequest *)&c->ioreq); status = SANE_STATUS_GOOD; } *idP = (void *)c; return status; } SANE_Status sanei_scsi_wait( void *id ) { SANEI_SCSI_Sense_Handler handler; SANE_Status status; command *c = id; int fd; status = SANE_STATUS_GOOD; WaitIO((struct IORequest *)&c->ioreq); /* wait for request to complete */ Remove((struct Node *)c); /* from d->cmdlist */ if( c->dst_size ) *(c->dst_size) = c->cmd.scsi_Actual; if( c->cmd.scsi_Status != 0 ) { handler = c->des->handler; fd = c->des - des; if( handler ) (*handler)(fd, (u_char *)c->sense, c->des->handler_arg); status = SANE_STATUS_IO_ERROR; } FreeMem(c, sizeof(*c)); return status; } SANE_Status sanei_scsi_cmd( int fd, const void * src, size_t src_size, void * dst, size_t * dst_size ) { SANE_Status status; void *id; status = sanei_scsi_req_enter(fd, src, src_size, dst, dst_size, &id); if( status == SANE_STATUS_GOOD ) status = sanei_scsi_req_wait(id); return status; } void sanei_scsi_req_flush_all_extended( int fd ) { if( fd < 0 || fd >= MAX_DES ) return; amiga_flush_cmds(&des[fd]); } void sanei_scsi_req_flush_all( void ) { int i; for( i = 0; i < MAX_DES; i++ ) amiga_flush_cmds(&des[i]); } static int amiga_inquiry( struct IOStdReq *ioreq, char *res, size_t reslen ) { command *c; int status; static UBYTE icmd[] = { 0x12, 0x00, 0x00, 0x00, 0x00, 0x00 }; status = -1; icmd[5] = reslen; c = amiga_create_command(ioreq, icmd, sizeof(icmd), res, reslen); if( c ) { DoIO((struct IORequest *)ioreq); status = c->cmd.scsi_Status; FreeMem(c, sizeof(*c)); } return status; } void sanei_scsi_find_devices( const char *vendor, const char *model, const char *type, int bus, int channel, int id, int lun, SANE_Status (*attach)(const char *dev) ) { #define RESLEN 96 char result[RESLEN]; struct IOStdReq *ioreq; int id2, lun2; int error; SANE_Bool match; char devstr[32]; if( amiga_init() != SANE_STATUS_GOOD ) return; /* bus: used as board, -1 becomes 0 * channel: ignored * id: max 6 (are there any Wide-SCSI adapters for Amiga?) * lun: max 7 */ if( bus < 0 ) bus = 0; for( id2 = 0; id2 < 7; id2++ ) { for( lun2 = 0; lun2 < 8; lun2++ ) { if( (id < 0 || id == id2) && (lun < 0 || lun == lun2) ) { amiga_open_device(NULL, bus, id2, lun2, &ioreq); if( ioreq ) { error = amiga_inquiry(ioreq, result, sizeof(result)); if( error != 0 ) return; /* TODO? */ match = TRUE; if( vendor && strnicmp(vendor, result+8, strlen(vendor)) != 0 ) match = FALSE; if( model && strnicmp(model, result+16, strlen(model)) != 0 ) match = FALSE; /* TODO: check type */ if( match ) { sprintf(devstr, ":%d:%d:%d", bus, id2, lun2); (*attach)(devstr); } amiga_close_device(ioreq); } } } } } /* The End */