Skip to content

Commit

Permalink
lib: nrf_modem_lib: lte_connectivity: Add CEREG listening
Browse files Browse the repository at this point in the history
lte_connectivity now monitors CEREG notifications to more accurately
determine whether connectivity is available.

Includes a configurable timeout controlling how long the device can be
without a serving cell before serving cell is considered lost.

Signed-off-by: Georges Oates_Larsen <georges.larsen@nordicsemi.no>
  • Loading branch information
glarsennordic committed Jun 22, 2023
1 parent a27675a commit 8d61db6
Show file tree
Hide file tree
Showing 4 changed files with 518 additions and 153 deletions.
259 changes: 199 additions & 60 deletions lib/nrf_modem_lib/lte_connectivity/lte_connectivity.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,22 @@ BUILD_ASSERT((CONFIG_NET_CONNECTION_MANAGER_STACK_SIZE >= 1024),

/* Forward declarations */
static void connection_timeout_work_fn(struct k_work *work);
static void connection_timeout_schedule(void);

int lte_connectivity_disconnect(struct conn_mgr_conn_binding *const if_conn);


/* Delayable work used to handle LTE connection timeouts. */
static K_WORK_DELAYABLE_DEFINE(connection_timeout_work, connection_timeout_work_fn);

/* Local reference to the network interface the l2 connection layer is bound to. */
static struct net_if *iface_bound;

/* Variable used to specify behavior when the network interface is brought down. */
static uint8_t net_if_down_options = LTE_CONNECTIVITY_IF_DOWN_MODEM_SHUTDOWN;
/* Container and protection mutex for internal state of the connectivity binding. */
K_MUTEX_DEFINE(internal_state_lock);
static struct lte_connectivity_state internal_state = {
.if_down_setting = LTE_CONNECTIVITY_IF_DOWN_MODEM_SHUTDOWN
};

/* Local functions */

Expand All @@ -64,6 +70,99 @@ void nrf_modem_fault_handler(struct nrf_modem_fault_info *fault_info)
fatal_error_notify_and_disconnect();
}

static void become_active(void)
{
LOG_DBG("Becoming active");
net_if_dormant_off(iface_bound);
k_work_cancel_delayable(&connection_timeout_work);
}

static void become_dormant(void)
{
int ret;

LOG_DBG("Becoming dormant");
net_if_dormant_on(iface_bound);

/* Return immediately after removing IP addresses in the case where PDN has been
* deactivated due to the modem being shutdown.
*/
if (!nrf_modem_is_initialized()) {
return;
}

if (conn_mgr_if_get_flag(iface_bound, CONN_MGR_IF_PERSISTENT)) {
/* If persistence is enabled, don't deactivate LTE. Let the modem try to
* re-establish the connection. Schedule a connection timeout work that
* deactivates LTE if the modem is not able to reconnect before the set timeout.
*/
connection_timeout_schedule();
} else {
/* If persistence is disabled, LTE is deactivated upon a lost connection.
* Re-establishment is reliant on the application calling conn_mgr_if_connect().
*/
ret = lte_connectivity_disconnect(NULL);
if (ret) {
LOG_ERR("lte_lc_func_mode_set, error: %d", ret);
net_mgmt_event_notify(NET_EVENT_CONN_IF_FATAL_ERROR, iface_bound);
}
}
}

/* Updates the internal state as instructed and checks if connectivity was gained or lost.
* Not thread safe.
*/
static void update_connectivity(bool has_pdn, bool has_cell)
{
bool had_connectivity = internal_state.has_pdn && internal_state.has_cell;
bool has_connectivity = has_pdn && has_cell;

internal_state.has_pdn = has_pdn;
internal_state.has_cell = has_cell;

if (had_connectivity != has_connectivity) {
if (has_connectivity) {
become_active();
} else {
become_dormant();
}
}
}

static void update_has_pdn(bool has_pdn)
{
(void)k_mutex_lock(&internal_state_lock, K_FOREVER);

if (has_pdn != internal_state.has_pdn) {
if (has_pdn) {
LOG_DBG("Gained PDN bearer");
} else {
LOG_DBG("Lost PDN bearer");
}

update_connectivity(has_pdn, internal_state.has_cell);
}

(void)k_mutex_unlock(&internal_state_lock);
}

static void update_has_cell(bool has_cell)
{
(void)k_mutex_lock(&internal_state_lock, K_FOREVER);

if (has_cell != internal_state.has_cell) {
if (has_cell) {
LOG_DBG("Gained serving cell");
} else {
LOG_DBG("Lost serving cell");
}

update_connectivity(internal_state.has_pdn, has_cell);
}

(void)k_mutex_unlock(&internal_state_lock);
}

/* Work handler that deactivates LTE. */
static void connection_timeout_work_fn(struct k_work *work)
{
Expand Down Expand Up @@ -154,9 +253,7 @@ static void on_pdn_activated(void)
return;
}

net_if_dormant_off(iface_bound);

k_work_cancel_delayable(&connection_timeout_work);
update_has_pdn(true);
}

static void on_pdn_deactivated(void)
Expand All @@ -177,32 +274,7 @@ static void on_pdn_deactivated(void)
return;
}

net_if_dormant_on(iface_bound);

/* Return immediately after removing IP addresses in the case where PDN has been
* deactivated due to the modem being shutdown.
*/
if (!nrf_modem_is_initialized()) {
return;
}

if (conn_mgr_if_get_flag(iface_bound, CONN_MGR_IF_PERSISTENT)) {
/* If persistence is enabled, don't deactivate LTE. Let the modem try to
* re-establish the connection. Schedule a connection timeout work that
* deactivate LTE if the modem is not able to reconnect before the set timeout.
*/
connection_timeout_schedule();
} else {
/* If persistence is disabled, LTE is deactivated upon a lost connection.
* Re-establishment is reliant on the application calling conn_mgr_if_connect().
*/
ret = lte_connectivity_disconnect(NULL);
if (ret) {
LOG_ERR("lte_lc_func_mode_set, error: %d", ret);
net_mgmt_event_notify(NET_EVENT_CONN_IF_FATAL_ERROR, iface_bound);
return;
}
}
update_has_pdn(false);
}

static void on_pdn_ipv6_up(void)
Expand Down Expand Up @@ -263,17 +335,45 @@ static void pdn_event_handler(uint8_t cid, enum pdn_event event, int reason)
}
}

static void lte_reg_handler(const struct lte_lc_evt *const evt)
{
if (evt->type != LTE_LC_EVT_NW_REG_STATUS) {
return;
}

switch (evt->nw_reg_status) {
case LTE_LC_NW_REG_REGISTERED_HOME:
__fallthrough;
case LTE_LC_NW_REG_REGISTERED_ROAMING:
/* Mark serving cell as available. */
LOG_DBG("Registered to serving cell");
update_has_cell(true);
break;
case LTE_LC_NW_REG_SEARCHING:
/* Searching for a new cell, do not consider this cell loss unless it
* fails (which will generate a new LTE_LC_EVT_NW_REG_STATUS event with
* an unregistered status).
*/
break;
default:
LOG_DBG("Not registered to serving cell");
/* Mark the serving cell as lost. */
update_has_cell(false);
break;
}
}

/* Public APIs */
void lte_connectivity_init(struct conn_mgr_conn_binding *if_conn)
{
int ret;
int timeout = CONFIG_LTE_CONNECTIVITY_CONNECT_TIMEOUT_SECONDS;
int persistent = IS_ENABLED(CONFIG_LTE_CONNECTIVITY_CONNECTION_PERSISTENCE);

net_if_dormant_on(if_conn->iface);

/* Default auto features off. */
/* Apply requested flag overrides */
if (!IS_ENABLED(CONFIG_LTE_CONNECTIVITY_AUTO_CONNECT)) {

ret = conn_mgr_if_set_flag(if_conn->iface, CONN_MGR_IF_NO_AUTO_CONNECT, true);
if (ret) {
LOG_ERR("conn_mgr_if_set_flag, error: %d", ret);
Expand All @@ -291,9 +391,17 @@ void lte_connectivity_init(struct conn_mgr_conn_binding *if_conn)
}
}

/* Set default values for connection persistence and timeout */
if (IS_ENABLED(CONFIG_LTE_CONNECTIVITY_CONNECTION_PERSISTENCE)) {
ret = conn_mgr_if_set_flag(if_conn->iface, CONN_MGR_IF_PERSISTENT, true);
if (ret) {
LOG_ERR("conn_mgr_if_set_flag, error: %d", ret);
net_mgmt_event_notify(NET_EVENT_CONN_IF_FATAL_ERROR, if_conn->iface);
return;
}
}

/* Set default values for timeouts */
if_conn->timeout = (timeout > CONN_MGR_IF_NO_TIMEOUT) ? timeout : CONN_MGR_IF_NO_TIMEOUT;
if_conn->flags |= (persistent == 1) ? BIT(CONN_MGR_IF_PERSISTENT) : 0;

if (if_conn->timeout > CONN_MGR_IF_NO_TIMEOUT) {
LOG_DBG("Connection timeout is enabled and set to %d seconds", if_conn->timeout);
Expand All @@ -312,6 +420,9 @@ void lte_connectivity_init(struct conn_mgr_conn_binding *if_conn)
return;
}

/* Register handler for registration status notifications */
lte_lc_register_handler(lte_reg_handler);

/* Keep local reference to the network interface that the connectivity layer is bound to. */
iface_bound = if_conn->iface;

Expand Down Expand Up @@ -346,13 +457,21 @@ int lte_connectivity_disable(void)
{
int ret;

if (net_if_down_options == LTE_CONNECTIVITY_IF_DOWN_MODEM_SHUTDOWN) {
enum lte_connectivity_if_down_options if_down_setting;

(void)k_mutex_lock(&internal_state_lock, K_FOREVER);

if_down_setting = internal_state.if_down_setting;

(void)k_mutex_unlock(&internal_state_lock);

if (if_down_setting == LTE_CONNECTIVITY_IF_DOWN_MODEM_SHUTDOWN) {
ret = nrf_modem_lib_shutdown();
if (ret) {
LOG_ERR("nrf_modem_lib_shutdown, retval: %d", ret);
return ret;
}
} else if (net_if_down_options == LTE_CONNECTIVITY_IF_DOWN_LTE_DISCONNECT) {
} else if (if_down_setting == LTE_CONNECTIVITY_IF_DOWN_LTE_DISCONNECT) {
ret = lte_connectivity_disconnect(NULL);
if (ret) {
LOG_ERR("lte_connectivity_disconnect, retval: %d", ret);
Expand Down Expand Up @@ -408,29 +527,38 @@ int lte_connectivity_options_set(struct conn_mgr_conn_binding *const if_conn, in
{
ARG_UNUSED(name);

uint8_t *temp;
switch (name) {
case LTE_CONNECTIVITY_IF_DOWN:
{
uint8_t if_down_setting;

if (name != LTE_CONNECTIVITY_IF_DOWN) {
return -ENOPROTOOPT;
}
if (length > sizeof(if_down_setting)) {
return -ENOBUFS;
}

if (length > sizeof(uint8_t)) {
return -ENOBUFS;
}
if_down_setting = *((uint8_t *)value);

temp = (uint8_t *)value;
net_if_down_options = *temp;
(void)k_mutex_lock(&internal_state_lock, K_FOREVER);

switch (net_if_down_options) {
case LTE_CONNECTIVITY_IF_DOWN_MODEM_SHUTDOWN:
LOG_DBG("Shutdown modem when the network interface is brought down");
break;
case LTE_CONNECTIVITY_IF_DOWN_LTE_DISCONNECT:
LOG_DBG("Disconnected from LTE when the network interface is brought down");
internal_state.if_down_setting = if_down_setting;

(void)k_mutex_unlock(&internal_state_lock);

switch (if_down_setting) {
case LTE_CONNECTIVITY_IF_DOWN_MODEM_SHUTDOWN:
LOG_DBG("Shutdown modem when the network interface is brought down");
break;
case LTE_CONNECTIVITY_IF_DOWN_LTE_DISCONNECT:
LOG_DBG("Disconnected from LTE when the network interface is brought down");
break;
default:
LOG_ERR("Unsupported option");
return -EBADF;
}
break;
}
default:
LOG_ERR("Unsupported option");
return -EBADF;
return -ENOPROTOOPT;
}

return 0;
Expand All @@ -439,16 +567,27 @@ int lte_connectivity_options_set(struct conn_mgr_conn_binding *const if_conn, in
int lte_connectivity_options_get(struct conn_mgr_conn_binding *const if_conn, int name, void *value,
size_t *length)
{
ARG_UNUSED(name);
switch (name) {
case LTE_CONNECTIVITY_IF_DOWN:
{
uint8_t *if_down_setting_out = (uint8_t *) value;

uint8_t *temp = (uint8_t *)value;
if (*length < sizeof(*if_down_setting_out)) {
return -ENOBUFS;
}

*length = sizeof(*if_down_setting_out);

(void)k_mutex_lock(&internal_state_lock, K_FOREVER);

*if_down_setting_out = (uint8_t) internal_state.if_down_setting;

if (name != LTE_CONNECTIVITY_IF_DOWN) {
(void)k_mutex_unlock(&internal_state_lock);
break;
}
default:
return -ENOPROTOOPT;
}

*temp = net_if_down_options;
*length = sizeof(net_if_down_options);

return 0;
}
12 changes: 12 additions & 0 deletions lib/nrf_modem_lib/lte_connectivity/lte_connectivity.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ enum lte_connectivity_options {
LTE_CONNECTIVITY_IF_DOWN = 1,
};


struct lte_connectivity_state {
/* Tracks the requested behavior when the network interface is brought down. */
enum lte_connectivity_if_down_options if_down_setting;

/* Tracks whether a PDN bearer is currently active. */
bool has_pdn;

/* Tracks whether a serving cell is currently or was recently available. */
bool has_cell;
};

/** @brief Macro used to define a private Connection Manager connectivity context type.
* Required but not implemented.
*/
Expand Down
3 changes: 3 additions & 0 deletions tests/lib/nrf_modem_lib/lte_connectivity/prj.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ CONFIG_NET_TEST=y
CONFIG_NET_CONNECTION_MANAGER=y
CONFIG_NET_CONNECTION_MANAGER_STACK_SIZE=1024

# Disable auto-down to make test-case expectations simpler
CONFIG_NET_CONNECTION_MANAGER_AUTO_IF_DOWN=n

# Disable DAD
CONFIG_NET_IPV6_NBR_CACHE=n
CONFIG_NET_IPV6_MLD=n
Expand Down
Loading

0 comments on commit 8d61db6

Please sign in to comment.