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_uart/blocking_adapter.h"
16
17 #include "pw_assert/check.h"
18 #include "pw_log/log.h"
19 #include "pw_status/try.h"
20
21 namespace pw::uart {
22
~UartBlockingAdapter()23 UartBlockingAdapter::~UartBlockingAdapter() {
24 // We can't safely allow this because the driver still has a reference.
25 // The safest thing to do here is crash.
26 // Few applications are likely to ever call this destructor anyway.
27 PW_CHECK(!rx_.pending());
28 PW_CHECK(!tx_.pending());
29 }
30
31 // Uart impl.
DoTryReadFor(ByteSpan rx_buffer,size_t min_bytes,std::optional<chrono::SystemClock::duration> timeout)32 StatusWithSize UartBlockingAdapter::DoTryReadFor(
33 ByteSpan rx_buffer,
34 size_t min_bytes,
35 std::optional<chrono::SystemClock::duration> timeout) {
36 if (rx_.pending()) {
37 PW_LOG_ERROR("RX transaction already started");
38 return StatusWithSize::Unavailable();
39 }
40
41 // Start a new transfer.
42 rx_.Start();
43 auto status = uart_.ReadAtLeast(
44 rx_buffer, min_bytes, [this](Status xfer_status, ConstByteSpan buffer) {
45 rx_.Complete(StatusWithSize(xfer_status, buffer.size()));
46 });
47 if (!status.ok()) {
48 return StatusWithSize(status, 0);
49 }
50
51 // Wait for completion.
52 if (rx_.WaitForCompletion(timeout)) {
53 return rx_.result();
54 }
55
56 // Handle timeout by trying to cancel.
57 return rx_.HandleTimeout(uart_.CancelRead());
58 }
59
DoTryWriteFor(ConstByteSpan tx_buffer,std::optional<chrono::SystemClock::duration> timeout)60 StatusWithSize UartBlockingAdapter::DoTryWriteFor(
61 ConstByteSpan tx_buffer,
62 std::optional<chrono::SystemClock::duration> timeout) {
63 if (tx_.pending()) {
64 PW_LOG_ERROR("TX transaction already started");
65 return StatusWithSize::Unavailable();
66 }
67
68 // Start a new transfer.
69 tx_.Start();
70 auto status = uart_.Write(
71 tx_buffer, [this](StatusWithSize result) { tx_.Complete(result); });
72 if (!status.ok()) {
73 return StatusWithSize(status, 0);
74 }
75
76 // Wait for completion.
77 if (tx_.WaitForCompletion(timeout)) {
78 return tx_.result();
79 }
80
81 // Handle timeout by trying to cancel.
82 return tx_.HandleTimeout(uart_.CancelWrite());
83 }
84
DoFlushOutput()85 Status UartBlockingAdapter::DoFlushOutput() {
86 if (tx_.pending()) {
87 PW_LOG_ERROR("Flush or write already started");
88 return Status::Unavailable();
89 }
90
91 // Start a flush.
92 tx_.Start();
93 PW_TRY(uart_.FlushOutput([this](Status result) { tx_.Complete(result); }));
94
95 // Wait for completion.
96 tx_.WaitForCompletion();
97 return tx_.result().status();
98 }
99
100 // UartBlockingAdapter::Transfer
Complete(StatusWithSize result)101 void UartBlockingAdapter::Transfer::Complete(StatusWithSize result) {
102 result_ = result;
103 pending_ = false;
104 complete_.release();
105 }
106
WaitForCompletion(std::optional<chrono::SystemClock::duration> timeout)107 bool UartBlockingAdapter::Transfer::WaitForCompletion(
108 std::optional<chrono::SystemClock::duration> timeout) {
109 if (timeout) {
110 return complete_.try_acquire_for(*timeout);
111 }
112 complete_.acquire();
113 return true;
114 }
115
WaitForCompletion()116 void UartBlockingAdapter::Transfer::WaitForCompletion() {
117 // With no timeout, this waits forever and must return true.
118 PW_CHECK(WaitForCompletion(std::nullopt));
119 }
120
HandleTimeout(bool cancel_result)121 StatusWithSize UartBlockingAdapter::Transfer::HandleTimeout(
122 bool cancel_result) {
123 if (cancel_result) {
124 // Cancelled successfully.
125 //
126 // The callback should have been invoked with a CANCELLED status, and
127 // released the notification. Acquire the notification to safely retrieve
128 // result.size().
129 if (complete_.try_acquire()) {
130 return StatusWithSize::DeadlineExceeded(result().size());
131 }
132
133 // We couldn't acquire the notification. The driver must be broken.
134 PW_LOG_WARN(
135 "Failed to acquire %s notification after successful cancel. "
136 "UART driver seems to be broken!",
137 what_);
138 } else {
139 // Couldn't cancel.
140 //
141 // Because we definitely started a transfer, either:
142 // 1. The transaction finished just after the timeout. The callback ran
143 // (or is running); the notification was released (or is about to be
144 // released).
145 // 2. The transaction couldn't be cancelled (past some point of no return).
146 // The callback will run with a non-CANCELLED status; the semaphore will
147 // be released.
148 //
149 // We could wait again, but there's really no point: If the completion
150 // didn't already happen in the user-provided timeout, it seems unlikely to
151 // happen now.
152 //
153 // Bail.
154 PW_LOG_WARN("Failed to cancel %s transfer after timeout.", what_);
155 }
156
157 // Note that pending() may still be set, so future requests will fail.
158 return StatusWithSize::DeadlineExceeded(0);
159 }
160
161 } // namespace pw::uart
162