Skip to main content

cat_dev/fsemul/
bsf.rs

1//! Code for creating/reading/managing BOOT1 System Files.
2//!
3//! `.bsf` files, or BOOT1 System Files as they're called in several places in
4//! the original cat-dev sources/documentations are the original files served
5//! by PCFS.
6
7use crate::errors::{CatBridgeError, FSError};
8use bytes::{BufMut, Bytes, BytesMut};
9
10const MIN_FILE_SIZE: usize = 32_usize;
11const MAX_FILE_SIZE: usize = 1_048_576_usize;
12const BOOTOS_BOOT_BLOCK_MAGIC: u32 = 0xFD9B_5B7A_u32;
13
14/// A boot file system.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct BootSystemFile {
17	header: BootOSBootBlockHeader,
18	data_info: Vec<BootOSDataInfo>,
19}
20
21impl BootSystemFile {
22	#[must_use]
23	pub const fn header(&self) -> &BootOSBootBlockHeader {
24		&self.header
25	}
26	#[must_use]
27	pub const fn data_info(&self) -> &Vec<BootOSDataInfo> {
28		&self.data_info
29	}
30}
31
32impl Default for BootSystemFile {
33	fn default() -> Self {
34		Self {
35			header: BootOSBootBlockHeader {
36				platform_info_physical_address: 0x2000_8000,
37				package_info_physical_address: 0x2000_8800,
38				boot1_flags: 0x8000_0000,
39				sticky_register_bits: 0,
40			},
41			data_info: Vec::with_capacity(0),
42		}
43	}
44}
45
46impl TryFrom<BootSystemFile> for Bytes {
47	type Error = CatBridgeError;
48
49	fn try_from(value: BootSystemFile) -> Result<Self, Self::Error> {
50		let mut buff = BytesMut::with_capacity(32 + (value.data_info.len() * 8));
51
52		buff.put_u32(BOOTOS_BOOT_BLOCK_MAGIC);
53		// Can't have more infos than this without extending file size.
54		let actual_len = std::cmp::min(
55			u32::try_from(value.data_info().len()).unwrap_or(131_068),
56			131_068,
57		);
58		buff.put_u32(actual_len);
59		buff.put_u32(value.header().platform_info_physical_address());
60		buff.put_u32(value.header().package_info_physical_address());
61		buff.put_u32(value.header().boot1_flags());
62		buff.put_u32(value.header().sticky_register_bits());
63		// Padding Bytes
64		buff.put_u32(0x0);
65		buff.put_u32(0x0);
66		for idx in
67			0..usize::try_from(actual_len).map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?
68		{
69			buff.put_u32(value.data_info[idx].address());
70			buff.put_u32(value.data_info[idx].length());
71		}
72
73		Ok(buff.freeze())
74	}
75}
76
77impl TryFrom<Bytes> for BootSystemFile {
78	type Error = FSError;
79
80	fn try_from(value: Bytes) -> Result<Self, Self::Error> {
81		if value.len() < 32 {
82			return Err(FSError::TooSmall(MIN_FILE_SIZE, value.len()));
83		}
84		if value.len() > MAX_FILE_SIZE {
85			return Err(FSError::TooLarge(MAX_FILE_SIZE, value.len()));
86		}
87
88		let actual_magic = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
89		if actual_magic != BOOTOS_BOOT_BLOCK_MAGIC {
90			return Err(FSError::InvalidFileMagic(
91				BOOTOS_BOOT_BLOCK_MAGIC,
92				actual_magic,
93			));
94		}
95
96		let data_info_count = u32::from_be_bytes([value[4], value[5], value[6], value[7]]);
97		let expected_file_size =
98			32_usize + (usize::try_from(data_info_count).unwrap_or(usize::MAX) * 8_usize);
99		if value.len() != expected_file_size {
100			return Err(FSError::InvalidFileSize(expected_file_size, value.len()));
101		}
102
103		let platform_info_addr = u32::from_be_bytes([value[8], value[9], value[10], value[11]]);
104		let package_info_addr = u32::from_be_bytes([value[12], value[13], value[14], value[15]]);
105		let boot1_flags = u32::from_be_bytes([value[16], value[17], value[18], value[19]]);
106		let sticky_register_bits = u32::from_be_bytes([value[20], value[21], value[22], value[23]]);
107
108		// There are two u32's here that are just used for padding here.
109		// Then we get into the data info blocks.
110		let mut index = 32_usize;
111		let mut data_infos =
112			Vec::with_capacity(usize::try_from(data_info_count).unwrap_or(usize::MAX));
113		for _ in 0..data_info_count {
114			let address = u32::from_be_bytes([
115				value[index],
116				value[index + 1],
117				value[index + 2],
118				value[index + 3],
119			]);
120			let length = u32::from_be_bytes([
121				value[index + 4],
122				value[index + 5],
123				value[index + 6],
124				value[index + 7],
125			]);
126
127			data_infos.push(BootOSDataInfo { address, length });
128			index += 8;
129		}
130
131		Ok(Self {
132			header: BootOSBootBlockHeader {
133				platform_info_physical_address: platform_info_addr,
134				package_info_physical_address: package_info_addr,
135				boot1_flags,
136				sticky_register_bits,
137			},
138			data_info: data_infos,
139		})
140	}
141}
142
143#[derive(Clone, Debug, PartialEq, Eq)]
144pub struct BootOSBootBlockHeader {
145	platform_info_physical_address: u32,
146	package_info_physical_address: u32,
147	boot1_flags: u32,
148	sticky_register_bits: u32,
149}
150impl BootOSBootBlockHeader {
151	#[must_use]
152	pub const fn platform_info_physical_address(&self) -> u32 {
153		self.platform_info_physical_address
154	}
155	#[must_use]
156	pub const fn package_info_physical_address(&self) -> u32 {
157		self.package_info_physical_address
158	}
159	#[must_use]
160	pub const fn boot1_flags(&self) -> u32 {
161		self.boot1_flags
162	}
163	#[must_use]
164	pub const fn sticky_register_bits(&self) -> u32 {
165		self.sticky_register_bits
166	}
167}
168
169#[derive(Clone, Debug, PartialEq, Eq)]
170pub struct BootOSDataInfo {
171	address: u32,
172	length: u32,
173}
174impl BootOSDataInfo {
175	#[must_use]
176	pub const fn address(&self) -> u32 {
177		self.address
178	}
179	#[must_use]
180	pub const fn length(&self) -> u32 {
181		self.length
182	}
183}
184
185#[cfg(test)]
186mod unit_tests {
187	use super::*;
188	use std::path::PathBuf;
189
190	#[test]
191	pub fn can_parse_boot_system_file() {
192		let mut test_data_dir = PathBuf::from(
193			std::env::var("CARGO_MANIFEST_DIR")
194				.expect("Failed to read `CARGO_MANIFEST_DIR` to locate test files!"),
195		);
196		test_data_dir.push("src");
197		test_data_dir.push("fsemul");
198		test_data_dir.push("test-data");
199
200		{
201			// Real life bsf that came from a caferun directory.
202			let mut bsf_file = test_data_dir.clone();
203			bsf_file.push("ppc.bsf");
204			let file_contents =
205				std::fs::read(&bsf_file).expect("Failed to read real life bsf file!");
206			let file = BootSystemFile::try_from(Bytes::from(file_contents))
207				.expect("Failed to parse real life ppc.bsf");
208
209			assert!(
210				file.data_info().is_empty(),
211				"Expected an empty data info list for Boot System File."
212			);
213
214			assert_eq!(
215				file.header().platform_info_physical_address(),
216				0x20008000,
217				"Invalid Platform Info Physical Address from Boot System File.",
218			);
219			assert_eq!(
220				file.header().package_info_physical_address(),
221				0x20008800,
222				"Invalid Package Info Physical Address from Boot System File.",
223			);
224			assert_eq!(
225				file.header().boot1_flags(),
226				0x80000000,
227				"Invalid Boot1 Flags from Boot System File.",
228			);
229			assert_eq!(
230				file.header().sticky_register_bits(),
231				0,
232				"Invalid Sticky Register Bits for Boot System File."
233			);
234		}
235	}
236}