elfconvert: add support for kelvin workloads

- make the pad field a file type (one of: application, springbok, kelvin)
- add -f kelvin to signal a kelvin workload
- add -f springbok and treat -f model as springbok for backwards compat
- add -f application

Newly formatted files are backward compatible.

Change-Id: I80d05ac7d9926f659126a4a901a50ddd715b4d63
diff --git a/misc/elfconvert/src/convert.rs b/misc/elfconvert/src/convert.rs
index df79e5d..8603d3a 100644
--- a/misc/elfconvert/src/convert.rs
+++ b/misc/elfconvert/src/convert.rs
@@ -25,7 +25,7 @@
  *    fsize: u32    Length of data that follows (bytes)
  *    msize: u32    Size of memory region (bytes)
  *    align: u32    Section data alignment (bytes)
- *    pad: u32      <ignore, reserved for future use>
+ *    ftype: u32    File type (see below)
  *    crc32: u32    CRC32 of the data that follows
  *
  * Section header flags (mostly from ELF program section):
@@ -68,9 +68,6 @@
     }
 }
 
-/// Maximum memory size for TCM, used for model loading
-const TCM_SIZE: usize = 0x1000000;
-
 // TODO(sleffler): use runtime defs
 const MAGIC: u64 = 0x0405_1957_1014_1955;
 
@@ -79,6 +76,10 @@
 const SECTION_EXEC: u32 = 0x4; // Data are executable
 const SECTION_ENTRY: u32 = 0x8; // Entry point valid
 
+const FTYPE_APPLICATION: u32 = 0x0405_1957; // CantripOS application
+const FTYPE_SPRINGBOK: u32 = 0x1014_1955; // Springbok model
+const FTYPE_KELVIN: u32 = 0x0124_1998; // Kelvin model
+
 /// Given a set of ELF flags, convert into a set of flags for a section
 /// recognizable by the CantripOS ProcessManager's loader.
 fn to_section_flags(elf_flags: xmas_elf::program::Flags) -> u32 {
@@ -107,12 +108,13 @@
     fsize: u32,
     msize: u32,
     align: u32, // Section data alignment (bytes)
-    pad: u32,
+    ftype: u32, // File type
     crc32: u32,
 }
 
 impl SectionHeader {
     fn new(
+        ftype: u32,
         vaddr: u64,
         flags: u32,
         entry: u64,
@@ -133,7 +135,7 @@
             fsize: (fsize as u32).to_be(),
             msize: (msize as u32).to_be(),
             align: (align as u32).to_be(),
-            pad: 0,
+            ftype: ftype.to_be(),
             crc32: crc.to_be(),
         }
     }
@@ -152,31 +154,6 @@
     }
 }
 
-/// The virtualized address of each loadable WMMU section (go/shodan-vc-memory).
-const TEXT_VADDR: u64 = 0x80000000;
-const CONST_DATA_VADDR: u64 = 0x81000000;
-const MODEL_OUTPUT_VADDR: u64 = 0x82000000;
-const STATIC_DATA_VADDR: u64 = 0x83000000;
-const TEMP_DATA_VADDR: u64 = 0x85000000;
-
-/// Helpful conversion from a ModelSection to a string name of that address
-fn vaddr_as_str(vaddr: u64) -> &'static str {
-    match vaddr {
-        TEXT_VADDR => ".text",
-        CONST_DATA_VADDR => ".data",
-        MODEL_OUTPUT_VADDR => ".model_output",
-        STATIC_DATA_VADDR => ".static",
-        TEMP_DATA_VADDR => ".bss",
-        _ => "<unknown>",
-    }
-}
-
-/// Predicate to determine if a given vaddr is loadable in the TCM
-fn is_vaddr_in_tcm(vaddr: u64) -> bool {
-    matches!(vaddr, TEXT_VADDR | CONST_DATA_VADDR | MODEL_OUTPUT_VADDR | STATIC_DATA_VADDR
-             | TEMP_DATA_VADDR)
-}
-
 #[derive(Debug)]
 pub enum ConversionError {
     SegmentOutsideTCM(u64),
@@ -211,14 +188,47 @@
     }
 }
 
-/// Converts an ELF-format ML model into CantripOS' loadable format.
+pub mod springbok {
+/// Springbok support.
+
+use super::*;
+
+/// Maximum memory size for TCM, used for model loading
+const TCM_SIZE: usize = 0x1000000;
+
+/// The virtualized address of each loadable WMMU section (go/shodan-vc-memory).
+const TEXT_VADDR: u64 = 0x80000000;
+const CONST_DATA_VADDR: u64 = 0x81000000;
+const MODEL_OUTPUT_VADDR: u64 = 0x82000000;
+const STATIC_DATA_VADDR: u64 = 0x83000000;
+const TEMP_DATA_VADDR: u64 = 0x85000000;
+
+/// Helpful conversion from a ModelSection to a string name of that address
+fn vaddr_as_str(vaddr: u64) -> &'static str {
+    match vaddr {
+        TEXT_VADDR => ".text",
+        CONST_DATA_VADDR => ".data",
+        MODEL_OUTPUT_VADDR => ".model_output",
+        STATIC_DATA_VADDR => ".static",
+        TEMP_DATA_VADDR => ".bss",
+        _ => "<unknown>",
+    }
+}
+
+/// Predicate to determine if a given vaddr is loadable in the TCM
+fn is_vaddr_in_tcm(vaddr: u64) -> bool {
+    matches!(vaddr, TEXT_VADDR | CONST_DATA_VADDR | MODEL_OUTPUT_VADDR | STATIC_DATA_VADDR
+             | TEMP_DATA_VADDR)
+}
+
+/// Converts an ELF-format ML (Springbok) model into CantripOS' loadable format.
 ///
 /// Returns the number of bytes written.
 pub fn model(elf: &ElfFile, output_file: &mut File) -> Result<u64, ConversionError> {
     let entry = elf.header.pt2.entry_point();
     info!("ELF entry point is {:#x}", entry);
 
-    for seg in elf.program_iter().filter(is_load_type) {
+    for seg in elf.program_iter().filter(super::is_load_type) {
         let fsize = seg.file_size() as usize;
         let msize = seg.mem_size() as usize;
         let align = seg.align() as usize;
@@ -256,6 +266,7 @@
             let mut digest = crc32::Digest::new(crc32::IEEE);
             digest.write(bytes);
             let section = SectionHeader::new(
+                FTYPE_SPRINGBOK,
                 seg.virtual_addr(),
                 flags,
                 entry,
@@ -279,6 +290,84 @@
     Ok(output_file.stream_position()?)
 }
 
+} // springbok
+
+pub mod kelvin {
+/// Kelvin support.
+
+use super::*;
+
+/// Maximum memory size for TCM, used for model loading
+const TCM_SIZE: usize = 0x400000;
+
+/// Converts an ELF-format Kelvin workload into CantripOS' loadable format.
+///
+/// Returns the number of bytes written.
+pub fn model(elf: &ElfFile, output_file: &mut File) -> Result<u64, ConversionError> {
+    let entry = elf.header.pt2.entry_point() as usize;
+    info!("ELF entry point is {:#x}", entry);
+
+    for seg in elf.program_iter().filter(super::is_load_type) {
+        let fsize = seg.file_size() as usize;
+        let msize = seg.mem_size() as usize;
+        let align = seg.align() as usize;
+        let mut flags = to_section_flags(seg.flags());
+        let vaddr = seg.virtual_addr() as usize;
+        let segment_name = "LOAD"; // XXX
+
+        debug!("Processing new section [vaddr={:#x}, fsize={:#x}, msize={:#x}, align={:#x}, flags={:#b}]",
+               vaddr, fsize, msize, align, flags);
+
+        if let SegmentData::Undefined(bytes) = seg.get_data(elf)? {
+            if vaddr + msize >= TCM_SIZE {
+                return Err(ConversionError::SegmentOutsideTCM(vaddr as u64));
+            }
+
+            if vaddr <= entry &&  entry < vaddr + msize {
+                debug!(
+                    "Marking segment {} as entrypoint [vaddr={:#x}, entry={:#x}]",
+                    segment_name, vaddr, entry
+                );
+                flags |= SECTION_ENTRY;
+            }
+
+            debug!(
+                "Processing {} segment [len={}, addr={:#x}, msize={}]",
+                segment_name,
+                bytes.len(),
+                vaddr,
+                msize
+            );
+
+            let mut digest = crc32::Digest::new(crc32::IEEE);
+            digest.write(bytes);
+            let section = SectionHeader::new(
+                FTYPE_KELVIN,
+                seg.virtual_addr(),
+                flags,
+                entry as u64,
+                align,
+                digest.sum32(),
+                fsize,
+                msize,
+            );
+
+            section.write(output_file, bytes)?;
+            info!(
+                "Wrote {} segment of {} bytes at {:#x} msize {}",
+                segment_name,
+                bytes.len(),
+                seg.virtual_addr(),
+                msize
+            );
+        }
+    }
+
+    Ok(output_file.stream_position()?)
+}
+
+} // kelvin
+
 /// Converts an ELF-format application binary into CantripOS' loadable format.
 ///
 /// Returns the number of bytes written.
@@ -309,6 +398,7 @@
             let mut digest = crc32::Digest::new(crc32::IEEE);
             digest.write(bytes);
             let header = SectionHeader::new(
+                FTYPE_APPLICATION,
                 seg.virtual_addr(),
                 flags,
                 entry,
diff --git a/misc/elfconvert/src/main.rs b/misc/elfconvert/src/main.rs
index a30a1b8..3e46e30 100644
--- a/misc/elfconvert/src/main.rs
+++ b/misc/elfconvert/src/main.rs
@@ -28,7 +28,9 @@
 #[derive(clap::ValueEnum, Clone)]
 enum FileType {
     App = 0,
-    Model,
+    Model,  // Springbok for backwards compat
+    Springbok,  // Springbok IREE output
+    Kelvin,  // Kelvin workload
 }
 
 #[derive(Parser)]
@@ -84,8 +86,12 @@
             let count = convert::application(&elf, &mut output_file)?;
             info!("Wrote {} bytes.", count);
         }
-        FileType::Model => {
-            let count = convert::model(&elf, &mut output_file)?;
+        FileType::Model | FileType::Springbok => {
+            let count = convert::springbok::model(&elf, &mut output_file)?;
+            info!("Wrote {} bytes.", count);
+        }
+        FileType::Kelvin => {
+            let count = convert::kelvin::model(&elf, &mut output_file)?;
             info!("Wrote {} bytes.", count);
         }
     }