Branch data Line data Source code
1 : : /*
2 : : * Copyright 2025 Jinwoo Sung
3 : : *
4 : : * Licensed under the Apache License, Version 2.0 (the "License");
5 : : * you may not use this file except in compliance with the License.
6 : : * You may obtain a copy of the License at
7 : : *
8 : : * http://www.apache.org/licenses/LICENSE-2.0
9 : : *
10 : : * Unless required by applicable law or agreed to in writing, software
11 : : * distributed under the License is distributed on an "AS IS" BASIS,
12 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : : * See the License for the specific language governing permissions and
14 : : * limitations under the License.
15 : : */
16 : :
17 : : #pragma once
18 : :
19 : : #include "wirestead/base/visibility.hpp"
20 : : #include "wirestead/interface/iserial_port.hpp"
21 : :
22 : : #ifdef __linux__
23 : : #include <linux/serial.h>
24 : : #include <sys/ioctl.h>
25 : : #endif
26 : : #if defined(__APPLE__)
27 : : #include <sys/ioctl.h>
28 : : #endif
29 : :
30 : : #include <optional>
31 : :
32 : : namespace wirestead {
33 : : namespace transport {
34 : :
35 : : namespace net = boost::asio;
36 : :
37 : : class WIRESTEAD_API BoostSerialPort : public interface::SerialPortInterface {
38 : : public:
39 : 15 : explicit BoostSerialPort(net::io_context& ioc) : port_(ioc) {}
40 : :
41 : 17 : void open(const std::string& device, boost::system::error_code& ec) override { port_.open(device, ec); }
42 : 41 : bool is_open() const override { return port_.is_open(); }
43 : 1 : void close(boost::system::error_code& ec) override { port_.close(ec); }
44 : :
45 : 1 : void set_option(const net::serial_port_base::baud_rate& option, boost::system::error_code& ec) override {
46 : 1 : port_.set_option(option, ec);
47 : 1 : }
48 : 1 : void set_option(const net::serial_port_base::character_size& option, boost::system::error_code& ec) override {
49 : 1 : port_.set_option(option, ec);
50 : 1 : }
51 : 1 : void set_option(const net::serial_port_base::stop_bits& option, boost::system::error_code& ec) override {
52 : 1 : port_.set_option(option, ec);
53 : 1 : }
54 : 1 : void set_option(const net::serial_port_base::parity& option, boost::system::error_code& ec) override {
55 : 1 : port_.set_option(option, ec);
56 : 1 : }
57 : 1 : void set_option(const net::serial_port_base::flow_control& option, boost::system::error_code& ec) override {
58 : 1 : port_.set_option(option, ec);
59 : 1 : }
60 : :
61 : : // Linux only. TIOCGSERIAL fails with ENOTTY on drivers that have no latency
62 : : // timer to clear, which is the common case for native UARTs and CDC-ACM, so
63 : : // the caller treats false as "nothing to do" rather than as a failure.
64 : 1 : bool set_low_latency() override {
65 : : #ifdef __linux__
66 : : struct serial_struct info;
67 : 1 : const int fd = port_.native_handle();
68 [ + - ]: 1 : if (::ioctl(fd, TIOCGSERIAL, &info) != 0) return false;
69 [ # # ]: 0 : if (info.flags & ASYNC_LOW_LATENCY) return true;
70 : 0 : info.flags |= ASYNC_LOW_LATENCY;
71 : 0 : return ::ioctl(fd, TIOCSSERIAL, &info) == 0;
72 : : #else
73 : : return false;
74 : : #endif
75 : : }
76 : :
77 : : // Linux only. A driver with no RS-485 support answers ENOTTY, which the
78 : : // caller treats as "this adapter does the switching itself".
79 : 0 : bool set_rs485(bool rts_on_send, bool rx_during_tx, unsigned delay_before_ms, unsigned delay_after_ms) override {
80 : : #ifdef __linux__
81 : 0 : struct serial_rs485 rs485 {};
82 : 0 : const int fd = port_.native_handle();
83 : : // Read first: some drivers carry board-specific bits in flags that a
84 : : // blind write would clear.
85 [ # # ]: 0 : if (::ioctl(fd, TIOCGRS485, &rs485) != 0) return false;
86 : 0 : rs485.flags |= SER_RS485_ENABLED;
87 [ # # ]: 0 : if (rts_on_send) {
88 : 0 : rs485.flags |= SER_RS485_RTS_ON_SEND;
89 : 0 : rs485.flags &= ~static_cast<decltype(rs485.flags)>(SER_RS485_RTS_AFTER_SEND);
90 : : } else {
91 : 0 : rs485.flags &= ~static_cast<decltype(rs485.flags)>(SER_RS485_RTS_ON_SEND);
92 : 0 : rs485.flags |= SER_RS485_RTS_AFTER_SEND;
93 : : }
94 [ # # ]: 0 : if (rx_during_tx) {
95 : 0 : rs485.flags |= SER_RS485_RX_DURING_TX;
96 : : } else {
97 : 0 : rs485.flags &= ~static_cast<decltype(rs485.flags)>(SER_RS485_RX_DURING_TX);
98 : : }
99 : 0 : rs485.delay_rts_before_send = delay_before_ms;
100 : 0 : rs485.delay_rts_after_send = delay_after_ms;
101 : 0 : return ::ioctl(fd, TIOCSRS485, &rs485) == 0;
102 : : #else
103 : : (void)rts_on_send;
104 : : (void)rx_during_tx;
105 : : (void)delay_before_ms;
106 : : (void)delay_after_ms;
107 : : return false;
108 : : #endif
109 : : }
110 : :
111 : 0 : bool set_modem_lines(std::optional<bool> dtr, std::optional<bool> rts) override {
112 : : #if defined(__linux__) || defined(__APPLE__)
113 : 0 : const int fd = port_.native_handle();
114 : 0 : bool ok = true;
115 : 0 : auto apply = [&](std::optional<bool> want, int bit) {
116 [ # # ]: 0 : if (!want) return;
117 : 0 : int lines = bit;
118 [ # # # # ]: 0 : if (::ioctl(fd, *want ? TIOCMBIS : TIOCMBIC, &lines) != 0) ok = false;
119 : 0 : };
120 : 0 : apply(dtr, TIOCM_DTR);
121 : 0 : apply(rts, TIOCM_RTS);
122 : 0 : return ok;
123 : : #else
124 : : (void)dtr;
125 : : (void)rts;
126 : : return false;
127 : : #endif
128 : : }
129 : :
130 : 1 : void async_read_some(const net::mutable_buffer& buffer,
131 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) override {
132 : 1 : port_.async_read_some(buffer, std::move(handler));
133 : 1 : }
134 : :
135 : 0 : void async_write(const net::const_buffer& buffer,
136 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) override {
137 : 0 : net::async_write(port_, buffer, std::move(handler));
138 : 0 : }
139 : :
140 : : // Real scatter-gather write: hands the sequence to asio, which issues one
141 : : // sendmsg/writev instead of one send per queued buffer.
142 : 1 : void async_write(const std::vector<net::const_buffer>& buffers,
143 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) override {
144 : 1 : net::async_write(port_, buffers, std::move(handler));
145 : 1 : }
146 : :
147 : : private:
148 : : net::serial_port port_;
149 : : };
150 : :
151 : : } // namespace transport
152 : : } // namespace wirestead
|