Les coulisses du Techdays 2014 : Développer un driver pour l’Oculus Rift : Part II

29 Janvier 2014 : J-10 avant l’édition des techdays 2014.

Dans mon précédant billet, nous avons vu comment préparer l’environnement de développement afin d’être dans les meilleurs conditions pour développer un driver, ainsi que la création d’une coquille vide pour notre driver.

Dans ce billet, nous allons nous atteler à intégrer notre driver aux Sensor APIS et faire en sorte qu’il soit reconnu comme un driver de type OrientationSensor.

Rappelez-vous, dans le squelette de notre driver, nous avions une classe CMyDriver qui dans sa méthode OnDeviceAdd, instanciait la classe CMyDevice, par l’intermédiaire d’une méthode statique CreateInstanceAndInitialize. Cette méthode appel elle même la méthode Initialize() qui a pour but essentiel, d’instancier la classe CMyDevice et de retourner un pointeur IUnknown sur la classe CMyDevice au Framework WDF.

HRESULT
CMyDevice::Initialize(
    __in IWDFDriver           * FxDriver,
    __in IWDFDeviceInitialize * FxDeviceInit
    )

{
    IWDFDevice *fxDevice = NULL;
    HRESULT hr = S_OK;
    IUnknown *unknown = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    //
    // Configure things like the locking model before we go to create our
    // partner device.
    //

    //
    // Set the locking model.
    //

    FxDeviceInit->SetLockingConstraint(None);

    //
    // Set power policy ownership.
    //
    FxDeviceInit->SetPowerPolicyOwnership(TRUE);

    hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to get IUnknown %!hresult!",
                    hr);
        goto Exit;
    }

    hr = FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice);
    DriverSafeRelease(unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create a framework device %!hresult!",
                    hr);
        goto Exit;
    }

    m_spWdfDevice = fxDevice;
    //
    // Drop the reference we got from CreateDevice.  Since this object
    // is partnered with the framework object they have the same
    // lifespan - there is no need for an additional reference.
    //
    
    

    DriverSafeRelease(fxDevice);

    //TODO : Init the SensorDDI here but move to OnPrepareHdware when link with the Oculus Rift
    
Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

La ligne this->QueryInterface(__uuidof(IUnknown), (void **)&unknown) récupère le pointeur IUnknown sur l’instance courante de la classe CMyDevice, que l’on fournit au Framework WDF par l’intermédiaire de la méthode CreateDevice de l’instance de la classe IWDFDriver passée en paramètre de la méthode Initialize.
FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice)

Si on passe un pointeur sur notre classe, c’est que l’on souhaite que le Framework WDF nous rappel. Pour ce faire, la classe CMyDevice qui a pour rôle de préparer le périphérique et de l’initialiser, doit implémenter entre autre, deux Interfaces IPnpCallBackHardware2 et IPnpCallback comme illustré dans le code suivant :

/*++

Module Name:

    Device.h

Abstract:

    This module contains the type definitions of the
    device driver.

Environment:

    Windows User-Mode Driver Framework (WUDF)

--*/

#pragma once
#include "Internal.h"
#include "SensorsManager.h"

//
// Class for the device.
//

class CMyDevice :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IPnpCallback,
    public IPnpCallbackHardware2,
    public IFileCallbackCleanup
{

public:

    DECLARE_NOT_AGGREGATABLE(CMyDevice)

    BEGIN_COM_MAP(CMyDevice)
        COM_INTERFACE_ENTRY(IPnpCallback)        
        COM_INTERFACE_ENTRY(IPnpCallbackHardware2)
        COM_INTERFACE_ENTRY(IFileCallbackCleanup)
    END_COM_MAP()

    CMyDevice();

    ~CMyDevice();
    

//
// Private data members.
//
private:

    //
    // Weak reference to framework device object.
    //
    IWDFDevice *            m_FxDevice;
    CComPtr<IWDFDevice>             m_spWdfDevice;

    //
    // Weak reference to I/O queue
    //
    CMyIoQueue *            m_IoQueue;    
    
    //CComObject<SensorManager>*         m_pSensorManager;
//
// Private methods.
//
private:

    HRESULT
    Initialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

    
    HRESULT InitializeSensorManager(_In_ IWDFDevice3 * pWdfDevice, _In_ IWDFCmResourceList * pWdfResourcesRaw, _In_ IWDFCmResourceList * pWdfResourcesTranslated);
    
    volatile DWORD64                    m_dwShutdownControlFlags;
    inline void     EnterShutdown();
    inline void     ExitShutdown();
    
//
// Public methods
//
public:
    inline HRESULT EnterProcessing(DWORD64 dwControlFlag);
    inline void    ExitProcessing(DWORD64 dwControlFlag);

    HRESULT ProcessIoControl(
        _In_ IWDFIoQueue*     pQueue,
        _In_ IWDFIoRequest*   pRequest,
        _In_ ULONG            ControlCode,
        SIZE_T           InputBufferSizeInBytes,
        SIZE_T           OutputBufferSizeInBytes,
        DWORD*           pcbWritten);

    //
    // The factory method used to create an instance of this driver.
    //
    
    static
    HRESULT
    CreateInstanceAndInitialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit,
        __out CMyDevice **Device
        );

    HRESULT
    Configure(
        VOID
        );

//
// COM methods
//
public:

    //
    // IPnpCallback
    //
    STDMETHOD(OnD0Entry)(_In_ IWDFDevice* pWdfDevice, _In_ WDF_POWER_DEVICE_STATE previousState);
    STDMETHOD(OnD0Exit)(_In_ IWDFDevice* pWdfDevice, _In_ WDF_POWER_DEVICE_STATE newState);
    STDMETHOD_(VOID, OnSurpriseRemoval)(_In_ IWDFDevice* pWdfDevice);
    STDMETHOD_(HRESULT, OnQueryRemove)(_In_ IWDFDevice* pWdfDevice);
    STDMETHOD_(HRESULT, OnQueryStop)(_In_ IWDFDevice* pWdfDevice);

    

    // IPnpCallbackHardware2
    STDMETHOD_(HRESULT, OnPrepareHardware)(
        _In_ IWDFDevice3 * pWdfDevice,
        _In_ IWDFCmResourceList * pWdfResourcesRaw,
        _In_ IWDFCmResourceList * pWdfResourcesTranslated);
    STDMETHOD_(HRESULT, OnReleaseHardware)(
        _In_ IWDFDevice3 * pWdfDevice,
        _In_ IWDFCmResourceList * pWdfResourcesTranslated);
    
    // IFileCallbackCleanup
    STDMETHOD_(VOID, OnCleanupFile)(_In_ IWDFFile *pWdfFile);
private :
    CComObject<SensorsManager>*         m_pSensorManager;
};

IPnpCallBackHardware2 expose deux méthodes à implémenter, OnPrepareHardware et OnReleaseHardware , qui seront respectivement appelées par le Framework WDF lors de l’initialisation du driver ou lorsqu’il sera releasé.

//
// IPnpCallbackHardware2
//
HRESULT CMyDevice::OnPrepareHardware(_In_ IWDFDevice3 * pWdfDevice,
    _In_ IWDFCmResourceList * pWdfResourcesRaw,
    _In_ IWDFCmResourceList * pWdfResourcesTranslated)
{
    
    HRESULT hr = (NULL != pWdfDevice) ? S_OK : E_UNEXPECTED;
    
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    if (SUCCEEDED(hr))
    {
        hr = EnterProcessing(PROCESSING_IPNPCALLBACKHARDWARE);
        if (nullptr != pWdfDevice)
        {
            m_spWdfDevice = pWdfDevice;
            

        }
        hr = this->InitializeSensorManager(pWdfDevice,pWdfResourcesRaw,pWdfResourcesTranslated);

    }

    ExitProcessing(PROCESSING_IPNPCALLBACKHARDWARE);
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit hr=%!HRESULT!", hr);
    return hr;
}
HRESULT CMyDevice::OnReleaseHardware(_In_ IWDFDevice3 * pWdfDevice, _In_ IWDFCmResourceList * pWdfResourcesTranslated)
{
    UNREFERENCED_PARAMETER(pWdfDevice);
    UNREFERENCED_PARAMETER(pWdfResourcesTranslated);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    HRESULT hr = S_OK;

    EnterProcessing(PROCESSING_IPNPCALLBACKHARDWARE);

    if (m_pSensorManager != nullptr)
    {
        hr = m_pSensorManager->Stop();
        m_pSensorManager->Unitialize();
        DriverSafeRelease<CComObject<SensorsManager>>(m_pSensorManager);
    }

    DriverSafeRelease<IWDFDevice>(m_FxDevice);

    ExitProcessing(PROCESSING_IPNPCALLBACKHARDWARE);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit hr = %!HRESULT!", hr);
    return hr;
}

Ici nous ne faisons qu’instancier et initialiser la classe SensorManager, que nous détaillerons un peu plus loin.

IPnpCallBack quand à elle, expose entre autre la méthode OnD0Entry qui nous sert essentiellement ici à démarrer le SensorManager.

HRESULT CMyDevice::OnD0Entry(
    __in IWDFDevice* pWdfDevice,
    __in WDF_POWER_DEVICE_STATE previousState
    )
{
    UNREFERENCED_PARAMETER(previousState);
    UNREFERENCED_PARAMETER(pWdfDevice);

    HRESULT hr = S_OK;
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
    hr = EnterProcessing(PROCESSING_IPNPCALLBACK);
    
    if (SUCCEEDED(hr))
    {
        if (m_pSensorManager != nullptr)
        {
            hr = m_pSensorManager->Start();
            if (FAILED(hr))
            {
                TraceEvents(TRACE_LEVEL_CRITICAL, TRACE_DEVICE, "Failed to initialize sensors hr=%!HRESULT!", hr);
            }
        }
    }
    
        
    ExitProcessing(PROCESSING_IPNPCALLBACK);
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit hr=%!HRESULT!", hr);
    return hr;
}

Le Framework WDF appelle la méthode OnD0Entry afin de notifier qu’il entre dans un état dit Full Power et qu’il peut fournir toute les fonctionnalités à l’utilisateur.

Dans notre exemple, la méthode Start() de notre classe SensorManager a pour but :

  1. d’instancier la classe Sensor (que nous verrons dans un prochain billet), qui aura pour rôle :
    • de faire le lien avec le capteur physique,
    • de récupérer les données du capteur,
    • de les convertir dans le bon format pour qu’elles soient compréhensibles par les APIs Sensor de Windows.
  2. d’instancier la classe SensorDDI (DDI =Device Driver Interface), qui a pour rôle :
    • d’informer le Framework WDF des capacités, des propriétés et du type de driver (OrientationSensor),
    • d’identifier lorsqu’un client se connecte/déconnecte, ou s’abonne/Désabonne à un évènement,
    • de retourner des données au client.
  3. d’instancier la classe ISensorClassExtension fournit par Windows pour simplifier le développement de driver. Cette classe permet :
    • de répondre au demande entrante (ProcessIoControl),
    • de poster un évènement (PostEvent) lorsque des données sont disponibles,
    • et de poster l’état du périphérique à un instant T (PosteStateChange)
  4. de démarrer un Thread qui sera à l’écoute, lorsque de nouvelles données seront disponible, que nous détaillerons également dans notre prochain billet.

 

HRESULT SensorsManager::Start()
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
    HRESULT hr = S_OK;
    

    if (FALSE == IsInitialized())
    {

        if (SUCCEEDED(hr))
        {
            m_pSensor = std::unique_ptr<Sensor>(new Sensor());
            hr = m_pSensor->InitializeSensor(m_spWdfDevice,m_pWdfResourcesRaw,m_pWdfResourcesTranslated);

            if (SUCCEEDED(hr))
            {
                m_pSensorList.AddTail(m_pSensor.get());
            }
        }

        if (SUCCEEDED(hr))
        {
            hr = this->InitializeDDIAndClassExtension();
        }
        
        if (SUCCEEDED(hr))
        {
            m_fSensorManagerInitialized = TRUE;
            m_fInitializationComplete = TRUE;
        }        
    }
    
    if (SUCCEEDED(hr))
    {
        // Step 1: Create the Data Changed Event Handle
        m_hSensorEvent = ::CreateEvent(NULL,        // No security attributes
            FALSE,       // Automatic-reset event object
            FALSE,       // Initial state is non-signaled
            NULL);     // Unnamed object
        if (m_pSensor != nullptr)
        {
            m_pSensor->SetDataEventHandle(m_hSensorEvent);
            m_pSensor->m_fInitialDataReceived = FALSE;
        }
        else
        {
            hr = E_UNEXPECTED;
        }
    }
    // Step 2: Activate & Create and start the eventing thread
    if (SUCCEEDED(hr))
    {
        Activate();
        m_hSensorManagerEventingThread = ::CreateThread(NULL,                                              // Cannot be inherited by child process
                                                        0,                                                   // Default stack size
                                                        &SensorsManager::_SensorEventThreadProc,   // Thread proc
                                                        (LPVOID)this,                                        // Thread proc argument
                                                        0,                                                   // Starting state = running
                                                        NULL);

        if (nullptr == m_hSensorManagerEventingThread)
        {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "%!FUNC! sensor event thread failed to create");
            hr = HRESULT_FROM_WIN32(::GetLastError());
        }
        else
        {
            TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! sensor event thread successfully created");
        }
        if (SUCCEEDED(hr))
        {
            m_fSensorManagerInitialized = TRUE;
            TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! sensor initialization completed ");
        }
        else
        {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "%!FUNC! sensor event initialization failed");
        }
    }
    if (SUCCEEDED(hr))
    {
        m_fDeviceStopped = FALSE;
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit hr=%!HRESULT!", hr);
    return hr;

}

HRESULT SensorsManager::InitializeDDIAndClassExtension()
{

    HRESULT hr = (NULL == m_spClassExtension) ? S_OK : E_UNEXPECTED;

    /*hr = EnterProcessing(PROCESSING_IPNPCALLBACKHARDWARE);*/
    if (FAILED(hr)) return hr;

    hr = CComObject<SensorDDI>::CreateInstance(&m_pSensorDDI);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "Failed to create the device");
        return hr;
    }
    m_pSensorDDI->AddRef();

    m_pSensorDDI->m_pSensorManager = this;
    //TODO: Juste for debug an comprehension;
    m_pSensorDDI->SetDevice(m_pDevice);

    hr = m_pSensorDDI->Initialize(m_spWdfDevice, nullptr, nullptr);
    if (FAILED(hr)) return hr;

    CComPtr<IUnknown> spUnknown;

    hr = m_pSensorDDI->QueryInterface(IID_IUnknown, (void**) &spUnknown);

    if (FAILED(hr)) return hr;

    hr = CoCreateInstance(CLSID_SensorClassExtension, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_spClassExtension));
    if (REGDB_E_CLASSNOTREG == hr)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "Class is not registered, hr = %!HRESULT!", hr);
        hr = E_UNEXPECTED;
        m_spClassExtension = nullptr;
    }

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "Could not create the sensor class extension");
        return hr;
    }

    if (m_spClassExtension != nullptr)
    {
        if (m_spWdfDevice != nullptr)
        {
            TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "Initializing the SensorClassExtension");
            hr = m_spClassExtension->Initialize(m_spWdfDevice, spUnknown);

        }

    }

    if (FAILED(hr)) return hr;

    hr = m_pSensorDDI->SetSensorClassExtension((m_spClassExtension));

    //ExitProcessing(PROCESSING_IPNPCALLBACKHARDWARE);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_CRITICAL, TRACE_DEVICE, "Abnormal results during hardware initialization, hr = %!HRESULT!", hr);
    }
    return hr;
}

La classe SensorDDI comme je l’ai dit plus haut, doit informer des capacités du driver, pour ce faire elle doit implémenter l’interface ISensorDriver et ses méthodes associées afin que le Framework WDF puisse les rappeler.

class SensorDDI : public CComObjectRoot, public ISensorDriver
{
public:
    SensorDDI();
    virtual ~SensorDDI();

    DECLARE_NOT_AGGREGATABLE(SensorDDI)

    BEGIN_COM_MAP(SensorDDI)
        COM_INTERFACE_ENTRY(ISensorDriver)
                
    END_COM_MAP()
    
    ULONG m_clientCount; //for this first version manage only one client
    ULONG m_countSubscriber;
    BOOL m_clientSubscribe;
    //Public methods
public :
    
    void SetDevice(CMyDevice *device);

    HRESULT Initialize(
        _In_ IWDFDevice* pWdfDevice,
        _In_ IWDFCmResourceList * pWdfResourcesRaw,
        _In_ IWDFCmResourceList * pWdfResourcesTranslated);

    HRESULT InitializeSensorDriverInterface(_In_ IWDFDevice* pWdfDevice);
    HRESULT InitializeClientManager();
    

    VOID Uninitialize();
    HRESULT SetSensorClassExtension(
        _In_ ISensorClassExtension* pClassExtension);

            
    // COM Interface methods
public:
    // ISensorDriver methods
    HRESULT STDMETHODCALLTYPE OnGetSupportedSensorObjects(
        _Out_ IPortableDeviceValuesCollection** ppSensorObjectCollection
        );

    HRESULT STDMETHODCALLTYPE OnGetSupportedProperties(
        _In_  LPWSTR pwszObjectID,
        _Out_ IPortableDeviceKeyCollection** ppSupportedProperties
        );

    HRESULT STDMETHODCALLTYPE OnGetSupportedDataFields(
        _In_  LPWSTR pwszObjectID,
        _Out_ IPortableDeviceKeyCollection** ppSupportedDataFields
        );

    HRESULT STDMETHODCALLTYPE OnGetSupportedEvents(
        _In_  LPWSTR pwszObjectID,
        _Out_ GUID** ppSupportedEvents,
        _Out_ ULONG* pulEventCount
        );

    HRESULT STDMETHODCALLTYPE OnGetProperties(
        _In_  IWDFFile* pClientFile,
        _In_  LPWSTR pwszObjectID,
        _In_  IPortableDeviceKeyCollection* pProperties,
        _Out_ IPortableDeviceValues** ppPropertyValues
        );

    HRESULT STDMETHODCALLTYPE OnGetDataFields(
        _In_  IWDFFile* pClientFile,
        _In_  LPWSTR pwszObjectID,
        _In_  IPortableDeviceKeyCollection* pDataFields,
        _Out_ IPortableDeviceValues** ppDataValues
        );

    HRESULT STDMETHODCALLTYPE OnSetProperties(
        _In_  IWDFFile* pClientFile,
        _In_  LPWSTR pwszObjectID,
        _In_  IPortableDeviceValues* pPropertiesToSet,
        _Out_ IPortableDeviceValues** ppResults
        );

    HRESULT STDMETHODCALLTYPE OnClientConnect(
        _In_ IWDFFile* pClientFile,
        _In_ LPWSTR pwszObjectID
        );

    HRESULT STDMETHODCALLTYPE OnClientDisconnect(
        _In_ IWDFFile* pClientFile,
        _In_ LPWSTR pwszObjectID
        );

    HRESULT STDMETHODCALLTYPE OnClientSubscribeToEvents(
        _In_ IWDFFile* pClientFile,
        _In_ LPWSTR pwszObjectID
        );

    HRESULT STDMETHODCALLTYPE OnClientUnsubscribeFromEvents(
        _In_ IWDFFile* pClientFile,
        _In_ LPWSTR pwszObjectID
        );

    HRESULT STDMETHODCALLTYPE OnProcessWpdMessage(
        _In_ IUnknown* pUnkPortableDeviceValuesParams,
        _In_ IUnknown* pUnkPortableDeviceValuesResults
        );

    // ISensorDeviceCallback methods
    HRESULT STDMETHODCALLTYPE OnNewData(
        _In_ IPortableDeviceValues* pValues
        );
    public :
        
        SensorsManager*                    m_pSensorManager;

    private:
        CComPtr<ISensorClassExtension>         m_spClassExtension;        
        CComObject<ClientManager>*            m_pClientManager;
        
        LPWSTR GetSensorObjectID();
        
        // Make calls to client thread safe
        CComAutoCriticalSection                m_ClientCriticalSection;
        CComAutoCriticalSection                m_CacheCriticalSection;

        CMyDevice* m_device; //TODO: Juste for debug now

        CComPtr<IWDFDevice2>                   m_spWdfDevice2;

        Sensor *m_pSensor;

        BOOL                                   m_fSensorInitialized;

        mutable CComAutoCriticalSection m_CriticalSection;
        BOOL                            m_fSensorManagerInitialized;
        HANDLE                          m_hSensorEvent;
        HANDLE                          m_hSensorManagerEventingThread;       // Thread handle for raising data events
        BOOL                            m_fThreadActive;                    // Flag denoting that driver thread is active
        BOOL                            m_fDeviceStopped;                   // Flag denoting that CSensorManager->Stop() was called
};

Tout d’abord un driver de type OrientationSensor d’après le document Integrating Motion and Orientation Sensors (Page 46), doit supporter un certain nombre de propriétés comme illustré sur la figure suivante :

const PROPERTYKEY g_SupportedRiftProperties[] =
{
    WPD_OBJECT_ID,
    SENSOR_PROPERTY_TYPE,
    SENSOR_PROPERTY_PERSISTENT_UNIQUE_ID,
    SENSOR_PROPERTY_MANUFACTURER,
    SENSOR_PROPERTY_MODEL,    
    SENSOR_PROPERTY_SERIAL_NUMBER,
    SENSOR_PROPERTY_FRIENDLY_NAME,
    SENSOR_PROPERTY_DESCRIPTION,
    SENSOR_PROPERTY_CONNECTION_TYPE,
    SENSOR_PROPERTY_CHANGE_SENSITIVITY,//TODO:
    SENSOR_PROPERTY_ACCURACY, //TODO:
    SENSOR_PROPERTY_STATE,
    SENSOR_PROPERTY_MIN_REPORT_INTERVAL,
    SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL,
    WPD_FUNCTIONAL_OBJECT_CATEGORY,
};

Sans détailler la totalité des propriétés que le driver doit supporter, il y en a qui parle d’elle même, les plus importantes seront :

WPD_OBJECT_ID, qui sera initialisée avec un identifiant unique (exemple “OculusRift42”)

WPD_FUNCTIONAL_OBJECT_CATEGORY, définie à la valeur SENSOR_CATEGORY_ORIENTATION , comme son nom l’indique permet de définir la catégorie du driver comme étant un driver Orientation.

SENSOR_PROPERTY_TYPE, initialisé à la valeur SENSOR_TYPE_AGGREGATED_DEVICE_ORIENTATION permet d’indiquer que c’est un driver Orientation, qu’il va agréger des données provenant de plusieurs capteurs (intégrés en faite à l’Oculus Rift) et les retourner sous forme de Quaternion ou d’une matrice 3X3.

SENSOR_PROPERTY_CONNECTION_TYPE définie SENSOR_CONNECTION_TYPE_PC_ATTACHED indique que le périphérique est un périphérique externe au PC à l’inverse de SENSOR_CONNECTION_TYPE_PC_INTEGRATED qui définie que le périphérique est intégré au PC.

SENSOR_PROPERTY_STATE qui permet de définir l’état du périphérique tels que SENSOR_STATE_READY, SENSOR_STATE_NOT_AVAILABLE, SENSOR_STATE_NO_DATA, SENSOR_STATE_INITIALIZING , SENSOR_STATE_ACCESS_DENIED , SENSOR_STATE_ERROR

SENSOR_MIN_REPORT_INTERVAL et SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL déterminele lapse de temps entre chaque levée d’évènement.

SENSOR_PROPERTY_CHANGE_SENSITIVITY cette propriété détermine l’angle entre deux Quaternions pour lequel les données seront retournées au client. (Sujet que nous aborderons dans notre prochain Billet)

Note : Ces deux dernières propriétés peuvent être modifiées par le client. Toutes ces propriétés sont initialisées par défaut lors de l’initialisation de la classe Sensor.

Pour obtenir les propriétés supportées par le driver ainsi que les valeurs associées, le Framework WDF, appellera successivement les méthodes OnGetSupportedSensorObjects, OnGetSupportedProperties, OnGetProperties.

HRESULT SensorDDI::OnGetSupportedSensorObjects(_Out_ IPortableDeviceValuesCollection** ppSensorObjectCollection)
{

    
    UNREFERENCED_PARAMETER(ppSensorObjectCollection);

    TraceEvents(TRACE_LEVEL_INFORMATION,TRACE_DDI, "%!FUNC! Entry");

    HRESULT hr = S_OK;
    m_device->EnterProcessing(PROCESSING_ISENSORDRIVER);
    if (NULL == ppSensorObjectCollection)
    {
        return E_POINTER;
    }
    CComPtr<IPortableDeviceValuesCollection> spObjects;
    CComPtr<IPortableDeviceKeyCollection> spKeys;
    CComPtr<IPortableDeviceValues> spValues;
    hr = spObjects.CoCreateInstance(CLSID_PortableDeviceValuesCollection);
    if (FAILED(hr))
        return hr;

    hr = OnGetSupportedProperties(GetSensorObjectID(), &spKeys); //the first parameters is the object ID, I don't need it at this time
    if (FAILED(hr))
        return hr;

    CComPtr<IWDFFile> spTemp; //Not use at this time

    hr = OnGetProperties(spTemp, GetSensorObjectID(), spKeys, &spValues);
    if (FAILED(hr))
        return hr;

    hr = spObjects->Add(spValues);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DDI, "Failed to get the supported sensor objects hr = %!HRESULT!", hr);
        return hr;
    }

    *ppSensorObjectCollection = spObjects.Detach();    
    m_device->ExitProcessing(PROCESSING_ISENSORDRIVER);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr= %!HRESULT!", hr);

    return hr;

}

HRESULT SensorDDI::OnGetSupportedProperties( _In_  LPWSTR pwszObjectID,    
                                            _Out_ IPortableDeviceKeyCollection** ppSupportedProperties)
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Entry");
    
    UNREFERENCED_PARAMETER(pwszObjectID); //Only One Sensor Device is supported, so no need to use de ObjectID at this time but it seems the rift has more than one sensors
    HRESULT hr = S_OK;
    
    hr = CoCreateInstance(CLSID_PortableDeviceKeyCollection,
        nullptr, CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(ppSupportedProperties));
    
    if (SUCCEEDED(hr))
    {        
        
        if (m_pSensor == nullptr)
        {
    
            hr = E_HANDLE;
        }
        if (SUCCEEDED(hr))
        {
            m_pSensor->GetSupportedProperties(ppSupportedProperties);
        }        
    }
            
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DDI, "Failed to get supported sensor properties %!HRESULT!", hr);
        return hr;
    }
            
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
    return hr;
}
HRESULT SensorDDI::OnGetSupportedDataFields(_In_  LPWSTR pwszObjectID,_Out_ IPortableDeviceKeyCollection** ppSupportedDataFields)
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Entry");
    UNREFERENCED_PARAMETER(pwszObjectID);
    
    HRESULT hr = S_OK;

    // CoCreate a collection to store the supported property keys.
    hr = CoCreateInstance(
        CLSID_PortableDeviceKeyCollection,
        nullptr,
        CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(ppSupportedDataFields));

    // Add supported property keys for the specified object to the collection
    if (SUCCEEDED(hr) && ppSupportedDataFields != nullptr)
    {
        //hr = CopyKeys(m_spSupportedSensorDataFields, *ppSupportedDataFields);
        hr = m_pSensor->GetSupportedDataFields(ppSupportedDataFields);

    }
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "Failed to get the supported sensor data fields hr = %!HRESULT!", hr);
    }
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
    return hr;
}

HRESULT SensorDDI::OnGetProperties(    _In_  IWDFFile* pClientFile,
                                    _In_  LPWSTR pwszObjectID,_In_  
                                     IPortableDeviceKeyCollection* pProperties,
                                     _Out_ IPortableDeviceValues** ppPropertyValues)
{
    UNREFERENCED_PARAMETER(pwszObjectID);
    UNREFERENCED_PARAMETER(pClientFile);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Entry");
    

    HRESULT hr = S_OK;
    DWORD keyCount = 0;
    
    CComPtr<IPortableDeviceValues>  pValues;
    
    if ((pProperties == nullptr) || ppPropertyValues == nullptr) return E_INVALIDARG;

    hr = pProperties->GetCount(&keyCount);
    if (FAILED(hr)) return hr;

    hr = pValues.CoCreateInstance(CLSID_PortableDeviceValues);
    if (FAILED(hr)) return hr;

    BOOL fError = FALSE;

    for (DWORD i = 0; i < keyCount; i++)
    {
        PROPERTYKEY key;
        hr = pProperties->GetAt(i, &key);
                
        if (SUCCEEDED(hr))
        {
            PROPVARIANT var;
            PropVariantInit(&var);
            HRESULT hrTemp = m_pSensor->GetProperty(key, &var);
            if (SUCCEEDED(hrTemp))
            {
                pValues->SetValue(key, &var);
            }
            else
            {
                TraceEvents(TRACE_LEVEL_ERROR, TRACE_DDI, "Failed to get the sensor property value hr = %!HRESULT!", hrTemp);
                
                pValues->SetErrorValue(key, hrTemp);
                fError = TRUE;
            }
            if ((var.vt & VT_VECTOR) == 0)
            {
                // For a VT_VECTOR type, PropVariantClear()
                // frees all underlying elements. Note pValues
                // now has a pointer to the vector structure
                // and is responsible for freeing it.

                // If var is not a VT_VECTOR, clear it.
                PropVariantClear(&var);
            }
        }
        else
        {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DDI, "Failed to get property key hr = %!HRESULT!", hr);
        }
                        
    }
    if (fError)
    {
        hr = S_FALSE;
    }

    *ppPropertyValues = pValues.Detach();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
    return hr;

}

Le driver doit également préciser le type de données qu’il retourne, en l’occurrence entre autre un Quaternion et une matrice 3X3.

const PROPERTYKEY g_SupportedRiftDataFields[] =
{
    SENSOR_DATA_TYPE_QUATERNION,
    SENSOR_DATA_TYPE_ROTATION_MATRIX,
    SENSOR_DATA_TYPE_TIMESTAMP,            
};

Le Framework invoquera alors la méthode OnGetSupportedDataFields pour obtenir cette information.

HRESULT SensorDDI::OnGetSupportedDataFields(_In_  LPWSTR pwszObjectID,_Out_ IPortableDeviceKeyCollection** ppSupportedDataFields)
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Entry");
    UNREFERENCED_PARAMETER(pwszObjectID);
    
    HRESULT hr = S_OK;

    // CoCreate a collection to store the supported property keys.
    hr = CoCreateInstance(
        CLSID_PortableDeviceKeyCollection,
        nullptr,
        CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(ppSupportedDataFields));

    // Add supported property keys for the specified object to the collection
    if (SUCCEEDED(hr) && ppSupportedDataFields != nullptr)
    {
        //hr = CopyKeys(m_spSupportedSensorDataFields, *ppSupportedDataFields);
        hr = m_pSensor->GetSupportedDataFields(ppSupportedDataFields);

    }
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "Failed to get the supported sensor data fields hr = %!HRESULT!", hr);
    }
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
    return hr;
}

  

Le driver doit également être capable de gérer des évènements, lorsque des données sont disponibles et lorsque le driver change d’état.

const PROPERTYKEY g_SupportedRiftEvents[] =
{
    SENSOR_EVENT_DATA_UPDATED, 0,
    SENSOR_EVENT_STATE_CHANGED, 0,
    
};

Le Framework WDF invoquera la méthode OnGetSupportedEvents pour obtenir la liste des évènement supportés par le driver.

HRESULT SensorDDI::OnGetSupportedEvents(_In_  LPWSTR pwszObjectID, _Out_ GUID** ppSupportedEvents, _Out_ ULONG* pulEventCount)
{
    UNREFERENCED_PARAMETER(pwszObjectID);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Entry");

    HRESULT hr = S_OK;
    ULONG count = ARRAY_SIZE(g_SupportedRiftEvents);

    GUID* pBuf = (GUID*) CoTaskMemAlloc(sizeof(GUID) *count);
    if (pBuf != nullptr)
    {
        for (DWORD i = 0; i < count; count++)
        {
            *(pBuf + i) = g_SupportedRiftEvents[i].fmtid;
        }

        *ppSupportedEvents = pBuf;
        *pulEventCount = count;
    }
    else
    {
        hr = E_OUTOFMEMORY;

        *ppSupportedEvents = nullptr;
        *pulEventCount = 0;
    }

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "Failed to get the supported sensor events hr = %!HRESULT!", hr);
    }
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
    return hr;
}

Continuons, rappelez vous, que notre driver est à utiliser avec des APIs de plus haut niveau et avec le Windows Runtime 8.1, ces APIs se trouvent dans l’espace de nom Windows.Devices.Sensors et pour notre driver plus précisément Windows.Devices.Sensors.OrientationSensor

Lorsque que nous recherchons un sensor, comme illustré dans le code suivant en C# :

private void ActivateOrientationSensor()
        {
            OrientationSensor sensor = OrientationSensor.GetDefault();
            
        }

La méthode GetDefault() est appelée, le Framework WDF invoque la méthode OnClientConnect qui notifie le driver qu’un client se connecte et OnClientDisconnect c’est l’inverse.

HRESULT SensorDDI::OnClientConnect(_In_ IWDFFile* pClientFile,_In_ LPWSTR pwszObjectID)
{    
    
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Enter");

    UNREFERENCED_PARAMETER(pwszObjectID);
    if (pClientFile == nullptr) return E_INVALIDARG;

    HRESULT hr = S_OK;
    // Synchronize access to the client manager so that after
    // the client connects it can query the new client
    // count atomically
    CComCritSecLock<CComAutoCriticalSection>
        scopeLock(m_ClientCriticalSection);
    
    m_clientCount = +1;    //TODO: Here queue the number of client connecter by default one.
    
    if (m_clientCount == 1)
    {

        TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "First client, so stop idle detection");
        // When using a power managed queue we are guaranteed
        // to be in D0 during OnClientConnect, so there is no need
        // to block on this call. It's safe to touch hardware at
        // this point. There is potential, however, to temporarily
        // transition from D0->Dx->D0 after this call returns, so be
        // sure to reconfigure the hardware in D0Enty.
        hr = m_spWdfDevice2->StopIdle(false);
    }
    
    
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
    return hr;
}

L’utilisateur peut demander directement des données au Sensor, comme illustré dans le code C# suivant :

private void ActivateOrientationSensor()
        {
            OrientationSensor sensor = OrientationSensor.GetDefault();
            OrientationSensorReading data = sensor.GetCurrentReading();            
            
        }

Cette fois-ci, le Framework WDF invoque la méthode, OnGetDataFields et les données seront retournées par l’intermédiaire d’un pointeur sur la collection IPortableDeviceValues.

HRESULT SensorDDI::OnGetDataFields(    _In_  IWDFFile* pClientFile,
                                    _In_  LPWSTR pwszObjectID,
                                    _In_  IPortableDeviceKeyCollection* pDataFields,    
                                    _Out_ IPortableDeviceValues** ppDataValues)
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Enter");
    UNREFERENCED_PARAMETER(pwszObjectID);
    UNREFERENCED_PARAMETER(pClientFile);
    UNREFERENCED_PARAMETER(pDataFields);

    HRESULT hr = S_OK;

    

    CComPtr<IPortableDeviceValues>  pValues;
    hr = pValues.CoCreateInstance(CLSID_PortableDeviceValues);
    
    BOOL IsNewData;
    hr=m_pSensor->GetOrientationFromRift(pValues,&IsNewData);
    
    
    m_pSensor->SetTimeStamp(pValues);
    
    

    *ppDataValues = pValues.Detach();
    

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
    return hr;
}

  

Plutôt que demander les données, l’utilisateur peut s’abonner à l’évènement ReadingChanged, comme illustré dans le code C# suivant :

private void ActivateOrientationSensor()
        {
            OrientationSensor sensor = OrientationSensor.GetDefault();        
            sensor.ReadingChanged += sensor_ReadingChanged;            
        }

        void sensor_ReadingChanged(OrientationSensor sender, OrientationSensorReadingChangedEventArgs args)
        {
            var data = args.Reading;
        }

Le Framework WDF, invoquera alors la méthode OnClientSubscribeToEvents/OnClientUnsubscribeFromEvents.

HRESULT SensorDDI::OnClientSubscribeToEvents(_In_ IWDFFile* pClientFile,
                                            _In_ LPWSTR pwszObjectID)
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Enter");
    UNREFERENCED_PARAMETER(pClientFile);
    UNREFERENCED_PARAMETER(pwszObjectID);

    CComCritSecLock<CComAutoCriticalSection> scopeLock(m_ClientCriticalSection);
    HRESULT hr = S_OK;
    if (pClientFile == nullptr) return E_INVALIDARG;

    m_countSubscriber += 1;
    if (m_clientSubscribe == FALSE && m_countSubscriber==1) //Only One suscriber to the event
    {
        m_clientSubscribe = TRUE;
        m_pSensor->SetOnlyOneSuscriber(1);
    }
    
    
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
    return hr;
}

HRESULT SensorDDI::OnClientUnsubscribeFromEvents(_In_ IWDFFile* pClientFile,_In_ LPWSTR pwszObjectID)
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Enter");
    UNREFERENCED_PARAMETER(pClientFile);
    UNREFERENCED_PARAMETER(pwszObjectID);
    CComCritSecLock<CComAutoCriticalSection> scopeLock(m_ClientCriticalSection);
    HRESULT hr = S_OK;
    m_countSubscriber -= 1;
    if (m_countSubscriber == 0) //no more client are connected to the sensor
    {
        m_clientSubscribe = FALSE;
        m_pSensor->SetOnlyOneSuscriber(0);
    }
    
    //TODO: When a client unsuscribe clean the last quaternion in the oculus rift
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
    return hr;
}

Dés que de nouvelles données seront disponibles, un évènement PostEvent sera levé afin de retourner les données au client.

Le client peut également modifier certaines propriétés comme SENSOR_PROPERTY_CHANGE_SENSITIVITY ou SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL de la manière suivante :

private void ActivateOrientationSensor()
        {
            OrientationSensor sensor = OrientationSensor.GetDefault();        
            sensor.ReadingChanged += sensor_ReadingChanged;
            sensor.ReportInterval = 10; //Valeur Exprime en millisecondes
        }

Dans ce cas la , le Framework WDF invoque la méthode OnSetProperties afin que le driver utilise un nouveau lapse de temps entre chaque levé d’évènement.

HRESULT SensorDDI::OnSetProperties(_In_  IWDFFile* pClientFile,
                                  _In_  LPWSTR pwszObjectID,
                                  _In_  IPortableDeviceValues* pPropertiesToSet,
                                  _Out_ IPortableDeviceValues** ppResults)
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Enter");
    UNREFERENCED_PARAMETER(pwszObjectID);
    
    HRESULT hr = S_OK;
    if ((pClientFile == nullptr) || (pPropertiesToSet == nullptr) || (ppResults == nullptr))
    {
        return E_INVALIDARG;
    }

    if (SUCCEEDED(hr))
    {
        DWORD count = 0;

        hr = CoCreateInstance(
            CLSID_PortableDeviceValues,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(ppResults));
        if (SUCCEEDED(hr))
        {
            hr=pPropertiesToSet->GetCount(&count);
        }
        if (SUCCEEDED(hr))
        {
            for (DWORD i = 0; i < count; i++)
            {
                PROPERTYKEY key = WPD_PROPERTY_NULL;
                PROPVARIANT var;

                PropVariantInit(&var);

                hr = pPropertiesToSet->GetAt(i, &key, &var);
                if (SUCCEEDED(hr))
                {
                    m_pSensor->SetProperty(key, &var, nullptr);
                }
            }
        }
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DDI, "%!FUNC! Exit hr = %!HRESULT!", hr);
    return hr;
}

Voici en quelques lignes, comment notre driver s’interface avec les APIs Sensor de Windows.

Vue d’ensemble de l’architecture du Driver :

archi

Dans le prochain billet, nous aborderons :

  • La manière dont les évènements sont retournés à l’appelant,
  • Comment formater les données pour qu’elles soit compréhensibles par les APis Sensor.
  • Enfin nous brancheront notre driver à l’Oculus Rift.

Eric