diff --git a/Cargo.lock b/Cargo.lock index 9ad6d33d..0c0c3f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -539,9 +539,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.139" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libgit2-sys" @@ -841,8 +841,11 @@ dependencies = [ [[package]] name = "redox_hwio" version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb516ad341a84372b5b15a5a35cf136ba901a639c8536f521b108253d7fce74" +source = "git+https://github.com/FrameworkComputer/rust-hwio?branch=freebsd#9e6e7529ffd6caf7aa6a17be1eca6756b302f736" +dependencies = [ + "lazy_static", + "nix", +] [[package]] name = "regex" diff --git a/README.md b/README.md index 0bb53cf6..80ad8166 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ see the [Support Matrices](support-matrices.md). - [x] OS Tool (`framework_tool`) - [x] Tested on Linux - [x] Tested on Windows - - [ ] Tested on FreeBSD + - [x] Tested on FreeBSD - [x] UEFI Shell tool (`framework_uefi`) ###### Firmware Information - [x] Show system information - - [x] ESRT table (UEFI and Linux only) (`--esrt`) + - [x] ESRT table (UEFI, Linux, FreeBSD only) (`--esrt`) - [x] SMBIOS - [x] Get firmware version from binary file - [x] Legacy EC (Intel 13th Gen and earlier) (`--ec-bin`) @@ -299,3 +299,15 @@ Keyboard backlight: 0% [DEBUG] send_command(command=0x22, ver=0, data_len=0) Keyboard backlight: 0% ``` + +## FreeBSD + +``` +sudo pkg install hidapi + +# Build the library and tool +cargo build --no-default-features --features freebsd + +# Running the tool +cargo run --no-default-features --features freebsd +``` diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml index bf0ea2a6..2e0fe967 100644 --- a/framework_lib/Cargo.toml +++ b/framework_lib/Cargo.toml @@ -8,10 +8,11 @@ build = "build.rs" [features] default = ["linux"] # Linux/FreeBSD -unix = ["std", "cros_ec_driver", "raw_pio", "smbios", "dep:nix"] -linux = ["linux_pio", "unix"] +unix = ["std", "raw_pio", "smbios", "dep:nix", "dep:libc"] +linux = ["unix", "linux_pio", "cros_ec_driver"] +freebsd = ["unix", "freebsd_pio"] # Windows does not have the cros_ec driver nor raw port I/O access to userspace -windows = ["std", "smbios", "dep:windows", "win_driver"] +windows = ["std", "smbios", "dep:windows", "win_driver", "raw_pio"] smbios = ["dep:smbios-lib"] std = ["dep:clap", "dep:clap-verbosity-flag", "dep:env_logger", "smbios-lib?/std", "dep:hidapi", "dep:rusb"] uefi = [ @@ -22,8 +23,10 @@ uefi = [ "sha2/force-soft" ] +# EC communication via Port I/O on FreeBSD +freebsd_pio = ["redox_hwio/std"] # EC communication via Port I/O on Linux -linux_pio = ["dep:libc"] +linux_pio = ["dep:libc", "redox_hwio/std"] # EC communication via raw Port I/O (e.g. UEFI or other ring 0 code) raw_pio = [] # EC communication via cros_ec driver on Linux @@ -39,8 +42,8 @@ built = { version = "0.5", features = ["chrono", "git2"] } lazy_static = "1.4.0" sha2 = { version = "0.10.6", default_features = false, features = [ "force-soft" ] } regex = { version = "1.10.0", default-features = false } -redox_hwio = { version = "0.1.5", default_features = false } -libc = { version = "0.2.137", optional = true } +redox_hwio = { git = "https://github.com/FrameworkComputer/rust-hwio", branch = "freebsd", default_features = false } +libc = { version = "0.2.155", optional = true } clap = { version = "4.0", features = ["derive"], optional = true } clap-verbosity-flag = { version = "2.0.1", optional = true } nix = { version = "0.25.0", optional = true } diff --git a/framework_lib/src/ccgx/binary.rs b/framework_lib/src/ccgx/binary.rs index cdc18153..30d7b33d 100644 --- a/framework_lib/src/ccgx/binary.rs +++ b/framework_lib/src/ccgx/binary.rs @@ -150,7 +150,7 @@ fn read_version( unsafe { std::ptr::read(data[0..version_len].as_ptr() as *const _) }; let base_version = BaseVersion::from(version_info.base_version); - let app_version = AppVersion::try_from(version_info.app_version).ok()?; + let app_version = AppVersion::from(version_info.app_version); let fw_silicon_id = version_info.silicon_id; let fw_silicon_family = version_info.silicon_family; diff --git a/framework_lib/src/ccgx/device.rs b/framework_lib/src/ccgx/device.rs index 1b71359f..83e3c07e 100644 --- a/framework_lib/src/ccgx/device.rs +++ b/framework_lib/src/ccgx/device.rs @@ -11,7 +11,7 @@ use core::prelude::rust_2021::derive; use crate::ccgx::{AppVersion, BaseVersion, ControllerVersion}; use crate::chromium_ec::command::EcCommands; -use crate::chromium_ec::{CrosEc, CrosEcDriver, EcError, EcResponseStatus, EcResult}; +use crate::chromium_ec::{CrosEc, CrosEcDriver, EcError, EcResult}; use crate::util::{self, assert_win_len, Config, Platform}; use std::mem::size_of; @@ -299,8 +299,7 @@ impl PdController { let data = self.ccgx_read(register, 8)?; Ok(ControllerVersion { base: BaseVersion::from(&data[..4]), - app: AppVersion::try_from(&data[4..]) - .or(Err(EcError::Response(EcResponseStatus::InvalidResponse)))?, + app: AppVersion::from(&data[4..]), }) } diff --git a/framework_lib/src/ccgx/hid.rs b/framework_lib/src/ccgx/hid.rs index 7af8d9bb..cc46111b 100644 --- a/framework_lib/src/ccgx/hid.rs +++ b/framework_lib/src/ccgx/hid.rs @@ -254,9 +254,17 @@ pub fn find_devices(api: &HidApi, filter_devs: &[u16], sn: Option<&str>) -> Vec< let vid = dev_info.vendor_id(); let pid = dev_info.product_id(); let usage_page = dev_info.usage_page(); + + debug!("Found {:X}:{:X} Usage Page: {}", vid, pid, usage_page); + let usage_page_filter = usage_page == CCG_USAGE_PAGE; + // On FreeBSD it seems we don't get different usage pages + // There's just one entry overall + #[cfg(target_os = "freebsd")] + let usage_page_filter = true; + if vid == FRAMEWORK_VID && filter_devs.contains(&pid) - && usage_page == CCG_USAGE_PAGE + && usage_page_filter && (sn.is_none() || sn == dev_info.serial_number()) { Some(dev_info.clone()) diff --git a/framework_lib/src/chromium_ec/portio.rs b/framework_lib/src/chromium_ec/portio.rs index 7ad22bcb..5ec0212e 100644 --- a/framework_lib/src/chromium_ec/portio.rs +++ b/framework_lib/src/chromium_ec/portio.rs @@ -4,8 +4,9 @@ use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; use core::convert::TryInto; +#[cfg(any(feature = "linux_pio", feature = "freebsd_pio", feature = "raw_pio"))] use hwio::{Io, Pio}; -#[cfg(feature = "linux_pio")] +#[cfg(all(feature = "linux_pio", target_os = "linux"))] use libc::ioperm; use log::Level; #[cfg(feature = "linux_pio")] diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 2faffbbd..a7d9db8c 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -292,7 +292,7 @@ fn print_versions(ec: &CrosEc) { println!("UEFI BIOS"); if let Some(smbios) = get_smbios() { let bios_entries = smbios.collect::(); - let bios = bios_entries.get(0).unwrap(); + let bios = bios_entries.first().unwrap(); println!(" Version: {}", bios.version()); println!(" Release Date: {}", bios.release_date()); } @@ -907,6 +907,14 @@ fn selftest(ec: &CrosEc) -> Option<()> { } fn smbios_info() { + println!("Summary"); + println!(" Is Framework: {}", is_framework()); + if let Some(platform) = smbios::get_platform() { + println!(" Platform: {:?}", platform); + } else { + println!(" Platform: Unknown",); + } + let smbios = get_smbios(); if smbios.is_none() { error!("Failed to find SMBIOS"); diff --git a/framework_lib/src/esrt/mod.rs b/framework_lib/src/esrt/mod.rs index 69fdc281..163db7de 100644 --- a/framework_lib/src/esrt/mod.rs +++ b/framework_lib/src/esrt/mod.rs @@ -20,8 +20,6 @@ use core::prelude::v1::derive; #[cfg(not(feature = "uefi"))] use guid_macros::guid; #[cfg(feature = "uefi")] -use std::slice; -#[cfg(feature = "uefi")] use uefi::{guid, Guid}; #[cfg(feature = "linux")] @@ -31,6 +29,15 @@ use std::io; #[cfg(feature = "linux")] use std::path::Path; +#[cfg(target_os = "freebsd")] +use nix::ioctl_readwrite; +#[cfg(target_os = "freebsd")] +use std::fs::OpenOptions; +#[cfg(target_os = "freebsd")] +use std::os::fd::AsRawFd; +#[cfg(target_os = "freebsd")] +use std::os::unix::fs::OpenOptionsExt; + /// Decode from GUID string version /// /// # Examples @@ -316,7 +323,7 @@ fn esrt_from_sysfs(dir: &Path) -> io::Result { Ok(esrt_table) } -#[cfg(all(not(feature = "uefi"), feature = "linux"))] +#[cfg(all(not(feature = "uefi"), feature = "linux", target_os = "linux"))] pub fn get_esrt() -> Option { let res = esrt_from_sysfs(Path::new("/sys/firmware/efi/esrt/entries")).ok(); if res.is_none() { @@ -332,11 +339,43 @@ pub fn get_esrt() -> Option { None } +#[cfg(target_os = "freebsd")] +#[repr(C)] +struct EfiGetTableIoc { + buf: *mut u8, + uuid: [u8; 16], + table_len: usize, + buf_len: usize, +} +#[cfg(target_os = "freebsd")] +ioctl_readwrite!(efi_get_table, b'E', 1, EfiGetTableIoc); + #[cfg(all(not(feature = "uefi"), target_os = "freebsd"))] pub fn get_esrt() -> Option { - // TODO: Implement - println!("Reading ESRT is not implemented on FreeBSD yet."); - None + let file = OpenOptions::new() + .read(true) + .write(true) + .custom_flags(libc::O_NONBLOCK) + .open("/dev/efi") + .unwrap(); + + let mut buf: Vec = Vec::new(); + let mut table = EfiGetTableIoc { + buf: std::ptr::null_mut(), + uuid: SYSTEM_RESOURCE_TABLE_GUID.to_bytes(), + buf_len: 0, + table_len: 0, + }; + unsafe { + let fd = file.as_raw_fd(); + let _res = efi_get_table(fd, &mut table).unwrap(); + buf.resize(table.table_len, 0); + table.buf_len = table.table_len; + table.buf = buf.as_mut_ptr(); + + let _res = efi_get_table(fd, &mut table).unwrap(); + esrt_from_buf(table.buf) + } } /// gEfiSystemResourceTableGuid from MdePkg/MdePkg.dec @@ -353,26 +392,32 @@ pub fn get_esrt() -> Option { let table_guid: Guid = unsafe { std::mem::transmute(table.guid) }; match table_guid { SYSTEM_RESOURCE_TABLE_GUID => unsafe { - let raw_esrt = &*(table.address as *const _Esrt); - let mut esrt = Esrt { - resource_count: raw_esrt.resource_count, - resource_count_max: raw_esrt.resource_count_max, - resource_version: raw_esrt.resource_version, - entries: vec![], - }; - - // Make sure it's the version we expect - debug_assert!(esrt.resource_version == ESRT_FIRMWARE_RESOURCE_VERSION); - - let src_ptr = std::ptr::addr_of!(raw_esrt.entries) as *const EsrtResourceEntry; - let slice_entries = slice::from_raw_parts(src_ptr, esrt.resource_count as usize); - - esrt.entries = slice_entries.to_vec(); - - return Some(esrt); + return esrt_from_buf(table.address as *const u8); }, _ => {} } } None } + +/// Parse the ESRT table buffer +#[cfg(any(feature = "uefi", target_os = "freebsd"))] +unsafe fn esrt_from_buf(ptr: *const u8) -> Option { + let raw_esrt = &*(ptr as *const _Esrt); + let mut esrt = Esrt { + resource_count: raw_esrt.resource_count, + resource_count_max: raw_esrt.resource_count_max, + resource_version: raw_esrt.resource_version, + entries: vec![], + }; + + // Make sure it's the version we expect + debug_assert!(esrt.resource_version == ESRT_FIRMWARE_RESOURCE_VERSION); + + let src_ptr = core::ptr::addr_of!(raw_esrt.entries) as *const EsrtResourceEntry; + let slice_entries = core::slice::from_raw_parts(src_ptr, esrt.resource_count as usize); + + esrt.entries = slice_entries.to_vec(); + + Some(esrt) +} diff --git a/framework_lib/src/smbios.rs b/framework_lib/src/smbios.rs index 2c345360..e88d785a 100644 --- a/framework_lib/src/smbios.rs +++ b/framework_lib/src/smbios.rs @@ -2,7 +2,7 @@ use std::prelude::v1::*; -#[cfg(not(feature = "uefi"))] +#[cfg(all(not(feature = "uefi"), not(target_os = "freebsd")))] use std::io::ErrorKind; use crate::util::{Config, Platform}; @@ -13,6 +13,9 @@ use spin::Mutex; #[cfg(not(feature = "uefi"))] use std::sync::Mutex; +#[cfg(target_os = "freebsd")] +use std::io::{Read, Seek, SeekFrom}; + /// Current platform. Won't ever change during the program's runtime static CACHED_PLATFORM: Mutex>> = Mutex::new(None); @@ -46,6 +49,13 @@ pub fn is_framework() -> bool { ) { return true; } + + // Don't need to parse SMBIOS on FreeBSD, can just read kenv + #[cfg(target_os = "freebsd")] + if let Ok(maker) = kenv_get("smbios.system.maker") { + return maker == "Framework"; + } + let smbios = if let Some(smbios) = get_smbios() { smbios } else { @@ -75,6 +85,103 @@ pub fn dmidecode_string_val(s: &SMBiosString) -> Option { } } +#[cfg(target_os = "freebsd")] +#[repr(C)] +pub struct Smbios3 { + pub anchor: [u8; 5], + pub checksum: u8, + pub length: u8, + pub major_version: u8, + pub minor_version: u8, + pub docrev: u8, + pub revision: u8, + _reserved: u8, + pub table_length: u32, + pub table_address: u64, +} + +#[cfg(target_os = "freebsd")] +#[repr(packed)] +pub struct Smbios { + pub anchor: [u8; 4], + pub checksum: u8, + pub length: u8, + pub major_version: u8, + pub minor_version: u8, + pub max_structure_size: u16, + pub revision: u8, + pub formatted: [u8; 5], + pub inter_anchor: [u8; 5], + pub inter_checksum: u8, + pub table_length: u16, + pub table_address: u32, + pub structure_count: u16, + pub bcd_revision: u8, +} + +#[cfg(target_os = "freebsd")] +pub fn get_smbios() -> Option { + // Get the SMBIOS entrypoint address from the kernel environment + let addr_hex = kenv_get("hint.smbios.0.mem").ok()?; + let addr_hex = addr_hex.trim_start_matches("0x"); + let addr = u64::from_str_radix(addr_hex, 16).unwrap(); + trace!("SMBIOS Entrypoint Addr: {} 0x{:x}", addr_hex, addr); + + let mut dev_mem = std::fs::File::open("/dev/mem").ok()?; + // Smbios struct is larger than Smbios3 struct + let mut header_buf = [0; std::mem::size_of::()]; + dev_mem.seek(SeekFrom::Start(addr)).ok()?; + dev_mem.read_exact(&mut header_buf).ok()?; + + let entrypoint = unsafe { &*(header_buf.as_ptr() as *const Smbios3) }; + + trace!("SMBIOS Anchor {:?} = ", entrypoint.anchor); + let (addr, len, version) = match entrypoint.anchor { + [b'_', b'S', b'M', b'3', b'_'] => { + trace!("_SM3_"); + let entrypoint = unsafe { &*(header_buf.as_ptr() as *const Smbios3) }; + let ver = Some(SMBiosVersion { + major: entrypoint.major_version, + minor: entrypoint.minor_version, + revision: 0, + }); + + (entrypoint.table_address, entrypoint.table_length, ver) + } + [b'_', b'S', b'M', b'_', _] => { + trace!("_SM_"); + let entrypoint = unsafe { &*(header_buf.as_ptr() as *const Smbios) }; + let ver = Some(SMBiosVersion { + major: entrypoint.major_version, + minor: entrypoint.minor_version, + revision: 0, + }); + + ( + entrypoint.table_address as u64, + entrypoint.table_length as u32, + ver, + ) + } + [b'_', b'D', b'M', b'I', b'_'] => { + error!("_DMI_ - UNSUPPORTED"); + return None; + } + _ => { + error!(" Unknown - UNSUPPORTED"); + return None; + } + }; + + // Get actual SMBIOS table data + let mut smbios_buf = vec![0; len as usize]; + dev_mem.seek(SeekFrom::Start(addr)).ok()?; + dev_mem.read_exact(&mut smbios_buf).ok()?; + + let smbios = SMBiosData::from_vec_and_version(smbios_buf, version); + Some(smbios) +} + #[cfg(feature = "uefi")] pub fn get_smbios() -> Option { let data = crate::uefi::smbios_data().unwrap(); @@ -83,9 +190,8 @@ pub fn get_smbios() -> Option { Some(smbios) } // On Linux this reads either from /dev/mem or sysfs -// On FreeBSD from /dev/mem // On Windows from the kernel API -#[cfg(not(feature = "uefi"))] +#[cfg(all(not(feature = "uefi"), not(target_os = "freebsd")))] pub fn get_smbios() -> Option { match smbioslib::table_load_from_device() { Ok(data) => Some(data), @@ -100,6 +206,28 @@ pub fn get_smbios() -> Option { } } +fn get_product_name() -> Option { + // On FreeBSD we can short-circuit and avoid parsing SMBIOS + #[cfg(target_os = "freebsd")] + if let Ok(product) = kenv_get("smbios.system.product") { + return Some(product); + } + + let smbios = get_smbios(); + if smbios.is_none() { + println!("Failed to find SMBIOS"); + } + let mut smbios = smbios.into_iter().flatten(); + smbios.find_map(|undefined_struct| { + if let DefinedStruct::SystemInformation(data) = undefined_struct.defined_struct() { + if let Some(product_name) = dmidecode_string_val(&data.product_name()) { + return Some(product_name.as_str().to_string()); + } + } + None + }) +} + pub fn get_platform() -> Option { #[cfg(feature = "uefi")] let mut cached_platform = CACHED_PLATFORM.lock(); @@ -120,46 +248,18 @@ pub fn get_platform() -> Option { } } - let smbios = get_smbios(); - if smbios.is_none() { - println!("Failed to find SMBIOS"); - } - let mut smbios = smbios.into_iter().flatten(); - let platform = smbios.find_map(|undefined_struct| { - if let DefinedStruct::SystemInformation(data) = undefined_struct.defined_struct() { - if let Some(product_name) = dmidecode_string_val(&data.product_name()) { - match product_name.as_str() { - "Laptop" => return Some(Platform::IntelGen11), - "Laptop (12th Gen Intel Core)" => return Some(Platform::IntelGen12), - "Laptop (13th Gen Intel Core)" => return Some(Platform::IntelGen13), - "Laptop 13 (AMD Ryzen 7040Series)" => return Some(Platform::Framework13Amd), - "Laptop 13 (AMD Ryzen 7040 Series)" => return Some(Platform::Framework13Amd), - "Laptop 13 (Intel Core Ultra Series 1)" => { - return Some(Platform::IntelCoreUltra1) - } - "Laptop 16 (AMD Ryzen 7040 Series)" => return Some(Platform::Framework16), - _ => {} - } - } - if let Some(family) = dmidecode_string_val(&data.family()) { - // Actually "Laptop", "13in Laptop", and "16in Laptop" - match family.as_str() { - // TGL Mainboard (I don't this ever appears in family) - "FRANBMCP" => return Some(Platform::IntelGen11), - // ADL Mainboard (I don't this ever appears in family) - "FRANMACP" => return Some(Platform::IntelGen12), - // RPL Mainboard (I don't this ever appears in family) - "FRANMCCP" => return Some(Platform::IntelGen13), - // Framework 13 AMD Mainboard - "FRANMDCP" => return Some(Platform::Framework13Amd), - // Framework 16 Mainboard - "FRANMZCP" => return Some(Platform::Framework16), - _ => {} - } - } - } - None - }); + let product_name = get_product_name()?; + + let platform = match product_name.as_str() { + "Laptop" => Some(Platform::IntelGen11), + "Laptop (12th Gen Intel Core)" => Some(Platform::IntelGen12), + "Laptop (13th Gen Intel Core)" => Some(Platform::IntelGen13), + "Laptop 13 (AMD Ryzen 7040Series)" => Some(Platform::Framework13Amd), + "Laptop 13 (AMD Ryzen 7040 Series)" => Some(Platform::Framework13Amd), + "Laptop 13 (Intel Core Ultra Series 1)" => Some(Platform::IntelCoreUltra1), + "Laptop 16 (AMD Ryzen 7040 Series)" => Some(Platform::Framework16), + _ => None, + }; if platform.is_none() { println!("Failed to find PlatformFamily"); @@ -169,3 +269,30 @@ pub fn get_platform() -> Option { *cached_platform = Some(platform); platform } + +#[cfg(target_os = "freebsd")] +fn kenv_get(name: &str) -> nix::Result { + use libc::{c_int, KENV_GET, KENV_MVALLEN}; + use nix::errno::Errno; + use std::ffi::{CStr, CString}; + + let cname = CString::new(name).unwrap(); + let name_ptr = cname.as_ptr(); + + let mut value_buf = [0; 1 + KENV_MVALLEN as usize]; + + unsafe { + let res: c_int = libc::kenv( + KENV_GET, + name_ptr, + value_buf.as_mut_ptr(), + value_buf.len() as c_int, + ); + Errno::result(res)?; + + let cvalue = CStr::from_ptr(value_buf.as_ptr()); + let value = cvalue.to_string_lossy().into_owned(); + + Ok(value) + } +} diff --git a/framework_lib/src/util.rs b/framework_lib/src/util.rs index d2a4f2ca..8bcdb5c1 100644 --- a/framework_lib/src/util.rs +++ b/framework_lib/src/util.rs @@ -104,8 +104,8 @@ pub unsafe fn any_as_mut_u8_slice(p: &mut T) -> &mut [u8] { /// Convert an array/slice of any type to a u8 slice (Like a C byte buffer) pub unsafe fn any_vec_as_u8_slice(p: &[T]) -> &[u8] { - let len = ::std::mem::size_of::() * p.len(); - ::std::slice::from_raw_parts((p.as_ptr() as *const T) as *const u8, len) + let len = ::std::mem::size_of_val(p); + ::std::slice::from_raw_parts(p.as_ptr() as *const u8, len) } /// Print a byte buffer as a series of hex bytes diff --git a/framework_tool/Cargo.toml b/framework_tool/Cargo.toml index 7ebb7270..a689eba1 100644 --- a/framework_tool/Cargo.toml +++ b/framework_tool/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [features] default = ["linux"] linux = ["framework_lib/linux"] +freebsd = ["framework_lib/freebsd"] windows = ["framework_lib/windows"] [dependencies.framework_lib]