forked from retronetworking/osmo-v5
537 lines
13 KiB
C
537 lines
13 KiB
C
#include <errno.h>
|
|
|
|
#include <osmocom/core/talloc.h>
|
|
#include <osmocom/core/linuxlist.h>
|
|
#include <osmocom/gsm/tlv.h>
|
|
|
|
#include "v5x_internal.h"
|
|
#include "v5x_protocol.h"
|
|
#include "lapv5.h"
|
|
#include "layer1.h"
|
|
#include "v5x_l1_fsm.h"
|
|
#include "v5x_le_ctrl_fsm.h"
|
|
#include "v5x_le_port_fsm.h"
|
|
#include "v5x_le_pstn_fsm.h"
|
|
#include "v52_le_lcp_fsm.h"
|
|
#include "v52_le_bcc_fsm.h"
|
|
#include "v52_le_pp_fsm.h"
|
|
#include "v5x_le_management.h"
|
|
#include "logging.h"
|
|
|
|
static LLIST_HEAD(v5_instances);
|
|
|
|
struct v5x_instance *v5x_instance_alloc(void *ctx)
|
|
{
|
|
struct v5x_instance *v5i = talloc_zero(ctx, struct v5x_instance);
|
|
if (!v5i)
|
|
return NULL;
|
|
|
|
INIT_LLIST_HEAD(&v5i->interfaces);
|
|
|
|
llist_add_tail(&v5i->list, &v5_instances);
|
|
|
|
return v5i;
|
|
}
|
|
|
|
void v5x_instance_free(struct v5x_instance *v5i)
|
|
{
|
|
struct v5x_interface *v5if, *v5if2;
|
|
|
|
llist_for_each_entry_safe(v5if, v5if2, &v5i->interfaces, list)
|
|
v5x_interface_free(v5if);
|
|
|
|
talloc_free(v5i);
|
|
}
|
|
|
|
static void v5x_ts_init(struct v5x_timeslot *v5ts, uint8_t nr, struct v5x_link *v5l, bool b_channel)
|
|
{
|
|
/* Never use slot 16 as B-channel, because some drivers (mISDN) will not allow this. */
|
|
if (nr == 16)
|
|
b_channel = false;
|
|
|
|
v5ts->nr = nr;
|
|
v5ts->link = v5l;
|
|
v5ts->b_channel = b_channel;
|
|
}
|
|
|
|
static void v5x_cchan_init(struct v5x_c_channel *v5cc, struct v5x_link *v5l, struct v5x_timeslot *v5ts, bool active)
|
|
{
|
|
v5cc->link = v5l;
|
|
v5cc->ts = v5ts;
|
|
v5cc->active = active;
|
|
}
|
|
|
|
struct v5x_link *v5x_link_create(struct v5x_interface *v5if, uint8_t id)
|
|
{
|
|
int count;
|
|
struct v5x_link *v5l;
|
|
unsigned int i;
|
|
int rc;
|
|
|
|
if (v5x_link_find_id(v5if, id)) {
|
|
LOGP(DV5, LOGL_ERROR, "Link %d already exists.\n", id);
|
|
return NULL;
|
|
}
|
|
|
|
count = v5x_link_count(v5if);
|
|
|
|
v5l = talloc_zero(v5if, struct v5x_link);
|
|
if (!v5l)
|
|
return NULL;
|
|
|
|
llist_add_tail(&v5l->list, &v5if->links);
|
|
count++;
|
|
|
|
v5l->id = id;
|
|
v5l->interface = v5if;
|
|
|
|
/* primary and secondary link will get TS 16 */
|
|
if (count <= 2) {
|
|
v5x_cchan_init(&v5l->c_channel[0], v5l, &v5l->ts[16], true);
|
|
}
|
|
|
|
for (i = 1; i < ARRAY_SIZE(v5l->ts); i++) {
|
|
bool b_channel = true;
|
|
if (v5l->c_channel[0].ts && v5l->c_channel[0].ts->nr == i)
|
|
b_channel = false;
|
|
if (v5l->c_channel[1].ts && v5l->c_channel[1].ts->nr == i)
|
|
b_channel = false;
|
|
if (v5l->c_channel[2].ts && v5l->c_channel[2].ts->nr == i)
|
|
b_channel = false;
|
|
v5x_ts_init(&v5l->ts[i], i, v5l, b_channel);
|
|
}
|
|
|
|
v5l->l1 = v5x_l1_fsm_create(v5if, v5l, id);
|
|
if (!v5l->l1)
|
|
goto error;
|
|
|
|
if (v5if->dialect == V5X_DIALECT_V52) {
|
|
v5l->fi = v52_le_lcp_create(v5if, v5l, id);
|
|
if (!v5l->fi)
|
|
goto error;
|
|
|
|
v5l->ctrl = v5x_le_ctrl_create(V5X_CTRL_TYPE_LINK, v5if, v5l, id);
|
|
if (!v5l->ctrl)
|
|
goto error;
|
|
}
|
|
|
|
if (count == 1) {
|
|
v5if->primary_link = v5l;
|
|
v5if->cc_link = v5l;
|
|
}
|
|
if (count == 2) {
|
|
v5if->secondary_link = v5l;
|
|
/* add PP */
|
|
v5if->protection.pp = v52_le_pp_create(v5if);
|
|
if (!v5if->protection.pp)
|
|
goto error;
|
|
v5if->protection.li[0] = lapv5_instance_alloc(1, &ph_data_req_dl_prot, v5if->primary_link, v5x_dl_rcv,
|
|
v5if->primary_link, &lapd_profile_lapv5dl, "protection0");
|
|
if (!v5if->protection.li[0])
|
|
goto error;
|
|
v5if->protection.li[1] = lapv5_instance_alloc(1, &ph_data_req_dl_prot, v5if->secondary_link, v5x_dl_rcv,
|
|
v5if->secondary_link, &lapd_profile_lapv5dl, "protection1");
|
|
if (!v5if->protection.li[1])
|
|
goto error;
|
|
}
|
|
|
|
return v5l;
|
|
|
|
error:
|
|
rc = v5x_link_destroy(v5l);
|
|
OSMO_ASSERT(rc == 0);
|
|
return NULL;
|
|
}
|
|
|
|
int v5x_link_destroy(struct v5x_link *v5l)
|
|
{
|
|
struct v5x_interface *v5if = v5l->interface;
|
|
int count;
|
|
struct v5x_user_port *v5up;
|
|
int i;
|
|
|
|
count = v5x_link_count(v5l->interface);
|
|
if (count > 1 && v5l->interface->primary_link == v5l) {
|
|
LOGP(DV5, LOGL_ERROR, "Link %d is primary. Cannot remove it, while other links exist.\n", v5l->id);
|
|
return -EINVAL;
|
|
}
|
|
if (count > 2 && v5l->interface->secondary_link == v5l) {
|
|
LOGP(DV5, LOGL_ERROR, "Link %d is secondary (standby). Cannot remove it, while other (not primary) links exist.\n", v5l->id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* detach user ports */
|
|
for (i = 1; i < (int)ARRAY_SIZE(v5l->ts); i++) {
|
|
if ((v5up = v5l->ts[i].v5up)) {
|
|
if (v5up->ts[0] == &v5l->ts[i]) {
|
|
v5up->ts[0] = NULL;
|
|
ph_deactivate_req(&v5l->ts[i]);
|
|
}
|
|
if (v5up->ts[1] == &v5l->ts[i]) {
|
|
v5up->ts[1] = NULL;
|
|
ph_deactivate_req(&v5l->ts[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (v5l->ctrl)
|
|
v5x_le_ctrl_destroy(v5l->ctrl);
|
|
|
|
if (v5l->fi)
|
|
v52_le_lcp_destroy(v5l->fi);
|
|
|
|
if (v5l->l1)
|
|
v5x_l1_fsm_destroy(v5l->l1);
|
|
|
|
if (v5l->interface->primary_link == v5l)
|
|
v5l->interface->primary_link = NULL;
|
|
if (v5l->interface->secondary_link == v5l)
|
|
v5l->interface->secondary_link = NULL;
|
|
if (v5l->interface->cc_link == v5l)
|
|
v5l->interface->cc_link = NULL;
|
|
|
|
if (v5l->e1_line)
|
|
e1_line_exit(v5l);
|
|
|
|
llist_del(&v5l->list);
|
|
count--;
|
|
|
|
talloc_free(v5l);
|
|
|
|
/* if we have only one link left */
|
|
if (count == 1) {
|
|
/* remove PP */
|
|
if (v5if->protection.li[0]) {
|
|
lapv5_instance_free(v5if->protection.li[0]);
|
|
v5if->protection.li[0] = NULL;
|
|
}
|
|
if (v5if->protection.li[1]) {
|
|
lapv5_instance_free(v5if->protection.li[1]);
|
|
v5if->protection.li[1] = NULL;
|
|
}
|
|
if (v5if->protection.pp) {
|
|
v52_le_pp_destroy(v5if->protection.pp);
|
|
v5if->protection.pp = NULL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct v5x_interface *v5x_interface_alloc(struct v5x_instance *v5i, enum v5x_dialect dialect)
|
|
{
|
|
struct v5x_interface *v5if;
|
|
struct v5x_link *v5l;
|
|
// struct v5x_c_channel *primary_c_chan;
|
|
|
|
v5if = talloc_zero(v5i, struct v5x_interface);
|
|
if (!v5if)
|
|
return NULL;
|
|
|
|
INIT_LLIST_HEAD(&v5if->links);
|
|
INIT_LLIST_HEAD(&v5if->user_ports);
|
|
INIT_LLIST_HEAD(&v5if->bcc_procs);
|
|
|
|
llist_add_tail(&v5if->list, &v5i->interfaces);
|
|
|
|
v5if->instance = v5i;
|
|
v5if->dialect = dialect;
|
|
|
|
if (v5if->dialect == V5X_DIALECT_V51) {
|
|
v5l = v5x_link_create(v5if, 0);
|
|
if (!v5l)
|
|
goto error;
|
|
}
|
|
|
|
v5if->mgmt = v5x_le_mgmt_create(v5if);
|
|
if (!v5if->mgmt)
|
|
goto error;
|
|
|
|
v5if->control.ctrl = v5x_le_ctrl_create(V5X_CTRL_TYPE_COMMON, v5if, v5if, 0);
|
|
if (!v5if->control.ctrl)
|
|
goto error;
|
|
v5if->control.li = lapv5_instance_alloc(1, &ph_data_req_dl_cc, v5if, v5x_dl_rcv, v5if,
|
|
&lapd_profile_lapv5dl, "control");
|
|
if (!v5if->control.li)
|
|
goto error;
|
|
|
|
v5if->pstn.li = lapv5_instance_alloc(1, &ph_data_req_dl_cc, v5if, v5x_dl_rcv, v5if,
|
|
&lapd_profile_lapv5dl, "pstn");
|
|
if (!v5if->pstn.li)
|
|
goto error;
|
|
|
|
if (v5if->dialect == V5X_DIALECT_V52) {
|
|
v5if->lcp.li = lapv5_instance_alloc(1, &ph_data_req_dl_cc, v5if, v5x_dl_rcv, v5if,
|
|
&lapd_profile_lapv5dl, "lcp");
|
|
if (!v5if->lcp.li)
|
|
goto error;
|
|
|
|
v5if->bcc.li = lapv5_instance_alloc(1, &ph_data_req_dl_cc, v5if, v5x_dl_rcv, v5if,
|
|
&lapd_profile_lapv5dl, "bcc");
|
|
if (!v5if->bcc.li)
|
|
goto error;
|
|
}
|
|
|
|
return v5if;
|
|
|
|
error:
|
|
v5x_interface_free(v5if);
|
|
return NULL;
|
|
}
|
|
|
|
void v5x_interface_free(struct v5x_interface *v5if) {
|
|
struct v5x_user_port *v5up, *v5up2;
|
|
struct v52_bcc_proc *bcc, *bcc2;
|
|
struct v5x_link *v5l;
|
|
int rc;
|
|
|
|
if (!v5if)
|
|
return;
|
|
|
|
llist_for_each_entry_safe(v5up, v5up2, &v5if->user_ports, list)
|
|
v5x_user_port_destroy(v5up);
|
|
|
|
llist_for_each_entry_safe(bcc, bcc2, &v5if->bcc_procs, list)
|
|
v52_le_bcc_destroy(bcc);
|
|
|
|
/* delete reversed safely */
|
|
while (!llist_empty(&v5if->links)) {
|
|
v5l = llist_last_entry(&v5if->links, struct v5x_link, list);
|
|
rc = v5x_link_destroy(v5l);
|
|
OSMO_ASSERT(rc == 0);
|
|
}
|
|
|
|
if (v5if->mgmt)
|
|
v5x_le_mgmt_destroy(v5if->mgmt);
|
|
|
|
if (v5if->control.ctrl)
|
|
v5x_le_ctrl_destroy(v5if->control.ctrl);
|
|
if (v5if->control.li)
|
|
lapv5_instance_free(v5if->control.li);
|
|
|
|
if (v5if->pstn.li)
|
|
lapv5_instance_free(v5if->pstn.li);
|
|
|
|
if (v5if->lcp.li)
|
|
lapv5_instance_free(v5if->lcp.li);
|
|
|
|
if (v5if->bcc.li)
|
|
lapv5_instance_free(v5if->bcc.li);
|
|
|
|
llist_del(&v5if->list);
|
|
|
|
talloc_free(v5if);
|
|
}
|
|
|
|
struct v5x_user_port *v5x_user_port_create(struct v5x_interface *v5if, uint16_t nr, enum v5x_user_type type,
|
|
uint8_t ts1, uint8_t ts2)
|
|
{
|
|
struct v5x_user_port *v5up;
|
|
int i;
|
|
int rc;
|
|
|
|
if (v5x_user_port_find(v5if, nr, (type == V5X_USER_TYPE_ISDN))) {
|
|
LOGP(DV5, LOGL_ERROR, "User port %d already exists.\n", nr);
|
|
return NULL;
|
|
}
|
|
|
|
if (v5if->dialect == V5X_DIALECT_V51) {
|
|
struct v5x_link *v5l;
|
|
|
|
v5l = llist_first_entry(&v5if->links, struct v5x_link, list);
|
|
if (!v5l->ts[ts1].b_channel) {
|
|
LOGP(DV5, LOGL_ERROR, "Time slot %d is not a B-channel, select a different one.\n", ts1);
|
|
return NULL;
|
|
}
|
|
if ((v5up = v5l->ts[ts1].v5up)) {
|
|
LOGP(DV5, LOGL_ERROR, "Time slot %d is already assigned to another user port.\n", ts1);
|
|
return NULL;
|
|
}
|
|
if (type == V5X_USER_TYPE_ISDN) {
|
|
if (!v5l->ts[ts2].b_channel) {
|
|
LOGP(DV5, LOGL_ERROR, "Time slot %d is not a B-channel, select a different one.\n",
|
|
ts2);
|
|
return NULL;
|
|
}
|
|
if ((v5up = v5l->ts[ts2].v5up)) {
|
|
LOGP(DV5, LOGL_ERROR, "Time slot %d is already assigned to another user port.\n", ts2);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
v5up = talloc_zero(v5if, struct v5x_user_port);
|
|
if (!v5up)
|
|
return NULL;
|
|
|
|
llist_add_tail(&v5up->list, &v5if->user_ports);
|
|
|
|
v5up->interface = v5if;
|
|
v5up->nr = nr;
|
|
v5up->type = type;
|
|
|
|
/* assign ports */
|
|
if (v5if->dialect == V5X_DIALECT_V51) {
|
|
struct v5x_link *v5l;
|
|
|
|
v5l = llist_first_entry(&v5if->links, struct v5x_link, list);
|
|
OSMO_ASSERT(ts1 >= 1 && ts1 <= 31);
|
|
v5l->ts[ts1].v5up = v5up;
|
|
v5up->ts[0] = &v5l->ts[ts1];
|
|
if (type == V5X_USER_TYPE_ISDN) {
|
|
OSMO_ASSERT(ts2 >= 1 && ts2 <= 31);
|
|
v5l->ts[ts2].v5up = v5up;
|
|
v5up->ts[1] = &v5l->ts[ts2];
|
|
}
|
|
}
|
|
|
|
v5up->ctrl = v5x_le_ctrl_create(V5X_CTRL_TYPE_PORT, v5up, v5up, nr);
|
|
if (!v5up->ctrl) {
|
|
LOGP(DV5, LOGL_ERROR, "Failed to create control protocol\n");
|
|
goto error;
|
|
}
|
|
|
|
switch (type) {
|
|
case V5X_USER_TYPE_ISDN:
|
|
LOGP(DV5, LOGL_NOTICE, "Creating V5 ISDN user port with L3 addr %d\n", nr);
|
|
sprintf(v5up->ifname, "isdn-%d", nr);
|
|
OSMO_ASSERT(nr <= 8175);
|
|
v5up->port_fi = v5x_le_port_isdn_create(v5up, nr);
|
|
if (!v5up->port_fi) {
|
|
LOGP(DV5, LOGL_ERROR, "Failed to create port control\n");
|
|
goto error;
|
|
}
|
|
break;
|
|
case V5X_USER_TYPE_PSTN:
|
|
LOGP(DV5, LOGL_NOTICE, "Creating V5 PSTN user port with L3 addr %d\n", nr);
|
|
sprintf(v5up->ifname, "pstn-%d", nr);
|
|
OSMO_ASSERT(nr <= 32767);
|
|
v5up->port_fi = v5x_le_port_pstn_create(v5up, nr);
|
|
if (!v5up->port_fi) {
|
|
LOGP(DV5, LOGL_ERROR, "Failed to create port control\n");
|
|
goto error;
|
|
}
|
|
v5up->pstn.proto = v5x_le_pstn_create(v5up, nr);
|
|
if (!v5up->pstn.proto) {
|
|
LOGP(DV5, LOGL_ERROR, "Failed to create PSTN protocol\n");
|
|
goto error;
|
|
}
|
|
/* bring port into service */
|
|
v5x_le_pstn_mdu_snd(v5up, MDU_CTRL_port_blocked);
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
v5up->ep[i].port = v5up;
|
|
/* EC needs to adapt, NLP causes better results on quick volume changes */
|
|
v5up->ep[i].ec = echo_can_create(EC_TAPS, ECHO_CAN_USE_ADAPTION | ECHO_CAN_USE_NLP);
|
|
if (!v5up->ep[i].ec) {
|
|
LOGP(DV5, LOGL_ERROR, "Failed to create line echo canceler\n");
|
|
goto error;
|
|
}
|
|
rc = answertone_init(&v5up->ep[i].at, 8000);
|
|
if (rc < 0) {
|
|
LOGP(DV5, LOGL_ERROR, "Failed to create answer tone detector\n");
|
|
goto error;
|
|
}
|
|
if (type != V5X_USER_TYPE_ISDN)
|
|
break;
|
|
}
|
|
|
|
rc = ph_socket_init(&v5up->ph_socket, ph_socket_rx_cb, v5up, v5up->ifname, 1);
|
|
if (rc < 0) {
|
|
LOGP(DV5, LOGL_ERROR, "Failed to create PH-socket\n");
|
|
goto error;
|
|
}
|
|
|
|
/* start ctrl FSM for this port */
|
|
if (v5if->control.established)
|
|
v5x_le_ctrl_start(v5up->ctrl);
|
|
|
|
return v5up;
|
|
|
|
error:
|
|
v5x_user_port_destroy(v5up);
|
|
return NULL;
|
|
}
|
|
|
|
void v5x_user_port_destroy(struct v5x_user_port *v5up)
|
|
{
|
|
int i;
|
|
|
|
LOGP(DV5, LOGL_NOTICE, "Destroying V5 user port with L3 addr %d\n", v5up->nr);
|
|
|
|
/* close first, because it sends messages */
|
|
ph_socket_exit(&v5up->ph_socket);
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
/* destory line echo canceler */
|
|
if (v5up->ep[i].ec) {
|
|
echo_can_free(v5up->ep[i].ec);
|
|
answertone_exit(&v5up->ep[i].at);
|
|
v5up->ep[i].ec = NULL;
|
|
}
|
|
|
|
/* unassign ports */
|
|
if (v5up->ts[i]) {
|
|
v5up->ts[i]->v5up = NULL;
|
|
v5up->ts[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (v5up->ctrl)
|
|
v5x_le_ctrl_destroy(v5up->ctrl);
|
|
|
|
if (v5up->port_fi)
|
|
osmo_fsm_inst_free(v5up->port_fi);
|
|
|
|
if (v5up->pstn.proto)
|
|
v5x_le_pstn_destroy(v5up->pstn.proto);
|
|
|
|
llist_del(&v5up->list);
|
|
|
|
talloc_free(v5up);
|
|
}
|
|
|
|
void v5x_echo_reset(struct v5x_echo_proc *ep, int enable)
|
|
{
|
|
ep->enabled = enable;
|
|
answertone_reset(&ep->at);
|
|
ep->tx_buffer_in = 0;
|
|
ep->tx_buffer_out = 0;
|
|
if (ep->ec)
|
|
echo_can_flush(ep->ec);
|
|
}
|
|
|
|
struct v5x_user_port *v5x_user_port_find(struct v5x_interface *v5if, uint16_t nr, bool is_isdn)
|
|
{
|
|
struct v5x_user_port *v5up;
|
|
|
|
llist_for_each_entry(v5up, &v5if->user_ports, list) {
|
|
if (v5up->nr == nr && is_isdn == (v5up->type == V5X_USER_TYPE_ISDN))
|
|
return v5up;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int v5x_link_count(struct v5x_interface *v5if)
|
|
{
|
|
struct v5x_link *v5l;
|
|
int count = 0;
|
|
|
|
llist_for_each_entry(v5l, &v5if->links, list)
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
struct v5x_link *v5x_link_find_id(struct v5x_interface *v5if, uint8_t id)
|
|
{
|
|
struct v5x_link *v5l;
|
|
|
|
llist_for_each_entry(v5l, &v5if->links, list) {
|
|
if (v5l->id == id)
|
|
return v5l;
|
|
}
|
|
return NULL;
|
|
}
|