// // Copyright 2019, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Alternatively, this software may be distributed under the terms of the // GNU General Public License ("GPL") version 2 as published by the Free // Software Foundation. // #[macro_use] extern crate log; mod cmd; pub use cmd::{dut_ctrl_toggle_wp, FlashromCmd}; #[derive(Copy, Clone, PartialEq, Debug)] pub enum FlashChip { EC, HOST, SERVO, DEDIPROG, } impl FlashChip { pub fn from(s: &str) -> Result { let r = match s { "ec" => Ok(FlashChip::EC), "host" => Ok(FlashChip::HOST), "servo" => Ok(FlashChip::SERVO), "dediprog" => Ok(FlashChip::DEDIPROG), _ => Err("cannot convert str to enum"), }; return r; } pub fn to(fc: FlashChip) -> &'static str { let r = match fc { FlashChip::EC => "ec", FlashChip::HOST => "host", FlashChip::SERVO => "ft2231_spi:type=servo-v2", FlashChip::DEDIPROG => "dediprog", }; return r; } /// Return whether the hardware write protect signal can be controlled. /// /// Servo and dediprog adapters are assumed to always have hardware write protect /// disabled. pub fn can_control_hw_wp(&self) -> bool { match self { FlashChip::HOST | FlashChip::EC => true, FlashChip::SERVO | FlashChip::DEDIPROG => false, } } } pub type FlashromError = String; #[derive(Default)] pub struct FlashromOpt<'a> { pub wp_opt: WPOpt, pub io_opt: IOOpt<'a>, pub layout: Option<&'a str>, // -l pub image: Option<&'a str>, // -i pub flash_name: bool, // --flash-name pub verbose: bool, // -V } #[derive(Default)] pub struct WPOpt { pub range: Option<(i64, i64)>, // --wp-range x0 x1 pub status: bool, // --wp-status pub list: bool, // --wp-list pub enable: bool, // --wp-enable pub disable: bool, // --wp-disable } #[derive(Default)] pub struct IOOpt<'a> { pub read: Option<&'a str>, // -r pub write: Option<&'a str>, // -w pub verify: Option<&'a str>, // -v pub erase: bool, // -E } pub trait Flashrom { fn get_size(&self) -> Result; fn dispatch(&self, fropt: FlashromOpt) -> Result<(Vec, Vec), FlashromError>; } pub fn name(cmd: &cmd::FlashromCmd) -> Result<(String, String), FlashromError> { let opts = FlashromOpt { io_opt: IOOpt { ..Default::default() }, flash_name: true, ..Default::default() }; let (stdout, stderr) = cmd.dispatch(opts)?; let output = String::from_utf8_lossy(stdout.as_slice()); let eoutput = String::from_utf8_lossy(stderr.as_slice()); debug!("name()'stdout: {:#?}.", output); debug!("name()'stderr: {:#?}.", eoutput); match extract_flash_name(&output) { None => Err("Didn't find chip vendor/name in flashrom output".into()), Some((vendor, name)) => Ok((vendor.into(), name.into())), } } /// Get a flash vendor and name from the first matching line of flashrom output. /// /// The target line looks like 'vendor="foo" name="bar"', as output by flashrom --flash-name. /// This is usually the last line of output. fn extract_flash_name(stdout: &str) -> Option<(&str, &str)> { for line in stdout.lines() { if !line.starts_with("vendor=\"") { continue; } let tail = line.trim_start_matches("vendor=\""); let mut split = tail.splitn(2, "\" name=\""); let vendor = split.next(); let name = split.next().map(|s| s.trim_end_matches('"')); match (vendor, name) { (Some(v), Some(n)) => return Some((v, n)), _ => continue, } } None } pub struct ROMWriteSpecifics<'a> { pub layout_file: Option<&'a str>, pub write_file: Option<&'a str>, pub name_file: Option<&'a str>, } pub fn write_file_with_layout( cmd: &cmd::FlashromCmd, rws: &ROMWriteSpecifics, ) -> Result { let opts = FlashromOpt { io_opt: IOOpt { write: rws.write_file, ..Default::default() }, layout: rws.layout_file, image: rws.name_file, ..Default::default() }; let (stdout, stderr) = cmd.dispatch(opts)?; let output = String::from_utf8_lossy(stdout.as_slice()); let eoutput = String::from_utf8_lossy(stderr.as_slice()); debug!("write_file_with_layout()'stdout:\n{}.", output); debug!("write_file_with_layout()'stderr:\n{}.", eoutput); Ok(true) } pub fn wp_range( cmd: &cmd::FlashromCmd, range: (i64, i64), wp_enable: bool, ) -> Result { let opts = FlashromOpt { wp_opt: WPOpt { range: Some(range), enable: wp_enable, ..Default::default() }, ..Default::default() }; let (stdout, stderr) = cmd.dispatch(opts)?; let output = String::from_utf8_lossy(stdout.as_slice()); let eoutput = String::from_utf8_lossy(stderr.as_slice()); debug!("wp_range()'stdout:\n{}.", output); debug!("wp_range()'stderr:\n{}.", eoutput); Ok(true) } pub fn wp_list(cmd: &cmd::FlashromCmd) -> Result { let opts = FlashromOpt { wp_opt: WPOpt { list: true, ..Default::default() }, ..Default::default() }; let (stdout, _) = cmd.dispatch(opts)?; let output = String::from_utf8_lossy(stdout.as_slice()); if output.len() == 0 { return Err( "wp_list isn't supported on platforms using the Linux kernel SPI driver wp_list".into(), ); } Ok(output.to_string()) } pub fn wp_status(cmd: &cmd::FlashromCmd, en: bool) -> Result { let status = if en { "en" } else { "dis" }; info!("See if chip write protect is {}abled", status); let opts = FlashromOpt { wp_opt: WPOpt { status: true, ..Default::default() }, ..Default::default() }; let (stdout, _) = cmd.dispatch(opts)?; let output = String::from_utf8_lossy(stdout.as_slice()); debug!("wp_status():\n{}", output); let s = std::format!("write protect is {}abled", status); Ok(output.contains(&s)) } pub fn wp_toggle(cmd: &cmd::FlashromCmd, en: bool) -> Result { let status = if en { "en" } else { "dis" }; // For MTD, --wp-range and --wp-enable must be used simultaneously. let range = if en { let rom_sz: i64 = cmd.get_size()?; Some((0, rom_sz)) // (start, len) } else { None }; let opts = FlashromOpt { wp_opt: WPOpt { range: range, enable: en, disable: !en, ..Default::default() }, ..Default::default() }; let (stdout, stderr) = cmd.dispatch(opts)?; let output = String::from_utf8_lossy(stdout.as_slice()); let eoutput = String::from_utf8_lossy(stderr.as_slice()); debug!("wp_toggle()'stdout:\n{}.", output); debug!("wp_toggle()'stderr:\n{}.", eoutput); match wp_status(&cmd, true) { Ok(_ret) => { info!("Successfully {}abled write-protect", status); Ok(true) } Err(e) => Err(format!("Cannot {}able write-protect: {}", status, e)), } } pub fn read(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> { let opts = FlashromOpt { io_opt: IOOpt { read: Some(path), ..Default::default() }, ..Default::default() }; let (stdout, _) = cmd.dispatch(opts)?; let output = String::from_utf8_lossy(stdout.as_slice()); debug!("read():\n{}", output); Ok(()) } pub fn write(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> { let opts = FlashromOpt { io_opt: IOOpt { write: Some(path), ..Default::default() }, ..Default::default() }; let (stdout, _) = cmd.dispatch(opts)?; let output = String::from_utf8_lossy(stdout.as_slice()); debug!("write():\n{}", output); Ok(()) } pub fn verify(cmd: &cmd::FlashromCmd, path: &str) -> Result<(), FlashromError> { let opts = FlashromOpt { io_opt: IOOpt { verify: Some(path), ..Default::default() }, ..Default::default() }; let (stdout, _) = cmd.dispatch(opts)?; let output = String::from_utf8_lossy(stdout.as_slice()); debug!("verify():\n{}", output); Ok(()) } pub fn erase(cmd: &cmd::FlashromCmd) -> Result<(), FlashromError> { let opts = FlashromOpt { io_opt: IOOpt { erase: true, ..Default::default() }, ..Default::default() }; let (stdout, _) = cmd.dispatch(opts)?; let output = String::from_utf8_lossy(stdout.as_slice()); debug!("erase():\n{}", output); Ok(()) } #[cfg(test)] mod tests { #[test] fn extract_flash_name() { use super::extract_flash_name; assert_eq!( extract_flash_name( "coreboot table found at 0x7cc13000\n\ Found chipset \"Intel Braswell\". Enabling flash write... OK.\n\ vendor=\"Winbond\" name=\"W25Q64DW\"\n" ), Some(("Winbond", "W25Q64DW")) ); assert_eq!( extract_flash_name( "vendor name is TEST\n\ Something failed!" ), None ) } }