squib_api/schemas/
hotplug_memory.rs1use serde::{Deserialize, Serialize};
9
10pub const DEFAULT_BLOCK_SIZE_MIB: u64 = 2;
12
13pub const DEFAULT_SLOT_SIZE_MIB: u64 = 128;
15
16pub const MAX_TOTAL_SIZE_MIB: u64 = 1_048_576; #[derive(Debug, Clone, Deserialize)]
22#[serde(deny_unknown_fields)]
23pub struct RawHotplugMemoryConfig {
24 pub total_size_mib: u64,
26 #[serde(default = "default_block_size")]
28 pub block_size_mib: u64,
29 #[serde(default = "default_slot_size")]
31 pub slot_size_mib: u64,
32}
33
34fn default_block_size() -> u64 {
35 DEFAULT_BLOCK_SIZE_MIB
36}
37
38fn default_slot_size() -> u64 {
39 DEFAULT_SLOT_SIZE_MIB
40}
41
42#[derive(Debug, Clone, Serialize)]
44#[non_exhaustive]
45pub struct HotplugMemoryConfig {
46 pub total_size_mib: u64,
48 pub block_size_mib: u64,
50 pub slot_size_mib: u64,
52}
53
54impl TryFrom<RawHotplugMemoryConfig> for HotplugMemoryConfig {
55 type Error = String;
56
57 fn try_from(raw: RawHotplugMemoryConfig) -> Result<Self, Self::Error> {
58 if raw.total_size_mib == 0 || raw.total_size_mib > MAX_TOTAL_SIZE_MIB {
59 return Err(format!(
60 "Invalid total_size_mib: must be 1..={MAX_TOTAL_SIZE_MIB}"
61 ));
62 }
63 if raw.block_size_mib == 0 {
64 return Err("Invalid block_size_mib: must be >= 1".into());
65 }
66 if raw.slot_size_mib == 0 {
67 return Err("Invalid slot_size_mib: must be >= 1".into());
68 }
69 if raw.slot_size_mib < raw.block_size_mib {
70 return Err("Invalid hotplug-memory: slot_size_mib must be >= block_size_mib".into());
71 }
72 if !raw.total_size_mib.is_multiple_of(raw.slot_size_mib) {
73 return Err("Invalid total_size_mib: must be a multiple of slot_size_mib".into());
74 }
75 Ok(Self {
76 total_size_mib: raw.total_size_mib,
77 block_size_mib: raw.block_size_mib,
78 slot_size_mib: raw.slot_size_mib,
79 })
80 }
81}
82
83#[derive(Debug, Clone, Deserialize)]
85#[serde(deny_unknown_fields)]
86pub struct RawHotplugMemoryUpdate {
87 pub requested_size_mib: u64,
89}
90
91#[derive(Debug, Clone, Serialize)]
93#[non_exhaustive]
94pub struct HotplugMemoryUpdate {
95 pub requested_size_mib: u64,
97}
98
99impl TryFrom<RawHotplugMemoryUpdate> for HotplugMemoryUpdate {
100 type Error = String;
101
102 fn try_from(raw: RawHotplugMemoryUpdate) -> Result<Self, Self::Error> {
103 if raw.requested_size_mib > MAX_TOTAL_SIZE_MIB {
104 return Err(format!(
105 "Invalid requested_size_mib: must be 0..={MAX_TOTAL_SIZE_MIB}"
106 ));
107 }
108 Ok(Self {
109 requested_size_mib: raw.requested_size_mib,
110 })
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_should_accept_minimal_hotplug_memory_config() {
120 let cfg = HotplugMemoryConfig::try_from(RawHotplugMemoryConfig {
121 total_size_mib: 256,
122 block_size_mib: DEFAULT_BLOCK_SIZE_MIB,
123 slot_size_mib: DEFAULT_SLOT_SIZE_MIB,
124 })
125 .unwrap();
126 assert_eq!(cfg.total_size_mib, 256);
127 }
128
129 #[test]
130 fn test_should_reject_unaligned_total_size() {
131 assert!(
132 HotplugMemoryConfig::try_from(RawHotplugMemoryConfig {
133 total_size_mib: 100,
134 block_size_mib: 2,
135 slot_size_mib: 128,
136 })
137 .is_err()
138 );
139 }
140
141 #[test]
142 fn test_should_reject_slot_smaller_than_block() {
143 assert!(
144 HotplugMemoryConfig::try_from(RawHotplugMemoryConfig {
145 total_size_mib: 256,
146 block_size_mib: 64,
147 slot_size_mib: 32,
148 })
149 .is_err()
150 );
151 }
152
153 #[test]
154 fn test_should_accept_zero_requested_size_for_unplug_to_zero() {
155 let upd = HotplugMemoryUpdate::try_from(RawHotplugMemoryUpdate {
156 requested_size_mib: 0,
157 })
158 .unwrap();
159 assert_eq!(upd.requested_size_mib, 0);
160 }
161
162 #[test]
163 fn test_should_round_trip_patch_through_serde() {
164 let raw: RawHotplugMemoryUpdate =
165 serde_json::from_str(r#"{"requested_size_mib":512}"#).unwrap();
166 assert_eq!(raw.requested_size_mib, 512);
167 }
168}