Export (0) Print
Expand All

Writing an EDDC Driver

Windows Automotive 4.2

An OEM can build an EDDC driver that interfaces to platform-specific hardware and enables an automotive platform to detect EDDC state changes and to provide notifications to applications.

The EDDC driver must include both a model device driver (MDD) layer and a platform dependent driver (PDD) layer. For additional information on this, see Driver Code Structure in the Windows CE .NET documentation.

NOTE   Code samples in this topic are targeted to a specific device and will vary depending on the type of hardware that your platform is based on.

MDD Layer

The MDD layer interfaces to the PDD layer and handles a set of devices. It must provide the following functionality to the EDDC driver:

  • Implement a DLL attach/detach entry point
  • Perform initialization for the EDDC device
  • Open/close an EDDC device
  • De-initialize a device
  • Handle an IOCTL code indicating a change in EDDC state
  • Handle Read operations
  • Handle Write operations
  • Apply/Remove power for the faceplate device

The following paragraphs describe the functions required for the MDD.

Implement a DLL attach/detach entry point

The MDD requires a standard DLL attach/detach entry point function that is called as a method of entry into the EDDC driver. When an entry point is established, the driver and device can communicate with each other.

This function should first check the reason that it has been called. If the reason code is DLL_PROCESS_ATTACH, then the DLL is being loaded into virtual memory, and the function can perform initialization tasks. If the reason code is DLL_PROCESS_DEATTACH, then the DLL is being unloaded from virtual memory, and the function can perform cleanup tasks.

The following code sample checks the reason that the DLL attach/detach entry point is being called. If the reason code is DLL_PROCESS_ATTACH, then it will register the EDDC driver with the debug system.

BOOL WINAPI EDDCEntry(   HINSTANCE    DllInstance,
                        INT          Reason,
                        LPVOID       Reserved)
{
    switch(Reason)
    {
        case DLL_PROCESS_ATTACH:
            DEBUGMSG(ZONE_INIT, (TEXT("EDDC: DLL_PROCESS_ATTACH\r\n")));
            DEBUGREGISTER(DllInstance);
            return( TRUE);

        case DLL_PROCESS_DETACH:
            DEBUGMSG(ZONE_INIT, (TEXT("EDDC: DLL_PROCESS_DETACH\r\n")));
            break;
    }
    return(TRUE);
}   // EEDEntry

Perform Initialization for the EDDC device

The MDD requires a function that performs initialization tasks for the EDDC device. Initialization tasks are performed to prepare the device for use by setting up default configuration values.

The following sample code demonstrates one possible way to implement this function. It calls a related function in the PDD layer and, if the call is successful, it increments the device count.

DWORD   DDC_Init( DWORD dwDevIndex)
{
    DEBUGMSG(ZONE_INIT, (TEXT("EDDC:DDC_Init\r\n")));

   if( !ddcDevInit())
   {
      DEBUGMSG (ZONE_ERROR, (TEXT("EDDC:DDC_Init ddcDevInit Failed\n")));
      return(0L);
   }
   // Return Non-Zero for success, will be passed to Deinit and Open
   DEBUGMSG (ZONE_INIT, (TEXT("DDC_Init Succeeded\n")));
   return( dwDevIndex + 1);
}

Open/Close an EDDC device

An MDD requires functions that open and close a handle reference to a peripheral device that is important to EDDC, such as a faceplate.

A XXX_Open function opens a device for reading and/or writing. An XXX_Close device closes a device context, which disables communication between the device and the EDDC driver.

The following sample code demonstrates one possible way to implement these functions:

DWORD DDC_Open( DWORD dwData,
                DWORD dwAccess,
                DWORD dwShareMode)
{
   DEBUGMSG(ZONE_FUNCTION,(TEXT("EDD:DDC_Open\r\n")));
   // The dwData passed in should be the device index that is to be opened
   if( dwData == 0)
   {
       DEBUGMSG(ZONE_ERROR,(TEXT("EDD:DDC_Open bad dev idx\r\n")));
        SetLastError( ERROR_INVALID_PARAMETER);
        return( 0);
   }
   return(1);
}
BOOL    DDC_Close( DWORD Handle)
{
   DEBUGMSG(ZONE_FUNCTION,(TEXT("EDD: DDC_Close\r\n")));

   if( Handle == 0)
   {
       DEBUGMSG(ZONE_ERROR,(TEXT("EDD:DDC_Close bad handle\r\n")));
        SetLastError( ERROR_INVALID_HANDLE);
        return( FALSE);
   }
    return(TRUE);
}   

De-initialize a device

The MDD requires a function that de-initializes an EDDC device. When a device is de-initialized, the hardware is configured so that the values set during initialization are set back to their default values. This task can involve closing handles, closing I/O connections, and freeing shared resources. When a device is de-initialized, it cannot communicate with the driver until it is initialized again.

The following sample code demonstrates one possible way to implement this function. It simply calls a related function in the PDD layer.

BOOL    DDC_Deinit( DWORD dwContext)
{
    DEBUGMSG(ZONE_INIT,(TEXT("EDDC: DDC_Deinit\r\n")));
    ddcDevDeInit();
    return(TRUE);
}   

Handle an IOCTL code indicating a change in EDDC state

The MDD requires a function to process the IOCTL code that tells the EDDC driver about a change in the vehicle's EDDC state. It is the OEM's responsibility to choose the IOCTL code that will determine EDDC state. For additional information on this, see EDDC Hardware Interface.

The following sample code checks the value of an IOCTL code. If it is DDC_IOCTL_GET_STATE, then the IOCTL code is validated, and information about the current EDDC state is retrieved from the IOCTL and placed into a shared buffer.

BOOL    DDC_IOControl(  DWORD Handle,
                        DWORD dwIoControlCode,
                        PBYTE pInBuf,
                        DWORD nInBufSize,
                        PBYTE pOutBuf,
                        DWORD nOutBufSize,
                        PDWORD pBytesReturned)
{
   BYTE      bTest;
   BYTE      bTest1;
   DEBUGMSG(ZONE_FUNCTION,(TEXT("EDD: DDC_IOControl\r\n")));
   if( Handle == 0)
   {
       DEBUGMSG(ZONE_ERROR,(TEXT("EDD:DDC_IOControl bad handle\r\n")));
        SetLastError( ERROR_INVALID_HANDLE);
        return( FALSE);
   }

   // Probe the IO buffers to check for access

    __try
    {
       if( pInBuf != NULL)
          bTest = pInBuf[0] + pInBuf[nInBufSize-1];

       if( pOutBuf != NULL)
       {
         bTest = pOutBuf[0];
         bTest1 = pOutBuf[nOutBufSize - 1];
         pOutBuf[0] = bTest;
         pOutBuf[nOutBufSize - 1] = bTest1;
       }
       if( pBytesReturned != NULL)
          *pBytesReturned = 0;         // Default value
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
       DEBUGMSG(ZONE_ERROR,(TEXT("EDD: DDC_IOControl bad ptr or length\r\n")));
        SetLastError( ERROR_INVALID_PARAMETER);
        return( FALSE);
    }

    switch( dwIoControlCode)
    {
        case DDC_IOCTL_GET_STATE:
            DEBUGCHK(nInBufSize==0);
            DEBUGCHK(nOutBufSize==4);
            DEBUGCHK(pInBuf==NULL);
            DEBUGCHK(pOutBuf!=NULL);
            *((DWORD*)pOutBuf) = g_dwEDDCState;
            if (pBytesReturned)
                *pBytesReturned = sizeof(g_dwEDDCState);
            break;

      default:
         SetLastError( ERROR_INVALID_PARAMETER);
           return( FALSE);
    }
    return( TRUE);
}   

Handle Read operations

The MDD requires a function that handles Read operations. A Read operation is not required in EDDC, so a XXX_Read function for EDDC should return 0, indicating that it is an invalid function for EDDC.

The following sample code demonstrates one possible way to implement this function:

DWORD DDC_Read(   DWORD    Handle,
                  LPVOID    pBuffer,
                  DWORD    dwNumBytes)
{
   DEBUGMSG(ZONE_FUNCTION,(TEXT("EDD:DDC_Read\r\n")));
   if( Handle == 0)
   {
       DEBUGMSG(ZONE_ERROR,(TEXT("EDD:DDC_Read bad handle\r\n")));
        SetLastError( ERROR_INVALID_HANDLE);
        return( 0);
   }
   return(0);
}

Handle Write operations

The MDD requires a function that handles Write operations. A Write operation is not required in EDDC, so a XXX_Write function for EDDC should return 0, indicating that it is an invalid function for EDDC.

The following sample code demonstrates one possible way to implement this function:

DWORD DDC_Write(   DWORD       Handle,
                  LPCVOID       pBuffer,
                  DWORD       dwNumBytes)
{
   DEBUGMSG(ZONE_FUNCTION,(TEXT("EDD: DDC_Write\r\n")));
   if( Handle == 0)
   {
       DEBUGMSG(ZONE_ERROR,(TEXT("EDD:DDC_Write bad handle\r\n")));
        SetLastError( ERROR_INVALID_HANDLE);
        return( 0);
   }
   if( pBuffer == NULL || dwNumBytes == 0)
   {
       DEBUGMSG(ZONE_ERROR,(TEXT("EDD:DDC_Write invalid ptr or len\r\n")));
        SetLastError( ERROR_INVALID_PARAMETER);
        return( 0);
   }
   return(0);
}

Apply/Remove power to a faceplate device

An MDD requires functions that apply and remove power to a faceplate device. This allows EDDC to directly control the power of the faceplate device and enables EDDC to apply power, or remove power, to the faceplate in response to a change in EDDC state. A driver can remove power from a faceplate device without de-initializing the device. A device should only be de-initialized when the EDDC driver does not need to communicate with it any longer.

The following sample code shows how to handle a call to turn on or turn off power on a faceplate device by calling a related function in the PDD layer:

void DDC_PowerUp(void)
{
   ddcDriverPowerOn();
   return;
}
void DDC_PowerDown(void)
{
   ddcDriverPowerOff();
   return;
}

PDD Layer

The PDD layer interfaces to the MDD layer and platform-specific hardware. It must provide the following functionality to the EDDC driver:

  • Perform platform-specific initialization on an EDDC device
  • Perform platform-specific de-initialization on an EDDC device
  • Handle a request to apply power to a faceplate device
  • Handle IRQs that signal EDDC state change
  • Clean up and free resources

For additional information on driver architecture concepts, see Driver Architecture in the Windows CE .NET documentation.

The following paragraphs describe the functions required for the PDD.

Perform platform-specific initialization on an EDDC device

A PDD requires a function that performs platform–specific initialization on an EDDC device. This function is called by the MDD and performs a variety of initialization tasks on the device to get the device ready to communicate with the driver. These tasks include setting the values in the DDCDEVINFO structure to 0, preparing resources for device use, allocating I/O addresses for device use, creating an event for the IST, setting up the IST, enabling various faceplate interrupts, registering the interrupts, and initializing the EDDC interrupt.

The following sample code demonstrates one possible way to implement this function:

BOOL   ddcDevInit( void )
{
   IRB       irb;
   PCIDEVSPACE   reg_space[2];
   PVOID      IOBase[2];

   DEBUGMSG(ZONE_FUNCTION, (TEXT("EDDC:ddcDevInit - Entered\r\n")));

    // Initialize the Global DDCDEVINFO for the driver distraction driver

    memset( (PBYTE)&g_ddcDevInfo, 0, sizeof(DDCDEVINFO));

    // Map the Display Distraction Control input into device.exe address space

   reg_space[0].dwBaseRegister = 0;
   reg_space[0].dwOffset = EDDC_OFFSET;
   reg_space[0].dwSize =   sizeof(DWORD);
   reg_space[0].dwOption = LOCALMAPPED;

    // Map the support module interrupt register & mask reg

   reg_space[1].dwBaseRegister = 0;
   reg_space[1].dwOffset = 0;
   reg_space[1].dwSize = 1*sizeof(DWORD);
   reg_space[1].dwOption = LOCALPHYSICAL;

    // Do the PCI address mapping

   if(!GetPCIDevAddresses( PCI_SUPPORTMODULE_VENDID,
                     PCI_SUPPORTMODULE_DEVID,
                     2,
                     &reg_space[0],
                     &IOBase[0]))
   {
        RETAILMSG (1, (TEXT("EDDC:ddcDevInit GetPCIDevAddress failed\r\n")));
      ddcClearInfo( &g_ddcDevInfo);
      return( FALSE);
   }

    g_ddcDevInfo.ddcdi_pIOBase = IOBase[0];
   g_ddcDevInfo.ddcdi_pCtrlReg = (volatile PDWORD)((PBYTE)IOBase[0]);

   // Create an event for the interrupt handler thread

   if((  g_ddcDevInfo.ddcdi_hIrqEvent = CreateEvent(NULL, FALSE, FALSE,NULL)) == NULL)
   {
      DEBUGMSG (ZONE_ERROR, (TEXT("EDDC:ddcDevInit create IrqEvent Failed\n")));
      ddcClearInfo( & g_ddcDevInfo);
      return( FALSE);
   }

   //  Enable the various faceplate interrupts on interrupt controller

   irb.pIntIdReg = (volatile DWORD *)((DWORD)(IOBase[1])+supportLine);
   irb.pIntMaskReg = (volatile DWORD *)((DWORD)(IOBase[1])+supportMr);
   irb.dwPri = THREAD_PRIORITY_NORMAL;
   irb.pISR = NULL;
   irb.pEnable = NULL;
   irb.pDisable = NULL;
   irb.pDone = NULL;
   irb.dwIntIdMask = mseddcall;
   irb.dwIntMask = mleddcall;
   irb.dwSYSINTR = SYSINTR_EDDC;

   // Register the interrupt, if it fails then the interrupt is already registered
   // so we don't need to bother checking the return value.

   FRegisterInterrupt(&irb);
   CascadeIntEnable(CPU_INTMASK, mpciINTA);   // Enables Support module interrupts

    // Initialize the EDDC interrupt

    if( !InterruptInitialize( SYSINTR_EDDC, g_ddcDevInfo.ddcdi_hIrqEvent, NULL, 0))
    {
        DEBUGMSG (ZONE_ERROR, (TEXT("EDDC:ddcDevInit InterruptInitialize Failed\n")));

      ddcClearInfo( &g_ddcDevInfo);
      return( FALSE);
    }

   if(( g_ddcDevInfo.ddcdi_hIrqThread =
                           CreateThread(( LPSECURITY_ATTRIBUTES)NULL,
                                     0,
                               (LPTHREAD_START_ROUTINE)ddcIrqThread,
                               (PVOID)& g_ddcDevInfo,
                               0,
                               NULL)) == NULL)
   {
      DEBUGMSG (ZONE_ERROR, (TEXT("EDDC:ddcDevInit Create irq thread Failed\n")));
      ddcClearInfo( &g_ddcDevInfo);
      return( FALSE);
   }

   if( !SetThreadPriority(  g_ddcDevInfo.ddcdi_hIrqThread, THREAD_PRIORITY_ABOVE_NORMAL))
    {
        DEBUGMSG(ZONE_WARNING,(TEXT("EDDC:Unable to set irq thread priority\r\n")));
    }
    return( TRUE);
}

Perform platform-specific de-initialization on an EDDC device

A PDD requires a function to perform platform specific de-initialization on an EDDC device. Before clearing system resources, this function must make sure that the IST thread ends before resources are released.

The following sample code demonstrates one possible way to implement this function. It fires the IRQ event and sets a de-initialization value to TRUE and waits until the thread ends properly. After the thread ends, the function calls ddClearInfo to clear system resources.

void    ddcDevDeInit( void )
{
   DEBUGMSG(ZONE_FUNCTION, (TEXT("EDDC:ddcDevDeInit - Entered\r\n")));

    if( g_ddcDevInfo.ddcdi_hIrqThread != NULL &&
            g_ddcDevInfo.ddcdi_hIrqEvent != NULL)
    {
        g_ddcDevInfo.ddcdi_fDeInit = TRUE;
        SetEvent( g_ddcDevInfo.ddcdi_hIrqEvent);
    }
    else
    {
        ddcClearInfo( &g_ddcDevInfo);
    }
    return;
}

Handle a request to apply power to a faceplate device

A PDD requires a function that handles a request to apply power to a faceplate device in response to a change in EDDC state. This is called by the MDD and used when the OEM decides to allow EDDC to control the power state of the faceplate device.

The following sample code demonstrates how to handle this request by generating a fake system interrupt to the IRQ thread:

void   ddcDriverPowerOn( void)
{
   DEBUGMSG(ZONE_FUNCTION, (TEXT("EDDC:ddcDriverPowerOn - Entered\r\n")));
    // Just let the interrupt thread run to look at the current state of        // the EDDC signals.

    SetInterruptEvent(SYSINTR_EDDC);
   return;
}

For additional information, see Interrupts in the Windows CE .NET documentation.

Handling IRQs that signal EDDC state change

A PDD requires a function that handles an interrupt request (IRQ) sent by the EDDC device to communicate a change in EDDC state. This function will process a system interrupt for EDDC, discover EDDC state, set current EDDC state information, and post a message to deliver the EDDC state information to EDDC-aware applications.

The function can use a simple macro to find the location in memory that indicates the current EDDC state of a vehicle. The following sample code demonstrates how to define a macro that specifies which bit of the control register will store the binary value that indicates EDDC state:

#define EDDC_CTRLMASK_INOPERATION   0x00000001

The following sample function demonstrates one way to create a dedicated thread that handles IRQs that signal a change in EDDC state:

DWORD  eddcIrqThread( DWORD  eddcDevInfo)
{
    PEDDCDEVINFO     peddcDevInfo;
    DWORD           dwActiveIrqs;
    DWORD           dwWaitTime;
    DWORD           dwWaitRes;
    DWORD           dwCtrlRegStatus;
    DWORD           dwEDDCState;
   peddcDevInfo = (PDDCDEVINFO)eddcDevInfo;
    // Set ddcdi_LastState Invalid so that the first pass will always
    // generate a notification
    peddcDevInfo->eddcdi_LastState = (DWORD)(-1);

    dwWaitTime = 3000;     // Make sure the notify is performed on                               // power up

   while( TRUE)
   {
       dwWaitRes = WaitForSingleObject(peddcDevInfo->eddcdi_hIrqEvent, dwWaitTime);
    if( peddcDevInfo->ddcdi_fDeInit)

       if( dwWaitRes != WAIT_OBJECT_0 && dwWaitRes != WAIT_TIMEOUT)
       {
          break;
      }

        if( dwWaitRes !=  WAIT_TIMEOUT)
        {
          FGetIntStatus(SYSINTR_EDDC, dwActiveIrqs);

          // Clear all the outstanding EDDC interrupts now that the
          // mask status has been recorded
          FSetIntStatus( SYSINTR_EDDC, dwActiveIrqs);
          InterruptDone(SYSINTR_EDDC);
        }

        // This sample code targets an EDDC driver that supports                 // two states:
        // Vehicle in operation
        // Vehicle Not in operation.
        // An OEM may decide to implement addition states such as:
        // Vehicle Operation state undetermined
        //
        // The platform target by this EDDC driver uses a single
        // register bit to provide input for determining the current         // operational state of the vehicle but the OEM may chose to
        // implement EDDC using several input
        // sources and determine the operational state of the vehicle         // based on the multiple inputs.

        // This eddcIrqThread may be awoken either by a true hardware           // interrupt or due
        // to a power on of the Auto PC. The requirement is the same in          // both cases:
        // read the current operational state of the vehicle and notify         // the APC Shell if the operational state has changed.

        dwCtrlRegStatus = *(peddcDevInfo->eddcdi_pCtrlReg);
        if( dwCtrlRegStatus & EDDC_CTRLMASK_INOPERATION)
        {
            dwEDDCState = EDDC_STATE_VEHICLE_IN_OPERATION;
        }
        else
        {
            dwEDDCState = EDDC_STATE_VEHICLE_NOT_IN_OPERATION;
        }

        if( dwEDDCState != peddcDevInfo->eddcdi_LastState)
        {
                                  if(SUCCEEDED(APCPostNotify(NOTIFY_EDDC,WM_APCSYSMSG_EDDCSTATE,
   dwEDDCState,0)))
            {
                g_dwEDDCState=dwEDDCState;
                peddcDevInfo->eddcdi_LastState = dwEDDCState;
                dwWaitTime = INFINITE;
            }
            else
            {
                // The SH_EDDC_SetState failed, proberly boot time and                 // the Shell API
                // is not ready yet. Set the wait time so that the                    // notify will retry

                dwWaitTime = 5000;
            }
        }
   }
   DEBUGCHK(0);
   ddcClearInfo( &g_ddcDevInfo);
   return( 0);
}

Clean up and free resources

The PDD requires a function that cleans up and frees all platform specific resources associated with an EDDC device.

The following sample code demonstrates one possible way to implement this function by cleaning up the values stored in the DDCDEVINFO enumeration:

void    ddcClearInfo(PDDCDEVINFO pddcDevInfo)
{
   if( pddcDevInfo->ddcdi_hIrqEvent != NULL)
      CloseHandle( pddcDevInfo->ddcdi_hIrqEvent);
   if( pddcDevInfo->ddcdi_hIrqThread != NULL)
      CloseHandle( pddcDevInfo->ddcdi_hIrqThread);
   if( pddcDevInfo->ddcdi_pIOBase != NULL)
       ReleasePCIDevAddress( pddcDevInfo->ddcdi_pIOBase);
   return;
}

Supporting code

To support the functions implemented in the PDD layer, you could define an enumeration that stores EDDC information.

The following enumeration is used to support the sample PDD functions described in this topic:

typedef struct   _DDCDEVINFO
{
   HANDLE ddcdi_hIrqEvent;   // Interrupt event for this eddc device
   HANDLE ddcdi_hIrqThread; // Thread handle of interrupt handler thread
   PVOID   ddcdi_pIOBase;   // Mapped register address
   volatile PDWORD ddcdi_pCtrlReg;   // Control register access
   DWORD ddcdi_LastState;   // The current state of the EDDC
   BOOL ddcdi_fDeInit;       // Set when time top De-Init
} DDCDEVINFO, * PDDCDEVINFO;

See Also

Building an EDDC Control

Show:
© 2015 Microsoft