1use crate::NR_CPU_IDS;
59use anyhow::Context;
60use anyhow::Result;
61use anyhow::bail;
62use bitvec::prelude::*;
63use sscanf::sscanf;
64use std::fmt;
65use std::ops::BitAndAssign;
66use std::ops::BitOrAssign;
67use std::ops::BitXorAssign;
68
69#[cfg(any(test, feature = "testutils"))]
70thread_local! {
71 static MASK_WIDTH_OVERRIDE: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
74}
75
76fn mask_width() -> usize {
78 #[cfg(any(test, feature = "testutils"))]
79 {
80 let ovr = MASK_WIDTH_OVERRIDE.with(|c| c.get());
81 if ovr > 0 {
82 return ovr;
83 }
84 }
85 *NR_CPU_IDS
86}
87
88#[cfg(any(test, feature = "testutils"))]
92pub fn set_cpumask_test_width(width: usize) {
93 MASK_WIDTH_OVERRIDE.with(|c| c.set(width));
94}
95
96#[derive(Debug, Eq, Clone, Hash, Ord, PartialEq, PartialOrd)]
97pub struct Cpumask {
98 mask: BitVec<u64, Lsb0>,
99}
100
101impl Cpumask {
102 fn check_cpu(&self, cpu: usize) -> Result<()> {
103 if cpu >= mask_width() {
104 bail!("Invalid CPU {} passed, max {}", cpu, mask_width());
105 }
106
107 Ok(())
108 }
109
110 #[allow(clippy::new_without_default)]
113 pub fn new() -> Cpumask {
114 Cpumask {
115 mask: bitvec![u64, Lsb0; 0; mask_width()],
116 }
117 }
118
119 #[allow(clippy::should_implement_trait)]
123 pub fn from_str(cpumask: &str) -> Result<Cpumask> {
124 match cpumask {
125 "none" => {
126 let mask = bitvec![u64, Lsb0; 0; mask_width()];
127 return Ok(Self { mask });
128 }
129 "all" => {
130 let mask = bitvec![u64, Lsb0; 1; mask_width()];
131 return Ok(Self { mask });
132 }
133 _ => {}
134 }
135 let hex_str = {
136 let mut tmp_str = cpumask
137 .strip_prefix("0x")
138 .unwrap_or(cpumask)
139 .replace('_', "");
140 if !tmp_str.len().is_multiple_of(2) {
141 tmp_str = "0".to_string() + &tmp_str;
142 }
143 tmp_str
144 };
145 let byte_vec =
146 hex::decode(&hex_str).with_context(|| format!("Failed to parse cpumask: {cpumask}"))?;
147
148 let mut mask = bitvec![u64, Lsb0; 0; mask_width()];
149 for (index, &val) in byte_vec.iter().rev().enumerate() {
150 let mut v = val;
151 while v != 0 {
152 let lsb = v.trailing_zeros() as usize;
153 v &= !(1 << lsb);
154 let cpu = index * 8 + lsb;
155 if cpu >= mask_width() {
156 bail!(
157 concat!(
158 "Found cpu ({}) in cpumask ({}) which is larger",
159 " than the number of cpus on the machine ({})"
160 ),
161 cpu,
162 cpumask,
163 mask_width()
164 );
165 }
166 mask.set(cpu, true);
167 }
168 }
169
170 Ok(Self { mask })
171 }
172
173 pub fn from_cpulist(cpulist: &str) -> Result<Cpumask> {
174 let mut mask = Cpumask::new();
175 for cpu_id in read_cpulist(cpulist)? {
176 let _ = mask.set_cpu(cpu_id);
177 }
178
179 Ok(mask)
180 }
181
182 pub fn to_cpulist(&self) -> String {
185 let cpus: Vec<usize> = self.iter().collect();
186 if cpus.is_empty() {
187 return String::from("none");
188 }
189
190 let mut ranges = Vec::new();
191 let mut start = cpus[0];
192 let mut end = cpus[0];
193
194 for &cpu in &cpus[1..] {
195 if cpu == end + 1 {
196 end = cpu;
197 } else {
198 ranges.push(if start == end {
199 format!("{}", start)
200 } else {
201 format!("{}-{}", start, end)
202 });
203 start = cpu;
204 end = cpu;
205 }
206 }
207
208 ranges.push(if start == end {
209 format!("{}", start)
210 } else {
211 format!("{}-{}", start, end)
212 });
213
214 ranges.join(",")
215 }
216
217 pub fn from_vec(vec: Vec<u64>) -> Self {
218 Self {
219 mask: BitVec::from_vec(vec),
220 }
221 }
222
223 pub fn from_bitvec(bitvec: BitVec<u64, Lsb0>) -> Self {
224 Self { mask: bitvec }
225 }
226
227 pub fn as_raw_slice(&self) -> &[u64] {
229 self.mask.as_raw_slice()
230 }
231
232 pub fn as_raw_bitvec_mut(&mut self) -> &mut BitVec<u64, Lsb0> {
234 &mut self.mask
235 }
236
237 pub fn as_raw_bitvec(&self) -> &BitVec<u64, Lsb0> {
239 &self.mask
240 }
241
242 pub fn set_all(&mut self) {
244 self.mask.fill(true);
245 }
246
247 pub fn clear_all(&mut self) {
249 self.mask.fill(false);
250 }
251
252 pub fn set_cpu(&mut self, cpu: usize) -> Result<()> {
255 self.check_cpu(cpu)?;
256 self.mask.set(cpu, true);
257 Ok(())
258 }
259
260 pub fn clear_cpu(&mut self, cpu: usize) -> Result<()> {
263 self.check_cpu(cpu)?;
264 self.mask.set(cpu, false);
265 Ok(())
266 }
267
268 pub fn test_cpu(&self, cpu: usize) -> bool {
271 match self.mask.get(cpu) {
272 Some(bit) => *bit,
273 None => false,
274 }
275 }
276
277 pub fn weight(&self) -> usize {
279 self.mask.count_ones()
280 }
281
282 pub fn is_empty(&self) -> bool {
284 self.mask.count_ones() == 0
285 }
286
287 pub fn is_full(&self) -> bool {
289 self.mask.count_ones() == mask_width()
290 }
291
292 pub fn len(&self) -> usize {
294 mask_width()
295 }
296
297 pub fn not(&self) -> Cpumask {
299 let mut new = self.clone();
300 new.mask = !new.mask;
301 new
302 }
303
304 pub fn and(&self, other: &Cpumask) -> Cpumask {
306 let mut new = self.clone();
307 new.mask &= other.mask.clone();
308 new
309 }
310
311 pub fn or(&self, other: &Cpumask) -> Cpumask {
313 let mut new = self.clone();
314 new.mask |= other.mask.clone();
315 new
316 }
317
318 pub fn xor(&self, other: &Cpumask) -> Cpumask {
320 let mut new = self.clone();
321 new.mask ^= other.mask.clone();
322 new
323 }
324
325 pub fn iter(&self) -> CpumaskIterator<'_> {
340 CpumaskIterator {
341 mask: self,
342 index: 0,
343 }
344 }
345
346 pub unsafe fn write_to_ptr(&self, bpfptr: *mut u64, len: usize) -> Result<()> {
354 let cpumask_slice = self.as_raw_slice();
355 if len != cpumask_slice.len() {
356 bail!(
357 "BPF CPU mask has length {} u64s, Cpumask size is {}",
358 len,
359 cpumask_slice.len()
360 );
361 }
362
363 let ptr = bpfptr as *mut [u64; 64];
364 let bpfmask: &mut [u64; 64] = unsafe { &mut *ptr };
365 let (left, _) = bpfmask.split_at_mut(cpumask_slice.len());
366 left.clone_from_slice(cpumask_slice);
367
368 Ok(())
369 }
370
371 fn fmt_with(&self, f: &mut fmt::Formatter<'_>, case: char) -> fmt::Result {
372 let mut masks: Vec<u32> = self
373 .as_raw_slice()
374 .iter()
375 .flat_map(|x| [*x as u32, (x >> 32) as u32])
376 .collect();
377
378 masks.truncate((mask_width()).div_ceil(32));
380
381 let width = match (mask_width()).div_ceil(4) % 8 {
383 0 => 8,
384 v => v,
385 };
386 match case {
387 'x' => write!(f, "{:0width$x}", masks.pop().unwrap(), width = width)?,
388 'X' => write!(f, "{:0width$X}", masks.pop().unwrap(), width = width)?,
389 _ => unreachable!(),
390 }
391
392 for submask in masks.iter().rev() {
394 match case {
395 'x' => write!(f, ",{submask:08x}")?,
396 'X' => write!(f, ",{submask:08X}")?,
397 _ => unreachable!(),
398 }
399 }
400 Ok(())
401 }
402}
403
404pub fn read_cpulist(cpulist: &str) -> Result<Vec<usize>> {
405 let cpulist = cpulist.trim_end_matches('\0');
406 let cpu_groups: Vec<&str> = cpulist.split(',').collect();
407 let mut cpu_ids = vec![];
408 for group in cpu_groups.iter() {
409 let (min, max) = match sscanf!(group.trim(), "{usize}-{usize}") {
410 Some((x, y)) => (x, y),
411 None => match sscanf!(group.trim(), "{usize}") {
412 Some(x) => (x, x),
413 None => {
414 bail!("Failed to parse cpulist {}", group.trim());
415 }
416 },
417 };
418 for i in min..(max + 1) {
419 cpu_ids.push(i);
420 }
421 }
422
423 Ok(cpu_ids)
424}
425
426pub struct CpumaskIterator<'a> {
427 mask: &'a Cpumask,
428 index: usize,
429}
430
431impl Iterator for CpumaskIterator<'_> {
432 type Item = usize;
433
434 fn next(&mut self) -> Option<Self::Item> {
435 while self.index < mask_width() {
436 let index = self.index;
437 self.index += 1;
438 let bit_val = self.mask.test_cpu(index);
439 if bit_val {
440 return Some(index);
441 }
442 }
443
444 None
445 }
446}
447
448impl fmt::Display for Cpumask {
449 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450 self.fmt_with(f, 'x')
451 }
452}
453
454impl fmt::LowerHex for Cpumask {
455 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456 self.fmt_with(f, 'x')
457 }
458}
459
460impl fmt::UpperHex for Cpumask {
461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462 self.fmt_with(f, 'X')
463 }
464}
465
466impl BitAndAssign<&Self> for Cpumask {
467 fn bitand_assign(&mut self, rhs: &Self) {
468 self.mask &= &rhs.mask;
469 }
470}
471
472impl BitOrAssign<&Self> for Cpumask {
473 fn bitor_assign(&mut self, rhs: &Self) {
474 self.mask |= &rhs.mask;
475 }
476}
477
478impl BitXorAssign<&Self> for Cpumask {
479 fn bitxor_assign(&mut self, rhs: &Self) {
480 self.mask ^= &rhs.mask;
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 #[test]
489 fn test_to_cpulist_empty() {
490 let mask = Cpumask::new();
491 assert_eq!(mask.to_cpulist(), "none");
492 }
493
494 #[test]
495 fn test_to_cpulist_single_cpu() {
496 set_cpumask_test_width(16);
497 let mut mask = Cpumask::new();
498 mask.set_cpu(5).unwrap();
499 assert_eq!(mask.to_cpulist(), "5");
500 }
501
502 #[test]
503 fn test_to_cpulist_contiguous_range() {
504 set_cpumask_test_width(16);
505 let mut mask = Cpumask::new();
506 for cpu in 0..8 {
507 mask.set_cpu(cpu).unwrap();
508 }
509 assert_eq!(mask.to_cpulist(), "0-7");
510 }
511
512 #[test]
513 fn test_to_cpulist_multiple_ranges() {
514 set_cpumask_test_width(16);
515 let mut mask = Cpumask::new();
516 for cpu in 0..4 {
517 mask.set_cpu(cpu).unwrap();
518 }
519 for cpu in 8..12 {
520 mask.set_cpu(cpu).unwrap();
521 }
522 assert_eq!(mask.to_cpulist(), "0-3,8-11");
523 }
524
525 #[test]
526 fn test_to_cpulist_scattered() {
527 set_cpumask_test_width(16);
528 let mut mask = Cpumask::new();
529 mask.set_cpu(1).unwrap();
530 mask.set_cpu(3).unwrap();
531 mask.set_cpu(5).unwrap();
532 assert_eq!(mask.to_cpulist(), "1,3,5");
533 }
534
535 #[test]
536 fn test_to_cpulist_mixed() {
537 set_cpumask_test_width(16);
538 let mut mask = Cpumask::new();
539 mask.set_cpu(0).unwrap();
540 mask.set_cpu(1).unwrap();
541 mask.set_cpu(2).unwrap();
542 mask.set_cpu(5).unwrap();
543 mask.set_cpu(10).unwrap();
544 mask.set_cpu(11).unwrap();
545 assert_eq!(mask.to_cpulist(), "0-2,5,10-11");
546 }
547
548 #[test]
549 fn test_to_cpulist_roundtrip() {
550 set_cpumask_test_width(32);
551 let original = "0-3,8-11,16";
552 let mask = Cpumask::from_cpulist(original).unwrap();
553 assert_eq!(mask.to_cpulist(), original);
554 }
555}