avrdude/src/micronucleus.c

973 lines
28 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*/
// 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 <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include "avrdude.h"
#include "micronucleus.h"
#include "usbdevs.h"
#if defined(HAVE_LIBUSB)
#if defined(HAVE_USB_H)
#include <usb.h>
#elif defined(HAVE_LUSB0_USB_H)
#include <lusb0_usb.h>
#else
#error "libusb needs either <usb.h> or <lusb0_usb.h>"
#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 bool micronucleus_is_device_responsive(pdata_t* pdata, struct usb_device* device)
{
pdata->usb_handle = usb_open(device);
if (pdata->usb_handle == NULL)
{
return false;
}
int result = micronucleus_check_connection(pdata);
usb_close(pdata->usb_handle);
pdata->usb_handle = NULL;
return result >= 0;
}
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++)
{
pmsg_notice("trying to reconnect ...\n");
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)
{
pmsg_warning("unable to get bootloader info block: %s\n", usb_strerror());
return result;
}
else if (result < sizeof(buffer))
{
pmsg_warning("received invalid bootloader info block size: %d\n", 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)
{
pmsg_warning("unable to get bootloader info block: %s\n", usb_strerror());
return result;
}
else if (result < sizeof(buffer))
{
pmsg_warning("received invalid bootloader info block size: %d\n", 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)
{
pmsg_notice("Bootloader version: %d.%d\n", pdata->major_version, pdata->minor_version);
imsg_notice("Available flash size: %u\n", pdata->flash_size);
imsg_notice("Page size: %u\n", pdata->page_size);
imsg_notice("Bootloader start: 0x%04X\n", pdata->bootloader_start);
imsg_notice("Write sleep: %ums\n", pdata->write_sleep);
imsg_notice("Erase sleep: %ums\n", pdata->erase_sleep);
imsg_notice("Signature1: 0x%02X\n", pdata->signature1);
imsg_notice("Signature2: 0x%02X\n", pdata->signature2);
}
static int micronucleus_erase_device(pdata_t* pdata)
{
pmsg_debug("micronucleus_erase_device()\n");
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:
pmsg_notice("ignoring last error of erase command: %s\n", usb_strerror());
break;
default:
pmsg_warning("erase command failed, code %d: %s\n", result, usb_strerror());
return result;
}
}
delay_ms(pdata->erase_sleep);
result = micronucleus_check_connection(pdata);
if (result < 0)
{
pmsg_notice("connection dropped, trying to reconnect ...\n");
result = micronucleus_reconnect(pdata);
if (result < 0)
{
pmsg_warning("unable to reconnect USB device: %s\n", 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
{
pmsg_error("the reset vector of the user program does not contain a branch instruction\n");
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,
(char*)buffer, size,
MICRONUCLEUS_DEFAULT_TIMEOUT);
if (result < 0)
{
pmsg_error("unable to transfer page: %s\n", 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)
{
pmsg_error("unable to transfer page: %s\n", 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)
{
pmsg_error("unable to transfer page: %s\n", usb_strerror());
return result;
}
}
return 0;
}
static int micronucleus_write_page(pdata_t* pdata, uint32_t address, uint8_t* buffer, uint32_t size)
{
pmsg_debug("micronucleus_write_page(address=0x%04X, size=%d)\n", 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)
{
pmsg_debug("micronucleus_start()\n");
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)
{
pmsg_warning("start command failed: %s\n", usb_strerror());
return result;
}
return 0;
}
//-----------------------------------------------------------------------------
static void micronucleus_setup(PROGRAMMER* pgm)
{
pmsg_debug("micronucleus_setup()\n");
if ((pgm->cookie = malloc(sizeof(pdata_t))) == 0)
{
pmsg_error("out of memory allocating private data\n");
exit(1);
}
memset(pgm->cookie, 0, sizeof(pdata_t));
}
static void micronucleus_teardown(PROGRAMMER* pgm)
{
pmsg_debug("micronucleus_teardown()\n");
free(pgm->cookie);
}
static int micronucleus_initialize(const PROGRAMMER *pgm, const AVRPART *p) {
pmsg_debug("micronucleus_initialize()\n");
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(const PROGRAMMER *pgm, const char *prefix) {
pmsg_debug("micronucleus_display()\n");
}
static void micronucleus_powerup(const PROGRAMMER *pgm) {
pmsg_debug("micronucleus_powerup()\n");
}
static void micronucleus_powerdown(const PROGRAMMER *pgm) {
pmsg_debug("micronucleus_powerdown()\n");
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, const AVRPART *p) {
pmsg_debug("micronucleus_enable()\n");
}
static void micronucleus_disable(const PROGRAMMER *pgm) {
pmsg_debug("micronucleus_disable()\n");
}
static int micronucleus_program_enable(const PROGRAMMER *pgm, const AVRPART *p) {
pmsg_debug("micronucleus_program_enable()\n");
return 0;
}
static int micronucleus_read_sig_bytes(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem) {
pmsg_debug("micronucleus_read_sig_bytes()\n");
if (mem->size < 3)
{
pmsg_error("memory size %d < 3 too small for read_sig_bytes", mem->size);
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(const PROGRAMMER *pgm, const AVRPART *p) {
pmsg_debug("micronucleus_chip_erase()\n");
pdata_t* pdata = PDATA(pgm);
return micronucleus_erase_device(pdata);
}
static int micronucleus_open(PROGRAMMER* pgm, const char *port) {
pmsg_debug("micronucleus_open(\"%s\")\n", port);
pdata_t* pdata = PDATA(pgm);
const 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)
{
pmsg_error("invalid -P value %s\n", port);
imsg_error("use -P usb:bus:device\n");
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))
{
pmsg_warning("using PID 0x%04x, ignoring remaining PIDs in list\n", pid);
}
}
usb_init();
bool show_retry_message = true;
bool show_unresponsive_device_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);
if (!micronucleus_is_device_responsive(pdata, device))
{
if (show_unresponsive_device_message)
{
pmsg_warning("unresponsive Micronucleus device detected, please reconnect ...\n");
show_unresponsive_device_message = false;
}
continue;
}
pmsg_notice("found device with Micronucleus V%d.%d, bus:device: %s:%s\n",
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)
{
pmsg_warning("device with unsupported Micronucleus version V%d.%d\n",
pdata->major_version, pdata->minor_version);
continue;
}
pdata->usb_handle = usb_open(device);
if (pdata->usb_handle == NULL)
{
pmsg_error("unable to open USB device: %s\n", usb_strerror());
}
}
}
}
if (pdata->usb_handle == NULL && pdata->wait_until_device_present)
{
if (show_retry_message)
{
if (pdata->wait_timout < 0)
{
pmsg_error("no device found, waiting for device to be plugged in ...\n");
}
else
{
pmsg_error("no device found, waiting %d seconds for device to be plugged in ...\n",
pdata->wait_timout);
}
pmsg_error("press CTRL-C to terminate\n");
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)
{
pmsg_error("cannot find device with Micronucleus bootloader (%04X:%04X)\n", vid, pid);
return -1;
}
return 0;
}
static void micronucleus_close(PROGRAMMER* pgm)
{
pmsg_debug("micronucleus_close()\n");
pdata_t* pdata = PDATA(pgm);
if (pdata->usb_handle != NULL)
{
usb_close(pdata->usb_handle);
pdata->usb_handle = NULL;
}
}
static int micronucleus_read_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem,
unsigned long addr, unsigned char* value)
{
pmsg_debug("micronucleus_read_byte(desc=%s, addr=0x%04lX)\n", 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
{
pmsg_notice("reading not supported for %s memory\n", mem->desc);
return -1;
}
}
static int micronucleus_write_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem,
unsigned long addr, unsigned char value)
{
pmsg_debug("micronucleus_write_byte(desc=%s, addr=0x%04lX)\n", mem->desc, addr);
return -1;
}
static int micronucleus_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem,
unsigned int page_size,
unsigned int addr, unsigned int n_bytes)
{
pmsg_debug("micronucleus_paged_load(page_size=0x%X, addr=0x%X, n_bytes=0x%X)\n", page_size, addr, n_bytes);
return -1;
}
static int micronucleus_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem,
unsigned int page_size,
unsigned int addr, unsigned int n_bytes)
{
pmsg_debug("micronucleus_paged_write(page_size=0x%X, addr=0x%X, n_bytes=0x%X)\n", page_size, addr, n_bytes);
if (strcmp(mem->desc, "flash") == 0)
{
pdata_t* pdata = PDATA(pgm);
if (n_bytes > page_size)
{
pmsg_error("buffer size %u exceeds page size %u\n", n_bytes, page_size);
return -1;
}
if (addr + n_bytes > pdata->flash_size)
{
pmsg_error("program size %u exceeds flash size %u\n", addr + n_bytes, pdata->flash_size);
return -1;
}
uint8_t* page_buffer = (uint8_t*)malloc(pdata->page_size);
if (page_buffer == NULL)
{
pmsg_error("unable to allocate memory\n");
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
{
pmsg_error("unsupported memory type: %s\n", mem->desc);
return -1;
}
}
static int micronucleus_parseextparams(const PROGRAMMER *pgm, const LISTID xparams) {
pmsg_debug("micronucleus_parseextparams()\n");
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
{
pmsg_error("invalid extended parameter '%s'\n", 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(PROGRAMMER* pgm, const char* name) {
pmsg_error("no usb support; please compile again with libusb installed\n");
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";