OpenHarmony平臺驅動開發(十)
MMC
概述
功能簡介
MMC(MultiMedia Card)即多媒體卡,是一種用于固態非易失性存儲的小體積大容量的快閃存儲卡。
MMC后續泛指一個接口協定(一種卡式),能符合這種接口的內存器都可稱作MMC儲存體。主要包括幾個部分:MMC控制器、MMC總線、存儲卡(包括MMC卡、SD卡、SDIO卡、TF卡)。
MMC、SD、SDIO總線,其總線規范類似,都是從MMC總線規范演化而來的。MMC強調的是多媒體存儲;SD強調的是安全和數據保護;SDIO是從SD演化出來的,強調的是接口,不再關注另一端的具體形態(可以是WIFI設備、Bluetooth設備、GPS等等)。
基本概念
-
SD卡(Secure Digital Memory Card)
SD卡即安全數碼卡。它是在MMC的基礎上發展而來,SD卡強調數據的安全,可以設定存儲內容的使用權限,防止數據被他人復制。在數據傳輸和物理規范上,SD卡(24mm*32mm*2.1mm,比MMC卡更厚一點),向前兼容了MMC卡。所有支持SD卡的設備也支持MMC卡。
-
SDIO(Secure Digital Input and Output)
即安全數字輸入輸出接口。SDIO是在SD規范的標準上定義的一種外設接口,它相較于SD規范增加了低速標準,可以用最小的硬件開銷支持低速I/O。SDIO接口兼容以前的SD內存卡,也可以連接SDIO接口的設備。
運作機制
在HDF框架中,MMC的接口適配模式采用獨立服務模式(如圖1所示)。在這種模式下,每一個設備對象會獨立發布一個設備服務來處理外部訪問,設備管理器收到API的訪問請求之后,通過提取該請求的參數,達到調用實際設備對象的相應內部方法的目的。獨立服務模式可以直接借助HDFDeviceManager的服務管理能力,但需要為每個設備單獨配置設備節點,增加內存占用。
獨立服務模式下,核心層不會統一發布一個服務供上層使用,因此這種模式下驅動要為每個控制器發布一個服務,具體表現為:
-
驅動適配者需要實現HdfDriverEntry的Bind鉤子函數以綁定服務。
-
device_info.hcs文件中deviceNode的policy字段為1或2,不能為0。
MMC模塊各分層作用:
-
接口層提供打開MMC設備、檢查MMC控制器是否存在設備、關閉MMC設備的接口。
-
核心層主要提供MMC控制器、移除和管理的能力,還有公共控制器業務。通過鉤子函數與適配層交互。
-
適配層主要是將鉤子函數的功能實例化,實現具體的功能。
圖 1 MMC獨立服務模式結構圖
開發指導
場景介紹
MMC用于多媒體文件的存儲,當驅動開發者需要將MMC設備適配到OpenHarmony時,需要進行MMC驅動適配。下文將介紹如何進行MMC驅動適配。
接口說明
為了保證上層在調用MMC接口時能夠正確的操作MMC控制器,核心層在//drivers/hdf_core/framework/model/storage/include/mmc/mmc_corex.h中定義了以下鉤子函數,驅動適配者需要在適配層實現這些函數的具體功能,并與鉤子函數掛接,從而完成適配層與核心層的交互。
MmcCntlrOps定義:
struct MmcCntlrOps {int32_t (*request)(struct MmcCntlr *cntlr, struct MmcCmd *cmd);int32_t (*setClock)(struct MmcCntlr *cntlr, uint32_t clock);int32_t (*setPowerMode)(struct MmcCntlr *cntlr, enum MmcPowerMode mode);int32_t (*setBusWidth)(struct MmcCntlr *cntlr, enum MmcBusWidth width);int32_t (*setBusTiming)(struct MmcCntlr *cntlr, enum MmcBusTiming timing);int32_t (*setSdioIrq)(struct MmcCntlr *cntlr, bool enable);int32_t (*hardwareReset)(struct MmcCntlr *cntlr);int32_t (*systemInit)(struct MmcCntlr *cntlr);int32_t (*setEnhanceStrobe)(struct MmcCntlr *cntlr, bool enable);int32_t (*switchVoltage)(struct MmcCntlr *cntlr, enum MmcVolt volt);bool (*devReadOnly)(struct MmcCntlr *cntlr);bool (*devPlugged)(struct MmcCntlr *cntlr);bool (*devBusy)(struct MmcCntlr *cntlr);int32_t (*tune)(struct MmcCntlr *cntlr, uint32_t cmdCode);int32_t (*rescanSdioDev)(struct MmcCntlr *cntlr);
};
表 1 MmcCntlrOps結構體成員的鉤子函數功能說明
成員函數 | 入參 | 返回值 | 功能 |
---|---|---|---|
doRequest | cntlr:結構體指針,核心層MMC控制器 cmd:結構體指針,傳入命令值 | HDF_STATUS相關狀態 | request相應處理 |
setClock | cntlr:結構體指針,核心層MMC控制器 clock:uint32_t類型,時鐘傳入值 | HDF_STATUS相關狀態 | 設置時鐘頻率 |
setPowerMode | cntlr:結構體指針,核心層MMC控制器 mode:枚舉值(見MmcPowerMode定義),功耗模式 | HDF_STATUS相關狀態 | 設置功耗模式 |
setBusWidth | cntlr:核心層結構體指針,核心層MMMC控制器 width:枚舉類型(見MmcBusWidth定義),總線帶寬 | HDF_STATUS相關狀態 | 設置總線帶寬 |
setBusTiming | cntlr:結構體指針,核心層MMC控制器 timing:枚舉類型(見MmcBusTiming定義),總線時序 | HDF_STATUS相關狀態 | 設置總線時序 |
setSdioIrq | cntlr:結構體指針,核心層MMC控制器 enable:布爾值,控制中斷 | HDF_STATUS相關狀態 | 使能/去使能SDIO中斷 |
hardwareReset | cntlr:結構體指針,核心層MMC控制器 | HDF_STATUS相關狀態 | 復位硬件 |
systemInit | cntlr:結構體指針,核心層MMC控制器 | HDF_STATUS相關狀態 | 系統初始化 |
setEnhanceStrobe | cntlr:結構體指針,核心層MMC控制器 enable:布爾值,設置功能 | HDF_STATUS相關狀態 | 設置增強選通 |
switchVoltage | cntlr:結構體指針,核心層MMC控制器 volt:枚舉值,電壓值(3.3,1.8,1.2V) | HDF_STATUS相關狀態 | 設置電壓值 |
devReadOnly | cntlr:結構體指針,核心層MMC控制器 | 布爾值 | 檢驗設備是否只讀 |
cardPlugged | cntlr:結構體指針,核心層MMC控制器 | 布爾值 | 檢驗設備是否拔出 |
devBusy | cntlr:結構體指針,核心層MMC控制器 | 布爾值 | 檢驗設備是否忙碌 |
tune | cntlr:結構體指針,核心層MMC控制器 cmdCode:uint32_t類型,命令代碼 | HDF_STATUS相關狀態 | 調諧 |
rescanSdioDev | cntlr:結構體指針,核心層MMC控制器 | HDF_STATUS相關狀態 | 掃描并添加SDIO設備 |
開發步驟
MMC模塊適配包含以下四個步驟:
-
實例化驅動入口
-
配置屬性文件
-
實例化MMC控制器對象
-
驅動調試
開發實例
下方將基于Hi3516DV300開發板以//device/soc/hisilicon/common/platform/mmc/himci_v200/himci.c驅動為示例,展示需要驅動適配者提供哪些內容來完整實現設備功能。
-
實例化驅動入口
驅動入口必須為HdfDriverEntry(在hdf_device_desc.h中定義)類型的全局變量,且moduleName要和device_info.hcs中保持一致。HDF框架會將所有加載的驅動的HdfDriverEntry對象首地址匯總,形成一個類似數組的段地址空間,方便上層調用。
一般在加載驅動時HDF會先調用Bind函數,再調用Init函數加載該驅動。當Init調用異常時,HDF框架會調用Release釋放驅動資源并退出。
MMC驅動入口開發參考:
struct HdfDriverEntry g_mmcDriverEntry = {.moduleVersion = 1,.Bind = HimciMmcBind, // 見Bind參考.Init = HimciMmcInit, // 見Init參考.Release = HimciMmcRelease, // 見Release參考.moduleName = "hi3516_mmc_driver", // 【必要且與HCS文件中里面的moduleName匹配】 }; HDF_INIT(g_mmcDriverEntry); // 調用HDF_INIT將驅動入口注冊到HDF框架中
-
配置屬性文件
完成驅動入口注冊之后,需要在device_info.hcs文件中添加deviceNode信息,deviceNode信息與驅動入口注冊相關。本例以三個MMC控制器為例,如有多個器件信息,則需要在device_info.hcs文件增加對應的deviceNode信息,以及在mmc_config.hcs文件中增加對應的器件屬性。器件屬性值與核心層MmcCntlr成員的默認值或限制范圍有密切關系,需要在mmc_config.hcs中配置器件屬性。
獨立服務模式的特點是device_info.hcs文件中設備節點代表著一個設備對象,如果存在多個設備對象,則按需添加,注意服務名與驅動私有數據匹配的關鍵字名稱必須唯一。其中各項參數如表2所示:
表 2 device_info.hcs節點參數說明
成員名 值 policy 驅動服務發布的策略,MMC控制器具體配置為2,表示驅動對內核態和用戶態都發布服務 priority 驅動啟動優先級(0-200),值越大優先級越低。MMC控制器控制器具體配置為10 permission 驅動創建設備節點權限,MMC控制器控制器具體配置為0664 moduleName 驅動名稱,MMC控制器控制器固定為hi3516_mmc_driver serviceName 驅動對外發布服務的名稱,MMC控制器控制器服務名設置為HDF_PLATFORM_MMC_X,X代表MMC控制器號 deviceMatchAttr 驅動私有數據匹配的關鍵字,MMC控制器控制器設置為hi3516_mmc_X,X代表控制器類型名 -
device_info.hcs 配置參考:
在//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs文件中添加deviceNode描述。
root {device_info {match_attr = "hdf_manager";platform :: host {hostName = "platform_host";priority = 50;device_mmc:: device {device0 :: deviceNode { // 驅動的DeviceNode節點policy = 2; // policy字段是驅動服務發布的策略,如果需要面向用戶態,則為2priority = 10; // 驅動啟動優先級permission = 0644; // 驅動創建設備節點權限moduleName = "hi3516_mmc_driver"; // 【必要】用于指定驅動名稱,需要與驅動Entry中的moduleName一致。serviceName = "HDF_PLATFORM_MMC_0"; // 【必要】驅動對外發布服務的名稱,必須唯一。deviceMatchAttr = "hi3516_mmc_emmc"; // 【必要】用于配置控制器私有數據,要與mmc_config.hcs中對應控制器保持一致。emmc類型。}device1 :: deviceNode {policy = 1;priority = 20;permission = 0644;moduleName = "hi3516_mmc_driver";serviceName = "HDF_PLATFORM_MMC_1";deviceMatchAttr = "hi3516_mmc_sd"; // SD類型}device2 :: deviceNode {policy = 1;priority = 30;permission = 0644;moduleName = "hi3516_mmc_driver";serviceName = "HDF_PLATFORM_MMC_2";deviceMatchAttr = "hi3516_mmc_sdio"; // SDIO類型}......}}} }
-
mmc_config.hcs配置參考:
在//device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/mmc/mmc_config.hcs文件配置器件屬性,其中配置參數如下:
root {platform {mmc_config {template mmc_controller { // 配置模板,如果下面節點使用時繼承該模板,則節點中未聲明的字段會使用該模板中的默認值。match_attr = "";voltDef = 0; // MMC默認電壓,0代表3.3V,1代表1.8V,2代表1.2VfreqMin = 50000; // 【必要】最小頻率值freqMax = 100000000; // 【必要】最大頻率值freqDef = 400000; // 【必要】默認頻率值maxBlkNum = 2048; // 【必要】最大的block號maxBlkSize = 512; // 【必要】最大block大小ocrDef = 0x300000; // 【必要】工作電壓設置相關caps2 = 0; // 【必要】屬性寄存器相關,見mmc_caps.h中MmcCaps2定義。regSize = 0x118; // 【必要】寄存器位寬hostId = 0; // 【必要】主機號regBasePhy = 0x10020000; // 【必要】寄存器物理基地址irqNum = 63; // 【必要】中斷號devType = 2; // 【必要】模式選擇:EMMC、SD、SDIO、COMBOcaps = 0x0001e045; // 【必要】屬性寄存器相關,見mmc_caps.h中MmcCaps定義。}controller_0x10100000 :: mmc_controller {match_attr = "hi3516_mmc_emmc"; // 【必要】需要和device_info.hcs中的deviceMatchAttr值一致hostId = 0;regBasePhy = 0x10100000;irqNum = 96;devType = 0; // eMMC類型caps = 0xd001e045;caps2 = 0x60;}controller_0x100f0000 :: mmc_controller {match_attr = "hi3516_mmc_sd";hostId = 1;regBasePhy = 0x100f0000;irqNum = 62;devType = 1; // SD類型caps = 0xd001e005;}controller_0x10020000 :: mmc_controller {match_attr = "hi3516_mmc_sdio";hostId = 2;regBasePhy = 0x10020000;irqNum = 63;devType = 2; // SDIO類型caps = 0x0001e04d;}}} }
需要注意的是,新增mmc_config.hcs配置文件后,必須在產品對應的hdf.hcs文件中將其包含如下語句所示,否則配置文件無法生效。
#include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/mmc/mmc_config.hcs" // 配置文件相對路徑
-
-
實例化MMC控制器對象
完成配置屬性文件之后,下一步就是以核心層MmcCntlr對象的初始化為核心,包括驅動適配自定義結構體(傳遞參數和數據),實例化MmcCntlr成員MmcCntlrOps(讓用戶可以通過接口來調用驅動底層函數),實現HdfDriverEntry成員函數(Bind、Init、Release)。
-
驅動適配者自定義結構體參考。
從驅動的角度看,自定義結構體是參數和數據的載體,而且mmc_config.hcs文件中的數值會被HDF讀入并通過DeviceResourceIface來初始化結構體成員,一些重要數值也會傳遞給核心層對象。
struct HimciHost {struct MmcCntlr *mmc; // 【必要】核心層控制對象struct MmcCmd *cmd; // 【必要】核心層結構體,傳遞命令,相關命令見枚舉量MmcCmdCodevoid *base; // 地址映射需要,寄存器基地址enum HimciPowerStatus powerStatus;uint8_t *alignedBuff;uint32_t buffLen;struct scatterlist dmaSg;struct scatterlist *sg;uint32_t dmaSgNum;DMA_ADDR_T dmaPaddr;uint32_t *dmaVaddr;uint32_t irqNum;bool isTuning;uint32_t id;struct OsalMutex mutex;bool waitForEvent;HIMCI_EVENT himciEvent; }; // MmcCntlr是核心層控制器結構體,其中的成員在Bind函數中會被賦值。 struct MmcCntlr {struct IDeviceIoService service;struct HdfDeviceObject *hdfDevObj;struct PlatformDevice device;struct OsalMutex mutex;struct OsalSem released;uint32_t devType;struct MmcDevice *curDev;struct MmcCntlrOps *ops;struct PlatformQueue *msgQueue;uint16_t index;uint16_t voltDef;uint32_t vddBit;uint32_t freqMin;uint32_t freqMax;uint32_t freqDef;union MmcOcr ocrDef;union MmcCaps caps;union MmcCaps2 caps2;uint32_t maxBlkNum;uint32_t maxBlkSize;uint32_t maxReqSize;bool devPlugged;bool detecting;void *priv; };
-
MmcCntlr成員鉤子函數結構體MmcCntlrOps的實例化。
static struct MmcCntlrOps g_himciHostOps = {.request = HimciDoRequest,.setClock = HimciSetClock,.setPowerMode = HimciSetPowerMode,.setBusWidth = HimciSetBusWidth,.setBusTiming = HimciSetBusTiming,.setSdioIrq = HimciSetSdioIrq,.hardwareReset = HimciHardwareReset,.systemInit = HimciSystemInit,.setEnhanceStrobe = HimciSetEnhanceStrobe,.switchVoltage = HimciSwitchVoltage,.devReadOnly = HimciDevReadOnly,.devPlugged = HimciCardPlugged,.devBusy = HimciDevBusy,.tune = HimciTune,.rescanSdioDev = HimciRescanSdioDev, };
-
Bind函數開發參考。
入參:
HdfDeviceObject:HDF框架給每一個驅動創建的設備對象,用來保存設備相關的私有數據和服務接口。
返回值:
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_INVALID_PARAM 參數非法 HDF_ERR_IO I/O?錯誤 HDF_SUCCESS 初始化成功 HDF_FAILURE 初始化失敗 函數說明: MmcCntlr、HimciHost、HdfDeviceObject之間互相賦值,方便其他函數可以相互轉化,初始化自定義結構體HimciHost對象,初始化MmcCntlr成員,調用核心層MmcCntlrAdd函數,完成MMC控制器的添加。
static int32_t HimciMmcBind(struct HdfDeviceObject *obj) {struct MmcCntlr *cntlr = NULL;struct HimciHost *host = NULL;int32_t ret;cntlr = (struct MmcCntlr *)OsalMemCalloc(sizeof(struct MmcCntlr));host = (struct HimciHost *)OsalMemCalloc(sizeof(struct HimciHost));host->mmc = cntlr; // 【必要】使HimciHost與MmcCntlr可以相互轉化的前提cntlr->priv = (void *)host; // 【必要】使HimciHost與MmcCntlr可以相互轉化的前提cntlr->ops = &g_himciHostOps; // 【必要】MmcCntlrOps的實例化對象的掛載cntlr->hdfDevObj = obj; // 【必要】使HdfDeviceObject與MmcCntlr可以相互轉化的前提obj->service = &cntlr->service; // 【必要】使HdfDeviceObject與MmcCntlr可以相互轉化的前提ret = MmcCntlrParse(cntlr, obj); // 【必要】 初始化cntlr,失敗就goto _ERR。......ret = HimciHostParse(host, obj); // 【必要】 初始化host對象的相關屬性,失敗就goto _ERR。......ret = HimciHostInit(host, cntlr); // 驅動適配者自定義的初始化,失敗就goto _ERR。......ret = MmcCntlrAdd(cntlr); // 調用核心層函數,失敗就goto _ERR。......(void)MmcCntlrAddDetectMsgToQueue(cntlr); // 將卡檢測消息添加到隊列中。HDF_LOGD("HimciMmcBind: success.");return HDF_SUCCESS; ERR:HimciDeleteHost(host);HDF_LOGD("HimciMmcBind: fail, err = %d.", ret);return ret; }
-
Init函數開發參考。
入參:
HdfDeviceObject:HDF框架給每一個驅動創建的設備對象,用來保存設備相關的私有數據和服務接口。
返回值:
HDF_STATUS相關狀態。
函數說明:
實現ProcMciInit。
static int32_t HimciMmcInit(struct HdfDeviceObject *obj) {static bool procInit = false;(void)obj;if (procInit == false) {if (ProcMciInit() == HDF_SUCCESS) {procInit = true;HDF_LOGD("HimciMmcInit: proc init success.");}}HDF_LOGD("HimciMmcInit: success.");return HDF_SUCCESS; }
-
Release函數開發參考。
入參:
HdfDeviceObject:HDF框架給每一個驅動創建的設備對象,用來保存設備相關的私有數據和服務接口。
返回值:
無。
函數說明:
釋放內存和刪除控制器等操作,該函數需要在驅動入口結構體中賦值給Release接口,當HDF框架調用Init函數初始化驅動失敗時,可以調用Release釋放驅動資源。
說明:
所有強制轉換獲取相應對象的操作前提是在Init函數中具備對應賦值的操作。static void HimciMmcRelease(struct HdfDeviceObject *obj) {struct MmcCntlr *cntlr = NULL;......cntlr = (struct MmcCntlr *)obj->service; // 這里有HdfDeviceObject到MmcCntlr的強制轉化,通過service成員,賦值見Bind函數。......HimciDeleteHost((struct HimciHost *)cntlr->priv); // 驅動適配者自定義的內存釋放函數,這里有MmcCntlr到HimciHost的強制轉化。 }
-
-
驅動調試
【可選】針對新增驅動程序,建議驗證驅動基本功能,例如掛載后的信息反饋,數據讀寫成功與否等。