/* * FreeLoader * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Note: mostly ripped from atapi.c * Some of this code was based on knowledge and/or code developed * by the Xbox Linux group: http://www.xbox-linux.org * */ #include #include DBG_DEFAULT_CHANNEL(DISK); #define XBOX_IDE_COMMAND_PORT 0x1f0 #define XBOX_IDE_CONTROL_PORT 0x170 #define IDE_SECTOR_BUF_SZ 512 #define IDE_MAX_POLL_RETRIES 100000 #define IDE_MAX_BUSY_RETRIES 50000 /* Control Block offsets and masks */ #define IDE_REG_ALT_STATUS 0x0000 #define IDE_REG_DEV_CNTRL 0x0000 /* device control register */ #define IDE_DC_SRST 0x04 /* drive reset (both drives) */ #define IDE_DC_nIEN 0x02 /* IRQ enable (active low) */ #define IDE_REG_DRV_ADDR 0x0001 /* Command Block offsets and masks */ #define IDE_REG_DATA_PORT 0x0000 #define IDE_REG_ERROR 0x0001 /* error register */ #define IDE_ER_AMNF 0x01 /* addr mark not found */ #define IDE_ER_TK0NF 0x02 /* track 0 not found */ #define IDE_ER_ABRT 0x04 /* command aborted */ #define IDE_ER_MCR 0x08 /* media change requested */ #define IDE_ER_IDNF 0x10 /* ID not found */ #define IDE_ER_MC 0x20 /* Media changed */ #define IDE_ER_UNC 0x40 /* Uncorrectable data error */ #define IDE_REG_PRECOMP 0x0001 #define IDE_REG_SECTOR_CNT 0x0002 #define IDE_REG_SECTOR_NUM 0x0003 #define IDE_REG_CYL_LOW 0x0004 #define IDE_REG_CYL_HIGH 0x0005 #define IDE_REG_DRV_HEAD 0x0006 #define IDE_DH_FIXED 0xA0 #define IDE_DH_LBA 0x40 #define IDE_DH_HDMASK 0x0F #define IDE_DH_DRV0 0x00 #define IDE_DH_DRV1 0x10 #define IDE_REG_STATUS 0x0007 #define IDE_SR_BUSY 0x80 #define IDE_SR_DRDY 0x40 #define IDE_SR_WERR 0x20 #define IDE_SR_DRQ 0x08 #define IDE_SR_ERR 0x01 #define IDE_REG_COMMAND 0x0007 /* IDE/ATA commands */ #define IDE_CMD_RESET 0x08 #define IDE_CMD_READ 0x20 #define IDE_CMD_READ_RETRY 0x21 #define IDE_CMD_WRITE 0x30 #define IDE_CMD_WRITE_RETRY 0x31 #define IDE_CMD_PACKET 0xA0 #define IDE_CMD_READ_MULTIPLE 0xC4 #define IDE_CMD_WRITE_MULTIPLE 0xC5 #define IDE_CMD_READ_DMA 0xC8 #define IDE_CMD_WRITE_DMA 0xCA #define IDE_CMD_FLUSH_CACHE 0xE7 #define IDE_CMD_FLUSH_CACHE_EXT 0xEA #define IDE_CMD_IDENT_ATA_DRV 0xEC #define IDE_CMD_IDENT_ATAPI_DRV 0xA1 #define IDE_CMD_GET_MEDIA_STATUS 0xDA /* * Access macros for command registers * Each macro takes an address of the command port block, and data */ #define IDEReadError(Address) \ (READ_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_ERROR))) #define IDEWritePrecomp(Address, Data) \ (WRITE_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_PRECOMP), (Data))) #define IDEReadSectorCount(Address) \ (READ_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_SECTOR_CNT))) #define IDEWriteSectorCount(Address, Data) \ (WRITE_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_SECTOR_CNT), (Data))) #define IDEReadSectorNum(Address) \ (READ_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_SECTOR_NUM))) #define IDEWriteSectorNum(Address, Data) \ (WRITE_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_SECTOR_NUM), (Data))) #define IDEReadCylinderLow(Address) \ (READ_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_CYL_LOW))) #define IDEWriteCylinderLow(Address, Data) \ (WRITE_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_CYL_LOW), (Data))) #define IDEReadCylinderHigh(Address) \ (READ_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_CYL_HIGH))) #define IDEWriteCylinderHigh(Address, Data) \ (WRITE_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_CYL_HIGH), (Data))) #define IDEReadDriveHead(Address) \ (READ_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_DRV_HEAD))) #define IDEWriteDriveHead(Address, Data) \ (WRITE_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_DRV_HEAD), (Data))) #define IDEReadStatus(Address) \ (READ_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_STATUS))) #define IDEWriteCommand(Address, Data) \ (WRITE_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_COMMAND), (Data))) #define IDEReadDMACommand(Address) \ (READ_PORT_UCHAR((PUCHAR)((Address)))) #define IDEWriteDMACommand(Address, Data) \ (WRITE_PORT_UCHAR((PUCHAR)((Address)), (Data))) #define IDEReadDMAStatus(Address) \ (READ_PORT_UCHAR((PUCHAR)((Address) + 2))) #define IDEWriteDMAStatus(Address, Data) \ (WRITE_PORT_UCHAR((PUCHAR)((Address) + 2), (Data))) #define IDEWritePRDTable(Address, Data) \ (WRITE_PORT_ULONG((PULONG)((Address) + 4), (Data))) /* * Data block read and write commands */ #define IDEReadBlock(Address, Buffer, Count) \ (READ_PORT_BUFFER_USHORT((PUSHORT)((Address) + IDE_REG_DATA_PORT), (PUSHORT)(Buffer), (Count) / 2)) #define IDEWriteBlock(Address, Buffer, Count) \ (WRITE_PORT_BUFFER_USHORT((PUSHORT)((Address) + IDE_REG_DATA_PORT), (PUSHORT)(Buffer), (Count) / 2)) #define IDEReadBlock32(Address, Buffer, Count) \ (READ_PORT_BUFFER_ULONG((PULONG)((Address) + IDE_REG_DATA_PORT), (PULONG)(Buffer), (Count) / 4)) #define IDEWriteBlock32(Address, Buffer, Count) \ (WRITE_PORT_BUFFER_ULONG((PULONG)((Address) + IDE_REG_DATA_PORT), (PULONG)(Buffer), (Count) / 4)) #define IDEReadWord(Address) \ (READ_PORT_USHORT((PUSHORT)((Address) + IDE_REG_DATA_PORT))) /* * Access macros for control registers * Each macro takes an address of the control port blank and data */ #define IDEReadAltStatus(Address) \ (READ_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_ALT_STATUS))) #define IDEWriteDriveControl(Address, Data) \ (WRITE_PORT_UCHAR((PUCHAR)((Address) + IDE_REG_DEV_CNTRL), (Data))) /* IDE_DRIVE_IDENTIFY */ typedef struct _IDE_DRIVE_IDENTIFY { USHORT ConfigBits; /*00*/ USHORT LogicalCyls; /*01*/ USHORT Reserved02; /*02*/ USHORT LogicalHeads; /*03*/ USHORT BytesPerTrack; /*04*/ USHORT BytesPerSector; /*05*/ USHORT SectorsPerTrack; /*06*/ UCHAR InterSectorGap; /*07*/ UCHAR InterSectorGapSize; UCHAR Reserved08H; /*08*/ UCHAR BytesInPLO; USHORT VendorUniqueCnt; /*09*/ UCHAR SerialNumber[20]; /*10*/ USHORT ControllerType; /*20*/ USHORT BufferSize; /*21*/ USHORT ECCByteCnt; /*22*/ UCHAR FirmwareRev[8]; /*23*/ UCHAR ModelNumber[40]; /*27*/ USHORT RWMultImplemented; /*47*/ USHORT DWordIo; /*48*/ USHORT Capabilities; /*49*/ #define IDE_DRID_STBY_SUPPORTED 0x2000 #define IDE_DRID_IORDY_SUPPORTED 0x0800 #define IDE_DRID_IORDY_DISABLE 0x0400 #define IDE_DRID_LBA_SUPPORTED 0x0200 #define IDE_DRID_DMA_SUPPORTED 0x0100 USHORT Reserved50; /*50*/ USHORT MinPIOTransTime; /*51*/ USHORT MinDMATransTime; /*52*/ USHORT TMFieldsValid; /*53*/ USHORT TMCylinders; /*54*/ USHORT TMHeads; /*55*/ USHORT TMSectorsPerTrk; /*56*/ USHORT TMCapacityLo; /*57*/ USHORT TMCapacityHi; /*58*/ USHORT RWMultCurrent; /*59*/ USHORT TMSectorCountLo; /*60*/ USHORT TMSectorCountHi; /*61*/ USHORT DmaModes; /*62*/ USHORT MultiDmaModes; /*63*/ USHORT Reserved64[5]; /*64*/ USHORT Reserved69[2]; /*69*/ USHORT Reserved71[4]; /*71*/ USHORT MaxQueueDepth; /*75*/ USHORT Reserved76[4]; /*76*/ USHORT MajorRevision; /*80*/ USHORT MinorRevision; /*81*/ USHORT SupportedFeatures82; /*82*/ USHORT SupportedFeatures83; /*83*/ USHORT SupportedFeatures84; /*84*/ USHORT EnabledFeatures85; /*85*/ USHORT EnabledFeatures86; /*86*/ USHORT EnabledFeatures87; /*87*/ USHORT UltraDmaModes; /*88*/ USHORT Reserved89[11]; /*89*/ USHORT Max48BitAddress[4]; /*100*/ USHORT Reserved104[151]; /*104*/ USHORT Checksum; /*255*/ } IDE_DRIVE_IDENTIFY, *PIDE_DRIVE_IDENTIFY; /* XboxDiskPolledRead * * DESCRIPTION: * Read a sector of data from the drive in a polled fashion. * * RUN LEVEL: * PASSIVE_LEVEL * * ARGUMENTS: * ULONG CommandPort Address of command port for drive * ULONG ControlPort Address of control port for drive * UCHAR PreComp Value to write to precomp register * UCHAR SectorCnt Value to write to sectorCnt register * UCHAR SectorNum Value to write to sectorNum register * UCHAR CylinderLow Value to write to CylinderLow register * UCHAR CylinderHigh Value to write to CylinderHigh register * UCHAR DrvHead Value to write to Drive/Head register * UCHAR Command Value to write to Command register * PVOID Buffer Buffer for output data * * RETURNS: * BOOLEAN: TRUE success, FALSE error */ static BOOLEAN XboxDiskPolledRead(ULONG CommandPort, ULONG ControlPort, UCHAR PreComp, UCHAR SectorCnt, UCHAR SectorNum, UCHAR CylinderLow, UCHAR CylinderHigh, UCHAR DrvHead, UCHAR Command, PVOID Buffer) { ULONG SectorCount = 0; ULONG RetryCount; BOOLEAN Junk = FALSE; UCHAR Status; /* Wait for BUSY to clear */ for (RetryCount = 0; RetryCount < IDE_MAX_BUSY_RETRIES; RetryCount++) { Status = IDEReadStatus(CommandPort); if (!(Status & IDE_SR_BUSY)) break; StallExecutionProcessor(10); } TRACE("status=0x%x\n", Status); TRACE("waited %d usecs for busy to clear\n", RetryCount * 10); if (RetryCount >= IDE_MAX_BUSY_RETRIES) { WARN("Drive is BUSY for too long\n"); return FALSE; } /* Write Drive/Head to select drive */ IDEWriteDriveHead(CommandPort, IDE_DH_FIXED | DrvHead); StallExecutionProcessor(500); /* Disable interrupts */ IDEWriteDriveControl(ControlPort, IDE_DC_nIEN); StallExecutionProcessor(500); /* Issue command to drive */ if (DrvHead & IDE_DH_LBA) { TRACE("READ:DRV=%d:LBA=1:BLK=%d:SC=0x%x:CM=0x%x\n", DrvHead & IDE_DH_DRV1 ? 1 : 0, ((DrvHead & 0x0f) << 24) + (CylinderHigh << 16) + (CylinderLow << 8) + SectorNum, SectorCnt, Command); } else { TRACE("READ:DRV=%d:LBA=0:CH=0x%x:CL=0x%x:HD=0x%x:SN=0x%x:SC=0x%x:CM=0x%x\n", DrvHead & IDE_DH_DRV1 ? 1 : 0, CylinderHigh, CylinderLow, DrvHead & 0x0f, SectorNum, SectorCnt, Command); } /* Setup command parameters */ IDEWritePrecomp(CommandPort, PreComp); IDEWriteSectorCount(CommandPort, SectorCnt); IDEWriteSectorNum(CommandPort, SectorNum); IDEWriteCylinderHigh(CommandPort, CylinderHigh); IDEWriteCylinderLow(CommandPort, CylinderLow); IDEWriteDriveHead(CommandPort, IDE_DH_FIXED | DrvHead); /* Issue the command */ IDEWriteCommand(CommandPort, Command); StallExecutionProcessor(50); /* Wait for DRQ or error */ for (RetryCount = 0; RetryCount < IDE_MAX_POLL_RETRIES; RetryCount++) { Status = IDEReadStatus(CommandPort); if (!(Status & IDE_SR_BUSY)) { if (Status & IDE_SR_ERR) { IDEWriteDriveControl(ControlPort, 0); StallExecutionProcessor(50); IDEReadStatus(CommandPort); return FALSE; } if (Status & IDE_SR_DRQ) { break; } else { IDEWriteDriveControl(ControlPort, 0); StallExecutionProcessor(50); IDEReadStatus(CommandPort); return FALSE; } } StallExecutionProcessor(10); } /* Timed out */ if (RetryCount >= IDE_MAX_POLL_RETRIES) { IDEWriteDriveControl(ControlPort, 0); StallExecutionProcessor(50); IDEReadStatus(CommandPort); return FALSE; } while (1) { /* Read data into buffer */ if (Junk == FALSE) { IDEReadBlock(CommandPort, Buffer, IDE_SECTOR_BUF_SZ); Buffer = (PVOID)((ULONG_PTR)Buffer + IDE_SECTOR_BUF_SZ); } else { UCHAR JunkBuffer[IDE_SECTOR_BUF_SZ]; IDEReadBlock(CommandPort, JunkBuffer, IDE_SECTOR_BUF_SZ); } SectorCount++; /* Check for error or more sectors to read */ for (RetryCount = 0; RetryCount < IDE_MAX_BUSY_RETRIES; RetryCount++) { Status = IDEReadStatus(CommandPort); if (!(Status & IDE_SR_BUSY)) { if (Status & IDE_SR_ERR) { IDEWriteDriveControl(ControlPort, 0); StallExecutionProcessor(50); IDEReadStatus(CommandPort); return FALSE; } if (Status & IDE_SR_DRQ) { if (SectorCount >= SectorCnt) { TRACE("Buffer size exceeded!\n"); Junk = TRUE; } break; } else { if (SectorCount > SectorCnt) { TRACE("Read %lu sectors of junk!\n", SectorCount - SectorCnt); } IDEWriteDriveControl(ControlPort, 0); StallExecutionProcessor(50); IDEReadStatus(CommandPort); return TRUE; } } } } } BOOLEAN XboxDiskReadLogicalSectors(UCHAR DriveNumber, ULONGLONG SectorNumber, ULONG SectorCount, PVOID Buffer) { ULONG StartSector; UCHAR Count; if (DriveNumber < 0x80 || (DriveNumber & 0x0f) >= 2) { /* Xbox has only 1 IDE controller and no floppy */ WARN("Invalid drive number\n"); return FALSE; } if (((SectorNumber + SectorCount) & UINT64_C(0xfffffffff0000000)) != UINT64_C(0)) { FIXME("48bit LBA required but not implemented\n"); return FALSE; } StartSector = (ULONG) SectorNumber; while (SectorCount > 0) { Count = (SectorCount <= 255 ? (UCHAR)SectorCount : 255); if (!XboxDiskPolledRead(XBOX_IDE_COMMAND_PORT, XBOX_IDE_CONTROL_PORT, 0, Count, StartSector & 0xff, (StartSector >> 8) & 0xff, (StartSector >> 16) & 0xff, ((StartSector >> 24) & 0x0f) | IDE_DH_LBA | ((DriveNumber & 0x0f) == 0 ? IDE_DH_DRV0 : IDE_DH_DRV1), IDE_CMD_READ, Buffer)) { return FALSE; } SectorCount -= Count; Buffer = (PVOID) ((PCHAR) Buffer + Count * IDE_SECTOR_BUF_SZ); } return TRUE; } BOOLEAN XboxDiskGetDriveGeometry(UCHAR DriveNumber, PGEOMETRY Geometry) { IDE_DRIVE_IDENTIFY DrvParms; ULONG i; BOOLEAN Atapi; Atapi = FALSE; /* FIXME */ /* Get the Drive Identify block from drive or die */ if (!XboxDiskPolledRead(XBOX_IDE_COMMAND_PORT, XBOX_IDE_CONTROL_PORT, 0, 1, 0, 0, 0, ((DriveNumber & 0x0f) == 0 ? IDE_DH_DRV0 : IDE_DH_DRV1), (Atapi ? IDE_CMD_IDENT_ATAPI_DRV : IDE_CMD_IDENT_ATA_DRV), (PUCHAR) &DrvParms)) { ERR("XboxDiskPolledRead() failed\n"); return FALSE; } Geometry->Cylinders = DrvParms.LogicalCyls; Geometry->Heads = DrvParms.LogicalHeads; Geometry->Sectors = DrvParms.SectorsPerTrack; if (!Atapi && (DrvParms.Capabilities & IDE_DRID_LBA_SUPPORTED) != 0) { /* LBA ATA drives always have a sector size of 512 */ Geometry->BytesPerSector = 512; } else { TRACE("BytesPerSector %d\n", DrvParms.BytesPerSector); if (DrvParms.BytesPerSector == 0) { Geometry->BytesPerSector = 512; } else { for (i = 1 << 15; i; i /= 2) { if ((DrvParms.BytesPerSector & i) != 0) { Geometry->BytesPerSector = i; break; } } } } TRACE("Cylinders %d\n", Geometry->Cylinders); TRACE("Heads %d\n", Geometry->Heads); TRACE("Sectors %d\n", Geometry->Sectors); TRACE("BytesPerSector %d\n", Geometry->BytesPerSector); return TRUE; } ULONG XboxDiskGetCacheableBlockCount(UCHAR DriveNumber) { /* 64 seems a nice number, it is used by the machpc code for LBA devices */ return 64; } /* EOF */