Upload upstream chromium 67.0.3396
[platform/framework/web/chromium-efl.git] / device / serial / serial_io_handler_posix.cc
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.
4
5 #include "device/serial/serial_io_handler_posix.h"
6
7 #include <sys/ioctl.h>
8 #include <termios.h>
9
10 #include "base/files/file_util.h"
11 #include "base/posix/eintr_wrapper.h"
12 #include "build/build_config.h"
13
14 #if defined(OS_LINUX)
15 #include <asm-generic/ioctls.h>
16 #include <linux/serial.h>
17
18 // The definition of struct termios2 is copied from asm-generic/termbits.h
19 // because including that header directly conflicts with termios.h.
20 extern "C" {
21 struct termios2 {
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
30 };
31 }
32
33 #endif  // defined(OS_LINUX)
34
35 #if defined(OS_MACOSX)
36 #include <IOKit/serial/ioss.h>
37 #endif
38
39 namespace {
40
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) \
45   case x:                        \
46     *speed = B##x;               \
47     return true;
48   switch (bitrate) {
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)
72 #endif
73     default:
74       return false;
75   }
76 #undef BITRATE_TO_SPEED_CASE
77 }
78
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) \
84   case B##x:                     \
85     *bitrate = x;                \
86     return true;
87   switch (speed) {
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)
104     default:
105       return false;
106   }
107 #undef SPEED_TO_BITRATE_CASE
108 }
109 #endif
110
111 }  // namespace
112
113 namespace device {
114
115 // static
116 scoped_refptr<SerialIoHandler> SerialIoHandler::Create(
117     scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) {
118   return new SerialIoHandlerPosix(ui_thread_task_runner);
119 }
120
121 void SerialIoHandlerPosix::ReadImpl() {
122   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
123   DCHECK(pending_read_buffer());
124   DCHECK(file().IsValid());
125
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.
130   AttemptRead(true);
131 }
132
133 void SerialIoHandlerPosix::WriteImpl() {
134   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
135   DCHECK(pending_write_buffer());
136   DCHECK(file().IsValid());
137
138   EnsureWatchingWrites();
139 }
140
141 void SerialIoHandlerPosix::CancelReadImpl() {
142   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
143   file_read_watcher_.reset();
144   QueueReadCompleted(0, read_cancel_reason());
145 }
146
147 void SerialIoHandlerPosix::CancelWriteImpl() {
148   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
149   file_write_watcher_.reset();
150   QueueWriteCompleted(0, write_cancel_reason());
151 }
152
153 bool SerialIoHandlerPosix::ConfigurePortImpl() {
154 #if defined(OS_LINUX)
155   struct termios2 config;
156   if (ioctl(file().GetPlatformFile(), TCGETS2, &config) < 0) {
157 #else
158   struct termios config;
159   if (tcgetattr(file().GetPlatformFile(), &config) != 0) {
160 #endif
161     VPLOG(1) << "Failed to get port configuration";
162     return false;
163   }
164
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;
170
171   // CLOCAL causes the system to disregard the DCD signal state.
172   // CREAD enables reading from the port.
173   config.c_cflag |= (CLOCAL | CREAD);
174
175   DCHECK(options().bitrate);
176   speed_t bitrate_opt = B0;
177 #if defined(OS_MACOSX)
178   bool need_iossiospeed = false;
179 #endif
180   if (BitrateToSpeedConstant(options().bitrate, &bitrate_opt)) {
181 #if defined(OS_LINUX)
182     config.c_cflag &= ~CBAUD;
183     config.c_cflag |= bitrate_opt;
184 #else
185     cfsetispeed(&config, bitrate_opt);
186     cfsetospeed(&config, bitrate_opt);
187 #endif
188   } else {
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;
199 #else
200     return false;
201 #endif
202   }
203
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;
209       break;
210     case mojom::SerialDataBits::EIGHT:
211     default:
212       config.c_cflag |= CS8;
213       break;
214   }
215
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;
221       break;
222     case mojom::SerialParityBit::ODD:
223       config.c_cflag |= (PARODD | PARENB);
224       break;
225     case mojom::SerialParityBit::NO_PARITY:
226     default:
227       config.c_cflag &= ~(PARODD | PARENB);
228       break;
229   }
230
231   error_detect_state_ = ErrorDetectState::NO_ERROR;
232   num_chars_stashed_ = 0;
233
234   if (config.c_cflag & PARENB) {
235     config.c_iflag &= ~IGNPAR;
236     config.c_iflag |= INPCK;
237     parity_check_enabled_ = true;
238   } else {
239     config.c_iflag |= IGNPAR;
240     config.c_iflag &= ~INPCK;
241     parity_check_enabled_ = false;
242   }
243
244   DCHECK(options().stop_bits != mojom::SerialStopBits::NONE);
245   switch (options().stop_bits) {
246     case mojom::SerialStopBits::TWO:
247       config.c_cflag |= CSTOPB;
248       break;
249     case mojom::SerialStopBits::ONE:
250     default:
251       config.c_cflag &= ~CSTOPB;
252       break;
253   }
254
255   DCHECK(options().has_cts_flow_control);
256   if (options().cts_flow_control) {
257     config.c_cflag |= CRTSCTS;
258   } else {
259     config.c_cflag &= ~CRTSCTS;
260   }
261
262 #if defined(OS_LINUX)
263   if (ioctl(file().GetPlatformFile(), TCSETS2, &config) < 0) {
264 #else
265   if (tcsetattr(file().GetPlatformFile(), TCSANOW, &config) != 0) {
266 #endif
267     VPLOG(1) << "Failed to set port attributes";
268     return false;
269   }
270
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";
276       return false;
277     }
278   }
279 #endif
280
281   return true;
282 }
283
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());
288 #else
289   return true;
290 #endif
291 }
292
293 SerialIoHandlerPosix::SerialIoHandlerPosix(
294     scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner)
295     : SerialIoHandler(ui_thread_task_runner) {}
296
297 SerialIoHandlerPosix::~SerialIoHandlerPosix() = default;
298
299 void SerialIoHandlerPosix::AttemptRead(bool within_read) {
300   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
301
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);
313       } else {
314         RunReadCompleted(within_read, 0,
315                          mojom::SerialReceiveError::SYSTEM_ERROR);
316       }
317     } else if (bytes_read == 0) {
318       RunReadCompleted(within_read, 0, mojom::SerialReceiveError::DEVICE_LOST);
319     } else {
320       bool break_detected = false;
321       bool parity_error_detected = false;
322       int new_bytes_read =
323           CheckReceiveError(pending_read_buffer(), pending_read_buffer_len(),
324                             bytes_read, break_detected, parity_error_detected);
325
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);
332       } else {
333         RunReadCompleted(within_read, new_bytes_read,
334                          mojom::SerialReceiveError::NONE);
335       }
336     }
337   } else {
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();
341   }
342 }
343
344 void SerialIoHandlerPosix::RunReadCompleted(bool within_read,
345                                             int bytes_read,
346                                             mojom::SerialReceiveError error) {
347   if (within_read) {
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();
351
352     QueueReadCompleted(bytes_read, error);
353   } else {
354     ReadCompleted(bytes_read, error);
355   }
356 }
357
358 void SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking() {
359   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
360
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);
367     } else {
368       WriteCompleted(bytes_written, mojom::SerialSendError::NONE);
369     }
370   } else {
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();
374   }
375 }
376
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));
384   }
385 }
386
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)));
395   }
396 }
397
398 bool SerialIoHandlerPosix::Flush() const {
399   if (tcflush(file().GetPlatformFile(), TCIOFLUSH) != 0) {
400     VPLOG(1) << "Failed to flush port";
401     return false;
402   }
403   return true;
404 }
405
406 mojom::SerialDeviceControlSignalsPtr SerialIoHandlerPosix::GetControlSignals()
407     const {
408   int status;
409   if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
410     VPLOG(1) << "Failed to get port control signals";
411     return mojom::SerialDeviceControlSignalsPtr();
412   }
413
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;
419   return signals;
420 }
421
422 bool SerialIoHandlerPosix::SetControlSignals(
423     const mojom::SerialHostControlSignals& signals) {
424   int status;
425
426   if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
427     VPLOG(1) << "Failed to get port control signals";
428     return false;
429   }
430
431   if (signals.has_dtr) {
432     if (signals.dtr) {
433       status |= TIOCM_DTR;
434     } else {
435       status &= ~TIOCM_DTR;
436     }
437   }
438
439   if (signals.has_rts) {
440     if (signals.rts) {
441       status |= TIOCM_RTS;
442     } else {
443       status &= ~TIOCM_RTS;
444     }
445   }
446
447   if (ioctl(file().GetPlatformFile(), TIOCMSET, &status) != 0) {
448     VPLOG(1) << "Failed to set port control signals";
449     return false;
450   }
451   return true;
452 }
453
454 mojom::SerialConnectionInfoPtr SerialIoHandlerPosix::GetPortInfo() const {
455 #if defined(OS_LINUX)
456   struct termios2 config;
457   if (ioctl(file().GetPlatformFile(), TCGETS2, &config) < 0) {
458 #else
459   struct termios config;
460   if (tcgetattr(file().GetPlatformFile(), &config) == -1) {
461 #endif
462     VPLOG(1) << "Failed to get port info";
463     return mojom::SerialConnectionInfoPtr();
464   }
465
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;
470 #else
471   speed_t ispeed = cfgetispeed(&config);
472   speed_t ospeed = cfgetospeed(&config);
473   if (ispeed == ospeed) {
474     int bitrate = 0;
475     if (SpeedConstantToBitrate(ispeed, &bitrate)) {
476       info->bitrate = bitrate;
477     } else if (ispeed > 0) {
478       info->bitrate = static_cast<int>(ispeed);
479     }
480   }
481 #endif
482
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;
487   } else {
488     info->data_bits = mojom::SerialDataBits::NONE;
489   }
490   if (config.c_cflag & PARENB) {
491     info->parity_bit = (config.c_cflag & PARODD) ? mojom::SerialParityBit::ODD
492                                                  : mojom::SerialParityBit::EVEN;
493   } else {
494     info->parity_bit = mojom::SerialParityBit::NO_PARITY;
495   }
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;
499   return info;
500 }
501
502 bool SerialIoHandlerPosix::SetBreak() {
503   if (ioctl(file().GetPlatformFile(), TIOCSBRK, 0) != 0) {
504     VPLOG(1) << "Failed to set break";
505     return false;
506   }
507
508   return true;
509 }
510
511 bool SerialIoHandlerPosix::ClearBreak() {
512   if (ioctl(file().GetPlatformFile(), TIOCCBRK, 0) != 0) {
513     VPLOG(1) << "Failed to clear break";
514     return false;
515   }
516   return true;
517 }
518
519 // break sequence:
520 // '\377'       -->        ErrorDetectState::MARK_377_SEEN
521 // '\0'         -->          ErrorDetectState::MARK_0_SEEN
522 // '\0'         -->                         break detected
523 //
524 // parity error sequence:
525 // '\377'       -->        ErrorDetectState::MARK_377_SEEN
526 // '\0'         -->          ErrorDetectState::MARK_0_SEEN
527 // character with parity error  -->  parity error detected
528 //
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,
532                                             int buffer_len,
533                                             int bytes_read,
534                                             bool& break_detected,
535                                             bool& parity_error_detected) {
536   int new_bytes_read = num_chars_stashed_;
537   DCHECK_LE(new_bytes_read, 2);
538
539   for (int i = 0; i < bytes_read; ++i) {
540     char ch = buffer[i];
541     if (new_bytes_read == 0) {
542       chars_stashed_[0] = ch;
543     } else if (new_bytes_read == 1) {
544       chars_stashed_[1] = ch;
545     } else {
546       buffer[new_bytes_read - 2] = ch;
547     }
548     ++new_bytes_read;
549     switch (error_detect_state_) {
550       case ErrorDetectState::NO_ERROR:
551         if (ch == '\377') {
552           error_detect_state_ = ErrorDetectState::MARK_377_SEEN;
553         }
554         break;
555       case ErrorDetectState::MARK_377_SEEN:
556         DCHECK_GE(new_bytes_read, 2);
557         if (ch == '\0') {
558           error_detect_state_ = ErrorDetectState::MARK_0_SEEN;
559         } else {
560           if (ch == '\377') {
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.
566             --new_bytes_read;
567           }
568           error_detect_state_ = ErrorDetectState::NO_ERROR;
569         }
570         break;
571       case ErrorDetectState::MARK_0_SEEN:
572         DCHECK_GE(new_bytes_read, 3);
573         if (ch == '\0') {
574           break_detected = true;
575           new_bytes_read -= 3;
576           error_detect_state_ = ErrorDetectState::NO_ERROR;
577         } else {
578           if (parity_check_enabled_) {
579             parity_error_detected = true;
580             new_bytes_read -= 3;
581             error_detect_state_ = ErrorDetectState::NO_ERROR;
582           } else if (ch == '\377') {
583             error_detect_state_ = ErrorDetectState::MARK_377_SEEN;
584           } else {
585             error_detect_state_ = ErrorDetectState::NO_ERROR;
586           }
587         }
588         break;
589     }
590   }
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[...].
594
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.
598   char tmp[2];
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);
605     } else {
606       if (new_bytes_read == 3) {
607         tmp[0] = chars_stashed_[1];
608       } else {
609         tmp[0] = buffer[new_bytes_read - 4];
610       }
611       tmp[1] = buffer[new_bytes_read - 3];
612     }
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];
619     } else {
620       tmp[0] = buffer[new_bytes_read - 3];
621     }
622     num_chars_stashed_ = 1;
623   }
624
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);
629   }
630   memcpy(buffer, chars_stashed_, std::min(new_bytes_read, 2));
631   memcpy(chars_stashed_, tmp, num_chars_stashed_);
632   return new_bytes_read;
633 }
634
635 std::string SerialIoHandler::MaybeFixUpPortName(const std::string& port_name) {
636   return port_name;
637 }
638
639 }  // namespace device