Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support COM port discovery via USB VID/PID #907

Closed
MCUdude opened this issue Mar 21, 2022 · 86 comments · Fixed by #1498
Closed

Support COM port discovery via USB VID/PID #907

MCUdude opened this issue Mar 21, 2022 · 86 comments · Fixed by #1498
Labels
enhancement New feature or request
Milestone

Comments

@MCUdude
Copy link
Collaborator

MCUdude commented Mar 21, 2022

Apparently, @mariusgreuel's Avrdude fork for windows supports COM port discovery via USB VID/PID. This is actually very neat if you're using a UART-based programmer that has a tendency to bump the COM port number. I have a similar issue on my mac as well; The serial port (/dev/cu.*) gets a different name depending on which USB port I connect it to.

Would it be possible, within a reasonable amount of time, to make this work on non-windows systems as well?

@mariusgreuel
Copy link
Contributor

The crucial part is figuring out the link between the USB device and the serial device. I never looked into non-Windows implementations, but on Windows there are two parts of metadata one can use: 1) You can enumerate through the device tree, and find the parent of the serial device, which will be the USB device. That is tricky if the USB device is a composite device. 2) Windows actually maintains a bunch of properties for every device, and Portname does what I needed.

@dl8dtl
Copy link
Contributor

dl8dtl commented Mar 21, 2022

For a completely different example, on FreeBSD, they can only be examined by sysctl.
Here's the example of the tty port of a curiosity nano board:

# sysctl dev.umodem
dev.umodem.0.ttyports: 1
dev.umodem.0.ttyname: U3
dev.umodem.0.%parent: uhub12
dev.umodem.0.%pnpinfo: vendor=0x03eb product=0x2175 devclass=0xef devsubclass=0x02 devproto=0x01 sernum="MCHP3372031800002359" release=0x0100 mode=host intclass=0x02 intsubclass=0x02 intprotocol=0x01 ttyname=U3 ttyports=1
dev.umodem.0.%location: bus=4 hubaddr=4 port=4 devaddr=5 interface=1 ugen=ugen4.5
dev.umodem.0.%driver: umodem
dev.umodem.0.%desc: Microchip Technology Incorporated nEDBG CMSIS-DAP, class 239/2, rev 2.00/1.00, addr 5
dev.umodem.%parent: 

These attach to the umodem driver (aka. CDC device), and the respective device here is /dev/cuaU3. Alas, one would have to walk across all possible drivers in addition to umodem (FTDI, PL230x, CH3xx).

So question is to find out how to get this information on MacOS.

Downside: it adds a lot of per-OS code to AVRDUDE.

@MCUdude
Copy link
Collaborator Author

MCUdude commented Mar 22, 2022

Thank you for the details! I'll do a bit of research this evening to see if I can figure it out on MacOS.

Downside: it adds a lot of per-OS code to AVRDUDE.

Good point. But on the other side, this (and probably the "Arduino Leonardo style") is IMO functionality that should be considered even though OSes handles this differently. The end-user won't notice anything, and it's very convenient!

I'll agree that adding functionality to one "OS build" that wouldn't work for the others is a bad idea, but if we can figure out how to do it for Windows, MacOS, FreeBSD and other UNIX compatible OSes, I think we should consider it for the sake of the convenience this functionality adds.

@dl8dtl
Copy link
Contributor

dl8dtl commented Mar 22, 2022

I guess finding a way for Linux will be possible as well.
It would make sense to somehow abstract an API for that, and put that into its own implementation file, a bit like that whereami.[ch] stuff that has recently been added to find out about the location of the executable.

@MCUdude
Copy link
Collaborator Author

MCUdude commented Mar 22, 2022

... a bit like that whereami.[ch] stuff that has recently been added to find out about the location of the executable.

Is whereami only needed for Windows builds?

I agree that it would make sense to "separate" OS-specific code into a separate API in order to not "pollute" existing code. The good this about this is that it makes it easier to add more OS-specific code later on. We should, however, be restrictive and not just throw in all the bells whistles just because we can. At the moment I can't think of other "nice to have" features that rely on OS-specific code other than port discovery via VID/PID and "1200bps touch" used on various Arduino boards.

EDIT:
Another wild thought. Instead of always having to "manually" find the VID/PID how about a keyword in avrdude.conf that could hold the VID, PID, and other information about a serial device as well?

serialadapter
  id        = "ch340";
  desc      = "CH340* USB to serial adapter";
  usbvid    = 0x1A86;
  usbpid    = 0x7523;
;

serialadapter
  id        = "cp2102";
  desc      = "CP2102* USB to serial adapter";
  usbvid    = 0x10C4;
  usbpid    = 0xEA60;
;

serialadapter
  id        = "cp2104";
  desc      = "CP2104* USB to serial adapter";
  usbvid    = 0x10C4;
  usbpid    = 0xEA60;
;


$ avrdude -C avrdude.conf -p avr128da48 -c serialupdi -P ch340 -v -t

@MCUdude
Copy link
Collaborator Author

MCUdude commented Mar 22, 2022

@dl8dtl It looks like sysctl is included in MacOS, but I couldn't figure out how to get it to work.
However, Google reveals that there is at least two commands that does what we want. Currently, I have a CH340N USB to serial adapter and a PicKit4 connected to my mac.

First command:

$ ioreg -p IOUSB 
+-o Root  <class IORegistryEntry, id 0x100000100, retain 15>
  +-o AppleUSBXHCI Root Hub Simulation@14000000  <class AppleUSBRootHubDevice, id 0x100000340, registered, matched, ac$
    +-o Bluetooth USB Host Controller@14300000  <class AppleUSBDevice, id 0x10000046b, registered, matched, active, bu$
    +-o USB2.0-Serial@14100000  <class AppleUSBDevice, id 0x10000069e, registered, matched, active, busy 0 (2 ms), ret$
    +-o MPLAB PICkit 4 CMSIS-DAP@14200000  <class AppleUSBDevice, id 0x1000006b0, registered, matched, active, busy 0 $

Second command:

$ system_profiler SPUSBDataType
2022-03-22 20:58:58.311 system_profiler[2351:23194] SPUSBDevice: IOCreatePlugInInterfaceForService failed 0xe00002be
USB:

    USB 3.0 Bus:

      Host Controller Driver: AppleUSBXHCIWPT
      PCI Device ID: 0x9cb1 
      PCI Revision ID: 0x0003 
      PCI Vendor ID: 0x8086 

        Bluetooth USB Host Controller:

          Product ID: 0x8290
          Vendor ID: 0x05ac (Apple Inc.)
          Version: 1.68
          Manufacturer: Broadcom Corp.
          Location ID: 0x14300000

        MPLAB PICkit 4 CMSIS-DAP:

          Product ID: 0x2177
          Vendor ID: 0x03eb  (Atmel Corporation)
          Version: 1.00
          Serial Number: BUR204071896
          Speed: Up to 480 Mb/sec
          Manufacturer: Microchip Technology Incorporated
          Location ID: 0x14200000 / 6
          Current Available (mA): 500
          Current Required (mA): 500
          Extra Operating Current (mA): 0

        USB2.0-Serial:

          Product ID: 0x7523
          Vendor ID: 0x1a86
          Version: 2.63
          Speed: Up to 12 Mb/sec
          Location ID: 0x14100000 / 5
          Current Available (mA): 500
          Current Required (mA): 98
          Extra Operating Current (mA): 0

@dl8dtl
Copy link
Contributor

dl8dtl commented Mar 23, 2022

Now, form that as an algorithm in C ;-)

@MCUdude
Copy link
Collaborator Author

MCUdude commented Mar 24, 2022

Now, form that as an algorithm in C ;-)

Challenge accepted! Here's a proof of concept. Lots of code borrowed from here. Note that it will only recognize "modems", so from my understanding, serial devices only. I've tried to plug in various other USB equipment, but my USB to serial devices are the only ones that show up.

Output:

$ ./usb_vid_pid_test 
Found modem. Port: /dev/cu.usbserial-1410 USB VID: 0x1A86 PID: 0x7523
Found modem. Port: /dev/cu.SLAB_USBtoUART USB VID: 0x10C4 PID: 0xEA60

Build command:

gcc -framework CoreServices -framework IOKit -o usb_vid_pid_test usb_vid_pid_test.c

usb_vid_pid_test.c

#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/serial/ioss.h>
#include <IOKit/IOBSD.h>


// Function prototypes
static kern_return_t findModems(io_iterator_t *matchingServices);
static kern_return_t getModemPath(io_iterator_t serialPortIterator, char *bsdPath, CFIndex maxPathSize);

// Returns an iterator across all known modems. Caller is responsible for
// releasing the iterator when iteration is complete.
static kern_return_t findModems(io_iterator_t *matchingServices)
{
  kern_return_t           kernResult;
  CFMutableDictionaryRef  classesToMatch;

  // Serial devices are instances of class IOSerialBSDClient.
  // Create a matching dictionary to find those instances.
  classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
  if (classesToMatch == NULL) {
    printf("IOServiceMatching returned a NULL dictionary.\n");
  }
  else {
    // Look for devices that claim to be modems.
    CFDictionarySetValue(classesToMatch,
                          CFSTR(kIOSerialBSDTypeKey),
                          CFSTR(kIOSerialBSDAllTypes));
  }

  // Get an iterator across all matching devices.
  kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, matchingServices);
  if (KERN_SUCCESS != kernResult)
    printf("IOServiceGetMatchingServices returned %d\n", kernResult);
  }

  return kernResult;
}

static kern_return_t getModemPath(io_iterator_t serialPortIterator, char *bsdPath, CFIndex maxPathSize)
{
  io_object_t   modemService;
  kern_return_t kernResult = KERN_FAILURE;
  bool          modemFound = false;
  int           vid;
  int           pid;

  // Initialize the returned path
  *bsdPath = '\0';

  // Iterate across all modems found. In this example, we bail after finding the first modem.
  while ((modemService = IOIteratorNext(serialPortIterator))) {
    
    // Variable declaration
    int pid, vid;
    CFTypeRef bsdPathAsCFString, cf_vendor, cf_product;    

    cf_vendor = IORegistryEntrySearchCFProperty(modemService, kIOServicePlane,
                                                CFSTR("idVendor"),
                                                kCFAllocatorDefault,
                                                kIORegistryIterateRecursively
                                                | kIORegistryIterateParents);

    cf_product = IORegistryEntrySearchCFProperty(modemService, kIOServicePlane,
                                                CFSTR("idProduct"),
                                                kCFAllocatorDefault,
                                                kIORegistryIterateRecursively
                                                | kIORegistryIterateParents);
    
    bsdPathAsCFString = IORegistryEntryCreateCFProperty(modemService,
                                                        CFSTR(kIOCalloutDeviceKey),
                                                        kCFAllocatorDefault,
                                                        0);

    // Decode & print port, VID & PID
    if (cf_vendor && cf_product && bsdPathAsCFString &&
        CFNumberGetValue(cf_vendor , kCFNumberIntType, &vid)  &&
        CFNumberGetValue(cf_product, kCFNumberIntType, &pid) &&
        CFStringGetCString(bsdPathAsCFString, bsdPath, maxPathSize, kCFStringEncodingUTF8)) {
        printf("Found modem. Port: %s USB VID: 0x%04X PID: 0x%04X\n", bsdPath, vid, pid);
        modemFound = true;
        kernResult = KERN_SUCCESS;
    }

    // Release CFTypeRef
    if (cf_vendor)  CFRelease(cf_vendor);
    if (cf_product) CFRelease(cf_product);
    if (bsdPathAsCFString) CFRelease(bsdPathAsCFString);
  }

  return kernResult;
}

int main(int argc, const char * argv[])
{
    kern_return_t kernResult;
    io_iterator_t serialPortIterator;
    char          bsdPath[MAXPATHLEN];

    kernResult = findModems(&serialPortIterator);
    if (KERN_SUCCESS != kernResult) {
        printf("No modems were found.\n");
    }

    kernResult = getModemPath(serialPortIterator, bsdPath, sizeof(bsdPath));
    if (KERN_SUCCESS != kernResult) {
        printf("Could not get path for modem.\n");
    }

    IOObjectRelease(serialPortIterator);

    return EX_OK;
}

@MCUdude
Copy link
Collaborator Author

MCUdude commented Mar 24, 2022

Here's another example where the USB VID and PID is passed as arguments, and the program will tell if the device is present or not:

Output:

.$ /search_usb_vid_pid 0x1a86 0x7523
USB device found. Port: /dev/cu.usbserial-1420 USB VID: 0x1A86 PID: 0x7523

$ ./search_usb_vid_pid 0x10c4 0xea60
USB device found. Port: /dev/cu.SLAB_USBtoUART USB VID: 0x10C4 PID: 0xEA60

# Looking for an FT231X that's not currenty plugged in:
$ ./search_usb_vid_pid 0x0403 0x6015
USB device not found.

# After connecting the FT231X:
$ ./search_usb_vid_pid 0x0403 0x6015
USB device found. Port: /dev/cu.usbserial-DN02SHCS USB VID: 0x0403 PID: 0x6015

Build command:

gcc -framework CoreServices -framework IOKit -o search_usb_vid_pid search_usb_vid_pid.c 

Source:

#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/serial/ioss.h>
#include <IOKit/IOBSD.h> 

// Function prototypes
static kern_return_t findModems(io_iterator_t *matchingServices);
static bool getModemPath(io_iterator_t serialPortIterator, char *bsdPath, CFIndex maxPathSize, int usb_vid, int usb_pid);

// Returns an iterator across all known modems. Caller is responsible for
// releasing the iterator when iteration is complete.
static kern_return_t findModems(io_iterator_t *matchingServices)
{
  kern_return_t           kernResult;
  CFMutableDictionaryRef  classesToMatch;

  // Serial devices are instances of class IOSerialBSDClient.
  // Create a matching dictionary to find those instances.
  classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
  if (classesToMatch == NULL) {
      printf("IOServiceMatching returned a NULL dictionary.\n");
  }
  else {
      // Look for devices that claim to be modems.
      CFDictionarySetValue(classesToMatch,
                           CFSTR(kIOSerialBSDTypeKey),
                           CFSTR(kIOSerialBSDAllTypes));
  }

  // Get an iterator across all matching devices.
  kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, matchingServices);
  if (KERN_SUCCESS != kernResult) {
      printf("IOServiceGetMatchingServices returned %d\n", kernResult);
  }

  return kernResult;
}

static bool getModemPath(io_iterator_t serialPortIterator, char *bsdPath, CFIndex maxPathSize, int usb_vid, int usb_pid)
{
  io_object_t     modemService;
  int             vid;
  int             pid;

  // Initialize the returned path
  *bsdPath = '\0';

  // Iterate across all modems found. In this example, we bail after finding the first modem.
  while ((modemService = IOIteratorNext(serialPortIterator))) {
    
    // Variable declaration
    int pid, vid;
    CFTypeRef bsdPathAsCFString, cf_vendor, cf_product;    

    cf_vendor = IORegistryEntrySearchCFProperty(modemService, kIOServicePlane,
                                                CFSTR("idVendor"),
                                                kCFAllocatorDefault,
                                                kIORegistryIterateRecursively
                                                | kIORegistryIterateParents);

    cf_product = IORegistryEntrySearchCFProperty(modemService, kIOServicePlane,
                                                CFSTR("idProduct"),
                                                kCFAllocatorDefault,
                                                kIORegistryIterateRecursively
                                                | kIORegistryIterateParents);
    
    bsdPathAsCFString = IORegistryEntryCreateCFProperty(modemService,
                                                        CFSTR(kIOCalloutDeviceKey),
                                                        kCFAllocatorDefault,
                                                        0);

    
    // Decode & print port, VID & PID
    if (cf_vendor && cf_product && bsdPathAsCFString &&
        CFNumberGetValue(cf_vendor , kCFNumberIntType, &vid)  &&
        CFNumberGetValue(cf_product, kCFNumberIntType, &pid) &&
        CFStringGetCString(bsdPathAsCFString, bsdPath, maxPathSize, kCFStringEncodingUTF8)){
        
        if(usb_vid == vid && usb_pid == pid) {
          printf("USB device found. Port: %s USB VID: 0x%04X PID: 0x%04X\n", bsdPath, vid, pid);
          return true;
        }
    }

    // Release CFTypeRef
    if (cf_vendor)  CFRelease(cf_vendor);
    if (cf_product) CFRelease(cf_product);
    if (bsdPathAsCFString) CFRelease(bsdPathAsCFString);
  }

  return false;
}

int main(int argc, const char * argv[])
{
    kern_return_t   kernResult;
    io_iterator_t   serialPortIterator;
    char            bsdPath[MAXPATHLEN];

    char * end_ptr;  
    int usb_vid;
    int usb_pid;
    
    char* token;
    
    usb_vid = strtol(argv[1], &end_ptr, 0);
    if (*end_ptr || (end_ptr == argv[1])) {
      printf("Could not parse argument %s\n", argv[1]);
      return 0;
    }
    usb_pid = strtol(argv[2], &end_ptr, 0);
    if (*end_ptr || (end_ptr == argv[1])) {
      printf("Could not parse argument %s\n", argv[2]);
      return 0;
    }
    
    kernResult = findModems(&serialPortIterator);

    if(KERN_SUCCESS != kernResult || !getModemPath(serialPortIterator, bsdPath, sizeof(bsdPath), usb_vid, usb_pid)) {
      printf("USB device not found.\n");
    }

    IOObjectRelease(serialPortIterator);

    return EX_OK;
}

@dl8dtl
Copy link
Contributor

dl8dtl commented Mar 24, 2022

Cool

I wouldn't consider it for v7.0 though, there are enough other things still on the plate, and I'd rather have a release anytime soon than deferring it further by having to implement and debug a completely new feature.

@MCUdude
Copy link
Collaborator Author

MCUdude commented Mar 24, 2022

I wouldn't consider it for v7.0 though, there are enough other things still on the plate, and I'd rather have a release anytime soon than deferring it further by having to implement and debug a completely new feature.

Very well, let's continue the discussion after 7.0 is released. Is there anything I or we can do to help out preparing for a release?

@dl8dtl
Copy link
Contributor

dl8dtl commented Mar 24, 2022

Is there anything I or we can do to help out preparing for a release?

Well, you already did a great job by walking through the old issues.
As I already wrote you, I'd like to have a look at that WiFi-based programming tool, and try making the timeout tweaks a little less hacky and more generic, but otherwise I'm fine with the current state.

@mcuee
Copy link
Collaborator

mcuee commented May 9, 2022

I remember python-serial has already got this done.
https://pyserial.readthedocs.io/en/latest/tools.html

serial.tools.list_ports.comports(include_links=False)
Platform:	Posix (/dev files)
Platform:	Linux (/dev files, sysfs)
Platform:	OSX (iokit)
Platform:	Windows (setupapi, registry)

Example run log under Windows.

(py39x64venv) PS C:\work\python\pyserial\serial\tools> python -m serial.tools.list_ports -v
COM14
    desc: Arduino Leonardo (COM14)
    hwid: USB VID:PID=2341:8036 SER=5&586B51A&0&1 LOCATION=1-1:x.0
1 ports found

Unfortunately it does not work under FreeBSD, just as what the document says.

(mypy38venv) mcuee@freebsdx64vm:~/build $ python -m serial.tools.list_ports -v
/dev/cuaU0          
    desc: n/a
    hwid: n/a
1 ports found

@MCUdude
Copy link
Collaborator Author

MCUdude commented Aug 17, 2022

Is this something that should be considered for the 7.1 release?

We could perhaps support the following syntaxes:

-P usb:0x1A86: 0x7523 or -P usb:1A86:7523 like @mariusgreuel's fork supports
-P ch340, where the serial adapter chip is specified in avrdude.conf.
or ft232:[serial_number] if two identical USB to serial adapters are present.

I'm having a hard time figuring out how to deal with config_gram.y, but we would probably need to extract id, desc, usbvid, usbpid, and place them in a struct. Then path and serno can be found using OS specific code. I'm able to extract the path and serial number on macOS using Apple's framework, IOKit, but have not tried on other operating systems

typedef struct serialport_t {
  LISTID     id;
  const char *desc;
  int        usbvid;
  LISTID     usbpid;
  char       path[MAXLEN];
  char       serno[MAXLEN];
} SERIALPORT;

Output from MacOS test program:

$ ./usb_vid_pid_test 
Found modem. Port: /dev/cu.usbserial-1410 USB VID: 0x1A86 PID: 0x7523, S/N: 
Found modem. Port: /dev/cu.usbmodem14201 USB VID: 0x2341 PID: 0x0058, S/N: 426D8DB85151363448202020FF16154A

@mcuee
Copy link
Collaborator

mcuee commented Aug 17, 2022

typedef struct serialport_t {
  LISTID     id;
  const char *desc;
  int        usbvid;
  LISTID     usbpid;
  char       path[MAXLEN];
  char       serno[MAXLEN];
} SERIALPORT;

Looks like a good proposal. The path will be OS specific.

It is kind of also similar to pyserial output.

(py310venv) C:\work\python>  python -m serial.tools.list_ports -v
COM7
    desc: USB-SERIAL CH340 (COM7)
    hwid: USB VID:PID=1A86:7523 SER= LOCATION=1-2.3.2.2
COM8
    desc: USB Serial Device (COM8)
    hwid: USB VID:PID=2341:0058 SER=0CB8B0715153314639202020FF0C0B23 LOCATION=1-2
2 ports found

@stefanrueger
Copy link
Collaborator

I would use const char * over char [MAXLEN]. I assume these strings won't be written to later on. I am using the libavrdude function cache_string(str) for the assignment in config_gram.y, which has the advantage that a string that's used hundreds of times will only be stored once, there is no need to strdup() these strings when doing a pgm_dup() and one does not have to free them on pgm_free(). Right now, the PROGRAMMER structure has the following USB related entries:

  int usbvid;
  LISTID usbpid;
  const char *usbdev;
  const char *usbsn;
  const char *usbvendor;
  const char *usbproduct;

I know little about USB (both hardware and software); is serno something you read from the physical programmer at runtime ot is that known as a thing associated to the type of programmer/manufacturer?

@mcuee
Copy link
Collaborator

mcuee commented Aug 17, 2022

 int usbvid;
  LISTID usbpid;
  const char *usbdev;
  const char *usbsn;
  const char *usbvendor;
  const char *usbproduct;

Great, just needs to add two entries.

 const char *desc;
 const char *path; (?)

The path may not be const since it may change for some use cases.

USB serial number needs to read from run-time and some device may not have serial number. And by USB spec the device either has no serial number or unique serial number (but some devices may violate this spec).

ABCs of USB:
https://www.usbmadesimple.co.uk/

@dl8dtl
Copy link
Contributor

dl8dtl commented Aug 17, 2022

serno is one of the standard USB string descriptors, like the vendor and product name. It is optional (device is not required to implement it).

Edit: mcuee was faster ;-)

@stefanrueger
Copy link
Collaborator

Thanks @mcuee and @dl8dtl. Some programmers already read in the serial number at run time. So, serno cannot be put into avrdude.conf.

It's likely that const char * is suitable for path. It's OK to assign different strings to a const char * during runtime, but it's not OK to write to the strings so assigned. If the string needs writing to at runtime (eg, want to replace all / with nul) then either an array is appropriate or one needs to strdup() a copy of the const char * variable first. The size for a path is some 4096 (in Linux) and that's sometimes a waste of space. For example, we used to have a 4096 byte char array for the path of the configuration file for each of the 300+ parts and 100+ programmers, so I changed that to const char *, so the full path of avrdude.conf is only stored once using cache_string(), and this turned out to be useful for many other string-like components for the parts and programmers.

@MCUdude
Copy link
Collaborator Author

MCUdude commented Aug 17, 2022

It is perhaps a good idea to move the OS specific things into separate .c and .h files. Perhaps io.c/h or iousb.c/h?

For reference, here's an updated version of the MacOS test program that finds the path and serial number for all connected serial devices. It is a result of some copy-pasting, but it works as a proof of concept. Perhaps we can figure out a way for Linux and Windows as well?

MacOS USB VID/PID test program
#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/serial/ioss.h>
#include <IOKit/IOBSD.h>

#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/hid/IOHIDKeys.h>

// Function prototypes
static kern_return_t findModems(io_iterator_t *matchingServices);
static kern_return_t getModemPath(io_iterator_t serialPortIterator, char *bsdPath, CFIndex maxPathSize);

CFStringRef find_serial(int idVendor, int idProduct)
{
  CFMutableDictionaryRef matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName);

  CFNumberRef numberRef;
  numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idVendor);
  CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBVendorID), numberRef);
  CFRelease(numberRef);
  numberRef = 0;

  numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idProduct);
  CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBProductID), numberRef);
  CFRelease(numberRef);
  numberRef = 0;

  io_iterator_t iter = NULL;
  if (IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iter) == KERN_SUCCESS) {
    io_service_t usbDeviceRef;
    if ((usbDeviceRef = IOIteratorNext(iter))) {
      CFMutableDictionaryRef dict = NULL;
      if (IORegistryEntryCreateCFProperties(usbDeviceRef, &dict, kCFAllocatorDefault, kNilOptions) == KERN_SUCCESS) {
        CFTypeRef obj = CFDictionaryGetValue(dict, CFSTR(kIOHIDSerialNumberKey));
        if (!obj) {
          obj = CFDictionaryGetValue(dict, CFSTR(kUSBSerialNumberString));
        }
        if (obj) {
          return CFStringCreateCopy(kCFAllocatorDefault, (CFStringRef)obj);
        }
      }
    }
  }
  return NULL;
}

// Returns an iterator across all known modems. Caller is responsible for
// releasing the iterator when iteration is complete.
static kern_return_t findModems(io_iterator_t *matchingServices)
{
  kern_return_t           kernResult;
  CFMutableDictionaryRef  classesToMatch;

  // Serial devices are instances of class IOSerialBSDClient.
  // Create a matching dictionary to find those instances.
  classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
  if (classesToMatch == NULL) {
    printf("IOServiceMatching returned a NULL dictionary.\n");
  }
  else {
    // Look for devices that claim to be modems.
    CFDictionarySetValue(classesToMatch,
                          CFSTR(kIOSerialBSDTypeKey),
                          CFSTR(kIOSerialBSDAllTypes));
  }

  // Get an iterator across all matching devices.
  kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, matchingServices);
  if (KERN_SUCCESS != kernResult)
    printf("IOServiceGetMatchingServices returned %d\n", kernResult);


  return kernResult;
}

static kern_return_t getModemPath(io_iterator_t serialPortIterator, char *bsdPath, CFIndex maxPathSize)
{
  io_object_t   modemService;
  kern_return_t kernResult = KERN_FAILURE;
  bool          modemFound = false;
  int           vid;
  int           pid;

  // Initialize the returned path
  *bsdPath = '\0';

  // Iterate across all modems found. In this example, we bail after finding the first modem.
  while ((modemService = IOIteratorNext(serialPortIterator))) {
    
    // Variable declaration
    int pid, vid;
    CFTypeRef bsdPathAsCFString, cf_vendor, cf_product;    

    cf_vendor = IORegistryEntrySearchCFProperty(modemService, kIOServicePlane,
                                                CFSTR("idVendor"),
                                                kCFAllocatorDefault,
                                                kIORegistryIterateRecursively
                                                | kIORegistryIterateParents);

    cf_product = IORegistryEntrySearchCFProperty(modemService, kIOServicePlane,
                                                CFSTR("idProduct"),
                                                kCFAllocatorDefault,
                                                kIORegistryIterateRecursively
                                                | kIORegistryIterateParents);
    
    bsdPathAsCFString = IORegistryEntryCreateCFProperty(modemService,
                                                        CFSTR(kIOCalloutDeviceKey),
                                                        kCFAllocatorDefault,
                                                        0);

    // Decode & print port, VID & PID
    if (cf_vendor && cf_product && bsdPathAsCFString &&
        CFNumberGetValue(cf_vendor , kCFNumberIntType, &vid)  &&
        CFNumberGetValue(cf_product, kCFNumberIntType, &pid) &&
        CFStringGetCString(bsdPathAsCFString, bsdPath, maxPathSize, kCFStringEncodingUTF8))
    {
      CFStringRef obj = find_serial(vid, pid);
      char serial[256] = {0x00};
      if (obj)
        CFStringGetCString(obj, serial, 256, CFStringGetSystemEncoding());

      printf("Found modem. Port: %s USB VID: 0x%04X PID: 0x%04X, S/N: %s\n", bsdPath, vid, pid, serial);
      modemFound = true;
      kernResult = KERN_SUCCESS;
    }

    // Release CFTypeRef
    if (cf_vendor)  CFRelease(cf_vendor);
    if (cf_product) CFRelease(cf_product);
    if (bsdPathAsCFString) CFRelease(bsdPathAsCFString);
  }

  return kernResult;
}

int main(int argc, const char * argv[])
{
    kern_return_t kernResult;
    io_iterator_t serialPortIterator;
    char          bsdPath[MAXPATHLEN];

    kernResult = findModems(&serialPortIterator);
    if (KERN_SUCCESS != kernResult) {
        printf("No modems were found.\n");
    }

    kernResult = getModemPath(serialPortIterator, bsdPath, sizeof(bsdPath));
    if (KERN_SUCCESS != kernResult) {
        printf("Could not get path for modem.\n");
    }

    IOObjectRelease(serialPortIterator);

    return EX_OK;
}
Output:
$ ./usb_vid_pid_test 
Found modem. Port: /dev/cu.usbserial-1410 USB VID: 0x1A86 PID: 0x7523, S/N: 
Found modem. Port: /dev/cu.usbmodem14201 USB VID: 0x2341 PID: 0x0058, S/N: 426D8DB85151363448202020FF16154A

@mcuee
Copy link
Collaborator

mcuee commented Aug 18, 2022

It's likely that const char * is suitable for path. It's OK to assign different strings to a const char * during runtime, but it's not OK to write to the strings so assigned. If the string needs writing to at runtime (eg, want to replace all / with nul) then either an array is appropriate or one needs to strdup() a copy of the const char * variable first.

Thanks. I am more talking about the following situation.

You can see that the COM port assignment, USB PID and Path can change during run time (basically two different devices already). Maybe we have to treat them as two devices. In that case, I think const char* is okay.

@mcuee
Copy link
Collaborator

mcuee commented Aug 18, 2022

@MCUdude

Just wondering if you can give libserialport a try. Sigrok libserialport supports Windows (MSVC and mingw), Linux, macOS and FreeBSD.

@MCUdude
Copy link
Collaborator Author

MCUdude commented Sep 4, 2022

@mcuee sorry, I totally forgot to reply!

Just wondering if you can give libserialport a try.

Wow, libserialport is a really nice tool! I think libserialport can make serial port discovery much easier! The provided examples are also very straightforward and easy to follow. Just what we need.

Here's the output from the list_ports example:

$ ./list_ports
Getting port list.
Found port: /dev/cu.Bluetooth-Incoming-Port
Found port: /dev/cu.usbserial-1410
Found 2 ports.
Freeing port list.

And here is the output from the port_info example:

$ ./port_info /dev/cu.usbserial-1410 
Looking for port /dev/cu.usbserial-1410.
Port name: /dev/cu.usbserial-1410
Description: USB2.0-Serial
Type: USB
Manufacturer: (null)
Product: USB2.0-Serial
Serial: (null)
VID: 1A86 PID: 7523
Bus: 0 Address: 0
Freeing port.

As you can see, libserialport gives us the /dev path and the USB VID/PID. Exactly what we need!

@mcuee mcuee added the help wanted Extra attention is needed label Jun 24, 2023
@mcuee
Copy link
Collaborator

mcuee commented Jun 25, 2023

@MCUdude and @stefanrueger

As @dl8dtl mentioned, this may add too many platform specific codes to avrdude, do we really want to persue this or we can leave it outside of avrdude?

@stefanrueger
Copy link
Collaborator

Seems like there is almost a solution that can be abstracted away. We need a champion, though. Sounds like this might be @MCUdude? If not I'd be fine with dropping this.

@mcuee mcuee removed the help wanted Extra attention is needed label Jun 27, 2023
@MCUdude
Copy link
Collaborator Author

MCUdude commented Jun 29, 2023

We use Avrdude for batch programming at work. I decided to stick with the USBasp programmer (USBISP hardware running modified USBasp firmware), since the COM port number on the Windows computer we use tends to change occasionally, even though there's only one USB to serial adapter connected.

If we instead could specify the USB vid/pid, or even better, the chip name itself, it would be much easier to deal with USB to serial devices when using a script to execute an Avrdude command. (-p ch340, -p 1A86:7523 -pch340:serno or -p 1A86:7523:serno)

Libserialport seems like the perfect match. It supports all major operating systems and has simple examples that do what we want.

I can create a test program that takes a chip name or a USB VID/PID and outputs the serial port. However, I'm not all that good at integrating with avrdude.conf and how integrating the libserialport source code. But I think this would be a very useful addition to Avrdude!

@dl8dtl
Copy link
Contributor

dl8dtl commented Aug 22, 2023

But then, you get compile errors from serialadapter.c.

@dl8dtl
Copy link
Contributor

dl8dtl commented Aug 22, 2023

config_gram.y:1126:37: warning: assignment to 'LISTID' {aka 'void *'} from 'int' makes pointer from integer without a cast [-Wint-conversion]
       current_serialadapter->usbpid = $3->value.number;
                                     ^

If you want a list of PIDs, please have a look at the "programmer" code. It takes a little more than just an assignment there.

@dl8dtl
Copy link
Contributor

dl8dtl commented Aug 22, 2023

Or, just start out with a single PID, and get the remainder running. Turning that into a list of PIDs could be handled later.

@dl8dtl
Copy link
Contributor

dl8dtl commented Aug 22, 2023

diff --git a/src/serialadapter.c b/src/serialadapter.c
index 05c8e3f3..3d90a837 100644
--- a/src/serialadapter.c
+++ b/src/serialadapter.c
@@ -91,7 +91,7 @@ SERIALADAPTER *locate_serialadapter_set(const LISTID serialadapters, const char
   return NULL;
 }
 
-SERIALADAPTER *locate_programmer(const LISTID serialadapters, const char *configid) {
+SERIALADAPTER *locate_serialadapter(const LISTID serialadapters, const char *configid) {
   return locate_serialadapter_set(serialadapters, configid, NULL);
 }
 

@dl8dtl
Copy link
Contributor

dl8dtl commented Aug 22, 2023

With this couple of changes, it compiles and links.
sleep(3600*6) :-)

@MCUdude
Copy link
Collaborator Author

MCUdude commented Aug 23, 2023

For some reason, I'm now getting a desc related error. And desc should not be an unknown component.

$ ./avrdude -curclock -patmega328p -P ch340 
avrdude error: unknown component desc in avrdude.conf main [/Users/hans/Downloads/avrdude/src/avrdude.conf:2535]
Segmentation fault: 11

@dl8dtl
Copy link
Contributor

dl8dtl commented Aug 23, 2023

Hmm, will have a look at it, but not sure whether I can manage it tonight.

@stefanrueger
Copy link
Collaborator

Ahh progress... @MCUdude Try this 0001-Fix-serialadapter-grammar-and-parsing.zip patch which should resolve the desc problem and the (independent) seg fault. Had changed my mail provider for my urclocks domain from gandi to zoho, which (by default!) filters incoming github emails into a notification folder. This kept the inbox nice and tidy making me think that AVRDUDE has a little summer break...

@MCUdude
Copy link
Collaborator Author

MCUdude commented Aug 25, 2023

Thanks for the patch, it certainly did the trick. I've applied the patch and pushed the changes. There seems to be an issue with the CI, but I'm not sure what's wrong. I can build on my computer using make and cmake/build.sh without any errors.

@stefanrueger
Copy link
Collaborator

@MCUdude BTW, this won't compile when libserialport isn't installed and therefore HAVE_LIBSERIALPORT is not set. The functions in serialadapter.c so far are all high-level grammar functions which are useful and needed even if the library isn't available. So, currently the #ifdef should not be there. That is needed just for bits that use the actual library.

@mcuee
Copy link
Collaborator

mcuee commented Aug 26, 2023

Indeed it will not build when libserialport is not installed.

[ 94%] Built target libavrdude
[ 95%] Building C object src/CMakeFiles/avrdude.dir/main.c.o
/home/mcuee/build/avr/avrdude_hans/src/main.c:455:13: warning: ‘serialadapter_not_found’ defined but not used [-Wunused-function]
  455 | static void serialadapter_not_found(const char *serialadapter) {
      |             ^~~~~~~~~~~~~~~~~~~~~~~
[ 97%] Building C object src/CMakeFiles/avrdude.dir/developer_opts.c.o
[ 98%] Building C object src/CMakeFiles/avrdude.dir/whereami.c.o
[100%] Linking C executable avrdude
/usr/bin/ld: CMakeFiles/avrdude.dir/main.c.o: in function `main':
/home/mcuee/build/avr/avrdude_hans/src/main.c:1093: undefined reference to `locate_serialadapter_set'
/usr/bin/ld: libavrdude.a(config.c.o): in function `cleanup_config':
/home/mcuee/build/avr/avrdude_hans/src/config.c:172: undefined reference to `serialadapter_free'
/usr/bin/ld: libavrdude.a(config_gram.c.o): in function `yyparse':
/home/mcuee/build/avr/avrdude_hans/src/config_gram.y:327: undefined reference to `locate_serialadapter'
/usr/bin/ld: /home/mcuee/build/avr/avrdude_hans/src/config_gram.y:334: undefined reference to `serialadapter_free'
/usr/bin/ld: /home/mcuee/build/avr/avrdude_hans/src/config_gram.y:347: undefined reference to `serialadapter_new'

After the installation of libserialport it will build. The warning is still there though.

[ 94%] Built target libavrdude
[ 95%] Building C object src/CMakeFiles/avrdude.dir/main.c.o
/home/mcuee/build/avr/avrdude_hans/src/main.c:455:13: warning: ‘serialadapter_not_found’ defined but not used [-Wunused-function]
  455 | static void serialadapter_not_found(const char *serialadapter) {
      |             ^~~~~~~~~~~~~~~~~~~~~~~
[ 97%] Building C object src/CMakeFiles/avrdude.dir/developer_opts.c.o
[ 98%] Linking C executable avrdude
[100%] Built target avrdude

Build succeeded.

Run

sudo cmake --build build_linux --target install

to install.

@stefanrueger
Copy link
Collaborator

| an issue with the CI

@MCUdude I had a look and I predict it is the wrongly placed #ifdef HAVE_LIBSERIALPORT in serialadapter.c.

@dl8dtl
Copy link
Contributor

dl8dtl commented Aug 27, 2023

Agreed.
Two possible solutions I'm seeing:

  1. Make that entire serialport stuff dependant from whether HAVE_LIBSERIALPORT is set. We've been doing such for HAVE_PARPORT, for example: avrdude.conf.in has conditionals around that, so if the target platform doesn't support parport, the resulting avrdude.conf doesn't get the respective definitions. Still, the grammar needs to allow for all serialport-related elements to be there, but the action bodies inside the grammar could be turned off with ifdef HAVE_LIBSERIALPORT. In that case, the entire serialadapter.c file can be disabled if the feature is unavailable.
  2. Make the decision at runtime. In that case, avrdude.conf and the grammar always have all the serialport-stuff around. serialadapter.c needs to always define all related functions, so the grammar can be parsed, and the related objects can be collected. Then, only if an actual serialadapter access is requested (i.e. anything in -P appears beyond -Pusb), it is checked whether HAVE_LIBSERIALPORT is set, and the access is rejected if not. We've been doing it that way for -Pusb already.

@stefanrueger
Copy link
Collaborator

stefanrueger commented Aug 27, 2023

I have a mild preference for 2. It is a bit of a pain to have avrdude.conf dependent on software configuration. In fact that's the main reason why I haven't implemented a developer option to create the full avrdude.conf file in canonical form. Right now it's -p*/s for the parts and -c*/s for the programmers. @MCUdude it would be cool if there was a -P*/s for the serial adapters.

The (by far) easier solution for the grammar for serial adapters would have been to do what C++ did with classes and structs: They are internally virtually identical (except for that inheritance for one is by default private for one and public for the other) but their use in practice is very different. Same could be done here: the C structure for serial adapters could simply be a one-line typedef of the programmer structure (actually, serial adapters only need a subset of the programmer components) and lexer.l could make serialadapter an alias to programmer, a one-line change. The grammar would not need to change at all. Serial adapters are then internally kept in the list of programmers (without the user knowing about that). This necessitates a small function that, given a programmer structure, determines whether it is actually a serial adapter definition (algorithm: the only initialised elements are the subset for serialadapters? yes!). This so that when the developer option -c*/s outputs its understanding of the list of programmer structures it prints the correct keyword, serialadapter or programmer. And inheritance for serial adapters comes for free, too.

@stefanrueger
Copy link
Collaborator

algorithm: the only initialised elements are the subset for serialadapters? yes!

Easier alternative algos:

  • prog_modes == 0? It's a serial adapter!
  • type not set? It's a serial adapter!

@MCUdude
Copy link
Collaborator Author

MCUdude commented Aug 27, 2023

The (by far) easier solution for the grammar for serial adapters would have been to do what C++ did with classes and structs...

As I've mentioned before, dealing with the grammar is what I think is the most difficult thing with the Avrdude project. If you have suggestions on how the work-in-progress can be improved, I'm more than willing to accept a patch or a PR if that makes the code neater and/or easier to maintain. Actually, feel free to do as much or as little as you want. I'm a little busy with other things next week, so I can't promise much progress over the next couple of days. I'm just glad the idea of having -c ch340, -c ch340:[sernum], -c [vid]:[pid], or -c [vid]:[pid]:[sernum] support may get implemented at some point!

@mcuee
Copy link
Collaborator

mcuee commented Aug 27, 2023

I have a mild preference for 2

@stefanrueger
I am not a programmer but somehow I feel Option 2 is more complicated to implement. It this correct or wrong? Thanks.

@stefanrueger
Copy link
Collaborator

This needs a design decision.

| suggestions on how the work-in-progress can be improved

I would make minimal changes to the grammar and use the programmer structure for the serial adapters. Let me create a branch based on that idea, and then you can compare your current approach (which isn't finished as it misses treatment of the developer options) and my approach and weigh up the advantages and disadvantages of either.

Ideally, I'd merge the current two PRs first, as to minimise the potential for merge conflicts.

OK?

@MCUdude
Copy link
Collaborator Author

MCUdude commented Aug 27, 2023

Yes please 😁

@stefanrueger
Copy link
Collaborator

I have a mild preference for 2

@stefanrueger I am not a programmer but somehow I feel Option 2 is more complicated to implement. It this correct or wrong? Thanks.

I am genuinely not aware why one would be more complicated than the other. In either case the code has to consider that the library is there or not, both so it can be compiled in either case and that the functionality changes accordingly.

@mcuee
Copy link
Collaborator

mcuee commented Aug 27, 2023

Ideally, I'd merge the current two PRs first, as to minimise the potential for merge conflicts.

Yes I think you can merge the two remaing PRs.

@mcuee
Copy link
Collaborator

mcuee commented Aug 27, 2023

I would make minimal changes to the grammar and use the programmer structure for the serial adapters. Let me create a branch based on that idea, and then you can compare your current approach (which isn't finished as it misses treatment of the developer options) and my approach and weigh up the advantages and disadvantages of either.

@stefanrueger and @MCUdude
I think it is good to have a WIP PR in this repo.

Hopefully it may attract a few more users to come and review or test.

@dl8dtl
Copy link
Contributor

dl8dtl commented Aug 27, 2023

I am not a programmer but somehow I feel Option 2 is more complicated to implement.

I don't think it is. It's just a matter of where the #ifdefs are placed.
I'm not so sure whether trying to combine the grammar with programmers really makes it easier to handle.

@mcuee
Copy link
Collaborator

mcuee commented Aug 28, 2023

@MCUdude

Somehow I got segmentation fault under Windows. I will check again.

$ ./port_info.exe COM4
Looking for port COM4.
Port name: COM4
Description: USB-SERIAL CH340 (COM4)
Type: USB
Manufacturer: (null)
Product: USB Serial
Serial: (null)
VID: 1A86 PID: 7523
Bus: 3 Address: 20
Freeing port.

$ ./port_finder.exe ch340
Found port:
id:     ch340
desc:   USB-SERIAL CH340 (COM4)
port:   COM4
serno:

MINGW64 /c/work/avr/avrdude_test/avrdude_hans/build_mingw64_nt-10.0-19045/src
$ ldd ./avrdude.exe
        ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ff889670000)
        KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ff888df0000)
        KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ff8870f0000)
        msvcrt.dll => /c/WINDOWS/System32/msvcrt.dll (0x7ff888540000)
        HID.DLL => /c/WINDOWS/SYSTEM32/HID.DLL (0x7ff8841e0000)
        SETUPAPI.dll => /c/WINDOWS/System32/SETUPAPI.dll (0x7ff889010000)
        cfgmgr32.dll => /c/WINDOWS/System32/cfgmgr32.dll (0x7ff8875f0000)
        ucrtbase.dll => /c/WINDOWS/System32/ucrtbase.dll (0x7ff8873f0000)
        RPCRT4.dll => /c/WINDOWS/System32/RPCRT4.dll (0x7ff8878f0000)
        bcrypt.dll => /c/WINDOWS/System32/bcrypt.dll (0x7ff887520000)
        USER32.dll => /c/WINDOWS/System32/USER32.dll (0x7ff888170000)
        win32u.dll => /c/WINDOWS/System32/win32u.dll (0x7ff8874f0000)
        GDI32.dll => /c/WINDOWS/System32/GDI32.dll (0x7ff888d20000)
        gdi32full.dll => /c/WINDOWS/System32/gdi32full.dll (0x7ff886d50000)
        msvcp_win.dll => /c/WINDOWS/System32/msvcp_win.dll (0x7ff887550000)
        WS2_32.dll => /c/WINDOWS/System32/WS2_32.dll (0x7ff8895b0000)

$ ./avrdude -c urclock -p m328p -P COM4 -v

avrdude: Version 7.2-20230825 (e7e8a570)
         Copyright the AVRDUDE authors;
         see https://github.com/avrdudes/avrdude/blob/main/AUTHORS

         System wide configuration file is C:\work\avr\avrdude_test\avrdude_hans\build_mingw64_nt-10.0-19045\src\avrdude.conf

Segmentation fault

$ ./avrdude -c urclock -p m328p -P ch340
port desc: WCH CH340 USB to serial adapter, vid: 0x1a86, pid: 0x7523
avrdude error: cannot open port ch340: The system cannot find the file specified.

avrdude error: unable to open programmer urclock on port ch340

avrdude done.  Thank you.

No issue with git main.

C:\work\avr\avrdude_test\avrdude_bin> .\avrdude_git -p m328p -c urclock -P COM4
avrdude_git: AVR device initialized and ready to accept instructions
avrdude_git: device signature = 0x1e950f (probably m328p)

avrdude_git done.  Thank you.

@dl8dtl
Copy link
Contributor

dl8dtl commented Aug 28, 2023

main.c contains:

ser = locate_serialadapter_set(serialadapters, prt, &prt);

The result is then used without checking for NULL. I guess that's just test code though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants