This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

BEAGLEBK: How to boot BBB from eMMC

Part Number: BEAGLEBK
Other Parts Discussed in Thread: SYSBIOS, TPS65217

In response to a question on how I did this, I summarized it here.  Hopefully this is enough to get someone started.

The older StarterWare is incompatible with the new TI-RTOS, as you have probably figured out. The main reason is that StarterWare has an assembler file which is the entry point for the apps. And you set that entry point in the linker. If you have worked with StarterWare, you see that the init steps will assign the stack pointer for the different MCU modes, resets prediction, perhaps sets the MMU, erases all of the BSS section, sets the base of the interrupt vector table and does a jump to “main”.  (I have successfully used some StarterWare libs in TI_RTOS as long as they don't mess with interrupts, or rely on some of this initialization.)

TI-RTOS sets “c_int00” as the entry point, and good luck finding the source code to that. Thanks to the CFG file, figuring out how to tweak these setting is a nightmare. 

So, how does booting work? It’s not quite the same as the “app” startup.

The ROM inside the MCU is designed to look for and read a filed called MLO (among many other method of boot strapping) The ROM knows how to read an eMMC or SD card, assumes they appear as a DOS format, and looks for a program called MLO in the root directory. It loads that program, and then uses the “TI Image Header” just like you to have to do for your “app”. Except at this stage, only the SRAM memory is setup. Which is a different address, and smaller amount of memory. (in case anyone is wondering, the "image" that is provided in "prebuilt-sdcards" is not a partitioned MBR like a hard disk, it is more like a floppy - but the ROM knows how to read either one)

Step “zero”… I wrote a small “blinker” program, non-TI_RTOS to keep the size down, set the linker script to exist in SRAM memory (memory location 0x402F0400), use the TIimage program to set the TI Header to that location, named it MLO, and put it on an SD card. Booted with the BOOT button pushed, and it flashed my LED.  I also had to reduce the stack size and heap to fit in the smaller memory. Way cool. 

This proves that ANY file named MLO will be loaded by the MCU ROM, as long as it can fit in that small SRAM area.

Step “zero point one”. YOU WILL NEED A LINUX PC. I put a Linux image on the SD card and booted it, you can make the image with in Windows. Use a minimal install that is just a console. When booted I fdisked the eMMC, and reformatted it. Note that /dev/mmcblk1 is always the OTHER memory. If you boot from eMMC, it’s the SD card, if you boot from the SD card, its eMMC. I won’t describe all the commands, but essentially these are the Linux steps:

fdisk /dev/mmcblk1
“d” delete the partitions
“c” create a new partition full size
“t” change the partition type to “b”
“a” make it bootable
“w” write it

Step “zero point two” – How do you move a program to the eMMC?

  • Take that SD card to a Linux PC, and mount the card.
  • Copy the program to the /home/Debian directory.
  • Unmount and remove the card, boot the Beagle with it.
  • Mount the emmc with mount /dev/mmcblk1p1 mount_point
  • Copy the app to it with cp app mount_point
  • Unmount the eMMC 

I did this to move the MLO program from above in step zero, and rebooted the BBB. It read the MLO program from eMMC and started flashing. Way cooler.

Step “zero point two” is tedious, but the only way to get an app into the eMMC for now. And every iteration of the new bootloader I wrote I had to do it that way… Also, whatever test program you have, like  “LED flasher" stored as “app” will need to be on the eMMC as well. Since the goal is to have the MLO bootloader read from the eMMC.

(Until I realized I could put the bootloader I was developing on the SD card, and it would still read the “app” from eMMC - LOL)

Next up…

The original StarterWare package has a lib that can read the SD card. But I was not able to figure out how to read the eMMC device in StarterWare. However, the new PDK 1.0.0.7 has a MMC lib that you can use to read and write from eMMC.

There is also sample that shows you how to read from a FATFS system. It presumes that it is reading from the SD card. Nice of them to provide an example, and no doc on how they stitched it together. There is a structure which you are required to provide,

FATFS_DrvFxnTable FATFS_drvFxnTable = {
   MMCSD_close,
   MMCSD_control,
   MMCSD_init,
   MMCSD_open,
   MMCSD_write,
   MMCSD_read
};

These actually provide the lower level interface the FATFS uses to access the SD card. Well, nice...  these are library calls into the MMC library, and the function signatures are the same. So, I replaced the MMCSD_open call with my own:

FATFS_DrvFxnTable FATFS_drvFxnTable = {
   MMCSD_close,
   MMCSD_control,
   MMCSD_init,
   custom_MMCSD_open,   // MMCSD_open,
   MMCSD_write,
   MMCSD_read
};

The MMCSD_Open function takes an index to indicate which MMC device to read. FATFS appears to have that hardcoded to the SD card way deep in it’s bowels, and no way to change it. (If you are able to rebuild the PDK or any portions of it, you are a better person than me… out of the box, it fails when I follow the instruction to rebuild it – TI response “Yeah, I’ve seen that before something about such-and-such…” Of course, no one fixed it.)

My replacement call to open the MMCSD card, I just changed the "index" to be eMMC:

MMCSD_Error custom_MMCSD_open( uint32_t index, MMCSD_Params params, MMCSD_Handle *handle) {
   return MMCSD_open(MMCSD_INSTANCE_EMMC, params, handle);
}

Okay, so far so good. I created a NON-SYSBIOS project, essentially and “empty project with main.c”, and it also provided me with a “startup_ARMCA8.s” assembler file. I could go through it and tweak it, similar to the StarterWare version of the init.S file. Then I commenced to manually set up linking to the libs in the PDK rather than the older StarterWare. I did this one at a time, every time the linker complained I had to find the symbol that was missing, and “fill in the hole”. NOTE: When linking to libs in the PDK, if there is a “non-os” version, use it, as the other ones rely on TI_RTOS and SysBios functionality to be present.

I unraveled the bootloader from StarterWare (not from the PDK). I took the portions, and started bringing in #defines, functions, etc… It accomplishes the process in these steps (I didn’t reverse engineer all of them, but I basically figured out what they do):

  • Called SetupBoard() which gets the chip version which is used to decide some settings across all possible boards. This could have been hard coded since I am only using the BBB.  It also calls “board_init()”
  • Called GetBoardInfo() which uses I2C to read the EEPROM… the EEPROM has the BBB version info in it. (Beagle says don’t alter the EEPROM, as you could erase these settings. Then the bootloader wouldn’t know what type of board it’s on – That’s why the PC board is defaulted to write protected).
  • Called SetupTPS65217()… the TPS65217 is a power control chip on the BBB, and is connected via I2C. This chip sets the operating voltages for the CPU, memory, and other things. It is described in the Beagle Tech manual. I just used the same settings that were in the bootloader code from StarterWare.
  • Then it set the PLL for the MCU, and the preiphs.
  • The last setup thing it did was set the DDR PLL frequency and initialize the DDR. Here’s where it got ugly…

The BBB has DDR 3, but the old StarterWare bootloader only understood DDR2 settings for regular BeagleBone (none for “Beagle Black”). Using the older values from the StarterWare worked, but the memory was not running at full speed. So I had to unravel the NEW bootloader from the PDK, and figure out what settings to put in the EMIF registers. Poor doc, lots of “You don’t have to change it” (except it is being changed), and the code is a twisted nightmare (Sets a value from a structure member, which is initialized somewhere else, using #defines that are defined yet somewhere else… The kind of code that someone finishes and feels proud of themselves for how “encapsulated” everything is, and then doesn’t document anything, leaving the next person pulling their hair out.)

  • Next… Call the MMCbootload() function which I migrated from the Bootloader code, which was easy to understand, and I rewrote some in my process. It uses the FATFS library to open the MMC (which is now directed to the eMMC memory thanks to change in the FATFS_drvFxnTable structure described above), reads the first 8 bytes, so it knows the size and the target load address. Sets a pointer to that address, and copies the program up there. It uses an internal 512 byte buffer that it reads into from the FATFS, and moves it to the upper address (typically 0x8000 0000) because the entire “app” program may not fit in the small SRAM memory.
  • Last step, it does a jump to the target address (0x8000 0000). Now the “app” is running.

The biggest nightmare was figuring out the DDR timings. None of it is documented, there is no engineering application notes about what the registers really do, or how the DDR interface works. And every time I would connect with the JTAG to debug, the JTAG initialization would set all those timings for me. So when tethered to a JTAG debugger it worked fine! But when booting directly, it wouldn’t work because the DDR wasn’t working, and the memory it was writing to and then reading from was essentially garbage.  I've got lists and lists of values where I stopped the MCU with the debugger and read the DDR and EMIF registers for the old boot loader, the newer PDK boot loader, the JTAG settings, and what the built in LINUX used  (from another BBB where I didn't erase Linux from the eMMC).  Then, of course there are registers you can't read...  SMH

Next project... to copy over a program. Well, easy enough except the MCU ROM can only open a program named MLO… so now I have different MLO programs. One to boot from eMMC as I just described, and one to read “app” from an SD card, and move it to eMMC. I have two different configuration builds based on #define… in the project settings. So to move a file into eMMC, I copy a different MLO to my SD card.

int main(void) {
   SetupBoard();
   GetBoardInfo();
   SetupTPS65217();

#ifdef INSTALLER
   MMCFileCopy();
#else
   MMCbootload();
#endif

   return 0;

The MMCFileCopy() is simple enough… It loads the “app” from the SD card by setting

/* Point to the SD card first... */
     FATFS_drvFxnTable.openDrvFxn = MMCSD_open;

and load the entire program. just like we would be booting the “app” . But it never calls the entry point. Instead it closes the SD card, and opens the eMMC:

   FATFS_close( fatfsHandle);
   fatfsHandle = NULL;
       /* Now point to the open for the eMMC */
   FATFS_drvFxnTable.openDrvFxn = custom_MMCSD_open;

Now, it opens/creates a file named “app” and saves the memory back to the eMMC, based on the original size.

The pretty much summarizes how I did it. I did sprinkle lots of GPIOPinWrite(GPIO_INSTANCE_ADDRESS, GPIO_PIN_4, GPIO_PIN_HIGH); functions all over the code in order to give me feedback on the progress.

I hope this helps someone, where I had to figure it out on my own...