* Copyright (C) 2021 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "hci_cmd.h"
#include <securec.h>
#include "btm/btm_thread.h"
#include "btstack.h"
#include "platform/include/alarm.h"
#include "platform/include/allocator.h"
#include "platform/include/bt_endian.h"
#include "platform/include/list.h"
#include "platform/include/mutex.h"
#include "platform/include/queue.h"
#include "hci/acl/hci_acl.h"
#include "hci/hci.h"
#include "hci/hci_def.h"
#include "hci/hci_error.h"
#include "hci/hci_failure.h"
#include "hci/hci_internal.h"
#include "hci_cmd_failure.h"
#define MAX_QUEUE_SIZE 32
#define CMD_TIMEOUT (10 * 1000)
#pragma pack(1)
typedef struct {
uint16_t opCode;
uint8_t parameterTotalLength;
} HciCmdHeader;
#pragma pack()
static uint8_t g_numberOfHciCmd = 1;
static Mutex *g_lockNumberOfHciCmd = NULL;
static Queue *g_cmdCache = NULL;
static List *g_processingCmds = NULL;
static Mutex *g_lockProcessingCmds = NULL;
static void HciFreeCmd(void *cmd);
void HciInitCmd()
{
g_cmdCache = QueueCreate(MAX_QUEUE_SIZE);
g_processingCmds = ListCreate(HciFreeCmd);
g_numberOfHciCmd = 1;
g_lockNumberOfHciCmd = MutexCreate();
g_lockProcessingCmds = MutexCreate();
}
void HciCloseCmd()
{
if (g_lockProcessingCmds != NULL) {
MutexDelete(g_lockProcessingCmds);
g_lockProcessingCmds = NULL;
}
if (g_lockNumberOfHciCmd != NULL) {
MutexDelete(g_lockNumberOfHciCmd);
g_lockNumberOfHciCmd = NULL;
}
if (g_cmdCache != NULL) {
QueueDelete(g_cmdCache, HciFreeCmd);
g_cmdCache = NULL;
}
if (g_processingCmds != NULL) {
ListDelete(g_processingCmds);
g_processingCmds = NULL;
}
}
static int HciCmdPushToTxQueue(HciCmd *cmd)
{
int result = BT_SUCCESS;
HciPacket *hciPacket = MEM_MALLOC.alloc(sizeof(HciPacket));
if (hciPacket != NULL) {
hciPacket->type = H2C_CMD;
hciPacket->packet = cmd->packet;
cmd->packet = NULL;
HciPushToTxQueue(hciPacket);
} else {
result = BT_NO_MEMORY;
}
return result;
}
static void HciCmdTimeoutTask(void *context)
{
HciCmd *pCmd = NULL;
uint16_t opCode = 0;
MutexLock(g_lockProcessingCmds);
ListNode *node = ListGetFirstNode(g_processingCmds);
while (node != NULL) {
pCmd = ListGetNodeData(node);
if (pCmd == context) {
opCode = pCmd->opCode;
break;
} else {
pCmd = NULL;
}
node = ListGetNextNode(node);
}
MutexUnlock(g_lockProcessingCmds);
if (opCode == 0) {
return;
}
HciCmdOnCommandStatus(opCode, HCI_TIMEOUT);
HciOnCmdTimeout();
}
static void HciCmdOnCmdTimeout(void *parameter)
{
Thread *thread = BTM_GetProcessingThread();
if (thread != NULL) {
ThreadPostTask(thread, HciCmdTimeoutTask, parameter);
}
}
void HciSetNumberOfHciCmd(uint8_t numberOfHciCmd)
{
MutexLock(g_lockNumberOfHciCmd);
g_numberOfHciCmd = numberOfHciCmd;
HciCmd *cmd = NULL;
while (g_numberOfHciCmd > 0) {
cmd = QueueTryDequeue(g_cmdCache);
if (cmd != NULL) {
int result = HciCmdPushToTxQueue(cmd);
if (result == BT_SUCCESS) {
g_numberOfHciCmd--;
MutexLock(g_lockProcessingCmds);
AlarmSet(cmd->alarm, CMD_TIMEOUT, HciCmdOnCmdTimeout, cmd);
ListAddLast(g_processingCmds, cmd);
MutexUnlock(g_lockProcessingCmds);
} else {
HciFreeCmd(cmd);
}
} else {
break;
}
}
MutexUnlock(g_lockNumberOfHciCmd);
}
static Packet *HciCreateCmdPacket(uint16_t opCode)
{
Packet *packet = PacketMalloc(sizeof(HciCmdHeader), 0, 0);
Buffer *headerBuffer = PacketHead(packet);
HciCmdHeader *header = (HciCmdHeader *)BufferPtr(headerBuffer);
if (header != NULL) {
header->opCode = opCode;
header->parameterTotalLength = 0;
}
return packet;
}
static Packet *HciCreateCmdPacketWithParam(uint16_t opCode, const void *param, uint8_t paramLength)
{
Packet *packet = PacketMalloc(sizeof(HciCmdHeader), 0, paramLength);
Buffer *headerBuffer = PacketHead(packet);
HciCmdHeader *header = (HciCmdHeader *)BufferPtr(headerBuffer);
if (header != NULL) {
header->opCode = opCode;
header->parameterTotalLength = paramLength;
}
PacketPayloadWrite(packet, param, 0, paramLength);
return packet;
}
HciCmd *HciAllocCmd(uint16_t opCode, const void *param, size_t paramLength)
{
HciCmd *cmd = MEM_MALLOC.alloc(sizeof(HciCmd));
if (cmd != NULL) {
cmd->opCode = opCode;
if (param != NULL && paramLength > 0) {
cmd->param = MEM_MALLOC.alloc(paramLength);
if (cmd->param != NULL) {
(void)memcpy_s(cmd->param, paramLength, param, paramLength);
}
cmd->packet = HciCreateCmdPacketWithParam(opCode, param, paramLength);
} else {
cmd->packet = HciCreateCmdPacket(opCode);
cmd->param = NULL;
}
cmd->alarm = AlarmCreate(NULL, false);
}
return cmd;
}
static void HciFreeCmd(void *cmd)
{
HciCmd *hciCmd = (HciCmd *)cmd;
if (hciCmd != NULL) {
if (hciCmd->alarm != NULL) {
AlarmCancel(hciCmd->alarm);
AlarmDelete(hciCmd->alarm);
}
if (hciCmd->param != NULL) {
MEM_MALLOC.free(hciCmd->param);
hciCmd->param = NULL;
}
if (hciCmd->packet != NULL) {
PacketFree(hciCmd->packet);
}
}
MEM_MALLOC.free(cmd);
}
int HciSendCmd(HciCmd *cmd)
{
int result = BT_SUCCESS;
MutexLock(g_lockNumberOfHciCmd);
if (g_numberOfHciCmd > 0) {
result = HciCmdPushToTxQueue(cmd);
if (result == BT_SUCCESS) {
g_numberOfHciCmd--;
MutexLock(g_lockProcessingCmds);
AlarmSet(cmd->alarm, CMD_TIMEOUT, HciCmdOnCmdTimeout, cmd);
ListAddLast(g_processingCmds, cmd);
MutexUnlock(g_lockProcessingCmds);
}
} else {
QueueEnqueue(g_cmdCache, cmd);
}
MutexUnlock(g_lockNumberOfHciCmd);
return result;
}
void HciCmdOnCommandStatus(uint16_t opCode, uint8_t status)
{
HciCmd *cmd = NULL;
MutexLock(g_lockProcessingCmds);
ListNode *node = ListGetFirstNode(g_processingCmds);
while (node != NULL) {
cmd = ListGetNodeData(node);
if (cmd != NULL) {
if (opCode == cmd->opCode) {
break;
}
cmd = NULL;
}
node = ListGetNextNode(node);
}
void *param = NULL;
if (cmd != NULL) {
AlarmCancel(cmd->alarm);
param = cmd->param;
cmd->param = NULL;
ListRemoveNode(g_processingCmds, cmd);
}
MutexUnlock(g_lockProcessingCmds);
if (cmd != NULL && opCode == HCI_DISCONNECT && status == HCI_SUCCESS) {
HciDisconnectParam *discParam = (HciDisconnectParam *)param;
HciAclOnDisconnectStatus(discParam->connectionHandle);
}
if (cmd != NULL && status != HCI_SUCCESS) {
HciOnCmdFailed(opCode, status, param);
}
if (param != NULL) {
MEM_MALLOC.free(param);
param = NULL;
}
}
void HciCmdOnCommandComplete(uint16_t opCode)
{
HciCmd *cmd = NULL;
MutexLock(g_lockProcessingCmds);
ListNode *node = ListGetFirstNode(g_processingCmds);
while (node != NULL) {
cmd = ListGetNodeData(node);
if (cmd != NULL) {
if (opCode == cmd->opCode) {
break;
}
cmd = NULL;
}
node = ListGetNextNode(node);
}
if (cmd != NULL) {
AlarmCancel(cmd->alarm);
ListRemoveNode(g_processingCmds, cmd);
}
MutexUnlock(g_lockProcessingCmds);
}