Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bluetooth: buf: Add a callback for freed buffer in rx pool #81646

Merged

Conversation

PavelVPV
Copy link
Collaborator

The Bluetooth data buffer API currently lacks a mechanism to notify when
a buffer is freed in the RX pool. This limitation forces HCI drivers to
adopt inefficient workarounds to manage buffer allocation.

HCI drivers face two suboptimal options:

  • Blocking calls: Use bt_buf_get_rx with K_FOREVER, which blocks the
    execution context until a buffer becomes available.
  • Polling: Repeatedly call bt_buf_get_rx with K_NO_WAIT, which increases
    CPU load and reduces efficiency.

This commit introduces a callback mechanism that is triggered each time
a buffer is freed in the RX pool. With this feature, HCI drivers can:

  • Call bt_buf_get_rx with K_NO_WAIT.
  • Wait for the callback notification if a NULL buffer is returned,
    avoiding unnecessary polling.

The new callback improves efficiency by enabling event-driven behavior
for buffer management, reducing CPU overhead while maintaining
responsiveness.

@alwa-nordic
Copy link
Collaborator

alwa-nordic commented Nov 20, 2024

We have gotten feedback about lack of thread safety in the Host. I think we should make sure all code we add is thread safe, or have the API documentation say exactly what to synchronize on to safely call the API, since we have committed to this.

I have sketched some (compile time tested) changes at alwa-nordic@03b0d39.

In the sketch, I chose to make it thread safe. I also considered that an application may collide with the driver and both will call the cb_set function. To prevent "silent corruption" in that case, I changed it from a single cb_set to a cb_register and cb_unregister.

Apart from this, I removed the dependency injection from buf.c to iso.c and instead make iso.c call buf.c directly. I think this simplifies the code (at the cost of adding a circular dependency).

@alwa-nordic
Copy link
Collaborator

alwa-nordic commented Nov 20, 2024

I think we can assign single-bit values to enum bt_buf_type, and then we wouldn't need enum bt_buf_type_bit.

@rugeGerritsen
Copy link
Collaborator

We have gotten feedback about lack of thread safety in the Host. I think we should make sure all code we add is thread safe, or have the API documentation say exactly what to synchronize on to safely call the API, since we have committed to this.

I have sketched some (compile time tested) changes at alwa-nordic@03b0d39.

In the sketch, I chose to make it thread safe. I also considered that an application may collide with the driver and both will call the cb_set function. To prevent "silent corruption" in that case, I changed it from a single cb_set to a cb_register and cb_unregister.

Apart from this, I removed the dependency injection from buf.c to iso.c and instead make iso.c call buf.c directly. I think this simplifies the code (at the cost of adding a circular dependency).

Can you elaborate why we want bt_buf_rx_freed_cb_set() to be thread safe? AFAIK, the RX buffers are only used when passed from controller to host, so the application should not call this API.

@alwa-nordic
Copy link
Collaborator

alwa-nordic commented Nov 20, 2024

Can you elaborate why we want bt_buf_rx_freed_cb_set() to be thread safe? AFAIK, the RX buffers are only used when passed from controller to host, so the application should not call this API.

Thread safe is the default, as stated in documentation. The alternative is to document the API's thread safety aspects. It may be right that this API does not have to be thread safe, I'm not arguing for or against this right now, but then the documentation of this API has to do some heavy lifting.

@alwa-nordic
Copy link
Collaborator

Food for thought: buf_rx_freed_notify as implemented is invoked from net_buf destroy callbacks. Those come from potentially arbitrary threads.

bt_buf_rx_freed_cb_t may have to be designated thread safe in case multiple threads call destroy concurrently.

Without synchronization with buf_rx_freed_notify, bt_buf_rx_freed_cb_set is safe to call only when the caller can guarantee a concurrent buf_rx_freed_notify is impossible. At what times can the caller infer this guarantee?

@PavelVPV
Copy link
Collaborator Author

We have gotten feedback about lack of thread safety in the Host. I think we should make sure all code we add is thread safe, or have the API documentation say exactly what to synchronize on to safely call the API, since we have committed to this.

Thread safe is the default, as stated in documentation. The alternative is to document the API's thread safety aspects. It may be right that this API does not have to be thread safe, I'm not arguing for or against this right now, but then the documentation of this API has to do some heavy lifting.

If you can come up with the scenario where this function is called from multiple contexts then I'll make it thread safe. But from my understanding there is only a single user of this API. More other, only 1 callback can be set, so calling this function from different contexts means that there is misunderstanding of the purpose of this API, which in my understanding should be called once at the initialization phase. Therefore, I'd rather update the API description.

@PavelVPV
Copy link
Collaborator Author

Food for thought: buf_rx_freed_notify as implemented is invoked from net_buf destroy callbacks. Those come from potentially arbitrary threads.

bt_buf_rx_freed_cb_t may have to be designated thread safe in case multiple threads call destroy concurrently.

Without synchronization with buf_rx_freed_notify, bt_buf_rx_freed_cb_set is safe to call only when > the caller can guarantee a concurrent buf_rx_freed_notify is impossible. At what times can the caller infer this guarantee?

With this I fully agree. I'll make the callback thread safe.

@PavelVPV
Copy link
Collaborator Author

Apart from this, I removed the dependency injection from buf.c to iso.c and instead make iso.c call buf.c directly. I think this simplifies the code (at the cost of adding a circular dependency).

I think I disagree with this suggestion precisely because it creates dependency of iso.c on buf.c. The dependency injection from buf.c to iso.c already exists.

@PavelVPV PavelVPV force-pushed the hci_driver_async_buf_get_upstream branch from f9e1bef to a41ae5b Compare November 21, 2024 07:09
@alwa-nordic
Copy link
Collaborator

If you can come up with the scenario where this function is called from multiple contexts then I'll make it thread safe. But from my understanding there is only a single user of this API. More other, only 1 callback can be set, so calling this function from different contexts means that there is misunderstanding of the purpose of this API, which in my understanding should be called once at the initialization phase. Therefore, I'd rather update the API description.

I don't have any scenario in mind. I am in agreement with you if we update the API documentation so the user is informed about when it's safe to call the API, and when it's safe to free the callback.

I would like any assumptions we make about our own code to be documented and treated like internal API. Let's see what assumptions we need to make, if any. We could put it in code comments or create an architecture design document.

I'll make the callback thread safe.

It's a good idea to document this requirement on bt_buf_rx_freed_cb_t.

@PavelVPV PavelVPV force-pushed the hci_driver_async_buf_get_upstream branch 2 times, most recently from b02b581 to e203e24 Compare November 22, 2024 08:21
jhedberg
jhedberg previously approved these changes Nov 22, 2024
jhedberg
jhedberg previously approved these changes Nov 22, 2024
Comment on lines 68 to 71
zassert_equal(test_vector[i].exp_type_mask, freed_buf_type,
"Unexpected buffer type");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not test for equality, but just the expected bit. Extra bits should not fail this test, right? Like for hci_raw that uses the same buffer pool for all pool types.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming the above is done, exp_type_mask in the test vector is over-fitting to the implementation. We should get rid of exp_type_mask and instead just check that type bit is set in the callback-provided value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather add a dedicated test for hci_raw that checks that it passes all 3 types. Then we know exactly what each variant returns.

Copy link
Collaborator

@alwa-nordic alwa-nordic Nov 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we want to test different things.

I want a test which accepts any implementation that follows the API specification. You want a test that fully specifies the behavior of the implementation with no wiggle room.

Both types of tests are useful. Your type is useful for enforcing strict refactoring without behavior change. My type is useful for testing multiple implementations or a re-implementation that takes advantage of the wiggle room in the specification.

Maybe we should have both tests? One test for the API, and one for each fully-specified implementation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I suggest a different test where this wiggle room is not needed: 5f1f5d9

- native_sim
- native_sim/native/64
integration_platforms:
- native_sim
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add extra_configs here as well to make sure the config is as we expect, no matter what the default is?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, though if we check the exact returned value, this may not be needed. I added CONFIG_BT_HCI_ACL_FLOW_CONTROL=y any way.

@PavelVPV PavelVPV force-pushed the hci_driver_async_buf_get_upstream branch from e930b25 to 88ca011 Compare November 26, 2024 15:07
@Thalley Thalley removed their request for review November 26, 2024 16:14
@PavelVPV PavelVPV force-pushed the hci_driver_async_buf_get_upstream branch from 88ca011 to e01cbc8 Compare December 9, 2024 13:13
@zephyrbot zephyrbot requested a review from cvinayak December 9, 2024 13:14
@jhedberg
Copy link
Member

jhedberg commented Dec 9, 2024

@PavelVPV there's a conflict that needs resolving - please rebase.

alwa-nordic
alwa-nordic previously approved these changes Dec 9, 2024
theob-pro
theob-pro previously approved these changes Dec 9, 2024
This allows to combine several types in a single value.

Signed-off-by: Pavel Vasilyev <[email protected]>
The Bluetooth data buffer API currently lacks a mechanism to notify when
a buffer is freed in the RX pool. This limitation forces HCI drivers to
adopt inefficient workarounds to manage buffer allocation.

HCI drivers face two suboptimal options:

- Blocking calls: Use bt_buf_get_rx with K_FOREVER, which blocks the
  execution context until a buffer becomes available.
- Polling: Repeatedly call bt_buf_get_rx with K_NO_WAIT, which increases
  CPU load and reduces efficiency.

This commit introduces a callback mechanism that is triggered each time
a buffer is freed in the RX pool. With this feature, HCI drivers can:

- Call bt_buf_get_rx with K_NO_WAIT.
- Wait for the callback notification if a NULL buffer is returned,
  avoiding unnecessary polling.

The new callback improves efficiency by enabling event-driven behavior
for buffer management, reducing CPU overhead while maintaining
responsiveness.

Signed-off-by: Pavel Vasilyev <[email protected]>
This commit adds a unit test that checks the freed buffer callback of
the bluetooth data buffer API.

Signed-off-by: Pavel Vasilyev <[email protected]>
@PavelVPV PavelVPV dismissed stale reviews from theob-pro and alwa-nordic via 2e32342 December 9, 2024 20:11
@PavelVPV PavelVPV force-pushed the hci_driver_async_buf_get_upstream branch from e01cbc8 to 2e32342 Compare December 9, 2024 20:11
@PavelVPV
Copy link
Collaborator Author

PavelVPV commented Dec 9, 2024

@PavelVPV there's a conflict that needs resolving - please rebase.

Done

@LuoZhongYao
Copy link
Contributor

Because enum bt_buf_type is now a bitmap, and struct bt_buf_data.type is uint8_t, and in the foreseeable future, bt_buf_type will increase, should we consider changing struct bt_buf_data.type to uint32_t?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Bluetooth Host Bluetooth Host (excluding BR/EDR) area: Bluetooth ISO Bluetooth LE Isochronous Channels area: Bluetooth
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants