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 thiserror::Error;
41
42#[derive(PartialEq)]
43pub struct FoundVersion {
44 pub agesa_version: String,
45 pub absolute_address: usize,
46 pub surrounding_region: MemoryRegion,
47}
48
49impl FoundVersion {
50 #[must_use]
52 pub fn offset_in_region(&self) -> usize {
53 self.absolute_address - self.surrounding_region.start_address
54 }
55}
56
57#[derive(Error, Debug)]
58pub enum SearchError {
59 #[error("could not open /dev/mem")]
60 DevMemUnopenable(#[source] io::Error),
61
62 #[error("could not read iomem memory map")]
63 IomemUnreadable(#[source] io::Error),
64
65 #[error("could not read byte in physical memory")]
66 ByteUnreadable(#[source] io::Error),
67}
68
69pub type SearchResult = Result<Option<FoundVersion>, SearchError>;
70
71pub fn find_agesa_version() -> SearchResult {
79 let possible_regions =
80 get_reserved_regions_in_extended_memory().map_err(SearchError::IomemUnreadable)?;
81
82 for region in possible_regions {
83 log::info!("Searching memory region: {}", region);
84 let search_result = find_agesa_version_in_memory_region(region)?;
85 if search_result.is_some() {
86 return Ok(search_result);
87 }
88 }
89
90 Ok(None)
91}
92
93pub fn find_agesa_version_in_memory_region(region: MemoryRegion) -> SearchResult {
100 let file = File::open("/dev/mem").map_err(SearchError::DevMemUnopenable)?;
101 let buf_reader = SkippingBufReader::new(file, region.start_address, Some(region.end_address));
102
103 if let Some((agesa_version, absolute_address)) = find_agesa_version_in_reader(buf_reader)? {
104 return Ok(Some(FoundVersion {
105 agesa_version,
106 absolute_address,
107 surrounding_region: region,
108 }));
109 }
110
111 Ok(None)
112}
113
114enum SearchState {
116 Searching,
117 PrefixFound,
118 VersionStartFound,
119}
120
121const SEARCH_SEQUENCE: &[u8] = b"AGESA!V";
123
124fn find_agesa_version_in_reader<R: Read + Seek>(
125 mut buf_reader: SkippingBufReader<R>,
126) -> Result<Option<(String, usize)>, SearchError> {
127 let mut agesa_version = Vec::new();
128
129 let mut search_state = SearchState::Searching;
130 let mut search_window = VecDeque::with_capacity(SEARCH_SEQUENCE.len());
131
132 for b in (&mut buf_reader).bytes() {
133 let byte = b.map_err(SearchError::ByteUnreadable)?;
134
135 match search_state {
136 SearchState::Searching => {
137 if search_window.len() == SEARCH_SEQUENCE.len() {
138 search_window.pop_front();
139 }
140 search_window.push_back(byte);
141
142 if search_window.eq(&SEARCH_SEQUENCE) {
143 search_state = SearchState::PrefixFound;
146 }
147 }
148 SearchState::PrefixFound => {
149 if byte == b'\0' {
150 search_state = SearchState::VersionStartFound;
153 }
154 }
155 SearchState::VersionStartFound if byte == b'\0' => {
156 let absolute_address = buf_reader.position_in_file() - agesa_version.len() - 1;
159 return Ok(Some((
160 String::from_utf8(agesa_version).unwrap(),
161 absolute_address,
162 )));
163 }
164 SearchState::VersionStartFound => {
165 agesa_version.push(byte);
166 }
167 }
168 }
169
170 Ok(None)
171}
172
173const EXTENDED_MEM_START: usize = 0x0000_0000_0010_0000;
175
176pub fn get_reserved_regions_in_extended_memory() -> io::Result<Vec<MemoryRegion>> {
189 let all_regions = parse_proc_iomem()?;
190 let reserved_high_mem_regions: Vec<MemoryRegion> = all_regions
191 .into_iter()
192 .filter(|r| r.region_type == MemoryRegionType::Reserved)
193 .filter(|r| r.start_address >= EXTENDED_MEM_START)
194 .collect();
195
196 Ok(reserved_high_mem_regions)
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 mod found_version {
204 use super::*;
205
206 #[test]
207 fn offset_in_region_returns_expected_offset() {
208 let version = FoundVersion {
209 agesa_version: "CezannePI-FP6 1.0.0.E".into(),
210 absolute_address: 20,
211 surrounding_region: MemoryRegion {
212 start_address: 5,
213 end_address: 100,
214 region_type: MemoryRegionType::Reserved,
215 },
216 };
217 assert_eq!(version.offset_in_region(), 15);
218 }
219 }
220
221 mod find_agesa_version_in_reader {
222 use super::*;
223 use indoc::indoc;
224 use std::io::Cursor;
225
226 #[test]
227 fn returns_expected_agesa_version_and_absolute_address() {
228 let file = Cursor::new(indoc! {b"
229 PreceedingUnrelated\0Bytes%p
230 AGESA!V9\0CezannePI-FP6 1.0.0.E\0
231 \0SubsequentUnrelatedBytes\0
232 "});
233 let buf_reader = SkippingBufReader::new(file, 0, None);
234
235 let result = find_agesa_version_in_reader(buf_reader).unwrap();
236 assert_eq!(result, Some(("CezannePI-FP6 1.0.0.E".into(), 37)));
237 }
238
239 #[test]
240 fn returns_none_if_no_agesa_signature_prefix_is_present() {
241 let file = Cursor::new(b"AESA!V9\0CezannePI-FP6 1.0.0.E\0");
242 let buf_reader = SkippingBufReader::new(file, 0, None);
243
244 let result = find_agesa_version_in_reader(buf_reader).unwrap();
245 assert_eq!(result, None);
246 }
247
248 #[test]
249 fn returns_none_if_agesa_version_string_never_ends() {
250 let file = Cursor::new(b"AGESA!V9\0CezannePI-FP6 1.0.0.E");
251 let buf_reader = SkippingBufReader::new(file, 0, None);
252
253 let result = find_agesa_version_in_reader(buf_reader).unwrap();
254 assert_eq!(result, None);
255 }
256 }
257}