OMAPL138 AIS generator

Posted by Marcus Folkesson on Saturday, July 13, 2024

OMAPL138 AIS Generator

I'm currently working on an old platform based on OMAPL138 from Texas Instruments.

The OMAPL138 SoC is cool in itself, it is a ARM9 core with a C674x DSP coprocessor. My project scope is to modernize the platform to a more recent kernel/bootloader and add support for a few more interfaces due to a new HW revision.

The OMAPL138 is from 2009 but still active. The latest revision for the chip (Rev. J) was released in 2017.

However, there are a few challanges to work with such old platforms when it comes to development tools and documentation. My first challenge is to compile a recent bootloader (U-Boot) and make it boot.

The ROM bootloader is not able to boot a U-Boot file but need a bootscript, so that was the first thing I had to look into.

Bootscript

The OMAP-L132/L138 uses a Appliction Image Script (AIS) in the boot process to determine boot mode, PLL and mDDR/DDR2 configurations. Unfortunately, the application (AISGen) referred to by TI is a Windows application were the most recent version is from 2012.

The application does start with mono but it behaves quite strange and gnaws at my trust issues, especially when nothing seems to boot properly. so I'm better write my own tool for this.

I took the opportunity to learn some Rust while writing a generator for AIS files.

The boot modes I have to support is UART and NAND. UART is used during development and NAND is part of the final product.

All images in this post are clippings from the Using the OMAP-L132/L138 Bootloader [1] reference document.

The AIS format

The AIS format is list of commands that the ROM bootloader interprets and execute in order :

/media/ais-format.png

omapl138-ais-generator

Rust is not my mother tounge, so there is plenty of improvements for sure. But this is what I came up with.

In short, what we have to add a AIS header and then jump the .text section of U-boot and start to execute from there.

Not all ELF segments from U-Boot is necessary, we will settle for .text, .rodata, .data and .u_boot_cmd only.

The full code is available at my github [2].

First we have some constant values. We use readelf to get the addresses for the ELF sections we are interrested in. LOAD and JUMP_CLOSE are AIS commands described later.

1const READELF: &str = "arm-linux-readelf -S";
2const U_BOOT: &str = "u-boot";
3const AIS_NAND_FILE: &str = "u-boot_nand.ais";
4const AIS_UART_FILE: &str = "u-boot_uart.ais";
5
6const LOAD: u32 = 0x58535901;
7const JUMP_CLOSE: u32 = 0x58535906;

The headers for NAND and UART boot mode differs in the EMIFA (external memory interface) setup. Lets create two different headers to keep them apart.

 1    let ais_nand_header = [
 2        hex("41504954"), // Magic word
 3        hex("5853590d"), // Function Execute Command
 4        hex("00020000"), // PLL0 Configuration (Index = 0, Argument Count = 2)
 5        hex("00180001"),
 6        hex("00000205"),
 7        hex("5853590d"), // Function Execute Command
 8        hex("00080003"), // mDDR/DDR2 Controller Configuration (Index = 3, Argument Count = 8)
 9        hex("20020001"),
10        hex("00000002"),
11        hex("000000c4"),
12        hex("02074622"),
13        hex("129129c8"),
14        hex("380f7000"),
15        hex("0000040d"),
16        hex("00000500"),
17        hex("5853590d"), // Function Execute Command
18        hex("00050005"), // EMIFA Async Configuration (Index = 5, Argument Count = 5)
19        hex("00000000"),
20        hex("081221ac"),
21        hex("00000000"),
22        hex("00000000"),
23        hex("00000002"),
24    ];
25
26    let ais_uart_header = [
27        hex("41504954"), // Magic word
28        hex("5853590d"), // Function Execute Command
29        hex("00030006"), // PLL and Clock Configuration(Index= 6, Argument Count= 3)
30        hex("00180001"),
31        hex("00000b05"),
32        hex("00010064"),
33        hex("5853590d"), // Function Execute Command
34        hex("00080003"), // mDDR/DDR2 Controller Configuration (Index = 3, Argument Count = 8)
35        hex("18010101"),
36        hex("00000002"),
37        hex("00000003"),
38        hex("06074622"),
39        hex("20da3291"),
40        hex("42948941"),
41        hex("00000492"),
42        hex("00000500"),
43    ];

Lookup the .text, .rodata, .data and .u_boot_cmd sections using readelf. Look through all sections and save the start address for the .text segment.

Load the segments using the LOAD command:

/media/ais-load.png
 1    let regex = Regex::new(r"\.(text|rodata|data|u_boot_cmd)\s+PROGBITS\s+([0-9a-f]+)\s([0-9a-f]+)\s([0-9a-f]+)\s").unwrap();
 2
 3    for line in reader.lines() {
 4        let line = line.expect("Failed to read line from readelf output");
 5
 6        if let Some(captures) = regex.captures(&line) {
 7            let name = &captures[1];
 8            let addr = u32::from_str_radix(&captures[2], 16).unwrap();
 9            let offset = u32::from_str_radix(&captures[3], 16).unwrap();
10            let size = u32::from_str_radix(&captures[4], 16).unwrap();
11
12            if name == "text" {
13                start_addr = addr;
14            }
15
16            println!("{} {:x} {:x} {:x}", name, addr, offset, size);
17
18            ais_nand_file.write_all(&LOAD.to_le_bytes()).expect("Unable to write to AIS NAND file");
19            ais_nand_file.write_all(&addr.to_le_bytes()).expect("Unable to write to AIS NAND file");
20
21            ais_uart_file.write_all(&LOAD.to_le_bytes()).expect("Unable to write to AIS UART file");
22            ais_uart_file.write_all(&addr.to_le_bytes()).expect("Unable to write to AIS UART file");
23
24            let rest = (4 - (size % 4)) % 4;
25            println!("Rest is {} for section .{}", rest, name);
26
27            ais_nand_file.write_all(&(size + rest).to_le_bytes()).expect("Unable to write to AIS NAND file");
28            ais_uart_file.write_all(&(size + rest).to_le_bytes()).expect("Unable to write to AIS UART file");
29
30            let mut section = vec![0; size as usize];
31            u_boot_file.seek(SeekFrom::Start(offset as u64)).expect("Couldn't seek to offset");
32            u_boot_file.read_exact(&mut section).expect("Couldn't read section");
33
34            ais_nand_file.write_all(&section).expect("Unable to write to AIS NAND file");
35            ais_uart_file.write_all(&section).expect("Unable to write to AIS UART file");
36
37            let padding = vec![0; rest as usize];
38            ais_nand_file.write_all(&padding).expect("Unable to write to AIS NAND file");
39            ais_uart_file.write_all(&padding).expect("Unable to write to AIS UART file");
40        }
41    }

Finally instruct the ROM bootloader to jump to the .text section with the Jump & Close Command:

/media/ais-jump-and-close.png
1    ais_nand_file.write_all(&JUMP_CLOSE.to_le_bytes()).expect("Unable to write to AIS NAND file");
2    ais_nand_file.write_all(&start_addr.to_le_bytes()).expect("Unable to write to AIS NAND file");
3
4    ais_uart_file.write_all(&JUMP_CLOSE.to_le_bytes()).expect("Unable to write to AIS UART file");
5    ais_uart_file.write_all(&start_addr.to_le_bytes()).expect("Unable to write to AIS UART file");