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/posix/eintr_wrapper.h"
13 #include <linux/serial.h>
14 #if defined(OS_CHROMEOS)
15 #include "base/bind.h"
16 #include "base/sys_info.h"
17 #include "chromeos/dbus/dbus_thread_manager.h"
18 #include "chromeos/dbus/permission_broker_client.h"
19 #endif // defined(OS_CHROMEOS)
20 #endif // defined(OS_LINUX)
24 // Convert an integral bit rate to a nominal one. Returns |true|
25 // if the conversion was successful and |false| otherwise.
26 bool BitrateToSpeedConstant(int bitrate
, speed_t
* speed
) {
27 #define BITRATE_TO_SPEED_CASE(x) \
32 BITRATE_TO_SPEED_CASE(0)
33 BITRATE_TO_SPEED_CASE(50)
34 BITRATE_TO_SPEED_CASE(75)
35 BITRATE_TO_SPEED_CASE(110)
36 BITRATE_TO_SPEED_CASE(134)
37 BITRATE_TO_SPEED_CASE(150)
38 BITRATE_TO_SPEED_CASE(200)
39 BITRATE_TO_SPEED_CASE(300)
40 BITRATE_TO_SPEED_CASE(600)
41 BITRATE_TO_SPEED_CASE(1200)
42 BITRATE_TO_SPEED_CASE(1800)
43 BITRATE_TO_SPEED_CASE(2400)
44 BITRATE_TO_SPEED_CASE(4800)
45 BITRATE_TO_SPEED_CASE(9600)
46 BITRATE_TO_SPEED_CASE(19200)
47 BITRATE_TO_SPEED_CASE(38400)
48 #if defined(OS_POSIX) && !defined(OS_MACOSX)
49 BITRATE_TO_SPEED_CASE(57600)
50 BITRATE_TO_SPEED_CASE(115200)
51 BITRATE_TO_SPEED_CASE(230400)
52 BITRATE_TO_SPEED_CASE(460800)
53 BITRATE_TO_SPEED_CASE(576000)
54 BITRATE_TO_SPEED_CASE(921600)
59 #undef BITRATE_TO_SPEED_CASE
62 // Convert a known nominal speed into an integral bitrate. Returns |true|
63 // if the conversion was successful and |false| otherwise.
64 bool SpeedConstantToBitrate(speed_t speed
, int* bitrate
) {
65 #define SPEED_TO_BITRATE_CASE(x) \
70 SPEED_TO_BITRATE_CASE(0)
71 SPEED_TO_BITRATE_CASE(50)
72 SPEED_TO_BITRATE_CASE(75)
73 SPEED_TO_BITRATE_CASE(110)
74 SPEED_TO_BITRATE_CASE(134)
75 SPEED_TO_BITRATE_CASE(150)
76 SPEED_TO_BITRATE_CASE(200)
77 SPEED_TO_BITRATE_CASE(300)
78 SPEED_TO_BITRATE_CASE(600)
79 SPEED_TO_BITRATE_CASE(1200)
80 SPEED_TO_BITRATE_CASE(1800)
81 SPEED_TO_BITRATE_CASE(2400)
82 SPEED_TO_BITRATE_CASE(4800)
83 SPEED_TO_BITRATE_CASE(9600)
84 SPEED_TO_BITRATE_CASE(19200)
85 SPEED_TO_BITRATE_CASE(38400)
86 #if defined(OS_POSIX) && !defined(OS_MACOSX)
87 SPEED_TO_BITRATE_CASE(57600)
88 SPEED_TO_BITRATE_CASE(115200)
89 SPEED_TO_BITRATE_CASE(230400)
90 SPEED_TO_BITRATE_CASE(460800)
91 SPEED_TO_BITRATE_CASE(576000)
92 SPEED_TO_BITRATE_CASE(921600)
97 #undef SPEED_TO_BITRATE_CASE
100 bool SetCustomBitrate(base::PlatformFile file
,
101 struct termios
* config
,
103 #if defined(OS_LINUX)
104 struct serial_struct serial
;
105 if (ioctl(file
, TIOCGSERIAL
, &serial
) < 0) {
108 serial
.flags
= (serial
.flags
& ~ASYNC_SPD_MASK
) | ASYNC_SPD_CUST
;
109 serial
.custom_divisor
= serial
.baud_base
/ bitrate
;
110 if (serial
.custom_divisor
< 1) {
111 serial
.custom_divisor
= 1;
113 cfsetispeed(config
, B38400
);
114 cfsetospeed(config
, B38400
);
115 return ioctl(file
, TIOCSSERIAL
, &serial
) >= 0;
116 #elif defined(OS_MACOSX)
117 speed_t speed
= static_cast<speed_t
>(bitrate
);
118 cfsetispeed(config
, speed
);
119 cfsetospeed(config
, speed
);
131 scoped_refptr
<SerialIoHandler
> SerialIoHandler::Create(
132 scoped_refptr
<base::SingleThreadTaskRunner
> file_thread_task_runner
,
133 scoped_refptr
<base::SingleThreadTaskRunner
> ui_thread_task_runner
) {
134 return new SerialIoHandlerPosix(file_thread_task_runner
,
135 ui_thread_task_runner
);
138 void SerialIoHandlerPosix::RequestAccess(
139 const std::string
& port
,
140 scoped_refptr
<base::SingleThreadTaskRunner
> file_task_runner
,
141 scoped_refptr
<base::SingleThreadTaskRunner
> ui_task_runner
) {
142 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
143 if (base::SysInfo::IsRunningOnChromeOS()) {
144 chromeos::PermissionBrokerClient
* client
=
145 chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient();
147 DVLOG(1) << "Could not get permission_broker client.";
148 OnRequestAccessComplete(port
, false /* failure */);
151 // PermissionBrokerClient should be called on the UI thread.
152 ui_task_runner
->PostTask(
155 &chromeos::PermissionBrokerClient::RequestPathAccess
,
156 base::Unretained(client
), port
, -1,
157 base::Bind(&SerialIoHandler::OnRequestAccessComplete
, this, port
)));
159 OnRequestAccessComplete(port
, true /* success */);
163 OnRequestAccessComplete(port
, true /* success */);
164 #endif // defined(OS_LINUX) && defined(OS_CHROMEOS)
167 void SerialIoHandlerPosix::ReadImpl() {
168 DCHECK(CalledOnValidThread());
169 DCHECK(pending_read_buffer());
170 DCHECK(file().IsValid());
172 EnsureWatchingReads();
175 void SerialIoHandlerPosix::WriteImpl() {
176 DCHECK(CalledOnValidThread());
177 DCHECK(pending_write_buffer());
178 DCHECK(file().IsValid());
180 EnsureWatchingWrites();
183 void SerialIoHandlerPosix::CancelReadImpl() {
184 DCHECK(CalledOnValidThread());
185 is_watching_reads_
= false;
186 file_read_watcher_
.StopWatchingFileDescriptor();
187 QueueReadCompleted(0, read_cancel_reason());
190 void SerialIoHandlerPosix::CancelWriteImpl() {
191 DCHECK(CalledOnValidThread());
192 is_watching_writes_
= false;
193 file_write_watcher_
.StopWatchingFileDescriptor();
194 QueueWriteCompleted(0, write_cancel_reason());
197 bool SerialIoHandlerPosix::ConfigurePortImpl() {
198 struct termios config
;
199 if (tcgetattr(file().GetPlatformFile(), &config
) != 0) {
200 VPLOG(1) << "Failed to get port attributes";
204 // Set flags for 'raw' operation
205 config
.c_lflag
&= ~(ICANON
| ECHO
| ECHOE
| ECHONL
| ISIG
);
207 ~(IGNBRK
| BRKINT
| PARMRK
| ISTRIP
| INLCR
| IGNCR
| ICRNL
| IXON
);
208 config
.c_oflag
&= ~OPOST
;
210 // CLOCAL causes the system to disregard the DCD signal state.
211 // CREAD enables reading from the port.
212 config
.c_cflag
|= (CLOCAL
| CREAD
);
214 DCHECK(options().bitrate
);
215 speed_t bitrate_opt
= B0
;
216 if (BitrateToSpeedConstant(options().bitrate
, &bitrate_opt
)) {
217 cfsetispeed(&config
, bitrate_opt
);
218 cfsetospeed(&config
, bitrate_opt
);
220 // Attempt to set a custom speed.
221 if (!SetCustomBitrate(file().GetPlatformFile(), &config
,
222 options().bitrate
)) {
227 DCHECK(options().data_bits
!= serial::DATA_BITS_NONE
);
228 config
.c_cflag
&= ~CSIZE
;
229 switch (options().data_bits
) {
230 case serial::DATA_BITS_SEVEN
:
231 config
.c_cflag
|= CS7
;
233 case serial::DATA_BITS_EIGHT
:
235 config
.c_cflag
|= CS8
;
239 DCHECK(options().parity_bit
!= serial::PARITY_BIT_NONE
);
240 switch (options().parity_bit
) {
241 case serial::PARITY_BIT_EVEN
:
242 config
.c_cflag
|= PARENB
;
243 config
.c_cflag
&= ~PARODD
;
245 case serial::PARITY_BIT_ODD
:
246 config
.c_cflag
|= (PARODD
| PARENB
);
248 case serial::PARITY_BIT_NO
:
250 config
.c_cflag
&= ~(PARODD
| PARENB
);
254 DCHECK(options().stop_bits
!= serial::STOP_BITS_NONE
);
255 switch (options().stop_bits
) {
256 case serial::STOP_BITS_TWO
:
257 config
.c_cflag
|= CSTOPB
;
259 case serial::STOP_BITS_ONE
:
261 config
.c_cflag
&= ~CSTOPB
;
265 DCHECK(options().has_cts_flow_control
);
266 if (options().cts_flow_control
) {
267 config
.c_cflag
|= CRTSCTS
;
269 config
.c_cflag
&= ~CRTSCTS
;
272 if (tcsetattr(file().GetPlatformFile(), TCSANOW
, &config
) != 0) {
273 VPLOG(1) << "Failed to set port attributes";
279 SerialIoHandlerPosix::SerialIoHandlerPosix(
280 scoped_refptr
<base::SingleThreadTaskRunner
> file_thread_task_runner
,
281 scoped_refptr
<base::SingleThreadTaskRunner
> ui_thread_task_runner
)
282 : SerialIoHandler(file_thread_task_runner
, ui_thread_task_runner
),
283 is_watching_reads_(false),
284 is_watching_writes_(false) {
287 SerialIoHandlerPosix::~SerialIoHandlerPosix() {
290 void SerialIoHandlerPosix::OnFileCanReadWithoutBlocking(int fd
) {
291 DCHECK(CalledOnValidThread());
292 DCHECK_EQ(fd
, file().GetPlatformFile());
294 if (pending_read_buffer()) {
295 int bytes_read
= HANDLE_EINTR(read(file().GetPlatformFile(),
296 pending_read_buffer(),
297 pending_read_buffer_len()));
298 if (bytes_read
< 0) {
299 if (errno
== ENXIO
) {
300 ReadCompleted(0, serial::RECEIVE_ERROR_DEVICE_LOST
);
302 ReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR
);
304 } else if (bytes_read
== 0) {
305 ReadCompleted(0, serial::RECEIVE_ERROR_DEVICE_LOST
);
307 ReadCompleted(bytes_read
, serial::RECEIVE_ERROR_NONE
);
310 // Stop watching the fd if we get notifications with no pending
311 // reads or writes to avoid starving the message loop.
312 is_watching_reads_
= false;
313 file_read_watcher_
.StopWatchingFileDescriptor();
317 void SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking(int fd
) {
318 DCHECK(CalledOnValidThread());
319 DCHECK_EQ(fd
, file().GetPlatformFile());
321 if (pending_write_buffer()) {
322 int bytes_written
= HANDLE_EINTR(write(file().GetPlatformFile(),
323 pending_write_buffer(),
324 pending_write_buffer_len()));
325 if (bytes_written
< 0) {
326 WriteCompleted(0, serial::SEND_ERROR_SYSTEM_ERROR
);
328 WriteCompleted(bytes_written
, serial::SEND_ERROR_NONE
);
331 // Stop watching the fd if we get notifications with no pending
332 // writes to avoid starving the message loop.
333 is_watching_writes_
= false;
334 file_write_watcher_
.StopWatchingFileDescriptor();
338 void SerialIoHandlerPosix::EnsureWatchingReads() {
339 DCHECK(CalledOnValidThread());
340 DCHECK(file().IsValid());
341 if (!is_watching_reads_
) {
342 is_watching_reads_
= base::MessageLoopForIO::current()->WatchFileDescriptor(
343 file().GetPlatformFile(),
345 base::MessageLoopForIO::WATCH_READ
,
351 void SerialIoHandlerPosix::EnsureWatchingWrites() {
352 DCHECK(CalledOnValidThread());
353 DCHECK(file().IsValid());
354 if (!is_watching_writes_
) {
355 is_watching_writes_
=
356 base::MessageLoopForIO::current()->WatchFileDescriptor(
357 file().GetPlatformFile(),
359 base::MessageLoopForIO::WATCH_WRITE
,
360 &file_write_watcher_
,
365 bool SerialIoHandlerPosix::Flush() const {
366 if (tcflush(file().GetPlatformFile(), TCIOFLUSH
) != 0) {
367 VPLOG(1) << "Failed to flush port";
373 serial::DeviceControlSignalsPtr
SerialIoHandlerPosix::GetControlSignals()
376 if (ioctl(file().GetPlatformFile(), TIOCMGET
, &status
) == -1) {
377 VPLOG(1) << "Failed to get port control signals";
378 return serial::DeviceControlSignalsPtr();
381 serial::DeviceControlSignalsPtr
signals(serial::DeviceControlSignals::New());
382 signals
->dcd
= (status
& TIOCM_CAR
) != 0;
383 signals
->cts
= (status
& TIOCM_CTS
) != 0;
384 signals
->dsr
= (status
& TIOCM_DSR
) != 0;
385 signals
->ri
= (status
& TIOCM_RI
) != 0;
386 return signals
.Pass();
389 bool SerialIoHandlerPosix::SetControlSignals(
390 const serial::HostControlSignals
& signals
) {
393 if (ioctl(file().GetPlatformFile(), TIOCMGET
, &status
) == -1) {
394 VPLOG(1) << "Failed to get port control signals";
398 if (signals
.has_dtr
) {
402 status
&= ~TIOCM_DTR
;
406 if (signals
.has_rts
) {
410 status
&= ~TIOCM_RTS
;
414 if (ioctl(file().GetPlatformFile(), TIOCMSET
, &status
) != 0) {
415 VPLOG(1) << "Failed to set port control signals";
421 serial::ConnectionInfoPtr
SerialIoHandlerPosix::GetPortInfo() const {
422 struct termios config
;
423 if (tcgetattr(file().GetPlatformFile(), &config
) == -1) {
424 VPLOG(1) << "Failed to get port info";
425 return serial::ConnectionInfoPtr();
427 serial::ConnectionInfoPtr
info(serial::ConnectionInfo::New());
428 speed_t ispeed
= cfgetispeed(&config
);
429 speed_t ospeed
= cfgetospeed(&config
);
430 if (ispeed
== ospeed
) {
432 if (SpeedConstantToBitrate(ispeed
, &bitrate
)) {
433 info
->bitrate
= bitrate
;
434 } else if (ispeed
> 0) {
435 info
->bitrate
= static_cast<int>(ispeed
);
438 if ((config
.c_cflag
& CSIZE
) == CS7
) {
439 info
->data_bits
= serial::DATA_BITS_SEVEN
;
440 } else if ((config
.c_cflag
& CSIZE
) == CS8
) {
441 info
->data_bits
= serial::DATA_BITS_EIGHT
;
443 info
->data_bits
= serial::DATA_BITS_NONE
;
445 if (config
.c_cflag
& PARENB
) {
446 info
->parity_bit
= (config
.c_cflag
& PARODD
) ? serial::PARITY_BIT_ODD
447 : serial::PARITY_BIT_EVEN
;
449 info
->parity_bit
= serial::PARITY_BIT_NO
;
452 (config
.c_cflag
& CSTOPB
) ? serial::STOP_BITS_TWO
: serial::STOP_BITS_ONE
;
453 info
->cts_flow_control
= (config
.c_cflag
& CRTSCTS
) != 0;
457 std::string
SerialIoHandler::MaybeFixUpPortName(const std::string
& port_name
) {
461 } // namespace device