1use crate::iomem::{MemoryRegion, MemoryRegionType, parse_proc_iomem};
35use crate::reader::SkippingBufReader;
36use std::collections::VecDeque;
37use std::fs::File;
38use std::io;
39use std::io::{Read, Seek};
40use std::str;
41use thiserror::Error;
42
43#[derive(PartialEq)]
45pub struct FoundVersion {
46 pub agesa_version: String,
48 pub absolute_address: usize,
50 pub surrounding_region: MemoryRegion,
52}
53
54impl FoundVersion {
55 #[must_use]
57 pub fn offset_in_region(&self) -> usize {
58 self.absolute_address - self.surrounding_region.start_address
59 }
60}
61
62#[derive(Error, Debug)]
63pub enum SearchError {
64 #[error("could not open `/dev/mem`")]
65 DevMemUnopenable(#[source] io::Error),
66
67 #[error("could not read memory map from `/proc/iomem`")]
68 IomemUnreadable(#[source] io::Error),
69
70 #[error("could not read byte in physical memory from `/dev/mem`")]
71 ByteUnreadable(#[source] io::Error),
72}
73
74pub type SearchResult = Result<Option<FoundVersion>, SearchError>;
75
76pub fn find_agesa_version() -> SearchResult {
84 let possible_regions =
85 get_reserved_regions_in_extended_memory().map_err(SearchError::IomemUnreadable)?;
86
87 for region in possible_regions {
88 log::info!("Searching memory region: {region}");
89 let maybe_found_version = find_agesa_version_in_memory_region(region)?;
90 if maybe_found_version.is_some() {
91 return Ok(maybe_found_version);
92 }
93 }
94
95 Ok(None)
96}
97
98pub fn find_agesa_version_in_memory_region(region: MemoryRegion) -> SearchResult {
105 let file = File::open("/dev/mem").map_err(SearchError::DevMemUnopenable)?;
106 let buf_reader = SkippingBufReader::new(file, region.start_address, Some(region.end_address));
107
108 if let Some((agesa_version, absolute_address)) = find_agesa_version_in_reader(buf_reader)? {
109 return Ok(Some(FoundVersion {
110 agesa_version,
111 absolute_address,
112 surrounding_region: region,
113 }));
114 }
115
116 Ok(None)
117}
118
119enum SearchState {
121 Searching,
122 SignatureFound,
123 VersionStartFound,
124}
125
126const SIGNATURE_V9: &[u8] = b"AGESA!V";
128const SIGNATURE_LENGTH: usize = SIGNATURE_V9.len();
129
130const SIGNATURE_V5: &[u8; SIGNATURE_LENGTH] = b"!!AGESA";
138
139fn find_agesa_version_in_reader<R: Read + Seek>(
140 mut buf_reader: SkippingBufReader<R>,
141) -> Result<Option<(String, usize)>, SearchError> {
142 let mut version_string = Vec::new();
143
144 let mut search_state = SearchState::Searching;
145 let mut search_window = VecDeque::with_capacity(SIGNATURE_LENGTH);
146
147 for b in (&mut buf_reader).bytes() {
148 let byte = b.map_err(SearchError::ByteUnreadable)?;
149
150 match search_state {
151 SearchState::Searching => {
152 if search_window.len() == SIGNATURE_LENGTH {
153 search_window.pop_front();
154 }
155 search_window.push_back(byte);
156
157 if search_window.eq(&SIGNATURE_V9) {
158 search_state = SearchState::SignatureFound;
161 } else if search_window.eq(&SIGNATURE_V5) {
162 search_state = SearchState::VersionStartFound;
167 }
168 }
169 SearchState::SignatureFound => {
170 if byte == b'\0' {
171 search_state = SearchState::VersionStartFound;
174 }
175 }
176 SearchState::VersionStartFound if byte == b'\0' => {
177 let trimmed_version = version_string.trim_ascii_start();
180 let absolute_address = buf_reader.position_in_file() - trimmed_version.len() - 1;
181 return Ok(Some((
182 str::from_utf8(trimmed_version).unwrap().into(),
183 absolute_address,
184 )));
185 }
186 SearchState::VersionStartFound => {
187 version_string.push(byte);
188 }
189 }
190 }
191
192 Ok(None)
193}
194
195const EXTENDED_MEM_START: usize = 0x0000_0000_0010_0000;
197
198pub fn get_reserved_regions_in_extended_memory() -> io::Result<Vec<MemoryRegion>> {
211 let all_regions = parse_proc_iomem()?;
212 let reserved_high_mem_regions: Vec<MemoryRegion> = all_regions
213 .into_iter()
214 .filter(|r| r.region_type == MemoryRegionType::Reserved)
215 .filter(|r| r.start_address >= EXTENDED_MEM_START)
216 .collect();
217
218 Ok(reserved_high_mem_regions)
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 mod found_version {
226 use super::*;
227
228 #[test]
229 fn offset_in_region_returns_expected_offset() {
230 let version = FoundVersion {
231 agesa_version: "CezannePI-FP6 1.0.1.1".into(),
232 absolute_address: 20,
233 surrounding_region: MemoryRegion {
234 start_address: 5,
235 end_address: 100,
236 region_type: MemoryRegionType::Reserved,
237 },
238 };
239 assert_eq!(version.offset_in_region(), 15);
240 }
241 }
242
243 mod find_agesa_version_in_reader {
244 use super::*;
245 use indoc::formatdoc;
246 use rstest::rstest;
247 use std::io::Cursor;
248
249 #[rstest]
250 #[case::agesa_v9_signature(
251 "AGESA!V9\0CezannePI-FP6 1.0.1.1\0",
252 "CezannePI-FP6 1.0.1.1",
253 37
254 )]
255 #[case::agesa_v5_signature_arch2008(
256 "!!AGESA KaveriPI V1.1.0.7 \0",
257 "KaveriPI V1.1.0.7 ",
258 36
259 )]
260 #[case::agesa_v5_signature_alternative(
261 "!!!AGESAKaveriPI V1.1.0.7 \0",
262 "KaveriPI V1.1.0.7 ",
263 36
264 )]
265 fn returns_expected_version_string_and_absolute_address(
266 #[case] version_in_memory: String,
267 #[case] expected_version_string: String,
268 #[case] expected_absolute_address: usize,
269 ) {
270 let file = Cursor::new(formatdoc! {"
271 PreceedingUnrelated\0Bytes%p
272 {version_in_memory}
273 \0SubsequentUnrelatedBytes\0
274 "});
275 let buf_reader = SkippingBufReader::new(file, 0, None);
276
277 let result = find_agesa_version_in_reader(buf_reader).unwrap();
278 assert_eq!(
279 result,
280 Some((expected_version_string, expected_absolute_address))
281 );
282 }
283
284 #[test]
285 fn returns_none_if_no_agesa_signature_is_present() {
286 let file = Cursor::new(b"AESA!V9\0CezannePI-FP6 1.0.1.1\0");
287 let buf_reader = SkippingBufReader::new(file, 0, None);
288
289 let result = find_agesa_version_in_reader(buf_reader).unwrap();
290 assert_eq!(result, None);
291 }
292
293 #[test]
294 fn returns_none_if_version_string_does_not_end() {
295 let file = Cursor::new(b"AGESA!V9\0CezannePI-FP6 1.0.1.1");
296 let buf_reader = SkippingBufReader::new(file, 0, None);
297
298 let result = find_agesa_version_in_reader(buf_reader).unwrap();
299 assert_eq!(result, None);
300 }
301 }
302}