1use std::collections::hash_map::Iter;
4use std::collections::{BTreeMap, HashMap};
5use std::marker::PhantomData;
6
7use serde::{
8 de, de::MapAccess, de::Visitor, Deserialize, Deserializer, Serialize,
9 Serializer,
10};
11
12use crate::MergedInterface;
13
14const ETHTOOL_FEATURE_CLI_ALIAS: [(&str, &str); 17] = [
15 ("rx", "rx-checksum"),
16 ("rx-checksumming", "rx-checksum"),
17 ("ufo", "tx-udp-fragmentation"),
18 ("gso", "tx-generic-segmentation"),
19 ("generic-segmentation-offload", "tx-generic-segmentation"),
20 ("gro", "rx-gro"),
21 ("generic-receive-offload", "rx-gro"),
22 ("lro", "rx-lro"),
23 ("large-receive-offload", "rx-lro"),
24 ("rxvlan", "rx-vlan-hw-parse"),
25 ("rx-vlan-offload", "rx-vlan-hw-parse"),
26 ("txvlan", "tx-vlan-hw-insert"),
27 ("tx-vlan-offload", "tx-vlan-hw-insert"),
28 ("ntuple", "rx-ntuple-filter"),
29 ("ntuple-filters", "rx-ntuple-filter"),
30 ("rxhash", "rx-hashing"),
31 ("receive-hashing", "rx-hashing"),
32];
33
34#[derive(Deserialize, Debug, Eq, PartialEq, Clone, Default)]
35#[serde(from = "HashMap<String, bool>")]
36#[non_exhaustive]
37pub struct EthtoolFeatureConfig {
38 data: HashMap<String, bool>,
39}
40
41impl EthtoolFeatureConfig {
42 pub fn remove(&mut self, key: &str) -> Option<bool> {
43 self.data.remove(key)
44 }
45
46 pub fn insert(&mut self, key: String, value: bool) -> Option<bool> {
47 self.data.insert(key, value)
48 }
49
50 pub fn get(&self, key: &str) -> Option<&bool> {
51 self.data.get(key)
52 }
53}
54
55impl<'a> IntoIterator for &'a EthtoolFeatureConfig {
56 type Item = (&'a String, &'a bool);
57 type IntoIter = Iter<'a, String, bool>;
58
59 fn into_iter(self) -> Self::IntoIter {
60 self.data.iter()
61 }
62}
63
64impl From<HashMap<String, bool>> for EthtoolFeatureConfig {
65 fn from(data: HashMap<String, bool>) -> Self {
66 Self { data }
67 }
68}
69
70impl From<EthtoolFeatureConfig> for HashMap<String, bool> {
71 fn from(c: EthtoolFeatureConfig) -> Self {
72 c.data
73 }
74}
75
76impl Serialize for EthtoolFeatureConfig {
77 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78 where
79 S: Serializer,
80 {
81 let ordered: BTreeMap<_, _> = self.data.iter().collect();
82 ordered.serialize(serializer)
83 }
84}
85
86#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
87#[non_exhaustive]
88#[serde(deny_unknown_fields)]
89pub struct EthtoolConfig {
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub pause: Option<EthtoolPauseConfig>,
123 #[serde(
124 skip_serializing_if = "Option::is_none",
125 default,
126 deserialize_with = "parse_ethtool_feature"
127 )]
128 pub feature: Option<EthtoolFeatureConfig>,
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub coalesce: Option<EthtoolCoalesceConfig>,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub ring: Option<EthtoolRingConfig>,
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub fec: Option<EthtoolFecConfig>,
140}
141
142impl EthtoolConfig {
143 pub fn new() -> Self {
144 Self::default()
145 }
146
147 pub(crate) fn apply_feature_alias(&mut self) {
149 if let Some(features) = self.feature.as_mut() {
150 for (cli_alias, kernel_name) in ETHTOOL_FEATURE_CLI_ALIAS {
151 if let Some(v) = features.remove(cli_alias) {
152 features.insert(kernel_name.to_string(), v);
153 }
154 }
155 }
156 }
157
158 pub(crate) fn sanitize(&mut self, is_desired: bool) {
159 if let Some(fec_conf) = self.fec.as_mut() {
160 fec_conf.sanitize(is_desired)
161 }
162 }
163}
164
165#[derive(
166 Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default, Copy,
167)]
168#[non_exhaustive]
169#[serde(deny_unknown_fields)]
170pub struct EthtoolPauseConfig {
171 #[serde(
172 skip_serializing_if = "Option::is_none",
173 default,
174 deserialize_with = "crate::deserializer::option_bool_or_string"
175 )]
176 pub rx: Option<bool>,
177 #[serde(
178 skip_serializing_if = "Option::is_none",
179 default,
180 deserialize_with = "crate::deserializer::option_bool_or_string"
181 )]
182 pub tx: Option<bool>,
183 #[serde(
184 skip_serializing_if = "Option::is_none",
185 default,
186 deserialize_with = "crate::deserializer::option_bool_or_string"
187 )]
188 pub autoneg: Option<bool>,
189}
190
191impl EthtoolPauseConfig {
192 pub fn new() -> Self {
193 Self::default()
194 }
195}
196
197#[derive(
198 Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default, Copy,
199)]
200#[serde(rename_all = "kebab-case", deny_unknown_fields)]
201#[non_exhaustive]
202pub struct EthtoolCoalesceConfig {
203 #[serde(
204 skip_serializing_if = "Option::is_none",
205 default,
206 deserialize_with = "crate::deserializer::option_bool_or_string"
207 )]
208 pub adaptive_rx: Option<bool>,
210 #[serde(
211 skip_serializing_if = "Option::is_none",
212 default,
213 deserialize_with = "crate::deserializer::option_bool_or_string"
214 )]
215 pub adaptive_tx: Option<bool>,
217 #[serde(
218 skip_serializing_if = "Option::is_none",
219 default,
220 deserialize_with = "crate::deserializer::option_u32_or_string"
221 )]
222 pub pkt_rate_high: Option<u32>,
224 #[serde(
225 skip_serializing_if = "Option::is_none",
226 default,
227 deserialize_with = "crate::deserializer::option_u32_or_string"
228 )]
229 pub pkt_rate_low: Option<u32>,
231 #[serde(
232 skip_serializing_if = "Option::is_none",
233 default,
234 deserialize_with = "crate::deserializer::option_u32_or_string"
235 )]
236 pub rx_frames: Option<u32>,
238 #[serde(
239 skip_serializing_if = "Option::is_none",
240 default,
241 deserialize_with = "crate::deserializer::option_u32_or_string"
242 )]
243 pub rx_frames_high: Option<u32>,
245 #[serde(
246 skip_serializing_if = "Option::is_none",
247 default,
248 deserialize_with = "crate::deserializer::option_u32_or_string"
249 )]
250 pub rx_frames_irq: Option<u32>,
252 #[serde(
253 skip_serializing_if = "Option::is_none",
254 default,
255 deserialize_with = "crate::deserializer::option_u32_or_string"
256 )]
257 pub rx_frames_low: Option<u32>,
259 #[serde(
260 skip_serializing_if = "Option::is_none",
261 default,
262 deserialize_with = "crate::deserializer::option_u32_or_string"
263 )]
264 pub rx_usecs: Option<u32>,
266 #[serde(
267 skip_serializing_if = "Option::is_none",
268 default,
269 deserialize_with = "crate::deserializer::option_u32_or_string"
270 )]
271 pub rx_usecs_high: Option<u32>,
273 #[serde(
274 skip_serializing_if = "Option::is_none",
275 default,
276 deserialize_with = "crate::deserializer::option_u32_or_string"
277 )]
278 pub rx_usecs_irq: Option<u32>,
280 #[serde(
281 skip_serializing_if = "Option::is_none",
282 default,
283 deserialize_with = "crate::deserializer::option_u32_or_string"
284 )]
285 pub rx_usecs_low: Option<u32>,
287 #[serde(
288 skip_serializing_if = "Option::is_none",
289 default,
290 deserialize_with = "crate::deserializer::option_u32_or_string"
291 )]
292 pub sample_interval: Option<u32>,
294 #[serde(
295 skip_serializing_if = "Option::is_none",
296 default,
297 deserialize_with = "crate::deserializer::option_u32_or_string"
298 )]
299 pub stats_block_usecs: Option<u32>,
301 #[serde(
302 skip_serializing_if = "Option::is_none",
303 default,
304 deserialize_with = "crate::deserializer::option_u32_or_string"
305 )]
306 pub tx_frames: Option<u32>,
308 #[serde(
309 skip_serializing_if = "Option::is_none",
310 default,
311 deserialize_with = "crate::deserializer::option_u32_or_string"
312 )]
313 pub tx_frames_high: Option<u32>,
315 #[serde(
316 skip_serializing_if = "Option::is_none",
317 default,
318 deserialize_with = "crate::deserializer::option_u32_or_string"
319 )]
320 pub tx_frames_irq: Option<u32>,
322 #[serde(
323 skip_serializing_if = "Option::is_none",
324 default,
325 deserialize_with = "crate::deserializer::option_u32_or_string"
326 )]
327 pub tx_frames_low: Option<u32>,
329 #[serde(
330 skip_serializing_if = "Option::is_none",
331 default,
332 deserialize_with = "crate::deserializer::option_u32_or_string"
333 )]
334 pub tx_usecs: Option<u32>,
336 #[serde(
337 skip_serializing_if = "Option::is_none",
338 default,
339 deserialize_with = "crate::deserializer::option_u32_or_string"
340 )]
341 pub tx_usecs_high: Option<u32>,
343 #[serde(
344 skip_serializing_if = "Option::is_none",
345 default,
346 deserialize_with = "crate::deserializer::option_u32_or_string"
347 )]
348 pub tx_usecs_irq: Option<u32>,
350 #[serde(
351 skip_serializing_if = "Option::is_none",
352 default,
353 deserialize_with = "crate::deserializer::option_u32_or_string"
354 )]
355 pub tx_usecs_low: Option<u32>,
357}
358
359impl EthtoolCoalesceConfig {
360 pub fn new() -> Self {
361 Self::default()
362 }
363}
364
365#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
366#[serde(rename_all = "kebab-case", deny_unknown_fields)]
367#[non_exhaustive]
368pub struct EthtoolRingConfig {
369 #[serde(
370 skip_serializing_if = "Option::is_none",
371 default,
372 deserialize_with = "crate::deserializer::option_u32_or_string"
373 )]
374 pub rx: Option<u32>,
375 #[serde(
376 skip_serializing_if = "Option::is_none",
377 default,
378 deserialize_with = "crate::deserializer::option_u32_or_string"
379 )]
380 pub rx_max: Option<u32>,
382 #[serde(
383 skip_serializing_if = "Option::is_none",
384 default,
385 deserialize_with = "crate::deserializer::option_u32_or_string"
386 )]
387 pub rx_jumbo: Option<u32>,
389 #[serde(
390 skip_serializing_if = "Option::is_none",
391 default,
392 deserialize_with = "crate::deserializer::option_u32_or_string"
393 )]
394 pub rx_jumbo_max: Option<u32>,
396 #[serde(
397 skip_serializing_if = "Option::is_none",
398 default,
399 deserialize_with = "crate::deserializer::option_u32_or_string"
400 )]
401 pub rx_mini: Option<u32>,
403 #[serde(
404 skip_serializing_if = "Option::is_none",
405 default,
406 deserialize_with = "crate::deserializer::option_u32_or_string"
407 )]
408 pub rx_mini_max: Option<u32>,
410 #[serde(
411 skip_serializing_if = "Option::is_none",
412 default,
413 deserialize_with = "crate::deserializer::option_u32_or_string"
414 )]
415 pub tx: Option<u32>,
416 #[serde(
417 skip_serializing_if = "Option::is_none",
418 default,
419 deserialize_with = "crate::deserializer::option_u32_or_string"
420 )]
421 pub tx_max: Option<u32>,
423}
424
425impl EthtoolRingConfig {
426 pub fn new() -> Self {
427 Self::default()
428 }
429}
430
431fn parse_ethtool_feature<'de, D>(
432 deserializer: D,
433) -> Result<Option<EthtoolFeatureConfig>, D::Error>
434where
435 D: Deserializer<'de>,
436{
437 struct FeatureVisitor(PhantomData<fn() -> Option<EthtoolFeatureConfig>>);
438
439 impl<'de> Visitor<'de> for FeatureVisitor {
440 type Value = Option<EthtoolFeatureConfig>;
441
442 fn expecting(
443 &self,
444 formatter: &mut std::fmt::Formatter,
445 ) -> std::fmt::Result {
446 formatter.write_str("Need to hash map of String:bool")
447 }
448
449 fn visit_map<M>(
450 self,
451 mut access: M,
452 ) -> Result<Option<EthtoolFeatureConfig>, M::Error>
453 where
454 M: MapAccess<'de>,
455 {
456 let mut ret =
457 HashMap::with_capacity(access.size_hint().unwrap_or(0));
458
459 while let Some((key, value)) =
460 access.next_entry::<String, serde_json::Value>()?
461 {
462 match value {
463 serde_json::Value::Bool(b) => {
464 ret.insert(key, b);
465 }
466 serde_json::Value::String(b)
467 if b.to_lowercase().as_str() == "false"
468 || b.as_str() == "0" =>
469 {
470 ret.insert(key, false);
471 }
472 serde_json::Value::String(b)
473 if b.to_lowercase().as_str() == "true"
474 || b.as_str() == "1" =>
475 {
476 ret.insert(key, true);
477 }
478 _ => {
479 return Err(de::Error::custom(
480 "Invalid feature value, should be boolean",
481 ));
482 }
483 }
484 }
485
486 Ok(Some(ret.into()))
487 }
488 }
489
490 deserializer.deserialize_any(FeatureVisitor(PhantomData))
491}
492
493impl MergedInterface {
494 pub(crate) fn post_inter_ifaces_process_ethtool(&mut self) {
495 if let Some(ethtool_conf) = self
496 .for_apply
497 .as_mut()
498 .map(|i| i.base_iface_mut())
499 .and_then(|b| b.ethtool.as_mut())
500 {
501 ethtool_conf.apply_feature_alias();
502 }
503 if let Some(ethtool_conf) = self
504 .for_verify
505 .as_mut()
506 .map(|i| i.base_iface_mut())
507 .and_then(|b| b.ethtool.as_mut())
508 {
509 ethtool_conf.apply_feature_alias();
510 }
511 }
512}
513
514#[derive(
515 Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default, Copy,
516)]
517#[non_exhaustive]
518#[serde(deny_unknown_fields)]
519pub struct EthtoolFecConfig {
520 #[serde(
522 skip_serializing_if = "Option::is_none",
523 default,
524 deserialize_with = "crate::deserializer::option_bool_or_string"
525 )]
526 pub auto: Option<bool>,
527 #[serde(skip_serializing_if = "Option::is_none")]
531 pub mode: Option<EthtoolFecMode>,
532}
533
534impl EthtoolFecConfig {
535 pub(crate) fn sanitize(&mut self, is_desired: bool) {
536 if is_desired && self.auto == Some(true) {
537 if let Some(mode) = self.mode.take() {
538 log::info!(
539 "Ignoring ethtool fec mode setting {mode} because auto \
540 enabled"
541 );
542 }
543 }
544 }
545}
546
547#[derive(
548 Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default, Copy,
549)]
550#[non_exhaustive]
551#[serde(rename_all = "kebab-case", deny_unknown_fields)]
552pub enum EthtoolFecMode {
553 #[default]
554 Off,
555 Rs,
556 Baser,
557 Llrs,
558}
559
560impl std::fmt::Display for EthtoolFecMode {
561 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562 write!(
563 f,
564 "{}",
565 match self {
566 Self::Off => "off",
567 Self::Rs => "rs",
568 Self::Baser => "baser",
569 Self::Llrs => "llrs",
570 }
571 )
572 }
573}