diff --git a/include/zephyr/bluetooth/conn.h b/include/zephyr/bluetooth/conn.h index c0b91fd5db1..8aed8d99f56 100644 --- a/include/zephyr/bluetooth/conn.h +++ b/include/zephyr/bluetooth/conn.h @@ -538,6 +538,112 @@ struct bt_conn_le_cs_config { uint8_t channel_map[10]; }; +/** Procedure done status */ +enum bt_conn_le_cs_procedure_done_status { + BT_CONN_LE_CS_PROCEDURE_COMPLETE = BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_COMPLETE, + BT_CONN_LE_CS_PROCEDURE_INCOMPLETE = BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_PARTIAL, + BT_CONN_LE_CS_PROCEDURE_ABORTED = BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_ABORTED, +}; + +/** Subevent done status */ +enum bt_conn_le_cs_subevent_done_status { + BT_CONN_LE_CS_SUBEVENT_COMPLETE = BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_COMPLETE, + BT_CONN_LE_CS_SUBEVENT_INCOMPLETE = BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_PARTIAL, + BT_CONN_LE_CS_SUBEVENT_ABORTED = BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_ABORTED, +}; + +/** Procedure abort reason */ +enum bt_conn_le_cs_procedure_abort_reason { + BT_CONN_LE_CS_PROCEDURE_NOT_ABORTED = BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_NO_ABORT, + BT_CONN_LE_CS_PROCEDURE_ABORT_REQUESTED = + BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_LOCAL_HOST_OR_REMOTE_REQUEST, + BT_CONN_LE_CS_PROCEDURE_ABORT_TOO_FEW_CHANNELS = + BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_TOO_FEW_CHANNELS, + BT_CONN_LE_CS_PROCEDURE_ABORT_CHMAP_INSTANT_PASSED = + BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_CHMAP_INSTANT_PASSED, + BT_CONN_LE_CS_PROCEDURE_ABORT_UNSPECIFIED = BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_UNSPECIFIED, +}; + +/** Subevent abort reason */ +enum bt_conn_le_cs_subevent_abort_reason { + BT_CONN_LE_CS_SUBEVENT_NOT_ABORTED = BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_NO_ABORT, + BT_CONN_LE_CS_SUBEVENT_ABORT_REQUESTED = + BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_LOCAL_HOST_OR_REMOTE_REQUEST, + BT_CONN_LE_CS_SUBEVENT_ABORT_NO_CS_SYNC = + BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_NO_CS_SYNC_RECEIVED, + BT_CONN_LE_CS_SUBEVENT_ABORT_SCHED_CONFLICT = + BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_SCHED_CONFLICT, + BT_CONN_LE_CS_SUBEVENT_ABORT_UNSPECIFIED = BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_UNSPECIFIED, +}; + +/** Subevent data for LE connections supporting CS */ +struct bt_conn_le_cs_subevent_result { + struct { + /** CS configuration identifier. + * + * Range: 0 to 3 + * + * If these results were generated by a CS Test, + * this value will be set to 0 and has no meaning. + */ + uint8_t config_id; + /** Starting ACL connection event counter. + * + * If these results were generated by a CS Test, + * this value will be set to 0 and has no meaning. + */ + uint16_t start_acl_conn_event; + /** CS procedure count associated with these results. + * + * This is the CS procedure count since the completion of + * the Channel Sounding Security Start procedure. + */ + uint16_t procedure_counter; + /** Frequency compensation value in units of 0.01 ppm. + * + * This is a 15-bit signed integer in the range [-100, 100] ppm. + * + * A value of @ref BT_HCI_LE_CS_SUBEVENT_RESULT_FREQ_COMPENSATION_NOT_AVAILABLE + * indicates that the role is not the initiator, or that the + * frequency compensation value is unavailable. + */ + uint16_t frequency_compensation; + /** Reference power level in dBm. + * + * Range: -127 to 20 + * + * A value of @ref BT_HCI_LE_CS_REF_POWER_LEVEL_UNAVAILABLE indicates + * that the reference power level was not available during a subevent. + */ + int8_t reference_power_level; + /** Procedure status. */ + enum bt_conn_le_cs_procedure_done_status procedure_done_status; + /** Subevent status. */ + enum bt_conn_le_cs_subevent_done_status subevent_done_status; + /** Abort reason. + * + * If the procedure status is + * @ref BT_CONN_LE_CS_PROCEDURE_ABORTED, this field will + * specify the reason for the abortion. + */ + enum bt_conn_le_cs_procedure_abort_reason procedure_abort_reason; + /** Abort reason. + * + * If the subevent status is + * @ref BT_CONN_LE_CS_SUBEVENT_ABORTED, this field will + * specify the reason for the abortion. + */ + enum bt_conn_le_cs_subevent_abort_reason subevent_abort_reason; + /** Number of antenna paths used during the phase measurement stage. + */ + uint8_t num_antenna_paths; + /** Number of CS steps in the subevent. + */ + uint8_t num_steps_reported; + } header; + struct net_buf_simple *step_data_buf; +}; + /** @brief Increment a connection's reference count. * * Increment the reference count of a connection object. @@ -1677,6 +1783,17 @@ struct bt_conn_cb { * @param config_id ID of the CS configuration that was removed. */ void (*le_cs_config_removed)(struct bt_conn *conn, uint8_t config_id); + + /** @brief Subevent Results from a CS procedure are available. + * + * This callback notifies the user that CS subevent results are + * available for the given connection object. + * + * @param conn Connection objects. + * @param result Subevent results + */ + void (*le_cs_subevent_data_available)(struct bt_conn *conn, + struct bt_conn_le_cs_subevent_result *result); #endif /** @internal Internally used field for list handling */ diff --git a/include/zephyr/bluetooth/cs.h b/include/zephyr/bluetooth/cs.h index c04fb3a9270..ffb839a9d15 100644 --- a/include/zephyr/bluetooth/cs.h +++ b/include/zephyr/bluetooth/cs.h @@ -517,6 +517,29 @@ struct bt_le_cs_create_config_params { uint8_t channel_map[10]; }; +/** Callbacks for CS Test */ +struct bt_le_cs_test_cb { + /**@brief CS Test Subevent data. + * + * @param[in] Subevent results. + */ + void (*le_cs_test_subevent_data_available)(struct bt_conn_le_cs_subevent_result *data); + /**@brief CS Test End Complete. */ + void (*le_cs_test_end_complete)(void); +}; + +/** Subevent result step */ +struct bt_le_cs_subevent_step { + /** CS step mode. */ + uint8_t mode; + /** CS step channel index. */ + uint8_t channel; + /** Length of role- and mode-specific information being reported. */ + uint8_t data_len; + /** Pointer to role- and mode-specific information. */ + const uint8_t *data; +}; + /** @brief Set all valid channel map bits * * This command is used to enable all valid channels in a @@ -567,6 +590,17 @@ int bt_le_cs_set_default_settings(struct bt_conn *conn, */ int bt_le_cs_read_remote_fae_table(struct bt_conn *conn); +/** @brief Register callbacks for the CS Test mode. + * + * Existing callbacks can be unregistered by providing NULL function + * pointers. + * + * @param cs_test_cb Set of callbacks to be used with CS Test + * + * @return Zero on success or (negative) error code on failure. + */ +int bt_le_cs_test_cb_register(struct bt_le_cs_test_cb cs_test_cb); + /** @brief Start a CS test * * This command is used to start a CS test where the IUT is placed in the role @@ -620,6 +654,37 @@ int bt_le_cs_create_config(struct bt_conn *conn, struct bt_le_cs_create_config_p */ int bt_le_cs_remove_config(struct bt_conn *conn, uint8_t config_id); +/** @brief Stop ongoing CS Test + * + * This command is used to stop any CS test that is in progress. + * + * The controller is expected to finish reporting any subevent results + * before completing this termination. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @return Zero on success or (negative) error code on failure. + */ +int bt_le_cs_stop_test(void); + +/** @brief Parse CS Test Subevent Results + * + * A helper for parsing HCI-formatted step data found in channel sounding subevent results. + * + * A typical use-case is filtering out data which does not meet certain packet quality or NADM + * requirements. + * + * @warning This function will consume the data when parsing. + * + * @param step_data_buf Pointer to a buffer containing the step data. + * @param func Callback function which will be called for each step data found. + * The callback should return true to continue parsing, or false to stop. + * @param user_data User data to be passed to the callback. + */ +void bt_le_cs_step_data_parse(struct net_buf_simple *step_data_buf, + bool (*func)(struct bt_le_cs_subevent_step *step, void *user_data), + void *user_data); + #ifdef __cplusplus } #endif diff --git a/include/zephyr/bluetooth/hci_types.h b/include/zephyr/bluetooth/hci_types.h index efc231efaef..131b81134c1 100644 --- a/include/zephyr/bluetooth/hci_types.h +++ b/include/zephyr/bluetooth/hci_types.h @@ -2612,6 +2612,8 @@ struct bt_hci_cp_le_cs_remove_config { uint8_t config_id; } __packed; +#define BT_HCI_OP_LE_CS_TEST_END BT_OP(BT_OGF_LE, 0x0096) /* 0x2096 */ + /* Event definitions */ #define BT_HCI_EVT_UNKNOWN 0x00 @@ -3463,6 +3465,182 @@ struct bt_hci_evt_le_cs_config_complete { uint8_t t_pm_time; } __packed; +#define BT_HCI_LE_CS_TEST_CONN_HANDLE 0x0FFF + +#define BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_COMPLETE 0x0 +#define BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_PARTIAL 0x1 +#define BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_ABORTED 0xF + +#define BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_COMPLETE 0x0 +#define BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_PARTIAL 0x1 +#define BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_ABORTED 0xF + +#define BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_NO_ABORT 0x0 +#define BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_LOCAL_HOST_OR_REMOTE_REQUEST 0x1 +#define BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_TOO_FEW_CHANNELS 0x2 +#define BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_CHMAP_INSTANT_PASSED 0x3 +#define BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_UNSPECIFIED 0xF + +#define BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_NO_ABORT 0x0 +#define BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_LOCAL_HOST_OR_REMOTE_REQUEST 0x1 +#define BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_NO_CS_SYNC_RECEIVED 0x2 +#define BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_SCHED_CONFLICT 0x3 +#define BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_UNSPECIFIED 0xF + +#define BT_HCI_LE_CS_SUBEVENT_RESULT_N_AP_IGNORED 0x00 +#define BT_HCI_LE_CS_SUBEVENT_RESULT_N_AP_1 0x01 +#define BT_HCI_LE_CS_SUBEVENT_RESULT_N_AP_2 0x02 +#define BT_HCI_LE_CS_SUBEVENT_RESULT_N_AP_3 0x03 +#define BT_HCI_LE_CS_SUBEVENT_RESULT_N_AP_4 0x04 + +#define BT_HCI_LE_CS_SUBEVENT_RESULT_FREQ_COMPENSATION_NOT_AVAILABLE 0xC000 + +#define BT_HCI_LE_CS_SUBEVENT_RESULT_PCT_NOT_AVAILABLE 0xFFFFFFFF + +#define BT_HCI_LE_CS_REF_POWER_LEVEL_UNAVAILABLE 0x7F + +#define BT_HCI_LE_CS_PCT_MASK 0xFFF + +#define BT_HCI_LE_CS_TONE_QUALITY_HIGH 0x0 +#define BT_HCI_LE_CS_TONE_QUALITY_MED 0x1 +#define BT_HCI_LE_CS_TONE_QUALITY_LOW 0x2 +#define BT_HCI_LE_CS_TONE_QUALITY_UNAVAILABLE 0x3 + +#define BT_HCI_LE_CS_NOT_TONE_EXT_SLOT 0x0 +#define BT_HCI_LE_CS_TONE_EXT_SLOT_EXT_NOT_EXPECTED 0x1 +#define BT_HCI_LE_CS_TONE_EXT_SLOT_EXT_EXPECTED 0x2 + +#define BT_HCI_LE_CS_TIME_DIFFERENCE_NOT_AVAILABLE 0x8000 + +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_EXT_UNLIKELY 0x00 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_VERY_UNLIKELY 0x01 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_UNLIKELY 0x02 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_POSSIBLE 0x03 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_LIKELY 0x04 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_VERY_LIKELY 0x05 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_EXT_LIKELY 0x06 +#define BT_HCI_LE_CS_PACKET_NADM_UNKNOWN 0xFF + +#define BT_HCI_EVT_LE_CS_SUBEVENT_RESULT 0x31 +/** Subevent result step data format: Mode 0 Initiator */ +struct bt_hci_le_cs_step_data_mode_0_initiator { + uint8_t packet_quality_aa_check: 4; + uint8_t packet_quality_bit_errors: 4; + uint8_t packet_rssi; + uint8_t packet_antenna; + uint16_t measured_freq_offset; +} __packed; + +/** Subevent result step data format: Mode 0 Reflector */ +struct bt_hci_le_cs_step_data_mode_0_reflector { + uint8_t packet_quality_aa_check: 4; + uint8_t packet_quality_bit_errors: 4; + uint8_t packet_rssi; + uint8_t packet_antenna; +} __packed; + +/** Subevent result step data format: Mode 1 */ +struct bt_hci_le_cs_step_data_mode_1 { + uint8_t packet_quality_aa_check: 4; + uint8_t packet_quality_bit_errors: 4; + uint8_t packet_nadm; + uint8_t packet_rssi; + union { + uint16_t toa_tod_initiator; + uint16_t tod_toa_reflector; + }; + uint8_t packet_antenna; +} __packed; + +/** Subevent result step data format: Mode 1 with sounding sequence RTT support */ +struct bt_hci_le_cs_step_data_mode_1_ss_rtt { + uint8_t packet_quality_aa_check: 4; + uint8_t packet_quality_bit_errors: 4; + uint8_t packet_nadm; + uint8_t packet_rssi; + union { + uint16_t toa_tod_initiator; + uint16_t tod_toa_reflector; + }; + uint8_t packet_antenna; + uint8_t packet_pct1[4]; + uint8_t packet_pct2[4]; +} __packed; + + +/** Format for per-antenna path step data in modes 2 and 3 */ +struct bt_hci_le_cs_step_data_tone_info { + uint8_t phase_correction_term[3]; + uint8_t quality_indicator: 4; + uint8_t extension_indicator: 4; +} __packed; + +/** Subevent result step data format: Mode 2 */ +struct bt_hci_le_cs_step_data_mode_2 { + uint8_t antenna_permutation_index; + struct bt_hci_le_cs_step_data_tone_info tone_info[]; +} __packed; + +/** Subevent result step data format: Mode 3 */ +struct bt_hci_le_cs_step_data_mode_3 { + uint8_t packet_quality_aa_check: 4; + uint8_t packet_quality_bit_errors: 4; + uint8_t packet_nadm; + uint8_t packet_rssi; + union { + uint16_t toa_tod_initiator; + uint16_t tod_toa_reflector; + }; + uint8_t packet_antenna; + uint8_t antenna_permutation_index; + struct bt_hci_le_cs_step_data_tone_info tone_info[]; +} __packed; + +/** Subevent result step data format: Mode 3 with sounding sequence RTT support */ +struct bt_hci_le_cs_step_data_mode_3_ss_rtt { + uint8_t packet_quality_aa_check: 4; + uint8_t packet_quality_bit_errors: 4; + uint8_t packet_nadm; + uint8_t packet_rssi; + union { + uint16_t toa_tod_initiator; + uint16_t tod_toa_reflector; + }; + uint8_t packet_antenna; + uint8_t packet_pct1[4]; + uint8_t packet_pct2[4]; + uint8_t antenna_permutation_index; + struct bt_hci_le_cs_step_data_tone_info tone_info[]; +} __packed; + +struct bt_hci_evt_le_cs_subevent_result_step { + uint8_t step_mode; + uint8_t step_channel; + uint8_t step_data_length; + uint8_t step_data[]; +} __packed; + +struct bt_hci_evt_le_cs_subevent_result { + uint16_t conn_handle; + uint8_t config_id; + uint16_t start_acl_conn_event_counter; + uint16_t procedure_counter; + uint16_t frequency_compensation; + uint8_t reference_power_level; + uint8_t procedure_done_status; + uint8_t subevent_done_status; + uint8_t procedure_abort_reason: 4; + uint8_t subevent_abort_reason: 4; + uint8_t num_antenna_paths; + uint8_t num_steps_reported; + uint8_t steps[]; +} __packed; + +#define BT_HCI_EVT_LE_CS_TEST_END_COMPLETE 0x33 +struct bt_hci_evt_le_cs_test_end_complete { + uint8_t status; +} __packed; + /* Event mask bits */ #define BT_EVT_BIT(n) (1ULL << (n)) @@ -3555,6 +3733,8 @@ struct bt_hci_evt_le_cs_config_complete { #define BT_EVT_MASK_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE BT_EVT_BIT(43) #define BT_EVT_MASK_LE_CS_READ_REMOTE_FAE_TABLE_COMPLETE BT_EVT_BIT(44) #define BT_EVT_MASK_LE_CS_CONFIG_COMPLETE BT_EVT_BIT(46) +#define BT_EVT_MASK_LE_CS_SUBEVENT_RESULT BT_EVT_BIT(48) +#define BT_EVT_MASK_LE_CS_TEST_END_COMPLETE BT_EVT_BIT(50) /** HCI Error Codes, BT Core Spec v5.4 [Vol 1, Part F]. */ #define BT_HCI_ERR_SUCCESS 0x00 diff --git a/subsys/bluetooth/host/conn.c b/subsys/bluetooth/host/conn.c index bf420579445..229f7adf0a4 100644 --- a/subsys/bluetooth/host/conn.c +++ b/subsys/bluetooth/host/conn.c @@ -3390,6 +3390,23 @@ void notify_cs_config_removed(struct bt_conn *conn, uint8_t config_id) } } } + +void notify_cs_subevent_result(struct bt_conn *conn, struct bt_conn_le_cs_subevent_result *result) +{ + struct bt_conn_cb *callback; + + SYS_SLIST_FOR_EACH_CONTAINER(&conn_cbs, callback, _node) { + if (callback->le_cs_subevent_data_available) { + callback->le_cs_subevent_data_available(conn, result); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) { + if (cb->le_cs_subevent_data_available) { + cb->le_cs_subevent_data_available(conn, result); + } + } +} #endif /* CONFIG_BT_CHANNEL_SOUNDING */ int bt_conn_le_param_update(struct bt_conn *conn, diff --git a/subsys/bluetooth/host/conn_internal.h b/subsys/bluetooth/host/conn_internal.h index 19cd2f04319..a818a85fb7f 100644 --- a/subsys/bluetooth/host/conn_internal.h +++ b/subsys/bluetooth/host/conn_internal.h @@ -505,6 +505,8 @@ void notify_cs_config_created(struct bt_conn *conn, struct bt_conn_le_cs_config void notify_cs_config_removed(struct bt_conn *conn, uint8_t config_id); +void notify_cs_subevent_result(struct bt_conn *conn, struct bt_conn_le_cs_subevent_result *result); + #if defined(CONFIG_BT_SMP) /* If role specific LTK is present */ bool bt_conn_ltk_present(const struct bt_conn *conn); diff --git a/subsys/bluetooth/host/cs.c b/subsys/bluetooth/host/cs.c index 8341e9f9364..05ab7a90504 100644 --- a/subsys/bluetooth/host/cs.c +++ b/subsys/bluetooth/host/cs.c @@ -18,6 +18,7 @@ LOG_MODULE_REGISTER(bt_cs); #if defined(CONFIG_BT_CHANNEL_SOUNDING) +static struct bt_le_cs_test_cb cs_test_callbacks; void bt_le_cs_set_valid_chmap_bits(uint8_t channel_map[10]) { @@ -241,6 +242,12 @@ void bt_hci_le_cs_read_remote_fae_table_complete(struct net_buf *buf) bt_conn_unref(conn); } +int bt_le_cs_test_cb_register(struct bt_le_cs_test_cb cb) +{ + cs_test_callbacks = cb; + return 0; +} + int bt_le_cs_start_test(const struct bt_le_cs_test_param *params) { struct bt_hci_op_le_cs_test *cp; @@ -344,6 +351,74 @@ int bt_le_cs_start_test(const struct bt_le_cs_test_param *params) return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_TEST, buf, NULL); } +void bt_hci_le_cs_subevent_result(struct net_buf *buf) +{ + struct bt_conn *conn = NULL; + struct bt_hci_evt_le_cs_subevent_result *evt; + struct bt_conn_le_cs_subevent_result result; + struct net_buf_simple step_data_buf; + + if (buf->len < sizeof(*evt)) { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + if (evt->subevent_done_status == BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_PARTIAL) { + LOG_WRN("Discarded incomplete CS subevent results."); + return; + } + + if (sys_le16_to_cpu(evt->conn_handle) == BT_HCI_LE_CS_TEST_CONN_HANDLE) { + if (!cs_test_callbacks.le_cs_test_subevent_data_available) { + LOG_WRN("No callback registered. Discarded subevent results from CS Test."); + return; + } + } else { + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->conn_handle), BT_CONN_TYPE_LE); + if (!conn) { + LOG_ERR("Unknown connection handle when processing subevent results"); + return; + } + } + + result.header.procedure_counter = sys_le16_to_cpu(evt->procedure_counter); + result.header.frequency_compensation = sys_le16_to_cpu(evt->frequency_compensation); + result.header.procedure_done_status = evt->procedure_done_status; + result.header.subevent_done_status = evt->subevent_done_status; + result.header.procedure_abort_reason = evt->procedure_abort_reason; + result.header.subevent_abort_reason = evt->subevent_abort_reason; + result.header.reference_power_level = evt->reference_power_level; + result.header.num_antenna_paths = evt->num_antenna_paths; + result.header.num_steps_reported = evt->num_steps_reported; + + if (evt->num_steps_reported) { + net_buf_simple_init_with_data(&step_data_buf, + evt->steps, + buf->len); + result.step_data_buf = &step_data_buf; + } else { + result.step_data_buf = NULL; + } + + if (sys_le16_to_cpu(evt->conn_handle) == BT_HCI_LE_CS_TEST_CONN_HANDLE) { + result.header.config_id = 0; + result.header.start_acl_conn_event = 0; + + cs_test_callbacks.le_cs_test_subevent_data_available(&result); + + } else { + result.header.config_id = evt->config_id; + result.header.start_acl_conn_event = + sys_le16_to_cpu(evt->start_acl_conn_event_counter); + + notify_cs_subevent_result(conn, &result); + + bt_conn_unref(conn); + } +} + void bt_hci_le_cs_config_complete_event(struct net_buf *buf) { struct bt_hci_evt_le_cs_config_complete *evt; @@ -447,4 +522,68 @@ int bt_le_cs_remove_config(struct bt_conn *conn, uint8_t config_id) return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_REMOVE_CONFIG, buf, NULL); } + +int bt_le_cs_stop_test(void) +{ + struct net_buf *buf; + + buf = bt_hci_cmd_create(BT_HCI_OP_LE_CS_TEST_END, 0); + if (!buf) { + return -ENOBUFS; + } + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_TEST_END, buf, NULL); +} + +void bt_hci_le_cs_test_end_complete(struct net_buf *buf) +{ + struct bt_hci_evt_le_cs_test_end_complete *evt; + + if (buf->len < sizeof(*evt)) { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + if (evt->status) { + LOG_INF("CS Test End failed with status 0x%02X", evt->status); + return; + } + + if (cs_test_callbacks.le_cs_test_end_complete) { + cs_test_callbacks.le_cs_test_end_complete(); + } +} + +void bt_le_cs_step_data_parse(struct net_buf_simple *step_data_buf, + bool (*func)(struct bt_le_cs_subevent_step *step, void *user_data), + void *user_data) +{ + if (!step_data_buf) { + LOG_INF("Tried to parse empty step data."); + return; + } + + while (step_data_buf->len > 1) { + struct bt_le_cs_subevent_step step; + + step.mode = net_buf_simple_pull_u8(step_data_buf); + step.channel = net_buf_simple_pull_u8(step_data_buf); + step.data_len = net_buf_simple_pull_u8(step_data_buf); + + if (step.data_len == 0) { + LOG_WRN("Encountered zero-length step data."); + return; + } + + step.data = step_data_buf->data; + + if (!func(&step, user_data)) { + return; + } + + net_buf_simple_pull(step_data_buf, step.data_len); + } +} + #endif /* CONFIG_BT_CHANNEL_SOUNDING */ diff --git a/subsys/bluetooth/host/hci_core.c b/subsys/bluetooth/host/hci_core.c index 88ba83932f0..5f42eff202c 100644 --- a/subsys/bluetooth/host/hci_core.c +++ b/subsys/bluetooth/host/hci_core.c @@ -2830,6 +2830,12 @@ static const struct event_handler meta_events[] = { sizeof(struct bt_hci_evt_le_cs_read_remote_fae_table_complete)), EVENT_HANDLER(BT_HCI_EVT_LE_CS_CONFIG_COMPLETE, bt_hci_le_cs_config_complete_event, sizeof(struct bt_hci_evt_le_cs_config_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_CS_SUBEVENT_RESULT, + bt_hci_le_cs_subevent_result, + sizeof(struct bt_hci_evt_le_cs_subevent_result)), + EVENT_HANDLER(BT_HCI_EVT_LE_CS_TEST_END_COMPLETE, + bt_hci_le_cs_test_end_complete, + sizeof(struct bt_hci_evt_le_cs_test_end_complete)), #endif /* CONFIG_BT_CHANNEL_SOUNDING */ }; @@ -3407,6 +3413,8 @@ static int le_set_event_mask(void) mask |= BT_EVT_MASK_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE; mask |= BT_EVT_MASK_LE_CS_READ_REMOTE_FAE_TABLE_COMPLETE; mask |= BT_EVT_MASK_LE_CS_CONFIG_COMPLETE; + mask |= BT_EVT_MASK_LE_CS_SUBEVENT_RESULT; + mask |= BT_EVT_MASK_LE_CS_TEST_END_COMPLETE; } sys_put_le64(mask, cp_mask->events); diff --git a/subsys/bluetooth/host/hci_core.h b/subsys/bluetooth/host/hci_core.h index a50b534f1b7..27387c9c53f 100644 --- a/subsys/bluetooth/host/hci_core.h +++ b/subsys/bluetooth/host/hci_core.h @@ -542,6 +542,8 @@ void bt_hci_le_past_received_v2(struct net_buf *buf); void bt_hci_le_cs_read_remote_supported_capabilities_complete(struct net_buf *buf); void bt_hci_le_cs_read_remote_fae_table_complete(struct net_buf *buf); void bt_hci_le_cs_config_complete_event(struct net_buf *buf); +void bt_hci_le_cs_subevent_result(struct net_buf *buf); +void bt_hci_le_cs_test_end_complete(struct net_buf *buf); /* Adv HCI event handlers */ void bt_hci_le_adv_set_terminated(struct net_buf *buf); diff --git a/subsys/bluetooth/host/shell/cs.c b/subsys/bluetooth/host/shell/cs.c index c5bc228f35c..fbc55623b14 100644 --- a/subsys/bluetooth/host/shell/cs.c +++ b/subsys/bluetooth/host/shell/cs.c @@ -136,6 +136,52 @@ static int cmd_read_remote_fae_table(const struct shell *sh, size_t argc, char * return 0; } +static bool process_step_data(struct bt_le_cs_subevent_step *step, void *user_data) +{ + shell_print(ctx_shell, "Subevent results contained step data: "); + shell_print(ctx_shell, "- Step mode %d\n" + "- Step channel %d\n" + "- Step data hexdump:", + step->mode, + step->channel); + shell_hexdump(ctx_shell, step->data, step->data_len); + + return true; +} + +static void cs_test_subevent_data_cb(struct bt_conn_le_cs_subevent_result *result) +{ + shell_print(ctx_shell, "Received subevent results."); + shell_print(ctx_shell, "Subevent Header:\n" + "- Procedure Counter: %d\n" + "- Frequency Compensation: 0x%04x\n" + "- Reference Power Level: %d\n" + "- Procedure Done Status: 0x%02x\n" + "- Subevent Done Status: 0x%02x\n" + "- Procedure Abort Reason: 0x%02x\n" + "- Subevent Abort Reason: 0x%02x\n" + "- Number of Antenna Paths: %d\n" + "- Number of Steps Reported: %d", + result->header.procedure_counter, + result->header.frequency_compensation, + result->header.reference_power_level, + result->header.procedure_done_status, + result->header.subevent_done_status, + result->header.procedure_abort_reason, + result->header.subevent_abort_reason, + result->header.num_antenna_paths, + result->header.num_steps_reported); + + if (result->step_data_buf) { + bt_le_cs_step_data_parse(result->step_data_buf, process_step_data, NULL); + } +} + +static void cs_test_end_complete_cb(void) +{ + shell_print(ctx_shell, "CS Test End Complete."); +} + static int cmd_cs_test_simple(const struct shell *sh, size_t argc, char *argv[]) { int err = 0; @@ -144,7 +190,7 @@ static int cmd_cs_test_simple(const struct shell *sh, size_t argc, char *argv[]) params.main_mode = BT_CONN_LE_CS_MAIN_MODE_1; params.sub_mode = BT_CONN_LE_CS_SUB_MODE_UNUSED; params.main_mode_repetition = 0; - params.mode_0_steps = 0x1; + params.mode_0_steps = 2; params.role = shell_strtoul(argv[1], 16, &err); @@ -164,9 +210,9 @@ static int cmd_cs_test_simple(const struct shell *sh, size_t argc, char *argv[]) params.rtt_type = BT_CONN_LE_CS_RTT_TYPE_AA_ONLY; params.cs_sync_phy = BT_CONN_LE_CS_SYNC_1M_PHY; params.cs_sync_antenna_selection = BT_LE_CS_TEST_CS_SYNC_ANTENNA_SELECTION_ONE; - params.subevent_len = 10000; - params.subevent_interval = 0; - params.max_num_subevents = 0; + params.subevent_len = 3000; + params.subevent_interval = 1; + params.max_num_subevents = 1; params.transmit_power_level = BT_HCI_OP_LE_CS_TEST_MAXIMIZE_TX_POWER; params.t_ip1_time = 80; params.t_ip2_time = 80; @@ -182,10 +228,23 @@ static int cmd_cs_test_simple(const struct shell *sh, size_t argc, char *argv[]) memset(params.override_config_0.not_set.channel_map, 0, sizeof(params.override_config_0.not_set.channel_map)); params.override_config_0.not_set.channel_map[1] = 0xFF; + params.override_config_0.not_set.channel_map[7] = 0xFF; + params.override_config_0.not_set.channel_map[8] = 0xFF; params.override_config_0.not_set.channel_selection_type = BT_CONN_LE_CS_CHSEL_TYPE_3B; params.override_config_0.not_set.ch3c_shape = BT_CONN_LE_CS_CH3C_SHAPE_HAT; params.override_config_0.not_set.ch3c_jump = 0x2; + struct bt_le_cs_test_cb cs_test_cb = { + .le_cs_test_subevent_data_available = cs_test_subevent_data_cb, + .le_cs_test_end_complete = cs_test_end_complete_cb, + }; + + err = bt_le_cs_test_cb_register(cs_test_cb); + if (err) { + shell_error(sh, "bt_le_cs_test_cb_register returned error %d", err); + return -ENOEXEC; + } + err = bt_le_cs_start_test(¶ms); if (err) { shell_error(sh, "bt_le_cs_start_test returned error %d", err); @@ -370,6 +429,19 @@ static int cmd_create_config(const struct shell *sh, size_t argc, char *argv[]) return 0; } +static int cmd_cs_stop_test(const struct shell *sh, size_t argc, char *argv[]) +{ + int err = 0; + + err = bt_le_cs_stop_test(); + if (err) { + shell_error(sh, "bt_cs_stop_test returned error %d", err); + return -ENOEXEC; + } + + return 0; +} + SHELL_STATIC_SUBCMD_SET_CREATE( cs_cmds, SHELL_CMD_ARG(read_remote_supported_capabilities, NULL, "", @@ -382,6 +454,7 @@ SHELL_STATIC_SUBCMD_SET_CREATE( SHELL_CMD_ARG(read_remote_fae_table, NULL, "", cmd_read_remote_fae_table, 1, 0), SHELL_CMD_ARG(start_simple_cs_test, NULL, "", cmd_cs_test_simple, 2, 0), + SHELL_CMD_ARG(stop_cs_test, NULL, "", cmd_cs_stop_test, 1, 0), SHELL_CMD_ARG( create_config, NULL, " "