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/posix/eintr_wrapper.h"
11
12#if defined(OS_LINUX)
13#include <linux/serial.h>
14#endif
15
16namespace {
17
18// Convert an integral bit rate to a nominal one. Returns |true|
19// if the conversion was successful and |false| otherwise.
20bool BitrateToSpeedConstant(int bitrate, speed_t* speed) {
21#define BITRATE_TO_SPEED_CASE(x) \
22  case x:                        \
23    *speed = B##x;               \
24    return true;
25  switch (bitrate) {
26    BITRATE_TO_SPEED_CASE(0)
27    BITRATE_TO_SPEED_CASE(50)
28    BITRATE_TO_SPEED_CASE(75)
29    BITRATE_TO_SPEED_CASE(110)
30    BITRATE_TO_SPEED_CASE(134)
31    BITRATE_TO_SPEED_CASE(150)
32    BITRATE_TO_SPEED_CASE(200)
33    BITRATE_TO_SPEED_CASE(300)
34    BITRATE_TO_SPEED_CASE(600)
35    BITRATE_TO_SPEED_CASE(1200)
36    BITRATE_TO_SPEED_CASE(1800)
37    BITRATE_TO_SPEED_CASE(2400)
38    BITRATE_TO_SPEED_CASE(4800)
39    BITRATE_TO_SPEED_CASE(9600)
40    BITRATE_TO_SPEED_CASE(19200)
41    BITRATE_TO_SPEED_CASE(38400)
42#if defined(OS_POSIX) && !defined(OS_MACOSX)
43    BITRATE_TO_SPEED_CASE(57600)
44    BITRATE_TO_SPEED_CASE(115200)
45    BITRATE_TO_SPEED_CASE(230400)
46    BITRATE_TO_SPEED_CASE(460800)
47    BITRATE_TO_SPEED_CASE(576000)
48    BITRATE_TO_SPEED_CASE(921600)
49#endif
50    default:
51      return false;
52  }
53#undef BITRATE_TO_SPEED_CASE
54}
55
56// Convert a known nominal speed into an integral bitrate. Returns |true|
57// if the conversion was successful and |false| otherwise.
58bool SpeedConstantToBitrate(speed_t speed, int* bitrate) {
59#define SPEED_TO_BITRATE_CASE(x) \
60  case B##x:                     \
61    *bitrate = x;                \
62    return true;
63  switch (speed) {
64    SPEED_TO_BITRATE_CASE(0)
65    SPEED_TO_BITRATE_CASE(50)
66    SPEED_TO_BITRATE_CASE(75)
67    SPEED_TO_BITRATE_CASE(110)
68    SPEED_TO_BITRATE_CASE(134)
69    SPEED_TO_BITRATE_CASE(150)
70    SPEED_TO_BITRATE_CASE(200)
71    SPEED_TO_BITRATE_CASE(300)
72    SPEED_TO_BITRATE_CASE(600)
73    SPEED_TO_BITRATE_CASE(1200)
74    SPEED_TO_BITRATE_CASE(1800)
75    SPEED_TO_BITRATE_CASE(2400)
76    SPEED_TO_BITRATE_CASE(4800)
77    SPEED_TO_BITRATE_CASE(9600)
78    SPEED_TO_BITRATE_CASE(19200)
79    SPEED_TO_BITRATE_CASE(38400)
80#if defined(OS_POSIX) && !defined(OS_MACOSX)
81    SPEED_TO_BITRATE_CASE(57600)
82    SPEED_TO_BITRATE_CASE(115200)
83    SPEED_TO_BITRATE_CASE(230400)
84    SPEED_TO_BITRATE_CASE(460800)
85    SPEED_TO_BITRATE_CASE(576000)
86    SPEED_TO_BITRATE_CASE(921600)
87#endif
88    default:
89      return false;
90  }
91#undef SPEED_TO_BITRATE_CASE
92}
93
94bool SetCustomBitrate(base::PlatformFile file,
95                      struct termios* config,
96                      int bitrate) {
97#if defined(OS_LINUX)
98  struct serial_struct serial;
99  if (ioctl(file, TIOCGSERIAL, &serial) < 0) {
100    return false;
101  }
102  serial.flags = (serial.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST;
103  serial.custom_divisor = serial.baud_base / bitrate;
104  if (serial.custom_divisor < 1) {
105    serial.custom_divisor = 1;
106  }
107  cfsetispeed(config, B38400);
108  cfsetospeed(config, B38400);
109  return ioctl(file, TIOCSSERIAL, &serial) >= 0;
110#elif defined(OS_MACOSX)
111  speed_t speed = static_cast<speed_t>(bitrate);
112  cfsetispeed(config, speed);
113  cfsetospeed(config, speed);
114  return true;
115#else
116  return false;
117#endif
118}
119
120}  // namespace
121
122namespace device {
123
124// static
125scoped_refptr<SerialIoHandler> SerialIoHandler::Create(
126    scoped_refptr<base::MessageLoopProxy> file_thread_message_loop) {
127  return new SerialIoHandlerPosix(file_thread_message_loop);
128}
129
130void SerialIoHandlerPosix::ReadImpl() {
131  DCHECK(CalledOnValidThread());
132  DCHECK(pending_read_buffer());
133  DCHECK(file().IsValid());
134
135  EnsureWatchingReads();
136}
137
138void SerialIoHandlerPosix::WriteImpl() {
139  DCHECK(CalledOnValidThread());
140  DCHECK(pending_write_buffer());
141  DCHECK(file().IsValid());
142
143  EnsureWatchingWrites();
144}
145
146void SerialIoHandlerPosix::CancelReadImpl() {
147  DCHECK(CalledOnValidThread());
148  is_watching_reads_ = false;
149  file_read_watcher_.StopWatchingFileDescriptor();
150  QueueReadCompleted(0, read_cancel_reason());
151}
152
153void SerialIoHandlerPosix::CancelWriteImpl() {
154  DCHECK(CalledOnValidThread());
155  is_watching_writes_ = false;
156  file_write_watcher_.StopWatchingFileDescriptor();
157  QueueWriteCompleted(0, write_cancel_reason());
158}
159
160SerialIoHandlerPosix::SerialIoHandlerPosix(
161    scoped_refptr<base::MessageLoopProxy> file_thread_message_loop)
162    : SerialIoHandler(file_thread_message_loop),
163      is_watching_reads_(false),
164      is_watching_writes_(false) {
165}
166
167SerialIoHandlerPosix::~SerialIoHandlerPosix() {
168}
169
170void SerialIoHandlerPosix::OnFileCanReadWithoutBlocking(int fd) {
171  DCHECK(CalledOnValidThread());
172  DCHECK_EQ(fd, file().GetPlatformFile());
173
174  if (pending_read_buffer()) {
175    int bytes_read = HANDLE_EINTR(read(file().GetPlatformFile(),
176                                       pending_read_buffer(),
177                                       pending_read_buffer_len()));
178    if (bytes_read < 0) {
179      if (errno == ENXIO) {
180        ReadCompleted(0, serial::RECEIVE_ERROR_DEVICE_LOST);
181      } else {
182        ReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
183      }
184    } else if (bytes_read == 0) {
185      ReadCompleted(0, serial::RECEIVE_ERROR_DEVICE_LOST);
186    } else {
187      ReadCompleted(bytes_read, serial::RECEIVE_ERROR_NONE);
188    }
189  } else {
190    // Stop watching the fd if we get notifications with no pending
191    // reads or writes to avoid starving the message loop.
192    is_watching_reads_ = false;
193    file_read_watcher_.StopWatchingFileDescriptor();
194  }
195}
196
197void SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking(int fd) {
198  DCHECK(CalledOnValidThread());
199  DCHECK_EQ(fd, file().GetPlatformFile());
200
201  if (pending_write_buffer()) {
202    int bytes_written = HANDLE_EINTR(write(file().GetPlatformFile(),
203                                           pending_write_buffer(),
204                                           pending_write_buffer_len()));
205    if (bytes_written < 0) {
206      WriteCompleted(0, serial::SEND_ERROR_SYSTEM_ERROR);
207    } else {
208      WriteCompleted(bytes_written, serial::SEND_ERROR_NONE);
209    }
210  } else {
211    // Stop watching the fd if we get notifications with no pending
212    // writes to avoid starving the message loop.
213    is_watching_writes_ = false;
214    file_write_watcher_.StopWatchingFileDescriptor();
215  }
216}
217
218void SerialIoHandlerPosix::EnsureWatchingReads() {
219  DCHECK(CalledOnValidThread());
220  DCHECK(file().IsValid());
221  if (!is_watching_reads_) {
222    is_watching_reads_ = base::MessageLoopForIO::current()->WatchFileDescriptor(
223        file().GetPlatformFile(),
224        true,
225        base::MessageLoopForIO::WATCH_READ,
226        &file_read_watcher_,
227        this);
228  }
229}
230
231void SerialIoHandlerPosix::EnsureWatchingWrites() {
232  DCHECK(CalledOnValidThread());
233  DCHECK(file().IsValid());
234  if (!is_watching_writes_) {
235    is_watching_writes_ =
236        base::MessageLoopForIO::current()->WatchFileDescriptor(
237            file().GetPlatformFile(),
238            true,
239            base::MessageLoopForIO::WATCH_WRITE,
240            &file_write_watcher_,
241            this);
242  }
243}
244
245bool SerialIoHandlerPosix::ConfigurePort(
246    const serial::ConnectionOptions& options) {
247  struct termios config;
248  tcgetattr(file().GetPlatformFile(), &config);
249  if (options.bitrate) {
250    speed_t bitrate_opt = B0;
251    if (BitrateToSpeedConstant(options.bitrate, &bitrate_opt)) {
252      cfsetispeed(&config, bitrate_opt);
253      cfsetospeed(&config, bitrate_opt);
254    } else {
255      // Attempt to set a custom speed.
256      if (!SetCustomBitrate(
257              file().GetPlatformFile(), &config, options.bitrate)) {
258        return false;
259      }
260    }
261  }
262  if (options.data_bits != serial::DATA_BITS_NONE) {
263    config.c_cflag &= ~CSIZE;
264    switch (options.data_bits) {
265      case serial::DATA_BITS_SEVEN:
266        config.c_cflag |= CS7;
267        break;
268      case serial::DATA_BITS_EIGHT:
269      default:
270        config.c_cflag |= CS8;
271        break;
272    }
273  }
274  if (options.parity_bit != serial::PARITY_BIT_NONE) {
275    switch (options.parity_bit) {
276      case serial::PARITY_BIT_EVEN:
277        config.c_cflag |= PARENB;
278        config.c_cflag &= ~PARODD;
279        break;
280      case serial::PARITY_BIT_ODD:
281        config.c_cflag |= (PARODD | PARENB);
282        break;
283      case serial::PARITY_BIT_NO:
284      default:
285        config.c_cflag &= ~(PARODD | PARENB);
286        break;
287    }
288  }
289  if (options.stop_bits != serial::STOP_BITS_NONE) {
290    switch (options.stop_bits) {
291      case serial::STOP_BITS_TWO:
292        config.c_cflag |= CSTOPB;
293        break;
294      case serial::STOP_BITS_ONE:
295      default:
296        config.c_cflag &= ~CSTOPB;
297        break;
298    }
299  }
300  if (options.has_cts_flow_control) {
301    if (options.cts_flow_control) {
302      config.c_cflag |= CRTSCTS;
303    } else {
304      config.c_cflag &= ~CRTSCTS;
305    }
306  }
307  return tcsetattr(file().GetPlatformFile(), TCSANOW, &config) == 0;
308}
309
310bool SerialIoHandlerPosix::PostOpen() {
311  struct termios config;
312  tcgetattr(file().GetPlatformFile(), &config);
313
314  // Set flags for 'raw' operation
315  config.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG);
316  config.c_iflag &=
317      ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
318  config.c_oflag &= ~OPOST;
319
320  // CLOCAL causes the system to disregard the DCD signal state.
321  // CREAD enables reading from the port.
322  config.c_cflag |= (CLOCAL | CREAD);
323
324  return tcsetattr(file().GetPlatformFile(), TCSANOW, &config) == 0;
325}
326
327bool SerialIoHandlerPosix::Flush() const {
328  return tcflush(file().GetPlatformFile(), TCIOFLUSH) == 0;
329}
330
331serial::DeviceControlSignalsPtr SerialIoHandlerPosix::GetControlSignals()
332    const {
333  int status;
334  if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
335    return serial::DeviceControlSignalsPtr();
336  }
337
338  serial::DeviceControlSignalsPtr signals(serial::DeviceControlSignals::New());
339  signals->dcd = (status & TIOCM_CAR) != 0;
340  signals->cts = (status & TIOCM_CTS) != 0;
341  signals->dsr = (status & TIOCM_DSR) != 0;
342  signals->ri = (status & TIOCM_RI) != 0;
343  return signals.Pass();
344}
345
346bool SerialIoHandlerPosix::SetControlSignals(
347    const serial::HostControlSignals& signals) {
348  int status;
349
350  if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
351    return false;
352  }
353
354  if (signals.has_dtr) {
355    if (signals.dtr) {
356      status |= TIOCM_DTR;
357    } else {
358      status &= ~TIOCM_DTR;
359    }
360  }
361
362  if (signals.has_rts) {
363    if (signals.rts) {
364      status |= TIOCM_RTS;
365    } else {
366      status &= ~TIOCM_RTS;
367    }
368  }
369
370  return ioctl(file().GetPlatformFile(), TIOCMSET, &status) == 0;
371}
372
373serial::ConnectionInfoPtr SerialIoHandlerPosix::GetPortInfo() const {
374  struct termios config;
375  if (tcgetattr(file().GetPlatformFile(), &config) == -1) {
376    return serial::ConnectionInfoPtr();
377  }
378  serial::ConnectionInfoPtr info(serial::ConnectionInfo::New());
379  speed_t ispeed = cfgetispeed(&config);
380  speed_t ospeed = cfgetospeed(&config);
381  if (ispeed == ospeed) {
382    int bitrate = 0;
383    if (SpeedConstantToBitrate(ispeed, &bitrate)) {
384      info->bitrate = bitrate;
385    } else if (ispeed > 0) {
386      info->bitrate = static_cast<int>(ispeed);
387    }
388  }
389  if ((config.c_cflag & CSIZE) == CS7) {
390    info->data_bits = serial::DATA_BITS_SEVEN;
391  } else if ((config.c_cflag & CSIZE) == CS8) {
392    info->data_bits = serial::DATA_BITS_EIGHT;
393  } else {
394    info->data_bits = serial::DATA_BITS_NONE;
395  }
396  if (config.c_cflag & PARENB) {
397    info->parity_bit = (config.c_cflag & PARODD) ? serial::PARITY_BIT_ODD
398                                                 : serial::PARITY_BIT_EVEN;
399  } else {
400    info->parity_bit = serial::PARITY_BIT_NO;
401  }
402  info->stop_bits =
403      (config.c_cflag & CSTOPB) ? serial::STOP_BITS_TWO : serial::STOP_BITS_ONE;
404  info->cts_flow_control = (config.c_cflag & CRTSCTS) != 0;
405  return info.Pass();
406}
407
408std::string SerialIoHandler::MaybeFixUpPortName(const std::string& port_name) {
409  return port_name;
410}
411
412}  // namespace device
413