/* $Id: Ide.c 33540 2010-10-28 09:27:05Z vboxsync $ */ /** @file * Ide.c */ /* * Copyright (C) 2009-2010 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ /** @file The file containing the helper functions implement of the Ide Bus driver Copyright (c) 2006 - 2008, Intel Corporation All rights reserved. This program and the accompanying materials are licensed and made available under the terms and conditions of the BSD License which accompanies this distribution. The full text of the license may be found at http://opensource.org/licenses/bsd-license.php THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. **/ #include "IdeBus.h" BOOLEAN ChannelDeviceDetected = FALSE; BOOLEAN SlaveDeviceExist = FALSE; UINT8 SlaveDeviceType = INVALID_DEVICE_TYPE; BOOLEAN MasterDeviceExist = FALSE; UINT8 MasterDeviceType = INVALID_DEVICE_TYPE; /** read a one-byte data from a IDE port. @param PciIo The PCI IO protocol instance @param Port the IDE Port number @return the one-byte data read from IDE port **/ UINT8 IDEReadPortB ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINT16 Port ) { UINT8 Data; Data = 0; // // perform 1-byte data read from register // PciIo->Io.Read ( PciIo, EfiPciIoWidthUint8, EFI_PCI_IO_PASS_THROUGH_BAR, (UINT64) Port, 1, &Data ); return Data; } /** Reads multiple words of data from the IDE data port. Call the IO abstraction once to do the complete read, not one word at a time @param PciIo Pointer to the EFI_PCI_IO instance @param Port IO port to read @param Count No. of UINT16's to read @param Buffer Pointer to the data buffer for read **/ VOID IDEReadPortWMultiple ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINT16 Port, IN UINTN Count, OUT VOID *Buffer ) { UINT16 *AlignedBuffer; UINT16 *WorkingBuffer; UINTN Size; // // Prepare an 16-bit aligned working buffer. CpuIo will return failure and // not perform actual I/O operations if buffer pointer passed in is not at // natural boundary. The "Buffer" argument is passed in by user and may not // at 16-bit natural boundary. // Size = sizeof (UINT16) * Count; gBS->AllocatePool ( EfiBootServicesData, Size + 1, (VOID**)&WorkingBuffer ); AlignedBuffer = (UINT16 *) ((UINTN)(((UINTN) WorkingBuffer + 0x1) & (~0x1))); // // Perform UINT16 data read from FIFO // PciIo->Io.Read ( PciIo, EfiPciIoWidthFifoUint16, EFI_PCI_IO_PASS_THROUGH_BAR, (UINT64) Port, Count, (UINT16*)AlignedBuffer ); // // Copy data to user buffer // CopyMem (Buffer, (UINT16*)AlignedBuffer, Size); gBS->FreePool (WorkingBuffer); } /** write a 1-byte data to a specific IDE port. @param PciIo PCI IO protocol instance @param Port The IDE port to be writen @param Data The data to write to the port **/ VOID IDEWritePortB ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINT16 Port, IN UINT8 Data ) { // // perform 1-byte data write to register // PciIo->Io.Write ( PciIo, EfiPciIoWidthUint8, EFI_PCI_IO_PASS_THROUGH_BAR, (UINT64) Port, 1, &Data ); } /** write a 1-word data to a specific IDE port. @param PciIo PCI IO protocol instance @param Port The IDE port to be writen @param Data The data to write to the port **/ VOID IDEWritePortW ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINT16 Port, IN UINT16 Data ) { // // perform 1-word data write to register // PciIo->Io.Write ( PciIo, EfiPciIoWidthUint16, EFI_PCI_IO_PASS_THROUGH_BAR, (UINT64) Port, 1, &Data ); } /** Write multiple words of data to the IDE data port. Call the IO abstraction once to do the complete read, not one word at a time @param PciIo Pointer to the EFI_PCI_IO instance @param Port IO port to read @param Count No. of UINT16's to read @param Buffer Pointer to the data buffer for read **/ VOID IDEWritePortWMultiple ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINT16 Port, IN UINTN Count, IN VOID *Buffer ) { UINT16 *AlignedBuffer; UINT32 *WorkingBuffer; UINTN Size; // // Prepare an 16-bit aligned working buffer. CpuIo will return failure and // not perform actual I/O operations if buffer pointer passed in is not at // natural boundary. The "Buffer" argument is passed in by user and may not // at 16-bit natural boundary. // Size = sizeof (UINT16) * Count; gBS->AllocatePool ( EfiBootServicesData, Size + 1, (VOID **) &WorkingBuffer ); AlignedBuffer = (UINT16 *) ((UINTN)(((UINTN) WorkingBuffer + 0x1) & (~0x1))); // // Copy data from user buffer to working buffer // CopyMem ((UINT16 *) AlignedBuffer, Buffer, Size); // // perform UINT16 data write to the FIFO // PciIo->Io.Write ( PciIo, EfiPciIoWidthFifoUint16, EFI_PCI_IO_PASS_THROUGH_BAR, (UINT64) Port, Count, (UINT16 *) AlignedBuffer ); gBS->FreePool (WorkingBuffer); } /** Get IDE IO port registers' base addresses by mode. In 'Compatibility' mode, use fixed addresses. In Native-PCI mode, get base addresses from BARs in the PCI IDE controller's Configuration Space. The steps to get IDE IO port registers' base addresses for each channel as follows: 1. Examine the Programming Interface byte of the Class Code fields in PCI IDE controller's Configuration Space to determine the operating mode. 2. a) In 'Compatibility' mode, use fixed addresses shown in the Table 1 below.
  ___________________________________________
  |           | Command Block | Control Block |
  |  Channel  |   Registers   |   Registers   |
  |___________|_______________|_______________|
  |  Primary  |  1F0h - 1F7h  |  3F6h - 3F7h  |
  |___________|_______________|_______________|
  | Secondary |  170h - 177h  |  376h - 377h  |
  |___________|_______________|_______________|

  Table 1. Compatibility resource mappings
  
b) In Native-PCI mode, IDE registers are mapped into IO space using the BARs in IDE controller's PCI Configuration Space, shown in the Table 2 below.
  ___________________________________________________
  |           |   Command Block   |   Control Block   |
  |  Channel  |     Registers     |     Registers     |
  |___________|___________________|___________________|
  |  Primary  | BAR at offset 0x10| BAR at offset 0x14|
  |___________|___________________|___________________|
  | Secondary | BAR at offset 0x18| BAR at offset 0x1C|
  |___________|___________________|___________________|

  Table 2. BARs for Register Mapping
  
@note Refer to Intel ICH4 datasheet, Control Block Offset: 03F4h for primary, 0374h for secondary. So 2 bytes extra offset should be added to the base addresses read from BARs. For more details, please refer to PCI IDE Controller Specification and Intel ICH4 Datasheet. @param PciIo Pointer to the EFI_PCI_IO_PROTOCOL instance @param IdeRegsBaseAddr Pointer to IDE_REGISTERS_BASE_ADDR to receive IDE IO port registers' base addresses @retval EFI_UNSUPPORTED return this value when the BARs is not IO type @retval EFI_SUCCESS Get the Base address successfully @retval other read the pci configuration data error **/ EFI_STATUS GetIdeRegistersBaseAddr ( IN EFI_PCI_IO_PROTOCOL *PciIo, OUT IDE_REGISTERS_BASE_ADDR *IdeRegsBaseAddr ) { EFI_STATUS Status; PCI_TYPE00 PciData; UINT8 PciClass; UINT8 PciSubClass; BOOLEAN fIDEAhciEmulation = FALSE; Status = PciIo->Pci.Read ( PciIo, EfiPciIoWidthUint8, 0, sizeof (PciData), &PciData ); if (EFI_ERROR (Status)) { return Status; } Status = PciIo->Pci.Read ( PciIo, EfiPciIoWidthUint8, PCI_CLASSCODE_OFFSET + 2, 1, &PciClass ); if (EFI_ERROR (Status)) { return Status; } Status = PciIo->Pci.Read ( PciIo, EfiPciIoWidthUint8, PCI_CLASSCODE_OFFSET + 1, 1, &PciSubClass ); if (EFI_ERROR (Status)) { return Status; } switch (PciSubClass) { case PCI_CLASS_MASS_STORAGE_IDE: fIDEAhciEmulation = FALSE; break; case 0x6: fIDEAhciEmulation = TRUE; break; default: ASSERT_EFI_ERROR((EFI_UNSUPPORTED)); return EFI_UNSUPPORTED; } // DEBUG((DEBUG_INFO, "class primary code: %x\n", PciData.Hdr.ClassCode[0] & IDE_PRIMARY_OPERATING_MODE)); // DEBUG((DEBUG_INFO, "class secondary code: %x\n", PciData.Hdr.ClassCode[0] & IDE_SECONDARY_OPERATING_MODE)); if ((PciData.Hdr.ClassCode[0] & IDE_PRIMARY_OPERATING_MODE) == 0) { IdeRegsBaseAddr[IdePrimary].CommandBlockBaseAddr = fIDEAhciEmulation ? 0x1e8 : 0x1f0; IdeRegsBaseAddr[IdePrimary].ControlBlockBaseAddr = fIDEAhciEmulation ? 0x3e6 :0x3f6; } else { // // The BARs should be of IO type // if ((PciData.Device.Bar[0] & BIT0) == 0 || (PciData.Device.Bar[1] & BIT0) == 0) { return EFI_UNSUPPORTED; } IdeRegsBaseAddr[IdePrimary].CommandBlockBaseAddr = fIDEAhciEmulation ? 0x1e8 : (UINT16) (PciData.Device.Bar[0] & 0x0000fff8); IdeRegsBaseAddr[IdePrimary].ControlBlockBaseAddr = fIDEAhciEmulation ? 0x3e6 :(UINT16) ((PciData.Device.Bar[1] & 0x0000fffc) + 2); } IdeRegsBaseAddr[IdePrimary].BusMasterBaseAddr = fIDEAhciEmulation ? 0 : (UINT16)((PciData.Device.Bar[4] & 0x0000fff0)); if ((PciData.Hdr.ClassCode[0] & IDE_SECONDARY_OPERATING_MODE) == 0) { IdeRegsBaseAddr[IdeSecondary].CommandBlockBaseAddr = fIDEAhciEmulation ? 0x168 : 0x170; IdeRegsBaseAddr[IdeSecondary].ControlBlockBaseAddr = fIDEAhciEmulation ? 0x366 : 0x376; } else { // // The BARs should be of IO type // if ((PciData.Device.Bar[2] & BIT0) == 0 || (PciData.Device.Bar[3] & BIT0) == 0) { return EFI_UNSUPPORTED; } IdeRegsBaseAddr[IdeSecondary].CommandBlockBaseAddr = fIDEAhciEmulation ? 0x168 : (UINT16) (PciData.Device.Bar[2] & 0x0000fff8); IdeRegsBaseAddr[IdeSecondary].ControlBlockBaseAddr = fIDEAhciEmulation ? 0x366 : (UINT16) ((PciData.Device.Bar[3] & 0x0000fffc) + 2); } IdeRegsBaseAddr[IdeSecondary].BusMasterBaseAddr = (UINT16) ((PciData.Device.Bar[fIDEAhciEmulation ? 5 : 4] & 0x0000fff0)); /* DEBUG((DEBUG_INFO, "%a:%d CommandBlockBaseAddr:%x, " "ControlBlockBaseAddr:%x, " "BusMasterBaseAddr:%x\n", __FILE__, __LINE__, IdeRegsBaseAddr[IdePrimary].CommandBlockBaseAddr, IdeRegsBaseAddr[IdePrimary].ControlBlockBaseAddr, IdeRegsBaseAddr[IdePrimary].BusMasterBaseAddr)); DEBUG((DEBUG_INFO, "%a:%d CommandBlockBaseAddr:%x, " "ControlBlockBaseAddr:%x, " "BusMasterBaseAddr:%x\n", __FILE__, __LINE__, IdeRegsBaseAddr[IdeSecondary].CommandBlockBaseAddr, IdeRegsBaseAddr[IdeSecondary].ControlBlockBaseAddr, IdeRegsBaseAddr[IdeSecondary].BusMasterBaseAddr)); */ return EFI_SUCCESS; } /** This function is used to requery IDE resources. The IDE controller will probably switch between native and legacy modes during the EFI->CSM->OS transfer. We do this everytime before an BlkIo operation to ensure its success. @param IdeDev The BLK_IO private data which specifies the IDE device @retval EFI_INVALID_PARAMETER return this value when the channel is invalid @retval EFI_SUCCESS reassign the IDE IO resource successfully @retval other get the IDE current base address effor **/ EFI_STATUS ReassignIdeResources ( IN IDE_BLK_IO_DEV *IdeDev ) { EFI_STATUS Status; IDE_REGISTERS_BASE_ADDR IdeRegsBaseAddr[IdeMaxChannel]; UINT16 CommandBlockBaseAddr; UINT16 ControlBlockBaseAddr; if (IdeDev->Channel >= IdeMaxChannel) { return EFI_INVALID_PARAMETER; } // // Requery IDE IO port registers' base addresses in case of the switch of // native and legacy modes // Status = GetIdeRegistersBaseAddr (IdeDev->PciIo, IdeRegsBaseAddr); if (EFI_ERROR (Status)) { return Status; } ZeroMem (IdeDev->IoPort, sizeof (IDE_BASE_REGISTERS)); CommandBlockBaseAddr = IdeRegsBaseAddr[IdeDev->Channel].CommandBlockBaseAddr; ControlBlockBaseAddr = IdeRegsBaseAddr[IdeDev->Channel].ControlBlockBaseAddr; IdeDev->IoPort->Data = CommandBlockBaseAddr; (*(UINT16 *) &IdeDev->IoPort->Reg1) = (UINT16) (CommandBlockBaseAddr + 0x01); IdeDev->IoPort->SectorCount = (UINT16) (CommandBlockBaseAddr + 0x02); IdeDev->IoPort->SectorNumber = (UINT16) (CommandBlockBaseAddr + 0x03); IdeDev->IoPort->CylinderLsb = (UINT16) (CommandBlockBaseAddr + 0x04); IdeDev->IoPort->CylinderMsb = (UINT16) (CommandBlockBaseAddr + 0x05); IdeDev->IoPort->Head = (UINT16) (CommandBlockBaseAddr + 0x06); (*(UINT16 *) &IdeDev->IoPort->Reg) = (UINT16) (CommandBlockBaseAddr + 0x07); (*(UINT16 *) &IdeDev->IoPort->Alt) = ControlBlockBaseAddr; IdeDev->IoPort->DriveAddress = (UINT16) (ControlBlockBaseAddr + 0x01); IdeDev->IoPort->MasterSlave = (UINT16) ((IdeDev->Device == IdeMaster) ? 1 : 0); IdeDev->IoPort->BusMasterBaseAddr = IdeRegsBaseAddr[IdeDev->Channel].BusMasterBaseAddr; return EFI_SUCCESS; } /** This function is called by DiscoverIdeDevice(). It is used for detect whether the IDE device exists in the specified Channel as the specified Device Number. There is two IDE channels: one is Primary Channel, the other is Secondary Channel.(Channel is the logical name for the physical "Cable".) Different channel has different register group. On each IDE channel, at most two IDE devices attach, one is called Device 0 (Master device), the other is called Device 1 (Slave device). The devices on the same channel co-use the same register group, so before sending out a command for a specified device via command register, it is a must to select the current device to accept the command by set the device number in the Head/Device Register. @param IdeDev pointer to IDE_BLK_IO_DEV data structure, used to record all the information of the IDE device. @retval EFI_SUCCESS successfully detects device. @retval other any failure during detection process will return this value. **/ EFI_STATUS DetectIDEController ( IN IDE_BLK_IO_DEV *IdeDev ) { EFI_STATUS Status; UINT8 SectorCountReg; UINT8 LBALowReg; UINT8 LBAMidReg; UINT8 LBAHighReg; UINT8 InitStatusReg; UINT8 StatusReg; // // Select slave device // IDEWritePortB ( IdeDev->PciIo, IdeDev->IoPort->Head, (UINT8) ((1 << 4) | 0xe0) ); gBS->Stall (100); // // Save the init slave status register // InitStatusReg = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Status); //CmdOrStatus // // Select Master back // IDEWritePortB ( IdeDev->PciIo, IdeDev->IoPort->Head, (UINT8) ((0 << 4) | 0xe0) ); gBS->Stall (100); // // Send ATA Device Execut Diagnostic command. // This command should work no matter DRDY is ready or not // IDEWritePortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Command, 0x90); Status = WaitForBSYClear (IdeDev, 3500); if (EFI_ERROR (Status)) { // DEBUG((EFI_D_ERROR, "New detecting method: Send Execute Diagnostic Command: WaitForBSYClear: Status: %d\n", Status)); return Status; } // // Read device signature // // // Select Master // IDEWritePortB ( IdeDev->PciIo, IdeDev->IoPort->Head, (UINT8) ((0 << 4) | 0xe0) ); gBS->Stall (100); SectorCountReg = IDEReadPortB ( IdeDev->PciIo, IdeDev->IoPort->SectorCount ); LBALowReg = IDEReadPortB ( IdeDev->PciIo, IdeDev->IoPort->SectorNumber ); LBAMidReg = IDEReadPortB ( IdeDev->PciIo, IdeDev->IoPort->CylinderLsb ); LBAHighReg = IDEReadPortB ( IdeDev->PciIo, IdeDev->IoPort->CylinderMsb ); if ((SectorCountReg == 0x1) && (LBALowReg == 0x1) && (LBAMidReg == 0x0) && (LBAHighReg == 0x0)) { MasterDeviceExist = TRUE; MasterDeviceType = ATA_DEVICE_TYPE; } else { if ((LBAMidReg == 0x14) && (LBAHighReg == 0xeb)) { MasterDeviceExist = TRUE; MasterDeviceType = ATAPI_DEVICE_TYPE; } } // // For some Hard Drive, it takes some time to get // the right signature when operating in single slave mode. // We stall 20ms to work around this. // if (!MasterDeviceExist) { gBS->Stall (20000); } // // Select Slave // IDEWritePortB ( IdeDev->PciIo, IdeDev->IoPort->Head, (UINT8) ((1 << 4) | 0xe0) ); gBS->Stall (100); SectorCountReg = IDEReadPortB ( IdeDev->PciIo, IdeDev->IoPort->SectorCount ); LBALowReg = IDEReadPortB ( IdeDev->PciIo, IdeDev->IoPort->SectorNumber ); LBAMidReg = IDEReadPortB ( IdeDev->PciIo, IdeDev->IoPort->CylinderLsb ); LBAHighReg = IDEReadPortB ( IdeDev->PciIo, IdeDev->IoPort->CylinderMsb ); StatusReg = IDEReadPortB ( IdeDev->PciIo, IdeDev->IoPort->Reg.Status ); if ((SectorCountReg == 0x1) && (LBALowReg == 0x1) && (LBAMidReg == 0x0) && (LBAHighReg == 0x0)) { SlaveDeviceExist = TRUE; SlaveDeviceType = ATA_DEVICE_TYPE; } else { if ((LBAMidReg == 0x14) && (LBAHighReg == 0xeb)) { SlaveDeviceExist = TRUE; SlaveDeviceType = ATAPI_DEVICE_TYPE; } } // // When single master is plugged, slave device // will be wrongly detected. Here's the workaround // for ATA devices by detecting DRY bit in status // register. // NOTE: This workaround doesn't apply to ATAPI. // if (MasterDeviceExist && SlaveDeviceExist && (StatusReg & ATA_STSREG_DRDY) == 0 && (InitStatusReg & ATA_STSREG_DRDY) == 0 && MasterDeviceType == SlaveDeviceType && SlaveDeviceType != ATAPI_DEVICE_TYPE) { SlaveDeviceExist = FALSE; } // // Indicate this channel has been detected // ChannelDeviceDetected = TRUE; return EFI_SUCCESS; } /** Detect if there is disk attached to this port @param IdeDev The BLK_IO private data which specifies the IDE device. @retval EFI_NOT_FOUND The device or channel is not found @retval EFI_SUCCESS The device is found **/ EFI_STATUS DiscoverIdeDevice ( IN IDE_BLK_IO_DEV *IdeDev ) { EFI_STATUS Status; EFI_STATUS LongPhyStatus; // // If a channel has not been checked, check it now. Then set it to "checked" state // After this step, all devices in this channel have been checked. // if (!ChannelDeviceDetected) { Status = DetectIDEController (IdeDev); if (EFI_ERROR (Status)) { return EFI_NOT_FOUND; } } Status = EFI_NOT_FOUND; // // Device exists. test if it is an ATA device. // Prefer the result from DetectIDEController, // if failed, try another device type to handle // devices that not follow the spec. // if ((IdeDev->Device == IdeMaster) && (MasterDeviceExist)) { if (MasterDeviceType == ATA_DEVICE_TYPE) { Status = ATAIdentify (IdeDev); if (EFI_ERROR (Status)) { Status = ATAPIIdentify (IdeDev); if (!EFI_ERROR (Status)) { MasterDeviceType = ATAPI_DEVICE_TYPE; } } } else { Status = ATAPIIdentify (IdeDev); if (EFI_ERROR (Status)) { Status = ATAIdentify (IdeDev); if (!EFI_ERROR (Status)) { MasterDeviceType = ATA_DEVICE_TYPE; } } } } if ((IdeDev->Device == IdeSlave) && (SlaveDeviceExist)) { if (SlaveDeviceType == ATA_DEVICE_TYPE) { Status = ATAIdentify (IdeDev); if (EFI_ERROR (Status)) { Status = ATAPIIdentify (IdeDev); if (!EFI_ERROR (Status)) { SlaveDeviceType = ATAPI_DEVICE_TYPE; } } } else { Status = ATAPIIdentify (IdeDev); if (EFI_ERROR (Status)) { Status = ATAIdentify (IdeDev); if (!EFI_ERROR (Status)) { SlaveDeviceType = ATA_DEVICE_TYPE; } } } } if (EFI_ERROR (Status)) { return EFI_NOT_FOUND; } // // Init Block I/O interface // LongPhyStatus = AtaEnableLongPhysicalSector (IdeDev); if (!EFI_ERROR (LongPhyStatus)) { IdeDev->BlkIo.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION2; } else { IdeDev->BlkIo.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION; } IdeDev->BlkIo.Reset = IDEBlkIoReset; IdeDev->BlkIo.ReadBlocks = IDEBlkIoReadBlocks; IdeDev->BlkIo.WriteBlocks = IDEBlkIoWriteBlocks; IdeDev->BlkIo.FlushBlocks = IDEBlkIoFlushBlocks; IdeDev->BlkMedia.LogicalPartition = FALSE; IdeDev->BlkMedia.WriteCaching = FALSE; // // Init Disk Info interface // gBS->CopyMem (&IdeDev->DiskInfo.Interface, &gEfiDiskInfoIdeInterfaceGuid, sizeof (EFI_GUID)); IdeDev->DiskInfo.Inquiry = IDEDiskInfoInquiry; IdeDev->DiskInfo.Identify = IDEDiskInfoIdentify; IdeDev->DiskInfo.SenseData = IDEDiskInfoSenseData; IdeDev->DiskInfo.WhichIde = IDEDiskInfoWhichIde; return EFI_SUCCESS; } /** This interface is used to initialize all state data related to the detection of one channel. **/ VOID InitializeIDEChannelData ( VOID ) { ChannelDeviceDetected = FALSE; MasterDeviceExist = FALSE; MasterDeviceType = 0xff; SlaveDeviceExist = FALSE; SlaveDeviceType = 0xff; } /** This function is used to poll for the DRQ bit clear in the Status Register. DRQ is cleared when the device is finished transferring data. So this function is called after data transfer is finished. @param IdeDev pointer pointing to IDE_BLK_IO_DEV data structure, used to record all the information of the IDE device. @param TimeoutInMilliSeconds used to designate the timeout for the DRQ clear. @retval EFI_SUCCESS DRQ bit clear within the time out. @retval EFI_TIMEOUT DRQ bit not clear within the time out. @note Read Status Register will clear interrupt status. **/ EFI_STATUS DRQClear ( IN IDE_BLK_IO_DEV *IdeDev, IN UINTN TimeoutInMilliSeconds ) { UINT32 Delay; UINT8 StatusRegister; UINT8 ErrorRegister; Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1); //1000 -> 33ms do { StatusRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Status); // // wait for BSY == 0 and DRQ == 0 // if ((StatusRegister & (ATA_STSREG_DRQ | ATA_STSREG_BSY)) == 0) { break; } if ((StatusRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) { ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error); if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) { return EFI_ABORTED; } } // // Stall for 30 us // gBS->Stall (30); Delay--; } while (Delay > 0); if (Delay == 0) { return EFI_TIMEOUT; } return EFI_SUCCESS; } /** This function is used to poll for the DRQ bit clear in the Alternate Status Register. DRQ is cleared when the device is finished transferring data. So this function is called after data transfer is finished. @param IdeDev pointer pointing to IDE_BLK_IO_DEV data structure, used to record all the information of the IDE device. @param TimeoutInMilliSeconds used to designate the timeout for the DRQ clear. @retval EFI_SUCCESS DRQ bit clear within the time out. @retval EFI_TIMEOUT DRQ bit not clear within the time out. @note Read Alternate Status Register will not clear interrupt status. **/ EFI_STATUS DRQClear2 ( IN IDE_BLK_IO_DEV *IdeDev, IN UINTN TimeoutInMilliSeconds ) { UINT32 Delay; UINT8 AltRegister; UINT8 ErrorRegister; Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1); do { AltRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Alt.AltStatus); // // wait for BSY == 0 and DRQ == 0 // if ((AltRegister & (ATA_STSREG_DRQ | ATA_STSREG_BSY)) == 0) { break; } if ((AltRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) { ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error); if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) { return EFI_ABORTED; } } // // Stall for 30 us // gBS->Stall (30); Delay--; } while (Delay > 0); if (Delay == 0) { return EFI_TIMEOUT; } return EFI_SUCCESS; } /** This function is used to poll for the DRQ bit set in the Status Register. DRQ is set when the device is ready to transfer data. So this function is called after the command is sent to the device and before required data is transferred. @param IdeDev pointer pointing to IDE_BLK_IO_DEV data structure,used to record all the information of the IDE device. @param TimeoutInMilliSeconds used to designate the timeout for the DRQ ready. @retval EFI_SUCCESS DRQ bit set within the time out. @retval EFI_TIMEOUT DRQ bit not set within the time out. @retval EFI_ABORTED DRQ bit not set caused by the command abort. @note Read Status Register will clear interrupt status. **/ EFI_STATUS DRQReady ( IN IDE_BLK_IO_DEV *IdeDev, IN UINTN TimeoutInMilliSeconds ) { UINT32 Delay; UINT8 StatusRegister; UINT8 ErrorRegister; Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1); do { // // read Status Register will clear interrupt // StatusRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Status); // // BSY==0,DRQ==1 // if ((StatusRegister & (ATA_STSREG_BSY | ATA_STSREG_DRQ)) == ATA_STSREG_DRQ) { break; } if ((StatusRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) { ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error); if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) { return EFI_ABORTED; } } // // Stall for 30 us // gBS->Stall (30); Delay--; } while (Delay > 0); if (Delay == 0) { return EFI_TIMEOUT; } return EFI_SUCCESS; } /** This function is used to poll for the DRQ bit set in the Alternate Status Register. DRQ is set when the device is ready to transfer data. So this function is called after the command is sent to the device and before required data is transferred. @param IdeDev pointer pointing to IDE_BLK_IO_DEV data structure, used to record all the information of the IDE device. @param TimeoutInMilliSeconds used to designate the timeout for the DRQ ready. @retval EFI_SUCCESS DRQ bit set within the time out. @retval EFI_TIMEOUT DRQ bit not set within the time out. @retval EFI_ABORTED DRQ bit not set caused by the command abort. @note Read Alternate Status Register will not clear interrupt status. **/ EFI_STATUS DRQReady2 ( IN IDE_BLK_IO_DEV *IdeDev, IN UINTN TimeoutInMilliSeconds ) { UINT32 Delay; UINT8 AltRegister; UINT8 ErrorRegister; Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1); do { // // Read Alternate Status Register will not clear interrupt status // AltRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Alt.AltStatus); // // BSY == 0 , DRQ == 1 // if ((AltRegister & (ATA_STSREG_BSY | ATA_STSREG_DRQ)) == ATA_STSREG_DRQ) { break; } if ((AltRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) { ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error); if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) { return EFI_ABORTED; } } // // Stall for 30 us // gBS->Stall (30); Delay--; } while (Delay > 0); if (Delay == 0) { return EFI_TIMEOUT; } return EFI_SUCCESS; } /** This function is used to poll for the BSY bit clear in the Status Register. BSY is clear when the device is not busy. Every command must be sent after device is not busy. @param IdeDev pointer pointing to IDE_BLK_IO_DEV data structure, used to record all the information of the IDE device. @param TimeoutInMilliSeconds used to designate the timeout for the DRQ ready. @retval EFI_SUCCESS BSY bit clear within the time out. @retval EFI_TIMEOUT BSY bit not clear within the time out. @note Read Status Register will clear interrupt status. **/ EFI_STATUS WaitForBSYClear ( IN IDE_BLK_IO_DEV *IdeDev, IN UINTN TimeoutInMilliSeconds ) { UINT32 Delay; UINT8 StatusRegister; Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1); do { StatusRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Status); if ((StatusRegister & ATA_STSREG_BSY) == 0x00) { break; } // // Stall for 30 us // gBS->Stall (30); Delay--; } while (Delay > 0); if (Delay == 0) { return EFI_TIMEOUT; } return EFI_SUCCESS; } /** This function is used to poll for the BSY bit clear in the Alternate Status Register. BSY is clear when the device is not busy. Every command must be sent after device is not busy. @param IdeDev pointer pointing to IDE_BLK_IO_DEV data structure, used to record all the information of the IDE device. @param TimeoutInMilliSeconds used to designate the timeout for the DRQ ready. @retval EFI_SUCCESS BSY bit clear within the time out. @retval EFI_TIMEOUT BSY bit not clear within the time out. @note Read Alternate Status Register will not clear interrupt status. **/ EFI_STATUS WaitForBSYClear2 ( IN IDE_BLK_IO_DEV *IdeDev, IN UINTN TimeoutInMilliSeconds ) { UINT32 Delay; UINT8 AltRegister; Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1); do { AltRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Alt.AltStatus); if ((AltRegister & ATA_STSREG_BSY) == 0x00) { break; } gBS->Stall (30); Delay--; } while (Delay > 0); if (Delay == 0) { return EFI_TIMEOUT; } return EFI_SUCCESS; } /** This function is used to poll for the DRDY bit set in the Status Register. DRDY bit is set when the device is ready to accept command. Most ATA commands must be sent after DRDY set except the ATAPI Packet Command. @param IdeDev pointer pointing to IDE_BLK_IO_DEV data structure, used to record all the information of the IDE device. @param DelayInMilliSeconds used to designate the timeout for the DRQ ready. @retval EFI_SUCCESS DRDY bit set within the time out. @retval EFI_TIMEOUT DRDY bit not set within the time out. @note Read Status Register will clear interrupt status. **/ EFI_STATUS DRDYReady ( IN IDE_BLK_IO_DEV *IdeDev, IN UINTN DelayInMilliSeconds ) { UINT32 Delay; UINT8 StatusRegister; UINT8 ErrorRegister; Delay = (UINT32) (((DelayInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1); do { StatusRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Status); // // BSY == 0 , DRDY == 1 // if ((StatusRegister & (ATA_STSREG_DRDY | ATA_STSREG_BSY)) == ATA_STSREG_DRDY) { break; } if ((StatusRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) { ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error); if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) { return EFI_ABORTED; } } gBS->Stall (30); Delay--; } while (Delay > 0); if (Delay == 0) { return EFI_TIMEOUT; } return EFI_SUCCESS; } /** This function is used to poll for the DRDY bit set in the Alternate Status Register. DRDY bit is set when the device is ready to accept command. Most ATA commands must be sent after DRDY set except the ATAPI Packet Command. @param IdeDev pointer pointing to IDE_BLK_IO_DEV data structure, used to record all the information of the IDE device. @param DelayInMilliSeconds used to designate the timeout for the DRQ ready. @retval EFI_SUCCESS DRDY bit set within the time out. @retval EFI_TIMEOUT DRDY bit not set within the time out. @note Read Alternate Status Register will clear interrupt status. **/ EFI_STATUS DRDYReady2 ( IN IDE_BLK_IO_DEV *IdeDev, IN UINTN DelayInMilliSeconds ) { UINT32 Delay; UINT8 AltRegister; UINT8 ErrorRegister; Delay = (UINT32) (((DelayInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1); do { AltRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Alt.AltStatus); //AltOrDev // // BSY == 0 , DRDY == 1 // if ((AltRegister & (ATA_STSREG_DRDY | ATA_STSREG_BSY)) == ATA_STSREG_DRDY) { break; } if ((AltRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) { ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error); if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) { return EFI_ABORTED; } } gBS->Stall (30); Delay--; } while (Delay > 0); if (Delay == 0) { return EFI_TIMEOUT; } return EFI_SUCCESS; } /** Release resources of an IDE device before stopping it. @param IdeBlkIoDevice Standard IDE device private data structure **/ VOID ReleaseIdeResources ( IN IDE_BLK_IO_DEV *IdeBlkIoDevice ) { if (IdeBlkIoDevice == NULL) { return ; } // // Release all the resources occupied by the IDE_BLK_IO_DEV // if (IdeBlkIoDevice->SenseData != NULL) { gBS->FreePool (IdeBlkIoDevice->SenseData); IdeBlkIoDevice->SenseData = NULL; } if (IdeBlkIoDevice->Cache != NULL) { gBS->FreePool (IdeBlkIoDevice->Cache); IdeBlkIoDevice->Cache = NULL; } if (IdeBlkIoDevice->IdData != NULL) { gBS->FreePool (IdeBlkIoDevice->IdData); IdeBlkIoDevice->IdData = NULL; } if (IdeBlkIoDevice->InquiryData != NULL) { gBS->FreePool (IdeBlkIoDevice->InquiryData); IdeBlkIoDevice->InquiryData = NULL; } if (IdeBlkIoDevice->ControllerNameTable != NULL) { FreeUnicodeStringTable (IdeBlkIoDevice->ControllerNameTable); IdeBlkIoDevice->ControllerNameTable = NULL; } if (IdeBlkIoDevice->IoPort != NULL) { gBS->FreePool (IdeBlkIoDevice->IoPort); } if (IdeBlkIoDevice->DevicePath != NULL) { gBS->FreePool (IdeBlkIoDevice->DevicePath); } if (IdeBlkIoDevice->ExitBootServiceEvent != NULL) { gBS->CloseEvent (IdeBlkIoDevice->ExitBootServiceEvent); IdeBlkIoDevice->ExitBootServiceEvent = NULL; } gBS->FreePool (IdeBlkIoDevice); IdeBlkIoDevice = NULL; return ; } /** Set the calculated Best transfer mode to a detected device. @param IdeDev Standard IDE device private data structure @param TransferMode The device transfer mode to be set @return Set transfer mode Command execute status. **/ EFI_STATUS SetDeviceTransferMode ( IN IDE_BLK_IO_DEV *IdeDev, IN ATA_TRANSFER_MODE *TransferMode ) { EFI_STATUS Status; UINT8 DeviceSelect; UINT8 SectorCount; DeviceSelect = 0; DeviceSelect = (UINT8) ((IdeDev->Device) << 4); SectorCount = *((UINT8 *) TransferMode); // // Send SET FEATURE command (sub command 0x03) to set pio mode. // Status = AtaNonDataCommandIn ( IdeDev, ATA_CMD_SET_FEATURES, DeviceSelect, 0x03, SectorCount, 0, 0, 0 ); return Status; } /** Set drive parameters for devices not support PACKETS command. @param IdeDev Standard IDE device private data structure @param DriveParameters The device parameters to be set into the disk @return SetParameters Command execute status. **/ EFI_STATUS SetDriveParameters ( IN IDE_BLK_IO_DEV *IdeDev, IN ATA_DRIVE_PARMS *DriveParameters ) { EFI_STATUS Status; UINT8 DeviceSelect; DeviceSelect = 0; DeviceSelect = (UINT8) ((IdeDev->Device) << 4); // // Send Init drive parameters // Status = AtaNonDataCommandIn ( IdeDev, ATA_CMD_INIT_DRIVE_PARAM, (UINT8) (DeviceSelect + DriveParameters->Heads), 0, DriveParameters->Sector, 0, 0, 0 ); // // Send Set Multiple parameters // Status = AtaNonDataCommandIn ( IdeDev, ATA_CMD_SET_MULTIPLE_MODE, DeviceSelect, 0, DriveParameters->MultipleSector, 0, 0, 0 ); return Status; } /** Enable Interrupt on IDE controller. @param IdeDev Standard IDE device private data structure @retval EFI_SUCCESS Enable Interrupt successfully **/ EFI_STATUS EnableInterrupt ( IN IDE_BLK_IO_DEV *IdeDev ) { UINT8 DeviceControl; // // Enable interrupt for DMA operation // DeviceControl = 0; IDEWritePortB (IdeDev->PciIo, IdeDev->IoPort->Alt.DeviceControl, DeviceControl); return EFI_SUCCESS; }