/*++

Copyright (c) 2006, 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.             

Module Name:

  cbi1.c

Abstract:
    cbi1 transportation protocol implementation files

--*/

#include "cbi.h"

//
// CBI Function prototypes
//
STATIC
EFI_STATUS
CBI1CommandPhase (
  IN  USB_CBI_DEVICE          *UsbCbiDev,
  IN  VOID                    *Command,
  IN  UINT8                   CommandSize,
  OUT UINT32                  *Result
  );

STATIC
EFI_STATUS
CBI1DataPhase (
  IN  USB_CBI_DEVICE          *UsbCbiDev,
  IN  UINT32                  DataSize,
  IN  OUT VOID                *DataBuffer,
  IN  EFI_USB_DATA_DIRECTION  Direction,
  IN  UINT16                  Timeout,
  OUT UINT32                  *Result
  );

//
// USB Atapi implementation
//
STATIC
EFI_STATUS
EFIAPI
CBI1AtapiCommand (
  IN  EFI_USB_ATAPI_PROTOCOL    *This,
  IN  VOID                      *Command,
  IN  UINT8                     CommandSize,
  IN  VOID                      *DataBuffer,
  IN  UINT32                    BufferLength,
  IN  EFI_USB_DATA_DIRECTION    Direction,
  IN  UINT16                    TimeOutInMilliSeconds
  );

STATIC
EFI_STATUS
EFIAPI
CBI1MassStorageReset (
  IN  EFI_USB_ATAPI_PROTOCOL      *This,
  IN  BOOLEAN                     ExtendedVerification
  );

//
// CBI1 Driver Binding Protocol
//
STATIC
EFI_STATUS
EFIAPI
CBI1DriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  );

STATIC
EFI_STATUS
EFIAPI
CBI1DriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  );

STATIC
EFI_STATUS
EFIAPI
CBI1DriverBindingStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN  EFI_HANDLE                     ControllerHandle,
  IN  UINTN                          NumberOfChildren,
  IN  EFI_HANDLE                     *ChildHandleBuffer
  );

STATIC
VOID
Cbi1ReportStatusCode (
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  IN EFI_STATUS_CODE_TYPE      CodeType,
  IN EFI_STATUS_CODE_VALUE     Value
  );


EFI_DRIVER_BINDING_PROTOCOL   gUsbCbi1DriverBinding = {
  CBI1DriverBindingSupported,
  CBI1DriverBindingStart,
  CBI1DriverBindingStop,
  0xa,
  NULL,
  NULL
};

STATIC EFI_USB_ATAPI_PROTOCOL CBI1AtapiProtocol = {
  CBI1AtapiCommand,
  CBI1MassStorageReset,
  0
};

//
// CBI1 Driver Binding implementation
//
STATIC
EFI_STATUS
EFIAPI
CBI1DriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
/*++

  Routine Description:
    Test to see if this driver supports ControllerHandle. Any ControllerHandle 
    than contains a BlockIo and DiskIo protocol can be supported.

  Arguments:
    This                - Protocol instance pointer.
    ControllerHandle    - Handle of device to test
    RemainingDevicePath - Not used

  Returns:
    EFI_SUCCESS         - This driver supports this device
    EFI_ALREADY_STARTED - This driver is already running on this device
    other               - This driver does not support this device

--*/
{
  EFI_STATUS                    Status;
  EFI_USB_IO_PROTOCOL           *UsbIo;
  EFI_USB_INTERFACE_DESCRIPTOR  InterfaceDescriptor;

  //
  // Check if the Controller supports USB IO protocol
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiUsbIoProtocolGuid,
                  (VOID **) &UsbIo,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Get the Controller interface descriptor
  //
  Status = UsbIo->UsbGetInterfaceDescriptor (
                    UsbIo,
                    &InterfaceDescriptor
                    );
  if (EFI_ERROR (Status)) {
    goto Exit;
  }
  //
  // Bug here: just let Vendor specific CBI protocol get supported
  //
  if (!((InterfaceDescriptor.InterfaceClass == 0xFF) &&
        (InterfaceDescriptor.InterfaceProtocol == 0))) {
    Status = EFI_UNSUPPORTED;
    goto Exit;
  }

Exit:
  gBS->CloseProtocol (
        ControllerHandle,
        &gEfiUsbIoProtocolGuid,
        This->DriverBindingHandle,
        ControllerHandle
        );
  return Status;

}

STATIC
EFI_STATUS
EFIAPI
CBI1DriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
/*++

  Routine Description:
    Start this driver on ControllerHandle by opening a Block IO and Disk IO 
    protocol, reading Device Path, and creating a child handle with a 
    Disk IO and device path protocol.

  Arguments:
    This                - Protocol instance pointer.
    ControllerHandle    - Handle of device to bind driver to
    RemainingDevicePath - Not used

  Returns:
    EFI_SUCCESS         - This driver is added to DeviceHandle
    EFI_ALREADY_STARTED - This driver is already running on DeviceHandle
    other               - This driver does not support this device

--*/
{
  USB_CBI_DEVICE                *UsbCbiDev;
  UINT8                         Index;
  EFI_USB_ENDPOINT_DESCRIPTOR   EndpointDescriptor;
  EFI_USB_INTERFACE_DESCRIPTOR  InterfaceDescriptor;
  EFI_STATUS                    Status;
  EFI_USB_IO_PROTOCOL           *UsbIo;
  BOOLEAN                       Found;

  Found = FALSE;
  //
  // Check if the Controller supports USB IO protocol
  //
  UsbCbiDev = NULL;

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiUsbIoProtocolGuid,
                  (VOID **) &UsbIo,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Get the controller interface descriptor
  //
  Status = UsbIo->UsbGetInterfaceDescriptor (
                    UsbIo,
                    &InterfaceDescriptor
                    );
  if (EFI_ERROR (Status)) {
    goto ErrorExit;
  }

  CBI1AtapiProtocol.CommandProtocol = InterfaceDescriptor.InterfaceSubClass;

  UsbCbiDev                         = AllocateZeroPool (sizeof (USB_CBI_DEVICE));
  if (UsbCbiDev == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ErrorExit;
  }

  UsbCbiDev->Signature            = USB_CBI_DEVICE_SIGNATURE;
  UsbCbiDev->UsbIo                = UsbIo;
  CopyMem (&UsbCbiDev->InterfaceDescriptor, &InterfaceDescriptor, sizeof (InterfaceDescriptor));
  CopyMem (&UsbCbiDev->UsbAtapiProtocol   , &CBI1AtapiProtocol,   sizeof (CBI1AtapiProtocol));

  //
  // Get the Device Path Protocol on Controller's handle
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &UsbCbiDev->DevicePath,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );

  if (EFI_ERROR (Status)) {
    goto ErrorExit;
  }

  for (Index = 0; Index < InterfaceDescriptor.NumEndpoints; Index++) {
    UsbIo->UsbGetEndpointDescriptor (
            UsbIo,
            Index,
            &EndpointDescriptor
            );

    //
    // We parse bulk endpoint
    //
    if (EndpointDescriptor.Attributes == 0x02) {
      if (EndpointDescriptor.EndpointAddress & 0x80) {
        CopyMem (&UsbCbiDev->BulkInEndpointDescriptor, &EndpointDescriptor, sizeof (EndpointDescriptor));
	  } else {
        CopyMem (&UsbCbiDev->BulkOutEndpointDescriptor, &EndpointDescriptor, sizeof (EndpointDescriptor));
	  }

      Found = TRUE;
    }
    //
    // We parse interrupt endpoint
    //
    if (EndpointDescriptor.Attributes == 0x03) {
      CopyMem (&UsbCbiDev->InterruptEndpointDescriptor, &EndpointDescriptor, sizeof (EndpointDescriptor));
      Found = TRUE;
    }

  }
  //
  // Double check we have these
  //
  if (!Found) {
    goto ErrorExit;
  }
  //
  // After installing Usb-Atapi protocol onto this handle
  // it will be called by upper layer drivers such as Fat
  //
  Cbi1ReportStatusCode (
    UsbCbiDev->DevicePath,
    EFI_PROGRESS_CODE,
    (EFI_PERIPHERAL_REMOVABLE_MEDIA | EFI_P_PC_ENABLE)
    );

  Status = gBS->InstallProtocolInterface (
                  &ControllerHandle,
                  &gEfiUsbAtapiProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &UsbCbiDev->UsbAtapiProtocol
                  );

  if (EFI_ERROR (Status)) {
    goto ErrorExit;
  }

  UsbCbiDev->ControllerNameTable = NULL;
  AddUnicodeString (
    "eng",
    gUsbCbi1ComponentName.SupportedLanguages,
    &UsbCbiDev->ControllerNameTable,
    (CHAR16 *) L"Usb Cbi1 Mass Storage"
    );

  return EFI_SUCCESS;

ErrorExit:
  gBS->CloseProtocol (
        ControllerHandle,
        &gEfiUsbIoProtocolGuid,
        This->DriverBindingHandle,
        ControllerHandle
        );
  if (UsbCbiDev != NULL) {
    gBS->FreePool (UsbCbiDev);
  }

  return Status;

}

STATIC
EFI_STATUS
EFIAPI
CBI1DriverBindingStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN  EFI_HANDLE                     ControllerHandle,
  IN  UINTN                          NumberOfChildren,
  IN  EFI_HANDLE                     *ChildHandleBuffer
  )
/*++

  Routine Description:
    Stop this driver on ControllerHandle. Support stoping any child handles 
    created by this driver.

  Arguments:
    This              - Protocol instance pointer.
    ControllerHandle  - Handle of device to stop driver on 
    NumberOfChildren  - Number of Children in the ChildHandleBuffer
    ChildHandleBuffer - List of handles for the children we need to stop.

  Returns:
    EFI_SUCCESS         - This driver is removed DeviceHandle
    EFI_UNSUPPORTED     - Can't open the gEfiUsbAtapiProtocolGuid protocol 
    other               - This driver was not removed from this device
   
--*/
{
  EFI_STATUS              Status;
  EFI_USB_ATAPI_PROTOCOL  *CBI1AtapiProtocol;
  USB_CBI_DEVICE          *UsbCbiDev;

  //
  // Get our context back.
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiUsbAtapiProtocolGuid,
                  (VOID **) &CBI1AtapiProtocol,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  UsbCbiDev = USB_CBI_DEVICE_FROM_THIS (CBI1AtapiProtocol);

  Cbi1ReportStatusCode (
    UsbCbiDev->DevicePath,
    EFI_PROGRESS_CODE,
    (EFI_PERIPHERAL_REMOVABLE_MEDIA | EFI_P_PC_DISABLE)
    );

  Status = gBS->UninstallProtocolInterface (
                  ControllerHandle,
                  &gEfiUsbAtapiProtocolGuid,
                  &UsbCbiDev->UsbAtapiProtocol
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->CloseProtocol (
                  ControllerHandle,
                  &gEfiUsbIoProtocolGuid,
                  This->DriverBindingHandle,
                  ControllerHandle
                  );
  gBS->FreePool (UsbCbiDev);

  return Status;

}
//
// CBI1 command
//
STATIC
EFI_STATUS
CBI1CommandPhase (
  IN  USB_CBI_DEVICE          *UsbCbiDev,
  IN  VOID                    *Command,
  IN  UINT8                   CommandSize,
  OUT UINT32                  *Result
  )
/*++

  Routine Description:
    In order to make consistence, CBI transportation protocol does only use
     the first 3 parameters. Other parameters are not used here.

  Arguments:
    UsbCbiDev    - USB_CBI_DEVICE
    Command      - Command to send 
    CommandSize  - Command Size
    Result       - Result to return

  Returns:
    EFI_SUCCESS         - This driver is removed DeviceHandle
    other               - This driver was not removed from this device 
--*/
{
  EFI_STATUS              Status;
  EFI_USB_IO_PROTOCOL     *UsbIo;
  EFI_USB_DEVICE_REQUEST  Request;
  UINT32                  TimeOutInMilliSeconds;

  UsbIo = UsbCbiDev->UsbIo;

  ZeroMem (&Request, sizeof (EFI_USB_DEVICE_REQUEST));

  //
  // Device request see CBI specification
  //
  Request.RequestType   = 0x21;
  Request.Length        = CommandSize;

  TimeOutInMilliSeconds = 1000;

  Status = UsbIo->UsbControlTransfer (
                    UsbIo,
                    &Request,
                    EfiUsbDataOut,
                    TimeOutInMilliSeconds,
                    Command,
                    CommandSize,
                    Result
                    );

  return Status;
}

STATIC
EFI_STATUS
CBI1DataPhase (
  IN  USB_CBI_DEVICE              *UsbCbiDev,
  IN  UINT32                      DataSize,
  IN  OUT VOID                    *DataBuffer,
  IN  EFI_USB_DATA_DIRECTION      Direction,
  IN  UINT16                      Timeout,
  OUT UINT32                      *Result
  )
/*++

Routine Description:

  CBI1 Data Phase

Arguments:

  UsbCbiDev   - USB_CBI_DEVICE
  DataSize    - Data Size
  DataBuffer  - Data Buffer
  Direction   - IN/OUT/NODATA
  Timeout     - Time out value in milliseconds
  Result      - Transfer result

Returns:

  EFI_SUCCESS - Success

--*/
{
  EFI_STATUS          Status;
  EFI_USB_IO_PROTOCOL *UsbIo;
  UINT8               EndpointAddr;
  UINTN               Remain;
  UINTN               Increment;
  UINT32              MaxPacketLen;
  UINT8               *BufferPtr;

  UsbIo     = UsbCbiDev->UsbIo;

  Remain    = DataSize;
  BufferPtr = (UINT8 *) DataBuffer;

  //
  // retrieve the the max packet length of the given endpoint
  //
  if (Direction == EfiUsbDataIn) {
    MaxPacketLen  = (UsbCbiDev->BulkInEndpointDescriptor).MaxPacketSize;
    EndpointAddr  = (UsbCbiDev->BulkInEndpointDescriptor).EndpointAddress;
  } else {
    MaxPacketLen  = (UsbCbiDev->BulkOutEndpointDescriptor).MaxPacketSize;
    EndpointAddr  = (UsbCbiDev->BulkOutEndpointDescriptor).EndpointAddress;
  }

  while (Remain > 0) {
    //
    // Using 15 packets to aVOID Bitstuff error
    //
    if (Remain > 15 * MaxPacketLen) {
      Increment = 15 * MaxPacketLen;
    } else {
      Increment = Remain;
    }

    Status = UsbIo->UsbBulkTransfer (
                      UsbIo,
                      EndpointAddr,
                      BufferPtr,
                      &Increment,
                      Timeout,
                      Result
                      );

    if (EFI_ERROR (Status)) {
      goto ErrorExit;
    }

    BufferPtr += Increment;
    Remain -= Increment;
  }

  return EFI_SUCCESS;

ErrorExit:

  if (Direction == EfiUsbDataIn) {
    Cbi1ReportStatusCode (
      UsbCbiDev->DevicePath,
      EFI_ERROR_CODE | EFI_ERROR_MINOR,
      (EFI_PERIPHERAL_REMOVABLE_MEDIA | EFI_P_EC_INPUT_ERROR)
      );
  } else {
    Cbi1ReportStatusCode (
      UsbCbiDev->DevicePath,
      EFI_ERROR_CODE | EFI_ERROR_MINOR,
      (EFI_PERIPHERAL_REMOVABLE_MEDIA | EFI_P_EC_OUTPUT_ERROR)
      );
  }

  if (((*Result) & EFI_USB_ERR_STALL) == EFI_USB_ERR_STALL) {
    //
    // just endpoint stall happens
    //
    UsbClearEndpointHalt (
      UsbIo,
      EndpointAddr,
      Result
      );
  }

  return Status;
}
//
// CBI1 USB ATAPI Protocol
//
STATIC
EFI_STATUS
EFIAPI
CBI1MassStorageReset (
  IN  EFI_USB_ATAPI_PROTOCOL      *This,
  IN  BOOLEAN                     ExtendedVerification
  )
/*++

  Routine Description:
    Reset CBI Devices
    
  Arguments:
    This                    - Protocol instance pointer.
    ExtendedVerification    - TRUE if we need to do strictly reset.

  Returns:
    EFI_SUCCESS         - Command succeeded.
    EFI_DEVICE_ERROR    - Command failed.

--*/
{
  UINT8               ResetCommand[12];
  EFI_USB_IO_PROTOCOL *UsbIo;
  USB_CBI_DEVICE      *UsbCbiDev;
  UINT8               EndpointAddr;
  UINT32              Result;

  UsbCbiDev = USB_CBI_DEVICE_FROM_THIS (This);
  UsbIo     = UsbCbiDev->UsbIo;

  Cbi1ReportStatusCode (
    UsbCbiDev->DevicePath,
    EFI_PROGRESS_CODE,
    (EFI_PERIPHERAL_REMOVABLE_MEDIA | EFI_P_PC_RESET)
    );

  if (ExtendedVerification) {
    UsbIo->UsbPortReset (UsbIo);
  }
  //
  // CBI reset command protocol
  //
  SetMem (ResetCommand, sizeof (ResetCommand), 0xff);
  ResetCommand[0] = 0x1d;
  ResetCommand[1] = 0x04;

  CBI1CommandPhase (
    UsbCbiDev,
    ResetCommand,
    12,
    &Result
    );

  //
  // clear bulk in endpoint stall feature
  //
  EndpointAddr = UsbCbiDev->BulkInEndpointDescriptor.EndpointAddress;
  UsbClearEndpointHalt (
    UsbIo,
    EndpointAddr,
    &Result
    );

  //
  // clear bulk out endpoint stall feature
  //
  EndpointAddr = UsbCbiDev->BulkOutEndpointDescriptor.EndpointAddress;
  UsbClearEndpointHalt (
    UsbIo,
    EndpointAddr,
    &Result
    );

  return EFI_SUCCESS;

}

STATIC
EFI_STATUS
EFIAPI
CBI1AtapiCommand (
  IN  EFI_USB_ATAPI_PROTOCOL  *This,
  IN  VOID                    *Command,
  IN  UINT8                   CommandSize,
  IN  VOID                    *DataBuffer,
  IN  UINT32                  BufferLength,
  IN  EFI_USB_DATA_DIRECTION  Direction,
  IN  UINT16                  TimeOutInMilliSeconds
  )
/*++

  Routine Description:
    Send ATAPI command using CBI1 protocol.
    
  Arguments:
    This              - Protocol instance pointer.
    Command           - Command buffer 
    CommandSize       - Size of Command Buffer
    DataBuffer        - Data buffer
    BufferLength      - Length of Data buffer
    Direction         - Data direction of this command
    TimeOutInMilliSeconds - Timeout value in ms

  Returns:
    EFI_SUCCESS         - Command succeeded.
    EFI_DEVICE_ERROR    - Command failed.

--*/
{
  EFI_STATUS      Status;
  USB_CBI_DEVICE  *UsbCbiDev;
  UINT32          Result;
  UINT8           Index;
  UINT8           MaxRetryNum;

  UsbCbiDev   = USB_CBI_DEVICE_FROM_THIS (This);

  MaxRetryNum = 3;

  for (Index = 0; Index < MaxRetryNum; Index++) {
  
    //
    // First send ATAPI command through CBI1
    //
    Status = CBI1CommandPhase (
              UsbCbiDev,
              Command,
              CommandSize,
              &Result
              );
    if (EFI_ERROR (Status)) {

      switch (Result) {

      case EFI_USB_NOERROR:
      case EFI_USB_ERR_STALL:
      case EFI_USB_ERR_SYSTEM:
        return EFI_DEVICE_ERROR;

      default:
        continue;
        break;
      }
    } else {
      break;
    }
  }

  if (Index == MaxRetryNum) {
    return EFI_DEVICE_ERROR;
  }

  for (Index = 0; Index < MaxRetryNum; Index++) {
    //
    // Send/Get Data if there is a Data Stage
    //
    switch (Direction) {

    case EfiUsbDataIn:
    case EfiUsbDataOut:
      Status = CBI1DataPhase (
                UsbCbiDev,
                BufferLength,
                DataBuffer,
                Direction,
                TimeOutInMilliSeconds,
                &Result
                );

      if (EFI_ERROR (Status)) {
        switch (Result) {

        case EFI_USB_NOERROR:
        case EFI_USB_ERR_STALL:
        case EFI_USB_ERR_SYSTEM:
          return EFI_DEVICE_ERROR;

        default:
          continue;
          break;
        }

      } else {

        return EFI_SUCCESS;
      }
      break;

    case EfiUsbNoData:
      return EFI_SUCCESS;
    }
  }
  //
  // If goes here, means met error.
  //
  return EFI_DEVICE_ERROR;
}

STATIC
VOID
Cbi1ReportStatusCode (
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  IN EFI_STATUS_CODE_TYPE      CodeType,
  IN EFI_STATUS_CODE_VALUE     Value
  )
/*++

  Routine Description:
    Report Status Code in Usb Cbi1 Driver

  Arguments:
    DevicePath  - Use this to get Device Path
    CodeType    - Status Code Type
    CodeValue   - Status Code Value

  Returns:
    None

--*/
{
  REPORT_STATUS_CODE_WITH_DEVICE_PATH (
    CodeType,
    Value,
    DevicePath
    );

}
