xref: /aosp_15_r20/external/pigweed/pw_uart/blocking_adapter.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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