Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: CMake

on:
push:
branches:
- non-contiguous-uf2
pull_request:

env:
BUILD_TYPE: Release

jobs:
build:
name: Build UF2 artifacts (pico2)
runs-on: ubuntu-latest

env:
PICO_SDK_PATH: ${{github.workspace}}/pico-sdk

steps:
- name: Compiler Cache
uses: actions/cache@v4
with:
path: /home/runner/.ccache
key: ccache-cmake-${{github.ref}}-${{github.sha}}
restore-keys: |
ccache-cmake-${{github.ref}}
ccache-cmake

- name: Checkout Code
uses: actions/checkout@v4
with:
path: src
submodules: recursive

- name: Checkout Pico SDK
uses: actions/checkout@v4
with:
ref: '2.2.0'
repository: raspberrypi/pico-sdk
path: pico-sdk
submodules: true

- name: Install Arm GNU Toolchain (arm-none-eabi-gcc)
uses: carlosperate/arm-none-eabi-gcc-action@v1
with:
release: '13.3.Rel1'

- name: Install CCache
run: sudo apt update && sudo apt install -y ccache

- name: Configure CMake
run: |
cmake -E make_directory ${{runner.workspace}}/build
cmake -S ${{github.workspace}}/src -B ${{runner.workspace}}/build \
-DPICO_SDK_PATH=${{github.workspace}}/pico-sdk \
-DPICO_BOARD=pico2 \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache

- name: Build
working-directory: ${{runner.workspace}}/build
run: |
ccache --zero-stats || true
cmake --build . --config $BUILD_TYPE -j$(nproc)
ccache --show-stats || true

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: uf2loader-pico2
path: ${{runner.workspace}}/output/*.uf2

# ── Update rolling latest release ────────────────────────────────────────
- name: Update latest release
if: github.event_name == 'push' && github.ref == 'refs/heads/non-contiguous-uf2'
uses: softprops/action-gh-release@v2
with:
tag_name: latest
name: Latest Build
prerelease: true
make_latest: 'true'
files: ${{runner.workspace}}/output/*.uf2
54 changes: 37 additions & 17 deletions ui/uf2.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ uintptr_t prog_area_end;
typedef struct
{
const char* filename;
uint32_t prog_addr;
uint32_t num_blks;
uint32_t num_blks_read;
uint32_t num_blks_written;
uint32_t family_id;
bool malformed_uf2; // indicates a badly formed pico2 uf2
uint8_t erased_sectors[512]; // covers up to 16MB of flash (4096 sectors × 4KB)
} prog_state_t;

static struct uf2_block _block_buf __attribute((aligned(256)));
Expand Down Expand Up @@ -71,7 +71,9 @@ bool handle_boot_stage2(const struct uf2_block* b)
uint8_t boot2[BOOT2_SIZE];
memcpy(boot2, (void*)XIP_BASE, BOOT2_SIZE);

FLASH_ERASE(XIP_BASE, b->num_blocks * b->payload_size);
// Only erase sector 0; all other sectors are erased on demand via ensure_sector_erased()
FLASH_ERASE(XIP_BASE, FLASH_SECTOR_SIZE);
s.erased_sectors[0] |= 1; // mark sector 0 as erased in the bitmap
FLASH_PROG(XIP_BASE, boot2, BOOT2_SIZE);

if (b->target_addr != XIP_BASE)
Expand All @@ -98,8 +100,8 @@ static inline int FLASH_ERASE(uintptr_t address, uint32_t size_bytes)
(CFLASH_SECLEVEL_VALUE_SECURE << CFLASH_SECLEVEL_LSB) |
(CFLASH_ASPACE_VALUE_RUNTIME << CFLASH_ASPACE_LSB)};

// Round up size_bytes or rom_flash_op will throw an alignment error
uint32_t size_aligned = (size_bytes + 0x1FFF) & -FLASH_SECTOR_SIZE;
// Round up size_bytes to the next sector boundary
uint32_t size_aligned = (size_bytes + FLASH_SECTOR_SIZE - 1) & -FLASH_SECTOR_SIZE;

return rom_flash_op(cflash_flags, address, size_aligned, NULL);
}
Expand All @@ -118,6 +120,30 @@ bool handle_boot_stage2(const struct uf2_block* b) { return false; }

#endif

// Erase the sector containing 'address', if not already erased.
// Uses a bitmap in prog_state_t to avoid erasing the same sector twice.
static int ensure_sector_erased(uintptr_t address)
{
uintptr_t sector_base = address & ~((uintptr_t)(FLASH_SECTOR_SIZE - 1));
uint32_t sector_idx = (sector_base - XIP_BASE) / FLASH_SECTOR_SIZE;
if (sector_idx >= sizeof(s.erased_sectors) * 8)
{
DEBUG_PRINT("Sector index %d out of bitmap range\n", sector_idx);
return -1;
}

uint32_t byte_idx = sector_idx / 8;
uint8_t bit_mask = 1u << (sector_idx % 8);

if (s.erased_sectors[byte_idx] & bit_mask)
return 0; // already erased

int ret = FLASH_ERASE(sector_base, FLASH_SECTOR_SIZE);
if (ret >= 0)
s.erased_sectors[byte_idx] |= bit_mask;
return ret;
}

char* get_short_path(const char* path)
{
static char sfn_path[512];
Expand Down Expand Up @@ -210,6 +236,12 @@ enum uf2_result_e __attribute__((optimize("-O0"))) load_application_from_uf2(con
// if block contains proginfo area, set it to all 0xFF's
bl_proginfo_clear(b->data, b->target_addr, b->payload_size);

if (ensure_sector_erased(b->target_addr) < 0)
{
DEBUG_PRINT("Erase failed for block %d at address 0x%08x\n", b->block_no, b->target_addr);
return UF2_UNKNOWN;
}

FLASH_PROG(b->target_addr, b->data, FLASH_PAGE_SIZE);

s.num_blks_written++;
Expand All @@ -224,10 +256,9 @@ enum uf2_result_e __attribute__((optimize("-O0"))) load_application_from_uf2(con
continue;
}

text_directory_ui_set_status("Erasing flash...");
if (!handle_boot_stage2(b))
{
if (FLASH_ERASE(b->target_addr, s.num_blks * b->payload_size) < 0)
if (ensure_sector_erased(b->target_addr) < 0)
{
// Don't even attempt to program if the erase fails
return UF2_UNKNOWN;
Expand All @@ -236,7 +267,6 @@ enum uf2_result_e __attribute__((optimize("-O0"))) load_application_from_uf2(con
FLASH_PROG(b->target_addr, b->data, FLASH_PAGE_SIZE);
}

s.prog_addr = b->target_addr;
s.num_blks_written++;
}
}
Expand Down Expand Up @@ -371,12 +401,6 @@ static bool check_1st_block(const struct uf2_block* b)
return false;
}

if (b->target_addr + FLASH_PAGE_SIZE * s.num_blks > prog_area_end)
{
DEBUG_PRINT("Requested range exceeds flash area\n");
return false;
}

return true;
}

Expand All @@ -395,10 +419,6 @@ static bool check_block(const prog_state_t* s, const struct uf2_block* b)
{
return false;
}
if (s->prog_addr + FLASH_PAGE_SIZE * s->num_blks_written != b->target_addr)
{
return false;
}

return true;
}