blob: ccf0c5ef61bd022a18a9bb8f2fa74e40552320d6 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_DEVICE_SERIAL_SERIAL_IO_HANDLER_H_
#define SERVICES_DEVICE_SERIAL_SERIAL_IO_HANDLER_H_
#include <stdint.h>
#include <memory>
#include <string>
#include "base/files/file.h"
#include "base/functional/callback.h"
#include "base/memory/raw_span.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_checker.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "services/device/public/mojom/serial.mojom.h"
namespace device {
// Provides a simplified interface for performing asynchronous I/O on serial
// devices by hiding platform-specific MessageLoop interfaces. Pending I/O
// operations hold a reference to this object until completion so that memory
// doesn't disappear out from under the OS.
class SerialIoHandler : public base::RefCountedThreadSafe<SerialIoHandler> {
public:
// Constructs an instance of some platform-specific subclass.
static scoped_refptr<SerialIoHandler> Create(
const base::FilePath& port,
scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner);
SerialIoHandler(const SerialIoHandler&) = delete;
SerialIoHandler& operator=(const SerialIoHandler&) = delete;
using OpenCompleteCallback = base::OnceCallback<void(bool success)>;
using ReadCompleteCallback =
base::OnceCallback<void(uint32_t bytes_read, mojom::SerialReceiveError)>;
using WriteCompleteCallback =
base::OnceCallback<void(uint32_t bytes_written, mojom::SerialSendError)>;
// Initiates an asynchronous Open of the device.
virtual void Open(const mojom::SerialConnectionOptions& options,
OpenCompleteCallback callback);
#if BUILDFLAG(IS_CHROMEOS)
// Signals that the port has been opened.
void OnPathOpened(
scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner,
base::ScopedFD fd);
// Signals that the permission broker failed to open the port.
void OnPathOpenError(
scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner,
const std::string& error_name,
const std::string& error_message);
// Reports the open error from the permission broker.
void ReportPathOpenError(const std::string& error_name,
const std::string& error_message);
#endif // BUILDFLAG(IS_CHROMEOS)
// Performs an async read operation. Behavior is undefined if this is called
// while a read is already pending. Otherwise, |callback| will be called
// (potentially synchronously) with a result. |buffer| must remain valid until
// |callback| is run.
void Read(base::span<uint8_t> buffer, ReadCompleteCallback callback);
// Performs an async write operation. Behavior is undefined if this is called
// while a write is already pending. Otherwise, |callback| will be called
// (potentially synchronously) with a result. |buffer| must remain valid until
// |callback| is run.
void Write(base::span<const uint8_t> buffer, WriteCompleteCallback callback);
// Indicates whether or not a read is currently pending.
bool IsReadPending() const;
// Indicates whether or not a write is currently pending.
bool IsWritePending() const;
// Attempts to cancel a pending read operation.
void CancelRead(mojom::SerialReceiveError reason);
// Attempts to cancel a pending write operation.
void CancelWrite(mojom::SerialSendError reason);
// Flushes input and output buffers.
virtual void Flush(mojom::SerialPortFlushMode mode) const = 0;
// Drains output buffers.
virtual void Drain() = 0;
// Reads current control signals (DCD, CTS, etc.) into an existing
// DeviceControlSignals structure. Returns |true| iff the signals were
// successfully read.
virtual mojom::SerialPortControlSignalsPtr GetControlSignals() const = 0;
// Sets one or more control signals. Returns |true| iff the signals were
// successfully set. Flags not present in |control_signals| are unchanged.
virtual bool SetControlSignals(
const mojom::SerialHostControlSignals& control_signals) = 0;
// Performs platform-specific port configuration. Returns |true| iff
// configuration was successful.
bool ConfigurePort(const mojom::SerialConnectionOptions& options);
// Performs a platform-specific port configuration query. Fills values in an
// existing ConnectionInfo. Returns |true| iff port configuration was
// successfully retrieved.
virtual mojom::SerialConnectionInfoPtr GetPortInfo() const = 0;
// Initiates an asynchronous close of the port.
void Close(base::OnceClosure callback);
protected:
explicit SerialIoHandler(
const base::FilePath& port,
scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner);
virtual ~SerialIoHandler();
// Performs a platform-specific read operation. This must guarantee that
// ReadCompleted is called when the underlying async operation is completed
// or the SerialIoHandler instance will leak.
// NOTE: Implementations of ReadImpl may call ReadCompleted directly.
virtual void ReadImpl() = 0;
// Performs a platform-specific write operation. This must guarantee that
// WriteCompleted is called when the underlying async operation is completed
// or the SerialIoHandler instance will leak.
// NOTE: Implementations of WriteImpl may call WriteCompleted directly.
virtual void WriteImpl() = 0;
// Platform-specific read cancelation.
virtual void CancelReadImpl() = 0;
// Platform-specific write cancelation.
virtual void CancelWriteImpl() = 0;
// Platform-specific port configuration applies options_ to the device.
virtual bool ConfigurePortImpl() = 0;
// Performs platform-specific, one-time port configuration on open.
virtual bool PostOpen();
// Performs platform-specific operations before |file_| is closed.
virtual void PreClose();
// Called by the implementation to signal that the active read has completed.
// WARNING: Calling this method can destroy the SerialIoHandler instance
// if the associated I/O operation was the only thing keeping it alive.
void ReadCompleted(int bytes_read, mojom::SerialReceiveError error);
// Called by the implementation to signal that the active write has completed.
// WARNING: Calling this method may destroy the SerialIoHandler instance
// if the associated I/O operation was the only thing keeping it alive.
void WriteCompleted(int bytes_written, mojom::SerialSendError error);
const base::File& file() const { return file_; }
base::span<uint8_t> pending_read_buffer() const {
return pending_read_buffer_;
}
mojom::SerialReceiveError read_cancel_reason() const {
return read_cancel_reason_;
}
bool read_canceled() const { return read_canceled_; }
base::span<const uint8_t> pending_write_buffer() const {
return pending_write_buffer_;
}
mojom::SerialSendError write_cancel_reason() const {
return write_cancel_reason_;
}
bool write_canceled() const { return write_canceled_; }
const mojom::SerialConnectionOptions& options() const { return options_; }
base::SingleThreadTaskRunner* ui_thread_task_runner() const {
return ui_thread_task_runner_.get();
}
const base::FilePath& port() const { return port_; }
SEQUENCE_CHECKER(sequence_checker_);
private:
friend class base::RefCountedThreadSafe<SerialIoHandler>;
void MergeConnectionOptions(const mojom::SerialConnectionOptions& options);
// Continues an Open operation on the FILE thread.
void StartOpen(scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
// Finalizes an Open operation (continued from StartOpen) on the IO thread.
void FinishOpen(base::File file);
// Continues a Close operation on the FILE thread.
static void DoClose(base::File port);
// File for the opened serial device. This value is only modified from the IO
// thread.
base::File file_;
// Currently applied connection options.
mojom::SerialConnectionOptions options_;
base::raw_span<uint8_t> pending_read_buffer_;
ReadCompleteCallback pending_read_callback_;
mojom::SerialReceiveError read_cancel_reason_;
bool read_canceled_;
base::raw_span<const uint8_t> pending_write_buffer_;
WriteCompleteCallback pending_write_callback_;
mojom::SerialSendError write_cancel_reason_;
bool write_canceled_;
// Callback to handle the completion of a pending Open() request.
OpenCompleteCallback open_complete_;
const base::FilePath port_;
// On Chrome OS, PermissionBrokerClient should be called on the UI thread.
scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner_;
};
} // namespace device
#endif // SERVICES_DEVICE_SERIAL_SERIAL_IO_HANDLER_H_