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