Friday, November 28, 2008

Buona Fortuna - Good Luck Ankit

Yesterday was the last day for my team mate Ankit in Accord.

He is all set to start off a new career and may be even in a new path in his new company - MindTree. All the best for you Ankit




Saturday, November 22, 2008

Dynamically Changing USB Client Function

Hi guys,

Here is a code for changing the current usb client function from Serial to Mass storage dynamically.



/*
* #FUNCTION
*
* @Function: GetUfnController
* @Return: INVALID_HANDLE_VALUE - USB Function device was not found
* Otherwise, if found
* @Parameters:
* @Description:
* Function to get the handle of a USB Client device.
*/
HANDLE GetUfnController()
{
HANDLE hUfn = NULL;
// Get a handle to the bus driver
DEVMGR_DEVICE_INFORMATION di;
memset(&di, 0, sizeof(di));
di.dwSize = sizeof(di);

#ifdef _SEARCH_WITH_GUID_
BYTE rgbGuidBuffer[sizeof(GUID) + 4]; // +4 because scanf writes longs
memset(&rgbGuidBuffer[0],0,sizeof(GUID) + 4);
LPGUID pguidBus = (LPGUID) rgbGuidBuffer;
LPCTSTR pszBusGuid = _T("E2BDC372-598F-4619-BC50-54B3F7848D35");
// Parse the GUID
if (FALSE == ConvertStringToGuid(&pszBusGuid[0],pguidBus) )
return INVALID_HANDLE_VALUE;
HANDLE hf = FindFirstDevice(DeviceSearchByGuid, pguidBus, &di);
#else
HANDLE hf = FindFirstDevice(DeviceSearchByLegacyName, L"UFN1:", &di);
#endif /* _SEARCH_WITH_GUID_ */
if (hf != INVALID_HANDLE_VALUE) {
hUfn = CreateFile(di.szBusName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, 0, NULL);
CloseHandle(hf);
}
else {
hUfn = INVALID_HANDLE_VALUE;
}
return hUfn;
} /* GetUfnController */

/*
* #FUNCTION
*
* @Function: ChangeClient
* @Return: TRUE - Client Function change was succesful
* FALSE - Client Function change failed
* @Parameters:
* hUfn - Handle to the USB client device
* pszNewClient - New usb function to be loaded
* @Description:
* Function to switch the active usb client function driver
*/
BOOL ChangeClient(HANDLE hUfn, LPCTSTR pszNewClient)
{
if(hUfn == INVALID_HANDLE_VALUE || pszNewClient == NULL)
return ERROR_INVALID_PARAMETER;
DWORD dwRet = ERROR_SUCCESS;
UFN_CLIENT_NAME ucn;
_tcscpy(ucn.szName, pszNewClient);
BOOL fSuccess = DeviceIoControl(hUfn,
IOCTL_UFN_CHANGE_CURRENT_CLIENT, &ucn, sizeof(ucn), NULL, 0, NULL, NULL);
return fSuccess;
} /* ChangeClient */


/*
* #FUNCTION
*
* @Function: ConnectionStatus
* @Return: TRUE - Active sync session is active
* FALSE - Active sync session is not detected
* @Parameters:
* @Description:
* Function to Check the connection status of ActiveSync
*/
BOOL ConnectionStatus(VOID)
{
RASCONN rsconn[10];
DWORD dwcb, dwConnections;
RASCONNSTATUS rasStatus;
BOOL bConnected = FALSE;

// Enumerate active connections
dwcb = sizeof(rsconn);
rsconn[0].dwSize = sizeof(RASCONN);
if(RasEnumConnections(rsconn, &dwcb, &dwConnections) == 0) {
if(dwConnections == 0 || rsconn[0].hrasconn == NULL) {
DBG_PRINT ("No current connections\r\n");
}
else{
// Get first RAS connection status
rasStatus.dwSize = sizeof(rasStatus);
if(RasGetConnectStatus(rsconn[0].hrasconn, &rasStatus) != 0) {
DBG_PRINT ("Could not get RAS connection status\r\n");
}
else{
// Is it connected?
if(rasStatus.rasconnstate != RASCS_Connected) {
DBG_PRINT("Not connected\r\n");
}
else {
if ((0 == wcscmp(L"direct",rasStatus.szDeviceType)) &&
(0 == wcscmp(L"Serial on USB",rasStatus.szDeviceName))) {
DBG_PRINT(TEXT("Got an activesync connection..\r\n"));
bConnected = TRUE;
}
}
}
}
}
else {
DBG_PRINT ("Could not enumerate RAS connections\r\n");
}
return bConnected;
} /* ConnectionStatus */

Monday, November 17, 2008

Remote Tools and CETK over activesync

I will explain on how to connect development pc to a board running windows ce 6.0 os image using Activesync. The development board that I used is similar to the Mainstone III development platform (based on a Marvell PXA 270 processor) and activesync is configured to connect over the USB client port.

Previously when I was trying to connect to my development board using activesync from remote tools, i was getting error message that " Unable to load device side components". The samething happens while I was using CETK. But Sctivesync was connceting properly. Then I started digging into discussion forums for finding a solution to this problem. Here is what I found:

  1. Open C:\Program Files\Common Files\Microsoft Shared\Windows CE Tools\Platman\target\wce600.
  2. Create a folder armV4 and copy the contents of armV4i into this one.
  3. Now Open Kernel Tracker or any remote tools.
  4. Then follow the same steps specified by Sue in her blog post.
Presto!!!... There you have your remote tools working over Activesync.

NOTE: Special Thanks to Sue Loh (Windows CE Base Team Blog) and Yan Sun (CE 6.0 and PlatMan remote tools)

Tuesday, November 11, 2008

Debugging a booting problem

In this post, I will explain about how I debugged a problem in which our development board was taking a lot of time to completly boot up into Windows CE.


The usual bootup time for the development board was around 30-35 seconds, but as we were adding new drivers , the bootup time went up by minutes. The booting was really slow. So I took up this issue.

As i analyzed the booting up process, it was clear that the Boot phase 1 (loading of FMD) was getting over without any hickups. And slow down seems to have been occuring while drivers are getting loaded and initialized.

We can follow two strategies for pin pointing this issue:

Remove each driver one-by-one.
If we are not clear with the source of the problem, then individually eliminating each component one-by-one and observing the behaviour is a straight forward but time consuming way.


Using Celog
In one of my earlier posts, I have mentioned about a cab file that I created with all the celog settings and files. I installed this file on to the development board and configured the registry to launch 'CeLogFlush.exe' during bootup. Also Celog was configured to dump the data into a local file.

[HKEY_LOCAL_MACHINE\init]
"Launch05"="CeLogFlush.exe" -- include celog.dll

[HKEY_LOCAL_MACHINE\System\CeLog]
"Transport"="LocalFile"
"FileName"="celog.clg"
"BufferSize"=dword:100000 // 1MB
"ZoneCE"=dword:815263
"FlushTimeout"=dword:1388 // 5 seconds
"FileFlags"=dword:1 // Keep file open
"ThreadPriority"=dword:C8 // Priority increased to 200
After the complete system was up, I killed the CeLogFlush.exe using ITaskMgr (a really good task manager application) and took the Celog.clg from the device. This output file will contain only the thread IDs, we need to fix this up using readlog (http://devwince.blogspot.com/2008/10/fixing-thread-names-in-celog-output.html).

Now we can view this file in Windows CE Remote Kernel tracker.

Image1


According the Celog data, the booting process went normally till the 7th second since celogflush was started and all the drivers till that point was loaded and threads have been started (that means the XXX_Init routine was completed successfully). But after that the next thread start only at the 52 seconds mark, which was the battery monitor thread. And according the driver loading order in registry, the drivers were the power button monitor and battery driver.

Image2


When I analyzed the XXX_Init of battery driver, I found out that there was a t_printf statement in the init routine. According to the MSDN documentation t_printf will dump the data onto a console, which is again a driver and is not started yet. So when I removed the t_printf statements, the booting speed was back to normal.

:)

Tuesday, November 4, 2008

Another nice article about booting

Here is another really informative article by the Windows CE Base team.:)

http://blogs.msdn.com/ce_base/archive/2007/11/26/How-does-Windows-Embedded-CE-6.0-start_3F00_.aspx

Saturday, October 25, 2008

Controlling a Stream Interface Driver

Special thanks to Bruce Eitman

/*
* Function to query the active drivre list using a search string
*/
VOID QueryDriver (const WCHAR* lpcParam)
{
DEVMGR_DEVICE_INFORMATION devmgrInfo;
devmgrInfo.dwSize=sizeof(DEVMGR_DEVICE_INFORMATION);
HANDLE hDevRes = FindFirstDevice (DeviceSearchByDeviceName, lpcParam,
&devmgrInfo);
if (INVALID_HANDLE_VALUE != hDevRes)
{
do
{
DBG_PRINT(_T("%d , %d , %s, %s, %s, %s\n"),
devmgrInfo.hDevice,
devmgrInfo.hParentDevice,
devmgrInfo.szLegacyName,
devmgrInfo.szDeviceKey,
devmgrInfo.szDeviceName,
devmgrInfo.szLegacyName );
}while (FindNextDevice(hDevRes,&devmgrInfo));
FindClose (hDevRes);
}
else
{
DWORD dwErrorCode = GetLastError();
DBG_PRINT(_T("QueryDriver , code: %d\n"),dwErrorCode);
}
return;
}


--------------------
/*
* Function to load a driver, when provided the driver registry key
*/
VOID LoadDriver (const WCHAR* lpcParam)
{
HANDLE hDriverShell = INVALID_HANDLE_VALUE;
hDriverShell = ActivateDeviceEx( lpcParam,NULL,0,NULL);

if (hDriverShell != INVALID_HANDLE_VALUE && hDriverShell != 0)
{
}
else
{
DWORD dwErrorCode = GetLastError();
DBG_PRINT(TEXT("LoadDriver: Failed to activate driver %d\n"), GetLastError() );
}
return;
}

--------------------
/*
* Function to unload a driver, when provided the diver name
*/
VOID UnloadDriver (const WCHAR* lpcParam)
{
DEVMGR_DEVICE_INFORMATION devmgrInfo;
devmgrInfo.dwSize=sizeof(DEVMGR_DEVICE_INFORMATION);
HANDLE hDevRes = FindFirstDevice (DeviceSearchByDeviceName, lpcParam,
&devmgrInfo);
if (INVALID_HANDLE_VALUE != hDevRes)
{
do
{
DeactivateDevice(devmgrInfo.hDevice);
}while (FindNextDevice(hDevRes,&devmgrInfo));
FindClose (hDevRes);
}
else
{
DWORD dwErrorCode = GetLastError();
DBG_PRINT(_T("QueryDriver , code: %d\n"),dwErrorCode);
}
return;
}

--------------------

Monday, October 13, 2008

Fixing Thread names in CeLog Output

When you are debugging on a standalone system (i.e. which is not connected to platform builder, using corecon), you may need to get the celog CLG file and review the performace data for the session in Kernel Tracker

But the file which you are getting will not have the thread information, it will only have the identifiers for each thread instead of the names. The names of the thread can be acquired using the Readlog utility. First copy the CLG file you obtained from the system into the build release directory and execute the following command :
Readlog -fixthreads celog.clg output.clg

Now output.clg file will have the thread names fixed up.

Fixing Thread Names With Readlog (http://msdn.microsoft.com/en-us/library/aa935935.aspx)

Monday, September 29, 2008

Interrupt ... whats the big deal??

In most of the discussion forums I have seen that developers are not clear about how interrupts are handled in windows ce. I am familiar with the Xscale and arm architectures. This post will not explain about how to handle or register interrupt in drivers, as it is covered in some of the previous posts. Instead how kernel manages an interrupt in windows ce 6.0. Lets start off with the concept of EXCEPTIONS in arm architectures.

"Exceptions are generated by internal and external sources to cause the processor to handle an event, such as an externally generated interrupt or an attempt to execute an Undefined instruction."
- I took it from the arm reference manual, couldn't put it in any better way myself:)

There are seven types of exceptions supported by ARM:
  1. Reset
  2. Undefined Instruction
  3. Software Interrupt
  4. Prefetch Abort
  5. Data Abort
  6. IRQ
  7. FIQ
When an exception occurs, the processor execution is halted and branches to a predefined address called exception vector. For each exception type, there is predefined exception vector, for example, exception vector for Data abort is 0x00000010 and that of reset is 0x00000000. There is also high vectors in which the exception vectors can be moved to an alternative address range(0xFFFF0000-0xFFFF001C), using bit[13] of the coprocessor 15 register 1 .

In the exception handler the banked version of register such as R14 and SPSR are used to save and restore the processor state.

Starting point for analyzing the exception handling in Windows ce for ARM architectures can be
WINCE600\PRIVATE\WINCEOS\COREOS\NK\KERNEL\ARM\exvector.s



Defenition to each of this hanlders can be found in armtrap.s (WINCE600\PRIVATE\WINCEOS\COREOS\NK\KERNEL\ARM)
This vector table will be loaded into the higher part of physical page at 0xffff0000 (same as the High vector address), along with other kernel data in ARM_HIGH structure (WINCE600\PRIVATE\WINCEOS\COREOS\NK\INC\nkarm.h)


Here are some references that you can start off with
- ARM Architecture Reference Manual

Here is the link to really informative article in MSDN regarding the boot sequence in bootloader and kernel.


Tuesday, September 23, 2008

Querying that powerstate of an NIC

This one is a continuation of the last post, Here is a code for chcking the current power state of an NIC
// Returns TRUE if device is enabled otherwise FALSE
BOOL QueryNIC (wchar_t* InterfaceName)
{
TCHAR                           szName[MAX_PATH];
int                             nChars;
TCHAR*                          pszAdapter;
TCHAR                           multiSz[257];
DWORD                           cbBuffer = sizeof(multiSz);
CEDEVICE_POWER_STATE Dx = PwrDeviceUnspecified;    
BOOL bDevPowered = TRUE;

pszAdapter = new TCHAR[wcslen(InterfaceName)+1];
wcscpy_s(pszAdapter,wcslen(InterfaceName)+1,InterfaceName);
nChars = _sntprintf_s(szName,
                                                    MAX_PATH-1,
                                                   MAX_PATH-1,
                                                  _T("%s\\%s"),
                                                                  PMCLASS_NDIS_MINIPORT, 
                                                                   pszAdapter);
szName[MAX_PATH-1]=0;

if(nChars != (-1)) 
::GetDevicePower(szName, POWER_NAME, &Dx);

if (!IsValidDx(Dx))
{
bDevPowered = FALSE;
}
if (D4 == Dx)
{
bDevPowered = FALSE;
}
delete pszAdapter;
return bDevPowered ;
}

i have to thank one person (seriously I forgot this name... i am really bad a remembering names and numbers), he posted an article in codeproject.com regarding this functionality. Then I found more helpful code in completing the circle.. :) 
WINCE600\PUBLIC\COMMON\OAK\DRIVERS\NETSAMP\NDISPWR
This is a sample program given in windows ce 6.0 showing how to query and the set the power state of network interface using deviceIocontrols (specifically,  ndis ioctls for binding and unbinding the adapter after a power state change) and setDevicePower functions. 

Here is the code for controlling (enabling and disabling, its the same operation as in enabling and disabling a network interface in network connection applet in control panel) an NIC.

Pay attention to the high lighted part of code.

void ControlNIC(wchar_t* InterfaceName,BOOL bEnable)
{
HANDLE                          hNdisPwr;
NDISPWR_SAVEPOWERSTATE SavePowerState;
TCHAR                           szName[MAX_PATH];
int                             nChars;
TCHAR*                          pszAdapter;
TCHAR                           multiSz[257];
DWORD                           cbBuffer = sizeof(multiSz);

hNdisPwr = CreateFile((PTCHAR)NDISPWR_DEVICE_NAME,
                                                          0x00,0x00,NULL,
                                                         OPEN_EXISTING,
                                                       FILE_ATTRIBUTE_NORMAL
                                                                | FILE_FLAG_OVERLAPPED,
                                                        (HANDLE)INVALID_HANDLE_VALUE);

if (hNdisPwr != INVALID_HANDLE_VALUE)
{
SavePowerState.pwcAdapterName = InterfaceName;
if (bEnable)
{
SavePowerState.CePowerState   = PwrDeviceUnspecified;
}
else
{
SavePowerState.CePowerState   = D4;
}

BOOL ret = DeviceIoControl(hNdisPwr,
                                                    IOCTL_NPW_SAVE_POWER_STATE,
                                                    &SavePowerState,
                                                    sizeof(NDISPWR_SAVEPOWERSTATE),
                                                    NULL,0x00,NULL,NULL);
// TODO: take care of the return value 'ret '

CloseHandle(hNdisPwr);

                // Just composing a Class-Qualified Device Name, to pass on to SetDevicePower
pszAdapter = new TCHAR[wcslen(InterfaceName)+1];
wcscpy_s(pszAdapter,wcslen(InterfaceName)+1,InterfaceName);
nChars = _sntprintf_s(szName,
                                                             MAX_PATH-1,
                                                             MAX_PATH-1,
                                                             _T("%s\\%s"),
                                                           PMCLASS_NDIS_MINIPORT,
                                                           pszAdapter);
szName[MAX_PATH-1]=0;

DWORD res = 0;
if(bEnable)
{
res = SetDevicePower(szName, 
                                                                     POWER_NAME, 
                                                                    PwrDeviceUnspecified);
}
else
{
res = SetDevicePower(szName, 
                                                                      POWER_NAME, 
                                                                      D4);
}

// TODO: take care of the return value 'res'

StringCchCopy(multiSz, (cbBuffer / sizeof(TCHAR))-1, pszAdapter);
multiSz[::_tcslen(multiSz)+1] = _T('\0'); // Multi sz needs an extra null

BOOL bRes;

if (bEnable)
{
bRes = DoNdisIOControl(IOCTL_NDIS_BIND_ADAPTER,
                                                                          multiSz,
                                                                          (_tcslen(multiSz)+2) * sizeof(TCHAR),
                                                                         NULL,NULL);
}
else
{
bRes = DoNdisIOControl(IOCTL_NDIS_UNBIND_ADAPTER,
                                                                            multiSz, 
                                                                           (_tcslen(multiSz)+2) * sizeof(TCHAR), 
                                                                          NULL,NULL);
}
// TODO: take care of the return value 'bRes '
}

}
While changing the power state of a device, be sure for bind and unbind an adapter so that NDIS can take care of the protocol drivers. 

Wednesday, September 17, 2008

Adding Low Power modes in PXA - P4

So far,we have discussed about how to put pxa into low power mode, how to wake it up, and briefly on how operating system restore things to the prior state after a transition to low power mode. Now lets put the bits and pieces together and see how windows ce does the low power mode transition in case of PXA.


As I have mentioned in the previous posts, we need to add the wakeup configuration first and then put the processor in to the low power mode.

For purpoes of explaining the concept, I will how to add a wakeup for GPIO 11 which is connected to the activity line of a communication module.

Adding the wakeup source
1) In OAL interrupt code(intr.c and bsp_cfg.h), add the code for creating a new interrupt corresponding to the wakeup source.

bsp_cfg.h - Add the new sysintr for the wakeup line

....
#define SYSINTR_COMMMOD_WAKEPUP (SYSINTR_FIRMWARE+22)
....


intr.c - map the sysintr to gpio 11 and add code for managing that
BOOL BSPIntrInit()
{
....
OALIntrStaticTranslate (SYSINTR_COMMMOD_WAKEPUP,IRQ_GPIOXX_2_GPIO11);
....
}


2) In OAL power code (power.c), add code for setting the pwer register. In mainstone bsp, the wakeups are register while OS starts (BSPPowerOffInit function)and the pwer value is written only while going into low power mode (BSPPowerOff).

BSPSetWakeSrc function - add a case for gpio 11 and set the gpio11 bit in the pPwer global variable. This variable will be written to the actual PWER register while going into suspend.

......
case IRQ_GPIOXX_2_GPIO11:
pPwer->gpio11 = 1;
OALMSG(1, ( L"BSPSetWakeSrc: IRQ Communication module source\r\n", irq));
break;
......


BSPGetWakeSrc function - add case for gpio 11 here also.This function to return the IRQ value if the PEDR (Power manager edge detect register) bit for gpio 11 is set.

....
else if (pPedr->gpio11)
{
*pSrcIrq = IRQ_GPIOXX_2_GPIO11;
}
....

BSPPowerOffInit function - use the OALIoCtlHalEnableWake function to set the global variable (g_oalWakeMask, an array) with the sysintr value corresponding to gpio11.
UINT32 commSysIntr = SYSINTR_COMMMOD_WAKEPUP;
...

OALIoCtlHalEnableWake(IOCTL_HAL_ENABLE_WAKE, &commSysIntr, sizeof(commSysIntr), NULL, 0, NULL);
...



While the BSPPowerOff function finds out which sysintr is mapped as a wakeup using OALPowerWakeSource and OALIntrTranslateSysIntr functions and sets the pwer bits with BSPSetWakeSrc function call.

When system resumes, BSPGetWakeSrc is used to get the wake cause and is stored in g_oalWakeSource variable which can be retrieved using kernel ioctl IOCTL_HAL_GET_WAKE_SOURCE



Wakeup Sources

When PXA is in low power modes such as Standby, Sleep or DeepSleep, the processor can be brought out using "Wakeups".

Wakeup is an "external or selected internal event". For example, assume that the power button is connected to GPIO0 of the processor, pressing of power button can be a wakeup source to the processor. Not all the GPIOs can act as wakeup for processor. For PXA the GPIO <116,> can act as wakeup sources. Also there are other wakeup sources such as USB host & client, RTC are wakeup sources.

Now we may not need to wakeup the processor for activity from any of these sources. Hence we can select the required wakeup source using the Power Manager Wake-up Enable (PWER) register. Also for GPIO wakeup sources, the edges needs to be configured using the Power Manager Rising Edge Detect Enable (PRER) and Falling Edge Detect Enable (PFER) register, so that wakeup will only happen for the selected edge of the transition on the GPIO.

NOTE: Take special care in configuring the direction of the GPIO which is chosen as a wakeup. If a wakeup source is configured for a GPIO which is configured as output, some undesirable results can happen.

For keypad and usim wakup there are seperate set of registers too for configuring specifics.

So in short, while configuring a gpio wake source in pxa, the following things should be taken into consideration:
  • PWER, selecting the wakeup source
  • PFER and PRER, fo which edge of GPIO transition , system needs to wakeup
  • GPDR, gpio should be configured as input

Tuesday, September 16, 2008

When things go wrong....

I found a useful post by Bruce Eitman, regarding how to debug some NOT SO FRIENDLY issues like data aborts etc.

http://geekswithblogs.net/BruceEitman/archive/2008/07/08/summary-of-when-things-go-wrong.aspx

Power Modes in PXA

PXA has the following power modes
  • Normal mode
  • Idle mode
  • Deep idle mode
  • Standby mode
  • Sleep mode
  • Deep Sleep mode
Each mode is characterized by the power domains that will be kept active and the clocks that will be running. For example in Sleep mode all the external low voltage power domains (i.e. domains controlled by PWR_EN pin of PXA) is disabled. While on the other hand, in Deep Sleep mode both the external low voltage and high voltage power domains are disabled as both PWR_EN and SYS_EN pins are de-asserted while entering the low power mode. The choice of which power mode to use for each depends solely on the power consumption requirements of the product.

In the beginning of the project, we mapped the Standby mode of PXA to POWER_STATE_SUSPEND state of Windows CE. But as we had a tight power consumption requirement for the Suspend mode, we had to re map the state to Sleep mode of PXA instead of standby.

Transition of Power Modes

For initiating a power mode transition in PXA, we need write in to the coprocessor 14 , register C7 (PWRMODE). Writing the bit pattern for each pattern corresponding to the power mode in to the PWRMODE[M] bits initiates the transition. For eg:

XllpPmEnterDeepSleep FUNCTION

ldr r3, =0x00000007
mcr p14, 0, r3, c7, c0, 0
nop
.......


A call to this assembly routine will initiate a transition to deep sleep mode.

Context Restoration


While going into low power modes such as Sleep or Deep Sleep, the processor contents can get corrupted.

Lets take the example of Sleep mode. In Sleep mode, the PC (Program Counter) value is corrupted along with some other set of registers. So in order to tackle this situation, operating systems are doing a context storage and restoration while going into low power mode. It stores the status of all the required registers such as GPIO configurations, level, stack address, core configuration registers in to a memory area. The address of this memory area is kept in a register which will be retained during the low power mode. In the case of PXA, it is the Power Manager Scratch Pad Register (PSPR). This register value will be maintained during a sleep reset, but will be cleared during GPIO, power on , hardware or watch dog resets.

So while going in to sleep mode, the required information is stored in a data area in RAM and the address to this area is stored in PSPR register. While resuming from sleep mode, the boot loader will check the PSPR register and will restore the configuration that is stored in the data area.

NOTE: bear in mind that the data area is in RAM, so the RAM needs to be properly initialized after a sleep reset, otherwise the boot loader can hand while trying to access the restoration area.

Monday, September 15, 2008

Adding Low Power modes in PXA - P1

For the windows ce project that we were working on had a lot of power states. As it was a PXA based board, with Mainstone III as the reference platform, we had to map these states to that of PXA.

The low power mode mappings were chosen as below:

  • POWER_STATE_SUSPEND - Sleep mode of PXA
  • POWER_STATE_OFF - Deep Sleep mode PXA
As you may have noticed in the Mainstone BSP's OAL code (the common soc code for pxa, i.e., WINCE600\PLATFORM\COMMON\SRC\SOC\PXA27X_MS_V1\OAL\POWER\off.c), the OemPowerOff Function uses the Deep Sleep mode of PXA. This function will be invoked from kernel when power manager issues a power off system.

Now with the new requirement for our software, we had to modify the existing OAL code and the power manager to add and map the new states to that of the PXA.

In this post I will walk you through on steps involved in adding a wakeup source in OAL.

We can splits the process in to two parts
  1. Mapping a SYSINTR value to the wakeup
  2. Adding the wakeup configuration specific to the processor
The detailed steps, I will explain in next post...

Its one of the most helpful tools that I have used in WinCE BSP development. Celog is an event-tracking tool that logs a set of predefined ( I think configured or filtered would be an apt word) for this) CeLog collects the data given by various sources such as applications or drivers in a location configured in registry.

Using tools such as CeLogFlush.exe, we can periodically collect the data and can be analyzed in a development workstation.
A Special Thanks to Sue Loh, for posting a really helpful and detail documentation in her blog in MSDN for using CeLog along with tools such as kernel tracker.
While working on the wrist pc project, we were experiencing some unexpected slow of system while booting up and running applications and we were pondering in dark just to figure out the cause of the snail's pace performance. Then I came across the tutorial given by Sue Loh in her microsoft blog on using CeLog on a standalone machine.

Previously I had used Remote Kernel Tracker, which is built into the platform builder for connecting to a development board using CoreCon and use CeLog to gather Kernel Data (such as interrupt, thread switching, events and other information), on a real time basis (well pretty close to real time). But for this the board needs to be connected to over ethernet or serial to a development work station.

Most of the times problems will arise while testing under real scenarios. So debugging such issues, I came up with a solution. I made a CAB file with the following things:
  • celog.dll
  • celogflush.exe
  • ITaskMgr_arm.exe (A free task manager I found in net, http://urana.info/mobile/wince/itaskmgr/index.html)
  • oscapture.exe
(Also added shortcuts in start menu to launch the exes too, just for the ease of use)

I distributed this cab file among my team and asked them to install in all the boards that we were using. So at any point of the system is showing any slowness, start the 'CeLogFlush.exe' application, which will periodically dump the performance data on to a file. After significant data is gathered, they can use the ITaskMgr_arm.exe to kill the CeLogFlush flush instance and then take the dumped file.

This file can be opened in Kernel tracker to view the information.

In later posts I will explain in detail about CeLogFlush and other tools for performance monitoring.

Regarding how to analyze the performance data, Sue has put up a detailed tutorial in her blog.

In my previous post, I have explained how to add an interrupt when source code is available.

Now I will explain how to handle an interrupt by writing an IST in the driver. For the reason of continuity, I am using the same power button driver and the Wireless Activity Interrupt (as I explain with in post "Adding Interrupt in OAL").

1. Create an even to be associated with the interrupt

HANDLE g_WlanActiveEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if( !g_WlanActiveEvent)
{
RETAILMSG(1, (TEXT("WlanActivityMonitorStart: Failed to create Intterupt Event\r\n")));
goto CleanUp;
}

2. Associate the Interrupt with the event using SYSINTR value
UINT32 g_SysIntr = SYSINTR_WLAN;
......
result = InterruptInitialize(g_SysIntr, g_WlanActiveEvent, NULL, 0);

if(!result)
{
RETAILMSG(1, (TEXT("WlanActivityMonitorStart: InterruptInitialize() failed. GetLastError=0x%x\r
\n"),GetLastError()));

goto CleanUp;
}

3. Start a thread for handling the interrupt.
g_WlanThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) WlanThread, NULL, 0, NULL);
if ( g_WlanThread == NULL )
{
RETAILMSG(1, (TEXT("WlanActivityMonitorStart: Failed to create WlanActive
Intterupt Thread\r\n")));
goto CleanUp;
}


4. In the thread, wait for the event to happen using the WaitForSingleObject or WaitForMultipleObjects API.
INT WlanThread (VOID)
{
while(!g_FlagExitThrd)
{
WaitForSingleObject(g_WlanActiveEvent, INFINITE);
.............
}
return ERROR_SUCCESS;
}

5. Perform the necessary operations to service that interrupt. Here I am pulsing another event to notify some other sets of appication.
WaitForSingleObject(g_WlanActiveEvent, INFINITE);
InterruptMask(g_SysIntr,TRUE);

if (g_WlanEvent)
{
RETAILMSG(1, (TEXT("WlanThread: Intterupt \r\n")));
PulseEvent(g_WlanEvent);
}

Sleep (1000); // Sleep to ignore some activity as this line toggles a lot
InterruptMask(g_SysIntr,FALSE);
6. Signal the completion of interrupt handling using InterruptDone
InterruptDone(g_SysIntr);



courtesy : Mrs. PV & Mappila sir

While going into low power modes of windows while coming back up, restore the processor and peripherals back to the state it was before.

One problem that we had faced in our project was regarding the restoration of PCCARD timings. Although we were using the original MainstoneIII BSP in Wince6, we had to modify the boot loader and other startup initializations to match the new board which we were designing. On doing so, we commented out the PCCARD timing initializations in xlli_low_level_init.s file.

On PCCARD socket, we had a Wireless LAN card, which was working properly. But after a suspend-resume cycle, the network adapter was shown as down in the drivers. We couldn't figure out what was the reason for this behaviour. Wasted a lot of precious time in debugging the issue. Using a debug version of the driver from vendor, we were able to figure out that after a suspend-resume cycle, the driver was not able to access the card.

So we decided to try a reinitialization of the full PCCARD driver. On trying the pccard and wlan was back up in working state. But the entire driver initialization was slowing down the resuming of the system.

We again analyzed the reinitialization process, then it hit me. I decided to access the pccard attribute memory immediatly after the resuming in OAL. There I came to realize that the problem is way before the OS.

We did a walk though of the Boot loader code, i.e. the path followed during a sleep reset and found out that the section of code for re-initialization of pccard was commented.

Although we fixed the problem, we had to waste a lot of precious project development time in debugging the issue.

So when dealing with low level initializations in bootloader or restoration code, be careful what you are modifying. One way or the other that will come back to haunt you :)


Sunday, September 7, 2008

Some useful videos from Microsoft Channel9

Here are links to some really good and informational videos, hosted in microsoft channel.
For me, they were really helpful in building up the basic of the windows ce system architecture.

http://channel9.msdn.com/posts/TravisHobrla/Porting-Drivers-to-Windows-CE-60/

http://channel9.msdn.com/posts/Rory/Windows-CE-Windows-XP-Embedded-and-Windows-Mobile-Explained/

http://channel9.msdn.com/posts/Charles/Bor-Ming-Hsieh-and-Sue-Loh-3rd-Generation-Kernel-for-Windows-CE/

http://channel9.msdn.com/posts/Charles/Juggs-Ravalia--Windows-CE-60-Device-Driver-Model/

When we are having the whole BSP source, there will be cases in which we may need to add interrupt in OAL. This can be accommodate a new driver or add a new functionality to the existing one.

For explaining the procedure I am using the Mainstone BSP that is available in windows ce. Lets assume that we want to add an interrupt for Wireless LAN activity. This interrupt line will be a GPIO line which is mapped to GPIO 91 of PXA.

These are the files that need modification:
* bsp_cfg.h (Src\Inc)
* intr.c (OAL)

In 'bsp_cfg.h', you need to define a SYSINTR (if needed) for the new interrupt that you are about to add.

.............
#define SYSINTR_PWRBTN (SYSINTR_FIRMWARE+14) // 30
#define SYSINTR_WLANACTIVE (SYSINTR_FIRMWARE+15) // 31 - The new interrupt
................


Now in OAL, intr.c, make the following changes:
1. In BSPIntrInit() function, add a call to OALIntrStaticTranslate() to map the
the new SYSINTR v alue to the GPIO.
...............
OALIntrStaticTranslate (SYSINTR_WLAN,IRQ_GPIOXX_2_GPIO91);
...............


2. In BSPIntrEnableIrq(), add the case of enabling the GPIO Interrupt
.................
else if (irq == IRQ_GPIOXX_2_GPIO91)
{
EnableGPIO91Irq(); // define this function for enabling falling or rising
//edge detect and clear edge detect
}

.................


3. In BSPIntrDisableIrq(), add the case of enabling the GPIO Interrupt
.................
else if (irq == IRQ_GPIOXX_2_GPIO91)
{
DisableGPIO91Irq(); // define this function for clearing falling rising
//edge detect so that interrupt wont come
}

.................

4. Define the functions for enabling and disabling the gpio interrupt
............
//~~ Function for disabling gpio91 interrupt
static void EnableGPIO91Irq(void)
{
SETREG32((PULONG)&g_pGPIORegs->GRER2, XLLP_BIT_27);
}

//~~ Function for enabling gpio91 interrupt
static void DisableGPIO91Irq(void)
{
CLRREG32((PULONG)&g_pGPIORegs->GEDR2, XLLP_BIT_27);
CLRREG32((PULONG)&g_pGPIORegs->GRER2, XLLP_BIT_27);
}


This is how we add a new interrupt in OAL.

My friend mukesh has written in his blog, how to dynamically add an interrupt handler, when OAL source code is not available.

In the next posts, I will explain how to add an IST (Interrupt Service Thread) to take care of this interrupt.

Power manager uses IOCTLs to interact with Power-Managed devices. So inorder to make a device power managed implement certain set of IOCTLs in the driver with registry changes.

For the purpose of explaining, i am taking the sample of the Power Button driver implemented in Mainstone platform available in windows ce 6.0. Please bear in mind that this driver is already having a function which monitors system power state transitions using Notification Queues. But I am not going to make use of them.

Here I will briefly describe the steps involved in doing the same. We can divide the changes in to two parts:
  1. Change in registry settings for driver
  2. Implementing IOCTLs

Change in registry settings for driver

Now as the documentation for power manager, each power managed device needs to be classified under a certain category i.e Generic power-manageable devices, Power-manageable block devices and Power-manageable NDIS miniports. Each class is identified by a certain GUID(defined in pm.h). Associating a device to a power manager class is really easy, just by adding a registry entry to the device driver entry, as follows.

I will make this power button driver as a generic device
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\PWB]
"Prefix"="PWB"
"Dll"="mainstoneii_pwrbutton.dll"
"Order"=dword:3
"IClass"="{A32942B7-920C-486b-B0E6-92A702A99B35}" ;; we are making it as generic device over here

When device manager loads the driver, it will associate the device to the class.

Now power manager will start issuing the IOCTLs to the driver and query the capabilities of the driver. The details I will explain in the next section.
Note : We should atleast implement IOCTL_POWER_CAPABILITIES in a driver to make it power managed

Implementing IOCTLs

The driver needs to implement the following IOCTLs for interacting with power manager:
  • IOCTL_POWER_CAPABILITIES **
  • IOCTL_POWER_GET
  • IOCTL_POWER_QUERY *
  • IOCTL_POWER_SET
  • IOCTL_REGISTER_POWER_RELATIONSHIP *
** - This IOCTL is mandatory for the entire thing to work
* - rather optional


When power manager receives a request to register a device driver under a particular device class, it first passes IOCTL_POWER_CAPABILITIES to the driver. If the call returns false, the driver wont be registered otherwise it will be. This IOCTL should return the device capabilities.

BOOL IOControl( ............. )
{

................
case IOCTL_POWER_CAPABILITIES :
if (NULL != pOutBuf)
{
memcpy(pOutBuf, &PWBDrvPowerCaps, sizeof(PWBDrvPowerCaps));
*pdwBytesTransferred = sizeof(PWBDrvPowerCaps);
OutBufLen = sizeof(PWBDrvPowerCaps);
dwErr = ERROR_SUCCESS;
}
else
{
dwErr = ERROR_INVALID_PARAMETER;
}
break; // IOCTL_POWER_CAPABILITIES
...............
if(dwErr != ERROR_SUCCESS) {
bRc = FALSE;
} else {
bRc = TRUE;
}

return bRc;

}


The variable PWBDrvPowerCaps contains the device power capabilities of the driver in a POWER_CAPABILITIES structure.

POWER_CAPABILITIES PWBDrvPowerCaps =
{
DX_MASK(D0) | DX_MASK(D2) | DX_MASK(D3) | DX_MASK(D4),
0,
0,
{
0x00000000, // D0
0x00000000, // D1
0x00000000, // D2
0x00000000, // D3
0x00000000 // D4
},
{
0x00000000, // D0
0x00000000, // D1
0x00000000, // D2
0x00000000, // D3
0x00000000 // D4
},
0
};


For information on the meaning of each parameter please refer MSDN. After returning true from the IOCTL, power manager will register this device under the requested class in registry.

From now on wards, when ever a state change occurs (or is requested) for the device or system, power manager will send the IOCTL_POWER_SET to the driver and the driver can take care of the same.

case IOCTL_POWER_SET:
if ( pOutBuf != NULL )
{
SetPwbPowerState(*(PCEDEVICE_POWER_STATE) pOutBuf);
dwErr = ERROR_SUCCESS;
}
else
dwErr = ERROR_INVALID_PARAMETER;
break; // IOCTL_POWER_SET


In SetPwbPowerState(), you can define the power depending on the paramater.