diff --git a/minichlink/Makefile b/minichlink/Makefile index 6d42c72d20536afc93317717bf2fe15f954f0529..964341c11b51b66accf732be82639d887df7c0cd 100644 --- a/minichlink/Makefile +++ b/minichlink/Makefile @@ -1,7 +1,7 @@ TOOLS:=minichlink minichlink.so CFLAGS:=-O0 -g3 -Wall -C_S:=minichlink.c pgm-wch-linke.c pgm-esp32s2-ch32xx.c nhc-link042.c minichgdb.c pgm-b003fun.c +C_S:=minichlink.c pgm-wch-linke.c pgm-esp32s2-ch32xx.c nhc-link042.c ardulink.c serial_dev.c pgm-b003fun.c minichgdb.c # General Note: To use with GDB, gdb-multiarch # gdb-multilib {file} @@ -23,9 +23,6 @@ else LIBINCLUDES:=$(shell pkg-config --libs-only-L libusb-1.0) INCS:=$(INCLUDES) $(LIBINCLUDES) endif - - # Until it's supported on Windows, add the Ardulink file here. - C_S:=$(C_S) ardulink.c endif all : $(TOOLS) diff --git a/minichlink/ardulink.c b/minichlink/ardulink.c index 5da4d10640b31c44aaa2c8651182aa14aedb8a08..82284c66d8e0be8a342b492b33419e4f82cb0280 100644 --- a/minichlink/ardulink.c +++ b/minichlink/ardulink.c @@ -1,17 +1,10 @@ #include <stdint.h> #include <stdio.h> #include <stdlib.h> - -#include <termios.h> -#include <unistd.h> -#include <fcntl.h> -#include <sys/ioctl.h> - -#include <errno.h> - +#include "serial_dev.h" #include "minichlink.h" -void * TryInit_Ardulink(void); +void * TryInit_Ardulink(const init_hints_t*); static int ArdulinkWriteReg32(void * dev, uint8_t reg_7_bit, uint32_t command); static int ArdulinkReadReg32(void * dev, uint8_t reg_7_bit, uint32_t * commandresp); @@ -19,10 +12,9 @@ static int ArdulinkFlushLLCommands(void * dev); static int ArdulinkExit(void * dev); static int ArdulinkTargetReset(void * dev, int reset); - typedef struct { struct ProgrammerStructBase psb; - int fd; + serial_dev_t serial; } ardulink_ctx_t; int ArdulinkWriteReg32(void * dev, uint8_t reg_7_bit, uint32_t command) @@ -38,10 +30,10 @@ int ArdulinkWriteReg32(void * dev, uint8_t reg_7_bit, uint32_t command) buf[4] = (command >> 16) & 0xff; buf[5] = (command >> 24) & 0xff; - if (write(((ardulink_ctx_t*)dev)->fd, buf, 6) == -1) + if (serial_dev_write(&((ardulink_ctx_t*)dev)->serial, buf, 6) == -1) return -errno; - if (read(((ardulink_ctx_t*)dev)->fd, buf, 1) == -1) + if (serial_dev_read(&((ardulink_ctx_t*)dev)->serial, buf, 1) == -1) return -errno; return buf[0] == '+' ? 0 : -EPROTO; @@ -53,10 +45,10 @@ int ArdulinkReadReg32(void * dev, uint8_t reg_7_bit, uint32_t * commandresp) buf[0] = 'r'; buf[1] = reg_7_bit; - if (write(((ardulink_ctx_t*)dev)->fd, buf, 2) == -1) + if (serial_dev_write(&((ardulink_ctx_t*)dev)->serial, buf, 2) == -1) return -errno; - if (read(((ardulink_ctx_t*)dev)->fd, buf, 4) == -1) + if (serial_dev_read(&((ardulink_ctx_t*)dev)->serial, buf, 4) == -1) return -errno; *commandresp = (uint32_t)buf[0] | (uint32_t)buf[1] << 8 | \ @@ -74,7 +66,7 @@ int ArdulinkFlushLLCommands(void * dev) int ArdulinkExit(void * dev) { - close(((ardulink_ctx_t*)dev)->fd); + serial_dev_close(&((ardulink_ctx_t*)dev)->serial); free(dev); return 0; } @@ -86,10 +78,10 @@ int ArdulinkTargetReset(void * dev, int reset) { // Assert reset. c = reset ? 'a' : 'A'; - if (write(((ardulink_ctx_t*)dev)->fd, &c, 1) == -1) + if (serial_dev_write(&((ardulink_ctx_t*)dev)->serial, &c, 1) == -1) return -errno; - if (read(((ardulink_ctx_t*)dev)->fd, &c, 1) == -1) + if (serial_dev_read(&((ardulink_ctx_t*)dev)->serial, &c, 1) == -1) return -errno; if (c != '+') @@ -99,67 +91,55 @@ int ArdulinkTargetReset(void * dev, int reset) { return 0; } -void * TryInit_Ardulink(void) +void * TryInit_Ardulink(const init_hints_t* hints) { ardulink_ctx_t *ctx; - struct termios attr; char first; - int argp = TIOCM_DTR; if (!(ctx = calloc(sizeof(ardulink_ctx_t), 1))) { perror("calloc"); return NULL; } - if ((ctx->fd = open("/dev/ttyACM0", O_RDWR | O_NOCTTY)) == -1) { - perror("open"); - return NULL; - } - - if (tcgetattr(ctx->fd, &attr) == -1) { - perror("tcgetattr"); - return NULL; - } - - cfmakeraw(&attr); - cfsetspeed(&attr, 115200); - - if (tcsetattr(ctx->fd, TCSANOW, &attr) == -1) { - perror("tcsetattr"); - return NULL; + const char* serial_to_open = NULL; + // Get the serial port that shall be opened. + // First, if we have a directly set serial port hint, use that. + // Otherwise, use the environment variable MINICHLINK_SERIAL. + // If that also doesn't exist, fall back to the default serial. + if (hints && hints->serial_port != NULL) { + serial_to_open = hints->serial_port; } - - // Arduino DTR reset. - if (ioctl(ctx->fd, TIOCMBIC, &argp) == -1) { - perror("ioctl"); - return NULL; + else if ((serial_to_open = getenv("MINICHLINK_SERIAL")) == NULL) { + // fallback + serial_to_open = DEFAULT_SERIAL_NAME; } - if (tcdrain(ctx->fd) == -1) { - perror("tcdrain"); + if (serial_dev_create(&ctx->serial, serial_to_open, 115200) == -1) { + perror("create"); return NULL; } - if (ioctl(ctx->fd, TIOCMBIS, &argp) == -1) { - perror("ioctl"); + if (serial_dev_open(&ctx->serial) == -1) { + perror("open"); return NULL; } - if (tcdrain(ctx->fd) == -1) { - perror("tcdrain"); + // Arduino DTR reset. + if (serial_dev_do_dtr_reset(&ctx->serial) == -1) { + perror("dtr reset"); return NULL; } // Flush anything that might be in the RX buffer, we need the sync char. - if (tcflush(ctx->fd, TCIFLUSH) == -1) { - perror("tcflush"); + if (serial_dev_flush_rx(&ctx->serial) == -1) { + perror("flush rx"); return NULL; } // Let the bootloader do its thing. - sleep(3); + usleep(3UL*1000UL*1000UL); - if (read(ctx->fd, &first, 1) == -1) { + if (serial_dev_read(&ctx->serial, &first, 1) == -1) { perror("read"); return NULL; } diff --git a/minichlink/minichlink.c b/minichlink/minichlink.c index 7e2e154aa0ad7cb0f62d2ddf8a55ba73a7cf90ea..2bb7a04429fea0d0546aeabe20ee4df20ccfc03d 100644 --- a/minichlink/minichlink.c +++ b/minichlink/minichlink.c @@ -8,17 +8,16 @@ #include <stdio.h> #include <string.h> #include <stdlib.h> +#include <getopt.h> #include "minichlink.h" #include "../ch32v003fun/ch32v003fun.h" #if defined(WINDOWS) || defined(WIN32) || defined(_WIN32) -#define DISABLE_ARDULINK void Sleep(uint32_t dwMilliseconds); #else #include <unistd.h> #endif - static int64_t StringToMemoryAddress( const char * number ) __attribute__((used)); static void StaticUpdatePROGBUFRegs( void * dev ) __attribute__((used)); static int InternalUnlockBootloader( void * dev ) __attribute__((used)); @@ -27,7 +26,7 @@ int DefaultReadBinaryBlob( void * dev, uint32_t address_to_read_from, uint32_t r void TestFunction(void * v ); struct MiniChlinkFunctions MCF; -void * MiniCHLinkInitAsDLL( struct MiniChlinkFunctions ** MCFO ) +void * MiniCHLinkInitAsDLL( struct MiniChlinkFunctions ** MCFO, const init_hints_t* init_hints ) { void * dev = 0; if( (dev = TryInit_WCHLinkE()) ) @@ -46,12 +45,10 @@ void * MiniCHLinkInitAsDLL( struct MiniChlinkFunctions ** MCFO ) { fprintf( stderr, "Found B003Fun Bootloader\n" ); } -#ifndef DISABLE_ARDULINK - else if ((dev = TryInit_Ardulink())) + else if ((dev = TryInit_Ardulink(init_hints))) { fprintf( stderr, "Found Ardulink Programmer\n" ); } -#endif else { fprintf( stderr, "Error: Could not initialize any supported programmers\n" ); @@ -66,6 +63,27 @@ void * MiniCHLinkInitAsDLL( struct MiniChlinkFunctions ** MCFO ) return dev; } +void parse_possible_init_hints(int argc, char **argv, init_hints_t *hints) +{ + if (!hints) + return; + int c; + opterr = 0; + /* we're only interested in the value for the COM port, given in a -c parameter */ + /* the '-' is really important so that getopt does not permutate the argv array and messes up parsing later */ + while ((c = getopt(argc, argv, "-c:")) != -1) + { + switch (c) + { + case 'c': + // we can use the pointer as-is because it points in our + // argv array and that is stable. + hints->serial_port = optarg; + break; + } + } +} + #if !defined( MINICHLINK_AS_LIBRARY ) && !defined( MINICHLINK_IMPORT ) int main( int argc, char ** argv ) { @@ -73,7 +91,10 @@ int main( int argc, char ** argv ) { goto help; } - void * dev = MiniCHLinkInitAsDLL( 0 ); + init_hints_t hints; + memset(&hints, 0, sizeof(hints)); + parse_possible_init_hints(argc, argv, &hints); + void * dev = MiniCHLinkInitAsDLL( 0, &hints ); if( !dev ) { fprintf( stderr, "Error: Could not initialize any supported programmers\n" ); @@ -151,6 +172,16 @@ keep_going: else goto unimplemented; break; + case 'c': + // COM port argument already parsed previously + // we still need to skip the next argument + iarg+=1; + if( iarg >= argc ) + { + fprintf( stderr, "-c argument (COM port) required 2 arguments\n" ); + goto unimplemented; + } + break; case 'u': if( MCF.Unbrick ) MCF.Unbrick( dev ); @@ -560,6 +591,7 @@ help: fprintf( stderr, " -5 Enable 5V\n" ); fprintf( stderr, " -t Disable 3.3V\n" ); fprintf( stderr, " -f Disable 5V\n" ); + fprintf( stderr, " -c [serial port for Ardulink, try /dev/ttyACM0 or COM11 etc]\n" ); fprintf( stderr, " -u Clear all code flash - by power off (also can unbrick)\n" ); fprintf( stderr, " -b Reboot out of Halt\n" ); fprintf( stderr, " -e Resume from halt\n" ); @@ -1543,6 +1575,11 @@ static int DefaultHaltMode( void * dev, int mode ) } #endif + // pull reset line back to 0 again (NO RESET) + if (MCF.TargetReset) { + MCF.TargetReset(dev, 0); + } + iss->processor_in_mode = mode; return 0; } diff --git a/minichlink/minichlink.exe b/minichlink/minichlink.exe index be95741c4b2eb1f14e5805055bedb7a41cab47d1..a52fdbc65872c0fc70a1facfb7fa9cbe22a35552 100644 Binary files a/minichlink/minichlink.exe and b/minichlink/minichlink.exe differ diff --git a/minichlink/minichlink.h b/minichlink/minichlink.h index 37960eacafe4751fdb2424adb80129134697a714..77310efb917dcf421acde7fc7436cec613c5fd32 100644 --- a/minichlink/minichlink.h +++ b/minichlink/minichlink.h @@ -145,16 +145,22 @@ struct InternalState #define DLLDECORATE #endif -void * MiniCHLinkInitAsDLL(struct MiniChlinkFunctions ** MCFO) DLLDECORATE; +/* initialization hints for init functions */ +/* could be expanded with more in the future (e.g., PID/VID hints, priorities, ...)*/ +/* not all init functions currently need these hints. */ +typedef struct { + const char* serial_port; +} init_hints_t; + +void * MiniCHLinkInitAsDLL(struct MiniChlinkFunctions ** MCFO, const init_hints_t* init_hints) DLLDECORATE; extern struct MiniChlinkFunctions MCF; - // Returns 'dev' on success, else 0. void * TryInit_WCHLinkE(void); void * TryInit_ESP32S2CHFUN(void); void * TryInit_NHCLink042(void); void * TryInit_B003Fun(void); -void * TryInit_Ardulink(); +void * TryInit_Ardulink(const init_hints_t*); // Returns 0 if ok, populated, 1 if not populated. int SetupAutomaticHighLevelFunctions( void * dev ); diff --git a/minichlink/serial_dev.c b/minichlink/serial_dev.c new file mode 100644 index 0000000000000000000000000000000000000000..f39780fa580f833a47a6ecb8ee756957937637fc --- /dev/null +++ b/minichlink/serial_dev.c @@ -0,0 +1,173 @@ +#include "serial_dev.h" + +int serial_dev_create(serial_dev_t *dev, const char* port, unsigned baud) { + if (!dev) + return -1; + dev->port = port; + dev->baud = baud; + #ifdef IS_WINDOWS + dev->handle = INVALID_HANDLE_VALUE; + #else + dev->fd = -1; + #endif + return 0; +} + +int serial_dev_open(serial_dev_t *dev) { + fprintf(stderr, "Opening serial port %s at %u baud.\n", dev->port, dev->baud); +#ifdef IS_WINDOWS + // Windows quirk: port = "COM10" is invalid, has to be encoded as "\\.\COM10". + // This also works for COM below 9. So, let's give the user the ability to use + // any "COMx" string and just prepend the "\\.\". + char winPortName[64]; + if(dev->port[0] != '\\') { + snprintf(winPortName, sizeof(winPortName), "\\\\.\\%s", dev->port); + } else { + // copy verbatim if string already starts with a '\' + snprintf(winPortName, sizeof(winPortName), "%s", dev->port); + } + dev->handle = CreateFileA(winPortName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0,0); + if (dev->handle == INVALID_HANDLE_VALUE) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + fprintf(stderr, "Serial port %s not found.\n", dev->port); + // weird: without this, errno = 0 (no error). + _set_errno(ERROR_FILE_NOT_FOUND); + return -1; // Device not found + } + // Error while opening the device + return -1; + } + DCB dcbSerialParams; + dcbSerialParams.DCBlength = sizeof(dcbSerialParams); + if (!GetCommState(dev->handle, &dcbSerialParams)) { + return -1; + } + // set baud and 8N1 serial formatting + dcbSerialParams.BaudRate = dev->baud; + dcbSerialParams.ByteSize = 8; + dcbSerialParams.StopBits = ONESTOPBIT; + dcbSerialParams.Parity = NOPARITY; + // write back + if (!SetCommState(dev->handle, &dcbSerialParams)){ + return -1; + } + // Set the timeout parameters to "no timeout" (blocking). + // see https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts + COMMTIMEOUTS timeouts; + timeouts.ReadIntervalTimeout = 0; + timeouts.ReadTotalTimeoutConstant = MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = MAXDWORD; + timeouts.WriteTotalTimeoutMultiplier = 0; + // Write the parameters + if (!SetCommTimeouts(dev->handle, &timeouts)) { + return -1; + } +#else + struct termios attr; + if ((dev->fd = open(dev->port, O_RDWR | O_NOCTTY)) == -1) { + perror("open"); + return -1; + } + + if (tcgetattr(dev->fd, &attr) == -1) { + perror("tcgetattr"); + return -1; + } + + cfmakeraw(&attr); + cfsetspeed(&attr, dev->baud); + + if (tcsetattr(dev->fd, TCSANOW, &attr) == -1) { + perror("tcsetattr"); + return -1; + } +#endif + // all okay if we get here + return 0; +} + +int serial_dev_write(serial_dev_t *dev, const void* data, size_t len) { +#ifdef IS_WINDOWS + DWORD dwBytesWritten; + if (!WriteFile(dev->handle, data, len, &dwBytesWritten,NULL)) { + return -1; + } + return (int) dwBytesWritten; +#else + return write(dev->fd, data, len); +#endif +} + +int serial_dev_read(serial_dev_t *dev, void* data, size_t len) { +#ifdef IS_WINDOWS + DWORD dwBytesRead = 0; + if (!ReadFile(dev->handle, data, len, &dwBytesRead, NULL)) { + return -1; + } + return (int) dwBytesRead; +#else + return read(dev->fd, data, len); +#endif +} + +int serial_dev_do_dtr_reset(serial_dev_t *dev) { +#ifdef IS_WINDOWS + // EscapeCommFunction returns 0 on fail + if(EscapeCommFunction(dev->handle, SETDTR) == 0) { + return -1; + } + if(EscapeCommFunction(dev->handle, CLRDTR) == 0) { + return -1; + } +#else + int argp = TIOCM_DTR; + // Arduino DTR reset. + if (ioctl(dev->fd, TIOCMBIC, &argp) == -1) { + perror("ioctl"); + return -1; + } + + if (tcdrain(dev->fd) == -1) { + perror("tcdrain"); + return -1; + } + + if (ioctl(dev->fd, TIOCMBIS, &argp) == -1) { + perror("ioctl"); + return -1; + } +#endif + return 0; +} + +int serial_dev_flush_rx(serial_dev_t *dev) { +#ifdef IS_WINDOWS + // PurgeComm returns 0 on fail + if (PurgeComm(dev->handle, PURGE_RXCLEAR) == 0) { + return -1; + } +#else + if (tcflush(dev->fd, TCIFLUSH) == -1) { + perror("tcflush"); + return -1; + } +#endif + return 0; +} + +int serial_dev_close(serial_dev_t *dev) { +#ifdef IS_WINDOWS + if(!CloseHandle(dev->handle)) { + return -1; + } + dev->handle = INVALID_HANDLE_VALUE; +#else + int ret = 0; + if((ret = close(dev->fd)) != 0) { + return ret; + } + dev->fd = -1; +#endif + return 0; +} \ No newline at end of file diff --git a/minichlink/serial_dev.h b/minichlink/serial_dev.h new file mode 100644 index 0000000000000000000000000000000000000000..a75733f41870308408ce0b2f5b82d20ef7ccfe54 --- /dev/null +++ b/minichlink/serial_dev.h @@ -0,0 +1,48 @@ +#ifndef _SERIAL_DEV_H +#define _SERIAL_DEV_H + +#include <stddef.h> + +#if defined(WINDOWS) || defined(WIN32) || defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#define IS_WINDOWS +#define DEFAULT_SERIAL_NAME "\\\\.\\COM3" +#else +#include <termios.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#define IS_POSIX +#define DEFAULT_SERIAL_NAME "/dev/ttyACM0" +#endif +/* these are available on all platforms */ +#include <errno.h> +#include <unistd.h> +#include <stdio.h> + +typedef struct { + const char* port; + unsigned baud; +#ifdef IS_WINDOWS + HANDLE handle; +#else + int fd; +#endif +} serial_dev_t; + +/* returns 0 if OK */ +int serial_dev_create(serial_dev_t *dev, const char* port, unsigned baud); +/* returns 0 if OK */ +int serial_dev_open(serial_dev_t *dev); +/* returns -1 on write error */ +int serial_dev_write(serial_dev_t *dev, const void* data, size_t len); +/* returns -1 on read error */ +int serial_dev_read(serial_dev_t *dev, void* data, size_t len); +/* returns -1 on reset error */ +int serial_dev_do_dtr_reset(serial_dev_t *dev); +/* returns -1 on flush error */ +int serial_dev_flush_rx(serial_dev_t *dev); +/* returns -1 on close error */ +int serial_dev_close(serial_dev_t *dev); + +#endif