OpenHarmony平臺驅動開發(十五)
SDIO
概述
功能簡介
SDIO(Secure Digital Input and Output)由SD卡發展而來,與SD卡統稱為MMC(MultiMediaCard),二者使用相同的通信協議。SDIO接口兼容以前的SD卡,并且可以連接支持SDIO接口的其他設備。
運作機制
在HDF框架中,SDIO的接口適配模式采用獨立服務模式(如圖1)。在這種模式下,每一個設備對象會獨立發布一個設備服務來處理外部訪問,設備管理器收到API的訪問請求之后,通過提取該請求的參數,達到調用實際設備對象的相應內部方法的目的。獨立服務模式可以直接借助HDFDeviceManager的服務管理能力,但需要為每個設備單獨配置設備節點,若設備過多可能增加內存占用。
獨立服務模式下,核心層不會統一發布一個服務供上層使用,因此這種模式下驅動要為每個控制器發布一個服務,具體表現為:
-
驅動適配者需要實現HdfDriverEntry的Bind鉤子函數以綁定服務。
-
device_info.hcs文件中deviceNode的policy字段為1或2,不能為0。
SDIO模塊各分層作用:
-
接口層提供打開SDIO設備、設置塊的大小、讀取數據、寫數據、設置公共信息、獲取公共信息、刷新數據、獨占HOST、釋放Host、使能SDIO功能設備、去使能SDIO功能設備、申請中斷、釋放中斷關閉SDIO設備的接口。
-
核心層主要提供SDIO控制器的添加、移除及管理的能力,通過鉤子函數與適配層交互。
-
適配層主要是將鉤子函數的功能實例化,實現具體的功能。
約束與限制
SDIO模塊API當前僅支持內核態調用。
開發指導
場景介紹
SDIO的應用比較廣泛,目前,有許多手機都支持SDIO功能,并且很多SDIO外設也被開發出來,使得手機外接外設更加容易。常見的SDIO外設有WLAN、GPS、CAMERA、藍牙等。當驅動開發者需要將SDIO設備適配到OpenHarmony時,需要進行SDIO驅動適配,下文將介紹如何進行SDIO驅動適配。
接口說明
為了保證上層在調用SDIO接口時能夠正確的操作硬件,核心層在//drivers/hdf_core/framework/model/storage/include/mmc/mmc_sdio.h中定義了以下鉤子函數。驅動適配者需要在適配層實現這些函數的具體功能,并與這些鉤子函數掛接,從而完成接口層與核心層的交互。
SdioDeviceOps定義:
struct SdioDeviceOps {int32_t (*incrAddrReadBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size);int32_t (*incrAddrWriteBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size);int32_t (*fixedAddrReadBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size, uint32_t scatterLen);int32_t (*fixedAddrWriteBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size, uint32_t scatterLen);int32_t (*func0ReadBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size);int32_t (*func0WriteBytes)(struct SdioDevice *dev, uint8_t *data, uint32_t addr, uint32_t size);int32_t (*setBlockSize)(struct SdioDevice *dev, uint32_t blockSize);int32_t (*getCommonInfo)(struct SdioDevice *dev, SdioCommonInfo *info, uint32_t infoType);int32_t (*setCommonInfo)(struct SdioDevice *dev, SdioCommonInfo *info, uint32_t infoType);int32_t (*flushData)(struct SdioDevice *dev);int32_t (*enableFunc)(struct SdioDevice *dev);int32_t (*disableFunc)(struct SdioDevice *dev);int32_t (*claimIrq)(struct SdioDevice *dev, SdioIrqHandler *irqHandler);int32_t (*releaseIrq)(struct SdioDevice *dev);int32_t (*findFunc)(struct SdioDevice *dev, struct SdioFunctionConfig *configData);int32_t (*claimHost)(struct SdioDevice *dev);int32_t (*releaseHost)(struct SdioDevice *dev);
};
表 1?SdioDeviceOps結構體成員的鉤子函數功能說明
函數 | 入參 | 出參 | 返回值 | 功能 |
---|---|---|---|---|
incrAddrReadBytes | dev:結構體指針,SDIO設備控制器 addr:uint32_t類型,地址值 size:uint32_t類型,大小 | data:uint8_t類型指針,傳出值 | HDF_STATUS相關狀態 | 從指定的SDIO地址增量讀取給定長度的數據 |
incrAddrWriteBytes | dev:結構體指針,SDIO設備控制器 data:uint8_t類型指針,傳入值 addr:uint32_t類型,地址值 size:uint32_t類型,大小 | 無 | HDF_STATUS相關狀態 | 將給定長度的數據增量寫入指定的SDIO地址 |
fixedAddrReadBytes | dev:結構體指針,SDIO設備控制器 addr:uint32_t類型,地址值 size:uint32_t類型,大小 scatterLen:uint32_t類型,數據長度 | data:uint8_t類型指針,傳出值 | HDF_STATUS相關狀態 | 從固定SDIO地址讀取給定長度的數據。 |
fixedAddrWriteBytes | dev:結構體指針,SDIO設備控制器 data:uint8_t類型指針,傳入值 addr:uint32_t類型,地址值 size:uint32_t類型,大小 scatterLen:uint32_t類型,數據長度 | 無 | HDF_STATUS相關狀態 | 將給定長度的數據寫入固定SDIO地址 |
func0ReadBytes | dev:結構體指針,SDIO設備控制器 addr:uint32_t類型,地址值 size:uint32_t類型,大小 | data:uint8_t類型指針,傳出值 | HDF_STATUS相關狀態 | 從SDIO函數0的地址空間讀取給定長度的數據。 |
func0WriteBytes | dev:結構體指針,SDIO設備控制器 data:uint8_t類型指針,傳入值 addr:uint32_t類型,地址值 size:uint32_t類型,大小 | 無 | HDF_STATUS相關狀態 | 將給定長度的數據寫入SDIO函數0的地址空間。 |
setBlockSize | dev:結構體指針,SDIO設備控制器 blockSize:uint32_t類型,Block大小 | 無 | HDF_STATUS相關狀態 | 設置block大小 |
getCommonInfo | dev:聯合體指針,SDIO設備控制器 infoType:uint32_t類型,info類型 | info:結構體指針,傳出SdioFuncInfo信息 | HDF_STATUS相關狀態 | 獲取CommonInfo,說明見下 |
setCommonInfo | dev:結構體指針,SDIO設備控制器 info:聯合體指針,SdioFuncInfo信息傳入 infoType:uint32_t類型,info類型 | 無 | HDF_STATUS相關狀態 | 設置CommonInfo,說明見下 |
flushData | dev:結構體指針,SDIO設備控制器 | 無 | HDF_STATUS相關狀態 | 當SDIO需要重新初始化或發生意外錯誤時調用的函數 |
enableFunc | dev:結構體指針,SDIO設備控制器 | 無 | HDF_STATUS相關狀態 | 使能SDIO設備 |
disableFunc | dev:結構體指針,SDIO設備控制器 | 無 | HDF_STATUS相關狀態 | 去使能SDIO設備 |
claimIrq | dev:結構體指針,SDIO設備控制器 irqHandler:void函數指針 | 無 | HDF_STATUS相關狀態 | 注冊SDIO中斷 |
releaseIrq | dev:結構體指針,SDIO設備控制器 | 無 | HDF_STATUS相關狀態 | 釋放SDIO中斷 |
findFunc | dev:結構體指針,SDIO設備控制器 configData:結構體指針,SDIO函數關鍵信息 | 無 | HDF_STATUS相關狀態 | 尋找匹配的funcNum |
claimHost | dev:結構體指針,SDIO設備控制器 | 無 | HDF_STATUS相關狀態 | 獨占HOST |
releaseHost | dev:結構體指針,SDIO設備控制器 | 無 | HDF_STATUS相關狀態 | 釋放HOST |
?說明:
CommonInfo包括maxBlockNum(單個request中最大block數)、maxBlockSize(單個block最大字節數)、maxRequestSize(單個Request最大字節數)、enTimeout(最大超時時間,毫秒)、funcNum(功能編號1~7)、irqCap(IRQ capabilities)、(void *)data。
開發步驟
SDIO模塊適配包含以下四個步驟:
-
實例化驅動入口
-
實例化HdfDriverEntry結構體成員。
-
調用HDF_INIT將HdfDriverEntry實例化對象注冊到HDF框架中。
-
-
配置屬性文件
-
在device_info.hcs文件中添加deviceNode描述。
-
【可選】添加sdio_config.hcs器件屬性文件。
-
-
實例化SDIO控制器對象
-
初始化SdioDevice成員。
-
實例化SdioDevice成員SdioDeviceOps。
?說明:
實例化SdioDevice成員SdioDeviceOps,其定義和成員說明見接口說明。
-
-
驅動調試
【可選】針對新增驅動程序,建議驗證驅動基本功能,例如SDIO控制狀態,中斷響應情況,讀寫數據是否成功等。
開發實例
下方將以//drivers/hdf_core/adapter/khdf/linux/model/storage/sdio_adapter.c為示例,展示需要驅動適配者提供哪些內容來完整實現設備功能。
-
實例化驅動入口
驅動入口必須為HdfDriverEntry(在hdf_device_desc.h中定義)類型的全局變量,且moduleName要和device_info.hcs中保持一致。HDF框架會將所有加載的驅動的HdfDriverEntry對象首地址匯總,形成一個類似數組的段地址空間,方便上層調用。
一般在加載驅動時HDF會先調用Bind函數,再調用Init函數加載該驅動。當Init調用異常時,HDF框架會調用Release釋放驅動資源并退出。
SDIO 驅動入口參考:
struct HdfDriverEntry g_sdioDriverEntry = {.moduleVersion = 1,.Bind = Hi35xxLinuxSdioBind, // 見Bind開發參考.Init = Hi35xxLinuxSdioInit, // 見Init開發參考.Release = Hi35xxLinuxSdioRelease, // 見Release開發參考.moduleName = "HDF_PLATFORM_SDIO", // 【必要且與HCS文件中里面的moduleName匹配】 }; HDF_INIT(g_sdioDriverEntry); // 調用HDF_INIT將驅動入口注冊到HDF框架中
-
配置屬性文件
完成驅動入口注冊之后,下一步請在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode信息,并在sdio_config.hcs中配置器件屬性。deviceNode信息與驅動入口注冊相關,器件屬性值與核心層SdioDevice成員的默認值或限制范圍有密切關系。本例只有一個SDIO控制器,如有多個器件信息,則需要在device_info.hcs文件增加deviceNode信息,以及在sdio_config文件中增加對應的器件屬性。
獨立服務模式的特點是device_info.hcs文件中設備節點代表著一個設備對象,如果存在多個設備對象,則按需添加,注意服務名與驅動私有數據匹配的關鍵字名稱必須唯一。其中各項參數如表2所示:
表 2?device_info.hcs節點參數說明
成員名 值 policy 驅動服務發布的策略,SDIO設備控制器具體配置為1,表示驅動對內核態發布服務 priority 驅動啟動優先級(0-200),值越大優先級越低。SDIO設備控制器具體配置為30 permission 驅動創建設備節點權限,SDIO設備控制器具體配置為0664 moduleName 驅動名稱,SDIO設備控制器固定為hi3516_mmc_driver serviceName 驅動對外發布服務的名稱,SDIO設備控制器服務名設置為HDF_PLATFORM_MMC_2 deviceMatchAttr 驅動私有數據匹配的關鍵字,SDIO設備控制器設置為hi3516_mmc_sdio -
device_info.hcs 配置參考:
root {device_info {match_attr = "hdf_manager";platform :: host {hostName = "platform_host";priority = 50;device_sdio :: device {device0 :: deviceNode {policy = 1;priority = 70;permission = 0644;moduleName = "HDF_PLATFORM_SDIO"; // 【必要】用于指定驅動名稱,需要與驅動Entry中的moduleName一致。serviceName = "HDF_PLATFORM_MMC_2"; // 【必要】驅動對外發布服務的名稱,必須唯一。deviceMatchAttr = "hisilicon_hi35xx_sdio_0"; // 【必要】用于配置控制器私有數據,要與sdio_config.hcs中對應控制器保持一致。}}}} }
-
sdio_config.hcs 配置參考:
root {platform {sdio_config {template sdio_controller {match_attr = "";hostId = 2; // 【必要】模式固定為2,在mmc_config.hcs有介紹。devType = 2; // 【必要】模式固定為2,在mmc_config.hcs有介紹。}controller_0x2dd1 :: sdio_controller {match_attr = "hisilicon_hi35xx_sdio_0"; // 【必要】需要和device_info.hcs中的deviceMatchAttr值一致。}} }
需要注意的是,新增sdio_config.hcs配置文件后,必須在hdf.hcs文件中包含sdio_config.hcs所在路徑信息,否則配置文件無法生效。
-
-
實例化SDIO設備控制器對象
完成屬性文件配置之后,下一步就是以核心層SdioDevice對象的初始化為核心,包括驅動適配者自定義結構體(傳遞參數和數據),實例化SdioDevice成員SdioDeviceOps(讓用戶可以通過接口來調用驅動底層函數),實現HdfDriverEntry成員函數(Bind、Init、Release)。
-
自定義結構體參考:
從驅動的角度看,自定義結構體是參數和數據的載體,而且sdio_config.hcs文件中的數值會被HDF讀入并通過DeviceResourceIface來初始化結構體成員,一些重要數值也會傳遞給核心層對象。
typedef struct {uint32_t maxBlockNum; // 單個request最大的block個數uint32_t maxBlockSize; // 單個block最大的字節數1~2048uint32_t maxRequestSize; // 單個request最大的字節數1~2048uint32_t enTimeout; // 最大超時時間,單位毫秒,且不能超過一秒。uint32_t funcNum; // 函數編號1~7uint32_t irqCap; // 中斷能力void *data; // 私有數據 } SdioFuncInfo;// SdioDevice是核心層控制器結構體,其中的成員在Bind函數中會被賦值。 struct SdioDevice {struct SdDevice sd;struct SdioDeviceOps *sdioOps;struct SdioRegister sdioReg;uint32_t functions;struct SdioFunction *sdioFunc[SDIO_MAX_FUNCTION_NUMBER];struct SdioFunction *curFunction;struct OsalThread thread; // 中斷線程struct OsalSem sem;bool irqPending;bool threadRunning; };
-
SdioDevice成員鉤子函數結構體SdioDeviceOps的實例化。
static struct SdioDeviceOps g_sdioDeviceOps = {.incrAddrReadBytes = Hi35xxLinuxSdioIncrAddrReadBytes,.incrAddrWriteBytes = Hi35xxLinuxSdioIncrAddrWriteBytes,.fixedAddrReadBytes = Hi35xxLinuxSdioFixedAddrReadBytes,.fixedAddrWriteBytes = Hi35xxLinuxSdioFixedAddrWriteBytes,.func0ReadBytes = Hi35xxLinuxSdioFunc0ReadBytes,.func0WriteBytes = Hi35xxLinuxSdioFunc0WriteBytes,.setBlockSize = Hi35xxLinuxSdioSetBlockSize,.getCommonInfo = Hi35xxLinuxSdioGetCommonInfo,.setCommonInfo = Hi35xxLinuxSdioSetCommonInfo,.flushData = Hi35xxLinuxSdioFlushData,.enableFunc = Hi35xxLinuxSdioEnableFunc,.disableFunc = Hi35xxLinuxSdioDisableFunc,.claimIrq = Hi35xxLinuxSdioClaimIrq,.releaseIrq = Hi35xxLinuxSdioReleaseIrq,.findFunc = Hi35xxLinuxSdioFindFunc,.claimHost = Hi35xxLinuxSdioClaimHost,.releaseHost = Hi35xxLinuxSdioReleaseHost, };
-
Bind函數開發參考。
入參:
HdfDeviceObject是整個驅動對外提供的接口參數,具備HCS配置文件的信息。
返回值:
HDF_STATUS相關狀態 (表3為部分展示,如需使用其他狀態,可參考//drivers/hdf_core/interfaces/inner_api/utils/hdf_base.h中HDF_STATUS的定義)。
表 3?HDF_STATUS相關狀態說明
狀態(值) 問題描述 HDF_ERR_INVALID_OBJECT 控制器對象非法 HDF_ERR_MALLOC_FAIL 內存分配失敗 HDF_ERR_IO I/O?錯誤 HDF_SUCCESS 初始化成功 HDF_FAILURE 初始化失敗 函數說明:
初始化自定義結構體對象,初始化SdioCntlr成員,調用核心層SdioCntlrAdd函數,以及其他驅動適配者自定義初始化操作。
static int32_t Hi35xxLinuxSdioBind(struct HdfDeviceObject *obj) {struct MmcCntlr *cntlr = NULL;int32_t ret;......cntlr = (struct MmcCntlr *)OsalMemCalloc(sizeof(struct MmcCntlr));// 分配內存......cntlr->ops = &g_sdioCntlrOps; // 【必要】struct MmcCntlrOps g_sdioCntlrOps={// .rescanSdioDev = Hi35xxLinuxSdioRescan,};cntlr->hdfDevObj = obj; // 【必要】使HdfDeviceObject與MmcCntlr可以相互轉化的前提obj->service = &cntlr->service; // 【必要】使HdfDeviceObject與MmcCntlr可以相互轉化的前提ret = Hi35xxLinuxSdioCntlrParse(cntlr, obj); // 【必要】初始化cntlr的index、devType,失敗則goto _ERR。......ret = MmcCntlrAdd(cntlr); // 【必要】調用核心層mmc_core.c的函數,失敗則goto _ERR。......ret = MmcCntlrAllocDev(cntlr, (enum MmcDevType)cntlr->devType); // 【必要】調用核心層mmc_core.c的函數,失敗則goto _ERR。......MmcDeviceAddOps(cntlr->curDev, &g_sdioDeviceOps); // 【必要】調用核心層mmc_core.c的函數,鉤子函數掛載。HDF_LOGD("Hi35xxLinuxSdioBind: Success!");return HDF_SUCCESS;_ERR:Hi35xxLinuxSdioDeleteCntlr(cntlr);HDF_LOGE("Hi35xxLinuxSdioBind: Fail!");return HDF_FAILURE; }
-
Init函數開發參考。
入參:
HdfDeviceObject是整個驅動對外提供的接口參數,具備HCS配置文件的信息。
返回值:
HDF_STATUS相關狀態。
函數說明:
無操作,可根據驅動適配者需要添加。
static int32_t Hi35xxLinuxSdioInit(struct HdfDeviceObject *obj) {(void)obj; // 無操作,可根據驅動適配者的需要進行添加HDF_LOGD("Hi35xxLinuxSdioInit: Success!");return HDF_SUCCESS; }
-
Release函數開發參考。
入參:
HdfDeviceObject是整個驅動對外提供的接口參數,具備HCS配置文件的信息。
返回值:
無。
函數說明:
釋放內存和刪除控制器,該函數需要在驅動入口結構體中賦值給Release接口,當HDF框架調用Init函數初始化驅動失敗時,可以調用Release釋放驅動資源。
?說明:
所有強制轉換獲取相應對象的操作前提是在Bind函數中具備對應賦值的操作。static void Hi35xxLinuxSdioRelease(struct HdfDeviceObject *obj) {if (obj == NULL) {return;}Hi35xxLinuxSdioDeleteCntlr((struct MmcCntlr *)obj->service); // 【必要】自定義的內存釋放函數,這里有HdfDeviceObject到MmcCntlr的強制轉換 }
-
-
驅動調試
【可選】針對新增驅動程序,建議驗證驅動基本功能,例如SDIO控制狀態,中斷響應情況,讀寫數據是否成功等。