osmo-bsc/src/osmo-bsc/vgcs_fsm.c

1319 lines
45 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/>.
*/
/* The process consists of two state machnes:
*
* The VGCS call state machine handles the voice group/broadcast call control.
* There is one instance for every call. It controls the uplink states of the
* call. They will be reported to the MSC or can be changed by the MSC.
* One SCCP connection for is associated with the state machine. This is used
* to talk to the MSC about state changes.
*
* The VGCS channel state machine handles the channel states in each cell.
* There is one instance for every cell and every call. The instances are
* linked to the call state process. It controls the uplink states of the
* channel. They will be reported to the call state machine or can be changed
* by the call state machine.
* One SCCP connection for every cell is associated with the state machine.
* It is used to perform VGCS channel assignment.
*
*/
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
#include <osmocom/bsc/osmo_bsc.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/vgcs_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_select.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/assignment_fsm.h>
#include <osmocom/bsc/gsm_08_08.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
#include <osmocom/bsc/bts_trx.h>
#include <osmocom/bsc/bts.h>
#include <osmocom/bsc/system_information.h>
#define S(x) (1 << (x))
#define LOG_CALL(conn, level, fmt, args...) \
LOGP(DASCI, level, \
(conn->vgcs_call.sf == GSM0808_SF_VGCS) ? ("VGCS callref %s: " fmt) : ("VBS callref %s: " fmt), \
gsm44068_group_id_string(conn->vgcs_call.call_ref), ##args)
#define LOG_CHAN(conn, level, fmt, args...) \
LOGP(DASCI, level, \
(conn->vgcs_chan.sf == GSM0808_SF_VGCS) ? ("VGCS callref %s, cell %s: " fmt) \
: ("VBS callref %s, cell %s: " fmt), \
gsm44068_group_id_string(conn->vgcs_chan.call_ref), conn->vgcs_chan.ci_str, ##args)
const char *gsm44068_group_id_string(uint32_t callref)
{
static char string[9];
snprintf(string, sizeof(string), "%08u", callref);
return string;
}
static struct osmo_fsm vgcs_call_fsm;
static struct osmo_fsm vgcs_chan_fsm;
static __attribute__((constructor)) void vgcs_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&vgcs_call_fsm) == 0);
OSMO_ASSERT(osmo_fsm_register(&vgcs_chan_fsm) == 0);
}
static const struct value_string vgcs_fsm_event_names[] = {
OSMO_VALUE_STRING(VGCS_EV_SETUP),
OSMO_VALUE_STRING(VGCS_EV_ASSIGN_REQ),
OSMO_VALUE_STRING(VGCS_EV_TALKER_DET),
OSMO_VALUE_STRING(VGCS_EV_LISTENER_DET),
OSMO_VALUE_STRING(VGCS_EV_MSC_ACK),
OSMO_VALUE_STRING(VGCS_EV_MSC_REJECT),
OSMO_VALUE_STRING(VGCS_EV_MSC_SEIZE),
OSMO_VALUE_STRING(VGCS_EV_MSC_RELEASE),
OSMO_VALUE_STRING(VGCS_EV_MSC_DTAP),
OSMO_VALUE_STRING(VGCS_EV_LCHAN_ACTIVE),
OSMO_VALUE_STRING(VGCS_EV_LCHAN_ERROR),
OSMO_VALUE_STRING(VGCS_EV_MGW_OK),
OSMO_VALUE_STRING(VGCS_EV_MGW_FAIL),
OSMO_VALUE_STRING(VGCS_EV_TALKER_EST),
OSMO_VALUE_STRING(VGCS_EV_TALKER_DATA),
OSMO_VALUE_STRING(VGCS_EV_TALKER_REL),
OSMO_VALUE_STRING(VGCS_EV_TALKER_FAIL),
OSMO_VALUE_STRING(VGCS_EV_BLOCK),
OSMO_VALUE_STRING(VGCS_EV_REJECT),
OSMO_VALUE_STRING(VGCS_EV_UNBLOCK),
OSMO_VALUE_STRING(VGCS_EV_CLEANUP),
OSMO_VALUE_STRING(VGCS_EV_CALLING_ASSIGNED),
{ }
};
static struct gsm_subscriber_connection *find_calling_subscr_conn(struct gsm_subscriber_connection *conn)
{
struct gsm_subscriber_connection *c;
llist_for_each_entry(c, &conn->network->subscr_conns, entry) {
if (!c->assignment.fi)
continue;
if (c->assignment.req.target_lchan != conn->lchan)
continue;
return c;
}
return NULL;
}
/*
* VGCS call FSM
*/
/* Add/update SI10. It must be called whenever a channel is activated or failed. */
static void si10_update(struct gsm_subscriber_connection *conn)
{
struct gsm_subscriber_connection *c;
uint8_t si10[SI10_LENGTH];
int rc;
/* Skip SI10 update, if not all channels have been activated or failed. */
llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) {
if (c->vgcs_chan.fi->state == VGCS_CHAN_ST_WAIT_EST) {
LOG_CALL(conn, LOGL_DEBUG, "There is a channel, not yet active. No SI10 update now.\n");
return;
}
}
LOG_CALL(conn, LOGL_DEBUG, "New channel(s) added, updating SI10 for all channels.\n");
/* Go through all channels. */
llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) {
/* Skip all channels that failed to activate or have not been aktivated yet.
* There shouldn't be any channel in that state now. */
if (!c->lchan)
continue;
/* Encode SI 10 for this channel. Skip, if it fails. */
rc = gsm_generate_si10((struct gsm48_system_information_type_10 *)si10, sizeof(si10), c);
if (rc < 0)
continue;
/* Add SI 10 to SACCH of this channel c. */
rsl_sacch_info_modify(c->lchan, RSL_SYSTEM_INFO_10, si10, sizeof(si10));
}
}
static void vgcs_call_detach_and_destroy(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct gsm_subscriber_connection *conn = fi->priv, *c;
struct msgb *msg;
/* Flush message queue. */
while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue)))
msgb_free(msg);
/* Detach all cell instances. */
while (!llist_empty(&conn->vgcs_call.chan_list)) {
c = llist_entry(conn->vgcs_call.chan_list.next, struct gsm_subscriber_connection, vgcs_chan.list);
c->vgcs_chan.call = NULL;
llist_del(&c->vgcs_chan.list);
}
/* No Talker. */
conn->vgcs_call.talker = NULL;
/* Remove pointer of FSM. */
conn->vgcs_call.fi = NULL;
}
static void vgcs_call_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
switch (event) {
case VGCS_EV_SETUP:
LOG_CALL(conn, LOGL_DEBUG, "VGCS/VBS SETUP from MSC.\n");
/* MSC sends VGCS/VBS SETUP for a new call. */
osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0);
/* Remove unsupported features. */
conn->vgcs_call.ff.tp_ind = 0;
conn->vgcs_call.ff.as_ind_circuit = 0;
conn->vgcs_call.ff.as_ind_link = 0;
conn->vgcs_call.ff.bss_res = 0;
conn->vgcs_call.ff.tcp = 0;
/* Acknowlege the call. */
bsc_tx_setup_ack(conn, &conn->vgcs_call.ff);
break;
default:
OSMO_ASSERT(false);
}
}
static void vgcs_call_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv, *c;
struct handover_rr_detect_data *d = data;
struct msgb *msg;
switch (event) {
case VGCS_EV_TALKER_DET:
LOG_CALL(conn, LOGL_DEBUG, "Talker detected.\n");
/* Talker detected on a channel, call becomes busy. */
osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BUSY, 0, 0);
conn->vgcs_call.talker = d->msg->lchan->conn;
/* Reset pending states. */
while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue)))
msgb_free(msg);
conn->vgcs_call.msc_ack = false;
conn->vgcs_call.talker_rel = false;
/* Report busy uplink to the MSC. */
bsc_tx_uplink_req(conn);
/* Block all other channels. */
llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) {
if (c == conn->vgcs_call.talker)
continue;
osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_BLOCK, NULL);
}
break;
case VGCS_EV_LISTENER_DET:
LOG_CALL(conn, LOGL_DEBUG, "Listener detected.\n");
// Listener detection not supported.
break;
case VGCS_EV_MSC_SEIZE:
LOG_CALL(conn, LOGL_DEBUG, "MSC seizes all channels.\n");
/* MSC seizes call (talker on a different BSS), call becomes blocked. */
osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BLOCKED, 0, 0);
/* Block all channels. */
llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list)
osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_BLOCK, NULL);
break;
case VGCS_EV_MSC_RELEASE:
/* Ignore, because there is no blocked channel in this state. */
break;
case VGCS_EV_MSC_REJECT:
LOG_CALL(conn, LOGL_DEBUG, "MSC rejects talker on uplink.\n");
/* Race condition: Talker released before the MSC rejects the talker. Ignore! */
break;
case VGCS_EV_CLEANUP:
LOG_CALL(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
osmo_fsm_inst_term(conn->vgcs_call.fi, 0, NULL);
break;
default:
OSMO_ASSERT(false);
}
}
/* Get L3 info from message, if exists. Return the length or otherwise return 0. */
int l3_data_from_msg(struct msgb *msg, uint8_t **l3_info)
{
struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
/* No space for L3 info */
if (msgb_l2len(msg) < sizeof(*rllh) + 3 || rllh->data[0] != RSL_IE_L3_INFO)
return 0;
*l3_info = msg->l3h = &rllh->data[3];
return msgb_l3len(msg);
}
static void vgcs_call_fsm_busy(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv, *c;
struct msgb *msg = data;
uint8_t cause = (data) ? *(uint8_t *)data : 0;
uint8_t *l3_info;
int l3_len;
int rc;
switch (event) {
case VGCS_EV_TALKER_EST:
LOG_CALL(conn, LOGL_DEBUG, "Talker established uplink.\n");
/* Talker established L2 connection. Sent L3 info to MSC, if MSC already acked, otherwise enqueue. */
if (conn->vgcs_call.msc_ack) {
LOG_CALL(conn, LOGL_DEBUG, "Sending establishment messages to MSC.\n");
l3_len = l3_data_from_msg(msg, &l3_info);
if (conn->vgcs_call.talker)
bsc_tx_uplink_req_conf(conn, &conn->vgcs_call.talker->vgcs_chan.ci, l3_info, l3_len);
else
LOG_CALL(conn, LOGL_ERROR, "Talker establishes, but talker not set, please fix!\n");
} else {
LOG_CALL(conn, LOGL_DEBUG, "No uplink request ack from MSC yet, queue message.\n");
msg = msgb_copy(msg, "Queued Talker establishment");
if (msg)
msgb_enqueue(&conn->vgcs_call.l3_queue, msg);
}
break;
case VGCS_EV_TALKER_DATA:
LOG_CALL(conn, LOGL_DEBUG, "Talker sent data on uplink.\n");
/* Talker sends data. Sent L3 info to MSC, if MSC already acked, otherwise enqueue. */
if (conn->vgcs_call.msc_ack) {
LOG_CALL(conn, LOGL_DEBUG, "Sending data messages to MSC.\n");
bsc_dtap(conn, 0, msg);
} else {
LOG_CALL(conn, LOGL_DEBUG, "No uplink request ack from MSC yet, queue message.\n");
msg = msgb_copy(msg, "Queued DTAP");
if (msg)
msgb_enqueue(&conn->vgcs_call.l3_queue, msg);
}
break;
case VGCS_EV_MSC_DTAP:
LOG_CALL(conn, LOGL_DEBUG, "MSC sends DTAP message to talker.\n");
if (!conn->vgcs_call.talker) {
msgb_free(data);
break;
}
rc = osmo_fsm_inst_dispatch(conn->vgcs_call.talker->vgcs_chan.fi, VGCS_EV_MSC_DTAP, data);
if (rc < 0)
msgb_free(data);
break;
case VGCS_EV_TALKER_REL:
LOG_CALL(conn, LOGL_DEBUG, "Talker released on uplink.\n");
if (!conn->vgcs_call.msc_ack) {
LOG_CALL(conn, LOGL_DEBUG, "Talker released before MSC acknowleded or rejected.\n");
conn->vgcs_call.talker_rel = true;
conn->vgcs_call.talker_cause = cause;
break;
}
talker_released:
/* Talker released channel, call becomes idle. */
osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0);
conn->vgcs_call.talker = NULL;
/* Report free uplink to the MSC. */
bsc_tx_uplink_release_ind(conn, cause);
/* Unblock all other channels. */
llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) {
if (c == conn->vgcs_call.talker)
continue;
osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_UNBLOCK, NULL);
}
break;
case VGCS_EV_MSC_SEIZE:
LOG_CALL(conn, LOGL_DEBUG, "MSC seizes all channels. (channels are blocked)\n");
/* Race condition: MSC seizes call (talker on a different BSS), call becomes blocked. */
osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BLOCKED, 0, 0);
/* Reject talker. (Forward to chan FSM.) */
if (conn->vgcs_call.talker) {
osmo_fsm_inst_dispatch(conn->vgcs_call.talker->vgcs_chan.fi, VGCS_EV_REJECT, NULL);
conn->vgcs_call.talker = NULL;
}
/* Block all channels. */
llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list)
osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_BLOCK, NULL);
break;
case VGCS_EV_MSC_ACK:
LOG_CALL(conn, LOGL_DEBUG, "MSC acks talker on uplink.\n");
/* MSC acknowledges uplink. Send L3 info to MSC, if talker already established. */
conn->vgcs_call.msc_ack = true;
/* Send establish message via UPLINK REQUEST CONFIRM, if already received. */
msg = msgb_dequeue(&conn->vgcs_call.l3_queue);
if (msg) {
LOG_CALL(conn, LOGL_DEBUG, "Sending queued establishment messages to MSC.\n");
l3_len = l3_data_from_msg(msg, &l3_info);
if (conn->vgcs_call.talker)
bsc_tx_uplink_req_conf(conn, &conn->vgcs_call.talker->vgcs_chan.ci, l3_info, l3_len);
else
LOG_CALL(conn, LOGL_ERROR, "MSC acks taker, but talker not set, please fix!\n");
msgb_free(msg);
}
/* Send data messages via UPLINK APPLICATION DATA, if already received. */
while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue))) {
LOG_CALL(conn, LOGL_DEBUG, "Sending queued DTAP messages to MSC.\n");
bsc_dtap(conn, 0, msg);
msgb_free(msg);
}
/* If there is a pending talker release. */
if (conn->vgcs_call.talker_rel) {
LOG_CALL(conn, LOGL_DEBUG, "Sending queued talker release messages to MSC.\n");
cause = conn->vgcs_call.talker_cause;
goto talker_released;
}
break;
case VGCS_EV_MSC_REJECT:
LOG_CALL(conn, LOGL_DEBUG, "MSC rejects talker on uplink.\n");
/* MSC rejects talker, call becomes idle. */
osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0);
/* Reject talker. (Forward to chan FSM.) */
if (conn->vgcs_call.talker)
osmo_fsm_inst_dispatch(conn->vgcs_call.talker->vgcs_chan.fi, VGCS_EV_REJECT, NULL);
else
LOG_CALL(conn, LOGL_ERROR, "MSC rejects, but talker not set, please fix!\n");
conn->vgcs_call.talker = NULL;
/* Unblock all other channels. */
llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) {
if (c == conn->vgcs_call.talker)
continue;
osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_UNBLOCK, NULL);
}
break;
case VGCS_EV_CLEANUP:
LOG_CALL(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
osmo_fsm_inst_term(conn->vgcs_call.fi, 0, NULL);
break;
default:
OSMO_ASSERT(false);
}
}
static void vgcs_call_fsm_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv, *c;
struct msgb *msg;
switch (event) {
case VGCS_EV_CALLING_ASSIGNED:
LOG_CALL(conn, LOGL_DEBUG, "Calling subscriber assigned and now on uplink.\n");
/* Talker detected on a channel, call becomes busy. */
osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BUSY, 0, 0);
conn->vgcs_call.talker = data;
/* Reset pending states, but imply that MSC acked this uplink session. */
while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue)))
msgb_free(msg);
conn->vgcs_call.msc_ack = true;
break;
case VGCS_EV_TALKER_REL:
LOG_CALL(conn, LOGL_DEBUG, "Talker released on uplink.\n");
/* Talker release was complete. Ignore. */
break;
case VGCS_EV_MSC_RELEASE:
LOG_CALL(conn, LOGL_DEBUG, "MSC releases all channels. (channels are free)\n");
/* MSC releases call (no mor talker on a different BSS), call becomes idle */
osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0);
/* Unblock all channels. */
llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list)
osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_UNBLOCK, NULL);
break;
case VGCS_EV_CLEANUP:
LOG_CALL(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
osmo_fsm_inst_term(conn->vgcs_call.fi, 0, NULL);
break;
default:
OSMO_ASSERT(false);
}
}
static const struct osmo_fsm_state vgcs_call_fsm_states[] = {
[VGCS_CALL_ST_NULL] = {
.name = "NULL",
.in_event_mask = S(VGCS_EV_SETUP),
.out_state_mask = S(VGCS_CALL_ST_IDLE),
.action = vgcs_call_fsm_null,
},
[VGCS_CALL_ST_IDLE] = {
.name = "IDLE",
.in_event_mask = S(VGCS_EV_TALKER_DET) |
S(VGCS_EV_MSC_SEIZE) |
S(VGCS_EV_MSC_RELEASE) |
S(VGCS_EV_MSC_REJECT) |
S(VGCS_EV_CLEANUP),
.out_state_mask = S(VGCS_CALL_ST_BUSY) |
S(VGCS_CALL_ST_BLOCKED) |
S(VGCS_CALL_ST_NULL),
.action = vgcs_call_fsm_idle,
},
[VGCS_CALL_ST_BUSY] = {
.name = "BUSY",
.in_event_mask = S(VGCS_EV_TALKER_EST) |
S(VGCS_EV_TALKER_DATA) |
S(VGCS_EV_MSC_DTAP) |
S(VGCS_EV_TALKER_REL) |
S(VGCS_EV_MSC_SEIZE) |
S(VGCS_EV_MSC_ACK) |
S(VGCS_EV_MSC_REJECT) |
S(VGCS_EV_CLEANUP),
.out_state_mask = S(VGCS_CALL_ST_IDLE) |
S(VGCS_CALL_ST_BLOCKED) |
S(VGCS_CALL_ST_NULL),
.action = vgcs_call_fsm_busy,
},
[VGCS_CALL_ST_BLOCKED] = {
.name = "BLOCKED",
.in_event_mask = S(VGCS_EV_CALLING_ASSIGNED) |
S(VGCS_EV_TALKER_REL) |
S(VGCS_EV_MSC_RELEASE) |
S(VGCS_EV_CLEANUP),
.out_state_mask = S(VGCS_CALL_ST_IDLE) |
S(VGCS_CALL_ST_BUSY) |
S(VGCS_CALL_ST_NULL),
.action = vgcs_call_fsm_blocked,
},
};
static struct osmo_fsm vgcs_call_fsm = {
.name = "vgcs_call",
.states = vgcs_call_fsm_states,
.num_states = ARRAY_SIZE(vgcs_call_fsm_states),
.log_subsys = DASCI,
.event_names = vgcs_fsm_event_names,
.cleanup = vgcs_call_detach_and_destroy,
};
/* Handle VGCS/VBS SETUP message.
*
* See 3GPP TS 48.008 §3.2.1.50
*/
int vgcs_vbs_call_start(struct gsm_subscriber_connection *conn, struct msgb *msg)
{
int payload_length = msg->tail - msg->l4h;
struct tlv_parsed tp;
struct gsm_subscriber_connection *c;
struct gsm0808_group_callref *gc = &conn->vgcs_call.gc_ie;
int rc;
uint8_t cause;
if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, payload_length - 1) < 0) {
LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS;
goto reject;
}
/* Check for mandatory Group Call Reference. */
if (!TLVP_PRESENT(&tp, GSM0808_IE_GROUP_CALL_REFERENCE)) {
LOGPFSML(conn->fi, LOGL_ERROR, "Mandatory group call reference not present.\n");
cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
goto reject;
}
/* Decode Group Call Reference. */
rc = gsm0808_dec_group_callref(gc, TLVP_VAL(&tp, GSM0808_IE_GROUP_CALL_REFERENCE),
TLVP_LEN(&tp, GSM0808_IE_GROUP_CALL_REFERENCE));
if (rc < 0) {
LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode group call reference.\n");
cause = GSM0808_CAUSE_INCORRECT_VALUE;
goto reject;
}
conn->vgcs_call.sf = gc->sf;
conn->vgcs_call.call_ref = (osmo_load32be_ext_2(gc->call_ref_hi, 3) << 3) | gc->call_ref_lo;
/* Check for duplicated callref. */
llist_for_each_entry(c, &conn->network->subscr_conns, entry) {
if (!c->vgcs_call.fi)
continue;
if (c == conn)
continue;
if (conn->vgcs_call.sf == c->vgcs_call.sf
&& conn->vgcs_call.call_ref == c->vgcs_call.call_ref) {
LOG_CALL(conn, LOGL_ERROR, "A %s call with callref %s already exists.\n",
(conn->vgcs_call.sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS",
gsm44068_group_id_string(conn->vgcs_call.call_ref));
cause = GSM0808_CAUSE_INCORRECT_VALUE;
goto reject;
}
}
/* Decode VGCS Feature Flags */
if (TLVP_PRESENT(&tp, GSM0808_IE_VGCS_FEATURE_FLAGS)) {
rc = gsm0808_dec_vgcs_feature_flags(&conn->vgcs_call.ff,
TLVP_VAL(&tp, GSM0808_IE_VGCS_FEATURE_FLAGS),
TLVP_LEN(&tp, GSM0808_IE_VGCS_FEATURE_FLAGS));
if (rc < 0) {
LOG_CALL(conn, LOGL_ERROR, "Unable to decode feature flags.\n");
cause = GSM0808_CAUSE_INCORRECT_VALUE;
goto reject;
}
conn->vgcs_call.ff_present = true;
}
/* Create VGCS FSM. */
conn->vgcs_call.fi = osmo_fsm_inst_alloc(&vgcs_call_fsm, conn->network, conn, LOGL_DEBUG, NULL);
if (!conn->vgcs_call.fi) {
cause = GSM0808_CAUSE_INCORRECT_VALUE;
goto reject;
}
/* Init list of cells that are used by the call. */
INIT_LLIST_HEAD(&conn->vgcs_call.chan_list);
/* Init L3 queue. */
INIT_LLIST_HEAD(&conn->vgcs_call.l3_queue);
osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_SETUP, NULL);
return 0;
reject:
bsc_tx_setup_refuse(conn, cause);
return -EINVAL;
}
/*
* VGCS chan FSM
*/
static void vgcs_chan_detach_and_destroy(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct gsm_subscriber_connection *conn = fi->priv;
if (conn->vgcs_chan.fi->state != VGCS_CHAN_ST_WAIT_EST) {
/* Remove call from notification channel. */
if (conn->lchan)
rsl_notification_cmd(conn->lchan->ts->trx->bts, NULL, &conn->vgcs_chan.gc_ie, NULL);
else
LOG_CHAN(conn, LOGL_ERROR, "Unable to remove notification, lchan is already gone.\n");
}
/* Detach from call, if not already. */
if (conn->vgcs_chan.call) {
llist_del(&conn->vgcs_chan.list);
conn->vgcs_chan.call = NULL;
}
/* Remove pointer of FSM. */
conn->vgcs_chan.fi = NULL;
}
static void uplink_released(struct gsm_subscriber_connection *conn)
{
LOG_CHAN(conn, LOGL_DEBUG, "Uplink is now released.\n");
/* Go into blocked or free state. */
if (conn->vgcs_chan.call && conn->vgcs_chan.call->vgcs_call.fi
&& conn->vgcs_chan.call->vgcs_call.fi->state == VGCS_CALL_ST_IDLE)
osmo_fsm_inst_state_chg(conn->vgcs_chan.fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
else
osmo_fsm_inst_state_chg(conn->vgcs_chan.fi, VGCS_CHAN_ST_ACTIVE_BLOCKED, 0, 0);
}
static void vgcs_chan_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
struct lchan_activate_info info;
switch (event) {
case VGCS_EV_ASSIGN_REQ:
LOG_CHAN(conn, LOGL_DEBUG, "MSC assigns channel.\n");
/* MSC requests channel assignment. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_WAIT_EST, 0, 0);
/* Requesting channel from BTS. */
info = (struct lchan_activate_info){
.activ_for = ACTIVATE_FOR_VGCS_CHANNEL,
.for_conn = conn,
.chreq_reason = GSM_CHREQ_REASON_OTHER,
.ch_mode_rate = conn->vgcs_chan.ch_mode_rate,
.ch_indctr = conn->vgcs_chan.ct.ch_indctr,
/* TSC is used from TS config. */
.encr = conn->vgcs_chan.new_lchan->encr,
/* Timing advance of 0 is used until channel is activated for uplink. */
.ta_known = true,
.ta = 0,
};
if (conn->vgcs_chan.call->vgcs_call.sf == GSM0808_SF_VGCS)
info.type_for = LCHAN_TYPE_FOR_VGCS;
else
info.type_for = LCHAN_TYPE_FOR_VBS;
/* Activate lchan. If an error occurs, this the function call may trigger VGCS_EV_LCHAN_ERROR event.
* This means that this must be the last action in this handler. */
lchan_activate(conn->vgcs_chan.new_lchan, &info);
break;
default:
OSMO_ASSERT(false);
}
}
static void vgcs_chan_fsm_wait_est(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
const struct mgcp_conn_peer *mgw_info;
switch (event) {
case VGCS_EV_LCHAN_ACTIVE:
LOG_CHAN(conn, LOGL_DEBUG, "lchan is active.\n");
/* If no MGW is used. */
if (!gscon_is_aoip(conn)) {
LOG_CHAN(conn, LOGL_DEBUG, "Not connecting MGW endpoint, no AoIP connection.\n");
goto no_aoip;
}
/* Send activation to MGW. */
LOG_CHAN(conn, LOGL_DEBUG, "Connecting MGW endpoint to the MSC's RTP port: %s:%u\n",
conn->vgcs_chan.msc_rtp_addr, conn->vgcs_chan.msc_rtp_port);
/* Connect MGW. The function call may trigger VGCS_EV_MGW_OK event.
* This means that this must be the last action in this handler.
* If this function fails, VGCS_EV_MGW_FAIL will not trigger. */
if (!gscon_connect_mgw_to_msc(conn,
conn->vgcs_chan.new_lchan,
conn->vgcs_chan.msc_rtp_addr,
conn->vgcs_chan.msc_rtp_port,
fi,
VGCS_EV_MGW_OK,
VGCS_EV_MGW_FAIL,
NULL,
NULL)) {
/* Report failure to MSC. */
bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
break;
}
break;
case VGCS_EV_LCHAN_ERROR:
LOG_CHAN(conn, LOGL_DEBUG, "lchan failed.\n");
/* BTS reports failure on channel request. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_NULL, 0, 0);
/* Add/update SI10. */
if (conn->vgcs_chan.call)
si10_update(conn->vgcs_chan.call);
/* Report failure to MSC. */
bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
break;
case VGCS_EV_MGW_OK:
LOG_CHAN(conn, LOGL_DEBUG, "MGW endpoint connected.\n");
/* MGW reports success. */
mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc);
if (!mgw_info) {
LOG_CHAN(conn, LOGL_ERROR, "Unable to retrieve RTP port info allocated by MGW for"
" the MSC side.");
/* Report failure to MSC. */
bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
break;
}
LOG_CHAN(conn, LOGL_DEBUG, "MGW's MSC side CI: %s:%u\n", mgw_info->addr, mgw_info->port);
no_aoip:
/* Channel established from BTS. */
gscon_change_primary_lchan(conn, conn->vgcs_chan.new_lchan);
/* Change state according to call state. */
if (conn->vgcs_chan.call && conn->vgcs_chan.call->vgcs_call.fi
&& conn->vgcs_chan.call->vgcs_call.fi->state == VGCS_CALL_ST_IDLE)
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
else
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_BLOCKED, 0, 0);
if (conn->vgcs_chan.call) {
/* Add call to notification channel. */
rsl_notification_cmd(conn->lchan->ts->trx->bts, conn->lchan, &conn->vgcs_chan.gc_ie, NULL);
/* Add/update SI10. */
si10_update(conn->vgcs_chan.call);
}
/* Report result to MSC. */
bsc_tx_vgcs_vbs_assignment_result(conn, &conn->vgcs_chan.ct, &conn->vgcs_chan.ci,
conn->vgcs_chan.call_id);
break;
case VGCS_EV_MGW_FAIL:
LOG_CHAN(conn, LOGL_DEBUG, "MGW endpoint failed.\n");
/* MGW reports failure. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_NULL, 0, 0);
/* Add/update SI10. */
if (conn->vgcs_chan.call)
si10_update(conn->vgcs_chan.call);
/* Report failure to MSC. */
bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
break;
case VGCS_EV_CLEANUP:
LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
/* MSC wants to terminate. */
osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
break;
case VGCS_EV_BLOCK:
case VGCS_EV_UNBLOCK:
/* Ignore, because channel is not yet ready. */
break;
default:
OSMO_ASSERT(false);
}
}
static void vgcs_chan_fsm_active_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv, *cc;
switch (event) {
case VGCS_EV_UNBLOCK:
LOG_CHAN(conn, LOGL_DEBUG, "Unblocking channel.\n");
/* No uplink is used in other cell. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
break;
case VGCS_EV_TALKER_DET:
LOG_CHAN(conn, LOGL_DEBUG, "Talker detected on blocked channel.\n");
if (conn->vgcs_chan.call->vgcs_call.sf == GSM0808_SF_VBS)
LOG_CHAN(conn, LOGL_ERROR, "Talker detection not allowed on VBS channel.\n");
/* Race condition: BTS detected a talker. Waiting for talker to establish or fail. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0);
break;
case VGCS_EV_TALKER_EST:
cc = find_calling_subscr_conn(conn);
if (!cc) {
LOG_CHAN(conn, LOGL_ERROR, "No assignment requested from MSC!\n");
/* Uplink is used while blocked. Waiting for channel to be release. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0);
/* Send UPLINK RELEASE to MS. */
gsm48_send_uplink_release(conn->lchan, GSM48_RR_CAUSE_NORMAL);
/* Go into blocked or free state. */
uplink_released(conn);
break;
}
/* Talker is assigning to this channel. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_EST, 0, 0);
/* Report talker detection to call state machine. */
if (conn->vgcs_chan.call)
osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_CALLING_ASSIGNED, conn);
/* Repeat notification for the MS that has been assigned. */
rsl_notification_cmd(conn->lchan->ts->trx->bts, conn->lchan, &conn->vgcs_chan.gc_ie, NULL);
break;
case VGCS_EV_CLEANUP:
LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
/* MSC wants to terminate. */
osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
break;
default:
OSMO_ASSERT(false);
}
}
static void vgcs_chan_fsm_enter_active_free(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct gsm_subscriber_connection *conn = fi->priv;
/* Send UPLINK FREE message to BTS. This hits on every state change (and or timer start). */
LOG_CHAN(conn, LOGL_DEBUG, "Sending UPLINK FREE message to channel.\n");
gsm48_send_uplink_free(conn->lchan, 0, NULL);
}
static void vgcs_chan_fsm_active_free(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
switch (event) {
case VGCS_EV_BLOCK:
LOG_CHAN(conn, LOGL_DEBUG, "Blocking channel.\n");
/* Uplink is used in other cell. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_BLOCKED, 0, 0);
/* Send UPLINK BUSY to MS. */
LOG_CHAN(conn, LOGL_DEBUG, "Sending UPLINK BUSY message to channel.\n");
gsm48_send_uplink_busy(conn->lchan);
break;
case VGCS_EV_TALKER_DET:
LOG_CHAN(conn, LOGL_DEBUG, "Talker detected on free channel.\n");
/* BTS detected a talker. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_INIT, 0, 0);
/* Report talker detection to call state machine. */
if (conn->vgcs_chan.call)
osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_DET, data);
break;
case VGCS_EV_CLEANUP:
LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
/* MSC wants to terminate. */
osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
break;
default:
OSMO_ASSERT(false);
}
}
static void vgcs_chan_fsm_active_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
uint8_t cause = (data) ? *(uint8_t *)data : 0;
switch (event) {
case VGCS_EV_BLOCK:
case VGCS_EV_REJECT:
LOG_CHAN(conn, LOGL_DEBUG, "Blocking/rejecting channel.\n");
/* Uplink is used in other cell. Waiting for channel to be established and then released. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0);
break;
case VGCS_EV_TALKER_EST:
LOG_CHAN(conn, LOGL_DEBUG, "Talker established uplink.\n");
/* Uplink has been established */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_EST, 0, 0);
/* Report talker establishment to call state machine. */
if (conn->vgcs_chan.call)
osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_EST, data);
break;
case VGCS_EV_TALKER_FAIL:
LOG_CHAN(conn, LOGL_NOTICE, "Uplink failed, establishment timeout.\n");
/* Release datalink */
rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END);
/* fall thru */
case VGCS_EV_TALKER_REL:
LOG_CHAN(conn, LOGL_DEBUG, "Uplink is now released.\n");
/* Uplink establishment failed. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
/* Report release indication to call state machine. */
if (conn->vgcs_chan.call)
osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_REL, &cause);
break;
case VGCS_EV_CLEANUP:
LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
/* MSC wants to terminate. */
osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
break;
default:
OSMO_ASSERT(false);
}
}
static void vgcs_chan_fsm_active_est(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
uint8_t cause = (data) ? *(uint8_t *)data : 0;
struct msgb *msg = data;
switch (event) {
case VGCS_EV_BLOCK:
case VGCS_EV_REJECT:
LOG_CHAN(conn, LOGL_DEBUG, "Blocking/rejecting channel.\n");
/* Uplink is used in other cell. Waiting for channel to be release. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0);
/* Send UPLINK RELEASE to MS. */
gsm48_send_uplink_release(conn->lchan, GSM48_RR_CAUSE_NORMAL);
/* Go into blocked or free state. */
uplink_released(conn);
break;
case VGCS_EV_TALKER_DATA:
LOG_CHAN(conn, LOGL_DEBUG, "Talker sends data on uplink.\n");
if (msg) {
struct gsm48_hdr *gh;
uint8_t pdisc;
uint8_t msg_type;
if (msgb_l3len(msg) < sizeof(*gh)) {
LOG_LCHAN(msg->lchan, LOGL_ERROR,
"Message too short for a GSM48 header (%u)\n", msgb_l3len(msg));
break;
}
gh = msgb_l3(msg);
pdisc = gsm48_hdr_pdisc(gh);
msg_type = gsm48_hdr_msg_type(gh);
if (pdisc == GSM48_PDISC_RR && msg_type == GSM48_MT_RR_UPLINK_RELEASE) {
LOG_CHAN(conn, LOGL_DEBUG, "Uplink is released by UPLINK RELEASE message.\n");
/* Release datalink */
rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END);
/* Talker released the uplink. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
/* Report talker release to call state machine. */
if (conn->vgcs_chan.call) {
cause = GSM0808_CAUSE_CALL_CONTROL;
osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_REL,
&cause);
}
break;
}
if (pdisc == GSM48_PDISC_RR && msg_type == GSM48_MT_RR_ASS_COMPL) {
LOG_CHAN(conn, LOGL_DEBUG, "Asssignment complete.\n");
struct gsm_subscriber_connection *cc;
cc = find_calling_subscr_conn(conn);
if (!cc) {
LOG_CHAN(conn, LOGL_ERROR, "No assignment requested from MSC!\n");
break;
}
LOG_CHAN(conn, LOGL_DEBUG, "Trigger State machine.\n");
osmo_fsm_inst_dispatch(cc->assignment.fi, ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE, msg);
break;
}
}
/* Report talker data to call state machine. */
if (conn->vgcs_chan.call)
osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_DATA, data);
break;
case VGCS_EV_MSC_DTAP:
LOG_CHAN(conn, LOGL_DEBUG, "MSC sends DTAP message to talker.\n");
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MT_DTAP, data);
break;
case VGCS_EV_TALKER_FAIL:
LOG_CHAN(conn, LOGL_NOTICE, "Uplink failed after establishment.\n");
/* Release datalink */
rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END);
/* fall thru */
case VGCS_EV_TALKER_REL:
LOG_CHAN(conn, LOGL_DEBUG, "Uplink is now released.\n");
/* Talker released the uplink. */
osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
/* Report talker release to call state machine. */
if (conn->vgcs_chan.call)
osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_REL, &cause);
break;
case VGCS_EV_CLEANUP:
LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
/* MSC wants to terminate. */
osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
break;
default:
OSMO_ASSERT(false);
}
}
static void vgcs_chan_fsm_active_rel(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
switch (event) {
case VGCS_EV_BLOCK:
case VGCS_EV_REJECT:
LOG_CHAN(conn, LOGL_DEBUG, "Blocking/rejecting channel.\n");
/* Race condition: Uplink is used in other cell, we are already releasing. */
break;
case VGCS_EV_TALKER_EST:
LOG_CHAN(conn, LOGL_DEBUG, "Talker established uplink, releasing.\n");
/* Finally the talker established the connection. Send UPLINK RELEASE to MS. */
gsm48_send_uplink_release(conn->lchan, GSM48_RR_CAUSE_NORMAL);
/* Release datalink */
rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END);
/* fall thru */
case VGCS_EV_TALKER_FAIL:
/* Release datalink */
rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END);
/* fall thru */
case VGCS_EV_TALKER_REL:
/* Go into blocked or free state. */
uplink_released(conn);
break;
case VGCS_EV_CLEANUP:
LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
/* MSC wants to terminate. */
osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
break;
default:
OSMO_ASSERT(false);
}
}
static const struct osmo_fsm_state vgcs_chan_fsm_states[] = {
[VGCS_CHAN_ST_NULL] = {
.name = "NULL",
.in_event_mask = S(VGCS_EV_ASSIGN_REQ),
.out_state_mask = S(VGCS_CHAN_ST_WAIT_EST),
.action = vgcs_chan_fsm_null,
},
[VGCS_CHAN_ST_WAIT_EST] = {
.name = "WAIT_EST",
.in_event_mask = S(VGCS_EV_LCHAN_ACTIVE) |
S(VGCS_EV_LCHAN_ERROR) |
S(VGCS_EV_MGW_OK) |
S(VGCS_EV_MGW_FAIL) |
S(VGCS_EV_CLEANUP) |
S(VGCS_EV_BLOCK) |
S(VGCS_EV_UNBLOCK),
.out_state_mask = S(VGCS_CHAN_ST_NULL) |
S(VGCS_CHAN_ST_ACTIVE_BLOCKED) |
S(VGCS_CHAN_ST_ACTIVE_FREE),
.action = vgcs_chan_fsm_wait_est,
},
[VGCS_CHAN_ST_ACTIVE_BLOCKED] = {
.name = "ACTIVE/BLOCKED",
.in_event_mask = S(VGCS_EV_UNBLOCK) |
S(VGCS_EV_TALKER_DET) |
S(VGCS_EV_TALKER_EST) |
S(VGCS_EV_CLEANUP),
.out_state_mask = S(VGCS_CHAN_ST_NULL) |
S(VGCS_CHAN_ST_ACTIVE_EST) |
S(VGCS_CHAN_ST_ACTIVE_FREE) |
S(VGCS_CHAN_ST_ACTIVE_REL),
.action = vgcs_chan_fsm_active_blocked,
},
[VGCS_CHAN_ST_ACTIVE_FREE] = {
.name = "ACTIVE/FREE",
.in_event_mask = S(VGCS_EV_BLOCK) |
S(VGCS_EV_TALKER_DET) |
S(VGCS_EV_CLEANUP),
.out_state_mask = S(VGCS_CHAN_ST_NULL) |
S(VGCS_CHAN_ST_ACTIVE_BLOCKED) |
S(VGCS_CHAN_ST_ACTIVE_INIT) |
S(VGCS_CHAN_ST_ACTIVE_FREE),
.action = vgcs_chan_fsm_active_free,
.onenter = vgcs_chan_fsm_enter_active_free,
},
[VGCS_CHAN_ST_ACTIVE_INIT] = {
.name = "ACTIVE/INIT",
.in_event_mask = S(VGCS_EV_BLOCK) |
S(VGCS_EV_REJECT) |
S(VGCS_EV_TALKER_EST) |
S(VGCS_EV_TALKER_FAIL) |
S(VGCS_EV_CLEANUP),
.out_state_mask = S(VGCS_CHAN_ST_NULL) |
S(VGCS_CHAN_ST_ACTIVE_EST) |
S(VGCS_CHAN_ST_ACTIVE_REL) |
S(VGCS_CHAN_ST_ACTIVE_FREE),
.action = vgcs_chan_fsm_active_init,
},
[VGCS_CHAN_ST_ACTIVE_EST] = {
.name = "ACTIVE/ESTABLISHED",
.in_event_mask = S(VGCS_EV_BLOCK) |
S(VGCS_EV_REJECT) |
S(VGCS_EV_TALKER_DATA) |
S(VGCS_EV_MSC_DTAP) |
S(VGCS_EV_TALKER_REL) |
S(VGCS_EV_TALKER_FAIL) |
S(VGCS_EV_CLEANUP),
.out_state_mask = S(VGCS_CHAN_ST_NULL) |
S(VGCS_CHAN_ST_ACTIVE_FREE) |
S(VGCS_CHAN_ST_ACTIVE_REL),
.action = vgcs_chan_fsm_active_est,
},
[VGCS_CHAN_ST_ACTIVE_REL] = {
.name = "ACTIVE/RELEASE",
.in_event_mask = S(VGCS_EV_BLOCK) |
S(VGCS_EV_REJECT) |
S(VGCS_EV_TALKER_EST) |
S(VGCS_EV_TALKER_REL) |
S(VGCS_EV_TALKER_FAIL) |
S(VGCS_EV_CLEANUP),
.out_state_mask = S(VGCS_CHAN_ST_NULL) |
S(VGCS_CHAN_ST_ACTIVE_BLOCKED) |
S(VGCS_CHAN_ST_ACTIVE_FREE),
.action = vgcs_chan_fsm_active_rel,
},
};
static struct osmo_fsm vgcs_chan_fsm = {
.name = "vgcs_chan",
.states = vgcs_chan_fsm_states,
.num_states = ARRAY_SIZE(vgcs_chan_fsm_states),
.log_subsys = DASCI,
.event_names = vgcs_fsm_event_names,
.cleanup = vgcs_chan_detach_and_destroy,
};
/* Handle VGCS/VBS ASSIGNMENT REQUEST message.
*
* See 3GPP TS 48.008 §3.2.1.53
*/
int vgcs_vbs_chan_start(struct gsm_subscriber_connection *conn, struct msgb *msg)
{
int payload_length = msg->tail - msg->l4h;
struct tlv_parsed tp;
struct gsm_subscriber_connection *c;
struct gsm0808_group_callref *gc = &conn->vgcs_chan.gc_ie;
struct assignment_request req = {
.aoip = gscon_is_aoip(conn),
};
uint8_t cause;
struct gsm_bts *bts;
struct gsm_lchan *lchan = NULL;
int rc;
int i;
if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, payload_length - 1) < 0) {
LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS;
goto reject;
}
/* Check for mandatory IEs. */
if (!TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_TYPE)
|| !TLVP_PRESENT(&tp, GSM0808_IE_ASSIGNMENT_REQUIREMENT)
|| !TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER)
|| !TLVP_PRESENT(&tp, GSM0808_IE_GROUP_CALL_REFERENCE)) {
LOGPFSML(conn->fi, LOGL_ERROR, "Mandatory IE not present.\n");
cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
goto reject;
}
/* Decode Channel Type element. */
rc = gsm0808_dec_channel_type(&conn->vgcs_chan.ct, TLVP_VAL(&tp, GSM0808_IE_CHANNEL_TYPE),
TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE));
if (rc < 0) {
LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Channel Type.\n");
cause = GSM0808_CAUSE_INCORRECT_VALUE;
goto reject;
}
/* Only speech is supported. */
if (conn->vgcs_chan.ct.ch_indctr != GSM0808_CHAN_SPEECH) {
cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS;
goto reject;
}
/* Decode Assignment Requirement element. */
rc = gsm0808_dec_assign_req(&conn->vgcs_chan.ar, TLVP_VAL(&tp, GSM0808_IE_ASSIGNMENT_REQUIREMENT),
TLVP_LEN(&tp, GSM0808_IE_ASSIGNMENT_REQUIREMENT));
if (rc < 0) {
LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Assignment Requirement.\n");
cause = GSM0808_CAUSE_INCORRECT_VALUE;
goto reject;
}
/* Decode Cell Identifier element. */
rc = gsm0808_dec_cell_id(&conn->vgcs_chan.ci, TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER),
TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER));
if (rc < 0) {
LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Cell Identifier.\n");
cause = GSM0808_CAUSE_INCORRECT_VALUE;
goto reject;
}
gsm0808_cell_id_u_name(conn->vgcs_chan.ci_str, sizeof(conn->vgcs_chan.ci_str), conn->vgcs_chan.ci.id_discr,
&conn->vgcs_chan.ci.id);
/* Decode Group Call Reference element. */
rc = gsm0808_dec_group_callref(gc, TLVP_VAL(&tp, GSM0808_IE_GROUP_CALL_REFERENCE),
TLVP_LEN(&tp, GSM0808_IE_GROUP_CALL_REFERENCE));
if (rc < 0) {
LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Group Call Reference.\n");
cause = GSM0808_CAUSE_INCORRECT_VALUE;
goto reject;
}
conn->vgcs_chan.sf = gc->sf;
conn->vgcs_chan.call_ref = (osmo_load32be_ext_2(gc->call_ref_hi, 3) << 3) | gc->call_ref_lo;
/* Find BTS from Cell Identity. */
bts = gsm_bts_by_cell_id(conn->network, &conn->vgcs_chan.ci, 0);
if (!bts) {
LOG_CHAN(conn, LOGL_ERROR, "No cell found that matches the given Cell Identifier.\n");
cause = GSM0808_CAUSE_RQSTED_TERRESTRIAL_RESOURCE_UNAVAILABLE;
goto reject;
}
/* If Cell Identity is ambiguous. */
if (gsm_bts_by_cell_id(conn->network, &conn->vgcs_chan.ci, 1))
LOG_CHAN(conn, LOGL_NOTICE, "More thant one cell found that match the given Cell Identifier.\n");
/* Decode channel related elements.
* This must be done after selecting the BTS, because codec selection requires relation to BTS. */
rc = bssmap_handle_ass_req_ct_speech(conn, bts, &tp, &conn->vgcs_chan.ct, &req, &cause);
if (rc < 0)
goto reject;
/* Store AoIP elements. */
osmo_strlcpy(conn->vgcs_chan.msc_rtp_addr, req.msc_rtp_addr, sizeof(conn->vgcs_chan.msc_rtp_addr));
conn->vgcs_chan.msc_rtp_port = req.msc_rtp_port;
if (TLVP_PRESENT(&tp, GSM0808_IE_CALL_ID)) {
/* Decode Call Identifier element. */
rc = gsm0808_dec_call_id(&conn->vgcs_chan.call_id, TLVP_VAL(&tp, GSM0808_IE_CALL_ID),
TLVP_LEN(&tp, GSM0808_IE_CALL_ID));
if (rc < 0) {
LOG_CHAN(conn, LOGL_ERROR, "Unable to decode Call Identifier.\n");
cause = GSM0808_CAUSE_INCORRECT_VALUE;
goto reject;
}
}
/* Try to allocate a new lchan in order of preference. */
for (i = 0; i < req.n_ch_mode_rate; i++) {
lchan = lchan_select_by_chan_mode(bts,
req.ch_mode_rate_list[i].chan_mode,
req.ch_mode_rate_list[i].chan_rate,
SELECT_FOR_VGCS, NULL);
if (!lchan)
continue;
LOG_CHAN(conn, LOGL_DEBUG, "Selected new lchan %s for mode[%d] = %s channel_rate=%d\n",
gsm_lchan_name(lchan), i, gsm48_chan_mode_name(req.ch_mode_rate_list[i].chan_mode),
req.ch_mode_rate_list[i].chan_rate);
conn->vgcs_chan.ch_mode_rate = req.ch_mode_rate_list[i];
break;
}
if (!lchan) {
LOG_CHAN(conn, LOGL_ERROR, "Requested lchan not available.\n");
cause = GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE;
goto reject;
}
conn->vgcs_chan.new_lchan = lchan;
/* Create VGCS FSM. */
conn->vgcs_chan.fi = osmo_fsm_inst_alloc(&vgcs_chan_fsm, conn->network, conn, LOGL_DEBUG, NULL);
if (!conn->vgcs_chan.fi)
goto reject;
/* Attach to call control instance, if a call with same callref exists. */
llist_for_each_entry(c, &conn->network->subscr_conns, entry) {
if (!c->vgcs_call.fi)
continue;
if (c->vgcs_call.sf == conn->vgcs_chan.sf
&& c->vgcs_call.call_ref == conn->vgcs_chan.call_ref) {
llist_add_tail(&conn->vgcs_chan.list, &c->vgcs_call.chan_list);
conn->vgcs_chan.call = c;
break;
}
}
if (!conn->vgcs_chan.call) {
LOG_CHAN(conn, LOGL_ERROR, "A %s call with callref %s does not exist.\n",
(conn->vgcs_chan.sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS",
gsm44068_group_id_string(conn->vgcs_chan.call_ref));
cause = GSM0808_CAUSE_VGCS_VBS_CALL_NON_EXISTENT;
osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
goto reject;
}
osmo_fsm_inst_dispatch(conn->vgcs_chan.fi, VGCS_EV_ASSIGN_REQ, NULL);
return 0;
reject:
bsc_tx_vgcs_vbs_assignment_fail(conn, cause);
return -EINVAL;
}
/* Return lchan of group call that exists in the same BTS. */
struct gsm_lchan *vgcs_vbs_find_lchan(struct gsm_bts *bts, struct gsm0808_group_callref *gc)
{
struct gsm_subscriber_connection *call = NULL, *c;
struct gsm_lchan *lchan = NULL;
uint32_t call_ref = (osmo_load32be_ext_2(gc->call_ref_hi, 3) << 3) | gc->call_ref_lo;
/* Find group call. */
llist_for_each_entry(c, &bts->network->subscr_conns, entry) {
if (!c->vgcs_call.fi)
continue;
if (c->vgcs_call.sf == gc->sf
&& c->vgcs_call.call_ref == call_ref) {
call = c;
break;
}
}
if (!call) {
LOGP(DASCI, LOGL_ERROR, "Cannot assign to channel, %s channel with callref %s does not exist.\n",
(gc->sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS", gsm44068_group_id_string(call_ref));
return NULL;
}
/* Find channel in same BTS. */
llist_for_each_entry(c, &call->vgcs_call.chan_list, vgcs_chan.list) {
if (c->lchan && c->lchan->ts->trx->bts == bts)
lchan = c->lchan;
}
if (!call) {
LOGP(DASCI, LOGL_ERROR, "Cannot assign to channel, caller's BTS has no %s channel with callref %s.\n",
(gc->sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS", gsm44068_group_id_string(call_ref));
return NULL;
}
return lchan;
}