2015 lines
52 KiB
C
2015 lines
52 KiB
C
/*
|
|
* DECT Link Control Entity (LCE)
|
|
*
|
|
* Copyright (c) 2009-2010 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.
|
|
*/
|
|
|
|
/**
|
|
* @defgroup lce Link Control Entity
|
|
* @{
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.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 <timer.h>
|
|
#include <io.h>
|
|
#include <s_fmt.h>
|
|
#include <b_fmt.h>
|
|
#include <clms.h>
|
|
#include <lce.h>
|
|
#include <cc.h>
|
|
#include <mm.h>
|
|
#include <ss.h>
|
|
#include <dect/auth.h>
|
|
|
|
static DECT_SFMT_MSG_DESC(lce_page_response,
|
|
DECT_SFMT_IE(DECT_IE_PORTABLE_IDENTITY, IE_NONE, IE_MANDATORY, 0),
|
|
DECT_SFMT_IE(DECT_IE_FIXED_IDENTITY, IE_NONE, IE_OPTIONAL, 0),
|
|
DECT_SFMT_IE(DECT_IE_NWK_ASSIGNED_IDENTITY, IE_NONE, IE_OPTIONAL, 0),
|
|
DECT_SFMT_IE(DECT_IE_CIPHER_INFO, IE_NONE, IE_OPTIONAL, 0),
|
|
DECT_SFMT_IE(DECT_IE_ESCAPE_TO_PROPRIETARY, IE_NONE, IE_OPTIONAL, 0),
|
|
DECT_SFMT_IE_END_MSG
|
|
);
|
|
|
|
static DECT_SFMT_MSG_DESC(lce_page_reject,
|
|
DECT_SFMT_IE(DECT_IE_PORTABLE_IDENTITY, IE_MANDATORY, IE_NONE, 0),
|
|
DECT_SFMT_IE(DECT_IE_FIXED_IDENTITY, IE_OPTIONAL, IE_NONE, 0),
|
|
DECT_SFMT_IE(DECT_IE_REJECT_REASON, IE_OPTIONAL, IE_NONE, 0),
|
|
DECT_SFMT_IE(DECT_IE_ESCAPE_TO_PROPRIETARY, IE_OPTIONAL, IE_NONE, 0),
|
|
DECT_SFMT_IE_END_MSG
|
|
);
|
|
|
|
static const struct dect_nwk_protocol *protocols[DECT_PD_MAX + 1];
|
|
|
|
#define lce_debug(fmt, args...) \
|
|
dect_debug(DECT_DEBUG_LCE, "LCE: " fmt, ## args)
|
|
|
|
void dect_lce_register_protocol(const struct dect_nwk_protocol *protocol)
|
|
{
|
|
protocols[protocol->pd] = protocol;
|
|
lce_debug("registered protocol %u (%s)\n",
|
|
protocol->pd, protocol->name);
|
|
}
|
|
|
|
/**
|
|
* Allocate a libdect message buffer
|
|
*
|
|
* @param dh libdect DECT handle
|
|
*
|
|
* Allocate a libdect message buffer. The buffer needs to be released again
|
|
* using dect_mbuf_free().
|
|
*/
|
|
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;
|
|
mb->refcnt = 1;
|
|
mb->next = NULL;
|
|
return mb;
|
|
}
|
|
EXPORT_SYMBOL(dect_mbuf_alloc);
|
|
|
|
/**
|
|
* Release reference to a libdect message buffer
|
|
*
|
|
* @param dh libdect DECT handle
|
|
* @param mb libdect message buffer
|
|
*
|
|
* Release reference to a libdect message buffer. When the reference count
|
|
* drops to zero, the buffer will be freed.
|
|
*/
|
|
void dect_mbuf_free(const struct dect_handle *dh, struct dect_msg_buf *mb)
|
|
{
|
|
if (--mb->refcnt > 0)
|
|
return;
|
|
dect_free(dh, mb);
|
|
}
|
|
EXPORT_SYMBOL(dect_mbuf_free);
|
|
|
|
/**
|
|
* Pull data from the head of a libdect message buffer
|
|
*
|
|
* @param mb libdect message buffer
|
|
* @param len amount of data to pull
|
|
*/
|
|
void *dect_mbuf_pull(struct dect_msg_buf *mb, unsigned int len)
|
|
{
|
|
dect_assert(len <= mb->len);
|
|
mb->data += len;
|
|
mb->len -= len;
|
|
return mb->data;
|
|
}
|
|
EXPORT_SYMBOL(dect_mbuf_pull);
|
|
|
|
/**
|
|
* Push data to the head of a libdect message buffer
|
|
*
|
|
* @param mb libdect message buffer
|
|
* @param len amount of data to push
|
|
*/
|
|
void *dect_mbuf_push(struct dect_msg_buf *mb, unsigned int len)
|
|
{
|
|
mb->data -= len;
|
|
mb->len += len;
|
|
dect_assert(mb->data >= mb->head);
|
|
return mb->data;
|
|
}
|
|
EXPORT_SYMBOL(dect_mbuf_push);
|
|
|
|
/**
|
|
* Reserve space at the head of a libdect message buffer
|
|
*
|
|
* @param mb libdect message buffer
|
|
* @param len amount of space to reserve
|
|
*/
|
|
void dect_mbuf_reserve(struct dect_msg_buf *mb, unsigned int len)
|
|
{
|
|
mb->data += len;
|
|
dect_assert(mb->data < mb->head + sizeof(mb->head));
|
|
}
|
|
EXPORT_SYMBOL(dect_mbuf_reserve);
|
|
|
|
/**
|
|
* Put data at the tail of a libdect message buffer
|
|
*
|
|
* @param mb libdect message buffer
|
|
* @param len amount of data to put
|
|
*/
|
|
void *dect_mbuf_put(struct dect_msg_buf *mb, unsigned int len)
|
|
{
|
|
void *ptr = mb->data + mb->len;
|
|
mb->len += len;
|
|
return ptr;
|
|
}
|
|
EXPORT_SYMBOL(dect_mbuf_put);
|
|
|
|
static ssize_t dect_mbuf_rcv(const struct dect_fd *dfd, struct msghdr *msg,
|
|
struct dect_msg_buf *mb)
|
|
{
|
|
struct iovec iov;
|
|
ssize_t len;
|
|
|
|
msg->msg_name = NULL;
|
|
msg->msg_namelen = 0;
|
|
msg->msg_iov = &iov;
|
|
msg->msg_iovlen = 1;
|
|
msg->msg_flags = MSG_NOSIGNAL;
|
|
|
|
iov.iov_base = mb->data;
|
|
iov.iov_len = sizeof(mb->head);
|
|
|
|
len = recvmsg(dfd->fd, msg, 0);
|
|
if (len < 0) {
|
|
lce_debug("recvmsg: %s\n", strerror(errno));
|
|
return len;
|
|
}
|
|
|
|
mb->len = len;
|
|
return len;
|
|
}
|
|
|
|
static ssize_t dect_mbuf_send(const struct dect_handle *dh,
|
|
const struct dect_fd *dfd,
|
|
struct msghdr *msg, const struct dect_msg_buf *mb)
|
|
{
|
|
struct iovec iov;
|
|
ssize_t len;
|
|
|
|
msg->msg_name = NULL;
|
|
msg->msg_namelen = 0;
|
|
msg->msg_iov = &iov;
|
|
msg->msg_iovlen = 1;
|
|
msg->msg_flags |= MSG_NOSIGNAL;
|
|
|
|
iov.iov_base = mb->data;
|
|
iov.iov_len = mb->len;
|
|
|
|
len = sendmsg(dfd->fd, msg, 0);
|
|
if (len < 0)
|
|
lce_debug("sendmsg: %u bytes: %s\n", mb->len, strerror(errno));
|
|
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* Location Table
|
|
*/
|
|
|
|
#define dect_ie_update(pos, ie) \
|
|
do { \
|
|
if (ie == NULL) \
|
|
break; \
|
|
dect_ie_put(dh, pos); \
|
|
pos = dect_ie_hold(ie); \
|
|
} while (0)
|
|
|
|
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, list) {
|
|
if (!dect_ipui_cmp(<e->ipui, ipui))
|
|
return lte;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct dect_lte *dect_lte_alloc(struct dect_handle *dh,
|
|
const struct dect_ipui *ipui)
|
|
{
|
|
struct dect_lte *lte;
|
|
|
|
lte = dect_malloc(dh, sizeof(*lte));
|
|
if (lte == NULL)
|
|
return NULL;
|
|
memset(lte, 0, sizeof(*lte));
|
|
lte->ipui = *ipui;
|
|
|
|
list_add_tail(<e->list, &dh->ldb);
|
|
return lte;
|
|
}
|
|
|
|
static void dect_lte_release(struct dect_handle *dh, struct dect_lte *lte)
|
|
{
|
|
dect_ie_put(dh, lte->setup_capability);
|
|
dect_ie_put(dh, lte->terminal_capability);
|
|
list_del(<e->list);
|
|
dect_free(dh, lte);
|
|
}
|
|
|
|
void dect_lte_update(struct dect_handle *dh, const struct dect_ipui *ipui,
|
|
struct dect_ie_setup_capability *setup_capability,
|
|
struct dect_ie_terminal_capability *terminal_capability)
|
|
{
|
|
struct dect_lte *lte;
|
|
|
|
lte = dect_lte_get_by_ipui(dh, ipui);
|
|
if (lte == NULL) {
|
|
lte = dect_lte_alloc(dh, ipui);
|
|
if (lte == NULL)
|
|
return;
|
|
}
|
|
|
|
dect_ie_update(lte->setup_capability, setup_capability);
|
|
dect_ie_update(lte->terminal_capability, terminal_capability);
|
|
}
|
|
|
|
void dect_lte_update_tpui(const struct dect_handle *dh,
|
|
const struct dect_ipui *ipui,
|
|
const struct dect_tpui *tpui)
|
|
{
|
|
struct dect_lte *lte;
|
|
|
|
lte = dect_lte_get_by_ipui(dh, ipui);
|
|
if (lte == NULL)
|
|
return;
|
|
lte->tpui = *tpui;
|
|
lte->tpui_valid = true;
|
|
}
|
|
|
|
static const struct dect_tpui *dect_tpui(const struct dect_handle *dh,
|
|
const struct dect_ipui *ipui)
|
|
{
|
|
const struct dect_lte *lte;
|
|
|
|
lte = dect_lte_get_by_ipui(dh, ipui);
|
|
if (lte == NULL || !lte->tpui_valid)
|
|
return NULL;
|
|
return <e->tpui;
|
|
}
|
|
|
|
static enum dect_setup_capabilities
|
|
dect_setup_capability(const struct dect_handle *dh,
|
|
const struct dect_ipui *ipui)
|
|
{
|
|
const struct dect_lte *lte;
|
|
|
|
lte = dect_lte_get_by_ipui(dh, ipui);
|
|
if (lte == NULL ||
|
|
lte->setup_capability == NULL)
|
|
return DECT_SETUP_NO_FAST_SETUP;
|
|
return lte->setup_capability->setup_capability;
|
|
}
|
|
|
|
static enum dect_page_capabilities
|
|
dect_page_capability(const struct dect_handle *dh,
|
|
const struct dect_ipui *ipui)
|
|
{
|
|
const struct dect_lte *lte;
|
|
|
|
lte = dect_lte_get_by_ipui(dh, ipui);
|
|
if (lte == NULL ||
|
|
lte->setup_capability == NULL)
|
|
return DECT_PAGE_CAPABILITY_NORMAL_PAGING;
|
|
return lte->setup_capability->page_capability;
|
|
}
|
|
|
|
static uint64_t dect_profile_indicator(const struct dect_handle *dh,
|
|
const struct dect_ipui *ipui)
|
|
{
|
|
const struct dect_lte *lte;
|
|
|
|
lte = dect_lte_get_by_ipui(dh, ipui);
|
|
if (lte == NULL ||
|
|
lte->terminal_capability == NULL)
|
|
return 0;
|
|
return lte->terminal_capability->profile_indicator;
|
|
}
|
|
|
|
/*
|
|
* Data links
|
|
*/
|
|
|
|
#define ddl_debug(ddl, fmt, args...) \
|
|
lce_debug("link %d (%s): " fmt "\n", \
|
|
(ddl)->dfd ? (ddl)->dfd->fd : -1, \
|
|
ddl_states[(ddl)->state], ## args)
|
|
|
|
static const char * const 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 const struct dect_trans_tbl dect_conn_types[] = {
|
|
TRANS_TBL(DECT_MAC_CONN_BASIC, "basic"),
|
|
TRANS_TBL(DECT_MAC_CONN_ADVANCED, "advanced"),
|
|
};
|
|
|
|
static const struct dect_trans_tbl dect_slot_types[] = {
|
|
TRANS_TBL(DECT_FULL_SLOT, "full slot"),
|
|
TRANS_TBL(DECT_HALF_SLOT, "half slot"),
|
|
TRANS_TBL(DECT_DOUBLE_SLOT, "double slot"),
|
|
TRANS_TBL(DECT_LONG_SLOT_640, "long slot j=640"),
|
|
TRANS_TBL(DECT_LONG_SLOT_672, "long slot j=672"),
|
|
};
|
|
|
|
static const struct dect_trans_tbl dect_service_types[] = {
|
|
TRANS_TBL(DECT_SERVICE_IN_MIN_DELAY, "In_minimum_delay"),
|
|
TRANS_TBL(DECT_SERVICE_IN_NORMAL_DELAY, "In_normal_delay"),
|
|
TRANS_TBL(DECT_SERVICE_UNKNOWN, "unknown"),
|
|
TRANS_TBL(DECT_SERVICE_C_CHANNEL_ONLY, "C channel only"),
|
|
TRANS_TBL(DECT_SERVICE_IP_ERROR_DETECTION, "Ip_error_detection"),
|
|
TRANS_TBL(DECT_SERVICE_IPQ_ERROR_DETECTION, "Ipq_error_detection"),
|
|
};
|
|
|
|
int dect_ddl_set_ipui(struct dect_handle *dh, struct dect_data_link *ddl,
|
|
const struct dect_ipui *ipui)
|
|
{
|
|
if (ddl->flags & DECT_DATA_LINK_IPUI_VALID) {
|
|
if (dect_ipui_cmp(&ddl->ipui, ipui))
|
|
return -1;
|
|
} else {
|
|
ddl_debug(ddl, "set IPUI: N EMC: %04x PSN: %05x",
|
|
ipui->pun.n.ipei.emc, ipui->pun.n.ipei.psn);
|
|
|
|
ddl->ipui = *ipui;
|
|
ddl->flags |= DECT_DATA_LINK_IPUI_VALID;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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 (ddl->flags & DECT_DATA_LINK_IPUI_VALID &&
|
|
!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, enum dect_transaction_role role)
|
|
{
|
|
struct dect_transaction *ta;
|
|
|
|
list_for_each_entry(ta, &ddl->transactions, list) {
|
|
if (ta->pd == pd && ta->tv == tv && ta->role == role)
|
|
return ta;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
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)
|
|
goto err1;
|
|
ddl->sdu_timer = dect_timer_alloc(dh);
|
|
if (ddl->sdu_timer == NULL)
|
|
goto err2;
|
|
ddl->state = DECT_DATA_LINK_RELEASED;
|
|
init_list_head(&ddl->list);
|
|
init_list_head(&ddl->transactions);
|
|
ptrlist_init(&ddl->msg_queue);
|
|
ddl_debug(ddl, "alloc");
|
|
return ddl;
|
|
|
|
err2:
|
|
dect_free(dh, ddl);
|
|
err1:
|
|
return NULL;
|
|
}
|
|
|
|
static void dect_ddl_destroy(struct dect_handle *dh, struct dect_data_link *ddl)
|
|
{
|
|
struct dect_msg_buf *mb;
|
|
unsigned int i;
|
|
|
|
ddl_debug(ddl, "destroy");
|
|
dect_assert(list_empty(&ddl->transactions));
|
|
|
|
for (i = 0; i < array_size(protocols); i++) {
|
|
if (protocols[i] && protocols[i]->rebind != NULL)
|
|
protocols[i]->rebind(dh, ddl, NULL);
|
|
}
|
|
|
|
list_del(&ddl->list);
|
|
|
|
while ((mb = ptrlist_dequeue_head(&ddl->msg_queue)))
|
|
dect_mbuf_free(dh, mb);
|
|
|
|
if (ddl->dfd != NULL) {
|
|
dect_fd_unregister(dh, ddl->dfd);
|
|
dect_close(dh, ddl->dfd);
|
|
}
|
|
|
|
if (dect_timer_running(ddl->sdu_timer))
|
|
dect_timer_stop(dh, ddl->sdu_timer);
|
|
dect_timer_free(dh, ddl->sdu_timer);
|
|
|
|
if (ddl->release_timer != NULL && dect_timer_running(ddl->release_timer))
|
|
dect_timer_stop(dh, ddl->release_timer);
|
|
dect_timer_free(dh, ddl->release_timer);
|
|
dect_free(dh, ddl);
|
|
}
|
|
|
|
static void dect_ddl_release_timer(struct dect_handle *dh, struct dect_timer *timer)
|
|
{
|
|
struct dect_data_link *ddl = timer->data;
|
|
|
|
ddl_debug(ddl, "<LCE.01>: Link release timer: normal release timeout");
|
|
dect_ddl_destroy(dh, ddl);
|
|
}
|
|
|
|
static void dect_ddl_release_complete(struct dect_handle *dh,
|
|
struct dect_data_link *ddl)
|
|
{
|
|
ddl_debug(ddl, "normal release complete");
|
|
ddl->state = DECT_DATA_LINK_RELEASED;
|
|
dect_timer_stop(dh, ddl->release_timer);
|
|
dect_ddl_destroy(dh, ddl);
|
|
}
|
|
|
|
static void dect_ddl_release(struct dect_handle *dh,
|
|
struct dect_data_link *ddl)
|
|
{
|
|
ddl_debug(ddl, "normal release");
|
|
|
|
/* Shut down transmission and wait until all outstanding frames
|
|
* are successfully transmitted or the release timeout occurs.
|
|
*/
|
|
if (shutdown(ddl->dfd->fd, SHUT_WR) < 0)
|
|
goto err1;
|
|
ddl->state = DECT_DATA_LINK_RELEASE_PENDING;
|
|
|
|
ddl->release_timer = dect_timer_alloc(dh);
|
|
if (ddl->release_timer == NULL)
|
|
goto err1;
|
|
dect_timer_setup(ddl->release_timer, dect_ddl_release_timer, ddl);
|
|
dect_timer_start(dh, ddl->release_timer, DECT_DDL_RELEASE_TIMEOUT);
|
|
return;
|
|
|
|
err1:
|
|
dect_ddl_destroy(dh, ddl);
|
|
}
|
|
|
|
static void dect_ddl_partial_release_timer(struct dect_handle *dh, struct dect_timer *timer)
|
|
{
|
|
struct dect_data_link *ddl = timer->data;
|
|
|
|
ddl_debug(ddl, "<LCE.02>: link maintain timer: partial release timeout");
|
|
if (list_empty(&ddl->transactions))
|
|
dect_ddl_destroy(dh, ddl);
|
|
}
|
|
|
|
static void dect_ddl_partial_release(struct dect_handle *dh,
|
|
struct dect_data_link *ddl)
|
|
{
|
|
ddl_debug(ddl, "partial release");
|
|
/* Timer may already be running if a transaction is aborted before the
|
|
* first message has been sent.
|
|
*/
|
|
if (dect_timer_running(ddl->sdu_timer))
|
|
dect_timer_stop(dh, ddl->sdu_timer);
|
|
|
|
dect_timer_setup(ddl->sdu_timer, dect_ddl_partial_release_timer, ddl);
|
|
dect_timer_start(dh, ddl->sdu_timer, DECT_DDL_LINK_MAINTAIN_TIMEOUT);
|
|
}
|
|
|
|
static void dect_ddl_shutdown(struct dect_handle *dh,
|
|
struct dect_data_link *ddl)
|
|
{
|
|
struct dect_transaction *ta, *next;
|
|
bool last = false;
|
|
|
|
ddl_debug(ddl, "shutdown");
|
|
ddl->state = DECT_DATA_LINK_RELEASED;
|
|
|
|
/* If no transactions are present, the link is waiting for a partial
|
|
* release timeout. Destroy immediately since destruction won't be
|
|
* triggered by closing transactions.
|
|
*/
|
|
if (list_empty(&ddl->transactions))
|
|
return dect_ddl_destroy(dh, ddl);
|
|
|
|
list_for_each_entry_safe(ta, next, &ddl->transactions, list) {
|
|
if (&next->list == &ddl->transactions)
|
|
last = true;
|
|
protocols[ta->pd]->shutdown(dh, ta);
|
|
if (last)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* dect_ddl_set_cipher_key - set cipher key for datalink
|
|
*
|
|
* @param ddl Datalink
|
|
* @param ck Cipher key
|
|
*/
|
|
int dect_ddl_set_cipher_key(const struct dect_data_link *ddl,
|
|
const uint8_t ck[DECT_CIPHER_KEY_LEN])
|
|
{
|
|
int err;
|
|
|
|
ddl_debug(ddl, "DL_ENC_KEY-req: %.16" PRIx64, *(uint64_t *)ck);
|
|
err = setsockopt(ddl->dfd->fd, SOL_DECT, DECT_DL_ENC_KEY,
|
|
ck, DECT_CIPHER_KEY_LEN);
|
|
if (err != 0)
|
|
ddl_debug(ddl, "setsockopt: %s", strerror(errno));
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* dect_ddl_encrypt_req - enable/disable encryption for a datalink
|
|
*
|
|
* @param ddl Datalink
|
|
* @param status desired ciphering state (enabled/disabled)
|
|
*/
|
|
int dect_ddl_encrypt_req(const struct dect_data_link *ddl,
|
|
enum dect_cipher_states status)
|
|
{
|
|
struct dect_dl_encrypt dle = { .status = status };
|
|
int err;
|
|
|
|
ddl_debug(ddl, "DL_ENCRYPT-req: status: %u\n", status);
|
|
err = setsockopt(ddl->dfd->fd, SOL_DECT, DECT_DL_ENCRYPT,
|
|
&dle, sizeof(dle));
|
|
if (err != 0)
|
|
ddl_debug(ddl, "setsockopt: %s", strerror(errno));
|
|
return err;
|
|
}
|
|
|
|
static void dect_ddl_encrypt_ind(struct dect_handle *dh,
|
|
struct dect_data_link *ddl,
|
|
enum dect_cipher_states state)
|
|
{
|
|
struct dect_transaction *ta, *next;
|
|
|
|
ddl_debug(ddl, "DL_ENCRYPT-ind");
|
|
ddl->cipher = state;
|
|
list_for_each_entry_safe(ta, next, &ddl->transactions, list) {
|
|
if (protocols[ta->pd]->encrypt_ind)
|
|
protocols[ta->pd]->encrypt_ind(dh, ta, state);
|
|
}
|
|
}
|
|
|
|
static void dect_ddl_sdu_timer(struct dect_handle *dh, struct dect_timer *timer)
|
|
{
|
|
struct dect_data_link *ddl = timer->data;
|
|
|
|
ddl_debug(ddl, "<LCE.05>: SDU timeout");
|
|
dect_ddl_destroy(dh, ddl);
|
|
}
|
|
|
|
static int dect_ddl_schedule_sdu_timer(const struct dect_handle *dh,
|
|
struct dect_data_link *ddl)
|
|
{
|
|
dect_timer_setup(ddl->sdu_timer, dect_ddl_sdu_timer, ddl);
|
|
dect_timer_start(dh, ddl->sdu_timer, DECT_DDL_ESTABLISH_SDU_TIMEOUT);
|
|
ddl_debug(ddl, "start <LCE.05>: 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 <LCE.05>: SDU timer");
|
|
dect_timer_stop(dh, ddl->sdu_timer);
|
|
}
|
|
|
|
static ssize_t dect_ddl_send(const struct dect_handle *dh,
|
|
const struct dect_data_link *ddl,
|
|
struct dect_msg_buf *mb)
|
|
{
|
|
struct msghdr msg;
|
|
ssize_t size;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
dect_mbuf_dump(DECT_DEBUG_LCE, mb, "LCE: TX");
|
|
size = dect_mbuf_send(dh, ddl->dfd, &msg, mb);
|
|
dect_mbuf_free(dh, mb);
|
|
return size;
|
|
}
|
|
|
|
static struct dect_msg_buf *
|
|
dect_lce_build_msg(const struct dect_handle *dh,
|
|
const struct dect_transaction *ta,
|
|
const struct dect_sfmt_msg_desc *desc,
|
|
const struct dect_msg_common *msg, uint8_t type)
|
|
{
|
|
struct dect_msg_buf *mb;
|
|
int err;
|
|
|
|
mb = dect_mbuf_alloc(dh);
|
|
if (mb == NULL)
|
|
goto err1;
|
|
|
|
dect_mbuf_reserve(mb, DECT_S_HDR_SIZE);
|
|
err = dect_build_sfmt_msg(dh, desc, msg, mb);
|
|
if (err < 0)
|
|
goto err2;
|
|
|
|
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;
|
|
return mb;
|
|
|
|
err2:
|
|
dect_mbuf_free(dh, mb);
|
|
err1:
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* dect_lce_send - Queue a S-Format message for transmission to the LCE
|
|
*/
|
|
int dect_lce_send(const struct dect_handle *dh,
|
|
struct dect_transaction *ta,
|
|
const struct dect_sfmt_msg_desc *desc,
|
|
const struct dect_msg_common *msg, uint8_t type)
|
|
{
|
|
struct dect_data_link *ddl = ta->link;
|
|
struct dect_msg_buf *mb;
|
|
|
|
mb = dect_lce_build_msg(dh, ta, desc, msg, type);
|
|
if (mb == NULL)
|
|
return -1;
|
|
|
|
if (ddl->sdu_timer && dect_timer_running(ddl->sdu_timer))
|
|
dect_ddl_stop_sdu_timer(dh, ddl);
|
|
|
|
if (ta->mb != NULL)
|
|
dect_mbuf_free(dh, ta->mb);
|
|
ta->mb = mb;
|
|
mb->refcnt++;
|
|
|
|
switch (ddl->state) {
|
|
case DECT_DATA_LINK_ESTABLISHED:
|
|
return dect_ddl_send(dh, ddl, mb);
|
|
case DECT_DATA_LINK_ESTABLISH_PENDING:
|
|
ptrlist_add_tail(mb, &ddl->msg_queue);
|
|
return 0;
|
|
default:
|
|
ddl_debug(ddl, "Invalid state: %u\n", ddl->state);
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
int dect_lce_send_cl(struct dect_handle *dh, const struct dect_ipui *ipui,
|
|
const struct dect_sfmt_msg_desc *desc,
|
|
const struct dect_msg_common *msg,
|
|
enum dect_pds pd, uint8_t type)
|
|
{
|
|
struct dect_data_link *ddl;
|
|
struct dect_msg_buf *mb;
|
|
struct dect_transaction ta = {
|
|
.pd = pd,
|
|
.tv = DECT_TV_CONNECTIONLESS,
|
|
};
|
|
|
|
ddl = dect_ddl_connect(dh, ipui);
|
|
if (ddl == NULL)
|
|
return -1;
|
|
|
|
mb = dect_lce_build_msg(dh, &ta, desc, msg, type);
|
|
if (mb == NULL)
|
|
return -1;
|
|
|
|
switch (ddl->state) {
|
|
case DECT_DATA_LINK_ESTABLISHED:
|
|
return dect_ddl_send(dh, ddl, mb);
|
|
case DECT_DATA_LINK_ESTABLISH_PENDING:
|
|
ptrlist_add_tail(mb, &ddl->msg_queue);
|
|
return 0;
|
|
default:
|
|
ddl_debug(ddl, "Invalid state: %u\n", ddl->state);
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
int dect_lce_retransmit(const struct dect_handle *dh,
|
|
struct dect_transaction *ta)
|
|
{
|
|
struct dect_data_link *ddl = ta->link;
|
|
|
|
if (ta->mb != NULL &&
|
|
ddl->state == DECT_DATA_LINK_ESTABLISHED) {
|
|
ta->mb->refcnt++;
|
|
return dect_ddl_send(dh, ddl, ta->mb);
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
static void dect_ddl_rcv_msg(struct dect_handle *dh, struct dect_data_link *ddl)
|
|
{
|
|
DECT_DEFINE_MSG_BUF_ONSTACK(_mb), *mb = &_mb;
|
|
struct dect_transaction *ta;
|
|
struct msghdr msg;
|
|
struct cmsghdr *cmsg;
|
|
char cmsg_buf[4 * CMSG_SPACE(16)];
|
|
uint8_t pd, tv;
|
|
bool f;
|
|
|
|
msg.msg_control = cmsg_buf;
|
|
msg.msg_controllen = sizeof(cmsg_buf);
|
|
|
|
if (dect_mbuf_rcv(ddl->dfd, &msg, mb) < 0) {
|
|
switch (errno) {
|
|
case ENOTCONN:
|
|
if (ddl->state == DECT_DATA_LINK_RELEASE_PENDING)
|
|
return dect_ddl_release_complete(dh, ddl);
|
|
else
|
|
return dect_ddl_shutdown(dh, ddl);
|
|
case ETIMEDOUT:
|
|
case ECONNRESET:
|
|
case EHOSTUNREACH:
|
|
return dect_ddl_shutdown(dh, ddl);
|
|
default:
|
|
ddl_debug(ddl, "unhandled receive error: %s",
|
|
strerror(errno));
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
|
const struct dect_dl_encrypt *dle;
|
|
|
|
if (cmsg->cmsg_level != SOL_DECT)
|
|
continue;
|
|
|
|
switch (cmsg->cmsg_type) {
|
|
case DECT_DL_ENCRYPT:
|
|
if (cmsg->cmsg_len < CMSG_LEN(sizeof(*dle)))
|
|
continue;
|
|
dle = (struct dect_dl_encrypt *)CMSG_DATA(cmsg);
|
|
dect_ddl_encrypt_ind(dh, ddl, dle->status);
|
|
continue;
|
|
default:
|
|
ddl_debug(ddl, "unhandled cmsg: %u\n", cmsg->cmsg_type);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
dect_mbuf_dump(DECT_DEBUG_LCE, mb, "LCE: 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", pd);
|
|
return;
|
|
}
|
|
|
|
if (tv >= protocols[pd]->max_transactions) {
|
|
ddl_debug(ddl, "invalid %s transaction value %u\n",
|
|
protocols[pd]->name, tv);
|
|
return;
|
|
}
|
|
|
|
if (dect_timer_running(ddl->sdu_timer))
|
|
dect_ddl_stop_sdu_timer(dh, ddl);
|
|
|
|
if (pd == DECT_PD_CLMS && tv == DECT_TV_CONNECTIONLESS)
|
|
return dect_clss_rcv(dh, mb);
|
|
|
|
ta = dect_ddl_transaction_lookup(ddl, pd, tv, !f);
|
|
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_ddl_complete_direct_establish(struct dect_handle *dh,
|
|
struct dect_data_link *ddl)
|
|
{
|
|
struct dect_msg_buf *mb;
|
|
socklen_t optlen;
|
|
char buf1[128], buf2[128], buf3[128];
|
|
|
|
dect_fd_unregister(dh, ddl->dfd);
|
|
if (dect_fd_register(dh, ddl->dfd, DECT_FD_READ) < 0)
|
|
goto err1;
|
|
|
|
optlen = sizeof(ddl->mcp);
|
|
if (getsockopt(ddl->dfd->fd, SOL_DECT, DECT_DL_MAC_CONN_PARAMS,
|
|
&ddl->mcp, &optlen))
|
|
goto err1;
|
|
|
|
ddl->state = DECT_DATA_LINK_ESTABLISHED;
|
|
ddl_debug(ddl, "complete direct link establishment");
|
|
|
|
ddl_debug(ddl, "MAC connection: type: %s service: %s slot: %s",
|
|
dect_val2str(dect_conn_types, buf1, ddl->mcp.type),
|
|
dect_val2str(dect_service_types, buf2, ddl->mcp.service),
|
|
dect_val2str(dect_slot_types, buf3, ddl->mcp.slot));
|
|
|
|
ddl_debug(ddl, "DL_ESTABLISH-cfm: success: 1");
|
|
dh->ops->lce_ops->dl_establish_cfm(dh, true, ddl, &ddl->mcp);
|
|
|
|
/* Send queued messages */
|
|
while ((mb = ptrlist_dequeue_head(&ddl->msg_queue)))
|
|
dect_ddl_send(dh, ddl, mb);
|
|
return;
|
|
|
|
err1:
|
|
dect_ddl_shutdown(dh, ddl);
|
|
}
|
|
|
|
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;
|
|
unsigned int i;
|
|
|
|
/* Stop page timer */
|
|
dect_timer_stop(dh, req->page_timer);
|
|
dect_timer_free(dh, req->page_timer);
|
|
|
|
ddl_debug(ddl, "complete indirect link establishment req %p", req);
|
|
dect_ddl_set_ipui(dh, ddl, &req->ipui);
|
|
|
|
for (i = 0; i < array_size(protocols); i++) {
|
|
if (protocols[i] && protocols[i]->rebind != NULL)
|
|
protocols[i]->rebind(dh, req, ddl);
|
|
}
|
|
|
|
/* 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", ddl);
|
|
list_move_tail(&ta->list, &ddl->transactions);
|
|
ta->link = ddl;
|
|
}
|
|
|
|
/* Send queued messages */
|
|
while ((mb = ptrlist_dequeue_head(&req->msg_queue)))
|
|
dect_ddl_send(dh, ddl, mb);
|
|
|
|
/* Release pending link */
|
|
dect_ddl_destroy(dh, req);
|
|
|
|
ddl_debug(ddl, "DL_ESTABLISH-cfm: success: 1");
|
|
dh->ops->lce_ops->dl_establish_cfm(dh, true, ddl, &ddl->mcp);
|
|
|
|
/* If the link was established for a connectionless transmission,
|
|
* no transaction exists. Perform a partial release. */
|
|
if (list_empty(&ddl->transactions))
|
|
return dect_ddl_partial_release(dh, ddl);
|
|
}
|
|
|
|
static void dect_ddl_page_timer(struct dect_handle *dh, struct dect_timer *timer);
|
|
static void dect_lce_data_link_event(struct dect_handle *dh,
|
|
struct dect_fd *dfd, uint32_t events);
|
|
|
|
static const struct dect_mac_conn_params default_mcp = {
|
|
.service = DECT_SERVICE_IN_MIN_DELAY,
|
|
.slot = DECT_FULL_SLOT,
|
|
};
|
|
|
|
/**
|
|
* dect_ddl_establish - Establish an outgoing data link
|
|
*/
|
|
static struct dect_data_link *dect_ddl_establish(struct dect_handle *dh,
|
|
const struct dect_ipui *ipui,
|
|
const struct dect_mac_conn_params *mcp)
|
|
{
|
|
struct dect_data_link *ddl;
|
|
|
|
ddl = dect_ddl_alloc(dh);
|
|
if (ddl == NULL)
|
|
goto err1;
|
|
ddl->mcp = mcp ? *mcp : default_mcp;
|
|
ddl->state = DECT_DATA_LINK_ESTABLISH_PENDING;
|
|
dect_ddl_set_ipui(dh, ddl, ipui);
|
|
|
|
if (dh->mode == DECT_MODE_FP &&
|
|
dect_setup_capability(dh, ipui) == DECT_SETUP_NO_FAST_SETUP) {
|
|
ddl->page_timer = dect_timer_alloc(dh);
|
|
if (ddl->page_timer == NULL)
|
|
goto err2;
|
|
dect_timer_setup(ddl->page_timer, dect_ddl_page_timer, ddl);
|
|
dect_ddl_page_timer(dh, ddl->page_timer);
|
|
} 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_index = dh->index;
|
|
ddl->dlei.dect_ari = dect_build_ari(&dh->pari) >> 24;
|
|
ddl->dlei.dect_pmid = dh->pmid;
|
|
ddl->dlei.dect_lln = 1;
|
|
ddl->dlei.dect_sapi = 0;
|
|
|
|
if (mcp != NULL &&
|
|
setsockopt(ddl->dfd->fd, SOL_DECT, DECT_DL_MAC_CONN_PARAMS,
|
|
mcp, sizeof(*mcp)) < 0)
|
|
goto err2;
|
|
|
|
dect_fd_setup(ddl->dfd, dect_lce_data_link_event, ddl);
|
|
if (dect_fd_register(dh, ddl->dfd, DECT_FD_WRITE) < 0)
|
|
goto err2;
|
|
|
|
if (connect(ddl->dfd->fd, (struct sockaddr *)&ddl->dlei,
|
|
sizeof(ddl->dlei)) < 0 && errno != EAGAIN)
|
|
goto err3;
|
|
}
|
|
|
|
list_add_tail(&ddl->list, &dh->links);
|
|
return ddl;
|
|
|
|
err3:
|
|
dect_fd_unregister(dh, ddl->dfd);
|
|
err2:
|
|
dect_free(dh, ddl);
|
|
err1:
|
|
lce_debug("dect_ddl_establish: %s\n", strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
int dect_dl_establish_req(struct dect_handle *dh, const struct dect_ipui *ipui,
|
|
const struct dect_mac_conn_params *mcp)
|
|
{
|
|
lce_debug("DL_ESTABLISH-req: IPUI: N EMC: %04x PSN: %05x\n",
|
|
ipui->pun.n.ipei.emc, ipui->pun.n.ipei.psn);
|
|
dect_ddl_establish(dh, ipui, mcp);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(dect_dl_establish_req);
|
|
|
|
struct dect_data_link *dect_ddl_connect(struct dect_handle *dh,
|
|
const struct dect_ipui *ipui)
|
|
{
|
|
struct dect_data_link *ddl;
|
|
|
|
ddl = dect_ddl_get_by_ipui(dh, ipui);
|
|
if (ddl == NULL)
|
|
ddl = dect_ddl_establish(dh, ipui, NULL);
|
|
return ddl;
|
|
}
|
|
|
|
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;
|
|
|
|
dect_debug(DECT_DEBUG_LCE, "\n");
|
|
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);
|
|
|
|
/* Close the page transaction after receiving the first
|
|
* message, which is expected to initiate a higher layer
|
|
* protocol transaction or reject the page response.
|
|
*/
|
|
if (dh->page_transaction.state == DECT_TRANSACTION_OPEN) {
|
|
dect_debug(DECT_DEBUG_LCE, "\n");
|
|
dect_transaction_close(dh, &dh->page_transaction,
|
|
DECT_DDL_RELEASE_NORMAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
socklen_t optlen;
|
|
char buf1[128], buf2[128], buf3[128];
|
|
|
|
dect_debug(DECT_DEBUG_LCE, "\n");
|
|
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;
|
|
|
|
optlen = sizeof(ddl->mcp);
|
|
if (getsockopt(nfd->fd, SOL_DECT, DECT_DL_MAC_CONN_PARAMS,
|
|
&ddl->mcp, &optlen))
|
|
goto err3;
|
|
|
|
dect_fd_setup(nfd, dect_lce_data_link_event, ddl);
|
|
if (dect_fd_register(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);
|
|
ddl_debug(ddl, "MAC connection: type: %s service: %s slot: %s",
|
|
dect_val2str(dect_conn_types, buf1, ddl->mcp.type),
|
|
dect_val2str(dect_service_types, buf2, ddl->mcp.service),
|
|
dect_val2str(dect_slot_types, buf3, ddl->mcp.slot));
|
|
return;
|
|
|
|
err4:
|
|
dect_fd_unregister(dh, nfd);
|
|
err3:
|
|
dect_close(dh, nfd);
|
|
err2:
|
|
dect_free(dh, ddl);
|
|
err1:
|
|
lce_debug("dect_lce_ssap_listener_event: %s\n", strerror(errno));
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Paging
|
|
*/
|
|
|
|
ssize_t dect_lce_broadcast(const struct dect_handle *dh,
|
|
const struct dect_msg_buf *mb,
|
|
bool long_page, bool fast_page)
|
|
{
|
|
struct msghdr msg;
|
|
struct dect_bsap_auxdata aux;
|
|
struct cmsghdr *cmsg;
|
|
union {
|
|
struct cmsghdr cmsg;
|
|
char buf[CMSG_SPACE(sizeof(aux))];
|
|
} cmsg_buf;
|
|
ssize_t size;
|
|
|
|
if (long_page) {
|
|
memset(cmsg_buf.buf, 0, sizeof(cmsg_buf.buf));
|
|
msg.msg_control = &cmsg_buf;
|
|
msg.msg_controllen = sizeof(cmsg_buf);
|
|
msg.msg_flags = 0;
|
|
|
|
cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(aux));
|
|
cmsg->cmsg_level = SOL_DECT;
|
|
cmsg->cmsg_type = DECT_BSAP_AUXDATA;
|
|
|
|
aux.long_page = true;
|
|
memcpy(CMSG_DATA(cmsg), &aux, sizeof(aux));
|
|
} else {
|
|
msg.msg_control = NULL;
|
|
msg.msg_controllen = 0;
|
|
if (fast_page)
|
|
msg.msg_flags = MSG_OOB;
|
|
}
|
|
|
|
dect_mbuf_dump(DECT_DEBUG_LCE, mb, "LCE: BCAST TX");
|
|
size = dect_mbuf_send(dh, dh->b_sap, &msg, mb);
|
|
dect_assert(size == (ssize_t)mb->len);
|
|
return 0;
|
|
}
|
|
|
|
static enum lce_request_page_hdr_codes
|
|
dect_page_service_to_hdr(enum dect_mac_service_types service)
|
|
{
|
|
switch (service) {
|
|
case DECT_SERVICE_IN_MIN_DELAY:
|
|
return DECT_LCE_PAGE_GENERAL_VOICE;
|
|
case DECT_SERVICE_IN_NORMAL_DELAY:
|
|
return DECT_LCE_PAGE_AUXILIARY;
|
|
case DECT_SERVICE_UNKNOWN:
|
|
return DECT_LCE_PAGE_UNKNOWN_RINGING;
|
|
case DECT_SERVICE_C_CHANNEL_ONLY:
|
|
return DECT_LCE_PAGE_U_PLANE_NONE;
|
|
default:
|
|
return DECT_LCE_PAGE_GENERAL_PURPOSE;
|
|
}
|
|
}
|
|
|
|
static enum dect_mac_service_types
|
|
dect_page_hdr_to_service(enum lce_request_page_hdr_codes hdr)
|
|
{
|
|
switch (hdr) {
|
|
case DECT_LCE_PAGE_U_PLANE_NONE:
|
|
return DECT_SERVICE_C_CHANNEL_ONLY;
|
|
case DECT_LCE_PAGE_UNKNOWN_RINGING:
|
|
return DECT_SERVICE_UNKNOWN;
|
|
case DECT_LCE_PAGE_GENERAL_PURPOSE:
|
|
return DECT_SERVICE_UNKNOWN;
|
|
case DECT_LCE_PAGE_GENERAL_VOICE:
|
|
return DECT_SERVICE_IN_MIN_DELAY;
|
|
case DECT_LCE_PAGE_AUXILIARY:
|
|
return DECT_SERVICE_IN_NORMAL_DELAY;
|
|
default:
|
|
BUG(); // FIXME
|
|
}
|
|
}
|
|
|
|
static enum dect_request_page_slot_types
|
|
dect_page_slot_to_info(enum dect_slot_types slot)
|
|
{
|
|
switch (slot) {
|
|
case DECT_FULL_SLOT:
|
|
return DECT_LCE_PAGE_FULL_SLOT;
|
|
case DECT_HALF_SLOT:
|
|
return DECT_LCE_PAGE_HALF_SLOT;
|
|
case DECT_DOUBLE_SLOT:
|
|
return DECT_LCE_PAGE_DOUBLE_SLOT;
|
|
case DECT_LONG_SLOT_640:
|
|
return DECT_LCE_PAGE_LONG_SLOT_J640;
|
|
case DECT_LONG_SLOT_672:
|
|
return DECT_LCE_PAGE_LONG_SLOT_J672;
|
|
default:
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
static enum dect_slot_types
|
|
dect_page_info_to_slot(enum dect_request_page_slot_types slot)
|
|
{
|
|
/* 8.2.4.2: backwards compatibility: bit 8 indicates "full slot" */
|
|
if (slot & 0x80)
|
|
return DECT_FULL_SLOT;
|
|
|
|
switch (slot) {
|
|
case DECT_LCE_PAGE_HALF_SLOT:
|
|
return DECT_HALF_SLOT;
|
|
case DECT_LCE_PAGE_LONG_SLOT_J640:
|
|
return DECT_LONG_SLOT_640;
|
|
case DECT_LCE_PAGE_LONG_SLOT_J672:
|
|
return DECT_LONG_SLOT_672;
|
|
case DECT_LCE_PAGE_FULL_SLOT:
|
|
return DECT_FULL_SLOT;
|
|
case DECT_LCE_PAGE_DOUBLE_SLOT:
|
|
return DECT_DOUBLE_SLOT;
|
|
default:
|
|
BUG(); // FIXME
|
|
}
|
|
}
|
|
|
|
static enum dect_request_page_setup_info
|
|
dect_page_service_to_setup_info(const struct dect_mac_conn_params *mcp)
|
|
{
|
|
if (mcp->service == DECT_SERVICE_IN_MIN_DELAY &&
|
|
mcp->slot == DECT_FULL_SLOT)
|
|
return DECT_LCE_PAGE_BASIC_CONN_ATTR_OPTIONAL;
|
|
else
|
|
return DECT_LCE_PAGE_NO_SETUP_INFO;
|
|
}
|
|
|
|
/**
|
|
* Request collective or group ringing
|
|
*
|
|
* @param dh libdect DECT handle
|
|
* @param pattern ring pattern
|
|
*/
|
|
int dect_lce_group_ring_req(struct dect_handle *dh,
|
|
enum dect_alerting_patterns pattern)
|
|
{
|
|
DECT_DEFINE_MSG_BUF_ONSTACK(_mb), *mb = &_mb;
|
|
struct dect_short_page_msg *msg;
|
|
uint16_t page;
|
|
|
|
dect_debug(DECT_DEBUG_LCE, "\nLCE: LCE_GROUP_RING-req\n");
|
|
msg = dect_mbuf_put(mb, sizeof(*msg));
|
|
|
|
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, mb, false, false);
|
|
}
|
|
EXPORT_SYMBOL(dect_lce_group_ring_req);
|
|
|
|
static int dect_lce_send_short_page(const struct dect_handle *dh,
|
|
const struct dect_ipui *ipui,
|
|
const struct dect_mac_conn_params *mcp)
|
|
{
|
|
DECT_DEFINE_MSG_BUF_ONSTACK(_mb), *mb = &_mb;
|
|
struct dect_short_page_msg *msg;
|
|
const struct dect_tpui *tpui;
|
|
struct dect_tpui _tpui;
|
|
bool fast_page = false;
|
|
uint16_t page;
|
|
|
|
msg = dect_mbuf_put(mb, sizeof(*msg));
|
|
msg->hdr = dect_page_service_to_hdr(mcp->service);
|
|
|
|
tpui = dect_tpui(dh, ipui);
|
|
if (tpui == NULL)
|
|
tpui = dect_ipui_to_tpui(&_tpui, ipui);
|
|
else
|
|
msg->hdr |= DECT_LCE_PAGE_W_FLAG;
|
|
|
|
page = dect_build_tpui(tpui) & DECT_LCE_SHORT_PAGE_TPUI_MASK;
|
|
msg->information = __cpu_to_be16(page);
|
|
|
|
if (dect_page_capability(dh, ipui) ==
|
|
DECT_PAGE_CAPABILITY_FAST_AND_NORMAL_PAGING)
|
|
fast_page = true;
|
|
|
|
return dect_lce_broadcast(dh, mb, false, fast_page);
|
|
}
|
|
|
|
static int dect_lce_send_full_page(const struct dect_handle *dh,
|
|
const struct dect_ipui *ipui,
|
|
const struct dect_mac_conn_params *mcp)
|
|
{
|
|
DECT_DEFINE_MSG_BUF_ONSTACK(_mb), *mb = &_mb;
|
|
struct dect_full_page_msg *msg;
|
|
const struct dect_tpui *tpui;
|
|
struct dect_tpui _tpui;
|
|
uint8_t ipui_buf[8];
|
|
bool fast_page = false;
|
|
uint32_t page;
|
|
|
|
msg = dect_mbuf_put(mb, sizeof(*msg));
|
|
msg->hdr = dect_page_service_to_hdr(mcp->service);
|
|
|
|
if (1) {
|
|
msg->hdr |= DECT_LCE_PAGE_W_FLAG;
|
|
|
|
tpui = dect_tpui(dh, ipui);
|
|
if (tpui == NULL)
|
|
tpui = dect_ipui_to_tpui(&_tpui, ipui);
|
|
|
|
page = dect_build_tpui(tpui) << DECT_LCE_FULL_PAGE_TPUI_SHIFT;
|
|
page |= dect_page_slot_to_info(mcp->slot) <<
|
|
DECT_LCE_FULL_PAGE_SLOT_TYPE_SHIFT;
|
|
page |= dect_page_service_to_setup_info(mcp) <<
|
|
DECT_LCE_FULL_PAGE_SETUP_INFO_SHIFT;
|
|
} else {
|
|
dect_build_ipui(ipui_buf, ipui);
|
|
page = ipui->put << 24;
|
|
page |= (ipui_buf[1] & 0x0f) << 24;
|
|
page |= ipui_buf[2] << 16;
|
|
page |= ipui_buf[3] << 8;
|
|
page |= ipui_buf[4];
|
|
}
|
|
msg->information = __cpu_to_be32(page);
|
|
|
|
if (dect_page_capability(dh, ipui) ==
|
|
DECT_PAGE_CAPABILITY_FAST_AND_NORMAL_PAGING)
|
|
fast_page = true;
|
|
|
|
return dect_lce_broadcast(dh, mb, false, fast_page);
|
|
}
|
|
|
|
static int dect_lce_page(const struct dect_handle *dh,
|
|
const struct dect_ipui *ipui,
|
|
const struct dect_mac_conn_params *mcp)
|
|
{
|
|
if (mcp->service == DECT_SERVICE_IN_MIN_DELAY &&
|
|
mcp->slot == DECT_FULL_SLOT)
|
|
return dect_lce_send_short_page(dh, ipui, mcp);
|
|
else
|
|
return dect_lce_send_full_page(dh, ipui, mcp);
|
|
}
|
|
|
|
static void dect_ddl_page_timer(struct dect_handle *dh, struct dect_timer *timer)
|
|
{
|
|
struct dect_data_link *ddl = timer->data;
|
|
|
|
if (ddl->page_count) {
|
|
dect_debug(DECT_DEBUG_LCE, "\n");
|
|
ddl_debug(ddl, "<LCE.03>: Page timer");
|
|
}
|
|
|
|
if (ddl->page_count++ == DECT_DDL_PAGE_RETRANS_MAX) {
|
|
ddl_debug(ddl, "DL_ESTABLISH-cfm: success: 0");
|
|
dh->ops->lce_ops->dl_establish_cfm(dh, false, NULL, NULL);
|
|
dect_ddl_shutdown(dh, ddl);
|
|
} else {
|
|
dect_lce_page(dh, &ddl->ipui, &ddl->mcp);
|
|
dect_timer_start(dh, ddl->page_timer, DECT_DDL_PAGE_TIMEOUT);
|
|
}
|
|
}
|
|
|
|
static int dect_lce_send_page_reject(const struct dect_handle *dh,
|
|
struct dect_transaction *ta,
|
|
struct dect_ie_portable_identity *portable_identity,
|
|
enum dect_reject_reasons reason)
|
|
{
|
|
struct dect_ie_reject_reason reject_reason;
|
|
struct dect_lce_page_reject_msg msg = {
|
|
.portable_identity = portable_identity,
|
|
.reject_reason = &reject_reason,
|
|
};
|
|
|
|
reject_reason.reason = reason;
|
|
|
|
return dect_lce_send(dh, ta, &lce_page_reject_msg_desc,
|
|
&msg.common, DECT_LCE_PAGE_REJECT);
|
|
}
|
|
|
|
static void dect_lce_rcv_page_response(struct dect_handle *dh,
|
|
struct dect_transaction *ta,
|
|
struct dect_msg_buf *mb)
|
|
{
|
|
struct dect_lce_page_response_msg msg;
|
|
struct dect_data_link *i, *req = NULL;
|
|
enum dect_sfmt_error err;
|
|
bool reject = true;
|
|
|
|
ddl_debug(ta->link, "LCE-PAGE-RESPONSE");
|
|
err = dect_parse_sfmt_msg(dh, &lce_page_response_msg_desc,
|
|
&msg.common, mb);
|
|
if (err < 0) {
|
|
dect_lce_send_page_reject(dh, ta, NULL, dect_sfmt_reject_reason(err));
|
|
return dect_ddl_release(dh, ta->link);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
dect_ddl_set_ipui(dh, ta->link, &msg.portable_identity->ipui);
|
|
|
|
if (req == NULL && dh->ops->lce_ops &&
|
|
dh->ops->lce_ops->lce_page_response) {
|
|
struct dect_lce_page_param *param;
|
|
|
|
param = dect_ie_collection_alloc(dh, sizeof(*param));
|
|
if (param == NULL)
|
|
goto err;
|
|
|
|
param->portable_identity = dect_ie_hold(msg.portable_identity);
|
|
param->fixed_identity = dect_ie_hold(msg.fixed_identity);
|
|
param->nwk_assigned_identity = dect_ie_hold(msg.nwk_assigned_identity);
|
|
param->cipher_info = dect_ie_hold(msg.cipher_info);
|
|
param->escape_to_proprietary = dect_ie_hold(msg.escape_to_proprietary);
|
|
|
|
reject = !dh->ops->lce_ops->lce_page_response(dh, ta->link, param);
|
|
dect_ie_collection_put(dh, param);
|
|
}
|
|
err:
|
|
if (req != NULL)
|
|
dect_ddl_complete_indirect_establish(dh, ta->link, req);
|
|
else if (reject) {
|
|
dect_lce_send_page_reject(dh, ta, msg.portable_identity,
|
|
DECT_REJECT_IPUI_UNKNOWN);
|
|
dect_ddl_release(dh, ta->link);
|
|
}
|
|
|
|
dect_msg_free(dh, &lce_page_response_msg_desc, &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 msg;
|
|
|
|
ddl_debug(ta->link, "LCE-PAGE-REJECT");
|
|
if (dect_parse_sfmt_msg(dh, &lce_page_reject_msg_desc,
|
|
&msg.common, mb) < 0)
|
|
return;
|
|
dect_msg_free(dh, &lce_page_reject_msg_desc, &msg.common);
|
|
}
|
|
|
|
static void dect_lce_send_page_response(struct dect_handle *dh,
|
|
const struct dect_mac_conn_params *mcp)
|
|
{
|
|
struct dect_data_link *ddl;
|
|
struct dect_ie_portable_identity portable_identity;
|
|
struct dect_ie_fixed_identity fixed_identity;
|
|
struct dect_lce_page_response_msg msg = {
|
|
.portable_identity = &portable_identity,
|
|
.fixed_identity = &fixed_identity,
|
|
};
|
|
|
|
portable_identity.type = DECT_PORTABLE_ID_TYPE_IPUI;
|
|
portable_identity.ipui = dh->ipui;
|
|
|
|
fixed_identity.type = DECT_FIXED_ID_TYPE_PARK;
|
|
fixed_identity.ari = dh->pari;
|
|
|
|
ddl = dect_ddl_establish(dh, &dh->ipui, mcp);
|
|
if (ddl == NULL)
|
|
return;
|
|
|
|
if (dect_ddl_transaction_open(dh, &dh->page_transaction, ddl,
|
|
DECT_PD_LCE) < 0)
|
|
goto err1;
|
|
|
|
dect_lce_send(dh, &dh->page_transaction, &lce_page_response_msg_desc,
|
|
&msg.common, DECT_LCE_PAGE_RESPONSE);
|
|
return;
|
|
|
|
err1:
|
|
dect_ddl_destroy(dh, ddl);
|
|
}
|
|
|
|
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;
|
|
struct dect_tpui tpui;
|
|
uint16_t info, t;
|
|
uint8_t hdr, pattern;
|
|
bool w;
|
|
|
|
w = msg->hdr & DECT_LCE_PAGE_W_FLAG;
|
|
hdr = msg->hdr & DECT_LCE_PAGE_HDR_MASK;
|
|
info = __be16_to_cpu(msg->information);
|
|
lce_debug("short page: w: %u hdr: %u information: %04x\n", w, hdr, info);
|
|
|
|
if (hdr == DECT_LCE_PAGE_UNKNOWN_RINGING) {
|
|
pattern = (info & DECT_LCE_SHORT_PAGE_RING_PATTERN_MASK) >>
|
|
DECT_LCE_SHORT_PAGE_RING_PATTERN_SHIFT;
|
|
|
|
if (w == 0) {
|
|
/* assigned connectionless group TPUI or CBI */
|
|
if ((info ^ DECT_TPUI_CBI) &
|
|
DECT_LCE_SHORT_PAGE_GROUP_MASK)
|
|
return;
|
|
} else {
|
|
/* group mask */
|
|
return;
|
|
}
|
|
|
|
lce_debug("LCE_GROUP_RING-ind: pattern: %x\n", pattern);
|
|
dh->ops->lce_ops->lce_group_ring_ind(dh, pattern);
|
|
} else {
|
|
if (w == 0) {
|
|
/* default individual TPUI */
|
|
t = dect_build_tpui(dect_ipui_to_tpui(&tpui, &dh->ipui));
|
|
if (info != t)
|
|
return;
|
|
} else {
|
|
/* assigned TPUI or CBI */
|
|
t = dect_build_tpui(&dh->tpui);
|
|
if (info != t && info != DECT_TPUI_CBI)
|
|
return;
|
|
}
|
|
|
|
dect_lce_send_page_response(dh, NULL);
|
|
}
|
|
}
|
|
|
|
static void dect_lce_rcv_full_page(struct dect_handle *dh,
|
|
struct dect_msg_buf *mb)
|
|
{
|
|
struct dect_full_page_msg *msg = (void *)mb->data;
|
|
struct dect_mac_conn_params mcp;
|
|
uint32_t info, ipui, tpui, t;
|
|
uint8_t ipui_buf[8];
|
|
uint8_t hdr, pattern, slot, setup;
|
|
bool w;
|
|
|
|
w = msg->hdr & DECT_LCE_PAGE_W_FLAG;
|
|
hdr = msg->hdr & DECT_LCE_PAGE_HDR_MASK;
|
|
info = __be32_to_cpu(msg->information);
|
|
lce_debug("full page: w: %u hdr: %u information: %08x\n", w, hdr, info);
|
|
|
|
if (hdr == DECT_LCE_PAGE_UNKNOWN_RINGING) {
|
|
pattern = (info & DECT_LCE_FULL_PAGE_RING_PATTERN_MASK) >>
|
|
DECT_LCE_FULL_PAGE_RING_PATTERN_SHIFT;
|
|
tpui = (info & DECT_LCE_FULL_PAGE_GROUP_MASK) >>
|
|
DECT_LCE_FULL_PAGE_GROUP_SHIFT;
|
|
|
|
if (w == 0) {
|
|
/* assigned connectionless group TPUI or CBI */
|
|
if (tpui != DECT_TPUI_CBI)
|
|
return;
|
|
} else {
|
|
/* group mask */
|
|
return;
|
|
}
|
|
|
|
lce_debug("LCE_GROUP_RING-ind: pattern: %x\n", pattern);
|
|
dh->ops->lce_ops->lce_group_ring_ind(dh, pattern);
|
|
} else {
|
|
memset(&mcp, 0, sizeof(mcp));
|
|
|
|
if (w == 0) {
|
|
/* IPUI */
|
|
dect_build_ipui(ipui_buf, &dh->ipui);
|
|
ipui = dh->ipui.put << 24;
|
|
ipui |= (ipui_buf[1] & 0x0f) << 24;
|
|
ipui |= ipui_buf[2] << 16;
|
|
ipui |= ipui_buf[3] << 8;
|
|
ipui |= ipui_buf[4];
|
|
|
|
if (info != ipui)
|
|
return;
|
|
} else {
|
|
/* assigned TPUI or CBI */
|
|
tpui = (info & DECT_LCE_FULL_PAGE_TPUI_MASK) >>
|
|
DECT_LCE_FULL_PAGE_TPUI_SHIFT;
|
|
|
|
t = dect_build_tpui(&dh->tpui);
|
|
if (0 && tpui != t && tpui != DECT_TPUI_CBI)
|
|
return;
|
|
|
|
slot = (info & DECT_LCE_FULL_PAGE_SLOT_TYPE_MASK) >>
|
|
DECT_LCE_FULL_PAGE_SLOT_TYPE_SHIFT;
|
|
setup = (info & DECT_LCE_FULL_PAGE_SETUP_INFO_MASK) >>
|
|
DECT_LCE_FULL_PAGE_SETUP_INFO_SHIFT;
|
|
|
|
mcp.service = dect_page_hdr_to_service(hdr);
|
|
mcp.slot = dect_page_info_to_slot(slot);
|
|
}
|
|
|
|
dect_lce_send_page_response(dh, &mcp);
|
|
}
|
|
}
|
|
|
|
static void dect_lce_rcv_long_page(struct dect_handle *dh,
|
|
struct dect_msg_buf *mb)
|
|
{
|
|
lce_debug("long page: length: %u\n", mb->len);
|
|
dect_clms_rcv_fixed(dh, mb);
|
|
}
|
|
|
|
static void dect_lce_bsap_event(struct dect_handle *dh, struct dect_fd *dfd,
|
|
uint32_t events)
|
|
{
|
|
DECT_DEFINE_MSG_BUF_ONSTACK(_mb), *mb = &_mb;
|
|
struct msghdr msg;
|
|
struct cmsghdr *cmsg;
|
|
char cmsg_buf[4 * CMSG_SPACE(16)];
|
|
bool long_page = false;
|
|
|
|
dect_debug(DECT_DEBUG_LCE, "\n");
|
|
|
|
msg.msg_control = cmsg_buf;
|
|
msg.msg_controllen = sizeof(cmsg_buf);
|
|
|
|
if (dect_mbuf_rcv(dfd, &msg, mb) < 0)
|
|
return;
|
|
|
|
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
|
const struct dect_bsap_auxdata *aux;
|
|
|
|
if (cmsg->cmsg_level != SOL_DECT)
|
|
continue;
|
|
|
|
switch (cmsg->cmsg_type) {
|
|
case DECT_BSAP_AUXDATA:
|
|
if (cmsg->cmsg_len < CMSG_LEN(sizeof(*aux)))
|
|
continue;
|
|
aux = (struct dect_bsap_auxdata *)CMSG_DATA(cmsg);
|
|
long_page = aux->long_page;
|
|
continue;
|
|
default:
|
|
lce_debug("LCE: unhandled cmsg: %u\n", cmsg->cmsg_type);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
dect_mbuf_dump(DECT_DEBUG_LCE, mb, "LCE: BCAST RX");
|
|
|
|
switch (mb->len) {
|
|
case 3:
|
|
return dect_lce_rcv_short_page(dh, mb);
|
|
case 5:
|
|
if (!long_page)
|
|
return dect_lce_rcv_full_page(dh, mb);
|
|
default:
|
|
return dect_lce_rcv_long_page(dh, mb);
|
|
}
|
|
}
|
|
|
|
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, "unknown message type %x", mb->type);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void dect_lce_open(struct dect_handle *dh,
|
|
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, "unknown message type %x", mb->type);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void dect_lce_shutdown(struct dect_handle *dh,
|
|
struct dect_transaction *ta)
|
|
{
|
|
lce_debug("shutdown page transaction\n");
|
|
dect_transaction_close(dh, ta, DECT_DDL_RELEASE_NORMAL);
|
|
}
|
|
|
|
static const struct dect_nwk_protocol lce_protocol = {
|
|
.name = "Link Control",
|
|
.pd = DECT_PD_LCE,
|
|
.max_transactions = 1,
|
|
.open = dect_lce_open,
|
|
.rcv = dect_lce_rcv,
|
|
.shutdown = dect_lce_shutdown,
|
|
};
|
|
|
|
/*
|
|
* Transactions
|
|
*/
|
|
|
|
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,
|
|
DECT_TRANSACTION_INITIATOR))
|
|
continue;
|
|
return tv;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void dect_transaction_link(struct dect_data_link *ddl,
|
|
struct dect_transaction *ta)
|
|
{
|
|
struct dect_transaction *last;
|
|
|
|
/* Insert MM transactions at the end of the list to make sure they get
|
|
* destroyed last on shutdown. This makes sure that other protocols
|
|
* which might invoke and wait for the completion of MM transactions
|
|
* have their transactions terminated first and don't mistake a link
|
|
* shutdown or a protocol specific error for a MM error.
|
|
*
|
|
* Ordering among MM transactions is such that the transaction opened
|
|
* last is shut down first.
|
|
*/
|
|
if (ta->pd == DECT_PD_MM) {
|
|
last = list_last_entry(&ddl->transactions, struct dect_transaction, list);
|
|
|
|
if (!list_empty(&ddl->transactions) && last->pd == DECT_PD_MM)
|
|
list_add_tail(&ta->list, &last->list);
|
|
else
|
|
list_add_tail(&ta->list, &ddl->transactions);
|
|
} else
|
|
list_add(&ta->list, &ddl->transactions);
|
|
}
|
|
|
|
int dect_ddl_transaction_open(struct dect_handle *dh, struct dect_transaction *ta,
|
|
struct dect_data_link *ddl, enum dect_pds pd)
|
|
{
|
|
const struct dect_nwk_protocol *protocol = protocols[pd];
|
|
int tv;
|
|
|
|
tv = dect_transaction_alloc_tv(ddl, protocol);
|
|
if (tv < 0)
|
|
return -1;
|
|
|
|
ddl_debug(ddl, "open transaction: %s TV: %u", protocol->name, tv);
|
|
ta->link = ddl;
|
|
ta->mb = NULL;
|
|
ta->pd = pd;
|
|
ta->role = DECT_TRANSACTION_INITIATOR;
|
|
ta->state = DECT_TRANSACTION_OPEN;
|
|
ta->tv = tv;
|
|
|
|
dect_transaction_link(ddl, ta);
|
|
return 0;
|
|
}
|
|
|
|
int dect_transaction_open(struct dect_handle *dh, struct dect_transaction *ta,
|
|
const struct dect_ipui *ipui, enum dect_pds pd)
|
|
{
|
|
struct dect_data_link *ddl;
|
|
|
|
ddl = dect_ddl_connect(dh, ipui);
|
|
if (ddl == NULL)
|
|
return -1;
|
|
|
|
return dect_ddl_transaction_open(dh, ta, ddl, pd);
|
|
}
|
|
|
|
void dect_transaction_confirm(struct dect_handle *dh, struct dect_transaction *ta,
|
|
const struct dect_transaction *req)
|
|
{
|
|
ta->link = req->link;
|
|
ta->mb = NULL;
|
|
ta->tv = req->tv;
|
|
ta->role = req->role;
|
|
ta->pd = req->pd;
|
|
ta->state = DECT_TRANSACTION_OPEN;
|
|
|
|
ddl_debug(req->link, "confirm transaction: %s TV: %u Role: %u",
|
|
protocols[ta->pd]->name, ta->tv, ta->role);
|
|
dect_transaction_link(req->link, ta);
|
|
}
|
|
|
|
void dect_transaction_close(struct dect_handle *dh, struct dect_transaction *ta,
|
|
enum dect_release_modes mode)
|
|
{
|
|
struct dect_data_link *ddl = ta->link;
|
|
|
|
ddl_debug(ddl, "close transaction: %s TV: %u Role: %u",
|
|
protocols[ta->pd]->name, ta->tv, ta->role);
|
|
|
|
list_del(&ta->list);
|
|
ta->state = DECT_TRANSACTION_CLOSED;
|
|
if (ta->mb != NULL)
|
|
dect_mbuf_free(dh, ta->mb);
|
|
|
|
switch (ddl->state) {
|
|
case DECT_DATA_LINK_RELEASED:
|
|
/* If link is already down, destroy immediately */
|
|
if (!list_empty(&ddl->transactions))
|
|
break;
|
|
case DECT_DATA_LINK_ESTABLISH_PENDING:
|
|
/* 14.2 states: "If a higher entity releases a call, whilst the
|
|
* initial messages are still queued, the queued messages shall
|
|
* be discarded, and the link establishment shall be immediately
|
|
* terminated".
|
|
*
|
|
* The term "call" is used ambiguously though, in order to support
|
|
* transactions that only send a single message and immediately
|
|
* close the transaction again, a call is interpreted to only
|
|
* refer to CC and COMS instances.
|
|
*
|
|
* In case of other higher layer protocols we wait until link
|
|
* establishment has succeeded or timed out.
|
|
*/
|
|
if (ta->pd == DECT_PD_CC || ta->pd == DECT_PD_COMS)
|
|
return dect_ddl_destroy(dh, ddl);
|
|
else
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (mode) {
|
|
case DECT_DDL_RELEASE_NORMAL:
|
|
if (!list_empty(&ddl->transactions))
|
|
return;
|
|
return dect_ddl_release(dh, ddl);
|
|
case DECT_DDL_RELEASE_PARTIAL:
|
|
return dect_ddl_partial_release(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_index = ddl->dlei.dect_index;
|
|
addr->dect_ari = ddl->dlei.dect_ari;
|
|
addr->dect_pmid = ddl->dlei.dect_pmid;
|
|
addr->dect_lcn = ddl->dlei.dect_lcn;
|
|
}
|
|
|
|
/*
|
|
* Identities
|
|
*/
|
|
|
|
static void dect_pp_set_default_pmid(struct dect_handle *dh)
|
|
{
|
|
dect_assert(!(dh->flags & DECT_PP_TPUI));
|
|
dh->pmid = DECT_PMID_DEFAULT_ID +
|
|
(random() & DECT_PMID_DEFAULT_NUM_MASK);
|
|
lce_debug("set default PMID: %05x\n", dh->pmid);
|
|
}
|
|
|
|
void dect_pp_change_pmid(struct dect_handle *dh)
|
|
{
|
|
dh->pmid = DECT_PMID_DEFAULT_ID +
|
|
((dh->pmid + 1) & DECT_PMID_DEFAULT_NUM_MASK);
|
|
lce_debug("change PMID: %05x\n", dh->pmid);
|
|
}
|
|
|
|
static void dect_pp_set_assigned_pmid(struct dect_handle *dh)
|
|
{
|
|
struct dect_pmid pmid;
|
|
|
|
dect_assert(dh->flags & DECT_PP_TPUI &&
|
|
dh->tpui.type == DECT_TPUI_INDIVIDUAL_ASSIGNED);
|
|
dh->pmid = dect_build_pmid(dect_tpui_to_pmid(&pmid, &dh->tpui));
|
|
lce_debug("set assigned PMID: %05x\n", dh->pmid);
|
|
}
|
|
|
|
/**
|
|
* Set the PP's IPUI
|
|
*
|
|
* @param dh libdect DECT handle
|
|
* @param ipui IPUI
|
|
*/
|
|
void dect_pp_set_ipui(struct dect_handle *dh, const struct dect_ipui *ipui)
|
|
{
|
|
dh->ipui = *ipui;
|
|
dh->flags |= DECT_PP_IPUI;
|
|
}
|
|
EXPORT_SYMBOL(dect_pp_set_ipui);
|
|
|
|
/**
|
|
* Set the PP's TPUI
|
|
*
|
|
* @param dh libdect DECT handle
|
|
* @param tpui TPUI
|
|
*/
|
|
void dect_pp_set_tpui(struct dect_handle *dh, const struct dect_tpui *tpui)
|
|
{
|
|
dh->tpui = *tpui;
|
|
dh->flags |= DECT_PP_TPUI;
|
|
dect_pp_set_assigned_pmid(dh);
|
|
}
|
|
EXPORT_SYMBOL(dect_pp_set_tpui);
|
|
|
|
int dect_lce_init(struct dect_handle *dh)
|
|
{
|
|
struct sockaddr_dect_ssap s_addr;
|
|
struct sockaddr_dect b_addr;
|
|
|
|
if (dh->mode == DECT_MODE_PP)
|
|
dect_pp_set_default_pmid(dh);
|
|
|
|
/* Open B-SAP socket */
|
|
dh->b_sap = dect_socket(dh, SOCK_DGRAM, DECT_B_SAP);
|
|
if (dh->b_sap == NULL)
|
|
goto err1;
|
|
|
|
memset(&b_addr, 0, sizeof(b_addr));
|
|
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;
|
|
|
|
dect_fd_setup(dh->b_sap, dect_lce_bsap_event, NULL);
|
|
if (dect_fd_register(dh, dh->b_sap, DECT_FD_READ) < 0)
|
|
goto err2;
|
|
|
|
dh->page_transaction.state = DECT_TRANSACTION_CLOSED;
|
|
|
|
/* Open S-SAP listener socket */
|
|
if (dh->mode == DECT_MODE_FP) {
|
|
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_index = dh->index;
|
|
s_addr.dect_lln = DECT_LLN_ANY;
|
|
s_addr.dect_sapi = DECT_SAPI_ANY;
|
|
|
|
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;
|
|
|
|
dect_fd_setup(dh->s_sap, dect_lce_ssap_listener_event, NULL);
|
|
if (dect_fd_register(dh, dh->s_sap, DECT_FD_READ) < 0)
|
|
goto err4;
|
|
}
|
|
|
|
dect_lce_register_protocol(&lce_protocol);
|
|
dect_lce_register_protocol(&dect_cc_protocol);
|
|
dect_lce_register_protocol(&dect_ciss_protocol);
|
|
dect_lce_register_protocol(&dect_clms_protocol);
|
|
dect_lce_register_protocol(&dect_mm_protocol);
|
|
return 0;
|
|
|
|
err4:
|
|
dect_close(dh, dh->s_sap);
|
|
err3:
|
|
dect_fd_unregister(dh, dh->b_sap);
|
|
err2:
|
|
dect_close(dh, dh->b_sap);
|
|
err1:
|
|
lce_debug("dect_lce_init: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
void dect_lce_exit(struct dect_handle *dh)
|
|
{
|
|
struct dect_data_link *ddl, *ddl_next;
|
|
struct dect_lte *lte, *lte_next;
|
|
|
|
list_for_each_entry_safe(ddl, ddl_next, &dh->links, list)
|
|
dect_ddl_shutdown(dh, ddl);
|
|
|
|
list_for_each_entry_safe(lte, lte_next, &dh->ldb, list)
|
|
dect_lte_release(dh, lte);
|
|
|
|
if (dh->mode == DECT_MODE_FP) {
|
|
dect_fd_unregister(dh, dh->s_sap);
|
|
dect_close(dh, dh->s_sap);
|
|
}
|
|
|
|
dect_fd_unregister(dh, dh->b_sap);
|
|
dect_close(dh, dh->b_sap);
|
|
}
|
|
|
|
/** @} */
|
|
/** @} */
|