diff --git a/include/zephyr/bluetooth/classic/hid_device.h b/include/zephyr/bluetooth/classic/hid_device.h new file mode 100644 index 00000000000..3b420a54f20 --- /dev/null +++ b/include/zephyr/bluetooth/classic/hid_device.h @@ -0,0 +1,248 @@ +/** @file + * @brief HID Device Protocol handling. + */ + +/* + * Copyright 2025 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_HID_DEVICE_H_ +#define ZEPHYR_INCLUDE_BLUETOOTH_HID_DEVICE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#define BT_HID_MAX_MTU 64 +#define BT_HID_REPORT_DATA_LEN 64 + +/** @brief HID handshake response code */ +enum { + BT_HID_HANDSHAKE_RSP_SUCCESS = 0, + BT_HID_HANDSHAKE_RSP_NOT_READY, + BT_HID_HANDSHAKE_RSP_ERR_INVALID_REP_ID, + BT_HID_HANDSHAKE_RSP_ERR_UNSUPPORTED_REQ, + BT_HID_HANDSHAKE_RSP_ERR_INVALID_PARAM, + BT_HID_HANDSHAKE_RSP_ERR_UNKNOWN = 14, + BT_HID_HANDSHAKE_RSP_ERR_FATAL, +}; + +/** @brief HID protocol parameters for set protocal */ +enum { + BT_HID_PROTOCOL_BOOT_MODE = 0, + BT_HID_PROTOCOL_REPORT_MODE, +}; + +/** @brief HID report types in get, set, data */ +enum { + BT_HID_REPORT_TYPE_OTHER = 0, + BT_HID_REPORT_TYPE_INPUT, + BT_HID_REPORT_TYPE_OUTPUT, + BT_HID_REPORT_TYPE_FEATURE, +}; + +/** @brief HID seesion role */ +enum bt_hid_session_role { + BT_HID_SESSION_ROLE_UNKNOWN, + BT_HID_SESSION_ROLE_CTRL, + BT_HID_SESSION_ROLE_INTR, +}; + +/** @brief HID role */ +enum bt_hid_role { + BT_HID_ROLE_ACCEPTOR, + BT_HID_ROLE_INITIATOR +}; + +/** @brief HID session structure. */ +struct bt_hid_session { + struct bt_l2cap_br_chan br_chan; + enum bt_hid_session_role role; /* As ctrl or intr */ +}; + +/** @brief HID Device structure. */ +struct bt_hid_device { + struct bt_conn *conn; + struct net_buf *buf; + + enum bt_hid_role role; /* As initial or accept */ + struct bt_hid_session ctrl_session; + struct bt_hid_session intr_session; + + uint8_t state; + uint8_t pending_vc_unplug; +}; + +/** @brief HID Device callbacks. */ +struct bt_hid_device_cb { + /** + * @brief HID Device Connected Callback + * + * The callback is called whenever an hid device connected. + * + * @param hid hid device connection object. + * + */ + void (*connected)(struct bt_hid_device *hid); + + /** + * @brief HID Device Disconected Callback + * + * The callback is called whenever an hid device disconnected. + * + * @param hid hid device connection object. + * + */ + void (*disconnected)(struct bt_hid_device *hid); + + /** + * @brief HID Device Set Report Callback + * + * An hid device set report request from remote. + * + * @param hid hid device connection object. + * @param data value. + * @param data len. + * + */ + void (*set_report)(struct bt_hid_device *hid, uint8_t *data, uint16_t len); + + /** + * @brief HID Device Get Report Callback + * + * An hid device get report request from remote. + * + * @param hid hid device connection object. + * @param data value. + * @param data len. + * + */ + void (*get_report)(struct bt_hid_device *hid, uint8_t *data, uint16_t len); + + /** + * @brief HID Device Set Protocol Callback + * + * An hid device set protocol request from remote. + * + * @param hid hid device connection object. + * @param protocol protocol. + * + */ + void (*set_protocol)(struct bt_hid_device *hid, uint8_t protocol); + + /** + * @brief HID Device Get Protocol Callback + * + * An hid device get protocol request from remote. + * + * @param hid hid device connection object. + * + */ + void (*get_protocol)(struct bt_hid_device *hid); + + /** + * @brief HID Device Intr data Callback + * + * An hid device intr data request from remote. + * + * @param hid hid device connection object. + * @param data value. + * @param data len. + * + */ + void (*intr_data)(struct bt_hid_device *hid, uint8_t *data, uint16_t len); + + /** + * @brief HID Device Unplug Callback + * + * An hid device unplug request from remote. + * + * @param hid hid device connection object. + * + */ + void (*vc_unplug)(struct bt_hid_device *hid); +}; + +/** @brief HID Device Register Callback. + * + * The cb is called when bt_hid_device is called or it is operated by remote device. + * + * @param cb The callback function. + * + * @return 0 in case of success and error code in case of error. + */ +int bt_hid_device_register(struct bt_hid_device_cb *cb); + +/** @brief HID Device Connect. + * + * This function is to be called after the conn parameter is obtained by + * performing a GAP procedure. The API is to be used to establish HID + * Device connection between devices. + * + * @param conn Pointer to bt_conn structure. + * + * @return pointer to struct bt_hid_device in case of success or NULL in case + * of error. + */ +struct bt_hid_device *bt_hid_device_connect(struct bt_conn *conn); + +/** @brief HID Device Disconnect + * + * This function close HID Device connection. + * + * @param hid The bt_hid_device instance. + * + * @return 0 in case of success and error code in case of error. + */ +int bt_hid_device_disconnect(struct bt_hid_device *hid); + +/** @brief HID Device Send Ctrl Data. + * + * Send hid data by ctrl channel. + * + * @param hid The bt_hid_device instance. + * @param type HID report type. + * @param data send buffer. + * @param len buffer size. + * + * @return size in case of success and error code in case of error. + */ +int bt_hid_device_send_ctrl_data(struct bt_hid_device *hid, uint8_t type, uint8_t *data, + uint16_t len); + +/** @brief HID Device Send Intr Data. + * + * Send hid data by Intr channel. + * + * @param hid The bt_hid_device instance. + * @param type HID report type. + * @param data send buffer. + * @param len buffer size. + * + * @return size in case of success and error code in case of error. + */ +int bt_hid_device_send_intr_data(struct bt_hid_device *hid, uint8_t type, uint8_t *data, + uint16_t len); + +/** @brief HID Device Send Error Response. + * + * Send hid error response by ctrl channel. + * + * @param hid The bt_hid_device instance. + * @param error error code. + * + * @return size in case of success and error code in case of error. + */ +int bt_hid_device_report_error(struct bt_hid_device *hid, uint8_t error); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_HID_DEVICE_H_ */ diff --git a/include/zephyr/bluetooth/classic/sdp.h b/include/zephyr/bluetooth/classic/sdp.h index 9ecf4525547..2c747b4d3ac 100644 --- a/include/zephyr/bluetooth/classic/sdp.h +++ b/include/zephyr/bluetooth/classic/sdp.h @@ -204,6 +204,8 @@ extern "C" { #define BT_SDP_ATTR_HID_SUPERVISION_TIMEOUT 0x020c /**< HID Supervision Timeout */ #define BT_SDP_ATTR_HID_NORMALLY_CONNECTABLE 0x020d /**< HID Normally Connectable */ #define BT_SDP_ATTR_HID_BOOT_DEVICE 0x020e /**< HID Boot Device */ +#define BT_SDP_ATTR_HID_MAX_LATENCY 0x020f /**< HID Max Latency */ +#define BT_SDP_ATTR_HID_MIN_LATENCY 0x0210 /**< HID Min Latency */ /** * @} */ @@ -592,7 +594,9 @@ int bt_sdp_discover_cancel(struct bt_conn *conn, /** @brief Protocols to be asked about specific parameters */ enum bt_sdp_proto { + BT_SDP_PROTO_SDP = 0x0001, BT_SDP_PROTO_RFCOMM = 0x0003, + BT_SDP_PROTO_HID = 0x0011, BT_SDP_PROTO_L2CAP = 0x0100, }; diff --git a/subsys/bluetooth/host/classic/CMakeLists.txt b/subsys/bluetooth/host/classic/CMakeLists.txt index 1741d0760c6..f789217b6fa 100644 --- a/subsys/bluetooth/host/classic/CMakeLists.txt +++ b/subsys/bluetooth/host/classic/CMakeLists.txt @@ -36,3 +36,8 @@ zephyr_library_sources_ifdef( CONFIG_BT_DID did.c ) + +zephyr_library_sources_ifdef( + CONFIG_BT_HID_DEVICE + hid_device.c + ) \ No newline at end of file diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index b07742280c2..072b173b5f2 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -230,6 +230,13 @@ config BT_DEVICE_VERSION endif # BT_DID +config BT_HID_DEVICE + bool "Bluetooth HID Device Profile [EXPERIMENTAL]" + select EXPERIMENTAL + default n + help + This option enables the HID Device profile + config BT_PAGE_TIMEOUT hex "Bluetooth Page Timeout" default 0x2000 diff --git a/subsys/bluetooth/host/classic/hid_device.c b/subsys/bluetooth/host/classic/hid_device.c new file mode 100644 index 00000000000..71b70557ce4 --- /dev/null +++ b/subsys/bluetooth/host/classic/hid_device.c @@ -0,0 +1,752 @@ +/* + * Copyright 2025 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "common/assert.h" + +#include +#include +#include +#include + +#include "host/hci_core.h" +#include "host/conn_internal.h" +#include "host/l2cap_internal.h" + +#include "hid_internal.h" + +#include +LOG_MODULE_REGISTER(bt_hid_device); + +#define HID_PAR_REPORT_TYPE_MASK 0x03 + +/* Get the HID device from CTRL L2CAP channel */ +#define HID_DEVICE_BY_CTRL_CHAN(_ch) \ + CONTAINER_OF(_ch, struct bt_hid_device, ctrl_session.br_chan.chan) + +/* Get the HID device from INTR L2CAP channel */ +#define HID_DEVICE_BY_INTR_CHAN(_ch) \ + CONTAINER_OF(_ch, struct bt_hid_device, intr_session.br_chan.chan) + +/* Get the HID session from L2CAP channel */ +#define HID_SESSION_BY_CHAN(_ch) CONTAINER_OF(_ch, struct bt_hid_session, br_chan.chan) + +/* HID device callback */ +static struct bt_hid_device_cb *hid_cb; + +/* HID device connections */ +static struct bt_hid_device connections[CONFIG_BT_MAX_CONN]; + +static void bt_hid_deinit(struct bt_hid_device *hid); + +static struct bt_hid_device *hid_get_connection(struct bt_conn *conn) +{ + struct bt_hid_device *hid = &connections[bt_conn_index(conn)]; + + if (hid->conn == NULL) { + /* Clean the memory area before returning */ + (void)memset(hid, 0, sizeof(*hid)); + } + + return hid; +} + +static enum bt_hid_session_role get_session_role_from_chan(struct bt_l2cap_chan *chan) +{ + struct bt_hid_session *session; + + session = HID_SESSION_BY_CHAN(chan); + if (session == NULL) { + LOG_ERR("HID session not found for channel %p", chan); + return BT_HID_SESSION_ROLE_UNKNOWN; + } + + return session->role; +} + +static struct net_buf *hid_create_pdu(uint8_t type, uint8_t param) +{ + struct net_buf *buf; + struct bt_hid_header *hdr; + + buf = bt_l2cap_create_pdu(NULL, sizeof(*hdr)); + if (buf == NULL) { + LOG_ERR("Can't create buf buf for type:%d, param:%d", type, param); + return NULL; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->type = type; + hdr->param = param; + + return buf; +} + +static int hid_send_pdu(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + int err; + + err = bt_l2cap_chan_send(chan, buf); + if (err < 0) { + LOG_ERR("L2CAP send fail, err:%d", err); + net_buf_unref(buf); + return err; + } + + return err; +} + +static int hid_send_data(struct bt_hid_session *session, uint8_t type, uint8_t *data, uint16_t len) +{ + struct net_buf *buf; + + buf = hid_create_pdu(BT_HID_TYPE_DATA, type); + if (buf == NULL) { + return -ENOMEM; + } + + if (len > 0) { + net_buf_add_mem(buf, data, len); + } + + return hid_send_pdu(&session->br_chan.chan, buf); +} + +static int hid_send_handshake(struct bt_hid_session *session, uint8_t response) +{ + struct net_buf *buf; + + buf = hid_create_pdu(BT_HID_TYPE_HANDSHAKE, response); + if (buf == NULL) { + return -ENOMEM; + } + + return hid_send_pdu(&session->br_chan.chan, buf); +} + +static int hid_control_handle(struct bt_l2cap_chan *chan, struct net_buf *buf, uint8_t control) +{ + struct bt_hid_device *hid; + int err = 0; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return -EINVAL; + } + + hid = HID_DEVICE_BY_CTRL_CHAN(chan); + if (hid == NULL) { + LOG_ERR("HID not found"); + return -EIO; + } + + switch (control) { + case BT_HID_CONTROL_VIRTUAL_CABLE_UNPLUG: + bt_hid_device_disconnect(hid); + hid->pending_vc_unplug = 1; + break; + case BT_HID_CONTROL_SUSPEND: + break; + case BT_HID_CONTROL_EXIT_SUSPEND: + break; + default: + LOG_ERR("HID control:%d not handle", control); + err = -EINVAL; + break; + } + + return err; +} + +static int hid_get_report_handle(struct bt_l2cap_chan *chan, struct net_buf *buf, uint8_t param) +{ + struct bt_hid_device *hid; + struct bt_hid_report report = {0}; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return -EINVAL; + } + + hid = HID_DEVICE_BY_CTRL_CHAN(chan); + if (hid == NULL) { + LOG_ERR("HID not found"); + return -EIO; + } + + memcpy(report.data, buf->data, buf->len); + report.type = param & HID_PAR_REPORT_TYPE_MASK; + report.len = buf->len; + + if (hid_cb == NULL || hid_cb->get_report == NULL) { + LOG_ERR("HID get report callback not found"); + return -ESRCH; + } + + hid_cb->get_report(hid, (uint8_t *)&report, sizeof(report)); + return 0; +} + +static int hid_set_report_handle(struct bt_l2cap_chan *chan, struct net_buf *buf, uint8_t param) +{ + struct bt_hid_device *hid; + struct bt_hid_report report = {0}; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return -EINVAL; + } + + hid = HID_DEVICE_BY_CTRL_CHAN(chan); + if (hid == NULL) { + LOG_ERR("HID not found"); + return -EIO; + } + + memcpy(report.data, buf->data, buf->len); + report.type = param & HID_PAR_REPORT_TYPE_MASK; + report.len = buf->len; + + if (hid_cb == NULL || hid_cb->set_report == NULL) { + LOG_ERR("HID set report callback not found"); + return -ESRCH; + } + + hid_cb->set_report(hid, (uint8_t *)&report, sizeof(report)); + return 0; +} + +static int hid_get_protocol_handle(struct bt_l2cap_chan *chan, struct net_buf *buf, uint8_t param) +{ + struct bt_hid_device *hid; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return -EINVAL; + } + + hid = HID_DEVICE_BY_CTRL_CHAN(chan); + if (hid == NULL) { + LOG_ERR("HID not found"); + return -EIO; + } + + if (hid_cb == NULL || hid_cb->get_protocol == NULL) { + LOG_ERR("HID get protocol callback not found"); + return -ESRCH; + } + + hid_cb->get_protocol(hid); + return 0; +} + +static int hid_set_protocol_handle(struct bt_l2cap_chan *chan, struct net_buf *buf, uint8_t param) +{ + struct bt_hid_device *hid; + struct bt_hid_session *session; + uint8_t protocol; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return -EINVAL; + } + + hid = HID_DEVICE_BY_CTRL_CHAN(chan); + if (hid == NULL) { + LOG_ERR("HID not found"); + return -EIO; + } + + protocol = param & BT_HID_PROTOCOL_MASK; + + if (hid_cb == NULL || hid_cb->set_protocol == NULL) { + LOG_ERR("HID set protocol callback not found"); + return -ESRCH; + } + + hid_cb->set_protocol(hid, protocol); + session = HID_SESSION_BY_CHAN(chan); + + return hid_send_handshake(session, BT_HID_HANDSHAKE_RSP_SUCCESS); +} + +static int hid_intr_handle(struct bt_l2cap_chan *chan, struct net_buf *buf, uint8_t param) +{ + struct bt_hid_device *hid; + struct bt_hid_report report = {0}; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return -EINVAL; + } + + hid = HID_DEVICE_BY_INTR_CHAN(chan); + if (hid == NULL) { + LOG_ERR("HID not found"); + return -EIO; + } + + report.type = param & HID_PAR_REPORT_TYPE_MASK; + report.len = buf->len; + memcpy(report.data, buf->data, buf->len); + + if (hid_cb == NULL || hid_cb->intr_data == NULL) { + LOG_ERR("HID intr data callback not found"); + return -ESRCH; + } + + hid_cb->intr_data(hid, (uint8_t *)&report, sizeof(report)); + return 0; +} + +static void bt_hid_l2cap_ctrl_connected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_device *hid; + enum bt_hid_session_role role; + int err; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return; + } + + hid = HID_DEVICE_BY_CTRL_CHAN(chan); + if (hid == NULL) { + LOG_ERR("HID not found"); + return; + } + + role = get_session_role_from_chan(chan); + + LOG_DBG("HID session:%d connected, state %d", role, hid->state); + + if (role != BT_HID_SESSION_ROLE_CTRL) { + LOG_ERR("HID invalid role:%d", role); + return; + } + + hid->state = BT_HID_STATE_CTRL_CONNECTED; + + if (hid->role == BT_HID_ROLE_ACCEPTOR) { + /* Wait for INTR channel connection from remote */ + LOG_DBG("HID wait for INTR channel connection from remote"); + return; + } + + err = bt_l2cap_chan_connect(hid->conn, &hid->intr_session.br_chan.chan, + BT_L2CAP_PSM_HID_INT); + if (err) { + LOG_ERR("HID connect INTR failed"); + hid->state = BT_HID_STATE_DISCONNECTING; + bt_l2cap_chan_disconnect(&hid->ctrl_session.br_chan.chan); + return; + } +} + +static void bt_hid_l2cap_intr_connected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_device *hid; + enum bt_hid_session_role role; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return; + } + + hid = HID_DEVICE_BY_INTR_CHAN(chan); + if (hid == NULL) { + LOG_ERR("HID not found"); + return; + } + + role = get_session_role_from_chan(chan); + + LOG_DBG("HID session:%d connected, state %d", role, hid->state); + + if (role != BT_HID_SESSION_ROLE_INTR) { + LOG_ERR("HID invalid role:%d", role); + return; + } + + hid->state = BT_HID_STATE_CONNECTED; + + if (hid_cb && hid_cb->connected) { + hid_cb->connected(hid); + } +} + +static void bt_hid_l2cap_ctrl_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_device *hid; + enum bt_hid_session_role role; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return; + } + + hid = HID_DEVICE_BY_CTRL_CHAN(chan); + if (hid == NULL) { + LOG_ERR("HID not found"); + return; + } + + role = get_session_role_from_chan(chan); + + LOG_DBG("HID session:%d connected, state %d", role, hid->state); + + if (role != BT_HID_SESSION_ROLE_CTRL) { + LOG_ERR("HID invalid role:%d", role); + return; + } + + /* if INTR session connected, it need to be disconnected */ + if (hid->intr_session.br_chan.chan.conn) { + LOG_DBG("HID disconnect INTR channel"); + bt_l2cap_chan_disconnect(&hid->intr_session.br_chan.chan); + return; + } + + if (hid->pending_vc_unplug && hid_cb && hid_cb->vc_unplug) { + hid_cb->vc_unplug(hid); + hid->pending_vc_unplug = 0; + } + + if (hid_cb && hid_cb->disconnected) { + hid_cb->disconnected(hid); + } + + bt_hid_deinit(hid); +} + +static void bt_hid_l2cap_intr_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_device *hid; + enum bt_hid_session_role role; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return; + } + + hid = HID_DEVICE_BY_INTR_CHAN(chan); + if (hid == NULL) { + LOG_ERR("HID not found"); + return; + } + + role = get_session_role_from_chan(chan); + + LOG_DBG("HID session:%d connected, state %d", role, hid->state); + + if (role != BT_HID_SESSION_ROLE_INTR) { + LOG_ERR("HID invalid role:%d", role); + return; + } + + /* Local request disconnect(INTR channel), and it need to disconnect CTRL channel as well */ + if (hid->state == BT_HID_STATE_DISCONNECTING) { + LOG_DBG("HID disconnect CTRL channel"); + bt_l2cap_chan_disconnect(&hid->ctrl_session.br_chan.chan); + return; + } + + /* Wait for remote disconnect CTRL channel */ + if (hid->ctrl_session.br_chan.chan.conn) { + LOG_DBG("Wait for remote disconnect CTRL channel"); + return; + } + + if (hid->pending_vc_unplug && hid_cb && hid_cb->vc_unplug) { + hid_cb->vc_unplug(hid); + hid->pending_vc_unplug = 0; + } + + if (hid_cb && hid_cb->disconnected) { + hid_cb->disconnected(hid); + } + + bt_hid_deinit(hid); +} + +static int bt_hid_l2cap_ctrl_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_hid_header *hdr; + enum bt_hid_session_role role; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return -EIO; + } + + if (buf->len < sizeof(*hdr)) { + LOG_ERR("HID buf len too short"); + return -EINVAL; + } + + hdr = (struct bt_hid_header *)buf->data; + net_buf_pull(buf, sizeof(*hdr)); + + role = get_session_role_from_chan(chan); + if (role != BT_HID_SESSION_ROLE_CTRL) { + LOG_ERR("HID invalid role:%d", role); + return -EIO; + } + + LOG_DBG("HID CTRL recv type[0x%x] param[0x%x]", hdr->type, hdr->param); + + switch (hdr->type) { + case BT_HID_TYPE_CONTROL: + hid_control_handle(chan, buf, hdr->param); + break; + case BT_HID_TYPE_GET_REPORT: + hid_get_report_handle(chan, buf, hdr->param); + break; + case BT_HID_TYPE_SET_REPORT: + hid_set_report_handle(chan, buf, hdr->param); + break; + case BT_HID_TYPE_GET_PROTOCOL: + hid_get_protocol_handle(chan, buf, hdr->param); + break; + case BT_HID_TYPE_SET_PROTOCOL: + hid_set_protocol_handle(chan, buf, hdr->param); + break; + default: + struct bt_hid_session *session; + + LOG_ERR("HID type:%d not handle", hdr->type); + session = HID_SESSION_BY_CHAN(chan); + hid_send_handshake(session, BT_HID_HANDSHAKE_RSP_ERR_UNSUPPORTED_REQ); + break; + } + + return 0; +} + +static int bt_hid_l2cap_intr_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_hid_header *hdr; + enum bt_hid_session_role role; + + if (chan == NULL) { + LOG_ERR("Invalid hid chan"); + return -EIO; + } + + if (buf->len < sizeof(*hdr)) { + LOG_ERR("HID buf len too short"); + return -EINVAL; + } + + role = get_session_role_from_chan(chan); + if (role != BT_HID_SESSION_ROLE_INTR) { + LOG_ERR("HID invalid role:%d", role); + return -EIO; + } + + hdr = (struct bt_hid_header *)buf->data; + net_buf_pull(buf, sizeof(*hdr)); + + LOG_DBG("HID recv type[0x%x] param[0x%x]", hdr->type, hdr->param); + + return hid_intr_handle(chan, buf, hdr->param); +} + +static void bt_hid_init(struct bt_hid_device *hid, struct bt_conn *conn, enum bt_hid_role role) +{ + static const struct bt_l2cap_chan_ops ctrl_ops = { + .connected = bt_hid_l2cap_ctrl_connected, + .disconnected = bt_hid_l2cap_ctrl_disconnected, + .recv = bt_hid_l2cap_ctrl_recv, + }; + + static const struct bt_l2cap_chan_ops intr_ops = { + .connected = bt_hid_l2cap_intr_connected, + .disconnected = bt_hid_l2cap_intr_disconnected, + .recv = bt_hid_l2cap_intr_recv, + }; + + hid->ctrl_session.br_chan.chan.ops = (struct bt_l2cap_chan_ops *)&ctrl_ops; + hid->ctrl_session.br_chan.rx.mtu = BT_HID_MAX_MTU; + hid->ctrl_session.role = BT_HID_SESSION_ROLE_CTRL; + + hid->intr_session.br_chan.chan.ops = (struct bt_l2cap_chan_ops *)&intr_ops; + hid->intr_session.br_chan.rx.mtu = BT_HID_MAX_MTU; + hid->intr_session.role = BT_HID_SESSION_ROLE_INTR; + + hid->role = role; + hid->state = BT_HID_STATE_CTRL_CONNECTING; + hid->conn = conn; + hid->buf = NULL; + hid->pending_vc_unplug = 0; +} + +static void bt_hid_deinit(struct bt_hid_device *hid) +{ + + if (hid->buf != NULL) { + net_buf_unref(hid->buf); + hid->buf = NULL; + } + + hid->conn = NULL; + hid->pending_vc_unplug = 0; + hid->state = BT_HID_STATE_DISTCONNECTED; +} + +static int hid_l2cap_ctrl_accept(struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_hid_device *hid; + + hid = hid_get_connection(conn); + if (hid == NULL) { + LOG_ERR("Cannot allocate memory for HID device"); + return -ENOMEM; + } + + bt_hid_init(hid, conn, BT_HID_ROLE_ACCEPTOR); + *chan = &hid->ctrl_session.br_chan.chan; + + return 0; +} + +static int hid_l2cap_intr_accept(struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_hid_device *hid; + + hid = hid_get_connection(conn); + if (hid == NULL) { + LOG_ERR("Cannot get HID device"); + return -ENOMEM; + } + + hid->state = BT_HID_STATE_INTR_CONNECTING; + *chan = &hid->intr_session.br_chan.chan; + + return 0; +} + +int bt_hid_device_send_ctrl_data(struct bt_hid_device *hid, uint8_t type, uint8_t *data, + uint16_t len) +{ + __ASSERT_NO_MSG(hid); + + return hid_send_data(&hid->ctrl_session, type, data, len); +} + +int bt_hid_device_send_intr_data(struct bt_hid_device *hid, uint8_t type, uint8_t *data, + uint16_t len) +{ + __ASSERT_NO_MSG(hid); + + return hid_send_data(&hid->intr_session, type, data, len); +} + +int bt_hid_device_report_error(struct bt_hid_device *hid, uint8_t error) +{ + __ASSERT_NO_MSG(hid); + + return hid_send_handshake(&hid->ctrl_session, error); +} + +struct bt_hid_device *bt_hid_device_connect(struct bt_conn *conn) +{ + struct bt_hid_device *hid; + int err; + + hid = hid_get_connection(conn); + if (hid == NULL) { + LOG_ERR("Cannot allocate memory"); + return NULL; + } + + if (hid->state != BT_HID_STATE_DISTCONNECTED) { + LOG_ERR("HID device is busy, state:%d", hid->state); + return NULL; + } + + bt_hid_init(hid, conn, BT_HID_ROLE_INITIATOR); + + err = bt_l2cap_chan_connect(conn, &hid->ctrl_session.br_chan.chan, BT_L2CAP_PSM_HID_CTL); + if (err != 0) { + LOG_WRN("HID connect failed, err:%d", err); + return NULL; + } + + hid->state = BT_HID_STATE_CTRL_CONNECTING; + return hid; +} + +int bt_hid_device_disconnect(struct bt_hid_device *hid) +{ + int err; + + __ASSERT_NO_MSG(hid); + + if (hid->state != BT_HID_STATE_CONNECTED) { + LOG_ERR("HID device not connected, state:%d", hid->state); + return -ENOTCONN; + } + + err = bt_l2cap_chan_disconnect(&hid->intr_session.br_chan.chan); + if (err != 0) { + LOG_WRN("HID disconnect session, err:%d", err); + return err; + } + + hid->state = BT_HID_STATE_DISCONNECTING; + return 0; +} + +int bt_hid_device_register(struct bt_hid_device_cb *cb) +{ + LOG_DBG(""); + + hid_cb = cb; + return 0; +} + +int bt_hid_dev_init(void) +{ + int err; + static struct bt_l2cap_server hiddev_ctrl_l2cap = { + .psm = BT_L2CAP_PSM_HID_CTL, + .sec_level = BT_SECURITY_L2, + .accept = hid_l2cap_ctrl_accept, + }; + static struct bt_l2cap_server hiddev_intr_l2cap = { + .psm = BT_L2CAP_PSM_HID_INT, + .sec_level = BT_SECURITY_L2, + .accept = hid_l2cap_intr_accept, + }; + + LOG_DBG(""); + + /* Register HID CTRL PSM with L2CAP */ + err = bt_l2cap_br_server_register(&hiddev_ctrl_l2cap); + if (err < 0) { + LOG_ERR("HID ctrl L2CAP registration failed, err:%d", err); + return err; + } + + /* Register HID INTR PSM with L2CAP */ + err = bt_l2cap_br_server_register(&hiddev_intr_l2cap); + if (err < 0) { + LOG_ERR("HID intr L2CAP registration failed, err:%d", err); + return err; + } + + return err; +} diff --git a/subsys/bluetooth/host/classic/hid_internal.h b/subsys/bluetooth/host/classic/hid_internal.h new file mode 100644 index 00000000000..7c649c95f4e --- /dev/null +++ b/subsys/bluetooth/host/classic/hid_internal.h @@ -0,0 +1,62 @@ +/** @file + * @brief Internal APIs for Bluetooth HID Device handling. + */ + +/* + * Copyright 2025 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/* Define the HID PSM */ +#define BT_L2CAP_PSM_HID_CTL 0x0011 +#define BT_L2CAP_PSM_HID_INT 0x0013 + +/* Define the HID transaction types + */ +#define BT_HID_TYPE_HANDSHAKE 0x00 +#define BT_HID_TYPE_CONTROL 0x01 +#define BT_HID_TYPE_GET_REPORT 0x04 +#define BT_HID_TYPE_SET_REPORT 0x05 +#define BT_HID_TYPE_GET_PROTOCOL 0x06 +#define BT_HID_TYPE_SET_PROTOCOL 0x07 +#define BT_HID_TYPE_GET_IDLE 0x08 +#define BT_HID_TYPE_SET_IDLE 0x09 +#define BT_HID_TYPE_DATA 0x0a +#define BT_HID_TYPE_DATAC 0x0b + +/* Parameters for Control */ +#define BT_HID_CONTROL_SUSPEND 0x03 +#define BT_HID_CONTROL_EXIT_SUSPEND 0x04 +#define BT_HID_CONTROL_VIRTUAL_CABLE_UNPLUG 0x05 + +/* Parameters for Protocol Type */ +#define BT_HID_PROTOCOL_MASK 0x01 +#define BT_HID_PROTOCOL_BOOT_MODE 0x00 +#define BT_HID_PROTOCOL_REPORT 0x01 + +/** @brief HID DEV STATE */ +#define BT_HID_STATE_DISTCONNECTED 0x00 +#define BT_HID_STATE_CTRL_CONNECTING 0x01 +#define BT_HID_STATE_CTRL_CONNECTED 0x02 +#define BT_HID_STATE_INTR_CONNECTING 0x03 +#define BT_HID_STATE_CONNECTED 0x04 +#define BT_HID_STATE_DISCONNECTING 0x05 + +/** @brief HID Device report structure */ +struct bt_hid_report { + uint8_t type; + int len; + uint8_t data[BT_HID_REPORT_DATA_LEN]; +}; + +/** @brief HID Device header structure */ +struct bt_hid_header { + uint8_t param: 4; + uint8_t type: 4; +} __packed; + +/* Initialize HID service */ +int bt_hid_dev_init(void); diff --git a/subsys/bluetooth/host/classic/l2cap_br.c b/subsys/bluetooth/host/classic/l2cap_br.c index 9ee8ec7773c..cbc3696343f 100644 --- a/subsys/bluetooth/host/classic/l2cap_br.c +++ b/subsys/bluetooth/host/classic/l2cap_br.c @@ -2142,4 +2142,8 @@ void bt_l2cap_br_init(struct bt_dev *hdev) if (IS_ENABLED(CONFIG_BT_DID)) { bt_did_init(); } + + if (IS_ENABLED(CONFIG_BT_HID_DEVICE)) { + bt_hid_dev_init(); + } } diff --git a/subsys/bluetooth/host/classic/shell/CMakeLists.txt b/subsys/bluetooth/host/classic/shell/CMakeLists.txt index dbf42a44cb1..5876e57b502 100644 --- a/subsys/bluetooth/host/classic/shell/CMakeLists.txt +++ b/subsys/bluetooth/host/classic/shell/CMakeLists.txt @@ -5,3 +5,4 @@ zephyr_library_sources(bredr.c) zephyr_library_sources_ifdef(CONFIG_BT_RFCOMM rfcomm.c) zephyr_library_sources_ifdef(CONFIG_BT_A2DP a2dp.c) zephyr_library_sources_ifdef(CONFIG_BT_AVRCP avrcp.c) +zephyr_library_sources_ifdef(CONFIG_BT_HID_DEVICE hid_device.c) diff --git a/subsys/bluetooth/host/classic/shell/hid_device.c b/subsys/bluetooth/host/classic/shell/hid_device.c new file mode 100644 index 00000000000..b297f3617f6 --- /dev/null +++ b/subsys/bluetooth/host/classic/shell/hid_device.c @@ -0,0 +1,438 @@ +/* + * Copyright 2025 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include "host/shell/bt.h" +#include "common/bt_shell_private.h" + +#define HELP_NONE "[none]" + +#define BT_HID_DEVICE_VERSION 0x0101 +#define BT_HID_PARSER_VERSION 0x0111 +#define BT_HID_DEVICE_SUBCLASS 0xc0 +#define BT_HID_DEVICE_COUNTRY_CODE 0x21 +#define BT_HID_PROTO_INTERRUPT 0x0013 + +#define BT_HID_LANG_ID_ENGLISH 0x0409 +#define BT_HID_LANG_ID_OFFSET 0x0100 + +#define BT_HID_SUPERVISION_TIMEOUT 1000 +#define BT_HID_MAX_LATENCY 240 +#define BT_HID_MIN_LATENCY 0 + +static uint8_t hid_descriptor[] = { + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop Controls) */ + 0x09, 0x02, /* USAGE (Mouse) */ + 0xa1, 0x01, /* COLLECTION (Application (mouse, keyboard)) */ + 0x85, 0x02, /* REPORT_ID (2) */ + 0x09, 0x01, /* USAGE (Pointer) */ + 0xa1, 0x00, /* COLLECTION (Physical (group of axes)) */ + 0x05, 0x09, /* usage page(Button) */ + 0x19, 0x01, /* Usage Minimum */ + 0x29, 0x08, /* Usage Maximum */ + 0x15, 0x00, /* Logical Minimum */ + 0x25, 0x01, /* Logical Maximum */ + 0x95, 0x08, /* Report Count */ + 0x75, 0x01, /* Report size */ + 0x81, 0x02, /* input() */ + 0x05, 0x01, /* usage page() */ + 0x09, 0x30, /* usage() */ + 0x09, 0x31, /* usage() */ + 0x09, 0x38, /* usage() */ + 0x15, 0x81, /* logical minimum */ + 0x25, 0x7f, /* logical maximum */ + 0x75, 0x08, /* report size */ + 0x95, 0x03, /* report count */ + 0x81, 0x06, /* input */ + 0xc0, 0xc0 /* END_COLLECTION */ +}; + +static struct bt_sdp_attribute hid_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HID_SVCLASS) + } + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 13), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_HID) + } + ) + }, + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_HID) + } + ) + }, + ) + ), + BT_SDP_LIST(BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HID_SVCLASS)}, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_DEVICE_VERSION) + } + ) + } + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_ADD_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 15), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 13), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_PROTO_INTERRUPT) + } + ) + }, + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_HID) + } + ) + } + ) + } + ) + ), + BT_SDP_SERVICE_NAME("HID CONTROL"), + { + BT_SDP_ATTR_HID_PARSER_VERSION, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_PARSER_VERSION) + } + }, + { + BT_SDP_ATTR_HID_DEVICE_SUBCLASS, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_16(BT_HID_DEVICE_SUBCLASS) + } + }, + { + BT_SDP_ATTR_HID_COUNTRY_CODE, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_16(BT_HID_DEVICE_COUNTRY_CODE) + } + }, + { + BT_SDP_ATTR_HID_VIRTUAL_CABLE, + { + BT_SDP_TYPE_SIZE(BT_SDP_BOOL), + BT_SDP_ARRAY_8(0x01) + } + }, + { + BT_SDP_ATTR_HID_RECONNECT_INITIATE, + { + BT_SDP_TYPE_SIZE(BT_SDP_BOOL), + BT_SDP_ARRAY_8(0x01) + } + }, + BT_SDP_LIST( + BT_SDP_ATTR_HID_DESCRIPTOR_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ16, sizeof(hid_descriptor) + 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ16, sizeof(hid_descriptor) + 5), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_8(0x22), + }, + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_TEXT_STR16, + sizeof(hid_descriptor) + ), + hid_descriptor, + } + ) + } + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_HID_LANG_ID_BASE_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_LANG_ID_ENGLISH), + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_LANG_ID_OFFSET), + } + ), + } + ) + ), + { + BT_SDP_ATTR_HID_BOOT_DEVICE, + { + BT_SDP_TYPE_SIZE(BT_SDP_BOOL), + BT_SDP_ARRAY_8(0x01) + } + }, + { + BT_SDP_ATTR_HID_SUPERVISION_TIMEOUT, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_SUPERVISION_TIMEOUT) + } + }, + { + BT_SDP_ATTR_HID_MAX_LATENCY, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_MAX_LATENCY) + } + }, + { + BT_SDP_ATTR_HID_MIN_LATENCY, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_MIN_LATENCY) + } + }, +}; + +static struct bt_hid_device *default_hid; +static int hid_registered; +static struct bt_sdp_record hid_rec = BT_SDP_RECORD(hid_attrs); + +static void hid_connect_cb(struct bt_hid_device *hid) +{ + bt_shell_print("hid:%p connected", hid); + + default_hid = hid; +} + +static void hid_disconnected_cb(struct bt_hid_device *hid) +{ + bt_shell_print("hid:%p disconnected", hid); + + default_hid = NULL; +} + +void hid_set_report_cb(struct bt_hid_device *hid, uint8_t *data, uint16_t len) +{ + bt_shell_print("hid:%p set report, len:%d", hid, len); +} + +void hid_get_report_cb(struct bt_hid_device *hid, uint8_t *data, uint16_t len) +{ + uint8_t buf[2] = {0}; + + bt_shell_print("hid:%p get report", hid); + + /* Send responde with 0x0102 for test */ + buf[0] = 0x01; + buf[1] = 0x02; + bt_hid_device_send_ctrl_data(hid, BT_HID_REPORT_TYPE_INPUT, buf, sizeof(buf)); +} + +void hid_set_protocol_cb(struct bt_hid_device *hid, uint8_t protocol) +{ + bt_shell_print("hid:%p set protocol:%d, ", hid, protocol); +} + +void hid_get_protocol_cb(struct bt_hid_device *hid) +{ + uint8_t protocol = BT_HID_PROTOCOL_REPORT_MODE; + + bt_shell_print("hid:%p get protocol", hid); + bt_hid_device_send_ctrl_data(hid, BT_HID_REPORT_TYPE_OTHER, &protocol, sizeof(protocol)); +} + +void hid_intr_data_cb(struct bt_hid_device *hid, uint8_t *data, uint16_t len) +{ + bt_shell_print("hid:%p inrt data len:%d", hid, len); +} + +void hid_vc_unplug_cb(struct bt_hid_device *hid) +{ + bt_shell_print("hid:%p unplug", hid); +} + +static struct bt_hid_device_cb hid_cb = { + .connected = hid_connect_cb, + .disconnected = hid_disconnected_cb, + .set_report = hid_set_report_cb, + .get_report = hid_get_report_cb, + .set_protocol = hid_set_protocol_cb, + .get_protocol = hid_get_protocol_cb, + .intr_data = hid_intr_data_cb, + .vc_unplug = hid_vc_unplug_cb, +}; + +static int cmd_hid_register(const struct shell *sh, int32_t argc, char *argv[]) +{ + int err; + + err = bt_hid_device_register(&hid_cb); + if (err != 0) { + bt_shell_print("register cb fail"); + return err; + } + + err = bt_sdp_register_service(&hid_rec); + if (err != 0) { + bt_shell_print("register desc fail"); + return err; + } + + hid_registered = 1; + bt_shell_print("success"); + return err; +} + +static int cmd_hid_connect(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!hid_registered) { + bt_shell_print("hid connection callbacks not registered"); + return -ENOEXEC; + } + + if (!default_conn) { + bt_shell_print("bt not connected"); + return -ENOEXEC; + } + + default_hid = bt_hid_device_connect(default_conn); + if (!default_hid) { + bt_shell_print("fail to connect hid device"); + return -ENOEXEC; + } + + return 0; +} + +static int cmd_hid_disconnect(const struct shell *sh, size_t argc, char *argv[]) +{ + if (!hid_registered) { + bt_shell_print("hid connection callbacks not registered"); + return -ENOEXEC; + } + + if (!default_hid) { + bt_shell_print("hid device is not connected"); + return -ENOEXEC; + } + + bt_hid_device_disconnect(default_hid); + default_hid = NULL; + + return 0; +} + +static int cmd_hid_send_report(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t buf[5] = {0}; + + if (!hid_registered) { + bt_shell_print("hid connection callbacks not registered"); + return -ENOEXEC; + } + + if (!default_hid) { + bt_shell_print("hid device is not connected"); + return -ENOEXEC; + } + + buf[0] = 0x02; /* Mouse */ + buf[1] = 0x00; + buf[2] = atoi(argv[1]); /* X Displacement */ + buf[3] = atoi(argv[2]); /* Y Displacement */ + bt_hid_device_send_intr_data(default_hid, BT_HID_REPORT_TYPE_INPUT, buf, sizeof(buf)); + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE( + hid_device_cmds, + SHELL_CMD_ARG(register, NULL, "register hid device", cmd_hid_register, 1, 0), + SHELL_CMD_ARG(connect, NULL, "hid connect", cmd_hid_connect, 1, 0), + SHELL_CMD_ARG(disconnect, NULL, "hid disconnect", cmd_hid_disconnect, 1, 0), + SHELL_CMD_ARG(send, NULL, "send report mouse: [X] [Y]", cmd_hid_send_report, 3, 0), + SHELL_SUBCMD_SET_END); + +static int cmd_hid_device(const struct shell *sh, size_t argc, char **argv) +{ + if (argc == 1) { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + bt_shell_print("%s unknown parameter: %s", argv[0], argv[1]); + + return -ENOEXEC; +} + +SHELL_CMD_ARG_REGISTER(hid_device, &hid_device_cmds, "Bluetooth HID Device sh commands", + cmd_hid_device, 1, 1);