1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "services/device/serial/serial_io_handler_posix.h"
13 #include "base/files/file_util.h"
14 #include "base/posix/eintr_wrapper.h"
15 #include "build/build_config.h"
18 #include <asm-generic/ioctls.h>
19 #include <linux/serial.h>
21 // The definition of struct termios2 is copied from asm-generic/termbits.h
22 // because including that header directly conflicts with termios.h.
25 tcflag_t c_iflag; // input mode flags
26 tcflag_t c_oflag; // output mode flags
27 tcflag_t c_cflag; // control mode flags
28 tcflag_t c_lflag; // local mode flags
29 cc_t c_line; // line discipline
30 cc_t c_cc[19]; // control characters
31 speed_t c_ispeed; // input speed
32 speed_t c_ospeed; // output speed
36 #endif // defined(OS_LINUX)
38 #if defined(OS_MACOSX)
39 #include <IOKit/serial/ioss.h>
44 // Convert an integral bit rate to a nominal one. Returns |true|
45 // if the conversion was successful and |false| otherwise.
46 bool BitrateToSpeedConstant(int bitrate, speed_t* speed) {
47 #define BITRATE_TO_SPEED_CASE(x) \
52 BITRATE_TO_SPEED_CASE(0)
53 BITRATE_TO_SPEED_CASE(50)
54 BITRATE_TO_SPEED_CASE(75)
55 BITRATE_TO_SPEED_CASE(110)
56 BITRATE_TO_SPEED_CASE(134)
57 BITRATE_TO_SPEED_CASE(150)
58 BITRATE_TO_SPEED_CASE(200)
59 BITRATE_TO_SPEED_CASE(300)
60 BITRATE_TO_SPEED_CASE(600)
61 BITRATE_TO_SPEED_CASE(1200)
62 BITRATE_TO_SPEED_CASE(1800)
63 BITRATE_TO_SPEED_CASE(2400)
64 BITRATE_TO_SPEED_CASE(4800)
65 BITRATE_TO_SPEED_CASE(9600)
66 BITRATE_TO_SPEED_CASE(19200)
67 BITRATE_TO_SPEED_CASE(38400)
68 #if !defined(OS_MACOSX)
69 BITRATE_TO_SPEED_CASE(57600)
70 BITRATE_TO_SPEED_CASE(115200)
71 BITRATE_TO_SPEED_CASE(230400)
72 BITRATE_TO_SPEED_CASE(460800)
73 BITRATE_TO_SPEED_CASE(576000)
74 BITRATE_TO_SPEED_CASE(921600)
79 #undef BITRATE_TO_SPEED_CASE
82 #if !defined(OS_LINUX)
83 // Convert a known nominal speed into an integral bitrate. Returns |true|
84 // if the conversion was successful and |false| otherwise.
85 bool SpeedConstantToBitrate(speed_t speed, int* bitrate) {
86 #define SPEED_TO_BITRATE_CASE(x) \
91 SPEED_TO_BITRATE_CASE(0)
92 SPEED_TO_BITRATE_CASE(50)
93 SPEED_TO_BITRATE_CASE(75)
94 SPEED_TO_BITRATE_CASE(110)
95 SPEED_TO_BITRATE_CASE(134)
96 SPEED_TO_BITRATE_CASE(150)
97 SPEED_TO_BITRATE_CASE(200)
98 SPEED_TO_BITRATE_CASE(300)
99 SPEED_TO_BITRATE_CASE(600)
100 SPEED_TO_BITRATE_CASE(1200)
101 SPEED_TO_BITRATE_CASE(1800)
102 SPEED_TO_BITRATE_CASE(2400)
103 SPEED_TO_BITRATE_CASE(4800)
104 SPEED_TO_BITRATE_CASE(9600)
105 SPEED_TO_BITRATE_CASE(19200)
106 SPEED_TO_BITRATE_CASE(38400)
110 #undef SPEED_TO_BITRATE_CASE
119 scoped_refptr<SerialIoHandler> SerialIoHandler::Create(
120 const base::FilePath& port,
121 scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) {
122 return new SerialIoHandlerPosix(port, std::move(ui_thread_task_runner));
125 void SerialIoHandlerPosix::ReadImpl() {
126 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
127 DCHECK(pending_read_buffer());
128 DCHECK(file().IsValid());
130 // Try to read immediately. This is needed because on some platforms
131 // (e.g., OSX) there may not be a notification from the message loop
132 // when the fd is ready to read immediately after it is opened. There
133 // is no danger of blocking because the fd is opened with async flag.
137 void SerialIoHandlerPosix::WriteImpl() {
138 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
139 DCHECK(pending_write_buffer());
140 DCHECK(file().IsValid());
142 EnsureWatchingWrites();
145 void SerialIoHandlerPosix::CancelReadImpl() {
146 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
147 file_read_watcher_.reset();
148 QueueReadCompleted(0, read_cancel_reason());
151 void SerialIoHandlerPosix::CancelWriteImpl() {
152 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
153 file_write_watcher_.reset();
154 QueueWriteCompleted(0, write_cancel_reason());
157 bool SerialIoHandlerPosix::ConfigurePortImpl() {
158 #if defined(OS_LINUX)
159 struct termios2 config;
160 if (ioctl(file().GetPlatformFile(), TCGETS2, &config) < 0) {
162 struct termios config;
163 if (tcgetattr(file().GetPlatformFile(), &config) != 0) {
165 VPLOG(1) << "Failed to get port configuration";
169 // Set flags for 'raw' operation
170 config.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG);
171 config.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
172 config.c_iflag |= PARMRK;
173 config.c_oflag &= ~OPOST;
175 // CLOCAL causes the system to disregard the DCD signal state.
176 // CREAD enables reading from the port.
177 config.c_cflag |= (CLOCAL | CREAD);
179 DCHECK(options().bitrate);
180 speed_t bitrate_opt = B0;
181 #if defined(OS_MACOSX)
182 bool need_iossiospeed = false;
184 if (BitrateToSpeedConstant(options().bitrate, &bitrate_opt)) {
185 #if defined(OS_LINUX)
186 config.c_cflag &= ~CBAUD;
187 config.c_cflag |= bitrate_opt;
189 cfsetispeed(&config, bitrate_opt);
190 cfsetospeed(&config, bitrate_opt);
193 // Attempt to set a custom speed.
194 #if defined(OS_LINUX)
195 config.c_cflag &= ~CBAUD;
196 config.c_cflag |= CBAUDEX;
197 config.c_ispeed = config.c_ospeed = options().bitrate;
198 #elif defined(OS_MACOSX)
199 // cfsetispeed and cfsetospeed sometimes work for custom baud rates on OS
200 // X but the IOSSIOSPEED ioctl is more reliable but has to be done after
201 // the rest of the port parameters are set or else it will be overwritten.
202 need_iossiospeed = true;
208 DCHECK(options().data_bits != mojom::SerialDataBits::NONE);
209 config.c_cflag &= ~CSIZE;
210 switch (options().data_bits) {
211 case mojom::SerialDataBits::SEVEN:
212 config.c_cflag |= CS7;
214 case mojom::SerialDataBits::EIGHT:
216 config.c_cflag |= CS8;
220 DCHECK(options().parity_bit != mojom::SerialParityBit::NONE);
221 switch (options().parity_bit) {
222 case mojom::SerialParityBit::EVEN:
223 config.c_cflag |= PARENB;
224 config.c_cflag &= ~PARODD;
226 case mojom::SerialParityBit::ODD:
227 config.c_cflag |= (PARODD | PARENB);
229 case mojom::SerialParityBit::NO_PARITY:
231 config.c_cflag &= ~(PARODD | PARENB);
235 error_detect_state_ = ErrorDetectState::NO_ERROR;
236 num_chars_stashed_ = 0;
238 if (config.c_cflag & PARENB) {
239 config.c_iflag &= ~IGNPAR;
240 config.c_iflag |= INPCK;
241 parity_check_enabled_ = true;
243 config.c_iflag |= IGNPAR;
244 config.c_iflag &= ~INPCK;
245 parity_check_enabled_ = false;
248 DCHECK(options().stop_bits != mojom::SerialStopBits::NONE);
249 switch (options().stop_bits) {
250 case mojom::SerialStopBits::TWO:
251 config.c_cflag |= CSTOPB;
253 case mojom::SerialStopBits::ONE:
255 config.c_cflag &= ~CSTOPB;
259 DCHECK(options().has_cts_flow_control);
260 if (options().cts_flow_control) {
261 config.c_cflag |= CRTSCTS;
263 config.c_cflag &= ~CRTSCTS;
266 #if defined(OS_LINUX)
267 if (ioctl(file().GetPlatformFile(), TCSETS2, &config) < 0) {
269 if (tcsetattr(file().GetPlatformFile(), TCSANOW, &config) != 0) {
271 VPLOG(1) << "Failed to set port attributes";
275 #if defined(OS_MACOSX)
276 if (need_iossiospeed) {
277 speed_t bitrate = options().bitrate;
278 if (ioctl(file().GetPlatformFile(), IOSSIOSPEED, &bitrate) == -1) {
279 VPLOG(1) << "Failed to set custom baud rate";
288 bool SerialIoHandlerPosix::PostOpen() {
289 #if defined(OS_CHROMEOS)
290 // The Chrome OS permission broker does not open devices in async mode.
291 return base::SetNonBlocking(file().GetPlatformFile());
297 SerialIoHandlerPosix::SerialIoHandlerPosix(
298 const base::FilePath& port,
299 scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner)
300 : SerialIoHandler(port, std::move(ui_thread_task_runner)) {}
302 SerialIoHandlerPosix::~SerialIoHandlerPosix() = default;
304 void SerialIoHandlerPosix::AttemptRead(bool within_read) {
305 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
307 if (pending_read_buffer()) {
309 HANDLE_EINTR(read(file().GetPlatformFile(), pending_read_buffer(),
310 pending_read_buffer_len()));
311 if (bytes_read < 0) {
312 if (errno == EAGAIN) {
313 // The fd does not have data to read yet so continue waiting.
314 EnsureWatchingReads();
315 } else if (errno == ENXIO) {
316 RunReadCompleted(within_read, 0,
317 mojom::SerialReceiveError::DEVICE_LOST);
319 RunReadCompleted(within_read, 0,
320 mojom::SerialReceiveError::SYSTEM_ERROR);
322 } else if (bytes_read == 0) {
323 RunReadCompleted(within_read, 0, mojom::SerialReceiveError::DEVICE_LOST);
325 bool break_detected = false;
326 bool parity_error_detected = false;
328 CheckReceiveError(pending_read_buffer(), pending_read_buffer_len(),
329 bytes_read, break_detected, parity_error_detected);
331 if (break_detected) {
332 RunReadCompleted(within_read, new_bytes_read,
333 mojom::SerialReceiveError::BREAK);
334 } else if (parity_error_detected) {
335 RunReadCompleted(within_read, new_bytes_read,
336 mojom::SerialReceiveError::PARITY_ERROR);
338 RunReadCompleted(within_read, new_bytes_read,
339 mojom::SerialReceiveError::NONE);
343 // Stop watching the fd if we get notifications with no pending
344 // reads or writes to avoid starving the message loop.
345 file_read_watcher_.reset();
349 void SerialIoHandlerPosix::RunReadCompleted(bool within_read,
351 mojom::SerialReceiveError error) {
353 // Stop watching the fd to avoid more reads until the queued ReadCompleted()
354 // completes and releases the pending_read_buffer.
355 file_read_watcher_.reset();
357 QueueReadCompleted(bytes_read, error);
359 ReadCompleted(bytes_read, error);
363 void SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking() {
364 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
366 if (pending_write_buffer()) {
368 HANDLE_EINTR(write(file().GetPlatformFile(), pending_write_buffer(),
369 pending_write_buffer_len()));
370 if (bytes_written < 0) {
371 WriteCompleted(0, mojom::SerialSendError::SYSTEM_ERROR);
373 WriteCompleted(bytes_written, mojom::SerialSendError::NONE);
376 // Stop watching the fd if we get notifications with no pending
377 // writes to avoid starving the message loop.
378 file_write_watcher_.reset();
382 void SerialIoHandlerPosix::EnsureWatchingReads() {
383 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
384 DCHECK(file().IsValid());
385 if (!file_read_watcher_) {
386 file_read_watcher_ = base::FileDescriptorWatcher::WatchReadable(
387 file().GetPlatformFile(),
388 base::BindRepeating(&SerialIoHandlerPosix::AttemptRead,
389 base::Unretained(this), false));
393 void SerialIoHandlerPosix::EnsureWatchingWrites() {
394 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
395 DCHECK(file().IsValid());
396 if (!file_write_watcher_) {
397 file_write_watcher_ = base::FileDescriptorWatcher::WatchWritable(
398 file().GetPlatformFile(),
400 &SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking,
401 base::Unretained(this)));
405 bool SerialIoHandlerPosix::Flush() const {
406 if (tcflush(file().GetPlatformFile(), TCIOFLUSH) != 0) {
407 VPLOG(1) << "Failed to flush port";
413 mojom::SerialPortControlSignalsPtr SerialIoHandlerPosix::GetControlSignals()
416 if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
417 VPLOG(1) << "Failed to get port control signals";
418 return mojom::SerialPortControlSignalsPtr();
421 auto signals = mojom::SerialPortControlSignals::New();
422 signals->dcd = (status & TIOCM_CAR) != 0;
423 signals->cts = (status & TIOCM_CTS) != 0;
424 signals->dsr = (status & TIOCM_DSR) != 0;
425 signals->ri = (status & TIOCM_RI) != 0;
429 bool SerialIoHandlerPosix::SetControlSignals(
430 const mojom::SerialHostControlSignals& signals) {
433 if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
434 VPLOG(1) << "Failed to get port control signals";
438 if (signals.has_dtr) {
442 status &= ~TIOCM_DTR;
446 if (signals.has_rts) {
450 status &= ~TIOCM_RTS;
454 if (ioctl(file().GetPlatformFile(), TIOCMSET, &status) != 0) {
455 VPLOG(1) << "Failed to set port control signals";
461 mojom::SerialConnectionInfoPtr SerialIoHandlerPosix::GetPortInfo() const {
462 #if defined(OS_LINUX)
463 struct termios2 config;
464 if (ioctl(file().GetPlatformFile(), TCGETS2, &config) < 0) {
466 struct termios config;
467 if (tcgetattr(file().GetPlatformFile(), &config) == -1) {
469 VPLOG(1) << "Failed to get port info";
470 return mojom::SerialConnectionInfoPtr();
473 auto info = mojom::SerialConnectionInfo::New();
474 #if defined(OS_LINUX)
475 // Linux forces c_ospeed to contain the correct value, which is nice.
476 info->bitrate = config.c_ospeed;
478 speed_t ispeed = cfgetispeed(&config);
479 speed_t ospeed = cfgetospeed(&config);
480 if (ispeed == ospeed) {
482 if (SpeedConstantToBitrate(ispeed, &bitrate)) {
483 info->bitrate = bitrate;
484 } else if (ispeed > 0) {
485 info->bitrate = static_cast<int>(ispeed);
490 if ((config.c_cflag & CSIZE) == CS7) {
491 info->data_bits = mojom::SerialDataBits::SEVEN;
492 } else if ((config.c_cflag & CSIZE) == CS8) {
493 info->data_bits = mojom::SerialDataBits::EIGHT;
495 info->data_bits = mojom::SerialDataBits::NONE;
497 if (config.c_cflag & PARENB) {
498 info->parity_bit = (config.c_cflag & PARODD) ? mojom::SerialParityBit::ODD
499 : mojom::SerialParityBit::EVEN;
501 info->parity_bit = mojom::SerialParityBit::NO_PARITY;
503 info->stop_bits = (config.c_cflag & CSTOPB) ? mojom::SerialStopBits::TWO
504 : mojom::SerialStopBits::ONE;
505 info->cts_flow_control = (config.c_cflag & CRTSCTS) != 0;
509 bool SerialIoHandlerPosix::SetBreak() {
510 if (ioctl(file().GetPlatformFile(), TIOCSBRK, 0) != 0) {
511 VPLOG(1) << "Failed to set break";
518 bool SerialIoHandlerPosix::ClearBreak() {
519 if (ioctl(file().GetPlatformFile(), TIOCCBRK, 0) != 0) {
520 VPLOG(1) << "Failed to clear break";
527 // '\377' --> ErrorDetectState::MARK_377_SEEN
528 // '\0' --> ErrorDetectState::MARK_0_SEEN
529 // '\0' --> break detected
531 // parity error sequence:
532 // '\377' --> ErrorDetectState::MARK_377_SEEN
533 // '\0' --> ErrorDetectState::MARK_0_SEEN
534 // character with parity error --> parity error detected
536 // break/parity error sequences are removed from the byte stream
537 // '\377' '\377' sequence is replaced with '\377'
538 int SerialIoHandlerPosix::CheckReceiveError(char* buffer,
541 bool& break_detected,
542 bool& parity_error_detected) {
543 int new_bytes_read = num_chars_stashed_;
544 DCHECK_LE(new_bytes_read, 2);
546 for (int i = 0; i < bytes_read; ++i) {
548 if (new_bytes_read == 0) {
549 chars_stashed_[0] = ch;
550 } else if (new_bytes_read == 1) {
551 chars_stashed_[1] = ch;
553 buffer[new_bytes_read - 2] = ch;
556 switch (error_detect_state_) {
557 case ErrorDetectState::NO_ERROR:
559 error_detect_state_ = ErrorDetectState::MARK_377_SEEN;
562 case ErrorDetectState::MARK_377_SEEN:
563 DCHECK_GE(new_bytes_read, 2);
565 error_detect_state_ = ErrorDetectState::MARK_0_SEEN;
568 // receive two bytes '\377' '\377', since ISTRIP is not set and
569 // PARMRK is set, a valid byte '\377' is passed to the program as
570 // two bytes, '\377' '\377'. Replace these two bytes with one byte
571 // of '\377', and set error_detect_state_ back to
572 // ErrorDetectState::NO_ERROR.
575 error_detect_state_ = ErrorDetectState::NO_ERROR;
578 case ErrorDetectState::MARK_0_SEEN:
579 DCHECK_GE(new_bytes_read, 3);
581 break_detected = true;
583 error_detect_state_ = ErrorDetectState::NO_ERROR;
585 if (parity_check_enabled_) {
586 parity_error_detected = true;
588 error_detect_state_ = ErrorDetectState::NO_ERROR;
589 } else if (ch == '\377') {
590 error_detect_state_ = ErrorDetectState::MARK_377_SEEN;
592 error_detect_state_ = ErrorDetectState::NO_ERROR;
598 // Now new_bytes_read bytes should be returned to the caller (including the
599 // previously stashed characters that were stored at chars_stashed_[]) and are
600 // now stored at: chars_stashed_[0], chars_stashed_[1], buffer[...].
602 // Stash up to 2 characters that are potentially part of a break/parity error
603 // sequence. The buffer may also not be large enough to store all the bytes.
604 // tmp[] stores the characters that need to be stashed for this read.
606 num_chars_stashed_ = 0;
607 if (error_detect_state_ == ErrorDetectState::MARK_0_SEEN ||
608 new_bytes_read - buffer_len == 2) {
609 // need to stash the last two characters
610 if (new_bytes_read == 2) {
611 memcpy(tmp, chars_stashed_, new_bytes_read);
613 if (new_bytes_read == 3) {
614 tmp[0] = chars_stashed_[1];
616 tmp[0] = buffer[new_bytes_read - 4];
618 tmp[1] = buffer[new_bytes_read - 3];
620 num_chars_stashed_ = 2;
621 } else if (error_detect_state_ == ErrorDetectState::MARK_377_SEEN ||
622 new_bytes_read - buffer_len == 1) {
623 // need to stash the last character
624 if (new_bytes_read <= 2) {
625 tmp[0] = chars_stashed_[new_bytes_read - 1];
627 tmp[0] = buffer[new_bytes_read - 3];
629 num_chars_stashed_ = 1;
632 new_bytes_read -= num_chars_stashed_;
633 if (new_bytes_read > 2) {
634 // right shift two bytes to store bytes from chars_stashed_[]
635 memmove(buffer + 2, buffer, new_bytes_read - 2);
637 memcpy(buffer, chars_stashed_, std::min(new_bytes_read, 2));
638 memcpy(chars_stashed_, tmp, num_chars_stashed_);
639 return new_bytes_read;
642 } // namespace device