1966 lines
65 KiB
C
1966 lines
65 KiB
C
/* Handle VGCS/VBCS calls. (Voice Group/Broadcast Call Service). */
|
|
/*
|
|
* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
|
* All Rights Reserved
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0+
|
|
*
|
|
* Author: Andreas Eversberg
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation; either version 3 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* Notes on the state machine:
|
|
*
|
|
* The state machine is different from the diagram depicted in the specs.
|
|
* This is because there are some messages missing and some state transitions
|
|
* are different or not shown.
|
|
*
|
|
* A call that has no channel is answered without joining the group channel.
|
|
* If it comes available, the establishment is performed and the U4 is entered.
|
|
*
|
|
* Uplink control is not described in the diagram. Talking/listening is
|
|
* requested by user and can be rejected by MM layer, if talking is not
|
|
* allowed.
|
|
*
|
|
* We can be sure that there is no other MM connection while doing VGCS call
|
|
* establishment: MMxx-EST-REQ is only accpepted, if there is no MM connection.
|
|
* We block calls from user, if there is some other transaction, which is not
|
|
* in state U3. Also we accept incoming indications any time and create
|
|
* transactions that go to state U3.
|
|
*
|
|
* If the upper layer or lower layer requests another call/SMS/SS while VGCS
|
|
* call is ongoing, this may cause undefined behaviour.
|
|
*
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <osmocom/core/msgb.h>
|
|
#include <osmocom/core/utils.h>
|
|
#include <osmocom/gsm/gsm48.h>
|
|
#include <osmocom/gsm/protocol/gsm_44_068.h>
|
|
#include <osmocom/core/talloc.h>
|
|
#include <osmocom/core/fsm.h>
|
|
|
|
#include <osmocom/bb/common/logging.h>
|
|
#include <osmocom/bb/common/osmocom_data.h>
|
|
#include <osmocom/bb/common/ms.h>
|
|
#include <osmocom/bb/mobile/mncc.h>
|
|
#include <osmocom/bb/mobile/transaction.h>
|
|
#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h>
|
|
#include <osmocom/bb/mobile/tch.h>
|
|
#include <osmocom/bb/mobile/vty.h>
|
|
#include <l1ctl_proto.h>
|
|
|
|
#define S(x) (1 << (x))
|
|
|
|
#define LOG_GCC(trans, level, fmt, args...) \
|
|
LOGP(((trans)->protocol == GSM48_PDISC_GROUP_CC) ? DGCC : DBCC, level, \
|
|
((trans)->protocol == GSM48_PDISC_GROUP_CC) ? ("VGCS callref %u: " fmt) : ("VBS callref %u: " fmt), \
|
|
(trans)->callref, ##args)
|
|
#define LOG_GCC_PR(protocol, ref, level, fmt, args...) \
|
|
LOGP((protocol == GSM48_PDISC_GROUP_CC) ? DGCC : DBCC, level, \
|
|
(protocol == GSM48_PDISC_GROUP_CC) ? ("VGCS callref %u: " fmt) : ("VBS callref %u: " fmt), \
|
|
ref, ##args)
|
|
|
|
/*
|
|
* init
|
|
*/
|
|
|
|
int gsm44068_gcc_init(struct osmocom_ms *ms)
|
|
{
|
|
LOGP(DGCC, LOGL_INFO, "init GCC/BCC\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gsm44068_gcc_exit(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_trans *trans, *trans2;
|
|
|
|
LOGP(DGCC, LOGL_INFO, "exit GCC/BCC processes for %s\n", ms->name);
|
|
|
|
llist_for_each_entry_safe(trans, trans2, &ms->trans_list, entry) {
|
|
if (trans->protocol == GSM48_PDISC_GROUP_CC || trans->protocol == GSM48_PDISC_BCAST_CC) {
|
|
LOG_GCC(trans, LOGL_NOTICE, "Free pendig CC-transaction.\n");
|
|
trans_free(trans);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* messages
|
|
*/
|
|
|
|
/* TS 44.068 Chapter 6.1.2.1 */
|
|
enum vgcs_gcc_fsm_states {
|
|
VGCS_GCC_ST_U0_NULL = 0,
|
|
VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING,
|
|
VGCS_GCC_ST_U1_GROUP_CALL_INITIATED,
|
|
VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE, /* sepeate link */
|
|
VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE, /* wait for receive mode */
|
|
VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, /* receive mode / U6 @ BCC */
|
|
VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE, /* wait for send and receive mode */
|
|
VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE, /* send and receive mode */
|
|
VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE, /* no channel */
|
|
VGCS_GCC_ST_U3_GROUP_CALL_PRESENT,
|
|
VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST,
|
|
VGCS_GCC_ST_U5_TERMINATION_REQUESTED,
|
|
};
|
|
|
|
/* TS 44.068 Figure 6.1 (additional events added) */
|
|
enum vgcs_gcc_fsm_event {
|
|
VGCS_GCC_EV_SETUP_REQ, /* calling user initiates call */
|
|
VGCS_GCC_EV_TERM_REQ, /* calling user requests termination */
|
|
VGCS_GCC_EV_MM_EST_CNF, /* MM connection established */
|
|
VGCS_GCC_EV_MM_EST_REJ, /* MM connection failed */
|
|
VGCS_GCC_EV_DI_TERMINATION, /* network acknowledges termination */
|
|
VGCS_GCC_EV_DI_TERM_REJECT, /* network rejects termination */
|
|
VGCS_GCC_EV_DI_CONNECT, /* network indicates connect */
|
|
VGCS_GCC_EV_TIMEOUT, /* several timeout events */
|
|
VGCS_GCC_EV_SETUP_IND, /* notification of ongoing call received */
|
|
VGCS_GCC_EV_REL_IND, /* notification of call being gone */
|
|
VGCS_GCC_EV_JOIN_GC_REQ, /* user wants to join ongoing call */
|
|
VGCS_GCC_EV_JOIN_GC_CNF, /* MM confirms joining ongoing call */
|
|
VGCS_GCC_EV_ABORT_REQ, /* user rejects or leaves call */
|
|
VGCS_GCC_EV_ABORT_IND, /* MM indicates channel released or failed */
|
|
VGCS_GCC_EV_TALK_REQ, /* user wants to talk */
|
|
VGCS_GCC_EV_TALK_CNF, /* MM confirms talk */
|
|
VGCS_GCC_EV_TALK_REJ, /* MM rejects talk */
|
|
VGCS_GCC_EV_LISTEN_REQ, /* user wants to listen */
|
|
VGCS_GCC_EV_LISTEN_CNF, /* MM confirms listen */
|
|
VGCS_GCC_EV_MM_IDLE, /* MM layer becomes ready for new channel */
|
|
VGCS_GCC_EV_UPLINK_FREE, /* MM layer indicates free uplink in group receive mode */
|
|
VGCS_GCC_EV_UPLINK_BUSY, /* MM layer indicates busy uplink in group receive mode */
|
|
};
|
|
|
|
static const struct value_string vgcs_gcc_fsm_event_names[] = {
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_SETUP_REQ),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_TERM_REQ),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_MM_EST_CNF),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_MM_EST_REJ),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_DI_TERMINATION),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_DI_TERM_REJECT),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_DI_CONNECT),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_TIMEOUT),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_SETUP_IND),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_REL_IND),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_JOIN_GC_REQ),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_JOIN_GC_CNF),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_ABORT_REQ),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_ABORT_IND),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_TALK_REQ),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_TALK_CNF),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_TALK_REJ),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_LISTEN_REQ),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_LISTEN_CNF),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_MM_IDLE),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_UPLINK_FREE),
|
|
OSMO_VALUE_STRING(VGCS_GCC_EV_UPLINK_BUSY),
|
|
{ }
|
|
};
|
|
|
|
/*! return string representation of GCC/BCC Message Type */
|
|
static const char *gsm44068_gcc_msg_name(uint8_t msg_type)
|
|
{
|
|
return get_value_string(osmo_gsm44068_msg_type_names, msg_type);
|
|
}
|
|
|
|
#define TFU(param) ((param < 0) ? "unchanged" : ((param) ? "T" : "F"))
|
|
|
|
/* Set state attributes and check if they are consistent with the current state. */
|
|
static int set_state_attributes(struct gsm_trans *trans, int d_att, int u_att, int comm, int orig, int call_state)
|
|
{
|
|
bool orig_t = false, comm_t = false;
|
|
|
|
LOG_GCC(trans, LOGL_DEBUG, "Setting state attributes: D-ATT = %s, U-ATT = %s, COMM = %s, ORIG = %s.\n",
|
|
TFU(d_att), TFU(u_att), TFU(comm), TFU(orig));
|
|
|
|
/* Control Speaker. */
|
|
if (d_att >= 0 && trans->gcc.d_att != d_att) {
|
|
LOG_GCC(trans, LOGL_DEBUG, "Switching Speaker to %d\n", d_att);
|
|
gsm48_rr_audio_mode(trans->ms, AUDIO_TX_MICROPHONE | (d_att * AUDIO_RX_SPEAKER));
|
|
}
|
|
|
|
if (d_att >= 0)
|
|
trans->gcc.d_att = d_att;
|
|
if (u_att >= 0)
|
|
trans->gcc.u_att = u_att;
|
|
if (comm >= 0)
|
|
trans->gcc.comm = comm;
|
|
if (orig >= 0)
|
|
trans->gcc.orig = orig;
|
|
if (call_state >= 0)
|
|
trans->gcc.call_state = call_state;
|
|
|
|
switch (trans->gcc.fi->state) {
|
|
case VGCS_GCC_ST_U3_GROUP_CALL_PRESENT:
|
|
case VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST:
|
|
orig_t = orig;
|
|
comm_t = comm;
|
|
break;
|
|
case VGCS_GCC_ST_U0_NULL:
|
|
case VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE:
|
|
case VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE:
|
|
comm_t = comm;
|
|
break;
|
|
}
|
|
|
|
if (orig_t)
|
|
LOG_GCC(trans, LOGL_ERROR, "ORIG = T is inconsistent with states U3 and U4. Please fix!");
|
|
|
|
if (comm_t)
|
|
LOG_GCC(trans, LOGL_ERROR,
|
|
"COMM = T is inconsistent with states U0, U3, U4, U2nc and U2r. Please fix!");
|
|
|
|
return (orig_t || comm_t) ? -EINVAL : 0;
|
|
}
|
|
|
|
static void vgcs_vty_notify(struct gsm_trans *trans, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
|
|
|
static void vgcs_vty_notify(struct gsm_trans *trans, const char *fmt, ...)
|
|
{
|
|
struct osmocom_ms *ms = trans->ms;
|
|
char buffer[1000];
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
vsnprintf(buffer, sizeof(buffer) - 1, fmt, args);
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
va_end(args);
|
|
|
|
l23_vty_ms_notify(ms, NULL);
|
|
l23_vty_ms_notify(ms, "%s call %d: %s", (trans->protocol == GSM48_PDISC_GROUP_CC) ? "Group" : "Broadcast",
|
|
trans->callref, buffer);
|
|
}
|
|
|
|
/*
|
|
* messages
|
|
*/
|
|
|
|
/* Send MMxx-GROUP-REQ to MM. */
|
|
static int vgcs_group_req(struct gsm_trans *trans)
|
|
{
|
|
struct msgb *nmsg;
|
|
struct gsm48_mmxx_hdr *nmmh;
|
|
uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_GROUP_REQ : GSM48_MMBCC_GROUP_REQ;
|
|
|
|
LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type));
|
|
|
|
OSMO_ASSERT(trans->gcc.ch_desc_present);
|
|
|
|
nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
nmmh = (struct gsm48_mmxx_hdr *) nmsg->data;
|
|
nmmh->ch_desc_present = trans->gcc.ch_desc_present;
|
|
memcpy(&nmmh->ch_desc, &trans->gcc.ch_desc, sizeof(nmmh->ch_desc));
|
|
|
|
return gsm48_mmxx_downmsg(trans->ms, nmsg);
|
|
}
|
|
|
|
/* Send MMxx-EST-REQ to MM. */
|
|
static int vgcs_est_req(struct gsm_trans *trans)
|
|
{
|
|
struct msgb *nmsg;
|
|
uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_EST_REQ : GSM48_MMBCC_EST_REQ;
|
|
|
|
LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type));
|
|
|
|
nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
|
|
return gsm48_mmxx_downmsg(trans->ms, nmsg);
|
|
}
|
|
|
|
/* Push message and send MMxx-DATA-REQ to MM. */
|
|
static int vgcs_data_req(struct gsm_trans *trans, struct msgb *msg)
|
|
{
|
|
struct gsm48_mmxx_hdr *mmh;
|
|
uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_DATA_REQ : GSM48_MMBCC_DATA_REQ;
|
|
|
|
/* push RR header */
|
|
msg->l3h = msg->data;
|
|
msgb_push(msg, sizeof(struct gsm48_mmxx_hdr));
|
|
mmh = (struct gsm48_mmxx_hdr *)msg->data;
|
|
mmh->msg_type = msg_type;
|
|
mmh->ref = trans->callref;
|
|
mmh->transaction_id = trans->transaction_id;
|
|
mmh->sapi = 0;
|
|
|
|
/* send message to MM */
|
|
return gsm48_mmxx_downmsg(trans->ms, msg);
|
|
}
|
|
|
|
/* Send MMxx-REL-REQ to MM. */
|
|
static int vgcs_rel_req(struct gsm_trans *trans)
|
|
{
|
|
struct msgb *nmsg;
|
|
uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_REL_REQ : GSM48_MMBCC_REL_REQ;
|
|
|
|
LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type));
|
|
|
|
nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
|
|
return gsm48_mmxx_downmsg(trans->ms, nmsg);
|
|
}
|
|
|
|
/* Send MMxx-UPLINK-REQ to MM. */
|
|
static int vgcs_uplink_req(struct gsm_trans *trans)
|
|
{
|
|
struct msgb *nmsg;
|
|
uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_UPLINK_REQ : GSM48_MMBCC_UPLINK_REQ;
|
|
|
|
LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type));
|
|
|
|
nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
|
|
return gsm48_mmxx_downmsg(trans->ms, nmsg);
|
|
}
|
|
|
|
/* Send MMxx-UPLINK-REL-REQ to MM. */
|
|
static int vgcs_uplink_rel_req(struct gsm_trans *trans)
|
|
{
|
|
struct msgb *nmsg;
|
|
uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_UPLINK_REL_REQ
|
|
: GSM48_MMBCC_UPLINK_REL_REQ;
|
|
|
|
LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type));
|
|
|
|
nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
|
|
return gsm48_mmxx_downmsg(trans->ms, nmsg);
|
|
}
|
|
|
|
static void _add_callref_ie(struct msgb *msg, uint32_t callref, bool with_prio, uint8_t prio)
|
|
{
|
|
uint32_t ie;
|
|
|
|
ie = callref << 5;
|
|
if (with_prio)
|
|
ie |= 0x10 | (prio << 1);
|
|
msgb_put_u32(msg, ie);
|
|
}
|
|
|
|
static void _add_user_user_ie(struct msgb *msg, uint8_t user_pdisc, uint8_t *user, uint8_t user_len)
|
|
{
|
|
uint8_t *ie;
|
|
|
|
ie = msgb_put(msg, user_len + 2);
|
|
*ie++ = GSM48_IE_USER_USER;
|
|
*ie++ = user_len;
|
|
memcpy(ie, user, user_len);
|
|
}
|
|
|
|
#define GSM44068_IE_CALL_STATE 0xA0
|
|
#define GSM44068_IE_STATE_ATTRS 0xB0
|
|
#define GSM44068_IE_TALKER_PRIO 0xc0
|
|
|
|
static void _add_call_state_ie(struct msgb *msg, uint8_t call_state)
|
|
{
|
|
msgb_put_u8(msg, GSM44068_IE_CALL_STATE | call_state);
|
|
}
|
|
|
|
static void _add_state_attrs_ie(struct msgb *msg, uint8_t da, uint8_t ua, uint8_t comm, uint8_t oi)
|
|
{
|
|
msgb_put_u8(msg, GSM44068_IE_STATE_ATTRS | (da << 3) | (ua << 2) | (comm << 1) | oi);
|
|
}
|
|
|
|
static void _add_talker_prio_ie(struct msgb *msg, uint8_t talker_prio)
|
|
{
|
|
msgb_put_u8(msg, GSM44068_IE_TALKER_PRIO | talker_prio);
|
|
}
|
|
|
|
static void _add_cause_ie(struct msgb *msg, uint8_t cause, uint8_t *diag, uint8_t diag_len)
|
|
{
|
|
uint8_t *ie;
|
|
|
|
ie = msgb_put(msg, diag_len + 2);
|
|
*ie++ = diag_len + 1;
|
|
*ie++ = 0x80 | cause;
|
|
if (diag_len && diag)
|
|
memcpy(ie, diag, diag_len);
|
|
}
|
|
|
|
static int _msg_too_short(struct gsm_trans *trans)
|
|
{
|
|
LOG_GCC(trans, LOGL_ERROR, "MSG too short.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int _ie_invalid(struct gsm_trans *trans)
|
|
{
|
|
LOG_GCC(trans, LOGL_ERROR, "IE invalid.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* 3GPP TS 44.068 Clause 8.4 */
|
|
static int gsm44068_rx_set_parameter(struct gsm_trans *trans, struct msgb *msg,
|
|
uint8_t *da, uint8_t *ua, uint8_t *comm, uint8_t *oi)
|
|
{
|
|
struct gsm48_hdr *gh = msgb_l3(msg);
|
|
unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh);
|
|
uint8_t *ie = gh->data;
|
|
|
|
/* State attributes */
|
|
if (remaining_len < 1)
|
|
return _msg_too_short(trans);
|
|
if (da)
|
|
*da = (ie[0] >> 3) & 0x1;
|
|
if (ua)
|
|
*ua = (ie[0] >> 2) & 0x1;
|
|
if (comm)
|
|
*comm = (ie[0] >> 1) & 0x1;
|
|
if (oi)
|
|
*oi = ie[0] & 0x1;
|
|
ie += 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* 3GPP TS 44.068 Clause 8.5 */
|
|
static int gsm44068_tx_setup(struct gsm_trans *trans, uint32_t callref, bool with_prio, uint8_t prio,
|
|
uint8_t user_pdisc, uint8_t *user, uint8_t user_len,
|
|
bool with_talker_prio, uint8_t talker_prio)
|
|
{
|
|
struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX SETUP");
|
|
struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
|
|
|
|
LOG_GCC(trans, LOGL_INFO, "Sending SETUP.\n");
|
|
|
|
gh->proto_discr = trans->protocol | (trans->transaction_id << 4);
|
|
gh->msg_type = OSMO_GSM44068_MSGT_SETUP;
|
|
_add_callref_ie(msg, callref, with_prio, prio);
|
|
if (user_len && user)
|
|
_add_user_user_ie(msg, user_pdisc, user, user_len);
|
|
if (with_talker_prio)
|
|
_add_talker_prio_ie(msg, talker_prio);
|
|
|
|
return vgcs_data_req(trans, msg);
|
|
}
|
|
|
|
/* 3GPP TS 44.068 Clause 8.6 */
|
|
static int gsm44068_tx_status(struct gsm_trans *trans, uint8_t cause, uint8_t *diag, uint8_t diag_len,
|
|
bool with_call_state, uint8_t call_state, bool with_state_attrs,
|
|
uint8_t da, uint8_t ua, uint8_t comm, uint8_t oi)
|
|
{
|
|
struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX STATUS");
|
|
struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
|
|
|
|
LOG_GCC(trans, LOGL_INFO, "Sending STATUS.\n");
|
|
|
|
gh->proto_discr = trans->protocol | (trans->transaction_id << 4);
|
|
gh->msg_type = OSMO_GSM44068_MSGT_STATUS;
|
|
_add_cause_ie(msg, cause, diag, diag_len);
|
|
if (with_call_state)
|
|
_add_call_state_ie(msg, call_state);
|
|
if (with_state_attrs)
|
|
_add_state_attrs_ie(msg, da, ua, comm, oi);
|
|
|
|
return vgcs_data_req(trans, msg);
|
|
}
|
|
|
|
/* 3GPP TS 44.068 Clause 8.7 and 8.8 */
|
|
static int gsm44068_rx_termination(struct gsm_trans *trans, struct msgb *msg, uint8_t *cause, uint8_t *diag, uint8_t *diag_len)
|
|
{
|
|
struct gsm48_hdr *gh = msgb_l3(msg);
|
|
unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh);
|
|
uint8_t *ie = gh->data;
|
|
uint8_t ie_len;
|
|
|
|
/* Cause */
|
|
if (remaining_len < 2 || ie[0] < remaining_len - 2)
|
|
return _msg_too_short(trans);
|
|
ie_len = ie[0];
|
|
if (remaining_len < ie_len + 1)
|
|
return _msg_too_short(trans);
|
|
if (ie_len < 1)
|
|
return _ie_invalid(trans);
|
|
if (cause)
|
|
*cause = ie[1] & 0x7f;
|
|
if (diag && diag_len) {
|
|
*diag_len = ie_len - 1;
|
|
if (*diag_len)
|
|
memcpy(diag, ie + 2, ie_len - 1);
|
|
}
|
|
remaining_len -= ie_len + 1;
|
|
ie += ie_len + 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* 3GPP TS 44.068 Clause 8.9 */
|
|
static int gsm44068_tx_termination_request(struct gsm_trans *trans, uint32_t callref, bool with_prio, uint8_t prio,
|
|
bool with_talker_prio, uint8_t talker_prio)
|
|
{
|
|
struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX TERMINATION REQUEST");
|
|
struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
|
|
|
|
LOG_GCC(trans, LOGL_INFO, "Sending TERMINATION REQUEST.\n");
|
|
|
|
gh->proto_discr = trans->protocol | (trans->transaction_id << 4);
|
|
gh->msg_type = OSMO_GSM44068_MSGT_TERMINATION_REQUEST;
|
|
_add_callref_ie(msg, callref, with_prio, prio);
|
|
if (with_talker_prio)
|
|
_add_talker_prio_ie(msg, talker_prio);
|
|
|
|
return vgcs_data_req(trans, msg);
|
|
}
|
|
|
|
/* 3GPP TS 44.018 Clause 9.1.48 */
|
|
static int gsm44068_tx_uplink_release(struct gsm_trans *trans, uint8_t cause)
|
|
{
|
|
struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX UPLINK RELEASE");
|
|
struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
|
|
struct gsm48_uplink_release *ur = (struct gsm48_uplink_release *) msgb_put(msg, sizeof(*ur));
|
|
|
|
LOG_GCC(trans, LOGL_INFO, "UPLINK RELEASE (cause #%d)\n", cause);
|
|
|
|
gh->proto_discr = GSM48_PDISC_RR;
|
|
gh->msg_type = GSM48_MT_RR_UPLINK_RELEASE;
|
|
ur->rr_cause = cause;
|
|
|
|
return vgcs_data_req(trans, msg);
|
|
}
|
|
|
|
/*
|
|
* GCC/BCC state machine
|
|
*
|
|
* For reference see Figure 6.1 of TS 44.068.
|
|
*
|
|
* Note: There are some events that are not depicted in the state diagram:
|
|
*
|
|
* "L: ABORT-IND" indicates closing/failing of radio channel.
|
|
* "H: TALK-REQ" request talking on the channel.
|
|
* "L: TALK-CNF" confirms talker.
|
|
* "L: TALK-REJ" rejects talker.
|
|
* "H: LISTEN-REQ" request listening.
|
|
* "L: LISTEN-CNF" confirms listening.
|
|
*
|
|
*/
|
|
|
|
/* Table 6.1 of TS 44.068 */
|
|
#define T_NO_CHANNEL 3
|
|
#define T_MM_EST 7
|
|
#define T_TERM 10
|
|
#define T_CONN_REQ 10
|
|
|
|
static void vgcs_gcc_fsm_u0_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U0);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u0_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_SETUP_REQ:
|
|
/* The calling user initiates a new call. */
|
|
LOG_GCC(trans, LOGL_INFO, "Received call from user.\n");
|
|
/* Change to MM CONNECTION PENDING state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING, 0, 0);
|
|
/* Send EST-REQ to MM layer. */
|
|
vgcs_est_req(trans);
|
|
break;
|
|
case VGCS_GCC_EV_SETUP_IND:
|
|
/* New call notification. */
|
|
LOG_GCC(trans, LOGL_INFO, "Received call from network.\n");
|
|
/* Change to GROUP CALL PRESENT state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0);
|
|
/* Notify call at VTY. */
|
|
vgcs_vty_notify(trans, "Incoming call\n");
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u0p_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, 0, 0, 0, 1, OSMO_GSM44068_CSTATE_U0p);
|
|
|
|
/* Start timer */
|
|
osmo_timer_schedule(&fi->timer, T_MM_EST, 0);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u0p_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
uint8_t *cause = data;
|
|
int rc;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_TERM_REQ:
|
|
/* The user terminates the call. */
|
|
LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Free transaction. MM confirmation/rejection is handled without transaction also. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_MM_EST_REJ:
|
|
/* The MM layer rejects the call. */
|
|
LOG_GCC(trans, LOGL_INFO, "Call was rejected by MM layer.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify reject at VTY. */
|
|
vgcs_vty_notify(trans, "Rejected (cause %d)\n", *cause);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_MM_EST_CNF:
|
|
/* The MM connection was confirmed. */
|
|
LOG_GCC(trans, LOGL_INFO, "Call was confirmed by MM layer.\n");
|
|
/* Change to GROUP CALL INITIATED state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U1_GROUP_CALL_INITIATED, 0, 0);
|
|
/* Choose transaction ID. */
|
|
rc = trans_assign_trans_id(trans->ms, trans->protocol, 0);
|
|
if (rc < 0) {
|
|
/* No free transaction ID. */
|
|
trans_free(trans);
|
|
return;
|
|
}
|
|
trans->transaction_id = rc;
|
|
/* Send SETUP towards network. */
|
|
gsm44068_tx_setup(trans, trans->callref, false, 0, false, NULL, 0, false, 0);
|
|
break;
|
|
case VGCS_GCC_EV_TIMEOUT:
|
|
/* Establishment of MM layer timed out. */
|
|
LOG_GCC(trans, LOGL_INFO, "MM layer timed out.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Free transaction. MM confirmation/rejection is handled without transaction also. */
|
|
trans_free(trans);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u1_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, 0, 0, 1, 1, OSMO_GSM44068_CSTATE_U1);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u1_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
uint8_t *cause = data;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_DI_CONNECT:
|
|
/* Received CONNECT from network. */
|
|
LOG_GCC(trans, LOGL_INFO, "Call was accepted by network.\n");
|
|
/* Change to GROUP CALL ACTIVE (separate link) state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Notify connect at VTY. */
|
|
vgcs_vty_notify(trans, "Connect\n");
|
|
break;
|
|
case VGCS_GCC_EV_DI_TERMINATION:
|
|
/* Received TERMINATION from network. */
|
|
LOG_GCC(trans, LOGL_INFO, "Call was terminated by network (cause %d).\n", *cause);
|
|
/* Chane to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause);
|
|
/* Release MM connection. */
|
|
vgcs_rel_req(trans);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_TERM_REQ:
|
|
/* The user terminates the call. */
|
|
LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n");
|
|
/* Change to TERMINATION REQUESTED state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U5_TERMINATION_REQUESTED, 0, 0);
|
|
/* Send TERMINATION REQUEST towards network. */
|
|
gsm44068_tx_termination_request(trans, trans->callref, false, 0, false, 0);
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_IND:
|
|
/* Radio link was released or failed. */
|
|
LOG_GCC(trans, LOGL_INFO, "Got release from MM layer.\n");
|
|
/* Chane to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Released (cause %d)\n", *cause);
|
|
/* Release MM connection. */
|
|
vgcs_rel_req(trans);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2sl_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, 1, 1, 1, 1, OSMO_GSM44068_CSTATE_U2sl_U2);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2sl_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
uint8_t *cause = data;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_DI_TERMINATION:
|
|
/* Received TERMINATION from network. */
|
|
LOG_GCC(trans, LOGL_INFO, "Call was terminated by network (cause %d).\n", *cause);
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause);
|
|
/* Don't release MM connection, this is done from network side using CHANNEL RELEASE. */
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_TERM_REQ:
|
|
/* The user terminates the call. */
|
|
LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n");
|
|
/* Change to TERMINATION REQUESTED state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U5_TERMINATION_REQUESTED, 0, 0);
|
|
/* Send TERMINATION REQUEST towards network. */
|
|
gsm44068_tx_termination_request(trans, trans->callref, false, 0, false, 0);
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_IND:
|
|
/* Radio link was released or failed. */
|
|
LOG_GCC(trans, LOGL_INFO, "Got release from MM layer.\n");
|
|
/* Chane to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Released (cause %d)\n", *cause);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_LISTEN_REQ:
|
|
/* The user wants to release dedicated link and join the group channel as listener. */
|
|
LOG_GCC(trans, LOGL_INFO, "User releases uplink on dedicated channel.\n");
|
|
/* Change state to ACTIVE (wait receive). */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Set flag that we change to group receive mode after separate link. */
|
|
trans->gcc.receive_after_sl = true;
|
|
/* Request release of the uplink. */
|
|
gsm44068_tx_uplink_release(trans, GSM48_RR_CAUSE_LEAVE_GROUP_CA);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2wr_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, 1, 0, 1, -1, OSMO_GSM44068_CSTATE_U2wr_U6);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2wr_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
uint8_t *cause = data;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_LISTEN_CNF:
|
|
/* The MM layer confirms uplink release. */
|
|
LOG_GCC(trans, LOGL_INFO, "Uplink is now released.\n");
|
|
/* Change to CALL PRESENT state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Notify answer at VTY. */
|
|
vgcs_vty_notify(trans, "Listening\n");
|
|
break;
|
|
case VGCS_GCC_EV_TERM_REQ:
|
|
/* The calling subscriber wants to terminate the call. */
|
|
LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n");
|
|
trans->gcc.termination = true;
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_REQ:
|
|
/* User aborts group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n");
|
|
/* Change to GROUP CALL PRESENT state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0);
|
|
/* Reset flag after we changed to group receive mode after separate link. */
|
|
trans->gcc.receive_after_sl = false;
|
|
/* Notify leaving at VTY. */
|
|
vgcs_vty_notify(trans, "Call left\n");
|
|
/* Release MM connection. (Leave call.) */
|
|
vgcs_rel_req(trans);
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_IND:
|
|
/* The MM layer released the group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n");
|
|
if (trans->gcc.receive_after_sl) {
|
|
LOG_GCC(trans, LOGL_INFO, "Ignoring release, because we released separate link.\n");
|
|
break;
|
|
}
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Released (cause %d)\n", *cause);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_MM_IDLE:
|
|
if (!trans->gcc.receive_after_sl)
|
|
break;
|
|
/* If no channel is available (no notification received), enter the U2nc state. */
|
|
if (!trans->gcc.ch_desc_present) {
|
|
/* Change state to ACTIVE (no channel). */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Notify answer at VTY. */
|
|
vgcs_vty_notify(trans, "Listen (no channel yet)\n");
|
|
break;
|
|
}
|
|
/* The MM layer indicates that the phone is ready to request group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "MM is now idle, we can request group channel.\n");
|
|
/* Send GROUP-REQ to MM layer. */
|
|
vgcs_group_req(trans);
|
|
break;
|
|
case VGCS_GCC_EV_JOIN_GC_CNF:
|
|
/* Reset flag after we changed to group receive mode after separate link. */
|
|
trans->gcc.receive_after_sl = false;
|
|
/* The MM layer confirms group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "Joined group call after releasing separate link.\n");
|
|
/* Change to CALL PRESENT state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Notify answer at VTY. */
|
|
vgcs_vty_notify(trans, "Listening\n");
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2r_u6_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, 1, 0, 0, -1, (trans->protocol == GSM48_PDISC_GROUP_CC) ?
|
|
OSMO_GSM44068_CSTATE_U2r : OSMO_GSM44068_CSTATE_U2wr_U6);
|
|
|
|
/* There is a pending termination request, request uplink. */
|
|
if (trans->gcc.termination)
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_REQ, NULL);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2r_u6_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
uint8_t *cause = data;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_TERM_REQ:
|
|
/* The calling subscriber wants to terminate the call. */
|
|
LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n");
|
|
trans->gcc.termination = true;
|
|
/* fall-thru */
|
|
case VGCS_GCC_EV_TALK_REQ:
|
|
/* The user wants to talk. */
|
|
LOG_GCC(trans, LOGL_INFO, "User wants to talk on the uplink.\n");
|
|
/* Change to GROUP CALL ACTIVE (wait send) state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Request group transmit mode from MM layer. */
|
|
vgcs_uplink_req(trans);
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_REQ:
|
|
/* User aborts group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n");
|
|
/* Change to GROUP CALL PRESENT state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0);
|
|
/* Notify leaving at VTY. */
|
|
vgcs_vty_notify(trans, "Call left\n");
|
|
/* Release MM connection. (Leave call.) */
|
|
vgcs_rel_req(trans);
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_IND:
|
|
/* The MM layer released the group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Released (cause %d)\n", *cause);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_UPLINK_FREE:
|
|
/* The MM layer indicates that the uplink is free. */
|
|
LOG_GCC(trans, LOGL_INFO, "Uplink free indication received from MM layer.\n");
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Uplink free => You may talk\n");
|
|
break;
|
|
case VGCS_GCC_EV_UPLINK_BUSY:
|
|
/* The MM layer indicates that the uplink is busy. */
|
|
LOG_GCC(trans, LOGL_INFO, "Uplink busy indication received from MM layer.\n");
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Uplink busy => You cannot talk\n");
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2ws_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, 1, 1, 0, -1, OSMO_GSM44068_CSTATE_U2ws);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2ws_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
uint8_t *cause = data;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_TALK_CNF:
|
|
/* Uplink was granted. */
|
|
LOG_GCC(trans, LOGL_INFO, "Uplink established, user can talk now.\n");
|
|
/* Change to GROUP CALL ACTIVE (send and receive) state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Talking\n");
|
|
break;
|
|
case VGCS_GCC_EV_TALK_REJ:
|
|
/* Uplink was rejected. */
|
|
LOG_GCC(trans, LOGL_INFO, "Uplink rejected, user cannot talk.\n");
|
|
/* Clear termination flag, if set. */
|
|
trans->gcc.termination = false;
|
|
/* Change to GROUP CALL ACTIVE (receive) state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Talking rejected (cause %d)\n", *cause);
|
|
break;
|
|
case VGCS_GCC_EV_TERM_REQ:
|
|
/* The calling subscriber wants to terminate the call. */
|
|
LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n");
|
|
trans->gcc.termination = true;
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_REQ:
|
|
/* User aborts group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n");
|
|
/* Change to GROUP CALL PRESENT state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0);
|
|
/* Notify leaving at VTY. */
|
|
vgcs_vty_notify(trans, "Call left\n");
|
|
/* Release MM connection. (Leave call.) */
|
|
vgcs_rel_req(trans);
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_IND:
|
|
/* The MM layer released the group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Released (cause %d)\n", *cause);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2sr_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, 1, 1, 1, -1, OSMO_GSM44068_CSTATE_U2sr);
|
|
|
|
/* There is a pending termination request, request uplink. */
|
|
if (trans->gcc.termination)
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TERM_REQ, NULL);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2sr_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
uint8_t *cause = data;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_DI_TERMINATION:
|
|
/* Received TERMINATION from network. */
|
|
LOG_GCC(trans, LOGL_INFO, "Call was terminated by network (cause %d).\n", *cause);
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause);
|
|
/* Don't release MM connection, this is done from network side using CHANNEL RELEASE. */
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_TALK_REJ:
|
|
/* Uplink was rejected by network. (Reject after granting access.) */
|
|
LOG_GCC(trans, LOGL_INFO, "Uplink rejected, user cannot talk.\n");
|
|
/* Change to GROUP CALL ACTIVE (receive) state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Talking rejected (cause %d)\n", *cause);
|
|
break;
|
|
case VGCS_GCC_EV_LISTEN_REQ:
|
|
/* The user wants to release the uplink and become a listener. */
|
|
LOG_GCC(trans, LOGL_INFO, "User wants to release the uplink.\n");
|
|
/* Change to GROUP CALL ACTIVE (wait receive) state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Request group receive mode from MM layer. */
|
|
vgcs_uplink_rel_req(trans);
|
|
break;
|
|
case VGCS_GCC_EV_TERM_REQ:
|
|
/* The user terminates the call. */
|
|
LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n");
|
|
/* Change to TERMINATION REQUESTED state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U5_TERMINATION_REQUESTED, 0, 0);
|
|
/* Send TERMINATION REQUEST towards network. */
|
|
gsm44068_tx_termination_request(trans, trans->callref, false, 0, false, 0);
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_REQ:
|
|
/* User aborts group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n");
|
|
/* Change to GROUP CALL PRESENT state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0);
|
|
/* Notify leaving at VTY. */
|
|
vgcs_vty_notify(trans, "Call left\n");
|
|
/* Release MM connection. (Leave call.) */
|
|
vgcs_rel_req(trans);
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_IND:
|
|
/* The MM layer released the group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Released (cause %d)\n", *cause);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2nc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, 1, 1, 0, -1, OSMO_GSM44068_CSTATE_U2nc);
|
|
|
|
/* Start timer */
|
|
osmo_timer_schedule(&fi->timer, T_NO_CHANNEL, 0);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u2nc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_SETUP_IND:
|
|
/* Channel becomes available, now join the group call. */
|
|
LOG_GCC(trans, LOGL_INFO, "Radio channel becomes available.\n");
|
|
/* Change to CALL PRESENT state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST, 0, 0);
|
|
/* Send GROUP-REQ to MM layer. */
|
|
vgcs_group_req(trans);
|
|
break;
|
|
case VGCS_GCC_EV_TERM_REQ:
|
|
/* The calling subscriber wants to terminate the call. */
|
|
LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n");
|
|
trans->gcc.termination = true;
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_REQ:
|
|
/* User aborts group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "User leaves the group channel, that is not yet established.\n");
|
|
/* Change to GROUP CALL PRESENT state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0);
|
|
break;
|
|
case VGCS_GCC_EV_REL_IND:
|
|
/* The MM layer indicates that group channel is gone. */
|
|
LOG_GCC(trans, LOGL_INFO, "Group call notification is gone.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Released\n");
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_TIMEOUT:
|
|
/* Group channel did not become availblet. */
|
|
LOG_GCC(trans, LOGL_INFO, "Timeout waiting for group channel.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Timeout\n");
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u3_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U3);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u3_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_JOIN_GC_REQ:
|
|
/* Join (answer) incoming group call. */
|
|
LOG_GCC(trans, LOGL_INFO, "Join call.\n");
|
|
/* If no channel is available, enter the U2nc state. */
|
|
if (!trans->gcc.ch_desc_present) {
|
|
/* Change state to ACTIVE (no channel). */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Notify answer at VTY. */
|
|
vgcs_vty_notify(trans, "Answer (no channel yet)\n");
|
|
break;
|
|
}
|
|
/* Change to CALL PRESENT state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST, 0, 0);
|
|
/* Send GROUP-REQ to MM layer. */
|
|
vgcs_group_req(trans);
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_REQ:
|
|
/* User rejects group call. */
|
|
LOG_GCC(trans, LOGL_INFO, "User rejects group call.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_REL_IND:
|
|
/* The notified call is gone. */
|
|
LOG_GCC(trans, LOGL_INFO, "Received call from network is gone.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Released\n");
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u4_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U4);
|
|
|
|
/* Start timer */
|
|
osmo_timer_schedule(&fi->timer, T_CONN_REQ, 0);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u4_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
uint8_t *cause = data;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_JOIN_GC_CNF:
|
|
/* The MM layer confirms the group receive mode. (We have a channel.) */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0);
|
|
/* Notify answer at VTY. */
|
|
vgcs_vty_notify(trans, "Answer\n");
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_REQ:
|
|
/* User aborts group channel. */
|
|
LOG_GCC(trans, LOGL_INFO, "User aborts group channel.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_REL_IND:
|
|
/* The notified call is gone. */
|
|
LOG_GCC(trans, LOGL_INFO, "Received call from network is gone.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Released\n");
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_IND:
|
|
/* The notified call is gone. */
|
|
LOG_GCC(trans, LOGL_INFO, "Call rejected.\n");
|
|
/* Change back to U3 state, so that call may be joined later. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Released (cause %d)\n", *cause);
|
|
break;
|
|
case VGCS_GCC_EV_TIMEOUT:
|
|
/* Group channel timed out. */
|
|
LOG_GCC(trans, LOGL_INFO, "Timeout waiting for group channel.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Timeout\n");
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u5_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
|
|
set_state_attributes(trans, -1, -1, 1, 1, OSMO_GSM44068_CSTATE_U5);
|
|
|
|
/* Start timer */
|
|
osmo_timer_schedule(&fi->timer, T_TERM, 0);
|
|
}
|
|
|
|
static void vgcs_gcc_fsm_u5_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct gsm_trans *trans = fi->priv;
|
|
uint8_t *cause = data;
|
|
|
|
switch (event) {
|
|
case VGCS_GCC_EV_DI_TERMINATION:
|
|
/* The network confirm the termination. */
|
|
LOG_GCC(trans, LOGL_INFO, "Termination confirmend by network.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause);
|
|
/* Don't release MM connection, this is done from network side using CHANNEL RELEASE. */
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_DI_TERM_REJECT:
|
|
/* Termination was rejected. */
|
|
LOG_GCC(trans, LOGL_INFO, "Termination rejected (cause %d), releasing MM connection.\n", *cause);
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Termination rejected (cause %d), Call left\n", *cause);
|
|
/* Release MM connection. (Leave call.)
|
|
* We must release here, because we can have a dedicated MM connection or joined a group channel.
|
|
* We cannot go back, because we don't know what state we had before U5 state was entered.
|
|
* Do we really need to go back or just leave the call? */
|
|
vgcs_rel_req(trans);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_ABORT_IND:
|
|
/* The notified call is gone. */
|
|
LOG_GCC(trans, LOGL_INFO, "Received call from network is gone.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Released (cause %d)\n", *cause);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
case VGCS_GCC_EV_TIMEOUT:
|
|
/* Termination timed out. */
|
|
LOG_GCC(trans, LOGL_INFO, "Timeout waiting for termination.\n");
|
|
/* Change to NULL state. */
|
|
osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0);
|
|
/* Notify termination at VTY. */
|
|
vgcs_vty_notify(trans, "Timeout\n");
|
|
/* Release MM connection. (Leave call.) */
|
|
vgcs_rel_req(trans);
|
|
/* Free transaction. */
|
|
trans_free(trans);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
static int vgcs_gcc_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
|
{
|
|
return osmo_fsm_inst_dispatch(fi, VGCS_GCC_EV_TIMEOUT, NULL);
|
|
}
|
|
|
|
static const struct osmo_fsm_state vgcs_gcc_fsm_states[] = {
|
|
[VGCS_GCC_ST_U0_NULL] = {
|
|
.name = "NULL (U0)",
|
|
.in_event_mask = S(VGCS_GCC_EV_SETUP_REQ) |
|
|
S(VGCS_GCC_EV_SETUP_IND),
|
|
.out_state_mask = S(VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING) |
|
|
S(VGCS_GCC_ST_U1_GROUP_CALL_INITIATED) |
|
|
S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT),
|
|
.onenter = vgcs_gcc_fsm_u0_onenter,
|
|
.action = vgcs_gcc_fsm_u0_action,
|
|
},
|
|
[VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING] = {
|
|
.name = "MM CONNECTION PENDING (U0.p)",
|
|
.in_event_mask = S(VGCS_GCC_EV_TERM_REQ) |
|
|
S(VGCS_GCC_EV_MM_EST_REJ) |
|
|
S(VGCS_GCC_EV_MM_EST_CNF) |
|
|
S(VGCS_GCC_EV_TIMEOUT),
|
|
.out_state_mask = S(VGCS_GCC_ST_U0_NULL) |
|
|
S(VGCS_GCC_ST_U1_GROUP_CALL_INITIATED),
|
|
.onenter = vgcs_gcc_fsm_u0p_onenter,
|
|
.action = vgcs_gcc_fsm_u0p_action,
|
|
},
|
|
[VGCS_GCC_ST_U1_GROUP_CALL_INITIATED] = {
|
|
.name = "GROUP CALL INITIATED (U1)",
|
|
.in_event_mask = S(VGCS_GCC_EV_DI_CONNECT) |
|
|
S(VGCS_GCC_EV_DI_TERMINATION) |
|
|
S(VGCS_GCC_EV_TERM_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_IND),
|
|
.out_state_mask = S(VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE) |
|
|
S(VGCS_GCC_ST_U5_TERMINATION_REQUESTED) |
|
|
S(VGCS_GCC_ST_U0_NULL),
|
|
.onenter = vgcs_gcc_fsm_u1_onenter,
|
|
.action = vgcs_gcc_fsm_u1_action,
|
|
},
|
|
[VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE] = {
|
|
.name = "GROUP CALL ACTIVE separate link (U2sl)",
|
|
.in_event_mask = S(VGCS_GCC_EV_DI_TERMINATION) |
|
|
S(VGCS_GCC_EV_TERM_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_IND) |
|
|
S(VGCS_GCC_EV_LISTEN_REQ),
|
|
.out_state_mask = S(VGCS_GCC_ST_U0_NULL) |
|
|
S(VGCS_GCC_ST_U5_TERMINATION_REQUESTED) |
|
|
S(VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE) |
|
|
S(VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE),
|
|
.onenter = vgcs_gcc_fsm_u2sl_onenter,
|
|
.action = vgcs_gcc_fsm_u2sl_action,
|
|
},
|
|
[VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE] = {
|
|
.name = "GROUP CALL ACTIVE wait for receive mode (U2wr)",
|
|
.in_event_mask = S(VGCS_GCC_EV_LISTEN_CNF) |
|
|
S(VGCS_GCC_EV_TERM_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_IND) |
|
|
S(VGCS_GCC_EV_MM_IDLE) |
|
|
S(VGCS_GCC_EV_JOIN_GC_CNF),
|
|
.out_state_mask = S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) |
|
|
S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) |
|
|
S(VGCS_GCC_ST_U0_NULL),
|
|
.onenter = vgcs_gcc_fsm_u2wr_onenter,
|
|
.action = vgcs_gcc_fsm_u2wr_action,
|
|
},
|
|
[VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE] = {
|
|
.name = "GROUP CALL ACTIVE receive mode (U2r or U6 @ BCC)",
|
|
.in_event_mask = S(VGCS_GCC_EV_TALK_REQ) |
|
|
S(VGCS_GCC_EV_TERM_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_IND) |
|
|
S(VGCS_GCC_EV_UPLINK_FREE) |
|
|
S(VGCS_GCC_EV_UPLINK_BUSY),
|
|
.out_state_mask = S(VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE) |
|
|
S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) |
|
|
S(VGCS_GCC_ST_U0_NULL),
|
|
.onenter = vgcs_gcc_fsm_u2r_u6_onenter,
|
|
.action = vgcs_gcc_fsm_u2r_u6_action,
|
|
},
|
|
[VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE] = {
|
|
.name = "GROUP CALL ACTIVE wait for send+receive mode (U2ws)",
|
|
.in_event_mask = S(VGCS_GCC_EV_TALK_CNF) |
|
|
S(VGCS_GCC_EV_TALK_REJ) |
|
|
S(VGCS_GCC_EV_TERM_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_IND),
|
|
.out_state_mask = S(VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE) |
|
|
S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) |
|
|
S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) |
|
|
S(VGCS_GCC_ST_U0_NULL),
|
|
.onenter = vgcs_gcc_fsm_u2ws_onenter,
|
|
.action = vgcs_gcc_fsm_u2ws_action,
|
|
},
|
|
[VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE] = {
|
|
.name = "GROUP CALL ACTIVE send+receive mode (U2sr)",
|
|
.in_event_mask = S(VGCS_GCC_EV_DI_TERMINATION) |
|
|
S(VGCS_GCC_EV_LISTEN_REQ) |
|
|
S(VGCS_GCC_EV_TALK_REJ) |
|
|
S(VGCS_GCC_EV_TERM_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_IND),
|
|
.out_state_mask = S(VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE) |
|
|
S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) |
|
|
S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) |
|
|
S(VGCS_GCC_ST_U0_NULL),
|
|
.onenter = vgcs_gcc_fsm_u2sr_onenter,
|
|
.action = vgcs_gcc_fsm_u2sr_action,
|
|
},
|
|
[VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE] = {
|
|
.name = "GROUP CALL ACTIVE no channel (U2nc)",
|
|
.in_event_mask = S(VGCS_GCC_EV_SETUP_IND) |
|
|
S(VGCS_GCC_EV_TERM_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_REQ) |
|
|
S(VGCS_GCC_EV_REL_IND) |
|
|
S(VGCS_GCC_EV_TIMEOUT),
|
|
.out_state_mask = S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) |
|
|
S(VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST) |
|
|
S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) |
|
|
S(VGCS_GCC_ST_U0_NULL),
|
|
.onenter = vgcs_gcc_fsm_u2nc_onenter,
|
|
.action = vgcs_gcc_fsm_u2nc_action,
|
|
},
|
|
[VGCS_GCC_ST_U3_GROUP_CALL_PRESENT] = {
|
|
.name = "GROUP CALL PRESENT (U3)",
|
|
.in_event_mask = S(VGCS_GCC_EV_JOIN_GC_REQ) |
|
|
S(VGCS_GCC_EV_ABORT_REQ) |
|
|
S(VGCS_GCC_EV_REL_IND),
|
|
.out_state_mask = S(VGCS_GCC_ST_U0_NULL) |
|
|
S(VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST) |
|
|
S(VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE),
|
|
.onenter = vgcs_gcc_fsm_u3_onenter,
|
|
.action = vgcs_gcc_fsm_u3_action,
|
|
},
|
|
[VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST] = {
|
|
.name = "GROUP CALL CONNECTION REQUEST (U4)",
|
|
.in_event_mask = S(VGCS_GCC_EV_JOIN_GC_CNF) |
|
|
S(VGCS_GCC_EV_ABORT_REQ) |
|
|
S(VGCS_GCC_EV_REL_IND) |
|
|
S(VGCS_GCC_EV_ABORT_IND) |
|
|
S(VGCS_GCC_EV_TIMEOUT),
|
|
.out_state_mask = S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) |
|
|
S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) |
|
|
S(VGCS_GCC_ST_U0_NULL),
|
|
.onenter = vgcs_gcc_fsm_u4_onenter,
|
|
.action = vgcs_gcc_fsm_u4_action,
|
|
},
|
|
[VGCS_GCC_ST_U5_TERMINATION_REQUESTED] = {
|
|
.name = "TERMINATION REQUESTED (U5)",
|
|
.in_event_mask = S(VGCS_GCC_EV_DI_TERMINATION) |
|
|
S(VGCS_GCC_EV_DI_TERM_REJECT) |
|
|
S(VGCS_GCC_EV_ABORT_IND) |
|
|
S(VGCS_GCC_EV_TIMEOUT),
|
|
.out_state_mask = S(VGCS_GCC_ST_U0_NULL),
|
|
.onenter = vgcs_gcc_fsm_u5_onenter,
|
|
.action = vgcs_gcc_fsm_u5_action,
|
|
},
|
|
};
|
|
|
|
static struct osmo_fsm vgcs_gcc_fsm = {
|
|
.name = "gcc",
|
|
.states = vgcs_gcc_fsm_states,
|
|
.num_states = ARRAY_SIZE(vgcs_gcc_fsm_states),
|
|
.log_subsys = DGCC,
|
|
.event_names = vgcs_gcc_fsm_event_names,
|
|
.timer_cb = vgcs_gcc_fsm_timer_cb,
|
|
};
|
|
|
|
static struct osmo_fsm vgcs_bcc_fsm = {
|
|
.name = "bcc",
|
|
.states = vgcs_gcc_fsm_states,
|
|
.num_states = ARRAY_SIZE(vgcs_gcc_fsm_states),
|
|
.log_subsys = DBCC,
|
|
.event_names = vgcs_gcc_fsm_event_names,
|
|
.timer_cb = vgcs_gcc_fsm_timer_cb,
|
|
};
|
|
|
|
static __attribute__((constructor)) void on_dso_load(void)
|
|
{
|
|
OSMO_ASSERT(osmo_fsm_register(&vgcs_gcc_fsm) == 0);
|
|
OSMO_ASSERT(osmo_fsm_register(&vgcs_bcc_fsm) == 0);
|
|
}
|
|
|
|
static const char *gsm44068_gcc_state_name(struct osmo_fsm_inst *fi)
|
|
{
|
|
return vgcs_gcc_fsm_states[fi->state].name;
|
|
}
|
|
|
|
/*
|
|
* transaction
|
|
*/
|
|
|
|
/* Create transaction together with state machine and set initail states. */
|
|
static struct gsm_trans *trans_alloc_vgcs(struct osmocom_ms *ms, uint8_t pdisc, uint8_t transaction_id,
|
|
uint32_t callref)
|
|
{
|
|
struct gsm_trans *trans;
|
|
|
|
trans = trans_alloc(ms, pdisc, 0xff, callref);
|
|
if (!trans)
|
|
return NULL;
|
|
trans->gcc.fi = osmo_fsm_inst_alloc((pdisc == GSM48_PDISC_GROUP_CC) ? &vgcs_gcc_fsm : &vgcs_bcc_fsm,
|
|
trans, trans, LOGL_DEBUG, NULL);
|
|
if (!trans->gcc.fi) {
|
|
trans_free(trans);
|
|
return NULL;
|
|
}
|
|
|
|
LOG_GCC(trans, LOGL_DEBUG, "Created transaction for callref %d, pdisc %d, transaction_id 0x%x\n",
|
|
callref, pdisc, transaction_id);
|
|
|
|
set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U0);
|
|
|
|
return trans;
|
|
}
|
|
|
|
/* Call Control Specific transaction release.
|
|
* gets called by trans_free, DO NOT CALL YOURSELF!
|
|
*/
|
|
void _gsm44068_gcc_bcc_trans_free(struct gsm_trans *trans)
|
|
{
|
|
if (!trans->gcc.fi)
|
|
return;
|
|
|
|
/* Free state machine. */
|
|
osmo_fsm_inst_free(trans->gcc.fi);
|
|
}
|
|
|
|
/* Find ongoing call. (The call must not be present.) */
|
|
struct gsm_trans *trans_find_ongoing_gcc_bcc(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_trans *trans;
|
|
|
|
llist_for_each_entry(trans, &ms->trans_list, entry) {
|
|
if (trans->protocol != GSM48_PDISC_GROUP_CC && trans->protocol != GSM48_PDISC_BCAST_CC)
|
|
continue;
|
|
if (trans->gcc.fi->state != VGCS_GCC_ST_U3_GROUP_CALL_PRESENT)
|
|
return trans;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* messages from upper/lower layers
|
|
*/
|
|
|
|
static int gsm44068_gcc_data_ind(struct gsm_trans *trans, struct msgb *msg, uint8_t transaction_id)
|
|
{
|
|
struct osmocom_ms *ms = trans->ms;
|
|
struct gsm48_hdr *gh = msgb_l3(msg);
|
|
int msg_type = gh->msg_type & 0xbf;
|
|
int rc = 0;
|
|
uint8_t cause = 0;
|
|
uint8_t d_att, u_att, comm, orig;
|
|
|
|
/* pull the MMCC header */
|
|
msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr));
|
|
|
|
/* Use transaction ID from message. If we join a group call, we don't have it until now. */
|
|
trans->transaction_id = transaction_id;
|
|
|
|
LOG_GCC(trans, LOGL_INFO, "(ms %s) Received '%s' in state %s\n", ms->name, gsm44068_gcc_msg_name(msg_type),
|
|
gsm44068_gcc_state_name(trans->gcc.fi));
|
|
|
|
switch (msg_type) {
|
|
case OSMO_GSM44068_MSGT_CONNECT:
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_DI_CONNECT, NULL);
|
|
break;
|
|
case OSMO_GSM44068_MSGT_TERMINATION:
|
|
rc = gsm44068_rx_termination(trans, msg, &cause, NULL, NULL);
|
|
if (rc < 0)
|
|
break;
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_DI_TERMINATION, &cause);
|
|
break;
|
|
case OSMO_GSM44068_MSGT_TERMINATION_REJECT:
|
|
rc = gsm44068_rx_termination(trans, msg, &cause, NULL, NULL);
|
|
if (rc < 0)
|
|
break;
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_DI_TERM_REJECT, &cause);
|
|
break;
|
|
case OSMO_GSM44068_MSGT_GET_STATUS:
|
|
/* Note: The message via UNIT DATA is not supported. */
|
|
gsm44068_tx_status(trans, OSMO_GSM44068_CAUSE_RESPONSE_TO_GET_STATUS, NULL, 0,
|
|
true, trans->gcc.call_state,
|
|
true, trans->gcc.d_att, trans->gcc.u_att, trans->gcc.comm, trans->gcc.orig);
|
|
break;
|
|
case OSMO_GSM44068_MSGT_SET_PARAMETER:
|
|
rc = gsm44068_rx_set_parameter(trans, msg, &d_att, &u_att, &comm, &orig);
|
|
if (rc >= 0)
|
|
set_state_attributes(trans, d_att, u_att, comm, orig, -1);
|
|
break;
|
|
default:
|
|
LOG_GCC(trans, LOGL_NOTICE, "Message '%s' not supported.\n", gsm44068_gcc_msg_name(msg_type));
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* receive message from MM layer */
|
|
int gsm44068_rcv_gcc_bcc(struct osmocom_ms *ms, struct msgb *msg)
|
|
{
|
|
struct gsm_settings *set = &ms->settings;
|
|
struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
|
|
int msg_type = mmh->msg_type;
|
|
uint8_t pdisc;
|
|
struct gsm_trans *trans;
|
|
int rc = 0;
|
|
|
|
/* Check for message class and get protocol type. */
|
|
switch ((msg_type & GSM48_MMXX_MASK)) {
|
|
case GSM48_MMGCC_CLASS:
|
|
pdisc = GSM48_PDISC_GROUP_CC;
|
|
if (!set->vgcs) {
|
|
LOGP(DGCC, LOGL_ERROR, "Ignoring message '%s', because VGCS is not supported!\n",
|
|
get_mmxx_name(msg_type));
|
|
return -ENOTSUP;
|
|
}
|
|
break;
|
|
case GSM48_MMBCC_CLASS:
|
|
pdisc = GSM48_PDISC_BCAST_CC;
|
|
if (!set->vbs) {
|
|
LOGP(DGCC, LOGL_ERROR, "Ignoring message '%s', because VBS is not supported!\n",
|
|
get_mmxx_name(msg_type));
|
|
return -ENOTSUP;
|
|
}
|
|
break;
|
|
default:
|
|
LOGP(DGCC, LOGL_ERROR, "Message class not allowed, please fix!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
trans = trans_find_by_callref(ms, pdisc, mmh->ref);
|
|
if (!trans) {
|
|
LOG_GCC_PR(pdisc, mmh->ref, LOGL_INFO, "(ms %s) Received '%s' without transaction.\n",
|
|
ms->name, get_mmxx_name(msg_type));
|
|
|
|
if (msg_type == GSM48_MMGCC_REL_IND || msg_type == GSM48_MMBCC_REL_IND) {
|
|
/* If we got the MMxx-EST-REJ after we aborted the call, we ignore. */
|
|
return 0;
|
|
}
|
|
if (msg_type == GSM48_MMGCC_EST_CNF || msg_type == GSM48_MMBCC_EST_CNF ||
|
|
msg_type == GSM48_MMGCC_GROUP_CNF || msg_type == GSM48_MMBCC_GROUP_CNF) {
|
|
struct msgb *nmsg;
|
|
LOG_GCC_PR(pdisc, mmh->ref, LOGL_ERROR, "Received confirm for GCC/BCC call, "
|
|
"but there is no transaction, releasing.\n");
|
|
/* If we got the MMxx-EST-CNF after we aborted the call, we release. */
|
|
msg_type = (pdisc == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_REL_REQ : GSM48_MMBCC_REL_REQ;
|
|
nmsg = gsm48_mmxx_msgb_alloc(msg_type, mmh->ref, mmh->transaction_id, 0);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
LOG_GCC_PR(pdisc, mmh->ref, LOGL_INFO, "Sending %s\n", get_mmxx_name(msg_type));
|
|
gsm48_mmxx_downmsg(ms, nmsg);
|
|
return 0;
|
|
|
|
} else if (msg_type == GSM48_MMGCC_NOTIF_IND || msg_type == GSM48_MMBCC_NOTIF_IND) {
|
|
/* Notification gone but no transaction. */
|
|
if (mmh->notify != MMXX_NOTIFY_SETUP)
|
|
return 0;
|
|
/* Incoming notification, creation transaction. */
|
|
trans = trans_alloc_vgcs(ms, pdisc, 0xff, mmh->ref);
|
|
if (!trans)
|
|
return -ENOMEM;
|
|
} else {
|
|
LOG_GCC_PR(pdisc, mmh->ref, LOGL_ERROR, "Received GCC/BCC message for unknown transaction.\n");
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
LOG_GCC(trans, LOGL_INFO, "(ms %s) Received '%s' in state %s\n", ms->name, get_mmxx_name(msg_type),
|
|
gsm44068_gcc_state_name(trans->gcc.fi));
|
|
|
|
switch (msg_type) {
|
|
case GSM48_MMGCC_EST_CNF:
|
|
case GSM48_MMBCC_EST_CNF:
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_MM_EST_CNF, NULL);
|
|
break;
|
|
case GSM48_MMGCC_ERR_IND:
|
|
case GSM48_MMBCC_ERR_IND:
|
|
case GSM48_MMGCC_REL_IND:
|
|
case GSM48_MMBCC_REL_IND:
|
|
/* Ignore release confirm or release collision. */
|
|
if (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT)
|
|
break;
|
|
/* If MM fails or is rejected during U0.p state, this is a MM-EST-REJ. */
|
|
if (trans->gcc.fi->state == VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING)
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_MM_EST_REJ, &mmh->cause);
|
|
else
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_ABORT_IND, &mmh->cause);
|
|
break;
|
|
case GSM48_MMGCC_DATA_IND:
|
|
case GSM48_MMBCC_DATA_IND:
|
|
rc = gsm44068_gcc_data_ind(trans, msg, mmh->transaction_id);
|
|
break;
|
|
case GSM48_MMGCC_NOTIF_IND:
|
|
case GSM48_MMBCC_NOTIF_IND:
|
|
switch (mmh->notify) {
|
|
case MMXX_NOTIFY_SETUP:
|
|
/* Store channel description. */
|
|
trans->gcc.ch_desc_present = mmh->ch_desc_present;
|
|
memcpy(&trans->gcc.ch_desc, &mmh->ch_desc, sizeof(trans->gcc.ch_desc));
|
|
if (trans->gcc.fi->state == VGCS_GCC_ST_U0_NULL) {
|
|
/* In null state this indication is a SETUP-IND. */
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_SETUP_IND, NULL);
|
|
} else if (trans->gcc.fi->state == VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE && mmh->ch_desc_present) {
|
|
/* In U2nc state with a channel info, is a SETUP-IND (with updated mode). */
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_SETUP_IND, NULL);
|
|
}
|
|
break;
|
|
case MMXX_NOTIFY_RELEASE:
|
|
if (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT ||
|
|
trans->gcc.fi->state == VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST ||
|
|
trans->gcc.fi->state == VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE) {
|
|
/* If notification is gone, release pending received call. */
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_REL_IND, NULL);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case GSM48_MMGCC_GROUP_CNF:
|
|
case GSM48_MMBCC_GROUP_CNF:
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_JOIN_GC_CNF, NULL);
|
|
break;
|
|
case GSM48_MMGCC_UPLINK_CNF:
|
|
case GSM48_MMBCC_UPLINK_CNF:
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_CNF, NULL);
|
|
break;
|
|
case GSM48_MMGCC_UPLINK_REL_IND:
|
|
case GSM48_MMBCC_UPLINK_REL_IND:
|
|
if (trans->gcc.fi->state == VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE ||
|
|
trans->gcc.fi->state == VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE) {
|
|
/* We are waiting to send, so this is a reject. */
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_REJ, &mmh->cause);
|
|
} else if (trans->gcc.fi->state == VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE) {
|
|
/* We are waiting to receive, so this is a confirm. */
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_LISTEN_CNF, &mmh->cause);
|
|
}
|
|
break;
|
|
case GSM48_MMGCC_UPLINK_FREE_IND:
|
|
case GSM48_MMBCC_UPLINK_FREE_IND:
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_UPLINK_FREE, NULL);
|
|
break;
|
|
case GSM48_MMGCC_UPLINK_BUSY_IND:
|
|
case GSM48_MMBCC_UPLINK_BUSY_IND:
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_UPLINK_BUSY, NULL);
|
|
break;
|
|
default:
|
|
LOG_GCC(trans, LOGL_NOTICE, "Message '%s' unhandled.\n", get_mmxx_name(msg_type));
|
|
rc = -ENOTSUP;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Special function to receive IDLE state of MM layer. This is required to request a channel after dedicated mode. */
|
|
int gsm44068_rcv_mm_idle(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_trans *trans;
|
|
|
|
trans = trans_find_ongoing_gcc_bcc(ms);
|
|
if (!trans)
|
|
return -ENOENT;
|
|
if (trans->gcc.fi->state == VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE && trans->gcc.receive_after_sl) {
|
|
/* Finally the MM layer is IDLE. */
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_MM_IDLE, NULL);
|
|
return 0;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Setup or join a VGC/VBC. */
|
|
int gcc_bcc_call(struct osmocom_ms *ms, uint8_t protocol, const char *number)
|
|
{
|
|
uint32_t callref = strtoul(number, NULL, 0);
|
|
struct gsm_trans *trans;
|
|
int i;
|
|
|
|
if (strlen(number) > 8) {
|
|
inval:
|
|
l23_vty_ms_notify(ms, NULL);
|
|
l23_vty_ms_notify(ms, "Invalid group '%s'\n", number);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < strlen(number); i++) {
|
|
if (number[i] < '0' || number[i] > '9')
|
|
goto inval;
|
|
}
|
|
|
|
if (callref == 0)
|
|
goto inval;
|
|
|
|
/* Reject if there is already an ongoing call. */
|
|
trans = trans_find_ongoing_gcc_bcc(ms);
|
|
if (trans) {
|
|
l23_vty_ms_notify(ms, NULL);
|
|
l23_vty_ms_notify(ms, "Cannot call/join, we are busy\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Find call that matches the given protocol+cellref. */
|
|
trans = trans_find_by_callref(ms, protocol, callref);
|
|
if (trans) {
|
|
/* Answer incoming call. */
|
|
if (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) {
|
|
osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_JOIN_GC_REQ, NULL);
|
|
return 0;
|
|
}
|
|
l23_vty_ms_notify(ms, NULL);
|
|
l23_vty_ms_notify(ms, "Call already established\n");
|
|
return -EEXIST;
|
|
}
|
|
|
|
/* Create new transaction. ORIG will be set when entering U2sl state. */
|
|
trans = trans_alloc_vgcs(ms, protocol, 0xff, callref);
|
|
if (!trans)
|
|
return -ENOMEM;
|
|
|
|
/* Setup new call. */
|
|
return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_SETUP_REQ, NULL);
|
|
}
|
|
|
|
/* Leave a VGC. (If we are the originator, we do not terminate.) */
|
|
int gcc_leave(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_trans *trans;
|
|
|
|
/* Reject if there is no call. */
|
|
trans = trans_find_ongoing_gcc_bcc(ms);
|
|
if (!trans) {
|
|
l23_vty_ms_notify(ms, NULL);
|
|
l23_vty_ms_notify(ms, "No Call\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (trans->gcc.fi->state == VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING ||
|
|
trans->gcc.fi->state == VGCS_GCC_ST_U1_GROUP_CALL_INITIATED ||
|
|
trans->gcc.fi->state == VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE ||
|
|
trans->gcc.fi->state == VGCS_GCC_ST_U5_TERMINATION_REQUESTED) {
|
|
LOG_GCC(trans, LOGL_NOTICE, "Cannot leave (abort), in this state.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Send ABORT-REQ to leave the call. */
|
|
return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_ABORT_REQ, NULL);
|
|
}
|
|
|
|
/* Hangup VGC/VBC. (If we are the originator, we terminate the call.) */
|
|
int gcc_bcc_hangup(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_trans *trans;
|
|
|
|
/* Reject if there is no call. */
|
|
trans = trans_find_ongoing_gcc_bcc(ms);
|
|
if (!trans) {
|
|
l23_vty_ms_notify(ms, NULL);
|
|
l23_vty_ms_notify(ms, "No Call\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (trans->gcc.orig) {
|
|
if (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT ||
|
|
trans->gcc.fi->state == VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST ||
|
|
trans->gcc.fi->state == VGCS_GCC_ST_U5_TERMINATION_REQUESTED) {
|
|
LOG_GCC(trans, LOGL_NOTICE, "Cannot hangup (terminate) in this state.\n");
|
|
return -EINVAL;
|
|
}
|
|
/* Send TERM-REQ to leave the call. */
|
|
return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TERM_REQ, NULL);
|
|
}
|
|
|
|
if (trans->gcc.fi->state == VGCS_GCC_ST_U5_TERMINATION_REQUESTED) {
|
|
LOG_GCC(trans, LOGL_NOTICE, "Cannot hangup (abort) in this state.\n");
|
|
return -EINVAL;
|
|
}
|
|
/* Send ABORT-REQ to leave the call. */
|
|
return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_ABORT_REQ, NULL);
|
|
}
|
|
|
|
int gcc_talk(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_trans *trans;
|
|
|
|
/* Reject if there is no call. */
|
|
trans = trans_find_ongoing_gcc_bcc(ms);
|
|
if (!trans) {
|
|
l23_vty_ms_notify(ms, NULL);
|
|
l23_vty_ms_notify(ms, "No Call\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (trans->protocol != GSM48_PDISC_GROUP_CC) {
|
|
LOG_GCC(trans, LOGL_NOTICE, "Cannot talk, the ongoing call is not a group call.\n");
|
|
vgcs_vty_notify(trans, "Not a group call\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (trans->gcc.fi->state != VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) {
|
|
LOG_GCC(trans, LOGL_NOTICE, "Cannot talk, we are not listening.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Send TALK-REQ to become talker. */
|
|
return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_REQ, NULL);
|
|
}
|
|
|
|
int gcc_listen(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_trans *trans;
|
|
|
|
/* Reject if there is no call. */
|
|
trans = trans_find_ongoing_gcc_bcc(ms);
|
|
if (!trans) {
|
|
l23_vty_ms_notify(ms, NULL);
|
|
l23_vty_ms_notify(ms, "No Call\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (trans->protocol != GSM48_PDISC_GROUP_CC) {
|
|
LOG_GCC(trans, LOGL_NOTICE, "Cannot become listener, the ongoing call is not a group call.\n");
|
|
vgcs_vty_notify(trans, "Not a group call\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (trans->gcc.fi->state != VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE &&
|
|
trans->gcc.fi->state != VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE) {
|
|
LOG_GCC(trans, LOGL_NOTICE, "Cannot listen, we are not talking.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Send LISTEN-REQ to become listener. */
|
|
return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_LISTEN_REQ, NULL);
|
|
}
|
|
|
|
int gsm44068_dump_calls(struct osmocom_ms *ms, void (*print)(void *, const char *, ...), void *priv)
|
|
{
|
|
struct gsm_trans *trans;
|
|
|
|
llist_for_each_entry(trans, &ms->trans_list, entry) {
|
|
if (trans->protocol != GSM48_PDISC_GROUP_CC && trans->protocol != GSM48_PDISC_BCAST_CC)
|
|
continue;
|
|
print(priv, "Voice %s call '%d' is %s.\n",
|
|
(trans->protocol == GSM48_PDISC_GROUP_CC) ? "group" : "broadcast", trans->callref,
|
|
(trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) ? "incoming" : "active");
|
|
}
|
|
|
|
return 0;
|
|
}
|