/* OpenBSC minimal LAPD implementation */ /* (C) 2009 by oystein@homelien.no * (C) 2009 by Holger Hans Peter Freyther * (C) 2010 by Digium and Matthew Fredrickson * (C) 2011 by Harald Welte * (C) 2011 by Andreas Eversberg * * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "internal.h" #include #include #include #include #include #include #include #include #include #include #define LAPD_ADDR2(sapi, cr) ((((sapi) & 0x3f) << 2) | (((cr) & 0x1) << 1)) #define LAPD_ADDR3(tei) ((((tei) & 0x7f) << 1) | 0x1) #define LAPD_ADDR_SAPI(addr) ((addr) >> 2) #define LAPD_ADDR_CR(addr) (((addr) >> 1) & 0x1) #define LAPD_ADDR_EA(addr) ((addr) & 0x1) #define LAPD_ADDR_TEI(addr) ((addr) >> 1) #define LAPD_CTRL_I4(ns) (((ns) & 0x7f) << 1) #define LAPD_CTRL_I5(nr, p) ((((nr) & 0x7f) << 1) | ((p) & 0x1)) #define LAPD_CTRL_S4(s) ((((s) & 0x3) << 2) | 0x1) #define LAPD_CTRL_S5(nr, p) ((((nr) & 0x7f) << 1) | ((p) & 0x1)) #define LAPD_CTRL_U4(u, p) ((((u) & 0x1c) << (5-2)) | (((p) & 0x1) << 4) | (((u) & 0x3) << 2) | 0x3) #define LAPD_CTRL_is_I(ctrl) (((ctrl) & 0x1) == 0) #define LAPD_CTRL_is_S(ctrl) (((ctrl) & 0x3) == 1) #define LAPD_CTRL_is_U(ctrl) (((ctrl) & 0x3) == 3) #define LAPD_CTRL_U_BITS(ctrl) ((((ctrl) & 0xC) >> 2) | ((ctrl) & 0xE0) >> 3) #define LAPD_CTRL_U_PF(ctrl) (((ctrl) >> 4) & 0x1) #define LAPD_CTRL_S_BITS(ctrl) (((ctrl) & 0xC) >> 2) #define LAPD_CTRL_S_PF(ctrl) (ctrl & 0x1) #define LAPD_CTRL_I_Ns(ctrl) (((ctrl) & 0xFE) >> 1) #define LAPD_CTRL_I_P(ctrl) (ctrl & 0x1) #define LAPD_CTRL_Nr(ctrl) (((ctrl) & 0xFE) >> 1) #define LAPD_LEN(len) ((len << 2) | 0x1) #define LAPD_EL 0x1 #define LAPD_SET_K(n, o) {n,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o} #define LOGLI(li, level, fmt, args ...) \ LOGP(DLLAPD, level, "(%s): " fmt, (li)->name, ## args) #define LOGTEI(teip, level, fmt, args ...) \ LOGP(DLLAPD, level, "(%s-T%u): " fmt, (teip)->li->name, (teip)->tei, ## args) #define LOGSAP(sap, level, fmt, args ...) \ LOGP(DLLAPD, level, "(%s): " fmt, (sap)->dl.name, ## args) #define DLSAP_MSGB_SIZE 128 #define DLSAP_MSGB_HEADROOM 56 const struct lapd_profile lapd_profile_isdn = { .k = LAPD_SET_K(7,7), .n200 = 3, .n201 = 260, .n202 = 3, .t200_sec = 1, .t200_usec = 0, .t201_sec = 1, .t201_usec = 0, .t202_sec = 2, .t202_usec = 0, .t203_sec = 10, .t203_usec = 0, .short_address = 0 }; const struct lapd_profile lapd_profile_abis = { .k = LAPD_SET_K(2,1), .n200 = 3, .n201 = 260, .n202 = 0, /* infinite */ .t200_sec = 0, .t200_usec = 240000, .t201_sec = 1, .t201_usec = 0, .t202_sec = 2, .t202_usec = 0, .t203_sec = 10, .t203_usec = 0, .short_address = 0 }; /* Ericssons OM2000 lapd dialect requires a sabm frame retransmission * timeout of exactly 300 msek. Shorter or longer retransmission will * cause the link establishment to fail permanently. Since the BTS is * periodically scanning through all timeslots to find the timeslot * where the bsc is transmitting its sabm frames the normal maximum * retransmission (n200) of 3 is not enough. In order not to miss * the bts, n200 has been increased to 50, which is an educated * guess. */ const struct lapd_profile lapd_profile_abis_ericsson = { .k = LAPD_SET_K(2,1), .n200 = 50, .n201 = 260, .n202 = 0, /* infinite */ .t200_sec = 0, .t200_usec = 300000, .t201_sec = 1, .t201_usec = 0, .t202_sec = 2, .t202_usec = 0, .t203_sec = 10, .t203_usec = 0, .short_address = 0 }; const struct lapd_profile lapd_profile_sat = { .k = LAPD_SET_K(15,15), .n200 = 5, .n201 = 260, .n202 = 5, .t200_sec = 2, .t200_usec = 400000, .t201_sec = 2, .t201_usec = 400000, .t202_sec = 2, .t202_usec = 400000, .t203_sec = 20, .t203_usec = 0, .short_address = 1 }; typedef enum { LAPD_TEI_NONE = 0, LAPD_TEI_ASSIGNED, LAPD_TEI_ACTIVE, } lapd_tei_state; const char *lapd_tei_states[] = { "NONE", "ASSIGNED", "ACTIVE", }; /* Structure representing an allocated TEI within a LAPD instance. */ struct lapd_tei { struct llist_head list; struct lapd_instance *li; uint8_t tei; lapd_tei_state state; struct llist_head sap_list; }; /* Structure representing a SAP within a TEI. It includes exactly one datalink * instance. */ struct lapd_sap { struct llist_head list; struct lapd_tei *tei; uint8_t sapi; struct lapd_datalink dl; }; /* Resolve TEI structure from given numeric TEI */ static struct lapd_tei *teip_from_tei(struct lapd_instance *li, uint8_t tei) { struct lapd_tei *lt; llist_for_each_entry(lt, &li->tei_list, list) { if (lt->tei == tei) return lt; } return NULL; }; /* Change state of TEI */ static void lapd_tei_set_state(struct lapd_tei *teip, int newstate) { LOGTEI(teip, LOGL_INFO, "LAPD state change on TEI %d: %s -> %s\n", teip->tei, lapd_tei_states[teip->state], lapd_tei_states[newstate]); teip->state = newstate; }; /* Allocate a new TEI */ struct lapd_tei *lapd_tei_alloc(struct lapd_instance *li, uint8_t tei) { struct lapd_tei *teip; teip = talloc_zero(li, struct lapd_tei); if (!teip) return NULL; teip->li = li; teip->tei = tei; llist_add(&teip->list, &li->tei_list); INIT_LLIST_HEAD(&teip->sap_list); lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED); return teip; } /* Find a SAP within a given TEI */ static struct lapd_sap *lapd_sap_find(struct lapd_tei *teip, uint8_t sapi) { struct lapd_sap *sap; llist_for_each_entry(sap, &teip->sap_list, list) { if (sap->sapi == sapi) return sap; } return NULL; } static int send_ph_data_req(struct lapd_msg_ctx *lctx, struct msgb *msg); static int send_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx); /* Allocate a new SAP within a given TEI */ static struct lapd_sap *lapd_sap_alloc(struct lapd_tei *teip, uint8_t sapi) { struct lapd_sap *sap; struct lapd_datalink *dl; struct lapd_instance *li = teip->li; struct lapd_profile *profile; char name[256]; int k; snprintf(name, sizeof(name), "%s-T%u-S%u", li->name, teip->tei, sapi); sap = talloc_zero(teip, struct lapd_sap); if (!sap) return NULL; LOGP(DLLAPD, LOGL_NOTICE, "(%s): LAPD Allocating SAP for SAPI=%u / TEI=%u (dl=%p, sap=%p)\n", name, sapi, teip->tei, &sap->dl, sap); sap->sapi = sapi; sap->tei = teip; dl = &sap->dl; profile = &li->profile; k = profile->k[sapi & 0x3f]; LOGP(DLLAPD, LOGL_NOTICE, "(%s): k=%d N200=%d N201=%d T200=%d.%d T203=%d.%d\n", name, k, profile->n200, profile->n201, profile->t200_sec, profile->t200_usec, profile->t203_sec, profile->t203_usec); lapd_dl_init2(dl, k, 128, profile->n201, name); dl->use_sabme = 1; /* use SABME instead of SABM (GSM) */ dl->send_ph_data_req = send_ph_data_req; dl->send_dlsap = send_dlsap; dl->n200 = profile->n200; dl->n200_est_rel = profile->n200; dl->t200_sec = profile->t200_sec; dl->t200_usec = profile->t200_usec; dl->t203_sec = profile->t203_sec; dl->t203_usec = profile->t203_usec; dl->lctx.dl = &sap->dl; dl->lctx.sapi = sapi; dl->lctx.tei = teip->tei; dl->lctx.n201 = profile->n201; lapd_set_mode(&sap->dl, (teip->li->network_side) ? LAPD_MODE_NETWORK : LAPD_MODE_USER); llist_add(&sap->list, &teip->sap_list); return sap; } /* Free SAP instance, including the datalink */ static void lapd_sap_free(struct lapd_sap *sap) { LOGSAP(sap, LOGL_NOTICE, "LAPD Freeing SAP for SAPI=%u / TEI=%u (dl=%p, sap=%p)\n", sap->sapi, sap->tei->tei, &sap->dl, sap); /* free datalink structures and timers */ lapd_dl_exit(&sap->dl); llist_del(&sap->list); talloc_free(sap); } /* Free TEI instance */ static void lapd_tei_free(struct lapd_tei *teip) { struct lapd_sap *sap, *sap2; llist_for_each_entry_safe(sap, sap2, &teip->sap_list, list) { lapd_sap_free(sap); } llist_del(&teip->list); talloc_free(teip); } /* Input function into TEI manager */ static int lapd_tei_receive(struct lapd_instance *li, uint8_t *data, int len) { uint8_t entity; uint8_t ref; uint8_t mt; uint8_t action; uint8_t e; uint8_t resp[8]; struct lapd_tei *teip; struct msgb *msg; if (len < 5) { LOGLI(li, LOGL_ERROR, "LAPD TEIMGR frame receive len %d < 5" ", ignoring\n", len); return -EINVAL; }; entity = data[0]; ref = data[1]; mt = data[3]; action = data[4] >> 1; e = data[4] & 1; DEBUGP(DLLAPD, "LAPD TEIMGR: entity %x, ref %x, mt %x, action %x, " "e %x\n", entity, ref, mt, action, e); switch (mt) { case 0x01: /* IDENTITY REQUEST */ DEBUGP(DLLAPD, "LAPD TEIMGR: identity request for TEI %u\n", action); teip = teip_from_tei(li, action); if (!teip) { LOGLI(li, LOGL_INFO, "TEI MGR: New TEI %u\n", action); teip = lapd_tei_alloc(li, action); if (!teip) return -ENOMEM; } /* Send ACCEPT */ memmove(resp, "\xfe\xff\x03\x0f\x00\x00\x02\x00", 8); resp[7] = (action << 1) | 1; msg = msgb_alloc_headroom(DLSAP_MSGB_SIZE, DLSAP_MSGB_HEADROOM, "DL EST"); msg->l2h = msgb_push(msg, 8); memcpy(msg->l2h, resp, 8); /* write to PCAP file, if enabled. */ osmo_pcap_lapd_write(li->pcap_fd, OSMO_LAPD_PCAP_OUTPUT, msg); LOGTEI(teip, LOGL_DEBUG, "TX: %s\n", osmo_hexdump(msg->data, msg->len)); li->transmit_cb(msg, li->transmit_cbdata); if (teip->state == LAPD_TEI_NONE) lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED); break; default: LOGLI(li, LOGL_NOTICE, "LAPD TEIMGR: unknown mt %x action %x\n", mt, action); break; }; return 0; } /* General input function for any data received for this LAPD instance */ int lapd_receive(struct lapd_instance *li, struct msgb *msg, int *error) { int i; struct lapd_msg_ctx lctx; int rc; struct lapd_sap *sap; struct lapd_tei *teip; /* write to PCAP file, if enabled. */ osmo_pcap_lapd_write(li->pcap_fd, OSMO_LAPD_PCAP_INPUT, msg); LOGLI(li, LOGL_DEBUG, "RX: %s\n", osmo_hexdump(msg->data, msg->len)); if (msg->len < 2) { LOGLI(li, LOGL_ERROR, "LAPD frame receive len %d < 2, ignoring\n", msg->len); *error = LAPD_ERR_BAD_LEN; return -EINVAL; }; msg->l2h = msg->data; memset(&lctx, 0, sizeof(lctx)); i = 0; /* adress field */ lctx.sapi = LAPD_ADDR_SAPI(msg->l2h[i]); lctx.cr = LAPD_ADDR_CR(msg->l2h[i]); lctx.lpd = 0; if (!LAPD_ADDR_EA(msg->l2h[i])) { if (msg->len < 3) { LOGLI(li, LOGL_ERROR, "LAPD frame with TEI receive " "len %d < 3, ignoring\n", msg->len); *error = LAPD_ERR_BAD_LEN; return -EINVAL; }; i++; lctx.tei = LAPD_ADDR_TEI(msg->l2h[i]); } i++; /* control field */ if (LAPD_CTRL_is_I(msg->l2h[i])) { lctx.format = LAPD_FORM_I; lctx.n_send = LAPD_CTRL_I_Ns(msg->l2h[i]); i++; if (msg->len < 3 && i == 2) { LOGLI(li, LOGL_ERROR, "LAPD I frame without TEI " "receive len %d < 3, ignoring\n", msg->len); *error = LAPD_ERR_BAD_LEN; return -EINVAL; }; if (msg->len < 4 && i == 3) { LOGLI(li, LOGL_ERROR, "LAPD I frame with TEI " "receive len %d < 4, ignoring\n", msg->len); *error = LAPD_ERR_BAD_LEN; return -EINVAL; }; lctx.n_recv = LAPD_CTRL_Nr(msg->l2h[i]); lctx.p_f = LAPD_CTRL_I_P(msg->l2h[i]); } else if (LAPD_CTRL_is_S(msg->l2h[i])) { lctx.format = LAPD_FORM_S; lctx.s_u = LAPD_CTRL_S_BITS(msg->l2h[i]); i++; if (msg->len < 3 && i == 2) { LOGLI(li, LOGL_ERROR, "LAPD S frame without TEI " "receive len %d < 3, ignoring\n", msg->len); *error = LAPD_ERR_BAD_LEN; return -EINVAL; }; if (msg->len < 4 && i == 3) { LOGLI(li, LOGL_ERROR, "LAPD S frame with TEI " "receive len %d < 4, ignoring\n", msg->len); *error = LAPD_ERR_BAD_LEN; return -EINVAL; }; lctx.n_recv = LAPD_CTRL_Nr(msg->l2h[i]); lctx.p_f = LAPD_CTRL_S_PF(msg->l2h[i]); } else if (LAPD_CTRL_is_U(msg->l2h[i])) { lctx.format = LAPD_FORM_U; lctx.s_u = LAPD_CTRL_U_BITS(msg->l2h[i]); lctx.p_f = LAPD_CTRL_U_PF(msg->l2h[i]); } else lctx.format = LAPD_FORM_UKN; i++; /* length */ msg->l3h = msg->l2h + i; msgb_pull(msg, i); lctx.length = msg->len; /* perform TEI assignment, if received */ if (lctx.tei == 127) { rc = lapd_tei_receive(li, msg->data, msg->len); msgb_free(msg); return rc; } /* resolve TEI and SAPI */ teip = teip_from_tei(li, lctx.tei); if (!teip) { LOGLI(li, LOGL_NOTICE, "LAPD Unknown TEI %u\n", lctx.tei); *error = LAPD_ERR_UNKNOWN_TEI; msgb_free(msg); return -EINVAL; } sap = lapd_sap_find(teip, lctx.sapi); if (!sap) { LOGTEI(teip, LOGL_INFO, "LAPD No SAP for TEI=%u / SAPI=%u, " "allocating\n", lctx.tei, lctx.sapi); sap = lapd_sap_alloc(teip, lctx.sapi); if (!sap) { *error = LAPD_ERR_NO_MEM; msgb_free(msg); return -ENOMEM; } } lctx.dl = &sap->dl; lctx.n201 = lctx.dl->maxf; if (msg->len > lctx.n201) { LOGSAP(sap, LOGL_ERROR, "message len %d > N201(%d) " "(discarding)\n", msg->len, lctx.n201); msgb_free(msg); *error = LAPD_ERR_BAD_LEN; return -EINVAL; } /* send to LAPD */ return lapd_ph_data_ind(msg, &lctx); } /* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */ int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi) { struct lapd_sap *sap; struct lapd_tei *teip; struct osmo_dlsap_prim dp; struct msgb *msg; teip = teip_from_tei(li, tei); if (!teip) teip = lapd_tei_alloc(li, tei); sap = lapd_sap_find(teip, sapi); if (sap) return -EEXIST; sap = lapd_sap_alloc(teip, sapi); if (!sap) return -ENOMEM; LOGSAP(sap, LOGL_NOTICE, "LAPD DL-ESTABLISH request TEI=%d SAPI=%d\n", tei, sapi); /* prepare prim */ msg = msgb_alloc_headroom(DLSAP_MSGB_SIZE, DLSAP_MSGB_HEADROOM, "DL EST"); msg->l3h = msg->data; osmo_prim_init(&dp.oph, 0, PRIM_DL_EST, PRIM_OP_REQUEST, msg); /* send to L2 */ return lapd_recv_dlsap(&dp, &sap->dl.lctx); } /* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */ int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi) { struct lapd_tei *teip; struct lapd_sap *sap; struct osmo_dlsap_prim dp; struct msgb *msg; teip = teip_from_tei(li, tei); if (!teip) return -ENODEV; sap = lapd_sap_find(teip, sapi); if (!sap) return -ENODEV; LOGSAP(sap, LOGL_NOTICE, "LAPD DL-RELEASE request TEI=%d SAPI=%d\n", tei, sapi); /* prepare prim */ msg = msgb_alloc_headroom(DLSAP_MSGB_SIZE, DLSAP_MSGB_HEADROOM, "DL REL"); msg->l3h = msg->data; osmo_prim_init(&dp.oph, 0, PRIM_DL_REL, PRIM_OP_REQUEST, msg); /* send to L2 */ return lapd_recv_dlsap(&dp, &sap->dl.lctx); } /* Transmit Data (DL-DATA request) on the given LAPD Instance / TEI / SAPI */ void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi, struct msgb *msg) { struct lapd_tei *teip = teip_from_tei(li, tei); struct lapd_sap *sap; struct osmo_dlsap_prim dp; if (!teip) { LOGLI(li, LOGL_ERROR, "LAPD Cannot transmit on non-existing TEI %u\n", tei); msgb_free(msg); return; } sap = lapd_sap_find(teip, sapi); if (!sap) { LOGTEI(teip, LOGL_INFO, "LAPD Tx on unknown SAPI=%u in TEI=%u\n", sapi, tei); msgb_free(msg); return; } /* prepare prim */ msg->l3h = msg->data; osmo_prim_init(&dp.oph, 0, PRIM_DL_DATA, PRIM_OP_REQUEST, msg); /* send to L2 */ lapd_recv_dlsap(&dp, &sap->dl.lctx); }; static int send_ph_data_req(struct lapd_msg_ctx *lctx, struct msgb *msg) { struct lapd_datalink *dl = lctx->dl; struct lapd_sap *sap = container_of(dl, struct lapd_sap, dl); struct lapd_instance *li = sap->tei->li; int format = lctx->format; int addr_len; /* control field */ switch (format) { case LAPD_FORM_I: msg->l2h = msgb_push(msg, 2); msg->l2h[0] = LAPD_CTRL_I4(lctx->n_send); msg->l2h[1] = LAPD_CTRL_I5(lctx->n_recv, lctx->p_f); break; case LAPD_FORM_S: msg->l2h = msgb_push(msg, 2); msg->l2h[0] = LAPD_CTRL_S4(lctx->s_u); msg->l2h[1] = LAPD_CTRL_S5(lctx->n_recv, lctx->p_f); break; case LAPD_FORM_U: msg->l2h = msgb_push(msg, 1); msg->l2h[0] = LAPD_CTRL_U4(lctx->s_u, lctx->p_f); break; default: msgb_free(msg); return -EINVAL; } /* address field */ if (li->profile.short_address && lctx->tei == 0) addr_len = 1; else addr_len = 2; msg->l2h = msgb_push(msg, addr_len); msg->l2h[0] = LAPD_ADDR2(lctx->sapi, lctx->cr); if (addr_len == 1) msg->l2h[0] |= 0x1; else msg->l2h[1] = LAPD_ADDR3(lctx->tei); /* write to PCAP file, if enabled. */ osmo_pcap_lapd_write(li->pcap_fd, OSMO_LAPD_PCAP_OUTPUT, msg); /* forward frame to L1 */ LOGDL(dl, LOGL_DEBUG, "TX: %s\n", osmo_hexdump(msg->data, msg->len)); li->transmit_cb(msg, li->transmit_cbdata); return 0; } /* A DL-SAP message is received from datalink instance and forwarded to L3 */ static int send_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) { struct lapd_datalink *dl = lctx->dl; struct lapd_sap *sap = container_of(dl, struct lapd_sap, dl); struct lapd_instance *li; uint8_t tei, sapi; char *op = (dp->oph.operation == PRIM_OP_INDICATION) ? "indication" : "confirm"; li = sap->tei->li; tei = lctx->tei; sapi = lctx->sapi; switch (dp->oph.primitive) { case PRIM_DL_EST: LOGDL(dl, LOGL_NOTICE, "LAPD DL-ESTABLISH %s TEI=%d " "SAPI=%d\n", op, lctx->tei, lctx->sapi); break; case PRIM_DL_REL: LOGDL(dl, LOGL_NOTICE, "LAPD DL-RELEASE %s TEI=%d " "SAPI=%d\n", op, lctx->tei, lctx->sapi); lapd_sap_free(sap); /* note: sap and dl is now gone, don't use it anymore */ break; default: ; } li->receive_cb(dp, tei, sapi, li->receive_cbdata); return 0; } /* Allocate a new LAPD instance */ struct lapd_instance *lapd_instance_alloc2(int network_side, void (*tx_cb)(struct msgb *msg, void *cbdata), void *tx_cbdata, void (*rx_cb)(struct osmo_dlsap_prim *odp, uint8_t tei, uint8_t sapi, void *rx_cbdata), void *rx_cbdata, const struct lapd_profile *profile, const char *name) { struct lapd_instance *li; li = talloc_zero(NULL, struct lapd_instance); if (!li) return NULL; li->network_side = network_side; li->transmit_cb = tx_cb; li->transmit_cbdata = tx_cbdata; li->receive_cb = rx_cb; li->receive_cbdata = rx_cbdata; li->pcap_fd = -1; li->name = talloc_strdup(li, name); memcpy(&li->profile, profile, sizeof(li->profile)); INIT_LLIST_HEAD(&li->tei_list); return li; } struct lapd_instance *lapd_instance_alloc(int network_side, void (*tx_cb)(struct msgb *msg, void *cbdata), void *tx_cbdata, void (*rx_cb)(struct osmo_dlsap_prim *odp, uint8_t tei, uint8_t sapi, void *rx_cbdata), void *rx_cbdata, const struct lapd_profile *profile) { return lapd_instance_alloc2(network_side, tx_cbdata, tx_cb, rx_cb, rx_cbdata, profile, NULL); } /* Change lapd-profile on the fly (use with caution!) */ void lapd_instance_set_profile(struct lapd_instance *li, const struct lapd_profile *profile) { memcpy(&li->profile, profile, sizeof(li->profile)); } void lapd_instance_free(struct lapd_instance *li) { struct lapd_tei *teip, *teip2; /* Free all TEI instances */ llist_for_each_entry_safe(teip, teip2, &li->tei_list, list) { lapd_tei_free(teip); } talloc_free(li); }