aya/maps/xdp/cpu_map.rs
1//! An array of available CPUs.
2
3use std::{
4 borrow::{Borrow, BorrowMut},
5 num::NonZeroU32,
6 os::fd::{AsFd, AsRawFd},
7};
8
9use aya_obj::generated::bpf_cpumap_val;
10
11use super::XdpMapError;
12use crate::{
13 maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError},
14 programs::ProgramFd,
15 sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError},
16 Pod, FEATURES,
17};
18
19/// An array of available CPUs.
20///
21/// XDP programs can use this map to redirect packets to a target
22/// CPU for processing.
23///
24/// # Minimum kernel version
25///
26/// The minimum kernel version required to use this feature is 4.15.
27///
28/// # Examples
29/// ```no_run
30/// # let elf_bytes = &[];
31/// use aya::maps::xdp::CpuMap;
32/// use aya::util::nr_cpus;
33///
34/// let nr_cpus = nr_cpus().unwrap() as u32;
35/// let mut bpf = aya::EbpfLoader::new()
36/// .set_max_entries("CPUS", nr_cpus)
37/// .load(elf_bytes)
38/// .unwrap();
39/// let mut cpumap = CpuMap::try_from(bpf.map_mut("CPUS").unwrap())?;
40/// let flags = 0;
41/// let queue_size = 2048;
42/// for i in 0..nr_cpus {
43/// cpumap.set(i, queue_size, None, flags);
44/// }
45///
46/// # Ok::<(), aya::EbpfError>(())
47/// ```
48///
49/// # See also
50///
51/// Kernel documentation: <https://docs.kernel.org/next/bpf/map_cpumap.html>
52#[doc(alias = "BPF_MAP_TYPE_CPUMAP")]
53pub struct CpuMap<T> {
54 pub(crate) inner: T,
55}
56
57impl<T: Borrow<MapData>> CpuMap<T> {
58 pub(crate) fn new(map: T) -> Result<Self, MapError> {
59 let data = map.borrow();
60
61 if FEATURES.cpumap_prog_id() {
62 check_kv_size::<u32, bpf_cpumap_val>(data)?;
63 } else {
64 check_kv_size::<u32, u32>(data)?;
65 }
66
67 Ok(Self { inner: map })
68 }
69
70 /// Returns the number of elements in the array.
71 ///
72 /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side.
73 pub fn len(&self) -> u32 {
74 self.inner.borrow().obj.max_entries()
75 }
76
77 /// Returns the queue size and optional program for a given CPU index.
78 ///
79 /// # Errors
80 ///
81 /// Returns [`MapError::OutOfBounds`] if `cpu_index` is out of bounds,
82 /// [`MapError::SyscallError`] if `bpf_map_lookup_elem` fails.
83 pub fn get(&self, cpu_index: u32, flags: u64) -> Result<CpuMapValue, MapError> {
84 let data = self.inner.borrow();
85 check_bounds(data, cpu_index)?;
86 let fd = data.fd().as_fd();
87
88 let value = if FEATURES.cpumap_prog_id() {
89 bpf_map_lookup_elem::<_, bpf_cpumap_val>(fd, &cpu_index, flags).map(|value| {
90 value.map(|value| CpuMapValue {
91 queue_size: value.qsize,
92 // SAFETY: map writes use fd, map reads use id.
93 // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6241
94 prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
95 })
96 })
97 } else {
98 bpf_map_lookup_elem::<_, u32>(fd, &cpu_index, flags).map(|value| {
99 value.map(|qsize| CpuMapValue {
100 queue_size: qsize,
101 prog_id: None,
102 })
103 })
104 };
105 value
106 .map_err(|(_, io_error)| SyscallError {
107 call: "bpf_map_lookup_elem",
108 io_error,
109 })?
110 .ok_or(MapError::KeyNotFound)
111 }
112
113 /// An iterator over the elements of the map.
114 pub fn iter(&self) -> impl Iterator<Item = Result<CpuMapValue, MapError>> + '_ {
115 (0..self.len()).map(move |i| self.get(i, 0))
116 }
117}
118
119impl<T: BorrowMut<MapData>> CpuMap<T> {
120 /// Sets the queue size at the given CPU index, and optionally a chained program.
121 ///
122 /// When sending the packet to the CPU at the given index, the kernel will queue up to
123 /// `queue_size` packets before dropping them.
124 ///
125 /// Starting from Linux kernel 5.9, another XDP program can be passed in that will be run on the
126 /// target CPU, instead of the CPU that receives the packets. This allows to perform minimal
127 /// computations on CPUs that directly handle packets from a NIC's RX queues, and perform
128 /// possibly heavier ones in other, less busy CPUs.
129 ///
130 /// The chained program must be loaded with the `BPF_XDP_CPUMAP` attach type. When using
131 /// `aya-ebpf`, that means XDP programs that specify the `map = "cpumap"` argument. See the
132 /// kernel-space `aya_ebpf::xdp` for more information.
133 ///
134 /// # Errors
135 ///
136 /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
137 /// if `bpf_map_update_elem` fails, [`XdpMapError::ChainedProgramNotSupported`] if the kernel
138 /// does not support chained programs and one is provided.
139 pub fn set(
140 &mut self,
141 cpu_index: u32,
142 queue_size: u32,
143 program: Option<&ProgramFd>,
144 flags: u64,
145 ) -> Result<(), XdpMapError> {
146 let data = self.inner.borrow_mut();
147 check_bounds(data, cpu_index)?;
148 let fd = data.fd().as_fd();
149
150 let res = if FEATURES.cpumap_prog_id() {
151 let mut value = unsafe { std::mem::zeroed::<bpf_cpumap_val>() };
152 value.qsize = queue_size;
153 // Default is valid as the kernel will only consider fd > 0:
154 // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/cpumap.c#L466
155 value.bpf_prog.fd = program
156 .map(|prog| prog.as_fd().as_raw_fd())
157 .unwrap_or_default();
158 bpf_map_update_elem(fd, Some(&cpu_index), &value, flags)
159 } else {
160 if program.is_some() {
161 return Err(XdpMapError::ChainedProgramNotSupported);
162 }
163 bpf_map_update_elem(fd, Some(&cpu_index), &queue_size, flags)
164 };
165
166 res.map_err(|(_, io_error)| {
167 MapError::from(SyscallError {
168 call: "bpf_map_update_elem",
169 io_error,
170 })
171 })?;
172 Ok(())
173 }
174}
175
176impl<T: Borrow<MapData>> IterableMap<u32, CpuMapValue> for CpuMap<T> {
177 fn map(&self) -> &MapData {
178 self.inner.borrow()
179 }
180
181 fn get(&self, key: &u32) -> Result<CpuMapValue, MapError> {
182 self.get(*key, 0)
183 }
184}
185
186unsafe impl Pod for bpf_cpumap_val {}
187
188#[derive(Clone, Copy, Debug)]
189/// The value of a CPU map.
190pub struct CpuMapValue {
191 /// Size of the for the CPU.
192 pub queue_size: u32,
193 /// Chained XDP program ID.
194 pub prog_id: Option<NonZeroU32>,
195}