espflash/
error.rs

1//! Library and application errors
2
3#[cfg(feature = "serialport")]
4use std::fmt::{Display, Formatter};
5
6use miette::Diagnostic;
7use std::io;
8use strum::VariantNames;
9use thiserror::Error;
10
11#[cfg(feature = "cli")]
12use crate::cli::monitor::parser::esp_defmt::DefmtError;
13#[cfg(feature = "serialport")]
14use crate::command::CommandType;
15use crate::{
16    flasher::{FlashFrequency, FlashSize},
17    targets::Chip,
18};
19#[cfg(feature = "serialport")]
20use slip_codec::SlipError;
21
22/// All possible errors returned by espflash
23#[derive(Debug, Diagnostic, Error)]
24#[non_exhaustive]
25pub enum Error {
26    #[error("App partition not found")]
27    #[diagnostic(code(espflash::app_partition_not_found))]
28    AppPartitionNotFound,
29
30    #[error("Operation was cancelled by the user")]
31    #[diagnostic(code(espflash::cancelled))]
32    Cancelled,
33
34    #[error("Unrecognized magic value: {0:#x}")]
35    #[diagnostic(
36        code(espflash::chip_detect_error),
37        help("Supported chips are: {}\n\
38              If your chip is supported, try hard-resetting the device and try again",
39             Chip::VARIANTS.join(", "))
40    )]
41    ChipDetectError(u32),
42
43    #[error("Chip provided ({0}) with `-c/--chip` does not match the detected chip ({1})")]
44    #[diagnostic(
45        code(espflash::chip_missmatch),
46        help("Ensure that the correct chip is selected, or remove the `-c/--chip` option to autodetect the chip")
47    )]
48    ChipMismatch(String, String),
49
50    #[error("Chip not argument provided, this is required when using the `--before no-reset-no-sync` option")]
51    #[diagnostic(
52        code(espflash::chip_not_provided),
53        help("Ensure that you provide the `-c/--chip` option with the proper chip")
54    )]
55    ChipNotProvided,
56
57    #[error("Corrupt data, expected {0:2x?} bytes but receved {1:2x?} bytes")]
58    #[diagnostic(code(espflash::read_flash::corrupt_data))]
59    CorruptData(usize, usize),
60
61    #[error("MD5 digest missmatch: expected {0:2x?}, received: {1:2x?}")]
62    #[diagnostic(code(espflash::read_flash::digest_missmatch))]
63    DigestMissmatch(Vec<u8>, Vec<u8>),
64
65    #[error("Supplied ELF image can not be run from RAM, as it includes segments mapped to ROM addresses")]
66    #[diagnostic(
67        code(espflash::not_ram_loadable),
68        help("Either build the binary to be all in RAM, or remove the `--ram` option to load the image to flash")
69    )]
70    ElfNotRamLoadable,
71
72    #[error(
73        "Supplied ELF image of {0}B is too big, and doesn't fit configured app partition of {1}B"
74    )]
75    #[diagnostic(
76        code(espflash::image_too_big),
77        help("Reduce the size of the binary or increase the size of the app partition."),
78        url("https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html#built-in-partition-tables")
79    )]
80    ElfTooBig(u32, u32),
81
82    #[error("Failed to connect to on-device flash")]
83    #[diagnostic(code(espflash::flash_connect))]
84    FlashConnect,
85
86    #[error("Expected MD5 digest (16 bytes), received: {0:#x} bytes")]
87    #[diagnostic(code(espflash::read_flash::incorrect_digest_length))]
88    IncorrectDigestLength(usize),
89
90    #[error("Incorrect response from the sutb/ROM loader")]
91    #[diagnostic(code(espflash::read_flash::incorrect_response))]
92    IncorrectReposnse,
93
94    #[error("The provided bootloader binary is invalid")]
95    InvalidBootloader,
96
97    #[error("Specified bootloader path is not a .bin file")]
98    #[diagnostic(code(espflash::invalid_bootloader_path))]
99    InvalidBootloaderPath,
100
101    #[error("The flash size '{0}' is invalid")]
102    #[diagnostic(
103        code(espflash::invalid_flash_size),
104        help("The accepted values are: {:?}", FlashSize::VARIANTS)
105    )]
106    InvalidFlashSize(String),
107
108    #[cfg(not(feature = "serialport"))]
109    #[error(transparent)]
110    IoError(#[from] io::Error),
111
112    #[error("Specified partition table path is not a .bin or .csv file")]
113    #[diagnostic(code(espflash::invalid_partition_table_path))]
114    InvalidPartitionTablePath,
115
116    #[error("No serial ports could be detected")]
117    #[diagnostic(
118        code(espflash::no_serial),
119        help("Make sure you have connected a device to the host system. If the device is connected but not listed, try using the `--list-all-ports` flag.")
120    )]
121    NoSerial,
122
123    #[error("Read more bytes than expected")]
124    #[diagnostic(code(espflash::read_flash::read_more_than_expected))]
125    ReadMoreThanExpected,
126
127    #[error("This command requires using the RAM stub")]
128    #[diagnostic(
129        code(espflash::stub_required),
130        help("Don't use the `--no-stub` option with the command")
131    )]
132    StubRequired,
133
134    #[error("The serial port '{0}' could not be found")]
135    #[diagnostic(
136        code(espflash::serial_not_found),
137        help("Make sure the correct device is connected to the host system")
138    )]
139    SerialNotFound(String),
140
141    #[error("The {chip} does not support {feature}")]
142    #[diagnostic(code(espflash::unsupported_feature))]
143    UnsupportedFeature { chip: Chip, feature: String },
144
145    #[error("Flash chip not supported, unrecognized flash ID: {0:#x}")]
146    #[diagnostic(code(espflash::unrecognized_flash))]
147    UnsupportedFlash(u8),
148
149    #[error("The specified flash frequency '{frequency}' is not supported by the {chip}")]
150    #[diagnostic(code(espflash::unsupported_flash_frequency))]
151    UnsupportedFlashFrequency {
152        chip: Chip,
153        frequency: FlashFrequency,
154    },
155
156    #[error(
157        "Minimum supported revision is {major}.{minor}, connected device's revision is {found_major}.{found_minor}"
158    )]
159    #[diagnostic(code(espflash::unsupported_chip_revision))]
160    UnsupportedChipRevision {
161        major: u16,
162        minor: u16,
163        found_major: u16,
164        found_minor: u16,
165    },
166
167    #[error("Failed to parse chip revision: {chip_rev}. Chip revision must be in the format `major.minor`")]
168    #[diagnostic(code(espflash::cli::parse_chip_rev_error))]
169    ParseChipRevError { chip_rev: String },
170
171    #[error("Error while connecting to device")]
172    #[diagnostic(transparent)]
173    Connection(#[source] ConnectionError),
174
175    #[error("Communication error while flashing device")]
176    #[diagnostic(transparent)]
177    Flashing(#[source] ConnectionError),
178
179    #[error("Supplied ELF image is not valid")]
180    #[diagnostic(
181        code(espflash::invalid_elf),
182        help("Try running `cargo clean` and rebuilding the image")
183    )]
184    InvalidElf(#[from] ElfError),
185
186    #[error("The bootloader returned an error")]
187    #[cfg(feature = "serialport")]
188    #[diagnostic(transparent)]
189    RomError(#[from] RomError),
190
191    #[cfg(feature = "cli")]
192    #[error(transparent)]
193    #[diagnostic(transparent)]
194    Defmt(#[from] DefmtError),
195
196    #[error("Verification of flash content failed")]
197    #[diagnostic(code(espflash::verify_failed))]
198    VerifyFailed,
199
200    #[cfg(feature = "cli")]
201    #[error(transparent)]
202    #[diagnostic(code(espflash::dialoguer_error))]
203    DialoguerError(#[from] dialoguer::Error),
204
205    #[error("Internal Error")]
206    InternalError,
207
208    #[error("Failed to open file: {0}")]
209    FileOpenError(String, #[source] io::Error),
210
211    #[error("Failed to parse partition table")]
212    Partition(#[from] esp_idf_part::Error),
213}
214
215#[cfg(feature = "serialport")]
216impl From<io::Error> for Error {
217    fn from(err: io::Error) -> Self {
218        Self::Connection(err.into())
219    }
220}
221
222#[cfg(feature = "serialport")]
223#[cfg_attr(docsrs, doc(cfg(feature = "serialport")))]
224impl From<serialport::Error> for Error {
225    fn from(err: serialport::Error) -> Self {
226        Self::Connection(err.into())
227    }
228}
229
230#[cfg(feature = "serialport")]
231impl From<SlipError> for Error {
232    fn from(err: SlipError) -> Self {
233        Self::Connection(err.into())
234    }
235}
236
237/// Connection-related errors
238#[derive(Debug, Diagnostic, Error)]
239#[non_exhaustive]
240pub enum ConnectionError {
241    #[error("Failed to connect to the device")]
242    #[diagnostic(
243        code(espflash::connection_failed),
244        help("Ensure that the device is connected and the reset and boot pins are not being held down")
245    )]
246    ConnectionFailed,
247
248    #[error("Serial port not found")]
249    #[diagnostic(
250        code(espflash::connection_failed),
251        help("Ensure that the device is connected and your host recognizes the serial adapter")
252    )]
253    DeviceNotFound,
254
255    #[error("Received packet has invalid SLIP framing")]
256    #[diagnostic(
257        code(espflash::slip_framing),
258        help("Try hard-resetting the device and try again, if the error persists your ROM may be corrupted")
259    )]
260    FramingError,
261
262    #[error("Invalid stub handshake response received")]
263    InvalidStubHandshake,
264
265    #[error("Download mode successfully detected, but getting no sync reply")]
266    #[diagnostic(
267        code(espflash::no_sync_reply),
268        help("The serial TX path seems to be down")
269    )]
270    NoSyncReply,
271
272    #[error("Received packet to large for buffer")]
273    #[diagnostic(
274        code(espflash::oversized_packet),
275        help("Try hard-resetting the device and try again, if the error persists your ROM may be corrupted")
276    )]
277    OverSizedPacket,
278
279    #[error("Failed to read the available bytes on the serial port. Available bytes: {0}, Read bytes: {1}")]
280    #[diagnostic(code(espflash::read_missmatch))]
281    ReadMissmatch(u32, u32),
282
283    #[cfg(feature = "serialport")]
284    #[error("Timeout while running {0}command")]
285    #[diagnostic(code(espflash::timeout))]
286    Timeout(TimedOutCommand),
287
288    #[cfg(feature = "serialport")]
289    #[error("IO error while using serial port: {0}")]
290    #[diagnostic(code(espflash::serial_error))]
291    Serial(#[source] serialport::Error),
292
293    #[error("Wrong boot mode detected ({0})! The chip needs to be in download mode.")]
294    #[diagnostic(code(espflash::wrong_boot_mode))]
295    WrongBootMode(String),
296}
297
298#[cfg(feature = "serialport")]
299impl From<io::Error> for ConnectionError {
300    fn from(err: io::Error) -> Self {
301        from_error_kind(err.kind(), err)
302    }
303}
304
305#[cfg(feature = "serialport")]
306#[cfg_attr(docsrs, doc(cfg(feature = "serialport")))]
307impl From<serialport::Error> for ConnectionError {
308    fn from(err: serialport::Error) -> Self {
309        use serialport::ErrorKind;
310
311        match err.kind() {
312            ErrorKind::Io(kind) => from_error_kind(kind, err),
313            ErrorKind::NoDevice => ConnectionError::DeviceNotFound,
314            _ => ConnectionError::Serial(err),
315        }
316    }
317}
318
319#[cfg(feature = "serialport")]
320impl From<SlipError> for ConnectionError {
321    fn from(err: SlipError) -> Self {
322        match err {
323            SlipError::FramingError => Self::FramingError,
324            SlipError::OversizedPacket => Self::OverSizedPacket,
325            SlipError::ReadError(io) => Self::from(io),
326            SlipError::EndOfStream => Self::FramingError,
327        }
328    }
329}
330
331/// An executed command which has timed out
332#[derive(Clone, Debug, Default)]
333#[cfg(feature = "serialport")]
334pub struct TimedOutCommand {
335    command: Option<CommandType>,
336}
337
338#[cfg(feature = "serialport")]
339impl Display for TimedOutCommand {
340    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
341        match &self.command {
342            Some(command) => write!(f, "{} ", command),
343            None => Ok(()),
344        }
345    }
346}
347
348#[cfg(feature = "serialport")]
349impl From<CommandType> for TimedOutCommand {
350    fn from(ct: CommandType) -> Self {
351        TimedOutCommand { command: Some(ct) }
352    }
353}
354
355/// Errors originating from a device's ROM functionality
356#[derive(Clone, Copy, Debug, Default, Diagnostic, Error, strum::FromRepr)]
357#[non_exhaustive]
358#[repr(u8)]
359#[cfg(feature = "serialport")]
360pub enum RomErrorKind {
361    #[error("Invalid message received")]
362    #[diagnostic(code(espflash::rom::invalid_message))]
363    InvalidMessage = 0x05,
364
365    #[error("Bootloader failed to execute command")]
366    #[diagnostic(code(espflash::rom::failed))]
367    FailedToAct = 0x06,
368
369    #[error("Received message has invalid CRC")]
370    #[diagnostic(code(espflash::rom::crc))]
371    InvalidCrc = 0x07,
372
373    #[error("Bootloader failed to write to flash")]
374    #[diagnostic(code(espflash::rom::flash_write))]
375    FlashWriteError = 0x08,
376
377    #[error("Bootloader failed to read from flash")]
378    #[diagnostic(code(espflash::rom::flash_read))]
379    FlashReadError = 0x09,
380
381    #[error("Invalid length for flash read")]
382    #[diagnostic(code(espflash::rom::flash_read_length))]
383    FlashReadLengthError = 0x0a,
384
385    #[error("Malformed compressed data received")]
386    #[diagnostic(code(espflash::rom::deflate))]
387    DeflateError = 0x0b,
388
389    #[error("Bad data length")]
390    #[diagnostic(code(espflash::rom::data_len))]
391    BadDataLen = 0xc0,
392
393    #[error("Bad data checksum")]
394    #[diagnostic(code(espflash::rom::data_crc))]
395    BadDataChecksum = 0xc1,
396
397    #[error("Bad block size")]
398    #[diagnostic(code(espflash::rom::block_size))]
399    BadBlocksize = 0xc2,
400
401    #[error("Invalid command")]
402    #[diagnostic(code(espflash::rom::cmd))]
403    InvalidCommand = 0xc3,
404
405    #[error("SPI operation failed")]
406    #[diagnostic(code(espflash::rom::spi))]
407    FailedSpiOp = 0xc4,
408
409    #[error("SPI unlock failed")]
410    #[diagnostic(code(espflash::rom::spi_unlock))]
411    FailedSpiUnlock = 0xc5,
412
413    #[error("Not in flash mode")]
414    #[diagnostic(code(espflash::rom::flash_mode))]
415    NotInFlashMode = 0xc6,
416
417    #[error("Error when uncompressing the data")]
418    #[diagnostic(code(espflash::rom::inflate))]
419    InflateError = 0xc7,
420
421    #[error("Didn't receive enough data")]
422    #[diagnostic(code(espflash::rom::not_enough))]
423    NotEnoughData = 0xc8,
424
425    #[error("Received too much data")]
426    #[diagnostic(code(espflash::rom::too_much_data))]
427    TooMuchData = 0xc9,
428
429    #[default]
430    #[error("Other")]
431    #[diagnostic(code(espflash::rom::other))]
432    Other = 0xff,
433}
434
435#[cfg(feature = "serialport")]
436impl From<u8> for RomErrorKind {
437    fn from(raw: u8) -> Self {
438        Self::from_repr(raw).unwrap_or_default()
439    }
440}
441
442/// An error originating from a device's ROM functionality
443#[derive(Clone, Copy, Debug, Diagnostic, Error)]
444#[error("Error while running {command} command")]
445#[cfg(feature = "serialport")]
446#[non_exhaustive]
447pub struct RomError {
448    command: CommandType,
449    #[source]
450    kind: RomErrorKind,
451}
452
453#[cfg(feature = "serialport")]
454impl RomError {
455    pub fn new(command: CommandType, kind: RomErrorKind) -> RomError {
456        RomError { command, kind }
457    }
458}
459
460/// Missing partition error
461#[derive(Debug, Diagnostic, Error)]
462#[error("Missing partition")]
463#[diagnostic(
464    code(espflash::partition_table::missing_partition),
465    help("Partition table must contain the partition of type `{0}` to be erased")
466)]
467pub struct MissingPartition(String);
468
469impl From<String> for MissingPartition {
470    fn from(part: String) -> Self {
471        MissingPartition(part)
472    }
473}
474
475/// Missing partition table error
476#[derive(Debug, Error, Diagnostic)]
477#[error("No partition table could be found")]
478#[diagnostic(
479    code(espflash::partition_table::missing_partition_table),
480    help("Try providing a CSV or binary paritition table with the `--partition-table` argument.")
481)]
482pub struct MissingPartitionTable;
483
484/// Invalid ELF file error
485#[derive(Debug, Error)]
486#[error("{0}")]
487pub struct ElfError(&'static str);
488
489impl From<&'static str> for ElfError {
490    fn from(err: &'static str) -> Self {
491        ElfError(err)
492    }
493}
494
495#[cfg(feature = "serialport")]
496pub(crate) trait ResultExt {
497    /// Mark an error as having occurred during the flashing stage
498    fn flashing(self) -> Self;
499    /// Mark the command from which this error originates
500    fn for_command(self, command: CommandType) -> Self;
501}
502
503#[cfg(feature = "serialport")]
504impl<T> ResultExt for Result<T, Error> {
505    fn flashing(self) -> Self {
506        match self {
507            Err(Error::Connection(err)) => Err(Error::Flashing(err)),
508            res => res,
509        }
510    }
511
512    fn for_command(self, command: CommandType) -> Self {
513        match self {
514            Err(Error::Connection(ConnectionError::Timeout(_))) => {
515                Err(Error::Connection(ConnectionError::Timeout(command.into())))
516            }
517            Err(Error::Flashing(ConnectionError::Timeout(_))) => {
518                Err(Error::Flashing(ConnectionError::Timeout(command.into())))
519            }
520            res => res,
521        }
522    }
523}
524
525#[cfg(feature = "serialport")]
526#[cfg_attr(docsrs, doc(cfg(feature = "serialport")))]
527fn from_error_kind<E>(kind: io::ErrorKind, err: E) -> ConnectionError
528where
529    E: Into<serialport::Error>,
530{
531    use io::ErrorKind;
532
533    match kind {
534        ErrorKind::TimedOut => ConnectionError::Timeout(TimedOutCommand::default()),
535        ErrorKind::NotFound => ConnectionError::DeviceNotFound,
536        _ => ConnectionError::Serial(err.into()),
537    }
538}