mirror of https://github.com/Desuuuu/klipper.git
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:
parent
8a3727ef74
commit
7c0559c6e6
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
Loading…
Reference in New Issue