1use super::block_device::BlockDevice;
12
13const SECTOR_SIZE: usize = 512;
18const FAT32_EOC: u32 = 0x0FFF_FFF8;
19const DIR_ENTRY_SIZE: usize = 32;
20
21const ATTR_READ_ONLY: u8 = 0x01;
22const ATTR_HIDDEN: u8 = 0x02;
23const ATTR_SYSTEM: u8 = 0x04;
24const ATTR_VOLUME_ID: u8 = 0x08;
25const ATTR_DIRECTORY: u8 = 0x10;
26const ATTR_ARCHIVE: u8 = 0x20;
27const ATTR_LFN: u8 = 0x0F;
28
29const MAX_CLUSTER_CHAIN: u32 = 1_048_576;
31
32const MAX_PATH_DEPTH: usize = 16;
34
35#[derive(Debug, Clone)]
40struct Bpb {
41 bytes_per_sector: u16,
42 sectors_per_cluster: u8,
43 reserved_sectors: u16,
44 num_fats: u8,
45 sectors_per_fat_32: u32,
46 root_cluster: u32,
47 total_sectors_32: u32,
48}
49
50impl Bpb {
51 fn from_bytes(buf: &[u8; SECTOR_SIZE]) -> Option<Self> {
59 if buf[0x1FE] != 0x55 || buf[0x1FF] != 0xAA {
61 return None;
62 }
63
64 if &buf[0x52..0x58] != b"FAT32 " {
66 return None;
67 }
68
69 let bytes_per_sector = u16::from_le_bytes([buf[0x0B], buf[0x0C]]);
70 let sectors_per_cluster = buf[0x0D];
71 let reserved_sectors = u16::from_le_bytes([buf[0x0E], buf[0x0F]]);
72 let num_fats = buf[0x10];
73 let sectors_per_fat_32 = u32::from_le_bytes([buf[0x24], buf[0x25], buf[0x26], buf[0x27]]);
74 let root_cluster = u32::from_le_bytes([buf[0x2C], buf[0x2D], buf[0x2E], buf[0x2F]]);
75 let total_sectors_32 = u32::from_le_bytes([buf[0x20], buf[0x21], buf[0x22], buf[0x23]]);
76
77 if bytes_per_sector == 0 || sectors_per_cluster == 0 {
78 return None;
79 }
80 if bytes_per_sector != SECTOR_SIZE as u16 {
81 return None;
82 }
83 if num_fats == 0 || sectors_per_fat_32 == 0 {
84 return None;
85 }
86 if root_cluster < 2 {
87 return None;
88 }
89
90 Some(Bpb {
91 bytes_per_sector,
92 sectors_per_cluster,
93 reserved_sectors,
94 num_fats,
95 sectors_per_fat_32,
96 root_cluster,
97 total_sectors_32,
98 })
99 }
100
101 fn bytes_per_cluster(&self) -> u32 {
102 self.bytes_per_sector as u32 * self.sectors_per_cluster as u32
103 }
104
105 fn fat_start_sector(&self) -> u32 {
106 self.reserved_sectors as u32
107 }
108
109 fn data_start_sector(&self) -> u32 {
110 self.fat_start_sector() + self.sectors_per_fat_32 * self.num_fats as u32
111 }
112
113 fn cluster_to_lba(&self, cluster: u32) -> Option<u32> {
116 if cluster < 2 {
117 return None;
118 }
119 let offset = (cluster as u64 - 2) * self.sectors_per_cluster as u64;
120 let lba = self.data_start_sector() as u64 + offset;
121 if lba > u32::MAX as u64 {
122 return None;
123 }
124 Some(lba as u32)
125 }
126
127 fn max_cluster(&self) -> u32 {
129 let data_sectors = self
130 .total_sectors_32
131 .saturating_sub(self.data_start_sector());
132 let clusters = data_sectors / self.sectors_per_cluster as u32;
133 2 + clusters
134 }
135}
136
137#[derive(Debug, Clone)]
142pub struct DirEntry {
143 name: DirEntryName,
144 attr: u8,
145 cluster: u32,
146 size: u32,
147}
148
149impl DirEntry {
150 fn is_dir(&self) -> bool {
151 self.attr & ATTR_DIRECTORY != 0
152 }
153
154 fn is_file(&self) -> bool {
155 self.attr & ATTR_DIRECTORY == 0
156 }
157}
158
159#[derive(Debug, Clone)]
160enum DirEntryName {
161 Short([u8; 11]),
162 Long(heapless::String<255>),
163}
164
165struct ShortNameDisplay {
167 buf: [u8; 12],
168 len: usize,
169}
170
171impl ShortNameDisplay {
172 fn from_sfn(sfn: &[u8; 11]) -> Self {
173 let mut display = ShortNameDisplay {
174 buf: [0u8; 12],
175 len: 0,
176 };
177 let mut pos = 0;
178
179 let mut i = 0;
181 while i < 8 && sfn[i] != b' ' && sfn[i] != 0 {
182 display.buf[pos] = sfn[i].to_ascii_lowercase();
183 pos += 1;
184 i += 1;
185 }
186
187 if sfn[8] != b' ' && sfn[8] != 0 {
189 display.buf[pos] = b'.';
190 pos += 1;
191 let mut i = 8;
192 while i < 11 && sfn[i] != b' ' && sfn[i] != 0 {
193 display.buf[pos] = sfn[i].to_ascii_lowercase();
194 pos += 1;
195 i += 1;
196 }
197 }
198
199 display.len = pos;
200 display
201 }
202
203 fn as_str(&self) -> &str {
204 core::str::from_utf8(&self.buf[..self.len]).unwrap_or("???")
205 }
206}
207
208impl DirEntryName {
209 fn display_name(&self) -> ShortNameDisplay {
210 match self {
211 DirEntryName::Long(s) => {
212 let mut d = ShortNameDisplay {
216 buf: [0u8; 12],
217 len: 0,
218 };
219 let bytes = s.as_bytes();
220 let len = bytes.len().min(12);
221 d.buf[..len].copy_from_slice(&bytes[..len]);
222 d.len = len;
223 d
224 }
225 DirEntryName::Short(sfn) => ShortNameDisplay::from_sfn(sfn),
226 }
227 }
228
229 fn matches_short(&self, upper_name: &[u8; 11]) -> bool {
230 match self {
231 DirEntryName::Short(sfn) => sfn == upper_name,
232 DirEntryName::Long(_) => false,
233 }
234 }
235
236 fn as_lfn_str(&self) -> Option<&str> {
237 match self {
238 DirEntryName::Long(s) => Some(s.as_str()),
239 DirEntryName::Short(_) => None,
240 }
241 }
242}
243
244fn make_sfn(name: &str) -> [u8; 11] {
252 let mut sfn = [b' '; 11];
253 let bytes = name.as_bytes();
254
255 let dot_pos = if bytes.len() > 1 && bytes[0] != b'.' {
258 bytes[1..].iter().rposition(|&b| b == b'.').map(|p| p + 1)
259 } else {
260 None
261 };
262
263 let (base, ext) = if let Some(dp) = dot_pos {
264 (&bytes[..dp], &bytes[dp + 1..])
265 } else {
266 (bytes, &b""[..])
267 };
268
269 let mut pos = 0;
271 let mut i = 0;
272 while pos < 8 && i < base.len() {
273 let c = base[i];
274 if c != b' ' {
275 sfn[pos] = c.to_ascii_uppercase();
276 pos += 1;
277 }
278 i += 1;
279 }
280
281 pos = 8;
283 i = 0;
284 while pos < 11 && i < ext.len() {
285 let c = ext[i];
286 if c != b' ' {
287 sfn[pos] = c.to_ascii_uppercase();
288 pos += 1;
289 }
290 i += 1;
291 }
292
293 if sfn[0] == 0xE5 {
295 sfn[0] = 0x05;
296 }
297
298 sfn
299}
300
301struct ClusterChainIter<'a, B: BlockDevice> {
307 fs: &'a mut FatFs<'a, B>,
308 next_cluster: Option<u32>,
309 iterations: u32,
310}
311
312impl<'a, B: BlockDevice> ClusterChainIter<'a, B> {
313 fn new(fs: &'a mut FatFs<'a, B>, start_cluster: u32) -> Self {
314 Self {
315 fs,
316 next_cluster: if start_cluster >= 2 {
317 Some(start_cluster)
318 } else {
319 None
320 },
321 iterations: 0,
322 }
323 }
324}
325
326impl<'a, B: BlockDevice> Iterator for ClusterChainIter<'a, B> {
327 type Item = u32;
328
329 fn next(&mut self) -> Option<u32> {
330 let current = self.next_cluster?;
331 self.iterations += 1;
332 if self.iterations > MAX_CLUSTER_CHAIN {
333 crate::serial_println!("[fat32] Cluster chain exceeds maximum length (cycle?)");
334 self.next_cluster = None;
335 return None;
336 }
337
338 match self.fs.next_cluster(current) {
339 Some(next) if next >= 2 => {
340 self.next_cluster = Some(next);
341 Some(current)
342 }
343 Some(_) => {
344 self.next_cluster = None;
345 Some(current)
346 }
347 None => {
348 self.next_cluster = None;
349 None
350 }
351 }
352 }
353}
354
355pub struct FatFs<'a, B: BlockDevice> {
360 block_dev: &'a mut B,
361 bpb: Bpb,
362}
363
364impl<'a, B: BlockDevice> FatFs<'a, B> {
365 pub fn new(block_dev: &'a mut B) -> Option<Self> {
367 let kernel_block_size = block_dev.block_size() as usize;
368 let mut sector_buf = [0u8; SECTOR_SIZE];
369 Self::read_sector_at(block_dev, 0, &mut sector_buf, kernel_block_size)?;
370
371 let bpb = Bpb::from_bytes(§or_buf)?;
372
373 crate::serial_println!(
374 "[fat32] BPB: {} bytes/sector, {} sec/cluster, {} FATs, root_cluster={}",
375 bpb.bytes_per_sector,
376 bpb.sectors_per_cluster,
377 bpb.num_fats,
378 bpb.root_cluster,
379 );
380
381 Some(FatFs { block_dev, bpb })
382 }
383
384 fn read_sector_at(
386 dev: &mut B,
387 fat32_sector: u32,
388 buf: &mut [u8; SECTOR_SIZE],
389 kernel_block_size: usize,
390 ) -> Option<()> {
391 if kernel_block_size >= SECTOR_SIZE {
392 let mut block_buf = [0u8; 4096];
394 dev.read_block(fat32_sector as u64, &mut block_buf[..kernel_block_size])
395 .ok()?;
396 let n = SECTOR_SIZE.min(kernel_block_size);
397 buf[..n].copy_from_slice(&block_buf[..n]);
398 } else {
399 let blocks_per_sector = SECTOR_SIZE / kernel_block_size;
401 let base_lba = fat32_sector as u64 * blocks_per_sector as u64;
402 let mut block_buf = [0u8; 4096];
403 for i in 0..blocks_per_sector {
404 dev.read_block(base_lba + i as u64, &mut block_buf[..kernel_block_size])
405 .ok()?;
406 let dst = i * kernel_block_size;
407 buf[dst..dst + kernel_block_size].copy_from_slice(&block_buf[..kernel_block_size]);
408 }
409 }
410 Some(())
411 }
412
413 fn read_sector(&mut self, fat32_sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> Option<()> {
414 let kbs = self.block_dev.block_size() as usize;
415 Self::read_sector_at(self.block_dev, fat32_sector, buf, kbs)
416 }
417
418 fn next_cluster(&mut self, cluster: u32) -> Option<u32> {
420 if cluster < 2 {
421 return None;
422 }
423
424 let fat_offset = cluster as usize * 4;
425 let fat_sector = self.bpb.fat_start_sector() + (fat_offset / SECTOR_SIZE) as u32;
426 let entry_offset = fat_offset % SECTOR_SIZE;
427
428 if entry_offset + 4 > SECTOR_SIZE {
430 crate::serial_println!("[fat32] FAT entry crosses sector boundary (corrupt?)");
431 return None;
432 }
433
434 let mut sector_buf = [0u8; SECTOR_SIZE];
435 self.read_sector(fat_sector, &mut sector_buf)?;
436
437 let entry = u32::from_le_bytes([
438 sector_buf[entry_offset],
439 sector_buf[entry_offset + 1],
440 sector_buf[entry_offset + 2],
441 sector_buf[entry_offset + 3],
442 ]) & 0x0FFF_FFFF;
443
444 if entry >= FAT32_EOC {
445 Some(0)
446 } else {
447 Some(entry)
448 }
449 }
450
451 fn read_chain(
456 &mut self,
457 start_cluster: u32,
458 clusters_to_skip: u32,
459 max_bytes: Option<usize>,
460 ) -> Option<alloc::vec::Vec<u8>> {
461 let spc = self.bpb.sectors_per_cluster as u32;
462 let mut data = alloc::vec::Vec::new();
463
464 if let Some(max) = max_bytes {
466 data.reserve(max);
467 }
468
469 let mut current = start_cluster;
470
471 for _ in 0..clusters_to_skip {
473 current = self.next_cluster(current)?;
474 if current < 2 {
475 return Some(data);
476 }
477 }
478
479 let mut iterations = 0u32;
480 while current >= 2 {
481 iterations += 1;
482 if iterations > MAX_CLUSTER_CHAIN {
483 crate::serial_println!("[fat32] Cluster chain too long (cycle?)");
484 return None;
485 }
486
487 let cluster_lba = self.bpb.cluster_to_lba(current)?;
488
489 for s in 0..spc {
490 let mut sector_buf = [0u8; SECTOR_SIZE];
491 self.read_sector(cluster_lba + s, &mut sector_buf)?;
492 data.extend_from_slice(§or_buf);
493
494 if let Some(max) = max_bytes {
495 if data.len() >= max {
496 data.truncate(max);
497 return Some(data);
498 }
499 }
500 }
501
502 current = self.next_cluster(current)?;
503 }
504
505 Some(data)
506 }
507
508 fn read_cluster_chain_alloc(&mut self, cluster: u32) -> Option<alloc::vec::Vec<u8>> {
510 self.read_chain(cluster, 0, None)
511 }
512
513 fn read_dir_entries(&mut self, cluster: u32) -> Option<alloc::vec::Vec<DirEntry>> {
515 let dir_data = self.read_cluster_chain_alloc(cluster)?;
516 self.parse_dir_entries(&dir_data)
517 }
518
519 fn parse_dir_entries(&self, data: &[u8]) -> Option<alloc::vec::Vec<DirEntry>> {
527 let mut entries = alloc::vec::Vec::new();
528 let mut lfn_chars: heapless::Vec<char, 255> = heapless::Vec::new();
529 let mut lfn_checksum: u8 = 0;
530 let mut i = 0;
531
532 while i + DIR_ENTRY_SIZE <= data.len() {
533 let raw = &data[i..i + DIR_ENTRY_SIZE];
534 i += DIR_ENTRY_SIZE;
535
536 if raw[0] == 0x00 {
538 break;
539 }
540
541 if raw[0] == 0xE5 {
543 lfn_chars.clear();
544 continue;
545 }
546
547 let attr = raw[11];
548
549 if attr == ATTR_LFN {
551 let seq = raw[0];
552 let is_last = (seq & 0x40) != 0;
553 let _index = seq & 0x3F;
554 let chksum = raw[13];
555
556 if is_last {
557 lfn_chars.clear();
558 lfn_checksum = chksum;
559 } else if lfn_checksum != chksum {
560 lfn_chars.clear();
562 continue;
563 }
564
565 let positions = [1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30];
570 for &pos in &positions {
571 let lo = raw[pos] as u16;
572 let hi = raw[pos + 1] as u16;
573 let ch16 = (hi << 8) | lo;
574 if ch16 == 0x0000 || ch16 == 0xFFFF {
575 continue;
576 }
577 if let Some(c) = char::from_u32(ch16 as u32) {
578 let _ = lfn_chars.push(c);
582 }
583 }
584 continue;
585 }
586
587 let cluster_hi = u16::from_le_bytes([raw[20], raw[21]]) as u32;
589 let cluster_lo = u16::from_le_bytes([raw[26], raw[27]]) as u32;
590 let cluster = (cluster_hi << 16) | cluster_lo;
591 let size = u32::from_le_bytes([raw[28], raw[29], raw[30], raw[31]]);
592
593 let name = if !lfn_chars.is_empty() {
594 let mut lfn_str = heapless::String::<255>::new();
598 for &c in lfn_chars.iter().rev() {
599 let _ = lfn_str.push(c);
600 }
601 lfn_chars.clear();
602 DirEntryName::Long(lfn_str)
603 } else {
604 let mut sfn = [0u8; 11];
605 sfn.copy_from_slice(&raw[0..11]);
606 DirEntryName::Short(sfn)
607 };
608
609 if attr & ATTR_VOLUME_ID != 0 {
611 continue;
612 }
613
614 entries.push(DirEntry {
615 name,
616 attr,
617 cluster,
618 size,
619 });
620 }
621
622 Some(entries)
623 }
624
625 fn resolve_path(&mut self, path: &str) -> Option<DirEntry> {
627 let path = path.trim_start_matches('/');
628 if path.is_empty() {
629 return Some(DirEntry {
630 name: DirEntryName::Short(*b"/ "),
631 attr: ATTR_DIRECTORY,
632 cluster: self.bpb.root_cluster,
633 size: 0,
634 });
635 }
636
637 let mut current_cluster = self.bpb.root_cluster;
638
639 let mut parts: heapless::Vec<&str, MAX_PATH_DEPTH> = heapless::Vec::new();
641 for part in path.split('/') {
642 if !part.is_empty() {
643 if parts.push(part).is_err() {
644 crate::serial_println!("[fat32] Path too deep: {}", path);
645 return None;
646 }
647 }
648 }
649
650 for (idx, part) in parts.iter().enumerate() {
651 let dir_entries = self.read_dir_entries(current_cluster)?;
652 let target_sfn = make_sfn(part);
653
654 let mut found = None;
655 for entry in &dir_entries {
656 if entry.name.matches_short(&target_sfn) {
657 found = Some(entry.clone());
658 break;
659 }
660 if let Some(lfn) = entry.name.as_lfn_str() {
662 if lfn.eq_ignore_ascii_case(part) {
663 found = Some(entry.clone());
664 break;
665 }
666 }
667 let display = entry.name.display_name();
669 if display.as_str().eq_ignore_ascii_case(part) {
670 found = Some(entry.clone());
671 break;
672 }
673 }
674
675 let entry = found?;
676
677 if idx == parts.len() - 1 {
678 return Some(entry);
679 }
680
681 if !entry.is_dir() {
682 return None;
683 }
684 current_cluster = entry.cluster;
685 }
686
687 None
688 }
689
690 pub fn read_file(&mut self, path: &str) -> Option<alloc::vec::Vec<u8>> {
692 let entry = self.resolve_path(path)?;
693 if entry.is_dir() {
694 return None;
695 }
696 if entry.cluster == 0 && entry.size == 0 {
697 return Some(alloc::vec::Vec::new());
698 }
699
700 let data = self.read_cluster_chain_alloc(entry.cluster)?;
701 let file_size = entry.size as usize;
702
703 if data.len() < file_size {
705 crate::serial_println!(
706 "[fat32] Warning: file {} truncated (read {} bytes, expected {})",
707 path,
708 data.len(),
709 file_size
710 );
711 }
712
713 let mut result = data;
714 result.truncate(file_size);
715 Some(result)
716 }
717
718 pub fn list_dir(&mut self, path: &str) -> Option<alloc::vec::Vec<DirEntry>> {
720 let entry = self.resolve_path(path)?;
721 if !entry.is_dir() {
722 return None;
723 }
724 self.read_dir_entries(entry.cluster)
725 }
726}
727
728fn is_valid_elf(data: &[u8]) -> bool {
740 if data.len() < 16 {
741 return false;
742 }
743
744 if &data[0..4] != b"\x7fELF" {
746 return false;
747 }
748
749 let class = data[4];
751 if class != 1 && class != 2 {
752 return false;
753 }
754
755 let data_enc = data[5];
757 if data_enc != 1 && data_enc != 2 {
758 return false;
759 }
760
761 let elf_type = if class == 2 {
763 u16::from_le_bytes([data[16], data[17]])
764 } else {
765 u16::from_le_bytes([data[16], data[17]])
766 };
767 if elf_type == 0 || elf_type > 4 {
768 return false;
769 }
770
771 true
772}
773
774#[derive(Debug, Clone, Copy)]
779pub struct ModuleInfo {
780 pub base: u64,
781 pub size: u64,
782}
783
784pub struct BootModules {
785 pub init: Option<ModuleInfo>,
786 pub console_admin: Option<ModuleInfo>,
787 pub strate_net: Option<ModuleInfo>,
788 pub strate_bus: Option<ModuleInfo>,
789 pub fs_ext4: Option<ModuleInfo>,
790 pub strate_fs_ramfs: Option<ModuleInfo>,
791 pub strate_wasm: Option<ModuleInfo>,
792 pub strate_webrtc: Option<ModuleInfo>,
793 pub dhcp_client: Option<ModuleInfo>,
794 pub ping: Option<ModuleInfo>,
795 pub telnetd: Option<ModuleInfo>,
796 pub udp_tool: Option<ModuleInfo>,
797 pub web_admin: Option<ModuleInfo>,
798}
799
800impl Default for BootModules {
801 fn default() -> Self {
802 Self {
803 init: None,
804 console_admin: None,
805 strate_net: None,
806 strate_bus: None,
807 fs_ext4: None,
808 strate_fs_ramfs: None,
809 strate_wasm: None,
810 strate_webrtc: None,
811 dhcp_client: None,
812 ping: None,
813 telnetd: None,
814 udp_tool: None,
815 web_admin: None,
816 }
817 }
818}
819
820const MODULE_DIR: &str = "/modules";
821
822const MODULE_NAMES: &[(&str, fn(&mut BootModules) -> &mut Option<ModuleInfo>)] = &[
823 ("init", |m| &mut m.init),
824 ("console_admin", |m| &mut m.console_admin),
825 ("strate_net", |m| &mut m.strate_net),
826 ("strate_bus", |m| &mut m.strate_bus),
827 ("fs_ext4", |m| &mut m.fs_ext4),
828 ("strate_fs_ramfs", |m| &mut m.strate_fs_ramfs),
829 ("strate_wasm", |m| &mut m.strate_wasm),
830 ("strate_webrtc", |m| &mut m.strate_webrtc),
831 ("dhcp_client", |m| &mut m.dhcp_client),
832 ("ping", |m| &mut m.ping),
833 ("telnetd", |m| &mut m.telnetd),
834 ("udp_tool", |m| &mut m.udp_tool),
835 ("web_admin", |m| &mut m.web_admin),
836];
837
838pub fn load_all_modules<B: BlockDevice>(block_dev: &mut B) -> BootModules {
840 let mut modules = BootModules::default();
841
842 let mut fs = match FatFs::new(block_dev) {
843 Some(fs) => fs,
844 None => {
845 crate::serial_println!("[fat32] Failed to mount filesystem");
846 return modules;
847 }
848 };
849
850 crate::serial_println!("[fat32] FAT32 filesystem mounted");
851
852 let entries = match fs.list_dir(MODULE_DIR) {
853 Some(e) => e,
854 None => {
855 crate::serial_println!("[fat32] No {} directory", MODULE_DIR);
856 return modules;
857 }
858 };
859
860 for entry in entries {
861 if !entry.is_file() {
862 continue;
863 }
864
865 let display = entry.name.display_name();
867 let name_str = display.as_str();
868
869 let stem = name_str.strip_suffix(".elf").unwrap_or(name_str);
870
871 for &(mod_name, setter) in MODULE_NAMES {
872 if stem.eq_ignore_ascii_case(mod_name) {
873 let full_path: heapless::String<64> = {
874 let mut s = heapless::String::new();
875 let _ = s.push_str(MODULE_DIR);
876 let _ = s.push('/');
877 let _ = s.push_str(name_str);
878 s
879 };
880
881 match load_elf_from_fat(&mut fs, &full_path, &entry) {
882 Some(info) => {
883 crate::serial_println!(
884 "[fat32] Loaded {} at {:#x} ({} bytes)",
885 full_path,
886 info.base,
887 info.size
888 );
889 *setter(&mut modules) = Some(info);
890 }
891 None => {
892 crate::serial_println!("[fat32] Failed to load {}", full_path);
893 }
894 }
895 break;
896 }
897 }
898 }
899
900 modules
901}
902
903pub fn load_module<B: BlockDevice>(block_dev: &mut B, path: &str) -> Option<ModuleInfo> {
905 let mut fs = FatFs::new(block_dev)?;
906 let entry = fs.resolve_path(path)?;
907 load_elf_from_fat(&mut fs, path, &entry)
908}
909
910fn load_elf_from_fat<B: BlockDevice>(
914 fs: &mut FatFs<'_, B>,
915 path: &str,
916 entry: &DirEntry,
917) -> Option<ModuleInfo> {
918 if entry.size == 0 {
919 crate::serial_println!("[fat32] {} is empty", path);
920 return None;
921 }
922
923 let file_data = fs.read_cluster_chain_alloc(entry.cluster)?;
925 let file_size = entry.size as usize;
926
927 if file_data.len() < file_size {
929 crate::serial_println!(
930 "[fat32] {} truncated (read {} bytes, expected {})",
931 path,
932 file_size,
933 file_size
934 );
935 return None;
936 }
937
938 if !is_valid_elf(&file_data) {
940 crate::serial_println!("[fat32] {} is not a valid ELF", path);
941 return None;
942 }
943
944 let layout = core::alloc::Layout::from_size_align(file_size, 4096).ok()?;
948 let ptr = unsafe { alloc::alloc::alloc(layout) };
949 if ptr.is_null() {
950 crate::serial_println!("[fat32] Alloc failed: {} bytes for {}", file_size, path);
951 return None;
952 }
953
954 let buf = unsafe { core::slice::from_raw_parts_mut(ptr, file_size) };
957 buf.copy_from_slice(&file_data[..file_size]);
958
959 Some(ModuleInfo {
960 base: ptr as u64,
961 size: file_size as u64,
962 })
963}