| Split Images |
| ============ |
| |
| Description |
| ----------- |
| |
| The split image mechanism divides a target into two separate images: one |
| capable of image upgrade; the other containing application code. By |
| isolating upgrade functionality to a separate image, the application can |
| support over-the-air upgrade without dedicating flash space to network |
| stack and management code. |
| |
| Concept |
| ------- |
| |
| Mynewt supports three image setups: |
| |
| +-----------+--------------------------------------------+ |
| | Setup | Description | |
| +===========+============================================+ |
| | Single | One large image; upgrade not supported. | |
| +-----------+--------------------------------------------+ |
| | Unified | Two standalone images. | |
| +-----------+--------------------------------------------+ |
| | Split | Kernel in slot 0; application in slot 1. | |
| +-----------+--------------------------------------------+ |
| |
| Each setup has its tradeoffs. The Single setup gives you the most flash |
| space, but doesn't allow you to upgrade after manufacturing. The Unified |
| setup allows for a complete failover in case a bad image gets uploaded, |
| but requires a lot of redundancy in each image, limiting the amount of |
| flash available to the application. The Split setup sits somewhere |
| between these two options. |
| |
| Before exploring the split setup in more detail, it might be helpful to |
| get a basic understanding of the Mynewt boot sequence. The boot process |
| is summarized below. |
| |
| Boot Sequence - Single |
| ^^^^^^^^^^^^^^^^^^^^^^ |
| |
| In the Single setup, there is no boot loader. Instead, the image is |
| placed at address 0. The hardware boots directly into the image code. |
| Upgrade is not possible because there is no boot loader to move an |
| alternate image into place. |
| |
| Boot Sequence - Unified |
| ^^^^^^^^^^^^^^^^^^^^^^^ |
| |
| In the Unified setup, the boot loader is placed at address 0. At |
| startup, the boot loader arranges for the correct image to be in image |
| slot 0, which may entail swapping the contents of the two image slots. |
| Finally, the boot loader jumps to the image in slot 0. |
| |
| Boot Sequence - Split |
| ^^^^^^^^^^^^^^^^^^^^^ |
| |
| The Split setup differs from the other setups mainly in that a target is |
| not fully contained in a single image. Rather, the target is partitioned |
| among two separate images: the *loader*, and the *application*. |
| Functionality is divided among these two images as follows: |
| |
| 1. Loader: |
| |
| - Mynewt OS. |
| - Network stack for connectivity during upgrade e.g. BLE stack. |
| - Anything else required for image upgrade. |
| |
| 2. Application: |
| |
| - Parts of Mynewt not required for image upgrade. |
| - Application-specific code. |
| |
| The loader image serves three purposes: |
| |
| 1. *Second-stage boot loader:* it jumps into the application image at |
| start up. |
| 2. *Image upgrade server:* the user can upgrade to a new loader + |
| application combo, even if an application image is not currently |
| running. |
| 3. *Functionality container:* the application image can directly access |
| all the code present in the loader image |
| |
| From the perspective of the boot loader, a loader image is identical to |
| a plain unified image. What makes a loader image different is a change |
| to its start up sequence: rather than starting the Mynewt OS, it jumps |
| to the application image in slot 1 if one is present. |
| |
| Tutorial |
| -------- |
| |
| Building a Split Image |
| ~~~~~~~~~~~~~~~~~~~~~~ |
| |
| We will be referring to the nRF51dk for examples in this document. Let's |
| take a look at this board's flash map (defined in |
| ``hw/bsp/nrf51dk/bsp.yml``): |
| |
| +---------------------+--------------+-------------+ |
| | Name | Offset | Size (kB) | |
| +=====================+==============+=============+ |
| | Boot loader | 0x00000000 | 16 | |
| +---------------------+--------------+-------------+ |
| | Reboot log | 0x00004000 | 16 | |
| +---------------------+--------------+-------------+ |
| | Image slot 0 | 0x00008000 | 110 | |
| +---------------------+--------------+-------------+ |
| | Image slot 1 | 0x00023800 | 110 | |
| +---------------------+--------------+-------------+ |
| | Image scratch | 0x0003f000 | 2 | |
| +---------------------+--------------+-------------+ |
| | Flash file system | 0x0003f800 | 2 | |
| +---------------------+--------------+-------------+ |
| |
| The application we will be building is |
| `bleprph <../../tutorials/bleprph>`__. First, we create a target to tie |
| our BSP and application together. |
| |
| :: |
| |
| newt target create bleprph-nrf51dk |
| newt target set bleprph-nrf51dk \ |
| app=@apache-mynewt-core/apps/bleprph \ |
| bsp=@apache-mynewt-core/hw/bsp/nrf51dk \ |
| build_profile=optimized \ |
| syscfg=BLE_LL_CFG_FEAT_LE_ENCRYPTION=0:BLE_SM_LEGACY=0 |
| |
| The two syscfg settings disable bluetooth security and keep the code |
| size down. |
| |
| We can verify the target using the ``target show`` command: |
| |
| :: |
| |
| [~/tmp/myproj2]$ newt target show bleprph-nrf51dk |
| targets/bleprph-nrf51dk |
| app=@apache-mynewt-core/apps/bleprph |
| bsp=@apache-mynewt-core/hw/bsp/nrf51dk |
| build_profile=optimized |
| syscfg=BLE_LL_CFG_FEAT_LE_ENCRYPTION=0:BLE_SM_LEGACY=0 |
| |
| Next, build the target: |
| |
| :: |
| |
| [~/tmp/myproj2]$ newt build bleprph-nrf51dk |
| Building target targets/bleprph-nrf51dk |
| # [...] |
| Target successfully built: targets/bleprph-nrf51dk |
| |
| With our target built, we can view a code size breakdown using the |
| ``newt size <target>`` command. In the interest of brevity, the smaller |
| entries are excluded from the below output: |
| |
| :: |
| |
| [~/tmp/myproj2]$ newt size bleprph-nrf51dk |
| Size of Application Image: app |
| FLASH RAM |
| 2446 1533 apps_bleprph.a |
| 1430 104 boot_bootutil.a |
| 1232 0 crypto_mbedtls.a |
| 1107 0 encoding_cborattr.a |
| 2390 0 encoding_tinycbor.a |
| 1764 0 fs_fcb.a |
| 2959 697 hw_drivers_nimble_nrf51.a |
| 4126 108 hw_mcu_nordic_nrf51xxx.a |
| 8161 4049 kernel_os.a |
| 2254 38 libc_baselibc.a |
| 2612 0 libgcc.a |
| 2232 24 mgmt_imgmgr.a |
| 1499 44 mgmt_newtmgr_nmgr_os.a |
| 23918 1930 net_nimble_controller.a |
| 28537 2779 net_nimble_host.a |
| 2207 205 sys_config.a |
| 1074 197 sys_console_full.a |
| 3268 97 sys_log.a |
| 1296 0 time_datetime.a |
| |
| objsize |
| text data bss dec hex filename |
| 105592 1176 13392 120160 1d560 /home/me/tmp/myproj2/bin/targets/bleprph-nrf51dk/app/apps/bleprph/bleprph.elf |
| |
| The full image text size is about 103kB (where 1kB = 1024 bytes). With |
| an image slot size of 110kB, this leaves only about 7kB of flash for |
| additional application code and data. Not good. This is the situation we |
| would be facing if we were using the Unified setup. |
| |
| The Split setup can go a long way in solving our problem. Our unified |
| bleprph image consists mostly of components that get used during an |
| image upgrade. By using the Split setup, we turn the unified image into |
| two separate images: the loader and the application. The functionality |
| related to image upgrade can be delegated to the loader image, freeing |
| up a significant amount of flash in the application image slot. |
| |
| Let's create a new target to use with the Split setup. We designate a |
| target as a split target by setting the ``loader`` variable. In our |
| example, we are going to use ``bleprph`` as the loader, and ``splitty`` |
| as the application. ``bleprph`` makes sense as a loader because it |
| contains the BLE stack and everything else required for an image |
| upgrade. |
| |
| :: |
| |
| newt target create split-nrf51dk |
| newt target set split-nrf51dk \ |
| loader=@apache-mynewt-core/apps/bleprph \ |
| app=@apache-mynewt-core/apps/splitty \ |
| bsp=@apache-mynewt-core/hw/bsp/nrf51dk \ |
| build_profile=optimized \ |
| syscfg=BLE_LL_CFG_FEAT_LE_ENCRYPTION=0:BLE_SM_LEGACY=0 |
| |
| Verify that the target looks correct: |
| |
| :: |
| |
| [~/tmp/myproj2]$ newt target show split-nrf51dk |
| targets/split-nrf51dk |
| app=@apache-mynewt-core/apps/splitty |
| bsp=@apache-mynewt-core/hw/bsp/nrf51dk |
| build_profile=optimized |
| loader=@apache-mynewt-core/apps/bleprph |
| syscfg=BLE_LL_CFG_FEAT_LE_ENCRYPTION=0:BLE_SM_LEGACY=0 |
| |
| Now, let's build the new target: |
| |
| :: |
| |
| [~/tmp/myproj2]$ newt build split-nrf51dk |
| Building target targets/split-nrf51dk |
| # [...] |
| Target successfully built: targets/split-nrf51dk |
| |
| And look at the size breakdown (again, smaller entries are removed): |
| |
| :: |
| |
| [~/tmp/myproj2]$ newt size split-nrf51dk |
| Size of Application Image: app |
| FLASH RAM |
| 3064 251 sys_shell.a |
| |
| objsize |
| text data bss dec hex filename |
| 4680 112 17572 22364 575c /home/me/tmp/myproj2/bin/targets/split-nrf51dk/app/apps/splitty/splitty.elf |
| |
| Size of Loader Image: loader |
| FLASH RAM |
| 2446 1533 apps_bleprph.a |
| 1430 104 boot_bootutil.a |
| 1232 0 crypto_mbedtls.a |
| 1107 0 encoding_cborattr.a |
| 2390 0 encoding_tinycbor.a |
| 1764 0 fs_fcb.a |
| 3168 705 hw_drivers_nimble_nrf51.a |
| 4318 109 hw_mcu_nordic_nrf51xxx.a |
| 8285 4049 kernel_os.a |
| 2274 38 libc_baselibc.a |
| 2612 0 libgcc.a |
| 2232 24 mgmt_imgmgr.a |
| 1491 44 mgmt_newtmgr_nmgr_os.a |
| 25169 1946 net_nimble_controller.a |
| 31397 2827 net_nimble_host.a |
| 2259 205 sys_config.a |
| 1318 202 sys_console_full.a |
| 3424 97 sys_log.a |
| 1053 60 sys_stats.a |
| 1296 0 time_datetime.a |
| |
| objsize |
| text data bss dec hex filename |
| 112020 1180 13460 126660 1eec4 /home/me/tmp/myproj2/bin/targets/split-nrf51dk/loader/apps/bleprph/bleprph.elf |
| |
| The size command shows two sets of output: one for the application, and |
| another for the loader. The addition of the split functionality did make |
| bleprph slightly bigger, but notice how small the application is: 4.5 |
| kB! Where before we only had 7 kB left, now we have 105.5 kB. |
| Furthermore, all the functionality in the loader is available to the |
| application at any time. For example, if your application needs |
| bluetooth functionality, it can use the BLE stack present in the loader |
| instead of containing its own copy. |
| |
| Finally, let's deploy the split image to our nRF51dk board. The |
| procedure here is the same as if we were using the Unified setup, i.e., |
| via either the ``newt load`` or ``newt run`` command. |
| |
| :: |
| |
| [~/repos/mynewt/core]$ newt load split-nrf51dk 0 |
| Loading app image into slot 2 |
| Loading loader image into slot 1 |
| |
| Image Management |
| ~~~~~~~~~~~~~~~~ |
| |
| Retrieve Current State (image list) |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| |
| Image management in the split setup is a bit more complicated than in |
| the unified setup. You can determine a device's image management state |
| with the ``newtmgr image list`` command. Here is how a device responds |
| to this command after our loader + application combo has been deployed: |
| |
| :: |
| |
| [~/tmp/myproj2]$ newtmgr -c A600ANJ1 image list |
| Images: |
| slot=0 |
| version: 0.0.0 |
| bootable: true |
| flags: active confirmed |
| hash: 948f118966f7989628f8f3be28840fd23a200fc219bb72acdfe9096f06c4b39b |
| slot=1 |
| version: 0.0.0 |
| bootable: false |
| flags: |
| hash: 78e4d263eeb5af5635705b7cae026cc184f14aa6c6c59c6e80616035cd2efc8f |
| Split status: matching |
| |
| There are several interesting things about this response: |
| |
| 1. *Two images:* This is expected; we deployed both a loader image and |
| an application image. |
| 2. *bootable flag:* Notice slot 0's bootable flag is set, while slot 1's |
| is not. This tells us that slot 0 contains a loader and slot 1 |
| contains an application. If an image is bootable, it can be booted |
| directly from the boot loader. Non-bootable images can only be |
| started from a loader image. |
| 3. *flags:* Slot 0 is ``active`` and ``confirmed``; none of slot 1's |
| flags are set. The ``active`` flag indicates that the image is |
| currently running; the ``confirmed`` flag indicates that the image |
| will continue to be used on subsequent reboots. Slot 1's lack of |
| enabled flags indicates that the image is not being used at all. |
| 4. *Split status:* The split status field tells you if the loader and |
| application are compatible. A loader + application combo is |
| compatible only if both images were built at the same time with |
| ``newt``. If the loader and application are not compatible, the |
| loader will not boot into the application. |
| |
| Enabling a Split Application |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| By default, the application image in slot 1 is disabled. This is |
| indicated in the ``image list`` response above. When you deploy a loader |
| / application combo to your device, the application image won't actually |
| run. Instead, the loader will act as though an application image is not |
| present and remain in "loader mode". Typically, a device in loader mode |
| simply acts as an image management server, listening for an image |
| upgrade or a request to activate the application image. |
| |
| Use the following command sequence to enable the split application |
| image: |
| |
| 1. Tell device to "test out" the application image on next boot |
| (``newtmgr image test <application-image-hash>``). |
| 2. Reboot device (``newtmgr reset``). |
| 3. Make above change permanent (``newtmgr image confirm``). |
| |
| After the above sequence, a ``newtmgr image list`` command elicits the |
| following response: |
| |
| :: |
| |
| [~/tmp/myproj2]$ newtmgr -c A600ANJ1 image confirm |
| Images: |
| slot=0 |
| version: 0.0.0 |
| bootable: true |
| flags: active confirmed |
| hash: 948f118966f7989628f8f3be28840fd23a200fc219bb72acdfe9096f06c4b39b |
| slot=1 |
| version: 0.0.0 |
| bootable: false |
| flags: active confirmed |
| hash: 78e4d263eeb5af5635705b7cae026cc184f14aa6c6c59c6e80616035cd2efc8f |
| Split status: matching |
| |
| The ``active confirmed`` flags value on both slots indicates that both |
| images are permanently running. |
| |
| Image Upgrade |
| ~~~~~~~~~~~~~ |
| |
| First, let's review of the image upgrade process for the Unified setup. |
| The user upgrades to a new image in this setup with the following steps: |
| |
| Image Upgrade - Unified |
| ^^^^^^^^^^^^^^^^^^^^^^^ |
| |
| 1. Upload new image to slot 1 (``newtmgr image upload <filename>``). |
| 2. Tell device to "test out" the new image on next boot |
| (``newtmgr image test <image-hash>``). |
| 3. Reboot device (``newtmgr reset``). |
| 4. Make new image permanent (``newtmgr image confirm``). |
| |
| Image Upgrade - Split |
| ^^^^^^^^^^^^^^^^^^^^^ |
| |
| The image upgrade process is a bit more complicated in the Split setup. |
| It is more complicated because two images need to be upgraded (loader |
| and application) rather than just one. The split upgrade process is |
| described below: |
| |
| 1. Disable split functionality; we need to deactivate the application |
| image in slot 1 (``newtmgr image test <current-loader-hash>``). |
| 2. Reboot device (``newtmgr reset``). |
| 3. Make above change permanent (``newtmgr image confirm``). |
| 4. Upload new loader to slot 1 (``newtmgr image upload <filename>``). |
| 5. Tell device to "test out" the new loader on next boot |
| (``newtmgr image test <new-loader-hash>``). |
| 6. Reboot device (``newtmgr reset``). |
| 7. Make above change of loader permanent (``newtmgr image confirm``). |
| 8. Upload new application to slot 1 |
| (``newtmgr image upload <filename>``). |
| 9. Tell device to "test out" the new application on next boot |
| (``newtmgr image test <new-application-hash>``). |
| 10. Reboot device (``newtmgr reset``). |
| 11. Make above change of application permanent |
| (``newtmgr image confirm``). |
| |
| When performing this process manually, it may be helpful to use |
| ``image list`` to check the image management state as you go. |
| |
| Syscfg |
| ------ |
| |
| Syscfg is Mynewt's system-wide configuration mechanism. In a split |
| setup, there is a single umbrella syscfg configuration that applies to |
| both the loader and the application. Consequently, overriding a value in |
| an application-only package potentially affects the loader (and |
| vice-versa). |
| |
| Loaders |
| ------- |
| |
| The following applications have been enabled as loaders. You may choose |
| to build your own loader application, and these can serve as samples. |
| |
| - @apache-mynewt-core/apps/slinky |
| - @apache-mynewt-core/apps/bleprph |
| |
| Split Apps |
| ---------- |
| |
| The following applications have been enabled as split applications. If |
| you choose to build your own split application these can serve as |
| samples. Note that slinky can be either a loader image or an application |
| image. |
| |
| - @apache-mynewt-core/apps/slinky |
| - @apache-mynewt-core/apps/splitty |
| |
| Theory of Operation |
| ------------------- |
| |
| A split image is built as follows: |
| |
| First newt builds the application and loader images separately to ensure |
| they are consistent (no errors) and to generate elf files which can |
| inform newt of the symbols used by each part. |
| |
| Then newt collects the symbols used by both application and loader in |
| two ways. It collects the set of symbols from the ``.elf`` files. It |
| also collects all the possible symbols from the ``.a`` files for each |
| application. |
| |
| Newt builds the set of packages that the two applications share. It |
| ensures that all the symbols used in those packages are matching. NOTE: |
| because of features and #ifdefs, its possible for the two package to |
| have symbols that are not the same. In this case newt generates an error |
| and will not build a split image. |
| |
| Then newt creates the list of symbols that the two applications share |
| from those packages (using the .elf files). |
| |
| Newt re-links the loader to ensure all of these symbols are present in |
| the loader application (by forcing the linker to include them in the |
| ``.elf``). |
| |
| Newt builds a special copy of the loader.elf with only these symbols |
| (and the handful of symbols discussed in the linking section above). |
| |
| Finally, newt links the application, replacing the common .a libraries |
| with the special loader.elf image during the link. |