/* $Id: USBProxyServiceOs2.cpp $ */
/** @file
 * VirtualBox USB Proxy Service, OS/2 Specialization.
 */

/*
 * Copyright (C) 2005-2012 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.
 */


/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#define INCL_BASE
#define INCL_ERRORS
#include "USBProxyService.h"
#include "Logging.h"

#include <VBox/usb.h>
#include <VBox/err.h>

#include <iprt/string.h>
#include <iprt/alloc.h>
#include <iprt/assert.h>
#include <iprt/file.h>
#include <iprt/err.h>


/**
 * Initialize data members.
 */
USBProxyServiceOs2::USBProxyServiceOs2(Host *aHost)
    : USBProxyService(aHost), mhev(NULLHANDLE), mhmod(NULLHANDLE),
    mpfnUsbRegisterChangeNotification(NULL), mpfnUsbDeregisterNotification(NULL),
    mpfnUsbQueryNumberDevices(NULL), mpfnUsbQueryDeviceReport(NULL)
{
    LogFlowThisFunc(("aHost=%p\n", aHost));

    /*
     * Try initialize the usbcalls stuff.
     */
    int rc = DosCreateEventSem(NULL, &mhev, 0, FALSE);
    rc = RTErrConvertFromOS2(rc);
    if (RT_SUCCESS(rc))
    {
        rc = DosLoadModule(NULL, 0, (PCSZ)"usbcalls", &mhmod);
        rc = RTErrConvertFromOS2(rc);
        if (RT_SUCCESS(rc))
        {
            if (    (rc = DosQueryProcAddr(mhmod, 0, (PCSZ)"UsbQueryNumberDevices",         (PPFN)&mpfnUsbQueryNumberDevices))          == NO_ERROR
                &&  (rc = DosQueryProcAddr(mhmod, 0, (PCSZ)"UsbQueryDeviceReport",          (PPFN)&mpfnUsbQueryDeviceReport))           == NO_ERROR
                &&  (rc = DosQueryProcAddr(mhmod, 0, (PCSZ)"UsbRegisterChangeNotification", (PPFN)&mpfnUsbRegisterChangeNotification))  == NO_ERROR
                &&  (rc = DosQueryProcAddr(mhmod, 0, (PCSZ)"UsbDeregisterNotification",     (PPFN)&mpfnUsbDeregisterNotification))      == NO_ERROR
               )
            {
                rc = mpfnUsbRegisterChangeNotification(&mNotifyId, mhev, mhev);
                if (!rc)
                {
                    /*
                     * Start the poller thread.
                     */
                    rc = start();
                    if (RT_SUCCESS(rc))
                    {
                        LogFlowThisFunc(("returns successfully - mNotifyId=%d\n", mNotifyId));
                        mLastError = VINF_SUCCESS;
                        return;
                    }
                }

                LogRel(("USBProxyServiceOs2: failed to register change notification, rc=%d\n", rc));
            }
            else
                LogRel(("USBProxyServiceOs2: failed to load usbcalls\n"));

            DosFreeModule(mhmod);
        }
        else
            LogRel(("USBProxyServiceOs2: failed to load usbcalls, rc=%d\n", rc));
        mhmod = NULLHANDLE;
    }
    else
        mhev = NULLHANDLE;

    mLastError = rc;
    LogFlowThisFunc(("returns failure!!! (rc=%Rrc)\n", rc));
}


/**
 * Stop all service threads and free the device chain.
 */
USBProxyServiceOs2::~USBProxyServiceOs2()
{
    LogFlowThisFunc(("\n"));

    /*
     * Stop the service.
     */
    if (isActive())
        stop();

    /*
     * Free resources.
     */
    if (mhmod)
    {
        if (mpfnUsbDeregisterNotification)
            mpfnUsbDeregisterNotification(mNotifyId);

        mpfnUsbRegisterChangeNotification = NULL;
        mpfnUsbDeregisterNotification = NULL;
        mpfnUsbQueryNumberDevices = NULL;
        mpfnUsbQueryDeviceReport = NULL;

        DosFreeModule(mhmod);
        mhmod = NULLHANDLE;
    }
}


int USBProxyServiceOs2::captureDevice(HostUSBDevice *aDevice)
{
    AssertReturn(aDevice, VERR_GENERAL_FAILURE);
    AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);

    AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
    LogFlowThisFunc(("aDevice=%s\n", aDevice->getName().c_str()));

    /*
     * Don't think we need to do anything when the device is held... fake it.
     */
    Assert(aDevice->isStatePending());
    devLock.release();
    interruptWait();

    return VINF_SUCCESS;
}


int USBProxyServiceOs2::releaseDevice(HostUSBDevice *aDevice)
{
    AssertReturn(aDevice, VERR_GENERAL_FAILURE);
    AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);

    AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
    LogFlowThisFunc(("aDevice=%s\n", aDevice->getName().c_str()));

    /*
     * We're not really holding it atm., just fake it.
     */
    Assert(aDevice->isStatePending());
    devLock.release();
    interruptWait();

    return VINF_SUCCESS;
}


bool USBProxyServiceOs2::updateDeviceState(HostUSBDevice *aDevice, PUSBDEVICE aUSBDevice, bool *aRunFilters, SessionMachine **aIgnoreMachine)
{
    AssertReturn(aDevice, false);
    AssertReturn(!aDevice->isWriteLockOnCurrentThread(), false);
    return updateDeviceStateFake(aDevice, aUSBDevice, aRunFilters, aIgnoreMachine);
}



int USBProxyServiceOs2::wait(RTMSINTERVAL aMillies)
{
    int rc = DosWaitEventSem(mhev, aMillies);
    return RTErrConvertFromOS2(rc);
}


int USBProxyServiceOs2::interruptWait(void)
{
    int rc = DosPostEventSem(mhev);
    return rc == NO_ERROR || rc == ERROR_ALREADY_POSTED
         ? VINF_SUCCESS
         : RTErrConvertFromOS2(rc);
}

#include <stdio.h>

PUSBDEVICE USBProxyServiceOs2::getDevices(void)
{
    /*
     * Count the devices.
     */
    ULONG cDevices = 0;
    int rc = mpfnUsbQueryNumberDevices((PULONG)&cDevices); /* Thanks to com/xpcom, PULONG and ULONG * aren't the same. */
    if (rc)
        return NULL;

    /*
     * Retrieve information about each device.
     */
    PUSBDEVICE pFirst = NULL;
    PUSBDEVICE *ppNext = &pFirst;
    for (ULONG i = 0; i < cDevices; i++)
    {
        /*
         * Query the device and config descriptors.
         */
        uint8_t abBuf[1024];
        ULONG cb = sizeof(abBuf);
        rc = mpfnUsbQueryDeviceReport(i + 1, (PULONG)&cb, &abBuf[0]); /* see above (PULONG) */
        if (rc)
            continue;
        PUSBDEVICEDESC pDevDesc = (PUSBDEVICEDESC)&abBuf[0];
        if (    cb < sizeof(*pDevDesc)
            ||  pDevDesc->bDescriptorType != USB_DT_DEVICE
            ||  pDevDesc->bLength < sizeof(*pDevDesc)
            ||  pDevDesc->bLength > sizeof(*pDevDesc) * 2)
            continue;
        PUSBCONFIGDESC pCfgDesc = (PUSBCONFIGDESC)&abBuf[pDevDesc->bLength];
        if (    pCfgDesc->bDescriptorType != USB_DT_CONFIG
            ||  pCfgDesc->bLength >= sizeof(*pCfgDesc))
            pCfgDesc = NULL;

        /*
         * Skip it if it's some kind of hub.
         */
        if (pDevDesc->bDeviceClass == USB_HUB_CLASSCODE)
            continue;

        /*
         * Allocate a new device node and initialize it with the basic stuff.
         */
        PUSBDEVICE pCur = (PUSBDEVICE)RTMemAlloc(sizeof(*pCur));
        pCur->bcdUSB = pDevDesc->bcdUSB;
        pCur->bDeviceClass = pDevDesc->bDeviceClass;
        pCur->bDeviceSubClass = pDevDesc->bDeviceSubClass;
        pCur->bDeviceProtocol = pDevDesc->bDeviceProtocol;
        pCur->idVendor = pDevDesc->idVendor;
        pCur->idProduct = pDevDesc->idProduct;
        pCur->bcdDevice = pDevDesc->bcdDevice;
        pCur->pszManufacturer = RTStrDup("");
        pCur->pszProduct = RTStrDup("");
        pCur->pszSerialNumber = NULL;
        pCur->u64SerialHash = 0;
        //pCur->bNumConfigurations = pDevDesc->bNumConfigurations;
        pCur->bNumConfigurations = 0;
        pCur->paConfigurations = NULL;
        pCur->enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
        pCur->enmSpeed = USBDEVICESPEED_UNKNOWN;
        pCur->pszAddress = NULL;
        RTStrAPrintf((char **)&pCur->pszAddress, "p=0x%04RX16;v=0x%04RX16;r=0x%04RX16;e=0x%08RX32",
                     pDevDesc->idProduct, pDevDesc->idVendor, pDevDesc->bcdDevice, i);

        pCur->bBus = 0;
        pCur->bLevel = 0;
        pCur->bDevNum = 0;
        pCur->bDevNumParent = 0;
        pCur->bPort = 0;
        pCur->bNumDevices = 0;
        pCur->bMaxChildren = 0;

        /* link it */
        pCur->pNext = NULL;
        pCur->pPrev = *ppNext;
        *ppNext = pCur;
        ppNext = &pCur->pNext;
    }

    return pFirst;
}

