/* * DECT Link Control Entity (LCE) * * Copyright (c) 2009-2010 Patrick McHardy * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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, ": 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, ": 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, ": 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 : 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_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, ": 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); } /** @} */ /** @} */