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 "services/device/serial/serial_io_handler_win.h"
14 #include "base/bind.h"
15 #include "base/macros.h"
16 #include "base/message_loop/message_loop_current.h"
17 #include "base/scoped_observer.h"
18 #include "base/sequence_checker.h"
19 #include "device/base/device_info_query_win.h"
20 #include "device/base/device_monitor_win.h"
21 #include "services/device/serial/serial_device_enumerator_win.h"
27 int BitrateToSpeedConstant(int bitrate) {
28 #define BITRATE_TO_SPEED_CASE(x) \
32 BITRATE_TO_SPEED_CASE(110);
33 BITRATE_TO_SPEED_CASE(300);
34 BITRATE_TO_SPEED_CASE(600);
35 BITRATE_TO_SPEED_CASE(1200);
36 BITRATE_TO_SPEED_CASE(2400);
37 BITRATE_TO_SPEED_CASE(4800);
38 BITRATE_TO_SPEED_CASE(9600);
39 BITRATE_TO_SPEED_CASE(14400);
40 BITRATE_TO_SPEED_CASE(19200);
41 BITRATE_TO_SPEED_CASE(38400);
42 BITRATE_TO_SPEED_CASE(57600);
43 BITRATE_TO_SPEED_CASE(115200);
44 BITRATE_TO_SPEED_CASE(128000);
45 BITRATE_TO_SPEED_CASE(256000);
47 // If the bitrate doesn't match that of one of the standard
48 // index constants, it may be provided as-is to the DCB
49 // structure, according to MSDN.
52 #undef BITRATE_TO_SPEED_CASE
55 int DataBitsEnumToConstant(mojom::SerialDataBits data_bits) {
57 case mojom::SerialDataBits::SEVEN:
59 case mojom::SerialDataBits::EIGHT:
65 int ParityBitEnumToConstant(mojom::SerialParityBit parity_bit) {
67 case mojom::SerialParityBit::EVEN:
69 case mojom::SerialParityBit::ODD:
71 case mojom::SerialParityBit::NO_PARITY:
77 int StopBitsEnumToConstant(mojom::SerialStopBits stop_bits) {
79 case mojom::SerialStopBits::TWO:
81 case mojom::SerialStopBits::ONE:
87 int SpeedConstantToBitrate(int speed) {
88 #define SPEED_TO_BITRATE_CASE(x) \
92 SPEED_TO_BITRATE_CASE(110);
93 SPEED_TO_BITRATE_CASE(300);
94 SPEED_TO_BITRATE_CASE(600);
95 SPEED_TO_BITRATE_CASE(1200);
96 SPEED_TO_BITRATE_CASE(2400);
97 SPEED_TO_BITRATE_CASE(4800);
98 SPEED_TO_BITRATE_CASE(9600);
99 SPEED_TO_BITRATE_CASE(14400);
100 SPEED_TO_BITRATE_CASE(19200);
101 SPEED_TO_BITRATE_CASE(38400);
102 SPEED_TO_BITRATE_CASE(57600);
103 SPEED_TO_BITRATE_CASE(115200);
104 SPEED_TO_BITRATE_CASE(128000);
105 SPEED_TO_BITRATE_CASE(256000);
107 // If it's not one of the standard index constants,
108 // it should be an integral baud rate, according to
112 #undef SPEED_TO_BITRATE_CASE
115 mojom::SerialDataBits DataBitsConstantToEnum(int data_bits) {
118 return mojom::SerialDataBits::SEVEN;
121 return mojom::SerialDataBits::EIGHT;
125 mojom::SerialParityBit ParityBitConstantToEnum(int parity_bit) {
126 switch (parity_bit) {
128 return mojom::SerialParityBit::EVEN;
130 return mojom::SerialParityBit::ODD;
133 return mojom::SerialParityBit::NO_PARITY;
137 mojom::SerialStopBits StopBitsConstantToEnum(int stop_bits) {
140 return mojom::SerialStopBits::TWO;
143 return mojom::SerialStopBits::ONE;
150 scoped_refptr<SerialIoHandler> SerialIoHandler::Create(
151 const base::FilePath& port,
152 scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) {
153 return new SerialIoHandlerWin(port, std::move(ui_thread_task_runner));
156 class SerialIoHandlerWin::UiThreadHelper final
157 : public DeviceMonitorWin::Observer {
160 base::WeakPtr<SerialIoHandlerWin> io_handler,
161 scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner)
162 : device_observer_(this),
163 io_handler_(io_handler),
164 io_thread_task_runner_(io_thread_task_runner) {}
166 ~UiThreadHelper() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); }
168 static void Start(UiThreadHelper* self) {
169 DETACH_FROM_THREAD(self->thread_checker_);
170 DeviceMonitorWin* device_monitor = DeviceMonitorWin::GetForAllInterfaces();
172 self->device_observer_.Add(device_monitor);
176 // DeviceMonitorWin::Observer
177 void OnDeviceRemoved(const GUID& class_guid,
178 const std::string& device_path) override {
179 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
180 io_thread_task_runner_->PostTask(
181 FROM_HERE, base::BindOnce(&SerialIoHandlerWin::OnDeviceRemoved,
182 io_handler_, device_path));
185 THREAD_CHECKER(thread_checker_);
186 ScopedObserver<DeviceMonitorWin, DeviceMonitorWin::Observer> device_observer_;
188 // This weak pointer is only valid when checked on this task runner.
189 base::WeakPtr<SerialIoHandlerWin> io_handler_;
190 scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner_;
192 DISALLOW_COPY_AND_ASSIGN(UiThreadHelper);
195 void SerialIoHandlerWin::OnDeviceRemoved(const std::string& device_path) {
196 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
198 DeviceInfoQueryWin device_info_query;
199 if (!device_info_query.device_info_list_valid()) {
200 DVPLOG(1) << "Failed to create a device information set";
204 // This will add the device so we can query driver info.
205 if (!device_info_query.AddDevice(device_path)) {
206 DVPLOG(1) << "Failed to get device interface data for " << device_path;
210 if (!device_info_query.GetDeviceInfo()) {
211 DVPLOG(1) << "Failed to get device info for " << device_path;
215 std::string friendly_name;
216 if (!device_info_query.GetDeviceStringProperty(DEVPKEY_Device_FriendlyName,
218 DVPLOG(1) << "Failed to get device service property";
222 base::Optional<base::FilePath> path =
223 SerialDeviceEnumeratorWin::GetPath(friendly_name);
225 DVPLOG(1) << "Failed to get device path from \"" << friendly_name << "\".";
230 CancelRead(mojom::SerialReceiveError::DEVICE_LOST);
233 bool SerialIoHandlerWin::PostOpen() {
234 DCHECK(!comm_context_);
235 DCHECK(!read_context_);
236 DCHECK(!write_context_);
238 base::MessageLoopCurrentForIO::Get()->RegisterIOHandler(
239 file().GetPlatformFile(), this);
241 comm_context_.reset(new base::MessagePumpForIO::IOContext());
242 read_context_.reset(new base::MessagePumpForIO::IOContext());
243 write_context_.reset(new base::MessagePumpForIO::IOContext());
245 scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner =
246 base::ThreadTaskRunnerHandle::Get();
248 new UiThreadHelper(weak_factory_.GetWeakPtr(), io_thread_task_runner);
249 ui_thread_task_runner()->PostTask(
250 FROM_HERE, base::BindOnce(&UiThreadHelper::Start, helper_));
252 // A ReadIntervalTimeout of MAXDWORD will cause async reads to complete
253 // immediately with any data that's available, even if there is none.
254 // This is OK because we never issue a read request until WaitCommEvent
255 // signals that data is available.
256 COMMTIMEOUTS timeouts = {0};
257 timeouts.ReadIntervalTimeout = MAXDWORD;
258 if (!::SetCommTimeouts(file().GetPlatformFile(), &timeouts)) {
259 VPLOG(1) << "Failed to set serial timeouts";
266 void SerialIoHandlerWin::ReadImpl() {
267 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
268 DCHECK(pending_read_buffer());
269 DCHECK(file().IsValid());
271 if (!SetCommMask(file().GetPlatformFile(), EV_RXCHAR)) {
272 VPLOG(1) << "Failed to set serial event flags";
276 BOOL ok = ::WaitCommEvent(file().GetPlatformFile(), &event_mask_,
277 &comm_context_->overlapped);
278 if (!ok && GetLastError() != ERROR_IO_PENDING) {
279 VPLOG(1) << "Failed to receive serial event";
280 QueueReadCompleted(0, mojom::SerialReceiveError::SYSTEM_ERROR);
282 is_comm_pending_ = true;
285 void SerialIoHandlerWin::WriteImpl() {
286 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
287 DCHECK(pending_write_buffer());
288 DCHECK(file().IsValid());
290 BOOL ok = ::WriteFile(file().GetPlatformFile(), pending_write_buffer(),
291 pending_write_buffer_len(), NULL,
292 &write_context_->overlapped);
293 if (!ok && GetLastError() != ERROR_IO_PENDING) {
294 VPLOG(1) << "Write failed";
295 QueueWriteCompleted(0, mojom::SerialSendError::SYSTEM_ERROR);
299 void SerialIoHandlerWin::CancelReadImpl() {
300 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
301 DCHECK(file().IsValid());
302 ::CancelIo(file().GetPlatformFile());
305 void SerialIoHandlerWin::CancelWriteImpl() {
306 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
307 DCHECK(file().IsValid());
308 ::CancelIo(file().GetPlatformFile());
311 bool SerialIoHandlerWin::ConfigurePortImpl() {
313 config.DCBlength = sizeof(config);
314 if (!GetCommState(file().GetPlatformFile(), &config)) {
315 VPLOG(1) << "Failed to get serial port info";
319 // Set up some sane default options that are not configurable.
320 config.fBinary = TRUE;
321 config.fParity = TRUE;
322 config.fAbortOnError = TRUE;
323 config.fOutxDsrFlow = FALSE;
324 config.fDtrControl = DTR_CONTROL_ENABLE;
325 config.fDsrSensitivity = FALSE;
326 config.fOutX = FALSE;
329 DCHECK(options().bitrate);
330 config.BaudRate = BitrateToSpeedConstant(options().bitrate);
332 DCHECK(options().data_bits != mojom::SerialDataBits::NONE);
333 config.ByteSize = DataBitsEnumToConstant(options().data_bits);
335 DCHECK(options().parity_bit != mojom::SerialParityBit::NONE);
336 config.Parity = ParityBitEnumToConstant(options().parity_bit);
338 DCHECK(options().stop_bits != mojom::SerialStopBits::NONE);
339 config.StopBits = StopBitsEnumToConstant(options().stop_bits);
341 DCHECK(options().has_cts_flow_control);
342 if (options().cts_flow_control) {
343 config.fOutxCtsFlow = TRUE;
344 config.fRtsControl = RTS_CONTROL_HANDSHAKE;
346 config.fOutxCtsFlow = FALSE;
347 config.fRtsControl = RTS_CONTROL_ENABLE;
350 if (!SetCommState(file().GetPlatformFile(), &config)) {
351 VPLOG(1) << "Failed to set serial port info";
357 SerialIoHandlerWin::SerialIoHandlerWin(
358 const base::FilePath& port,
359 scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner)
360 : SerialIoHandler(port, std::move(ui_thread_task_runner)),
362 is_comm_pending_(false),
364 weak_factory_(this) {}
366 SerialIoHandlerWin::~SerialIoHandlerWin() {
367 ui_thread_task_runner()->DeleteSoon(FROM_HERE, helper_);
370 void SerialIoHandlerWin::OnIOCompleted(
371 base::MessagePumpForIO::IOContext* context,
372 DWORD bytes_transferred,
374 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
375 if (context == comm_context_.get()) {
378 if (!ClearCommError(file().GetPlatformFile(), &errors, &status) ||
380 if (errors & CE_BREAK) {
381 ReadCompleted(0, mojom::SerialReceiveError::BREAK);
382 } else if (errors & CE_FRAME) {
383 ReadCompleted(0, mojom::SerialReceiveError::FRAME_ERROR);
384 } else if (errors & CE_OVERRUN) {
385 ReadCompleted(0, mojom::SerialReceiveError::OVERRUN);
386 } else if (errors & CE_RXOVER) {
387 ReadCompleted(0, mojom::SerialReceiveError::BUFFER_OVERFLOW);
388 } else if (errors & CE_RXPARITY) {
389 ReadCompleted(0, mojom::SerialReceiveError::PARITY_ERROR);
391 ReadCompleted(0, mojom::SerialReceiveError::SYSTEM_ERROR);
396 if (read_canceled()) {
397 ReadCompleted(bytes_transferred, read_cancel_reason());
398 } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
399 ReadCompleted(0, mojom::SerialReceiveError::SYSTEM_ERROR);
400 } else if (pending_read_buffer()) {
401 BOOL ok = ::ReadFile(file().GetPlatformFile(), pending_read_buffer(),
402 pending_read_buffer_len(), NULL,
403 &read_context_->overlapped);
404 if (!ok && GetLastError() != ERROR_IO_PENDING) {
405 VPLOG(1) << "Read failed";
406 ReadCompleted(0, mojom::SerialReceiveError::SYSTEM_ERROR);
409 } else if (context == read_context_.get()) {
410 if (read_canceled()) {
411 ReadCompleted(bytes_transferred, read_cancel_reason());
412 } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
413 ReadCompleted(0, mojom::SerialReceiveError::SYSTEM_ERROR);
415 ReadCompleted(bytes_transferred,
416 error == ERROR_SUCCESS
417 ? mojom::SerialReceiveError::NONE
418 : mojom::SerialReceiveError::SYSTEM_ERROR);
420 } else if (context == write_context_.get()) {
421 DCHECK(pending_write_buffer());
422 if (write_canceled()) {
423 WriteCompleted(0, write_cancel_reason());
424 } else if (error != ERROR_SUCCESS && error != ERROR_OPERATION_ABORTED) {
425 WriteCompleted(0, mojom::SerialSendError::SYSTEM_ERROR);
426 if (error == ERROR_GEN_FAILURE && IsReadPending()) {
427 // For devices using drivers such as FTDI, CP2xxx, when device is
428 // disconnected, the context is comm_context_ and the error is
429 // ERROR_OPERATION_ABORTED.
430 // However, for devices using CDC-ACM driver, when device is
431 // disconnected, the context is write_context_ and the error is
432 // ERROR_GEN_FAILURE. In this situation, in addition to a write error
433 // signal, also need to generate a read error signal
434 // mojom::SerialOnReceiveError which will notify the app about the
436 CancelRead(mojom::SerialReceiveError::SYSTEM_ERROR);
439 WriteCompleted(bytes_transferred,
440 error == ERROR_SUCCESS
441 ? mojom::SerialSendError::NONE
442 : mojom::SerialSendError::SYSTEM_ERROR);
445 NOTREACHED() << "Invalid IOContext";
449 bool SerialIoHandlerWin::Flush() const {
450 if (!PurgeComm(file().GetPlatformFile(), PURGE_RXCLEAR | PURGE_TXCLEAR)) {
451 VPLOG(1) << "Failed to flush serial port";
457 mojom::SerialPortControlSignalsPtr SerialIoHandlerWin::GetControlSignals()
460 if (!GetCommModemStatus(file().GetPlatformFile(), &status)) {
461 VPLOG(1) << "Failed to get port control signals";
462 return mojom::SerialPortControlSignalsPtr();
465 auto signals = mojom::SerialPortControlSignals::New();
466 signals->dcd = (status & MS_RLSD_ON) != 0;
467 signals->cts = (status & MS_CTS_ON) != 0;
468 signals->dsr = (status & MS_DSR_ON) != 0;
469 signals->ri = (status & MS_RING_ON) != 0;
473 bool SerialIoHandlerWin::SetControlSignals(
474 const mojom::SerialHostControlSignals& signals) {
475 if (signals.has_dtr) {
476 if (!EscapeCommFunction(file().GetPlatformFile(),
477 signals.dtr ? SETDTR : CLRDTR)) {
478 VPLOG(1) << "Failed to configure DTR signal";
482 if (signals.has_rts) {
483 if (!EscapeCommFunction(file().GetPlatformFile(),
484 signals.rts ? SETRTS : CLRRTS)) {
485 VPLOG(1) << "Failed to configure RTS signal";
492 mojom::SerialConnectionInfoPtr SerialIoHandlerWin::GetPortInfo() const {
494 config.DCBlength = sizeof(config);
495 if (!GetCommState(file().GetPlatformFile(), &config)) {
496 VPLOG(1) << "Failed to get serial port info";
497 return mojom::SerialConnectionInfoPtr();
499 auto info = mojom::SerialConnectionInfo::New();
500 info->bitrate = SpeedConstantToBitrate(config.BaudRate);
501 info->data_bits = DataBitsConstantToEnum(config.ByteSize);
502 info->parity_bit = ParityBitConstantToEnum(config.Parity);
503 info->stop_bits = StopBitsConstantToEnum(config.StopBits);
504 info->cts_flow_control = config.fOutxCtsFlow != 0;
508 bool SerialIoHandlerWin::SetBreak() {
509 if (!SetCommBreak(file().GetPlatformFile())) {
510 VPLOG(1) << "Failed to set break";
516 bool SerialIoHandlerWin::ClearBreak() {
517 if (!ClearCommBreak(file().GetPlatformFile())) {
518 VPLOG(1) << "Failed to clear break";
524 } // namespace device