mirror of https://github.com/Desuuuu/klipper.git
429 lines
14 KiB
C
429 lines
14 KiB
C
/*
|
|
* 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
|