电池驱动介绍

来源:互联网 发布:mac屏幕尺寸 编辑:程序博客网 时间:2024/05/04 11:56

 

电池驱动介绍

一.整体框架

         电池驱动代码量很小,可是麻雀虽小,五脏俱全。与其他的很多Driver一样,分为PDD+MDD层,双层之间通过PDD的如下导出接口相联系。    

Programming element

Description

BatteryDrvrGetLevels

This function returns the number of levels that the battery driver is capable of returning in the BatteryFlag and BackupBatteryFlag members of the SYSTEM_POWER_STATUS_EX2 structure.

BatteryDrvrSupportsChangeNotification

This function indicates whether the battery driver can report whether the batteries were changed.

BatteryGetLifeTimeInfo

This function retrieves the time the user changed the batteries, the amount of time they used the batteries, and the amount of time they used the batteries before replacing them.

BatteryNotifyOfTimeChange

This function adjusts times to account for the user changing the real time.

BatteryPDDDeinitialize

This function allows the battery PDD to perform hardware-specific cleanup.

BatteryPDDGetLevels

This function indicates how many battery levels are reported in the BatteryFlag and BackupBatteryFlag members of the SYSTEM_POWER_STATUS_EX2 structure filled in by BatteryPDDGetStatus.

BatteryPDDGetStatus

This function obtains the most current battery and power status available on the platform. It fills in the structures pointed to by its parameters.

BatteryPDDInitialize

This function allows the battery PDD to perform hardware-specific initialization.

BatteryPDDPowerHandler

This power callback performs hardware-specific processing for the battery driver.

BatteryPDDResume

This function performs hardware-specific battery processing in a thread context following system resume.

BatteryPDDSupportsChangeNotification

This function indicates whether the battery driver can report whether the batteries were changed.

PFN_BATTERY_PDD_IOCONTROL

This function signature is for the battery driver custom IOCTL handler. It implements the optional PDD IOCTL interface.

         微软提供了电池驱动的Sample Code,从目录/WINCE600/PUBLIC/COMMON/OAK/DRIVERS

/BATTDRVR下可以找到。

         注册表的配置如下:

IF BSP_NOBATTERY !

 

; HIVE BOOT SECTION

 

[HKEY_LOCAL_MACHINE/System/Events]

    "SYSTEM/BatteryAPIsReady"="Battery Interface APIs"

 

; END HIVE BOOT SECTION

 

; These registry entries load the battery driver.  The IClass value must match

; the BATTERY_DRIVER_CLASS definition in battery.h -- this is how the system

; knows which device is the battery driver.  Note that we are using

; DEVFLAGS_NAKEDENTRIES with this driver.  This tells the device manager

; to instantiate the device with the prefix named in the registry but to look

; for DLL entry points without the prefix.  For example, it will look for Init

; instead of BAT_Init.  This allows the prefix to be changed in the registry (if

; desired) without editing the driver code.

[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/Battery]

   "Prefix"="BAT"

   "Dll"="battdrvr.dll"

   "Flags"=dword:8                      ; DEVFLAGS_NAKEDENTRIES

   "Order"=dword:0

   "IClass"="{DD176277-CD34-4980-91EE-67DBEF3D8913}"

 

ENDIF BSP_NOBATTERY !

         PDD层基本上就是实现电池电量的采集和电池一些其它基本信息的获取,而MDD层主要是建立了一个线程BatteryThreadProc,该线程用来和控制面板中电源管理小工具进行通信。

二.值得一说的问题

         电池驱动实在是太简单了,没啥可介绍的。

         就说说常见的问题吧。

1.电池驱动的导出接口没有Prefix

         一般的流接口驱动导出接口都会有一个前缀,即注册表中配置的Prefix的值,为三个字节的大写字母。

         这是注册表中”Flags”的值配置成8DEVFLAGS_NAKEDENTRIES)引起的,在这种情况下Device Manager操作流接口驱动程序的时候,就可以不需要Prefix前导符号。

         详细的”Flags”的配置如下:

Flag

Value

Description

DEVFLAGS_NONE

0x00000000

No flags are defined.

DEVFLAGS_UNLOAD

0x00000001

Driver unloads after a call to the XXX_Init entry point or after the XXX_Init entry point returns. No error code is returned.

Bus Enumerator typically runs with this flag.

DEVFLAGS_LOADLIBRARY

0x00000002

Driver is loaded with LoadLibrary instead of LoadDriver.

DEVFLAGS_NOLOAD

0x00000004

Driver is not loaded.

DEVFLAGS_NAKEDENTRIES

0x00000008

Driver entry points do not have a XXX Prefix prepended.

DEVFLAGS_BOOTPHASE_1

0x00001000

Driver is loaded during boot phase one. By default, device drivers are loaded during boot phase two.

Boot phase zero is before the Device Manager loads.

Boot phase one is to find the registry.

Boot phase two is when initial device drivers load.

Boot phase three is after initial device drivers load.

DEVFLAGS_IRQ_EXCLUSIVE

0x00000100

Driver loads only when it has exclusive access to the IRQ.

DEVFLAGS_TRUSTEDCALLERONLY

0x00010000

Driver can only be opened by a trusted application.

         接下来,我们从Device Manager的代码中找到其原因。通常在应用程序或者一些Driver中尝试去动态加载流驱动的话,可以去调用API ActivateDeviceEx(),其实该函数最终调用的就是Device Manager中的函数I_ActivateDeviceEx()

         在文件/WINCE600/PRIVATE/WINCEOS/COREOS/DEVICE/DEVCORE/ devload.c中可以找到函数I_ActivateDeviceEx()的具体实现,该函数主要完成驱动程序对应的DLL的加载和初始化函数的调用,它首先会去调用函数CreateDevice()创建设备的一些结构体信息。在函数CreateDevice()中可以找到对”Flags”的一些判断处理。

         代码如下:

// This routine allocates a device driver structure in memory and initializes it.

// As part of this process, it loads the driver's DLL and obtains pointers to

// its entry points.  This routine returns a pointer to the new driver description

// structure, or NULL if there's an error.

static fsdev_t *

CreateDevice(

    LPCWSTR lpszPrefix,

    DWORD dwIndex,

    DWORD dwLegacyIndex,

    DWORD dwId,

    LPCWSTR lpszLib,

    DWORD dwFlags,

    LPCWSTR lpszBusPrefix,

    LPCWSTR lpszBusName,

    LPCWSTR lpszDeviceKey,

    HANDLE hParent

    )

{

    fsdev_t *lpdev;

    DWORD dwSize;

    DWORD dwStatus = ERROR_SUCCESS;

    WCHAR szDeviceName[MAXDEVICENAME];

    WCHAR szLegacyName[MAXDEVICENAME];

 

    DEBUGCHK(lpszPrefix != NULL);

    DEBUGCHK(lpszLib != NULL);

    DEBUGCHK(wcslen(lpszPrefix) <= 3);

    DEBUGCHK(dwLegacyIndex == dwIndex || (dwLegacyIndex == 0 && dwIndex == 10));

    DEBUGCHK(lpszBusName != NULL);

 

    // figure out how much memory to allocate

    dwSize = sizeof(*lpdev);

 

    // is the device named?

    if(lpszPrefix[0] == 0) {

        // unnamed device

        szDeviceName[0] = 0;

    } else {

        // named device, allocate room for its names

        StringCchPrintf(szDeviceName,MAXDEVICENAME,TEXT("%s%u"), lpszPrefix, dwIndex);

        if(dwLegacyIndex <= 9) {

            // allocate room for name and null

            StringCchPrintf(szLegacyName, MAXDEVICENAME, L"%s%u:", lpszPrefix, dwLegacyIndex);

            dwSize += (wcslen(szLegacyName) + 1) * sizeof(WCHAR);

        }

 

        // allocate room for name and null       

        dwSize += (wcslen(szDeviceName) + 1) * sizeof(WCHAR);

    }

 

    // If the bus driver didn't allocate a name the device may still support the

    // bus name interface -- use its device name (if present) just in case.

    if(lpszBusName[0] == 0 && szDeviceName[0] != 0) {

        lpszBusName = szDeviceName;

    }

   

    // allocate room for the bus name

    if(lpszBusName[0] != 0) {

        dwSize += (wcslen(lpszBusName) + 1) * sizeof(WCHAR);

    }

 

    // make room to store the device key as well

    if(lpszDeviceKey != NULL) {

        dwSize += (wcslen(lpszDeviceKey) + 1) * sizeof(WCHAR);

    }

   

    // allocate the structure

    if (!(lpdev = LocalAlloc(0, dwSize))) {

        DEBUGMSG(ZONE_WARNING, (_T("DEVICE!CreateDevice: couldn't allocate device structure/r/n")));

        dwStatus = ERROR_OUTOFMEMORY;

    } else {

        LPCWSTR pEffType = NULL;

        LPWSTR psz = (LPWSTR) (((LPBYTE) lpdev) + sizeof(*lpdev));

        memset(lpdev, 0, dwSize);

        lpdev->dwId = dwId;

        lpdev->wFlags = 0;           

        lpdev->dwFlags = dwFlags;

        if(PSLGetCallerTrust() == OEM_CERTIFY_TRUST) {

            lpdev->wFlags |= DF_TRUSTED_LOADER;

            lpdev->hParent = hParent;

        }

        if (lpszPrefix[0] != 0) {

            if(dwLegacyIndex <= 9) {

                lpdev->pszLegacyName = psz;

                wcscpy(lpdev->pszLegacyName, szLegacyName);

                psz += wcslen(lpdev->pszLegacyName) + 1;

            }

            lpdev->pszDeviceName = psz;

            wcscpy(lpdev->pszDeviceName, szDeviceName);

            psz += wcslen(lpdev->pszDeviceName) + 1;

        }

        if(lpszBusName[0] != 0) {

            lpdev->pszBusName = psz;

            wcscpy(lpdev->pszBusName, lpszBusName);

            psz += wcslen(lpszBusName) + 1;

        }

        if(lpszDeviceKey != NULL) {

            lpdev->pszDeviceKey= psz;

            wcscpy(lpdev->pszDeviceKey, lpszDeviceKey);

            psz += wcslen(lpszDeviceKey) + 1;

        }

        if((dwFlags & DEVFLAGS_NAKEDENTRIES) == 0) {

            if(lpszPrefix[0] != 0) {

                DEBUGCHK(lpszBusPrefix[0] == 0 || wcsicmp(lpszBusPrefix, lpszPrefix) == 0);

                pEffType = lpszPrefix;      // use standard prefix decoration

            } else if(lpszBusPrefix[0] != 0 && lpdev->pszBusName != NULL) {

                pEffType = lpszBusPrefix;   // no standard prefix, use bus prefix decoration

            } else {

                if(lpdev->pszDeviceName != NULL) {

                    // device is expected to have a device or bus name, but we don't know

                    // how to look for its entry points

                    DEBUGMSG(ZONE_ACTIVE || ZONE_ERROR,

                        (_T("DEVICE!CreateDevice: no entry point information for '%s' can't load '%s'/r/n"),

                        lpszLib, lpdev->pszDeviceName));

                    dwStatus = ERROR_INVALID_FUNCTION;

                }

            }

        }

        // 这里会去判断Flags的值,决定是否使用Prefix

        if ((dwFlags & DEVFLAGS_LOAD_AS_USERPROC)) {

            lpdev->hLib = NULL;

            lpdev->dwData  = Reflector_Create(lpszDeviceKey, pEffType, lpszLib, dwFlags );

            if (lpdev->dwData != 0 ) {

                lpdev->fnInit = NULL;

                lpdev->fnInitEx = (pInitExFn)Reflector_InitEx;

                lpdev->fnPreDeinit = (pDeinitFn)Reflector_PreDeinit;

                lpdev->fnDeinit = (pDeinitFn)Reflector_Deinit;

                lpdev->fnOpen = (pOpenFn)Reflector_Open;

                lpdev->fnPreClose = (pCloseFn)Reflector_PreClose;

                lpdev->fnClose = (pCloseFn)Reflector_Close;

                lpdev->fnRead = (pReadFn)Reflector_Read;

                lpdev->fnWrite = (pWriteFn)Reflector_Write;

                lpdev->fnSeek = (pSeekFn)Reflector_SeekFn;

                lpdev->fnControl = (pControlFn)Reflector_Control;

                lpdev->fnPowerup = (pPowerupFn)Reflector_Powerup;

                lpdev->fnPowerdn = (pPowerupFn)Reflector_Powerdn;

            }

            else {

                DEBUGMSG(ZONE_WARNING, (_T("DEVICE!CreateDevice: couldn't load(%s) to user mode!!/r/n"),lpszLib));

                dwStatus = ERROR_FILE_NOT_FOUND;

            }

        }

        else {

            DEBUGMSG(ZONE_ACTIVE, (_T("DEVICE!CreateDevice: loading driver DLL '%s'/r/n"), lpszLib));

            // 这里去判断的如何去加载Stream Driver,其实这里最终会影响到驱动DLL需要的内存的Page-inpage-out,这些不是这里要说的重点,暂且不说

            lpdev->hLib =

                (dwFlags & DEVFLAGS_LOADLIBRARY) ? LoadLibrary(lpszLib) : LoadDriver(lpszLib);

            if (!lpdev->hLib) {

                DEBUGMSG(ZONE_WARNING, (_T("DEVICE!CreateDevice: couldn't load '%s' -- error %d/r/n"),

                    lpszLib, GetLastError()));

                dwStatus = ERROR_FILE_NOT_FOUND;

            } else {

                // 最终如果配置为0x08,程序将会走到这里

                lpdev->fnInitEx = NULL;

                lpdev->fnInit = (pInitFn)GetDMProcAddr(pEffType,L"Init",lpdev->hLib);

                lpdev->fnPreDeinit = (pDeinitFn)GetDMProcAddr(pEffType,L"PreDeinit",lpdev->hLib);

                lpdev->fnDeinit = (pDeinitFn)GetDMProcAddr(pEffType,L"Deinit",lpdev->hLib);

                lpdev->fnOpen = (pOpenFn)GetDMProcAddr(pEffType,L"Open",lpdev->hLib);

                lpdev->fnPreClose = (pCloseFn)GetDMProcAddr(pEffType,L"PreClose",lpdev->hLib);

                lpdev->fnClose = (pCloseFn)GetDMProcAddr(pEffType,L"Close",lpdev->hLib);

                lpdev->fnRead = (pReadFn)GetDMProcAddr(pEffType,L"Read",lpdev->hLib);

                lpdev->fnWrite = (pWriteFn)GetDMProcAddr(pEffType,L"Write",lpdev->hLib);

                lpdev->fnSeek = (pSeekFn)GetDMProcAddr(pEffType,L"Seek",lpdev->hLib);

                lpdev->fnControl = (pControlFn)GetDMProcAddr(pEffType,L"IOControl",lpdev->hLib);

                lpdev->fnPowerup = (pPowerupFn)GetDMProcAddr(pEffType,L"PowerUp",lpdev->hLib);

                lpdev->fnPowerdn = (pPowerdnFn)GetDMProcAddr(pEffType,L"PowerDown",lpdev->hLib);

 

                // Make sure that the driver has an init and deinit routine.  If it is named,

                // it must have open and close, plus at least one of the I/O routines (read, write

                // ioctl, and/or seek).  If a named driver has a pre-close routine, it must also

                // have a pre-deinit routine.

                if (!(lpdev->fnInit && lpdev->fnDeinit) ||

                    lpdev->pszDeviceName != NULL && (!lpdev->fnOpen ||

                                 !lpdev->fnClose ||

                                 (!lpdev->fnRead && !lpdev->fnWrite &&

                                  !lpdev->fnSeek && !lpdev->fnControl) ||

                                 (lpdev->fnPreClose && !lpdev->fnPreDeinit))) {

                    DEBUGMSG(ZONE_WARNING, (_T("DEVICE!CreateDevice: illegal entry point combination in driver DLL '%s'/r/n"),

                        lpszLib));

                    dwStatus = ERROR_INVALID_FUNCTION;

                }

 

                if (!lpdev->fnOpen) lpdev->fnOpen = (pOpenFn) DevFileNotSupportedBool;

                if (!lpdev->fnClose) lpdev->fnClose = (pCloseFn) DevFileNotSupportedBool;

                if (!lpdev->fnControl) lpdev->fnControl = (pControlFn) DevFileNotSupportedBool;

                if (!lpdev->fnRead) lpdev->fnRead = (pReadFn) DevFileNotSupportedDword;

                if (!lpdev->fnWrite) lpdev->fnWrite = (pWriteFn) DevFileNotSupportedDword;

                if (!lpdev->fnSeek) lpdev->fnSeek = (pSeekFn) DevFileNotSupportedDword;

            }

        }

    }

 

    // did everything go ok?

    if(dwStatus != ERROR_SUCCESS) {

        if(lpdev != NULL) {

            DeleteDevice(lpdev);

            lpdev = NULL;

        }

        SetLastError(dwStatus);

    }

 

    DEBUGMSG(ZONE_ACTIVE || (dwStatus != ERROR_SUCCESS && ZONE_WARNING),

        (_T("CreateDevice: creation of type '%s', index %d, lib '%s' returning 0x%08x, error code %d/r/n"),

        lpszPrefix[0] != 0 ? lpszPrefix : _T("<unnamed>"), dwIndex, lpszLib, lpdev, dwStatus));

 

    return lpdev;

}

//

// This routine is security check for dwInfo passed in by either RegistryDevice or ActiveDevice

DWORD CheckLauchDeviceParam(DWORD dwInfo)

{

    if (CeGetCallerTrust() != OEM_CERTIFY_TRUST) { // Untrusted caller will do following.

        LPCTSTR lpActivePath = (LPCTSTR) dwInfo; // We assume it is Registry Path.

        if (lpActivePath) {

            HKEY hActiveKey;

            if (RegOpenKeyEx( HKEY_LOCAL_MACHINE, lpActivePath, 0, 0, &hActiveKey) == ERROR_SUCCESS ) { // It is registry.

                // We need check Registry is in secure location.

                CE_REGISTRY_INFO regInfo;

                DWORD dwRet = ERROR_INVALID_PARAMETER;

                memset(&regInfo,0,sizeof(regInfo));

                regInfo.cbSize = sizeof(CE_REGISTRY_INFO);

                if (CeFsIoControl(NULL, FSCTL_GET_REGISTRY_INFO, &hActiveKey, sizeof(HKEY), &regInfo, sizeof(CE_REGISTRY_INFO), NULL, NULL)) { // Succeed

                    if (regInfo.dwFlags & CE_REG_INFO_FLAG_TRUST_PROTECTED) {

                        dwRet = ERROR_SUCCESS;

                    }

                }

                RegCloseKey( hActiveKey );

                return dwRet;

            }

        }

    }

    return ERROR_SUCCESS; 

}

         另外,注册表项”Flags”的值是函数I_ActivateDeviceEx()调用RegReadActivationValues()来获取的。篇幅有限,这里不再对函数RegReadActivationValues()进行解释。

         其实注册表项”Flags”的值很有用处,能够控制加载过程中的很多行为。

2.电池电量更新的问题

         CE5.0中,默认情况下系统会每0.5s去获取一次主电池和辅助电池的电量,其默认值得定义在文件battdrvr.c中,如下:

#define DEF_BATTERYPOLLTIMEOUT          500         // in milliseconds

 

         6.0中,可能微软也觉得0.5s获取一次电池电量有点变态,而且也没有必要,所以改为5s去获取一次电量,同样通过宏定义进行定义。

// 5 Seconds for average delay of 2.5 seconds

#define DEF_BATTERYPOLLTIMEOUT          (5*1000)         // in milliseconds

         当然,你可以通过注册表项"PollInterval"去配置这个值,如下:

[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/Battery]

"Prefix"="BAT"

"Dll"="battdrvr.dll"

"Flags"=dword:8                      ; DEVFLAGS_NAKEDENTRIES

"Order"=dword:45

"IClass"="{DD176277-CD34-4980-91EE-67DBEF3D8913}"

"PollInterval"=dword:1388

         其实,就算是用户去通过控制面板去查看电池电量,也没有必要把这个时间设置的很短。再者,一般的手持设备上都会去调用API  GetSystemPowerStatusEx()去获取电池电量百分比。

         毫无疑问,将更新电池电量的时间配置的越大越好,可是有些时候,我们可能希望在用户插入AC进行充电的时候,控制面板中的电池电量马上有反应,而不是在等待5s钟之后,这样显得产品更加人性化一点。这种情况下,可以通过使AC的插入产生一个中断,然后把这个中断和电池驱动MDD层中的线程BatteryThreadProc Event联系起来,这样就可以满足插入AC,控制面板中的电池电量马上就有反应的需求。

3.电池电量显示不准确

         最简单的电池电量的获取是通过当前AD值和满电量AD值相比得到的。举个例子,假设满电量的时候AD值为100,当前的值为50,则当前的电量就是50/100*100% = 50%,并通过PDD层函数BatteryPDDGetStatus()返回给上层调用者。

         实际使用中发现,电池电量为100%的时候很耐用,可是从电池电量80%左右的时候开始,电池电量很快就被消耗完毕。

         为什么?

         根本原因是,电池电量和电池电压并不成正比,而AD转换的结果恰巧反映的就是电池电压。

         了解了电池的充放电原理后发现,电池电量和电池电压的关系是一条抛物线。接下来就有两种改进的方法获取电池电量:

         第一种方法:得到电池电量和电池电压的准确关系。

         事先去测量电池电量和电池电压的关系,得到这条抛物线的方程,从而得到准确的电池电量和电池电压的关系。通常不是计算这条抛物线,而是将这条抛物线简单的分隔成几个段,近似的认为每一段是一条斜率恒定的直线,然后通过测量结果确定这几条直线的斜率以及合时的分隔点。

         做过衡器或者其它一些仪器仪表的朋友,对这种方法肯定不陌生,呵呵。

         这种做法的最大缺点是,每更换一种电池都需要重新的去测时充放电曲线,比较麻烦。优点是不用增加硬件成本。

         第二种方法:利用电源管理芯片。

         这种方法没用过,只是听有人在论坛上讨论过。利用一颗电源管理芯片去获取电池电量,其原理是通过AD转换值经过一套复杂的算法得到电量,搞不清楚,据说很好用。但是Cost呵呵

        

 

原创粉丝点击