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 <windows.h>
6
7#include "device/serial/serial_io_handler_win.h"
8
9namespace device {
10
11namespace {
12
13int BitrateToSpeedConstant(int bitrate) {
14#define BITRATE_TO_SPEED_CASE(x) \
15  case x:                        \
16    return CBR_##x;
17  switch (bitrate) {
18    BITRATE_TO_SPEED_CASE(110);
19    BITRATE_TO_SPEED_CASE(300);
20    BITRATE_TO_SPEED_CASE(600);
21    BITRATE_TO_SPEED_CASE(1200);
22    BITRATE_TO_SPEED_CASE(2400);
23    BITRATE_TO_SPEED_CASE(4800);
24    BITRATE_TO_SPEED_CASE(9600);
25    BITRATE_TO_SPEED_CASE(14400);
26    BITRATE_TO_SPEED_CASE(19200);
27    BITRATE_TO_SPEED_CASE(38400);
28    BITRATE_TO_SPEED_CASE(57600);
29    BITRATE_TO_SPEED_CASE(115200);
30    BITRATE_TO_SPEED_CASE(128000);
31    BITRATE_TO_SPEED_CASE(256000);
32    default:
33      // If the bitrate doesn't match that of one of the standard
34      // index constants, it may be provided as-is to the DCB
35      // structure, according to MSDN.
36      return bitrate;
37  }
38#undef BITRATE_TO_SPEED_CASE
39}
40
41int DataBitsEnumToConstant(serial::DataBits data_bits) {
42  switch (data_bits) {
43    case serial::DATA_BITS_SEVEN:
44      return 7;
45    case serial::DATA_BITS_EIGHT:
46    default:
47      return 8;
48  }
49}
50
51int ParityBitEnumToConstant(serial::ParityBit parity_bit) {
52  switch (parity_bit) {
53    case serial::PARITY_BIT_EVEN:
54      return EVENPARITY;
55    case serial::PARITY_BIT_ODD:
56      return ODDPARITY;
57    case serial::PARITY_BIT_NO:
58    default:
59      return NOPARITY;
60  }
61}
62
63int StopBitsEnumToConstant(serial::StopBits stop_bits) {
64  switch (stop_bits) {
65    case serial::STOP_BITS_TWO:
66      return TWOSTOPBITS;
67    case serial::STOP_BITS_ONE:
68    default:
69      return ONESTOPBIT;
70  }
71}
72
73int SpeedConstantToBitrate(int speed) {
74#define SPEED_TO_BITRATE_CASE(x) \
75  case CBR_##x:                  \
76    return x;
77  switch (speed) {
78    SPEED_TO_BITRATE_CASE(110);
79    SPEED_TO_BITRATE_CASE(300);
80    SPEED_TO_BITRATE_CASE(600);
81    SPEED_TO_BITRATE_CASE(1200);
82    SPEED_TO_BITRATE_CASE(2400);
83    SPEED_TO_BITRATE_CASE(4800);
84    SPEED_TO_BITRATE_CASE(9600);
85    SPEED_TO_BITRATE_CASE(14400);
86    SPEED_TO_BITRATE_CASE(19200);
87    SPEED_TO_BITRATE_CASE(38400);
88    SPEED_TO_BITRATE_CASE(57600);
89    SPEED_TO_BITRATE_CASE(115200);
90    SPEED_TO_BITRATE_CASE(128000);
91    SPEED_TO_BITRATE_CASE(256000);
92    default:
93      // If it's not one of the standard index constants,
94      // it should be an integral baud rate, according to
95      // MSDN.
96      return speed;
97  }
98#undef SPEED_TO_BITRATE_CASE
99}
100
101serial::DataBits DataBitsConstantToEnum(int data_bits) {
102  switch (data_bits) {
103    case 7:
104      return serial::DATA_BITS_SEVEN;
105    case 8:
106    default:
107      return serial::DATA_BITS_EIGHT;
108  }
109}
110
111serial::ParityBit ParityBitConstantToEnum(int parity_bit) {
112  switch (parity_bit) {
113    case EVENPARITY:
114      return serial::PARITY_BIT_EVEN;
115    case ODDPARITY:
116      return serial::PARITY_BIT_ODD;
117    case NOPARITY:
118    default:
119      return serial::PARITY_BIT_NO;
120  }
121}
122
123serial::StopBits StopBitsConstantToEnum(int stop_bits) {
124  switch (stop_bits) {
125    case TWOSTOPBITS:
126      return serial::STOP_BITS_TWO;
127    case ONESTOPBIT:
128    default:
129      return serial::STOP_BITS_ONE;
130  }
131}
132
133}  // namespace
134
135// static
136scoped_refptr<SerialIoHandler> SerialIoHandler::Create(
137    scoped_refptr<base::MessageLoopProxy> file_thread_message_loop) {
138  return new SerialIoHandlerWin(file_thread_message_loop);
139}
140
141bool SerialIoHandlerWin::PostOpen() {
142  DCHECK(!comm_context_);
143  DCHECK(!read_context_);
144  DCHECK(!write_context_);
145
146  base::MessageLoopForIO::current()->RegisterIOHandler(file().GetPlatformFile(),
147                                                       this);
148
149  comm_context_.reset(new base::MessageLoopForIO::IOContext());
150  comm_context_->handler = this;
151  memset(&comm_context_->overlapped, 0, sizeof(comm_context_->overlapped));
152
153  read_context_.reset(new base::MessageLoopForIO::IOContext());
154  read_context_->handler = this;
155  memset(&read_context_->overlapped, 0, sizeof(read_context_->overlapped));
156
157  write_context_.reset(new base::MessageLoopForIO::IOContext());
158  write_context_->handler = this;
159  memset(&write_context_->overlapped, 0, sizeof(write_context_->overlapped));
160
161  // A ReadIntervalTimeout of MAXDWORD will cause async reads to complete
162  // immediately with any data that's available, even if there is none.
163  // This is OK because we never issue a read request until WaitCommEvent
164  // signals that data is available.
165  COMMTIMEOUTS timeouts = {0};
166  timeouts.ReadIntervalTimeout = MAXDWORD;
167  if (!::SetCommTimeouts(file().GetPlatformFile(), &timeouts)) {
168    return false;
169  }
170
171  DCB config = {0};
172  config.DCBlength = sizeof(config);
173  if (!GetCommState(file().GetPlatformFile(), &config)) {
174    return false;
175  }
176  // Setup some sane default state.
177  config.fBinary = TRUE;
178  config.fParity = FALSE;
179  config.fAbortOnError = TRUE;
180  config.fOutxCtsFlow = FALSE;
181  config.fOutxDsrFlow = FALSE;
182  config.fRtsControl = RTS_CONTROL_ENABLE;
183  config.fDtrControl = DTR_CONTROL_ENABLE;
184  config.fDsrSensitivity = FALSE;
185  config.fOutX = FALSE;
186  config.fInX = FALSE;
187  return SetCommState(file().GetPlatformFile(), &config) != 0;
188}
189
190void SerialIoHandlerWin::ReadImpl() {
191  DCHECK(CalledOnValidThread());
192  DCHECK(pending_read_buffer());
193  DCHECK(file().IsValid());
194
195  DWORD errors;
196  COMSTAT status;
197  if (!ClearCommError(file().GetPlatformFile(), &errors, &status) ||
198      errors != 0) {
199    QueueReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
200    return;
201  }
202
203  SetCommMask(file().GetPlatformFile(), EV_RXCHAR);
204
205  event_mask_ = 0;
206  BOOL ok = ::WaitCommEvent(
207      file().GetPlatformFile(), &event_mask_, &comm_context_->overlapped);
208  if (!ok && GetLastError() != ERROR_IO_PENDING) {
209    QueueReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
210  }
211  is_comm_pending_ = true;
212}
213
214void SerialIoHandlerWin::WriteImpl() {
215  DCHECK(CalledOnValidThread());
216  DCHECK(pending_write_buffer());
217  DCHECK(file().IsValid());
218
219  BOOL ok = ::WriteFile(file().GetPlatformFile(),
220                        pending_write_buffer(),
221                        pending_write_buffer_len(),
222                        NULL,
223                        &write_context_->overlapped);
224  if (!ok && GetLastError() != ERROR_IO_PENDING) {
225    QueueWriteCompleted(0, serial::SEND_ERROR_SYSTEM_ERROR);
226  }
227}
228
229void SerialIoHandlerWin::CancelReadImpl() {
230  DCHECK(CalledOnValidThread());
231  DCHECK(file().IsValid());
232  ::CancelIo(file().GetPlatformFile());
233}
234
235void SerialIoHandlerWin::CancelWriteImpl() {
236  DCHECK(CalledOnValidThread());
237  DCHECK(file().IsValid());
238  ::CancelIo(file().GetPlatformFile());
239}
240
241SerialIoHandlerWin::SerialIoHandlerWin(
242    scoped_refptr<base::MessageLoopProxy> file_thread_message_loop)
243    : SerialIoHandler(file_thread_message_loop),
244      event_mask_(0),
245      is_comm_pending_(false) {
246}
247
248SerialIoHandlerWin::~SerialIoHandlerWin() {
249}
250
251void SerialIoHandlerWin::OnIOCompleted(
252    base::MessageLoopForIO::IOContext* context,
253    DWORD bytes_transferred,
254    DWORD error) {
255  DCHECK(CalledOnValidThread());
256  if (context == comm_context_) {
257    if (read_canceled()) {
258      ReadCompleted(bytes_transferred, read_cancel_reason());
259    } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
260      ReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
261    } else if (pending_read_buffer()) {
262      BOOL ok = ::ReadFile(file().GetPlatformFile(),
263                           pending_read_buffer(),
264                           pending_read_buffer_len(),
265                           NULL,
266                           &read_context_->overlapped);
267      if (!ok && GetLastError() != ERROR_IO_PENDING) {
268        ReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
269      }
270    }
271  } else if (context == read_context_) {
272    if (read_canceled()) {
273      ReadCompleted(bytes_transferred, read_cancel_reason());
274    } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
275      ReadCompleted(0, serial::RECEIVE_ERROR_SYSTEM_ERROR);
276    } else {
277      ReadCompleted(bytes_transferred,
278                    error == ERROR_SUCCESS
279                        ? serial::RECEIVE_ERROR_NONE
280                        : serial::RECEIVE_ERROR_SYSTEM_ERROR);
281    }
282  } else if (context == write_context_) {
283    DCHECK(pending_write_buffer());
284    if (write_canceled()) {
285      WriteCompleted(0, write_cancel_reason());
286    } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
287      WriteCompleted(0, serial::SEND_ERROR_SYSTEM_ERROR);
288    } else {
289      WriteCompleted(bytes_transferred,
290                     error == ERROR_SUCCESS ? serial::SEND_ERROR_NONE
291                                            : serial::SEND_ERROR_SYSTEM_ERROR);
292    }
293  } else {
294    NOTREACHED() << "Invalid IOContext";
295  }
296}
297
298bool SerialIoHandlerWin::ConfigurePort(
299    const serial::ConnectionOptions& options) {
300  DCB config = {0};
301  config.DCBlength = sizeof(config);
302  if (!GetCommState(file().GetPlatformFile(), &config)) {
303    return false;
304  }
305  if (options.bitrate)
306    config.BaudRate = BitrateToSpeedConstant(options.bitrate);
307  if (options.data_bits != serial::DATA_BITS_NONE)
308    config.ByteSize = DataBitsEnumToConstant(options.data_bits);
309  if (options.parity_bit != serial::PARITY_BIT_NONE)
310    config.Parity = ParityBitEnumToConstant(options.parity_bit);
311  if (options.stop_bits != serial::STOP_BITS_NONE)
312    config.StopBits = StopBitsEnumToConstant(options.stop_bits);
313  if (options.has_cts_flow_control) {
314    if (options.cts_flow_control) {
315      config.fOutxCtsFlow = TRUE;
316      config.fRtsControl = RTS_CONTROL_HANDSHAKE;
317    } else {
318      config.fOutxCtsFlow = FALSE;
319      config.fRtsControl = RTS_CONTROL_ENABLE;
320    }
321  }
322  return SetCommState(file().GetPlatformFile(), &config) != 0;
323}
324
325bool SerialIoHandlerWin::Flush() const {
326  return PurgeComm(file().GetPlatformFile(), PURGE_RXCLEAR | PURGE_TXCLEAR) !=
327         0;
328}
329
330serial::DeviceControlSignalsPtr SerialIoHandlerWin::GetControlSignals() const {
331  DWORD status;
332  if (!GetCommModemStatus(file().GetPlatformFile(), &status)) {
333    return serial::DeviceControlSignalsPtr();
334  }
335
336  serial::DeviceControlSignalsPtr signals(serial::DeviceControlSignals::New());
337  signals->dcd = (status & MS_RLSD_ON) != 0;
338  signals->cts = (status & MS_CTS_ON) != 0;
339  signals->dsr = (status & MS_DSR_ON) != 0;
340  signals->ri = (status & MS_RING_ON) != 0;
341  return signals.Pass();
342}
343
344bool SerialIoHandlerWin::SetControlSignals(
345    const serial::HostControlSignals& signals) {
346  if (signals.has_dtr) {
347    if (!EscapeCommFunction(file().GetPlatformFile(),
348                            signals.dtr ? SETDTR : CLRDTR)) {
349      return false;
350    }
351  }
352  if (signals.has_rts) {
353    if (!EscapeCommFunction(file().GetPlatformFile(),
354                            signals.rts ? SETRTS : CLRRTS)) {
355      return false;
356    }
357  }
358  return true;
359}
360
361serial::ConnectionInfoPtr SerialIoHandlerWin::GetPortInfo() const {
362  DCB config = {0};
363  config.DCBlength = sizeof(config);
364  if (!GetCommState(file().GetPlatformFile(), &config)) {
365    return serial::ConnectionInfoPtr();
366  }
367  serial::ConnectionInfoPtr info(serial::ConnectionInfo::New());
368  info->bitrate = SpeedConstantToBitrate(config.BaudRate);
369  info->data_bits = DataBitsConstantToEnum(config.ByteSize);
370  info->parity_bit = ParityBitConstantToEnum(config.Parity);
371  info->stop_bits = StopBitsConstantToEnum(config.StopBits);
372  info->cts_flow_control = config.fOutxCtsFlow != 0;
373  return info.Pass();
374}
375
376std::string SerialIoHandler::MaybeFixUpPortName(const std::string& port_name) {
377  // For COM numbers less than 9, CreateFile is called with a string such as
378  // "COM1". For numbers greater than 9, a prefix of "\\\\.\\" must be added.
379  if (port_name.length() > std::string("COM9").length())
380    return std::string("\\\\.\\").append(port_name);
381
382  return port_name;
383}
384
385}  // namespace device
386