# Firmware configuration file for the LinkIt Smart

#
# Firmware metadata
#

# All of these can be overriden using environment variables of the same name.
#
#  Run 'fwup -m' to query values in a .fw file.
#  Use 'fw_printenv' to query values on the target.
#
# These are used by Nerves libraries to introspect.
define(NERVES_FW_PRODUCT, "Nerves Firmware")
define(NERVES_FW_DESCRIPTION, "")
define(NERVES_FW_VERSION, "${NERVES_SDK_VERSION}")
define(NERVES_FW_PLATFORM, "linkit")
define(NERVES_FW_ARCHITECTURE, "arm")
define(NERVES_FW_AUTHOR, "The Nerves Team")

define(NERVES_FW_DEVPATH, "/dev/mmcblk0")
define(NERVES_FW_APPLICATION_PART0_DEVPATH, "/dev/mmcblk0p2") # Linux part number is 1-based
define(NERVES_FW_APPLICATION_PART0_FSTYPE, "ext4")
define(NERVES_FW_APPLICATION_PART0_TARGET, "/root")

# Default paths if not specified via the commandline
define(ROOTFS, "${NERVES_SYSTEM}/images/rootfs.squashfs")

# This configuration file will create an image that has an MBR and the
# following 3 partitions:
#
# +----------------------------+
# | MBR                        |
# +----------------------------+
# | p0*: Rootfs A (squashfs)   |
# +----------------------------+
# | p0*: Rootfs B (squashfs)   |
# +----------------------------+
# | p1: Application (ext4)     |
# +----------------------------+
# | p2: Unused                 |
# +----------------------------+
# | p3: Unused                 |
# +----------------------------+
#
# The p0 partition points to whichever of Rootfs A or B that
# is active.

define(UBOOT_ENV_OFFSET, 16)
define(UBOOT_ENV_COUNT, 16)  # 8 KB

# Let the rootfs have room to grow up to 64 MiB and align
# it to the nearest 1 MB boundary
define(ROOTFS_A_PART_OFFSET, 2048)
define(ROOTFS_A_PART_COUNT, 131072)
define-eval(ROOTFS_B_PART_OFFSET, "${ROOTFS_A_PART_OFFSET} + ${ROOTFS_A_PART_COUNT}")
define(ROOTFS_B_PART_COUNT, ${ROOTFS_A_PART_COUNT})

# Application partition
# NOTE: Keep the total amount used under 1.78 GiB so that
#       everything fits in the "2 GB" eMMC.
define-eval(APP_PART_OFFSET, "${ROOTFS_B_PART_OFFSET} + ${ROOTFS_B_PART_COUNT}")
define(APP_PART_COUNT, 1048576)

# Firmware archive metadata
meta-product = ${NERVES_FW_PRODUCT}
meta-description = ${NERVES_FW_DESCRIPTION}
meta-version = ${NERVES_FW_VERSION}
meta-platform = ${NERVES_FW_PLATFORM}
meta-architecture = ${NERVES_FW_ARCHITECTURE}
meta-author = ${NERVES_FW_AUTHOR}

file-resource rootfs.img {
    host-path = ${ROOTFS}

    # Error out if the rootfs size exceeds the partition size
    assert-size-lte = ${ROOTFS_A_PART_COUNT}
}
# See comment below in write-kernel
#file-resource uImage {
#    host-path = "${NERVES_SYSTEM}/images/uImage"
#    assert-size-lte = 30720  # 15 MiB
#}

mbr mbr-a {
    partition 0 {
        block-offset = ${ROOTFS_A_PART_OFFSET}
        block-count = ${ROOTFS_A_PART_COUNT}
        type = 0x83 # Linux
    }
    partition 1 {
        block-offset = ${APP_PART_OFFSET}
        block-count = ${APP_PART_COUNT}
        type = 0x83 # Linux
    }
    # partition 2 and 3 are unused
}

mbr mbr-b {
    partition 0 {
        block-offset = ${ROOTFS_B_PART_OFFSET}
        block-count = ${ROOTFS_B_PART_COUNT}
        type = 0x83 # Linux
    }
    partition 1 {
        block-offset = ${APP_PART_OFFSET}
        block-count = ${APP_PART_COUNT}
        type = 0x83 # Linux
    }
    # partition 2 and 3 are unused
}

# Location where installed firmware information is stored.
# While this is called "u-boot", u-boot isn't involved in this
# setup. It just provides a convenient key/value store format.
uboot-environment uboot-env {
    block-offset = ${UBOOT_ENV_OFFSET}
    block-count = ${UBOOT_ENV_COUNT}
}

# This firmware task writes everything to the destination media
task complete {
    on-init {
        mbr_write(mbr-a)

        uboot_clearenv(uboot-env)
        uboot_setenv(uboot-env, "nerves_fw_active", "a")
        uboot_setenv(uboot-env, "nerves_fw_devpath", ${NERVES_FW_DEVPATH})
        uboot_setenv(uboot-env, "a.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
        uboot_setenv(uboot-env, "a.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
        uboot_setenv(uboot-env, "a.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
        uboot_setenv(uboot-env, "a.nerves_fw_product", ${NERVES_FW_PRODUCT})
        uboot_setenv(uboot-env, "a.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
        uboot_setenv(uboot-env, "a.nerves_fw_version", ${NERVES_FW_VERSION})
        uboot_setenv(uboot-env, "a.nerves_fw_platform", ${NERVES_FW_PLATFORM})
        uboot_setenv(uboot-env, "a.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
        uboot_setenv(uboot-env, "a.nerves_fw_author", ${NERVES_FW_AUTHOR})
    }

    on-resource rootfs.img {
        # write to the first rootfs partition
        raw_write(${ROOTFS_A_PART_OFFSET})
    }

    on-finish {
        # Clear out any old data in the B partition that might be mistaken for
        # a file system. This is mostly to avoid confusion in humans when
        # reprogramming SDCards with unknown contents.
        raw_memset(${ROOTFS_B_PART_OFFSET}, 256, 0xff)

        # Invalidate the application data partition so that it is guaranteed to
        # trigger the corrupt filesystem detection code on first boot and get
        # formatted. If this isn't done and an old SDCard is reused, the
        # application data could be in a weird state.
        raw_memset(${APP_PART_OFFSET}, 256, 0xff)
    }
}

task upgrade.a {
    # This task upgrades the A partition
    require-partition-offset(0, ${ROOTFS_B_PART_OFFSET})

    on-init {
        info("Upgrading the A partition")

        # Clear some firmware information just in case this update gets
        # interrupted midway.
        uboot_unsetenv(uboot-env, "nerves_fw_a_version")
    }

    on-resource rootfs.img { raw_write(${ROOTFS_A_PART_OFFSET}) }

    on-finish {
        # Update firmware metadata
        uboot_setenv(uboot-env, "a.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
        uboot_setenv(uboot-env, "a.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
        uboot_setenv(uboot-env, "a.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
        uboot_setenv(uboot-env, "a.nerves_fw_product", ${NERVES_FW_PRODUCT})
        uboot_setenv(uboot-env, "a.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
        uboot_setenv(uboot-env, "a.nerves_fw_version", ${NERVES_FW_VERSION})
        uboot_setenv(uboot-env, "a.nerves_fw_platform", ${NERVES_FW_PLATFORM})
        uboot_setenv(uboot-env, "a.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
        uboot_setenv(uboot-env, "a.nerves_fw_author", ${NERVES_FW_AUTHOR})

	# Switch over to boot the new firmware
        uboot_setenv(uboot-env, "nerves_fw_active", "a")
        mbr_write(mbr-a)
    }

    on-error {
    }
}

task upgrade.b {
    # This task upgrades the B partition
    require-partition-offset(0, ${ROOTFS_A_PART_OFFSET})

    on-init {
        info("Upgrading the B partition")

        # Clear some firmware information just in case this update gets
        # interrupted midway.
        uboot_unsetenv(uboot-env, "nerves_fw_b_version")
    }

    on-resource rootfs.img { raw_write(${ROOTFS_B_PART_OFFSET}) }

    on-finish {
        # Update firmware metadata
        uboot_setenv(uboot-env, "b.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
        uboot_setenv(uboot-env, "b.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
        uboot_setenv(uboot-env, "b.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
        uboot_setenv(uboot-env, "b.nerves_fw_product", ${NERVES_FW_PRODUCT})
        uboot_setenv(uboot-env, "b.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
        uboot_setenv(uboot-env, "b.nerves_fw_version", ${NERVES_FW_VERSION})
        uboot_setenv(uboot-env, "b.nerves_fw_platform", ${NERVES_FW_PLATFORM})
        uboot_setenv(uboot-env, "b.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
        uboot_setenv(uboot-env, "b.nerves_fw_author", ${NERVES_FW_AUTHOR})

	# Switch over to boot the new firmware
        uboot_setenv(uboot-env, "nerves_fw_active", "b")
        mbr_write(mbr-b)
    }

    on-error {
    }
}

task upgrade.unexpected {
    on-init {
        error("Please check the media being upgraded. It doesn't look like either the A or B partitions are active.")
    }
}

# The kernel is being included in /boot now. This isn't ideal since
# it's still not possible to upgrade the rootfs and kernel together.
# However, rather than including the kernel twice and bloating the
# firmware file, we run `dd if=/boot/uImage of=/dev/mtdblock3 bs=1M`
# manually if the kernel ever needs to be updated.

#task write_kernel {
#    on-resource uImage {
#        # It is expected that this is pointed at /dev/mtdblock3
#        raw_write(0)
#    }
#}
