OpenHarmony HDF WIFI 之 FlowControl 原创 精华
Flow Contrl
WIFI驱动可以选择是否使用Flow Control来缓存网络数据的收发。
以下是Flow Control的定义,可以看到有两个线程分别负责处理收发数据的逻辑,有对应的两个信号量来完成线程的同步,以及由进程状态的变量,还有由netbuf组成的两个收发队列,这些是FlowControl模块使用到的数据。
struct FlowControlModule {
OSAL_DECLARE_THREAD(txTransferThread); //发送数据
OSAL_DECLARE_THREAD(rxTransferThread); //接收数据
struct OsalSem sem[FLOW_DIR_COUNT]; //线程信号量
FcThreadStatus threadStatus[FLOW_DIR_COUNT]; //线程状态
struct FlowControlQueues fcmQueue[FLOW_DIR_COUNT]; //netuf缓存队列
struct FlowControlOp *op; //驱动实现
struct FlowControlInterface *interface; //提供给开发者的接口
void *fcmPriv; /**< Private data of the flow control module */
};
然后还提供给开发者接口来操作FlowControl模块,例如FlowControlInterface,其定义在下面。根据g_fcInterface给我们提供的接口,可以知道开发者使用FlowControl模块主要就是向指定队列发送netbuf就完事了。当然还有最重要的,使用RegisterFlowControlOp注册一个FlowControlOp结构体。
static struct FlowControlInterface g_fcInterface = {
.setQueueThreshold = SetQueueThreshold, //设置队列最大值
.getQueueIdByEtherBuff = GetQueueIdByEtherBuff, //获取队列buff
.sendBuffToFCM = SendBuffToFCM, //发送netbuf到队列
.schedFCM = SchedTransfer, //调度线程
.registerFlowControlOp = RegisterFlowControlOp, //注册FlowControlOp结构体
};
FlowControlOp由驱动开发者根据具体的wifi芯片实现,负责操作wifi 芯片的逻辑。
struct FlowControlOp {
//检查wifi芯片是否处于station或p2p模式
bool (*isDeviceStaOrP2PClient)(void);
//发送队列中的数据到网口
int32_t (*txDataPacket)(NetBufQueue *q, void *fcmPrivate, int32_t fwPriorityId);
//发送队列中的数据到协议栈
int32_t (*rxDataPacket)(NetBufQueue *q, void *fcmPrivate, int32_t fwPriorityId);
//获取发送队列id
FlowControlQueueID (*getTxQueueId)(const void *para);
//获取接收队列id
FlowControlQueueID (*getRxQueueId)(const void *para);
//获取发送队列优先级
int32_t (*getTxPriorityId)(FlowControlQueueID id);
//获取接收队列优先级
int32_t (*getRxPriorityId)(FlowControlQueueID id);
};
一、创建FlowControlModule
创建FlowContriol模块很简单,就是初始化所有成员变量。
struct FlowControlModule *InitFlowControl(void *fcmPriv)
{
//创建内存
struct FlowControlModule *fcm = NULL;
fcm = (struct FlowControlModule *)OsalMemCalloc(sizeof(struct FlowControlModule));
(void)memset_s(fcm, sizeof(struct FlowControlModule), 0, sizeof(struct FlowControlModule));
//初始化两个队列
FlowControlQueueInit(fcm);
//初始化信号量
for (i = 0; i < FLOW_DIR_COUNT; i++) {
if (OsalSemInit(&fcm->sem[i], 0) != HDF_SUCCESS) {
OsalMemFree(fcm);
return NULL;
}
}
//初始化线程
if (CreateFlowControlTask(fcm) != HDF_SUCCESS) {
return NULL;
}
//注册接口
fcm->interface = &g_fcInterface;
fcm->fcmPriv = fcmPriv;
g_fcm = fcm;
return fcm;
}
1.1、收发线程
在创建flowcontrol 线程中可以看到两个线程的优先级是最高的,因为网络数据需要及时处理,否则会导致队列溢出。
int32_t CreateFlowControlTask(struct FlowControlModule *fcm)
{
struct OsalThreadParam config = {
.priority = OSAL_THREAD_PRI_HIGHEST, //最高的优先级
.stackSize = 0, //为什么栈大小为0?
};
int32_t ret = CreateTask(&fcm->txTransferThread, RX_THREAD_NAME, RunWiFiTxFlowControl, &config, fcm);
ret = CreateTask(&fcm->rxTransferThread, TX_THREAD_NAME, RunWiFiRxFlowControl, &config, fcm);
}
两个线程使用同一套代码,根据参数dir来判断是接收还是发送。
static int32_t RunWiFiFlowControl(void *para, FlowDir dir)
{
struct FlowControlModule *fcm = (struct FlowControlModule *)para;
while (true) {
fcm->threadStatus[dir] = THREAD_WAITING;
//等待信号量同步
if (OsalSemWait(&fcm->sem[dir], HDF_WAIT_FOREVER) != HDF_SUCCESS) {
HDF_LOGE("%s exit: OsalSemWait return false!", __func__);
continue;
}
fcm->threadStatus[dir] = THREAD_RUNNING;
//分别处理收发逻辑
if (dir == FLOW_TX) {
FlowControlTxTreadProcess(fcm);
} else if (dir == FLOW_RX) {
FlowControlRxTreadProcess(fcm);
}
}
}
1.2、队列
我们知道无论什么类型的网络数据,最终都要进入队列,因为该模块的队列比较多,我们先看看有队列是什么以及有哪些队列:
FlowControlQueue定义如下,其本质是在NetBufQueue的基础上,增加一些变量来给FlowControl管理。
struct FlowControlQueue {
FlowControlQueueID queueID; //见下文
NetBufQueue dataQueue; //由netbuf组成的队列
uint32_t queueThreshold; //网络数据最大值
OsalSpinlock lock; /**< Queue lock */
uint32_t pktCount; /**< Number of packets received by the network data queue */
};
FlowControlQueueID是用于表示一个队列组所拥有的所有类型的队列,一个队列组包含了9个不同等级(类型)的队列,这些队列是有优先级的。例如其定义:
typedef enum {
CTRL_QUEUE_ID = 0, /**< Control queue ID */
VIP_QUEUE_ID, /**< VIP queue ID */
NORMAL_QUEUE_ID, /**< Normal queue ID */
TCP_DATA_QUEUE_ID, /**< TCP data queue ID */
TCP_ACK_QUEUE_ID, /**< TCP ACK queue ID */
BK_QUEUE_ID, /**< Background flow queue ID */
BE_QUEUE_ID, /**< Best-effort flow queue ID */
VI_QUEUE_ID, /**< Video flow queue ID */
VO_QUEUE_ID, /**< Voice flow queue ID */
QUEUE_ID_COUNT /**< Total number of queue IDs */
} FlowControlQueueID;
在全局变量中,就有如下四个队列组,其意义见代码注释
//sta模式发送线程所拥有的队列类型
static FlowControlQueueID g_staPriorityMapTx[QUEUE_ID_COUNT] = {
CTRL_QUEUE_ID, VIP_QUEUE_ID, NORMAL_QUEUE_ID, TCP_ACK_QUEUE_ID, TCP_DATA_QUEUE_ID, VO_QUEUE_ID, VI_QUEUE_ID,
BE_QUEUE_ID, BK_QUEUE_ID
};
//sta模式接收线程所拥有的队列类型
static FlowControlQueueID g_staPriorityMapRx[QUEUE_ID_COUNT] = {
CTRL_QUEUE_ID, VIP_QUEUE_ID, NORMAL_QUEUE_ID, TCP_DATA_QUEUE_ID, TCP_ACK_QUEUE_ID, VO_QUEUE_ID, VI_QUEUE_ID,
BE_QUEUE_ID, BK_QUEUE_ID
};
//应该是ap模式下的
static FlowControlQueueID g_priorityMapTx[QUEUE_ID_COUNT] = {
CTRL_QUEUE_ID, VIP_QUEUE_ID, NORMAL_QUEUE_ID, TCP_DATA_QUEUE_ID, TCP_ACK_QUEUE_ID, VO_QUEUE_ID, VI_QUEUE_ID,
BE_QUEUE_ID, BK_QUEUE_ID
};
static FlowControlQueueID g_priorityMapRx[QUEUE_ID_COUNT] = {
CTRL_QUEUE_ID, VIP_QUEUE_ID, NORMAL_QUEUE_ID, TCP_ACK_QUEUE_ID, TCP_DATA_QUEUE_ID, VO_QUEUE_ID, VI_QUEUE_ID,
BE_QUEUE_ID, BK_QUEUE_ID
};
所以,收发线程的作用就是负责把队列中的netbuf送到他们该去的地方。发送的netbuf就应该传递给网口驱动,而接收到的netbuf就该传递给协议栈。
//发送线程处理队列中的数据
static void FlowControlTxTreadProcess(struct FlowControlModule *fcm)
{
//sta发送队列组
if (isSta) {
//遍历队列组的所有队列
for (i = 0; i < FLOW_CONTROL_MAP_SIZE; i++) {
SendFlowControlQueue(fcm, g_staPriorityMapTx[i], FLOW_TX);
}
} else {
//同上
for (i = 0; i < FLOW_CONTROL_MAP_SIZE; i++) {
SendFlowControlQueue(fcm, g_priorityMapTx[i], FLOW_TX);
}
}
}
FlowControlTxTreadProcess和FlowControlRxTreadProcess最后会调用SendFlowControlQueue来入队,最终调用txDataPacket或rxDataPacket来发送接受数据。
int32_t SendFlowControlQueue(struct FlowControlModule *fcm, uint32_t id, uint32_t dir)
{
//获取指定队列
q = &fcm->fcmQueue[dir].queues[id].dataQueue;
if (dir == FLOW_TX) {
if (fcm->op != NULL && fcm->op->getTxPriorityId != NULL) {
//获取队列优先级
fwPriorityId = fcm->op->getTxPriorityId(id);
}
if (fcm->op != NULL && fcm->op->txDataPacket != NULL) {
//调用FlowControlOp的发送函数,把队列中的netbuf通过网口发送
fcm->op->txDataPacket(q, fcm->fcmPriv, fwPriorityId);
} else {
HDF_LOGE("%s fail : fcm->op->txDataPacket = null!", __func__);
return HDF_ERR_INVALID_PARAM;
}
}
if (dir == FLOW_RX) {
if (fcm->op != NULL && fcm->op->getRxPriorityId != NULL) {
rxPriorityId = fcm->op->getRxPriorityId(id);
}
if (fcm->op != NULL && fcm->op->rxDataPacket != NULL) {
//调用FlowControlOp的接收函数,把队列中的netbuf传递给协议栈
fcm->op->rxDataPacket(q, fcm->fcmPriv, rxPriorityId);
} else {
HDF_LOGE("%s fail : fcm->op->txDataPacket = null!", __func__);
return HDF_ERR_INVALID_PARAM;
}
}
return HDF_SUCCESS;
}
二、FlowControlInterface
既然我们了解了FlowControl模块的线程的主要作用,即将数据入队。那么是由谁来发起(同步)线程的运行呢?这就需要使用到FlowControlInterface,开发者调用FlowControlInterface的接口来实现对队列的操作。我们重点关注sendBuffToFCM()和schedFCM()
struct FlowControlInterface {
//根据netbuf获取到队列的id
FlowControlQueueID (*getQueueIdByEtherBuff)(const NetBuf *buff);
//设置队列最大容量
int32_t (*setQueueThreshold)(struct FlowControlModule *fcm, uint32_t queueThreshold, uint32_t id, uint32_t dir);
//发送netbuf到队列
int32_t (*sendBuffToFCM)(struct FlowControlModule *fcm, NetBuf *buff, uint32_t id, uint32_t dir);
//发送信号量,同步线程
int32_t (*schedFCM)(struct FlowControlModule *fcm, FlowDir dir);
//注册FlowControlOp
int32_t (*registerFlowControlOp)(struct FlowControlModule *fcm, struct FlowControlOp *op);
};
2.1、schedFCM
将netbuf推进到指定队列:
static int32_t SendBuffToFCM(struct FlowControlModule *fcm, NetBuf *buff, uint32_t id, uint32_t dir)
{
struct FlowControlQueue *fcmQueue = NULL;
NetBufQueue *dataQ = NULL;
//检查参数
if (!IsValidSentToFCMPra(fcm, id, dir)) {
HDF_LOGE("%s fail : IsValidSentToFCMPra FALSE!", __func__);
return HDF_ERR_INVALID_PARAM;
}
//获取队列
fcmQueue = &fcm->fcmQueue[dir].queues[id];
//获取netbuf队列
dataQ = &fcmQueue->dataQueue;
//检查fcmQueue的容量
FcmQueuePreProcess(fcmQueue);
if (NetBufQueueIsEmpty(dataQ)) {
fcm->fcmQueue[dir].queues[id].pktCount = 0;
}
//netbuf入队
NetBufQueueEnqueue(dataQ, buff);
//数据包+1
fcm->fcmQueue[dir].queues[id].pktCount++;
return HDF_SUCCESS;
}
一般驱动程序在调用SendBuffToFCM()后,还要调用SchedTransfer()来同步线程,使线程即使的处理队列中的netbuf
static int32_t SchedTransfer(struct FlowControlModule *fcm, FlowDir dir)
{
OsalSemPost(&fcm->sem[dir]);
return HDF_SUCCESS;
}