Showing posts with label driver. Show all posts
Showing posts with label driver. Show all posts

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.

Friday, February 19, 2016

Hands on GPIOs

Now that we know what a GPIO is, let's try to write a driver that we will use further to play with our Pi.

The reference document for this work is the BCM2835-ARM-Peripherals.pdf that you can easily find on the web.

GPIO Registers

GPFSELx : GPIO Function Select with x in [0;5]
Everyone of these registers configures up to 10 pins (except for GPFSEL5 which configures only 3).
Each pin can be configured as an input, output or alternate.
Here is the table with the definition of every alternate configuration for every pi.

GPSETx : GPIO Set function with x in [0;1]
Every pin is represented by a bit in these registers. If the pin is configured as an output, setting its bit in  GPSET will drive it to HIGH level.
GPCLRx : GPIO Clear function with x in [0;1]
Every pin is represented by a bit in these registers. If the pin is configured as an output, setting its bit in  GPSET will drive it to LOW level.
GPLEVx : GPIO Level wih x in [0;1]
Every pin is represented by a bit in these registers. You can get the current level of a pin by reading its pin in these registers.
GPEDSx : GPIO pin event status with x in [0;1]
Every pin is represented by a bit in these registers. A bit is set by the micro-controller whenever an event occurs on a pin and matches the event we are waiting for (configurable edge or level).
You can also configure the interrupt controller to be alerted with an interrupt for any pin.

GPIO pin configuration registers

GPRENx : GPIO Rising edge detect enable with x in [0;1]
GPFENx : GPIO  Falling edge detect enable with x in [0;1]
GPHENx: GPIO High detect enable with x in [0;1]
GPLENx : GPIO Low detect enable with x in [0;1]
GPARENx : GPIO Asynchronous rising edge  detect enable with x in [0;1]
GPAFENx: GPIO Asynchronous falling edge  detect enable with x in [0;1]

Asynchronous means the incoming signal is not sampled by the system clock. As such
rising edges of very short duration can be detected.
GPPUD : GPIO Pull up/down register
GPPUDCLKx : GPIO Pull up/down clock register with x in [0;1]
Use GPPUD to configure a pull up, pull down or unused mode. Then, use GPPUDCLKx to apply this mode to the desired pins.

Writing the driver step by step

Now that we know the registers to modify to handle raspberry GPIOs, we can go further and define our design. Here is a list of what we need :

A. GPIO registers memory mapping : Every register is located at a specific address in the microcontroller.  The first step would be to define them.
B. GPIO registers accessors: typically macros to avoid using masks in our code.
C. a GPIO static configuration : we will define the pins default configuration at build time. This is usually done to limit the number of function calls at boot to configure the device.
D. an interface : how will we use this driver ? We won't discuss about the integration of this driver into linux here.

And of course, everything we need to test our work !

A. GPIO Registers memory mapping

Given the BCM2835 document, the addresses start from 0x7E200000 and go to 0x7E2000B0, with 4 bytes per register (32 bits platform). My choice here is to create a structure where all of the registers are listed in the same order as the reference manual rather than defining every one of the registers with a #define statement. It is more elegant and a lot more handy when it comes to use them in the code. The final step will be to define the structure as volatile and to assign to it the GPIO base address (0x7E200000).

typedef struct
{
      uint32_t GPFSEL[6];
      uint32_t Reserved0; // Reserved byte
     
uint32_t GPSET[2]
      uint32_t Reserved1;
     
uint32_t GPCLR[2];
      uint32_t Reserved2;
     
uint32_t GPLEV[2];
      uint32_t Reserved3;
     
uint32_t GPEDS[2];
      uint32_t Reserved4;
     
uint32_t GPREN[2];
      uint32_t Reserved5;
     
uint32_t GPFEN[2];
      uint32_t Reserved6;
     
uint32_t GPHEN[2];
      uint32_t Reserved7;
     
uint32_t GPLEN[2];
      uint32_t Reserved8;
     
uint32_t GPAREN[2];
      uint32_t Reserved9;
     
uint32_t GPAFEN[2];
      uint32_t Reserved10;
     
uint32_t GPPUD;
     
uint32_t GPPUDCLK[2];
      uint32_t Reserved11[4];
      uint8 Test;
}volatile* GPIO_MemMap_ts;


The structures declared in GPIO_MemMap_ts should be defined too.
 When you are done, all you need is to declare a global variable for the GPIO_MemMap_ts:

const GPIO_MemMap_ts GPIO_BASE = 0x7E200000;

B. GPIO Registers Accessors

What we need here is to have an access to the bit of our choice in one of the GPIO registers. There is of course many ways to do that, here is a possible solution:

/* GPFSEL accessing macros */
#define GPFSEL_MODE_INPUT   ((uint32_t)0x000)
#define GPFSEL_MODE_OUTPUT  ((uint32_t)0x001)
#define GPFSEL_MODE_ALT0    ((uint32_t)0x100)
#define GPFSEL_MODE_ALT1    ((uint32_t)0x101)
#define GPFSEL_MODE_ALT2    ((uint32_t)0x110)
#define GPFSEL_MODE_ALT3    ((uint32_t)0x111)
#define GPFSEL_MODE_ALT4    ((uint32_t)0x011)
#define GPFSEL_MODE_ALT5    ((uint32_t)0x010)

#define GPFSEL_SET_FSELy(base, y, mode) (((volatile* GPIO_MemMap_ts)base).GPFSEL[y/10]) |= (mode << (3* (y%10)))

C. GPIO Static configuration

Work in progress...

 
biz.