Bluetooth: host: remove TX thread

We don't need the TX thread anymore.

Generalizing the pull-based architecture (ie. `tx_processor`) to HCI
commands makes it possible to run the whole TX path from the the system
workqueue, or any workqueue really.

There is an edge-case, where we call `bt_hci_cmd_send_sync()` from the
syswq, stalling the system. The proposed mitigation is to attempt to drain
the command queue from within `bt_hci_cmd_send_sync()`.

My spidey sense tingles however, and it would be better to just remove the
capability of calling this fn from the syswq. But doing this requires
refactoring a bunch of synchronous procedures in the stack (e.g. stack
init, connection establishment, address setting etc), dragging in more
work. I will do it, but in a later patch.

Signed-off-by: Jonathan Rico <jonathan.rico@nordicsemi.no>
This commit is contained in:
Jonathan Rico 2023-08-01 13:25:13 +02:00 committed by Alberto Escolar
commit 28be8909a6
6 changed files with 113 additions and 100 deletions

View file

@ -51,9 +51,6 @@ LOG_MODULE_REGISTER(bt_conn);
K_FIFO_DEFINE(free_tx);
void tx_processor(struct k_work *item);
K_WORK_DELAYABLE_DEFINE(tx_work, tx_processor);
static void tx_free(struct bt_conn_tx *tx);
static void conn_tx_destroy(struct bt_conn *conn, struct bt_conn_tx *tx)
@ -794,12 +791,6 @@ static bool should_stop_tx(struct bt_conn *conn)
return true;
}
void bt_tx_irq_raise(void)
{
LOG_DBG("");
k_work_reschedule(&tx_work, K_NO_WAIT);
}
void bt_conn_data_ready(struct bt_conn *conn)
{
LOG_DBG("DR");
@ -922,7 +913,20 @@ static void destroy_and_callback(struct bt_conn *conn,
}
}
void tx_processor(struct k_work *item)
static volatile bool _suspend_tx;
#if defined(CONFIG_BT_TESTING)
void bt_conn_suspend_tx(bool suspend)
{
_suspend_tx = suspend;
LOG_DBG("%sing all data TX", suspend ? "suspend" : "resum");
bt_tx_irq_raise();
}
#endif /* CONFIG_BT_TESTING */
void bt_conn_tx_processor(void)
{
LOG_DBG("start");
struct bt_conn *conn;
@ -935,6 +939,10 @@ void tx_processor(struct k_work *item)
return;
}
if (IS_ENABLED(CONFIG_BT_TESTING) && _suspend_tx) {
return;
}
conn = get_conn_ready();
if (!conn) {
@ -1009,7 +1017,7 @@ void tx_processor(struct k_work *item)
/* Always kick the TX work. It will self-suspend if it doesn't get
* resources or there is nothing left to send.
*/
k_work_reschedule(&tx_work, K_NO_WAIT);
bt_tx_irq_raise();
}
static void process_unack_tx(struct bt_conn *conn)

View file

@ -541,8 +541,7 @@ void bt_conn_cleanup_all(void);
/* Selects based on connection type right semaphore for ACL packets */
struct k_sem *bt_conn_get_pkts(struct bt_conn *conn);
/* k_poll related helpers for the TX thread */
int bt_conn_prepare_events(struct k_poll_event events[]);
void bt_conn_tx_processor(void);
/* To be called by upper layers when they want to send something.
* Functions just like an IRQ.

View file

@ -71,6 +71,8 @@ LOG_MODULE_REGISTER(bt_hci_core);
#define BT_HCI_BUS BT_DT_HCI_BUS_GET(BT_HCI_DEV)
#define BT_HCI_NAME BT_DT_HCI_NAME_GET(BT_HCI_DEV)
void bt_tx_irq_raise(void);
#define HCI_CMD_TIMEOUT K_SECONDS(10)
/* Stacks for the threads */
@ -80,8 +82,6 @@ static K_WORK_DEFINE(rx_work, rx_work_handler);
static struct k_work_q bt_workq;
static K_KERNEL_STACK_DEFINE(rx_thread_stack, CONFIG_BT_RX_STACK_SIZE);
#endif /* CONFIG_BT_RECV_WORKQ_BT */
static struct k_thread tx_thread_data;
static K_KERNEL_STACK_DEFINE(tx_thread_stack, CONFIG_BT_HCI_TX_STACK_SIZE);
static void init_work(struct k_work *work);
@ -337,10 +337,12 @@ int bt_hci_cmd_send(uint16_t opcode, struct net_buf *buf)
}
net_buf_put(&bt_dev.cmd_tx_queue, buf);
bt_tx_irq_raise();
return 0;
}
static bool process_pending_cmd(k_timeout_t timeout);
int bt_hci_cmd_send_sync(uint16_t opcode, struct net_buf *buf,
struct net_buf **rsp)
{
@ -357,21 +359,47 @@ int bt_hci_cmd_send_sync(uint16_t opcode, struct net_buf *buf,
LOG_DBG("buf %p opcode 0x%04x len %u", buf, opcode, buf->len);
/* This local sem is just for suspending the current thread until the
* command is processed by the LL. It is given (and we are awaken) by
* the cmd_complete/status handlers.
*/
k_sem_init(&sync_sem, 0, 1);
cmd(buf)->sync = &sync_sem;
net_buf_put(&bt_dev.cmd_tx_queue, net_buf_ref(buf));
bt_tx_irq_raise();
/* Wait for a response from the Bluetooth Controller.
* The Controller may fail to respond if:
* - It was never programmed or connected.
* - There was a fatal error.
*
* See the `BT_HCI_OP_` macros in hci_types.h or
* Core_v5.4, Vol 4, Part E, Section 5.4.1 and Section 7
* to map the opcode to the HCI command documentation.
* Example: 0x0c03 represents HCI_Reset command.
/* TODO: disallow sending sync commands from syswq altogether */
/* Since the commands are now processed in the syswq, we cannot suspend
* and wait. We have to send the command from the current context.
*/
if (k_current_get() == &k_sys_work_q.thread) {
/* drain the command queue until we get to send the command of interest. */
struct net_buf *cmd = NULL;
do {
cmd = k_fifo_peek_head(&bt_dev.cmd_tx_queue);
LOG_DBG("process cmd %p want %p", cmd, buf);
/* Wait for a response from the Bluetooth Controller.
* The Controller may fail to respond if:
* - It was never programmed or connected.
* - There was a fatal error.
*
* See the `BT_HCI_OP_` macros in hci_types.h or
* Core_v5.4, Vol 4, Part E, Section 5.4.1 and Section 7
* to map the opcode to the HCI command documentation.
* Example: 0x0c03 represents HCI_Reset command.
*/
bool success = process_pending_cmd(HCI_CMD_TIMEOUT);
BT_ASSERT_MSG(success, "command opcode 0x%04x timeout", opcode);
(void)success; /* cmon zephyr fix your assert macros */
} while (buf != cmd);
}
/* Now that we have sent the command, suspend until the LL replies */
err = k_sem_take(&sync_sem, HCI_CMD_TIMEOUT);
BT_ASSERT_MSG(err == 0,
"Controller unresponsive, command opcode 0x%04x timeout with err %d",
@ -2425,6 +2453,7 @@ static void hci_cmd_done(uint16_t opcode, uint8_t status, struct net_buf *evt_bu
/* If the command was synchronous wake up bt_hci_cmd_send_sync() */
if (cmd(buf)->sync) {
LOG_DBG("sync cmd released");
cmd(buf)->status = status;
k_sem_give(cmd(buf)->sync);
}
@ -2465,6 +2494,7 @@ static void hci_cmd_complete(struct net_buf *buf)
/* Allow next command to be sent */
if (ncmd) {
k_sem_give(&bt_dev.ncmd_sem);
bt_tx_irq_raise();
}
}
@ -2485,6 +2515,7 @@ static void hci_cmd_status(struct net_buf *buf)
/* Allow next command to be sent */
if (ncmd) {
k_sem_give(&bt_dev.ncmd_sem);
bt_tx_irq_raise();
}
}
@ -2896,20 +2927,16 @@ static void hci_event(struct net_buf *buf)
net_buf_unref(buf);
}
static void send_cmd(void)
static void hci_core_send_cmd(void)
{
struct net_buf *buf;
int err;
/* Get next command */
LOG_DBG("calling net_buf_get");
LOG_DBG("fetch cmd");
buf = net_buf_get(&bt_dev.cmd_tx_queue, K_NO_WAIT);
BT_ASSERT(buf);
/* Wait until ncmd > 0 */
LOG_DBG("calling sem_take_wait");
k_sem_take(&bt_dev.ncmd_sem, K_FOREVER);
/* Clear out any existing sent command */
if (bt_dev.sent_cmd) {
LOG_ERR("Uncleared pending sent_cmd");
@ -2927,27 +2954,7 @@ static void send_cmd(void)
k_sem_give(&bt_dev.ncmd_sem);
hci_cmd_done(cmd(buf)->opcode, BT_HCI_ERR_UNSPECIFIED, buf);
net_buf_unref(buf);
}
}
static void process_events(struct k_poll_event *ev, int count)
{
LOG_DBG("count %d", count);
for (; count; ev++, count--) {
LOG_DBG("ev->state %u", ev->state);
switch (ev->state) {
case K_POLL_STATE_FIFO_DATA_AVAILABLE:
if (ev->tag == BT_EVENT_CMD_TX) {
send_cmd();
}
break;
default:
LOG_WRN("Unexpected k_poll event state %u", ev->state);
__ASSERT_NO_MSG(0);
break;
}
bt_tx_irq_raise();
}
}
@ -2969,38 +2976,6 @@ static void process_events(struct k_poll_event *ev, int count)
#endif /* CONFIG_BT_ISO */
#endif /* CONFIG_BT_CONN */
static void hci_tx_thread(void *p1, void *p2, void *p3)
{
static struct k_poll_event events[EV_COUNT] = {
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&bt_dev.cmd_tx_queue,
BT_EVENT_CMD_TX),
};
LOG_DBG("Started");
while (1) {
int ev_count, err;
events[0].state = K_POLL_STATE_NOT_READY;
ev_count = 1;
LOG_DBG("Calling k_poll with %d events", ev_count);
err = k_poll(events, ev_count, K_FOREVER);
BT_ASSERT(err == 0);
process_events(events, ev_count);
/* Make sure we don't hog the CPU if there's all the time
* some ready events.
*/
k_yield();
}
}
static void read_local_ver_complete(struct net_buf *buf)
{
struct bt_hci_rp_read_local_version_info *rp = (void *)buf->data;
@ -4210,7 +4185,8 @@ static void rx_work_handler(struct k_work *work)
#if defined(CONFIG_BT_TESTING)
k_tid_t bt_testing_tx_tid_get(void)
{
return &tx_thread_data;
/* We now TX everything from the syswq */
return &k_sys_work_q.thread;
}
#endif
@ -4262,13 +4238,6 @@ int bt_enable(bt_ready_cb_t cb)
k_sem_init(&bt_dev.ncmd_sem, 0, 1);
}
k_fifo_init(&bt_dev.cmd_tx_queue);
/* TX thread */
k_thread_create(&tx_thread_data, tx_thread_stack,
K_KERNEL_STACK_SIZEOF(tx_thread_stack),
hci_tx_thread, NULL, NULL, NULL,
K_PRIO_COOP(CONFIG_BT_HCI_TX_PRIO),
0, K_NO_WAIT);
k_thread_name_set(&tx_thread_data, "BT TX");
#if defined(CONFIG_BT_RECV_WORKQ_BT)
/* RX thread */
@ -4341,9 +4310,6 @@ int bt_disable(void)
disconnected_handles_reset();
#endif /* CONFIG_BT_CONN */
/* Abort TX thread */
k_thread_abort(&tx_thread_data);
#if defined(CONFIG_BT_RECV_WORKQ_BT)
/* Abort RX thread */
k_thread_abort(&bt_workq.thread);
@ -4644,3 +4610,41 @@ int bt_configure_data_path(uint8_t dir, uint8_t id, uint8_t vs_config_len,
return err;
}
/* Return `true` if a command was processed/sent */
static bool process_pending_cmd(k_timeout_t timeout)
{
if (!k_fifo_is_empty(&bt_dev.cmd_tx_queue)) {
if (k_sem_take(&bt_dev.ncmd_sem, K_NO_WAIT) == 0) {
hci_core_send_cmd();
return true;
}
}
return false;
}
static void tx_processor(struct k_work *item)
{
LOG_DBG("TX process start");
if (process_pending_cmd(K_NO_WAIT)) {
/* If we processed a command, let the scheduler run before
* processing another command (or data).
*/
bt_tx_irq_raise();
return;
}
/* Hand over control to conn to process pending data */
if (IS_ENABLED(CONFIG_BT_CONN_TX)) {
bt_conn_tx_processor();
}
}
K_WORK_DELAYABLE_DEFINE(tx_work, tx_processor);
void bt_tx_irq_raise(void)
{
LOG_DBG("kick TX");
k_work_reschedule(&tx_work, K_NO_WAIT);
}

View file

@ -594,3 +594,5 @@ void bt_hci_le_df_cte_req_failed(struct net_buf *buf);
void bt_hci_le_per_adv_subevent_data_request(struct net_buf *buf);
void bt_hci_le_per_adv_response_report(struct net_buf *buf);
void bt_tx_irq_raise(void);

View file

@ -729,9 +729,9 @@ static bool iso_has_data(struct bt_conn *conn)
{
#if defined(CONFIG_BT_ISO_TX)
return !k_fifo_is_empty(&conn->iso.txq);
#else
#else /* !CONFIG_BT_ISO_TX */
return false;
#endif
#endif /* CONFIG_BT_ISO_TX */
}
static struct net_buf *iso_data_pull(struct bt_conn *conn, size_t amount)

View file

@ -32,8 +32,8 @@ static atomic_t nwrites;
static atomic_t indications;
static atomic_t notifications;
/* Defined in hci_core.c */
extern k_tid_t bt_testing_tx_tid_get(void);
/* Defined in conn.c */
extern void bt_conn_suspend_tx(bool suspend);
static struct bt_conn *dconn;
@ -159,7 +159,7 @@ static ssize_t written_to(struct bt_conn *conn,
if (atomic_get(&nwrites) == 0) {
/* Suspend on the first write, which is an ATT Request */
LOG_INF("suspending HCI TX thread");
k_thread_suspend(bt_testing_tx_tid_get());
bt_conn_suspend_tx(true);
}
atomic_inc(&nwrites);
@ -311,7 +311,7 @@ void test_procedure_0(void)
WAIT_FOR_VAL(nwrites, 3);
/* Send RSP to LL */
k_thread_resume(bt_testing_tx_tid_get());
bt_conn_suspend_tx(false);
PASS("DUT done\n");
}