| /* rfcomm.c - RFCOMM handling */ |
| |
| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/zephyr.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <zephyr/sys/atomic.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/debug/stack.h> |
| |
| #include <zephyr/bluetooth/hci.h> |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/drivers/bluetooth/hci_driver.h> |
| #include <zephyr/bluetooth/l2cap.h> |
| |
| #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_RFCOMM) |
| #define LOG_MODULE_NAME bt_rfcomm |
| #include "common/log.h" |
| |
| #include <zephyr/bluetooth/rfcomm.h> |
| |
| #include "hci_core.h" |
| #include "conn_internal.h" |
| #include "l2cap_internal.h" |
| #include "rfcomm_internal.h" |
| |
| #define RFCOMM_CHANNEL_START 0x01 |
| #define RFCOMM_CHANNEL_END 0x1e |
| |
| #define RFCOMM_MIN_MTU BT_RFCOMM_SIG_MIN_MTU |
| #define RFCOMM_DEFAULT_MTU 127 |
| |
| #define RFCOMM_MAX_CREDITS (CONFIG_BT_BUF_ACL_RX_COUNT - 1) |
| #define RFCOMM_CREDITS_THRESHOLD (RFCOMM_MAX_CREDITS / 2) |
| #define RFCOMM_DEFAULT_CREDIT RFCOMM_MAX_CREDITS |
| |
| #define RFCOMM_CONN_TIMEOUT K_SECONDS(60) |
| #define RFCOMM_DISC_TIMEOUT K_SECONDS(20) |
| #define RFCOMM_IDLE_TIMEOUT K_SECONDS(2) |
| |
| #define DLC_RTX(_w) CONTAINER_OF(_w, struct bt_rfcomm_dlc, rtx_work) |
| #define SESSION_RTX(_w) CONTAINER_OF(_w, struct bt_rfcomm_session, rtx_work) |
| |
| static struct bt_rfcomm_server *servers; |
| |
| /* Pool for dummy buffers to wake up the tx threads */ |
| NET_BUF_POOL_DEFINE(dummy_pool, CONFIG_BT_MAX_CONN, 0, 0, NULL); |
| |
| #define RFCOMM_SESSION(_ch) CONTAINER_OF(_ch, \ |
| struct bt_rfcomm_session, br_chan.chan) |
| |
| static struct bt_rfcomm_session bt_rfcomm_pool[CONFIG_BT_MAX_CONN]; |
| |
| /* reversed, 8-bit, poly=0x07 */ |
| static const uint8_t rfcomm_crc_table[256] = { |
| 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, |
| 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, |
| 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, |
| 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, |
| |
| 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, |
| 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, |
| 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, |
| 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, |
| |
| 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, |
| 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, |
| 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, |
| 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, |
| |
| 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, |
| 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, |
| 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, |
| 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, |
| |
| 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, |
| 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, |
| 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, |
| 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, |
| |
| 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, |
| 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, |
| 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, |
| 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, |
| |
| 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, |
| 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, |
| 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, |
| 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, |
| |
| 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, |
| 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, |
| 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, |
| 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf |
| }; |
| |
| static uint8_t rfcomm_calc_fcs(uint16_t len, const uint8_t *data) |
| { |
| uint8_t fcs = 0xff; |
| |
| while (len--) { |
| fcs = rfcomm_crc_table[fcs ^ *data++]; |
| } |
| |
| /* Ones compliment */ |
| return (0xff - fcs); |
| } |
| |
| static bool rfcomm_check_fcs(uint16_t len, const uint8_t *data, |
| uint8_t recvd_fcs) |
| { |
| uint8_t fcs = 0xff; |
| |
| while (len--) { |
| fcs = rfcomm_crc_table[fcs ^ *data++]; |
| } |
| |
| /* Ones compliment */ |
| fcs = rfcomm_crc_table[fcs ^ recvd_fcs]; |
| |
| /*0xCF is the reversed order of 11110011.*/ |
| return (fcs == 0xcf); |
| } |
| |
| static struct bt_rfcomm_dlc *rfcomm_dlcs_lookup_dlci(struct bt_rfcomm_dlc *dlcs, |
| uint8_t dlci) |
| { |
| for (; dlcs; dlcs = dlcs->_next) { |
| if (dlcs->dlci == dlci) { |
| return dlcs; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static struct bt_rfcomm_dlc *rfcomm_dlcs_remove_dlci(struct bt_rfcomm_dlc *dlcs, |
| uint8_t dlci) |
| { |
| struct bt_rfcomm_dlc *tmp; |
| |
| if (!dlcs) { |
| return NULL; |
| } |
| |
| /* If first node is the one to be removed */ |
| if (dlcs->dlci == dlci) { |
| dlcs->session->dlcs = dlcs->_next; |
| return dlcs; |
| } |
| |
| for (tmp = dlcs, dlcs = dlcs->_next; dlcs; dlcs = dlcs->_next) { |
| if (dlcs->dlci == dlci) { |
| tmp->_next = dlcs->_next; |
| return dlcs; |
| } |
| tmp = dlcs; |
| } |
| |
| return NULL; |
| } |
| |
| static struct bt_rfcomm_server *rfcomm_server_lookup_channel(uint8_t channel) |
| { |
| struct bt_rfcomm_server *server; |
| |
| for (server = servers; server; server = server->_next) { |
| if (server->channel == channel) { |
| return server; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static struct bt_rfcomm_session * |
| rfcomm_sessions_lookup_bt_conn(struct bt_conn *conn) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(bt_rfcomm_pool); i++) { |
| struct bt_rfcomm_session *session = &bt_rfcomm_pool[i]; |
| |
| if (session->br_chan.chan.conn == conn) { |
| return session; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| int bt_rfcomm_server_register(struct bt_rfcomm_server *server) |
| { |
| if (server->channel < RFCOMM_CHANNEL_START || |
| server->channel > RFCOMM_CHANNEL_END || !server->accept) { |
| return -EINVAL; |
| } |
| |
| /* Check if given channel is already in use */ |
| if (rfcomm_server_lookup_channel(server->channel)) { |
| BT_DBG("Channel already registered"); |
| return -EADDRINUSE; |
| } |
| |
| BT_DBG("Channel 0x%02x", server->channel); |
| |
| server->_next = servers; |
| servers = server; |
| |
| return 0; |
| } |
| |
| static void rfcomm_dlc_tx_give_credits(struct bt_rfcomm_dlc *dlc, |
| uint8_t credits) |
| { |
| BT_DBG("dlc %p credits %u", dlc, credits); |
| |
| while (credits--) { |
| k_sem_give(&dlc->tx_credits); |
| } |
| |
| BT_DBG("dlc %p updated credits %u", dlc, |
| k_sem_count_get(&dlc->tx_credits)); |
| } |
| |
| static void rfcomm_dlc_destroy(struct bt_rfcomm_dlc *dlc) |
| { |
| BT_DBG("dlc %p", dlc); |
| |
| k_work_cancel_delayable(&dlc->rtx_work); |
| dlc->state = BT_RFCOMM_STATE_IDLE; |
| dlc->session = NULL; |
| |
| if (dlc->ops && dlc->ops->disconnected) { |
| dlc->ops->disconnected(dlc); |
| } |
| } |
| |
| static void rfcomm_dlc_disconnect(struct bt_rfcomm_dlc *dlc) |
| { |
| uint8_t old_state = dlc->state; |
| |
| BT_DBG("dlc %p", dlc); |
| |
| if (dlc->state == BT_RFCOMM_STATE_DISCONNECTED) { |
| return; |
| } |
| |
| dlc->state = BT_RFCOMM_STATE_DISCONNECTED; |
| |
| switch (old_state) { |
| case BT_RFCOMM_STATE_CONNECTED: |
| /* Queue a dummy buffer to wake up and stop the |
| * tx thread for states where it was running. |
| */ |
| net_buf_put(&dlc->tx_queue, |
| net_buf_alloc(&dummy_pool, K_NO_WAIT)); |
| |
| /* There could be a writer waiting for credits so return a |
| * dummy credit to wake it up. |
| */ |
| rfcomm_dlc_tx_give_credits(dlc, 1); |
| k_sem_give(&dlc->session->fc); |
| break; |
| default: |
| rfcomm_dlc_destroy(dlc); |
| break; |
| } |
| } |
| |
| static void rfcomm_session_disconnected(struct bt_rfcomm_session *session) |
| { |
| struct bt_rfcomm_dlc *dlc; |
| |
| BT_DBG("Session %p", session); |
| |
| if (session->state == BT_RFCOMM_STATE_DISCONNECTED) { |
| return; |
| } |
| |
| for (dlc = session->dlcs; dlc;) { |
| struct bt_rfcomm_dlc *next; |
| |
| /* prefetch since disconnected callback may cleanup */ |
| next = dlc->_next; |
| dlc->_next = NULL; |
| |
| rfcomm_dlc_disconnect(dlc); |
| |
| dlc = next; |
| } |
| |
| session->state = BT_RFCOMM_STATE_DISCONNECTED; |
| session->dlcs = NULL; |
| } |
| |
| struct net_buf *bt_rfcomm_create_pdu(struct net_buf_pool *pool) |
| { |
| /* Length in RFCOMM header can be 2 bytes depending on length of user |
| * data |
| */ |
| return bt_conn_create_pdu(pool, |
| sizeof(struct bt_l2cap_hdr) + |
| sizeof(struct bt_rfcomm_hdr) + 1); |
| } |
| |
| static int rfcomm_send(struct bt_rfcomm_session *session, struct net_buf *buf) |
| { |
| int err; |
| |
| err = bt_l2cap_chan_send(&session->br_chan.chan, buf); |
| if (err < 0) { |
| net_buf_unref(buf); |
| } |
| |
| return err; |
| } |
| |
| static int rfcomm_send_sabm(struct bt_rfcomm_session *session, uint8_t dlci) |
| { |
| struct bt_rfcomm_hdr *hdr; |
| struct net_buf *buf; |
| uint8_t cr, fcs; |
| |
| buf = bt_l2cap_create_pdu(NULL, 0); |
| |
| hdr = net_buf_add(buf, sizeof(*hdr)); |
| cr = BT_RFCOMM_CMD_CR(session->role); |
| hdr->address = BT_RFCOMM_SET_ADDR(dlci, cr); |
| hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_SABM, BT_RFCOMM_PF_NON_UIH); |
| hdr->length = BT_RFCOMM_SET_LEN_8(0); |
| |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_NON_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(session, buf); |
| } |
| |
| static int rfcomm_send_disc(struct bt_rfcomm_session *session, uint8_t dlci) |
| { |
| struct bt_rfcomm_hdr *hdr; |
| struct net_buf *buf; |
| uint8_t fcs, cr; |
| |
| BT_DBG("dlci %d", dlci); |
| |
| buf = bt_l2cap_create_pdu(NULL, 0); |
| |
| hdr = net_buf_add(buf, sizeof(*hdr)); |
| cr = BT_RFCOMM_RESP_CR(session->role); |
| hdr->address = BT_RFCOMM_SET_ADDR(dlci, cr); |
| hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_DISC, BT_RFCOMM_PF_NON_UIH); |
| hdr->length = BT_RFCOMM_SET_LEN_8(0); |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_NON_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(session, buf); |
| } |
| |
| static void rfcomm_session_disconnect(struct bt_rfcomm_session *session) |
| { |
| if (session->dlcs) { |
| return; |
| } |
| |
| session->state = BT_RFCOMM_STATE_DISCONNECTING; |
| rfcomm_send_disc(session, 0); |
| k_work_reschedule(&session->rtx_work, RFCOMM_DISC_TIMEOUT); |
| } |
| |
| static struct net_buf *rfcomm_make_uih_msg(struct bt_rfcomm_session *session, |
| uint8_t cr, uint8_t type, |
| uint8_t len) |
| { |
| struct bt_rfcomm_hdr *hdr; |
| struct bt_rfcomm_msg_hdr *msg_hdr; |
| struct net_buf *buf; |
| uint8_t hdr_cr; |
| |
| buf = bt_l2cap_create_pdu(NULL, 0); |
| |
| hdr = net_buf_add(buf, sizeof(*hdr)); |
| hdr_cr = BT_RFCOMM_UIH_CR(session->role); |
| hdr->address = BT_RFCOMM_SET_ADDR(0, hdr_cr); |
| hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_UIH, BT_RFCOMM_PF_UIH); |
| hdr->length = BT_RFCOMM_SET_LEN_8(sizeof(*msg_hdr) + len); |
| |
| msg_hdr = net_buf_add(buf, sizeof(*msg_hdr)); |
| msg_hdr->type = BT_RFCOMM_SET_MSG_TYPE(type, cr); |
| msg_hdr->len = BT_RFCOMM_SET_LEN_8(len); |
| |
| return buf; |
| } |
| |
| static void rfcomm_connected(struct bt_l2cap_chan *chan) |
| { |
| struct bt_rfcomm_session *session = RFCOMM_SESSION(chan); |
| |
| BT_DBG("Session %p", session); |
| |
| /* Need to include UIH header and FCS*/ |
| session->mtu = MIN(session->br_chan.rx.mtu, |
| session->br_chan.tx.mtu) - |
| BT_RFCOMM_HDR_SIZE + BT_RFCOMM_FCS_SIZE; |
| |
| if (session->state == BT_RFCOMM_STATE_CONNECTING) { |
| rfcomm_send_sabm(session, 0); |
| } |
| } |
| |
| static void rfcomm_disconnected(struct bt_l2cap_chan *chan) |
| { |
| struct bt_rfcomm_session *session = RFCOMM_SESSION(chan); |
| |
| BT_DBG("Session %p", session); |
| |
| k_work_cancel_delayable(&session->rtx_work); |
| rfcomm_session_disconnected(session); |
| session->state = BT_RFCOMM_STATE_IDLE; |
| } |
| |
| static void rfcomm_dlc_rtx_timeout(struct k_work *work) |
| { |
| struct bt_rfcomm_dlc *dlc = DLC_RTX(work); |
| struct bt_rfcomm_session *session = dlc->session; |
| |
| BT_WARN("dlc %p state %d timeout", dlc, dlc->state); |
| |
| rfcomm_dlcs_remove_dlci(session->dlcs, dlc->dlci); |
| rfcomm_dlc_disconnect(dlc); |
| rfcomm_session_disconnect(session); |
| } |
| |
| static void rfcomm_dlc_init(struct bt_rfcomm_dlc *dlc, |
| struct bt_rfcomm_session *session, |
| uint8_t dlci, |
| bt_rfcomm_role_t role) |
| { |
| BT_DBG("dlc %p", dlc); |
| |
| dlc->dlci = dlci; |
| dlc->session = session; |
| dlc->rx_credit = RFCOMM_DEFAULT_CREDIT; |
| dlc->state = BT_RFCOMM_STATE_INIT; |
| dlc->role = role; |
| k_work_init_delayable(&dlc->rtx_work, rfcomm_dlc_rtx_timeout); |
| |
| /* Start a conn timer which includes auth as well */ |
| k_work_schedule(&dlc->rtx_work, RFCOMM_CONN_TIMEOUT); |
| |
| dlc->_next = session->dlcs; |
| session->dlcs = dlc; |
| } |
| |
| static struct bt_rfcomm_dlc *rfcomm_dlc_accept(struct bt_rfcomm_session *session, |
| uint8_t dlci) |
| { |
| struct bt_rfcomm_server *server; |
| struct bt_rfcomm_dlc *dlc; |
| uint8_t channel; |
| |
| channel = BT_RFCOMM_GET_CHANNEL(dlci); |
| server = rfcomm_server_lookup_channel(channel); |
| if (!server) { |
| BT_ERR("Server Channel not registered"); |
| return NULL; |
| } |
| |
| if (server->accept(session->br_chan.chan.conn, &dlc) < 0) { |
| BT_DBG("Incoming connection rejected"); |
| return NULL; |
| } |
| |
| if (!BT_RFCOMM_CHECK_MTU(dlc->mtu)) { |
| rfcomm_dlc_destroy(dlc); |
| return NULL; |
| } |
| |
| rfcomm_dlc_init(dlc, session, dlci, BT_RFCOMM_ROLE_ACCEPTOR); |
| dlc->mtu = MIN(dlc->mtu, session->mtu); |
| |
| return dlc; |
| } |
| |
| static int rfcomm_send_dm(struct bt_rfcomm_session *session, uint8_t dlci) |
| { |
| struct bt_rfcomm_hdr *hdr; |
| struct net_buf *buf; |
| uint8_t fcs, cr; |
| |
| BT_DBG("dlci %d", dlci); |
| |
| buf = bt_l2cap_create_pdu(NULL, 0); |
| |
| hdr = net_buf_add(buf, sizeof(*hdr)); |
| cr = BT_RFCOMM_RESP_CR(session->role); |
| hdr->address = BT_RFCOMM_SET_ADDR(dlci, cr); |
| /* For DM PF bit is not relevant, we set it 1 */ |
| hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_DM, BT_RFCOMM_PF_NON_UIH); |
| hdr->length = BT_RFCOMM_SET_LEN_8(0); |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_NON_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(session, buf); |
| } |
| |
| static void rfcomm_check_fc(struct bt_rfcomm_dlc *dlc) |
| { |
| BT_DBG("%p", dlc); |
| |
| BT_DBG("Wait for credits or MSC FC %p", dlc); |
| /* Wait for credits or MSC FC */ |
| k_sem_take(&dlc->tx_credits, K_FOREVER); |
| |
| if (dlc->session->cfc == BT_RFCOMM_CFC_SUPPORTED) { |
| return; |
| } |
| |
| k_sem_take(&dlc->session->fc, K_FOREVER); |
| |
| /* Give the sems immediately so that sem will be available for all |
| * the bufs in the queue. It will be blocked only once all the bufs |
| * are sent (which will preempt this thread) and FCOFF / FC bit |
| * with 1, is received. |
| */ |
| k_sem_give(&dlc->session->fc); |
| k_sem_give(&dlc->tx_credits); |
| } |
| |
| static void rfcomm_dlc_tx_thread(void *p1, void *p2, void *p3) |
| { |
| struct bt_rfcomm_dlc *dlc = p1; |
| k_timeout_t timeout = K_FOREVER; |
| struct net_buf *buf; |
| |
| BT_DBG("Started for dlc %p", dlc); |
| |
| while (dlc->state == BT_RFCOMM_STATE_CONNECTED || |
| dlc->state == BT_RFCOMM_STATE_USER_DISCONNECT) { |
| /* Get next packet for dlc */ |
| BT_DBG("Wait for buf %p", dlc); |
| buf = net_buf_get(&dlc->tx_queue, timeout); |
| /* If its dummy buffer or non user disconnect then break */ |
| if ((dlc->state != BT_RFCOMM_STATE_CONNECTED && |
| dlc->state != BT_RFCOMM_STATE_USER_DISCONNECT) || |
| !buf || !buf->len) { |
| if (buf) { |
| net_buf_unref(buf); |
| } |
| break; |
| } |
| |
| rfcomm_check_fc(dlc); |
| if (dlc->state != BT_RFCOMM_STATE_CONNECTED && |
| dlc->state != BT_RFCOMM_STATE_USER_DISCONNECT) { |
| net_buf_unref(buf); |
| break; |
| } |
| |
| if (rfcomm_send(dlc->session, buf) < 0) { |
| /* This fails only if channel is disconnected */ |
| dlc->state = BT_RFCOMM_STATE_DISCONNECTED; |
| break; |
| } |
| |
| if (dlc->state == BT_RFCOMM_STATE_USER_DISCONNECT) { |
| timeout = K_NO_WAIT; |
| } |
| } |
| |
| BT_DBG("dlc %p disconnected - cleaning up", dlc); |
| |
| /* Give back any allocated buffers */ |
| while ((buf = net_buf_get(&dlc->tx_queue, K_NO_WAIT))) { |
| net_buf_unref(buf); |
| } |
| |
| if (dlc->state == BT_RFCOMM_STATE_USER_DISCONNECT) { |
| dlc->state = BT_RFCOMM_STATE_DISCONNECTING; |
| } |
| |
| if (dlc->state == BT_RFCOMM_STATE_DISCONNECTING) { |
| rfcomm_send_disc(dlc->session, dlc->dlci); |
| k_work_reschedule(&dlc->rtx_work, RFCOMM_DISC_TIMEOUT); |
| } else { |
| rfcomm_dlc_destroy(dlc); |
| } |
| |
| BT_DBG("dlc %p exiting", dlc); |
| } |
| |
| static int rfcomm_send_ua(struct bt_rfcomm_session *session, uint8_t dlci) |
| { |
| struct bt_rfcomm_hdr *hdr; |
| struct net_buf *buf; |
| uint8_t cr, fcs; |
| |
| buf = bt_l2cap_create_pdu(NULL, 0); |
| |
| hdr = net_buf_add(buf, sizeof(*hdr)); |
| cr = BT_RFCOMM_RESP_CR(session->role); |
| hdr->address = BT_RFCOMM_SET_ADDR(dlci, cr); |
| hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_UA, BT_RFCOMM_PF_NON_UIH); |
| hdr->length = BT_RFCOMM_SET_LEN_8(0); |
| |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_NON_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(session, buf); |
| } |
| |
| static int rfcomm_send_msc(struct bt_rfcomm_dlc *dlc, uint8_t cr, |
| uint8_t v24_signal) |
| { |
| struct bt_rfcomm_msc *msc; |
| struct net_buf *buf; |
| uint8_t fcs; |
| |
| buf = rfcomm_make_uih_msg(dlc->session, cr, BT_RFCOMM_MSC, |
| sizeof(*msc)); |
| |
| msc = net_buf_add(buf, sizeof(*msc)); |
| /* cr bit should be always 1 in MSC */ |
| msc->dlci = BT_RFCOMM_SET_ADDR(dlc->dlci, 1); |
| msc->v24_signal = v24_signal; |
| |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(dlc->session, buf); |
| } |
| |
| static int rfcomm_send_rls(struct bt_rfcomm_dlc *dlc, uint8_t cr, |
| uint8_t line_status) |
| { |
| struct bt_rfcomm_rls *rls; |
| struct net_buf *buf; |
| uint8_t fcs; |
| |
| buf = rfcomm_make_uih_msg(dlc->session, cr, BT_RFCOMM_RLS, |
| sizeof(*rls)); |
| |
| rls = net_buf_add(buf, sizeof(*rls)); |
| /* cr bit should be always 1 in RLS */ |
| rls->dlci = BT_RFCOMM_SET_ADDR(dlc->dlci, 1); |
| rls->line_status = line_status; |
| |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(dlc->session, buf); |
| } |
| |
| static int rfcomm_send_rpn(struct bt_rfcomm_session *session, uint8_t cr, |
| struct bt_rfcomm_rpn *rpn) |
| { |
| struct net_buf *buf; |
| uint8_t fcs; |
| |
| buf = rfcomm_make_uih_msg(session, cr, BT_RFCOMM_RPN, sizeof(*rpn)); |
| |
| net_buf_add_mem(buf, rpn, sizeof(*rpn)); |
| |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(session, buf); |
| } |
| |
| static int rfcomm_send_test(struct bt_rfcomm_session *session, uint8_t cr, |
| uint8_t *pattern, uint8_t len) |
| { |
| struct net_buf *buf; |
| uint8_t fcs; |
| |
| buf = rfcomm_make_uih_msg(session, cr, BT_RFCOMM_TEST, len); |
| |
| net_buf_add_mem(buf, pattern, len); |
| |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(session, buf); |
| } |
| |
| static int rfcomm_send_nsc(struct bt_rfcomm_session *session, uint8_t cmd_type) |
| { |
| struct net_buf *buf; |
| uint8_t fcs; |
| |
| buf = rfcomm_make_uih_msg(session, BT_RFCOMM_MSG_RESP_CR, |
| BT_RFCOMM_NSC, sizeof(cmd_type)); |
| |
| net_buf_add_u8(buf, cmd_type); |
| |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(session, buf); |
| } |
| |
| static int rfcomm_send_fcon(struct bt_rfcomm_session *session, uint8_t cr) |
| { |
| struct net_buf *buf; |
| uint8_t fcs; |
| |
| buf = rfcomm_make_uih_msg(session, cr, BT_RFCOMM_FCON, 0); |
| |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(session, buf); |
| } |
| |
| static int rfcomm_send_fcoff(struct bt_rfcomm_session *session, uint8_t cr) |
| { |
| struct net_buf *buf; |
| uint8_t fcs; |
| |
| buf = rfcomm_make_uih_msg(session, cr, BT_RFCOMM_FCOFF, 0); |
| |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(session, buf); |
| } |
| |
| static void rfcomm_dlc_connected(struct bt_rfcomm_dlc *dlc) |
| { |
| dlc->state = BT_RFCOMM_STATE_CONNECTED; |
| |
| rfcomm_send_msc(dlc, BT_RFCOMM_MSG_CMD_CR, BT_RFCOMM_DEFAULT_V24_SIG); |
| |
| if (dlc->session->cfc == BT_RFCOMM_CFC_UNKNOWN) { |
| /* This means PN negotiation is not done for this session and |
| * can happen only for 1.0b device. |
| */ |
| dlc->session->cfc = BT_RFCOMM_CFC_NOT_SUPPORTED; |
| } |
| |
| if (dlc->session->cfc == BT_RFCOMM_CFC_NOT_SUPPORTED) { |
| BT_DBG("CFC not supported %p", dlc); |
| rfcomm_send_fcon(dlc->session, BT_RFCOMM_MSG_CMD_CR); |
| /* Use tx_credits as binary sem for MSC FC */ |
| k_sem_init(&dlc->tx_credits, 0, 1); |
| } |
| |
| /* Cancel conn timer */ |
| k_work_cancel_delayable(&dlc->rtx_work); |
| |
| k_fifo_init(&dlc->tx_queue); |
| k_thread_create(&dlc->tx_thread, dlc->stack, |
| K_KERNEL_STACK_SIZEOF(dlc->stack), |
| rfcomm_dlc_tx_thread, dlc, NULL, NULL, K_PRIO_COOP(7), |
| 0, K_NO_WAIT); |
| k_thread_name_set(&dlc->tx_thread, "BT DLC"); |
| |
| if (dlc->ops && dlc->ops->connected) { |
| dlc->ops->connected(dlc); |
| } |
| } |
| |
| enum security_result { |
| RFCOMM_SECURITY_PASSED, |
| RFCOMM_SECURITY_REJECT, |
| RFCOMM_SECURITY_PENDING |
| }; |
| |
| static enum security_result rfcomm_dlc_security(struct bt_rfcomm_dlc *dlc) |
| { |
| struct bt_conn *conn = dlc->session->br_chan.chan.conn; |
| |
| BT_DBG("dlc %p", dlc); |
| |
| /* If current security level is greater than or equal to required |
| * security level then return SUCCESS. |
| * For SSP devices the current security will be at least MEDIUM |
| * since L2CAP is enforcing it |
| */ |
| if (conn->sec_level >= dlc->required_sec_level) { |
| return RFCOMM_SECURITY_PASSED; |
| } |
| |
| if (!bt_conn_set_security(conn, dlc->required_sec_level)) { |
| /* If Security elevation is initiated or in progress */ |
| return RFCOMM_SECURITY_PENDING; |
| } |
| |
| /* Security request failed */ |
| return RFCOMM_SECURITY_REJECT; |
| } |
| |
| static void rfcomm_dlc_drop(struct bt_rfcomm_dlc *dlc) |
| { |
| BT_DBG("dlc %p", dlc); |
| |
| rfcomm_dlcs_remove_dlci(dlc->session->dlcs, dlc->dlci); |
| rfcomm_dlc_destroy(dlc); |
| } |
| |
| static int rfcomm_dlc_close(struct bt_rfcomm_dlc *dlc) |
| { |
| BT_DBG("dlc %p", dlc); |
| |
| switch (dlc->state) { |
| case BT_RFCOMM_STATE_SECURITY_PENDING: |
| if (dlc->role == BT_RFCOMM_ROLE_ACCEPTOR) { |
| rfcomm_send_dm(dlc->session, dlc->dlci); |
| } |
| __fallthrough; |
| case BT_RFCOMM_STATE_INIT: |
| rfcomm_dlc_drop(dlc); |
| break; |
| case BT_RFCOMM_STATE_CONNECTING: |
| case BT_RFCOMM_STATE_CONFIG: |
| dlc->state = BT_RFCOMM_STATE_DISCONNECTING; |
| rfcomm_send_disc(dlc->session, dlc->dlci); |
| k_work_reschedule(&dlc->rtx_work, RFCOMM_DISC_TIMEOUT); |
| break; |
| case BT_RFCOMM_STATE_CONNECTED: |
| dlc->state = BT_RFCOMM_STATE_DISCONNECTING; |
| |
| /* Queue a dummy buffer to wake up and stop the |
| * tx thread. |
| */ |
| net_buf_put(&dlc->tx_queue, |
| net_buf_alloc(&dummy_pool, K_NO_WAIT)); |
| |
| /* There could be a writer waiting for credits so return a |
| * dummy credit to wake it up. |
| */ |
| rfcomm_dlc_tx_give_credits(dlc, 1); |
| break; |
| case BT_RFCOMM_STATE_DISCONNECTING: |
| case BT_RFCOMM_STATE_DISCONNECTED: |
| break; |
| case BT_RFCOMM_STATE_IDLE: |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static void rfcomm_handle_sabm(struct bt_rfcomm_session *session, uint8_t dlci) |
| { |
| if (!dlci) { |
| if (rfcomm_send_ua(session, dlci) < 0) { |
| return; |
| } |
| |
| session->state = BT_RFCOMM_STATE_CONNECTED; |
| } else { |
| struct bt_rfcomm_dlc *dlc; |
| enum security_result result; |
| |
| dlc = rfcomm_dlcs_lookup_dlci(session->dlcs, dlci); |
| if (!dlc) { |
| dlc = rfcomm_dlc_accept(session, dlci); |
| if (!dlc) { |
| rfcomm_send_dm(session, dlci); |
| return; |
| } |
| } |
| |
| result = rfcomm_dlc_security(dlc); |
| switch (result) { |
| case RFCOMM_SECURITY_PENDING: |
| dlc->state = BT_RFCOMM_STATE_SECURITY_PENDING; |
| return; |
| case RFCOMM_SECURITY_PASSED: |
| break; |
| case RFCOMM_SECURITY_REJECT: |
| default: |
| rfcomm_send_dm(session, dlci); |
| rfcomm_dlc_drop(dlc); |
| return; |
| } |
| |
| if (rfcomm_send_ua(session, dlci) < 0) { |
| return; |
| } |
| |
| /* Cancel idle timer if any */ |
| k_work_cancel_delayable(&session->rtx_work); |
| |
| rfcomm_dlc_connected(dlc); |
| } |
| } |
| |
| static int rfcomm_send_pn(struct bt_rfcomm_dlc *dlc, uint8_t cr) |
| { |
| struct bt_rfcomm_pn *pn; |
| struct net_buf *buf; |
| uint8_t fcs; |
| |
| buf = rfcomm_make_uih_msg(dlc->session, cr, BT_RFCOMM_PN, sizeof(*pn)); |
| |
| BT_DBG("mtu %x", dlc->mtu); |
| |
| pn = net_buf_add(buf, sizeof(*pn)); |
| pn->dlci = dlc->dlci; |
| pn->mtu = sys_cpu_to_le16(dlc->mtu); |
| if (dlc->state == BT_RFCOMM_STATE_CONFIG && |
| (dlc->session->cfc == BT_RFCOMM_CFC_UNKNOWN || |
| dlc->session->cfc == BT_RFCOMM_CFC_SUPPORTED)) { |
| pn->credits = dlc->rx_credit; |
| if (cr) { |
| pn->flow_ctrl = BT_RFCOMM_PN_CFC_CMD; |
| } else { |
| pn->flow_ctrl = BT_RFCOMM_PN_CFC_RESP; |
| } |
| } else { |
| /* If PN comes in already opened dlc or cfc not supported |
| * these should be 0 |
| */ |
| pn->credits = 0U; |
| pn->flow_ctrl = 0U; |
| } |
| pn->max_retrans = 0U; |
| pn->ack_timer = 0U; |
| pn->priority = 0U; |
| |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(dlc->session, buf); |
| } |
| |
| static int rfcomm_send_credit(struct bt_rfcomm_dlc *dlc, uint8_t credits) |
| { |
| struct bt_rfcomm_hdr *hdr; |
| struct net_buf *buf; |
| uint8_t fcs, cr; |
| |
| BT_DBG("Dlc %p credits %d", dlc, credits); |
| |
| buf = bt_l2cap_create_pdu(NULL, 0); |
| |
| hdr = net_buf_add(buf, sizeof(*hdr)); |
| cr = BT_RFCOMM_UIH_CR(dlc->session->role); |
| hdr->address = BT_RFCOMM_SET_ADDR(dlc->dlci, cr); |
| hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_UIH, |
| BT_RFCOMM_PF_UIH_CREDIT); |
| hdr->length = BT_RFCOMM_SET_LEN_8(0); |
| net_buf_add_u8(buf, credits); |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| return rfcomm_send(dlc->session, buf); |
| } |
| |
| static int rfcomm_dlc_start(struct bt_rfcomm_dlc *dlc) |
| { |
| enum security_result result; |
| |
| BT_DBG("dlc %p", dlc); |
| |
| result = rfcomm_dlc_security(dlc); |
| switch (result) { |
| case RFCOMM_SECURITY_PASSED: |
| dlc->mtu = MIN(dlc->mtu, dlc->session->mtu); |
| dlc->state = BT_RFCOMM_STATE_CONFIG; |
| rfcomm_send_pn(dlc, BT_RFCOMM_MSG_CMD_CR); |
| break; |
| case RFCOMM_SECURITY_PENDING: |
| dlc->state = BT_RFCOMM_STATE_SECURITY_PENDING; |
| break; |
| case RFCOMM_SECURITY_REJECT: |
| default: |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static void rfcomm_handle_ua(struct bt_rfcomm_session *session, uint8_t dlci) |
| { |
| struct bt_rfcomm_dlc *dlc, *next; |
| int err; |
| |
| if (!dlci) { |
| switch (session->state) { |
| case BT_RFCOMM_STATE_CONNECTING: |
| session->state = BT_RFCOMM_STATE_CONNECTED; |
| for (dlc = session->dlcs; dlc; dlc = next) { |
| next = dlc->_next; |
| if (dlc->role == BT_RFCOMM_ROLE_INITIATOR && |
| dlc->state == BT_RFCOMM_STATE_INIT) { |
| if (rfcomm_dlc_start(dlc) < 0) { |
| rfcomm_dlc_drop(dlc); |
| } |
| } |
| } |
| /* Disconnect session if there is no dlcs left */ |
| rfcomm_session_disconnect(session); |
| break; |
| case BT_RFCOMM_STATE_DISCONNECTING: |
| session->state = BT_RFCOMM_STATE_DISCONNECTED; |
| /* Cancel disc timer */ |
| k_work_cancel_delayable(&session->rtx_work); |
| err = bt_l2cap_chan_disconnect(&session->br_chan.chan); |
| if (err < 0) { |
| session->state = BT_RFCOMM_STATE_IDLE; |
| } |
| break; |
| default: |
| break; |
| } |
| } else { |
| dlc = rfcomm_dlcs_lookup_dlci(session->dlcs, dlci); |
| if (!dlc) { |
| return; |
| } |
| |
| switch (dlc->state) { |
| case BT_RFCOMM_STATE_CONNECTING: |
| rfcomm_dlc_connected(dlc); |
| break; |
| case BT_RFCOMM_STATE_DISCONNECTING: |
| rfcomm_dlc_drop(dlc); |
| rfcomm_session_disconnect(session); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| static void rfcomm_handle_dm(struct bt_rfcomm_session *session, uint8_t dlci) |
| { |
| struct bt_rfcomm_dlc *dlc; |
| |
| BT_DBG("dlci %d", dlci); |
| |
| dlc = rfcomm_dlcs_remove_dlci(session->dlcs, dlci); |
| if (!dlc) { |
| return; |
| } |
| |
| rfcomm_dlc_disconnect(dlc); |
| rfcomm_session_disconnect(session); |
| } |
| |
| static void rfcomm_handle_msc(struct bt_rfcomm_session *session, |
| struct net_buf *buf, uint8_t cr) |
| { |
| struct bt_rfcomm_msc *msc = (void *)buf->data; |
| struct bt_rfcomm_dlc *dlc; |
| uint8_t dlci = BT_RFCOMM_GET_DLCI(msc->dlci); |
| |
| BT_DBG("dlci %d", dlci); |
| |
| dlc = rfcomm_dlcs_lookup_dlci(session->dlcs, dlci); |
| if (!dlc) { |
| return; |
| } |
| |
| if (cr == BT_RFCOMM_MSG_RESP_CR) { |
| return; |
| } |
| |
| if (dlc->session->cfc == BT_RFCOMM_CFC_NOT_SUPPORTED) { |
| /* Only FC bit affects the flow on RFCOMM level */ |
| if (BT_RFCOMM_GET_FC(msc->v24_signal)) { |
| /* If FC bit is 1 the device is unable to accept frames. |
| * Take the semaphore with timeout K_NO_WAIT so that |
| * dlc thread will be blocked when it tries sem_take |
| * before sending the data. K_NO_WAIT timeout will make |
| * sure that RX thread will not be blocked while taking |
| * the semaphore. |
| */ |
| k_sem_take(&dlc->tx_credits, K_NO_WAIT); |
| } else { |
| /* Give the sem so that it will unblock the waiting dlc |
| * thread in sem_take(). |
| */ |
| k_sem_give(&dlc->tx_credits); |
| } |
| } |
| |
| rfcomm_send_msc(dlc, BT_RFCOMM_MSG_RESP_CR, msc->v24_signal); |
| } |
| |
| static void rfcomm_handle_rls(struct bt_rfcomm_session *session, |
| struct net_buf *buf, uint8_t cr) |
| { |
| struct bt_rfcomm_rls *rls = (void *)buf->data; |
| uint8_t dlci = BT_RFCOMM_GET_DLCI(rls->dlci); |
| struct bt_rfcomm_dlc *dlc; |
| |
| BT_DBG("dlci %d", dlci); |
| |
| if (!cr) { |
| /* Ignore if its a response */ |
| return; |
| } |
| |
| dlc = rfcomm_dlcs_lookup_dlci(session->dlcs, dlci); |
| if (!dlc) { |
| return; |
| } |
| |
| /* As per the ETSI same line status has to returned in the response */ |
| rfcomm_send_rls(dlc, BT_RFCOMM_MSG_RESP_CR, rls->line_status); |
| } |
| |
| static void rfcomm_handle_rpn(struct bt_rfcomm_session *session, |
| struct net_buf *buf, uint8_t cr) |
| { |
| struct bt_rfcomm_rpn default_rpn, *rpn = (void *)buf->data; |
| uint8_t dlci = BT_RFCOMM_GET_DLCI(rpn->dlci); |
| uint8_t data_bits, stop_bits, parity_bits; |
| /* Exclude fcs to get number of value bytes */ |
| uint8_t value_len = buf->len - 1; |
| |
| BT_DBG("dlci %d", dlci); |
| |
| if (!cr) { |
| /* Ignore if its a response */ |
| return; |
| } |
| |
| if (value_len == sizeof(*rpn)) { |
| /* Accept all the values proposed by the sender */ |
| rpn->param_mask = sys_cpu_to_le16(BT_RFCOMM_RPN_PARAM_MASK_ALL); |
| rfcomm_send_rpn(session, BT_RFCOMM_MSG_RESP_CR, rpn); |
| return; |
| } |
| |
| if (value_len != 1U) { |
| return; |
| } |
| |
| /* If only one value byte then current port settings has to be returned |
| * We will send default values |
| */ |
| default_rpn.dlci = BT_RFCOMM_SET_ADDR(dlci, 1); |
| default_rpn.baud_rate = BT_RFCOMM_RPN_BAUD_RATE_9600; |
| default_rpn.flow_control = BT_RFCOMM_RPN_FLOW_NONE; |
| default_rpn.xoff_char = BT_RFCOMM_RPN_XOFF_CHAR; |
| default_rpn.xon_char = BT_RFCOMM_RPN_XON_CHAR; |
| data_bits = BT_RFCOMM_RPN_DATA_BITS_8; |
| stop_bits = BT_RFCOMM_RPN_STOP_BITS_1; |
| parity_bits = BT_RFCOMM_RPN_PARITY_NONE; |
| default_rpn.line_settings = BT_RFCOMM_SET_LINE_SETTINGS(data_bits, |
| stop_bits, |
| parity_bits); |
| default_rpn.param_mask = sys_cpu_to_le16(BT_RFCOMM_RPN_PARAM_MASK_ALL); |
| |
| rfcomm_send_rpn(session, BT_RFCOMM_MSG_RESP_CR, &default_rpn); |
| } |
| |
| static void rfcomm_handle_pn(struct bt_rfcomm_session *session, |
| struct net_buf *buf, uint8_t cr) |
| { |
| struct bt_rfcomm_pn *pn = (void *)buf->data; |
| struct bt_rfcomm_dlc *dlc; |
| |
| dlc = rfcomm_dlcs_lookup_dlci(session->dlcs, pn->dlci); |
| if (!dlc) { |
| /* Ignore if it is a response */ |
| if (!cr) { |
| return; |
| } |
| |
| if (!BT_RFCOMM_CHECK_MTU(pn->mtu)) { |
| BT_ERR("Invalid mtu %d", pn->mtu); |
| rfcomm_send_dm(session, pn->dlci); |
| return; |
| } |
| |
| dlc = rfcomm_dlc_accept(session, pn->dlci); |
| if (!dlc) { |
| rfcomm_send_dm(session, pn->dlci); |
| return; |
| } |
| |
| BT_DBG("Incoming connection accepted dlc %p", dlc); |
| |
| dlc->mtu = MIN(dlc->mtu, sys_le16_to_cpu(pn->mtu)); |
| |
| if (pn->flow_ctrl == BT_RFCOMM_PN_CFC_CMD) { |
| if (session->cfc == BT_RFCOMM_CFC_UNKNOWN) { |
| session->cfc = BT_RFCOMM_CFC_SUPPORTED; |
| } |
| k_sem_init(&dlc->tx_credits, 0, K_SEM_MAX_LIMIT); |
| rfcomm_dlc_tx_give_credits(dlc, pn->credits); |
| } else { |
| session->cfc = BT_RFCOMM_CFC_NOT_SUPPORTED; |
| } |
| |
| dlc->state = BT_RFCOMM_STATE_CONFIG; |
| rfcomm_send_pn(dlc, BT_RFCOMM_MSG_RESP_CR); |
| /* Cancel idle timer if any */ |
| k_work_cancel_delayable(&session->rtx_work); |
| } else { |
| /* If its a command */ |
| if (cr) { |
| if (!BT_RFCOMM_CHECK_MTU(pn->mtu)) { |
| BT_ERR("Invalid mtu %d", pn->mtu); |
| rfcomm_dlc_close(dlc); |
| return; |
| } |
| dlc->mtu = MIN(dlc->mtu, sys_le16_to_cpu(pn->mtu)); |
| rfcomm_send_pn(dlc, BT_RFCOMM_MSG_RESP_CR); |
| } else { |
| if (dlc->state != BT_RFCOMM_STATE_CONFIG) { |
| return; |
| } |
| |
| dlc->mtu = MIN(dlc->mtu, sys_le16_to_cpu(pn->mtu)); |
| if (pn->flow_ctrl == BT_RFCOMM_PN_CFC_RESP) { |
| if (session->cfc == BT_RFCOMM_CFC_UNKNOWN) { |
| session->cfc = BT_RFCOMM_CFC_SUPPORTED; |
| } |
| k_sem_init(&dlc->tx_credits, 0, K_SEM_MAX_LIMIT); |
| rfcomm_dlc_tx_give_credits(dlc, pn->credits); |
| } else { |
| session->cfc = BT_RFCOMM_CFC_NOT_SUPPORTED; |
| } |
| |
| dlc->state = BT_RFCOMM_STATE_CONNECTING; |
| rfcomm_send_sabm(session, dlc->dlci); |
| } |
| } |
| } |
| |
| static void rfcomm_handle_disc(struct bt_rfcomm_session *session, uint8_t dlci) |
| { |
| struct bt_rfcomm_dlc *dlc; |
| |
| BT_DBG("Dlci %d", dlci); |
| |
| if (dlci) { |
| dlc = rfcomm_dlcs_remove_dlci(session->dlcs, dlci); |
| if (!dlc) { |
| rfcomm_send_dm(session, dlci); |
| return; |
| } |
| |
| rfcomm_send_ua(session, dlci); |
| rfcomm_dlc_disconnect(dlc); |
| |
| if (!session->dlcs) { |
| /* Start a session idle timer */ |
| k_work_reschedule(&dlc->session->rtx_work, |
| RFCOMM_IDLE_TIMEOUT); |
| } |
| } else { |
| /* Cancel idle timer */ |
| k_work_cancel_delayable(&session->rtx_work); |
| rfcomm_send_ua(session, 0); |
| rfcomm_session_disconnected(session); |
| } |
| } |
| |
| static void rfcomm_handle_msg(struct bt_rfcomm_session *session, |
| struct net_buf *buf) |
| { |
| struct bt_rfcomm_msg_hdr *hdr; |
| uint8_t msg_type, len, cr; |
| |
| if (buf->len < sizeof(*hdr)) { |
| BT_ERR("Too small RFCOMM message"); |
| return; |
| } |
| |
| hdr = net_buf_pull_mem(buf, sizeof(*hdr)); |
| msg_type = BT_RFCOMM_GET_MSG_TYPE(hdr->type); |
| cr = BT_RFCOMM_GET_MSG_CR(hdr->type); |
| len = BT_RFCOMM_GET_LEN(hdr->len); |
| |
| BT_DBG("msg type %x cr %x", msg_type, cr); |
| |
| switch (msg_type) { |
| case BT_RFCOMM_PN: |
| rfcomm_handle_pn(session, buf, cr); |
| break; |
| case BT_RFCOMM_MSC: |
| rfcomm_handle_msc(session, buf, cr); |
| break; |
| case BT_RFCOMM_RLS: |
| rfcomm_handle_rls(session, buf, cr); |
| break; |
| case BT_RFCOMM_RPN: |
| rfcomm_handle_rpn(session, buf, cr); |
| break; |
| case BT_RFCOMM_TEST: |
| if (!cr) { |
| break; |
| } |
| rfcomm_send_test(session, BT_RFCOMM_MSG_RESP_CR, buf->data, |
| buf->len - 1); |
| break; |
| case BT_RFCOMM_FCON: |
| if (session->cfc == BT_RFCOMM_CFC_SUPPORTED) { |
| BT_ERR("FCON received when CFC is supported "); |
| return; |
| } |
| |
| if (!cr) { |
| break; |
| } |
| |
| /* Give the sem so that it will unblock the waiting dlc threads |
| * of this session in sem_take(). |
| */ |
| k_sem_give(&session->fc); |
| rfcomm_send_fcon(session, BT_RFCOMM_MSG_RESP_CR); |
| break; |
| case BT_RFCOMM_FCOFF: |
| if (session->cfc == BT_RFCOMM_CFC_SUPPORTED) { |
| BT_ERR("FCOFF received when CFC is supported "); |
| return; |
| } |
| |
| if (!cr) { |
| break; |
| } |
| |
| /* Take the semaphore with timeout K_NO_WAIT so that all the |
| * dlc threads in this session will be blocked when it tries |
| * sem_take before sending the data. K_NO_WAIT timeout will |
| * make sure that RX thread will not be blocked while taking |
| * the semaphore. |
| */ |
| k_sem_take(&session->fc, K_NO_WAIT); |
| rfcomm_send_fcoff(session, BT_RFCOMM_MSG_RESP_CR); |
| break; |
| default: |
| BT_WARN("Unknown/Unsupported RFCOMM Msg type 0x%02x", msg_type); |
| rfcomm_send_nsc(session, hdr->type); |
| break; |
| } |
| } |
| |
| static void rfcomm_dlc_update_credits(struct bt_rfcomm_dlc *dlc) |
| { |
| uint8_t credits; |
| |
| if (dlc->session->cfc == BT_RFCOMM_CFC_NOT_SUPPORTED) { |
| return; |
| } |
| |
| BT_DBG("dlc %p credits %u", dlc, dlc->rx_credit); |
| |
| /* Only give more credits if it went below the defined threshold */ |
| if (dlc->rx_credit > RFCOMM_CREDITS_THRESHOLD) { |
| return; |
| } |
| |
| /* Restore credits */ |
| credits = RFCOMM_MAX_CREDITS - dlc->rx_credit; |
| dlc->rx_credit += credits; |
| |
| rfcomm_send_credit(dlc, credits); |
| } |
| |
| static void rfcomm_handle_data(struct bt_rfcomm_session *session, |
| struct net_buf *buf, uint8_t dlci, uint8_t pf) |
| |
| { |
| struct bt_rfcomm_dlc *dlc; |
| |
| BT_DBG("dlci %d, pf %d", dlci, pf); |
| |
| dlc = rfcomm_dlcs_lookup_dlci(session->dlcs, dlci); |
| if (!dlc) { |
| BT_ERR("Data recvd in non existing DLC"); |
| rfcomm_send_dm(session, dlci); |
| return; |
| } |
| |
| BT_DBG("dlc %p rx credit %d", dlc, dlc->rx_credit); |
| |
| if (dlc->state != BT_RFCOMM_STATE_CONNECTED) { |
| return; |
| } |
| |
| if (pf == BT_RFCOMM_PF_UIH_CREDIT) { |
| rfcomm_dlc_tx_give_credits(dlc, net_buf_pull_u8(buf)); |
| } |
| |
| if (buf->len > BT_RFCOMM_FCS_SIZE) { |
| if (dlc->session->cfc == BT_RFCOMM_CFC_SUPPORTED && |
| !dlc->rx_credit) { |
| BT_ERR("Data recvd when rx credit is 0"); |
| rfcomm_dlc_close(dlc); |
| return; |
| } |
| |
| /* Remove FCS */ |
| buf->len -= BT_RFCOMM_FCS_SIZE; |
| if (dlc->ops && dlc->ops->recv) { |
| dlc->ops->recv(dlc, buf); |
| } |
| |
| dlc->rx_credit--; |
| rfcomm_dlc_update_credits(dlc); |
| } |
| } |
| |
| int bt_rfcomm_dlc_send(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) |
| { |
| uint8_t fcs, cr; |
| |
| if (!buf) { |
| return -EINVAL; |
| } |
| |
| BT_DBG("dlc %p tx credit %d", dlc, k_sem_count_get(&dlc->tx_credits)); |
| |
| if (dlc->state != BT_RFCOMM_STATE_CONNECTED) { |
| return -ENOTCONN; |
| } |
| |
| if (buf->len > dlc->mtu) { |
| return -EMSGSIZE; |
| } |
| |
| /* length */ |
| if (buf->len > BT_RFCOMM_MAX_LEN_8) { |
| /* Length is 2 byte */ |
| net_buf_push_le16(buf, BT_RFCOMM_SET_LEN_16(buf->len)); |
| } else { |
| net_buf_push_u8(buf, BT_RFCOMM_SET_LEN_8(buf->len)); |
| } |
| |
| /* control */ |
| net_buf_push_u8(buf, BT_RFCOMM_SET_CTRL(BT_RFCOMM_UIH, |
| BT_RFCOMM_PF_UIH_NO_CREDIT)); |
| /* address */ |
| cr = BT_RFCOMM_UIH_CR(dlc->session->role); |
| net_buf_push_u8(buf, BT_RFCOMM_SET_ADDR(dlc->dlci, cr)); |
| |
| fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); |
| net_buf_add_u8(buf, fcs); |
| |
| net_buf_put(&dlc->tx_queue, buf); |
| |
| return buf->len; |
| } |
| |
| static int rfcomm_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) |
| { |
| struct bt_rfcomm_session *session = RFCOMM_SESSION(chan); |
| struct bt_rfcomm_hdr *hdr = (void *)buf->data; |
| uint8_t dlci, frame_type, fcs, fcs_len; |
| |
| /* Need to consider FCS also*/ |
| if (buf->len < (sizeof(*hdr) + 1)) { |
| BT_ERR("Too small RFCOMM Frame"); |
| return 0; |
| } |
| |
| dlci = BT_RFCOMM_GET_DLCI(hdr->address); |
| frame_type = BT_RFCOMM_GET_FRAME_TYPE(hdr->control); |
| |
| BT_DBG("session %p dlci %x type %x", session, dlci, frame_type); |
| |
| fcs_len = (frame_type == BT_RFCOMM_UIH) ? BT_RFCOMM_FCS_LEN_UIH : |
| BT_RFCOMM_FCS_LEN_NON_UIH; |
| fcs = *(net_buf_tail(buf) - 1); |
| if (!rfcomm_check_fcs(fcs_len, buf->data, fcs)) { |
| BT_ERR("FCS check failed"); |
| return 0; |
| } |
| |
| if (BT_RFCOMM_LEN_EXTENDED(hdr->length)) { |
| net_buf_pull(buf, sizeof(*hdr) + 1); |
| } else { |
| net_buf_pull(buf, sizeof(*hdr)); |
| } |
| |
| switch (frame_type) { |
| case BT_RFCOMM_SABM: |
| rfcomm_handle_sabm(session, dlci); |
| break; |
| case BT_RFCOMM_UIH: |
| if (!dlci) { |
| rfcomm_handle_msg(session, buf); |
| } else { |
| rfcomm_handle_data(session, buf, dlci, |
| BT_RFCOMM_GET_PF(hdr->control)); |
| } |
| break; |
| case BT_RFCOMM_DISC: |
| rfcomm_handle_disc(session, dlci); |
| break; |
| case BT_RFCOMM_UA: |
| rfcomm_handle_ua(session, dlci); |
| break; |
| case BT_RFCOMM_DM: |
| rfcomm_handle_dm(session, dlci); |
| break; |
| default: |
| BT_WARN("Unknown/Unsupported RFCOMM Frame type 0x%02x", |
| frame_type); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static void rfcomm_encrypt_change(struct bt_l2cap_chan *chan, |
| uint8_t hci_status) |
| { |
| struct bt_rfcomm_session *session = RFCOMM_SESSION(chan); |
| struct bt_conn *conn = chan->conn; |
| struct bt_rfcomm_dlc *dlc, *next; |
| |
| BT_DBG("session %p status 0x%02x encr 0x%02x", session, hci_status, |
| conn->encrypt); |
| |
| for (dlc = session->dlcs; dlc; dlc = next) { |
| next = dlc->_next; |
| |
| if (dlc->state != BT_RFCOMM_STATE_SECURITY_PENDING) { |
| continue; |
| } |
| |
| if (hci_status || !conn->encrypt || |
| conn->sec_level < dlc->required_sec_level) { |
| rfcomm_dlc_close(dlc); |
| continue; |
| } |
| |
| if (dlc->role == BT_RFCOMM_ROLE_ACCEPTOR) { |
| rfcomm_send_ua(session, dlc->dlci); |
| rfcomm_dlc_connected(dlc); |
| } else { |
| dlc->mtu = MIN(dlc->mtu, session->mtu); |
| dlc->state = BT_RFCOMM_STATE_CONFIG; |
| rfcomm_send_pn(dlc, BT_RFCOMM_MSG_CMD_CR); |
| } |
| } |
| } |
| |
| static void rfcomm_session_rtx_timeout(struct k_work *work) |
| { |
| struct bt_rfcomm_session *session = SESSION_RTX(work); |
| |
| BT_WARN("session %p state %d timeout", session, session->state); |
| |
| switch (session->state) { |
| case BT_RFCOMM_STATE_CONNECTED: |
| rfcomm_session_disconnect(session); |
| break; |
| case BT_RFCOMM_STATE_DISCONNECTING: |
| session->state = BT_RFCOMM_STATE_DISCONNECTED; |
| if (bt_l2cap_chan_disconnect(&session->br_chan.chan) < 0) { |
| session->state = BT_RFCOMM_STATE_IDLE; |
| } |
| break; |
| } |
| } |
| |
| static struct bt_rfcomm_session *rfcomm_session_new(bt_rfcomm_role_t role) |
| { |
| int i; |
| static const struct bt_l2cap_chan_ops ops = { |
| .connected = rfcomm_connected, |
| .disconnected = rfcomm_disconnected, |
| .recv = rfcomm_recv, |
| .encrypt_change = rfcomm_encrypt_change, |
| }; |
| |
| for (i = 0; i < ARRAY_SIZE(bt_rfcomm_pool); i++) { |
| struct bt_rfcomm_session *session = &bt_rfcomm_pool[i]; |
| |
| if (session->br_chan.chan.conn) { |
| continue; |
| } |
| |
| BT_DBG("session %p initialized", session); |
| |
| session->br_chan.chan.ops = &ops; |
| session->br_chan.rx.mtu = CONFIG_BT_RFCOMM_L2CAP_MTU; |
| session->state = BT_RFCOMM_STATE_INIT; |
| session->role = role; |
| session->cfc = BT_RFCOMM_CFC_UNKNOWN; |
| k_work_init_delayable(&session->rtx_work, |
| rfcomm_session_rtx_timeout); |
| k_sem_init(&session->fc, 0, 1); |
| |
| return session; |
| } |
| |
| return NULL; |
| } |
| |
| int bt_rfcomm_dlc_connect(struct bt_conn *conn, struct bt_rfcomm_dlc *dlc, |
| uint8_t channel) |
| { |
| struct bt_rfcomm_session *session; |
| struct bt_l2cap_chan *chan; |
| uint8_t dlci; |
| int ret; |
| |
| BT_DBG("conn %p dlc %p channel %d", conn, dlc, channel); |
| |
| if (!dlc) { |
| return -EINVAL; |
| } |
| |
| if (!conn || conn->state != BT_CONN_CONNECTED) { |
| return -ENOTCONN; |
| } |
| |
| if (channel < RFCOMM_CHANNEL_START || channel > RFCOMM_CHANNEL_END) { |
| return -EINVAL; |
| } |
| |
| if (!BT_RFCOMM_CHECK_MTU(dlc->mtu)) { |
| return -EINVAL; |
| } |
| |
| session = rfcomm_sessions_lookup_bt_conn(conn); |
| if (!session) { |
| session = rfcomm_session_new(BT_RFCOMM_ROLE_INITIATOR); |
| if (!session) { |
| return -ENOMEM; |
| } |
| } |
| |
| dlci = BT_RFCOMM_DLCI(session->role, channel); |
| |
| if (rfcomm_dlcs_lookup_dlci(session->dlcs, dlci)) { |
| return -EBUSY; |
| } |
| |
| rfcomm_dlc_init(dlc, session, dlci, BT_RFCOMM_ROLE_INITIATOR); |
| |
| switch (session->state) { |
| case BT_RFCOMM_STATE_INIT: |
| if (session->role == BT_RFCOMM_ROLE_ACCEPTOR) { |
| /* There is an ongoing incoming conn */ |
| break; |
| } |
| chan = &session->br_chan.chan; |
| BR_CHAN(chan)->required_sec_level = dlc->required_sec_level; |
| ret = bt_l2cap_chan_connect(conn, chan, BT_L2CAP_PSM_RFCOMM); |
| if (ret < 0) { |
| session->state = BT_RFCOMM_STATE_IDLE; |
| goto fail; |
| } |
| session->state = BT_RFCOMM_STATE_CONNECTING; |
| break; |
| case BT_RFCOMM_STATE_CONNECTING: |
| break; |
| case BT_RFCOMM_STATE_CONNECTED: |
| ret = rfcomm_dlc_start(dlc); |
| if (ret < 0) { |
| goto fail; |
| } |
| /* Cancel idle timer if any */ |
| k_work_cancel_delayable(&session->rtx_work); |
| break; |
| default: |
| BT_ERR("Invalid session state %d", session->state); |
| ret = -EINVAL; |
| goto fail; |
| } |
| |
| return 0; |
| |
| fail: |
| rfcomm_dlcs_remove_dlci(session->dlcs, dlc->dlci); |
| dlc->state = BT_RFCOMM_STATE_IDLE; |
| dlc->session = NULL; |
| return ret; |
| } |
| |
| int bt_rfcomm_dlc_disconnect(struct bt_rfcomm_dlc *dlc) |
| { |
| BT_DBG("dlc %p", dlc); |
| |
| if (!dlc) { |
| return -EINVAL; |
| } |
| |
| if (dlc->state == BT_RFCOMM_STATE_CONNECTED) { |
| /* This is to handle user initiated disconnect to send pending |
| * bufs in the queue before disconnecting |
| * Queue a dummy buffer (in case if queue is empty) to wake up |
| * and stop the tx thread. |
| */ |
| dlc->state = BT_RFCOMM_STATE_USER_DISCONNECT; |
| net_buf_put(&dlc->tx_queue, |
| net_buf_alloc(&dummy_pool, K_NO_WAIT)); |
| |
| k_work_reschedule(&dlc->rtx_work, RFCOMM_DISC_TIMEOUT); |
| |
| return 0; |
| } |
| |
| return rfcomm_dlc_close(dlc); |
| } |
| |
| static int rfcomm_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan) |
| { |
| struct bt_rfcomm_session *session; |
| |
| BT_DBG("conn %p", conn); |
| |
| session = rfcomm_session_new(BT_RFCOMM_ROLE_ACCEPTOR); |
| if (session) { |
| *chan = &session->br_chan.chan; |
| return 0; |
| } |
| |
| BT_ERR("No available RFCOMM context for conn %p", conn); |
| |
| return -ENOMEM; |
| } |
| |
| void bt_rfcomm_init(void) |
| { |
| static struct bt_l2cap_server server = { |
| .psm = BT_L2CAP_PSM_RFCOMM, |
| .accept = rfcomm_accept, |
| .sec_level = BT_SECURITY_L1, |
| }; |
| |
| bt_l2cap_br_server_register(&server); |
| } |