Bluetooth: Mesh: Refactor proxy adv

Refactors proxy advertising implementation to allow fair sharing of
advertising resources between all subnets.

In the new implementation, each subnet is agnostic to any other subnet
that might have active proxy advertisement work. When proxy
advertisement is triggered, the implementation will first check how
many subnets that has active work. If there are more than one active
subnet, a maximum timeslot will be calculated to ensure that all active
subnets get access to the medium. The implementation will then poll
the next eligible subnet for a proxy advertising request. If the
duration of this request exceeds the maximum timeslot, the duration
for the next advertisement will be set to the maximum value.

If a proxy advertisement for a subnet is interrupted by other
advertising activity, the implementation will now ensure that the
interrupted proxy adv continues from the point where it was
interrupted so that the subnet gets to utilize the entire allocated
timeslot.

This PR also alters the priv_proxy_net_id_multi Bsim test to align
with the refactored proxy advertising scheme.

Signed-off-by: Anders Storrø <anders.storro@nordicsemi.no>
This commit is contained in:
Anders Storrø 2023-12-06 13:59:39 +01:00 committed by Carles Cufí
commit 6c67ab3a63
2 changed files with 191 additions and 153 deletions

View file

@ -625,7 +625,7 @@ static int net_id_adv(struct bt_mesh_subnet *sub, int32_t duration)
return 0;
}
static bool advertise_subnet(struct bt_mesh_subnet *sub)
static bool is_sub_proxy_active(struct bt_mesh_subnet *sub)
{
if (sub->net_idx == BT_MESH_KEY_UNUSED) {
return false;
@ -633,44 +633,18 @@ static bool advertise_subnet(struct bt_mesh_subnet *sub)
return (sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING ||
#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV)
sub->solicited ||
(bt_mesh_od_priv_proxy_get() > 0 && sub->solicited) ||
#endif
bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED ||
bt_mesh_priv_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED);
}
static struct bt_mesh_subnet *next_sub(void)
static bool active_proxy_sub_cnt_cb(struct bt_mesh_subnet *sub, void *cb_data)
{
struct bt_mesh_subnet *sub = NULL;
int *cnt = cb_data;
if (!beacon_sub) {
beacon_sub = bt_mesh_subnet_next(NULL);
if (!beacon_sub) {
/* No valid subnets */
return NULL;
}
}
sub = beacon_sub;
do {
if (advertise_subnet(sub)) {
beacon_sub = sub;
return sub;
}
sub = bt_mesh_subnet_next(sub);
} while (sub != beacon_sub);
/* No subnets to advertise on */
return NULL;
}
static bool sub_count_cb(struct bt_mesh_subnet *sub, void *cb_data)
{
int *count = cb_data;
if (advertise_subnet(sub)) {
(*count)++;
if (is_sub_proxy_active(sub)) {
(*cnt)++;
}
/* Don't stop until we've visited all subnets.
@ -679,155 +653,225 @@ static bool sub_count_cb(struct bt_mesh_subnet *sub, void *cb_data)
return false;
}
static int sub_count(void)
static int active_proxy_sub_cnt_get(void)
{
int count = 0;
int cnt = 0;
(void)bt_mesh_subnet_find(sub_count_cb, &count);
(void)bt_mesh_subnet_find(active_proxy_sub_cnt_cb, &cnt);
return count;
return cnt;
}
static void proxy_adv_timeout_eval(struct bt_mesh_subnet *sub)
{
int32_t time_passed;
if (sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING) {
time_passed = k_uptime_get_32() - sub->node_id_start;
if (time_passed > (NODE_ID_TIMEOUT - MSEC_PER_SEC)) {
bt_mesh_proxy_identity_stop(sub);
LOG_DBG("Node ID stopped for subnet %d after %dms", sub->net_idx,
time_passed);
}
}
#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV)
static void gatt_proxy_solicited(struct bt_mesh_subnet *sub)
{
int64_t now = k_uptime_get();
int64_t timeout = 0;
int32_t remaining;
if (sub->priv_net_id_sent > 0) {
timeout = sub->priv_net_id_sent +
MSEC_PER_SEC * (int64_t) bt_mesh_od_priv_proxy_get();
remaining = MIN(timeout - now, INT32_MAX);
} else {
remaining = MSEC_PER_SEC * bt_mesh_od_priv_proxy_get();
}
if ((timeout > 0 && now > timeout) || (remaining / MSEC_PER_SEC < 1)) {
LOG_DBG("Advertising Private Network ID timed out "
"after solicitation");
if (bt_mesh_od_priv_proxy_get() > 0 && sub->solicited && sub->priv_net_id_sent) {
time_passed = k_uptime_get_32() - sub->priv_net_id_sent;
if (time_passed > ((MSEC_PER_SEC * bt_mesh_od_priv_proxy_get()) - MSEC_PER_SEC)) {
sub->priv_net_id_sent = 0;
sub->solicited = false;
} else {
LOG_DBG("Advertising Private Network ID for %ds"
"(%d remaining)",
bt_mesh_od_priv_proxy_get(),
remaining / MSEC_PER_SEC);
priv_net_id_adv(sub, remaining);
if (!sub->priv_net_id_sent) {
sub->priv_net_id_sent = now;
}
LOG_DBG("Private Network ID stopped for subnet %d after %dms on "
"solicitation",
sub->net_idx, time_passed);
}
}
#endif
}
static int gatt_proxy_advertise(struct bt_mesh_subnet *sub)
enum proxy_adv_evt {
NET_ID,
PRIV_NET_ID,
NODE_ID,
PRIV_NODE_ID,
OD_PRIV_NET_ID,
};
struct proxy_adv_request {
int32_t duration;
enum proxy_adv_evt evt;
};
static bool proxy_adv_request_get(struct bt_mesh_subnet *sub, struct proxy_adv_request *request)
{
int32_t remaining = SYS_FOREVER_MS;
int subnet_count;
int err = -EBUSY;
bool planned = false;
if (!sub) {
return false;
}
if (sub->net_idx == BT_MESH_KEY_UNUSED) {
return false;
}
#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV)
if (bt_mesh_od_priv_proxy_get() > 0 && sub->solicited) {
int32_t timeout = MSEC_PER_SEC * (int32_t)bt_mesh_od_priv_proxy_get();
request->evt = OD_PRIV_NET_ID;
request->duration = !sub->priv_net_id_sent
? timeout
: timeout - (k_uptime_get_32() - sub->priv_net_id_sent);
return true;
}
#endif
if (sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING) {
request->duration = NODE_ID_TIMEOUT - (k_uptime_get_32() - sub->node_id_start);
request->evt =
#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
sub->priv_beacon_ctx.node_id ? PRIV_NODE_ID :
#endif
NODE_ID;
return true;
}
if (bt_mesh_priv_gatt_proxy_get() == BT_MESH_FEATURE_ENABLED) {
request->evt = PRIV_NET_ID;
request->duration = PROXY_RANDOM_UPDATE_INTERVAL;
return true;
}
if (bt_mesh_gatt_proxy_get() == BT_MESH_FEATURE_ENABLED) {
request->evt = NET_ID;
request->duration = SYS_FOREVER_MS;
return true;
}
return false;
}
static struct bt_mesh_subnet *adv_sub_get_next(struct bt_mesh_subnet *sub_start,
struct proxy_adv_request *request)
{
struct bt_mesh_subnet *sub_temp = sub_start;
do {
if (proxy_adv_request_get(sub_temp, request)) {
return sub_temp;
}
sub_temp = bt_mesh_subnet_next(sub_temp);
} while (sub_temp != sub_start);
return NULL;
}
static struct {
int32_t start;
struct bt_mesh_subnet *sub;
struct proxy_adv_request request;
} sub_adv;
static int gatt_proxy_advertise(void)
{
int err;
int32_t max_adv_duration;
int cnt;
struct bt_mesh_subnet *sub;
struct proxy_adv_request request;
LOG_DBG("");
/* Close proxy activity that has timed out on all subnets */
bt_mesh_subnet_foreach(proxy_adv_timeout_eval);
if (!bt_mesh_proxy_has_avail_conn()) {
LOG_DBG("Connectable advertising deferred (max connections)");
return -ENOMEM;
}
sub = beacon_sub ? beacon_sub : bt_mesh_subnet_next(beacon_sub);
if (!sub) {
LOG_WRN("No subnets to advertise on");
cnt = active_proxy_sub_cnt_get();
if (!cnt) {
LOG_DBG("No subnets to advertise proxy on");
return -ENOENT;
}
subnet_count = sub_count();
LOG_DBG("sub_count %u", subnet_count);
if (subnet_count > 1) {
int32_t max_timeout;
} else if (cnt > 1) {
/** There is more than one subnet that requires proxy adv,
* and the adv resources must be shared.
*/
/* We use NODE_ID_TIMEOUT as a starting point since it may
* be less than 60 seconds. Divide this period into at least
* 6 slices, but make sure that a slice is at least one
* 6 slices, but make sure that a slice is more than one
* second long (to avoid excessive rotation).
*/
max_timeout = NODE_ID_TIMEOUT / MAX(subnet_count, 6);
max_timeout = MAX(max_timeout, 1 * MSEC_PER_SEC);
max_adv_duration = NODE_ID_TIMEOUT / MAX(cnt, 6);
max_adv_duration = MAX(max_adv_duration, MSEC_PER_SEC + 20);
if (remaining > max_timeout || remaining == SYS_FOREVER_MS) {
remaining = max_timeout;
/* Check if the previous subnet finished its allocated timeslot */
if ((sub_adv.request.duration != SYS_FOREVER_MS) &&
proxy_adv_request_get(sub_adv.sub, &request) &&
(sub_adv.request.evt == request.evt)) {
int32_t time_passed = k_uptime_get_32() - sub_adv.start;
if (time_passed < sub_adv.request.duration &&
((sub_adv.request.duration - time_passed) >= MSEC_PER_SEC)) {
sub = sub_adv.sub;
request.duration = sub_adv.request.duration - time_passed;
goto end;
}
}
}
for (int i = 0; i < subnet_count; i++) {
if (sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING) {
uint32_t active = k_uptime_get_32() - sub->node_id_start;
bool priv_node_id = false;
if (active < NODE_ID_TIMEOUT) {
remaining = MIN(remaining, NODE_ID_TIMEOUT - active);
LOG_DBG("Node ID active for %u ms, %d ms remaining",
active, remaining);
#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
priv_node_id = sub->priv_beacon_ctx.node_id;
#endif
if (priv_node_id) {
err = priv_node_id_adv(sub, remaining);
} else {
err = node_id_adv(sub, remaining);
}
planned = true;
} else {
bt_mesh_proxy_identity_stop(sub);
LOG_DBG("Node ID stopped");
sub = adv_sub_get_next(bt_mesh_subnet_next(sub_adv.sub), &request);
if (!sub) {
LOG_ERR("Could not find subnet to advertise");
return -ENOENT;
}
end:
if (cnt > 1) {
request.duration = (request.duration == SYS_FOREVER_MS)
? max_adv_duration
: MIN(request.duration, max_adv_duration);
}
/* MshPRTv1.1: section 7.2.2.2.1:
* "A node that does not support the Proxy feature or
* has the GATT Proxy state disabled shall not advertise with Network ID."
*/
if (sub->node_id == BT_MESH_NODE_IDENTITY_STOPPED) {
if (IS_ENABLED(CONFIG_BT_MESH_PRIV_BEACONS) &&
(bt_mesh_priv_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED)) {
/* MshPRTv1.1: 7.2.2.2.4: The Random
* field should be updated every 10 minutes. Limit advertising to
* 10 minutes to ensure regeneration of a new random value at least
* that often.
*/
if (remaining == SYS_FOREVER_MS ||
remaining > PROXY_RANDOM_UPDATE_INTERVAL) {
remaining = PROXY_RANDOM_UPDATE_INTERVAL;
}
err = priv_net_id_adv(sub, remaining);
planned = true;
} else if (bt_mesh_gatt_proxy_get() == BT_MESH_FEATURE_ENABLED) {
err = net_id_adv(sub, remaining);
planned = true;
}
/* Save current state for next iteration */
sub_adv.start = k_uptime_get_32();
sub_adv.sub = sub;
sub_adv.request = request;
switch (request.evt) {
case NET_ID:
err = net_id_adv(sub, request.duration);
break;
#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV)
else if (bt_mesh_od_priv_proxy_get() > 0 &&
sub->solicited) {
gatt_proxy_solicited(sub);
case OD_PRIV_NET_ID:
if (!sub->priv_net_id_sent) {
sub->priv_net_id_sent = k_uptime_get();
}
/* Fall through */
#endif
case PRIV_NET_ID:
err = priv_net_id_adv(sub, request.duration);
break;
case NODE_ID:
err = node_id_adv(sub, request.duration);
break;
case PRIV_NODE_ID:
err = priv_node_id_adv(sub, request.duration);
break;
default:
LOG_ERR("Unexpected proxy adv evt: %d", request.evt);
return -ENODEV;
}
beacon_sub = bt_mesh_subnet_next(sub);
if (planned) {
LOG_DBG("Advertising %d ms for net_idx 0x%04x", remaining, sub->net_idx);
if (err) {
LOG_ERR("Advertising proxy failed (err: %d)", err);
return err;
}
sub = beacon_sub;
}
return 0;
LOG_DBG("Advertising %d ms for net_idx 0x%04x", request.duration, sub->net_idx);
return err;
}
static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt)
@ -1152,7 +1196,7 @@ int bt_mesh_proxy_adv_start(void)
return -ENOTSUP;
}
return gatt_proxy_advertise(next_sub());
return gatt_proxy_advertise();
}
BT_CONN_CB_DEFINE(conn_callbacks) = {

View file

@ -1763,12 +1763,6 @@ static void test_tx_priv_multi_net_id(void)
{
tx_priv_common_init(PP_MULT_NET_ID_WAIT_TIME);
/* TODO: This should be removed as soon as
* SNB/proxy service advertising issue has
* been resolved.
*/
bt_mesh_beacon_set(false);
/* Add second network */
ASSERT_OK_MSG(bt_mesh_subnet_add(TEST_NET_IDX2, test_net_key_secondary),
"Failed to add second subnet");
@ -1819,8 +1813,8 @@ static void test_rx_priv_multi_net_id(void)
/* Verify last Net ID adv result */
ASSERT_IN_RANGE(k_uptime_get() - net_ctx[old_idx].start,
MAX_TIMEOUT - 1000, MAX_TIMEOUT);
ASSERT_IN_RANGE(net_ctx[old_idx].recv_cnt, 9, 10);
MAX_TIMEOUT - 1000, MAX_TIMEOUT + 1000);
ASSERT_IN_RANGE(net_ctx[old_idx].recv_cnt, 9, 12);
net_ctx[old_idx].recv_cnt = 0;
old_idx = i;