add minichlink

This commit is contained in:
Kizarm 2025-02-05 15:55:13 +01:00
parent b9a3524daf
commit 0d77388545
23 changed files with 26050 additions and 0 deletions

4
.gitignore vendored
View file

@ -27,4 +27,8 @@ V203/usb/scope/software/obj/*
V203/usb/scope/software/qrc_src.cpp
V203/usb/scope/software/ui_mainwindow.h
V203/usb/spitest/*
V203F6P6/programmer/software/programmer
minichlink/minichlink

View file

@ -70,3 +70,13 @@ zásobníku. Ta gsm knihovna toho sežere asi moc. Nebo, a to je pravděpodobně
u tohoto ořezaného jádra neumí se zásobníkem správně pracovat. To se ukazuje i v jiných
přikladech (viz pwm). Prostě ta v003 je divná, podivně funguje i optimalizace LTO, vynechává
některé potřebné funkce.
# V203F6P6
Koupil jsem omylem pár čipů CH32V003 ve 20. pinovém pouzdře, je to dost ořezané, ale
použitelné, takže to má i vlastní hardware.
# minichlink
Tohle je převzato, ale bylo nutné to trochu poopravit, takže to dám sem.

12564
ch32v003fun/ch32v003fun.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
diff -Naurw ./old/microgdbstub.h ./new/microgdbstub.h
--- ./old/microgdbstub.h 2025-02-05 14:31:54.679397633 +0100
+++ ./new/microgdbstub.h 2025-02-05 12:22:13.979925751 +0100
@@ -286,6 +286,7 @@
int mslen = strlen( MICROGDBSTUB_MEMORY_MAP ) + 32;
char map[mslen];
struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
+ printf("flash=0x%X, ss=%d, ram=0x%X\n", iss->flash_size, iss->sector_size, iss->ram_size);
snprintf( map, mslen, MICROGDBSTUB_MEMORY_MAP, iss->flash_size, iss->sector_size, iss->ram_size );
SendReplyFull( map );
}
diff -Naurw ./old/minichlink.c ./new/minichlink.c
--- ./old/minichlink.c 2025-02-05 14:31:54.683397570 +0100
+++ ./new/minichlink.c 2025-02-05 13:59:27.185611922 +0100
@@ -78,7 +78,7 @@
struct InternalState * iss = calloc( 1, sizeof( struct InternalState ) );
((struct ProgrammerStructBase*)dev)->internal = iss;
iss->ram_base = 0x20000000;
- iss->ram_size = 2048;
+ iss->ram_size = 4*0x400; // minimal size for CH32V003
iss->sector_size = 64;
iss->flash_size = 16384;
iss->target_chip_type = 0;
diff -Naurw ./old/pgm-wch-linke.c ./new/pgm-wch-linke.c
--- ./old/pgm-wch-linke.c 2025-02-05 14:31:54.683397570 +0100
+++ ./new/pgm-wch-linke.c 2025-02-05 13:40:17.011872846 +0100
@@ -413,7 +413,8 @@
fprintf( stderr, "Error: could not get part status\n" );
return -1;
}
- fprintf( stderr, "Flash Storage: %d kB\n", (rbuff[2]<<8) | rbuff[3] ); // Is this Flash size?
+ const uint32_t flskb = ((uint32_t)rbuff[2]<<8) | rbuff[3];
+ fprintf( stderr, "Flash Storage: %d kB\n", flskb ); // Is this Flash size?
fprintf( stderr, "Part UUID : %02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x\n", rbuff[4], rbuff[5], rbuff[6], rbuff[7], rbuff[8], rbuff[9], rbuff[10], rbuff[11] );
fprintf( stderr, "PFlags : %02x-%02x-%02x-%02x\n", rbuff[12], rbuff[13], rbuff[14], rbuff[15] );
fprintf( stderr, "Part Type (B): %02x-%02x-%02x-%02x\n", rbuff[16], rbuff[17], rbuff[18], rbuff[19] );
@@ -431,7 +432,11 @@
fprintf(stderr, "Read protection: disabled\n");
}
- iss->flash_size = ((rbuff[2]<<8) | rbuff[3])*1024;
+ iss->flash_size = flskb * 0x400u;
+ if (chip == CHIP_CH32V20x) { // diferent version chip
+ if (flskb > 32u) iss->ram_size = 20u * 0x400u; // 64K
+ else iss->ram_size = 10u * 0x400u; // 32K
+ }
return 0;
}

View file

@ -0,0 +1,13 @@
SUBSYSTEM=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="8010", GROUP="plugdev", MODE="0660"
# Programmer in ARM mode
SUBSYSTEM=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="8012", GROUP="plugdev", MODE="0660"
# Programmer in IAP mode
SUBSYSTEM=="usb", ATTRS{idVendor}=="4348", ATTRS{idProduct}=="55e0", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="usb", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="4004", GROUP="plugdev", MODE="0660"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="4004", GROUP="plugdev", MODE="0660"
# rv003usb bootloader
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="b003", GROUP="plugdev", MODE="0660"
#KERNEL=="hiddev*", SUBSYSTEM=="usbmisc", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="b003", GROUP="plugdev", MODE="0660"

52
minichlink/Makefile Normal file
View file

@ -0,0 +1,52 @@
TOOLS:=minichlink minichlink.so
CFLAGS:=-O0 -g3 -Wall -DCH32V003 -I.
C_S:=minichlink.c pgm-wch-linke.c pgm-esp32s2-ch32xx.c nhc-link042.c ardulink.c serial_dev.c pgm-b003fun.c minichgdb.c
# General Note: To use with GDB, gdb-multiarch
# gdb-multilib {file}
# target remote :2345
ifeq ($(OS),Windows_NT)
LDFLAGS:=-L. -lpthread -lusb-1.0 -lsetupapi -lws2_32
CFLAGS:=-Os -s -Wall -D_WIN32_WINNT=0x0600 -DCH32V003 -I.
TOOLS:=minichlink.exe
else
OS_NAME := $(shell uname -s | tr A-Z a-z)
ifeq ($(OS_NAME),linux)
LDFLAGS:=-lpthread -lusb-1.0 -ludev
endif
ifeq ($(OS_NAME),darwin)
LDFLAGS:=-lpthread -lusb-1.0 -framework CoreFoundation -framework IOKit
CFLAGS:=-O0 -Wall -Wno-asm-operand-widths -Wno-deprecated-declarations -Wno-deprecated-non-prototype -D__MACOSX__ -DCH32V003 -I.
INCLUDES:=$(shell pkg-config --cflags-only-I libusb-1.0)
LIBINCLUDES:=$(shell pkg-config --libs-only-L libusb-1.0)
INCS:=$(INCLUDES) $(LIBINCLUDES)
endif
endif
all : $(TOOLS)
# will need mingw-w64-x86-64-dev gcc-mingw-w64-x86-64
minichlink.exe : $(C_S)
x86_64-w64-mingw32-gcc -o $@ $^ $(LDFLAGS) $(CFLAGS)
minichlink : $(C_S)
gcc -o $@ $^ $(LDFLAGS) $(CFLAGS) $(INCS)
minichlink.so : $(C_S)
gcc -o $@ $^ $(LDFLAGS) $(CFLAGS) $(INCS) -shared -fPIC
minichlink.dll : $(C_S)
x86_64-w64-mingw32-gcc -o $@ $^ $(LDFLAGS) $(CFLAGS) $(INCS) -shared -DMINICHLINK_AS_LIBRARY
install_udev_rules :
cp 99-WCH-LinkE.rules /etc/udev/rules.d/
service udev restart
inspect_bootloader : minichlink
./minichlink -r test.bin launcher 0x780
riscv64-unknown-elf-objdump -S -D test.bin -b binary -m riscv:rv32 | less
clean :
rm -rf $(TOOLS)

42
minichlink/README.md Normal file
View file

@ -0,0 +1,42 @@
# minichlink
A free, open mechanism to use the CH-LinkE $4 programming dongle for the CH32V003.
If on Linux, be sure to type make sure to install the `99-WCH-LinkE.rules` build rule to `/etc/udev/rules.d/`
On Windows, if you need to you can install the WinUSB driver over the WCH interface 1.
The exe here is about 12kB and contains everything except for the libusb driver. In Linux you need `libusb-1.0-dev`.
## Usage
```
Usage: minichlink [args]
single-letter args may be combined, i.e. -3r
multi-part args cannot.
-3 Enable 3.3V
-5 Enable 5V
-t Disable 3.3V
-f Disable 5V
-u Clear all code flash - by power off (also can unbrick)
-b Reboot out of Halt
-e Resume from halt
-a Place into Halt
-D Configure NRST as GPIO
-d Configure NRST as NRST
-s [debug register] [value]
-g [debug register]
-w [binary image to write] [address, decimal or 0x, try0x08000000]
-r [output binary image] [memory address, decimal or 0x, try 0x08000000] [size, decimal or 0x, try 16384]
Note: for memory addresses, you can use 'flash' 'launcher' 'bootloader' 'option' 'ram' and say "ram+0x10" for instance
For filename, you can use - for raw or + for hex.
-T is a terminal. This MUST be the last argument.
```
# Kizarm NOTES
Software je převzato z https://github.com/cnlohr/ch32v003fun/tree/master , projekt je živý, ale chyby
v komunikaci s gdb nejsou opraveny. Ty, co mi nejvíce vadily - pro CH32V203 je špatně nastavena paměť,
jak RAM, tak flash, takže se na některé proměnné prostě nedostanete - hlavně na zásobník.
Pro ukázku, co chybí je zde 3564ce8b030428153a53003d6be79729463fa63b.patch pro revizi danou názvem.
Nebudu se do toho autorům montovat, chtělo by to kompletně předělat pro různé verze čipů a v tom
se vůbec nevyznám a z čínské dokumentace se toho taky moc nedozvíte.

174
minichlink/ardulink.c Normal file
View file

@ -0,0 +1,174 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "serial_dev.h"
#include "minichlink.h"
void * TryInit_Ardulink(const init_hints_t*);
static int ArdulinkWriteReg32(void * dev, uint8_t reg_7_bit, uint32_t command);
static int ArdulinkReadReg32(void * dev, uint8_t reg_7_bit, uint32_t * commandresp);
static int ArdulinkFlushLLCommands(void * dev);
static int ArdulinkDelayUS(void * dev, int microseconds);
static int ArdulinkControl3v3(void * dev, int power_on);
static int ArdulinkExit(void * dev);
typedef struct {
struct ProgrammerStructBase psb;
serial_dev_t serial;
} ardulink_ctx_t;
int ArdulinkWriteReg32(void * dev, uint8_t reg_7_bit, uint32_t command)
{
uint8_t buf[6];
buf[0] = 'w';
buf[1] = reg_7_bit;
//fprintf(stderr, "WriteReg32: 0x%02x = 0x%08x\n", reg_7_bit, command);
buf[2] = command & 0xff;
buf[3] = (command >> 8) & 0xff;
buf[4] = (command >> 16) & 0xff;
buf[5] = (command >> 24) & 0xff;
if (serial_dev_write(&((ardulink_ctx_t*)dev)->serial, buf, 6) == -1)
return -errno;
if (serial_dev_read(&((ardulink_ctx_t*)dev)->serial, buf, 1) == -1)
return -errno;
return buf[0] == '+' ? 0 : -71; // EPROTO
}
int ArdulinkReadReg32(void * dev, uint8_t reg_7_bit, uint32_t * commandresp)
{
uint8_t buf[4];
buf[0] = 'r';
buf[1] = reg_7_bit;
if (serial_dev_write(&((ardulink_ctx_t*)dev)->serial, buf, 2) == -1)
return -errno;
if (serial_dev_read(&((ardulink_ctx_t*)dev)->serial, buf, 4) == -1)
return -errno;
*commandresp = (uint32_t)buf[0] | (uint32_t)buf[1] << 8 | \
(uint32_t)buf[2] << 16 | (uint32_t)buf[3] << 24;
//fprintf(stderr, "ReadReg32: 0x%02x = 0x%08x\n", reg_7_bit, *commandresp);
return 0;
}
int ArdulinkFlushLLCommands(void * dev)
{
return 0;
}
int ArdulinkControl3v3(void * dev, int power_on) {
char c;
fprintf(stderr, "Ardulink: target power %d\n", power_on);
c = power_on ? 'p' : 'P';
if (serial_dev_write(&((ardulink_ctx_t*)dev)->serial, &c, 1) == -1)
return -errno;
if (serial_dev_read(&((ardulink_ctx_t*)dev)->serial, &c, 1) == -1)
return -errno;
if (c != '+')
return -71; // EPROTO
MCF.DelayUS(dev, 20000);
return 0;
}
int ArdulinkDelayUS(void * dev, int microseconds) {
//fprintf(stderr, "Ardulink: faking delay %d\n", microseconds);
//usleep(microseconds);
return 0;
}
int ArdulinkExit(void * dev)
{
serial_dev_close(&((ardulink_ctx_t*)dev)->serial);
free(dev);
return 0;
}
int ArdulinkSetupInterface( void * dev )
{
char first;
// Let the bootloader do its thing.
MCF.DelayUS(dev, 3UL*1000UL*1000UL);
if (serial_dev_read(&((ardulink_ctx_t*)dev)->serial, &first, 1) == -1) {
perror("read");
return -1;
}
if (first != '!') {
fprintf(stderr, "Ardulink: not the sync character.\n");
return -1;
}
return 0;
}
void * TryInit_Ardulink(const init_hints_t* hints)
{
ardulink_ctx_t *ctx;
if (!(ctx = calloc(sizeof(ardulink_ctx_t), 1))) {
perror("calloc");
return NULL;
}
const char* serial_to_open = NULL;
// Get the serial port that shall be opened.
// First, if we have a directly set serial port hint, use that.
// Otherwise, use the environment variable MINICHLINK_SERIAL.
// If that also doesn't exist, fall back to the default serial.
if (hints && hints->serial_port != NULL) {
serial_to_open = hints->serial_port;
}
else if ((serial_to_open = getenv("MINICHLINK_SERIAL")) == NULL) {
// fallback
serial_to_open = DEFAULT_SERIAL_NAME;
}
if (serial_dev_create(&ctx->serial, serial_to_open, 115200) == -1) {
perror("create");
return NULL;
}
if (serial_dev_open(&ctx->serial) == -1) {
perror("open");
return NULL;
}
// Arduino DTR reset.
if (serial_dev_do_dtr_reset(&ctx->serial) == -1) {
perror("dtr reset");
return NULL;
}
// Flush anything that might be in the RX buffer, we need the sync char.
if (serial_dev_flush_rx(&ctx->serial) == -1) {
perror("flush rx");
return NULL;
}
fprintf(stderr, "Ardulink: synced.\n");
MCF.WriteReg32 = ArdulinkWriteReg32;
MCF.ReadReg32 = ArdulinkReadReg32;
MCF.FlushLLCommands = ArdulinkFlushLLCommands;
MCF.Control3v3 = ArdulinkControl3v3;
MCF.DelayUS = ArdulinkDelayUS;
MCF.Exit = ArdulinkExit;
MCF.SetupInterface = ArdulinkSetupInterface;
return ctx;
}

7
minichlink/funconfig.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef _FUNCONFIG_H
#define _FUNCONFIG_H
#define MINICHLINK 1
#endif

4477
minichlink/hidapi.c Normal file

File diff suppressed because it is too large Load diff

406
minichlink/hidapi.h Normal file
View file

@ -0,0 +1,406 @@
/*******************************************************
HIDAPI - Multi-Platform library for
communication with HID devices.
Alan Ott
Signal 11 Software
8/22/2009
Copyright 2009, All Rights Reserved.
At the discretion of the user of this library,
this software may be licensed under the terms of the
GNU General Public License v3, a BSD-Style license, or the
original HIDAPI license as outlined in the LICENSE.txt,
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
files located at the root of the source distribution.
These files may also be found in the public source
code repository located at:
http://github.com/signal11/hidapi .
********************************************************/
/* Copy of LICENSE-orig.txt (compatible with MIT/x11 license)
HIDAPI - Multi-Platform library for
communication with HID devices.
Copyright 2009, Alan Ott, Signal 11 Software.
All Rights Reserved.
This software may be used by anyone for any reason so
long as the copyright notice in the source files
remains intact.
*/
/** @file
* @defgroup API hidapi API
*/
#ifndef HIDAPI_H__
#define HIDAPI_H__
#include <wchar.h>
#ifdef _WIN32
#define HID_API_EXPORT __declspec(dllexport)
#define HID_API_CALL
#else
#define HID_API_EXPORT /**< API export macro */
#define HID_API_CALL /**< API call macro */
#endif
#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/
#ifdef __cplusplus
extern "C" {
#endif
struct hid_device_;
typedef struct hid_device_ hid_device; /**< opaque hidapi structure */
/** hidapi info structure */
struct hid_device_info {
/** Platform-specific device path */
char *path;
/** Device Vendor ID */
unsigned short vendor_id;
/** Device Product ID */
unsigned short product_id;
/** Serial Number */
wchar_t *serial_number;
/** Device Release Number in binary-coded decimal,
also known as Device Version Number */
unsigned short release_number;
/** Manufacturer String */
wchar_t *manufacturer_string;
/** Product string */
wchar_t *product_string;
/** Usage Page for this Device/Interface
(Windows/Mac only). */
unsigned short usage_page;
/** Usage for this Device/Interface
(Windows/Mac only).*/
unsigned short usage;
/** The USB interface which this logical device
represents. Valid on both Linux implementations
in all cases, and valid on the Windows implementation
only if the device contains more than one interface. */
int interface_number;
/** Pointer to the next device */
struct hid_device_info *next;
};
/** @brief Initialize the HIDAPI library.
This function initializes the HIDAPI library. Calling it is not
strictly necessary, as it will be called automatically by
hid_enumerate() and any of the hid_open_*() functions if it is
needed. This function should be called at the beginning of
execution however, if there is a chance of HIDAPI handles
being opened by different threads simultaneously.
@ingroup API
@returns
This function returns 0 on success and -1 on error.
*/
int HID_API_EXPORT HID_API_CALL hid_init(void);
/** @brief Finalize the HIDAPI library.
This function frees all of the static data associated with
HIDAPI. It should be called at the end of execution to avoid
memory leaks.
@ingroup API
@returns
This function returns 0 on success and -1 on error.
*/
int HID_API_EXPORT HID_API_CALL hid_exit(void);
/** @brief Enumerate the HID Devices.
This function returns a linked list of all the HID devices
attached to the system which match vendor_id and product_id.
If @p vendor_id is set to 0 then any vendor matches.
If @p product_id is set to 0 then any product matches.
If @p vendor_id and @p product_id are both set to 0, then
all HID devices will be returned.
@ingroup API
@param vendor_id The Vendor ID (VID) of the types of device
to open.
@param product_id The Product ID (PID) of the types of
device to open.
@returns
This function returns a pointer to a linked list of type
struct #hid_device, containing information about the HID devices
attached to the system, or NULL in the case of failure. Free
this linked list by calling hid_free_enumeration().
*/
struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id);
/** @brief Free an enumeration Linked List
This function frees a linked list created by hid_enumerate().
@ingroup API
@param devs Pointer to a list of struct_device returned from
hid_enumerate().
*/
void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs);
/** @brief Open a HID device using a Vendor ID (VID), Product ID
(PID) and optionally a serial number.
If @p serial_number is NULL, the first device with the
specified VID and PID is opened.
@ingroup API
@param vendor_id The Vendor ID (VID) of the device to open.
@param product_id The Product ID (PID) of the device to open.
@param serial_number The Serial Number of the device to open
(Optionally NULL).
@returns
This function returns a pointer to a #hid_device object on
success or NULL on failure.
*/
HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number);
/** @brief Open a HID device by its path name.
The path name be determined by calling hid_enumerate(), or a
platform-specific path name can be used (eg: /dev/hidraw0 on
Linux).
@ingroup API
@param path The path name of the device to open
@returns
This function returns a pointer to a #hid_device object on
success or NULL on failure.
*/
HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path);
/** @brief Write an Output report to a HID device.
The first byte of @p data[] must contain the Report ID. For
devices which only support a single report, this must be set
to 0x0. The remaining bytes contain the report data. Since
the Report ID is mandatory, calls to hid_write() will always
contain one more byte than the report contains. For example,
if a hid report is 16 bytes long, 17 bytes must be passed to
hid_write(), the Report ID (or 0x0, for devices with a
single report), followed by the report data (16 bytes). In
this example, the length passed in would be 17.
hid_write() will send the data on the first OUT endpoint, if
one exists. If it does not, it will send the data through
the Control Endpoint (Endpoint 0).
@ingroup API
@param device A device handle returned from hid_open().
@param data The data to send, including the report number as
the first byte.
@param length The length in bytes of the data to send.
@returns
This function returns the actual number of bytes written and
-1 on error.
*/
int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length);
/** @brief Read an Input report from a HID device with timeout.
Input reports are returned
to the host through the INTERRUPT IN endpoint. The first byte will
contain the Report number if the device uses numbered reports.
@ingroup API
@param device A device handle returned from hid_open().
@param data A buffer to put the read data into.
@param length The number of bytes to read. For devices with
multiple reports, make sure to read an extra byte for
the report number.
@param milliseconds timeout in milliseconds or -1 for blocking wait.
@returns
This function returns the actual number of bytes read and
-1 on error. If no packet was available to be read within
the timeout period, this function returns 0.
*/
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
/** @brief Read an Input report from a HID device.
Input reports are returned
to the host through the INTERRUPT IN endpoint. The first byte will
contain the Report number if the device uses numbered reports.
@ingroup API
@param device A device handle returned from hid_open().
@param data A buffer to put the read data into.
@param length The number of bytes to read. For devices with
multiple reports, make sure to read an extra byte for
the report number.
@returns
This function returns the actual number of bytes read and
-1 on error. If no packet was available to be read and
the handle is in non-blocking mode, this function returns 0.
*/
int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length);
/** @brief Set the device handle to be non-blocking.
In non-blocking mode calls to hid_read() will return
immediately with a value of 0 if there is no data to be
read. In blocking mode, hid_read() will wait (block) until
there is data to read before returning.
Nonblocking can be turned on and off at any time.
@ingroup API
@param device A device handle returned from hid_open().
@param nonblock enable or not the nonblocking reads
- 1 to enable nonblocking
- 0 to disable nonblocking.
@returns
This function returns 0 on success and -1 on error.
*/
int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock);
/** @brief Send a Feature report to the device.
Feature reports are sent over the Control endpoint as a
Set_Report transfer. The first byte of @p data[] must
contain the Report ID. For devices which only support a
single report, this must be set to 0x0. The remaining bytes
contain the report data. Since the Report ID is mandatory,
calls to hid_send_feature_report() will always contain one
more byte than the report contains. For example, if a hid
report is 16 bytes long, 17 bytes must be passed to
hid_send_feature_report(): the Report ID (or 0x0, for
devices which do not use numbered reports), followed by the
report data (16 bytes). In this example, the length passed
in would be 17.
@ingroup API
@param device A device handle returned from hid_open().
@param data The data to send, including the report number as
the first byte.
@param length The length in bytes of the data to send, including
the report number.
@returns
This function returns the actual number of bytes written and
-1 on error.
*/
int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length);
/** @brief Get a feature report from a HID device.
Set the first byte of @p data[] to the Report ID of the
report to be read. Make sure to allow space for this
extra byte in @p data[]. Upon return, the first byte will
still contain the Report ID, and the report data will
start in data[1].
@ingroup API
@param device A device handle returned from hid_open().
@param data A buffer to put the read data into, including
the Report ID. Set the first byte of @p data[] to the
Report ID of the report to be read, or set it to zero
if your device does not use numbered reports.
@param length The number of bytes to read, including an
extra byte for the report ID. The buffer can be longer
than the actual report.
@returns
This function returns the number of bytes read plus
one for the report ID (which is still in the first
byte), or -1 on error.
*/
int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length);
/** @brief Close a HID device.
@ingroup API
@param device A device handle returned from hid_open().
*/
void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device);
/** @brief Get The Manufacturer String from a HID device.
@ingroup API
@param device A device handle returned from hid_open().
@param string A wide string buffer to put the data into.
@param maxlen The length of the buffer in multiples of wchar_t.
@returns
This function returns 0 on success and -1 on error.
*/
int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen);
/** @brief Get The Product String from a HID device.
@ingroup API
@param device A device handle returned from hid_open().
@param string A wide string buffer to put the data into.
@param maxlen The length of the buffer in multiples of wchar_t.
@returns
This function returns 0 on success and -1 on error.
*/
int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen);
/** @brief Get The Serial Number String from a HID device.
@ingroup API
@param device A device handle returned from hid_open().
@param string A wide string buffer to put the data into.
@param maxlen The length of the buffer in multiples of wchar_t.
@returns
This function returns 0 on success and -1 on error.
*/
int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen);
/** @brief Get a string from a HID device, based on its string index.
@ingroup API
@param device A device handle returned from hid_open().
@param string_index The index of the string to get.
@param string A wide string buffer to put the data into.
@param maxlen The length of the buffer in multiples of wchar_t.
@returns
This function returns 0 on success and -1 on error.
*/
int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen);
/** @brief Get a string describing the last error which occurred.
@ingroup API
@param device A device handle returned from hid_open().
@returns
This function returns a string containing the last error
which occurred or NULL if none has occurred.
*/
HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device);
#ifdef __cplusplus
}
#endif
#endif

2133
minichlink/libusb.h Normal file

File diff suppressed because it is too large Load diff

826
minichlink/microgdbstub.h Normal file
View file

@ -0,0 +1,826 @@
/*
* Micro GDBStub Driver, for implementing a gdbserver.
* Copyright (C) Charles Lohr 2023
* You may freely license this file under the MIT-x11, or the 2- or 3- or New BSD Licenses.
* You may also use this as though it is public domain.
*
* This project is based off of picorvd. https://github.com/aappleby/PicoRVD/
*
* Simply:
* 1: define the RV_ Functions
* 2: Call the MicroGDB* functions needed.
* 3: Define MICROGDBSTUB_IMPLEMENTATION at least in one place this is included in your program.
* 4: If you want to let this manage the server as a network device, simply #define MICROGDBSTUB_SOCKETS
*
* To connect to your GDBStub running, you can:
* gdb-multiarch -ex 'target remote :2000' ./blink.elf
*
*/
#ifndef _MICROGDBSTUB_H
#define _MICROGDBSTUB_H
// You must write these for your processor.
void RVNetPoll(void * dev );
int RVSendGDBHaltReason( void * dev );
void RVNetConnect( void * dev );
int RVReadCPURegister( void * dev, int regno, uint32_t * regret );
int RVWriteCPURegister( void * dev, int regno, uint32_t value );
void RVDebugExec( void * dev, int halt_reset_or_resume );
int RVReadMem( void * dev, uint32_t memaddy, uint8_t * payload, int len );
int RVHandleBreakpoint( void * dev, int set, uint32_t address );
int RVWriteRAM(void * dev, uint32_t memaddy, uint32_t length, uint8_t * payload );
void RVCommandResetPart( void * dev, int mode );
void RVHandleDisconnect( void * dev );
void RVHandleGDBBreakRequest( void * dev );
void RVHandleKillRequest( void * dev );
int RVErase( void * dev, uint32_t memaddy, uint32_t length );
int RVWriteFlash( void * dev, uint32_t memaddy, uint32_t length, uint8_t * payload );
#ifdef MICROGDBSTUB_SOCKETS
int MicroGDBPollServer( void * dev );
int MicroGDBStubStartup( void * dev );
void MicroGDBExitServer( void * dev );
#endif
// If you are not a network socket, you can pass in this data.
void MicroGDBStubSendReply( const void * data, int len, int docs );
void MicroGDBStubHandleClientData( void * dev, const uint8_t * rxdata, int len );
#ifdef MICROGDBSTUB_IMPLEMENTATION
///////////////////////////////////////////////////////////////////////////////
// Protocol Stuff
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef MICROGDBSTUB_SOCKETS
#if defined( WIN32 ) || defined( _WIN32 )
#include <winsock2.h>
#if !defined( POLLIN )
typedef struct pollfd { SOCKET fd; SHORT events; SHORT revents; };
#define POLLIN 0x0001
#define POLLERR 0x008
#define POLLHUP 0x010
int WSAAPI WSAPoll(struct pollfd * fdArray, ULONG fds, INT timeout );
#endif
#define poll WSAPoll
#define socklen_t uint32_t
#define SHUT_RDWR SD_BOTH
#define MSG_NOSIGNAL 0
#else
#define closesocket close
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <poll.h>
#endif
#endif
#ifdef __linux__
#include <linux/in.h>
#endif
char gdbbuffer[65536];
uint8_t gdbchecksum = 0;
int gdbbufferplace = 0;
int gdbbufferstate = 0;
int gdbrunningcsum = 0;
static inline char ToHEXNibble( int i )
{
i &= 0xf;
return ( i < 10 )?('0' + i):('a' - 10 + i);
}
static int fromhex( char c )
{
if( c >= '0' && c <= '9' ) c = c - '0';
else if( c >= 'A' && c <= 'F' ) c = c - 'A' + 10;
else if( c >= 'a' && c <= 'f' ) c = c - 'a' + 10;
else return -1;
return c;
}
// output must have length of len / 2.
static int DecodeHexToBytes(const char* hexstr, size_t string_len, void* output, size_t out_len) {
// 2 hex chars make up one byte. out buffer needs to have >= slen/2 bytes.
// further, we only want to decode even-length strings
if (out_len < (string_len / 2) || (string_len % 2) != 0)
return -1;
uint8_t* out = (uint8_t*) output;
for(size_t i = 0; i < string_len; i += 2) {
int nibble1, nibble2;
if((nibble1 = fromhex(hexstr[i])) < 0)
return nibble1; // error
if((nibble2 = fromhex(hexstr[i + 1])) < 0)
return nibble2; // error
out[i / 2] = ((uint8_t)nibble1 << 4u) | ((uint8_t)nibble2);
}
return string_len / 2; // number of output bytes written
}
// if (numhex < 0)
static int ReadHex( char ** instr, int numhex, uint32_t * outwrite )
{
if( !instr ) return -1;
char * str = *instr;
// If negative - error.
// If positive - number of bytes read.
*outwrite = 0;
int scanhex = numhex;
if( scanhex < 0 )
scanhex = strlen( str );
int i;
for( i = 0; i < scanhex; i++ )
{
int v = fromhex( *(str++) );
if( v < 0 )
{
if( numhex < 0 )
{
str--;
*instr = str;
return i;
}
else
{
*instr = str;
return - i - 1;
}
}
(*outwrite) = ((*outwrite) << 4) | v;
}
*instr = str;
return i;
}
static int StringMatch( const char * haystack, const char * mat )
{
int i;
for( i = 0; mat[i] && haystack[i]; i++ )
if( mat[i] != haystack[i] || haystack[i] == 0 ) break;
return mat[i] == 0;
}
void SendReplyFull( const char * replyMessage )
{
MicroGDBStubSendReply( replyMessage, -1, '$' );
}
void MakeGDBPrintText(const char* msg) {
// ASCII to hex conversion doubles size, plus 'O', plus NUL
size_t buf_len = 2 * strlen(msg) + 2;
char* buf = alloca(buf_len);
memset(buf, 0, buf_len);
buf[0] = 'O'; // for "Output"
char* target = buf + 1;
for(size_t i = 0; i < strlen(msg); i++) {
target[2*i] = ToHEXNibble((msg[i] & 0xf0u) >> 4u);
target[2*i + 1] = ToHEXNibble(msg[i] & 0x0fu);
}
SendReplyFull(buf);
}
///////////////////////////////////////////////////////////////////////////////
// General Protocol
void HandleGDBPacket( void * dev, char * data, int len )
{
int i;
char * odata = data;
// Got a packet?
if( data[0] != '$' ) return;
data++;
char cmd = *(data++);
switch( cmd )
{
case 'q':
if( StringMatch( data, "Attached" ) )
SendReplyFull( "1" ); //Attached to an existing process.
else if( StringMatch( data, "Supported" ) )
SendReplyFull( "PacketSize=f000;qXfer:memory-map:read+" );
else if( StringMatch( data, "C") ) // Get Current Thread ID. (Can't be -1 or 0. Those are special)
SendReplyFull( "QC1" );
else if( StringMatch( data, "fThreadInfo" ) ) // Query all active thread IDs (Can't be 0 or 1)
SendReplyFull( "m1" );
else if( StringMatch( data, "sThreadInfo" ) ) // Query all active thread IDs, continued
SendReplyFull( "l" );
// Unimplemented commands.
else if( StringMatch( data, "Offsets" ) ) // Trace-Status
SendReplyFull( "" );
else if( StringMatch( data, "Symbol" ) ) // Trace-Status
SendReplyFull( "" );
else if( StringMatch( data, "TStatus" ) ) // Trace-Status
SendReplyFull( "" );
else if( StringMatch( data, "Rcmd," ) ) // "monitor <command>"
{
// will e.g. be "Rcmd,7265736574#"
// hex-decode the rest of it back to ASCII
char* cmdHex = data + strlen("Rcmd,");
// check if cmd is empty (no character or only '#' following)
if(strlen(cmdHex) > 1) {
char cmd[128];
memset(cmd, 0, sizeof(cmd));
if ( DecodeHexToBytes(cmdHex, strlen(cmdHex) - 1, cmd, sizeof(cmd) - 1) < 0 ) {
// decoding failed
SendReplyFull( "" );
break;
}
printf("Got monitor command: %s\n", cmd);
// Support commands that OpenOCD also does:
// https://openocd.org/doc/html/General-Commands.html
if(StringMatch(cmd, "halt")) {
// only halt
RVCommandResetPart( dev, HALT_MODE_HALT_BUT_NO_RESET);
SendReplyFull( "+" );
}
else if(StringMatch(cmd, "reset halt")) {
// reset and keep halted after reset
RVCommandResetPart( dev, HALT_MODE_HALT_AND_RESET);
SendReplyFull( "+" );
}
else if(StringMatch(cmd, "reset run")) {
// reset and run (i.e., reboot)
RVCommandResetPart( dev, HALT_MODE_REBOOT);
SendReplyFull( "+" );
}
else if(StringMatch(cmd, "reset")) {
// same as reset run per OpenOCD
RVCommandResetPart( dev, HALT_MODE_REBOOT);
SendReplyFull( "+" );
} else if(StringMatch(cmd, "resume")) {
// just resume
RVCommandResetPart( dev, HALT_MODE_RESUME);
SendReplyFull( "+" );
} else if(StringMatch(cmd, "help")) {
static const char helptext[] =
"minichlink GDB monitor help:\n"
"- halt: Halt execution\n"
"- resume: Resume execution\n"
"- reset [halt, run]: Reset and optionally halt or run\n";
MakeGDBPrintText(helptext);
SendReplyFull( "+" );
}
else {
printf("Unknown monitor command '%s', use 'monitor help'.\n", cmd);
MakeGDBPrintText("Unknown monitor command, use 'monitor help'\n");
SendReplyFull( "-" );
}
} else {
MakeGDBPrintText("No monitor command given, use 'monitor help'\n");
SendReplyFull( "-" );
}
}
else if( StringMatch( data, "Xfer:memory-map" ) )
{
int mslen = strlen( MICROGDBSTUB_MEMORY_MAP ) + 32;
char map[mslen];
struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
printf("flash=0x%X, ss=%d, ram=0x%X\n", iss->flash_size, iss->sector_size, iss->ram_size);
snprintf( map, mslen, MICROGDBSTUB_MEMORY_MAP, iss->flash_size, iss->sector_size, iss->ram_size );
SendReplyFull( map );
}
else
{
printf( "Unknown command: %s\n", data );
SendReplyFull( "" );
}
break;
case 'c':
case 'C':
RVDebugExec( dev, (cmd == 's' )?9:(cmd == 'C')?4:2 );
SendReplyFull( "OK" );
break;
case 's':
RVDebugExec( dev, 4 );
SendReplyFull( "OK" );
//RVHandleGDBBreakRequest( dev );
RVSendGDBHaltReason( dev );
break;
case 'D':
// Handle disconnect.
RVHandleDisconnect( dev );
break;
case 'k':
RVHandleKillRequest( dev ); // no reply.
break;
case 'P': // Set register
{
uint32_t reg, value;
if( ReadHex( &data, -1, &reg ) < 0 ) goto err;
if( *(data++) != ',' ) goto err;
if( ReadHex( &data, -1, &value ) < 0 ) goto err;
printf( "REG: %02x = %08x\n", reg, value );
RVWriteCPURegister( dev, reg, value );
break;
}
case 'Z': // set
case 'z': // unset
{
uint32_t type = 0;
uint32_t addr = 0;
uint32_t time = 0;
if( ReadHex( &data, -1, &type ) < 0 ) goto err;
if( *(data++) != ',' ) goto err;
if( ReadHex( &data, -1, &addr ) < 0 ) goto err;
if( *(data++) != ',' ) goto err;
if( ReadHex( &data, -1, &time ) < 0 ) goto err;
if( RVHandleBreakpoint( dev, cmd == 'Z', addr ) == 0 )
{
SendReplyFull( "OK" );
}
else
goto err;
break;
}
case 'm':
{
// Read memory (Binary)
uint32_t address_to_read = 0;
uint32_t length_to_read = 0;
if( ReadHex( &data, -1, &address_to_read ) < 0 ) goto err;
if( *(data++) != ',' ) goto err;
if( ReadHex( &data, -1, &length_to_read ) < 0 ) goto err;
uint8_t * pl = alloca( length_to_read * 2 );
if( RVReadMem( dev, address_to_read, pl, length_to_read ) < 0 )
goto err;
char * repl = alloca( length_to_read * 2 );
int i;
for( i = 0; i < length_to_read; i++ )
{
sprintf( repl + i * 2, "%02x", pl[i] );
}
SendReplyFull( repl );
break;
}
case 'M':
{
uint32_t address_to_write = 0;
uint32_t length_to_write = 0;
if( ReadHex( &data, -1, &address_to_write ) < 0 ) goto err;
if( *(data++) != ',' ) goto err;
if( ReadHex( &data, -1, &length_to_write ) < 0 ) goto err;
if( *(data++) != ':' ) goto err;
uint8_t * meml = alloca( length_to_write );
int i;
for( i = 0; i < length_to_write; i++ )
{
uint32_t rv;
if( ReadHex( &data, 2, &rv ) < 0 ) goto err;
meml[i] = rv;
}
if( RVWriteRAM( dev, address_to_write, length_to_write, meml ) < 0 ) goto err;
SendReplyFull( "OK" );
break;
}
case 'X':
{
// Write memory, binary.
uint32_t address_to_write = 0;
uint32_t length_to_write = 0;
if( ReadHex( &data, -1, &address_to_write ) < 0 ) goto err;
if( *(data++) != ',' ) goto err;
if( ReadHex( &data, -1, &length_to_write ) < 0 ) goto err;
if( *(data++) != ':' ) goto err;
if( RVWriteRAM( dev, address_to_write, length_to_write, (uint8_t*)data ) < 0 ) goto err;
SendReplyFull( "OK" );
break;
}
case 'v':
if( StringMatch( data, "Cont" ) ) // vCont?
{
// Request a list of actions supported by the vCont packet.
// We don't support vCont
SendReplyFull( "vCont;s;c;;" ); //no ;t because we don't implement them.
}
else if( StringMatch( data, "MustReplyEmpty" ) ) //vMustReplyEmpty
{
SendReplyFull( "" );
}
else if( StringMatch( data, "FlashDone" ) ) //vFlashDone
{
SendReplyFull( "OK" );
}
else if( StringMatch( data, "FlashErase" ) ) //vFlashErase
{
data += 10; // FlashErase
if( *(data++) != ':' ) goto err;
uint32_t address_to_erase = 0;
uint32_t length_to_erase = 0;
if( ReadHex( &data, -1, &address_to_erase ) < 0 ) goto err;
if( *(data++) != ',' ) goto err;
if( ReadHex( &data, -1, &length_to_erase ) < 0 ) goto err;
if( RVErase( dev, address_to_erase, length_to_erase ) == 0 )
SendReplyFull( "OK" );
else
SendReplyFull( "E 93" );
}
else if( StringMatch( data, "FlashWrite" ) ) //vFlashWrite
{
data += 10; // FlashWrite
printf( "Write\n" );
if( *(data++) != ':' ) goto err;
uint32_t address_to_write = 0;
if( ReadHex( &data, -1, &address_to_write ) < 0 ) goto err;
if( *(data++) != ':' ) goto err;
int toflash = len - (data - odata) - 1;
printf( "LEN: %08x %d %d %c\n", address_to_write, len, toflash, data[0] );
if( RVWriteFlash( dev, address_to_write, len, (uint8_t*)data ) == 0 )
{
printf( "Write OK\n" );
SendReplyFull( "OK" );
}
else
SendReplyFull( "E 93" );
}
else
{
printf( "Warning: Unknown v command %s\n", data );
SendReplyFull( "E 01" );
}
break;
case 'g':
{
// Register Read (All regs)
char cts[17*8+1];
for( i = 0; i < 17; i++ )
{
uint32_t regret;
if( RVReadCPURegister( dev, i, &regret ) ) goto err;
sprintf( cts + i * 8, "%08x", (uint32_t)htonl( regret ) );
}
SendReplyFull( cts );
break;
}
case 'p':
{
uint32_t regno;
// Register Read (Specific Reg)
if( ReadHex( &data, 2, &regno ) < 0 )
SendReplyFull( "E 10" );
else
{
char cts[9];
uint32_t regret;
if( RVReadCPURegister( dev, regno, &regret ) ) goto err;
sprintf( cts, "%08x", (uint32_t)htonl( regret ) );
SendReplyFull( cts );
}
break;
}
case '?': // Query reason for target halt.
RVSendGDBHaltReason( dev );
break;
case 'H':
// This is for things like selecting threads.
// I am going to leave this stubbed out.
SendReplyFull( "" );
break;
default:
printf( "UNKNOWN PACKET: %d (%s)\n", len, data-1 );
for( i = 0; i < len; i++ )
{
printf( "%02x ", data[i] );
}
printf( "\n" );
goto err;
break;
}
return;
err:
SendReplyFull( "E 00" );
}
void MicroGDBStubHandleClientData( void * dev, const uint8_t * rxdata, int len )
{
int pl = 0;
for( pl = 0; pl < len; pl++ )
{
int c = rxdata[pl];
if( c == '$' && gdbbufferstate == 0 )
{
gdbrunningcsum = 0;
gdbbufferplace = 0;
gdbbufferstate = 1;
}
if( c == 3 && gdbbufferstate == 0 )
{
RVHandleGDBBreakRequest( dev );
continue;
}
switch( gdbbufferstate )
{
default:
break;
case 1:
if( c != '#' ) gdbrunningcsum += (uint8_t)c;
if( gdbbufferplace < sizeof( gdbbuffer ) - 2 )
{
if( c == '}' ) { gdbbufferstate = 9; break; }
gdbbuffer[gdbbufferplace++] = c;
}
if( c == '#' ) gdbbufferstate = 2;
break;
case 9: // escape
gdbrunningcsum += (uint8_t)c;
if( gdbbufferplace < sizeof( gdbbuffer ) - 2 )
{
char escaped = c ^ 0x20;
gdbbuffer[gdbbufferplace++] = escaped;
printf( "ESCAPED @ %02x -> %c [%d]\n", gdbbufferplace, escaped, escaped );
gdbbufferstate = 1;
}
break;
case 2:
case 3:
{
c = fromhex( c );
if( gdbbufferstate == 2 )
{
gdbchecksum = c << 4;
gdbbufferstate++;
break;
}
if( gdbbufferstate == 3 ) gdbchecksum |= c;
gdbbuffer[gdbbufferplace] = 0;
gdbrunningcsum = (gdbrunningcsum - '$')&0xff;
if( gdbrunningcsum == gdbchecksum )
{
MicroGDBStubSendReply( "+", -1, 0 );
HandleGDBPacket( dev, (char*)gdbbuffer, gdbbufferplace );
}
else
{
printf( "Checksum Error: Got %02x expected %02x / len: %d\n", gdbrunningcsum, gdbchecksum, gdbbufferplace );
int i;
for( i = 0; i < gdbbufferplace; i++ )
{
int c = ((uint8_t*)gdbbuffer)[i];
printf( "%02x [%c] ", c, (c>=32 && c < 128)?c:' ');
if( ( i & 0xf ) == 0xf ) printf( "\n" );
}
printf( "\n" );
MicroGDBStubSendReply( "-", -1, 0 );
}
gdbbufferplace = 0;
gdbbufferstate = 0;
break;
}
}
}
}
#ifdef MICROGDBSTUB_SOCKETS
#include <fcntl.h>
#include <sys/time.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
int listenMode; // 0 for uninit. 1 for server, 2 for client.
int serverSocket;
///////////////////////////////////////////////////////////////////////////////
// Network layer.
void MicroGDBStubHandleDisconnect( void * dev )
{
RVHandleDisconnect( dev );
}
static int GDBListen( void * dev )
{
struct sockaddr_in sin;
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
//Make sure the socket worked.
if( serverSocket == -1 )
{
fprintf( stderr, "Error: Cannot create socket.\n" );
return -1;
}
//Disable SO_LINGER (Well, enable it but turn it way down)
#if defined( WIN32 ) || defined( _WIN32 )
struct linger lx;
lx.l_onoff = 1;
lx.l_linger = 0;
setsockopt( serverSocket, SOL_SOCKET, SO_LINGER, (const char *)&lx, sizeof( lx ) );
//Enable SO_REUSEADDR
int reusevar = 1;
setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reusevar, sizeof(reusevar));
#else
struct linger lx;
lx.l_onoff = 1;
lx.l_linger = 0;
setsockopt( serverSocket, SOL_SOCKET, SO_LINGER, &lx, sizeof( lx ) );
//Enable SO_REUSEADDR
int reusevar = 1;
setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &reusevar, sizeof(reusevar));
#endif
//Setup socket for listening address.
memset( &sin, 0, sizeof( sin ) );
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons( MICROGDBSTUB_PORT );
//Actually bind to the socket
if( bind( serverSocket, (struct sockaddr *) &sin, sizeof( sin ) ) == -1 )
{
fprintf( stderr, "Could not bind to socket: %d\n", MICROGDBSTUB_PORT );
closesocket( serverSocket );
serverSocket = 0;
return -1;
}
//Finally listen.
if( listen( serverSocket, 5 ) == -1 )
{
fprintf(stderr, "Could not lieten to socket.");
closesocket( serverSocket );
serverSocket = 0;
return -1;
}
fprintf( stderr, "gdbserver running on port %d\n", MICROGDBSTUB_PORT );
return 0;
}
int MicroGDBPollServer( void * dev )
{
if( !serverSocket ) return -4;
int pollct = 1;
struct pollfd allpolls[1] = { 0 };
allpolls[0].fd = serverSocket;
#if defined( WIN32 ) || defined( _WIN32 )
allpolls[0].events = 0x00000100; //POLLRDNORM;
#else
allpolls[0].events = POLLIN;
#endif
int r = poll( allpolls, pollct, 0 );
if( r < 0 )
{
printf( "R: %d\n", r );
}
//If there's faults, bail.
if( allpolls[0].revents & (POLLERR|POLLHUP) )
{
closesocket( serverSocket );
if( listenMode == 1 )
{
// Some sort of weird fatal close? Is this even possible?
fprintf( stderr, "Error: serverSocke was forcibly closed\n" );
exit( -4 );
}
else if( listenMode == 2 )
{
MicroGDBStubHandleDisconnect( dev );
if( serverSocket ) close( serverSocket );
serverSocket = 0;
listenMode = 1;
GDBListen( dev );
}
}
if( allpolls[0].revents & POLLIN )
{
if( listenMode == 1 )
{
struct sockaddr_in tin;
socklen_t addrlen = sizeof(tin);
memset( &tin, 0, addrlen );
int tsocket = accept( serverSocket, (struct sockaddr *)&tin, (void*)&addrlen );
closesocket( serverSocket );
serverSocket = tsocket;
listenMode = 2;
gdbbufferstate = 0;
RVNetConnect( dev );
fprintf( stderr, "Connection established to gdbserver backend\n" );
// Established.
}
else if( listenMode == 2 )
{
// Got data from a peer.
uint8_t buffer[16384];
ssize_t rx = recv( serverSocket, (char*)buffer, sizeof( buffer ), MSG_NOSIGNAL );
if( rx == 0 )
{
MicroGDBStubHandleDisconnect( dev );
close( serverSocket );
serverSocket = 0;
listenMode = 1;
GDBListen( dev );
}
else
MicroGDBStubHandleClientData( dev, buffer, (int)rx );
}
}
if( listenMode == 2 )
{
RVNetPoll( dev );
}
return 0;
}
void MicroGDBExitServer( void * dev )
{
shutdown( serverSocket, SHUT_RDWR );
if( listenMode == 2 )
{
MicroGDBStubHandleDisconnect( dev );
}
}
void MicroGDBStubSendReply( const void * data, int len, int docs )
{
if( len < 0 ) len = strlen( data );
if( docs )
{
uint8_t * localbuffer = alloca( len ) + 5;
localbuffer[0] = '$';
uint8_t checksum = 0;
int i;
for( i = 0; i < len; i++ )
{
uint8_t v = ((const uint8_t*)data)[i];
checksum += v;
localbuffer[i+1] = v;
}
localbuffer[len+1] = '#';
localbuffer[len+2] = ToHEXNibble( checksum >> 4 );
localbuffer[len+3] = ToHEXNibble( checksum );
localbuffer[len+4] = 0;
data = (void*)localbuffer;
len += 4;
}
if( listenMode == 2 )
{
//printf( ">>>>%s<<<<(%d)\n", data );
send( serverSocket, data, len, MSG_NOSIGNAL );
}
}
int MicroGDBStubStartup( void * dev )
{
#if defined( WIN32 ) || defined( _WIN32 )
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
fprintf( stderr, "WSAStartup failed with error: %d\n", err);
return 1;
}
}
#endif
listenMode = 1;
return GDBListen( dev );
}
#endif
#endif
#endif

467
minichlink/minichgdb.c Normal file
View file

@ -0,0 +1,467 @@
// This file is loosely based on aappleby's GDBServer.
// Connect in with:
// gdb-multiarch -ex 'target remote :2000' ./blink.elf
#include "minichlink.h"
#define MICROGDBSTUB_IMPLEMENTATION
#define MICROGDBSTUB_SOCKETS
#define MICROGDBSTUB_PORT 2000
const char* MICROGDBSTUB_MEMORY_MAP = "l<?xml version=\"1.0\"?>"
"<!DOCTYPE memory-map PUBLIC \"+//IDN gnu.org//DTD GDB Memory Map V1.0//EN\" \"http://sourceware.org/gdb/gdb-memory-map.dtd\">"
"<memory-map>"
" <memory type=\"flash\" start=\"0x00000000\" length=\"0x%x\">"
" <property name=\"blocksize\">%d</property>"
" </memory>"
" <memory type=\"ram\" start=\"0x20000000\" length=\"0x%x\">"
" <property name=\"blocksize\">1</property>"
" </memory>"
" <memory type=\"ram\" start=\"0x40000000\" length=\"0x10000000\">"
" <property name=\"blocksize\">4</property>"
" </memory>"
"</memory-map>";
#include "microgdbstub.h"
void SendReplyFull( const char * replyMessage );
///////////////////////////////////////////////////////////////////////////////
// Actual Chip Operations
// Several pieces from picorvd. https://github.com/aappleby/PicoRVD/
int shadow_running_state = 1;
int last_halt_reason = 5;
uint32_t backup_regs[17];
#define MAX_SOFTWARE_BREAKPOINTS 128
int num_software_breakpoints = 0;
uint8_t software_breakpoint_type[MAX_SOFTWARE_BREAKPOINTS]; // 0 = not in use, 1 = 32-bit, 2 = 16-bit.
uint32_t software_breakpoint_addy[MAX_SOFTWARE_BREAKPOINTS];
uint32_t previous_word_at_breakpoint_address[MAX_SOFTWARE_BREAKPOINTS];
int IsGDBServerInShadowHaltState( void * dev ) { return !shadow_running_state; }
static int InternalClearFlashOfSoftwareBreakpoint( void * dev, int i );
static int InternalWriteBreakpointIntoAddress( void * v, int i );
void RVCommandPrologue( void * dev )
{
if( !MCF.ReadCPURegister )
{
fprintf( stderr, "Error: Programmer does not support register reading\n" );
exit( -5 );
}
MCF.WriteReg32( dev, DMABSTRACTAUTO, 0 ); // Disable autoexec.
if( MCF.ReadAllCPURegisters( dev, backup_regs ) )
{
fprintf( stderr, "WARNING: failed to preserve registers\n" );
}
MCF.VoidHighLevelState( dev );
}
void RVCommandEpilogue( void * dev )
{
MCF.WriteReg32( dev, DMABSTRACTAUTO, 0 ); // Disable autoexec.
MCF.WriteAllCPURegisters( dev, backup_regs );
MCF.VoidHighLevelState( dev );
MCF.WriteReg32( dev, DMDATA0, 0 );
}
void RVCommandResetPart( void * dev , int mode)
{
MCF.HaltMode( dev, mode );
RVCommandPrologue( dev );
}
void RVNetConnect( void * dev )
{
// ??? Should we actually halt?
MCF.HaltMode( dev, 5 );
MCF.SetEnableBreakpoints( dev, 1, 0 );
RVCommandPrologue( dev );
shadow_running_state = 0;
}
int RVSendGDBHaltReason( void * dev )
{
char st[5];
sprintf( st, "T%02x", last_halt_reason );
SendReplyFull( st );
return 0;
}
void RVNetPoll(void * dev )
{
if( !MCF.ReadReg32 )
{
fprintf( stderr, "Error: Can't poll GDB because no ReadReg32 supported on this programmer\n" );
return;
}
uint32_t status;
if( MCF.ReadReg32( dev, DMSTATUS, &status ) )
{
fprintf( stderr, "Error: Could not get part status\n" );
return;
}
int statusrunning = ((status & (1<<10)));
static int laststatus;
if( status != laststatus )
{
//printf( "DMSTATUS: %08x => %08x\n", laststatus, status );
laststatus = status;
}
if( statusrunning != shadow_running_state )
{
// If was running but now is halted.
if( statusrunning == 0 )
{
RVCommandPrologue( dev );
last_halt_reason = 5;//((dscr>>6)&3)+5;
RVSendGDBHaltReason( dev );
}
shadow_running_state = statusrunning;
}
}
int RVReadCPURegister( void * dev, int regno, uint32_t * regret )
{
if( shadow_running_state )
{
MCF.HaltMode( dev, 5 );
RVCommandPrologue( dev );
shadow_running_state = 0;
}
if( regno == 32 ) regno = 16; // Hack - Make 32 also 16 for old GDBs.
if( regno > 16 ) return 0; // Invalid register.
*regret = backup_regs[regno];
return 0;
}
int RVWriteCPURegister( void * dev, int regno, uint32_t value )
{
if( shadow_running_state )
{
MCF.HaltMode( dev, 5 );
RVCommandPrologue( dev );
shadow_running_state = 0;
}
if( regno == 32 ) regno = 16; // Hack - Make 32 also 16 for old GDBs.
if( regno > 16 ) return 0; // Invalid register.
backup_regs[regno] = value;
if( !MCF.WriteAllCPURegisters )
{
fprintf( stderr, "ERROR: MCF.WriteAllCPURegisters is not implemented on this platform\n" );
return -99;
}
int r;
if( ( r = MCF.WriteAllCPURegisters( dev, backup_regs ) ) )
{
fprintf( stderr, "Error: WriteAllCPURegisters failed (%d)\n", r );
return r;
}
return 0;
}
void RVDebugExec( void * dev, int halt_reset_or_resume )
{
if( !MCF.HaltMode )
{
fprintf( stderr, "Error: Can't alter halt mode with this programmer.\n" );
exit( -6 );
}
// Special case halt_reset_or_resume = 4: Skip instruction and resume.
if( halt_reset_or_resume == 4 || halt_reset_or_resume == 2 )
{
// First see if we already know about this breakpoint
int matchingbreakpoint = -1;
// For this we want to advance PC.
uint32_t exceptionptr = backup_regs[16];
uint32_t instruction = 0;
int i;
for( i = 0; i < MAX_SOFTWARE_BREAKPOINTS; i++ )
{
if( exceptionptr == software_breakpoint_addy[i] && software_breakpoint_type[i] )
{
matchingbreakpoint = i;
}
}
if( matchingbreakpoint >= 0 )
{
// This is a known breakpoint. Need to set it back. Single Step. Then continue.
InternalClearFlashOfSoftwareBreakpoint( dev, matchingbreakpoint );
MCF.SetEnableBreakpoints( dev, 1, 1 );
InternalWriteBreakpointIntoAddress( dev, matchingbreakpoint );
}
else
{
// Unknown breakpoint (was originally in the firmware)
// Just proceed past it.
if( exceptionptr & 2 )
{
uint32_t part1, part2;
MCF.ReadWord( dev, exceptionptr & ~3, &part1 );
MCF.ReadWord( dev, (exceptionptr & ~3)+4, &part2 );
instruction = (part1 >> 16) | (part2 << 16);
}
else
{
MCF.ReadWord( dev, exceptionptr, &instruction );
}
if( instruction == 0x00100073 )
backup_regs[16]+=4;
else if( ( instruction & 0xffff ) == 0x9002 )
backup_regs[16]+=2;
else
; //No change, it is a normal instruction.
if( halt_reset_or_resume == 4 )
{
MCF.SetEnableBreakpoints( dev, 1, 1 );
}
}
halt_reset_or_resume = HALT_MODE_RESUME;
}
if( shadow_running_state != ( halt_reset_or_resume >= 2 ) )
{
if( halt_reset_or_resume < 2 )
{
RVCommandPrologue( dev );
}
else
{
RVCommandEpilogue( dev );
}
MCF.HaltMode( dev, halt_reset_or_resume );
}
shadow_running_state = halt_reset_or_resume >= 2;
}
int RVReadMem( void * dev, uint32_t memaddy, uint8_t * payload, int len )
{
if( !MCF.ReadBinaryBlob )
{
fprintf( stderr, "Error: Can't alter halt mode with this programmer.\n" );
exit( -6 );
}
int ret = MCF.ReadBinaryBlob( dev, memaddy, len, payload );
if( ret < 0 )
{
fprintf( stderr, "Error reading binary blob at %08x\n", memaddy );
}
return ret;
}
static int InternalClearFlashOfSoftwareBreakpoint( void * dev, int i )
{
int r;
if( software_breakpoint_type[i] == 1 )
{
//32-bit instruction
r = MCF.WriteBinaryBlob( dev, software_breakpoint_addy[i], 4, (uint8_t*)&previous_word_at_breakpoint_address[i] );
}
else
{
//16-bit instruction
r = MCF.WriteBinaryBlob( dev, software_breakpoint_addy[i], 2, (uint8_t*)&previous_word_at_breakpoint_address[i] );
}
return r;
}
static int InternalWriteBreakpointIntoAddress( void * dev, int i )
{
int r;
uint32_t address = software_breakpoint_addy[i];
if( software_breakpoint_type[i] == 1 )
{
//32-bit instruction
uint32_t ebreak = 0x00100073; // ebreak
r = MCF.WriteBinaryBlob( dev, address, 4, (uint8_t*)&ebreak );
}
else
{
//16-bit instruction
uint32_t ebreak = 0x9002; // c.ebreak
r = MCF.WriteBinaryBlob( dev, address, 2, (uint8_t*)&ebreak );
}
return r;
}
static int InternalDisableBreakpoint( void * dev, int i )
{
int r;
r = InternalClearFlashOfSoftwareBreakpoint( dev, i );
previous_word_at_breakpoint_address[i] = 0;
software_breakpoint_type[i] = 0;
software_breakpoint_addy[i] = 0;
return r;
}
int RVHandleBreakpoint( void * dev, int set, uint32_t address )
{
int i;
int first_free = -1;
for( i = 0; i < MAX_SOFTWARE_BREAKPOINTS; i++ )
{
if( software_breakpoint_type[i] && software_breakpoint_addy[i] == address )
break;
if( first_free < 0 && software_breakpoint_type[i] == 0 )
first_free = i;
}
if( i != MAX_SOFTWARE_BREAKPOINTS )
{
// There is already a break slot here.
if( !set )
{
InternalDisableBreakpoint( dev, i );
}
else
{
// Already set.
}
}
else
{
if( first_free == -1 )
{
fprintf( stderr, "Error: Too many breakpoints\n" );
return -1;
}
if( set )
{
i = first_free;
uint32_t readval_at_addy;
int r = MCF.ReadBinaryBlob( dev, address, 4, (uint8_t*)&readval_at_addy );
if( r ) return -5;
if( ( readval_at_addy & 3 ) == 3 ) // Check opcode LSB's.
{
// 32-bit instruction.
software_breakpoint_type[i] = 1;
software_breakpoint_addy[i] = address;
previous_word_at_breakpoint_address[i] = readval_at_addy;
}
else
{
// 16-bit instructions
software_breakpoint_type[i] = 2;
software_breakpoint_addy[i] = address;
previous_word_at_breakpoint_address[i] = readval_at_addy & 0xffff;
}
InternalWriteBreakpointIntoAddress( dev, i );
}
else
{
// Already unset.
}
}
return 0;
}
int RVWriteRAM(void * dev, uint32_t memaddy, uint32_t length, uint8_t * payload )
{
if( !MCF.WriteBinaryBlob )
{
fprintf( stderr, "Error: Can't alter halt mode with this programmer.\n" );
exit( -6 );
}
int r = MCF.WriteBinaryBlob( dev, memaddy, length, payload );
return r;
}
int RVWriteFlash(void * dev, uint32_t memaddy, uint32_t length, uint8_t * payload )
{
if( (memaddy & 0xff000000 ) == 0 )
{
memaddy |= 0x08000000;
}
return RVWriteRAM( dev, memaddy, length, payload );
}
int RVErase( void * dev, uint32_t memaddy, uint32_t length )
{
if( !MCF.Erase )
{
fprintf( stderr, "Error: Can't alter halt mode with this programmer.\n" );
exit( -6 );
}
int r = MCF.Erase( dev, memaddy, length, 0 ); // 0 = not whole chip.
return r;
}
void RVHandleDisconnect( void * dev )
{
MCF.HaltMode( dev, 5 );
MCF.SetEnableBreakpoints( dev, 0, 0 );
int i;
for( i = 0; i < MAX_SOFTWARE_BREAKPOINTS; i++ )
{
if( software_breakpoint_type[i] )
{
InternalDisableBreakpoint( dev, i );
}
}
if( shadow_running_state == 0 )
{
RVCommandEpilogue( dev );
}
MCF.HaltMode( dev, 2 );
shadow_running_state = 1;
}
void RVHandleGDBBreakRequest( void * dev )
{
if( shadow_running_state )
{
MCF.HaltMode( dev, 5 );
}
}
int PollGDBServer( void * dev )
{
return MicroGDBPollServer( dev );
}
void ExitGDBServer( void * dev )
{
MicroGDBExitServer( dev );
}
int SetupGDBServer( void * dev )
{
return MicroGDBStubStartup( dev );
}
void RVHandleKillRequest( void * dev )
{
// Do nothing.
}

2056
minichlink/minichlink.c Normal file

File diff suppressed because it is too large Load diff

205
minichlink/minichlink.h Normal file
View file

@ -0,0 +1,205 @@
#ifndef _MINICHLINK_H
#define _MINICHLINK_H
#include <stdint.h>
struct MiniChlinkFunctions
{
// All functions return 0 if OK, negative number if fault, positive number as status code.
// Low-level functions, if they exist.
int (*WriteReg32)( void * dev, uint8_t reg_7_bit, uint32_t command );
int (*ReadReg32)( void * dev, uint8_t reg_7_bit, uint32_t * commandresp );
int (*FlushLLCommands)( void * dev );
int (*DelayUS)( void * dev, int microseconds );
// Higher-level functions can be generated automatically.
int (*SetupInterface)( void * dev );
int (*Control3v3)( void * dev, int bOn );
int (*Control5v)( void * dev, int bOn );
int (*Unbrick)( void * dev ); // Turns on chip, erases everything, powers off.
int (*Exit)( void * dev );
int (*HaltMode)( void * dev, int mode ); //0 for halt, 1 for reset, 2 for resume
int (*ConfigureNRSTAsGPIO)( void * dev, int one_if_yes_gpio );
int (*ConfigureReadProtection)( void * dev, int one_if_yes_protect );
// No boundary or limit rules. Must support any combination of alignment and size.
int (*WriteBinaryBlob)( void * dev, uint32_t address_to_write, uint32_t blob_size, uint8_t * blob );
int (*ReadBinaryBlob)( void * dev, uint32_t address_to_read_from, uint32_t read_size, uint8_t * blob );
int (*Erase)( void * dev, uint32_t address, uint32_t length, int type ); //type = 0 for fast, 1 for whole-chip
// MUST be 4-byte-aligned.
int (*VoidHighLevelState)( void * dev );
int (*WriteWord)( void * dev, uint32_t address_to_write, uint32_t data );
int (*ReadWord)( void * dev, uint32_t address_to_read, uint32_t * data );
// Debugging operations.
// Note: You must already be in break mode to use these otherwise they
// will return nonsensical data.
// For x0...xN, use 0x1000 + regno.
// For PC, use 0x7b1
int (*ReadCPURegister)( void * dev, uint32_t regno, uint32_t * regret );
int (*WriteCPURegister)( void * dev, uint32_t regno, uint32_t regval );
// Actually returns 17 registers (All 16 CPU registers + the debug register)
int (*ReadAllCPURegisters)( void * dev, uint32_t * regret );
int (*WriteAllCPURegisters)( void * dev, uint32_t * regret );
int (*SetEnableBreakpoints)( void * dev, int halt_on_break, int single_step );
int (*PrepForLongOp)( void * dev ); // Called before the command that will take a while.
int (*WaitForFlash)( void * dev );
int (*WaitForDoneOp)( void * dev, int ignore );
int (*PrintChipInfo)( void * dev );
// Geared for flash, but could be anything. Note: If in flash, must also erase.
int (*BlockWrite64)( void * dev, uint32_t address_to_write, uint8_t * data );
// Returns positive if received text.
// Returns negative if error.
// Returns 0 if no text waiting.
// Note: YOU CANNOT make lsb of leaveflagA bit in place 0x80 be high!!!
int (*PollTerminal)( void * dev, uint8_t * buffer, int maxlen, uint32_t leaveflagA, int leaveflagB );
int (*PerformSongAndDance)( void * dev );
int (*VendorCommand)( void * dev, const char * command );
// Probably no need to override these. The base layer handles them.
int (*WriteHalfWord)( void * dev, uint32_t address_to_write, uint16_t data );
int (*ReadHalfWord)( void * dev, uint32_t address_to_read, uint16_t * data );
int (*WriteByte)( void * dev, uint32_t address_to_write, uint8_t data );
int (*ReadByte)( void * dev, uint32_t address_to_read, uint8_t * data );
};
/** If you are writing a driver, the minimal number of functions you can implement are:
WriteReg32
ReadReg32
FlushLLCommands
*/
inline static int IsAddressFlash( uint32_t addy ) { return ( addy & 0xff000000 ) == 0x08000000 || ( addy & 0x1FFFF000 ) == 0x1FFFF000; }
#define HALT_MODE_HALT_AND_RESET 0
#define HALT_MODE_REBOOT 1
#define HALT_MODE_RESUME 2
#define HALT_MODE_GO_TO_BOOTLOADER 3
#define HALT_MODE_HALT_BUT_NO_RESET 5
// Convert a 4-character string to an int.
#define STTAG( x ) (*((uint32_t*)(x)))
struct InternalState;
struct ProgrammerStructBase
{
struct InternalState * internal;
// You can put other things here.
};
#define MAX_FLASH_SECTORS 262144
enum RiscVChip {
CHIP_CH32V10x = 0x01,
CHIP_CH57x = 0x02,
CHIP_CH56x = 0x03,
CHIP_CH32V20x = 0x05,
CHIP_CH32V30x = 0x06,
CHIP_CH58x = 0x07,
CHIP_CH32V003 = 0x09
};
struct InternalState
{
uint32_t statetag;
uint32_t currentstateval;
uint32_t flash_unlocked;
int lastwriteflags;
int processor_in_mode;
int autoincrement;
uint32_t ram_base;
uint32_t ram_size;
int sector_size;
int flash_size;
enum RiscVChip target_chip_type;
uint8_t flash_sector_status[MAX_FLASH_SECTORS]; // 0 means unerased/unknown. 1 means erased.
};
#define DMDATA0 0x04
#define DMDATA1 0x05
#define DMCONTROL 0x10
#define DMSTATUS 0x11
#define DMHARTINFO 0x12
#define DMABSTRACTCS 0x16
#define DMCOMMAND 0x17
#define DMABSTRACTAUTO 0x18
#define DMPROGBUF0 0x20
#define DMPROGBUF1 0x21
#define DMPROGBUF2 0x22
#define DMPROGBUF3 0x23
#define DMPROGBUF4 0x24
#define DMPROGBUF5 0x25
#define DMPROGBUF6 0x26
#define DMPROGBUF7 0x27
#define DMCPBR 0x7C
#define DMCFGR 0x7D
#define DMSHDWCFGR 0x7E
#if defined( WIN32 ) || defined( _WIN32 )
#if defined( MINICHLINK_AS_LIBRARY )
#define DLLDECORATE __declspec(dllexport)
#elif defined( MINICHLINK_IMPORT )
#define DLLDECORATE __declspec(dllimport)
#else
#define DLLDECORATE
#endif
#else
#define DLLDECORATE
#endif
/* initialization hints for init functions */
/* could be expanded with more in the future (e.g., PID/VID hints, priorities, ...)*/
/* not all init functions currently need these hints. */
typedef struct {
const char * serial_port;
const char * specific_programmer;
} init_hints_t;
void * MiniCHLinkInitAsDLL(struct MiniChlinkFunctions ** MCFO, const init_hints_t* init_hints) DLLDECORATE;
extern struct MiniChlinkFunctions MCF;
// Returns 'dev' on success, else 0.
void * TryInit_WCHLinkE(void);
void * TryInit_ESP32S2CHFUN(void);
void * TryInit_NHCLink042(void);
void * TryInit_B003Fun(void);
void * TryInit_Ardulink(const init_hints_t*);
// Returns 0 if ok, populated, 1 if not populated.
int SetupAutomaticHighLevelFunctions( void * dev );
// Useful for converting numbers like 0x, etc.
int64_t SimpleReadNumberInt( const char * number, int64_t defaultNumber );
// For drivers to call
int DefaultVoidHighLevelState( void * dev );
int InternalUnlockBootloader( void * dev );
int InternalIsMemoryErased( struct InternalState * iss, uint32_t address );
void InternalMarkMemoryNotErased( struct InternalState * iss, uint32_t address );
int InternalUnlockFlash( void * dev, struct InternalState * iss );
// GDBSever Functions
int SetupGDBServer( void * dev );
int PollGDBServer( void * dev );
int IsGDBServerInShadowHaltState( void * dev );
void ExitGDBServer( void * dev );
#endif

163
minichlink/nhc-link042.c Normal file
View file

@ -0,0 +1,163 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "minichlink.h"
#include "libusb.h"
void * TryInit_NHCLink042(void);
static int NHCLinkWriteReg32(void * dev, uint8_t reg_7_bit, uint32_t command);
static int NHCLinkReadReg32(void * dev, uint8_t reg_7_bit, uint32_t * commandresp);
static int NHCLinkFlushLLCommands(void * dev);
static int NHCLinkDelayUS(void * dev, int microseconds);
static int NHCLinkExit(void * dev);
static libusb_device_handle *hdev = 0;
int NHCLinkWriteReg32(void * dev, uint8_t reg_7_bit, uint32_t command)
{
uint8_t buff[64];
int32_t len;
int status;
buff[0] = 0xa3;
buff[1] = reg_7_bit;
buff[2] = (command >> 0);
buff[3] = (command >> 8);
buff[4] = (command >> 16);
buff[5] = (command >> 24);
status = libusb_bulk_transfer(dev, 0x01, buff, 64, &len, 5000);
if ((status) || (len != 64))
{
return status;
}
return 0;
}
int NHCLinkReadReg32(void * dev, uint8_t reg_7_bit, uint32_t * commandresp)
{
uint8_t buff[64];
int32_t len;
uint32_t tmp;
int status;
buff[0] = 0xa2;
buff[1] = reg_7_bit;
status = libusb_bulk_transfer(dev, 0x01, buff, 64, &len, 5000);
if ((status) || (len != 64))
{
return status;
}
status = libusb_bulk_transfer(dev, 0x81, buff, 64, &len, 5000);
if ((status) || (len != 64))
{
return status;
}
if (!buff[0])
{
return 1;
}
tmp = buff[4];
tmp <<= 8;
tmp += buff[3];
tmp <<= 8;
tmp += buff[2];
tmp <<= 8;
tmp += buff[1];
*commandresp = tmp;
return 0;
}
int NHCLinkFlushLLCommands(void * dev)
{
return 0;
}
int NHCLinkDelayUS(void * dev, int microseconds)
{
uint8_t buff[64];
int32_t len;
uint32_t tmp;
int status;
tmp = microseconds;
buff[0] = 0xa6;
buff[1] = (tmp >> 0);
buff[2] = (tmp >> 8);
buff[3] = (tmp >> 16);
buff[4] = (tmp >> 24);
status = libusb_bulk_transfer(dev, 0x01, buff, 64, &len, 5000);
if ((status) || (len != 64))
{
return status;
}
return 0;
}
int NHCLinkExit(void * dev)
{
uint8_t buff[64];
int32_t len;
int status;
buff[0] = 0xa1;
status = libusb_bulk_transfer(dev, 0x01, buff, 64, &len, 5000);
if ((status) || (len != 64))
{
return status;
}
return 0;
}
void * TryInit_NHCLink042(void)
{
libusb_context * ctx = 0;
int status;
uint8_t buff[64];
int32_t len;
status = libusb_init(&ctx);
if (status < 0) {
fprintf( stderr, "Error: libusb_init_context() returned %d\n", status );
exit( status );
}
hdev = libusb_open_device_with_vid_pid(ctx, 0x1986, 0x0034);
if( !hdev )
{
return 0;
}
libusb_claim_interface(hdev, 0);
buff[0] = 0xa0;
status = libusb_bulk_transfer(hdev, 0x01, buff, 64, &len, 5000);
if ((status) || (len != 64))
{
return 0;
}
MCF.WriteReg32 = NHCLinkWriteReg32;
MCF.ReadReg32 = NHCLinkReadReg32;
MCF.DelayUS = NHCLinkDelayUS;
MCF.FlushLLCommands = NHCLinkFlushLLCommands;
MCF.Exit = NHCLinkExit;
return hdev;
}

777
minichlink/pgm-b003fun.c Normal file
View file

@ -0,0 +1,777 @@
#include <stdint.h>
#include "hidapi.h"
#include "minichlink.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "../ch32v003fun/ch32v003fun.h"
//#define DEBUG_B003
#if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
void Sleep(uint32_t dwMilliseconds);
#define usleep( x ) Sleep( x / 1000 );
#else
#include <unistd.h>
#endif
struct B003FunProgrammerStruct
{
void * internal; // Part of struct ProgrammerStructBase
hid_device * hd;
uint8_t commandbuffer[128];
uint8_t respbuffer[128];
int commandplace;
int prepping_for_erase;
};
static const unsigned char byte_wise_read_blob[] = { // No alignment restrictions.
0x23, 0xa0, 0x05, 0x00, 0x13, 0x07, 0x45, 0x03, 0x0c, 0x43, 0x50, 0x43,
0x2e, 0x96, 0x21, 0x07, 0x94, 0x21, 0x14, 0xa3, 0x85, 0x05, 0x05, 0x07,
0xe3, 0xcc, 0xc5, 0xfe, 0x93, 0x06, 0xf0, 0xff, 0x14, 0xc1, 0x82, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char half_wise_read_blob[] = { // size and address must be aligned by 2.
0x23, 0xa0, 0x05, 0x00, 0x13, 0x07, 0x45, 0x03, 0x0c, 0x43, 0x50, 0x43,
0x2e, 0x96, 0x21, 0x07, 0x96, 0x21, 0x16, 0xa3, 0x89, 0x05, 0x09, 0x07,
0xe3, 0xcc, 0xc5, 0xfe, 0x93, 0x06, 0xf0, 0xff, 0x14, 0xc1, 0x82, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char word_wise_read_blob[] = { // size and address must be aligned by 4.
0x23, 0xa0, 0x05, 0x00, 0x13, 0x07, 0x45, 0x03, 0x0c, 0x43, 0x50, 0x43,
0x2e, 0x96, 0x21, 0x07, 0x94, 0x41, 0x14, 0xc3, 0x91, 0x05, 0x11, 0x07,
0xe3, 0xcc, 0xc5, 0xfe, 0x93, 0x06, 0xf0, 0xff, 0x14, 0xc1, 0x82, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char word_wise_write_blob[] = { // size and address must be aligned by 4.
0x23, 0xa0, 0x05, 0x00, 0x13, 0x07, 0x45, 0x03, 0x0c, 0x43, 0x50, 0x43,
0x2e, 0x96, 0x21, 0x07, 0x14, 0x43, 0x94, 0xc1, 0x91, 0x05, 0x11, 0x07,
0xe3, 0xcc, 0xc5, 0xfe, 0x93, 0x06, 0xf0, 0xff, 0x14, 0xc1, 0x82, 0x80, // NOTE: No readback!
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
/*
0x23, 0xa0, 0x05, 0x00, 0x13, 0x07, 0x45, 0x03, 0x0c, 0x43, 0x50, 0x43,
0x2e, 0x96, 0x21, 0x07, 0x14, 0x43, 0x94, 0xc1, 0x94, 0x41, 0x14, 0xc3, // With readback.
0x91, 0x05, 0x11, 0x07, 0xe3, 0xca, 0xc5, 0xfe, 0x93, 0x06, 0xf0, 0xff,
0x14, 0xc1, 0x82, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 */
};
static const unsigned char write64_flash[] = { // size and address must be aligned by 4.
0x13, 0x07, 0x45, 0x03, 0x0c, 0x43, 0x13, 0x86, 0x05, 0x04, 0x5c, 0x43,
0x8c, 0xc7, 0x14, 0x47, 0x94, 0xc1, 0xb7, 0x06, 0x05, 0x00, 0xd4, 0xc3,
0x94, 0x41, 0x91, 0x05, 0x11, 0x07, 0xe3, 0xc8, 0xc5, 0xfe, 0xc1, 0x66,
0x93, 0x86, 0x06, 0x04, 0xd4, 0xc3, 0xfd, 0x56, 0x14, 0xc1, 0x82, 0x80
};
static const unsigned char half_wise_write_blob[] = { // size and address must be aligned by 2
0x23, 0xa0, 0x05, 0x00, 0x13, 0x07, 0x45, 0x03, 0x0c, 0x43, 0x50, 0x43,
0x2e, 0x96, 0x21, 0x07, 0x16, 0x23, 0x96, 0xa1, 0x96, 0x21, 0x16, 0xa3,
0x89, 0x05, 0x09, 0x07, 0xe3, 0xca, 0xc5, 0xfe, 0x93, 0x06, 0xf0, 0xff,
0x14, 0xc1, 0x82, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char byte_wise_write_blob[] = { // no division requirements.
0x23, 0xa0, 0x05, 0x00, 0x13, 0x07, 0x45, 0x03, 0x0c, 0x43, 0x50, 0x43,
0x2e, 0x96, 0x21, 0x07, 0x14, 0x23, 0x94, 0xa1, 0x94, 0x21, 0x14, 0xa3,
0x85, 0x05, 0x05, 0x07, 0xe3, 0xca, 0xc5, 0xfe, 0x93, 0x06, 0xf0, 0xff,
0x14, 0xc1, 0x82, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// Just set the countdown to 0 to avoid any issues.
// li a3, 0; sw a3, 0(a1); li a3, -1; sw a3, 0(a0); ret;
static const unsigned char halt_wait_blob[] = {
0x81, 0x46, 0x94, 0xc1, 0xfd, 0x56, 0x14, 0xc1, 0x82, 0x80 };
// Set the countdown to -1 to cause main system to execute.
// li a3, -1; sw a3, 0(a1); li a3, -1; sw a3, 0(a0); ret;
//static const unsigned char run_app_blob[] = {
// 0xfd, 0x56, 0x94, 0xc1, 0xfd, 0x56, 0x14, 0xc1, 0x82, 0x80 };
//
// Alternatively, we do it ourselves.
static const unsigned char run_app_blob[] = {
0x37, 0x07, 0x67, 0x45, 0xb7, 0x27, 0x02, 0x40, 0x13, 0x07, 0x37, 0x12,
0x98, 0xd7, 0x37, 0x97, 0xef, 0xcd, 0x13, 0x07, 0xb7, 0x9a, 0x98, 0xd7,
0x23, 0xa6, 0x07, 0x00, 0x13, 0x07, 0x00, 0x08, 0x98, 0xcb, 0xb7, 0xf7,
0x00, 0xe0, 0x37, 0x07, 0x00, 0x80, 0x23, 0xa8, 0xe7, 0xd0, 0x82, 0x80,
};
static void ResetOp( struct B003FunProgrammerStruct * eps )
{
memset( eps->commandbuffer, 0, sizeof( eps->commandbuffer ) );
memcpy( eps->commandbuffer, "\xaa\x00\x00\x00", 4 );
eps->commandplace = 4;
}
static void WriteOp4( struct B003FunProgrammerStruct * eps, uint32_t opsend )
{
int place = eps->commandplace;
int newend = place + 4;
if( newend < sizeof( eps->commandbuffer ) )
{
memcpy( eps->commandbuffer + place, &opsend, 4 );
}
eps->commandplace = newend;
}
static void WriteOpArb( struct B003FunProgrammerStruct * eps, const uint8_t * data, int len )
{
int place = eps->commandplace;
int newend = place + len;
if( newend < sizeof( eps->commandbuffer ) )
{
memcpy( eps->commandbuffer + place, data, len );
}
eps->commandplace = newend;
}
static int CommitOp( struct B003FunProgrammerStruct * eps )
{
int retries = 0;
int r;
uint32_t magic_go = 0x1234abcd;
memcpy( eps->commandbuffer + 124, &magic_go, 4 );
#ifdef DEBUG_B003
{
int i;
printf( "Commit TX: %lu bytes\n", sizeof(eps->commandbuffer) );
for( i = 0; i < sizeof(eps->commandbuffer) ; i++ )
{
printf( "%02x ", eps->commandbuffer[i] );
if( ( i & 0xf ) == 0xf ) printf( "\n" );
}
if( ( i & 0xf ) != 0xf ) printf( "\n" );
}
#endif
resend:
r = hid_send_feature_report( eps->hd, eps->commandbuffer, sizeof(eps->commandbuffer) );
#ifdef DEBUG_B003
printf( "hid_send_feature_report = %d\n", r );
#endif
if( r < 0 )
{
fprintf( stderr, "Warning: Issue with hid_send_feature_report. Retrying\n" );
if( retries++ > 10 )
return r;
else
goto resend;
}
if( eps->prepping_for_erase )
{
usleep(4000);
}
int timeout = 0;
do
{
eps->respbuffer[0] = 0xaa;
r = hid_get_feature_report( eps->hd, eps->respbuffer, sizeof(eps->respbuffer) );
#ifdef DEBUG_B003
{
int i;
printf( "Commit RX: %d bytes\n", r );
for( i = 0; i < r; i++ )
{
printf( "%02x ", eps->respbuffer[i] );
if( ( i & 0xf ) == 0xf ) printf( "\n" );
}
if( ( i & 0xf ) != 0xf ) printf( "\n" );
}
#endif
if( r < 0 )
{
if( retries++ > 10 ) return r;
continue;
}
if( eps->respbuffer[1] == 0xff ) break;
if( timeout++ > 20 )
{
printf( "Error: Timed out waiting for stub to complete\n" );
return -99;
}
} while( 1 );
return 0;
}
static int B003FunFlushLLCommands( void * dev )
{
// All commands are synchronous anyway.
return 0;
}
static int B003FunWaitForDoneOp( void * dev, int ignore )
{
// It's synchronous, so no issue here.
return 0;
}
static int B003FunDelayUS( void * dev, int microseconds )
{
usleep( microseconds );
return 0;
}
// Does not handle erasing
static int InternalB003FunWriteBinaryBlob( void * dev, uint32_t address_to_write_to, uint32_t write_size, const uint8_t * blob )
{
struct B003FunProgrammerStruct * eps = (struct B003FunProgrammerStruct *)dev;
int is_flash = IsAddressFlash( address_to_write_to );
if( ( address_to_write_to & 0x1 ) && write_size > 0 )
{
// Need to do byte-wise writing in front to line up with word alignment.
ResetOp( eps );
WriteOpArb( eps, byte_wise_write_blob, sizeof(byte_wise_write_blob) );
WriteOp4( eps, address_to_write_to ); // Base address to write.
WriteOp4( eps, 1 ); // write 1 bytes.
memcpy( &eps->commandbuffer[60], blob, 1 );
if( CommitOp( eps ) ) return -5;
if( is_flash && memcmp( &eps->commandbuffer[60], blob, 1 ) ) goto verifyfail;
blob++;
write_size --;
address_to_write_to++;
}
if( ( address_to_write_to & 0x2 ) && write_size > 1 )
{
// Need to do byte-wise writing in front to line up with word alignment.
ResetOp( eps );
WriteOpArb( eps, half_wise_write_blob, sizeof(half_wise_write_blob) );
WriteOp4( eps, address_to_write_to ); // Base address to write.
WriteOp4( eps, 2 ); // write 2 bytes.
memcpy( &eps->commandbuffer[60], blob, 2 );
if( CommitOp( eps ) ) return -5;
if( is_flash && memcmp( &eps->commandbuffer[60], blob, 2 ) ) goto verifyfail;
blob += 2;
write_size -= 2;
address_to_write_to+=2;
}
while( write_size > 3 )
{
int to_write_this_time = write_size & (~3);
if( to_write_this_time > 64 ) to_write_this_time = 64;
// Need to do byte-wise writing in front to line up with word alignment.
ResetOp( eps );
WriteOpArb( eps, word_wise_write_blob, sizeof(word_wise_write_blob) );
WriteOp4( eps, address_to_write_to ); // Base address to write.
WriteOp4( eps, to_write_this_time ); // write 4 bytes.
memcpy( &eps->commandbuffer[60], blob, to_write_this_time );
if( CommitOp( eps ) ) return -5;
if( is_flash && memcmp( &eps->commandbuffer[60], blob, to_write_this_time ) ) goto verifyfail;
blob += to_write_this_time;
write_size -= to_write_this_time;
address_to_write_to += to_write_this_time;
}
if( write_size > 1 )
{
// Need to do byte-wise writing in front to line up with word alignment.
ResetOp( eps );
WriteOpArb( eps, half_wise_write_blob, sizeof(half_wise_write_blob) );
WriteOp4( eps, address_to_write_to ); // Base address to write.
WriteOp4( eps, 2 ); // write 2 bytes.
memcpy( &eps->commandbuffer[60], blob, 2 );
if( CommitOp( eps ) ) return -5;
if( is_flash && memcmp( &eps->commandbuffer[60], blob, 2 ) ) goto verifyfail;
blob += 2;
write_size -= 2;
address_to_write_to += 2;
}
if( write_size )
{
// Need to do byte-wise writing in front to line up with word alignment.
ResetOp( eps );
WriteOpArb( eps, byte_wise_write_blob, sizeof(byte_wise_write_blob) );
WriteOp4( eps, address_to_write_to ); // Base address to write.
WriteOp4( eps, 1 ); // write 1 byte.
memcpy( &eps->commandbuffer[60], blob, 1 );
if( CommitOp( eps ) ) return -5;
if( is_flash && memcmp( &eps->commandbuffer[60], blob, 1 ) ) goto verifyfail;
blob += 1;
write_size -= 1;
address_to_write_to+=1;
}
eps->prepping_for_erase = 0;
return 0;
verifyfail:
fprintf( stderr, "Error: Write Binary Blob: %d bytes to %08x\n", write_size, address_to_write_to );
return -6;
}
static int B003FunReadBinaryBlob( void * dev, uint32_t address_to_read_from, uint32_t read_size, uint8_t * blob )
{
struct B003FunProgrammerStruct * eps = (struct B003FunProgrammerStruct *)dev;
#ifdef DEBUG_B003
printf( "Read Binary Blob: %d bytes from %08x\n", read_size, address_to_read_from );
#endif
if( ( address_to_read_from & 0x1 ) && read_size > 0 )
{
// Need to do byte-wise reading in front to line up with word alignment.
ResetOp( eps );
WriteOpArb( eps, byte_wise_read_blob, sizeof(byte_wise_read_blob) );
WriteOp4( eps, address_to_read_from ); // Base address to read.
WriteOp4( eps, 1 ); // Read 1 bytes.
if( CommitOp( eps ) ) return -5;
memcpy( blob, &eps->respbuffer[60], 1 );
blob++;
read_size --;
address_to_read_from++;
}
if( ( address_to_read_from & 0x2 ) && read_size > 1 )
{
// Need to do byte-wise reading in front to line up with word alignment.
ResetOp( eps );
WriteOpArb( eps, half_wise_read_blob, sizeof(half_wise_read_blob) );
WriteOp4( eps, address_to_read_from ); // Base address to read.
WriteOp4( eps, 2 ); // Read 2 bytes.
if( CommitOp( eps ) ) return -5;
memcpy( blob, &eps->respbuffer[60], 2 );
blob += 2;
read_size -= 2;
address_to_read_from+=2;
}
while( read_size > 3 )
{
int to_read_this_time = read_size & (~3);
if( to_read_this_time > 64 ) to_read_this_time = 64;
// Need to do byte-wise reading in front to line up with word alignment.
ResetOp( eps );
WriteOpArb( eps, word_wise_read_blob, sizeof(word_wise_read_blob) );
WriteOp4( eps, address_to_read_from ); // Base address to read.
WriteOp4( eps, to_read_this_time ); // Read 4 bytes.
if( CommitOp( eps ) ) return -5;
memcpy( blob, &eps->respbuffer[60], to_read_this_time );
blob += to_read_this_time;
read_size -= to_read_this_time;
address_to_read_from += to_read_this_time;
}
if( read_size > 1 )
{
// Need to do byte-wise reading in front to line up with word alignment.
ResetOp( eps );
WriteOpArb( eps, half_wise_read_blob, sizeof(half_wise_read_blob) );
WriteOp4( eps, address_to_read_from ); // Base address to read.
WriteOp4( eps, 2 ); // Read 2 bytes.
if( CommitOp( eps ) ) return -5;
memcpy( blob, &eps->respbuffer[60], 2 );
blob += 2;
read_size -= 2;
address_to_read_from += 2;
}
if( read_size )
{
// Need to do byte-wise reading in front to line up with word alignment.
ResetOp( eps );
WriteOpArb( eps, byte_wise_read_blob, sizeof(byte_wise_read_blob) );
WriteOp4( eps, address_to_read_from ); // Base address to read.
WriteOp4( eps, 1 ); // Read 1 byte.
if( CommitOp( eps ) ) return -5;
memcpy( blob, &eps->respbuffer[60], 1 );
blob += 1;
read_size -= 1;
address_to_read_from+=1;
}
return 0;
}
static int InternalB003FunBoot( void * dev )
{
struct B003FunProgrammerStruct * eps = (struct B003FunProgrammerStruct*) dev;
printf( "Booting\n" );
ResetOp( eps );
WriteOpArb( eps, run_app_blob, sizeof(run_app_blob) );
if( CommitOp( eps ) ) return -5;
return 0;
}
static int B003FunSetupInterface( void * dev )
{
struct B003FunProgrammerStruct * eps = (struct B003FunProgrammerStruct*) dev;
printf( "Halting Boot Countdown\n" );
ResetOp( eps );
WriteOpArb( eps, halt_wait_blob, sizeof(halt_wait_blob) );
if( CommitOp( eps ) ) return -5;
return 0;
}
static int B003FunExit( void * dev )
{
return 0;
}
// MUST be 4-byte-aligned.
static int B003FunWriteWord( void * dev, uint32_t address_to_write, uint32_t data )
{
return InternalB003FunWriteBinaryBlob( dev, address_to_write, 4, (uint8_t*)&data );
}
static int B003FunReadWord( void * dev, uint32_t address_to_read, uint32_t * data )
{
return B003FunReadBinaryBlob( dev, address_to_read, 4, (uint8_t*)data );
}
static int B003FunBlockWrite64( void * dev, uint32_t address_to_write, uint8_t * data )
{
struct B003FunProgrammerStruct * eps = (struct B003FunProgrammerStruct*) dev;
struct InternalState * iss = eps->internal;
if( IsAddressFlash( address_to_write ) )
{
if( !iss->flash_unlocked )
{
int rw;
if( ( rw = InternalUnlockFlash( dev, iss ) ) )
return rw;
}
if( !InternalIsMemoryErased( iss, address_to_write ) )
{
if( MCF.Erase( dev, address_to_write, 64, 0 ) )
{
fprintf( stderr, "Error: Failed to erase sector at %08x\n", address_to_write );
return -9;
}
}
// Not actually needed.
MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG ); // (intptr_t)&FLASH->CTLR = 0x40022010
MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG | CR_BUF_RST); // (intptr_t)&FLASH->CTLR = 0x40022010
ResetOp( eps );
WriteOpArb( eps, write64_flash, sizeof(write64_flash) );
WriteOp4( eps, address_to_write ); // Base address to write. @52
WriteOp4( eps, 0x4002200c ); // FLASH STATR base address. @ 56
memcpy( &eps->commandbuffer[60], data, 64 ); // @60
if( MCF.PrepForLongOp ) MCF.PrepForLongOp( dev ); // Give the programmer a headsup this next operation could take a while.
if( CommitOp( eps ) ) return -5;
// This is actually built-in.
// MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG|CR_STRT_Set); // (intptr_t)&FLASH->CTLR = 0x40022010 (actually commit)
}
else
{
return InternalB003FunWriteBinaryBlob( dev, address_to_write, 64, data );
}
return 0;
}
static int B003FunWriteHalfWord( void * dev, uint32_t address_to_write, uint16_t data )
{
return InternalB003FunWriteBinaryBlob( dev, address_to_write, 2, (uint8_t*)&data );
}
static int B003FunReadHalfWord( void * dev, uint32_t address_to_read, uint16_t * data )
{
return B003FunReadBinaryBlob( dev, address_to_read, 2, (uint8_t*)data );
}
static int B003FunWriteByte( void * dev, uint32_t address_to_write, uint8_t data )
{
return InternalB003FunWriteBinaryBlob( dev, address_to_write, 1, &data );
}
static int B003FunReadByte( void * dev, uint32_t address_to_read, uint8_t * data )
{
return B003FunReadBinaryBlob( dev, address_to_read, 1, data );
}
static int B003FunHaltMode( void * dev, int mode )
{
struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
switch ( mode )
{
case HALT_MODE_HALT_BUT_NO_RESET: // Don't reboot.
case HALT_MODE_HALT_AND_RESET: // Reboot and halt
// This programmer is always halted anyway.
break;
case HALT_MODE_REBOOT: // Actually boot?
InternalB003FunBoot( dev );
break;
case HALT_MODE_RESUME:
fprintf( stderr, "Warning: this programmer cannot resume\n" );
// We can't do this.
break;
case HALT_MODE_GO_TO_BOOTLOADER:
fprintf( stderr, "Warning: this programmer is already a bootloader. Can't go into bootloader\n" );
break;
default:
fprintf( stderr, "Error: Unknown halt mode %d\n", mode );
}
iss->processor_in_mode = mode;
return 0;
}
int B003FunPrepForLongOp( void * dev )
{
struct B003FunProgrammerStruct * d = (struct B003FunProgrammerStruct*)dev;
d->prepping_for_erase = 1;
return 0;
}
void * TryInit_B003Fun()
{
#define VID 0x1209
#define PID 0xb003
hid_init();
hid_device * hd = hid_open( VID, PID, 0); // third parameter is "serial"
if( !hd ) return 0;
//extern int g_hidapiSuppress;
//g_hidapiSuppress = 1; // Suppress errors for this device. (don't do this yet)
struct B003FunProgrammerStruct * eps = malloc( sizeof( struct B003FunProgrammerStruct ) );
memset( eps, 0, sizeof( *eps ) );
eps->hd = hd;
eps->commandplace = 1;
memset( &MCF, 0, sizeof( MCF ) );
MCF.WriteReg32 = 0;
MCF.ReadReg32 = 0;
MCF.FlushLLCommands = B003FunFlushLLCommands;
MCF.DelayUS = B003FunDelayUS;
MCF.Control3v3 = 0;
MCF.SetupInterface = B003FunSetupInterface;
MCF.Exit = B003FunExit;
MCF.HaltMode = 0;
MCF.VoidHighLevelState = 0;
MCF.PollTerminal = 0;
// These are optional. Disabling these is a good mechanismto make sure the core functions still work.
MCF.WriteWord = B003FunWriteWord;
MCF.ReadWord = B003FunReadWord;
MCF.WriteHalfWord = B003FunWriteHalfWord;
MCF.ReadHalfWord = B003FunReadHalfWord;
MCF.WriteByte = B003FunWriteByte;
MCF.ReadByte = B003FunReadByte;
MCF.WaitForDoneOp = B003FunWaitForDoneOp;
MCF.BlockWrite64 = B003FunBlockWrite64;
MCF.ReadBinaryBlob = B003FunReadBinaryBlob;
MCF.PrepForLongOp = B003FunPrepForLongOp;
MCF.HaltMode = B003FunHaltMode;
return eps;
}
// Utility for generating bootloader code:
// make rv003usb.bin && xxd -i -s 100 -l 44 rv003usb.bin
/*
// Read data, arbitrarily from memory. (byte-wise)
. = 0x66
sw x0, 0(a1); // Stop Countdown
addi a4, a0, 52; // Start reading properties, starting from scratchpad + 52.
c.lw a1, 0(a4); // Get starting address to read
c.lw a2, 4(a4); // Get length to read.
c.add a2, a1 // a2 is now ending address.
c.addi a4, 8 // start writing back at byte 60.
1:
XW_C_LBU(a3, a1, 0); //lbu a3, 0(a1) // Read from RAM
XW_C_SB(a3, a4, 0); //sb a3, 0(a4) // Store into scratchpad
c.addi a1, 1 // Advance pointers
c.addi a4, 1
blt a1, a2, 1b // Loop til all read.
addi a3, x0, -1
sw a3, 0(a0) // Write -1 into 0x00 indicating all done.
ret
.long 0,0,0,0,0,0,0
*/
/*
// Read data, arbitrarily from memory. (half-wise)
. = 0x66
sw x0, 0(a1); // Stop Countdown
addi a4, a0, 52; // Start reading properties, starting from scratchpad + 52.
c.lw a1, 0(a4); // Get starting address to read
c.lw a2, 4(a4); // Get length to read.
c.add a2, a1 // a2 is now ending address.
c.addi a4, 8 // start writing back at byte 60.
1:
XW_C_LHU(a3, a1, 0); //lhu a3, 0(a1) // Read from RAM
XW_C_SH(a3, a4, 0); //sh a3, 0(a4) // Store into scratchpad
c.addi a1, 2 // Advance pointers
c.addi a4, 2
blt a1, a2, 1b // Loop til all read.
addi a3, x0, -1
sw a3, 0(a0) // Write -1 into 0x00 indicating all done.
ret
.long 0,0,0,0,0,0,0
*/
/*
// Read data, arbitrarily from memory. (word-wise)
. = 0x66
sw x0, 0(a1); // Stop Countdown
addi a4, a0, 52; // Start reading properties, starting from scratchpad + 52.
c.lw a1, 0(a4); // Get starting address to read
c.lw a2, 4(a4); // Get length to read.
c.add a2, a1 // a2 is now ending address.
c.addi a4, 8 // start writing back at byte 60.
1:
lw a3, 0(a1); //lw a3, 0(a1) // Read from RAM
sw a3, 0(a4); //sw a3, 0(a4) // Store into scratchpad
c.addi a1, 4 // Advance pointers
c.addi a4, 4
blt a1, a2, 1b // Loop til all read.
addi a3, x0, -1
sw a3, 0(a0) // Write -1 into 0x00 indicating all done.
ret
.long 0,0,0,0,0,0,0
*/
/*
// Write data, arbitrarily to memory. (word-wise)
. = 0x66
sw x0, 0(a1); // Stop Countdown
addi a4, a0, 52; // Start reading properties, starting from scratchpad + 52.
c.lw a1, 0(a4); // Get starting address to read
c.lw a2, 4(a4); // Get length to read.
c.add a2, a1 // a2 is now ending address.
c.addi a4, 8 // start writing back at byte 60.
1:
lw a3, 0(a4); //lw a3, 0(a1) // Read from RAM
sw a3, 0(a1); //sw a3, 0(a4) // Store into scratchpad
lw a3, 0(a1); // Read-back
sw a3, 0(a4);
c.addi a1, 4 // Advance pointers
c.addi a4, 4
blt a1, a2, 1b // Loop til all read.
addi a3, x0, -1
sw a3, 0(a0) // Write -1 into 0x00 indicating all done.
ret
.long 0,0,0,0,0,0
*/
/*
// Write data, arbitrarily to memory. (word-wise)
. = 0x66
sw x0, 0(a1); // Stop Countdown
addi a4, a0, 52; // Start reading properties, starting from scratchpad + 52.
c.lw a1, 0(a4); // Get starting address to read
c.lw a2, 4(a4); // Get length to read.
c.add a2, a1 // a2 is now ending address.
c.addi a4, 8 // start writing back at byte 60.
1:
XW_C_LHU(a3, a4, 0); //lbu a3, 0(a4) // Read from scratchpad
XW_C_SH(a3, a1, 0); //sb a3, 0(a1) // Store into RAM
XW_C_LHU(a3, a1, 0); //lbu a3, 0(a4) // Read back
XW_C_SH(a3, a4, 0); //sb a3, 0(a1)
c.addi a1, 2 // Advance pointers
c.addi a4, 2
blt a1, a2, 1b // Loop til all read.
addi a3, x0, -1
sw a3, 0(a0) // Write -1 into 0x00 indicating all done.
ret
.long 0,0,0,0,0,0
*/
/*
// Write data, arbitrarily to memory. (byte-wise)
. = 0x66
sw x0, 0(a1); // Stop Countdown
addi a4, a0, 52; // Start reading properties, starting from scratchpad + 52.
c.lw a1, 0(a4); // Get starting address to read
c.lw a2, 4(a4); // Get length to read.
c.add a2, a1 // a2 is now ending address.
c.addi a4, 8 // start writing back at byte 60.
1:
XW_C_LBU(a3, a4, 0); //lbu a3, 0(a4) // Read from scratchpad
XW_C_SB(a3, a1, 0); //sb a3, 0(a1) // Store into RAM
XW_C_LBU(a3, a1, 0); //Read back
XW_C_SB(a3, a4, 0);
c.addi a1, 1 // Advance pointers
c.addi a4, 1
blt a1, a2, 1b // Loop til all read.
addi a3, x0, -1
sw a3, 0(a0) // Write -1 into 0x00 indicating all done.
ret
.long 0,0,0,0,0,0
*/
/* Run app blob
FLASH->BOOT_MODEKEYR = FLASH_KEY1;
FLASH->BOOT_MODEKEYR = FLASH_KEY2;
FLASH->STATR = 0; // 1<<14 is zero, so, boot user code.
FLASH->CTLR = CR_LOCK_Set;
PFIC->SCTLR = 1<<31;
*/
/* Write flash block 64.
. = 0x66
addi a4, a0, 52; // Start reading properties, starting from scratchpad + 52.
c.lw a1, 0(a4); // a1 = Address to write to.
addi a2, a1, 64 // a2 = end of section to write to
c.lw a5, 4(a4); // a5 = Get flash address (0x40022010)
// Must be done outside.
// li a3, 0x00080000 | 0x00010000;
//c.sw a3, 0(a5); //FLASH->CTLR = CR_BUF_RST | CR_PAGE_PG
c.sw a1, 8(a5); //FLASH->ADDR = writing location.
1:
c.lw a3, 8(a4); //lw a3, 0(a1) // Read from RAM (Starting @60)
c.sw a3, 0(a1); //sw a3, 0(a4) // Store into flash
li a3, 0x00010000 | 0x00040000; // CR_PAGE_PG | FLASH_CTLR_BUF_LOAD
c.sw a3, 4(a5); // Load into flash write buffer.
c.lw a3, 0(a1); //Tricky: By reading from flash here, we force it to wait for completion.
c.addi a1, 4 // Advance pointers
c.addi a4, 4
// // Wait for write to complete.
// 2: c.lw a3, 0(a5) // read FLASH->STATR
// c.andi a3, 1 // Mask off BUSY bit.
// c.bnez a3, 2b
blt a1, a2, 1b // Loop til all read.
li a3, 0x00010000 | 0x00000040 //CR_PAGE_PG|CR_STRT_Set
c.sw a3, 4(a5); //FLASH->CTRL = CR_PAGE_PG|CR_STRT_Set
li a3, -1
c.sw a3, 0(a0) // Write -1 into 0x00 indicating all done.
ret
*/

View file

@ -0,0 +1,456 @@
#include <stdint.h>
#include "hidapi.c"
#include "minichlink.h"
struct ESP32ProgrammerStruct
{
void * internal;
hid_device * hd;
uint32_t state;
uint8_t commandbuffer[256];
int commandplace;
uint8_t reply[256];
int replylen;
int dev_version;
};
int ESPFlushLLCommands( void * dev );
static inline int SRemain( struct ESP32ProgrammerStruct * e )
{
return sizeof( e->commandbuffer ) - e->commandplace - 2; //Need room for EOF.
}
static inline void Write4LE( struct ESP32ProgrammerStruct * e, uint32_t val )
{
if( SRemain( e ) < 4 ) return;
uint8_t * d = e->commandbuffer + e->commandplace;
d[0] = val & 0xff;
d[1] = (val>>8) & 0xff;
d[2] = (val>>16) & 0xff;
d[3] = (val>>24) & 0xff;
e->commandplace += 4;
}
static inline void Write2LE( struct ESP32ProgrammerStruct * e, uint16_t val )
{
if( SRemain( e ) < 2 ) return;
uint8_t * d = e->commandbuffer + e->commandplace;
d[0] = val & 0xff;
d[1] = (val>>8) & 0xff;
e->commandplace += 2;
}
static inline void Write1( struct ESP32ProgrammerStruct * e, uint8_t val )
{
if( SRemain( e ) < 1 ) return;
uint8_t * d = e->commandbuffer + e->commandplace;
d[0] = val & 0xff;
e->commandplace ++;
}
static int ESPWriteReg32( void * dev, uint8_t reg_7_bit, uint32_t value )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
// printf( "WriteReg: %02x -> %08x\n", reg_7_bit, value );
if( SRemain( eps ) < 5 ) ESPFlushLLCommands( eps );
Write1( eps, (reg_7_bit<<1) | 1 );
Write4LE( eps, value );
return 0;
}
int ESPReadReg32( void * dev, uint8_t reg_7_bit, uint32_t * commandresp )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
ESPFlushLLCommands( eps );
Write1( eps, (reg_7_bit<<1) | 0 );
ESPFlushLLCommands( eps );
// printf( "ReadReg: %02x -> %d\n", reg_7_bit,eps->replylen );
if( eps->replylen < 6 )
{
return -9;
}
else
{
memcpy( commandresp, eps->reply+2, 4 );
return 0;
}
}
int ESPFlushLLCommands( void * dev )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
if( eps->commandplace >= sizeof( eps->commandbuffer ) )
{
fprintf( stderr, "Error: Command buffer overflow\n" );
return -5;
}
if( eps->commandplace == 1 ) return 0;
int r;
eps->commandbuffer[0] = 0xad; // Key report ID
eps->commandbuffer[eps->commandplace] = 0xff;
#if 0
int i;
for( i = 0; i < eps->commandplace; i++ )
{
if( ( i & 0xff ) == 0 ) printf( "\n" );
printf( "%02x ", eps->commandbuffer[i] );
}
printf("\n" );
#endif
r = hid_send_feature_report( eps->hd, eps->commandbuffer, 255 );
eps->commandplace = 1;
if( r < 0 )
{
fprintf( stderr, "Error: Got error %d when sending hid feature report.\n", r );
exit( -9 );
}
retry:
eps->reply[0] = 0xad; // Key report ID
r = hid_get_feature_report( eps->hd, eps->reply, sizeof( eps->reply ) );
/*
int i;
printf( "RESP: %d\n",eps->reply[0] );
for( i = 0; i < eps->reply[0]; i++ )
{
printf( "%02x ", eps->reply[i+1] );
if( (i % 16) == 15 ) printf( "\n" );
}
printf( "\n" );*/
if( eps->reply[0] == 0xff ) goto retry;
//printf( ">:::%d: %02x %02x %02x %02x %02x %02x\n", eps->replylen, eps->reply[0], eps->reply[1], eps->reply[2], eps->reply[3], eps->reply[4], eps->reply[5] );
if( r < 0 )
{
fprintf( stderr, "Error: Got error %d when sending hid feature report.\n", r );
return r;
}
eps->replylen = eps->reply[0] + 1; // Include the header byte.
return r;
}
int ESPControl3v3( void * dev, int bOn )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
if( SRemain( eps ) < 2 )
ESPFlushLLCommands( eps );
if( bOn )
Write2LE( eps, 0x03fe );
else
Write2LE( eps, 0x02fe );
return 0;
}
int ESPReadWord( void * dev, uint32_t address_to_read, uint32_t * data )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
//printf( "READ: %08x\n", address_to_read );
if( SRemain( eps ) < 6 )
ESPFlushLLCommands( eps );
Write2LE( eps, 0x09fe );
Write4LE( eps, address_to_read );
ESPFlushLLCommands( eps );
// printf( "Got: %d\n", eps->replylen );
if( eps->replylen < 5 )
{
return -9;
}
int tail = eps->replylen-5;
memcpy( data, eps->reply + tail + 1, 4 );
// printf( "Read Mem: %08x => %08x\n", address_to_read, *data );
return eps->reply[tail];
}
int ESPWriteWord( void * dev, uint32_t address_to_write, uint32_t data )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
//printf( "WRITE: %08x\n", address_to_write );
if( SRemain( eps ) < 10 )
ESPFlushLLCommands( eps );
Write2LE( eps, 0x08fe );
Write4LE( eps, address_to_write );
Write4LE( eps, data );
return 0;
}
static int ESPDelayUS( void * dev, int microseconds )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
if( SRemain( eps ) < 6 )
ESPFlushLLCommands( eps );
Write2LE( eps, 0x04fe );
Write2LE( eps, microseconds );
return 0;
}
static int ESPWaitForFlash( void * dev )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
if( SRemain( eps ) < 2 )
ESPFlushLLCommands( eps );
Write2LE( eps, 0x06fe );
return 0;
}
static int ESPWaitForDoneOp( void * dev, int ignore )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
if( SRemain( eps ) < 2 )
ESPFlushLLCommands( dev );
Write2LE( eps, 0x07fe );
return 0;
}
int ESPExit( void * dev )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
hid_close( eps->hd );
free( eps );
return 0;
}
int ESPBlockWrite64( void * dev, uint32_t address_to_write, uint8_t * data )
{
int writeretry = 0;
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
ESPFlushLLCommands( dev );
retry:
if( eps->dev_version >= 2 && InternalIsMemoryErased( (struct InternalState*)eps->internal, address_to_write ) )
Write2LE( eps, 0x0efe );
else
Write2LE( eps, 0x0bfe );
Write4LE( eps, address_to_write );
int i;
int timeout = 0;
for( i = 0; i < 64; i++ ) Write1( eps, data[i] );
InternalMarkMemoryNotErased( (struct InternalState*)eps->internal, address_to_write );
do
{
ESPFlushLLCommands( dev );
timeout++;
if( timeout > 1000 )
{
fprintf( stderr, "Error: Timed out block-writing 64\n" );
return -49;
}
} while( eps->replylen < 2 );
if( eps->reply[1] )
{
fprintf( stderr, "Error: Got code %d from ESP write algo. %d [%02x %02x %02x]\n", (char)eps->reply[1], eps->replylen, eps->reply[0], eps->reply[1], eps->reply[2] );
if( writeretry < 10 )
{
writeretry++;
goto retry;
}
}
return (char)eps->reply[1];
}
int ESPPerformSongAndDance( void * dev )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
Write2LE( eps, 0x01fe );
ESPFlushLLCommands( dev );
return 0;
}
int ESPVoidHighLevelState( void * dev )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
Write2LE( eps, 0x05fe );
ESPFlushLLCommands( dev );
DefaultVoidHighLevelState( dev );
return 0;
}
int ESPVendorCommand( void * dev, const char * cmd )
{
char command[10] = { 0 };
char tbuf[10] = { 0 };
int fields[10];
char c;
int i = 0;
int f = 0;
while( (c = *cmd++) )
{
if( c == ':' ) break;
if( c == '\0' ) break;
if( i + 1 >= sizeof( command )) break;
command[i++] = c;
command[i] = 0;
}
i = 0;
f = 0;
while( 1 )
{
c = *cmd++;
if( c == ':' || c == '\0' )
{
fields[f++] = SimpleReadNumberInt( tbuf, 0 );
puts( tbuf );
if( f == 10 ) break;
tbuf[0] = 0;
i = 0;
if( c == '\0' ) break;
continue;
}
if( i + 1 >= sizeof( tbuf )) break;
tbuf[i++] = c;
tbuf[i] = 0;
}
printf( "Got Vendor Command \"%s\"\n", command );
ESPFlushLLCommands( dev );
if( strcasecmp( command, "ECLK" ) == 0 )
{
printf( "Setting up external clock on pin.\n" );
if( f < 5 )
{
fprintf( stderr, "Error: Need fields :use_apll:sdm0:sdm1:sdm2:odiv try 1:0:0:8:3 for 24MHz\n" );
fprintf( stderr, "Definition:\n\
use_apll = Configures APLL = 480 / 4 = 120\n\
40 * (SDM2 + SDM1/(2^8) + SDM0/(2^16) + 4) / ( 2 * (ODIV+2) );\n\
Datasheet recommends that numerator is between 300 and 500MHz.\n ");
return -9;
}
Write2LE( dev, 0x0cfe );
Write1( dev, fields[0] );
Write1( dev, fields[1] );
Write1( dev, fields[2] );
Write1( dev, fields[3] );
Write1( dev, fields[4] );
Write1( dev, 0 );
Write1( dev, 0 );
Write1( dev, 0 );
ESPFlushLLCommands( dev );
}
else
{
fprintf( stderr, "Error: Unknown vendor command %s\n", command );
}
return 0;
}
int ESPPollTerminal( void * dev, uint8_t * buffer, int maxlen, uint32_t leaveflagA, int leaveflagB )
{
struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
ESPFlushLLCommands( dev );
Write1( dev, 0xfe );
Write1( dev, 0x0d );
Write4LE( dev, leaveflagA );
Write4LE( dev, leaveflagB );
Write1( dev, 0xff );
ESPFlushLLCommands( dev );
int rlen = eps->reply[0];
if( rlen < 1 ) return -8;
#if 0
int i;
printf( "RESP (ML %d): %d\n", maxlen,eps->reply[0] );
for( i = 0; i < eps->reply[0]; i++ )
{
printf( "%02x ", eps->reply[i+1] );
if( (i % 16) == 15 ) printf( "\n" );
}
printf( "\n" );
#endif
int errc = eps->reply[1];
if( errc > 7 ) return -7;
if( rlen - 1 >= maxlen ) return -6;
memcpy( buffer, eps->reply + 2, rlen - 1 );
return rlen - 1;
}
void * TryInit_ESP32S2CHFUN()
{
#define VID 0x303a
#define PID 0x4004
hid_init();
hid_device * hd = hid_open( VID, PID, L"s2-ch32xx-pgm-v0"); // third parameter is "serial"
if( !hd ) return 0;
struct ESP32ProgrammerStruct * eps = malloc( sizeof( struct ESP32ProgrammerStruct ) );
memset( eps, 0, sizeof( *eps ) );
eps->hd = hd;
eps->commandplace = 1;
eps->dev_version = 0;
memset( &MCF, 0, sizeof( MCF ) );
MCF.WriteReg32 = ESPWriteReg32;
MCF.ReadReg32 = ESPReadReg32;
MCF.FlushLLCommands = ESPFlushLLCommands;
MCF.DelayUS = ESPDelayUS;
MCF.Control3v3 = ESPControl3v3;
MCF.Exit = ESPExit;
MCF.VoidHighLevelState = ESPVoidHighLevelState;
MCF.PollTerminal = ESPPollTerminal;
// These are optional. Disabling these is a good mechanismto make sure the core functions still work.
MCF.WriteWord = ESPWriteWord;
MCF.ReadWord = ESPReadWord;
MCF.WaitForFlash = ESPWaitForFlash;
MCF.WaitForDoneOp = ESPWaitForDoneOp;
MCF.PerformSongAndDance = ESPPerformSongAndDance;
MCF.BlockWrite64 = ESPBlockWrite64;
MCF.VendorCommand = ESPVendorCommand;
// Reset internal programmer state.
Write2LE( eps, 0x0afe );
ESPFlushLLCommands( eps );
Write2LE( eps, 0xfefe );
ESPFlushLLCommands( eps );
if( eps->replylen > 1 )
{
eps->dev_version = eps->reply[1];
}
return eps;
}

786
minichlink/pgm-wch-linke.c Normal file
View file

@ -0,0 +1,786 @@
// The "bootloader" blob is (C) WCH.
// Tricky: You need to use wch link to use WCH-LinkRV.
// you can always uninstall it in device manager. It will be under USB devices or something like that at the bottom.
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "libusb.h"
#include "minichlink.h"
struct LinkEProgrammerStruct
{
void * internal;
libusb_device_handle * devh;
int lasthaltmode; // For non-003 chips
};
static void printChipInfo(enum RiscVChip chip) {
switch(chip) {
case CHIP_CH32V10x:
fprintf(stderr, "Detected: CH32V10x\n");
break;
case CHIP_CH57x:
fprintf(stderr, "Detected: CH57x\n");
break;
case CHIP_CH56x:
fprintf(stderr, "Detected: CH56x\n");
break;
case CHIP_CH32V20x:
fprintf(stderr, "Detected: CH32V20x\n");
break;
case CHIP_CH32V30x:
fprintf(stderr, "Detected: CH32V30x\n");
break;
case CHIP_CH58x:
fprintf(stderr, "Detected: CH58x\n");
break;
case CHIP_CH32V003:
fprintf(stderr, "Detected: CH32V003\n");
break;
}
}
static int checkChip(enum RiscVChip chip) {
switch(chip) {
case CHIP_CH32V003:
return 0; // Use direct mode
case CHIP_CH32V10x:
case CHIP_CH32V20x:
case CHIP_CH32V30x:
return 1; // Use binary blob mode
case CHIP_CH56x:
case CHIP_CH57x:
case CHIP_CH58x:
default:
return -1; // Not supported yet
}
}
// For non-ch32v003 chips.
//static int LEReadBinaryBlob( void * d, uint32_t offset, uint32_t amount, uint8_t * readbuff );
static int InternalLinkEHaltMode( void * d, int mode );
static int LEWriteBinaryBlob( void * d, uint32_t address_to_write, uint32_t len, uint8_t * blob );
#define WCHTIMEOUT 5000
#define WCHCHECK(x) if( (status = x) ) { fprintf( stderr, "Bad USB Operation on " __FILE__ ":%d (%d)\n", __LINE__, status ); exit( status ); }
void wch_link_command( libusb_device_handle * devh, const void * command_v, int commandlen, int * transferred, uint8_t * reply, int replymax )
{
uint8_t * command = (uint8_t*)command_v;
uint8_t buffer[1024];
int got_to_recv = 0;
int status;
int transferred_local;
if( !transferred ) transferred = &transferred_local;
status = libusb_bulk_transfer( devh, 0x01, command, commandlen, transferred, WCHTIMEOUT );
if( status ) goto sendfail;
got_to_recv = 1;
if( !reply )
{
reply = buffer; replymax = sizeof( buffer );
}
// printf("wch_link_command send (%d)", commandlen); for(int i = 0; i< commandlen; printf(" %02x",command[i++])); printf("\n");
status = libusb_bulk_transfer( devh, 0x81, reply, replymax, transferred, WCHTIMEOUT );
// printf("wch_link_command reply (%d)", *transferred); for(int i = 0; i< *transferred; printf(" %02x",reply[i++])); printf("\n");
if( status ) goto sendfail;
return;
sendfail:
fprintf( stderr, "Error sending WCH command (%s): ", got_to_recv?"on recv":"on send" );
int i;
for( i = 0; i < commandlen; i++ )
{
printf( "%02x ", command[i] );
}
printf( "\n" );
exit( status );
}
static void wch_link_multicommands( libusb_device_handle * devh, int nrcommands, ... )
{
int i;
va_list argp;
va_start(argp, nrcommands);
for( i = 0; i < nrcommands; i++ )
{
int clen = va_arg(argp, int);
wch_link_command( devh, va_arg(argp, char *), clen, 0, 0, 0 );
}
va_end( argp );
}
static inline libusb_device_handle * wch_link_base_setup( int inhibit_startup )
{
libusb_context * ctx = 0;
int status;
status = libusb_init(&ctx);
if (status < 0) {
fprintf( stderr, "Error: libusb_init_context() returned %d\n", status );
exit( status );
}
libusb_device **list;
ssize_t cnt = libusb_get_device_list(ctx, &list);
ssize_t i = 0;
libusb_device *found = NULL;
libusb_device * found_arm_programmer = NULL;
libusb_device * found_programmer_in_iap = NULL;
for (i = 0; i < cnt; i++) {
libusb_device *device = list[i];
struct libusb_device_descriptor desc;
int r = libusb_get_device_descriptor(device,&desc);
if( r == 0 && desc.idVendor == 0x1a86 && desc.idProduct == 0x8010 ) { found = device; }
if( r == 0 && desc.idVendor == 0x1a86 && desc.idProduct == 0x8012) { found_arm_programmer = device; }
if( r == 0 && desc.idVendor == 0x4348 && desc.idProduct == 0x55e0) { found_programmer_in_iap = device; }
}
if( !found )
{
// On a lark see if we have a programmer which got stuck in IAP mode.
if (found_arm_programmer) {
fprintf( stderr, "Warning: found at least one WCH-LinkE in ARM programming mode. Attempting automatic switch to RISC-V. Will need a to re-attempt.\n" );
fprintf( stderr, "For more information, you may need to change it to RISC-V mode as per https://github.com/cnlohr/ch32v003fun/issues/227\n" );
// Just in case we got stuck in IAP mode, try sending 0x83 to eject.
libusb_device_handle * devh = 0;
status = libusb_open( found_arm_programmer, &devh );
if( status )
{
fprintf( stderr, "Found programmer in ARM mode, but couldn't open it.\n" );
exit( -10 );
}
// https://github.com/wagiminator/MCU-Flash-Tools/blob/main/rvmode.py
uint8_t rbuff[4] = { 0x81, 0xff, 0x01, 0x52 };
int transferred = 0;
libusb_bulk_transfer( devh, 0x02, rbuff, 4, &transferred, 1 );
fprintf( stderr, "RISC-V command sent (%d)\n", transferred );
exit( -3 );
}
if( found_programmer_in_iap )
{
// Just in case we got stuck in IAP mode, try sending 0x83 to eject.
fprintf( stderr, "Found programmer in IAP mode. Attempting to eject it out of IAP.\n" );
libusb_device_handle * devh = 0;
status = libusb_open( found_programmer_in_iap, &devh );
if( status )
{
fprintf( stderr, "Found programmer in IAP mode, but couldn't open it.\n" );
exit( -10 );
}
uint8_t rbuff[4];
int transferred = 0;
rbuff[0] = 0x83;
libusb_bulk_transfer( devh, 0x02, rbuff, 1, &transferred, 1 );
fprintf( stderr, "Eject command sent (%d)\n", transferred );
exit( -3 );
}
return 0;
}
libusb_device_handle * devh;
status = libusb_open( found, &devh );
if( status )
{
fprintf( stderr, "Error: couldn't open wch link device (libusb_open() = %d)\n", status );
return 0;
}
WCHCHECK( libusb_claim_interface(devh, 0) );
uint8_t rbuff[1024];
int transferred;
libusb_bulk_transfer( devh, 0x81, rbuff, 1024, &transferred, 1 ); // Clear out any pending transfers. Don't wait though.
return devh;
}
// DMI_OP decyphered From https://github.com/karlp/openocd-hacks/blob/27af153d4a373f29ad93dab28a01baffb7894363/src/jtag/drivers/wlink.c
// Thanks, CW2 for pointing this out. See DMI_OP for more info.
int LEWriteReg32( void * dev, uint8_t reg_7_bit, uint32_t command )
{
libusb_device_handle * devh = ((struct LinkEProgrammerStruct*)dev)->devh;
const uint8_t iOP = 2; // op 2 = write
uint8_t req[] = {
0x81, 0x08, 0x06, reg_7_bit,
(command >> 24) & 0xff,
(command >> 16) & 0xff,
(command >> 8) & 0xff,
(command >> 0) & 0xff,
iOP };
uint8_t resp[128];
int resplen;
wch_link_command( devh, req, sizeof(req), &resplen, resp, sizeof(resp) );
if( resplen != 9 || resp[8] == 0x02 || resp[8] == 0x03 ) //|| resp[3] != reg_7_bit )
{
fprintf( stderr, "Error setting write reg. Tell cnlohr. Maybe we should allow retries here?\n" );
fprintf( stderr, "RR: %d :", resplen );
int i;
for( i = 0; i < resplen; i++ )
{
fprintf( stderr, "%02x ", resp[i] );
}
fprintf( stderr, "\n" );
}
return 0;
}
int LEReadReg32( void * dev, uint8_t reg_7_bit, uint32_t * commandresp )
{
libusb_device_handle * devh = ((struct LinkEProgrammerStruct*)dev)->devh;
const uint8_t iOP = 1; // op 1 = read
uint32_t transferred;
uint8_t rbuff[128] = { 0 };
uint8_t req[] = {
0x81, 0x08, 0x06, reg_7_bit,
0, 0, 0, 0,
iOP };
wch_link_command( devh, req, sizeof( req ), (int*)&transferred, rbuff, sizeof( rbuff ) );
*commandresp = ( rbuff[4]<<24 ) | (rbuff[5]<<16) | (rbuff[6]<<8) | (rbuff[7]<<0);
if( transferred != 9 || rbuff[8] == 0x02 || rbuff[8] == 0x03 ) //|| rbuff[3] != reg_7_bit )
{
fprintf( stderr, "Error setting write reg. Tell cnlohr. Maybe we should allow retries here?\n" );
fprintf( stderr, "RR: %d :", transferred );
int i;
for( i = 0; i < transferred; i++ )
{
fprintf( stderr, "%02x ", rbuff[i] );
}
fprintf( stderr, "\n" );
}
/*
printf( "RR: %d :", transferred );
int i;
for( i = 0; i < transferred; i++ )
{
printf( "%02x ", rbuff[i] );
}
printf( "\n" );
*/
return 0;
}
int LEFlushLLCommands( void * dev )
{
return 0;
}
static int LESetupInterface( void * d )
{
libusb_device_handle * dev = ((struct LinkEProgrammerStruct*)d)->devh;
struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)d)->internal);
uint8_t rbuff[1024];
uint32_t transferred = 0;
// This puts the processor on hold to allow the debugger to run.
wch_link_command( dev, "\x81\x0d\x01\x03", 4, (int*)&transferred, rbuff, 1024 ); // Reply: Ignored, 820d050900300500
// Place part into reset.
wch_link_command( dev, "\x81\x0d\x01\x01", 4, (int*)&transferred, rbuff, 1024 ); // Reply is: "\x82\x0d\x04\x02\x08\x02\x00"
switch(rbuff[5]) {
case 1:
fprintf(stderr, "WCH Programmer is CH549 version %d.%d\n",rbuff[3], rbuff[4]);
break;
case 2:
fprintf(stderr, "WCH Programmer is CH32V307 version %d.%d\n",rbuff[3], rbuff[4]);
break;
case 3:
fprintf(stderr, "WCH Programmer is CH32V203 version %d.%d\n",rbuff[3], rbuff[4]);
break;
case 4:
fprintf(stderr, "WCH Programmer is LinkB version %d.%d\n",rbuff[3], rbuff[4]);
break;
case 18:
fprintf(stderr, "WCH Programmer is LinkE version %d.%d\n",rbuff[3], rbuff[4]);
break;
default:
fprintf(stderr, "Unknown WCH Programmer %02x (Ver %d.%d)\n", rbuff[5], rbuff[3], rbuff[4]);
break;
}
// TODO: What in the world is this? It doesn't appear to be needed.
wch_link_command( dev, "\x81\x0c\x02\x09\x01", 5, 0, 0, 0 ); //Reply is: 820c0101
// Note from further debugging:
// My capture differs in this case: \x05 instead of \x09 -> But does not seem to be needed
//wch_link_command( dev, "\x81\x0c\x02\x05\x01", 5, 0, 0, 0 ); //Reply is: 820c0101
// This puts the processor on hold to allow the debugger to run.
int already_tried_reset = 0;
do
{
wch_link_command( dev, "\x81\x0d\x01\x02", 4, (int*)&transferred, rbuff, 1024 ); // Reply: Ignored, 820d050900300500
if (rbuff[0] == 0x81 && rbuff[1] == 0x55 && rbuff[2] == 0x01 ) // && rbuff[3] == 0x01 )
{
// The following code may try to execute a few times to get the processor to actually reset.
// This code could likely be much better.
fprintf(stderr, "link error, nothing connected to linker (%d = [%02x %02x %02x %02x]). Trying to put processor in hold and retrying.\n", transferred, rbuff[0], rbuff[1], rbuff[2], rbuff[3]);
// Give up if too long
if( already_tried_reset > 10 )
return -1;
wch_link_multicommands( (libusb_device_handle *)dev, 1, 4, "\x81\x0d\x01\x13" ); // Try forcing reset line low.
wch_link_command( (libusb_device_handle *)dev, "\x81\x0d\x01\xff", 4, 0, 0, 0); //Exit programming
if( already_tried_reset > 3 )
{
MCF.DelayUS( iss, 5000 );
wch_link_command( dev, "\x81\x0d\x01\x03", 4, (int*)&transferred, rbuff, 1024 ); // Reply: Ignored, 820d050900300500
}
else
{
MCF.DelayUS( iss, 5000 );
}
wch_link_multicommands( (libusb_device_handle *)dev, 1, 4, "\x81\x0d\x01\x14" ); // Release reset line.
wch_link_multicommands( (libusb_device_handle *)dev, 3, 4, "\x81\x0b\x01\x01", 4, "\x81\x0d\x01\x02", 4, "\x81\x0d\x01\xff" );
already_tried_reset++;
}
else
{
break;
}
} while( 1 );
if(rbuff[3] == 0x08 || rbuff[3] > 0x09) {
fprintf( stderr, "Chip Type unknown. Aborting...\n" );
return -1;
}
enum RiscVChip chip = (enum RiscVChip)rbuff[3];
printChipInfo(chip);
int result = checkChip(chip);
if( result == 1 ) // Using blob write
{
fprintf( stderr, "Using binary blob write for operation.\n" );
MCF.WriteBinaryBlob = LEWriteBinaryBlob;
iss->sector_size = 256;
wch_link_command( dev, "\x81\x0d\x01\x03", 4, (int*)&transferred, rbuff, 1024 ); // Reply: Ignored, 820d050900300500
} else if( result < 0 ) {
fprintf( stderr, "Chip type not supported. Aborting...\n" );
return -1;
}
iss->target_chip_type = chip;
// For some reason, if we don't do this sometimes the programmer starts in a hosey mode.
MCF.WriteReg32( d, DMCONTROL, 0x80000001 ); // Make the debug module work properly.
MCF.WriteReg32( d, DMCONTROL, 0x80000001 ); // Initiate a halt request.
MCF.WriteReg32( d, DMCONTROL, 0x80000001 ); // No, really make sure.
MCF.WriteReg32( d, DMABSTRACTCS, 0x00000700 ); // Ignore any pending errors.
MCF.WriteReg32( d, DMABSTRACTAUTO, 0 );
MCF.WriteReg32( d, DMCOMMAND, 0x00221000 ); // Read x0 (Null command) with nopostexec (to fix v307 read issues)
int r = 0;
r |= MCF.WaitForDoneOp( d, 0 );
if( r )
{
fprintf( stderr, "Fault on setup\n" );
}
else
{
fprintf( stderr, "Setup success\n" );
}
// This puts the processor on hold to allow the debugger to run.
// Recommended to switch to 05 from 09 by Alexander M
// wch_link_command( dev, "\x81\x11\x01\x09", 4, (int*)&transferred, rbuff, 1024 ); // Reply: Chip ID + Other data (see below)
wch_link_command( dev, "\x81\x11\x01\x05", 4, (int*)&transferred, rbuff, 1024 ); // Reply: Chip ID + Other data (see below)
if( transferred != 20 )
{
fprintf( stderr, "Error: could not get part status\n" );
return -1;
}
const uint32_t flskb = ((uint32_t)rbuff[2]<<8) | rbuff[3];
fprintf( stderr, "Flash Storage: %d kB\n", flskb ); // Is this Flash size?
fprintf( stderr, "Part UUID : %02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x\n", rbuff[4], rbuff[5], rbuff[6], rbuff[7], rbuff[8], rbuff[9], rbuff[10], rbuff[11] );
fprintf( stderr, "PFlags : %02x-%02x-%02x-%02x\n", rbuff[12], rbuff[13], rbuff[14], rbuff[15] );
fprintf( stderr, "Part Type (B): %02x-%02x-%02x-%02x\n", rbuff[16], rbuff[17], rbuff[18], rbuff[19] );
// Check for read protection
wch_link_command( dev, "\x81\x06\x01\x01", 4, (int*)&transferred, rbuff, 1024 );
if(transferred != 4) {
fprintf(stderr, "Error: could not get read protection status\n");
return -1;
}
if(rbuff[3] == 0x01) {
fprintf(stderr, "Read protection: enabled\n");
} else {
fprintf(stderr, "Read protection: disabled\n");
}
iss->flash_size = flskb * 0x400u;
if (chip == CHIP_CH32V20x) { // diferent version chip
if (flskb > 32u) iss->ram_size = 20u * 0x400u; // 64K
else iss->ram_size = 10u * 0x400u; // 32K
}
return 0;
}
static int LEControl3v3( void * d, int bOn )
{
libusb_device_handle * dev = ((struct LinkEProgrammerStruct*)d)->devh;
if( bOn )
wch_link_command( (libusb_device_handle *)dev, "\x81\x0d\x01\x09", 4, 0, 0, 0 );
else
wch_link_command( (libusb_device_handle *)dev, "\x81\x0d\x01\x0a", 4, 0, 0, 0 );
return 0;
}
static int LEControl5v( void * d, int bOn )
{
libusb_device_handle * dev = ((struct LinkEProgrammerStruct*)d)->devh;
if( bOn )
wch_link_command( (libusb_device_handle *)dev, "\x81\x0d\x01\x0b", 4, 0, 0, 0 );
else
wch_link_command( (libusb_device_handle *)dev, "\x81\x0d\x01\x0c", 4, 0, 0, 0 );
return 0;
}
static int LEUnbrick( void * d )
{
printf( "Sending unbrick\n" );
libusb_device_handle * dev = ((struct LinkEProgrammerStruct*)d)->devh;
wch_link_command( (libusb_device_handle *)dev, "\x81\x0d\x01\x0f\x09", 5, 0, 0, 0 );
printf( "Done unbrick\n" );
return 0;
}
static int LEConfigureNRSTAsGPIO( void * d, int one_if_yes_gpio )
{
libusb_device_handle * dev = ((struct LinkEProgrammerStruct*)d)->devh;
if( one_if_yes_gpio )
{
wch_link_multicommands( (libusb_device_handle *)dev, 2, 11, "\x81\x06\x08\x02\xff\xff\xff\xff\xff\xff\xff", 4, "\x81\x0b\x01\x01" );
}
else
{
wch_link_multicommands( (libusb_device_handle *)dev, 2, 11, "\x81\x06\x08\x02\xf7\xff\xff\xff\xff\xff\xff", 4, "\x81\x0b\x01\x01" );
}
return 0;
}
static int LEConfigureReadProtection( void * d, int one_if_yes_protect )
{
libusb_device_handle * dev = ((struct LinkEProgrammerStruct*)d)->devh;
if( one_if_yes_protect )
{
wch_link_multicommands( (libusb_device_handle *)dev, 2, 11, "\x81\x06\x08\x03\xf7\xff\xff\xff\xff\xff\xff", 4, "\x81\x0b\x01\x01" );
}
else
{
wch_link_multicommands( (libusb_device_handle *)dev, 2, 11, "\x81\x06\x08\x02\xf7\xff\xff\xff\xff\xff\xff", 4, "\x81\x0b\x01\x01" );
}
return 0;
}
int LEExit( void * d )
{
libusb_device_handle * dev = ((struct LinkEProgrammerStruct*)d)->devh;
wch_link_command( (libusb_device_handle *)dev, "\x81\x0d\x01\xff", 4, 0, 0, 0);
return 0;
}
void * TryInit_WCHLinkE()
{
libusb_device_handle * wch_linke_devh;
wch_linke_devh = wch_link_base_setup(0);
if( !wch_linke_devh ) return 0;
struct LinkEProgrammerStruct * ret = malloc( sizeof( struct LinkEProgrammerStruct ) );
memset( ret, 0, sizeof( *ret ) );
ret->devh = wch_linke_devh;
ret->lasthaltmode = 0;
MCF.ReadReg32 = LEReadReg32;
MCF.WriteReg32 = LEWriteReg32;
MCF.FlushLLCommands = LEFlushLLCommands;
MCF.SetupInterface = LESetupInterface;
MCF.Control3v3 = LEControl3v3;
MCF.Control5v = LEControl5v;
MCF.Unbrick = LEUnbrick;
MCF.ConfigureNRSTAsGPIO = LEConfigureNRSTAsGPIO;
MCF.ConfigureReadProtection = LEConfigureReadProtection;
MCF.Exit = LEExit;
return ret;
};
#if 1
// Flash Bootloader for V20x and V30x series MCUs
const uint8_t * bootloader_v1 = (const uint8_t*)
"\x93\x77\x15\x00\x41\x11\x99\xCF\xB7\x06\x67\x45\xB7\x27\x02\x40" \
"\x93\x86\x36\x12\x37\x97\xEF\xCD\xD4\xC3\x13\x07\xB7\x9A\xD8\xC3" \
"\xD4\xD3\xD8\xD3\x93\x77\x25\x00\x9D\xC7\xB7\x27\x02\x40\x98\x4B" \
"\xAD\x66\x37\x38\x00\x40\x13\x67\x47\x00\x98\xCB\x98\x4B\x93\x86" \
"\xA6\xAA\x13\x67\x07\x04\x98\xCB\xD8\x47\x05\x8B\x63\x1F\x07\x10" \
"\x98\x4B\x6D\x9B\x98\xCB\x93\x77\x45\x00\xA9\xCB\x93\x07\xF6\x07" \
"\x9D\x83\x2E\xC0\x2D\x68\x81\x76\x3E\xC4\xB7\x08\x02\x00\xB7\x27" \
"\x02\x40\x37\x33\x00\x40\x13\x08\xA8\xAA\xFD\x16\x98\x4B\x33\x67" \
"\x17\x01\x98\xCB\x02\x47\xD8\xCB\x98\x4B\x13\x67\x07\x04\x98\xCB" \
"\xD8\x47\x05\x8B\x71\xEF\x98\x4B\x75\x8F\x98\xCB\x02\x47\x13\x07" \
"\x07\x08\x3A\xC0\x22\x47\x7D\x17\x3A\xC4\x69\xFB\x93\x77\x85\x00" \
"\xED\xC3\x93\x07\xF6\x07\x2E\xC0\x9D\x83\x37\x27\x02\x40\x3E\xC4" \
"\x1C\x4B\xC1\x66\x37\x08\x08\x00\xD5\x8F\x1C\xCB\xA1\x48\x37\x17" \
"\x00\x20\xB7\x27\x02\x40\x37\x03\x04\x00\x94\x4B\xB3\xE6\x06\x01" \
"\x94\xCB\xD4\x47\x85\x8A\xF5\xFE\x82\x46\x3A\x8E\x36\xC2\x46\xC6" \
"\x92\x46\x83\x2E\x07\x00\x41\x07\x23\xA0\xD6\x01\x92\x46\x83\x2E" \
"\x47\xFF\x23\xA2\xD6\x01\x92\x46\x83\x2E\x87\xFF\x23\xA4\xD6\x01" \
"\x92\x46\x03\x2E\xCE\x00\x23\xA6\xC6\x01\x94\x4B\xB3\xE6\x66\x00" \
"\x94\xCB\xD4\x47\x85\x8A\xF5\xFE\x92\x46\x3A\x8E\xC1\x06\x36\xC2" \
"\xB2\x46\xFD\x16\x36\xC6\xCD\xFE\x82\x46\xD4\xCB\x94\x4B\x93\xE6" \
"\x06\x04\x94\xCB\xD4\x47\x85\x8A\xF5\xFE\xD4\x47\xD1\x8A\x85\xC6" \
"\xD8\x47\xB7\x06\xF3\xFF\xFD\x16\x13\x67\x47\x01\xD8\xC7\x98\x4B" \
"\x21\x45\x75\x8F\x98\xCB\x41\x01\x02\x90\x23\x20\xD8\x00\xE9\xBD" \
"\x23\x20\x03\x01\x31\xBF\x82\x46\x93\x86\x06\x08\x36\xC0\xA2\x46" \
"\xFD\x16\x36\xC4\xB9\xFA\x98\x4B\xB7\x06\xF3\xFF\xFD\x16\x75\x8F" \
"\x98\xCB\x41\x89\x15\xC9\x2E\xC0\x0D\x06\x02\xC4\x09\x82\x32\xC6" \
"\xB7\x17\x00\x20\x98\x43\x13\x86\x47\x00\xA2\x47\x82\x46\x8A\x07" \
"\xB6\x97\x9C\x43\x63\x1C\xF7\x00\xA2\x47\x85\x07\x3E\xC4\xA2\x46" \
"\x32\x47\xB2\x87\xE3\xE0\xE6\xFE\x01\x45\x71\xBF\x41\x45\x61\xBF" \
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
const uint8_t * bootloader_v2 = (const uint8_t*)
"\x93\x77\x15\x00\x41\x11\x99\xcf\xb7\x06\x67\x45\xb7\x27\x02\x40" \
"\x93\x86\x36\x12\x37\x97\xef\xcd\xd4\xc3\x13\x07\xb7\x9a\xd8\xc3" \
"\xd4\xd3\xd8\xd3\x93\x77\x25\x00\x95\xc7\xb7\x27\x02\x40\x98\x4b" \
"\xad\x66\x37\x38\x00\x40\x13\x67\x47\x00\x98\xcb\x98\x4b\x93\x86" \
"\xa6\xaa\x13\x67\x07\x04\x98\xcb\xd8\x47\x05\x8b\x61\xeb\x98\x4b" \
"\x6d\x9b\x98\xcb\x93\x77\x45\x00\xa9\xcb\x93\x07\xf6\x0f\xa1\x83" \
"\x2e\xc0\x2d\x68\x81\x76\x3e\xc4\xb7\x08\x02\x00\xb7\x27\x02\x40" \
"\x37\x33\x00\x40\x13\x08\xa8\xaa\xfd\x16\x98\x4b\x33\x67\x17\x01" \
"\x98\xcb\x02\x47\xd8\xcb\x98\x4b\x13\x67\x07\x04\x98\xcb\xd8\x47" \
"\x05\x8b\x41\xeb\x98\x4b\x75\x8f\x98\xcb\x02\x47\x13\x07\x07\x10" \
"\x3a\xc0\x22\x47\x7d\x17\x3a\xc4\x69\xfb\x93\x77\x85\x00\xd5\xcb" \
"\x93\x07\xf6\x0f\x2e\xc0\xa1\x83\x3e\xc4\x37\x27\x02\x40\x1c\x4b" \
"\xc1\x66\x41\x68\xd5\x8f\x1c\xcb\xb7\x16\x00\x20\xb7\x27\x02\x40" \
"\x93\x08\x00\x04\x37\x03\x20\x00\x98\x4b\x33\x67\x07\x01\x98\xcb" \
"\xd8\x47\x05\x8b\x75\xff\x02\x47\x3a\xc2\x46\xc6\x32\x47\x0d\xef" \
"\x98\x4b\x33\x67\x67\x00\x98\xcb\xd8\x47\x05\x8b\x75\xff\xd8\x47" \
"\x41\x8b\x39\xc3\xd8\x47\xc1\x76\xfd\x16\x13\x67\x07\x01\xd8\xc7" \
"\x98\x4b\x21\x45\x75\x8f\x98\xcb\x41\x01\x02\x90\x23\x20\xd8\x00" \
"\x25\xb7\x23\x20\x03\x01\xa5\xb7\x12\x47\x13\x8e\x46\x00\x94\x42" \
"\x14\xc3\x12\x47\x11\x07\x3a\xc2\x32\x47\x7d\x17\x3a\xc6\xd8\x47" \
"\x09\x8b\x75\xff\xf2\x86\x5d\xb7\x02\x47\x13\x07\x07\x10\x3a\xc0" \
"\x22\x47\x7d\x17\x3a\xc4\x49\xf3\x98\x4b\xc1\x76\xfd\x16\x75\x8f" \
"\x98\xcb\x41\x89\x15\xc9\x2e\xc0\x0d\x06\x02\xc4\x09\x82\x32\xc6" \
"\xb7\x17\x00\x20\x98\x43\x13\x86\x47\x00\xa2\x47\x82\x46\x8a\x07" \
"\xb6\x97\x9c\x43\x63\x1c\xf7\x00\xa2\x47\x85\x07\x3e\xc4\xa2\x46" \
"\x32\x47\xb2\x87\xe3\xe0\xe6\xfe\x01\x45\xbd\xbf\x41\x45\xad\xbf" \
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
int bootloader_len = 512;
#endif
static const uint8_t * GetFlashLoader( enum RiscVChip chip )
{
switch(chip) {
case CHIP_CH32V10x:
return bootloader_v1;
case CHIP_CH32V20x:
case CHIP_CH32V30x:
default:
return bootloader_v2;
}
}
static int InternalLinkEHaltMode( void * d, int mode )
{
libusb_device_handle * dev = ((struct LinkEProgrammerStruct*)d)->devh;
if( mode == ((struct LinkEProgrammerStruct*)d)->lasthaltmode )
return 0;
((struct LinkEProgrammerStruct*)d)->lasthaltmode = mode;
if( mode == 0 )
{
printf( "Holding in reset\n" );
// Part one "immediately" places the part into reset. Part 2 says when we're done, leave part in reset.
wch_link_multicommands( (libusb_device_handle *)dev, 2, 4, "\x81\x0d\x01\x02", 4, "\x81\x0d\x01\x01" );
}
else if( mode == 1 )
{
// This is clearly not the "best" method to exit reset. I don't know why this combination works.
wch_link_multicommands( (libusb_device_handle *)dev, 3, 4, "\x81\x0b\x01\x01", 4, "\x81\x0d\x01\x02", 4, "\x81\x0d\x01\xff" );
}
else
{
return -999;
}
return 0;
}
#if 0
static int LEReadBinaryBlob( void * d, uint32_t offset, uint32_t amount, uint8_t * readbuff )
{
libusb_device_handle * dev = ((struct LinkEProgrammerStruct*)d)->devh;
InternalLinkEHaltMode( d, 0 );
int i;
int status;
uint8_t rbuff[1024];
int transferred = 0;
int readbuffplace = 0;
wch_link_command( (libusb_device_handle *)dev, "\x81\x06\x01\x01", 4, 0, 0, 0 );
// Flush out any pending data.
libusb_bulk_transfer( (libusb_device_handle *)dev, 0x82, rbuff, 1024, &transferred, 1 );
// 3/8 = Read Memory
// First 4 bytes are big-endian location.
// Next 4 bytes are big-endian amount.
uint8_t readop[11] = { 0x81, 0x03, 0x08, };
readop[3] = (offset>>24)&0xff;
readop[4] = (offset>>16)&0xff;
readop[5] = (offset>>8)&0xff;
readop[6] = (offset>>0)&0xff;
readop[7] = (amount>>24)&0xff;
readop[8] = (amount>>16)&0xff;
readop[9] = (amount>>8)&0xff;
readop[10] = (amount>>0)&0xff;
wch_link_command( (libusb_device_handle *)dev, readop, 11, 0, 0, 0 );
// Perform operation
wch_link_command( (libusb_device_handle *)dev, "\x81\x02\x01\x0c", 4, 0, 0, 0 );
uint32_t remain = amount;
while( remain )
{
transferred = 0;
WCHCHECK( libusb_bulk_transfer( (libusb_device_handle *)dev, 0x82, rbuff, 1024, &transferred, WCHTIMEOUT ) );
memcpy( ((uint8_t*)readbuff) + readbuffplace, rbuff, transferred );
readbuffplace += transferred;
remain -= transferred;
}
// Flip internal endian. Must be done separately in case something was unaligned when
// reading.
for( i = 0; i < readbuffplace/4; i++ )
{
uint32_t r = ((uint32_t*)readbuff)[i];
((uint32_t*)readbuff)[i] = (r>>24) | ((r & 0xff0000) >> 8) | ((r & 0xff00)<<8) | (( r & 0xff )<<24);
}
return 0;
}
#endif
static int LEWriteBinaryBlob( void * d, uint32_t address_to_write, uint32_t len, uint8_t * blob )
{
libusb_device_handle * dev = ((struct LinkEProgrammerStruct*)d)->devh;
struct InternalState * iss = (struct InternalState*)(((struct LinkEProgrammerStruct*)d)->internal);
InternalLinkEHaltMode( d, 0 );
int i;
int status;
uint8_t rbuff[1024];
int transferred;
int padlen = ((len-1) & (~(iss->sector_size-1))) + iss->sector_size;
wch_link_command( (libusb_device_handle *)dev, "\x81\x06\x01\x01", 4, 0, 0, 0 );
wch_link_command( (libusb_device_handle *)dev, "\x81\x06\x01\x01", 4, 0, 0, 0 ); // Not sure why but it seems to work better when we request twice.
// This contains the write data quantity, in bytes. (The last 2 octets)
// Then it just rollllls on in.
char rksbuff[11] = { 0x81, 0x01, 0x08,
// Address to write
(uint8_t)(address_to_write >> 24), (uint8_t)(address_to_write >> 16),
(uint8_t)(address_to_write >> 8), (uint8_t)(address_to_write & 0xff),
// Length to write
(uint8_t)(len >> 24), (uint8_t)(len >> 16),
(uint8_t)(len >> 8), (uint8_t)(len & 0xff) };
wch_link_command( (libusb_device_handle *)dev, rksbuff, 11, 0, 0, 0 );
wch_link_command( (libusb_device_handle *)dev, "\x81\x02\x01\x05", 4, 0, 0, 0 );
const uint8_t *bootloader = GetFlashLoader(iss->target_chip_type);
int pplace = 0;
for( pplace = 0; pplace < bootloader_len; pplace += iss->sector_size )
{
WCHCHECK( libusb_bulk_transfer( (libusb_device_handle *)dev, 0x02, (uint8_t*)(bootloader+pplace), iss->sector_size, &transferred, WCHTIMEOUT ) );
}
for( i = 0; i < 10; i++ )
{
wch_link_command( (libusb_device_handle *)dev, "\x81\x02\x01\x07", 4, &transferred, rbuff, 1024 );
if( transferred == 4 && rbuff[0] == 0x82 && rbuff[1] == 0x02 && rbuff[2] == 0x01 && rbuff[3] == 0x07 )
{
break;
}
}
if( i == 10 )
{
fprintf( stderr, "Error, confusing responses to 02/01/07\n" );
exit( -109 );
}
wch_link_command( (libusb_device_handle *)dev, "\x81\x02\x01\x02", 4, 0, 0, 0 );
for( pplace = 0; pplace < padlen; pplace += iss->sector_size )
{
if( pplace + iss->sector_size > len )
{
uint8_t paddeddata[iss->sector_size];
int gap = pplace + iss->sector_size - len;
int okcopy = len - pplace;
memcpy( paddeddata, blob + pplace, okcopy );
memset( paddeddata + okcopy, 0xff, gap );
WCHCHECK( libusb_bulk_transfer( (libusb_device_handle *)dev, 0x02, paddeddata, iss->sector_size, &transferred, WCHTIMEOUT ) );
}
else
{
WCHCHECK( libusb_bulk_transfer( (libusb_device_handle *)dev, 0x02, blob+pplace, iss->sector_size, &transferred, WCHTIMEOUT ) );
}
}
return 0;
}

173
minichlink/serial_dev.c Normal file
View file

@ -0,0 +1,173 @@
#include "serial_dev.h"
int serial_dev_create(serial_dev_t *dev, const char* port, unsigned baud) {
if (!dev)
return -1;
dev->port = port;
dev->baud = baud;
#ifdef IS_WINDOWS
dev->handle = INVALID_HANDLE_VALUE;
#else
dev->fd = -1;
#endif
return 0;
}
int serial_dev_open(serial_dev_t *dev) {
fprintf(stderr, "Opening serial port %s at %u baud.\n", dev->port, dev->baud);
#ifdef IS_WINDOWS
// Windows quirk: port = "COM10" is invalid, has to be encoded as "\\.\COM10".
// This also works for COM below 9. So, let's give the user the ability to use
// any "COMx" string and just prepend the "\\.\".
char winPortName[64];
if(dev->port[0] != '\\') {
snprintf(winPortName, sizeof(winPortName), "\\\\.\\%s", dev->port);
} else {
// copy verbatim if string already starts with a '\'
snprintf(winPortName, sizeof(winPortName), "%s", dev->port);
}
dev->handle = CreateFileA(winPortName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0,0);
if (dev->handle == INVALID_HANDLE_VALUE) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
fprintf(stderr, "Serial port %s not found.\n", dev->port);
// weird: without this, errno = 0 (no error).
_set_errno(ERROR_FILE_NOT_FOUND);
return -1; // Device not found
}
// Error while opening the device
return -1;
}
DCB dcbSerialParams;
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
if (!GetCommState(dev->handle, &dcbSerialParams)) {
return -1;
}
// set baud and 8N1 serial formatting
dcbSerialParams.BaudRate = dev->baud;
dcbSerialParams.ByteSize = 8;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = NOPARITY;
// write back
if (!SetCommState(dev->handle, &dcbSerialParams)){
return -1;
}
// Set the timeout parameters to "no timeout" (blocking).
// see https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = 0;
timeouts.ReadTotalTimeoutConstant = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = MAXDWORD;
timeouts.WriteTotalTimeoutMultiplier = 0;
// Write the parameters
if (!SetCommTimeouts(dev->handle, &timeouts)) {
return -1;
}
#else
struct termios attr;
if ((dev->fd = open(dev->port, O_RDWR | O_NOCTTY)) == -1) {
perror("open");
return -1;
}
if (tcgetattr(dev->fd, &attr) == -1) {
perror("tcgetattr");
return -1;
}
cfmakeraw(&attr);
cfsetspeed(&attr, dev->baud);
if (tcsetattr(dev->fd, TCSANOW, &attr) == -1) {
perror("tcsetattr");
return -1;
}
#endif
// all okay if we get here
return 0;
}
int serial_dev_write(serial_dev_t *dev, const void* data, size_t len) {
#ifdef IS_WINDOWS
DWORD dwBytesWritten;
if (!WriteFile(dev->handle, data, len, &dwBytesWritten,NULL)) {
return -1;
}
return (int) dwBytesWritten;
#else
return write(dev->fd, data, len);
#endif
}
int serial_dev_read(serial_dev_t *dev, void* data, size_t len) {
#ifdef IS_WINDOWS
DWORD dwBytesRead = 0;
if (!ReadFile(dev->handle, data, len, &dwBytesRead, NULL)) {
return -1;
}
return (int) dwBytesRead;
#else
return read(dev->fd, data, len);
#endif
}
int serial_dev_do_dtr_reset(serial_dev_t *dev) {
#ifdef IS_WINDOWS
// EscapeCommFunction returns 0 on fail
if(EscapeCommFunction(dev->handle, SETDTR) == 0) {
return -1;
}
if(EscapeCommFunction(dev->handle, CLRDTR) == 0) {
return -1;
}
#else
int argp = TIOCM_DTR;
// Arduino DTR reset.
if (ioctl(dev->fd, TIOCMBIC, &argp) == -1) {
perror("ioctl");
return -1;
}
if (tcdrain(dev->fd) == -1) {
perror("tcdrain");
return -1;
}
if (ioctl(dev->fd, TIOCMBIS, &argp) == -1) {
perror("ioctl");
return -1;
}
#endif
return 0;
}
int serial_dev_flush_rx(serial_dev_t *dev) {
#ifdef IS_WINDOWS
// PurgeComm returns 0 on fail
if (PurgeComm(dev->handle, PURGE_RXCLEAR) == 0) {
return -1;
}
#else
if (tcflush(dev->fd, TCIFLUSH) == -1) {
perror("tcflush");
return -1;
}
#endif
return 0;
}
int serial_dev_close(serial_dev_t *dev) {
#ifdef IS_WINDOWS
if(!CloseHandle(dev->handle)) {
return -1;
}
dev->handle = INVALID_HANDLE_VALUE;
#else
int ret = 0;
if((ret = close(dev->fd)) != 0) {
return ret;
}
dev->fd = -1;
#endif
return 0;
}

48
minichlink/serial_dev.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef _SERIAL_DEV_H
#define _SERIAL_DEV_H
#include <stddef.h>
#if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define IS_WINDOWS
#define DEFAULT_SERIAL_NAME "\\\\.\\COM3"
#else
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define IS_POSIX
#define DEFAULT_SERIAL_NAME "/dev/ttyACM0"
#endif
/* these are available on all platforms */
#include <errno.h>
#include <stdio.h>
typedef struct {
const char* port;
unsigned baud;
#ifdef IS_WINDOWS
HANDLE handle;
#else
int fd;
#endif
} serial_dev_t;
/* returns 0 if OK */
int serial_dev_create(serial_dev_t *dev, const char* port, unsigned baud);
/* returns 0 if OK */
int serial_dev_open(serial_dev_t *dev);
/* returns -1 on write error */
int serial_dev_write(serial_dev_t *dev, const void* data, size_t len);
/* returns -1 on read error */
int serial_dev_read(serial_dev_t *dev, void* data, size_t len);
/* returns -1 on reset error */
int serial_dev_do_dtr_reset(serial_dev_t *dev);
/* returns -1 on flush error */
int serial_dev_flush_rx(serial_dev_t *dev);
/* returns -1 on close error */
int serial_dev_close(serial_dev_t *dev);
#endif

162
minichlink/terminalhelp.h Normal file
View file

@ -0,0 +1,162 @@
// terminalhelp from mini-rv32ima.
#ifndef _TERMINALHELP_H
#define _TERMINALHELP_H
#include <stdint.h>
// Provides the following:
static void CaptureKeyboardInput() __attribute__((used));
static void ResetKeyboardInput() __attribute__((used));
static uint64_t GetTimeMicroseconds() __attribute__((used));
static int ReadKBByte() __attribute__((used));
static int IsKBHit() __attribute__((used));
#if defined(WINDOWS) || defined(WIN32) || defined(_WIN32)
#include <windows.h>
#include <conio.h>
#define strtoll _strtoi64
static void CaptureKeyboardInput()
{
system(""); // Poorly documented tick: Enable VT100 Windows mode.
}
static void ResetKeyboardInput()
{
}
static uint64_t GetTimeMicroseconds()
{
static LARGE_INTEGER lpf;
LARGE_INTEGER li;
if( !lpf.QuadPart )
QueryPerformanceFrequency( &lpf );
QueryPerformanceCounter( &li );
return ((uint64_t)li.QuadPart * 1000000LL) / (uint64_t)lpf.QuadPart;
}
static int IsKBHit()
{
return _kbhit();
}
static int ReadKBByte()
{
// This code is kind of tricky, but used to convert windows arrow keys
// to VT100 arrow keys.
static int is_escape_sequence = 0;
int r;
if( is_escape_sequence == 1 )
{
is_escape_sequence++;
return '[';
}
r = _getch();
if( is_escape_sequence )
{
is_escape_sequence = 0;
switch( r )
{
case 'H': return 'A'; // Up
case 'P': return 'B'; // Down
case 'K': return 'D'; // Left
case 'M': return 'C'; // Right
case 'G': return 'H'; // Home
case 'O': return 'F'; // End
default: return r; // Unknown code.
}
}
else
{
switch( r )
{
case 13: return 10; //cr->lf
case 224: is_escape_sequence = 1; return 27; // Escape arrow keys
default: return r;
}
}
}
#else
#include <sys/ioctl.h>
#include <termios.h>
#undef BS0
#undef BS1
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
static void CtrlC()
{
fprintf( stderr, "Minichlink Closing\n" );
ResetKeyboardInput();
exit( 0 );
}
// Override keyboard, so we can capture all keyboard input for the VM.
static void CaptureKeyboardInput()
{
// Hook exit, because we want to re-enable keyboard.
atexit(ResetKeyboardInput);
signal(SIGINT, CtrlC);
signal(SIGPIPE, CtrlC);
struct termios term;
tcgetattr(0, &term);
term.c_lflag &= ~(ICANON | ECHO); // Disable echo as well
tcsetattr(0, TCSANOW, &term);
}
static void ResetKeyboardInput()
{
// Re-enable echo, etc. on keyboard.
struct termios term;
tcgetattr(0, &term);
term.c_lflag |= ICANON | ECHO;
tcsetattr(0, TCSANOW, &term);
}
static uint64_t GetTimeMicroseconds()
{
struct timeval tv;
gettimeofday( &tv, 0 );
return tv.tv_usec + ((uint64_t)(tv.tv_sec)) * 1000000LL;
}
static int is_eofd;
static int ReadKBByte()
{
if( is_eofd ) return 0xffffffff;
char rxchar = 0;
int rread = read(fileno(stdin), (char*)&rxchar, 1);
if( rread > 0 ) // Tricky: getchar can't be used with arrow keys.
return rxchar;
else
return -1;
}
static int IsKBHit()
{
if( is_eofd ) return -1;
int byteswaiting;
ioctl(0, FIONREAD, &byteswaiting);
if( !byteswaiting && write( fileno(stdin), 0, 0 ) != 0 ) { is_eofd = 1; return -1; } // Is end-of-file for
return !!byteswaiting;
}
#endif
#endif