/*
 * SCSI_media.c -
 *
 * Written by Eryk Vershen
 */

/*
 * Copyright 1997,1998 by Apple Computer, Inc.
 *              All Rights Reserved 
 *  
 * Permission to use, copy, modify, and distribute this software and 
 * its documentation for any purpose and without fee is hereby granted, 
 * provided that the above copyright notice appears in all copies and 
 * that both the copyright notice and this permission notice appear in 
 * supporting documentation. 
 *  
 * APPLE COMPUTER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE 
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE. 
 *  
 * IN NO EVENT SHALL APPLE COMPUTER BE LIABLE FOR ANY SPECIAL, INDIRECT, OR 
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT, 
 * NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
 */


// for printf() & sprintf()
#include <stdio.h>
// for malloc() & free()
#include <stdlib.h>
#include "DoSCSICommand.h"
#include "SCSI_media.h"
#include "util.h"


/*
 * Defines
 */
#define DriverRefNumToSCSI(x)  ((signed short) (~(x) - 32))


/*
 * Types
 */
typedef struct SCSI_media *SCSI_MEDIA;

struct SCSI_media {
    struct media    m;
    long            bus;
    long            id;
};

struct bus_entry {
    long    bus;
    long    sort_value;
    long    max_id;
    long    master_id;
};

struct SCSI_manager {
    long        exists;
    long        kind;
    long        bus_count;
    struct bus_entry *bus_list;
};

typedef struct SCSI_media_iterator *SCSI_MEDIA_ITERATOR;

struct SCSI_media_iterator {
    struct media_iterator   m;
    long                    bus_index;
    long                    bus;
    long                    id;
};

struct linux_order_cache {
    struct cache_item *first;
    struct cache_item *last;
    long next_disk;
    long next_cdrom;
    long loaded;
};

struct cache_item {
    struct cache_item *next;
    long bus;
    long id;
    long value;
    long is_cdrom;
    long unsure;
};


/*
 * Global Constants
 */
enum {
    kNoDevice = 0x00FF
};

enum {
    kRequiredSCSIinquiryLength = 36
};


/*
 * Global Variables
 */
static long scsi_inited = 0;
static struct SCSI_manager scsi_mgr;
static struct linux_order_cache linux_order;


/*
 * Forward declarations
 */
int AsyncSCSIPresent(void);
void scsi_init(void);
SCSI_MEDIA new_scsi_media(void);
long read_scsi_media(MEDIA m, long long offset, unsigned long count, void *address);
long write_scsi_media(MEDIA m, long long offset, unsigned long count, void *address);
long close_scsi_media(MEDIA m);
long os_reload_scsi_media(MEDIA m);
long compute_id(long bus, long device);
int SCSI_ReadBlock(UInt32 id, UInt32 bus, UInt32 block_size, UInt32 block, UInt8 *address);
int SCSI_WriteBlock(UInt32 id, UInt32 bus, UInt32 block_size, UInt32 block, UInt8 *address);
int DoTestUnitReady(UInt8 targetID, int bus);
int DoReadCapacity(UInt32 id, UInt32 bus, UInt32 *blockCount, UInt32 *blockSize);
SCSI_MEDIA_ITERATOR new_scsi_iterator(void);
void reset_scsi_iterator(MEDIA_ITERATOR m);
char *step_scsi_iterator(MEDIA_ITERATOR m);
void delete_scsi_iterator(MEDIA_ITERATOR m);
void fill_bus_entry(struct bus_entry *entry, long bus);
/*long get_bus_sort_value(long bus);*/
int bus_entry_compare(const void* a, const void* b);
int DoInquiry(UInt32 id, UInt32 bus, UInt32 *devType);
void probe_all(void);
void probe_scsi_device(long bus, long id, int unsure);
long lookup_scsi_device(long bus, long id, int *is_cdrom, int *unsure);
long lookup_scsi_index(long index, int is_cdrom, long *bus, long *id);
void add_to_cache(long bus, long id, int is_cdrom, int unsure);
void init_linux_cache(void);
void clear_linux_cache(void);
void mark_linux_cache_loaded(void);
int linux_cache_loaded(void);


/*
 * Routines
 */
int
AsyncSCSIPresent(void)
{
    return (TrapAvailable(_SCSIAtomic));
}


void
scsi_init(void)
{
    int i;
    int old_scsi;
    
    if (scsi_inited != 0) {
	return;
    }
    scsi_inited = 1;
    
    scsi_mgr.exists = 1;
    scsi_mgr.kind = allocate_media_kind();

    if (AsyncSCSIPresent()) {
	AllocatePB();

	scsi_mgr.bus_count = gSCSIHiBusID + 1;
	old_scsi = 0;
    } else {
	scsi_mgr.bus_count = 1;
	old_scsi = 1;
    }
    
    scsi_mgr.bus_list = (struct bus_entry *)
	    calloc(scsi_mgr.bus_count, sizeof(struct bus_entry));
	    
    if (scsi_mgr.bus_list == 0) {
	scsi_mgr.bus_count = 0;
    } else {
	for (i = 0; i < scsi_mgr.bus_count; i++) {
	    if (old_scsi) {
		scsi_mgr.bus_list[i].bus = 0xFF;
	    } else {
		scsi_mgr.bus_list[i].bus = i;
	    }
	    fill_bus_entry(&scsi_mgr.bus_list[i], i);
	}
	qsort((void *)scsi_mgr.bus_list,    /* address of array */
		scsi_mgr.bus_count,         /* number of elements */
		sizeof(struct bus_entry),   /* size of element */
		bus_entry_compare);         /* element comparison routine */
    }
    
    init_linux_cache();
}

void
fill_bus_entry(struct bus_entry *entry, long bus)
{
    OSErr           status;
    SCSIBusInquiryPB    pb;
    long len;
    long result;
    long x, y;

    if (!AsyncSCSIPresent()) {
    	entry->sort_value = 0;
	entry->max_id = 7;
	entry->master_id = 7;
	return;
    }
    len = sizeof(SCSIBusInquiryPB);
    clear_memory((Ptr) &pb, len);
    pb.scsiPBLength = len;
    pb.scsiFunctionCode = SCSIBusInquiry;
    pb.scsiDevice.bus = bus;
    status = SCSIAction((SCSI_PB *) &pb);
    if (status != noErr) {
	result = 6;
    } else {
	switch (pb.scsiHBAslotType) {
	case scsiMotherboardBus:    x = 0; break;
	case scsiPDSBus:            x = 1; break;
	case scsiNuBus:             x = 2; break;
	case scsiPCIBus:            x = 3; break;
	case scsiFireWireBridgeBus: x = 4; break;
	case scsiPCMCIABus:         x = 5; break;
	default:                    x = 7 + pb.scsiHBAslotType; break;
	};
	
	switch (pb.scsiFeatureFlags & scsiBusInternalExternalMask) {
	case scsiBusInternal:                   y = 0; break;
	case scsiBusInternalExternal:           y = 1; break;
	case scsiBusExternal:                   y = 2; break;
	default:
	case scsiBusInternalExternalUnknown:    y = 3; break;
	};
	result = x * 4 + y;
    }
    entry->sort_value = result;
    entry->max_id = pb.scsiMaxLUN;
    entry->master_id = pb.scsiInitiatorID;
}


int
bus_entry_compare(const void* a, const void* b)
{
    long result;
    
    const struct bus_entry *x = (const struct bus_entry *) a;
    const struct bus_entry *y = (const struct bus_entry *) b;
    
    result = x->sort_value - y->sort_value;
    if (result == 0) {
	result = x->bus - y->bus;
    }
    return result;
}


SCSI_MEDIA
new_scsi_media(void)
{
    return (SCSI_MEDIA) new_media(sizeof(struct SCSI_media));
}


MEDIA
open_old_scsi_as_media(long device)
{
    return open_scsi_as_media(kOriginalSCSIBusAdaptor, device);
}


MEDIA
open_scsi_as_media(long bus, long device)
{
    SCSI_MEDIA  a;
    UInt32 blockCount;
    UInt32 blockSize;
    
    if (scsi_inited == 0) {
	scsi_init();
    }
    
    if (scsi_mgr.exists == 0) {
	return 0;
    }

    a = 0;
    if (DoTestUnitReady(device, bus) > 0) {
	if (DoReadCapacity(device, bus, &blockCount, &blockSize) != 0) {
	    a = new_scsi_media();
	    if (a != 0) {
		a->m.kind = scsi_mgr.kind;
		a->m.grain = blockSize;
		a->m.size_in_bytes = ((long long)blockCount) * blockSize;
		a->m.do_read = read_scsi_media;
		a->m.do_write = write_scsi_media;
		a->m.do_close = close_scsi_media;
		a->m.do_os_reload = os_reload_scsi_media;
		a->bus = bus;
		a->id = device;
	    }
	}
    }
    return (MEDIA) a;
}


long
read_scsi_media(MEDIA m, long long offset, unsigned long count, void *address)
{
    SCSI_MEDIA a;
    long rtn_value;
    long block;
    long block_count;
    long block_size;
    unsigned char *buffer;
    int i;
    
    block = (long) offset;
//printf("scsi %d count %d\n", block, count);
    a = (SCSI_MEDIA) m;
    rtn_value = 0;
    if (a == 0) {
	/* no media */
    } else if (a->m.kind != scsi_mgr.kind) {
	/* wrong kind - XXX need to error here - this is an internal problem */
    } else if (count <= 0 || count % a->m.grain != 0) {
	/* can't handle size */
    } else if (offset < 0 || offset % a->m.grain != 0) {
	/* can't handle offset */
    } else if (offset + count > a->m.size_in_bytes) {
	/* check for offset (and offset+count) too large */
    } else {
	/* XXX do a read on the physical device */
	block_size = a->m.grain;
	block = offset / block_size;
	block_count = count / block_size;
	buffer = address;
	rtn_value = 1;
	for (i = 0; i < block_count; i++) {
	    if (SCSI_ReadBlock(a->id, a->bus, block_size, block, buffer) == 0) {
		rtn_value = 0;
		break;
	    }
	    buffer += block_size;
	    block += 1;
	}
    }
    return rtn_value;
}


long
write_scsi_media(MEDIA m, long long offset, unsigned long count, void *address)
{
    SCSI_MEDIA a;
    long rtn_value;
    long block;
    long block_count;
    long block_size;
    unsigned char *buffer;
    int i;
    
    a = (SCSI_MEDIA) m;
    rtn_value = 0;
    if (a == 0) {
	/* no media */
    } else if (a->m.kind != scsi_mgr.kind) {
	/* XXX need to error here - this is an internal problem */
    } else if (count <= 0 || count % a->m.grain != 0) {
	/* can't handle size */
    } else if (offset < 0 || offset % a->m.grain != 0) {
	/* can't handle offset */
    } else if (offset + count > a->m.size_in_bytes) {
	/* check for offset (and offset+count) too large */
    } else {
	/* XXX do a write on the physical device */
	block_size = a->m.grain;
	block = offset / block_size;
	block_count = count / block_size;
	buffer = address;
	rtn_value = 1;
	for (i = 0; i < block_count; i++) {
	    if (SCSI_WriteBlock(a->id, a->bus, block_size, block, buffer) == 0) {
		rtn_value = 0;
		break;
	    }
	    buffer += block_size;
	    block += 1;
	}
    }
    return rtn_value;
}


long
close_scsi_media(MEDIA m)
{
    SCSI_MEDIA a;
    
    a = (SCSI_MEDIA) m;
    if (a == 0) {
	return 0;
    } else if (a->m.kind != scsi_mgr.kind) {
	/* XXX need to error here - this is an internal problem */
	return 0;
    }
    /* XXX nothing to do - I think? */
    return 1;
}


long
os_reload_scsi_media(MEDIA m)
{
    printf("Reboot your system so the partition table will be reread.\n");
    return 1;
}


#pragma mark -


int
DoTestUnitReady(UInt8 targetID, int bus)
{
    OSErr                   status;
    Str255                  errorText;
    char*       msg;
    static const SCSI_6_Byte_Command gTestUnitReadyCommand = {
	kScsiCmdTestUnitReady, 0, 0, 0, 0, 0
    };
    SCSI_Sense_Data         senseData;
    DeviceIdent scsiDevice;
    int rtn_value;

    scsiDevice.diReserved = 0;
    scsiDevice.bus = bus;
    scsiDevice.targetID = targetID;
    scsiDevice.LUN = 0;

    status = DoSCSICommand(
		scsiDevice,
		"\pTest Unit Ready",
		(SCSI_CommandPtr) &gTestUnitReadyCommand,
		NULL,
		0,
		scsiDirectionNone,
		NULL,
		&senseData,
		errorText
		);
    if (status == scsiNonZeroStatus) {
	rtn_value = -1;
    } else if (status != noErr) {
	rtn_value = 0;
    } else {
	rtn_value = 1;
    }
    return rtn_value;
}


int
SCSI_ReadBlock(UInt32 id, UInt32 bus, UInt32 block_size, UInt32 block, UInt8 *address)
{
    OSErr                   status;
    Str255                  errorText;
    char*       msg;
    static SCSI_10_Byte_Command gReadCommand = {
	kScsiCmdRead10, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };
    SCSI_Sense_Data         senseData;
    DeviceIdent scsiDevice;
    int rtn_value;
    long count;

//printf("scsi read %d:%d block %d size %d\n", bus, id, block, block_size);
    scsiDevice.diReserved = 0;
    scsiDevice.bus = bus;
    scsiDevice.targetID = id;
    scsiDevice.LUN = 0;

    gReadCommand.lbn4 = (block >> 24) & 0xFF;
    gReadCommand.lbn3 = (block >> 16) & 0xFF;
    gReadCommand.lbn2 = (block >> 8) & 0xFF;
    gReadCommand.lbn1 = block & 0xFF;

    count = 1;
    gReadCommand.len2 = (count >> 8) & 0xFF;
    gReadCommand.len1 = count & 0xFF;

    status = DoSCSICommand(
		scsiDevice,
		"\pRead",
		(SCSI_CommandPtr) &gReadCommand,
		(Ptr) address,
		count * block_size,
		scsiDirectionIn,
		NULL,
		&senseData,
		errorText
	);
    if (status == noErr) {
	rtn_value = 1;
    } else {
	rtn_value = 0;
    }
    return rtn_value;
}


int
SCSI_WriteBlock(UInt32 id, UInt32 bus, UInt32 block_size, UInt32 block, UInt8 *address)
{
    OSErr                   status;
    Str255                  errorText;
    char*       msg;
    static SCSI_10_Byte_Command gWriteCommand = {
	kScsiCmdWrite10, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };
    SCSI_Sense_Data         senseData;
    DeviceIdent scsiDevice;
    int rtn_value;
    long count;

    scsiDevice.diReserved = 0;
    scsiDevice.bus = bus;
    scsiDevice.targetID = id;
    scsiDevice.LUN = 0;

    gWriteCommand.lbn4 = (block >> 24) & 0xFF;
    gWriteCommand.lbn3 = (block >> 16) & 0xFF;
    gWriteCommand.lbn2 = (block >> 8) & 0xFF;
    gWriteCommand.lbn1 = block & 0xFF;

    count = 1;
    gWriteCommand.len2 = (count >> 8) & 0xFF;
    gWriteCommand.len1 = count & 0xFF;

    status = DoSCSICommand(
		scsiDevice,
		"\pWrite",
		(SCSI_CommandPtr) &gWriteCommand,
		(Ptr) address,
		count * block_size,
		scsiDirectionOut,
		NULL,
		&senseData,
		errorText
	);
    if (status == noErr) {
	rtn_value = 1;
    } else {
	rtn_value = 0;
    }
    return rtn_value;
}


int
DoReadCapacity(UInt32 id, UInt32 bus, UInt32 *blockCount, UInt32 *blockSize)
{
    OSErr       status;
    Str255      errorText;
    static const SCSI_10_Byte_Command gCapacityCommand = {
	kScsiCmdReadCapacity, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };
    SCSI_Sense_Data senseData;
    DeviceIdent     scsiDevice;
    SCSI_Capacity_Data  capacityData;
    UInt32      temp;
    int rtn_value;

    scsiDevice.diReserved = 0;
    scsiDevice.bus = bus;
    scsiDevice.targetID = id;
    scsiDevice.LUN = 0;

    CLEAR(capacityData);

    status = DoSCSICommand(
		scsiDevice,
		"\pRead Capacity",
		(SCSI_CommandPtr) &gCapacityCommand,
		(Ptr) &capacityData,
		sizeof (SCSI_Capacity_Data),
		scsiDirectionIn,
		NULL,
		&senseData,
		errorText
		);

    if (status == noErr) {
	temp = capacityData.lbn4;
	temp = (temp << 8) | capacityData.lbn3;
	temp = (temp << 8) | capacityData.lbn2;
	temp = (temp << 8) | capacityData.lbn1;
	*blockCount = temp;

	temp = capacityData.len4;
	temp = (temp << 8) | capacityData.len3;
	temp = (temp << 8) | capacityData.len2;
	temp = (temp << 8) | capacityData.len1;
	*blockSize = temp;

	rtn_value = 1;
    } else {
	rtn_value = 0;
    }
    return rtn_value;
}


int
DoInquiry(UInt32 id, UInt32 bus, UInt32 *devType)
{
    OSErr       status;
    Str255      errorText;
    static const SCSI_6_Byte_Command gInquiryCommand = {
	kScsiCmdInquiry, 0, 0, 0, kRequiredSCSIinquiryLength, 0
    };
    SCSI_Sense_Data senseData;
    DeviceIdent     scsiDevice;
    SCSI_Inquiry_Data  inquiryData;
    UInt32      temp;
    int rtn_value;

    scsiDevice.diReserved = 0;
    scsiDevice.bus = bus;
    scsiDevice.targetID = id;
    scsiDevice.LUN = 0;

    CLEAR(inquiryData);

    status = DoSCSICommand(
		scsiDevice,
		"\pInquiry",
		(SCSI_CommandPtr) &gInquiryCommand,
		(Ptr) &inquiryData,
		kRequiredSCSIinquiryLength,
		scsiDirectionIn,
		NULL,
		&senseData,
		errorText
		);

    if (status == noErr) {
	*devType = inquiryData.devType & kScsiDevTypeMask;
	rtn_value = 1;
    } else {
	rtn_value = 0;
    }
    return rtn_value;
}


MEDIA
SCSI_FindDevice(long dRefNum)
{
    SCSIDriverPB            pb;
    OSErr                   status;
    short                   targetID;
    
    status = nsvErr;
    if (AsyncSCSIPresent()) {
	clear_memory((Ptr) &pb, sizeof pb);
	
	pb.scsiPBLength = sizeof (SCSIDriverPB);
	pb.scsiCompletion = NULL;
	pb.scsiFlags = 0;
	pb.scsiFunctionCode = SCSILookupRefNumXref;
	pb.scsiDevice.bus = kNoDevice;  /* was *((long *) &pb.scsiDevice) = 0xFFFFFFFFL; */
	
	do {
	    status = SCSIAction((SCSI_PB *) &pb);
	    
	    if (status != noErr) {
		break;
	    } else if (pb.scsiDriver == dRefNum
		    && pb.scsiDevice.bus != kNoDevice) {
		return open_scsi_as_media(pb.scsiDevice.bus, pb.scsiDevice.targetID);

	    } else {
		pb.scsiDevice = pb.scsiNextDevice;
	    }
	}
	while (pb.scsiDevice.bus != kNoDevice);
    }
    if (status == nsvErr) {
	/*
	 * The asynchronous SCSI Manager is missing or the
	 * driver didn't register with the SCSI Manager.*/
	targetID = DriverRefNumToSCSI(dRefNum);
	if (targetID >= 0 && targetID <= 6) {
	    return open_old_scsi_as_media(targetID);
	}
    }
     return 0;
}


#pragma mark -


SCSI_MEDIA_ITERATOR
new_scsi_iterator(void)
{
    return (SCSI_MEDIA_ITERATOR) new_media_iterator(sizeof(struct SCSI_media_iterator));
}


MEDIA_ITERATOR
create_scsi_iterator(void)
{
    SCSI_MEDIA_ITERATOR a;
    
    if (scsi_inited == 0) {
	scsi_init();
    }
    
    if (scsi_mgr.exists == 0) {
	return 0;
    }

    a = new_scsi_iterator();
    if (a != 0) {
	a->m.kind = scsi_mgr.kind;
	a->m.state = kInit;
	a->m.do_reset = reset_scsi_iterator;
	a->m.do_step = step_scsi_iterator;
	a->m.do_delete = delete_scsi_iterator;
	a->bus_index = 0;
	a->bus = 0;
	a->id = 0;
    }

    return (MEDIA_ITERATOR) a;
}


void
reset_scsi_iterator(MEDIA_ITERATOR m)
{
    SCSI_MEDIA_ITERATOR a;
    
    a = (SCSI_MEDIA_ITERATOR) m;
    if (a == 0) {
	/* no media */
    } else if (a->m.kind != scsi_mgr.kind) {
	/* wrong kind - XXX need to error here - this is an internal problem */
    } else if (a->m.state != kInit) {
	a->m.state = kReset;
    }
}


char *
step_scsi_iterator(MEDIA_ITERATOR m)
{
    SCSI_MEDIA_ITERATOR a;
    char *result;
    
    a = (SCSI_MEDIA_ITERATOR) m;
    if (a == 0) {
	/* no media */
    } else if (a->m.kind != scsi_mgr.kind) {
	/* wrong kind - XXX need to error here - this is an internal problem */
    } else {
	switch (a->m.state) {
	case kInit:
	    /* find # of buses - done in AllocatePB() out of scsi_init() */
	    a->m.state = kReset;
	    /* fall through to reset */
	case kReset:
	    a->bus_index = 0 /* first bus id */;
	    a->bus = scsi_mgr.bus_list[a->bus_index].bus;
	    a->id = 0 /* first device id */;
	    a->m.state = kIterating;
	    clear_linux_cache();
	    /* fall through to iterate */
	case kIterating:
	    while (1) {
		if (a->bus_index >= scsi_mgr.bus_count /* max bus id */) {
		    break;
		}
		if (a->id == scsi_mgr.bus_list[a->bus_index].master_id) {
		    /* next id */
		    a->id += 1;
		}
		if (a->id > scsi_mgr.bus_list[a->bus_index].max_id) {
		    a->bus_index += 1;
		    a->bus = scsi_mgr.bus_list[a->bus_index].bus;
		    a->id = 0 /* first device id */;
		    continue;   /* try again */
		}
		/* generate result */
		result = (char *) malloc(20);
		if (result != NULL) {
		    if (a->bus == 0xFF) {
			snprintf(result, 20, "/dev/scsi%c", '0'+a->id);
			probe_scsi_device(a->bus, a->id, 1);
		    } else {
			// insure bus number in range
			if (a->bus > 9) {
			    free(result);
			    result = NULL;
			    break;
			}
			snprintf(result, 20, "/dev/scsi%c.%c",
			    '0'+a->bus, '0'+a->id);
			/* only probe out of iterate; so always added in order. */
			probe_scsi_device(a->bus, a->id, 0);
		    }
		}

		a->id += 1; /* next id */
		return result;
	    }
	    a->m.state = kEnd;
	    /* fall through to end */
	case kEnd:
	    mark_linux_cache_loaded();
	default:
	    break;
	}
    }
    return 0 /* no entry */;
}


void
delete_scsi_iterator(MEDIA_ITERATOR m)
{
    return;
}


#pragma mark -


MEDIA
open_linux_scsi_as_media(long index, int is_cdrom)
{
    MEDIA m;
    long bus;
    long id;
    
    if (lookup_scsi_index(index, is_cdrom, &bus, &id) > 0) {
	m = open_scsi_as_media(bus, id);
    } else {
	m = 0;
    }
    
    return m;
}


char *
linux_old_scsi_name(long id)
{
    linux_scsi_name(kOriginalSCSIBusAdaptor, id);
}


char *
linux_scsi_name(long bus, long id)
{
    char *result = 0;
    long value;
    int is_cdrom;
    int unsure;
    char *suffix;

    /* name is sda, sdb, sdc, ...
     * in order by buses and ids, but only count responding devices ...
     */
    if ((value = lookup_scsi_device(bus, id, &is_cdrom, &unsure)) >= 0) {
	result = (char *) malloc(20);
	if (result != NULL) {
	    if (unsure) {
		suffix = " ?";
	    } else {
		suffix = "";
	    }
	    if (is_cdrom) {
		if (value > 9) {
		    // too many CD's, give up
		    free(result); result = NULL;
		} else {
		    snprintf(result, 20, "/dev/scd%c%s", '0' + value, suffix);
		}
	    } else {
		if (value < 26) {
		    snprintf(result, 20, "/dev/sd%c%s", 'a' + value, suffix);
		} else {
		    snprintf(result, 20, "/dev/sd%c%c%s",
			    'a' + value / 26, 'a' + value % 26, suffix);
		}
	    }
	}
    }
    return result;
}


void
probe_all(void)
{
    MEDIA_ITERATOR iter;
    char *name;

    iter = create_scsi_iterator();
    if (iter == 0) {
	return;
    }

    printf("finding devices ");
    fflush(stdout);
    while ((name = step_media_iterator(iter)) != 0) {
    	/* step does the probe for us */
	printf(".");
	fflush(stdout);
	free(name);
    }
    delete_media_iterator(iter);
    printf("\n");
    fflush(stdout);
}


void
probe_scsi_device(long bus, long id, int unsure)
{
    UInt32 devType;
    
    if (DoInquiry(id, bus, &devType)) {
    	if (devType == kScsiDevTypeDirect
    		|| devType == kScsiDevTypeOptical) {
    	    add_to_cache(bus, id, 0, unsure);
    	} else if (devType == kScsiDevTypeCDROM
		|| devType == kScsiDevTypeWorm) {
    	    add_to_cache(bus, id, 1, unsure);
    	}
    }
}


long
lookup_scsi_device(long bus, long id, int *is_cdrom, int *unsure)
{
    /* walk down list looking for bus and id ?
     *
     * only probe out of iterate (so always add in order)
     * reset list if we reset the iterate
     */
    struct cache_item *item;
    struct cache_item *next;
    long result = -1;
    int count = 0;
    
    if (scsi_inited == 0) {
	scsi_init();
    }

    while (1) {
    	count++;
	for (item = linux_order.first; item != NULL; item = item->next) {
	    if (item->bus == bus && item->id == id) {
		result = item->value;
		*is_cdrom = item->is_cdrom;
		*unsure = item->unsure;
		break;
	    }
	}
	if (count < 2 && result < 0) {
	    probe_all();
	} else {
	    break;
	}
    };

    return result;
}


/*
 * This has the same structure as lookup_scsi_device() except we are
 * matching on the value & type rather than the bus & id.
 */
long
lookup_scsi_index(long index, int is_cdrom, long *bus, long *id)
{
    struct cache_item *item;
    struct cache_item *next;
    long result = 0;
    int count = 0;
    
    if (scsi_inited == 0) {
	scsi_init();
    }

    while (1) {
    	count++;
	for (item = linux_order.first; item != NULL; item = item->next) {
	    if (item->value == index && item->is_cdrom == is_cdrom
		    && item->unsure == 0) {
		result = 1;
		*bus = item->bus;
		*id = item->id;
		break;
	    }
	}
	if (count < 2 && result == 0 && !linux_cache_loaded()) {
	    probe_all();
	} else {
	    break;
	}
    };

    return result;
}


void
add_to_cache(long bus, long id, int is_cdrom, int unsure)
{
    struct cache_item *item;
    
    item = malloc(sizeof(struct cache_item));
    if (item == NULL) {
	return;
    } else {
	item->bus = bus;
	item->id = id;
	item->is_cdrom = is_cdrom;
	item->unsure = unsure;
	if (is_cdrom) {
	    item->value = linux_order.next_cdrom;
	    linux_order.next_cdrom++;
	} else {
	    item->value = linux_order.next_disk;
	    linux_order.next_disk++;
	}
	item->next = 0;
    }
    if (linux_order.first == NULL) {
	linux_order.first = item;
	linux_order.last = item;
    } else {
	linux_order.last->next = item;
	linux_order.last = item;
    }
}


void
init_linux_cache(void)
{
    linux_order.first = NULL;
    clear_linux_cache();
}


void
clear_linux_cache(void)
{
    struct cache_item *item;
    struct cache_item *next;
    
    for (item = linux_order.first; item != NULL; item = next) {
	next = item->next;
	free(item);
    }
    /* back to starting value */
    linux_order.first = NULL;
    linux_order.last = NULL;
    linux_order.next_disk = 0;
    linux_order.next_cdrom = 0;
    linux_order.loaded = 0;
}


void
mark_linux_cache_loaded(void)
{
    linux_order.loaded = 1;
}


int
linux_cache_loaded(void)
{
    return linux_order.loaded;
}
