/* port/common/link CTRL protocol FSM / G.964 14.4 / G.965 16.3 */ /* (C) 2021 by Harald Welte * (C) 2022 by Andreas Eversberg * * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ /***********************************************************************/ /* internal data structures */ /***********************************************************************/ #include #include #include #include #include #include #include #include "v5x_internal.h" #include "v5x_protocol.h" #include "v5x_le_ctrl_fsm.h" #include "v5x_le_port_fsm.h" #include "v52_le_lcp_fsm.h" #include "v5x_le_management.h" #include "logging.h" #define S(x) (1 << (x)) #define TIMEOUT 1 // T01 T02 TLCT01 /* uncomment to test lost TX messages at the first transmission */ //#define TEST_TX_FAILURE /***********************************************************************/ /* state names, event names, primitives, ... */ /***********************************************************************/ enum v5x_ctrl_fsm_state { V5X_CTRL_ST_OUT_OF_SERVICE = 0, V5X_CTRL_ST_IN_SERVICE, V5X_CTRL_ST_AWAIT_ACK, }; enum v5x_ctrl_fsm_event { V5X_CTRL_EV_MDU_START_TRAFFIC, V5X_CTRL_EV_MDU_STOP_TRAFFIC, V5X_CTRL_EV_MDU_CTRL, V5X_CTRL_EV_RX_CONTROL, V5X_CTRL_EV_RX_CONTROL_ACK, }; static const struct value_string v5x_ctrl_fsm_event_names[] = { { V5X_CTRL_EV_MDU_START_TRAFFIC, "MDU-start_traffic" }, { V5X_CTRL_EV_MDU_STOP_TRAFFIC, "MDU-stop_traffic" }, { V5X_CTRL_EV_MDU_CTRL, "MDU-CTRL" }, { V5X_CTRL_EV_RX_CONTROL, "CONTROL" }, { V5X_CTRL_EV_RX_CONTROL_ACK, "CONTROL_ACK" }, { 0, NULL } }; static void v5x_ctrl_mdu(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp) { struct v5x_ctrl_proto *ctrl = fi->priv; struct v5x_interface *v5if = ctrl->priv; struct v5x_user_port *v5up = ctrl->priv; struct v5x_link *v5l = ctrl->priv; enum v5x_ctrl_func_id cfi; uint8_t variant; uint8_t rej_cause; uint32_t interface_id; enum v5x_ctrl_func_id cfe; uint8_t perf_grading; enum v52_link_ctrl_func lcf; switch (ctrl->type) { case V5X_CTRL_TYPE_COMMON: cfi = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ID) & 0x7f; variant = 0; rej_cause = 0; interface_id = 0; LOGPFSML(fi, LOGL_DEBUG, "MDU-CTRL received.\n"); if (TLVP_PRESENT(tp, V5X_CTRL_IEI_VARIANT)) variant = *TLVP_VAL(tp, V5X_CTRL_IEI_VARIANT) & 0x7f; if (TLVP_PRESENT(tp, V5X_CTRL_IEI_REJECTION_CAUSE)) rej_cause = *TLVP_VAL(tp, V5X_CTRL_IEI_REJECTION_CAUSE) & 0x0f; if (TLVP_PRESENT(tp, V5X_CTRL_IEI_INTERFACE_ID)) interface_id = osmo_load32be_ext_2(TLVP_VAL(tp, V5X_CTRL_IEI_INTERFACE_ID), 3); v5x_le_common_ctrl_rcv(v5if, cfi, variant, rej_cause, interface_id); break; case V5X_CTRL_TYPE_PORT: cfe = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ELEMENT) & 0x7f; perf_grading = 0; LOGPFSML(fi, LOGL_DEBUG, "FE received for address %d.\n", v5up->nr); if (TLVP_PRESENT(tp, V5X_CTRL_IEI_PERFORMANCE_GRADING)) perf_grading = *TLVP_VAL(tp, V5X_CTRL_IEI_PERFORMANCE_GRADING) & 0x0f; switch (v5up->type) { case V5X_USER_TYPE_ISDN: v5x_le_port_isdn_fe_rcv(v5up, cfe, perf_grading); break; case V5X_USER_TYPE_PSTN: v5x_le_port_pstn_fe_rcv(v5up, cfe); break; } break; case V5X_CTRL_TYPE_LINK: lcf = *TLVP_VAL(tp, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION) & 0x7f; LOGPFSML(fi, LOGL_DEBUG, "FE received for link ID %d.\n", v5l->id); v52_le_lcp_fe_rcv(v5l, lcf); break; } } /* display MDU-error_indication */ static void v5x_mdu_error(struct osmo_fsm_inst *fi, const char *error) { LOGPFSML(fi, LOGL_NOTICE, "MDU-error_indication: %s\n", error); } /* send control towards lower (DL) layer */ static void v5x_ctrl_send(struct v5x_ctrl_proto *ctrl, struct msgb *msg) { struct osmo_fsm_inst *fi = ctrl->fi; struct v5x_interface *v5if = ctrl->priv; struct v5x_user_port *v5up = ctrl->priv; struct v5x_link *v5l = ctrl->priv; switch (fi->state) { case V5X_CTRL_ST_IN_SERVICE: LOGPFSML(fi, LOGL_DEBUG, "We are in service, so we send our message now.\n"); /* no message, clone current message and store to be repeated */ ctrl->tx_msg = msgb_copy(msg, NULL); /* go to AWAIT_PENDING_ACK */ osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_AWAIT_ACK, TIMEOUT, 0); /* send message towards DL */ #ifdef TEST_TX_FAILURE msgb_free(msg); #else switch (ctrl->type) { case V5X_CTRL_TYPE_COMMON: v5x_dl_snd(v5if, V5X_DLADDR_CTRL, msg); break; case V5X_CTRL_TYPE_PORT: v5x_dl_snd(v5up->interface, V5X_DLADDR_CTRL, msg); break; case V5X_CTRL_TYPE_LINK: v5x_dl_snd(v5l->interface, V52_DLADDR_LCP, msg); break; } #endif break; case V5X_CTRL_ST_AWAIT_ACK: LOGPFSML(fi, LOGL_DEBUG, "We are waiting for ack, so we queue our message.\n"); /* pending message, save message in queue */ msgb_enqueue(&ctrl->tx_queue, msg); break; } } /* receive acknowledge */ static void v5x_ctrl_ack(struct osmo_fsm_inst *fi, const struct tlv_parsed __attribute__((unused)) *tp) { struct v5x_ctrl_proto *ctrl = fi->priv; struct msgb *msg; LOGPFSML(fi, LOGL_DEBUG, "Received acknowledge, removing pending message, if any.\n"); /* free pending copy of message, if acked before retry */ if (ctrl->tx_msg) { msgb_free(ctrl->tx_msg); ctrl->tx_msg = NULL; } /* go to IN_SERVICE */ osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_IN_SERVICE, 0, 0); /* sending next pending message in queue */ msg = msgb_dequeue(&ctrl->tx_queue); if (msg) { LOGPFSML(fi, LOGL_DEBUG, "Found pending message in queue.\n"); v5x_ctrl_send(ctrl, msg); } } /* stop traffic */ static void v5x_ctrl_stop(struct osmo_fsm_inst *fi) { struct v5x_ctrl_proto *ctrl = fi->priv; struct msgb *msg; /* flush pending messages */ if (ctrl->tx_msg) { msgb_free(ctrl->tx_msg); ctrl->tx_msg = NULL; } while ((msg = msgb_dequeue(&ctrl->tx_queue))) msgb_free(msg); /* go to OUT_OF_SERVICE */ osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_OUT_OF_SERVICE, 0, 0); } /* T01 / T02 */ static int v5x_ctrl_fsm_timer_cb(struct osmo_fsm_inst *fi) { struct v5x_ctrl_proto *ctrl = fi->priv; struct v5x_interface *v5if = ctrl->priv; struct v5x_user_port *v5up = ctrl->priv; struct v5x_link *v5l = ctrl->priv; struct msgb *msg; if (ctrl->tx_msg) { LOGPFSML(fi, LOGL_DEBUG, "Timer fired the first time, resending message.\n"); /* first expiry: repeat CONTROL; re-start T01 */ /* send message towards DL */ osmo_timer_schedule(&fi->timer, TIMEOUT, 0); msg = ctrl->tx_msg; ctrl->tx_msg = NULL; switch (ctrl->type) { case V5X_CTRL_TYPE_COMMON: v5x_dl_snd(v5if, V5X_DLADDR_CTRL, msg); break; case V5X_CTRL_TYPE_PORT: v5x_dl_snd(v5up->interface, V5X_DLADDR_CTRL, msg); break; case V5X_CTRL_TYPE_LINK: v5x_dl_snd(v5l->interface, V52_DLADDR_LCP, msg); break; } } else { LOGPFSML(fi, LOGL_DEBUG, "Timer fired the second time, indicate an error.\n"); /* second expiry: send MDU-error_ind; go to IN_SERVICE */ v5x_mdu_error(fi, "Second timeout while waiting for CONTROL ACK."); osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_IN_SERVICE, 0, 0); /* sending next pending message in queue */ msg = msgb_dequeue(&ctrl->tx_queue); if (msg) { LOGPFSML(fi, LOGL_DEBUG, "Found pending message in queue.\n"); v5x_ctrl_send(ctrl, msg); } } return 0; } /* events in state OUT OF SERVICE */ static void v5x_ctrl_fsm_oos(struct osmo_fsm_inst *fi, uint32_t event, void *data) { switch (event) { case V5X_CTRL_EV_MDU_START_TRAFFIC: /* go to IN_SERVICE */ osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_IN_SERVICE, 0, 0); break; case V5X_CTRL_EV_MDU_STOP_TRAFFIC: v5x_mdu_error(fi, "Got MDU-stop, but traffic not started."); break; case V5X_CTRL_EV_MDU_CTRL: msgb_free(data); v5x_mdu_error(fi, "Got MDU-CTRL, but traffic not started."); break; case V5X_CTRL_EV_RX_CONTROL: v5x_mdu_error(fi, "Received CONTROL, but traffic not started."); break; case V5X_CTRL_EV_RX_CONTROL_ACK: v5x_mdu_error(fi, "Received CONTROL ACK, but traffic not started."); break; default: OSMO_ASSERT(0); } } /* events in state IN SERVICE */ static void v5x_ctrl_fsm_ins(struct osmo_fsm_inst *fi, uint32_t event, void *data) { switch (event) { case V5X_CTRL_EV_MDU_START_TRAFFIC: break; case V5X_CTRL_EV_MDU_STOP_TRAFFIC: v5x_ctrl_stop(fi); break; case V5X_CTRL_EV_MDU_CTRL: v5x_ctrl_send(fi->priv, data); break; case V5X_CTRL_EV_RX_CONTROL: v5x_ctrl_mdu(fi, data); break; default: OSMO_ASSERT(0); } } /* events in state AWAIT PORT/COMMON ACK */ static void v5x_ctrl_fsm_aa(struct osmo_fsm_inst *fi, uint32_t event, void *data) { switch (event) { case V5X_CTRL_EV_MDU_START_TRAFFIC: break; case V5X_CTRL_EV_MDU_STOP_TRAFFIC: v5x_ctrl_stop(fi); break; case V5X_CTRL_EV_MDU_CTRL: v5x_ctrl_send(fi->priv, data); break; case V5X_CTRL_EV_RX_CONTROL: v5x_ctrl_mdu(fi, data); break; case V5X_CTRL_EV_RX_CONTROL_ACK: v5x_ctrl_ack(fi, data); break; default: OSMO_ASSERT(0); } } static const struct osmo_fsm_state v5x_ctrl_fsm_states[] = { [V5X_CTRL_ST_OUT_OF_SERVICE] = { .name = "OUT_OF_SERVICE", .in_event_mask = S(V5X_CTRL_EV_MDU_START_TRAFFIC) | S(V5X_CTRL_EV_MDU_STOP_TRAFFIC) | S(V5X_CTRL_EV_MDU_CTRL) | S(V5X_CTRL_EV_RX_CONTROL) | S(V5X_CTRL_EV_RX_CONTROL_ACK), .out_state_mask = S(V5X_CTRL_ST_IN_SERVICE), .action = v5x_ctrl_fsm_oos, }, [V5X_CTRL_ST_IN_SERVICE] = { .name = "IN_SERVICE", .in_event_mask = S(V5X_CTRL_EV_MDU_START_TRAFFIC) | S(V5X_CTRL_EV_MDU_STOP_TRAFFIC) | S(V5X_CTRL_EV_MDU_CTRL) | S(V5X_CTRL_EV_RX_CONTROL), .out_state_mask = S(V5X_CTRL_ST_OUT_OF_SERVICE) | S(V5X_CTRL_ST_AWAIT_ACK), .action = v5x_ctrl_fsm_ins, }, [V5X_CTRL_ST_AWAIT_ACK] = { .name = "AWAIT_ACK", .in_event_mask = S(V5X_CTRL_EV_MDU_START_TRAFFIC) | S(V5X_CTRL_EV_MDU_STOP_TRAFFIC) | S(V5X_CTRL_EV_MDU_CTRL) | S(V5X_CTRL_EV_RX_CONTROL) | S(V5X_CTRL_EV_RX_CONTROL_ACK), .out_state_mask = S(V5X_CTRL_ST_OUT_OF_SERVICE) | S(V5X_CTRL_ST_IN_SERVICE), .action = v5x_ctrl_fsm_aa, }, }; struct osmo_fsm v5x_ctrl_fsm = { .name = "V5X_CTRL", .states = v5x_ctrl_fsm_states, .num_states = ARRAY_SIZE(v5x_ctrl_fsm_states), .allstate_event_mask = 0, .allstate_action = NULL, .cleanup = NULL, .timer_cb = v5x_ctrl_fsm_timer_cb, .log_subsys = DV5CTRL, .event_names = v5x_ctrl_fsm_event_names, }; void v5x_le_ctrl_init(void) { int rc; rc = osmo_fsm_register(&v5x_ctrl_fsm); OSMO_ASSERT(!rc); LOGP(DV5CTRL, LOGL_NOTICE, "Using V5x control protocol\n"); } struct v5x_ctrl_proto *v5x_le_ctrl_create(enum v5x_ctrl_type type, void *ctx, void *priv, uint16_t nr) { struct v5x_ctrl_proto *ctrl; struct v5x_link *v5l = NULL; struct v5x_interface *v5if = NULL; struct v5x_user_port *v5up = NULL; OSMO_ASSERT(priv); ctrl = talloc_zero(ctx, struct v5x_ctrl_proto); if (!ctrl) return NULL; ctrl->type = type; ctrl->priv = priv; INIT_LLIST_HEAD(&ctrl->tx_queue); ctrl->fi = osmo_fsm_inst_alloc(&v5x_ctrl_fsm, ctrl, ctrl, LOGL_DEBUG, NULL); if (!ctrl->fi) { v5x_le_ctrl_destroy(ctrl); return NULL; } switch (type) { case V5X_CTRL_TYPE_LINK: v5l = priv; osmo_fsm_inst_update_id_f(ctrl->fi, "%s-LINK-L%u", v5x_interface_name(v5l->interface), nr); break; case V5X_CTRL_TYPE_COMMON: v5if = priv; osmo_fsm_inst_update_id_f(ctrl->fi, "%s-COMMON", v5x_interface_name(v5if)); break; case V5X_CTRL_TYPE_PORT: v5up = priv; osmo_fsm_inst_update_id_f(ctrl->fi, "%s-PORT-P%u", v5x_interface_name(v5up->interface), nr); break; } return ctrl; } void v5x_le_ctrl_destroy(struct v5x_ctrl_proto *ctrl) { /* get rid of pending messages */ msgb_queue_free(&ctrl->tx_queue); if (ctrl->tx_msg) msgb_free(ctrl->tx_msg); if (ctrl->fi) osmo_fsm_inst_free(ctrl->fi); talloc_free(ctrl); } /*********************************************************************** * V5 Message encoding / sending ***********************************************************************/ /* G.964 Section 14.4.1.1 / Table 48 */ static struct msgb *v5x_enc_ctrl_port(struct v5x_user_port *v5up, enum v5x_ctrl_func_el cfe, bool is_isdn) { uint8_t cfe_ie = cfe | 0x80; struct v5x_l3_hdr *l3h; struct msgb *msg = msgb_alloc_v5x(); if (!msg) return NULL; l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h)); l3h->pdisc = V5X_CTRL_PDISC; l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, is_isdn)); l3h->msg_type = V5X_CTRL_MSGT_PORT_CTRL; msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie); return msg; } /* G.964 Section 14.4.1.2 / Table 49 */ static struct msgb *v5x_enc_ctrl_port_ack(struct v5x_user_port *v5up, enum v5x_ctrl_func_el cfe, bool is_isdn) { uint8_t cfe_ie = cfe | 0x80; struct v5x_l3_hdr *l3h; struct msgb *msg = msgb_alloc_v5x(); if (!msg) return NULL; l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h)); l3h->pdisc = V5X_CTRL_PDISC; l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, is_isdn)); l3h->msg_type = V5X_CTRL_MSGT_PORT_CTRL_ACK; msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie); return msg; } /* G.964 Section 14.4.1.3 / Table 50 */ static struct msgb *v5x_enc_ctrl_common(enum v5x_ctrl_func_id cfi, uint8_t *variant, uint8_t *rej_cause, uint32_t *interface_id) { uint8_t cfi_ie = cfi | 0x80; struct v5x_l3_hdr *l3h; struct msgb *msg = msgb_alloc_v5x(); if (!msg) return NULL; l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h)); l3h->pdisc = V5X_CTRL_PDISC; l3h->l3_addr = htons(v5x_l3_addr_enc(V5X_DLADDR_CTRL, true)); l3h->msg_type = V5X_CTRL_MSGT_COMMON_CTRL; msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie); if (variant) { /* Conditional: Variant */ uint8_t variant_ie = *variant | 0x80; msgb_tlv_put(msg, V5X_CTRL_IEI_VARIANT, 1, &variant_ie); } if (rej_cause) { /* Conditional: Rejection Cause */ msgb_put_u8(msg, 0xF0 | (*rej_cause & 0x0F)); } if (interface_id) { /* Conditional: Interface Id */ uint8_t iid_ie[3]; osmo_store32be_ext(*interface_id, iid_ie, 3); msgb_tlv_put(msg, V5X_CTRL_IEI_INTERFACE_ID, sizeof(iid_ie), iid_ie); } return msg; } /* G.964 Section 14.4.1.4 / Table 51 */ static struct msgb *v5x_enc_ctrl_common_ack(enum v5x_ctrl_func_id cfi) { uint8_t cfi_ie = cfi | 0x80; struct v5x_l3_hdr *l3h; struct msgb *msg = msgb_alloc_v5x(); if (!msg) return NULL; l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h)); l3h->pdisc = V5X_CTRL_PDISC; l3h->l3_addr = htons(v5x_l3_addr_enc(V5X_DLADDR_CTRL, true)); l3h->msg_type = V5X_CTRL_MSGT_COMMON_CTRL_ACK; msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie); return msg; } /* G.965 Section 16.3.1.1 / Table 19 */ static struct msgb *v52_enc_ctrl_link_ack(struct v5x_link *v5l, enum v52_link_ctrl_func lcf) { uint8_t lcf_ie = lcf | 0x80; struct v5x_l3_hdr *l3h; struct msgb *msg = msgb_alloc_v5x(); if (!msg) return NULL; l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h)); l3h->pdisc = V5X_CTRL_PDISC; l3h->l3_addr = htons(v5l->id); l3h->msg_type = V52_CTRL_MSGT_LCP_LINK_CTRL_ACK; msgb_tlv_put(msg, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION, 1, &lcf_ie); return msg; } /* G.965 Section 16.3.1.2 / Table 20 */ static struct msgb *v52_enc_ctrl_link(struct v5x_link *v5l, enum v52_link_ctrl_func lcf) { uint8_t lcf_ie = lcf | 0x80; struct v5x_l3_hdr *l3h; struct msgb *msg = msgb_alloc_v5x(); if (!msg) return NULL; l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h)); l3h->pdisc = V5X_CTRL_PDISC; l3h->l3_addr = htons(v5l->id); l3h->msg_type = V52_CTRL_MSGT_LCP_LINK_CTRL; msgb_tlv_put(msg, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION, 1, &lcf_ie); return msg; } /*********************************************************************** * V5 Message receiving / decoding ***********************************************************************/ static int v5x_rcv_ctrl_port(struct v5x_user_port *v5up, uint8_t msg_type, const struct tlv_parsed *tp) { enum v5x_ctrl_func_el cfe = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ELEMENT) & 0x7f; switch (msg_type) { case V5X_CTRL_MSGT_PORT_CTRL: /* send ACK to AN */ v5x_dl_snd(v5up->interface, V5X_DLADDR_CTRL, v5x_enc_ctrl_port_ack(v5up, cfe, v5up->type == V5X_USER_TYPE_ISDN)); /* FIXME: send event to FSM */ osmo_fsm_inst_dispatch(v5up->ctrl->fi, V5X_CTRL_EV_RX_CONTROL, (void *)tp); return 0; case V5X_CTRL_MSGT_PORT_CTRL_ACK: osmo_fsm_inst_dispatch(v5up->ctrl->fi, V5X_CTRL_EV_RX_CONTROL_ACK, (void *)tp); default: return -EINVAL; } } static int v5x_rcv_ctrl_common(struct v5x_interface *v5if, uint8_t msg_type, const struct tlv_parsed *tp) { enum v5x_ctrl_func_id cfi = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ID) & 0x7f; switch (msg_type) { case V5X_CTRL_MSGT_COMMON_CTRL: v5x_dl_snd(v5if, V5X_DLADDR_CTRL, v5x_enc_ctrl_common_ack(cfi)); /* send event to FSM */ osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V5X_CTRL_EV_RX_CONTROL, (void *)tp); /* send ACK to AN */ return 0; case V5X_CTRL_MSGT_COMMON_CTRL_ACK: /* send event to FSM */ osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V5X_CTRL_EV_RX_CONTROL_ACK, (void *)tp); return 0; default: return -EINVAL; } } static int v52_rcv_ctrl_link(struct v5x_link *v5l, uint8_t msg_type, const struct tlv_parsed *tp) { enum v52_link_ctrl_func lcf = *TLVP_VAL(tp, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION) & 0x7f; switch (msg_type) { case V52_CTRL_MSGT_LCP_LINK_CTRL: v5x_dl_snd(v5l->interface, V52_DLADDR_LCP, v52_enc_ctrl_link_ack(v5l, lcf)); /* send event to FSM */ osmo_fsm_inst_dispatch(v5l->ctrl->fi, V5X_CTRL_EV_RX_CONTROL, (void *)tp); /* send ACK to AN */ return 0; case V52_CTRL_MSGT_LCP_LINK_CTRL_ACK: /* send event to FSM */ osmo_fsm_inst_dispatch(v5l->ctrl->fi, V5X_CTRL_EV_RX_CONTROL_ACK, (void *)tp); return 0; default: return -EINVAL; } } /* receive message from lower (DL) layer */ int v5x_le_ctrl_dl_rcv(struct v5x_interface *v5if, uint16_t l3_addr, bool is_isdn, uint8_t msg_type, const struct tlv_parsed *tp) { struct v5x_user_port *v5up; struct v5x_link *v5l; switch (msg_type) { case V5X_CTRL_MSGT_PORT_CTRL: case V5X_CTRL_MSGT_PORT_CTRL_ACK: v5up = v5x_user_port_find(v5if, l3_addr, is_isdn); if (!v5up) { LOGV5IF(v5if, DV5CTRL, LOGL_ERROR, "Received %s port control message with unknown layer 3 address %d. " "Please check provisioning!\n", is_isdn ? "ISDN" : "PSTN", l3_addr); return -ENODEV; } return v5x_rcv_ctrl_port(v5up, msg_type, tp); case V5X_CTRL_MSGT_COMMON_CTRL: case V5X_CTRL_MSGT_COMMON_CTRL_ACK: if (l3_addr != V5X_DLADDR_CTRL) return -EINVAL; return v5x_rcv_ctrl_common(v5if, msg_type, tp); case V52_CTRL_MSGT_LCP_LINK_CTRL: case V52_CTRL_MSGT_LCP_LINK_CTRL_ACK: v5l = v5x_link_find_id(v5if, l3_addr); if (!v5l) { LOGV5IF(v5if, DV5CTRL, LOGL_ERROR, "Received link control message with unknown link ID %d. Please " "check provisioning!\n", l3_addr); return -ENODEV; } return v52_rcv_ctrl_link(v5l, msg_type, tp); } return -EINVAL; } /*********************************************************************** * V5 Message sending / encoding ***********************************************************************/ /* send common message from upper layer */ int v5x_le_ctrl_common_snd(struct v5x_interface *v5if, enum v5x_ctrl_func_id cfi, uint8_t *rej_cause, uint8_t *variant, uint32_t *interface_id) { LOGV5IF(v5if, DV5CTRL, LOGL_DEBUG, "Sending MDU-CTRL (from common control).\n"); osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V5X_CTRL_EV_MDU_CTRL, v5x_enc_ctrl_common(cfi, variant, rej_cause, interface_id)); return 0; } /* send port message from upper layer */ int v5x_le_ctrl_port_snd(struct v5x_user_port *v5up, enum v5x_ctrl_func_el cfe) { LOGV5UP(v5up, DV5CTRL, LOGL_DEBUG, "Sending FE (from port).\n"); osmo_fsm_inst_dispatch(v5up->ctrl->fi, V5X_CTRL_EV_MDU_CTRL, v5x_enc_ctrl_port(v5up, cfe, v5up->type == V5X_USER_TYPE_ISDN)); return 0; } /* send link message from upper layer */ int v52_le_ctrl_link_snd(struct v5x_link *v5l, enum v52_link_ctrl_func lcf) { LOGV5L(v5l, DV5CTRL, LOGL_DEBUG, "Sending FE (from LCP).\n"); osmo_fsm_inst_dispatch(v5l->ctrl->fi, V5X_CTRL_EV_MDU_CTRL, v52_enc_ctrl_link(v5l, lcf)); return 0; } void v5x_le_ctrl_start(struct v5x_ctrl_proto *ctrl) { osmo_fsm_inst_dispatch(ctrl->fi, V5X_CTRL_EV_MDU_START_TRAFFIC, NULL); } void v5x_le_ctrl_stop(struct v5x_ctrl_proto *ctrl) { if (ctrl->fi->state == V5X_CTRL_ST_OUT_OF_SERVICE) return; osmo_fsm_inst_dispatch(ctrl->fi, V5X_CTRL_EV_MDU_STOP_TRAFFIC, NULL); } bool v5x_le_ctrl_is_in_service(struct v5x_ctrl_proto *ctrl) { return (ctrl->fi->state >= V5X_CTRL_ST_IN_SERVICE); }