xref: /aosp_15_r20/external/pigweed/pw_channel/rp2_stdio_channel.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_channel/rp2_stdio_channel.h"
16 
17 #include "pico/stdlib.h"
18 #include "pw_assert/check.h"
19 #include "pw_async2/dispatcher_base.h"
20 #include "pw_log/log.h"
21 #include "pw_multibuf/allocator.h"
22 #include "pw_multibuf/multibuf.h"
23 #include "pw_status/status.h"
24 
25 namespace pw::channel {
26 namespace {
27 
28 using ::pw::async2::Context;
29 using ::pw::async2::Pending;
30 using ::pw::async2::Poll;
31 using ::pw::async2::Ready;
32 using ::pw::async2::Waker;
33 using ::pw::multibuf::MultiBuf;
34 using ::pw::multibuf::MultiBufAllocationFuture;
35 using ::pw::multibuf::MultiBufAllocator;
36 
37 Waker global_chars_available_waker;
38 
InitStdio()39 void InitStdio() {
40   stdio_init_all();
41   stdio_set_chars_available_callback(
42       []([[maybe_unused]] void* arg) {
43         std::move(global_chars_available_waker).Wake();
44       },
45       nullptr);
46 }
47 
WriteMultiBuf(const MultiBuf & buf)48 void WriteMultiBuf(const MultiBuf& buf) {
49   for (std::byte b : buf) {
50     putchar_raw(static_cast<int>(b));
51   }
52 }
53 
PollReadByte(Context & cx)54 Poll<std::byte> PollReadByte(Context& cx) {
55   int c = getchar_timeout_us(0);
56   if (c == PICO_ERROR_TIMEOUT) {
57     PW_ASYNC_STORE_WAKER(
58         cx,
59         global_chars_available_waker,
60         "RP2StdioChannel is waiting for stdio chars available");
61     // Read again to ensure that no race occurred.
62     //
63     // The concern is an interleaving like this
64     // Thread one: get_char is called and times out
65     // Thread two: char becomes available, Wake is called
66     // Thread one: sets Waker
67     //
68     // In this interleaving, the task on Thread one is never awoken,
69     // so we must check for available characters *after* setting the Waker.
70     c = getchar_timeout_us(0);
71     if (c == PICO_ERROR_TIMEOUT) {
72       return Pending();
73     }
74   }
75   return static_cast<std::byte>(c);
76 }
77 
78 // Channel implementation which writes to and reads from rp2040's stdio.
79 //
80 // NOTE: only one Rp2StdioChannel may be in existence.
81 class Rp2StdioChannel final : public pw::channel::Implement<ByteReaderWriter> {
82  public:
Rp2StdioChannel(MultiBufAllocator & read_allocator,MultiBufAllocator & write_allocator)83   Rp2StdioChannel(MultiBufAllocator& read_allocator,
84                   MultiBufAllocator& write_allocator)
85       : read_allocation_future_(read_allocator),
86         write_allocation_future_(write_allocator),
87         buffer_(std::nullopt) {}
88 
89   Rp2StdioChannel(const Rp2StdioChannel&) = delete;
90   Rp2StdioChannel& operator=(const Rp2StdioChannel&) = delete;
91 
92   // This is a singleton static, so there's no need for move constructors.
93   Rp2StdioChannel(Rp2StdioChannel&&) = delete;
94   Rp2StdioChannel& operator=(Rp2StdioChannel&&) = delete;
95 
96  private:
97   static constexpr size_t kMinimumReadSize = 64;
98   static constexpr size_t kDesiredReadSize = 1024;
99 
100   Poll<Status> PendGetReadBuffer(Context& cx);
101 
102   Poll<Result<MultiBuf>> DoPendRead(Context& cx) override;
103 
104   Poll<Status> DoPendReadyToWrite(Context& cx) override;
105 
DoPendAllocateWriteBuffer(Context & cx,size_t min_bytes)106   Poll<std::optional<MultiBuf>> DoPendAllocateWriteBuffer(
107       Context& cx, size_t min_bytes) override {
108     write_allocation_future_.SetDesiredSize(min_bytes);
109     return write_allocation_future_.Pend(cx);
110   }
111 
112   Status DoStageWrite(MultiBuf&& data) override;
113 
DoPendWrite(Context &)114   Poll<Status> DoPendWrite(Context&) override { return OkStatus(); }
115 
DoPendClose(Context &)116   Poll<Status> DoPendClose(Context&) override { return Ready(OkStatus()); }
117 
118   MultiBufAllocationFuture read_allocation_future_;
119   MultiBufAllocationFuture write_allocation_future_;
120   std::optional<MultiBuf> buffer_;
121 };
122 
PendGetReadBuffer(Context & cx)123 Poll<Status> Rp2StdioChannel::PendGetReadBuffer(Context& cx) {
124   if (buffer_.has_value()) {
125     return OkStatus();
126   }
127 
128   read_allocation_future_.SetDesiredSizes(
129       kMinimumReadSize, kDesiredReadSize, pw::multibuf::kNeedsContiguous);
130   Poll<std::optional<MultiBuf>> maybe_multibuf =
131       read_allocation_future_.Pend(cx);
132   if (maybe_multibuf.IsPending()) {
133     return Pending();
134   }
135   if (!maybe_multibuf->has_value()) {
136     PW_LOG_ERROR("Failed to allocate multibuf for reading");
137     return Status::ResourceExhausted();
138   }
139 
140   buffer_ = std::move(**maybe_multibuf);
141   return OkStatus();
142 }
143 
DoPendRead(Context & cx)144 Poll<Result<MultiBuf>> Rp2StdioChannel::DoPendRead(Context& cx) {
145   Poll<Status> buffer_ready = PendGetReadBuffer(cx);
146   if (buffer_ready.IsPending() || !buffer_ready->ok()) {
147     return buffer_ready;
148   }
149 
150   size_t len = 0;
151   for (std::byte& b : *buffer_) {
152     Poll<std::byte> next = PollReadByte(cx);
153     if (next.IsPending()) {
154       break;
155     }
156     b = *next;
157     ++len;
158   }
159   if (len == 0) {
160     return Pending();
161   }
162   buffer_->Truncate(len);
163   MultiBuf buffer = std::move(*buffer_);
164   buffer_ = std::nullopt;
165   return buffer;
166 }
167 
DoPendReadyToWrite(Context &)168 Poll<Status> Rp2StdioChannel::DoPendReadyToWrite(Context&) {
169   return OkStatus();
170 }
171 
DoStageWrite(MultiBuf && data)172 Status Rp2StdioChannel::DoStageWrite(MultiBuf&& data) {
173   WriteMultiBuf(data);
174 
175   return OkStatus();
176 }
177 
178 }  // namespace
179 
Rp2StdioChannelInit(MultiBufAllocator & read_allocator,MultiBufAllocator & write_allocator)180 ByteReaderWriter& Rp2StdioChannelInit(MultiBufAllocator& read_allocator,
181                                       MultiBufAllocator& write_allocator) {
182   static Rp2StdioChannel channel = [&] {
183     InitStdio();
184     return Rp2StdioChannel(read_allocator, write_allocator);
185   }();
186   return channel.channel();
187 }
188 
Rp2StdioChannelInit(MultiBufAllocator & allocator)189 ByteReaderWriter& Rp2StdioChannelInit(MultiBufAllocator& allocator) {
190   return Rp2StdioChannelInit(allocator, allocator);
191 }
192 
193 }  // namespace pw::channel
194