| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/device/serial/serial_io_handler_posix.h" |
| |
| #include <sys/ioctl.h> |
| #include <termios.h> |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/device_event_log/device_event_log.h" |
| |
| #if defined(OS_LINUX) || defined(OS_CHROMEOS) |
| #include <asm-generic/ioctls.h> |
| #include <linux/serial.h> |
| |
| // The definition of struct termios2 is copied from asm-generic/termbits.h |
| // because including that header directly conflicts with termios.h. |
| extern "C" { |
| struct termios2 { |
| tcflag_t c_iflag; // input mode flags |
| tcflag_t c_oflag; // output mode flags |
| tcflag_t c_cflag; // control mode flags |
| tcflag_t c_lflag; // local mode flags |
| cc_t c_line; // line discipline |
| cc_t c_cc[19]; // control characters |
| speed_t c_ispeed; // input speed |
| speed_t c_ospeed; // output speed |
| }; |
| } |
| |
| #endif // defined(OS_LINUX) || defined(OS_CHROMEOS) |
| |
| #if defined(OS_MAC) |
| #include <IOKit/serial/ioss.h> |
| #endif |
| |
| namespace { |
| |
| // Convert an integral bit rate to a nominal one. Returns |true| |
| // if the conversion was successful and |false| otherwise. |
| bool BitrateToSpeedConstant(int bitrate, speed_t* speed) { |
| #define BITRATE_TO_SPEED_CASE(x) \ |
| case x: \ |
| *speed = B##x; \ |
| return true; |
| switch (bitrate) { |
| BITRATE_TO_SPEED_CASE(0) |
| BITRATE_TO_SPEED_CASE(50) |
| BITRATE_TO_SPEED_CASE(75) |
| BITRATE_TO_SPEED_CASE(110) |
| BITRATE_TO_SPEED_CASE(134) |
| BITRATE_TO_SPEED_CASE(150) |
| BITRATE_TO_SPEED_CASE(200) |
| BITRATE_TO_SPEED_CASE(300) |
| BITRATE_TO_SPEED_CASE(600) |
| BITRATE_TO_SPEED_CASE(1200) |
| BITRATE_TO_SPEED_CASE(1800) |
| BITRATE_TO_SPEED_CASE(2400) |
| BITRATE_TO_SPEED_CASE(4800) |
| BITRATE_TO_SPEED_CASE(9600) |
| BITRATE_TO_SPEED_CASE(19200) |
| BITRATE_TO_SPEED_CASE(38400) |
| #if !defined(OS_MAC) |
| BITRATE_TO_SPEED_CASE(57600) |
| BITRATE_TO_SPEED_CASE(115200) |
| BITRATE_TO_SPEED_CASE(230400) |
| BITRATE_TO_SPEED_CASE(460800) |
| BITRATE_TO_SPEED_CASE(576000) |
| BITRATE_TO_SPEED_CASE(921600) |
| #endif |
| default: |
| return false; |
| } |
| #undef BITRATE_TO_SPEED_CASE |
| } |
| |
| #if !defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| // Convert a known nominal speed into an integral bitrate. Returns |true| |
| // if the conversion was successful and |false| otherwise. |
| bool SpeedConstantToBitrate(speed_t speed, int* bitrate) { |
| #define SPEED_TO_BITRATE_CASE(x) \ |
| case B##x: \ |
| *bitrate = x; \ |
| return true; |
| switch (speed) { |
| SPEED_TO_BITRATE_CASE(0) |
| SPEED_TO_BITRATE_CASE(50) |
| SPEED_TO_BITRATE_CASE(75) |
| SPEED_TO_BITRATE_CASE(110) |
| SPEED_TO_BITRATE_CASE(134) |
| SPEED_TO_BITRATE_CASE(150) |
| SPEED_TO_BITRATE_CASE(200) |
| SPEED_TO_BITRATE_CASE(300) |
| SPEED_TO_BITRATE_CASE(600) |
| SPEED_TO_BITRATE_CASE(1200) |
| SPEED_TO_BITRATE_CASE(1800) |
| SPEED_TO_BITRATE_CASE(2400) |
| SPEED_TO_BITRATE_CASE(4800) |
| SPEED_TO_BITRATE_CASE(9600) |
| SPEED_TO_BITRATE_CASE(19200) |
| SPEED_TO_BITRATE_CASE(38400) |
| default: |
| return false; |
| } |
| #undef SPEED_TO_BITRATE_CASE |
| } |
| #endif |
| |
| } // namespace |
| |
| namespace device { |
| |
| // static |
| scoped_refptr<SerialIoHandler> SerialIoHandler::Create( |
| const base::FilePath& port, |
| scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) { |
| return new SerialIoHandlerPosix(port, std::move(ui_thread_task_runner)); |
| } |
| |
| void SerialIoHandlerPosix::ReadImpl() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(IsReadPending()); |
| |
| // Try to read immediately. This is needed because on some platforms |
| // (e.g., OSX) there may not be a notification from the message loop |
| // when the fd is ready to read immediately after it is opened. There |
| // is no danger of blocking because the fd is opened with async flag. |
| AttemptRead(); |
| } |
| |
| void SerialIoHandlerPosix::WriteImpl() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(IsWritePending()); |
| |
| EnsureWatchingWrites(); |
| } |
| |
| void SerialIoHandlerPosix::CancelReadImpl() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| StopWatchingFileRead(); |
| ReadCompleted(0, read_cancel_reason()); |
| } |
| |
| void SerialIoHandlerPosix::CancelWriteImpl() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| StopWatchingFileWrite(); |
| WriteCompleted(0, write_cancel_reason()); |
| } |
| |
| bool SerialIoHandlerPosix::ConfigurePortImpl() { |
| #if defined(OS_LINUX) || defined(OS_CHROMEOS) |
| struct termios2 config; |
| if (ioctl(file().GetPlatformFile(), TCGETS2, &config) < 0) { |
| #else |
| struct termios config; |
| if (tcgetattr(file().GetPlatformFile(), &config) != 0) { |
| #endif |
| SERIAL_PLOG(DEBUG) << "Failed to get port configuration"; |
| return false; |
| } |
| |
| // Set flags for 'raw' operation |
| config.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG); |
| config.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | ICRNL | IXON); |
| config.c_iflag |= PARMRK; |
| config.c_oflag &= ~OPOST; |
| |
| // CLOCAL causes the system to disregard the DCD signal state. |
| // CREAD enables reading from the port. |
| config.c_cflag |= (CLOCAL | CREAD); |
| |
| DCHECK(options().bitrate); |
| speed_t bitrate_opt = B0; |
| #if defined(OS_MAC) |
| bool need_iossiospeed = false; |
| #endif |
| if (BitrateToSpeedConstant(options().bitrate, &bitrate_opt)) { |
| #if defined(OS_LINUX) || defined(OS_CHROMEOS) |
| config.c_cflag &= ~CBAUD; |
| config.c_cflag |= bitrate_opt; |
| #else |
| cfsetispeed(&config, bitrate_opt); |
| cfsetospeed(&config, bitrate_opt); |
| #endif |
| } else { |
| // Attempt to set a custom speed. |
| #if defined(OS_LINUX) || defined(OS_CHROMEOS) |
| config.c_cflag &= ~CBAUD; |
| config.c_cflag |= CBAUDEX; |
| config.c_ispeed = config.c_ospeed = options().bitrate; |
| #elif defined(OS_MAC) |
| // cfsetispeed and cfsetospeed sometimes work for custom baud rates on OS |
| // X but the IOSSIOSPEED ioctl is more reliable but has to be done after |
| // the rest of the port parameters are set or else it will be overwritten. |
| need_iossiospeed = true; |
| #else |
| return false; |
| #endif |
| } |
| |
| DCHECK(options().data_bits != mojom::SerialDataBits::NONE); |
| config.c_cflag &= ~CSIZE; |
| switch (options().data_bits) { |
| case mojom::SerialDataBits::SEVEN: |
| config.c_cflag |= CS7; |
| break; |
| case mojom::SerialDataBits::EIGHT: |
| default: |
| config.c_cflag |= CS8; |
| break; |
| } |
| |
| DCHECK(options().parity_bit != mojom::SerialParityBit::NONE); |
| switch (options().parity_bit) { |
| case mojom::SerialParityBit::EVEN: |
| config.c_cflag |= PARENB; |
| config.c_cflag &= ~PARODD; |
| break; |
| case mojom::SerialParityBit::ODD: |
| config.c_cflag |= (PARODD | PARENB); |
| break; |
| case mojom::SerialParityBit::NO_PARITY: |
| default: |
| config.c_cflag &= ~(PARODD | PARENB); |
| break; |
| } |
| |
| error_detect_state_ = ErrorDetectState::NO_ERROR; |
| num_chars_stashed_ = 0; |
| |
| if (config.c_cflag & PARENB) { |
| config.c_iflag &= ~IGNPAR; |
| config.c_iflag |= INPCK; |
| parity_check_enabled_ = true; |
| } else { |
| config.c_iflag |= IGNPAR; |
| config.c_iflag &= ~INPCK; |
| parity_check_enabled_ = false; |
| } |
| |
| DCHECK(options().stop_bits != mojom::SerialStopBits::NONE); |
| switch (options().stop_bits) { |
| case mojom::SerialStopBits::TWO: |
| config.c_cflag |= CSTOPB; |
| break; |
| case mojom::SerialStopBits::ONE: |
| default: |
| config.c_cflag &= ~CSTOPB; |
| break; |
| } |
| |
| DCHECK(options().has_cts_flow_control); |
| if (options().cts_flow_control) { |
| config.c_cflag |= CRTSCTS; |
| } else { |
| config.c_cflag &= ~CRTSCTS; |
| } |
| |
| #if defined(OS_LINUX) || defined(OS_CHROMEOS) |
| if (ioctl(file().GetPlatformFile(), TCSETS2, &config) < 0) { |
| #else |
| if (tcsetattr(file().GetPlatformFile(), TCSANOW, &config) != 0) { |
| #endif |
| SERIAL_PLOG(DEBUG) << "Failed to set port attributes"; |
| return false; |
| } |
| |
| #if defined(OS_MAC) |
| if (need_iossiospeed) { |
| speed_t bitrate = options().bitrate; |
| if (ioctl(file().GetPlatformFile(), IOSSIOSPEED, &bitrate) == -1) { |
| SERIAL_PLOG(DEBUG) << "Failed to set custom baud rate"; |
| return false; |
| } |
| } |
| #endif |
| |
| return true; |
| } |
| |
| bool SerialIoHandlerPosix::PostOpen() { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) |
| // The Chrome OS permission broker does not open devices in async mode. |
| return base::SetNonBlocking(file().GetPlatformFile()); |
| #else |
| return true; |
| #endif |
| } |
| |
| void SerialIoHandlerPosix::PreClose() { |
| StopWatchingFileRead(); |
| StopWatchingFileWrite(); |
| } |
| |
| SerialIoHandlerPosix::SerialIoHandlerPosix( |
| const base::FilePath& port, |
| scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) |
| : SerialIoHandler(port, std::move(ui_thread_task_runner)) {} |
| |
| SerialIoHandlerPosix::~SerialIoHandlerPosix() = default; |
| |
| void SerialIoHandlerPosix::AttemptRead() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (IsReadPending()) { |
| int bytes_read = HANDLE_EINTR(read(file().GetPlatformFile(), |
| pending_read_buffer().data(), |
| pending_read_buffer().size())); |
| if (bytes_read < 0) { |
| if (errno == EAGAIN) { |
| // The fd does not have data to read yet so continue waiting. |
| EnsureWatchingReads(); |
| } else if (errno == ENXIO) { |
| StopWatchingFileRead(); |
| ReadCompleted(0, mojom::SerialReceiveError::DEVICE_LOST); |
| } else { |
| SERIAL_PLOG(DEBUG) << "Read failed"; |
| ReadCompleted(0, mojom::SerialReceiveError::SYSTEM_ERROR); |
| } |
| } else if (bytes_read == 0) { |
| StopWatchingFileRead(); |
| ReadCompleted(0, mojom::SerialReceiveError::DEVICE_LOST); |
| } else { |
| bool break_detected = false; |
| bool parity_error_detected = false; |
| int new_bytes_read = |
| CheckReceiveError(pending_read_buffer(), bytes_read, break_detected, |
| parity_error_detected); |
| |
| if (break_detected) { |
| ReadCompleted(new_bytes_read, mojom::SerialReceiveError::BREAK); |
| } else if (parity_error_detected) { |
| ReadCompleted(new_bytes_read, mojom::SerialReceiveError::PARITY_ERROR); |
| } else { |
| ReadCompleted(new_bytes_read, mojom::SerialReceiveError::NONE); |
| } |
| } |
| } else { |
| // Stop watching the fd if we get notifications with no pending |
| // reads or writes to avoid starving the message loop. |
| StopWatchingFileRead(); |
| } |
| } |
| |
| void SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (IsWritePending()) { |
| int bytes_written = HANDLE_EINTR(write(file().GetPlatformFile(), |
| pending_write_buffer().data(), |
| pending_write_buffer().size())); |
| if (bytes_written < 0) { |
| if (errno == EIO || errno == ENXIO) { |
| StopWatchingFileWrite(); |
| WriteCompleted(0, mojom::SerialSendError::DISCONNECTED); |
| } else { |
| SERIAL_PLOG(DEBUG) << "Write failed"; |
| WriteCompleted(0, mojom::SerialSendError::SYSTEM_ERROR); |
| } |
| } else { |
| WriteCompleted(bytes_written, mojom::SerialSendError::NONE); |
| } |
| } else { |
| // Stop watching the fd if we get notifications with no pending |
| // writes to avoid starving the message loop. |
| StopWatchingFileWrite(); |
| } |
| } |
| |
| void SerialIoHandlerPosix::EnsureWatchingReads() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(file().IsValid()); |
| if (!file_read_watcher_) { |
| file_read_watcher_ = base::FileDescriptorWatcher::WatchReadable( |
| file().GetPlatformFile(), |
| base::BindRepeating(&SerialIoHandlerPosix::AttemptRead, |
| base::Unretained(this))); |
| } |
| } |
| |
| void SerialIoHandlerPosix::EnsureWatchingWrites() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(file().IsValid()); |
| if (!file_write_watcher_) { |
| file_write_watcher_ = base::FileDescriptorWatcher::WatchWritable( |
| file().GetPlatformFile(), |
| base::BindRepeating( |
| &SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking, |
| base::Unretained(this))); |
| } |
| } |
| |
| void SerialIoHandlerPosix::StopWatchingFileRead() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (file_read_watcher_) { |
| // Check that file is valid before stopping the watch, to avoid getting a |
| // hard to diagnose crash in MessagePumpLibEvent. https://crbug.com/996777 |
| CHECK(file().IsValid()); |
| file_read_watcher_.reset(); |
| } |
| } |
| |
| void SerialIoHandlerPosix::StopWatchingFileWrite() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (file_write_watcher_) { |
| // Check that file is valid before stopping the watch, to avoid getting a |
| // hard to diagnose crash in MessagePumpLibEvent. https://crbug.com/996777 |
| CHECK(file().IsValid()); |
| file_write_watcher_.reset(); |
| } |
| } |
| |
| void SerialIoHandlerPosix::Flush(mojom::SerialPortFlushMode mode) const { |
| int queue_selector; |
| switch (mode) { |
| case mojom::SerialPortFlushMode::kReceiveAndTransmit: |
| queue_selector = TCIOFLUSH; |
| break; |
| case mojom::SerialPortFlushMode::kReceive: |
| queue_selector = TCIFLUSH; |
| break; |
| case mojom::SerialPortFlushMode::kTransmit: |
| queue_selector = TCOFLUSH; |
| break; |
| } |
| |
| if (tcflush(file().GetPlatformFile(), queue_selector) != 0) |
| SERIAL_PLOG(DEBUG) << "Failed to flush port"; |
| } |
| |
| void SerialIoHandlerPosix::Drain() { |
| if (tcdrain(file().GetPlatformFile()) != 0) |
| SERIAL_PLOG(DEBUG) << "Failed to drain port"; |
| } |
| |
| mojom::SerialPortControlSignalsPtr SerialIoHandlerPosix::GetControlSignals() |
| const { |
| int status; |
| if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) { |
| SERIAL_PLOG(DEBUG) << "Failed to get port control signals"; |
| return mojom::SerialPortControlSignalsPtr(); |
| } |
| |
| auto signals = mojom::SerialPortControlSignals::New(); |
| signals->dcd = (status & TIOCM_CAR) != 0; |
| signals->cts = (status & TIOCM_CTS) != 0; |
| signals->dsr = (status & TIOCM_DSR) != 0; |
| signals->ri = (status & TIOCM_RI) != 0; |
| return signals; |
| } |
| |
| bool SerialIoHandlerPosix::SetControlSignals( |
| const mojom::SerialHostControlSignals& signals) { |
| // Collect signals that need to be set or cleared on the port. |
| int set = 0; |
| int clear = 0; |
| |
| if (signals.has_dtr) { |
| if (signals.dtr) { |
| set |= TIOCM_DTR; |
| } else { |
| clear |= TIOCM_DTR; |
| } |
| } |
| |
| if (signals.has_rts) { |
| if (signals.rts) { |
| set |= TIOCM_RTS; |
| } else { |
| clear |= TIOCM_RTS; |
| } |
| } |
| |
| if (set && ioctl(file().GetPlatformFile(), TIOCMBIS, &set) != 0) { |
| SERIAL_PLOG(DEBUG) << "Failed to set port control signals"; |
| return false; |
| } |
| |
| if (clear && ioctl(file().GetPlatformFile(), TIOCMBIC, &clear) != 0) { |
| SERIAL_PLOG(DEBUG) << "Failed to clear port control signals"; |
| return false; |
| } |
| |
| if (signals.has_brk) { |
| if (signals.brk) { |
| if (ioctl(file().GetPlatformFile(), TIOCSBRK, 0) != 0) { |
| SERIAL_PLOG(DEBUG) << "Failed to set break"; |
| return false; |
| } |
| } else { |
| if (ioctl(file().GetPlatformFile(), TIOCCBRK, 0) != 0) { |
| SERIAL_PLOG(DEBUG) << "Failed to clear break"; |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| mojom::SerialConnectionInfoPtr SerialIoHandlerPosix::GetPortInfo() const { |
| #if defined(OS_LINUX) || defined(OS_CHROMEOS) |
| struct termios2 config; |
| if (ioctl(file().GetPlatformFile(), TCGETS2, &config) < 0) { |
| #else |
| struct termios config; |
| if (tcgetattr(file().GetPlatformFile(), &config) == -1) { |
| #endif |
| SERIAL_PLOG(DEBUG) << "Failed to get port info"; |
| return mojom::SerialConnectionInfoPtr(); |
| } |
| |
| auto info = mojom::SerialConnectionInfo::New(); |
| #if defined(OS_LINUX) || defined(OS_CHROMEOS) |
| // Linux forces c_ospeed to contain the correct value, which is nice. |
| info->bitrate = config.c_ospeed; |
| #else |
| speed_t ispeed = cfgetispeed(&config); |
| speed_t ospeed = cfgetospeed(&config); |
| if (ispeed == ospeed) { |
| int bitrate = 0; |
| if (SpeedConstantToBitrate(ispeed, &bitrate)) { |
| info->bitrate = bitrate; |
| } else if (ispeed > 0) { |
| info->bitrate = static_cast<int>(ispeed); |
| } |
| } |
| #endif |
| |
| if ((config.c_cflag & CSIZE) == CS7) { |
| info->data_bits = mojom::SerialDataBits::SEVEN; |
| } else if ((config.c_cflag & CSIZE) == CS8) { |
| info->data_bits = mojom::SerialDataBits::EIGHT; |
| } else { |
| info->data_bits = mojom::SerialDataBits::NONE; |
| } |
| if (config.c_cflag & PARENB) { |
| info->parity_bit = (config.c_cflag & PARODD) ? mojom::SerialParityBit::ODD |
| : mojom::SerialParityBit::EVEN; |
| } else { |
| info->parity_bit = mojom::SerialParityBit::NO_PARITY; |
| } |
| info->stop_bits = (config.c_cflag & CSTOPB) ? mojom::SerialStopBits::TWO |
| : mojom::SerialStopBits::ONE; |
| info->cts_flow_control = (config.c_cflag & CRTSCTS) != 0; |
| return info; |
| } |
| |
| // break sequence: |
| // '\377' --> ErrorDetectState::MARK_377_SEEN |
| // '\0' --> ErrorDetectState::MARK_0_SEEN |
| // '\0' --> break detected |
| // |
| // parity error sequence: |
| // '\377' --> ErrorDetectState::MARK_377_SEEN |
| // '\0' --> ErrorDetectState::MARK_0_SEEN |
| // character with parity error --> parity error detected |
| // |
| // break/parity error sequences are removed from the byte stream |
| // '\377' '\377' sequence is replaced with '\377' |
| int SerialIoHandlerPosix::CheckReceiveError(base::span<uint8_t> buffer, |
| int bytes_read, |
| bool& break_detected, |
| bool& parity_error_detected) { |
| int new_bytes_read = num_chars_stashed_; |
| DCHECK_LE(new_bytes_read, 2); |
| |
| for (int i = 0; i < bytes_read; ++i) { |
| uint8_t ch = buffer[i]; |
| if (new_bytes_read == 0) { |
| chars_stashed_[0] = ch; |
| } else if (new_bytes_read == 1) { |
| chars_stashed_[1] = ch; |
| } else { |
| buffer[new_bytes_read - 2] = ch; |
| } |
| ++new_bytes_read; |
| switch (error_detect_state_) { |
| case ErrorDetectState::NO_ERROR: |
| if (ch == 0377) { |
| error_detect_state_ = ErrorDetectState::MARK_377_SEEN; |
| } |
| break; |
| case ErrorDetectState::MARK_377_SEEN: |
| DCHECK_GE(new_bytes_read, 2); |
| if (ch == 0) { |
| error_detect_state_ = ErrorDetectState::MARK_0_SEEN; |
| } else { |
| if (ch == 0377) { |
| // receive two bytes '\377' '\377', since ISTRIP is not set and |
| // PARMRK is set, a valid byte '\377' is passed to the program as |
| // two bytes, '\377' '\377'. Replace these two bytes with one byte |
| // of '\377', and set error_detect_state_ back to |
| // ErrorDetectState::NO_ERROR. |
| --new_bytes_read; |
| } |
| error_detect_state_ = ErrorDetectState::NO_ERROR; |
| } |
| break; |
| case ErrorDetectState::MARK_0_SEEN: |
| DCHECK_GE(new_bytes_read, 3); |
| if (ch == 0) { |
| break_detected = true; |
| new_bytes_read -= 3; |
| error_detect_state_ = ErrorDetectState::NO_ERROR; |
| } else { |
| if (parity_check_enabled_) { |
| parity_error_detected = true; |
| new_bytes_read -= 3; |
| error_detect_state_ = ErrorDetectState::NO_ERROR; |
| } else if (ch == 0377) { |
| error_detect_state_ = ErrorDetectState::MARK_377_SEEN; |
| } else { |
| error_detect_state_ = ErrorDetectState::NO_ERROR; |
| } |
| } |
| break; |
| } |
| } |
| // Now new_bytes_read bytes should be returned to the caller (including the |
| // previously stashed characters that were stored at chars_stashed_[]) and are |
| // now stored at: chars_stashed_[0], chars_stashed_[1], buffer[...]. |
| |
| // Stash up to 2 characters that are potentially part of a break/parity error |
| // sequence. The buffer may also not be large enough to store all the bytes. |
| // tmp[] stores the characters that need to be stashed for this read. |
| uint8_t tmp[2]; |
| num_chars_stashed_ = 0; |
| if (error_detect_state_ == ErrorDetectState::MARK_0_SEEN || |
| new_bytes_read - buffer.size() == 2) { |
| // need to stash the last two characters |
| if (new_bytes_read == 2) { |
| memcpy(tmp, chars_stashed_, new_bytes_read); |
| } else { |
| if (new_bytes_read == 3) { |
| tmp[0] = chars_stashed_[1]; |
| } else { |
| tmp[0] = buffer[new_bytes_read - 4]; |
| } |
| tmp[1] = buffer[new_bytes_read - 3]; |
| } |
| num_chars_stashed_ = 2; |
| } else if (error_detect_state_ == ErrorDetectState::MARK_377_SEEN || |
| new_bytes_read - buffer.size() == 1) { |
| // need to stash the last character |
| if (new_bytes_read <= 2) { |
| tmp[0] = chars_stashed_[new_bytes_read - 1]; |
| } else { |
| tmp[0] = buffer[new_bytes_read - 3]; |
| } |
| num_chars_stashed_ = 1; |
| } |
| |
| new_bytes_read -= num_chars_stashed_; |
| if (new_bytes_read > 2) { |
| // right shift two bytes to store bytes from chars_stashed_[] |
| memmove(&buffer[2], &buffer[0], new_bytes_read - 2); |
| } |
| memcpy(&buffer[0], chars_stashed_, std::min(new_bytes_read, 2)); |
| memcpy(chars_stashed_, tmp, num_chars_stashed_); |
| return new_bytes_read; |
| } |
| |
| } // namespace device |