aboutsummaryrefslogtreecommitdiffstats
path: root/bindings/rust/libflashrom
diff options
context:
space:
mode:
Diffstat (limited to 'bindings/rust/libflashrom')
-rw-r--r--bindings/rust/libflashrom/.cargo/config.toml2
-rw-r--r--bindings/rust/libflashrom/Cargo.toml20
-rw-r--r--bindings/rust/libflashrom/build.rs17
-rw-r--r--bindings/rust/libflashrom/src/lib.rs1094
-rw-r--r--bindings/rust/libflashrom/src/log.c61
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);
+}