diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..68138db --- /dev/null +++ b/.github/workflows/build.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d4f6d3d --- /dev/null +++ b/CMakeLists.txt @@ -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 .) diff --git a/README.md b/README.md index a900068..5bcecac 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/getopt_compat.c b/getopt_compat.c new file mode 100644 index 0000000..0549bf8 --- /dev/null +++ b/getopt_compat.c @@ -0,0 +1,122 @@ +#include "getopt_compat.h" + +#include +#include + +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; + } +} diff --git a/getopt_compat.h b/getopt_compat.h new file mode 100644 index 0000000..2cbc7e1 --- /dev/null +++ b/getopt_compat.h @@ -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 diff --git a/usbboot.c b/usbboot.c index 8e10188..97646c7 100644 --- a/usbboot.c +++ b/usbboot.c @@ -23,13 +23,21 @@ ****************************************************************************/ #include +#ifdef _WIN32 +#include +#include "getopt_compat.h" +#define sleep(seconds) Sleep((DWORD)((seconds) * 1000UL)) +#else #include #include +#endif #include #include +#include #include #include #include +#include #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); }