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