#include #include #include #include #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; v5l->e1_line = -1; /* 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; 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->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->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; } struct v5x_link *v5x_link_find_e1_line(struct v5x_interface *v5if, uint8_t e1_line) { struct v5x_link *v5l; llist_for_each_entry(v5l, &v5if->links, list) { if (v5l->e1_line == e1_line) return v5l; } return NULL; }