Compare commits

..
Author SHA1 Message Date
HarukiToreda f896c32fb7 trunk 2026-06-15 10:23:27 -04:00
HarukiToreda 2101785b3d Wipe messages 2026-06-15 10:14:38 -04:00
Ben MeadorsGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Copilot Autofix powered by AI
d878c81ce8 Clamp position precision on public / known-keys (#10665)
* Clamp position precision on public / known-keys (Compliance)

* Fix review comments: bounds check, all-channel precision clamp, disabled channel guard

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Refactor position precision comments for clarity and update channel configuration handling in AdminModule

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-14 18:53:18 -05:00
oscgonferandGitHub 8a0c7592cc Remove duplicate code from AQ telemetry, probably from merge conflict (#10708) 2026-06-14 06:38:49 -05:00
Benjamin FaershteinandGitHub 882ca0a216 Improve GPS stale probe recovery (#10714)
* Improve GPS stale probe recovery

* Address GPS review feedback
2026-06-14 06:30:20 -05:00
Benjamin FaershteinandGitHub 5d1c4f15b7 Make nRF52 lockdown support opt-in (#10712)
* Make nRF52 lockdown support opt-in

* Scope lockdown opt-in normalization to nRF52
2026-06-13 21:14:47 -05:00
745b53698a Mesh node t1 fixes (#10602)
* Fixes

* Remove BATTERY_LPCOMP_THRESHOLD

BATTERY_LPCOMP_THRESHOLD is dead code — in main-nrf52.cpp it's inside #ifdef BATTERY_LPCOMP_INPUT, which this board intentionally doesn't define. The threshold value is never reached.

* Trunk fix

* Update MotionSensor.cpp

* fix

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-06-13 19:57:28 -05:00
b938b63e8a fix a long-running CI bug that overran a lot (#10707)
* fix the fix

* Address Copilot review: add EXIT trap and clarify PKC comment

Add `trap` to kill meshtasticd on any early exit (python harness
failure, socket timeout) so CI never leaks a background process.

Reword the ARCH_PORTDUINO comment to make explicit that pki_encrypted=true
causes the from==0 plain-admin branch to be skipped, routing into the
PKC key-check — the underlying logic was correct all along.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Update PORTDUINO comment to reflect from==0 auth fix

The from==0 branch no longer requires !pki_encrypted (fixed upstream
in this branch), so update the simulator comment to reflect the actual
remaining reason for the early intercept: is_managed could still block
exit_simulator even for local packets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:46:29 -05:00
TomandGitHub b76e5e6ba4 removes NRF52832 from codebase - it is vestigal at best (#10709) 2026-06-13 18:46:08 -05:00
bbcc35e209 Stm32 general (#10700)
* Attempt to generalize ARCH_STM32

* Trunk

* One More ARCH_STM32

* Whoops, one snuck in there

* Fix comment to reflect define change

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-13 12:21:56 -05:00
Jonathan BennettandBen Meadors bede05356d Lora led rx (#10674)
* add optional LED_LORA to indicate LoRa TX

* Briefly flash LED_LORA on packet RX

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-06-13 12:18:22 -05:00
Jonathan BennettandBen Meadors 031e73bbe6 Use standard GPS enable pin, for smarter power control on M3 (#10671)
* Use standard GPS enable pin, for smarter power control on M3

* Enable GPS pin in variant.cpp initialization

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-06-13 12:05:24 -05:00
Ben Meadors 9ea1f0065a Update protos 2026-06-13 07:30:56 -05:00
Jonathan BennettGitHubBen MeadorsCopilotWesselcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>thebentern
8267bb22bd Packet Signing via XEdDSA (#10478)
* Test commit for XEdDSA support

* Update to Crypto lib in Meshtatic org

* Generate a new node identity on key generation (#7628)

* Generate a new node identity on key generation

* Fixes

* Fixes

* Fixes

* Messed up

* Fixes

* Update src/modules/AdminModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/mesh/NodeDB.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Figured it out!

* Cleanup

* Update src/mesh/NodeDB.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/mesh/NodeDB.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/modules/AdminModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update crypto commit hash

* Some fixes for xeddsa pr (#9610)

* fix: add null check for getMeshNode() in NodeInfoModule

getMeshNode() can return nullptr for unknown nodes. Dereferencing
without a check crashes the firmware when receiving NodeInfo from
a node not yet in the database.

* fix: enforce XEdDSA signature verification and prevent stripping

Previously, failed signature verification still allowed the packet
through, making signatures purely cosmetic. Now:

- Failed verification drops the packet (DECODE_FAILURE)
- Successfully verified nodes get HAS_XEDDSA_SIGNED bitfield set
- Unsigned packets from previously-signing nodes are rejected
- Log levels reduced from WARN/ERROR to DEBUG/WARN as appropriate

* fix: include packet metadata in XEdDSA signature

The signature now covers [fromNode | packetId | portnum | payload]
instead of just the payload bytes. This prevents:
- Replay attacks (different packetId fails verification)
- Reattribution (different fromNode fails verification)
- Portnum redirection (different portnum fails verification)

Also adds a key initialization check to xeddsa_sign (returns false
if XEdDSA keys are all zeros) and checks the return value in the
encode path.

* fix: handle existing key pair in AdminModule security config

When a user provides both a valid private key and public key via
admin config, the crypto engine's DH private key and owner public
key were never loaded. DMs and XEdDSA signing would silently break.

Add an else branch to load both keys into the crypto engine.

* perf: cache Ed25519 public key conversion in xeddsa_verify

curve_to_ed_pub() performs field element parsing, inversion, and
multiplication on every call. Since packets from the same node
tend to arrive in bursts, a single-entry cache avoids repeating
this expensive conversion for consecutive packets from one sender.

* fix: skip identity cleanup when node number is unchanged

createNewIdentity() was called on every generateCryptoKeyPair(),
including normal boots where the same key is regenerated. This
caused unnecessary NodeDB writes and old-node cleanup logic to
run when the node number hadn't actually changed.

Also fixes only zeroing byte[0] of the old node's public key
instead of clearing the entire array.

* fix: replace hardcoded 120 with derived XEDDSA_SIGNATURE_SIZE constant

The payload size check for XEdDSA signing used a magic number (120).
Replace with a derivation from DATA_PAYLOAD_LEN and XEDDSA_SIGNATURE_SIZE
so the limit adjusts automatically if constants change. This also
increases the max signable payload from 120 to 169 bytes, which is
still safe since the actual encoded size is checked after pb_encode.

* fix: add const qualifiers to XEdDSA verify and curve_to_ed_pub inputs

pubKey, payload, and signature parameters in xeddsa_verify are
input-only and should not be modified. Same for curve_pubkey in
curve_to_ed_pub.

* chore: remove commented-out old Crypto dependency in portduino.ini

* Leave out the admin module change for now

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>

* trunk

* protobuf re-update

* Protobufs

* Merge resolution fix

* Put XEDDSA on the right bit

* NodeDB update to new nodeInfoLite accessors, etc

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Refine unsigned packet rejection logic in Router (#10534)

* use hardware random to fill the first 32 signature bytes with entropy prior to signing.

* Add XEdDSA packet-signing policy tests and update dependencies for macos

* Minor fixes

* integrate XEdDSA support and update dependencies across multiple modules

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Wessel <github@weebl.me>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-06-13 06:45:56 -05:00
07a87a8254 security: runtime-toggleable MESHTASTIC_LOCKDOWN hardening for nRF52 (#10349)
* security: add MESHTASTIC_LOCKDOWN hardened build option

Meshtastic nodes ship with secrets on flash (channel PSKs, the device
private key, admin keys, wifi PSK) and over-the-wire access to admin
APIs that can re-key the mesh. Lose the device, at a border crossing,
in a raid, off a backpack, and an attacker reads everything in 30s
with a USB cable. There's no at-rest encryption, no client auth, the
screen leaks contents, and SWD is wide open. This adds an opt-in
hardened build for users who care.

-DMESHTASTIC_LOCKDOWN=1 on nRF52 (CC310) turns on:

  DEBUG_MUTE                         silence USB/serial logs
  MESHTASTIC_ENCRYPTED_STORAGE       AES-128-CTR + HMAC-SHA256 on
                                     LocalConfig / channels / NodeDB.
                                     Passphrase-gated DEK, TTL/boot
                                     unlock token, failed-attempt
                                     backoff (within-boot, wall-clock,
                                     persisted bootsSinceFail).
  MESHTASTIC_PHONEAPI_ACCESS_CONTROL per-connection auth gate. Secrets
                                     emitted as empty proto structs
                                     to unauthenticated clients.
  MESHTASTIC_ENABLE_APPROTECT        one-way UICR APPROTECT, reset
                                     applied same boot. Recoverable
                                     only via \`nrfjprog --recover\`,
                                     which also wipes the DEK.
  LockdownDisplay                    screen shows "LOCKED" when locked
                                     or idle 30s. OLED only; InkHUD /
                                     niche / device-ui not yet wired.

Wire format is the LockdownAuth / LockdownStatus pair from
meshtastic/protobufs#911 (AdminMessage tag 104, FromRadio tag 18).

Access-control state is a file-scope 6-slot table in PhoneAPI.cpp
keyed by \`this\`, not class members. Adding *any* per-instance field
to PhoneAPI breaks USB-CDC enumeration on the current nRF52 Adafruit
framework, one volatile bool was enough. Out-of-line side-steps it.

lockdown_auth is handled synchronously in PhoneAPI::handleToRadioPacket
rather than routed through the mesh Router into AdminModule. Two
reasons: the passphrase never travels through a routed MeshPacket
queue, and per-connection authorization runs while \`this\` is still on
the call stack. The previous async-via-router design lost connection
identity (g_currentContext was null by the time AdminModule processed
the auth), so per-connection unlock never actually took effect on the
originating client.

Non-nRF52: #warning, only DEBUG_MUTE activates. tools/lockdown_provision.py
drives provision / unlock / lock-now / watch over USB.

Display privacy is a screen-lock latch separate from storage-lock
state: shouldRedactDisplay() is true when storage is locked OR the
latch is set. Screen::setOn(false) sets the latch when the stock idle
timeout powers the display off (reusing config.display.screen_on_secs,
no second timer); it is cleared only when a client authenticates with
the passphrase. A device idling on the mesh keeps routing but hides
its screen until re-auth; button input wakes the backlight to the
LOCKED frame, not content. The earlier lockdown-specific 30s idle
timer is removed — it duplicated PowerFSM idle detection and showed a
misleading LOCKED screen on a merely-idle device.

Unlock-token TTL fix: a token carrying both a boot-count and a
wall-clock TTL is no longer destroyed when the RTC is invalid at cold
boot. The boot count is independently verifiable without a clock, so
the token falls back to boot-count enforcement instead of being
deleted. A token is only hard-rejected when its wall-clock TTL can be
evaluated and is found expired.

NodeDB::reloadFromDisk() after unlock is deferred to the main loop via
lockdownReloadPending rather than run inline on the transport callback
stack — the reload is too heavy for the BLE/serial task stack and was
resetting the device immediately after a successful unlock.

The screen-lock latch also swallows local input events in
InputBroker::handleInputEvent while it (or storage-locked) is set.
Without that, a blind operator could drive on-device menus, fire
canned messages, or change settings through the joystick/buttons even
though the screen content was hidden. PowerFSM is still triggered
first so the backlight wakes to the LOCKED frame; the event is dropped
before reaching the UI observers.

The screen-lock latch is initialised to true at boot, so even a
token-auto-unlocked cold boot comes up redacted. Otherwise an attacker
holding a screen-locked device could power-cycle it (the RAM latch
resets) and recover a content screen. After any boot, the operator
must authenticate from a client to reveal screen content.

MyNodeInfo.device_id is also redacted for unauthenticated clients —
it is a stable hardware identifier useful to an attacker for
fingerprinting / correlating the device across observations. The
public mesh fields (my_node_num, owner short/long name, public key,
hw model) are left as-is because they are already broadcast on-mesh.

ModuleConfig.mqtt is also redacted for unauthenticated clients —
MQTTConfig carries broker username, password, server address, and
root_topic. The empty MQTTConfig is emitted via the same zero-init
pattern as the other gated sections.

Uptime-based session limit (MESHTASTIC_LOCKDOWN_SESSION_DEFAULT_SECONDS)
caps how long a single auto-unlocked session can hold storage open,
measured in firmware millis() since unlock. 0 = unlimited (existing
token-only behavior, suitable for tower/infra nodes); non-zero arms a
timer on every passphrase unlock and on every token-auto-unlock that
inherits the value, since the cap is persisted in the token (token
format bumped to v2: adds sessionMaxSeconds, body 56→60 bytes).

On expiry the device revokes per-connection auth, re-engages the
screen-lock latch, and reboots WITHOUT deleting the token. Next boot
auto-unlocks via the boot count (decrementing it) and arms a fresh
session window. Hard exposure ceiling: bootsRemaining * sessionMaxSeconds.
Explicit user Lock Now still deletes the token (passphrase required to
recover); only session expiry preserves it.

Why uptime, not wall-clock: getValidTime() is fed by GPS/RTC/client
time pushes — all manipulable by an attacker with the device (GPS
spoof to roll the clock back, pull the RTC backup cell, Faraday-cage
the whole thing). millis() comes off the Cortex-M's internal cycle
counter, sealed inside the chip; the only way to reset it is a reboot,
which costs a boot from the on-flash token counter. APPROTECT remains
the load-bearing defense against forging higher boot counts via SWD.

A future LockdownAuth.max_session_seconds proto field will let the
client set this per-token; until that lands the build-time
MESHTASTIC_LOCKDOWN_SESSION_DEFAULT_SECONDS macro is the only source.

Session expiry now decrements the on-flash boot count in place and
re-arms the uptime timer WITHOUT rebooting, while budget remains.
Mesh routing keeps running across session boundaries; the device only
reboots when bootsRemaining reaches zero (rollback budget exhausted),
at which point it hard-locks and forces passphrase re-entry.

Each session boundary still: revokes per-connection admin auth so
clients must re-authenticate to see content, re-engages the screen
lock latch, and emits LockdownStatus{LOCKED, needs_auth, boots=N}
so connected clients see the decremented count and know to re-auth.
Storage stays unlocked (DEK in RAM) for continuity.

The boot count's role as the rollback ledger is unchanged — it
decrements monotonically once per session boundary, whether the
session ends in a reboot or an in-place roll. Attacker who power-
cycles to dodge the session timer still pays a boot via the existing
readAndConsumeToken decrement-at-load path. APPROTECT remains the
only defense against forging higher counts.

Net effect for an unattended/tower node with bootsRemaining=50,
sessionSeconds=3600: 50 hours of continuous mesh service, one
reboot at the end, vs. the previous design's 50 reboots over the
same period. Same exposure ceiling, far better uptime.

LockdownAuth.max_session_seconds (proto tag 5) is now consumed: when
non-zero the client value wins; 0 falls back to the firmware-side
MESHTASTIC_LOCKDOWN_SESSION_DEFAULT_SECONDS, matching the boots_remaining
sentinel convention. Protobufs submodule pin bumped to develop tip
which contains meshtastic/protobufs#916 (merged).

* security: drop dead is_managed allowlist for set_config(security).private_key

The 'isLockdownSecurityCmd' allowlist in handleReceivedProtobuf dates
from the pre-LockdownAuth design when the passphrase was smuggled
through SecurityConfig.private_key. With lockdown_auth handled
synchronously in PhoneAPI::handleToRadioPacket before any admin message
reaches the Router, this allowlist now serves no legitimate purpose
and lets an unauthenticated local client mutate security settings on
a managed device by setting private_key.size>=1 — including
potentially disabling is_managed itself.

Remove the allowlist. Managed-mode local admin now requires a
PhoneAPI connection that has already authenticated via lockdown_auth
(or, on the pki_encrypted branch below, a valid PKC admin key).

Resolves Copilot review feedback on src/modules/AdminModule.cpp:105.

* security: protect lockdown-status drain slot from concurrent writers

g_pendingLockdownStatus / g_hasPendingLockdownStatus are written from
multiple call sites (PhoneAPI::handleLockdownAuthInline on the BLE/USB
transport callback, AdminModule on the Router thread, main loop session
expiry) and read in getFromRadio() on whichever transport is draining
FromRadio. The struct read/write was unprotected, so a writer could
corrupt the slot mid-encode. Same pattern as nodeInfoMutex — wrap
both the queue path and the drain in a small lock. Drain re-checks
the bool under the lock to handle the case where another reader
grabbed the slot first.

Resolves Copilot review feedback on src/mesh/PhoneAPI.cpp:1560.

* security: derive readAndDecrypt size cap from caller buffer, not a hardcoded 64 KB

The MAX_PROTO_FILE_SIZE = 65536 + OVERHEAD ceiling was an absolute
constant chosen against a since-outdated assumption that 'meshtastic
proto files are well under 64 KB'. On variants where MAX_NUM_NODES
pushes the serialised NodeDatabase past 64 KB the legitimate file gets
rejected at load and the device treats its own real config as corrupt.

The caller already knows the maximum plaintext it expects (outBufSize).
Cap the ciphertext at outBufSize + OVERHEAD instead — this is the tightest
sound bound (anything larger could not possibly decode into the caller's
buffer), still defends against OOM / integer overflow, and scales with
the platform's actual NodeDB size rather than an arbitrary constant.

Resolves Copilot review feedback on src/security/EncryptedStorage.cpp:1327.

* docs: fix stale 'passphrase delivery via AdminModule' references in configuration.h

The lockdown overview comment block was written when passphrase delivery
ran through AdminModule's handleReceivedProtobuf. With the synchronous
refactor that path now lives in PhoneAPI::handleLockdownAuthInline,
called before the admin message reaches the Router. Update both the
nRF52 feature list and the non-nRF52 degraded-mode rationale to point
at the current code path.

Resolves Copilot review feedback on src/configuration.h:578 (and :604).

* docs: refresh unlock-token format doc to match v2 layout

The header comment for the UTOK file still described v1 (version 0x01,
no session_max_seconds, 71 bytes) even after the in-flight bump to
TOKEN_VERSION=0x02 and TOKEN_TOTAL_SIZE=75. The inline body-size
breakdown comment was also wrong (claimed 39 bytes and mismatched the
real NONCE_SIZE/AES_KEY_SIZE constants). Rewrite both to match the
actual on-flash layout and note how v1 tokens are handled on upgrade
(rejected via the version byte; passphrase re-entry mints a v2).

Resolves Copilot review feedback on src/security/EncryptedStorage.h:50.

* docs: correct session-limit comment re: token-auto-unlock behavior

The s_sessionMaxMs comment block claimed 'token-auto-unlocked
sessions have no session timer (the session feature is a
passphrase-unlock-only knob)'. Stale: readAndConsumeToken() now
persists sessionMaxSeconds in the token file and re-calls
setSession() from the token-load path, so token-auto-unlocked
sessions DO inherit the same cap (and consumeSessionBoot() re-arms
in place between sessions on a single boot). Update the comment to
match.

Resolves Copilot review feedback on src/security/EncryptedStorage.cpp:72.

* docs: clarify input-swallow gate re: screen-lock latch vs storage state

The previous comment said input is swallowed 'until a client authenticates
and unlockScreen() clears the latch (or storage is unlocked)'. The
parenthetical was misleading: storage being unlocked is not in itself
enough to clear the latch — the latch persists across the
storage-unlocked-but-screen-locked steady state, and only an explicit
unlockScreen() (called from a successful passphrase auth path) clears
it. Reword so the only-passphrase-clears-the-latch invariant is
explicit and local input is named as something that does NOT clear it.

Resolves Copilot review feedback on src/input/InputBroker.cpp:134.

* docs: fix reloadFromDisk() trigger comment in NodeDB.h

The header still claimed reloadFromDisk() is called by AdminModule
after a successful passphrase op. With the synchronous PhoneAPI
refactor the actual trigger is PhoneAPI::handleLockdownAuthInline
setting lockdownReloadPending, with main.cpp's loop() dispatching
the heavy reload on the main thread (the transport callback stack
isn't large enough). Update the comment to point at the real path
and explain why the deferral exists.

Resolves Copilot review feedback on src/mesh/NodeDB.h:393.

* style: clang-format lockdown sources

Apply trunk clang-format (16.0.3) to satisfy the format check.

* style: black-format lockdown_provision.py

Satisfy the trunk black formatter check.

* security: drop unused v1 EncryptedStorage formats and migration

This storage layer has never shipped, so there are no v1 DEK files,
v1 unlock tokens, or v1 backoff records anywhere to stay compatible
with. Remove the dead compatibility machinery:

- legacy init() (FICR-only KEK, no passphrase) — had no callers
- deriveKEKv1() / loadDEKv1() and the v1->v2 DEK migration paths in
  provisionPassphrase() and unlockWithPassphrase()
- the 5-byte v1 backoff file format

Also drop the now-pointless version byte from the on-disk MENC, MDEK,
and UTOK formats. Each is identified by its 4-byte magic (and, for the
keyed formats, its HMAC); with only one version that will ever exist,
the version field added nothing. Sizes shrink by one byte each
(overhead 54->53, DEK 66->65, token 75->74).

Rename the surviving helpers to drop the _v2 suffix (deriveKEK,
loadDEK, saveDEK, KEK_DOMAIN). No behavioral change for provisioning,
unlock, token consumption, or session handling.

Verified with an nRF52 lockdown build (rak4631).

* fix(lockdown): harden auth-table and lockdown_auth handler (audit)

Audit findings addressed:

C3 — `~PhoneAPI()` now clears its auth slot unconditionally. The previous
slot-clear in `close()` was gated on `state != STATE_SEND_NOTHING`, so a
PhoneAPI that never reached config (or that already closed) left
`slot.who` pointing at freed memory; a future PhoneAPI heap-allocated at
the same address would inherit the prior session's authorization through
`findOrAllocSlot`.

C4 — All access to `g_authSlots`, `g_authEpoch`, and `g_currentContext` is
now serialised through `g_authSlotsMutex`. Previously these were touched
without locking from BLE/USB/TCP/Router tasks, so two parallel slot scans
could hand out the same slot and mid-update reads could observe
authorized=true alongside a stale epoch. Granularity is fine — every
critical section is a short linear scan over six entries, and getFromRadio
(which calls `getAdminAuthorized()` per redaction check) tolerates the
brief blocking.

A4 / H1 — `lock_now` now requires the originating connection to be
already authorized. Previously any unauthenticated client (BLE/USB/TCP)
could submit `lockdown_auth { lock_now=true }` and force a reboot,
which was a trivial local-presence DoS — an attacker near the radio
could brick-loop it indefinitely. The original "panic button without
auth" property is dropped; panic now requires the operator to have
passphrase-unlocked the connection.

H2 — Empty-passphrase `lockdown_auth` (with `lock_now=false`) used to
silently return success. The client received no feedback distinguishing
that case from a real success, and an attacker could probe lockdown
state for free. Now emits UNLOCK_FAILED with no backoff increment
(empty-passphrase is more likely a client bug than an attack, but the
honest signal still lets the client correct itself).

H14 — `la.boots_remaining > 255` previously truncated silently
(256 → 0 → mapped to TOKEN_DEFAULT_BOOTS=50; 257 → 1). Honest clients
could not detect the misbehavior. Now rejected explicitly with
UNLOCK_FAILED.

L1 — The `to == nodeDB->getNodeNum()` allowance in the unauth ToRadio
gate now also requires `getNodeNum() != 0`. During the locked-default
boot path `getNodeNum()` returns 0, so a packet with `to=0` could
otherwise satisfy the equality and bypass the gate.

L2 — Comment added on `g_authEpoch` wrap. Practically unreachable
(2^32 lockNow events on one boot), but worth recording the behavior.

M17 — `findOrAllocSlot_LH` now evicts the first unauthorized stale slot
when the table is full of non-nullptr entries, rather than failing
closed. Authorized slots are never evicted — they represent live
operator sessions. Fail-closed (with LOG_WARN) only when every slot
holds a different live authorized PhoneAPI, which would require seven
simultaneous authed connections.

M18 — `s_screenLocked` is now `std::atomic<bool>` with relaxed ordering.
Plain bool happened to work on single-core Cortex-M4 today but breaks
silently if lockdown ports to ESP32 / RP2040, or under LTO whole-
program elision.

Verified with an nRF52 lockdown build (rak4631).

* fix(lockdown): gate every admin op on per-connection auth + storage unlock

Audit findings addressed:

H6 — Unauthenticated local clients could previously set_config / set_module_config /
set_channel etc. on a lockdown device whenever is_managed was unset.
The previous gate inside AdminModule's is_managed branch consulted
PhoneAPI::isLocalAdminAuthorized(), which reads a global g_currentContext
set during synchronous PhoneAPI dispatch — but AdminModule runs on the
Router task, by which time the dispatch task has exited and the global is
unrelated to the originating connection. The check was both broken (always
false on Router, so even authed clients were rejected) and unsafe (when it
did fire, the wrong connection could be authorized).

The fix relocates the gate to PhoneAPI::handleToRadioPacket, where dispatch
is synchronous and getAdminAuthorized() can be trusted. The admin payload
is already decoded there to extract lockdown_auth; extend the same branch
so that any non-lockdown_auth admin variant from an unauthorized connection
is dropped before ever reaching the Router queue.

H7 — Same root cause: get_config_request / get_module_config_request /
get_channel_request handlers returned full security/network/mqtt content
to unauthorized local clients. With the H6 gate in PhoneAPI, these
requests never reach AdminModule, so handleGetConfig / handleGetModuleConfig
/ handleGetChannel are only callable from authorized connections.

H9 — Remote admin (PKC-authorized peers, mesh-relayed admin) bypassed
lockdown entirely. If admin_keys were baked in via USERPREFS or set on a
prior unlocked boot, a remote attacker could drive factory_reset /
set_config against a locked device before the operator ever unlocked it.
Added an EncryptedStorage::isUnlocked() early-return at the top of
AdminModule::handleReceivedProtobuf. The local lockdown_auth path is
unaffected because PhoneAPI handles it synchronously before AdminModule
runs.

H10 — Removed g_currentContext, the ContextGuard, authorizeLocalAdmin(),
and isLocalAdminAuthorized() entirely. The audit's race (Router-thread
reads a pointer set by an unrelated parallel dispatch and authorizes the
wrong PhoneAPI) and the always-false-on-Router behavior both disappear
with the code that produced them. The PKC-admin auto-authorize path is
gone — PKC admin and the per-connection lockdown auth are now
independent: clients using PKC admin from a local app must also send
lockdown_auth to unlock the redacted FromRadio stream.

Cleaned up AdminModule's is_managed branch: under lockdown the
PhoneAPI-layer gate has already done its job, so no additional check
is needed; without lockdown the legacy is_managed-blocks-plain-admin
semantics are preserved.

Verified with an nRF52 lockdown build (rak4631).

* fix(lockdown): hold radio silent until storage is unlocked

Audit finding H8: while locked, the device beaconed nodeinfo and
telemetry on the public LongFast default PSK and routed incoming default-
channel packets through the locked router. The locked-default boot path
in NodeDB::loadFromDisk installs config via installDefaultConfig, which
honours USERPREFS_CONFIG_LORA_REGION (the common shape for managed
deployments) and synthesises the default LongFast channel. So a locked
device on managed firmware came up TX-enabled on a well-known PSK
before any operator interaction.

Force config.lora.region = UNSET in the locked-boot block.
RadioLibInterface gates both TX (startSend) and RX (readData) on
region != UNSET — locked devices no longer initialise the SX12xx for
either direction. Also set tx_enabled = false for any code path that
checks the flag directly without consulting region.

reloadFromDisk() restores the persisted lora config once the operator
unlocks. Note: until the audit's M8 (radio re-init after reload, the
upcoming commit 5 in this remediation series) lands, an unlocked
device may need to reboot before its radio fully comes up under the
real config; this is no worse than the pre-fix state, where the radio
was already running on the wrong (default) config and any real config
change required an explicit reconfigure or reboot anyway.

Verified with an nRF52 lockdown build (rak4631).

* fix(lockdown): per-connection status queue, redaction expansion, log/banner mute (audit)

M14 — Replaces the single file-scope LockdownStatus slot with a per-
PhoneAPI table keyed by PhoneAPI*, parallel to the auth-slot table and
sharing g_authSlotsMutex. Previously a status produced for connection
A (UNLOCKED with the active TTL, or UNLOCK_FAILED with a backoff)
could be drained by connection B before A read it, leaking A's auth
state to B. queueLockdownStatus is now a per-instance method writing
to this->slot. A new static broadcastLockdownStatus exists for the
main-loop session-expiry callers that have no PhoneAPI* in hand —
those want every connected client to learn about the session roll,
which is the only legitimate broadcast use case. hasPendingLockdownStatus
is a const helper for the FromRadio available()/drain check.

M13 — buildStatus_LH (the single point where lock_reason crosses into
the on-wire LockdownStatus) collapses any token_* reason to a generic
"locked" before emission. The specific reasons (token_hmac_fail,
token_wrong_size, token_bad_magic, token_boots_zero, token_expired,
token_dek_fail, token_missing) still go to local logs, but no longer
tell an unauthenticated client that the firmware noticed their
tampering / rollback / corrupt-file attempt.

M15 — Extended the STATE_SEND_MY_INFO redaction (previously device_id
only) to also wipe pio_env and min_app_version for unauth clients —
both are pure build-fingerprint vectors that tell an attacker which
known issues to probe. Kept my_node_num (broadcast on the mesh anyway)
and nodedb_count (clients need it post-unlock to decide whether to
pull the node DB). Added equivalent redaction for STATE_SEND_METADATA:
the whole DeviceMetadata struct is wiped for unauth clients
(firmware_version, device_state_version, hw_model, hw_model_string,
has_bluetooth/has_wifi/has_ethernet, role, position_flags,
excluded_modules). Clients re-fetch after authenticating.

M16 — LoRa config is now whitelisted for unauth clients to the set
that is intrinsically observable on the air anyway: region,
modem_preset, use_preset, channel_num, hop_limit. Operator-private
knobs (ignore_incoming, override_duty_cycle, override_frequency,
sx126x_rx_boosted_gain, tx_power, ignore_mqtt, fem_lna_mode,
config_ok_to_mqtt) are zeroed. The whitelist is built as a fresh
LoRaConfig stack copy rather than masked in place to avoid touching
the persisted struct.

M12 — Skip the DEBUG_MUTE "we are muted, FYI" banner under
MESHTASTIC_LOCKDOWN. The banner spilled APP_VERSION / APP_ENV /
APP_REPO over USB CDC even with all other logging suppressed, which
defeats the muting in lockdown builds and gives a USB-attached
attacker a free firmware-fingerprint primitive.

L9 — Removed the numeric backoff value from the LOG_WARN unlock-
failed message. The client receives backoff_seconds via the
UNLOCK_FAILED status; printing it again to USB serial under
non-DEBUG_MUTE builds (i.e. MESHTASTIC_LOCKDOWN_DEBUG dev builds)
was the only place it appeared in logs.

Verified with an nRF52 lockdown build (rak4631).

* fix(lockdown): atomic post-unlock reload with corruption surface (audit)

Closes M6, M7, M8, M9 from the lockdown security audit.

M6 — handleLockdownAuthInline no longer flips the connection to
authorized or emits UNLOCKED on the cold-unlock path (the first
successful passphrase verify after a locked boot). The client keeps
seeing LOCKED until reloadFromDisk has actually populated config /
channelFile / nodeDatabase with the operator's real values. Without
this, the window between the auth call and the main-loop reload
exposed two race-friendly bugs: (a) the client could read the
locked-default placeholders as if they were the real config, and (b)
a set_config in the window would silently overwrite a corrupted
baseline once the reload swapped values in.

A new per-status-slot bool pendingUnlockAfterReload records that the
connection is mid-unlock. The re-verify path (storage already
unlocked) is unchanged and authorizes immediately — there is nothing
to reload.

M7 — reloadFromDisk now holds a new file-scope mutex
(g_reloadFromDiskMutex) against itself, parks the radio in sleep
mode before swapping config / channelFile, and reconfigures the
radio with the now-real settings after. Other readers of config.lora
/ channelFile / nodeDatabase do not take this lock today; closing
those races is a wider locking-discipline change outside the audit's
M7 scope. The radio standby+reconfigure prevents the SX12xx from
sitting in a half-old/half-new register set across the swap, which
otherwise required a reboot to recover from.

M8 — RadioInterface::reconfigure() is now called at the end of a
successful reload, so the SX12xx register set actually reflects
the unlocked operator settings (region, modem preset, channels)
rather than staying on the locked-default placeholder. Routed through
a new Router::getRadioIface() accessor — the radio interface is
owned by Router as a unique_ptr and was not exposed.

M9 — NodeDB::loadProto now sets a NodeDB::storageCorruptThisLoad
flag whenever an encrypted file fails to decrypt or proto-decode.
reloadFromDisk consumes the flag and returns false on any failure
instead of silently falling back to defaults. main.cpp's reload
service then calls EncryptedStorage::lockNow() and
PhoneAPI::revokeAllAuth(), and the new
PhoneAPI::completePendingUnlocks(false) emits LOCKED(storage_corrupt)
to every pending connection — they stay unauthorized so any
set_config they send is dropped at the existing unauth gates.
The lock_reason string passes through buildStatus_LH's M13
redaction unchanged because it does not start with token_.

The success path goes through PhoneAPI::completePendingUnlocks(true)
which authorizes each pending connection, emits UNLOCKED with the
current TTL, and clears the screen-lock latch once. Snapshots the
target PhoneAPI* list outside the auth-table lock to avoid re-entry
when setAdminAuthorized takes the same lock.

Verified with an nRF52 lockdown build (rak4631).

* fix(lockdown): UI/pairing fixes for first-pair + content-flash + e-ink (audit)

Closes H13, M19, M20, L4 from the lockdown audit. (L3 dropped per
explicit decision — battery level is not a meaningful security side
channel.)

H13 — BLE pairing PIN was suppressed by the lockdown lock screen on
locked devices. Screen.cpp updateUiFrame's lockdown short-circuit
intercepts before ui->update() runs, so the pairing-PIN overlay
banner that NRF52Bluetooth::onPairingPasskey queued never painted.
Net effect: a freshly-locked device on first BLE pair could not be
unlocked over BLE because the operator could never see the PIN —
chicken and egg.

Adds a new notificationTypeEnum::pairing_pin value and special-cases
it in the short-circuit: paint the LOCKED frame first (so the
underlying background remains the redacted view, never dashboard
content) then let ui->update() composite the PIN banner overlay on
top. The PIN itself is an ephemeral pair-handshake artifact
(regenerated per attempt, dies on banner timeout) and is not
operator content, so this does not regress the redaction guarantee.

NRF52Bluetooth::onPairingPasskey switches from showSimpleBanner to
showOverlayBanner with notificationType = pairing_pin so the
short-circuit's lookup matches.

M19 — Brief content-visible window on Screen::handleSetOn(true)
wake. OLED GDDRAM physically retains the last-rendered frame while
the panel is powered off; the next ui->update() after displayOn() is
async, so an observer (or shoulder-surfer) could see the previous
frame's content for 16-50 ms on every wake. Under MESHTASTIC_LOCKDOWN
we now paint the LOCKED frame into GDDRAM in handleSetOn(false)
before calling displayOff(). On wake the only thing the panel can
flash is the redacted view. Gated on lockdown only — non-lockdown
builds keep the previous frame as a UX cue.

M20 — E-ink panels physically retain the last-rendered image
without power. A power-cycled lockdown handheld kept showing
operator-identifying content (position, messages, nodeinfo) until
the firmware's first natural refresh — which on e-ink can be
seconds into boot. Now, under MESHTASTIC_LOCKDOWN && USE_EINK, the
panel init path in Screen::setup() paints the LOCKED frame and
forces a full refresh (forceDisplay) immediately after ui->init()
and before any other rendering. Persistent pixels are wiped to the
redacted view before an observer can see them. Build-tested on
seeed_wio_tracker_L1_eink; hardware-verified visual confirmation
is pending a T-Echo session.

L4 — Screen::blink() bypasses the normal ui->update() path that
the lockdown short-circuit gates. It draws arbitrary geometry, not
node data, so it does not actually leak today; but any future
change that puts content into blink would silently leak past
redaction. Added an early-return on shouldRedactDisplay() to make
the function honor the redaction contract.

Verified with nRF52 lockdown builds on both rak4631 (OLED) and
seeed_wio_tracker_L1_eink (e-ink).

* fix(lockdown): refuse APPROTECT on vulnerable silicon, gate on provision (audit)

Closes M22 and M23 from the lockdown audit.

M22 — APPROTECT lockout on nRF52840 is publicly known to be bypassable
on every silicon revision shipping in current Meshtastic hardware
(AAB0..AAF0) via SWD glitching, per LimitedResults' published research
on the nRF52 series. Engaging APPROTECT on these revisions has two
bad properties: (1) the lockout is irreversible without a destructive
nrfjprog --recover, and (2) it gives the operator a false sense of
security because the lockout itself can be defeated by anyone with
ten minutes and a glitcher.

enableAPProtect() now reads FICR.INFO.VARIANT (encoded as a 4-byte
ASCII word) and refuses to engage on any known-vulnerable revision,
logging the variant so the operator knows their device's specific
build code. To override (e.g. for end-to-end testing of the engage
path on hardware that's known affected), rebuild with
-DMESHTASTIC_APPROTECT_OVERRIDE_VULNERABLE_SILICON=1.

The vulnerable list is explicit and easy to update: any future
revision shown to be fixed can be removed from the list and APPROTECT
will engage on it as before.

M23 — APPROTECT engagement moved from very early in setup() to
after fsInit() + EncryptedStorage::initLocked(), and gated on
EncryptedStorage::isProvisioned(). A misconfigured CI build of a
lockdown variant flashed to a dev board would otherwise burn SWD on
first boot before the operator had set any passphrase, taking the
board out of the development/recovery workflow with zero real
security benefit (there is no DEK to protect on an unprovisioned
device). Engagement now follows operator intent: SWD locks only
once they've committed to lockdown via passphrase provisioning.

The SWD-attachable window between boot and APPROTECT engagement
widens slightly from this reorder (now ~hundreds of ms while fsInit
runs) but APPROTECT remains effective on the only payload it could
protect (the in-RAM DEK loaded by initLocked which now runs *after*
APPROTECT for already-provisioned devices).

Verified with an nRF52 lockdown build (rak4631).

* tools: harden lockdown_provision.py (audit)

Closes M26-M30 and addresses L7.

M26 — passphrase input. --passphrase on argv now requires
--insecure-passphrase-on-cmdline as an explicit acknowledgement;
without it the tool refuses and points at --passphrase-file or the
interactive prompt. --passphrase-file refuses to read anything that
isn't mode 0600 (so a passphrase another user can read off the
filesystem doesn't silently succeed). With neither, the tool reads
the passphrase via getpass.getpass — and on 'provision' double-prompts
with a confirm.

M27 — provision now requires an explicit 'yes' confirmation unless
--yes is passed, after printing the warning that the passphrase
cannot be recovered. The double-passphrase prompt is built into
gather_passphrase(confirm=True). Reduces the chance of a typo
binding a device to an unrecoverable passphrase.

M28 — 'lock' subcommand gains a 'lock-now' alias, matching how the
audit and wire docs refer to it everywhere. Both forms now require
'yes' confirmation unless --yes is set, so an accidental command
doesn't immediately reboot the device into a locked state.

M29 — the 4-second sleep is gone. Replaced with a StatusFuture
single-shot that the FromRadio interceptor signals when the next
LockdownStatus arrives. provision/unlock/lock wait up to --wait
seconds (default 8) for the actual reply and exit non-zero with the
device's reason on UNLOCK_FAILED, surfacing backoff_seconds in the
error line. Exit codes are now meaningful:
  0 = UNLOCKED
  1 = no status / unexpected
  2 = NEEDS_PROVISION (or a precondition fault: missing pkg, bad args)
  3 = LOCKED (ambiguous: device reported locked rather than the
              expected unlocked result)
  4 = UNLOCK_FAILED
This lets ops scripts decide what to do without parsing stdout.

M30 — top-of-file docstring gained an explicit SECURITY MODEL block
that names the threat model (USB-only, passphrase cleartext on the
cable) and forbids extension to TCP/BLE/UDP without a redesign. A
runtime banner reprints the headline on every invocation. --port
values starting with tcp:/tcp://, ble:/ble://, udp:/udp://, ws:/wss:
are rejected at argument parse before any connection attempt; a
copy-paste of an example into a context with a different --port
cannot silently leak credentials to the wire.

L7 — private meshtastic APIs (_handleFromRadio, _sendToRadio,
_generatePacketId) are still in use because the lib does not yet
dispatch LockdownStatus on a public pubsub topic and there is no
public seam for raw ToRadio. Their use is now wrapped in
getattr-with-clear-error so a future lib version that removes them
produces an actionable error instead of an obscure traceback. The
top-of-file note explains why we're on the private surface.

Verified end-to-end on hardware (R1-Neo + Seeed Wio Tracker L1)
during the audit-remediation hardware test pass:
  - provision (interactive, with confirm and double-prompt)
  - unlock (success returns UNLOCKED + boots TTL)
  - watch (passive listener emits LockdownStatus events)
  - lock-now (with --yes)

* fix(lockdown): H13 — render pairing PIN steady over LOCKED frame

Two bugs in the H13 fix from commit 614b7f001:

1. NotificationRenderer::drawBannercallback's switch had no case for
   the new notificationTypeEnum::pairing_pin. The function fell through
   to no-op so the banner never rendered. Added pairing_pin alongside
   text_banner so it dispatches to drawAlertBannerOverlay (same
   rendering, distinct type so the lockdown short-circuit in Screen.cpp
   can recognise it).

2. updateUiFrame's lockdown short-circuit called ui->update() to
   composite the banner. That redraws the current carousel frame
   (the dashboard) into the host framebuffer BEFORE the overlay
   paints, so the panel flashed dashboard content under the banner
   on every cycle. Replaced with a direct call to drawBannercallback
   so only the banner box is painted on top of the LOCKED pixels.

Also: drawLockdownLockScreen used to commit to the panel
(display->display()) at its end. With the banner overlay then
painting and committing a second time, the panel visibly flickered
between 'just LOCKED' and 'LOCKED + banner' on every render cycle.
Split into drawLockdownLockScreenIntoBuffer (no commit) for the
lockdown short-circuit, and a thin drawLockdownLockScreen wrapper
that calls Buffer + display() for the other call sites that don't
composite anything on top. The short-circuit now commits exactly
once per frame after both LOCKED + any overlay are in the buffer.

Verified end-to-end on hardware (Seeed Wio Tracker L1, OLED):
fresh BLE pair against a locked device now shows the pairing PIN
steadily on top of the LOCKED frame, no flicker, no dashboard
leak, and pair completes normally.

* fix(lockdown): backoff MAC + atomic writes + fault wipe + size cap (audit)

Closes H3, H4, H12, M10, M11, M25 from the lockdown audit. Non-format-
breaking: existing devices keep their .dek and .unlock_token but their
old plaintext .backoff file (6 bytes, no MAC) is silently rejected as
tampered on first read and reseeded with the MAC'd 38-byte format on
the next failed-attempt OR successful unlock.

H3 — Pre-increment the failed-attempt counter BEFORE running the HMAC
verify in unlockWithPassphrase. The previous order wrote the counter
only after a failed verify, so an attacker glitching the chip between
verify and write could skip the increment and bypass backoff. The
slot is now reserved atomically up front; the success path writes
attempts=0 to clear the reservation. Worst case for a legitimate user
who power-cycles mid-success is one phantom attempt — backoff
recovers next try.

H4 — .backoff file is now MAC'd with HMAC-SHA256(ephemeralKEK,
"backoff-auth" || body) (32-byte tag), and written atomically via
SafeFile (tmp + readback verify + rename). readBackoff treats
missing / wrong-size / MAC-fail uniformly as max-attempts (255) so an
attacker who deletes or rewrites the file can only INCREASE the wait,
never decrease it. clearBackoff() now writes an attempts=0 sentinel
instead of removing the file, so 'missing == tamper' is unambiguous
post-provision. bumpBootsSinceFailOnBoot() skips on un-provisioned
devices to avoid false 'tamper' detection during the legitimate fresh
window between fsInit and provisionPassphrase.

H12 — saveDEK and writeUnlockToken now write via SafeFile in
fullAtomic mode (tmp file + readback verify + atomic rename) instead
of remove-then-open-then-write. Power loss during a DEK or token
write previously left the device unable to unlock — the encrypted
prefs files are unreadable without a valid DEK. The atomic path
rolls back to the previous file on partial write.

M10 — readAndConsumeToken's 74-byte stack buffer (entire wrapped
DEK + HMAC, explicitly called out by the audit as never wiped before
return) is now a meshtastic_security::ZeroizingBuffer that the
destructor scrubs on every return path. Same treatment for the
computedHmac stack array next to it, and for the new backoff state
buffers in readBackoff / writeBackoff / computeBackoffHmac. Removes
the manual secure_zero calls those buffers had on success paths and
fixes the missing wipes on the failure-return paths.

M11 — Added EncryptedStorage::secureWipeKeys() public API that
zeros dek/kek/ephemeralKek in BSS without touching flash, no
logging, no locks (safe from interrupt context). HardFault_Impl now
calls it as the very first thing on entry, before the diagnostic
print / coredump path runs, so a hard-fault crash dump won't capture
the DEK / KEK material that the rest of the module leaves in RAM.

M25 — migrateFile now refuses to allocate a buffer for any file
larger than 64 KiB. The legitimate ceiling is well under that on
every supported variant; anything larger is either corrupt or a
DFU-injected OOM attempt.

Verified with an nRF52 lockdown build (rak4631).

* fix(lockdown): MENC header MAC + token rollback counter (audit)

Closes M2 and M4 from the lockdown audit. **FORMAT-BREAKING** — devices
provisioned with prior lockdown firmware must factory-erase /prefs and
reprovision; the previous tokens and encrypted prefs files will not
decrypt under the new HMAC/body layouts.

M2 — The HMAC on MENC encrypted proto files now covers the full on-disk
header (4-byte magic + 13-byte nonce + 4-byte plaintext_len + ciphertext)
instead of just (nonce + ciphertext). Without this, magic and
plaintext_len were integrity-protected only by the equality check
`plaintextLen == ciphertextLen` — which holds today (no padding /
compression / AAD) but would silently produce length-oracle and
downgrade vulnerabilities the instant any of those got added. Putting
the header inside the MAC closes that pre-condition cleanly. The
verify side in readAndDecrypt and the compose side in encryptAndWrite
update in lockstep.

M4 — UTOK gains a 4-byte monotonic counter field inside its MAC'd
body. The highest counter ever issued is persisted to a new
/prefs/.tokmono file MAC'd with HMAC-SHA256(ephemeralKEK,
"tokmono-auth" || counter). On every readAndConsumeToken, any
token whose counter is less than the persisted value is rejected as
a rollback attempt and deleted. Defeats the audit's threat: an
attacker who once captured a token (e.g. bootsRemaining=255 from
before the operator lowered the policy) tries to write it back to
disk later. Counter is incremented monotonically across the device's
lifetime so any captured snapshot loses to the persisted max-seen.

Self-heal: a token whose counter exceeds the persisted value (e.g.
the .tokmono write itself failed after the token committed, or the
.tokmono got wiped via factory-erase) is accepted AND the counter
file is promoted to match. This avoids spuriously rejecting valid
tokens after partial-update recovery.

Threat model caveat (consistent with C2 acceptance): an attacker who
has both flash extraction AND FICR can recompute the .tokmono MAC
and restore a matching pair (.unlock_token + .tokmono) from an
earlier capture. M4 raises the bar to that combined capability;
the flash-write-only attacker is now blocked.

Verified with an nRF52 lockdown build (rak4631).

MIGRATION: devices already provisioned with the prior lockdown
firmware will fail to auto-unlock at boot (token format mismatch),
fall back to LOCKED(needs_auth), and every passphrase attempt will
fail because the encrypted /prefs files are HMAC'd against the old
input. Recovery is: factory-erase via the bootloader UF2 then
re-provision via lockdown_provision.py or the Android app.

* feat(lockdown): make lockdown a runtime client-toggleable setting

Converts MESHTASTIC_LOCKDOWN from a per-variant compile-time flag that
forced lockdown ON into an internal capability that is ALWAYS compiled
in for nRF52 and gated purely at runtime by whether a passphrase has
been provisioned. A device that has never been provisioned (or that the
operator disabled) behaves exactly like stock firmware.

Build/config:
- configuration.h auto-defines MESHTASTIC_LOCKDOWN (+ ACCESS_CONTROL,
  ENCRYPTED_STORAGE, APPROTECT-capable) for ARCH_NRF52 unconditionally.
  No variant sets -DMESHTASTIC_LOCKDOWN anymore. Flash-constrained
  variants can opt out with -DMESHTASTIC_EXCLUDE_LOCKDOWN=1. DEBUG_MUTE
  is no longer coupled to lockdown (a capable-but-off device must log
  normally). rak4631 lands at 96.2% flash with lockdown always-in.

Runtime predicate:
- EncryptedStorage::isLockdownActive() == isProvisioned() (.dek exists)
  is the single source of truth for active/inactive.
- PhoneAPI::getAdminAuthorized() returns true when lockdown is inactive,
  so every existing redaction gate no-ops on a capable-but-off device
  with no per-site changes. The locked-boot defaults path (NodeDB), the
  AdminModule storage-locked gate, the screen-redaction predicate, and
  the plaintext->encrypted migrate block are all additionally gated on
  isLockdownActive() so an un-provisioned device loads/serves plaintext
  normally.
- sendConfigComplete emits LockdownStatus{DISABLED} when capable-but-off
  so the client renders its toggle OFF.

Enable (off->on): client provisions a passphrase. provisionPassphrase
generates the DEK; the existing reload path encrypts the plaintext
config in place (migration runs live with the DEK in RAM) and authorizes
the connection -> UNLOCKED. No reboot.

Disable (on->off): LockdownAuth{passphrase, disable=true}. PhoneAPI
verifies the passphrase (loads DEK), sets lockdownDisablePending; the
main loop runs NodeDB::disableLockdownToPlaintext() which decrypts every
pref via EncryptedStorage::migrateFileToPlaintext() then
removeLockdownArtifacts() deletes the DEK/token/counter/backoff (the
.dek delete is the atomic commit), then reboots into normal mode.
Power-loss safe and re-runnable without a persistent marker — and the
crypto runs live with the operator's passphrase in RAM rather than via
a boot-time marker an attacker could plant to trigger an unprompted
decrypt. APPROTECT is NOT reversed (sticky; permanent on silicon where
it engaged).

Generated bindings (admin.pb.h / mesh.pb.h) regenerated against
protobufs#927 (LockdownAuth.disable, LockdownStatus.State.DISABLED).
Submodule pointer stays at the pinned develop commit; the bindings are
ahead until #927 merges and the submodule is bumped, same flow as the
max_session_seconds work.

Builds clean: rak4631 with no flags now auto-includes lockdown.

NOTE: this changes the LockdownStatus the firmware emits and adds the
disable path; pairs with protobufs#927 and the upcoming Android client
toggle work.

* fix(lockdown): re-lock per-connection auth on BLE reconnect

A provisioned device reused a single BLE PhoneAPI instance, and the
per-connection auth slot (keyed by that instance) was only cleared on the
!isConnected() disconnect transition. A fast disconnect/reconnect could
begin a new config burst while state was still STATE_SEND_PACKETS, so the
reconnected client inherited the prior session's authorization: it received
SecurityConfig in the clear and no LockdownStatus, and never re-authenticated.

Reset the auth slot in NRF52Bluetooth onConnect(), which fires once per
physical link, so every new connection starts locked regardless of whether
the previous link's close() raced the new handshake. handleStartConfig keeps
its !isConnected() reset (do NOT reset on a same-connection want_config: the
post-unlock re-fetch is the client pulling now-unredacted config and must keep
the auth it just earned, otherwise config comes back redacted and set_config
writes get dropped).

* fix(lockdown): persist config on a lockdown-capable but disabled device

saveProto always called encryptAndWrite when encrypted storage was compiled,
and saveToDiskNoRetry skipped every save when !isUnlocked(). On a disabled
(never provisioned) device there is no DEK and isUnlocked() is always false,
so both paths fired and NO config ever persisted: a LoRa region set before
enabling lockdown lived only in RAM, then provisioning migrated the UNSET
default from disk and the region was lost.

Gate both on isLockdownActive(): when lockdown is inactive the device writes
plaintext exactly like stock firmware; the reloadFromDisk migrate pass then
re-saves those plaintext files encrypted once the device is provisioned.
Verified on hardware: region set while disabled now survives enable, reboot,
and unlock.

* fix(lockdown): suppress LoRa region picker under the lock screen

A locked-boot lockdown device installs region=UNSET as a deliberate RAM
placeholder (the real region is in encrypted storage, restored on unlock).
Screen.cpp popped the region picker / onboard message whenever region==UNSET,
so it rendered over the lock screen and trapped input with no way out. Skip it
while the display is being redacted for lockdown.

* fix(lockdown): silence cppcheck void* false positive + ruff docstring lints

The nRF52 `check` (cppcheck --fail-on-defect=low) flagged
arithOperationsOnVoidPointer on EncryptedStorage.cpp buffers. These are
false positives: make_zeroizing_array() returns unique_ptr<uint8_t[], ...>
so .get() is uint8_t*, not void* — cppcheck just can't resolve the
custom-deleter alias. File-scoped suppression, matching the existing
crypto-code convention in suppressions.txt.

Trunk flagged 5 ruff docstring issues in lockdown_provision.py: D301
(backslashes need a raw docstring) and D405/D407/D411/D413 (the EXAMPLES
heading was being parsed as a numpydoc section). Made the docstring raw
and renamed the heading to USAGE to dodge section detection while keeping
the ASCII-box formatting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(lockdown): resolve cppcheck const/null-deref defects

The nRF52 `check` job (pio check --fail-on-defect=low) flagged seven
real cppcheck defects in the lockdown code:

  - EncryptedStorage.cpp: nonce/encDek are read-only views into the
    token buffer -> const uint8_t *.
  - NodeDB.cpp: segments[] lookup table is never mutated -> const.
  - PhoneAPI.cpp: clearStatusSlot_LH's p is only compared; the auth-check
    slot and the hasPendingLockdownStatus loop var are read-only -> const.
  - Screen.cpp: the MESHTASTIC_LOCKDOWN drawLockdownLockScreen() guard
    introduced a redundant null check (nullPointerRedundantCheck) since
    dispdev->displayOff() right below derefs it unguarded, as does the
    rest of the file. Dropped the guard.

Verified with cppcheck 2.21 locally against the project suppressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(lockdown): const-qualify clearAuthSlot_LH param (cppcheck cascade)

Making clearStatusSlot_LH take const PhoneAPI* let cppcheck propagate the
same to clearAuthSlot_LH, whose p is only compared and forwarded. The
remaining PhoneAPI* params (findOrAlloc*Slot_LH) store p into the slot
table, so they correctly stay non-const.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(lockdown): wire runtime-toggle disable flow into provision tool

Addresses Copilot review on tools/lockdown_provision.py — the reference
tool advertised the runtime-toggle disable lifecycle but couldn't exercise
it:

  - _STATE_NAMES: map LockdownStatus.DISABLED so a capable-but-off boot
    prints DISABLED instead of an opaque state=<num>.
  - build_lockdown_auth(): add a disable param that actually sets
    la.disable, failing loudly on pre-runtime-toggle bindings instead of
    silently sending a plain unlock.
  - cmd_disable() + 'disable' subcommand: send LockdownAuth{disable=true,
    passphrase=...} and wait for the resulting LockdownStatus. Mirrors the
    firmware: non-empty passphrase required, DISABLED broadcast precedes
    the reboot, TTL/session fields ignored.
  - _exit_code_for_status(): treat DISABLED as a success (exit 0) like
    UNLOCKED.

All DISABLED/disable references are hasattr-guarded so the tool still
imports and runs the lock/unlock/provision paths against the currently
released meshtastic package (verified: it has LockdownAuth but not yet
disable/DISABLED). Verified with ruff 0.15.13 and black.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 15:39:49 -05:00
Ben Meadors eb719f6fca Refine IPv6 address logging for CH390 driver in WiFiAPClient 2026-06-11 15:37:22 -05:00
Ben Meadors 02081dc85d Fix Ethernet handling and dependencies for CH390 driver 2026-06-11 15:36:13 -05:00
Ben Meadors ed52e3019d Change handleSetOwner parameter to const reference and improve long name handling 2026-06-11 14:24:12 -05:00
Ben Meadors c2bcec93d0 Fix long name clamping and adjust related structures for compatibility 2026-06-11 12:18:26 -05:00
8bb5364d8c tunk (#10684)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-06-11 07:57:06 -05:00
Ben Meadors 83c7e4ede3 Add board_level configuration for Heltec V4, RAK WisMesh Tag, and Seeed Wio Tracker L1 2026-06-11 07:21:25 -05:00
Ben Meadors a14f7afe87 fix(workflows): expand trusted author criteria for flasher comments 2026-06-10 20:04:40 -05:00
Ben Meadors 1490daa7ca Update runner configuration to use GitHub-hosted runners for checks 2026-06-10 19:12:32 -05:00
Ben Meadors a4001d71d5 Improve PR resolution logic for web flasher link comments 2026-06-10 17:54:24 -05:00
Ben Meadors 6da9f5f20e Add placeholder comment for web flasher during PR builds 2026-06-10 17:28:30 -05:00
TomandGitHub ab882c5619 EU regions merge (#10675)
* stronger together

* validate 2.4ghz regions

* less noise

* you're right, and that shapens the analysis significantly

* sassy rejoinder
2026-06-10 18:37:14 +01:00
Ben Meadors 2541db2bef fix(workflows): update artifact selection to exclude expired firmware size artifacts 2026-06-10 10:01:12 -05:00
Ben Meadors f875518b28 Flasher link fix 2026-06-10 08:00:05 -05:00
Ben Meadors 334ad9b313 Restrict web flasher link comments to organization members only 2026-06-10 06:33:33 -05:00
Ben Meadors 0953706e9e Add GitHub Action to post web flasher link comments on successful PR workflows 2026-06-10 05:48:39 -05:00
Ben Meadors 309d51a3e8 fix(NodeInfoModule): update user handling in allocReply to prevent global state clobbering 2026-06-09 21:00:40 -05:00
Ben Meadors 93f87c57b9 MacOS fixes 2026-06-09 21:00:05 -05:00
Ben MeadorsandGitHub 94ef2ae451 Revert "Automated version bumps (#10667)" (#10672)
This reverts commit abef0d85a2.
2026-06-09 20:04:26 -05:00
abef0d85a2 Automated version bumps (#10667)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-06-09 19:32:27 -05:00
6b3f975ba5 fix(ble): reliably expose and update BLE battery level (BAS) (#10622)
* fix(ble): reliably expose and update BLE battery level (BAS)

The Battery Service (0x180F / 0x2A19) is now wired up per the Bluetooth
BAS spec: the Battery Level characteristic always holds a valid 0-100
value and is pushed on change.

- NimBLE: seed an initial level at setup and cache the value on every
  update so a READ returns the current level even while disconnected;
  only notify when a client is connected.
- Power: mirror the battery level to the Battery Service from
  readPowerStatus() on change, so it updates independent of GPS/position
  events (previously the only push path was MeshService).

Also fixes two regressions the above would otherwise introduce:

- NimBLE use-after-free: BLEDevice::deinit(true) frees the GATT objects
  but left the global BatteryCharacteristic dangling. Several AdminModule
  paths (e.g. serial-config entry) deinit BLE while config.bluetooth.enabled
  stays true, so the periodic push would deref freed memory. Null the
  pointer in deinit().
- NRF52: guard blebas.write() on the nrf52Bluetooth instance so the new
  periodic push can't call it before the Battery Service is begun in setup().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ble): clamp BAS battery level to 0-100 and skip redundant updates

Address review feedback on the Battery Level characteristic (0x2A19):

- Clamp the value to the BAS-mandated 0-100 range at the platform write
  boundary (NimBLE seed + update, NRF52 update), so a misbehaving battery
  backend can't put an out-of-range value on the characteristic.
- Skip the write/notify when the level is unchanged, so repeated callers
  (e.g. MeshService refresh paths) don't emit redundant notifications.
- Simplify Power.cpp to a direct guarded call now that clamping and
  de-duplication live at the boundary, which also removes the implicit
  int->uint8_t narrowing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-06-09 18:58:26 -05:00
Jason PandGitHub e028663658 BaseUI: First attempt at Ham Mode implementation (#10663)
* First attempt at Ham Mode implementation

* Simplify licensedOnly check

* Move related code closer together

* TX Disabled if N0CALL, enabled if properly set

* Only disable if callsign is N0CALL, don't enable at this stage.

* Allow users back to Normal mode if they don't pick an ITU region
2026-06-09 19:52:29 -04:00
bf68b9e597 NRF52 LTO flags (#10655)
* Add LTO support for nrf52840 while preserving interrupt handlers

* nrf52840: enable whole-image LTO on all targets via nrf52_base

Moves -flto + the nrf52_lto.py exclusion middleware from the rak4631 env
(771018ca8) up to [nrf52_base], so every nrf52840 target inherits it.

nrf52_lto.py keeps the interrupt handlers out of LTO (framework core +
TinyUSB USBD_IRQHandler) -- they're referenced only from the asm vector
table, so whole-program LTO would otherwise drop them and the chip hangs.

HW-validated: RAK4631 (SX1262, -60KB) and muzi-base (LR1121) both boot and
init their radios. Build-verified on canaryone (fresh board, base-inherited).

Caveat: the RAK "1-Watt" variant's radio does not tolerate global-LTO
(SX126x init fails); it shares the rak4631 binary, so keep that hardware on
src-only or split it into a separate target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nrf52840: move -fmerge-all-constants to nrf52_base (all targets)

It had been trialed on rak4631 only; it's a general image-wide flag (the
same one stm32 uses globally, ~0.7KB), so move it up to [nrf52_base] next
to -flto so every nrf52840 target gets it instead of just rak4631.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nrf52_lto.py: normalize path separators for Windows (Copilot review)

get_abspath() returns backslash-separated paths on Windows, so the
"/FrameworkArduino/" / "/cores/nRF5/" substring matches would miss and the
ISR-owning objects would still be LTO'd -> hang on first IRQ. Replace
backslashes with forward slashes before matching. No-op on macOS/Linux.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix  directory handling for LTO

* Add post-link guard to check for dropped ISR handlers in nrf52 LTO

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jason P <applewiz@mac.com>
2026-06-09 16:09:25 -05:00
a9b98f47e9 GPS: cache model and baudrate and skip full sweep every startup (#10544)
* GPS cache

* trunk and CRLF fix

* Fix GPS.cpp formatting for trunk

* Format GPS.cpp for trunk clang-format

* Show gps model instead of model number

* Potential fix for pull request finding

Useful fix

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Update GPS.cpp

* Update GPS.cpp

* Trunk fix

* Update GPS.cpp

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-09 10:24:59 -05:00
Jason PandGitHub 90a3ac5938 Update SharedUIDisplay.cpp (#10659) 2026-06-09 09:17:43 -05:00
Jonathan Bennett 38f15db1d0 Bump protos to latest develop and regen 2026-06-08 16:28:40 -05:00
AustinandJonathan Bennett da821ec663 Actions: Update protobufs using the triggering branch (#10612) 2026-06-08 16:21:52 -05:00
Jonathan Bennett 124bffad84 Update Thinknode m7 pins (#10635) 2026-06-08 16:10:51 -05:00
Jason PandJonathan Bennett f98abe00f3 Update clock to be 70% max versus 80% to avoid unintended overlaps (#10516) 2026-06-08 16:01:02 -05:00
Thomas GöttgensandJonathan Bennett 56a33a07f7 remove private flag 2026-06-08 15:54:38 -05:00
Thomas GöttgensandJonathan Bennett ce80433e43 activate HWID 2026-06-08 15:54:24 -05:00
3d98622b96 Add hex picker (#10650)
* Add hex picker

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-08 14:50:55 -05:00
Jonathan BennettandGitHub d3691258d3 Update nanopb download URL in workflow 2026-06-08 14:23:22 -05:00
Jason PandGitHub 360c54f1f9 Random 2.8 Warning cleanups (#10649)
* Clean up Compass warning

* Update ICM42607PSensor.cpp
2026-06-08 08:12:57 -05:00
Thomas GöttgensandGitHub 8c4900a52f Prevent ghost nodes during onboarding (#10647)
* Prevent ghost nodes during onboarding
* Coplilot is exceptionally nit-picky today
2026-06-07 22:54:25 +02:00
bfb833982e Flip C6 to supported. (#10646)
* Flip C6 to supported.

* Re-add board_level pr

and remove redundant lib_deps

---------

Co-authored-by: Austin <vidplace7@gmail.com>
2026-06-07 15:12:59 -05:00
TomandGitHub 1410f170f9 makes clod format as it goes (#10645) 2026-06-07 08:09:19 -05:00
AustinandGitHub 14e998e6c3 ESP32: Update pioarduino to v3.3.9 (#10637)
Arduino Release v3.3.9 based on ESP-IDF v5.5.4

May help with recent compile troubles?
2026-06-06 17:03:41 -04:00
AustinandGitHub 57f678240d Add 1.25 Meter '125cm' amateur radio region support (#10638) 2026-06-06 07:43:49 -05:00
AustinandGitHub 99df0a6fe9 Update protobufs (#10636) 2026-06-05 14:46:29 -05:00
ce7444c1a1 feat: add WIZnet W5500-EVB-Pico2 + E22P-868M30S variant (#10552)
* feat: add WIZnet W5500-EVB-Pico2 + E22P-868M30S variant

Adds a community variant for the WIZnet W5500-EVB-Pico2 development
board (https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico2)
paired with an external EBYTE E22P-868M30S LoRa module.

This is the hardware twin of variants/rp2350/diy/pico2_w5500_e22 — same
LoRa pinout, same W5500 pin mapping — and reuses its variant.h verbatim
via `-I variants/rp2350/diy/pico2_w5500_e22`. No duplicated pinout file.

Two differences vs pico2_w5500_e22 justify the separate env:

1. Board target: `wiznet_5500_evb_pico2` (2 MB flash) instead of
   `rpipico2` (4 MB flash). The WIZnet PCB ships with a smaller Q-SPI
   chip than a stock Pi Pico 2. Selecting the correct board makes the
   linker fail fast on overflow instead of producing a UF2 that gets
   silently truncated when flashed to the device.
2. W5500 PHY is integrated on the PCB (hard-wired SPI0 GP16-20) rather
   than supplied as an external module the user wires manually.

The E22P-868M30S uses a single combined RFEN pin (LNA + PA enable)
instead of the older E22-900M30S's split RXEN/TXEN pins. RFEN is held
HIGH at all times via `SX126X_ANT_SW 3`; TX switching still uses the
on-module DIO2 → TXEN bridge through `SX126X_DIO2_AS_RF_SWITCH`.

Build verified: wiznet_5500_evb_pico2_e22p SUCCESS
(RAM 19.2%, Flash 65.6% of 1.5 MB partition / 2 MB chip).

* style(wiznet-evb-pico2-e22p): prettier-format README tables

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-06-05 09:08:43 -05:00
LittleatonandGitHub e3ae0a6ab5 missing module config (#10496)
13302 and 13300 had missing module config for slot 2, also the rf switch and voltage info was missing.

Without that the auto setting in the config.yaml will try to use the default  lora-hat-rak-6421-pi-hat.yaml and things do not work correctly.
2026-06-05 08:34:28 -05:00
pdxlocationsandGitHub 733430ed45 feat: Add Module configuration for RAK13300 and RAK13302 in Slot 2 (#10632) 2026-06-05 06:10:25 -05:00
cd2466692f fix: FEM Sleep/Wake Fix - Enable PA after sleep (#10617)
* fix: release Heltec v4 FEM sleep holds

* fix: increase FEM power settle delay

* Fix heltec_V4 documentation

Fixing the heltec_v4 documentation for enabling the PA after sleep.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Add comment for the next guy about why 5ms was chosen.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-06-04 20:40:05 -05:00
AustinandGitHub 4b424f0234 Add support for T-Beam Supreme 144mhz variant (#10624)
Adds support for T-Beam-Supreme 144mhz version
https://github.com/Xinyuan-LilyGO/LilyGo-LoRa-Series/blob/master/docs/en/t_beam_supreme/t_beam_supreme_144_hw.md

Tested / working.
2026-06-04 16:03:39 -05:00
a212d2e84f Save Frame Visibility Actions (#10576)
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
2026-06-04 10:05:51 -05:00
HarukiToredaandGitHub 5154e81d0b Unify InkHUD message storage with BaseUI's MessageStore (#10596)
* Unified Messagestore.

* DM fix

* Copilot fixes
2026-06-04 10:05:31 -05:00
TomGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Austin
5c1b6b2a23 Size change reporting (#10488)
* feat: add firmware size reporting and comparison scripts from #9860

* feat: add silence output feature to size_report and implement tests for size reporting scripts

* rm shame

* Fix baseline artifact paths in size report workflow

* feat: add firmware size reporting and comparison scripts from #9860

* feat: add silence output feature to size_report and implement tests for size reporting scripts

* rm shame

* Fix baseline artifact paths in size report workflow

* fix write permissions

* tunk

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Austin <vidplace7@gmail.com>
2026-06-04 06:31:27 -05:00
de345939af Automatic variable hop limits based on mesh activity and size estimation (#10176)
* asdf

* Implement SphereOfInfluenceModule for traffic management and eviction tracking

* Implement Sphere of Influence module for dynamic hop limit adjustment and role-based floor

* Update SAMPLING_DENOMINATOR to improve mesh size estimation accuracy

* Add debug logging for scale factor estimation and per-hop node counts in SphereOfInfluenceModule

* Enable variable hop limits and role-based hop floors in Sphere of Influence module

* Respond to copilot review

* Disable variable hop limits and role-based hop floors in Sphere of Influence module

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Implement adaptive sampling for unique node ID tracking in Sphere of Influence module

* Add state persistence for Sphere of Influence module

* Enhance Sphere of Influence module with state management and adaptive sampling adjustments

* Refactor hop scaling functionality: remove SphereOfInfluenceModule and introduce HopScalingModule

- Deleted SphereOfInfluenceModule.h, consolidating its responsibilities into the new HopScalingModule.
- Added HopScalingModule.h and HopScalingModule.cpp to manage hop scaling logic, including eviction tracking and sampling-based mesh size estimation.
- Implemented methods for recording evictions and packet senders, estimating scale factors, and computing required hops based on node activity.
- Introduced state persistence for hop scaling parameters to maintain continuity across reboots.
- Enhanced thread safety and modularity by utilizing concurrency features.

* Guard out STM32. Sowwy.

* Refactor HopScalingModule: enhance sampling logic and improve state management

* Add unit tests for HopScalingModule: implement mock database and various test scenarios

* Refactor test output in HopScalingModule tests: replace printf with TEST_MESSAGE for better integration with Unity

* Refactor HopScalingModule logging: replace lastStatusMode with descriptive mode names for improved readability

* Refactor test_main.cpp: change NodeNum variable to static and improve comments for clarity

* Remove unnecessary delay in setup function for improved test performance

* Add missing include for MeshTypes in test_main.cpp

* Refactor HopScalingModule tests: enhance mesh topology scenarios and improve test clarity

* Update HopScalingModule tests: flesh out node scenarios and improve clarity for dense and sparse mesh cases

* Fix politeness factor calculations in HopScalingModule and update related test scenarios for clarity. Remove outdated design doc.

* Enhance HopScalingModule: add sampled estimate for scaling decisions and refactor initial run state management

* Add sample traffic injection for HopScaling tests to enhance sampledEst visibility

* Enhance HopScalingModule: adjust windowFraction calculation for early triggers and improve test output formatting

* Enhance HopScalingModule: add jitter functionality to sampling denominator and update tests for consistent behavior

* Enhance HopScalingModule: implement adaptive sampling denominator adjustment and add reset functionality for tests

* Enhance HopScalingTestShim: add test-only clock and window helpers, update injectSampleTraffic for adaptive sampling, and improve scenario summary output

* Enhance HopScalingModule: add detailed documentation for functions, improve clarity of jitter and sampling logic, and reset functionality in tests

* Enhance HopScalingModule: add evictionEstimate parameter to estimateScaleFactor and update related logging for improved mesh size estimation

* Enhance HopScalingModule: adjust effective rolls calculation for improved accuracy, add eviction estimate logic, responding to all copilot review points

* Implement CompactHistogram for parallel hop scaling sampling

- Added CompactHistogram class to track node hop distances with bitwise sampling.
- Integrated CompactHistogram into HopScalingModule for independent packet sampling.
- Updated NodeDB to feed both the hop scaling module and the new histogram sampler.
- Enhanced HopScalingModule with methods to sample packets for the histogram and retrieve hop distribution statistics.
- Implemented tests for CompactHistogram functionality, including sampling, window rolling, and adaptive denominator scaling.
- Updated existing tests to validate the integration of the new histogram sampling mechanism.

* Enhance CompactHistogram and HopScalingModule: add per-hop distribution functionality, improve time handling for unit tests, and refine test setup for deterministic behavior

* CompactHistogram: add mesh size estimation, improve entry replacement logic, and update logging for per-hop distribution

* Refactor HopScalingModule and CompactHistogram integration

- Removed the suggestedHopFromCompactHistogram function to streamline hop suggestion logic.
- Updated HopScalingModule to directly utilize CompactHistogram's internal methods for hop suggestions and sampling.
- Enhanced logging in HopScalingModule to provide detailed histogram statistics.
- Modified test cases to ensure comprehensive coverage of new histogram behaviors and sampling logic.
- Improved node ID distribution in tests to better exercise sampling mechanisms.
- Ensured that filtering denominators are held for 12 hours before dropping, enhancing stability in sampling.

* Refactor CompactHistogram to support 13-hour activity tracking and introduce politeness regimes

- Updated the bitfield structure to accommodate 13-hour seen tracking.
- Changed the logic in rollHour() to analyze activity over the last 0-2 hours vs. 1-3 hours for politeness factor calculation.
- Introduced three politeness levels: GENEROUS, DEFAULT, and STRICT based on recent activity ratios.
- Adjusted filtering and sampling logic to reflect the new 13-hour tracking period.
- Updated unit tests to validate new behavior and ensure proper functionality of politeness regimes.

* Enhance CompactHistogram and HopScalingModule for improved sampling and decision-making

- Introduced a session-specific hash seed in CompactHistogram to reduce bias in node ID sampling.
- Updated sampling logic to use hashed node IDs instead of raw IDs for filtering and entry management.
- Added histogram rollover tracking in HopScalingModule to ensure proper decision-making after initial data collection.
- Adjusted logging to reflect the active state of the histogram and its comparison with NodeDB advisory hops.
- Enhanced unit tests to validate new sampling logic and memory layout changes.

* Expose hashNodeId for testing in CompactHistogram

* Add mesh trend statistics to CompactHistogram for enhanced activity tracking

* Implement histogram state persistence in CompactHistogram with save and load functions

* Refactor CompactHistogram to improve entry management and enhance rollHour logging

* feat: add HopScalingModule for adaptive hop limit recommendations

Introduces HopScalingModule, a sampled hop-distance histogram that
recommends the minimum hop limit needed to reach ~40 nodes, and
automatically reducing the hops as the mesh grows.

Key design:
- 512-byte packed histogram (128 × 4-byte Record entries) embedded
   in a new HopScalingModule.
- Each Record: 16-bit node hash, 3-bit hop distance, 13-bit seen bitmap
- Sampling filter: only nodes where (hash & (denom-1)) == 0 are kept;
  denominator doubles on overflow and halves when utilisation is low
- Hourly rollHour(): tallies per-hop counts, walks scaled buckets to
  find the minimum hop satisfying TARGET_AFFECTED_NODES (40), applies a
  politeness extension based on recent/older activity ratio, shifts all
  seen bitmaps, and persists state to /prefs/hopScalingState.bin
- Hop recommendation gated by bootstrap (requires >=1 rollHour before
  overriding HOP_MAX)
- NodeDB calls samplePacketForHistogram() on every non-MQTT rx packet
- Module also estimates total mesh size and logs useful information about
  mesh characteristics.

Changes:
- src/modules/HopScalingModule.h/.cpp: new module
- src/mesh/NodeDB.cpp: wire up samplePacketForHistogram
- src/mesh/Router.cpp: consume getLastRequiredHop()
- test/test_hop_scaling/: 12-test suite covering all mesh topologies and
  anticipated operational requirements

* test: increase run iterations in sparse to dense transition test

* feat: refactor HopScalingModule to use RUNS_PER_HOUR constant and improve logging

* feat: enhance HopScalingModule with filtering denominator management and add tests for state transitions

* refactor: remove CompactHistogram module and related files

* address copilot review comments

* Tweak: packet sampling only lora

* ove role-based hop floor logic and related definitions into the module - keep it in one place.

* Refactor MockNodeDB to use nodeInfoLiteSetBit for MQTT flag setting

* Refactor hop scaling parameters and logic to integer maths and put default values in defaults.h. Small flash size reduction, no functional impact.

* Update unit test preprocessor directives to PIO_UNIT_TESTING for consistency

* refactor: improve test output organization and clarity in hop scaling tests

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-06-04 05:59:49 -05:00
AustinandGitHub ef51c7ec11 Add 2 Meter (~144mhz) Amateur Radio Regions (#10623)
Default slots:
ITU1_2M: Slot 26 (144.510 MHz)
ITU2_2M: Slot 51 (145.010 MHz)
ITU3_2M: Slot 33 (144.650 MHz)
2026-06-03 18:05:03 -04:00
AustinandGitHub 3e873c51b7 Add TinyFast and TinySlow presets to modem configuration and menu actions (#10597) 2026-06-03 16:42:39 +01:00
Thomas GöttgensandGitHub f86cb7781e Remove fragile JSON libraries from the firmware while retaining Meshtasticd JSON support (#10152) 2026-06-03 16:47:30 +02:00
TomandGitHub fe23dcfa3a Merge pull request #10619 from NomDeTom/test_fixes
Some fixes and tidies for testing both online and in unit_tests
2026-06-03 13:43:54 +01:00
nomdetom 2d6f2ba1a4 docs: enhance README with verbose test output instructions and usage examples 2026-06-03 11:51:29 +01:00
nomdetom 5d55353939 Some fixes and tidies for testing both online and in unit_tests 2026-06-03 02:47:23 +01:00
AustinandGitHub c3a46d4d85 Update protobufs (#10614) 2026-06-02 19:25:35 -04:00
oscgonferandGitHub 0e0e17928b Remove reocurring pio deps for SHT sensors (#10601) 2026-06-02 06:23:23 -04:00
a522973fe7 fix(t5s3-epaper): ED047TC1 display margins, remove calibration diagnostic, fix touch threshold (#10304)
* fix(t5s3-epaper): increase ED047TC1 margins to 16px on all sides

Tested on device — 16px uniform margins look noticeably cleaner than
the previous asymmetric 8–9px values. Canvas shrinks from 944×523 to
928×508 (H_OFFSET_BYTES=2, V_OFFSET_TOP/BOTTOM=16).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(t5s3-epaper): remove EINK_EDGE_LINES calibration diagnostic from ED047TC1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(t5s3-epaper): align TOUCH_THRESHOLD_X with TOUCH_THRESHOLD_Y (40px)

X axis threshold was 60 while Y was 40, causing asymmetric swipe detection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
2026-06-01 21:29:51 -04:00
AustinGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
cbfa8d7ffd Add low bandwidth conversions to MeshRadio (#10595)
* Add low bandwidth conversions to MeshRadio

* Add test cases for new bandwidth conversion values (8/10/16/21/42) and round-trip tests

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-01 18:24:41 -04:00
AustinandGitHub 3feb155a5f Fix null pointer dereference in updateBatteryLevel function (#10588) 2026-05-31 14:46:28 -04:00
Chloe BethelandGitHub 64e35908c9 Add Xiao RP2040 and RP2350 variants (#10109) 2026-05-31 12:51:15 -04:00
Ben Meadors 8a1d6d9285 Add stable uid generation for PLI entities in allocAtakPli function 2026-05-31 12:26:00 -04:00
TomandGitHub ee441dd7b2 Merge pull request #10560 from meshtastic/migrate-overrideslot
Move overrideSlot from RegionProfile to RegionInfo (override per-region)
2026-05-30 22:04:37 +01:00
vidplace7andCopilot 178ae0a7f1 Move overrideSlot from RegionProfile to RegionInfo (override per-region)
Move default frequency (slot) override from RegionProfile to RegionInfo (set per-region).

This is usually set to `0`, but will be especially useful for Ham modes where each region default must fit within a band plan.

Co-authored-by: Copilot <copilot@github.com>
2026-05-30 16:20:17 -04:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
a08872299e Fix thinknode_m7 esp32s3 CI build failure from missing esp_eth_driver.h (#10585)
* Initial plan

* Add ESP32 ethernet header compatibility shim

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-30 11:26:02 -05:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
cc9d433db0 Fix mini-epaper-s3 build: resolve SensorLib isBitSet macro conflict with SparkFun MMC5983MA (#10584)
* Initial plan

* Fix SensorLib isBitSet macro conflict with SparkFun MMC5983MA library

SensorLib 0.3.4 defines isBitSet as a C preprocessor macro in SensorLib.h,
which conflicts with SparkFun_MMC5983MA_IO.h's class method of the same name.
When both libraries are included in the same translation unit (e.g., via
configuration.h → SensorRtcHelper.hpp → SensorLib chain, alongside the
SparkFun MMC5983MA library in lib_deps), the macro expansion causes compile
errors like 'expected unqualified-id before const'.

Fix: undefine the isBitSet macro right after including SensorRtcHelper.hpp
in configuration.h, so it doesn't interfere with SparkFun's class method.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-30 08:59:26 -05:00
60303968bb Add Heltec mesh node t1 (#10416)
* add heltec-mesh-node-t1

* fixed low power

* Update the sensor enumeration values.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Fix memory leak in ICM42607PSensor

* fix  ST7735_MISO  error

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-29 12:47:09 -05:00
Catalin PatuleaandGitHub d3c7f05baa Simplify tracking of BLE connection handle & improve thread safety. (#10390)
- Redefine isConnected in terms of nimbleBluetoothConnHandle. isConnected was not safe because BLEServer::getConnectedCount is not thread-safe (https://github.com/espressif/arduino-esp32/issues/12538) while isConnected is called from various threads. Now we can avoid checking bleServer every time before calling isConnected.
- getRssi: don't try to "populate nimbleBluetoothConnHandle", that requires calling BLEServer::getPeerDevices which is not thread-safe (same issue as above).
2026-05-29 06:48:43 -05:00
Ben Meadors 1971e5ab13 Remove now unused payload variant settings in allocAtakPli function 2026-05-29 06:15:11 -05:00
Ben Meadors e2fda6598c Update protobufs 2026-05-28 21:01:30 -05:00
982440d21d Noise floor (#9347)
* add noise floor

* Sliding window noise floor

* Add getCurrentRSSI() to SimRadio for noise floor support

* Remove sendLocalStatsToPhone call from runOnce

* Change noise floor to int32_t type

* Use int32_t for RSSI sample storage in noise floor

* Remove float cast from noise floor assignment

* Fix Copilot review issues: fix noise floor logic, types, and null pointer

- Use robust busyTx/busyRx checks instead of simple isReceiving check
- Initialize noiseFloorSamples to NOISE_FLOOR_MIN instead of 0
- Move noise_floor assignment inside null check to prevent potential crash
- Change getNoiseFloor() and getAverageNoiseFloor() to return int32_t
- Fix RSSI validation to check for positive values (rssi > 0)
- Fix format specifier from %.1f to %d for int32_t
- Update comments to accurately reflect the sampling logic

* Fix RSSI condition to include zero value

* Change noise floor initialization to zero

* Disable noise floor for LR11x0 chips: getRSSI(bool) unsupported

* Remove updateNoiseFloor call from onNotify to avoid radio queue overflow

Per PR review feedback, calling updateNoiseFloor() in onNotify() for every
ISR event (ISR_TX, ISR_RX, TRANSMIT_DELAY_COMPLETED) can cause the LoRa
radio queue to get full. The noise floor sampling still happens in
startReceive() and after transmitting.

* fix lr11x0 current rssi

* Address noise floor review comments

* Address Copilot SimRadio noise floor comments

* Fix RadioLibInterface formatting

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-05-28 19:39:41 -05:00
Ben Meadors f9fea562aa Add TAKTALK message and room data structures to support voice/text chat 2026-05-27 09:05:38 -05:00
Ben Meadors c366296ab4 Update LoRaConfig region codes and add new amateur radio bands 2026-05-26 18:56:16 -05:00
Ben MeadorsGitHubCopilotcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
a076d97eb6 Implement ATAK Plugin V2 and drop unishox2 compression support (#10105)
* Implement ATAK Plugin V2 and drop unishox2 compression support

* Fix course calculation and improve error handling in allocAtakPli

* Update src/modules/AtakPluginModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix buffer overflow in allocAtakPli by ensuring null termination for callsigns

* Add missing include for concurrency::OSThread in AtakPluginModule.h

* Update src/modules/AtakPluginModule.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor allocAtakPli to use configurable team and role; improve position source mapping

* Update src/modules/PositionModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove OSThread inheritance from AtakPluginModule - pure passthrough needs no periodic scheduling

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/bdc82eb6-77c4-4711-839c-04bcbb1aa9cd

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* Apply clang-format (trunk fmt)

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-26 15:16:47 -05:00
Clive BlackledgeandGitHub 32dcd90abf Preserve forwarded position payload precision (#10554) 2026-05-26 06:33:11 -05:00
5b7a5b2c22 feat: add Raspberry Pi Pico 2 + W5500 + E22-900M30S variant (#10135)
* feat: add Raspberry Pi Pico 2 + W5500 + E22-900M30S variant

Adds community variant for Raspberry Pi Pico 2 (RP2350, 4 MB flash)
with external WIZnet W5500 Ethernet module and EBYTE E22-900M30S LoRa
module (SX1262, 30 dBm PA, 868/915 MHz).

Key details:
- LoRa on SPI1: GP10/11/12/13 (SCK/MOSI/MISO/CS), RST=GP15,
  DIO1=GP14, BUSY=GP2, RXEN=GP3 (held HIGH via SX126X_ANT_SW)
- W5500 on SPI0: GP16/17/18/19/20 (MISO/CS/SCK/MOSI/RST)
- SX126X_DIO2_AS_RF_SWITCH: DIO2→TXEN bridge on module handles PA
- SX126X_DIO3_TCXO_VOLTAGE 1.8: TCXO support via EBYTE_E22 flags
- DHCP timeout reduced to 10 s to avoid blocking LoRa startup
- GPS on UART1/Serial2: GP8 TX, GP9 RX
- Reuses WIZNET_5500_EVB_PICO2 code paths for Ethernet init

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* pico2_w5500_e22: rename define and address review feedback

Rename WIZNET_5500_EVB_PICO2 to PICO2_W5500_E22 so the variant-specific
define matches the variant directory name and isn't confused with an
on-board EVB SKU.

Review fixes from PR #10135:
- Gate the 10 s Ethernet DHCP timeout behind PICO2_W5500_E22 so other
  Ethernet builds keep the default 60 s behavior; apply the same timeout
  to reconnectETH() for consistency.
- Drop the unused -D EBYTE_E22 flag; EBYTE_E22_900M30S already selects
  TX_GAIN_LORA / SX126X_MAX_POWER in src/configuration.h.
- Rewrite "on-board W5500" comments to describe the external module.
- Correct README TX_GAIN_LORA value (7, not 10) and drop the EBYTE_E22
  row.

* fix(pico2_w5500_e22): drop DEBUG_RP2040_PORT=Serial

The arduino-pico framework hooks _write() when DEBUG_RP2040_PORT=Serial
is set and dumps raw debug bytes onto USB CDC, corrupting any binary
protobuf stream sent through StreamAPI (e.g. `meshtastic --port COMx`).

The variant excludes BT and WiFi, so the primary client transport is
Ethernet TCP via ethServerAPI — unaffected — but users who configure
the node over USB serial would see protobuf decode failures from
debug-byte interleaving. Removing the flag restores clean USB CDC.

Debug output can still be enabled per-build by adding -D DEBUG_RP2040_PORT=Serial1
to redirect to UART0 instead of USB CDC.

* style(pico2_w5500_e22): apply trunk fmt — fixes Trunk Check

- variant.h: clang-format 16.0.3 (drop manual #define alignment)
- README.md: prettier + add `text` language to fenced code blocks (markdownlint MD040)
- wiring.svg: svgo optimization

Resolves the Trunk Check Runner failure on this PR (3 unformatted
files + 8 markdownlint issues). No functional changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(pico2_w5500_e22): address review — move to rp2350/diy, generic guards

Maintainer feedback from NomDeTom on PR #10135:

- Move the variant from variants/rp2350/ to variants/rp2350/diy/ to
  distinguish DIY from prebuilt boards (matches the variants/*/diy/
  pattern; still discovered via the existing variants/*/diy/*/platformio.ini
  glob in the root platformio.ini).
- Replace the board-name macro PICO2_W5500_E22 in shared code with a
  generic capability macro USE_ARDUINO_ETHERNET, defined from variant.h.
  DebugConfiguration.h / ethServerAPI.h / ethClient.cpp no longer
  reference a board name.
- Drop the architecture.h hook entirely: variant.h now defines PRIVATE_HW,
  which the existing `#elif defined(PRIVATE_HW)` branch already handles.

No functional change. Build verified: pico2_w5500_e22 SUCCESS
(RAM 19.2%, Flash 28.1%).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(pico2_w5500_e22): drop unoptimized wiring.svg to fix trunk fmt check

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-25 14:05:46 -05:00
Alexander BalyaandGitHub 5bce26d9b7 Fix SHT2x detection for INA219 addresses (#10482) 2026-05-24 21:03:24 -05:00
ManuelandGitHub ef734b73c7 fix: mbed TLS crash in Arduino 3.x (pioarduino) (#10535)
* fix mbed TLS crash

* adapt SSL_MAX_CONTENT_LEN size to framework-libs

* found two more
2026-05-22 20:14:42 -05:00
Ben Meadors c7748a1602 Fix update neighbor_info before checking update_interval in handleSetModuleConfig 2026-05-22 14:53:52 -05:00
Jaime RoldanandGitHub 91f930d5c0 fix(telemetry): stop emitting -0.001V sentinel when battery unavailable (#7958) (#10217)
* fix(telemetry): stop emitting -0.001V sentinel when battery unavailable (#7958)

* address review: use int32_t for batteryMv to avoid uint16_t signed wrap
2026-05-22 06:58:21 -05:00
κρμγandGitHub f2c5cb0a05 fix: first set pinMode, then write to pin (#10520) 2026-05-21 13:30:27 -05:00
e10e13226d nrf54l15: fix SHT4x libdep -- arduino-sht, not Adafruit_SHT4X (#10515)
SHTXXSensor (the SHT4x driver) includes <SHTSensor.h>, gated by
__has_include(<SHTSensor.h>). That header ships in Sensirion/arduino-sht.
Adafruit_SHT4X ships Adafruit_SHT4X.h and has no consumer anywhere in
src/, so the SHT40 driver was silently excluded from the build -- the
nRF54L15 variant could not read an SHT4x sensor as committed in #10193.

Replace the dead Adafruit_SHT4X libdep with arduino-sht v1.2.6.
Validated on nRF54L15-DK: SHT40-AD1B @0x44, 24.3h soak, 0 reboots,
temperature/humidity telemetry stable end-to-end.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-21 11:00:10 -05:00
5e69bc6c3f Enable Narrow and Lite regions for EU (#10120)
* Enable Lite and Narrow regions and introduce getEffectiveDutyCycle for Lite profiles

* Add TrafficType enum and extend getConfiguredOrDefaultMsScaled to manage based on regionProfile settings

* Refactor telemetry modules to include TrafficType in getConfiguredOrDefaultMsScaled calls

* Update submodule protobufs to latest commit

* Add support for new region presets and modem presets in menu options

* Add new LoRa region codes and modem presets for EU bands

* boof

* Add modem presets for LITE and NARROW configurations

* Update subproject commit reference in protobufs

* Update protobufs

* Refactor modem preset definitions to use macro for consistency and clarity

* Refactor modem preset cases to use PRESET macro for consistency

* fix: update LoRa region code for EU 868 narrowband configuration

Co-authored-by: Copilot <copilot@github.com>

* Fix test suite failure

Co-authored-by: Copilot <copilot@github.com>

* Add override slot override - for when one override isn't enough.

Co-authored-by: Copilot <copilot@github.com>

* address copilot comments

---------

Co-authored-by: Copilot <copilot@github.com>
2026-05-21 10:20:09 -05:00
f3cb2bff78 Refactor keyboard cell height logic for consistency (#10501)
Adjust keyboard cell height calculation for better layout consistency across different screen sizes.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-21 06:12:34 -05:00
AustinandGitHub 894c5556cf Actions: Fix tagging upon release. (#10521)
Current release tags are actually based upon the latest state of `develop` currently...
Specify target_commitish to always use the commit that triggered the build
2026-05-21 06:09:57 -05:00
Ben MeadorsandGitHub 2f92eb8499 Refactor position precision handling to honor explicit channel settings and prevent location leaks (#10513) 2026-05-20 10:18:46 -05:00
Ben Meadors 8d08077412 Add Ethernet configuration to platformio.ini for ThinkNode variants 2026-05-20 07:12:43 -05:00
Ben Meadors 00ec69201d Develop protos should be on develop 2026-05-19 06:57:05 -05:00
Ben Meadors 4304480ca3 Merge remote-tracking branch 'origin/master' into develop 2026-05-19 05:37:01 -05:00
82aefd1af1 Upgrade trunk (#10503)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-19 05:23:01 -05:00
0f9eb86830 Enabled SX_LNA_EN by default (#10469)
* Enabled SX_LNA_EN by default
* Update I2C configuration for IO direction and pull settings

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2026-05-19 04:56:01 -05:00
Thomas Göttgens e2aa44ec54 T-Echo-Card support (#10267)
# Conflicts:
#	src/graphics/draw/UIRenderer.cpp
2026-05-19 09:53:45 +02:00
Thomas GöttgensandGitHub 1747e2d8e5 T-Echo-Card support (#10267) 2026-05-19 09:31:04 +02:00
622aa046f1 Enabled SX_LNA_EN by default (#10469)
* Enabled SX_LNA_EN by default
* Update I2C configuration for IO direction and pull settings

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2026-05-19 08:31:42 +02:00
Thomas GöttgensandGitHub 0832330327 Fix antenna switch initialization logic 2026-05-19 08:00:02 +02:00
Thomas GöttgensandGitHub 98e0604edf Fix antenna switch initialization logic once more 2026-05-19 07:58:24 +02:00
23ead5f2f1 Update protobufs (#10500)
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
2026-05-19 07:47:06 +02:00
af3739fd63 Update protobufs (#10499)
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
2026-05-19 07:22:15 +02:00
0148a89ddb Upgrade trunk (#10493)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-18 20:49:28 -05:00
Thomas Göttgens 6199faacf1 cherry pick backport fix for cardputer 2026-05-18 23:24:32 +02:00
Thomas GöttgensandGitHub 3261c04afb Fix Antenna Switch on Cardputer (#10491) 2026-05-18 11:18:40 +02:00
AustinGitHubmverch67ManuelCatalin Patuleacopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>vidplace7CopilotthebenternBen Meadors
a541957480 ESP32: Migrate to Arduino 3.x (pioarduino) (#9122)
* Migrate esp32 families to pioarduino platform

* ESP32c6 align text.handler_execute same as C3

* Use pioarduino `develop`

The latest fixes and the latest bugs!

* preliminary esp32p4.ini

* pioarduino: Update LovyanGFX

Includes Manuel's recent commit

* pioarduino 3.3.6

* pioarduino 3.3.6 *release*

chasing the release

* pioarduino: Fix OG ESP32 duplicate libs

* pioarduino: T-Beam 1W CDC mode

* pioarduino: disable network provisioning (wifiprov)

* pioarduino: use legacy esptoolpy naming (forward-compatible)

* Update lovyangfx from `develop` commit to 1.2.19

* fix esp32p4.ini

* check for esp32 w/ wifi

* esp32-p4 specific adaptations

* Switch to meshtastic/esp32_https_server fork (idf5 branch)

* don't ignore esp_lcd

* config for MUI

* fix/workaround SDMMC

* revert a6f6175, update to 3.3.8

* enable esp_hosted for esp32-p4 (experimental)

* Pioarduino 55.03.38-1

* NimBLE-Arduino -> Arduino "BLE" (3.3.x) migration (#10164)

* NimBLE-Arduino -> Arduino "BLE" (3.3.x) migration

* More NimBLE

* Fix Device Name in ATT Read Request (0x2A00).

Device Name is exposed in two places:

- Advertisement data: this is set properly in startAdvertising.
- GATT attribute Device Name (0x2A00). This one is handled internally in NimBLE
  and comes from ble_svc_gap_device_name_set. This is set initially, but then
  BLEDevice::createServer calls ble_svc_gap_init which resets the device name.
  This causes the device to apparently "change name after pairing":

< ACL Data TX:... flags 0x00 dlen 7  #113 [hci0] 14.241149
      ATT: Read Request (0x0a) len 2
        Handle: 0x0003 Type: Device Name (0x2a00)
> ACL Data RX: Handle 2048 flags 0x02 dlen 11             #115 [hci0] 14.269050
      ATT: Read Response (0x0b) len 6
        Value[6]: 6e696d626c65   # "nimble"

Workaround this by setting the device name once again after
BLEDevice::createServer.

* Temporarily lower CORE_DEBUG_LEVEL to INFO to avoid triggering an apparent ESP-IDF Bluetooth bug when re-connecting to Pixel 8 Android devices.

Initial pairing works, but after ESP32 is rebooted, phone fails to reconnect. Meshtastic app shows it as disconnecting immediately. LightBlue shows a more detailed error "Peripheral Connection - Warning: onConnectionStatusChange: status 61" (0x3D - MIC Failure).

Bug report to Espresssif: https://github.com/espressif/esp-idf/issues/18126#issuecomment-4286197744

* Temporarily disable ble_gap_set_data_len, causes crash with Pixel 8 Android reconnect.

Crash looks like this:
  [ 11966][E][BLEAdvertising.cpp:341] setScanResponseData(): ble_gap_adv_rsp_set_data: 22
  [ 11975][E][BLEAdvertising.cpp:1554] start(): Host reset, wait for sync.
  ERROR | ??:??:?? 11 BLE failed to start advertising
  Guru Meditation Error: Core  0 panic'ed (LoadProhibited). Exception was unhandled.

  Core  0 register dump:
  PC      : 0x420e6190  PS      : 0x00060730  A0      : 0x820e158b  A1      : 0x3fce50c0
  A2      : 0x00000000  A3      : 0x3fcb8600  A4      : 0x3fcb85cc  A5      : 0x00000000
  A6      : 0x00000000  A7      : 0x00000c03  A8      : 0x00000000  A9      : 0x3fce50b0
  A10     : 0x0000000e  A11     : 0x00000000  A12     : 0x00000010  A13     : 0x3fce50e0
  A14     : 0x00000c03  A15     : 0x00000001  SAR     : 0x0000001e  EXCCAUSE: 0x0000001c
  EXCVADDR: 0x00000000  LBEG    : 0x400570e8  LEND    : 0x400570f3  LCOUNT  : 0x00000000

  Backtrace: 0x420e618d:0x3fce50c0 0x420e1588:0x3fce5110 0x420dfe87:0x3fce5200 0x420dfefb:0x3fce5220 0x420dff3f:0x3fce5240 0x4219602b:0x3fce5260 0x4037b0e5:0x3fce5280 0x4201edf3:0x3fce52a0

Connection seems fast enough even without this. We'll investigate the
reason for the crash and re-enable once it's safe.

---------

Co-authored-by: Catalin Patulea <cronos586@gmail.com>

* Add extension from pioarduino nag
"Jason2866.esp-decoder"

* Cleanup after merge

* ESP32: Disable classic bluetooth

* Cleanup: Fix ADC channels on new variants

* InkHUD: Fix type casting for message size in saveToFlash method

inkhud compiles again!

* update p4 esp_hosted for BT

* I thought I fixed this

* fix linker error using response file (p4 only)

* fix infinite loop

* Fix Power.cpp check warning

Local variable 'config' shadows outer variable [shadowVariable]

* Build ESP32 original with NimBLE ('custom_sdkconfig' approach). (#10235)

* Re-enable littlefs json manifest

This works locally again :)
Not sure what changed

* Re-add tool-mklittlefs

* sensecap indicator fixes after upgrade arduino-esp & lovyanGFX libs

* hackaday fix

* robot tbeam cache error fix

Co-authored-by: Copilot <copilot@github.com>

* trunk fmt

* ignore trunk

* BLEDevice::deinit() added

Co-authored-by: Copilot <copilot@github.com>

* platformio-custom: Modify mtjson target dependency to prevent fake-success. (#10291)

Co-authored-by: Copilot <copilot@github.com>

* Fix ESP32-C6 linker errors.

Align .text.handler_execute section to 4 bytes and update watchdog timer core mask configuration

Co-authored-by: Copilot <copilot@github.com>

* tlora-c6: Disable Screen

MESHTASTIC_EXCLUDE_SCREEN=1 on tlora-c6.

It doesn't have a screen, and this gets it compiling again (saving flash).

* Use mverch's iram_memset hack for all OG-ESP32

* Refactor watchdog timer initialization and handling

* use adc_channel_t in variant.h

* Fix variant headers

* More idiomatic default ethernet that doesn't break the build

* Elecrows: Delete problematic variant.cpp

Not needed after USE_ETHERNET_DEFAULT

---------

Co-authored-by: mverch67 <manuel.verch@gmx.de>
Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>
Co-authored-by: Catalin Patulea <cronos586@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-17 18:53:32 -05:00
Jonathan BennettandGitHub 767a748188 add optional LED_LORA to indicate LoRa TX (#10465) 2026-05-16 22:11:49 -05:00
fc5556b8e6 Clamp direct position packets to channel precision (fixes #8640) (#10383)
* Fix position precision for direct sends

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Clarify zero position precision logging

* Use const channel reference for position precision

* Use C linkage for position precision test entrypoints

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-16 14:08:46 -05:00
4a1ff18f57 feat: add Nordic nRF54L15-DK variant (Zephyr + BLE + LoRa) (#10193)
* feat: add Nordic nRF54L15-DK variant (Zephyr + BLE + LoRa)

Adds a community hardware variant for the Nordic nRF54L15-DK (PCA10156)
with an external EBYTE E22-900M30S (SX1262) LoRa module. First Meshtastic
port running on the Zephyr RTOS; all other Nordic targets use the nRF5
SoftDevice stack.

Scope
-----
- New Zephyr-based platform layer under src/platform/nrf54l15/ providing
  Arduino-compatible shims (Arduino.h, SPI, Wire, Print, Stream) over the
  Zephyr APIs plus a LittleFS-backed InternalFileSystem on SPIM20.
- Bluetooth LE peripheral (NRF54L15Bluetooth.*) built on the Zephyr BT
  host stack, exposing the Meshtastic GATT service with legacy
  connectable advertising, just-works pairing, dynamic MTU exchange
  (up to 247 bytes), and iOS connection-parameter tweaks.
- Variant directory variants/nrf54l15/nrf54l15dk/ with pin map for the
  E22 module on connector J1, PlatformIO env (nrf54l15dk), Zephyr
  DT overlay and a wiring README.
- Zephyr project config (zephyr/prj.conf + board overlay) tuned for
  BT + LoRa: 16 KB main stack, 4 KB BT RX thread, RTT logging in
  immediate mode, newlib-nano heap sized to leave room for the GATT
  pools while still allowing ATT MTU=247.
- extra_scripts/nrf54l15_linker.py works around a PlatformIO + old Ninja
  issue where Zephyr's two-pass linker script generation does not run
  automatically; the post-script parses build.ninja and invokes the
  gcc -E step directly before the final link.
- boards/nrf54l15dk.json board definition (PlatformIO needs it for the
  DK; the Seeed platform only ships the XIAO variants).
- variants/rp2350/rp2350.ini excludes platform/nrf54l15/ from RP2350
  build_src_filter so the shared platform tree does not leak between
  targets.
- .gitignore: add nRF J-Link / RTT debug artifacts (flash.jlink,
  rtt_*.txt).

Shared source changes
---------------------
- src/main.{cpp,h}, src/RedirectablePrint.cpp, src/FSCommon.{cpp,h},
  src/mesh/{Channels,NodeDB,RadioLibInterface,MeshService,PhoneAPI}.cpp,
  src/mesh/RadioLibInterface.h, src/modules/AdminModule.cpp: add small
  guards / helpers so the Zephyr build compiles alongside the Arduino
  targets. Behavior on existing boards is unchanged.

Hardware model
--------------
HW_VENDOR maps to meshtastic_HardwareModel_PRIVATE_HW until a dedicated
protobuf enum value is assigned upstream. The variant declares
custom_meshtastic_hw_model = 132 so the maintainers can wire the new
enum value through the protobufs repo after merge.

Hardware note
-------------
The E22-900M30S does not connect its DIO2 pin to TXEN internally — a
wire/solder bridge between DIO2 and TXEN on the module is required for
TX to work. Details and full pin map are in the variant README.

Validation
----------
Built clean against develop. On real hardware (April 2026) the port
passes end-to-end: iOS companion app pairs and connects, configuration
round-trip works, LoRa TX/RX reaches a canonical tbeam on the same mesh
channel, NodeDB updates propagate both ways, and traceroute completes.

* fix(nrf54l15): use atomic fs_rename instead of copy fallback

Zephyr LittleFS on nrf54l15 supports fs_rename natively, so route it
through the same atomic path as ESP32. The previous copyFile+remove
fallback truncated the destination before copying, leaving 0-byte files
if interrupted mid-write.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(nrf54l15): expand storage_partition from 36KB to 700KB

LittleFS on the default 9-block (36KB) storage_partition ran out of
space during copy-on-write of config.proto, causing fs_write to return
ENOSPC and pb_encode to surface "io error" when saving configuration
via the mobile app.

Reclaim slot1_partition (the MCUboot secondary slot — unused since we
flash directly via J-Link) and grow storage_partition to span
0xb6000..0x165000 (~175 blocks).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(nrf54l15): drop USERPREFS_LORACONFIG_* so LoRa config stays mutable

NodeDB rewrites LoRa config from USERPREFS_LORACONFIG_* on every boot,
which prevented reconfiguration via the BLE/serial app. Drop the
variant-level defaults; users configure region and modem preset through
the app like every other variant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(nrf54l15): enforce MITM passkey pairing on GATT service

- Add MESH_PERM_READ/MESH_PERM_WRITE macros (READ_AUTHEN/WRITE_AUTHEN)
  on all mesh service characteristics so clients must complete passkey
  exchange before accessing fromNum/fromRadio/toRadio/logRadio.
- Wire FIXED_PIN mode to bt_passkey_set() so the device advertises a
  known PIN (config.bluetooth.fixed_pin); RANDOM_PIN keeps default
  per-pairing random passkey.
- Reduce BleDeferredThread HARD_WATCHDOG_MS from 3min to 1min.
- prj.conf: CONFIG_BT_SMP_ENFORCE_MITM=y, CONFIG_BT_FIXED_PASSKEY=y,
  CONFIG_BT_SMP_SC_PAIR_ONLY=n (legacy fallback for clients that abort
  SC pairing with reason 0x01 within 150ms).

* fix(nrf54l15): resolve develop-merge conflict + cppcheck warnings

The `Merge branch 'develop'` left two ~RadioLibInterface() declarations
in src/mesh/RadioLibInterface.h: the inline version added upstream by
PR #10254 (which independently applied the same UAF guard this PR was
carrying) and the out-of-line version this PR introduced. GCC rejects
the duplicate, breaking every platform build. Drop the out-of-line
declaration + definition; keep upstream's inline form.

Also silence the 13 cppcheck low warnings introduced by the new
nrf54l15 Arduino shim — Arduino's `String`/`SPISettings` API contract
relies on implicit single-arg constructors used pervasively by
existing Meshtastic code, so suppress `noExplicitConstructor` inline
with a comment instead of breaking the API. The few mechanical wins
(`const tmp[2]`, `const uint32_t *sp`) are applied directly.

* fmt: fix Trunk Check lint issues on nrf54l15-port

- extra_scripts/nrf54l15_linker.py: move regular imports above
  Import("env") to silence E402, add trunk-ignore-all(F821) for the
  PIO/SCons SConstruct injection (matches esp32_pre.py / nrf52_extra.py
  convention)
- src/platform/nrf54l15/NRF54L15Bluetooth.cpp: clang-format 16.0.3
- boards/nrf54l15dk.json + variants/nrf54l15/nrf54l15dk/README.md:
  prettier 3.8.3 (also resolves markdownlint MD060 on README tables)

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(nrf54l15): address Copilot review comments + correct clang-format style

Six review threads from the 2026-04-30 Copilot review:

- src/platform/nrf54l15/nrf54l15_main.cpp: validate PSP against the nRF54L15
  SRAM range (0x20000000..0x20040000) and 4-byte alignment before walking the
  faulting thread's stack, and clamp the walk so it never reads past the end
  of RAM. Prevents a second fault inside the fatal handler when PSP is
  corrupted (common in real faults).

- src/platform/nrf54l15/nrf54l15_arduino.cpp: gate the bring-up printk traces
  in digitalWrite/digitalRead (CS/NRESET toggle log, BUSY-before-NRESET
  snapshot, BUSY periodic timeline) behind a new -DNRF54L15_GPIO_DEBUG flag
  that is off by default. The "dev NOT READY" message stays unconditional —
  it indicates a genuine hardware/DTS misconfig.

- src/modules/AdminModule.cpp: don't mutate config.device.output_gpio_enabled
  from handleGetConfig(). Reflect the live pin state in the response payload
  only — a getter must not write back to disk-persisted state.

- src/platform/nrf54l15/InternalFileSystem.h: derive totalBytes() from
  FIXED_PARTITION_SIZE(storage_partition) at compile time so it tracks the
  DK overlay's ~700 KB partition instead of the stale 36 KB hard-coded value.
  Updated the file header comment accordingly.

- extra_scripts/nrf54l15_linker.py: make _extract_gcc_command() handle the
  POSIX Ninja COMMAND format (no `cmd.exe /C "..."` wrapper) in addition to
  the Windows form, so the script doesn't hard-fail on Linux/macOS hosts.

- src/platform/nrf54l15/NRF54L15Bluetooth.cpp: clamp NO_PIN to RANDOM_PIN
  with a one-shot LOG_WARN. The mesh GATT permissions are declared with
  BT_GATT_PERM_*_AUTHEN and prj.conf sets CONFIG_BT_SMP_ENFORCE_MITM=y, so
  NO_PIN with no auth callbacks would leave every characteristic returning
  BT_ATT_ERR_AUTHENTICATION. Falling back to RANDOM_PIN keeps the link
  usable instead of silently broken. Also re-formatted this file with the
  project's .trunk/configs/.clang-format (Linux braces, 4-space indent,
  130-col) — the previous lint-fix commit a2aca3234 accidentally used the
  default LLVM style here, which CI's clang-format would have rejected.

Build verified: pio run -e nrf54l15dk passes, RAM 47.4%, Flash 28.6%.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(nrf54l15): address remaining Copilot review threads

Round 2/3 review fixes — bugs first, then docs/portability:

BLE concurrency (NRF54L15Bluetooth.cpp):
- onNowHasData / sendLog / BleDeferredThread / shutdown: acquire
  active_conn under ble_mutex via new acquire_active_conn() helper so
  disconnected_cb can't free the conn between the null check and
  bt_conn_ref/bt_gatt_notify (use-after-unref).
- write_toradio: reject writes that exceed MAX_TO_FROM_RADIO_SIZE with
  ATT_ERR_INVALID_ATTRIBUTE_LEN instead of returning success and silently
  dropping the payload (would hide failed config writes from the phone).
- start_advertising: truncate the device name to fit the 31-byte legacy
  scan-response limit and switch to BT_DATA_NAME_SHORTENED so
  bt_le_adv_start() doesn't reject the payload when the name approaches
  CONFIG_BT_DEVICE_NAME_MAX=32.

Linker / portability:
- main.h: drop the rp2040Loop() forward declaration that had no
  definition and no callers — would surface as a link error if any RP2040
  build added a call to the symbol.
- nrf54l15_arduino.cpp: transfer16() now uses static __aligned(4) DMA
  buffers (matching transfer()), removing the EasyDMA-reachability hazard
  of caller-stack buffers on this part.

Filesystem (InternalFileSystem.h):
- usedBytes(): return real usage from fs_statvfs() instead of 0 so OTA
  / range-test free-space guards work.
- rewindDirectory(): close the dir before reopening — Zephyr fs_dir_t has
  no rewind, and re-fs_opendir on an open handle leaks LittleFS state.

Crash handler (nrf54l15_main.cpp):
- After the stack walk, busy-wait 50 ms to flush RTT/printk and call
  sys_reboot(SYS_REBOOT_COLD) directly so the saved_crash record is
  actually reported on the next boot. Default Zephyr config has
  RESET_ON_FATAL_ERROR=n, so the previous k_fatal_halt() spun forever.

Generalization / config:
- PhoneAPI.cpp: replace the NRF54L15_DK ifdef with a
  MESHTASTIC_EXCLUDE_FILES_MANIFEST capability flag (defined in the
  nrf54l15dk env) so future variants can opt in/out without touching
  shared code.
- variants/nrf54l15/nrf54l15.ini: parameterize libdeps include paths via
  ${PIOENV} so additional nRF54L15 envs sharing nrf54l15_base don't break.
- prj.conf: drop the stale "36 KB storage_partition" comments — the DK
  overlay reclaims slot1 to ~700 KB and runtime size comes from
  FIXED_PARTITION_SIZE.
- nrf54l15dk overlay: remove the zephyr,console / zephyr,shell-uart
  chosen entries that conflicted with CONFIG_UART_CONSOLE=n + RTT
  console; keep uart30 enabled so swapping the console is one Kconfig
  flip away.

Build: nrf54l15dk SUCCESS (flash 28.6%, RAM 47.4%); wiznet_5500_evb_pico2
SUCCESS (verifies the shared main.h change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(nrf54l15dk): use PRIVATE_HW (255) for custom_meshtastic_hw_model

Per @thebentern's review: the nRF54L15-DK is a development kit, not a
canonical Meshtastic SKU, so it falls under HardwareModel::PRIVATE_HW
(255) — the same enum value already used at runtime via HW_VENDOR. The
placeholder 132 is removed; no dedicated enum number will be assigned
for DK boards. Slug stays NRF54L15_DK as a human-readable identifier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(nrf54l15): unstarve bt_long_wq so SC pairing completes

bt_pub_key_gen() runs the ECC P256 key generation on bt_long_wq.  At default
prio=10 (preemptible) and stack=1400 it gets starved by Meshtastic app
threads at boot — sc_public_key stays NULL for minutes, smp_public_key()
defers with SMP_FLAG_PKEY_SEND, and every SC pairing attempt stalls right
after the public-key exchange.  iOS shows "Connecting…" forever with no
PIN prompt; bleak/CLI fails the first CCC notify write with
"Protocol Error 0x05: Insufficient Authentication".

Set CONFIG_BT_LONG_WQ_PRIO=0 (highest preemptible, ties with main) and
CONFIG_BT_LONG_WQ_STACK_SIZE=4096 (margin for the P256M driver frames).

Validated E2E with iOS Meshtastic app: bt_smp_pkey_ready fires within ~40 s
of boot, 20 SC Passkey Entry rounds complete with matching pcnf/cfm,
encrypt 0x01 / sec_level 0x04 (Authenticated MITM), bonded=1.

* feat(nrf54l15): hardware I2C bus via TWIM30 + sensor telemetry

Adds the Arduino TwoWire layer for the nRF54L15-DK so Meshtastic's
sensor drivers can talk to external I2C devices over the hardware
TWIM30 peripheral.

Bus binding:
- &uart30 disabled in the board overlay (peripheral instance 30 is
  shared between UARTE30 / TWIM30 / SPIM30 — pick one). Console stays
  on RTT via CONFIG_RTT_CONSOLE.
- New i2c30_default / i2c30_sleep pinctrl with SDA=P0.03 / SCL=P0.04.
  External 4.7 kOhm pull-ups required on both lines.
- &i2c30 enabled at I2C_BITRATE_FAST (400 kHz).
- button_3 (SW3, P0.04) deleted from DTS so the pad can be claimed by
  i2c30 pinctrl; SW3 is still wired to the pad on the DK, do not press
  it during I2C use or it will short SCL to GND.

Arduino layer:
- src/platform/nrf54l15/Wire.cpp resolves the DT node at compile time
  via DEVICE_DT_GET(DT_NODELABEL(i2c30)) and dispatches Arduino's
  beginTransmission / write / endTransmission / requestFrom to
  i2c_write / i2c_write_read / i2c_read. Buffer is sized to 256 bytes
  for forward compatibility with the SE050 secure element on the
  custom PCB.
- Wire.h drops the prior compile-only stubs and exposes the real
  TwoWire surface.
- Arduino.h: BitOrder becomes an enum (not #define) so Adafruit_BusIO's
  `typedef BitOrder BusIOBitOrder;` compiles.

Variant + build flags:
- nrf54l15.ini flips HAS_WIRE / HAS_SENSOR / HAS_TELEMETRY from 0 to 1
  and cherry-picks the sensor libs Meshtastic needs (BusIO, Sensor,
  BMP280, BME280, INA219/226/260/3221, SHT4X). The full
  environmental_base group is avoided because it pulls
  Adafruit_SSD1306 / Adafruit_GFX which rely on Arduino pin macros the
  Zephyr shim does not implement.
- nrf54l15dk variant.h defines PIN_WIRE_SDA / PIN_WIRE_SCL for parity
  with the Arduino convention used by other variants. The actual bus
  wiring is fixed by the overlay pinctrl above.

Validated 2026-05-14/15 on the DK with BMP280 @ 0x76 (temperature +
barometric pressure) and INA3221 @ 0x42 (rail voltage / current);
EnvironmentTelemetry / PowerTelemetry packets transmit successfully
over LoRa.

Footprint cost on nrf54l15dk: +45 KB flash, +1.7 KB RAM.

* feat(nodedb): honor USERPREFS for environment telemetry on first boot

installDefaultConfig() now respects two new compile-time prefs:

  USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL
  USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED

The mobile apps enforce a 30 min floor on environment_update_interval
in the settings UI, which makes short-interval bring-up testing of new
sensor hardware painful — you have to wait half an hour for the first
LoRa packet to confirm wiring + driver. With these prefs baked into
the variant, the firmware can ship a freshly-flashed device that
broadcasts on a shorter cadence (e.g. 900 s) the moment storage_partition
is empty.

Both prefs are gated on #ifdef so the behavior is unchanged for any
variant that does not opt in. Documented in userPrefs.jsonc with the
existing telemetry-interval pref block.

* fix(nrf54l15): allow multiple bonded BLE peers

CONFIG_BT_MAX_PAIRED defaults to 1, so once the first peer (e.g. an
iOS phone) has paired and bonded, every subsequent pairing attempt
from a different MAC fails inside bt_keys_get_addr() with no free
key slot — the host returns BT_SECURITY_ERR_KEY_DOES_NOT_EXIST and
the second peer never gets past SMP.

Raise the slot count to 4 so the device can simultaneously hold an
iOS phone, a Windows host, a Linux host, and one spare bond. Add
BT_KEYS_OVERWRITE_OLDEST so that once the table fills, the LRU peer
is evicted on the next pairing rather than rejecting the new peer.
This matches the behavior other Meshtastic ports already provide
(nRF52 uses CONFIG_BT_PERIPHERAL_PRIO_CONN with similar semantics).

Discovered while bringing up the Python CLI on Windows alongside
the existing iOS bond.

* fix(nrf54l15): zero-initialize TwoWire buffers + clang-format Wire

cppcheck on every CI target (esp32s3, rp2040, rp2350, nrf52840, ...) was
failing the build with two `uninitMemberVar` warnings on TwoWire's
constructor: `txBuf` and `rxBuf` (256-byte arrays) were not initialized.
Even though the buffers are only read after txLen/rxLen is set, leaving
them uninitialized is a footgun if any future caller bypasses the
len-set step. Use C++11 value-initialization in the member initializer
list — costs ~512 B of memset at boot, gains a clean cppcheck pass and
defensive-against-future-changes semantics.

Also reformat Wire.{cpp,h} with the project's `.trunk/configs/.clang-format`
config so the Trunk Check Runner passes — clang-format moved the
`<errno.h>` include before the Zephyr-namespaced ones in Wire.cpp and
collapsed two inline overloads to single lines in Wire.h.

* fix(AdminModule): remove dead OUTPUT_GPIO_PIN/GpioOutputModule references

OUTPUT_GPIO_PIN is never defined and modules/GpioOutputModule.h doesn't
exist in the codebase; all #ifdef OUTPUT_GPIO_PIN branches were dead code
introduced by the nRF54L15-DK variant commit. Strips the include, the
output_gpio_enabled OFF→ON/ON→OFF transition logic in handleSetConfig(),
and the digitalRead() reflection in handleGetConfig().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-16 06:16:11 -05:00
502c5af524 Upgrade trunk (#10481)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-15 20:03:50 -05:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
05707079bd Update libch341-spi-userspace digest to 2e5ff75 (#10485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-15 19:30:04 -05:00
Ben MeadorsandGitHub 1c05633fcd Add more support for small fonts in screen resolution determination (#10480) 2026-05-15 05:59:15 -05:00
fce419b335 Upgrade trunk (#10476)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-14 06:43:06 -05:00
451 changed files with 20578 additions and 17200 deletions
+17
View File
@@ -0,0 +1,17 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "f=$(tr -d '\\n' | grep -o '\"file_path\"[[:space:]]*:[[:space:]]*\"[^\"]*\"' | head -1 | sed 's/.*:[[:space:]]*\"//; s/\"$//'); [ -n \"$f\" ] && [ -f \"$f\" ] || exit 0; t=$(command -v trunk || echo \"$HOME/.cache/trunk/launcher/trunk\"); [ -x \"$t\" ] || { echo \"trunk-fmt hook: trunk not found; its launcher needs curl or wget to bootstrap the CLI (see 'Formatting & the trunk toolchain' in .github/copilot-instructions.md)\" >&2; exit 1; }; out=$(\"$t\" fmt --force \"$f\" 2>&1) || { echo \"trunk-fmt hook: trunk fmt failed on $f: $out\" >&2; exit 1; }",
"timeout": 120,
"statusMessage": "Formatting (trunk)..."
}
]
}
]
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
# trunk-ignore(hadolint/DL3008): apt packages are not pinned.
# trunk-ignore(terrascan/AC_DOCKER_0002): apt packages are not pinned.
RUN apt-get update && apt-get install --no-install-recommends -y \
cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \
cmake git zip libgpiod-dev libjsoncpp-dev libbluetooth-dev libi2c-dev \
libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \
libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev libsdl2-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
+1 -1
View File
@@ -31,7 +31,7 @@ cmake --install "$WORK/ulfius/$SANITIZER" --prefix /usr
cd "$SRC/firmware"
PLATFORMIO_EXTRA_SCRIPTS=$(echo -e "pre:.clusterfuzzlite/platformio-clusterfuzzlite-pre.py\npost:.clusterfuzzlite/platformio-clusterfuzzlite-post.py")
STATIC_LIBS=$(pkg-config --libs --static libulfius openssl libgpiod yaml-cpp bluez --silence-errors)
STATIC_LIBS=$(pkg-config --libs --static libulfius openssl libgpiod yaml-cpp jsoncpp bluez --silence-errors)
export PLATFORMIO_EXTRA_SCRIPTS
export STATIC_LIBS
export PLATFORMIO_WORKSPACE_DIR="$WORK/pio/$SANITIZER"
+1
View File
@@ -16,6 +16,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
libssl-dev \
libulfius-dev \
libyaml-cpp-dev \
libjsoncpp-dev \
pipx \
pkg-config \
python3 \
+1 -1
View File
@@ -13,7 +13,7 @@ runs:
shell: bash
run: |
sudo apt-get -y update --fix-missing
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev lsb-release
- name: Setup Python
uses: actions/setup-python@v6
+1 -1
View File
@@ -11,4 +11,4 @@ runs:
- name: Install libs needed for native build
shell: bash
run: |
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev
+32 -8
View File
@@ -283,6 +283,15 @@ firmware/
## Coding Conventions
### Formatting & the trunk toolchain
`trunk fmt` is the project formatter (`trunk_check` CI rejects unformatted code). For Claude Code users, `.claude/settings.json` ships a PostToolUse hook that runs `trunk fmt --force` on every file the agent writes or edits. The hook is pure sh/grep/sed — no python or jq required — but trunk itself must be able to run:
- Trunk's launcher (`~/.cache/trunk/launcher/trunk`, or `trunk` on PATH) downloads the CLI version pinned in `.trunk/trunk.yaml` on first use and again whenever that pin is bumped. **The launcher needs `curl` or `wget`**; without one it fails with "Cannot download… please install curl or wget", and the hook surfaces that as a warning on every write.
- No curl/wget available (e.g. a minimal WSL image)? Bootstrap by hand with any Python (PlatformIO bundles one at `~/.platformio/penv/bin/python`): download `https://trunk.io/releases/<ver>/trunk-<ver>-linux-x86_64.tar.gz` and place the `trunk` binary at `~/.cache/trunk/cli/<ver>-linux-x86_64/trunk` (chmod +x), where `<ver>` is the `cli.version` from `.trunk/trunk.yaml`.
- The hook fails loudly by design (visible warning, non-blocking). Silent no-op formatting hooks hide real breakage — don't re-add `2>/dev/null || true` around the whole thing.
- More generally: don't assume a stock Linux userland in hooks or helper scripts — minimal WSL/container images may lack `python3`, `curl`, `wget`, and `jq`. Prefer plain sh + coreutils, or PlatformIO's bundled Python for anything heavier.
### General Style
- Follow existing code style - run `trunk fmt` before commits
@@ -609,20 +618,35 @@ Most workflows can be triggered manually via `workflow_dispatch` for testing.
### Native unit tests (C++)
Unit tests in `test/` directory with 12 test suites:
Unit tests in `test/` directory with 17 test suites:
- `test_crypto/` - Cryptography
- `test_mqtt/` - MQTT integration
- `test_radio/` - Radio interface
- `test_mesh_module/` - Module framework
- `test_meshpacket_serializer/` - Packet serialization
- `test_transmit_history/` - Retransmission tracking
- `test_admin_radio/` - LoRa region/config validation and AdminModule dispatch
- `test_atak/` - ATAK integration
- `test_crypto/` - Cryptography
- `test_default/` - Default configuration
- `test_http_content_handler/` - HTTP handling
- `test_mac_from_string/` - MAC address parsing
- `test_mesh_module/` - Module framework
- `test_meshpacket_serializer/` - Packet serialization
- `test_mqtt/` - MQTT integration
- `test_packet_history/` - Packet history tracking
- `test_position_precision/` - Position precision helpers
- `test_radio/` - Radio interface
- `test_serial/` - Serial communication
- `test_traffic_management/` - Traffic management
- `test_transmit_history/` - Retransmission tracking
- `test_type_conversions/` - NodeDB v25 type conversion (bitfield round-trips, NodeInfoLite)
- `test_utf8/` - UTF-8 utilities
Run with: `pio test -e native`
Run command (preferred — avoids pipe-buffering and the Ubuntu externally-managed-environment error):
```bash
~/.platformio/penv/bin/python -m platformio test -e native -f test_your_suite > /tmp/test_out.txt 2>&1
grep -E 'error:|PASS|FAIL|succeeded|failed' /tmp/test_out.txt
tail -15 /tmp/test_out.txt
```
Do **not** use `pio test … | tail -N` — it discards build errors and shows stale cached results. Do **not** use `pio test … | grep` — line-buffering makes the terminal appear hung until the process exits. Redirect to a file first, then grep.
Simulation testing: `bin/test-simulator.sh`
@@ -0,0 +1,62 @@
name: Post Firmware Size Comment
on:
workflow_run:
workflows: [CI]
types: [completed]
permissions:
pull-requests: write
actions: read
jobs:
post-size-comment:
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion != 'cancelled' &&
github.repository == 'meshtastic/firmware'
continue-on-error: true
runs-on: ubuntu-latest
steps:
- name: Download size report
id: download
uses: actions/download-artifact@v8
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: size-report
path: ./
- name: Post or update PR comment
if: steps.download.outcome == 'success'
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const marker = '<!-- firmware-size-report -->';
const body = fs.readFileSync('./size-report.md', 'utf8');
const prNumber = parseInt(fs.readFileSync('./pr-number.txt', 'utf8').trim(), 10);
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}
+187
View File
@@ -0,0 +1,187 @@
name: Post Web Flasher Link Comment
on:
workflow_run:
workflows: [CI]
types: [completed]
permissions:
pull-requests: write
actions: read
jobs:
post-flasher-link:
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion != 'cancelled' &&
github.repository == 'meshtastic/firmware'
continue-on-error: true
runs-on: ubuntu-latest
steps:
# Per-board manifests carry the firmware's own metadata (activelySupported,
# displayName, ...) generated from each target's custom_meshtastic_* config.
- name: Download board manifests
uses: actions/download-artifact@v8
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
pattern: manifest-*
path: ./manifests
merge-multiple: true
- name: Post or update web flasher link comment
uses: actions/github-script@v8
with:
script: |
const marker = '<!-- web-flasher-link -->';
const run = context.payload.workflow_run;
const { owner, repo } = context.repo;
// Resolve the PR by matching the run's head SHA against the repo's open
// PRs. workflow_run.pull_requests is empty for fork PRs, and
// listPullRequestsAssociatedWithCommit won't return an open fork PR by
// its head commit — but pulls.list includes fork PRs. Matching on head
// SHA also enforces that the run is for the PR's current commit, so stale
// re-runs of an outdated commit won't match.
const openPrs = await github.paginate(github.rest.pulls.list, {
owner, repo, state: 'open', per_page: 100,
});
const pr = openPrs.find((p) => p.head.sha === run.head_sha);
if (!pr) {
core.info(`No open pull request matches commit ${run.head_sha}; skipping.`);
return;
}
const prNumber = pr.number;
// Restrict to trusted authors. NOTE: author_association is computed for
// the GITHUB_TOKEN, which cannot see *private/concealed* org memberships —
// those members come back as CONTRIBUTOR, not MEMBER. So gating on MEMBER
// alone silently excludes most maintainers. We allow the trusted set the
// token can actually identify (members, collaborators, and anyone with a
// previously merged PR). For strict members-only you'd need an org-read
// App/PAT token to call orgs.checkMembershipForUser.
const allowedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR', 'CONTRIBUTOR'];
if (!allowedAssociations.includes(pr.author_association)) {
core.info(`Author association ${pr.author_association} is not trusted; skipping.`);
return;
}
// Require at least one per-arch firmware artifact from gather-artifacts
const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, {
owner, repo, run_id: run.id, per_page: 100,
});
const archRe = /^firmware-(esp32|esp32s3|esp32c3|esp32c6|nrf52840|rp2040|rp2350|stm32)-(\d+\.\d+\.\d+\.[0-9a-f]+)$/;
const archArtifacts = artifacts.filter((a) => archRe.test(a.name) && !a.expired);
if (archArtifacts.length === 0) {
core.info('No per-arch firmware artifacts found; skipping.');
return;
}
const version = archRe.exec(archArtifacts[0].name)[2];
const expiresAt = archArtifacts[0].expires_at
? new Date(archArtifacts[0].expires_at).toISOString().slice(0, 10)
: null;
// Read each built board's manifest (.mt.json). activelySupported,
// displayName and architecture come straight from the board's
// custom_meshtastic_* platformio config, so the list is in sync with
// the firmware itself — no external device database needed.
const fs = require('fs');
let boards = [];
try {
boards = fs.readdirSync('./manifests')
.filter((f) => f.endsWith('.mt.json'))
.map((f) => {
try { return JSON.parse(fs.readFileSync(`./manifests/${f}`, 'utf8')); }
catch { return null; }
})
.filter((m) => m && m.activelySupported === true && m.platformioTarget)
.map((m) => ({
board: m.platformioTarget,
platform: m.architecture || '',
// displayName is maintainer-authored text; escape table-breaking pipes
displayName: String(m.displayName || m.platformioTarget).replace(/\|/g, '\\|'),
image: Array.isArray(m.images) && m.images[0] ? String(m.images[0]) : '',
}))
.sort((a, b) => a.board.localeCompare(b.board));
} catch (e) {
core.warning(`Could not read board manifests: ${e.message}`);
}
const flasherUrl = `https://flasher.meshtastic.org/?pr=${prNumber}`;
// Device illustrations are served by the flasher from the same image
// names the manifest declares (custom_meshtastic_images). The flasher
// serves its SPA shell (HTML, 200) for unknown paths, so confirm each
// image really resolves to an image before linking it.
const imageBase = 'https://flasher.meshtastic.org/img/devices/';
await Promise.all(boards.map(async (b) => {
if (!b.image) return;
try {
const res = await fetch(`${imageBase}${encodeURIComponent(b.image)}`);
const type = res.headers.get('content-type') || '';
if (!res.ok || !type.startsWith('image/')) b.image = '';
} catch { b.image = ''; }
}));
const boardLines = boards
.map((b) => {
const img = b.image ? `<img src="${imageBase}${encodeURIComponent(b.image)}" alt="" height="34">` : '';
return `| ${img} | ${b.displayName} | [\`${b.board}\`](${flasherUrl}&device=${encodeURIComponent(b.board)}) | ${b.platform} |`;
})
.join('\n');
// Shields.io badges. Only non-user-controlled, charset-constrained values
// (version, commit sha, counts, dates) go into badge URLs — never board
// names or the PR title — so the rendered comment cannot be spoofed.
const shieldText = (s) =>
encodeURIComponent(String(s).replace(/-/g, '--').replace(/_/g, '__').replace(/ /g, '_'));
const shield = (label, message, color) =>
`https://img.shields.io/badge/${shieldText(label)}-${shieldText(message)}-${color}`;
const buttonUrl =
`https://img.shields.io/badge/${shieldText('Flash this PR in the Web Flasher')}-2C2D3C?style=for-the-badge`;
const badges = [
`![firmware](${shield('firmware', version, '67EA94')})`,
`![commit](${shield('commit', run.head_sha.slice(0, 7), '2C2D3C')})`,
`![boards](${shield('boards', boards.length, '5C6BC0')})`,
];
if (expiresAt) badges.push(`![expires](${shield('expires', expiresAt, '9A4E00')})`);
// Only render the board table when there are supported boards to list
const boardTable = boards.length > 0 ? [
`<details><summary>Supported boards built by this PR (${boards.length})</summary>`,
'',
'| | Device | Board | Platform |',
'| --- | --- | --- | --- |',
boardLines,
'',
'</details>',
'',
] : [];
const body = [
marker,
'## ⚡ Try this PR in the Web Flasher',
'',
`[![Flash this PR in the Web Flasher](${buttonUrl})](${flasherUrl})`,
'',
badges.join(' '),
'',
'> [!WARNING]',
'> This is an automated, unreviewed CI test build. Back up your device configuration',
'> before flashing, and only flash devices you are able to recover.',
'',
...boardTable,
`*Build artifacts expire${expiresAt ? ` on ${expiresAt}` : ' after 30 days'}. Updated for \`${run.head_sha.slice(0, 7)}\`.*`,
].join('\n');
// Sticky comment: update in place when the marker is found
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: prNumber, per_page: 100,
});
const existing = comments.find((c) => c.body?.includes(marker));
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });
}
@@ -0,0 +1,62 @@
name: Post Web Flasher Build Placeholder
# Drops an immediate "build in progress" comment when a PR opens, so the web
# flasher entry shows up right away. The real CI-driven workflow
# (flasher-link-comment.yml) later replaces it in place via the shared marker.
#
# SECURITY: this uses pull_request_target (write token, runs for fork PRs) but is
# safe because it never checks out or runs PR code and posts a fully static body
# — no PR title, branch name, or other untrusted input is used anywhere.
on:
pull_request_target:
types: [opened, reopened]
permissions:
pull-requests: write
jobs:
post-placeholder:
if: github.repository == 'meshtastic/firmware'
continue-on-error: true
runs-on: ubuntu-latest
steps:
- name: Post web flasher build-in-progress placeholder
uses: actions/github-script@v8
with:
script: |
const marker = '<!-- web-flasher-link -->';
const { owner, repo } = context.repo;
const pr = context.payload.pull_request;
// Trusted authors only (matches the real workflow). author_association
// can't reflect private org membership for the token, so concealed
// members appear as CONTRIBUTOR — include it, or maintainers are excluded.
const allowedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR', 'CONTRIBUTOR'];
if (!allowedAssociations.includes(pr.author_association)) {
core.info(`Author association ${pr.author_association} is not trusted; skipping.`);
return;
}
// Only seed a placeholder when no flasher comment exists yet — never
// overwrite a real (or existing placeholder) comment.
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: pr.number, per_page: 100,
});
if (comments.some((c) => c.body?.includes(marker))) {
core.info('Flasher comment already exists; nothing to do.');
return;
}
const body = [
marker,
'## ⚡ Try this PR in the Web Flasher',
'',
'> [!NOTE]',
'> Building this pull request… the flash button, badges and supported-board',
'> list will appear here automatically once CI finishes.',
].join('\n');
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number, body,
});
+109 -28
View File
@@ -82,8 +82,9 @@ jobs:
fail-fast: false
matrix:
check: ${{ fromJson(needs.setup.outputs.check) }}
# Use 'arctastic' self-hosted runner pool when checking in the main repo
runs-on: ${{ github.repository_owner == 'meshtastic' && 'arctastic' || 'ubuntu-latest' }}
# Runs on GitHub-hosted runners so checks don't compete with builds for the
# self-hosted 'arctastic' pool (which builds use).
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
steps:
- uses: actions/checkout@v6
@@ -245,47 +246,126 @@ jobs:
path: ./*.elf
retention-days: 30
shame:
firmware-size-report:
if: github.repository == 'meshtastic/firmware'
continue-on-error: true
permissions:
contents: read
actions: read
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v6
if: github.event_name == 'pull_request'
with:
filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head)
fetch-depth: 0
- name: Download the current manifests
- name: Download current manifests
uses: actions/download-artifact@v8
with:
path: ./manifests-new/
path: ./manifests/
pattern: manifest-*
merge-multiple: true
- name: Upload combined manifests for later commit and global stats crunching.
- name: Collect current firmware sizes
run: python3 bin/collect_sizes.py ./manifests/ ./current-sizes.json
- name: Upload size report artifact
uses: actions/upload-artifact@v7
id: upload-manifest
with:
name: manifests-${{ github.sha }}
name: firmware-sizes-${{ github.sha }}
overwrite: true
path: manifests-new/*.mt.json
- name: Find the merge base
path: ./current-sizes.json
retention-days: 90
- name: Download baseline sizes from develop
if: github.event_name == 'pull_request'
run: echo "MERGE_BASE=$(git merge-base "origin/$base" "$head")" >> $GITHUB_ENV
continue-on-error: true
id: baseline-develop
env:
base: ${{ github.base_ref }}
head: ${{ github.sha }}
# Currently broken (for-loop through EVERY artifact -- rate limiting)
# - name: Download the old manifests
# if: github.event_name == 'pull_request'
# run: gh run download -R "$repo" --name "manifests-$merge_base" --dir manifest-old/
# env:
# GH_TOKEN: ${{ github.token }}
# merge_base: ${{ env.MERGE_BASE }}
# repo: ${{ github.repository }}
# - name: Do scan and post comment
# if: github.event_name == 'pull_request'
# run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/
GH_TOKEN: ${{ github.token }}
run: |
RUN_ID=$(gh run list -R "${{ github.repository }}" \
--workflow CI --branch develop --status success \
--limit 1 --json databaseId --jq '.[0].databaseId // empty')
if [ -n "$RUN_ID" ]; then
ARTIFACT_NAME=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" \
--jq '.artifacts[] | select(.name | startswith("firmware-sizes-")) | select(.expired == false) | .name' | head -1)
if [ -n "$ARTIFACT_NAME" ]; then
gh run download "$RUN_ID" -R "${{ github.repository }}" \
--name "$ARTIFACT_NAME" --dir ./baseline-develop/
cp "./baseline-develop/current-sizes.json" ./develop-sizes.json
echo "found=true" >> "$GITHUB_OUTPUT"
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi
- name: Download baseline sizes from master
if: github.event_name == 'pull_request'
continue-on-error: true
id: baseline-master
env:
GH_TOKEN: ${{ github.token }}
run: |
RUN_ID=$(gh run list -R "${{ github.repository }}" \
--workflow CI --branch master --status success \
--limit 1 --json databaseId --jq '.[0].databaseId // empty')
if [ -n "$RUN_ID" ]; then
ARTIFACT_NAME=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" \
--jq '.artifacts[] | select(.name | startswith("firmware-sizes-")) | select(.expired == false) | .name' | head -1)
if [ -n "$ARTIFACT_NAME" ]; then
gh run download "$RUN_ID" -R "${{ github.repository }}" \
--name "$ARTIFACT_NAME" --dir ./baseline-master/
cp "./baseline-master/current-sizes.json" ./master-sizes.json
echo "found=true" >> "$GITHUB_OUTPUT"
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi
- name: Generate size comparison report
if: github.event_name == 'pull_request'
id: report
run: |
ARGS="./current-sizes.json"
if [ -f ./develop-sizes.json ]; then
ARGS="$ARGS --baseline develop:./develop-sizes.json"
fi
if [ -f ./master-sizes.json ]; then
ARGS="$ARGS --baseline master:./master-sizes.json"
fi
REPORT=$(python3 bin/size_report.py $ARGS)
if [ -z "$REPORT" ]; then
echo "has_report=false" >> "$GITHUB_OUTPUT"
else
echo "has_report=true" >> "$GITHUB_OUTPUT"
{
echo '<!-- firmware-size-report -->'
echo '# Firmware Size Report'
echo ''
echo "$REPORT"
echo ''
echo '---'
echo "*Updated for ${{ github.sha }}*"
} > ./size-report.md
cat ./size-report.md >> "$GITHUB_STEP_SUMMARY"
fi
- name: Save PR number
if: github.event_name == 'pull_request' && steps.report.outputs.has_report == 'true'
run: echo "${{ github.event.pull_request.number }}" > ./pr-number.txt
- name: Upload size report
if: github.event_name == 'pull_request' && steps.report.outputs.has_report == 'true'
uses: actions/upload-artifact@v7
with:
name: size-report
path: |
./size-report.md
./pr-number.txt
retention-days: 5
release-artifacts:
permissions: # Needed for 'gh release upload'.
@@ -333,6 +413,7 @@ jobs:
prerelease: true
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.version.outputs.long }}
target_commitish: ${{ github.sha }}
body: ${{ steps.release_notes.outputs.notes }}
- name: Download source deb
+21 -1
View File
@@ -37,13 +37,33 @@ jobs:
sed -i -e "s#${PWD}#.#" coverage_base.info # Make paths relative.
- name: Integration test
# Cap the whole step: if the simulator ever fails to exit (e.g. the
# exit_simulator admin path regresses again) the job must fail fast,
# not run to GitHub's 6-hour limit.
timeout-minutes: 5
run: |
.pio/build/coverage/meshtasticd -s &
PID=$!
trap 'kill "$PID" 2>/dev/null || true' EXIT
timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done"
echo "Simulator started, launching python test..."
python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
wait
# The Python harness sends exit_simulator and exits; the simulator is
# expected to terminate on its own. Give it a moment, then verify.
# If it is still alive the exit handshake is broken — fail loudly and
# do NOT fall through to `wait`, which would otherwise block until the
# job's hard timeout.
for i in $(seq 1 10); do
kill -0 "$PID" 2>/dev/null || break
sleep 1
done
if kill -0 "$PID" 2>/dev/null; then
echo "::error title=Simulator did not exit::meshtasticd ignored exit_simulator and is still running after the integration test. The exit_simulator admin path is broken (see AdminModule::handleReceivedProtobuf, ARCH_PORTDUINO bypass). Killing it to avoid a 6-hour CI overrun."
kill -9 "$PID" 2>/dev/null || true
wait "$PID" 2>/dev/null || true
exit 1
fi
wait "$PID" 2>/dev/null || true
- name: Capture coverage information
if: always() # run this step even if previous step failed
+9 -4
View File
@@ -16,13 +16,18 @@ jobs:
submodules: true
- name: Update submodule
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' }}
if: ${{ github.ref_name == 'master' || github.ref_name == 'develop' }}
working-directory: protobufs
env:
# Use the branch that triggered the workflow as the protobuf branch.
GIT_BRANCH: ${{ github.ref_name }}
run: |
git submodule update --remote protobufs
git fetch --prune origin $GIT_BRANCH
git checkout origin/$GIT_BRANCH
- name: Download nanopb
run: |
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.9.1-linux-x86.tar.gz
wget https://github.com/nanopb/nanopb/releases/download/nanopb-0.4.9.1/nanopb-0.4.9.1-linux-x86.tar.gz
tar xvzf nanopb-0.4.9.1-linux-x86.tar.gz
mv nanopb-0.4.9.1-linux-x86 nanopb-0.4.9
@@ -33,7 +38,7 @@ jobs:
- name: Create pull request
uses: peter-evans/create-pull-request@v8
with:
branch: create-pull-request/update-protobufs
branch: create-pull-request/update-protobufs-${{ github.ref_name }}
labels: submodules
title: Update protobufs and classes
commit-message: Update protobufs
+4 -7
View File
@@ -47,6 +47,10 @@ data/boot/logo.*
managed_components/*
arduino-lib-builder*
dependencies.lock
# JLink / RTT debug artifacts (nRF SoCs)
flash.jlink
rtt_*.txt
idf_component.yml
CMakeLists.txt
/sdkconfig.*
@@ -62,10 +66,3 @@ userPrefs.jsonc.mcp-session-bak
# compiled .proto outputs are ephemeral build artifacts.
build/fixtures/
bin/_generated/
# Build artifacts: anything compiling .o/.a outside .pio/ is accidental.
# Explicit exceptions for vendored binaries (Morse Micro mm-iot-esp32 SDK).
*.o
*.a
!lib/MorseWlan/lib/**/*.a
!lib/MorseWlan/src/*.mbin.o
+4 -4
View File
@@ -4,11 +4,11 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.9.0
ref: v1.10.0
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.528
- checkov@3.2.529
- renovate@43.150.0
- prettier@3.8.3
- trufflehog@3.95.3
@@ -16,7 +16,7 @@ lint:
- bandit@1.9.4
- trivy@0.70.0
- taplo@0.10.0
- ruff@0.15.12
- ruff@0.15.13
- isort@8.0.1
- markdownlint@0.48.0
- oxipng@10.1.1
@@ -26,7 +26,7 @@ lint:
- hadolint@2.14.0
- shfmt@3.6.0
- shellcheck@0.11.0
- black@26.3.1
- black@26.5.1
- git-diff-check
- gitleaks@8.30.1
- clang-format@16.0.3
+14 -14
View File
@@ -10,18 +10,18 @@ This file (`AGENTS.md`) is a short pointer + quick reference for agents that don
## Quick command reference
| Action | Command |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Build a firmware variant | `pio run -e <env>` (e.g. `pio run -e rak4631`, `pio run -e heltec-v3`) |
| Build native macOS host binary | `pio run -e native-macos` (Homebrew prereqs + CH341 LoRa setup in `variants/native/portduino/platformio.ini`) |
| Clean + rebuild | `pio run -e <env> -t clean && pio run -e <env>` |
| Flash a device | `pio run -e <env> -t upload --upload-port <port>` (or use the `pio_flash` MCP tool) |
| Run firmware unit tests (native) | `pio test -e native` |
| Run MCP hardware tests | `./mcp-server/run-tests.sh` |
| Live TUI test runner | `mcp-server/.venv/bin/meshtastic-mcp-test-tui` |
| Format before commit | `trunk fmt` |
| Regenerate protobuf bindings | `bin/regen-protos.sh` |
| Generate CI matrix | `./bin/generate_ci_matrix.py all [--level pr]` |
| Action | Command |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Build a firmware variant | `pio run -e <env>` (e.g. `pio run -e rak4631`, `pio run -e heltec-v3`) |
| Build native macOS host binary | `pio run -e native-macos` (Homebrew prereqs + CH341 LoRa setup in `variants/native/portduino/platformio.ini`) |
| Clean + rebuild | `pio run -e <env> -t clean && pio run -e <env>` |
| Flash a device | `pio run -e <env> -t upload --upload-port <port>` (or use the `pio_flash` MCP tool) |
| Run firmware unit tests (native) | `~/.platformio/penv/bin/python -m platformio test -e native > /tmp/test_out.txt 2>&1` then `grep -E 'error:\|PASS\|FAIL\|succeeded\|failed' /tmp/test_out.txt` (redirect first — piping causes line-buffering) |
| Run MCP hardware tests | `./mcp-server/run-tests.sh` |
| Live TUI test runner | `mcp-server/.venv/bin/meshtastic-mcp-test-tui` |
| Format before commit | `trunk fmt` |
| Regenerate protobuf bindings | `bin/regen-protos.sh` |
| Generate CI matrix | `./bin/generate_ci_matrix.py all [--level pr]` |
## MCP server (device + test automation)
@@ -64,7 +64,7 @@ Key rotation to never trigger casually: only the **full** factory reset (`factor
- **One MCP call per serial port at a time.** The port lock is exclusive; concurrent calls deadlock. Sequence: open → read/mutate → close, then next device.
- **`userPrefs.jsonc` is session state during tests.** The `_session_userprefs` fixture snapshots + restores it; never edit it from inside a test.
- **Don't speculate about firmware root causes.** When evidence doesn't support a classification, say "unknown" and list what would disambiguate.
- **Run `trunk fmt` before proposing a commit.** The `trunk_check` CI gate will reject unformatted code.
- **Run `trunk fmt` before proposing a commit.** The `trunk_check` CI gate will reject unformatted code. Claude Code runs it automatically via the PostToolUse hook in `.claude/settings.json`; trunk's launcher needs `curl` or `wget` to bootstrap its pinned CLI — see **Formatting & the trunk toolchain** in `.github/copilot-instructions.md` for the no-curl bootstrap procedure.
- **`confirm=True` on destructive MCP tools is a real gate, not a formality.** Don't bypass it via auto-approve settings.
- **Keep code comments minimal — one or two lines, max.** Comment only when the _why_ isn't obvious from the code; never restate what the next line does. No multi-paragraph block comments explaining straightforward changes. The diff and commit message carry the rationale; the code carries the behavior.
- **Use `Throttle` for time-based rate limiting, not raw `millis()` math.** `src/mesh/Throttle.h` provides `Throttle::isWithinTimespanMs(lastMs, intervalMs)` (returns true while inside the cooldown) and `Throttle::execute(&lastMs, intervalMs, func)` (function-pointer form that updates the timestamp on fire). Use these for any "did N ms pass since X" check — raw `millis() > lastMs + N` is rollover-unsafe (breaks after ~49.7 days) and inconsistent with the rest of the codebase. The helpers compute `now - lastMs` with unsigned subtraction, which wraps correctly.
@@ -108,7 +108,7 @@ Sequence these; don't parallelize on the same port.
| `src/modules/` | Feature modules; `Telemetry/Sensor/` has 50+ I2C sensor drivers |
| `variants/` | 200+ hardware variant definitions (`variant.h` + `platformio.ini` per board) |
| `protobufs/` | `.proto` definitions; regenerate with `bin/regen-protos.sh` |
| `test/` | Firmware unit tests (12 suites; `pio test -e native`) |
| `test/` | Firmware unit tests (17 suites; `pio test -e native`) |
| `mcp-server/` | Python MCP server + pytest hardware integration tests |
| `mcp-server/tests/` | Tiered pytest suite: `unit/`, `mesh/`, `telemetry/`, `monitor/`, `recovery/`, `ui/`, `fleet/`, `admin/`, `provisioning/` |
| `.claude/commands/` | Claude Code slash command bodies |
+2 -2
View File
@@ -14,7 +14,7 @@ ENV PIP_BREAK_SYSTEM_PACKAGES=1
RUN apt-get update && apt-get install --no-install-recommends -y \
curl wget g++ zip git ca-certificates pkg-config \
python3-pip python3-grpc-tools \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libgpiod-dev libyaml-cpp-dev libjsoncpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
@@ -53,7 +53,7 @@ ENV TZ=Etc/UTC
USER root
RUN apt-get update && apt-get --no-install-recommends -y install \
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libjsoncpp26 libi2c0 libuv1t64 libusb-1.0-0-dev \
liborcania2.3 libulfius2.7t64 libssl3t64 \
libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
+1 -1
View File
@@ -7,7 +7,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
# hadolint ignore=DL3008
RUN apt-get update && apt-get install --no-install-recommends -y \
g++ git ca-certificates pkg-config \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libgpiod-dev libyaml-cpp-dev libjsoncpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
+2 -2
View File
@@ -16,7 +16,7 @@ ENV PIP_BREAK_SYSTEM_PACKAGES=1
RUN apk --no-cache add \
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
py3-pip py3-grpcio-tools \
libgpiod-dev yaml-cpp-dev bluez-dev \
libgpiod-dev yaml-cpp-dev jsoncpp-dev bluez-dev \
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev \
&& rm -rf /var/cache/apk/* \
@@ -48,7 +48,7 @@ LABEL org.opencontainers.image.title="Meshtastic" \
USER root
RUN apk --no-cache add \
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
shadow libstdc++ libbsd libgpiod yaml-cpp jsoncpp libusb \
i2c-tools libuv libx11 libinput libxkbcommon sdl2 \
&& rm -rf /var/cache/apk/* \
&& mkdir -p /var/lib/meshtasticd \
+1 -1
View File
@@ -38,4 +38,4 @@ cp bin/device-install.* $OUTDIR/
cp bin/device-update.* $OUTDIR/
echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Collect firmware binary sizes from manifest (.mt.json) files into a single report."""
import json
import os
import sys
def collect_sizes(manifest_dir):
"""Scan manifest_dir for .mt.json files and return {board: size_bytes} dict."""
sizes = {}
for fname in sorted(os.listdir(manifest_dir)):
if not fname.endswith(".mt.json"):
continue
path = os.path.join(manifest_dir, fname)
with open(path) as f:
data = json.load(f)
board = data.get("platformioTarget", fname.replace(".mt.json", ""))
# Find the main firmware .bin size (largest .bin, excluding OTA/littlefs/bleota)
bin_size = None
for entry in data.get("files", []):
name = entry.get("name", "")
if name.startswith("firmware-") and name.endswith(".bin"):
bin_size = entry["bytes"]
break
# Fallback: any .bin that isn't ota/littlefs/bleota
if bin_size is None:
for entry in data.get("files", []):
name = entry.get("name", "")
if name.endswith(".bin") and not any(
x in name for x in ["littlefs", "bleota", "ota"]
):
bin_size = entry["bytes"]
break
if bin_size is not None:
sizes[board] = bin_size
return sizes
if __name__ == "__main__":
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <manifest_dir> <output.json>", file=sys.stderr)
sys.exit(1)
manifest_dir = sys.argv[1]
output_path = sys.argv[2]
sizes = collect_sizes(manifest_dir)
with open(output_path, "w") as f:
json.dump(sizes, f, indent=2, sort_keys=True)
print(f"Collected sizes for {len(sizes)} targets -> {output_path}")
+5 -1
View File
@@ -5,7 +5,9 @@ Meta:
- raspberry-pi
Lora:
### RAK13300 in Slot 2 pins
### RAK13300 in Slot 2
Module: sx1262
IRQ: 18 #IO6
Reset: 24 # IO4
Busy: 19 # IO5
@@ -13,5 +15,7 @@ Lora:
Enable_Pins:
- 26
- 23
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: 7
+6 -2
View File
@@ -5,14 +5,18 @@ Meta:
- raspberry-pi
Lora:
### RAK13302 in Slot 2 pins
### RAK13302 in Slot 2
Module: sx1262
IRQ: 18 #IO6
Reset: 24 # IO4
Busy: 19 # IO5
# Ant_sw: 23 # IO3
# Ant_sw: 23 # IO3
Enable_Pins:
- 26
- 23
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: 7
TX_GAIN_LORA: [9, 9, 10, 11, 9, 8, 9, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 10, 9, 8]
+8 -4
View File
@@ -293,9 +293,12 @@ if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
board_arch = infer_architecture(env.BoardConfig())
should_skip_manifest = board_arch is None
# For host/native envs, avoid depending on 'buildprog' (some targets don't define it)
mtjson_deps = [] if should_skip_manifest else ["buildprog"]
if not should_skip_manifest and platform.name == "espressif32":
# Most platforms can generate the manifest as part of the default 'buildprog' target.
# Typically this passes success/failure properly.
mtjson_deps = ["buildprog"]
if platform.name == "espressif32":
# On ESP32, we need to explicitly depend upon the binary to prevent fake-success upon failure.
mtjson_deps = ["$BUILD_DIR/${PROGNAME}.bin"]
# Build littlefs image as part of mtjson target
# Equivalent to `pio run -t buildfs`
target_lfs = env.DataToBin(
@@ -309,7 +312,8 @@ if should_skip_manifest:
env.AddCustomTarget(
name="mtjson",
dependencies=mtjson_deps,
# For host/native envs, avoid depending on 'buildprog' (some targets don't define it)
dependencies=[],
actions=[skip_manifest],
title="Meshtastic Manifest (skipped)",
description="mtjson generation is skipped for native environments",
-95
View File
@@ -1,95 +0,0 @@
import sys
import os
import json
from github import Github
def parseFile(path):
with open(path, "r") as f:
data = json.loads(f)
for file in data["files"]:
if file["name"].endswith(".bin"):
return file["name"], file["bytes"]
if len(sys.argv) != 4:
print(f"expected usage: {sys.argv[0]} <PR number> <path to old-manifests> <path to new-manifests>")
sys.exit(1)
pr_number = int(sys.argv[1])
token = os.getenv("GITHUB_TOKEN")
if not token:
raise EnvironmentError("GITHUB_TOKEN not found in environment.")
repo_name = os.getenv("GITHUB_REPOSITORY") # "owner/repo"
if not repo_name:
raise EnvironmentError("GITHUB_REPOSITORY not found in environment.")
oldFiles = sys.argv[2]
old = set(os.path.join(oldFiles, f) for f in os.listdir(oldFiles) if os.path.isfile(f))
newFiles = sys.argv[3]
new = set(os.path.join(newFiles, f) for f in os.listdir(newFiles) if os.path.isfile(f))
startMarkdown = "# Target Size Changes\n\n"
markdown = ""
newlyIntroduced = new - old
if len(newlyIntroduced) > 0:
markdown += "## Newly Introduced Targets\n\n"
# create a table
markdown += "| File | Size |\n"
markdown += "| ---- | ---- |\n"
for f in newlyIntroduced:
name, size = parseFile(f)
markdown += f"| `{name}` | {size}b |\n"
# do not log removed targets
# PRs only run a small subset of builds, so removed targets are not meaningful
# since they are very likely to just be not ran in PR CI
both = old & new
degradations = []
improvements = []
for f in both:
oldName, oldSize = parseFile(f)
_, newSize = parseFile(f)
if oldSize != newSize:
if newSize < oldSize:
improvements.append((oldName, oldSize, newSize))
else:
degradations.append((oldName, oldSize, newSize))
if len(degradations) > 0:
markdown += "\n## Degradation\n\n"
# create a table
markdown += "| File | Difference | Old Size | New Size |\n"
markdown += "| ---- | ---------- | -------- | -------- |\n"
for oldName, oldSize, newSize in degradations:
markdown += f"| `{oldName}` | **{oldSize - newSize}b** | {oldSize}b | {newSize}b |\n"
if len(improvements) > 0:
markdown += "\n## Improvement\n\n"
# create a table
markdown += "| File | Difference | Old Size | New Size |\n"
markdown += "| ---- | ---------- | -------- | -------- |\n"
for oldName, oldSize, newSize in improvements:
markdown += f"| `{oldName}` | **{oldSize - newSize}b** | {oldSize}b | {newSize}b |\n"
if len(markdown) == 0:
markdown = "No changes in target sizes detected."
g = Github(token)
repo = g.get_repo(repo_name)
pr = repo.get_pull(pr_number)
existing_comment = None
for comment in pr.get_issue_comments():
if comment.body.startswith(startMarkdown):
existing_comment = comment
break
final_markdown = startMarkdown + markdown
if existing_comment:
existing_comment.edit(body=final_markdown)
else:
pr.create_issue_comment(body=final_markdown)
+171
View File
@@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""Compare firmware size reports and generate a markdown summary.
Usage:
size_report.py <new_sizes.json> [--baseline <label>:<old_sizes.json>]...
Examples:
# Compare PR against develop and master baselines
size_report.py pr.json --baseline develop:develop.json --baseline master:master.json
# Single baseline comparison
size_report.py pr.json --baseline develop:develop.json
# No baselines — shows sizes with blank delta columns
size_report.py pr.json
"""
import argparse
import json
import sys
def load_sizes(path):
with open(path) as f:
return json.load(f)
def format_delta(n):
"""Format byte delta with sign and human-friendly suffix."""
sign = "+" if n > 0 else ""
if abs(n) >= 1024:
return f"{sign}{n:,} ({sign}{n / 1024:.1f} KB)"
return f"{sign}{n:,}"
def generate_markdown(new_sizes, baselines, top_n=5):
"""Generate a single table with current size and delta columns per baseline.
baselines: list of (label, old_sizes_dict), may be empty
"""
labels = [label for label, _ in baselines]
# Build rows: (board, current_size, [(delta, abs_delta) per baseline])
rows = []
for board in sorted(new_sizes):
current = new_sizes[board]
deltas = []
for _, old_sizes in baselines:
old = old_sizes.get(board)
if old is not None:
d = current - old
deltas.append((d, abs(d)))
else:
deltas.append((None, 0))
# Sort key: max abs delta across baselines (biggest changes first)
max_abs = max((ad for _, ad in deltas), default=0)
rows.append((board, current, deltas, max_abs))
rows.sort(key=lambda r: r[3], reverse=True)
# Summary line
sections = []
summary_parts = [f"{len(new_sizes)} targets"]
for i, (label, old_sizes) in enumerate(baselines):
increases = sum(
1 for _, _, deltas, _ in rows if deltas[i][0] is not None and deltas[i][0] > 0
)
decreases = sum(
1 for _, _, deltas, _ in rows if deltas[i][0] is not None and deltas[i][0] < 0
)
net = sum(
deltas[i][0] for _, _, deltas, _ in rows if deltas[i][0] is not None
)
parts = []
if increases:
parts.append(f"{increases} increased")
if decreases:
parts.append(f"{decreases} decreased")
if parts:
parts.append(f"net {format_delta(net)}")
summary_parts.append(f"vs `{label}`: {', '.join(parts)}")
else:
summary_parts.append(f"vs `{label}`: no changes")
if not baselines:
summary_parts.append("no baseline available yet")
sections.append(f"**{' | '.join(summary_parts)}**\n")
# Table header
header = "| Target | Size |"
separator = "|--------|-----:|"
for label in labels:
header += f" vs `{label}` |"
separator += "----------:|"
sections.append(header)
sections.append(separator)
def format_row(board, current, deltas):
row = f"| `{board}` | {current:,} |"
for d, _ in deltas:
if d is None:
row += " |"
elif d == 0:
row += " 0 |"
else:
icon = "📈" if d > 0 else "📉"
row += f" {icon} {format_delta(d)} |"
return row
# Top N rows always visible
top = rows[:top_n]
for board, current, deltas, _ in top:
sections.append(format_row(board, current, deltas))
# Remaining rows in expandable section
rest = rows[top_n:]
if rest:
sections.append("")
sections.append(
f"<details><summary>Show {len(rest)} more target(s)</summary>\n"
)
sections.append(header)
sections.append(separator)
for board, current, deltas, _ in rest:
sections.append(format_row(board, current, deltas))
sections.append("\n</details>")
sections.append("")
return "\n".join(sections)
def main():
parser = argparse.ArgumentParser(description="Compare firmware size reports")
parser.add_argument("new_sizes", help="Path to new sizes JSON")
parser.add_argument(
"--baseline",
action="append",
default=[],
metavar="LABEL:PATH",
help="Baseline to compare against (e.g. develop:develop.json)",
)
parser.add_argument(
"--top",
type=int,
default=5,
help="Number of top changes to show before collapsing (default: 5)",
)
args = parser.parse_args()
new_sizes = load_sizes(args.new_sizes)
# Silence output when no targets were built — repo maintainer choice
if not new_sizes:
return
baselines = []
for b in args.baseline:
if ":" not in b:
print(f"Error: baseline must be LABEL:PATH, got '{b}'", file=sys.stderr)
sys.exit(1)
label, path = b.split(":", 1)
baselines.append((label, load_sizes(path)))
md = generate_markdown(new_sizes, baselines, top_n=args.top)
print(md)
if __name__ == "__main__":
main()
+262
View File
@@ -0,0 +1,262 @@
#!/usr/bin/env python3
"""Tests for bin/collect_sizes.py and bin/size_report.py."""
import json
import os
import subprocess
import sys
import tempfile
SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "bin")
def make_manifest(target, firmware_bytes, extra_files=None):
"""Create a minimal .mt.json manifest dict."""
files = [{"name": f"firmware-{target}-2.6.0.bin", "bytes": firmware_bytes}]
if extra_files:
files.extend(extra_files)
return {
"platformioTarget": target,
"version": "2.6.0.test",
"files": files,
}
def write_manifests(tmpdir, manifests):
"""Write manifest dicts as .mt.json files into tmpdir."""
for target, data in manifests.items():
path = os.path.join(tmpdir, f"firmware-{target}.mt.json")
with open(path, "w") as f:
json.dump(data, f)
def run_script(script, args):
"""Run a Python script and return (returncode, stdout, stderr)."""
result = subprocess.run(
[sys.executable, os.path.join(SCRIPTS_DIR, script)] + args,
capture_output=True,
text=True,
)
return result.returncode, result.stdout, result.stderr
def test_collect_sizes_basic():
"""collect_sizes picks up firmware-*.bin entries from manifests."""
with tempfile.TemporaryDirectory() as tmpdir:
outfile = os.path.join(tmpdir, "sizes.json")
manifests = {
"heltec-v3": make_manifest("heltec-v3", 1048576),
"rak4631": make_manifest("rak4631", 524288),
"tbeam": make_manifest("tbeam", 786432),
}
write_manifests(tmpdir, manifests)
rc, stdout, stderr = run_script("collect_sizes.py", [tmpdir, outfile])
assert rc == 0, f"collect_sizes failed: {stderr}"
assert "3 targets" in stdout
with open(outfile) as f:
sizes = json.load(f)
assert sizes == {"heltec-v3": 1048576, "rak4631": 524288, "tbeam": 786432}
def test_collect_sizes_fallback_bin():
"""collect_sizes falls back to non-firmware-prefixed .bin if no firmware-*.bin."""
with tempfile.TemporaryDirectory() as tmpdir:
outfile = os.path.join(tmpdir, "sizes.json")
# Manifest with only a generic .bin (no firmware- prefix)
data = {
"platformioTarget": "custom-board",
"files": [
{"name": "littlefs-custom-board.bin", "bytes": 100000},
{"name": "custom-board.bin", "bytes": 500000},
],
}
path = os.path.join(tmpdir, "firmware-custom-board.mt.json")
with open(path, "w") as f:
json.dump(data, f)
rc, stdout, stderr = run_script("collect_sizes.py", [tmpdir, outfile])
assert rc == 0, f"collect_sizes failed: {stderr}"
with open(outfile) as f:
sizes = json.load(f)
assert sizes == {"custom-board": 500000}
def test_collect_sizes_skips_ota_littlefs():
"""collect_sizes ignores ota/littlefs/bleota .bin files in fallback."""
with tempfile.TemporaryDirectory() as tmpdir:
outfile = os.path.join(tmpdir, "sizes.json")
data = {
"platformioTarget": "board-x",
"files": [
{"name": "littlefs-board-x.bin", "bytes": 100000},
{"name": "bleota-board-x.bin", "bytes": 50000},
{"name": "mt-board-x-ota.bin", "bytes": 60000},
],
}
path = os.path.join(tmpdir, "firmware-board-x.mt.json")
with open(path, "w") as f:
json.dump(data, f)
rc, stdout, stderr = run_script("collect_sizes.py", [tmpdir, outfile])
assert rc == 0
with open(outfile) as f:
sizes = json.load(f)
# No valid firmware .bin found, board should be absent
assert sizes == {}
def test_collect_sizes_ignores_non_mt_json():
"""collect_sizes skips non .mt.json files."""
with tempfile.TemporaryDirectory() as tmpdir:
outfile = os.path.join(tmpdir, "sizes.json")
# Write a valid manifest
manifests = {"rak4631": make_manifest("rak4631", 500000)}
write_manifests(tmpdir, manifests)
# Write a decoy file
with open(os.path.join(tmpdir, "readme.txt"), "w") as f:
f.write("not a manifest")
rc, stdout, stderr = run_script("collect_sizes.py", [tmpdir, outfile])
assert rc == 0
with open(outfile) as f:
sizes = json.load(f)
assert list(sizes.keys()) == ["rak4631"]
def test_size_report_no_baseline():
"""size_report with no baselines shows sizes only."""
with tempfile.TemporaryDirectory() as tmpdir:
sizes_file = os.path.join(tmpdir, "new.json")
with open(sizes_file, "w") as f:
json.dump({"heltec-v3": 1000000, "rak4631": 500000}, f)
rc, stdout, stderr = run_script("size_report.py", [sizes_file])
assert rc == 0, f"size_report failed: {stderr}"
assert "2 targets" in stdout
assert "no baseline available yet" in stdout
assert "`heltec-v3`" in stdout
assert "`rak4631`" in stdout
def test_size_report_with_baseline():
"""size_report shows deltas against a baseline."""
with tempfile.TemporaryDirectory() as tmpdir:
new_file = os.path.join(tmpdir, "new.json")
old_file = os.path.join(tmpdir, "old.json")
with open(new_file, "w") as f:
json.dump({"heltec-v3": 1050000, "rak4631": 500000, "tbeam": 800000}, f)
with open(old_file, "w") as f:
json.dump({"heltec-v3": 1000000, "rak4631": 500000, "tbeam": 810000}, f)
rc, stdout, stderr = run_script(
"size_report.py", [new_file, "--baseline", f"develop:{old_file}"]
)
assert rc == 0, f"size_report failed: {stderr}"
assert "3 targets" in stdout
assert "1 increased" in stdout
assert "1 decreased" in stdout
# heltec-v3 grew by 50000
assert "📈" in stdout
# tbeam shrank by 10000
assert "📉" in stdout
# rak4631 unchanged
assert "vs `develop`" in stdout
def test_size_report_multiple_baselines():
"""size_report handles multiple baselines."""
with tempfile.TemporaryDirectory() as tmpdir:
new_file = os.path.join(tmpdir, "new.json")
dev_file = os.path.join(tmpdir, "develop.json")
master_file = os.path.join(tmpdir, "master.json")
with open(new_file, "w") as f:
json.dump({"board-a": 100000}, f)
with open(dev_file, "w") as f:
json.dump({"board-a": 95000}, f)
with open(master_file, "w") as f:
json.dump({"board-a": 90000}, f)
rc, stdout, stderr = run_script(
"size_report.py",
[new_file, "--baseline", f"develop:{dev_file}", "--baseline", f"master:{master_file}"],
)
assert rc == 0, f"size_report failed: {stderr}"
assert "vs `develop`" in stdout
assert "vs `master`" in stdout
def test_size_report_new_target_no_baseline_entry():
"""size_report handles targets not present in baseline (new boards)."""
with tempfile.TemporaryDirectory() as tmpdir:
new_file = os.path.join(tmpdir, "new.json")
old_file = os.path.join(tmpdir, "old.json")
with open(new_file, "w") as f:
json.dump({"new-board": 300000, "existing": 500000}, f)
with open(old_file, "w") as f:
json.dump({"existing": 500000}, f)
rc, stdout, stderr = run_script(
"size_report.py", [new_file, "--baseline", f"develop:{old_file}"]
)
assert rc == 0, f"size_report failed: {stderr}"
assert "`new-board`" in stdout
assert "no changes" in stdout # only existing is compared, delta=0
def test_size_report_all_unchanged():
"""size_report shows 'no changes' when all sizes match."""
with tempfile.TemporaryDirectory() as tmpdir:
sizes_file = os.path.join(tmpdir, "sizes.json")
with open(sizes_file, "w") as f:
json.dump({"board-a": 100000, "board-b": 200000}, f)
rc, stdout, stderr = run_script(
"size_report.py", [sizes_file, "--baseline", f"develop:{sizes_file}"]
)
assert rc == 0, f"size_report failed: {stderr}"
assert "no changes" in stdout
def test_collect_sizes_bad_args():
"""collect_sizes exits with error on wrong arg count."""
rc, stdout, stderr = run_script("collect_sizes.py", [])
assert rc == 1
assert "Usage" in stderr
def test_size_report_bad_baseline_format():
"""size_report exits with error on malformed --baseline."""
with tempfile.TemporaryDirectory() as tmpdir:
sizes_file = os.path.join(tmpdir, "sizes.json")
with open(sizes_file, "w") as f:
json.dump({"x": 1}, f)
rc, stdout, stderr = run_script(
"size_report.py", [sizes_file, "--baseline", "no-colon-here"]
)
assert rc == 1
assert "LABEL:PATH" in stderr
if __name__ == "__main__":
tests = [v for k, v in globals().items() if k.startswith("test_")]
passed = 0
failed = 0
for test in tests:
try:
test()
print(f" PASS: {test.__name__}")
passed += 1
except AssertionError as e:
print(f" FAIL: {test.__name__}: {e}")
failed += 1
except Exception as e:
print(f" ERROR: {test.__name__}: {type(e).__name__}: {e}")
failed += 1
print(f"\n{passed} passed, {failed} failed out of {passed + failed}")
sys.exit(1 if failed else 0)
+1 -1
View File
@@ -7,7 +7,7 @@
"extra_flags": [
"-D CDEBYTE_EORA_S3",
"-D ARDUINO_USB_CDC_ON_BOOT=1",
"-D ARDUINO_USB_MODE=0",
"-D ARDUINO_USB_MODE=1",
"-D ARDUINO_RUNNING_CORE=1",
"-D ARDUINO_EVENT_RUNNING_CORE=1",
"-D BOARD_HAS_PSRAM"
+1 -1
View File
@@ -6,7 +6,7 @@
"core": "esp32",
"extra_flags": [
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DBOARD_HAS_PSRAM"
+1 -1
View File
@@ -8,7 +8,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+54
View File
@@ -0,0 +1,54 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x4405"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x2886", "0x1667"]
],
"usb_product": "HT-n5262",
"mcu": "nrf52840",
"variant": "heltec_mesh_node_t1",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Heltec Mesh Node T1",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://heltec.org",
"vendor": "Heltec"
}
+1 -1
View File
@@ -9,7 +9,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -9,7 +9,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -9,7 +9,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -8,7 +8,7 @@
"extra_flags": [
"-DHELTEC_WIRELESS_TRACKER",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -7,7 +7,7 @@
"core": "esp32",
"extra_flags": [
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+26
View File
@@ -0,0 +1,26 @@
{
"build": {
"cpu": "cortex-m33",
"f_cpu": "128000000L",
"mcu": "nrf54l15",
"zephyr": {
"variant": "nrf54l15dk/nrf54l15/cpuapp"
}
},
"connectivity": ["bluetooth"],
"debug": {
"default_tools": ["jlink"],
"jlink_device": "nRF54L15_M33",
"svd_path": "nrf54l15.svd"
},
"frameworks": ["zephyr"],
"name": "Nordic nRF54L15-DK (PCA10156)",
"upload": {
"maximum_ram_size": 262144,
"maximum_size": 1572864,
"protocol": "jlink",
"protocols": ["jlink"]
},
"url": "https://www.nordicsemi.com/Products/nRF54L15",
"vendor": "Nordic Semiconductor"
}
+1 -1
View File
@@ -8,7 +8,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=0"
],
+1 -1
View File
@@ -9,7 +9,7 @@
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_1W",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -8,7 +8,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -8,7 +8,7 @@
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_S3_CORE",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -7,7 +7,7 @@
"extra_flags": [
"-DLILYGO_T3S3_V1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DBOARD_HAS_PSRAM"
+1 -1
View File
@@ -10,7 +10,7 @@
"-DBOARD_HAS_PSRAM",
"-DUNPHONE_SPIN=9",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
-50
View File
@@ -1,50 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "nrf52832_s132_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DNRF52832_XXAA -DNRF52",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "Feather nRF52832 Express",
"mcu": "nrf52832",
"variant": "WisCore_RAK4600_Board",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS132",
"sd_name": "s132",
"sd_version": "6.1.1",
"sd_fwid": "0x00B7"
},
"zephyr": {
"variant": "nrf52_adafruit_feather"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52832_xxAA",
"svd_path": "nrf52.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino", "zephyr"],
"name": "Adafruit Bluefruit nRF52832 Feather",
"upload": {
"maximum_ram_size": 65536,
"maximum_size": 524288,
"require_upload_port": true,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"]
},
"url": "https://www.adafruit.com/product/3406",
"vendor": "Adafruit"
}
+1
View File
@@ -14,6 +14,7 @@ Build-Depends: debhelper-compat (= 13),
g++,
pkg-config,
libyaml-cpp-dev,
libjsoncpp-dev,
libgpiod-dev,
libbluetooth-dev,
libusb-1.0-0-dev,
+7
View File
@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x640000,
app1, app, ota_1, 0x650000,0x640000,
spiffs, data, spiffs, 0xc90000,0x360000,
coredump, data, coredump,0xFF0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x640000
5 app1 app ota_1 0x650000 0x640000
6 spiffs data spiffs 0xc90000 0x360000
7 coredump data coredump 0xFF0000 0x10000
+7
View File
@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x330000,
app1, app, ota_1, 0x340000,0x330000,
spiffs, data, spiffs, 0x670000,0x180000,
coredump, data, coredump,0x7F0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x330000
5 app1 app ota_1 0x340000 0x330000
6 spiffs data spiffs 0x670000 0x180000
7 coredump data coredump 0x7F0000 0x10000
+3 -27
View File
@@ -70,30 +70,6 @@ def esp32_create_combined_bin(source, target, env):
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
esp32_kind = env.GetProjectOption("custom_esp32_kind")
if esp32_kind == "esp32":
# Free up some IRAM by removing auxiliary SPI flash chip drivers.
# Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c.
env.Append(
LINKFLAGS=[
"-Wl,--wrap=esp_flash_chip_gd",
"-Wl,--wrap=esp_flash_chip_issi",
"-Wl,--wrap=esp_flash_chip_winbond",
]
)
else:
# For newer ESP32 targets, using newlib nano works better. Skip on
# variants that explicitly opt out — the IDF 5.1 framework override the
# HaLow variant uses already includes nano.specs, so re-adding it triggers
# a duplicate spec definition error at link time.
cppdefines = env.get("CPPDEFINES", [])
if not any(
(isinstance(d, str) and d == "MESHTASTIC_SKIP_NANO_SPECS")
or (
isinstance(d, (list, tuple))
and len(d) > 0
and d[0] == "MESHTASTIC_SKIP_NANO_SPECS"
)
for d in cppdefines
):
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
# Enable Newlib Nano formatting to save space
# ...but allow printf float support (compromise)
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
# force linker response file instead of command line arguments
Import("env")
def wrap_with_tempfile(command_key):
command = env.get(command_key)
if not command or not isinstance(command, str):
return
if "TEMPFILE(" in command:
return
env.Replace(**{command_key: "${TEMPFILE('%s')}" % command})
# Force SCons to spill long commands into response files on this target.
env.Replace(MAXLINELENGTH=8192)
for key in ("LINKCOM", "CXXLINKCOM", "SHLINKCOM", "SHCXXLINKCOM"):
wrap_with_tempfile(key)
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821)
#
# Whole-image LTO for nrf52840 (~-60KB; ~-23KB beyond src-only LTO), EXCEPT the objects
# that own interrupt/exception handlers.
#
# Every ISR is referenced only from the assembly vector table (gcc_startup_nrf52840.S),
# which LTO cannot see -> whole-program LTO judges the handlers dead, removes them, and
# the weak `b .` Default_Handler stubs prevail -> the IRQ lands in an infinite loop and the
# chip hangs (or the peripheral silently stalls). Compiling the handler-bearing objects
# WITHOUT LTO lets ordinary linking keep the strong handlers; everything else stays LTO'd:
# - framework core (/FrameworkArduino/, /cores/nRF5/): every nrfx ISR + the FreeRTOS
# SVC/PendSV port.
# - TinyUSB nrf port (Adafruit_TinyUSB_nrf.cpp): USBD_IRQHandler (USB data path).
# - library .cpp files that own a vector ISR (would otherwise be silently dropped):
# bluefruit.cpp -> SD_EVT/SWI2_EGU2 (SoftDevice BLE-event delivery -- advertising
# hangs without it)
# Wire_nRF52.cpp -> SPIM0/TWIM0 + SPIM1/TWIM1 (interrupt-driven I2C/SPI)
# PDM.cpp -> PDM_IRQHandler (PDM microphone)
# RotaryEncoder.cpp -> QDEC_IRQHandler (hardware quadrature/rotary encoder)
#
# A post-link guard (bottom of this file) fails the build if a critical handler was dropped
# anyway -- so a future deps bump or a new ISR-owning library becomes a red build, not a field
# hang. To hunt a dropped ISR by hand: nm the .elf for `_IRQHandler$` symbols marked `W`, then
# grep the libs/framework for who defines them.
#
# HW-validated: RAK4631 (SX1262) + muzi-base (LR1121).
import glob
import os
Import("env")
env.Append(LINKFLAGS=["-flto", "-flto-partition=1to1"])
# The -fno-lto re-compiles below run with the global env, which lacks the framework's
# bundled-library include dirs -- and those libs cross-include each other (Wire pulls in
# Adafruit_TinyUSB.h, which pulls in SPI.h, ...). Add every bundled-lib dir (+ its src/) so
# the re-compiles resolve without chasing headers one at a time.
_fw = env.PioPlatform().get_package_dir("framework-arduinoadafruitnrf52") or ""
_extra_inc = []
for _d in sorted(glob.glob(os.path.join(_fw, "libraries", "*"))):
if os.path.isdir(_d):
_extra_inc.append(_d)
if os.path.isdir(os.path.join(_d, "src")):
_extra_inc.append(os.path.join(_d, "src"))
FRAMEWORK = ("/FrameworkArduino/", "/cores/nRF5/")
USB_ISR = "Adafruit_TinyUSB_nrf" # USBD_IRQHandler
# Library .cpp files that define vector-table ISRs (the rest of their lib stays LTO'd):
LIB_ISR = ("/bluefruit.cpp", "/Wire_nRF52.cpp", "/PDM.cpp", "/RotaryEncoder.cpp")
def _no_lto(node):
try:
path = node.get_abspath()
except Exception:
path = str(node)
path = path.replace(
"\\", "/"
) # normalize Windows backslashes so matches work cross-platform
if (
USB_ISR in path
or any(s in path for s in FRAMEWORK)
or any(s in path for s in LIB_ISR)
):
return env.Object(
node,
CCFLAGS=env["CCFLAGS"] + ["-fno-lto"],
CPPPATH=env["CPPPATH"] + _extra_inc,
)
return node
env.AddBuildMiddleware(_no_lto)
# --- post-link guard: catch a dropped ISR handler at build time (CI footgun protection) ----
# After every link, fail the build if one of these critical vector-table handlers resolved to
# the weak `b .` Default_Handler stub -- i.e. LTO (or a deps bump, or a new ISR-owning library
# that nobody added to LIB_ISR) silently dropped it. A dropped handler hangs the chip the
# instant that IRQ fires; this turns a field hang into a red build. CI builds every nrf52840
# target, so this runs on every PR automatically. If a board deliberately stops using one of
# these, edit the tuples on purpose.
_REQUIRED_STRONG = (
"SWI2_EGU2_IRQHandler", # SoftDevice BLE event (SD_EVT) -- advertising & connections
"GPIOTE_IRQHandler", # GPIO interrupts: radio DIO + buttons
"RTC1_IRQHandler", # FreeRTOS scheduler tick
)
# Owned by the TinyUSB stack, so only required when the board builds with USB at all.
# Boards without native USB wiring (e.g. wio-sdk-wm1110's CH340 UART) strip TinyUSB via
# disable_adafruit_usb.py / unflagging USE_TINYUSB, leaving these legitimately weak.
_REQUIRED_STRONG_USB = (
"USBD_IRQHandler", # USB CDC (serial console + 1200bps DFU trigger)
"POWER_CLOCK_IRQHandler", # USB power events (VBUS detect/ready) via TinyUSB hal
)
_tc = env.PioPlatform().get_package_dir("toolchain-gccarmnoneeabi") or ""
_NM = os.path.join(_tc, "bin", "arm-none-eabi-nm")
if not os.path.isfile(_NM):
_NM = "arm-none-eabi-nm" # fall back to PATH
def _assert_isr_handlers_survived(source, target, env):
import subprocess
import sys
try:
# Resolve the ELF at build time; target[0] is the buildprog alias, not the file.
elf = env.subst("$BUILD_DIR/${PROGNAME}.elf")
out = subprocess.check_output([_NM, elf], universal_newlines=True)
except Exception as exc: # tooling hiccup: warn loudly, don't wedge the build
print("nrf52_lto: WARNING - ISR-handler guard skipped (nm failed: %s)" % exc)
return
# nm line: "<addr> <type> <symbol>". type 'T'/'t' = strong (good); 'W'/'w' = weak stub.
kind = {}
for line in out.split("\n"):
f = line.split()
if len(f) >= 3 and f[-1].endswith("_IRQHandler"):
kind[f[-1]] = f[-2]
required = list(_REQUIRED_STRONG)
defines = [
str(d[0] if isinstance(d, tuple) else d) for d in env.get("CPPDEFINES", [])
]
if "USE_TINYUSB" in defines:
required += _REQUIRED_STRONG_USB
dropped = [h for h in required if kind.get(h, "W").upper() != "T"]
if dropped:
sys.stderr.write(
"\n*** nrf52 LTO guard: interrupt handler(s) DROPPED: %s ***\n"
"Each resolved to the weak Default_Handler stub, so the chip hangs when that IRQ\n"
"fires. Compile the .cpp that defines the handler with -fno-lto by adding it to\n"
"LIB_ISR in extra_scripts/nrf52_lto.py. Find the owner of FOO_IRQHandler with:\n"
" grep -rl FOO_IRQHandler <framework-arduinoadafruitnrf52>/{libraries,cores}\n\n"
% ", ".join(dropped)
)
from SCons.Script import Exit
Exit(1) # canonical SCons build-abort -> red build
print(
"nrf52_lto: ISR-handler guard OK -- %d critical handlers strong" % len(required)
)
# Attach to the phony "buildprog" alias, NOT the .elf file node: SCons can skip a post-action
# on a file target during an incremental relink (observed), but the buildprog alias runs every
# build -- so the guard fires on local incremental rebuilds and clean CI builds alike.
env.AddPostAction("buildprog", _assert_isr_handlers_survived)
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
#
# post:extra_scripts/nrf54l15_linker.py
#
# Fix for Zephyr two-pass link on nRF54L15:
# platformio-build.py registers env.Depends("$PROG_PATH", final_ld_script) but
# the SCons dependency chain is broken (final_ld_script Command never runs).
# This script adds a PreAction on the final firmware binary that runs the gcc
# preprocessing command directly (extracted from build.ninja) to generate
# zephyr/linker.cmd before the link step.
#
# PlatformIO bundles an old Ninja that can't handle multi-output depslog rules,
# so we parse the COMMAND line from build.ninja and run just the gcc -E part,
# skipping the cmake_transform_depfile step (only needed for Ninja deps tracking).
import os
import re
import subprocess
Import("env")
if env.get("PIOENV") != "nrf54l15dk":
pass # Only for the nrf54l15dk environment
else:
def _extract_gcc_command(ninja_build):
"""Parse build.ninja to find the gcc -E command that generates linker.cmd.
The rule format depends on the host:
Windows (CMake's RunCMake wraps every command):
COMMAND = cmd.exe /C "cd /D DIR && arm-none-eabi-gcc.exe ... -o linker.cmd && cmake.exe -E cmake_transform_depfile ..."
POSIX (Linux/macOS — no wrapper):
COMMAND = cd DIR && arm-none-eabi-gcc ... -o linker.cmd && cmake -E cmake_transform_depfile ...
Returns (gcc_cmd_string, cwd_path) or raises RuntimeError.
"""
in_rule = False
with open(ninja_build, "r", encoding="utf-8", errors="replace") as f:
for line in f:
# Detect start of the linker.cmd custom command rule
if not in_rule:
if "build zephyr/linker.cmd" in line and "CUSTOM_COMMAND" in line:
in_rule = True
continue
stripped = line.strip()
if not stripped.startswith("COMMAND = "):
continue
command_val = stripped[len("COMMAND = ") :]
# On Windows the value is wrapped in `cmd.exe /C "..."` — strip
# the wrapper. On POSIX hosts the inner sequence is the value
# itself (no quoting layer).
m = re.search(r'/C\s+"(.*)"\s*$', command_val)
inner = m.group(1) if m else command_val
parts = inner.split(" && ")
cwd = None
gcc_cmd = None
for part in parts:
part = part.strip()
if part.startswith("cd /D "): # Windows form
cwd = part[len("cd /D ") :]
elif part.startswith("cd "): # POSIX form
cwd = part[len("cd ") :]
elif "arm-none-eabi-gcc" in part:
gcc_cmd = part
if not gcc_cmd:
raise RuntimeError(
"nRF54L15 linker fix: arm-none-eabi-gcc command not found in:\n%s"
% inner[:400]
)
return gcc_cmd, cwd
raise RuntimeError(
"nRF54L15 linker fix: 'build zephyr/linker.cmd' rule not found in build.ninja"
)
def _generate_linker_cmd(target, source, env):
"""Generate zephyr/linker.cmd via direct gcc invocation before the final link."""
build_dir = env.subst("$BUILD_DIR")
zephyr_dir = os.path.join(build_dir, "zephyr")
linker_cmd = os.path.join(zephyr_dir, "linker.cmd")
if os.path.exists(linker_cmd):
return # Already present — nothing to do
ninja_build = os.path.join(build_dir, "build.ninja")
if not os.path.exists(ninja_build):
raise RuntimeError(
"nRF54L15 linker fix: build.ninja not found at %s\n"
"Run a full build first so CMake generates the Ninja files."
% ninja_build
)
gcc_cmd, cwd = _extract_gcc_command(ninja_build)
run_cwd = cwd if cwd else zephyr_dir
print(
"==> nRF54L15: Generating zephyr/linker.cmd (LINKER_ZEPHYR_FINAL) via GCC"
)
# gcc_cmd comes verbatim from our own build.ninja (never user input) and
# contains Windows-style paths with spaces that cannot be safely argv-split
# with shlex, so we run it via the platform shell. nosec/nosemgrep below
# acknowledge this deliberate, scoped use of shell=True.
result = subprocess.run( # nosec B602
gcc_cmd,
shell=True, # nosemgrep: python.lang.security.audit.subprocess-shell-true.subprocess-shell-true
cwd=run_cwd,
capture_output=True,
text=True,
)
if result.returncode != 0:
print("GCC stdout:", result.stdout[:2000])
print("GCC stderr:", result.stderr[:2000])
raise RuntimeError(
"nRF54L15 linker fix: GCC failed to generate linker.cmd (rc=%d)"
% result.returncode
)
if not os.path.exists(linker_cmd):
raise RuntimeError(
"nRF54L15 linker fix: GCC returned 0 but linker.cmd was not created at %s"
% linker_cmd
)
print("==> linker.cmd generated successfully")
# Use PIOMAINPROG (set by ZephyrBuildProgram) to get the exact SCons node
prog = env.get("PIOMAINPROG")
if prog:
env.AddPreAction(prog, _generate_linker_cmd)
else:
print(
"[nrf54l15_linker] WARNING: PIOMAINPROG not set, falling back to $PROG_PATH"
)
env.AddPreAction(env.subst("$PROG_PATH"), _generate_linker_cmd)
-28
View File
@@ -1,28 +0,0 @@
"""
PlatformIO doesn't natively link .o files vendored inside a library directory.
The Morse Micro SDK ships the chip firmware (mm6108.mbin.o) and per-region BCF
(bcf_mf08651_us.mbin.o) as pre-built object files containing data sections.
This script appends them to LINKFLAGS so they land in the final ELF.
US region only for now — when we add EU/JP/KR variants, gate the BCF here on a
build flag and pick the matching .o file.
"""
import os
Import("env", "projenv")
LIB_DIR = os.path.join(env.subst("$PROJECT_DIR"), "lib", "MorseWlan")
mbin_objects = [
os.path.join(LIB_DIR, "src", "mm6108.mbin.o"),
os.path.join(LIB_DIR, "src", "bcf_mf08651_us.mbin.o"),
]
# Only add objects that actually exist; missing ones surface as link errors,
# not silent corruption.
for obj in mbin_objects:
if not os.path.isfile(obj):
print("warning: mm-iot-esp32 blob missing: %s" % obj)
env.Append(LINKFLAGS=mbin_objects)
-77
View File
@@ -1,77 +0,0 @@
/*
* Copyright 2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MBIN Morse BINary Loader API
*
* This file defines the structure of the @c MBIN file.
*
* @{
*/
#pragma once
#ifndef PACKED
/** Macro for the compiler packed attribute */
#define PACKED __attribute__((packed))
#endif
/** Enumeration of TLV field types */
enum mbin_tlv_types {
FIELD_TYPE_FW_TLV_BCF_ADDR = 0x0001,
FIELD_TYPE_MAGIC = 0x8000,
FIELD_TYPE_FW_SEGMENT = 0x8001,
FIELD_TYPE_FW_SEGMENT_DEFLATED = 0x8002,
FIELD_TYPE_BCF_BOARD_CONFIG = 0x8100,
FIELD_TYPE_BCF_REGDOM = 0x8101,
FIELD_TYPE_BCF_BOARD_DESC = 0x8102,
FIELD_TYPE_BCF_BUILD_VER = 0x8103,
FIELD_TYPE_SW_SEGMENT = 0x8201,
FIELD_TYPE_SW_SEGMENT_DEFLATED = 0x8202,
FIELD_TYPE_EOF = 0x8f00,
FIELD_TYPE_EOF_WITH_SIGNATURE = 0x8f01,
};
/** TLV header data structure. */
struct PACKED mbin_tlv_hdr {
/** Type (see mbin_tlv_types). */
uint16_t type;
/** Length of payload (excludes header). */
uint16_t len;
};
/** Data header in a FIELD_TYPE_XX_SEGMENT field. */
struct PACKED mbin_segment_hdr {
/** Destination base address at which the data should be loaded. */
uint32_t base_address;
};
/** Data header in a FIELD_TYPE_XX_SEGMENT_DEFLATED field. */
struct PACKED mbin_deflated_segment_hdr {
/** Destination base address at which the data should be loaded. */
uint32_t base_address;
/** Size of deflated data, infer size of compressed data from TLV length */
uint16_t chunk_size;
/** ZLib header */
uint8_t zlib_header[2];
};
/** Data header in a @c FIELD_TYPE_BCF_REGDOM field. */
struct PACKED mbin_regdom_hdr {
/** Country code that this @c regdom applies to. */
uint8_t country_code[2];
/** Reserved */
uint16_t reserved;
};
/** Expected value of the magic field for a SW image @c MMSW. */
#define MBIN_SW_MAGIC_NUMBER (0x57534d4d)
/** Expected value of the magic field for a firmware image @c MMFW. */
#define MBIN_FW_MAGIC_NUMBER (0x57464d4d)
/** Expected value of the magic field for a BCF @c MMBC. */
#define MBIN_BCF_MAGIC_NUMBER (0x43424d4d)
/** @} */
-124
View File
@@ -1,124 +0,0 @@
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* This file should be included from the application mbedtls_config.h file to ensure that
* the mbedTLS features necessary for morselib functionality are enabled.
*
* It is recommended to include this file at the _end_ of the application mbedtls_config.h file
* to avoid redefinition of macros.
*/
/* Cipher modes */
#ifndef MBEDTLS_CIPHER_MODE_CBC
#define MBEDTLS_CIPHER_MODE_CBC
#endif
#ifndef MBEDTLS_CIPHER_MODE_CTR
#define MBEDTLS_CIPHER_MODE_CTR
#endif
/* EC curves */
#ifndef MBEDTLS_ECP_DP_SECP256R1_ENABLED
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
#endif
#ifndef MBEDTLS_ECP_DP_SECP384R1_ENABLED
#define MBEDTLS_ECP_DP_SECP384R1_ENABLED
#endif
#ifndef MBEDTLS_ECP_DP_SECP521R1_ENABLED
#define MBEDTLS_ECP_DP_SECP521R1_ENABLED
#endif
/* Features */
#ifndef MBEDTLS_AES_C
#define MBEDTLS_AES_C
#endif
#ifndef MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_ASN1_PARSE_C
#endif
#ifndef MBEDTLS_ASN1_WRITE_C
#define MBEDTLS_ASN1_WRITE_C
#endif
#ifndef MBEDTLS_BIGNUM_C
#define MBEDTLS_BIGNUM_C
#endif
#ifndef MBEDTLS_CIPHER_C
#define MBEDTLS_CIPHER_C
#endif
#ifndef MBEDTLS_CMAC_C
#define MBEDTLS_CMAC_C
#endif
#ifndef MBEDTLS_CTR_DRBG_C
#define MBEDTLS_CTR_DRBG_C
#endif
#ifndef MBEDTLS_ECDH_C
#define MBEDTLS_ECDH_C
#endif
#ifndef MBEDTLS_ECP_C
#define MBEDTLS_ECP_C
#endif
#ifndef MBEDTLS_ENTROPY_C
#define MBEDTLS_ENTROPY_C
#endif
#ifndef MBEDTLS_MD_C
#define MBEDTLS_MD_C
#endif
#ifndef MBEDTLS_NIST_KW_C
#define MBEDTLS_NIST_KW_C
#endif
#ifndef MBEDTLS_OID_C
#define MBEDTLS_OID_C
#endif
#ifndef MBEDTLS_PK_C
#define MBEDTLS_PK_C
#endif
#ifndef MBEDTLS_PK_PARSE_C
#define MBEDTLS_PK_PARSE_C
#endif
#ifndef MBEDTLS_PK_WRITE_C
#define MBEDTLS_PK_WRITE_C
#endif
#ifndef MBEDTLS_PKCS5_C
#define MBEDTLS_PKCS5_C
#endif
#ifndef MBEDTLS_SHA1_C
#define MBEDTLS_SHA1_C
#endif
#ifndef MBEDTLS_SHA224_C
#define MBEDTLS_SHA224_C
#endif
#ifndef MBEDTLS_SHA256_C
#define MBEDTLS_SHA256_C
#endif
#ifndef MBEDTLS_SHA384_C
#define MBEDTLS_SHA384_C
#endif
#ifndef MBEDTLS_SHA512_C
#define MBEDTLS_SHA512_C
#endif
-331
View File
@@ -1,331 +0,0 @@
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMHAL Morse Micro Hardware Abstraction Layer (mmhal) API
*
* This API provides abstraction from the underlying hardware/BSP.
*
* @{
*/
#pragma once
#include "mmhal_flash.h"
#include "mmhal_wlan.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Initialization before RTOS scheduler starts. */
void mmhal_early_init(void);
/** Initialization after RTOS scheduler started. */
void mmhal_init(void);
/** Enumeration of ISR states (i.e., whether in ISR or not). */
enum mmhal_isr_state {
MMHAL_NOT_IN_ISR, /**< The function was not executed from ISR context. */
MMHAL_IN_ISR, /**< The function was executed from ISR context. */
MMHAL_ISR_STATE_UNKNOWN, /**< The HAL does not support checking ISR state. */
};
/**
* Enumeration for different LED's on the board.
*
* @note Some of these LED's may not be available on all boards and some of these values
* may refer to the same LED.
*/
enum mmhal_led_id { LED_RED, LED_GREEN, LED_BLUE, LED_WHITE };
/** Enumeration of MCU sleep state. */
enum mmhal_sleep_state {
MMHAL_SLEEP_DISABLED, /**< Disable MCU sleep. */
MMHAL_SLEEP_SHALLOW, /**< MCU to enter shallow sleep. */
MMHAL_SLEEP_DEEP, /**< MCU can enter deep sleep. */
};
/** A value of 0 turns OFF an LED */
#define LED_OFF 0
/**
* A value of 255 turns an LED ON fully.
*
* Some boards support varying an LED's brightness. For these boards a value between 1 and 255
* will result in proportionately varying levels of brightness. LED's that do not have a
* brightness control feature will just turn ON fully for any non zero value.
*/
#define LED_ON 255
/**
* Get the current ISR state (i.e., whether in ISR or not).
*
* @returns the current ISR state, or @c MMHAL_ISR_STATE_UNKNOWN if the HAL does not support
* checking ISR state.
*/
enum mmhal_isr_state mmhal_get_isr_state(void);
/**
* Write to the debug log.
*
* It is assumed the caller will have mechanisms in place to prevent concurrent access.
*
* @param data Buffer containing data to write.
* @param len Length of data in buffer.
*/
void mmhal_log_write(const uint8_t *data, size_t len);
/**
* Flush the debug log before returning.
*
* @warning Implementations of this function must support being invoked with interrupts disabled.
*/
void mmhal_log_flush(void);
/**
* Generate a random 32 bit integer within the given range.
*
* @param min Minimum value (inclusive).
* @param max Maximum value (inclusive).
*
* @returns a randomly generated integer (min <= i <= max).
*/
uint32_t mmhal_random_u32(uint32_t min, uint32_t max);
/** Reset the microcontroller. */
void mmhal_reset(void);
/**
* Set the specified LED to the requested level. Do nothing if the requested LED does not exist.
*
* @param led The LED to set, if the platform supports it. See @ref mmhal_led_id
* @param level The level to set it to. 0 means OFF and non-zero means ON. If the platform supports
* brightness levels then 255 is full. Defines @ref LED_ON and @ref LED_OFF are
* provided for ease of use.
*/
void mmhal_set_led(uint8_t led, uint8_t level);
/**
* Set the error LED to the requested state.
*
* @note This function is called by the bootloader and so needs to do all the initialization
* required to set the LED's as the bootloader does not use the regular BSP initialization
* located in main.c for configuring @c GPIO's and setting clock gates as required.
*
* @param state Set to true if the LED needs to be turned on,
* or false if the led needs to be turned off.
*/
void mmhal_set_error_led(bool state);
/**
* Enumeration for buttons on the board.
*
* @note The support for each button is platform dependent.
*/
enum mmhal_button_id { BUTTON_ID_USER0 };
/**
* Enumeration for button states
*/
enum mmhal_button_state { BUTTON_RELEASED, BUTTON_PRESSED };
/** Button state callback function prototype. */
typedef void (*mmhal_button_state_cb_t)(enum mmhal_button_id button_id, enum mmhal_button_state button_state);
/**
* Registers a callback handler for button state changes.
*
* @note The callback will be executed in an Interrupt Service Routine context
*
* @param button_id The button whose state should be reported to the callback
* @param button_state_cb The function to call on button state change, or NULL to disable.
* @returns True if the callback is registered successfully, False if not supported
*/
bool mmhal_set_button_callback(enum mmhal_button_id button_id, mmhal_button_state_cb_t button_state_cb);
/**
* Returns the registered callback handler for button state changes.
*
* @param button_id The button whose callback should be returned
* @returns The registered callback or NULL if no callback registered
*/
mmhal_button_state_cb_t mmhal_get_button_callback(enum mmhal_button_id button_id);
/**
* Reads the state of the specified button.
*
* @param button_id The button state to read
* @returns The current button state, or BUTTON_RELEASED if not supported
*/
enum mmhal_button_state mmhal_get_button(enum mmhal_button_id button_id);
/**
* Reads information that can be used to identify the hardware platform, such as
* hardware ID and version number, in the form of a user readable string.
*
* This function attempts to detect the correct hardware and version.
* The actual means of detecting the correct hardware and version will vary from
* implementation to implementation and may use means such as identification
* information stored in EEPROM or devices detected on GPIO, SPI or I2C interfaces.
* Returns false if the hardware could not be identified correctly.
*
* @param version_buffer The pre-allocated buffer to return the hardware ID and version in.
* @param version_buffer_length The length of the pre-allocated buffer.
* @returns True if the hardware was correctly identified and returned.
*/
bool mmhal_get_hardware_version(char *version_buffer, size_t version_buffer_length);
/**
* Macro to simplify debug pin mask/value definition.
*
* @param _pin_num The pin number to set in the mask. Must be 0-31 (inclusive).
*
* Example:
*
* mmhal_set_debug_pins(MMHAL_DEBUG_PIN(0), MMHAL_DEBUG_PIN(0));
*/
#define MMHAL_DEBUG_PIN(_pin_num) (1ul << (_pin_num))
/** Bit mask with all debug pins selected. */
#define MMHAL_ALL_DEBUG_PINS (UINT32_MAX)
/**
* Set the value one or more debug pins.
*
* Each platform can define up to 32 GPIOs for application use. If a GPIO is not supported
* by a platform then attempts to set it will be silently ignored. These GPIOs are intended
* for debug/test purposes.
*
* @param mask Mask of GPIOs to modify. Each bit in this mask that is set will result in
* the corresponding GPIO being being set to the corresponding value given in
* @p values.
* @param values Bit field, where each bit corresponds to a GPIO, specifying the direction
* to drive each GPIO with 1 meaning drive high and 0 meaning drive low.
* Only GPIOs with the corresponding bit set in @p mask will be modified.
*
* @sa MM_DEBUG_PIN_MASK
*/
void mmhal_set_debug_pins(uint32_t mask, uint32_t values);
/**
* Returns the time of day as set in the RTC.
* Time is in UTC.
*
* @return Epoch time (seconds since 1 Jan 1970) or 0 on failure.
*/
time_t mmhal_get_time();
/**
* Sets the RTC to the specified time in UTC.
*
* @note While Unix epoch time supports years from 1970, most Real Time Clock
* chips support years from 2000 only as they store the year as years
* from 2000. So do not attempt to set any years below 2000 as this could cause
* the year to wrap around to an unreasonably high value. Definitely do not do:
* @code
* mmhal_set_time(0);
* @endcode
*
* @param epoch Time in Unix epoch time (seconds since 1 Jan 1970).
*/
void mmhal_set_time(time_t epoch);
/**
* Function to prepare MCU to enter sleep.
* This will halt the timer that generates the RTOS tick.
*
* @param expected_idle_time_ms Expected time to sleep in milliseconds.
*
* @returns the type of sleep permitted by the current system state.
*/
enum mmhal_sleep_state mmhal_sleep_prepare(uint32_t expected_idle_time_ms);
/**
* Function to enter MCU sleep.
*
* @param sleep_state Sleep state to enter into.
* @param expected_idle_time_ms Expected time to sleep in milliseconds.
*
* @returns Elapsed sleep time in milliseconds.
*/
uint32_t mmhal_sleep(enum mmhal_sleep_state sleep_state, uint32_t expected_idle_time_ms);
/**
* Function to abort the MCU sleep state.
*
* @note This must only be invoked after @ref mmhal_sleep_prepare()
* and before @ref mmhal_sleep().
*
* @param sleep_state Sleep state to abort.
*/
void mmhal_sleep_abort(enum mmhal_sleep_state sleep_state);
/**
* Function to cleanup on exit from the MCU sleep state.
*/
void mmhal_sleep_cleanup(void);
/** Enumeration of veto_id ranges for use with @ref mmhal_set_deep_sleep_veto() and
* @ref mmhal_clear_deep_sleep_veto(). */
enum mmhal_veto_id {
/** Start of deep sleep veto ID range that is available for application use. */
MMHAL_VETO_ID_APP_MIN = 0,
/** End of deep sleep veto ID range that is available for application use. */
MMHAL_VETO_ID_APP_MAX = 7,
/** Start of deep sleep veto ID range that is available for HAL use. */
MMHAL_VETO_ID_HAL_MIN = 8,
/** End of deep sleep veto ID range that is available for HAL use. */
MMHAL_VETO_ID_HAL_MAX = 15,
/** Start of deep sleep veto ID range that is allocated for morselib use. Note that this
* must not be changed as it is built into morselib. */
MMHAL_VETO_ID_MORSELIB_MIN = 16,
/** End of deep sleep veto ID range that is allocated for morselib use. Note that this must not
* be changed as it is built into morselib. */
MMHAL_VETO_ID_MORSELIB_MAX = 19,
/** Deep sleep veto ID for data-link subsystem. */
MMHAL_VETO_ID_DATALINK = 20,
/** Deep sleep veto ID allocated to @ref MMCONFIG. */
MMHAL_VETO_ID_MMCONFIG = 21,
/** Start of deep sleep veto ID range reserved for future use. */
MMHAL_VETO_ID_RESERVED_MIN = 22,
/** End of deep sleep veto ID range reserved for future use. */
MMHAL_VETO_ID_RESERVED_MAX = 31,
};
/**
* Sets a deep sleep veto that will prevent the device from entering deep sleep. The device
* will not enter deep sleep until there are no vetoes remaining. This veto can be cleared
* by a call to @ref mmhal_clear_deep_sleep_veto() with the same veto_id.
*
* Up to 32 vetoes are supported (ID numbers 0-31). Each veto should be used exclusively by
* a given aspect of the system (e.g., to prevent deep sleep when a DMA transfer is in progress,
* or to prevent deep sleep when there is log data buffered for transmit, etc.).
*
* @param veto_id The veto identifier. Valid values are 0-31, and these are split up into ranges
* for use by different subsystems -- see @ref mmhal_veto_id.
*/
void mmhal_set_deep_sleep_veto(uint8_t veto_id);
/**
* Clears a deep sleep veto that was preventing the device from entering deep sleep (see
* @ref mmhal_set_deep_sleep_veto()). If the given veto was not already set then this has
* no effect.
*
* @param veto_id The veto identifier. Valid values are 0-31, and these are split up into ranges
* for use by different subsystems -- see @ref mmhal_veto_id.
*/
void mmhal_clear_deep_sleep_veto(uint8_t veto_id);
#ifdef __cplusplus
}
#endif
/** @} */
-145
View File
@@ -1,145 +0,0 @@
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @ingroup MMHAL Morse Micro Flash Hardware Abstraction Layer (mmhal_flash) API
*
* This API provides abstraction from the underlying flash hardware/.
*
* @{
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* This is the value erased flash bytes are set to. This shall be @c 0xFF as this is the
* value that hardware flash erases to.
*/
#define MMHAL_FLASH_ERASE_VALUE 0xFF
/** LittleFS configuration structure. Include @c lfs.h for definition. */
struct lfs_config;
/**
* Flash partition configuration structure
*
* This should be initialized using @c MMHAL_FLASH_PARTITION_CONFIG_DEFAULT.
* For example:
*
* @code{.c}
* struct mmhal_flash_partition_config partition = MMHAL_FLASH_PARTITION_CONFIG_DEFAULT;
* @endcode
*/
struct mmhal_flash_partition_config {
/** The start address of the partition, this may be a physical address
* or a relative address depending on implementation
*/
uint32_t partition_start;
/** The size of the partition */
uint32_t partition_size;
/**
* If true, then the partition is not memory mapped and cannot be directly accessed
* at the physical @c partition_start address
*/
bool not_memory_mapped;
};
/** Initial values for @ref mmhal_flash_partition_config. */
#define MMHAL_FLASH_PARTITION_CONFIG_DEFAULT \
{ \
0, 0, false \
}
/**
* Get MMCONFIG flash partition configuration.
*
* MMCONFIG initialization is done by @c mmconfig_init() in @c mmconfig.c.
* which in turn calls this function to fetch the partition configuration for config store
* from the HAL layer. If config store is not supported by the platform then we just
* return NULL. This function returns a static pointer to
* @c struct @c mmhal_flash_partition_config.
*
* @return A static pointer to the partition config for MMCONFIG, or NULL if not supported.
*/
const struct mmhal_flash_partition_config *mmhal_get_mmconfig_partition(void);
/**
* Erases a block of Flash pointed to by the block_address.
*
* The block address may be anywhere within the block to erase. The entire block gets erased.
* Once erased all bytes in the block shall be @c MMHAL_FLASH_ERASE_VALUE (@c 0xFF).
*
* @param block_address The address of the block of Flash to erase.
* @return 0 on success, negative number on failure
*/
int mmhal_flash_erase(uint32_t block_address);
/**
* Returns the size of the Flash block at the specified address.
*
* @param block_address The address of the Flash block.
* @return The size of the Flash block in bytes.
* Returns 0 if an invalid address is specified.
*/
uint32_t mmhal_flash_getblocksize(uint32_t block_address);
/**
* Read a block of data from the specified Flash address into the buffer.
*
* @param read_address The address in Flash to read from.
* @param buf The buffer to read into.
* @param size The number of bytes to read.
* @return 0 on success, or a negative number on failure.
*/
int mmhal_flash_read(uint32_t read_address, uint8_t *buf, size_t size);
/**
* Write a block of data to the specified Flash address.
*
* There is no alignment or minimum size requirement. This function will
* take care of aligning the data and merging with existing Flash contents.
* The Flash block is not erased, it is up to the application to determine
* if the block needs to be erased before programming.
*
* @param write_address The address in Flash to write to.
* @param data A pointer to the block of data to write.
* @param size The number of bytes to write.
* @return 0 on success, or a negative number on failure.
*/
int mmhal_flash_write(uint32_t write_address, const uint8_t *data, size_t size);
/**
* Get LittleFS configuration.
*
* LittleFS initialization is done by @c littlefs_init() in @c mmosal_shim_fileio.c.
* which in turn calls this function to fetch the hardware configuration for LittleFS
* from the HAL layer. The LittleFS configuration will vary from platform to platform.
* If LittleFS is not supported by the platform then we just return NULL. This function
* returns a static pointer to @c struct @c lfs_config which is defined in @c lfs.h.
*
* See @c mmhal_littlefs.c for the full HAL layer implementation for your platform.
* See @c mmosal_shim_fileio.c for the @c libc shims for LittleFS.
* See @c README.md in the @c src/littlefs folder for detailed information on LittleFS.
*
* @return A static pointer to the LittleFS config structure, or NULL if not supported.
*/
const struct lfs_config *mmhal_get_littlefs_config(void);
#ifdef __cplusplus
}
#endif
/** @} */
-89
View File
@@ -1,89 +0,0 @@
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @ingroup MMHAL
* @defgroup MMHAL_UART Morse Micro Abstraction Layer API for UART
*
* This provides an abstraction layer for a UART. This is used by MM-IoT-SDK example
* applications.
*
* This is a very simple API and leaves UART configuration to the HAL.
*
* @{
*/
#pragma once
#include "mmhal.h"
#include "mmosal.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Function type for UART RX callback.
*
* @note The UART HAL must not invoke this function from interrupt context. However, the
* implementation should not block for long periods of time or received data may be lost.
*
* @param data The received data.
* @param length Length of the received data.
* @param arg Opaque argument (as passed in to @ref mmhal_uart_init()).
*/
typedef void (*mmhal_uart_rx_cb_t)(const uint8_t *data, size_t length, void *arg);
/**
* Initialize the UART HAL and perform any setup necessary.
*
* @param rx_cb Optional callback to be invoked on receive (may be NULL).
* This callback may be invoked from interrupt context so should return
* quickly.
* @param rx_cb_arg Optional opaque argument to be passed to the RX callback. May be NULL.
*/
void mmhal_uart_init(mmhal_uart_rx_cb_t rx_cb, void *rx_cb_arg);
/**
* Deinitialize the UART HAL, and disable the UART.
*/
void mmhal_uart_deinit(void);
/**
* Transmit data on the UART. This will block until all data is buffered for transmit (but may
* return before transmission has completed).
*
* @param data Data to transmit.
* @param length Length of @p data.
*/
void mmhal_uart_tx(const uint8_t *data, size_t length);
/** Enumeration of deep sleep modes for the UART HAL. */
enum mmhal_uart_deep_sleep_mode {
/** Deep sleep mode is disabled. */
MMHAL_UART_DEEP_SLEEP_DISABLED,
/** Enable deep sleep until activity occurs on data-link transport. */
MMHAL_UART_DEEP_SLEEP_ONE_SHOT,
};
/**
* Set the deep sleep mode for the UART. See @ref mmhal_uart_deep_sleep_mode for possible deep
* sleep modes. Note that a given platform may not support all modes.
*
* @param mode The deep sleep mode to set.
*
* @returns true if the mode was set successfully; false on failure (e.g., unsupported mode).
*/
bool mmhal_uart_set_deep_sleep_mode(enum mmhal_uart_deep_sleep_mode mode);
#ifdef __cplusplus
}
#endif
/** @} */
-640
View File
@@ -1,640 +0,0 @@
/*
* Copyright 2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @ingroup MMHAL
* @defgroup MMHAL_WLAN WLAN HAL
*
* API for communicating with the WLAN transceiver.
*
* There are different interfaces supported for communicating with the transceiver:
*
* * @ref MMHAL_WLAN_SDIO
* * @ref MMHAL_WLAN_SPI
*
* @warning These functions shall not be called directly by the end application they are for use
* by Morselib.
*
* @{
*/
#pragma once
#include "mmpkt.h"
#include "mmwlan.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Function prototype for interrupt handler callbacks.
*/
typedef void (*mmhal_irq_handler_t)(void);
/**
* Initialize the WLAN HAL.
*
* Things to do here may include:
* * Enable SPI peripheral
* * Configure GPIOs
* * Enable power to the Morse Micro transceiver
*
* @note If enabling power for the Morse Micro transceiver in this function you may need to add a
* blocking delay to allow the power rail to stabilize. This is hardware specific so is not
* accounted for in the calling function.
*/
void mmhal_wlan_init(void);
/**
* Deinitialize the WLAN HAL.
*
* Things to do here may include:
* * Disable SPI peripheral
* * Disable GPIOs
* * Disable power to the Morse Micro transceiver
*/
void mmhal_wlan_deinit(void);
/**
* Get MAC address override.
*
* This function allows the HAL to override the MAC address to be used by the device. The
* MAC address override should be written to @p mac_addr. If no override is required then
* @p mac_addr should be left untouched.
*
* @param[out] mac_addr Location where the MAC address will be stored. This will be initialized
* to zero the first time this function is invoked, and to the previously
* configured MAC address on subsequent invocations.
*/
void mmhal_read_mac_addr(uint8_t *mac_addr);
/**
* Assert the WLAN wake pin.
*/
void mmhal_wlan_wake_assert(void);
/**
* Deassert the WLAN wake pin.
*/
void mmhal_wlan_wake_deassert(void);
/**
* Tests whether the busy pin is currently asserted.
*
* @note This is whether it is logically asserted and does not necessarily
* represent the level of the GPIO pin.
*
* @returns @c true if asserted, else @c false.
*/
bool mmhal_wlan_busy_is_asserted(void);
/**
* Register a handler for busy interrupts.
*
* @param handler The handler to register.
*/
void mmhal_wlan_register_busy_irq_handler(mmhal_irq_handler_t handler);
/**
* Sets whether the busy interrupt is enabled.
*
* @warning The interrupt handler function must be configured using
* mmhal_wlan_register_busy_irq_handler() before enabling the interrupt.
*
* @param enabled @c true to enable or @c false to disable.
*/
void mmhal_wlan_set_busy_irq_enabled(bool enabled);
/**
* Read-only buffer data structure.
*
* The design of this data structure allows the buffer to exist either in statically or
* dynamically allocated memory.
*
* For statically allocated memory, the field @c free_cb may be set to @c NULL and @c free_arg
* ignored. For example:
*
* @code{.c}
* const uint8_t some_data[] = { 0x00, 0x01, 0x02 };
*
* void put_some_data_into_buf(struct mmhal_robuf *robuf)
* {
* robuf->buf = some_data;
* robuf->len = sizeof(some_data);
* robuf->free_cb = NULL;
* }
* @endcode
*
* For dynamically allocated memory, the field @c free_cb is set to the appropriate function to
* free the buffer and @c free_arg is an opaque argument to the free function. This approach might
* be used, for example, when reading into a temporary buffer from storage that is not memory
* mapped. For example:
*
* @code{.c}
* #define SOME_DATA_MAXLEN (64)
*
* void put_some_data_into_buf(struct mmhal_robuf *robuf)
* {
* uint8_t *buf = malloc(SOME_DATA_MAXLEN);
* robuf->buf = buf;
* if (robuf->buf == NULL)
* return;
*
* // HERE: copy data into buf and set robuf->len as appropriate
*
* robuf->free_cb = free;
* robuf->free_arg = buf;
* }
* @endcode
*
*/
struct mmhal_robuf {
/** Pointer to the start of the read-only buffer. May be NULL only if @c len is zero. */
const uint8_t *buf;
/** Length of the buffer contents. */
uint32_t len;
/**
* Optional callback to be invoked by the consumer to release the buffer when it is
* no longer required. If not required, set to @c NULL.
*
* @note The values of @c buf and @c len in this structure may be modified before
* @c free_cb() is invoked. However, the value of @c free_arg will be passed
* to @c free_cb().
*/
void (*free_cb)(void *arg);
/** Optional argument to @c free_cb. Ignored if @c free_cb is @c NULL. */
void *free_arg;
};
/** Minimum length of data to be returned by @ref mmhal_wlan_read_bcf_file() and
* @ref mmhal_wlan_read_fw_file(). */
#define MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH (4)
/**
* Retrieves the content of the Morse Micro Board Configuration File and places it into the given
* buffer.
*
* @param offset Offset from which to start reading the bcf
* @param requested_len Length of data we would like to read. The length of the data returned
* by this function may be less than @p requested_len, but must be at
* least @ref MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH.
* @param robuf Read-only buffer data structure to be filled out by this function.
*
* @note On error, this function should set @c robuf->buf to @c NULL.
* @note The caller must zero @p robuf before invoking the function.
* @note The BCF must be in mbin format.
*
* @warning The caller is responsible for checking @c robuf->free_cb and calling when the buffer is
* no longer required. Ignored if @c robuf->free_cb is @c NULL.
*/
void mmhal_wlan_read_bcf_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf);
/**
* Retrieves the content of the Morse Micro Chip Firmware and places it into the given buffer.
*
* @param offset Offset from which to start reading the bcf
* @param requested_len Length of data we would like to read. The length of the data returned
* by this function may be less than @p requested_len, but must be at
* least @ref MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH.
* @param robuf Read-only buffer data structure to be filled out by this function.
*
* @note On error, this function should set @c robuf->buf to @c NULL.
* @note The caller must zero @p robuf before invoking the function.
* @note The firmware must be in mbin format.
*
* @warning The caller is responsible for checking @c robuf->free_cb and calling when the buffer
* is no longer required. Ignored if @c robuf->free_cb is @c NULL.
*/
void mmhal_wlan_read_fw_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf);
/**
* @defgroup MMHAL_WLAN_SPI WLAN HAL API for SPI interface
*
* API for communicating with the WLAN transceiver over an SPI interface.
*
* @note These functions should only be implemented if using a SPI interface. They are not
* required when using the @ref MMHAL_WLAN_SDIO.
*
* @warning These functions shall not be called directly by the end application they are for use
* by Morselib.
*
* @{
*/
/**
* Assert the WLAN SPI chip select pin.
*/
void mmhal_wlan_spi_cs_assert(void);
/**
* Deassert the WLAN SPI chip select pin.
*/
void mmhal_wlan_spi_cs_deassert(void);
/**
* Simultaneously read and write on the SPI bus.
*
* @param data Data to be written.
*
* @return the value that was read.
*/
uint8_t mmhal_wlan_spi_rw(uint8_t data);
/**
* Receive multiple octets of data from SPI bus.
*
* @param buf The buffer to receive into.
* @param len The number of octets to receive.
*/
void mmhal_wlan_spi_read_buf(uint8_t *buf, unsigned len);
/**
* Transmit multiple octets of data to SPI bus.
*
* @param buf The buffer to transmit from.
* @param len The number of octets to transmit.
*
* @note Blocks until transfer complete.
*/
void mmhal_wlan_spi_write_buf(const uint8_t *buf, unsigned len);
/**
* Hard reset the chip by asserting and then releasing the reset pin.
*
* @warning This function must return with the chip in a fully booted state. i.e only return once
* the reset_n line has been high for at least the boot time specified in the data sheet.
* Failure to do so may lead to undefined behavior.
*/
void mmhal_wlan_hard_reset(void);
/**
* Invoked by the driver to check whether the external crystal initialization sequence is required.
*
* Implementation of this function is optional if the external crystal initialization sequence
* is not required. If this function is not implemented then the external crystal initialization
* sequence will be disabled. Refer to the data sheet for your module to check if this initialization
* is required.
*
* @returns true if the external crystal initialization sequence is required else false.
*/
bool mmhal_wlan_ext_xtal_init_is_required(void);
/**
* Issue the training sequence required to put the transceiver into SPI mode.
*/
void mmhal_wlan_send_training_seq(void);
/**
* Register a handler for SPI interrupts.
*
* @param handler The handler to register.
*/
void mmhal_wlan_register_spi_irq_handler(mmhal_irq_handler_t handler);
/**
* Sets whether the SPI interrupt is enabled.
*
* @warning The interrupt handler function must be configured using
* @ref mmhal_wlan_register_spi_irq_handler() before enabling the interrupt.
*
* @param enabled @c true to enable or @c false to disable.
*/
void mmhal_wlan_set_spi_irq_enabled(bool enabled);
/**
* Tests whether the SPI interrupt pin is currently asserted.
*
* @note This is whether it is logically asserted and does not necessarily
* represent the level of the GPIO pin.
*
* @returns @c true if asserted, else @c false.
*/
bool mmhal_wlan_spi_irq_is_asserted(void);
/**
* Clear the SPI IRQ.
*
* @deprecated Do not invoke this function because it is deprecated and will be removed from the
* mmhal API in a future release. This function need not be implemented as a weak
* stub is used in morselib.
*/
void mmhal_wlan_clear_spi_irq(void);
/** @} */
/**
* @defgroup MMHAL_WLAN_PKT WLAN HAL API for packet memory allocation
*
* API for allocating and freeing packet memory.
*
* @warning These functions shall not be called directly by the end application they are for use
* by Morselib.
*
* @{
*/
/**
* Flow control callback that can be invoked by the transmit packet memory manager to pause
* and resume the data path in response to resource availability.
*
* @param state Current flow control state.
*/
typedef void (*mmhal_wlan_pktmem_tx_flow_control_cb_t)(enum mmwlan_tx_flow_control_state state);
/** Initialization arguments passed to @ref mmhal_wlan_pktmem_init(). */
struct mmhal_wlan_pktmem_init_args {
/** Flow control callback that can be used by the transmit packet memory manager. */
mmhal_wlan_pktmem_tx_flow_control_cb_t tx_flow_control_cb;
};
/**
* Invoked by the driver to initialize the packet memory in the HAL.
*
* @param args Initialization arguments.
*/
void mmhal_wlan_pktmem_init(struct mmhal_wlan_pktmem_init_args *args);
/**
* Invoked by the driver to deinitialize the packet memory in the HAL.
*
* This can free reserved memory and check for memory leaks.
*/
void mmhal_wlan_pktmem_deinit(void);
/**
* Enumeration of packet classes used by @ref mmhal_wlan_alloc_mmpkt_for_tx().
* These definitions must match the corresponding values in @c mmdrv_pkt_class.
*/
enum mmhal_wlan_pkt_class {
MMHAL_WLAN_PKT_DATA_TID0, /**< Data TID0 */
MMHAL_WLAN_PKT_DATA_TID1, /**< Data TID1 */
MMHAL_WLAN_PKT_DATA_TID2, /**< Data TID2 */
MMHAL_WLAN_PKT_DATA_TID3, /**< Data TID3 */
MMHAL_WLAN_PKT_DATA_TID4, /**< Data TID4 */
MMHAL_WLAN_PKT_DATA_TID5, /**< Data TID5 */
MMHAL_WLAN_PKT_DATA_TID6, /**< Data TID6 */
MMHAL_WLAN_PKT_DATA_TID7, /**< Data TID7 */
MMHAL_WLAN_PKT_MANAGEMENT, /**< 802.11 Management and other important frames */
MMHAL_WLAN_PKT_COMMAND, /**< Commands from driver to chip */
};
/**
* Allocates an mmpkt for transmission.
*
* When the pool of mmpkt buffers available for TX is exhausted, the HAL should pause the TX
* path using the flow control callback that was registered when @ref mmhal_wlan_pktmem_init()
* was invoked. Similarly, when the buffers become available again (and assuming the TX path is
* not otherwise blocked) the driver should unpause the TX path.
*
* @param pkt_class The class of packet (to allow for prioritization).
* @param space_at_start Amount of space to allocate at start of mmpkt (for prepend).
* @param space_at_end Amount of space to allocate at end of mmpkt (for append).
* @param metadata_length Amount of space to allocate for metadata (used internally by the
* Morse driver).
*
* @returns a pointer to the allocated packet on success or @c NULL on allocation failure.
*/
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_tx(uint8_t pkt_class, uint32_t space_at_start, uint32_t space_at_end,
uint32_t metadata_length);
/**
* Allocates an mmpkt for reception.
*
* @param capacity Amount of space to allocate for data.
* @param metadata_length Amount of space to allocate for metadata (used internally by the
* Morse driver).
*
* @returns a pointer to the allocated packet on success or @c NULL on allocation failure.
*/
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_rx(uint32_t capacity, uint32_t metadata_length);
/** @} */
/**
* @defgroup MMHAL_WLAN_SDIO WLAN HAL API for SDIO interface
*
* API for communicating with the WLAN transceiver over an SDIO interface
*
* @warning These functions shall not be called directly by the end application they are for use
* by Morselib.
*
* @{
*/
/** Enumeration of error codes that may be returned from @c mmhal_wlan_sdio_XXX() functions. */
enum mmhal_sdio_error_codes {
/** Invalid argument given (e.g., incorrect buffer alignment). */
MMHAL_SDIO_INVALID_ARGUMENT = -1,
/** Local hardware error (e.g., issue with SDIO controller). */
MMHAL_SDIO_HW_ERROR = -2,
/** Timeout executing SDIO command. */
MMHAL_SDIO_CMD_TIMEOUT = -3,
/** CRC error executing SDIO command. */
MMHAL_SDIO_CMD_CRC_ERROR = -4,
/** Timeout transferring data. */
MMHAL_SDIO_DATA_TIMEOUT = -5,
/** CRC error transferring data. */
MMHAL_SDIO_DATA_CRC_ERROR = -6,
/** Underflow filling SDIO controller FIFO. */
MMHAL_SDIO_DATA_UNDERFLOW = -7,
/** Overflow reading from SDIO controller FIFO. */
MMHAL_SDIO_DATA_OVERRUN = -8,
/** Another error not covered by the above error codes. */
MMHAL_SDIO_OTHER_ERROR = -9,
};
/**
* Perform transport specific startup.
*
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
*/
int mmhal_wlan_sdio_startup(void);
/**
* Execute an SDIO command without data.
*
* @param[in] cmd_idx The Command Index.
* @param[in] arg Command argument. This corresponds to the 32 bits of the command between
* the Command Index field and the CRC7 field.
* @param[out] rsp The contents of the command response between the Command Index field
* and the CRC7 field. May be @c NULL if the response is not required.
* The returned value is undefined if the return code is not zero.
*
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
*/
int mmhal_wlan_sdio_cmd(uint8_t cmd_idx, uint32_t arg, uint32_t *rsp);
/**
* Arguments structure for @ref mmhal_wlan_sdio_cmd53_write().
*/
struct mmhal_wlan_sdio_cmd53_write_args {
/** The SDIO argument. This corresponds to the 32 bits of the command between
* the Command Index field and the CRC7 field. */
uint32_t sdio_arg;
/** Pointer to the data buffer. 32 bit word aligned. */
const uint8_t *data;
/** Transfer length measured in blocks if block_size is non-zero otherwise in bytes.
* If transfer_length is measured in bytes, it will be a multiple of 4. */
uint16_t transfer_length;
/**
* If non-zero this indicates that the data should be transferred in block mode with
* the given block size. If zero then the data should be transferred in byte mode and
* @c transfer_length is guaranteed to not exceed the block size of the function.
*/
uint16_t block_size;
};
/**
* Execute an SDIO CMD53 write.
*
* @param args The write arguments.
*
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
*/
int mmhal_wlan_sdio_cmd53_write(const struct mmhal_wlan_sdio_cmd53_write_args *args);
/**
* Arguments structure for @ref mmhal_wlan_sdio_cmd53_read().
*/
struct mmhal_wlan_sdio_cmd53_read_args {
/** The SDIO argument. This corresponds to the 32 bits of the command between
* the Command Index field and the CRC7 field. */
uint32_t sdio_arg;
/** Pointer to the data buffer to receive the data. 32 bit word aligned. */
uint8_t *data;
/** Transfer length measured in blocks if block_size is non-zero otherwise in bytes.
* If transfer_length is measured in bytes, it will be a multiple of 4. */
uint16_t transfer_length;
/**
* If non-zero this indicates that the data should be transferred in block mode with
* the given block size. If zero then the data should be transferred in byte mode and
* @c transfer_length is guaranteed to not exceed the block size of the function.
*/
uint16_t block_size;
};
/**
* Execute an SDIO CMD53 read.
*
* @param args The read arguments.
*
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
*/
int mmhal_wlan_sdio_cmd53_read(const struct mmhal_wlan_sdio_cmd53_read_args *args);
/**
* @defgroup MMHAL_WLAN_SDIO_UTILS SDIO Utilities
*
* Useful macros and inline utilities function for use by SDIO and SPI HALs.
*
* @{
*/
/*
* SDIO argument definition, per SDIO Specification Version 4.10, Part E1, Section 5.3.
*/
/** SDIO CMD52/CMD53 R/W flag. */
enum mmhal_sdio_rw {
MMHAL_SDIO_READ = 0, /**< Read operation */
MMHAL_SDIO_WRITE = (1ul << 31), /**< Write operation */
};
/** SDIO CMD52/CMD53 function number. */
enum mmhal_sdio_function {
MMHAL_SDIO_FUNCTION_0 = 0, /** Function 0 */
MMHAL_SDIO_FUNCTION_1 = (1ul << 28), /** Function 1 */
MMHAL_SDIO_FUNCTION_2 = (2ul << 28), /** Function 2 */
};
/** SDIO CMD53 block mode*/
enum mmhal_sdio_mode {
MMHAL_SDIO_MODE_BYTE = 0, /** Byte mode */
MMHAL_SDIO_MODE_BLOCK = (1ul << 27), /** Block mode */
};
/** SDIO CMD53 OP code */
enum mmhal_sdio_opcode {
/** Operate on a single, fixed address. */
MMHAL_SDIO_OPCODE_FIXED_ADDR = 0,
/** Increment address by 1 after each byte. */
MMHAL_SDIO_OPCODE_INC_ADDR = (1ul << 26),
};
/** CMD52/53 Register Address (17 bit) offset. */
#define MMHAL_SDIO_ADDRESS_OFFSET (9)
/** CMD52/53 Register Address maximum value. */
#define MMHAL_SDIO_ADDRESS_MAX ((1ul << 18) - 1)
/** CMD53 Byte/block count offset (9 bit). */
#define MMHAL_SDIO_COUNT_OFFSET (0)
/**CMD53 Byte/block count maximum value. */
#define MMHAL_SDIO_COUNT_MAX ((1ul << 10) - 1)
/** CMD52 Data (8 bit) offset */
#define MMHAL_SDIO_CMD52_DATA_OFFSET (0)
/**
* Construct an SDIO CMD52 argument based on the given arguments.
*
* @param rw Flag indication direction (read or write).
* @param fn The applicable function.
* @param address The address to read/write. Must be <= @c MMHAL_SDIO_ADDRESS_MAX.
* @param write_data The data to write if this is a write operation. Should be set to zero
* for a read operation.
*
* @return the SDIO CMD52 argument generated based on the given arguments.
*/
static inline uint32_t mmhal_make_cmd52_arg(enum mmhal_sdio_rw rw, enum mmhal_sdio_function fn, uint32_t address,
uint8_t write_data)
{
uint32_t arg;
arg = rw | fn;
arg |= (address << MMHAL_SDIO_ADDRESS_OFFSET);
arg |= (write_data << MMHAL_SDIO_CMD52_DATA_OFFSET);
return arg;
}
/**
* Construct an SDIO CMD53 argument based on the given arguments.
*
* @param rw Flag indication direction (read or write).
* @param fn The applicable function.
* @param mode Selects between byte and block mode.
* @param address The address to read/write. Must be <= @c MMHAL_SDIO_ADDRESS_MAX.
* @param count The count of bytes/blocks (depending on @p mode) to transfer. Must
* be <= @c MMHAL_SDIO_COUNT_MAX.
*
* @note OP Code 1 (incrementing address) is assumed. See also @ref MMHAL_SDIO_OPCODE_INC_ADDR.
*
* @return the SDIO CMD53 argument generated based on the given arguments.
*/
static inline uint32_t mmhal_make_cmd53_arg(enum mmhal_sdio_rw rw, enum mmhal_sdio_function fn, enum mmhal_sdio_mode mode,
uint32_t address, uint16_t count)
{
uint32_t arg;
arg = rw | fn | MMHAL_SDIO_OPCODE_INC_ADDR | mode;
arg |= (address << MMHAL_SDIO_ADDRESS_OFFSET);
arg |= (count << MMHAL_SDIO_COUNT_OFFSET);
return arg;
}
/** @} */
/** @} */
#ifdef __cplusplus
}
#endif
/** @} */
-383
View File
@@ -1,383 +0,0 @@
/*
* Copyright 2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMIPAL Morse Micro IP Stack Abstraction Layer (MMIPAL) API
*
* This API provides a layer of abstraction from the underlying IP stack for common operations
* such as configuring the link and getting link status.
*
* @{
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
/** Maximum length of an IP address string, including null-terminator. */
#ifndef MMIPAL_IPADDR_STR_MAXLEN
#define MMIPAL_IPADDR_STR_MAXLEN (48)
#endif
/** Maximum number of IPv6 addresses supported. */
#ifndef MMIPAL_MAX_IPV6_ADDRESSES
#define MMIPAL_MAX_IPV6_ADDRESSES (3)
#endif
/** Enumeration of status codes returned by MMIPAL functions. */
enum mmipal_status {
/** Completed successfully. */
MMIPAL_SUCCESS,
/** One or more arguments were invalid. */
MMIPAL_INVALID_ARGUMENT,
/** The operation could not complete because the link is not up. */
MMIPAL_NO_LINK,
/** Failed due to memory allocation failure. */
MMIPAL_NO_MEM,
/** This functionality is not supported (e.g., due to build configuration). */
MMIPAL_NOT_SUPPORTED,
};
/** Enumeration of link states. */
enum mmipal_link_state {
/** Link is down. */
MMIPAL_LINK_DOWN,
/** Link is up. */
MMIPAL_LINK_UP,
};
/** Enumeration of IP address allocation modes. */
enum mmipal_addr_mode {
/** Disabled. */
MMIPAL_DISABLED,
/** Static IP address. */
MMIPAL_STATIC,
/** IP address allocated via DHCP. @c LWIP_DHCP must be set to 1 if using LWIP. */
MMIPAL_DHCP,
/** IP address allocated via AutoIP. @c LWIP_DHCP must be set to 1 if using LWIP. */
MMIPAL_AUTOIP,
/** DHCP offloaded to chip. */
MMIPAL_DHCP_OFFLOAD,
};
/** IP address string type. */
typedef char mmipal_ip_addr_t[MMIPAL_IPADDR_STR_MAXLEN];
/**
* IPv4 configuration structure.
*
* This should be initialized using @c MMIPAL_IP_CONFIG_DEFAULT.
* For example:
*
* @code{.c}
* struct mmipal_ip_config config = MMIPAL_IP_CONFIG_DEAFULT;
* @endcode
*/
struct mmipal_ip_config {
/** IP address allocation mode. */
enum mmipal_addr_mode mode;
/** local IP address */
mmipal_ip_addr_t ip_addr;
/** Netmask address */
mmipal_ip_addr_t netmask;
/** Gateway address */
mmipal_ip_addr_t gateway_addr;
};
/** Initializer for @ref mmipal_ip_config. */
#define MMIPAL_IP_CONFIG_DEFAULT \
{ \
MMIPAL_DHCP, "", "", "", \
}
/** Enumeration of IPv6 address allocation modes. */
enum mmipal_ip6_addr_mode {
/** Disabled. */
MMIPAL_IP6_DISABLED,
/** Static IPv6 addresses. */
MMIPAL_IP6_STATIC,
/** IPv6 address allocated via autoconfiguration.
* @c LWIP_IPV6_AUTOCONFIG must be set to 1 if using LWIP. */
MMIPAL_IP6_AUTOCONFIG,
/** IPv6 address allocated via stateless DHCPv6.
* @c LWIP_IPV6_DHCP6_STATELESS must be set to 1 if using LWIP. */
MMIPAL_IP6_DHCP6_STATELESS,
};
/**
* IPv6 configuration structure.
*
* This should be initialized using @c MMIPAL_IP6_CONFIG_DEFAULT.
* For example:
*
* @code{.c}
* struct mmipal_ip6_config config = MMIPAL_IP6_CONFIG_DEFAULT;
* @endcode
*/
struct mmipal_ip6_config {
/** IPv6 addresses allocation mode. */
enum mmipal_ip6_addr_mode ip6_mode;
/** Array of IPv6 addresses. */
mmipal_ip_addr_t ip6_addr[MMIPAL_MAX_IPV6_ADDRESSES];
};
/** Initializer for @ref mmipal_ip6_config. */
#define MMIPAL_IP6_CONFIG_DEFAULT \
{ \
MMIPAL_IP6_AUTOCONFIG \
}
/**
* Initialize arguments structure.
*
* This should be initialized using @c MMIPAL_INIT_ARGS_DEFAULT.
* For example:
*
* @code{.c}
* struct mmipal_init_args args = MMIPAL_INIT_ARGS_DEFAULT;
* @endcode
*/
struct mmipal_init_args {
/** IP address allocation mode to use. */
enum mmipal_addr_mode mode;
/** IP address to use (if @c mode is @c MMIPAL_STATIC). */
mmipal_ip_addr_t ip_addr;
/** Netmask to use (if @c mode is @c MMIPAL_STATIC). */
mmipal_ip_addr_t netmask;
/** Gateway IP address to use (if @c mode is @c MMIPAL_STATIC). */
mmipal_ip_addr_t gateway_addr;
/** IPv6 address allocation mode to use. */
enum mmipal_ip6_addr_mode ip6_mode;
/** IPv6 address to use (if @c ip6_mode is @c MMIPAL_IP6_STATIC). */
mmipal_ip_addr_t ip6_addr;
/** Flag requesting ARP response offload feature */
bool offload_arp_response;
/** ARP refresh offload interval in seconds */
uint32_t offload_arp_refresh_s;
};
/**
* Default values for @ref mmipal_init_args. This should be used when initializing the
* @ref mmipal_init_args structure.
*/
#define MMIPAL_INIT_ARGS_DEFAULT \
{ \
MMIPAL_DHCP, {0}, {0}, {0}, MMIPAL_IP6_DISABLED, {0}, false, 0 \
}
/**
* Initialize the IP stack and enable the MMWLAN interface.
*
* This will implicitly initialize and boot MMWLAN, and will block until this has completed.
*
* @note This function will boot the Morse Micro transceiver using @ref mmwlan_boot() in order
* to read the MAC address. It is the responsibility of the caller to shut down the
* transceiver using @ref mmwlan_shutdown() as required.
*
* @warning @ref mmwlan_init() must be called before invoking this function.
*
* @param args Initialization arguments.
*
* @return @c MMIPAL_SUCCESS on success. otherwise a vendor specific error code.
*/
enum mmipal_status mmipal_init(const struct mmipal_init_args *args);
/**
* Structure representing the current status of the link.
*/
struct mmipal_link_status {
/** State of the link (up/down). */
enum mmipal_link_state link_state;
/** Current IP address. */
mmipal_ip_addr_t ip_addr;
/** Current netmask. */
mmipal_ip_addr_t netmask;
/** Current gateway IP address. */
mmipal_ip_addr_t gateway;
};
/**
* Prototype for callback function invoked on link status changes.
*
* @param link_status The current link status.
*/
typedef void (*mmipal_link_status_cb_fn_t)(const struct mmipal_link_status *link_status);
/**
* Sets the callback function to be invoked on link status changes.
*
* This will be used when DHCP is enabled.
*
* @note This is for IPv4 only. To get IPv6 status use @c mmipal_get_ip6_config.
* @note If an opaque argument is required then use @ref mmipal_set_ext_link_status_callback()
* instead.
*
* @param fn Function pointer to the callback function.
*/
void mmipal_set_link_status_callback(mmipal_link_status_cb_fn_t fn);
/**
* Prototype for callback function invoked on link status changes.
*
* This is similar to @ref mmipal_link_status_cb_fn_t but with the addition of the @p arg
* parameter.
*
* @param link_status The current link status.
* @param arg Opaque argument that was provided when the callback was registered.
*/
typedef void (*mmipal_ext_link_status_cb_fn_t)(const struct mmipal_link_status *link_status, void *arg);
/**
* Sets the extended link status callback function to be invoked on link status changes.
* This is similar to @ref mmipal_set_link_status_callback() with the exception that
* an opaque argument may also be specified.
*
* This will be used when DHCP is enabled.
*
* @note This is for IPv4 only. To get IPv6 status use @c mmipal_get_ip6_config.
*
* @param fn Function pointer to the callback function.
* @param arg Opaque argument to be passed to the callback.
*/
void mmipal_set_ext_link_status_callback(mmipal_ext_link_status_cb_fn_t fn, void *arg);
/**
* Get the total number of transmitted and received packets on the MMWLAN interface
*
* @note If using LWIP, this function requires LWIP_STATS to be defined in your application,
* otherwise packet counters will always return as 0.
*
* @param tx_packets Pointer to location to store total tx packets
* @param rx_packets Pointer to location to store total rx packets
*/
void mmipal_get_link_packet_counts(uint32_t *tx_packets, uint32_t *rx_packets);
/**
* Set the QoS Traffic ID to use when transmitting.
*
* @param tid The QoS TID to use (0 - @ref MMWLAN_MAX_QOS_TID).
*/
void mmipal_set_tx_qos_tid(uint8_t tid);
/**
* Gets the local address for the MMWLAN interface that is appropriate for a given
* destination address.
*
* The following table shows how the returned @c local_addr is selected:
*
* | @p dest_addr | @c local_addr returned |
* |--------------|---------------------------|
* | type is IPv4 | IPv4 address |
* | type is IPv6 | An IPv6 source address selected from interface's IPv6 addresses or ERR_CONN |
*
* (X = don't care)
*
* If the given parameters would result in a @p local_addr type of IPv4 and IPv4 is not enabled,
* or IPv6 and IPv6 is not enabled, then @c MMIPAL_INVALID_ARGUMENT will be returned.
*
* @param[out] local_addr Output local address for the MMWLAN interface, as noted above.
* @param[in] dest_addr Destination address.
*
* @return @c MMIPAL_SUCESS if @p local_addr successfully set. otherwise an
* appropriate error code.
*/
enum mmipal_status mmipal_get_local_addr(mmipal_ip_addr_t local_addr, const mmipal_ip_addr_t dest_addr);
/**
* Get the IP configurations.
*
* This can be used to get the local IP configurations.
*
* @param config Pointer to the IP configurations.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported.
*/
enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config);
/**
* Set the IP configurations.
*
* This can be used to set the local IP configurations.
*
* @param config Pointer to the IP configurations.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported.
*/
enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config);
/**
* Gets the current IPv4 broadcast address.
*
* @param[out] broadcast_addr Buffer to receive the broadcast address as a string.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported.
*/
enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr);
/**
* Get the IP configurations.
*
* This can be used to get the local IP configurations.
*
* @param config Pointer to the IP configurations.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv6 is not supported..
*/
enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config);
/**
* Set the IPv6 configurations.
*
* This can be used to set the local IPv6 configurations.
*
* @param config Pointer to the IPv6 configurations.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv6 is not supported.
*/
enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config);
/**
* Get current IPv4 link state.
*
* @returns the current IPv4 link state (up or down).
*/
enum mmipal_link_state mmipal_get_link_state(void);
/**
* Set the DNS server at the given index.
*
* @warning Depending on IP stack implementation, this setting may be overridden by DHCP.
*
* @param[in] index Index of the DNS server to set.
* @param[out] addr Address of the DNS server to set.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_INVALID_ARGUMENT if an invalid index or IP
* address was given.
*/
enum mmipal_status mmipal_set_dns_server(uint8_t index, const mmipal_ip_addr_t addr);
/**
* Get the DNS server at the given index.
*
* @param[in] index Index of the DNS server to set.
* @param[out] addr IP address buffer to receive the IP address of the DNS server at the given
* index. Will be set to empty string if no server at the given index.
*
* @returns @c MMIPAL_SUCCESS on success.
*/
enum mmipal_status mmipal_get_dns_server(uint8_t index, mmipal_ip_addr_t addr);
#ifdef __cplusplus
}
#endif
/** @} */
-64
View File
@@ -1,64 +0,0 @@
/*
* Morse logging API
*
* Copyright 2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
/**
* Macro for printing a @c uint64_t as two separate @c uint32_t values. This is to allow printing of
* these values even when the @c printf implementation doesn't support it.
*/
#define MM_X64_VAL(value) ((uint32_t)(value >> 32)), ((uint32_t)value)
/** Macro for format specifier to print @ref MM_X64_VAL */
#define MM_X64_FMT "%08lx%08lx"
/**
* Macro for printing a MAC address. This saves writing it out by hand.
*
* Must be used in conjunction with @ref MM_MAC_ADDR_FMT. For example:
*
* @code
* uint8_t mac_addr[] = { 0, 1, 2, 3, 4, 5 };
* printf("MAC address: " MM_MAC_ADDR_FMT "\n", MM_MAC_ADDR_VAL(mac_addr));
* @endcode
*/
#define MM_MAC_ADDR_VAL(value) ((value)[0]), ((value)[1]), ((value)[2]), ((value)[3]), ((value)[4]), ((value)[5])
/** Macro for format specifier to print @ref MM_MAC_ADDR_VAL */
#define MM_MAC_ADDR_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
/**
* Initialize Morse logging API.
*
* This should be invoked after OS initialization since it will create a mutex for
* logging.
*/
void mm_logging_init(void);
/**
* Dumps a binary buffer in hex.
*
* @param level A single character indicating log level.
* @param function Name of function this was invoked from.
* @param line_number Line number this was invoked from.
* @param title Title of the buffer.
* @param buf The buffer to dump.
* @param len Length of the buffer.
*/
void mm_hexdump(char level, const char *function, unsigned line_number, const char *title, const uint8_t *buf, size_t len);
#ifdef __cplusplus
}
#endif
File diff suppressed because it is too large Load Diff
-515
View File
@@ -1,515 +0,0 @@
/*
* Copyright 2022-2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMPKT Morse Micro Packet Buffer (mmpkt) API
*
* This API provides support for buffers tailored towards packets.
*
* @{
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "mmosal.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Round @p x up to the next multiple of @p m (where @p m is a power of 2).
*
* @warning @p m must be a power of 2.
*/
#ifndef MM_FAST_ROUND_UP
#define MM_FAST_ROUND_UP(x, m) ((((x)-1) | ((m)-1)) + 1)
#endif
struct mmdrv_cmd_metadata;
struct mmdrv_tx_metadata;
struct mmdrv_rx_metadata;
struct mmpkt_ops;
/**
* Union of pointer types for mmpkt metadata.
*
* The metadata is accessed through one of these pointers, depending on which context the packet
* is being used in.
*/
union mmpkt_metadata_ptr {
/** Opaque pointer for contexts which are unaware of the specific metadata structure. */
void *opaque;
/** Metadata for a packet which is being transmitted. */
struct mmdrv_tx_metadata *tx;
/** Metadata for a packet which is being received. */
struct mmdrv_rx_metadata *rx;
/** Control block for a command response sent to the host. */
struct mmdrv_cmd_metadata *cmd;
};
/**
* Core mmpkt data structure.
*
* @note The contents of this data structure should never need to be accessed directly. Rather
* the various functions provided as part of this API should be used.
*
* @code
* +----------------------------------------------------------+--------------+
* | RESERVED | Data | RESERVED | METADATA |
* +----------------------------------------------------------+--------------+
* ^ ^ ^ ^
* | | | |
* | |<-----------data_len--------->| |
* | start_offset |
* | |
* |<-----------------------buf_len-------------------------->|
* buf
* @endcode
*/
struct mmpkt {
/** The buffer where data is stored. */
uint8_t *buf;
/** Length of the buffer. */
uint32_t buf_len;
/** Offset where actual data starts in the buffer. */
uint32_t start_offset;
/** Length of actual data in the buffer. */
uint32_t data_len;
/** Packet metadata used by driver (context dependent). */
union mmpkt_metadata_ptr metadata;
/** Reference to operations data structure for this mmpkt. */
const struct mmpkt_ops *ops;
/** Pointer that can be used to construct linked lists. */
struct mmpkt *volatile next;
};
/** Operations data structure for mmpkt. */
struct mmpkt_ops {
/** Free the given mmpkt. */
void (*free_mmpkt)(void *mmpkt);
};
/**
* Opened view of an mmpkt.
*
* In this implementation, this structure does not actually exist. We only use it as a pointer type
* which is incompatible with @ref mmpkt, to distinguish between functions which operate on opened
* packet views versus functions which can operate on unopened packets.
*
* In other implementations of this API, packets must be "opened" (mapped into memory) before their
* contents can be accessed. Thus the distinction between opened and unopened packets is important
* for those implementations.
*/
struct mmpktview;
/**
* Initialize an mmpkt header with the given values.
*
* @param mmpkt mmpkt to initialize.
* @param buf Pointer to buffer.
* @param buf_len Length of @p buf.
* @param data_start_offset Initial value for @c start_offset.
* @param ops Operations data structure.
*/
static inline void mmpkt_init(struct mmpkt *mmpkt, uint8_t *buf, uint32_t buf_len, uint32_t data_start_offset,
const struct mmpkt_ops *ops)
{
memset(mmpkt, 0, sizeof(*mmpkt));
mmpkt->buf = buf;
mmpkt->buf_len = buf_len;
mmpkt->start_offset = data_start_offset;
mmpkt->ops = ops;
}
/**
* Initialize an mmpkt in a single buffer using the given values.
*
* @param buf Pointer to buffer.
* @param buf_len Length of @p buf.
* @param space_at_start Amount of space to reserve at start of buffer.
* @param space_at_end Amount of space to reserve at end of buffer.
* @param metadata_size Size of metadata (0 for no metadata).
*
* @param ops Operations data structure.
*
* @note @p buf_len must be large enough to contain the @c mmkpt header, the data buffer
* (rounded up to the nearest 4 bytes) and metadata (rounded up to the nearest 4 bytes).
*
* @returns a pointer to the initialized @c mmpkt (will be the same address as @p buf) or @c NULL
* on error (@p buf length too short).
*/
static inline struct mmpkt *mmpkt_init_buf(uint8_t *buf, uint32_t buf_len, uint32_t space_at_start, uint32_t space_at_end,
uint32_t metadata_size, const struct mmpkt_ops *ops)
{
struct mmpkt *mmpkt = (struct mmpkt *)buf;
uint8_t *data_start;
uint32_t header_size = MM_FAST_ROUND_UP(sizeof(*mmpkt), 4);
uint32_t data_len = MM_FAST_ROUND_UP(space_at_start + space_at_end, 4);
metadata_size = MM_FAST_ROUND_UP(metadata_size, 4);
if (header_size + data_len + metadata_size > buf_len) {
return NULL;
}
data_start = ((uint8_t *)mmpkt) + header_size;
mmpkt_init(mmpkt, data_start, data_len, space_at_start, ops);
if (metadata_size != 0) {
mmpkt->metadata.opaque = data_start + data_len;
memset(mmpkt->metadata.opaque, 0, metadata_size);
}
return mmpkt;
}
/**
* Allocate a new mmpkt on the heap (using @ref mmosal_malloc()).
*
* @param space_at_start Amount of space to reserve at start of buffer.
* @param space_at_end Amount of space to reserve at end of buffer.
* @param metadata_size Size of metadata (0 for no metadata).
*
* @note @c start_offset will be set to @p space_at_start, and @c buf_len will be the sum
* of @p space_at_start and @p space_at_end (rounded up to a multiple of 4).
*
* @returns newly allocated mmpkt on success or @c NULL on failure.
*/
struct mmpkt *mmpkt_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end, uint32_t metadata_size);
/**
* Release a reference to the given mmpkt. If this was the last reference (@c addition_ref_cnt
* was 0) then the mmpkt will be freed using the appropriate op callback.
*
* @param mmpkt The mmpkt to release reference to. May be @c NULL.
*/
void mmpkt_release(struct mmpkt *mmpkt);
/**
* Open a view of the given mmpkt.
*
* Packets must be opened before the contents of their buffer can be accessed. Most of the
* functions below expect to be passed an opened view of a packet to operate on.
*
* The view must be closed by calling @ref mmpkt_close() before the packet is released by
* @ref mmpkt_release().
*
* @param mmpkt The mmpkt to be opened.
*
* @returns a pointer representing the opened view.
*/
static inline struct mmpktview *mmpkt_open(struct mmpkt *mmpkt)
{
return (struct mmpktview *)mmpkt;
}
/**
* Close the given view.
*
* @param[in,out] view Pointer to a variable holding the view to be closed.
* This will be modified to indicate it is no longer valid.
*/
static inline void mmpkt_close(struct mmpktview **view)
{
(void)(view);
}
/**
* Get the underlying mmpkt from an opened view.
*
* @param view View of an mmpkt.
*
* @returns the underlying mmpkt.
*/
static inline struct mmpkt *mmpkt_from_view(struct mmpktview *view)
{
return (struct mmpkt *)view;
}
/**
* Gets a pointer to the start of the data in the mmpkt.
*
* @param view The opened mmpkt to operate on.
*
* @returns a pointer to the start of the data in the mmpkt.
*/
static inline uint8_t *mmpkt_get_data_start(struct mmpktview *view)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return mmpkt->buf + mmpkt->start_offset;
}
/**
* Gets a pointer to the end of the data in the mmpkt.
*
* @param view The opened mmpkt to operate on.
*
* @returns a pointer to the end of the data in the mmpkt.
*/
static inline uint8_t *mmpkt_get_data_end(struct mmpktview *view)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return mmpkt->buf + mmpkt->start_offset + mmpkt->data_len;
}
/**
* Peek the length of the data currently from an unopened mmpkt.
*
* @param mmpkt The unopened mmpkt to operate on.
*
* @returns the length of the data currently in the mmpkt (note that this is different from the
* length of the available buffer space).
*/
static inline uint32_t mmpkt_peek_data_length(struct mmpkt *mmpkt)
{
return mmpkt->data_len;
}
/**
* Gets the length of the data currently in the mmpkt.
*
* @param view The opened mmpkt to operate on.
*
* @returns the length of the data currently in the mmpkt (note that this is different from the
* length of the available buffer space).
*/
static inline uint32_t mmpkt_get_data_length(struct mmpktview *view)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return mmpkt->data_len;
}
/**
* Returns the amount of space available for prepending to the data in the buffer.
*
* @param view The opened mmpkt to operate on.
*
* @returns the available space in bytes.
*/
static inline uint32_t mmpkt_available_space_at_start(struct mmpktview *view)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return mmpkt->start_offset;
}
/**
* Returns the amount of space available for appending to the data in the buffer.
*
* @param view The opened mmpkt to operate on.
*
* @returns the available space in bytes.
*/
static inline uint32_t mmpkt_available_space_at_end(struct mmpktview *view)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return mmpkt->buf_len - (mmpkt->start_offset + mmpkt->data_len);
}
/**
* Reserves space immediately before the data currently in the given mmpkt and returns
* a pointer to this space.
*
* For a function that also copies data in, see @ref mmpkt_prepend_data().
*
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start().
*
* @param view The opened mmpkt to operate on.
* @param len Length of data to be prepended.
*
* @returns a pointer to the place in the buffer where the data should be put.
*/
static inline uint8_t *mmpkt_prepend(struct mmpktview *view, uint32_t len)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
MMOSAL_ASSERT(len <= mmpkt_available_space_at_start(view));
mmpkt->start_offset -= len;
mmpkt->data_len += len;
return mmpkt->buf + mmpkt->start_offset;
}
/**
* Prepends the given data to the data already in the mmpkt.
*
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start().
*
* @warning The memory area pointed to by data must not overlap with the mmpkt data.
*
* @param view The opened mmpkt to operate on.
* @param data The data to be prepended.
* @param len Length of data to be prepended.
*/
static inline void mmpkt_prepend_data(struct mmpktview *view, const uint8_t *data, uint32_t len)
{
uint8_t *dest = mmpkt_prepend(view, len);
memcpy(dest, data, len);
}
/**
* Reserves space immediately after the data currently in the given mmpkt and returns
* a pointer to this space.
*
* For a function that also copies data in, see @ref mmpkt_append_data().
*
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_end().
*
* @param view The opened mmpkt to operate on.
* @param len Length of data to be append.
*
* @returns a pointer to the place in the buffer where the data should be put.
*/
static inline uint8_t *mmpkt_append(struct mmpktview *view, uint32_t len)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
uint8_t *ret = mmpkt_get_data_end(view);
MMOSAL_ASSERT(len <= mmpkt_available_space_at_end(view));
mmpkt->data_len += len;
return ret;
}
/**
* Appends the given data to the data already in the mmpkt.
*
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start().
*
* @param view The opened mmpkt to operate on.
* @param data The data to be prepended.
* @param len Length of data to be prepended.
*/
static inline void mmpkt_append_data(struct mmpktview *view, const uint8_t *data, uint32_t len)
{
uint8_t *dest = mmpkt_append(view, len);
memcpy(dest, data, len);
}
/**
* Retrieve a reference to the metadata associated with the given mmpkt.
*
* @param mmpkt The mmpkt to operate on.
*
* @returns a reference to the mmpkt metadata.
*/
static inline union mmpkt_metadata_ptr mmpkt_get_metadata(struct mmpkt *mmpkt)
{
return mmpkt->metadata;
}
/**
* Remove data from the start of the mmpkt.
*
* @param view The opened mmpkt to operate on.
* @param len Length of data to remove.
*
* @returns a pointer to the removed data or NULL if the mmpkt data length was less than @p len.
*/
static inline uint8_t *mmpkt_remove_from_start(struct mmpktview *view, uint32_t len)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
uint8_t *ret;
if (mmpkt_get_data_length(view) < len) {
return NULL;
}
ret = mmpkt_get_data_start(view);
mmpkt->start_offset += len;
mmpkt->data_len -= len;
return ret;
}
/**
* Remove data from the end of the mmpkt.
*
* @param view The opened mmpkt to operate on.
* @param len Length of data to remove.
*
* @returns a pointer to the removed data or NULL if the mmpkt data length was less than @p len.
*/
static inline uint8_t *mmpkt_remove_from_end(struct mmpktview *view, uint32_t len)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
uint8_t *ret;
if (mmpkt_get_data_length(view) < len) {
return NULL;
}
ret = mmpkt_get_data_end(view) - len;
mmpkt->data_len -= len;
return ret;
}
/**
* Truncate the mmpkt data to the given length.
*
* @param mmpkt mmpkt to operate on.
* @param len New data length. (Must be less than or equal to the data length
* of the mmpkt).
*/
static inline void mmpkt_truncate(struct mmpkt *mmpkt, uint32_t len)
{
MMOSAL_ASSERT(len <= mmpkt->data_len);
mmpkt->data_len = len;
}
/**
* Get the `next` pointer embedded in the mmpkt.
*
* Used by the mmpkt_list structure for making linked lists of mmpkts.
*
* @param mmpkt The mmpkt to operate on.
*
* @returns pointer to the next mmpkt in the chain (may be NULL).
*/
static inline struct mmpkt *mmpkt_get_next(struct mmpkt *mmpkt)
{
return mmpkt->next;
}
/**
* Set the `next` pointer embedded in the mmpkt.
*
* Used by the mmpkt_list structure for making linked lists of mmpkts.
*
* @param mmpkt The mmpkt to operate on.
* @param next The next mmpkt in the chain.
*/
static inline void mmpkt_set_next(struct mmpkt *mmpkt, struct mmpkt *next)
{
mmpkt->next = next;
}
/**
* Check whether the given pointer is pointing inside the mmpkt's buffer.
*
* @note This checks against the full buffer, which includes any unused regions at the beginning and
* end of the buffer which do not contain valid packet data.
*
* @param view The opened mmpkt to operate on.
* @param ptr The pointer to check.
*
* @returns true if the pointer points into the buffer.
*/
static inline bool mmpkt_contains_ptr(struct mmpktview *view, const void *ptr)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return ((const uint8_t *)ptr >= &mmpkt->buf[0] && (const uint8_t *)ptr < &mmpkt->buf[mmpkt->buf_len]);
}
#ifdef __cplusplus
}
#endif
/** @} */
-172
View File
@@ -1,172 +0,0 @@
/*
* Copyright 2022-2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @ingroup MMPKT
*
* @{
*/
#pragma once
#include <stddef.h>
#include "mmpkt.h"
#ifdef __cplusplus
extern "C" {
#endif
/** Structure that can be used as the head of a linked list of mmpkts that counts its length. */
struct mmpkt_list {
/** First mmpkt in the list. */
struct mmpkt *volatile head;
/** Last mmpkt in the list. */
struct mmpkt *volatile tail;
/** Length of the list. */
volatile uint32_t len;
};
/** Static initializer for @ref mmpkt_list. */
#define MMPKT_LIST_INIT \
{ \
NULL, NULL, 0 \
}
/**
* Initialization function for @ref mmpkt_list, for cases where @c MMPKT_LIST_INIT
* cannot be used.
*
* @param list The mmpkt_list to init.
*/
static inline void mmpkt_list_init(struct mmpkt_list *list)
{
list->head = NULL;
list->tail = NULL;
list->len = 0;
}
/**
* Add an mmpkt to the start of an mmpkt list.
*
* @param list The list to prepend to.
* @param mmpkt The mmpkt to prepend.
*/
void mmpkt_list_prepend(struct mmpkt_list *list, struct mmpkt *mmpkt);
/**
* Add an mmpkt to the end of an mmpkt list.
*
* @param list The list to append to.
* @param mmpkt The mmpkt to append.
*/
void mmpkt_list_append(struct mmpkt_list *list, struct mmpkt *mmpkt);
/**
* Remove an mmpkt from an mmpkt list.
*
* @param list The list to remove from.
* @param mmpkt The mmpkt to remove.
*/
void mmpkt_list_remove(struct mmpkt_list *list, struct mmpkt *mmpkt);
/**
* Remove the mmpkt at the head of the list and return it.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmpkt, or @c NULL if the list is empty.
*/
struct mmpkt *mmpkt_list_dequeue(struct mmpkt_list *list);
/**
* Remove the mmpkt at the tail of the list and return it.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmpkt, or @c NULL if the list is empty.
*/
struct mmpkt *mmpkt_list_dequeue_tail(struct mmpkt_list *list);
/**
* Remove all mmpkts from the list and return as a linked list.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmpkts, or @c NULL if the list is empty.
*/
static inline struct mmpkt *mmpkt_list_dequeue_all(struct mmpkt_list *list)
{
struct mmpkt *head = list->head;
list->head = NULL;
list->tail = NULL;
list->len = 0;
return head;
}
/**
* Checks whether the given mmpkt list is empty.
*
* @param list The list to check.
*
* @returns @c true if the list is empty, else @c false.
*/
static inline bool mmpkt_list_is_empty(struct mmpkt_list *list)
{
return (list->head == NULL);
}
/**
* Returns the head of the mmpkt list.
*
* @param list The list to peek into.
*
* @returns the mmpkt at the head of the list.
*/
static inline struct mmpkt *mmpkt_list_peek(struct mmpkt_list *list)
{
return list->head;
}
/**
* Returns the tail of the mmpkt list.
*
* @param list The list to peek into.
*
* @returns the mmpkt at the tail of the list.
*/
static inline struct mmpkt *mmpkt_list_peek_tail(struct mmpkt_list *list)
{
return list->tail;
}
/**
* Free all the packets in the given list and reset the list to empty state.
*
* @param list The list to clear.
*/
void mmpkt_list_clear(struct mmpkt_list *list);
/**
* Safely walk the mmpkt list.
*
* @warning This macro cannot be used following an if statement with no parentheses if there
* is an else clause. For example, do not do:
* `if (x) MMPKT_LIST_WALK(a,b,c) else foo();` -- instead:
* `if (x) { MMPKT_LIST_WALK(a,b,c) } else foo();`
*/
#define MMPKT_LIST_WALK(_lst, _wlk, _nxt) \
if ((_lst)->head != NULL) /* NOLINT(readability/braces) */ \
for (_wlk = (_lst)->head, _nxt = mmpkt_get_next(_wlk); _wlk != NULL; \
_wlk = _nxt, _nxt = _wlk ? mmpkt_get_next(_wlk) : NULL)
#ifdef __cplusplus
}
#endif
/**
* @}
*/
File diff suppressed because it is too large Load Diff
-302
View File
@@ -1,302 +0,0 @@
/*
*
* Copyright 2022-2023 Morse Micro
*/
/**
* @ingroup MMWLAN_REGDB
* @defgroup MMWLAN_REGDB_TEMPLATE Template S1G regulatory database
*
* \{
*
* @section MMWLAN_REGDB_TEMPLATE_DISCLAIMER Disclaimer
*
* While every effort has been made to maintain accuracy of this database, no guarantee is
* given as to the accuracy of the information contained herein.
*
* @section MMWLAN_REGDB_TEMPLATE_COUNTRIES Country code list
*
* | Country Code | Country |
* | ------------ | ------- |
* | AU | Australia |
* | EU | EU |
* | IN | India |
* | JP | Japan |
* | KR | South Korea |
* | NZ | New Zealand |
* | SG | Singapore |
* | US | USA |
*/
#include "mmwlan.h"
/** List of valid S1G channels for Australia. */
static const struct mmwlan_s1g_channel s1g_channels_AU[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 915500000, 10000, false, 68, 22, 27, 1, 30, 0, 0, 0 },
{ 916500000, 10000, false, 68, 22, 29, 1, 30, 0, 0, 0 },
{ 917500000, 10000, false, 68, 22, 31, 1, 30, 0, 0, 0 },
{ 918500000, 10000, false, 68, 22, 33, 1, 30, 0, 0, 0 },
{ 919500000, 10000, false, 68, 22, 35, 1, 30, 0, 0, 0 },
{ 920500000, 10000, false, 68, 22, 37, 1, 30, 0, 0, 0 },
{ 921500000, 10000, false, 68, 22, 39, 1, 30, 0, 0, 0 },
{ 922500000, 10000, false, 68, 22, 41, 1, 30, 0, 0, 0 },
{ 923500000, 10000, false, 68, 22, 43, 1, 30, 0, 0, 0 },
{ 924500000, 10000, false, 68, 22, 45, 1, 30, 0, 0, 0 },
{ 925500000, 10000, false, 68, 22, 47, 1, 30, 0, 0, 0 },
{ 926500000, 10000, false, 68, 22, 49, 1, 30, 0, 0, 0 },
{ 927500000, 10000, false, 68, 22, 51, 1, 30, 0, 0, 0 },
{ 917000000, 10000, false, 69, 23, 30, 2, 30, 0, 0, 0 },
{ 919000000, 10000, false, 69, 23, 34, 2, 30, 0, 0, 0 },
{ 921000000, 10000, false, 69, 23, 38, 2, 30, 0, 0, 0 },
{ 923000000, 10000, false, 69, 23, 42, 2, 30, 0, 0, 0 },
{ 925000000, 10000, false, 69, 23, 46, 2, 30, 0, 0, 0 },
{ 927000000, 10000, false, 69, 23, 50, 2, 30, 0, 0, 0 },
{ 918000000, 10000, false, 70, 24, 32, 4, 30, 0, 0, 0 },
{ 922000000, 10000, false, 70, 24, 40, 4, 30, 0, 0, 0 },
{ 926000000, 10000, false, 70, 24, 48, 4, 30, 0, 0, 0 },
{ 924000000, 10000, false, 71, 25, 44, 8, 30, 0, 0, 0 },
};
/** Channel list structure for Australia. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_AU = {
.country_code = "AU",
.num_channels = (sizeof(s1g_channels_AU)/sizeof(s1g_channels_AU[0])),
.channels = s1g_channels_AU,
};
/** List of valid S1G channels for EU. */
static const struct mmwlan_s1g_channel s1g_channels_EU[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 863500000, 280, false, 66, 6, 1, 1, 16, 0, 0, 0 },
{ 864500000, 280, false, 66, 6, 3, 1, 16, 0, 0, 0 },
{ 865500000, 280, false, 66, 6, 5, 1, 16, 0, 0, 0 },
{ 866500000, 280, false, 66, 6, 7, 1, 16, 0, 0, 0 },
{ 867500000, 280, false, 66, 6, 9, 1, 16, 0, 0, 0 },
};
/** Channel list structure for EU. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_EU = {
.country_code = "EU",
.num_channels = (sizeof(s1g_channels_EU)/sizeof(s1g_channels_EU[0])),
.channels = s1g_channels_EU,
};
/** List of valid S1G channels for India. */
static const struct mmwlan_s1g_channel s1g_channels_IN[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 865500000, 280, false, 66, 6, 5, 1, 16, 0, 0, 0 },
{ 866500000, 280, false, 66, 6, 7, 1, 16, 0, 0, 0 },
{ 867500000, 280, false, 66, 6, 9, 1, 16, 0, 0, 0 },
};
/** Channel list structure for India. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_IN = {
.country_code = "IN",
.num_channels = (sizeof(s1g_channels_IN)/sizeof(s1g_channels_IN[0])),
.channels = s1g_channels_IN,
};
/** List of valid S1G channels for Japan. */
static const struct mmwlan_s1g_channel s1g_channels_JP[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 921000000, 1000, true, 73, 8, 9, 1, 16, 2000, 2000, 100000 },
{ 923000000, 1000, true, 73, 8, 13, 1, 16, 2000, 2000, 100000 },
{ 924000000, 1000, true, 73, 8, 15, 1, 16, 2000, 2000, 100000 },
{ 925000000, 1000, true, 73, 8, 17, 1, 16, 2000, 2000, 100000 },
{ 926000000, 1000, true, 73, 8, 19, 1, 16, 2000, 2000, 100000 },
{ 927000000, 1000, true, 73, 8, 21, 1, 16, 2000, 2000, 100000 },
{ 923500000, 1000, true, 64, 9, 2, 2, 16, 2000, 2000, 100000 },
{ 924500000, 1000, true, 64, 10, 4, 2, 16, 2000, 2000, 100000 },
{ 925500000, 1000, true, 64, 9, 6, 2, 16, 2000, 2000, 100000 },
{ 926500000, 1000, true, 64, 10, 8, 2, 16, 2000, 2000, 100000 },
{ 924500000, 1000, true, 65, 11, 36, 4, 16, 2000, 2000, 100000 },
{ 925500000, 1000, true, 65, 12, 38, 4, 16, 2000, 2000, 100000 },
};
/** Channel list structure for Japan. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_JP = {
.country_code = "JP",
.num_channels = (sizeof(s1g_channels_JP)/sizeof(s1g_channels_JP[0])),
.channels = s1g_channels_JP,
};
/** List of valid S1G channels for South Korea. */
static const struct mmwlan_s1g_channel s1g_channels_KR[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 918000000, 10000, false, 74, 14, 1, 1, 4, 50000, 0, 4000000 },
{ 919000000, 10000, false, 74, 14, 3, 1, 4, 50000, 0, 4000000 },
{ 920000000, 10000, false, 74, 14, 5, 1, 4, 50000, 0, 4000000 },
{ 921000000, 10000, false, 74, 14, 7, 1, 4, 50000, 0, 4000000 },
{ 922000000, 10000, false, 74, 14, 9, 1, 10, 50000, 0, 4000000 },
{ 923000000, 10000, false, 74, 14, 11, 1, 10, 50000, 0, 4000000 },
{ 918500000, 10000, false, 75, 15, 2, 2, 4, 50000, 0, 4000000 },
{ 920500000, 10000, false, 75, 15, 6, 2, 4, 50000, 0, 4000000 },
{ 922500000, 10000, false, 75, 15, 10, 2, 10, 50000, 0, 4000000 },
{ 921500000, 10000, false, 76, 16, 8, 4, 4, 50000, 0, 4000000 },
{ 926500000, 10000, false, 74, 14, 18, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
{ 927500000, 10000, false, 74, 14, 20, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
{ 928500000, 10000, false, 74, 14, 22, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
{ 929500000, 10000, false, 74, 14, 24, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
{ 927000000, 10000, false, 75, 15, 19, 2, 20, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
{ 929000000, 10000, false, 75, 15, 23, 2, 20, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
};
/** Channel list structure for South Korea. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_KR = {
.country_code = "KR",
.num_channels = (sizeof(s1g_channels_KR)/sizeof(s1g_channels_KR[0])),
.channels = s1g_channels_KR,
};
/** List of valid S1G channels for New Zealand. */
static const struct mmwlan_s1g_channel s1g_channels_NZ[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 915500000, 10000, false, 68, 26, 27, 1, 30, 0, 0, 0 },
{ 916500000, 10000, false, 68, 26, 29, 1, 30, 0, 0, 0 },
{ 917500000, 10000, false, 68, 26, 31, 1, 30, 0, 0, 0 },
{ 918500000, 10000, false, 68, 26, 33, 1, 30, 0, 0, 0 },
{ 919500000, 10000, false, 68, 26, 35, 1, 30, 0, 0, 0 },
{ 920500000, 10000, false, 68, 26, 37, 1, 36, 0, 0, 0 },
{ 921500000, 10000, false, 68, 26, 39, 1, 36, 0, 0, 0 },
{ 922500000, 10000, false, 68, 26, 41, 1, 36, 0, 0, 0 },
{ 923500000, 10000, false, 68, 26, 43, 1, 36, 0, 0, 0 },
{ 924500000, 10000, false, 68, 26, 45, 1, 36, 0, 0, 0 },
{ 925500000, 10000, false, 68, 26, 47, 1, 36, 0, 0, 0 },
{ 926500000, 10000, false, 68, 26, 49, 1, 36, 0, 0, 0 },
{ 927500000, 10000, false, 68, 26, 51, 1, 36, 0, 0, 0 },
{ 917000000, 10000, false, 69, 27, 30, 2, 30, 0, 0, 0 },
{ 919000000, 10000, false, 69, 27, 34, 2, 30, 0, 0, 0 },
{ 921000000, 10000, false, 69, 27, 38, 2, 36, 0, 0, 0 },
{ 923000000, 10000, false, 69, 27, 42, 2, 36, 0, 0, 0 },
{ 925000000, 10000, false, 69, 27, 46, 2, 36, 0, 0, 0 },
{ 927000000, 10000, false, 69, 27, 50, 2, 36, 0, 0, 0 },
{ 918000000, 10000, false, 70, 28, 32, 4, 30, 0, 0, 0 },
{ 922000000, 10000, false, 70, 28, 40, 4, 36, 0, 0, 0 },
{ 926000000, 10000, false, 70, 28, 48, 4, 36, 0, 0, 0 },
{ 924000000, 10000, false, 71, 29, 44, 8, 36, 0, 0, 0 },
};
/** Channel list structure for New Zealand. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_NZ = {
.country_code = "NZ",
.num_channels = (sizeof(s1g_channels_NZ)/sizeof(s1g_channels_NZ[0])),
.channels = s1g_channels_NZ,
};
/** List of valid S1G channels for Singapore. */
static const struct mmwlan_s1g_channel s1g_channels_SG[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 866500000, 277, false, 66, 17, 7, 1, 29, 100000, 0, 1000000 },
{ 867500000, 277, false, 66, 17, 9, 1, 29, 100000, 0, 1000000 },
{ 868500000, 277, false, 66, 17, 11, 1, 29, 100000, 0, 1000000 },
{ 868000000, 277, false, 67, 19, 10, 2, 29, 100000, 0, 1000000 },
{ 920500000, 10000, false, 68, 18, 37, 1, 22, 0, 0, 0 },
{ 921500000, 10000, false, 68, 18, 39, 1, 22, 0, 0, 0 },
{ 922500000, 10000, false, 68, 18, 41, 1, 22, 0, 0, 0 },
{ 923500000, 10000, false, 68, 18, 43, 1, 22, 0, 0, 0 },
{ 924500000, 10000, false, 68, 18, 45, 1, 22, 0, 0, 0 },
{ 921000000, 10000, false, 69, 20, 38, 2, 22, 0, 0, 0 },
{ 923000000, 10000, false, 69, 20, 42, 2, 22, 0, 0, 0 },
{ 922000000, 10000, false, 70, 21, 40, 4, 22, 0, 0, 0 },
};
/** Channel list structure for Singapore. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_SG = {
.country_code = "SG",
.num_channels = (sizeof(s1g_channels_SG)/sizeof(s1g_channels_SG[0])),
.channels = s1g_channels_SG,
};
/** List of valid S1G channels for USA. */
static const struct mmwlan_s1g_channel s1g_channels_US[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 902500000, 10000, false, 68, 1, 1, 1, 36, 0, 0, 0 }, /* Warning: regulatory requirements may not be met */
{ 903500000, 10000, false, 68, 1, 3, 1, 36, 0, 0, 0 },
{ 904500000, 10000, false, 68, 1, 5, 1, 36, 0, 0, 0 },
{ 905500000, 10000, false, 68, 1, 7, 1, 36, 0, 0, 0 },
{ 906500000, 10000, false, 68, 1, 9, 1, 36, 0, 0, 0 },
{ 907500000, 10000, false, 68, 1, 11, 1, 36, 0, 0, 0 },
{ 908500000, 10000, false, 68, 1, 13, 1, 36, 0, 0, 0 },
{ 909500000, 10000, false, 68, 1, 15, 1, 36, 0, 0, 0 },
{ 910500000, 10000, false, 68, 1, 17, 1, 36, 0, 0, 0 },
{ 911500000, 10000, false, 68, 1, 19, 1, 36, 0, 0, 0 },
{ 912500000, 10000, false, 68, 1, 21, 1, 36, 0, 0, 0 },
{ 913500000, 10000, false, 68, 1, 23, 1, 36, 0, 0, 0 },
{ 914500000, 10000, false, 68, 1, 25, 1, 36, 0, 0, 0 },
{ 915500000, 10000, false, 68, 1, 27, 1, 36, 0, 0, 0 },
{ 916500000, 10000, false, 68, 1, 29, 1, 36, 0, 0, 0 },
{ 917500000, 10000, false, 68, 1, 31, 1, 36, 0, 0, 0 },
{ 918500000, 10000, false, 68, 1, 33, 1, 36, 0, 0, 0 },
{ 919500000, 10000, false, 68, 1, 35, 1, 36, 0, 0, 0 },
{ 920500000, 10000, false, 68, 1, 37, 1, 36, 0, 0, 0 },
{ 921500000, 10000, false, 68, 1, 39, 1, 36, 0, 0, 0 },
{ 922500000, 10000, false, 68, 1, 41, 1, 36, 0, 0, 0 },
{ 923500000, 10000, false, 68, 1, 43, 1, 36, 0, 0, 0 },
{ 924500000, 10000, false, 68, 1, 45, 1, 36, 0, 0, 0 },
{ 925500000, 10000, false, 68, 1, 47, 1, 36, 0, 0, 0 },
{ 926500000, 10000, false, 68, 1, 49, 1, 36, 0, 0, 0 },
{ 927500000, 10000, false, 68, 1, 51, 1, 36, 0, 0, 0 },
{ 903000000, 10000, false, 69, 2, 2, 2, 36, 0, 0, 0 }, /* Warning: regulatory requirements may not be met */
{ 905000000, 10000, false, 69, 2, 6, 2, 36, 0, 0, 0 },
{ 907000000, 10000, false, 69, 2, 10, 2, 36, 0, 0, 0 },
{ 909000000, 10000, false, 69, 2, 14, 2, 36, 0, 0, 0 },
{ 911000000, 10000, false, 69, 2, 18, 2, 36, 0, 0, 0 },
{ 913000000, 10000, false, 69, 2, 22, 2, 36, 0, 0, 0 },
{ 915000000, 10000, false, 69, 2, 26, 2, 36, 0, 0, 0 },
{ 917000000, 10000, false, 69, 2, 30, 2, 36, 0, 0, 0 },
{ 919000000, 10000, false, 69, 2, 34, 2, 36, 0, 0, 0 },
{ 921000000, 10000, false, 69, 2, 38, 2, 36, 0, 0, 0 },
{ 923000000, 10000, false, 69, 2, 42, 2, 36, 0, 0, 0 },
{ 925000000, 10000, false, 69, 2, 46, 2, 36, 0, 0, 0 },
{ 927000000, 10000, false, 69, 2, 50, 2, 36, 0, 0, 0 },
{ 906000000, 10000, false, 70, 3, 8, 4, 36, 0, 0, 0 },
{ 910000000, 10000, false, 70, 3, 16, 4, 36, 0, 0, 0 },
{ 914000000, 10000, false, 70, 3, 24, 4, 36, 0, 0, 0 },
{ 918000000, 10000, false, 70, 3, 32, 4, 36, 0, 0, 0 },
{ 922000000, 10000, false, 70, 3, 40, 4, 36, 0, 0, 0 },
{ 926000000, 10000, false, 70, 3, 48, 4, 36, 0, 0, 0 },
{ 908000000, 10000, false, 71, 4, 12, 8, 36, 0, 0, 0 },
{ 916000000, 10000, false, 71, 4, 28, 8, 36, 0, 0, 0 },
{ 924000000, 10000, false, 71, 4, 44, 8, 36, 0, 0, 0 },
};
/** Channel list structure for USA. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_US = {
.country_code = "US",
.num_channels = (sizeof(s1g_channels_US)/sizeof(s1g_channels_US[0])),
.channels = s1g_channels_US,
};
/** Array of all channel list structs used for the regulatory database. */
static const struct mmwlan_s1g_channel_list *regulatory_db_domains[] = {
&s1g_channel_list_AU,
&s1g_channel_list_EU,
&s1g_channel_list_IN,
&s1g_channel_list_JP,
&s1g_channel_list_KR,
&s1g_channel_list_NZ,
&s1g_channel_list_SG,
&s1g_channel_list_US,
};
/** Regulatory database. */
static const struct mmwlan_regulatory_db regulatory_db = {
.num_domains = (sizeof(regulatory_db_domains)/sizeof(regulatory_db_domains[0])),
.domains = regulatory_db_domains,
};
/**
* Get a pointer to regulatory_db. This function isn't strictly necessary, since regulatory_db
* can be accessed directly, but will prevent the compiler from generated warnings about
* regulatory_db being unused.
*
* @return Reference to the regulatory database
*/
static inline const struct mmwlan_regulatory_db *get_regulatory_db(void)
{
return &regulatory_db;
}
/** \} */
Binary file not shown.
-14
View File
@@ -1,14 +0,0 @@
{
"name": "MorseWlan",
"version": "0.1.0",
"description": "Morse Micro mm-iot-esp32 SDK vendored for Meshtastic HaLow transport. Static morselib (Apache-2.0) + open-source shims.",
"frameworks": ["arduino", "espidf"],
"platforms": ["espressif32"],
"build": {
"srcDir": "src",
"srcFilter": ["+<*.c>"],
"includeDir": "include",
"libArchive": false,
"extraScript": "extra_script.py"
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-148
View File
@@ -1,148 +0,0 @@
/*
* Copyright 2022-2024 Morse Micro
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include <stdint.h>
/*
* ----
* Endianness operations
* ----
*/
#ifdef __big_endian__
#define __BYTE_ORDER __BIG_ENDIAN
#else
#define __BYTE_ORDER __LITTLE_ENDIAN
#endif
#define bswap_16(x) __builtin_bswap16(x)
#define bswap_32(x) __builtin_bswap32(x)
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
/* Protocol families. */
#define PF_INET 2 /* IP protocol family. */
#define PF_INET6 10 /* IP version 6. */
/* Address families. */
#define AF_INET PF_INET
#define AF_INET6 PF_INET6
/*
* ----
* Type definitions
* ----
*/
typedef signed char __s8;
typedef unsigned char __u8;
typedef signed short __s16;
typedef unsigned short __u16;
typedef signed int __s32;
typedef unsigned int __u32;
struct in_addr {
__u32 s_addr;
};
struct in6_addr {
union {
__u32 u32_addr[4];
__u8 u8_addr[16];
} un;
#define s6_addr un.u8_addr
};
/* Stub function declaration for inet_ntop which is called from write_ipv4_info
* function in robust_av.c. Since they are not used we will create a dummy
* function declarion here.
*/
const char *inet_ntop(int __af, const void *__cp, char *__buf, int __len);
/* Rename crypto functions to match symbol name mangling in morselib for avoidance of namespace
* collisions. */
#define aes_decrypt mmint_aes_decrypt
#define aes_decrypt_deinit mmint_aes_decrypt_deinit
#define aes_decrypt_init mmint_aes_decrypt_init
#define crypto_bignum_add mmint_crypto_bignum_add
#define crypto_bignum_addmod mmint_crypto_bignum_addmod
#define crypto_bignum_cmp mmint_crypto_bignum_cmp
#define crypto_bignum_deinit mmint_crypto_bignum_deinit
#define crypto_bignum_div mmint_crypto_bignum_div
#define crypto_bignum_exptmod mmint_crypto_bignum_exptmod
#define crypto_bignum_init mmint_crypto_bignum_init
#define crypto_bignum_init_set mmint_crypto_bignum_init_set
#define crypto_bignum_init_uint mmint_crypto_bignum_init_uint
#define crypto_bignum_inverse mmint_crypto_bignum_inverse
#define crypto_bignum_is_odd mmint_crypto_bignum_is_odd
#define crypto_bignum_is_one mmint_crypto_bignum_is_one
#define crypto_bignum_is_zero mmint_crypto_bignum_is_zero
#define crypto_bignum_legendre mmint_crypto_bignum_legendre
#define crypto_bignum_mod mmint_crypto_bignum_mod
#define crypto_bignum_mulmod mmint_crypto_bignum_mulmod
#define crypto_bignum_rand mmint_crypto_bignum_rand
#define crypto_bignum_rshift mmint_crypto_bignum_rshift
#define crypto_bignum_sqrmod mmint_crypto_bignum_sqrmod
#define crypto_bignum_sub mmint_crypto_bignum_sub
#define crypto_bignum_to_bin mmint_crypto_bignum_to_bin
#define crypto_ec_deinit mmint_crypto_ec_deinit
#define crypto_ec_get_a mmint_crypto_ec_get_a
#define crypto_ec_get_b mmint_crypto_ec_get_b
#define crypto_ec_get_generator mmint_crypto_ec_get_generator
#define crypto_ec_get_order mmint_crypto_ec_get_order
#define crypto_ec_get_prime mmint_crypto_ec_get_prime
#define crypto_ec_init mmint_crypto_ec_init
#define crypto_ec_order_len mmint_crypto_ec_order_len
#define crypto_ec_point_add mmint_crypto_ec_point_add
#define crypto_ec_point_cmp mmint_crypto_ec_point_cmp
#define crypto_ec_point_x mmint_crypto_ec_point_x
#define crypto_ec_point_compute_y_sqr mmint_crypto_ec_point_compute_y_sqr
#define crypto_ec_point_deinit mmint_crypto_ec_point_deinit
#define crypto_ec_point_from_bin mmint_crypto_ec_point_from_bin
#define crypto_ec_point_init mmint_crypto_ec_point_init
#define crypto_ec_point_invert mmint_crypto_ec_point_invert
#define crypto_ec_point_is_at_infinity mmint_crypto_ec_point_is_at_infinity
#define crypto_ec_point_is_on_curve mmint_crypto_ec_point_is_on_curve
#define crypto_ec_point_mul mmint_crypto_ec_point_mul
#define crypto_ec_point_to_bin mmint_crypto_ec_point_to_bin
#define crypto_ec_prime_len mmint_crypto_ec_prime_len
#define crypto_ec_prime_len_bits mmint_crypto_ec_prime_len_bits
#define crypto_ecdh_deinit mmint_crypto_ecdh_deinit
#define crypto_ecdh_get_pubkey mmint_crypto_ecdh_get_pubkey
#define crypto_ecdh_init mmint_crypto_ecdh_init
#define crypto_ecdh_init2 mmint_crypto_ecdh_init2
#define crypto_ecdh_set_peerkey mmint_crypto_ecdh_set_peerkey
#define crypto_ecdh_prime_len mmint_crypto_ecdh_prime_len
#define crypto_get_random mmint_crypto_get_random
#define crypto_unload mmint_crypto_unload
#define hmac_md5 mmint_hmac_md5
#define hmac_sha1 mmint_hmac_sha1
#define hmac_sha1_vector mmint_hmac_sha1_vector
#define hmac_sha256 mmint_hmac_sha256
#define hmac_sha256_vector mmint_hmac_sha256_vector
#define hmac_sha384 mmint_hmac_sha384
#define hmac_sha384_vector mmint_hmac_sha384_vector
#define hmac_sha512 mmint_hmac_sha512
#define hmac_sha512_vector mmint_hmac_sha512_vector
#define omac1_aes_vector mmint_omac1_aes_vector
#define omac1_aes_128 mmint_omac1_aes_128
#define pbkdf2_sha1 mmint_pbkdf2_sha1
#define sha1_prf mmint_sha1_prf
#define sha1_vector mmint_sha1_vector
#define sha256_prf mmint_sha256_prf
#define sha256_prf_bits mmint_sha256_prf_bits
#define sha256_vector mmint_sha256_vector
#define sha384_prf mmint_sha384_prf
#define sha384_vector mmint_sha384_vector
#define sha512_prf mmint_sha512_prf
#define sha512_vector mmint_sha512_vector
#define wpabuf_alloc mmint_wpabuf_alloc
#define wpabuf_alloc_copy mmint_wpabuf_alloc_copy
#define wpabuf_clear_free mmint_wpabuf_clear_free
#define wpabuf_put mmint_wpabuf_put
-27
View File
@@ -1,27 +0,0 @@
#ifdef USE_MM_IOT_ESP32
/*
* Stubs for LWIP netif callback API that Arduino-ESP32's prebuilt LWIP omits
* (CONFIG_LWIP_NETIF_STATUS_CALLBACK / _LINK_CALLBACK are disabled in its
* sdkconfig, so the real functions are absent from the static library).
*
* mmipal calls these once during init to register a single callback for
* link-up / IP-configured events. With these stubs, the callback never fires —
* mmwlan_register_link_state_cb (driven by the radio firmware) remains the
* authoritative source of link state, so this only costs us LWIP-level
* notifications (e.g. "DHCP got an address"). HaLowInterface polls
* mmipal_get_ip_config when it needs to know.
*/
#include "lwip/netif.h"
void __attribute__((weak)) netif_set_link_callback(struct netif *netif, netif_status_callback_fn link_callback)
{
(void)netif;
(void)link_callback;
}
void __attribute__((weak)) netif_set_status_callback(struct netif *netif, netif_status_callback_fn status_callback)
{
(void)netif;
(void)status_callback;
}
#endif
Binary file not shown.
-216
View File
@@ -1,216 +0,0 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include "mmbuf.h"
#include "mmosal.h"
#include "mmutils.h"
static const struct mmbuf_ops mmbuf_heap_ops = {.free_mmbuf = mmosal_free};
struct mmbuf *mmbuf_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end)
{
struct mmbuf *mmbuf;
uint8_t *buf;
uint32_t alloc_len = MM_FAST_ROUND_UP(sizeof(*mmbuf), 4) + MM_FAST_ROUND_UP(space_at_start + space_at_end, 4);
mmbuf = (struct mmbuf *)mmosal_malloc(alloc_len);
if (mmbuf == NULL) {
return NULL;
}
/* We zero the buffer as a defensive measure to reduce the likelihood of unintentionally
* leaking information. */
memset((uint8_t *)mmbuf, 0, alloc_len);
buf = ((uint8_t *)mmbuf) + MM_FAST_ROUND_UP(sizeof(*mmbuf), 4);
mmbuf_init(mmbuf, buf, MM_FAST_ROUND_UP(space_at_start + space_at_end, 4), space_at_start, &mmbuf_heap_ops);
return mmbuf;
}
struct mmbuf *mmbuf_make_copy_on_heap(struct mmbuf *original)
{
struct mmbuf *mmbuf;
uint8_t *buf;
uint32_t alloc_len = MM_FAST_ROUND_UP(sizeof(*original), 4) + original->buf_len;
mmbuf = (struct mmbuf *)mmosal_malloc(alloc_len);
if (mmbuf == NULL) {
return NULL;
}
buf = ((uint8_t *)mmbuf) + MM_FAST_ROUND_UP(sizeof(*mmbuf), 4);
mmbuf_init(mmbuf, buf, original->buf_len, original->start_offset, &mmbuf_heap_ops);
mmbuf->data_len = original->data_len;
if (original->data_len) {
memcpy(mmbuf_get_data_start(mmbuf), mmbuf_get_data_start(original), mmbuf_get_data_length(original));
}
return mmbuf;
}
void mmbuf_release(struct mmbuf *mmbuf)
{
if (mmbuf == NULL) {
return;
}
MMOSAL_ASSERT(mmbuf->ops != NULL && mmbuf->ops->free_mmbuf != NULL);
mmbuf->ops->free_mmbuf(mmbuf);
}
#ifdef MMBUF_SANITY
static void mmbuf_list_sanity_check(struct mmbuf_list *list)
{
unsigned cnt = 0;
struct mmbuf *walk;
struct mmbuf *prev = NULL;
for (walk = list->head; walk != NULL; walk = walk->next) {
cnt++;
prev = walk;
}
MMOSAL_ASSERT(cnt == list->len);
MMOSAL_ASSERT(prev == list->tail);
}
#endif
void mmbuf_list_prepend(struct mmbuf_list *list, struct mmbuf *mmbuf)
{
mmbuf->next = list->head;
list->head = mmbuf;
list->len++;
if (list->tail == NULL) {
list->tail = list->head;
}
#ifdef MMBUF_SANITY
mmbuf_list_sanity_check(list);
#endif
}
void mmbuf_list_append(struct mmbuf_list *list, struct mmbuf *mmbuf)
{
mmbuf->next = NULL;
if (list->head == NULL) {
list->head = mmbuf;
list->tail = mmbuf;
} else {
list->tail->next = mmbuf;
list->tail = mmbuf;
}
list->len++;
#ifdef MMBUF_SANITY
mmbuf_list_sanity_check(list);
#endif
}
static struct mmbuf *mmbuf_find_prev(struct mmbuf_list *list, struct mmbuf *mmbuf)
{
struct mmbuf *walk, *next;
for (walk = list->head, next = walk->next; next != NULL; walk = next, next = walk->next) {
if (next == mmbuf) {
return walk;
}
}
return NULL;
}
bool mmbuf_list_remove(struct mmbuf_list *list, struct mmbuf *mmbuf)
{
struct mmbuf *prev = NULL;
if (list->head == NULL) {
return false;
}
if (list->head == mmbuf) {
list->head = mmbuf->next;
} else {
prev = mmbuf_find_prev(list, mmbuf);
if (prev == NULL) {
return false;
}
prev->next = mmbuf->next;
}
if (list->tail == mmbuf) {
list->tail = prev;
}
list->len--;
mmbuf->next = NULL;
#ifdef MMBUF_SANITY
mmbuf_list_sanity_check(list);
#endif
return true;
}
struct mmbuf *mmbuf_list_dequeue(struct mmbuf_list *list)
{
if (list->head == NULL) {
return NULL;
} else {
struct mmbuf *mmbuf = list->head;
list->head = mmbuf->next;
list->len--;
if (list->tail == mmbuf) {
list->tail = NULL;
}
#ifdef MMBUF_SANITY
mmbuf_list_sanity_check(list);
#endif
if (mmbuf != NULL) {
mmbuf->next = NULL;
}
return mmbuf;
}
}
struct mmbuf *mmbuf_list_dequeue_tail(struct mmbuf_list *list)
{
if (list->tail == NULL) {
return NULL;
}
struct mmbuf *mmbuf = list->tail;
mmbuf_list_remove(list, mmbuf);
return mmbuf;
}
void mmbuf_list_clear(struct mmbuf_list *list)
{
struct mmbuf *walk;
struct mmbuf *next;
#ifdef MMBUF_SANITY
mmbuf_list_sanity_check(list);
#endif
if (list->head == NULL) {
return;
}
for (walk = list->head, next = walk->next; walk != NULL; walk = next, next = walk ? walk->next : NULL) {
mmbuf_release(walk);
}
list->len = 0;
list->head = NULL;
list->tail = NULL;
}
#endif /* USE_MM_IOT_ESP32 */
-457
View File
@@ -1,457 +0,0 @@
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMBUF Morse Micro Buffer (mmbuf) API
*
* This API provides support for buffers tailored towards packet-like data that has
* headers and trailers that are applied at subsequent layers.
*
* It is designed to support various backends for memory allocation. The default
* is allocation on the heap, but other methods could be used due to the flexible API.
*
* @{
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "mmosal.h"
#ifdef __cplusplus
extern "C" {
#endif
struct mmbuf_ops;
/**
* Core mmbuf data structure.
*
* @note The contents of this data structure should never need to be accessed directly. Rather
* the various functions provided as part of this API should be used.
*
* @code
* +----------------------------------------------------------+
* | RESERVED | Data | RESERVED |
* +----------------------------------------------------------+
* ^ ^ ^ ^
* | | | |
* | |<-----------data_len--------->| |
* | start_offset |
* | |
* |<-----------------------buf_len-------------------------->|
* buf
* @endcode
*/
struct mmbuf {
/** The buffer where data is stored. */
uint8_t *buf;
/** Length of the buffer. */
uint32_t buf_len;
/** Offset where actual data starts in the buffer. */
uint32_t start_offset;
/** Length of actual data in the buffer. */
uint32_t data_len;
/** Reference to operations data structure for this mmbuf. */
const struct mmbuf_ops *ops;
/** Pointer that can be used to construct linked lists. */
struct mmbuf *volatile next;
};
/** Operations data structure for mmbuf. */
struct mmbuf_ops {
/** Free the given mmbuf. */
void (*free_mmbuf)(void *mmbuf);
};
/**
* Initialize an mmbuf header with the given values.
*
* @param mmbuf mmbuf to initialize.
* @param buf Pointer to buffer.
* @param buf_len Length of @p buf.
* @param data_start_offset Initial value for @c start_offset.
* @param ops Operations data structure.
*/
static inline void mmbuf_init(struct mmbuf *mmbuf, uint8_t *buf, uint32_t buf_len, uint32_t data_start_offset,
const struct mmbuf_ops *ops)
{
memset(mmbuf, 0, sizeof(*mmbuf));
mmbuf->buf = buf;
mmbuf->buf_len = buf_len;
mmbuf->start_offset = data_start_offset;
mmbuf->ops = ops;
}
/**
* Allocate a new mmbuf on the heap (using @ref mmosal_malloc()).
*
* @param space_at_start Amount of space to reserve at start of buffer.
* @param space_at_end Amount of space to reserve at end of buffer.
*
* @note @c start_offset will be set to @p space_at_start, and @c buf_len will be the sum
* of @p space_at_start and @p space_at_end (rounded up to a multiple of 4).
*
* @returns newly allocated mmbuf on success or @c NULL on failure.
*/
struct mmbuf *mmbuf_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end);
/**
* Make a copy of the given mmbuf. Note that regardless of the backend that allocated
* @p original, the newly allocated mmbuf will be allocated on the heap using
* @ref mmbuf_alloc_on_heap().
*
* @param original mmbuf to copy.
*
* @returns newly allocated mmbuf on success or @c NULL on failure.
*/
struct mmbuf *mmbuf_make_copy_on_heap(struct mmbuf *original);
/**
* Release a reference to the given mmbuf. If this was the last reference (@c addition_ref_cnt
* was 0) then the mmbuf will be freed using the appropriate op callback.
*
* @param mmbuf The mmbuf to release reference to. May be @c NULL.
*/
void mmbuf_release(struct mmbuf *mmbuf);
/**
* Gets a pointer to the start of the data in the mmbuf.
*
* @param mmbuf The mmbuf to operate on.
*
* @returns a pointer to the start of the data in the mmbuf.
*/
static inline uint8_t *mmbuf_get_data_start(struct mmbuf *mmbuf)
{
return mmbuf->buf + mmbuf->start_offset;
}
/**
* Gets a pointer to the end of the data in the mmbuf.
*
* @param mmbuf The mmbuf to operate on.
*
* @returns a pointer to the end of the data in the mmbuf.
*/
static inline uint8_t *mmbuf_get_data_end(struct mmbuf *mmbuf)
{
return mmbuf->buf + mmbuf->start_offset + mmbuf->data_len;
}
/**
* Gets the length of the data currently in the mmbuf.
*
* @param mmbuf The mmbuf to operate on.
*
* @returns the length of the data currently in the mmbuf (note that this is different from the
* length of the available buffer space).
*/
static inline uint32_t mmbuf_get_data_length(struct mmbuf *mmbuf)
{
return mmbuf->data_len;
}
/**
* Returns the amount of space available for prepending to the data in the buffer.
*
* @param mmbuf The mmbuf to operate on.
*
* @returns the available space in bytes.
*/
static inline uint32_t mmbuf_available_space_at_start(struct mmbuf *mmbuf)
{
return mmbuf->start_offset;
}
/**
* Returns the amount of space available for appending to the data in the buffer.
*
* @param mmbuf The mmbuf to operate on.
*
* @returns the available space in bytes.
*/
static inline uint32_t mmbuf_available_space_at_end(struct mmbuf *mmbuf)
{
return mmbuf->buf_len - (mmbuf->start_offset + mmbuf->data_len);
}
/**
* Reserves space immediately before the data currently in the given mmbuf and returns
* a pointer to this space.
*
* For a function that also copies data in, see @ref mmbuf_prepend_data().
*
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start().
*
* @param mmbuf The mmbuf to operate on.
* @param len Length of data to be prepended.
*
* @returns a pointer to the place in the buffer where the data should be put.
*/
static inline uint8_t *mmbuf_prepend(struct mmbuf *mmbuf, uint32_t len)
{
MMOSAL_ASSERT(len <= mmbuf_available_space_at_start(mmbuf));
mmbuf->start_offset -= len;
mmbuf->data_len += len;
return mmbuf->buf + mmbuf->start_offset;
}
/**
* Prepends the given data to the data already in the mmbuf.
*
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start().
*
* @warning The memory area pointed to by data must not overlap with the mmbuf data.
*
* @param mmbuf The mmbuf to operate on.
* @param data The data to be prepended.
* @param len Length of data to be prepended.
*/
static inline void mmbuf_prepend_data(struct mmbuf *mmbuf, const uint8_t *data, uint32_t len)
{
uint8_t *dest = mmbuf_prepend(mmbuf, len);
memcpy(dest, data, len);
}
/**
* Reserves space immediately after the data currently in the given mmbuf and returns
* a pointer to this space.
*
* For a function that also copies data in, see @ref mmbuf_append_data().
*
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_end().
*
* @param mmbuf The mmbuf to operate on.
* @param len Length of data to be append.
*
* @returns a pointer to the place in the buffer where the data should be put.
*/
static inline uint8_t *mmbuf_append(struct mmbuf *mmbuf, uint32_t len)
{
uint8_t *ret = mmbuf_get_data_end(mmbuf);
MMOSAL_ASSERT(len <= mmbuf_available_space_at_end(mmbuf));
mmbuf->data_len += len;
return ret;
}
/**
* Appends the given data to the data already in the mmbuf.
*
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start().
*
* @param mmbuf The mmbuf to operate on.
* @param data The data to be prepended.
* @param len Length of data to be prepended.
*/
static inline void mmbuf_append_data(struct mmbuf *mmbuf, const uint8_t *data, uint32_t len)
{
uint8_t *dest = mmbuf_append(mmbuf, len);
memcpy(dest, data, len);
}
/**
* Remove data from the start of the mmbuf.
*
* @param mmbuf mmbuf to operate on.
* @param len Length of data to remove.
*
* @returns a pointer to the removed data or NULL if the mmbuf data length was less than @p len.
*/
static inline uint8_t *mmbuf_remove_from_start(struct mmbuf *mmbuf, uint32_t len)
{
uint8_t *ret;
if (mmbuf_get_data_length(mmbuf) < len) {
return NULL;
}
ret = mmbuf_get_data_start(mmbuf);
mmbuf->start_offset += len;
mmbuf->data_len -= len;
return ret;
}
/**
* Remove data from the end of the mmbuf.
*
* @param mmbuf mmbuf to operate on.
* @param len Length of data to remove.
*
* @returns a pointer to the removed data or NULL if the mmbuf data length was less than @p len.
*/
static inline uint8_t *mmbuf_remove_from_end(struct mmbuf *mmbuf, uint32_t len)
{
uint8_t *ret;
if (mmbuf_get_data_length(mmbuf) < len) {
return NULL;
}
ret = mmbuf_get_data_end(mmbuf) - len;
mmbuf->data_len -= len;
return ret;
}
/**
* Truncate the mmbuf data to the given length.
*
* @param mmbuf mmbuf to operate on.
* @param len New data length. (Must be less than or equal to the data length
* of the mmbuf).
*/
static inline void mmbuf_truncate(struct mmbuf *mmbuf, uint32_t len)
{
MMOSAL_ASSERT(len <= mmbuf->data_len);
mmbuf->data_len = len;
}
/* --------------------------------------------------------------------------------------------- */
/** Structure that can be used as the head of a linked list of mmbufs that counts its length. */
struct mmbuf_list {
/** First mmbuf in the list. */
struct mmbuf *volatile head;
/** Last mmbuf in the list. */
struct mmbuf *volatile tail;
/** Length of the list. */
volatile uint32_t len;
};
/** Static initializer for @ref mmbuf_list. */
#define MMBUF_LIST_INIT \
{ \
NULL, NULL, 0 \
}
/**
* Initialization function for @ref mmbuf_list, for cases where @c MMBUF_LIST_INIT
* cannot be used.
*
* @param list The mmbuf_list to init.
*/
static inline void mmbuf_list_init(struct mmbuf_list *list)
{
list->head = NULL;
list->tail = NULL;
list->len = 0;
}
/**
* Add an mmbuf to the start of an mmbuf list.
*
* @param list The list to prepend to.
* @param mmbuf The mmbuf to prepend.
*/
void mmbuf_list_prepend(struct mmbuf_list *list, struct mmbuf *mmbuf);
/**
* Add an mmbuf to the end of an mmbuf list.
*
* @param list The list to append to.
* @param mmbuf The mmbuf to append.
*/
void mmbuf_list_append(struct mmbuf_list *list, struct mmbuf *mmbuf);
/**
* Remove an mmbuf from an mmbuf list.
*
* @param list The list to remove from.
* @param mmbuf The mmbuf to remove.
*
* @returns @c true if the given @c mmbuf was present in @c list, else @c fase.
*/
bool mmbuf_list_remove(struct mmbuf_list *list, struct mmbuf *mmbuf);
/**
* Remove the mmbuf at the head of the list and return it.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmbuf, or @c NULL if the list is empty.
*/
struct mmbuf *mmbuf_list_dequeue(struct mmbuf_list *list);
/**
* Remove the mmbuf at the tail of the list and return it.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmbuf, or @c NULL if the list is empty.
*/
struct mmbuf *mmbuf_list_dequeue_tail(struct mmbuf_list *list);
/**
* Remove all mmbufs from the list and return as a linked list.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmbufs, or @c NULL if the list is empty.
*/
static inline struct mmbuf *mmbuf_list_dequeue_all(struct mmbuf_list *list)
{
struct mmbuf *head = list->head;
list->head = NULL;
list->tail = NULL;
list->len = 0;
return head;
}
/**
* Checks whether the given mmbuf list is empty.
*
* @param list The list to check.
*
* @returns @c true if the list is empty, else @c false.
*/
static inline bool mmbuf_list_is_empty(struct mmbuf_list *list)
{
return (list->head == NULL);
}
/**
* Returns the head of the mmbuf list.
*
* @param list The list to peek into.
*
* @returns the mmbuf at the head of the list.
*/
static inline struct mmbuf *mmbuf_list_peek(struct mmbuf_list *list)
{
return list->head;
}
/**
* Returns the tail of the mmbuf list.
*
* @param list The list to peek into.
*
* @returns the mmbuf at the tail of the list.
*/
static inline struct mmbuf *mmbuf_list_peek_tail(struct mmbuf_list *list)
{
return list->tail;
}
/**
* Free all the packets in the given list and reset the list to empty state.
*
* @param list The list to clear.
*/
void mmbuf_list_clear(struct mmbuf_list *list);
#ifdef __cplusplus
}
#endif
/** @} */
-50
View File
@@ -1,50 +0,0 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mmcrc.h"
/**
* Static table used for the table_driven implementation.
*/
static const uint16_t crc16_xmodem_lookup_table[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce,
0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c,
0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee,
0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e,
0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,
0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae,
0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e,
0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c,
0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e,
0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e,
0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c,
0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e,
0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1,
0x1ef0};
/**
* @note This implementation(with a few modifications) and corresponding table was generated using
* pycrc v0.9.2 (MIT) using the XMODEM model. https://pycrc.org/. The code generated by pycrc
* is not considered a substantial portion of the software, therefore the licence does not
* cover the generated code, and the author of pycrc will not claim any copyright on the
* generated code (https://pypi.org/project/pycrc/0.9.2/).
*/
uint16_t mmcrc_16_xmodem(uint16_t crc, const void *data, size_t data_len)
{
const uint8_t *d = (const uint8_t *)data;
while (data_len--) {
crc = (crc16_xmodem_lookup_table[((crc >> 8) ^ *d++)] ^ (crc << 8));
}
return crc;
}
#endif /* USE_MM_IOT_ESP32 */
-40
View File
@@ -1,40 +0,0 @@
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMCRC Morse Micro Cyclic Redundancy Check (mmcrc) API
*
* This API provides support for CRC algorithms used by Morse Micro code.
*
* @{
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Compute the CRC-16 for the data buffer using the XMODEM model.
*
* @param crc Seed for CRC calc, zero in most cases this is zero (0). If chaining calls
* then this is the output from the previous invocation.
* @param data Pointer to the start of the data to calculate the crc over.
* @param data_len Length of the data array in bytes.
*
* @return Returns the CRC value.
*/
uint16_t mmcrc_16_xmodem(uint16_t crc, const void *data, size_t data_len);
#ifdef __cplusplus
}
#endif
/** @} */
-100
View File
@@ -1,100 +0,0 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mmhal.h"
#include "mmosal.h"
#include "mmutils.h"
#include "mmwlan.h"
#include "driver/gpio.h"
#include "esp_random.h"
#include "esp_system.h"
#include "sdkconfig.h"
void mmhal_init(void)
{
/* We initialise the MM_RESET_N Pin here so that we can hold the MM6108 in reset regardless of
* whether the mmhal_wlan_init/deinit function have been called. This allows us to ensure the
* chip is in its lowest power state. You may want to revise this depending on your particular
* hardware configuration. */
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1 << CONFIG_MM_RESET_N);
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
gpio_set_level(CONFIG_MM_RESET_N, 0);
/* Initialise the gpio ISR handler service. This allows per-pin GPIO interrupt handlers and is
* what is used to register all the wlan related interrupt. */
gpio_install_isr_service(0);
}
void mmhal_log_write(const uint8_t *data, size_t length)
{
while (length--) {
putc(*data++, stdout);
}
}
void mmhal_log_flush(void) {}
void mmhal_read_mac_addr(uint8_t *mac_addr)
{
/* We do not override the MAC address here. Therefore the driver will attempt to read it from
* the chip and failing that will assign a randomly generated address. */
(void)(mac_addr);
}
uint32_t mmhal_random_u32(uint32_t min, uint32_t max)
{
/* Note: the below implementation does not guarantee a uniform distribution. */
uint32_t random_value = esp_random();
if (min == 0 && max == UINT32_MAX) {
return random_value;
} else {
/* Calculate the range and shift required to fit within [min, max] */
return (random_value % (max - min + 1)) + min;
}
}
void mmhal_reset(void)
{
esp_restart();
while (1) {
}
}
void mmhal_set_deep_sleep_veto(uint8_t veto_id)
{
MM_UNUSED(veto_id);
}
void mmhal_clear_deep_sleep_veto(uint8_t veto_id)
{
MM_UNUSED(veto_id);
}
void mmhal_set_led(uint8_t led, uint8_t level)
{
MM_UNUSED(led);
MM_UNUSED(level);
}
bool mmhal_get_hardware_version(char *version_buffer, size_t version_buffer_length)
{
/* Note: You need to identify the correct hardware and or version
* here using whatever means available (GPIO's, version number stored in EEPROM, etc)
* and return the correct string here. */
return !mmosal_safer_strcpy(version_buffer, "MM-ESP32S3 V1.0", version_buffer_length);
}
#endif /* USE_MM_IOT_ESP32 */
-72
View File
@@ -1,72 +0,0 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mmhal_wlan.h"
#include "mmosal.h"
/*
* ---------------------------------------------------------------------------------------------
* BCF Retrieval
* ---------------------------------------------------------------------------------------------
*/
/*
* The following implementation reads the BCF File from the config store.
*/
void mmhal_wlan_read_bcf_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf)
{
/** Points to the start of the BCF binary image. Defined as part of the Makefile */
extern uint8_t bcf_binary_start;
/** Points to the end of the BCF binary image. Defined as part of the Makefile */
extern uint8_t bcf_binary_end;
size_t bcf_len = &bcf_binary_end - &bcf_binary_start;
/* Initialise robuf */
robuf->buf = NULL;
robuf->len = 0;
robuf->free_arg = NULL;
robuf->free_cb = NULL;
/* Sanity check */
if (bcf_len < offset) {
printf("Detected an attempt to start reading off the end of the bcf file.\n");
return;
}
robuf->buf = (uint8_t *)&bcf_binary_start + offset;
robuf->len = bcf_len - offset;
robuf->len = (robuf->len < requested_len) ? robuf->len : requested_len;
}
/*
* ---------------------------------------------------------------------------------------------
* Firmware Retrieval
* ---------------------------------------------------------------------------------------------
*/
/** Points to the start of the firmware binary image. Defined as part of the Makefile */
extern uint8_t firmware_binary_start;
/** Points to the end of the firmware binary image. Defined as part of the Makefile */
extern uint8_t firmware_binary_end;
void mmhal_wlan_read_fw_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf)
{
uint32_t firmware_len = &firmware_binary_end - &firmware_binary_start;
if (offset > firmware_len) {
printf("Detected an attempt to start read off the end of the firmware file.\n");
robuf->buf = NULL;
return;
}
robuf->buf = (&firmware_binary_start + offset);
firmware_len -= offset;
robuf->len = (firmware_len < requested_len) ? firmware_len : requested_len;
}
#endif /* USE_MM_IOT_ESP32 */
-726
View File
@@ -1,726 +0,0 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mmipal.h"
#include "mmnetif.h"
#include "mmosal.h"
#include "mmutils.h"
#include "mmwlan.h"
#include "lwip/api.h"
#include "lwip/autoip.h"
#include "lwip/def.h"
#include "lwip/dhcp.h"
#include "lwip/dhcp6.h"
#include "lwip/dns.h"
#include "lwip/etharp.h"
#include "lwip/ethip6.h"
#include "lwip/igmp.h"
#include "lwip/inet.h"
#include "lwip/ip4_frag.h"
#include "lwip/ip6_frag.h"
#include "lwip/ip_addr.h"
#include "lwip/mem.h"
#include "lwip/sockets.h"
#include "lwip/stats.h"
#include "lwip/tcp.h"
#include "lwip/tcpip.h"
#include "lwip/udp.h"
static struct mmipal_data {
struct netif lwip_mmnetif;
/** This stores the IPv4 link state for the IP stack. I.e., do we have an IP address or not. */
enum mmipal_link_state ip_link_state;
/** Flag requesting ARP response offload feature */
bool offload_arp_response;
/** ARP refresh offload interval in seconds */
uint32_t offload_arp_refresh_s;
bool dhcp_offload_init_complete;
/** The link status callback function that has been registered. */
mmipal_link_status_cb_fn_t link_status_callback;
/** The extended link status callback function that has been registered. */
mmipal_ext_link_status_cb_fn_t ext_link_status_callback;
/** Argument for the extended link status callback function that has been registered. */
void *ext_link_status_callback_arg;
#if LWIP_IPV4
enum mmipal_addr_mode ip4_mode;
#endif
#if LWIP_IPV6
enum mmipal_ip6_addr_mode ip6_mode;
#endif
} mmipal_data = {};
/** Getter function to retrieve the global mmipal data structure.*/
static inline struct mmipal_data *mmipal_get_data(void)
{
return &mmipal_data;
}
static void netif_status_callback(struct netif *netif);
#if LWIP_IPV4
/**
* DHCP Lease update callback, invoked when we get a new DHCP lease.
*
* @param lease_info The new DHCP lease.
*/
static void mmipal_dhcp_lease_updated(const struct mmwlan_dhcp_lease_info *lease_info, void *arg)
{
struct mmipal_data *data = mmipal_get_data();
ip4_addr_t ip_addr, netmask, gateway;
ip_addr_t dns_addr = ip_addr_any;
MM_UNUSED(arg);
data->dhcp_offload_init_complete = true;
ip4_addr_set_u32(&ip_addr, lease_info->ip4_addr);
ip4_addr_set_u32(&netmask, lease_info->mask4_addr);
ip4_addr_set_u32(&gateway, lease_info->gw4_addr);
ip4_addr_set_u32(ip_2_ip4(&dns_addr), lease_info->dns4_addr);
LOCK_TCPIP_CORE();
netif_set_addr(&data->lwip_mmnetif, &ip_addr, &netmask, &gateway);
dns_setserver(0, &dns_addr);
UNLOCK_TCPIP_CORE();
netif_status_callback(&data->lwip_mmnetif);
}
enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config)
{
struct mmipal_data *data = mmipal_get_data();
char *result;
config->mode = data->ip4_mode;
result = ipaddr_ntoa_r(&data->lwip_mmnetif.ip_addr, config->ip_addr, sizeof(config->ip_addr));
LWIP_ASSERT("IP buf too short", result != NULL);
result = ipaddr_ntoa_r(&data->lwip_mmnetif.netmask, config->netmask, sizeof(config->netmask));
LWIP_ASSERT("IP buf too short", result != NULL);
result = ipaddr_ntoa_r(&data->lwip_mmnetif.gw, config->gateway_addr, sizeof(config->gateway_addr));
LWIP_ASSERT("IP buf too short", result != NULL);
return MMIPAL_SUCCESS;
}
enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config)
{
struct mmipal_data *data = mmipal_get_data();
int result;
ip_addr_t ip_addr = ip_addr_any;
ip_addr_t netmask = ip_addr_any;
ip_addr_t gateway = ip_addr_any;
struct netif *netif = &data->lwip_mmnetif;
if (config->mode != MMIPAL_DHCP_OFFLOAD && data->ip4_mode == MMIPAL_DHCP_OFFLOAD) {
printf("Once enabled DHCP offload mode cannot be disabled\n");
return MMIPAL_NOT_SUPPORTED;
}
switch (config->mode) {
case MMIPAL_DISABLED:
printf("%s mode not supported\n", "DISABLED");
return MMIPAL_INVALID_ARGUMENT;
case MMIPAL_AUTOIP:
printf("%s mode not supported\n", "AutoIP");
return MMIPAL_INVALID_ARGUMENT;
case MMIPAL_DHCP_OFFLOAD:
/* Currently we only support enabling DHCP offload when initialising */
printf("%s mode not supported\n", "DHCP_OFFLOAD");
return MMIPAL_INVALID_ARGUMENT;
case MMIPAL_STATIC:
result = ipaddr_aton(config->ip_addr, &ip_addr);
if (!result) {
return MMIPAL_INVALID_ARGUMENT;
}
result = ipaddr_aton(config->netmask, &netmask);
if (!result) {
return MMIPAL_INVALID_ARGUMENT;
}
result = ipaddr_aton(config->gateway_addr, &gateway);
if (!result) {
return MMIPAL_INVALID_ARGUMENT;
}
break;
case MMIPAL_DHCP:
break;
}
LOCK_TCPIP_CORE();
if (config->mode != MMIPAL_DHCP && data->ip4_mode == MMIPAL_DHCP) {
/* Stop DHCP if it was started earlier before setting static IP */
dhcp_stop(netif);
}
data->ip4_mode = config->mode;
netif_set_addr(netif, ip_2_ip4(&ip_addr), ip_2_ip4(&netmask), ip_2_ip4(&gateway));
if (data->ip4_mode == MMIPAL_DHCP) {
result = dhcp_start(netif);
LWIP_ASSERT("DHCP start error", result == ERR_OK);
}
UNLOCK_TCPIP_CORE();
return MMIPAL_SUCCESS;
}
enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr)
{
struct mmipal_data *data = mmipal_get_data();
char *result;
uint32_t ip_addr = ip_addr_get_ip4_u32(&data->lwip_mmnetif.ip_addr);
uint32_t netmask = ip_addr_get_ip4_u32(&data->lwip_mmnetif.netmask);
uint32_t broadcast_u32 = (ip_addr & netmask) | (0xffffffff & ~netmask);
ip_addr_t broadcast_ip_addr;
ip_addr_t *_broadcast_ip_addr = &broadcast_ip_addr;
ip_addr_set_ip4_u32(_broadcast_ip_addr, broadcast_u32);
result = ipaddr_ntoa_r(&broadcast_ip_addr, broadcast_addr, MMIPAL_IPADDR_STR_MAXLEN);
LWIP_ASSERT("IP buf too short", result != NULL);
return MMIPAL_SUCCESS;
}
#else
enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config)
{
MM_UNUSED(config);
LWIP_ASSERT("IPv4 not enabled", false);
return MMIPAL_NOT_SUPPORTED;
}
enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config)
{
MM_UNUSED(config);
LWIP_ASSERT("IPv4 not enabled", false);
return MMIPAL_NOT_SUPPORTED;
}
enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr)
{
MM_UNUSED(broadcast_addr);
LWIP_ASSERT("IPv4 not enabled", false);
return MMIPAL_NOT_SUPPORTED;
}
#endif
#if LWIP_IPV6
enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config)
{
struct mmipal_data *data = mmipal_get_data();
unsigned ii;
struct netif *netif = &data->lwip_mmnetif;
if (config == NULL) {
return MMIPAL_INVALID_ARGUMENT;
}
config->ip6_mode = data->ip6_mode;
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
char *result;
const ip_addr_t *addr = &ip6_addr_any;
if (ip6_addr_isvalid(netif_ip6_addr_state(netif, ii))) {
addr = &data->lwip_mmnetif.ip6_addr[ii];
}
result = ipaddr_ntoa_r(addr, config->ip6_addr[ii], sizeof(config->ip6_addr[ii]));
LWIP_ASSERT("IP buf too short", result != NULL);
}
return MMIPAL_SUCCESS;
}
enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config)
{
struct mmipal_data *data = mmipal_get_data();
struct netif *netif = &data->lwip_mmnetif;
err_t result;
unsigned ii;
ip_addr_t ip6_addr[LWIP_IPV6_NUM_ADDRESSES];
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
int result = ipaddr_aton(config->ip6_addr[ii], &ip6_addr[ii]);
if (!result) {
return MMIPAL_INVALID_ARGUMENT;
}
}
LOCK_TCPIP_CORE();
if (config->ip6_mode == MMIPAL_IP6_STATIC) {
if (data->ip6_mode != MMIPAL_IP6_STATIC) {
#if LWIP_IPV6_DHCP6
dhcp6_disable(netif);
#endif
netif_set_ip6_autoconfig_enabled(netif, 0);
data->ip6_mode = MMIPAL_IP6_STATIC;
}
if (!ip6_addr_islinklocal(ip_2_ip6(&(ip6_addr[0])))) {
printf("First address must be linklocal address (address start with fe80)\n");
}
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
if (ip_addr_isany_val(ip6_addr[ii])) {
netif_ip6_addr_set(netif, ii, IP6_ADDR_ANY6);
netif_ip6_addr_set_state(netif, ii, IP6_ADDR_INVALID);
} else {
netif_ip6_addr_set(netif, ii, ip_2_ip6(&(ip6_addr[ii])));
netif_ip6_addr_set_state(netif, ii, IP6_ADDR_TENTATIVE);
netif_ip6_addr_set_valid_life(netif, ii, IP6_ADDR_LIFE_STATIC);
}
}
} else {
if (data->ip6_mode == MMIPAL_IP6_STATIC) {
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
netif_ip6_addr_set(netif, ii, IP6_ADDR_ANY6);
netif_ip6_addr_set_state(netif, ii, IP6_ADDR_INVALID);
}
}
netif_set_ip6_autoconfig_enabled(netif, 1);
netif_create_ip6_linklocal_address(netif, 1);
data->ip6_mode = MMIPAL_IP6_AUTOCONFIG;
}
if (config->ip6_mode == MMIPAL_IP6_DHCP6_STATELESS)
#if LWIP_IPV6_DHCP6
{
result = dhcp6_enable_stateless(netif);
LWIP_ASSERT("Stateless DHCP6 start error", result == ERR_OK);
data->ip6_mode = MMIPAL_IP6_DHCP6_STATELESS;
} else {
dhcp6_disable(netif);
}
#else
{
printf("LWIP_IPV6_DHCP6 is not enabled\n");
}
#endif
UNLOCK_TCPIP_CORE();
return MMIPAL_SUCCESS;
}
#else
enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config)
{
MM_UNUSED(config);
LWIP_ASSERT("IPv6 not enabled", false);
return MMIPAL_NOT_SUPPORTED;
}
enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config)
{
MM_UNUSED(config);
LWIP_ASSERT("IPv6 not enabled", false);
return MMIPAL_NOT_SUPPORTED;
}
#endif
static bool mmipal_link_status_check(struct netif *netif)
{
bool ip4_addr_check = true;
#if LWIP_IPV4
ip4_addr_check = !ip_addr_isany(&(netif->ip_addr));
#endif
return ip4_addr_check && netif_is_link_up(netif);
}
/** Handler for @c netif status callbacks from LWIP. */
static void netif_status_callback(struct netif *netif)
{
struct mmipal_data *data = mmipal_get_data();
enum mmipal_link_state new_link_state = MMIPAL_LINK_DOWN;
#if LWIP_IPV4
if (data->ip4_mode == MMIPAL_DHCP_OFFLOAD) {
/* Initialize DHCP offload on link up */
if (mmwlan_enable_dhcp_offload(mmipal_dhcp_lease_updated, NULL) != MMWLAN_SUCCESS) {
printf("Failed to enable DHCP offload!\n");
}
if (!data->dhcp_offload_init_complete) {
/* This just prevents a spurious 'Link Up' message on very first call */
return;
}
}
#endif
if (mmipal_link_status_check(netif)) {
new_link_state = MMIPAL_LINK_UP;
}
if (data->ip_link_state != new_link_state) {
data->ip_link_state = new_link_state;
if (data->link_status_callback || data->ext_link_status_callback) {
struct mmipal_link_status link_status;
memset(&link_status, 0, sizeof(link_status));
link_status.link_state = data->ip_link_state;
#if LWIP_IPV4
char *result = ipaddr_ntoa_r(&netif->ip_addr, link_status.ip_addr, sizeof(link_status.ip_addr));
LWIP_ASSERT("IP buf too short", result != NULL);
result = ipaddr_ntoa_r(&netif->netmask, link_status.netmask, sizeof(link_status.netmask));
LWIP_ASSERT("IP buf too short", result != NULL);
result = ipaddr_ntoa_r(&netif->gw, link_status.gateway, sizeof(link_status.gateway));
LWIP_ASSERT("IP buf too short", result != NULL);
if (data->ip_link_state == MMIPAL_LINK_UP) {
/* Check if ARP response offload feature is enabled */
if (data->offload_arp_response) {
mmwlan_enable_arp_response_offload(ip4_addr_get_u32(netif_ip4_addr(netif)));
}
/* Check if ARP refresh offload feature is enabled */
if (data->offload_arp_refresh_s > 0) {
mmwlan_enable_arp_refresh_offload(data->offload_arp_refresh_s, ip4_addr_get_u32(netif_ip4_gw(netif)), true);
}
}
#endif
if (data->link_status_callback) {
data->link_status_callback(&link_status);
}
if (data->ext_link_status_callback) {
data->ext_link_status_callback(&link_status, data->ext_link_status_callback_arg);
}
}
}
}
void mmipal_set_link_status_callback(mmipal_link_status_cb_fn_t fn)
{
struct mmipal_data *data = mmipal_get_data();
data->link_status_callback = fn;
}
void mmipal_set_ext_link_status_callback(mmipal_ext_link_status_cb_fn_t fn, void *arg)
{
struct mmipal_data *data = mmipal_get_data();
data->ext_link_status_callback = fn;
data->ext_link_status_callback_arg = arg;
}
static volatile bool tcpip_init_done = false;
struct lwip_init_args {
enum mmipal_addr_mode mode;
enum mmipal_ip6_addr_mode ip6_mode;
ip_addr_t ip_addr;
ip_addr_t netmask;
ip_addr_t gateway_addr;
ip_addr_t ip6_addr;
};
static void tcpip_init_done_handler(void *arg)
{
struct mmipal_data *data = mmipal_get_data();
struct netif *netif = &data->lwip_mmnetif;
struct lwip_init_args *args = (struct lwip_init_args *)arg;
netif_add_noaddr(netif, NULL, mmnetif_init, tcpip_input);
netif_set_default(netif);
netif_set_up(netif);
#if LWIP_IPV4
err_t result;
data->ip4_mode = args->mode;
if (args->mode == MMIPAL_DHCP) {
result = dhcp_start(netif);
LWIP_ASSERT("DHCP start error", result == ERR_OK);
} else if (args->mode == MMIPAL_STATIC) {
netif_set_addr(netif, ip_2_ip4(&(args->ip_addr)), ip_2_ip4(&(args->netmask)), ip_2_ip4(&(args->gateway_addr)));
}
#endif
netif_set_link_callback(netif, netif_status_callback);
netif_set_status_callback(netif, netif_status_callback);
#if LWIP_IPV6
err_t result6;
data->ip6_mode = args->ip6_mode;
if (args->ip6_mode == MMIPAL_IP6_STATIC) {
netif_ip6_addr_set(netif, 0, ip_2_ip6(&(args->ip6_addr)));
netif_ip6_addr_set_state(netif, 0, IP6_ADDR_TENTATIVE);
} else if (data->ip6_mode == MMIPAL_IP6_AUTOCONFIG) {
netif_set_ip6_autoconfig_enabled(netif, 1);
netif_create_ip6_linklocal_address(netif, 1);
} else if (data->ip6_mode == MMIPAL_IP6_DHCP6_STATELESS)
#if LWIP_IPV6_DHCP6
{
result6 = dhcp6_enable_stateless(netif);
LWIP_ASSERT("Stateless DHCP6 start error", result6 == ERR_OK);
}
#else
{
printf("LWIP_IPV6_DHCP6 is not enabled\n");
}
#endif
#endif
mmosal_free(args);
tcpip_init_done = true;
}
enum mmipal_status mmipal_init(const struct mmipal_init_args *args)
{
struct mmipal_data *data = mmipal_get_data();
enum mmipal_status status = MMIPAL_INVALID_ARGUMENT;
int result;
struct lwip_init_args *lwip_args = (struct lwip_init_args *)mmosal_malloc(sizeof(*lwip_args));
if (lwip_args == NULL) {
printf("malloc failure\n");
return MMIPAL_NO_MEM;
}
memset(lwip_args, 0, sizeof(*lwip_args));
lwip_args->mode = args->mode;
lwip_args->ip6_mode = args->ip6_mode;
data->link_status_callback = NULL;
data->offload_arp_response = args->offload_arp_response;
data->offload_arp_refresh_s = args->offload_arp_refresh_s;
/* Validate arguments */
#if LWIP_IPV4
switch (args->mode) {
case MMIPAL_DISABLED:
printf("%s mode not supported\n", "DISABLED");
goto exit;
case MMIPAL_DHCP_OFFLOAD:
case MMIPAL_STATIC:
result = ipaddr_aton(args->ip_addr, &lwip_args->ip_addr);
if (!result) {
goto exit;
}
result = ipaddr_aton(args->netmask, &lwip_args->netmask);
if (!result) {
goto exit;
}
result = ipaddr_aton(args->gateway_addr, &lwip_args->gateway_addr);
if (!result) {
goto exit;
}
if (ip_addr_isany_val(lwip_args->ip_addr)) {
printf("IP address not specified\n");
goto exit;
}
break;
case MMIPAL_DHCP:
if (LWIP_DHCP == 0) {
printf("DHCP not compiled in\n");
goto exit;
}
break;
case MMIPAL_AUTOIP:
printf("%s mode not supported\n", "AutoIP");
break;
}
#endif
#if LWIP_IPV6
switch (args->ip6_mode) {
case MMIPAL_IP6_DISABLED:
break;
case MMIPAL_IP6_STATIC:
result = ipaddr_aton(args->ip6_addr, &lwip_args->ip6_addr);
if (!result) {
goto exit;
}
if (ip_addr_isany_val(lwip_args->ip6_addr)) {
printf("IP address not specified\n");
goto exit;
}
break;
case MMIPAL_IP6_AUTOCONFIG:
if (LWIP_IPV6_AUTOCONFIG == 0) {
printf("AUTOCONFIG not compiled in\n");
goto exit;
}
break;
case MMIPAL_IP6_DHCP6_STATELESS:
if (LWIP_IPV6_DHCP6_STATELESS == 0) {
printf("DHCP6_STATELESS not compiled in\n");
goto exit;
}
break;
}
#endif
tcpip_init(tcpip_init_done_handler, lwip_args);
/* Block until initialisation is complete */
while (!tcpip_init_done) {
mmosal_task_sleep(10);
}
return MMIPAL_SUCCESS;
exit:
mmosal_free(lwip_args);
return status;
}
void mmipal_get_link_packet_counts(uint32_t *tx_packets, uint32_t *rx_packets)
{
#if LWIP_STATS
*tx_packets = lwip_stats.link.xmit;
*rx_packets = lwip_stats.link.recv;
#else
*tx_packets = 0;
*rx_packets = 0;
#endif
}
void mmipal_set_tx_qos_tid(uint8_t tid)
{
struct mmipal_data *data = mmipal_get_data();
bool ok = tcpip_init_done;
MMOSAL_ASSERT(ok);
mmnetif_set_tx_qos_tid(&data->lwip_mmnetif, tid);
}
enum mmipal_link_state mmipal_get_link_state(void)
{
struct mmipal_data *data = mmipal_get_data();
return data->ip_link_state;
}
static enum mmipal_status mmipal_get_local_addr_(ip_addr_t *local_addr, const ip_addr_t *dest_addr)
{
struct mmipal_data *data = mmipal_get_data();
struct netif *netif = &data->lwip_mmnetif;
#if LWIP_IPV6
if (IP_IS_V6(dest_addr)) {
const ip_addr_t *src_addr = ip6_select_source_address(netif, ip_2_ip6(dest_addr));
if (src_addr == NULL) {
return MMIPAL_NO_LINK;
}
ip_addr_copy(*local_addr, *src_addr);
return MMIPAL_SUCCESS;
} else {
MM_UNUSED(dest_addr);
}
#endif
#if LWIP_IPV4
if (IP_IS_V4(dest_addr)) {
ip_addr_copy(*local_addr, netif->ip_addr);
return MMIPAL_SUCCESS;
} else {
MM_UNUSED(dest_addr);
}
#endif
#if !LWIP_IPV4 && !LWIP_IPV6
MM_UNUSED(local_addr);
MM_UNUSED(dest_addr);
#endif
return MMIPAL_INVALID_ARGUMENT;
}
enum mmipal_status mmipal_get_local_addr(mmipal_ip_addr_t local_addr, const mmipal_ip_addr_t dest_addr)
{
ip_addr_t lwip_dest_addr;
ip_addr_t lwip_local_addr;
int ok;
enum mmipal_status status;
if (dest_addr == NULL) {
return MMIPAL_INVALID_ARGUMENT;
}
ok = ipaddr_aton(dest_addr, &lwip_dest_addr);
if (!ok) {
return MMIPAL_INVALID_ARGUMENT;
}
status = mmipal_get_local_addr_(&lwip_local_addr, &lwip_dest_addr);
if (status != 0) {
return status;
}
if (ipaddr_ntoa_r(&lwip_local_addr, local_addr, MMIPAL_IPADDR_STR_MAXLEN) == NULL) {
return MMIPAL_NO_MEM;
} else {
return MMIPAL_SUCCESS;
}
}
enum mmipal_status mmipal_set_dns_server(uint8_t index, const mmipal_ip_addr_t addr)
{
ip_addr_t dns_addr;
int ok;
if (index >= DNS_MAX_SERVERS) {
return MMIPAL_INVALID_ARGUMENT;
}
ok = ipaddr_aton(addr, &dns_addr);
if (!ok) {
return MMIPAL_INVALID_ARGUMENT;
}
dns_setserver(index, &dns_addr);
return MMIPAL_SUCCESS;
}
enum mmipal_status mmipal_get_dns_server(uint8_t index, mmipal_ip_addr_t addr)
{
const ip_addr_t *dns_addr;
if (index >= DNS_MAX_SERVERS) {
return MMIPAL_INVALID_ARGUMENT;
}
dns_addr = dns_getserver(index);
#if LWIP_IPV4
/* dns_getserver() returns ip_addr_any if no address configured. */
if (!memcmp(dns_addr, &ip_addr_any, sizeof(*dns_addr))) {
addr[0] = '\0';
return MMIPAL_SUCCESS;
}
#endif
if (ipaddr_ntoa_r(dns_addr, addr, MMIPAL_IPADDR_STR_MAXLEN) == NULL) {
return MMIPAL_NO_MEM;
} else {
return MMIPAL_SUCCESS;
}
}
#endif
-218
View File
@@ -1,218 +0,0 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mmnetif.h"
#include "mmosal.h"
#include "mmwlan.h"
#include "lwip/etharp.h"
#include "lwip/ethip6.h"
#include "lwip/tcpip.h"
#if LWIP_SNMP
#include "lwip/snmp.h"
#endif
struct netif_state {
volatile uint8_t tx_qos_tid;
};
static struct netif_state *get_netif_state(struct netif *netif)
{
MMOSAL_ASSERT(netif->state != NULL);
return (struct netif_state *)netif->state;
}
/** pbuf wrapper around an mmpkt. */
struct mmpkt_pbuf_wrapper {
struct pbuf_custom p;
struct mmpkt *pkt;
struct mmpktview *pktview;
};
LWIP_MEMPOOL_DECLARE(RX_POOL, MMPKTMEM_RX_POOL_N_BLOCKS, sizeof(struct mmpkt_pbuf_wrapper), "mmpkt_rx");
static void mmpkt_pbuf_wrapper_free(struct pbuf *p)
{
struct mmpkt_pbuf_wrapper *pbuf = (struct mmpkt_pbuf_wrapper *)p;
if (p == NULL) {
return;
}
mmpkt_close(&pbuf->pktview);
mmpkt_release(pbuf->pkt);
LWIP_MEMPOOL_FREE(RX_POOL, pbuf);
}
static void mmnetif_rx(struct mmpkt *rxpkt, void *arg)
{
struct netif *netif = (struct netif *)arg;
LWIP_ASSERT("arg NULL", netif != NULL);
LWIP_DEBUGF(NETIF_DEBUG, ("mmnetif: packet received\n"));
struct mmpkt_pbuf_wrapper *pbuf = (struct mmpkt_pbuf_wrapper *)LWIP_MEMPOOL_ALLOC(RX_POOL);
if (pbuf != NULL) {
struct pbuf *p;
pbuf->p.custom_free_function = mmpkt_pbuf_wrapper_free;
pbuf->pkt = rxpkt;
pbuf->pktview = mmpkt_open(pbuf->pkt);
p = pbuf_alloced_custom(PBUF_RAW, mmpkt_get_data_length(pbuf->pktview), PBUF_REF, &pbuf->p,
mmpkt_get_data_start(pbuf->pktview), mmpkt_get_data_length(pbuf->pktview));
int ret = tcpip_input(p, netif);
if (ret == ERR_OK) {
LINK_STATS_INC(link.recv);
} else {
LWIP_DEBUGF(NETIF_DEBUG, ("mmnetif: input error\n"));
pbuf_free(p);
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
}
} else {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: alloc error\n"));
LINK_STATS_INC(link.memerr);
mmpkt_release(rxpkt);
}
}
static void mmnetif_link_state(enum mmwlan_link_state link_state, void *arg)
{
struct netif *netif = (struct netif *)arg;
LWIP_ASSERT("arg NULL", netif != NULL);
LOCK_TCPIP_CORE();
if (link_state == MMWLAN_LINK_DOWN) {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: link down\n"));
/* Note: we cast netif_set_link_down to tcpip_callback_fn since the tcpip_callback_fn
* has a "void *" parameter and netif_set_link_down has "struct netif *". */
err_t err = tcpip_callback_with_block((tcpip_callback_fn)netif_set_link_down, netif, 0);
LWIP_ASSERT("sched callback failed", err == ERR_OK);
} else {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: link up\n"));
/* Note: we cast netif_set_link_down to tcpip_callback_fn since the tcpip_callback_fn
* has a "void *" parameter and netif_set_link_down has "struct netif *". */
err_t err = tcpip_callback_with_block((tcpip_callback_fn)netif_set_link_up, netif, 0);
LWIP_ASSERT("sched callback failed", err == ERR_OK);
}
UNLOCK_TCPIP_CORE();
}
static err_t mmnetif_tx(struct netif *netif, struct pbuf *p)
{
struct mmpkt *pkt;
struct mmpktview *pktview;
enum mmwlan_status status;
struct pbuf *walk;
struct mmwlan_tx_metadata metadata = {
.tid = get_netif_state(netif)->tx_qos_tid,
};
status = mmwlan_tx_wait_until_ready(1000);
if (status != MMWLAN_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: transmit blocked\n"));
LINK_STATS_INC(link.drop);
return ERR_BUF;
}
pkt = mmwlan_alloc_mmpkt_for_tx(p->tot_len, metadata.tid);
if (pkt == NULL) {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: allocation failure\n"));
LINK_STATS_INC(link.memerr);
return ERR_MEM;
}
pktview = mmpkt_open(pkt);
for (walk = p; walk != NULL; walk = walk->next) {
mmpkt_append_data(pktview, (const uint8_t *)walk->payload, walk->len);
}
mmpkt_close(&pktview);
status = mmwlan_tx_pkt(pkt, &metadata);
if (status != MMWLAN_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: error sending packet\n"));
LINK_STATS_INC(link.drop);
return ERR_BUF;
}
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: packet sent\n"));
LINK_STATS_INC(link.xmit);
return ERR_OK;
}
err_t mmnetif_init(struct netif *netif)
{
static bool initialised = false;
if (initialised) {
return ERR_IF;
}
LWIP_MEMPOOL_INIT(RX_POOL);
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: initialising mmnetif\n"));
#if LWIP_SNMP
NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, 1000000UL);
#endif
enum mmwlan_status status;
/* Boot the transceiver so that we can read the MAC address. */
struct mmwlan_boot_args boot_args = MMWLAN_BOOT_ARGS_INIT;
status = mmwlan_boot(&boot_args);
if (status != MMWLAN_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mmwlan_boot failed with code %d\n", status));
}
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
/* Set MAC hardware address */
netif->hwaddr_len = MMWLAN_MAC_ADDR_LEN;
status = mmwlan_get_mac_addr(netif->hwaddr);
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
netif->mtu = 1500;
#if LWIP_IPV4 && !LWIP_IPV6
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP;
#else
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP | NETIF_FLAG_MLD6;
#endif
netif->state = NULL;
netif->name[0] = 'M';
netif->name[1] = 'M';
#if LWIP_IPV4
netif->output = etharp_output;
#endif
#if LWIP_IPV6
netif->output_ip6 = ethip6_output;
#endif
netif->linkoutput = mmnetif_tx;
struct netif_state *state = (struct netif_state *)mmosal_malloc(sizeof(*state));
MMOSAL_ASSERT(state != NULL);
state->tx_qos_tid = MMWLAN_TX_DEFAULT_QOS_TID;
netif->state = state;
status = mmwlan_register_rx_pkt_cb(mmnetif_rx, netif);
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
status = mmwlan_register_link_state_cb(mmnetif_link_state, netif);
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
printf("Morse LwIP interface initialised. MAC address %02x:%02x:%02x:%02x:%02x:%02x\n", netif->hwaddr[0], netif->hwaddr[1],
netif->hwaddr[2], netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]);
initialised = true;
return ERR_OK;
}
void mmnetif_set_tx_qos_tid(struct netif *netif, uint8_t tid)
{
MMOSAL_ASSERT(tid <= MMWLAN_MAX_QOS_TID);
get_netif_state(netif)->tx_qos_tid = tid;
}
#endif
-29
View File
@@ -1,29 +0,0 @@
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "lwip/err.h"
#include "lwip/netif.h"
#ifdef __cplusplus
extern "C" {
#endif
/** Initializer for the Morse Micro network interface */
err_t mmnetif_init(struct netif *netif);
/**
* Configure the QoS TID for the @c netif. QoS data will be sent using this TID.
*
* @param netif The @c netif to configure.
* @param tid The TID value to set.
*/
void mmnetif_set_tx_qos_tid(struct netif *netif, uint8_t tid);
#ifdef __cplusplus
}
#endif
@@ -1,563 +0,0 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_debug_helpers.h"
#include "esp_private/startup_internal.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "rom/ets_sys.h"
#include "mmhal.h"
#include "mmosal.h"
/* --------------------------------------------------------------------------------------------- */
/** Maximum number of failure records to store (must be a power of 2). */
#define MAX_FAILURE_RECORDS 4
/** Fast implementation of _x % _m where _m is a power of 2. */
#define FAST_MOD(_x, _m) ((_x) & ((_m)-1))
/** Duration to delay before resetting the device on assert. */
#define DELAY_BEFORE_RESET_MS 1000
/** Data structure for assertion information to be preserved. */
struct mmosal_preserved_failure_info {
/** Magic number, to check if the info is valid. */
uint32_t magic;
/** Number of failures recorded. */
uint32_t failure_count;
/** Number of most recently displayed failure. */
uint32_t displayed_failure_count;
/** Preserved information from the most recent failure(s). */
struct mmosal_failure_info info[MAX_FAILURE_RECORDS];
};
/** Magic number to put in @c mmosal_assert_info.magic to indicate that the assertion info
* is valid. */
#define ASSERT_INFO_MAGIC (0xabcd1234)
/* Persistent assertion info. Linker script should put this into memory that is not
* zeroed on boot. Be careful to update linker script if renaming. */
struct mmosal_preserved_failure_info preserved_failure_info __attribute__((section(".noinit")));
void mmosal_log_failure_info(const struct mmosal_failure_info *info)
{
uint32_t record_num;
if (preserved_failure_info.magic != ASSERT_INFO_MAGIC) {
preserved_failure_info.failure_count = 0;
preserved_failure_info.displayed_failure_count = 0;
}
preserved_failure_info.magic = ASSERT_INFO_MAGIC;
record_num = FAST_MOD(preserved_failure_info.failure_count, MAX_FAILURE_RECORDS);
preserved_failure_info.failure_count++;
memcpy(&preserved_failure_info.info[record_num], info, sizeof(*info));
}
static void mmosal_dump_failure_info(void)
{
unsigned first_failure_num = preserved_failure_info.displayed_failure_count;
unsigned new_failure_count = preserved_failure_info.failure_count - preserved_failure_info.displayed_failure_count;
unsigned failure_offset;
if (new_failure_count >= MAX_FAILURE_RECORDS) {
first_failure_num = FAST_MOD(preserved_failure_info.failure_count, MAX_FAILURE_RECORDS);
new_failure_count = MAX_FAILURE_RECORDS;
}
for (failure_offset = 0; failure_offset < new_failure_count; failure_offset++) {
unsigned ii;
unsigned idx = FAST_MOD(first_failure_num + failure_offset, MAX_FAILURE_RECORDS);
struct mmosal_failure_info *info = &preserved_failure_info.info[idx];
ets_printf("Failure %u logged at pc 0x%08lx, lr 0x%08lx, line %ld in %08lx\n", first_failure_num + failure_offset,
info->pc, info->lr, info->line, info->fileid);
for (ii = 0; ii < sizeof(info->platform_info) / sizeof(info->platform_info[0]); ii++) {
ets_printf(" 0x%08lx\n", info->platform_info[ii]);
}
}
preserved_failure_info.displayed_failure_count = preserved_failure_info.failure_count;
}
void mmosal_impl_assert(void)
{
ets_printf("MMOSAL Assert, CPU %d (current core) backtrace", xPortGetCoreID());
(void)esp_backtrace_print(100);
#ifdef HALT_ON_ASSERT
if (preserved_failure_info.magic == ASSERT_INFO_MAGIC) {
mmosal_dump_failure_info();
}
mmosal_disable_interrupts();
mmhal_log_flush();
MMPORT_BREAKPOINT();
#else
mmosal_task_sleep(DELAY_BEFORE_RESET_MS);
mmhal_reset();
#endif
while (1) {
}
}
/* Function to be called as part of the secondary initialization. See [System
* Initialization](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/startup.html#system-initialization)
* for more information. */
ESP_SYSTEM_INIT_FN(mmosal_dump_failure_info, BIT(0), 999)
{
if (preserved_failure_info.magic == ASSERT_INFO_MAGIC) {
mmosal_dump_failure_info();
}
return ESP_OK;
}
/* --------------------------------------------------------------------------------------------- */
void *mmosal_malloc_(size_t size)
{
return pvPortMalloc(size);
}
#ifdef MMOSAL_TRACK_ALLOCATIONS
void *mmosal_malloc_dbg(size_t size, const char *name, unsigned line_number)
{
return pvPortMalloc_dbg(size, name, line_number);
}
#else
void *mmosal_malloc_dbg(size_t size, const char *name, unsigned line_number)
{
(void)name;
(void)line_number;
return pvPortMalloc(size);
}
#endif
void mmosal_free(void *p)
{
vPortFree(p);
}
void *mmosal_realloc(void *ptr, size_t size)
{
return realloc(ptr, size);
}
void *mmosal_calloc(size_t nitems, size_t size)
{
void *ptr = pvPortMalloc(nitems * size);
if (ptr == NULL) {
return NULL;
}
memset(ptr, 0, nitems * size);
return ptr;
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_task_arg {
mmosal_task_fn_t task_fn;
void *task_fn_arg;
};
void mmosal_task_main(void *arg)
{
struct mmosal_task_arg task_arg = *(struct mmosal_task_arg *)arg;
mmosal_free(arg);
task_arg.task_fn(task_arg.task_fn_arg);
mmosal_task_delete(NULL);
}
struct mmosal_task *mmosal_task_create(mmosal_task_fn_t task_fn, void *argument, enum mmosal_task_priority priority,
unsigned stack_size_u32, const char *name)
{
TaskHandle_t handle;
UBaseType_t freertos_priority = tskIDLE_PRIORITY + priority;
struct mmosal_task_arg *task_arg = (struct mmosal_task_arg *)mmosal_malloc(sizeof(*task_arg));
if (task_arg == NULL) {
return NULL;
}
task_arg->task_fn = task_fn;
task_arg->task_fn_arg = argument;
BaseType_t result = xTaskCreate(mmosal_task_main, name, stack_size_u32 * 4, task_arg, freertos_priority, &handle);
if (result == pdFAIL) {
mmosal_free(task_arg);
return NULL;
}
return (struct mmosal_task *)handle;
}
void mmosal_task_delete(struct mmosal_task *task)
{
vTaskDelete((TaskHandle_t)task);
}
/*
* Warning: this function should not be used since eTaskGetState() is not a reliable
* means of testing whether a task has completed.
*
* This function will be removed in future.
*/
void mmosal_task_join(struct mmosal_task *task)
{
while (eTaskGetState((TaskHandle_t)task) != eDeleted) {
mmosal_task_sleep(10);
}
}
struct mmosal_task *mmosal_task_get_active(void)
{
return (struct mmosal_task *)xTaskGetCurrentTaskHandle();
}
void mmosal_task_yield(void)
{
taskYIELD();
}
void mmosal_task_sleep(uint32_t duration_ms)
{
vTaskDelay(duration_ms / portTICK_PERIOD_MS);
}
static portMUX_TYPE task_spinlock = portMUX_INITIALIZER_UNLOCKED;
void mmosal_task_enter_critical(void)
{
taskENTER_CRITICAL(&task_spinlock);
}
void mmosal_task_exit_critical(void)
{
taskEXIT_CRITICAL(&task_spinlock);
}
void mmosal_disable_interrupts(void)
{
taskDISABLE_INTERRUPTS();
}
void mmosal_enable_interrupts(void)
{
taskENABLE_INTERRUPTS();
}
const char *mmosal_task_name(void)
{
TaskHandle_t t = xTaskGetCurrentTaskHandle();
return pcTaskGetName(t);
}
bool mmosal_task_wait_for_notification(uint32_t timeout_ms)
{
TickType_t wait = portMAX_DELAY;
if (timeout_ms < UINT32_MAX) {
wait = pdMS_TO_TICKS(timeout_ms);
}
uint32_t ret = ulTaskNotifyTake(pdTRUE, /* Act as binary semaphore */
wait);
return (ret != 0);
}
void mmosal_task_notify(struct mmosal_task *task)
{
xTaskNotifyGive((TaskHandle_t)task);
}
void mmosal_task_notify_from_isr(struct mmosal_task *task)
{
BaseType_t higher_priority_task_woken = pdFALSE;
vTaskNotifyGiveFromISR((TaskHandle_t)task, &higher_priority_task_woken);
portYIELD_FROM_ISR(higher_priority_task_woken);
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_mutex *mmosal_mutex_create(const char *name)
{
struct mmosal_mutex *mutex = (struct mmosal_mutex *)xSemaphoreCreateMutex();
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
if (name != NULL) {
vTraceSetMutexName(mutex, name);
}
#else
(void)name;
#endif
return mutex;
}
void mmosal_mutex_delete(struct mmosal_mutex *mutex)
{
if (mutex != NULL) {
vQueueDelete((SemaphoreHandle_t)mutex);
}
}
bool mmosal_mutex_get(struct mmosal_mutex *mutex, uint32_t timeout_ms)
{
uint32_t timeout_ticks = portMAX_DELAY;
if (timeout_ms != UINT32_MAX) {
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
}
return (xSemaphoreTake((SemaphoreHandle_t)mutex, timeout_ticks) == pdPASS);
}
bool mmosal_mutex_release(struct mmosal_mutex *mutex)
{
return (xSemaphoreGive((SemaphoreHandle_t)mutex) == pdPASS);
}
bool mmosal_mutex_is_held_by_active_task(struct mmosal_mutex *mutex)
{
return xSemaphoreGetMutexHolder((SemaphoreHandle_t)mutex) == xTaskGetCurrentTaskHandle();
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_sem *mmosal_sem_create(unsigned max_count, unsigned initial_count, const char *name)
{
struct mmosal_sem *sem = (struct mmosal_sem *)xSemaphoreCreateCounting(max_count, initial_count);
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
if (name != NULL) {
vTraceSetSemaphoreName(sem, name);
}
#else
(void)name;
#endif
return sem;
}
void mmosal_sem_delete(struct mmosal_sem *sem)
{
vQueueDelete((SemaphoreHandle_t)sem);
}
bool mmosal_sem_give(struct mmosal_sem *sem)
{
return xSemaphoreGive((SemaphoreHandle_t)sem);
}
bool mmosal_sem_give_from_isr(struct mmosal_sem *sem)
{
BaseType_t task_woken = false;
BaseType_t ret = xSemaphoreGiveFromISR((SemaphoreHandle_t)sem, &task_woken);
if (ret == pdPASS) {
portYIELD_FROM_ISR(task_woken);
return true;
} else {
return false;
}
}
bool mmosal_sem_wait(struct mmosal_sem *sem, uint32_t timeout_ms)
{
uint32_t timeout_ticks = portMAX_DELAY;
if (timeout_ms != UINT32_MAX) {
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
}
return (xSemaphoreTake((SemaphoreHandle_t)sem, timeout_ticks) == pdPASS);
}
uint32_t mmosal_sem_get_count(struct mmosal_sem *sem)
{
return uxSemaphoreGetCount((SemaphoreHandle_t)sem);
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_semb *mmosal_semb_create(const char *name)
{
struct mmosal_semb *semb = (struct mmosal_semb *)xSemaphoreCreateBinary();
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
if (name != NULL) {
vTraceSetSemaphoreName(semb, name);
}
#else
(void)name;
#endif
return semb;
}
void mmosal_semb_delete(struct mmosal_semb *semb)
{
vQueueDelete((SemaphoreHandle_t)semb);
}
bool mmosal_semb_give(struct mmosal_semb *semb)
{
return (xSemaphoreGive((SemaphoreHandle_t)semb) == pdPASS);
}
bool mmosal_semb_give_from_isr(struct mmosal_semb *semb)
{
BaseType_t task_woken = pdFALSE;
BaseType_t ret = xSemaphoreGiveFromISR((SemaphoreHandle_t)semb, &task_woken);
if (ret == pdPASS) {
portYIELD_FROM_ISR(task_woken);
return true;
} else {
return false;
}
}
bool mmosal_semb_wait(struct mmosal_semb *semb, uint32_t timeout_ms)
{
uint32_t timeout_ticks = portMAX_DELAY;
if (timeout_ms != UINT32_MAX) {
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
}
return (xSemaphoreTake((SemaphoreHandle_t)semb, timeout_ticks) == pdPASS);
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_queue *mmosal_queue_create(size_t num_items, size_t item_size, const char *name)
{
struct mmosal_queue *queue = (struct mmosal_queue *)xQueueCreate(num_items, item_size);
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
if (name != NULL) {
vTraceSetQueueName(queue, name);
}
#else
(void)name;
#endif
return queue;
}
void mmosal_queue_delete(struct mmosal_queue *queue)
{
vQueueDelete((SemaphoreHandle_t)queue);
}
bool mmosal_queue_pop(struct mmosal_queue *queue, void *item, uint32_t timeout_ms)
{
uint32_t timeout_ticks = portMAX_DELAY;
if (timeout_ms != UINT32_MAX) {
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
}
return (xQueueReceive((SemaphoreHandle_t)queue, item, timeout_ticks) == pdPASS);
}
bool mmosal_queue_push(struct mmosal_queue *queue, const void *item, uint32_t timeout_ms)
{
uint32_t timeout_ticks = portMAX_DELAY;
if (timeout_ms != UINT32_MAX) {
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
}
return (xQueueSendToBack((SemaphoreHandle_t)queue, item, timeout_ticks) == pdPASS);
}
bool mmosal_queue_pop_from_isr(struct mmosal_queue *queue, void *item)
{
BaseType_t task_woken = pdFALSE;
if (xQueueReceiveFromISR((SemaphoreHandle_t)queue, item, &task_woken) == pdTRUE) {
portYIELD_FROM_ISR(task_woken);
return true;
} else {
return false;
}
}
bool mmosal_queue_push_from_isr(struct mmosal_queue *queue, const void *item)
{
BaseType_t task_woken = pdFALSE;
if (xQueueSendToBackFromISR((SemaphoreHandle_t)queue, item, &task_woken) == pdTRUE) {
portYIELD_FROM_ISR(task_woken);
return true;
} else {
return false;
}
}
/* --------------------------------------------------------------------------------------------- */
uint32_t mmosal_get_time_ms(void)
{
return xTaskGetTickCount() * portTICK_PERIOD_MS;
}
uint32_t mmosal_get_time_ticks(void)
{
return xTaskGetTickCount();
}
uint32_t mmosal_ticks_per_second(void)
{
return portTICK_PERIOD_MS * 1000;
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_timer *mmosal_timer_create(const char *name, uint32_t timer_period, bool auto_reload, void *arg,
timer_callback_t callback)
{
/*
* The software timer callback functions execute in the context of a task that is
* created automatically when the FreeRTOS scheduler is started. Therefore, it is essential that
* software timer callback functions never call FreeRTOS API functions that will result in the
* calling task entering the Blocked state. It is ok to call functions such as xQueueReceive(), but
* only if the functions xTicksToWait parameter (which specifies the functions block time) is set
* to 0. It is not ok to call functions such as vTaskDelay(), as calling vTaskDelay() will always
* place the calling task into the Blocked state.
*/
return (struct mmosal_timer *)xTimerCreate(name, pdMS_TO_TICKS(timer_period), (UBaseType_t)auto_reload, arg,
(TimerCallbackFunction_t)callback);
}
void mmosal_timer_delete(struct mmosal_timer *timer)
{
if (timer != NULL) {
BaseType_t ret = xTimerDelete((TimerHandle_t)timer, 0);
configASSERT(ret == pdPASS);
}
}
bool mmosal_timer_start(struct mmosal_timer *timer)
{
BaseType_t ret = xTimerStart((TimerHandle_t)timer, 0);
return (ret == pdPASS);
}
bool mmosal_timer_stop(struct mmosal_timer *timer)
{
BaseType_t ret = xTimerStop((TimerHandle_t)timer, 0);
return (ret == pdPASS);
}
bool mmosal_timer_change_period(struct mmosal_timer *timer, uint32_t new_period)
{
BaseType_t ret = xTimerChangePeriod((TimerHandle_t)timer, pdMS_TO_TICKS(new_period), 0);
return (ret == pdPASS);
}
void *mmosal_timer_get_arg(struct mmosal_timer *timer)
{
return pvTimerGetTimerID((TimerHandle_t)timer);
}
bool mmosal_is_timer_active(struct mmosal_timer *timer)
{
BaseType_t ret = xTimerIsTimerActive((TimerHandle_t)timer);
return (ret != pdFALSE);
}
#endif /* USE_MM_IOT_ESP32 */
-245
View File
@@ -1,245 +0,0 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdatomic.h>
#include <stdint.h>
#include "mmhal.h"
#include "mmosal.h"
#include "mmpkt.h"
#include "mmpkt_list.h"
#include "mmutils.h"
/* MMPKTMEM_TX_POOL_N_BLOCKS and MMPKTMEM_RX_POOL_N_BLOCKS provide an upper bound on the number
* of packets we will allocate in the transmit and receive directions respectively. */
#ifndef MMPKTMEM_TX_POOL_N_BLOCKS
#error MMPKTMEM_TX_POOL_N_BLOCKS not defined
#endif
#ifndef MMPKTMEM_RX_POOL_N_BLOCKS
#error MMPKTMEM_RX_POOL_N_BLOCKS not defined
#endif
/* Packet pool for data/management frames configuration. */
#define TX_DATA_POOL_UNPAUSE_THRESHOLD (MMPKTMEM_TX_POOL_N_BLOCKS - 2)
#define TX_DATA_POOL_PAUSE_THRESHOLD (MMPKTMEM_TX_POOL_N_BLOCKS - 1)
/* Packet pool for commands configuration. */
#define TX_COMMAND_POOL_BLOCK_SIZE (256)
#define TX_COMMAND_POOL_N_BLOCKS (2)
#ifndef MMPKT_LOG
#define MMPKT_LOG(...) printf(__VA_ARGS__)
#endif
struct pktmem_data {
/** Count of allocated tx packets (excluding command pool -- see below). */
volatile atomic_int_least32_t tx_data_pool_allocated;
/** Boolean tracking whether the data path is currently paused. */
volatile atomic_uint_fast8_t tx_data_pool_tx_paused;
/** Count of allocated rx packets. */
volatile atomic_int_least32_t rx_pool_allocated;
/** Command pool free (unallocated) packet list. */
struct mmpkt_list tx_command_pool_free_list;
/** Statically allocated memory for the command pool. */
uint8_t tx_command_pool[TX_COMMAND_POOL_BLOCK_SIZE * TX_COMMAND_POOL_N_BLOCKS];
/** Flow control callback function pointer. */
mmhal_wlan_pktmem_tx_flow_control_cb_t tx_flow_control_cb;
};
static struct pktmem_data pktmem;
void mmhal_wlan_pktmem_init(struct mmhal_wlan_pktmem_init_args *args)
{
unsigned ii;
memset(&pktmem, 0, sizeof(pktmem));
pktmem.tx_flow_control_cb = args->tx_flow_control_cb;
/* Initialize the free (unallocated) packet list of the transmit command pool. */
for (ii = 0; ii < TX_COMMAND_POOL_N_BLOCKS; ii++) {
size_t offset = TX_COMMAND_POOL_BLOCK_SIZE * ii;
mmpkt_list_append(&pktmem.tx_command_pool_free_list, (struct mmpkt *)(pktmem.tx_command_pool + offset));
}
}
void mmhal_wlan_pktmem_deinit(void)
{
size_t ii;
/* If there is still memory allocated, allow some time for other threads to clean up. */
for (ii = 0; ii < 100; ii++) {
if ((pktmem.tx_command_pool_free_list.len | pktmem.tx_data_pool_allocated | pktmem.tx_data_pool_allocated) == 0) {
break;
}
mmosal_task_sleep(10);
}
/* Check for memory leaks. */
if (pktmem.tx_data_pool_allocated != 0) {
MMPKT_LOG("Potential memory leak: %d %s pool allocations at deinit\n", (int)pktmem.tx_data_pool_allocated, "data");
}
if (pktmem.tx_command_pool_free_list.len != TX_COMMAND_POOL_N_BLOCKS) {
MMPKT_LOG("Potential memory leak: %d %s pool allocations at deinit\n",
TX_COMMAND_POOL_N_BLOCKS - (int)pktmem.tx_command_pool_free_list.len, "command");
}
}
/*
* --------------------------------------------------------------------------------------
* Command pool
* --------------------------------------------------------------------------------------
*/
static void tx_command_reserved_free(void *mmpkt)
{
struct mmpkt *pkt = (struct mmpkt *)mmpkt;
MMOSAL_TASK_ENTER_CRITICAL();
mmpkt_list_append(&pktmem.tx_command_pool_free_list, pkt);
MMOSAL_TASK_EXIT_CRITICAL();
}
static const struct mmpkt_ops tx_command_pool_ops = {
.free_mmpkt = tx_command_reserved_free,
};
static struct mmpkt *alloc_pkt_from_list(struct mmpkt_list *list, uint32_t pktbufsize, uint32_t space_at_start,
uint32_t space_at_end, uint32_t metadata_length)
{
struct mmpkt *mmpkt_buf;
struct mmpkt *mmpkt;
MMOSAL_TASK_ENTER_CRITICAL();
mmpkt_buf = mmpkt_list_dequeue(list);
MMOSAL_TASK_EXIT_CRITICAL();
if (mmpkt_buf == NULL) {
return NULL;
}
mmpkt = mmpkt_init_buf((uint8_t *)mmpkt_buf, pktbufsize, space_at_start, space_at_end, metadata_length, &tx_command_pool_ops);
if (mmpkt == NULL) {
/* Command was too big for the reserved buffer. Return the reserved buffer. */
tx_command_reserved_free(mmpkt_buf);
}
return mmpkt;
}
static struct mmpkt *command_pool_alloc(uint32_t space_at_start, uint32_t space_at_end, uint32_t metadata_length)
{
return alloc_pkt_from_list(&pktmem.tx_command_pool_free_list, TX_COMMAND_POOL_BLOCK_SIZE, space_at_start, space_at_end,
metadata_length);
}
/*
* --------------------------------------------------------------------------------------
* Data pool
* --------------------------------------------------------------------------------------
*/
static void tx_data_pool_pkt_free(void *mmpkt)
{
atomic_int_least32_t old_value = atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1);
MMOSAL_ASSERT(old_value > 0);
mmosal_free(mmpkt);
if (pktmem.tx_data_pool_allocated < TX_DATA_POOL_UNPAUSE_THRESHOLD) {
atomic_uint_fast8_t old_tx_paused = atomic_exchange(&pktmem.tx_data_pool_tx_paused, 0);
if (old_tx_paused) {
pktmem.tx_flow_control_cb(MMWLAN_TX_READY);
}
}
}
static const struct mmpkt_ops tx_data_pool_pkt_ops = {
.free_mmpkt = tx_data_pool_pkt_free,
};
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_tx(uint8_t pkt_class, uint32_t space_at_start, uint32_t space_at_end,
uint32_t metadata_length)
{
atomic_int_least32_t old_value;
struct mmpkt *mmpkt;
/* For command packets, try allocating from the command pool first. If that fails then
* we proceed to allocate from the data pool. */
if (pkt_class == MMHAL_WLAN_PKT_COMMAND) {
mmpkt = command_pool_alloc(space_at_start, space_at_end, metadata_length);
if (mmpkt != NULL) {
return mmpkt;
}
}
old_value = atomic_fetch_add(&pktmem.tx_data_pool_allocated, 1);
if (old_value >= MMPKTMEM_TX_POOL_N_BLOCKS) {
/* Maximum allocations reached. Do not attempt to increase further. */
atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1);
return NULL;
}
mmpkt = mmpkt_alloc_on_heap(space_at_start, space_at_end, metadata_length);
if (mmpkt == NULL) {
atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1);
return NULL;
}
mmpkt->ops = &tx_data_pool_pkt_ops;
if (pktmem.tx_data_pool_allocated > TX_DATA_POOL_PAUSE_THRESHOLD) {
atomic_uint_fast8_t old_tx_paused = atomic_exchange(&pktmem.tx_data_pool_tx_paused, 1);
if (!old_tx_paused) {
pktmem.tx_flow_control_cb(MMWLAN_TX_PAUSED);
}
}
return mmpkt;
}
static void rx_pkt_free(void *mmpkt)
{
if (mmpkt != NULL) {
atomic_fetch_sub(&pktmem.rx_pool_allocated, 1);
mmosal_free(mmpkt);
}
}
static const struct mmpkt_ops mmpkt_rx_ops = {.free_mmpkt = rx_pkt_free};
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_rx(uint32_t capacity, uint32_t metadata_length)
{
atomic_int_least32_t old_value;
struct mmpkt *mmpkt;
old_value = atomic_fetch_add(&pktmem.rx_pool_allocated, 1);
if (old_value >= MMPKTMEM_RX_POOL_N_BLOCKS) {
/* Maximum allocations reached. Do not attempt to increase further. */
atomic_fetch_sub(&pktmem.rx_pool_allocated, 1);
return NULL;
}
/* For now we do not put an explicit limit on the of packets buffers on the RX path. */
mmpkt = mmpkt_alloc_on_heap(0, capacity, metadata_length);
if (mmpkt == NULL) {
atomic_fetch_sub(&pktmem.rx_pool_allocated, 1);
return NULL;
}
/* Override packet ops to use a custom free function that also decrements the
* allocation count. */
mmpkt->ops = &mmpkt_rx_ops;
return mmpkt;
}
#endif
-12
View File
@@ -1,12 +0,0 @@
/*
* Copyright 2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#define MMPORT_BREAKPOINT() while (1)
#define MMPORT_GET_LR() (__builtin_return_address(0))
#define MMPORT_GET_PC(_a) ((_a) = 0) // TODO
#define MMPORT_MEM_SYNC() __sync_synchronize()
-301
View File
@@ -1,301 +0,0 @@
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMUTILS Morse Micro Utilities
*
* Utility macros and functions to improve quality of life.
*
* @{
*/
#pragma once
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Get the minimum of 2 values.
*
* Note that this macro is not ideal and should be used with caution. Caveats include:
* * The two parameters may be evaluated more than once, so should be constant values that
* do not have side effects. For example, do NOT do `MM_MIN(a++, b)`.
* * There are no explicit constraints on types, so be careful of comparing different integer
* types, etc.
*
* @param _x The first value to compare.
* @param _y The second value to compare.
*
* @returns the minimum of @p _x and @p _y.
*/
#define MM_MIN(_x, _y) (((_x) < (_y)) ? (_x) : (_y))
/**
* Get the maximum of 2 values.
*
* Note that this macro is not ideal and should be used with caution. Caveats include:
* * The two parameters may be evaluated more than once, so should be constant values that
* do not have side effects. For example, do NOT do `MM_MAX(a++, b)`.
* * There are no explicit constraints on types, so be careful of comparing different integer
* types, etc.
*
* @param _x The first value to compare.
* @param _y The second value to compare.
*
* @returns the maximum of @p _x and @p _y.
*/
#define MM_MAX(_x, _y) (((_x) > (_y)) ? (_x) : (_y))
/**
* Round @p x up to the next multiple of @p m (where @p m is a power of 2).
*
* @warning @p m must be a power of 2.
*/
#ifndef MM_FAST_ROUND_UP
#define MM_FAST_ROUND_UP(x, m) ((((x)-1) | ((m)-1)) + 1)
#endif
/** Casts the given expression to void to avoid "unused" warnings from the compiler. */
#define MM_UNUSED(_x) (void)(_x)
/** Tells the compiler to pack the structure. */
#ifndef MM_PACKED
#define MM_PACKED __attribute__((packed))
#endif
/** Used to declare a weak symbol. */
#ifndef MM_WEAK
#define MM_WEAK __attribute__((weak))
#endif
#ifndef MM_STATIC_ASSERT
/**
* Assertion check that is evaluated at compile time.
*
* The constant expression, @p _expression, is evaluted at compile time. If zero then
* it triggers a compilation error and @p _message is displayed. If non-zero, no code
* is emitted.
*
* @param _expression Constant expression to evaluate. If zero then a compilation error
* is triggered.
* @param _message Message to display on error.
*/
#define MM_STATIC_ASSERT(_expression, _message) _Static_assert((_expression), _message)
#endif
/**
* Return the number of elements in the given array.
*
* @param _a The array to get the element count for. Note that this must be an _array_
* and not a pointer. Beware that array-type function arguments are
* actually treated as pointers by the compiler. Must not be NULL.
*
* @returns the count of elements in the given array.
*/
#define MM_ARRAY_COUNT(_a) (sizeof(_a) / sizeof((_a)[0]))
/**
* Convert the least significant 4 bits of the given argument to a character representing their
* hexadecimal value.
*
* For example, for input 0xde this will return 'E', for 0x01 it will return '1'.
*
* @param nibble The input nibble (upper 4 bits will be discarded).
*
* @return The character that represents the hexadecimal value of the lower 4 bits of @p nibble.
* Values greater than 0x09 will be represented with upper case characters.
*/
static inline char mm_nibble_to_hex_char(uint8_t nibble)
{
nibble &= 0x0f;
if (nibble < 0x0a) {
return '0' + nibble;
} else {
return 'A' + nibble - 0x0a;
}
}
/**
* @defgroup MMUTILS_WLAN WLAN Utilities
*
* Utility macros and functions relating to WLAN.
*
* @{
*/
/** Enumeration of Authentication Key Management (AKM) Suite OUIs as BE32 integers. */
enum mm_akm_suite_oui {
/** Open (no security) */
MM_AKM_SUITE_NONE = 0,
/** Pre-shared key (WFA OUI) */
MM_AKM_SUITE_PSK = 0x506f9a02,
/** Simultaneous Authentication of Equals (SAE) */
MM_AKM_SUITE_SAE = 0x000fac08,
/** OWE */
MM_AKM_SUITE_OWE = 0x000fac12,
/** Another suite not in this enum */
MM_AKM_SUITE_OTHER = 1,
};
/** Enumeration of Cipher Suite OUIs as BE32 integers. */
enum mm_cipher_suite_oui {
/** Open (no security) */
MM_CIPHER_SUITE_AES_CCM = 0x000fac04,
/** Another cipher suite not in this enum */
MM_CIPHER_SUITE_OTHER = 1,
};
/** Maximum number of pairwise cipher suites our parser will process. */
#ifndef MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES
#define MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES (2)
#endif
/** Maximum number of AKM suites our parser will process. */
#ifndef MM_RSN_INFORMATION_MAX_AKM_SUITES
#define MM_RSN_INFORMATION_MAX_AKM_SUITES (2)
#endif
/** Tag number of the RSN information element, in which we can find security details of the AP. */
#define MM_RSN_INFORMATION_IE_TYPE (48)
/** Tag number of the Vendor Specific information element. */
#define MM_VENDOR_SPECIFIC_IE_TYPE (221)
/** Explicitly defined errno values to obviate the need to include errno.h. MM prefix to
* avoid namespace collision in case errno.h gets included. */
enum mm_errno {
MM_ENOMEM = 12,
MM_EFAULT = 14,
MM_ENODEV = 19,
MM_EINVAL = 22,
MM_ETIMEDOUT = 110,
};
/**
* Data structure to represent information extracted from an RSN information element.
*
* All integers in host order.
*/
struct mm_rsn_information {
/** The group cipher suite OUI. */
uint32_t group_cipher_suite;
/** Pairwise cipher suite OUIs. Count given by @c num_pairwise_cipher_suites. */
uint32_t pairwise_cipher_suites[MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES];
/** AKM suite OUIs. Count given by @c num_akm_suites. */
uint32_t akm_suites[MM_RSN_INFORMATION_MAX_AKM_SUITES];
/** Number of pairwise cipher suites in @c pairwise_cipher_suites. */
uint16_t num_pairwise_cipher_suites;
/** Number of AKM suites in @c akm_suites. */
uint16_t num_akm_suites;
/** Version number of the RSN IE. */
uint16_t version;
/** RSN Capabilities field of the RSN IE (in host order). */
uint16_t rsn_capabilities;
};
/**
* Get the name of the given AKM Suite as a string.
*
* @param akm_suite_oui The OUI of the AKM suite as a big endian integer.
*
* @returns the string representation.
*/
const char *mm_akm_suite_to_string(uint32_t akm_suite_oui);
/**
* Search a list of Information Elements (IEs) from the given starting offset and find the first
* instance of matching the given type.
*
* @warning A @p search_offset that is not aligned to the start of an IE header will result in
* undefined behaviour.
*
* @param ies Buffer containing the information elements.
* @param ies_len Length of @p ies
* @param search_offset Offset to start searching from. This **must** point to a IE header.
* @param ie_type The type of the IE to look for.
*
* @return If the information element is found, the offset of the start of the IE within @p ies; if
* no match is found then -1; if the IE is found but is malformed then -2.
*/
int mm_find_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, uint8_t ie_type);
/**
* Search a list of Information Elements (IEs) and find the first instance of matching the
* given type.
*
* @param ies Buffer containing the information elements.
* @param ies_len Length of @p ies
* @param ie_type The type of the IE to look for.
*
* @return If the information element is found, the offset of the start of the IE within @p ies;
* if no match is found then -1; if the IE is found but is malformed then -2.
*/
static inline int mm_find_ie(const uint8_t *ies, uint32_t ies_len, uint8_t ie_type)
{
return mm_find_ie_from_offset(ies, ies_len, 0, ie_type);
}
/**
* Search through the given list of Information Elements (IEs) from the given starting offset to
* find the first Vendor Specific IE that matches the given id.
*
* @warning A @p search_offset that is not aligned to the start of an IE header will result in
* undefined behaviour.
*
* @param[in] ies Buffer containing the information elements.
* @param[in] ies_len Length of @p ies
* @param[in] search_offset Offset to start searching from. This **must** point to a IE header.
* @param[in] id Buffer containing the IE ID, usually OUI+TYPE.
* @param[in] id_len Length of the ID.
*
* @return If the information element is found, the offset of the start of the IE within @p ies; if
* no match is found then -1; if the IE is found but is malformed then -2.
*/
int mm_find_vendor_specific_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, const uint8_t *id,
size_t id_len);
/**
* Search through the given list of Information Elements (IEs) to find the first Vendor Specific IE
* that matches the given id.
*
* @param[in] ies Buffer containing the information elements.
* @param[in] ies_len Length of @p ies
* @param[in] id Buffer containing the IE ID, usually OUI+TYPE.
* @param[in] id_len Length of the ID.
*
* @return If the information element is found, the offset of the start of the IE within @p ies; if
* no match is found then -1; if the IE is found but is malformed then -2.
*/
static inline int mm_find_vendor_specific_ie(const uint8_t *ies, uint32_t ies_len, const uint8_t *id, size_t id_len)
{
return mm_find_vendor_specific_ie_from_offset(ies, ies_len, 0, id, id_len);
}
/**
* Search through the given list of information elements to find the RSN IE then parse it
* to extract relevant information into an instance of @ref mm_rsn_information.
*
* @param[in] ies Buffer containing the information elements.
* @param[in] ies_len Length of @p ies
* @param[out] output Pointer to an instance of @ref mm_rsn_information to receive output.
*
* @returns -2 on parse error, -1 if the RSN IE was not found, 0 if the RSN IE was found.
*/
int mm_parse_rsn_information(const uint8_t *ies, uint32_t ies_len, struct mm_rsn_information *output);
/** @} */
#ifdef __cplusplus
}
#endif
/** @} */
-163
View File
@@ -1,163 +0,0 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "mmutils.h"
const char *mm_akm_suite_to_string(uint32_t akm_suite_oui)
{
switch (akm_suite_oui) {
case MM_AKM_SUITE_NONE:
return "None";
case MM_AKM_SUITE_PSK:
return "PSK";
case MM_AKM_SUITE_SAE:
return "SAE";
case MM_AKM_SUITE_OWE:
return "OWE";
default:
return "Other";
}
}
int mm_find_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, uint8_t ie_type)
{
while ((search_offset + 2) <= ies_len) {
uint8_t type = ies[search_offset];
uint8_t length = ies[search_offset + 1];
if (type == ie_type) {
if ((search_offset + 2 + length) > ies_len) {
return -2;
}
return search_offset;
}
search_offset += 2 + length;
}
return -1;
}
int mm_find_vendor_specific_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, const uint8_t *id,
size_t id_len)
{
int offset = 0;
while ((search_offset + id_len) <= ies_len) {
offset = mm_find_ie_from_offset(ies, ies_len, search_offset, MM_VENDOR_SPECIFIC_IE_TYPE);
if (offset < 0) {
return offset;
}
uint8_t ie_type = ies[offset];
uint8_t ie_length = ies[offset + 1];
const uint8_t *ie_data = ies + (offset + 2);
if (ie_type == MM_VENDOR_SPECIFIC_IE_TYPE && id_len <= ie_length && (memcmp(id, ie_data, id_len) == 0)) {
if (((uint32_t)offset + 2 + ie_length) > ies_len) {
return -2;
}
return offset;
}
search_offset = 2 + ie_length + (uint32_t)offset;
}
return -1;
}
int mm_parse_rsn_information(const uint8_t *ies, uint32_t ies_len, struct mm_rsn_information *output)
{
uint8_t length;
uint16_t num_pairwise_cipher_suites;
uint16_t num_akm_suites;
uint16_t ii;
int offset = mm_find_ie(ies, ies_len, MM_RSN_INFORMATION_IE_TYPE);
memset(output, 0, sizeof(*output));
if (offset < 0) {
return offset;
}
/* Note that we rely on mm_find_ie() to validate that the IE does not extend past the end
* of the given buffer. */
length = ies[offset + 1];
offset += 2;
if (length < 8) {
printf("*WRN* RSN IE too short\n");
return -2;
}
/* Skip version field */
output->version = ies[offset] | ies[offset + 1] << 8;
offset += 2;
length -= 2;
output->group_cipher_suite = ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3];
offset += 4;
length -= 4;
num_pairwise_cipher_suites = ies[offset] | ies[offset + 1] << 8;
offset += 2;
length -= 2;
output->num_pairwise_cipher_suites = num_pairwise_cipher_suites;
if (num_pairwise_cipher_suites > MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES) {
output->num_pairwise_cipher_suites = MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES;
}
if (length < 4 * num_pairwise_cipher_suites + 2) {
printf("*WRN* RSN IE too short\n");
return -2;
}
for (ii = 0; ii < num_pairwise_cipher_suites; ii++) {
if (ii < output->num_pairwise_cipher_suites) {
output->pairwise_cipher_suites[ii] =
ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3];
}
offset += 4;
length -= 4;
}
num_akm_suites = ies[offset] | ies[offset + 1] << 8;
offset += 2;
length -= 2;
output->num_akm_suites = num_akm_suites;
if (num_akm_suites > MM_RSN_INFORMATION_MAX_AKM_SUITES) {
output->num_akm_suites = MM_RSN_INFORMATION_MAX_AKM_SUITES;
}
if (length < 4 * num_akm_suites + 2) {
printf("*WRN* RSN IE too short\n");
return -2;
}
for (ii = 0; ii < num_akm_suites; ii++) {
if (ii < output->num_akm_suites) {
output->akm_suites[ii] = ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3];
}
offset += 4;
length -= 4;
}
output->rsn_capabilities = ies[offset] | ies[offset + 1] << 8;
return 0;
}
#endif /* USE_MM_IOT_ESP32 */
-267
View File
@@ -1,267 +0,0 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mmhal.h"
#include "mmosal.h"
#include "driver/gpio.h"
#include "driver/spi_common.h"
#include "driver/spi_master.h"
#include "esp_random.h"
#include "esp_system.h"
/** 10x8bit training seq */
#define BYTE_TRAIN 16
/** SPI hw interrupt handler. Must be set before enabling irq */
static mmhal_irq_handler_t spi_irq_handler = NULL;
/** busy interrupt handler. Must be set before enabling irq */
static mmhal_irq_handler_t busy_irq_handler = NULL;
static spi_device_handle_t spi_handle;
static void wlan_hal_gpio_init(void)
{
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = ((1ull << CONFIG_MM_WAKE) | (1ull << CONFIG_MM_SPI_CS));
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
gpio_set_level(CONFIG_MM_WAKE, 0);
gpio_set_level(CONFIG_MM_SPI_CS, 0);
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ull << CONFIG_MM_BUSY);
io_conf.pull_down_en = 1;
gpio_config(&io_conf);
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ull << CONFIG_MM_SPI_IRQ);
io_conf.pull_down_en = 0;
gpio_config(&io_conf);
}
static void wlan_hal_spi_init(void)
{
esp_err_t ret;
spi_bus_config_t buscfg = {
.miso_io_num = CONFIG_MM_SPI_MISO,
.mosi_io_num = CONFIG_MM_SPI_MOSI,
.sclk_io_num = CONFIG_MM_SPI_SCK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
/* max_transfer_sz defaults to 4092 if 0 when DMA enabled, or to SOC_SPI_MAXIMUM_BUFFER_SIZE
* if DMA is disabled. */
.max_transfer_sz = 0,
.flags = SPICOMMON_BUSFLAG_MASTER,
};
ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK) {
printf("spi_bus_initialize failed\n");
}
/* Selected the highest available SPI clock speed that is still below the MM6108's maximum of
* 50MHz */
spi_device_interface_config_t dev_cfg = {
.clock_speed_hz = SPI_MASTER_FREQ_40M,
.mode = 0,
.spics_io_num = -1,
.queue_size = 1,
};
ret = spi_bus_add_device(SPI2_HOST, &dev_cfg, &spi_handle);
if (ret != ESP_OK) {
printf("spi_bus_add_device failed\n");
}
/* The actual clock frequency may not be the one that was set as it is re-calculated by the
* driver to the nearest hardware-compatible number. Importantly it is the "nearest", so it could be above
* the value set. */
int actual_freq_khz = 0;
spi_device_get_actual_freq(spi_handle, &actual_freq_khz);
printf("Actual SPI CLK %dkHz\n", actual_freq_khz);
}
static void wlan_hal_spi_deinit(void)
{
esp_err_t ret = spi_bus_remove_device(spi_handle);
if (ret != ESP_OK) {
printf("spi_bus_remove_device failed\n");
}
ret = spi_bus_free(SPI2_HOST);
if (ret != ESP_OK) {
printf("spi_bus_initialize failed\n");
}
}
/**
* Minium transfer length in bytes before interrupt based transactions are used. This is because
* there is some setup time associated with using the interrupt based method when compared to the
* polling method. In the cases where the difference in setup time exceeds the transaction duration
* it is more efficient to uses the polling method instead of the interrupt based one. The below
* equation was used to calculate this.
*
* (DMA_TRANSACTION_DURATION - POLL_TRANSACTION_DURATION) / (8/SPI_FREQ)
*
* The typical duration for the ESP32 can be found in the [transaction
* duration](https://docs.espressif.com/projects/esp-idf/en/v5.1.1/esp32s3/api-reference/peripherals/spi_master.html#transaction-duration)
* section of the docs.
*/
#define INTERRUPT_TRANSFER_MIN_LENGTH 75
static void spi_master_rw(const uint8_t *w_data, uint8_t *r_data, size_t len)
{
spi_transaction_t trans_desc = {
.rx_buffer = r_data,
.tx_buffer = w_data,
.length = (len * 8),
.flags = 0,
};
esp_err_t err;
if (len < INTERRUPT_TRANSFER_MIN_LENGTH) {
err = spi_device_polling_transmit(spi_handle, &trans_desc);
} else {
err = spi_device_transmit(spi_handle, &trans_desc);
}
if (err != ESP_OK) {
printf("SPI rw error = %x\n", err);
}
}
void mmhal_wlan_hard_reset(void)
{
gpio_set_level(CONFIG_MM_RESET_N, 0);
mmosal_task_sleep(5);
gpio_set_level(CONFIG_MM_RESET_N, 1);
mmosal_task_sleep(20);
}
void mmhal_wlan_spi_cs_assert(void)
{
gpio_set_level(CONFIG_MM_SPI_CS, 0);
}
void mmhal_wlan_spi_cs_deassert(void)
{
gpio_set_level(CONFIG_MM_SPI_CS, 1);
}
uint8_t mmhal_wlan_spi_rw(uint8_t data)
{
uint8_t readval;
spi_master_rw(&data, &readval, 1);
return readval;
}
void mmhal_wlan_spi_read_buf(uint8_t *buf, unsigned len)
{
spi_master_rw(NULL, buf, len);
}
void mmhal_wlan_spi_write_buf(const uint8_t *buf, unsigned len)
{
spi_master_rw(buf, NULL, len);
}
void mmhal_wlan_send_training_seq(void)
{
mmhal_wlan_spi_cs_deassert();
/* Send >74 clock pulses to card to stabilize CLK.
* This method of stacking up the TX data is described in RM0090 rev 19 Figure 253.
* It results is a reduction in the time between bytes of ~85% (316ns -> 48ns).
* Could not get this to work for the other transactions however.
*/
uint8_t buf[BYTE_TRAIN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
spi_master_rw(buf, NULL, BYTE_TRAIN);
}
void mmhal_wlan_register_spi_irq_handler(mmhal_irq_handler_t handler)
{
spi_irq_handler = handler;
gpio_isr_handler_add(CONFIG_MM_SPI_IRQ, (gpio_isr_t)spi_irq_handler, NULL);
}
bool mmhal_wlan_spi_irq_is_asserted(void)
{
return !gpio_get_level(CONFIG_MM_SPI_IRQ);
}
void mmhal_wlan_set_spi_irq_enabled(bool enabled)
{
if (enabled) {
gpio_set_intr_type(CONFIG_MM_SPI_IRQ, GPIO_INTR_LOW_LEVEL);
} else {
gpio_set_intr_type(CONFIG_MM_SPI_IRQ, GPIO_INTR_DISABLE);
}
}
void mmhal_wlan_init(void)
{
wlan_hal_gpio_init();
wlan_hal_spi_init();
/* Raise the RESET_N line to enable the WLAN transceiver. */
gpio_set_level(CONFIG_MM_RESET_N, 1);
}
void mmhal_wlan_deinit(void)
{
/* Lower the RESET_N line to disable the WLAN transceiver. This will put the transceiver in its
* lowest power state. */
gpio_set_level(CONFIG_MM_RESET_N, 0);
wlan_hal_spi_deinit();
/* Clean up any ISR handlers that have been added. These will be added again if the WLAN
* interface is brought back up. */
gpio_isr_handler_remove(CONFIG_MM_SPI_IRQ);
gpio_isr_handler_remove(CONFIG_MM_BUSY);
}
void mmhal_wlan_wake_assert(void)
{
gpio_set_level(CONFIG_MM_WAKE, 1);
}
void mmhal_wlan_wake_deassert(void)
{
gpio_set_level(CONFIG_MM_WAKE, 0);
}
bool mmhal_wlan_busy_is_asserted(void)
{
return gpio_get_level(CONFIG_MM_BUSY);
}
void mmhal_wlan_register_busy_irq_handler(mmhal_irq_handler_t handler)
{
busy_irq_handler = handler;
gpio_isr_handler_add(CONFIG_MM_BUSY, (gpio_isr_t)busy_irq_handler, NULL);
}
void mmhal_wlan_set_busy_irq_enabled(bool enabled)
{
if (enabled) {
gpio_set_intr_type(CONFIG_MM_BUSY, GPIO_INTR_POSEDGE);
} else {
gpio_set_intr_type(CONFIG_MM_BUSY, GPIO_INTR_DISABLE);
}
}
#endif /* USE_MM_IOT_ESP32 */
+6 -2
View File
@@ -191,10 +191,12 @@ echo
# PASS/FAIL — every hardware test would SKIP with "role not present". We
# narrow to tests/unit explicitly so the summary reads as "no hardware,
# unit suite only" instead of "big skip count looks suspicious".
# Keep terminal output condensed (`-q -r fE`) so skip-heavy runs do not print
# each skipped test in full; skip counts still appear in pytest's summary.
if [[ -z $DETECTED && $# -eq 0 ]]; then
echo "[pre-flight] no supported devices detected; running unit tier only."
echo
exec "$VENV_PY" -m pytest tests/unit -v --report-log=tests/reportlog.jsonl
exec "$VENV_PY" -m pytest tests/unit -q -r fE --report-log=tests/reportlog.jsonl
fi
# Default pytest args when the user passed none. Power users can invoke
@@ -210,11 +212,13 @@ fi
# skipping half the hardware tests with "not baked with session profile"
# errors. Power users who know their hardware is current and want to shave
# those seconds can pass `--assume-baked` explicitly.
# Defaults also use condensed reporting (`-q -r fE`) to avoid listing every
# skipped test verbatim while still surfacing failures/errors and summary data.
if [[ $# -eq 0 ]]; then
set -- tests/ \
--html=tests/report.html --self-contained-html \
--junitxml=tests/junit.xml \
-v --tb=short
-q -r fE --tb=short
fi
# UI tier requires opencv-python-headless (and ideally easyocr). If it's
+1 -1
View File
@@ -44,7 +44,7 @@ _ESP32_ARCHES = {
"esp32-c6",
"esp32c6",
}
_NRF52_ARCHES = {"nrf52", "nrf52840", "nrf52832"}
_NRF52_ARCHES = {"nrf52", "nrf52840"}
def _wait_port_free(port: str, *, timeout_s: float = 15.0, role: str = "") -> None:
+1
View File
@@ -34,6 +34,7 @@ BuildRequires: python3dist(grpcio-tools)
BuildRequires: git-core
BuildRequires: gcc-c++
BuildRequires: pkgconfig(yaml-cpp)
BuildRequires: pkgconfig(jsoncpp)
BuildRequires: pkgconfig(libgpiod)
BuildRequires: pkgconfig(bluez)
BuildRequires: pkgconfig(libusb-1.0)
+6 -7
View File
@@ -2,7 +2,7 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = tbeam
default_envs = heltec-v3
extra_configs =
variants/*/*.ini
@@ -17,6 +17,7 @@ test_build_src = true
extra_scripts =
pre:bin/platformio-pre.py
bin/platformio-custom.py
post:extra_scripts/nrf54l15_linker.py
; note: we add src to our include search path so that lmic_project_config can override
; note: TINYGPS_OPTION_NO_CUSTOM_FIELDS is VERY important. We don't use custom fields and somewhere in that pile
; of code is a heap corruption bug!
@@ -185,12 +186,16 @@ lib_deps =
https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/archive/refs/tags/v1.1.2.zip
# renovate: datasource=github-tags depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/SparkFun_ICM-20948_ArduinoLibrary
https://github.com/sparkfun/SparkFun_ICM-20948_ArduinoLibrary/archive/refs/tags/v1.3.2.zip
# renovate: datasource=github-tags depName=TDK InvenSense ICM42670P packageName=tdk-invn-oss/motion.arduino.ICM42670P
https://github.com/tdk-invn-oss/motion.arduino.ICM42670P/archive/refs/tags/1.0.8.zip
# renovate: datasource=github-tags depName=Adafruit LTR390 Library packageName=adafruit/Adafruit_LTR390
https://github.com/adafruit/Adafruit_LTR390/archive/refs/tags/1.1.2.zip
# renovate: datasource=github-tags depName=Adafruit PCT2075 packageName=adafruit/Adafruit_PCT2075
https://github.com/adafruit/Adafruit_PCT2075/archive/refs/tags/1.0.6.zip
# renovate: datasource=github-tags depName=DFRobot_BMM150 packageName=dfrobot/DFRobot_BMM150
https://github.com/DFRobot/DFRobot_BMM150/archive/refs/tags/V1.0.0.zip
# renovate: datasource=github-tags depName=SparkFun MMC5983MA Magnetometer packageName=sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library
https://github.com/sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library/archive/refs/tags/v1.1.4.zip
# renovate: datasource=github-tags depName=Adafruit_TSL2561 packageName=adafruit/Adafruit_TSL2561
https://github.com/adafruit/Adafruit_TSL2561/archive/refs/tags/1.1.3.zip
# renovate: datasource=github-tags depName=BH1750_WE packageName=wollewald/BH1750_WE
@@ -203,16 +208,10 @@ lib_deps =
https://github.com/adafruit/Adafruit_BMP3XX/archive/refs/tags/2.1.6.zip
# renovate: datasource=github-tags depName=Adafruit MAX1704X packageName=adafruit/Adafruit_MAX1704X
https://github.com/adafruit/Adafruit_MAX1704X/archive/refs/tags/1.0.3.zip
# renovate: datasource=github-tags depName=Adafruit SHTC3 packageName=adafruit/Adafruit_SHTC3
https://github.com/adafruit/Adafruit_SHTC3/archive/refs/tags/1.0.2.zip
# renovate: datasource=github-tags depName=Adafruit LPS2X packageName=adafruit/Adafruit_LPS2X
https://github.com/adafruit/Adafruit_LPS2X/archive/refs/tags/2.0.6.zip
# renovate: datasource=github-tags depName=Adafruit SHT31 packageName=adafruit/Adafruit_SHT31
https://github.com/adafruit/Adafruit_SHT31/archive/refs/tags/2.2.2.zip
# renovate: datasource=github-tags depName=Adafruit VEML7700 packageName=adafruit/Adafruit_VEML7700
https://github.com/adafruit/Adafruit_VEML7700/archive/refs/tags/2.1.6.zip
# renovate: datasource=github-tags depName=Adafruit SHT4x packageName=adafruit/Adafruit_SHT4X
https://github.com/adafruit/Adafruit_SHT4X/archive/refs/tags/1.0.5.zip
# renovate: datasource=github-tags depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/SparkFun_Qwiic_Scale_NAU7802_Arduino_Library
https://github.com/sparkfun/SparkFun_Qwiic_Scale_NAU7802_Arduino_Library/archive/refs/tags/v1.0.6.zip
# renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001
+5 -4
View File
@@ -152,15 +152,16 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
// Default Bluetooth PIN
#define defaultBLEPin 123456
#if HAS_ETHERNET && defined(USE_CH390D)
#if HAS_ETHERNET && defined(USE_ARDUINO_ETHERNET)
#include <Ethernet.h> // arduino-libraries/Ethernet — supports W5500 auto-detect
#elif HAS_ETHERNET && defined(USE_CH390D)
#include <ESP32_CH390.h>
#elif HAS_ETHERNET && !defined(USE_WS5500)
#include <RAK13800_W5100S.h>
#endif // HAS_ETHERNET
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#endif // HAS_ETHERNET
#if HAS_WIFI
+28 -9
View File
@@ -1,4 +1,5 @@
#include "DisplayFormatters.h"
#include "MeshRadio.h"
const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
bool usePreset)
@@ -11,33 +12,51 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
}
switch (preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
case PRESET(SHORT_TURBO):
return useShortName ? "ShortT" : "ShortTurbo";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
case PRESET(SHORT_SLOW):
return useShortName ? "ShortS" : "ShortSlow";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
case PRESET(SHORT_FAST):
return useShortName ? "ShortF" : "ShortFast";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
case PRESET(MEDIUM_SLOW):
return useShortName ? "MedS" : "MediumSlow";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
case PRESET(MEDIUM_FAST):
return useShortName ? "MedF" : "MediumFast";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
case PRESET(LONG_SLOW):
return useShortName ? "LongS" : "LongSlow";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
case PRESET(LONG_FAST):
return useShortName ? "LongF" : "LongFast";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
case PRESET(LONG_TURBO):
return useShortName ? "LongT" : "LongTurbo";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
case PRESET(LONG_MODERATE):
return useShortName ? "LongM" : "LongMod";
break;
case PRESET(LITE_FAST):
return useShortName ? "LiteF" : "LiteFast";
break;
case PRESET(LITE_SLOW):
return useShortName ? "LiteS" : "LiteSlow";
break;
case PRESET(NARROW_FAST):
return useShortName ? "NarF" : "NarrowFast";
break;
case PRESET(NARROW_SLOW):
return useShortName ? "NarS" : "NarrowSlow";
break;
case PRESET(TINY_FAST):
return useShortName ? "TinyF" : "TinyFast";
break;
case PRESET(TINY_SLOW):
return useShortName ? "TinyS" : "TinySlow";
break;
default:
return useShortName ? "Custom" : "Invalid";
break;
+1 -1
View File
@@ -277,7 +277,7 @@ void fsInit()
*/
void setupSDCard()
{
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI) && !defined(HAS_SD_MMC)
concurrency::LockGuard g(spiLock);
SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
if (!SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY)) {

Some files were not shown because too many files have changed in this diff Show More