1 /*
2 * This file is part of the flashrom project.
3 *
4 * Copyright (C) 2022 The Chromium OS Authors
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16
17 //! # libflashrom
18 //!
19 //! The `libflashrom` library is a rust FFI binding to the flashrom library.
20 //! libflashrom can be used to read write and modify some settings of flash chips.
21 //! The library closely follows the libflashrom C API, but exports a `safe` interface
22 //! including automatic resource management and forced error checking.
23 //!
24 //! libflashrom does not support threading, all usage of this library must occur on one thread.
25 //!
26 //! Most of the library functionality is defined on the [`Chip`] type.
27 //!
28 //! Example:
29 //!
30 //! ```
31 //! use libflashrom::*;
32 //! let mut chip = Chip::new(
33 //! Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(),
34 //! Some("W25Q128.V")
35 //! ).unwrap();
36 //! let mut buf = chip.image_read(None).unwrap();
37 //! buf[0] = 0xFE;
38 //! chip.image_write(&mut buf, None).unwrap();
39 //! ```
40
41 use once_cell::sync::Lazy;
42 use regex::Regex;
43 use std::error;
44 use std::ffi::c_void;
45 use std::ffi::CStr;
46 use std::ffi::CString;
47 use std::fmt;
48 use std::io::Write;
49 use std::ptr::null;
50 use std::ptr::null_mut;
51 use std::ptr::NonNull;
52 use std::sync::Mutex;
53 use std::sync::Once;
54
55 pub use libflashrom_sys::{
56 flashrom_log_level, FLASHROM_MSG_DEBUG, FLASHROM_MSG_DEBUG2, FLASHROM_MSG_ERROR,
57 FLASHROM_MSG_INFO, FLASHROM_MSG_SPEW, FLASHROM_MSG_WARN,
58 };
59
60 pub use libflashrom_sys::flashrom_wp_mode;
61
62 // libflashrom uses (start, len) or inclusive [start, end] for ranges.
63 // This type exists to rust RangeBounds types to a convenient internal format.
64 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
65 struct RangeInternal {
66 start: usize,
67 len: usize,
68 }
69
70 /// The type returned for write protect and layout queries
71 pub type Range = std::ops::Range<usize>;
72
73 impl<T> From<T> for RangeInternal
74 where
75 T: std::ops::RangeBounds<usize>,
76 {
from(range: T) -> Self77 fn from(range: T) -> Self {
78 let start = match range.start_bound() {
79 std::ops::Bound::Included(start) => *start,
80 std::ops::Bound::Excluded(start) => *start + 1,
81 std::ops::Bound::Unbounded => 0,
82 };
83 RangeInternal {
84 start,
85 len: match range.end_bound() {
86 std::ops::Bound::Included(end) => *end - start + 1,
87 std::ops::Bound::Excluded(end) => *end - start,
88 std::ops::Bound::Unbounded => usize::MAX - start,
89 },
90 }
91 }
92 }
93
94 impl RangeInternal {
95 // inclusive end for libflashrom
end(&self) -> usize96 fn end(&self) -> usize {
97 self.start + self.len - 1
98 }
99 }
100
101 // log_c is set to be the callback at [`Programmer`] init. It deals with va_list and calls log_rust.
102 // log_rust calls a user defined function, or by default log_eprint.
103 // log_eprint just writes to stderr.
104 extern "C" {
set_log_callback()105 fn set_log_callback();
106 // Modifying and reading current_level is not thread safe, but neither is
107 // the libflashrom implementation, so we shouldnt be using threads anyway.
108 static mut current_level: libflashrom_sys::flashrom_log_level;
109 }
110
111 /// Callers can use this function to log to the [`Logger`] they have set via [`set_log_level`]
112 ///
113 /// However from rust it is likely easier to call the [`Logger`] directly.
114 #[no_mangle]
log_rust( level: libflashrom_sys::flashrom_log_level, format: &std::os::raw::c_char, )115 pub extern "C" fn log_rust(
116 level: libflashrom_sys::flashrom_log_level,
117 format: &std::os::raw::c_char,
118 ) {
119 // Because this function is called from C, it must not panic.
120 // SAFETY: log_c always provides a non null ptr to a null terminated string
121 // msg does not outlive format.
122 let msg = unsafe { CStr::from_ptr(format) }.to_string_lossy();
123 // Locking can fail if a thread panics while holding the lock.
124 match LOG_FN.lock() {
125 Ok(g) => (*g)(level, msg.as_ref()),
126 Err(_) => eprintln!("ERROR: libflashrom log failure to lock function"),
127 };
128 }
129
log_eprint(_level: libflashrom_sys::flashrom_log_level, msg: &str)130 fn log_eprint(_level: libflashrom_sys::flashrom_log_level, msg: &str) {
131 // Because this function is called from C, it must not panic.
132 // Ignore the error.
133 let _ = std::io::stderr().write_all(msg.as_bytes());
134 }
135
136 // Can't directly atexit(flashrom_shutdown) because it is unsafe
shutdown_wrapper()137 extern "C" fn shutdown_wrapper() {
138 unsafe {
139 libflashrom_sys::flashrom_shutdown();
140 }
141 }
142
143 /// A callback to log flashrom messages. This must not panic.
144 pub type Logger = fn(libflashrom_sys::flashrom_log_level, &str);
145
146 static LOG_FN: Lazy<Mutex<Logger>> = Lazy::new(|| Mutex::new(log_eprint));
147
148 /// Set the maximum log message level that will be passed to [`Logger`]
149 ///
150 /// log_rust and therefore the provided [`Logger`] will only be called for messages
151 /// greater or equal to the provided priority.
152 ///
153 /// ```
154 /// use libflashrom::set_log_level;
155 /// use libflashrom::FLASHROM_MSG_SPEW;
156 /// // Disable logging.
157 /// set_log_level(None);
158 /// // Log all messages at priority FLASHROM_MSG_SPEW and above.
159 /// set_log_level(Some(FLASHROM_MSG_SPEW));
160 /// ```
set_log_level(level: Option<libflashrom_sys::flashrom_log_level>)161 pub fn set_log_level(level: Option<libflashrom_sys::flashrom_log_level>) {
162 // SAFETY: current_level is only read by log_c, in this thread.
163 match level {
164 Some(level) => unsafe { current_level = level + 1 },
165 None => unsafe { current_level = 0 },
166 };
167 }
168
169 /// Set a [`Logger`] logging callback function
170 ///
171 /// Provided function must not panic, as it is called from C.
set_log_function(logger: Logger)172 pub fn set_log_function(logger: Logger) {
173 *LOG_FN.lock().unwrap() = logger;
174 }
175
176 /// A type holding the error code returned by libflashrom and the function that returned the error.
177 ///
178 /// The error codes returned from each function differ in meaning, see libflashrom.h
179 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
180 pub struct ErrorCode {
181 function: &'static str,
182 code: i32,
183 }
184
185 impl fmt::Display for ErrorCode {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result186 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187 write!(f, "libflashrom: {} returned {}", self.function, self.code)
188 }
189 }
190
191 impl error::Error for ErrorCode {}
192
193 impl From<ErrorCode> for String {
from(e: ErrorCode) -> Self194 fn from(e: ErrorCode) -> Self {
195 format!("{}", e)
196 }
197 }
198
199 /// Errors from initialising libflashrom or a [`Programmer`]
200 #[derive(Clone, Debug, Eq, PartialEq)]
201 pub enum InitError {
202 DuplicateInit,
203 FlashromInit(ErrorCode),
204 InvalidName(std::ffi::NulError),
205 ProgrammerInit(ErrorCode),
206 }
207
208 impl fmt::Display for InitError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result209 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
210 write!(f, "{:?}", self)
211 }
212 }
213
214 impl error::Error for InitError {}
215
216 impl From<InitError> for String {
from(e: InitError) -> Self217 fn from(e: InitError) -> Self {
218 format!("{:?}", e)
219 }
220 }
221
222 impl From<std::ffi::NulError> for InitError {
from(err: std::ffi::NulError) -> InitError223 fn from(err: std::ffi::NulError) -> InitError {
224 InitError::InvalidName(err)
225 }
226 }
227
228 /// Errors from probing a [`Chip`]
229 #[derive(Clone, Debug, Eq, PartialEq)]
230 pub enum ChipInitError {
231 InvalidName(std::ffi::NulError),
232 NoChipError,
233 MultipleChipsError,
234 ProbeError,
235 }
236
237 impl fmt::Display for ChipInitError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result238 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
239 write!(f, "{:?}", self)
240 }
241 }
242
243 impl error::Error for ChipInitError {}
244
245 impl From<ChipInitError> for String {
from(e: ChipInitError) -> Self246 fn from(e: ChipInitError) -> Self {
247 format!("{:?}", e)
248 }
249 }
250
251 impl From<std::ffi::NulError> for ChipInitError {
from(err: std::ffi::NulError) -> ChipInitError252 fn from(err: std::ffi::NulError) -> ChipInitError {
253 ChipInitError::InvalidName(err)
254 }
255 }
256
257 #[derive(Clone, Debug, Eq, PartialEq)]
258 pub enum RegionError {
259 ErrorCode(ErrorCode),
260 InvalidName(std::ffi::NulError),
261 }
262
263 impl fmt::Display for RegionError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result264 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
265 write!(f, "{:?}", self)
266 }
267 }
268
269 impl error::Error for RegionError {}
270
271 impl From<RegionError> for String {
from(e: RegionError) -> Self272 fn from(e: RegionError) -> Self {
273 format!("{:?}", e)
274 }
275 }
276
277 impl From<std::ffi::NulError> for RegionError {
from(err: std::ffi::NulError) -> RegionError278 fn from(err: std::ffi::NulError) -> RegionError {
279 RegionError::InvalidName(err)
280 }
281 }
282
283 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
284 pub struct ParseLayoutError(String);
285
286 impl fmt::Display for ParseLayoutError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result287 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288 write!(f, "{:?}", self)
289 }
290 }
291
292 impl error::Error for ParseLayoutError {}
293
294 impl From<ParseLayoutError> for String {
from(e: ParseLayoutError) -> Self295 fn from(e: ParseLayoutError) -> Self {
296 format!("{:?}", e)
297 }
298 }
299
300 /// A translation of the flashrom_wp_result type
301 ///
302 /// WpOK is omitted, as it is not an error.
303 /// WpErrUnknown is used for an unknown error type.
304 /// Keep this list in sync with libflashrom.h
305 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
306 pub enum WPError {
307 WpErrChipUnsupported,
308 WpErrOther,
309 WpErrReadFailed,
310 WpErrWriteFailed,
311 WpErrVerifyFailed,
312 WpErrRangeUnsupported,
313 WpErrModeUnsupported,
314 WpErrRangeListUnavailable,
315 WpErrUnsupportedState,
316 WpErrUnknown(libflashrom_sys::flashrom_wp_result),
317 }
318
319 impl From<libflashrom_sys::flashrom_wp_result> for WPError {
from(e: libflashrom_sys::flashrom_wp_result) -> Self320 fn from(e: libflashrom_sys::flashrom_wp_result) -> Self {
321 assert!(e != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK);
322 match e {
323 libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_CHIP_UNSUPPORTED => {
324 WPError::WpErrChipUnsupported
325 }
326 libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_OTHER => WPError::WpErrOther,
327 libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_READ_FAILED => {
328 WPError::WpErrReadFailed
329 }
330 libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_WRITE_FAILED => {
331 WPError::WpErrWriteFailed
332 }
333 libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_VERIFY_FAILED => {
334 WPError::WpErrVerifyFailed
335 }
336 libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_RANGE_UNSUPPORTED => {
337 WPError::WpErrRangeUnsupported
338 }
339 libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_MODE_UNSUPPORTED => {
340 WPError::WpErrModeUnsupported
341 }
342 libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_RANGE_LIST_UNAVAILABLE => {
343 WPError::WpErrRangeListUnavailable
344 }
345 libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_UNSUPPORTED_STATE => {
346 WPError::WpErrUnsupportedState
347 }
348 _ => WPError::WpErrUnknown(e), // this could also be a panic
349 }
350 }
351 }
352
353 impl fmt::Display for WPError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result354 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
355 write!(f, "{:?}", self)
356 }
357 }
358
359 impl error::Error for WPError {}
360
361 impl From<WPError> for String {
from(e: WPError) -> Self362 fn from(e: WPError) -> Self {
363 format!("{:?}", e)
364 }
365 }
366
367 /// Return a rust sanitised string derived from flashrom_version_info.
flashrom_version_info() -> Option<String>368 pub fn flashrom_version_info() -> Option<String> {
369 let p = unsafe { libflashrom_sys::flashrom_version_info() };
370 if p.is_null() {
371 None
372 } else {
373 // SAFETY: flashrom_version_info returns a global `const char flashrom_version[]`
374 // derived from `-DFLASHROM_VERSION`, this is not guaranteed to be
375 // null terminated, but is in a normal build.
376 Some(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned())
377 }
378 }
379
380 /// Structure for an initialised flashrom_programmer
381 // flashrom_programmer_init returns a pointer accepted by flashrom_flash_probe
382 // but this is not implemented at time of writing. When implemented the pointer
383 // can be stored here.
384 #[derive(Debug)]
385 pub struct Programmer {}
386
387 /// Structure for an initialised flashrom chip, or flashrom_flashctx
388 // As returned by flashrom_flash_probe
389 // The layout is owned here as the chip only stores a pointer when a layout is set.
390 #[derive(Debug)]
391 pub struct Chip {
392 ctx: NonNull<libflashrom_sys::flashrom_flashctx>,
393 _programmer: Programmer,
394 layout: Option<Layout>,
395 }
396
397 impl Programmer {
398 /// Initialise libflashrom and a programmer
399 ///
400 /// See libflashrom.h flashrom_programmer_init for argument documentation.
401 ///
402 /// Panics:
403 ///
404 /// If this libflashrom implementation returns a programmer pointer.
new( programmer_name: &str, programmer_options: Option<&str>, ) -> Result<Programmer, InitError>405 pub fn new(
406 programmer_name: &str,
407 programmer_options: Option<&str>,
408 ) -> Result<Programmer, InitError> {
409 static ONCE: Once = Once::new();
410 if ONCE.is_completed() {
411 // Flashrom does not currently support concurrent programmers
412 // Flashrom also does not currently support initialising a second programmer after a first has been initialised.
413 // This is used to warn the user if they try to initialise a second programmer.
414 return Err(InitError::DuplicateInit);
415 }
416 ONCE.call_once(|| {});
417
418 static INIT_RES: Lazy<Result<(), InitError>> = Lazy::new(|| {
419 unsafe { set_log_callback() };
420 // always perform_selfcheck
421 let res = unsafe { libflashrom_sys::flashrom_init(1) };
422 if res == 0 {
423 let res = unsafe { libc::atexit(shutdown_wrapper) };
424 if res == 0 {
425 Ok(())
426 } else {
427 unsafe { libflashrom_sys::flashrom_shutdown() };
428 Err(InitError::FlashromInit(ErrorCode {
429 function: "atexit",
430 code: res,
431 }))
432 }
433 } else {
434 Err(InitError::FlashromInit(ErrorCode {
435 function: "flashrom_init",
436 code: res,
437 }))
438 }
439 });
440 (*INIT_RES).clone()?;
441
442 let mut programmer: *mut libflashrom_sys::flashrom_programmer = null_mut();
443 let programmer_name = CString::new(programmer_name)?;
444 let programmer_options = match programmer_options {
445 Some(programmer_options) => Some(CString::new(programmer_options)?),
446 None => None,
447 };
448 let res = unsafe {
449 libflashrom_sys::flashrom_programmer_init(
450 &mut programmer,
451 programmer_name.as_ptr(),
452 programmer_options.as_ref().map_or(null(), |x| x.as_ptr()),
453 )
454 };
455 if res != 0 {
456 Err(InitError::ProgrammerInit(ErrorCode {
457 function: "flashrom_programmer_init",
458 code: res,
459 }))
460 } else if !programmer.is_null() {
461 panic!("flashrom_programmer_init returning a programmer pointer is not supported")
462 } else {
463 Ok(Programmer {})
464 }
465 }
466 }
467
468 impl Drop for Programmer {
drop(&mut self)469 fn drop(&mut self) {
470 unsafe {
471 libflashrom_sys::flashrom_programmer_shutdown(null_mut());
472 }
473 }
474 }
475
476 /// A translation of the flashrom_flag type
477 ///
478 /// Keep this list in sync with libflashrom.h
479 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
480 pub enum FlashromFlag {
481 FlashromFlagForce,
482 FlashromFlagForceBoardmismatch,
483 FlashromFlagVerifyAfterWrite,
484 FlashromFlagVerifyWholeChip,
485 FlashromFlagSkipUnreadableRegions,
486 FlashromFlagSkipUnwritableRegions,
487 }
488
489 impl From<FlashromFlag> for libflashrom_sys::flashrom_flag {
from(e: FlashromFlag) -> Self490 fn from(e: FlashromFlag) -> Self {
491 match e {
492 FlashromFlag::FlashromFlagForce => libflashrom_sys::flashrom_flag::FLASHROM_FLAG_FORCE,
493 FlashromFlag::FlashromFlagForceBoardmismatch => {
494 libflashrom_sys::flashrom_flag::FLASHROM_FLAG_FORCE_BOARDMISMATCH
495 }
496 FlashromFlag::FlashromFlagVerifyAfterWrite => {
497 libflashrom_sys::flashrom_flag::FLASHROM_FLAG_VERIFY_AFTER_WRITE
498 }
499 FlashromFlag::FlashromFlagVerifyWholeChip => {
500 libflashrom_sys::flashrom_flag::FLASHROM_FLAG_VERIFY_WHOLE_CHIP
501 }
502 FlashromFlag::FlashromFlagSkipUnreadableRegions => {
503 libflashrom_sys::flashrom_flag::FLASHROM_FLAG_SKIP_UNREADABLE_REGIONS
504 }
505 FlashromFlag::FlashromFlagSkipUnwritableRegions => {
506 libflashrom_sys::flashrom_flag::FLASHROM_FLAG_SKIP_UNWRITABLE_REGIONS
507 }
508 e => panic!("Unexpected FlashromFlag: {:?}", e),
509 }
510 }
511 }
512
513 /// Various flags of the flashrom context
514 ///
515 /// Keep the struct in sync with the flags in flash.h
516 #[derive(Debug)]
517 pub struct FlashromFlags {
518 pub force: bool,
519 pub force_boardmismatch: bool,
520 pub verify_after_write: bool,
521 pub verify_whole_chip: bool,
522 pub skip_unreadable_regions: bool,
523 pub skip_unwritable_regions: bool,
524 }
525
526 /// Keep the default values in sync with cli_classic.c
527 impl Default for FlashromFlags {
default() -> Self528 fn default() -> Self {
529 Self {
530 force: false,
531 force_boardmismatch: false,
532 verify_after_write: true,
533 verify_whole_chip: true,
534 // These flags are introduced to address issues related to CSME locking parts. Setting
535 // them to true as default values
536 skip_unreadable_regions: true,
537 skip_unwritable_regions: true,
538 }
539 }
540 }
541
542 impl fmt::Display for FlashromFlags {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result543 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544 write!(
545 f,
546 "FlashromFlags {{ force: {}, force_boardmismatch: {}, verify_after_write: {}, verify_whole_chip: {}, skip_unreadable_regions: {}, skip_unwritable_regions: {} }}",
547 self.force,
548 self.force_boardmismatch,
549 self.verify_after_write,
550 self.verify_whole_chip,
551 self.skip_unreadable_regions,
552 self.skip_unwritable_regions,
553 )
554 }
555 }
556
557 impl Chip {
558 /// Probe for a chip
559 ///
560 /// See libflashrom.h flashrom_flash_probe for argument documentation.
new(programmer: Programmer, chip_name: Option<&str>) -> Result<Chip, ChipInitError>561 pub fn new(programmer: Programmer, chip_name: Option<&str>) -> Result<Chip, ChipInitError> {
562 let mut flash_ctx: *mut libflashrom_sys::flashrom_flashctx = null_mut();
563 let chip_name = match chip_name {
564 Some(chip_name) => Some(CString::new(chip_name)?),
565 None => None,
566 };
567 match unsafe {
568 libflashrom_sys::flashrom_flash_probe(
569 &mut flash_ctx,
570 null(),
571 chip_name.as_ref().map_or(null(), |x| x.as_ptr()),
572 )
573 } {
574 0 => Ok(Chip {
575 ctx: NonNull::new(flash_ctx).expect("flashrom_flash_probe returned null"),
576 _programmer: programmer,
577 layout: None,
578 }),
579 3 => Err(ChipInitError::MultipleChipsError),
580 2 => Err(ChipInitError::NoChipError),
581 _ => Err(ChipInitError::ProbeError),
582 }
583 }
584
get_size(&self) -> usize585 pub fn get_size(&self) -> usize {
586 unsafe { libflashrom_sys::flashrom_flash_getsize(self.ctx.as_ref()) }
587 }
588
589 /// Read the write protect config of this [`Chip`]
get_wp(&mut self) -> std::result::Result<WriteProtectCfg, WPError>590 pub fn get_wp(&mut self) -> std::result::Result<WriteProtectCfg, WPError> {
591 let mut cfg = WriteProtectCfg::new()?;
592 let res =
593 unsafe { libflashrom_sys::flashrom_wp_read_cfg(cfg.wp.as_mut(), self.ctx.as_mut()) };
594 if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK {
595 return Err(res.into());
596 }
597 Ok(cfg)
598 }
599
600 /// Set the write protect config of this [`Chip`]
set_wp(&mut self, wp: &WriteProtectCfg) -> std::result::Result<(), WPError>601 pub fn set_wp(&mut self, wp: &WriteProtectCfg) -> std::result::Result<(), WPError> {
602 let res =
603 unsafe { libflashrom_sys::flashrom_wp_write_cfg(self.ctx.as_mut(), wp.wp.as_ref()) };
604 if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK {
605 return Err(res.into());
606 }
607 Ok(())
608 }
609
610 /// Read the write protect ranges of this [`Chip`]
611 ///
612 /// # Panics
613 ///
614 /// Panics if flashrom_wp_get_available_ranges returns FLASHROM_WP_OK and a NULL pointer.
get_wp_ranges(&mut self) -> std::result::Result<Vec<Range>, WPError>615 pub fn get_wp_ranges(&mut self) -> std::result::Result<Vec<Range>, WPError> {
616 let mut ranges: *mut libflashrom_sys::flashrom_wp_ranges = null_mut();
617 let res = unsafe {
618 libflashrom_sys::flashrom_wp_get_available_ranges(&mut ranges, self.ctx.as_mut())
619 };
620 if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK {
621 return Err(res.into());
622 }
623 let ranges = WriteProtectRanges {
624 ranges: NonNull::new(ranges).expect("flashrom_wp_get_available_ranges returned null"),
625 };
626
627 let count =
628 unsafe { libflashrom_sys::flashrom_wp_ranges_get_count(ranges.ranges.as_ref()) };
629 let mut ret = Vec::with_capacity(count);
630 for index in 0..count {
631 let mut start = 0;
632 let mut len = 0;
633 let res = unsafe {
634 libflashrom_sys::flashrom_wp_ranges_get_range(
635 &mut start,
636 &mut len,
637 ranges.ranges.as_ref(),
638 // TODO: fix after https://review.coreboot.org/c/flashrom/+/64996
639 index
640 .try_into()
641 .expect("flashrom_wp_ranges_get_count does not fit in a u32"),
642 )
643 };
644 if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK {
645 return Err(res.into());
646 }
647 ret.push(start..(start + len))
648 }
649 Ok(ret)
650 }
651
652 /// Returns the layout read from the fmap of this [`Chip`]
653 ///
654 /// # Panics
655 ///
656 /// Panics if flashrom_layout_read_fmap_from_rom returns FLASHROM_WP_OK and a NULL pointer.
layout_read_fmap_from_rom(&mut self) -> std::result::Result<Layout, ErrorCode>657 pub fn layout_read_fmap_from_rom(&mut self) -> std::result::Result<Layout, ErrorCode> {
658 let mut layout: *mut libflashrom_sys::flashrom_layout = null_mut();
659 let err = unsafe {
660 libflashrom_sys::flashrom_layout_read_fmap_from_rom(
661 &mut layout,
662 self.ctx.as_mut(),
663 0,
664 self.get_size(),
665 )
666 };
667 if err != 0 {
668 return Err(ErrorCode {
669 function: "flashrom_layout_read_fmap_from_rom",
670 code: err,
671 });
672 }
673 Ok(Layout {
674 layout: NonNull::new(layout).expect("flashrom_layout_read_fmap_from_rom returned null"),
675 })
676 }
677
678 /// Sets the layout of this [`Chip`]
679 ///
680 /// [`Chip`] takes ownership of Layout to ensure it is not released before the [`Chip`].
set_layout(&mut self, layout: Layout)681 fn set_layout(&mut self, layout: Layout) {
682 unsafe { libflashrom_sys::flashrom_layout_set(self.ctx.as_mut(), layout.layout.as_ref()) };
683 self.layout = Some(layout)
684 }
685
unset_layout(&mut self) -> Option<Layout>686 fn unset_layout(&mut self) -> Option<Layout> {
687 unsafe { libflashrom_sys::flashrom_layout_set(self.ctx.as_mut(), null()) };
688 self.layout.take()
689 }
690
691 /// Read the whole [`Chip`], or a portion specified in a Layout
image_read( &mut self, layout: Option<Layout>, ) -> std::result::Result<Vec<u8>, ErrorCode>692 pub fn image_read(
693 &mut self,
694 layout: Option<Layout>,
695 ) -> std::result::Result<Vec<u8>, ErrorCode> {
696 if let Some(layout) = layout {
697 self.set_layout(layout);
698 }
699 let len = self.get_size();
700 let mut buf = vec![0; len];
701 let res = unsafe {
702 libflashrom_sys::flashrom_image_read(
703 self.ctx.as_mut(),
704 buf.as_mut_ptr() as *mut c_void,
705 len,
706 )
707 };
708 self.unset_layout();
709
710 if res == 0 {
711 Ok(buf)
712 } else {
713 Err(ErrorCode {
714 function: "flashrom_image_read",
715 code: res,
716 })
717 }
718 }
719
720 /// Write the whole [`Chip`], or a portion specified in a Layout
image_write( &mut self, buf: &mut [u8], layout: Option<Layout>, ) -> std::result::Result<(), ErrorCode>721 pub fn image_write(
722 &mut self,
723 buf: &mut [u8],
724 layout: Option<Layout>,
725 ) -> std::result::Result<(), ErrorCode> {
726 if let Some(layout) = layout {
727 self.set_layout(layout);
728 }
729 let res = unsafe {
730 libflashrom_sys::flashrom_image_write(
731 self.ctx.as_mut(),
732 buf.as_mut_ptr() as *mut c_void,
733 buf.len(),
734 null(),
735 )
736 };
737 self.unset_layout();
738
739 if res == 0 {
740 Ok(())
741 } else {
742 Err(ErrorCode {
743 function: "flashrom_image_write",
744 code: res,
745 })
746 }
747 }
748
749 /// Verify the whole [`Chip`], or a portion specified in a Layout
image_verify( &mut self, buf: &[u8], layout: Option<Layout>, ) -> std::result::Result<(), ErrorCode>750 pub fn image_verify(
751 &mut self,
752 buf: &[u8],
753 layout: Option<Layout>,
754 ) -> std::result::Result<(), ErrorCode> {
755 if let Some(layout) = layout {
756 self.set_layout(layout);
757 }
758 let res = unsafe {
759 libflashrom_sys::flashrom_image_verify(
760 self.ctx.as_mut(),
761 buf.as_ptr() as *const c_void,
762 buf.len(),
763 )
764 };
765 self.unset_layout();
766
767 if res == 0 {
768 Ok(())
769 } else {
770 Err(ErrorCode {
771 function: "flashrom_image_verify",
772 code: res,
773 })
774 }
775 }
776
777 /// Erase the whole [`Chip`]
erase(&mut self) -> std::result::Result<(), ErrorCode>778 pub fn erase(&mut self) -> std::result::Result<(), ErrorCode> {
779 let res = unsafe { libflashrom_sys::flashrom_flash_erase(self.ctx.as_mut()) };
780 if res == 0 {
781 Ok(())
782 } else {
783 Err(ErrorCode {
784 function: "flashrom_flash_erase",
785 code: res,
786 })
787 }
788 }
789
790 /// Set a flag in the given flash context
flag_set(&mut self, flag: FlashromFlag, value: bool) -> ()791 pub fn flag_set(&mut self, flag: FlashromFlag, value: bool) -> () {
792 unsafe { libflashrom_sys::flashrom_flag_set(self.ctx.as_mut(), flag.into(), value) }
793 }
794 }
795
796 impl Drop for Chip {
drop(&mut self)797 fn drop(&mut self) {
798 unsafe {
799 libflashrom_sys::flashrom_flash_release(self.ctx.as_mut());
800 }
801 }
802 }
803
804 /// Structure for an initialised flashrom_wp_cfg
805 #[derive(Debug)]
806 pub struct WriteProtectCfg {
807 wp: NonNull<libflashrom_sys::flashrom_wp_cfg>,
808 }
809
810 impl WriteProtectCfg {
811 /// Create an empty [`WriteProtectCfg`]
812 ///
813 /// # Panics
814 ///
815 /// Panics if flashrom_wp_cfg_new returns FLASHROM_WP_OK and a NULL pointer.
new() -> std::result::Result<WriteProtectCfg, WPError>816 pub fn new() -> std::result::Result<WriteProtectCfg, WPError> {
817 let mut cfg: *mut libflashrom_sys::flashrom_wp_cfg = null_mut();
818 let res = unsafe { libflashrom_sys::flashrom_wp_cfg_new(&mut cfg) };
819 if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK {
820 return Err(res.into());
821 }
822 Ok(WriteProtectCfg {
823 wp: NonNull::new(cfg).expect("flashrom_wp_cfg_new returned null"),
824 })
825 }
826
get_mode(&self) -> libflashrom_sys::flashrom_wp_mode827 pub fn get_mode(&self) -> libflashrom_sys::flashrom_wp_mode {
828 unsafe { libflashrom_sys::flashrom_wp_get_mode(self.wp.as_ref()) }
829 }
830
set_mode(&mut self, mode: libflashrom_sys::flashrom_wp_mode)831 pub fn set_mode(&mut self, mode: libflashrom_sys::flashrom_wp_mode) {
832 unsafe { libflashrom_sys::flashrom_wp_set_mode(self.wp.as_mut(), mode) }
833 }
834
get_range(&self) -> Range835 pub fn get_range(&self) -> Range {
836 let mut start = 0;
837 let mut len = 0;
838 unsafe { libflashrom_sys::flashrom_wp_get_range(&mut start, &mut len, self.wp.as_ref()) };
839 start..(start + len)
840 }
841
set_range<T>(&mut self, range: T) where T: std::ops::RangeBounds<usize>,842 pub fn set_range<T>(&mut self, range: T)
843 where
844 T: std::ops::RangeBounds<usize>,
845 {
846 let range: RangeInternal = range.into();
847 unsafe { libflashrom_sys::flashrom_wp_set_range(self.wp.as_mut(), range.start, range.len) }
848 }
849 }
850
851 impl Drop for WriteProtectCfg {
drop(&mut self)852 fn drop(&mut self) {
853 unsafe {
854 libflashrom_sys::flashrom_wp_cfg_release(self.wp.as_mut());
855 }
856 }
857 }
858
859 impl fmt::Display for WriteProtectCfg {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result860 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
861 write!(f, "range:{:?} mode:{:?}", self.get_range(), self.get_mode())
862 }
863 }
864
865 #[derive(Debug)]
866 struct WriteProtectRanges {
867 ranges: NonNull<libflashrom_sys::flashrom_wp_ranges>,
868 }
869
870 impl WriteProtectRanges {}
871
872 impl Drop for WriteProtectRanges {
drop(&mut self)873 fn drop(&mut self) {
874 unsafe {
875 libflashrom_sys::flashrom_wp_ranges_release(self.ranges.as_mut());
876 }
877 }
878 }
879
880 /// Structure for an initialised flashrom_layout
881 #[derive(Debug)]
882 pub struct Layout {
883 layout: NonNull<libflashrom_sys::flashrom_layout>,
884 }
885
886 impl Layout {
887 /// Create an empty [`Layout`]
888 ///
889 /// # Panics
890 ///
891 /// Panics if flashrom_layout_new returns 0 and a NULL pointer.
new() -> std::result::Result<Layout, ErrorCode>892 pub fn new() -> std::result::Result<Layout, ErrorCode> {
893 let mut layout: *mut libflashrom_sys::flashrom_layout = null_mut();
894 let err = unsafe { libflashrom_sys::flashrom_layout_new(&mut layout) };
895 if err != 0 {
896 Err(ErrorCode {
897 function: "flashrom_layout_new",
898 code: err,
899 })
900 } else {
901 Ok(Layout {
902 layout: NonNull::new(layout).expect("flashrom_layout_new returned null"),
903 })
904 }
905 }
906
907 /// Add a region to the [`Layout`]
908 ///
909 /// Not the region will not be 'included', include_region must be called to include the region.
910 ///
911 /// # Errors
912 ///
913 /// This function will return an error if the region is not a valid CString,
914 /// or if libflashrom returns an error.
add_region<T>(&mut self, region: &str, range: T) -> std::result::Result<(), RegionError> where T: std::ops::RangeBounds<usize>,915 pub fn add_region<T>(&mut self, region: &str, range: T) -> std::result::Result<(), RegionError>
916 where
917 T: std::ops::RangeBounds<usize>,
918 {
919 let range: RangeInternal = range.into();
920 let err = {
921 let region = CString::new(region)?;
922 unsafe {
923 libflashrom_sys::flashrom_layout_add_region(
924 self.layout.as_mut(),
925 range.start,
926 range.end(),
927 region.as_ptr(),
928 )
929 }
930 };
931
932 if err != 0 {
933 Err(RegionError::ErrorCode(ErrorCode {
934 function: "flashrom_layout_add_region",
935 code: err,
936 }))
937 } else {
938 Ok(())
939 }
940 }
941
942 /// Include a region
943 ///
944 /// # Errors
945 ///
946 /// This function will return an error if the region is not a valid CString,
947 /// or if libflashrom returns an error.
include_region(&mut self, region: &str) -> std::result::Result<(), RegionError>948 pub fn include_region(&mut self, region: &str) -> std::result::Result<(), RegionError> {
949 let err = {
950 let region = CString::new(region)?;
951 unsafe {
952 libflashrom_sys::flashrom_layout_include_region(
953 self.layout.as_mut(),
954 region.as_ptr(),
955 )
956 }
957 };
958 if err != 0 {
959 Err(RegionError::ErrorCode(ErrorCode {
960 function: "flashrom_layout_include_region",
961 code: err,
962 }))
963 } else {
964 Ok(())
965 }
966 }
967
968 /// Exclude a region
969 ///
970 /// # Errors
971 ///
972 /// This function will return an error if the region is not a valid CString,
973 /// or if libflashrom returns an error.
exclude_region(&mut self, region: &str) -> std::result::Result<(), RegionError>974 pub fn exclude_region(&mut self, region: &str) -> std::result::Result<(), RegionError> {
975 let err = {
976 let region = CString::new(region)?;
977 unsafe {
978 libflashrom_sys::flashrom_layout_exclude_region(
979 self.layout.as_mut(),
980 region.as_ptr(),
981 )
982 }
983 };
984 if err != 0 {
985 Err(RegionError::ErrorCode(ErrorCode {
986 function: "flashrom_layout_exclude_region",
987 code: err,
988 }))
989 } else {
990 Ok(())
991 }
992 }
993
994 /// Get the [`Range`] for the given region
995 ///
996 /// # Errors
997 ///
998 /// This function will return an error if the region is not a valid CString,
999 /// or if libflashrom returns an error.
get_region_range(&mut self, region: &str) -> std::result::Result<Range, RegionError>1000 pub fn get_region_range(&mut self, region: &str) -> std::result::Result<Range, RegionError> {
1001 let mut start: std::os::raw::c_uint = 0;
1002 let mut len: std::os::raw::c_uint = 0;
1003 let err = {
1004 let region = CString::new(region)?;
1005 unsafe {
1006 libflashrom_sys::flashrom_layout_get_region_range(
1007 self.layout.as_mut(),
1008 region.as_ptr(),
1009 &mut start,
1010 &mut len,
1011 )
1012 }
1013 };
1014 if err != 0 {
1015 Err(RegionError::ErrorCode(ErrorCode {
1016 function: "flashrom_layout_get_region_range",
1017 code: err,
1018 }))
1019 } else {
1020 // should be safe to assume sizeof(size_t) >= sizeof(unsigned int)
1021 // TODO: fix after https://review.coreboot.org/c/flashrom/+/65944
1022 Ok(start.try_into().unwrap()..(start + len).try_into().unwrap())
1023 }
1024 }
1025 }
1026
1027 // TODO this will be replaced with an API implementation: https://review.coreboot.org/c/flashrom/+/65999
1028 impl std::str::FromStr for Layout {
1029 type Err = Box<dyn error::Error>;
1030
1031 /// This will attempt to parse the [`Layout`] file format into a [`Layout`]
1032 ///
1033 /// The format is documented in the flashrom man page.
from_str(s: &str) -> std::result::Result<Self, Self::Err>1034 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1035 let mut ret = Layout::new()?;
1036
1037 // format is hex:hex name
1038 // flashrom layout.c seems to allow any characters in the name string
1039 // I am restricting here to non whitespace
1040 let re = Regex::new(r"^([0-9A-Za-z]+):([0-9A-Za-z]+)\s+(\S+)$").unwrap();
1041
1042 // we dont use captures_iter else we would ignore incorrect lines
1043 for line in s.lines() {
1044 let (start, end, name) = match re.captures(line) {
1045 Some(caps) => (
1046 caps.get(1).unwrap(),
1047 caps.get(2).unwrap(),
1048 caps.get(3).unwrap(),
1049 ),
1050 None => Err(ParseLayoutError(format!("failed to parse: {:?}", line)))?,
1051 };
1052 let start = usize::from_str_radix(start.as_str(), 16)?;
1053 let end = usize::from_str_radix(end.as_str(), 16)?;
1054 ret.add_region(name.as_str(), start..=end)?;
1055 }
1056
1057 Ok(ret)
1058 }
1059 }
1060
1061 impl Drop for Layout {
drop(&mut self)1062 fn drop(&mut self) {
1063 unsafe {
1064 libflashrom_sys::flashrom_layout_release(self.layout.as_mut());
1065 }
1066 }
1067 }
1068
1069 #[cfg(test)]
1070 mod tests {
1071 use gag::BufferRedirect;
1072 use std::io::Read;
1073
1074 use super::flashrom_version_info;
1075 use super::set_log_level;
1076 use super::Chip;
1077 use super::ChipInitError;
1078 use super::InitError;
1079 use crate::set_log_function;
1080 use crate::Layout;
1081 use crate::Programmer;
1082 use crate::WriteProtectCfg;
1083
1084 // flashrom contains global state, which prevents correct initialisation of
1085 // a second programmer or probing of a second chip. Run all unit tests in
1086 // forked subprocesses to avoid this issue.
1087 use rusty_fork::rusty_fork_test;
1088 rusty_fork_test! {
1089
1090 #[test]
1091 fn version() {
1092 // There is no version requirement yet, but for example:
1093 // assert!(flashrom_version_info().contains("v1.2"))
1094 assert!(!flashrom_version_info().unwrap().is_empty())
1095 }
1096
1097 #[test]
1098 fn only_one_programmer() {
1099 {
1100 let _1 = Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap();
1101 // Only one programmer can be initialised at a time.
1102 assert_eq!(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap_err(), InitError::DuplicateInit)
1103 }
1104 // Only one programmer can ever be initialised
1105 assert_eq!(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap_err(), InitError::DuplicateInit)
1106 }
1107
1108 #[test]
1109 fn programmer_bad_cstring_name() {
1110 assert!(matches!(Programmer::new("dummy\0", None).unwrap_err(), InitError::InvalidName(_)))
1111 }
1112
1113 #[test]
1114 fn chip_none() {
1115 // Not specifying a chip will select one if there is one.
1116 Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), None).unwrap();
1117 }
1118
1119 #[test]
1120 fn chip_some() {
1121 // Specifying a valid chip.
1122 Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap();
1123 }
1124
1125 #[test]
1126 fn chip_nochip() {
1127 // Choosing a non existent chip fails.
1128 assert_eq!(
1129 Chip::new(Programmer::new("dummy", None).unwrap(), Some("W25Q128.V")).unwrap_err(),
1130 ChipInitError::NoChipError
1131 );
1132 }
1133
1134 #[test]
1135 fn logging_stderr() {
1136 let mut buf = BufferRedirect::stderr().unwrap();
1137 let mut fc = Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap();
1138
1139 set_log_level(Some(libflashrom_sys::FLASHROM_MSG_INFO));
1140 fc.image_read(None).unwrap();
1141 let mut stderr = String::new();
1142 if buf.read_to_string(&mut stderr).unwrap() == 0 {
1143 panic!("stderr empty when it should have some messages");
1144 }
1145
1146 set_log_level(None);
1147 fc.image_read(None).unwrap();
1148 if buf.read_to_string(&mut stderr).unwrap() != 0 {
1149 panic!("stderr not empty when it should be silent");
1150 }
1151 }
1152
1153 #[test]
1154 fn logging_custom() {
1155 // Check that a custom logging callback works
1156 static mut BUF: String = String::new();
1157 fn logger(
1158 _: libflashrom_sys::flashrom_log_level,
1159 format: &str,
1160 ) {
1161 unsafe {BUF.push_str(format)}
1162 }
1163 set_log_function(logger);
1164 set_log_level(Some(libflashrom_sys::FLASHROM_MSG_SPEW));
1165 Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap();
1166 assert_ne!(unsafe{BUF.len()}, 0);
1167 }
1168
1169 #[test]
1170 fn flashchip() {
1171 // basic tests of the flashchip methods
1172 let mut fc = Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap();
1173 fc.get_size();
1174
1175 let mut wp = fc.get_wp().unwrap();
1176 wp.set_mode(libflashrom_sys::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED);
1177 fc.set_wp(&wp).unwrap();
1178 fc.get_wp_ranges().unwrap();
1179
1180 fn test_layout() -> Layout {
1181 let mut layout = Layout::new().unwrap();
1182 layout.add_region("xyz", 100..200).unwrap();
1183 layout.include_region("xyz").unwrap();
1184 layout.add_region("abc", 100..200).unwrap();
1185 layout
1186 }
1187
1188 fc.image_read(None).unwrap();
1189 fc.image_read(Some(test_layout())).unwrap();
1190
1191 let mut buf = vec![0; fc.get_size()];
1192 fc.image_write(&mut buf, None).unwrap();
1193 fc.image_write(&mut buf, Some(test_layout())).unwrap();
1194
1195 fc.image_verify(&buf, None).unwrap();
1196 fc.image_verify(&buf, Some(test_layout())).unwrap();
1197
1198 fc.erase().unwrap();
1199 }
1200
1201 #[test]
1202 fn write_protect() {
1203 let mut wp = WriteProtectCfg::new().unwrap();
1204 wp.set_mode(libflashrom_sys::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED);
1205 wp.set_range(100..200);
1206 assert_eq!(wp.get_mode(), libflashrom_sys::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED);
1207 assert_eq!(wp.get_range(), 100..200);
1208 }
1209 }
1210 }
1211