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