rp2040: add make flash support

This adds `make flash` support for the rp2040 target. Flashing is
performed using a custom `rp2040_flash` tool that uses the PICOBOOT
protocol. Root is not required.

The user specifies the serial device of the rp2040 they wish to flash as
the device. This device is reset into bootsel mode and `rp2040_flash`
is invoked on the original USB device path.

If the device is already in bootloader mode, the user can specify
'first' as `FLASH_DEVICE` which will simply invoke `rp2040_flash` with
no bus/address options.

Signed-off-by: Lasse Dalegaard <dalegaard@gmail.com>
This commit is contained in:
Lasse Dalegaard 2022-01-03 23:28:54 +01:00 committed by KevinOConnor
parent 8a3727ef74
commit 7c0559c6e6
10 changed files with 1124 additions and 7 deletions

1
lib/.gitignore vendored
View File

@ -3,3 +3,4 @@ bossac/bin/
bossac/obj/ bossac/obj/
hidflash/hid-flash hidflash/hid-flash
hub-ctrl/hub-ctrl hub-ctrl/hub-ctrl
rp2040_flash/rp2040_flash

View File

@ -74,6 +74,11 @@ version 1.2.0 (bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7). It has been
modified so that it can build outside of the pico sdk. See modified so that it can build outside of the pico sdk. See
rp2040.patch for the modifications. rp2040.patch for the modifications.
The rp2040_flash directory contains a light-weight bootsel flash tool.
It uses C part of the the `picoboot_connection` directory found in:
https://github.com/raspberrypi/picotool.git
version v1.1.0 (55fd880c3dc029b961fc1a0967a6cfdc0af02721).
The hub-ctrl directory contains code from: The hub-ctrl directory contains code from:
https://github.com/codazoda/hub-ctrl.c/ https://github.com/codazoda/hub-ctrl.c/
revision 42095e522859059e8a5f4ec05c1e3def01a870a9. revision 42095e522859059e8a5f4ec05c1e3def01a870a9.

124
lib/rp2040/boot/picoboot.h Normal file
View File

@ -0,0 +1,124 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _BOOT_PICOBOOT_H
#define _BOOT_PICOBOOT_H
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#ifndef NO_PICO_PLATFORM
#include "pico/platform.h"
#endif
/** \file picoboot.h
* \defgroup boot_picoboot boot_picoboot
*
* Header file for the PICOBOOT USB interface exposed by an RP2040 in BOOTSEL mode.
*/
#define PICOBOOT_MAGIC 0x431fd10bu
// --------------------------------------------
// CONTROL REQUESTS FOR THE PICOBOOT INTERFACE
// --------------------------------------------
// size 0 OUT - unstall EPs and reset
#define PICOBOOT_IF_RESET 0x41
// size 16 IN - return the status of the last command
#define PICOBOOT_IF_CMD_STATUS 0x42
// --------------------------------------------------
// COMMAND REQUESTS SENT TO THE PICOBOOT OUT ENDPOINT
// --------------------------------------------------
//
// picoboot_cmd structure of size 32 is sent to OUT endpoint
// transfer_length bytes are transferred via IN/OUT
// device responds on success with 0 length ACK packet set via OUT/IN
// device may stall the transferring endpoint in case of error
enum picoboot_cmd_id {
PC_EXCLUSIVE_ACCESS = 0x1,
PC_REBOOT = 0x2,
PC_FLASH_ERASE = 0x3,
PC_READ = 0x84, // either RAM or FLASH
PC_WRITE = 5, // either RAM or FLASH (does no erase)
PC_EXIT_XIP = 0x6,
PC_ENTER_CMD_XIP = 0x7,
PC_EXEC = 0x8,
PC_VECTORIZE_FLASH = 0x9
};
enum picoboot_status {
PICOBOOT_OK = 0,
PICOBOOT_UNKNOWN_CMD = 1,
PICOBOOT_INVALID_CMD_LENGTH = 2,
PICOBOOT_INVALID_TRANSFER_LENGTH = 3,
PICOBOOT_INVALID_ADDRESS = 4,
PICOBOOT_BAD_ALIGNMENT = 5,
PICOBOOT_INTERLEAVED_WRITE = 6,
PICOBOOT_REBOOTING = 7,
PICOBOOT_UNKNOWN_ERROR = 8,
};
struct __packed picoboot_reboot_cmd {
uint32_t dPC; // 0 means reset into bootrom
uint32_t dSP;
uint32_t dDelayMS;
};
// used for EXEC, VECTORIZE_FLASH
struct __packed picoboot_address_only_cmd {
uint32_t dAddr;
};
// used for READ, WRITE, FLASH_ERASE
struct __packed picoboot_range_cmd {
uint32_t dAddr;
uint32_t dSize;
};
enum picoboot_exclusive_type {
NOT_EXCLUSIVE = 0,
EXCLUSIVE,
EXCLUSIVE_AND_EJECT
};
struct __packed picoboot_exclusive_cmd {
uint8_t bExclusive;
};
// little endian
struct __packed __aligned(4) picoboot_cmd {
uint32_t dMagic;
uint32_t dToken; // an identifier for this token to correlate with a status response
uint8_t bCmdId; // top bit set for IN
uint8_t bCmdSize; // bytes of actual data in the arg part of this structure
uint16_t _unused;
uint32_t dTransferLength; // length of IN/OUT transfer (or 0) if none
union {
uint8_t args[16];
struct picoboot_reboot_cmd reboot_cmd;
struct picoboot_range_cmd range_cmd;
struct picoboot_address_only_cmd address_only_cmd;
struct picoboot_exclusive_cmd exclusive_cmd;
};
};
static_assert(32 == sizeof(struct picoboot_cmd), "picoboot_cmd must be 32 bytes big");
struct __packed __aligned(4) picoboot_cmd_status {
uint32_t dToken;
uint32_t dStatusCode;
uint8_t bCmdId;
uint8_t bInProgress;
uint8_t _pad[6];
};
static_assert(16 == sizeof(struct picoboot_cmd_status), "picoboot_cmd_status must be 16 bytes big");
#endif

139
lib/rp2040/pico/platform.h Normal file
View File

@ -0,0 +1,139 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_PLATFORM_H_
#define _PICO_PLATFORM_H_
#include "hardware/platform_defs.h"
#include <stddef.h>
#ifdef __unix__
#include <sys/cdefs.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define __not_in_flash(grup)
#define __not_in_flash_func(func) func
#define __no_inline_not_in_flash_func(func)
#define __in_flash(group)
#define __scratch_x(group)
#define __scratch_y(group)
#define __packed_aligned
#define __packed
#define __time_critical_func(x) x
#define __after_data(group)
//int running_on_fpga() { return false; }
extern void tight_loop_contents();
#ifndef __STRING
#define __STRING(x) #x
#endif
#ifndef _MSC_VER
#ifndef __noreturn
#define __noreturn __attribute((noreturn))
#endif
#ifndef __unused
#define __unused __attribute__((unused))
#endif
#ifndef __noinline
#define __noinline __attribute__((noinline))
#endif
#ifndef __aligned
#define __aligned(x) __attribute__((aligned(x)))
#endif
#define PICO_WEAK_FUNCTION_DEF(x) _Pragma(__STRING(weak x))
#define PICO_WEAK_FUNCTION_IMPL_NAME(x) x
#else
#ifndef __noreturn
#define __noreturn __declspec(noreturn)
#endif
#ifndef __unused
#define __unused
#endif
#ifndef __noinline
#define __noinline __declspec(noinline)
#endif
#ifndef __aligned
#define __aligned(x) __declspec(align(x))
#endif
#ifndef __CONCAT
#define __CONCAT(x,y) x ## y
#endif
#define __thread __declspec( thread )
#define PICO_WEAK_FUNCTION_DEF(x) __pragma(comment(linker, __STRING(/alternatename:_##x=_##x##__weak)));
#define PICO_WEAK_FUNCTION_IMPL_NAME(x) x ## __weak
static __noreturn void __builtin_unreachable() {
}
#include <intrin.h>
#define __builtin_clz __lzcnt
#endif
#ifndef count_of
#define count_of(a) (sizeof(a)/sizeof((a)[0]))
#endif
#ifndef MAX
#define MAX(a, b) ((a)>(b)?(a):(b))
#endif
#ifndef MIN
#define MIN(a, b) ((b)>(a)?(a):(b))
#endif
// abort in our case
void __noreturn __breakpoint();
void __noreturn panic_unsupported();
void __noreturn panic(const char *fmt, ...);
// arggggghhhh there is a weak function called sem_init used by SDL
#define sem_init sem_init_alternative
extern uint32_t host_safe_hw_ptr_impl(uintptr_t x);
// return a 32 bit handle for a raw ptr; DMA chaining for example embeds pointers in 32 bit values
// which of course does not work if we're running the code natively on a 64 bit platforms. Therefore
// we provide this macro which allows that code to provide a 64->32 bit mapping in host mode
#define host_safe_hw_ptr(x) host_safe_hw_ptr_impl((uintptr_t)(x))
void *decode_host_safe_hw_ptr(uint32_t ptr);
#define __fast_mul(a,b) ((a)*(b))
typedef unsigned int uint;
static inline int32_t __mul_instruction(int32_t a,int32_t b)
{
return a*b;
}
static inline void __compiler_memory_barrier(void) {
}
#ifdef __cplusplus
}
#endif
#endif

20
lib/rp2040_flash/Makefile Normal file
View File

@ -0,0 +1,20 @@
CC=gcc
CFLAGS=-c -Wall -ggdb
LDFALGS=
SOURCES=main.c picoboot_connection.c
OBJECTS=$(SOURCES:.c=.o)
LIBS=`pkg-config libusb-1.0 --libs`
INCLUDE_DIRS+=-I../rp2040/ `pkg-config libusb-1.0 --cflags`
EXECUTABLE=rp2040_flash
all: $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS)
$(CC) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $@
.c.o:
$(CC) $(CFLAGS) $(INCLUDE_DIRS) $< -o $@
clean:
rm -f $(OBJECTS) $(EXECUTABLE)

246
lib/rp2040_flash/main.c Normal file
View File

@ -0,0 +1,246 @@
// Simple rp2040 picoboot based flash tool for use with Klipper
//
// Copyright (C) 2022 Lasse Dalegaard <dalegaard@gmail.com>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "picoboot_connection.h"
#include "boot/uf2.h"
#define FLASH_MAX_SIZE (FLASH_END - FLASH_START)
#define FLASH_NUM_WRITE_BLOCKS (FLASH_MAX_SIZE / PAGE_SIZE)
#define FLASH_NUM_ERASE_BLOCKS (FLASH_MAX_SIZE / FLASH_SECTOR_ERASE_SIZE)
struct flash_data {
size_t num_blocks;
uint8_t flash_data[FLASH_MAX_SIZE];
bool write_blocks[FLASH_NUM_WRITE_BLOCKS];
bool erase_blocks[FLASH_NUM_ERASE_BLOCKS];
};
int load_flash_data(const char *filename, struct flash_data *target) {
int rc = 0;
FILE *file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Could not open image file %s\n", filename);
rc = errno;
goto do_exit;
}
target->num_blocks = 0;
memset(target->write_blocks, 0, sizeof(target->write_blocks));
memset(target->erase_blocks, 0, sizeof(target->erase_blocks));
struct uf2_block block;
while (1) {
if(fread(&block, sizeof(struct uf2_block), 1, file) != 1) {
if (feof(file)) {
break;
}
fprintf(stderr, "Unexpected EOF reading image\n");
rc = errno;
goto do_exit;
}
// Check magic numbers
if (block.magic_start0 != UF2_MAGIC_START0) continue;
if (block.magic_start1 != UF2_MAGIC_START1) continue;
if (block.magic_end != UF2_MAGIC_END) continue;
// Check block is valid for flashing
// Always family ID.
if (!(block.flags & UF2_FLAG_FAMILY_ID_PRESENT)) continue;
if (block.file_size != RP2040_FAMILY_ID) continue;
if (block.flags & UF2_FLAG_NOT_MAIN_FLASH) continue;
if (block.payload_size != PAGE_SIZE) continue;
// Bounds and alignment checking
if (block.target_addr != (block.target_addr & ~(PAGE_SIZE-1))) continue;
if (block.target_addr > FLASH_END - PAGE_SIZE) continue;
if (block.target_addr < FLASH_START) continue;
uint32_t offset = block.target_addr - FLASH_START;
// Copy data and mark the matching write and erase blocks
memcpy(&target->flash_data[offset], block.data, PAGE_SIZE);
target->write_blocks[offset / PAGE_SIZE] = 1;
target->erase_blocks[offset / FLASH_SECTOR_ERASE_SIZE] = 1;
target->num_blocks++;
}
do_exit:
if (file) {
fclose(file);
}
return rc;
}
const char *status_codes_strings[] = {
"ok",
"unknown command",
"bad address alignment",
"interleaved write",
"invalid address",
"invalid cmd length",
"invalid transfer length",
"rebooting",
"unknown error",
};
int report_error(libusb_device_handle *handle, const char *cmd) {
struct picoboot_cmd_status status;
status.dStatusCode = 0;
int rc = picoboot_cmd_status(handle, &status);
if (rc) {
fprintf(stderr, "Command %s failed, and it was not possible to "
"query PICOBOOT status\n", cmd);
} else {
if (status.dStatusCode == 0) status.dStatusCode = 8;
fprintf(stderr, "Command %s failed with status %d: %s\n",
cmd, status.dStatusCode,
status_codes_strings[status.dStatusCode]);
}
return 1;
};
int picoboot_flash(libusb_device_handle *handle, struct flash_data *image) {
fprintf(stderr, "Resetting interface\n");
if (picoboot_reset(handle)) {
return report_error(handle, "reset");
}
fprintf(stderr, "Locking\n");
if (picoboot_exclusive_access(handle, EXCLUSIVE)) {
return report_error(handle, "exclusive_access");
}
fprintf(stderr, "Exiting XIP mode\n");
if (picoboot_exit_xip(handle)) {
return report_error(handle, "exit_xip");
}
fprintf(stderr, "Erasing\n");
for(size_t i = 0; i < FLASH_NUM_ERASE_BLOCKS; i++) {
if (!image->erase_blocks[i]) continue;
uint32_t addr = FLASH_START + i * FLASH_SECTOR_ERASE_SIZE;
if (picoboot_flash_erase(handle, addr, FLASH_SECTOR_ERASE_SIZE)) {
return report_error(handle, "flash_erase");
}
}
fprintf(stderr, "Flashing\n");
for(size_t i = 0; i < FLASH_NUM_WRITE_BLOCKS; i++) {
if (!image->write_blocks[i]) continue;
uint32_t addr = FLASH_START + i * PAGE_SIZE;
uint8_t *buf = &image->flash_data[i * PAGE_SIZE];
if (picoboot_write(handle, addr, buf, PAGE_SIZE)) {
return report_error(handle, "write");
}
}
fprintf(stderr, "Rebooting device\n");
if (picoboot_reboot(handle, 0, 0, 500)) {
return report_error(handle, "reboot");
}
return 0;
}
void print_usage(char *argv[]) {
fprintf(stderr, "Usage: %s <uf2 image> [bus addr]\n", argv[0]);
exit(1);
}
int main(int argc, char *argv[]) {
libusb_context *ctx = 0;
struct libusb_device **devs = 0;
libusb_device_handle *handle = 0;
struct flash_data *image = malloc(sizeof(struct flash_data));
int rc = 0;
if (argc != 2 && argc != 4) {
print_usage(argv);
}
if (load_flash_data(argv[1], image)) {
fprintf(stderr, "Could not load flash image, exiting\n");
rc = 1;
goto do_exit;
}
fprintf(stderr, "Loaded UF2 image with %lu pages\n", image->num_blocks);
bool has_target = false;
uint8_t target_bus = 0;
uint8_t target_address = 0;
if(argc == 4) {
has_target = true;
char *endptr;
target_bus = strtol(argv[2], &endptr, 10);
if (endptr == argv[2] || *endptr != 0) print_usage(argv);
target_address = strtol(argv[3], &endptr, 10);
if (endptr == argv[3] || *endptr != 0) print_usage(argv);
}
if (libusb_init(&ctx)) {
fprintf(stderr, "Could not initialize libusb\n");
rc = 1;
goto do_exit;
}
ssize_t cnt = libusb_get_device_list(ctx, &devs);
if (cnt < 0) {
fprintf(stderr, "Failed to enumerate USB devices: %s",
libusb_strerror(cnt));
rc = 1;
goto do_exit;
}
for (libusb_device **dev = devs; *dev; ++dev) {
if (has_target) {
if (target_bus != libusb_get_bus_number(*dev)) continue;
if (target_address != libusb_get_device_address(*dev)) continue;
}
enum picoboot_device_result res = picoboot_open_device(*dev, &handle);
if (res == dr_vidpid_bootrom_ok) {
break;
}
if (handle) {
libusb_close(handle);
handle = 0;
}
}
if (!handle) {
fprintf(stderr, "No rp2040 in BOOTSEL mode was found\n");
goto do_exit;
}
libusb_device *dev = libusb_get_device(handle);
fprintf(stderr, "Found rp2040 device on USB bus %d address %d\n",
libusb_get_bus_number(dev), libusb_get_device_address(dev));
fprintf(stderr, "Flashing...\n");
rc = picoboot_flash(handle, image);
do_exit:
if (handle) {
libusb_close(handle);
}
if (devs) {
libusb_free_device_list(devs, 1);
}
if (ctx) {
libusb_exit(ctx);
}
free(image);
return rc;
}

View File

@ -0,0 +1,428 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "picoboot_connection.h"
#if false && !defined(NDEBUG)
#define output(format,...) printf(format, __VA_ARGS__)
#else
#define output(format,...) ((void)0)
#endif
static bool verbose;
// todo test sparse binary (well actually two range is this)
#define VENDOR_ID_RASPBERRY_PI 0x2e8au
#define PRODUCT_ID_RP2_USBBOOT 0x0003u
#define PRODUCT_ID_PICOPROBE 0x0004u
#define PRODUCT_ID_MICROPYTHON 0x0005u
#define PRODUCT_ID_STDIO_USB 0x000au
uint32_t crc32_for_byte(uint32_t remainder) {
const uint32_t POLYNOMIAL = 0x4C11DB7;
remainder <<= 24u;
for (uint bit = 8; bit > 0; bit--) {
if (remainder & 0x80000000)
remainder = (remainder << 1) ^ POLYNOMIAL;
else
remainder = (remainder << 1);
}
return remainder;
}
uint32_t crc32_sw(const uint8_t *buf, uint count, uint32_t crc) {
static uint32_t table[0x100];
if (!table[1]) {
for (uint i = 0; i < count_of(table); i++) {
table[i] = crc32_for_byte(i);
}
}
for (uint i = 0; i < count; ++i) {
crc = (crc << 8u) ^ table[(uint8_t) ((crc >> 24u) ^ buf[i])];
}
return crc;
}
uint interface;
uint out_ep;
uint in_ep;
enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_device_handle **dev_handle) {
struct libusb_device_descriptor desc;
struct libusb_config_descriptor *config;
*dev_handle = NULL;
int ret = libusb_get_device_descriptor(device, &desc);
if (ret && verbose) {
output("Failed to read device descriptor");
}
if (!ret) {
if (desc.idVendor != VENDOR_ID_RASPBERRY_PI) {
return dr_vidpid_unknown;
}
switch (desc.idProduct) {
case PRODUCT_ID_MICROPYTHON:
return dr_vidpid_micropython;
case PRODUCT_ID_PICOPROBE:
return dr_vidpid_picoprobe;
case PRODUCT_ID_STDIO_USB:
return dr_vidpid_stdio_usb;
case PRODUCT_ID_RP2_USBBOOT:
break;
default:
return dr_vidpid_unknown;
}
ret = libusb_get_active_config_descriptor(device, &config);
if (ret && verbose) {
output("Failed to read config descriptor\n");
}
}
if (!ret) {
ret = libusb_open(device, dev_handle);
if (ret && verbose) {
output("Failed to open device %d\n", ret);
}
if (ret) {
return dr_vidpid_bootrom_cant_connect;
}
}
if (!ret) {
if (config->bNumInterfaces == 1) {
interface = 0;
} else {
interface = 1;
}
if (config->interface[interface].altsetting[0].bInterfaceClass == 0xff &&
config->interface[interface].altsetting[0].bNumEndpoints == 2) {
out_ep = config->interface[interface].altsetting[0].endpoint[0].bEndpointAddress;
in_ep = config->interface[interface].altsetting[0].endpoint[1].bEndpointAddress;
}
if (out_ep && in_ep && !(out_ep & 0x80u) && (in_ep & 0x80u)) {
if (verbose) output("Found PICOBOOT interface\n");
ret = libusb_claim_interface(*dev_handle, interface);
if (ret) {
if (verbose) output("Failed to claim interface\n");
return dr_vidpid_bootrom_no_interface;
}
return dr_vidpid_bootrom_ok;
} else {
if (verbose) output("Did not find PICOBOOT interface\n");
return dr_vidpid_bootrom_no_interface;
}
}
assert(ret);
if (*dev_handle) {
libusb_close(*dev_handle);
*dev_handle = NULL;
}
return dr_error;
}
static bool is_halted(libusb_device_handle *usb_device, int ep) {
uint8_t data[2];
int transferred = libusb_control_transfer(
usb_device,
/*LIBUSB_REQUEST_TYPE_STANDARD | */LIBUSB_RECIPIENT_ENDPOINT | LIBUSB_ENDPOINT_IN,
LIBUSB_REQUEST_GET_STATUS,
0, ep,
data, sizeof(data),
1000);
if (transferred != sizeof(data)) {
output("Get status failed\n");
return false;
}
if (data[0] & 1) {
if (verbose) output("%d was halted\n", ep);
return true;
}
if (verbose) output("%d was not halted\n", ep);
return false;
}
int picoboot_reset(libusb_device_handle *usb_device) {
if (verbose) output("RESET\n");
if (is_halted(usb_device, in_ep))
libusb_clear_halt(usb_device, in_ep);
if (is_halted(usb_device, out_ep))
libusb_clear_halt(usb_device, out_ep);
int ret =
libusb_control_transfer(usb_device, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_INTERFACE,
PICOBOOT_IF_RESET, 0, interface, NULL, 0, 1000);
if (ret != 0) {
output(" ...failed\n");
return ret;
}
if (verbose) output(" ...ok\n");
return 0;
}
int picoboot_cmd_status_verbose(libusb_device_handle *usb_device, struct picoboot_cmd_status *status, bool local_verbose) {
struct picoboot_cmd_status s;
if (!status) status = &s;
if (local_verbose) output("CMD_STATUS\n");
int ret =
libusb_control_transfer(usb_device,
LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
PICOBOOT_IF_CMD_STATUS, 0, interface, (uint8_t *) status, sizeof(*status), 1000);
if (ret != sizeof(*status)) {
output(" ...failed\n");
return ret;
}
if (local_verbose)
output(" ... cmd %02x%s tok=%08x status=%d\n", status->bCmdId, status->bInProgress ? " (in progress)" : "",
status->dToken, status->dStatusCode);
return 0;
}
int picoboot_cmd_status(libusb_device_handle *usb_device, struct picoboot_cmd_status *status) {
return picoboot_cmd_status_verbose(usb_device, status, verbose);
}
int one_time_bulk_timeout;
int picoboot_cmd(libusb_device_handle *usb_device, struct picoboot_cmd *cmd, uint8_t *buffer, uint buf_size) {
int sent = 0;
int ret;
static int token = 1;
cmd->dMagic = PICOBOOT_MAGIC;
cmd->dToken = token++;
ret = libusb_bulk_transfer(usb_device, out_ep, (uint8_t *) cmd, sizeof(struct picoboot_cmd), &sent, 3000);
if (ret != 0 || sent != sizeof(struct picoboot_cmd)) {
output(" ...failed to send command %d\n", ret);
return ret;
}
int timeout = 10000;
if (one_time_bulk_timeout) {
timeout = one_time_bulk_timeout;
one_time_bulk_timeout = 0;
}
if (cmd->dTransferLength != 0) {
assert(buf_size >= cmd->dTransferLength);
if (cmd->bCmdId & 0x80u) {
if (verbose) output(" receive %d...\n", cmd->dTransferLength);
int received = 0;
ret = libusb_bulk_transfer(usb_device, in_ep, buffer, cmd->dTransferLength, &received, timeout);
if (ret != 0 || received != (int) cmd->dTransferLength) {
output(" ...failed to receive data %d %d/%d\n", ret, received, cmd->dTransferLength);
if (!ret) ret = 1;
return ret;
}
} else {
if (verbose) output(" send %d...\n", cmd->dTransferLength);
ret = libusb_bulk_transfer(usb_device, out_ep, buffer, cmd->dTransferLength, &sent, timeout);
if (ret != 0 || sent != (int) cmd->dTransferLength) {
output(" ...failed to send data %d %d/%d\n", ret, sent, cmd->dTransferLength);
if (!ret) ret = 1;
picoboot_cmd_status_verbose(usb_device, NULL, true);
return ret;
}
}
}
// ack is in opposite direction
int received = 0;
uint8_t spoon[64];
if (cmd->bCmdId & 0x80u) {
if (verbose) output("zero length out\n");
ret = libusb_bulk_transfer(usb_device, out_ep, spoon, 1, &received, cmd->dTransferLength == 0 ? timeout : 3000);
} else {
if (verbose) output("zero length in\n");
ret = libusb_bulk_transfer(usb_device, in_ep, spoon, 1, &received, cmd->dTransferLength == 0 ? timeout : 3000);
}
return ret;
}
int picoboot_exclusive_access(libusb_device_handle *usb_device, uint8_t exclusive) {
if (verbose) output("EXCLUSIVE ACCESS %d\n", exclusive);
struct picoboot_cmd cmd;
cmd.bCmdId = PC_EXCLUSIVE_ACCESS;
cmd.exclusive_cmd.bExclusive = exclusive;
cmd.bCmdSize = sizeof(struct picoboot_exclusive_cmd);
cmd.dTransferLength = 0;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_exit_xip(libusb_device_handle *usb_device) {
struct picoboot_cmd cmd;
if (verbose) output("EXIT_XIP\n");
cmd.bCmdId = PC_EXIT_XIP;
cmd.bCmdSize = 0;
cmd.dTransferLength = 0;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_enter_cmd_xip(libusb_device_handle *usb_device) {
struct picoboot_cmd cmd;
if (verbose) output("ENTER_CMD_XIP\n");
cmd.bCmdId = PC_ENTER_CMD_XIP;
cmd.bCmdSize = 0;
cmd.dTransferLength = 0;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_reboot(libusb_device_handle *usb_device, uint32_t pc, uint32_t sp, uint32_t delay_ms) {
struct picoboot_cmd cmd;
if (verbose) output("REBOOT %08x %08x %u\n", (uint) pc, (uint) sp, (uint) delay_ms);
cmd.bCmdId = PC_REBOOT;
cmd.bCmdSize = sizeof(cmd.reboot_cmd);
cmd.dTransferLength = 0;
cmd.reboot_cmd.dPC = pc;
cmd.reboot_cmd.dSP = sp;
cmd.reboot_cmd.dDelayMS = delay_ms;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_exec(libusb_device_handle *usb_device, uint32_t addr) {
struct picoboot_cmd cmd;
// shouldn't be necessary any more
// addr |= 1u; // Thumb bit
if (verbose) output("EXEC %08x\n", (uint) addr);
cmd.bCmdId = PC_EXEC;
cmd.bCmdSize = sizeof(cmd.address_only_cmd);
cmd.dTransferLength = 0;
cmd.address_only_cmd.dAddr = addr;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_flash_erase(libusb_device_handle *usb_device, uint32_t addr, uint32_t len) {
struct picoboot_cmd cmd;
if (verbose) output("FLASH_ERASE %08x+%08x\n", (uint) addr, (uint) len);
cmd.bCmdId = PC_FLASH_ERASE;
cmd.bCmdSize = sizeof(cmd.range_cmd);
cmd.range_cmd.dAddr = addr;
cmd.range_cmd.dSize = len;
cmd.dTransferLength = 0;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_vector(libusb_device_handle *usb_device, uint32_t addr) {
struct picoboot_cmd cmd;
if (verbose) output("VECTOR %08x\n", (uint) addr);
cmd.bCmdId = PC_VECTORIZE_FLASH;
cmd.bCmdSize = sizeof(cmd.address_only_cmd);
cmd.range_cmd.dAddr = addr;
cmd.dTransferLength = 0;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_write(libusb_device_handle *usb_device, uint32_t addr, uint8_t *buffer, uint32_t len) {
struct picoboot_cmd cmd;
if (verbose) output("WRITE %08x+%08x\n", (uint) addr, (uint) len);
cmd.bCmdId = PC_WRITE;
cmd.bCmdSize = sizeof(cmd.range_cmd);
cmd.range_cmd.dAddr = addr;
cmd.range_cmd.dSize = cmd.dTransferLength = len;
return picoboot_cmd(usb_device, &cmd, buffer, len);
}
int picoboot_read(libusb_device_handle *usb_device, uint32_t addr, uint8_t *buffer, uint32_t len) {
memset(buffer, 0xaa, len);
if (verbose) output("READ %08x+%08x\n", (uint) addr, (uint) len);
struct picoboot_cmd cmd;
cmd.bCmdId = PC_READ;
cmd.bCmdSize = sizeof(cmd.range_cmd);
cmd.range_cmd.dAddr = addr;
cmd.range_cmd.dSize = cmd.dTransferLength = len;
int ret = picoboot_cmd(usb_device, &cmd, buffer, len);
if (!ret && len < 256 && verbose) {
for (uint32_t i = 0; i < len; i += 32) {
output("\t");
for (uint32_t j = i; j < MIN(len, i + 32); j++) {
output("0x%02x, ", buffer[j]);
}
output("\n");
}
}
return ret;
}
#if 0
// Peek/poke via EXEC
// 00000000 <poke>:
// 0: 4801 ldr r0, [pc, #4] ; (8 <data>)
// 2: 4902 ldr r1, [pc, #8] ; (c <addr>)
// 4: 6008 str r0, [r1, #0]
// 6: 4770 bx lr
// 00000008 <data>:
// 8: 12345678 .word 0x12345678
// 0000000c <addr>:
// c: 9abcdef0 .word 0x9abcdef0
static const size_t picoboot_poke_cmd_len = 8;
static const uint8_t picoboot_poke_cmd[] = {
0x01, 0x48, 0x02, 0x49, 0x08, 0x60, 0x70, 0x47
};
// 00000000 <peek>:
// 0: 4802 ldr r0, [pc, #8] ; (c <inout>)
// 2: 6800 ldr r0, [r0, #0]
// 4: 4679 mov r1, pc
// 6: 6048 str r0, [r1, #4]
// 8: 4770 bx lr
// a: 46c0 nop ; (mov r8, r8)
// 0000000c <inout>:
// c: 0add7355 .word 0x0add7355
static const size_t picoboot_peek_cmd_len = 12;
static const uint8_t picoboot_peek_cmd[] = {
0x02, 0x48, 0x00, 0x68, 0x79, 0x46, 0x48, 0x60, 0x70, 0x47, 0xc0, 0x46
};
// TODO better place for this e.g. the USB DPRAM location the controller has already put it in
#define PEEK_POKE_CODE_LOC 0x20000000u
int picoboot_poke(libusb_device_handle *usb_device, uint32_t addr, uint32_t data) {
const size_t prog_size = picoboot_poke_cmd_len + 8;
uint8_t prog[prog_size];
output("POKE (D)%08x -> (A)%08x\n", data, addr);
memcpy(prog, picoboot_poke_cmd, picoboot_poke_cmd_len);
*(uint32_t *) (prog + picoboot_poke_cmd_len) = data;
*(uint32_t *) (prog + picoboot_poke_cmd_len + 4) = addr;
int ret = picoboot_write(usb_device, PEEK_POKE_CODE_LOC, prog, prog_size);
if (ret)
return ret;
return picoboot_exec(usb_device, PEEK_POKE_CODE_LOC);
}
// TODO haven't checked the store goes to the right address :)
int picoboot_peek(libusb_device_handle *usb_device, uint32_t addr, uint32_t *data) {
const size_t prog_size = picoboot_peek_cmd_len + 4;
uint8_t prog[prog_size];
output("PEEK %08x\n", addr);
memcpy(prog, picoboot_peek_cmd, picoboot_peek_cmd_len);
*(uint32_t *) (prog + picoboot_peek_cmd_len) = addr;
int ret = picoboot_write(usb_device, PEEK_POKE_CODE_LOC, prog, prog_size);
if (ret)
return ret;
ret = picoboot_exec(usb_device, PEEK_POKE_CODE_LOC);
if (ret)
return ret;
return picoboot_read(usb_device, PEEK_POKE_CODE_LOC + picoboot_peek_cmd_len, (uint8_t *) data, sizeof(uint32_t));
}
#endif

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICOBOOT_CONNECTION_H
#define _PICOBOOT_CONNECTION_H
// todo we should use fully encapsulate libusb
#include <assert.h>
#include <libusb.h>
#include "boot/picoboot.h"
#ifdef __cplusplus
extern "C" {
#endif
enum picoboot_device_result {
dr_vidpid_bootrom_ok,
dr_vidpid_bootrom_no_interface,
dr_vidpid_bootrom_cant_connect,
dr_vidpid_micropython,
dr_vidpid_picoprobe,
dr_vidpid_unknown,
dr_error,
dr_vidpid_stdio_usb,
};
enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_device_handle **dev_handle);
int picoboot_reset(libusb_device_handle *usb_device);
int picoboot_cmd_status_verbose(libusb_device_handle *usb_device, struct picoboot_cmd_status *status,
bool local_verbose);
int picoboot_cmd_status(libusb_device_handle *usb_device, struct picoboot_cmd_status *status);
int picoboot_exclusive_access(libusb_device_handle *usb_device, uint8_t exclusive);
int picoboot_enter_cmd_xip(libusb_device_handle *usb_device);
int picoboot_exit_xip(libusb_device_handle *usb_device);
int picoboot_reboot(libusb_device_handle *usb_device, uint32_t pc, uint32_t sp, uint32_t delay_ms);
int picoboot_exec(libusb_device_handle *usb_device, uint32_t addr);
int picoboot_flash_erase(libusb_device_handle *usb_device, uint32_t addr, uint32_t len);
int picoboot_vector(libusb_device_handle *usb_device, uint32_t addr);
int picoboot_write(libusb_device_handle *usb_device, uint32_t addr, uint8_t *buffer, uint32_t len);
int picoboot_read(libusb_device_handle *usb_device, uint32_t addr, uint8_t *buffer, uint32_t len);
int picoboot_poke(libusb_device_handle *usb_device, uint32_t addr, uint32_t data);
int picoboot_peek(libusb_device_handle *usb_device, uint32_t addr, uint32_t *data);
#define ROM_START 0x00000000
#define ROM_END 0x00004000
#define FLASH_START 0x10000000
#define FLASH_END 0x11000000 // this is maximum
#define XIP_SRAM_BASE 0x15000000
#define XIP_SRAM_END 0x15004000
#define SRAM_START 0x20000000
#define SRAM_END 0x20042000
#define SRAM_UNSTRIPED_START 0x21000000
#define SRAM_UNSTRIPED_END 0x21040000
// we require 256 (as this is the page size supported by the device)
#define LOG2_PAGE_SIZE 8u
#define PAGE_SIZE (1u << LOG2_PAGE_SIZE)
#define FLASH_SECTOR_ERASE_SIZE 4096u
enum memory_type {
rom,
flash,
sram,
sram_unstriped,
xip_sram,
invalid,
};
// inclusive of ends
static inline enum memory_type get_memory_type(uint32_t addr) {
if (addr >= ROM_START && addr <= ROM_END) {
return rom;
}
if (addr >= FLASH_START && addr <= FLASH_END) {
return flash;
}
if (addr >= SRAM_START && addr <= SRAM_END) {
return sram;
}
if (addr >= SRAM_UNSTRIPED_START && addr <= SRAM_UNSTRIPED_END) {
return sram_unstriped;
}
if (addr >= XIP_SRAM_BASE && addr <= XIP_SRAM_END) {
return xip_sram;
}
return invalid;
}
static inline bool is_transfer_aligned(uint32_t addr) {
enum memory_type t = get_memory_type(addr);
return t != invalid && !(t == flash && addr & (PAGE_SIZE-1));
}
static inline bool is_size_aligned(uint32_t addr, int size) {
#ifndef _MSC_VER
assert(__builtin_popcount(size)==1);
#endif
return !(addr & (size-1));
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -162,6 +162,20 @@ def flash_atsamd(options, binfile):
options.device, str(e))) options.device, str(e)))
sys.exit(-1) sys.exit(-1)
# Look for an rp2040 and flash it with rp2040_flash.
def rp2040_flash(devpath, binfile):
args = ["lib/rp2040_flash/rp2040_flash", binfile]
if len(devpath) > 0:
with open(devpath + "/busnum") as f:
bus = f.read().strip()
with open(devpath + "/devnum") as f:
addr = f.read().strip()
args += [bus, addr]
sys.stderr.write(" ".join(args) + '\n\n')
res = subprocess.call(args)
if res != 0:
raise error("Error running rp2040_flash")
SMOOTHIE_HELP = """ SMOOTHIE_HELP = """
Failed to flash to %s: %s Failed to flash to %s: %s
@ -240,11 +254,39 @@ def flash_stm32f4(options, binfile):
options.device, str(e), options.device)) options.device, str(e), options.device))
sys.exit(-1) sys.exit(-1)
RP2040_HELP = """
Failed to flash to %s: %s
If the device is already in bootloader mode, use 'first' as FLASH_DEVICE.
This will use rp2040_flash to flash the first available rp2040.
Alternatively, one can flash rp2040 boards like the Pico by manually
entering bootloader mode(hold bootsel button during powerup), mount the
device as a usb drive, and copy klipper.uf2 to the device.
"""
def flash_rp2040(options, binfile):
try:
if options.device.lower() == "first":
rp2040_flash("", binfile)
return
buspath, devpath = translate_serial_to_usb_path(options.device)
# We need one level up to get access to busnum/devnum files
devpath = os.path.dirname(devpath)
enter_bootloader(options.device)
wait_path(devpath)
rp2040_flash(devpath, binfile)
except error as e:
sys.stderr.write(RP2040_HELP % (options.device, str(e)))
sys.exit(-1)
MCUTYPES = { MCUTYPES = {
'sam3': flash_atsam3, 'sam4': flash_atsam4, 'samd': flash_atsamd, 'sam3': flash_atsam3, 'sam4': flash_atsam4, 'samd': flash_atsamd,
'lpc176': flash_lpc176x, 'stm32f103': flash_stm32f1, 'lpc176': flash_lpc176x, 'stm32f103': flash_stm32f1,
'stm32f4': flash_stm32f4, 'stm32f042': flash_stm32f4, 'stm32f4': flash_stm32f4, 'stm32f042': flash_stm32f4,
'stm32f072': flash_stm32f4 'stm32f072': flash_stm32f4, 'rp2040': flash_rp2040
} }

View File

@ -43,10 +43,11 @@ $(OUT)klipper.uf2: $(OUT)klipper.elf $(OUT)lib/rp2040/elf2uf2/elf2uf2
@echo " Creating uf2 file $@" @echo " Creating uf2 file $@"
$(Q)$(OUT)lib/rp2040/elf2uf2/elf2uf2 $< $@ $(Q)$(OUT)lib/rp2040/elf2uf2/elf2uf2 $< $@
lib/rp2040_flash/rp2040_flash:
@echo " Building rp2040_flash"
$(Q)make -C lib/rp2040_flash rp2040_flash
# Flash rules # Flash rules
flash: $(OUT)klipper.uf2 flash: $(OUT)klipper.uf2 lib/rp2040_flash/rp2040_flash
@echo "Error: Flashing not supported on rp2040." @echo " Flashing $< to $(FLASH_DEVICE)"
@echo "Place target board in bootloader mode (hold bootsel button" $(Q)$(PYTHON) ./scripts/flash_usb.py -t $(CONFIG_MCU) -d "$(FLASH_DEVICE)" $(OUT)klipper.uf2
@echo "during powerup), mount the device as a usb drive, and copy"
@echo "$< to the device."
@exit -1