/* Management information forwarding between AN <-> LE */ /* (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. * */ /* * Part 1 does all the startup procedures and handles port blocking/unblocking. * Three state machines handle system startup, PSTN DL startup and PSTN restart. * Depending on the dialect, individual ports are blocked/unblocked or all * ports are blocked/unblocked with the accellerated alignment procedure. * * To understand it, please read C.13 of both G.964 and G.965 carefully. * * Notes: * * To prevent blocking/unblocking when PH-sockets get connected, there is a * startup delay. If unblocking would be performed during startup, AN may * send blocking indications that have been queued before. This results in an * inconsistency between LE and AN. The AN will have the port unblocked, but * LE will still think it is blocked. * * Part 2 handles link blocking/unblocking. * * Part 3 forwards messages between AN and PSTN/ISDN protocol (NAT protocol) * * Part 4 handles bearer channel allocation and activation. * * Part 5 handles switch over of protection protocol */ #include #include #include "v5x_internal.h" #include "v5x_protocol.h" #include "v5x_le_ctrl_fsm.h" #include "v5x_le_port_fsm.h" #include "v5x_le_pstn_fsm.h" #include "v52_le_lcp_fsm.h" #include "v52_le_bcc_fsm.h" #include "v52_le_pp_fsm.h" #include "v5x_le_management.h" #include "layer1.h" #include "lapv5.h" #include "logging.h" #define S(x) (1 << (x)) struct variant_interface_id { uint8_t variant; uint32_t interface_id; }; /* * Part 1: startup of common control + PSTN link, blocking/unblocking */ /* send (un)blocking to socket and store (un)blocking state of AN */ static void block_unblock_socket(struct v5x_user_port *v5up, bool block) { uint8_t ctrl; if (block && v5up->an_unblocked) { v5up->an_unblocked = false; ctrl = PH_CTRL_BLOCK; ph_socket_tx_msg(&v5up->ph_socket, v5up->type == V5X_USER_TYPE_ISDN ? 3 : 0, PH_PRIM_CTRL_IND, &ctrl, 1); return; } if (!block && !v5up->an_unblocked) { v5up->an_unblocked = true; ctrl = PH_CTRL_UNBLOCK; ph_socket_tx_msg(&v5up->ph_socket, v5up->type == V5X_USER_TYPE_ISDN ? 3 : 0, PH_PRIM_CTRL_IND, &ctrl, 1); return; } } /* (silently) block all ports */ static void block_all_ports(struct v5x_interface *v5if, bool silently, bool isdn, bool pstn) { struct v5x_user_port *v5up; bool any = false; LOGV5IF(v5if,DV5MGMT, LOGL_INFO, "%slocking %s%s%s ports:\n", (silently) ? "Silently b" : "B", (isdn) ? "ISDN" : "", (isdn && pstn) ? "+" : "", (pstn) ? "PSTN" : ""); llist_for_each_entry(v5up, &v5if->user_ports, list) { if (!isdn && !v5up->pstn.proto) continue; if (!pstn && v5up->pstn.proto) continue; LOGV5UP(v5up, DV5MGMT, LOGL_INFO, " -> Set %s port into blocked state.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); any = true; if (silently) block_unblock_socket(v5up, true); if (!silently) v5x_port_mph_snd(v5up, MPH_BI); if (v5up->pstn.proto) { if (silently) v5x_le_port_pstn_block(v5up->port_fi); LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, " -> Send port blocked control to PSTN port.\n"); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_blocked); } else { if (silently) v5x_le_port_isdn_block(v5up->port_fi); } } if (!any) LOGV5IF(v5if, DV5MGMT, LOGL_INFO, " -> Not any port to block.\n"); } /* (silently) unblock all ports */ static void unblock_all_ports(struct v5x_interface *v5if, bool silently, bool isdn, bool pstn) { struct v5x_user_port *v5up; bool any = false; LOGV5IF(v5if, DV5MGMT, LOGL_INFO, "%snblocking %s%s%s ports:\n", (silently) ? "Silently u" : "U", (isdn) ? "ISDN" : "", (isdn && pstn) ? "+" : "", (pstn) ? "PSTN" : ""); llist_for_each_entry(v5up, &v5if->user_ports, list) { if (!v5up->le_unblocked) continue; if (!isdn && !v5up->pstn.proto) continue; if (!pstn && v5up->pstn.proto) continue; LOGV5UP(v5up, DV5MGMT, LOGL_INFO, " -> Set %s port into unblocked state.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); any = true; if (silently) block_unblock_socket(v5up, false); if (!silently) v5x_port_mph_snd(v5up, MPH_UBR); if (v5up->pstn.proto) { if (silently) v5x_le_port_pstn_unblock(v5up->port_fi); LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, " -> Send port unblocked control to PSTN port.\n"); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_unblocked); } else { if (silently) v5x_le_port_isdn_unblock(v5up->port_fi); } } if (!any) LOGV5IF(v5if, DV5MGMT, LOGL_INFO, " -> Not any port to unblock.\n"); } /* align all ports */ static void align_all_ports(struct v5x_interface *v5if) { struct v5x_user_port *v5up; bool any = false; LOGV5IF(v5if, DV5MGMT, LOGL_INFO, "Aligning all ports:\n"); llist_for_each_entry(v5up, &v5if->user_ports, list) { if (v5up->le_unblocked) { LOGV5UP(v5up, DV5MGMT, LOGL_INFO, " -> Set %s port into unblocked state.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); any = true; v5x_port_mph_snd(v5up, MPH_UBR); if (v5up->pstn.proto) { LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, " -> Send port unblocked control to PSTN port.\n"); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_unblocked); } } else { LOGV5UP(v5up, DV5MGMT, LOGL_INFO, " -> Set %s port into blocked state.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); any = true; v5x_port_mph_snd(v5up, MPH_BI); if (v5up->pstn.proto) { LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, " -> Send port blocked control to PSTN port.\n"); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_blocked); } } } if (!any) LOGV5IF(v5if, DV5MGMT, LOGL_INFO, " -> Not any port to align.\n"); } /***********************************************************************/ /* state names, event names, primitives, ... */ /***********************************************************************/ /* C.17 States in the system management */ enum v5x_le_system_state { V5X_LE_SYSTEM_S_LESYS0_SYSTEM_STARTUP, /* LESYS0 */ V5X_LE_SYSTEM_S_LESYS1_WAIT_VARIANT_AND_ID, /* LESYS1 */ V5X_LE_SYSTEM_S_LESYS2_IN_SERVICE, /* LESYS2 */ V5X_LE_SYSTEM_S_LESYS3_CONTROL_DL_RELEASED_1, /* LESYS3 */ V5X_LE_SYSTEM_S_LESYS4_CONTROL_DL_RELEASED_2, /* LESYS4 */ V5X_LE_SYSTEM_S_LESYS5_SWITCH_OVER, /* LESYS5 */ V5X_LE_SYSTEM_S_LESYS6_AWAIT_V5_IF_INIT, /* LESYS6 */ V5X_LE_SYSTEM_S_LESYS7_FORCE_SYSTEM_RESTART, /* LESYS7 */ V5X_LE_SYSTEM_S_LESYS8_SYSTEM_DEACTIVATED, /* LESYS8 */ }; enum v5x_le_pstn_dl_state { V5X_LE_PSTN_DL_S_LEPDL0_PSTN_NULL, /* LEPDL0 */ V5X_LE_PSTN_DL_S_LEPDL1_ACTIVATE_PSTN, /* LEPDL1 */ V5X_LE_PSTN_DL_S_LEPDL2_PSTN_IN_SERVICE, /* LEPDL2 */ V5X_LE_PSTN_DL_S_LEPDL3_PSTN_DL_RELEASED_1, /* LEPDL3 */ V5X_LE_PSTN_DL_S_LEPDL4_PSTN_DL_RELEASED_2, /* LEPDL4 */ }; enum v5x_le_pstn_restart_state { V5X_LE_PSTN_RS_S_LEPRS0_RESTART_NULL, /* LEPRS0 */ V5X_LE_PSTN_RS_S_LEPRS1_RESTART, /* LEPRS1 */ }; /* these states are not described, but derived from C.28 description */ enum v5x_le_unblk_all_state { V5X_LE_UNBLK_ALL_S_NULL, V5X_LE_UNBLK_ALL_S_REQUESTED, /* request sent, wait for accepted */ V5X_LE_UNBLK_ALL_S_COMPLETED, /* completed sent, wait for completed */ }; /* C.13 - C.16 Events for system management */ enum v5x_le_system_fsm_event { V5X_LE_SYSTEM_E_START, V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED, V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED, V5X_LE_SYSTEM_E_VARIANT_AND_ID, V5X_LE_SYSTEM_E_TIMEOUT_TV1, V5X_LE_SYSTEM_E_TIMEOUT_TC1, V5X_LE_SYSTEM_E_TIMEOUT_TC2, V5X_LE_SYSTEM_E_TIMEOUT_TC8, V5X_LE_SYSTEM_E_TIMEOUT_TC9, }; static const struct value_string v5x_le_system_fsm_event_names[] = { { V5X_LE_SYSTEM_E_START, "Start" }, { V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED, "Control DL establish" }, { V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED, "Control DL release" }, { V5X_LE_SYSTEM_E_VARIANT_AND_ID, "Got variant and interface ID" }, { V5X_LE_SYSTEM_E_TIMEOUT_TV1, "Timeout TV1" }, { V5X_LE_SYSTEM_E_TIMEOUT_TC1, "Timeout TC1" }, { V5X_LE_SYSTEM_E_TIMEOUT_TC2, "Timeout TC2" }, { V5X_LE_SYSTEM_E_TIMEOUT_TC8, "Timeout TC8" }, { V5X_LE_SYSTEM_E_TIMEOUT_TC9, "Timeout TC9" }, { 0, NULL } }; enum v5x_le_pstn_dl_fsm_event { V5X_LE_PSTN_DL_E_START, V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED, V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED, V5X_LE_PSTN_DL_E_TIMEOUT_TC3, }; static const struct value_string v5x_le_pstn_dl_fsm_event_names[] = { { V5X_LE_PSTN_DL_E_START, "Start" }, { V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED, "PSTN DL establish" }, { V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED, "PSTN DL release" }, { V5X_LE_PSTN_DL_E_TIMEOUT_TC3, "Timeout TC3" }, { 0, NULL } }; enum v5x_le_pstn_rs_fsm_event { V5X_LE_PSTN_RS_E_MDU_CTRL_restart_request, V5X_LE_PSTN_RS_E_MDU_CTRL_restart_complete, V5X_LE_PSTN_RS_E_Request_PSTN_Restart, V5X_LE_PSTN_RS_E_Complete_PSTN_Restart, V5X_LE_PSTN_RS_E_TIMEOUT_TR1, V5X_LE_PSTN_RS_E_TIMEOUT_TR2, }; static const struct value_string v5x_le_pstn_rs_fsm_event_names[] = { { V5X_LE_PSTN_RS_E_MDU_CTRL_restart_request, "MDU-CTRL (restart request)" }, { V5X_LE_PSTN_RS_E_MDU_CTRL_restart_complete, "MDU-CTRL (restart complete)" }, { V5X_LE_PSTN_RS_E_Request_PSTN_Restart, "Request restart by system mgmt" }, { V5X_LE_PSTN_RS_E_Complete_PSTN_Restart, "Completed restart of all PSTN" }, { V5X_LE_PSTN_RS_E_TIMEOUT_TR1, "Timeout TR1" }, { V5X_LE_PSTN_RS_E_TIMEOUT_TR2, "Timeout TR2" }, { 0, NULL } }; enum v5x_le_unblk_all_fsm_event { V5X_LE_UNBLK_ALL_E_Request_Unblock_Block, V5X_LE_UNBLK_ALL_E_REQUEST, V5X_LE_UNBLK_ALL_E_REJECTED, V5X_LE_UNBLK_ALL_E_ACCEPTED, V5X_LE_UNBLK_ALL_E_COMPLETED, V5X_LE_UNBLK_ALL_E_TIMEOUT_TU1, V5X_LE_UNBLK_ALL_E_TIMEOUT_TU2, }; static const struct value_string v5x_le_unblk_all_fsm_event_names[] = { { V5X_LE_UNBLK_ALL_E_Request_Unblock_Block, "Request Unblock/Block" }, { V5X_LE_UNBLK_ALL_E_REQUEST, "REQUEST received" }, { V5X_LE_UNBLK_ALL_E_REJECTED, "REJECTED received" }, { V5X_LE_UNBLK_ALL_E_ACCEPTED, "ACCEPTED received" }, { V5X_LE_UNBLK_ALL_E_COMPLETED, "COMPLETED received" }, { V5X_LE_UNBLK_ALL_E_TIMEOUT_TU1, "Timeout TU1" }, { V5X_LE_UNBLK_ALL_E_TIMEOUT_TU2, "Timeout TU2" }, { 0, NULL } }; /***********************************************************************/ /* timers of all states FSM */ /***********************************************************************/ #define TIMEOUT_TU1 100 #define TIMEOUT_TU2 60 static void start_timer_system(struct v5x_mgmt_proto *mgmt, int delay) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Start timer for system startup delay.\n"); osmo_timer_schedule(&mgmt->timer_system, delay, 0); } static void start_timer_tr1(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Start timer TR1.\n"); osmo_timer_schedule(&mgmt->timer_tr1, 100, 0); } static void start_timer_tr2(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Start timer TR2.\n"); osmo_timer_schedule(&mgmt->timer_tr2, 120, 0); } static void start_timer_tc1(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Start timer TC1.\n"); osmo_timer_schedule(&mgmt->timer_tc1, 15, 0); } static void start_timer_tc2(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Start timer TC2.\n"); osmo_timer_schedule(&mgmt->timer_tc2, 60, 0); } static void start_timer_tc3(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Start timer TC3.\n"); osmo_timer_schedule(&mgmt->timer_tc3, 15, 0); } static void start_timer_tc8(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Start timer TC8.\n"); osmo_timer_schedule(&mgmt->timer_tc8, 20, 0); } static void start_timer_tc9(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Start timer TC9.\n"); osmo_timer_schedule(&mgmt->timer_tc9, 95, 0); } static void start_timer_tv1(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Start timer TV1.\n"); osmo_timer_schedule(&mgmt->timer_tv1, 15, 0); } static void stop_timer_system(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Stop timer for system startup delay.\n"); osmo_timer_del(&mgmt->timer_system); } static void stop_timer_tr1(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Stop timer TR1.\n"); osmo_timer_del(&mgmt->timer_tr1); } static void stop_timer_tr2(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Stop timer TR2.\n"); osmo_timer_del(&mgmt->timer_tr2); } static void stop_timer_tc1(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Stop timer TC1.\n"); osmo_timer_del(&mgmt->timer_tc1); } static void stop_timer_tc2(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Stop timer TC2.\n"); osmo_timer_del(&mgmt->timer_tc2); } static void stop_timer_tc3(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Stop timer TC3.\n"); osmo_timer_del(&mgmt->timer_tc3); } static void stop_timer_tc8(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Stop timer TC8.\n"); osmo_timer_del(&mgmt->timer_tc8); } static void stop_timer_tc9(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Stop timer TC9.\n"); osmo_timer_del(&mgmt->timer_tc9); } static void stop_timer_tv1(struct v5x_mgmt_proto *mgmt) { LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Stop timer TV1.\n"); osmo_timer_del(&mgmt->timer_tv1); } static void timeout_system(void *data) { struct v5x_mgmt_proto *mgmt = data; LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Timeout timer for system startup delay.\n"); v5x_le_mgmt_start(mgmt->interface); } static void timeout_tr1(void *data) { struct v5x_mgmt_proto *mgmt = data; LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Timeout TR1.\n"); osmo_fsm_inst_dispatch(mgmt->pstn_rs_fi, V5X_LE_PSTN_RS_E_TIMEOUT_TR1, NULL); } static void timeout_tr2(void *data) { struct v5x_mgmt_proto *mgmt = data; LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Timeout TR2.\n"); osmo_fsm_inst_dispatch(mgmt->pstn_rs_fi, V5X_LE_PSTN_RS_E_TIMEOUT_TR2, NULL); } static void timeout_tc1(void *data) { struct v5x_mgmt_proto *mgmt = data; LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Timeout TC1.\n"); osmo_fsm_inst_dispatch(mgmt->system_fi, V5X_LE_SYSTEM_E_TIMEOUT_TC1, NULL); } static void timeout_tc2(void *data) { struct v5x_mgmt_proto *mgmt = data; LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Timeout TC2.\n"); osmo_fsm_inst_dispatch(mgmt->system_fi, V5X_LE_SYSTEM_E_TIMEOUT_TC2, NULL); } static void timeout_tc3(void *data) { struct v5x_mgmt_proto *mgmt = data; LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Timeout TC3.\n"); osmo_fsm_inst_dispatch(mgmt->pstn_dl_fi, V5X_LE_PSTN_DL_E_TIMEOUT_TC3, NULL); } static void timeout_tc8(void *data) { struct v5x_mgmt_proto *mgmt = data; LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Timeout TC8.\n"); osmo_fsm_inst_dispatch(mgmt->system_fi, V5X_LE_SYSTEM_E_TIMEOUT_TC8, NULL); } static void timeout_tc9(void *data) { struct v5x_mgmt_proto *mgmt = data; LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Timeout TC9.\n"); osmo_fsm_inst_dispatch(mgmt->system_fi, V5X_LE_SYSTEM_E_TIMEOUT_TC9, NULL); } static void timeout_tv1(void *data) { struct v5x_mgmt_proto *mgmt = data; LOGV5IF(mgmt->interface, DV5MGMT, LOGL_DEBUG, "Timeout TV1.\n"); osmo_fsm_inst_dispatch(mgmt->system_fi, V5X_LE_SYSTEM_E_TIMEOUT_TV1, NULL); } static void reset_all_timers(struct v5x_mgmt_proto *mgmt) { stop_timer_tr1(mgmt); stop_timer_tr2(mgmt); stop_timer_tc1(mgmt); stop_timer_tc2(mgmt); stop_timer_tc3(mgmt); stop_timer_tc8(mgmt); stop_timer_tc9(mgmt); stop_timer_tv1(mgmt); } /***********************************************************************/ /* system startup state FSM */ /***********************************************************************/ static void disable_lapv5(struct v5x_mgmt_proto *mgmt) { struct v5x_interface *v5if = mgmt->interface; lapv5_set_enabled(v5if->control.li, false); v5if->control.established = 0; if (v5if->pstn.li) { lapv5_set_enabled(v5if->pstn.li, false); v5if->pstn.established = 0; } if (v5if->lcp.li) { lapv5_set_enabled(v5if->lcp.li, false); v5if->lcp.established = 0; } if (v5if->bcc.li) { lapv5_set_enabled(v5if->bcc.li, false); v5if->bcc.established = 0; } if (v5if->protection.li[0]) { lapv5_set_enabled(v5if->protection.li[0], false); v5if->protection.established[0] = 0; } if (v5if->protection.li[1]) { lapv5_set_enabled(v5if->protection.li[1], false); v5if->protection.established[1] = 0; } } static void system_startup(struct v5x_mgmt_proto *mgmt) { struct v5x_interface *v5if = mgmt->interface; struct v5x_link *v5l; struct v5x_user_port *v5up; /* enable LAPV5 DLs except PSTN */ lapv5_set_enabled(v5if->control.li, true); if (v5if->pstn.li) lapv5_set_enabled(v5if->pstn.li, mgmt->pstn_enable_early); if (v5if->lcp.li) lapv5_set_enabled(v5if->lcp.li, true); if (v5if->bcc.li) lapv5_set_enabled(v5if->bcc.li, true); if (v5if->protection.li[0]) lapv5_set_enabled(v5if->protection.li[0], true); if (v5if->protection.li[1]) lapv5_set_enabled(v5if->protection.li[1], true); /* no pending restart */ mgmt->pstn_rs_pending = false; /* reset state machines */ if (v5if->control.ctrl) v5x_le_ctrl_stop(v5if->control.ctrl); llist_for_each_entry(v5l, &v5if->links, list) { if (v5l->ctrl) v5x_le_ctrl_stop(v5l->ctrl); } llist_for_each_entry(v5up, &v5if->user_ports, list) { if (v5up->ctrl) v5x_le_ctrl_stop(v5up->ctrl); } /* block ISDN and PSTN ports */ block_all_ports(v5if, true, true, true); /* reset timers */ reset_all_timers(mgmt); /* reset management state machines */ mgmt->pstn_dl_fi->state = V5X_LE_PSTN_DL_S_LEPDL0_PSTN_NULL; mgmt->pstn_rs_fi->state = V5X_LE_PSTN_RS_S_LEPRS0_RESTART_NULL; /* establish CONTROL DL and other DLs, except PSTN DL */ if (mgmt->do_est) { lapv5_dl_est_req(v5if->control.li, V5X_DLADDR_CTRL); if (v5if->pstn.li) lapv5_dl_est_req(v5if->pstn.li, V5X_DLADDR_PSTN); if (v5if->lcp.li) lapv5_dl_est_req(v5if->lcp.li, V52_DLADDR_LCP); if (v5if->bcc.li) lapv5_dl_est_req(v5if->bcc.li, V52_DLADDR_BCC); if (v5if->protection.li[0]) lapv5_dl_est_req(v5if->protection.li[0], V52_DLADDR_PROTECTION); if (v5if->protection.li[1]) lapv5_dl_est_req(v5if->protection.li[1], V52_DLADDR_PROTECTION); } } static void start_traffic(struct v5x_mgmt_proto *mgmt) { struct v5x_interface *v5if = mgmt->interface; struct v5x_user_port *v5up; /* COMMON_CONTROL */ v5x_le_ctrl_start(v5if->control.ctrl); /* PORT_CONTROL */ llist_for_each_entry(v5up, &v5if->user_ports, list) v5x_le_ctrl_start(v5up->ctrl); } static void stop_traffic(struct v5x_mgmt_proto *mgmt) { struct v5x_interface *v5if = mgmt->interface; struct v5x_user_port *v5up; /* COMMON_CONTROL */ v5x_le_ctrl_stop(v5if->control.ctrl); /* PORT_CONTROL */ llist_for_each_entry(v5up, &v5if->user_ports, list) v5x_le_ctrl_stop(v5up->ctrl); } static void system_lesys0_system_startup(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_SYSTEM_E_START: LOGPFSML(fi, LOGL_INFO, "Perform system startup\n"); system_startup(mgmt); break; case V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED: LOGPFSML(fi, LOGL_INFO, "Establishment of control DL success.\n"); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_SYSTEM_S_LESYS1_WAIT_VARIANT_AND_ID, 0, 0); /* start traffic to CONTROL_COMMON and CONTROL_PORT FSM */ start_traffic(mgmt); /* start timer TV1 */ start_timer_tv1(mgmt); /* request variant and ID */ LOGPFSML(fi, LOGL_INFO, "Requesting variant and interface ID.\n"); v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_REQUEST_VARIANT_AND_INTERFACE_ID, NULL, NULL, NULL); break; case V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED: LOGPFSML(fi, LOGL_ERROR, "Establishment of control DL failed.\n"); /* re-establish DL */ if (mgmt->do_est) lapv5_dl_est_req(v5if->control.li, V5X_DLADDR_CTRL); break; default: OSMO_ASSERT(0); } } static void system_lesys1_wait_variant_and_id(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct variant_interface_id *vi; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_SYSTEM_E_VARIANT_AND_ID: vi = data; LOGPFSML(fi, LOGL_INFO, "AN replies variant and interface ID. (variant = %d, interface_id = %d)\n", vi->variant, vi->interface_id); /* stop timer TV1 */ stop_timer_tv1(mgmt); /* handle variant and ID */ v5if->variant_remote = vi->variant; v5if->variant_remote_valid = true; v5if->id_remote = vi->interface_id; v5if->id_remote_valid = true; if (v5if->variant_remote != v5if->variant) { LOGPFSML(fi, LOGL_ERROR, "Variant does not match: AN(%d) != LE(%d)\n", v5if->variant_remote, v5if->variant); } if (v5if->id_remote != v5if->id) { LOGPFSML(fi, LOGL_ERROR, "Interface ID does not match: AN(%d) != LE(%d)\n", v5if->id_remote, v5if->id); } if (v5if->variant_remote != v5if->variant || v5if->id_remote != v5if->id) { LOGPFSML(fi, LOGL_ERROR, "************************************************************\n"); LOGPFSML(fi, LOGL_ERROR, "You must set matching Variant and Interface ID at AN and LE!\n"); LOGPFSML(fi, LOGL_ERROR, "************************************************************\n"); /* disable LAPV5 DLs */ disable_lapv5(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_SYSTEM_S_LESYS7_FORCE_SYSTEM_RESTART, 0, 0); /* start timer TC9 */ start_timer_tc9(mgmt); break; } /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_SYSTEM_S_LESYS2_IN_SERVICE, 0, 0); if (mgmt->do_align) { /* unblock all ISDN user ports */ if (mgmt->acc_align && v5if->dialect == V5X_DIALECT_V52) osmo_fsm_inst_dispatch(mgmt->unblk_all_fi[UNBLK_ALL_ISDN], V5X_LE_UNBLK_ALL_E_Request_Unblock_Block, (void *)UNBLK_ALL_ISDN); else unblock_all_ports(v5if, false, true, false); } if (v5if->pstn.li) { /* enable PSTN DL and trigger PSTN DL startup */ lapv5_set_enabled(v5if->pstn.li, true); osmo_fsm_inst_dispatch(mgmt->pstn_dl_fi, V5X_LE_PSTN_DL_E_START, NULL); } break; case V5X_LE_SYSTEM_E_TIMEOUT_TV1: /* start timer TV1 */ start_timer_tv1(mgmt); /* request variant and ID again */ v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_REQUEST_VARIANT_AND_INTERFACE_ID, NULL, NULL, NULL); break; case V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED: /* ignore */ break; case V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED: LOGPFSML(fi, LOGL_ERROR, "Establishment of control DL failed.\n"); /* stop timer TV1 */ stop_timer_tv1(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_SYSTEM_S_LESYS0_SYSTEM_STARTUP, 0, 0); /* stop traffic to CONTROL_COMMON and CONTROL_PORT FSM */ stop_traffic(mgmt); break; default: OSMO_ASSERT(0); } } static void system_lesys2_in_service(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED: /* ignore */ break; case V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED: LOGPFSML(fi, LOGL_ERROR, "Establishment of control DL failed.\n"); /* start timer TC1 */ start_timer_tc1(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_SYSTEM_S_LESYS3_CONTROL_DL_RELEASED_1, 0, 0); /* re-establish DL */ if (mgmt->do_est) lapv5_dl_est_req(v5if->control.li, V5X_DLADDR_CTRL); break; default: OSMO_ASSERT(0); } } static void system_lesys3_control_dl_released_1(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED: LOGPFSML(fi, LOGL_INFO, "Establishment of control DL success.\n"); /* stop timer TC1 */ stop_timer_tc1(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_SYSTEM_S_LESYS2_IN_SERVICE, 0, 0); break; case V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED: /* re-establish DL */ if (mgmt->do_est) lapv5_dl_est_req(v5if->control.li, V5X_DLADDR_CTRL); break; case V5X_LE_SYSTEM_E_TIMEOUT_TC1: LOGPFSML(fi, LOGL_ERROR, "Blocking all ports and wait for establishment of control DL.\n"); /* stop traffic to CONTROL_COMMON and CONTROL_PORT FSM */ stop_traffic(mgmt); /* block all ISDN user ports */ block_all_ports(v5if, true, true, false); /* start timer TC2 */ start_timer_tc2(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_SYSTEM_S_LESYS4_CONTROL_DL_RELEASED_2, 0, 0); break; default: OSMO_ASSERT(0); } } static void system_lesys4_control_dl_released_2(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED: LOGPFSML(fi, LOGL_INFO, "Establishment of control DL success.\n"); /* stop timer TC2 */ stop_timer_tc2(mgmt); /* start traffic to CONTROL_COMMON and CONTROL_PORT FSM */ start_traffic(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_SYSTEM_S_LESYS2_IN_SERVICE, 0, 0); if (mgmt->do_align) { /* unblock all ISDN user ports */ if (mgmt->acc_align && v5if->dialect == V5X_DIALECT_V52) osmo_fsm_inst_dispatch(mgmt->unblk_all_fi[UNBLK_ALL_ISDN], V5X_LE_UNBLK_ALL_E_Request_Unblock_Block, (void *)UNBLK_ALL_ISDN); else unblock_all_ports(v5if, false, true, false); } /* trigger PSTN DL restart */ if (mgmt->pstn_rs_pending && v5if->pstn.li) { mgmt->pstn_rs_pending = false; osmo_fsm_inst_dispatch(mgmt->pstn_rs_fi, V5X_LE_PSTN_RS_E_Request_PSTN_Restart, NULL); } break; case V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED: /* re-establish DL */ if (mgmt->do_est) lapv5_dl_est_req(v5if->control.li, V5X_DLADDR_CTRL); break; case V5X_LE_SYSTEM_E_TIMEOUT_TC2: LOGPFSML(fi, LOGL_ERROR, "Could not establish control DL, shutting down.\n"); /* disable LAPV5 DLs */ disable_lapv5(mgmt); /* block all PSTN user ports */ block_all_ports(v5if, true, false, true); /* start timer TC8 */ start_timer_tc8(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_SYSTEM_S_LESYS7_FORCE_SYSTEM_RESTART, 0, 0); break; default: OSMO_ASSERT(0); } } #if 0 static void system_lesys5_switch_over(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { switch (event) { default: OSMO_ASSERT(0); } } static void system_lesys6_await_v5_if_init(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { switch (event) { default: OSMO_ASSERT(0); } } #endif static void system_lesys7_force_system_restart(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED: case V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED: /* ignore */ break; case V5X_LE_SYSTEM_E_TIMEOUT_TC8: case V5X_LE_SYSTEM_E_TIMEOUT_TC9: /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_SYSTEM_S_LESYS8_SYSTEM_DEACTIVATED, 0, 0); if (mgmt->auto_restart) { /* start automatically */ v5x_le_mgmt_start(v5if); } break; default: OSMO_ASSERT(0); } } static void system_lesys8_system_deactivated(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; switch (event) { case V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED: case V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED: /* ignore */ break; case V5X_LE_SYSTEM_E_START: LOGPFSML(fi, LOGL_INFO, "Perform system restart\n"); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_SYSTEM_S_LESYS0_SYSTEM_STARTUP, 0, 0); system_startup(mgmt); break; default: OSMO_ASSERT(0); } } /* There is no table, it is described in text at clause C.13 and C.15 of ITU-T G.964 */ static const struct osmo_fsm_state v5x_le_system_fsm_states[] = { [V5X_LE_SYSTEM_S_LESYS0_SYSTEM_STARTUP] = { .name = "SYSTEM STARTUP (LESYS0)", .in_event_mask = S(V5X_LE_SYSTEM_E_START) | S(V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED) | S(V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED), .out_state_mask = S(V5X_LE_SYSTEM_S_LESYS1_WAIT_VARIANT_AND_ID), .action = system_lesys0_system_startup, }, [V5X_LE_SYSTEM_S_LESYS1_WAIT_VARIANT_AND_ID] = { .name = "WAIT FOR VARIANT & INTERFACE ID (LESYS1)", .in_event_mask = S(V5X_LE_SYSTEM_E_VARIANT_AND_ID) | S(V5X_LE_SYSTEM_E_TIMEOUT_TV1) | S(V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED) | S(V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED), .out_state_mask = S(V5X_LE_SYSTEM_S_LESYS2_IN_SERVICE) | S(V5X_LE_SYSTEM_S_LESYS7_FORCE_SYSTEM_RESTART) | S(V5X_LE_SYSTEM_S_LESYS0_SYSTEM_STARTUP), .action = system_lesys1_wait_variant_and_id, }, [V5X_LE_SYSTEM_S_LESYS2_IN_SERVICE] = { .name = "IN SERVICE (LESYS2)", .in_event_mask = S(V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED) | S(V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED), .out_state_mask = S(V5X_LE_SYSTEM_S_LESYS3_CONTROL_DL_RELEASED_1), .action = system_lesys2_in_service, }, [V5X_LE_SYSTEM_S_LESYS3_CONTROL_DL_RELEASED_1] = { .name = "CONTROL DL RELEASE 1 (LESYS3)", .in_event_mask = S(V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED) | S(V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED) | S(V5X_LE_SYSTEM_E_TIMEOUT_TC1), .out_state_mask = S(V5X_LE_SYSTEM_S_LESYS2_IN_SERVICE) | S(V5X_LE_SYSTEM_S_LESYS4_CONTROL_DL_RELEASED_2), .action = system_lesys3_control_dl_released_1, }, [V5X_LE_SYSTEM_S_LESYS4_CONTROL_DL_RELEASED_2] = { .name = "CONTROL DL RELEASE 2 (LESYS4)", .in_event_mask = S(V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED) | S(V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED) | S(V5X_LE_SYSTEM_E_TIMEOUT_TC2), .out_state_mask = S(V5X_LE_SYSTEM_S_LESYS7_FORCE_SYSTEM_RESTART) | S(V5X_LE_SYSTEM_S_LESYS2_IN_SERVICE), .action = system_lesys4_control_dl_released_2, }, #if 0 [V5X_LE_SYSTEM_S_LESYS5_SWITCH_OVER] = { .name = "SWITCH OVER (LESYS5)", .action = system_lesys5_switch_over, }, [V5X_LE_SYSTEM_S_LESYS6_AWAIT_V5_IF_INIT] = { .name = "AWAIT V5 INTERFACE INIT (LESYS6)", .action = system_lesys6_await_v5_if_init, }, #endif [V5X_LE_SYSTEM_S_LESYS7_FORCE_SYSTEM_RESTART] = { .name = "FORCE SYSTEM RESTART (LESYS7)", .in_event_mask = S(V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED) | S(V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED) | S(V5X_LE_SYSTEM_E_TIMEOUT_TC8) | S(V5X_LE_SYSTEM_E_TIMEOUT_TC9), .out_state_mask = S(V5X_LE_SYSTEM_S_LESYS8_SYSTEM_DEACTIVATED), .action = system_lesys7_force_system_restart, }, [V5X_LE_SYSTEM_S_LESYS8_SYSTEM_DEACTIVATED] = { .name = "SYSTEM DEACTIVATED (LESYS8)", .in_event_mask = S(V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED) | S(V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED) | S(V5X_LE_SYSTEM_E_START), .out_state_mask = S(V5X_LE_SYSTEM_S_LESYS0_SYSTEM_STARTUP), .action = system_lesys8_system_deactivated, }, }; struct osmo_fsm v5x_le_system_fsm = { .name = "System_startup", .states = v5x_le_system_fsm_states, .num_states = ARRAY_SIZE(v5x_le_system_fsm_states), .allstate_event_mask = 0, .allstate_action = NULL, .cleanup = NULL, .timer_cb = NULL, .log_subsys = DV5MGMT, .event_names = v5x_le_system_fsm_event_names, }; const char *v5x_le_system_fsm_state_name(struct osmo_fsm_inst *fi) { return v5x_le_system_fsm_states[fi->state].name; } /***********************************************************************/ /* PSTN DL startup state FSM */ /***********************************************************************/ static void pstn_lepdl0_pstn_null(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_PSTN_DL_E_START: /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_PSTN_DL_S_LEPDL1_ACTIVATE_PSTN, 0, 0); /* if DL is already established */ if (mgmt->pstn_enable_early && v5if->pstn.established) { osmo_fsm_inst_dispatch(fi, V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED, NULL); break; } /* establish DL */ if (mgmt->do_est && !v5if->pstn.established) lapv5_dl_est_req(v5if->pstn.li, V5X_DLADDR_PSTN); break; case V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED: /* ignore */ break; case V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED: /* ignore */ break; default: OSMO_ASSERT(0); } } static void pstn_lepdl1_active_pstn(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED: LOGPFSML(fi, LOGL_INFO, "Establishment of PSTN DL success.\n"); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_PSTN_DL_S_LEPDL2_PSTN_IN_SERVICE, 0, 0); if (mgmt->do_align) { /* unblock all PSTN user ports */ if (mgmt->acc_align && v5if->dialect == V5X_DIALECT_V52) osmo_fsm_inst_dispatch(mgmt->unblk_all_fi[UNBLK_ALL_PSTN], V5X_LE_UNBLK_ALL_E_Request_Unblock_Block, (void *)UNBLK_ALL_PSTN); else unblock_all_ports(v5if, false, false, true); } break; case V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED: LOGPFSML(fi, LOGL_ERROR, "Establishment of PSTN DL failed.\n"); /* re-establish DL */ if (mgmt->do_est) lapv5_dl_est_req(v5if->pstn.li, V5X_DLADDR_PSTN); break; default: OSMO_ASSERT(0); } } static void pstn_lepdl2_pstn_in_service(struct osmo_fsm_inst *fi, uint32_t event, __attribute__((unused)) void *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED: /* ignore */ break; case V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED: LOGPFSML(fi, LOGL_ERROR, "Establishment of PSTN DL failed.\n"); /* start timer TC3 */ start_timer_tc3(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_PSTN_DL_S_LEPDL3_PSTN_DL_RELEASED_1, 0, 0); /* re-establish DL */ if (mgmt->do_est) lapv5_dl_est_req(v5if->pstn.li, V5X_DLADDR_PSTN); break; default: OSMO_ASSERT(0); } } static void pstn_lepdl3_pstn_dl_released_1(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED: LOGPFSML(fi, LOGL_INFO, "Establishment of PSTN DL success.\n"); /* stop timer TC3 */ stop_timer_tc3(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_PSTN_DL_S_LEPDL2_PSTN_IN_SERVICE, 0, 0); break; case V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED: /* re-establish DL */ if (mgmt->do_est) lapv5_dl_est_req(v5if->pstn.li, V5X_DLADDR_PSTN); break; case V5X_LE_PSTN_DL_E_TIMEOUT_TC3: /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_PSTN_DL_S_LEPDL4_PSTN_DL_RELEASED_2, 0, 0); /* block all PSTN user ports */ block_all_ports(v5if, true, false, true); break; default: OSMO_ASSERT(0); } } static void pstn_lepdl4_pstn_dl_released_2(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED: LOGPFSML(fi, LOGL_INFO, "Establishment of PSTN DL success.\n"); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_PSTN_DL_S_LEPDL2_PSTN_IN_SERVICE, 0, 0); /* start PSTN restart process */ if (mgmt->system_fi->state == V5X_LE_SYSTEM_S_LESYS4_CONTROL_DL_RELEASED_2) mgmt->pstn_rs_pending = true; else osmo_fsm_inst_dispatch(mgmt->pstn_rs_fi, V5X_LE_PSTN_RS_E_Request_PSTN_Restart, NULL); break; case V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED: /* re-establish DL */ if (mgmt->do_est) lapv5_dl_est_req(v5if->pstn.li, V5X_DLADDR_PSTN); break; default: OSMO_ASSERT(0); } } static const struct osmo_fsm_state v5x_le_pstn_dl_fsm_states[] = { [V5X_LE_PSTN_DL_S_LEPDL0_PSTN_NULL] = { .name = "PSTN NULL (LEPDL0)", .in_event_mask = S(V5X_LE_PSTN_DL_E_START) | S(V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED) | S(V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED), .out_state_mask = S(V5X_LE_PSTN_DL_S_LEPDL1_ACTIVATE_PSTN), .action = pstn_lepdl0_pstn_null, }, [V5X_LE_PSTN_DL_S_LEPDL1_ACTIVATE_PSTN] = { .name = "ACTIVE PSTN (LEPDL1)", .in_event_mask = S(V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED) | S(V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED), .out_state_mask = S(V5X_LE_PSTN_DL_S_LEPDL2_PSTN_IN_SERVICE), .action = pstn_lepdl1_active_pstn, }, [V5X_LE_PSTN_DL_S_LEPDL2_PSTN_IN_SERVICE] = { .name = "PSTN IN SERVICE (LEPDL2)", .in_event_mask = S(V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED) | S(V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED), .out_state_mask = S(V5X_LE_PSTN_DL_S_LEPDL3_PSTN_DL_RELEASED_1), .action = pstn_lepdl2_pstn_in_service, }, [V5X_LE_PSTN_DL_S_LEPDL3_PSTN_DL_RELEASED_1] = { .name = "PSTN DL RELEASED 1 (LEPDL3)", .in_event_mask = S(V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED) | S(V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED) | S(V5X_LE_PSTN_DL_E_TIMEOUT_TC3), .out_state_mask = S(V5X_LE_PSTN_DL_S_LEPDL2_PSTN_IN_SERVICE) | S(V5X_LE_PSTN_DL_S_LEPDL4_PSTN_DL_RELEASED_2), .action = pstn_lepdl3_pstn_dl_released_1, }, [V5X_LE_PSTN_DL_S_LEPDL4_PSTN_DL_RELEASED_2] = { .name = "PSTN DL RELEASED 2 (LEPDL4)", .in_event_mask = S(V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED) | S(V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED), .out_state_mask = S(V5X_LE_PSTN_DL_S_LEPDL2_PSTN_IN_SERVICE), .action = pstn_lepdl4_pstn_dl_released_2, }, }; struct osmo_fsm v5x_le_pstn_dl_fsm = { .name = "PSTN_DL_startup", .states = v5x_le_pstn_dl_fsm_states, .num_states = ARRAY_SIZE(v5x_le_pstn_dl_fsm_states), .allstate_event_mask = 0, .allstate_action = NULL, .cleanup = NULL, .timer_cb = NULL, .log_subsys = DV5MGMT, .event_names = v5x_le_pstn_dl_fsm_event_names, }; const char *v5x_le_pstn_dl_fsm_state_name(struct osmo_fsm_inst *fi) { return v5x_le_pstn_dl_fsm_states[fi->state].name; } /***********************************************************************/ /* PSTN Restart state FSM */ /***********************************************************************/ static void restart_all_pstn_ports(struct v5x_interface *v5if) { struct v5x_user_port *v5up; llist_for_each_entry(v5up, &v5if->user_ports, list) { if (v5up->pstn.proto) { LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Send restart request to PSTN port.\n"); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_restart_req); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_restart_compl); } } } static void pstn_leprs0_restart_null(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_PSTN_RS_E_Request_PSTN_Restart: LOGPFSML(fi, LOGL_INFO, "PSTN Restart was requested locally.\n"); /* send restart request to COMMON_CONTROL */ v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_RESTART_REQUEST, NULL, NULL, NULL); /* fall through */ case V5X_LE_PSTN_RS_E_MDU_CTRL_restart_request: LOGPFSML(fi, LOGL_INFO, "PSTN Restart was requested by remote.\n"); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_PSTN_RS_S_LEPRS1_RESTART, 0, 0); /* start TR1 & TR2 */ start_timer_tr1(mgmt); start_timer_tr2(mgmt); /* send restart to all PSTN ports, they are always complete */ restart_all_pstn_ports(v5if); /* as the PSTN restart is complete, proceed directly */ osmo_fsm_inst_dispatch(mgmt->pstn_rs_fi, V5X_LE_PSTN_RS_E_Complete_PSTN_Restart, NULL); break; case V5X_LE_PSTN_RS_E_MDU_CTRL_restart_complete: /* ignore */ break; default: OSMO_ASSERT(0); } } static void pstn_leprs1_restart(struct osmo_fsm_inst *fi, uint32_t event, void __attribute__((unused)) *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; switch (event) { case V5X_LE_PSTN_RS_E_MDU_CTRL_restart_complete: /* if timer TR2 is not running */ if (!osmo_timer_pending(&mgmt->timer_tr2)) break; /* stop timer TR2 */ stop_timer_tr2(mgmt); /* if restart of local PSTN ports is complete (always) or timer TR1 is not running */ if (1 || !osmo_timer_pending(&mgmt->timer_tr1)) { /* stop timer TR1 */ stop_timer_tr1(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_PSTN_RS_S_LEPRS0_RESTART_NULL, 0, 0); /* MDU-CTRL (restart complete) to PSTN ports was already performed */ } break; case V5X_LE_PSTN_RS_E_Complete_PSTN_Restart: /* if timer TR1 is not running */ if (!osmo_timer_pending(&mgmt->timer_tr1)) break; /* stop timer TR1 */ stop_timer_tr1(mgmt); /* if restart of local PSTN ports is complete (always) */ if (1) { /* stop timer TR2 */ stop_timer_tr2(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_PSTN_RS_S_LEPRS0_RESTART_NULL, 0, 0); } /* send restart complete to COMMON_CONTROL */ v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_RESTART_COMPLETE, NULL, NULL, NULL); if (mgmt->do_align) { /* unblock all PSTN user ports */ if (mgmt->acc_align && v5if->dialect == V5X_DIALECT_V52) osmo_fsm_inst_dispatch(mgmt->unblk_all_fi[UNBLK_ALL_PSTN], V5X_LE_UNBLK_ALL_E_Request_Unblock_Block, (void *)UNBLK_ALL_PSTN); else unblock_all_ports(v5if, false, false, true); } break; case V5X_LE_PSTN_RS_E_TIMEOUT_TR1: LOGPFSML(fi, LOGL_ERROR, "Local PSTN Restart timed out.\n"); /* if restart of local PSTN ports is complete (always) */ if (1) { /* stop timer TR2 */ stop_timer_tr2(mgmt); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_PSTN_RS_S_LEPRS0_RESTART_NULL, 0, 0); } /* send restart complete to COMMON_CONTROL */ v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_RESTART_COMPLETE, NULL, NULL, NULL); /* unblock all PSTN user ports */ unblock_all_ports(v5if, false, false, true); break; case V5X_LE_PSTN_RS_E_TIMEOUT_TR2: LOGPFSML(fi, LOGL_ERROR, "Remote PSTN Restart timed out.\n"); /* new state */ osmo_fsm_inst_state_chg(fi, V5X_LE_PSTN_RS_S_LEPRS0_RESTART_NULL, 0, 0); /* send restart complete to COMMON_CONTROL */ v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_RESTART_COMPLETE, NULL, NULL, NULL); if (mgmt->do_align) { /* unblock all PSTN user ports */ if (mgmt->acc_align && v5if->dialect == V5X_DIALECT_V52) osmo_fsm_inst_dispatch(mgmt->unblk_all_fi[UNBLK_ALL_PSTN], V5X_LE_UNBLK_ALL_E_Request_Unblock_Block, (void *)UNBLK_ALL_PSTN); else unblock_all_ports(v5if, false, false, true); } break; case V5X_LE_PSTN_RS_E_Request_PSTN_Restart: LOGPFSML(fi, LOGL_INFO, "PSTN Restart was requested locally.\n"); /* ignore */ break; case V5X_LE_PSTN_RS_E_MDU_CTRL_restart_request: LOGPFSML(fi, LOGL_INFO, "PSTN Restart was requested by remote.\n"); /* ignore */ break; default: OSMO_ASSERT(0); } } static const struct osmo_fsm_state v5x_le_pstn_rs_fsm_states[] = { [V5X_LE_PSTN_RS_S_LEPRS0_RESTART_NULL] = { .name = "RESTART NULL (LEPRS0)", .in_event_mask = S(V5X_LE_PSTN_RS_E_MDU_CTRL_restart_request) | S(V5X_LE_PSTN_RS_E_MDU_CTRL_restart_complete) | S(V5X_LE_PSTN_RS_E_Request_PSTN_Restart), .out_state_mask = S(V5X_LE_PSTN_RS_S_LEPRS1_RESTART), .action = pstn_leprs0_restart_null, }, [V5X_LE_PSTN_RS_S_LEPRS1_RESTART] = { .name = "RESTART (LEPRS1)", .in_event_mask = S(V5X_LE_PSTN_RS_E_MDU_CTRL_restart_complete) | S(V5X_LE_PSTN_RS_E_Complete_PSTN_Restart) | S(V5X_LE_PSTN_RS_E_TIMEOUT_TR1) | S(V5X_LE_PSTN_RS_E_TIMEOUT_TR2) | S(V5X_LE_PSTN_RS_E_MDU_CTRL_restart_request) | S(V5X_LE_PSTN_RS_E_Request_PSTN_Restart), .out_state_mask = S(V5X_LE_PSTN_RS_S_LEPRS0_RESTART_NULL), .action = pstn_leprs1_restart, }, }; struct osmo_fsm v5x_le_pstn_rs_fsm = { .name = "PSTN_restart", .states = v5x_le_pstn_rs_fsm_states, .num_states = ARRAY_SIZE(v5x_le_pstn_rs_fsm_states), .allstate_event_mask = 0, .allstate_action = NULL, .cleanup = NULL, .timer_cb = NULL, .log_subsys = DV5MGMT, .event_names = v5x_le_pstn_rs_fsm_event_names, }; const char *v5x_le_pstn_rs_fsm_state_name(struct osmo_fsm_inst *fi) { return v5x_le_pstn_rs_fsm_states[fi->state].name; } /***********************************************************************/ /* UNBLK/BLK ALL state FSM (5 instances used accelerated alignment) */ /***********************************************************************/ static int v5x_le_unblk_all_timer_cb(struct osmo_fsm_inst *fi) { if (fi->T == 1) osmo_fsm_inst_dispatch(fi, V5X_LE_PSTN_RS_E_TIMEOUT_TR1, NULL); if (fi->T == 2) osmo_fsm_inst_dispatch(fi, V5X_LE_PSTN_RS_E_TIMEOUT_TR2, NULL); return 0; } static void do_accelerated_alignment(struct v5x_interface *v5if, int ins) { switch (ins) { case UNBLK_ALL_PSTN_ISDN: unblock_all_ports(v5if, true, true, true); break; case UNBLK_ALL_PSTN: unblock_all_ports(v5if, true, false, true); break; case UNBLK_ALL_ISDN: unblock_all_ports(v5if, true, true, false); break; case BLK_ALL_PSTN: block_all_ports(v5if, true, false, true); break; case BLK_ALL_ISDN: block_all_ports(v5if, true, true, false); break; } } static void complete_accelerated_alignment(struct v5x_interface *v5if, int ins) { struct v5x_user_port *v5up; if (ins == BLK_ALL_PSTN || ins == BLK_ALL_ISDN) return; llist_for_each_entry(v5up, &v5if->user_ports, list) { if (ins == UNBLK_ALL_PSTN && !v5up->pstn.proto) continue; if (ins == UNBLK_ALL_ISDN && v5up->pstn.proto) continue; /* a complete set (ISDN/PSTN) was was unblocked, now we send blocking of actual blocked ports */ if ((ins == UNBLK_ALL_PSTN_ISDN || ins == UNBLK_ALL_PSTN || ins == UNBLK_ALL_ISDN) && !v5up->le_unblocked) { LOGV5UP(v5up, DV5MGMT, LOGL_INFO, " -> Send block indication to %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); v5x_port_mph_snd(v5up, MPH_BI); } /* a complete set (ISDN/PSTN) was was blocked, now we send unblocking of actual unblocked ports */ if ((ins == BLK_ALL_PSTN || ins == BLK_ALL_ISDN) && v5up->le_unblocked) { LOGV5UP(v5up, DV5MGMT, LOGL_INFO, " -> Send unblock request to %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); v5x_port_mph_snd(v5up, MPH_UBR); } } } static void unblk_all_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; /* instance */ long ins = (long)data; switch (event) { case V5X_LE_UNBLK_ALL_E_REQUEST: if (!mgmt->acc_align) { LOGPFSML(fi, LOGL_INFO, "Rejecting multiple port (un)blocking, It is not enabled!\n"); v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_REJECTED + ins*4, NULL, NULL, NULL); break; } /* sending accepted */ LOGPFSML(fi, LOGL_INFO, "Accepting multiple port (un)blocking, sending 'ACCEPTED' and perform it.\n"); v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_ACCEPTED + ins*4, NULL, NULL, NULL); /* start timer 2 and change state */ osmo_fsm_inst_state_chg(fi, V5X_LE_UNBLK_ALL_S_COMPLETED, TIMEOUT_TU2, 2); /* silently align user ports */ do_accelerated_alignment(v5if, ins); /* sending completed */ LOGPFSML(fi, LOGL_INFO, "Completed multiple port (un)blocking, sending 'COMPLETED'.\n"); v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_COMPLETED + ins*4, NULL, NULL, NULL); break; case V5X_LE_UNBLK_ALL_E_Request_Unblock_Block: /* start timer 1 and change state */ osmo_fsm_inst_state_chg(fi, V5X_LE_UNBLK_ALL_S_REQUESTED, TIMEOUT_TU1, 1); /* sending request */ LOGPFSML(fi, LOGL_INFO, "Requesting multiple port (un)blocking, sending 'REQUEST'.\n"); v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_REQUEST + ins*4, NULL, NULL, NULL); break; default: OSMO_ASSERT(0); } } static void unblk_all_requested(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; /* instance */ long ins = (long)data; switch (event) { case V5X_LE_UNBLK_ALL_E_REQUEST: /* sending accepted */ LOGPFSML(fi, LOGL_INFO, "Collision of multiple port (un)blocking, sending 'ACCEPTED'.\n"); v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_ACCEPTED + ins*4, NULL, NULL, NULL); break; case V5X_LE_UNBLK_ALL_E_ACCEPTED: LOGPFSML(fi, LOGL_INFO, "Remote accepted multiple port (un)blocking, perform it.\n"); /* start timer 2 and change state */ osmo_fsm_inst_state_chg(fi, V5X_LE_UNBLK_ALL_S_COMPLETED, TIMEOUT_TU2, 2); /* silently align user ports */ do_accelerated_alignment(v5if, ins); /* sending completed */ LOGPFSML(fi, LOGL_INFO, "Completed multiple port (un)blocking, sending 'COMPLETED'.\n"); v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_COMPLETED + ins*4, NULL, NULL, NULL); break; case V5X_LE_UNBLK_ALL_E_REJECTED: LOGPFSML(fi, LOGL_ERROR, "Multiple port (un)blocking was rejected, aborting.\n"); /* strop timer 1 and change state */ osmo_fsm_inst_state_chg(fi, V5X_LE_UNBLK_ALL_S_NULL, 0, 0); break; case V5X_LE_UNBLK_ALL_E_TIMEOUT_TU1: LOGPFSML(fi, LOGL_ERROR, "Multiple port (un)blocking timed out, aborting.\n"); /* change state */ osmo_fsm_inst_state_chg(fi, V5X_LE_UNBLK_ALL_S_NULL, 0, 0); break; default: OSMO_ASSERT(0); } } static void unblk_all_completed(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v5x_mgmt_proto *mgmt = fi->priv; struct v5x_interface *v5if = mgmt->interface; /* instance */ long ins = (long)data; switch (event) { case V5X_LE_UNBLK_ALL_E_COMPLETED: LOGPFSML(fi, LOGL_INFO, "Multiple port (un)blocking completed by remote.\n"); /* stop timer 2 and change state */ osmo_fsm_inst_state_chg(fi, V5X_LE_UNBLK_ALL_S_NULL, 0, 0); /* send MPH-UBR/BI to align individual ports */ complete_accelerated_alignment(v5if, ins); break; case V5X_LE_UNBLK_ALL_E_TIMEOUT_TU2: LOGPFSML(fi, LOGL_NOTICE, "Multiple port (un)blocking completed due to timeout.\n"); /* change state */ osmo_fsm_inst_state_chg(fi, V5X_LE_UNBLK_ALL_S_NULL, 0, 0); /* send MPH-UBR/BI to align individual ports */ complete_accelerated_alignment(v5if, ins); break; default: OSMO_ASSERT(0); } } static const struct osmo_fsm_state v5x_le_unblk_all_fsm_states[] = { [V5X_LE_UNBLK_ALL_S_NULL] = { .name = "NULL", .in_event_mask = S(V5X_LE_UNBLK_ALL_E_REQUEST) | S(V5X_LE_UNBLK_ALL_E_Request_Unblock_Block), .out_state_mask = S(V5X_LE_UNBLK_ALL_S_REQUESTED) | S(V5X_LE_UNBLK_ALL_S_COMPLETED), .action = unblk_all_null, }, [V5X_LE_UNBLK_ALL_S_REQUESTED] = { .name = "REQUESTED", .in_event_mask = S(V5X_LE_UNBLK_ALL_E_REQUEST) | S(V5X_LE_UNBLK_ALL_E_ACCEPTED) | S(V5X_LE_UNBLK_ALL_E_REJECTED) | S(V5X_LE_UNBLK_ALL_E_TIMEOUT_TU1), .out_state_mask = S(V5X_LE_UNBLK_ALL_S_COMPLETED) | S(V5X_LE_UNBLK_ALL_S_NULL), .action = unblk_all_requested, }, [V5X_LE_UNBLK_ALL_S_COMPLETED] = { .name = "COMPLETED", .in_event_mask = S(V5X_LE_UNBLK_ALL_E_COMPLETED) | S(V5X_LE_UNBLK_ALL_E_TIMEOUT_TU2), .out_state_mask = S(V5X_LE_UNBLK_ALL_S_NULL), .action = unblk_all_completed, }, }; struct osmo_fsm v5x_le_unblk_all_fsm = { .name = "UNBLOCK_BLOCK_ALL", .states = v5x_le_unblk_all_fsm_states, .num_states = ARRAY_SIZE(v5x_le_unblk_all_fsm_states), .allstate_event_mask = 0, .allstate_action = NULL, .cleanup = NULL, .timer_cb = v5x_le_unblk_all_timer_cb, .log_subsys = DV5MGMT, .event_names = v5x_le_unblk_all_fsm_event_names, }; const char *v5x_le_unblk_all_fsm_state_name(struct osmo_fsm_inst *fi) { return v5x_le_unblk_all_fsm_states[fi->state].name; } /***********************************************************************/ /* create and destroy of all state FSMs */ /***********************************************************************/ void v5x_le_mgmt_init(void) { int rc; rc = osmo_fsm_register(&v5x_le_system_fsm); OSMO_ASSERT(!rc); rc = osmo_fsm_register(&v5x_le_pstn_dl_fsm); OSMO_ASSERT(!rc); rc = osmo_fsm_register(&v5x_le_pstn_rs_fsm); OSMO_ASSERT(!rc); rc = osmo_fsm_register(&v5x_le_unblk_all_fsm); OSMO_ASSERT(!rc); LOGP(DV5MGMT, LOGL_NOTICE, "Using V5x management\n"); } struct v5x_mgmt_proto *v5x_le_mgmt_create(struct v5x_interface *v5if) { struct v5x_mgmt_proto *mgmt; const char *if_name = v5x_interface_name(v5if); int i; mgmt = talloc_zero(v5if, struct v5x_mgmt_proto); if (!mgmt) return NULL; mgmt->interface = v5if; mgmt->auto_restart = true; mgmt->pstn_enable_early = false; /* accellerated alignment when using V5.2 */ if (v5if->dialect == V5X_DIALECT_V52) mgmt->acc_align = true; mgmt->system_fi = osmo_fsm_inst_alloc(&v5x_le_system_fsm, mgmt, mgmt, LOGL_DEBUG, if_name); if (!mgmt->system_fi) goto error; mgmt->pstn_dl_fi = osmo_fsm_inst_alloc(&v5x_le_pstn_dl_fsm, mgmt, mgmt, LOGL_DEBUG, if_name); if (!mgmt->pstn_dl_fi) goto error; mgmt->pstn_rs_fi = osmo_fsm_inst_alloc(&v5x_le_pstn_rs_fsm, mgmt, mgmt, LOGL_DEBUG, if_name); if (!mgmt->pstn_rs_fi) goto error; if (v5if->dialect == V5X_DIALECT_V52) { for (i = 0; i < 5; i++) { mgmt->unblk_all_fi[i] = osmo_fsm_inst_alloc(&v5x_le_unblk_all_fsm, mgmt, mgmt, LOGL_DEBUG, if_name); if (!mgmt->unblk_all_fi[i]) goto error; } } /* timer events */ mgmt->timer_system.data = mgmt; mgmt->timer_system.cb = timeout_system; mgmt->timer_tr1.data = mgmt; mgmt->timer_tr1.cb = timeout_tr1; mgmt->timer_tr2.data = mgmt; mgmt->timer_tr2.cb = timeout_tr2; mgmt->timer_tc1.data = mgmt; mgmt->timer_tc1.cb = timeout_tc1; mgmt->timer_tc2.data = mgmt; mgmt->timer_tc2.cb = timeout_tc2; mgmt->timer_tc3.data = mgmt; mgmt->timer_tc3.cb = timeout_tc3; mgmt->timer_tc8.data = mgmt; mgmt->timer_tc8.cb = timeout_tc8; mgmt->timer_tc9.data = mgmt; mgmt->timer_tc9.cb = timeout_tc9; mgmt->timer_tv1.data = mgmt; mgmt->timer_tv1.cb = timeout_tv1; return mgmt; error: v5x_le_mgmt_destroy(mgmt); return NULL; } void v5x_le_mgmt_destroy(struct v5x_mgmt_proto *mgmt) { int i = 0; if (mgmt->system_fi) osmo_fsm_inst_free(mgmt->system_fi); if (mgmt->pstn_dl_fi) osmo_fsm_inst_free(mgmt->pstn_dl_fi); if (mgmt->pstn_rs_fi) osmo_fsm_inst_free(mgmt->pstn_rs_fi); for (i = 0; i < 5; i++) { if (mgmt->unblk_all_fi[i]) osmo_fsm_inst_free(mgmt->unblk_all_fi[i]); } stop_timer_system(mgmt); stop_timer_tr1(mgmt); stop_timer_tr2(mgmt); stop_timer_tc1(mgmt); stop_timer_tc2(mgmt); stop_timer_tc3(mgmt); stop_timer_tc8(mgmt); stop_timer_tc9(mgmt); stop_timer_tv1(mgmt); talloc_free(mgmt); } /***********************************************************************/ /* trigger processes */ /***********************************************************************/ /* do system startup after given delay */ void v5x_le_mgmt_start_delay(struct v5x_interface *v5if, int delay) { start_timer_system(v5if->mgmt, delay); } /* do system startup now */ int v5x_le_mgmt_start(struct v5x_interface *v5if) { if (v5if->mgmt->system_fi->state != V5X_LE_SYSTEM_S_LESYS0_SYSTEM_STARTUP && v5if->mgmt->system_fi->state != V5X_LE_SYSTEM_S_LESYS8_SYSTEM_DEACTIVATED) return -EINVAL; osmo_fsm_inst_dispatch(v5if->mgmt->system_fi, V5X_LE_SYSTEM_E_START, NULL); return 0; } /* do PSTN restart */ int v5x_le_pstn_restart(struct v5x_interface *v5if) { if (v5if->mgmt->system_fi->state != V5X_LE_SYSTEM_S_LESYS2_IN_SERVICE) return -EINVAL; osmo_fsm_inst_dispatch(v5if->mgmt->pstn_rs_fi, V5X_LE_PSTN_RS_E_Request_PSTN_Restart, NULL); return 0; } /* do alignment of all ports */ int v5x_le_align_ports(struct v5x_interface *v5if, int accelerated) { if (v5if->mgmt->system_fi->state != V5X_LE_SYSTEM_S_LESYS2_IN_SERVICE) return -EINVAL; /* unblock all ISDN user ports */ if (accelerated && v5if->dialect == V5X_DIALECT_V52) osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_PSTN_ISDN], V5X_LE_UNBLK_ALL_E_Request_Unblock_Block, (void *)UNBLK_ALL_PSTN_ISDN); else align_all_ports(v5if->mgmt->interface); return 0; } /***********************************************************************/ /* messages from other layers */ /***********************************************************************/ static bool v5x_le_mgmt_port_is_in_service(struct v5x_user_port *v5up) { if (v5up->interface->mgmt->system_fi->state != V5X_LE_SYSTEM_S_LESYS2_IN_SERVICE) return false; if (v5up->interface->mgmt->pstn_dl_fi->state != V5X_LE_PSTN_DL_S_LEPDL2_PSTN_IN_SERVICE) return false; if (v5up->interface->mgmt->pstn_rs_fi->state != V5X_LE_PSTN_RS_S_LEPRS0_RESTART_NULL) return false; return v5x_le_ctrl_is_in_service(v5up->ctrl); } /* messages from data link layer */ void v5x_le_mdl_rcv(struct v5x_interface *v5if, struct v5x_link *v5l, enum v5x_mgmt_prim prim, uint16_t dladdr) { switch (prim) { case MDL_ESTABLISH_ind: case MDL_ESTABLISH_cnf: switch (dladdr) { case V5X_DLADDR_CTRL: v5if->control.established = true; osmo_fsm_inst_dispatch(v5if->mgmt->system_fi, V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED, NULL); break; case V5X_DLADDR_PSTN: v5if->pstn.established = true; osmo_fsm_inst_dispatch(v5if->mgmt->pstn_dl_fi, V5X_LE_PSTN_DL_E_PSTN_DL_ESTABLISHED, NULL); break; case V52_DLADDR_LCP: v5if->lcp.established = true; /* start CONTROL_LINK FSM */ llist_for_each_entry(v5l, &v5if->links, list) v5x_le_ctrl_start(v5l->ctrl); break; case V52_DLADDR_BCC: v5if->bcc.established = true; break; case V52_DLADDR_PROTECTION: if (v5l == v5if->primary_link) v5if->protection.established[0] = true; if (v5l == v5if->secondary_link) v5if->protection.established[1] = true; break; } break; case MDL_RELEASE_ind: switch (dladdr) { case V5X_DLADDR_CTRL: v5if->control.established = false; osmo_fsm_inst_dispatch(v5if->mgmt->system_fi, V5X_LE_SYSTEM_E_CONTROL_DL_RELEASED, NULL); break; case V5X_DLADDR_PSTN: v5if->pstn.established = false; osmo_fsm_inst_dispatch(v5if->mgmt->pstn_dl_fi, V5X_LE_PSTN_DL_E_PSTN_DL_RELEASED, NULL); break; case V52_DLADDR_LCP: if (v5if->dialect != V5X_DIALECT_V52) break; v5if->lcp.established = false; /* re-establish DL */ if (v5if->mgmt->do_est) lapv5_dl_est_req(v5if->lcp.li, V52_DLADDR_LCP); /* stop CONTROL_LINK FSM */ llist_for_each_entry(v5l, &v5if->links, list) v5x_le_ctrl_stop(v5l->ctrl); break; case V52_DLADDR_BCC: if (v5if->dialect != V5X_DIALECT_V52) break; v5if->bcc.established = false; /* re-establish DL */ if (v5if->mgmt->do_est) lapv5_dl_est_req(v5if->bcc.li, V52_DLADDR_BCC); break; case V52_DLADDR_PROTECTION: if (v5if->dialect != V5X_DIALECT_V52) break; if (v5l == v5if->primary_link) { v5if->protection.established[0] = false; /* re-establish DL */ if (v5if->mgmt->do_est) lapv5_dl_est_req(v5if->protection.li[0], V52_DLADDR_PROTECTION); } if (v5l == v5if->secondary_link) { v5if->protection.established[1] = false; /* re-establish DL */ if (v5if->mgmt->do_est) lapv5_dl_est_req(v5if->protection.li[1], V52_DLADDR_PROTECTION); } break; } break; case MDL_ERROR_ind: case MDL_LAYER_1_FAILURE_ind: break; default: LOGV5IF(v5if, DV5MGMT, LOGL_NOTICE, "Ignoring data link message %d from address = %d\n", prim, dladdr); } } /* messages from common control layer */ void v5x_le_common_ctrl_rcv(struct v5x_interface *v5if, uint8_t cfi, uint8_t variant, uint8_t rej_cause, uint32_t interface_id) { struct variant_interface_id vi; LOGV5IF(v5if, DV5MGMT, LOGL_DEBUG, "Received common control message from control protocol. " "(cfi = %d, variant = %d, rej_cause = %d, interface_id = %d)\n", cfi, variant, rej_cause, interface_id); switch (cfi) { /* variant & interface ID */ case V5X_CTRL_ID_REQUEST_VARIANT_AND_INTERFACE_ID: LOGV5IF(v5if, DV5MGMT, LOGL_INFO, "AN requests variant and interface ID, replying.\n"); v5x_le_ctrl_common_snd(v5if, V5X_CTRL_ID_VARIANT_AND_INTERFACE_ID, NULL, &v5if->variant, &v5if->id); break; case V5X_CTRL_ID_VARIANT_AND_INTERFACE_ID: vi.variant = variant; vi.interface_id = interface_id; osmo_fsm_inst_dispatch(v5if->mgmt->system_fi, V5X_LE_SYSTEM_E_VARIANT_AND_ID, &vi); break; /* PSTN restart */ case V5X_CTRL_ID_RESTART_REQUEST: osmo_fsm_inst_dispatch(v5if->mgmt->pstn_rs_fi, V5X_LE_PSTN_RS_E_MDU_CTRL_restart_request, NULL); break; case V5X_CTRL_ID_RESTART_COMPLETE: osmo_fsm_inst_dispatch(v5if->mgmt->pstn_rs_fi, V5X_LE_PSTN_RS_E_MDU_CTRL_restart_complete, NULL); break; /* accelerated aligment: request */ case V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_REQUEST: LOGV5IF(v5if, DV5MGMT, LOGL_INFO, "Unblock of all related PSTN and ISDN ports received.\n"); osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_PSTN_ISDN], V5X_LE_UNBLK_ALL_E_REQUEST, (void *)UNBLK_ALL_PSTN_ISDN); break; case V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_REQUEST: LOGV5IF(v5if, DV5MGMT, LOGL_INFO, "Unblock of all related PSTN ports received.\n"); osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_PSTN], V5X_LE_UNBLK_ALL_E_REQUEST, (void *)UNBLK_ALL_PSTN); break; case V5X_CTRL_ID_UNBLK_ALL_REL_ISDN_REQUEST: LOGV5IF(v5if, DV5MGMT, LOGL_INFO, "Unblock of all related ISDN ports received.\n"); osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_ISDN], V5X_LE_UNBLK_ALL_E_REQUEST, (void *)UNBLK_ALL_ISDN); break; case V5X_CTRL_ID_BLK_ALL_PSTN_REQUEST: LOGV5IF(v5if, DV5MGMT, LOGL_INFO, "Block of all related PSTN ports received.\n"); osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[BLK_ALL_PSTN], V5X_LE_UNBLK_ALL_E_REQUEST, (void *)BLK_ALL_PSTN); break; case V5X_CTRL_ID_BLK_ALL_ISDN_REQUEST: LOGV5IF(v5if, DV5MGMT, LOGL_INFO, "Block of all related ISDN ports received.\n"); osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[BLK_ALL_ISDN], V5X_LE_UNBLK_ALL_E_REQUEST, (void *)BLK_ALL_ISDN); break; /* accelerated aligment: accepted */ case V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_ACCEPTED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_PSTN_ISDN], V5X_LE_UNBLK_ALL_E_ACCEPTED, (void *)UNBLK_ALL_PSTN_ISDN); break; case V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ACCEPTED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_PSTN], V5X_LE_UNBLK_ALL_E_ACCEPTED, (void *)UNBLK_ALL_PSTN); break; case V5X_CTRL_ID_UNBLK_ALL_REL_ISDN_ACCEPTED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_ISDN], V5X_LE_UNBLK_ALL_E_ACCEPTED, (void *)UNBLK_ALL_ISDN); break; case V5X_CTRL_ID_BLK_ALL_PSTN_ACCEPTED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[BLK_ALL_PSTN], V5X_LE_UNBLK_ALL_E_ACCEPTED, (void *)BLK_ALL_PSTN); break; case V5X_CTRL_ID_BLK_ALL_ISDN_ACCEPTED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[BLK_ALL_ISDN], V5X_LE_UNBLK_ALL_E_ACCEPTED, (void *)BLK_ALL_ISDN); break; /* accelerated aligment: rejected */ case V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_REJECTED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_PSTN_ISDN], V5X_LE_UNBLK_ALL_E_REJECTED, (void *)UNBLK_ALL_PSTN_ISDN); break; case V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_REJECTED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_PSTN], V5X_LE_UNBLK_ALL_E_REJECTED, (void *)UNBLK_ALL_PSTN); break; case V5X_CTRL_ID_UNBLK_ALL_REL_ISDN_REJECTED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_ISDN], V5X_LE_UNBLK_ALL_E_REJECTED, (void *)UNBLK_ALL_ISDN); break; case V5X_CTRL_ID_BLK_ALL_PSTN_REJECTED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[BLK_ALL_PSTN], V5X_LE_UNBLK_ALL_E_REJECTED, (void *)BLK_ALL_PSTN); break; case V5X_CTRL_ID_BLK_ALL_ISDN_REJECTED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[BLK_ALL_ISDN], V5X_LE_UNBLK_ALL_E_REJECTED, (void *)BLK_ALL_ISDN); break; /* accelerated aligment: completed */ case V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_ISDN_COMPLETED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_PSTN_ISDN], V5X_LE_UNBLK_ALL_E_COMPLETED, (void *)UNBLK_ALL_PSTN_ISDN); break; case V5X_CTRL_ID_UNBLK_ALL_REL_PSTN_COMPLETED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_PSTN], V5X_LE_UNBLK_ALL_E_COMPLETED, (void *)UNBLK_ALL_PSTN); break; case V5X_CTRL_ID_UNBLK_ALL_REL_ISDN_COMPLETED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[UNBLK_ALL_ISDN], V5X_LE_UNBLK_ALL_E_COMPLETED, (void *)UNBLK_ALL_ISDN); break; case V5X_CTRL_ID_BLK_ALL_PSTN_COMPLETED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[BLK_ALL_PSTN], V5X_LE_UNBLK_ALL_E_COMPLETED, (void *)BLK_ALL_PSTN); break; case V5X_CTRL_ID_BLK_ALL_ISDN_COMPLETED: osmo_fsm_inst_dispatch(v5if->mgmt->unblk_all_fi[BLK_ALL_ISDN], V5X_LE_UNBLK_ALL_E_COMPLETED, (void *)BLK_ALL_ISDN); break; default: LOGV5IF(v5if, DV5MGMT, LOGL_NOTICE, "Ignoring common control message with cfi = %d\n", cfi); } } void v5x_le_mgmt_port_unblock(struct v5x_user_port *v5up) { v5up->le_unblocked = true; if (v5x_le_mgmt_port_is_in_service(v5up)) { v5x_port_mph_snd(v5up, MPH_UBR); if (v5up->pstn.proto && v5up->an_unblocked) { LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, "Send port unblocked control to PSTN port.\n"); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_unblocked); } } if (!v5up->an_unblocked) { LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Reply with blocking, because AN is blocked.\n"); uint8_t ctrl = PH_CTRL_BLOCK; ph_socket_tx_msg(&v5up->ph_socket, v5up->type == V5X_USER_TYPE_ISDN ? 3 : 0, PH_PRIM_CTRL_IND, &ctrl, 1); } } void v5x_le_mgmt_port_block(struct v5x_user_port *v5up) { v5up->le_unblocked = false; if (v5x_le_mgmt_port_is_in_service(v5up)) { v5x_port_mph_snd(v5up, MPH_BI); if (v5up->pstn.proto) { LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, "Send port blocked control to PSTN port.\n"); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_blocked); } } } /* received management primitives from LE (NAT protocol via PH-socket) */ void v5x_le_nat_ph_rcv(struct v5x_user_port *v5up, uint8_t prim, uint8_t *data, int length) { ph_socket_t *s = &v5up->ph_socket; switch (prim) { case PH_PRIM_CTRL_REQ: if (length && *data == PH_CTRL_UNBLOCK) { LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received unblocking from LE of %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); v5x_le_mgmt_port_unblock(v5up); } if (length && *data == PH_CTRL_BLOCK) { LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received blocking from LE of %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); v5x_le_mgmt_port_block(v5up); } break; case PH_PRIM_ACT_REQ: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received activation from LE of %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); if (!v5up->an_unblocked) { ph_socket_tx_msg(s, 3, PH_PRIM_DACT_IND, NULL, 0); break; } v5x_port_mph_snd(v5up, MPH_AR); break; case PH_PRIM_DACT_REQ: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received deactivation from LE of %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); v5x_port_mph_snd(v5up, MPH_DR); break; default: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received unknown prim %d from LE for %s port.\n", prim, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); } } /* received management primitives from AN (via port control protocol) */ void v5x_le_port_mph_rcv(struct v5x_user_port *v5up, enum v5x_mph_prim prim, uint8_t perf_grading) { switch (prim) { case MPH_UBR: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received unblocking request from AN of %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); if (v5up->le_unblocked) { LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Reply with unblocking request, because port available at LE.\n"); v5x_port_mph_snd(v5up, MPH_UBR); block_unblock_socket(v5up, false); if (v5up->pstn.proto) { LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, "Send port unblocked control to PSTN port.\n"); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_unblocked); } } else { LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Reply with blocking indication, because port unavailable at LE.\n"); v5up->an_unblocked = false; v5x_port_mph_snd(v5up, MPH_BI); } break; case MPH_UBI: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received unblocking indication from AN of %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); block_unblock_socket(v5up, false); if (v5up->pstn.proto) { LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, "Send port unblocked control to PSTN port.\n"); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_unblocked); } break; case MPH_BR: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received blocking request from AN of %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Reply with blocking indication.\n"); v5x_port_mph_snd(v5up, MPH_BI); block_unblock_socket(v5up, true); if (v5up->pstn.proto) { LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, "Send port blocked control to PSTN port.\n"); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_blocked); } break; case MPH_BI: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received blocking indication from AN of %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); block_unblock_socket(v5up, true); if (v5up->pstn.proto) { LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, "Send port blocked control to PSTN port.\n"); v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_blocked); } break; case MPH_AI: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received activation indication from AN of ISDN port.\n"); ph_socket_tx_msg(&v5up->ph_socket, 3, PH_PRIM_ACT_IND, NULL, 0); break; case MPH_AWI: break; case MPH_DSAI: break; case MPH_DI: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received deactivation indication from AN of ISDN port.\n"); ph_socket_tx_msg(&v5up->ph_socket, 3, PH_PRIM_DACT_IND, NULL, 0); break; case MPH_GI: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received performance grading from AN for ISDN port: %s\n", (perf_grading) ? "Degraded" : "Normal grade"); break; default: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received unknown prim %d from AN for %s port.\n", prim, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); } } /* receive MDU primitives from PSTN protocol */ void v5x_le_pstn_mdu_rcv(struct v5x_user_port *v5up, enum v5x_mgmt_prim prim) { switch (prim) { case MDU_error_ind: LOGV5UP(v5up, DV5MGMT, LOGL_NOTICE, "Received MDU-error_indication from PSTN protocol of port.\n"); break; case MDU_CTRL_port_restart_ack: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received restart acknowledge from PSTN protocol of port.\n"); break; default: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Received unknown prim %d from PSTN protocol for port.\n", prim); } } /* flush all channels from non-operational link */ static void flush_link(struct v5x_link *v5l) { struct v5x_user_port *v5up; int t; for (t = 1; t <= 31; t++) { if (!v5l->ts[t].b_channel) continue; if ((v5up = v5l->ts[t].v5up)) { if (v5up->ts[0] == &v5l->ts[t]) { v5up->ts[0] = NULL; ph_deactivate_req(&v5l->ts[t]); } if (v5up->ts[1] == &v5l->ts[t]) { v5up->ts[1] = NULL; ph_deactivate_req(&v5l->ts[t]); } v5l->ts[t].v5up = NULL; } } } /* * Part 2: LCP handling */ /* receive MDU primitives from LCP protocol */ void v52_le_lcp_mdu_rcv(struct v5x_link *v5l, enum v5x_mgmt_prim prim) { switch (prim) { case MDU_AI: LOGV5L(v5l, DV5MGMT, LOGL_INFO, "Link is active.\n"); break; case MDU_DI: LOGV5L(v5l, DV5MGMT, LOGL_INFO, "Link is inactive.\n"); break; case MDU_LAI: LOGV5L(v5l, DV5MGMT, LOGL_INFO, "Link becomes active in blocked state, try unblocking.\n"); v52_le_lcp_mdu_snd(v5l, MDU_LUBR); break; case MDU_IDReq: LOGV5L(v5l, DV5MGMT, LOGL_INFO, "Remote side perform link ID check of link, replying.\n"); v52_le_lcp_mdu_snd(v5l, MDU_IDAck); break; case MDU_IDAck: break; case MDU_IDRel: LOGV5L(v5l, DV5MGMT, LOGL_INFO, "Remote side ID check done.\n"); break; case MDU_IDRej: break; case MDU_EIg: break; case MDU_LUBR: LOGV5L(v5l, DV5MGMT, LOGL_INFO, "Remote side unblocks link, replying.\n"); v52_le_lcp_mdu_snd(v5l, MDU_LUBR); break; case MDU_LUBI: LOGV5L(v5l, DV5MGMT, LOGL_INFO, "Remote side link is now unblocked.\n"); break; case MDU_LBI: LOGV5L(v5l, DV5MGMT, LOGL_INFO, "LCP indicated blocked link.\n"); flush_link(v5l); break; case MDU_LBR: case MDU_LBRN: LOGV5L(v5l, DV5MGMT, LOGL_INFO, "Remote request blocking of link link, replying.\n"); flush_link(v5l); v52_le_lcp_mdu_snd(v5l, MDU_LBI); break; default: LOGV5L(v5l, DV5MGMT, LOGL_INFO, "Received unknown prim %d from LCP protocol for link.\n", prim); } } /* * Part 3: forwarding messages between PSTN and gateway application */ static const struct value_string v5x_fe_prim_names[] = { { FE_establish_req, "FE-establish_req" }, { FE_establish_ack_ind, "FE-establish_ack_ind" }, { FE_establish_ind, "FE-establish_ind" }, { FE_establish_ack_req, "FE-establish_ack_req" }, { FE_line_signal_req, "FE-line_signal_req" }, { FE_line_signal_ind, "FE-line_signal_ind" }, { FE_protocol_param_req, "FE-protocol_param_req" }, { FE_disconnect_req, "FE-disconnect_req" }, { FE_disconnect_compl_req, "FE-disconnect_compl_req" }, { FE_disconnect_compl_ind, "FE-disconnect_compl_ind" }, { 0, NULL } }; /* receive FE-message from NAT (PH-socket) and forward it to the PSTN protocol */ void v5x_le_nat_fe_rcv(struct v5x_user_port *v5up, struct msgb *msg) { enum v5x_fe_prim prim; prim = *msg->data; msgb_pull(msg, 1); if (!v5x_le_port_pstn_is_operational(v5up->port_fi)) { LOGV5UP(v5up, DV5MGMT, LOGL_NOTICE, "Dropping (NAT->LE) message of non-operational PSTN port."); msgb_free(msg); return; } LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "FE message: %s NAT -> PSTN_LE of PSTN port.\n", get_value_string(v5x_fe_prim_names, prim)); v5x_le_pstn_fe_snd(v5up, prim, msg); } /* receive FE-message from PSTN protocol and forward it to NAT (PH-socket) */ void v5x_le_pstn_fe_rcv(struct v5x_user_port *v5up, enum v5x_fe_prim prim, struct msgb *msg) { *msgb_push(msg, 1) = prim; if (!v5x_le_port_pstn_is_operational(v5up->port_fi) && prim != FE_disconnect_compl_ind) { LOGV5UP(v5up, DV5MGMT, LOGL_NOTICE, "Dropping (AN->NAT) message of non-operational PSTN port.\n"); msgb_free(msg); return; } LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "FE message: %s PSTN_LE -> NAT of PSTN port.\n", get_value_string(v5x_fe_prim_names, prim)); ph_socket_tx_msg(&v5up->ph_socket, 0, PH_PRIM_DATA_IND, msg->data, msg->len); msgb_free(msg); } /* * Part 4: BCC channel handling, also activation/deactivation for V5.1 */ /* assign channel */ void v5x_le_channel_assign(struct v5x_user_port *v5up, int channel) { struct v5x_interface *v5if = v5up->interface; struct v5x_link *v5l = NULL; int found, t; OSMO_ASSERT(channel >= 1 && channel <= 2); /* reset echo canceler state */ v5x_echo_reset(&v5up->ep[channel - 1], v5up->use_line_echo); /* the channels are pre-assigned with V5.1, so only activation state is set */ if (v5if->dialect == V5X_DIALECT_V51) { if (!v5up->ts[channel - 1]) { LOGV5UP(v5up, DV5MGMT, LOGL_ERROR, "Cannot activate channel, because no TS is assigned for %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); return; } if (v5up->ts[channel - 1]->b_activated) return; LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, "Activating channel %d for %s port at TS %d of link %d.\n", channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->ts[channel - 1]->nr, v5up->ts[channel - 1]->link->id); ph_activate_req(v5up->ts[channel - 1]); return; } /* channel already assigned, just activate it, if not already */ if (v5up->ts[channel - 1]) { ph_activate_req(v5up->ts[channel - 1]); return; } /* hunt for a channel */ found = 0; llist_for_each_entry(v5l, &v5if->links, list) { if (!v52_le_lcp_is_operational(v5l->fi)) { LOGV5UP(v5up, DV5MGMT, LOGL_NOTICE, "While searching for a bearer channel: Skipping link with ID %d, " "because it is not operational.\n", v5l->id); continue; } for (t = 1; t <= 31; t++) { if (!v5l->ts[t].b_channel) continue; if (!v5l->ts[t].v5up) { found = 1; break; } } if (found) break; } if (!found) { LOGV5UP(v5up, DV5MGMT, LOGL_NOTICE, "No free channel found.\n"); return; } /* assign channel */ v5l->ts[t].v5up = v5up; v5up->ts[channel - 1] = &v5l->ts[t]; /* request BCC to allocate channel on AN */ v52_le_bcc_mdu_snd(v5if, v5up->nr, (v5up->type == V5X_USER_TYPE_ISDN), v5up->ts[channel - 1]->link->id, v5up->ts[channel - 1]->nr, 1, channel, NULL, (v5if->use_capability) ? &v5if->capability : NULL, MDU_BCC_allocation_req); } /* unassign channel */ void v5x_le_channel_unassign(struct v5x_user_port *v5up, int channel) { struct v5x_interface *v5if = v5up->interface; /* the channels are pre-assigned with V5.1, so only activation state is set */ if (v5if->dialect == V5X_DIALECT_V51) { if (!v5up->ts[channel - 1]) { LOGV5UP(v5up, DV5MGMT, LOGL_ERROR, "Cannot deactivate channel, because no TS is assigned for %s port.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN"); return; } if (!v5up->ts[channel - 1]->b_activated) return; LOGV5UP(v5up, DV5MGMT, LOGL_DEBUG, "Deactivating channel %d for %s port at TS %d of link %d.\n", channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->ts[channel - 1]->nr, v5up->ts[channel - 1]->link->id); ph_deactivate_req(v5up->ts[channel - 1]); return; } /* if not assigned */ if (!v5up->ts[channel - 1]) return; /* deactivate */ ph_deactivate_req(v5up->ts[channel - 1]); /* unassign or abort ongoing BCC allocation */ v52_le_bcc_mdu_snd(v5if, v5up->nr, v5up->type == V5X_USER_TYPE_ISDN, v5up->ts[channel - 1]->link->id, v5up->ts[channel - 1]->nr, 1, channel, NULL, 0, MDU_BCC_deallocation_req); } int v52_le_bcc_mdu_rcv(struct v5x_interface *v5if, uint8_t link_id, uint8_t ts, uint8_t __attribute__((unused)) *isdn_multislot, enum v5x_mgmt_prim prim, const uint8_t *cause, uint8_t cause_len) { struct v5x_link *v5l; struct v5x_user_port *v5up; int channel = 0; if (ts > 31) { LOGV5IF(v5if, DV5MGMT, LOGL_ERROR, "TS %d out of range.\n", ts); return -EINVAL; } v5l = v5x_link_find_id(v5if, link_id); if (!v5l) { LOGV5IF(v5if, DV5MGMT, LOGL_ERROR, "Invalid link ID %d.\n", link_id); return -EINVAL; } v5up = v5l->ts[ts].v5up; if (!v5up) { LOGV5L(v5l, DV5MGMT, LOGL_NOTICE, "TS %d of link ID %d is not assigned.\n", ts, link_id); return 0; } if (v5up->ts[0] == &v5l->ts[ts]) channel = 1; if (v5up->ts[1] == &v5l->ts[ts]) channel = 2; OSMO_ASSERT(channel); switch (prim) { case MDU_BCC_allocation_conf: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Assigned channel %d for %s port at TS %d of link %d.\n", channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", ts, link_id); ph_activate_req(v5up->ts[channel - 1]); break; case MDU_BCC_deallocation_conf: LOGV5UP(v5up, DV5MGMT, LOGL_INFO, "Unassigned channel %d for %s port at TS %d of link %d.\n", channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", ts, link_id); v5l->ts[ts].v5up = NULL; v5up->ts[0] = NULL; break; case MDU_BCC_allocation_reject_ind: case MDU_BCC_deallocation_reject_ind: if (cause_len < 1) break; LOGV5UP(v5up, DV5MGMT, LOGL_ERROR, "Failed to (un)assign channel %d for %s port at TS %d of link %d: %s.\n", channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", ts, link_id, get_value_string(v52_bcc_reject_cause_type_str, cause[0] & 0x7f)); if (cause_len > 1) LOGV5UP(v5up, DV5MGMT, LOGL_ERROR, " -> Diagnostic: %s\n", osmo_hexdump(cause + 1, cause_len - 1)); v5l->ts[ts].v5up = NULL; v5up->ts[0] = NULL; break; case MDU_BCC_protocol_error_ind: if (cause_len < 1) break; LOGV5UP(v5up, DV5MGMT, LOGL_ERROR, "Failed to (un)assign channel %d for %s port at TS %d of link %d: %s.\n", channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", ts, link_id, get_value_string(v5x_cause_type_str, cause[0] & 0x7f)); if (cause_len > 1) LOGV5UP(v5up, DV5MGMT, LOGL_ERROR, " -> Diagnostic: %s\n", osmo_hexdump(cause + 1, cause_len - 1)); if (cause_len >= 3 && cause[1] == 0x20 && cause[2] == 0x47) { LOGV5UP(v5up, DV5MGMT, LOGL_ERROR, "AN rejects information-transfer-capability information element, " "please disable via VTY!\n"); } v5l->ts[ts].v5up = NULL; v5up->ts[0] = NULL; break; default: LOGV5UP(v5up, DV5MGMT, LOGL_ERROR, "Failed to (un)assign channel %d for %s port at TS %d of link %d.\n", channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", ts, link_id); } return 0; } /* * Part 5: Protection protocol handling */ static void switchover_request_link(struct v5x_link *v5l, uint8_t link_id, uint8_t ts) { LOGV5L(v5l, DV5MGMT, LOGL_NOTICE, "Switch over request from AN to link %d and TS %d.\n", link_id, ts); if (!v5l) { LOGV5L(v5l, DV5MGMT, LOGL_ERROR, " -> Rejecting, because requested link is not primary, nor secondary.\n"); v52_le_pp_mdu_snd(v5l->interface, MDU_Protection_switch_over_rej, link_id, ts, V52_PP_REJECT_CAUSE_T_TARGET_CC_NOT_PROV); return; } if (ts != v5l->c_channel[0].ts->nr) { LOGV5L(v5l, DV5MGMT, LOGL_ERROR, " -> Rejecting, because requested TS is not a C-channel.\n"); v52_le_pp_mdu_snd(v5l->interface, MDU_Protection_switch_over_rej, link_id, ts, V52_PP_REJECT_CAUSE_T_TARGET_CC_NOT_PROV); return; } if (!v52_le_lcp_is_operational(v5l->fi)) { LOGV5L(v5l,DV5MGMT, LOGL_ERROR, " -> Rejecting, because requested link is not operational.\n"); v52_le_pp_mdu_snd(v5l->interface, MDU_Protection_switch_over_rej, link_id, ts, V52_PP_REJECT_CAUSE_T_TARGET_CC_NOT_OPER); return; } if (v5l == v5l->interface->cc_link) { LOGV5L(v5l, DV5MGMT, LOGL_ERROR, " -> Rejecting, because requested allocation exists already.\n"); v52_le_pp_mdu_snd(v5l->interface, MDU_Protection_switch_over_rej, link_id, ts, V52_PP_REJECT_CAUSE_T_ALLOC_EXISTS_ALREADY); return; } LOGV5L(v5l, DV5MGMT, LOGL_NOTICE, "Sending switch over command to AN.\n"); v52_le_pp_mdu_snd(v5l->interface, MDU_Protection_switch_over_com, link_id, ts, 0); } static void switchover_request(struct v5x_interface *v5if) { LOGV5IF(v5if, DV5MGMT, LOGL_NOTICE, "Switch over request from AN, due to link failure.\n"); if (v5if->primary_link == v5if->cc_link) { LOGV5IF(v5if, DV5MGMT, LOGL_NOTICE, "Sending switch over command to AN. (go to secondary link)\n"); v52_le_pp_mdu_snd(v5if, MDU_Protection_switch_over_com, v5if->secondary_link->id, 16, 0); } if (v5if->secondary_link == v5if->cc_link) { LOGV5IF(v5if, DV5MGMT, LOGL_NOTICE, "Sending switch over command to AN. (go to primary link)\n"); v52_le_pp_mdu_snd(v5if, MDU_Protection_switch_over_com, v5if->primary_link->id, 16, 0); } } int v52_le_pp_mdu_rcv(struct v5x_interface *v5if, enum v5x_mgmt_prim prim, uint8_t link_id, uint8_t ts, const uint8_t *cause, uint8_t cause_len) { struct v5x_link *v5l = NULL; /* get link from what the AN is talking about */ if (link_id == v5if->primary_link->id) v5l = v5if->primary_link; if (link_id == v5if->secondary_link->id) v5l = v5if->secondary_link; switch (prim) { case MDU_Protection_switch_over_ack: LOGV5L(v5l, DV5MGMT, LOGL_NOTICE, "Switch over request from LE to link %d and TS %d was acked.\n", link_id, ts); LOGV5L(v5l, DV5MGMT, LOGL_NOTICE, "Performing switch over.\n"); if (v5l) v5if->cc_link = v5l; break; case MDU_Protection_switch_over_req: if (ts) switchover_request_link(v5l, link_id, ts); else switchover_request(v5if); break; case MDU_Protection_switch_over_reject_ind: if (cause_len < 1) break; LOGV5L(v5l, DV5MGMT, LOGL_ERROR, "Switch over to link %d and TS %d was rejected by AN: %s.\n", link_id, ts, get_value_string(v52_pp_reject_cause_type_str, cause[0] & 0x7f)); break; case MDU_Protection_switch_over_error_ind: LOGV5L(v5l, DV5MGMT, LOGL_NOTICE, "Switch over request from LE to link %d and TS %d failed.\n", link_id, ts); break; case MDU_Protection_reset_SN_ind: LOGV5L(v5l, DV5MGMT, LOGL_NOTICE, "AN resets serial numbers at Protection protocol.\n"); break; case MDU_Protection_reset_SN_com: LOGV5L(v5l, DV5MGMT, LOGL_NOTICE, "LE resets serial numbers at Protection protocol.\n"); break; case MDU_Protection_reset_SN_ack: LOGV5L(v5l, DV5MGMT, LOGL_NOTICE, "AN acknowledges reset serial numbers at Protection protocol.\n"); break; case MDU_Protection_reset_SN_error_ind: LOGV5L(v5l, DV5MGMT, LOGL_NOTICE, "Switch over request failed, due to serial numbers reset at Protection " "protocol.\n"); break; case MDU_Protection_protocol_error_ind: if (cause_len < 1) break; LOGV5L(v5l, DV5MGMT, LOGL_ERROR, "Protocol error: %s.\n", get_value_string(v5x_cause_type_str, cause[0] & 0x7f)); if (cause_len > 1) LOGV5L(v5l, DV5MGMT, LOGL_ERROR, " -> Diagnostic: %s\n", osmo_hexdump(cause + 1, cause_len - 1)); break; default: LOGV5L(v5l, DV5MGMT, LOGL_ERROR, "Unknown message primitive %d.\n", prim); } return 0; }