Chinaunix首页 | 论坛 | 博客
  • 博客访问: 57156
  • 博文数量: 8
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 105
  • 用 户 组: 普通用户
  • 注册时间: 2015-06-19 10:45
个人简介

分享笔者从事嵌入式工作几年来在Linux, SylixOS等操作系统中关于内核、驱动框架以及各种中间件开发和设计的技术。

文章分类
文章存档

2015年(8)

我的朋友

分类: C/C++

2015-07-24 19:13:49

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设备又存在不同的应用,比如WIFIGPSUART等,实际上也就存在很多不同的驱动。SDM需要实现为一个新的设备找到正确的设备驱动的方法,即驱动匹配工作。

      7未定义设备

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

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

3. SDM Host信息

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

  1. struct sd_host {
  2.     CPCHAR    SDHOST_cpcName;

  3.     INT       SDHOST_iType;
  4. #define       SDHOST_TYPE_SD 0
  5. #define       SDHOST_TYPE_SPI 1

  6.     INT       SDHOST_iCapbility;               /* 主动支持的特性            */
  7. #define SDHOST_CAP_HIGHSPEED (1 << 0)          /* 支持高速传输              */
  8. #define SDHOST_CAP_DATA_4BIT (1 << 1)          /* 支持4位数据传输           */
  9. #define SDHOST_CAP_DATA_8BIT (1 << 2)          /* 支持8位数据传输           */

  10.     VOID    (*SDHOST_pfuncSpicsEn)(SD_HOST  *psdhost);
  11.     VOID    (*SDHOST_pfuncSpicsDis)(SD_HOST *psdhost);
  12.     INT     (*SDHOST_pfuncCallbackInstall)
  13.             (
  14.               SD_HOST      *psdhost,
  15.               INT           iCallbackType,      /* 安装的回调函数的类型      */
  16.               SD_CALLBACK   callback,           /* 回调函数指针             */
  17.               PVOID         pvCallbackArg       /* 回调函数的参数           */
  18.             );

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

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

  29.     VOID    (*SDHOST_pfuncDevAttach)(SD_HOST *psdhost, CPCHAR cpcDevName);
  30.     VOID    (*SDHOST_pfuncDevDetach)(SD_HOST *psdhost);
  31. };

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


SDHOST_iType:控制器的类型,是SD还是SPISDM将通过名称和类型找到系统中对应的总线适配器,以便创建对应的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);

该函数成功,将返回一个HostSDM里面管理的一个对象指针。驱动程序使用该指针向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基础驱动使用,其结构如下:

  1. struct sd_drv
  2.     LW_LIST_LINE  SDDRV_lineManage;     /* 驱动挂载链         */

  3.     CPCHAR        SDDRV_cpcName;

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

  6.     atomic_t      SDDRV_atomicDevCnt;

  7.     VOID         *SDDRV_pvSpec;
  8. };


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

SDDRV_cpcName:驱动名称。

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

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

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

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

       可以看到,一个设备类驱动完全不必关心硬件控制器的任何信息。使用如下APISDM注册一个SD驱动:

INT   API_SdmSdDrvRegister(SD_DRV *psddrv);


5. SDM SDIO设备类驱动

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

  1. struct sdio_drv {
  2.     LW_LIST_LINE  SDIODRV_lineManage;        /* 驱动挂载链            */
  3.     CPCHAR        SDIODRV_cpcName;

  4.     INT         (*SDIODRV_pfuncDevCreate)
  5.                 (
  6.                   SDIO_DRV *psdiodrv,
  7.                   SDIO_INIT_DATA *pinitdata,
  8.                   VOID **ppvDevPriv
  9.                 );

  10.     INT         (*SDIODRV_pfuncDevDelete)(SDIO_DRV *psdiodrv, VOID *pvDevPriv);

  11.     VOID        (*SDIODRV_pfuncIrqHandle)(SDIO_DRV *psdiodrv, VOID *pvDevPriv);

  12.     SDIO_DEV_ID  *SDIODRV_pdevidTbl;
  13.     INT           SDIODRV_iDevidCnt;
  14.     VOID         *SDIODRV_pvSpec;

  15.     atomic_t      SDIODRV_atomicDevCnt;
  16. };

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


  1. #define SDIO_FUNC_MAX 8
  2. struct sdio_init_data {
  3.     SDIO_FUNC         INIT_psdiofuncTbl[SDIO_FUNC_MAX];
  4.     INT               INIT_iFuncCnt;      /* 不包括Func0    */
  5.     PLW_SDCORE_DEVICE INIT_psdcoredev;
  6.     SDIO_CCCR         INIT_sdiocccr;
  7. };
  8. typedef struct sdio_init_data SDIO_INIT_DATA;


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

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


  1. struct sdio_dev_id {
  2.     UINT8   DEVID_ucClass;    /* Std interface or SDIO_ANY_ID */
  3.     UINT16  DEVID_usVendor;   /* Vendor or SDIO_ANY_ID        */
  4.     UINT16  DEVID_usDevice;   /* Device ID or SDIO_ANY_ID     */
  5. };
  6. #define SDIO_DEV_ID_ANY (~0)


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

INT   API_SdmSdioDrvRegister(SDIO_DRV *psdiodrv);

 

6. 孤儿设备

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

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

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




阅读(2479) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~