Bluetooth: RFCOMM: Implement Aggregate Flow Control
This is mainly for backward compatibility with 1.0b devices and for ICS & spec compliance. CFC is mandatory post 1.0b spec where in Aggregate FC shall not be used. Aggregate FC is managed using FCOFF and FCON messages. This is for the entire session which means that all the dlcs in that session will be affected. Implementation is done using binary semaphore wherein it will be blocked when FCOFF is recieved and unblocked in FCON. Once tx thread is scheduled then semaphore should be always available until all the buf in queue is sent. Change-Id: Ibfd2c4d033cef64c238ead83474f9e171572de1e Signed-off-by: Jaganath Kanakkassery <jaganathx.kanakkassery@intel.com>
This commit is contained in:
parent
93d7512215
commit
461591728f
2 changed files with 148 additions and 9 deletions
|
@ -294,6 +294,7 @@ static void rfcomm_dlc_disconnect(struct bt_rfcomm_dlc *dlc)
|
||||||
* dummy credit to wake it up.
|
* dummy credit to wake it up.
|
||||||
*/
|
*/
|
||||||
rfcomm_dlc_tx_give_credits(dlc, 1);
|
rfcomm_dlc_tx_give_credits(dlc, 1);
|
||||||
|
k_sem_give(&dlc->session->fc);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
rfcomm_dlc_destroy(dlc);
|
rfcomm_dlc_destroy(dlc);
|
||||||
|
@ -526,6 +527,26 @@ static int rfcomm_send_dm(struct bt_rfcomm_session *session, uint8_t dlci)
|
||||||
return bt_l2cap_chan_send(&session->br_chan.chan, buf);
|
return bt_l2cap_chan_send(&session->br_chan.chan, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void rfcomm_check_fc(struct bt_rfcomm_dlc *dlc)
|
||||||
|
{
|
||||||
|
BT_DBG("%p", dlc);
|
||||||
|
|
||||||
|
if (dlc->session->cfc == BT_RFCOMM_CFC_SUPPORTED) {
|
||||||
|
BT_DBG("Wait for credits %p", dlc);
|
||||||
|
/* Wait for credits */
|
||||||
|
k_sem_take(&dlc->tx_credits, K_FOREVER);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_sem_take(&dlc->session->fc, K_FOREVER);
|
||||||
|
|
||||||
|
/* Give the sem 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 is received.
|
||||||
|
*/
|
||||||
|
k_sem_give(&dlc->session->fc);
|
||||||
|
}
|
||||||
|
|
||||||
static void rfcomm_dlc_tx_thread(void *p1, void *p2, void *p3)
|
static void rfcomm_dlc_tx_thread(void *p1, void *p2, void *p3)
|
||||||
{
|
{
|
||||||
struct bt_rfcomm_dlc *dlc = p1;
|
struct bt_rfcomm_dlc *dlc = p1;
|
||||||
|
@ -549,9 +570,7 @@ static void rfcomm_dlc_tx_thread(void *p1, void *p2, void *p3)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
BT_DBG("Wait for credits %p", dlc);
|
rfcomm_check_fc(dlc);
|
||||||
/* Wait for credits */
|
|
||||||
k_sem_take(&dlc->tx_credits, K_FOREVER);
|
|
||||||
if (dlc->state != BT_RFCOMM_STATE_CONNECTED &&
|
if (dlc->state != BT_RFCOMM_STATE_CONNECTED &&
|
||||||
dlc->state != BT_RFCOMM_STATE_USER_DISCONNECT) {
|
dlc->state != BT_RFCOMM_STATE_USER_DISCONNECT) {
|
||||||
net_buf_unref(buf);
|
net_buf_unref(buf);
|
||||||
|
@ -700,12 +719,50 @@ static int rfcomm_send_nsc(struct bt_rfcomm_session *session, uint8_t cmd_type)
|
||||||
return bt_l2cap_chan_send(&session->br_chan.chan, buf);
|
return bt_l2cap_chan_send(&session->br_chan.chan, 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 bt_l2cap_chan_send(&session->br_chan.chan, 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 bt_l2cap_chan_send(&session->br_chan.chan, buf);
|
||||||
|
}
|
||||||
|
|
||||||
static void rfcomm_dlc_connected(struct bt_rfcomm_dlc *dlc)
|
static void rfcomm_dlc_connected(struct bt_rfcomm_dlc *dlc)
|
||||||
{
|
{
|
||||||
dlc->state = BT_RFCOMM_STATE_CONNECTED;
|
dlc->state = BT_RFCOMM_STATE_CONNECTED;
|
||||||
|
|
||||||
rfcomm_send_msc(dlc, BT_RFCOMM_MSG_CMD_CR);
|
rfcomm_send_msc(dlc, BT_RFCOMM_MSG_CMD_CR);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/* Cancel conn timer */
|
/* Cancel conn timer */
|
||||||
k_delayed_work_cancel(&dlc->rtx_work);
|
k_delayed_work_cancel(&dlc->rtx_work);
|
||||||
|
|
||||||
|
@ -859,11 +916,19 @@ static int rfcomm_send_pn(struct bt_rfcomm_dlc *dlc, uint8_t cr)
|
||||||
pn = net_buf_add(buf, sizeof(*pn));
|
pn = net_buf_add(buf, sizeof(*pn));
|
||||||
pn->dlci = dlc->dlci;
|
pn->dlci = dlc->dlci;
|
||||||
pn->mtu = sys_cpu_to_le16(dlc->mtu);
|
pn->mtu = sys_cpu_to_le16(dlc->mtu);
|
||||||
if (dlc->state == BT_RFCOMM_STATE_CONFIG) {
|
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;
|
pn->credits = dlc->rx_credit;
|
||||||
pn->flow_ctrl = cr ? 0xf0 : 0xe0;
|
if (cr) {
|
||||||
|
pn->flow_ctrl = BT_RFCOMM_PN_CFC_CMD;
|
||||||
|
} else {
|
||||||
|
pn->flow_ctrl = BT_RFCOMM_PN_CFC_RESP;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/* If PN comes in already opened dlc these should be 0*/
|
/* If PN comes in already opened dlc or cfc not supported
|
||||||
|
* these should be 0
|
||||||
|
*/
|
||||||
pn->credits = 0;
|
pn->credits = 0;
|
||||||
pn->flow_ctrl = 0;
|
pn->flow_ctrl = 0;
|
||||||
}
|
}
|
||||||
|
@ -1108,7 +1173,16 @@ static void rfcomm_handle_pn(struct bt_rfcomm_session *session,
|
||||||
BT_DBG("Incoming connection accepted dlc %p", dlc);
|
BT_DBG("Incoming connection accepted dlc %p", dlc);
|
||||||
|
|
||||||
dlc->mtu = min(dlc->mtu, sys_le16_to_cpu(pn->mtu));
|
dlc->mtu = min(dlc->mtu, sys_le16_to_cpu(pn->mtu));
|
||||||
rfcomm_dlc_tx_give_credits(dlc, pn->credits);
|
|
||||||
|
if (pn->flow_ctrl == BT_RFCOMM_PN_CFC_CMD) {
|
||||||
|
if (session->cfc == BT_RFCOMM_CFC_UNKNOWN) {
|
||||||
|
session->cfc = BT_RFCOMM_CFC_SUPPORTED;
|
||||||
|
}
|
||||||
|
rfcomm_dlc_tx_give_credits(dlc, pn->credits);
|
||||||
|
} else {
|
||||||
|
session->cfc = BT_RFCOMM_CFC_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
dlc->state = BT_RFCOMM_STATE_CONFIG;
|
dlc->state = BT_RFCOMM_STATE_CONFIG;
|
||||||
rfcomm_send_pn(dlc, BT_RFCOMM_MSG_RESP_CR);
|
rfcomm_send_pn(dlc, BT_RFCOMM_MSG_RESP_CR);
|
||||||
/* Cancel idle timer if any */
|
/* Cancel idle timer if any */
|
||||||
|
@ -1129,7 +1203,15 @@ static void rfcomm_handle_pn(struct bt_rfcomm_session *session,
|
||||||
}
|
}
|
||||||
|
|
||||||
dlc->mtu = min(dlc->mtu, sys_le16_to_cpu(pn->mtu));
|
dlc->mtu = min(dlc->mtu, sys_le16_to_cpu(pn->mtu));
|
||||||
rfcomm_dlc_tx_give_credits(dlc, pn->credits);
|
if (pn->flow_ctrl == BT_RFCOMM_PN_CFC_RESP) {
|
||||||
|
if (session->cfc == BT_RFCOMM_CFC_UNKNOWN) {
|
||||||
|
session->cfc = BT_RFCOMM_CFC_SUPPORTED;
|
||||||
|
}
|
||||||
|
rfcomm_dlc_tx_give_credits(dlc, pn->credits);
|
||||||
|
} else {
|
||||||
|
session->cfc = BT_RFCOMM_CFC_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
dlc->state = BT_RFCOMM_STATE_CONNECTING;
|
dlc->state = BT_RFCOMM_STATE_CONNECTING;
|
||||||
rfcomm_send_sabm(session, dlc->dlci);
|
rfcomm_send_sabm(session, dlc->dlci);
|
||||||
}
|
}
|
||||||
|
@ -1193,6 +1275,41 @@ static void rfcomm_handle_msg(struct bt_rfcomm_session *session,
|
||||||
rfcomm_send_test(session, BT_RFCOMM_MSG_RESP_CR, buf->data,
|
rfcomm_send_test(session, BT_RFCOMM_MSG_RESP_CR, buf->data,
|
||||||
buf->len - 1);
|
buf->len - 1);
|
||||||
break;
|
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:
|
default:
|
||||||
BT_WARN("Unknown/Unsupported RFCOMM Msg type 0x%02x", msg_type);
|
BT_WARN("Unknown/Unsupported RFCOMM Msg type 0x%02x", msg_type);
|
||||||
rfcomm_send_nsc(session, hdr->type);
|
rfcomm_send_nsc(session, hdr->type);
|
||||||
|
@ -1204,6 +1321,10 @@ static void rfcomm_dlc_update_credits(struct bt_rfcomm_dlc *dlc)
|
||||||
{
|
{
|
||||||
uint8_t credits;
|
uint8_t credits;
|
||||||
|
|
||||||
|
if (dlc->session->cfc == BT_RFCOMM_CFC_NOT_SUPPORTED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BT_DBG("dlc %p credits %u", dlc, dlc->rx_credit);
|
BT_DBG("dlc %p credits %u", dlc, dlc->rx_credit);
|
||||||
|
|
||||||
/* Only give more credits if it went below the defined threshold */
|
/* Only give more credits if it went below the defined threshold */
|
||||||
|
@ -1244,7 +1365,8 @@ static void rfcomm_handle_data(struct bt_rfcomm_session *session,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buf->len > BT_RFCOMM_FCS_SIZE) {
|
if (buf->len > BT_RFCOMM_FCS_SIZE) {
|
||||||
if (!dlc->rx_credit) {
|
if (dlc->session->cfc == BT_RFCOMM_CFC_SUPPORTED &&
|
||||||
|
!dlc->rx_credit) {
|
||||||
BT_ERR("Data recvd when rx credit is 0");
|
BT_ERR("Data recvd when rx credit is 0");
|
||||||
rfcomm_dlc_close(dlc);
|
rfcomm_dlc_close(dlc);
|
||||||
return;
|
return;
|
||||||
|
@ -1441,8 +1563,10 @@ static struct bt_rfcomm_session *rfcomm_session_new(bt_rfcomm_role_t role)
|
||||||
session->br_chan.rx.mtu = CONFIG_BLUETOOTH_RFCOMM_L2CAP_MTU;
|
session->br_chan.rx.mtu = CONFIG_BLUETOOTH_RFCOMM_L2CAP_MTU;
|
||||||
session->state = BT_RFCOMM_STATE_INIT;
|
session->state = BT_RFCOMM_STATE_INIT;
|
||||||
session->role = role;
|
session->role = role;
|
||||||
|
session->cfc = BT_RFCOMM_CFC_UNKNOWN;
|
||||||
k_delayed_work_init(&session->rtx_work,
|
k_delayed_work_init(&session->rtx_work,
|
||||||
rfcomm_session_rtx_timeout);
|
rfcomm_session_rtx_timeout);
|
||||||
|
k_sem_init(&session->fc, 0, 1);
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,16 +20,25 @@
|
||||||
|
|
||||||
#include <bluetooth/rfcomm.h>
|
#include <bluetooth/rfcomm.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BT_RFCOMM_CFC_UNKNOWN,
|
||||||
|
BT_RFCOMM_CFC_NOT_SUPPORTED,
|
||||||
|
BT_RFCOMM_CFC_SUPPORTED,
|
||||||
|
} __packed bt_rfcomm_cfc_t;
|
||||||
|
|
||||||
/* RFCOMM signalling connection specific context */
|
/* RFCOMM signalling connection specific context */
|
||||||
struct bt_rfcomm_session {
|
struct bt_rfcomm_session {
|
||||||
/* L2CAP channel this context is associated with */
|
/* L2CAP channel this context is associated with */
|
||||||
struct bt_l2cap_br_chan br_chan;
|
struct bt_l2cap_br_chan br_chan;
|
||||||
/* Response Timeout eXpired (RTX) timer */
|
/* Response Timeout eXpired (RTX) timer */
|
||||||
struct k_delayed_work rtx_work;
|
struct k_delayed_work rtx_work;
|
||||||
|
/* Binary sem for aggregate fc */
|
||||||
|
struct k_sem fc;
|
||||||
struct bt_rfcomm_dlc *dlcs;
|
struct bt_rfcomm_dlc *dlcs;
|
||||||
uint16_t mtu;
|
uint16_t mtu;
|
||||||
uint8_t state;
|
uint8_t state;
|
||||||
bt_rfcomm_role_t role;
|
bt_rfcomm_role_t role;
|
||||||
|
bt_rfcomm_cfc_t cfc;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -99,6 +108,9 @@ struct bt_rfcomm_rpn {
|
||||||
#define BT_RFCOMM_TEST 0x08
|
#define BT_RFCOMM_TEST 0x08
|
||||||
#define BT_RFCOMM_NSC 0x04
|
#define BT_RFCOMM_NSC 0x04
|
||||||
|
|
||||||
|
#define BT_RFCOMM_FCON 0x28
|
||||||
|
#define BT_RFCOMM_FCOFF 0x18
|
||||||
|
|
||||||
/* Default RPN Settings */
|
/* Default RPN Settings */
|
||||||
#define BT_RFCOMM_RPN_BAUD_RATE_9600 0x03
|
#define BT_RFCOMM_RPN_BAUD_RATE_9600 0x03
|
||||||
#define BT_RFCOMM_RPN_DATA_BITS_8 0x03
|
#define BT_RFCOMM_RPN_DATA_BITS_8 0x03
|
||||||
|
@ -203,5 +215,8 @@ struct bt_rfcomm_rpn {
|
||||||
#define BT_RFCOMM_PF_UIH_CREDIT 1
|
#define BT_RFCOMM_PF_UIH_CREDIT 1
|
||||||
#define BT_RFCOMM_PF_UIH_NO_CREDIT 0
|
#define BT_RFCOMM_PF_UIH_NO_CREDIT 0
|
||||||
|
|
||||||
|
#define BT_RFCOMM_PN_CFC_CMD 0xf0
|
||||||
|
#define BT_RFCOMM_PN_CFC_RESP 0xe0
|
||||||
|
|
||||||
/* Initialize RFCOMM signal layer */
|
/* Initialize RFCOMM signal layer */
|
||||||
void bt_rfcomm_init(void);
|
void bt_rfcomm_init(void);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue