diff options
Diffstat (limited to 'bindings/rust/libflashrom')
-rw-r--r-- | bindings/rust/libflashrom/.cargo/config.toml | 2 | ||||
-rw-r--r-- | bindings/rust/libflashrom/Cargo.toml | 20 | ||||
-rw-r--r-- | bindings/rust/libflashrom/build.rs | 17 | ||||
-rw-r--r-- | bindings/rust/libflashrom/src/lib.rs | 1094 | ||||
-rw-r--r-- | bindings/rust/libflashrom/src/log.c | 61 |
5 files changed, 1194 insertions, 0 deletions
diff --git a/bindings/rust/libflashrom/.cargo/config.toml b/bindings/rust/libflashrom/.cargo/config.toml new file mode 100644 index 00000000..8af59dd8 --- /dev/null +++ b/bindings/rust/libflashrom/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +RUST_TEST_THREADS = "1" diff --git a/bindings/rust/libflashrom/Cargo.toml b/bindings/rust/libflashrom/Cargo.toml new file mode 100644 index 00000000..68636dff --- /dev/null +++ b/bindings/rust/libflashrom/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "libflashrom" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libflashrom-sys = { path = "../libflashrom-sys" } +libc = "0.2.124" +regex = "1.5.5" +once_cell = "1.7.2" + +[dev-dependencies] +rusty-fork = "0.3.0" +gag = "1" + +[build-dependencies] +pkg-config = "0.3.19" +cc = "1.0.72" diff --git a/bindings/rust/libflashrom/build.rs b/bindings/rust/libflashrom/build.rs new file mode 100644 index 00000000..9908ebbc --- /dev/null +++ b/bindings/rust/libflashrom/build.rs @@ -0,0 +1,17 @@ +extern crate cc; + +fn main() { + // pkg_config is needed only to pick up the include path for log.c to use. + // libflashrom-sys tells cargo how to link to libflashrom. + let flashrom = pkg_config::Config::new() + .cargo_metadata(false) + .probe("flashrom") + .unwrap(); + let mut log_c = cc::Build::new(); + log_c.file("src/log.c"); + for p in flashrom.include_paths { + log_c.include(p); + } + log_c.compile("log.o"); + println!("cargo:rerun-if-changed=src/log.c"); +} diff --git a/bindings/rust/libflashrom/src/lib.rs b/bindings/rust/libflashrom/src/lib.rs new file mode 100644 index 00000000..39e76acb --- /dev/null +++ b/bindings/rust/libflashrom/src/lib.rs @@ -0,0 +1,1094 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2022 The Chromium OS Authors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +//! # libflashrom +//! +//! The `libflashrom` library is a rust FFI binding to the flashrom library. +//! libflashrom can be used to read write and modify some settings of flash chips. +//! The library closely follows the libflashrom C API, but exports a `safe` interface +//! including automatic resource management and forced error checking. +//! +//! libflashrom does not support threading, all usage of this library must occur on one thread. +//! +//! Most of the library functionality is defined on the [`Chip`] type. +//! +//! Example: +//! +//! ``` +//! use libflashrom::*; +//! let mut chip = Chip::new( +//! Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), +//! Some("W25Q128.V") +//! ).unwrap(); +//! let mut buf = chip.image_read(None).unwrap(); +//! buf[0] = 0xFE; +//! chip.image_write(&mut buf, None).unwrap(); +//! ``` + +use once_cell::sync::Lazy; +use regex::Regex; +use std::error; +use std::ffi::c_void; +use std::ffi::CStr; +use std::ffi::CString; +use std::fmt; +use std::io::Write; +use std::ptr::null; +use std::ptr::null_mut; +use std::ptr::NonNull; +use std::sync::Mutex; +use std::sync::Once; + +pub use libflashrom_sys::{ + flashrom_log_level, FLASHROM_MSG_DEBUG, FLASHROM_MSG_DEBUG2, FLASHROM_MSG_ERROR, + FLASHROM_MSG_INFO, FLASHROM_MSG_SPEW, FLASHROM_MSG_WARN, +}; + +pub use libflashrom_sys::flashrom_wp_mode; + +// libflashrom uses (start, len) or inclusive [start, end] for ranges. +// This type exists to rust RangeBounds types to a convenient internal format. +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +struct RangeInternal { + start: usize, + len: usize, +} + +/// The type returned for write protect and layout queries +pub type Range = std::ops::Range<usize>; + +impl<T> From<T> for RangeInternal +where + T: std::ops::RangeBounds<usize>, +{ + fn from(range: T) -> Self { + let start = match range.start_bound() { + std::ops::Bound::Included(start) => *start, + std::ops::Bound::Excluded(start) => *start + 1, + std::ops::Bound::Unbounded => 0, + }; + RangeInternal { + start, + len: match range.end_bound() { + std::ops::Bound::Included(end) => *end - start + 1, + std::ops::Bound::Excluded(end) => *end - start, + std::ops::Bound::Unbounded => usize::MAX - start, + }, + } + } +} + +impl RangeInternal { + // inclusive end for libflashrom + fn end(&self) -> usize { + self.start + self.len - 1 + } +} + +// log_c is set to be the callback at [`Programmer`] init. It deals with va_list and calls log_rust. +// log_rust calls a user defined function, or by default log_eprint. +// log_eprint just writes to stderr. +extern "C" { + fn set_log_callback(); + // Modifying and reading current_level is not thread safe, but neither is + // the libflashrom implementation, so we shouldnt be using threads anyway. + static mut current_level: libflashrom_sys::flashrom_log_level; +} + +/// Callers can use this function to log to the [`Logger`] they have set via [`set_log_level`] +/// +/// However from rust it is likely easier to call the [`Logger`] directly. +#[no_mangle] +pub extern "C" fn log_rust( + level: libflashrom_sys::flashrom_log_level, + format: &std::os::raw::c_char, +) { + // Because this function is called from C, it must not panic. + // SAFETY: log_c always provides a non null ptr to a null terminated string + // msg does not outlive format. + let msg = unsafe { CStr::from_ptr(format) }.to_string_lossy(); + // Locking can fail if a thread panics while holding the lock. + match LOG_FN.lock() { + Ok(g) => (*g)(level, msg.as_ref()), + Err(_) => eprintln!("ERROR: libflashrom log failure to lock function"), + }; +} + +fn log_eprint(_level: libflashrom_sys::flashrom_log_level, msg: &str) { + // Because this function is called from C, it must not panic. + // Ignore the error. + let _ = std::io::stderr().write_all(msg.as_bytes()); +} + +// Can't directly atexit(flashrom_shutdown) because it is unsafe +extern "C" fn shutdown_wrapper() { + unsafe { + libflashrom_sys::flashrom_shutdown(); + } +} + +/// A callback to log flashrom messages. This must not panic. +pub type Logger = fn(libflashrom_sys::flashrom_log_level, &str); + +static LOG_FN: Lazy<Mutex<Logger>> = Lazy::new(|| Mutex::new(log_eprint)); + +/// Set the maximum log message level that will be passed to [`Logger`] +/// +/// log_rust and therefore the provided [`Logger`] will only be called for messages +/// greater or equal to the provided priority. +/// +/// ``` +/// use libflashrom::set_log_level; +/// use libflashrom::FLASHROM_MSG_SPEW; +/// // Disable logging. +/// set_log_level(None); +/// // Log all messages at priority FLASHROM_MSG_SPEW and above. +/// set_log_level(Some(FLASHROM_MSG_SPEW)); +/// ``` +pub fn set_log_level(level: Option<libflashrom_sys::flashrom_log_level>) { + // SAFETY: current_level is only read by log_c, in this thread. + match level { + Some(level) => unsafe { current_level = level + 1 }, + None => unsafe { current_level = 0 }, + }; +} + +/// Set a [`Logger`] logging callback function +/// +/// Provided function must not panic, as it is called from C. +pub fn set_log_function(logger: Logger) { + *LOG_FN.lock().unwrap() = logger; +} + +/// A type holding the error code returned by libflashrom and the function that returned the error. +/// +/// The error codes returned from each function differ in meaning, see libflashrom.h +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ErrorCode { + function: &'static str, + code: i32, +} + +impl fmt::Display for ErrorCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "libflashrom: {} returned {}", self.function, self.code) + } +} + +impl error::Error for ErrorCode {} + +impl From<ErrorCode> for String { + fn from(e: ErrorCode) -> Self { + format!("{}", e) + } +} + +/// Errors from initialising libflashrom or a [`Programmer`] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum InitError { + DuplicateInit, + FlashromInit(ErrorCode), + InvalidName(std::ffi::NulError), + ProgrammerInit(ErrorCode), +} + +impl fmt::Display for InitError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl error::Error for InitError {} + +impl From<InitError> for String { + fn from(e: InitError) -> Self { + format!("{:?}", e) + } +} + +impl From<std::ffi::NulError> for InitError { + fn from(err: std::ffi::NulError) -> InitError { + InitError::InvalidName(err) + } +} + +/// Errors from probing a [`Chip`] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ChipInitError { + InvalidName(std::ffi::NulError), + NoChipError, + MultipleChipsError, + ProbeError, +} + +impl fmt::Display for ChipInitError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl error::Error for ChipInitError {} + +impl From<ChipInitError> for String { + fn from(e: ChipInitError) -> Self { + format!("{:?}", e) + } +} + +impl From<std::ffi::NulError> for ChipInitError { + fn from(err: std::ffi::NulError) -> ChipInitError { + ChipInitError::InvalidName(err) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum RegionError { + ErrorCode(ErrorCode), + InvalidName(std::ffi::NulError), +} + +impl fmt::Display for RegionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl error::Error for RegionError {} + +impl From<RegionError> for String { + fn from(e: RegionError) -> Self { + format!("{:?}", e) + } +} + +impl From<std::ffi::NulError> for RegionError { + fn from(err: std::ffi::NulError) -> RegionError { + RegionError::InvalidName(err) + } +} + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ParseLayoutError(String); + +impl fmt::Display for ParseLayoutError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl error::Error for ParseLayoutError {} + +impl From<ParseLayoutError> for String { + fn from(e: ParseLayoutError) -> Self { + format!("{:?}", e) + } +} + +/// A translation of the flashrom_wp_result type +/// +/// WpOK is omitted, as it is not an error. +/// WpErrUnknown is used for an unknown error type. +/// Keep this list in sync with libflashrom.h +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum WPError { + WpErrChipUnsupported, + WpErrOther, + WpErrReadFailed, + WpErrWriteFailed, + WpErrVerifyFailed, + WpErrRangeUnsupported, + WpErrModeUnsupported, + WpErrRangeListUnavailable, + WpErrUnknown(libflashrom_sys::flashrom_wp_result), +} + +impl From<libflashrom_sys::flashrom_wp_result> for WPError { + fn from(e: libflashrom_sys::flashrom_wp_result) -> Self { + assert!(e != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK); + match e { + libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_CHIP_UNSUPPORTED => { + WPError::WpErrChipUnsupported + } + libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_OTHER => WPError::WpErrOther, + libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_READ_FAILED => { + WPError::WpErrReadFailed + } + libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_WRITE_FAILED => { + WPError::WpErrWriteFailed + } + libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_VERIFY_FAILED => { + WPError::WpErrVerifyFailed + } + libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_RANGE_UNSUPPORTED => { + WPError::WpErrRangeUnsupported + } + libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_MODE_UNSUPPORTED => { + WPError::WpErrModeUnsupported + } + libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_RANGE_LIST_UNAVAILABLE => { + WPError::WpErrRangeListUnavailable + } + _ => WPError::WpErrUnknown(e), // this could also be a panic + } + } +} + +impl fmt::Display for WPError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl error::Error for WPError {} + +impl From<WPError> for String { + fn from(e: WPError) -> Self { + format!("{:?}", e) + } +} + +/// Return a rust sanitised string derived from flashrom_version_info. +pub fn flashrom_version_info() -> Option<String> { + let p = unsafe { libflashrom_sys::flashrom_version_info() }; + if p.is_null() { + None + } else { + // SAFETY: flashrom_version_info returns a global `const char flashrom_version[]` + // derived from `-DFLASHROM_VERSION`, this is not guaranteed to be + // null terminated, but is in a normal build. + Some(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()) + } +} + +/// Structure for an initialised flashrom_programmer +// flashrom_programmer_init returns a pointer accepted by flashrom_flash_probe +// but this is not implemented at time of writing. When implemented the pointer +// can be stored here. +#[derive(Debug)] +pub struct Programmer {} + +/// Structure for an initialised flashrom chip, or flashrom_flashctx +// As returned by flashrom_flash_probe +// The layout is owned here as the chip only stores a pointer when a layout is set. +#[derive(Debug)] +pub struct Chip { + ctx: NonNull<libflashrom_sys::flashrom_flashctx>, + _programmer: Programmer, + layout: Option<Layout>, +} + +impl Programmer { + /// Initialise libflashrom and a programmer + /// + /// See libflashrom.h flashrom_programmer_init for argument documentation. + /// + /// Panics: + /// + /// If this libflashrom implementation returns a programmer pointer. + pub fn new( + programmer_name: &str, + programmer_options: Option<&str>, + ) -> Result<Programmer, InitError> { + static ONCE: Once = Once::new(); + if ONCE.is_completed() { + // Flashrom does not currently support concurrent programmers + // Flashrom also does not currently support initialising a second programmer after a first has been initialised. + // This is used to warn the user if they try to initialise a second programmer. + return Err(InitError::DuplicateInit); + } + ONCE.call_once(|| {}); + + static INIT_RES: Lazy<Result<(), InitError>> = Lazy::new(|| { + unsafe { set_log_callback() }; + // always perform_selfcheck + let res = unsafe { libflashrom_sys::flashrom_init(1) }; + if res == 0 { + let res = unsafe { libc::atexit(shutdown_wrapper) }; + if res == 0 { + Ok(()) + } else { + unsafe { libflashrom_sys::flashrom_shutdown() }; + Err(InitError::FlashromInit(ErrorCode { + function: "atexit", + code: res, + })) + } + } else { + Err(InitError::FlashromInit(ErrorCode { + function: "flashrom_init", + code: res, + })) + } + }); + (*INIT_RES).clone()?; + + let mut programmer: *mut libflashrom_sys::flashrom_programmer = null_mut(); + let programmer_name = CString::new(programmer_name)?; + let programmer_options = match programmer_options { + Some(programmer_options) => Some(CString::new(programmer_options)?), + None => None, + }; + let res = unsafe { + libflashrom_sys::flashrom_programmer_init( + &mut programmer, + programmer_name.as_ptr(), + programmer_options.as_ref().map_or(null(), |x| x.as_ptr()), + ) + }; + if res != 0 { + Err(InitError::ProgrammerInit(ErrorCode { + function: "flashrom_programmer_init", + code: res, + })) + } else if !programmer.is_null() { + panic!("flashrom_programmer_init returning a programmer pointer is not supported") + } else { + Ok(Programmer {}) + } + } +} + +impl Drop for Programmer { + fn drop(&mut self) { + unsafe { + libflashrom_sys::flashrom_programmer_shutdown(null_mut()); + } + } +} + +impl Chip { + /// Probe for a chip + /// + /// See libflashrom.h flashrom_flash_probe for argument documentation. + pub fn new(programmer: Programmer, chip_name: Option<&str>) -> Result<Chip, ChipInitError> { + let mut flash_ctx: *mut libflashrom_sys::flashrom_flashctx = null_mut(); + let chip_name = match chip_name { + Some(chip_name) => Some(CString::new(chip_name)?), + None => None, + }; + match unsafe { + libflashrom_sys::flashrom_flash_probe( + &mut flash_ctx, + null(), + chip_name.as_ref().map_or(null(), |x| x.as_ptr()), + ) + } { + 0 => Ok(Chip { + ctx: NonNull::new(flash_ctx).expect("flashrom_flash_probe returned null"), + _programmer: programmer, + layout: None, + }), + 3 => Err(ChipInitError::MultipleChipsError), + 2 => Err(ChipInitError::NoChipError), + _ => Err(ChipInitError::ProbeError), + } + } + + pub fn get_size(&self) -> usize { + unsafe { libflashrom_sys::flashrom_flash_getsize(self.ctx.as_ref()) } + } + + /// Read the write protect config of this [`Chip`] + pub fn get_wp(&mut self) -> std::result::Result<WriteProtectCfg, WPError> { + let mut cfg = WriteProtectCfg::new()?; + let res = + unsafe { libflashrom_sys::flashrom_wp_read_cfg(cfg.wp.as_mut(), self.ctx.as_mut()) }; + if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK { + return Err(res.into()); + } + Ok(cfg) + } + + /// Set the write protect config of this [`Chip`] + pub fn set_wp(&mut self, wp: &WriteProtectCfg) -> std::result::Result<(), WPError> { + let res = + unsafe { libflashrom_sys::flashrom_wp_write_cfg(self.ctx.as_mut(), wp.wp.as_ref()) }; + if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK { + return Err(res.into()); + } + Ok(()) + } + + /// Read the write protect ranges of this [`Chip`] + /// + /// # Panics + /// + /// Panics if flashrom_wp_get_available_ranges returns FLASHROM_WP_OK and a NULL pointer. + pub fn get_wp_ranges(&mut self) -> std::result::Result<Vec<Range>, WPError> { + let mut ranges: *mut libflashrom_sys::flashrom_wp_ranges = null_mut(); + let res = unsafe { + libflashrom_sys::flashrom_wp_get_available_ranges(&mut ranges, self.ctx.as_mut()) + }; + if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK { + return Err(res.into()); + } + let ranges = WriteProtectRanges { + ranges: NonNull::new(ranges).expect("flashrom_wp_get_available_ranges returned null"), + }; + + let count = + unsafe { libflashrom_sys::flashrom_wp_ranges_get_count(ranges.ranges.as_ref()) }; + let mut ret = Vec::with_capacity(count); + for index in 0..count { + let mut start = 0; + let mut len = 0; + let res = unsafe { + libflashrom_sys::flashrom_wp_ranges_get_range( + &mut start, + &mut len, + ranges.ranges.as_ref(), + // TODO: fix after https://review.coreboot.org/c/flashrom/+/64996 + index + .try_into() + .expect("flashrom_wp_ranges_get_count does not fit in a u32"), + ) + }; + if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK { + return Err(res.into()); + } + ret.push(start..(start + len)) + } + Ok(ret) + } + + /// Returns the layout read from the fmap of this [`Chip`] + /// + /// # Panics + /// + /// Panics if flashrom_layout_read_fmap_from_rom returns FLASHROM_WP_OK and a NULL pointer. + pub fn layout_read_fmap_from_rom(&mut self) -> std::result::Result<Layout, ErrorCode> { + let mut layout: *mut libflashrom_sys::flashrom_layout = null_mut(); + let err = unsafe { + libflashrom_sys::flashrom_layout_read_fmap_from_rom( + &mut layout, + self.ctx.as_mut(), + 0, + self.get_size(), + ) + }; + if err != 0 { + return Err(ErrorCode { + function: "flashrom_layout_read_fmap_from_rom", + code: err, + }); + } + Ok(Layout { + layout: NonNull::new(layout).expect("flashrom_layout_read_fmap_from_rom returned null"), + }) + } + + /// Sets the layout of this [`Chip`] + /// + /// [`Chip`] takes ownership of Layout to ensure it is not released before the [`Chip`]. + fn set_layout(&mut self, layout: Layout) { + unsafe { libflashrom_sys::flashrom_layout_set(self.ctx.as_mut(), layout.layout.as_ref()) }; + self.layout = Some(layout) + } + + fn unset_layout(&mut self) -> Option<Layout> { + unsafe { libflashrom_sys::flashrom_layout_set(self.ctx.as_mut(), null()) }; + self.layout.take() + } + + /// Read the whole [`Chip`], or a portion specified in a Layout + pub fn image_read( + &mut self, + layout: Option<Layout>, + ) -> std::result::Result<Vec<u8>, ErrorCode> { + if let Some(layout) = layout { + self.set_layout(layout); + } + let len = self.get_size(); + let mut buf = vec![0; len]; + let res = unsafe { + libflashrom_sys::flashrom_image_read( + self.ctx.as_mut(), + buf.as_mut_ptr() as *mut c_void, + len, + ) + }; + self.unset_layout(); + + if res == 0 { + Ok(buf) + } else { + Err(ErrorCode { + function: "flashrom_image_read", + code: res, + }) + } + } + + /// Write the whole [`Chip`], or a portion specified in a Layout + pub fn image_write( + &mut self, + buf: &mut [u8], + layout: Option<Layout>, + ) -> std::result::Result<(), ErrorCode> { + if let Some(layout) = layout { + self.set_layout(layout); + } + let res = unsafe { + libflashrom_sys::flashrom_image_write( + self.ctx.as_mut(), + buf.as_mut_ptr() as *mut c_void, + buf.len(), + null(), + ) + }; + self.unset_layout(); + + if res == 0 { + Ok(()) + } else { + Err(ErrorCode { + function: "flashrom_image_write", + code: res, + }) + } + } + + /// Verify the whole [`Chip`], or a portion specified in a Layout + pub fn image_verify( + &mut self, + buf: &[u8], + layout: Option<Layout>, + ) -> std::result::Result<(), ErrorCode> { + if let Some(layout) = layout { + self.set_layout(layout); + } + let res = unsafe { + libflashrom_sys::flashrom_image_verify( + self.ctx.as_mut(), + buf.as_ptr() as *const c_void, + buf.len(), + ) + }; + self.unset_layout(); + + if res == 0 { + Ok(()) + } else { + Err(ErrorCode { + function: "flashrom_image_verify", + code: res, + }) + } + } + + /// Erase the whole [`Chip`] + pub fn erase(&mut self) -> std::result::Result<(), ErrorCode> { + let res = unsafe { libflashrom_sys::flashrom_flash_erase(self.ctx.as_mut()) }; + if res == 0 { + Ok(()) + } else { + Err(ErrorCode { + function: "flashrom_flash_erase", + code: res, + }) + } + } +} + +impl Drop for Chip { + fn drop(&mut self) { + unsafe { + libflashrom_sys::flashrom_flash_release(self.ctx.as_mut()); + } + } +} + +/// Structure for an initialised flashrom_wp_cfg +#[derive(Debug)] +pub struct WriteProtectCfg { + wp: NonNull<libflashrom_sys::flashrom_wp_cfg>, +} + +impl WriteProtectCfg { + /// Create an empty [`WriteProtectCfg`] + /// + /// # Panics + /// + /// Panics if flashrom_wp_cfg_new returns FLASHROM_WP_OK and a NULL pointer. + pub fn new() -> std::result::Result<WriteProtectCfg, WPError> { + let mut cfg: *mut libflashrom_sys::flashrom_wp_cfg = null_mut(); + let res = unsafe { libflashrom_sys::flashrom_wp_cfg_new(&mut cfg) }; + if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK { + return Err(res.into()); + } + Ok(WriteProtectCfg { + wp: NonNull::new(cfg).expect("flashrom_wp_cfg_new returned null"), + }) + } + + pub fn get_mode(&self) -> libflashrom_sys::flashrom_wp_mode { + unsafe { libflashrom_sys::flashrom_wp_get_mode(self.wp.as_ref()) } + } + + pub fn set_mode(&mut self, mode: libflashrom_sys::flashrom_wp_mode) { + unsafe { libflashrom_sys::flashrom_wp_set_mode(self.wp.as_mut(), mode) } + } + + pub fn get_range(&self) -> Range { + let mut start = 0; + let mut len = 0; + unsafe { libflashrom_sys::flashrom_wp_get_range(&mut start, &mut len, self.wp.as_ref()) }; + start..(start + len) + } + + pub fn set_range<T>(&mut self, range: T) + where + T: std::ops::RangeBounds<usize>, + { + let range: RangeInternal = range.into(); + unsafe { libflashrom_sys::flashrom_wp_set_range(self.wp.as_mut(), range.start, range.len) } + } +} + +impl Drop for WriteProtectCfg { + fn drop(&mut self) { + unsafe { + libflashrom_sys::flashrom_wp_cfg_release(self.wp.as_mut()); + } + } +} + +impl fmt::Display for WriteProtectCfg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "range:{:?} mode:{:?}", self.get_range(), self.get_mode()) + } +} + +#[derive(Debug)] +struct WriteProtectRanges { + ranges: NonNull<libflashrom_sys::flashrom_wp_ranges>, +} + +impl WriteProtectRanges {} + +impl Drop for WriteProtectRanges { + fn drop(&mut self) { + unsafe { + libflashrom_sys::flashrom_wp_ranges_release(self.ranges.as_mut()); + } + } +} + +/// Structure for an initialised flashrom_layout +#[derive(Debug)] +pub struct Layout { + layout: NonNull<libflashrom_sys::flashrom_layout>, +} + +impl Layout { + /// Create an empty [`Layout`] + /// + /// # Panics + /// + /// Panics if flashrom_layout_new returns 0 and a NULL pointer. + pub fn new() -> std::result::Result<Layout, ErrorCode> { + let mut layout: *mut libflashrom_sys::flashrom_layout = null_mut(); + let err = unsafe { libflashrom_sys::flashrom_layout_new(&mut layout) }; + if err != 0 { + Err(ErrorCode { + function: "flashrom_layout_new", + code: err, + }) + } else { + Ok(Layout { + layout: NonNull::new(layout).expect("flashrom_layout_new returned null"), + }) + } + } + + /// Add a region to the [`Layout`] + /// + /// Not the region will not be 'included', include_region must be called to include the region. + /// + /// # Errors + /// + /// This function will return an error if the region is not a valid CString, + /// or if libflashrom returns an error. + pub fn add_region<T>(&mut self, region: &str, range: T) -> std::result::Result<(), RegionError> + where + T: std::ops::RangeBounds<usize>, + { + let range: RangeInternal = range.into(); + let err = { + let region = CString::new(region)?; + unsafe { + libflashrom_sys::flashrom_layout_add_region( + self.layout.as_mut(), + range.start, + range.end(), + region.as_ptr(), + ) + } + }; + + if err != 0 { + Err(RegionError::ErrorCode(ErrorCode { + function: "flashrom_layout_add_region", + code: err, + })) + } else { + Ok(()) + } + } + + /// Include a region + /// + /// # Errors + /// + /// This function will return an error if the region is not a valid CString, + /// or if libflashrom returns an error. + pub fn include_region(&mut self, region: &str) -> std::result::Result<(), RegionError> { + let err = { + let region = CString::new(region)?; + unsafe { + libflashrom_sys::flashrom_layout_include_region( + self.layout.as_mut(), + region.as_ptr(), + ) + } + }; + if err != 0 { + Err(RegionError::ErrorCode(ErrorCode { + function: "flashrom_layout_include_region", + code: err, + })) + } else { + Ok(()) + } + } + + /// Get the [`Range`] for the given region + /// + /// # Errors + /// + /// This function will return an error if the region is not a valid CString, + /// or if libflashrom returns an error. + pub fn get_region_range(&mut self, region: &str) -> std::result::Result<Range, RegionError> { + let mut start: std::os::raw::c_uint = 0; + let mut len: std::os::raw::c_uint = 0; + let err = { + let region = CString::new(region)?; + unsafe { + libflashrom_sys::flashrom_layout_get_region_range( + self.layout.as_mut(), + region.as_ptr(), + &mut start, + &mut len, + ) + } + }; + if err != 0 { + Err(RegionError::ErrorCode(ErrorCode { + function: "flashrom_layout_get_region_range", + code: err, + })) + } else { + // should be safe to assume sizeof(size_t) >= sizeof(unsigned int) + // TODO: fix after https://review.coreboot.org/c/flashrom/+/65944 + Ok(start.try_into().unwrap()..(start + len).try_into().unwrap()) + } + } +} + +// TODO this will be replaced with an API implementation: https://review.coreboot.org/c/flashrom/+/65999 +impl std::str::FromStr for Layout { + type Err = Box<dyn error::Error>; + + /// This will attempt to parse the [`Layout`] file format into a [`Layout`] + /// + /// The format is documented in the flashrom man page. + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + let mut ret = Layout::new()?; + + // format is hex:hex name + // flashrom layout.c seems to allow any characters in the name string + // I am restricting here to non whitespace + let re = Regex::new(r"^([0-9A-Za-z]+):([0-9A-Za-z]+)\s+(\S+)$").unwrap(); + + // we dont use captures_iter else we would ignore incorrect lines + for line in s.lines() { + let (start, end, name) = match re.captures(line) { + Some(caps) => ( + caps.get(1).unwrap(), + caps.get(2).unwrap(), + caps.get(3).unwrap(), + ), + None => Err(ParseLayoutError(format!("failed to parse: {:?}", line)))?, + }; + let start = usize::from_str_radix(start.as_str(), 16)?; + let end = usize::from_str_radix(end.as_str(), 16)?; + ret.add_region(name.as_str(), start..=end)?; + } + + Ok(ret) + } +} + +impl Drop for Layout { + fn drop(&mut self) { + unsafe { + libflashrom_sys::flashrom_layout_release(self.layout.as_mut()); + } + } +} + +#[cfg(test)] +mod tests { + use gag::BufferRedirect; + use std::io::Read; + + use super::flashrom_version_info; + use super::set_log_level; + use super::Chip; + use super::ChipInitError; + use super::InitError; + use crate::set_log_function; + use crate::Layout; + use crate::Programmer; + use crate::WriteProtectCfg; + + // flashrom contains global state, which prevents correct initialisation of + // a second programmer or probing of a second chip. Run all unit tests in + // forked subprocesses to avoid this issue. + use rusty_fork::rusty_fork_test; + rusty_fork_test! { + + #[test] + fn version() { + // There is no version requirement yet, but for example: + // assert!(flashrom_version_info().contains("v1.2")) + assert!(!flashrom_version_info().unwrap().is_empty()) + } + + #[test] + fn only_one_programmer() { + { + let _1 = Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(); + // Only one programmer can be initialised at a time. + assert_eq!(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap_err(), InitError::DuplicateInit) + } + // Only one programmer can ever be initialised + assert_eq!(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap_err(), InitError::DuplicateInit) + } + + #[test] + fn programmer_bad_cstring_name() { + assert!(matches!(Programmer::new("dummy\0", None).unwrap_err(), InitError::InvalidName(_))) + } + + #[test] + fn chip_none() { + // Not specifying a chip will select one if there is one. + Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), None).unwrap(); + } + + #[test] + fn chip_some() { + // Specifying a valid chip. + Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap(); + } + + #[test] + fn chip_nochip() { + // Choosing a non existent chip fails. + assert_eq!( + Chip::new(Programmer::new("dummy", None).unwrap(), Some("W25Q128.V")).unwrap_err(), + ChipInitError::NoChipError + ); + } + + #[test] + fn logging_stderr() { + let mut buf = BufferRedirect::stderr().unwrap(); + let mut fc = Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap(); + + set_log_level(Some(libflashrom_sys::FLASHROM_MSG_INFO)); + fc.image_read(None).unwrap(); + let mut stderr = String::new(); + if buf.read_to_string(&mut stderr).unwrap() == 0 { + panic!("stderr empty when it should have some messages"); + } + + set_log_level(None); + fc.image_read(None).unwrap(); + if buf.read_to_string(&mut stderr).unwrap() != 0 { + panic!("stderr not empty when it should be silent"); + } + } + + #[test] + fn logging_custom() { + // Check that a custom logging callback works + static mut BUF: String = String::new(); + fn logger( + _: libflashrom_sys::flashrom_log_level, + format: &str, + ) { + unsafe {BUF.push_str(format)} + } + set_log_function(logger); + set_log_level(Some(libflashrom_sys::FLASHROM_MSG_SPEW)); + Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap(); + assert_ne!(unsafe{BUF.len()}, 0); + } + + #[test] + fn flashchip() { + // basic tests of the flashchip methods + let mut fc = Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap(); + fc.get_size(); + + let mut wp = fc.get_wp().unwrap(); + wp.set_mode(libflashrom_sys::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED); + fc.set_wp(&wp).unwrap(); + fc.get_wp_ranges().unwrap(); + + fn test_layout() -> Layout { + let mut layout = Layout::new().unwrap(); + layout.add_region("xyz", 100..200).unwrap(); + layout.include_region("xyz").unwrap(); + layout.add_region("abc", 100..200).unwrap(); + layout + } + + fc.image_read(None).unwrap(); + fc.image_read(Some(test_layout())).unwrap(); + + let mut buf = vec![0; fc.get_size()]; + fc.image_write(&mut buf, None).unwrap(); + fc.image_write(&mut buf, Some(test_layout())).unwrap(); + + fc.image_verify(&buf, None).unwrap(); + fc.image_verify(&buf, Some(test_layout())).unwrap(); + + fc.erase().unwrap(); + } + + #[test] + fn write_protect() { + let mut wp = WriteProtectCfg::new().unwrap(); + wp.set_mode(libflashrom_sys::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED); + wp.set_range(100..200); + assert_eq!(wp.get_mode(), libflashrom_sys::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED); + assert_eq!(wp.get_range(), 100..200); + } + } +} diff --git a/bindings/rust/libflashrom/src/log.c b/bindings/rust/libflashrom/src/log.c new file mode 100644 index 00000000..6ca3ad15 --- /dev/null +++ b/bindings/rust/libflashrom/src/log.c @@ -0,0 +1,61 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2022 The Chromium OS Authors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> + +#include "libflashrom.h" + +void log_rust(enum flashrom_log_level level, const char *format); + +// LOG_LEVEL = 0 (ERROR) means no messages are printed. +enum flashrom_log_level current_level = 0; + +static int log_c(enum flashrom_log_level level, const char *format, va_list ap) +{ + static char *buf = NULL; + static int len = 0; + if (level >= current_level) { + return 0; + } + + va_list ap2; + va_copy(ap2, ap); + // when buf is NULL, len is zero and this will not write to buf + int req_len = vsnprintf(buf, len, format, ap2); + va_end(ap2); + + if (req_len > len) { + char *new_buf = realloc(buf, req_len + 1); + if (!new_buf) { + return 0; + } + buf = new_buf; + len = req_len + 1; + req_len = vsnprintf(buf, len, format, ap); + } + + if (req_len > 0) { + log_rust(level, buf); + } + return req_len; +} + +void set_log_callback() +{ + flashrom_set_log_callback(log_c); +} |