1use std::fmt::Debug;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7use crate::cpu_config::templates::{CpuTemplateType, CustomCpuTemplate, StaticCpuTemplate};
8
9pub const DEFAULT_MEM_SIZE_MIB: usize = 128;
11pub const MAX_SUPPORTED_VCPUS: u8 = 32;
14
15#[rustfmt::skip]
17#[derive(Debug, thiserror::Error, displaydoc::Display, PartialEq, Eq)]
18pub enum MachineConfigError {
19 IncompatibleBalloonSize,
21 InvalidMemorySize,
23 InvalidVcpuCount,
25 InvalidVmState,
27 #[cfg(target_arch = "aarch64")]
29 SmtNotSupported,
30 KernelVersion,
32}
33
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
36pub enum HugePageConfig {
37 #[default]
39 None,
40 #[serde(rename = "2M")]
42 Hugetlbfs2M,
43}
44
45impl HugePageConfig {
46 fn is_valid_mem_size(&self, mem_size_mib: usize) -> bool {
49 let divisor = match self {
50 HugePageConfig::None => 1,
52 HugePageConfig::Hugetlbfs2M => 2,
53 };
54
55 mem_size_mib % divisor == 0
56 }
57
58 pub fn mmap_flags(&self) -> libc::c_int {
61 match self {
62 HugePageConfig::None => 0,
63 HugePageConfig::Hugetlbfs2M => libc::MAP_HUGETLB | libc::MAP_HUGE_2MB,
64 }
65 }
66
67 pub fn is_hugetlbfs(&self) -> bool {
69 matches!(self, HugePageConfig::Hugetlbfs2M)
70 }
71
72 pub fn page_size(&self) -> usize {
74 match self {
75 HugePageConfig::None => 4096,
76 HugePageConfig::Hugetlbfs2M => 2 * 1024 * 1024,
77 }
78 }
79}
80
81impl From<HugePageConfig> for Option<memfd::HugetlbSize> {
82 fn from(value: HugePageConfig) -> Self {
83 match value {
84 HugePageConfig::None => None,
85 HugePageConfig::Hugetlbfs2M => Some(memfd::HugetlbSize::Huge2MB),
86 }
87 }
88}
89
90#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
92#[serde(deny_unknown_fields)]
93pub struct MachineConfig {
94 pub vcpu_count: u8,
96 pub mem_size_mib: usize,
98 #[serde(default)]
100 pub smt: bool,
101 #[serde(
104 default,
105 skip_serializing_if = "is_none_or_custom_template",
106 deserialize_with = "deserialize_static_template",
107 serialize_with = "serialize_static_template"
108 )]
109 pub cpu_template: Option<CpuTemplateType>,
110 #[serde(default)]
112 pub track_dirty_pages: bool,
113 #[serde(default)]
115 pub huge_pages: HugePageConfig,
116 #[serde(default)]
118 pub enable_nested_virt: bool,
119 #[cfg(feature = "gdb")]
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub gdb_socket_path: Option<String>,
123}
124
125fn is_none_or_custom_template(template: &Option<CpuTemplateType>) -> bool {
126 matches!(template, None | Some(CpuTemplateType::Custom(_)))
127}
128
129fn deserialize_static_template<'de, D>(deserializer: D) -> Result<Option<CpuTemplateType>, D::Error>
130where
131 D: Deserializer<'de>,
132{
133 Option::<StaticCpuTemplate>::deserialize(deserializer)
134 .map(|maybe_template| maybe_template.map(CpuTemplateType::Static))
135}
136
137fn serialize_static_template<S>(
138 template: &Option<CpuTemplateType>,
139 serializer: S,
140) -> Result<S::Ok, S::Error>
141where
142 S: Serializer,
143{
144 let Some(CpuTemplateType::Static(template)) = template else {
145 unreachable!()
147 };
148
149 template.serialize(serializer)
150}
151
152impl Default for MachineConfig {
153 fn default() -> Self {
154 Self {
155 vcpu_count: 1,
156 mem_size_mib: DEFAULT_MEM_SIZE_MIB,
157 smt: false,
158 cpu_template: None,
159 track_dirty_pages: false,
160 huge_pages: HugePageConfig::None,
161 enable_nested_virt: false,
162 #[cfg(feature = "gdb")]
163 gdb_socket_path: None,
164 }
165 }
166}
167
168#[derive(Clone, Default, Debug, PartialEq, Eq, Deserialize)]
175#[serde(deny_unknown_fields)]
176pub struct MachineConfigUpdate {
177 #[serde(default)]
179 pub vcpu_count: Option<u8>,
180 #[serde(default)]
182 pub mem_size_mib: Option<usize>,
183 #[serde(default)]
185 pub smt: Option<bool>,
186 #[serde(default)]
188 pub cpu_template: Option<StaticCpuTemplate>,
189 #[serde(default)]
191 pub track_dirty_pages: Option<bool>,
192 #[serde(default)]
194 pub huge_pages: Option<HugePageConfig>,
195 #[serde(default)]
197 pub enable_nested_virt: Option<bool>,
198 #[cfg(feature = "gdb")]
200 #[serde(default)]
201 pub gdb_socket_path: Option<String>,
202}
203
204impl MachineConfigUpdate {
205 pub fn is_empty(&self) -> bool {
209 self == &Default::default()
210 }
211}
212
213impl From<MachineConfig> for MachineConfigUpdate {
214 fn from(cfg: MachineConfig) -> Self {
215 MachineConfigUpdate {
216 vcpu_count: Some(cfg.vcpu_count),
217 mem_size_mib: Some(cfg.mem_size_mib),
218 smt: Some(cfg.smt),
219 cpu_template: cfg.static_template(),
220 track_dirty_pages: Some(cfg.track_dirty_pages),
221 huge_pages: Some(cfg.huge_pages),
222 enable_nested_virt: Some(cfg.enable_nested_virt),
223 #[cfg(feature = "gdb")]
224 gdb_socket_path: cfg.gdb_socket_path,
225 }
226 }
227}
228
229impl MachineConfig {
230 pub fn set_custom_cpu_template(&mut self, cpu_template: CustomCpuTemplate) {
232 self.cpu_template = Some(CpuTemplateType::Custom(cpu_template));
233 }
234
235 fn static_template(&self) -> Option<StaticCpuTemplate> {
236 match self.cpu_template {
237 Some(CpuTemplateType::Static(template)) => Some(template),
238 _ => None,
239 }
240 }
241
242 pub fn update(
248 &self,
249 update: &MachineConfigUpdate,
250 ) -> Result<MachineConfig, MachineConfigError> {
251 let vcpu_count = update.vcpu_count.unwrap_or(self.vcpu_count);
252
253 let smt = update.smt.unwrap_or(self.smt);
254
255 #[cfg(target_arch = "aarch64")]
256 if smt {
257 return Err(MachineConfigError::SmtNotSupported);
258 }
259
260 if vcpu_count == 0 || vcpu_count > MAX_SUPPORTED_VCPUS {
261 return Err(MachineConfigError::InvalidVcpuCount);
262 }
263
264 if smt && vcpu_count > 1 && vcpu_count % 2 == 1 {
267 return Err(MachineConfigError::InvalidVcpuCount);
268 }
269
270 let mem_size_mib = update.mem_size_mib.unwrap_or(self.mem_size_mib);
271 let page_config = update.huge_pages.unwrap_or(self.huge_pages);
272
273 if mem_size_mib == 0 || !page_config.is_valid_mem_size(mem_size_mib) {
274 return Err(MachineConfigError::InvalidMemorySize);
275 }
276
277 let cpu_template = match update.cpu_template {
278 None => self.cpu_template.clone(),
279 Some(StaticCpuTemplate::None) => None,
280 Some(other) => Some(CpuTemplateType::Static(other)),
281 };
282
283 Ok(MachineConfig {
284 vcpu_count,
285 mem_size_mib,
286 smt,
287 cpu_template,
288 track_dirty_pages: update.track_dirty_pages.unwrap_or(self.track_dirty_pages),
289 huge_pages: page_config,
290 enable_nested_virt: update.enable_nested_virt.unwrap_or(self.enable_nested_virt),
291 #[cfg(feature = "gdb")]
292 gdb_socket_path: update.gdb_socket_path.clone(),
293 })
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use crate::cpu_config::templates::{CpuTemplateType, CustomCpuTemplate, StaticCpuTemplate};
300 use crate::vmm_config::machine_config::MachineConfig;
301
302 #[test]
308 fn test_serialize_machine_config() {
309 #[cfg(target_arch = "aarch64")]
310 const TEMPLATE: StaticCpuTemplate = StaticCpuTemplate::V1N1;
311 #[cfg(target_arch = "x86_64")]
312 const TEMPLATE: StaticCpuTemplate = StaticCpuTemplate::T2S;
313
314 let mconfig = MachineConfig {
315 cpu_template: None,
316 ..Default::default()
317 };
318
319 let serialized = serde_json::to_string(&mconfig).unwrap();
320 let deserialized = serde_json::from_str::<MachineConfig>(&serialized).unwrap();
321
322 assert!(deserialized.cpu_template.is_none());
323
324 let mconfig = MachineConfig {
325 cpu_template: Some(CpuTemplateType::Static(TEMPLATE)),
326 ..Default::default()
327 };
328
329 let serialized = serde_json::to_string(&mconfig).unwrap();
330 let deserialized = serde_json::from_str::<MachineConfig>(&serialized).unwrap();
331
332 assert_eq!(
333 deserialized.cpu_template,
334 Some(CpuTemplateType::Static(TEMPLATE))
335 );
336
337 let mconfig = MachineConfig {
338 cpu_template: Some(CpuTemplateType::Custom(CustomCpuTemplate::default())),
339 ..Default::default()
340 };
341
342 let serialized = serde_json::to_string(&mconfig).unwrap();
343 let deserialized = serde_json::from_str::<MachineConfig>(&serialized).unwrap();
344
345 assert!(deserialized.cpu_template.is_none());
346 }
347}