1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_bluetooth_sapphire/internal/host/l2cap/credit_based_flow_control_tx_engine.h"
16
17 #include <pw_bluetooth/l2cap_frames.emb.h>
18
19 #include <algorithm>
20
21 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
22 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
23
24 namespace bt::l2cap::internal {
25 namespace {
26
27 namespace emboss = pw::bluetooth::emboss;
28
29 constexpr auto kLeCreditBasedFlowControlMode =
30 CreditBasedFlowControlMode::kLeCreditBasedFlowControl;
31
32 constexpr size_t kMinimumLeMtu = 23;
33 constexpr size_t kMinimumLeMps = 23;
34 constexpr auto kPduHeaderSize = emboss::KFramePduHeader::IntrinsicSizeInBytes();
35 constexpr auto kSduHeaderSize = emboss::KFrameSduHeader::IntrinsicSizeInBytes();
36
37 constexpr uint32_t kMaxCredits = 65535;
38
39 template <typename Enum>
EnumValue(const Enum & e)40 constexpr typename std::underlying_type<Enum>::type EnumValue(const Enum& e) {
41 return static_cast<typename std::underlying_type<Enum>::type>(e);
42 }
43
44 // Returns the payload size of the next PDU needed to transmit the maximal
45 // amount of the remaining |total_payload_size| bytes.
NextPduPayloadSize(size_t total_payload_size,uint16_t max_pdu_size)46 uint16_t NextPduPayloadSize(size_t total_payload_size, uint16_t max_pdu_size) {
47 PW_DCHECK(max_pdu_size > kPduHeaderSize);
48 // Factor in the header size.
49 uint16_t max_payload_size = max_pdu_size - kPduHeaderSize;
50 // There is no risk of overflow in this static cast as any value of
51 // `total_payload_size` larger than uint16_t max will be bounded by the
52 // smaller value in `max_payload_size`.
53 return static_cast<uint16_t>(
54 std::min<size_t>(total_payload_size, max_payload_size));
55 }
56
57 // Create and initialize a K-Frame with appropriate PDU header.
58 // Returns a tuple of the frame itself and a view of the payload.
CreateFrame(uint16_t payload_size,uint16_t channel_id)59 std::tuple<DynamicByteBuffer, MutableBufferView> CreateFrame(
60 uint16_t payload_size, uint16_t channel_id) {
61 uint16_t pdu_size = kPduHeaderSize + payload_size;
62
63 DynamicByteBuffer buf(pdu_size);
64 auto header =
65 emboss::KFramePduHeaderWriter(buf.mutable_data(), kPduHeaderSize);
66 header.pdu_length().Write(payload_size);
67 header.channel_id().Write(channel_id);
68
69 MutableBufferView payload = buf.mutable_view(kPduHeaderSize);
70 return std::make_tuple(std::move(buf), payload);
71 }
72 } // namespace
73
CreditBasedFlowControlTxEngine(ChannelId channel_id,uint16_t max_tx_sdu_size,TxChannel & channel,CreditBasedFlowControlMode mode,uint16_t max_tx_pdu_size,uint16_t initial_credits)74 CreditBasedFlowControlTxEngine::CreditBasedFlowControlTxEngine(
75 ChannelId channel_id,
76 uint16_t max_tx_sdu_size,
77 TxChannel& channel,
78 CreditBasedFlowControlMode mode,
79 uint16_t max_tx_pdu_size,
80 uint16_t initial_credits)
81 : TxEngine(channel_id, max_tx_sdu_size, channel),
82 mode_(mode),
83 max_tx_pdu_size_(max_tx_pdu_size),
84 credits_(initial_credits) {
85 // The enhanced flow control mode is not yet supported.
86 PW_CHECK(mode_ == kLeCreditBasedFlowControlMode,
87 "Credit based flow control mode unsupported: 0x%.2ux",
88 EnumValue(mode));
89
90 PW_DCHECK(
91 mode != kLeCreditBasedFlowControlMode || max_tx_sdu_size > kMinimumLeMtu,
92 "Invalid MTU for LE mode: %d",
93 max_tx_sdu_size);
94 PW_DCHECK(
95 mode != kLeCreditBasedFlowControlMode || max_tx_pdu_size > kMinimumLeMps,
96 "Invalid MPS for LE mode: %d",
97 max_tx_pdu_size);
98 }
99
100 CreditBasedFlowControlTxEngine::~CreditBasedFlowControlTxEngine() = default;
101
NotifySduQueued()102 void CreditBasedFlowControlTxEngine::NotifySduQueued() { ProcessSdus(); }
103
AddCredits(uint16_t credits)104 bool CreditBasedFlowControlTxEngine::AddCredits(uint16_t credits) {
105 if (static_cast<uint32_t>(credits_) + credits > kMaxCredits)
106 return false;
107
108 credits_ += credits;
109
110 // If there are queued SDUs, use the newly added credits to send them.
111 ProcessSdus();
112 return true;
113 }
114
credits() const115 uint16_t CreditBasedFlowControlTxEngine::credits() const { return credits_; }
116
segments_count() const117 size_t CreditBasedFlowControlTxEngine::segments_count() const {
118 return segments_.size();
119 }
120
SegmentSdu(ByteBufferPtr sdu)121 void CreditBasedFlowControlTxEngine::SegmentSdu(ByteBufferPtr sdu) {
122 size_t payload_remaining = sdu->size() + kSduHeaderSize;
123
124 do {
125 // A credit based flow control packet has a fairly simple segmentation
126 // scheme where each PDU contains (at most) |max_tx_pdu_size_| - header_size
127 // bytes. The only bit of (relatively minor) complexity is the SDU size
128 // field, which is only included in the first K-Frame/PDU of the SDU. This
129 // loop iterates over each potential packet (and will only execute once if
130 // the entire SDU fits in a single PDU).
131
132 DynamicByteBuffer frame;
133 MutableBufferView payload;
134 std::tie(frame, payload) = CreateFrame(
135 NextPduPayloadSize(payload_remaining, max_tx_pdu_size_), channel_id());
136 PW_DCHECK(payload.size() <= payload_remaining);
137
138 if (payload_remaining > sdu->size()) {
139 // First frame of the SDU, write the SDU header.
140 emboss::KFrameSduHeaderWriter header(payload.mutable_data(),
141 kSduHeaderSize);
142 header.sdu_length().Write(sdu->size());
143
144 payload = payload.mutable_view(kSduHeaderSize);
145 payload_remaining -= kSduHeaderSize;
146 PW_DCHECK(payload_remaining == sdu->size());
147 }
148
149 sdu->Copy(&payload, sdu->size() - payload_remaining, payload.size());
150 payload_remaining -= payload.size();
151
152 segments_.push_back(std::make_unique<DynamicByteBuffer>(std::move(frame)));
153 } while (payload_remaining > 0);
154 }
155
TrySendSegments()156 void CreditBasedFlowControlTxEngine::TrySendSegments() {
157 while (credits_ > 0 && !segments_.empty()) {
158 channel().SendFrame(std::move(segments_.front()));
159 segments_.pop_front();
160 --credits_;
161 }
162 }
163
ProcessSdus()164 void CreditBasedFlowControlTxEngine::ProcessSdus() {
165 TrySendSegments();
166 while (credits_ > 0) {
167 std::optional<ByteBufferPtr> sdu = channel().GetNextQueuedSdu();
168
169 if (!sdu)
170 break;
171 PW_CHECK(*sdu);
172
173 if ((*sdu)->size() > max_tx_sdu_size()) {
174 bt_log(INFO,
175 "l2cap",
176 "SDU size exceeds channel TxMTU (channel-id: 0x%.4x)",
177 channel_id());
178 return;
179 }
180
181 SegmentSdu(std::move(*sdu));
182 TrySendSegments();
183 }
184 }
185
186 } // namespace bt::l2cap::internal
187