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_win.h"
12 #include "base/bind.h"
13 #include "base/macros.h"
14 #include "base/message_loop/message_loop_current.h"
15 #include "base/scoped_observer.h"
16 #include "base/sequence_checker.h"
17 #include "device/base/device_info_query_win.h"
18 #include "device/base/device_monitor_win.h"
19 #include "third_party/re2/src/re2/re2.h"
25 int BitrateToSpeedConstant(int bitrate) {
26 #define BITRATE_TO_SPEED_CASE(x) \
30 BITRATE_TO_SPEED_CASE(110);
31 BITRATE_TO_SPEED_CASE(300);
32 BITRATE_TO_SPEED_CASE(600);
33 BITRATE_TO_SPEED_CASE(1200);
34 BITRATE_TO_SPEED_CASE(2400);
35 BITRATE_TO_SPEED_CASE(4800);
36 BITRATE_TO_SPEED_CASE(9600);
37 BITRATE_TO_SPEED_CASE(14400);
38 BITRATE_TO_SPEED_CASE(19200);
39 BITRATE_TO_SPEED_CASE(38400);
40 BITRATE_TO_SPEED_CASE(57600);
41 BITRATE_TO_SPEED_CASE(115200);
42 BITRATE_TO_SPEED_CASE(128000);
43 BITRATE_TO_SPEED_CASE(256000);
45 // If the bitrate doesn't match that of one of the standard
46 // index constants, it may be provided as-is to the DCB
47 // structure, according to MSDN.
50 #undef BITRATE_TO_SPEED_CASE
53 int DataBitsEnumToConstant(mojom::SerialDataBits data_bits) {
55 case mojom::SerialDataBits::SEVEN:
57 case mojom::SerialDataBits::EIGHT:
63 int ParityBitEnumToConstant(mojom::SerialParityBit parity_bit) {
65 case mojom::SerialParityBit::EVEN:
67 case mojom::SerialParityBit::ODD:
69 case mojom::SerialParityBit::NO_PARITY:
75 int StopBitsEnumToConstant(mojom::SerialStopBits stop_bits) {
77 case mojom::SerialStopBits::TWO:
79 case mojom::SerialStopBits::ONE:
85 int SpeedConstantToBitrate(int speed) {
86 #define SPEED_TO_BITRATE_CASE(x) \
90 SPEED_TO_BITRATE_CASE(110);
91 SPEED_TO_BITRATE_CASE(300);
92 SPEED_TO_BITRATE_CASE(600);
93 SPEED_TO_BITRATE_CASE(1200);
94 SPEED_TO_BITRATE_CASE(2400);
95 SPEED_TO_BITRATE_CASE(4800);
96 SPEED_TO_BITRATE_CASE(9600);
97 SPEED_TO_BITRATE_CASE(14400);
98 SPEED_TO_BITRATE_CASE(19200);
99 SPEED_TO_BITRATE_CASE(38400);
100 SPEED_TO_BITRATE_CASE(57600);
101 SPEED_TO_BITRATE_CASE(115200);
102 SPEED_TO_BITRATE_CASE(128000);
103 SPEED_TO_BITRATE_CASE(256000);
105 // If it's not one of the standard index constants,
106 // it should be an integral baud rate, according to
110 #undef SPEED_TO_BITRATE_CASE
113 mojom::SerialDataBits DataBitsConstantToEnum(int data_bits) {
116 return mojom::SerialDataBits::SEVEN;
119 return mojom::SerialDataBits::EIGHT;
123 mojom::SerialParityBit ParityBitConstantToEnum(int parity_bit) {
124 switch (parity_bit) {
126 return mojom::SerialParityBit::EVEN;
128 return mojom::SerialParityBit::ODD;
131 return mojom::SerialParityBit::NO_PARITY;
135 mojom::SerialStopBits StopBitsConstantToEnum(int stop_bits) {
138 return mojom::SerialStopBits::TWO;
141 return mojom::SerialStopBits::ONE;
145 // Searches for the COM port in the device's friendly name, assigns its value to
146 // com_port, and returns whether the operation was successful.
147 bool GetCOMPort(const std::string friendly_name, std::string* com_port) {
148 return RE2::PartialMatch(friendly_name, ".* \\((COM[0-9]+)\\)", com_port);
154 scoped_refptr<SerialIoHandler> SerialIoHandler::Create(
155 scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) {
156 return new SerialIoHandlerWin(ui_thread_task_runner);
159 class SerialIoHandlerWin::UiThreadHelper final
160 : public DeviceMonitorWin::Observer {
163 base::WeakPtr<SerialIoHandlerWin> io_handler,
164 scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner)
165 : device_observer_(this),
166 io_handler_(io_handler),
167 io_thread_task_runner_(io_thread_task_runner) {}
169 ~UiThreadHelper() { DCHECK(thread_checker_.CalledOnValidThread()); }
171 static void Start(UiThreadHelper* self) {
172 self->thread_checker_.DetachFromThread();
173 DeviceMonitorWin* device_monitor = DeviceMonitorWin::GetForAllInterfaces();
175 self->device_observer_.Add(device_monitor);
179 // DeviceMonitorWin::Observer
180 void OnDeviceRemoved(const GUID& class_guid,
181 const std::string& device_path) override {
182 DCHECK(thread_checker_.CalledOnValidThread());
183 io_thread_task_runner_->PostTask(
184 FROM_HERE, base::Bind(&SerialIoHandlerWin::OnDeviceRemoved, io_handler_,
188 base::ThreadChecker thread_checker_;
189 ScopedObserver<DeviceMonitorWin, DeviceMonitorWin::Observer> device_observer_;
191 // This weak pointer is only valid when checked on this task runner.
192 base::WeakPtr<SerialIoHandlerWin> io_handler_;
193 scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner_;
195 DISALLOW_COPY_AND_ASSIGN(UiThreadHelper);
198 void SerialIoHandlerWin::OnDeviceRemoved(const std::string& device_path) {
199 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
201 DeviceInfoQueryWin device_info_query;
202 if (!device_info_query.device_info_list_valid()) {
203 DVPLOG(1) << "Failed to create a device information set";
207 // This will add the device so we can query driver info.
208 if (!device_info_query.AddDevice(device_path)) {
209 DVPLOG(1) << "Failed to get device interface data for " << device_path;
213 if (!device_info_query.GetDeviceInfo()) {
214 DVPLOG(1) << "Failed to get device info for " << device_path;
218 std::string friendly_name;
219 if (!device_info_query.GetDeviceStringProperty(DEVPKEY_Device_FriendlyName,
221 DVPLOG(1) << "Failed to get device service property";
225 std::string com_port;
226 if (!GetCOMPort(friendly_name, &com_port)) {
227 DVPLOG(1) << "Failed to get port name from \"" << friendly_name << "\".";
231 if (port() == com_port)
232 CancelRead(mojom::SerialReceiveError::DEVICE_LOST);
235 bool SerialIoHandlerWin::PostOpen() {
236 DCHECK(!comm_context_);
237 DCHECK(!read_context_);
238 DCHECK(!write_context_);
240 base::MessageLoopCurrentForIO::Get()->RegisterIOHandler(
241 file().GetPlatformFile(), this);
243 comm_context_.reset(new base::MessagePumpForIO::IOContext());
244 read_context_.reset(new base::MessagePumpForIO::IOContext());
245 write_context_.reset(new base::MessagePumpForIO::IOContext());
247 scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner =
248 base::ThreadTaskRunnerHandle::Get();
250 new UiThreadHelper(weak_factory_.GetWeakPtr(), io_thread_task_runner);
251 ui_thread_task_runner()->PostTask(
252 FROM_HERE, base::Bind(&UiThreadHelper::Start, helper_));
254 // A ReadIntervalTimeout of MAXDWORD will cause async reads to complete
255 // immediately with any data that's available, even if there is none.
256 // This is OK because we never issue a read request until WaitCommEvent
257 // signals that data is available.
258 COMMTIMEOUTS timeouts = {0};
259 timeouts.ReadIntervalTimeout = MAXDWORD;
260 if (!::SetCommTimeouts(file().GetPlatformFile(), &timeouts)) {
261 VPLOG(1) << "Failed to set serial timeouts";
268 void SerialIoHandlerWin::ReadImpl() {
269 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
270 DCHECK(pending_read_buffer());
271 DCHECK(file().IsValid());
273 if (!SetCommMask(file().GetPlatformFile(), EV_RXCHAR)) {
274 VPLOG(1) << "Failed to set serial event flags";
278 BOOL ok = ::WaitCommEvent(
279 file().GetPlatformFile(), &event_mask_, &comm_context_->overlapped);
280 if (!ok && GetLastError() != ERROR_IO_PENDING) {
281 VPLOG(1) << "Failed to receive serial event";
282 QueueReadCompleted(0, mojom::SerialReceiveError::SYSTEM_ERROR);
284 is_comm_pending_ = true;
287 void SerialIoHandlerWin::WriteImpl() {
288 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
289 DCHECK(pending_write_buffer());
290 DCHECK(file().IsValid());
292 BOOL ok = ::WriteFile(file().GetPlatformFile(),
293 pending_write_buffer(),
294 pending_write_buffer_len(),
296 &write_context_->overlapped);
297 if (!ok && GetLastError() != ERROR_IO_PENDING) {
298 VPLOG(1) << "Write failed";
299 QueueWriteCompleted(0, mojom::SerialSendError::SYSTEM_ERROR);
303 void SerialIoHandlerWin::CancelReadImpl() {
304 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
305 DCHECK(file().IsValid());
306 ::CancelIo(file().GetPlatformFile());
309 void SerialIoHandlerWin::CancelWriteImpl() {
310 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
311 DCHECK(file().IsValid());
312 ::CancelIo(file().GetPlatformFile());
315 bool SerialIoHandlerWin::ConfigurePortImpl() {
317 config.DCBlength = sizeof(config);
318 if (!GetCommState(file().GetPlatformFile(), &config)) {
319 VPLOG(1) << "Failed to get serial port info";
323 // Set up some sane default options that are not configurable.
324 config.fBinary = TRUE;
325 config.fParity = TRUE;
326 config.fAbortOnError = TRUE;
327 config.fOutxDsrFlow = FALSE;
328 config.fDtrControl = DTR_CONTROL_ENABLE;
329 config.fDsrSensitivity = FALSE;
330 config.fOutX = FALSE;
333 DCHECK(options().bitrate);
334 config.BaudRate = BitrateToSpeedConstant(options().bitrate);
336 DCHECK(options().data_bits != mojom::SerialDataBits::NONE);
337 config.ByteSize = DataBitsEnumToConstant(options().data_bits);
339 DCHECK(options().parity_bit != mojom::SerialParityBit::NONE);
340 config.Parity = ParityBitEnumToConstant(options().parity_bit);
342 DCHECK(options().stop_bits != mojom::SerialStopBits::NONE);
343 config.StopBits = StopBitsEnumToConstant(options().stop_bits);
345 DCHECK(options().has_cts_flow_control);
346 if (options().cts_flow_control) {
347 config.fOutxCtsFlow = TRUE;
348 config.fRtsControl = RTS_CONTROL_HANDSHAKE;
350 config.fOutxCtsFlow = FALSE;
351 config.fRtsControl = RTS_CONTROL_ENABLE;
354 if (!SetCommState(file().GetPlatformFile(), &config)) {
355 VPLOG(1) << "Failed to set serial port info";
361 SerialIoHandlerWin::SerialIoHandlerWin(
362 scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner)
363 : SerialIoHandler(ui_thread_task_runner),
365 is_comm_pending_(false),
367 weak_factory_(this) {}
369 SerialIoHandlerWin::~SerialIoHandlerWin() {
370 ui_thread_task_runner()->DeleteSoon(FROM_HERE, helper_);
373 void SerialIoHandlerWin::OnIOCompleted(
374 base::MessagePumpForIO::IOContext* context,
375 DWORD bytes_transferred,
377 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
378 if (context == comm_context_.get()) {
381 if (!ClearCommError(file().GetPlatformFile(), &errors, &status) ||
383 if (errors & CE_BREAK) {
384 ReadCompleted(0, mojom::SerialReceiveError::BREAK);
385 } else if (errors & CE_FRAME) {
386 ReadCompleted(0, mojom::SerialReceiveError::FRAME_ERROR);
387 } else if (errors & CE_OVERRUN) {
388 ReadCompleted(0, mojom::SerialReceiveError::OVERRUN);
389 } else if (errors & CE_RXOVER) {
390 ReadCompleted(0, mojom::SerialReceiveError::BUFFER_OVERFLOW);
391 } else if (errors & CE_RXPARITY) {
392 ReadCompleted(0, mojom::SerialReceiveError::PARITY_ERROR);
394 ReadCompleted(0, mojom::SerialReceiveError::SYSTEM_ERROR);
399 if (read_canceled()) {
400 ReadCompleted(bytes_transferred, read_cancel_reason());
401 } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
402 ReadCompleted(0, mojom::SerialReceiveError::SYSTEM_ERROR);
403 } else if (pending_read_buffer()) {
404 BOOL ok = ::ReadFile(file().GetPlatformFile(),
405 pending_read_buffer(),
406 pending_read_buffer_len(),
408 &read_context_->overlapped);
409 if (!ok && GetLastError() != ERROR_IO_PENDING) {
410 VPLOG(1) << "Read failed";
411 ReadCompleted(0, mojom::SerialReceiveError::SYSTEM_ERROR);
414 } else if (context == read_context_.get()) {
415 if (read_canceled()) {
416 ReadCompleted(bytes_transferred, read_cancel_reason());
417 } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
418 ReadCompleted(0, mojom::SerialReceiveError::SYSTEM_ERROR);
420 ReadCompleted(bytes_transferred,
421 error == ERROR_SUCCESS
422 ? mojom::SerialReceiveError::NONE
423 : mojom::SerialReceiveError::SYSTEM_ERROR);
425 } else if (context == write_context_.get()) {
426 DCHECK(pending_write_buffer());
427 if (write_canceled()) {
428 WriteCompleted(0, write_cancel_reason());
429 } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
430 WriteCompleted(0, mojom::SerialSendError::SYSTEM_ERROR);
431 if (error == ERROR_GEN_FAILURE && IsReadPending()) {
432 // For devices using drivers such as FTDI, CP2xxx, when device is
433 // disconnected, the context is comm_context_ and the error is
434 // ERROR_OPERATION_ABORTED.
435 // However, for devices using CDC-ACM driver, when device is
436 // disconnected, the context is write_context_ and the error is
437 // ERROR_GEN_FAILURE. In this situation, in addition to a write error
438 // signal, also need to generate a read error signal
439 // mojom::SerialOnReceiveError which will notify the app about the
441 CancelRead(mojom::SerialReceiveError::SYSTEM_ERROR);
444 WriteCompleted(bytes_transferred,
445 error == ERROR_SUCCESS
446 ? mojom::SerialSendError::NONE
447 : mojom::SerialSendError::SYSTEM_ERROR);
450 NOTREACHED() << "Invalid IOContext";
454 bool SerialIoHandlerWin::Flush() const {
455 if (!PurgeComm(file().GetPlatformFile(), PURGE_RXCLEAR | PURGE_TXCLEAR)) {
456 VPLOG(1) << "Failed to flush serial port";
462 mojom::SerialDeviceControlSignalsPtr SerialIoHandlerWin::GetControlSignals()
465 if (!GetCommModemStatus(file().GetPlatformFile(), &status)) {
466 VPLOG(1) << "Failed to get port control signals";
467 return mojom::SerialDeviceControlSignalsPtr();
470 auto signals = mojom::SerialDeviceControlSignals::New();
471 signals->dcd = (status & MS_RLSD_ON) != 0;
472 signals->cts = (status & MS_CTS_ON) != 0;
473 signals->dsr = (status & MS_DSR_ON) != 0;
474 signals->ri = (status & MS_RING_ON) != 0;
478 bool SerialIoHandlerWin::SetControlSignals(
479 const mojom::SerialHostControlSignals& signals) {
480 if (signals.has_dtr) {
481 if (!EscapeCommFunction(file().GetPlatformFile(),
482 signals.dtr ? SETDTR : CLRDTR)) {
483 VPLOG(1) << "Failed to configure DTR signal";
487 if (signals.has_rts) {
488 if (!EscapeCommFunction(file().GetPlatformFile(),
489 signals.rts ? SETRTS : CLRRTS)) {
490 VPLOG(1) << "Failed to configure RTS signal";
497 mojom::SerialConnectionInfoPtr SerialIoHandlerWin::GetPortInfo() const {
499 config.DCBlength = sizeof(config);
500 if (!GetCommState(file().GetPlatformFile(), &config)) {
501 VPLOG(1) << "Failed to get serial port info";
502 return mojom::SerialConnectionInfoPtr();
504 auto info = mojom::SerialConnectionInfo::New();
505 info->bitrate = SpeedConstantToBitrate(config.BaudRate);
506 info->data_bits = DataBitsConstantToEnum(config.ByteSize);
507 info->parity_bit = ParityBitConstantToEnum(config.Parity);
508 info->stop_bits = StopBitsConstantToEnum(config.StopBits);
509 info->cts_flow_control = config.fOutxCtsFlow != 0;
513 bool SerialIoHandlerWin::SetBreak() {
514 if (!SetCommBreak(file().GetPlatformFile())) {
515 VPLOG(1) << "Failed to set break";
521 bool SerialIoHandlerWin::ClearBreak() {
522 if (!ClearCommBreak(file().GetPlatformFile())) {
523 VPLOG(1) << "Failed to clear break";
529 std::string SerialIoHandler::MaybeFixUpPortName(const std::string& port_name) {
530 // For COM numbers less than 9, CreateFile is called with a string such as
531 // "COM1". For numbers greater than 9, a prefix of "\\\\.\\" must be added.
532 if (port_name.length() > std::string("COM9").length())
533 return std::string("\\\\.\\").append(port_name);
538 } // namespace device