Alioth Code Coverage

balloon.rs0.00%

1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt::Debug;
16use std::io::{IoSlice, IoSliceMut};
17use std::sync::Arc;
18use std::sync::mpsc::Receiver;
19use std::thread::JoinHandle;
20
21use alioth_macros::Layout;
22use libc::{_SC_PAGESIZE, sysconf};
23use mio::Registry;
24use mio::event::Event;
25use parking_lot::RwLock;
26use serde::Deserialize;
27use serde_aco::Help;
28use zerocopy::{FromBytes, Immutable, IntoBytes};
29
30use crate::hv::IoeventFd;
31use crate::mem::emulated::{Action, Mmio};
32use crate::mem::mapped::{Ram, RamBus};
33use crate::sync::notifier::Notifier;
34use crate::virtio::dev::{DevParam, DeviceId, Virtio, WakeEvent};
35use crate::virtio::queue::{QueueReg, Status, VirtQueue};
36use crate::virtio::worker::mio::{ActiveMio, Mio, VirtioMio};
37use crate::virtio::{FEATURE_BUILT_IN, IrqSender, Result};
38use crate::{bitflags, consts, ffi, impl_mmio_for_zerocopy, mem};
39
40#[repr(C, align(8))]
41#[derive(Debug, Clone, Default, FromBytes, IntoBytes, Immutable, Layout)]
42pub struct BalloonConfig {
43 num_pages: u32,
44 actual: u32,
45 free_page_hint_cmd_id: u32,
46 poison_val: u32,
47}
48
49impl_mmio_for_zerocopy!(BalloonConfig);
50
51#[derive(Debug)]
52pub struct BalloonConfigMmio {
53 name: Arc<str>,
54 config: RwLock<BalloonConfig>,
55}
56
57impl Mmio for BalloonConfigMmio {
58 fn size(&self) -> u64 {
59 size_of::<BalloonConfig>() as u64
60 }
61
62 fn read(&self, offset: u64, size: u8) -> mem::Result<u64> {
63 let config = self.config.read();
64 Mmio::read(&*config, offset, size)
65 }
66
67 fn write(&self, offset: u64, size: u8, val: u64) -> mem::Result<Action> {
68 let config = &mut *self.config.write();
69 match (offset as usize, size as usize) {
70 BalloonConfig::LAYOUT_ACTUAL => {
71 config.actual = val as u32;
72 log::info!(
73 "{}: update: num_pages = {:#x}, actual = {val:#x}",
74 self.name,
75 config.num_pages,
76 );
77 Ok(Action::None)
78 }
79 _ => Mmio::write(config, offset, size, val),
80 }
81 }
82}
83
84bitflags! {
85 pub struct BalloonFeature(u128) {
86 MUST_TELL_HOST = 1 << 0;
87 STATS_VQ = 1 << 1;
88 DEFLATE_ON_OOM = 1 << 2;
89 FREE_PAGE_HINT = 1 << 3;
90 PAGE_POISON = 1 << 4;
91 PAGE_REPORTING = 1 << 5;
92 }
93}
94
95consts! {
96 pub struct BalloonStats(u16) {
97 SWAP_IN = 0;
98 SWAP_OUT = 1;
99 MAJFLT = 2;
100 MINFLT = 3;
101 MEMFREE = 4;
102 MEMTOT = 5;
103 AVAIL = 6;
104 CACHES = 7;
105 HTLB_PGALLOC = 8;
106 HTLB_PGFAIL = 9;
107 }
108}
109
110#[derive(Debug, Clone, Copy)]
111enum BalloonQueue {
112 Inflate,
113 Deflate,
114 Stats,
115 FreePage,
116 Reporting,
117 NotExist,
118}
119
120#[derive(Debug)]
121pub struct Balloon {
122 name: Arc<str>,
123 config: Arc<BalloonConfigMmio>,
124 feature: BalloonFeature,
125 queues: [BalloonQueue; 5],
126}
127
128impl Balloon {
129 pub fn new(param: BalloonParam, name: impl Into<Arc<str>>) -> Result<Self> {
130 if unsafe { sysconf(_SC_PAGESIZE) } != 1 << 12 {
131 let err = std::io::ErrorKind::Unsupported;
132 Err(std::io::Error::from(err))?;
133 }
134 let config = BalloonConfig {
135 num_pages: 0,
136 ..Default::default()
137 };
138 let mut feature = BalloonFeature::all();
139 if !param.free_page_reporting {
140 feature.remove(BalloonFeature::PAGE_REPORTING);
141 };
142 let name = name.into();
143 Ok(Balloon {
144 name: name.clone(),
145 config: Arc::new(BalloonConfigMmio {
146 config: RwLock::new(config),
147 name,
148 }),
149 feature,
150 queues: [BalloonQueue::NotExist; 5],
151 })
152 }
153
154 fn inflate(&self, desc: &[IoSlice], ram: &Ram) {
155 for buf in desc {
156 for bytes in buf.chunks(size_of::<u32>()) {
157 let Ok(page_num) = u32::read_from_bytes(bytes) else {
158 log::error!(
159 "{}: inflate: invalid page_num bytes: {bytes:02x?}",
160 self.name
161 );
162 continue;
163 };
164 let gpa = (page_num as u64) << 12;
165 if let Err(e) = ram.madvise(gpa, 1 << 12, libc::MADV_DONTNEED) {
166 log::error!("{}: inflate at GPA {gpa:#x}: {e:?}", self.name);
167 } else {
168 log::trace!("{}: freed GPA {gpa:#x}", self.name);
169 }
170 }
171 }
172 }
173
174 fn free_reporting(&self, desc: &mut [IoSliceMut]) {
175 for buf in desc.iter_mut() {
176 let addr = buf.as_mut_ptr();
177 let len = buf.len();
178 let ret = ffi!(unsafe { libc::madvise(addr as _, len, libc::MADV_DONTNEED) });
179 if let Err(e) = ret {
180 log::error!("freeing pages: {addr:p} {len:#x}: {e:?}");
181 } else {
182 log::trace!("freed pages: {addr:p} {len:#x}");
183 }
184 }
185 }
186}
187
188impl Virtio for Balloon {
189 type Config = BalloonConfigMmio;
190 type Feature = BalloonFeature;
191
192 fn id(&self) -> DeviceId {
193 DeviceId::BALLOON
194 }
195
196 fn name(&self) -> &str {
197 &self.name
198 }
199
200 fn spawn_worker<S, E>(
201 self,
202 event_rx: Receiver<WakeEvent<S, E>>,
203 memory: Arc<RamBus>,
204 queue_regs: Arc<[QueueReg]>,
205 ) -> Result<(JoinHandle<()>, Arc<Notifier>)>
206 where
207 S: IrqSender,
208 E: IoeventFd,
209 {
210 Mio::spawn_worker(self, event_rx, memory, queue_regs)
211 }
212
213 fn num_queues(&self) -> u16 {
214 self.queues.len() as u16
215 }
216
217 fn config(&self) -> Arc<BalloonConfigMmio> {
218 self.config.clone()
219 }
220
221 fn feature(&self) -> u128 {
222 FEATURE_BUILT_IN | self.feature.bits()
223 }
224}
225
226impl VirtioMio for Balloon {
227 fn activate<'m, Q, S, E>(
228 &mut self,
229 feature: u128,
230 _active_mio: &mut ActiveMio<'_, '_, 'm, Q, S, E>,
231 ) -> Result<()>
232 where
233 Q: VirtQueue<'m>,
234 S: IrqSender,
235 E: IoeventFd,
236 {
237 let feature = BalloonFeature::from_bits_retain(feature);
238 self.queues[0] = BalloonQueue::Inflate;
239 self.queues[1] = BalloonQueue::Deflate;
240 let mut index = 2;
241 if feature.contains(BalloonFeature::STATS_VQ) {
242 self.queues[index] = BalloonQueue::Stats;
243 index += 1;
244 }
245 if feature.contains(BalloonFeature::FREE_PAGE_HINT) {
246 self.queues[index] = BalloonQueue::FreePage;
247 index += 1;
248 }
249 if feature.contains(BalloonFeature::PAGE_REPORTING) {
250 self.queues[index] = BalloonQueue::Reporting;
251 }
252 Ok(())
253 }
254
255 fn handle_queue<'m, Q, S, E>(
256 &mut self,
257 index: u16,
258 active_mio: &mut ActiveMio<'_, '_, 'm, Q, S, E>,
259 ) -> Result<()>
260 where
261 Q: VirtQueue<'m>,
262 S: IrqSender,
263 E: IoeventFd,
264 {
265 let Some(Some(queue)) = active_mio.queues.get_mut(index as usize) else {
266 log::error!("{}: invalid queue index {index}", self.name);
267 return Ok(());
268 };
269 let Some(&ballon_q) = self.queues.get(index as usize) else {
270 log::error!("{}: invalid queue index {index}", self.name);
271 return Ok(());
272 };
273 match ballon_q {
274 BalloonQueue::Stats => {
275 log::info!("{}: VQ_STATES available", self.name);
276 return Ok(());
277 }
278 BalloonQueue::FreePage => {
279 log::info!("{}: VQ_FREE_PAGE available", self.name);
280 return Ok(());
281 }
282 _ => {}
283 };
284 queue.handle_desc(index, active_mio.irq_sender, |chain| {
285 match ballon_q {
286 BalloonQueue::Inflate => self.inflate(&chain.readable, active_mio.mem),
287 BalloonQueue::Deflate => {
288 log::info!("{}: VQ_DEFLATE available", self.name);
289 }
290 BalloonQueue::Reporting => self.free_reporting(&mut chain.writable),
291 BalloonQueue::Stats | BalloonQueue::FreePage => todo!(),
292 BalloonQueue::NotExist => log::error!("{}: invalid queue index {index}", self.name),
293 }
294 Ok(Status::Done { len: 0 })
295 })
296 }
297
298 fn handle_event<'a, 'm, Q, S, E>(
299 &mut self,
300 _event: &Event,
301 _active_mio: &mut ActiveMio<'_, '_, 'm, Q, S, E>,
302 ) -> Result<()>
303 where
304 Q: VirtQueue<'m>,
305 S: IrqSender,
306 E: IoeventFd,
307 {
308 Ok(())
309 }
310
311 fn reset(&mut self, _registry: &Registry) {
312 self.queues = [BalloonQueue::NotExist; 5];
313 }
314}
315
316#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Help)]
317pub struct BalloonParam {
318 /// Enable free page reporting. [default: false]
319 #[serde(default)]
320 pub free_page_reporting: bool,
321}
322
323impl DevParam for BalloonParam {
324 type Device = Balloon;
325
326 fn build(self, name: impl Into<Arc<str>>) -> Result<Self::Device> {
327 Balloon::new(self, name)
328 }
329}
330