Tighten runtime ownership and metadata resets

This commit is contained in:
CinderSocket
2026-03-24 14:40:37 -07:00
parent 5b6adadb81
commit bdb79a67ca
13 changed files with 114 additions and 44 deletions
+2 -1
View File
@@ -21,6 +21,7 @@ App(
"*.c",
"aeabi_uldivmod.sx",
"!hf_interface_fal/*.c",
"!wiegand_interface_fal/*.c",
],
fap_icon="icons/logo.png",
fap_category="NFC",
@@ -60,7 +61,7 @@ App(
apptype=FlipperAppType.PLUGIN,
entry_point="plugin_wiegand_ep",
requires=["seader"],
sources=["hf_interface_fal/wiegand.c"],
sources=["wiegand_interface_fal/wiegand.c"],
fal_embedded=True,
)
+37 -15
View File
@@ -79,7 +79,7 @@ Rules:
| `HF` | `Loaded` | plugin manager, plugin EP, plugin ctx may be non-`NULL`; pollers may be `NULL` |
| `HF` | `Active` | plugin manager, plugin EP, plugin ctx must be non-`NULL`; active pollers may be non-`NULL` |
| `HF` | `TearingDown` | teardown owns all pointer mutation; no scene code may touch HF runtime |
| `UHF` | `Unloaded` | all HF runtime pointers must be `NULL` |
| `UHF` | `Unloaded` | all HF runtime pointers must be `NULL`; UHF maintenance/probe flow owns mode runtime |
Invalid combinations are bugs:
@@ -92,14 +92,23 @@ Invalid combinations are bugs:
### HF startup
1. Verify `mode_runtime == None`
2. Verify `hf_session_state == Unloaded`
3. Load `plugin_hf.fal`
4. Resolve plugin entry point
5. Allocate plugin context
6. Set `hf_session_state = Loaded`
7. Set `mode_runtime = HF`
8. Start read and transition to `Active`
Legal startup paths:
1. Cold acquire:
- verify `mode_runtime == None`
- verify `hf_session_state == Unloaded`
- load `plugin_hf.fal`
- resolve plugin entry point
- allocate plugin context
- set `hf_session_state = Loaded`
- set `mode_runtime = HF`
- start read and transition to `Active`
2. Fast-path re-acquire:
- allowed only when the existing HF runtime is already coherent
- preserve the existing `Loaded` or `Active` state
- do not unload/reload the plugin
Any partial pointer/state combination must first normalize to `Unloaded`.
### HF teardown
@@ -113,7 +122,8 @@ Invalid combinations are bugs:
8. Set `hf_session_state = Unloaded`
9. Set `mode_runtime = None`
This order must be implemented in one worker-owned path and nowhere else.
The blocking fallback teardown path must use the same state machine and ordering.
This order must be implemented in one worker-owned release primitive and nowhere else.
## Forbidden actions
@@ -125,17 +135,28 @@ This order must be implemented in one worker-owned path and nowhere else.
- Starting UHF while HF session state is not `Unloaded`
- Starting HF while `mode_runtime == UHF`
## Plugin boundary
## UHF runtime
`hf_interface_fal/` is part of this repository. It is not a submodule.
`SeaderModeRuntimeUHF` is active only while the SAM maintenance/SNMP probe flow is active.
Rules:
- HF plugin source must remain in-tree and follow this contract.
- UHF runtime must be entered when the probe starts.
- UHF runtime must be cleared when the probe finishes.
- While UHF runtime is active, HF acquire must be rejected.
- UHF runtime must not coexist with any live HF runtime pointer.
## Plugin boundary
`hf_interface_fal/` and `wiegand_interface_fal/` are part of this repository. They are not submodules.
Rules:
- HF and Wiegand plugin sources must remain in-tree and follow this contract.
- The host/plugin boundary is narrow:
- host owns orchestration, SAM transport, UI routing, and lifetime
- plugin owns HF protocol execution only
- The plugin must not directly own scene transitions or global app teardown.
- each plugin owns only its protocol-specific execution
- Plugins must not directly own scene transitions or global app teardown.
## Change checklist
@@ -147,4 +168,5 @@ Before merging a change that touches HF/UHF/session code, confirm:
- no scene code mutates live HF runtime
- no teardown path mutates app-lifetime callback wiring
- all state-table invariants still hold
- `OWNERSHIP_MODEL.md` changed in the same patch as any lifetime/order/state-machine change
- this document still matches the implementation
+9
View File
@@ -0,0 +1,9 @@
# Seader embedded HF plugin sources
This directory is part of the main Seader repository.
It contains the embedded HF `.fal` plugin sources used by Seader:
- `hf.c`
- `hf_interface.h`
The HF plugin source path in `application.fam` must point at `hf_interface_fal/hf.c`.
-9
View File
@@ -1,9 +0,0 @@
# Seader embedded plugin sources
This directory is part of the main Seader repository.
It contains the embedded `.fal` plugin sources used by Seader:
- `wiegand.c`
- `hf.c`
The source paths in `application.fam` must point at `hf_interface_fal/*.c`.
+16
View File
@@ -71,6 +71,17 @@ static SeaderWorker* seader_get_active_worker(Seader* seader) {
return seader ? seader->worker : NULL;
}
static void seader_reset_cached_sam_metadata(Seader* seader) {
if(!seader) {
return;
}
seader->sam_version[0] = 0U;
seader->sam_version[1] = 0U;
seader->uhf_status_label[0] = '\0';
seader_uhf_snmp_probe_init(&seader->snmp_probe);
}
static bool seader_snmp_probe_send_next_request(Seader* seader) {
SeaderUartBridge* seader_uart = seader_get_uart(seader);
uint8_t* scratch = seader_uart ? (seader_uart->tx_buf + MAX_FRAME_HEADERS) : NULL;
@@ -99,6 +110,9 @@ static void seader_snmp_probe_finish(Seader* seader) {
return;
}
if(seader->mode_runtime == SeaderModeRuntimeUHF) {
seader->mode_runtime = SeaderModeRuntimeNone;
}
seader_sam_set_state(seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING);
}
@@ -107,6 +121,7 @@ static void seader_start_snmp_probe(Seader* seader) {
return;
}
seader->mode_runtime = SeaderModeRuntimeUHF;
seader_uhf_snmp_probe_init(&seader->snmp_probe);
seader_update_uhf_status_label(seader);
seader_sam_set_state(
@@ -648,6 +663,7 @@ void seader_worker_send_serial_number(Seader* seader) {
void seader_worker_send_version(Seader* seader) {
SamCommand_t samCommand = {0};
samCommand.present = SamCommand_PR_version;
seader_reset_cached_sam_metadata(seader);
seader->sam_present = true;
seader_update_sam_key_label(seader, NULL, 0U);
seader_sam_set_state(
-3
View File
@@ -21,9 +21,6 @@ void seader_scene_read_prepare(Seader* seader) {
seader->samCommand = SamCommand_PR_NOTHING;
}
memset(seader->read_error, 0, sizeof(seader->read_error));
if(seader->worker) {
seader_worker_reset_poller_session(seader->worker);
}
}
void seader_scene_read_cleanup(Seader* seader) {
+13 -4
View File
@@ -799,13 +799,13 @@ static void seader_hf_teardown_blocking(Seader* seader) {
return;
}
seader->hf_session_state = SeaderHfSessionStateTearingDown;
if(!seader_worker_acquire(seader) || !seader->worker || !seader->uart) {
FURI_LOG_W(TAG, "HF blocking teardown fallback");
seader_hf_plugin_release(seader);
return;
}
seader->hf_session_state = SeaderHfSessionStateTearingDown;
seader_worker_stop(seader->worker);
FURI_LOG_I(TAG, "HF teardown blocking");
seader_worker_start(
@@ -820,12 +820,11 @@ static void seader_hf_teardown_blocking(Seader* seader) {
void seader_hf_plugin_release(Seader* seader) {
furi_assert(seader);
seader->hf_session_state = SeaderHfSessionStateTearingDown;
if(seader->plugin_hf && seader->hf_plugin_ctx) {
seader->plugin_hf->stop(seader->hf_plugin_ctx);
seader->plugin_hf->free(seader->hf_plugin_ctx);
}
seader->hf_plugin_ctx = NULL;
seader->plugin_hf = NULL;
if(seader->poller) {
FURI_LOG_I(TAG, "Stopping host NFC poller");
@@ -841,12 +840,22 @@ void seader_hf_plugin_release(Seader* seader) {
seader->picopass_poller = NULL;
}
if(seader->plugin_hf && seader->hf_plugin_ctx) {
seader->plugin_hf->free(seader->hf_plugin_ctx);
}
seader->hf_plugin_ctx = NULL;
seader->plugin_hf = NULL;
if(seader->hf_plugin_manager) {
FURI_LOG_I(TAG, "Unloading HF plugin");
plugin_manager_free(seader->hf_plugin_manager);
seader->hf_plugin_manager = NULL;
}
if(seader->worker) {
seader_worker_reset_poller_session(seader->worker);
}
if(seader->mode_runtime == SeaderModeRuntimeHF) {
seader->mode_runtime = SeaderModeRuntimeNone;
}
+1 -1
View File
@@ -39,7 +39,7 @@
#include <Payload.h>
#include <FrameProtocol.h>
#include "hf_interface_fal/interface.h"
#include "wiegand_interface_fal/interface.h"
#include "hf_interface_fal/hf_interface.h"
#include <flipper_application/flipper_application.h>
#include <flipper_application/plugins/plugin_manager.h>
+1 -4
View File
@@ -24,9 +24,6 @@ static void seader_worker_release_hf_session(Seader* seader) {
}
seader_hf_plugin_release(seader);
if(seader->worker) {
seader_worker_reset_poller_session(seader->worker);
}
}
typedef struct {
@@ -222,7 +219,7 @@ void seader_worker_start(
seader_worker_stop(seader_worker);
}
seader_worker->stage = SeaderPollerEventTypeCardDetect;
seader_worker_reset_poller_session(seader_worker);
seader_worker->callback = callback;
seader_worker->context = context;
seader_worker->uart = uart;
+26 -7
View File
@@ -10,16 +10,35 @@ static size_t seader_uhf_append_family(
bool* wrote_any,
const char* name,
bool key_present) {
if(*wrote_any) {
pos += (size_t)snprintf(out + pos, out_size - pos, "/");
} else {
pos += (size_t)snprintf(out + pos, out_size - pos, "UHF: ");
*wrote_any = true;
int written = 0;
if(pos >= out_size) {
return out_size - 1U;
}
if(*wrote_any) {
written = snprintf(out + pos, out_size - pos, "/");
} else {
written = snprintf(out + pos, out_size - pos, "UHF: ");
*wrote_any = true;
}
pos += (size_t)written;
if(pos >= out_size) {
return out_size - 1U;
}
written = snprintf(out + pos, out_size - pos, "%s", name);
pos += (size_t)written;
if(pos >= out_size) {
return out_size - 1U;
}
pos += (size_t)snprintf(out + pos, out_size - pos, "%s", name);
if(!key_present) {
pos += (size_t)snprintf(out + pos, out_size - pos, " [no key]");
written = snprintf(out + pos, out_size - pos, " [no key]");
pos += (size_t)written;
if(pos >= out_size) {
return out_size - 1U;
}
}
return pos;
}
+9
View File
@@ -0,0 +1,9 @@
# Seader embedded Wiegand plugin sources
This directory is part of the main Seader repository.
It contains the embedded Wiegand `.fal` plugin sources used by Seader:
- `wiegand.c`
- `interface.h`
The Wiegand plugin source path in `application.fam` must point at `wiegand_interface_fal/wiegand.c`.