osmo-v5/src/v5x_le_management.c

2544 lines
83 KiB
C

/* Management information forwarding between AN <-> LE */
/* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
*
* 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 <stdint.h>
#include <errno.h>
#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;
LOGP(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;
LOGP(DV5MGMT, LOGL_INFO, " -> Set %s port %d into blocked state.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
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);
LOGP(DV5MGMT, LOGL_DEBUG, " -> Send port blocked control to PSTN port %d.\n", v5up->nr);
v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_blocked);
} else {
if (silently)
v5x_le_port_isdn_block(v5up->port_fi);
}
}
if (!any)
LOGP(DV5MGMT, LOGL_INFO, " -> Not any port to unblock.\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;
LOGP(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;
LOGP(DV5MGMT, LOGL_INFO, " -> Set %s port %d into unblocked state.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
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);
LOGP(DV5MGMT, LOGL_DEBUG, " -> Send port unblocked control to PSTN port %d.\n", v5up->nr);
v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_unblocked);
} else {
if (silently)
v5x_le_port_isdn_unblock(v5up->port_fi);
}
}
if (!any)
LOGP(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;
LOGP(DV5MGMT, LOGL_INFO, "Aligning all ports:\n");
llist_for_each_entry(v5up, &v5if->user_ports, list) {
if (v5up->le_unblocked) {
LOGP(DV5MGMT, LOGL_INFO, " -> Set %s port %d into unblocked state.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
any = true;
v5x_port_mph_snd(v5up, MPH_UBR);
if (v5up->pstn.proto) {
LOGP(DV5MGMT, LOGL_DEBUG, " -> Send port unblocked control to PSTN port %d.\n",
v5up->nr);
v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_unblocked);
}
} else {
LOGP(DV5MGMT, LOGL_INFO, " -> Set %s port %d into blocked state.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
any = true;
v5x_port_mph_snd(v5up, MPH_BI);
if (v5up->pstn.proto) {
LOGP(DV5MGMT, LOGL_DEBUG, " -> Send port blocked control to PSTN port %d.\n",
v5up->nr);
v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_blocked);
}
}
}
if (!any)
LOGP(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)
{
LOGP(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)
{
LOGP(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)
{
LOGP(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)
{
LOGP(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)
{
LOGP(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)
{
LOGP(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)
{
LOGP(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)
{
LOGP(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)
{
LOGP(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)
{
LOGP(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)
{
LOGP(DV5MGMT, LOGL_DEBUG, "Stop timer TR1.\n");
osmo_timer_del(&mgmt->timer_tr1);
}
static void stop_timer_tr2(struct v5x_mgmt_proto *mgmt)
{
LOGP(DV5MGMT, LOGL_DEBUG, "Stop timer TR2.\n");
osmo_timer_del(&mgmt->timer_tr2);
}
static void stop_timer_tc1(struct v5x_mgmt_proto *mgmt)
{
LOGP(DV5MGMT, LOGL_DEBUG, "Stop timer TC1.\n");
osmo_timer_del(&mgmt->timer_tc1);
}
static void stop_timer_tc2(struct v5x_mgmt_proto *mgmt)
{
LOGP(DV5MGMT, LOGL_DEBUG, "Stop timer TC2.\n");
osmo_timer_del(&mgmt->timer_tc2);
}
static void stop_timer_tc3(struct v5x_mgmt_proto *mgmt)
{
LOGP(DV5MGMT, LOGL_DEBUG, "Stop timer TC3.\n");
osmo_timer_del(&mgmt->timer_tc3);
}
static void stop_timer_tc8(struct v5x_mgmt_proto *mgmt)
{
LOGP(DV5MGMT, LOGL_DEBUG, "Stop timer TC8.\n");
osmo_timer_del(&mgmt->timer_tc8);
}
static void stop_timer_tc9(struct v5x_mgmt_proto *mgmt)
{
LOGP(DV5MGMT, LOGL_DEBUG, "Stop timer TC9.\n");
osmo_timer_del(&mgmt->timer_tc9);
}
static void stop_timer_tv1(struct v5x_mgmt_proto *mgmt)
{
LOGP(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;
LOGP(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;
LOGP(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;
LOGP(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;
LOGP(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;
LOGP(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;
LOGP(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;
LOGP(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;
LOGP(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;
LOGP(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, false);
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:
LOGP(DV5MGMT, LOGL_INFO, "Perform system startup\n");
system_startup(mgmt);
break;
case V5X_LE_SYSTEM_E_CONTROL_DL_ESTABLISHED:
LOGP(DV5MGMT, 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 */
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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;
LOGP(DV5MGMT, 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_local) {
LOGP(DV5MGMT, LOGL_ERROR, "Variant does not match: AN(%d) != LE(%d)\n",
v5if->variant_remote, v5if->variant_local);
}
if (v5if->id_remote != v5if->id_local) {
LOGP(DV5MGMT, LOGL_ERROR, "Interface ID does not match: AN(%d) != LE(%d)\n",
v5if->id_remote, v5if->id_local);
}
if (v5if->variant_remote != v5if->variant_local || v5if->id_remote != v5if->id_local) {
LOGP(DV5MGMT, LOGL_ERROR, "************************************************************\n");
LOGP(DV5MGMT, LOGL_ERROR, "You must set matching Variant and Interface ID at AN and LE!\n");
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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);
/* 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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) {
LOGP(DV5MGMT, LOGL_INFO, "Send restart request to PSTN port %d.\n", v5up->nr);
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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, LOGL_INFO, "PSTN Restart was requested locally.\n");
/* ignore */
break;
case V5X_LE_PSTN_RS_E_MDU_CTRL_restart_request:
LOGP(DV5MGMT, 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) {
LOGP(DV5MGMT, LOGL_INFO, " -> Send block indication to %s port %d.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
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) {
LOGP(DV5MGMT, LOGL_INFO, " -> Send unblock request to %s port %d.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
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) {
LOGP(DV5MGMT, 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 */
LOGP(DV5MGMT, 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 */
LOGP(DV5MGMT, 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 */
LOGP(DV5MGMT, 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 */
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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 */
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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:
LOGP(DV5MGMT, 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;
int i;
mgmt = talloc_zero(v5if, struct v5x_mgmt_proto);
if (!mgmt)
return NULL;
mgmt->interface = v5if;
mgmt->auto_restart = true;
/* 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, NULL);
if (!mgmt->system_fi)
goto error;
mgmt->pstn_dl_fi = osmo_fsm_inst_alloc(&v5x_le_pstn_dl_fsm, mgmt, mgmt, LOGL_DEBUG, NULL);
if (!mgmt->pstn_dl_fi)
goto error;
mgmt->pstn_rs_fi = osmo_fsm_inst_alloc(&v5x_le_pstn_rs_fsm, mgmt, mgmt, LOGL_DEBUG, NULL);
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,
NULL);
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:
LOGP(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;
LOGP(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:
LOGP(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_local,
&v5if->id_local);
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:
LOGP(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:
LOGP(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:
LOGP(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:
LOGP(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:
LOGP(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:
LOGP(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) {
LOGP(DV5MGMT, LOGL_DEBUG, "Send port unblocked control to PSTN port %d.\n",
v5up->nr);
v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_unblocked);
}
}
if (!v5up->an_unblocked) {
LOGP(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) {
LOGP(DV5MGMT, LOGL_DEBUG, "Send port blocked control to PSTN port %d.\n",
v5up->nr);
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) {
LOGP(DV5MGMT, LOGL_INFO, "Received unblocking from LE of %s port %d.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
v5x_le_mgmt_port_unblock(v5up);
}
if (length && *data == PH_CTRL_BLOCK) {
LOGP(DV5MGMT, LOGL_INFO, "Received blocking from LE of %s port %d.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
v5x_le_mgmt_port_block(v5up);
}
break;
case PH_PRIM_ACT_REQ:
LOGP(DV5MGMT, LOGL_INFO, "Received activation from LE of %s port %d.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
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:
LOGP(DV5MGMT, LOGL_INFO, "Received deactivation from LE of %s port %d.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
v5x_port_mph_snd(v5up, MPH_DR);
break;
default:
LOGP(DV5MGMT, LOGL_INFO, "Received unknown prim %d from LE for %s port %d.\n", prim,
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
}
}
/* 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:
LOGP(DV5MGMT, LOGL_INFO, "Received unblocking request from AN of %s port %d.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
if (v5up->le_unblocked) {
LOGP(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) {
LOGP(DV5MGMT, LOGL_DEBUG, "Send port unblocked control to PSTN port %d.\n", v5up->nr);
v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_unblocked);
}
} else {
LOGP(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:
LOGP(DV5MGMT, LOGL_INFO, "Received unblocking indication from AN of %s port %d.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
block_unblock_socket(v5up, false);
if (v5up->pstn.proto) {
LOGP(DV5MGMT, LOGL_DEBUG, "Send port unblocked control to PSTN port %d.\n", v5up->nr);
v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_unblocked);
}
break;
case MPH_BR:
LOGP(DV5MGMT, LOGL_INFO, "Received blocking request from AN of %s port %d.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
LOGP(DV5MGMT, LOGL_INFO, "Reply with blocking indication.\n");
v5x_port_mph_snd(v5up, MPH_BI);
block_unblock_socket(v5up, true);
if (v5up->pstn.proto) {
LOGP(DV5MGMT, LOGL_DEBUG, "Send port blocked control to PSTN port %d.\n", v5up->nr);
v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_blocked);
}
break;
case MPH_BI:
LOGP(DV5MGMT, LOGL_INFO, "Received blocking indication from AN of %s port %d.\n",
(v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
block_unblock_socket(v5up, true);
if (v5up->pstn.proto) {
LOGP(DV5MGMT, LOGL_DEBUG, "Send port blocked control to PSTN port %d.\n", v5up->nr);
v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_blocked);
}
break;
case MPH_AI:
LOGP(DV5MGMT, LOGL_INFO, "Received activation indication from AN of ISDN port %d.\n", v5up->nr);
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:
LOGP(DV5MGMT, LOGL_INFO, "Received deactivation indication from AN of ISDN port %d.\n", v5up->nr);
ph_socket_tx_msg(&v5up->ph_socket, 3, PH_PRIM_DACT_IND, NULL, 0);
break;
case MPH_GI:
LOGP(DV5MGMT, LOGL_INFO, "Received performance grading from AN for ISDN port %d: %s\n", v5up->nr,
(perf_grading) ? "Degraded" : "Normal grade");
break;
default:
LOGP(DV5MGMT, LOGL_INFO, "Received unknown prim %d from AN for %s port %d.\n", prim, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
}
}
/* 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:
LOGP(DV5MGMT, LOGL_NOTICE, "Received MDU-error_indication from PSTN protocol of port %d.\n", v5up->nr);
break;
case MDU_CTRL_port_restart_ack:
LOGP(DV5MGMT, LOGL_INFO, "Received restart acknowledge from PSTN protocol of port %d.\n", v5up->nr);
break;
default:
LOGP(DV5MGMT, LOGL_INFO, "Received unknown prim %d from PSTN protocol for port %d.\n", prim, v5up->nr);
}
}
/* 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:
LOGP(DV5MGMT, LOGL_INFO, "Link %d is active.\n", v5l->id);
break;
case MDU_DI:
LOGP(DV5MGMT, LOGL_INFO, "Link %d is inactive.\n", v5l->id);
break;
case MDU_LAI:
LOGP(DV5MGMT, LOGL_INFO, "Link %d becomes active in blocked state, try unblocking.\n", v5l->id);
v52_le_lcp_mdu_snd(v5l, MDU_LUBR);
break;
case MDU_IDReq:
LOGP(DV5MGMT, LOGL_INFO, "Remote side perform link ID check of link %d, replying.\n", v5l->id);
v52_le_lcp_mdu_snd(v5l, MDU_IDAck);
break;
case MDU_IDAck:
break;
case MDU_IDRel:
LOGP(DV5MGMT, LOGL_INFO, "Remote side ID check done.\n");
break;
case MDU_IDRej:
break;
case MDU_EIg:
break;
case MDU_LUBR:
LOGP(DV5MGMT, LOGL_INFO, "Remote side unblocks link ID %d, replying.\n", v5l->id);
v52_le_lcp_mdu_snd(v5l, MDU_LUBR);
break;
case MDU_LUBI:
LOGP(DV5MGMT, LOGL_INFO, "Remote side link ID %d is now unblocked.\n", v5l->id);
break;
case MDU_LBI:
LOGP(DV5MGMT, LOGL_INFO, "LCP indicated blocked link ID %d.\n", v5l->id);
flush_link(v5l);
break;
case MDU_LBR:
case MDU_LBRN:
LOGP(DV5MGMT, LOGL_INFO, "Remote request blocking of link link ID %d, replying.\n", v5l->id);
flush_link(v5l);
v52_le_lcp_mdu_snd(v5l, MDU_LBI);
break;
default:
LOGP(DV5MGMT, LOGL_INFO, "Received unknown prim %d from LCP protocol for link ID %d.\n", prim, v5l->id);
}
}
/*
* 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)) {
LOGP(DV5MGMT, LOGL_NOTICE, "Dropping (NAT->LE) message of non-operational PSTN port for L3Addr %d.\n",
v5up->nr);
msgb_free(msg);
return;
}
LOGP(DV5MGMT, LOGL_INFO, "FE message: %s NAT -> PSTN_LE of PSTN port %d.\n",
get_value_string(v5x_fe_prim_names, prim), v5up->nr);
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) {
LOGP(DV5MGMT, LOGL_NOTICE, "Dropping (AN->NAT) message of non-operational PSTN port for L3Addr %d.\n",
v5up->nr);
msgb_free(msg);
return;
}
LOGP(DV5MGMT, LOGL_INFO, "FE message: %s PSTN_LE -> NAT of PSTN port %d.\n",
get_value_string(v5x_fe_prim_names, prim), v5up->nr);
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]) {
LOGP(DV5MGMT, LOGL_ERROR, "Cannot activate channel, because no TS is assigned for %s port "
"%d.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5l->id);
return;
}
if (v5up->ts[channel - 1]->b_activated)
return;
LOGP(DV5MGMT, LOGL_DEBUG, "Activating channel %d for %s port %d at TS %d of link %d.\n",
channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr,
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)) {
LOGP(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) {
LOGP(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]) {
LOGP(DV5MGMT, LOGL_ERROR, "Cannot deactivate channel, because no TS is assigned for %s port "
"%d.\n", (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr);
return;
}
if (!v5up->ts[channel - 1]->b_activated)
return;
LOGP(DV5MGMT, LOGL_DEBUG, "Deactivating channel %d for %s port %d at TS %d of link %d.\n",
channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr,
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) {
LOGP(DV5MGMT, LOGL_ERROR, "TS %d out of range.\n", ts);
return -EINVAL;
}
v5l = v5x_link_find_id(v5if, link_id);
if (!v5l) {
LOGP(DV5MGMT, LOGL_ERROR, "Invalid link ID %d.\n", link_id);
return -EINVAL;
}
v5up = v5l->ts[ts].v5up;
if (!v5up) {
LOGP(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:
LOGP(DV5MGMT, LOGL_INFO, "Assigned channel %d for %s port %d at TS %d of link %d.\n",
channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr, ts, link_id);
ph_activate_req(v5up->ts[channel - 1]);
break;
case MDU_BCC_deallocation_conf:
LOGP(DV5MGMT, LOGL_INFO, "Unassigned channel %d for %s port %d at TS %d of link %d.\n",
channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr, 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;
LOGP(DV5MGMT, LOGL_ERROR, "Failed to (un)assign channel %d for %s port %d at TS %d of link %d: %s.\n",
channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr, ts, link_id,
get_value_string(v52_bcc_reject_cause_type_str, cause[0] & 0x7f));
if (cause_len > 1)
LOGP(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;
LOGP(DV5MGMT, LOGL_ERROR, "Failed to (un)assign channel %d for %s port %d at TS %d of link %d: %s.\n",
channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr, ts, link_id,
get_value_string(v5x_cause_type_str, cause[0] & 0x7f));
if (cause_len > 1)
LOGP(DV5MGMT, LOGL_ERROR, " -> Diagnostic: %s\n", osmo_hexdump(cause + 1, cause_len - 1));
if (cause_len >= 3 && cause[1] == 0x20 && cause[2] == 0x47) {
LOGP(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:
LOGP(DV5MGMT, LOGL_ERROR, "Failed to (un)assign channel %d for %s port %d at TS %d of link %d.\n",
channel, (v5up->type == V5X_USER_TYPE_PSTN) ? "PSTN" : "ISDN", v5up->nr, 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)
{
LOGP(DV5MGMT, LOGL_NOTICE, "Switch over request from AN to link %d and TS %d.\n", link_id, ts);
if (!v5l) {
LOGP(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) {
LOGP(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)) {
LOGP(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) {
LOGP(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;
}
LOGP(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)
{
LOGP(DV5MGMT, LOGL_NOTICE, "Switch over request from AN, due to link failure.\n");
if (v5if->primary_link == v5if->cc_link) {
LOGP(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) {
LOGP(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:
LOGP(DV5MGMT, LOGL_NOTICE, "Switch over request from LE to link %d and TS %d was acked.\n", link_id,
ts);
LOGP(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;
LOGP(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:
LOGP(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:
LOGP(DV5MGMT, LOGL_NOTICE, "AN resets serial numbers at Protection protocol.\n");
break;
case MDU_Protection_reset_SN_com:
LOGP(DV5MGMT, LOGL_NOTICE, "LE resets serial numbers at Protection protocol.\n");
break;
case MDU_Protection_reset_SN_ack:
LOGP(DV5MGMT, LOGL_NOTICE, "AN acknowledges reset serial numbers at Protection protocol.\n");
break;
case MDU_Protection_reset_SN_error_ind:
LOGP(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;
LOGP(DV5MGMT, LOGL_ERROR, "Protocol error: %s.\n",
get_value_string(v5x_cause_type_str, cause[0] & 0x7f));
if (cause_len > 1)
LOGP(DV5MGMT, LOGL_ERROR, " -> Diagnostic: %s\n", osmo_hexdump(cause + 1, cause_len - 1));
break;
default:
LOGP(DV5MGMT, LOGL_ERROR, "Unknown message primitive %d.\n", prim);
}
return 0;
}