Add disk I/O (well, input only, no output) for the Xbox
[reactos.git] / freeldr / freeldr / arch / i386 / xboxdisk.c
diff --git a/freeldr/freeldr/arch/i386/xboxdisk.c b/freeldr/freeldr/arch/i386/xboxdisk.c
new file mode 100644 (file)
index 0000000..8a290bd
--- /dev/null
@@ -0,0 +1,398 @@
+/* $Id: xboxdisk.c,v 1.1 2004/11/09 23:36:19 gvg Exp $
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Note: mostly ripped from atapi.c
+ *
+ */
+
+#include "freeldr.h"
+#include "debug.h"
+#include "hardware.h"
+#include "machxbox.h"
+#include "portio.h"
+#include "rtl.h"
+
+#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((PU16)((Address) + IDE_REG_DATA_PORT), (PU16)(Buffer), (Count) / 2))
+#define IDEWriteBlock(Address, Buffer, Count) \
+  (WRITE_PORT_BUFFER_USHORT((PU16)((Address) + IDE_REG_DATA_PORT), (PU16)(Buffer), (Count) / 2))
+
+#define IDEReadBlock32(Address, Buffer, Count) \
+  (READ_PORT_BUFFER_ULONG((PU32)((Address) + IDE_REG_DATA_PORT), (PU32)(Buffer), (Count) / 4))
+#define IDEWriteBlock32(Address, Buffer, Count) \
+  (WRITE_PORT_BUFFER_ULONG((PU32)((Address) + IDE_REG_DATA_PORT), (PU32)(Buffer), (Count) / 4))
+
+#define IDEReadWord(Address) \
+  (READ_PORT_USHORT((PU16)((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)))
+
+/*  XboxDiskPolledRead
+ *
+ *  DESCRIPTION:
+ *    Read a sector of data from the drive in a polled fashion.
+ *
+ *  RUN LEVEL:
+ *    PASSIVE_LEVEL
+ *
+ *  ARGUMENTS:
+ *    U32   CommandPort   Address of command port for drive
+ *    U32   ControlPort   Address of control port for drive
+ *    U8    PreComp       Value to write to precomp register
+ *    U8    SectorCnt     Value to write to sectorCnt register
+ *    U8    SectorNum     Value to write to sectorNum register
+ *    U8    CylinderLow   Value to write to CylinderLow register
+ *    U8    CylinderHigh  Value to write to CylinderHigh register
+ *    U8    DrvHead       Value to write to Drive/Head register
+ *    U8    Command       Value to write to Command register
+ *    PVOID Buffer        Buffer for output data
+ *
+ *  RETURNS:
+ *    BOOL: TRUE success, FALSE error
+ */
+
+static BOOL
+XboxDiskPolledRead(U32 CommandPort,
+                   U32 ControlPort,
+                   U8 PreComp,
+                   U8 SectorCnt,
+                   U8 SectorNum,
+                   U8 CylinderLow,
+                   U8 CylinderHigh,
+                   U8 DrvHead,
+                   U8 Command,
+                   PVOID Buffer)
+{
+  U32 SectorCount = 0;
+  U32 RetryCount;
+  BOOL Junk = FALSE;
+  U8 Status;
+
+  /* Wait for BUSY to clear */
+  for (RetryCount = 0; RetryCount < IDE_MAX_BUSY_RETRIES; RetryCount++)
+    {
+      Status = IDEReadStatus(CommandPort);
+      if (!(Status & IDE_SR_BUSY))
+        {
+          break;
+        }
+      KeStallExecutionProcessor(10);
+    }
+  DbgPrint((DPRINT_DISK, "status=0x%x\n", Status));
+  DbgPrint((DPRINT_DISK, "waited %d usecs for busy to clear\n", RetryCount * 10));
+  if (RetryCount >= IDE_MAX_BUSY_RETRIES)
+    {
+      DbgPrint((DPRINT_DISK, "Drive is BUSY for too long\n"));
+      return FALSE;
+    }
+
+  /*  Write Drive/Head to select drive  */
+  IDEWriteDriveHead(CommandPort, IDE_DH_FIXED | DrvHead);
+  KeStallExecutionProcessor(500);
+
+  /* Disable interrupts */
+  IDEWriteDriveControl(ControlPort, IDE_DC_nIEN);
+  KeStallExecutionProcessor(500);
+
+  /*  Issue command to drive  */
+  if (DrvHead & IDE_DH_LBA)
+    {
+      DbgPrint((DPRINT_DISK, "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
+    {
+      DbgPrint((DPRINT_DISK, "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);
+  KeStallExecutionProcessor(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);
+             KeStallExecutionProcessor(50);
+             IDEReadStatus(CommandPort);
+
+             return FALSE;
+           }
+
+         if (Status & IDE_SR_DRQ)
+           {
+             break;
+           }
+         else
+           {
+             IDEWriteDriveControl(ControlPort, 0);
+             KeStallExecutionProcessor(50);
+             IDEReadStatus(CommandPort);
+
+             return FALSE;
+           }
+       }
+      KeStallExecutionProcessor(10);
+    }
+
+  /*  timed out  */
+  if (RetryCount >= IDE_MAX_POLL_RETRIES)
+    {
+      IDEWriteDriveControl(ControlPort, 0);
+      KeStallExecutionProcessor(50);
+      IDEReadStatus(CommandPort);
+
+      return FALSE;
+    }
+
+  while (1)
+    {
+      /*  Read data into buffer  */
+      if (Junk == FALSE)
+       {
+         IDEReadBlock(CommandPort, Buffer, IDE_SECTOR_BUF_SZ);
+         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);
+                 KeStallExecutionProcessor(50);
+                 IDEReadStatus(CommandPort);
+
+                 return FALSE;
+               }
+             if (Status & IDE_SR_DRQ)
+               {
+                 if (SectorCount >= SectorCnt)
+                   {
+                     DbgPrint((DPRINT_DISK, "Buffer size exceeded!\n"));
+                     Junk = TRUE;
+                   }
+                 break;
+               }
+             else
+               {
+                 if (SectorCount > SectorCnt)
+                   {
+                     DbgPrint((DPRINT_DISK, "Read %lu sectors of junk!\n",
+                                SectorCount - SectorCnt));
+                   }
+                 IDEWriteDriveControl(ControlPort, 0);
+                 KeStallExecutionProcessor(50);
+                 IDEReadStatus(CommandPort);
+
+                 return TRUE;
+               }
+           }
+       }
+    }
+}
+
+BOOL
+XboxDiskReadLogicalSectors(U32 DriveNumber, U64 SectorNumber, U32 SectorCount, PVOID Buffer)
+{
+  U32 StartSector;
+  U8 Count;
+
+  if (DriveNumber < 0x80 || 2 <= (DriveNumber & 0x0f))
+    {
+      /* Xbox has only 1 IDE controller and no floppy */
+      DbgPrint((DPRINT_DISK, "Invalid drive number\n"));
+      return FALSE;
+    }
+
+  if (UINT64_C(0) != ((SectorNumber + SectorCount) & UINT64_C(0xfffffffff0000000)))
+    {
+      DbgPrint((DPRINT_DISK, "48bit LBA required but not implemented\n"));
+      return FALSE;
+    }
+
+  StartSector = (U32) SectorNumber;
+  while (0 < SectorCount)
+    {
+      Count = (SectorCount <= 255 ? 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 |
+                               (0 == (DriveNumber & 0x0f) ? 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;
+}
+
+/* EOF */