613 lines
16 KiB
C
613 lines
16 KiB
C
/****************************************************************************
|
|
* Copyright (C) 2023 Xiaomi Corporation
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "bt_list.h"
|
|
#include "bt_spp.h"
|
|
#include "bt_tools.h"
|
|
#include "bt_uuid.h"
|
|
#include "euv_pty.h"
|
|
#include "uv_thread_loop.h"
|
|
|
|
typedef struct {
|
|
struct list_node node;
|
|
euv_pty_t* pty;
|
|
int fd;
|
|
int port;
|
|
} spp_device_t;
|
|
|
|
typedef struct {
|
|
void* handle;
|
|
bt_address_t addr;
|
|
uint16_t port;
|
|
uint16_t scn;
|
|
uint8_t* buf;
|
|
uint16_t len;
|
|
uint32_t state;
|
|
char* name;
|
|
} spp_cmd_t;
|
|
|
|
typedef struct {
|
|
void* handle;
|
|
uint8_t port;
|
|
enum {
|
|
TRANS_IDLE = 0,
|
|
TRANS_WRITING,
|
|
TRANS_SENDING,
|
|
TRANS_RECVING,
|
|
} state;
|
|
uint8_t* bulk_buf;
|
|
int32_t bulk_count;
|
|
uint32_t bulk_length;
|
|
uint32_t trans_total_size;
|
|
uint32_t received_size;
|
|
uint64_t start_timestamp;
|
|
uint64_t end_timestamp;
|
|
} transmit_context_t;
|
|
|
|
static int start_server_cmd(void* handle, int argc, char* argv[]);
|
|
static int stop_server_cmd(void* handle, int argc, char* argv[]);
|
|
static int connect_cmd(void* handle, int argc, char* argv[]);
|
|
static int insecure_connect_cmd(void* handle, int argc, char* argv[]);
|
|
static int disconnect_cmd(void* handle, int argc, char* argv[]);
|
|
static int write_cmd(void* handle, int argc, char* argv[]);
|
|
static int speed_test_cmd(void* handle, int argc, char* argv[]);
|
|
static int dump_cmd(void* handle, int argc, char* argv[]);
|
|
|
|
static const char* TRANS_START = "START:";
|
|
static const char* TRANS_START_ACK = "START_ACK";
|
|
static const char* TRANS_EOF = "EOF";
|
|
|
|
static struct list_node device_list = LIST_INITIAL_VALUE(device_list);
|
|
static sem_t spp_send_sem;
|
|
static void* spp_app_handle = NULL;
|
|
static uv_loop_t spp_thread_loop = { 0 };
|
|
static transmit_context_t trans_ctx = { 0 };
|
|
|
|
static bt_command_t g_spp_tables[] = {
|
|
{ "start", start_server_cmd, 0, "\"start spp server param: <scn>(range in [1,28]) <uuid>\"" },
|
|
{ "stop", stop_server_cmd, 0, "\"stop spp server param: <scn>(range in [1,28])\"" },
|
|
{ "connect", connect_cmd, 0, "\"connect spp device param: <address> <port> <uuid>\"" },
|
|
{ "insconnect", insecure_connect_cmd, 0, "\"connect spp device with insecure mode param: <address> <port> <uuid>\"" },
|
|
{ "disconnect", disconnect_cmd, 0, "\"disconnect peer device param: <address> <port>\"" },
|
|
{ "write", write_cmd, 0, "\"write data to peer param: <port> <data>\"" },
|
|
{ "speed", speed_test_cmd, 0, "\"performance test param: <port> <iteration>\" note:iteration * 990 shoule less than free memory" },
|
|
{ "dump", dump_cmd, 0, "\"dump spp current state\"" },
|
|
};
|
|
|
|
static void usage(void)
|
|
{
|
|
printf("Usage:\n");
|
|
printf("\tport: serial port (1~28)\n"
|
|
"\tuuid: uuid default 0x1101\n"
|
|
"\taddress: peer device address like 00:01:02:03:04:05\n");
|
|
printf("Commands:\n");
|
|
for (int i = 0; i < ARRAY_SIZE(g_spp_tables); i++) {
|
|
printf("\t%-8s\t%s\n", g_spp_tables[i].cmd, g_spp_tables[i].help);
|
|
}
|
|
}
|
|
|
|
static spp_device_t* find_pty_by_port(int port)
|
|
{
|
|
struct list_node* list = &device_list;
|
|
struct list_node* node;
|
|
spp_device_t* device;
|
|
|
|
list_for_every(list, node)
|
|
{
|
|
device = (spp_device_t*)node;
|
|
if (device->port == port) {
|
|
return device;
|
|
}
|
|
}
|
|
|
|
PRINT("Device not found for port:%d", port);
|
|
return NULL;
|
|
}
|
|
|
|
static spp_device_t* find_pty_by_handle(void* handle)
|
|
{
|
|
struct list_node* list = &device_list;
|
|
struct list_node* node;
|
|
spp_device_t* device;
|
|
|
|
list_for_every(list, node)
|
|
{
|
|
device = (spp_device_t*)node;
|
|
if (device->pty == handle) {
|
|
return device;
|
|
}
|
|
}
|
|
|
|
PRINT("Device not found for handle:%p", handle);
|
|
return NULL;
|
|
}
|
|
|
|
static void bulk_trans_complete(euv_pty_t* handle, uint8_t* buf, int status)
|
|
{
|
|
transmit_context_t* ctx = &trans_ctx;
|
|
|
|
ctx->bulk_count--;
|
|
if (ctx->bulk_count)
|
|
euv_pty_write(handle, buf, ctx->bulk_length, bulk_trans_complete);
|
|
else
|
|
free(buf);
|
|
}
|
|
|
|
static void spp_trans_reset(void)
|
|
{
|
|
memset(&trans_ctx, 0, sizeof(trans_ctx));
|
|
}
|
|
|
|
static void show_result(uint64_t start, uint64_t end, uint32_t bytes)
|
|
{
|
|
float use = (float)(end - start) / 1000;
|
|
float spd = (float)(bytes / 1024) / use;
|
|
|
|
PRINT("transmit done, total: %" PRIu32 " bytes, use: %f seconds, speed: %f KB/s", bytes, use, spd);
|
|
}
|
|
|
|
static void speed_test_start(void* cmd)
|
|
{
|
|
spp_cmd_t* msg = cmd;
|
|
spp_device_t* device;
|
|
transmit_context_t* ctx = &trans_ctx;
|
|
static uint8_t start[100];
|
|
uint16_t port = msg->port;
|
|
uint16_t times = msg->len;
|
|
|
|
free(msg);
|
|
|
|
device = find_pty_by_port(port);
|
|
if (!device)
|
|
return;
|
|
|
|
if (ctx->state != TRANS_IDLE) {
|
|
PRINT("spp is testing");
|
|
return;
|
|
}
|
|
ctx->handle = device->pty;
|
|
ctx->state = TRANS_SENDING;
|
|
ctx->bulk_length = 990;
|
|
ctx->bulk_count = times;
|
|
ctx->trans_total_size = ctx->bulk_length * ctx->bulk_count;
|
|
|
|
memset(start, 0, sizeof(start));
|
|
sprintf((char*)start, "START:%" PRIu32 ";", ctx->trans_total_size);
|
|
euv_pty_write(device->pty, start, strlen((const char*)start), NULL);
|
|
PRINT("transmit start, waiting for %" PRIu32 " bytes transmit done", ctx->trans_total_size);
|
|
}
|
|
|
|
static void spp_data_received(euv_pty_t* handle, const uint8_t* buf, ssize_t size)
|
|
{
|
|
transmit_context_t* ctx = &trans_ctx;
|
|
|
|
if (ctx->state != TRANS_IDLE && handle != ctx->handle) {
|
|
PRINT("spp is testing ,ignore it");
|
|
return;
|
|
}
|
|
|
|
switch (ctx->state) {
|
|
case TRANS_IDLE:
|
|
if (strncmp((const char*)buf, TRANS_START, strlen(TRANS_START)) == 0) {
|
|
spp_trans_reset();
|
|
ctx->handle = handle;
|
|
ctx->state = TRANS_RECVING;
|
|
sscanf((const char*)buf, "START:%" PRIu32 ";", &ctx->trans_total_size);
|
|
PRINT("receive start, waiting for %" PRIu32 " bytes transmit done", ctx->trans_total_size);
|
|
euv_pty_write(handle, (uint8_t*)TRANS_START_ACK, strlen(TRANS_START_ACK), NULL);
|
|
ctx->start_timestamp = get_timestamp_msec();
|
|
} else
|
|
lib_dumpbuffer("spp read", buf, size);
|
|
break;
|
|
case TRANS_SENDING:
|
|
if (strncmp((const char*)buf, TRANS_EOF, strlen(TRANS_EOF)) == 0) {
|
|
ctx->end_timestamp = get_timestamp_msec();
|
|
show_result(ctx->start_timestamp, ctx->end_timestamp, ctx->trans_total_size);
|
|
spp_trans_reset();
|
|
} else if (strncmp((const char*)buf, TRANS_START_ACK, strlen(TRANS_START_ACK)) == 0) {
|
|
sem_post(&spp_send_sem);
|
|
ctx->bulk_buf = malloc(ctx->bulk_length);
|
|
memset(ctx->bulk_buf, 0xA5, ctx->bulk_length);
|
|
ctx->start_timestamp = get_timestamp_msec();
|
|
euv_pty_write(handle, ctx->bulk_buf, ctx->bulk_length, bulk_trans_complete);
|
|
}
|
|
break;
|
|
case TRANS_RECVING:
|
|
ctx->received_size += size;
|
|
if (ctx->received_size >= ctx->trans_total_size) {
|
|
ctx->end_timestamp = get_timestamp_msec();
|
|
show_result(ctx->start_timestamp, ctx->end_timestamp, ctx->trans_total_size);
|
|
euv_pty_write(handle, (uint8_t*)TRANS_EOF, 4, NULL);
|
|
spp_trans_reset();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void pty_read_cb(euv_pty_t* handle, const uint8_t* buf, ssize_t size)
|
|
{
|
|
if (size > 0)
|
|
spp_data_received(handle, buf, size);
|
|
else if (size < 0) {
|
|
PRINT("%s read failed, status:%d", __func__, (int)size);
|
|
euv_pty_read_stop(handle);
|
|
spp_device_t* device = find_pty_by_handle(handle);
|
|
if (device == NULL)
|
|
return;
|
|
|
|
euv_pty_close(device->pty);
|
|
device->pty = NULL;
|
|
list_delete(&device->node);
|
|
free(device);
|
|
}
|
|
}
|
|
|
|
static void check_resource_release(uint16_t port)
|
|
{
|
|
spp_device_t* device;
|
|
|
|
device = find_pty_by_port(port);
|
|
if (device == NULL)
|
|
return;
|
|
|
|
euv_pty_close(device->pty);
|
|
device->pty = NULL;
|
|
list_delete(&device->node);
|
|
free(device);
|
|
}
|
|
|
|
static void connection_state_process(void* data)
|
|
{
|
|
spp_cmd_t* msg = data;
|
|
char addr_str[BT_ADDR_STR_LENGTH] = { 0 };
|
|
|
|
bt_addr_ba2str(&msg->addr, addr_str);
|
|
PRINT("%s addr:%s, scn: %" PRIx16 ", port: %" PRIx16 ", state:%" PRIu32, __func__, addr_str, msg->scn, msg->port, msg->state);
|
|
|
|
if (msg->state == PROFILE_STATE_DISCONNECTED) {
|
|
check_resource_release(msg->port);
|
|
/**
|
|
* Reset the SPP transation ctx when SPP disconnct.
|
|
*/
|
|
spp_trans_reset();
|
|
}
|
|
|
|
free(msg);
|
|
}
|
|
|
|
static void connection_state_callback(void* handle, bt_address_t* addr, uint16_t scn,
|
|
uint16_t port, profile_connection_state_t state)
|
|
{
|
|
spp_cmd_t* msg = malloc(sizeof(spp_cmd_t));
|
|
if (!msg)
|
|
return;
|
|
|
|
msg->handle = handle;
|
|
memcpy(&msg->addr, addr, sizeof(bt_address_t));
|
|
msg->scn = scn;
|
|
msg->port = port;
|
|
msg->state = state;
|
|
|
|
do_in_thread_loop(&spp_thread_loop, connection_state_process, (void*)msg);
|
|
}
|
|
|
|
static void pty_open_process(void* data)
|
|
{
|
|
spp_cmd_t* msg = data;
|
|
char addr_str[BT_ADDR_STR_LENGTH] = { 0 };
|
|
|
|
int fd = open(msg->name, O_RDWR | O_NOCTTY | O_CLOEXEC);
|
|
if (fd < 0)
|
|
return;
|
|
|
|
bt_addr_ba2str(&msg->addr, addr_str);
|
|
|
|
PRINT("%s addr:%s, scn:%d, port: %d, name:%s, slave fd:%d", __func__, addr_str, msg->scn, msg->port, msg->name, fd);
|
|
spp_device_t* device = malloc(sizeof(spp_device_t));
|
|
device->fd = fd;
|
|
device->port = msg->port;
|
|
free(msg);
|
|
device->pty = euv_pty_init(&spp_thread_loop, fd, UV_TTY_MODE_IO);
|
|
if (device->pty == NULL) {
|
|
free(device);
|
|
PRINT("%s pty init error", __func__);
|
|
return;
|
|
}
|
|
|
|
list_add_tail(&device_list, &device->node);
|
|
euv_pty_read_start(device->pty, 2048, pty_read_cb);
|
|
}
|
|
|
|
static void pty_open_callback(void* handle, bt_address_t* addr, uint16_t scn, uint16_t port, char* name)
|
|
{
|
|
spp_cmd_t* msg = malloc(sizeof(spp_cmd_t));
|
|
if (!msg)
|
|
return;
|
|
|
|
msg->handle = handle;
|
|
memcpy(&msg->addr, addr, sizeof(bt_address_t));
|
|
msg->scn = scn;
|
|
msg->port = port;
|
|
msg->name = strdup(name);
|
|
|
|
do_in_thread_loop(&spp_thread_loop, pty_open_process, (void*)msg);
|
|
}
|
|
|
|
static int start_server_cmd(void* handle, int argc, char* argv[])
|
|
{
|
|
uint16_t uuid;
|
|
bt_uuid_t uuid16;
|
|
|
|
if (argc < 1)
|
|
return CMD_PARAM_NOT_ENOUGH;
|
|
|
|
uint16_t scn = atoi(argv[0]);
|
|
if (argc == 2)
|
|
uuid = strtol(argv[1], NULL, 16);
|
|
else
|
|
uuid = BT_UUID_SERVCLASS_SERIAL_PORT;
|
|
|
|
bt_uuid16_create(&uuid16, uuid);
|
|
if (bt_spp_server_start(handle, spp_app_handle, scn, &uuid16,
|
|
CONFIG_BLUETOOTH_SPP_SERVER_MAX_CONNECTIONS)
|
|
!= BT_STATUS_SUCCESS) {
|
|
PRINT("server_start failed, scn:%d, uuid: 0x%04x\n", scn, uuid);
|
|
return CMD_ERROR;
|
|
}
|
|
|
|
return CMD_OK;
|
|
}
|
|
|
|
static int stop_server_cmd(void* handle, int argc, char* argv[])
|
|
{
|
|
if (argc < 1)
|
|
return CMD_PARAM_NOT_ENOUGH;
|
|
|
|
uint16_t scn = atoi(argv[0]);
|
|
bt_spp_server_stop(handle, spp_app_handle, scn);
|
|
|
|
return CMD_OK;
|
|
}
|
|
|
|
static int connect_cmd(void* handle, int argc, char* argv[])
|
|
{
|
|
int16_t scn;
|
|
uint16_t uuid;
|
|
uint16_t port;
|
|
bt_uuid_t uuid16;
|
|
bt_address_t addr;
|
|
|
|
if (argc < 2)
|
|
return CMD_PARAM_NOT_ENOUGH;
|
|
|
|
if (bt_addr_str2ba(argv[0], &addr) < 0)
|
|
return CMD_INVALID_ADDR;
|
|
|
|
scn = atoi(argv[1]);
|
|
|
|
if (argc == 3)
|
|
uuid = strtol(argv[2], NULL, 16);
|
|
else
|
|
uuid = BT_UUID_SERVCLASS_SERIAL_PORT;
|
|
|
|
bt_uuid16_create(&uuid16, uuid);
|
|
if (bt_spp_connect(handle, spp_app_handle, &addr, scn, &uuid16, &port) != BT_STATUS_SUCCESS) {
|
|
PRINT("connect scn:%d, failed\n", scn);
|
|
return CMD_ERROR;
|
|
}
|
|
|
|
PRINT("%s, address:%s scn:%d, port:%d, uuid:0x%04x", __func__, argv[0], scn, port, uuid);
|
|
return CMD_OK;
|
|
}
|
|
|
|
static int insecure_connect_cmd(void* handle, int argc, char* argv[])
|
|
{
|
|
int16_t scn;
|
|
uint16_t uuid;
|
|
uint16_t port;
|
|
bt_uuid_t uuid16;
|
|
bt_address_t addr;
|
|
|
|
if (argc < 2)
|
|
return CMD_PARAM_NOT_ENOUGH;
|
|
|
|
if (bt_addr_str2ba(argv[0], &addr) < 0)
|
|
return CMD_INVALID_ADDR;
|
|
|
|
scn = atoi(argv[1]);
|
|
|
|
if (argc == 3)
|
|
uuid = strtol(argv[2], NULL, 16);
|
|
else
|
|
uuid = BT_UUID_SERVCLASS_SERIAL_PORT;
|
|
|
|
bt_uuid16_create(&uuid16, uuid);
|
|
if (bt_spp_insecure_connect(handle, spp_app_handle, &addr, scn, &uuid16, &port) != BT_STATUS_SUCCESS) {
|
|
PRINT("connect scn:%d, failed\n", scn);
|
|
return CMD_ERROR;
|
|
}
|
|
|
|
PRINT("%s, address:%s scn:%d, port:%d, uuid:0x%04x", __func__, argv[1], scn, port, uuid);
|
|
return CMD_OK;
|
|
}
|
|
|
|
static void spp_disconnect(void* data)
|
|
{
|
|
spp_device_t* device;
|
|
spp_cmd_t* msg = data;
|
|
bt_address_t addr;
|
|
|
|
device = find_pty_by_port(msg->port);
|
|
if (device == NULL) {
|
|
free(data);
|
|
return;
|
|
}
|
|
|
|
bt_addr_set_empty(&addr);
|
|
bt_spp_disconnect(msg->handle, spp_app_handle, &addr, msg->port);
|
|
PRINT("%s, port:%d disconnecting", __func__, msg->port);
|
|
free(data);
|
|
}
|
|
|
|
static int disconnect_cmd(void* handle, int argc, char* argv[])
|
|
{
|
|
if (argc < 2)
|
|
return CMD_PARAM_NOT_ENOUGH;
|
|
|
|
spp_cmd_t* msg = malloc(sizeof(spp_cmd_t));
|
|
if (!msg)
|
|
return CMD_ERROR;
|
|
|
|
msg->handle = handle;
|
|
msg->port = atoi(argv[1]);
|
|
|
|
PRINT("%s, address:%s port:%d", __func__, argv[0], msg->port);
|
|
do_in_thread_loop(&spp_thread_loop, spp_disconnect, msg);
|
|
|
|
return CMD_OK;
|
|
}
|
|
|
|
static void write_complete(euv_pty_t* handle, uint8_t* buf, int status)
|
|
{
|
|
free(buf);
|
|
}
|
|
|
|
static void spp_write(void* data)
|
|
{
|
|
spp_device_t* device;
|
|
spp_cmd_t* msg = data;
|
|
|
|
device = find_pty_by_port(msg->port);
|
|
if (!device)
|
|
goto error;
|
|
|
|
if (trans_ctx.handle == device->pty) {
|
|
PRINT("spp is testing");
|
|
goto error;
|
|
}
|
|
|
|
euv_pty_write(device->pty, msg->buf, msg->len, write_complete);
|
|
free(msg);
|
|
return;
|
|
|
|
error:
|
|
free(msg->buf);
|
|
free(msg);
|
|
}
|
|
|
|
static int write_cmd(void* handle, int argc, char* argv[])
|
|
{
|
|
uint16_t port;
|
|
uint8_t* buf;
|
|
|
|
if (argc < 2)
|
|
return CMD_PARAM_NOT_ENOUGH;
|
|
|
|
port = atoi(argv[0]);
|
|
buf = (uint8_t*)strdup(argv[1]);
|
|
|
|
spp_cmd_t* msg = malloc(sizeof(spp_cmd_t));
|
|
if (!msg) {
|
|
free(buf);
|
|
return CMD_ERROR;
|
|
}
|
|
|
|
msg->port = port;
|
|
msg->buf = buf;
|
|
msg->len = strlen(argv[1]);
|
|
|
|
do_in_thread_loop(&spp_thread_loop, spp_write, msg);
|
|
return CMD_OK;
|
|
}
|
|
|
|
static int speed_test_cmd(void* handle, int argc, char* argv[])
|
|
{
|
|
int port, times;
|
|
|
|
if (argc < 2)
|
|
return CMD_PARAM_NOT_ENOUGH;
|
|
|
|
port = atoi(argv[0]);
|
|
times = atoi(argv[1]);
|
|
if (port < 0 || times < 0)
|
|
return CMD_INVALID_PARAM;
|
|
|
|
spp_cmd_t* msg = malloc(sizeof(spp_cmd_t));
|
|
if (!msg)
|
|
return CMD_ERROR;
|
|
|
|
msg->port = port;
|
|
msg->len = times;
|
|
do_in_thread_loop(&spp_thread_loop, speed_test_start, msg);
|
|
/* wait start ack */
|
|
struct timespec ts;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 2;
|
|
if (sem_timedwait(&spp_send_sem, &ts) < 0) {
|
|
spp_trans_reset();
|
|
return CMD_ERROR;
|
|
}
|
|
|
|
return CMD_OK;
|
|
}
|
|
|
|
static int dump_cmd(void* handle, int argc, char* argv[])
|
|
{
|
|
return CMD_OK;
|
|
}
|
|
|
|
static spp_callbacks_t spp_cbs = {
|
|
sizeof(spp_callbacks_t),
|
|
pty_open_callback,
|
|
connection_state_callback,
|
|
};
|
|
|
|
int spp_command_init(void* handle)
|
|
{
|
|
sem_init(&spp_send_sem, 0, 0);
|
|
thread_loop_init(&spp_thread_loop);
|
|
thread_loop_run(&spp_thread_loop, true, "spp_client");
|
|
spp_app_handle = bt_spp_register_app_ext(handle, "bttool", SPP_PORT_TYPE_TTY, &spp_cbs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void spp_command_uninit(void* handle)
|
|
{
|
|
bt_spp_unregister_app(handle, spp_app_handle);
|
|
sem_destroy(&spp_send_sem);
|
|
thread_loop_exit(&spp_thread_loop);
|
|
memset(&spp_thread_loop, 0, sizeof(spp_thread_loop));
|
|
}
|
|
|
|
int spp_command_exec(void* handle, int argc, char* argv[])
|
|
{
|
|
int ret = CMD_USAGE_FAULT;
|
|
|
|
if (argc > 0)
|
|
ret = execute_command_in_table(handle, g_spp_tables, ARRAY_SIZE(g_spp_tables), argc, argv);
|
|
|
|
if (ret < 0)
|
|
usage();
|
|
|
|
return ret;
|
|
}
|