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 "device/serial/serial_io_handler_posix.h"
10 #include "base/files/file_util.h"
11 #include "base/posix/eintr_wrapper.h"
12 #include "build/build_config.h"
15 #include <asm-generic/ioctls.h>
16 #include <linux/serial.h>
18 // The definition of struct termios2 is copied from asm-generic/termbits.h
19 // because including that header directly conflicts with termios.h.
22 tcflag_t c_iflag; // input mode flags
23 tcflag_t c_oflag; // output mode flags
24 tcflag_t c_cflag; // control mode flags
25 tcflag_t c_lflag; // local mode flags
26 cc_t c_line; // line discipline
27 cc_t c_cc[19]; // control characters
28 speed_t c_ispeed; // input speed
29 speed_t c_ospeed; // output speed
33 #endif // defined(OS_LINUX)
35 #if defined(OS_MACOSX)
36 #include <IOKit/serial/ioss.h>
41 // Convert an integral bit rate to a nominal one. Returns |true|
42 // if the conversion was successful and |false| otherwise.
43 bool BitrateToSpeedConstant(int bitrate, speed_t* speed) {
44 #define BITRATE_TO_SPEED_CASE(x) \
49 BITRATE_TO_SPEED_CASE(0)
50 BITRATE_TO_SPEED_CASE(50)
51 BITRATE_TO_SPEED_CASE(75)
52 BITRATE_TO_SPEED_CASE(110)
53 BITRATE_TO_SPEED_CASE(134)
54 BITRATE_TO_SPEED_CASE(150)
55 BITRATE_TO_SPEED_CASE(200)
56 BITRATE_TO_SPEED_CASE(300)
57 BITRATE_TO_SPEED_CASE(600)
58 BITRATE_TO_SPEED_CASE(1200)
59 BITRATE_TO_SPEED_CASE(1800)
60 BITRATE_TO_SPEED_CASE(2400)
61 BITRATE_TO_SPEED_CASE(4800)
62 BITRATE_TO_SPEED_CASE(9600)
63 BITRATE_TO_SPEED_CASE(19200)
64 BITRATE_TO_SPEED_CASE(38400)
65 #if !defined(OS_MACOSX)
66 BITRATE_TO_SPEED_CASE(57600)
67 BITRATE_TO_SPEED_CASE(115200)
68 BITRATE_TO_SPEED_CASE(230400)
69 BITRATE_TO_SPEED_CASE(460800)
70 BITRATE_TO_SPEED_CASE(576000)
71 BITRATE_TO_SPEED_CASE(921600)
76 #undef BITRATE_TO_SPEED_CASE
79 #if !defined(OS_LINUX)
80 // Convert a known nominal speed into an integral bitrate. Returns |true|
81 // if the conversion was successful and |false| otherwise.
82 bool SpeedConstantToBitrate(speed_t speed, int* bitrate) {
83 #define SPEED_TO_BITRATE_CASE(x) \
88 SPEED_TO_BITRATE_CASE(0)
89 SPEED_TO_BITRATE_CASE(50)
90 SPEED_TO_BITRATE_CASE(75)
91 SPEED_TO_BITRATE_CASE(110)
92 SPEED_TO_BITRATE_CASE(134)
93 SPEED_TO_BITRATE_CASE(150)
94 SPEED_TO_BITRATE_CASE(200)
95 SPEED_TO_BITRATE_CASE(300)
96 SPEED_TO_BITRATE_CASE(600)
97 SPEED_TO_BITRATE_CASE(1200)
98 SPEED_TO_BITRATE_CASE(1800)
99 SPEED_TO_BITRATE_CASE(2400)
100 SPEED_TO_BITRATE_CASE(4800)
101 SPEED_TO_BITRATE_CASE(9600)
102 SPEED_TO_BITRATE_CASE(19200)
103 SPEED_TO_BITRATE_CASE(38400)
107 #undef SPEED_TO_BITRATE_CASE
116 scoped_refptr<SerialIoHandler> SerialIoHandler::Create(
117 scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) {
118 return new SerialIoHandlerPosix(ui_thread_task_runner);
121 void SerialIoHandlerPosix::ReadImpl() {
122 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
123 DCHECK(pending_read_buffer());
124 DCHECK(file().IsValid());
126 // Try to read immediately. This is needed because on some platforms
127 // (e.g., OSX) there may not be a notification from the message loop
128 // when the fd is ready to read immediately after it is opened. There
129 // is no danger of blocking because the fd is opened with async flag.
133 void SerialIoHandlerPosix::WriteImpl() {
134 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
135 DCHECK(pending_write_buffer());
136 DCHECK(file().IsValid());
138 EnsureWatchingWrites();
141 void SerialIoHandlerPosix::CancelReadImpl() {
142 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
143 file_read_watcher_.reset();
144 QueueReadCompleted(0, read_cancel_reason());
147 void SerialIoHandlerPosix::CancelWriteImpl() {
148 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
149 file_write_watcher_.reset();
150 QueueWriteCompleted(0, write_cancel_reason());
153 bool SerialIoHandlerPosix::ConfigurePortImpl() {
154 #if defined(OS_LINUX)
155 struct termios2 config;
156 if (ioctl(file().GetPlatformFile(), TCGETS2, &config) < 0) {
158 struct termios config;
159 if (tcgetattr(file().GetPlatformFile(), &config) != 0) {
161 VPLOG(1) << "Failed to get port configuration";
165 // Set flags for 'raw' operation
166 config.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG);
167 config.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
168 config.c_iflag |= PARMRK;
169 config.c_oflag &= ~OPOST;
171 // CLOCAL causes the system to disregard the DCD signal state.
172 // CREAD enables reading from the port.
173 config.c_cflag |= (CLOCAL | CREAD);
175 DCHECK(options().bitrate);
176 speed_t bitrate_opt = B0;
177 #if defined(OS_MACOSX)
178 bool need_iossiospeed = false;
180 if (BitrateToSpeedConstant(options().bitrate, &bitrate_opt)) {
181 #if defined(OS_LINUX)
182 config.c_cflag &= ~CBAUD;
183 config.c_cflag |= bitrate_opt;
185 cfsetispeed(&config, bitrate_opt);
186 cfsetospeed(&config, bitrate_opt);
189 // Attempt to set a custom speed.
190 #if defined(OS_LINUX)
191 config.c_cflag &= ~CBAUD;
192 config.c_cflag |= CBAUDEX;
193 config.c_ispeed = config.c_ospeed = options().bitrate;
194 #elif defined(OS_MACOSX)
195 // cfsetispeed and cfsetospeed sometimes work for custom baud rates on OS
196 // X but the IOSSIOSPEED ioctl is more reliable but has to be done after
197 // the rest of the port parameters are set or else it will be overwritten.
198 need_iossiospeed = true;
204 DCHECK(options().data_bits != mojom::SerialDataBits::NONE);
205 config.c_cflag &= ~CSIZE;
206 switch (options().data_bits) {
207 case mojom::SerialDataBits::SEVEN:
208 config.c_cflag |= CS7;
210 case mojom::SerialDataBits::EIGHT:
212 config.c_cflag |= CS8;
216 DCHECK(options().parity_bit != mojom::SerialParityBit::NONE);
217 switch (options().parity_bit) {
218 case mojom::SerialParityBit::EVEN:
219 config.c_cflag |= PARENB;
220 config.c_cflag &= ~PARODD;
222 case mojom::SerialParityBit::ODD:
223 config.c_cflag |= (PARODD | PARENB);
225 case mojom::SerialParityBit::NO_PARITY:
227 config.c_cflag &= ~(PARODD | PARENB);
231 error_detect_state_ = ErrorDetectState::NO_ERROR;
232 num_chars_stashed_ = 0;
234 if (config.c_cflag & PARENB) {
235 config.c_iflag &= ~IGNPAR;
236 config.c_iflag |= INPCK;
237 parity_check_enabled_ = true;
239 config.c_iflag |= IGNPAR;
240 config.c_iflag &= ~INPCK;
241 parity_check_enabled_ = false;
244 DCHECK(options().stop_bits != mojom::SerialStopBits::NONE);
245 switch (options().stop_bits) {
246 case mojom::SerialStopBits::TWO:
247 config.c_cflag |= CSTOPB;
249 case mojom::SerialStopBits::ONE:
251 config.c_cflag &= ~CSTOPB;
255 DCHECK(options().has_cts_flow_control);
256 if (options().cts_flow_control) {
257 config.c_cflag |= CRTSCTS;
259 config.c_cflag &= ~CRTSCTS;
262 #if defined(OS_LINUX)
263 if (ioctl(file().GetPlatformFile(), TCSETS2, &config) < 0) {
265 if (tcsetattr(file().GetPlatformFile(), TCSANOW, &config) != 0) {
267 VPLOG(1) << "Failed to set port attributes";
271 #if defined(OS_MACOSX)
272 if (need_iossiospeed) {
273 speed_t bitrate = options().bitrate;
274 if (ioctl(file().GetPlatformFile(), IOSSIOSPEED, &bitrate) == -1) {
275 VPLOG(1) << "Failed to set custom baud rate";
284 bool SerialIoHandlerPosix::PostOpen() {
285 #if defined(OS_CHROMEOS)
286 // The Chrome OS permission broker does not open devices in async mode.
287 return base::SetNonBlocking(file().GetPlatformFile());
293 SerialIoHandlerPosix::SerialIoHandlerPosix(
294 scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner)
295 : SerialIoHandler(ui_thread_task_runner) {}
297 SerialIoHandlerPosix::~SerialIoHandlerPosix() = default;
299 void SerialIoHandlerPosix::AttemptRead(bool within_read) {
300 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
302 if (pending_read_buffer()) {
303 int bytes_read = HANDLE_EINTR(read(file().GetPlatformFile(),
304 pending_read_buffer(),
305 pending_read_buffer_len()));
306 if (bytes_read < 0) {
307 if (errno == EAGAIN) {
308 // The fd does not have data to read yet so continue waiting.
309 EnsureWatchingReads();
310 } else if (errno == ENXIO) {
311 RunReadCompleted(within_read, 0,
312 mojom::SerialReceiveError::DEVICE_LOST);
314 RunReadCompleted(within_read, 0,
315 mojom::SerialReceiveError::SYSTEM_ERROR);
317 } else if (bytes_read == 0) {
318 RunReadCompleted(within_read, 0, mojom::SerialReceiveError::DEVICE_LOST);
320 bool break_detected = false;
321 bool parity_error_detected = false;
323 CheckReceiveError(pending_read_buffer(), pending_read_buffer_len(),
324 bytes_read, break_detected, parity_error_detected);
326 if (break_detected) {
327 RunReadCompleted(within_read, new_bytes_read,
328 mojom::SerialReceiveError::BREAK);
329 } else if (parity_error_detected) {
330 RunReadCompleted(within_read, new_bytes_read,
331 mojom::SerialReceiveError::PARITY_ERROR);
333 RunReadCompleted(within_read, new_bytes_read,
334 mojom::SerialReceiveError::NONE);
338 // Stop watching the fd if we get notifications with no pending
339 // reads or writes to avoid starving the message loop.
340 file_read_watcher_.reset();
344 void SerialIoHandlerPosix::RunReadCompleted(bool within_read,
346 mojom::SerialReceiveError error) {
348 // Stop watching the fd to avoid more reads until the queued ReadCompleted()
349 // completes and releases the pending_read_buffer.
350 file_read_watcher_.reset();
352 QueueReadCompleted(bytes_read, error);
354 ReadCompleted(bytes_read, error);
358 void SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking() {
359 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
361 if (pending_write_buffer()) {
362 int bytes_written = HANDLE_EINTR(write(file().GetPlatformFile(),
363 pending_write_buffer(),
364 pending_write_buffer_len()));
365 if (bytes_written < 0) {
366 WriteCompleted(0, mojom::SerialSendError::SYSTEM_ERROR);
368 WriteCompleted(bytes_written, mojom::SerialSendError::NONE);
371 // Stop watching the fd if we get notifications with no pending
372 // writes to avoid starving the message loop.
373 file_write_watcher_.reset();
377 void SerialIoHandlerPosix::EnsureWatchingReads() {
378 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
379 DCHECK(file().IsValid());
380 if (!file_read_watcher_) {
381 file_read_watcher_ = base::FileDescriptorWatcher::WatchReadable(
382 file().GetPlatformFile(), base::Bind(&SerialIoHandlerPosix::AttemptRead,
383 base::Unretained(this), false));
387 void SerialIoHandlerPosix::EnsureWatchingWrites() {
388 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
389 DCHECK(file().IsValid());
390 if (!file_write_watcher_) {
391 file_write_watcher_ = base::FileDescriptorWatcher::WatchWritable(
392 file().GetPlatformFile(),
393 base::Bind(&SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking,
394 base::Unretained(this)));
398 bool SerialIoHandlerPosix::Flush() const {
399 if (tcflush(file().GetPlatformFile(), TCIOFLUSH) != 0) {
400 VPLOG(1) << "Failed to flush port";
406 mojom::SerialDeviceControlSignalsPtr SerialIoHandlerPosix::GetControlSignals()
409 if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
410 VPLOG(1) << "Failed to get port control signals";
411 return mojom::SerialDeviceControlSignalsPtr();
414 auto signals = mojom::SerialDeviceControlSignals::New();
415 signals->dcd = (status & TIOCM_CAR) != 0;
416 signals->cts = (status & TIOCM_CTS) != 0;
417 signals->dsr = (status & TIOCM_DSR) != 0;
418 signals->ri = (status & TIOCM_RI) != 0;
422 bool SerialIoHandlerPosix::SetControlSignals(
423 const mojom::SerialHostControlSignals& signals) {
426 if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
427 VPLOG(1) << "Failed to get port control signals";
431 if (signals.has_dtr) {
435 status &= ~TIOCM_DTR;
439 if (signals.has_rts) {
443 status &= ~TIOCM_RTS;
447 if (ioctl(file().GetPlatformFile(), TIOCMSET, &status) != 0) {
448 VPLOG(1) << "Failed to set port control signals";
454 mojom::SerialConnectionInfoPtr SerialIoHandlerPosix::GetPortInfo() const {
455 #if defined(OS_LINUX)
456 struct termios2 config;
457 if (ioctl(file().GetPlatformFile(), TCGETS2, &config) < 0) {
459 struct termios config;
460 if (tcgetattr(file().GetPlatformFile(), &config) == -1) {
462 VPLOG(1) << "Failed to get port info";
463 return mojom::SerialConnectionInfoPtr();
466 auto info = mojom::SerialConnectionInfo::New();
467 #if defined(OS_LINUX)
468 // Linux forces c_ospeed to contain the correct value, which is nice.
469 info->bitrate = config.c_ospeed;
471 speed_t ispeed = cfgetispeed(&config);
472 speed_t ospeed = cfgetospeed(&config);
473 if (ispeed == ospeed) {
475 if (SpeedConstantToBitrate(ispeed, &bitrate)) {
476 info->bitrate = bitrate;
477 } else if (ispeed > 0) {
478 info->bitrate = static_cast<int>(ispeed);
483 if ((config.c_cflag & CSIZE) == CS7) {
484 info->data_bits = mojom::SerialDataBits::SEVEN;
485 } else if ((config.c_cflag & CSIZE) == CS8) {
486 info->data_bits = mojom::SerialDataBits::EIGHT;
488 info->data_bits = mojom::SerialDataBits::NONE;
490 if (config.c_cflag & PARENB) {
491 info->parity_bit = (config.c_cflag & PARODD) ? mojom::SerialParityBit::ODD
492 : mojom::SerialParityBit::EVEN;
494 info->parity_bit = mojom::SerialParityBit::NO_PARITY;
496 info->stop_bits = (config.c_cflag & CSTOPB) ? mojom::SerialStopBits::TWO
497 : mojom::SerialStopBits::ONE;
498 info->cts_flow_control = (config.c_cflag & CRTSCTS) != 0;
502 bool SerialIoHandlerPosix::SetBreak() {
503 if (ioctl(file().GetPlatformFile(), TIOCSBRK, 0) != 0) {
504 VPLOG(1) << "Failed to set break";
511 bool SerialIoHandlerPosix::ClearBreak() {
512 if (ioctl(file().GetPlatformFile(), TIOCCBRK, 0) != 0) {
513 VPLOG(1) << "Failed to clear break";
520 // '\377' --> ErrorDetectState::MARK_377_SEEN
521 // '\0' --> ErrorDetectState::MARK_0_SEEN
522 // '\0' --> break detected
524 // parity error sequence:
525 // '\377' --> ErrorDetectState::MARK_377_SEEN
526 // '\0' --> ErrorDetectState::MARK_0_SEEN
527 // character with parity error --> parity error detected
529 // break/parity error sequences are removed from the byte stream
530 // '\377' '\377' sequence is replaced with '\377'
531 int SerialIoHandlerPosix::CheckReceiveError(char* buffer,
534 bool& break_detected,
535 bool& parity_error_detected) {
536 int new_bytes_read = num_chars_stashed_;
537 DCHECK_LE(new_bytes_read, 2);
539 for (int i = 0; i < bytes_read; ++i) {
541 if (new_bytes_read == 0) {
542 chars_stashed_[0] = ch;
543 } else if (new_bytes_read == 1) {
544 chars_stashed_[1] = ch;
546 buffer[new_bytes_read - 2] = ch;
549 switch (error_detect_state_) {
550 case ErrorDetectState::NO_ERROR:
552 error_detect_state_ = ErrorDetectState::MARK_377_SEEN;
555 case ErrorDetectState::MARK_377_SEEN:
556 DCHECK_GE(new_bytes_read, 2);
558 error_detect_state_ = ErrorDetectState::MARK_0_SEEN;
561 // receive two bytes '\377' '\377', since ISTRIP is not set and
562 // PARMRK is set, a valid byte '\377' is passed to the program as
563 // two bytes, '\377' '\377'. Replace these two bytes with one byte
564 // of '\377', and set error_detect_state_ back to
565 // ErrorDetectState::NO_ERROR.
568 error_detect_state_ = ErrorDetectState::NO_ERROR;
571 case ErrorDetectState::MARK_0_SEEN:
572 DCHECK_GE(new_bytes_read, 3);
574 break_detected = true;
576 error_detect_state_ = ErrorDetectState::NO_ERROR;
578 if (parity_check_enabled_) {
579 parity_error_detected = true;
581 error_detect_state_ = ErrorDetectState::NO_ERROR;
582 } else if (ch == '\377') {
583 error_detect_state_ = ErrorDetectState::MARK_377_SEEN;
585 error_detect_state_ = ErrorDetectState::NO_ERROR;
591 // Now new_bytes_read bytes should be returned to the caller (including the
592 // previously stashed characters that were stored at chars_stashed_[]) and are
593 // now stored at: chars_stashed_[0], chars_stashed_[1], buffer[...].
595 // Stash up to 2 characters that are potentially part of a break/parity error
596 // sequence. The buffer may also not be large enough to store all the bytes.
597 // tmp[] stores the characters that need to be stashed for this read.
599 num_chars_stashed_ = 0;
600 if (error_detect_state_ == ErrorDetectState::MARK_0_SEEN ||
601 new_bytes_read - buffer_len == 2) {
602 // need to stash the last two characters
603 if (new_bytes_read == 2) {
604 memcpy(tmp, chars_stashed_, new_bytes_read);
606 if (new_bytes_read == 3) {
607 tmp[0] = chars_stashed_[1];
609 tmp[0] = buffer[new_bytes_read - 4];
611 tmp[1] = buffer[new_bytes_read - 3];
613 num_chars_stashed_ = 2;
614 } else if (error_detect_state_ == ErrorDetectState::MARK_377_SEEN ||
615 new_bytes_read - buffer_len == 1) {
616 // need to stash the last character
617 if (new_bytes_read <= 2) {
618 tmp[0] = chars_stashed_[new_bytes_read - 1];
620 tmp[0] = buffer[new_bytes_read - 3];
622 num_chars_stashed_ = 1;
625 new_bytes_read -= num_chars_stashed_;
626 if (new_bytes_read > 2) {
627 // right shift two bytes to store bytes from chars_stashed_[]
628 memmove(buffer + 2, buffer, new_bytes_read - 2);
630 memcpy(buffer, chars_stashed_, std::min(new_bytes_read, 2));
631 memcpy(chars_stashed_, tmp, num_chars_stashed_);
632 return new_bytes_read;
635 std::string SerialIoHandler::MaybeFixUpPortName(const std::string& port_name) {
639 } // namespace device