Message ID | 20240501100859.175690-1-surban@surban.net (mailing list archive) |
---|---|
State | Accepted |
Commit | e8dda7907df8532ae8cd79c3569a9524c5886ac9 |
Headers | show |
Series | [v8] Bluetooth: compute LE flow credits based on recvbuf space | expand |
Context | Check | Description |
---|---|---|
tedd_an/pre-ci_am | success | Success |
tedd_an/CheckPatch | success | CheckPatch PASS |
tedd_an/GitLint | success | Gitlint PASS |
tedd_an/SubjectPrefix | success | Gitlint PASS |
tedd_an/BuildKernel | success | BuildKernel PASS |
tedd_an/CheckAllWarning | success | CheckAllWarning PASS |
tedd_an/CheckSparse | success | CheckSparse PASS |
tedd_an/CheckSmatch | fail | CheckSparse: FAIL: Segmentation fault (core dumped) make[4]: *** [scripts/Makefile.build:244: net/bluetooth/hci_core.o] Error 139 make[4]: *** Deleting file 'net/bluetooth/hci_core.o' make[3]: *** [scripts/Makefile.build:485: net/bluetooth] Error 2 make[2]: *** [scripts/Makefile.build:485: net] Error 2 make[2]: *** Waiting for unfinished jobs.... Segmentation fault (core dumped) make[4]: *** [scripts/Makefile.build:244: drivers/bluetooth/bcm203x.o] Error 139 make[4]: *** Deleting file 'drivers/bluetooth/bcm203x.o' make[4]: *** Waiting for unfinished jobs.... make[3]: *** [scripts/Makefile.build:485: drivers/bluetooth] Error 2 make[2]: *** [scripts/Makefile.build:485: drivers] Error 2 make[1]: *** [/github/workspace/src/src/Makefile:1919: .] Error 2 make: *** [Makefile:240: __sub-make] Error 2 |
tedd_an/BuildKernel32 | success | BuildKernel32 PASS |
tedd_an/TestRunnerSetup | success | TestRunnerSetup PASS |
tedd_an/TestRunner_l2cap-tester | success | TestRunner PASS |
tedd_an/TestRunner_iso-tester | success | TestRunner PASS |
tedd_an/TestRunner_bnep-tester | success | TestRunner PASS |
tedd_an/TestRunner_mgmt-tester | success | TestRunner PASS |
tedd_an/TestRunner_rfcomm-tester | success | TestRunner PASS |
tedd_an/TestRunner_sco-tester | success | TestRunner PASS |
tedd_an/TestRunner_ioctl-tester | success | TestRunner PASS |
tedd_an/TestRunner_mesh-tester | success | TestRunner PASS |
tedd_an/TestRunner_smp-tester | success | TestRunner PASS |
tedd_an/TestRunner_userchan-tester | success | TestRunner PASS |
tedd_an/IncrementalBuild | success | Incremental Build PASS |
This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=849612 ---Test result--- Test Summary: CheckPatch PASS 1.23 seconds GitLint PASS 0.33 seconds SubjectPrefix PASS 0.13 seconds BuildKernel PASS 29.87 seconds CheckAllWarning PASS 32.65 seconds CheckSparse PASS 37.96 seconds CheckSmatch FAIL 34.70 seconds BuildKernel32 PASS 29.03 seconds TestRunnerSetup PASS 517.01 seconds TestRunner_l2cap-tester PASS 20.73 seconds TestRunner_iso-tester PASS 28.64 seconds TestRunner_bnep-tester PASS 4.69 seconds TestRunner_mgmt-tester PASS 108.29 seconds TestRunner_rfcomm-tester PASS 7.18 seconds TestRunner_sco-tester PASS 14.97 seconds TestRunner_ioctl-tester PASS 7.65 seconds TestRunner_mesh-tester PASS 5.78 seconds TestRunner_smp-tester PASS 6.73 seconds TestRunner_userchan-tester PASS 4.91 seconds IncrementalBuild PASS 27.92 seconds Details ############################## Test: CheckSmatch - FAIL Desc: Run smatch tool with source Output: Segmentation fault (core dumped) make[4]: *** [scripts/Makefile.build:244: net/bluetooth/hci_core.o] Error 139 make[4]: *** Deleting file 'net/bluetooth/hci_core.o' make[3]: *** [scripts/Makefile.build:485: net/bluetooth] Error 2 make[2]: *** [scripts/Makefile.build:485: net] Error 2 make[2]: *** Waiting for unfinished jobs.... Segmentation fault (core dumped) make[4]: *** [scripts/Makefile.build:244: drivers/bluetooth/bcm203x.o] Error 139 make[4]: *** Deleting file 'drivers/bluetooth/bcm203x.o' make[4]: *** Waiting for unfinished jobs.... make[3]: *** [scripts/Makefile.build:485: drivers/bluetooth] Error 2 make[2]: *** [scripts/Makefile.build:485: drivers] Error 2 make[1]: *** [/github/workspace/src/src/Makefile:1919: .] Error 2 make: *** [Makefile:240: __sub-make] Error 2 --- Regards, Linux Bluetooth
Hi, ke, 2024-05-01 kello 12:08 +0200, Sebastian Urban kirjoitti: > Previously LE flow credits were returned to the > sender even if the socket's receive buffer was > full. This meant that no back-pressure > was applied to the sender, thus it continued to > send data, resulting in data loss without any > error being reported. Furthermore, the amount > of credits was essentially fixed to a small > amount, leading to reduced performance. > > This is fixed by computing the number of returned > LE flow credits based on the estimated available > space in the receive buffer of an L2CAP socket. > Consequently, if the receive buffer is full, no > credits are returned until the buffer is read and > thus cleared by user-space. > > Since the computation of available receive buffer > space can only be performed approximately (due to > sk_buff overhead) and the receive buffer size may > be changed by user-space after flow credits have > been sent, superfluous received data is temporary > stored within l2cap_pinfo. This is necessary > because Bluetooth LE provides no retransmission > mechanism once the data has been acked by the > physical layer. > > If receive buffer space estimation is not possible > at the moment, we fall back to providing credits > for one full packet as before. This is currently > the case during connection setup, when MPS is not > yet available. > > Signed-off-by: Sebastian Urban <surban@surban.net> > --- > include/net/bluetooth/l2cap.h | 11 ++++- > net/bluetooth/l2cap_core.c | 56 ++++++++++++++++++--- > net/bluetooth/l2cap_sock.c | 91 ++++++++++++++++++++++++++++------- > 3 files changed, 132 insertions(+), 26 deletions(-) > > diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h > index b6eac37f5b74..2dd77de38d1d 100644 > --- a/include/net/bluetooth/l2cap.h > +++ b/include/net/bluetooth/l2cap.h > @@ -554,6 +554,9 @@ struct l2cap_chan { > __u16 tx_credits; > __u16 rx_credits; > > + /* estimated available receive buffer space or -1 if unknown */ > + ssize_t rx_avail; > + > __u8 tx_state; > __u8 rx_state; > > @@ -688,10 +691,15 @@ struct l2cap_user { > /* ----- L2CAP socket info ----- */ > #define l2cap_pi(sk) ((struct l2cap_pinfo *) sk) > > +struct l2cap_rx_busy { > + struct list_head list; > + struct sk_buff *skb; > +}; > + > struct l2cap_pinfo { > struct bt_sock bt; > struct l2cap_chan *chan; > - struct sk_buff *rx_busy_skb; > + struct list_head rx_busy; Does it need to be custom list, or could this be skb queue instead (struct sk_buff_head)? AFAICS, if these skb are going to go to __sock_queue_rcv_skb() they probably can be put to queue before that. > > enum { > @@ -950,6 +958,7 @@ int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu); > int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len, > const struct sockcm_cookie *sockc); > void l2cap_chan_busy(struct l2cap_chan *chan, int busy); > +void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail); > int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator); > void l2cap_chan_set_defaults(struct l2cap_chan *chan); > int l2cap_ertm_init(struct l2cap_chan *chan); > diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c > index 142cc1eaeefa..b818660ae170 100644 > --- a/net/bluetooth/l2cap_core.c > +++ b/net/bluetooth/l2cap_core.c > @@ -454,6 +454,9 @@ struct l2cap_chan *l2cap_chan_create(void) > /* Set default lock nesting level */ > atomic_set(&chan->nesting, L2CAP_NESTING_NORMAL); > > + /* Available receive buffer space is initially unknown */ > + chan->rx_avail = -1; > + > write_lock(&chan_list_lock); > list_add(&chan->global_l, &chan_list); > write_unlock(&chan_list_lock); > @@ -535,6 +538,28 @@ void l2cap_chan_set_defaults(struct l2cap_chan *chan) > } > EXPORT_SYMBOL_GPL(l2cap_chan_set_defaults); > > +static __u16 l2cap_le_rx_credits(struct l2cap_chan *chan) > +{ > + size_t sdu_len = chan->sdu ? chan->sdu->len : 0; > + > + if (chan->mps == 0) > + return 0; > + > + /* If we don't know the available space in the receiver buffer, give > + * enough credits for a full packet. > + */ > + if (chan->rx_avail == -1) > + return (chan->imtu / chan->mps) + 1; > + > + /* If we know how much space is available in the receive buffer, give > + * out as many credits as would fill the buffer. > + */ > + if (chan->rx_avail <= sdu_len) > + return 0; > + > + return DIV_ROUND_UP(chan->rx_avail - sdu_len, chan->mps); > +} > + > static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits) > { > chan->sdu = NULL; > @@ -543,8 +568,7 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits) > chan->tx_credits = tx_credits; > /* Derive MPS from connection MTU to stop HCI fragmentation */ > chan->mps = min_t(u16, chan->imtu, chan->conn->mtu - L2CAP_HDR_SIZE); > - /* Give enough credits for a full packet */ > - chan->rx_credits = (chan->imtu / chan->mps) + 1; > + chan->rx_credits = l2cap_le_rx_credits(chan); > > skb_queue_head_init(&chan->tx_q); > } > @@ -556,7 +580,7 @@ static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits) > /* L2CAP implementations shall support a minimum MPS of 64 octets */ > if (chan->mps < L2CAP_ECRED_MIN_MPS) { > chan->mps = L2CAP_ECRED_MIN_MPS; > - chan->rx_credits = (chan->imtu / chan->mps) + 1; > + chan->rx_credits = l2cap_le_rx_credits(chan); > } > } > > @@ -6519,9 +6543,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan) > { > struct l2cap_conn *conn = chan->conn; > struct l2cap_le_credits pkt; > - u16 return_credits; > - > - return_credits = (chan->imtu / chan->mps) + 1; > + u16 return_credits = l2cap_le_rx_credits(chan); > > if (chan->rx_credits >= return_credits) > return; > @@ -6540,6 +6562,19 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan) > l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt); > } > > +void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail) > +{ > + if (chan->rx_avail == rx_avail) > + return; > + > + BT_DBG("chan %p has %zd bytes avail for rx", chan, rx_avail); > + > + chan->rx_avail = rx_avail; > + > + if (chan->state == BT_CONNECTED) > + l2cap_chan_le_send_credits(chan); > +} > + > static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb) > { > int err; > @@ -6549,6 +6584,12 @@ static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb) > /* Wait recv to confirm reception before updating the credits */ > err = chan->ops->recv(chan, skb); > > + if (err < 0 && chan->rx_avail != -1) { > + BT_ERR("Queueing received LE L2CAP data failed"); > + l2cap_send_disconn_req(chan, ECONNRESET); > + return err; > + } > + > /* Update credits whenever an SDU is received */ > l2cap_chan_le_send_credits(chan); > > @@ -6571,7 +6612,8 @@ static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb) > } > > chan->rx_credits--; > - BT_DBG("rx_credits %u -> %u", chan->rx_credits + 1, chan->rx_credits); > + BT_DBG("chan %p: rx_credits %u -> %u", > + chan, chan->rx_credits + 1, chan->rx_credits); > > /* Update if remote had run out of credits, this should only happens > * if the remote is not using the entire MPS. > diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c > index 53a4c0db3be7..03d904d6bfc7 100644 > --- a/net/bluetooth/l2cap_sock.c > +++ b/net/bluetooth/l2cap_sock.c > @@ -1140,6 +1140,34 @@ static int l2cap_sock_sendmsg(struct socket *sock, struct msghdr *msg, > return err; > } > > +static void l2cap_publish_rx_avail(struct l2cap_chan *chan) > +{ > + struct sock *sk = chan->data; > + ssize_t avail = sk->sk_rcvbuf - atomic_read(&sk->sk_rmem_alloc); > + int expected_skbs, skb_overhead; > + > + if (avail <= 0) { > + l2cap_chan_rx_avail(chan, 0); > + return; > + } > + > + if (!chan->mps) { > + l2cap_chan_rx_avail(chan, -1); > + return; > + } > + > + /* Correct available memory by estimated sk_buff overhead. > + * This is significant due to small transfer sizes. However, accept > + * at least one full packet if receive space is non-zero. > + */ > + expected_skbs = DIV_ROUND_UP(avail, chan->mps); > + skb_overhead = expected_skbs * sizeof(struct sk_buff); > + if (skb_overhead < avail) > + l2cap_chan_rx_avail(chan, avail - skb_overhead); > + else > + l2cap_chan_rx_avail(chan, -1); > +} > + > static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg, > size_t len, int flags) > { > @@ -1180,28 +1208,33 @@ static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg, > else > err = bt_sock_recvmsg(sock, msg, len, flags); > > - if (pi->chan->mode != L2CAP_MODE_ERTM) > + if (pi->chan->mode != L2CAP_MODE_ERTM && > + pi->chan->mode != L2CAP_MODE_LE_FLOWCTL && > + pi->chan->mode != L2CAP_MODE_EXT_FLOWCTL) > return err; > > - /* Attempt to put pending rx data in the socket buffer */ > - > lock_sock(sk); > > - if (!test_bit(CONN_LOCAL_BUSY, &pi->chan->conn_state)) > - goto done; > + l2cap_publish_rx_avail(pi->chan); > > - if (pi->rx_busy_skb) { > - if (!__sock_queue_rcv_skb(sk, pi->rx_busy_skb)) > - pi->rx_busy_skb = NULL; > - else > + /* Attempt to put pending rx data in the socket buffer */ > + while (!list_empty(&pi->rx_busy)) { > + struct l2cap_rx_busy *rx_busy = > + list_first_entry(&pi->rx_busy, > + struct l2cap_rx_busy, > + list); > + if (__sock_queue_rcv_skb(sk, rx_busy->skb) < 0) > goto done; > + list_del(&rx_busy->list); > + kfree(rx_busy); > } > > /* Restore data flow when half of the receive buffer is > * available. This avoids resending large numbers of > * frames. > */ > - if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf >> 1) > + if (test_bit(CONN_LOCAL_BUSY, &pi->chan->conn_state) && > + atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf >> 1) > l2cap_chan_busy(pi->chan, 0); > > done: > @@ -1462,17 +1495,20 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan) > static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb) > { > struct sock *sk = chan->data; > + struct l2cap_pinfo *pi = l2cap_pi(sk); > int err; > > lock_sock(sk); > > - if (l2cap_pi(sk)->rx_busy_skb) { > + if (chan->mode == L2CAP_MODE_ERTM && !list_empty(&pi->rx_busy)) { > err = -ENOMEM; > goto done; > } > > if (chan->mode != L2CAP_MODE_ERTM && > - chan->mode != L2CAP_MODE_STREAMING) { > + chan->mode != L2CAP_MODE_STREAMING && > + chan->mode != L2CAP_MODE_LE_FLOWCTL && > + chan->mode != L2CAP_MODE_EXT_FLOWCTL) { > /* Even if no filter is attached, we could potentially > * get errors from security modules, etc. > */ > @@ -1483,7 +1519,9 @@ static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb) > > err = __sock_queue_rcv_skb(sk, skb); > > - /* For ERTM, handle one skb that doesn't fit into the recv > + l2cap_publish_rx_avail(chan); > + > + /* For ERTM and LE, handle a skb that doesn't fit into the recv > * buffer. This is important to do because the data frames > * have already been acked, so the skb cannot be discarded. > * > @@ -1492,8 +1530,18 @@ static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb) > * acked and reassembled until there is buffer space > * available. > */ > - if (err < 0 && chan->mode == L2CAP_MODE_ERTM) { > - l2cap_pi(sk)->rx_busy_skb = skb; > + if (err < 0 && > + (chan->mode == L2CAP_MODE_ERTM || > + chan->mode == L2CAP_MODE_LE_FLOWCTL || > + chan->mode == L2CAP_MODE_EXT_FLOWCTL)) { > + struct l2cap_rx_busy *rx_busy = > + kmalloc(sizeof(*rx_busy), GFP_KERNEL); > + if (!rx_busy) { > + err = -ENOMEM; > + goto done; > + } > + rx_busy->skb = skb; > + list_add_tail(&rx_busy->list, &pi->rx_busy); > l2cap_chan_busy(chan, 1); > err = 0; > } > @@ -1719,6 +1767,8 @@ static const struct l2cap_ops l2cap_chan_ops = { > > static void l2cap_sock_destruct(struct sock *sk) > { > + struct l2cap_rx_busy *rx_busy, *next; > + > BT_DBG("sk %p", sk); > > if (l2cap_pi(sk)->chan) { > @@ -1726,9 +1776,10 @@ static void l2cap_sock_destruct(struct sock *sk) > l2cap_chan_put(l2cap_pi(sk)->chan); > } > > - if (l2cap_pi(sk)->rx_busy_skb) { > - kfree_skb(l2cap_pi(sk)->rx_busy_skb); > - l2cap_pi(sk)->rx_busy_skb = NULL; > + list_for_each_entry_safe(rx_busy, next, &l2cap_pi(sk)->rx_busy, list) { > + kfree_skb(rx_busy->skb); > + list_del(&rx_busy->list); > + kfree(rx_busy); > } > > skb_queue_purge(&sk->sk_receive_queue); > @@ -1812,6 +1863,8 @@ static void l2cap_sock_init(struct sock *sk, struct sock *parent) > > chan->data = sk; > chan->ops = &l2cap_chan_ops; > + > + l2cap_publish_rx_avail(chan); > } > > static struct proto l2cap_proto = { > @@ -1833,6 +1886,8 @@ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, > sk->sk_destruct = l2cap_sock_destruct; > sk->sk_sndtimeo = L2CAP_CONN_TIMEOUT; > > + INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy); > + > chan = l2cap_chan_create(); > if (!chan) { > sk_free(sk);
On 5/1/24 12:43, Pauli Virtanen wrote: >> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h >> index b6eac37f5b74..2dd77de38d1d 100644 >> --- a/include/net/bluetooth/l2cap.h >> +++ b/include/net/bluetooth/l2cap.h >> @@ -554,6 +554,9 @@ struct l2cap_chan { >> __u16 tx_credits; >> __u16 rx_credits; >> >> + /* estimated available receive buffer space or -1 if unknown */ >> + ssize_t rx_avail; >> + >> __u8 tx_state; >> __u8 rx_state; >> >> @@ -688,10 +691,15 @@ struct l2cap_user { >> /* ----- L2CAP socket info ----- */ >> #define l2cap_pi(sk) ((struct l2cap_pinfo *) sk) >> >> +struct l2cap_rx_busy { >> + struct list_head list; >> + struct sk_buff *skb; >> +}; >> + >> struct l2cap_pinfo { >> struct bt_sock bt; >> struct l2cap_chan *chan; >> - struct sk_buff *rx_busy_skb; >> + struct list_head rx_busy; > > Does it need to be custom list, or could this be skb queue instead > (struct sk_buff_head)? > > AFAICS, if these skb are going to go to __sock_queue_rcv_skb() they > probably can be put to queue before that. Yes, it must be a custom list. During dequeueing we don't know how many skbs will fit in the socket's receive buffer and I am not aware of any method to partially put an skb queue into a socket's receive buffer. -- Sebastian Urban
Hello: This patch was applied to bluetooth/bluetooth-next.git (master) by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>: On Wed, 1 May 2024 12:08:58 +0200 you wrote: > Previously LE flow credits were returned to the > sender even if the socket's receive buffer was > full. This meant that no back-pressure > was applied to the sender, thus it continued to > send data, resulting in data loss without any > error being reported. Furthermore, the amount > of credits was essentially fixed to a small > amount, leading to reduced performance. > > [...] Here is the summary with links: - [v8] Bluetooth: compute LE flow credits based on recvbuf space https://git.kernel.org/bluetooth/bluetooth-next/c/e8dda7907df8 You are awesome, thank you!
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index b6eac37f5b74..2dd77de38d1d 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -554,6 +554,9 @@ struct l2cap_chan { __u16 tx_credits; __u16 rx_credits; + /* estimated available receive buffer space or -1 if unknown */ + ssize_t rx_avail; + __u8 tx_state; __u8 rx_state; @@ -688,10 +691,15 @@ struct l2cap_user { /* ----- L2CAP socket info ----- */ #define l2cap_pi(sk) ((struct l2cap_pinfo *) sk) +struct l2cap_rx_busy { + struct list_head list; + struct sk_buff *skb; +}; + struct l2cap_pinfo { struct bt_sock bt; struct l2cap_chan *chan; - struct sk_buff *rx_busy_skb; + struct list_head rx_busy; }; enum { @@ -950,6 +958,7 @@ int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu); int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len, const struct sockcm_cookie *sockc); void l2cap_chan_busy(struct l2cap_chan *chan, int busy); +void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail); int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator); void l2cap_chan_set_defaults(struct l2cap_chan *chan); int l2cap_ertm_init(struct l2cap_chan *chan); diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 142cc1eaeefa..b818660ae170 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -454,6 +454,9 @@ struct l2cap_chan *l2cap_chan_create(void) /* Set default lock nesting level */ atomic_set(&chan->nesting, L2CAP_NESTING_NORMAL); + /* Available receive buffer space is initially unknown */ + chan->rx_avail = -1; + write_lock(&chan_list_lock); list_add(&chan->global_l, &chan_list); write_unlock(&chan_list_lock); @@ -535,6 +538,28 @@ void l2cap_chan_set_defaults(struct l2cap_chan *chan) } EXPORT_SYMBOL_GPL(l2cap_chan_set_defaults); +static __u16 l2cap_le_rx_credits(struct l2cap_chan *chan) +{ + size_t sdu_len = chan->sdu ? chan->sdu->len : 0; + + if (chan->mps == 0) + return 0; + + /* If we don't know the available space in the receiver buffer, give + * enough credits for a full packet. + */ + if (chan->rx_avail == -1) + return (chan->imtu / chan->mps) + 1; + + /* If we know how much space is available in the receive buffer, give + * out as many credits as would fill the buffer. + */ + if (chan->rx_avail <= sdu_len) + return 0; + + return DIV_ROUND_UP(chan->rx_avail - sdu_len, chan->mps); +} + static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits) { chan->sdu = NULL; @@ -543,8 +568,7 @@ static void l2cap_le_flowctl_init(struct l2cap_chan *chan, u16 tx_credits) chan->tx_credits = tx_credits; /* Derive MPS from connection MTU to stop HCI fragmentation */ chan->mps = min_t(u16, chan->imtu, chan->conn->mtu - L2CAP_HDR_SIZE); - /* Give enough credits for a full packet */ - chan->rx_credits = (chan->imtu / chan->mps) + 1; + chan->rx_credits = l2cap_le_rx_credits(chan); skb_queue_head_init(&chan->tx_q); } @@ -556,7 +580,7 @@ static void l2cap_ecred_init(struct l2cap_chan *chan, u16 tx_credits) /* L2CAP implementations shall support a minimum MPS of 64 octets */ if (chan->mps < L2CAP_ECRED_MIN_MPS) { chan->mps = L2CAP_ECRED_MIN_MPS; - chan->rx_credits = (chan->imtu / chan->mps) + 1; + chan->rx_credits = l2cap_le_rx_credits(chan); } } @@ -6519,9 +6543,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan) { struct l2cap_conn *conn = chan->conn; struct l2cap_le_credits pkt; - u16 return_credits; - - return_credits = (chan->imtu / chan->mps) + 1; + u16 return_credits = l2cap_le_rx_credits(chan); if (chan->rx_credits >= return_credits) return; @@ -6540,6 +6562,19 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan) l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt); } +void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail) +{ + if (chan->rx_avail == rx_avail) + return; + + BT_DBG("chan %p has %zd bytes avail for rx", chan, rx_avail); + + chan->rx_avail = rx_avail; + + if (chan->state == BT_CONNECTED) + l2cap_chan_le_send_credits(chan); +} + static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb) { int err; @@ -6549,6 +6584,12 @@ static int l2cap_ecred_recv(struct l2cap_chan *chan, struct sk_buff *skb) /* Wait recv to confirm reception before updating the credits */ err = chan->ops->recv(chan, skb); + if (err < 0 && chan->rx_avail != -1) { + BT_ERR("Queueing received LE L2CAP data failed"); + l2cap_send_disconn_req(chan, ECONNRESET); + return err; + } + /* Update credits whenever an SDU is received */ l2cap_chan_le_send_credits(chan); @@ -6571,7 +6612,8 @@ static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb) } chan->rx_credits--; - BT_DBG("rx_credits %u -> %u", chan->rx_credits + 1, chan->rx_credits); + BT_DBG("chan %p: rx_credits %u -> %u", + chan, chan->rx_credits + 1, chan->rx_credits); /* Update if remote had run out of credits, this should only happens * if the remote is not using the entire MPS. diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 53a4c0db3be7..03d904d6bfc7 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -1140,6 +1140,34 @@ static int l2cap_sock_sendmsg(struct socket *sock, struct msghdr *msg, return err; } +static void l2cap_publish_rx_avail(struct l2cap_chan *chan) +{ + struct sock *sk = chan->data; + ssize_t avail = sk->sk_rcvbuf - atomic_read(&sk->sk_rmem_alloc); + int expected_skbs, skb_overhead; + + if (avail <= 0) { + l2cap_chan_rx_avail(chan, 0); + return; + } + + if (!chan->mps) { + l2cap_chan_rx_avail(chan, -1); + return; + } + + /* Correct available memory by estimated sk_buff overhead. + * This is significant due to small transfer sizes. However, accept + * at least one full packet if receive space is non-zero. + */ + expected_skbs = DIV_ROUND_UP(avail, chan->mps); + skb_overhead = expected_skbs * sizeof(struct sk_buff); + if (skb_overhead < avail) + l2cap_chan_rx_avail(chan, avail - skb_overhead); + else + l2cap_chan_rx_avail(chan, -1); +} + static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, int flags) { @@ -1180,28 +1208,33 @@ static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg, else err = bt_sock_recvmsg(sock, msg, len, flags); - if (pi->chan->mode != L2CAP_MODE_ERTM) + if (pi->chan->mode != L2CAP_MODE_ERTM && + pi->chan->mode != L2CAP_MODE_LE_FLOWCTL && + pi->chan->mode != L2CAP_MODE_EXT_FLOWCTL) return err; - /* Attempt to put pending rx data in the socket buffer */ - lock_sock(sk); - if (!test_bit(CONN_LOCAL_BUSY, &pi->chan->conn_state)) - goto done; + l2cap_publish_rx_avail(pi->chan); - if (pi->rx_busy_skb) { - if (!__sock_queue_rcv_skb(sk, pi->rx_busy_skb)) - pi->rx_busy_skb = NULL; - else + /* Attempt to put pending rx data in the socket buffer */ + while (!list_empty(&pi->rx_busy)) { + struct l2cap_rx_busy *rx_busy = + list_first_entry(&pi->rx_busy, + struct l2cap_rx_busy, + list); + if (__sock_queue_rcv_skb(sk, rx_busy->skb) < 0) goto done; + list_del(&rx_busy->list); + kfree(rx_busy); } /* Restore data flow when half of the receive buffer is * available. This avoids resending large numbers of * frames. */ - if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf >> 1) + if (test_bit(CONN_LOCAL_BUSY, &pi->chan->conn_state) && + atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf >> 1) l2cap_chan_busy(pi->chan, 0); done: @@ -1462,17 +1495,20 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan) static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb) { struct sock *sk = chan->data; + struct l2cap_pinfo *pi = l2cap_pi(sk); int err; lock_sock(sk); - if (l2cap_pi(sk)->rx_busy_skb) { + if (chan->mode == L2CAP_MODE_ERTM && !list_empty(&pi->rx_busy)) { err = -ENOMEM; goto done; } if (chan->mode != L2CAP_MODE_ERTM && - chan->mode != L2CAP_MODE_STREAMING) { + chan->mode != L2CAP_MODE_STREAMING && + chan->mode != L2CAP_MODE_LE_FLOWCTL && + chan->mode != L2CAP_MODE_EXT_FLOWCTL) { /* Even if no filter is attached, we could potentially * get errors from security modules, etc. */ @@ -1483,7 +1519,9 @@ static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb) err = __sock_queue_rcv_skb(sk, skb); - /* For ERTM, handle one skb that doesn't fit into the recv + l2cap_publish_rx_avail(chan); + + /* For ERTM and LE, handle a skb that doesn't fit into the recv * buffer. This is important to do because the data frames * have already been acked, so the skb cannot be discarded. * @@ -1492,8 +1530,18 @@ static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb) * acked and reassembled until there is buffer space * available. */ - if (err < 0 && chan->mode == L2CAP_MODE_ERTM) { - l2cap_pi(sk)->rx_busy_skb = skb; + if (err < 0 && + (chan->mode == L2CAP_MODE_ERTM || + chan->mode == L2CAP_MODE_LE_FLOWCTL || + chan->mode == L2CAP_MODE_EXT_FLOWCTL)) { + struct l2cap_rx_busy *rx_busy = + kmalloc(sizeof(*rx_busy), GFP_KERNEL); + if (!rx_busy) { + err = -ENOMEM; + goto done; + } + rx_busy->skb = skb; + list_add_tail(&rx_busy->list, &pi->rx_busy); l2cap_chan_busy(chan, 1); err = 0; } @@ -1719,6 +1767,8 @@ static const struct l2cap_ops l2cap_chan_ops = { static void l2cap_sock_destruct(struct sock *sk) { + struct l2cap_rx_busy *rx_busy, *next; + BT_DBG("sk %p", sk); if (l2cap_pi(sk)->chan) { @@ -1726,9 +1776,10 @@ static void l2cap_sock_destruct(struct sock *sk) l2cap_chan_put(l2cap_pi(sk)->chan); } - if (l2cap_pi(sk)->rx_busy_skb) { - kfree_skb(l2cap_pi(sk)->rx_busy_skb); - l2cap_pi(sk)->rx_busy_skb = NULL; + list_for_each_entry_safe(rx_busy, next, &l2cap_pi(sk)->rx_busy, list) { + kfree_skb(rx_busy->skb); + list_del(&rx_busy->list); + kfree(rx_busy); } skb_queue_purge(&sk->sk_receive_queue); @@ -1812,6 +1863,8 @@ static void l2cap_sock_init(struct sock *sk, struct sock *parent) chan->data = sk; chan->ops = &l2cap_chan_ops; + + l2cap_publish_rx_avail(chan); } static struct proto l2cap_proto = { @@ -1833,6 +1886,8 @@ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, sk->sk_destruct = l2cap_sock_destruct; sk->sk_sndtimeo = L2CAP_CONN_TIMEOUT; + INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy); + chan = l2cap_chan_create(); if (!chan) { sk_free(sk);
Previously LE flow credits were returned to the sender even if the socket's receive buffer was full. This meant that no back-pressure was applied to the sender, thus it continued to send data, resulting in data loss without any error being reported. Furthermore, the amount of credits was essentially fixed to a small amount, leading to reduced performance. This is fixed by computing the number of returned LE flow credits based on the estimated available space in the receive buffer of an L2CAP socket. Consequently, if the receive buffer is full, no credits are returned until the buffer is read and thus cleared by user-space. Since the computation of available receive buffer space can only be performed approximately (due to sk_buff overhead) and the receive buffer size may be changed by user-space after flow credits have been sent, superfluous received data is temporary stored within l2cap_pinfo. This is necessary because Bluetooth LE provides no retransmission mechanism once the data has been acked by the physical layer. If receive buffer space estimation is not possible at the moment, we fall back to providing credits for one full packet as before. This is currently the case during connection setup, when MPS is not yet available. Signed-off-by: Sebastian Urban <surban@surban.net> --- include/net/bluetooth/l2cap.h | 11 ++++- net/bluetooth/l2cap_core.c | 56 ++++++++++++++++++--- net/bluetooth/l2cap_sock.c | 91 ++++++++++++++++++++++++++++------- 3 files changed, 132 insertions(+), 26 deletions(-)