diff --git a/examples/dump_eeprom_page.rs b/examples/dump_eeprom_page.rs new file mode 100644 index 0000000..98bed4d --- /dev/null +++ b/examples/dump_eeprom_page.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT + +use futures::stream::TryStreamExt; + +fn main() { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_io() + .build() + .unwrap(); + let iface_name = std::env::args().nth(1); + rt.block_on(get_eeprom(iface_name.as_deref())); +} + +async fn get_eeprom(iface_name: Option<&str>) { + let (connection, mut handle, _) = ethtool::new_connection().unwrap(); + tokio::spawn(connection); + + let mut eeprom_handle = handle + .eeprom() + .get(iface_name, 0, 1, 0, 0, 0x50) + .execute() + .await; + + let mut msgs = Vec::new(); + while let Some(msg) = eeprom_handle.try_next().await.unwrap() { + msgs.push(msg); + } + assert!(!msgs.is_empty()); + for msg in msgs { + println!("{:?}", msg); + } +} diff --git a/src/eeprom/attr.rs b/src/eeprom/attr.rs new file mode 100644 index 0000000..8b16492 --- /dev/null +++ b/src/eeprom/attr.rs @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT + +use anyhow::Context; +use byteorder::{ByteOrder, NativeEndian}; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED}, + DecodeError, Emitable, Parseable, +}; + +use crate::{EthtoolAttr, EthtoolHeader}; + + +const ETHTOOL_A_MODULE_EEPROM_HEADER: u16 = 1; +const ETHTOOL_A_MODULE_EEPROM_OFFSET: u16 = 2; +const ETHTOOL_A_MODULE_EEPROM_LENGTH: u16 = 3; +const ETHTOOL_A_MODULE_EEPROM_PAGE: u16 = 4; +const ETHTOOL_A_MODULE_EEPROM_BANK: u16 = 5; +const ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS: u16 = 6; +const ETHTOOL_A_MODULE_EEPROM_DATA: u16 = 7; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum EthtoolModuleEEPROMAttr { + Header(Vec), + Offset(u32), + Length(u32), + Page(u8), + Bank(u8), + I2CAddress(u8), + Data(Vec), + Other(DefaultNla), +} + +impl Nla for EthtoolModuleEEPROMAttr { + fn value_len(&self) -> usize { + match self { + Self::Header(hdrs) => hdrs.as_slice().buffer_len(), + Self::Data(data) => data.len(), + Self::Page(_) + | Self::Bank(_) + | Self::I2CAddress(_) => 1, + Self::Offset(_) + | Self::Length(_) => 4, + Self::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Header(_) => ETHTOOL_A_MODULE_EEPROM_HEADER | NLA_F_NESTED, + Self::Offset(_) => ETHTOOL_A_MODULE_EEPROM_OFFSET, + Self::Length(_) => ETHTOOL_A_MODULE_EEPROM_LENGTH, + Self::Page(_) => ETHTOOL_A_MODULE_EEPROM_PAGE, + Self::Bank(_) => ETHTOOL_A_MODULE_EEPROM_BANK, + Self::I2CAddress(_) => ETHTOOL_A_MODULE_EEPROM_I2C_ADDRESS, + Self::Data(_) => ETHTOOL_A_MODULE_EEPROM_DATA, + Self::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Header(ref nlas) => nlas.as_slice().emit(buffer), + Self::Data(d) => buffer.copy_from_slice(d.as_slice()), + Self::Page(d) + | Self::Bank(d) + | Self::I2CAddress(d) => buffer[0] = *d, + Self::Offset(d) + | Self::Length(d) => NativeEndian::write_u32(buffer, *d), + Self::Other(ref attr) => attr.emit(buffer), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for EthtoolModuleEEPROMAttr +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + ETHTOOL_A_MODULE_EEPROM_HEADER => { + let mut nlas = Vec::new(); + let error_msg = "failed to parse module eeprom header attributes"; + for nla in NlasIterator::new(payload) { + let nla = &nla.context(error_msg)?; + let parsed = + EthtoolHeader::parse(nla).context(error_msg)?; + nlas.push(parsed); + } + Self::Header(nlas) + } + ETHTOOL_A_MODULE_EEPROM_DATA => Self::Data( + Vec::from(payload), + ), + kind => Self::Other( + DefaultNla::parse(buf) + .context(format!("invalid ethtool module eeprom NLA kind {kind}"))?, + ), + }) + } +} + +pub(crate) fn parse_module_eeprom_nlas( + buffer: &[u8], +) -> Result, DecodeError> { + let mut nlas = Vec::new(); + for nla in NlasIterator::new(buffer) { + let error_msg = + format!("Failed to parse ethtool module eeprom message attribute {nla:?}"); + let nla = &nla.context(error_msg.clone())?; + let parsed = EthtoolModuleEEPROMAttr::parse(nla).context(error_msg)?; + nlas.push(EthtoolAttr::ModuleEEPROM(parsed)); + } + Ok(nlas) +} diff --git a/src/eeprom/get.rs b/src/eeprom/get.rs new file mode 100644 index 0000000..6a8336c --- /dev/null +++ b/src/eeprom/get.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT + +use futures::TryStream; +use netlink_packet_generic::GenlMessage; + +use crate::{ethtool_execute, EthtoolError, EthtoolHandle, EthtoolMessage}; + +pub struct EthtoolModuleEEPROMGetRequest { + handle: EthtoolHandle, + iface_name: Option, + offset: u32, + length: u32, + page: u8, + bank: u8, + i2c_address: u8, +} + +impl EthtoolModuleEEPROMGetRequest { + pub(crate) fn new(handle: EthtoolHandle, iface_name: Option<&str>, + offset: u32, + length: u32, + page: u8, + bank: u8, + i2c_address: u8 + ) -> Self { + EthtoolModuleEEPROMGetRequest { + handle, + iface_name: iface_name.map(|i| i.to_string()), + offset, + length, + page, + bank, + i2c_address + } + } + + pub async fn execute( + self, + ) -> impl TryStream, Error = EthtoolError> + { + let EthtoolModuleEEPROMGetRequest { + mut handle, + iface_name, + offset, + length, + page, + bank, + i2c_address + } = self; + + let ethtool_msg = EthtoolMessage::new_module_eeprom_get(iface_name.as_deref(), offset, length, page, bank, i2c_address); + ethtool_execute(&mut handle, iface_name.is_none(), ethtool_msg).await + } +} diff --git a/src/eeprom/handle.rs b/src/eeprom/handle.rs new file mode 100644 index 0000000..b96f79f --- /dev/null +++ b/src/eeprom/handle.rs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +use crate::{EthtoolHandle, EthtoolModuleEEPROMGetRequest}; + +pub struct EthtoolModuleEEPROMHandle(EthtoolHandle); + +impl EthtoolModuleEEPROMHandle { + pub fn new(handle: EthtoolHandle) -> Self { + EthtoolModuleEEPROMHandle(handle) + } + + /// Retrieve the module eeprom data pages of a interface (used by `ethtool -m + /// eth1`) + pub fn get(&mut self, iface_name: Option<&str>, + offset: u32, + length: u32, + page: u8, + bank: u8, + i2c_address: u8 + ) -> EthtoolModuleEEPROMGetRequest { + EthtoolModuleEEPROMGetRequest::new(self.0.clone(), iface_name, offset, length, page, bank, i2c_address) + } +} diff --git a/src/eeprom/mod.rs b/src/eeprom/mod.rs new file mode 100644 index 0000000..5d6744c --- /dev/null +++ b/src/eeprom/mod.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +mod attr; +mod get; +mod handle; + +pub(crate) use attr::parse_module_eeprom_nlas; + +pub use attr::EthtoolModuleEEPROMAttr; +pub use get::EthtoolModuleEEPROMGetRequest; +pub use handle::EthtoolModuleEEPROMHandle; diff --git a/src/handle.rs b/src/handle.rs index 8fa8a78..8b49085 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -9,9 +9,9 @@ use netlink_packet_generic::GenlMessage; use netlink_packet_utils::DecodeError; use crate::{ - try_ethtool, EthtoolChannelHandle, EthtoolCoalesceHandle, EthtoolError, - EthtoolFeatureHandle, EthtoolFecHandle, EthtoolLinkModeHandle, - EthtoolMessage, EthtoolPauseHandle, EthtoolRingHandle, EthtoolTsInfoHandle, + try_ethtool, EthtoolChannelHandle, EthtoolCoalesceHandle, EthtoolError, EthtoolFeatureHandle, + EthtoolFecHandle, EthtoolLinkModeHandle, EthtoolMessage, EthtoolPauseHandle, EthtoolRingHandle, + EthtoolTsInfoHandle, EthtoolModuleEEPROMHandle }; #[derive(Clone, Debug)] @@ -56,6 +56,10 @@ impl EthtoolHandle { EthtoolChannelHandle::new(self.clone()) } + pub fn eeprom(&mut self) -> EthtoolModuleEEPROMHandle { + EthtoolModuleEEPROMHandle::new(self.clone()) + } + pub async fn request( &mut self, message: NetlinkMessage>, diff --git a/src/lib.rs b/src/lib.rs index 87c9469..eff2044 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ mod bitset_util; mod channel; mod coalesce; mod connection; +mod eeprom; mod error; mod feature; mod fec; @@ -30,6 +31,7 @@ pub use coalesce::{ #[cfg(feature = "tokio_socket")] pub use connection::new_connection; pub use connection::new_connection_with_socket; +pub use eeprom::{EthtoolModuleEEPROMAttr, EthtoolModuleEEPROMGetRequest, EthtoolModuleEEPROMHandle}; pub use error::EthtoolError; pub use feature::{ EthtoolFeatureAttr, EthtoolFeatureBit, EthtoolFeatureGetRequest, diff --git a/src/message.rs b/src/message.rs index 66a79a1..b87e0f0 100644 --- a/src/message.rs +++ b/src/message.rs @@ -10,6 +10,7 @@ use crate::{ coalesce::{parse_coalesce_nlas, EthtoolCoalesceAttr}, feature::{parse_feature_nlas, EthtoolFeatureAttr}, fec::{parse_fec_nlas, EthtoolFecAttr}, + eeprom::{parse_module_eeprom_nlas, EthtoolModuleEEPROMAttr}, link_mode::{parse_link_mode_nlas, EthtoolLinkModeAttr}, pause::{parse_pause_nlas, EthtoolPauseAttr}, ring::{parse_ring_nlas, EthtoolRingAttr}, @@ -34,6 +35,8 @@ const ETHTOOL_MSG_FEC_GET_REPLY: u8 = 30; const ETHTOOL_MSG_CHANNELS_GET: u8 = 17; const ETHTOOL_MSG_CHANNELS_GET_REPLY: u8 = 18; const ETHTOOL_MSG_CHANNELS_SET: u8 = 18; +const ETHTOOL_MSG_MODULE_EEPROM_GET: u8 = 31; +const ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY: u8 = 32; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum EthtoolCmd { @@ -54,6 +57,8 @@ pub enum EthtoolCmd { ChannelGet, ChannelGetReply, ChannelSet, + ModuleEEPROMGet, + ModuleEEPROMGetReply, } impl From for u8 { @@ -76,6 +81,8 @@ impl From for u8 { EthtoolCmd::ChannelGet => ETHTOOL_MSG_CHANNELS_GET, EthtoolCmd::ChannelGetReply => ETHTOOL_MSG_CHANNELS_GET_REPLY, EthtoolCmd::ChannelSet => ETHTOOL_MSG_CHANNELS_SET, + EthtoolCmd::ModuleEEPROMGet => ETHTOOL_MSG_MODULE_EEPROM_GET, + EthtoolCmd::ModuleEEPROMGetReply => ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY, } } } @@ -90,6 +97,7 @@ pub enum EthtoolAttr { TsInfo(EthtoolTsInfoAttr), Fec(EthtoolFecAttr), Channel(EthtoolChannelAttr), + ModuleEEPROM(EthtoolModuleEEPROMAttr), } impl Nla for EthtoolAttr { @@ -103,6 +111,7 @@ impl Nla for EthtoolAttr { Self::TsInfo(attr) => attr.value_len(), Self::Fec(attr) => attr.value_len(), Self::Channel(attr) => attr.value_len(), + Self::ModuleEEPROM(attr) => attr.value_len(), } } @@ -116,6 +125,7 @@ impl Nla for EthtoolAttr { Self::TsInfo(attr) => attr.kind(), Self::Fec(attr) => attr.kind(), Self::Channel(attr) => attr.kind(), + Self::ModuleEEPROM(attr) => attr.kind(), } } @@ -129,6 +139,7 @@ impl Nla for EthtoolAttr { Self::TsInfo(attr) => attr.emit_value(buffer), Self::Fec(attr) => attr.emit_value(buffer), Self::Channel(attr) => attr.emit_value(buffer), + Self::ModuleEEPROM(attr) => attr.emit_value(buffer), } } } @@ -289,11 +300,41 @@ impl EthtoolMessage { vec![EthtoolAttr::Channel(EthtoolChannelAttr::Header(vec![ EthtoolHeader::DevName(iface_name.to_string()), ]))]; + EthtoolMessage { cmd: EthtoolCmd::ChannelSet, nlas, } } + + + pub fn new_module_eeprom_get( + iface_name: Option<&str>, + offset: u32, + length: u32, + page:u8, + bank:u8, + i2c_address:u8) -> Self { + let mut nlas = match iface_name { + Some(s) => { + vec![EthtoolAttr::ModuleEEPROM(EthtoolModuleEEPROMAttr::Header(vec![ + EthtoolHeader::DevName(s.to_string()), + ]))] + } + None => { + vec![EthtoolAttr::ModuleEEPROM(EthtoolModuleEEPROMAttr::Header(vec![]))] + } + }; + nlas.push(EthtoolAttr::ModuleEEPROM(EthtoolModuleEEPROMAttr::Offset(offset))); + nlas.push(EthtoolAttr::ModuleEEPROM(EthtoolModuleEEPROMAttr::Length(length))); + nlas.push(EthtoolAttr::ModuleEEPROM(EthtoolModuleEEPROMAttr::Page(page))); + nlas.push(EthtoolAttr::ModuleEEPROM(EthtoolModuleEEPROMAttr::Bank(bank))); + nlas.push(EthtoolAttr::ModuleEEPROM(EthtoolModuleEEPROMAttr::I2CAddress(i2c_address))); + EthtoolMessage { + cmd: EthtoolCmd::ModuleEEPROMGet, + nlas, + } + } } impl Emitable for EthtoolMessage { @@ -344,6 +385,10 @@ impl ParseableParametrized<[u8], GenlHeader> for EthtoolMessage { cmd: EthtoolCmd::ChannelGetReply, nlas: parse_channel_nlas(buffer)?, }, + ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY => Self { + cmd: EthtoolCmd::ModuleEEPROMGetReply, + nlas: parse_module_eeprom_nlas(buffer)?, + }, cmd => { return Err(DecodeError::from(format!( "Unsupported ethtool reply command: {cmd}"