diff --git a/lib/.gitignore b/lib/.gitignore index 5d195d36..48aa9508 100644 --- a/lib/.gitignore +++ b/lib/.gitignore @@ -3,3 +3,4 @@ bossac/bin/ bossac/obj/ hidflash/hid-flash hub-ctrl/hub-ctrl +rp2040_flash/rp2040_flash diff --git a/lib/README b/lib/README index 84c3e0d9..4a94c9f2 100644 --- a/lib/README +++ b/lib/README @@ -74,6 +74,11 @@ version 1.2.0 (bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7). It has been modified so that it can build outside of the pico sdk. See 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: https://github.com/codazoda/hub-ctrl.c/ revision 42095e522859059e8a5f4ec05c1e3def01a870a9. diff --git a/lib/rp2040/boot/picoboot.h b/lib/rp2040/boot/picoboot.h new file mode 100644 index 00000000..ddfa0aaa --- /dev/null +++ b/lib/rp2040/boot/picoboot.h @@ -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 +#include +#include + +#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 diff --git a/lib/rp2040/pico/platform.h b/lib/rp2040/pico/platform.h new file mode 100644 index 00000000..499bdf64 --- /dev/null +++ b/lib/rp2040/pico/platform.h @@ -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 + +#ifdef __unix__ + +#include + +#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 +#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 diff --git a/lib/rp2040_flash/Makefile b/lib/rp2040_flash/Makefile new file mode 100644 index 00000000..d98a72d9 --- /dev/null +++ b/lib/rp2040_flash/Makefile @@ -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) diff --git a/lib/rp2040_flash/main.c b/lib/rp2040_flash/main.c new file mode 100644 index 00000000..1c111951 --- /dev/null +++ b/lib/rp2040_flash/main.c @@ -0,0 +1,246 @@ +// Simple rp2040 picoboot based flash tool for use with Klipper +// +// Copyright (C) 2022 Lasse Dalegaard +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include +#include +#include +#include +#include +#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 [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; +} diff --git a/lib/rp2040_flash/picoboot_connection.c b/lib/rp2040_flash/picoboot_connection.c new file mode 100644 index 00000000..c0d21769 --- /dev/null +++ b/lib/rp2040_flash/picoboot_connection.c @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#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 : +// 0: 4801 ldr r0, [pc, #4] ; (8 ) +// 2: 4902 ldr r1, [pc, #8] ; (c ) +// 4: 6008 str r0, [r1, #0] +// 6: 4770 bx lr +// 00000008 : +// 8: 12345678 .word 0x12345678 +// 0000000c : +// 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 : +// 0: 4802 ldr r0, [pc, #8] ; (c ) +// 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 : +// 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 diff --git a/lib/rp2040_flash/picoboot_connection.h b/lib/rp2040_flash/picoboot_connection.h new file mode 100644 index 00000000..ebdabfc9 --- /dev/null +++ b/lib/rp2040_flash/picoboot_connection.h @@ -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 +#include +#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 diff --git a/scripts/flash_usb.py b/scripts/flash_usb.py index 04e2ba3f..581aa5c0 100755 --- a/scripts/flash_usb.py +++ b/scripts/flash_usb.py @@ -162,6 +162,20 @@ def flash_atsamd(options, binfile): options.device, str(e))) 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 = """ Failed to flash to %s: %s @@ -240,11 +254,39 @@ def flash_stm32f4(options, binfile): options.device, str(e), options.device)) 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 = { 'sam3': flash_atsam3, 'sam4': flash_atsam4, 'samd': flash_atsamd, 'lpc176': flash_lpc176x, 'stm32f103': flash_stm32f1, 'stm32f4': flash_stm32f4, 'stm32f042': flash_stm32f4, - 'stm32f072': flash_stm32f4 + 'stm32f072': flash_stm32f4, 'rp2040': flash_rp2040 } diff --git a/src/rp2040/Makefile b/src/rp2040/Makefile index b40df32d..2915b994 100644 --- a/src/rp2040/Makefile +++ b/src/rp2040/Makefile @@ -43,10 +43,11 @@ $(OUT)klipper.uf2: $(OUT)klipper.elf $(OUT)lib/rp2040/elf2uf2/elf2uf2 @echo " Creating uf2 file $@" $(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: $(OUT)klipper.uf2 - @echo "Error: Flashing not supported on rp2040." - @echo "Place target board in bootloader mode (hold bootsel button" - @echo "during powerup), mount the device as a usb drive, and copy" - @echo "$< to the device." - @exit -1 +flash: $(OUT)klipper.uf2 lib/rp2040_flash/rp2040_flash + @echo " Flashing $< to $(FLASH_DEVICE)" + $(Q)$(PYTHON) ./scripts/flash_usb.py -t $(CONFIG_MCU) -d "$(FLASH_DEVICE)" $(OUT)klipper.uf2