uae-wii/src/blkdev-libscg.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
};