Simple FAT16/32 driver implementation

This commit is contained in:
Ebu
2026-03-22 10:58:47 +01:00
commit 93d9cf0ae1
15 changed files with 1508 additions and 0 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[build]
target = "x86_64-unknown-uefi"

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/target
/disk.img
/OVMF_CODE.fd
/OVMF_VARS.fd

11
.zed/settings.json Normal file
View File

@@ -0,0 +1,11 @@
{
"lsp": {
"rust-analyzer": {
"initialization_options": {
"cargo": {
"allTargets": false,
},
},
},
},
}

164
Cargo.lock generated Normal file
View File

@@ -0,0 +1,164 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bit_field"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "efi-loader"
version = "0.1.0"
dependencies = [
"bitflags",
"log",
"uefi",
"zerocopy",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "ptr_meta"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79"
dependencies = [
"ptr_meta_derive",
]
[[package]]
name = "ptr_meta_derive"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "ucs2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79298e11f316400c57ec268f3c2c29ac3c4d4777687955cd3d4f3a35ce7eba"
dependencies = [
"bit_field",
]
[[package]]
name = "uefi"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71fe9058b73ee2b6559524af9e33199c13b2485ddbf3ad1181b68051cdc50c17"
dependencies = [
"bitflags",
"cfg-if",
"log",
"ptr_meta",
"ucs2",
"uefi-macros",
"uefi-raw",
"uguid",
]
[[package]]
name = "uefi-macros"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4687412b5ac74d245d5bfb1733ede50c31be19bf8a4b6a967a29b451bab49e67"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "uefi-raw"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f64fe59e11af447d12fd60a403c74106eb104309f34b4c6dbce6e927d97da9d"
dependencies = [
"bitflags",
"uguid",
]
[[package]]
name = "uguid"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8352f8c05e47892e7eaf13b34abd76a7f4aeaf817b716e88789381927f199c"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "zerocopy"
version = "0.8.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

12
Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "efi-loader"
version = "0.1.0"
edition = "2024"
[dependencies]
bitflags = "2.11.0"
log = "0.4.29"
uefi = { version = "0.36.1", features = ["logger", "panic_handler", "log-debugcon", "alloc", "global_allocator"] }
zerocopy = { version = "0.8.47", features = ["derive"] }
[workspace]

7
README.md Normal file
View File

@@ -0,0 +1,7 @@
# Setup
- Install `qemu-system-x86_64`, `gdisk`
- Install `cargo` (using [rustup](https://rustup.rs/) is the preferred cross-platform way to get Rust set up on your machine)
- We also require `OVMF_CODE.fd` and `OVMF_VARS.fd` in the root directory to boot QEMU in UEFI mode. These files are omitted due to potential licensing issues (read: I haven't properly looked into it yet).
- For creating a test disk image automatically, we require `mkfs.fat` and `mkfs.ext4` to be available.
# Running
Simply run `./run.sh` in the root directory to take care of setting up the environment and running QEMU with the required options.

29
run.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
set -e
PROJECT_DIR=$(dirname "$(realpath "$0")")
cargo build
mkdir -p "$PROJECT_DIR/target/esp/efi/boot"
cp "$PROJECT_DIR/target/x86_64-unknown-uefi/debug/efi-loader.efi" "$PROJECT_DIR/target/esp/efi/boot/bootx64.efi"
if [[ ! -f "$PROJECT_DIR/disk.img" ]]; then
dd if=/dev/zero of="$PROJECT_DIR/disk.img" bs=1M count=256
sgdisk --zap-all --new=1::+64M --new=2:: disk.img
echo Creating file systems on test disk, might require password
DEVICE=$(sudo losetup -fP --show "$PROJECT_DIR/disk.img")
sudo mkfs.fat -F32 ${DEVICE}p1
sudo mkfs.ext4 ${DEVICE}p2
sudo losetup -d $DEVICE
fi
qemu-system-x86_64 -enable-kvm \
-drive format=raw,file=fat:rw:target/esp,id=disk0,if=none \
-drive format=raw,file=disk.img,id=disk1,if=none \
-drive if=pflash,format=raw,readonly=on,file=OVMF_CODE.fd \
-drive if=pflash,format=raw,readonly=on,file=OVMF_VARS.fd \
-device ahci,id=ahci \
-device ide-hd,bus=ahci.0,drive=disk0 \
-device ide-hd,bus=ahci.1,drive=disk1 \
--no-shutdown

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
targets = ["x86_64-unknown-uefi"]

View File

@@ -0,0 +1,33 @@
use core::{
fmt::Debug,
ops::{Deref, DerefMut},
};
use alloc::vec::Vec;
use crate::fs::fat::Cluster;
#[derive(Clone, Default)]
pub struct ClusterChain(Vec<Cluster>);
impl Deref for ClusterChain {
type Target = [Cluster];
fn deref(&self) -> &[Cluster] {
&self.0[..]
}
}
impl DerefMut for ClusterChain {
fn deref_mut(&mut self) -> &mut [Cluster] {
&mut self.0[..]
}
}
impl From<Vec<Cluster>> for ClusterChain {
fn from(value: Vec<Cluster>) -> Self {
Self(value)
}
}
impl Debug for ClusterChain {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!("ClusterChain({:04X?})", self.0))
}
}

289
src/fs/fat/directory.rs Normal file
View File

@@ -0,0 +1,289 @@
use core::{fmt::Display, ops::Index};
use alloc::{
string::{String, ToString},
vec::Vec,
};
use bitflags::bitflags;
use zerocopy::{FromBytes, TryFromBytes};
use crate::fs::{
Error,
fat::{Cluster, FatFs, cluster_chain::ClusterChain},
};
#[derive(Debug, Clone, Copy, FromBytes)]
pub struct DirectoryAttribute(u8);
impl Display for DirectoryAttribute {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let r = if self.contains(Self::READ_ONLY) {
"R"
} else {
"W"
};
let d = if self.contains(Self::DIRECTORY) {
"DIR"
} else if self.contains(Self::LFN) {
"LFN"
} else {
" "
};
let h = if self.contains(Self::HIDDEN) {
"H"
} else {
"-"
};
let s = if self.contains(Self::SYSTEM) {
"S"
} else {
"-"
};
let v = if self.contains(Self::VOLUME_ID) {
"V"
} else {
"-"
};
let a = if self.contains(Self::ARCHIVE) {
"A"
} else {
"-"
};
f.write_fmt(format_args!("{d} {r}{h}{s}{v}{a}"))
}
}
#[allow(unused)]
impl DirectoryAttribute {
pub fn is_writable(&self) -> bool {
!self.contains(Self::READ_ONLY)
}
pub fn is_hidden(&self) -> bool {
self.contains(Self::HIDDEN)
}
pub fn is_system(&self) -> bool {
self.contains(Self::SYSTEM)
}
pub fn is_directory(&self) -> bool {
self.contains(Self::DIRECTORY)
}
pub fn is_archive(&self) -> bool {
self.contains(Self::ARCHIVE)
}
pub fn is_volume_id(&self) -> bool {
self.contains(Self::VOLUME_ID)
}
pub fn is_lfn(&self) -> bool {
self.intersects(Self::LFN)
}
}
bitflags! {
impl DirectoryAttribute: u8 {
const NONE = 0;
const READ_ONLY = 0x01;
const HIDDEN = 0x02;
const SYSTEM = 0x04;
const VOLUME_ID = 0x08;
const DIRECTORY = 0x10;
const ARCHIVE = 0x20;
const LFN = 0x0F;
}
}
#[derive(Debug, Clone)]
struct DirectoryList(Vec<DirectoryEntry>);
impl Index<usize> for DirectoryList {
type Output = DirectoryEntry;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
pub struct DirectoryListIter {
list: DirectoryList,
list_index: usize,
}
impl DirectoryListIter {
pub fn from_chain(fs: &mut FatFs, cluster_chain: &ClusterChain) -> Self {
let mut result = Self {
list_index: 0,
list: DirectoryList(Vec::with_capacity(32)),
};
result.buffer_entries_from_chain(fs, cluster_chain).unwrap();
result
}
pub fn from_root(fs: &mut FatFs) -> Self {
let mut result = Self {
list_index: 0,
list: DirectoryList(Vec::with_capacity(fs.boot_sector.root_entry_count as usize)),
};
result.buffer_entries_from_root(fs).unwrap();
result
}
fn buffer_entries_from_chain(
&mut self,
fs: &mut FatFs,
chain: &ClusterChain,
) -> Result<(), Error<()>> {
let mut sector_buffer = Vec::with_capacity(fs.sector_size_bytes());
sector_buffer.resize(fs.sector_size_bytes(), 0);
for cluster in chain.iter() {
self.append_directory_list_from_cluster(fs, *cluster, sector_buffer.as_mut_slice())?;
}
Ok(())
}
fn buffer_entries_from_root(&mut self, fs: &mut FatFs) -> Result<(), Error<()>> {
let mut sector_buffer = Vec::with_capacity(fs.sector_size_bytes());
sector_buffer.resize(fs.sector_size_bytes(), 0);
fs.seek(fs.root_dir_sector());
for _ in 0..fs.root_dir_sectors {
fs.partition.read(&mut sector_buffer)?;
if !self.load_entries_from_bytes(sector_buffer.as_slice()) {
break;
}
}
Ok(())
}
fn append_directory_list_from_cluster(
&mut self,
fs: &mut FatFs,
cluster: Cluster,
buffer: &mut [u8],
) -> Result<(), Error<()>> {
fs.seek(cluster);
fs.partition.read(buffer)?;
self.load_entries_from_bytes(buffer as &[u8]);
Ok(())
}
fn load_entries_from_bytes(&mut self, mut bytes: &[u8]) -> bool {
while bytes.len() > 0 {
let (entry, remainder) = DirectoryEntry::try_read_from_prefix(bytes).unwrap();
bytes = remainder;
if entry.is_end() {
return false;
}
if entry.is_used() {
self.list.0.push(entry);
}
}
true
}
}
impl Iterator for DirectoryListIter {
type Item = DirectoryEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.list_index >= self.list.0.len() {
None
} else {
let result = self.list.0[self.list_index];
self.list_index += 1;
Some(result)
}
}
}
#[derive(Debug, Copy, Clone, FromBytes)]
#[repr(packed, C)]
pub struct DirectoryEntry {
filename: [u8; 11],
attributes: DirectoryAttribute,
reserved_0: u8,
ctime_100th: u8,
ctime: u32,
atime: u16,
cluster_high: u16,
mtime: u32,
cluster_low: u16,
file_size: u32,
}
impl DirectoryEntry {
pub fn start_cluster(&self) -> Cluster {
Cluster((self.cluster_high as u32) << 16 | self.cluster_low as u32)
}
pub fn is_end(&self) -> bool {
self.filename[0] == 0
}
pub fn is_unused(&self) -> bool {
self.filename[0] == 0xE5
}
pub fn is_used(&self) -> bool {
!self.is_unused() && !self.is_end()
}
pub fn is_lfn(&self) -> bool {
self.attributes.is_lfn()
}
pub fn decode_lfn(&self) -> (u8, String) {
let mut concat = [0u8; 26];
let mut result = [0u16; 13];
unsafe {
let data = core::mem::transmute::<*const DirectoryEntry, *const u8>(self as *const _);
let temp = concat.as_mut_ptr();
temp.byte_add(0)
.copy_from_nonoverlapping(data.byte_add(1), 10);
temp.byte_add(10)
.copy_from_nonoverlapping(data.byte_add(14), 12);
temp.byte_add(22)
.copy_from_nonoverlapping(data.byte_add(28), 4);
//String::from_utf16(unsafe { core::mem::transmute(concat.as_slice()) })
}
concat
.chunks(2)
.enumerate()
.for_each(|(i, val)| result[i] = u16::from_le_bytes([val[0], val[1]]));
let end = result.iter().position(|p| *p == 0).unwrap_or(13);
(self.filename[0], String::from_utf16_lossy(&result[..end]))
}
pub fn name(&self) -> String {
String::from_utf8_lossy(&self.filename).to_string()
}
pub fn attributes(&self) -> DirectoryAttribute {
self.attributes
}
pub fn size(&self) -> u32 {
self.file_size
}
}
impl Display for DirectoryEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let end = if self.is_end() {
"END "
} else if self.is_used() {
"USED"
} else {
"FREE"
};
if self.attributes.is_lfn() {
let (part, lfn) = self.decode_lfn();
f.write_fmt(format_args!("{lfn:13} {} [{:02X}]", self.attributes, part))
} else {
let mut filename = self.filename.clone();
if self.is_used() {
f.write_fmt(format_args!(
"{:13} {} {end} {}",
self.name(),
self.attributes,
self.start_cluster()
))
} else {
filename.iter_mut().for_each(|e| {
if *e < 0x20 {
*e = b' '
} else if *e == 0xE5 {
*e = b'_'
}
});
f.write_fmt(format_args!(
"{:13} {} {end} {}",
self.name(),
self.attributes,
self.start_cluster()
))
}
}
}
}

78
src/fs/fat/fat_type.rs Normal file
View File

@@ -0,0 +1,78 @@
use alloc::vec::Vec;
use zerocopy::FromBytes;
use crate::fs::fat::{Cluster, FatFs, Sector};
#[derive(Debug, Copy, Clone)]
#[repr(packed, C)]
pub struct Fat12;
//impl Fat12 {
// pub fn read_fat(_fat: &mut FatFs) -> Result<Vec<u16>, uefi::Error> {
// unimplemented!()
// }
//}
#[derive(Debug, Copy, Clone, FromBytes)]
#[repr(packed, C)]
pub struct Fat16 {
pub bios_drive_num: u8,
reserved_0: u8,
pub boot_signature: u16,
pub volume_id: u32,
pub volume_label: [u8; 11],
pub fat_type_label: [u8; 8],
}
impl Fat16 {
pub fn read_fat(fat: &mut FatFs) -> Result<Vec<Cluster>, uefi::Error> {
let fat_len_bytes = fat.fat_size_bytes() as usize;
let mut buffer = Vec::with_capacity(fat_len_bytes);
buffer.resize(fat_len_bytes, 0u8);
fat.seek(Sector(fat.first_fat_sector));
fat.partition.read(&mut buffer)?;
Ok(buffer
.chunks_exact(2)
.into_iter()
.map(|val| Cluster::from_16(u16::from_ne_bytes([val[0], val[1]])))
.collect::<Vec<_>>())
}
}
#[derive(Debug, Copy, Clone, FromBytes)]
#[repr(packed, C)]
pub struct Fat32 {
pub table_size_32: u32,
pub extended_flags: u16,
pub fat_version: u16,
pub root_cluster: u32,
pub fat_info: u16,
pub backup_bs_sector: u16,
reserved_0: [u8; 12],
pub drive_number: u8,
reserved_1: u8,
pub boot_signature: u8,
pub volume_id: u32,
pub volume_label: [u8; 11],
pub fat_type_label: [u8; 8],
}
impl Fat32 {
pub fn read_fat(fat: &mut FatFs) -> Result<Vec<Cluster>, uefi::Error> {
let fat_len_bytes = fat.fat_size_bytes() as usize;
let mut buffer = Vec::with_capacity(fat_len_bytes);
buffer.resize(fat_len_bytes, 0u8);
fat.seek(Sector(fat.first_fat_sector));
fat.partition.read(&mut buffer)?;
Ok(buffer
.chunks_exact(4)
.into_iter()
.map(|val| Cluster::from_32(u32::from_ne_bytes([val[0], val[1], val[2], val[3]])))
.collect::<Vec<_>>())
}
}
#[derive(Debug, Copy, Clone)]
#[allow(unused)]
pub enum FatType {
Fat12(Fat12),
Fat16(Fat16),
Fat32(Fat32),
}

568
src/fs/fat/mod.rs Normal file
View File

@@ -0,0 +1,568 @@
pub mod cluster_chain;
pub mod directory;
pub mod fat_type;
use core::fmt::{Debug, Display};
use alloc::{
string::{String, ToString},
vec::Vec,
};
use log::debug;
use zerocopy::{FromBytes, TryFromBytes};
use crate::{
Partition, Seek,
fs::{
BpbError, Error, Seekable,
fat::{
cluster_chain::ClusterChain,
directory::{DirectoryAttribute, DirectoryEntry, DirectoryListIter},
fat_type::{Fat12, Fat16, Fat32, FatType},
},
},
};
pub struct LfnBuilder(Vec<(u8, String)>);
impl LfnBuilder {
pub fn new() -> Self {
Self(Vec::with_capacity(16))
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn add(&mut self, part: (u8, String)) {
self.0.push(part);
}
pub fn build(mut self) -> String {
self.0.sort_by_key(|e| e.0);
self.0.into_iter().map(|e| e.1).collect::<Vec<_>>().join("")
}
}
#[derive(Debug, Copy, Clone, Default, FromBytes)]
#[repr(packed)]
pub struct FatBootSector {
bootjmp: [u8; 3],
oem_name: [u8; 8],
bytes_per_sector: u16,
sectors_per_cluster: u8,
reserved_sector_count: u16,
table_count: u8,
root_entry_count: u16,
total_sectors_16: u16,
media_type: u8,
table_size_16: u16,
sectors_per_track: u16,
head_side_count: u16,
hidden_sector_count: u32,
total_sectors_32: u32,
}
impl FatBootSector {
pub fn cluster_size_bytes(&self) -> u32 {
let bps = unsafe { (&raw const self.bytes_per_sector).read_unaligned() };
bps as u32 * self.sectors_per_cluster as u32
}
pub fn from_bytes(bytes: &[u8]) -> Result<(FatBootSector, FatType), BpbError> {
let (bpb, extended_fat_info) = FatBootSector::try_read_from_prefix(bytes).unwrap();
let bps = unsafe { (&raw const bpb.bytes_per_sector).read_unaligned() as u32 };
if ![512, 1024, 2048, 4096].contains(&bps) {
return Err(BpbError::BytesPerSector);
}
let total_sectors = if bpb.total_sectors_16 == 0 {
bpb.total_sectors_32
} else {
bpb.total_sectors_16 as u32
};
let fat_size = bpb.table_size_16 as u32;
let root_dir_sectors = (bpb.root_entry_count as u32 * 32 + (bps - 1)) / bps;
let data_sectors = total_sectors - bpb.reserved_sector_count as u32
+ bpb.table_count as u32 * fat_size
+ root_dir_sectors;
let total_clusters = data_sectors / bpb.sectors_per_cluster as u32;
let fat_type = if total_clusters < 4085 {
FatType::Fat12(Fat12)
} else if total_clusters < 65525 {
let (fat, _) = Fat16::try_read_from_prefix(extended_fat_info).unwrap();
FatType::Fat16(fat)
} else {
let (fat, _) = Fat32::try_read_from_prefix(extended_fat_info).unwrap();
FatType::Fat32(fat)
};
if bpb.sectors_per_cluster == 0
|| (bpb.sectors_per_cluster & (bpb.sectors_per_cluster.wrapping_sub(1))) != 0
{
return Err(BpbError::SectorsPerCluster);
}
if bpb.reserved_sector_count == 0 {
return Err(BpbError::ReservedSectorCount);
}
if bpb.table_count == 0 {
return Err(BpbError::NumFats);
}
if bps * bpb.sectors_per_cluster as u32 == 0 {
return Err(BpbError::ClusterSize);
}
if total_sectors == 0 {
return Err(BpbError::TotalSectorCount);
}
Ok((bpb, fat_type))
}
}
#[derive(Debug)]
pub struct FatFs {
partition: Partition,
boot_sector: FatBootSector,
fat_type: FatType,
allocation_table: Option<FileAllocationTable>,
root_directory: CachedFsEntry,
//total_sectors: u32,
fat_size: u32,
root_dir_sectors: u32,
first_data_sector: u32,
first_fat_sector: u32,
//data_sectors: u32,
//total_clusters: u32,
}
impl FatFs {
pub fn mount(mut partition: Partition) -> Result<FatFs, Error<BpbError>> {
let mut sector = [0u8; 90];
partition.seek(Seek::SetFromStart(0));
if partition.read(&mut sector).is_err() {
return Err(Error::Open);
}
let (boot_sector, fat_type) = FatBootSector::from_bytes(&sector)?;
let bps = unsafe { (&raw const boot_sector.bytes_per_sector).read_unaligned() as u32 };
//let total_sectors = if boot_sector.total_sectors_16 == 0 {
// boot_sector.total_sectors_32
//} else {
// boot_sector.total_sectors_16 as u32
//};
let fat_size = if boot_sector.table_size_16 == 0 {
match fat_type {
FatType::Fat32(fat) => fat.table_size_32,
_ => boot_sector.table_size_16 as u32,
}
} else {
boot_sector.table_size_16 as u32
};
let root_dir_sectors = (boot_sector.root_entry_count as u32 * 32 + (bps - 1)) / bps;
//let data_sectors = total_sectors - boot_sector.reserved_sector_count as u32
// + boot_sector.table_count as u32 * fat_size
// + root_dir_sectors;
//let total_clusters = data_sectors / boot_sector.sectors_per_cluster as u32;
Ok(Self {
boot_sector,
fat_type,
partition,
allocation_table: None,
root_directory: CachedFsEntry::root(),
//total_sectors,
fat_size,
root_dir_sectors,
first_data_sector: boot_sector.reserved_sector_count as u32
+ (boot_sector.table_count as u32 * fat_size)
+ root_dir_sectors,
first_fat_sector: boot_sector.reserved_sector_count as u32,
//data_sectors,
//total_clusters,
})
}
//pub fn is_fat12(&self) -> bool {
// match self.fat_type {
// FatType::Fat12(_) => true,
// _ => false,
// }
//}
pub fn is_fat16(&self) -> bool {
match self.fat_type {
FatType::Fat16(_) => true,
_ => false,
}
}
pub fn is_fat32(&self) -> bool {
match self.fat_type {
FatType::Fat32(_) => true,
_ => false,
}
}
pub fn sector_size_bytes(&self) -> usize {
self.boot_sector.bytes_per_sector as usize
}
pub fn cluster_size_bytes(&self) -> usize {
self.boot_sector.cluster_size_bytes() as usize
}
fn fat_size_bytes(&self) -> u32 {
self.fat_size * self.boot_sector.bytes_per_sector as u32
}
fn read_allocation_table(&mut self) -> Result<(), uefi::Error> {
self.allocation_table = Some(FileAllocationTable::from_fat(self)?);
Ok(())
}
fn root_dir_sector(&self) -> Sector {
Sector(self.first_data_sector - self.root_dir_sectors)
}
pub fn open(&mut self, path: &str) -> Result<FileHandle, Error<()>> {
if !path.starts_with("/") {
return Err(Error::InvalidPath);
}
if self.allocation_table.is_none() {
self.read_allocation_table()?;
if let Some(fat) = &self.allocation_table {
match self.fat_type {
FatType::Fat32(fat32) => {
self.root_directory.cluster_chain =
fat.read_cluster_chain(Cluster::from_32(fat32.root_cluster));
}
FatType::Fat16(_) => {
let children = CachedFsEntry::load_children_from_root_dir(self);
self.root_directory.children = Some(children);
}
_ => {}
}
}
}
let path_segments = path[1..].split("/").collect::<Vec<_>>();
let mut path_index = 0;
let mut current_entry = None;
while path_index < path_segments.len() {
if current_entry
.as_ref()
.unwrap_or(&self.root_directory)
.children
.is_none()
{
let cluster_chain = current_entry
.as_ref()
.unwrap_or(&self.root_directory)
.cluster_chain
.clone();
let loaded_children = CachedFsEntry::load_children(&cluster_chain, self);
current_entry
.as_mut()
.unwrap_or(&mut self.root_directory)
.children = Some(loaded_children);
}
if let Some(child) = current_entry
.as_ref()
.unwrap_or(&self.root_directory)
.find_child_cached(path_segments[path_index])
{
debug!("{child}");
current_entry = Some(child);
path_index += 1;
} else {
return Err(Error::NotFound);
}
}
let entry = current_entry.ok_or(Error::NotFound)?;
if entry.is_directory() {
return Err(Error::Directory);
}
Ok(entry.into())
}
pub fn seek<S: Seekable<FatFs>>(&mut self, to: S) {
let offset = to.to_byte_offset(self);
self.partition.seek(Seek::SetFromStart(offset));
}
}
#[derive(Debug)]
pub struct FileAllocationTable {
cluster_chain: Vec<Cluster>,
}
impl FileAllocationTable {
pub fn from_fat(fat_fs: &mut FatFs) -> Result<FileAllocationTable, uefi::Error> {
let cluster_chain = match fat_fs.fat_type {
FatType::Fat32(_) => Fat32::read_fat(fat_fs)?,
FatType::Fat16(_) => Fat16::read_fat(fat_fs)?,
_ => unimplemented!(),
};
Ok(Self { cluster_chain })
}
pub fn read_cluster_chain(&self, mut start_cluster: Cluster) -> ClusterChain {
let mut buffer = Vec::with_capacity(16);
while start_cluster.is_allocated() {
let next_cluster = self.cluster_chain[start_cluster.masked() as usize];
if next_cluster.is_empty() {
break;
}
buffer.push(start_cluster);
start_cluster = next_cluster;
}
let chain: ClusterChain = buffer.into();
chain
}
}
#[derive(Debug, Clone)]
pub struct CachedFsEntry {
long_name: Option<String>,
short_name: String,
attributes: DirectoryAttribute,
cluster_chain: ClusterChain,
size: u32,
children: Option<Vec<CachedFsEntry>>,
root: bool,
}
impl CachedFsEntry {
pub fn root() -> Self {
Self {
long_name: None,
short_name: "".to_string(),
attributes: DirectoryAttribute::DIRECTORY,
children: None,
cluster_chain: ClusterChain::default(),
size: 0,
root: true,
}
}
pub fn find_child_cached(&self, name: &str) -> Option<CachedFsEntry> {
if let Some(children) = &self.children {
for child in children {
let name_match = if let Some(long_name) = &child.long_name {
long_name == name || child.short_name == name.to_ascii_uppercase()
} else {
child.short_name == name.to_ascii_uppercase()
};
if name_match {
return Some(child.clone());
}
}
}
None
}
fn load_children(cluster_chain: &ClusterChain, fs: &mut FatFs) -> Vec<CachedFsEntry> {
let mut children = Vec::new();
let iter = DirectoryListIter::from_chain(fs, cluster_chain);
let mut lfn_builder = LfnBuilder::new();
for dir in iter {
if dir.is_lfn() {
lfn_builder.add(dir.decode_lfn());
} else {
let mut lfn = None;
if !lfn_builder.is_empty() {
lfn = Some(lfn_builder.build());
lfn_builder = LfnBuilder::new();
}
let mut entry = CachedFsEntry::from(dir, fs);
entry.long_name = lfn;
children.push(entry);
}
}
children
}
fn load_children_from_root_dir(fs: &mut FatFs) -> Vec<CachedFsEntry> {
let mut children = Vec::new();
let iter = DirectoryListIter::from_root(fs);
let mut lfn_builder = LfnBuilder::new();
for dir in iter {
if dir.is_lfn() {
lfn_builder.add(dir.decode_lfn());
} else {
let mut lfn = None;
if !lfn_builder.is_empty() {
lfn = Some(lfn_builder.build());
lfn_builder = LfnBuilder::new();
}
let mut entry = CachedFsEntry::from(dir, fs);
entry.long_name = lfn;
children.push(entry);
}
}
children
}
pub fn is_directory(&self) -> bool {
self.root || self.attributes.is_directory()
}
}
impl CachedFsEntry {
pub fn from(value: DirectoryEntry, fs: &FatFs) -> Self {
Self {
attributes: value.attributes(),
children: None,
cluster_chain: fs
.allocation_table
.as_ref()
.unwrap()
.read_cluster_chain(value.start_cluster()),
long_name: None,
root: false,
short_name: value.name(),
size: value.size(),
}
}
}
impl Display for CachedFsEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!(
"{} {}",
self.attributes,
self.long_name.as_ref().unwrap_or(&self.short_name)
))
}
}
pub struct FileHandle {
chain: ClusterChain,
length: u64,
position: u64,
}
impl FileHandle {
//pub fn seek(&mut self, seek: Seek) {
// match seek {
// Seek::Move(i) => self.position += i.cast_unsigned(),
// Seek::SetFromStart(i) => self.position = i,
// Seek::SetFromEnd(i) => self.position = self.length - i,
// };
// self.position = self.position.clamp(0, self.length);
//}
pub fn read(&mut self, buffer: &mut [u8], fs: &mut FatFs) -> Result<usize, Error<()>> {
let max_copy = ((self.length - self.position) as usize).min(buffer.len());
let cluster_low = self.position as usize / fs.cluster_size_bytes();
let cluster_high = (self.position as usize + max_copy) / fs.cluster_size_bytes();
let needed_clusters = if cluster_low == cluster_high {
&[self.chain[cluster_low]]
} else {
&self.chain[cluster_low..=cluster_high]
};
let cluster_buffer_len = needed_clusters.len() * fs.cluster_size_bytes();
let mut cluster_buffer = Vec::with_capacity(cluster_buffer_len);
cluster_buffer.resize(cluster_buffer_len, 0u8);
for (i, cluster) in needed_clusters.iter().enumerate() {
let slice =
&mut cluster_buffer[i * fs.cluster_size_bytes()..(i + 1) * fs.cluster_size_bytes()];
fs.seek(cluster);
fs.partition.read(slice)?;
}
let cluster_slice_start = self.position as usize % fs.cluster_size_bytes();
buffer[0..max_copy]
.copy_from_slice(&cluster_buffer[cluster_slice_start..cluster_slice_start + max_copy]);
Ok(max_copy)
}
pub fn len(&self) -> usize {
self.length as usize
}
}
impl From<CachedFsEntry> for FileHandle {
fn from(value: CachedFsEntry) -> Self {
Self {
chain: value.cluster_chain.clone(),
length: value.size as u64,
position: 0,
}
}
}
#[repr(transparent)]
#[derive(Debug, Copy, Clone)]
pub struct Cluster(u32);
impl Cluster {
pub fn from_32(value: u32) -> Self {
Cluster(value)
}
pub fn from_16(value: u16) -> Self {
if value >= 0xfff8 {
Cluster(0x0ffffff8)
} else {
Cluster(value as u32)
}
}
//pub fn from_12(_value: u16) -> Self {
// unimplemented!()
//}
pub fn start_sector(&self, fs: &FatFs) -> Sector {
Sector((self.0 - 2) * fs.boot_sector.sectors_per_cluster as u32 + fs.first_data_sector)
}
pub fn is_empty(&self) -> bool {
self.masked() == 0
}
pub fn is_end(&self) -> bool {
self.masked() >= 0x0ffffff8
}
pub fn is_allocated(&self) -> bool {
self.masked() >= 2 && !self.is_end()
}
pub fn masked(&self) -> u32 {
self.0 & 0x0fffffff
}
//pub fn unmasked(&self) -> u32 {
// self.0
//}
}
// TODO: There must be a nicer way to handle both `Cluster` as well as `&Cluster`
impl Seekable<FatFs> for Cluster {
fn to_byte_offset(self, fs: &FatFs) -> u64 {
self.start_sector(fs).byte_offset(fs)
}
}
// TODO: There must be a nicer way to handle both `Cluster` as well as `&Cluster`
impl Seekable<FatFs> for &Cluster {
fn to_byte_offset(self, fs: &FatFs) -> u64 {
self.start_sector(fs).byte_offset(fs)
}
}
impl Display for Cluster {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_allocated() {
f.write_fmt(format_args!("Cluster 0x{:08X}", self.0))
} else {
f.write_str("No cluster")
}
}
}
#[repr(transparent)]
#[derive(Debug, Copy, Clone)]
pub struct Sector(u32);
impl Sector {
pub fn byte_offset(&self, fs: &FatFs) -> u64 {
self.0 as u64 * fs.boot_sector.bytes_per_sector as u64
}
}
// TODO: There must be a nicer way to handle both `Sector` as well as `&Sector`
impl Seekable<FatFs> for Sector {
fn to_byte_offset(self, fs: &FatFs) -> u64 {
self.byte_offset(fs)
}
}
// TODO: There must be a nicer way to handle both `Sector` as well as `&Sector`
impl Seekable<FatFs> for &Sector {
fn to_byte_offset(self, fs: &FatFs) -> u64 {
self.byte_offset(fs)
}
}
impl From<u32> for Sector {
fn from(value: u32) -> Self {
Sector(value)
}
}

51
src/fs/mod.rs Normal file
View File

@@ -0,0 +1,51 @@
use core::fmt::Debug;
pub mod fat;
pub enum Error<T> {
Open,
NotFound,
Directory,
InvalidPath,
Uefi(uefi::Error),
Other(T),
}
impl<T> Debug for Error<T>
where
T: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Open => write!(f, "Open"),
Self::NotFound => write!(f, "NotFound"),
Self::Directory => write!(f, "Directory"),
Self::InvalidPath => write!(f, "InvalidPath"),
Self::Uefi(arg0) => f.debug_tuple("Uefi").field(arg0).finish(),
Self::Other(arg0) => f.debug_tuple("Other").field(arg0).finish(),
}
}
}
#[derive(Debug)]
pub enum BpbError {
BytesPerSector,
SectorsPerCluster,
ReservedSectorCount,
NumFats,
ClusterSize,
TotalSectorCount,
}
impl From<BpbError> for Error<BpbError> {
fn from(value: BpbError) -> Self {
Error::Other(value)
}
}
impl<T> From<uefi::Error> for Error<T> {
fn from(value: uefi::Error) -> Self {
Error::Uefi(value)
}
}
pub trait Seekable<T> {
fn to_byte_offset(self, fs: &T) -> u64;
}

1
src/loader/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub fn load_elf_at() {}

257
src/main.rs Normal file
View File

@@ -0,0 +1,257 @@
#![no_main]
#![no_std]
extern crate alloc;
mod fs;
mod loader;
use alloc::vec::Vec;
use core::{fmt::Display, time::Duration};
use log::{debug, error, warn};
use uefi::{
Identify,
boot::{OpenProtocolParams, ScopedProtocol, SearchType, locate_handle_buffer, open_protocol},
prelude::*,
proto::{
ProtocolPointer,
device_path::{
DevicePath, DevicePathNodeEnum, DevicePathNodeIterator,
media::{HardDrive, PartitionFormat},
messaging::{Atapi, Iscsi, MasterSlave, NvmeNamespace, PrimarySecondary, Sata, Scsi},
},
media::{
block::{BlockIO, BlockIOMedia},
disk::DiskIo,
},
},
};
use crate::fs::fat::FatFs;
enum Seek {
SetFromStart(u64),
SetFromEnd(u64),
Move(i64),
}
#[derive(Debug)]
struct Partition {
part_offset: u64,
media_id: u32,
disk: ScopedProtocol<DiskIo>,
position: u64,
length: u64,
}
impl Partition {
fn read(&mut self, buffer: &mut [u8]) -> Result<u64, uefi::Error> {
if let Err(err) = self.disk.read_disk(self.media_id, self.position, buffer) {
Err(err)
} else {
self.position += buffer.len() as u64;
Ok(buffer.len() as u64)
}
}
fn write(&mut self, buffer: &mut [u8]) -> Result<u64, uefi::Error> {
if let Err(err) = self.disk.write_disk(self.media_id, self.position, buffer) {
Err(err)
} else {
self.position += buffer.len() as u64;
Ok(buffer.len() as u64)
}
}
fn seek(&mut self, seek: Seek) {
match seek {
Seek::Move(i) => self.position += i.cast_unsigned(),
Seek::SetFromStart(i) => self.position = i,
Seek::SetFromEnd(i) => self.position = self.length - i,
};
//info!(
// "Seek to 0x{:08X} (0x{:08X})",
// self.position,
// self.position + self.part_offset
//);
}
}
struct BlockDevice<'a> {
handle: &'a Handle,
media: BlockDeviceMedia<'a>,
raw_media: &'a BlockIOMedia,
hd: &'a HardDrive,
}
impl<'a> BlockDevice<'a> {
pub fn new(
handle: &'a Handle,
raw_media: &'a BlockIOMedia,
path_iter: DevicePathNodeIterator<'a>,
) -> Result<Self, uefi::Error> {
let mut part: Option<&'a HardDrive> = None;
let mut media: Option<BlockDeviceMedia<'a>> = None;
for node in path_iter {
if let Ok(node_enum) = node.as_enum() {
match node_enum {
DevicePathNodeEnum::MediaHardDrive(hd) => part = Some(hd),
_ => {}
}
if let Ok(node_media) = BlockDeviceMedia::try_from(node_enum) {
media = Some(node_media);
}
}
}
if let Some(part) = part.take()
&& let Some(media) = media.take()
{
Ok(BlockDevice {
handle,
media,
raw_media,
hd: part,
})
} else {
Err(uefi::Error::new(Status::NOT_FOUND, ()))
}
}
fn open_disk(&self) -> uefi::Result<Partition> {
open_proto::<DiskIo>(self.handle).map(|proto| Partition {
part_offset: self.hd.partition_start() * self.raw_media.block_size() as u64,
disk: proto,
position: 0,
length: self.hd.partition_size() * self.raw_media.block_size() as u64,
media_id: self.raw_media.media_id(),
})
}
}
impl Display for BlockDevice<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let format = if self.hd.partition_format() == PartitionFormat::MBR {
"MBR"
} else {
"GPT"
};
f.write_fmt(format_args!(
"{}/{format}(0x{:X})",
self.media,
self.hd.partition_number()
))
}
}
#[derive(Debug)]
enum BlockDeviceMedia<'a> {
Atapi(&'a Atapi),
Nvme(&'a NvmeNamespace),
Sata(&'a Sata),
Scsi(&'a Scsi),
Iscsi(&'a Iscsi),
}
impl Display for BlockDeviceMedia<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
BlockDeviceMedia::Sata(sata) => f.write_fmt(format_args!(
"Sata(0x{:X},0x{:X},0x{:X})",
sata.hba_port_number(),
sata.port_multiplier_port_number(),
sata.logical_unit_number()
)),
BlockDeviceMedia::Atapi(atapi) => f.write_fmt(format_args!(
"Atapi({},{},0x{:X})",
if atapi.primary_secondary() == PrimarySecondary::PRIMARY {
"Primary"
} else {
"Secondary"
},
if atapi.master_slave() == MasterSlave::MASTER {
"Master"
} else {
"Slave"
},
atapi.logical_unit_number()
)),
BlockDeviceMedia::Nvme(nvme) => f.write_fmt(format_args!(
"NVMe(0x{:X},0x{:X})",
nvme.namespace_identifier(),
nvme.ieee_extended_unique_identifier()
)),
BlockDeviceMedia::Scsi(_) => f.write_fmt(format_args!("SCSI(???)")),
BlockDeviceMedia::Iscsi(_) => f.write_fmt(format_args!("iSCSI(???)")),
}
}
}
impl<'a> TryFrom<DevicePathNodeEnum<'a>> for BlockDeviceMedia<'a> {
type Error = ();
fn try_from(value: DevicePathNodeEnum<'a>) -> Result<Self, Self::Error> {
match value {
DevicePathNodeEnum::MessagingAtapi(atapi) => Ok(Self::Atapi(atapi)),
DevicePathNodeEnum::MessagingNvmeNamespace(nvme) => Ok(Self::Nvme(nvme)),
DevicePathNodeEnum::MessagingSata(sata) => Ok(Self::Sata(sata)),
DevicePathNodeEnum::MessagingScsi(scsi) => Ok(Self::Scsi(scsi)),
DevicePathNodeEnum::MessagingIscsi(iscsi) => Ok(Self::Iscsi(iscsi)),
_ => Err(()),
}
}
}
fn open_proto<P: ProtocolPointer + ?Sized>(handle: &Handle) -> uefi::Result<ScopedProtocol<P>> {
unsafe {
open_protocol::<P>(
OpenProtocolParams {
handle: *handle,
agent: boot::image_handle(),
controller: None,
},
boot::OpenProtocolAttributes::GetProtocol,
)
}
}
#[entry]
fn main() -> Status {
uefi::helpers::init().unwrap();
let all_block_io_handles =
locate_handle_buffer(SearchType::ByProtocol(&BlockIO::GUID)).unwrap();
for handle in all_block_io_handles.iter() {
if let Ok(block_io) = open_proto::<BlockIO>(handle) {
let media = block_io.media();
if !media.is_logical_partition() || !media.is_media_present() {
continue;
}
if let Ok(device_path) = open_proto::<DevicePath>(handle)
&& let Ok(block_dev) = BlockDevice::new(handle, media, device_path.node_iter())
&& let Ok(disk) = block_dev.open_disk()
{
match FatFs::mount(disk) {
Ok(mut fat) => {
if fat.is_fat32() {
if let Ok(mut handle) = fat.open("/path/to/beemovie.txt") {
let mut buffer = [0u8; 1000];
let read = handle.read(&mut buffer, &mut fat).unwrap();
debug!("Read {read} bytes");
} else {
warn!("FAT32 open failed");
}
} else if fat.is_fat16() {
if let Ok(mut handle) = fat.open("/efi/boot/bootx64.efi") {
let size = handle.len();
let mut buffer = Vec::with_capacity(size);
buffer.resize(size, 0u8);
let read = handle.read(&mut buffer, &mut fat).unwrap();
debug!("Read {read} bytes");
} else {
warn!("FAT16 open failed");
}
}
}
Err(_) => {}
}
}
} else {
error!("Failed to open BlockIO {handle:?}");
}
}
boot::stall(Duration::from_secs(60));
Status::SUCCESS
}