From 605ba1385952c315af72078bbbfd18f13656ea30 Mon Sep 17 00:00:00 2001 From: Kai Cheng Date: Tue, 21 Oct 2025 23:15:59 +0800 Subject: [PATCH 1/3] Bluetooth: Classic: add power mode control for sniff mode Implement BR/EDR power mode control with sniff mode functionality. Adds APIs bt_conn_br_enter_sniff_mode() and bt_conn_br_exit_sniff_mode() to manage power saving modes. Includes parameter validation and HCI command handling for sniff mode configuration with min/max intervals, attempt count, and timeout parameters. Signed-off-by: Kai Cheng --- include/zephyr/bluetooth/conn.h | 24 +++++++++ include/zephyr/bluetooth/hci_types.h | 27 ++++++++++ subsys/bluetooth/host/classic/Kconfig | 7 +++ subsys/bluetooth/host/conn.c | 74 +++++++++++++++++++++++++++ subsys/bluetooth/host/conn_internal.h | 5 ++ 5 files changed, 137 insertions(+) diff --git a/include/zephyr/bluetooth/conn.h b/include/zephyr/bluetooth/conn.h index 2f9b796e1ec14..9d080f5163012 100644 --- a/include/zephyr/bluetooth/conn.h +++ b/include/zephyr/bluetooth/conn.h @@ -2944,6 +2944,30 @@ int bt_conn_br_switch_role(const struct bt_conn *conn, uint8_t role); */ int bt_conn_br_set_role_switch_enable(const struct bt_conn *conn, bool enable); +#if defined(CONFIG_BT_POWER_MODE_CONTROL) +/** @brief bluetooth conn check and enter sniff mode + * + * This function is used to identify which ACL link connection is to + * be placed in Sniff mode + * + * @param conn bt_conn conn + * @param min_interval Minimum sniff interval. + * @param max_interval Maxmum sniff interval. + * @param attempt Number of Baseband receive slots for sniff attempt. + * @param timeout Number of Baseband receive slots for sniff timeout. + */ +int bt_conn_br_enter_sniff_mode(struct bt_conn *conn, uint16_t min_interval, + uint16_t max_interval, uint16_t attempt, uint16_t timeout); + +/** @brief bluetooth conn check and exit sniff mode + * + * @param conn bt_conn conn + * + * @return Zero for success, non-zero otherwise. + */ +int bt_conn_br_exit_sniff_mode(struct bt_conn *conn); +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + #ifdef __cplusplus } #endif diff --git a/include/zephyr/bluetooth/hci_types.h b/include/zephyr/bluetooth/hci_types.h index cd6c716c1cbe1..1cf631c9014fa 100644 --- a/include/zephyr/bluetooth/hci_types.h +++ b/include/zephyr/bluetooth/hci_types.h @@ -601,6 +601,20 @@ struct bt_hci_cp_write_default_link_policy_settings { uint16_t default_link_policy_settings; } __packed; +#define BT_HCI_OP_SNIFF_MODE BT_OP(BT_OGF_LINK_POLICY, 0x0003) /* 0x0803 */ +struct bt_hci_cp_sniff_mode { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; + uint16_t attempt; + uint16_t timeout; +} __packed; + +#define BT_HCI_OP_EXIT_SNIFF_MODE BT_OP(BT_OGF_LINK_POLICY, 0x0004) /* 0x0804 */ +struct bt_hci_cp_exit_sniff_mode { + uint16_t handle; +} __packed; + #define BT_HCI_OP_SET_EVENT_MASK BT_OP(BT_OGF_BASEBAND, 0x0001) /* 0x0c01 */ struct bt_hci_cp_set_event_mask { uint8_t events[8]; @@ -2963,6 +2977,19 @@ struct bt_hci_evt_num_completed_packets { struct bt_hci_handle_count h[0]; } __packed; +/* Current mode */ +#define BT_ACTIVE_MODE 0x00 +#define BT_HOLD_MODE 0x01 +#define BT_SNIFF_MODE 0x02 + +#define BT_HCI_EVT_MODE_CHANGE 0x14 +struct bt_hci_evt_mode_change { + uint8_t status; + uint16_t handle; + uint8_t mode; + uint16_t interval; +} __packed; + #define BT_HCI_EVT_PIN_CODE_REQ 0x16 struct bt_hci_evt_pin_code_req { bt_addr_t bdaddr; diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 60252cbcecc84..c771cfdcd043e 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -567,6 +567,13 @@ config BT_DEFAULT_ROLE_SWITCH_ENABLE This option sets the controller's default link policy to enable/disable the role switch. +config BT_POWER_MODE_CONTROL + bool "Bluetooth Power Mode Control(Active/Sniff)" + help + This option enables the power mode control feature. This feature + allows the application to control the power mode of the Bluetooth + controller. + config BT_GOEP bool "Bluetooth GOEP Profile [EXPERIMENTAL]" select BT_RFCOMM diff --git a/subsys/bluetooth/host/conn.c b/subsys/bluetooth/host/conn.c index fb31f879191e0..ba20dce2e8039 100644 --- a/subsys/bluetooth/host/conn.c +++ b/subsys/bluetooth/host/conn.c @@ -2421,6 +2421,10 @@ struct bt_conn *bt_conn_add_br(const bt_addr_t *peer) conn->get_and_clear_cb = acl_get_and_clear_cb; conn->has_data = acl_has_data; +#if defined(CONFIG_BT_POWER_MODE_CONTROL) + conn->br.mode = BT_ACTIVE_MODE; +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + return conn; } #endif /* CONFIG_BT_CLASSIC */ @@ -4402,6 +4406,76 @@ void bt_hci_le_df_cte_req_failed(struct net_buf *buf) } #endif /* CONFIG_BT_DF_CONNECTION_CTE_REQ */ +#if defined(CONFIG_BT_POWER_MODE_CONTROL) +int bt_conn_br_enter_sniff_mode(struct bt_conn *conn, uint16_t min_interval, uint16_t max_interval, + uint16_t attempt, uint16_t timeout) +{ + struct bt_hci_cp_sniff_mode *cp; + struct net_buf *buf; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) { + return -EINVAL; + } + + if (conn->state != BT_CONN_CONNECTED) { + return -ENOTCONN; + } + + if (conn->br.mode == BT_SNIFF_MODE) { + return -EBUSY; + } + + /* Check if the parameters are valid */ + if (min_interval < 0x0006 || min_interval > 0x0540 || max_interval < 0x0006 || + max_interval > 0x0540 || min_interval > max_interval || attempt == 0 || + attempt > 0x01F3 || timeout > 0x0028) { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(K_FOREVER); + if (!buf) { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->max_interval = sys_cpu_to_le16(max_interval); + cp->min_interval = sys_cpu_to_le16(min_interval); + cp->attempt = sys_cpu_to_le16(attempt); + cp->timeout = sys_cpu_to_le16(timeout); + + return bt_hci_cmd_send_sync(BT_HCI_OP_SNIFF_MODE, buf, NULL); +} + +int bt_conn_br_exit_sniff_mode(struct bt_conn *conn) +{ + struct bt_hci_cp_exit_sniff_mode *cp; + struct net_buf *buf; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) { + return -EINVAL; + } + + if (conn->state != BT_CONN_CONNECTED) { + return -ENOTCONN; + } + + if (conn->br.mode == BT_ACTIVE_MODE) { + return -EBUSY; + } + + buf = bt_hci_cmd_alloc(K_FOREVER); + if (!buf) { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + + return bt_hci_cmd_send_sync(BT_HCI_OP_EXIT_SNIFF_MODE, buf, NULL); +} + +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ #endif /* CONFIG_BT_CONN */ #if defined(CONFIG_BT_CONN_TX_NOTIFY_WQ) diff --git a/subsys/bluetooth/host/conn_internal.h b/subsys/bluetooth/host/conn_internal.h index c64ad277b4f8f..247d16778c4a1 100644 --- a/subsys/bluetooth/host/conn_internal.h +++ b/subsys/bluetooth/host/conn_internal.h @@ -155,6 +155,11 @@ struct bt_conn_br { uint8_t features[LMP_MAX_PAGES][8]; struct bt_keys_link_key *link_key; + +#if defined(CONFIG_BT_POWER_MODE_CONTROL) + /* For power mode */ + uint8_t mode; +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ }; struct bt_conn_sco { From 7a75fed803110eb8ee21260525781c54c662d906 Mon Sep 17 00:00:00 2001 From: Kai Cheng Date: Tue, 21 Oct 2025 23:19:16 +0800 Subject: [PATCH 2/3] Bluetooth: Classic: add mode change notification for sniff mode Implement mode change event handling and callback notification for BR/EDR power mode transitions. Adds br_mode_changed callback to notify applications when connection switches between active and sniff modes. Handles HCI mode change events and propagates mode and interval information to registered callbacks. Signed-off-by: Kai Cheng --- include/zephyr/bluetooth/conn.h | 12 ++++++++++ subsys/bluetooth/host/classic/br.c | 34 +++++++++++++++++++++++++++ subsys/bluetooth/host/conn.c | 17 ++++++++++++++ subsys/bluetooth/host/conn_internal.h | 5 ++++ subsys/bluetooth/host/hci_core.c | 4 ++++ subsys/bluetooth/host/hci_core.h | 3 +++ 6 files changed, 75 insertions(+) diff --git a/include/zephyr/bluetooth/conn.h b/include/zephyr/bluetooth/conn.h index 9d080f5163012..78e1bc0ff1d84 100644 --- a/include/zephyr/bluetooth/conn.h +++ b/include/zephyr/bluetooth/conn.h @@ -1987,6 +1987,18 @@ struct bt_conn_cb { struct bt_conn_remote_info *remote_info); #endif /* defined(CONFIG_BT_REMOTE_INFO) */ +#if defined(CONFIG_BT_POWER_MODE_CONTROL) + /** @brief The connection mode change + * + * This callback notifies the application that the sniff mode has changed + * + * @param conn Connection object. + * @param mode Active/Sniff mode. + * @param interval Sniff interval. + */ + void (*br_mode_changed)(struct bt_conn *conn, uint8_t mode, uint16_t interval); +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + #if defined(CONFIG_BT_USER_PHY_UPDATE) /** @brief The PHY of the connection has changed. * diff --git a/subsys/bluetooth/host/classic/br.c b/subsys/bluetooth/host/classic/br.c index 23a43a01959b3..5ca935168e329 100644 --- a/subsys/bluetooth/host/classic/br.c +++ b/subsys/bluetooth/host/classic/br.c @@ -706,6 +706,40 @@ void bt_hci_role_change(struct net_buf *buf) bt_conn_unref(conn); } +#if defined(CONFIG_BT_POWER_MODE_CONTROL) +void bt_hci_link_mode_change(struct net_buf *buf) +{ + struct bt_hci_evt_mode_change *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->handle); + uint16_t interval = sys_le16_to_cpu(evt->interval); + struct bt_conn *conn; + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_BR); + if (!conn) { + LOG_ERR("Can't find conn for handle 0x%x", handle); + return; + } + + if (conn->state != BT_CONN_CONNECTED) { + LOG_ERR("Invalid state %d", conn->state); + bt_conn_unref(conn); + return; + } + + if (evt->status) { + LOG_ERR("Error %d, type %d", evt->status, conn->type); + bt_conn_unref(conn); + return; + } + + LOG_DBG("hdl 0x%x mode %d intervel %d", handle, evt->mode, interval); + + conn->br.mode = evt->mode; + bt_conn_notify_mode_changed(conn, evt->mode, interval); + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + static int read_ext_features(void) { int i; diff --git a/subsys/bluetooth/host/conn.c b/subsys/bluetooth/host/conn.c index ba20dce2e8039..6cff5de0c972b 100644 --- a/subsys/bluetooth/host/conn.c +++ b/subsys/bluetooth/host/conn.c @@ -4475,6 +4475,23 @@ int bt_conn_br_exit_sniff_mode(struct bt_conn *conn) return bt_hci_cmd_send_sync(BT_HCI_OP_EXIT_SNIFF_MODE, buf, NULL); } +void bt_conn_notify_mode_changed(struct bt_conn *conn, uint8_t mode, uint16_t interval) +{ + struct bt_conn_cb *callback; + + SYS_SLIST_FOR_EACH_CONTAINER(&conn_cbs, callback, _node) { + if (callback->br_mode_changed) { + callback->br_mode_changed(conn, mode, interval); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) { + if (cb->br_mode_changed) { + cb->br_mode_changed(conn, mode, interval); + } + } +} + #endif /* CONFIG_BT_POWER_MODE_CONTROL */ #endif /* CONFIG_BT_CONN */ diff --git a/subsys/bluetooth/host/conn_internal.h b/subsys/bluetooth/host/conn_internal.h index 247d16778c4a1..9a01a2b3549da 100644 --- a/subsys/bluetooth/host/conn_internal.h +++ b/subsys/bluetooth/host/conn_internal.h @@ -540,6 +540,11 @@ void bt_conn_identity_resolved(struct bt_conn *conn); void bt_conn_security_changed(struct bt_conn *conn, uint8_t hci_err, enum bt_security_err err); +#if defined(CONFIG_BT_POWER_MODE_CONTROL) +/* Notify higher layers that connection sniff mode changed */ +void bt_conn_notify_mode_changed(struct bt_conn *conn, uint8_t mode, uint16_t interval); +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + /* Prepare a PDU to be sent over a connection */ #if defined(CONFIG_NET_BUF_LOG) struct net_buf *bt_conn_create_pdu_timeout_debug(struct net_buf_pool *pool, diff --git a/subsys/bluetooth/host/hci_core.c b/subsys/bluetooth/host/hci_core.c index 33306596b2ce7..35efc8cf9cde1 100644 --- a/subsys/bluetooth/host/hci_core.c +++ b/subsys/bluetooth/host/hci_core.c @@ -3088,6 +3088,10 @@ static const struct event_handler normal_events[] = { sizeof(struct bt_hci_evt_remote_ext_features)), EVENT_HANDLER(BT_HCI_EVT_ROLE_CHANGE, bt_hci_role_change, sizeof(struct bt_hci_evt_role_change)), +#if defined(CONFIG_BT_POWER_MODE_CONTROL) + EVENT_HANDLER(BT_HCI_EVT_MODE_CHANGE, bt_hci_link_mode_change, + sizeof(struct bt_hci_evt_mode_change)), +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ EVENT_HANDLER(BT_HCI_EVT_SYNC_CONN_COMPLETE, bt_hci_synchronous_conn_complete, sizeof(struct bt_hci_evt_sync_conn_complete)), #endif /* CONFIG_BT_CLASSIC */ diff --git a/subsys/bluetooth/host/hci_core.h b/subsys/bluetooth/host/hci_core.h index 315db2d388846..6bbd23bbe2490 100644 --- a/subsys/bluetooth/host/hci_core.h +++ b/subsys/bluetooth/host/hci_core.h @@ -563,6 +563,9 @@ void bt_hci_remote_name_request_complete(struct net_buf *buf); void bt_hci_read_remote_features_complete(struct net_buf *buf); void bt_hci_read_remote_ext_features_complete(struct net_buf *buf); void bt_hci_role_change(struct net_buf *buf); +#if defined(CONFIG_BT_POWER_MODE_CONTROL) +void bt_hci_link_mode_change(struct net_buf *buf); +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ void bt_hci_synchronous_conn_complete(struct net_buf *buf); void bt_hci_le_df_connection_iq_report(struct net_buf *buf); From a46304f28c65222389b35550a8d033d4336e3304 Mon Sep 17 00:00:00 2001 From: Kai Cheng Date: Tue, 21 Oct 2025 23:20:52 +0800 Subject: [PATCH 3/3] Bluetooth: shell: add sniff mode control command Add shell command for testing BR/EDR power mode control. Supports entering sniff mode with configurable parameters (min/max interval, attempt, timeout) and exiting back to active mode. Provides real-time feedback on mode change requests and status. Signed-off-by: Kai Cheng --- subsys/bluetooth/host/classic/shell/bredr.c | 53 +++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/subsys/bluetooth/host/classic/shell/bredr.c b/subsys/bluetooth/host/classic/shell/bredr.c index a3f57f25fe3b9..fd2b34173c327 100644 --- a/subsys/bluetooth/host/classic/shell/bredr.c +++ b/subsys/bluetooth/host/classic/shell/bredr.c @@ -1408,6 +1408,54 @@ static int cmd_set_role_switchable(const struct shell *sh, size_t argc, char *ar return 0; } +#if defined(CONFIG_BT_POWER_MODE_CONTROL) +static int cmd_set_sniff_mode(const struct shell *sh, size_t argc, char *argv[]) +{ + const char *action; + int err = 0; + + action = argv[1]; + if (!default_conn) { + shell_print(sh, "Not connected"); + return -ENOEXEC; + } + + if (!strcmp(action, "on")) { + uint16_t min_interval; + uint16_t max_interval; + uint16_t attempt; + uint16_t timeout; + + min_interval = atoi(argv[2]); + max_interval = atoi(argv[3]); + attempt = atoi(argv[4]); + timeout = atoi(argv[5]); + err = bt_conn_br_enter_sniff_mode(default_conn, min_interval, max_interval, attempt, + timeout); + if (err) { + shell_print(sh, "request enter sniff mode, err:%d", err); + } else { + shell_print(sh, + "request enter sniff mode, min_interval:%d, max_interval:%d, " + "attempt:%d, timeout:%d", + min_interval, max_interval, attempt, timeout); + } + } else if (!strcmp(action, "off")) { + err = bt_conn_br_exit_sniff_mode(default_conn); + if (err) { + shell_print(sh, "request enter active mode, err:%d", err); + } else { + shell_print(sh, "request enter active mode success"); + } + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + return 0; +} +#endif + #if defined(CONFIG_BT_L2CAP_CONNLESS) static void connless_recv(struct bt_conn *conn, uint16_t psm, struct net_buf *buf) { @@ -1578,6 +1626,11 @@ SHELL_STATIC_SUBCMD_SET_CREATE(br_cmds, SHELL_CMD_ARG(switch-role, NULL, "", cmd_switch_role, 2, 0), SHELL_CMD_ARG(set-role-switchable, NULL, "", cmd_set_role_switchable, 2, 0), +#if defined(CONFIG_BT_POWER_MODE_CONTROL) + SHELL_CMD_ARG(set_sniff_mode, NULL, + " [min_interval] [max_interval] [attempt] [timeout]", + cmd_set_sniff_mode, 2, 4), +#endif SHELL_SUBCMD_SET_END );