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