20
Sharing I2C bus between OAL RTC part and Driver part on Windows CE
8 Comments | Posted by Vinoth - Microsoft Certified Technology Specialist in Windows CE
Real Time Clock or RTC is an essential component in all embedded systems. Though all the modern day CPUs have inbuilt RTC, designers are comfortable with external RTC for some specific reasons such as to reduce the power burden, RTC alarms to wake-up the CPU etc. The power consumption of external RTC devices are extremely low and some of them offer additional battery-backed SRAM with tamper-detection etc. Designers interface these RTC chips through a common I2C bus shared by a lot of peripherals and sensors. Therefore a common I2C bus driver is needed to the developers.
Since an I2C interface is a shared bus, synchronization is needed between the devices to access the I2C bus. Windows CE supports synchronization objects like critical sections, semaphore and mutex etc.., to share the device or bus in driver level without conflict. But RTC related functionalities are implemented in the OAL.exe. Therefore it is not possible to use synchronization objects in the OAL part. This blog depicts - how to share I2C bus between OAL and driver without conflict.
RTC related OAL functions
Following functions are RTC related OAL functions.
a) BOOL OEMSetAlarmTime (LPSYSTEMTIME lpst );
b) BOOL OEMSetRealTime (LPSYSTEMTIME lpst);
c) BOOL OEMGetRealTime(LPSYSTEMTIME lpst);
d) Calling KernelIoControl with the IOCTL_HAL_INIT_RTC as a parameter.
These functions are called from the windows CE standard time functions. IOCTL_HAL_INIT_RTC resets the real-time clock by calling the OEMSetRealTime function during the booting. OEM is responsible for filling the stub inside these functions.
I2C Driver
I2C driver is a windows CE stream driver. Since the I2C bus is shared by the devices like camera sensor, temperature sensor, battery fuel gauge, Ambient light sensor etc. Therefore I2C bus driver is implemented with synchronization objects. For example, critical section is suitable for this case. This I2C driver will be utilized by other peripheral drivers. These techniques shall be effective to avoid races within the I2C driver. But the same I2C controller is shared between the OAL RTC code and the I2C driver code. This poses a different challenge.
Issue
Same I2C controller is shared between the OAL RTC and other peripheral drivers. Unfortunately, it is not possible to use synchronization objects between the OAL and the device drivers. Because “OAL.exe” is part of the Windows CE kernel. Device drivers are running on the top of the kernel. In order to synchronize the I2C bus access between OAL and driver, we need to maintain a sharable lock between these areas. The sharable lock can be achieved using the reserved memory region named DRVGLOB.
Solution
We can reserve a memory region with the known address in the config.bib file. A structure of sharable data between the OAL and the drivers can be maintained in the DRVGLOB reserved region. The following is the example of a config.bib that contains the DRVGLOB reserved region.
EBOOT 80000000 00100000 RESERVED
NK 80100000 01F00000 RAMIMAGE
RAM 82000000 0DF00000 RAM ;233MB
KITLUSB20 8FF00000 00010000 RESERVED ;For USB2.0 RNDIS Kitl DMA Buffer
LCDBOOTFRM 8FF10000 000D0000 RESERVED ;LCD FRAME BUFFER on EBOOT
DRVGLOB 8FFE0000 00010000 RESERVED ;Driver Globals
The following is a sample structure of DRVGLOB and it is declared in the DRVGLOB.H header file.
#ifndef __DRVGLOB_H
#define __DRVGLOB_H
typedef struct {
UINT32 Timeout; // RTCTime
UINT32 TouchWakeup; // Optional Wake up Source
.
.
.
UINT32 bI2CBusy; //Lock logic for sharing the I2C bus between OAL and Driver.
} DRV_GLOB, *P_DRV_GLOB;
#define DRVGLOB_BASE 0×8FFE0000
#define DRVGLOB_SIZE 0×10000
#endif
“bI2CBusy” is the variable used for maintaining the synchronization between the OAL and I2CDriver. We can access this reserved region in both the OAL and the driver. Following code snippet explains – how to access the structure in OAL and driver.
• Initializing the DRVGLOB structure in OAL
volatile DRV_GLOB* g_DrvGlob;
g_DrvGlob = (volatile DRV_GLOB*) OALPAtoVA(DRVGLOB_BASE, FALSE);
if ( !g_DrvGlob )
{
RETAILMSG(1, (_T(”RTC::DRVGLOB Mem Alloc Failedrn”)));
return FALSE;
}
• Initializing the DRVGLOB structure in I2C Driver
volatile DRV_GLOB* g_DrvGlob;
PHYSICAL_ADDRESS Drv_Glop_Base = {DRVGLOB_BASE};
g_DrvGlob = (volatile DRV_GLOB*) MmMapIoSpace(Drv_Glop_Base, sizeof(DRV_GLOB), FALSE);
if ( !g_DrvGlob )
{
RETAILMSG(1, (_T(”I2C::DRVGLOB Mem Alloc Failedrn”), GetLastError()));
return FALSE;
}
• Synchronizing OAL RTC lib and I2C Driver
Following procedure explains the implementation.
I2C Read/Write Function in OAL RTC
a) Check I2C is busy with I2C driver. You can add some timeout, instead of waiting infinitely to avoid accidental hanging. If it is busy, wait in the loop until the bus is free. Following code snippet explains this.
while (g_DrvGlob->bI2CBusy)
{
Millisecond Delay function with 1 ms delay.
}
b) If it is free set the “bI2CBusy” as 1 to make it busy.
g_DrvGlob->bI2CBusy = 1;
c) Perform I2C bus access.
//I2C Read or Write operations
.
.
d) Clear the “bI2CBusy” to assure that the bus access is finished.
g_DrvGlob->bI2CBusy = 0;
I2C Read/Write Function in Driver
a) Enter Critical section to deny other peripheral driver accessing the I2C driver.
b) Check I2C is busy with OAL RTC. You can add some timeout, instead of waiting infinitely to avoid accidental hanging. If it is busy wait, in the loop until the bus is free. Following code snippet explains this.
while (g_DrvGlob->bI2CBusy)
{
Millisecond Delay function with 1 ms delay.
}
c) If it is free set the “bI2CBusy” as 1 to make it busy.
g_DrvGlob->bI2CBusy = 1;
d) Perform I2C bus access.
//I2C Read or Write operations
.
.
e) Clear the “bI2CBusy” to assure that the bus access is finished.
g_DrvGlob->bI2CBusy = 0;
f) Exit Critical section to allow other peripheral driver to access the I2C bus.
In this way, we can share the I2C bus with OAL part and the driver part.
8 Comments for Sharing I2C bus between OAL RTC part and Driver part on Windows CE
Prashanth | July 26, 2009 at 11:20 pm
Gary (Cash Systemes Industrie) | September 10, 2009 at 4:36 am
Very interesting article, however I still don’t see how it could function. From within the OAL RTC, if the I2C driver is occupied with the I2C, the code block “while (g_DrvGlob->bI2CBusy)” will occupy the OAL entirely resulting in a deadlock. The OAL has priority over the I2C driver which has a resource required by the OAL. Unless you have some method of “sleeping” in the OAL RTC “while…” loop. If you do I am very interested, if you don’t then how can this code work?
Gary (Cash Systemes Industrie) | September 16, 2009 at 2:12 am
I saw that code and we agree. My question was closer to “how do you sleep in the OAL?” Simply saying “Millisecond Delay function with 1 ms delay.” or “This 1 ms delay is the time, the OAL will sleep” doesn’t explain how one can sleep in the OAL. I’m pretty familiar with the Windows CE API and the only routines which are similar to a sleep in the OAL are actually just loops that wait for a timeout. To my knowledge it is not possible to “relinquish” the processor for 1 millisecond from within the OAL. It seems that this is what is required here.
Gary | September 17, 2009 at 4:57 am
Very well. I can use those routines but would still be quite interested in discussing other aspects of your example - notably a usage of the virtual address in the call to OALPAtoVA. The “config.bib” file normally uses virtual addresses yet it is the same address used there that is passed to the OALPAtoVA routine. This routine normally accepts a physical address and returns the virtual address. I wouldn’t mention it but as I built my shared section based on your example, I lost a bit of time figuring out why OALPAtoVA wasn’t working (it was returning NULL). I wouldn’t blame you for not posting this so feel free to answer directly to my email address. In any case much thanks for your help.


Vinoth,
Nice article.
But i have a small concern with this approach. Is this method foolproof? In the sense it could so happen that both OAL RTC and some other driver which is accessing I2C, executes “while (g_DrvGlob->bI2CBusy)” at the same time and both may get I2C access permission, before any one setting this field to 1.
This is the exact reason why CriticalSection/Mutex OS object which is an Atomic operation is used in Application space rather than using a global variable.