以前也曾经写过类似的文章,但是每次调试到sd的时候都会有些许收获,在此就来个全乎的吧,哈哈!

一,  MS SD驱动架构

在代码分析之前很有必要先看一张图,这样至少可以大致了解模块的作用,也可以知道该模块内的源文件甚至函数是为谁服务的。

 

 

这张图,初次见到的时候没有怎么在意。不过通过阅读源代码的时候,才发现这张图真真切切的反映了真个代码的调用流程。很清晰而又简明直接的一张图。

从图上,我们可以看出从APP到硬件的执行有3层,首先hardware部分我们可以先不用理会,而clients的上半部分也可以先不用理会,因为那是MS在系统中已经做好了。好了,剩下的也就是图中的深红色的部分。

通常,对于软件驱动工程师来说,这剩下的部分可以分为3个层次:SDclientSDbusSDhcMSWince下的sd驱动也是这么划分并分目录存放的。源代码在\WINCE500\PUBLIC\COMMON\OAK\DRIVERS\SDCARD目录下。牛也吹了,下面的部分我们就分别来介绍这3层,主要以详细的函数调用流程为主,请在阅读本文的时候对照源代码一起分析。

一,  SD主机控制器驱动(SDHC

     首先在开始之前有必要说一下,这里之所以要先介绍SDHC驱动这部分,主要是因为,该层是直接与硬件SDHC打交道的,并且与上一层的SDBUS驱动交互。这里必然提供了很多接口来支持SDBUS的服务(当然SDBUS也提供了很多的接口来支持SDHC),从功能的实现方面讲,上层的功能函数调用最终是要调用到最底层驱动的函数,以实现与硬件的交互。

 

     地球人都知道,这些驱动最终展示给系统的真面目是DLL文件。那我们要分析它们的源代码也最好从它的入口函数DllEntry或者说从包含该函数的文件开始。为了具体一些,这里以s3c2440sdhc为例分析。Come on

[sdhc\sdhcbase\sdhcmain.cpp]

BOOL DllEntry(HINSTANCE hInstance, ULONG Reason, LPVOID pReserved)

{

    BOOL fRet = TRUE;

    if(Reason == DLL_PROCESS_ATTACH)

     {

        DEBUGREGISTER(hInstance);

        DisableThreadLibraryCalls( (HMODULE) hInstance );

        if( !SDInitializeCardLib() )

        {

            fRet = FALSE;

        }

        else if( !SD_API_SUCCESS( SDHCDInitializeHCLib() ) )

        {

            SDDeinitializeCardLib();

            fRet = FALSE;

        }

        g_fRegisteredWithBusDriver = FALSE;

    }

    if(Reason == DLL_PROCESS_DETACH)

     {   

        SDHCDDeinitializeHCLib();

        SDDeinitializeCardLib();

    }

    return(TRUE);

}

虽然DllEntry函数都是千片一律的,但是这里还是给了我们两个突破口,如上蓝色部分。SDInitializeCardLib这个函数没有什么特别之处,总是能返回true。不过从这里我们知道了有SDCARD_API_FUNCTIONS g_SDClientApiFunctions这么个全局的结构体变量,从字面上可以大致猜测其目的,留待分析。SDHCDInitializeHCLib这个函数缺是个重量级的初始化函数。

[public\common\oak\drivers\sdcard\sdhclib\sdhclib.cpp]

static SDHOST_API_FUNCTIONS g_SDHostFuncs;

// SDHCDInitializeHCLib - Initialize the host controller library

//

// Return: SD_API_STATUS

// Notes: Call from DLL entry

//

SD_API_STATUS SDHCDInitializeHCLib()

{

#ifdef DEBUG

    memset(&g_SDHostFuncs, 0xCC, sizeof(g_SDHostFuncs));

#endif

 

    g_SDHostFuncs.dwSize = sizeof(g_SDHostFuncs);

    SD_API_STATUS status = SDHCDGetHCFunctions(&g_SDHostFuncs);

 

    if (SD_API_SUCCESS(status))

{

        DEBUGCHK(g_SDHostFuncs.pAllocateContext);

        DEBUGCHK(g_SDHostFuncs.pDeleteContext);

        DEBUGCHK(g_SDHostFuncs.pRegisterHostController);

        DEBUGCHK(g_SDHostFuncs.pDeregisterHostController);

        DEBUGCHK(g_SDHostFuncs.pIndicateSlotStateChange);

        DEBUGCHK(g_SDHostFuncs.pIndicateBusRequestComplete);

        DEBUGCHK(g_SDHostFuncs.pUnlockRequest);

        DEBUGCHK(g_SDHostFuncs.pGetAndLockCurrentRequest);

        DEBUGCHK(g_SDHostFuncs.pPowerUpDown);

    }

    else {

        DEBUGMSG(SDCARD_ZONE_ERROR, (TEXT("SDHCDInitializeHCLib: Failed to get HC functions\n")));

    }

 

    return status;

}

这个函数虽然很重要,但是功能也很简单,说白了也就是直接调用了SDHCDGetHCFunctions来获取一些功能函数,这里称做sdhc_api。同时也知道有g_SDHostFuncs这么个静态的全局变量。

 

[public\common\oak\drivers\sdcard\sdbusdriver\sdmain.cpp]

SD_API_STATUS

SDHCDGetHCFunctions(PSDHOST_API_FUNCTIONS pFunctions)

{

    SD_API_STATUS status = SD_API_STATUS_INVALID_PARAMETER;

 

if ( pFunctions && (pFunctions->dwSize == sizeof(*pFunctions)) )

{

        pFunctions->pAllocateContext = &SDHCDAllocateContext__X;

        pFunctions->pDeleteContext = &SDHCDDeleteContext__X;

        pFunctions->pRegisterHostController = &SDHCDRegisterHostController__X;

        pFunctions->pDeregisterHostController = &SDHCDDeregisterHostController__X;

        pFunctions->pIndicateSlotStateChange = &SDHCDIndicateSlotStateChange__X;

        pFunctions->pIndicateBusRequestComplete = &SDHCDIndicateBusRequestComplete__X;

        pFunctions->pUnlockRequest = &SDHCDUnlockRequest__X;

        pFunctions->pGetAndLockCurrentRequest = &SDHCDGetAndLockCurrentRequest__X;

        pFunctions->pPowerUpDown = &SDHCDPowerUpDown__X;

 

        status = SD_API_STATUS_SUCCESS;

    }

else

{

        DEBUGMSG(SDCARD_ZONE_ERROR, (TEXT("SDHCDGetHCFunctions: Invalid parameter\n")));

    }

 

    return status;

}

再继续追踪的话,你会发现这些abcd_X的函数都是在[public\common\oak\drivers\sdcard\sdbusdriver\sdhcenum.cpp]这一系列的函数实现都是sdbus的部分,功能留待后面解析。

 

     接下来我们来看SDH_Init这个函数的实现,是标准的流接口初始化函数,它是在dll被加载的时候被加栽程序调用以来初始化驱动的。这个函数依次调用下面的函数:

1status = SDHCDAllocateContext(SDHCD_SLOTS, &pHostContext);

2pController = CreateSDIOController( pHostContext );

3pController->InterpretCapabilities((LPCTSTR)dwContext)

4status = SDHCDRegisterHostController(pHostContext);

 

对于SDHCDAllocateContext的功能就是分配一个host contextpHostContextoutput变量,注意SDHCD_SLOTS,这个参数,我的理解是对应一个控制器,可以控制的卡槽数,我们这里的是1

 

CreateSDIOController这个函数的实现在[sdhc\sdhc\sdiocontroller.cpp]中。它直接用(1)中分配的host-context作为参数new了一个CSDIOController类。这个地方还是有点意思的:

CSDIOController::CSDIOController( PSDCARD_HC_CONTEXT pHCContext )

: CSDIOControllerBase( pHCContext )

{

}//直接构造了CSDIOControllerBase类的实例。

CSDIOControllerBase的构造函数中我们找到m_pHCContext = pHCContext;这样一句话,直觉上认为这句是很重要的,因为前面花了那么多力气弄了这么个东西,到目前为止只有在这个地方被赋值,也就是说以后就由m_pHCContext带着兄弟们混了。后话了。

 

对于InterpretCapabilities函数的调用,我们从名字上应该可以猜测的差不多,它是CSDIOControllerBase类的成员函数,功能大致分为两部分:读取注册表以填充成员变量;调用SDHCDSet[这里用的这些都是宏定义,实现在public\common\oak\inc\sdhc.h中,虽然这是一个头文件,但是这个文件无疑是相当重要的,在sdclient的时候也有个类似的文件。]打头的功能函数设置m_pHCContext的成员变量,这里的这些设置,那是相当的重要啊!这里的操作可以看成是SDHCSDBUS提供的功能函数集,只不过是通过host context这一变量作为载体,因为后面还有host context注册到sdbus中。

 

终于到了SDHCDRegisterHostController,虽然这个函数是在sdbus部分实现的,但是在这里有必要做个了解。打开它的实现,一片青山绿水,首先映入俺们眼帘的是:pHCContext->pSystemContext = SDGetSystemContext();这么一个语句

PVOID SDGetSystemContext()

{

if (s_pSDBusDriver != NULL)

{

        if (s_pSDBusDriver->IsReady())

{

            // return the bus driver object as a context

            return (PVOID)s_pSDBusDriver;

        }

    }

    return NULL;

}

s_pSDBusDriverCSDBusDriver类型指针的一个静态全局变量。不用着急,它是在SDC_Init中被动态创建并初始化的。从注册表中的顺序以及函数间的调用关系看,应该是sdbus早于sdhc驱动被加载。从上面的代码看,这里只是把s_pSDBusDriver的值赋给了pHCContext->pSystemContext,恩,记住了SystemContext!在接下来的表演中都是SDBusDriver是主角,在此不做过多介绍,不过需要注意的是这么一句status = pBusDriver->RegisterHostController(pHCContext);真正的注册就是这里,把传入的参数pHCContext加入到BusDriver类的成员变量m_HostControllerListHead中。

如果说上面的真正注册的表演让你没有尽兴,那么接下来的这一经典绝伦的表演你就不得不看,status = pHCContext->pInitHandlerpHCContext);说到pInitHandler这一失传的绝世武工,还得从上古时代的SDH_Init开始,还记得CSDIOControllerBase::InterpretCapabilities吧,时间好象也不是很长,其中的一条是SDHCDSetControllerInitHandler(m_pHCContext,CSDIOControllerBase::SDHCDInitialize); 好吧,那就看下SD_API_STATUS CSDIOControllerBase::Initialize()的实现了,这里就不列出了。相信你一定有一种他乡遇故知的感觉吧,IOreg, SDIreg, CLKPWR, DMAreg, VirtualAlloc, VirtualCopy, CreateEvent, InterruptInitialize, CreateThread,啥也别说了,眼泪哇哇的,亲人啊!

CSDIOControllerBase::Initialize()中需要注意的是有3个线程:

(1)         SD_CardDetectThread:检测卡的插入或拔出

(2)         SD_IOInterruptIstThreadSDIO数据传输中断的IST

(3)         SD_TransferIstThread:处理SDIODMA数据传输中断

它们在处理的最后都会调用函数indicate SDBUSIsCardPresent函数的实现是通过判断指定gpio的电平来判断卡是否插入的。

这里值得分析的上TransferIstThread的实现。首先看到它等待的事件是m_hResponseReceivedEvent,接收到卡的响应事件。它是在BusRequestHandler[最终通过写寄存器把数据或命令送到线上发送给设备]中被设置的,也就是发送完了请求之后就set这个事件。在这个线程体中,它首先获取响应的信息,并判断响应的类型。如果是命令响应则indicate SDBUS requset完成;如果是数据传输响应,则判断是读响应还是写响应。对于数据传输响应,有两种处理方式,一种是轮循,一种是DMA。这里重要看下DMA的处理方式。

对于SDHC的介绍到这里差不多该结束了,总结一下:SDHCSDBUS的交互,主要是互相注册函数来实现的,SDHCSDBUS提供函数服务的方式是设置host context结构变量的函数指针;SDBUSSDHC提供函数服务的方式是提供函数SDHCDGetHCFunctionsg_SDHostFuncs变量中函数指针赋值。

 

一,  SDBUS

 SDBUS在整个的3层结构中,属于中间层,那也就是说它负责沟通上下两方,从这架势上看其作用与代码的复杂度可见一斑。

从上一节中,我们或多或少的了解了一些SDBUS的构成。这里再做详细分析,首先还是从DllMain开始。实现在publi\common\oak\drivers\sdcard\sdbusdriver\sdmain.cpp中,它的实现似乎比SDHCD的更简单,只有一个SDInitializeCardLib。那好我们接着看SDC_Init,上面我们说过,s_pSDBusDriver这个重量级的变量就是在这里被动态创建的。之后就是s_pSDBusDriver->Initialize();这个初始化了。在CSDBusDriver的构造函数中有一个m_CompletedRequestQueue的初始化,Request相关,后面也许会用到,先放在这。CSDBusDriver类实现在[public\common\oak\drivers\sdcard\sdbusdriver\sdbusdriver.cpp]

CSDBusDriver::Initialize()的具体实现有

(1)         读注册表以填充一些成员变量

(2)         m_BusRequestListm_SynchList都用SDCreateMemoryList创建

(3)         m_pBusRequestCompleteDispatcherCSDWorkItem的实例

(4)         m_pBusRequestCompleteDispatcher->StartWorkItem();

通过对CSDWorkItem的观察,它的StartWorkItem其实就是起了一个线程,线程体是构造时的一个参数,在m_pBusRequestCompleteDispatche中就CSDBusDriver::BusRequestCompleteDispatch

VOID CSDBusDriver::BusRequestCompleteDispatch(CSDWorkItem *pWorkItem, CSDBusDriver *pBusDriver)

{

    SD_API_STATUS            status;        // intermediate status

while(1)

{

        // wait for an event

        status = pWorkItem->WaitWakeUp();

 

        if (!SD_API_SUCCESS(status))

{

            if (status == SD_API_STATUS_SHUT_DOWN)

{

                

            }

            return;

        }

        // handle the bus request completion

        pBusDriver->HandleBusRequestComplete();

 

    }

}

看到这里似乎就显山露水了,就是一个对总线请求完成响应的处理,激活这个线程体的源头应该就是SDHCD中的indicate函数。在CSDWorkItem::WaitWakeUp中有WaitForSingleObject(m_hWorkerWakeUp, TimeOut); m_hWorkerWakeUp这个就启动线程体的事件了。发现只有CSDWorkItemWakeUpWorkItem这个inline函数调用了m_hWorkerWakeUpsetevent

VOID WakeUpWorkItem()

{

     SetEvent(m_hWorkerWakeUp);

}

经过搜索发现在CSDBusDriver中有一个inline函数:

VOID WakeUpBusRequestCompleteDispatcher()

{

     // wake up the bus request complete dispatcher

     m_pBusRequestCompleteDispatcher->WakeUpWorkItem();   

 }

而这个函数又是在SDHCDIndicateSlotStateChange__X中被调用的,OK,调查取证到此结束。

 

SDBUS的初始化工作也到此为止了!完了?似乎没啥感觉啊?是的,初始化工作是做完了,没啥感觉也是正常的。但是整个流程并没有因为初始化完成而结束,还记得上一节中我们留的一个待解的地方吗?就是SDHCDRegisterHostController__X。它在正确获取了pBusDriver之后将要进入一个新的时代。

(1)         pHCContext->dwHCNumber = pBusDriver->GetNewHCNumber();

(2)         pSlotContext = SDHCGetSlotContext(pHCContext, dwSlot);

SDInitializeQueue(&pSlotContext->RequestQueue);

(3)         pHCContext->pSlotOptionHandler//这个就到SDHCD那去找实现吧

(4)         pDispatcher = new CSDWorkItem

(5)         status = pDispatcher->StartWorkItem();

以上这些都是在那个真正注册sdhc之前做的。真正的工作是在StartWorkItem中启动的。这次它的线程体变成了CSDBusDriver::SlotStatusChange。代码如下:

VOID CSDBusDriver::SlotStatusChange(CSDWorkItem *pWorkItem, CSDBusDriver *pBusDriver)

{

    PWORK_ITEM_MESSAGE_BLOCK pMessage;      // the message received

    PSDCARD_HC_SLOT_EVENT    pSlotEvent;    // slot event packet

    PSDBUS_HC_SLOT_CONTEXT   pSlotContext;  // the slot context

    SD_API_STATUS            status;        // the status

 

while(1)

{

        // this blocks

        status = pWorkItem->GetMessage(&pMessage);

 

        if (!SD_API_SUCCESS(status))

{

            if (status == SD_API_STATUS_SHUT_DOWN)

{

                 //debug info

            }

            return;

        }

 

        // get the message block and cast to a slot event

        pSlotEvent = GetMessageBlock(PSDCARD_HC_SLOT_EVENT, pMessage);

 

        pSlotContext = pSlotEvent->pSlotContext;

 

        switch (pSlotEvent->SlotEvent)

{

            case DeviceInserted:

                pBusDriver->HandleAddDevice(pSlotContext);

                break;

            case DeviceEjected:

                pBusDriver->HandleRemoveDevice(pSlotContext);

                break;

            case DeviceInterrupting:

                pBusDriver->HandleDeviceInterrupting(pSlotContext);

                break;

#ifdef ENABLE_SDIO_V1_1_SUPPORT

              case SlotDeselectRequest:

              case SlotSelectRequest:

              case SlotResetRequest:

                   pBusDriver->HandleSlotSelectDeselect(pSlotContext, pSlotEvent->SlotEvent);

                   break;

#endif //ENABLE_SDIO_V1_1_SUPPORT

            default:

                //debug info

                break;

        }

 

        // free the message

        pWorkItem->FreeMessage(pMessage);  

    }

}

从这里,可以看出和slot卡槽相关的动作都是在这里处理的。我们主要看下DeviceInsertedDeviceInterrupting这两个的处理,DeviceInserted这部分就是用来加载各种卡的驱动的。处理的实现是VOID CSDBusDriver::HandleAddDevice(PSDBUS_HC_SLOT_CONTEXT pSlot)

这里的函数,特别是CSDBusDriver::SDLoadDevice(pCurrentDevice);的实现,不是按类CSDBusDriver的实现而划分存放文件的,而是依据其函数的功能存放的,这个函数就存放在public\common\oak\drivers\sdcard\sdbusdriver\sdclient.cpp文件中,意思就是说这里存放的是sdbussdclient交互操作相关的部分。

 

一,  SDClient

 

现在我们正式进入sdclient的介绍部分,其实从整个的sd驱动来看,只有这一块才是我们开发一款新产品时真正需要涉及的。当然了,对于一般的驱动工程师来说sdhcd这一部分会更重要一些。目前介绍sdmem的很多,这里选择btsdio来介绍。

 

首先我们一样先来看看DllMain函数的实现,在public\common\oak\drivers\sdcard\sdclientdrivers\bluetooth\hcisdio.cpp中。它依然不例外的调用了SDInitializeCardLib,接着就上g_pSdioDevice = new CSdioDevice;[这个类的构造函数无特别之处],在这里插入一下,在该目录下的hcisdio.def文件,其内容是:

EXPORTS

     HCI_ReadHciParameters

     HCI_SetCallback

     HCI_StartHardware

     HCI_StopHardware

     HCI_OpenConnection

     HCI_CloseConnection

     HCI_ReadPacket

     HCI_WritePacket

     BSD_Init

     BSD_Deinit

     BSD_Open

     BSD_Close

     BSD_IOControl

从其名字我们可以看出,HCI打头的是给蓝牙的HCI提供交互操作的,BSD打头的是SD设备驱动的标准前缀。下面我们就再看下这个BSD_Init[实现在同目录的sdiodev.cpp文件中]。它的实现比较简单:

if (g_pSdioDevice->IsAttached())

{

        fRetVal = FALSE;

        goto exit;

    }

   

if (! g_pSdioDevice->Attach(dwContext))

{

        fRetVal = FALSE;

        goto exit;

}

     下面主要说下Attach这个函数的流程:

(1)         SDGetDeviceHandle,获取设备句柄,在sdbus中被初始化

(2)         SDRegisterClient,获取sdbus的支持服务

(3)         SDIOConnectInterrupt

(4)         CreateBusAccessHandle

(5)         SDSetCardFeature

(6)         SDCardInfoQuery

(7)         g_Data.pfnHCICallback(DEVICE_UP, NULL);通知hci设备准备好了

HCI调用HCI_SetCallback来设置g_Data.pfnHCICallback

 

public\common\ddk\inc\sdcardddk.h中可以看到很多SDClientApiFunction,这些函数是以#define形式给出的,实际调用g_SDClientApiFunctions.xyz函数指针,与SDBUS提供给SDHCDapi有异曲同工之美。这里的函数通常是由HCI_xyz调用的。