1603 lines
46 KiB
C
1603 lines
46 KiB
C
/* ITU-T G.964 Section 13 PSTN signalling protocol specification - 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.
|
|
*
|
|
*/
|
|
|
|
/***********************************************************************/
|
|
/* internal data structures */
|
|
/***********************************************************************/
|
|
|
|
#include <errno.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <osmocom/core/linuxlist.h>
|
|
#include <osmocom/gsm/tlv.h>
|
|
|
|
#include "v5x_internal.h"
|
|
#include "v5x_protocol.h"
|
|
#include "v5x_le_pstn_fsm.h"
|
|
#include "v5x_le_management.h"
|
|
#include "logging.h"
|
|
|
|
/* Table 28/G.964 Timers in the LE */
|
|
#define TIMEOUT_T1 2 /* default */
|
|
#define TIMEOUT_T3 2
|
|
#define TIMEOUT_T4 2
|
|
#define TIMEOUT_Tr 5
|
|
#define TIMEOUT_Tt 10
|
|
|
|
#define S(x) (1 << (x))
|
|
|
|
/***********************************************************************/
|
|
/* state names, event names, primitives, ... */
|
|
/***********************************************************************/
|
|
|
|
/* 13.2.1.2 PSTN protocol FSM - LE(PSTN) */
|
|
enum v5x_le_pstn_state {
|
|
V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE, /* LE0 */
|
|
V5x_LE_PTSN_S_LE1_NULL, /* LE1 */
|
|
V5x_LE_PTSN_S_LE2_PATH_INITIATED_LE, /* LE2 */
|
|
V5x_LE_PTSN_S_LE3_PATH_INITIATED_AN, /* LE3 */
|
|
V5x_LE_PTSN_S_LE4_PATH_ACTIVE, /* LE4 */
|
|
V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ, /* LE5 */
|
|
V5x_LE_PTSN_S_LE6_PORT_BLOCKED, /* LE6 */
|
|
};
|
|
|
|
enum v5x_le_pstn_fsm_event {
|
|
/* inbound function elements from AN (Table 3/G.964) */
|
|
V5x_PSTN_LE_FE_establish_req,
|
|
V5x_PSTN_LE_FE_establish_ack_req,
|
|
V5x_PSTN_LE_FE_line_signal_req,
|
|
V5x_PSTN_LE_FE_protocol_param_req,
|
|
V5x_PSTN_LE_FE_disconnect_req,
|
|
V5x_PSTN_LE_FE_disconnect_compl_req,
|
|
/* inbound messages from AN (Table 3/G.964) */
|
|
V5x_PSTN_LE_DISCONNECT,
|
|
V5x_PSTN_LE_DISCONNECT_COMPL,
|
|
V5x_PSTN_LE_ESTABLISH,
|
|
V5x_PSTN_LE_ESTABLISH_ACK,
|
|
V5x_PSTN_LE_SIGNAL,
|
|
V5x_PSTN_LE_SIGNAL_ACK,
|
|
V5x_PSTN_LE_STATUS,
|
|
/* timer events (Table 3/G.964) */
|
|
V5x_PSTN_LE_TIMEOUT_T1,
|
|
V5x_PSTN_LE_TIMEOUT_T3,
|
|
V5x_PSTN_LE_TIMEOUT_T4,
|
|
V5x_PSTN_LE_TIMEOUT_Tr,
|
|
V5x_PSTN_LE_TIMEOUT_Tt,
|
|
/* inbound primitives from Mgmt (Table 3/G.964) */
|
|
V5x_PSTN_LE_MDU_CTRL_port_blocked,
|
|
V5x_PSTN_LE_MDU_CTRL_port_unblocked,
|
|
V5x_PSTN_LE_MDU_CTRL_restart_req,
|
|
V5x_PSTN_LE_MDU_CTRL_restart_compl_req,
|
|
};
|
|
|
|
static const struct value_string v5x_ctrl_le_port_fsm_event_names[] = {
|
|
{ V5x_PSTN_LE_FE_establish_req, "FE-establish_request" },
|
|
{ V5x_PSTN_LE_FE_establish_ack_req, "FE-establish_acknowledge_request" },
|
|
{ V5x_PSTN_LE_FE_line_signal_req, "FE-line_signal_request" },
|
|
{ V5x_PSTN_LE_FE_protocol_param_req, "FE-protocol_parameter_request" },
|
|
{ V5x_PSTN_LE_FE_disconnect_req, "FE-disconnect_request" },
|
|
{ V5x_PSTN_LE_FE_disconnect_compl_req, "FE-disconnect_complete_request" },
|
|
{ V5x_PSTN_LE_DISCONNECT, "DISCONNECT" },
|
|
{ V5x_PSTN_LE_DISCONNECT_COMPL, "DISCONNECT COMPLETE" },
|
|
{ V5x_PSTN_LE_ESTABLISH, "ESTABLISH " },
|
|
{ V5x_PSTN_LE_ESTABLISH_ACK, "ESTABLISH ACK" },
|
|
{ V5x_PSTN_LE_SIGNAL, "SIGNAL" },
|
|
{ V5x_PSTN_LE_SIGNAL_ACK, "SIGNAL_ACK" },
|
|
{ V5x_PSTN_LE_STATUS, "STATUS" },
|
|
{ V5x_PSTN_LE_TIMEOUT_T1, "TIMEOUT T1" },
|
|
{ V5x_PSTN_LE_TIMEOUT_T3, "TIMEOUT T3" },
|
|
{ V5x_PSTN_LE_TIMEOUT_T4, "TIMEOUT T4" },
|
|
{ V5x_PSTN_LE_TIMEOUT_Tr, "TIMEOUT Tr" },
|
|
{ V5x_PSTN_LE_TIMEOUT_Tt, "TIMEOUT Tt" },
|
|
{ V5x_PSTN_LE_MDU_CTRL_port_blocked, "MDU-CTRL (port blocked)" },
|
|
{ V5x_PSTN_LE_MDU_CTRL_port_unblocked, "MDU-CTRL (port unblocked)" },
|
|
{ V5x_PSTN_LE_MDU_CTRL_restart_req, "MDU-CTRL (restart request)" },
|
|
{ V5x_PSTN_LE_MDU_CTRL_restart_compl_req, "MDU-CTRL (restart complete)" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/***********************************************************************
|
|
* V5 Message encoding / sending
|
|
***********************************************************************/
|
|
|
|
/* G.964 Section 13.3.1 / Table 5 */
|
|
static struct msgb *v5x_enc_establish(struct v5x_user_port *v5up, uint8_t *cond_ie, uint8_t cond_len)
|
|
{
|
|
struct v5x_l3_hdr *l3h;
|
|
struct msgb *msg = msgb_alloc_v5x();
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
|
l3h->pdisc = V5X_CTRL_PDISC;
|
|
l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, false));
|
|
l3h->msg_type = V5X_CTRL_MSGT_ESTABLISH;
|
|
|
|
/* optional conditional IE */
|
|
if (cond_ie && cond_len)
|
|
memcpy(msgb_put(msg, cond_len), cond_ie, cond_len);
|
|
|
|
return msg;
|
|
}
|
|
|
|
/* G.964 Section 13.3.1 / Table 6 */
|
|
static struct msgb *v5x_enc_establish_ack(struct v5x_user_port *v5up, uint8_t *cond_ie, uint8_t cond_len)
|
|
{
|
|
struct v5x_l3_hdr *l3h;
|
|
struct msgb *msg = msgb_alloc_v5x();
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
|
l3h->pdisc = V5X_CTRL_PDISC;
|
|
l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, false));
|
|
l3h->msg_type = V5X_CTRL_MSGT_ESTABLISH_ACK;
|
|
|
|
/* optional conditional IE */
|
|
if (cond_ie && cond_len)
|
|
memcpy(msgb_put(msg, cond_len), cond_ie, cond_len);
|
|
|
|
return msg;
|
|
}
|
|
|
|
/* G.964 Section 13.3.1 / Table 7 */
|
|
static struct msgb *v5x_enc_signal(struct v5x_user_port *v5up, uint8_t seq_nr, uint8_t *cond_ie, uint8_t cond_len)
|
|
{
|
|
uint8_t seq_ie = seq_nr | 0x80;
|
|
struct v5x_l3_hdr *l3h;
|
|
struct msgb *msg = msgb_alloc_v5x();
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
|
l3h->pdisc = V5X_CTRL_PDISC;
|
|
l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, false));
|
|
l3h->msg_type = V5X_CTRL_MSGT_SIGNAL;
|
|
|
|
/* Sequence-number */
|
|
msgb_tlv_put(msg, V5X_CTRL_IEI_SEQUENCE_NR, 1, &seq_ie);
|
|
/* manatory conditional IE */
|
|
OSMO_ASSERT(cond_ie && cond_len);
|
|
memcpy(msgb_put(msg, cond_len), cond_ie, cond_len);
|
|
|
|
return msg;
|
|
}
|
|
|
|
/* G.964 Section 13.3.1 / Table 8 */
|
|
static struct msgb *v5x_enc_signal_ack(struct v5x_user_port *v5up, uint8_t seq_nr)
|
|
{
|
|
uint8_t seq_ie = seq_nr | 0x80;
|
|
struct v5x_l3_hdr *l3h;
|
|
struct msgb *msg = msgb_alloc_v5x();
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
|
l3h->pdisc = V5X_CTRL_PDISC;
|
|
l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, false));
|
|
l3h->msg_type = V5X_CTRL_MSGT_SIGNAL_ACK;
|
|
|
|
/* Sequence-number */
|
|
msgb_tlv_put(msg, V5X_CTRL_IEI_SEQUENCE_NR, 1, &seq_ie);
|
|
|
|
return msg;
|
|
}
|
|
|
|
/* G.964 Section 13.3.1 / Table 10 */
|
|
static struct msgb *v5x_enc_status_enquiry(struct v5x_user_port *v5up)
|
|
{
|
|
struct v5x_l3_hdr *l3h;
|
|
struct msgb *msg = msgb_alloc_v5x();
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
|
l3h->pdisc = V5X_CTRL_PDISC;
|
|
l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, false));
|
|
l3h->msg_type = V5X_CTRL_MSGT_STATUS_ENQUIRY;
|
|
|
|
return msg;
|
|
}
|
|
|
|
/* G.964 Section 13.3.1 / Table 11 */
|
|
static struct msgb *v5x_enc_disconnect(struct v5x_user_port *v5up, uint8_t *steady_ie, uint8_t steady_len)
|
|
{
|
|
struct v5x_l3_hdr *l3h;
|
|
struct msgb *msg = msgb_alloc_v5x();
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
|
l3h->pdisc = V5X_CTRL_PDISC;
|
|
l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, false));
|
|
l3h->msg_type = V5X_CTRL_MSGT_DISCONNECT;
|
|
|
|
/* optional conditional IE */
|
|
if (steady_ie && steady_len)
|
|
memcpy(msgb_put(msg, steady_len), steady_ie, steady_len);
|
|
|
|
return msg;
|
|
}
|
|
|
|
/* G.964 Section 13.3.1 / Table 12 */
|
|
static struct msgb *v5x_enc_disconnect_compl(struct v5x_user_port *v5up, uint8_t *steady_ie, uint8_t steady_len)
|
|
{
|
|
struct v5x_l3_hdr *l3h;
|
|
struct msgb *msg = msgb_alloc_v5x();
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
|
l3h->pdisc = V5X_CTRL_PDISC;
|
|
l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, false));
|
|
l3h->msg_type = V5X_CTRL_MSGT_DISCONNECT_COMPLETE;
|
|
|
|
/* optional conditional IE */
|
|
if (steady_ie && steady_len)
|
|
memcpy(msgb_put(msg, steady_len), steady_ie, steady_len);
|
|
|
|
return msg;
|
|
}
|
|
|
|
/* G.964 Section 13.3.1 / Table 13 */
|
|
static struct msgb *v5x_enc_protocol_param(struct v5x_user_port *v5up, uint8_t seq_nr, uint8_t *cond_ie, uint8_t cond_len)
|
|
{
|
|
uint8_t seq_ie = seq_nr | 0x80;
|
|
struct v5x_l3_hdr *l3h;
|
|
struct msgb *msg = msgb_alloc_v5x();
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
|
l3h->pdisc = V5X_CTRL_PDISC;
|
|
l3h->l3_addr = htons(v5x_l3_addr_enc(v5up->nr, false));
|
|
l3h->msg_type = V5X_CTRL_MSGT_PROTOCOL_PARAMETER;
|
|
|
|
/* Sequence-number */
|
|
msgb_tlv_put(msg, V5X_CTRL_IEI_SEQUENCE_NR, 1, &seq_ie);
|
|
/* manatory conditional IE */
|
|
OSMO_ASSERT(cond_ie && cond_len);
|
|
memcpy(msgb_put(msg, cond_len), cond_ie, cond_len);
|
|
|
|
return msg;
|
|
}
|
|
|
|
/***********************************************************************/
|
|
/* upper layer interface */
|
|
/***********************************************************************/
|
|
|
|
static void rcv_fe(struct osmo_fsm_inst *fi, enum v5x_fe_prim prim, const struct tlv_parsed *tp)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
struct msgb *msg;
|
|
uint8_t *tlv, len;
|
|
|
|
msg = msgb_alloc_v5x();
|
|
OSMO_ASSERT(msg);
|
|
|
|
/* add IE, if tp was given */
|
|
if (tp) {
|
|
/* single octet */
|
|
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_PULSE_NOTIFICATION))
|
|
*msgb_put(msg, 1) = *TLVP_VAL(tp, V5X_CTRL_IEI_PULSE_NOTIFICATION);
|
|
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_LINE_INFORMATION))
|
|
*msgb_put(msg, 1) = *TLVP_VAL(tp, V5X_CTRL_IEI_LINE_INFORMATION);
|
|
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_SEQUENCE_RESPONSE))
|
|
*msgb_put(msg, 1) = *TLVP_VAL(tp, V5X_CTRL_IEI_SEQUENCE_RESPONSE);
|
|
/* multiple octets */
|
|
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_PULSED_SIGNAL)) {
|
|
len = TLVP_LEN(tp, V5X_CTRL_IEI_PULSED_SIGNAL);
|
|
tlv = msgb_put(msg, 2 + len);
|
|
tlv[0] = V5X_CTRL_IEI_PULSED_SIGNAL;
|
|
tlv[1] = len;
|
|
memcpy(tlv + 2, TLVP_VAL(tp, V5X_CTRL_IEI_PULSED_SIGNAL), len);
|
|
}
|
|
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_STEADY_SIGNAL)) {
|
|
len = TLVP_LEN(tp, V5X_CTRL_IEI_STEADY_SIGNAL);
|
|
tlv = msgb_put(msg, 2 + len);
|
|
tlv[0] = V5X_CTRL_IEI_STEADY_SIGNAL;
|
|
tlv[1] = len;
|
|
memcpy(tlv + 2, TLVP_VAL(tp, V5X_CTRL_IEI_STEADY_SIGNAL), len);
|
|
}
|
|
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_DIGIT_SIGNAL)) {
|
|
len = TLVP_LEN(tp, V5X_CTRL_IEI_DIGIT_SIGNAL);
|
|
tlv = msgb_put(msg, 2 + len);
|
|
tlv[0] = V5X_CTRL_IEI_DIGIT_SIGNAL;
|
|
tlv[1] = len;
|
|
memcpy(tlv + 2, TLVP_VAL(tp, V5X_CTRL_IEI_DIGIT_SIGNAL), len);
|
|
}
|
|
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_RESOURCE_UNAVAILABLE)) {
|
|
len = TLVP_LEN(tp, V5X_CTRL_IEI_RESOURCE_UNAVAILABLE);
|
|
tlv = msgb_put(msg, 2 + len);
|
|
tlv[0] = V5X_CTRL_IEI_RESOURCE_UNAVAILABLE;
|
|
tlv[1] = len;
|
|
memcpy(tlv + 2, TLVP_VAL(tp, V5X_CTRL_IEI_RESOURCE_UNAVAILABLE), len);
|
|
}
|
|
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_METERING_REPORT)) {
|
|
len = TLVP_LEN(tp, V5X_CTRL_IEI_METERING_REPORT);
|
|
tlv = msgb_put(msg, 2 + len);
|
|
tlv[0] = V5X_CTRL_IEI_METERING_REPORT;
|
|
tlv[1] = len;
|
|
memcpy(tlv + 2, TLVP_VAL(tp, V5X_CTRL_IEI_METERING_REPORT), len);
|
|
}
|
|
}
|
|
|
|
v5x_le_pstn_fe_rcv(pstn->v5up, prim, msg);
|
|
}
|
|
|
|
static void rcv_mdu(struct osmo_fsm_inst *fi, enum v5x_mgmt_prim prim)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
|
|
v5x_le_pstn_mdu_rcv(pstn->v5up, prim);
|
|
}
|
|
|
|
void v5x_le_pstn_fe_snd(struct v5x_user_port *v5up, enum v5x_fe_prim prim, struct msgb *msg)
|
|
{
|
|
struct v5x_pstn_proto *pstn = v5up->pstn.proto;
|
|
struct osmo_fsm_inst *fi = pstn->fi;
|
|
enum v5x_le_pstn_fsm_event event;
|
|
int rc;
|
|
|
|
OSMO_ASSERT(msg);
|
|
|
|
switch (prim) {
|
|
case FE_establish_req:
|
|
event = V5x_PSTN_LE_FE_establish_req;
|
|
break;
|
|
case FE_establish_ack_req:
|
|
event = V5x_PSTN_LE_FE_establish_ack_req;
|
|
break;
|
|
case FE_line_signal_req:
|
|
event = V5x_PSTN_LE_FE_line_signal_req;
|
|
break;
|
|
case FE_protocol_param_req:
|
|
event = V5x_PSTN_LE_FE_protocol_param_req;
|
|
break;
|
|
case FE_disconnect_req:
|
|
event = V5x_PSTN_LE_FE_disconnect_req;
|
|
break;
|
|
case FE_disconnect_compl_req:
|
|
event = V5x_PSTN_LE_FE_disconnect_compl_req;
|
|
break;
|
|
default:
|
|
LOGPFSML(v5up->port_fi, LOGL_NOTICE, "Got invalid prim %d at this protocol\n", prim);
|
|
return;
|
|
}
|
|
|
|
/* send event to FSM */
|
|
rc = osmo_fsm_inst_dispatch(fi, event, msg);
|
|
if (rc < 0)
|
|
msgb_free(msg);
|
|
}
|
|
|
|
void v5x_le_pstn_mdu_snd(struct v5x_user_port *v5up, enum v5x_mgmt_prim prim)
|
|
{
|
|
struct v5x_pstn_proto *pstn = v5up->pstn.proto;
|
|
struct osmo_fsm_inst *fi = pstn->fi;
|
|
enum v5x_le_pstn_fsm_event event;
|
|
|
|
switch (prim) {
|
|
case MDU_CTRL_port_blocked:
|
|
event = V5x_PSTN_LE_MDU_CTRL_port_blocked;
|
|
break;
|
|
case MDU_CTRL_port_unblocked:
|
|
event = V5x_PSTN_LE_MDU_CTRL_port_unblocked;
|
|
break;
|
|
case MDU_CTRL_port_restart_req:
|
|
event = V5x_PSTN_LE_MDU_CTRL_restart_req;
|
|
break;
|
|
case MDU_CTRL_port_restart_compl:
|
|
event = V5x_PSTN_LE_MDU_CTRL_restart_compl_req;
|
|
break;
|
|
default:
|
|
LOGPFSML(v5up->port_fi, LOGL_NOTICE, "Got invalid prim %d at this protocol\n", prim);
|
|
return;
|
|
}
|
|
|
|
/* send event to FSM */
|
|
osmo_fsm_inst_dispatch(fi, event, NULL);
|
|
}
|
|
|
|
/***********************************************************************/
|
|
/* lower layer interface */
|
|
/***********************************************************************/
|
|
|
|
static void dl_send(struct osmo_fsm_inst *fi, struct msgb *msg)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
|
|
v5x_dl_snd(pstn->v5up->interface, V5X_DLADDR_PSTN, msg);
|
|
}
|
|
|
|
/***********************************************************************/
|
|
/* PSTN state FSM */
|
|
/***********************************************************************/
|
|
|
|
static void start_timer_Tr(struct v5x_pstn_proto *pstn)
|
|
{
|
|
LOGPFSML(pstn->fi, LOGL_DEBUG, "Start timer Tr\n");
|
|
osmo_timer_schedule(&pstn->timer_Tr, TIMEOUT_Tr, 0);
|
|
}
|
|
|
|
static void start_timer_Tt(struct v5x_pstn_proto *pstn)
|
|
{
|
|
LOGPFSML(pstn->fi, LOGL_DEBUG, "Start timer Tt\n");
|
|
osmo_timer_schedule(&pstn->timer_Tt, TIMEOUT_Tt, 0);
|
|
}
|
|
|
|
static void stop_timer_Tr(struct v5x_pstn_proto *pstn)
|
|
{
|
|
if (osmo_timer_pending(&pstn->timer_Tr)) {
|
|
LOGPFSML(pstn->fi, LOGL_DEBUG, "Stop timer Tr\n");
|
|
osmo_timer_del(&pstn->timer_Tr);
|
|
}
|
|
}
|
|
|
|
static void stop_timer_Tt(struct v5x_pstn_proto *pstn)
|
|
{
|
|
if (osmo_timer_pending(&pstn->timer_Tt)) {
|
|
LOGPFSML(pstn->fi, LOGL_DEBUG, "Stop timer Tt\n");
|
|
osmo_timer_del(&pstn->timer_Tt);
|
|
}
|
|
}
|
|
|
|
static void stop_timer(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "Stop all timers\n");
|
|
osmo_timer_del(&fi->timer);
|
|
stop_timer_Tr(pstn);
|
|
stop_timer_Tt(pstn);
|
|
}
|
|
|
|
static void start_timer(struct osmo_fsm_inst *fi, enum v5x_le_pstn_fsm_event event, int count)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
int timeout = 0;
|
|
const char *timer_name = NULL;
|
|
|
|
switch (event) {
|
|
case V5x_PSTN_LE_TIMEOUT_T1:
|
|
timer_name = "T1";
|
|
timeout = TIMEOUT_T1;
|
|
fi->T = 1;
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_T3:
|
|
timer_name = "T3";
|
|
timeout = TIMEOUT_T3;
|
|
fi->T = 3;
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_T4:
|
|
timer_name = "T4";
|
|
timeout = TIMEOUT_T4;
|
|
fi->T = 4;
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "Start timer %s (count = %d)\n", timer_name, count);
|
|
pstn->timeout_event = event;
|
|
pstn->timeout_count = count;
|
|
osmo_timer_schedule(&fi->timer, timeout, 0);
|
|
}
|
|
|
|
static int v5x_le_pstn_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
|
|
osmo_fsm_inst_dispatch(fi, pstn->timeout_event, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void timeout_Tr_cb(void *data)
|
|
{
|
|
struct v5x_pstn_proto *pstn = data;
|
|
|
|
osmo_fsm_inst_dispatch(pstn->fi, V5x_PSTN_LE_TIMEOUT_Tr, NULL);
|
|
}
|
|
|
|
static void timeout_Tt_cb(void *data)
|
|
{
|
|
struct v5x_pstn_proto *pstn = data;
|
|
|
|
osmo_fsm_inst_dispatch(pstn->fi, V5x_PSTN_LE_TIMEOUT_Tt, NULL);
|
|
}
|
|
|
|
static void do_status_enquiry(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
|
|
LOGPFSML(fi, LOGL_NOTICE, "Received message not allowed in current state, sending status enquiry.\n");
|
|
|
|
/* start T4 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, 1);
|
|
/* send STATUS ENQUIRY */
|
|
dl_send(fi, v5x_enc_status_enquiry(pstn->v5up));
|
|
}
|
|
|
|
static void do_establish_request(struct osmo_fsm_inst *fi, struct msgb *msg)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
|
|
/* change state to LE2 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE2_PATH_INITIATED_LE, 0, 0);
|
|
/* init sequence variables */
|
|
pstn->S_s = pstn->S_a = pstn->S_r = 0;
|
|
/* start T1 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T1, 1);
|
|
/* store ESTABLISH message */
|
|
if (pstn->tx_msg)
|
|
msgb_free(pstn->tx_msg);
|
|
pstn->tx_msg = msgb_copy(msg, NULL);
|
|
/* send ESTABLISH */
|
|
dl_send(fi, v5x_enc_establish(pstn->v5up, msg->data, msg->len));
|
|
msgb_free(msg);
|
|
}
|
|
|
|
static void do_establish_indication(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
|
|
/* init sequence variables */
|
|
pstn->S_s = pstn->S_a = pstn->S_r = 0;
|
|
/* send FE-establish_indication */
|
|
rcv_fe(fi, FE_establish_ind, tp);
|
|
}
|
|
|
|
static void do_disconnect_complete(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
|
|
{
|
|
/* change state to LE1 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE1_NULL, 0, 0);
|
|
/* stop all timers */
|
|
stop_timer(fi);
|
|
/* send FE-disconnect_complete_indication */
|
|
rcv_fe(fi, FE_disconnect_compl_ind, tp);
|
|
}
|
|
|
|
static void do_disconnect_with_error(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
|
|
/* change state to LE5 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ, 0, 0);
|
|
/* stop all timers */
|
|
stop_timer(fi);
|
|
/* start T3 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T3, 1);
|
|
/* store empty disconnect message */
|
|
if (pstn->tx_msg) {
|
|
msgb_free(pstn->tx_msg);
|
|
pstn->tx_msg = NULL;
|
|
}
|
|
/* send DISCONNECT */
|
|
dl_send(fi, v5x_enc_disconnect(pstn->v5up, NULL, 0));
|
|
/* send MDU-error_indication */
|
|
rcv_mdu(fi, MDU_error_ind);
|
|
}
|
|
|
|
static void do_block(struct osmo_fsm_inst *fi)
|
|
{
|
|
/* change state to LE6 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE6_PORT_BLOCKED, 0, 0);
|
|
/* stop all timers */
|
|
stop_timer(fi);
|
|
/* send FE-disconnect_complete_indication */
|
|
rcv_fe(fi, FE_disconnect_compl_ind, NULL);
|
|
}
|
|
|
|
static void do_restart(struct osmo_fsm_inst *fi)
|
|
{
|
|
/* change state to LE0 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE, 0, 0);
|
|
/* send FE-disconnect_complete_indication */
|
|
rcv_fe(fi, FE_disconnect_compl_ind, NULL);
|
|
/* send MDU-CTRL (restart ack) */
|
|
rcv_mdu(fi, MDU_CTRL_port_restart_ack);
|
|
}
|
|
|
|
static void pstn_le0_out_of_service(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
struct msgb *msg = data;
|
|
const struct tlv_parsed *tp = data;
|
|
uint8_t cause;
|
|
|
|
switch (event) {
|
|
case V5x_PSTN_LE_FE_establish_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_ESTABLISH:
|
|
case V5x_PSTN_LE_DISCONNECT:
|
|
case V5x_PSTN_LE_ESTABLISH_ACK:
|
|
case V5x_PSTN_LE_SIGNAL:
|
|
case V5x_PSTN_LE_SIGNAL_ACK:
|
|
/* do several thing here */
|
|
do_status_enquiry(fi);
|
|
break;
|
|
case V5x_PSTN_LE_DISCONNECT_COMPL:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_FE_establish_ack_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_STATUS:
|
|
cause = *TLVP_VAL(tp, V5X_CTRL_IEI_CAUSE) & 0x7f;
|
|
if (cause == 0) {
|
|
/* stop T4 */
|
|
stop_timer(fi);
|
|
} else {
|
|
/* send MDU-error_indication */
|
|
rcv_mdu(fi, MDU_error_ind);
|
|
}
|
|
break;
|
|
case V5x_PSTN_LE_FE_protocol_param_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_FE_disconnect_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_blocked:
|
|
/* change state to LE6 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE6_PORT_BLOCKED, 0, 0);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_unblocked:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_req:
|
|
/* send MDU-CTRL (restart ack) */
|
|
rcv_mdu(fi, MDU_CTRL_port_restart_ack);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_compl_req:
|
|
/* change state to LE1 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE1_NULL, 0, 0);
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_T4:
|
|
/* first or second timeout ? */
|
|
if (pstn->timeout_count <= 3) {
|
|
/* start T4 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1);
|
|
/* send STATUS ENQUIRY again */
|
|
dl_send(fi, v5x_enc_status_enquiry(pstn->v5up));
|
|
} else {
|
|
/* send MDU-error_indication */
|
|
rcv_mdu(fi, MDU_error_ind);
|
|
}
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void pstn_le1_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
struct msgb *msg = data;
|
|
const struct tlv_parsed *tp = data;
|
|
uint8_t cause;
|
|
|
|
switch (event) {
|
|
case V5x_PSTN_LE_FE_establish_req:
|
|
/* do several thing here */
|
|
do_establish_request(fi, msg);
|
|
break;
|
|
case V5x_PSTN_LE_ESTABLISH:
|
|
/* change state to LE3 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE3_PATH_INITIATED_AN, 0, 0);
|
|
/* do several thing here */
|
|
do_establish_indication(fi, tp);
|
|
break;
|
|
case V5x_PSTN_LE_DISCONNECT:
|
|
/* send DISCONNECT COMPLETE */
|
|
dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0));
|
|
break;
|
|
case V5x_PSTN_LE_ESTABLISH_ACK:
|
|
case V5x_PSTN_LE_SIGNAL:
|
|
case V5x_PSTN_LE_SIGNAL_ACK:
|
|
/* do several thing here */
|
|
do_status_enquiry(fi);
|
|
break;
|
|
case V5x_PSTN_LE_STATUS:
|
|
cause = *TLVP_VAL(tp, V5X_CTRL_IEI_CAUSE) & 0x7f;
|
|
if (cause == 0) {
|
|
/* stop T4 */
|
|
stop_timer(fi);
|
|
} else {
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
case V5x_PSTN_LE_FE_protocol_param_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_blocked:
|
|
/* do several thing here */
|
|
do_block(fi);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_unblocked:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_req:
|
|
/* do several thing here */
|
|
do_restart(fi);
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_T4:
|
|
/* first or second timeout ? */
|
|
if (pstn->timeout_count < 3) {
|
|
/* start T4 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1);
|
|
/* send STATUS ENQUIRY again */
|
|
dl_send(fi, v5x_enc_status_enquiry(pstn->v5up));
|
|
} else {
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void pstn_le2_path_initiated_le(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
struct msgb *msg = data;
|
|
const struct tlv_parsed *tp = data;
|
|
uint8_t cause;
|
|
|
|
switch (event) {
|
|
case V5x_PSTN_LE_ESTABLISH:
|
|
/* do several thing here */
|
|
do_establish_indication(fi, tp);
|
|
break;
|
|
case V5x_PSTN_LE_DISCONNECT:
|
|
/* send DISCONNECT COMPLETE */
|
|
dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0));
|
|
/* do several thing here */
|
|
do_disconnect_complete(fi, tp);
|
|
break;
|
|
case V5x_PSTN_LE_DISCONNECT_COMPL:
|
|
/* do several thing here */
|
|
do_disconnect_complete(fi, tp);
|
|
break;
|
|
case V5x_PSTN_LE_FE_establish_ack_req:
|
|
/* change state to LE4 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE4_PATH_ACTIVE, 0, 0);
|
|
/* stop T1 */
|
|
stop_timer(fi);
|
|
/* send ESTABLISH ACK */
|
|
dl_send(fi, v5x_enc_establish_ack(pstn->v5up, msg->data, msg->len));
|
|
msgb_free(msg);
|
|
break;
|
|
case V5x_PSTN_LE_ESTABLISH_ACK:
|
|
/* change state to LE4 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE4_PATH_ACTIVE, 0, 0);
|
|
/* stop T1 */
|
|
stop_timer(fi);
|
|
/* send FE-establish_acknowledge_indication */
|
|
rcv_fe(fi, FE_establish_ack_ind, tp);
|
|
break;
|
|
case V5x_PSTN_LE_SIGNAL:
|
|
case V5x_PSTN_LE_SIGNAL_ACK:
|
|
/* do several thing here */
|
|
do_status_enquiry(fi);
|
|
break;
|
|
case V5x_PSTN_LE_STATUS:
|
|
cause = *TLVP_VAL(tp, V5X_CTRL_IEI_CAUSE) & 0x7f;
|
|
if (cause == 0) {
|
|
/* stop T4 */
|
|
stop_timer(fi);
|
|
} else {
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
case V5x_PSTN_LE_FE_protocol_param_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_FE_disconnect_req:
|
|
/* change state to LE5 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ, 0, 0);
|
|
/* stop all timers */
|
|
stop_timer(fi);
|
|
/* start T3 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T3, 2);
|
|
/* store DISCONNECT message */
|
|
if (pstn->tx_msg)
|
|
msgb_free(pstn->tx_msg);
|
|
pstn->tx_msg = msgb_copy(msg, NULL);
|
|
/* send DISCONNECT */
|
|
dl_send(fi, v5x_enc_disconnect(pstn->v5up, msg->data, msg->len));
|
|
msgb_free(msg);
|
|
break;
|
|
case V5x_PSTN_LE_FE_disconnect_compl_req:
|
|
// NOTE: The Table 31-32/G.964 is wrong at this point, there is no use here
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_blocked:
|
|
/* do several thing here */
|
|
do_block(fi);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_unblocked:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_req:
|
|
/* send DISCONNECT COMPLETE */
|
|
dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0));
|
|
/* do several thing here */
|
|
do_restart(fi);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_compl_req:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_T1:
|
|
/* first timeout ? */
|
|
if (pstn->timeout_count == 1) {
|
|
/* start T1 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T1, 2);
|
|
/* send ESTABLISH again */
|
|
OSMO_ASSERT(pstn->tx_msg);
|
|
dl_send(fi, v5x_enc_establish(pstn->v5up, pstn->tx_msg->data, pstn->tx_msg->len));
|
|
} else {
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_T4:
|
|
/* first or second timeout ? */
|
|
if (pstn->timeout_count < 3) {
|
|
/* start T4 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1);
|
|
/* send STATUS ENQUIRY again */
|
|
dl_send(fi, v5x_enc_status_enquiry(pstn->v5up));
|
|
} else {
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void pstn_le3_path_initiated_an(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
struct msgb *msg = data;
|
|
const struct tlv_parsed *tp = data;
|
|
uint8_t cause;
|
|
|
|
switch (event) {
|
|
case V5x_PSTN_LE_FE_establish_req:
|
|
/* do several thing here */
|
|
do_establish_request(fi, msg);
|
|
break;
|
|
case V5x_PSTN_LE_ESTABLISH:
|
|
/* do several thing here */
|
|
do_establish_indication(fi, tp);
|
|
break;
|
|
case V5x_PSTN_LE_DISCONNECT:
|
|
/* send DISCONNECT COMPLETE */
|
|
dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0));
|
|
/* do several thing here */
|
|
do_disconnect_complete(fi, tp);
|
|
break;
|
|
case V5x_PSTN_LE_DISCONNECT_COMPL:
|
|
/* do several thing here */
|
|
do_disconnect_complete(fi, tp);
|
|
break;
|
|
case V5x_PSTN_LE_FE_establish_ack_req:
|
|
/* change state to LE4 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE4_PATH_ACTIVE, 0, 0);
|
|
/* send ESTABLISH ACK */
|
|
dl_send(fi, v5x_enc_establish_ack(pstn->v5up, msg->data, msg->len));
|
|
msgb_free(msg);
|
|
break;
|
|
case V5x_PSTN_LE_ESTABLISH_ACK:
|
|
case V5x_PSTN_LE_SIGNAL:
|
|
case V5x_PSTN_LE_SIGNAL_ACK:
|
|
/* do several thing here */
|
|
do_status_enquiry(fi);
|
|
break;
|
|
case V5x_PSTN_LE_STATUS:
|
|
cause = *TLVP_VAL(tp, V5X_CTRL_IEI_CAUSE) & 0x7f;
|
|
if (cause == 0) {
|
|
/* stop T4 */
|
|
stop_timer(fi);
|
|
} else {
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
case V5x_PSTN_LE_FE_protocol_param_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_FE_disconnect_req:
|
|
/* change state to LE5 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ, 0, 0);
|
|
/* stop all timers */
|
|
stop_timer(fi);
|
|
/* start T3 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T3, 2);
|
|
/* store DISCONNECT message */
|
|
if (pstn->tx_msg)
|
|
msgb_free(pstn->tx_msg);
|
|
pstn->tx_msg = msgb_copy(msg, NULL);
|
|
/* Send DISCONNECT */
|
|
dl_send(fi, v5x_enc_disconnect(pstn->v5up, msg->data, msg->len));
|
|
msgb_free(msg);
|
|
break;
|
|
case V5x_PSTN_LE_FE_disconnect_compl_req:
|
|
/* change state to LE1 */
|
|
// NOTE: The Table 31-32/G.964 is wrong at this point, see Figure B.3/G.964
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE1_NULL, 0, 0);
|
|
/* stop all timers */
|
|
stop_timer(fi);
|
|
/* send DISCONNECT COMPLETE */
|
|
dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, msg->data, msg->len));
|
|
msgb_free(msg);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_blocked:
|
|
/* do several thing here */
|
|
do_block(fi);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_unblocked:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_req:
|
|
/* send DISCONNECT COMPLETE */
|
|
dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0));
|
|
/* do several thing here */
|
|
do_restart(fi);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_compl_req:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_T4:
|
|
/* first or second timeout ? */
|
|
if (pstn->timeout_count < 3) {
|
|
/* start T4 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1);
|
|
/* send STATUS ENQUIRY again */
|
|
dl_send(fi, v5x_enc_status_enquiry(pstn->v5up));
|
|
} else {
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void pstn_le4_path_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
struct msgb *msg = data;
|
|
const struct tlv_parsed *tp = data;
|
|
uint8_t cause, seq_nr, unacked, acked;
|
|
|
|
switch (event) {
|
|
case V5x_PSTN_LE_ESTABLISH:
|
|
/* do several thing here */
|
|
do_status_enquiry(fi);
|
|
break;
|
|
case V5x_PSTN_LE_DISCONNECT:
|
|
/* send DISCONNECT COMPLETE */
|
|
dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0));
|
|
/* do several thing here */
|
|
do_disconnect_complete(fi, tp);
|
|
break;
|
|
case V5x_PSTN_LE_DISCONNECT_COMPL:
|
|
/* send FE-disconnect_complete_indication */
|
|
rcv_fe(fi, FE_disconnect_compl_ind, NULL);
|
|
/* do several thing here */
|
|
do_disconnect_complete(fi, tp);
|
|
break;
|
|
case V5x_PSTN_LE_ESTABLISH_ACK:
|
|
/* start T4 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, 1);
|
|
/* send STATUS ENQUIRY */
|
|
dl_send(fi, v5x_enc_status_enquiry(pstn->v5up));
|
|
break;
|
|
case V5x_PSTN_LE_SIGNAL:
|
|
/* get sequence number and check if msg is in sequence */
|
|
seq_nr = *TLVP_VAL(tp, V5X_CTRL_IEI_SEQUENCE_NR) & 0x7f;
|
|
if (pstn->S_r == seq_nr) {
|
|
LOGPFSML(fi, LOGL_DEBUG, "Received expected 'receive' sequence number: S(R)=%d == M(R)=%d\n",
|
|
pstn->S_r, seq_nr);
|
|
/* start Tr, if not pending */
|
|
if (!osmo_timer_pending(&pstn->timer_Tr))
|
|
start_timer_Tr(pstn);
|
|
/* increment receive sequence number */
|
|
pstn->S_r = (pstn->S_r + 1) & 127;
|
|
/* send FE-signal_indication */
|
|
rcv_fe(fi, FE_line_signal_ind, tp);
|
|
} else {
|
|
LOGPFSML(fi, LOGL_ERROR, "Invalid 'receive' sequence number: S(R)=%d != M(R)=%d\n",
|
|
pstn->S_r, seq_nr);
|
|
/* send FE-signal_indication */
|
|
rcv_fe(fi, FE_line_signal_ind, tp);
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
case V5x_PSTN_LE_SIGNAL_ACK:
|
|
seq_nr = *TLVP_VAL(tp, V5X_CTRL_IEI_SEQUENCE_NR) & 0x7f;
|
|
unacked = (pstn->S_s - pstn->S_a) & 127;
|
|
acked = (pstn->S_s - seq_nr) & 127;
|
|
if (acked <= unacked) {
|
|
LOGPFSML(fi, LOGL_DEBUG, "Received expected 'transmit' sequence number: M(R)=%d is inside "
|
|
"S(A)=%d...S(S)=%d range\n", seq_nr, pstn->S_a, pstn->S_s);
|
|
LOGPFSML(fi, LOGL_DEBUG, "We had %d unacked messages, now we have %d acked messages.\n", unacked, acked);
|
|
/* update acknowledge sequence number */
|
|
pstn->S_a = seq_nr;
|
|
if (pstn->S_a == pstn->S_s) {
|
|
LOGPFSML(fi, LOGL_DEBUG, "No more outstanding message, so we don't need Tt anymore.\n");
|
|
/* stop Tt */
|
|
stop_timer_Tt(pstn);
|
|
} else {
|
|
LOGPFSML(fi, LOGL_DEBUG, "There are still outstanding message, we still need Tt.\n");
|
|
/* start Tt */
|
|
start_timer_Tt(pstn);
|
|
}
|
|
} else {
|
|
LOGPFSML(fi, LOGL_ERROR, "Invalid 'transmit' sequence number: M(R)=%d not inside "
|
|
"S(A)=%d...S(S)=%d range\n", seq_nr, pstn->S_a, pstn->S_s);
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
case V5x_PSTN_LE_STATUS:
|
|
cause = *TLVP_VAL(tp, V5X_CTRL_IEI_CAUSE) & 0x7f;
|
|
if (cause == 0) {
|
|
/* stop T4 */
|
|
stop_timer(fi);
|
|
} else {
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
case V5x_PSTN_LE_FE_line_signal_req:
|
|
/* if the maximum number of outstanding sequence numbers is not exceeded (127) */
|
|
if (((pstn->S_s + 1) & 127) != pstn->S_a) {
|
|
/* start Tt, if not pending: This is wrong in the state tables, but look at 13.5.5.2.2! */
|
|
if (!osmo_timer_pending(&pstn->timer_Tt))
|
|
start_timer_Tt(pstn);
|
|
/* store SIGNAL message */
|
|
if (pstn->tx_msg)
|
|
msgb_free(pstn->tx_msg);
|
|
pstn->tx_msg = msgb_copy(msg, NULL);
|
|
/* send SIGNAL */
|
|
dl_send(fi, v5x_enc_signal(pstn->v5up, pstn->S_s, msg->data, msg->len));
|
|
msgb_free(msg);
|
|
/* increment send sequence number */
|
|
pstn->S_s = (pstn->S_s + 1) & 127;
|
|
} else {
|
|
/* send SIGNAL */
|
|
dl_send(fi, v5x_enc_signal(pstn->v5up, pstn->S_s, msg->data, msg->len));
|
|
msgb_free(msg);
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
case V5x_PSTN_LE_FE_protocol_param_req:
|
|
/* if the maximum number of outstanding sequence numbers is not exceeded (127) */
|
|
if (((pstn->S_s + 1) & 127) != pstn->S_a) {
|
|
/* start Tt */
|
|
start_timer_Tt(pstn);
|
|
/* send SIGNAL */
|
|
dl_send(fi, v5x_enc_protocol_param(pstn->v5up, pstn->S_s, msg->data, msg->len));
|
|
msgb_free(msg);
|
|
/* increment send sequence number */
|
|
pstn->S_s = (pstn->S_s + 1) & 127;
|
|
} else {
|
|
/* send SIGNAL */
|
|
dl_send(fi, v5x_enc_protocol_param(pstn->v5up, pstn->S_s, msg->data, msg->len));
|
|
msgb_free(msg);
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
case V5x_PSTN_LE_FE_disconnect_req:
|
|
/* change state to LE5 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ, 0, 0);
|
|
/* stop all timers */
|
|
stop_timer(fi);
|
|
/* start T3 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T3, 2);
|
|
/* store DISCONNECT message */
|
|
if (pstn->tx_msg)
|
|
msgb_free(pstn->tx_msg);
|
|
pstn->tx_msg = msgb_copy(msg, NULL);
|
|
/* Send DISCONNECT */
|
|
dl_send(fi, v5x_enc_disconnect(pstn->v5up, msg->data, msg->len));
|
|
msgb_free(msg);
|
|
break;
|
|
case V5x_PSTN_LE_FE_disconnect_compl_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_blocked:
|
|
/* do several thing here */
|
|
do_block(fi);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_unblocked:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_req:
|
|
/* send DISCONNECT COMPLETE */
|
|
dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0));
|
|
/* do several thing here */
|
|
do_restart(fi);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_compl_req:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_T4:
|
|
/* first or second timeout ? */
|
|
if (pstn->timeout_count < 3) {
|
|
/* start T4 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1);
|
|
/* send STATUS ENQUIRY again */
|
|
dl_send(fi, v5x_enc_status_enquiry(pstn->v5up));
|
|
} else {
|
|
/* do several thing here */
|
|
do_disconnect_with_error(fi);
|
|
}
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_Tr:
|
|
/* send SIGNAL ACK */
|
|
LOGPFSML(fi, LOGL_DEBUG, "Sending recent 'receive' sequence number: S(R)=%d as acknowledge.\n",
|
|
pstn->S_r);
|
|
dl_send(fi, v5x_enc_signal_ack(pstn->v5up, pstn->S_r));
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_Tt:
|
|
/* do several thing here */
|
|
LOGPFSML(fi, LOGL_ERROR, "Aborting, because of missing acknowledge of outstanding 'transmit' sequence number: S(A)=%d...S(S)=%d\n", pstn->S_a, pstn->S_s);
|
|
do_disconnect_with_error(fi);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void pstn_le5_path_disconnect_req(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
struct msgb *msg = data;
|
|
const struct tlv_parsed *tp = data;
|
|
|
|
switch (event) {
|
|
case V5x_PSTN_LE_ESTABLISH:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_DISCONNECT:
|
|
case V5x_PSTN_LE_DISCONNECT_COMPL:
|
|
/* stop T3 */
|
|
stop_timer(fi);
|
|
/* change state to LE1 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE1_NULL, 0, 0);
|
|
/* send FE-disconnect_complete_indication */
|
|
rcv_fe(fi, FE_disconnect_compl_ind, tp);
|
|
break;
|
|
case V5x_PSTN_LE_ESTABLISH_ACK:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_SIGNAL:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_SIGNAL_ACK:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_STATUS:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_FE_line_signal_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_FE_protocol_param_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_FE_disconnect_compl_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_blocked:
|
|
/* do several thing here */
|
|
do_block(fi);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_unblocked:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_req:
|
|
/* send DISCONNECT COMPLETE */
|
|
dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0));
|
|
/* do several thing here */
|
|
do_restart(fi);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_compl_req:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_T3:
|
|
/* send DISCONNECT again */
|
|
if (pstn->tx_msg)
|
|
dl_send(fi, v5x_enc_disconnect(pstn->v5up, pstn->tx_msg->data,
|
|
pstn->tx_msg->len));
|
|
else
|
|
dl_send(fi, v5x_enc_disconnect(pstn->v5up, NULL, 0));
|
|
/* start T3 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T3, pstn->timeout_count + 1);
|
|
/* third timeout ? */
|
|
if (pstn->timeout_count > 3) {
|
|
/* send MDU-error_indication */
|
|
rcv_mdu(fi, MDU_error_ind);
|
|
}
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void pstn_le6_port_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct v5x_pstn_proto *pstn = fi->priv;
|
|
struct msgb *msg = data;
|
|
|
|
switch (event) {
|
|
case V5x_PSTN_LE_FE_establish_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_ESTABLISH:
|
|
case V5x_PSTN_LE_DISCONNECT:
|
|
case V5x_PSTN_LE_ESTABLISH_ACK:
|
|
case V5x_PSTN_LE_SIGNAL:
|
|
case V5x_PSTN_LE_SIGNAL_ACK:
|
|
/* do several thing here */
|
|
do_status_enquiry(fi);
|
|
break;
|
|
case V5x_PSTN_LE_DISCONNECT_COMPL:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_FE_establish_ack_req:
|
|
msgb_free(msg);
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_STATUS:
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_blocked:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_port_unblocked:
|
|
/* change state to LE1 */
|
|
osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE1_NULL, 0, 0);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_req:
|
|
/* send MDU-CTRL (restart ack) */
|
|
rcv_mdu(fi, MDU_CTRL_port_restart_ack);
|
|
break;
|
|
case V5x_PSTN_LE_MDU_CTRL_restart_compl_req:
|
|
/* ignore */
|
|
break;
|
|
case V5x_PSTN_LE_TIMEOUT_T4:
|
|
/* first or second timeout ? */
|
|
if (pstn->timeout_count < 3) {
|
|
/* start T4 */
|
|
start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1);
|
|
/* send STATUS ENQUIRY again */
|
|
dl_send(fi, v5x_enc_status_enquiry(pstn->v5up));
|
|
} else {
|
|
/* send MDU-error_indication */
|
|
rcv_mdu(fi, MDU_error_ind);
|
|
}
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
/* Table 3/G.964 Primitives, messages and timers used in the LE(PSTN) FSM */
|
|
static const struct osmo_fsm_state v5x_le_pstn_fsm_states[] = {
|
|
[V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE] = {
|
|
.name = "Out of service (LE0)",
|
|
.in_event_mask = S(V5x_PSTN_LE_FE_establish_req) |
|
|
S(V5x_PSTN_LE_ESTABLISH) |
|
|
S(V5x_PSTN_LE_DISCONNECT) |
|
|
S(V5x_PSTN_LE_DISCONNECT_COMPL) |
|
|
S(V5x_PSTN_LE_FE_establish_ack_req) |
|
|
S(V5x_PSTN_LE_ESTABLISH_ACK) |
|
|
S(V5x_PSTN_LE_SIGNAL) |
|
|
S(V5x_PSTN_LE_SIGNAL_ACK) |
|
|
S(V5x_PSTN_LE_STATUS) |
|
|
S(V5x_PSTN_LE_FE_protocol_param_req) |
|
|
S(V5x_PSTN_LE_FE_disconnect_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_blocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) |
|
|
S(V5x_PSTN_LE_TIMEOUT_T4),
|
|
.out_state_mask = S(V5x_LE_PTSN_S_LE1_NULL) |
|
|
S(V5x_LE_PTSN_S_LE6_PORT_BLOCKED),
|
|
.action = pstn_le0_out_of_service,
|
|
},
|
|
[V5x_LE_PTSN_S_LE1_NULL] = {
|
|
.name = "Null (LE1)",
|
|
.in_event_mask = S(V5x_PSTN_LE_FE_establish_req) |
|
|
S(V5x_PSTN_LE_ESTABLISH) |
|
|
S(V5x_PSTN_LE_DISCONNECT) |
|
|
S(V5x_PSTN_LE_ESTABLISH_ACK) |
|
|
S(V5x_PSTN_LE_SIGNAL) |
|
|
S(V5x_PSTN_LE_SIGNAL_ACK) |
|
|
S(V5x_PSTN_LE_STATUS) |
|
|
S(V5x_PSTN_LE_FE_protocol_param_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_blocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_req) |
|
|
S(V5x_PSTN_LE_TIMEOUT_T4),
|
|
.out_state_mask = S(V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE) |
|
|
S(V5x_LE_PTSN_S_LE2_PATH_INITIATED_LE) |
|
|
S(V5x_LE_PTSN_S_LE3_PATH_INITIATED_AN) |
|
|
S(V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ) |
|
|
S(V5x_LE_PTSN_S_LE6_PORT_BLOCKED),
|
|
.action = pstn_le1_null,
|
|
},
|
|
[V5x_LE_PTSN_S_LE2_PATH_INITIATED_LE] = {
|
|
.name = "Path initiated by LE (LE2)",
|
|
.in_event_mask = S(V5x_PSTN_LE_ESTABLISH) |
|
|
S(V5x_PSTN_LE_DISCONNECT) |
|
|
S(V5x_PSTN_LE_DISCONNECT_COMPL) |
|
|
S(V5x_PSTN_LE_FE_establish_ack_req) |
|
|
S(V5x_PSTN_LE_ESTABLISH_ACK) |
|
|
S(V5x_PSTN_LE_SIGNAL) |
|
|
S(V5x_PSTN_LE_SIGNAL_ACK) |
|
|
S(V5x_PSTN_LE_STATUS) |
|
|
S(V5x_PSTN_LE_FE_protocol_param_req) |
|
|
S(V5x_PSTN_LE_FE_disconnect_req) |
|
|
S(V5x_PSTN_LE_FE_disconnect_compl_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_blocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) |
|
|
S(V5x_PSTN_LE_TIMEOUT_T1) |
|
|
S(V5x_PSTN_LE_TIMEOUT_T4),
|
|
.out_state_mask = S(V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE) |
|
|
S(V5x_LE_PTSN_S_LE1_NULL) |
|
|
S(V5x_LE_PTSN_S_LE4_PATH_ACTIVE) |
|
|
S(V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ) |
|
|
S(V5x_LE_PTSN_S_LE6_PORT_BLOCKED),
|
|
.action = pstn_le2_path_initiated_le,
|
|
},
|
|
[V5x_LE_PTSN_S_LE3_PATH_INITIATED_AN] = {
|
|
.name = "Path initiated by AN (LE3)",
|
|
.in_event_mask = S(V5x_PSTN_LE_FE_establish_req) |
|
|
S(V5x_PSTN_LE_ESTABLISH) |
|
|
S(V5x_PSTN_LE_DISCONNECT) |
|
|
S(V5x_PSTN_LE_DISCONNECT_COMPL) |
|
|
S(V5x_PSTN_LE_FE_establish_ack_req) |
|
|
S(V5x_PSTN_LE_ESTABLISH_ACK) |
|
|
S(V5x_PSTN_LE_SIGNAL) |
|
|
S(V5x_PSTN_LE_SIGNAL_ACK) |
|
|
S(V5x_PSTN_LE_STATUS) |
|
|
S(V5x_PSTN_LE_FE_protocol_param_req) |
|
|
S(V5x_PSTN_LE_FE_disconnect_req) |
|
|
S(V5x_PSTN_LE_FE_disconnect_compl_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_blocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) |
|
|
S(V5x_PSTN_LE_TIMEOUT_T4),
|
|
.out_state_mask = S(V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE) |
|
|
S(V5x_LE_PTSN_S_LE1_NULL) |
|
|
S(V5x_LE_PTSN_S_LE2_PATH_INITIATED_LE) |
|
|
S(V5x_LE_PTSN_S_LE4_PATH_ACTIVE) |
|
|
S(V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ) |
|
|
S(V5x_LE_PTSN_S_LE6_PORT_BLOCKED),
|
|
.action = pstn_le3_path_initiated_an,
|
|
},
|
|
[V5x_LE_PTSN_S_LE4_PATH_ACTIVE] = {
|
|
.name = "Path active (LE4)",
|
|
.in_event_mask = S(V5x_PSTN_LE_ESTABLISH) |
|
|
S(V5x_PSTN_LE_DISCONNECT) |
|
|
S(V5x_PSTN_LE_DISCONNECT_COMPL) |
|
|
S(V5x_PSTN_LE_ESTABLISH_ACK) |
|
|
S(V5x_PSTN_LE_SIGNAL) |
|
|
S(V5x_PSTN_LE_SIGNAL_ACK) |
|
|
S(V5x_PSTN_LE_STATUS) |
|
|
S(V5x_PSTN_LE_FE_line_signal_req) |
|
|
S(V5x_PSTN_LE_FE_protocol_param_req) |
|
|
S(V5x_PSTN_LE_FE_disconnect_req) |
|
|
S(V5x_PSTN_LE_FE_disconnect_compl_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_blocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) |
|
|
S(V5x_PSTN_LE_TIMEOUT_Tr) |
|
|
S(V5x_PSTN_LE_TIMEOUT_Tt) |
|
|
S(V5x_PSTN_LE_TIMEOUT_T4),
|
|
.out_state_mask = S(V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE) |
|
|
S(V5x_LE_PTSN_S_LE1_NULL) |
|
|
S(V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ) |
|
|
S(V5x_LE_PTSN_S_LE6_PORT_BLOCKED),
|
|
.action = pstn_le4_path_active,
|
|
},
|
|
[V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ] = {
|
|
.name = "Path Disconnect request (LE5)",
|
|
.in_event_mask = S(V5x_PSTN_LE_ESTABLISH) |
|
|
S(V5x_PSTN_LE_DISCONNECT) |
|
|
S(V5x_PSTN_LE_DISCONNECT_COMPL) |
|
|
S(V5x_PSTN_LE_ESTABLISH_ACK) |
|
|
S(V5x_PSTN_LE_SIGNAL) |
|
|
S(V5x_PSTN_LE_SIGNAL_ACK) |
|
|
S(V5x_PSTN_LE_STATUS) |
|
|
S(V5x_PSTN_LE_FE_line_signal_req) |
|
|
S(V5x_PSTN_LE_FE_protocol_param_req) |
|
|
S(V5x_PSTN_LE_FE_disconnect_compl_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_blocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) |
|
|
S(V5x_PSTN_LE_TIMEOUT_T3),
|
|
.out_state_mask = S(V5x_LE_PTSN_S_LE1_NULL),
|
|
.action = pstn_le5_path_disconnect_req,
|
|
},
|
|
[V5x_LE_PTSN_S_LE6_PORT_BLOCKED] = {
|
|
.name = "Port blocked (LE6)",
|
|
.in_event_mask = S(V5x_PSTN_LE_FE_establish_req) |
|
|
S(V5x_PSTN_LE_ESTABLISH) |
|
|
S(V5x_PSTN_LE_DISCONNECT) |
|
|
S(V5x_PSTN_LE_DISCONNECT_COMPL) |
|
|
S(V5x_PSTN_LE_FE_establish_ack_req) |
|
|
S(V5x_PSTN_LE_ESTABLISH_ACK) |
|
|
S(V5x_PSTN_LE_SIGNAL) |
|
|
S(V5x_PSTN_LE_SIGNAL_ACK) |
|
|
S(V5x_PSTN_LE_STATUS) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_blocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_req) |
|
|
S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) |
|
|
S(V5x_PSTN_LE_TIMEOUT_T4),
|
|
.out_state_mask = S(V5x_LE_PTSN_S_LE1_NULL),
|
|
.action = pstn_le6_port_blocked,
|
|
},
|
|
};
|
|
|
|
struct osmo_fsm v5x_le_pstn_fsm = {
|
|
.name = "V5X_LE_PSTN",
|
|
.states = v5x_le_pstn_fsm_states,
|
|
.num_states = ARRAY_SIZE(v5x_le_pstn_fsm_states),
|
|
.allstate_event_mask = 0,
|
|
.allstate_action = NULL,
|
|
.cleanup = NULL,
|
|
.timer_cb = v5x_le_pstn_fsm_timer_cb,
|
|
.log_subsys = DV5PSTN,
|
|
.event_names = v5x_ctrl_le_port_fsm_event_names,
|
|
};
|
|
|
|
struct v5x_pstn_proto *v5x_le_pstn_create(struct v5x_user_port *v5up, uint16_t nr)
|
|
{
|
|
struct v5x_pstn_proto *pstn;
|
|
|
|
OSMO_ASSERT(v5up);
|
|
|
|
pstn = talloc_zero(v5up, struct v5x_pstn_proto);
|
|
if (!pstn)
|
|
return NULL;
|
|
pstn->v5up = v5up;
|
|
|
|
pstn->fi = osmo_fsm_inst_alloc(&v5x_le_pstn_fsm, pstn, pstn, LOGL_DEBUG, NULL);
|
|
if (!pstn->fi)
|
|
NULL;
|
|
osmo_fsm_inst_update_id_f(pstn->fi, "%d", nr);
|
|
pstn->timer_Tr.data = pstn;
|
|
pstn->timer_Tr.cb = timeout_Tr_cb;
|
|
pstn->timer_Tt.data = pstn;
|
|
pstn->timer_Tt.cb = timeout_Tt_cb;
|
|
|
|
return pstn;
|
|
}
|
|
|
|
void v5x_le_pstn_destroy(struct v5x_pstn_proto *pstn)
|
|
{
|
|
/* free pending message */
|
|
if (pstn->tx_msg)
|
|
msgb_free(pstn->tx_msg);
|
|
|
|
if (pstn->fi) {
|
|
stop_timer_Tr(pstn);
|
|
stop_timer_Tt(pstn);
|
|
osmo_fsm_inst_free(pstn->fi);
|
|
}
|
|
|
|
talloc_free(pstn);
|
|
}
|
|
|
|
const char *v5x_le_pstn_state_name(struct v5x_pstn_proto *pstn)
|
|
{
|
|
return v5x_le_pstn_fsm_states[pstn->fi->state].name;
|
|
}
|
|
|
|
void v5x_le_pstn_init(void)
|
|
{
|
|
int rc;
|
|
|
|
rc = osmo_fsm_register(&v5x_le_pstn_fsm);
|
|
OSMO_ASSERT(!rc);
|
|
LOGP(DV5PSTN, LOGL_NOTICE, "Using V5x PSTN protocol\n");
|
|
}
|
|
|
|
/***********************************************************************
|
|
* V5 Message receiving / decoding
|
|
***********************************************************************/
|
|
|
|
/* receive message from lower (DL) layer */
|
|
int v5x_le_pstn_dl_rcv(struct v5x_user_port *v5up, uint8_t msg_type, const struct tlv_parsed *tp)
|
|
{
|
|
enum v5x_le_pstn_fsm_event event;
|
|
|
|
switch (msg_type) {
|
|
case V5X_CTRL_MSGT_ESTABLISH:
|
|
event = V5x_PSTN_LE_ESTABLISH;
|
|
break;
|
|
case V5X_CTRL_MSGT_ESTABLISH_ACK:
|
|
event = V5x_PSTN_LE_ESTABLISH_ACK;
|
|
break;
|
|
case V5X_CTRL_MSGT_SIGNAL:
|
|
event = V5x_PSTN_LE_SIGNAL;
|
|
break;
|
|
case V5X_CTRL_MSGT_SIGNAL_ACK:
|
|
event = V5x_PSTN_LE_SIGNAL_ACK;
|
|
break;
|
|
case V5X_CTRL_MSGT_DISCONNECT:
|
|
event = V5x_PSTN_LE_DISCONNECT;
|
|
break;
|
|
case V5X_CTRL_MSGT_DISCONNECT_COMPLETE:
|
|
event = V5x_PSTN_LE_DISCONNECT_COMPL;
|
|
break;
|
|
case V5X_CTRL_MSGT_STATUS:
|
|
event = V5x_PSTN_LE_STATUS;
|
|
break;
|
|
default:
|
|
LOGPFSML(v5up->port_fi, LOGL_NOTICE, "Invalid PSTN protocol message %d receied from AN.\n", msg_type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
osmo_fsm_inst_dispatch(v5up->pstn.proto->fi, event, (void *)tp);
|
|
|
|
return 0;
|
|
}
|