From 081b6fe2299232f191526f969b310fb7605e1574 Mon Sep 17 00:00:00 2001
From: Joerg Wunsch <j@uriah.heep.sax.de>
Date: Sat, 20 Feb 2016 22:50:13 +0000
Subject: [PATCH] Use libhidapi as (optional) transport for CMSIS-DAP compliant
 debuggers (JTAGICE3 with firmware 3+, AtmelICE, EDBG, mEDBG).

git-svn-id: svn://svn.savannah.nongnu.org/avrdude/trunk/avrdude@1381 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 ChangeLog    |   9 ++
 Makefile.am  |   1 +
 NEWS         |   4 +
 configure.ac |   2 +-
 jtag3.c      |  32 ++++-
 libavrdude.h |   1 +
 usb_hidapi.c | 353 +++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 397 insertions(+), 5 deletions(-)
 create mode 100644 usb_hidapi.c

diff --git a/ChangeLog b/ChangeLog
index bba6e491..bc4ac224 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2016-02-20  Joerg Wunsch <j.gnu@uriah.heep.sax.de>
+
+	* jtag3.c: add support for libhidapi as (optional) transport for
+	CMSIS-DAP compliant debuggers (JTAGICE3 with firmware 3+,
+	AtmelICE, EDBG, mEDBG)
+	* usb_hidapi.c: (New file)
+	* libavrdude.h: Mention usbhid_serdev
+	* configure.ac: Bump version date
+
 2016-02-18  Joerg Wunsch <j.gnu@uriah.heep.sax.de>
 
 	(Obtained from patch #8717: pattch for mcprog and libhidapi support)
diff --git a/Makefile.am b/Makefile.am
index 632ea1ca..d1ed4784 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -169,6 +169,7 @@ libavrdude_a_SOURCES = \
 	usbasp.c \
 	usbasp.h \
 	usbdevs.h \
+	usb_hidapi.c \
 	usb_libusb.c \
 	usbtiny.h \
 	usbtiny.c \
diff --git a/NEWS b/NEWS
index a72013d8..be0c9239 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,10 @@ Current:
 
   * Major changes compared to the previous version:
 
+    - libhidapi support (part of patch #8717)
+    - use libhidapi as (optional) transport for CMSIS-DAP compliant
+      debuggers (JTAGICE3 with firmware 3+, AtmelICE, EDBG, mEDBG)
+
   * New devices supported:
 
   * New programmers supported:
diff --git a/configure.ac b/configure.ac
index 6ea8849a..8194123a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -23,7 +23,7 @@
 # Process this file with autoconf to produce a configure script.
 
 AC_PREREQ(2.60)
-AC_INIT(avrdude, 6.3-20160216, avrdude-dev@nongnu.org)
+AC_INIT(avrdude, 6.3-20160220, avrdude-dev@nongnu.org)
 
 AC_CANONICAL_BUILD
 AC_CANONICAL_HOST
diff --git a/jtag3.c b/jtag3.c
index 4b718e6e..3084c320 100644
--- a/jtag3.c
+++ b/jtag3.c
@@ -1314,8 +1314,8 @@ int jtag3_open_common(PROGRAMMER * pgm, char * port)
   LNODEID usbpid;
   int rv = -1;
 
-#if !defined(HAVE_LIBUSB)
-  avrdude_message(MSG_INFO, "avrdude was compiled without usb support.\n");
+#if !defined(HAVE_LIBUSB) && !defined(HAVE_LIBHIDAPI)
+  avrdude_message(MSG_INFO, "avrdude was compiled without USB or HIDAPI support.\n");
   return -1;
 #endif
 
@@ -1325,7 +1325,6 @@ int jtag3_open_common(PROGRAMMER * pgm, char * port)
     return -1;
   }
 
-  serdev = &usb_serdev_frame;
   if (pgm->usbvid)
     pinfo.usbinfo.vid = pgm->usbvid;
   else
@@ -1335,17 +1334,42 @@ int jtag3_open_common(PROGRAMMER * pgm, char * port)
   if (lfirst(pgm->usbpid) == NULL)
     ladd(pgm->usbpid, (void *)USB_DEVICE_JTAGICE3);
 
+#if defined(HAVE_LIBHIDAPI)
+  /*
+   * Try HIDAPI first.  LibUSB is more generic, but might then cause
+   * troubles for HID-class devices in some OSes (like Windows).
+   */
+  serdev = &usbhid_serdev;
   for (usbpid = lfirst(pgm->usbpid); rv < 0 && usbpid != NULL; usbpid = lnext(usbpid)) {
     pinfo.usbinfo.flags = PINFO_FL_SILENT;
     pinfo.usbinfo.pid = *(int *)(ldata(usbpid));
     pgm->fd.usb.max_xfer = USBDEV_MAX_XFER_3;
     pgm->fd.usb.rep = USBDEV_BULK_EP_READ_3;
     pgm->fd.usb.wep = USBDEV_BULK_EP_WRITE_3;
-    pgm->fd.usb.eep = USBDEV_EVT_EP_READ_3;
+    pgm->fd.usb.eep = 0;
 
     strcpy(pgm->port, port);
     rv = serial_open(port, pinfo, &pgm->fd);
   }
+  if (rv < 0) {
+#endif	/* HAVE_LIBHIDAPI */
+#if defined(HAVE_LIBUSB)
+    serdev = &usb_serdev_frame;
+    for (usbpid = lfirst(pgm->usbpid); rv < 0 && usbpid != NULL; usbpid = lnext(usbpid)) {
+      pinfo.usbinfo.flags = PINFO_FL_SILENT;
+      pinfo.usbinfo.pid = *(int *)(ldata(usbpid));
+      pgm->fd.usb.max_xfer = USBDEV_MAX_XFER_3;
+      pgm->fd.usb.rep = USBDEV_BULK_EP_READ_3;
+      pgm->fd.usb.wep = USBDEV_BULK_EP_WRITE_3;
+      pgm->fd.usb.eep = USBDEV_EVT_EP_READ_3;
+
+      strcpy(pgm->port, port);
+      rv = serial_open(port, pinfo, &pgm->fd);
+    }
+#endif	/* HAVE_LIBUSB */
+#if defined(HAVE_LIBHIDAPI)
+  }
+#endif
   if (rv < 0) {
     avrdude_message(MSG_INFO, "%s: jtag3_open_common(): Did not find any device matching VID 0x%04x and PID list: ",
                     progname, (unsigned)pinfo.usbinfo.vid);
diff --git a/libavrdude.h b/libavrdude.h
index ac79640c..d5f32e46 100644
--- a/libavrdude.h
+++ b/libavrdude.h
@@ -563,6 +563,7 @@ extern struct serial_device serial_serdev;
 extern struct serial_device usb_serdev;
 extern struct serial_device usb_serdev_frame;
 extern struct serial_device avrdoper_serdev;
+extern struct serial_device usbhid_serdev;
 
 #define serial_open (serdev->open)
 #define serial_setspeed (serdev->setspeed)
diff --git a/usb_hidapi.c b/usb_hidapi.c
new file mode 100644
index 00000000..aa306b69
--- /dev/null
+++ b/usb_hidapi.c
@@ -0,0 +1,353 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2016 Joerg Wunsch
+ *
+ * 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 2 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/>.
+ */
+
+/* $Id$ */
+
+/*
+ * USB interface via libhidapi for avrdude.
+ */
+
+#include "ac_cfg.h"
+#if defined(HAVE_LIBHIDAPI)
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <wchar.h>
+
+#include <hidapi/hidapi.h>
+
+#include "avrdude.h"
+#include "libavrdude.h"
+
+#include "usbdevs.h"
+
+#if defined(WIN32NATIVE)
+/* someone has defined "interface" to "struct" in Cygwin */
+#  undef interface
+#endif
+
+/*
+ * The "baud" parameter is meaningless for USB devices, so we reuse it
+ * to pass the desired USB device ID.
+ */
+static int usbhid_open(char * port, union pinfo pinfo, union filedescriptor *fd)
+{
+  hid_device *dev;
+  char *serno, *cp2;
+  size_t x;
+  unsigned char usbbuf[USBDEV_MAX_XFER_3 + 1];
+
+  if (fd->usb.max_xfer == 0)
+    fd->usb.max_xfer = USBDEV_MAX_XFER_3;
+
+  /*
+   * The syntax for usb devices is defined as:
+   *
+   * -P usb[:serialnumber]
+   *
+   * See if we've got a serial number passed here.  The serial number
+   * might contain colons which we remove below, and we compare it
+   * right-to-left, so only the least significant nibbles need to be
+   * specified.
+   */
+  if ((serno = strchr(port, ':')) != NULL)
+    {
+      /* first, drop all colons there if any */
+      cp2 = ++serno;
+
+      while ((cp2 = strchr(cp2, ':')) != NULL)
+	{
+	  x = strlen(cp2) - 1;
+	  memmove(cp2, cp2 + 1, x);
+	  cp2[x] = '\0';
+	}
+
+      if (strlen(serno) > 12)
+	{
+	  avrdude_message(MSG_INFO, "%s: usbhid_open(): invalid serial number \"%s\"\n",
+                          progname, serno);
+	  return -1;
+	}
+
+      wchar_t wserno[15];
+      mbstowcs(wserno, serno, 15);
+      size_t serlen = strlen(serno);
+
+      /*
+       * Now, try finding all devices matching VID:PID, and compare
+       * their serial numbers against the requested one.
+       */
+      struct hid_device_info *list, *walk;
+      list = hid_enumerate(pinfo.usbinfo.vid, pinfo.usbinfo.pid);
+      if (list == NULL)
+	return -1;
+
+      walk = list;
+      while (walk)
+      {
+	avrdude_message(MSG_NOTICE, "%s: usbhid_open(): Found %ls, serno: %ls\n",
+			progname, walk->product_string, walk->serial_number);
+	if (wcscmp(walk->serial_number + serlen, wserno) == 0)
+          {
+	    /* found matching serial number */
+	    break;
+          }
+	avrdude_message(MSG_DEBUG, "%s: usbhid_open(): serial number doesn't match\n",
+                          progname);
+      }
+      if (walk == NULL)
+      {
+	avrdude_message(MSG_INFO, "%s: usbhid_open(): No matching device found\n",
+			progname);
+	return -1;
+      }
+      avrdude_message(MSG_DEBUG, "%s: usbhid_open(): Opening path %s\n",
+                      progname, walk->path);
+      dev = hid_open_path(walk->path);
+      hid_free_enumeration(list);
+      if (dev == NULL)
+      {
+	avrdude_message(MSG_INFO,
+			"%s: usbhid_open(): Found device, but hid_open_path() failed\n",
+			progname);
+	return -1;
+      }
+    }
+  else
+    {
+      /*
+       * No serial number requested, pass straight to hid_open()
+       */
+      dev = hid_open(pinfo.usbinfo.vid, pinfo.usbinfo.pid, NULL);
+      if (dev == NULL)
+      {
+	avrdude_message(MSG_INFO, "%s: usbhid_open(): No device found\n",
+			progname);
+	return -1;
+      }
+    }
+
+  fd->usb.handle = dev;
+
+  /*
+   * Try finding out the endpoint size.  Alas, libhidapi doesn't
+   * provide us with an API function for that, nor for the report
+   * descriptor (which also contains that information).
+   *
+   * Since the Atmel tools a very picky to only respond to incoming
+   * packets that have full size, we need to know whether our device
+   * handles 512-byte data (JTAGICE3 in CMSIS-DAP mode, or AtmelICE,
+   * both on USB 2.0 connections), or 64-byte data only (both these on
+   * USB 1.1 connections, or mEDBG devices).
+   *
+   * In order to find out, we send a CMSIS-DAP DAP_Info command
+   * (0x00), with an ID of 0xFF (get maximum packet size).  In theory,
+   * this gets us the desired information, but this suffers from a
+   * chicken-and-egg problem: the request must be sent with a
+   * full-sized packet lest the ICE won't answer.  Thus, we send a
+   * 64-byte packet first, and if we don't get a timely reply,
+   * complete that request by sending another 448 bytes, and hope it
+   * will eventually reply.
+   *
+   * Note that libhidapi always requires a report ID as the first
+   * byte.  If the target doesn't use report IDs (Atmel targets
+   * don't), this first byte must be 0x00.  However, the length must
+   * be incremented by one, as the report ID will be omitted by the
+   * hidapi library.
+   */
+  if (pinfo.usbinfo.vid == USB_VENDOR_ATMEL)
+    {
+      avrdude_message(MSG_DEBUG, "%s: usbhid_open(): Probing for max. packet size\n",
+		      progname);
+      memset(usbbuf, 0, sizeof usbbuf);
+      usbbuf[0] = 0;		/* no HID reports used */
+      usbbuf[1] = 0;		/* DAP_Info */
+      usbbuf[2] = 0xFF;		/* get max. packet size */
+
+      hid_write(dev, usbbuf, 65);
+      fd->usb.max_xfer = 64;	/* first guess */
+
+      memset(usbbuf, 0, sizeof usbbuf);
+      int res = hid_read_timeout(dev, usbbuf, 10 /* bytes */, 50 /* milliseconds */);
+      if (res == 0) {
+	/* no timely response, assume 512 byte size */
+	hid_write(dev, usbbuf, (512 - 64) + 1);
+	fd->usb.max_xfer = 512;
+	res = hid_read_timeout(dev, usbbuf, 10, 50);
+      }
+      if (res <= 0) {
+	avrdude_message(MSG_INFO, "%s: usbhid_open(): No response from device\n",
+			progname);
+	hid_close(dev);
+	return -1;
+      }
+      if (usbbuf[0] != 0 || usbbuf[1] != 2) {
+	avrdude_message(MSG_INFO,
+			"%s: usbhid_open(): Unexpected reply to DAP_Info: 0x%02x 0x%02x\n",
+			progname, usbbuf[0], usbbuf[1]);
+      } else {
+	fd->usb.max_xfer = usbbuf[2] + (usbbuf[3] << 8);
+	avrdude_message(MSG_DEBUG,
+			"%s: usbhid_open(): Setting max_xfer from DAP_Info response to %d\n",
+			progname, fd->usb.max_xfer);
+      }
+    }
+  if (fd->usb.max_xfer > USBDEV_MAX_XFER_3) {
+    avrdude_message(MSG_INFO,
+		    "%s: usbhid_open(): Unexpected max size %d, reducing to %d\n",
+		    progname, fd->usb.max_xfer, USBDEV_MAX_XFER_3);
+    fd->usb.max_xfer = USBDEV_MAX_XFER_3;
+  }
+
+  return 0;
+}
+
+static void usbhid_close(union filedescriptor *fd)
+{
+  hid_device *udev = (hid_device *)fd->usb.handle;
+
+  if (udev == NULL)
+    return;
+
+  hid_close(udev);
+}
+
+
+static int usbhid_send(union filedescriptor *fd, const unsigned char *bp, size_t mlen)
+{
+  hid_device *udev = (hid_device *)fd->usb.handle;
+  int rv;
+  int i = mlen;
+  const unsigned char * p = bp;
+  unsigned char usbbuf[USBDEV_MAX_XFER_3];
+
+
+  int tx_size;
+
+  if (udev == NULL)
+    return -1;
+
+  tx_size = (mlen < USBDEV_MAX_XFER_3)? mlen: USBDEV_MAX_XFER_3;
+  usbbuf[0] = 0;		/* no report ID used */
+  memcpy(usbbuf + 1, bp, tx_size);
+  rv = hid_write(udev, usbbuf, tx_size + 1);
+  if (rv < 0) {
+    avrdude_message(MSG_INFO, "%s: Failed to write %d bytes to USB\n",
+		    progname, tx_size);
+    return -1;
+  }
+  if (rv != tx_size + 1)
+    avrdude_message(MSG_INFO, "%s: Short write to USB: %d bytes out of %d written\n",
+		    progname, rv, tx_size + 1);
+
+  if (verbose > 4)
+  {
+      avrdude_message(MSG_TRACE2, "%s: Sent: ", progname);
+
+      while (i) {
+        unsigned char c = *p;
+        if (isprint(c)) {
+          avrdude_message(MSG_TRACE2, "%c ", c);
+        }
+        else {
+          avrdude_message(MSG_TRACE2, ". ");
+        }
+        avrdude_message(MSG_TRACE2, "[%02x] ", c);
+
+        p++;
+        i--;
+      }
+      avrdude_message(MSG_TRACE2, "\n");
+  }
+  return 0;
+}
+
+static int usbhid_recv(union filedescriptor *fd, unsigned char *buf, size_t nbytes)
+{
+  hid_device *udev = (hid_device *)fd->usb.handle;
+  int i, rv;
+  unsigned char * p = buf;
+
+  if (udev == NULL)
+    return -1;
+
+  rv = i = hid_read_timeout(udev, buf, nbytes, 100);
+  if (i != nbytes)
+    avrdude_message(MSG_INFO,
+		    "%s: Short read, read only %d out of %u bytes\n",
+		    progname, i, nbytes);
+
+  if (verbose > 4)
+  {
+      avrdude_message(MSG_TRACE2, "%s: Recv: ", progname);
+
+      while (i) {
+        unsigned char c = *p;
+        if (isprint(c)) {
+          avrdude_message(MSG_TRACE2, "%c ", c);
+        }
+        else {
+          avrdude_message(MSG_TRACE2, ". ");
+        }
+        avrdude_message(MSG_TRACE2, "[%02x] ", c);
+
+        p++;
+        i--;
+      }
+      avrdude_message(MSG_TRACE2, "\n");
+  }
+
+  return rv;
+}
+
+static int usbhid_drain(union filedescriptor *fd, int display)
+{
+  /*
+   * There is not much point in trying to flush any data
+   * on an USB endpoint, as the endpoint is supposed to
+   * start afresh after being configured from the host.
+   *
+   * As trying to flush the data here caused strange effects
+   * in some situations (see
+   * https://savannah.nongnu.org/bugs/index.php?43268 )
+   * better avoid it.
+   */
+
+  return 0;
+}
+
+/*
+ * Device descriptor.
+ */
+struct serial_device usbhid_serdev =
+{
+  .open = usbhid_open,
+  .close = usbhid_close,
+  .send = usbhid_send,
+  .recv = usbhid_recv,
+  .drain = usbhid_drain,
+  .flags = SERDEV_FL_NONE,
+};
+
+#endif  /* HAVE_LIBHIDAPI */