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: host: Allow concurrent advertising with multiple ids #34848

Merged

Conversation

rugeGerritsen
Copy link
Collaborator

@rugeGerritsen rugeGerritsen commented May 5, 2021

The HCI specification creates additional complexity to allow this
configuration:

  • When a connection gets established, we need to know which
    identity the HCI_LE_Connection_Complete event corresponds to.
  • The identity is a property of the advertising set.
    Therefore we need the advertising handle.
  • The advertising handle is part of the
    HCI_LE_Advertising_Set_Terminated event and is not part of
    the HCI_LE_Connection_Complete event. Therefore
    the information of both events needs to be combined.

By spec the LE_Connection_Complete comes first. Therefore we cache
this event until the identity is available.
The event is only cached when a connection gets established as
that is the only case where we need to resolve the identity.

As the caching requires more resources, it is only enabled if the
application requires multiple advertising sets and multiple
identities.

The host maps the HCI_LE_Advertising_Set_Terminated event with
the HCI_LE_Connection_Complete event by comparing the connection
handles.

Fixes: #31588

@rugeGerritsen rugeGerritsen force-pushed the ext-adv-multiple-ids branch 2 times, most recently from 4346318 to 38d95be Compare May 5, 2021 07:42
@rugeGerritsen rugeGerritsen marked this pull request as ready for review May 5, 2021 09:44
@trond-snekvik trond-snekvik added this to the v2.6.0 milestone May 5, 2021
* should be processed or not.
*/
bool conn_complete_received;
struct bt_hci_evt_le_enh_conn_complete cached_conn_complete;
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this can lead to a race condition if two advertisers manage to establish a connection before the TERMINATED is received for the first one.

Should this scale by CONFIG_BT_ID_MAX?

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'm assuming that the race condition you are considering is in step 5 and 6 below:

  1. Start advertising set 0 using identity 0
  2. Start advertising set 1 using identity 1
  3. Receive LE Connection Complete for advertising set 0
  4. Receive LE Connection Complete for advertising set 1
  5. Receive LE Advertising Set terminated event for advertising set 0
  6. Receive LE Advertising Set terminated event for advertising set 1

If that sequence occurs, the host has no way of mapping the LE Advertising Set Terminated event to the Connection Complete event. That is, it can't map an identity to the newly established connection.

Therefore, according to errata 14871, the controller should guarantee that the Advertising Set Terminated event follows the Connection Complete event. Hence, the controller should prevent sequences like the one above from happening.

Choose a reason for hiding this comment

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

what about the warning in hci_core:1058? will that be raised in such scenario?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, this warning (https://github.com/zephyrproject-rtos/zephyr/pull/34848/files#diff-774b3de86adb275da54becf2a0958a2d4dae03d63e47840037ecc575e9d658b4R1058 ) will be printed in case the controller is raising another enhanced connection complete event before the LE Advertising Set Terminated event is processed

Copy link
Collaborator

Choose a reason for hiding this comment

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

The idea of caching the connection complete event is sound I believe. Since the terminated event contains a connection handle, we can do a lookup from the adv set to the enhanced connection complete event.

The current implementation would not support the scenario above, where we receive 2 connection complete events before the advertising set terminated event, it only supports caching of one such even.

I think this could be implemented as

  1. Only Enchance Connection Complete Event, store the event in an array (same size as MIN(CONFIG_BT_MAX_CONN, CONFIG_BT_EXT_ADV_MAX_ADV_SET))
  2. On Advertising Set terminated lookup the connection handle to find the matching enhanced connection complete event
  3. Finalize connection complete

Furthermore, I believe this PR is missing some crucial connection cancellation features, e.g. if the application stops the advertising set before advertising set terminated event, but after the connection complete event (corner case, but can still cause a desync).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@Thalley , thanks for your review. I believe I've addressed your comments by doing the following:

  • Checking if the advertiser is still running before processing the cached connection complete event to avoid that the application gets a connection complete callback after stopping the advertiser.
  • No longer depend on the order of events from the controller. Now the host caches all events that result in a connection being established. The host will compare the connection handles in those two events in order to create the mapping. The cost is some extra RAM usage.

@carlescufi carlescufi requested a review from Thalley May 5, 2021 12:26
@zephyrbot zephyrbot requested a review from asbjornsabo May 5, 2021 18:20
* should be processed or not.
*/
bool conn_complete_received;
struct bt_hci_evt_le_enh_conn_complete cached_conn_complete;
Copy link
Collaborator

Choose a reason for hiding this comment

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

The idea of caching the connection complete event is sound I believe. Since the terminated event contains a connection handle, we can do a lookup from the adv set to the enhanced connection complete event.

The current implementation would not support the scenario above, where we receive 2 connection complete events before the advertising set terminated event, it only supports caching of one such even.

I think this could be implemented as

  1. Only Enchance Connection Complete Event, store the event in an array (same size as MIN(CONFIG_BT_MAX_CONN, CONFIG_BT_EXT_ADV_MAX_ADV_SET))
  2. On Advertising Set terminated lookup the connection handle to find the matching enhanced connection complete event
  3. Finalize connection complete

Furthermore, I believe this PR is missing some crucial connection cancellation features, e.g. if the application stops the advertising set before advertising set terminated event, but after the connection complete event (corner case, but can still cause a desync).

@rugeGerritsen rugeGerritsen force-pushed the ext-adv-multiple-ids branch from 38d95be to 3635534 Compare May 6, 2021 12:21
@rugeGerritsen rugeGerritsen requested a review from Thalley May 6, 2021 12:25
@rugeGerritsen rugeGerritsen force-pushed the ext-adv-multiple-ids branch from 3635534 to 6016c3d Compare May 6, 2021 12:26
Copy link
Collaborator

@Thalley Thalley left a comment

Choose a reason for hiding this comment

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

A few formatting comments.

Since this may introduce introduce issues with enhanced connections, I would be a lot happier to approve if it was accompanied by some test that validates the change.

subsys/bluetooth/host/hci_core.c Outdated Show resolved Hide resolved
subsys/bluetooth/host/hci_core.c Outdated Show resolved Hide resolved
subsys/bluetooth/host/hci_core.c Outdated Show resolved Hide resolved
subsys/bluetooth/host/hci_core.c Outdated Show resolved Hide resolved
subsys/bluetooth/host/hci_core.c Show resolved Hide resolved
@rugeGerritsen rugeGerritsen force-pushed the ext-adv-multiple-ids branch from 6016c3d to 1e71dd5 Compare May 6, 2021 13:18
The HCI specification creates additional complexity to allow this
configuration:
 - When a connection gets established, we need to know which
   identity the HCI_LE_Connection_Complete event corresponds to.
 - The identity is a property of the advertising set.
   Therefore we need the advertising handle.
 - The advertising handle is part of the
   HCI_LE_Advertising_Set_Terminated event and is not part of
   the HCI_LE_Connection_Complete event. Therefore
   the information of both events needs to be combined.

By spec the LE_Connection_Complete comes first. Therefore we cache
this event until the identity is available.
The event is only cached when a connection gets established as
that is the only case where we need to resolve the identity.

As the caching requires more resources, it is only enabled if the
application requires multiple advertising sets and multiple
identities.

The host maps the HCI_LE_Advertising_Set_Terminated event with
the HCI_LE_Connection_Complete event by comparing the connection
handles.

Signed-off-by: Rubin Gerritsen <rubin.gerritsen@nordicsemi.no>
This will exercise caching of the HCI_LE_Connection_Complete
event in the host.

Signed-off-by: Rubin Gerritsen <rubin.gerritsen@nordicsemi.no>
@rugeGerritsen rugeGerritsen force-pushed the ext-adv-multiple-ids branch from 1e71dd5 to 7b9c14b Compare May 6, 2021 13:45
@github-actions github-actions bot added the area: Tests Issues related to a particular existing or missing test label May 6, 2021
*/
bt_hci_le_enh_conn_complete(&bt_dev.cached_conn_complete[i].evt);
}
bt_dev.cached_conn_complete[i].valid = false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

It still seems that in case that the application disables the advertising set after the connection complete event is received, but before the adv terminated event, then we don't clean up the cached connection complete event, as there won't be a adv terminated event.

Copy link
Collaborator

@Thalley Thalley left a comment

Choose a reason for hiding this comment

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

I still think it's missing handling of a corner case.

@rugeGerritsen
Copy link
Collaborator Author

@Thalley , I have thought a bit more about the race conditions you have mentioned. I'm not convinced there are race conditions that need to be handled. LE_Set_Advertising_Enable is handled synchronously,
It may succeed or fail if it is issued before the LE_Connection_Complete event is fetched, and will fail if it is fetched afterwards. The controller will either send both the events LE_Connection_Complete and LE_Advertising_Set_Terminated or none of them. That will prevent the host and controller from ending up in an out-of-sync state.

Let me try to illustrate this by providing some examples:

Case 1: Adv is stopped with success:

  1. HCI_LE_Set_Advertising_Enable (disable) -> Success
  2. <controller>_evt_get() -> No connection complete events

Case 2: Adv is stopped but fails because the controller has a pending event:

  1. A connection is established, an event is prepared.
  2. HCI_LE_Set_Advertising_Enable (disable) -> Disallowed
  3. bt_recv(LE_Connection_Complete)
  4. bt_recv(LE_Advertising_Set_Terminated)

Case 3: Adv is stopped after the enhanced connection complete event is processed:

  1. bt_recv(LE_Connection_Complete)
  2. HCI_LE_Set_Advertising_Enable (disable) -> Disallowed. The controller connection complete
    event has already been fetched from the controller.
  3. bt_recv(LE_Advertising_Set_Terminated)

Furthermore, when restarting the advertiser will not result in race conditions as the flag BT_ADV_ENABLED is cleared when processing the LE_Advertising_Set_Terminated event.

Case 4: Advertiser is restared after fetching both events:

  1. bt_le_adv_start()
  2. bt_recv(LE_Connection_Complete)
  3. bt_recv(LE_Advertising_Set_Terminated)
  4. bt_le_adv_start(). No issue. All previous state has been cleaned up.

Case 5: Advertiser is restarted after fetching the first event:

  1. bt_le_adv_start()
  2. bt_recv(LE_Connection_Complete)
  3. bt_le_adv_start() -> fails because the flag BT_ADV_ENABLED is not yet cleared.
  4. bt_recv(LE_Advertising_Set_Terminated)

From this analysis I conclude that we don't need to validate the advertiser flags when processing these events. That is, remove the check if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)

Is there something I've misunderstood or overlooked?

@Thalley
Copy link
Collaborator

Thalley commented May 6, 2021

@Thalley , I have thought a bit more about the race conditions you have mentioned. I'm not convinced there are race conditions that need to be handled. LE_Set_Advertising_Enable is handled synchronously,
It may succeed or fail if it is issued before the LE_Connection_Complete event is fetched, and will fail if it is fetched afterwards. The controller will either send both the events LE_Connection_Complete and LE_Advertising_Set_Terminated or none of them. That will prevent the host and controller from ending up in an out-of-sync state.

Let me try to illustrate this by providing some examples:

Case 1: Adv is stopped with success:

  1. HCI_LE_Set_Advertising_Enable (disable) -> Success
  2. _evt_get() -> No connection complete events

Case 2: Adv is stopped but fails because the controller has a pending event:

  1. A connection is established, an event is prepared.
  2. HCI_LE_Set_Advertising_Enable (disable) -> Disallowed
  3. bt_recv(LE_Connection_Complete)
  4. bt_recv(LE_Advertising_Set_Terminated)

Case 3: Adv is stopped after the enhanced connection complete event is processed:

  1. bt_recv(LE_Connection_Complete)
  2. HCI_LE_Set_Advertising_Enable (disable) -> Disallowed. The controller connection complete
    event has already been fetched from the controller.
  3. bt_recv(LE_Advertising_Set_Terminated)

Furthermore, when restarting the advertiser will not result in race conditions as the flag BT_ADV_ENABLED is cleared when processing the LE_Advertising_Set_Terminated event.

Case 4: Advertiser is restared after fetching both events:

  1. bt_le_adv_start()
  2. bt_recv(LE_Connection_Complete)
  3. bt_recv(LE_Advertising_Set_Terminated)
  4. bt_le_adv_start(). No issue. All previous state has been cleaned up.

Case 5: Advertiser is restarted after fetching the first event:

  1. bt_le_adv_start()
  2. bt_recv(LE_Connection_Complete)
  3. bt_le_adv_start() -> fails because the flag BT_ADV_ENABLED is not yet cleared.
  4. bt_recv(LE_Advertising_Set_Terminated)

From this analysis I conclude that we don't need to validate the advertiser flags when processing these events. That is, remove the check if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)

Is there something I've misunderstood or overlooked?

You might a good point, and I think you are right that it should work. The sentence

The controller will either send both the events LE_Connection_Complete and LE_Advertising_Set_Terminated or none of them.
Makes sure of that. Thanks for checking! :)

Copy link
Collaborator

@Thalley Thalley left a comment

Choose a reason for hiding this comment

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

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: API Changes to public APIs area: Bluetooth Host area: Bluetooth area: Tests Issues related to a particular existing or missing test
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants