| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SRC_DEVICES_LIB_GOLDFISH_PIPE_IO_PIPE_IO_H_ |
| #define SRC_DEVICES_LIB_GOLDFISH_PIPE_IO_PIPE_IO_H_ |
| |
| #include <fidl/fuchsia.hardware.goldfish.pipe/cpp/wire.h> |
| #include <lib/ddk/io-buffer.h> |
| #include <lib/dma-buffer/buffer.h> |
| #include <lib/fzl/owned-vmo-mapper.h> |
| #include <lib/fzl/pinned-vmo.h> |
| #include <lib/stdcompat/span.h> |
| #include <lib/zircon-internal/thread_annotations.h> |
| #include <lib/zx/result.h> |
| |
| #include <vector> |
| |
| #include <fbl/mutex.h> |
| |
| namespace goldfish { |
| |
| // PipeIo is a generic library drivers could use to read from and write to |
| // goldfish Pipe devices. It supports blocking and non-blocking read / write |
| // and read / write with frame headers, which are used in some QEMUD pipes |
| // like goldfish sensor pipe. |
| class PipeIo { |
| public: |
| PipeIo(fidl::WireSyncClient<fuchsia_hardware_goldfish_pipe::GoldfishPipe> pipe, |
| const char* pipe_name); |
| ~PipeIo(); |
| |
| template <class T> |
| using ReadContents = std::conditional_t<std::is_same_v<T, char>, std::string, std::vector<T>>; |
| |
| template <class T> |
| using ReadResult = zx::result<ReadContents<T>>; |
| |
| // Read |size| elements of type |T| from the pipe. |
| // Returns: |
| // - |ZX_OK|: if read succeeds. |
| // - |ZX_ERR_SHOULD_WAIT|: only occurs when |blocking| = false, when fewer |
| // than |size| bytes can be read from the pipe without waiting. |
| // - Other errors: if pipe read fails, or event wait fails. |
| template <class T> |
| ReadResult<T> Read(size_t size, bool blocking = false) { |
| ReadContents<T> result(size, T{}); |
| auto read_to_status = ReadTo(result.data(), size * sizeof(T), blocking); |
| if (read_to_status != ZX_OK) { |
| return zx::error(read_to_status); |
| } |
| if constexpr (std::is_same_v<T, char>) { |
| if (auto pos = result.find('\0'); pos != std::string::npos) { |
| result.erase(pos); |
| } |
| } |
| return zx::ok(std::move(result)); |
| } |
| |
| // First read the header for payload size, then read the payload. |
| // Returns: |
| // - |ZX_OK|: if read succeeds. |
| // - |ZX_ERR_SHOULD_WAIT|: only occurs when |blocking| = false, when less than |
| // 4 bytes cannot be read (for header), or fewer than |size| bytes |
| // can be read from the pipe without waiting. |
| // Other errors: if pipe read fails, or event wait fails. |
| ReadResult<char> ReadWithHeader(bool blocking = false); |
| |
| // The source of a pipe Write operation. |
| struct WriteSrc { |
| // A null-terminated string. The terminator '\0' will be sent to the pipe as |
| // well. |
| using Str = const std::string_view; |
| // A byte vector. |
| using Span = const cpp20::span<const uint8_t>; |
| // A VMO that is pinned using |PinVmo()| so that pipe device has access to |
| // its pages. The VMO must be physically contiguous and pinned using |
| // ZX_BTI_CONTIGUOUS option flag. |
| using PinnedVmo = struct { |
| const fzl::PinnedVmo* vmo; |
| size_t offset; |
| size_t size; |
| }; |
| |
| std::variant<Str, Span, PinnedVmo> data; |
| }; |
| |
| // Write |sources| to the pipe. All the write operations will be executed |
| // in a single pipe command. |
| // Returns: |
| // - |ZX_OK|: if write succeeds. |
| // - |ZX_ERR_SHOULD_WAIT|: only occurs when |blocking| = false, when fewer |
| // than the payload size can be written to the pipe without waiting. |
| // Other errors: if pipe write fails, or event wait fails. |
| zx_status_t Write(cpp20::span<const WriteSrc> sources, bool blocking = false); |
| |
| // Write a null-terminated string |payload| to the pipe. The terminator '\0' |
| // will be sent to the pipe as well. |
| // Returns: Same as "Write(cpp20::span<const WriteSrc>, bool)" |
| zx_status_t Write(const char* payload, bool blocking = false); |
| |
| // Write a byte vector |payload| to the pipe. The terminator '\0' |
| // will be sent to the pipe as well. |
| // Returns: Same as "Write(cpp20::span<const WriteSrc>, bool)". |
| zx_status_t Write(const std::vector<uint8_t>& payload, bool blocking = false); |
| |
| // First write the header of the payload (4-bytes size in hexadecimal), then |
| // write the payload. |
| // Returns: |
| // - |ZX_OK|: if write succeeds. |
| // - |ZX_ERR_SHOULD_WAIT|: only occurs when |blocking| = false, when less than |
| // 4 bytes cannot be written (for header), or fewer than |size| bytes |
| // can be written to the pipe without waiting. |
| // Other errors: if pipe read fails, or event wait fails. |
| zx_status_t WriteWithHeader(const char* payload, bool blocking = false); |
| |
| // First write the header of the payload (4-bytes size), then write the |
| // payload. |
| // Returns: Same as "WriteWithHeader(const char*, bool)". |
| zx_status_t WriteWithHeader(const std::vector<uint8_t>& payload, bool blocking = false); |
| |
| // First write all the |write_sources| to the pipe, then read |read_size| |
| // elements of type |T| from the pipe. The operations will be executed within |
| // a single pipe call command. |
| // Returns: |
| // - |ZX_OK| and data read from pipe: if write and read succeeds. |
| // - |ZX_ERR_SHOULD_WAIT|: only occurs when |blocking| = false, when |
| // |write_sources| cannot be written to the pipe or fewer than |
| // |size| bytes can be read from the pipe without waiting. |
| // - Other errors: if pipe write/read fails, or event wait fails. |
| template <class T> |
| ReadResult<T> Call(cpp20::span<const WriteSrc> write_sources, size_t read_size, |
| bool blocking = false) { |
| ReadContents<T> result(read_size, T{}); |
| auto call_to_status = CallTo(write_sources, result.data(), read_size * sizeof(T), blocking); |
| if (call_to_status != ZX_OK) { |
| return zx::error(call_to_status); |
| } |
| if constexpr (std::is_same_v<T, char>) { |
| if (auto pos = result.find('\0'); pos != std::string::npos) { |
| result.erase(pos); |
| } |
| } |
| return zx::ok(std::move(result)); |
| } |
| |
| // Pin pages of |vmo| and grant goldfish pipe device the ability to access the |
| // VMO pages, using |options| as defined in |zx_bti_pin()| syscall. |
| // The caller *must* unpin all the VMOs before destroying |PipeIo|. |
| fzl::PinnedVmo PinVmo(zx::vmo& vmo, uint32_t options); |
| |
| // Pin pages of the range [offset, offset + size) of |vmo| and grant goldfish |
| // pipe device the ability to access the VMO pages, using |options| as |
| // defined in |zx_bti_pin()| syscall. |
| // The caller *must* unpin all the VMOs before destroying |PipeIo|. |
| fzl::PinnedVmo PinVmo(zx::vmo& vmo, uint32_t options, size_t offset, size_t size); |
| |
| const zx::event& pipe_event() const { return pipe_event_; } |
| bool valid() const { return valid_; } |
| |
| private: |
| zx_status_t Init(const char* pipe_name); |
| zx_status_t SetupPipe(); |
| |
| // Read |size| bytes to |dst|. Caller must make sure that there are |size| |
| // bytes allocated at |dst|. |
| // Return value: Same as Read(). |
| zx_status_t ReadTo(void* dst, size_t size, bool blocking = false); |
| |
| // Write the |write_sources| and read |size| bytes to |read_dst|. |
| // Caller must make sure that there are |size| bytes allocated at |dst|. |
| // Return value: Same as Call(). |
| zx_status_t CallTo(cpp20::span<const WriteSrc> write_sources, void* read_dst, size_t read_size, |
| bool blocking = false); |
| |
| zx::result<size_t> ReadOnceLocked(void* buf, size_t size) TA_REQ(lock_); |
| |
| struct TransferOp { |
| struct IoBuffer { |
| size_t offset = 0u; |
| }; |
| struct PinnedVmo { |
| size_t paddr = 0u; |
| }; |
| |
| enum class Type { kWrite, kRead } type; |
| std::variant<IoBuffer, PinnedVmo> data; |
| size_t size; |
| }; |
| |
| // Run a TransferOp command to read / write data stored in IoBuffer or pinned |
| // VMO, as specified in |data|. |
| zx::result<uint32_t> TransferLocked(const TransferOp& op) TA_REQ(lock_); |
| |
| // Run a TransferOp command to read / write multiple data stored in IoBuffer |
| // or pinned VMO. |
| // If there are both kWrite and kRead in |ops|, all kWrite ops must occur |
| // before kRead ops. Otherwise it will return |ZX_ERR_INVALID_ARGS|. |
| zx::result<uint32_t> TransferLocked(cpp20::span<const TransferOp> ops) TA_REQ(lock_); |
| |
| // Once the command buffer is set up, this runs the transfer command on |
| // goldfish pipe device, and will request a wake-up if it gets back pressure. |
| zx::result<uint32_t> ExecTransferCommandLocked(bool has_write, bool has_read) TA_REQ(lock_); |
| |
| fbl::Mutex lock_; |
| |
| bool valid_ = false; |
| int32_t id_ = 0; |
| zx::bti bti_ TA_GUARDED(lock_); |
| fzl::OwnedVmoMapper cmd_buffer_ TA_GUARDED(lock_); |
| std::unique_ptr<dma_buffer::ContiguousBuffer> io_buffer_ TA_GUARDED(lock_); |
| size_t io_buffer_size_ = 0u; |
| zx::event pipe_event_; |
| |
| fidl::WireSyncClient<fuchsia_hardware_goldfish_pipe::GoldfishPipe> pipe_; |
| }; |
| |
| } // namespace goldfish |
| |
| #endif // SRC_DEVICES_LIB_GOLDFISH_PIPE_IO_PIPE_IO_H_ |