/* Copyright 2026 Armel F4HWN * https://github.com/armel * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Overlay-app loader (POC). * * A leaf, modal app is a self-contained blob held in the external SPI flash. To * run it the loader validates the header + code CRC, copies the code into the * 4 KiB overlay (the PY25Q16 sector cache, shared VMA with the multiboot RAM * stub), and calls its entry point. The blob runs from RAM and reaches every * resident service through app_api_t. The loader never touches the internal * application flash, so a corrupt or incompatible blob is only refused (header/CRC * mismatch) or crashes into a watchdog reset. * * This is NOT a security sandbox. The CRC is an integrity check, not authentication, * and a launched app is TRUSTED NATIVE CODE: it runs privileged with full access to * memory and peripherals (no MPU, no crypto auth), so a malicious or buggy app CAN * brick the radio - e.g. by driving the internal-flash controller itself. Only * install apps you trust. * * External-flash "Apps" region (carved from the free space in the PY25Q16 map, * after the multiboot markers, before the RX/TX log): * * 0x102000 slot 0 ] 16 slots x 8 KiB = 128 KiB * 0x104000 slot 1 ] each slot: 4 KiB header sector + 4 KiB code * ... ] * 0x120000 slot 15 ] * * Host tooling (APP_SlotErase/Write/Info) touches the external flash only and is * never brick-critical; a bad slot is simply refused at launch by the CRC check. */ #ifndef APPS_APP_OVERLAY_H #define APPS_APP_OVERLAY_H #include #include #include "app_api.h" /* ---- external-flash Apps region ---- */ #define APP_REGION_BASE 0x00102000u /* first app slot */ #define APP_SLOT_STRIDE 0x00002000u /* 8 KiB per slot */ #define APP_CODE_OFFSET 0x00001000u /* code starts after the 4 KiB header sector */ #define APP_SECTOR_SIZE 0x00001000u /* external NOR erase granularity */ #define APP_SLOT_COUNT 16u #define APP_SLOT_BASE(s) (APP_REGION_BASE + (uint32_t)(s) * APP_SLOT_STRIDE) /* ---- overlay RAM budget: the PY25Q16 4 KiB sector cache reused as workspace. * The link VMA itself is pinned in Core/py32f071xb.ld (ORIGIN+0x280) and passed * via compile-app.sh; the loader checks each blob's link_vma against it. ---- */ #define APP_OVERLAY_MAX 0x00001000u /* 4 KiB */ /* ---- blob header (64 bytes, little-endian; see App/apps/pack_app.py) ---- */ #define APP_MAGIC 0x31504146u /* "FAP1" */ #define APP_HDR_VERSION 1u #define APP_FLAG_COMMITTED 0x0001u #define APP_FLAG_SCREEN_SAVER 0x0002u #define APP_FLAG_SHORTCUT_SHIFT 8u #define APP_FLAG_SHORTCUT_MASK 0x0F00u #define APP_NAME_LEN 16 #define APP_VERSION_LEN 16 #define APP_SHORTCUT_FM 0x01u #define APP_SHORTCUT_FOXHUNT 0x02u #define APP_SHORTCUT_BEACON 0x04u #define APP_SHORTCUT_BEAM 0x08u /* Optional resident facilities an app may require. Requirements live in the * previously reserved header bytes, so app_header_t remains 64 bytes. */ #define APP_CAP_FM 0x00000001u typedef struct __attribute__((packed)) { uint32_t magic; /* APP_MAGIC */ uint16_t hdr_version; /* APP_HDR_VERSION */ uint8_t abi_major; /* required ABI family */ uint8_t api_min; /* minimum append-only API level */ uint32_t code_size; /* bytes of code, <= APP_OVERLAY_MAX */ uint32_t code_crc32; /* CRC-32 (zlib) over code_size bytes */ uint16_t entry_off; /* entry offset within the code (0) */ uint16_t flags; /* APP_FLAG_COMMITTED, ... */ char name[APP_NAME_LEN]; /* human-readable, NUL-terminated */ char version[APP_VERSION_LEN];/* app version string */ uint32_t link_vma; /* RAM VMA the code was linked at */ uint32_t required_caps; /* APP_CAP_* required by this app */ uint8_t reserved[4]; /* pad to 64 bytes */ } app_header_t; enum { APP_OK = 0, APP_ERR_SLOT, /* slot index out of range */ APP_ERR_MAGIC, /* no/invalid header */ APP_ERR_ABI, /* ABI family/API level mismatch */ APP_ERR_NOT_COMMITTED, /* image not marked complete */ APP_ERR_SIZE, /* code_size out of range */ APP_ERR_CRC, /* code CRC-32 mismatch */ APP_ERR_VMA, /* overlay buffer not at the link VMA */ APP_ERR_AUTH, /* host write refused: timestamp mismatch */ APP_ERR_CAP, /* required firmware capability missing */ }; /* Read + validate a slot header (no CRC of the code). */ uint8_t APP_ValidateSlot(uint8_t slot, app_header_t *out_header); /* Validate, load into the overlay, verify the code CRC, and run the app. * Returns when the app exits; the internal flash is never touched. */ uint8_t APP_LaunchOverlay(uint8_t slot); /* Launch the first installed app advertising this shortcut bit. */ uint8_t APP_LaunchOverlayShortcut(uint8_t shortcut); /* Cached availability of apps exposed as resident quick actions. */ uint8_t APP_OverlayShortcutMask(void); /* Host-tool slot management (external flash only, never brick-critical). */ uint8_t APP_SlotInfo(uint8_t slot, app_header_t *out_header); uint8_t APP_SlotErase(uint8_t slot); uint8_t APP_SlotWrite(uint8_t slot, uint32_t offset, const uint8_t *data, uint32_t len); #endif /* APPS_APP_OVERLAY_H */