Initial Release

This commit is contained in:
graham sanderson 2021-01-19 22:28:45 -06:00
commit d54bf53ab9
14 changed files with 4681 additions and 0 deletions

35
CMakeLists.txt Normal file
View File

@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.12)
project(picotool)
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (NOT PICO_SDK_PATH)
message(FATAL_ERROR "PICO_SDK_PATH is not defined")
endif()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(CMAKE_CXX_STANDARD 14)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
add_subdirectory(picoboot_connection)
find_package(LIBUSB)
if (NOT LIBUSB_FOUND)
message(FATAL_ERROR "picotool cannot be built because libUSB is not found")
else()
add_subdirectory(${PICO_SDK_PATH}/src/common/pico_binary_info pico_binary_info)
add_subdirectory(${PICO_SDK_PATH}/src/common/boot_uf2 boot_uf2_headers)
add_subdirectory(${PICO_SDK_PATH}/src/common/boot_picoboot boot_picoboot_headers)
add_subdirectory(${PICO_SDK_PATH}/src/host/pico_platform pico_platform)
add_executable(picotool main.cpp)
target_include_directories(picotool PRIVATE ${LIBUSB_INCLUDE_DIR})
target_link_libraries(picotool pico_binary_info boot_uf2_headers boot_picoboot_headers pico_platform_headers picoboot_connection_cxx ${LIBUSB_LIBRARIES})
endif()

443
README.md Normal file
View File

@ -0,0 +1,443 @@
## Building
You need to set PICO_SDK_PATH in the environment, or pass it to cmake
You need to install libusb-1.0.
Linux/Mac: use your favorite package tool
Windows: download from here https://libusb.info/
If you are on Windows, set LIBUSB_ROOT environment variable to the install directory
```bash
mkdir build
cd build
cmake ..
make
```
for Windows non mingw/lsw:
```bash
mkdir build
cd build
cmake -G "NMake Makefiles" ..
nmake
```
## Overview
Picotool is a tool for inspecting RP2040 binaries, and interacting with RP2040 devices when they are in BOOTSEL mode.
Note for full documentation see #todo url
```text
$ picotool help
PICOTOOL:
Tool for interacting with a RP2040 device in BOOTSEL mode, or with a RP2040 binary
SYNOPSYS:
picotool info [-b] [-p] [-d] [-l] [-a] [--bus <bus>] [--address <addr>]
picotool info [-b] [-p] [-d] [-l] [-a] <filename> [-t <type>]
picotool load [-v] [-r] <filename> [-t <type>] [--bus <bus>] [--address <addr>]
picotool save [-p] [--bus <bus>] [--address <addr>] <filename> [-t <type>]
picotool save -a [--bus <bus>] [--address <addr>] <filename> [-t <type>]
picotool save -r <from> <to> [--bus <bus>] [--address <addr>] <filename> [-t <type>]
picotool verify [--bus <bus>] [--address <addr>] <filename> [-t <type>] [-r <from> <to>]
picotool reboot [-a] [-u] [--bus <bus>] [--address <addr>]
picotool help [<cmd>]
COMMANDS:
info Display information from the target device(s) or file.
Without any arguments, this will display basic information for all connected RP2040 devices in
BOOTSEL mode
load Load the program / memory range stored in a file onto the device.
save Save the program / memory stored in flash on the device to a file.
verify Check that the device contents match those in the file.
reboot Reboot the device
help Show general help or help for a specific command
Use "picotool help <cmd>" for more info
```
Note commands that aren't acting on files require an RP2040 device in BOOTSEL mode to be connected.
## info
So there is now _Binary Information_ support in the SDK which allows for easily storing compact information that `picotool`
can find (See Binary Info section below). The info command is for reading this information.
The information can be either read from one or more connected RP2040 devices in BOOTSEL mode, or from
a file. This file can be an ELF, a UF2 or a BIN file.
```asciidoc
$ picotool help info
INFO:
Display information from the target device(s) or file.
Without any arguments, this will display basic information for all connected RP2040 devices in USB boot
mode
SYNOPSYS:
picotool info [-b] [-p] [-d] [-l] [-a] [--bus <bus>] [--address <addr>]
picotool info [-b] [-p] [-d] [-l] [-a] <filename> [-t <type>]
OPTIONS:
Information to display
-b, --basic
Include basic information. This is the default
-p, --pins
Include pin information
-d, --device
Include device information
-l, --build
Include build attributes
-a, --all
Include all information
TARGET SELECTION:
To target one or more connected RP2040 device(s) in BOOTSEL mode (the default)
--bus <bus>
Filter devices by USB bus number
--address <addr>
Filter devices by USB device address
To target a file
<filename>
The file name
-t <type>
Specify file type (uf2 | elf | bin) explicitly, ignoring file extension
```
e.g.
```text
$ picotool info
Program Information
name: hello_world
features: stdout to UART
```
```text
$ picotool info -a
Program Information
name: hello_world
features: stdout to UART
binary start: 0x10000000
binary end: 0x1000606c
Fixed Pin Information
20: UART1 TX
21: UART1 RX
Build Information
build date: Dec 31 2020
build attributes: Debug build
Device Information
flash size: 2048K
ROM version: 2
```
```text
$ picotool info -bp
Program Information
name: hello_world
features: stdout to UART
Fixed Pin Information
20: UART1 TX
21: UART1 RX
```
```text
$ picotool info -a lcd_1602_i2c.uf2
File lcd_1602_i2c.uf2:
Program Information
name: lcd_1602_i2c
web site: https://github.com/raspberrypi/pico-examples/tree/HEAD/i2c/lcd_1602_i2c
binary start: 0x10000000
binary end: 0x10003c1c
Fixed Pin Information
4: I2C0 SDA
5: I2C0 SCL
Build Information
build date: Dec 31 2020
```
## save
Save allows you to save a range of memory or a program or the whole of flash from the device to a BIN file or a UF2 file
```text
$ picotool help save
SAVE:
Save the program / memory stored in flash on the device to a file.
SYNOPSYS:
picotool save [-p] [--bus <bus>] [--address <addr>] <filename> [-t <type>]
picotool save -a [--bus <bus>] [--address <addr>] <filename> [-t <type>]
picotool save -r <from> <to> [--bus <bus>] [--address <addr>] <filename> [-t <type>]
OPTIONS:
Selection of data to save
-p, --program
Save the installed program only. This is the default
-a, --all
Save all of flash memory
-r, --range
Save a range of memory; note that the range is expanded to 256 byte boundaries
<from>
The lower address bound in hex
<to>
The upper address bound in hex
Source device selection
--bus <bus>
Filter devices by USB bus number
--address <addr>
Filter devices by USB device address
File to save to
<filename>
The file name
-t <type>
Specify file type (uf2 | elf | bin) explicitly, ignoring file extension
```
e.g.
```text
$ picotool info
Program Information
name: lcd_1602_i2c
web site: https://github.com/raspberrypi/pico-examples/tree/HEAD/i2c/lcd_1602_i2c
```
```text
$ picotool save spoon.uf2
Saving file: [==============================] 100%
Wrote 51200 bytes to spoon.uf2
```
```text
$ picotool info spoon.uf2
File spoon.uf2:
Program Information
name: lcd_1602_i2c
web site: https://github.com/raspberrypi/pico-examples/tree/HEAD/i2c/lcd_1602_i2c
```
## Binary Information
Binary information is machine locatable and generally machine consumable. I say generally because anyone can
include any information, and we can tell it from ours, but it is up to them whether they make their data self describing.
Note that we will certainly add more binary info over time, but I'd like to get a minimum core set included
in most binaries from launch!!
### Basic Information
This information is really handy when you pick up a Pico and don't know what is on it!
Basic information includes
- program name
- program description
- program version string
- program build date
- program url
- program end address
- program features - this is a list built from individual strings in the binary, that can be displayed (e.g. we will have one for UART stdio and one for USB stdio) in the SDK
- build attributes - this is a similar list of strings, for things pertaining to the binary itself (e.g. Debug Build)
Note it is my intention that things like MicroPython would include features for parts of the language/libraries they include.
This might not be as a feature string per se, but could be another aggregating list (features/attributes are well known)
but we can add another piece of binary info to name a list attribute that aggregates strings with a particular tag, so you
could trivially add "MicroPython libraries:" etc to the `picotool` output without changing the tool itself.
### Pins
This is certainly handy when you have an execute called 'hello_world.elf' but you forgot what board it is built for...
Static (fixed) pin assignments can be recorded in the binary in very compact form:
```text
$ picotool info --pins sprite_demo.elf
File sprite_demo.elf:
Fixed Pin Information
0-4: Red 0-4
6-10: Green 0-4
11-15: Blue 0-4
16: HSync
17: VSync
18: Display Enable
19: Pixel Clock
20: UART1 TX
21: UART1 RX
```
### Including Binary information
Binary information is declared in the program by macros (vile warped macros); for the previous example:
```text
$ picotool info --pins sprite_demo.elf
File sprite_demo.elf:
Fixed Pin Information
0-4: Red 0-4
6-10: Green 0-4
11-15: Blue 0-4
16: HSync
17: VSync
18: Display Enable
19: Pixel Clock
20: UART1 TX
21: UART1 RX
```
... there is one line in the `setup_default_uart` function:
```c
bi_decl_if_func_used(bi_2pins_with_func(PICO_DEFAULT_UART_RX_PIN, PICO_DEFAULT_UART_TX_PIN, GPIO_FUNC_UART));
```
The two pin numbers, and the function UART are stored, then decoded to their actual function names (UART1 TX etc) by picotool.
The `bi_decl_if_func_used` makes sure the binary information is only included if the containing function is called.
Equally, the video code contains a few lines like this:
```c
bi_decl_if_func_used(bi_pin_mask_with_name(0x1f << (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_DPI_PIXEL_RSHIFT), "Red 0-4"));
```
### Details
Things are designed to waste as little space as possible, but you can turn everything of with preprocessor var `PICO_NO_BINARY_INFO=1`. Additionally
any SDK code that inserts binary info cane be separately excluded by its own preprocesor var.
You need
```c
#include "pico/binary_info.h"
```
Basically you either use `bi_decl(bi_blah(...))` for unconditional inclusion of the binary info blah, or
`bi_decl_if_func_used(bi_blah(...))` for binary information that may be stripped if the enclosing function
is not included in the binary by the linker (think `--gc-sections`)
There are a bunch of bi_ macros in the headers
```c
#define bi_binary_end(end) ...
#define bi_program_name(name) ...
#define bi_program_description(description) ...
#define bi_program_version_string(version_string) ...
#define bi_program_build_date_string(date_string) ...
#define bi_program_url(url) ...
#define bi_program_feature(feature) ...
#define bi_program_build_attribute(attr) ...
#define bi_1pin_with_func(p0, func) ...
#define bi_2pins_with_func(p0, p1, func) ...
#define bi_3pins_with_func(p0, p1, p2, func) ...
#define bi_4pins_with_func(p0, p1, p2, p3, func) ...
#define bi_5pins_with_func(p0, p1, p2, p3, p4, func) ...
#define bi_pin_range_with_func(plo, phi, func) ...
#define bi_pin_mask_with_name(pmask, label) ...
#define bi_pin_mask_with_names(pmask, label) ...
#define bi_1pin_with_name(p0, name) ...
#define bi_2pins_with_names(p0, name0, p1, name1) ...
#define bi_3pins_with_names(p0, name0, p1, name1, p2, name2) ...
#define bi_4pins_with_names(p0, name0, p1, name1, p2, name2, p3, name3) ...
```
which make use of underlying macros, e.g.
```c
#define bi_program_url(url) bi_string(BINARY_INFO_TAG_RASPBERRY_PI, BINARY_INFO_ID_RP_PROGRAM_URL, url)
```
NOTE: It is easy to forget to enclose these in `bi_decl` etc., so an effort has been made (at the expense of a lot of kittens)
to make the build fail with a _somewhat_ helpful error message if you do so.
For example, trying to compile
```c
bi_1pin_with_name(0, "Toaster activator");
```
gives
```
/home/graham/dev/mu/pico_sdk/src/common/pico_binary_info/include/pico/binary_info/code.h:17:55: error: '_error_bi_is_missing_enclosing_decl_261' undeclared here (not in a function)
17 | #define __bi_enclosure_check_lineno_var_name __CONCAT(_error_bi_is_missing_enclosing_decl_,__LINE__)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... more macro call stack of doom
```
## Setting common fields from Cmake
You can use
```cmake
pico_set_program_name(foo "not foo") # as "foo" would be the default
pico_set_program_description(foo "this is a foo")
pico_set_prorgam_version_string(foo "0.00001a")
pico_set_program_url(foo "www.plinth.com/foo")
```
Note all of these are passed as command line arguments to the compilation, so if you plan to use
quotes, newlines etc you may have better luck defining vi bi_decl in the code.
## Additional binary information/picotool features
### SDK version
Should add this; git revision in general is hard since the user may not have the SDK checked out from git, so we'll have to stick
a version number in a header
### Block devices
MicroPython and CircuitPython, eventually the SDK and others may support one or more storage devices in flash. We already
have macros to define these although picotool doesn't do wanything with them yet... but backup/restore/file copy and even fuse mount
in the future might be interesting.
I suggest we tag these now...
This is what I have right now off the top of my head (at the time)
```c
#define bi_block_device(_tag, _name, _offset, _size, _extra, _flags)
```
with the data going into
```c
typedef struct __packed _binary_info_block_device {
struct _binary_info_core core;
bi_ptr_of(const char) name; // optional static name (independent of what is formatted)
uint32_t offset;
uint32_t size;
bi_ptr_of(binary_info_t) extra; // additional info
uint16_t flags;
} binary_info_block_device_t;
```
and
```c
enum {
BINARY_INFO_BLOCK_DEV_FLAG_READ = 1 << 0, // if not readable, then it is basically hidden, but tools may choose to avoid overwriting it
BINARY_INFO_BLOCK_DEV_FLAG_WRITE = 1 << 1,
BINARY_INFO_BLOCK_DEV_FLAG_REFORMAT = 1 << 2, // may be reformatted..
BINARY_INFO_BLOCK_DEV_FLAG_PT_UNKNOWN = 0 << 4, // unknown free to look
BINARY_INFO_BLOCK_DEV_FLAG_PT_MBR = 1 << 4, // expect MBR
BINARY_INFO_BLOCK_DEV_FLAG_PT_GPT = 2 << 4, // expect GPT
BINARY_INFO_BLOCK_DEV_FLAG_PT_NONE = 3 << 4, // no partition table
};
```
### USB device descriptors
Seems like tagging these might be nice (we just need to store the pointer to it assuming - as is often the case -
the descriptor is just a linear chunk of memory) ... I assume there is a tool out there to prettyify such a think if picotool dumps the descriptor
in binary..
### Issues
If you ctrl+c out of the middle of a long operation, then libusb seems to get a bit confused, which means we aren't able
to unlock our lockout of USB MSD writes (we have turned them off so the user doesn't step on their own toes). Simply running
`picotool info` again will unlock it properly the nex time (or you can reboot the device).

908
cli.h Normal file
View File

@ -0,0 +1,908 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _CLI_H
#define _CLI_H
#include <algorithm>
#include <exception>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <cassert>
#include <iostream>
#include <numeric>
#include <sstream>
/**
* Note this is a hastily hacked together command line parser.
*
* I like the syntax of clipp; but it seemed really buggy, and writing something less functional
* with similar syntax seemed like the quickest way to go.
*
* Ironically this is probably just as buggy off the happy path as clipp appeared to be, but
* in this case the happy path is ours!
*/
namespace cli {
typedef std::string string;
template<typename T> using vector = std::vector<T>;
template<typename A, typename B> using map = std::map<A, B>;
template<typename A, typename B> using pair = std::pair<A, B>;
template<typename T> using shared_ptr = std::shared_ptr<T>;
auto join = [](const vector<string> &range, const string &separator) {
if (range.empty()) return string();
return accumulate(
next(begin(range)), // there is at least 1 element, so OK.
end(range),
range[0], // the initial value
[&separator](auto result, const auto &value) {
return result + separator + value;
});
};
struct parse_error : public std::exception {
explicit parse_error(string what) : _what(std::move(what)) {}
const char *what() const noexcept override {
return _what.c_str();
}
private:
string _what;
};
struct group;
template<typename T>
struct matchable_derived;
template <typename K, typename V> struct map_and_order {
map<K,V> _map;
vector<K> _order;
V& operator[](const K& key) {
auto i = _map.find(key);
if (i == _map.end()) {
_order.push_back(key);
}
return _map[key];
}
vector<K> ordered_keys() {
return _order;
}
};
struct option_map {
typedef map_and_order<string, map_and_order<string, vector<pair<string, string>>>> container;
void add(const string& major_group, const string& minor_group, const string& option, const string& description) {
auto &v = contents[major_group][minor_group];
// we don't want to repeated the same option
if (std::find_if(v.begin(), v.end(), [&](const auto &x) { return x.first == option; }) == v.end()) {
v.emplace_back(option, description);
}
}
container contents;
};
struct matchable;
enum struct match_type {
not_yet,
match,
error,
no_match,
};
struct opaque_settings {
virtual shared_ptr<opaque_settings> copy() = 0;
virtual void save_into() = 0;
virtual void apply_from() = 0;
};
struct settings_holder {
explicit settings_holder(shared_ptr<opaque_settings> settings) : settings(settings) {}
settings_holder(const settings_holder &other) {
settings = other.settings->copy();
}
void save_into() {
settings->save_into();
}
void apply_from() {
settings->apply_from();
}
shared_ptr<opaque_settings> settings;
};
struct match_state {
vector<string> remaining_args;
string error_message;
int match_count = 0;
int error_count = 0;
// if we are an error for something mising; we should rather report on an up next
// unsupported option than our error message
bool prefer_unknown_option_message = false;
std::map<const matchable *, int> matchable_counts;
settings_holder settings;
match_state(const settings_holder& settings) : settings(settings) {}
void apply_settings_from() {
settings.apply_from();
}
void save_settings_into() {
settings.save_into();
}
match_type match_value(const matchable *matchable, std::function<bool(const string&)> filter);
match_type check_min_max(const matchable *matchable);
int get_match_count(const std::shared_ptr<matchable>& element) {
return matchable_counts[element.get()];
}
match_type update_stats(match_type type, const matchable *matchable) {
assert(type != match_type::not_yet);
if (type == match_type::match) {
match_count++;
matchable_counts[matchable]++;
} else if (type == match_type::error) {
error_count++;
matchable_counts[matchable]++;
}
return type;
}
match_type match_if_equal(const matchable *matchable, const string& s);
};
struct matcher {
};
struct matchable {
matchable() = default;
explicit matchable(string name) : _name(std::move(name)) {}
std::function<string(string)> action = [](const string&) { return ""; };
std::function<string()> missing;
virtual match_type match(match_state& m) const { return match_type::no_match; }
string name() const {
return _name;
}
virtual std::vector<string> synopsys() const {
return {_name};
}
virtual bool is_optional() const {
return !_min;
}
bool doc_non_optional() const {
return _doc_non_optional;
}
bool force_expand_help() const {
return _force_expand_help;
}
string doc() const {
return _doc;
}
virtual bool get_option_help(string major_group, string minor_group, option_map &options) const {
return false;
}
int min() const {
return _min;
}
int max() const {
return _max;
}
protected:
string _name;
string _doc;
int _min = 1;
int _max = 1;
bool _doc_non_optional = false;
bool _force_expand_help = false;
};
template<typename D>
struct matchable_derived : public matchable {
matchable_derived() = default;
explicit matchable_derived(string name) : matchable(std::move(name)) {}
D &on_action(std::function<string(const string&)> action) {
this->action = action;
return *static_cast<D *>(this);
}
D &if_missing(std::function<string()> missing) {
this->missing = missing;
return *static_cast<D *>(this);
}
D &operator%(const string& doc) {
_doc = doc;
return *static_cast<D *>(this);
}
D &required() {
_min = 1;
_max = std::max(_min, _max);
return *static_cast<D *>(this);
}
D &repeatable() {
_max = std::numeric_limits<int>::max();
return *static_cast<D *>(this);
}
D &min(int v) {
_min = v;
return *static_cast<D *>(this);
}
D &doc_non_optional(bool v) {
_doc_non_optional = v;
return *static_cast<D *>(this);
}
D &force_expand_help(bool v) {
_force_expand_help = v;
return *static_cast<D *>(this);
}
D &max(int v) {
_max = v;
return *static_cast<D *>(this);
}
std::shared_ptr<matchable> to_ptr() const {
return std::shared_ptr<matchable>(new D(*static_cast<const D *>(this)));
}
template<typename T>
group operator&(const matchable_derived<T> &m);
template<typename T>
group operator|(const matchable_derived<T> &m);
template<typename T>
group operator+(const matchable_derived<T> &m);
};
template<typename D>
struct value_base : public matchable_derived<D> {
std::function<bool(const string&)> exclusion_filter = [](const string &x){return false;};
explicit value_base(string name) : matchable_derived<D>(std::move(name)) {
this->_min = 1;
this->_max = 1;
}
vector<string> synopsys() const override {
string s = string("<") + this->_name + ">";
return {s};
}
bool get_option_help(string major_group, string minor_group, option_map &options) const override {
if (this->doc().empty()) {
return false;
}
options.add(major_group, minor_group, string("<") + this->_name + ">", this->doc());
return true;
}
match_type match(match_state& ms) const override {
match_type rc = ms.check_min_max(this);
if (rc == match_type::not_yet) {
rc = ms.match_value(this, exclusion_filter);
}
return rc;
}
D &with_exclusion_filter(std::function<bool(const string&)> exclusion_filter) {
this->exclusion_filter = exclusion_filter;
return *static_cast<D *>(this);
}
};
struct option : public matchable_derived<option> {
explicit option(char short_opt) : option(short_opt, "") {}
explicit option(string _long_opt) : option(0, std::move(_long_opt)) {}
option(char _short_opt, string _long_opt) {
_min = 0;
short_opt = _short_opt ? "-" + string(1, _short_opt) : "";
long_opt = std::move(_long_opt);
_name = short_opt.empty() ? long_opt : short_opt;
}
bool get_option_help(string major_group, string minor_group, option_map &options) const override {
if (doc().empty()) return false;
string label = short_opt.empty() ? "" : _name;
if (!long_opt.empty()) {
if (!label.empty()) label += ", ";
label += long_opt;
}
options.add(major_group, minor_group, label, doc());
return true;
}
template<typename T>
option &set(T &t) {
// note we cannot capture "this"
on_action([&t](const string& value) {
t = true;
return "";
});
return *this;
}
template<typename T>
option &clear(T &t) {
// note we cannot capture "this"
on_action([&t](const string& value) {
t = false;
return "";
});
return *this;
}
match_type match(match_state &ms) const override {
match_type rc = ms.match_if_equal(this, short_opt);
if (rc == match_type::no_match) {
rc = ms.match_if_equal(this, long_opt);
}
return rc;
}
private:
string short_opt;
string long_opt;
};
struct value : public value_base<value> {
explicit value(string name) : value_base(std::move(name)) {}
template<typename T>
value &set(T &t) {
on_action([&](const string& value) {
t = value;
return "";
});
return *this;
}
};
struct integer : public value_base<integer> {
explicit integer(string name) : value_base(std::move(name)) {}
template<typename T>
integer &set(T &t) {
int min = _min_value;
int max = _max_value;
string nm = "<" + name() + ">";
// note we cannot capture "this"
on_action([&t, min, max, nm](const string& value) {
size_t pos = 0;
long lvalue = std::numeric_limits<long>::max();
try {
lvalue = std::stol(value, &pos);
if (pos != value.length()) {
return "Garbage after integer value: " + value.substr(pos);
}
} catch (std::invalid_argument&) {
return value + " is not a valid integer";
} catch (std::out_of_range&) {
}
if (lvalue != (int)lvalue) {
return value + " is too big";
}
t = (int)lvalue;
if (t < min) {
return nm + " must be >= " + std::to_string(min);
}
if (t > max) {
return nm + " must be <= " + std::to_string(max);
}
return string("");
});
return *this;
}
integer& min_value(int v) {
_min_value = v;
return *this;
}
integer& max_value(int v) {
_max_value = v;
return *this;
}
int _min_value = 0;
int _max_value = std::numeric_limits<int>::max();
};
struct hex : public value_base<hex> {
explicit hex(string name) : value_base(std::move(name)) {}
template<typename T>
hex &set(T &t) {
unsigned int min = _min_value;
unsigned int max = _max_value;
string nm = "<" + name() + ">";
// note we cannot capture "this"
on_action([&t, min, max, nm](string value) {
auto ovalue = value;
if (value.find("0x") == 0) value = value.substr(2);
size_t pos = 0;
long lvalue = std::numeric_limits<long>::max();
try {
lvalue = std::stoul(value, &pos, 16);
if (pos != value.length()) {
return "Garbage after hex value: " + value.substr(pos);
}
} catch (std::invalid_argument&) {
return ovalue + " is not a valid hex value";
} catch (std::out_of_range&) {
}
if (lvalue != (unsigned int)lvalue) {
return value + " is not a valid 32 bit value";
}
t = (unsigned int)lvalue;
if (t < min) {
std::stringstream ss;
ss << nm << " must be >= 0x" << std::hex << std::to_string(min);
return ss.str();
}
if (t > max) {
std::stringstream ss;
ss << nm << " must be M= 0x" << std::hex << std::to_string(min);
return ss.str();
}
return string("");
});
return *this;
}
hex& min_value(unsigned int v) {
_min_value = v;
return *this;
}
hex& max_value(unsigned int v) {
_max_value = v;
return *this;
}
unsigned int _min_value = 0;
unsigned int _max_value = std::numeric_limits<unsigned int>::max();
};
struct group : public matchable_derived<group> {
enum group_type {
sequence,
set,
exclusive,
};
public:
group() : type(set) {}
template<typename T>
explicit group(const T &t) : type(set), elements{t.to_ptr()} {}
template<class Matchable, class... Matchables>
group(Matchable m, Matchable ms...) : elements{m, ms}, type(set) {}
group &set_type(group_type t) {
type = t;
return *this;
}
group &major_group(string g) {
_major_group = std::move(g);
return *this;
}
static string decorate(const matchable &e, string s) {
if (e.is_optional() && !e.doc_non_optional()) {
return string("[") + s + "]";
} else {
return s;
}
}
vector<string> synopsys() const override {
vector<string> rc;
switch (type) {
case set:
case sequence: {
std::vector<std::vector<string>> tmp{{}};
for (auto &x : elements) {
auto xs = x->synopsys();
if (xs.size() == 1) {
for (auto &s : tmp) {
s.push_back(decorate(*x, xs[0]));
}
} else {
auto save = tmp;
tmp.clear();
for (auto &v : save) {
for (auto &s : xs) {
auto nv = v;
nv.push_back(decorate(*x, s));
tmp.push_back(nv);
}
}
}
}
for (const auto &v : tmp) {
rc.push_back(join(v, " "));
}
break;
}
case exclusive:
for (auto &x : elements) {
auto xs = x->synopsys();
std::transform(xs.begin(), xs.end(), std::back_inserter(rc), [&](const auto &s) {
return decorate(*x, s);
});
}
break;
default:
assert(false);
break;
}
return rc;
}
group operator|(const group &g) {
return matchable_derived::operator|(g);
}
group operator&(const group &g) {
return matchable_derived::operator&(g);
}
group operator+(const group &g) {
return matchable_derived::operator+(g);
}
bool no_match_beats_error() const {
return _no_match_beats_error;
}
group &no_match_beats_error(bool v) {
_no_match_beats_error = v;
return *this;
}
template<typename T>
group operator&(const matchable_derived<T> &m) {
if (type == sequence) {
elements.push_back(m.to_ptr());
return *this;
}
return matchable_derived::operator&(m);
}
template<typename T>
group operator|(const matchable_derived<T> &m) {
if (type == exclusive) {
elements.push_back(m.to_ptr());
return *this;
}
return matchable_derived::operator|(m);
}
template<typename T>
group operator+(const matchable_derived<T> &m) {
if (type == set) {
elements.push_back(m.to_ptr());
return *this;
}
return matchable_derived::operator+(m);
}
bool get_option_help(string major_group, string minor_group, option_map &options) const override {
// todo beware.. this check is necessary as is, but I'm not sure what removing it breaks in terms of formatting :-(
if (is_optional() && !this->_doc_non_optional && !this->_force_expand_help) {
options.add(major_group, minor_group, synopsys()[0], doc());
return true;
}
if (!doc().empty()) {
minor_group = doc();
}
if (!_major_group.empty()) {
major_group = _major_group;
}
for (const auto &e : elements) {
e->get_option_help(major_group, minor_group, options);
}
return true;
}
match_type match(match_state& ms) const override {
match_type rc = ms.check_min_max(this);
if (rc == match_type::no_match) return rc;
assert(rc == match_type::not_yet);
switch(type) {
case sequence:
rc = match_sequence(ms);
break;
case set:
rc = match_set(ms);
break;
default:
rc = match_exclusive(ms);
break;
}
return ms.update_stats(rc, this);
}
match_type match_sequence(match_state& ms) const {
match_type rc = match_type::no_match;
for(const auto& e : elements) {
rc = e->match(ms);
assert(rc != match_type::not_yet);
if (rc != match_type::match) {
break;
}
}
return rc;
}
match_type match_set(match_state& ms) const {
// because of repeatability, we keep matching until there is nothing left to match
// vector<match_type> types(elements.size(), match_type::not_yet);
bool had_any_matches = false;
bool final_pass = false;
do {
bool matches_this_time = false;
bool errors_this_time = false;
bool not_min_this_time = false;
for (size_t i=0;i<elements.size();i++) {
// if (types[i] == match_type::not_yet) {
auto ms_prime = ms;
ms_prime.apply_settings_from();
match_type t = elements[i]->match(ms_prime);
assert(t != match_type::not_yet);
if (t == match_type::match) {
// we got a match, so record in ms and try again
// (if the matchable isn't repeatable it will no match next time)
// types[i] = match_type::not_yet;
ms_prime.save_settings_into();
ms = ms_prime;
had_any_matches = true;
matches_this_time = true;
} else if (t == match_type::error) {
if (final_pass) {
ms_prime.save_settings_into();
ms = ms_prime;
return t;
}
errors_this_time = true;
} else {
if (ms.get_match_count(elements[i]) < elements[i]->min()) {
if (final_pass) {
ms.error_message = elements[i]->missing ? elements[i]->missing() : "missing required argument";
return match_type::error;
}
not_min_this_time = true;
}
}
// }
}
if (final_pass) break;
if (!matches_this_time) {
if (errors_this_time || not_min_this_time) {
final_pass = true;
} else {
break;
}
}
} while (true);
return had_any_matches ? match_type::match : match_type::no_match;
}
match_type match_exclusive(match_state& ms) const {
vector<match_state> matches(elements.size(), ms);
vector<match_type> types(elements.size(), match_type::no_match);
int elements_with_errors = 0;
int elements_with_no_match = 0;
int error_at = -1;
int error_match_count = -1;
for (size_t i=0;i<elements.size();i++) {
match_type t;
matches[i].apply_settings_from();
do {
t = elements[i]->match(matches[i]);
assert(t != match_type::not_yet);
if (t != match_type::no_match) {
types[i] = t;
}
} while (t == match_type::match);
matches[i].save_settings_into();
if (types[i] == match_type::match) {
ms = matches[i];
return match_type::match;
} else if (types[i] == match_type::error) {
if (matches[i].match_count > error_match_count) {
error_match_count = matches[i].match_count;
error_at = i;
}
elements_with_errors++;
} else if (types[i] == match_type::no_match) {
elements_with_no_match++;
}
}
if (elements_with_no_match && (!elements_with_errors || no_match_beats_error())) {
return match_type::no_match;
}
if (elements_with_errors) {
ms = matches[error_at];
ms.apply_settings_from(); // todo perhaps want to apply the previous settings instead?
return match_type::error;
} else {
// back out any modified settings
ms.apply_settings_from();
return match_type::no_match;
}
}
private:
string _major_group;
vector<std::shared_ptr<matchable>> elements;
group_type type;
bool _no_match_beats_error = true;
};
template<typename D>
template<typename T>
group matchable_derived<D>::operator|(const matchable_derived<T> &m) {
return group{this->to_ptr(), m.to_ptr()}.set_type(group::exclusive);
}
template<typename D>
template<typename T>
group matchable_derived<D>::operator&(const matchable_derived<T> &m) {
int _min = matchable::min();
int _max = matchable::max();
min(1);
max(1);
return group{this->to_ptr(), m.to_ptr()}.set_type(group::sequence).min(_min).max(_max);
}
template<typename D>
template<typename T>
group matchable_derived<D>::operator+(const matchable_derived<T> &m) {
return group{this->to_ptr(), m.to_ptr()};
}
vector<string> make_args(int argc, char **argv) {
vector<string> args;
for (int i = 1; i < argc; i++) {
string arg(argv[i]);
if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-') {
// expand collapsed args (unconditionally for now)
for (auto c = arg.begin() + 1; c != arg.end(); c++) {
args.push_back("-" + string(1, *c));
}
} else {
args.push_back(arg);
}
}
return args;
}
match_type match_state::check_min_max(const matchable *matchable) {
if (matchable_counts[matchable] < matchable->min()) {
return match_type::not_yet;
}
if (matchable_counts[matchable] >= matchable->max()) {
return match_type::no_match;
}
return match_type::not_yet;
}
match_type match_state::match_if_equal(const matchable *matchable, const string& s) {
if (remaining_args.empty()) return match_type::no_match;
if (remaining_args[0] == s) {
auto message = matchable->action(s);
assert(message.empty());
remaining_args.erase(remaining_args.begin());
return update_stats(match_type::match, matchable);
}
return match_type::no_match;
}
match_type match_state::match_value(const matchable *matchable, std::function<bool(const string&)> exclusion_filter) {
// treat an excluded value as missing
bool empty = remaining_args.empty() || exclusion_filter(remaining_args[0]);
if (empty) {
if (matchable_counts[matchable] < matchable->min()) {
prefer_unknown_option_message = !remaining_args.empty();
error_message = matchable->missing ? matchable->missing() : "missing <" + matchable->name() +">";
return update_stats(match_type::error, matchable);
}
return match_type::no_match;
}
auto message = matchable->action(remaining_args[0]);
if (!message.empty()) {
error_message = message;
return update_stats(match_type::error, matchable);
}
remaining_args.erase(remaining_args.begin());
return update_stats(match_type::match, matchable);
}
template<typename S> struct typed_settings : public opaque_settings {
explicit typed_settings(S& settings) : root_settings(settings), settings(settings) {
}
shared_ptr<cli::opaque_settings> copy() override {
auto c = std::make_shared<typed_settings<S>>(*this);
c->settings = settings;
return c;
}
void save_into() override {
settings = root_settings;
}
void apply_from() override {
root_settings = settings;
}
S& root_settings;
S settings;
};
template<typename S> void match(S& settings, const group& g, std::vector<string> args) {
auto holder = settings_holder(std::make_shared<typed_settings<S>>(settings));
match_state ms(holder);
ms.remaining_args = std::move(args);
auto t = g.match(ms);
if (!ms.prefer_unknown_option_message) {
if (t == match_type::error) {
throw parse_error(ms.error_message);
}
}
if (!ms.remaining_args.empty()) {
if (ms.remaining_args[0].find('-')==0) {
throw parse_error("unexpected option: "+ms.remaining_args[0]);
} else {
throw parse_error("unexpected argument: "+ms.remaining_args[0]);
}
}
if (ms.prefer_unknown_option_message) {
if (t == match_type::error) {
throw parse_error(ms.error_message);
}
}
}
}
#endif

20
clipp/LICENSE Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 André Müller; foss@andremueller-online.de
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

396
clipp/clipp.h Normal file
View File

@ -0,0 +1,396 @@
/*****************************************************************************
* ___ _ _ ___ ___
* | _|| | | | | _ \ _ \ CLIPP - command line interfaces for modern C++
* | |_ | |_ | | | _/ _/ version 1.2.3
* |___||___||_| |_| |_| https://github.com/muellan/clipp
*
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
* Copyright (c) 2017-2018 André Müller <foss@andremueller-online.de>
*
* ---------------------------------------------------------------------------
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*****************************************************************************/
/**
* NOTE: this is just the formatting_ostream class from the original clipp header
*/
#ifndef AM_CLIPP_H__
#define AM_CLIPP_H__
#include <cstring>
#include <string>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <memory>
#include <vector>
#include <limits>
#include <stack>
#include <algorithm>
#include <sstream>
#include <utility>
#include <iterator>
#include <functional>
/*************************************************************************//**
*
* @brief primary namespace
*
*****************************************************************************/
namespace clipp {
/*****************************************************************************
*
* basic constants and datatype definitions
*
*****************************************************************************/
using arg_index = int;
using arg_string = std::string;
using doc_string = std::string;
using arg_list = std::vector<arg_string>;
/*************************************************************************//**
*
* @brief stream decorator
* that applies formatting like line wrapping
*
*****************************************************************************/
template<class OStream = std::ostream, class StringT = doc_string>
class formatting_ostream
{
public:
using string_type = StringT;
using size_type = typename string_type::size_type;
using char_type = typename string_type::value_type;
formatting_ostream(OStream& os):
os_(os),
curCol_{0}, firstCol_{0}, lastCol_{100},
hangingIndent_{0}, paragraphSpacing_{0}, paragraphSpacingThreshold_{2},
curBlankLines_{0}, curParagraphLines_{1},
totalNonBlankLines_{0},
ignoreInputNls_{false}
{}
//---------------------------------------------------------------
const OStream& base() const noexcept { return os_; }
OStream& base() noexcept { return os_; }
bool good() const { return os_.good(); }
//---------------------------------------------------------------
/** @brief determines the leftmost border of the text body */
formatting_ostream& first_column(int c) {
firstCol_ = c < 0 ? 0 : c;
return *this;
}
int first_column() const noexcept { return firstCol_; }
/** @brief determines the rightmost border of the text body */
formatting_ostream& last_column(int c) {
lastCol_ = c < 0 ? 0 : c;
return *this;
}
int last_column() const noexcept { return lastCol_; }
int text_width() const noexcept {
return lastCol_ - firstCol_;
}
/** @brief additional indentation for the 2nd, 3rd, ... line of
a paragraph (sequence of soft-wrapped lines) */
formatting_ostream& hanging_indent(int amount) {
hangingIndent_ = amount;
return *this;
}
int hanging_indent() const noexcept {
return hangingIndent_;
}
/** @brief amount of blank lines between paragraphs */
formatting_ostream& paragraph_spacing(int lines) {
paragraphSpacing_ = lines;
return *this;
}
int paragraph_spacing() const noexcept {
return paragraphSpacing_;
}
/** @brief insert paragraph spacing
if paragraph is at least 'lines' lines long */
formatting_ostream& min_paragraph_lines_for_spacing(int lines) {
paragraphSpacingThreshold_ = lines;
return *this;
}
int min_paragraph_lines_for_spacing() const noexcept {
return paragraphSpacingThreshold_;
}
/** @brief if set to true, newline characters will be ignored */
formatting_ostream& ignore_newline_chars(bool yes) {
ignoreInputNls_ = yes;
return *this;
}
bool ignore_newline_chars() const noexcept {
return ignoreInputNls_;
}
//---------------------------------------------------------------
/* @brief insert 'n' spaces */
void write_spaces(int n) {
if(n < 1) return;
os_ << string_type(size_type(n), ' ');
curCol_ += n;
}
/* @brief go to new line, but continue current paragraph */
void wrap_soft(int times = 1) {
if(times < 1) return;
if(times > 1) {
os_ << string_type(size_type(times), '\n');
} else {
os_ << '\n';
}
curCol_ = 0;
++curParagraphLines_;
}
/* @brief go to new line, and start a new paragraph */
void wrap_hard(int times = 1) {
if(times < 1) return;
if(paragraph_spacing() > 0 &&
paragraph_lines() >= min_paragraph_lines_for_spacing())
{
times = paragraph_spacing() + 1;
}
if(times > 1) {
os_ << string_type(size_type(times), '\n');
curBlankLines_ += times - 1;
} else {
os_ << '\n';
}
if(at_begin_of_line()) {
++curBlankLines_;
}
curCol_ = 0;
curParagraphLines_ = 1;
}
//---------------------------------------------------------------
bool at_begin_of_line() const noexcept {
return curCol_ <= current_line_begin();
}
int current_line_begin() const noexcept {
return in_hanging_part_of_paragraph()
? firstCol_ + hangingIndent_
: firstCol_;
}
int current_column() const noexcept {
return curCol_;
}
int total_non_blank_lines() const noexcept {
return totalNonBlankLines_;
}
int paragraph_lines() const noexcept {
return curParagraphLines_;
}
int blank_lines_before_paragraph() const noexcept {
return curBlankLines_;
}
//---------------------------------------------------------------
template<class T>
friend formatting_ostream&
operator << (formatting_ostream& os, const T& x) {
os.write(x);
return os;
}
void flush() {
os_.flush();
}
private:
bool in_hanging_part_of_paragraph() const noexcept {
return hanging_indent() > 0 && paragraph_lines() > 1;
}
bool current_line_empty() const noexcept {
return curCol_ < 1;
}
bool left_of_text_area() const noexcept {
return curCol_ < current_line_begin();
}
bool right_of_text_area() const noexcept {
return curCol_ > lastCol_;
}
int columns_left_in_line() const noexcept {
return lastCol_ - std::max(current_line_begin(), curCol_);
}
void fix_indent() {
if(left_of_text_area()) {
const auto fst = current_line_begin();
write_spaces(fst - curCol_);
curCol_ = fst;
}
}
template<class Iter>
bool only_whitespace(Iter first, Iter last) const {
return last == std::find_if_not(first, last,
[](char_type c) { return std::isspace(c); });
}
/** @brief write any object */
template<class T>
void write(const T& x) {
std::ostringstream ss;
ss << x;
write(std::move(ss).str());
}
/** @brief write a stringstream */
void write(const std::ostringstream& s) {
write(s.str());
}
/** @brief write a string */
void write(const string_type& s) {
write(s.begin(), s.end());
}
/** @brief partition output into lines */
template<class Iter>
void write(Iter first, Iter last)
{
if(first == last) return;
if(*first == '\n') {
if(!ignore_newline_chars()) wrap_hard();
++first;
if(first == last) return;
}
auto i = std::find(first, last, '\n');
if(i != last) {
if(ignore_newline_chars()) ++i;
if(i != last) {
write_line(first, i);
write(i, last);
}
}
else {
write_line(first, last);
}
}
/** @brief handle line wrapping due to column constraints */
template<class Iter>
void write_line(Iter first, Iter last)
{
if(first == last) return;
if(only_whitespace(first, last)) return;
if(right_of_text_area()) wrap_soft();
if(at_begin_of_line()) {
//discard whitespace, it we start a new line
first = std::find_if(first, last,
[](char_type c) { return !std::isspace(c); });
if(first == last) return;
}
const auto n = int(std::distance(first,last));
const auto m = columns_left_in_line();
//if text to be printed is too long for one line -> wrap
if(n > m) {
//break before word, if break is mid-word
auto breakat = first + m;
while(breakat > first && !std::isspace(*breakat)) --breakat;
//could not find whitespace before word -> try after the word
if(!std::isspace(*breakat) && breakat == first) {
breakat = std::find_if(first+m, last,
[](char_type c) { return std::isspace(c); });
}
if(breakat > first) {
if(curCol_ < 1) ++totalNonBlankLines_;
fix_indent();
std::copy(first, breakat, std::ostream_iterator<char_type>(os_));
curBlankLines_ = 0;
}
if(breakat < last) {
wrap_soft();
write_line(breakat, last);
}
}
else {
if(curCol_ < 1) ++totalNonBlankLines_;
fix_indent();
std::copy(first, last, std::ostream_iterator<char_type>(os_));
curCol_ += n;
curBlankLines_ = 0;
}
}
/** @brief write a single character */
void write(char_type c)
{
if(c == '\n') {
if(!ignore_newline_chars()) wrap_hard();
}
else {
if(at_begin_of_line()) ++totalNonBlankLines_;
fix_indent();
os_ << c;
++curCol_;
}
}
OStream& os_;
int curCol_;
int firstCol_;
int lastCol_;
int hangingIndent_;
int paragraphSpacing_;
int paragraphSpacingThreshold_;
int curBlankLines_;
int curParagraphLines_;
int totalNonBlankLines_;
bool ignoreInputNls_;
};
} //namespace clipp
#endif

28
cmake/FindLIBUSB.cmake Normal file
View File

@ -0,0 +1,28 @@
# - Try to find the libusb library
# Once done this defines
#
# LIBUSB_FOUND - system has libusb
# LIBUSB_INCLUDE_DIR - the libusb include directory
# LIBUSB_LIBRARIES - Link these to use libusb
# Copyright (c) 2006, 2008 Laurent Montel, <montel@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
if (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
# in cache already
set(LIBUSB_FOUND TRUE)
else (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
IF (NOT WIN32)
# use pkg-config to get the directories and then use these values
# in the FIND_PATH() and FIND_LIBRARY() calls
find_package(PkgConfig)
pkg_check_modules(PC_LIBUSB libusb-1.0)
ENDIF(NOT WIN32)
FIND_PATH(LIBUSB_INCLUDE_DIR libusb.h
PATHS ${PC_LIBUSB_INCLUDEDIR} ${PC_LIBUSB_INCLUDE_DIRS})
FIND_LIBRARY(LIBUSB_LIBRARIES NAMES usb-1.0
PATHS ${PC_LIBUSB_LIBDIR} ${PC_LIBUSB_LIBRARY_DIRS})
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBUSB DEFAULT_MSG LIBUSB_LIBRARIES LIBUSB_INCLUDE_DIR)
MARK_AS_ADVANCED(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES)
endif (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)

View File

@ -0,0 +1,22 @@
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

60
elf.h Normal file
View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _ELF_H
#define _ELF_H
#include <stdint.h>
#define ELF_MAGIC 0x464c457fu
#define EM_ARM 0x28u
#define EF_ARM_ABI_FLOAT_HARD 0x00000400u
#define PT_LOAD 0x00000001u
#pragma pack(push, 1)
struct elf_header {
uint32_t magic;
uint8_t arch_class;
uint8_t endianness;
uint8_t version;
uint8_t abi;
uint8_t abi_version;
uint8_t _pad[7];
uint16_t type;
uint16_t machine;
uint32_t version2;
};
struct elf32_header {
struct elf_header common;
uint32_t entry;
uint32_t ph_offset;
uint32_t sh_offset;
uint32_t flags;
uint16_t eh_size;
uint16_t ph_entry_size;
uint16_t ph_num;
uint16_t sh_entry_size;
uint16_t sh_num;
uint16_t sh_str_index;
};
struct elf32_ph_entry {
uint32_t type;
uint32_t offset;
uint32_t vaddr;
uint32_t paddr;
uint32_t filez;
uint32_t memsz;
uint32_t flags;
uint32_t align;
};
#pragma pack(pop)
#endif

2061
main.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
add_library(picoboot_connection INTERFACE)
target_sources(picoboot_connection INTERFACE
${CMAKE_CURRENT_LIST_DIR}/picoboot_connection.c)
target_include_directories(picoboot_connection INTERFACE ${CMAKE_CURRENT_LIST_DIR})
add_library(picoboot_connection_cxx INTERFACE)
target_sources(picoboot_connection_cxx INTERFACE
${CMAKE_CURRENT_LIST_DIR}/picoboot_connection_cxx.cpp)
target_link_libraries(picoboot_connection_cxx INTERFACE picoboot_connection)

View File

@ -0,0 +1,425 @@
/*
* 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
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_RP2_USBBOOT:
break;
default:
return dr_vidpid_unknown;
}
ret = libusb_get_active_config_descriptor(device, &config);
if (ret && verbose) {
output("Failed to read config descriptor\n");
}
}
if (!ret) {
ret = libusb_open(device, dev_handle);
if (ret && verbose) {
output("Failed to open device %d\n", ret);
}
if (ret) {
return dr_vidpid_bootrom_cant_connect;
}
}
if (!ret) {
if (config->bNumInterfaces == 1) {
interface = 0;
} else {
interface = 1;
}
if (config->interface[interface].altsetting[0].bInterfaceClass == 0xff &&
config->interface[interface].altsetting[0].bNumEndpoints == 2) {
out_ep = config->interface[interface].altsetting[0].endpoint[0].bEndpointAddress;
in_ep = config->interface[interface].altsetting[0].endpoint[1].bEndpointAddress;
}
if (out_ep && in_ep && !(out_ep & 0x80u) && (in_ep & 0x80u)) {
if (verbose) output("Found PICOBOOT interface\n");
ret = libusb_claim_interface(*dev_handle, interface);
if (ret) {
if (verbose) output("Failed to claim interface\n");
return dr_vidpid_bootrom_no_interface;
}
return dr_vidpid_bootrom_ok;
} else {
if (verbose) output("Did not find PICOBOOT interface\n");
return dr_vidpid_bootrom_no_interface;
}
}
assert(ret);
if (*dev_handle) {
libusb_close(*dev_handle);
*dev_handle = NULL;
}
return dr_error;
}
static bool is_halted(libusb_device_handle *usb_device, int ep) {
uint8_t data[2];
int transferred = libusb_control_transfer(
usb_device,
/*LIBUSB_REQUEST_TYPE_STANDARD | */LIBUSB_RECIPIENT_ENDPOINT | LIBUSB_ENDPOINT_IN,
LIBUSB_REQUEST_GET_STATUS,
0, ep,
data, sizeof(data),
1000);
if (transferred != sizeof(data)) {
output("Get status failed\n");
return false;
}
if (data[0] & 1) {
if (verbose) output("%d was halted\n", ep);
return true;
}
if (verbose) output("%d was not halted\n", ep);
return false;
}
int picoboot_reset(libusb_device_handle *usb_device) {
if (verbose) output("RESET\n");
if (is_halted(usb_device, in_ep))
libusb_clear_halt(usb_device, in_ep);
if (is_halted(usb_device, out_ep))
libusb_clear_halt(usb_device, out_ep);
int ret =
libusb_control_transfer(usb_device, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_INTERFACE,
PICOBOOT_IF_RESET, 0, interface, NULL, 0, 1000);
if (ret != 0) {
output(" ...failed\n");
return ret;
}
if (verbose) output(" ...ok\n");
return 0;
}
int picoboot_cmd_status_verbose(libusb_device_handle *usb_device, struct picoboot_cmd_status *status, bool local_verbose) {
struct picoboot_cmd_status s;
if (!status) status = &s;
if (local_verbose) output("CMD_STATUS\n");
int ret =
libusb_control_transfer(usb_device,
LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
PICOBOOT_IF_CMD_STATUS, 0, interface, (uint8_t *) status, sizeof(*status), 1000);
if (ret != sizeof(*status)) {
output(" ...failed\n");
return ret;
}
if (local_verbose)
output(" ... cmd %02x%s tok=%08x status=%d\n", status->bCmdId, status->bInProgress ? " (in progress)" : "",
status->dToken, status->dStatusCode);
return 0;
}
int picoboot_cmd_status(libusb_device_handle *usb_device, struct picoboot_cmd_status *status) {
return picoboot_cmd_status_verbose(usb_device, status, verbose);
}
int one_time_bulk_timeout;
int picoboot_cmd(libusb_device_handle *usb_device, struct picoboot_cmd *cmd, uint8_t *buffer, uint buf_size) {
int sent = 0;
int ret;
static int token = 1;
cmd->dMagic = PICOBOOT_MAGIC;
cmd->dToken = token++;
ret = libusb_bulk_transfer(usb_device, out_ep, (uint8_t *) cmd, sizeof(struct picoboot_cmd), &sent, 3000);
if (ret != 0 || sent != sizeof(struct picoboot_cmd)) {
output(" ...failed to send command %d\n", ret);
return ret;
}
int timeout = 10000;
if (one_time_bulk_timeout) {
timeout = one_time_bulk_timeout;
one_time_bulk_timeout = 0;
}
if (cmd->dTransferLength != 0) {
assert(buf_size >= cmd->dTransferLength);
if (cmd->bCmdId & 0x80u) {
if (verbose) output(" receive %d...\n", cmd->dTransferLength);
int received = 0;
ret = libusb_bulk_transfer(usb_device, in_ep, buffer, cmd->dTransferLength, &received, timeout);
if (ret != 0 || received != (int) cmd->dTransferLength) {
output(" ...failed to receive data %d %d/%d\n", ret, received, cmd->dTransferLength);
if (!ret) ret = 1;
return ret;
}
} else {
if (verbose) output(" send %d...\n", cmd->dTransferLength);
ret = libusb_bulk_transfer(usb_device, out_ep, buffer, cmd->dTransferLength, &sent, timeout);
if (ret != 0 || sent != (int) cmd->dTransferLength) {
output(" ...failed to send data %d %d/%d\n", ret, sent, cmd->dTransferLength);
if (!ret) ret = 1;
picoboot_cmd_status_verbose(usb_device, NULL, true);
return ret;
}
}
}
// ack is in opposite direction
int received = 0;
uint8_t spoon[64];
if (cmd->bCmdId & 0x80u) {
if (verbose) output("zero length out\n");
ret = libusb_bulk_transfer(usb_device, out_ep, spoon, 1, &received, cmd->dTransferLength == 0 ? timeout : 3000);
} else {
if (verbose) output("zero length in\n");
ret = libusb_bulk_transfer(usb_device, in_ep, spoon, 1, &received, cmd->dTransferLength == 0 ? timeout : 3000);
}
return ret;
}
int picoboot_exclusive_access(libusb_device_handle *usb_device, uint8_t exclusive) {
if (verbose) output("EXCLUSIVE ACCESS %d\n", exclusive);
struct picoboot_cmd cmd;
cmd.bCmdId = PC_EXCLUSIVE_ACCESS;
cmd.exclusive_cmd.bExclusive = exclusive;
cmd.bCmdSize = sizeof(struct picoboot_exclusive_cmd);
cmd.dTransferLength = 0;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_exit_xip(libusb_device_handle *usb_device) {
struct picoboot_cmd cmd;
if (verbose) output("EXIT_XIP\n");
cmd.bCmdId = PC_EXIT_XIP;
cmd.bCmdSize = 0;
cmd.dTransferLength = 0;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_enter_cmd_xip(libusb_device_handle *usb_device) {
struct picoboot_cmd cmd;
if (verbose) output("ENTER_CMD_XIP\n");
cmd.bCmdId = PC_ENTER_CMD_XIP;
cmd.bCmdSize = 0;
cmd.dTransferLength = 0;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_reboot(libusb_device_handle *usb_device, uint32_t pc, uint32_t sp, uint32_t delay_ms) {
struct picoboot_cmd cmd;
if (verbose) output("REBOOT %08x %08x %u\n", (uint) pc, (uint) sp, (uint) delay_ms);
cmd.bCmdId = PC_REBOOT;
cmd.bCmdSize = sizeof(cmd.reboot_cmd);
cmd.dTransferLength = 0;
cmd.reboot_cmd.dPC = pc;
cmd.reboot_cmd.dSP = sp;
cmd.reboot_cmd.dDelayMS = delay_ms;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_exec(libusb_device_handle *usb_device, uint32_t addr) {
struct picoboot_cmd cmd;
// shouldn't be necessary any more
// addr |= 1u; // Thumb bit
if (verbose) output("EXEC %08x\n", (uint) addr);
cmd.bCmdId = PC_EXEC;
cmd.bCmdSize = sizeof(cmd.address_only_cmd);
cmd.dTransferLength = 0;
cmd.address_only_cmd.dAddr = addr;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_flash_erase(libusb_device_handle *usb_device, uint32_t addr, uint32_t len) {
struct picoboot_cmd cmd;
if (verbose) output("FLASH_ERASE %08x+%08x\n", (uint) addr, (uint) len);
cmd.bCmdId = PC_FLASH_ERASE;
cmd.bCmdSize = sizeof(cmd.range_cmd);
cmd.range_cmd.dAddr = addr;
cmd.range_cmd.dSize = len;
cmd.dTransferLength = 0;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_vector(libusb_device_handle *usb_device, uint32_t addr) {
struct picoboot_cmd cmd;
if (verbose) output("VECTOR %08x\n", (uint) addr);
cmd.bCmdId = PC_VECTORIZE_FLASH;
cmd.bCmdSize = sizeof(cmd.address_only_cmd);
cmd.range_cmd.dAddr = addr;
cmd.dTransferLength = 0;
return picoboot_cmd(usb_device, &cmd, NULL, 0);
}
int picoboot_write(libusb_device_handle *usb_device, uint32_t addr, uint8_t *buffer, uint32_t len) {
struct picoboot_cmd cmd;
if (verbose) output("WRITE %08x+%08x\n", (uint) addr, (uint) len);
cmd.bCmdId = PC_WRITE;
cmd.bCmdSize = sizeof(cmd.range_cmd);
cmd.range_cmd.dAddr = addr;
cmd.range_cmd.dSize = cmd.dTransferLength = len;
return picoboot_cmd(usb_device, &cmd, buffer, len);
}
int picoboot_read(libusb_device_handle *usb_device, uint32_t addr, uint8_t *buffer, uint32_t len) {
memset(buffer, 0xaa, len);
if (verbose) output("READ %08x+%08x\n", (uint) addr, (uint) len);
struct picoboot_cmd cmd;
cmd.bCmdId = PC_READ;
cmd.bCmdSize = sizeof(cmd.range_cmd);
cmd.range_cmd.dAddr = addr;
cmd.range_cmd.dSize = cmd.dTransferLength = len;
int ret = picoboot_cmd(usb_device, &cmd, buffer, len);
if (!ret && len < 256 && verbose) {
for (uint32_t i = 0; i < len; i += 32) {
output("\t");
for (uint32_t j = i; j < MIN(len, i + 32); j++) {
output("0x%02x, ", buffer[j]);
}
output("\n");
}
}
return ret;
}
#if 0
// Peek/poke via EXEC
// 00000000 <poke>:
// 0: 4801 ldr r0, [pc, #4] ; (8 <data>)
// 2: 4902 ldr r1, [pc, #8] ; (c <addr>)
// 4: 6008 str r0, [r1, #0]
// 6: 4770 bx lr
// 00000008 <data>:
// 8: 12345678 .word 0x12345678
// 0000000c <addr>:
// c: 9abcdef0 .word 0x9abcdef0
static const size_t picoboot_poke_cmd_len = 8;
static const uint8_t picoboot_poke_cmd[] = {
0x01, 0x48, 0x02, 0x49, 0x08, 0x60, 0x70, 0x47
};
// 00000000 <peek>:
// 0: 4802 ldr r0, [pc, #8] ; (c <inout>)
// 2: 6800 ldr r0, [r0, #0]
// 4: 4679 mov r1, pc
// 6: 6048 str r0, [r1, #4]
// 8: 4770 bx lr
// a: 46c0 nop ; (mov r8, r8)
// 0000000c <inout>:
// c: 0add7355 .word 0x0add7355
static const size_t picoboot_peek_cmd_len = 12;
static const uint8_t picoboot_peek_cmd[] = {
0x02, 0x48, 0x00, 0x68, 0x79, 0x46, 0x48, 0x60, 0x70, 0x47, 0xc0, 0x46
};
// TODO better place for this e.g. the USB DPRAM location the controller has already put it in
#define PEEK_POKE_CODE_LOC 0x20000000u
int picoboot_poke(libusb_device_handle *usb_device, uint32_t addr, uint32_t data) {
const size_t prog_size = picoboot_poke_cmd_len + 8;
uint8_t prog[prog_size];
output("POKE (D)%08x -> (A)%08x\n", data, addr);
memcpy(prog, picoboot_poke_cmd, picoboot_poke_cmd_len);
*(uint32_t *) (prog + picoboot_poke_cmd_len) = data;
*(uint32_t *) (prog + picoboot_poke_cmd_len + 4) = addr;
int ret = picoboot_write(usb_device, PEEK_POKE_CODE_LOC, prog, prog_size);
if (ret)
return ret;
return picoboot_exec(usb_device, PEEK_POKE_CODE_LOC);
}
// TODO haven't checked the store goes to the right address :)
int picoboot_peek(libusb_device_handle *usb_device, uint32_t addr, uint32_t *data) {
const size_t prog_size = picoboot_peek_cmd_len + 4;
uint8_t prog[prog_size];
output("PEEK %08x\n", addr);
memcpy(prog, picoboot_peek_cmd, picoboot_peek_cmd_len);
*(uint32_t *) (prog + picoboot_peek_cmd_len) = addr;
int ret = picoboot_write(usb_device, PEEK_POKE_CODE_LOC, prog, prog_size);
if (ret)
return ret;
ret = picoboot_exec(usb_device, PEEK_POKE_CODE_LOC);
if (ret)
return ret;
return picoboot_read(usb_device, PEEK_POKE_CODE_LOC + picoboot_peek_cmd_len, (uint8_t *) data, sizeof(uint32_t));
}
#endif

View File

@ -0,0 +1,109 @@
/*
* 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 <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,
};
enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_device_handle **dev_handle);
int picoboot_reset(libusb_device_handle *usb_device);
int picoboot_cmd_status_verbose(libusb_device_handle *usb_device, struct picoboot_cmd_status *status,
bool local_verbose);
int picoboot_cmd_status(libusb_device_handle *usb_device, struct picoboot_cmd_status *status);
int picoboot_exclusive_access(libusb_device_handle *usb_device, uint8_t exclusive);
int picoboot_enter_cmd_xip(libusb_device_handle *usb_device);
int picoboot_exit_xip(libusb_device_handle *usb_device);
int picoboot_reboot(libusb_device_handle *usb_device, uint32_t pc, uint32_t sp, uint32_t delay_ms);
int picoboot_exec(libusb_device_handle *usb_device, uint32_t addr);
int picoboot_flash_erase(libusb_device_handle *usb_device, uint32_t addr, uint32_t len);
int picoboot_vector(libusb_device_handle *usb_device, uint32_t addr);
int picoboot_write(libusb_device_handle *usb_device, uint32_t addr, uint8_t *buffer, uint32_t len);
int picoboot_read(libusb_device_handle *usb_device, uint32_t addr, uint8_t *buffer, uint32_t len);
int picoboot_poke(libusb_device_handle *usb_device, uint32_t addr, uint32_t data);
int picoboot_peek(libusb_device_handle *usb_device, uint32_t addr, uint32_t *data);
#define ROM_START 0x00000000
#define ROM_END 0x00004000
#define FLASH_START 0x10000000
#define FLASH_END 0x11000000 // this is maximum
#define XIP_SRAM_BASE 0x15000000
#define XIP_SRAM_END 0x15004000
#define SRAM_START 0x20000000
#define SRAM_END 0x20042000
#define SRAM_UNSTRIPED_START 0x21000000
#define SRAM_UNSTRIPED_END 0x21040000
// we require 256 (as this is the page size supported by the device)
#define LOG2_PAGE_SIZE 8u
#define PAGE_SIZE (1u << LOG2_PAGE_SIZE)
#define FLASH_SECTOR_ERASE_SIZE 4096u
enum memory_type {
rom,
flash,
sram,
sram_unstriped,
xip_sram,
invalid,
};
// inclusive of ends
static inline enum memory_type get_memory_type(uint32_t addr) {
if (addr >= ROM_START && addr <= ROM_END) {
return rom;
}
if (addr >= FLASH_START && addr <= FLASH_END) {
return flash;
}
if (addr >= SRAM_START && addr <= SRAM_END) {
return sram;
}
if (addr >= SRAM_UNSTRIPED_START && addr <= SRAM_UNSTRIPED_END) {
return sram_unstriped;
}
if (addr >= XIP_SRAM_BASE && addr <= XIP_SRAM_END) {
return xip_sram;
}
return invalid;
}
static inline bool is_transfer_aligned(uint32_t addr) {
enum memory_type t = get_memory_type(addr);
return t != invalid && !(t == flash && addr & (PAGE_SIZE-1));
}
static inline bool is_size_aligned(uint32_t addr, int size) {
#ifndef _MSC_VER
assert(__builtin_popcount(size)==1);
#endif
return !(addr & (size-1));
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdexcept>
#include <system_error>
#include <map>
#include <algorithm>
#include "picoboot_connection_cxx.h"
using picoboot::connection;
using picoboot::connection_error;
using picoboot::command_failure;
std::map<enum picoboot_status, const char *> status_code_strings = {
{picoboot_status::PICOBOOT_OK, "ok"},
{picoboot_status::PICOBOOT_BAD_ALIGNMENT, "bad address alignment"},
{picoboot_status::PICOBOOT_INTERLEAVED_WRITE, "interleaved write"},
{picoboot_status::PICOBOOT_INVALID_ADDRESS, "invalid address"},
{picoboot_status::PICOBOOT_INVALID_CMD_LENGTH, "invalid cmd length"},
{picoboot_status::PICOBOOT_INVALID_TRANSFER_LENGTH, "invalid transfer length"},
{picoboot_status::PICOBOOT_REBOOTING, "rebooting"},
{picoboot_status::PICOBOOT_UNKNOWN_CMD, "unknown cmd"},
};
const char *command_failure::what() const noexcept {
auto f = status_code_strings.find((enum picoboot_status) code);
if (f != status_code_strings.end()) {
return f->second;
} else {
return "<unknown>";
}
}
template <typename F> void connection::wrap_call(F&& func) {
int rc = func();
#if 0
// we should always get a failure if there is an error, hence this is NDEBUG
if (!rc) {
struct picoboot_cmd_status status;
rc = picoboot_cmd_status(device, &status);
if (!rc) {
assert(!status.dStatusCode);
}
}
#endif
if (rc) {
struct picoboot_cmd_status status;
status.dStatusCode = 0;
rc = picoboot_cmd_status(device, &status);
if (!rc) {
throw command_failure(status.dStatusCode ? (int)status.dStatusCode : PICOBOOT_UNKNOWN_ERROR);
}
throw connection_error(rc);
}
}
void connection::reset() {
wrap_call([&] { return picoboot_reset(device); });
}
void connection::exclusive_access(uint8_t exclusive) {
wrap_call([&] { return picoboot_exclusive_access(device, exclusive); });
}
void connection::enter_cmd_xip() {
wrap_call([&] { return picoboot_enter_cmd_xip(device); });
}
void connection::exit_xip() {
wrap_call([&] { return picoboot_exit_xip(device); });
}
void connection::reboot(uint32_t pc, uint32_t sp, uint32_t delay_ms) {
wrap_call([&] { return picoboot_reboot(device, pc, sp, delay_ms); });
}
void connection::exec(uint32_t addr) {
wrap_call([&] { return picoboot_exec(device, addr); });
}
void connection::flash_erase(uint32_t addr, uint32_t len) {
wrap_call([&] { return picoboot_flash_erase(device, addr, len); });
}
void connection::vector(uint32_t addr) {
wrap_call([&] { return picoboot_vector(device, addr); });
}
void connection::write(uint32_t addr, uint8_t *buffer, uint32_t len) {
wrap_call([&] { return picoboot_write(device, addr, buffer, len); });
}
void connection::read(uint32_t addr, uint8_t *buffer, uint32_t len) {
wrap_call([&] { return picoboot_read(device, addr, buffer, len); });
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICOBOOT_CONNECTION_CXX_H
#define _PICOBOOT_CONNECTION_CXX_H
#include "picoboot_connection.h"
#include <vector>
namespace picoboot {
struct command_failure : public std::exception {
explicit command_failure(int code) : code(code) {}
const char *what() const noexcept override;
private:
int code;
};
struct connection_error : public std::exception {
connection_error(int libusb_code) : libusb_code(libusb_code) {}
const int libusb_code;
};
struct connection {
explicit connection(libusb_device_handle *device, bool exclusive = true) : device(device), exclusive(exclusive) {
if (exclusive) exclusive_access(EXCLUSIVE);
}
~connection() {
if (exclusive) {
picoboot_exclusive_access(device, NOT_EXCLUSIVE);
}
}
void reset();
void exclusive_access(uint8_t exclusive);
void enter_cmd_xip();
void exit_xip();
void reboot(uint32_t pc, uint32_t sp, uint32_t delay_ms);
void exec(uint32_t addr);
void flash_erase(uint32_t addr, uint32_t len);
void vector(uint32_t addr);
void write(uint32_t addr, uint8_t *buffer, uint32_t len);
void write_bytes(uint32_t addr, std::vector<uint8_t> bytes) {
write(addr, bytes.data(), bytes.size());
}
void read(uint32_t addr, uint8_t *buffer, uint32_t len);
std::vector<uint8_t> read_bytes(uint32_t addr, uint32_t len) {
std::vector<uint8_t> bytes(len);
read(addr, bytes.data(), len);
return bytes;
}
private:
template <typename F> void wrap_call(F&& func);
libusb_device_handle *device;
bool exclusive;
};
}
#endif