/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ANDROID_USB_DEVICE_OBJECT_H__
#define ANDROID_USB_DEVICE_OBJECT_H__
/** \file
  This file consists of declaration of class AndroidUsbDeviceObject that
  encapsulates an extension for KMDF device (FDO) object.
*/

#include "android_usb_wdf_object.h"

// Forward declaration for file object extension
class AndroidUsbFileObject;

/** AndroidUsbDeviceObject class encapsulates an extension for KMDF FDO device
  object. Instances of this class must be allocated from NonPagedPool.
*/
class AndroidUsbDeviceObject : public AndroidUsbWdfObject {
 public:
  /** \brief Constructs the object.

    This method must be called at low IRQL.
  */
  AndroidUsbDeviceObject();

  /** \brief Destructs the object.

    This method can be called at any IRQL.
  */
   ~AndroidUsbDeviceObject();

 public:
  /** \brief Creates and initializes FDO device object extension

    This method is called from driver's OnAddDevice method in response to
    AddDevice call from the PnP manager
    @param device_init[in] A pointer to a framework-allocated WDFDEVICE_INIT
           structure.
    @return If the routine succeeds, it returns STATUS_SUCCESS. Otherwise,
            it returns one of the error status values defined in ntstatus.h.
  */
  NTSTATUS CreateFDODevice(PWDFDEVICE_INIT device_init);

  /** \brief Resets target device

    When executing this method instance of this class may be deleted!
    This method must be called at PASSIVE IRQL.
    @return STATUS_SUCCESS or an appropriate error code
  */
  NTSTATUS ResetDevice();

 private:
  /** \name Device event handlers and callbacks
  */
  ///@{

  /** \brief Handler for PnP prepare hardware event

    This method performs any operations that are needed to make a device
    accessible to the driver. The framework calls this callback after the PnP
    manager has assigned hardware resources to the device and after the device
    has entered its uninitialized D0 state. This callback is called before
    calling the driver's EvtDeviceD0Entry callback function.
    This method is called at PASSIVE IRQL.
    @param resources_raw[in] A handle to a framework resource-list object that
           identifies the raw hardware resources that the PnP manager has
           assigned to the device.
    @param resources_translated[in] A handle to a framework resource-list
           object that identifies the translated hardware resources that the
           PnP manager has assigned to the device. 
    @return Successful status or an appropriate error code
  */
  NTSTATUS OnEvtDevicePrepareHardware(WDFCMRESLIST resources_raw,
                                      WDFCMRESLIST resources_translated);

  /** \brief Handler for PnP release hardware event

    This method performs operations that  that are needed when a device is no
    longer accessible. Framework calls the callback function if the device is
    being removed, or if the PnP manager is attempting to redistribute hardware
    resources. The framework calls the EvtDeviceReleaseHardware callback
    function after the driver's device has been shut off, the PnP manager has
    reclaimed the hardware resources that it assigned to the device, and the
    device is no longer accessible. (The PCI configuration state is still
    accessible.) Typically, a EvtDeviceReleaseHardware callback function unmaps
    memory that the driver's EvtDevicePrepareHardware callback function mapped.
    Usually, all other hardware shutdown operations should take place in the
    driver's EvtDeviceD0Exit callback function.
    This method is called at PASSIVE IRQL.
    @param wdf_device[in] A handle to a framework device object. 
    @param resources_translated[in] A handle to a framework resource-list
           object that identifies the translated hardware resources that the
           PnP manager has assigned to the device. 
    @return Successful status or an appropriate error code
  */
  NTSTATUS OnEvtDeviceReleaseHardware(WDFCMRESLIST resources_translated);

  /** \brief Handler for create file event (request)

    This method performs operations that are needed when an application
    requests access to an item within this device path (including device
    itself). This method is called synchronously, in the context of the
    user thread that opens the item.
    This method is called at PASSIVE IRQL.
    @param request[in] A handle to a framework request object that represents
           a file creation request.
    @param wdf_fo[in] A handle to a framework file object that describes a
           file that is being created with this request.
    @return Successful status or an appropriate error code
  */
  void OnEvtDeviceFileCreate(WDFREQUEST request, WDFFILEOBJECT wdf_fo);

  /** \brief Entry point for PnP prepare hardware event

    This callback performs any operations that are needed to make a device
    accessible to the driver. The framework calls this callback after the PnP
    manager has assigned hardware resources to the device and after the device
    has entered its uninitialized D0 state. This callback is called before
    calling the driver's EvtDeviceD0Entry callback function.
    This callback is called at PASSIVE IRQL.
    @param wdf_dev[in] A handle to a framework device object. 
    @param resources_raw[in] A handle to a framework resource-list object that
           identifies the raw hardware resources that the PnP manager has
           assigned to the device.
    @param resources_translated[in] A handle to a framework resource-list
           object that identifies the translated hardware resources that the
           PnP manager has assigned to the device. 
    @return Successful status or an appropriate error code
  */
  static NTSTATUS EvtDevicePrepareHardwareEntry(WDFDEVICE wdf_dev,
                                                WDFCMRESLIST resources_raw,
                                                WDFCMRESLIST resources_translated);

  /** \brief Entry point for PnP release hardware event

    This callback performs operations that  that are needed when a device is no
    longer accessible. Framework calls the callback function if the device is
    being removed, or if the PnP manager is attempting to redistribute hardware
    resources. The framework calls the EvtDeviceReleaseHardware callback
    function after the driver's device has been shut off, the PnP manager has
    reclaimed the hardware resources that it assigned to the device, and the
    device is no longer accessible. (The PCI configuration state is still
    accessible.) Typically, a EvtDeviceReleaseHardware callback function unmaps
    memory that the driver's EvtDevicePrepareHardware callback function mapped.
    Usually, all other hardware shutdown operations should take place in the
    driver's EvtDeviceD0Exit callback function.
    This callback is called at PASSIVE IRQL.
    @param wdf_dev[in] A handle to a framework device object. 
    @param resources_translated[in] A handle to a framework resource-list
           object that identifies the translated hardware resources that the
           PnP manager has assigned to the device. 
    @return Successful status or an appropriate error code
  */
  static NTSTATUS EvtDeviceReleaseHardwareEntry(WDFDEVICE wdf_dev,
                                                WDFCMRESLIST resources_translated);

  /** \brief Entry point for create file event (request)

    This callback performs operations that that are needed when an application
    requests access to a device. The framework calls a driver's
    EvtDeviceFileCreate callback function when a user application or another
    driver opens the device (or file on this device) to perform an I/O
    operation, such as reading or writing a file. This callback function is
    called synchronously, in the context of the user thread that opens the
    device.
    This callback is called at PASSIVE IRQL.
    @param wdf_dev[in] A handle to a framework device object. 
    @param request[in] A handle to a framework request object that represents
           a file creation request.
    @param wdf_fo[in] A handle to a framework file object that describes a
           file that is being created with this request.
    @return Successful status or an appropriate error code
  */
  static void EvtDeviceFileCreateEntry(WDFDEVICE wdf_dev,
                                       WDFREQUEST request,
                                       WDFFILEOBJECT wdf_fo);

  ///@}

 private:
  /** \name I/O request event handlers and callbacks
  */
  ///@{

  /** \brief Read event handler

    This method is called when a read request comes to a file object opened
    on this device.
    This method can be called IRQL <= DISPATCH_LEVEL.
    @param request[in] A handle to a framework request object.
    @param length[in] The number of bytes to be read.
  */
  void OnEvtIoRead(WDFREQUEST request, size_t length);

  /** \brief Write event handler

    This method is called when a write request comes to a file object opened
    on this device.
    This method can be called IRQL <= DISPATCH_LEVEL.
    @param request[in] A handle to a framework request object.
    @param length[in] The number of bytes to be written.
  */
  void OnEvtIoWrite(WDFREQUEST request, size_t length);

  /** \brief IOCTL event handler

    This method is called when a device control request comes to a file object
    opened on this device.
    This method can be called IRQL <= DISPATCH_LEVEL.
    @param request[in] A handle to a framework request object.
    @param output_buf_len[in] The length, in bytes, of the request's output
           buffer, if an output buffer is available.
    @param input_buf_len[in] The length, in bytes, of the request's input
           buffer, if an input buffer is available.
    @param ioctl_code[in] The driver-defined or system-defined I/O control code
           that is associated with the request.
  */
  void OnEvtIoDeviceControl(WDFREQUEST request,
                            size_t output_buf_len,
                            size_t input_buf_len,
                            ULONG ioctl_code);

  /** \brief Entry point for read event

    This callback is called when a read request comes to a file object opened
    on this device.
    This callback can be called IRQL <= DISPATCH_LEVEL.
    @param queue[in] A handle to the framework queue object that is associated
           with the I/O request.
    @param request[in] A handle to a framework request object.
    @param length[in] The number of bytes to be read.
  */
  static void EvtIoReadEntry(WDFQUEUE queue,
                             WDFREQUEST request,
                             size_t length);

  /** \brief Entry point for write event

    This callback is called when a write request comes to a file object opened
    on this device.
    This callback can be called IRQL <= DISPATCH_LEVEL.
    @param queue[in] A handle to the framework queue object that is associated
           with the I/O request.
    @param request[in] A handle to a framework request object.
    @param length[in] The number of bytes to be written.
  */
  static void EvtIoWriteEntry(WDFQUEUE queue,
                              WDFREQUEST request,
                              size_t length);

  /** \brief Entry point for device IOCTL event

    This callback is called when a device control request comes to a file
    object opened on this device.
    This callback can be called IRQL <= DISPATCH_LEVEL.
    @param queue[in] A handle to the framework queue object that is associated
           with the I/O request.
    @param request[in] A handle to a framework request object.
    @param output_buf_len[in] The length, in bytes, of the request's output
           buffer, if an output buffer is available.
    @param input_buf_len[in] The length, in bytes, of the request's input
           buffer, if an input buffer is available.
    @param ioctl_code[in] The driver-defined or system-defined I/O control code
           that is associated with the request.
  */
  static void EvtIoDeviceControlEntry(WDFQUEUE queue,
                                      WDFREQUEST request,
                                      size_t output_buf_len,
                                      size_t input_buf_len,
                                      ULONG ioctl_code);

  ///@}

 public:
  /** \name Device level I/O request handlers
  */
  ///@{

  /** \brief Gets USB device descriptor

    This method can be called at IRQL <= DISPATCH_LEVEL
    @param request[in] A handle to a framework request object for this IOCTL.
    @param output_buf_len[in] The length, in bytes, of the request's output
           buffer, if an output buffer is available.
  */
  void OnGetUsbDeviceDescriptorCtl(WDFREQUEST request, size_t output_buf_len);

  /** \brief Gets USB configuration descriptor for the selected configuration.

    This method can be called at IRQL <= DISPATCH_LEVEL
    @param request[in] A handle to a framework request object for this IOCTL.
    @param output_buf_len[in] The length, in bytes, of the request's output
           buffer, if an output buffer is available.
  */
  void OnGetUsbConfigDescriptorCtl(WDFREQUEST request, size_t output_buf_len);

  /** \brief Gets USB configuration descriptor for the selected interface.

    This method can be called at IRQL <= DISPATCH_LEVEL
    @param request[in] A handle to a framework request object for this IOCTL.
    @param output_buf_len[in] The length, in bytes, of the request's output
           buffer, if an output buffer is available.
  */
  void OnGetUsbInterfaceDescriptorCtl(WDFREQUEST request, size_t output_buf_len);

  /** \brief Gets information about an endpoint.

    This method can be called at IRQL <= DISPATCH_LEVEL
    @param request[in] A handle to a framework request object for this IOCTL.
    @param input_buf_len[in] The length, in bytes, of the request's input
           buffer, if an input buffer is available.
    @param output_buf_len[in] The length, in bytes, of the request's output
           buffer, if an output buffer is available.
  */
  void OnGetEndpointInformationCtl(WDFREQUEST request,
                                   size_t input_buf_len,
                                   size_t output_buf_len);

  /** \brief Gets device serial number.

    Serial number is returned in form of zero-terminated string that in the
    output buffer. This method must be called at low IRQL.
    @param request[in] A handle to a framework request object for this IOCTL.
    @param output_buf_len[in] The length, in bytes, of the request's output
           buffer, if an output buffer is available.
  */
  void OnGetSerialNumberCtl(WDFREQUEST request, size_t output_buf_len);

  ///@}

 private:
  /** \name Internal methods
  */
  ///@{

  /** \brief Creates default request queue for this device.

    In KMDF all I/O requests are coming through the queue object. So, in order
    to enable our device to receive I/O requests we must create a queue for it.
    This method is called at PASSIVE IRQL.
    @return STATUS_SUCCESS or an appropriate error code.
  */
  NTSTATUS CreateDefaultQueue();

  /** \brief Configures our device.

    This method is called from the prepare hardware handler after underlying
    FDO device has been created.
    This method is called at PASSSIVE IRQL.
    @return STATUS_SUCCESS or an appropriate error code.
  */
  NTSTATUS ConfigureDevice();

  /** \brief Selects interfaces on our device.

    This method is called from the prepare hardware handler after underlying
    FDO device has been created and configured.
    This method is called at PASSSIVE IRQL.
    @return STATUS_SUCCESS or an appropriate error code.
  */
  NTSTATUS SelectInterfaces();
  
  /** \brief Gets pipe index from a file name

    This method is called from OnEvtDeviceFileCreate to determine index of
    the pipe this file is addressing.
    This method is called at PASSIVE IRQL.
    @param file_path[in] Path to the file that being opened.
    @return Pipe index or INVALID_UCHAR if index cannot be calculated.
  */
  UCHAR GetPipeIndexFromFileName(PUNICODE_STRING file_path);

  /** \brief Creates file object extension for a pipe

    This method is called from OnEvtDeviceFileCreate to create an appropriate
    file object extension for a particular pipe type.
    This method is called at PASSIVE IRQL.
    @param wdf_fo[in] KMDF file to extend.
    @param wdf_pipe_obj[in] KMDF pipe for this extension
    @param pipe_info[in] Pipe information
    @param wdf_file_ext[out] Upon successfull completion will receive instance
           of the extension.
    @return STATUS_SUCCESS or an appropriate error code
  */
  NTSTATUS CreatePipeFileObjectExt(WDFFILEOBJECT wdf_fo,
                                   WDFUSBPIPE wdf_pipe_obj,
                                   const WDF_USB_PIPE_INFORMATION* pipe_info,
                                   AndroidUsbFileObject** wdf_file_ext);

  ///@}

 private:
  /** \name Debugging support
  */
  ///@{

#if DBG
  /// Prints USB_DEVICE_DESCRIPTOR to debug output
  void PrintUsbDeviceDescriptor(const USB_DEVICE_DESCRIPTOR* desc);

  /// Prints WDF_USB_DEVICE_INFORMATION to debug output
  void PrintUsbTargedDeviceInformation(const WDF_USB_DEVICE_INFORMATION* info);

  /// Prints USB_CONFIGURATION_DESCRIPTOR to debug output
  void PrintConfigDescriptor(const USB_CONFIGURATION_DESCRIPTOR* desc,
                             ULONG size);

  /// Prints WDF_USB_DEVICE_SELECT_CONFIG_PARAMS to debug output
  void PrintSelectedConfig(const WDF_USB_DEVICE_SELECT_CONFIG_PARAMS* config);

  /// Prints USB_INTERFACE_DESCRIPTOR to debug output
  void PrintInterfaceDescriptor(const USB_INTERFACE_DESCRIPTOR* desc);

  /// Prints WDF_USB_PIPE_INFORMATION to debug output
  void PrintPipeInformation(const WDF_USB_PIPE_INFORMATION* info,
                            UCHAR pipe_index);

#endif  // DBG

  ///@}

 public:
  /// Gets WDF device handle for this device
  __forceinline WDFDEVICE wdf_device() const {
    return reinterpret_cast<WDFDEVICE>(wdf_object());
  }

  /// Gets target USB device descriptor
  __forceinline const USB_DEVICE_DESCRIPTOR* usb_device_descriptor() const {
    return &usb_device_descriptor_;
  }

  /// Gets target USB device information
  __forceinline const WDF_USB_DEVICE_INFORMATION* usb_device_info() const {
    return &usb_device_info_;
  }

  /// Gets selected interface descriptor
  __forceinline const USB_INTERFACE_DESCRIPTOR* interface_descriptor() const {
    return &interface_descriptor_;
  }

  /// Gets target (PDO) device handle
  __forceinline WDFUSBDEVICE wdf_target_device() const {
    return wdf_target_device_;
  }

  /// Checks if target device has been created
  __forceinline bool IsTaretDeviceCreated() const {
    return (NULL != wdf_target_device());
  }

  /// Gets USB configuration descriptor
  __forceinline const USB_CONFIGURATION_DESCRIPTOR* configuration_descriptor() const {
    return configuration_descriptor_;
  }

  /// Checks if device has been configured
  __forceinline bool IsDeviceConfigured() const {
    return (NULL != configuration_descriptor());
  }

  /// Gets number of interfaces for this device
  __forceinline UCHAR GetInterfaceCount() const {
    ASSERT(IsDeviceConfigured());
    return IsDeviceConfigured() ? configuration_descriptor()->bNumInterfaces : 0;
  }

  /// Checks if this is "single interface" device
  __forceinline bool IsSingleInterfaceDevice() const {
    return (1 == GetInterfaceCount());
  }

  /// Gets USB interface selected on this device
  __forceinline WDFUSBINTERFACE wdf_usb_interface() const {
    return wdf_usb_interface_;
  }

  /// Checks if an interface has been selected on this device
  __forceinline bool IsInterfaceSelected() const {
    return (NULL != wdf_usb_interface());
  }

  /// Gets number of pipes configured on this device
  __forceinline UCHAR configured_pipes_num() const {
    return configured_pipes_num_;
  }

  /// Gets index of the bulk read pipe
  __forceinline UCHAR bulk_read_pipe_index() const {
    return bulk_read_pipe_index_;
  }

  /// Gets index of the bulk write pipe
  __forceinline UCHAR bulk_write_pipe_index() const {
    return bulk_write_pipe_index_;
  }

  /// Checks if this is a high speed device
  __forceinline bool IsHighSpeed() const {
    return (0 != (usb_device_info()->Traits & WDF_USB_DEVICE_TRAIT_AT_HIGH_SPEED));
  }

  /// Checks if bulk read pipe index is known
  __forceinline bool IsBulkReadPipeKnown() const {
    return (INVALID_UCHAR != bulk_read_pipe_index());
  }

  /// Checks if bulk write pipe index is known
  __forceinline bool IsBulkWritePipeKnown() const {
    return (INVALID_UCHAR != bulk_write_pipe_index());
  }

  /// Gets device serial number string. Note that string may be
  /// not zero-terminated. Use serial_number_len() to get actual
  /// length of this string.
  __forceinline const WCHAR* serial_number() const {
    ASSERT(NULL != serial_number_handle_);
    return (NULL != serial_number_handle_) ?
      reinterpret_cast<const WCHAR*>
        (WdfMemoryGetBuffer(serial_number_handle_, NULL)) :
      NULL;
  }

  /// Gets length (in bytes) of device serial number string
  __forceinline USHORT serial_number_char_len() const {
    return serial_number_char_len_;
  }

  /// Gets length (in bytes) of device serial number string
  __forceinline USHORT serial_number_byte_len() const {
    return serial_number_char_len() * sizeof(WCHAR);
  }

 protected:
  /// Target USB device descriptor
  USB_DEVICE_DESCRIPTOR         usb_device_descriptor_;

  /// Target USB device information
  WDF_USB_DEVICE_INFORMATION    usb_device_info_;

  /// Selected interface descriptor
  USB_INTERFACE_DESCRIPTOR      interface_descriptor_;

  /// USB configuration descriptor
  PUSB_CONFIGURATION_DESCRIPTOR configuration_descriptor_;

  /// Target (PDO?) device handle
  WDFUSBDEVICE                  wdf_target_device_;

  /// USB interface selected on this device
  WDFUSBINTERFACE               wdf_usb_interface_;

  /// Device serial number
  WDFMEMORY                     serial_number_handle_;

  /// Device serial number string length
  USHORT                        serial_number_char_len_;

  /// Number of pipes configured on this device
  UCHAR                         configured_pipes_num_;

  /// Index of the bulk read pipe
  UCHAR                         bulk_read_pipe_index_;

  /// Index of the bulk write pipe
  UCHAR                         bulk_write_pipe_index_;
};

/** \brief Gets device KMDF object extension for the given KMDF object

  @param wdf_dev[in] KMDF handle describing device object
  @return Instance of AndroidUsbDeviceObject associated with KMDF object or
          NULL if association is not found.
*/
__forceinline AndroidUsbDeviceObject* GetAndroidUsbDeviceObjectFromHandle(
    WDFDEVICE wdf_dev) {
  AndroidUsbWdfObject* wdf_object_ext =
    GetAndroidUsbWdfObjectFromHandle(wdf_dev);
  ASSERT((NULL != wdf_object_ext) &&
         wdf_object_ext->Is(AndroidUsbWdfObjectTypeDevice));
  if ((NULL != wdf_object_ext) &&
      wdf_object_ext->Is(AndroidUsbWdfObjectTypeDevice)) {
    return reinterpret_cast<AndroidUsbDeviceObject*>(wdf_object_ext);
  }
  return NULL;
}

#endif  // ANDROID_USB_DEVICE_OBJECT_H__