From 65e3fe358a678720cf4a6025c7de0919bb94bdff Mon Sep 17 00:00:00 2001 From: Marius Greuel Date: Wed, 29 Dec 2021 22:27:36 +0100 Subject: [PATCH 01/19] Always upload GitHub Build artifacts, even on failure --- .github/workflows/build_cmake.yml | 47 ++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build_cmake.yml b/.github/workflows/build_cmake.yml index bf7119c5..bcbf72ef 100644 --- a/.github/workflows/build_cmake.yml +++ b/.github/workflows/build_cmake.yml @@ -16,7 +16,7 @@ # along with this program. If not, see . # -name: CMake Build +name: Build on: push: @@ -27,7 +27,7 @@ env: BUILD_TYPE: RelWithDebInfo jobs: - build-ubuntu: + linux-x86_64: runs-on: ubuntu-latest defaults: run: @@ -46,16 +46,22 @@ jobs: libftdi1-dev libhidapi-dev - name: Configure - run: cmake -DDEBUG_CMAKE=1 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -B ${{github.workspace}}/build + run: >- + cmake + -D DEBUG_CMAKE=1 + -D CMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + -B ${{github.workspace}}/build - name: Build - run: cmake --build ../build + run: cmake --build ${{github.workspace}}/build - name: Archive build artifacts + if: always() uses: actions/upload-artifact@v2 with: - name: build-ubuntu + name: linux-x86_64 path: | ${{github.workspace}}/build/* - build-macos: + + macos-x86_64: runs-on: macos-latest defaults: run: @@ -73,16 +79,23 @@ jobs: libftdi hidapi - name: Configure - run: cmake -DCMAKE_C_FLAGS=-I/usr/local/include -DCMAKE_EXE_LINKER_FLAGS=-L/usr/local/Cellar -DDEBUG_CMAKE=1 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -B ${{github.workspace}}/build + run: >- + cmake + -D CMAKE_C_FLAGS=-I/usr/local/include + -D CMAKE_EXE_LINKER_FLAGS=-L/usr/local/Cellar + -D DEBUG_CMAKE=1 + -D CMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + -B ${{github.workspace}}/build - name: Build - run: cmake --build ../build + run: cmake --build ${{github.workspace}}/build - name: Archive build artifacts + if: always() uses: actions/upload-artifact@v2 with: - name: build-macos + name: macos-x86_64 path: | ${{github.workspace}}/build/* - build-mingw: + mingw: runs-on: windows-latest defaults: run: @@ -109,12 +122,18 @@ jobs: mingw-w64-${{matrix.env}}-hidapi mingw-w64-${{matrix.env}}-libftdi - name: Configure - run: cmake -G"MSYS Makefiles" -DDEBUG_CMAKE=1 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -B ../build_${{matrix.sys}} + run: >- + cmake + -G"MSYS Makefiles" + -D DEBUG_CMAKE=1 + -D CMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + -B ../build - name: Build - run: cmake --build ../build_${{matrix.sys}} + run: cmake --build ../build - name: Archive build artifacts + if: always() uses: actions/upload-artifact@v2 with: - name: build-mingw-${{matrix.sys}} + name: mingw-${{matrix.env}} path: | - ${{github.workspace}}/build_${{matrix.sys}}/* + ${{github.workspace}}/build/* From 5246cf1750736f900b14013c8a0e8788b7271783 Mon Sep 17 00:00:00 2001 From: Marius Greuel Date: Wed, 29 Dec 2021 22:18:15 +0100 Subject: [PATCH 02/19] Make Windows version resource accept fewer than four version numbers --- src/windows.rc.in | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/windows.rc.in b/src/windows.rc.in index 21c2b600..e90362f9 100644 --- a/src/windows.rc.in +++ b/src/windows.rc.in @@ -27,10 +27,34 @@ BEGIN 100 "AVRDUDE" END -#define VER_MAJOR @PROJECT_VERSION_MAJOR@ -#define VER_MINOR @PROJECT_VERSION_MINOR@ -#define VER_BUILD @PROJECT_VERSION_PATCH@ -#define VER_REVISION @PROJECT_VERSION_TWEAK@ +#cmakedefine PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ +#cmakedefine PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@ +#cmakedefine PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@ +#cmakedefine PROJECT_VERSION_TWEAK @PROJECT_VERSION_TWEAK@ + +#ifdef PROJECT_VERSION_MAJOR +#define VER_MAJOR PROJECT_VERSION_MAJOR +#else +#define VER_MAJOR 0 +#endif + +#ifdef PROJECT_VERSION_MINOR +#define VER_MINOR PROJECT_VERSION_MINOR +#else +#define VER_MINOR 0 +#endif + +#ifdef PROJECT_VERSION_PATCH +#define VER_BUILD PROJECT_VERSION_PATCH +#else +#define VER_BUILD 0 +#endif + +#ifdef PROJECT_VERSION_TWEAK +#define VER_REVISION PROJECT_VERSION_TWEAK +#else +#define VER_REVISION 0 +#endif #define _STR(s) #s #define _VER_STR(a, b, c, d) _STR(a) "." _STR(b) "." _STR(c) "." _STR(d) From 89c4ab037567d0a9f09705569eb13f57183afc06 Mon Sep 17 00:00:00 2001 From: Joerg Wunsch Date: Fri, 31 Dec 2021 00:07:52 +0100 Subject: [PATCH 03/19] Remove the License subclause --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 616949db..3296487f 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,3 @@ MacOS Brew requires ./configure CPPFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/Cellar ``` -## License - -AVRDUDE is licensed under the [GNU GPLv2](./COPYING). From 302b6eb05adeb858b1a70148fc7ee9cf052ee82e Mon Sep 17 00:00:00 2001 From: MCUdude Date: Sat, 1 Jan 2022 11:21:30 +0100 Subject: [PATCH 04/19] Add PicKit4 and SNAP ISP and PDI programmer options --- src/avrdude.conf.in | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/avrdude.conf.in b/src/avrdude.conf.in index 523137fd..b6418742 100644 --- a/src/avrdude.conf.in +++ b/src/avrdude.conf.in @@ -1274,6 +1274,22 @@ programmer usbpid = 0x2177, 0x2178, 0x2179; ; +programmer + id = "pickit4_pdi"; + desc = "MPLAB(R) PICkit 4 in PDI mode"; + type = "jtagice3_pdi"; + connection_type = usb; + usbpid = 0x2177, 0x2178, 0x2179; +; + +programmer + id = "pickit4_isp"; + desc = "MPLAB(R) PICkit 4 in ISP mode"; + type = "jtagice3_isp"; + connection_type = usb; + usbpid = 0x2177, 0x2178, 0x2179; +; + programmer id = "snap_updi"; desc = "MPLAB(R) SNAP in UPDI mode"; @@ -1282,6 +1298,22 @@ programmer usbpid = 0x217F, 0x2180, 0x2181; ; +programmer + id = "snap_pdi"; + desc = "MPLAB(R) SNAP in PDI mode"; + type = "jtagice3_pdi"; + connection_type = usb; + usbpid = 0x217F, 0x2180, 0x2181; +; + +programmer + id = "snap_isp"; + desc = "MPLAB(R) SNAP in ISP mode"; + type = "jtagice3_isp"; + connection_type = usb; + usbpid = 0x217F, 0x2180, 0x2181; +; + programmer id = "pkobn_updi"; desc = "Curiosity nano (nEDBG) in UPDI mode"; From 2623e7a9fa7bc72f574f15015651630774b03727 Mon Sep 17 00:00:00 2001 From: MCUdude Date: Sat, 1 Jan 2022 11:27:54 +0100 Subject: [PATCH 05/19] Update docs to reflect new programmers --- src/avrdude.1 | 4 ++-- src/doc/avrdude.texi | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/avrdude.1 b/src/avrdude.1 index 940cd499..86c83970 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -216,8 +216,8 @@ The Atmel DFU bootloader is supported in both, FLIP protocol version 1 (AT90USB* and ATmega*U* devices), as well as version 2 (Xmega devices). See below for some hints about FLIP version 1 protocol behaviour. .Pp -The MPLAB(R) PICkit 4, MPLAB(R) SNAP, and Curiosity Nano boards are -supported in UPDI mode. The Curiosity Nano board is dubbed +The MPLAB(R) PICkit 4 and MPLAB(R) SNAP, are supported in ISP, PDI and UPDI mode. +The Curiosity Nano board is supported in UPDI mode. It is dubbed .Dq PICkit on Board , thus the name .Pa pkobn_updi . diff --git a/src/doc/avrdude.texi b/src/doc/avrdude.texi index c45f7e26..4565fe4f 100644 --- a/src/doc/avrdude.texi +++ b/src/doc/avrdude.texi @@ -303,8 +303,8 @@ The Atmel DFU bootloader is supported in both, FLIP protocol version 1 (AT90USB* and ATmega*U* devices), as well as version 2 (Xmega devices). See below for some hints about FLIP version 1 protocol behaviour. -The MPLAB(R) PICkit 4, MPLAB(R) SNAP, and Curiosity Nano boards are -supported in UPDI mode. The Curiosity Nano board is dubbed ``PICkit on +The MPLAB(R) PICkit 4 and MPLAB(R) SNAP are supported in ISP, PDI and UPDI mode. +The Curiosity Nano board is supported in UPDI mode. It is dubbed ``PICkit on Board'', thus the name @code{pkobn_updi}. SerialUPDI programmer implementation is based on Microchip's From a6a06f47f6f34e176829752af569e4ae71dc23de Mon Sep 17 00:00:00 2001 From: Joerg Wunsch Date: Sat, 1 Jan 2022 20:58:26 +0100 Subject: [PATCH 06/19] Prevent `spi' and `pgm' commands from crashing terminal mode These commands are been meaningful only on direct bitbang programming adapters which implement a pgm->setpin method. Disable these commands for all other programmers, and issue an informational message. This is a partial fix for bug #790. --- src/term.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/term.c b/src/term.c index f222fb46..28aa6253 100644 --- a/src/term.c +++ b/src/term.c @@ -733,18 +733,26 @@ static int cmd_help(PROGRAMMER * pgm, struct avrpart * p, static int cmd_spi(PROGRAMMER * pgm, struct avrpart * p, int argc, char * argv[]) { - pgm->setpin(pgm, PIN_AVR_RESET, 1); - spi_mode = 1; - return 0; + if (pgm->setpin != NULL) { + pgm->setpin(pgm, PIN_AVR_RESET, 1); + spi_mode = 1; + return 0; + } + avrdude_message(MSG_INFO, "`spi' command unavailable for this programmer type\n"); + return -1; } static int cmd_pgm(PROGRAMMER * pgm, struct avrpart * p, int argc, char * argv[]) { - pgm->setpin(pgm, PIN_AVR_RESET, 0); - spi_mode = 0; - pgm->initialize(pgm, p); - return 0; + if (pgm->setpin != NULL) { + pgm->setpin(pgm, PIN_AVR_RESET, 0); + spi_mode = 0; + pgm->initialize(pgm, p); + return 0; + } + avrdude_message(MSG_INFO, "`pgm' command unavailable for this programmer type\n"); + return -1; } static int cmd_verbose(PROGRAMMER * pgm, struct avrpart * p, From c69627a87c80a6dde1d82c01f3bf0adce1cd4239 Mon Sep 17 00:00:00 2001 From: Joerg Wunsch Date: Sat, 1 Jan 2022 21:11:31 +0100 Subject: [PATCH 07/19] Update NEWS Structure has also been changed to use the Github terminology (issues and pull requests, rather than bugs and patches). --- NEWS | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index d743f081..6473bf86 100644 --- a/NEWS +++ b/NEWS @@ -15,15 +15,39 @@ Changes since version 6.4: * New programmers supported: + - SerialUPDI (UPDI devices connected to serial port with few + passive parts) + - PicKit4 / SNAP (now also in ISP and PDI mode) - * Bugfixes: + * Issues fixed: + - Curiosity Nano and terminal mode #790 (only the actual bugs + reported) + - CMake doesn't correctly handle conditionals in avrdude.conf.in + #776 + - CMake doesn't detect FreeBSD's libusb-1.0 (compatibility) #775 + - CMake doesn't correctly handle conditionals in avrdude.conf.in + #776 - * Patches: + * Pull requests: + - GitHub Migration #765 + - Update toplevel files. #767 + - GitHub Migration part 2 #768 + - Remove 'windows' folder with giveio.sys driver #769 + - SerialUPDI implementation - release candidate 1 #772 + - Fix typos #777 + - Fix memory leaks #779 + - As promised, documentation for SerialUPDI programmer #782 + - Improve CMake project #783 + - Fix avr_read() for page reads #784 + - Serialupdi manpage #787 + - Add PicKit4 and SNAP programmers #791 * Internals: + - Development moved to Github + Version 6.4: From e843db55ae3f1153273380a628979ce5f1c50011 Mon Sep 17 00:00:00 2001 From: Marius Greuel Date: Mon, 3 Jan 2022 12:57:58 +0100 Subject: [PATCH 08/19] Use yacc/byacc as an alternative to bison --- src/CMakeLists.txt | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 89e92c0f..cb87d3d1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,12 +37,13 @@ option(DEBUG_CMAKE "Enable debugging output for this CMake project" OFF) include(CheckIncludeFile) include(CheckFunctionExists) include(GNUInstallDirs) +include(FindPackageMessage) set(CONFIG_DIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}") set(AVRDUDE_EXTERNAL_PATH "${PROJECT_SOURCE_DIR}/external") # ===================================== -# Detect Flex and Bison +# Detect flex and yacc/bison # ===================================== find_package(FLEX) @@ -56,11 +57,26 @@ else() message(SEND_ERROR "This CMake project requires 'flex', which is not installed on your system." ) endif() -find_package(BISON) +find_package(BISON QUIET) if(BISON_FOUND) + find_package_message(BISON "Found BISON: ${BISON_EXECUTABLE} (found version \"${BISON_VERSION}\")" "[${BISON_EXECUTABLE}][${BISON_VERSION}]") BISON_TARGET(Parser config_gram.y "${PROJECT_BINARY_DIR}/config_gram.c" DEFINES_FILE "${PROJECT_BINARY_DIR}/config_gram.h") else() - message(SEND_ERROR "This CMake project requires 'bison', which is not installed on your system." ) + find_program(YACC_EXECUTABLE NAMES yacc byacc DOC "path to the yacc executable") + mark_as_advanced(YACC_EXECUTABLE) + if(YACC_EXECUTABLE) + find_package_message(YACC "Found YACC: ${YACC_EXECUTABLE}" "[${YACC_EXECUTABLE}]") + set(YACC_TARGET_outputs "${PROJECT_BINARY_DIR}/config_gram.c") + add_custom_command(OUTPUT ${YACC_TARGET_outputs} + COMMAND ${YACC_EXECUTABLE} -d -o ${YACC_TARGET_outputs} config_gram.y + VERBATIM + COMMENT "[YACC][Parser] Building parser with yacc" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + set(BISON_Parser_OUTPUTS ${YACC_TARGET_outputs}) + else() + message(SEND_ERROR "This CMake project requires 'bison', 'yacc', or 'byacc', which is not installed on your system." ) + endif() endif() # ===================================== From 12a67554d9fb9041d8ac602cf6447f4af0ba9960 Mon Sep 17 00:00:00 2001 From: Marius Greuel Date: Mon, 3 Jan 2022 12:29:59 +0100 Subject: [PATCH 09/19] Derive program version from last commit --- src/CMakeLists.txt | 48 ++++++++++++++++++++++++++++++++++++++++++- src/cmake_config.h.in | 2 +- src/windows.rc.in | 16 ++++++++++----- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cb87d3d1..4ac7a1bc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,7 +21,7 @@ # cmake --build build cmake_minimum_required(VERSION 3.12) -project(avrdude VERSION 6.99.20211218) +project(avrdude VERSION 6.99) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED True) @@ -40,8 +40,53 @@ include(GNUInstallDirs) include(FindPackageMessage) set(CONFIG_DIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}") +set(AVRDUDE_FULL_VERSION ${CMAKE_PROJECT_VERSION}) set(AVRDUDE_EXTERNAL_PATH "${PROJECT_SOURCE_DIR}/external") +# ===================================== +# Get Git commit info +# ===================================== + +# GIT_COMMIT_HASH -> hash of latest commit, e.g. b8b859f5 +# GIT_COMMIT_DATE -> date of latest commit, e.g. 20201231 +# GIT_COMMIT_YEAR -> year of latest commit, e.g. 2020 + +find_package(Git) +if(Git_FOUND) + execute_process( + COMMAND "${GIT_EXECUTABLE}" log -1 --format=%h + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + OUTPUT_VARIABLE GIT_COMMIT_HASH + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process( + COMMAND "${GIT_EXECUTABLE}" log -1 --format=%ad --date=format:%Y%m%d + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + OUTPUT_VARIABLE GIT_COMMIT_DATE + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process( + COMMAND "${GIT_EXECUTABLE}" log -1 --format=%ad --date=format:%Y + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + OUTPUT_VARIABLE GIT_COMMIT_YEAR + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process( + COMMAND "${GIT_EXECUTABLE}" log -1 --tags --format=%h + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + OUTPUT_VARIABLE GIT_TAG_HASH + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # If the commit is not tagged, include the date and commit hash in the full version string. + if(NOT GIT_COMMIT_HASH STREQUAL GIT_TAG_HASH) + set(AVRDUDE_FULL_VERSION "${CMAKE_PROJECT_VERSION}-${GIT_COMMIT_DATE} (${GIT_COMMIT_HASH})") + endif() +endif() + # ===================================== # Detect flex and yacc/bison # ===================================== @@ -253,6 +298,7 @@ if (DEBUG_CMAKE) message(STATUS "CMAKE_HOST_SYSTEM: ${CMAKE_HOST_SYSTEM}") message(STATUS "CMAKE_SYSTEM: ${CMAKE_SYSTEM}") message(STATUS "CONFIG_DIR: ${CONFIG_DIR}") + message(STATUS "AVRDUDE_FULL_VERSION: ${AVRDUDE_FULL_VERSION}") message(STATUS "USE_EXTERNAL: ${USE_EXTERNAL}") message(STATUS "USE_LIBUSBWIN32: ${USE_LIBUSBWIN32}") message(STATUS "HAVE_LIBELF: ${HAVE_LIBELF}") diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index 9643546e..087f3eba 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -20,7 +20,7 @@ #include "msvc/msvc_compat.h" #endif -#define VERSION "@PROJECT_VERSION@" +#define VERSION "@AVRDUDE_FULL_VERSION@" /* Options */ diff --git a/src/windows.rc.in b/src/windows.rc.in index e90362f9..33314e75 100644 --- a/src/windows.rc.in +++ b/src/windows.rc.in @@ -28,34 +28,40 @@ BEGIN END #cmakedefine PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ -#cmakedefine PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@ -#cmakedefine PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@ -#cmakedefine PROJECT_VERSION_TWEAK @PROJECT_VERSION_TWEAK@ - #ifdef PROJECT_VERSION_MAJOR #define VER_MAJOR PROJECT_VERSION_MAJOR #else #define VER_MAJOR 0 #endif +#cmakedefine PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@ #ifdef PROJECT_VERSION_MINOR #define VER_MINOR PROJECT_VERSION_MINOR #else #define VER_MINOR 0 #endif +#cmakedefine PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@ #ifdef PROJECT_VERSION_PATCH #define VER_BUILD PROJECT_VERSION_PATCH #else #define VER_BUILD 0 #endif +#cmakedefine PROJECT_VERSION_TWEAK @PROJECT_VERSION_TWEAK@ #ifdef PROJECT_VERSION_TWEAK #define VER_REVISION PROJECT_VERSION_TWEAK #else #define VER_REVISION 0 #endif +#cmakedefine GIT_COMMIT_YEAR "@GIT_COMMIT_YEAR@" +#ifdef GIT_COMMIT_YEAR +#define VER_COMMIT_YEAR GIT_COMMIT_YEAR +#else +#define VER_COMMIT_YEAR "" +#endif + #define _STR(s) #s #define _VER_STR(a, b, c, d) _STR(a) "." _STR(b) "." _STR(c) "." _STR(d) @@ -67,7 +73,7 @@ END #define VER_FILEDESCRIPTION_STR "AVRDUDE" #define VER_PRODUCTNAME_STR "AVRDUDE" #define VER_INTERNALNAME_STR "avrdude.exe" -#define VER_LEGALCOPYRIGHT_STR "\251 2021 The AVRDUDE authors" +#define VER_LEGALCOPYRIGHT_STR "\251 " VER_COMMIT_YEAR " The AVRDUDE authors" #define VER_ORIGINALFILENAME_STR VER_INTERNALNAME_STR #define VER_COMMENTS_STR "https://github.com/avrdudes/avrdude" #define VER_FILETYPE VFT_APP From 4d5af6370d2a46a298ac5b45c3b919f06b1b61f1 Mon Sep 17 00:00:00 2001 From: Marius Greuel Date: Mon, 3 Jan 2022 15:13:00 +0100 Subject: [PATCH 10/19] Change version URL from Savannah to GitHub --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index 480e1dba..e6cf959d 100644 --- a/src/main.c +++ b/src/main.c @@ -131,7 +131,7 @@ static void usage(void) " -q Quell progress output. -q -q for less.\n" " -l logfile Use logfile rather than stderr for diagnostics.\n" " -? Display this usage.\n" - "\navrdude version %s, URL: \n" + "\navrdude version %s, URL: \n" ,progname, version); } From ecca860972b7ceee100e1849d849a04a630b3bd8 Mon Sep 17 00:00:00 2001 From: MCUdude Date: Mon, 3 Jan 2022 18:49:55 +0100 Subject: [PATCH 11/19] Add target voltage adjustment for Curiosity Nano boards in Avrdude terminal mode --- src/jtag3.c | 32 ++++++++++++++++++++++++++++++++ src/jtag3_private.h | 7 +++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/jtag3.c b/src/jtag3.c index 0c04426a..1681a001 100644 --- a/src/jtag3.c +++ b/src/jtag3.c @@ -2305,6 +2305,31 @@ int jtag3_read_sib(PROGRAMMER * pgm, AVRPART * p, char * sib) return 0; } +static int jtag3_set_vtarget(PROGRAMMER * pgm, double v) +{ + unsigned uaref, utarg; + unsigned char buf[2]; + + utarg = (unsigned)(v * 1000); + + if (jtag3_getparm(pgm, SCOPE_GENERAL, 1, PARM3_VTARGET, buf, 2) < 0) { + avrdude_message(MSG_INFO, "%s: jtag3_set_vtarget(): cannot obtain V[aref]\n", + progname); + return -1; + } + + uaref = b2_to_u16(buf); + u16_to_b2(buf, utarg); + + avrdude_message(MSG_INFO, "%s: jtag3_set_vtarget(): changing V[target] from %.1f to %.1f\n", + progname, uaref / 1000.0, v); + + if (jtag3_setparm(pgm, SCOPE_GENERAL, 1, PARM3_VADJUST, buf, sizeof(buf)) < 0) + return -1; + + return 0; +} + static void jtag3_display(PROGRAMMER * pgm, const char * p) { unsigned char parms[5]; @@ -2564,5 +2589,12 @@ void jtag3_updi_initpgm(PROGRAMMER * pgm) pgm->flag = PGM_FL_IS_UPDI; pgm->unlock = jtag3_unlock_erase_key; pgm->read_sib = jtag3_read_sib; + + /* + * enable target voltage adjustment for PKOB/nEDBG boards + */ + if (matches(ldata(lfirst(pgm->id)), "pkobn_updi")) { + pgm->set_vtarget = jtag3_set_vtarget; + } } diff --git a/src/jtag3_private.h b/src/jtag3_private.h index 5ce634f9..a3e7fb08 100644 --- a/src/jtag3_private.h +++ b/src/jtag3_private.h @@ -189,8 +189,11 @@ #define PARM3_FW_RELEASE 0x03 /* section 0, generic scope, 1 byte; * always asked for by Atmel Studio, * but never displayed there */ -#define PARM3_VTARGET 0x00 /* section 1, generic scope, 2 bytes, - * in millivolts */ +#define PARM3_VTARGET 0x00 /* section 1, generic scope, 2 bytes, in millivolts */ +#define PARM3_VBUF 0x01 /* section 1, generic scope, 2 bytes, bufferred target voltage reference */ +#define PARM3_VUSB 0x02 /* section 1, generic scope, 2 bytes, USB voltage */ +#define PARM3_VADJUST 0x20 /* section 1, generic scope, 2 bytes, set voltage */ + #define PARM3_DEVICEDESC 0x00 /* section 2, memory etc. configuration, * 31 bytes for tiny/mega AVR, 47 bytes * for Xmega; is also used in command From 5c896992cde6ef28ea9c041567b4f48125108a2c Mon Sep 17 00:00:00 2001 From: Kristof Mulier Date: Mon, 3 Jan 2022 23:20:31 +0100 Subject: [PATCH 12/19] Find 'avrdude.conf' based on absolute path to executable (#780) * Find 'avrdude.conf' based on absolute path to executable * Update coding style * Update coding style * Update 'src/doc/avrdude.texi' to reflect the new search method for 'avrdude.conf' --- src/CMakeLists.txt | 2 + src/Makefile.am | 2 + src/doc/avrdude.texi | 24 +- src/main.c | 129 ++++++- src/whereami.c | 796 +++++++++++++++++++++++++++++++++++++++++++ src/whereami.h | 67 ++++ 6 files changed, 1004 insertions(+), 16 deletions(-) create mode 100644 src/whereami.c create mode 100644 src/whereami.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4ac7a1bc..7e0bc3a9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -547,6 +547,8 @@ add_executable(avrdude main.c term.c term.h + whereami.c + whereami.h "${EXTRA_WINDOWS_SOURCES}" ) diff --git a/src/Makefile.am b/src/Makefile.am index 0acd099f..0abb8123 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -194,6 +194,8 @@ include_HEADERS = libavrdude.h avrdude_SOURCES = \ main.c \ + whereami.c \ + whereami.h \ term.c \ term.h diff --git a/src/doc/avrdude.texi b/src/doc/avrdude.texi index 4565fe4f..023e0206 100644 --- a/src/doc/avrdude.texi +++ b/src/doc/avrdude.texi @@ -448,10 +448,22 @@ Currently, the following programmer ids are understood and supported: @item -C @var{config-file} Use the specified config file for configuration data. This file contains all programmer and part definitions that AVRDUDE knows about. -If not -specified, AVRDUDE reads the configuration file from -/usr/local/etc/avrdude.conf (FreeBSD and Linux). See Appendix A for -the method of searching for the configuration file for Windows. +If not specified, AVRDUDE looks for the configuration file in the following +two locations: + +@enumerate + +@item +@code{/../etc/avrdude.conf} + +@item +@code{/avrdude.conf} + +@end enumerate + +If not found there, the lookup procedure becomes platform dependent. On FreeBSD +and Linux, AVRDUDE looks at @code{/usr/local/etc/avrdude.conf}. See Appendix A +for the method of searching on Windows. If @var{config-file} is written as @var{+filename} then this file is read after the system wide and user configuration @@ -2296,6 +2308,10 @@ configuration files: @enumerate +@item +Only for the system configuration file: +@code{/../etc/avrdude.conf} + @item The directory from which the application loaded. diff --git a/src/main.c b/src/main.c index e6cf959d..c56c933a 100644 --- a/src/main.c +++ b/src/main.c @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -329,6 +330,10 @@ int main(int argc, char * argv []) char * partdesc; /* part id */ char sys_config[PATH_MAX]; /* system wide config file */ char usr_config[PATH_MAX]; /* per-user config file */ + char executable_abspath[PATH_MAX]; /* absolute path to avrdude executable */ + char executable_dirpath[PATH_MAX]; /* absolute path to folder with executable */ + bool executable_abspath_found = false; /* absolute path to executable found */ + bool sys_config_found = false; /* 'avrdude.conf' file found */ char * e; /* for strtol() error checking */ int baudrate; /* override default programmer baud rate */ double bitclock; /* Specify programmer bit clock (JTAG ICE) */ @@ -417,31 +422,131 @@ int main(int argc, char * argv []) is_open = 0; logfile = NULL; + /* + * EXECUTABLE ABSPATH + * ------------------ + * Determine the absolute path to avrdude executable. This will be used to + * locate the 'avrdude.conf' file later. + */ + int executable_dirpath_len; + int executable_abspath_len = wai_getExecutablePath( + executable_abspath, + PATH_MAX, + &executable_dirpath_len + ); + if ( + (executable_abspath_len != -1) && + (executable_abspath_len != 0) && + (executable_dirpath_len != -1) && + (executable_dirpath_len != 0) + ) { + // All requirements satisfied, executable path was found + executable_abspath_found = true; + + // Make sure the string is null terminated + executable_abspath[executable_abspath_len] = '\0'; + + // Replace all backslashes with forward slashes + i = 0; + while (true) { + if (executable_abspath[i] == '\0') { + break; + } + if (executable_abspath[i] == '\\') { + executable_abspath[i] = '/'; + } + i++; + } + + // Define 'executable_dirpath' to be the path to the parent folder of the + // executable. + strcpy(executable_dirpath, executable_abspath); + executable_dirpath[executable_dirpath_len] = '\0'; + + // Debug output + // avrdude_message(MSG_INFO, "executable_abspath = %s\n", executable_abspath); + // avrdude_message(MSG_INFO, "executable_abspath_len = %i\n", executable_abspath_len); + // avrdude_message(MSG_INFO, "executable_dirpath = %s\n", executable_dirpath); + // avrdude_message(MSG_INFO, "executable_dirpath_len = %i\n", executable_dirpath_len); + } + + /* + * SYSTEM CONFIG + * ------------- + * Determine the location of 'avrdude.conf'. Check in this order: + * 1. /../etc/avrdude.conf + * 2. /avrdude.conf + * 3. CONFIG_DIR/avrdude.conf + * + * When found, write the result into the 'sys_config' variable. + */ + if (executable_abspath_found) { + // 1. Check /../etc/avrdude.conf + strcpy(sys_config, executable_dirpath); + sys_config[PATH_MAX - 1] = '\0'; + i = strlen(sys_config); + if (i && (sys_config[i - 1] != '/')) + strcat(sys_config, "/"); + strcat(sys_config, "../etc/avrdude.conf"); + sys_config[PATH_MAX - 1] = '\0'; + if (access(sys_config, F_OK) == 0) { + sys_config_found = true; + } + else { + // 2. Check /avrdude.conf + strcpy(sys_config, executable_dirpath); + sys_config[PATH_MAX - 1] = '\0'; + i = strlen(sys_config); + if (i && (sys_config[i - 1] != '/')) + strcat(sys_config, "/"); + strcat(sys_config, "avrdude.conf"); + sys_config[PATH_MAX - 1] = '\0'; + if (access(sys_config, F_OK) == 0) { + sys_config_found = true; + } + } + } + if (!sys_config_found) { + // 3. Check CONFIG_DIR/avrdude.conf #if defined(WIN32NATIVE) - - win_sys_config_set(sys_config); - win_usr_config_set(usr_config); - + win_sys_config_set(sys_config); #else + strcpy(sys_config, CONFIG_DIR); + i = strlen(sys_config); + if (i && (sys_config[i - 1] != '/')) + strcat(sys_config, "/"); + strcat(sys_config, "avrdude.conf"); +#endif + if (access(sys_config, F_OK) == 0) { + sys_config_found = true; + } + } + // Debug output + // avrdude_message(MSG_INFO, "sys_config = %s\n", sys_config); + // avrdude_message(MSG_INFO, "sys_config_found = %s\n", sys_config_found ? "true" : "false"); + // avrdude_message(MSG_INFO, "\n"); - strcpy(sys_config, CONFIG_DIR); - i = strlen(sys_config); - if (i && (sys_config[i-1] != '/')) - strcat(sys_config, "/"); - strcat(sys_config, "avrdude.conf"); - + /* + * USER CONFIG + * ----------- + * Determine the location of '.avrduderc'. Nothing changed here. + */ +#if defined(WIN32NATIVE) + win_usr_config_set(usr_config); +#else usr_config[0] = 0; homedir = getenv("HOME"); if (homedir != NULL) { strcpy(usr_config, homedir); i = strlen(usr_config); - if (i && (usr_config[i-1] != '/')) + if (i && (usr_config[i - 1] != '/')) strcat(usr_config, "/"); strcat(usr_config, ".avrduderc"); } - #endif + + len = strlen(progname) + 2; for (i=0; i dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +// in case you want to #include "whereami.c" in a larger compilation unit +#if !defined(WHEREAMI_H) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(WAI_MALLOC) || !defined(WAI_FREE) || !defined(WAI_REALLOC) +#include +#endif + +#if !defined(WAI_MALLOC) +#define WAI_MALLOC(size) malloc(size) +#endif + +#if !defined(WAI_FREE) +#define WAI_FREE(p) free(p) +#endif + +#if !defined(WAI_REALLOC) +#define WAI_REALLOC(p, size) realloc(p, size) +#endif + +#ifndef WAI_NOINLINE +#if defined(_MSC_VER) +#define WAI_NOINLINE __declspec(noinline) +#elif defined(__GNUC__) +#define WAI_NOINLINE __attribute__((noinline)) +#else +#error unsupported compiler +#endif +#endif + +#if defined(_MSC_VER) +#define WAI_RETURN_ADDRESS() _ReturnAddress() +#elif defined(__GNUC__) +#define WAI_RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0)) +#else +#error unsupported compiler +#endif + +#if defined(_WIN32) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#if defined(_MSC_VER) +#pragma warning(push, 3) +#endif +#include +#include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +#include + +static int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length) +{ + wchar_t buffer1[MAX_PATH]; + wchar_t buffer2[MAX_PATH]; + wchar_t* path = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + DWORD size; + int length_, length__; + + size = GetModuleFileNameW(module, buffer1, sizeof(buffer1) / sizeof(buffer1[0])); + + if (size == 0) + break; + else if (size == (DWORD)(sizeof(buffer1) / sizeof(buffer1[0]))) + { + DWORD size_ = size; + do + { + wchar_t* path_; + + path_ = (wchar_t*)WAI_REALLOC(path, sizeof(wchar_t) * size_ * 2); + if (!path_) + break; + size_ *= 2; + path = path_; + size = GetModuleFileNameW(module, path, size_); + } + while (size == size_); + + if (size == size_) + break; + } + else + path = buffer1; + + if (!_wfullpath(buffer2, path, MAX_PATH)) + break; + length_ = (int)wcslen(buffer2); + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_ , out, capacity, NULL, NULL); + + if (length__ == 0) + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_, NULL, 0, NULL, NULL); + if (length__ == 0) + break; + + if (length__ <= capacity && dirname_length) + { + int i; + + for (i = length__ - 1; i >= 0; --i) + { + if (out[i] == '\\') + { + *dirname_length = i; + break; + } + } + } + + length = length__; + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + return WAI_PREFIX(getModulePath_)(NULL, out, capacity, dirname_length); +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + HMODULE module; + int length = -1; + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4054) +#endif + if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)WAI_RETURN_ADDRESS(), &module)) +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + { + length = WAI_PREFIX(getModulePath_)(module, out, capacity, dirname_length); + } + + return length; +} + +#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(WAI_USE_PROC_SELF_EXE) + +#include +#include +#include +#if defined(__linux__) +#include +#else +#include +#endif +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include +#include + +#if !defined(WAI_PROC_SELF_EXE) +#if defined(__sun) +#define WAI_PROC_SELF_EXE "/proc/self/path/a.out" +#else +#define WAI_PROC_SELF_EXE "/proc/self/exe" +#endif +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + resolved = realpath(WAI_PROC_SELF_EXE, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + return ok ? length : -1; +} + +#if !defined(WAI_PROC_SELF_MAPS_RETRY) +#define WAI_PROC_SELF_MAPS_RETRY 5 +#endif + +#if !defined(WAI_PROC_SELF_MAPS) +#if defined(__sun) +#define WAI_PROC_SELF_MAPS "/proc/self/map" +#else +#define WAI_PROC_SELF_MAPS "/proc/self/maps" +#endif +#endif + +#if defined(__ANDROID__) || defined(ANDROID) +#include +#include +#include +#endif +#include + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + int length = -1; + FILE* maps = NULL; + + for (int r = 0; r < WAI_PROC_SELF_MAPS_RETRY; ++r) + { + maps = fopen(WAI_PROC_SELF_MAPS, "r"); + if (!maps) + break; + + for (;;) + { + char buffer[PATH_MAX < 1024 ? 1024 : PATH_MAX]; + uint64_t low, high; + char perms[5]; + uint64_t offset; + uint32_t major, minor; + char path[PATH_MAX]; + uint32_t inode; + + if (!fgets(buffer, sizeof(buffer), maps)) + break; + + if (sscanf(buffer, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %x:%x %u %s\n", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8) + { + uint64_t addr = (uintptr_t)WAI_RETURN_ADDRESS(); + if (low <= addr && addr <= high) + { + char* resolved; + + resolved = realpath(path, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); +#if defined(__ANDROID__) || defined(ANDROID) + if (length > 4 + &&buffer[length - 1] == 'k' + &&buffer[length - 2] == 'p' + &&buffer[length - 3] == 'a' + &&buffer[length - 4] == '.') + { + int fd = open(path, O_RDONLY); + if (fd == -1) + { + length = -1; // retry + break; + } + + char* begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0); + if (begin == MAP_FAILED) + { + close(fd); + length = -1; // retry + break; + } + + char* p = begin + offset - 30; // minimum size of local file header + while (p >= begin) // scan backwards + { + if (*((uint32_t*)p) == 0x04034b50UL) // local file header signature found + { + uint16_t length_ = *((uint16_t*)(p + 26)); + + if (length + 2 + length_ < (int)sizeof(buffer)) + { + memcpy(&buffer[length], "!/", 2); + memcpy(&buffer[length + 2], p + 30, length_); + length += 2 + length_; + } + + break; + } + + --p; + } + + munmap(begin, offset); + close(fd); + } +#endif + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + + break; + } + } + } + + fclose(maps); + maps = NULL; + + if (length != -1) + break; + } + + return length; +} + +#elif defined(__APPLE__) + +#define _DARWIN_BETTER_REALPATH +#include +#include +#include +#include +#include +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + uint32_t size = (uint32_t)sizeof(buffer1); + if (_NSGetExecutablePath(path, &size) == -1) + { + path = (char*)WAI_MALLOC(size); + if (!_NSGetExecutablePath(path, &size)) + break; + } + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__QNXNTO__) + +#include +#include +#include +#include +#include +#include + +#if !defined(WAI_PROC_SELF_EXE) +#define WAI_PROC_SELF_EXE "/proc/self/exefile" +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* resolved = NULL; + FILE* self_exe = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + self_exe = fopen(WAI_PROC_SELF_EXE, "r"); + if (!self_exe) + break; + + if (!fgets(buffer1, sizeof(buffer1), self_exe)) + break; + + resolved = realpath(buffer1, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + fclose(self_exe); + + return ok ? length : -1; +} + +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__OpenBSD__) + +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[4096]; + char buffer2[PATH_MAX]; + char buffer3[PATH_MAX]; + char** argv = (char**)buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV }; + size_t size; + + if (sysctl(mib, 4, NULL, &size, NULL, 0) != 0) + break; + + if (size > sizeof(buffer1)) + { + argv = (char**)WAI_MALLOC(size); + if (!argv) + break; + } + + if (sysctl(mib, 4, argv, &size, NULL, 0) != 0) + break; + + if (strchr(argv[0], '/')) + { + resolved = realpath(argv[0], buffer2); + if (!resolved) + break; + } + else + { + const char* PATH = getenv("PATH"); + if (!PATH) + break; + + size_t argv0_length = strlen(argv[0]); + + const char* begin = PATH; + while (1) + { + const char* separator = strchr(begin, ':'); + const char* end = separator ? separator : begin + strlen(begin); + + if (end - begin > 0) + { + if (*(end -1) == '/') + --end; + + if (((end - begin) + 1 + argv0_length + 1) <= sizeof(buffer2)) + { + memcpy(buffer2, begin, end - begin); + buffer2[end - begin] = '/'; + memcpy(buffer2 + (end - begin) + 1, argv[0], argv0_length + 1); + + resolved = realpath(buffer2, buffer3); + if (resolved) + break; + } + } + + if (!separator) + break; + + begin = ++separator; + } + + if (!resolved) + break; + } + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + if (argv != (char**)buffer1) + WAI_FREE(argv); + + return ok ? length : -1; +} + +#else + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { +#if defined(__NetBSD__) + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME }; +#else + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; +#endif + size_t size = sizeof(buffer1); + + if (sysctl(mib, 4, path, &size, NULL, 0) != 0) + break; + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + return ok ? length : -1; +} + +#endif + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#else + +#error unsupported platform + +#endif + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/whereami.h b/src/whereami.h new file mode 100644 index 00000000..d5edffb8 --- /dev/null +++ b/src/whereami.h @@ -0,0 +1,67 @@ +// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +#ifndef WHEREAMI_H +#define WHEREAMI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WAI_FUNCSPEC + #define WAI_FUNCSPEC +#endif +#ifndef WAI_PREFIX +#define WAI_PREFIX(function) wai_##function +#endif + +/** + * Returns the path to the current executable. + * + * Usage: + * - first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to + * retrieve the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getExecutablePath(path, length, NULL)` again to retrieve the + * path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the executable path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length); + +/** + * Returns the path to the current module + * + * Usage: + * - first call `int length = wai_getModulePath(NULL, 0, NULL);` to retrieve + * the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getModulePath(path, length, NULL)` again to retrieve the path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the module path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length); + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef WHEREAMI_H \ No newline at end of file From 3a77394d67c6f9c60e8a4d3ccf91a783f350b729 Mon Sep 17 00:00:00 2001 From: Joerg Wunsch Date: Mon, 3 Jan 2022 23:35:10 +0100 Subject: [PATCH 13/19] Mention recent PRs and fixed issues --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 6473bf86..f21626c1 100644 --- a/NEWS +++ b/NEWS @@ -28,6 +28,7 @@ Changes since version 6.4: - CMake doesn't detect FreeBSD's libusb-1.0 (compatibility) #775 - CMake doesn't correctly handle conditionals in avrdude.conf.in #776 + - CMake: Recognize more than just bison #785 * Pull requests: @@ -43,6 +44,9 @@ Changes since version 6.4: - Fix avr_read() for page reads #784 - Serialupdi manpage #787 - Add PicKit4 and SNAP programmers #791 + - Use yacc/byacc as an alternative to bison, closes #785 #793 + - Derive program version string from last commit #794 + - Find 'avrdude.conf' based on absolute path to executable #780 * Internals: From fa079bec8c233bbec793b31d883eba698fff2cb3 Mon Sep 17 00:00:00 2001 From: Joerg Wunsch Date: Mon, 3 Jan 2022 23:35:59 +0100 Subject: [PATCH 14/19] Hint about possibly differing licensing terms. With the advent of whereami.[ch], not all files have a uniform license anymore. --- COPYING | 3 +++ 1 file changed, 3 insertions(+) diff --git a/COPYING b/COPYING index e69cf7e6..e6eecee0 100644 --- a/COPYING +++ b/COPYING @@ -1,3 +1,6 @@ +(Note that individual files might have a different license than this +one, but this one is the overall project license.) + GNU GENERAL PUBLIC LICENSE Version 2, June 1991 From f9ea588525a51c959a6bf9dffadababfcda965d4 Mon Sep 17 00:00:00 2001 From: Joerg Wunsch Date: Mon, 3 Jan 2022 23:53:51 +0100 Subject: [PATCH 15/19] Update the man page to reflect the search order for avrdude.conf --- src/avrdude.1 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/avrdude.1 b/src/avrdude.1 index 86c83970..9805ac75 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -1147,8 +1147,16 @@ line. .It Pa /dev/ppi0 default device to be used for communication with the programming hardware -.It Pa ${PREFIX}/etc/avrdude.conf +.It Pa avrdude.conf programmer and parts configuration file +.Pp +On Windows systems, this file is looked up in the same directory as the +executable file. +On all other systems, the file is first looked up in +.Pa ../etc/ , +relative to the path of the executable, then in the same directory as +the executable itself, and finally in the system default location +.Pa ${PREFIX}/etc/avrdude.conf . .It Pa ${HOME}/.avrduderc programmer and parts configuration file (per-user overrides) .It Pa ~/.inputrc From 9c7bb3787d8eedbe11a284cb0a35461a52d7b94c Mon Sep 17 00:00:00 2001 From: Joerg Wunsch Date: Tue, 4 Jan 2022 12:53:27 +0100 Subject: [PATCH 16/19] Revert "Hint about possibly differing licensing terms." This reverts commit fa079bec8c233bbec793b31d883eba698fff2cb3. Changing the COPYING file prevents automatic license detections from working correctly. --- COPYING | 3 --- 1 file changed, 3 deletions(-) diff --git a/COPYING b/COPYING index e6eecee0..e69cf7e6 100644 --- a/COPYING +++ b/COPYING @@ -1,6 +1,3 @@ -(Note that individual files might have a different license than this -one, but this one is the overall project license.) - GNU GENERAL PUBLIC LICENSE Version 2, June 1991 From 863f77d8277b5947993ce88df6e1f8cb87796567 Mon Sep 17 00:00:00 2001 From: Joerg Wunsch Date: Tue, 4 Jan 2022 13:48:11 +0100 Subject: [PATCH 17/19] Fix a (valid) warning about comparison of char vs. int against EOF Closes PR #796 --- src/buspirate.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/buspirate.c b/src/buspirate.c index f2a6d9ec..84b2f042 100644 --- a/src/buspirate.c +++ b/src/buspirate.c @@ -178,6 +178,7 @@ static int buspirate_getc(struct programmer_t *pgm) static char *buspirate_readline_noexit(struct programmer_t *pgm, char *buf, size_t len) { char *buf_p; + int c; long orig_serial_recv_timeout = serial_recv_timeout; /* Static local buffer - this may come handy at times */ @@ -190,12 +191,12 @@ static char *buspirate_readline_noexit(struct programmer_t *pgm, char *buf, size buf_p = buf; memset(buf, 0, len); while (buf_p < (buf + len - 1)) { /* keep the very last byte == 0 */ - *buf_p = buspirate_getc(pgm); - if (*buf_p == '\r') + *buf_p = c = buspirate_getc(pgm); + if (c == '\r') continue; - if (*buf_p == '\n') + if (c == '\n') break; - if (*buf_p == EOF) { + if (c == EOF) { *buf_p = '\0'; break; } From dba89e726939326289219cc0e1bb8c242492de1b Mon Sep 17 00:00:00 2001 From: Joerg Wunsch Date: Tue, 4 Jan 2022 14:07:15 +0100 Subject: [PATCH 18/19] Mention PR #796 as fixed --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index f21626c1..b32b7345 100644 --- a/NEWS +++ b/NEWS @@ -47,6 +47,7 @@ Changes since version 6.4: - Use yacc/byacc as an alternative to bison, closes #785 #793 - Derive program version string from last commit #794 - Find 'avrdude.conf' based on absolute path to executable #780 + - buspirate: fix -Wtautological-constant-out-of-range-compare #796 * Internals: From 3747db516ad53d19631ef2a1159bb9f5fc19d88a Mon Sep 17 00:00:00 2001 From: Marius Greuel Date: Tue, 28 Dec 2021 11:55:12 +0100 Subject: [PATCH 19/19] Add support for Micronucleus bootloader --- src/CMakeLists.txt | 2 + src/Makefile.am | 2 + src/avrdude.1 | 19 + src/avrdude.conf.in | 9 + src/doc/avrdude.texi | 18 + src/micronucleus.c | 951 +++++++++++++++++++++++++++++++++++++++++++ src/micronucleus.h | 35 ++ src/pgm_type.c | 2 + 8 files changed, 1038 insertions(+) create mode 100644 src/micronucleus.c create mode 100644 src/micronucleus.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7e0bc3a9..a89fb68a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -467,6 +467,8 @@ add_library(libavrdude STATIC linuxspi.h linux_ppdev.h lists.c + micronucleus.c + micronucleus.h my_ddk_hidsdi.h par.c par.h diff --git a/src/Makefile.am b/src/Makefile.am index 0abb8123..834f32fc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -136,6 +136,8 @@ libavrdude_a_SOURCES = \ linuxspi.h \ linux_ppdev.h \ lists.c \ + micronucleus.c \ + micronucleus.h \ my_ddk_hidsdi.h \ par.c \ par.h \ diff --git a/src/avrdude.1 b/src/avrdude.1 index 9805ac75..dec2f3b5 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -233,6 +233,15 @@ In a nutshell, this programmer consists of simple USB->UART adapter, diode and couple of resistors. It uses serial connection to provide UPDI interface. See the texinfo documentation for more details and known issues. .Pp +The Micronucleus bootloader is supported for both protocol version V1 +and V2. As the bootloader does not support reading from flash memory, +use the +.Fl V +option to prevent AVRDUDE from verifing the flash memory. +See the section on +.Em extended parameters +for Micronucleus specific options. +.Pp Input files can be provided, and output files can be written in different file formats, such as raw binary files containing the data to download to the chip, Intel hex format, or Motorola S-record @@ -1084,6 +1093,16 @@ Especially in ascii mode this happens very often, so setting a smaller value can speed up programming a lot. The default value is 100ms. Using 10ms might work in most cases. .El +.It Ar Micronucleus bootloader +.Bl -tag -offset indent -width indent +.It Ar wait[=] +If the device is not connected, wait for the device to be plugged in. +The optional +.Ar timeout +specifies the connection time-out in seconds. +If no time-out is specified, AVRDUDE will wait indefinitely until the +device is plugged in. +.El .It Ar Wiring When using the Wiring programmer type, the following optional extended parameter is accepted: diff --git a/src/avrdude.conf.in b/src/avrdude.conf.in index b6418742..4390708c 100644 --- a/src/avrdude.conf.in +++ b/src/avrdude.conf.in @@ -916,6 +916,15 @@ programmer usbpid = 0x0BA5; ; +programmer + id = "micronucleus"; + desc = "Micronucleus Bootloader"; + type = "micronucleus"; + connection_type = usb; + usbvid = 0x16D0; + usbpid = 0x0753; +; + # commercial version of USBtiny, using a separate VID/PID programmer id = "iseavrprog"; diff --git a/src/doc/avrdude.texi b/src/doc/avrdude.texi index 023e0206..85762839 100644 --- a/src/doc/avrdude.texi +++ b/src/doc/avrdude.texi @@ -315,6 +315,12 @@ In a nutshell, this programmer consists of simple USB->UART adapter, diode and couple of resistors. It uses serial connection to provide UPDI interface. @xref{SerialUPDI programmer} for more details and known issues. +The Micronucleus bootloader is supported for both protocol version V1 +and V2. As the bootloader does not support reading from flash memory, +use the @code{-V} option to prevent AVRDUDE from verifing the flash memory. +See the section on @emph{extended parameters} +below for Micronucleus specific options. + @menu * History:: @end menu @@ -968,6 +974,18 @@ The default value is 100ms. Using 10ms might work in most cases. @end table +@item Micronucleus bootloader + +When using the Micronucleus programmer type, the +following optional extended parameter is accepted: +@table @code +@item @samp{wait=@var{timeout}} +If the device is not connected, wait for the device to be plugged in. +The optional @var{timeout} specifies the connection time-out in seconds. +If no time-out is specified, AVRDUDE will wait indefinitely until the +device is plugged in. +@end table + @item Wiring When using the Wiring programmer type, the diff --git a/src/micronucleus.c b/src/micronucleus.c new file mode 100644 index 00000000..ef758078 --- /dev/null +++ b/src/micronucleus.c @@ -0,0 +1,951 @@ +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * Copyright (C) 2019 Marius Greuel + * Portions Copyright (C) 2014 T. Bo"scke + * Portions Copyright (C) 2012 ihsan Kehribar + * + * 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 . + */ + +// Notes: +// This file adds support for the Micronucleus bootloader V1 and V2, +// so you do no longer need the Micronucleus command-line utility. +// +// This bootloader is typically used on small ATtiny boards, +// such as Digispark (ATtiny85), Digispark Pro (ATtiny167), +// and the respective clones. +// By default, it bootloader uses the VID/PID 16d0:0753 (MCS Digistump). +// +// As the micronucleus bootloader is optimized for size, it implements +// writing to flash memory only. Since it does not support reading, +// use the -V option to prevent avrdude from verifing the flash memory. +// To have avrdude wait for the device to be connected, use the +// extended option '-x wait'. +// +// Example: +// avrdude -c micronucleus -p t85 -x wait -V -U flash:w:main.hex + +#include "ac_cfg.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "avrdude.h" +#include "micronucleus.h" +#include "usbdevs.h" + +#if defined(HAVE_LIBUSB) + +#if defined(HAVE_USB_H) +#include +#elif defined(HAVE_LUSB0_USB_H) +#include +#else +#error "libusb needs either or " +#endif + +//----------------------------------------------------------------------------- + +#define MICRONUCLEUS_VID 0x16D0 +#define MICRONUCLEUS_PID 0x0753 + +#define MICRONUCLEUS_CONNECT_WAIT 100 + +#define MICRONUCLEUS_CMD_INFO 0 +#define MICRONUCLEUS_CMD_TRANSFER 1 +#define MICRONUCLEUS_CMD_ERASE 2 +#define MICRONUCLEUS_CMD_PROGRAM 3 +#define MICRONUCLEUS_CMD_START 4 + +#define MICRONUCLEUS_DEFAULT_TIMEOUT 500 +#define MICRONUCLEUS_MAX_MAJOR_VERSION 2 + +#define PDATA(pgm) ((pdata_t*)(pgm->cookie)) + +//----------------------------------------------------------------------------- + +typedef struct pdata +{ + usb_dev_handle* usb_handle; + // Extended parameters + bool wait_until_device_present; + int wait_timout; // in seconds + // Bootloader version + uint8_t major_version; + uint8_t minor_version; + // Bootloader info (via USB request) + uint16_t flash_size; // programmable size (in bytes) of flash + uint8_t page_size; // size (in bytes) of page + uint8_t write_sleep; // milliseconds + uint8_t signature1; // only used in protocol v2 + uint8_t signature2; // only used in protocol v2 + // Calculated bootloader info + uint16_t pages; // total number of pages to program + uint16_t bootloader_start; // start of the bootloader (at page boundary) + uint16_t erase_sleep; // milliseconds + // State + uint16_t user_reset_vector; // reset vector of user program + bool write_last_page; // last page already programmed + bool start_program; // require start after flash +} pdata_t; + +//----------------------------------------------------------------------------- + +static void delay_ms(uint32_t duration) +{ + usleep(duration * 1000); +} + +static int micronucleus_check_connection(pdata_t* pdata) +{ + if (pdata->major_version >= 2) + { + uint8_t buffer[6] = { 0 }; + int result = usb_control_msg( + pdata->usb_handle, + USB_ENDPOINT_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + MICRONUCLEUS_CMD_INFO, 0, 0, (char*)buffer, sizeof(buffer), MICRONUCLEUS_DEFAULT_TIMEOUT); + return result == sizeof(buffer) ? 0 : -1; + } + else + { + uint8_t buffer[4] = { 0 }; + int result = usb_control_msg( + pdata->usb_handle, + USB_ENDPOINT_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + MICRONUCLEUS_CMD_INFO, 0, 0, (char*)buffer, sizeof(buffer), MICRONUCLEUS_DEFAULT_TIMEOUT); + return result == sizeof(buffer) ? 0 : -1; + } +} + +static int micronucleus_reconnect(pdata_t* pdata) +{ + struct usb_device* device = usb_device(pdata->usb_handle); + + usb_close(pdata->usb_handle); + pdata->usb_handle = NULL; + + for (int i = 0; i < 25; i++) + { + avrdude_message(MSG_NOTICE, "%s: Trying to reconnect...\n", progname); + + pdata->usb_handle = usb_open(device); + if (pdata->usb_handle != NULL) + return 0; + + delay_ms(MICRONUCLEUS_CONNECT_WAIT); + } + + return -1; +} + +static int micronucleus_get_bootloader_info_v1(pdata_t* pdata) +{ + uint8_t buffer[4] = { 0 }; + int result = usb_control_msg( + pdata->usb_handle, + USB_ENDPOINT_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + MICRONUCLEUS_CMD_INFO, 0, 0, (char*)buffer, sizeof(buffer), MICRONUCLEUS_DEFAULT_TIMEOUT); + if (result < 0) + { + avrdude_message(MSG_INFO, "%s: WARNING: Failed to get bootloader info block: %s\n", + progname, usb_strerror()); + return result; + } + else if (result < sizeof(buffer)) + { + avrdude_message(MSG_INFO, "%s: WARNING: Received invalid bootloader info block size: %d\n", + progname, result); + return -1; + } + + pdata->flash_size = (buffer[0] << 8) | buffer[1]; + pdata->page_size = buffer[2]; + pdata->write_sleep = buffer[3] & 127; + + // Take a wild guess on the part ID, so that we can supply it for device verification + if (pdata->page_size == 128) + { + // ATtiny167 + pdata->signature1 = 0x94; + pdata->signature2 = 0x87; + } + else if (pdata->page_size == 64) + { + if (pdata->flash_size > 4096) + { + // ATtiny85 + pdata->signature1 = 0x93; + pdata->signature2 = 0x0B; + } + else + { + // ATtiny45 + pdata->signature1 = 0x92; + pdata->signature2 = 0x06; + } + } + else if (pdata->page_size == 16) + { + // ATtiny841 + pdata->signature1 = 0x93; + pdata->signature2 = 0x15; + } + else + { + // Unknown device + pdata->signature1 = 0; + pdata->signature2 = 0; + } + + pdata->pages = (pdata->flash_size + pdata->page_size - 1) / pdata->page_size; + pdata->bootloader_start = pdata->pages * pdata->page_size; + pdata->erase_sleep = pdata->write_sleep * pdata->pages; + + return 0; +} + +static int micronucleus_get_bootloader_info_v2(pdata_t* pdata) +{ + uint8_t buffer[6] = { 0 }; + int result = usb_control_msg( + pdata->usb_handle, + USB_ENDPOINT_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + MICRONUCLEUS_CMD_INFO, 0, 0, (char*)buffer, sizeof(buffer), MICRONUCLEUS_DEFAULT_TIMEOUT); + if (result < 0) + { + avrdude_message(MSG_INFO, "%s: WARNING: Failed to get bootloader info block: %s\n", + progname, usb_strerror()); + return result; + } + else if (result < sizeof(buffer)) + { + avrdude_message(MSG_INFO, "%s: WARNING: Received invalid bootloader info block size: %d\n", + progname, result); + return -1; + } + + pdata->flash_size = (buffer[0] << 8) + buffer[1]; + pdata->page_size = buffer[2]; + pdata->write_sleep = (buffer[3] & 127) + 2; + pdata->signature1 = buffer[4]; + pdata->signature2 = buffer[5]; + + pdata->pages = (pdata->flash_size + pdata->page_size - 1) / pdata->page_size; + pdata->bootloader_start = pdata->pages * pdata->page_size; + pdata->erase_sleep = pdata->write_sleep * pdata->pages; + + // if bit 7 of write sleep time is set, divide the erase time by four to + // accomodate to the 4*page erase of the ATtiny841/441 + if ((buffer[3] & 128) != 0) + { + pdata->erase_sleep /= 4; + } + + return 0; +} + +static int micronucleus_get_bootloader_info(pdata_t* pdata) +{ + if (pdata->major_version >= 2) + { + return micronucleus_get_bootloader_info_v2(pdata); + } + else + { + return micronucleus_get_bootloader_info_v1(pdata); + } +} + +static void micronucleus_dump_device_info(pdata_t* pdata) +{ + avrdude_message(MSG_NOTICE, "%s: Bootloader version: %d.%d\n", progname, pdata->major_version, pdata->minor_version); + avrdude_message(MSG_NOTICE, "%s: Available flash size: %u\n", progname, pdata->flash_size); + avrdude_message(MSG_NOTICE, "%s: Page size: %u\n", progname, pdata->page_size); + avrdude_message(MSG_NOTICE, "%s: Bootloader start: 0x%04X\n", progname, pdata->bootloader_start); + avrdude_message(MSG_NOTICE, "%s: Write sleep: %ums\n", progname, pdata->write_sleep); + avrdude_message(MSG_NOTICE, "%s: Erase sleep: %ums\n", progname, pdata->erase_sleep); + avrdude_message(MSG_NOTICE, "%s: Signature1: 0x%02X\n", progname, pdata->signature1); + avrdude_message(MSG_NOTICE, "%s: Signature2: 0x%02X\n", progname, pdata->signature2); +} + +static int micronucleus_erase_device(pdata_t* pdata) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_erase_device()\n", progname); + + int result = usb_control_msg( + pdata->usb_handle, + USB_ENDPOINT_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + MICRONUCLEUS_CMD_ERASE, 0, 0, NULL, 0, MICRONUCLEUS_DEFAULT_TIMEOUT); + if (result < 0) + { + switch (result) + { + case -EIO: + case -EPIPE: + avrdude_message(MSG_NOTICE, "%s: Ignoring last error of erase command: %s\n", progname, usb_strerror()); + break; + default: + avrdude_message(MSG_INFO, "%s: WARNING: Failed is issue erase command, code %d: %s\n", progname, result, usb_strerror()); + return result; + } + } + + delay_ms(pdata->erase_sleep); + + result = micronucleus_check_connection(pdata); + if (result < 0) + { + avrdude_message(MSG_NOTICE, "%s: Connection dropped, trying to reconnect...\n", progname); + + result = micronucleus_reconnect(pdata); + if (result < 0) + { + avrdude_message(MSG_INFO, "%s: WARNING: Failed to reconnect USB device: %s\n", progname, usb_strerror()); + return result; + } + } + + return 0; +} + +static int micronucleus_patch_reset_vector(pdata_t* pdata, uint8_t* buffer) +{ + // Save user reset vector. + uint16_t word0 = (buffer[1] << 8) | buffer[0]; + uint16_t word1 = (buffer[3] << 8) | buffer[2]; + + if (word0 == 0x940C) + { + // long jump + pdata->user_reset_vector = word1; + } + else if ((word0 & 0xF000) == 0xC000) + { + // rjmp + pdata->user_reset_vector = (word0 & 0x0FFF) + 1; + } + else + { + avrdude_message(MSG_INFO, "%s: The reset vector of the user program does not contain a branch instruction.\n", progname); + return -1; + } + + // Patch in jmp to bootloader. + if (pdata->bootloader_start > 0x2000) + { + // jmp + uint16_t data = 0x940C; + buffer[0] = (uint8_t)(data >> 0); + buffer[1] = (uint8_t)(data >> 8); + buffer[2] = (uint8_t)(pdata->bootloader_start >> 0); + buffer[3] = (uint8_t)(pdata->bootloader_start >> 8); + } + else + { + // rjmp + uint16_t data = 0xC000 | ((pdata->bootloader_start / 2 - 1) & 0x0FFF); + buffer[0] = (uint8_t)(data >> 0); + buffer[1] = (uint8_t)(data >> 8); + } + + return 0; +} + +static void micronucleus_patch_user_vector(pdata_t* pdata, uint8_t* buffer) +{ + uint16_t user_reset_addr = pdata->bootloader_start - 4; + uint16_t address = pdata->bootloader_start - pdata->page_size; + if (user_reset_addr > 0x2000) + { + // jmp + uint16_t data = 0x940C; + buffer[user_reset_addr - address + 0] = (uint8_t)(data >> 0); + buffer[user_reset_addr - address + 1] = (uint8_t)(data >> 8); + buffer[user_reset_addr - address + 2] = (uint8_t)(pdata->user_reset_vector >> 0); + buffer[user_reset_addr - address + 3] = (uint8_t)(pdata->user_reset_vector >> 8); + } + else + { + // rjmp + uint16_t data = 0xC000 | ((pdata->user_reset_vector - user_reset_addr / 2 - 1) & 0x0FFF); + buffer[user_reset_addr - address + 0] = (uint8_t)(data >> 0); + buffer[user_reset_addr - address + 1] = (uint8_t)(data >> 8); + } +} + +static int micronucleus_write_page_v1(pdata_t* pdata, uint32_t address, uint8_t* buffer, uint32_t size) +{ + int result = usb_control_msg(pdata->usb_handle, + USB_ENDPOINT_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + MICRONUCLEUS_CMD_TRANSFER, + size, address, + buffer, size, + MICRONUCLEUS_DEFAULT_TIMEOUT); + if (result < 0) + { + avrdude_message(MSG_INFO, "%s: Failed to transfer page: %s\n", progname, usb_strerror()); + return result; + } + + return 0; +} + +static int micronucleus_write_page_v2(pdata_t* pdata, uint32_t address, uint8_t* buffer, uint32_t size) +{ + int result = usb_control_msg( + pdata->usb_handle, + USB_ENDPOINT_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + MICRONUCLEUS_CMD_TRANSFER, + size, address, + NULL, 0, + MICRONUCLEUS_DEFAULT_TIMEOUT); + if (result < 0) + { + avrdude_message(MSG_INFO, "%s: Failed to transfer page: %s\n", progname, usb_strerror()); + return result; + } + + for (int i = 0; i < size; i += 4) + { + int w1 = (buffer[i + 1] << 8) | (buffer[i + 0] << 0); + int w2 = (buffer[i + 3] << 8) | (buffer[i + 2] << 0); + result = usb_control_msg( + pdata->usb_handle, + USB_ENDPOINT_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + MICRONUCLEUS_CMD_PROGRAM, + w1, w2, + NULL, 0, + MICRONUCLEUS_DEFAULT_TIMEOUT); + if (result < 0) + { + avrdude_message(MSG_INFO, "%s: Failed to transfer page: %s\n", progname, usb_strerror()); + return result; + } + } + + return 0; +} + +static int micronucleus_write_page(pdata_t* pdata, uint32_t address, uint8_t* buffer, uint32_t size) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_write_page(address=0x%04X, size=%d)\n", progname, address, size); + + if (address == 0) + { + if (pdata->major_version >= 2) + { + int result = micronucleus_patch_reset_vector(pdata, buffer); + if (result < 0) + { + return result; + } + } + + // Require last page (with application reset vector) to be written. + pdata->write_last_page = true; + + // Require software start. + pdata->start_program = true; + } + else if (address >= pdata->bootloader_start - pdata->page_size) + { + if (pdata->major_version >= 2) + { + micronucleus_patch_user_vector(pdata, buffer); + } + + // Mark last page as written. + pdata->write_last_page = false; + } + + int result; + if (pdata->major_version >= 2) + { + result = micronucleus_write_page_v2(pdata, address, buffer, size); + } + else + { + result = micronucleus_write_page_v1(pdata, address, buffer, size); + } + + if (result < 0) + { + return result; + } + + delay_ms(pdata->write_sleep); + + return 0; +} + +static int micronucleus_start(pdata_t* pdata) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_start()\n", progname); + + int result = usb_control_msg( + pdata->usb_handle, + USB_ENDPOINT_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + MICRONUCLEUS_CMD_START, 0, 0, NULL, 0, MICRONUCLEUS_DEFAULT_TIMEOUT); + if (result < 0) + { + avrdude_message(MSG_INFO, "%s: WARNING: Failed is issue start command: %s\n", progname, usb_strerror()); + return result; + } + + return 0; +} + +//----------------------------------------------------------------------------- + +static void micronucleus_setup(PROGRAMMER* pgm) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_setup()\n", progname); + + if ((pgm->cookie = malloc(sizeof(pdata_t))) == 0) + { + avrdude_message(MSG_INFO, "%s: micronucleus_setup(): Out of memory allocating private data\n", progname); + exit(1); + } + + memset(pgm->cookie, 0, sizeof(pdata_t)); +} + +static void micronucleus_teardown(PROGRAMMER* pgm) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_teardown()\n", progname); + free(pgm->cookie); +} + +static int micronucleus_initialize(PROGRAMMER* pgm, AVRPART* p) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_initialize()\n", progname); + + pdata_t* pdata = PDATA(pgm); + + int result = micronucleus_get_bootloader_info(pdata); + if (result < 0) + return result; + + micronucleus_dump_device_info(pdata); + + return 0; +} + +static void micronucleus_display(PROGRAMMER* pgm, const char* prefix) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_display()\n", progname); +} + +static void micronucleus_powerup(PROGRAMMER* pgm) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_powerup()\n", progname); +} + +static void micronucleus_powerdown(PROGRAMMER* pgm) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_powerdown()\n", progname); + + pdata_t* pdata = PDATA(pgm); + if (pdata->write_last_page) + { + pdata->write_last_page = false; + + uint8_t* buffer = (unsigned char*)malloc(pdata->page_size); + if (buffer != NULL) + { + memset(buffer, 0xFF, pdata->page_size); + micronucleus_write_page(pdata, pdata->bootloader_start - pdata->page_size, buffer, pdata->page_size); + free(buffer); + } + } + + if (pdata->start_program) + { + pdata->start_program = false; + + micronucleus_start(pdata); + } +} + +static void micronucleus_enable(PROGRAMMER* pgm) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_enable()\n", progname); +} + +static void micronucleus_disable(PROGRAMMER* pgm) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_disable()\n", progname); +} + +static int micronucleus_program_enable(PROGRAMMER* pgm, AVRPART* p) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_program_enable()\n", progname); + return 0; +} + +static int micronucleus_read_sig_bytes(PROGRAMMER* pgm, AVRPART* p, AVRMEM* mem) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_read_sig_bytes()\n", progname); + + if (mem->size < 3) + { + avrdude_message(MSG_INFO, "%s: memory size too small for read_sig_bytes", progname); + return -1; + } + + pdata_t* pdata = PDATA(pgm); + mem->buf[0] = 0x1E; + mem->buf[1] = pdata->signature1; + mem->buf[2] = pdata->signature2; + return 0; +} + +static int micronucleus_chip_erase(PROGRAMMER* pgm, AVRPART* p) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_chip_erase()\n", progname); + + pdata_t* pdata = PDATA(pgm); + return micronucleus_erase_device(pdata); +} + +static int micronucleus_open(PROGRAMMER* pgm, char* port) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_open(\"%s\")\n", progname, port); + + pdata_t* pdata = PDATA(pgm); + char* bus_name = NULL; + char* dev_name = NULL; + + // if no -P was given or '-P usb' was given + if (strcmp(port, "usb") == 0) + { + port = NULL; + } + else + { + // calculate bus and device names from -P option + if (strncmp(port, "usb", 3) == 0 && ':' == port[3]) + { + bus_name = port + 4; + dev_name = strchr(bus_name, ':'); + if (dev_name != NULL) + { + *dev_name = '\0'; + dev_name++; + } + } + } + + if (port != NULL && dev_name == NULL) + { + avrdude_message(MSG_INFO, "%s: ERROR: Invalid -P value: '%s'\n", progname, port); + avrdude_message(MSG_INFO, "%sUse -P usb:bus:device\n", progbuf); + return -1; + } + + // Determine VID/PID + int vid = pgm->usbvid ? pgm->usbvid : MICRONUCLEUS_VID; + int pid = MICRONUCLEUS_PID; + + LNODEID usbpid = lfirst(pgm->usbpid); + if (usbpid != NULL) + { + pid = *(int*)(ldata(usbpid)); + if (lnext(usbpid)) + { + avrdude_message(MSG_INFO, "%s: WARNING: using PID 0x%04x, ignoring remaining PIDs in list\n", + progname, pid); + } + } + + usb_init(); + + bool show_retry_message = true; + + time_t start_time = time(NULL); + for (;;) + { + usb_find_busses(); + usb_find_devices(); + + pdata->usb_handle = NULL; + + // Search for device + struct usb_bus* bus = NULL; + for (bus = usb_busses; bus != NULL && pdata->usb_handle == NULL; bus = bus->next) + { + struct usb_device* device = NULL; + for (device = bus->devices; device != NULL && pdata->usb_handle == NULL; device = device->next) + { + if (device->descriptor.idVendor == vid && device->descriptor.idProduct == pid) + { + pdata->major_version = (uint8_t)(device->descriptor.bcdDevice >> 8); + pdata->minor_version = (uint8_t)(device->descriptor.bcdDevice >> 0); + + avrdude_message(MSG_NOTICE, "%s: Found device with Micronucleus V%d.%d, bus:device: %s:%s\n", + progname, + pdata->major_version, pdata->minor_version, + bus->dirname, device->filename); + + // if -P was given, match device by device name and bus name + if (port != NULL) + { + if (dev_name == NULL || strcmp(bus->dirname, bus_name) || strcmp(device->filename, dev_name)) + { + continue; + } + } + + if (pdata->major_version > MICRONUCLEUS_MAX_MAJOR_VERSION) + { + avrdude_message(MSG_INFO, "%s: WARNING: device with unsupported version (V%d.%d) of Micronucleus detected.\n", + progname, + pdata->major_version, pdata->minor_version); + continue; + } + + pdata->usb_handle = usb_open(device); + if (pdata->usb_handle == NULL) + { + avrdude_message(MSG_INFO, "%s: ERROR: Failed to open USB device: %s\n", progname, usb_strerror()); + } + } + } + } + + if (pdata->usb_handle == NULL && pdata->wait_until_device_present) + { + if (show_retry_message) + { + if (pdata->wait_timout < 0) + { + avrdude_message(MSG_INFO, "%s: No device found, waiting for device to be plugged in...\n", progname); + } + else + { + avrdude_message(MSG_INFO, "%s: No device found, waiting %d seconds for device to be plugged in...\n", + progname, + pdata->wait_timout); + } + + avrdude_message(MSG_INFO, "%s: Press CTRL-C to terminate.\n", progname); + show_retry_message = false; + } + + if (pdata->wait_timout < 0 || (time(NULL) - start_time) < pdata->wait_timout) + { + delay_ms(MICRONUCLEUS_CONNECT_WAIT); + continue; + } + } + + break; + } + + if (!pdata->usb_handle) + { + avrdude_message(MSG_INFO, "%s: ERROR: Could not find device with Micronucleus bootloader (%04X:%04X)\n", + progname, vid, pid); + return -1; + } + + return 0; +} + +static void micronucleus_close(PROGRAMMER* pgm) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_close()\n", progname); + + pdata_t* pdata = PDATA(pgm); + if (pdata->usb_handle != NULL) + { + usb_close(pdata->usb_handle); + pdata->usb_handle = NULL; + } +} + +static int micronucleus_read_byte(PROGRAMMER* pgm, AVRPART* p, AVRMEM* mem, + unsigned long addr, unsigned char* value) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_read_byte(desc=%s, addr=0x%0X)\n", + progname, mem->desc, addr); + + if (strcmp(mem->desc, "lfuse") == 0 || + strcmp(mem->desc, "hfuse") == 0 || + strcmp(mem->desc, "efuse") == 0 || + strcmp(mem->desc, "lock") == 0) + { + *value = 0xFF; + return 0; + } + else + { + avrdude_message(MSG_INFO, "%s: Unsupported memory type: %s\n", progname, mem->desc); + return -1; + } +} + +static int micronucleus_write_byte(PROGRAMMER* pgm, AVRPART* p, AVRMEM* mem, + unsigned long addr, unsigned char value) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_write_byte(desc=%s, addr=0x%0X)\n", + progname, mem->desc, addr); + return -1; +} + +static int micronucleus_paged_load(PROGRAMMER* pgm, AVRPART* p, AVRMEM* mem, + unsigned int page_size, + unsigned int addr, unsigned int n_bytes) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_paged_load(page_size=0x%X, addr=0x%X, n_bytes=0x%X)\n", + progname, page_size, addr, n_bytes); + return -1; +} + +static int micronucleus_paged_write(PROGRAMMER* pgm, AVRPART* p, AVRMEM* mem, + unsigned int page_size, + unsigned int addr, unsigned int n_bytes) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_paged_write(page_size=0x%X, addr=0x%X, n_bytes=0x%X)\n", + progname, page_size, addr, n_bytes); + + if (strcmp(mem->desc, "flash") == 0) + { + pdata_t* pdata = PDATA(pgm); + + if (n_bytes > page_size) + { + avrdude_message(MSG_INFO, "%s: Buffer size (%u) exceeds page size (%u)\n", progname, n_bytes, page_size); + return -1; + } + + if (addr + n_bytes > pdata->flash_size) + { + avrdude_message(MSG_INFO, "%s: Program size (%u) exceeds flash size (%u)\n", progname, addr + n_bytes, pdata->flash_size); + return -1; + } + + uint8_t* page_buffer = (uint8_t*)malloc(pdata->page_size); + if (page_buffer == NULL) + { + avrdude_message(MSG_INFO, "%s: Failed to allocate memory\n", progname); + return -1; + } + + // Note: Page size reported by the bootloader may be smaller than device page size as configured in avrdude.conf. + int result = 0; + while (n_bytes > 0) + { + size_t chunk_size = n_bytes < pdata->page_size ? n_bytes : pdata->page_size; + + memcpy(page_buffer, mem->buf + addr, chunk_size); + memset(page_buffer + chunk_size, 0xFF, pdata->page_size - chunk_size); + + result = micronucleus_write_page(pdata, addr, page_buffer, pdata->page_size); + if (result < 0) + { + break; + } + + addr += chunk_size; + n_bytes -= chunk_size; + } + + free(page_buffer); + return result; + } + else + { + avrdude_message(MSG_INFO, "%s: Unsupported memory type: %s\n", progname, mem->desc); + return -1; + } +} + +static int micronucleus_parseextparams(PROGRAMMER* pgm, LISTID xparams) +{ + avrdude_message(MSG_DEBUG, "%s: micronucleus_parseextparams()\n", progname); + + pdata_t* pdata = PDATA(pgm); + for (LNODEID node = lfirst(xparams); node != NULL; node = lnext(node)) + { + const char* param = ldata(node); + + if (strcmp(param, "wait") == 0) + { + pdata->wait_until_device_present = true; + pdata->wait_timout = -1; + } + else if (strncmp(param, "wait=", 5) == 0) + { + pdata->wait_until_device_present = true; + pdata->wait_timout = atoi(param + 5); + } + else + { + avrdude_message(MSG_INFO, "%s: Invalid extended parameter '%s'\n", progname, param); + return -1; + } + } + + return 0; +} + +void micronucleus_initpgm(PROGRAMMER* pgm) +{ + strcpy(pgm->type, "Micronucleus V2.0"); + + pgm->setup = micronucleus_setup; + pgm->teardown = micronucleus_teardown; + pgm->initialize = micronucleus_initialize; + pgm->display = micronucleus_display; + pgm->powerup = micronucleus_powerup; + pgm->powerdown = micronucleus_powerdown; + pgm->enable = micronucleus_enable; + pgm->disable = micronucleus_disable; + pgm->program_enable = micronucleus_program_enable; + pgm->read_sig_bytes = micronucleus_read_sig_bytes; + pgm->chip_erase = micronucleus_chip_erase; + pgm->cmd = NULL; + pgm->open = micronucleus_open; + pgm->close = micronucleus_close; + pgm->read_byte = micronucleus_read_byte; + pgm->write_byte = micronucleus_write_byte; + pgm->paged_load = micronucleus_paged_load; + pgm->paged_write = micronucleus_paged_write; + pgm->parseextparams = micronucleus_parseextparams; +} + +#else /* !HAVE_LIBUSB */ + + // Give a proper error if we were not compiled with libusb +static int micronucleus_nousb_open(struct programmer_t* pgm, char* name) +{ + avrdude_message(MSG_INFO, "%s: error: No usb support. Please compile again with libusb installed.\n", progname); + return -1; +} + +void micronucleus_initpgm(PROGRAMMER* pgm) +{ + strcpy(pgm->type, "micronucleus"); + pgm->open = micronucleus_nousb_open; +} + +#endif /* HAVE_LIBUSB */ + +const char micronucleus_desc[] = "Micronucleus Bootloader"; diff --git a/src/micronucleus.h b/src/micronucleus.h new file mode 100644 index 00000000..46c67182 --- /dev/null +++ b/src/micronucleus.h @@ -0,0 +1,35 @@ +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * Copyright (C) 2019 Marius Greuel + * + * 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 . + */ + +#ifndef micronucleus_h +#define micronucleus_h + +#include "libavrdude.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const char micronucleus_desc[]; +void micronucleus_initpgm(PROGRAMMER* pgm); + +#ifdef __cplusplus +} +#endif + +#endif /* micronucleus_h */ diff --git a/src/pgm_type.c b/src/pgm_type.c index becf7bdd..195b6ffb 100644 --- a/src/pgm_type.c +++ b/src/pgm_type.c @@ -41,6 +41,7 @@ #include "jtag3.h" #include "linuxgpio.h" #include "linuxspi.h" +#include "micronucleus.h" #include "par.h" #include "pickit2.h" #include "ppi.h" @@ -85,6 +86,7 @@ const PROGRAMMER_TYPE programmers_types[] = { {"jtagice3_isp", stk500v2_jtag3_initpgm, stk500v2_jtag3_desc}, {"linuxgpio", linuxgpio_initpgm, linuxgpio_desc}, {"linuxspi", linuxspi_initpgm, linuxspi_desc}, + {"micronucleus", micronucleus_initpgm, micronucleus_desc}, {"par", par_initpgm, par_desc}, {"pickit2", pickit2_initpgm, pickit2_desc}, {"serbb", serbb_initpgm, serbb_desc},