Friday, April 7, 2017

WCE7 : How to write a device driver


Quick introduction

An application interacts with a device driver thanks to file system functions (CreateFile, ReadFile, WriteFile,...).
To identify the driver to be opened, the application needs to specify a file name to CreateFile:

* [Usual format] "XXXy:" : with X a capital letter and y a number in [1;9] (0 is authorize to indicate the 10th index)
* [Device mount point] "\\<device_mount_point>\\XXXy:"
* [Bus mount point] "\\<bus_mount_point>\\XXXy:"
The three capital letters form a prefix that will identify the driver. The number indicates the index of the driver if several can be instanciated.
This prefix is also used by Windows to load the device driver methods. In fact, the prefix shall precede the device driver methods in driver dll:
* XXX_CreateFile
* XXX_ReadFile
* ...

All device drivers are listed in registry below HKLM\Drivers\BuiltIn with at least the following subkeys:

Key Description
Prefix Explained above (e.g. "XXX")
Dll Driver dll method to be loaded

At system boot, Device Manager will iterate over these entries and load them. For every loaded driver, an entry will be created in HKLM\Drivers\Active.
The order of load can be specified if the driver entry specifies a key Order followed by a number that indicates when it shall be loaded (with 1 the first to be loaded)
Other optional subkeys can be used for a driver, but they won't be described here.
In the following chapters, we will create the necessary files to add a driver to our BSP. In the scope of this tutorial, we will create a driver for the DS1307 RTC chip.

 

Add a new catalog entry

The first step is to add a catalog entry to our BSP. To do so, either open the *.pbcxml file with Visual Studio and use the graphical interface or edit the *.pbcxml file and add the following lines:
<Bsp>
    ...
    <BspItemId>FT5X06_TOUCH:LNI Swissgass:FT5X06_LniBSP</BspItemId>
    <BspItemId>RTC_DS1307:LNI Swissgass:RTC_DS1307_LniBSP</BspItemId>
    <BspItemId>DVI_Type:LNI Swissgas:DVI_Type_LniBSP</BspItemId>
    ...
</Bsp>
<Item Id="RTC_DS1307:LNI Swissgass:RTC_DS1307_LniBSP">
    <Title>RTC driver</Title>
    <Description>Real Time Clock</Description>
    <Comment>Only valid for DS1307 chip</Comment>
    <Type>BspSpecific</Type>
    <Variable>BSP_RTC_DS1307</Variable>
    <Module>rtc_ds1307.dll</Module>
    <Location ConditionLocation="">Drivers\RTC</Location>
</Item>
These lines add a new driver to the catalog under Driver\RTC. If checked, this driver defines the variable BSP_RTC_DS1307.

 

Hands on code : create the driver component

Create a directory for our driver under <MyBSP>\SRC\Drivers : here we create a folder named RTC. The following steps are to be done in this directory.
The most obvious file to add to our componen is makefile. This file is always the same for all drivers so copy/paste an existing one or create it and add the following lines:
!if 0
Copyright (c) Microsoft Corporation.  All rights reserved.
!endif
!if 0
Use of this source code is subject to the terms of the Microsoft end-user
license agreement (EULA) under which you licensed this SOFTWARE PRODUCT.
If you did not accept the terms of the EULA, you are not authorized to use
this source code. For a copy of the EULA, please see the LICENSE.RTF on your
install media.
!endif
#
# DO NOT EDIT THIS FILE!!!  Edit .\sources. if you want to add a new source
# file to this component.  This file merely indirects to the real make file
# that is shared by all the components of Windows CE.
#
!INCLUDE $(_MAKEENVROOT)\makefile.def
Our driver needs to be declared in registry. Create an rtc_ds1307.reg file and the following lines:
IF BSP_RTC_DS1307
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\RTC_DS1307]
    "Prefix"="RTC" 
    "Dll"="rtc_ds1307.dll" 
    "Index"=dword:1
    "order"=dword:101                ; Should be loaded after I2C
ENDIF BSP_RTC_DS1307
The file structure of a WEC7 BSP requires a file named sources in every folder that contains files to take into consideration during build. Create one with the following lines:
!if 0
;================================================================================
; RTC Driver DS1307
;================================================================================
!endif

!IF "$(BSP_RTC_DS1307)" == "" 
SKIPBUILD=1
!ENDIF

TARGETNAME=rtc_ds1307
TARGETTYPE=DYNLINK

TARGETLIBS= \
    $(_PLATLIB)\$(_CPUDEPPATH)\ceddk.lib \
    $(_COMMONSDKROOT)\lib\$(_CPUINDPATH)\coredll.lib

SOURCES= \
    rtc_ds1307.c \
Our driver will obviously need to export the stream interface driver methods. This is done with a *.def file as follows:
LIBRARY     RTC_DS1307

EXPORTS RTC_Init
    RTC_Deinit
    RTC_Open
    RTC_Close
    RTC_Read
    RTC_Write
    RTC_IOControl
And now, we can add some code :
//
//  File: rtc_ds1307.c
//
//  This file implements device driver for RTC DS1307. 
//
#include "omap.h" 
#include "ceddkex.h" 
#include "sdk_padcfg.h" 
#include "bsp_padcfg.h" 
#include "bsp.h" 

#include <nkintr.h>
#include <winuserm.h>
#include <pmpolicy.h>

// ==== Debug Zones ================================================================================
// simple handling: debug zones are forced with TRUE or FALSE and need a recompile to be activated
// for sophisticated handling: use macro DEBUGZONE(), DBGPARAM dpCurSettings etc.
// (see MSDN e.g. Win CE 5.0, Debug Zones)
#ifndef SHIP_BUILD

#undef ZONE_ERROR
#undef ZONE_WARN
#undef ZONE_FUNCTION
#undef ZONE_INFO
#undef ZONE_TIPSTATE

#define ZONE_ERROR          DEBUGZONE(0)
#define ZONE_WARN           DEBUGZONE(1)
#define ZONE_FUNCTION       DEBUGZONE(2)
#define ZONE_INFO           DEBUGZONE(3)
#define ZONE_TIPSTATE       DEBUGZONE(4)

static DBGPARAM dpCurSettings = {
    L"RTC_DS1307", {
        L"Errors",      L"Warnings",    L"Function",    L"Info",
            L"IST",         L"Undefined",   L"Undefined",   L"Undefined",
            L"Undefined",   L"Undefined",   L"Undefined",   L"Undefined",
            L"Undefined",   L"Undefined",   L"Undefined",   L"Undefined" 
    },
    // Bitfield controlling the zones.  1 means the zone is enabled, 0 disabled
    // We'll enable the Error, Warning, and Info zones by default here
    1 | 1 << 1 | 1 << 2 | 1 << 3
};

#endif

//------------------------------------------------------------------------------
DWORD RTC_Init(LPCTSTR szContext, LPCVOID pBusContext)
//  Called by device manager to initialize device.
{
    int i;
    DWORD rc = (DWORD)NULL;

    UNREFERENCED_PARAMETER(pBusContext);

    DEBUGMSG(ZONE_FUNCTION, (L"+RTC_Init(%s, 0x%08x)\r\n", szContext, pBusContext));
    RETAILMSG(1, (L"+RTC_Init\r\n"));

cleanUp:
    DEBUGMSG(ZONE_FUNCTION, (L"-RTC_Init(rc = %d\r\n", rc));

    return rc;
}

//------------------------------------------------------------------------------
BOOL RTC_Deinit(DWORD context)
{
    BOOL rc = FALSE;

    DEBUGMSG(ZONE_FUNCTION, (L"+RTC_Deinit(0x%08x)\r\n", context));

    rc = TRUE;

cleanUp:
    DEBUGMSG(ZONE_FUNCTION, (L"-RTC_Deinit(rc = %d)\r\n", rc));
    return rc;
}

//------------------------------------------------------------------------------
DWORD RTC_Open(DWORD context, DWORD accessCode, DWORD shareMode)
{
    UNREFERENCED_PARAMETER(context);
    UNREFERENCED_PARAMETER(accessCode);
    UNREFERENCED_PARAMETER(shareMode);
    return context;
}

//------------------------------------------------------------------------------
BOOL RTC_Close(DWORD context)
{
    UNREFERENCED_PARAMETER(context);
    return TRUE;
}

//------------------------------------------------------------------------------
DWORD RTC_Read(DWORD context, LPVOID pBuffer, DWORD Count)
{
    UNREFERENCED_PARAMETER(context);
    UNREFERENCED_PARAMETER(pBuffer);
    UNREFERENCED_PARAMETER(Count);
    return 0;
}

//------------------------------------------------------------------------------
DWORD RTC_Write(DWORD context, LPCVOID pSourceBytes, DWORD NumberOfBytes)
{
    UNREFERENCED_PARAMETER(context);
    UNREFERENCED_PARAMETER(pSourceBytes);
    UNREFERENCED_PARAMETER(NumberOfBytes);
    return 0;
}

//------------------------------------------------------------------------------
BOOL RTC_IOControl(
    DWORD context, DWORD code, BYTE *pInBuffer, DWORD inSize, BYTE *pOutBuffer,
    DWORD outSize, DWORD *pOutSize
) {
    UNREFERENCED_PARAMETER(context);
    UNREFERENCED_PARAMETER(code);
    UNREFERENCED_PARAMETER(pInBuffer);
    UNREFERENCED_PARAMETER(inSize);
    UNREFERENCED_PARAMETER(pOutBuffer);
    UNREFERENCED_PARAMETER(outSize);
    UNREFERENCED_PARAMETER(pOutSize);
    DEBUGMSG(ZONE_INIT, (L"RTC_IOControl"));
    return FALSE;
}

//------------------------------------------------------------------------------
BOOL __stdcall DllMain(HANDLE hDLL, DWORD reason, VOID *pReserved)
{
    UNREFERENCED_PARAMETER(pReserved);
    switch (reason) {
    case DLL_PROCESS_ATTACH:
        DEBUGREGISTER(hDLL);
        DisableThreadLibraryCalls((HMODULE)hDLL);
        break;
    }
    return TRUE;
}
and a header (optional):
#ifndef __RTC_DS1307_H
#define __RTC_DS1307_H

// Stream interface entry points

// Initialization of controlled hardware, setup of ISR and IST(if any),
// setup of memory access (if any) 
DWORD RTC_Init(LPCTSTR szContext, LPCVOID pBusContext);
// Cleans up what Init did
BOOL RTC_Deinit(DWORD context);
// Implicitely called by CreateFile
// Opens the context for client usage
DWORD RTC_Open(DWORD context, DWORD accessCode, DWORD shareMode);
// Closes the context opened in Open
BOOL RTC_Close(DWORD context);
// Use if required : read data out of driver
DWORD RTC_Read(DWORD context, LPVOID pBuffer, DWORD Count);
// Use if required : write data to driver
DWORD RTC_Write(DWORD context, LPCVOID pSourceBytes, DWORD NumberOfBytes);
// Driver specific functionality
BOOL RTC_IOControl(
    DWORD context, DWORD code, BYTE *pInBuffer, DWORD inSize, BYTE *pOutBuffer,
    DWORD outSize, DWORD *pOutSize
);

#endif // __RTC_DS1307_H

Add driver to project build

The driver component is ready, we need to include it to the project. As said earlier, the file structure of a BSP requires sources files to include files to build but also dirs files to list the directories that will be visited during build. Open <MyBSP>\SRC\Drivers\dirs and add an entry for our new driver.
DIRS=\
        ...
    FT5X06\
    RTC_DS1307\
    WAVEDEV2\
        ...
For rom image creation, we need now to tell the project that it should put the driver's dll into the image.
Open platform.bib and add the following lines:
...
IF BSP_RTC_DS1307
rtc_ds1307.dll    $(_FLATRELEASEDIR)\rtc_ds1307.dll            NK  SHK
ENDIF
...
Finally, add the following lines to platform.reg to integrate the component's registry settings:
...
;===============================================================================
; RTC DS1307 driver
#include "$(PLAT_DRIVERS_DIR)\RTC_DS1307\rtc_ds1307.reg" 
...
You're good to start the development of the driver.

Cem SOYDING

Author & Editor

Senior software engineer with 12 years of experience in both embedded systems and C# .NET

0 comments:

Post a Comment

Note: Only a member of this blog may post a comment.

 
biz.