mirror of
https://github.com/Wack0/entii-for-workcubes.git
synced 2025-12-25 02:15:12 -05:00
827 lines
26 KiB
C
827 lines
26 KiB
C
// IDE over SPI (EXI) driver
|
|
#include <stdio.h>
|
|
#include <memory.h>
|
|
#include "arc.h"
|
|
#include "types.h"
|
|
#include "runtime.h"
|
|
#include "exi.h"
|
|
#include "exi_ide.h"
|
|
#include "exi_sdmc.h"
|
|
#include "timer.h"
|
|
|
|
enum {
|
|
IDE_DRIVE_COUNT = 4,
|
|
IDE_SECTOR_SIZE = 0x200
|
|
};
|
|
|
|
// IDE registers.
|
|
enum {
|
|
IDE_REG_DATA = 0x10,
|
|
IDE_REG_ERROR,
|
|
IDE_REG_SECTOR_COUNT,
|
|
IDE_REG_START_SECTOR,
|
|
IDE_REG_CYLINDER_LOW,
|
|
IDE_REG_CYLINDER_HIGH,
|
|
IDE_REG_DEVICE_SELECT,
|
|
IDE_REG_STATUS,
|
|
|
|
IDE_REG_ALTERNATE_STATUS = 0x08 | 6,
|
|
IDE_REG_DRIVE_ADDRESS,
|
|
|
|
// Write-only registers
|
|
IDE_REG_FEATURES = IDE_REG_ERROR,
|
|
IDE_REG_COMMAND = IDE_REG_STATUS,
|
|
IDE_REG_DEVICE_CONTROL = IDE_REG_ALTERNATE_STATUS,
|
|
|
|
// Alternate names
|
|
IDE_REG_LBA0 = IDE_REG_START_SECTOR,
|
|
IDE_REG_LBA1 = IDE_REG_CYLINDER_LOW,
|
|
IDE_REG_LBA2 = IDE_REG_CYLINDER_HIGH,
|
|
|
|
// Upper command bits.
|
|
IDE_CMD_MULTI_TRANSFER = ARC_BIT(5),
|
|
IDE_CMD_WORD_TRANSFER = ARC_BIT(6),
|
|
IDE_CMD_WRITE = ARC_BIT(7),
|
|
};
|
|
|
|
enum {
|
|
IDE_EXI_LEN_READ8 = 2,
|
|
IDE_EXI_LEN_WRITE8 = 3,
|
|
IDE_EXI_LEN_READ16 = 2,
|
|
IDE_EXI_LEN_WRITE16 = 4,
|
|
IDE_EXI_LEN_READMULTI32 = 4,
|
|
IDE_EXI_LEN_WRITEMULTI32 = 3
|
|
};
|
|
|
|
enum {
|
|
IDE_MULTI32_MAX_LENGTH = 0x3FFFC
|
|
};
|
|
|
|
// ATA command set.
|
|
enum {
|
|
ATA_CMD_IDENTIFY = 0xEC,
|
|
ATA_CMD_READ_SECTOR = 0x21,
|
|
ATA_CMD_READ_SECTOR_EXT = 0x24,
|
|
ATA_CMD_READ_MULTIPLE_EXT = 0x29,
|
|
ATA_CMD_WRITE_SECTOR = 0x30,
|
|
ATA_CMD_WRITE_SECTOR_EXT = 0x34,
|
|
ATA_CMD_WRITE_MULTIPLE_EXT = 0x39,
|
|
ATA_CMD_READ_MULTIPLE = 0xC4,
|
|
ATA_CMD_WRITE_MULTIPLE = 0xC5,
|
|
ATA_CMD_SET_MULTIPLE_MODE = 0xC6,
|
|
ATA_CMD_UNLOCK = 0xF2,
|
|
ATA_CMD_SECURITY_DISABLE = 0xF6,
|
|
};
|
|
|
|
// ATA IDENTIFY 16-bit offsets.
|
|
enum {
|
|
ATA_IDENTIFY_CYLINDERS = 1,
|
|
ATA_IDENTIFY_HEADS = 3,
|
|
ATA_IDENTIFY_SECTORS = 6,
|
|
ATA_IDENTIFY_SERIAL = 10,
|
|
ATA_IDENTIFY_MODEL = 27,
|
|
ATA_IDENTIFY_MULTIPLE = 47,
|
|
ATA_IDENTIFY_CAPABILITY = 49,
|
|
ATA_IDENTIFY_LBASECTORS = 60,
|
|
ATA_IDENTIFY_COMMANDSET = 83,
|
|
ATA_IDENTIFY_LBA48SECTORS = 100,
|
|
|
|
ATA_IDENTIFY_LBA48MASK = ARC_BIT(10), // Mask for LBA support in COMMANDSET
|
|
ATA_IDENTIFY_LBA28MASK = ARC_BIT(9), // Mask for LBA support in CAPABILITY
|
|
};
|
|
|
|
// Status register bits
|
|
enum {
|
|
SR_ERR = ARC_BIT(0),
|
|
SR_IDX = ARC_BIT(1),
|
|
SR_CORR = ARC_BIT(2),
|
|
SR_DRQ = ARC_BIT(3),
|
|
SR_DSC = ARC_BIT(4),
|
|
SR_DF = ARC_BIT(5),
|
|
SR_DRDY = ARC_BIT(6),
|
|
SR_BSY = ARC_BIT(7)
|
|
};
|
|
|
|
// Error register bits
|
|
enum {
|
|
ER_AMNF = ARC_BIT(0),
|
|
ER_TK0NF = ARC_BIT(1),
|
|
ER_ABRT = ARC_BIT(2),
|
|
ER_MCR = ARC_BIT(3),
|
|
ER_IDNF = ARC_BIT(4),
|
|
ER_MC = ARC_BIT(5),
|
|
ER_UNC = ARC_BIT(6)
|
|
};
|
|
|
|
// Head register bits
|
|
enum {
|
|
HEAD_USE_LBA = ARC_BIT(6)
|
|
};
|
|
|
|
// Device control bits
|
|
enum {
|
|
DCON_NIEN = ARC_BIT(1),
|
|
DCON_SRST = ARC_BIT(2)
|
|
};
|
|
|
|
enum {
|
|
IDEEXI_ID_V2 = 0x49444532,
|
|
IDEEXI_ID_V3 = 0x49444533
|
|
};
|
|
|
|
typedef enum {
|
|
IDEEXI_UNKNOWN,
|
|
IDEEXI_V1,
|
|
IDEEXI_V2,
|
|
IDEEXI_V3
|
|
} IDEEXI_HW_VERSION;
|
|
|
|
typedef enum {
|
|
CAPABILITY_CHS,
|
|
CAPABILITY_LBA28,
|
|
CAPABILITY_LBA48
|
|
} IDE_CAPABILITY;
|
|
|
|
typedef struct _IDE_STATE {
|
|
uint64_t NumberOfSectors;
|
|
ULONG SizeInMegabytes;
|
|
ULONG SizeInGigabytes;
|
|
ULONG Cylinders, Heads, Sectors;
|
|
ULONG MultipleSectorCount;
|
|
UCHAR HwVersion; // IDEEXI_HW_VERSION
|
|
UCHAR ClockFreq; // EXI_CLOCK_FREQUENCY
|
|
UCHAR Capability; // IDE_CAPABILITY
|
|
bool Initialised;
|
|
char Model[48];
|
|
char Serial[24];
|
|
} IDE_STATE, *PIDE_STATE;
|
|
|
|
// Temporary transfer buffer used for ATA IDENTIFY output.
|
|
static UCHAR s_TransferBuffer[IDE_SECTOR_SIZE] ARC_ALIGNED(32);
|
|
|
|
// IDE state for each EXI device
|
|
static IDE_STATE s_IdeState[IDE_DRIVE_COUNT];
|
|
|
|
static ULONG IdepCreateCommandRead8(UCHAR Register) {
|
|
return (ULONG)Register << 8;
|
|
}
|
|
|
|
static ULONG IdepCreateCommandWrite8(UCHAR Register, UCHAR Value) {
|
|
ULONG Ret = Register | IDE_CMD_WRITE;
|
|
Ret <<= 8;
|
|
Ret |= Value;
|
|
Ret <<= 8;
|
|
return Ret;
|
|
}
|
|
|
|
// there's only one single register that can be read by 16-bit!
|
|
static ULONG IdepCreateCommandRead16(void) {
|
|
return (ULONG)(IDE_REG_DATA | IDE_CMD_WORD_TRANSFER) << 8;
|
|
}
|
|
|
|
static ULONG IdepCreateCommandWrite16(USHORT Value) {
|
|
ULONG Ret = (ULONG)IDE_REG_DATA | IDE_CMD_WORD_TRANSFER | IDE_CMD_WRITE;
|
|
Ret <<= 8;
|
|
Ret |= (Value >> 8);
|
|
Ret <<= 8;
|
|
Ret |= (Value & 0xFF);
|
|
Ret <<= 8;
|
|
return Ret;
|
|
}
|
|
|
|
// same for by 32-bit
|
|
// expect caller to verify length, maximum length is 0x3FFFC bytes, therefore 511 sectors
|
|
static ULONG IdepCreateCommandReadMulti32(ULONG Length) {
|
|
Length /= sizeof(ULONG);
|
|
ULONG Ret = (ULONG)IDE_REG_DATA | IDE_CMD_WORD_TRANSFER | IDE_CMD_MULTI_TRANSFER;
|
|
Ret <<= 8;
|
|
Ret |= (Length & 0xFF);
|
|
Ret <<= 8;
|
|
Ret |= ((Length >> 8) & 0xFF);
|
|
Ret <<= 8;
|
|
return Ret;
|
|
}
|
|
|
|
// expect caller to verify length, maximum length is 0x3FFFC bytes, therefore 511 sectors
|
|
static ULONG IdepCreateCommandWriteMulti32(ULONG Length) {
|
|
Length /= sizeof(ULONG);
|
|
ULONG Ret = (ULONG)IDE_REG_DATA | IDE_CMD_WORD_TRANSFER | IDE_CMD_MULTI_TRANSFER | IDE_CMD_WRITE;
|
|
Ret <<= 8;
|
|
Ret |= (Length & 0xFF);
|
|
Ret <<= 8;
|
|
Ret |= ((Length >> 8) & 0xFF);
|
|
return Ret;
|
|
}
|
|
|
|
static UCHAR IdepGetReadByte(ULONG readByte) {
|
|
return (UCHAR)(LoadToRegister32(readByte) & 0xFF);
|
|
}
|
|
|
|
static USHORT IdepGetRead16(ULONG readData) {
|
|
return (USHORT)(LoadToRegister32(readData) & 0xFFFF);
|
|
}
|
|
|
|
static bool IdepReadRegister8(PIDE_STATE State, ULONG Channel, ULONG Device, UCHAR Register, PUCHAR ReadRegister) {
|
|
if (!ExiSelectDevice(Channel, Device, State->ClockFreq, false)) return false;
|
|
bool success = false;
|
|
do {
|
|
ULONG Value;
|
|
if (!ExiTransferImmediate(Channel, IdepCreateCommandRead8(Register), IDE_EXI_LEN_READ8, EXI_TRANSFER_WRITE, NULL)) break;
|
|
if (!ExiTransferImmediate(Channel, 0, 1, EXI_TRANSFER_READ, &Value)) break;
|
|
*ReadRegister = IdepGetReadByte(Value);
|
|
success = true;
|
|
} while (false);
|
|
ExiUnselectDevice(Channel);
|
|
return success;
|
|
}
|
|
|
|
static bool IdepReadStatusReg(PIDE_STATE State, ULONG Channel, ULONG Device, PUCHAR Register) {
|
|
return IdepReadRegister8(State, Channel, Device, IDE_REG_STATUS, Register);
|
|
}
|
|
|
|
static bool IdepReadErrorReg(PIDE_STATE State, ULONG Channel, ULONG Device, PUCHAR Register) {
|
|
return IdepReadRegister8(State, Channel, Device, IDE_REG_ERROR, Register);
|
|
}
|
|
|
|
static bool IdepWriteRegister8(PIDE_STATE State, ULONG Channel, ULONG Device, UCHAR Register, UCHAR Value) {
|
|
if (!ExiSelectDevice(Channel, Device, State->ClockFreq, false)) return false;
|
|
bool success = false;
|
|
do {
|
|
if (!ExiTransferImmediate(Channel, IdepCreateCommandWrite8(Register, Value), IDE_EXI_LEN_WRITE8, EXI_TRANSFER_WRITE, NULL)) break;
|
|
success = true;
|
|
} while (false);
|
|
ExiUnselectDevice(Channel);
|
|
return success;
|
|
}
|
|
|
|
static bool IdepWriteData16(PIDE_STATE State, ULONG Channel, ULONG Device, USHORT Value) {
|
|
if (!ExiSelectDevice(Channel, Device, State->ClockFreq, false)) return false;
|
|
bool success = false;
|
|
do {
|
|
if (!ExiTransferImmediate(Channel, IdepCreateCommandWrite16(Value), IDE_EXI_LEN_WRITE16, EXI_TRANSFER_WRITE, NULL)) break;
|
|
success = true;
|
|
} while (false);
|
|
ExiUnselectDevice(Channel);
|
|
return success;
|
|
}
|
|
|
|
static bool IdepReadData16(PIDE_STATE State, ULONG Channel, ULONG Device, PUSHORT ReadRegister) {
|
|
if (!ExiSelectDevice(Channel, Device, State->ClockFreq, false)) return false;
|
|
bool success = false;
|
|
do {
|
|
ULONG Value;
|
|
if (!ExiTransferImmediate(Channel, IdepCreateCommandRead16(), IDE_EXI_LEN_READ16, EXI_TRANSFER_WRITE, NULL)) break;
|
|
if (!ExiTransferImmediate(Channel, 0, 2, EXI_TRANSFER_READ, &Value)) break;
|
|
*ReadRegister = IdepGetRead16(Value);
|
|
success = true;
|
|
} while (false);
|
|
ExiUnselectDevice(Channel);
|
|
return success;
|
|
}
|
|
|
|
static bool IdepReadBuffer(PIDE_STATE State, ULONG Channel, ULONG Device, PVOID Buffer, ULONG Length) {
|
|
if (Length > IDE_MULTI32_MAX_LENGTH) return false;
|
|
if ((Length & 3) != 0) return false;
|
|
PULONG buf32 = (PULONG)Buffer;
|
|
|
|
if (!ExiSelectDevice(Channel, Device, State->ClockFreq, false)) return false;
|
|
bool success = false;
|
|
do {
|
|
ULONG Value;
|
|
if (!ExiTransferImmediate(Channel, IdepCreateCommandReadMulti32(Length), IDE_EXI_LEN_READMULTI32, EXI_TRANSFER_WRITE, NULL)) break;
|
|
if (State->HwVersion == IDEEXI_V1) {
|
|
// Oldest hw version, cannot use dma and must toggle the chip select after every 32 bits transferred.
|
|
if (!ExiRefreshDevice(Channel)) break;
|
|
bool InnerSuccess = true;
|
|
for (ULONG i = 0; i < (Length / sizeof(buf32[0])); i++) {
|
|
if (!ExiTransferImmediate(Channel, 0, sizeof(buf32[0]), EXI_TRANSFER_READ, &buf32[i])) {
|
|
InnerSuccess = false;
|
|
break;
|
|
}
|
|
if (!ExiRefreshDevice(Channel)) {
|
|
InnerSuccess = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!InnerSuccess) break;
|
|
// Read another 32 bits for some reason
|
|
if (!ExiTransferImmediate(Channel, 0, sizeof(Value), EXI_TRANSFER_READ, &Value)) break;
|
|
}
|
|
else {
|
|
// Just dma the data, if possible, otherwise receive via immediate
|
|
if (ExiBufferCanDma(Buffer, Length)) {
|
|
if (!ExiTransferDma(Channel, Buffer, Length, EXI_TRANSFER_READ, EXI_SWAP_BOTH)) break;
|
|
}
|
|
else {
|
|
if (!ExiTransferImmediateBuffer(Channel, NULL, Buffer, Length, EXI_TRANSFER_READ)) break;
|
|
// Buffer needs bswap16.
|
|
#if 0
|
|
PUSHORT buf16 = (PUSHORT)Buffer;
|
|
for (ULONG i = 0; i < (Length / sizeof(USHORT)); i++) buf16[i] = __builtin_bswap16(buf16[i]);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
success = true;
|
|
} while (false);
|
|
ExiUnselectDevice(Channel);
|
|
return success;
|
|
}
|
|
|
|
static bool IdepWriteBuffer(PIDE_STATE State, ULONG Channel, ULONG Device, PVOID Buffer, ULONG Length) {
|
|
if (Length > IDE_MULTI32_MAX_LENGTH) return false;
|
|
if ((Length & 3) != 0) return false;
|
|
|
|
if (State->HwVersion < IDEEXI_V3) {
|
|
// v1 and v2 can only do PIO writes
|
|
PUSHORT ptr = (PUSHORT)Buffer;
|
|
for (ULONG i = 0; i < (Length / sizeof(ptr[0])); i++) {
|
|
if (!IdepWriteData16(State, Channel, Device, ptr[i])) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (!ExiSelectDevice(Channel, Device, State->ClockFreq, false)) return false;
|
|
bool success = false;
|
|
do {
|
|
if (!ExiTransferImmediate(Channel, IdepCreateCommandWriteMulti32(Length), IDE_EXI_LEN_WRITEMULTI32, EXI_TRANSFER_WRITE, NULL)) break;
|
|
// Send the data
|
|
if (ExiBufferCanDma(Buffer, Length)) {
|
|
if (!ExiTransferDma(Channel, Buffer, Length, EXI_TRANSFER_WRITE, EXI_SWAP_BOTH)) break;
|
|
}
|
|
else {
|
|
if (!ExiTransferImmediateBuffer(Channel, Buffer, NULL, Length, EXI_TRANSFER_WRITE)) break;
|
|
}
|
|
// Finish the write operation
|
|
if (!ExiTransferImmediate(Channel, 0, 1, EXI_TRANSFER_WRITE, NULL)) break;
|
|
|
|
success = true;
|
|
} while (false);
|
|
ExiUnselectDevice(Channel);
|
|
return success;
|
|
}
|
|
|
|
static IDEEXI_HW_VERSION IdepGetVersion(PIDE_STATE state, ULONG channel, ULONG device) {
|
|
ULONG DeviceId;
|
|
// If this device is a mounted SD card, don't touch it.
|
|
EXI_SDMC_DRIVE drive;
|
|
if (channel == 0) {
|
|
if (device == 0) drive = SDMC_DRIVE_CARD_A;
|
|
if (device == 2) drive = SDMC_DRIVE_SP1;
|
|
}
|
|
else {
|
|
if (channel == 1) drive = SDMC_DRIVE_CARD_B;
|
|
if (channel == 2) drive = SDMC_DRIVE_SP2;
|
|
}
|
|
IDEEXI_HW_VERSION ret = IDEEXI_UNKNOWN;
|
|
do {
|
|
if (SdmcexiIsMounted(drive)) {
|
|
break;
|
|
}
|
|
|
|
// get the deviceID
|
|
if (!ExiGetDeviceIdentifier(channel, device, &DeviceId)) {
|
|
break;
|
|
}
|
|
|
|
if (DeviceId == IDEEXI_ID_V2) {
|
|
ret = IDEEXI_V2;
|
|
break;
|
|
}
|
|
if (DeviceId == IDEEXI_ID_V3) {
|
|
ret = IDEEXI_V3;
|
|
break;
|
|
}
|
|
// if it's an unknown version act as if it's v3
|
|
if ((DeviceId & ~0xFF) == (IDEEXI_ID_V2 & ~0xFF)) {
|
|
ret = IDEEXI_V3;
|
|
break;
|
|
}
|
|
|
|
// might be v1?
|
|
// this check might not actually work, but theoretically it should? the result of read8 with no chip select line pulled down
|
|
if ((DeviceId & 0xFF000000) == 0xFF000000) ret = IDEEXI_V1;
|
|
break;
|
|
} while (false);
|
|
state->HwVersion = ret;
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
ob_ide_fixup_string(unsigned char* s, unsigned int len)
|
|
{
|
|
unsigned char* p = s, * end = &s[len & ~1];
|
|
|
|
/*
|
|
* if big endian arch, byte swap the string
|
|
*/
|
|
//#ifdef CONFIG_BIG_ENDIAN
|
|
for (p = end; p != s;) {
|
|
unsigned short* pp = (unsigned short*)(p -= 2);
|
|
*pp = __builtin_bswap16(*pp);
|
|
}
|
|
//#endif
|
|
|
|
while (s != end && *s == ' ')
|
|
++s;
|
|
while (s != end && *s)
|
|
if (*s++ != ' ' || (s != end && *s && *s != ' '))
|
|
*p++ = *(s - 1);
|
|
while (p != end)
|
|
*p++ = '\0';
|
|
}
|
|
|
|
static bool IdepWaitStatus(PIDE_STATE state, ULONG channel, ULONG device, UCHAR bits_wanted, UCHAR bits_not_wanted, PUCHAR output) {
|
|
udelay(1);
|
|
UCHAR status;
|
|
for (ULONG i = 0; i < 5000; i++) {
|
|
if (!IdepReadStatusReg(state, channel, device, &status)) status = bits_not_wanted & ~bits_wanted;
|
|
if ((status & SR_BSY) == 0) break;
|
|
udelay(1000);
|
|
}
|
|
|
|
if (output != NULL) *output = status;
|
|
if ((status & bits_not_wanted) != 0) return false;
|
|
if (bits_wanted == 0) return true;
|
|
return ((status & bits_wanted) != 0);
|
|
}
|
|
|
|
static bool IdepMountDrive(PIDE_STATE state, ULONG channel, ULONG device) {
|
|
memset(state, 0, sizeof(*state));
|
|
state->ClockFreq = EXI_CLOCK_27;
|
|
|
|
IDEEXI_HW_VERSION version = IdepGetVersion(state, channel, device);
|
|
if (version == IDEEXI_UNKNOWN) return false;
|
|
if (version == IDEEXI_V1 && channel == 2) return false;
|
|
|
|
if (version == IDEEXI_V1) {
|
|
// double check
|
|
UCHAR Status;
|
|
if (!IdepReadStatusReg(state, channel, device, &Status) || Status == 0xFF) {
|
|
state->HwVersion = IDEEXI_UNKNOWN;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Select ide0
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_DEVICE_SELECT, 0)) return false;
|
|
// Wait for drive to be ready
|
|
if (!IdepWaitStatus(state, channel, device, 0, SR_BSY, NULL)) return false;
|
|
|
|
// Soft-reset it
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_DEVICE_CONTROL, DCON_NIEN | DCON_SRST)) return false;
|
|
udelay(1);
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_DEVICE_CONTROL, DCON_NIEN)) return false;
|
|
udelay(1);
|
|
|
|
// Ensure registers are valid for ATA device after reset
|
|
{
|
|
UCHAR Register;
|
|
if (!IdepReadRegister8(state, channel, device, IDE_REG_SECTOR_COUNT, &Register)) return false;
|
|
if (Register != 0x01) return false;
|
|
if (!IdepReadRegister8(state, channel, device, IDE_REG_START_SECTOR, &Register)) return false;
|
|
if (Register != 0x01) return false;
|
|
UCHAR CL, CH, Status;
|
|
if (!IdepReadRegister8(state, channel, device, IDE_REG_CYLINDER_LOW, &CL)) return false;
|
|
if (!IdepReadRegister8(state, channel, device, IDE_REG_CYLINDER_HIGH, &CH)) return false;
|
|
if (!IdepReadRegister8(state, channel, device, IDE_REG_STATUS, &Status)) return false;
|
|
if (CL != 0 || CH != 0 || Status == 0) return false;
|
|
}
|
|
|
|
// Wait for drive to be ready
|
|
if (!IdepWaitStatus(state, channel, device, 0, SR_BSY, NULL)) return false;
|
|
|
|
// Write the identify command
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_COMMAND, ATA_CMD_IDENTIFY)) return false;
|
|
|
|
// Wait for drive to respond
|
|
if (!IdepWaitStatus(state, channel, device, SR_DRQ, 0, NULL)) return false;
|
|
udelay(2000);
|
|
|
|
// Read IDENTIFY data by PIO
|
|
PUSHORT buf = (PUSHORT)s_TransferBuffer;
|
|
for (ULONG i = 0; i < 0x200 / sizeof(USHORT); i++) {
|
|
if (!IdepReadData16(state, channel, device, &buf[i])) return false;
|
|
//buf[i] = __builtin_bswap16(buf[i]);
|
|
}
|
|
|
|
// Get the info out of the buffer
|
|
if ((buf[ATA_IDENTIFY_COMMANDSET] & ATA_IDENTIFY_LBA48MASK) != 0) state->Capability = CAPABILITY_LBA48;
|
|
else if ((buf[ATA_IDENTIFY_CAPABILITY] & ATA_IDENTIFY_LBA28MASK) != 0) state->Capability = CAPABILITY_LBA28;
|
|
else state->Capability = CAPABILITY_CHS;
|
|
|
|
|
|
uint64_t NumberOfSectors = 0;
|
|
if (state->Capability == CAPABILITY_LBA48) {
|
|
USHORT Lba[3] = {
|
|
buf[ATA_IDENTIFY_LBA48SECTORS], buf[ATA_IDENTIFY_LBA48SECTORS + 1], buf[ATA_IDENTIFY_LBA48SECTORS + 2]
|
|
};
|
|
|
|
NumberOfSectors = (uint64_t)Lba[2] << 32;
|
|
NumberOfSectors |= (ULONG)Lba[1] << 16;
|
|
NumberOfSectors |= Lba[0];
|
|
state->NumberOfSectors = NumberOfSectors;
|
|
}
|
|
else {
|
|
state->Cylinders = buf[ATA_IDENTIFY_CYLINDERS];
|
|
state->Heads = buf[ATA_IDENTIFY_HEADS];
|
|
state->Sectors = buf[ATA_IDENTIFY_SECTORS];
|
|
NumberOfSectors = ((ULONG)(buf[ATA_IDENTIFY_LBASECTORS + 1]) << 16) | buf[ATA_IDENTIFY_LBASECTORS];
|
|
state->NumberOfSectors = NumberOfSectors;
|
|
}
|
|
|
|
state->SizeInMegabytes = (NumberOfSectors / 2) / 1024;
|
|
state->SizeInGigabytes = (NumberOfSectors / 2) / 1024 / 1024;
|
|
|
|
state->MultipleSectorCount = 1;
|
|
UCHAR MultipleSectorCount = IdepGetReadByte(buf[ATA_IDENTIFY_MULTIPLE]);
|
|
if (MultipleSectorCount != 0) {
|
|
if (IdepWriteRegister8(state, channel, device, IDE_REG_SECTOR_COUNT, MultipleSectorCount)) {
|
|
if (IdepWriteRegister8(state, channel, device, IDE_REG_COMMAND, ATA_CMD_SET_MULTIPLE_MODE)) {
|
|
// Wait for drive to respond
|
|
if (IdepWaitStatus(state, channel, device, 0, SR_ERR, NULL)) state->MultipleSectorCount = MultipleSectorCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy serial string
|
|
memcpy(state->Serial, &buf[ATA_IDENTIFY_SERIAL], 20);
|
|
ob_ide_fixup_string((UCHAR*)state->Serial, 20);
|
|
|
|
// same for model string
|
|
memcpy(state->Model, &buf[ATA_IDENTIFY_MODEL], 40);
|
|
ob_ide_fixup_string((UCHAR*)state->Model, 40);
|
|
|
|
state->Initialised = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool IdepReadSectors(PIDE_STATE state, ULONG channel, ULONG device, uint64_t Sector, ULONG SectorCount, PVOID buffer) {
|
|
bool NeedsLba48 = ((Sector + SectorCount) > (1ULL << 28));
|
|
|
|
if (SectorCount > state->MultipleSectorCount) return false;
|
|
if ((Sector + SectorCount) > state->NumberOfSectors) return false;
|
|
if (state->Capability != CAPABILITY_LBA48 && NeedsLba48) return false;
|
|
if (!NeedsLba48 && SectorCount >= 0x100) return false;
|
|
// Wait for drive to be ready
|
|
if (!IdepWaitStatus(state, channel, device, 0, 0, NULL)) return false;
|
|
|
|
if (NeedsLba48) {
|
|
// LBA48
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_DEVICE_SELECT, HEAD_USE_LBA)) return false;
|
|
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_SECTOR_COUNT, (SectorCount >> 8) & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA0, (Sector >> 24) & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA1, (Sector >> 32) & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA2, (Sector >> 40) & 0xFF)) return false;
|
|
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_SECTOR_COUNT, SectorCount & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA0, Sector & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA1, (Sector >> 8) & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA2, (Sector >> 16) & 0xFF)) return false;
|
|
}
|
|
else if (state->Capability >= CAPABILITY_LBA28) {
|
|
// LBA28
|
|
UCHAR SectorTop = (Sector >> 24) & 0xF;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_DEVICE_SELECT, 0xE0 | SectorTop)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_SECTOR_COUNT, SectorCount & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA0, Sector & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA1, (Sector >> 8) & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA2, (Sector >> 16) & 0xFF)) return false;
|
|
}
|
|
else {
|
|
// CHS
|
|
ULONG track = (Sector / state->Sectors);
|
|
ULONG sect = (Sector % state->Sectors) + 1;
|
|
ULONG head = (track % state->Heads);
|
|
ULONG cyl = (track / state->Heads);
|
|
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_DEVICE_SELECT, head)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_SECTOR_COUNT, SectorCount & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_START_SECTOR, sect)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_CYLINDER_LOW, cyl & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_CYLINDER_HIGH, (cyl >> 8) & 0xFF)) return false;
|
|
}
|
|
|
|
UCHAR Command;
|
|
if (SectorCount > 1) {
|
|
Command = NeedsLba48 ? ATA_CMD_READ_MULTIPLE_EXT : ATA_CMD_READ_MULTIPLE;
|
|
}
|
|
else {
|
|
Command = NeedsLba48 ? ATA_CMD_READ_SECTOR_EXT : ATA_CMD_READ_SECTOR;
|
|
}
|
|
|
|
ULONG Length = SectorCount * IDE_SECTOR_SIZE;
|
|
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_COMMAND, Command)) return false;
|
|
|
|
UCHAR Status;
|
|
if (!IdepWaitStatus(state, channel, device, 0, SR_ERR, &Status)) {
|
|
return false;
|
|
}
|
|
|
|
if (!IdepWaitStatus(state, channel, device, SR_DRQ, 0, NULL)) return false;
|
|
|
|
if (!IdepReadBuffer(state, channel, device, buffer, Length)) return false;
|
|
|
|
if (!IdepReadStatusReg(state, channel, device, &Status)) return false;
|
|
return (Status & SR_ERR) == 0;
|
|
}
|
|
|
|
static bool IdepWriteSectors(PIDE_STATE state, ULONG channel, ULONG device, uint64_t Sector, ULONG SectorCount, PVOID buffer) {
|
|
bool NeedsLba48 = ((Sector + SectorCount) > (1ULL << 28));
|
|
|
|
if (SectorCount > state->MultipleSectorCount) return false;
|
|
if ((Sector + SectorCount) > state->NumberOfSectors) return false;
|
|
if (state->Capability != CAPABILITY_LBA48 && NeedsLba48) return false;
|
|
if (!NeedsLba48 && SectorCount >= 0x100) return false;
|
|
// Wait for drive to be ready
|
|
if (!IdepWaitStatus(state, channel, device, 0, 0, NULL)) return false;
|
|
|
|
if (NeedsLba48) {
|
|
// LBA48
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_DEVICE_SELECT, HEAD_USE_LBA)) return false;
|
|
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_SECTOR_COUNT, (SectorCount >> 8) & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA0, (Sector >> 24) & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA1, (Sector >> 32) & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA2, (Sector >> 40) & 0xFF)) return false;
|
|
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_SECTOR_COUNT, SectorCount & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA0, Sector & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA1, (Sector >> 8) & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA2, (Sector >> 16) & 0xFF)) return false;
|
|
}
|
|
else if (state->Capability >= CAPABILITY_LBA28) {
|
|
// LBA28
|
|
UCHAR SectorTop = (Sector >> 24) & 0xF;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_DEVICE_SELECT, 0xE0 | SectorTop)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_SECTOR_COUNT, SectorCount & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA0, Sector & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA1, (Sector >> 8) & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_LBA2, (Sector >> 16) & 0xFF)) return false;
|
|
}
|
|
else {
|
|
// CHS
|
|
ULONG track = (Sector / state->Sectors);
|
|
ULONG sect = (Sector % state->Sectors) + 1;
|
|
ULONG head = (track % state->Heads);
|
|
ULONG cyl = (track / state->Heads);
|
|
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_DEVICE_SELECT, head)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_SECTOR_COUNT, SectorCount & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_START_SECTOR, sect)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_CYLINDER_LOW, cyl & 0xFF)) return false;
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_CYLINDER_HIGH, (cyl >> 8) & 0xFF)) return false;
|
|
}
|
|
|
|
UCHAR Command;
|
|
if (SectorCount > 1) {
|
|
Command = NeedsLba48 ? ATA_CMD_WRITE_MULTIPLE_EXT : ATA_CMD_WRITE_MULTIPLE;
|
|
}
|
|
else {
|
|
Command = NeedsLba48 ? ATA_CMD_WRITE_SECTOR_EXT : ATA_CMD_WRITE_SECTOR;
|
|
}
|
|
|
|
ULONG Length = SectorCount * IDE_SECTOR_SIZE;
|
|
|
|
if (!IdepWriteRegister8(state, channel, device, IDE_REG_COMMAND, Command)) return false;
|
|
|
|
UCHAR Status;
|
|
if (!IdepWaitStatus(state, channel, device, 0, SR_ERR, &Status)) {
|
|
return false;
|
|
}
|
|
|
|
if (!IdepWaitStatus(state, channel, device, SR_DRQ, 0, NULL)) return false;
|
|
|
|
if (!IdepWriteBuffer(state, channel, device, buffer, Length)) return false;
|
|
|
|
if (!IdepWaitStatus(state, channel, device, 0, 0, NULL)) return false;
|
|
|
|
if (!IdepReadStatusReg(state, channel, device, &Status)) return false;
|
|
return (Status & SR_ERR) == 0;
|
|
}
|
|
|
|
static PIDE_STATE IdepGetMountedState(EXI_IDE_DRIVE drive) {
|
|
if ((ULONG)drive >= IDE_DRIVE_COUNT) return false;
|
|
PIDE_STATE state = &s_IdeState[drive];
|
|
|
|
if (state->Initialised) {
|
|
return state;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ULONG IdepGetExiChannel(EXI_IDE_DRIVE drive) {
|
|
// 0,1,0,2
|
|
ULONG driveIdx = (ULONG)drive;
|
|
if ((driveIdx & 1) == 0) return 0;
|
|
return (driveIdx + 1) / 2;
|
|
}
|
|
|
|
static ULONG IdepGetExiDevice(EXI_IDE_DRIVE drive) {
|
|
// 0,0,2,0
|
|
return (drive == IDE_DRIVE_SP1) ? 2 : 0;
|
|
}
|
|
|
|
bool IdeexiIsMounted(EXI_IDE_DRIVE drive) {
|
|
return IdepGetMountedState(drive) != NULL;
|
|
}
|
|
|
|
bool IdeexiMount(EXI_IDE_DRIVE drive) {
|
|
if ((ULONG)drive >= IDE_DRIVE_COUNT) return false;
|
|
PIDE_STATE state = &s_IdeState[drive];
|
|
|
|
if (state->Initialised) {
|
|
return true;
|
|
}
|
|
|
|
ULONG channel = IdepGetExiChannel(drive);
|
|
ULONG device = IdepGetExiDevice(drive);
|
|
|
|
return IdepMountDrive(state, channel, device);
|
|
}
|
|
|
|
ULONG IdeexiTransferrableSectorCount(EXI_IDE_DRIVE drive) {
|
|
if ((ULONG)drive >= IDE_DRIVE_COUNT) return 0;
|
|
|
|
PIDE_STATE state = &s_IdeState[drive];
|
|
if (!state->Initialised) return 0;
|
|
|
|
return state->MultipleSectorCount;
|
|
}
|
|
|
|
uint64_t IdeexiSectorCount(EXI_IDE_DRIVE drive) {
|
|
if ((ULONG)drive >= IDE_DRIVE_COUNT) return 0;
|
|
|
|
PIDE_STATE state = &s_IdeState[drive];
|
|
if (!state->Initialised) return 0;
|
|
|
|
return state->NumberOfSectors;
|
|
}
|
|
|
|
uint64_t IdeexiReadBlocks(EXI_IDE_DRIVE drive, PVOID buffer, uint64_t sector, ULONG count) {
|
|
if (count == 0) return 0;
|
|
|
|
PIDE_STATE state = IdepGetMountedState(drive);
|
|
if (state == NULL) return 0;
|
|
|
|
ULONG channel = IdepGetExiChannel(drive);
|
|
ULONG device = IdepGetExiDevice(drive);
|
|
PUCHAR buf8 = (PUCHAR)buffer;
|
|
|
|
uint64_t readCount = 0;
|
|
|
|
for (ULONG i = 0; i < count; i += state->MultipleSectorCount) {
|
|
ULONG thisCount = state->MultipleSectorCount;
|
|
if ((count - i) < thisCount) thisCount = (count - i);
|
|
|
|
if (!IdepReadSectors(state, channel, device, sector + i, thisCount, buf8)) break;
|
|
buf8 += (thisCount * IDE_SECTOR_SIZE);
|
|
readCount += thisCount;
|
|
}
|
|
|
|
return readCount;
|
|
}
|
|
|
|
uint64_t IdeexiWriteBlocks(EXI_IDE_DRIVE drive, PVOID buffer, uint64_t sector, ULONG count) {
|
|
if (count == 0) return 0;
|
|
|
|
PIDE_STATE state = IdepGetMountedState(drive);
|
|
if (state == NULL) return 0;
|
|
|
|
ULONG channel = IdepGetExiChannel(drive);
|
|
ULONG device = IdepGetExiDevice(drive);
|
|
PUCHAR buf8 = (PUCHAR)buffer;
|
|
|
|
uint64_t readCount = 0;
|
|
|
|
for (ULONG i = 0; i < count; i += state->MultipleSectorCount) {
|
|
ULONG thisCount = state->MultipleSectorCount;
|
|
if ((count - i) < thisCount) thisCount = (count - i);
|
|
|
|
if (!IdepWriteSectors(state, channel, device, sector + i, thisCount, buf8)) break;
|
|
buf8 += (thisCount * IDE_SECTOR_SIZE);
|
|
readCount += thisCount;
|
|
}
|
|
|
|
return readCount;
|
|
}
|
|
|
|
void IdeexiInit(void) {
|
|
// Initialise every drive.
|
|
for (ULONG i = 0; i < IDE_DRIVE_COUNT; i++) {
|
|
memset(&s_IdeState[i], 0, sizeof(s_IdeState));
|
|
}
|
|
|
|
// Try to mount every drive.
|
|
for (ULONG i = 0; i < IDE_DRIVE_COUNT; i++) {
|
|
PIDE_STATE state = &s_IdeState[i];
|
|
ULONG channel = IdepGetExiChannel(i);
|
|
ULONG device = IdepGetExiDevice(i);
|
|
IdepMountDrive(state, channel, device);
|
|
}
|
|
} |