elfloader: properly handle PHDRs that span flash pages

Rework the previous hack to properly handle PHDRs that span flash
pages and require loading. This should support any ELF image up to the
maximum configured number of PHDRs (30); in particular this handles the
cheriot-rtos test suite firmware image that has 27 PHDRs..

Change-Id: I7be17df12903f9099c9b40f26abb18b0553748fc
diff --git a/capsules/src/elfloader_capsule.rs b/capsules/src/elfloader_capsule.rs
index 0e80753..7f8faf3 100644
--- a/capsules/src/elfloader_capsule.rs
+++ b/capsules/src/elfloader_capsule.rs
@@ -55,7 +55,10 @@
 struct SEL4State {
     // State for current file being loaded.
     load_offset: u32, // File offset
-    phdrs: [Option<Elf32Phdr>; 20], // ELF phdrs
+    // Buffering used when a PHDR spans flash pages
+    partial_phdr: Option<(usize, [u8; core::mem::size_of::<Elf32Phdr>()])>,
+    // NB: 30 is enough for cheriot-rtos' test suite
+    phdrs: [Option<Elf32Phdr>; 30], // ELF phdrs
 
     // Saved state for the "kernel" image.
     kernel_entry_point: u32,
@@ -66,6 +69,27 @@
     capdl_phys_min: u32,
     capdl_phys_max: u32,
 }
+impl SEL4State {
+    fn reset_phdrs(&mut self) {
+        self.partial_phdr = None;
+        self.phdrs = SEL4State::default().phdrs;
+    }
+    fn add_phdr(&mut self, phdr: Elf32Phdr) {
+        let mut next_slot: Option<usize> = None;
+        for i in 0..self.phdrs.len() {
+            if self.phdrs[i] == None {
+                next_slot = Some(i);
+                break;
+            }
+        }
+//dprintf!("[{:?}] {:#08x?}\r\n", next_slot, phdr);
+        if let Some(slot) = next_slot {
+            self.phdrs[slot] = Some(phdr);
+        } else {
+            panic!("Too many ELF program headers");
+        }
+    }
+}
 
 #[derive(Clone, Copy, PartialEq)]
 enum ElfLoaderState {
@@ -303,7 +327,7 @@
     //   headers must be recorded below (see |load_elf_header_callback|).
     fn load_elf_headers(&self) {
         let mut sel4_state = self.sel4_state.get();
-        sel4_state.phdrs = SEL4State::default().phdrs; // Reset shared phdrs
+        sel4_state.reset_phdrs(); // Reset shared phdr state
         let cursor = sel4_state.load_offset;
         assert!(cursor  != INVALID_LOAD_OFFSET);
         self.sel4_state.replace(sel4_state);
@@ -357,45 +381,76 @@
     // headers have been read this also records per-file data based on the
     // full set of headers (that may be clobbered if another file is read).
     fn load_program_header_table_callback(&self) {
+        let mut have_partial_phdr: bool = false;
+        let mut new_page_offset: usize = 0;
         self.read_page.map(|page| {
             let mut_page = page.as_mut();
             match self.state.get() {
-                ElfLoaderState::LoadProgramHeaderTable(_, page_offset, _, _index) => {
-                    let page_bytes = &mut_page[page_offset..];
-                    // NB: assume word-alignment so it's safe to read the p_type field
-                    if Elf32Phdr::get_type(page_bytes) == PT_LOAD {
-                        // NB: we do not handle a PHDR spanning flash pages
-                        assert!(page_offset + core::mem::size_of::<Elf32Phdr>() <= self.page_len as usize,
-                                "ELF PHDR spans flash pages");
-                        let phdr = Elf32Phdr::from_bytes(page_bytes);
-//dprintf!("[{}] {:#08x?}\r\n", index, &phdr);
-                        let mut sel4_state = self.sel4_state.get();
-                        let mut next_slot: Option<usize> = None;
-                        for i in 0..sel4_state.phdrs.len() {
-                            if sel4_state.phdrs[i] == None {
-                                next_slot = Some(i);
-                                break;
+                ElfLoaderState::LoadProgramHeaderTable(_, mut page_offset, count, mut index) => {
+                    let mut sel4_state = self.sel4_state.get();
+                    if let Some((part_len, mut part)) = sel4_state.partial_phdr {
+                        // There is a partial PHDR, append the remainder and process.
+                        // NB: PHDR is a fixed size that cannot span >2 flash pages
+                        //   so there must be room for the 2nd part
+                        let remaining_bytes = core::mem::size_of::<Elf32Phdr>() - part_len;
+                        let buffer = &mut part[..];
+                        assert!(page_offset == 0);
+                        buffer[part_len..].copy_from_slice(&mut_page[..remaining_bytes]);
+                        sel4_state.add_phdr(Elf32Phdr::from_bytes(buffer));
+                        sel4_state.partial_phdr = None;
+
+                        // Advance to next PHDR.
+                        page_offset += remaining_bytes;
+                        index += 1;
+                    }
+                    if index < count {
+                        let page_bytes = &mut_page[page_offset..];
+                        // NB: assume word-alignment so it's safe to read the p_type field
+                        if Elf32Phdr::get_type(page_bytes) == PT_LOAD {
+                            if page_bytes.len() < core::mem::size_of::<Elf32Phdr>() {
+                                // This PHDR spans flash pages; stash what we have and arrange
+                                // for the next flash page to be read without advancing the PHDR
+                                // state. Note the logic below depends on there not being
+                                // back-to-back spanning headers (which cannot happen since a
+                                // header is 32 bytes and a page is 512 bytes).
+                                let mut buffer = [0u8; core::mem::size_of::<Elf32Phdr>()];
+                                buffer[..page_bytes.len()].copy_from_slice(page_bytes);
+                                sel4_state.partial_phdr = Some((page_bytes.len(), buffer));
+                                have_partial_phdr = true;
+                            } else {
+                                // We have the entire PHDR; process without using the copy buffer.
+                                sel4_state.add_phdr(Elf32Phdr::from_bytes(page_bytes));
                             }
                         }
-                        if let Some(slot) = next_slot {
-                            sel4_state.phdrs[slot] = Some(phdr);
-                        } else {
-                            panic!("Too many ELF program headers");
-                        }
-                        self.sel4_state.replace(sel4_state);
                     }
+                    self.sel4_state.replace(sel4_state);
+                    new_page_offset = page_offset;
                 }
                 _ => unreachable!(),
             };
         });
         match self.state.get() {
-            ElfLoaderState::LoadProgramHeaderTable(cursor, page_offset, count, index) => {
-                if index + 1 < count {
-                    let mut new_page_offset = page_offset + core::mem::size_of::<Elf32Phdr>();
+            ElfLoaderState::LoadProgramHeaderTable(cursor, _, count, index) => {
+                if have_partial_phdr {
+                    // We just recorded a partial PHDR; read the next page without
+                    // advancing the PHDR index.
+                    let new_cursor = cursor + self.page_len;
+                    self.state.set(ElfLoaderState::LoadProgramHeaderTable(
+                        new_cursor,
+                        0,
+                        count,
+                        index,
+                    ));
+                    self.read_page(new_cursor);
+                } else if index + 1 < count {
+                    // We just recorded a complete PHDR; advance to the next PHDR
+                    // and read the next page required.
+                    new_page_offset += core::mem::size_of::<Elf32Phdr>();
                     let new_cursor = if new_page_offset >= self.page_len as usize {
                         new_page_offset %= self.page_len as usize;
                         cursor + self.page_len
                     } else {
+                        // NB: this reads the same flash page for multiple PHDRs
                         cursor
                     };
                     self.state.set(ElfLoaderState::LoadProgramHeaderTable(
@@ -404,7 +459,6 @@
                         count,
                         index + 1,
                     ));
-                    // NB: this may read the same flash page for multiple PHDRs
                     self.read_page(new_cursor);
                 } else {
                     // Record state derived from phdrs before they get clobbered.