Initial Release
This commit is contained in:
commit
d54bf53ab9
|
@ -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()
|
|
@ -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).
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
||||
|
||||
|
|
@ -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)
|
|
@ -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.
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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); });
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue