bluetooth: host: Add support for processing CS subevent results

Adds support for:
- LE CS Subevent Result event
- LE CS Test End Complete event

For now, recombination of subevent results with more steps than could
fit within a single HCI event is not supported, and such events
are discarded.

Signed-off-by: Olivier Lesage <olivier.lesage@nordicsemi.no>
This commit is contained in:
Olivier Lesage 2024-10-04 11:38:28 +02:00 committed by Fabio Baltieri
parent 6fc8563b16
commit a12b869eac
9 changed files with 607 additions and 4 deletions

View file

@ -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 */

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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);

View file

@ -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 */

View file

@ -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);

View file

@ -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);

View file

@ -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(&params);
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, "<None>",
@ -382,6 +454,7 @@ SHELL_STATIC_SUBCMD_SET_CREATE(
SHELL_CMD_ARG(read_remote_fae_table, NULL, "<None>", cmd_read_remote_fae_table, 1, 0),
SHELL_CMD_ARG(start_simple_cs_test, NULL, "<Role selection (initiator, reflector): 0, 1>",
cmd_cs_test_simple, 2, 0),
SHELL_CMD_ARG(stop_cs_test, NULL, "<None>", cmd_cs_stop_test, 1, 0),
SHELL_CMD_ARG(
create_config, NULL,
"<id> <context: local-only, local-remote> <role: initiator, reflector> "