blob: 69fc4ba9170774198207a61794a3c813d172c1b0 [file]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2024 Intel Corporation
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include "src/shared/util.h"
#include "src/shared/queue.h"
#include "src/shared/shell.h"
#include "src/shared/hci.h"
#include "monitor/bt.h"
#include "hci.h"
static struct bt_hci *hci;
static struct queue *events;
struct hci_event {
uint8_t event;
unsigned int id;
};
static void hci_open(int argc, char *argv[])
{
long index;
char *endptr = NULL;
if (hci) {
bt_shell_printf("HCI channel already open\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
index = strtol(argv[1], &endptr, 0);
if (!endptr || *endptr != '\0' || index < 0 || index > UINT16_MAX) {
bt_shell_printf("Invalid index: %s\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (!strcasecmp(argv[2], "raw")) {
hci = bt_hci_new_raw_device(index);
if (!hci) {
bt_shell_printf("Unable to open raw channel\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
} else if (!strcasecmp(argv[2], "user")) {
hci = bt_hci_new_user_channel(index);
if (!hci) {
bt_shell_printf("Unable to open user channel\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
} else {
bt_shell_printf("Invalid channel: %s\n", argv[2]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("HCI index %ld %s channel opened\n", index, argv[2]);
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static uint8_t *str2bytearray(char *arg, size_t *val_len)
{
uint8_t value[UINT8_MAX];
char *entry;
unsigned int i;
for (i = 0; (entry = strsep(&arg, " \t")) != NULL; i++) {
long val;
char *endptr = NULL;
if (*entry == '\0')
continue;
if (i >= G_N_ELEMENTS(value)) {
bt_shell_printf("Too much data\n");
return NULL;
}
val = strtol(entry, &endptr, 0);
if (!endptr || *endptr != '\0' || val > UINT8_MAX) {
bt_shell_printf("Invalid value at index %d\n", i);
return NULL;
}
value[i] = val;
}
*val_len = i;
return util_memdup(value, i);
}
static void hci_cmd_complete(const void *data, uint8_t size, void *user_data)
{
bt_shell_printf("HCI Command complete:\n");
bt_shell_hexdump(data, size);
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void hci_cmd(int argc, char *argv[])
{
long opcode;
struct iovec iov = {};
char *endptr = NULL;
unsigned int ret;
if (!hci) {
bt_shell_printf("HCI channel not open\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
opcode = strtol(argv[1], &endptr, 0);
if (!endptr || *endptr != '\0' || opcode < 0 || opcode > UINT16_MAX) {
bt_shell_printf("Invalid opcode: %s\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (argc > 2) {
iov.iov_base = str2bytearray(argv[2], &iov.iov_len);
if (!iov.iov_base) {
bt_shell_printf("Invalid parameters: %s\n", argv[2]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
ret = bt_hci_send(hci, opcode, iov.iov_base, iov.iov_len,
hci_cmd_complete, NULL, NULL);
free(iov.iov_base);
if (!ret)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
static void hci_send(int argc, char *argv[])
{
uint8_t type;
long handle;
struct iovec iov = {};
char *endptr = NULL;
bool ret;
if (!hci) {
bt_shell_printf("HCI channel not open\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (!strcasecmp(argv[1], "acl")) {
type = BT_H4_ACL_PKT;
} else if (!strcasecmp(argv[1], "sco")) {
type = BT_H4_SCO_PKT;
} else if (!strcasecmp(argv[1], "iso")) {
type = BT_H4_ISO_PKT;
} else {
bt_shell_printf("Invalid type: %s\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
handle = strtol(argv[2], &endptr, 0);
if (!endptr || *endptr != '\0' || handle < 0 || handle > UINT16_MAX) {
bt_shell_printf("Invalid handle: %s\n", argv[2]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (argc > 3) {
iov.iov_base = str2bytearray(argv[3], &iov.iov_len);
if (!iov.iov_base) {
bt_shell_printf("Invalid data: %s\n", argv[3]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
ret = bt_hci_send_data(hci, type, handle, iov.iov_base, iov.iov_len);
free(iov.iov_base);
return bt_shell_noninteractive_quit(ret ? EXIT_SUCCESS : EXIT_FAILURE);
}
static bool match_event(const void *data, const void *match_data)
{
const struct hci_event *evt = data;
uint8_t event = PTR_TO_UINT(match_data);
return evt->event == event;
}
static void hci_evt_received(const void *data, uint8_t size, void *user_data)
{
struct hci_event *evt = user_data;
bt_shell_printf("HCI Event 0x%02x received:\n", evt->event);
bt_shell_hexdump(data, size);
}
static void hci_register(int argc, char *argv[])
{
struct hci_event *evt;
long event;
char *endptr = NULL;
if (!hci) {
bt_shell_printf("HCI channel not open\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
event = strtol(argv[1], &endptr, 0);
if (!endptr || *endptr != '\0' || event < 0 || event > UINT8_MAX) {
bt_shell_printf("Invalid event: %s\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (!events)
events = queue_new();
evt = queue_find(events, match_event, UINT_TO_PTR(event));
if (evt) {
bt_shell_printf("Event already registered\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
evt = new0(struct hci_event, 1);
evt->event = event;
evt->id = bt_hci_register(hci, event, hci_evt_received, evt, NULL);
if (!evt->id) {
free(evt);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("HCI Event 0x%02x registered\n", (uint8_t)event);
queue_push_tail(events, evt);
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void hci_unregister(int argc, char *argv[])
{
struct hci_event *evt;
long event;
char *endptr = NULL;
if (!hci) {
bt_shell_printf("HCI channel not open\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
event = strtol(argv[1], &endptr, 0);
if (!endptr || *endptr != '\0' || event < 0 || event > UINT8_MAX) {
bt_shell_printf("Invalid event: %s\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
evt = queue_find(events, match_event, UINT_TO_PTR(event));
if (!evt) {
bt_shell_printf("Event not registered\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_hci_unregister(hci, evt->id);
queue_remove(events, evt);
free(evt);
bt_shell_printf("HCI Event 0x%02x unregistered\n", (uint8_t)event);
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void hci_close(int argc, char *argv[])
{
if (!hci) {
bt_shell_printf("HCI channel not open\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_hci_unref(hci);
hci = NULL;
bt_shell_printf("HCI channel closed\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static const struct bt_shell_menu hci_menu = {
.name = "hci",
.desc = "HCI Submenu",
.entries = {
{ "open", "<index> <chan=raw,user>", hci_open,
"Open HCI channel" },
{ "cmd", "<opcode> [parameters...]", hci_cmd,
"Send HCI command" },
{ "send", "<type=acl,sco,iso> <handle> [data...]", hci_send,
"Send HCI data" },
{ "register", "<event>", hci_register,
"Register HCI event handler" },
{ "unregister", "<event>", hci_unregister,
"Unregister HCI event handler" },
{ "close", NULL, hci_close, "Close HCI channel" },
{} },
};
void hci_add_submenu(void)
{
bt_shell_add_submenu(&hci_menu);
}
void hci_remove_submenu(void)
{
if (!hci)
return;
if (events) {
queue_destroy(events, free);
events = NULL;
}
bt_hci_unref(hci);
hci = NULL;
}