• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / alioth / 17114607742

21 Aug 2025 01:31AM UTC coverage: 10.411%. Remained the same
17114607742

Pull #273

github

web-flow
Merge 897d3fdbf into 7925c9625
Pull Request #273: feat: virtio packed queue

30 of 57 new or added lines in 9 files covered. (52.63%)

50 existing lines in 4 files now uncovered.

714 of 6858 relevant lines covered (10.41%)

16.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/alioth/src/virtio/dev/balloon.rs
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

15
use std::fmt::Debug;
16
use std::io::{IoSlice, IoSliceMut};
17
use std::sync::Arc;
18
use std::sync::mpsc::Receiver;
19
use std::thread::JoinHandle;
20

21
use alioth_macros::Layout;
22
use bitflags::bitflags;
23
use libc::{_SC_PAGESIZE, sysconf};
24
use mio::Registry;
25
use mio::event::Event;
26
use parking_lot::RwLock;
27
use serde::Deserialize;
28
use serde_aco::Help;
29
use zerocopy::{FromBytes, Immutable, IntoBytes};
30

31
use crate::hv::IoeventFd;
32
use crate::mem::emulated::{Action, Mmio};
33
use crate::mem::mapped::{Ram, RamBus};
34
use crate::virtio::dev::{DevParam, DeviceId, Virtio, WakeEvent};
35
use crate::virtio::queue::{Queue, VirtQueue};
36
use crate::virtio::worker::Waker;
37
use crate::virtio::worker::mio::{ActiveMio, Mio, VirtioMio};
38
use crate::virtio::{FEATURE_BUILT_IN, IrqSender, Result};
39
use crate::{c_enum, ffi, impl_mmio_for_zerocopy, mem};
40

41
#[repr(C, align(8))]
42
#[derive(Debug, Clone, Default, FromBytes, IntoBytes, Immutable, Layout)]
43
pub struct BalloonConfig {
44
    num_pages: u32,
45
    actual: u32,
46
    free_page_hint_cmd_id: u32,
47
    poison_val: u32,
48
}
49

50
impl_mmio_for_zerocopy!(BalloonConfig);
51

52
#[derive(Debug)]
53
pub struct BalloonConfigMmio {
54
    name: Arc<str>,
55
    config: RwLock<BalloonConfig>,
56
}
57

58
impl Mmio for BalloonConfigMmio {
59
    fn size(&self) -> u64 {
×
60
        size_of::<BalloonConfig>() as u64
×
61
    }
62

63
    fn read(&self, offset: u64, size: u8) -> mem::Result<u64> {
×
64
        let config = self.config.read();
×
65
        Mmio::read(&*config, offset, size)
×
66
    }
67

68
    fn write(&self, offset: u64, size: u8, val: u64) -> mem::Result<Action> {
×
69
        let config = &mut *self.config.write();
×
70
        match (offset as usize, size as usize) {
×
71
            BalloonConfig::LAYOUT_ACTUAL => {
72
                config.actual = val as u32;
×
73
                log::info!(
×
74
                    "{}: update: num_pages = {:#x}, actual = {val:#x}",
75
                    self.name,
76
                    config.num_pages,
77
                );
78
                Ok(Action::None)
×
79
            }
80
            _ => Mmio::write(config, offset, size, val),
×
81
        }
82
    }
83
}
84

85
bitflags! {
86
    #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
87
    pub struct BalloonFeature: u128 {
88
        const MUST_TELL_HOST = 1 << 0;
89
        const STATS_VQ = 1 << 1;
90
        const DEFLATE_ON_OOM = 1 << 2;
91
        const FREE_PAGE_HINT = 1 << 3;
92
        const PAGE_POISON = 1 << 4;
93
        const PAGE_REPORTING = 1 << 5;
94
    }
95
}
96

97
c_enum! {
98
    pub struct BalloonStats(u16);
99
    {
100
        SWAP_IN = 0;
101
        SWAP_OUT = 1;
102
        MAJFLT = 2;
103
        MINFLT = 3;
104
        MEMFREE = 4;
105
        MEMTOT = 5;
106
        AVAIL = 6;
107
        CACHES = 7;
108
        HTLB_PGALLOC = 8;
109
        HTLB_PGFAIL = 9;
110
    }
111
}
112

113
#[derive(Debug, Clone, Copy)]
114
enum BalloonQueue {
115
    Inflate,
116
    Deflate,
117
    Stats,
118
    FreePage,
119
    Reporting,
120
    NotExist,
121
}
122

123
#[derive(Debug)]
124
pub struct Balloon {
125
    name: Arc<str>,
126
    config: Arc<BalloonConfigMmio>,
127
    feature: BalloonFeature,
128
    queues: [BalloonQueue; 5],
129
}
130

131
impl Balloon {
132
    pub fn new(param: BalloonParam, name: impl Into<Arc<str>>) -> Result<Self> {
×
133
        if unsafe { sysconf(_SC_PAGESIZE) } != 1 << 12 {
×
134
            let err = std::io::ErrorKind::Unsupported;
×
135
            Err(std::io::Error::from(err))?;
×
136
        }
137
        let config = BalloonConfig {
138
            num_pages: 0,
139
            ..Default::default()
140
        };
141
        let mut feature = BalloonFeature::all();
×
142
        if !param.free_page_reporting {
×
143
            feature.remove(BalloonFeature::PAGE_REPORTING);
×
144
        };
145
        let name = name.into();
×
146
        Ok(Balloon {
×
147
            name: name.clone(),
×
148
            config: Arc::new(BalloonConfigMmio {
×
149
                config: RwLock::new(config),
×
150
                name,
×
151
            }),
152
            feature,
×
153
            queues: [BalloonQueue::NotExist; 5],
×
154
        })
155
    }
156

157
    fn inflate(&self, desc: &[IoSlice], ram: &Ram) {
×
158
        for buf in desc {
×
159
            for bytes in buf.chunks(size_of::<u32>()) {
×
160
                let Ok(page_num) = u32::read_from_bytes(bytes) else {
×
161
                    log::error!(
×
162
                        "{}: inflate: invalid page_num bytes: {bytes:02x?}",
163
                        self.name
164
                    );
165
                    continue;
166
                };
167
                let gpa = (page_num as u64) << 12;
×
168
                if let Err(e) = ram.madvise(gpa, 1 << 12, libc::MADV_DONTNEED) {
×
169
                    log::error!("{}: inflate at GPA {gpa:#x}: {e:?}", self.name);
×
170
                } else {
171
                    log::trace!("{}: freed GPA {gpa:#x}", self.name);
×
172
                }
173
            }
174
        }
175
    }
176

177
    fn free_reporting(&self, desc: &mut [IoSliceMut]) {
×
178
        for buf in desc.iter_mut() {
×
179
            let addr = buf.as_mut_ptr();
×
180
            let len = buf.len();
×
181
            let ret = ffi!(unsafe { libc::madvise(addr as _, len, libc::MADV_DONTNEED) });
×
182
            if let Err(e) = ret {
×
183
                log::error!("freeing pages: {addr:p} {len:#x}: {e:?}");
×
184
            } else {
185
                log::trace!("freed pages: {addr:p} {len:#x}");
×
186
            }
187
        }
188
    }
189
}
190

191
impl Virtio for Balloon {
192
    type Config = BalloonConfigMmio;
193
    type Feature = BalloonFeature;
194

195
    fn id(&self) -> DeviceId {
×
196
        DeviceId::Balloon
×
197
    }
198

199
    fn name(&self) -> &str {
×
200
        &self.name
×
201
    }
202

203
    fn spawn_worker<S, E>(
×
204
        self,
205
        event_rx: Receiver<WakeEvent<S, E>>,
206
        memory: Arc<RamBus>,
207
        queue_regs: Arc<[Queue]>,
208
    ) -> Result<(JoinHandle<()>, Arc<Waker>)>
209
    where
210
        S: IrqSender,
211
        E: IoeventFd,
212
    {
213
        Mio::spawn_worker(self, event_rx, memory, queue_regs)
×
214
    }
215

216
    fn num_queues(&self) -> u16 {
×
217
        self.queues.len() as u16
218
    }
219

220
    fn config(&self) -> Arc<BalloonConfigMmio> {
×
221
        self.config.clone()
×
222
    }
223

224
    fn feature(&self) -> u128 {
×
225
        FEATURE_BUILT_IN | self.feature.bits()
×
226
    }
227
}
228

229
impl VirtioMio for Balloon {
230
    fn activate<'a, 'm, Q, S, E>(
×
231
        &mut self,
232
        feature: u128,
233
        _active_mio: &mut ActiveMio<'a, 'm, Q, S, E>,
234
    ) -> Result<()>
235
    where
236
        Q: VirtQueue<'m>,
237
        S: IrqSender,
238
        E: IoeventFd,
239
    {
240
        let feature = BalloonFeature::from_bits_retain(feature);
×
241
        self.queues[0] = BalloonQueue::Inflate;
×
242
        self.queues[1] = BalloonQueue::Deflate;
×
243
        let mut index = 2;
×
244
        if feature.contains(BalloonFeature::STATS_VQ) {
×
245
            self.queues[index] = BalloonQueue::Stats;
×
246
            index += 1;
×
247
        }
248
        if feature.contains(BalloonFeature::FREE_PAGE_HINT) {
×
249
            self.queues[index] = BalloonQueue::FreePage;
×
250
            index += 1;
×
251
        }
252
        if feature.contains(BalloonFeature::PAGE_REPORTING) {
×
253
            self.queues[index] = BalloonQueue::Reporting;
×
254
        }
255
        Ok(())
×
256
    }
257

258
    fn handle_queue<'a, 'm, Q, S, E>(
×
259
        &mut self,
260
        index: u16,
261
        active_mio: &mut ActiveMio<'a, 'm, Q, S, E>,
262
    ) -> Result<()>
263
    where
264
        Q: VirtQueue<'m>,
265
        S: IrqSender,
266
        E: IoeventFd,
267
    {
268
        let Some(Some(queue)) = active_mio.queues.get_mut(index as usize) else {
×
269
            log::error!("{}: invalid queue index {index}", self.name);
×
270
            return Ok(());
×
271
        };
272
        let Some(&ballon_q) = self.queues.get(index as usize) else {
×
273
            log::error!("{}: invalid queue index {index}", self.name);
×
274
            return Ok(());
×
275
        };
276
        match ballon_q {
×
277
            BalloonQueue::Stats => {
×
278
                log::info!("{}: VQ_STATES available", self.name);
×
279
                return Ok(());
×
280
            }
281
            BalloonQueue::FreePage => {
×
282
                log::info!("{}: VQ_FREE_PAGE available", self.name);
×
283
                return Ok(());
×
284
            }
285
            _ => {}
×
286
        };
NEW
287
        queue.handle_desc(index, active_mio.irq_sender, |desc| {
×
288
            match ballon_q {
×
NEW
289
                BalloonQueue::Inflate => self.inflate(&desc.readable, active_mio.mem),
×
290
                BalloonQueue::Deflate => {
×
291
                    log::info!("{}: VQ_DEFLATE available", self.name);
×
292
                }
NEW
293
                BalloonQueue::Reporting => self.free_reporting(&mut desc.writable),
×
294
                BalloonQueue::Stats | BalloonQueue::FreePage => todo!(),
×
295
                BalloonQueue::NotExist => log::error!("{}: invalid queue index {index}", self.name),
×
296
            }
297
            Ok(Some(0))
×
298
        })
299
    }
300

301
    fn handle_event<'a, 'm, Q, S, E>(
×
302
        &mut self,
303
        _event: &Event,
304
        _active_mio: &mut ActiveMio<'a, 'm, Q, S, E>,
305
    ) -> Result<()>
306
    where
307
        Q: VirtQueue<'m>,
308
        S: IrqSender,
309
        E: IoeventFd,
310
    {
311
        Ok(())
×
312
    }
313

314
    fn reset(&mut self, _registry: &Registry) {
×
315
        self.queues = [BalloonQueue::NotExist; 5];
×
316
    }
317
}
318

319
#[derive(Debug, Clone, Deserialize, Help, Default)]
320
pub struct BalloonParam {
321
    /// Enable free page reporting. [default: false]
322
    #[serde(default)]
323
    pub free_page_reporting: bool,
324
}
325

326
impl DevParam for BalloonParam {
327
    type Device = Balloon;
328

329
    fn build(self, name: impl Into<Arc<str>>) -> Result<Self::Device> {
×
330
        Balloon::new(self, name)
×
331
    }
332
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc