摘要
OpenHarmony系統中使用了liteos-m、liteos-a、linux三種內核,工作隊列是linux內核引入的一種異步處理機制。本文對liteos-a內核下工作隊列的實現原理進行分析,并對芯海科技的PPG芯片CS1262接入OpenHarmony過程中對工作隊列的使用方法進行總結分享。
工作隊列工作隊列(Workqueue)是linux內核引入的一種異步進程調用的機制,允許內核代碼請求在將來某個時間調用一個函數。在內核源碼的documentation中也有對workqueue的說明,路徑為Documentation/core-api/workqueue.rst。網絡上也有很多對工作隊列機制的總結文章可以學習,《Linux workqueue工作原理》講解的就比較詳細。簡單理解就是先創建一個隊列用于存放work,并創建一個處理線程叫worker;用戶進程通過調用接口往隊列里面添加work驅動著worker來處理,如下:

而OpenHarmony系統根據不同的使用場景使用了liteos-m/liteos-a/linux三種內核。
這里通過CS1262驅動的實現,對liteos-a內核中工作隊列的使用及實現做一下分析。
工作隊列的使用方法
Sensor設備作為外接設備重要組成模塊,Sensor驅動模型為上層Sensor服務系統提供穩定的Sensor基礎能力接口,包括Sensor列表查詢、Sensor啟停、Sensor訂閱及去訂閱,Sensor參數配置等功能。傳感器驅動模型總體框架如圖1所示。
OpenHarmony系統對workqueue提供了幾個接口,以方便用戶的使用。
1. 調用HdfWorkQueueInit,傳入queue名稱,創建并初始化一個workqueue
2. 調用HdfWorkInit,可以理解為初始化一個work的模板,主要記錄處理這個queue里面work的回調函數func以及參數para信息,類似于linux的work_struct
3. 通過調用HdfAddWork往workqueue中添加work,觸發調用與此queue關聯的回調函數func
步驟1~2可以在CS1262驅動HdfDriverEntry對象的Init接口中看到
int32_t InitPpgDriver(struct HdfDeviceObject *device){ CHECK_NULL_PTR_RETURN_VALUE(device, HDF_ERR_INVALID_PARAM); struct PpgDrvData *drvData = (struct PpgDrvData *)device->service; CHECK_NULL_PTR_RETURN_VALUE(drvData, HDF_ERR_INVALID_PARAM);
if (HdfWorkQueueInit(&drvData->ppgWorkQueue, HDF_PPG_WORK_QUEUE) != HDF_SUCCESS) { HDF_LOGE("%s: Ppg init work queue failed", __func__); return HDF_FAILURE; }
if (HdfWorkInit(&drvData->ppgWork, PpgDataWorkEntry, drvData) != HDF_SUCCESS) { HDF_LOGE("%s: Ppg create thread failed", __func__); return HDF_FAILURE; }
drvData->initStatus = true; drvData->enable = false; drvData->detectFlag = false;
HDF_LOGI("%s: init Ppg driver success", __func__); return HDF_SUCCESS;}步驟3在CS1262的中斷處理函數中,有數據需要上報時會產生一個中斷,中斷處理中添加一個work通過工作隊列機制來實現數據的上報。
static int32_t PpgReadInt(uint16_t gpio, void *data){ struct PpgDrvData *drvData = PpgGetDrvData(); CHECK_PPG_INIT_RETURN_VALUE(drvData, HDF_ERR_NOT_SUPPORT);
if (!drvData->enable) { HDF_LOGE("%s: ppg not enabled", __func__); return HDF_SUCCESS; }
if (!HdfAddWork(&drvData->ppgWorkQueue, &drvData->ppgWork)) { HDF_LOGE("%s: Ppg add work queue failed", __func__); return HDF_FAILURE; }
return HDF_SUCCESS;}CS1262工作對列中work的回調接口實現,兩個主要功能:獲取數據,數據上報。
static void PpgDataWorkEntry(void *arg){ int32_t ret; struct PpgDrvData *drvData = (struct PpgDrvData *)arg; uint16_t readLen = 0;
CHECK_NULL_PTR_RETURN(drvData); CHECK_NULL_PTR_RETURN(drvData->chipData.opsCall.ReadData);
ret = drvData->chipData.opsCall.ReadData(g_fifoBuf, sizeof(g_fifoBuf), &readLen); if ((ret != HDF_SUCCESS) || (readLen > sizeof(g_fifoBuf))) { HDF_LOGE("%s: Ppg read data failed", __func__); return; }
if (PpgReportInt(g_fifoBuf, readLen) != HDF_SUCCESS) { HDF_LOGE("%s: Cs1262ReportInt fail", __func__); }}說明:當前CS1262驅動已提交PR,還未正式上庫
Liteos-a內核中工作隊列的實現
1.HdfWorkQueueInit接口
(1)在liteos-a系統源碼中搜索,接口定義如下:
源文件:drivers/adapter/khdf/liteos/osal/src/osal_workqueue.c代碼如下:int32_t HdfWorkQueueInit(HdfWorkQueue *queue, char *name){......
queue->realWorkQueue = create_singlethread_workqueue(name);
......
return HDF_SUCCESS;}(2)create_singlethread_workqueue接口實現如下
源文件:kernel/liteos_a/bsd/compat/linuxkpi/include/linux/workqueue.h
宏定義:#define create_singlethread_workqueue(name) \ linux_create_singlethread_workqueue(name)
源文件:kernel/liteos_a/bsd/compat/linuxkpi/src/linux_workqueue.c代碼如下:struct workqueue_struct *linux_create_singlethread_workqueue(char *name){ return __create_workqueue_key(name, 1, 0, 0, NULL, NULL);}(3)__create_workqueue_key中初始化化了一個event后面會用;創建一個workqueueThread線程用來處理這個workqueue里的所有work
源文件:kernel/liteos_a/bsd/compat/linuxkpi/src/linux_workqueue.c代碼如下:struct workqueue_struct *__create_workqueue_key(char *name, int singleThread, int freezeable, int rt, struct lock_class_key *key, const char *lockName){......
(VOID)LOS_EventInit(&wq->wq_event);
if (singleThread) { cwq = InitCpuWorkqueue(wq, singleThread); ret = CreateWorkqueueThread(cwq, singleThread); } else { LOS_MemFree(m_aucSysMem0, wq->cpu_wq); LOS_MemFree(m_aucSysMem0, wq); return NULL; }
if (ret) { destroy_workqueue(wq); wq = NULL; }
return wq;}(4)LOS_EventInit就是liteos系統的task之間通信的事件機制實現
(5)CreateWorkqueueThread就是調用的liteos的LOS_TaskCreate來創建一個Task(也即thread),處理函數為WorkerThread,如下
源文件:kernel/liteos_a/bsd/compat/linuxkpi/src/linux_workqueue.c代碼如下:STATIC UINT32 CreateWorkqueueThread(cpu_workqueue_struct *cwq, INT32 cpu){ ......
taskInitParam.pfnTaskEntry = (TSK_ENTRY_FUNC)WorkerThread;
......
ret = LOS_TaskCreate(&cwq->wq->wq_id, &taskInitParam);
......
return LOS_OK;}
STATIC VOID WorkerThread(cpu_workqueue_struct *cwqParam){ cpu_workqueue_struct *cwq = cwqParam;
for (;;) { if (WorkqueueIsEmpty(cwq)) {(VOID)LOS_EventRead(&(cwq->wq->wq_event),0x01,LOS_WAITMODE_OR|LOS_WAITMODE_CLR,LOS_WAIT_FOREVER); } RunWorkqueue(cwq); }}線程處理函數里面就是一個死循環,當workqueue中為空時,代碼會阻塞在LOS_EventRead處,讀一個還未發生的事件時,代碼就會在此處一直阻塞,直到事件發生;
(6)在事件發生(有work可以處理)時,就會調用真正的處理接口RunWorkqueue,對work調用回調函數(例如上面CS1262中的PpgDataWorkEntry)
源文件:kernel/liteos_a/bsd/compat/linuxkpi/src/linux_workqueue.c代碼如下:STATIC VOID RunWorkqueue(cpu_workqueue_struct *cwq){ ...... if (!WorkqueueIsEmpty(cwq)) { ...... func = work->func; func(work); ...... } LOS_SpinUnlockRestore(&g_workqueueSpin, intSave);}2.HdfWorkInit接口
(1)接口實現如下
源文件:drivers/adapter/khdf/liteos/osal/src/osal_workqueue.c代碼如下:int32_t HdfWorkInit(HdfWork *work, HdfWorkFunc func, void *para){ ......
wrapper = (struct WorkWrapper *)OsalMemCalloc(sizeof(*wrapper)); if (wrapper == NULL) { HDF_LOGE("%s malloc fail", __func__); return HDF_ERR_MALLOC_FAIL; } realWork = &(wrapper->work.work); wrapper->workFunc = func; wrapper->para = para;
INIT_WORK(realWork, WorkEntry); work->realWork = wrapper;
return HDF_SUCCESS;}(2)從這里看這個處理函數func賦值給了wrapper->workFunc,而在INIT_WORK中將WorkEntry接口賦值給了work的func
源文件:drivers/adapter/khdf/liteos/osal/src/osal_workqueue.c代碼如下:#ifdef WORKQUEUE_SUPPORT_PRIORITY#define INIT_WORK(work, callbackFunc) do { \ INIT_LIST_HEAD(&((work)->entry)); \ (work)->func = (callbackFunc); \ (work)->data = (atomic_long_t)(0); \ (work)->work_status = 0; \ (work)->work_pri = OS_WORK_PRIORITY_DEFAULT; \} while (0)#else#define INIT_WORK(work, callbackFunc) do { \ INIT_LIST_HEAD(&((work)->entry)); \ (work)->func = (callbackFunc); \ (work)->data = (atomic_long_t)(0); \ (work)->work_status = 0; \} while (0)#endif(3)從前面分析,有work需要處理時調用的就是這個(work)->func;而這里看這個接口的值是WorkEntry,怎么跟驅動側輸入的處理接口聯系的呢?就是這個WorkEntry實現的
源文件:drivers/adapter/khdf/liteos/osal/src/osal_workqueue.c代碼如下:static void WorkEntry(struct work_struct *work){ struct WorkWrapper *wrapper = NULL; if (work != NULL) { wrapper = (struct WorkWrapper *)work; if (wrapper->workFunc != NULL) { wrapper->workFunc(wrapper->para); } else { HDF_LOGE("%s routine null", __func__); } } else { HDF_LOGE("%s work null", __func__); }}從這里看就是調用的wrapper->workFunc,也即HdfWorkInit接口傳入的回調函數。
3.HdfAddWork接口
(1)接口定義如下
源文件:drivers/adapter/khdf/liteos/osal/src/osal_workqueue.c代碼如下:bool HdfAddWork(HdfWorkQueue *queue, HdfWork *work){ ......
return queue_work(queue->realWorkQueue, &((struct WorkWrapper *)work->realWork)->work.work);}(2)queue_work接口是一個宏,定義如下
源文件:drivers/adapter/khdf/liteos/osal/src/osal_workqueue.c代碼如下:#define queue_work(wq, work) \ linux_queue_work(wq, work)
源文件:kernel/liteos_a/bsd/compat/linuxkpi/src/linux_workqueue.c
linux_queue_work最終調用的接口是InsertWork,中間的調用如下
linux_queue_work() |--> QueueWorkOn() |--> QueueWork() |-->InsertWork()
接口實現:STATIC VOID InsertWork(cpu_workqueue_struct *cwq, struct work_struct *work, struct list_head *head, UINT32 *intSave){#ifdef WORKQUEUE_SUPPORT_PRIORITY WorkListAdd(&work->entry, head, work->work_pri);#else WorkListAddTail(&work->entry, head);#endif LOS_SpinUnlockRestore(&g_workqueueSpin, *intSave); (VOID)LOS_EventWrite(&(cwq->wq->wq_event), 0x01); LOS_SpinLockSave(&g_workqueueSpin, intSave);}從實現上看這里就是寫了一個Event,還記得前面在init里面一個阻塞在讀Event的嗎?這里的LOS_EventWrite就代表了事件發生,然后就可以正常處理work了
總結
OpenHarmony的liteos-a內核中工作隊列的實現就是參照linux內核的實現,只是底層使用的是嵌入式系統中事件處理機制。
OpenHarmony在內核上層做了一層封裝(OSAL),在使用linux和liteos-a內核時工作隊列的使用方式統一,驅動開發時無感知。
芯海科技作為OpenHarmony項目群B類捐贈人,已加入DriverFramework SIG和DevBoard SIG。在DriverFramework SIG中負責心率傳感器PPG驅動模型和HDI的實現。CS1262是芯海科技最新推出的一款用于光電血管容積圖(PPG)信號采集的高端模擬前端(AFE),通過SPI總線與主控通信。關鍵性能指標達到業界一流水平:
◆ 高精度測量:最高PPG SNR高達110dB
◆ 超強抗干擾:PSRR ≥ 90dB(0.5Hz~10MHz 范圍內的Boost噪聲)
◆ 低功耗:83uA@100Hz@4Ch@2階環境光
◆ 全膚色支持:單路LED Driver最大電流可達125mA,兩路合并后支持250mA
◆ 高可靠:支持過溫保護/關鍵寄存器保護/LED過流保護/SPI通訊可靠性check
◆ 易用性:支持1/2階環境光消減/硬件佩戴檢測/自動調光/動態配置刷新
-
芯片
+關注
關注
463文章
54007瀏覽量
465902 -
OpenHarmony
+關注
關注
33文章
3952瀏覽量
21093
發布評論請先 登錄
ALED1262ZT汽車級12通道LED驅動芯片詳解
CS5801搭配AS721芯片實現HDMI轉DP雙向互轉方案
【「Linux 設備驅動開發(第 2 版)」閱讀體驗】Linux內核開發基礎
芯品速遞 | 芯海科技CS32C010:以32位性能革新8位MCU場景應用
在qemu上體驗芯來RISC-V處理器運行鴻蒙LiteOS-M內核
芯海單片機 燒錄器提示“燒錄文件錯誤”怎么解決
請教一些關于芯海CS1262芯片的問題
[開發工具] CS32L015的相關資料,基于芯海科技MCU的小尺寸彩屏顯示解決方案
Kubernetes集群運維經驗總結
昂科燒錄器支持ChipSea芯海科技的32位微控制器CS32F030C8T
芯品速遞 | 芯海科技CS32G501:高性能、高集成、安全型32位MCU新旗艦
Liteos-a內核工作隊列的實現原理分析及經驗總結——芯海科技PPG芯片CS1262接入OpenHarmony實戰
評論