blob: 52aad173d397064ef9d9ddf6c54d76d8524b0a3e [file] [log] [blame]
// 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 "device/serial/serial_io_handler_posix.h"
#include <sys/ioctl.h>
#include <termios.h>
#include "base/posix/eintr_wrapper.h"
#if defined(OS_LINUX)
#include <linux/serial.h>
#if defined(OS_CHROMEOS)
#include "base/bind.h"
#include "base/sys_info.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/permission_broker_client.h"
#endif // defined(OS_CHROMEOS)
// 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)
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_MACOSX)
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
}
// 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)
#if !defined(OS_MACOSX)
SPEED_TO_BITRATE_CASE(57600)
SPEED_TO_BITRATE_CASE(115200)
SPEED_TO_BITRATE_CASE(230400)
SPEED_TO_BITRATE_CASE(460800)
SPEED_TO_BITRATE_CASE(576000)
SPEED_TO_BITRATE_CASE(921600)
#endif
default:
return false;
}
#undef SPEED_TO_BITRATE_CASE
}
bool SetCustomBitrate(base::PlatformFile file,
struct termios* config,
int bitrate) {
#if defined(OS_LINUX)
struct termios2 tio;
if (ioctl(file, TCGETS2, &tio) < 0) {
VPLOG(1) << "Failed to get parameters to set custom bitrate";
return false;
}
tio.c_cflag &= ~CBAUD;
tio.c_cflag |= CBAUDEX;
tio.c_ispeed = bitrate;
tio.c_ospeed = bitrate;
if (ioctl(file, TCSETS2, &tio) < 0) {
VPLOG(1) << "Failed to set custom bitrate";
return false;
}
return true;
#elif defined(OS_MACOSX)
speed_t speed = static_cast<speed_t>(bitrate);
cfsetispeed(config, speed);
cfsetospeed(config, speed);
return true;
#else
return false;
#endif
}
} // namespace
namespace device {
// static
scoped_refptr<SerialIoHandler> SerialIoHandler::Create(
scoped_refptr<base::SingleThreadTaskRunner> file_thread_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) {
return new SerialIoHandlerPosix(file_thread_task_runner,
ui_thread_task_runner);
}
void SerialIoHandlerPosix::RequestAccess(
const std::string& port,
scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
#if defined(OS_LINUX) && defined(OS_CHROMEOS)
if (base::SysInfo::IsRunningOnChromeOS()) {
chromeos::PermissionBrokerClient* client =
chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient();
if (!client) {
DVLOG(1) << "Could not get permission_broker client.";
OnRequestAccessComplete(port, false /* failure */);
return;
}
// PermissionBrokerClient should be called on the UI thread.
ui_task_runner->PostTask(
FROM_HERE,
base::Bind(
&chromeos::PermissionBrokerClient::RequestPathAccess,
base::Unretained(client), port, -1,
base::Bind(&SerialIoHandler::OnRequestAccessComplete, this, port)));
} else {
OnRequestAccessComplete(port, true /* success */);
return;
}
#else
OnRequestAccessComplete(port, true /* success */);
#endif // defined(OS_LINUX) && defined(OS_CHROMEOS)
}
void SerialIoHandlerPosix::ReadImpl() {
DCHECK(CalledOnValidThread());
DCHECK(pending_read_buffer());
DCHECK(file().IsValid());
EnsureWatchingReads();
}
void SerialIoHandlerPosix::WriteImpl() {
DCHECK(CalledOnValidThread());
DCHECK(pending_write_buffer());
DCHECK(file().IsValid());
EnsureWatchingWrites();
}
void SerialIoHandlerPosix::CancelReadImpl() {
DCHECK(CalledOnValidThread());
is_watching_reads_ = false;
file_read_watcher_.StopWatchingFileDescriptor();
QueueReadCompleted(0, read_cancel_reason());
}
void SerialIoHandlerPosix::CancelWriteImpl() {
DCHECK(CalledOnValidThread());
is_watching_writes_ = false;
file_write_watcher_.StopWatchingFileDescriptor();
QueueWriteCompleted(0, write_cancel_reason());
}
bool SerialIoHandlerPosix::ConfigurePortImpl() {
struct termios config;
if (tcgetattr(file().GetPlatformFile(), &config) != 0) {
VPLOG(1) << "Failed to get port attributes";
return false;
}
// Set flags for 'raw' operation
config.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG);
config.c_iflag &=
~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
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 (BitrateToSpeedConstant(options().bitrate, &bitrate_opt)) {
cfsetispeed(&config, bitrate_opt);
cfsetospeed(&config, bitrate_opt);
} else {
// Attempt to set a custom speed.
if (!SetCustomBitrate(file().GetPlatformFile(), &config,
options().bitrate)) {
return false;
}
}
DCHECK(options().data_bits != serial::DATA_BITS_NONE);
config.c_cflag &= ~CSIZE;
switch (options().data_bits) {
case serial::DATA_BITS_SEVEN:
config.c_cflag |= CS7;
break;
case serial::DATA_BITS_EIGHT:
default:
config.c_cflag |= CS8;
break;
}
DCHECK(options().parity_bit != serial::PARITY_BIT_NONE);
switch (options().parity_bit) {
case serial::PARITY_BIT_EVEN:
config.c_cflag |= PARENB;
config.c_cflag &= ~PARODD;
break;
case serial::PARITY_BIT_ODD:
config.c_cflag |= (PARODD | PARENB);
break;
case serial::PARITY_BIT_NO:
default:
config.c_cflag &= ~(PARODD | PARENB);
break;
}
DCHECK(options().stop_bits != serial::STOP_BITS_NONE);
switch (options().stop_bits) {
case serial::STOP_BITS_TWO:
config.c_cflag |= CSTOPB;
break;
case serial::STOP_BITS_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 (tcsetattr(file().GetPlatformFile(), TCSANOW, &config) != 0) {
VPLOG(1) << "Failed to set port attributes";
return false;
}
return true;
}
SerialIoHandlerPosix::SerialIoHandlerPosix(
scoped_refptr<base::SingleThreadTaskRunner> file_thread_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner)
: SerialIoHandler(file_thread_task_runner, ui_thread_task_runner),
is_watching_reads_(false),
is_watching_writes_(false) {
}
SerialIoHandlerPosix::~SerialIoHandlerPosix() {
}
void SerialIoHandlerPosix::OnFileCanReadWithoutBlocking(int fd) {
DCHECK(CalledOnValidThread());
DCHECK_EQ(fd, file().GetPlatformFile());
if (pending_read_buffer()) {
int bytes_read = HANDLE_EINTR(read(file().GetPlatformFile(),
pending_read_buffer(),
pending_read_buffer_len()));
if (bytes_read < 0) {
if (errno == ENXIO) {
ReadCompleted(0, serial::RECEIVE_ERROR_DEVICE_LOST);
} else {
ReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
}
} else if (bytes_read == 0) {
ReadCompleted(0, serial::RECEIVE_ERROR_DEVICE_LOST);
} else {
ReadCompleted(bytes_read, serial::RECEIVE_ERROR_NONE);
}
} else {
// Stop watching the fd if we get notifications with no pending
// reads or writes to avoid starving the message loop.
is_watching_reads_ = false;
file_read_watcher_.StopWatchingFileDescriptor();
}
}
void SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking(int fd) {
DCHECK(CalledOnValidThread());
DCHECK_EQ(fd, file().GetPlatformFile());
if (pending_write_buffer()) {
int bytes_written = HANDLE_EINTR(write(file().GetPlatformFile(),
pending_write_buffer(),
pending_write_buffer_len()));
if (bytes_written < 0) {
WriteCompleted(0, serial::SEND_ERROR_SYSTEM_ERROR);
} else {
WriteCompleted(bytes_written, serial::SEND_ERROR_NONE);
}
} else {
// Stop watching the fd if we get notifications with no pending
// writes to avoid starving the message loop.
is_watching_writes_ = false;
file_write_watcher_.StopWatchingFileDescriptor();
}
}
void SerialIoHandlerPosix::EnsureWatchingReads() {
DCHECK(CalledOnValidThread());
DCHECK(file().IsValid());
if (!is_watching_reads_) {
is_watching_reads_ = base::MessageLoopForIO::current()->WatchFileDescriptor(
file().GetPlatformFile(),
true,
base::MessageLoopForIO::WATCH_READ,
&file_read_watcher_,
this);
}
}
void SerialIoHandlerPosix::EnsureWatchingWrites() {
DCHECK(CalledOnValidThread());
DCHECK(file().IsValid());
if (!is_watching_writes_) {
is_watching_writes_ =
base::MessageLoopForIO::current()->WatchFileDescriptor(
file().GetPlatformFile(),
true,
base::MessageLoopForIO::WATCH_WRITE,
&file_write_watcher_,
this);
}
}
bool SerialIoHandlerPosix::Flush() const {
if (tcflush(file().GetPlatformFile(), TCIOFLUSH) != 0) {
VPLOG(1) << "Failed to flush port";
return false;
}
return true;
}
serial::DeviceControlSignalsPtr SerialIoHandlerPosix::GetControlSignals()
const {
int status;
if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
VPLOG(1) << "Failed to get port control signals";
return serial::DeviceControlSignalsPtr();
}
serial::DeviceControlSignalsPtr signals(serial::DeviceControlSignals::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.Pass();
}
bool SerialIoHandlerPosix::SetControlSignals(
const serial::HostControlSignals& signals) {
int status;
if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
VPLOG(1) << "Failed to get port control signals";
return false;
}
if (signals.has_dtr) {
if (signals.dtr) {
status |= TIOCM_DTR;
} else {
status &= ~TIOCM_DTR;
}
}
if (signals.has_rts) {
if (signals.rts) {
status |= TIOCM_RTS;
} else {
status &= ~TIOCM_RTS;
}
}
if (ioctl(file().GetPlatformFile(), TIOCMSET, &status) != 0) {
VPLOG(1) << "Failed to set port control signals";
return false;
}
return true;
}
serial::ConnectionInfoPtr SerialIoHandlerPosix::GetPortInfo() const {
struct termios config;
if (tcgetattr(file().GetPlatformFile(), &config) == -1) {
VPLOG(1) << "Failed to get port info";
return serial::ConnectionInfoPtr();
}
serial::ConnectionInfoPtr info(serial::ConnectionInfo::New());
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);
}
}
if ((config.c_cflag & CSIZE) == CS7) {
info->data_bits = serial::DATA_BITS_SEVEN;
} else if ((config.c_cflag & CSIZE) == CS8) {
info->data_bits = serial::DATA_BITS_EIGHT;
} else {
info->data_bits = serial::DATA_BITS_NONE;
}
if (config.c_cflag & PARENB) {
info->parity_bit = (config.c_cflag & PARODD) ? serial::PARITY_BIT_ODD
: serial::PARITY_BIT_EVEN;
} else {
info->parity_bit = serial::PARITY_BIT_NO;
}
info->stop_bits =
(config.c_cflag & CSTOPB) ? serial::STOP_BITS_TWO : serial::STOP_BITS_ONE;
info->cts_flow_control = (config.c_cflag & CRTSCTS) != 0;
return info.Pass();
}
std::string SerialIoHandler::MaybeFixUpPortName(const std::string& port_name) {
return port_name;
}
} // namespace device