dect
/
libdect
Archived
13
0
Fork 0
This repository has been archived on 2022-02-17. You can view files and clone it, but cannot push or open issues or pull requests.
libdect/src/lce.c

791 lines
19 KiB
C

/*
* DECT Link Control Entity (LCE)
*
* Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/byteorder/little_endian.h>
#include <linux/dect.h>
#include <asm/byteorder.h>
#include <libdect.h>
#include <identities.h>
#include <utils.h>
#include <s_fmt.h>
#include <b_fmt.h>
#include <lce.h>
#include <ss.h>
static const struct dect_sfmt_ie_desc lce_page_response_msg[] = {
DECT_SFMT_IE(S_VL_IE_PORTABLE_IDENTITY, IE_NONE, IE_MANDATORY, 0),
DECT_SFMT_IE(S_VL_IE_FIXED_IDENTITY, IE_NONE, IE_OPTIONAL, 0),
DECT_SFMT_IE(S_VL_IE_NWK_ASSIGNED_IDENTITY, IE_NONE, IE_OPTIONAL, 0),
DECT_SFMT_IE(S_VL_IE_CIPHER_INFO, IE_NONE, IE_OPTIONAL, 0),
DECT_SFMT_IE(S_VL_IE_ESCAPE_TO_PROPRIETARY, IE_NONE, IE_OPTIONAL, 0),
DECT_SFMT_IE_END_MSG
};
static const struct dect_sfmt_ie_desc lce_page_reject_msg[] = {
DECT_SFMT_IE(S_VL_IE_PORTABLE_IDENTITY, IE_MANDATORY, IE_NONE, 0),
DECT_SFMT_IE(S_VL_IE_FIXED_IDENTITY, IE_OPTIONAL, IE_NONE, 0),
DECT_SFMT_IE(S_VL_IE_REJECT_REASON, IE_OPTIONAL, IE_NONE, 0),
DECT_SFMT_IE(S_VL_IE_ESCAPE_TO_PROPRIETARY, IE_OPTIONAL, IE_NONE, 0),
DECT_SFMT_IE_END_MSG
};
static const struct dect_nwk_protocol *protocols[DECT_S_PD_MAX + 1];
void dect_lce_register_protocol(const struct dect_nwk_protocol *protocol)
{
protocols[protocol->pd] = protocol;
}
struct dect_msg_buf *dect_mbuf_alloc(const struct dect_handle *dh)
{
struct dect_msg_buf *mb;
mb = dect_malloc(dh, sizeof(*mb));
if (mb == NULL)
return NULL;
memset(mb->head, 0, sizeof(mb->head));
mb->data = mb->head;
mb->len = 0;
mb->type = 0;
return mb;
}
static ssize_t dect_mbuf_rcv(const struct dect_fd *dfd, struct dect_msg_buf *mb)
{
ssize_t len;
memset(mb, 0, sizeof(*mb));
mb->data = mb->head;
len = recv(dfd->fd, mb->data, sizeof(mb->head), 0);
if (len < 0)
return len;
mb->len = len;
return len;
}
#if 0
/*
* Location Table
*/
static struct dect_lte *dect_lte_get_by_ipui(const struct dect_handle *dh,
const struct dect_ipui *ipui)
{
struct dect_lte *lte;
list_for_each_entry(lte, &dh->ldb.entries, list) {
if (!dect_ipui_cmp(&lte->ipui, ipui))
return lte;
}
return NULL;
}
static struct dect_lte *dect_lte_alloc(const struct dect_handle *dh,
const struct dect_ipui *ipui)
{
struct dect_lte *lte;
lte = dect_malloc(dh, sizeof(*lte));
if (lte == NULL)
return NULL;
memcpy(&lte->ipui, ipui, sizeof(lte->ipui));
return lte;
}
#endif
/*
* Paging
*/
static int dect_lce_broadcast(const struct dect_handle *dh,
const uint8_t *msg, size_t len)
{
ssize_t size;
dect_hexdump("BROADCAST", msg, len);
size = send(dh->b_sap->fd, msg, len, 0);
assert(size == (ssize_t)len);
return 0;
}
int dect_lce_group_ring(struct dect_handle *dh, enum dect_ring_patterns pattern)
{
struct dect_short_page_msg msg;
uint16_t page;
msg.hdr = DECT_LCE_PAGE_W_FLAG;
msg.hdr |= DECT_LCE_PAGE_GENERAL_VOICE;
page = pattern << DECT_LCE_SHORT_PAGE_RING_PATTERN_SHIFT;
page = 0;
page |= DECT_TPUI_CBI & DECT_LCE_SHORT_PAGE_TPUI_MASK;
msg.information = __cpu_to_be16(page);
return dect_lce_broadcast(dh, &msg.hdr, sizeof(msg));
}
static int dect_lce_page(const struct dect_handle *dh,
const struct dect_ipui *ipui)
{
struct dect_short_page_msg msg;
struct dect_tpui tpui;
uint16_t page;
dect_default_individual_tpui(&tpui, ipui);
msg.hdr = DECT_LCE_PAGE_GENERAL_VOICE;
page = tpui.tpui & DECT_LCE_SHORT_PAGE_TPUI_MASK;
msg.information = __cpu_to_be16(page);
return dect_lce_broadcast(dh, &msg.hdr, sizeof(msg));
}
static void dect_lce_rcv_short_page(struct dect_handle *dh,
struct dect_msg_buf *mb)
{
struct dect_short_page_msg *msg = (void *)mb->data;
uint8_t hdr;
bool w;
w = msg->hdr & DECT_LCE_PAGE_W_FLAG;
hdr = msg->hdr & DECT_LCE_PAGE_HDR_MASK;
dect_debug("short page: w=%u hdr=%u information=%x\n",
w, hdr, __be16_to_cpu(msg->information));
}
static void dect_lce_bsap_event(struct dect_handle *dh, struct dect_fd *dfd,
uint32_t events)
{
struct dect_msg_buf _mb, *mb = &_mb;
if (dect_mbuf_rcv(dfd, mb) < 0)
return;
dect_mbuf_dump(mb, "BCAST RX");
switch (mb->len) {
case 3:
return dect_lce_rcv_short_page(dh, mb);
default:
break;
}
}
/*
* Data links
*/
#define ddl_debug(ddl, fmt, args...) \
dect_debug("link %d (%s): " fmt "\n", \
(ddl)->dfd ? (ddl)->dfd->fd : -1, \
ddl_states[(ddl)->state], ## args)
static const char *ddl_states[DECT_DATA_LINK_STATE_MAX + 1] = {
[DECT_DATA_LINK_RELEASED] = "RELEASED",
[DECT_DATA_LINK_ESTABLISHED] = "ESTABLISHED",
[DECT_DATA_LINK_ESTABLISH_PENDING] = "ESTABLISH_PENDING",
[DECT_DATA_LINK_RELEASE_PENDING] = "RELEASE_PENDING",
[DECT_DATA_LINK_SUSPENDED] = "SUSPENDED",
[DECT_DATA_LINK_SUSPEND_PENDING] = "SUSPEND_PENDING",
[DECT_DATA_LINK_RESUME_PENDING] = "RESUME_PENDING",
};
static struct dect_data_link *dect_ddl_get_by_ipui(const struct dect_handle *dh,
const struct dect_ipui *ipui)
{
struct dect_data_link *ddl;
list_for_each_entry(ddl, &dh->links, list) {
if (!dect_ipui_cmp(&ddl->ipui, ipui))
return ddl;
}
return NULL;
}
static struct dect_transaction *
dect_ddl_transaction_lookup(const struct dect_data_link *ddl, uint8_t pd, uint8_t tv)
{
struct dect_transaction *ta;
list_for_each_entry(ta, &ddl->transactions, list) {
if (ta->pd == pd && ta->tv == tv)
return ta;
}
return NULL;
}
static void dect_ddl_destroy(struct dect_handle *dh, struct dect_data_link *ddl)
{
struct dect_msg_buf *mb, *next;
ddl_debug(ddl, "destroy");
assert(ddl->sdu_timer == NULL);
assert(list_empty(&ddl->transactions));
list_del(&ddl->list);
list_for_each_entry_safe(mb, next, &ddl->msg_queue, list)
dect_free(dh, mb);
if (ddl->dfd != NULL) {
dect_unregister_fd(dh, ddl->dfd);
dect_close(dh, ddl->dfd);
}
dect_free(dh, ddl);
}
static struct dect_data_link *dect_ddl_alloc(const struct dect_handle *dh)
{
struct dect_data_link *ddl;
ddl = dect_zalloc(dh, sizeof(*ddl));
if (ddl == NULL)
return NULL;
ddl->state = DECT_DATA_LINK_RELEASED;
init_list_head(&ddl->list);
init_list_head(&ddl->transactions);
init_list_head(&ddl->msg_queue);
ddl_debug(ddl, "alloc");
return ddl;
}
static void dect_ddl_sdu_timer(struct dect_handle *dh, struct dect_timer *timer)
{
struct dect_data_link *ddl = timer->data;
ddl_debug(ddl, "SDU timer");
dect_free(dh, ddl->sdu_timer);
ddl->sdu_timer = NULL;
dect_ddl_destroy(dh, ddl);
}
static int dect_ddl_schedule_sdu_timer(const struct dect_handle *dh,
struct dect_data_link *ddl)
{
ddl->sdu_timer = dect_alloc_timer(dh);
if (ddl->sdu_timer == NULL)
return -1;
ddl->sdu_timer->data = ddl;
ddl->sdu_timer->callback = dect_ddl_sdu_timer;
dect_start_timer(dh, ddl->sdu_timer, DECT_DDL_ESTABLISH_SDU_TIMEOUT);
ddl_debug(ddl, "start SDU timer");
return 0;
}
static void dect_ddl_stop_sdu_timer(const struct dect_handle *dh,
struct dect_data_link *ddl)
{
ddl_debug(ddl, "stop SDU timer");
dect_stop_timer(dh, ddl->sdu_timer);
dect_free(dh, ddl->sdu_timer);
ddl->sdu_timer = NULL;
}
static int dect_send(const struct dect_handle *dh,
const struct dect_data_link *ddl,
struct dect_msg_buf *mb)
{
ssize_t len;
dect_mbuf_dump(mb, "TX");
len = send(ddl->dfd->fd, mb->data, mb->len, 0);
assert(len == (ssize_t)mb->len);
dect_free(dh, mb);
return len;
}
/**
* dect_send - Queue a S-Format message for transmission to the LCE
*
*/
int dect_lce_send(const struct dect_handle *dh,
const struct dect_transaction *ta,
const struct dect_sfmt_ie_desc *desc,
const struct dect_msg_common *msg, uint8_t type,
const char *prefix)
{
struct dect_data_link *ddl = ta->link;
struct dect_msg_buf *mb;
mb = dect_mbuf_alloc(dh);
if (mb == NULL)
return -1;
dect_mbuf_reserve(mb, DECT_S_HDR_SIZE);
dect_build_sfmt_msg(dh, desc, msg, mb);
if (ddl->sdu_timer != NULL)
dect_ddl_stop_sdu_timer(dh, ddl);
dect_mbuf_push(mb, DECT_S_HDR_SIZE);
mb->data[1] = type;
mb->data[0] = ta->pd;
mb->data[0] |= ta->tv << DECT_S_TI_TV_SHIFT;
if (ta->role == DECT_TRANSACTION_RESPONDER)
mb->data[0] |= DECT_S_TI_F_FLAG;
switch (ddl->state) {
case DECT_DATA_LINK_ESTABLISHED:
return dect_send(dh, ddl, mb);
case DECT_DATA_LINK_ESTABLISH_PENDING:
list_add_tail(&mb->list, &ddl->msg_queue);
return 0;
default:
BUG();
}
}
/**
* dect_ddl_establish - Establish an outgoing data link
*
*/
static void dect_lce_data_link_event(struct dect_handle *dh,
struct dect_fd *dfd, uint32_t events);
static struct dect_data_link *dect_ddl_establish(struct dect_handle *dh,
const struct dect_ipui *ipui)
{
struct dect_data_link *ddl;
//lte = dect_lte_get_by_ipui(dh, lte);
ddl = dect_ddl_alloc(dh);
if (ddl == NULL)
goto err1;
ddl->state = DECT_DATA_LINK_ESTABLISH_PENDING;
if (dh->mode == DECT_MODE_FP) {
memcpy(&ddl->ipui, ipui, sizeof(ddl->ipui));
dect_lce_page(dh, ipui);
} else {
ddl->dfd = dect_socket(dh, SOCK_SEQPACKET, DECT_S_SAP);
if (ddl->dfd == NULL)
goto err2;
ddl->dlei.dect_family = AF_DECT;
ddl->dlei.dect_ari = dect_build_ari(&dh->pari) >> 24;
ddl->dlei.dect_pmid = 0xe98a1;
ddl->dlei.dect_lln = 1;
ddl->dlei.dect_sapi = 0;
ddl->dfd->callback = dect_lce_data_link_event;
ddl->dfd->data = ddl;
if (dect_register_fd(dh, ddl->dfd, DECT_FD_WRITE) < 0)
goto err2;
if (connect(ddl->dfd->fd, (struct sockaddr *)&ddl->dlei,
sizeof(ddl->dlei)) < 0 && errno != EAGAIN)
perror("connect\n");
}
list_add_tail(&ddl->list, &dh->links);
return ddl;
err2:
dect_free(dh, ddl);
err1:
return NULL;
}
#if 0
int dect_send_reject(const struct dect_handle *dh,
const struct dect_transaction *ta,
enum dect_reject_reasons reason)
{
struct dect_ie_reject_reason reject_reason;
struct dect_lce_page_reject msg = {
.portable_identity = NULL,
.reject_reason = &reject_reason,
};
dect_send(dh, ta, mb);
}
#endif
static void dect_ddl_complete_direct_establish(struct dect_handle *dh,
struct dect_data_link *ddl)
{
struct dect_msg_buf *mb, *mb_next;
ddl->state = DECT_DATA_LINK_ESTABLISHED;
ddl_debug(ddl, "complete direct link establishment");
/* Send queued messages */
list_for_each_entry_safe(mb, mb_next, &ddl->msg_queue, list) {
list_del(&mb->list);
dect_send(dh, ddl, mb);
}
dect_unregister_fd(dh, ddl->dfd);
dect_register_fd(dh, ddl->dfd, DECT_FD_READ);
}
static void dect_ddl_complete_indirect_establish(struct dect_handle *dh,
struct dect_data_link *ddl,
struct dect_data_link *req)
{
struct dect_transaction *ta, *ta_next;
struct dect_msg_buf *mb, *mb_next;
ddl_debug(ddl, "complete indirect link establishment req %p", req);
/* Transfer transactions to the new link */
list_for_each_entry_safe(ta, ta_next, &req->transactions, list) {
ddl_debug(ta->link, "transfer transaction to link %p\n", ddl);
list_move_tail(&ta->list, &ddl->transactions);
ta->link = ddl;
}
/* Send queued messages */
list_for_each_entry_safe(mb, mb_next, &req->msg_queue, list) {
list_del(&mb->list);
dect_send(dh, ddl, mb);
}
/* Release pending link */
dect_ddl_destroy(dh, req);
}
static void dect_lce_rcv_page_response(struct dect_handle *dh,
const struct dect_transaction *ta,
struct dect_msg_buf *mb)
{
struct dect_lce_page_response msg;
struct dect_data_link *i, *req = NULL;
ddl_debug(ta->link, "LCE-PAGE-RESPONSE");
if (dect_parse_sfmt_msg(dh, lce_page_response_msg, &msg.common, mb) < 0)
return;
dect_debug("portable_identity: %p\n", msg.portable_identity);
dect_debug("fixed identity: %p\n", msg.fixed_identity);
dect_debug("nwk assigned identity: %p\n", msg.nwk_assigned_identity);
dect_debug("cipher info: %p\n", msg.cipher_info);
dect_debug("escape: %p\n", msg.escape_to_proprietary);
list_for_each_entry(i, &dh->links, list) {
if (dect_ipui_cmp(&i->ipui, &msg.portable_identity->ipui))
continue;
if (i->state != DECT_DATA_LINK_ESTABLISH_PENDING)
continue;
req = i;
break;
}
if (req != NULL)
dect_ddl_complete_indirect_establish(dh, ta->link, req);
else {
/* send page reject */
dect_ddl_destroy(dh, ta->link);
}
dect_msg_free(dh, lce_page_response_msg, &msg.common);
}
static void dect_lce_rcv_page_reject(struct dect_handle *dh,
struct dect_transaction *ta,
struct dect_msg_buf *mb)
{
struct dect_lce_page_reject msg;
ddl_debug(ta->link, "LCE-PAGE-REJECT");
if (dect_parse_sfmt_msg(dh, lce_page_reject_msg, &msg.common, mb) < 0)
return;
dect_msg_free(dh, lce_page_reject_msg, &msg.common);
}
static void dect_lce_rcv(struct dect_handle *dh, struct dect_transaction *ta,
struct dect_msg_buf *mb)
{
switch (mb->type) {
case DECT_LCE_PAGE_REJECT:
return dect_lce_rcv_page_reject(dh, ta, mb);
default:
ddl_debug(ta->link, "LCE: unknown message type %x", mb->type);
return;
}
}
static void dect_lce_open(struct dect_handle *dh,
const struct dect_transaction *ta,
struct dect_msg_buf *mb)
{
switch (mb->type) {
case DECT_LCE_PAGE_RESPONSE:
return dect_lce_rcv_page_response(dh, ta, mb);
default:
ddl_debug(ta->link, "LCE: unknown message type %x", mb->type);
return;
}
}
static const struct dect_nwk_protocol lce_protocol = {
.name = "Link Control",
.pd = DECT_S_PD_LCE,
.max_transactions = 1,
.open = dect_lce_open,
.rcv = dect_lce_rcv,
};
static void dect_ddl_rcv_msg(struct dect_handle *dh, struct dect_data_link *ddl)
{
struct dect_msg_buf _mb, *mb = &_mb;
struct dect_transaction *ta;
uint8_t pd, tv;
bool f;
if (ddl->sdu_timer != NULL)
dect_ddl_stop_sdu_timer(dh, ddl);
if (dect_mbuf_rcv(ddl->dfd, mb) < 0)
return;
dect_mbuf_dump(mb, "RX");
if (mb->len < DECT_S_HDR_SIZE)
return;
f = (mb->data[0] & DECT_S_TI_F_FLAG);
tv = (mb->data[0] & DECT_S_TI_TV_MASK) >> DECT_S_TI_TV_SHIFT;
pd = (mb->data[0] & DECT_S_PD_MASK);
mb->type = (mb->data[1] & DECT_S_PD_MSG_TYPE_MASK);
dect_mbuf_pull(mb, DECT_S_HDR_SIZE);
if (pd >= array_size(protocols) || protocols[pd] == NULL) {
ddl_debug(ddl, "unknown protocol %u\n", pd);
return;
}
if (tv == DECT_TV_CONNECTIONLESS)
return dect_clss_rcv(dh, mb);
ta = dect_ddl_transaction_lookup(ddl, pd, tv);
if (ta == NULL) {
struct dect_transaction req = {
.link = ddl,
.pd = pd,
.role = DECT_TRANSACTION_RESPONDER,
.tv = tv,
};
ddl_debug(ddl, "new transaction: protocol: %s F: %u TV: %u",
protocols[pd]->name, f, tv);
protocols[pd]->open(dh, &req, mb);
} else
protocols[pd]->rcv(dh, ta, mb);
}
static void dect_lce_data_link_event(struct dect_handle *dh,
struct dect_fd *dfd, uint32_t events)
{
struct dect_data_link *ddl = dfd->data;
if (events & DECT_FD_WRITE) {
switch (ddl->state) {
case DECT_DATA_LINK_ESTABLISH_PENDING:
dect_ddl_complete_direct_establish(dh, ddl);
break;
default:
break;
}
}
if (events & DECT_FD_READ) {
dect_ddl_rcv_msg(dh, ddl);
}
}
static int dect_transaction_alloc_tv(const struct dect_data_link *ddl,
const struct dect_nwk_protocol *protocol)
{
uint16_t tv;
for (tv = 0; tv < protocol->max_transactions; tv++) {
if (dect_ddl_transaction_lookup(ddl, protocol->pd, tv))
continue;
return tv;
}
return -1;
}
int dect_open_transaction(struct dect_handle *dh, struct dect_transaction *ta,
const struct dect_ipui *ipui)
{
struct dect_data_link *ddl;
int tv;
ddl = dect_ddl_get_by_ipui(dh, ipui);
if (ddl == NULL) {
ddl = dect_ddl_establish(dh, ipui);
if (ddl == NULL)
return -1;
}
ddl_debug(ddl, "open transaction");
tv = dect_transaction_alloc_tv(ddl, protocols[ta->pd]);
if (tv < 0)
return -1;
ta->link = ddl;
ta->role = DECT_TRANSACTION_INITIATOR;
ta->tv = tv;
list_add_tail(&ta->list, &ddl->transactions);
return 0;
}
void dect_confirm_transaction(struct dect_handle *dh, struct dect_transaction *ta,
const struct dect_transaction *req)
{
ta->link = req->link;
ta->tv = req->tv;
ta->role = req->role;
ta->pd = req->pd;
ddl_debug(req->link, "confirm transaction");
list_add_tail(&ta->list, &req->link->transactions);
}
void dect_close_transaction(struct dect_handle *dh, struct dect_transaction *ta)
{
struct dect_data_link *ddl = ta->link;
ddl_debug(ddl, "close transaction");
list_del(&ta->list);
list_for_each_entry(ta, &ddl->transactions, list)
dect_debug("\ttrans %p proto %u TV %u\n", ta, ta->pd, ta->tv);
if (!list_empty(&ddl->transactions))
return;
dect_ddl_destroy(dh, ddl);
}
void dect_transaction_get_ulei(struct sockaddr_dect_lu *addr,
const struct dect_transaction *ta)
{
struct dect_data_link *ddl = ta->link;
memset(addr, 0, sizeof(*addr));
addr->dect_family = AF_DECT;
addr->dect_ari = ddl->dlei.dect_ari;
addr->dect_pmid = ddl->dlei.dect_pmid;
addr->dect_lcn = ddl->dlei.dect_lcn;
}
static void dect_lce_ssap_listener_event(struct dect_handle *dh,
struct dect_fd *dfd, uint32_t events)
{
struct dect_data_link *ddl;
struct dect_fd *nfd;
ddl = dect_ddl_alloc(dh);
if (ddl == NULL)
goto err1;
nfd = dect_accept(dh, dfd, (struct sockaddr *)&ddl->dlei,
sizeof(ddl->dlei));
if (nfd == NULL)
goto err2;
ddl->dfd = nfd;
nfd->callback = dect_lce_data_link_event;
nfd->data = ddl;
if (dect_register_fd(dh, nfd, DECT_FD_READ) < 0)
goto err3;
ddl->state = DECT_DATA_LINK_ESTABLISHED;
if (dect_ddl_schedule_sdu_timer(dh, ddl) < 0)
goto err4;
list_add_tail(&ddl->list, &dh->links);
ddl_debug(ddl, "new link: PMID: %x LCN: %u LLN: %u SAPI: %u",
ddl->dlei.dect_pmid, ddl->dlei.dect_lcn,
ddl->dlei.dect_lln, ddl->dlei.dect_sapi);
return;
err4:
dect_unregister_fd(dh, nfd);
err3:
dect_close(dh, nfd);
err2:
dect_free(dh, ddl);
err1:
return;
}
int dect_lce_init(struct dect_handle *dh)
{
struct sockaddr_dect_ssap s_addr;
struct sockaddr_dect b_addr;
/* Open B-SAP socket */
dh->b_sap = dect_socket(dh, SOCK_DGRAM, DECT_B_SAP);
if (dh->b_sap == NULL)
goto err1;
b_addr.dect_family = AF_DECT;
b_addr.dect_index = dh->index;
if (bind(dh->b_sap->fd, (struct sockaddr *)&b_addr, sizeof(b_addr)) < 0)
goto err2;
dh->b_sap->callback = dect_lce_bsap_event;
if (dect_register_fd(dh, dh->b_sap, DECT_FD_READ) < 0)
goto err2;
/* Open S-SAP listener socket */
dh->s_sap = dect_socket(dh, SOCK_SEQPACKET, DECT_S_SAP);
if (dh->s_sap == NULL)
goto err3;
memset(&s_addr, 0, sizeof(s_addr));
s_addr.dect_family = AF_DECT;
s_addr.dect_lln = 1;
s_addr.dect_sapi = 0;
if (bind(dh->s_sap->fd, (struct sockaddr *)&s_addr, sizeof(s_addr)) < 0)
goto err4;
if (listen(dh->s_sap->fd, 10) < 0)
goto err4;
dh->s_sap->callback = dect_lce_ssap_listener_event;
if (dect_register_fd(dh, dh->s_sap, DECT_FD_READ) < 0)
goto err4;
protocols[DECT_S_PD_LCE] = &lce_protocol;
return 0;
err4:
dect_close(dh, dh->s_sap);
err3:
dect_unregister_fd(dh, dh->b_sap);
err2:
dect_close(dh, dh->b_sap);
err1:
return -1;
}
void dect_lce_exit(struct dect_handle *dh)
{
struct dect_data_link *ddl, *ddl_next;
struct dect_transaction *ta, *ta_next;
LIST_HEAD(transactions);
list_for_each_entry_safe(ddl, ddl_next, &dh->links, list) {
ddl_debug(ddl, "shutdown");
list_splice_init(&ddl->transactions, &transactions);
list_for_each_entry_safe(ta, ta_next, &transactions, list)
protocols[ta->pd]->shutdown(dh, ta);
}
dect_unregister_fd(dh, dh->s_sap);
dect_close(dh, dh->s_sap);
dect_unregister_fd(dh, dh->b_sap);
dect_close(dh, dh->b_sap);
}