diff --git a/include/bluetooth/hdf_bt_transport.h b/include/bluetooth/hdf_bt_transport.h new file mode 100644 index 0000000000000000000000000000000000000000..514c8be188eab970a9792437af341938ff19bd10 --- /dev/null +++ b/include/bluetooth/hdf_bt_transport.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * HDF is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * See the LICENSE file in the root of this repository for complete details. + */ + +#ifndef HDF_BT_TRANSPORT_H +#define HDF_BT_TRANSPORT_H +#include "hdf_device_desc.h" + +struct HdfBtTransportOps; + +struct HdfBtTransport { + const struct HdfBtTransportOps *ops; +}; + +struct HdfBtTransportOps { + int32_t (*Init)(struct HdfBtTransport *transport); + int32_t (*GetVfsDevName)(struct HdfBtTransport *transport, char *buf, uint32_t size); + void (*Deinit)(struct HdfBtTransport *transport); + void (*Destory)(struct HdfBtTransport *transport); +}; + +struct HdfBtTransportService { + struct IDeviceIoService base; + struct HdfBtTransport *(*CreateTransport)(const struct DeviceResourceNode *node); +}; + +#endif diff --git a/include/bluetooth/hdf_chip.h b/include/bluetooth/hdf_chip.h new file mode 100644 index 0000000000000000000000000000000000000000..8b633f136f2155a62b78dacefac504c26becff07 --- /dev/null +++ b/include/bluetooth/hdf_chip.h @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * HDF is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * See the LICENSE file in the root of this repository for complete details. + */ + +#ifndef HDF_CHIP_H +#define HDF_CHIP_H + +#include "hdf_chip_config.h" +#include "osal/osal_time.h" + +struct HdfReset; +struct HdfPower; + +struct HdfPowerOps { + /** + * @brief Powers on the device using a specified power manage interface. + * + * @param powerMgr Indicates the pointer to the power manage interface. + * @return Returns 0 if the device is powered on; returns a negative value otherwise. + * + * @since 1.0 + * @version 1.0 + */ + int32_t (*On)(struct HdfPower *powerMgr); + + /** + * @brief Powers off the device using a specified power manage interface. + * + * @param powerMgr Indicates the pointer to the power manage interface. + * @return Returns 0 if the device is powered off; returns a negative value otherwise. + * + * @since 1.0 + * @version 1.0 + */ + int32_t (*Off)(struct HdfPower *powerMgr); + + /** + * @brief Releases power using a specified power manage interface. + * + * @param powerMgr Indicates the pointer to the power manage interface. + * + * @since 1.0 + * @version 1.0 + */ + void (*Release)(struct HdfPower *powerMgr); +}; +/** + * @brief Provides functions for powering on and off the device, releasing power, and creating a power manage interface. + * + * @since 1.0 + * @version 1.0 + */ +struct HdfPower { + const struct HdfPowerOps *ops; +}; + +struct HdfResetOps { + /** + * @brief Resets the WLAN module using a specified reset manage interface. + * + * @param resetManager Indicates the pointer to the reset manage interface. + * @return Returns 0 if the WLAN module is reset; returns a negative value otherwise. + * + * @since 1.0 + * @version 1.0 + */ + int32_t (*Reset)(struct HdfReset *resetManager); + + /** + * @brief Releases a specified reset manage interface. + * + * @param resetMgr Indicates the pointer to the reset manage interface. + * + * @since 1.0 + * @version 1.0 + */ + void (*Release)(struct HdfReset *resetMgr); +}; + +/** + * @brief Describes the reset manage interface, including its configuration and functions. + * + * @since 1.0 + * @version 1.0 + */ +struct HdfReset { + const struct HdfResetOps *ops; +}; + +struct HdfUartBus { + const char *name; +}; + +struct HdfBus { + uint8_t type; + union { + struct HdfUartBus uart; + }; +}; + +enum FunctionType { FUNC_TYPE_WLAN = 0, FUNC_TYPE_BT }; + +struct HdfVirtualDevice { + const char *name; + struct HdfCompositeDevice *parent; + struct HdfPower *power; + struct HdfReset *reset; + struct HdfBus *bus; + uint8_t bootUpTimeOut; + uint8_t functionType; +}; + +struct HdfVirtualDevice *CreateVirtualDevice(struct HdfChipConfig *config); +void ReleaseVirtualDevice(struct HdfVirtualDevice *device); + +inline static int32_t HdfPowerOnVirtualDevice(struct HdfVirtualDevice *device) { + if (device == NULL) { + return HDF_FAILURE; + } + if (device->power == NULL || device->power->ops == NULL || device->power->ops->On == NULL) { + return HDF_FAILURE; + } + return device->power->ops->On(device->power); +} +inline static int32_t HdfPowerOffVirtualDevice(struct HdfVirtualDevice *device) { + if (device == NULL) { + return HDF_FAILURE; + } + if (device->power == NULL || device->power->ops == NULL || device->power->ops->Off == NULL) { + return HDF_FAILURE; + } + return device->power->ops->Off(device->power); +} +inline static int32_t HdfResetVirtualDevice(struct HdfVirtualDevice *device) { + int32_t ret; + if (device == NULL) { + return HDF_FAILURE; + } + if (device->reset == NULL || device->reset->ops == NULL || device->reset->ops->Reset == NULL) { + return HDF_FAILURE; + } + ret = device->reset->ops->Reset(device->reset); + if (ret != HDF_SUCCESS) { + return ret; + } + OsalMSleep(device->bootUpTimeOut); +} + +#endif \ No newline at end of file diff --git a/include/bluetooth/hdf_chip_config.h b/include/bluetooth/hdf_chip_config.h new file mode 100644 index 0000000000000000000000000000000000000000..b4f4ac97ddfbed7af9ad47c2356b90c72607cc14 --- /dev/null +++ b/include/bluetooth/hdf_chip_config.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * HDF is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * See the LICENSE file in the root of this repository for complete details. + */ + +#ifndef HDF_CHIP_CONFIG_H +#define HDF_CHIP_CONFIG_H + +#include "device_resource_if.h" +#include "hdf_base.h" +#include "hdf_log.h" +#include "osal/osal_mem.h" +#include "securec.h" + +#define HDF_CHIP_MAX_POWER_SUPPORTED 2 + +#define BUS_FUNC_MAX 1 + +enum PowerType +{ + POWER_TYPE_ALWAYS_ON = 0, + POWER_TYPE_GPIO +}; + +struct HdfConfigGpioBasedSwitch { + uint8_t gpioId; + uint8_t activeLevel; +}; + +struct HdfPowerConfig { + uint8_t powerSeqDelay; + uint8_t type; + union { + struct HdfConfigGpioBasedSwitch gpio; + }; +}; + +struct HdfPowersConfig { + uint8_t powerCount; + struct HdfPowerConfig power[0]; +}; + +enum ResetType +{ + RESET_TYPE_NOT_MANAGEABLE = 0, + RESET_TYPE_GPIO +}; + +struct HdfResetConfig { + union { + struct HdfConfigGpioBasedSwitch gpio; + }; + uint8_t resetType; + uint8_t resetHoldTime; +}; + +struct HdfChipConfig { + const char *name; + struct HdfPowersConfig *powers; + struct HdfResetConfig reset; + uint8_t bootUpTimeOut; +}; + +static inline int ParsePowerConfig(const struct DeviceResourceNode *node, struct HdfPowerConfig *config) { + struct DeviceResourceIface *drsOps = NULL; + if (node == NULL || config == NULL) { + HDF_LOGE("%s: one of the input para is NULL!", __func__); + return HDF_FAILURE; + } + drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); + if (drsOps == NULL || drsOps->GetUint8 == NULL) { + HDF_LOGE("%s: at least one of the paras is NULL!", __func__); + return HDF_FAILURE; + } + + if (drsOps->GetUint8(node, "powerSeqDelay", &config->powerSeqDelay, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: powersSeqDelay fail!", __func__); + return HDF_FAILURE; + } + + if (drsOps->GetUint8(node, "powerType", &config->type, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: type fail!", __func__); + return HDF_FAILURE; + } + + if (config->type == POWER_TYPE_GPIO) { + if (drsOps->GetUint8(node, "gpioId", &config->gpio.gpioId, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: gpioId fail!", __func__); + return HDF_FAILURE; + } + if (drsOps->GetUint8(node, "activeLevel", &config->gpio.activeLevel, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: activeLevel fail!", __func__); + return HDF_FAILURE; + } + } + + return HDF_SUCCESS; +} + +static inline struct HdfPowersConfig *ParsePowersConfig(const struct DeviceResourceNode *node) { + struct DeviceResourceIface *drsOps = NULL; + struct DeviceResourceNode *childNode = NULL; + struct HdfPowersConfig *config = NULL; + uint8_t nodeCount = 0; + int32_t ret; + if (node == NULL) { + HDF_LOGE("%s: input para is NULL!", __func__); + return NULL; + } + drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); + if (drsOps == NULL || drsOps->GetChildNode == NULL) { + HDF_LOGE("%s: at least one of the paras is NULL!", __func__); + return NULL; + } + DEV_RES_NODE_FOR_EACH_CHILD_NODE(node, childNode) { ++nodeCount; } + if (nodeCount > HDF_CHIP_MAX_POWER_SUPPORTED) { + return NULL; + } + config = OsalMemCalloc(sizeof(struct HdfPowersConfig) + nodeCount * sizeof(struct HdfPowerConfig)); + if (config == NULL) { + return NULL; + } + config->powerCount = nodeCount; + for (uint8_t i = 0; i < nodeCount; i++) { + char buff[32] = {0}; + ret = snprintf_s(buff, 32, 32, "power%d", i); + if (ret < 0) { + HDF_LOGE("%s:snprintf_s failed!ret=%d, i=%d", __func__, ret, i); + break; + } + const struct DeviceResourceNode *powerNode = drsOps->GetChildNode(node, buff); + if (powerNode == NULL) { + HDF_LOGE("%s:Can not get node %s", __func__, buff); + ret = HDF_FAILURE; + break; + } + ret = ParsePowerConfig(powerNode, config->power + i); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:parse node %s failed!ret=%d", __func__, buff, ret); + break; + } + } + + if (ret != HDF_SUCCESS) { + OsalMemFree(config); + config = NULL; + } + return config; +} + +static inline int ParseResetConfig(const struct DeviceResourceNode *node, struct HdfResetConfig *reset) { + struct DeviceResourceIface *drsOps = NULL; + if (node == NULL || reset == NULL) { + HDF_LOGE("%s: at least one of the paras is NULL!", __func__); + return HDF_FAILURE; + } + + drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); + if (drsOps == NULL || drsOps->GetUint8 == NULL) { + HDF_LOGE("%s: at least one of the paras is NULL!", __func__); + return HDF_FAILURE; + } + + if (drsOps->GetUint8(node, "resetType", &reset->resetType, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: powersSeqDelay fail!", __func__); + return HDF_FAILURE; + } + if (reset->resetType == RESET_TYPE_GPIO) { + if (drsOps->GetUint8(node, "gpioId", &reset->gpio.gpioId, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: gpioId fail!", __func__); + return HDF_FAILURE; + } + + if (drsOps->GetUint8(node, "activeLevel", &reset->gpio.activeLevel, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: read activeLevel fail!", __func__); + return HDF_FAILURE; + } + + if (drsOps->GetUint8(node, "resetHoldTime", &reset->resetHoldTime, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: read resetHoldTime fail!", __func__); + return HDF_FAILURE; + } + } + return HDF_SUCCESS; +} + +static inline void ClearChipConfig(struct HdfChipConfig *config) { + + if (config->powers != NULL) { + OsalMemFree(config->powers); + config->powers = NULL; + } +} + +static inline int32_t ParseChipConfig(const struct DeviceResourceNode *node, struct HdfChipConfig *config) { + struct DeviceResourceIface *drsOps = NULL; + const struct DeviceResourceNode *devPowerNode = NULL; + const struct DeviceResourceNode *resetNode = NULL; + int32_t ret = HDF_SUCCESS; + if (node == NULL || config == NULL) { + HDF_LOGE("%s: invalid node or devLstConfig!", __func__); + return HDF_FAILURE; + } + drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); + if (drsOps == NULL || drsOps->GetUint8 == NULL || drsOps->GetChildNode == NULL) { + HDF_LOGE("%s: at least one of the paras is NULL!", __func__); + return HDF_FAILURE; + } + config->name = node->name; + + if (drsOps->GetUint8(node, "bootUpTimeOut", &config->bootUpTimeOut, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: bootUpTimeOut fail!", __func__); + return HDF_FAILURE; + } + + resetNode = drsOps->GetChildNode(node, "reset"); + if (resetNode == NULL) { + HDF_LOGE("%s: GetChildNode fail!", __func__); + return HDF_FAILURE; + } + if (ParseResetConfig(resetNode, &config->reset) != HDF_SUCCESS) { + return HDF_FAILURE; + } + + do { + devPowerNode = drsOps->GetChildNode(node, "powers"); + if (devPowerNode == NULL) { + HDF_LOGE("%s: GetChildNode fail!", __func__); + ret = HDF_FAILURE; + break; + } + config->powers = ParsePowersConfig(devPowerNode); + if (config->powers == NULL) { + ret = HDF_FAILURE; + break; + } + } while (false); + + if (ret != HDF_SUCCESS) { + ClearChipConfig(config); + } + return ret; +} + +#endif \ No newline at end of file diff --git a/model/network/bluetooth/hdf_bt_core.c b/model/network/bluetooth/hdf_bt_core.c new file mode 100644 index 0000000000000000000000000000000000000000..df35fa7dcfd7a602e628a88ee876eaf916a772f3 --- /dev/null +++ b/model/network/bluetooth/hdf_bt_core.c @@ -0,0 +1,548 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * HDF is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * See the LICENSE file in the root of this repository for complete details. + */ + +#include "devsvc_manager_clnt.h" +#include "hdf_bt_transport.h" +#include "hdf_chip.h" +#include "hdf_chip_config.h" +#include "hdf_device_desc.h" +#include "hdf_io_service_if.h" +#include "hdf_log.h" +#include "osal/osal_io.h" +#include "osal/osal_mem.h" +#include "osal/osal_spinlock.h" +#include "platform/gpio_if.h" + +#define HDF_LOG_TAG HDF_BT + +#define MAX_BT_DEVICE_COUNT 2 + +#define MAX_NODE_NAME_SIZE 32 + +struct HdfBtVirtualDevice { + struct HdfVirtualDevice *device; + struct HdfBtTransport *transport; +}; + +enum HdfBtTransportType +{ + BT_TRANSPORT_TYPE_RAW = 0, + BT_TRANSPORT_TYPE_CUSTOM +}; + +struct HdfBtTransportConfig { + uint8_t type; + union { + const char *devName; + const char *serviceName; + }; +}; + +struct HdfBtRawTransport { + struct HdfBtTransport base; + const char *devName; +}; + +typedef int32_t (*DeviceOperator)(struct HdfVirtualDevice *); +typedef int32_t (*BtDeviceOperator)(struct HdfBtVirtualDevice *); + +enum HDF_BT_CMD +{ + HDF_BT_CMD_GET_DEVICE_COUNT = 0, + HDF_BT_CMD_INIT_DEVICE, + HDF_BT_CMD_DEINIT_DEVICE +}; + +// registed device count +uint8_t g_deviceCount = 0; +// registed device tab +struct HdfBtVirtualDevice g_btDevices[MAX_BT_DEVICE_COUNT]; +// lock of the device tab +OSAL_DECLARE_SPINLOCK(g_devicesLock); + +static int32_t ParseTransportConfig(const struct DeviceResourceNode *node, struct HdfBtTransportConfig *config) { + struct DeviceResourceIface *drsOps = NULL; + int32_t ret = HDF_SUCCESS; + if (node == NULL || config == NULL) { + HDF_LOGE("%s: one of the input para is NULL!", __func__); + return HDF_FAILURE; + } + drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); + if (drsOps == NULL || drsOps->GetUint8 == NULL || drsOps->GetString == NULL) { + HDF_LOGE("%s: bad DeviceResourceIface!", __func__); + return HDF_FAILURE; + } + if (drsOps->GetUint8(node, "type", &config->type, 0) != HDF_SUCCESS) { + HDF_LOGE("%s: read type fail!", __func__); + return HDF_FAILURE; + } + switch (config->type) { + case BT_TRANSPORT_TYPE_RAW: { + if (drsOps->GetString(node, "devName", &config->devName, "") != HDF_SUCCESS) { + HDF_LOGE("%s: devName is required!", __func__); + ret = HDF_FAILURE; + } + break; + } + case BT_TRANSPORT_TYPE_CUSTOM: { + if (drsOps->GetString(node, "serviceName", &config->serviceName, "") != HDF_SUCCESS) { + HDF_LOGE("%s: serviceName is required!", __func__); + ret = HDF_FAILURE; + } + break; + } + default: + HDF_LOGE("%s: unexpected transport type %d!", __func__, config->type); + ret = HDF_FAILURE; + break; + } + return ret; +} + +static int32_t InitDeivceList() { + (void)memset_s(g_btDevices, sizeof(g_btDevices), 0, sizeof(g_btDevices)); + g_deviceCount = 0; + return OsalSpinInit(&g_devicesLock); +} + +static int32_t RegistBtDevice(struct HdfVirtualDevice *device, struct HdfBtTransport *transport) { + int ret; + if (device == NULL || transport == NULL) { + HDF_LOGE("%s:nullptr!", __func__); + return HDF_FAILURE; + } + ret = OsalSpinLockIrq(&g_devicesLock); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:lock failed!ret=%d", __func__, ret); + return ret; + } + if (g_deviceCount < MAX_BT_DEVICE_COUNT) { + g_btDevices[g_deviceCount].device = device; + g_btDevices[g_deviceCount].transport = transport; + ++g_deviceCount; + } else { + HDF_LOGE("%s:deviceList is full!", __func__); + ret = HDF_FAILURE; + } + (void)OsalSpinUnlockIrq(&g_devicesLock); + return ret; +} + +inline static int32_t OperateDevice(uint8_t id, DeviceOperator operator) { + int32_t ret = OsalSpinLockIrq(&g_devicesLock); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:lock failed!ret=%d", __func__, ret); + return ret; + } + HDF_LOGW("%s:operator %d", __func__, id); + + do { + if (id >= g_deviceCount) { + HDF_LOGE("%s:no such device", __func__); + ret = HDF_FAILURE; + break; + } + ret = operator(g_btDevices[id].device); + } while (false); + + (void)OsalSpinUnlockIrq(&g_devicesLock); + return ret; +} + +#define OperateBtDevice(RET, ID, Operator, ARGS...) \ + do { \ + RET = OsalSpinLockIrq(&g_devicesLock); \ + if (RET != HDF_SUCCESS) { \ + HDF_LOGE("%s:lock failed!ret=%d", __func__, RET); \ + break; \ + } \ + do { \ + if (id >= g_deviceCount) { \ + HDF_LOGE("%s:no such device", __func__); \ + RET = HDF_FAILURE; \ + break; \ + } \ + RET = Operator(&g_btDevices[ID], ##ARGS); \ + } while (false); \ + (void)OsalSpinUnlockIrq(&g_devicesLock); \ + } while (false) + +inline int32_t PowerOnDevice(uint8_t id) { + return OperateDevice(id, HdfPowerOnVirtualDevice); +} + +inline int32_t PowerOffDevice(uint8_t id) { + return OperateDevice(id, HdfPowerOffVirtualDevice); +} + +static int32_t InitTransportOperation(struct HdfBtVirtualDevice *device) { + if (device == NULL) { + HDF_LOGE("%s:nullptr", __func__); + return HDF_FAILURE; + } + if (device->transport != NULL && device->transport->ops != NULL && device->transport->ops->Init != NULL) { + return device->transport->ops->Init(device->transport); + } + return HDF_SUCCESS; +} + +inline int32_t InitTransport(uint8_t id) { + int ret = HDF_SUCCESS; + OperateBtDevice(ret, id, InitTransportOperation); + return ret; +} + +static int32_t DeinitTransportOperation(struct HdfBtVirtualDevice *device) { + if (device == NULL) { + HDF_LOGE("%s:nullptr", __func__); + return HDF_FAILURE; + } + if (device->transport != NULL && device->transport->ops != NULL && device->transport->ops->Deinit != NULL) { + device->transport->ops->Deinit(device->transport); + } + return HDF_SUCCESS; +} + +inline int32_t DeinitTransport(uint8_t id) { + int ret = HDF_SUCCESS; + OperateBtDevice(ret, id, DeinitTransportOperation); + return ret; +} + +static int32_t GetDevNodeNameOperation(struct HdfBtVirtualDevice *device, char *buf, uint32_t size) { + if (device == NULL || device->transport == NULL || device->transport->ops == NULL || + device->transport->ops->GetVfsDevName == NULL) { + HDF_LOGE("%s:bad transport.", __func__); + return HDF_FAILURE; + } + return device->transport->ops->GetVfsDevName(device->transport, buf, size); +} + +inline int32_t GetDevNodeName(uint8_t id, char *buf, uint32_t size) { + int ret = HDF_SUCCESS; + OperateBtDevice(ret, id, GetDevNodeNameOperation, buf, size); + return ret; +} + +static int32_t BtMessageDispatcher(struct HdfDeviceIoClient *client, int id, struct HdfSBuf *reqData, + struct HdfSBuf *rspData) { + int ret = HDF_FAILURE; + HDF_LOGV("%s: enter", __func__); + (void)client; + if (reqData == NULL) { + HDF_LOGE("%s:nullptr", __func__); + return ret; + } + switch (id) { + case HDF_BT_CMD_GET_DEVICE_COUNT: { + if (HdfSbufWriteUint8(rspData, g_deviceCount)) { + ret = HDF_SUCCESS; + } else { + HDF_LOGE("%s:reponse device count failed!", __func__); + } + break; + } + case HDF_BT_CMD_INIT_DEVICE: { + uint8_t id = 0; + char buff[MAX_NODE_NAME_SIZE]; + if (!HdfSbufReadUint8(reqData, &id)) { + HDF_LOGE("%s:read deviceID failed!", __func__); + break; + } + HDF_LOGI("%s:power on. devID=%d", __func__, id); + ret = PowerOnDevice(id); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:power on failed.devideID=%d", __func__, id); + break; + } + ret = InitTransport(id); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:init transport failed.devideID=%d,ret=%d", __func__, id, ret); + break; + } + ret = GetDevNodeName(id, buff, MAX_NODE_NAME_SIZE); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:get dev node failed!id=%d", __func__, id); + break; + } + if (rspData != NULL) { + if (!HdfSbufWriteString(rspData, buff)) { + HDF_LOGE("%s:respose dev node failed!id=%d", __func__, id); + break; + } + } + ret = HDF_SUCCESS; + break; + } + case HDF_BT_CMD_DEINIT_DEVICE: { + uint8_t id = 0; + if (!HdfSbufReadUint8(reqData, &id)) { + HDF_LOGE("%s:read deviceID failed!", __func__); + break; + } + ret = DeinitTransport(id); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:Deinit transport failed.devideID=%d", __func__, id); + } + HDF_LOGI("%s:power on %d", __func__, id); + ret = PowerOffDevice(id); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:power off failed.devideID=%d", __func__, id); + break; + } + + break; + } + default: + HDF_LOGE("%s:unexpected cmd %d!", __func__, id); + break; + }; + return ret; +} + +static int HdfBtChipDriverBind(struct HdfDeviceObject *dev) { + static struct IDeviceIoService btService = { + .object.objectId = 1, + .Dispatch = BtMessageDispatcher, + }; + HDF_LOGV("%s: enter", __func__); + + dev->service = &btService; + return 0; +} + +static int32_t HdfBtInitRawTransport(struct HdfBtTransport *transport) { + return HDF_SUCCESS; +} + +static void HdfBtDeinitRawTransport(struct HdfBtTransport *transport) { + return; +} + +static void HdfBtDestoryRawTransport(struct HdfBtTransport *transport) { + if (transport != NULL) { + OsalMemFree(transport); + } +} + +static int32_t HdfBtGetRawTransportDeviceName(struct HdfBtTransport *transport, char *buf, uint32_t size) { + struct HdfBtRawTransport *rawTransport = (struct HdfBtRawTransport *)transport; + if (rawTransport == NULL) { + return HDF_FAILURE; + } + if (strncpy_s(buf, size, rawTransport->devName, strlen(rawTransport->devName)) != EOK) { + return HDF_FAILURE; + } + return HDF_SUCCESS; +} + +static const struct HdfBtTransportOps g_rawTransportOps = {.Init = HdfBtInitRawTransport, + .Deinit = HdfBtDeinitRawTransport, + .GetVfsDevName = HdfBtGetRawTransportDeviceName, + .Destory = HdfBtDestoryRawTransport}; + +static struct HdfBtRawTransport *CreateRawTransport(const char *devName) { + struct HdfBtRawTransport *transport = NULL; + if (devName == NULL) { + return NULL; + } + transport = (struct HdfBtRawTransport *)OsalMemCalloc(sizeof(struct HdfBtRawTransport)); + if (transport == NULL) { + return NULL; + } + transport->base.ops = &g_rawTransportOps; + transport->devName = devName; + return transport; +} + +static struct HdfBtTransport *CreateCustomTransport(const char *serviceName, const struct DeviceResourceNode *node) { + struct SubscriberCallback callback = {NULL}; + struct HdfDeviceObject *object = NULL; + struct HdfBtTransportService *service = NULL; + + int ret = DevSvcManagerClntSubscribeService(serviceName, callback); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:load service failed!serviceName=%s", __func__, serviceName); + return NULL; + } + object = DevSvcManagerClntGetDeviceObject(serviceName); + if (object == NULL || object->service == NULL) { + HDF_LOGE("%s:bad service %s", __func__, serviceName); + return NULL; + } + service = (struct HdfBtTransportService *)object->service; + if (service->CreateTransport == NULL) { + HDF_LOGE("%s:service %s has no CreateTransport method", __func__, serviceName); + return NULL; + } + return service->CreateTransport(node); +} + +static struct HdfBtTransport *CreateTransport(const struct DeviceResourceNode *node) { + struct HdfBtTransport *transport = NULL; + struct DeviceResourceIface *drsOps = NULL; + struct HdfBtTransportConfig config = {0}; + const struct DeviceResourceNode *transportNode = NULL; + int32_t ret; + drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); + if (drsOps == NULL || drsOps->GetChildNode == NULL) { + HDF_LOGE("%s: bad DeviceResourceIface!", __func__); + return NULL; + } + transportNode = drsOps->GetChildNode(node, "transport"); + if (transportNode == NULL) { + HDF_LOGE("%s:node transport in hcs is required", __func__); + return NULL; + } + + ret = ParseTransportConfig(transportNode, &config); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: parse transport config failed!ret= %d", __func__, ret); + return NULL; + } + switch (config.type) { + case BT_TRANSPORT_TYPE_RAW: { + transport = (struct HdfBtTransport *)CreateRawTransport(config.devName); + break; + } + case BT_TRANSPORT_TYPE_CUSTOM: { + transport = CreateCustomTransport(config.serviceName, node); + break; + } + default: + HDF_LOGE("%s:unexpected transport type %d", __func__, config.type); + } + return transport; +} + +static int32_t InitDevice(const struct DeviceResourceNode *node) { + struct HdfChipConfig config = {0}; + struct HdfVirtualDevice *device = NULL; + struct HdfBtTransport *transport = NULL; + + int32_t ret = ParseChipConfig(node, &config); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:parse config failed!", __func__); + return ret; + } + do { + device = CreateVirtualDevice(&config); + if (device == NULL) { + HDF_LOGE("%s:Create virtual device failed!", __func__); + ret = HDF_FAILURE; + break; + } + transport = CreateTransport(node); + if (transport == NULL) { + HDF_LOGE("%s:Create transport failed!", __func__); + ret = HDF_FAILURE; + break; + } + ret = RegistBtDevice(device, transport); + } while (false); + if (ret != HDF_SUCCESS) { + if (device != NULL) { + ReleaseVirtualDevice(device); + device = NULL; + } + if (transport != NULL && transport->ops != NULL && transport->ops->Destory != NULL) { + transport->ops->Destory(transport); + } + transport = NULL; + } + ClearChipConfig(&config); + return ret; +} + +static int32_t InitDevices(struct HdfDeviceObject *device) { + struct DeviceResourceIface *drsOps = NULL; + const struct DeviceResourceNode *devListNode = NULL; + struct DeviceResourceNode *childNode = NULL; + int32_t ret = HDF_SUCCESS; + if (device == NULL || device->property == NULL) { + HDF_LOGE("%s:nullptr!", __func__); + return HDF_FAILURE; + } + + drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); + if (drsOps == NULL || drsOps->GetChildNode == NULL) { + HDF_LOGE("%s: invalid drs ops fail!", __func__); + return HDF_FAILURE; + } + + devListNode = drsOps->GetChildNode(device->property, "deviceList"); + if (devListNode == NULL) { + HDF_LOGW("%s:no device list defined!", __func__); + return HDF_SUCCESS; + } + + DEV_RES_NODE_FOR_EACH_CHILD_NODE(devListNode, childNode) { + ret = InitDevice(childNode); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:init device %s failed!", __func__, childNode->name); + break; + } + } + + return ret; +} + +static int32_t HdfBtChipDriverInit(struct HdfDeviceObject *device) { + + int ret; + HDF_LOGV("%s:driver init...", __func__); + + ret = InitDeivceList(); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:InitDeivceList failed!ret=%d", __func__, ret); + return ret; + } + + ret = InitDevices(device); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:init devices failed!ret=%d", __func__, ret); + return ret; + } + return HDF_SUCCESS; +}; + +static void HdfBtChipDriverRelease(struct HdfDeviceObject *object) { + uint8_t i; + int ret = OsalSpinLockIrq(&g_devicesLock); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:lock failed!ret=%d", __func__, ret); + return; + } + + for (i = g_deviceCount - 1; i >= 0; --i) { + g_deviceCount--; + if (g_btDevices[i].device != NULL) { + ReleaseVirtualDevice(g_btDevices[i].device); + g_btDevices[i].device = NULL; + } + + if (g_btDevices[i].transport != NULL && g_btDevices[i].transport->ops != NULL && + g_btDevices[i].transport->ops->Destory != NULL) { + g_btDevices[i].transport->ops->Destory(g_btDevices[i].transport); + } + g_btDevices[i].transport = NULL; + } + + (void)OsalSpinUnlockIrq(&g_devicesLock); + return; +} + +struct HdfDriverEntry g_hdfBTDriver = { + .moduleVersion = 1, + .Bind = HdfBtChipDriverBind, + .Init = HdfBtChipDriverInit, + .Release = HdfBtChipDriverRelease, + .moduleName = "HDF_BT", +}; + +HDF_INIT(g_hdfBTDriver); diff --git a/model/network/bluetooth/hdf_chip.c b/model/network/bluetooth/hdf_chip.c new file mode 100644 index 0000000000000000000000000000000000000000..e40daeb58b722459086582667957b4ab65d8ab83 --- /dev/null +++ b/model/network/bluetooth/hdf_chip.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * HDF is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * See the LICENSE file in the root of this repository for complete details. + */ + +#include "hdf_chip.h" +#include "hdf_chip_config.h" +#include "hdf_device_desc.h" +#include "hdf_power.h" +#include "hdf_reset.h" +#include "osal/osal_mem.h" + +struct HdfVirtualDevice *CreateVirtualDevice(struct HdfChipConfig *config) { + struct HdfVirtualDevice *device = NULL; + int32_t ret = HDF_SUCCESS; + if (config == NULL) { + return NULL; + } + device = (struct HdfVirtualDevice *)OsalMemCalloc(sizeof(struct HdfVirtualDevice)); + if (device == NULL) { + return NULL; + } + do { + device->name = config->name; + + device->power = CreateVirtualPower(config->powers); + if (device->power == NULL) { + ret = HDF_FAILURE; + break; + } + + device->reset = CreateVirtualReset(&config->reset); + if (device->reset == NULL) { + ret = HDF_FAILURE; + break; + } + } while (false); + + if (ret != HDF_SUCCESS) { + ReleaseVirtualDevice(device); + device = NULL; + } + return device; +} +void ReleaseVirtualDevice(struct HdfVirtualDevice *device) { + if (device == NULL) { + return; + } + if (device->power != NULL && device->power->ops != NULL && device->power->ops->Release != NULL) { + device->power->ops->Release(device->power); + device->power = NULL; + } + + if (device->reset != NULL && device->reset->ops != NULL && device->reset->ops->Release != NULL) { + device->reset->ops->Release(device->reset); + device->reset = NULL; + } + OsalMemFree(device); +} diff --git a/model/network/bluetooth/hdf_power.c b/model/network/bluetooth/hdf_power.c new file mode 100644 index 0000000000000000000000000000000000000000..a27bcfde19209938964065239b52fb848bdb8ed6 --- /dev/null +++ b/model/network/bluetooth/hdf_power.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * HDF is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * See the LICENSE file in the root of this repository for complete details. + */ + +#include "hdf_power.h" +#include "gpio_if.h" +#include "hdf_base.h" +#include "hdf_chip.h" +#include "hdf_chip_config.h" + +#define MAX_POWER_COUNT 4 + +struct NoManagablePower { + struct HdfPower base; + uint8_t powerSeqDelay; +}; + +struct GpioBasedPower { + struct HdfPower base; + uint8_t powerSeqDelay; + uint8_t gpioId; + uint8_t activeLevel; +}; + +struct MutiPowers { + struct HdfPower base; + uint8_t innerPowerCount; + struct HdfPower *powers[0]; +}; + +static int32_t NotManagablePowerOn(struct HdfPower *power) { + (void)power; + return HDF_SUCCESS; +} + +static int32_t NotManagablePowerOff(struct HdfPower *power) { + (void)power; + return HDF_FAILURE; +} + +static void ReleasePower(struct HdfPower *power) { + if (power == NULL) { + return; + } + OsalMemFree(power); +} + +static struct NoManagablePower *CreateNoManagablePower(const struct HdfPowerConfig *power) { + struct NoManagablePower *result = NULL; + static const struct HdfPowerOps notManagablePowerOps = {.On = NotManagablePowerOn, + .Off = NotManagablePowerOff, + .Release = ReleasePower}; + result = (struct NoManagablePower *)OsalMemCalloc(sizeof(struct NoManagablePower)); + if (result == NULL) { + return NULL; + } + result->base.ops = ¬ManagablePowerOps; + result->powerSeqDelay = power->powerSeqDelay; + return result; +} + +static int32_t GpioPowerOn(struct HdfPower *power) { + int32_t ret; + struct GpioBasedPower *gpioPower = (struct GpioBasedPower *)power; + if (power == NULL) { + HDF_LOGE("%s:nullptr", __func__); + return HDF_FAILURE; + } + ret = GpioSetDir(gpioPower->gpioId, 1); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:set dir fail! ret=%d\n", __func__, ret); + return HDF_FAILURE; + } + ret = GpioWrite(gpioPower->gpioId, gpioPower->activeLevel); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:set power on fail! ret=%d\n", __func__, ret); + return HDF_FAILURE; + } + return HDF_SUCCESS; +} + +static int32_t GpioPowerOff(struct HdfPower *power) { + int32_t ret; + struct GpioBasedPower *gpioPower = (struct GpioBasedPower *)power; + if (power == NULL) { + HDF_LOGE("%s:nullptr", __func__); + return HDF_FAILURE; + } + ret = GpioSetDir(gpioPower->gpioId, 1); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:set dir fail! ret=%d\n", __func__, ret); + return HDF_FAILURE; + } + ret = GpioWrite(gpioPower->gpioId, !gpioPower->activeLevel); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s:set power on fail! ret=%d\n", __func__, ret); + return HDF_FAILURE; + } + return HDF_SUCCESS; +} + +static struct GpioBasedPower *CreateGpioBasedPower(const struct HdfPowerConfig *power) { + struct GpioBasedPower *result = NULL; + result = (struct GpioBasedPower *)OsalMemCalloc(sizeof(struct GpioBasedPower)); + if (result == NULL) { + return NULL; + } + static const struct HdfPowerOps notManagablePowerOps = {.On = GpioPowerOn, + .Off = GpioPowerOff, + .Release = ReleasePower}; + result->base.ops = ¬ManagablePowerOps; + result->powerSeqDelay = power->powerSeqDelay; + result->gpioId = power->gpio.gpioId; + result->activeLevel = power->gpio.activeLevel; + return result; +} + +static struct HdfPower *CreatePower(const struct HdfPowerConfig *power) { + if (power == NULL) { + return NULL; + } + if (power->type == POWER_TYPE_ALWAYS_ON) { + return (struct HdfPower *)CreateNoManagablePower(power); + } else if (power->type == POWER_TYPE_GPIO) { + return (struct HdfPower *)CreateGpioBasedPower(power); + } else { + HDF_LOGE("%s:not supported power type %d", __func__, power->type); + return NULL; + } +} + +static int32_t ActiveMutiPower(struct HdfPower *power) { + struct MutiPowers *mutiPower = (struct MutiPowers *)power; + int ret; + if (power == NULL) { + HDF_LOGE("%s:nullptr", __func__); + return HDF_FAILURE; + } + for (uint8_t i = 0; i < mutiPower->innerPowerCount; i++) { + if (mutiPower->powers[i] == NULL || mutiPower->powers[i]->ops == NULL || + mutiPower->powers[i]->ops->On == NULL) { + HDF_LOGW("%s:bad power!index=%d", __func__, i); + ret = HDF_FAILURE; + break; + } + if (i > 0) { + struct NoManagablePower *innerPower = (struct NoManagablePower *)mutiPower->powers[i]; + OsalMSleep(innerPower->powerSeqDelay); + } + ret = mutiPower->powers[i]->ops->On(mutiPower->powers[i]); + if (ret != HDF_SUCCESS) { + break; + } + } + return ret; +} + +static int32_t DeactiveMutiPower(struct HdfPower *power) { + struct MutiPowers *mutiPower = (struct MutiPowers *)power; + int ret; + if (power == NULL) { + HDF_LOGE("%s:nullptr", __func__); + return HDF_FAILURE; + } + for (uint8_t i = 0; i < mutiPower->innerPowerCount; i++) { + if (mutiPower->powers[i] == NULL || mutiPower->powers[i]->ops == NULL || + mutiPower->powers[i]->ops->Off == NULL) { + HDF_LOGW("%s:bad power!index=%d", __func__, i); + ret = HDF_FAILURE; + break; + } + ret = mutiPower->powers[i]->ops->Off(mutiPower->powers[i]); + if (ret != HDF_SUCCESS) { + break; + } + } + return ret; +} + +static void ReleaseMutiPower(struct HdfPower *power) { + struct MutiPowers *mutiPower = (struct MutiPowers *)power; + if (power == NULL) { + HDF_LOGE("%s:nullptr", __func__); + return; + } + for (uint8_t i = 0; i < mutiPower->innerPowerCount; i++) { + if (mutiPower->powers[i] == NULL || mutiPower->powers[i]->ops == NULL || + mutiPower->powers[i]->ops->Release == NULL) { + HDF_LOGW("%s:bad power!index=%d", __func__, i); + } else { + mutiPower->powers[i]->ops->Release(mutiPower->powers[i]); + } + mutiPower->powers[i] = NULL; + } + OsalMemFree(power); +} + +static struct MutiPowers *CreateMutiPower(const struct HdfPowersConfig *powersConfig) { + int ret = HDF_SUCCESS; + struct MutiPowers *mutiPower = + OsalMemCalloc(sizeof(struct MutiPowers) + sizeof(struct HdfPower *) * powersConfig->powerCount); + static const struct HdfPowerOps mutiPowerOps = {.On = ActiveMutiPower, + .Off = DeactiveMutiPower, + .Release = ReleaseMutiPower}; + for (uint8_t i = 0; i < powersConfig->powerCount; i++) { + mutiPower->powers[i] = CreatePower(powersConfig->power + i); + if (mutiPower->powers[i] == NULL) { + OsalMemFree(mutiPower); + ret = HDF_FAILURE; + break; + } + } + mutiPower->innerPowerCount = powersConfig->powerCount; + if (ret != HDF_SUCCESS) { + ReleaseMutiPower((struct HdfPower *)mutiPower); + return NULL; + } + mutiPower->base.ops = &mutiPowerOps; + return mutiPower; +} + +struct HdfPower *CreateVirtualPower(const struct HdfPowersConfig *powers) { + if (powers == NULL) { + HDF_LOGE("%s:nullptr", __func__); + return NULL; + } + if (powers->powerCount > MAX_POWER_COUNT) { + HDF_LOGE("%s:too many power in config!count=%d", __func__, powers->powerCount); + return NULL; + } + if (powers->powerCount == 1) { + return CreatePower(&powers->power[0]); + } else if (powers->powerCount > 1) { + return (struct HdfPower *)CreateMutiPower(powers); + } else { + return NULL; + } +} diff --git a/model/network/bluetooth/hdf_power.h b/model/network/bluetooth/hdf_power.h new file mode 100644 index 0000000000000000000000000000000000000000..6fcca853785983669a3a5f0267d08ad2ae4ca779 --- /dev/null +++ b/model/network/bluetooth/hdf_power.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * HDF is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * See the LICENSE file in the root of this repository for complete details. + */ + +#ifndef HDF_POWER_H +#define HDF_POWER_H +#include "hdf_chip.h" + +struct HdfPower *CreateVirtualPower(const struct HdfPowersConfig *powers); + +#endif \ No newline at end of file diff --git a/model/network/bluetooth/hdf_reset.c b/model/network/bluetooth/hdf_reset.c new file mode 100644 index 0000000000000000000000000000000000000000..7bf868d024df763b0976532101c8ed15b7886cb2 --- /dev/null +++ b/model/network/bluetooth/hdf_reset.c @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * HDF is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * See the LICENSE file in the root of this repository for complete details. + */ + +#include "hdf_reset.h" +#include "gpio_if.h" +#include "hdf_base.h" + +struct GpioBasedReset { + struct HdfReset base; + uint8_t resetHoldTime; + uint8_t gpioId; + uint8_t activeLevel; +}; + +int32_t ResetNoManagableReset(struct HdfReset *reset) { + (void)reset; + return HDF_FAILURE; +} + +void ReleaseNoManagableReset(struct HdfReset *reset) { + if (reset != NULL) { + OsalMemFree(reset); + } +} + +int32_t ResetGpioBasedReset(struct HdfReset *reset) { + int ret; + struct GpioBasedReset *gpioBasedReset = (struct GpioBasedReset *)reset; + if (reset == NULL) { + HDF_LOGE("%s:nullptr", __func__); + return HDF_FAILURE; + } + ret = GpioSetDir(gpioBasedReset->gpioId, GPIO_DIR_OUT); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: set dir fail!", __func__); + return ret; + } + ret = GpioWrite(gpioBasedReset->gpioId, gpioBasedReset->activeLevel); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: write active fail! ret=%d", __func__, ret); + return ret; + } + OsalMSleep(gpioBasedReset->resetHoldTime); + + ret = GpioWrite(gpioBasedReset->gpioId, !gpioBasedReset->activeLevel); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%s: write deactivate fail! ret=%d", __func__, ret); + return ret; + } + return ret; +} + +void ReleaseGpioBasedReset(struct HdfReset *reset) { + if (reset != NULL) { + OsalMemFree(reset); + } +} + +struct HdfReset *CreateVirtualReset(const struct HdfResetConfig *resetConfig) { + struct HdfReset *result = NULL; + if (resetConfig == NULL) { + HDF_LOGE("%s:nullptr", __func__); + return NULL; + } + if (resetConfig->resetType == RESET_TYPE_NOT_MANAGEABLE) { + const static struct HdfResetOps noManagableResetOps = {.Reset = ResetNoManagableReset, + .Release = ReleaseNoManagableReset}; + result = (struct HdfReset *)OsalMemCalloc(sizeof(struct HdfReset)); + result->ops = &noManagableResetOps; + } else if (resetConfig->resetType == RESET_TYPE_GPIO) { + const static struct HdfResetOps gpioBasedResetOps = {.Reset = ResetGpioBasedReset, + .Release = ReleaseGpioBasedReset}; + struct GpioBasedReset *reset = (struct GpioBasedReset *)OsalMemCalloc(sizeof(struct GpioBasedReset)); + reset->resetHoldTime = resetConfig->resetHoldTime; + reset->gpioId = resetConfig->gpio.gpioId; + reset->activeLevel = resetConfig->gpio.activeLevel; + reset->base.ops = &gpioBasedResetOps; + result = (struct HdfReset *)reset; + } + return result; +} \ No newline at end of file diff --git a/model/network/bluetooth/hdf_reset.h b/model/network/bluetooth/hdf_reset.h new file mode 100644 index 0000000000000000000000000000000000000000..9859140db08f8faf61d7ca1da70f0be4acdbd608 --- /dev/null +++ b/model/network/bluetooth/hdf_reset.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * + * HDF is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * See the LICENSE file in the root of this repository for complete details. + */ + +#ifndef HDF_RESET_H +#define HDF_RESET_H +#include "hdf_chip.h" +#include "hdf_chip_config.h" + +struct HdfReset *CreateVirtualReset(const struct HdfResetConfig *powers); + +#endif \ No newline at end of file