/*
* ATA_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()
#include <stdio.h>
// for malloc() & free()
#include <stdlib.h>
#include <ATA.h>
// for SCSI command structures
#include "MacSCSICommand.h"
#include "ATA_media.h"
#include "util.h"


/*
* Defines
*/
#define RESULT_OFFSET(type) \
   ((sizeof(type) == 1) ? 3 : ((sizeof(type) == 2) ? 1 : 0))
#define TBTrapTableAddress(trapNum) (((trapNum & 0x03FF) << 2) + 0xE00)
#define SWAP_SHORTS(x)  ((((x) & 0xFFFF) << 16) | (((x) >> 16) & 0xFFFF))
#define LBA_CAPABLE 0x0200


/*
* Types
*/
typedef struct ATA_info *ATA_INFO;

struct ATA_info {
   long            lba;
   long            heads;
   long            sectors;
};

typedef struct ATA_media *ATA_MEDIA;

struct ATA_media {
   struct media    m;
   long            id;
   struct ATA_info info;
};

struct ATA_manager {
   long        exists;
   long        kind;
   struct {
       char    major;
       char    minor;
   } version;
   short       busCount;
   long        *bus_list;
};

typedef struct ATA_media_iterator *ATA_MEDIA_ITERATOR;

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

struct ATA_identify_drive_info {        /* word */
   uint16_t  config_bits;        /*  0 */
   uint16_t  num_cylinders;      /*  1 */
   uint16_t  reserved2;          /*  2 */
   uint16_t  num_heads;          /*  3 */
   uint16_t  bytes_per_track;    /*  4 */
   uint16_t  bytes_per_sector;   /*  5 */
   uint16_t  sectors_per_track;  /*  6 */
   uint16_t  vendor7[3];         /*  7-9 */
   char            serial_number[20];  /* 10-19 */
   uint16_t  buffer_type;        /* 20 */
   uint16_t  buffer_size;        /* 21 */
   uint16_t  num_of_ecc_bytes;   /* 22 */
   char            firmware_rev[8];    /* 23-26 */
   char            model_number[40];   /* 27-46 */
   uint16_t  word47;             /* 47 */
   uint16_t  double_word_io;     /* 48 */
   uint16_t  capabilities;       /* 49 */
   uint16_t  reserved50;         /* 50 */
   uint16_t  pio_timing;         /* 51 */
   uint16_t  dma_timing;         /* 52 */
   uint16_t  current_is_valid;   /* 53 */
   uint16_t  cur_cylinders;      /* 54 */
   uint16_t  cur_heads;          /* 55 */
   uint16_t  cur_sec_per_track;  /* 56 */
   uint32_t   total_sectors;      /* 57-58 */
   uint16_t  multiple_sectors;   /* 59 */
   uint32_t   lba_sectors;        /* 60-61 */
   uint16_t  singleword_dma;     /* 62 */
   uint16_t  multiword_dma;      /* 63 */
   uint16_t  reserved64[64];     /* 64-127 */
   uint16_t  vendor128[32];      /* 128-159 */
   uint16_t  reserved160[96];    /* 160-255 */
};

struct ATAPI_identify_drive_info {      /* word */
   uint16_t  config_bits;        /*  0 */
   uint16_t  retired1[9];        /*  1-9 */
   char            serial_number[20];  /* 10-19 */
   uint16_t  retired20[3];       /* 20-22 */
   char            firmware_rev[8];    /* 23-26 */
   char            model_number[40];   /* 27-46 */
   uint16_t  retired47[2];       /* 47-48 */
   uint16_t  capabilities;       /* 49 */
   uint16_t  reserved50;         /* 50 */
   uint16_t  pio_timing;         /* 51 */
   uint16_t  dma_timing;         /* 52 */
   uint16_t  current_is_valid;   /* 53 */
   uint16_t  retired54[8];       /* 54-61 */
   uint16_t  singleword_dma;     /* 62 */
   uint16_t  multiword_dma;      /* 63 */
   uint16_t  pio_transfer;       /* 64 */
   uint16_t  min_cycle_time;     /* 65 */
   uint16_t  rec_cycle_time;     /* 66 */
   uint16_t  min_wo_flow;        /* 67 */
   uint16_t  min_with_flow;      /* 68 */
   uint16_t  reserved69[2];      /* 69-70 */
   uint16_t  release_over;       /* 71 */
   uint16_t  release_service;    /* 72 */
   uint16_t  major_rev;          /* 73 */
   uint16_t  minor_rev;          /* 74 */
   uint16_t  reserved75[53];     /* 75-127 */
   uint16_t  vendor128[32];      /* 128-159 */
   uint16_t  reserved160[96];    /* 160-255 */
};

/* Identifies the bus protocol type. */
enum {
   kDevUnknown     =   0,
   kDevATA         =   1,
   kDevATAPI       =   2,
   kDevPCMCIA      =   3
};


/*
* Global Constants
*/
enum {
   kNoDevice = 0x00FF,
   kATAtimeout = 3000,
   kATAcmdATAPIPacket          = 0x00A0                        /* ATAPI packet command */
};


/*
* Global Variables
*/
static long ata_inited = 0;
static struct ATA_manager ata_mgr;

/*
* Forward declarations
*/
int ATAManagerPresent(void);
int ATAHardwarePresent(void);
pascal SInt16 ataManager(ataPB *pb);
void ata_init(void);
ATA_MEDIA new_ata_media(void);
long read_ata_media(MEDIA m, long long offset, uint32_t count, void *address);
long write_ata_media(MEDIA m, long long offset, uint32_t count, void *address);
long close_ata_media(MEDIA m);
long os_reload_ata_media(MEDIA m);
long compute_id(long bus, long device);
pascal SInt16 ataManager(ataPB *pb);
int ATA_ReadBlock(UInt32 deviceID, ATA_INFO info, UInt32 block_size, UInt32 block, UInt8 *address);
int ATA_WriteBlock(UInt32 deviceID, ATA_INFO info, UInt32 block_size, UInt32 block, UInt8 *address);
long get_info(long id, struct ATA_identify_drive_info *ip);
long get_pi_info(long id, struct ATAPI_identify_drive_info *ip);
long is_atapi(long id);
long read_atapi_media(MEDIA m, long long offset, uint32_t count, void *address);
long write_atapi_media(MEDIA m, long long offset, uint32_t count, void *address);
int ATAPI_ReadBlock(UInt32 deviceID, UInt32 block_size, UInt32 block, UInt8 *address);
int ATAPI_TestUnitReady(UInt32 deviceID);
int ATAPI_ReadCapacity(UInt32 deviceID, uint32_t *block_size, uint32_t *blocks);
ATA_MEDIA_ITERATOR new_ata_iterator(void);
void reset_ata_iterator(MEDIA_ITERATOR m);
char *step_ata_iterator(MEDIA_ITERATOR m);
void delete_ata_iterator(MEDIA_ITERATOR m);
int ata_bus_present(int num);


/*
* Routines
*/
#if GENERATINGPOWERPC
pascal SInt16
ataManager(ataPB *pb)
{
   #ifdef applec
       #if sizeof(SInt16) > 4
           #error "Result types larger than 4 bytes are not supported."
       #endif
   #endif
   long    private_result;

   private_result = CallUniversalProc(
       *(UniversalProcPtr*)TBTrapTableAddress(0xAAF1),
       kPascalStackBased
        | RESULT_SIZE(SIZE_CODE(sizeof(SInt16)))
        | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(pb))),
       pb);
   return *(((SInt16*)&private_result) + RESULT_OFFSET(SInt16));
}
#endif


int
ATAHardwarePresent(void)
{
   UInt16  configFlags;

   // Hardware configuration flags
   configFlags = LMGetHWCfgFlags();

   return ((configFlags & 0x0080) != 0);
}


int
ATAManagerPresent(void)
{
   if (ATAHardwarePresent()) {
       return (TrapAvailable(kATATrap));
   } else {
       return 0;
   }
}

void
ata_init(void)
{
   ataMgrInquiry   pb;
   OSErr           status;
   int i;
   int j;

   if (ata_inited != 0) {
       return;
   }
   ata_inited = 1;

   if (ATAManagerPresent() == 0) {
       ata_mgr.exists = 0;
       return;
   }

   ata_mgr.exists = 1;
   ata_mgr.kind = allocate_media_kind();

   clear_memory((void *)&pb, sizeof(pb));

   pb.ataPBFunctionCode =  kATAMgrManagerInquiry;
   pb.ataPBVers =          kATAPBVers1;

   status = ataManager((ataPB*) &pb );

   if (status != noErr) {
       ata_mgr.exists = 0;
       return;
   }
   ata_mgr.version.major = pb.ataMgrVersion.majorRev;
   ata_mgr.version.minor = pb.ataMgrVersion.minorAndBugRev >> 4;
   ata_mgr.busCount = pb.ataBusCnt;

   ata_mgr.bus_list = (long *) calloc(ata_mgr.busCount, sizeof(long));
   if (ata_mgr.bus_list == 0) {
       ata_mgr.busCount = 0;
   } else {
       for (i = 0, j = 0; j < ata_mgr.busCount; i++) {
           if (ata_bus_present(i)) {
               ata_mgr.bus_list[j] = i;
               j++;
           }
       }
   }
}


int
ata_bus_present(int num)
{
   ataBusInquiry   pb;
   OSErr           status;

   clear_memory((void *)&pb, sizeof(pb));

   pb.ataPBFunctionCode =  kATAMgrBusInquiry;
   pb.ataPBVers =          kATAPBVers1;
   pb.ataPBDeviceID =      num;

   status = ataManager((ataPB*) &pb );

   if (status == noErr) {
       return 1;
   } else {
       //printf("status = %d\n", status);
       return 0;
   }
}


ATA_MEDIA
new_ata_media(void)
{
   return (ATA_MEDIA) new_media(sizeof(struct ATA_media));
}


#pragma mark -


long
compute_id(long bus, long device)
{
   long id;
   int i;

   id = -1;
   for (i = 0; i < ata_mgr.busCount; i++) {
       if (bus == ata_mgr.bus_list[i]) {
           break;
       }
   }
   if (i >= ata_mgr.busCount) {
       /* bad bus id */
   } else if (ata_mgr.version.major < 3) {
       if (device != 0) {
           /* bad device id */
       } else {
           id = bus & 0xFF;
       }
   } else {
       if (device < 0 || device > 1) {
           /* bad device id */
       } else {
           id = ((device & 0xFF) << 8) | (bus & 0xFF);
       }
   }
   return id;
}


static long
get_info(long id, struct ATA_identify_drive_info *ip)
{
   ataIdentify     pb;
   ataDevConfiguration pb2;
   OSErr           status;
   long            rtn_value;
   long            atapi;

   if (sizeof(struct ATA_identify_drive_info) < 512) {
       return 0;
   }
   clear_memory((void *)ip, sizeof(struct ATA_identify_drive_info));

   clear_memory((void *)&pb, sizeof(pb));
   pb.ataPBFunctionCode    =   kATAMgrDriveIdentify;
   pb.ataPBVers            =   kATAPBVers1;
   pb.ataPBDeviceID        =   id;
   pb.ataPBFlags           =   mATAFlagIORead | mATAFlagByteSwap;
   pb.ataPBTimeOut         =   kATAtimeout;
   pb.ataPBBuffer          =   (void*) ip;

   status = ataManager((ataPB*) &pb );

   if (status != noErr) {
       //printf("get info status = %d\n", status);
       rtn_value = 0;
   } else {
       ip->total_sectors = SWAP_SHORTS(ip->total_sectors);
       ip->lba_sectors = SWAP_SHORTS(ip->lba_sectors);
       rtn_value = 1;
   }
   return rtn_value;
}


static long
is_atapi(long id)
{
   ataDevConfiguration pb;
   OSErr           status;
   long            atapi;

   atapi = 0;
   if (ata_mgr.version.major >= 2) {
       clear_memory((void *)&pb, sizeof(pb));
       pb.ataPBFunctionCode    =   kATAMgrGetDrvConfiguration;
       pb.ataPBVers            =   kATAPBVers2;
       pb.ataPBDeviceID        =   id;
       pb.ataPBTimeOut     =   kATAtimeout;

       status = ataManager((ataPB*) &pb );
       if (status != noErr) {
           //printf("is atatpi status = %d\n", status);
       } else if (pb.ataDeviceType == kDevATAPI) {
           atapi = 1;
           /* the drive can be asleep or something in which case this doesn't work */
           /* how do we do reads */
       }
   }
   return atapi;
}


MEDIA
open_ata_as_media(long bus, long device)
{
   ATA_MEDIA   a;
   long        id;
   struct ATA_identify_drive_info  info;
   uint8_t *buf;
   uint32_t total;

   if (ata_inited == 0) {
       ata_init();
   }

   if (ata_mgr.exists == 0) {
       //printf("ATA manager does not exist\n");
       return 0;
   }

   id = compute_id(bus, device);

   if (id < 0) {
       return 0;

   } else if (is_atapi(id)) {
       a = (ATA_MEDIA) open_atapi_as_media(bus, device);

   } else {
       a = 0;
       if (get_info(id, &info) != 0) {
           a = new_ata_media();
           if (a != 0) {
               a->m.kind = ata_mgr.kind;
               if ((info.capabilities & LBA_CAPABLE) != 0) {
                   total = info.lba_sectors;
                   a->info.lba = 1;
                   a->info.heads = 0;
                   a->info.sectors = 0;
               } else {
                   /* Only CHS - Cylinder Head Sector addressing */
                   total = info.total_sectors;
                   a->info.lba = 0;
                   a->info.heads = info.cur_heads;
                   a->info.sectors = info.cur_sec_per_track;
               }
               { /* XXX this should be a loop in a subroutine */
                   buf = malloc(2048);
                   if (ATA_ReadBlock(id, &a->info, 512, 0, buf)) {
                       a->m.grain = 512;
                   } else if (ATA_ReadBlock(id, &a->info, 1024, 0, buf)) {
                       a->m.grain = 1024;
                   } else if (ATA_ReadBlock(id, &a->info, 2048, 0, buf)) {
                       a->m.grain = 2048;
                   } else {
                       a->m.grain = 512; /* XXX should really return failure here */
                   }
                   free(buf);
               }
               if (total == 0) {
                   a->m.size_in_bytes = ((long long)1000) * a->m.grain; /* XXX not right */
               } else {
                   a->m.size_in_bytes = ((long long)total) * a->m.grain;
               }
               a->m.do_read = read_ata_media;
               a->m.do_write = write_ata_media;
               a->m.do_close = close_ata_media;
               a->m.do_os_reload = os_reload_ata_media;
               a->id = id;
           }
       } else {
           printf("ATA - couldn't get info\n");
       }
   }
   return (MEDIA) a;
}


long
read_ata_media(MEDIA m, long long offset, uint32_t count, void *address)
{
   ATA_MEDIA a;
   ataIOPB pb;
   OSErr       status;
   long rtn_value;
   long block;
   long block_count;
   long block_size;
   uint8_t *buffer;
   int i;

   a = (ATA_MEDIA) m;
   rtn_value = 0;
   if (a == 0) {
       /* no media */
   } else if (a->m.kind != ata_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 {
       /* 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 (ATA_ReadBlock(a->id, &a->info, block_size, block, buffer) == 0) {
               rtn_value = 0;
               break;
           }
           buffer += block_size;
           block += 1;
       }
   }
   return rtn_value;
}


long
write_ata_media(MEDIA m, long long offset, uint32_t count, void *address)
{
   ATA_MEDIA a;
   long rtn_value;
   long block;
   long block_count;
   long block_size;
   uint8_t *buffer;
   int i;

   a = (ATA_MEDIA) m;
   rtn_value = 0;
   if (a == 0) {
       /* no media */
   } else if (a->m.kind != ata_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 {
       /* 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 (ATA_WriteBlock(a->id, &a->info, block_size, block, buffer) == 0) {
               rtn_value = 0;
               break;
           }
           buffer += block_size;
           block += 1;
       }
   }
   return rtn_value;
}


long
close_ata_media(MEDIA m)
{
   ATA_MEDIA a;

   a = (ATA_MEDIA) m;
   if (a == 0) {
       return 0;
   } else if (a->m.kind != ata_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_ata_media(MEDIA m)
{
   printf("Reboot your system so the partition table will be reread.\n");
   return 1;
}


int
ATA_ReadBlock(UInt32 deviceID, ATA_INFO info, UInt32 block_size, UInt32 block, UInt8 *address)
{
   ataIOPB     pb;
   OSErr       status;
   long        slave;
   long        lba, cyl, head, sector;

   clear_memory((void *)&pb, sizeof(pb));
   pb.ataPBFunctionCode    =   kATAMgrExecIO;
   pb.ataPBVers            =   kATAPBVers1;
   pb.ataPBDeviceID        =   deviceID;
   pb.ataPBFlags           =   mATAFlagTFRead | mATAFlagIORead ;
   pb.ataPBTimeOut         =   kATAtimeout;

   pb.ataPBLogicalBlockSize =  block_size;
   pb.ataPBBuffer          =   address;
   pb.ataPBByteCount = block_size;
   if (info->lba) {
       lba = 0x40;
       sector = block & 0xFF;
       head = (block >> 24) & 0xF;
       cyl = (block >> 8) & 0xFFFF;
   } else {
       lba = 0x00;
       sector = (block % info->sectors) + 1;
       cyl = block / info->sectors;
       head = cyl % info->heads;
       cyl = cyl / info->heads;
   }

   pb.ataPBTaskFile.ataTFCount = 1;
   pb.ataPBTaskFile.ataTFSector = sector;
   pb.ataPBTaskFile.ataTFCylinder = cyl;
   if (deviceID & 0x0FF00) {
       slave = 0x10;
   } else {
       slave = 0x0;
   }
                             /* std | L/C  | Drive | head */
   pb.ataPBTaskFile.ataTFSDH = 0xA0 | lba | slave | head;
   pb.ataPBTaskFile.ataTFCommand = kATAcmdRead;

   status = ataManager((ataPB*) &pb );
   if (status != noErr) {
       /* failure */
       //printf(" ATA read status = %d\n", status);
       return 0;
   } else {
       return 1;
   }
}


int
ATA_WriteBlock(UInt32 deviceID, ATA_INFO info, UInt32 block_size, UInt32 block, UInt8 *address)
{
   ataIOPB     pb;
   OSErr       status;
   long        slave;
   long        lba, cyl, head, sector;

   clear_memory((void *)&pb, sizeof(pb));
   pb.ataPBFunctionCode    =   kATAMgrExecIO;
   pb.ataPBVers            =   kATAPBVers1;
   pb.ataPBDeviceID        =   deviceID;
   pb.ataPBFlags           =   mATAFlagTFRead | mATAFlagIOWrite ;
   pb.ataPBTimeOut         =   kATAtimeout;

   pb.ataPBLogicalBlockSize =  block_size;
   pb.ataPBBuffer          =   address;
   pb.ataPBByteCount = block_size;
   if (info->lba) {
       lba = 0x40;
       sector = block & 0xFF;
       head = (block >> 24) & 0xF;
       cyl = (block >> 8) & 0xFFFF;
   } else {
       lba = 0x00;
       sector = (block % info->sectors) + 1;
       cyl = block / info->sectors;
       head = cyl % info->heads;
       cyl = cyl / info->heads;
   }
   pb.ataPBTaskFile.ataTFCount = 1;
   pb.ataPBTaskFile.ataTFSector = sector;
   pb.ataPBTaskFile.ataTFCylinder = cyl;
   if (deviceID & 0x0FF00) {
       slave = 0x10;
   } else {
       slave = 0x0;
   }
                             /* std | L/C  | Drive | head */
   pb.ataPBTaskFile.ataTFSDH = 0xA0 | lba | slave | head;
   pb.ataPBTaskFile.ataTFCommand = kATAcmdWrite;

   status = ataManager((ataPB*) &pb );
   if (status != noErr) {
       /* failure */
       return 0;
   } else {
       return 1;
   }
}


#pragma mark -


/*
* ATAPI stuff
*/
static long
get_pi_info(long id, struct ATAPI_identify_drive_info *ip)
{
   ataIdentify     pb;
   OSErr           status;
   long            rtn_value;

   if (sizeof(struct ATAPI_identify_drive_info) < 512) {
       return 0;
   }
   clear_memory((void *)ip, sizeof(struct ATAPI_identify_drive_info));

   clear_memory((void *)&pb, sizeof(pb));
   pb.ataPBFunctionCode    =   kATAMgrDriveIdentify;
   pb.ataPBVers            =   kATAPBVers1;
   pb.ataPBDeviceID        =   id;
   pb.ataPBFlags           =   mATAFlagIORead | mATAFlagByteSwap | mATAFlagProtocol1;
   pb.ataPBTimeOut         =   kATAtimeout;
   pb.ataPBBuffer          =   (void*) ip;

   status = ataManager((ataPB*) &pb );

   if (status != noErr) {
       //printf("get pi info status = %d\n", status);
       rtn_value = 0;
   } else {
       rtn_value = 1;
   }
   return rtn_value;
}


MEDIA
open_atapi_as_media(long bus, long device)
{
   ATA_MEDIA   a;
   long        id;
   struct ATAPI_identify_drive_info    info;
   uint8_t *buf;
   uint32_t block_size;
   uint32_t blocks;

   if (ata_inited == 0) {
       ata_init();
   }

   if (ata_mgr.exists == 0) {
       return 0;
   }

   id = compute_id(bus, device);

   if (!is_atapi(id)) {
       a = 0;

   } else {
       a = 0;
       if (get_pi_info(id, &info) != 0
               && (info.capabilities & LBA_CAPABLE) != 0) {
           if (ATAPI_TestUnitReady(id) != 0) {
               a = new_ata_media();
               if (a != 0) {
                   a->m.kind = ata_mgr.kind;
                   if (ATAPI_ReadCapacity(id, &block_size, &blocks) == 0) {
                       block_size = 2048;
                       blocks = 1000;
                   }
                   a->m.grain = block_size;
                   a->m.size_in_bytes = ((long long)blocks) * a->m.grain;
                   a->m.do_read = read_atapi_media;
                   a->m.do_write = write_atapi_media;
                   a->m.do_close = close_ata_media;
                   a->m.do_os_reload = os_reload_ata_media;
                   a->id = id;
               }
           } else {
               printf("ATAPI - unit not ready\n");
           }
       } else {
           printf("ATAPI - couldn't get info or not LBA capable\n");
       }
   }
   return (MEDIA) a;
}


long
read_atapi_media(MEDIA m, long long offset, uint32_t count, void *address)
{
   ATA_MEDIA a;
   ataIOPB pb;
   OSErr       status;
   long rtn_value;
   long block;
   long block_count;
   long block_size;
   uint8_t *buffer;
   int i;

   a = (ATA_MEDIA) m;
   rtn_value = 0;
   if (a == 0) {
       /* no media */
   } else if (a->m.kind != ata_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 (ATAPI_ReadBlock(a->id, block_size, block, buffer) == 0) {
               rtn_value = 0;
               break;
           }
           buffer += block_size;
           block += 1;
       }
   }
   return rtn_value;
}


long
write_atapi_media(MEDIA m, long long offset, uint32_t count, void *address)
{
   return 0;
}


int
ATAPI_ReadBlock(UInt32 deviceID, UInt32 block_size, UInt32 block, UInt8 *address)
{
   ataIOPB         pb;
   OSErr           status;
   long            slave;
   ATAPICmdPacket  cmdPacket;
   SCSI_10_Byte_Command *gRead;
   long count;

   clear_memory((void *)&pb, sizeof(pb));
   pb.ataPBFunctionCode    =   kATAMgrExecIO;
   pb.ataPBVers            =   kATAPBVers1;
   pb.ataPBDeviceID        =   deviceID;
   pb.ataPBFlags           =   mATAFlagTFRead | mATAFlagIORead | mATAFlagProtocol1;
   pb.ataPBTimeOut         =   kATAtimeout;

   pb.ataPBBuffer          =   address;
   pb.ataPBByteCount = block_size;
   pb.ataPBTaskFile.ataTFCylinder = block_size;
   if (deviceID & 0x0FF00) {
       slave = 0x10;
   } else {
       slave = 0x0;
   }
                             /* std | L/C  | Drive | head */
   pb.ataPBTaskFile.ataTFSDH = 0xA0 | 0x40 | slave;
   pb.ataPBTaskFile.ataTFCommand = kATAcmdATAPIPacket;
   pb.ataPBPacketPtr = &cmdPacket;

   cmdPacket.atapiPacketSize = 16;
   clear_memory((void *)&cmdPacket.atapiCommandByte, 16);
   gRead = (SCSI_10_Byte_Command *) &cmdPacket.atapiCommandByte[0];

   gRead->opcode = kScsiCmdRead10;

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

   count = 1;
   gRead->len2 = (count >> 8) & 0xFF;
   gRead->len1 = count & 0xFF;


   status = ataManager((ataPB*) &pb );
   if (status != noErr) {
       /* failure */
       //printf("ATAPI read status = %d\n", status);
       return 0;
   } else {
       return 1;
   }
}


int
ATAPI_TestUnitReady(UInt32 deviceID)
{
   ataIOPB         pb;
   OSErr           status;
   long            slave;
   ATAPICmdPacket  cmdPacket;
   SCSI_10_Byte_Command *gTestUnit;

   clear_memory((void *)&pb, sizeof(pb));
   pb.ataPBFunctionCode    =   kATAMgrExecIO;
   pb.ataPBVers            =   kATAPBVers1;
   pb.ataPBDeviceID        =   deviceID;
   pb.ataPBFlags           =   mATAFlagTFRead | mATAFlagIORead | mATAFlagProtocol1;
   pb.ataPBTimeOut         =   kATAtimeout;

   if (deviceID & 0x0FF00) {
       slave = 0x10;
   } else {
       slave = 0x0;
   }
                             /* std | L/C  | Drive | head */
   pb.ataPBTaskFile.ataTFSDH = 0xA0 | 0x40 | slave;
   pb.ataPBTaskFile.ataTFCommand = kATAcmdATAPIPacket;
   pb.ataPBPacketPtr = &cmdPacket;

   cmdPacket.atapiPacketSize = 16;
   clear_memory((void *)&cmdPacket.atapiCommandByte, 16);
   gTestUnit = (SCSI_10_Byte_Command *) &cmdPacket.atapiCommandByte[0];

   gTestUnit->opcode = kScsiCmdTestUnitReady;


   status = ataManager((ataPB*) &pb );
   if (status != noErr) {
       /* failure */
       //printf("ATAPI test unit ready status = %d\n", status);
       return 0;
   } else {
       return 1;
   }
}


int
ATAPI_ReadCapacity(UInt32 deviceID, uint32_t *block_size, uint32_t *blocks)
{
   ataIOPB         pb;
   OSErr           status;
   long            slave;
   ATAPICmdPacket  cmdPacket;
   SCSI_10_Byte_Command *gReadCap;
   struct read_cap_data {
       long    addr;
       long    size;
   } rcd;

   clear_memory((void *)&pb, sizeof(pb));
   pb.ataPBFunctionCode    =   kATAMgrExecIO;
   pb.ataPBVers            =   kATAPBVers1;
   pb.ataPBDeviceID        =   deviceID;
   pb.ataPBFlags           =   mATAFlagTFRead | mATAFlagIORead | mATAFlagProtocol1;
   pb.ataPBTimeOut         =   kATAtimeout;

   pb.ataPBBuffer          =   (uint8_t *)&rcd;
   pb.ataPBByteCount = 8;
   pb.ataPBTaskFile.ataTFCylinder = 8;
   if (deviceID & 0x0FF00) {
       slave = 0x10;
   } else {
       slave = 0x0;
   }
                             /* std | L/C  | Drive | head */
   pb.ataPBTaskFile.ataTFSDH = 0xA0 | 0x40 | slave;
   pb.ataPBTaskFile.ataTFCommand = kATAcmdATAPIPacket;
   pb.ataPBPacketPtr = &cmdPacket;

   cmdPacket.atapiPacketSize = 16;
   clear_memory((void *)&cmdPacket.atapiCommandByte, 16);
   gReadCap = (SCSI_10_Byte_Command *) &cmdPacket.atapiCommandByte[0];

   gReadCap->opcode = kScsiCmdReadCapacity;


   status = ataManager((ataPB*) &pb );
   if (status != noErr) {
       /* failure */
       //printf("ATAPI read capacity status = %d\n", status);
       return 0;
   } else {
       *blocks = rcd.addr;
       *block_size = rcd.size;
       return 1;
   }
}


MEDIA
ATA_FindDevice(long dRefNum)
{
   ataDrvrRegister pb;
   OSErr       status;

   if (ATAManagerPresent()) {
       clear_memory((void *)&pb, sizeof(pb));

       pb.ataPBFunctionCode    =   kATAMgrFindDriverRefnum;
       pb.ataPBVers            =   kATAPBVers1;
       pb.ataPBDeviceID        =   0xFFFF;
       pb.ataPBTimeOut         =   kATAtimeout;

       pb.ataDeviceNextID = 1;
       do {
           status = ataManager((ataPB*) &pb);

           if (status != noErr) {
               break;
           } else if (pb.ataDrvrRefNum == dRefNum
                   && pb.ataPBDeviceID != kNoDevice) {
               return open_ata_as_media(pb.ataPBDeviceID & 0xFF,
                       (pb.ataPBDeviceID >> 8) & 0xFF);
           } else {
               pb.ataPBDeviceID = pb.ataDeviceNextID;
           }
       } while (pb.ataPBDeviceID != kNoDevice);
   }
   return 0;
}


#pragma mark -


ATA_MEDIA_ITERATOR
new_ata_iterator(void)
{
   return (ATA_MEDIA_ITERATOR) new_media_iterator(sizeof(struct ATA_media_iterator));
}


MEDIA_ITERATOR
create_ata_iterator(void)
{
   ATA_MEDIA_ITERATOR a;

   if (ata_inited == 0) {
       ata_init();
   }

   if (ata_mgr.exists == 0) {
       return 0;
   }

   a = new_ata_iterator();
   if (a != 0) {
       a->m.kind = ata_mgr.kind;
       a->m.state = kInit;
       a->m.do_reset = reset_ata_iterator;
       a->m.do_step = step_ata_iterator;
       a->m.do_delete = delete_ata_iterator;
       a->bus_index = 0;
       a->bus = 0;
       a->id = 0;
   }

   return (MEDIA_ITERATOR) a;
}


void
reset_ata_iterator(MEDIA_ITERATOR m)
{
   ATA_MEDIA_ITERATOR a;

   a = (ATA_MEDIA_ITERATOR) m;
   if (a == 0) {
       /* no media */
   } else if (a->m.kind != ata_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_ata_iterator(MEDIA_ITERATOR m)
{
   ATA_MEDIA_ITERATOR a;
   char *result;

   a = (ATA_MEDIA_ITERATOR) m;
   if (a == 0) {
       /* no media */
   } else if (a->m.kind != ata_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 ata_init) */
           a->m.state = kReset;
           /* fall through to reset */
       case kReset:
           a->bus_index = 0 /* low bus id */;
           a->bus = ata_mgr.bus_list[a->bus_index];
           a->id = 0 /* low device id */;
           a->m.state = kIterating;
           /* fall through to iterate */
       case kIterating:
           while (1) {
               if (a->bus_index >= ata_mgr.busCount/* max bus id */) {
                   break;
               }
               if (a->id > 1 /*max id for bus */) {
                   a->bus_index += 1;
                   a->bus = ata_mgr.bus_list[a->bus_index];
                   a->id = 0 /* low device id */;
                   continue;   /* try again */
               }
               if (a->bus > 9) {
                   // insure that name creation works
                   break;
               }
               /* generate result */
               result = (char *) malloc(20);
               if (result != NULL) {
                   snprintf(result, 20, "/dev/ata%c.%c",
                       '0'+a->bus, '0'+a->id);
               }

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


void
delete_ata_iterator(MEDIA_ITERATOR m)
{
   return;
}


#pragma mark -


#ifdef notdef
MEDIA
open_linux_ata_as_media(long index)
{
   long bus;
   long id;
   long i;

   i = index / 2;
   if (i >= ata_mgr.busCount) {
       // set bogus id
       bus = 0;
       id = 2;
   } else {
       bus = ata_mgr.bus_list[index / 2];
       id = index % 2;
   }

   return open_ata_as_media(bus, id);
}

#else

MEDIA
open_linux_ata_as_media(long index)
{
   long bus;
   long id;

   bus = index / 2;
   id = index % 2;

   return open_ata_as_media(bus, id);
}
#endif


char *
linux_ata_name(long bus, long id)
{
   char *result;

   if (bus >= 13) {
       // a bus >= 13 would be a bogus character
       return NULL;
   }
   result = (char *) malloc(20);
   if (result != NULL) {
       /* name is hda, hdb, hdc, hdd, ...
        * in order (0,0)  (0,1)  (1,0)  (1,1) ...
        */
       snprintf(result, 20, "/dev/hd%c", 'a' + (bus*2 + id));
   }
   return result;
}