1 /*
2 * UsbCdcIoChannel.cpp
3 *
4 * IOChannel via CDC (VCOM) over USB communication.
5 *
6 * Copyright (C) 2007 - 2011 Texas Instruments Incorporated - http://www.ti.com/
7 *
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the
19 * distribution.
20 *
21 * Neither the name of Texas Instruments Incorporated nor the names of
22 * its contributors may be used to endorse or promote products derived
23 * from this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
37
38 #include <pch.h>
39
40 #include "UsbCdcIoChannel.h"
41 #include "logging/Logging.h"
42
43 #include <boost/asio/read.hpp>
44 #include <boost/asio/write.hpp>
45 #include <boost/asio/io_service.hpp>
46
47 #if defined(_WIN32) || defined(_WIN64)
48
49 extern "C" {
50 #include <setupapi.h>
51 #include <dbt.h>
52 }
53
54 #elif defined(__APPLE__)
55
56 #include <CoreFoundation/CoreFoundation.h>
57 #include <IOKit/IOKitLib.h>
58 #include <IOKit/IOCFPlugIn.h>
59 #include <IOKit/usb/IOUSBLib.h>
60 #include <IOKit/IOMessage.h>
61 #include <mach/mach_port.h>
62 #include <IOKit/serial/IOSerialKeys.h>
63 #include <sys/stat.h>
64 #define MAXPATHLEN 128
65 #define MAXNAMELEN 64
66
67 #else
68
69 #include <unistd.h>
70 #include <boost/filesystem.hpp>
71
72 using namespace boost::filesystem;
73
74 #endif
75
76 using namespace TI::DLL430;
77 using namespace std;
78 using namespace std::placeholders;
79 using namespace boost::asio;
80
81 #define XOFF 0x13
82 #define XON 0x11
83 #define XMASK 0x10
84 #define XMASK_OFF 0x03
85 #define XMASK_ON 0x01
86 #define XMASK_MASK 0x00
87
88
UsbCdcIoChannel(const PortInfo & portInfo)89 UsbCdcIoChannel::UsbCdcIoChannel(const PortInfo& portInfo)
90 : UsbIoChannel(portInfo)
91 , inputBuffer(260)
92 , ioService(0)
93 , port(0)
94 , comState(ComStateRcv)
95 , bytesReceived(0)
96 , timerEvent(false)
97 , readEvent(false)
98 , cancelled(false)
99 {
100 retrieveStatus();
101 }
102
~UsbCdcIoChannel()103 UsbCdcIoChannel::~UsbCdcIoChannel()
104 {
105 this->cleanup();
106 }
107
createCdcPortList(const uint16_t vendorId,const uint16_t productId,PortMap & portList)108 void UsbCdcIoChannel::createCdcPortList(const uint16_t vendorId, const uint16_t productId, PortMap& portList)
109 {
110 #if defined(_WIN32) || defined(_WIN64)
111 stringstream cdcIdStream;
112 cdcIdStream << hex << setfill('0') << "USB\\VID_" << setw(4) << vendorId << "&PID_" << setw(4) << productId;
113
114 const int BUFFER_SIZE = 128;
115
116 HDEVINFO hDevInfo = ::SetupDiGetClassDevs(nullptr, "USB", 0, DIGCF_PRESENT | DIGCF_ALLCLASSES );
117 SP_DEVINFO_DATA devInfoData;
118 devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
119
120 for (int i = 0; ::SetupDiEnumDeviceInfo(hDevInfo, i, &devInfoData); ++i )
121 {
122 char deviceId[BUFFER_SIZE] = {0};
123 BOOL result = ::SetupDiGetDeviceInstanceId(hDevInfo, &devInfoData, deviceId, BUFFER_SIZE, nullptr);
124
125 //not TI and/or not CDC
126 if (result && string(deviceId).find(cdcIdStream.str()) != string::npos)
127 {
128 DWORD propertyType = 0;
129 BYTE property[BUFFER_SIZE] = {0};
130
131 ::SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData, SPDRP_FRIENDLYNAME, &propertyType, property, BUFFER_SIZE, nullptr);
132
133 stringstream sstr;
134 for (int k = 0; k < BUFFER_SIZE && property[k] != 0; ++k)
135 {
136 sstr << property[k];
137 }
138
139 const size_t idBegin = sstr.str().find_last_of('(') + 1;
140 const size_t idEnd = sstr.str().find_last_of(')');
141 assert(idEnd > idBegin);
142
143 const string name = sstr.str().substr(idBegin, idEnd - idBegin);
144
145 if ((name[0] && (sstr.str().compare(0, 19, "MSP Debug Interface") == 0 ))|| (name[0] && (sstr.str().compare(0, 19, "MSP-FET430UIF - CDC") == 0 )))
146 {
147 PortInfo portInfo(name, string("\\\\.\\")+name, PortInfo::CDC, retrieveSerialFromId(deviceId));
148 if (name[0] && (sstr.str().compare(0, 19, "MSP Debug Interface") == 0 ))
149 {
150 portInfo.useFlowControl = false;
151 portInfo.useCrc = false;
152 }
153 else if (name[0] && (sstr.str().compare(0, 19, "MSP-FET430UIF - CDC") == 0 ))
154 {
155 portInfo.useFlowControl = true;
156 portInfo.useCrc = true;
157 }
158
159 //if (open)
160 {
161 portInfo.status = UsbCdcIoChannel(portInfo).getStatus();
162 }
163 portList[portInfo.name] = portInfo;
164 }
165 }
166 }
167 ::SetupDiDestroyDeviceInfoList(hDevInfo);//free resources
168
169 #elif defined(__APPLE__)
170 CFMutableDictionaryRef matchingDict;
171 kern_return_t kernResult;
172 io_iterator_t iterator;
173
174 matchingDict = IOServiceMatching(kIOSerialBSDServiceValue);
175 CFDictionarySetValue(matchingDict, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes));
176 kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iterator);
177
178 if (kernResult != KERN_SUCCESS) {
179 return;
180 }
181
182 for (;;)
183 {
184 io_service_t device = IOIteratorNext(iterator);
185 if (device == 0)
186 {
187 if (!IOIteratorIsValid(iterator))
188 {
189 /*
190 * Apple documentation advises resetting the iterator if
191 * it should become invalid during iteration.
192 */
193 IOIteratorReset(iterator);
194 device = IOIteratorNext(iterator);
195 if (device == 0)
196 {
197 break;
198 }
199 }
200 else
201 {
202 break;
203 }
204 }
205
206 CFTypeRef bsdPathAsCFString = nullptr;
207 CFTypeRef vendorIdAsCFNumber = nullptr;
208 CFTypeRef productIdAsCFNumber = nullptr;
209 CFTypeRef ttyDeviceAsCFString = nullptr;
210 CFTypeRef interfaceNumberAsCFNumber = nullptr;
211
212 char ttyDevice[MAXNAMELEN];
213 SInt32 interfaceNumber;
214 char path[MAXPATHLEN];
215 SInt32 vID = 0;
216 SInt32 pID = 0;
217
218 // Get the name of the modem's callout device
219 bsdPathAsCFString = IORegistryEntryCreateCFProperty(device, CFSTR(kIOCalloutDeviceKey),
220 kCFAllocatorDefault, 0);
221
222 ttyDeviceAsCFString = IORegistryEntryCreateCFProperty(device, CFSTR(kIOTTYDeviceKey),
223 kCFAllocatorDefault, 0);
224
225 io_name_t name;
226 IORegistryEntryGetName(device, name);
227
228 // wander up the hierarchy until we find the level that can give us the
229 // vendor/product IDs and the product name, if available
230 io_registry_entry_t parent;
231 kern_return_t kernResult = IORegistryEntryGetParentEntry(device, kIOServicePlane, &parent);
232 IOObjectRelease(device);
233
234 while ( kernResult == KERN_SUCCESS && ( !vendorIdAsCFNumber || !productIdAsCFNumber || !interfaceNumberAsCFNumber) )
235 {
236 if (!vendorIdAsCFNumber)
237 {
238 vendorIdAsCFNumber = IORegistryEntrySearchCFProperty(parent,
239 kIOServicePlane,
240 CFSTR(kUSBVendorID),
241 kCFAllocatorDefault, 0);
242 }
243
244 if (!productIdAsCFNumber)
245 {
246 productIdAsCFNumber = IORegistryEntrySearchCFProperty(parent,
247 kIOServicePlane,
248 CFSTR(kUSBProductID),
249 kCFAllocatorDefault, 0);
250 }
251
252 if (!interfaceNumberAsCFNumber)
253 {
254 interfaceNumberAsCFNumber = IORegistryEntrySearchCFProperty(parent,
255 kIOServicePlane,
256 CFSTR(kUSBInterfaceNumber),
257 kCFAllocatorDefault, 0);
258 }
259
260 io_registry_entry_t oldparent = parent;
261 kernResult = IORegistryEntryGetParentEntry(parent, kIOServicePlane, &parent);
262 IOObjectRelease(oldparent);
263 }
264
265 if (interfaceNumberAsCFNumber)
266 {
267 CFNumberGetValue((CFNumberRef)interfaceNumberAsCFNumber, kCFNumberSInt32Type, &interfaceNumber);
268 CFRelease(interfaceNumberAsCFNumber);
269 if (interfaceNumber != 1)
270 {
271 continue;
272 }
273 }
274
275 if (ttyDeviceAsCFString)
276 {
277 CFStringGetCString((CFStringRef)ttyDeviceAsCFString, ttyDevice, PATH_MAX, kCFStringEncodingUTF8);
278 CFRelease(ttyDeviceAsCFString);
279 }
280
281 if (bsdPathAsCFString)
282 {
283 CFStringGetCString((CFStringRef)bsdPathAsCFString, path, PATH_MAX, kCFStringEncodingUTF8);
284 CFRelease(bsdPathAsCFString);
285 }
286
287 if (vendorIdAsCFNumber)
288 {
289 CFNumberGetValue((CFNumberRef)vendorIdAsCFNumber, kCFNumberSInt32Type, &vID);
290 CFRelease(vendorIdAsCFNumber);
291 }
292
293 if (productIdAsCFNumber)
294 {
295 CFNumberGetValue((CFNumberRef)productIdAsCFNumber, kCFNumberSInt32Type, &pID);
296 CFRelease(productIdAsCFNumber);
297 }
298
299 if ((vID == vendorId) && (pID == productId))
300 {
301 PortInfo portInfo(ttyDevice, path, PortInfo::CDC);
302 if (productId == 0x0010)
303 {
304 portInfo.useFlowControl = true;
305 portInfo.useCrc = true;
306 }
307 portInfo.status = UsbCdcIoChannel(portInfo).getStatus();
308 portList[portInfo.name] = portInfo;
309 }
310 }
311 #else
312 stringstream cdcIdStream;
313
314 #ifdef __FreeBSD__
315
316 path p( "/dev" );
317 if( exists(p) && is_directory(p) ) {
318
319 cdcIdStream << hex << setfill('0') << "mspfet" << setw(4) << productId;
320
321 const directory_iterator end;
322 for( directory_iterator it(p); it != end; ++it ) {
323
324 string dir = it->path().string();
325 if( dir.find( cdcIdStream.str() ) != string::npos ) {
326 {
327
328 #else
329 cdcIdStream << hex << setfill('0') << "usb:v" << setw(4) << vendorId << "p" << setw(4) << productId;
330
331 path p("/sys/class/tty/");
332 if (exists(p) && is_directory(p))
333 {
334 const directory_iterator end;
335 for (directory_iterator it(p); it != end; ++it)
336 {
337 string dir = it->path().string();
338 if (dir.find("ttyACM") != string::npos)
339 {
340 string modalias;
341 int interfaceNumber = -1;
342
343 std::ifstream modAliasStream((it->path()/"device/modalias").string().c_str());
344 modAliasStream >> modalias;
345
346 std::ifstream ifNumStream((it->path()/"device/bInterfaceNumber").string().c_str());
347 ifNumStream >> interfaceNumber;
348 if (modalias.find(cdcIdStream.str()) == 0 && interfaceNumber == 0)
349 {
350 #endif
351 const string filename = it->path().filename().string();
352 const string portPath = string("/dev/") + filename;
353
354 PortInfo portInfo(filename, portPath, PortInfo::CDC);
355
356 if (productId == 0x0010)
357 {
358 portInfo.useFlowControl = true;
359 portInfo.useCrc = true;
360 }
361
362 //if (open)
363 {
364 portInfo.status = UsbCdcIoChannel(portInfo).getStatus();
365 }
366 portList[portInfo.name] = portInfo;
367 }
368 }
369 }
370 }
371 #endif
372 }
373
374
375 void UsbCdcIoChannel::enumeratePorts (PortMap& portList, bool open)
376 {
377 createCdcPortList(0x2047, 0x0013, portList); //eZ-FET
378 createCdcPortList(0x2047, 0x0014, portList); //MSP-FET
379 createCdcPortList(0x2047, 0x0010, portList); //UIF
380 }
381
382 std::string UsbCdcIoChannel::retrieveSerialFromId(const std::string& id)
383 {
384 #if defined(_WIN32) || defined(_WIN64)
385 const size_t idBegin = id.find_last_of('\\') + 1;
386 return id.substr(idBegin, 16);
387 #else
388 const size_t begin = id.find_last_of('_') + 1;
389 const size_t end = id.find_last_of('-');
390 return id.substr( begin, end - begin );
391 #endif
392 }
393
394 bool UsbCdcIoChannel::openPort()
395 {
396 ioService = new boost::asio::io_service;
397 port = new boost::asio::serial_port(*ioService);
398 timer = new boost::asio::deadline_timer(*ioService);
399
400 boost::system::error_code ec = port->open(portInfo.path, ec);
401 if (ec != boost::system::error_condition(boost::system::errc::success))
402 {
403 int retry = 5;
404 while ((ec != boost::system::error_condition(boost::system::errc::success))
405 && (--retry ))
406 {
407 std::this_thread::sleep_for(std::chrono::milliseconds(5));
408 ec = port->open(portInfo.path, ec);
409 }
410
411 if (ec == boost::system::error_condition(boost::system::errc::permission_denied))
412 {
413 portInfo.status = PortInfo::inUseByAnotherInstance;
414 }
415 if (ec != boost::system::error_condition(boost::system::errc::success))
416 {
417 close();
418 return false;
419 }
420 }
421 return true;
422 }
423
424 void UsbCdcIoChannel::retrieveStatus()
425 {
426 portInfo.status = PortInfo::freeForUse;
427
428 if (!isOpen())
429 {
430 openPort();
431 //Seeing issues on some platforms (eg. Ubuntu) when port is immediately closed again
432 std::this_thread::sleep_for(std::chrono::milliseconds(100));
433 close();
434 }
435 }
436
437
438 bool UsbCdcIoChannel::open()
439 {
440 if (!isOpen() && !openPort())
441 {
442 return false;
443 }
444
445 portInfo.status = PortInfo::freeForUse;
446
447 try
448 {
449 const int baudrate = 460800;
450
451 #if defined(__APPLE__)
452 // Vanilla boost does not provide method to set non-standard baudrates,
453 // so we have to set it low-level
454 if (ioctl (port->native_handle(), _IOW('T', 2, speed_t), &baudrate, 1) < 0)
455 {
456 return false;
457 }
458 #else
459 port->set_option( serial_port::baud_rate( baudrate ) );
460 #endif
461 port->set_option( serial_port::flow_control( serial_port::flow_control::none ) );
462 port->set_option( serial_port::parity( serial_port::parity::none ) );
463 port->set_option( serial_port::stop_bits( serial_port::stop_bits::one ) );
464 port->set_option( serial_port::character_size(8) );
465 }
466 catch (const boost::system::system_error&)
467 {
468 return false;
469 }
470
471 return true;
472 }
473
474 void UsbCdcIoChannel::cleanup()
475 {
476 if (isOpen())
477 {
478 boost::system::error_code ec = port->close(ec);
479 }
480 delete timer;
481 timer = 0;
482 delete port;
483 port = 0;
484 delete ioService;
485 ioService = 0;
486 }
487
488 bool UsbCdcIoChannel::close()
489 {
490 cleanup();
491 return true;
492 }
493
494
495 bool UsbCdcIoChannel::isOpen() const
496 {
497 return port && port->is_open();
498 }
499
500
501 void UsbCdcIoChannel::cancel()
502 {
503 cancelled = true;
504
505 boost::system::error_code ec;
506 if (timer && timer->expires_from_now(boost::posix_time::milliseconds(0), ec) > 0)
507 {
508 timer->async_wait(std::bind(&UsbCdcIoChannel::onTimer, this, std::placeholders::_1));
509 }
510 }
511
512
513 void UsbCdcIoChannel::setTimer(uint32_t duration)
514 {
515 timerEvent = false;
516
517 if (timer)
518 {
519 boost::system::error_code ec;
520 timer->expires_from_now(boost::posix_time::milliseconds(duration), ec);
521 timer->async_wait(bind(&UsbCdcIoChannel::onTimer, this, std::placeholders::_1));
522 }
523 }
524
525
526 void UsbCdcIoChannel::startRead(size_t offset, size_t numBytes)
527 {
528 bytesReceived = 0;
529 readEvent = false;
530 async_read(*port, buffer(&inputBuffer[offset], numBytes), bind(&UsbCdcIoChannel::onRead, this, std::placeholders::_1, std::placeholders::_2));
531 }
532
533
534 void UsbCdcIoChannel::onTimer(const boost::system::error_code& ec)
535 {
536 timerEvent = (ec != error::operation_aborted);
537 }
538
539
540 void UsbCdcIoChannel::onRead(const boost::system::error_code& ec, size_t numBytes)
541 {
542 readEvent = (ec != error::operation_aborted);
543 bytesReceived = numBytes;
544 }
545
546
547 size_t UsbCdcIoChannel::read(HalResponse& resp)
548 {
549 if (!isOpen())
550 return 0;
551
552 size_t actSize = 0;
553 size_t expSize = 1;
554
555 setTimer(1000);
556 startRead(0, expSize);
557
558 boost::system::error_code ec;
559
560 while (ioService->run_one(ec))
561 {
562 if (readEvent)
563 {
564 if (bytesReceived > 0)
565 {
566 if (actSize == 0)
567 expSize = inputBuffer[0] + ( (inputBuffer[0] & 0x01) ? 3 : 4);
568
569 actSize += bytesReceived;
570
571 if (actSize == expSize)
572 {
573 timer->cancel(ec);
574 break;
575 }
576 }
577
578 startRead(actSize, expSize - actSize);
579 }
580
581 else if (timerEvent)
582 {
583 if (wasUnplugged() || cancelled)
584 {
585 cancelled = false;
586 port->cancel(ec);
587 break;
588 }
589
590 setTimer(1000);
591 }
592
593 if (ioService->stopped())
594 {
595 ioService->reset();
596 }
597 }
598
599 //Let cancelled tasks finish
600 ioService->run(ec);
601 ioService->reset();
602
603
604 if (actSize == expSize)
605 {
606 processMessage(actSize, resp);
607 return actSize;
608 }
609 return 0;
610 }
611
612
613 bool UsbCdcIoChannel::wasUnplugged()
614 {
615 #if defined(_WIN32) || defined(_WIN64)
616 boost::system::error_code ec = serial_port(*ioService).open(portInfo.path, ec);
617 if (ec == boost::system::error_condition(boost::system::errc::no_such_file_or_directory))
618 {
619 comState = ComStateDisconnect;
620 }
621 #else
622 /*
623 * Workaround for El Capitan with UIF: the above branch triggers sending of SET_LINE_CODING
624 * Do not trigger SET_LINE_CODING here
625 */
626
627 /* Also workaround for Ubuntu 16 with UIF: the above branch triggers sending of SetControlLineState, which we don't want here */
628
629 struct stat dev;
630 if (stat(portInfo.path.c_str(), &dev) != 0)
631 {
632 comState = ComStateDisconnect;
633 }
634 #endif
635 return comState == ComStateDisconnect;
636 }
637
638
639 void UsbCdcIoChannel::processMessage(size_t msgSize, HalResponse& resp)
640 {
641 #ifdef DB_PRINT
642 Logging::DefaultLogger().PrintReceiveBuffer(&inputBuffer[0], static_cast<long>(msgSize));
643 #endif // DB_PRINT
644
645 if (portInfo.useCrc)
646 {
647 const uint16_t expCrc = createCrc(&inputBuffer[0]);
648 const uint16_t actCrc = (inputBuffer[msgSize-1] << 8) | inputBuffer[msgSize-2];
649 if (actCrc != expCrc)
650 {
651 resp.setError(HalResponse::Error_CRC);
652 }
653 }
654 resp.setType(inputBuffer[1]);
655 resp.setId(inputBuffer[2] & 0x7f); //Don't mask async bit (0x40)
656 resp.setIsComplete(inputBuffer[2]);
657
658 if (msgSize >= 2)
659 {
660 resp.append(&inputBuffer[1], inputBuffer[0]);
661 }
662 }
663
664
665 enum ComState UsbCdcIoChannel::poll()
666 {
667 return comState;
668 }
669
670
671 size_t UsbCdcIoChannel::write(const uint8_t* payload, size_t len)
672 {
673 if (!isOpen())
674 return 0;
675
676 const size_t ret_len = len;
677
678 uint8_t report[256] = {0};
679
680 if (payload)
681 memcpy(report, payload, len);
682
683 // test for fill byte
684 if (!(report[0] & 0x01))
685 report[len++] = 0x00;
686
687 if (portInfo.useCrc)
688 {
689 //create crc and append it to data
690 uint16_t crc = createCrc(report);
691
692 report[len++] = crc & 0x00ff;
693 report[len++] = (crc & 0xff00) >> 8;
694 }
695
696 size_t n_write = 0;
697 uint8_t send_buf[512];
698
699 if (portInfo.useFlowControl)
700 {
701 //mask XOFF, XON and MASK in data stream
702 size_t j = 0;
703
704 for (size_t i = 0; i < len; i++)
705 {
706 const uint8_t ch = report[i];
707 switch (ch)
708 {
709 case XOFF:
710 case XON:
711 case XMASK:
712 send_buf[j] = XMASK;
713 j++;
714 send_buf[j] = ch & 0x3;
715 break;
716 default:
717 send_buf[j] = ch;
718 }
719 j++;
720 }
721 n_write = j;
722 }
723 else
724 {
725 n_write = len;
726 memcpy(send_buf, report, n_write);
727 }
728
729 #ifdef DB_PRINT
730 Logging::DefaultLogger().PrintSendBuffer(send_buf, static_cast<long>(n_write));
731 #endif // DB_PRINT
732
733 boost::system::error_code ec;
734 const size_t nWritten = boost::asio::write(*port, buffer(send_buf, n_write), transfer_all(), ec);
735
736 if (nWritten != n_write)
737 {
738 return 0;
739 }
740
741 return ret_len;
742 }
743
744 const char* UsbCdcIoChannel::getName() const
745 {
746 return portInfo.name.c_str();
747 }
748
749 string UsbCdcIoChannel::getSerial() const
750 {
751 return portInfo.serial;
752 }
753
754 PortInfo::Status UsbCdcIoChannel::getStatus() const
755 {
756 return portInfo.status;
757 }
758