/*
* This file has been modified for the cdrkit suite.
*
* The behaviour and appearence of the program code below can differ to a major
* extent from the version distributed by the original author(s).
*
* For details, see Changelog file distributed with the cdrkit package. If you
* received this file from another source then ask the distributing person for
* a log of modifications.
*
*/
/* @(#)scsi-mac-iokit.c 1.10 05/05/15 Copyright 1997,2001-2004 J. Schilling */
/*
* Interface to the Darwin IOKit SCSI drivers
*
* Notes: Uses the IOKit/scsi-commands/SCSITaskLib interface
*
* As of October 2001, this interface does not support SCSI parallel bus
* (old-fashioned SCSI). It does support ATAPI, Firewire, and USB.
*
* First version done by Constantine Sapuntzakis <csapuntz@Stanford.EDU>
*
* Warning: you may change this source, but if you do that
* you need to change the _usal_version and _usal_auth* string below.
* You may not return "schily" for an SCG_AUTHOR request anymore.
* Choose your name instead of "schily" and make clear that the version
* string is related to a modified source.
*
* Copyright (c) 1997,2001-2004 J. Schilling
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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; see the file COPYING. If not, write to the Free Software
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
* Warning: you may change this source, but if you do that
* you need to change the _usal_version and _usal_auth* string below.
* You may not return "schily" for an SCG_AUTHOR request anymore.
* Choose your name instead of "schily" and make clear that the version
* string is related to a modified source.
*/
static char _usal_trans_version[] = "scsi-mac-iokit.c-1.10"; /* The version for this transport */
#define MAX_SCG 16 /* Max # of SCSI controllers */
#define MAX_TGT 16
#define MAX_LUN 8
#include <statdefs.h>
#include <mach/mach.h>
#include <Carbon/Carbon.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/scsi-commands/SCSITaskLib.h>
#include <mach/mach_error.h>
struct usal_local {
MMCDeviceInterface **mmcDeviceInterface;
SCSITaskDeviceInterface **scsiTaskDeviceInterface;
mach_port_t masterPort;
};
#define usallocal(p) ((struct usal_local *)((p)->local))
#define MAX_DMA_NEXT (32*1024)
#if 0
#define MAX_DMA_NEXT (64*1024) /* Check if this is not too big */
#endif
/*
* Return version information for the low level SCSI transport code.
* This has been introduced to make it easier to trace down problems
* in applications.
*/
static char *
usalo_version(SCSI *usalp, int what)
{
if (usalp != (SCSI *)0) {
switch (what) {
case SCG_VERSION:
return (_usal_trans_version);
/*
* If you changed this source, you are not allowed to
* return "schily" for the SCG_AUTHOR request.
*/
case SCG_AUTHOR:
return (_usal_auth_cdrkit);
case SCG_SCCS_ID:
return (__sccsid);
}
}
return ((char *)0);
}
static int
usalo_help(SCSI *usalp, FILE *f)
{
__usal_help(f, "SCSITaskDeviceInterface", "Apple SCSI",
"", "Mac Prom device name", "IOCompactDiscServices/0",
FALSE, FALSE);
return (0);
}
/*
* Valid Device names:
* IOCompactDiscServices
* IODVDServices
* IOSCSIPeripheralDeviceNub
*
* Also a / and a number can be appended to refer to something
* more than the first device (e.g. IOCompactDiscServices/5 for the 5th
* compact disc attached)
*/
static int
usalo_open(SCSI *usalp, char *device)
{
mach_port_t masterPort = NULL;
io_iterator_t scsiObjectIterator = NULL;
IOReturn ioReturnValue = kIOReturnSuccess;
CFMutableDictionaryRef dict = NULL;
io_object_t scsiDevice = NULL;
HRESULT plugInResult;
IOCFPlugInInterface **plugInInterface = NULL;
MMCDeviceInterface **mmcDeviceInterface = NULL;
SCSITaskDeviceInterface **scsiTaskDeviceInterface = NULL;
SInt32 score = 0;
int err = -1;
char *realdevice = NULL, *tmp;
int driveidx = 1, idx = 1;
if (device == NULL) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"Please specify a device name (e.g. IOCompactDiscServices/0)");
goto out;
}
realdevice = tmp = strdup(device);
tmp = strchr(tmp, '/');
if (tmp != NULL) {
*tmp++ = '\0';
driveidx = atoi(tmp);
}
if (usalp->local == NULL) {
usalp->local = malloc(sizeof (struct usal_local));
if (usalp->local == NULL)
goto out;
}
ioReturnValue = IOMasterPort(bootstrap_port, &masterPort);
if (ioReturnValue != kIOReturnSuccess) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"Couldn't get a master IOKit port. Error %d",
ioReturnValue);
goto out;
}
dict = IOServiceMatching(realdevice);
if (dict == NULL) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"Couldn't create dictionary for searching");
goto out;
}
ioReturnValue = IOServiceGetMatchingServices(masterPort, dict,
&scsiObjectIterator);
dict = NULL;
if (scsiObjectIterator == NULL ||
(ioReturnValue != kIOReturnSuccess)) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"No matching device %s found.", device);
goto out;
}
if (driveidx <= 0)
driveidx = 1;
idx = 1;
while ((scsiDevice = IOIteratorNext(scsiObjectIterator)) != NULL) {
if (idx == driveidx)
break;
IOObjectRelease(scsiDevice);
scsiDevice = NULL;
idx++;
}
if (scsiDevice == NULL) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"No matching device found. Iterator failed.");
goto out;
}
ioReturnValue = IOCreatePlugInInterfaceForService(scsiDevice,
kIOMMCDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugInInterface, &score);
if (ioReturnValue != kIOReturnSuccess) {
goto try_generic;
}
plugInResult = (*plugInInterface)->QueryInterface(plugInInterface,
CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID),
(LPVOID)&mmcDeviceInterface);
if (plugInResult != KERN_SUCCESS) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"Unable to get MMC Interface: 0x%lX",
(long)plugInResult);
goto out;
}
scsiTaskDeviceInterface =
(*mmcDeviceInterface)->GetSCSITaskDeviceInterface(mmcDeviceInterface);
if (scsiTaskDeviceInterface == NULL) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"Failed to get taskDeviceInterface");
goto out;
}
goto init;
try_generic:
ioReturnValue = IOCreatePlugInInterfaceForService(scsiDevice,
kIOSCSITaskDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugInInterface, &score);
if (ioReturnValue != kIOReturnSuccess) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"Unable to get plugin Interface: %x",
ioReturnValue);
goto out;
}
plugInResult = (*plugInInterface)->QueryInterface(plugInInterface,
CFUUIDGetUUIDBytes(kIOSCSITaskDeviceInterfaceID),
(LPVOID)&scsiTaskDeviceInterface);
if (plugInResult != KERN_SUCCESS) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"Unable to get generic Interface: 0x%lX",
(long)plugInResult);
goto out;
}
init:
ioReturnValue =
(*scsiTaskDeviceInterface)->ObtainExclusiveAccess(scsiTaskDeviceInterface);
if (ioReturnValue != kIOReturnSuccess) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"Unable to get exclusive access to device");
goto out;
}
if (mmcDeviceInterface) {
(*mmcDeviceInterface)->AddRef(mmcDeviceInterface);
}
(*scsiTaskDeviceInterface)->AddRef(scsiTaskDeviceInterface);
usallocal(usalp)->mmcDeviceInterface = mmcDeviceInterface;
usallocal(usalp)->scsiTaskDeviceInterface = scsiTaskDeviceInterface;
usallocal(usalp)->masterPort = masterPort;
usal_settarget(usalp, 0, 0, 0);
err = 1;
out:
if (scsiTaskDeviceInterface != NULL) {
(*scsiTaskDeviceInterface)->Release(scsiTaskDeviceInterface);
}
if (plugInInterface != NULL) {
(*plugInInterface)->Release(plugInInterface);
}
if (scsiDevice != NULL) {
IOObjectRelease(scsiDevice);
}
if (scsiObjectIterator != NULL) {
IOObjectRelease(scsiObjectIterator);
}
if (err < 0) {
if (usalp->local) {
free(usalp->local);
usalp->local = NULL;
}
if (masterPort) {
mach_port_deallocate(mach_task_self(), masterPort);
}
}
if (dict != NULL) {
CFRelease(dict);
}
if (realdevice != NULL) {
free(realdevice);
}
return (err);
}
static int
usalo_close(SCSI *usalp)
{
SCSITaskDeviceInterface **sc;
MMCDeviceInterface **mmc;
if (usalp->local == NULL)
return (-1);
sc = usallocal(usalp)->scsiTaskDeviceInterface;
(*sc)->ReleaseExclusiveAccess(sc);
(*sc)->Release(sc);
usallocal(usalp)->scsiTaskDeviceInterface = NULL;
mmc = usallocal(usalp)->mmcDeviceInterface;
if (mmc != NULL)
(*mmc)->Release(mmc);
mach_port_deallocate(mach_task_self(), usallocal(usalp)->masterPort);
free(usalp->local);
usalp->local = NULL;
return (0);
}
static long
usalo_maxdma(SCSI *usalp, long amt)
{
long maxdma = MAX_DMA_NEXT;
#ifdef SGIOCMAXDMA
int m;
if (ioctl(usallocal(usalp)->usalfile, SGIOCMAXDMA, &m) >= 0) {
maxdma = m;
if (usalp->debug > 0) {
fprintf((FILE *)usalp->errfile,
"maxdma: %d\n", maxdma);
}
}
#endif
return (maxdma);
}
static void *
usalo_getbuf(SCSI *usalp, long amt)
{
if (usalp->debug > 0) {
fprintf((FILE *)usalp->errfile,
"usalo_getbuf: %ld bytes\n", amt);
}
usalp->bufbase = malloc((size_t)(amt));
return (usalp->bufbase);
}
static void
usalo_freebuf(SCSI *usalp)
{
if (usalp->bufbase)
free(usalp->bufbase);
usalp->bufbase = NULL;
}
static BOOL
usalo_havebus(SCSI *usalp, int busno)
{
if (busno == 0)
return (TRUE);
return (FALSE);
}
static int
usalo_fileno(SCSI *usalp, int busno, int tgt, int tlun)
{
return (-1);
}
static int
usalo_initiator_id(SCSI *usalp)
{
return (-1);
}
static int
usalo_isatapi(SCSI *usalp)
{
return (FALSE);
}
static int
usalo_reset(SCSI *usalp, int what)
{
if (what == SCG_RESET_NOP)
return (0);
if (what != SCG_RESET_BUS) {
errno = EINVAL;
return (-1);
}
errno = 0;
return (-1);
}
static int
usalo_send(SCSI *usalp)
{
struct usal_cmd *sp = usalp->scmd;
SCSITaskDeviceInterface **sc = NULL;
SCSITaskInterface **cmd = NULL;
IOVirtualRange iov;
SCSI_Sense_Data senseData;
SCSITaskStatus status;
UInt64 bytesTransferred;
IOReturn ioReturnValue;
int ret = 0;
if (usalp->local == NULL) {
return (-1);
}
sc = usallocal(usalp)->scsiTaskDeviceInterface;
cmd = (*sc)->CreateSCSITask(sc);
if (cmd == NULL) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"Failed to create SCSI task");
ret = -1;
sp->error = SCG_FATAL;
sp->ux_errno = EIO;
goto out;
}
iov.address = (IOVirtualAddress) sp->addr;
iov.length = sp->size;
ioReturnValue = (*cmd)->SetCommandDescriptorBlock(cmd,
sp->cdb.cmd_cdb, sp->cdb_len);
if (ioReturnValue != kIOReturnSuccess) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"SetCommandDescriptorBlock failed with status %x",
ioReturnValue);
ret = -1;
goto out;
}
ioReturnValue = (*cmd)->SetScatterGatherEntries(cmd, &iov, 1, sp->size,
(sp->flags & SCG_RECV_DATA) ?
kSCSIDataTransfer_FromTargetToInitiator :
kSCSIDataTransfer_FromInitiatorToTarget);
if (ioReturnValue != kIOReturnSuccess) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"SetScatterGatherEntries failed with status %x",
ioReturnValue);
ret = -1;
goto out;
}
ioReturnValue = (*cmd)->SetTimeoutDuration(cmd, sp->timeout * 1000);
if (ioReturnValue != kIOReturnSuccess) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"SetTimeoutDuration failed with status %x",
ioReturnValue);
ret = -1;
goto out;
}
memset(&senseData, 0, sizeof (senseData));
seterrno(0);
ioReturnValue = (*cmd)->ExecuteTaskSync(cmd,
&senseData, &status, &bytesTransferred);
sp->resid = sp->size - bytesTransferred;
sp->error = SCG_NO_ERROR;
sp->ux_errno = geterrno();
if (ioReturnValue != kIOReturnSuccess) {
snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
"Command execution failed with status %x",
ioReturnValue);
sp->error = SCG_RETRYABLE;
ret = -1;
goto out;
}
memset(&sp->scb, 0, sizeof (sp->scb));
memset(&sp->u_sense.cmd_sense, 0, sizeof (sp->u_sense.cmd_sense));
if (senseData.VALID_RESPONSE_CODE != 0 || status == 0x02) {
/*
* There is no sense length - we need to asume that
* we always get 18 bytes.
*/
sp->sense_count = kSenseDefaultSize;
memmove(&sp->u_sense.cmd_sense, &senseData, kSenseDefaultSize);
if (sp->ux_errno == 0)
sp->ux_errno = EIO;
}
sp->u_scb.cmd_scb[0] = status;
/* ??? */
if (status == kSCSITaskStatus_No_Status) {
sp->error = SCG_RETRYABLE;
ret = -1;
goto out;
}
/*
* XXX Is it possible to have other senseful SCSI transport error codes?
*/
out:
if (cmd != NULL) {
(*cmd)->Release(cmd);
}
return (ret);
}