1use std::fs::File;
35use std::io::{self, Read, Seek};
36
37use heapless::HistoryBuffer;
38use thiserror::Error;
39
40use crate::iomem::{MemoryRegion, MemoryRegionType, parse_proc_iomem};
41use crate::reader::SkippingBufReader;
42
43#[derive(PartialEq)]
45pub struct AgesaVersion {
46 pub version_string: String,
48 pub absolute_address: usize,
50 pub surrounding_region: MemoryRegion,
52}
53
54impl AgesaVersion {
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<AgesaVersion>, 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((version_string, absolute_address)) = find_agesa_version_in_reader(buf_reader)? {
109 return Ok(Some(AgesaVersion {
110 version_string,
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
139pub fn find_agesa_version_in_reader<R: Read + Seek>(
148 mut buf_reader: SkippingBufReader<R>,
149) -> Result<Option<(String, usize)>, SearchError> {
150 let mut version_string = Vec::new();
151
152 let mut search_state = SearchState::Searching;
153 let mut search_window: HistoryBuffer<u8, SIGNATURE_LENGTH> = HistoryBuffer::new();
154
155 let mut buffer = [0; 1024];
156 loop {
157 let bytes_read = buf_reader
158 .read(&mut buffer)
159 .map_err(SearchError::ByteUnreadable)?;
160 if bytes_read == 0 {
161 break;
162 }
163
164 for (i, &byte) in buffer[..bytes_read].iter().enumerate() {
165 match search_state {
166 SearchState::Searching => {
167 search_window.write(byte);
168
169 if search_window.oldest_ordered().eq(SIGNATURE_V9) {
170 search_state = SearchState::SignatureFound;
173 } else if search_window.oldest_ordered().eq(SIGNATURE_V5) {
174 search_state = SearchState::VersionStartFound;
179 }
180 }
181 SearchState::SignatureFound => {
182 if byte == b'\0' {
183 search_state = SearchState::VersionStartFound;
186 }
187 }
188 SearchState::VersionStartFound if byte == b'\0' => {
189 let trimmed_version = version_string.trim_ascii_start();
192 let current_offset = buf_reader.position_in_file() - (bytes_read - i);
193 let version_offset = current_offset - trimmed_version.len();
194
195 return Ok(Some((
196 String::from_utf8_lossy(trimmed_version).into(),
197 version_offset,
198 )));
199 }
200 SearchState::VersionStartFound => {
201 version_string.push(byte);
202 }
203 }
204 }
205 }
206
207 Ok(None)
208}
209
210const EXTENDED_MEM_START: usize = 0x0000_0000_0010_0000;
212
213pub fn get_reserved_regions_in_extended_memory() -> io::Result<Vec<MemoryRegion>> {
226 let all_regions = parse_proc_iomem()?;
227 let reserved_high_mem_regions: Vec<MemoryRegion> = all_regions
228 .into_iter()
229 .filter(|r| r.region_type == MemoryRegionType::Reserved)
230 .filter(|r| r.start_address >= EXTENDED_MEM_START)
231 .collect();
232
233 Ok(reserved_high_mem_regions)
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 mod found_version {
241 use super::*;
242
243 #[test]
244 fn offset_in_region_returns_expected_offset() {
245 let version = AgesaVersion {
246 version_string: "CezannePI-FP6 1.0.1.1".into(),
247 absolute_address: 20,
248 surrounding_region: MemoryRegion {
249 start_address: 5,
250 end_address: 100,
251 region_type: MemoryRegionType::Reserved,
252 },
253 };
254 assert_eq!(version.offset_in_region(), 15);
255 }
256 }
257
258 mod find_agesa_version_in_reader {
259 use super::*;
260 use indoc::formatdoc;
261 use rstest::rstest;
262 use std::io::Cursor;
263
264 #[rstest]
265 #[case::agesa_v9_signature(
266 "AGESA!V9\0CezannePI-FP6 1.0.1.1\0",
267 "CezannePI-FP6 1.0.1.1",
268 37
269 )]
270 #[case::agesa_v5_signature_arch2008(
271 "!!AGESA KaveriPI V1.1.0.7 \0",
272 "KaveriPI V1.1.0.7 ",
273 36
274 )]
275 #[case::agesa_v5_signature_alternative(
276 "!!!AGESAKaveriPI V1.1.0.7 \0",
277 "KaveriPI V1.1.0.7 ",
278 36
279 )]
280 fn returns_expected_version_string_and_absolute_address(
281 #[case] version_in_memory: String,
282 #[case] expected_version_string: String,
283 #[case] expected_absolute_address: usize,
284 ) {
285 let file = Cursor::new(formatdoc! {"
286 PreceedingUnrelated\0Bytes%p
287 {version_in_memory}
288 \0SubsequentUnrelatedBytes\0
289 "});
290 let buf_reader = SkippingBufReader::new(file, 0, None);
291
292 let result = find_agesa_version_in_reader(buf_reader).unwrap();
293 assert_eq!(
294 result,
295 Some((expected_version_string, expected_absolute_address))
296 );
297 }
298
299 #[test]
300 fn returns_none_if_no_agesa_signature_is_present() {
301 let file = Cursor::new(b"AESA!V9\0CezannePI-FP6 1.0.1.1\0");
302 let buf_reader = SkippingBufReader::new(file, 0, None);
303
304 let result = find_agesa_version_in_reader(buf_reader).unwrap();
305 assert_eq!(result, None);
306 }
307
308 #[test]
309 fn returns_none_if_version_string_does_not_end() {
310 let file = Cursor::new(b"AGESA!V9\0CezannePI-FP6 1.0.1.1");
311 let buf_reader = SkippingBufReader::new(file, 0, None);
312
313 let result = find_agesa_version_in_reader(buf_reader).unwrap();
314 assert_eq!(result, None);
315 }
316 }
317}