--- a/lib/btstack/src/hci.c +++ b/lib/btstack/src/hci.c @@ -93,6 +93,20 @@ #endif #endif +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH +#if !defined(ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL) || (HCI_HOST_ACL_PACKET_NUM != 3) +#error "SWITCH_PICO_HCI_CREDIT_BATCH requires controller-to-host flow control with three ACL credits" +#endif +#endif +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER +#ifndef SWITCH_PICO_HCI_CREDIT_BATCH +#error "SWITCH_PICO_HCI_CREDIT_BUFFER requires SWITCH_PICO_HCI_CREDIT_BATCH" +#endif +#if !defined(MAX_NR_HCI_CONNECTIONS) || (MAX_NR_HCI_CONNECTIONS < 1) || (MAX_NR_HCI_CONNECTIONS > 63) +#error "SWITCH_PICO_HCI_CREDIT_BUFFER requires 1..63 configured HCI connections" +#endif +#endif + #ifndef MAX_NR_CONTROLLER_ACL_BUFFERS #define MAX_NR_CONTROLLER_ACL_BUFFERS 255 #endif @@ -273,6 +287,84 @@ #endif static hci_stack_t * hci_stack = NULL; +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH +static btstack_timer_source_t hci_credit_batch_timer; +static bool hci_credit_batch_armed; +static bool hci_credit_batch_due; +static bool hci_credit_batch_enabled; +static uint32_t hci_credit_batch_generation; +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER +static bool hci_credit_batch_in_flight; +// Independent of the prepared ACL buffer, including transport header and word padding. +#define HCI_CREDIT_BATCH_PRE_BUFFER_SIZE ((HCI_OUTGOING_PRE_BUFFER_SIZE + 3u) & ~3u) +static uint32_t hci_credit_batch_buffer[ + (HCI_CREDIT_BATCH_PRE_BUFFER_SIZE + 4u + 4u * MAX_NR_HCI_CONNECTIONS) / sizeof(uint32_t)]; +#endif + +static void hci_credit_batch_reset(void){ + if (hci_credit_batch_armed){ + btstack_run_loop_remove_timer(&hci_credit_batch_timer); + hci_credit_batch_armed = false; + } + hci_credit_batch_due = false; +} + +static void hci_credit_batch_stop(void){ + hci_credit_batch_enabled = false; +#ifndef SWITCH_PICO_HCI_CREDIT_BUFFER + hci_credit_batch_generation++; +#endif + // Cancellation does not end an asynchronous transfer; completion or close does. + hci_credit_batch_reset(); +} + +static void hci_credit_batch_timeout(btstack_timer_source_t * timer); + +static void hci_credit_batch_arm(void){ + if (!hci_credit_batch_enabled || hci_credit_batch_armed || (hci_stack == NULL)) return; + if (hci_stack->state != HCI_STATE_WORKING) return; + btstack_run_loop_set_timer_handler(&hci_credit_batch_timer, hci_credit_batch_timeout); + // Pico adds one millisecond to its truncated clock: 1 ms requests expire within 2 ms. + btstack_run_loop_set_timer(&hci_credit_batch_timer, 1); + hci_credit_batch_armed = true; + btstack_run_loop_add_timer(&hci_credit_batch_timer); +} + +static bool hci_credit_batch_ready(void){ + uint32_t pending = 0; + bool sco_pending = false; + btstack_linked_item_t * it; + for (it = (btstack_linked_item_t *) hci_stack->connections; it; it = it->next){ + hci_connection_t * connection = (hci_connection_t *) it; + pending += connection->num_packets_completed; + if ((connection->address_type == BD_ADDR_TYPE_SCO) && (connection->num_packets_completed != 0)){ + sco_pending = true; + } + } + if (pending == 0){ + hci_stack->host_completed_packets = 0; + hci_credit_batch_reset(); + return false; + } + // Three advertised ACL credits leave one in reserve at the two-completion low water mark. + return hci_credit_batch_due || sco_pending || (pending >= 2); +} + +static void hci_credit_batch_timeout(btstack_timer_source_t * timer){ + UNUSED(timer); + if (!hci_credit_batch_armed) return; + hci_credit_batch_armed = false; + if (!hci_credit_batch_enabled || (hci_stack == NULL)) return; + if (hci_stack->state != HCI_STATE_WORKING) return; + hci_credit_batch_due = true; + if (!hci_credit_batch_ready()) return; + // Retry at a future tick if the transport/buffer is busy, never spin. Arm before hci_run: + // a synchronous send may close/reinitialize HCI, so do not touch its state afterwards. + hci_credit_batch_arm(); + hci_run(); +} +#endif + #ifdef ENABLE_CLASSIC // default name static const char * default_classic_name = "BTstack 00:00:00:00:00:00"; @@ -738,6 +830,9 @@ // only used to send HCI Host Number Completed Packets static int hci_can_send_command_packet_transport(void){ +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER + if (hci_credit_batch_in_flight) return 0; +#endif if (hci_stack->hci_packet_buffer_reserved) return 0; // check for async hci transport implementations @@ -756,6 +851,9 @@ } static int hci_transport_can_send_prepared_packet_now(uint8_t packet_type){ +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER + if (hci_credit_batch_in_flight) return false; +#endif // check for async hci transport implementations if (!hci_stack->hci_transport->can_send_packet_now) return true; return hci_stack->hci_transport->can_send_packet_now(packet_type); @@ -1195,6 +1293,9 @@ #ifdef ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL hci_stack->host_completed_packets = 1; conn->num_packets_completed++; +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + hci_credit_batch_arm(); +#endif #endif // handle different packet types @@ -1269,8 +1370,10 @@ return; } +#ifndef SWITCH_PICO_HCI_CREDIT_BATCH // execute main loop hci_run(); +#endif } static void hci_connection_stop_timer(hci_connection_t * conn){ @@ -1296,6 +1399,9 @@ btstack_linked_list_remove(&hci_stack->connections, (btstack_linked_item_t *) conn); btstack_memory_hci_connection_free( conn ); +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + (void) hci_credit_batch_ready(); +#endif // now it's gone hci_emit_nr_connections_changed(); @@ -4314,6 +4420,12 @@ conn = hci_connection_for_handle(handle); if (!conn) break; +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + // The controller reclaims disconnected credits. Clear them before pairing or + // disconnection callbacks can re-enter hci_run, including handle reuse. + conn->num_packets_completed = 0; + (void) hci_credit_batch_ready(); +#endif #ifdef ENABLE_CLASSIC // pairing failed if it was ongoing hci_pairing_complete(conn, ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION); @@ -4367,6 +4479,13 @@ log_error("Synchronous HCI Transport shouldn't send HCI_EVENT_TRANSPORT_PACKET_SENT"); return; // instead of break: to avoid re-entering hci_run() } +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER + if (hci_credit_batch_in_flight){ + hci_credit_batch_in_flight = false; + // This completion owns only the credit buffer, not a prepared ACL fragment. + break; + } +#endif hci_stack->acl_fragmentation_tx_active = 0; #ifdef ENABLE_LE_ISOCHRONOUS_STREAMS hci_stack->iso_fragmentation_tx_active = 0; @@ -4780,6 +4899,10 @@ #ifdef ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL conn->num_packets_completed++; hci_stack->host_completed_packets = 1; +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + // SCO is never deferred, but needs the same transport-busy retry guarantee. + hci_credit_batch_arm(); +#endif hci_run(); #endif } @@ -4814,6 +4937,10 @@ break; case HCI_ACL_DATA_PACKET: acl_handler(packet, size); +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + // Also service credits for malformed fragments that return early after accounting. + if (hci_stack != NULL) hci_run(); +#endif break; #ifdef ENABLE_CLASSIC case HCI_SCO_DATA_PACKET: @@ -4869,6 +4996,14 @@ #endif static void hci_state_reset(void){ +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + hci_credit_batch_reset(); + hci_credit_batch_generation++; +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER + hci_credit_batch_in_flight = false; +#endif + hci_stack->host_completed_packets = 0; +#endif // no connections yet hci_stack->connections = NULL; @@ -4953,6 +5088,10 @@ #endif void hci_init(const hci_transport_t *transport, const void *config){ +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + hci_credit_batch_stop(); + hci_credit_batch_enabled = true; +#endif #ifdef HAVE_MALLOC if (!hci_stack) { @@ -5082,6 +5221,9 @@ } void hci_deinit(void){ +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + hci_credit_batch_stop(); +#endif btstack_run_loop_remove_timer(&hci_stack->timeout); #ifdef HAVE_MALLOC if (hci_stack) { @@ -5138,6 +5280,9 @@ } void hci_close(void){ +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + hci_credit_batch_stop(); +#endif #ifdef ENABLE_CLASSIC // close remote device db @@ -5264,6 +5409,10 @@ // HCI_STATE_FALLING_ASLEEP on open static int hci_power_control_on(void){ +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + hci_credit_batch_stop(); + hci_credit_batch_enabled = true; +#endif // power on int err = 0; @@ -5300,11 +5449,18 @@ } static void hci_power_control_off(void){ +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + hci_credit_batch_stop(); +#endif log_info("hci_power_control_off"); // close low-level device hci_stack->hci_transport->close(); +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER + hci_credit_batch_in_flight = false; + hci_credit_batch_generation++; +#endif log_info("hci_power_control_off - hci_transport closed"); @@ -5319,6 +5475,9 @@ } static void hci_power_control_sleep(void){ +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + hci_credit_batch_stop(); +#endif log_info("hci_power_control_sleep"); @@ -5363,6 +5522,10 @@ } static void hci_power_enter_initializing_state(void){ +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + hci_credit_batch_stop(); + hci_credit_batch_enabled = true; +#endif // set up state machine hci_stack->num_cmd_packets = 1; // assume that one cmd can be sent hci_stack->hci_packet_buffer_reserved = false; @@ -5543,6 +5706,10 @@ } int hci_power_control(HCI_POWER_MODE power_mode){ +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + // Repeated POWER_ON while working must not restart a pending credit's deadline. + if (power_mode != HCI_POWER_ON) hci_credit_batch_stop(); +#endif log_info("hci_power_control: %d, current mode %u", power_mode, hci_stack->state); btstack_run_loop_remove_timer(&hci_stack->timeout); int err = 0; @@ -5834,10 +6001,19 @@ #ifdef ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL static void hci_host_num_completed_packets(void){ +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + // Clear batching state before any transport/application callback can add new credits. + hci_credit_batch_reset(); + uint32_t generation = hci_credit_batch_generation; +#endif // create packet manually as arrays are not supported and num_commands should not get reduced +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER + uint8_t * packet = (uint8_t *) hci_credit_batch_buffer + HCI_CREDIT_BATCH_PRE_BUFFER_SIZE; +#else hci_reserve_packet_buffer(); uint8_t * packet = hci_get_outgoing_packet_buffer(); +#endif uint16_t size = 0; uint16_t num_handles = 0; @@ -5851,6 +6027,9 @@ for (it = (btstack_linked_item_t *) hci_stack->connections; it ; it = it->next){ hci_connection_t * connection = (hci_connection_t *) it; if (connection->num_packets_completed){ +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER + btstack_assert(num_handles < MAX_NR_HCI_CONNECTIONS); +#endif little_endian_store_16(packet, size, connection->con_handle); size += 2; little_endian_store_16(packet, size, connection->num_packets_completed); @@ -5866,12 +6045,22 @@ hci_stack->host_completed_packets = 0; +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER + hci_credit_batch_in_flight = true; +#endif hci_dump_packet(HCI_COMMAND_DATA_PACKET, 0, packet, size); hci_stack->hci_transport->send_packet(HCI_COMMAND_DATA_PACKET, packet, size); +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + if ((hci_stack == NULL) || (generation != hci_credit_batch_generation)) return; +#endif // release packet buffer for synchronous transport implementations if (hci_transport_synchronous()){ +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER + hci_credit_batch_in_flight = false; +#else hci_release_packet_buffer(); +#endif hci_emit_transport_packet_sent(); } } @@ -7652,6 +7841,21 @@ return; } +#ifdef SWITCH_PICO_HCI_CREDIT_BUFFER + if (hci_credit_batch_in_flight) return; + // Return receive credits before continuing an ACL packet that may exhaust + // transmit credits. A prepared packet is not an occupied transport. + if (hci_stack->host_completed_packets && hci_credit_batch_ready()){ + if (!hci_transport_synchronous() && hci_stack->acl_fragmentation_tx_active) return; +#ifdef ENABLE_LE_ISOCHRONOUS_STREAMS + if (hci_stack->iso_fragmentation_tx_active) return; +#endif + if (!hci_transport_can_send_prepared_packet_now(HCI_COMMAND_DATA_PACKET)) return; + hci_host_num_completed_packets(); + return; + } +#endif + bool done; // send continuation fragments first, as they block the prepared packet buffer @@ -7664,12 +7868,23 @@ #endif #ifdef ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL +#ifndef SWITCH_PICO_HCI_CREDIT_BUFFER +#ifdef SWITCH_PICO_HCI_CREDIT_BATCH + // A single deferred ACL credit must not block ordinary HCI commands. + if (hci_stack->host_completed_packets && hci_credit_batch_ready()){ + if (!hci_can_send_command_packet_transport()) return; + hci_host_num_completed_packets(); + return; + } +#else // send host num completed packets next as they don't require num_cmd_packets > 0 if (!hci_can_send_command_packet_transport()) return; if (hci_stack->host_completed_packets){ hci_host_num_completed_packets(); return; } +#endif +#endif #endif if (!hci_can_send_command_packet_now()) return;