SylixOS SD协议栈之五:SDM驱动管理

1. SDM的主要功能

上一篇说道,SD Core设备主要是封装了底层的细节,为应用层提供唯一的传输接口。SDM,即SD Driver Management,是用来管理SD协议栈里的驱动的,包括硬件控制器驱动和设备类驱动(这与USB协议栈的设计非常类似),以此将两者隔离开来。SDM的功能主要有以下几点:

  1. 对上,提供Client层驱动注册的接口,包括SD 基础驱动和 SDIO子设备驱动

  2. 对下,提供Host层驱动信息注册的接口,这些信息一部分是应用驱动需要的,比如控制器类型,支持的位宽等等,由SDM管理,在适当的时候提供给应用驱动。这些信息还包括控制器本身在一些特定条件下的行为定义,比如当一个设备经设备驱动创建或移除后,主控如何去处理的方法。因为在具体的Host驱动实现中,其内部状态可能会依赖插入其控制器的设备的状态(比如SDHCI标准驱动就需要提供此信息)。

  3. 为每类设备提供一些设备相关的内部信息,如:可能存在使用同一驱动的设备,各个设备的名称应该是不同的,由SDM给出。

  4. 提供让Host驱动通知SDM设备状态改变的通知机制,当设备插入/拔出,由SDM负责设备驱动匹配,进行设备的自动创建和删除工作(在以前的版本中,由于没有SDM,都是由控制器驱动来完成设备的创建和删除,非常不合理)。

2. SDM需要处理的问题

为了完成以上功能,在实现具体的SDM代码之前,我们需要考虑,在实际应用中,一个支持热插拔多对象的系统,都会存在哪些必须解决的问题,笔者总结有如下最重要的几点:

  1. 如何做到Client层与Host层真正隔离?
    前面说道,Client层实际上只需要一个SD Core设备传输对象就可以了,为了让Client不比关系SD Core设备的细节,必须让SDM来负责管理(创建、删除)SD Core设备。因此,SDM必须能够得到创建一个SD Coe设备对象所必要的硬件信息。这写信息应该由注册到SDM的控制器驱动提供。

  2. 如何让SDM感知到设备的状态变化?
    这里的状态变化主要指的是插入/拔出状态(后续可能在具体的SD设备上有更多其他状态)。因为有了SDM后,设备的创建和删除工作由SDM内部处理。此状态首先由控制器端检测到,因此需要提供某种机制,让控制器端将状态报告给SDM。

  3. 如何正确处理设备状态变化?
    这主要存在两类问题:其一是, SDM如何判断是哪一个Host上插入或删除了设备,也就是处理设备与控制器的关联性问题。其二是,SDM如何以正确的驱动创建当前设备、如何以正确的驱动删除设备,也就是设备与设备驱动的关联性问题。因此,SDM内部必须完成对设备驱动、控制器驱动、设备实体对象三者之间数据结构良好的定义以及管理措施,能够有效地在合适的时候将三者关联(或取消关联)。

  4. 设备特征信息管理
    SDM应该假设在一个系统中可能同时存在多个SD控制器,SD设备,并且可能有很多SD设备都是相同类型的,内部如何区分它们?这就需要为相同类型的对象设置唯一的特征信息。

  5. 考虑对象生命周期的依赖性
    即SDM应该考虑内部管理的对象(就是上面所说的设备、设备驱动、控制器驱动等)它们之间的相互依赖关系,有的是没有必然关系的,有的存在依赖关系,比如当有设备在使用驱动时,该驱动就不应该被注销或删除。

  6. 如何为一个新的设备找到合适的设备驱动?
    由于SD设备只有SD存储卡和SDIO卡两种,这里的驱动也就只有SD存储卡驱动和SDIO驱动两大类。但SDIO设备又存在不同的应用,比如WIFI、GPS、UART等,实际上也就存在很多不同的驱动。SDM需要实现为一个新的设备找到正确的设备驱动的方法,即驱动匹配工作。

  7. 未定义设备
    指的是如果一个插入的设备没有对应的驱动,该如何处理?

在接下来的讲解中,所有涉及的数据结构和API函数,都是围绕以上问题设计的。

3. SDM Host信息

这里称为Host信息而不是驱动,是因为严格来讲,之前所说的Hos总线适配器才是驱动,因为它实实在在地完成了控制器硬件上的数据传输工作。这里的信息只是为配合SDM管理而产生的。在文件sdcard/core/sddrvm.h文件中,其数据结构的定义如下:

/*********************************************************************************************************
  sd host 信息结构
*********************************************************************************************************/

#ifdef  __cplusplus
typedef INT     (*SD_CALLBACK)(...);
#else
typedef INT     (*SD_CALLBACK)();
#endif

struct sd_host {
    CPCHAR        SDHOST_cpcName;

    INT           SDHOST_iType;
#define SDHOST_TYPE_SD                  0
#define SDHOST_TYPE_SPI                 1

    INT           SDHOST_iCapbility;                                /*  主动支持的特性                  */
#define SDHOST_CAP_HIGHSPEED            (1 << 0)                    /*  支持高速传输                    */
#define SDHOST_CAP_DATA_4BIT            (1 << 1)                    /*  支持4位数据传输                 */
#define SDHOST_CAP_DATA_8BIT            (1 << 2)                    /*  支持8位数据传输                 */
#define SDHOST_CAP_DATA_4BIT_DDR        (1 << 3)                    /*  支持4位ddr数据传输              */
#define SDHOST_CAP_DATA_8BIT_DDR        (1 << 4)                    /*  支持8位ddr数据传输              */
#define SDHOST_CAP_MMC_FORCE_1BIT       (1 << 5)                    /*  MMC卡 强制使用 1 位总线         */
#define SDHOST_CAP_SDIO_FORCE_1BIT      (1 << 6)                    /*  SDIO 卡 强制使用 1 位总线       */
#define SDHOST_CAP_SD_FORCE_1BIT        (1 << 7)                    /*  SD 卡 强制使用 1 位总线         */

    VOID          (*SDHOST_pfuncSpicsEn)(SD_HOST *psdhost);
    VOID          (*SDHOST_pfuncSpicsDis)(SD_HOST *psdhost);
    INT           (*SDHOST_pfuncCallbackInstall)
                  (
                  SD_HOST          *psdhost,
                  INT               iCallbackType,                  /*  安装的回调函数的类型            */
                  SD_CALLBACK       callback,                       /*  回调函数指针                    */
                  PVOID             pvCallbackArg                   /*  回调函数的参数                  */
                  );

    INT           (*SDHOST_pfuncCallbackUnInstall)
                  (
                  SD_HOST          *psdhost,
                  INT               iCallbackType                   /*  安装的回调函数的类型            */
                  );
#define SDHOST_CALLBACK_CHECK_DEV       0                           /*  卡状态检测                      */
#define SDHOST_DEVSTA_UNEXIST           0                           /*  卡状态:不存在                   */
#define SDHOST_DEVSTA_EXIST             1                           /*  卡状态:存在                     */

    VOID          (*SDHOST_pfuncSdioIntEn)(SD_HOST *psdhost, BOOL bEnable);
    BOOL          (*SDHOST_pfuncIsCardWp)(SD_HOST *psdhost);

    VOID          (*SDHOST_pfuncDevAttach)(SD_HOST *psdhost, CPCHAR cpcDevName);
    VOID          (*SDHOST_pfuncDevDetach)(SD_HOST *psdhost);
};

SDHOST_cpcName:
表明该信息描述对应的控制器名称,必须是系统 中存在的总线适配器(SD或SPI)的名称,通常为/bus/sd/0、/bus/spi/1等。

SDHOST_iType:
控制器的类型,是SD还是SPI。SDM将通过名称和类型找到系统中对应的总线适配器,以便创建对应的SD Core设备对象。

SDHOST_iCapbility:
控制器的功能特性。这些特性可能是和硬件电路的设计有关的(比如支持的速度、使用的传输位宽等),需要由驱动提供。

SDHOST_pfuncSpicsEn:

SDHOST_pfuncSpicsDis:
如果是SPI,则该方法必须提供。这两个函数也是与硬件电路设计有关,由驱动提供。

SDHOST_pfuncCallbackInstall:

SDHOST_pfuncCallbackUnInstall:
必须提供。这两个函数为安装上层的回电函数的方法。在后面的驱动开发里将具体说明如何使用。

SDHOST_pfuncSdioIntEn:
用于设置控制器的SDIO中断的使能或禁止状态。因为总线适配器里面没有处理SDIO中断的相关措施,所以如果Host支持SDIO,则该方法必须提供。

SDHOST_pfuncIsCardWp:
用于判断SD卡(主要是SD存储卡)是否写保护。卡的写保护检测也和具体的电路设计相关,如果不支持,则Host驱动可以不提供该方法。

SDHOST_pfuncDevAttach:

SDHOST_pfuncDevDetach:
可选。这两个函数为HOST驱动提供了在SDM创建/删除设备时其处理内部信息或者状态的一个机制。Host驱动不一定需要处理,因此是可选实现的。

有了以上这些信息,SDM就能正确的完成SD Core设备的创建工作,并能为客户驱动提供一些实用的额外信息。这就解决了上面所讲的第一个问题。向SDM注册Host信息的API如下:

PVOID API_SdmHostRegister(SD_HOST *psdhost);

该函数成功,将返回一个Host在SDM里面管理的一个对象指针。驱动程序使用该指针向SDM报告自己检测到的相关事件。其API如下:

INT API_SdmEventNotify(PVOID pvSdmHost, INT iEvtType);

该函数第一个参数即为上面一个函数的返回值,第二个参数表示报告的事件类型,有如下定义:

#define SDM_EVENT_DEV_INSERT 0

#define SDM_EVENT_DEV_REMOVE 1

#define SDM_EVENT_SDIO_INTERRUPT 2

SDM根据接收到的不同事件类型,处理不同的工作。具体如何处理,不需要Host驱动关心。从这里也看出,一个控制器驱动自始至终都不关心当前系统的设备情况,只负责处理自身相关的信息。这实际上解决了上面的第一和第二点问题。

4. SDM SD通用设备类驱动

SD通用设备类驱动主要是供SD存储和SDIO基础驱动使用,其结构如下:

/*********************************************************************************************************
  sd 驱动(用于sd memory 和 sdio base)
*********************************************************************************************************/

struct sd_drv {
    LW_LIST_LINE  SDDRV_lineManage;                               /*  驱动挂载链                        */

    CPCHAR        SDDRV_cpcName;
#define SDDRV_SDMEM_NAME      "sd memory"
#define SDDRV_SDIOB_NAME      "sdio base"

    INT         (*SDDRV_pfuncDevCreate)(SD_DRV *psddrv, PLW_SDCORE_DEVICE psdcoredev, VOID **ppvDevPriv);
    INT         (*SDDRV_pfuncDevDelete)(SD_DRV *psddrv, VOID *pvDevPriv);

    atomic_t      SDDRV_atomicDevCnt;

    VOID         *SDDRV_pvSpec;
};

SDDRV_lineManage:
SDM内部将注册的驱动以链表的形式管理起来。

SDDRV_cpcName:
驱动名称。

SDDRV_pfuncDevCreate:
驱动创建对应设备的方法。参数 psdcoredev由SDM提供,输出参数ppvDevPriv保存创建的设备私有数据。

SDDRV_pfuncDevDelete:
驱动删除对应设备的方法。参数pvDevPriv即为创建时的设备私有数据。上面的设备创建和删除方法的定义是很多系统中采用的形式,即驱动完成具体的工作和自身的数据处理,而SDM负责在何时使用驱动处理这些工作。

SDDRV_atomicDevCnt:
该驱动对应的设备计数,即当前有多少个设备在使用该驱动,此信息的主要用途是判断当前驱动是否能被删除,如上面所讲的第5点问题。

SDDRV_pvSpec:
由SDM内部使用。驱动程序本身不关心该成员的意义。当前,SDM内部使用此指针保存一个“驱动为它的设备分配设备单元号的【数据对象】”,解决上面的第4点问题。

可以看到,一个设备类驱动完全不必关心硬件控制器的任何信息。使用如下API向SDM注册一个SD驱动:
INT API_SdmSdDrvRegister(SD_DRV *psddrv);

5. SDM SDIO设备类驱动

SDIO驱动相关信息位于SylixOS/system/device/sdcard/core/sdiodrvm.h中,一个具体的SDIO设备驱动(比如SDIO无线网卡,SDIO串口等)结构定义如下:

/*********************************************************************************************************
  sdio 驱动
*********************************************************************************************************/

struct sdio_drv {
    LW_LIST_LINE  SDIODRV_lineManage;                               /*  驱动挂载链                      */
    CPCHAR        SDIODRV_cpcName;

    INT         (*SDIODRV_pfuncDevCreate)(SDIO_DRV         *psdiodrv,
                                          SDIO_INIT_DATA   *pinitdata,
                                          VOID            **ppvDevPriv);
    INT         (*SDIODRV_pfuncDevDelete)(SDIO_DRV *psdiodrv, VOID *pvDevPriv);
    VOID        (*SDIODRV_pfuncIrqHandle)(SDIO_DRV *psdiodrv, VOID *pvDevPriv);

    SDIO_DEV_ID  *SDIODRV_pdevidTbl;
    INT           SDIODRV_iDevidCnt;
    VOID         *SDIODRV_pvSpec;

    atomic_t      SDIODRV_atomicDevCnt;
};

对比SD驱动,两者的结构非常相似,不过后者多了几个与SDIO相关的数据信息。在其设备创建方法SDIODRV_pfuncDevCreate中,由SDIO_INIT_DATA替换了SD驱动中的PLW_SDCORE_DEVICE数据类型。SDIO_INIT_DATA结构的定义如下:

#define SDIO_FUNC_MAX   8
struct sdio_init_data {
    SDIO_FUNC          INIT_psdiofuncTbl[SDIO_FUNC_MAX];
    INT                INIT_iFuncCnt;                               /*  不包括Func0                     */
    PLW_SDCORE_DEVICE  INIT_psdcoredev;
    SDIO_CCCR          INIT_sdiocccr;
    const SDIO_DEV_ID *INIT_pdevidCurr[SDIO_FUNC_MAX];              /*  当前设备的所有匹配的功能ID      */
};

首先需要明白的是,SDIO_INIT_DATA数据对象是由SDM层提供的,实际上,这正是SDIO Base驱动在调用具体的SDIO驱动之前,进行设备初始化时获得的SDIO设备的通用信息,因此把它称作“sdio init data”,这些信息可供具体的SDIO设备驱动直接使用,而无需再次去获取或处理。这一点与USB主栈枚举设备的过程非常相似,即先获得设备描述符、配置描述符、接口描述符、端点描述符等信息,随后根据这些信息去匹配正确的USB设备类驱动,当找到具体的驱动时,调用其对应的设备创建方法,并且会提供相应的接口,让这些方法能够获取到这些通用信息。SDIO_INIT_DATA里面,主要由SDIO_FUNC结构成员和SDIO_CCCR结构成员,两者均与SDIO协议息息相关。由于本博文的重点是软件设计的结构而非协议本身,因此SDIO协议的详细信息,将会在其他文章中,本篇不再作介绍。

SDIODRV_pdevidTbl与SDIODRV_iDevidCnt两个成员定义了当前SDIO驱动可支持的SDIO设备ID。SDIO_DEV_ID与USB或PCI里对设备标识的定义类似,只不过它相对更加简单,包括设备的接口类型(比如无线网卡,GPS,串口等)、厂商标识、当前设备编码。如下定义:

/*********************************************************************************************************
  sdio 设备 相关定义
*********************************************************************************************************/

struct sdio_dev_id {
    UINT8    DEVID_ucClass;                                         /*  Std interface or SDIO_ANY_ID    */
    UINT16   DEVID_usVendor;                                        /*  Vendor or SDIO_ANY_ID           */
    UINT16   DEVID_usDevice;                                        /*  Device ID or SDIO_ANY_ID        */
    VOID    *DEVID_pvDrvPriv;                                       /*  driver private data             */
};
#define SDIO_DEV_ID_ANY     (~0)

此外,SDIODRV_pfuncIrqHandle也是一个SDIO驱动必须实现的方法,当控制器产生SDIO中断时,SDM层会调用该方法,以完成具体设备对中断的处理。使用以下API向SDM注册一个SDIO驱动:
INT API_SdmSdioDrvRegister(SDIO_DRV *psdiodrv);

6. 孤儿设备

孤儿(orphan)设备指的是在系统中存在与其对应的设备实体,但是没有与之对应的驱动程序,该设备当前还无法正常工作,但它仍然处于系统的管理之下。定义孤儿设备,是为了满足实用性的需求。这里我们可以对比在Windows系统中,当我们插入一个还没有安装任何驱动的USB设备时,在设备管理器中将会看到一个图标带有问号的设备,并显示该设备不能正常工作。如果此时我们安装该USB设备的驱动,该设备随后就能工作,而不需要重新插拔设备或重启系统。这种策略,让设备驱动与设备硬件本身在系统上的存在顺序没有任何依赖关系,这给实际应用带来了更大的灵活性。而在SylixOS(或是Linux、Unix等)的标准IO设备中,创建设备时必须指明该设备的驱动程序,否则不能创建设备,这说明标准IO驱动与设备实体的产生存在必然的依赖关系。

SDM同样支持孤儿设备的管理,这样达到的效果就是,注册设备驱动,注册控制器驱动和合适插如SD设备三者之间的操作顺序完全独立,只要他们三者都存在,对应的SD设备就能正确创建并正常工作。

总体来看,SDM组件解决了第2节中列出的所有问题,当前已经满足绝大多数需求。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值