lide.device/atapi.c
Matt Harlum c866b3d4be ATAPI mode sense/select translation
* Ensure data is not too short and not too long
* Ensure memory is freed on error paths
* Copy required mode header fields into the correct locations
2025-04-26 08:03:03 +00:00

1368 lines
40 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/* This file is part of lide.device
* Copyright (C) 2023 Matthew Harlum <matt@harlum.net>
*/
#include <devices/scsidisk.h>
#include <devices/timer.h>
#include <devices/trackdisk.h>
#include <inline/timer.h>
#include <proto/exec.h>
#include <proto/expansion.h>
#include <exec/errors.h>
#include <exec/execbase.h>
#include <stdbool.h>
#include "debug.h"
#include "device.h"
#include "ata.h"
#include "atapi.h"
#include "scsi.h"
#include "string.h"
#include "sleep.h"
#include "blockcopy.h"
#include "lide_alib.h"
/**
* atapi_status_reg_delay
*
* We need a short delay before actually checking the status register to let the drive update the status
* Reading a CIA register should ensure a consistent delay regardless of CPU speed
* More info: https://wiki.osdev.org/ATA_PIO_Mode#400ns_delays
*
* @param unit Pointer to an IDEUnit struct
*/
static void __attribute__((always_inline)) atapi_status_reg_delay() {
asm volatile (
"tst.b 0xBFE001"
);
}
/**
* atapi_wait_drq
*
* Poll DRQ in the status register until set or timeout
* @param unit Pointer to an IDEUnit struct
* @param tries Tries, sets the timeout
*/
static bool atapi_wait_drq(struct IDEUnit *unit, ULONG tries) {
UBYTE status;
Trace("atapi_wait_drq enter\n");
atapi_status_reg_delay();
for (int i=0; i < tries; i++) {
status = *unit->drive.status_command;
if ((status & ata_flag_drq) != 0) return true;
if (status & (ata_flag_error | ata_flag_df)) return false;
atapi_status_reg_delay();
}
Trace("atapi_wait_drq timeout\n");
return false;
}
/**
* atapi_wait_drq_not_bsy
*
* Poll the status register until DRQ set and BSY clear or timeout
* @param unit Pointer to an IDEUnit struct
* @param tries Tries, sets the timeout
*/
static bool atapi_wait_drq_not_bsy(struct IDEUnit *unit, ULONG tries) {
atapi_status_reg_delay();
for (int i=0; i < tries; i++) {
if ((*unit->drive.status_command & (ata_flag_busy | ata_flag_drq)) == ata_flag_drq) return true;
atapi_status_reg_delay();
}
return false;
}
/**
* atapi_wait_not_bsy
*
* Polls the BSY bit in the status register until cleared, an error occurs, or the timeout is reached.
*
* For long-running commands (e.g., FORMAT UNIT, BLANK, up to 30 minutes), uses timer-based waits (1 ms per poll) to yield to other tasks, ensuring AmigaOS multitasking friendliness.
* For faster commands (e.g., READ (10), WRITE (10)), uses a ~1.4 µs delay via CIA register reads for CPU-independent timing and high throughput.
*
* Timeout durations:
* - Short (~40 seconds, CIA-based): For fast commands like READ (10), WRITE (10).
* - Medium (120 seconds, timer-based): For cache flushes (e.g., SYNCHRONIZE CACHE, CLOSE TRACK/SESSION).
* - Long (30 minutes, timer-based): For formatting (e.g., FORMAT UNIT, BLANK).
*
* Returns true if BSY clears, false on timeout or if error/device fault bits are set (ERR, DF).
*
* @param unit Pointer to an IDEUnit struct.
* @param tries Number of polling iterations—timeout depends on longDelay: ~1.4 µs/try (CIA) or 1 ms/try (timer).
* @param longDelay If true, uses timer.device (1 ms waits) for multitasking; if false, uses CIA (~1.4 µs) for speed.
*/
static bool atapi_wait_not_bsy(struct IDEUnit *unit, ULONG tries, bool longDelay) {
Trace("atapi_wait_not_bsy enter\n");
atapi_status_reg_delay();
if (longDelay) {
struct timerequest *tr = unit->itask->tr;
for (int i=0; i < tries; i++) {
if ((*unit->drive.status_command & ata_flag_busy) == 0) return true;
sleep_us(tr,ATAPI_BSY_WAIT_LOOP_LONG_US);
}
} else {
for (int i=0; i < tries; i++) {
if ((*unit->drive.status_command & ata_flag_busy) == 0) return true;
atapi_status_reg_delay();
}
}
Trace("atapi_wait_not_bsy timeout\n");
return false;
}
/**
* atapi_wait_not_drqbsy
*
* Poll DRQ & BSY in the status register until clear or timeout
* @param unit Pointer to an IDEUnit struct
* @param tries Tries, sets the timeout
*/
static bool atapi_wait_not_drqbsy(struct IDEUnit *unit, ULONG tries) {
Trace("atapi_wait_not_drqbsy enter\n");
atapi_status_reg_delay();
for (int i=0; i < tries; i++) {
if ((*(volatile BYTE *)unit->drive.status_command & (ata_flag_busy | ata_flag_drq)) == 0) return true;
atapi_status_reg_delay();
}
Trace("atapi_wait_not_drqbsy timeout\n");
return false;
}
/**
* atapi_check_ir
*
* Mask the Interrupt Reason register and check against a value
*
* @param unit Pointer to an IDEUnit struct
* @param mask Mask
* @param value Expected value after masking
* @param tries Maximum attempts to find the expected value
* @returns True if value matches
*/
static bool atapi_check_ir(struct IDEUnit *unit, UBYTE mask, UBYTE value, UWORD tries) {
for (int i=0; i<tries; i++) {
atapi_status_reg_delay();
if ((*unit->drive.sectorCount & mask) == value) return true;
}
return false;
}
/**
* atapi_check_error
* *
* @param unit Pointer to an IDEUnit struct
* @returns True if error is indicated
*/
static bool __attribute__((always_inline)) atapi_check_error(struct IDEUnit *unit) {
atapi_status_reg_delay();
return (*unit->drive.status_command & (ata_flag_error | ata_flag_df));
}
/**
* atapi_dev_reset
*
* Resets the device by sending a DEVICE RESET command to it
* @param unit Pointer to an IDEUnit struct
*/
void atapi_dev_reset(struct IDEUnit *unit) {
Info("ATAPI: Resetting device\n");
atapi_wait_not_bsy(unit,10000,true);
*unit->drive.status_command = ATA_CMD_DEVICE_RESET;
atapi_wait_not_bsy(unit,ATAPI_BSY_WAIT_COUNT_SHORT,false);
}
/**
* atapi_check_signature
*
* Resets the device then checks the signature in the LBA High and Mid registers to see if an ATAPI device is present
* @param unit Pointer to an IDEUnit struct
*/
bool atapi_check_signature(struct IDEUnit *unit) {
atapi_dev_reset(unit);
sleep_us(unit->itask->tr,10000);
for (int i=0; i<20; i++) {
if ((*unit->drive.lbaHigh == 0xEB) && (*unit->drive.lbaMid == 0x14)) return true;
}
return false;
}
/**
* atapi_identify
*
* Send an "IDENTIFY PACKET DEVICE" command and read the results into a buffer
*
* @param unit Pointer to an IDEUnit struct
* @param buffer Pointer to the destination buffer
* @returns True on success, false on failure
*/
bool atapi_identify(struct IDEUnit *unit, UWORD *buffer) {
UBYTE drvSel = (unit->primary) ? 0xE0 : 0xF0; // Select drive
// Only update the devHead register if absolutely necessary to save time
ata_select(unit,drvSel,false);
//if (!atapi_wait_rdy(unit,ATAPI_RDY_WAIT_COUNT))
// return HFERR_SelTimeout;
*unit->drive.sectorCount = 0;
*unit->drive.lbaLow = 0;
*unit->drive.lbaMid = 0;
*unit->drive.lbaHigh = 0;
*unit->drive.error_features = 0;
*unit->drive.status_command = ATAPI_CMD_IDENTIFY;
if (!atapi_wait_drq(unit,ATAPI_DRQ_WAIT_COUNT)) {
if (*unit->drive.status_command & (ata_flag_error | ata_flag_df)) {
Info("ATAPI: IDENTIFY Status: Error\n");
Info("ATAPI: last_error: %08lx\n",&unit->last_error[0]);
// Save the error details
unit->last_error[0] = *unit->drive.error_features;
unit->last_error[1] = *unit->drive.lbaHigh;
unit->last_error[2] = *unit->drive.lbaMid;
unit->last_error[3] = *unit->drive.lbaLow;
unit->last_error[4] = *unit->drive.status_command;
}
return false;
}
if (buffer) {
UWORD read_data;
for (int i=0; i<256; i++) {
read_data = *unit->drive.data;
buffer[i] = ((read_data >> 8) | (read_data << 8));
}
}
return true;
}
/**
* atapi_translate
*
* Translate TD commands to ATAPI and issue them to the device
*
* @param io_Data Pointer to the data buffer
* @param lba LBA to transfer
* @param count Number of LBAs to transfer
* @param io_Actual Pointer to the io_Actual field of the ioreq
* @param unit Pointer to the IDE Unit
* @param direction Transfer direction
* @returns error
*/
BYTE atapi_translate(APTR io_Data, ULONG lba, ULONG count, ULONG *io_Actual, struct IDEUnit *unit, enum xfer_dir direction)
{
Trace("atapi_translate enter\n");
struct SCSICmd *cmd = MakeSCSICmd(SZ_CDB_10);
if (cmd == NULL) return TDERR_NoMem;
struct SCSI_CDB_10 *cdb = (struct SCSI_CDB_10 *)cmd->scsi_Command;
UBYTE errorCode = 0;
UBYTE senseKey = 0;
UBYTE asc = 0;
UBYTE asq = 0;
if (count == 0) {
return IOERR_BADLENGTH;
}
Trace("%ld lba %ld count\n %ld bs\n",lba,count,unit->blockShift);
BYTE err = 0;
BYTE ret = 0;
for (int tries = 4; tries > 0; tries--) {
cmd->scsi_CmdLength = sizeof(struct SCSI_CDB_10);
cmd->scsi_CmdActual = 0;
cmd->scsi_Flags = (direction == READ) ? SCSIF_READ : SCSIF_WRITE;
cmd->scsi_Data = io_Data;
cmd->scsi_Length = count * unit->blockSize;
cmd->scsi_Actual = 0;
cmd->scsi_SenseData = NULL;
cmd->scsi_SenseLength = 0;
cdb->operation = (direction == READ) ? SCSI_CMD_READ_10 : SCSI_CMD_WRITE_10;
cdb->control = 0;
cdb->flags = 0;
cdb->group = 0;
cdb->lba = lba;
cdb->length = (UWORD)count;
if ((err = atapi_packet(cmd,unit)) == 0) {
goto done;
} else {
if (cmd->scsi_Status == 2) {
// Unit reported CHECK STATUS
// Request the sense data
if ((ret = atapi_request_sense(unit,&errorCode,&senseKey,&asc,&asq)) != 0) {
// Got an error even trying to get the sense data :/
goto done;
}
switch (senseKey) {
case 0x01: // Recovered error
ret = 0;
goto done;
case 0x02: // Unit not ready
if (asc == 0x4) { // Becoming ready
ret = TDERR_DiskChanged;
sleep_s(unit->itask->tr,1); // Wait
continue; // and try again
} else {
ret = TDERR_DiskChanged; // No media
atapi_update_presence(unit,false);
goto done;
}
case 0x06: // Media changed or unit completed reset
ret = err;
continue; // Try the command again
case 0x07: // Disk is write protected
ret = TDERR_WriteProt;
goto done;
default: // Anything else
ret = TDERR_NotSpecified;
continue; // Try again
}
} else {
// Command time outs / bad phase etc end up here
atapi_dev_reset(unit); // Reset the unit before trying again
continue;
}
}
}
done:
Trace("atapi_packet returns %ld\n",ret);
*io_Actual = cmd->scsi_Actual;
DeleteSCSICmd(cmd);
return ret;
}
#pragma GCC push_options
#pragma GCC optimize ("-O3")
/**
* atapi_packet
*
* Send a SCSICmd to an ATAPI device
*
* @param cmd Pointer to a SCSICmd struct
* @param unit Pointer to the IDEUnit
* @returns error, sense key returned in SenseData
*/
BYTE atapi_packet(struct SCSICmd *cmd, struct IDEUnit *unit) {
struct ExecBase *SysBase = unit->SysBase;
Trace("atapi_packet\n");
LONG byte_count = 0;
LONG remaining = cmd->scsi_Length;
BYTE ret = 0;
UBYTE senseKey = 0;
UWORD data;
ULONG busy_wait;
bool busy_useTimer;
Trace("Length: %ld\n",cmd->scsi_Length);
volatile UBYTE *status = unit->drive.status_command;
if (cmd->scsi_Length > 0 && cmd->scsi_Data == NULL) {
ret = IOERR_BADADDRESS;
goto end;
}
if (cmd->scsi_CmdLength > 12 || cmd->scsi_CmdLength < 6) {
ret = IOERR_BADADDRESS;
goto end;
}
// Adjust BSY wait timeout for long running commands
switch (cmd->scsi_Command[0]) {
case SCSI_CMD_FORMAT_UNIT:
case SCSI_CMD_BLANK:
busy_useTimer = true;
busy_wait = ATAPI_BSY_WAIT_COUNT_LONG; // Up to 30 mins
break;
case SCSI_CMD_SYNC_CACHE_10:
case SCSI_CMD_CLOSE_TRACK_SESS:
case SCSI_CMD_SEND_OPC_INFO:
case SCSI_CMD_REPAIR_TRACK:
busy_useTimer = true;
busy_wait = ATAPI_BSY_WAIT_COUNT_MEDIUM; // Up to 2 mins
break;
default:
busy_useTimer = false;
busy_wait = ATAPI_BSY_WAIT_COUNT_SHORT; // 30 seconds
break;
}
cmd->scsi_Actual = 0;
UBYTE drvSelHead = ((unit->primary) ? 0xE0 : 0xF0);
// Only update the devHead register if absolutely necessary to save time
ata_select(unit,drvSelHead,true);
if (!atapi_wait_not_drqbsy(unit,ATAPI_BSY_WAIT_COUNT_SHORT)) {
ret = IOERR_UNITBUSY;
goto end;
}
if (cmd->scsi_Length > 65534) {
byte_count = 65534;
} else {
byte_count = cmd->scsi_Length;
}
*unit->drive.lbaMid = byte_count & 0xFF;
*unit->drive.lbaHigh = byte_count >> 8 & 0xFF;
*unit->drive.error_features = 0;
*unit->drive.devHead = drvSelHead;
*unit->drive.status_command = ATAPI_CMD_PACKET;
if (atapi_check_error(unit)) {
ret = IOERR_ABORTED;
goto ata_error;
}
if (!atapi_wait_drq_not_bsy(unit,ATAPI_BSY_WAIT_COUNT_SHORT)) {
Trace("ATAPI: Packet bsy timeout\n");
ret = IOERR_UNITBUSY;
goto end;
}
if (!atapi_check_ir(unit,IR_STATUS,IR_COMMAND,1000)) {
Trace("ATAPI: Failed command phase\n");
ret = HFERR_Phase;
goto end;
}
for (int i=0; i < (cmd->scsi_CmdLength/2); i++)
{
data = *((UWORD *)cmd->scsi_Command + i);
*unit->drive.data = data;
Trace("ATAPI: CMD Word: %ld\n",data);
}
// ATAPI requires 12 command bytes transferred, pad out our 6 and 10 byte CDBs
if (cmd->scsi_CmdLength < 12)
{
for (int i = cmd->scsi_CmdLength; i < 12; i+=2) {
*unit->drive.data = 0x00;
Trace("ATAPI: CMD Word: 0\n");
}
}
if (atapi_check_error(unit)) {
ret = IOERR_ABORTED;
goto ata_error;
}
cmd->scsi_CmdActual = cmd->scsi_CmdLength;
ULONG index = 0;
while (1) {
if (!atapi_wait_not_bsy(unit,busy_wait,busy_useTimer)) {
ret = IOERR_UNITBUSY;
goto end;
}
if (cmd->scsi_Length == 0) break;
if ((atapi_check_ir(unit,0x03,IR_STATUS,100))) break;
if (!(atapi_wait_drq(unit,ATAPI_DRQ_WAIT_COUNT))) break;
byte_count = *unit->drive.lbaHigh << 8 | *unit->drive.lbaMid;
byte_count += (byte_count & 0x01); // Ensure that the byte count is always an even number
while (byte_count > 0) {
if ((byte_count >= 512 && remaining >= 512)) {
// 512 or more bytes to transfer, use the fast ATA transfer routines
if (cmd->scsi_Flags & SCSIF_READ) {
unit->read_fast((void *)unit->drive.data, cmd->scsi_Data + index);
} else {
unit->write_fast(cmd->scsi_Data + index, (void *)unit->drive.data);
}
index += 256;
cmd->scsi_Actual += 512;
byte_count -= 512;
} else if (remaining > 0) {
// Less than 512 bytes means we can't use the fast ATA transfer routines, copy word-by-word
if (cmd->scsi_Flags & SCSIF_READ) {
cmd->scsi_Data[index] = *unit->drive.data;
} else {
*unit->drive.data = cmd->scsi_Data[index];
}
index++;
cmd->scsi_Actual+=2;
byte_count -= 2;
} else {
// If we got here the drive wanted to transfer more data than the buffer could take
//
// Make the drive happy by reading/writing some more...
if (cmd->scsi_Flags & SCSIF_READ) {
*unit->drive.data;
} else {
*unit->drive.data = 0;
}
byte_count -= 2;
}
}
remaining = cmd->scsi_Length - cmd->scsi_Actual;
}
if (unit->SysBase->LibNode.lib_Version > 36) {
CacheClearE(cmd->scsi_Data,cmd->scsi_Length,CACRF_ClearI);
}
atapi_wait_not_bsy(unit,ATAPI_BSY_WAIT_COUNT_SHORT,false);
if (!atapi_check_ir(unit,atapi_flag_cd,IR_COMMAND,10)) {
// Drive is still in the data phase but should be either reporting completion or ready for a command
Warn("ATAPI: Completion not reported at end of command\n");
ret = HFERR_Phase;
goto ata_error;
}
end:
if (*status & ata_flag_error) {
ata_error:
unit->last_error[0] = *unit->drive.error_features;
unit->last_error[1] = *unit->drive.status_command;
unit->last_error[2] = *unit->drive.sectorCount;
senseKey = *unit->drive.error_features >> 4;
Warn("ATAPI ERROR!\n");
Warn("Sense Key: %02lx\n",senseKey);
Warn("Error: %02lx\n",*unit->drive.error_features);
Warn("Status: %02lx\n",*status);
Warn("Interrupt reason: %02lx\n",*unit->drive.sectorCount);
cmd->scsi_Status = 2;
if (cmd->scsi_Flags & (SCSIF_AUTOSENSE)) {
Trace("Auto sense requested\n");
if (ret == IOERR_UNITBUSY) {
// This was a timeout, fake the autosense data
scsi_sense(cmd,0,0,ret);
} else {
atapi_autosense(cmd,unit);
}
}
if (ret == 0) ret = HFERR_BadStatus;
}
Trace("Remaining: %ld\n",remaining);
Trace("exit atapi_packet\n");
return ret;
}
#pragma GCC pop_options
/**
* atapi_test_unit_ready
*
* Send a TEST UNIT READY to the unit and update the media change count & presence
*
* @param unit Pointer to an IDEUnit struct
* @param immediate Don't wait for medium to become ready
* @returns nonzero if there was an error
*/
BYTE atapi_test_unit_ready(struct IDEUnit *unit, bool immediate) {
struct SCSICmd *cmd = MakeSCSICmd(SZ_CDB_10);
if (cmd == NULL) return TDERR_NoMem;
struct SCSI_CDB_10 *cdb = (struct SCSI_CDB_10 *)cmd->scsi_Command;
UBYTE senseError = 0;
UBYTE senseKey = 0;
UBYTE asc = 0;
UBYTE asq = 0;
UBYTE ret = 0;
for (int tries = 4; tries > 0; tries--) {
cdb->operation = SCSI_CMD_TEST_UNIT_READY;
cmd->scsi_Command = (UBYTE *)cdb;
cmd->scsi_CmdLength = sizeof(struct SCSI_CDB_10);
cmd->scsi_Length = 0;
cmd->scsi_Data = NULL;
cmd->scsi_Flags = SCSIF_READ;
ret = atapi_packet(cmd,unit);
if (ret == 0) {
break;
} else {
if ((ret = atapi_request_sense(unit,&senseError,&senseKey,&asc,&asq)) == 0) {
Trace("SenseKey: %lx ASC: %lx ASQ: %lx\n",senseKey,asc,asq);
switch (senseKey) {
case 0x02: // Not ready
if (asc == 4) { // Becoming ready
// The medium is becoming ready, wait a few seconds before checking again
ret = TDERR_DiskChanged;
if (!immediate)
if (tries > 0) sleep_s(unit->itask->tr,2);
} else { // Anything else - No medium/bad medium etc
ret = TDERR_DiskChanged;
goto done;
}
break;
case 0x06: // Unit attention
if (asc == 0x28) { // Medium became ready
ret = 0;
}
break;
case 0x03: // Medium error
ret = TDERR_DiskChanged;
break;
default:
// Anything else, could be a timeout/bad phase etc
// Reset the unit and try again
//atapi_dev_reset(unit);
ret = TDERR_NotSpecified;
break;
}
} else {
atapi_dev_reset(unit);
break;
}
}
}
done:
atapi_update_presence(unit,(ret == 0)); // Update the media presence
DeleteSCSICmd(cmd);
return ret;
}
/**
* atapi_request_sense
*
* Request extended sense data from the ATAPI device
*
* @param unit Pointer to an IDEUnit struct
* @param senseKey Pointer for the senseKey result
* @param asc Pointer for the asc result
* @param asq Pointer for the asq result
* @return non-zero on error
*/
BYTE atapi_request_sense(struct IDEUnit *unit, UBYTE *errorCode, UBYTE *senseKey, UBYTE *asc, UBYTE *asq) {
struct ExecBase *SysBase = unit->SysBase;
struct SCSICmd *cmd = MakeSCSICmd(SZ_CDB_10);
if (cmd == NULL) return TDERR_NoMem;
UBYTE *cdb = (UBYTE *)cmd->scsi_Command;
UBYTE *buf;
if ((buf = AllocMem(18,MEMF_CLEAR|MEMF_ANY)) == NULL) {
DeleteSCSICmd(cmd);
return TDERR_NoMem;
}
UBYTE ret;
cdb[0] = SCSI_CMD_REQUEST_SENSE;
cdb[4] = 18;
cmd->scsi_Command = (UBYTE *)cdb;
cmd->scsi_CmdLength = sizeof(struct SCSI_CDB_10);
cmd->scsi_Length = 18;
cmd->scsi_Data = (UWORD *)buf;
cmd->scsi_Flags = SCSIF_READ;
ret = atapi_packet(cmd,unit);
Trace("ATAPI RS: Status %lx\n",ret);
*errorCode = buf[0];
*senseKey = buf[2] & 0x0F;
*asc = buf[12];
*asq = buf[13];
DeleteSCSICmd(cmd);
if (buf) FreeMem(buf,18);
return ret;
}
/**
* atapi_get_capacity
*
* Send a READ CAPACITY (10) to the ATAPI device then update the unit geometry with the returned values
*
* @param unit Pointer to an IDEUnit struct
* @return non-zero on error
*/
BYTE atapi_get_capacity(struct IDEUnit *unit) {
struct SCSICmd *cmd = MakeSCSICmd(SZ_CDB_10);
if (cmd == NULL) return TDERR_NoMem;
struct SCSI_CDB_10 *cdb = (struct SCSI_CDB_10 *)cmd->scsi_Command;
BYTE ret;
struct {
ULONG logicalSectors;
ULONG blockSize;
} capacity;
cdb->operation = SCSI_CMD_READ_CAPACITY_10;
cmd->scsi_CmdLength = sizeof(struct SCSI_CDB_10);
cmd->scsi_CmdActual = 0;
cmd->scsi_Command = (UBYTE *)cdb;
cmd->scsi_Flags = SCSIF_READ;
cmd->scsi_Data = (UWORD *)&capacity;
cmd->scsi_Length = 8;
cmd->scsi_SenseData = NULL;
unit->cylinders = 0;
unit->heads = 0;
unit->sectorsPerTrack = 0;
unit->logicalSectors = 0;
unit->blockShift = 0;
for (int retry = 3; retry > 0; retry--) {
if ((ret = atapi_packet(cmd,unit)) == 0) {
unit->logicalSectors = capacity.logicalSectors + 1;
unit->blockSize = capacity.blockSize;
while ((unit->blockSize >> unit->blockShift) > 1) {
unit->blockShift++;
}
break;
}
}
Trace("New geometry: %ld %ld\n",unit->logicalSectors, unit->blockSize);
DeleteSCSICmd(cmd);
return ret;
}
/**
* atapi_mode_sense
*
* Send a MODE SENSE (10) request to the ATAPI device
*
* @param unit Pointer to an IDEUnit struct
* @param page_code Page code to request
* @param buffer Pointer to a buffer for the mode sense data response
* @param length Size of the buffer
* @param actual Pointer to the actual byte count returned
* @return Non-zero on error
*/
BYTE atapi_mode_sense(struct IDEUnit *unit, BYTE page_code, BYTE subpage_code, UWORD *buffer, UWORD length, UWORD *actual, BOOL dbd) {
struct SCSICmd *cmd = MakeSCSICmd(SZ_CDB_10);
if (cmd == NULL) return TDERR_NoMem;
UBYTE *cdb = cmd->scsi_Command;
BYTE ret;
cdb[0] = SCSI_CMD_MODE_SENSE_10;
cdb[1] = (dbd ? 1 : 0) << 3;
cdb[2] = page_code;
cdb[3] = subpage_code;
cdb[7] = length >> 8;
cdb[8] = length & 0xFF;
cmd->scsi_CmdLength = sizeof(struct SCSI_CDB_10);
cmd->scsi_CmdActual = 0;
cmd->scsi_Command = (UBYTE *)cdb;
cmd->scsi_Flags = SCSIF_READ;
cmd->scsi_Data = buffer;
cmd->scsi_Length = length;
cmd->scsi_SenseData = NULL;
ret = atapi_packet(cmd,unit);
if (actual) *actual = cmd->scsi_Actual;
DeleteSCSICmd(cmd);
return ret;
}
/**
* atapi_scsi_mode_sense_6
*
* ATAPI devices do not support MODE SENSE (6) so translate to a MODE SENSE (10)
*
* @param cmd Pointer to a SCSICmd struct containing a MODE SENSE (6) request
* @param unit Pointer to an IDEUnit struct
* @returns non-zero on error, mode-sense data in cmd->scsi_Data
*/
BYTE atapi_scsi_mode_sense_6(struct SCSICmd *cmd, struct IDEUnit *unit) {
struct ExecBase *SysBase = unit->SysBase;
if (cmd->scsi_Data == NULL || cmd->scsi_Length == 0) return IOERR_BADADDRESS;
BYTE ret;
UBYTE *buf = NULL;
UBYTE *dest = (UBYTE *)cmd->scsi_Data;
ULONG len = cmd->scsi_Command[4] + 4; // Original allocation length
struct SCSICmd *cmd_sense = NULL;
if ((buf = AllocMem(len,MEMF_ANY|MEMF_CLEAR)) == NULL) {
return TDERR_NoMem;
}
cmd_sense = MakeSCSICmd(SZ_CDB_10);
if (cmd_sense == NULL) {
ret = TDERR_NoMem;
goto cleanup;
}
cmd_sense->scsi_Command[0] = SCSI_CMD_MODE_SENSE_10;
cmd_sense->scsi_Command[1] = cmd->scsi_Command[1]; // DBD flag
cmd_sense->scsi_Command[2] = cmd->scsi_Command[2]; // Page Code
cmd_sense->scsi_Command[3] = cmd->scsi_Command[3]; // Subpage Code
cmd_sense->scsi_Command[7] = (len >> 8) & 0xFF; // Allocation Length
cmd_sense->scsi_Command[8] = len; // Allocation Length
cmd_sense->scsi_Data = (UWORD *)buf;
cmd_sense->scsi_Length = len;
cmd_sense->scsi_Flags = SCSIF_READ;
ret = atapi_packet(cmd_sense,unit);
if (ret == 0 && cmd_sense->scsi_Status == 0) {
// Check that mode data is not too short and does not exceed mode sense(6) length and
if (cmd_sense->scsi_Actual < 8 || buf[0] || buf[6] || buf[1] < 3) {
cmd->scsi_Status = 2;
ret = IOERR_BADLENGTH;
} else {
// Translate the mode parameter header
dest[0] = (buf[1] - 3); // Length
dest[1] = buf[2]; // Medium type
dest[2] = buf[3]; // WP/DPOFUA Flags
dest[3] = buf[7]; // Block descriptor length
// Copy the mode sense data
for (int i = 0; i < (cmd_sense->scsi_Actual - 8); i++) {
dest[i+4] = buf[i+8];
}
cmd->scsi_Actual = cmd_sense->scsi_Actual - 4;
cmd->scsi_Status = 0;
}
} else {
cmd->scsi_Status = 2;
}
cmd->scsi_CmdActual = cmd->scsi_CmdLength;
cmd->scsi_SenseActual = cmd_sense->scsi_SenseActual;
cleanup:
if (buf) FreeMem(buf,len);
if (cmd_sense) DeleteSCSICmd(cmd_sense);
return ret;
}
/**
* atapi_scsi_mode_select_6
*
* ATAPI devices do not support MODE SELECT (6) so translate to a MODE SELECT (10)
*
* @param cmd Pointer to a SCSICmd struct containing a MODE SELECT (6) request
* @param unit Pointer to an IDEUnit struct
* @returns non-zero on error, mode-sense data in cmd->scsi_Data
*/
BYTE atapi_scsi_mode_select_6(struct SCSICmd *cmd, struct IDEUnit *unit) {
struct ExecBase *SysBase = unit->SysBase;
BYTE ret;
UBYTE *buf = NULL;
UBYTE *src = NULL;
UBYTE *dst = NULL;
ULONG len = 0;
struct SCSICmd *cmd_select = NULL;
if (cmd->scsi_Data == NULL || cmd->scsi_Length == 0) return IOERR_BADADDRESS;
ULONG bufSize = cmd->scsi_Command[4] + 4;
if ((buf = AllocMem(bufSize,MEMF_ANY|MEMF_CLEAR)) == NULL) {
return TDERR_NoMem;
}
src = (UBYTE *)cmd->scsi_Data;
dst = buf;
cmd_select = MakeSCSICmd(SZ_CDB_10);
if (cmd_select == NULL) {
ret = TDERR_NoMem;
goto cleanup;
}
cmd_select->scsi_Command[0] = SCSI_CMD_MODE_SELECT_10;
cmd_select->scsi_Command[1] = cmd->scsi_Command[1]; // PF / SP
cmd_select->scsi_Command[7] = (bufSize >> 8) & 0xFF; // Parameter list length
cmd_select->scsi_Command[8] = bufSize; // Parameter list length
cmd_select->scsi_Data = (UWORD *)buf;
cmd_select->scsi_Length = bufSize;
cmd_select->scsi_Flags = cmd->scsi_Flags;
cmd_select->scsi_SenseData = cmd->scsi_SenseData;
cmd_select->scsi_SenseLength = cmd->scsi_SenseLength;
// Copy Mode parameter header fields
dst[2] = src[1]; // Mediun Type
dst[3] = src[2]; // Device specific parameter
dst[7] = src[3]; // Block descriptor length
// Copy the Mode Parameters
len = bufSize - 8;
CopyMem(src + 4, dst + 8, len);
ret = atapi_packet(cmd_select, unit);
if (ret == 0 && cmd_select->scsi_Status == 0) {
cmd->scsi_Status = 0;
} else {
cmd->scsi_Status = 2;
}
cmd->scsi_SenseActual = cmd_select->scsi_SenseActual;
cmd->scsi_CmdActual = cmd->scsi_CmdLength;
cmd->scsi_Actual = cmd_select->scsi_Actual;
cleanup:
if (buf) FreeMem(buf,bufSize);
if (cmd_select) DeleteSCSICmd(cmd_select);
return ret;
}
/**
* atapi_scsi_read_write_6
*
* ATAPI devices do not support READ (6) or WRITE (6)
* Translate these calls to READ (10) / WRITE (10);
*
* @param cmd Pointer to a SCSICmd struct
* @param unit Pointer to an IDEUnit struct
* @returns non-zero on error
*/
BYTE atapi_scsi_read_write_6 (struct SCSICmd *cmd, struct IDEUnit *unit) {
struct ExecBase *SysBase = unit->SysBase;
BYTE ret;
struct SCSI_CDB_10 *cdb = AllocMem(sizeof(struct SCSI_CDB_10),MEMF_ANY|MEMF_CLEAR);
if (!cdb) return TDERR_NoMem;
struct SCSI_CDB_6 *oldcdb = (struct SCSI_CDB_6 *)cmd->scsi_Command;
cdb->operation = oldcdb->operation | 0x20;
cdb->length = oldcdb->length;
cdb->lba = oldcdb->lba_high << 16 |
oldcdb->lba_mid << 8 |
oldcdb->lba_low;
if (cdb->length == 0) cdb->length = 256; // for SCSI READ/WRITE 6 a transfer length of 0 specifies that 256 blocks will be transferred
cmd->scsi_Command = (BYTE *)cdb;
if (!((ULONG)cmd->scsi_Data & 0x01)) { // Buffer is word-aligned?
ret = atapi_packet(cmd,unit);
} else {
ret = atapi_packet_unaligned(cmd,unit);
}
FreeMem(cdb,sizeof(struct SCSI_CDB_10));
cmd->scsi_Command = (BYTE *)oldcdb;
return ret;
}
/**
* atapi_packet_unaligned
*
* In the unlikely event that someone has allocated an unaligned data buffer, align the data first by making a copy
*
* @param cmd Pointer to a SCSICmd struct
* @param unit Pointer to an IDEUnit struct
* @returns non-zero on exit
*/
BYTE atapi_packet_unaligned(struct SCSICmd *cmd, struct IDEUnit *unit) {
struct ExecBase *SysBase = unit->SysBase;
BYTE error = 0;
// Some bozo with an unaligned data buffer... (lookin' at you HDToolbox!)
// Allocate an aligned buffer and CopyMem to / from this one
UWORD *orig_buffer = cmd->scsi_Data;
if ((cmd->scsi_Data = AllocMem(cmd->scsi_Length,MEMF_CLEAR|MEMF_ANY)) == NULL) {
cmd->scsi_Data = orig_buffer;
error = TDERR_NoMem;
return error;
}
if (cmd->scsi_Flags & SCSIF_READ) {
error = atapi_packet(cmd,unit);
CopyMem(cmd->scsi_Data,orig_buffer,cmd->scsi_Length);
} else {
CopyMem(orig_buffer,cmd->scsi_Data,cmd->scsi_Length);
error = atapi_packet(cmd,unit);
}
FreeMem(cmd->scsi_Data,cmd->scsi_Length);
cmd->scsi_Data = orig_buffer;
return error;
}
/**
* atapi_start_stop_unit
*
* send START STOP command to ATAPI drive e.g to eject the disc
*
* @param unit Pointer to an IDEUnit struct
* @param start Start bit of START STOP
* @param loej loej bit of START STOP
* @returns non-zero on error
*/
BYTE atapi_start_stop_unit(struct IDEUnit *unit, bool start, bool loej, bool immediate) {
UBYTE operation = 0;
UBYTE ret = 0;
if (loej) operation |= (1<<1);
if (start) operation |= (1<<0);
BYTE cdb[10] = {0};
struct SCSI_FIXED_SENSE sense;
memset(&sense,0,sizeof(struct SCSI_FIXED_SENSE));
struct SCSICmd cmd = {
.scsi_Command = (APTR)&cdb,
.scsi_CmdActual = 0,
.scsi_CmdLength = 10,
.scsi_Data = NULL,
.scsi_Length = 0,
.scsi_Flags = SCSIF_AUTOSENSE,
.scsi_SenseData = (APTR)&sense,
.scsi_SenseLength = sizeof(struct SCSI_FIXED_SENSE),
.scsi_SenseActual = 0,
.scsi_Status = 0
};
cdb[0] = SCSI_CMD_START_STOP_UNIT;
cdb[1] = (immediate) ? 1 : 0;
cdb[4] = operation;
ret = atapi_packet(&cmd,unit);
if (cmd.scsi_Status == 0x02) {
if (sense.senseKey == 0x02) {
if (sense.asc == 0x04) {
ret = 0; // Becoming ready
} else {
ret = TDERR_DiskChanged;
}
} else {
ret = TDERR_NotSpecified;
}
}
atapi_test_unit_ready(unit,true);
return ret;
}
/**
* atapi_check_wp
*
* Check write-protect status of the disk
*
* @param unit Pointer to an IDEUnit struct
* @returns non-zero on error
*/
BYTE atapi_check_wp(struct IDEUnit *unit) {
struct ExecBase *SysBase = unit->SysBase;
UBYTE ret = 0;
UBYTE *buf = NULL;
if ((buf = AllocMem(512,MEMF_ANY|MEMF_CLEAR)) == NULL) return TDERR_NoMem;
UWORD actual = 0;
if ((ret = atapi_mode_sense(unit,0x3F,0,(UWORD *)buf,512,&actual,false)) == 0) {
if (buf[3] & 1<<7)
ret = TDERR_WriteProt;
}
if (buf) FreeMem(buf,512);
return ret;
}
/**
* atapi_update_presence
*
* If the medium has changed state update the unit info, geometry etc
* @param unit Pointer to an IDEUnit struct
* @param present Medium present
* @returns bool true if changed
*/
bool atapi_update_presence(struct IDEUnit *unit, bool present) {
bool ret = false;
if (present && unit->mediumPresent == false) {
unit->changeCount++;
unit->mediumPresent = true;
atapi_get_capacity(unit);
ret = true;
} else if (!present && unit->mediumPresent == true) {
unit->changeCount++;
unit->mediumPresent = false;
unit->logicalSectors = 0;
unit->blockShift = 0;
unit->blockSize = 0;
ret = true;
}
return ret;
}
/**
* atapi_adjust_end_msf
*
* Decrement the MSF by one frame.
* i.e get the end of the last track by inputting the MSF for the lead-out
*
* @param msf
*/
static void atapi_adjust_end_msf(struct SCSI_TRACK_MSF *msf) {
if (msf->frame > 0) {
msf->frame--;
} else {
msf->frame = 74;
if (msf->second > 0) {
msf->second --;
} else {
msf->second = 59;
if (msf->minute > 0)
msf->minute--;
}
}
}
/**
* atapi_read_toc
*
* Reads the CD TOC into the supplied buffer
*
* @param unit Pointer to an IDEUnit struct
* @param buf Pointer to the buffer
* @param bufSize Size of buffer
* @returns non-zero on error
*/
BYTE atapi_read_toc(struct IDEUnit *unit, BYTE *buf, ULONG bufSize) {
BYTE ret = 0;
if (buf == NULL || bufSize == 0) {
return IOERR_BADADDRESS;
}
struct SCSICmd *cmd = MakeSCSICmd(SZ_CDB_10);
if (cmd == NULL) return TDERR_NoMem;
cmd->scsi_Data = (UWORD *)buf;
cmd->scsi_Length = bufSize;
cmd->scsi_Flags = SCSIF_READ;
cmd->scsi_Command[0] = SCSI_CMD_READ_TOC;
cmd->scsi_Command[1] = 0x02; // MSF Flag
cmd->scsi_Command[7] = bufSize >> 8;
cmd->scsi_Command[8] = bufSize & 0xFF;
ret = atapi_packet(cmd,unit) != 0;
DeleteSCSICmd(cmd);
return ret;
}
/**
* atapi_get_track_msf
*
* Find the M/S/F of a Track
*
* @param toc pointer to a SCSI_CD_TOC struct
* @param trackNum track number to find
* @param msf Pointer to a SCSI_TRACK_MSF struct
* @returns true if track found
*/
BOOL atapi_get_track_msf(struct SCSI_CD_TOC *toc, int trackNum, struct SCSI_TRACK_MSF *msf) {
if (toc == NULL || msf == NULL) {
return false;
}
int numTracks = (toc->lastTrack - toc->firstTrack) + 1;
for (int t=0; t<=numTracks; t++) {
if (toc->td[t].trackNumber == trackNum) {
msf->minute = toc->td[t].minute;
msf->second = toc->td[t].second;
msf->frame = toc->td[t].frame;
return true;
}
}
return false;
}
/**
* atapi_play_track_index
*
* Find tracks <start> and <end> in TOC and issue a PLAY AUDIO MSF command
*
* @param unit Pointer to an IDEUnit struct
* @param start Start track number
* @param end End track number
* @returns non-zero on error
*/
BYTE atapi_play_track_index(struct IDEUnit *unit, UBYTE start, UBYTE end) {
struct ExecBase *SysBase = unit->SysBase;
BYTE ret = 0;
struct SCSI_TRACK_MSF startmsf, endmsf;
struct SCSI_CD_TOC *toc = AllocMem(SCSI_TOC_SIZE,MEMF_ANY|MEMF_CLEAR);
if (toc == NULL) return TDERR_NoMem;
ret = atapi_read_toc(unit,(BYTE *)toc,SCSI_TOC_SIZE);
if (ret == 0) {
if (end > toc->lastTrack) end = 0xAA; // Lead out
if (atapi_get_track_msf(toc,start,&startmsf) &&
atapi_get_track_msf(toc,end,&endmsf))
{
// Point to end of last track rather than lead-out
if (end == 0xAA) atapi_adjust_end_msf(&endmsf);
ret = atapi_play_audio_msf(unit,&startmsf,&endmsf);
} else {
ret = IOERR_BADADDRESS;
}
}
FreeMem(toc,SCSI_TOC_SIZE);
return ret;
}
/**
* atapi_play_audio_msf
*
* Issue a PLAY AUDIO MSF command to the drive
*
* @param unit Pointer to an IDEUnit struct
* @param start Pointer to a SCSI_TRACK_MSF struct for the starting position
* @param end Pointer to a SCSI_TRACK_MSF struct for the ending position
* @returns non-zero on error
*/
BYTE atapi_play_audio_msf(struct IDEUnit *unit, struct SCSI_TRACK_MSF *start, struct SCSI_TRACK_MSF *end) {
BYTE ret = 0;
struct SCSICmd *cmd = MakeSCSICmd(SZ_CDB_10);
if (cmd == NULL) return TDERR_NoMem;
cmd->scsi_Command[0] = SCSI_CMD_PLAY_AUDIO_MSF;
cmd->scsi_Command[3] = start->minute;
cmd->scsi_Command[4] = start->second;
cmd->scsi_Command[5] = start->frame;
cmd->scsi_Command[6] = end->minute;
cmd->scsi_Command[7] = end->second;
cmd->scsi_Command[8] = end->frame;
cmd->scsi_Flags = SCSIF_READ;
cmd->scsi_Data = NULL;
cmd->scsi_Length = 0;
ret = atapi_packet(cmd,unit);
DeleteSCSICmd(cmd);
return ret;
}
/**
* atapi_translate_play_audio_index
*
* PLAY AUDIO INDEX was deprecated with SCSI-3 and is not supported by ATAPI drives
* Some software makes use of this, so we translate it to a PLAY AUDIO MSF command
*
* @param cmd Pointer to a SCSICmd struct for a PLAY AUDIO INDEX command
* @param unit Pointer to an IDEUnit struct
* @returns non-zero on error
*/
BYTE atapi_translate_play_audio_index(struct SCSICmd *cmd, struct IDEUnit *unit) {
UBYTE start = cmd->scsi_Command[4];
UBYTE end = cmd->scsi_Command[7];
BYTE ret = 0;
ret = atapi_play_track_index(unit,start,end);
cmd->scsi_CmdActual = cmd->scsi_CmdLength;
cmd->scsi_Status = (ret == 0) ? 0 : SCSI_CHECK_CONDITION;
return ret;
}
/**
* atapi_autosense
*
* Perform a REQUEST SENSE and put the result into scsi_SenseData of a supplied SCSICmd
*
* @param scsi_command Pointer to a SCSICmd struct
* @param unit Pointer to an IDEUnit struct
* @returns non-zero on error
*/
BYTE atapi_autosense(struct SCSICmd *scsi_command, struct IDEUnit *unit) {
UBYTE ret = 0;
if (scsi_command->scsi_SenseData == NULL || scsi_command->scsi_SenseLength == 0)
return IOERR_BADADDRESS;
struct SCSICmd *cmd = MakeSCSICmd(SZ_CDB_12);
if (cmd != NULL) {
for (int retry=3; retry > 0; retry--) {
cmd->scsi_Command[0] = SCSI_CMD_REQUEST_SENSE;
cmd->scsi_Command[4] = scsi_command->scsi_SenseLength & 0xFF;
cmd->scsi_Data = (UWORD *)scsi_command->scsi_SenseData;
cmd->scsi_Length = scsi_command->scsi_SenseLength;
cmd->scsi_Flags = SCSIF_READ;
cmd->scsi_CmdLength = 12;
ret = atapi_packet(cmd,unit);
scsi_command->scsi_SenseActual = cmd->scsi_Actual;
if (ret == 0) break;
sleep_us(unit->itask->tr,250000);
}
DeleteSCSICmd(cmd);
return ret;
} else {
return TDERR_NoMem;
}
}