摘要
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) 在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);
}
(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接口传入的回调函数。
(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阶环境光消减/硬件佩戴检测/自动调光/动态配置刷新
全部0条评论
快来发表一下你的评论吧 !