Skip to content

Commit

Permalink
boards/common/qn908x: Compute the image checksum
Browse files Browse the repository at this point in the history
QN908X CPUs require the image to have a valid checksum. The checksum is
a simple addition of the first 7 uint32_t values stored in the 8th
position of the image header. This position is a reseved entry of the
Cortex-M Vector Table and its value depends on other fields that are
computed at link time. Performing this checksum at link time seems
hard to do, so instead this patch uses a python script to patch the
checksum from the ELF file. This redefines the value of FLASHFILE
to the new .elf file with the checksum fixed.

With this patch, OpenOCD can program and verify QN908X images since
now they have a valid checksum value.
  • Loading branch information
iosabi committed Dec 3, 2020
1 parent 9d46bc7 commit 7bcf4b8
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 0 deletions.
14 changes: 14 additions & 0 deletions boards/common/qn908x/Makefile.include
Expand Up @@ -14,3 +14,17 @@ OPENOCD_CONFIG ?= $(RIOTBOARD)/common/qn908x/dist/openocd.cfg
# verify the image, which needs to have the WDT disabled but it is normally
# enabled after a 'reset halt' command.
OPENOCD_PRE_FLASH_CMDS += "-c qn908x disable_wdog"

# In order to boot, the CPU requires that the Vector table contain a valid
# checksum in one of the "reserved" fields. We don't generate this checksum when
# compiling and linking the code, instead this make target computes the checksum
# in another ELF file and we set it as the FLASHFILE.
ELFFILE ?= $(BINDIR)/$(APPLICATION).elf
ELFFILE_CHECKSUM ?= $(ELFFILE:.elf=-checksum.elf)

$(ELFFILE_CHECKSUM): $(ELFFILE)
$(Q)$(OBJCOPY) --dump-section .vectors=$<.vectors $<
$(Q)$(RIOTBOARD)/common/qn908x/dist/nxp_checksum.py $(if $(Q),--quiet) \
$<.vectors
$(Q)$(OBJCOPY) --update-section .vectors=$<.vectors $< $@
FLASHFILE ?= $(ELFFILE_CHECKSUM)
76 changes: 76 additions & 0 deletions boards/common/qn908x/dist/nxp_checksum.py
@@ -0,0 +1,76 @@
#!/usr/bin/env python3

# Copyright (C) 2020 iosabi
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.

"""
NXP Vector table checksum generator for RAW binary files.
Computes and verifies the Vector table checksum entry for the NXP QN908x
family. These MCUs need a specific checksum value in the Vector table in order
to boot.
"""

import argparse
import logging
import struct


class NXPChecksumError(Exception):
"""Generic tool error."""


# Number of uint32_t entries that the checksum covers in the QN908X family.
# The computed checksum is stored right after these values.
CHECKSUM_ENTRIES = 7


def FixChecksum(binary_name, dry_run):
with open(binary_name, 'rb' if dry_run else 'rb+') as fobj:

checksum_fmt = '<%dI' % (CHECKSUM_ENTRIES + 1)
checksum_data_size = struct.calcsize(checksum_fmt)
data = fobj.read(checksum_data_size)

if len(data) < checksum_data_size:
raise NXPChecksumError(
'File %s is too small, it should have at least %d bytes' % (
binary_name, checksum_data_size))

values = list(struct.unpack(checksum_fmt, data))
checksum = (-sum(values[:-1])) % (1 << 32)
if checksum != values[-1]:
logging.info(
'Computed checksum 0x%.8x doesn\'t match the value in the file '
'(0x%.8x), updating...', checksum, values[-1])
if not dry_run:
fobj.seek(checksum_data_size - 4)
fobj.write(struct.pack('<I', checksum))
else:
logging.info('Checksum not updated [DRY-RUN].')
logging.info('Checksum updated. Done.')
else:
logging.info('Checksum in file is already valid. Done.')


def main_func():
parser = argparse.ArgumentParser(description=__doc__)

parser.add_argument('--dry-run', action='store_true', default=False)
parser.add_argument('--quiet', action='store_true', default=False)
parser.add_argument(
'bin', help='Path to RAW binary to check/update', type=str)

args = parser.parse_args()
logging.basicConfig(
format='%(filename)s: %(message)s',
level=logging.ERROR if args.quiet else logging.INFO)

FixChecksum(args.bin, args.dry_run)


if __name__ == '__main__':
main_func()

0 comments on commit 7bcf4b8

Please sign in to comment.