diff options
Diffstat (limited to 'Source/serial.cpp')
-rwxr-xr-x | Source/serial.cpp | 905 |
1 files changed, 905 insertions, 0 deletions
diff --git a/Source/serial.cpp b/Source/serial.cpp new file mode 100755 index 0000000..67e9be3 --- /dev/null +++ b/Source/serial.cpp @@ -0,0 +1,905 @@ +/* Serial port object for use with wxWidgets + * Copyright 2010, Paul Stoffregen (paul@pjrc.com) + * Modified by: Leonardo Laguna Ruiz + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "serial.h" + + +#if defined(LINUX) + #include <sys/types.h> + #include <fcntl.h> + #include <errno.h> + #include <sys/select.h> + #include <termios.h> + #include <unistd.h> + #include <dirent.h> + #include <sys/stat.h> + #include <sys/ioctl.h> + #include <linux/serial.h> + #include <errno.h> + #include <stdio.h> + #include <string.h> +#elif defined(MACOSX) + #include <stdio.h> + #include <string.h> + #include <unistd.h> + #include <fcntl.h> + #include <sys/ioctl.h> + #include <errno.h> + #include <paths.h> + #include <termios.h> + #include <sysexits.h> + #include <sys/param.h> + #include <sys/select.h> + #include <sys/time.h> + #include <time.h> + #include <CoreFoundation/CoreFoundation.h> + #include <IOKit/IOKitLib.h> + #include <IOKit/serial/IOSerialKeys.h> + #include <IOKit/IOBSD.h> +#elif defined(WINDOWS) + #include <windows.h> + #define win32_err(s) FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, \ + GetLastError(), 0, (s), sizeof(s), NULL) + #define QUERYDOSDEVICE_BUFFER_SIZE 262144 + #if _MSC_VER + #define snprintf _snprintf_s + #endif +#else + #error "This platform is unsupported, sorry" +#endif + + + + +Serial::Serial() +{ + port_is_open = 0; + baud_rate = 38400; // default baud rate +} + +Serial::~Serial() +{ + Close(); +} + + + +// Open a port, by name. Return 0 on success, non-zero for error +int Serial::Open(const string& name) +{ + Close(); +#if defined(LINUX) + struct serial_struct kernel_serial_settings; + int bits; + port_fd = open(name.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + if (port_fd < 0) { + if (errno == EACCES) { + error_msg = "Unable to access " + name + ", insufficient permission"; + // TODO: we could look at the permission bits and owner + // to make a better message here + } else if (errno == EISDIR) { + error_msg = "Unable to open " + name + + ", Object is a directory, not a serial port"; + } else if (errno == ENODEV || errno == ENXIO) { + error_msg = "Unable to open " + name + + ", Serial port hardware not installed"; + } else if (errno == ENOENT) { + error_msg = "Unable to open " + name + + ", Device name does not exist"; + } else { + error_msg = "Unable to open " + name + + ", " + strerror(errno); + } + return -1; + } + if (ioctl(port_fd, TIOCMGET, &bits) < 0) { + close(port_fd); + error_msg = "Unable to query serial port signals"; + return -1; + } + bits &= ~(TIOCM_DTR | TIOCM_RTS); + if (ioctl(port_fd, TIOCMSET, &bits) < 0) { + close(port_fd); + error_msg = "Unable to control serial port signals"; + return -1; + } + if (tcgetattr(port_fd, &settings_orig) != 0) { + close(port_fd); + error_msg = "Unable to query serial port settings (perhaps not a serial port)"; + return -1; + } + memset(&settings, 0, sizeof(settings)); + settings.c_iflag = IGNBRK | IGNPAR; + settings.c_cflag = CS8 | CREAD | HUPCL | CLOCAL; + Set_baud(baud_rate); + if (ioctl(port_fd, TIOCGSERIAL, &kernel_serial_settings) == 0) { + kernel_serial_settings.flags |= ASYNC_LOW_LATENCY; + ioctl(port_fd, TIOCSSERIAL, &kernel_serial_settings); + } + tcflush(port_fd, TCIFLUSH); +#elif defined(MACOSX) + int bits; + port_fd = open(name.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + if (port_fd < 0) { + error_msg = "Unable to open " + name + ", " + strerror(errno); + return -1; + } + if (ioctl(port_fd, TIOCEXCL) == -1) { + close(port_fd); + error_msg = "Unable to get exclussive access to port " + name; + return -1; + } + if (ioctl(port_fd, TIOCMGET, &bits) < 0) { + close(port_fd); + error_msg = "Unable to query serial port signals on " + name; + return -1; + } + bits &= ~(TIOCM_DTR | TIOCM_RTS); + if (ioctl(port_fd, TIOCMSET, &bits) < 0) { + close(port_fd); + error_msg = "Unable to control serial port signals on " + name; + return -1; + } + if (tcgetattr(port_fd, &settings_orig) < 0) { + close(port_fd); + error_msg = "Unable to access baud rate on port " + name; + return -1; + } + memset(&settings, 0, sizeof(settings)); + settings.c_cflag = CS8 | CLOCAL | CREAD | HUPCL; + settings.c_iflag = IGNBRK | IGNPAR; + Set_baud(baud_rate); + tcflush(port_fd, TCIFLUSH); +#elif defined(WINDOWS) + COMMCONFIG cfg; + COMMTIMEOUTS timeouts; + int got_default_cfg=0, port_num; + char buf[1024], name_createfile[64], name_commconfig[64], *p; + DWORD len; + + snprintf(buf, sizeof(buf), "%s", name.c_str()); + p = strstr(buf, "COM"); + if (p && sscanf(p + 3, "%d", &port_num) == 1) { + //printf("port_num = %d\n", port_num); + snprintf(name_createfile, sizeof(name_createfile), "\\\\.\\COM%d", port_num); + snprintf(name_commconfig, sizeof(name_commconfig), "COM%d", port_num); + } else { + snprintf(name_createfile, sizeof(name_createfile), "%s", name.c_str()); + snprintf(name_commconfig, sizeof(name_commconfig), "%s", name.c_str()); + } + len = sizeof(COMMCONFIG); + if (GetDefaultCommConfig(name_commconfig, &cfg, &len)) { + // this prevents unintentionally raising DTR when opening + // might only work on COM1 to COM9 + got_default_cfg = 1; + memcpy(&port_cfg_orig, &cfg, sizeof(COMMCONFIG)); + cfg.dcb.fDtrControl = DTR_CONTROL_DISABLE; + cfg.dcb.fRtsControl = RTS_CONTROL_DISABLE; + SetDefaultCommConfig(name_commconfig, &cfg, sizeof(COMMCONFIG)); + } else { + printf("error with GetDefaultCommConfig\n"); + } + port_handle = CreateFile(name_createfile, GENERIC_READ | GENERIC_WRITE, + 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (port_handle == INVALID_HANDLE_VALUE) { + win32_err(buf); + error_msg = "Unable to open " + name + ", " + buf; + return -1; + } + len = sizeof(COMMCONFIG); + if (!GetCommConfig(port_handle, &port_cfg, &len)) { + CloseHandle(port_handle); + win32_err(buf); + error_msg = "Unable to read communication config on " + name + ", " + buf; + return -1; + } + if (!got_default_cfg) { + memcpy(&port_cfg_orig, &port_cfg, sizeof(COMMCONFIG)); + } + // http://msdn2.microsoft.com/en-us/library/aa363188(VS.85).aspx + port_cfg.dcb.BaudRate = baud_rate; + port_cfg.dcb.fBinary = TRUE; + port_cfg.dcb.fParity = FALSE; + port_cfg.dcb.fOutxCtsFlow = FALSE; + port_cfg.dcb.fOutxDsrFlow = FALSE; + port_cfg.dcb.fDtrControl = DTR_CONTROL_ENABLE; + port_cfg.dcb.fDsrSensitivity = FALSE; + port_cfg.dcb.fTXContinueOnXoff = TRUE; // ??? + port_cfg.dcb.fOutX = FALSE; + port_cfg.dcb.fInX = FALSE; + port_cfg.dcb.fErrorChar = FALSE; + port_cfg.dcb.fNull = FALSE; + port_cfg.dcb.fRtsControl = RTS_CONTROL_DISABLE; + port_cfg.dcb.fAbortOnError = FALSE; + port_cfg.dcb.ByteSize = 8; + port_cfg.dcb.Parity = NOPARITY; + port_cfg.dcb.StopBits = ONESTOPBIT; + if (!SetCommConfig(port_handle, &port_cfg, sizeof(COMMCONFIG))) { + CloseHandle(port_handle); + win32_err(buf); + error_msg = "Unable to write communication config to " + name + ", " + buf; + return -1; + } + if (!EscapeCommFunction(port_handle, CLRDTR | CLRRTS)) { + CloseHandle(port_handle); + win32_err(buf); + error_msg = "Unable to control serial port signals on " + name + ", " + buf; + return -1; + } + // http://msdn2.microsoft.com/en-us/library/aa363190(VS.85).aspx + // setting to all zeros means timeouts are not used + //timeouts.ReadIntervalTimeout = 0; + timeouts.ReadIntervalTimeout = MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + if (!SetCommTimeouts(port_handle, &timeouts)) { + CloseHandle(port_handle); + win32_err(buf); + error_msg = "Unable to write timeout settings to " + name + ", " + buf; + return -1; + } +#endif + port_name = name; + port_is_open = 1; + return 0; +} + +string Serial::get_name(void) +{ + if (!port_is_open) return ""; + return port_name; +} + + + +// Return the last error message in a (hopefully) user friendly format +string Serial::error_message(void) +{ + return error_msg; +} + +int Serial::Is_open(void) +{ + return port_is_open; +} + +// Close the port +void Serial::Close(void) +{ + if (!port_is_open) return; + Output_flush(); + Input_discard(); + port_is_open = 0; + port_name = ""; +#if defined(LINUX) || defined(MACOSX) + tcsetattr(port_fd, TCSANOW, &settings_orig); + close(port_fd); +#elif defined(WINDOWS) + //SetCommConfig(port_handle, &port_cfg_orig, sizeof(COMMCONFIG)); + CloseHandle(port_handle); +#endif +} + + + + + + +// set the baud rate +int Serial::Set_baud(int baud) +{ + if (baud <= 0) return -1; + //printf("set_baud: %d\n", baud); + baud_rate = baud; + if (!port_is_open) return -1; +#if defined(LINUX) + speed_t spd; + switch (baud) { + case 230400: spd = B230400; break; + case 115200: spd = B115200; break; + case 57600: spd = B57600; break; + case 38400: spd = B38400; break; + case 19200: spd = B19200; break; + case 9600: spd = B9600; break; + case 4800: spd = B4800; break; + case 2400: spd = B2400; break; + case 1800: spd = B1800; break; + case 1200: spd = B1200; break; + case 600: spd = B600; break; + case 300: spd = B300; break; + case 200: spd = B200; break; + case 150: spd = B150; break; + case 134: spd = B134; break; + case 110: spd = B110; break; + case 75: spd = B75; break; + case 50: spd = B50; break; + #ifdef B460800 + case 460800: spd = B460800; break; + #endif + #ifdef B500000 + case 500000: spd = B500000; break; + #endif + #ifdef B576000 + case 576000: spd = B576000; break; + #endif + #ifdef B921600 + case 921600: spd = B921600; break; + #endif + #ifdef B1000000 + case 1000000: spd = B1000000; break; + #endif + #ifdef B1152000 + case 1152000: spd = B1152000; break; + #endif + #ifdef B1500000 + case 1500000: spd = B1500000; break; + #endif + #ifdef B2000000 + case 2000000: spd = B2000000; break; + #endif + #ifdef B2500000 + case 2500000: spd = B2500000; break; + #endif + #ifdef B3000000 + case 3000000: spd = B3000000; break; + #endif + #ifdef B3500000 + case 3500000: spd = B3500000; break; + #endif + #ifdef B4000000 + case 4000000: spd = B4000000; break; + #endif + #ifdef B7200 + case 7200: spd = B7200; break; + #endif + #ifdef B14400 + case 14400: spd = B14400; break; + #endif + #ifdef B28800 + case 28800: spd = B28800; break; + #endif + #ifdef B76800 + case 76800: spd = B76800; break; + #endif + default: { + return -1; + } + } + cfsetospeed(&settings, spd); + cfsetispeed(&settings, spd); + if (tcsetattr(port_fd, TCSANOW, &settings) < 0) return -1; +#elif defined(MACOSX) + cfsetospeed(&settings, (speed_t)baud); + cfsetispeed(&settings, (speed_t)baud); + if (tcsetattr(port_fd, TCSANOW, &settings) < 0) return -1; +#elif defined(WINDOWS) + DWORD len = sizeof(COMMCONFIG); + port_cfg.dcb.BaudRate = baud; + SetCommConfig(port_handle, &port_cfg, len); +#endif + return 0; +} + +int Serial::Set_baud(const string& baud_str) +{ + unsigned long b; + b = atoi(baud_str.c_str()); + if (!b) return -1; + return Set_baud((int)b); +} + + +// Read from the serial port. Returns only the bytes that are +// already received, up to count. This always returns without delay, +// returning 0 if nothing has been received +int Serial::Read(void *ptr, int count) +{ + if (!port_is_open) return -1; + if (count <= 0) return 0; +#if defined(LINUX) + int n, bits; + n = read(port_fd, ptr, count); + if (n < 0 && (errno == EAGAIN || errno == EINTR)) return 0; + if (n == 0 && ioctl(port_fd, TIOCMGET, &bits) < 0) return -99; + return n; +#elif defined(MACOSX) + int n; + n = read(port_fd, ptr, count); + if (n < 0 && (errno == EAGAIN || errno == EINTR)) return 0; + return n; +#elif defined(WINDOWS) + // first, we'll find out how many bytes have been received + // and are currently waiting for us in the receive buffer. + // http://msdn.microsoft.com/en-us/library/ms885167.aspx + // http://msdn.microsoft.com/en-us/library/ms885173.aspx + // http://source.winehq.org/WineAPI/ClearCommError.html + COMSTAT st; + DWORD errmask=0, num_read, num_request; + OVERLAPPED ov; + int r; + if (!ClearCommError(port_handle, &errmask, &st)) return -1; + //printf("Read, %d requested, %lu buffered\n", count, st.cbInQue); + if (st.cbInQue <= 0) return 0; + // now do a ReadFile, now that we know how much we can read + // a blocking (non-overlapped) read would be simple, but win32 + // is all-or-nothing on async I/O and we must have it enabled + // because it's the only way to get a timeout for WaitCommEvent + num_request = ((DWORD)count < st.cbInQue) ? (DWORD)count : st.cbInQue; + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (ov.hEvent == NULL) return -1; + ov.Internal = ov.InternalHigh = 0; + ov.Offset = ov.OffsetHigh = 0; + if (ReadFile(port_handle, ptr, num_request, &num_read, &ov)) { + // this should usually be the result, since we asked for + // data we knew was already buffered + //printf("Read, immediate complete, num_read=%lu\n", num_read); + r = num_read; + } else { + if (GetLastError() == ERROR_IO_PENDING) { + if (GetOverlappedResult(port_handle, &ov, &num_read, TRUE)) { + //printf("Read, delayed, num_read=%lu\n", num_read); + r = num_read; + } else { + //printf("Read, delayed error\n"); + r = -1; + } + } else { + //printf("Read, error\n"); + r = -1; + } + } + CloseHandle(ov.hEvent); + // TODO: how much overhead does creating new event objects on + // every I/O request really cost? Would it be better to create + // just 3 when we open the port, and reset/reuse them every time? + // Would mutexes or critical sections be needed to protect them? + return r; +#endif +} + +// Write to the serial port. This blocks until the data is +// sent (or in a buffer to be sent). All bytes are written, +// unless there is some sort of error. +int Serial::Write(const void *ptr, int len) +{ + //printf("Write %d\n", len); + if (!port_is_open) return -1; +#if defined(LINUX) || defined(MACOSX) + int n, written=0; + fd_set wfds; + struct timeval tv; + while (written < len) { + n = write(port_fd, (const char *)ptr + written, len - written); + if (n < 0 && (errno == EAGAIN || errno == EINTR)) n = 0; + //printf("Write, n = %d\n", n); + if (n < 0) return -1; + if (n > 0) { + written += n; + } else { + tv.tv_sec = 10; + tv.tv_usec = 0; + FD_ZERO(&wfds); + FD_SET(port_fd, &wfds); + n = select(port_fd+1, NULL, &wfds, NULL, &tv); + if (n < 0 && errno == EINTR) n = 1; + if (n <= 0) return -1; + } + } + return written; +#elif defined(WINDOWS) + DWORD num_written; + OVERLAPPED ov; + int r; + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (ov.hEvent == NULL) return -1; + ov.Internal = ov.InternalHigh = 0; + ov.Offset = ov.OffsetHigh = 0; + if (WriteFile(port_handle, ptr, len, &num_written, &ov)) { + //printf("Write, immediate complete, num_written=%lu\n", num_written); + r = num_written; + } else { + if (GetLastError() == ERROR_IO_PENDING) { + if (GetOverlappedResult(port_handle, &ov, &num_written, TRUE)) { + //printf("Write, delayed, num_written=%lu\n", num_written); + r = num_written; + } else { + //printf("Write, delayed error\n"); + r = -1; + } + } else { + //printf("Write, error\n"); + r = -1; + } + }; + CloseHandle(ov.hEvent); + return r; +#endif +} + +// Wait up to msec for data to become available for reading. +// return 0 if timeout, or non-zero if one or more bytes are +// received and can be read. -1 if an error occurs +int Serial::Input_wait(int msec) +{ + if (!port_is_open) return -1; +#if defined(LINUX) || defined(MACOSX) + fd_set rfds; + struct timeval tv; + tv.tv_sec = msec / 1000; + tv.tv_usec = (msec % 1000) * 1000; + FD_ZERO(&rfds); + FD_SET(port_fd, &rfds); + return select(port_fd+1, &rfds, NULL, NULL, &tv); +#elif defined(WINDOWS) + // http://msdn2.microsoft.com/en-us/library/aa363479(VS.85).aspx + // http://msdn2.microsoft.com/en-us/library/aa363424(VS.85).aspx + // http://source.winehq.org/WineAPI/WaitCommEvent.html + COMSTAT st; + DWORD errmask=0, eventmask=EV_RXCHAR, ret; + OVERLAPPED ov; + int r; + // first, request comm event when characters arrive + if (!SetCommMask(port_handle, EV_RXCHAR)) return -1; + // look if there are characters in the buffer already + if (!ClearCommError(port_handle, &errmask, &st)) return -1; + //printf("Input_wait, %lu buffered, timeout = %d ms\n", st.cbInQue, msec); + if (st.cbInQue > 0) return 1; + + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (ov.hEvent == NULL) return -1; + ov.Internal = ov.InternalHigh = 0; + ov.Offset = ov.OffsetHigh = 0; + if (WaitCommEvent(port_handle, &eventmask, &ov)) { + //printf("Input_wait, WaitCommEvent, immediate success\n"); + r = 1; + } else { + if (GetLastError() == ERROR_IO_PENDING) { + ret = WaitForSingleObject(ov.hEvent, msec); + if (ret == WAIT_OBJECT_0) { + //printf("Input_wait, WaitCommEvent, delayed success\n"); + r = 1; + } else if (ret == WAIT_TIMEOUT) { + //printf("Input_wait, WaitCommEvent, timeout\n"); + GetCommMask(port_handle, &eventmask); + r = 0; + } else { // WAIT_FAILED or WAIT_ABANDONED + //printf("Input_wait, WaitCommEvent, delayed error\n"); + r = -1; + } + } else { + //printf("Input_wait, WaitCommEvent, immediate error\n"); + r = -1; + } + } + SetCommMask(port_handle, 0); + CloseHandle(ov.hEvent); + return r; +#endif +} + +// Discard all received data that hasn't been read +void Serial::Input_discard(void) +{ + if (!port_is_open) return; +#if defined(LINUX) || defined(MACOSX) + // does this really work properly (and is it thread safe) on Linux?? + tcflush(port_fd, TCIFLUSH); +#elif defined(WINDOWS) + PurgeComm(port_handle, PURGE_RXCLEAR); +#endif +} + +// Wait for all transmitted data with Write to actually leave the serial port +void Serial::Output_flush(void) +{ + if (!port_is_open) return; +#if defined(LINUX) || defined(MACOSX) + tcdrain(port_fd); +#elif defined(WINDOWS) + FlushFileBuffers(port_handle); +#endif +} + + +// set DTR and RTS, 0 = low, 1=high, -1=unchanged +int Serial::Set_control(int dtr, int rts) +{ + if (!port_is_open) return -1; +#if defined(LINUX) || defined(MACOSX) + // on Mac OS X, "man 4 tty" + // on Linux, where is this actually documented? + int bits; + if (ioctl(port_fd, TIOCMGET, &bits) < 0) return -1; + if (dtr == 1) { + bits |= TIOCM_DTR; + } else if (dtr == 0) { + bits &= ~TIOCM_DTR; + } + if (rts == 1) { + bits |= TIOCM_RTS; + } else if (rts == 0) { + bits &= ~TIOCM_RTS; + } + if (ioctl(port_fd, TIOCMSET, &bits) < 0) return -1;; +#elif defined(WINDOWS) + // http://msdn.microsoft.com/en-us/library/aa363254(VS.85).aspx + if (dtr == 1) { + if (!EscapeCommFunction(port_handle, SETDTR)) return -1; + } else if (dtr == 0) { + if (!EscapeCommFunction(port_handle, CLRDTR)) return -1; + } + if (rts == 1) { + if (!EscapeCommFunction(port_handle, SETRTS)) return -1; + } else if (rts == 0) { + if (!EscapeCommFunction(port_handle, CLRRTS)) return -1; + } +#endif + return 0; +} + + + +#if defined(LINUX) +// All linux serial port device names. Hopefully all of them anyway. This +// is a long list, but each entry takes only a few bytes and a quick strcmp() +static const char *devnames[] = { +"S", // "normal" Serial Ports - MANY drivers using this +"USB", // USB to serial converters +"ACM", // USB serial modem, CDC class, Abstract Control Model +"MI", // MOXA Smartio/Industio family multiport serial... nice card, I have one :-) +"MX", // MOXA Intellio family multiport serial +"C", // Cyclades async multiport, no longer available, but I have an old ISA one! :-) +"D", // Digiboard (still in 2.6 but no longer supported), new Moschip MCS9901 +"P", // Hayes ESP serial cards (obsolete) +"M", // PAM Software's multimodem & Multitech ISI-Cards +"E", // Stallion intelligent multiport (no longer made) +"L", // RISCom/8 multiport serial +"W", // specialix IO8+ multiport serial +"X", // Specialix SX series cards, also SI & XIO series +"SR", // Specialix RIO serial card 257+ +"n", // Digi International Neo (yes lowercase 'n', drivers/serial/jsm/jsm_driver.c) +"FB", // serial port on the 21285 StrongArm-110 core logic chip +"AM", // ARM AMBA-type serial ports (no DTR/RTS) +"AMA", // ARM AMBA-type serial ports (no DTR/RTS) +"AT", // Atmel AT91 / AT32 Serial ports +"BF", // Blackfin 5xx serial ports (Analog Devices embedded DSP chips) +"CL", // CLPS711x serial ports (ARM processor) +"A", // ICOM Serial +"SMX", // Motorola IMX serial ports +"SOIC", // ioc3 serial +"IOC", // ioc4 serial +"PSC", // Freescale MPC52xx PSCs configured as UARTs +"MM", // MPSC (UART mode) on Marvell GT64240, GT64260, MV64340... +"B", // Mux console found in some PA-RISC servers +"NX", // NetX serial port +"PZ", // PowerMac Z85c30 based ESCC cell found in the "macio" ASIC +"SAC", // Samsung S3C24XX onboard UARTs +"SA", // SA11x0 serial ports +"AM", // KS8695 serial ports & Sharp LH7A40X embedded serial ports +"TX", // TX3927/TX4927/TX4925/TX4938 internal SIO controller +"SC", // Hitachi SuperH on-chip serial module +"SG", // C-Brick Serial Port (and console) SGI Altix machines +"HV", // SUN4V hypervisor console +"UL", // Xilinx uartlite serial controller +"VR", // NEC VR4100 series Serial Interface Unit +"CPM", // CPM (SCC/SMC) serial ports; core driver +"Y", // Amiga A2232 board +"SL", // Microgate SyncLink ISA and PCI high speed multiprotocol serial +"SLG", // Microgate SyncLink GT (might be sync HDLC only?) +"SLM", // Microgate SyncLink Multiport high speed multiprotocol serial +"CH", // Chase Research AT/PCI-Fast serial card +"F", // Computone IntelliPort serial card +"H", // Chase serial card +"I", // virtual modems +"R", // Comtrol RocketPort +"SI", // SmartIO serial card +"T", // Technology Concepts serial card +"V" // Comtrol VS-1000 serial controller +}; +#define NUM_DEVNAMES (sizeof(devnames) / sizeof(const char *)) +#endif + + + +#if defined(MACOSX) +static void macos_ports(io_iterator_t * PortIterator, vector<string>& list) +{ + io_object_t modemService; + CFTypeRef nameCFstring; + char s[MAXPATHLEN]; + + while ((modemService = IOIteratorNext(*PortIterator))) { + nameCFstring = IORegistryEntryCreateCFProperty(modemService, + CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0); + if (nameCFstring) { + if (CFStringGetCString((const __CFString *)nameCFstring, + s, sizeof(s), kCFStringEncodingASCII)) { + list.push_back(s); + } + CFRelease(nameCFstring); + } + IOObjectRelease(modemService); + } +} +#endif + + +// Return a list of all serial ports +vector<string> Serial::port_list() +{ + vector<string> list; +#if defined(LINUX) + // This is ugly guessing, but Linux doesn't seem to provide anything else. + // If there really is an API to discover serial devices on Linux, please + // email paul@pjrc.com with the info. Please? + // The really BAD aspect is all ports get DTR raised briefly, because linux + // has no way to open the port without raising DTR, and there isn't any way + // to tell if the device file really represents hardware without opening it. + // maybe sysfs or udev provides a useful API?? + DIR *dir; + struct dirent *f; + struct stat st; + unsigned int i, len[NUM_DEVNAMES]; + char s[512]; + int fd, bits; + termios mytios; + + dir = opendir("/dev/"); + if (dir == NULL) return list; + for (i=0; i<NUM_DEVNAMES; i++) len[i] = strlen(devnames[i]); + // Read all the filenames from the /dev directory... + while ((f = readdir(dir)) != NULL) { + // ignore everything that doesn't begin with "tty" + if (strncmp(f->d_name, "tty", 3)) continue; + // ignore anything that's not a known serial device name + for (i=0; i<NUM_DEVNAMES; i++) { + if (!strncmp(f->d_name + 3, devnames[i], len[i])) break; + } + if (i >= NUM_DEVNAMES) continue; + snprintf(s, sizeof(s), "/dev/%s", f->d_name); + // check if it's a character type device (almost certainly is) + if (stat(s, &st) != 0 || !(st.st_mode & S_IFCHR)) continue; + // now see if we can open the file - if the device file is + // populating /dev but doesn't actually represent a loaded + // driver, this is where we will detect it. + fd = open(s, O_RDONLY | O_NOCTTY | O_NONBLOCK); + if (fd < 0) { + // if permission denied, give benefit of the doubt + // (otherwise the port will be invisible to the user + // and we won't have a to alert them to the permssion + // problem) + if (errno == EACCES) list.push_back(s); + // any other error, assume it's not a real device + continue; + } + // does it respond to termios requests? (probably will since + // the name began with tty). Some devices where a single + // driver exports multiple names will open but this is where + // we can really tell if they work with real hardare. + if (tcgetattr(fd, &mytios) != 0) { + close(fd); + continue; + } + // does it respond to reading the control signals? If it's + // some sort of non-serial terminal (eg, pseudo terminals) + // this is where we will detect it's not really a serial port + if (ioctl(fd, TIOCMGET, &bits) < 0) { + close(fd); + continue; + } + // it passed all the tests, it's a serial port, or some sort + // of "terminal" that looks exactly like a real serial port! + close(fd); + // unfortunately, Linux always raises DTR when open is called. + // not nice! Every serial port is going to get DTR raised + // and then lowered. I wish there were a way to prevent this, + // but it seems impossible. + list.push_back(s); + } + closedir(dir); +#elif defined(MACOSX) + // adapted from SerialPortSample.c, by Apple + // http://developer.apple.com/samplecode/SerialPortSample/listing2.html + // and also testserial.c, by Keyspan + // http://www.keyspan.com/downloads-files/developer/macosx/KesypanTestSerial.c + // www.rxtx.org, src/SerialImp.c seems to be based on Keyspan's testserial.c + // neither keyspan nor rxtx properly release memory allocated. + // more documentation at: + // http://developer.apple.com/documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/chapter_2_section_6.html + mach_port_t masterPort; + CFMutableDictionaryRef classesToMatch; + io_iterator_t serialPortIterator; + if (IOMasterPort(NULL, &masterPort) != KERN_SUCCESS) return list; + // a usb-serial adaptor is usually considered a "modem", + // especially when it implements the CDC class spec + classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); + if (!classesToMatch) return list; + CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), + CFSTR(kIOSerialBSDModemType)); + if (IOServiceGetMatchingServices(masterPort, classesToMatch, + &serialPortIterator) != KERN_SUCCESS) return list; + macos_ports(&serialPortIterator, list); + IOObjectRelease(serialPortIterator); + // but it might be considered a "rs232 port", so repeat this + // search for rs232 ports + classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); + if (!classesToMatch) return list; + CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), + CFSTR(kIOSerialBSDRS232Type)); + if (IOServiceGetMatchingServices(masterPort, classesToMatch, + &serialPortIterator) != KERN_SUCCESS) return list; + macos_ports(&serialPortIterator, list); + IOObjectRelease(serialPortIterator); +#elif defined(WINDOWS) + // http://msdn.microsoft.com/en-us/library/aa365461(VS.85).aspx + // page with 7 ways - not all of them work! + // http://www.naughter.com/enumser.html + // may be possible to just query the windows registary + // http://it.gps678.com/2/ca9c8631868fdd65.html + // search in HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM + // Vista has some special new way, vista-only + // http://msdn2.microsoft.com/en-us/library/aa814070(VS.85).aspx + char *buffer, *p; + //DWORD size = QUERYDOSDEVICE_BUFFER_SIZE; + DWORD ret; + + buffer = (char *)malloc(QUERYDOSDEVICE_BUFFER_SIZE); + if (buffer == NULL) return list; + memset(buffer, 0, QUERYDOSDEVICE_BUFFER_SIZE); + ret = QueryDosDeviceA(NULL, buffer, QUERYDOSDEVICE_BUFFER_SIZE); + if (ret) { + //printf("Detect Serial using QueryDosDeviceA: "); + for (p = buffer; *p; p += strlen(p) + 1) { + if (strncmp(p, "COM", 3)) continue; + list.push_back(string(p) ); + //printf(": %s\n", p); + } + } else { + char buf[1024]; + win32_err(buf); + printf("QueryDosDeviceA failed, error \"%s\"\n", buf); + printf("Detect Serial using brute force GetDefaultCommConfig probing: "); + for (int i=1; i<=32; i++) { + printf("try %s", buf); + COMMCONFIG cfg; + DWORD len; + snprintf(buf, sizeof(buf), "COM%d", i); + if (GetDefaultCommConfig(buf, &cfg, &len)) { + std::ostringstream name; + name << "COM" << i << ":"; + list.push_back(name.str()); + printf(": %s", buf); + } + } + } + free(buffer); +#endif + std::sort (list.begin(), list.end()); + return list; +} + + + + + + + + |