aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/.gitignore1
-rw-r--r--lib/README5
-rw-r--r--lib/rp2040/boot/picoboot.h124
-rw-r--r--lib/rp2040/pico/platform.h139
-rw-r--r--lib/rp2040_flash/Makefile20
-rw-r--r--lib/rp2040_flash/main.c246
-rw-r--r--lib/rp2040_flash/picoboot_connection.c428
-rw-r--r--lib/rp2040_flash/picoboot_connection.h111
-rwxr-xr-xscripts/flash_usb.py44
-rw-r--r--src/rp2040/Makefile13
10 files changed, 1124 insertions, 7 deletions
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 <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
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 <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
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 <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;
+}
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 <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
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 <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
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