|
| 1 | +use std::io::Write; |
| 2 | + |
| 3 | +use anyhow::Context; |
| 4 | +use itertools::Itertools; |
| 5 | + |
1 | 6 | use super::setup::run_with_sudo; |
2 | 7 |
|
| 8 | +pub struct IrqIsolation { |
| 9 | + default_affinity: String, |
| 10 | + irq_affinity: Vec<(u32, String)>, |
| 11 | +} |
| 12 | + |
| 13 | +const DEFAULT_AFFINITY_PATH: &str = "/proc/irq/default_smp_affinity"; |
| 14 | + |
| 15 | +impl IrqIsolation { |
| 16 | + fn irq_smp_affinity_path(irq: u32) -> String { |
| 17 | + format!("/proc/irq/{irq}/smp_affinity") |
| 18 | + } |
| 19 | + |
| 20 | + /// Read from a virtual file and chomp away the trailing newline. |
| 21 | + fn read_vfile(path: &str) -> anyhow::Result<String> { |
| 22 | + let mut s = |
| 23 | + std::fs::read_to_string(path).with_context(|| format!("failed to read {path}"))?; |
| 24 | + if s.ends_with('\n') { |
| 25 | + s.pop(); |
| 26 | + } |
| 27 | + Ok(s) |
| 28 | + } |
| 29 | + |
| 30 | + /// Write to a virtual file within a single syscall. |
| 31 | + fn write_vfile(path: &str, content: &str) -> anyhow::Result<()> { |
| 32 | + std::fs::File::options() |
| 33 | + .write(true) |
| 34 | + .open(path) |
| 35 | + .map_err(Into::into) |
| 36 | + .and_then(|mut f| match f.write(content.as_bytes()) { |
| 37 | + Ok(n) if n == content.len() => Ok(()), |
| 38 | + Ok(n) => anyhow::bail!("partial write of {n} bytes"), |
| 39 | + Err(err) => Err(err.into()), |
| 40 | + }) |
| 41 | + .with_context(|| format!("failed to write {content:?} to {path}")) |
| 42 | + } |
| 43 | + |
| 44 | + pub fn new() -> anyhow::Result<Self> { |
| 45 | + let default_affinity = Self::read_vfile(DEFAULT_AFFINITY_PATH)?; |
| 46 | + |
| 47 | + let mut irq_affinity = Vec::new(); |
| 48 | + for ent in std::fs::read_dir("/proc/irq")?.filter_map(Result::ok) { |
| 49 | + if !ent.file_type()?.is_dir() { |
| 50 | + continue; |
| 51 | + } |
| 52 | + let Some(irq) = ent.file_name().to_str().and_then(|s| s.parse::<u32>().ok()) else { |
| 53 | + continue; |
| 54 | + }; |
| 55 | + |
| 56 | + irq_affinity.push((irq, Self::read_vfile(&Self::irq_smp_affinity_path(irq))?)); |
| 57 | + } |
| 58 | + irq_affinity.sort_unstable_by_key(|(irq, ..)| *irq); |
| 59 | + |
| 60 | + Ok(Self { |
| 61 | + default_affinity, |
| 62 | + irq_affinity, |
| 63 | + }) |
| 64 | + } |
| 65 | + |
| 66 | + pub fn isolate(&self, allowed_cpus: &[u32]) -> anyhow::Result<()> { |
| 67 | + let convert_mask = |mask: &str| { |
| 68 | + let mut masks = mask |
| 69 | + .split(',') |
| 70 | + .map(|seg| u32::from_str_radix(seg, 16)) |
| 71 | + .collect::<Result<Vec<_>, _>>()?; |
| 72 | + |
| 73 | + let mut changed = false; |
| 74 | + for &cpu in allowed_cpus { |
| 75 | + let (idx, bit) = (cpu as usize / 32, 1u32 << (cpu % 32)); |
| 76 | + if masks[idx] & bit != 0 { |
| 77 | + changed = true; |
| 78 | + masks[idx] ^= bit; |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + // Skip uneffected IRQs. |
| 83 | + if !changed { |
| 84 | + return Ok(None); |
| 85 | + } |
| 86 | + |
| 87 | + // Do not leave it empty, it would be invalid. CPU 0 must not be used by us. |
| 88 | + if masks.iter().all(|&m| m == 0) { |
| 89 | + masks[0] = 1; |
| 90 | + } |
| 91 | + |
| 92 | + let new_masks = masks |
| 93 | + .iter() |
| 94 | + .format_with(",", |&m, f| f(&format_args!("{m:x}"))) |
| 95 | + .to_string(); |
| 96 | + Ok(Some(new_masks)) |
| 97 | + }; |
| 98 | + |
| 99 | + if let Some(mask) = &self.default_affinity { |
| 100 | + // convert_mask(value)? |
| 101 | + // Self::write_vfile(DEFAULT_AFFINITY_PATH, )?; |
| 102 | + } |
| 103 | + |
| 104 | + for (irq, _, new_masks) in &self.irq_affinity {} |
| 105 | + } |
| 106 | +} |
| 107 | + |
3 | 108 | pub struct CpuIsolation { |
4 | 109 | nproc: u32, |
5 | 110 | } |
@@ -53,6 +158,10 @@ impl CpuIsolation { |
53 | 158 |
|
54 | 159 | Ok(()) |
55 | 160 | } |
| 161 | + |
| 162 | + pub fn isolate_irqs(&self) -> anyhow::Result<()> { |
| 163 | + Ok(()) |
| 164 | + } |
56 | 165 | } |
57 | 166 |
|
58 | 167 | impl Drop for CpuIsolation { |
|
0 commit comments