From 2f2acc20e0c60b85bd38337e7dfdd6d85964c7fb Mon Sep 17 00:00:00 2001 From: Serge Date: Sat, 13 Oct 2018 20:37:14 -0700 Subject: [PATCH] Add serial routines. --- Makefile | 2 +- Makefile-mingw | 2 +- radio.c | 8 +- serial.c | 631 +++++++++++++++++++++++++++++++++++++++++++++++++ util.h | 12 + 5 files changed, 652 insertions(+), 3 deletions(-) create mode 100644 serial.c diff --git a/Makefile b/Makefile index 8398279..e6e2706 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ UNAME = $(shell uname) CFLAGS = -g -O -Wall -Werror -DVERSION='"$(VERSION).$(GITCOUNT)"' LDFLAGS = -g -OBJS = main.o util.o radio.o dfu-libusb.o uv380.o md380.o rd5r.o gd77.o hid.o +OBJS = main.o util.o radio.o dfu-libusb.o uv380.o md380.o rd5r.o gd77.o hid.o serial.o LIBS = -lusb-1.0 # Linux diff --git a/Makefile-mingw b/Makefile-mingw index d6bf430..9cbbe55 100644 --- a/Makefile-mingw +++ b/Makefile-mingw @@ -5,7 +5,7 @@ GITCOUNT = $(shell git rev-list HEAD --count) CFLAGS = -g -O -Wall -Werror -DVERSION='"$(VERSION).$(GITCOUNT)"' LDFLAGS = -g -s -OBJS = main.o util.o radio.o dfu-windows.o uv380.o md380.o rd5r.o gd77.o hid.o hid-windows.o +OBJS = main.o util.o radio.o dfu-windows.o uv380.o md380.o rd5r.o gd77.o hid.o hid-windows.o serial.o LIBS = -lhid -lsetupapi # Compiling Windows binary from Linux diff --git a/radio.c b/radio.c index f6dd4fb..0c860e5 100644 --- a/radio.c +++ b/radio.c @@ -68,6 +68,7 @@ void radio_disconnect() dfu_reboot(); dfu_close(); hid_close(); + serial_close(); } // @@ -89,10 +90,15 @@ void radio_connect() // Try TYT MD family. ident = dfu_init(0x0483, 0xdf11); if (! ident) { - // Try RD-5R. + // Try RD-5R and GD-77. if (hid_init(0x15a2, 0x0073) >= 0) ident = hid_identify(); } + if (! ident) { + // Try AT-D868UV. + if (serial_init(0x28e9, 0x018a) >= 0) + ident = serial_identify(); + } if (! ident) { fprintf(stderr, "No radio detected.\n"); fprintf(stderr, "Check your USB cable!\n"); diff --git a/serial.c b/serial.c new file mode 100644 index 0000000..ba5f2d8 --- /dev/null +++ b/serial.c @@ -0,0 +1,631 @@ +/* + * Interface to virtual serial USB port. + * + * Copyright (C) 2018 Serge Vakulenko, KK6ABQ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#include +#include "util.h" + +#if defined(__WIN32__) || defined(WIN32) + #include + #include + static void *fd = INVALID_HANDLE_VALUE; + static DCB saved_mode; +#else + #include + static int fd = -1; + static struct termios saved_mode; +#endif + +#ifdef __linux__ +# include +#endif + +#ifdef __APPLE__ +# include +# include +# include +# include +# include +#endif + +static char *dev_path; + +static const unsigned char CMD_PRG[] = "PROGRAM"; +static const unsigned char CMD_QX[] = "QX\6"; +static const unsigned char CMD_ACK2[] = "\2"; + +// +// Encode the speed in bits per second into bit value +// accepted by cfsetspeed() function. +// Return -1 when speed is not supported. +// +static int baud_encode(int bps) +{ +#if defined(__WIN32__) || defined(WIN32) || B115200 == 115200 + // Windows, BSD, Mac OS: any baud rate allowed. + return bps; +#else + // Linux: only a limited set of values permitted. + switch (bps) { +#ifdef B75 + case 75: return B75; +#endif +#ifdef B110 + case 110: return B110; +#endif +#ifdef B134 + case 134: return B134; +#endif +#ifdef B150 + case 150: return B150; +#endif +#ifdef B200 + case 200: return B200; +#endif +#ifdef B300 + case 300: return B300; +#endif +#ifdef B600 + case 600: return B600; +#endif +#ifdef B1200 + case 1200: return B1200; +#endif +#ifdef B1800 + case 1800: return B1800; +#endif +#ifdef B2400 + case 2400: return B2400; +#endif +#ifdef B4800 + case 4800: return B4800; +#endif +#ifdef B9600 + case 9600: return B9600; +#endif +#ifdef B19200 + case 19200: return B19200; +#endif +#ifdef B38400 + case 38400: return B38400; +#endif +#ifdef B57600 + case 57600: return B57600; +#endif +#ifdef B115200 + case 115200: return B115200; +#endif +#ifdef B230400 + case 230400: return B230400; +#endif +#ifdef B460800 + case 460800: return B460800; +#endif +#ifdef B500000 + case 500000: return B500000; +#endif +#ifdef B576000 + case 576000: return B576000; +#endif +#ifdef B921600 + case 921600: return B921600; +#endif +#ifdef B1000000 + case 1000000: return B1000000; +#endif +#ifdef B1152000 + case 1152000: return B1152000; +#endif +#ifdef B1500000 + case 1500000: return B1500000; +#endif +#ifdef B2000000 + case 2000000: return B2000000; +#endif +#ifdef B2500000 + case 2500000: return B2500000; +#endif +#ifdef B3000000 + case 3000000: return B3000000; +#endif +#ifdef B3500000 + case 3500000: return B3500000; +#endif +#ifdef B4000000 + case 4000000: return B4000000; +#endif + } +printf("Unknown baud\n"); + return -1; +#endif +} + +// +// Send data to device. +// Return number of bytes, or -1 on error. +// +int serial_write(const unsigned char *data, int len) +{ +#if defined(__WIN32__) || defined(WIN32) + DWORD written; + + if (! WriteFile(fd, data, len, &written, 0)) + return -1; + return written; +#else + return write(fd, data, len); +#endif +} + +// +// Receive data from device. +// Return number of bytes, or -1 on error. +// +int serial_read(unsigned char *data, int len, int timeout_msec) +{ +#if defined(__WIN32__) || defined(WIN32) + DWORD got; + COMMTIMEOUTS ctmo; + + // Reset the Windows RX timeout to the current timeout_msec + // value, as it may have changed since the last read. + // + memset(&ctmo, 0, sizeof(ctmo)); + ctmo.ReadIntervalTimeout = 0; + ctmo.ReadTotalTimeoutMultiplier = 0; + ctmo.ReadTotalTimeoutConstant = timeout_msec; + if (! SetCommTimeouts(fd, &ctmo)) { + fprintf(stderr, "Cannot set timeouts in serial_read()\n"); + return -1; + } + + if (! ReadFile(fd, data, len, &got, 0)) { + fprintf(stderr, "serial_read: read error\n"); + exit(-1); + } +#else + struct timeval timeout, to2; + long got; + fd_set rfds; + + timeout.tv_sec = timeout_msec / 1000; + timeout.tv_usec = timeout_msec % 1000 * 1000; +again: + to2 = timeout; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + got = select(fd + 1, &rfds, 0, 0, &to2); + if (got < 0) { + if (errno == EINTR || errno == EAGAIN) { + if (trace_flag > 0) + printf("serial_read: retry on select\n"); + goto again; + } + fprintf(stderr, "serial_read: select error: %s\n", strerror(errno)); + exit(-1); + } +#endif + if (got == 0) { + if (trace_flag > 0) + printf("serial_read: no characters to read\n"); + return 0; + } + +#if ! defined(__WIN32__) && ! defined(WIN32) + got = read(fd, data, (len > 1024) ? 1024 : len); + if (got < 0) { + fprintf(stderr, "serial_read: read error\n"); + exit(-1); + } +#endif + return got; +} + +// +// Close the serial port. +// +void serial_close() +{ +#if defined(__WIN32__) || defined(WIN32) + if (fd != INVALID_HANDLE_VALUE) { + SetCommState(fd, &saved_mode); + CloseHandle(fd); + fd = INVALID_HANDLE_VALUE; + } +#else + if (fd >= 0) { + tcsetattr(fd, TCSANOW, &saved_mode); + close(fd); + fd = -1; + } +#endif +} + +// +// Open the serial port. +// Return -1 on error. +// +int serial_open(const char *devname, int baud_rate) +{ +#if defined(__WIN32__) || defined(WIN32) + DCB new_mode; +#else + struct termios new_mode; +#endif + +#if defined(__WIN32__) || defined(WIN32) + // Check for the Windows device syntax and bend a DOS device + // into that syntax to allow higher COM numbers than 9 + // + if (devname[0] != '\\') { + // Prepend device prefix: COM23 -> \\.\COM23 + char *buf = alloca(5 + strlen(devname)); + if (! buf) { + fprintf(stderr, "%s: Out of memory\n", devname); + return -1; + } + strcpy(buf, "\\\\.\\"); + strcat(buf, devname); + devname = buf; + } + + // Open port + fd = CreateFile(devname, GENERIC_READ | GENERIC_WRITE, + 0, 0, OPEN_EXISTING, 0, 0); + if (fd == INVALID_HANDLE_VALUE) { + fprintf(stderr, "%s: Cannot open\n", devname); + return -1; + } + + // Set serial attributes + memset(&saved_mode, 0, sizeof(saved_mode)); + if (! GetCommState(fd, &saved_mode)) { + fprintf(stderr, "%s: Cannot get state\n", devname); + return -1; + } + + new_mode = saved_mode; + + new_mode.fDtrControl = DTR_CONTROL_ENABLE; + new_mode.BaudRate = baud_rate; + new_mode.ByteSize = 8; + new_mode.StopBits = ONESTOPBIT; + new_mode.Parity = 0; + new_mode.fParity = FALSE; + new_mode.fOutX = FALSE; + new_mode.fInX = FALSE; + new_mode.fOutxCtsFlow = FALSE; + new_mode.fOutxDsrFlow = FALSE; + new_mode.fRtsControl = RTS_CONTROL_ENABLE; + new_mode.fNull = FALSE; + new_mode.fAbortOnError = FALSE; + new_mode.fBinary = TRUE; + if (! SetCommState(fd, &new_mode)) { + fprintf(stderr, "%s: Cannot set state\n", devname); + return -1; + } +#else + // Encode baud rate. + int baud_code = baud_encode(baud_rate); + if (baud_code < 0) { + fprintf(stderr, "%s: Bad baud rate %d\n", devname, baud_rate); + return -1; + } + + // Open port + fd = open(devname, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (fd < 0) { + perror(devname); + return -1; + } + + // Set serial attributes + memset(&saved_mode, 0, sizeof(saved_mode)); + tcgetattr(fd, &saved_mode); + + // 8n1, ignore parity + memset(&new_mode, 0, sizeof(new_mode)); + new_mode.c_cflag = CS8 | CLOCAL | CREAD; + new_mode.c_iflag = IGNBRK; + new_mode.c_oflag = 0; + new_mode.c_lflag = 0; + new_mode.c_cc[VTIME] = 0; + new_mode.c_cc[VMIN] = 1; + cfsetispeed(&new_mode, baud_code); + cfsetospeed(&new_mode, baud_code); + tcflush(fd, TCIFLUSH); + tcsetattr(fd, TCSANOW, &new_mode); + + // Clear O_NONBLOCK flag. + int flags = fcntl(fd, F_GETFL, 0); + if (flags >= 0) + fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); +#endif + return 0; +} + +// +// Find a device path by vid/pid. +// +static char *find_path(int vid, int pid) +{ + char *result = 0; + +#if defined(__linux__) + // Create the udev object. + struct udev *udev = udev_new(); + if (! udev) { + printf("Can't create udev\n"); + return 0; + } + + // Create a list of the devices in the 'tty' subsystem. + struct udev_enumerate *enumerate = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(enumerate, "tty"); + udev_enumerate_scan_devices(enumerate); + + struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate); + + // For each item enumerated, figure out its information. + struct udev_list_entry *dev_list_entry; + udev_list_entry_foreach(dev_list_entry, devices) { + // Get the filename of the /sys entry for the device + // and create a udev_device object (dev) representing it. + const char *syspath = udev_list_entry_get_name(dev_list_entry); + struct udev_device *comport = udev_device_new_from_syspath(udev, syspath); + //printf("syspath = %s\n", syspath); + + // Get the parent device with the subsystem/devtype pair + // of "usb"/"usb_device". + struct udev_device *parent = udev_device_get_parent_with_subsystem_devtype(comport, + "usb", "usb_device"); + if (! parent) { + //printf("Unable to find parent usb device.\n"); + continue; + } + + // Get the path to the device node in /dev. + const char *devpath = udev_device_get_devnode(comport); + //printf("devpath = %s\n", devpath); + //printf("parent = %s\n", udev_device_get_devnode(parent)); + + const char *idVendor = udev_device_get_sysattr_value(parent, "idVendor"); + const char *idProduct = udev_device_get_sysattr_value(parent, "idProduct"); + if (! idVendor || ! idProduct) { + // No vendor and product ID. + continue; + } + //printf("vid = %s\n", idVendor); + //printf("pid = %s\n", idProduct); + + unsigned vendor_id = strtoul(idVendor, 0, 16); + unsigned product_id = strtoul(idProduct, 0, 16); + if (vendor_id != vid || product_id != pid) { + // Wrong ID. + continue; + } + + // Print names of vendor and product. + //const char *vendor = udev_device_get_sysattr_value(parent, "manufacturer"); + //const char *product = udev_device_get_sysattr_value(parent, "product"); + //printf("vendor = %s\n", vendor); + //printf("product = %s\n", product); + + // Return result. + udev_device_unref(parent); + result = strdup(devpath); + break; + } + + // Free the enumerator object + udev_enumerate_unref(enumerate); + udev_unref(udev); + return result; + +#elif defined(__APPLE__) + // Create a list of the devices in the 'IOMedia' class. + CFMutableDictionaryRef dict = IOServiceMatching(kIOMediaClass); + if (! dict) { + printf("Cannot create IO Service dictionary.\n"); + return; + } + + // Select devices with attributes Removeable=True and Whole=True. + CFDictionarySetValue(dict, CFSTR(kIOMediaRemovableKey), kCFBooleanTrue); + CFDictionarySetValue(dict, CFSTR(kIOMediaWholeKey), kCFBooleanTrue); + + io_iterator_t devices = IO_OBJECT_NULL; + kern_return_t result = IOServiceGetMatchingServices(kIOMasterPortDefault, + dict, &devices); + if (result != KERN_SUCCESS) { + printf("Cannot find matching IO services.\n"); + return; + } + + // For each matching device, print out its information. + io_object_t device; + while ((device = IOIteratorNext(devices)) != MACH_PORT_NULL) { + // Get device path. + char devname[1024] = "/dev/r"; + CFStringRef ref = (CFStringRef) IORegistryEntrySearchCFProperty(device, + kIOServicePlane, CFSTR(kIOBSDNameKey), + kCFAllocatorDefault, kIORegistryIterateRecursively); + if (! ref || ! CFStringGetCString(ref, devname + 6, + sizeof(devname) - 6, kCFStringEncodingUTF8)) { + // Cannot get device path. + continue; + } +#if 0 + // Get device size in bytes. + long long size; + ref = IORegistryEntryCreateCFProperty(device, + CFSTR(kIOMediaSizeKey), kCFAllocatorDefault, 0); + if (! ref || ! CFNumberGetValue((CFNumberRef)ref, kCFNumberLongLongType, &size)) { + // Cannot get device size. + continue; + } +#endif + // Get a list of device characteristics. + CFMutableDictionaryRef dict = (CFMutableDictionaryRef) + IORegistryEntrySearchCFProperty(device, kIOServicePlane, + CFSTR(kIOPropertyDeviceCharacteristicsKey), + kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively); + if (! dict) { + // Cannot get device characteristics. + continue; + } + + // Get vendor and product names. + char vendor[1024], product[1024]; + ref = CFDictionaryGetValue(dict, CFSTR(kIOPropertyVendorNameKey)); + if (! ref || ! CFStringGetCString(ref, vendor, + sizeof(vendor), kCFStringEncodingUTF8)) { + // Cannot get vendor name. + continue; + } + ref = CFDictionaryGetValue(dict, CFSTR(kIOPropertyProductNameKey)); + if (! ref || ! CFStringGetCString(ref, product, + sizeof(product), kCFStringEncodingUTF8)) { + // Cannot get product name. + continue; + } + + char buf[1024]; + sprintf(buf, "%s - size %u MB, %s %s", + devname, (unsigned) (size / 1000000), vendor, product); + IOObjectRelease(device); + devtab[ndev++] = strdup(buf); + } + + // Free the iterator object + IOObjectRelease(devices); +#else + printf("Don't know how to get the list of CD devices on this system.\n"); + return 0; +#endif +} + +// +// Connect to the specified device. +// Initiate the programming session. +// +int serial_init(int vid, int pid) +{ + dev_path = find_path(vid, pid); + if (!dev_path) { + if (trace_flag) { + fprintf(stderr, "Cannot find USB device %04x:%04x\n", + vid, pid); + } + return -1; + } + + // Succeeded. + printf("Serial port: %s\n", dev_path); + return 0; +} + +// +// Send the command sequence and get back a response. +// +static int send_recv(const unsigned char *cmd, int cmdlen, + unsigned char *response, int reply_len) +{ + unsigned char *p; + int len, i, got; + + // + // Send command. + // + if (trace_flag > 0) { + fprintf(stderr, "----Send [%d] %x", cmdlen, cmd[0]); + for (i=1; i 0) { + fprintf(stderr, "----Recv [%d] %x", reply_len, response[0]); + for (i=1; i