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