Add cross-platform Windows and Linux CI builds
Introduce a reproducible CMake build and GitHub Actions matrix for Ubuntu Linux x64 and Windows x64. Each platform installs libusb, builds the usbboot executable, and publishes a platform-specific artifact containing the executable, required boot binaries, and documentation. The Windows artifact also includes libusb-1.0.dll. Add a small Windows getopt_long compatibility layer because the Microsoft CRT does not provide the POSIX GNU option parser used by usbboot. Add Windows sleep support and make the source explicitly include the fixed-width integer and time headers needed by the cross-platform build. Replace C99 variable-length arrays with checked heap allocations so the source compiles with MSVC, which does not support VLAs. Cast libusb request-type flags to avoid MSVC enum warnings, and use a conforming cleanup(void) callback signature for atexit(). Correct the existing cleanup NULL check so an open libusb handle is actually closed. Document local CMake builds, CI artifacts, the Windows libusb DLL, and the requirement to bind the boot-mode device to WinUSB before use. Credit: OpenAI Codex (GPT-5), collaborating with dark98 on the Windows port and CI implementation.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
name: Build usbboot
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.platform }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
platform: linux-x64
|
||||
- os: windows-latest
|
||||
platform: windows-x64
|
||||
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends --yes cmake libusb-1.0-0-dev
|
||||
|
||||
- name: Install Windows dependencies
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: vcpkg install libusb:x64-windows
|
||||
|
||||
- name: Configure (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Configure (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: >-
|
||||
cmake -S . -B build -A x64
|
||||
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake"
|
||||
-DVCPKG_TARGET_TRIPLET=x64-windows
|
||||
|
||||
- name: Build (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: cmake --build build --config Release --parallel
|
||||
|
||||
- name: Build (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: cmake --build build --config Release --parallel
|
||||
|
||||
- name: Package Linux artifact
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
mkdir dist
|
||||
cp build/usbboot dist/usbboot
|
||||
cp spl.bin uboot.bin README.md dist/
|
||||
|
||||
- name: Package Windows artifact
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force dist | Out-Null
|
||||
Copy-Item build\Release\usbboot.exe dist\
|
||||
Copy-Item "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin\libusb-1.0.dll" dist\
|
||||
Copy-Item spl.bin, uboot.bin, README.md dist\
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: usbboot-${{ matrix.platform }}
|
||||
path: dist
|
||||
@@ -0,0 +1,37 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(ingenic_usbboot C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
|
||||
find_path(LIBUSB_INCLUDE_DIR
|
||||
NAMES libusb.h
|
||||
PATH_SUFFIXES libusb-1.0
|
||||
)
|
||||
find_library(LIBUSB_LIBRARY
|
||||
NAMES usb-1.0 libusb-1.0
|
||||
)
|
||||
|
||||
if(NOT LIBUSB_INCLUDE_DIR OR NOT LIBUSB_LIBRARY)
|
||||
message(FATAL_ERROR
|
||||
"libusb-1.0 was not found. Install libusb-1.0-dev on Linux or "
|
||||
"libusb through vcpkg on Windows.")
|
||||
endif()
|
||||
|
||||
set(USBBOOT_SOURCES usbboot.c)
|
||||
if(WIN32)
|
||||
list(APPEND USBBOOT_SOURCES getopt_compat.c)
|
||||
endif()
|
||||
|
||||
add_executable(usbboot ${USBBOOT_SOURCES})
|
||||
target_include_directories(usbboot PRIVATE ${LIBUSB_INCLUDE_DIR})
|
||||
target_link_libraries(usbboot PRIVATE ${LIBUSB_LIBRARY})
|
||||
|
||||
if(MSVC)
|
||||
target_compile_definitions(usbboot PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
install(TARGETS usbboot RUNTIME DESTINATION . LIBRARY DESTINATION . ARCHIVE DESTINATION .)
|
||||
install(FILES spl.bin uboot.bin README.md DESTINATION .)
|
||||
@@ -3,6 +3,21 @@
|
||||
|
||||
`make`
|
||||
|
||||
Cross-platform release artifacts are built by GitHub Actions for Linux x64 and
|
||||
Windows x64. See the workflow runs under the repository's Actions tab.
|
||||
|
||||
For a local CMake build, install CMake and libusb-1.0, then run:
|
||||
|
||||
```sh
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --config Release
|
||||
```
|
||||
|
||||
On Windows, install libusb through vcpkg and configure CMake with the vcpkg
|
||||
toolchain file. The Windows artifact includes `libusb-1.0.dll` beside
|
||||
`usbboot.exe`. Before using it, bind the boot-mode device to WinUSB with a
|
||||
tool such as Zadig.
|
||||
|
||||
## Running
|
||||
To see console output, a USB to TTL adapter is needed. For the Creality K1 board, the serial pinout is by the reset/boot botton.
|
||||
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
#include "getopt_compat.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
char *optarg;
|
||||
int optind = 1;
|
||||
int opterr = 1;
|
||||
int optopt;
|
||||
|
||||
static const char *short_options;
|
||||
|
||||
static int option_requires_argument(const char *optstring, int option)
|
||||
{
|
||||
const char *match = strchr(optstring, option);
|
||||
return match != NULL && match[1] == ':';
|
||||
}
|
||||
|
||||
static int missing_argument(int option)
|
||||
{
|
||||
optopt = option;
|
||||
optarg = NULL;
|
||||
return '?';
|
||||
}
|
||||
|
||||
int getopt_long(int argc, char * const argv[], const char *optstring,
|
||||
const struct option *longopts, int *longindex)
|
||||
{
|
||||
const char *arg;
|
||||
|
||||
optarg = NULL;
|
||||
if (optind >= argc)
|
||||
return -1;
|
||||
|
||||
arg = argv[optind];
|
||||
if (arg[0] != '-' || arg[1] == '\0')
|
||||
return -1;
|
||||
|
||||
if (arg[1] == '-') {
|
||||
const char *name = arg + 2;
|
||||
const char *equals = strchr(name, '=');
|
||||
size_t name_length = equals != NULL
|
||||
? (size_t)(equals - name)
|
||||
: strlen(name);
|
||||
size_t i;
|
||||
|
||||
if (name_length == 0) {
|
||||
optind++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; longopts[i].name != NULL; i++) {
|
||||
if (strlen(longopts[i].name) != name_length ||
|
||||
strncmp(longopts[i].name, name, name_length) != 0)
|
||||
continue;
|
||||
|
||||
if (longindex != NULL)
|
||||
*longindex = (int)i;
|
||||
|
||||
if (longopts[i].has_arg == required_argument) {
|
||||
if (equals != NULL)
|
||||
optarg = (char *)(equals + 1);
|
||||
else if (optind + 1 < argc)
|
||||
optarg = argv[++optind];
|
||||
else
|
||||
return missing_argument(longopts[i].val);
|
||||
} else if (equals != NULL) {
|
||||
return missing_argument(longopts[i].val);
|
||||
}
|
||||
|
||||
optind++;
|
||||
if (longopts[i].flag != NULL) {
|
||||
*longopts[i].flag = longopts[i].val;
|
||||
return 0;
|
||||
}
|
||||
return longopts[i].val;
|
||||
}
|
||||
|
||||
optind++;
|
||||
optopt = 0;
|
||||
return '?';
|
||||
}
|
||||
|
||||
/* Parse one short option, including clusters such as -vbh. */
|
||||
if (short_options == NULL || *short_options == '\0')
|
||||
short_options = arg + 1;
|
||||
|
||||
{
|
||||
int option = (unsigned char)*short_options++;
|
||||
const char *match = strchr(optstring, option);
|
||||
|
||||
if (match == NULL || option == ':') {
|
||||
optopt = option;
|
||||
if (*short_options == '\0') {
|
||||
short_options = NULL;
|
||||
optind++;
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
|
||||
if (option_requires_argument(optstring, option)) {
|
||||
if (*short_options != '\0') {
|
||||
optarg = (char *)short_options;
|
||||
short_options = NULL;
|
||||
optind++;
|
||||
} else if (optind + 1 < argc) {
|
||||
optarg = argv[++optind];
|
||||
short_options = NULL;
|
||||
optind++;
|
||||
} else {
|
||||
short_options = NULL;
|
||||
optind++;
|
||||
return missing_argument(option);
|
||||
}
|
||||
} else if (*short_options == '\0') {
|
||||
short_options = NULL;
|
||||
optind++;
|
||||
}
|
||||
|
||||
return option;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef GETOPT_COMPAT_H
|
||||
#define GETOPT_COMPAT_H
|
||||
|
||||
/* The Microsoft CRT does not provide the POSIX getopt_long API. */
|
||||
#define no_argument 0
|
||||
#define required_argument 1
|
||||
|
||||
struct option {
|
||||
const char *name;
|
||||
int has_arg;
|
||||
int *flag;
|
||||
int val;
|
||||
};
|
||||
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
extern int opterr;
|
||||
extern int optopt;
|
||||
|
||||
int getopt_long(int argc, char * const argv[], const char *optstring,
|
||||
const struct option *longopts, int *longindex);
|
||||
|
||||
#endif
|
||||
@@ -23,13 +23,21 @@
|
||||
****************************************************************************/
|
||||
|
||||
#include <libusb.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include "getopt_compat.h"
|
||||
#define sleep(seconds) Sleep((DWORD)((seconds) * 1000UL))
|
||||
#else
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
|
||||
#define VR_GET_CPU_INFO 0
|
||||
#define VR_SET_DATA_ADDRESS 1
|
||||
@@ -42,6 +50,8 @@
|
||||
#define VR_WRITE 0x12
|
||||
#define VR_READ 0x13
|
||||
#define VR_UPDATE_CFG 0x14
|
||||
#define USB_VENDOR_IN ((uint8_t)((uint8_t)LIBUSB_ENDPOINT_IN | (uint8_t)LIBUSB_REQUEST_TYPE_VENDOR | (uint8_t)LIBUSB_RECIPIENT_DEVICE))
|
||||
#define USB_VENDOR_OUT ((uint8_t)((uint8_t)LIBUSB_ENDPOINT_OUT | (uint8_t)LIBUSB_REQUEST_TYPE_VENDOR | (uint8_t)LIBUSB_RECIPIENT_DEVICE))
|
||||
|
||||
#define MAGIC_DEBUG ('D' << 24) | ('B' << 16) | ('G' << 8) | 0
|
||||
#define MAGIC_MMC ('M' << 24) | ('M' << 16) | ('C' << 8) | 0
|
||||
@@ -175,7 +185,7 @@ void jz_get_cpu_info(void)
|
||||
|
||||
uint8_t buf[9];
|
||||
int ret = libusb_control_transfer(g_usb_dev,
|
||||
LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
|
||||
USB_VENDOR_IN,
|
||||
VR_GET_CPU_INFO, 0, 0, buf, 8, 1000);
|
||||
if(ret != 0)
|
||||
die("Can't get CPU info: %d", ret);
|
||||
@@ -227,8 +237,8 @@ void bulk_transfer_out(void* data, int length) {
|
||||
void name(unsigned long param) { \
|
||||
ensure_usb(); \
|
||||
verbose("Issue " #type fmt, param); \
|
||||
int ret = libusb_control_transfer(g_usb_dev, \
|
||||
LIBUSB_ENDPOINT_OUT|LIBUSB_REQUEST_TYPE_VENDOR|LIBUSB_RECIPIENT_DEVICE, \
|
||||
int ret = libusb_control_transfer(g_usb_dev, \
|
||||
USB_VENDOR_OUT, \
|
||||
VR_##type, param >> 16, param & 0xffff, NULL, 0, 1000); \
|
||||
if(ret != 0) \
|
||||
die("Request " #type " failed: %d", ret); \
|
||||
@@ -249,7 +259,7 @@ void jz_generic_out(uint8_t op,
|
||||
ensure_usb();
|
||||
verbose("Issue 0x%x", op);
|
||||
int ret = libusb_control_transfer(g_usb_dev,
|
||||
LIBUSB_ENDPOINT_OUT|LIBUSB_REQUEST_TYPE_VENDOR|LIBUSB_RECIPIENT_DEVICE,
|
||||
USB_VENDOR_OUT,
|
||||
op, param >> 16, param & 0xffff, data, len, 1000);
|
||||
if(ret != len)
|
||||
die("Request 0x%x failed, only transfered: %d", op, ret);
|
||||
@@ -282,7 +292,7 @@ void jz_get_ack() {
|
||||
|
||||
uint8_t buf[4];
|
||||
int ret = libusb_control_transfer(g_usb_dev,
|
||||
LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
|
||||
USB_VENDOR_IN,
|
||||
VR_GET_ACK, 0, 0, buf, 4, 1000);
|
||||
if(ret != 4)
|
||||
die("Can't get ACK: %d", ret);
|
||||
@@ -314,7 +324,9 @@ void enable_mmc() {
|
||||
memset(mmc_param_info->data, 0, sizeof(mmc_param));
|
||||
|
||||
uint32_t data_size = 3 * 8 + policy_param_info->size + dbg_param_info->size + mmc_param_info->size;
|
||||
unsigned char data[data_size];
|
||||
unsigned char *data = (unsigned char*)malloc(data_size);
|
||||
if(data == NULL)
|
||||
die("Can't allocate %u bytes for MMC configuration", data_size);
|
||||
unsigned char *p = data;
|
||||
uint32_t offset = 0;
|
||||
memcpy(p + offset, dbg_param_info, 4 + 4 + dbg_param_info->size);
|
||||
@@ -335,6 +347,7 @@ void enable_mmc() {
|
||||
free(dbg_param_info);
|
||||
free(mmc_param_info);
|
||||
free(update);
|
||||
free(data);
|
||||
|
||||
jz_get_ack();
|
||||
jz_init(0);
|
||||
@@ -371,11 +384,16 @@ void mmc_read_partition(uint32_t offset, uint32_t length, const char* fname) {
|
||||
printf("dumping parition at offset 0x%x, size 0x%x\n", offset, length);
|
||||
|
||||
uint32_t chunk_size = 1024 * 1024 * 2; // 2mb
|
||||
unsigned char chunk[chunk_size];
|
||||
|
||||
FILE* f = fopen(fname, "wb");
|
||||
if(f == NULL)
|
||||
die("Can't open file '%s' for writing", fname);
|
||||
|
||||
unsigned char *chunk = (unsigned char*)malloc(chunk_size);
|
||||
if(chunk == NULL) {
|
||||
fclose(f);
|
||||
die("Can't allocate %u bytes for partition dump", chunk_size);
|
||||
}
|
||||
|
||||
uint32_t cursor = offset;
|
||||
uint32_t end = offset + length;
|
||||
@@ -399,6 +417,7 @@ void mmc_read_partition(uint32_t offset, uint32_t length, const char* fname) {
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
free(chunk);
|
||||
}
|
||||
|
||||
void mmc_write(uint32_t offset, uint32_t length, unsigned char* in) {
|
||||
@@ -418,12 +437,14 @@ void swap_ota_partition(bool force) {
|
||||
ensure_usb();
|
||||
enable_mmc();
|
||||
|
||||
uint32_t ota_len = 512;
|
||||
unsigned char ota[ota_len];
|
||||
const uint32_t ota_len = 512;
|
||||
unsigned char *ota = (unsigned char*)malloc(ota_len);
|
||||
char *ota_in = (char*)malloc(ota_len);
|
||||
if(ota == NULL || ota_in == NULL)
|
||||
die("Can't allocate OTA buffers");
|
||||
|
||||
mmc_read(0x100000, ota_len, ota);
|
||||
|
||||
char ota_in[ota_len];
|
||||
memset(ota_in, 0, ota_len);
|
||||
|
||||
if (strncmp((char*)ota, "ota:kernel2", 11) == 0) {
|
||||
@@ -443,6 +464,8 @@ void swap_ota_partition(bool force) {
|
||||
|
||||
mmc_write(0x100000, ota_len, (unsigned char*)ota_in);
|
||||
printf("Switched OTA to %s", ota_in);
|
||||
free(ota);
|
||||
free(ota_in);
|
||||
}
|
||||
|
||||
/* Default settings */
|
||||
@@ -552,9 +575,9 @@ Known CPU types and default stage1/stage2 binary settings:\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void cleanup()
|
||||
void cleanup(void)
|
||||
{
|
||||
if(g_usb_dev == NULL)
|
||||
if(g_usb_dev != NULL)
|
||||
libusb_close(g_usb_dev);
|
||||
libusb_exit(NULL);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user