/*
 * 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.
 */

/** \file
  This file consists of implementation of class AndroidUsbDriverObject that
  encapsulates our driver object
*/
#pragma data_seg()
#pragma code_seg()

#include "precomp.h"
#include "android_usb_device_object.h"
#include "android_usb_driver_object.h"

#pragma data_seg()

/** Globally accessible instance of the AndroidUsbDriverObject.
  NT OS design allows us using of a global pointer to our driver object
  instance since it can't be created or destroyed concurently and its value
  is not going to change between creation and destruction.
*/
AndroidUsbDriverObject* global_driver_object = NULL;

#pragma code_seg("INIT")

extern "C" {

/// Main entry point to the driver
NTSTATUS DriverEntry(PDRIVER_OBJECT drv_object, PUNICODE_STRING reg_path) {
  // Just pass it down inside the class
  return AndroidUsbDriverObject::DriverEntry(drv_object, reg_path);
}

}  // extern "C"

NTSTATUS AndroidUsbDriverObject::DriverEntry(PDRIVER_OBJECT drv_object,
                                             PUNICODE_STRING reg_path) {
  ASSERT_IRQL_PASSIVE();
  ASSERT(NULL != drv_object);
  ASSERT((NULL != reg_path) &&
         (NULL != reg_path->Buffer) &&
         (0 != reg_path->Length));

  // Instantiate driver object
  global_driver_object = new(NonPagedPool, GANDR_POOL_TAG_DRIVER_OBJECT)
    AndroidUsbDriverObject(drv_object, reg_path);
  ASSERT(NULL != global_driver_object);
  if (NULL == global_driver_object)
    return STATUS_INSUFFICIENT_RESOURCES;

  // Initialize driver object
  NTSTATUS status = global_driver_object->OnDriverEntry(drv_object, reg_path);

  if (!NT_SUCCESS(status)) {
    // Something went wrong. Delete our driver object and get out of here.
    delete global_driver_object;
  }

  return status;
}

AndroidUsbDriverObject::AndroidUsbDriverObject(PDRIVER_OBJECT drv_object,
                                               PUNICODE_STRING reg_path)
    : driver_object_(drv_object),
      wdf_driver_(NULL) {
  ASSERT_IRQL_PASSIVE();
  ASSERT(NULL != driver_object());
}

NTSTATUS AndroidUsbDriverObject::OnDriverEntry(PDRIVER_OBJECT drv_object,
                                               PUNICODE_STRING reg_path) {
  ASSERT_IRQL_PASSIVE();
  ASSERT(driver_object() == drv_object);

  // Initiialize driver config, specifying our unload callback and default
  // pool tag for memory allocations that KMDF does on our behalf.
  WDF_DRIVER_CONFIG config;
  WDF_DRIVER_CONFIG_INIT(&config, EvtDeviceAddEntry);
  config.EvtDriverUnload = EvtDriverUnloadEntry;
  config.DriverPoolTag = GANDR_POOL_TAG_DEFAULT;

  // Create a framework driver object to represent our driver.
  NTSTATUS status = WdfDriverCreate(drv_object,
                                    reg_path,
                                    WDF_NO_OBJECT_ATTRIBUTES,
                                    &config,
                                    &wdf_driver_);
  ASSERT(NT_SUCCESS(status));
  if (!NT_SUCCESS(status))
    return status;

  GoogleDbgPrint("\n>>>>>>>>>> Android USB driver has started >>>>>>>>>>");

  return STATUS_SUCCESS;
}

#pragma code_seg("PAGE")

AndroidUsbDriverObject::~AndroidUsbDriverObject() {
  ASSERT_IRQL_PASSIVE();
}

NTSTATUS AndroidUsbDriverObject::OnAddDevice(PWDFDEVICE_INIT device_init) {
  ASSERT_IRQL_PASSIVE();
  GoogleDbgPrint("\n++++++++++ AndroidUsbDriverObject::OnAddDevice ++++++++++");
  // Instantiate our device object extension for this device
  AndroidUsbDeviceObject* wdf_device_ext =
    new(NonPagedPool, GANDR_POOL_TAG_KMDF_DEVICE) AndroidUsbDeviceObject();
  ASSERT(NULL != wdf_device_ext);
  if (NULL == wdf_device_ext)
    return STATUS_INSUFFICIENT_RESOURCES;

  // Create and initialize FDO device
  NTSTATUS status = wdf_device_ext->CreateFDODevice(device_init);
  ASSERT(NT_SUCCESS(status));
  if (!NT_SUCCESS(status))
    delete wdf_device_ext;

  return status;
}

void AndroidUsbDriverObject::OnDriverUnload() {
  ASSERT_IRQL_PASSIVE();
  GoogleDbgPrint("\n<<<<<<<<<< Android USB driver is unloaded <<<<<<<<<<");
}

NTSTATUS AndroidUsbDriverObject::EvtDeviceAddEntry(
    WDFDRIVER wdf_drv,
    PWDFDEVICE_INIT device_init) {
  ASSERT_IRQL_PASSIVE();
  ASSERT((NULL != global_driver_object) && (global_driver_object->wdf_driver() == wdf_drv));

  // Pass it down to our driver object
  if ((NULL == global_driver_object) ||
      (global_driver_object->wdf_driver() != wdf_drv)) {
    return STATUS_INTERNAL_ERROR;
  }

  return global_driver_object->OnAddDevice(device_init);
}

VOID AndroidUsbDriverObject::EvtDriverUnloadEntry(WDFDRIVER wdf_drv) {
  ASSERT_IRQL_PASSIVE();
  ASSERT((NULL != global_driver_object) &&
         (global_driver_object->wdf_driver() == wdf_drv));

  // Pass it down to our driver object
  if ((NULL != global_driver_object) &&
      (global_driver_object->wdf_driver() == wdf_drv)) {
    global_driver_object->OnDriverUnload();
    // Now we can (and have to) delete our driver object
    delete global_driver_object;
  }
}

#if DBG

#pragma code_seg()

ULONG __cdecl GoogleDbgPrint(char* format, ...) {
  va_list arg_list;
  va_start(arg_list, format);
  ULONG ret =
    vDbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, format, arg_list);
  va_end(arg_list);

  return ret;
}

#endif  // DBG

#pragma data_seg()
#pragma code_seg()
