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 : : #include <termios.h>
26 : : #endif
27 : : #if defined(__APPLE__)
28 : : #include <sys/ioctl.h>
29 : : #endif
30 : :
31 : : #include <optional>
32 : :
33 : : namespace wirestead {
34 : : namespace transport {
35 : :
36 : : namespace net = boost::asio;
37 : :
38 : : class WIRESTEAD_API BoostSerialPort : public interface::SerialPortInterface {
39 : : public:
40 : 43 : explicit BoostSerialPort(net::io_context& ioc) : port_(ioc) {}
41 : :
42 : 36 : void open(const std::string& device, boost::system::error_code& ec) override { port_.open(device, ec); }
43 : 101 : bool is_open() const override { return port_.is_open(); }
44 : 24 : void close(boost::system::error_code& ec) override { port_.close(ec); }
45 : :
46 : 24 : void set_option(const net::serial_port_base::baud_rate& option, boost::system::error_code& ec) override {
47 : 24 : port_.set_option(option, ec);
48 : 24 : }
49 : 24 : void set_option(const net::serial_port_base::character_size& option, boost::system::error_code& ec) override {
50 : 24 : port_.set_option(option, ec);
51 : 24 : }
52 : 24 : void set_option(const net::serial_port_base::stop_bits& option, boost::system::error_code& ec) override {
53 : 24 : port_.set_option(option, ec);
54 : 24 : }
55 : 24 : void set_option(const net::serial_port_base::parity& option, boost::system::error_code& ec) override {
56 : 24 : port_.set_option(option, ec);
57 : 24 : }
58 : 24 : void set_option(const net::serial_port_base::flow_control& option, boost::system::error_code& ec) override {
59 : 24 : port_.set_option(option, ec);
60 : 24 : }
61 : :
62 : : // Linux only. TIOCGSERIAL fails with ENOTTY on drivers that have no latency
63 : : // timer to clear, which is the common case for native UARTs and CDC-ACM, so
64 : : // the caller treats false as "nothing to do" rather than as a failure.
65 : 24 : bool set_low_latency() override {
66 : : #ifdef __linux__
67 : : struct serial_struct info;
68 : 24 : const int fd = port_.native_handle();
69 [ + - ]: 24 : if (::ioctl(fd, TIOCGSERIAL, &info) != 0) return false;
70 : 0 : const auto low_latency = static_cast<decltype(info.flags)>(ASYNC_LOW_LATENCY);
71 [ # # ]: 0 : if (info.flags & low_latency) return true;
72 : 0 : info.flags |= low_latency;
73 : 0 : return ::ioctl(fd, TIOCSSERIAL, &info) == 0;
74 : : #else
75 : : return false;
76 : : #endif
77 : : }
78 : :
79 : : // Linux only. A driver with no RS-485 support answers ENOTTY, which the
80 : : // caller treats as "this adapter does the switching itself".
81 : 0 : bool set_rs485(bool rts_on_send, bool rx_during_tx, unsigned delay_before_ms, unsigned delay_after_ms) override {
82 : : #ifdef __linux__
83 : 0 : struct serial_rs485 rs485 {};
84 : 0 : const int fd = port_.native_handle();
85 : : // Read first: some drivers carry board-specific bits in flags that a
86 : : // blind write would clear.
87 [ # # ]: 0 : if (::ioctl(fd, TIOCGRS485, &rs485) != 0) return false;
88 : 0 : rs485.flags |= SER_RS485_ENABLED;
89 [ # # ]: 0 : if (rts_on_send) {
90 : 0 : rs485.flags |= SER_RS485_RTS_ON_SEND;
91 : 0 : rs485.flags &= ~static_cast<decltype(rs485.flags)>(SER_RS485_RTS_AFTER_SEND);
92 : : } else {
93 : 0 : rs485.flags &= ~static_cast<decltype(rs485.flags)>(SER_RS485_RTS_ON_SEND);
94 : 0 : rs485.flags |= SER_RS485_RTS_AFTER_SEND;
95 : : }
96 [ # # ]: 0 : if (rx_during_tx) {
97 : 0 : rs485.flags |= SER_RS485_RX_DURING_TX;
98 : : } else {
99 : 0 : rs485.flags &= ~static_cast<decltype(rs485.flags)>(SER_RS485_RX_DURING_TX);
100 : : }
101 : 0 : rs485.delay_rts_before_send = delay_before_ms;
102 : 0 : rs485.delay_rts_after_send = delay_after_ms;
103 : 0 : return ::ioctl(fd, TIOCSRS485, &rs485) == 0;
104 : : #else
105 : : (void)rts_on_send;
106 : : (void)rx_during_tx;
107 : : (void)delay_before_ms;
108 : : (void)delay_after_ms;
109 : : return false;
110 : : #endif
111 : : }
112 : :
113 : 0 : bool set_modem_lines(std::optional<bool> dtr, std::optional<bool> rts) override {
114 : : #if defined(__linux__) || defined(__APPLE__)
115 : 0 : const int fd = port_.native_handle();
116 : 0 : bool ok = true;
117 : 0 : auto apply = [&](std::optional<bool> want, int bit) {
118 [ # # ]: 0 : if (!want) return;
119 : 0 : int lines = bit;
120 [ # # # # ]: 0 : if (::ioctl(fd, *want ? TIOCMBIS : TIOCMBIC, &lines) != 0) ok = false;
121 : 0 : };
122 : 0 : apply(dtr, TIOCM_DTR);
123 : 0 : apply(rts, TIOCM_RTS);
124 : 0 : return ok;
125 : : #else
126 : : (void)dtr;
127 : : (void)rts;
128 : : return false;
129 : : #endif
130 : : }
131 : :
132 : 0 : bool keep_dtr_on_close() override {
133 : : #ifdef __linux__
134 : 0 : const int fd = port_.native_handle();
135 : 0 : struct termios settings {};
136 [ # # ]: 0 : if (::tcgetattr(fd, &settings) != 0) return false;
137 : 0 : int line = TIOCM_DTR;
138 [ # # ]: 0 : if (::ioctl(fd, TIOCMBIS, &line) != 0) return false;
139 : 0 : settings.c_cflag &= ~static_cast<tcflag_t>(HUPCL);
140 [ # # ]: 0 : if (::tcsetattr(fd, TCSANOW, &settings) != 0) return false;
141 : : // Some drivers accept settings without applying them.
142 [ # # # # ]: 0 : return ::tcgetattr(fd, &settings) == 0 && !(settings.c_cflag & HUPCL);
143 : : #else
144 : : return false;
145 : : #endif
146 : : }
147 : :
148 : 33 : void async_read_some(const net::mutable_buffer& buffer,
149 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) override {
150 : 33 : port_.async_read_some(buffer, std::move(handler));
151 : 33 : }
152 : :
153 : 0 : void async_write(const net::const_buffer& buffer,
154 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) override {
155 : 0 : net::async_write(port_, buffer, std::move(handler));
156 : 0 : }
157 : :
158 : : // Real scatter-gather write: hands the sequence to asio, which issues one
159 : : // sendmsg/writev instead of one send per queued buffer.
160 : 0 : void async_write(const std::vector<net::const_buffer>& buffers,
161 : : std::function<void(const boost::system::error_code&, std::size_t)> handler) override {
162 : 0 : net::async_write(port_, buffers, std::move(handler));
163 : 0 : }
164 : :
165 : : private:
166 : : net::serial_port port_;
167 : : };
168 : :
169 : : } // namespace transport
170 : : } // namespace wirestead
|