/*
* 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"
/*
* 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, uint32_t count, void *address);
long write_scsi_media(MEDIA m, long long offset, uint32_t 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;
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;
long
read_scsi_media(MEDIA m, long long offset, uint32_t count, void *address)
{
SCSI_MEDIA a;
long rtn_value;
long block;
long block_count;
long block_size;
uint8_t *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, uint32_t count, void *address)
{
SCSI_MEDIA a;
long rtn_value;
long block;
long block_count;
long block_size;
uint8_t *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;
}
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;
}
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;