mirror of
https://github.com/Oibaf66/uae-wii.git
synced 2024-11-14 06:45:07 +01:00
630 lines
15 KiB
C
630 lines
15 KiB
C
/*
|
|
* UAE - The Un*x Amiga Emulator
|
|
*
|
|
* Block device access using libscg
|
|
*
|
|
* Copyright 2004-2005 Richard Drummond
|
|
*
|
|
* Heavily based on code:
|
|
* Copyright 1995 Bernd Schmidt
|
|
* Copyright 1999 Patrick Ohly
|
|
*
|
|
*/
|
|
|
|
#include "sysconfig.h"
|
|
#include "sysdeps.h"
|
|
|
|
#include "options.h"
|
|
#include "memory.h"
|
|
#include "threaddep/thread.h"
|
|
#include "blkdev.h"
|
|
#include "scsidev.h"
|
|
#include "sleep.h"
|
|
#include "gui.h"
|
|
|
|
//#define DEBUG_ME
|
|
#ifdef DEBUG_ME
|
|
#define DEBUG_LOG write_log
|
|
#else
|
|
#define DEBUG_LOG(...) do { ; } while (0);
|
|
#endif
|
|
|
|
typedef int BOOL;
|
|
|
|
#include "scg/scgcmd.h"
|
|
#include "scg/scsidefs.h"
|
|
#include "scg/scsireg.h"
|
|
#include "scg/scsitransp.h"
|
|
|
|
#define MAX_DRIVES 16
|
|
|
|
struct scsidevdata {
|
|
int bus, target, lun;
|
|
int isatapi;
|
|
int max_dma;
|
|
SCSI *scgp;
|
|
};
|
|
|
|
static struct scsidevdata drives[MAX_DRIVES];
|
|
static int total_drives;
|
|
|
|
static const uae_u8 *execscsicmd_in (int unitnum, const uae_u8 *data, int len, int *outlen);
|
|
|
|
/*
|
|
* scg_isatapi() is not implemented on all platforms. Therefore,
|
|
* this little piece of magic from Toni Wilen is needed to detect
|
|
* ATAPI devices.
|
|
*/
|
|
static int is_atapi_drive (int unitnum)
|
|
{
|
|
static const uae_u8 cmd[6] = {0x12, 0, 0, 0, 36, 0}; /* INQUIRY */
|
|
uae_u8 out[36];
|
|
int outlen = sizeof (out);
|
|
const uae_u8 *p = execscsicmd_in (unitnum, cmd, sizeof (cmd), &outlen);
|
|
if (!p)
|
|
return 0;
|
|
if (outlen >= 2 && (p[0] & 31) == 5 && (p[2] & 7) == 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int add_drive (SCSI *scgp)
|
|
{
|
|
int result = 0;
|
|
|
|
if (total_drives < MAX_DRIVES) {
|
|
int isatapi;
|
|
|
|
drives [total_drives].bus = scgp->addr.scsibus;
|
|
drives [total_drives].target = scgp->addr.target;
|
|
drives [total_drives].lun = scgp->addr.lun;
|
|
|
|
isatapi = scg_isatapi (scgp);
|
|
|
|
/* If scg_isatapi returned false, we need to double-check
|
|
* because it may not be implemented on the target platform
|
|
*/
|
|
if (!isatapi) {
|
|
drives [total_drives].scgp = scgp;
|
|
isatapi = is_atapi_drive (total_drives);
|
|
drives [total_drives].scgp = 0;
|
|
}
|
|
drives [total_drives].isatapi = isatapi;
|
|
|
|
total_drives++;
|
|
result = 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Global lock - this needs to be replaced with a per-device lock */
|
|
uae_sem_t scgp_sem;
|
|
|
|
/********** generic SCSI stuff stolen from cdrecord and scsitransp.c *********/
|
|
|
|
static int inquiry (SCSI *scgp, void *bp, int cnt)
|
|
{
|
|
struct scg_cmd *scmd = scgp->scmd;
|
|
int result;
|
|
|
|
memset (bp, 0, cnt);
|
|
memset ((void *)scmd, 0, sizeof(*scmd));
|
|
scmd->addr = bp;
|
|
scmd->size = cnt;
|
|
scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
|
|
scmd->cdb_len = SC_G0_CDBLEN;
|
|
scmd->sense_len = CCS_SENSE_LEN;
|
|
scmd->cdb.g0_cdb.cmd = SC_INQUIRY;
|
|
scmd->cdb.g0_cdb.lun = scg_lun(scgp);
|
|
scmd->cdb.g0_cdb.count = cnt;
|
|
|
|
scgp->cmdname = "inquiry";
|
|
|
|
result = scg_cmd(scgp);
|
|
|
|
return result;
|
|
}
|
|
|
|
static int test_unit_ready (SCSI *scgp)
|
|
{
|
|
struct scg_cmd *scmd = scgp->scmd;
|
|
int result;
|
|
|
|
memset ((void *)scmd, 0, sizeof(*scmd));
|
|
scmd->addr = (caddr_t)0;
|
|
scmd->size = 0;
|
|
scmd->flags = SCG_DISRE_ENA | (scgp->silent ? SCG_SILENT:0);
|
|
scmd->cdb_len = SC_G0_CDBLEN;
|
|
scmd->sense_len = CCS_SENSE_LEN;
|
|
scmd->cdb.g0_cdb.cmd = SC_TEST_UNIT_READY;
|
|
scmd->cdb.g0_cdb.lun = scg_lun(scgp);
|
|
|
|
scgp->cmdname = "test unit ready";
|
|
|
|
result = scg_cmd (scgp);
|
|
|
|
return result;
|
|
}
|
|
|
|
static int unit_ready (SCSI *scgp)
|
|
{
|
|
register struct scg_cmd *scmd = scgp->scmd;
|
|
|
|
if (test_unit_ready(scgp) >= 0) /* alles OK */
|
|
return 1;
|
|
else if (scmd->error >= SCG_FATAL) /* nicht selektierbar */
|
|
return 0;
|
|
|
|
if (scg_sense_key(scgp) == SC_UNIT_ATTENTION) {
|
|
if (test_unit_ready(scgp) >= 0) /* alles OK */
|
|
return 1;
|
|
}
|
|
if ((scg_cmd_status(scgp) & ST_BUSY) != 0) {
|
|
/* Busy/reservation_conflict */
|
|
uae_msleep (500);
|
|
|
|
if (test_unit_ready(scgp) >= 0) /* alles OK */
|
|
return 1;
|
|
}
|
|
if (scg_sense_key(scgp) == -1) { /* non extended Sense */
|
|
if (scg_sense_code(scgp) == 4) /* NOT_READY */
|
|
return 0;
|
|
return 1;
|
|
}
|
|
/* FALSE wenn NOT_READY */
|
|
return (scg_sense_key (scgp) != SC_NOT_READY);
|
|
}
|
|
|
|
static void print_product (const struct scsi_inquiry *ip)
|
|
{
|
|
write_log ("'%.8s' ", ip->vendor_info);
|
|
write_log ("'%.16s' ", ip->prod_ident);
|
|
write_log ("'%.4s' ", ip->prod_revision);
|
|
|
|
if (ip->type == INQ_ROMD)
|
|
write_log ("CD-ROM");
|
|
else if (ip->type == INQ_DASD)
|
|
write_log ("Disk");
|
|
}
|
|
|
|
/* get integer value from env or return default value, if unset */
|
|
static int getenvint (const char *varname, int def)
|
|
{
|
|
const char *val = getenv (varname);
|
|
return val ? atoi (val) : def;
|
|
}
|
|
|
|
static SCSI *openscsi (int scsibus, int target, int lun)
|
|
{
|
|
SCSI *scgp = scg_smalloc ();
|
|
|
|
if (scgp) {
|
|
char *device;
|
|
|
|
scgp->debug = getenvint ("UAE_SCSI_DEBUG", 0);
|
|
scgp->kdebug = getenvint ("UAE_SCSI_KDEBUG", 0);
|
|
scgp->silent = getenvint ("UAE_SCSI_SILENT", 1);
|
|
scgp->verbose = getenvint ("UAE_SCSI_VERBOSE", 0);
|
|
device = getenv ("UAE_SCSI_DEVICE");
|
|
|
|
if (!device || (strlen(device) == 0))
|
|
device = currprefs.scsi_device;
|
|
|
|
write_log ("SCSIDEV: Device '%s'\n", device);
|
|
|
|
scg_settarget (scgp, scsibus, target, lun);
|
|
scg_settimeout (scgp, 80*60);
|
|
|
|
if (scg__open (scgp, device) <= 0) {
|
|
if (scgp->errstr)
|
|
write_log ("SCSIDEV: Failed to open '%s': %s\n",
|
|
device, scgp->errstr);
|
|
scg_sfree (scgp);
|
|
scgp = 0;
|
|
}
|
|
}
|
|
return scgp;
|
|
}
|
|
|
|
static void closescsi (SCSI *scgp)
|
|
{
|
|
scg__close (scgp);
|
|
scg_sfree (scgp);
|
|
}
|
|
|
|
/********************* start of our own code ************************/
|
|
|
|
static uae_u8 scsibuf [DEVICE_SCSI_BUFSIZE];
|
|
|
|
static int execscsicmd (int unitnum, const uae_u8 *data, int len, uae_u8 *inbuf,
|
|
int inlen)
|
|
{
|
|
int sactual = 0;
|
|
struct scsidevdata *sdd = &drives[unitnum];
|
|
SCSI *scgp = sdd->scgp;
|
|
struct scg_cmd *scmd;
|
|
|
|
scmd = scgp->scmd;
|
|
|
|
DEBUG_LOG ("SCSIDEV: execscicmd data=%08lx len=%d, inbuf=%08lx"\
|
|
" inlen=%d\n", data, len, inbuf, inlen);
|
|
|
|
uae_sem_wait (&scgp_sem);
|
|
memset (scmd, 0, sizeof (*scmd));
|
|
|
|
scmd->timeout = 80 * 60;
|
|
if (inbuf) {
|
|
scmd->addr = (caddr_t) inbuf;
|
|
scmd->size = inlen;
|
|
scmd->flags = SCG_RECV_DATA;
|
|
memset (inbuf, 0, inlen);
|
|
} else {
|
|
scmd->flags = SCG_DISRE_ENA;
|
|
}
|
|
|
|
scmd->cdb_len = len;
|
|
memcpy (&scmd->cdb, data, len);
|
|
scmd->target = sdd->target;
|
|
scmd->sense_len = -1;
|
|
scmd->sense_count = 0;
|
|
*(uae_u8 *)&scmd->scb = 0;
|
|
|
|
scg_settarget (scgp, sdd->bus, sdd->target, sdd->lun);
|
|
scgp->cmdname = "???";
|
|
scgp->curcmdname = "???";
|
|
|
|
DEBUG_LOG ("SCSIDEV: sending command: 0x%2x\n", scmd->cdb.g0_cdb.cmd);
|
|
|
|
gui_cd_led (1);
|
|
|
|
scg_cmd (scgp);
|
|
|
|
uae_sem_post (&scgp_sem);
|
|
|
|
DEBUG_LOG ("SCSIDEV: result: %d %d\n", scmd->error, scmd->ux_errno);
|
|
|
|
return scmd->size;
|
|
}
|
|
|
|
static int execscsicmd_direct (int unitnum, uaecptr acmd)
|
|
{
|
|
int sactual = 0;
|
|
struct scsidevdata *sdd = &drives[unitnum];
|
|
SCSI *scgp = sdd->scgp;
|
|
struct scg_cmd *scmd = scgp->scmd;
|
|
|
|
uaecptr scsi_data = get_long (acmd + 0);
|
|
uae_u32 scsi_len = get_long (acmd + 4);
|
|
uaecptr scsi_cmd = get_long (acmd + 12);
|
|
int scsi_cmd_len = get_word (acmd + 16);
|
|
int scsi_cmd_len_orig = scsi_cmd_len;
|
|
uae_u8 scsi_flags = get_byte (acmd + 20);
|
|
uaecptr scsi_sense = get_long (acmd + 22);
|
|
uae_u16 scsi_sense_len = get_word (acmd + 26);
|
|
int io_error = 0;
|
|
int parm;
|
|
addrbank *bank_data = &get_mem_bank (scsi_data);
|
|
addrbank *bank_cmd = &get_mem_bank (scsi_cmd);
|
|
uae_u8 *scsi_datap;
|
|
uae_u8 *scsi_datap_org;
|
|
|
|
DEBUG_LOG ("SCSIDEV: unit=%d: execscsicmd_direct\n", unitnum);
|
|
|
|
/* do transfer directly to and from Amiga memory */
|
|
if (!bank_data || !bank_data->check (scsi_data, scsi_len))
|
|
return -5; /* IOERR_BADADDRESS */
|
|
|
|
uae_sem_wait (&scgp_sem);
|
|
|
|
memset (scmd, 0, sizeof (*scmd));
|
|
/* the Amiga does not tell us how long the timeout shall be, so make it
|
|
* _very_ long (specified in seconds) */
|
|
scmd->timeout = 80 * 60;
|
|
scsi_datap = scsi_datap_org = scsi_len
|
|
? bank_data->xlateaddr (scsi_data) : 0;
|
|
scmd->size = scsi_len;
|
|
scmd->flags = (scsi_flags & 1) ? SCG_RECV_DATA : SCG_DISRE_ENA;
|
|
memcpy (&scmd->cdb, bank_cmd->xlateaddr (scsi_cmd), scsi_cmd_len);
|
|
scmd->target = sdd->target;
|
|
scmd->sense_len = (scsi_flags & 4) ? 4 : /* SCSIF_OLDAUTOSENSE */
|
|
(scsi_flags & 2) ? scsi_sense_len : /* SCSIF_AUTOSENSE */
|
|
-1;
|
|
scmd->sense_count = 0;
|
|
*(uae_u8 *)&scmd->scb = 0;
|
|
if (sdd->isatapi)
|
|
scsi_atapi_fixup_pre (scmd->cdb.cmd_cdb, &scsi_cmd_len, &scsi_datap,
|
|
&scsi_len, &parm);
|
|
scmd->addr = (caddr_t)scsi_datap;
|
|
scmd->cdb_len = scsi_cmd_len;
|
|
|
|
scg_settarget (scgp, sdd->bus, sdd->target, sdd->lun);
|
|
scgp->cmdname = "???";
|
|
scgp->curcmdname = "???";
|
|
|
|
DEBUG_LOG ("SCSIDEV: sending command: 0x%2x\n", scmd->cdb.g0_cdb.cmd);
|
|
|
|
scg_cmd (scgp);
|
|
|
|
DEBUG_LOG ("SCSIDEV: result: %d %d %s\n", scmd->error, scmd->ux_errno,\
|
|
scgp->errstr);
|
|
|
|
gui_cd_led (1);
|
|
|
|
put_word (acmd + 18, scmd->error == SCG_FATAL
|
|
? 0 : scsi_cmd_len); /* fake scsi_CmdActual */
|
|
put_byte (acmd + 21, *(uae_u8 *)&scmd->scb); /* scsi_Status */
|
|
|
|
if (*(uae_u8 *)&scmd->scb) {
|
|
io_error = 45; /* HFERR_BadStatus */
|
|
/* copy sense? */
|
|
for (sactual = 0;
|
|
scsi_sense && sactual < scsi_sense_len && sactual < scmd->sense_count;
|
|
sactual++) {
|
|
put_byte (scsi_sense + sactual, scmd->u_sense.cmd_sense[sactual]);
|
|
}
|
|
put_long (acmd + 8, 0); /* scsi_Actual */
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < scsi_sense_len; i++)
|
|
put_byte (scsi_sense + i, 0);
|
|
sactual = 0;
|
|
if (scmd->error != SCG_NO_ERROR || scmd->ux_errno != 0) {
|
|
/* We might have been limited by the hosts DMA limits,
|
|
which is usually indicated by ENOMEM */
|
|
if (scsi_len > (unsigned int)sdd->max_dma && scmd->ux_errno == ENOMEM)
|
|
io_error = (uae_u8)-4; /* IOERR_BADLENGTH */
|
|
else {
|
|
io_error = 20; /* io_Error, but not specified */
|
|
put_long (acmd + 8, 0); /* scsi_Actual */
|
|
}
|
|
} else {
|
|
scsi_len = scmd->size;
|
|
if (sdd->isatapi)
|
|
scsi_atapi_fixup_post (scmd->cdb.cmd_cdb, scsi_cmd_len,
|
|
scsi_datap_org, scsi_datap,
|
|
&scsi_len, parm);
|
|
io_error = 0;
|
|
put_long (acmd + 8, scsi_len); /* scsi_Actual */
|
|
}
|
|
}
|
|
put_word (acmd + 28, sactual);
|
|
|
|
uae_sem_post (&scgp_sem);
|
|
|
|
if (scsi_datap != scsi_datap_org)
|
|
free (scsi_datap);
|
|
|
|
return io_error;
|
|
}
|
|
|
|
static const uae_u8 *execscsicmd_out (int unitnum, const uae_u8 *data, int len)
|
|
{
|
|
DEBUG_LOG ("SCSIDEV: unit=%d: execscsicmd_out\n", unitnum);
|
|
|
|
if (execscsicmd (unitnum, data, len, 0, 0) < 0)
|
|
return 0;
|
|
|
|
return data;
|
|
}
|
|
|
|
static const uae_u8 *execscsicmd_in (int unitnum, const uae_u8 *data, int len, int *outlen)
|
|
{
|
|
int v;
|
|
|
|
DEBUG_LOG ("SCSIDEV: unit=%d: execscsicmd_in\n", unitnum);
|
|
|
|
v = execscsicmd (unitnum, data, len, scsibuf, DEVICE_SCSI_BUFSIZE);
|
|
|
|
if (v < 0)
|
|
return 0;
|
|
if (v == 0)
|
|
return 0;
|
|
if (outlen)
|
|
*outlen = v;
|
|
|
|
return scsibuf;
|
|
}
|
|
|
|
/*
|
|
* Scan SCSI busses to detect any devices
|
|
* that we want to supply to the Amgia.
|
|
*
|
|
* Based on code from cdrecord
|
|
*/
|
|
static int scanscsi (SCSI *scgp)
|
|
{
|
|
int bus;
|
|
int tgt;
|
|
int lun = 0;
|
|
int initiator;
|
|
int have_tgt;
|
|
int n;
|
|
|
|
scgp->silent++;
|
|
for (bus = 0; bus < 16; bus++) {
|
|
scg_settarget (scgp, bus, 0, 0);
|
|
|
|
if (!scg_havebus (scgp, bus))
|
|
continue;
|
|
|
|
initiator = scg_initiator_id (scgp);
|
|
write_log ("scsibus%d:\n", bus);
|
|
|
|
for (tgt = 0; tgt < 16; tgt++) {
|
|
n = bus * 100 + tgt;
|
|
|
|
scg_settarget (scgp, bus, tgt, lun);
|
|
have_tgt = unit_ready (scgp) || scgp->scmd->error != SCG_FATAL;
|
|
|
|
if (!have_tgt && tgt > 7) {
|
|
if (scgp->scmd->ux_errno == EINVAL)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
write_log (" %d,%d,%d %d ", bus, tgt, lun, n);
|
|
|
|
if (tgt == initiator) {
|
|
write_log ("HOST ADAPTOR\n");
|
|
continue;
|
|
}
|
|
if (!have_tgt) {
|
|
/* Hack: fd -> -2 means no access */
|
|
write_log( "%c\n", scgp->fd == -2 ? '?':'*');
|
|
continue;
|
|
}
|
|
|
|
if ((scgp->scmd->error < SCG_FATAL)
|
|
|| (scgp->scmd->scb.chk && scgp->scmd->sense_count > 0)) {
|
|
struct scsi_inquiry *inq = scgp->inq;
|
|
|
|
inquiry (scgp, inq, sizeof (*inq));
|
|
print_product (inq);
|
|
|
|
/* Just CD/DVD drives for now */
|
|
if (inq->type == INQ_ROMD)
|
|
add_drive (scgp);
|
|
}
|
|
|
|
write_log ("\n");
|
|
}
|
|
}
|
|
scgp->silent--;
|
|
return 0;
|
|
}
|
|
|
|
static int open_scsi_bus (int flags)
|
|
{
|
|
int result = 0;
|
|
int debug, verbose;
|
|
SCSI *scgp_scan;
|
|
char *device;
|
|
char errstr[128];
|
|
static int init = 0;
|
|
|
|
DEBUG_LOG ("SCSIDEV: open_scsi_bus\n");
|
|
|
|
if (!init) {
|
|
init = 1;
|
|
uae_sem_init (&scgp_sem, 0, 1);
|
|
/* TODO: replace global lock with per-device locks */
|
|
}
|
|
|
|
debug = getenvint ("UAE_SCSI_DEBUG", 0);
|
|
verbose = getenvint ("UAE_SCSI_VERBOSE", 0);
|
|
device = getenv ("UAE_SCSI_DEVICE");
|
|
|
|
if (!device || (strlen (device) == 0))
|
|
device = currprefs.scsi_device;
|
|
|
|
if ((scgp_scan = scg_open (device, errstr, sizeof (errstr),
|
|
debug, verbose)) != (SCSI *)0) {
|
|
scanscsi (scgp_scan);
|
|
result = 1;
|
|
scg_close (scgp_scan);
|
|
} else {
|
|
write_log ("SCSIDEV: can't open bus: %s\n", errstr);
|
|
}
|
|
|
|
write_log ("SCSIDEV: %d devices found\n", total_drives);
|
|
return result;
|
|
}
|
|
|
|
static void close_scsi_bus (void)
|
|
{
|
|
int i;
|
|
|
|
DEBUG_LOG ("SCSIDEV: close_scsi_bus\n");
|
|
|
|
for (i = 0; i < total_drives; i++) {
|
|
closescsi (drives[i].scgp);
|
|
drives[i].scgp = 0;
|
|
}
|
|
}
|
|
|
|
static int open_scsi_device (int unitnum)
|
|
{
|
|
int result = 0;
|
|
|
|
DEBUG_LOG ("SCSIDEV: unit=%d: open_scsi_device\n", unitnum);
|
|
|
|
if (unitnum < total_drives) {
|
|
struct scsidevdata *sdd = &drives[unitnum];
|
|
|
|
if (!sdd->scgp) {
|
|
if ((sdd->scgp = openscsi (sdd->bus, sdd->target, sdd->lun)) != 0)
|
|
result = 1;
|
|
} else
|
|
/* already open */
|
|
result = 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void close_scsi_device (int unitnum)
|
|
{
|
|
DEBUG_LOG ("SCSIDEV: unit=%d: close_scsi_device\n", unitnum);
|
|
|
|
/* do nothing - leave devices open until the bus is closed */
|
|
}
|
|
|
|
static int media_check (SCSI *scgp)
|
|
{
|
|
int media = 0;
|
|
|
|
uae_sem_wait (&scgp_sem);
|
|
|
|
if (test_unit_ready (scgp) >= 0)
|
|
media = 1;
|
|
|
|
uae_sem_post (&scgp_sem);
|
|
|
|
DEBUG_LOG ("SCSIDEV: media check :%d\n", media);
|
|
|
|
return media;
|
|
}
|
|
|
|
static struct device_info *info_device (int unitnum, struct device_info *di)
|
|
{
|
|
DEBUG_LOG ("SCSIDEV: unit=%d: info_device\n", unitnum);
|
|
|
|
if (unitnum < total_drives) {
|
|
struct scsidevdata *sdd = &drives[unitnum];
|
|
|
|
di->bus = 0;
|
|
di->target = unitnum;
|
|
di->lun = 0;
|
|
di->media_inserted = media_check (sdd->scgp);
|
|
di->write_protected = 1;
|
|
di->bytespersector = 2048;
|
|
di->cylinders = 1;
|
|
di->type = INQ_ROMD; /* We only support CD/DVD drives for now */
|
|
di->id = unitnum + 1;
|
|
/* TODO: Create a more informative device label */
|
|
sprintf (di->label, "(%d,%d,%d)", sdd->bus, sdd->target, sdd->lun);
|
|
} else
|
|
di = 0;
|
|
|
|
return di;
|
|
}
|
|
|
|
static int check_isatapi (int unitnum)
|
|
{
|
|
return drives[unitnum].isatapi;
|
|
}
|
|
|
|
struct device_functions devicefunc_scsi_libscg = {
|
|
open_scsi_bus,
|
|
close_scsi_bus,
|
|
open_scsi_device,
|
|
close_scsi_device,
|
|
info_device,
|
|
execscsicmd_out,
|
|
execscsicmd_in,
|
|
execscsicmd_direct,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
check_isatapi,
|
|
0, 0
|
|
};
|