1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Board support crate for the **M5Stack Fire27** (ESP32) and **CoreS3**
//! (ESP32-S3).
//!
//! Provides chip-agnostic peripheral drivers ([`driver`]), a shared async I2C
//! bus and reusable `embassy`-based IO task loops ([`io`]), and board bring-up
//! helpers ([`board`]).
//!
//! The crate also owns the board/chip boilerplate a binary would otherwise
//! hand-roll, so a consumer's `main` collapses to a thin entry shell:
//!
//! - [`mem::init_heap`] — the global heap (esp-alloc DRAM regions + HIL-proven
//! per-board sizes; `heap` feature, implied by `psram`).
//! - [`io::console::install`] — one-call logging over the chip's native
//! transport (UART0 / USB-Serial-JTAG CDC) + an RTC panic breadcrumb
//! ([`io::console::take_panic_breadcrumb`]) + (`app-desc` feature) the
//! firmware identity, logged once as the very first BSP-emitted line.
//! - the `panic-handler` feature exports the `#[panic_handler]`, and
//! [`app_desc!`] the esp-idf app descriptor (`app-desc` feature, implied by
//! `heap`) — plus [`app_elf_sha256`], and, under `identity`, an enforced
//! build-time git identity in `app_desc!()`'s version field (see the
//! README's "Firmware identity" section).
//! - [`board::run_app_core`] — the second-core harness (`multicore` feature).
//! - [`io::input_caps`] — the board's input model (keypad vs pointer), so a UI
//! installs the matching indev without hardcoding the board.
//!
//! Exactly one board feature must be enabled: `fire27` (xtensa-esp32) or
//! `cores3` (xtensa-esp32s3). Radio (`ble`/`wifi`/`wifi-sta`/`coex`), `heap`/
//! `psram`/`app-desc`/`identity`, `console-serial`, `panic-handler`, and
//! `multicore` are orthogonal opt-ins. See the README for the full feature
//! matrix and usage examples.
// `PsramSafe` (mem::, `psram` feature) is a Send/Sync-style auto trait with
// negative impls for the atomic types. The item is `#[cfg(psram)]`, but cfg
// stripping happens *after* parsing, so the `auto trait` syntax is parsed even
// in a psram-free `heap` build and would warn unless the gate is active. The
// crate is nightly-only regardless, so enable it unconditionally.
// `board::run_app_core`'s APP-core idle loop uses the Xtensa `waiti` instruction
// via inline asm, which is still unstable for this architecture.
// Link-only: pins esp-rom-sys to the version esp-hal's code actually needs
// (esp-hal 1.1.x under-constrains it to ~0.1 but calls a 0.1.4 API). Referenced
// here so the pin in Cargo.toml survives `cargo package` rather than being
// dropped as an unused dependency.
use esp_rom_sys as _;
/// Replaces embassy-executor's `Spawner::must_spawn`, dropped in 0.10: panics
/// on pool exhaustion with call-site context. Works for `Spawner` and
/// `SendSpawner`.
/// BSP-provided `#[panic_handler]` (opt in with the `panic-handler` feature).
/// Body is [`io::console::on_panic`]: record the RTC breadcrumb, best-effort
/// drain the ring over the raw transport, then halt and let the RWDT recover.
/// A consumer that wants its own panic policy simply leaves the feature off.
!
// `app_desc!` wraps esp-bootloader-esp-idf's `esp_app_desc!`. The bootloader
// crate is pulled by the `app-desc` feature (implied by `heap`); re-exported
// (hidden) so the macro can name it from the call site without the binary
// depending on it directly.
pub use esp_bootloader_esp_idf as __bootloader;
/// Reads back the descriptor [`app_desc!`] emits.
///
/// [`app_desc!`] expands to a `static` named `ESP_APP_DESC` **at its call
/// site** (i.e. in the binary, not this crate), so this reads it back by its
/// linker symbol (`esp_app_desc`, `EspAppDesc` is `#[repr(C)]`) rather than by
/// path — the one way a BSP function can reach a descriptor the *consumer*
/// created. Requires [`app_desc!`] to have been invoked somewhere in the
/// binary: otherwise this fails to **link**, not silently reads zeroes.
///
/// This indirection matters beyond just "how do we reach it": reading
/// `ESP_APP_DESC` via an `extern` symbol, rather than by path from *inside*
/// the same crate that defines it, is what keeps [`app_elf_sha256`] correct.
/// `espflash` patches that field into the flashed image **after**
/// compilation — the compiler only ever sees the macro's zero initializer.
/// A same-crate path read of a `static` with a compiler-visible initializer
/// is free to const-fold to that initializer (needing a `read_volatile` to
/// stop it); an `extern` read of a symbol whose initializer lives in a
/// *different* compilation unit has no initializer to see in the first
/// place, so the hazard cannot arise — no volatile needed. `version` has no
/// such concern (nothing patches it post-link), but `app_elf_sha256` does:
/// don't "simplify" this back to a path reference.
/// Bytes budget for [`__pkg_version_bytes`] — plenty for any real semver
/// string (`"0.4.3"` is 5 bytes; `"1.0.0-beta.12"` is 13). Unlike the
/// identity mark, a too-long crate version here is a cosmetic display
/// nicety, not an identity-tracking risk, so it's a silent truncation, not
/// enforced by a compile-time assertion the way `app_desc!`'s mark is.
pub const PKG_VERSION_BYTES: usize = 16;
/// Zero-pads/truncates `s` into a fixed-size, C-safe byte array — the same
/// shape `EspAppDesc`'s own fields use, chosen deliberately: a plain `&str`
/// (a fat pointer) has no stable ABI for an `extern` static to read back
/// across the crate boundary the way [`app_desc()`] reads `EspAppDesc`
/// (which is `#[repr(C)]`); a fixed byte array does. Used only by
/// [`app_desc!`]'s expansion, to export `CARGO_PKG_VERSION` for
/// [`pkg_version()`] to read back — needed because `identity` repurposes
/// `EspAppDesc::version` for the git mark, so the crate version isn't
/// available there anymore.
pub const
/// Reads back the `CARGO_PKG_VERSION` [`app_desc!`] exports alongside the
/// descriptor — see [`__str_to_fixed`] for why this needs its own static
/// rather than reusing [`app_desc()`]'s mechanism.
///
/// Gated on `identity`, not merely `app-desc`: without `identity` the
/// descriptor's own `version` field still holds `CARGO_PKG_VERSION`, so
/// [`log_boot_identity`] reads it straight from there and this reader has no
/// caller. `app_desc!` exports the symbol in **both** cases, so a consumer that
/// wants it can still link against it — only this crate's private reader is
/// conditional.
/// The ELF's content hash, straight from the esp-idf application descriptor —
/// the first bytes distinguish two images built from the same commit with
/// different uncommitted edits. Unambiguous with no consumer input needed: it
/// is a function of the linked image, computed and patched in by `espflash`.
/// Requires [`app_desc!`] to have been invoked (see [`app_desc()`]).
/// Logs the descriptor's version (plain `CARGO_PKG_VERSION`, or the enforced
/// `<pkg>/<bin>/<features>/<hash><dirty>` git mark under `identity` — which
/// already carries the binary name, so it isn't repeated separately there),
/// the crate's `version=` (always the real `CARGO_PKG_VERSION`, even under
/// `identity` where the descriptor's own version field no longer holds it —
/// see [`pkg_version()`]), and a 6-byte `app_elf_sha256` prefix, once, as
/// early as possible on boot — called by [`io::console::install`], not meant
/// to be called directly. Like [`app_elf_sha256`], requires [`app_desc!`] to
/// have been invoked somewhere in the binary (a link error otherwise, not a
/// silent no-op): any binary enabling `app-desc` — directly, or via `heap` —
/// is expected to call [`app_desc!`], matching this crate's existing "thin
/// entry shell" framing.
pub
/// Emit the esp-idf application descriptor. Invoke once **in the binary** (not
/// the BSP) so it captures the *application's* `CARGO_PKG_VERSION`. Thin wrapper
/// over `esp_bootloader_esp_idf::esp_app_desc!` so the binary keeps a single
/// `m5stack_core::app_desc!();` line instead of naming the bootloader crate.
///
/// With the `identity` feature off (default), the descriptor's version field
/// is plain `CARGO_PKG_VERSION`, as always. With `identity` on, this same call
/// site — unchanged — instead requires `M5STACK_CORE_BUILD_MARK`, a build-time
/// git descriptor the consumer's own `build.rs` sets (see the
/// `m5stack-core-build` crate). BSP owns the mechanism, never the content: if
/// the `build.rs` wiring is missing, `env!()` fails to compile in the
/// *consumer's* crate — a real compile error, not a silent fallback.
///
/// `project_name` is `CARGO_BIN_NAME`, not `CARGO_PKG_NAME`: a package can
/// have more than one `[[bin]]`, and only the per-binary compilation (where
/// this macro expands) knows which one is being built — `CARGO_PKG_NAME`
/// would report the same value for every binary in a multi-bin package.
///
/// Also exports `CARGO_PKG_VERSION` under its own linker symbol (see
/// [`__str_to_fixed`]) — even though the descriptor's own `version` field
/// already holds it here, `identity`'s arm below doesn't have that field
/// free, so both arms export it the same way for [`log_boot_identity`] /
/// [`pkg_version()`] to read uniformly.
/// See the non-`identity` [`app_desc!`] above — same default call site. The
/// version field becomes `CARGO_PKG_NAME` + `CARGO_BIN_NAME` joined with the
/// git mark (`<pkg>/<bin>/<features>/<hash><dirty>`, e.g.
/// `demos/display/crypto-opt/0f63a4926303+`) — both names included for the
/// same reason as `project_name` above (a package can have more than one
/// `[[bin]]`, and disambiguating *which package* matters too once several
/// crates in a workspace all use `identity`), joined here (rather than by
/// `m5stack-core-build`) because only this per-binary compilation knows
/// `CARGO_BIN_NAME`; a `build.rs` runs once per *package* and can't know
/// which binary it's describing. `CARGO_PKG_VERSION` is exported separately
/// (see [`__str_to_fixed`]) since the descriptor's `version` field is now
/// the mark, not the crate version.
///
/// `EspAppDesc::version` is a fixed 32-byte C string with no reserved NUL
/// terminator (see `m5stack-core-build`'s docs) — 31 bytes is the true safe
/// ceiling, and real package/binary names routinely don't leave room for a
/// features tag alongside a 12-hex commit (a package/binary pair over ~17
/// bytes combined already eats the whole budget). Enforced here as a real
/// compile error, not a silent truncation.
///
/// **`app_desc!("prefix")`** — an optional string-literal argument replaces
/// the automatic `CARGO_PKG_NAME`/`CARGO_BIN_NAME` join with exactly what you
/// pass, e.g. `app_desc!("oxichg/evcc-hl")`. This is the lever for a project
/// whose real names don't fit: nothing here truncates or abbreviates on your
/// behalf (an automatic scheme is a guess, and a wrong guess in an identity
/// string is worse than no identity — see `#43`'s own reasoning for not
/// letting the BSP derive a commit itself), so *you* decide what the prefix
/// says and how long it is. `project_name` still reports the real
/// `CARGO_BIN_NAME` regardless — only the mark's prefix is overridable.
/// Default (no argument) is unaffected and unchanged for every existing
/// caller.