From 8ec3e5dbb8ad143a05188a1b828e6b05ce36eca9 Mon Sep 17 00:00:00 2001 From: Kalle Valo Date: Mon, 12 Feb 2024 12:10:45 +0200 Subject: [PATCH] Add ath12k-fwencoder Signed-off-by: Kalle Valo --- tools/scripts/ath12k/ath12k-fwencoder | 722 ++++++++++++++++++++++++++ 1 file changed, 722 insertions(+) create mode 100755 tools/scripts/ath12k/ath12k-fwencoder diff --git a/tools/scripts/ath12k/ath12k-fwencoder b/tools/scripts/ath12k/ath12k-fwencoder new file mode 100755 index 0000000..2e78b99 --- /dev/null +++ b/tools/scripts/ath12k/ath12k-fwencoder @@ -0,0 +1,722 @@ +#!/usr/bin/python3 +# +# Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved +# Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. +# Copyright (c) 2015-2017 Qualcomm Atheros, Inc. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import ctypes +import struct +import optparse +import time +import math +import logging +import sys +import os.path +import traceback +import binascii +import tempfile +import subprocess +import hashlib + +DEFAULT_FW_API_VERSION = 2 + +ATH12K_SIGNATURE = b'QCOM-ATH12K-FW' +MAX_LEN = 50000000 + +ATH12K_FW_IE_TIMESTAMP = 0 +ATH12K_FW_IE_FEATURES = 1 +ATH12K_FW_IE_AMSS_IMAGE = 2 +ATH12K_FW_IE_M3_IMAGE = 3 +ATH11K_FW_IE_AMSS_DUALMAC_IMAGE = 4 + +# enum ath12k_fw_features from ath12k/core.h +ATH12K_FW_FEATURE_MULTI_QRTR_ID = 0 +ATH12K_FW_FEATURE_MAX = 1 + +feature_map = { + 'multi-qrtr-id': ATH12K_FW_FEATURE_MULTI_QRTR_ID, +} + +# global variables +logger = None + + +class FWEncoderError(Exception): + pass + + +def get_output_name(fw_api=None): + if fw_api is not None: + api = fw_api + else: + api = DEFAULT_FW_API_VERSION + + return 'firmware-%s.bin' % api + + +class FirmwareContainer: + + def add_element(self, type_id, value): + length = len(value) + padding_len = padding_needed(length) + length = length + padding_len + + padding = ctypes.create_string_buffer(padding_len) + + for i in range(padding_len): + struct.pack_into('= maximum): + logger.error("bitmap %d out of maximum (%d >= %d)" % + (ie, max(enabled), maximum)) + return + + bytes = [0] * maximum + + for i in range(maximum): + if i not in enabled: + continue + + max_set = i + index = int(i / 8) + bit = i % 8 + bytes[index] = bytes[index] | (1 << bit) + + # remove trailing null bits away, that changing only + # maximum doesn't affect created binary size + length = int(math.ceil((max_set + 1) / float(8))) + bytes = bytes[:length] + + buf = ctypes.create_string_buffer(length) + + for index in range(length): + struct.pack_into('> 1 + + return bits + + def set_signature(self, signature): + self.signature = signature + self.signature_len = len(signature) + + # include the null byte + length = len(signature) + 1 + + padding_len = padding_needed(length) + + length = length + padding_len + + padding = ctypes.create_string_buffer(padding_len) + + for i in range(padding_len): + struct.pack_into(' self.buf_len: + logger.error("Buffer too short") + return + + fmt = '<%ds' % length + (payload,) = struct.unpack_from(fmt, self.buf, offset) + offset = offset + length + offset = offset + padding_needed(offset) + + self.elements[type_id] = payload + + return True + + def __init__(self, signature): + self.buf = ctypes.create_string_buffer(MAX_LEN) + self.buf_len = 0 + + self.set_signature(signature) + + +class Ath12kFirmwareContainer(object): + def _load_file(self, name): + if os.path.getsize(name) > MAX_LEN: + raise FWEncoderError('file %s is too large, maximum size is %d' % + (name, MAX_LEN)) + + f = open(name, 'rb') + buf = f.read() + f.close() + + return buf + + def set_features(self, features): + self.features = features.split(',') + + enabled = [] + for capa in self.features: + if capa not in feature_map: + print("Error: '%s' not found from the feature map" % capa) + return 1 + + enabled.append(feature_map[capa]) + + self.features_bitmap = enabled + + def get_features(self): + s = "" + + if self.features_bitmap is None: + return None + + for capa in self.features_bitmap: + # find value from the dict + try: + name = [key for key, value in feature_map.items() + if value == capa][0] + except IndexError: + name = str(capa) + + s = s + name + "," + + # strip last comma + if len(s) > 0: + s = s[:-1] + + return s + + def set_amss_image(self, amss_image_name): + self.amss_image = self._load_file(amss_image_name) + + def get_amss_image(self): + return self.amss_image + + def set_amss_dualmac_image(self, amss_dualmac_image_name): + self.amss_dualmac_image = self._load_file(amss_dualmac_image_name) + + def get_amss_dualmac_image(self): + return self.amss_dualmac_image + + def set_m3_image(self, m3_image_name): + self.m3_image = self._load_file(m3_image_name) + + def get_m3_image(self): + return self.m3_image + + def set_timestamp(self, timestamp): + self.timestamp = int(timestamp) + + def get_timestamp(self): + return self.timestamp + + def get_timestamp_as_iso8601(self): + if self.timestamp is None: + return None + + return time.strftime('%Y-%m-%d %H:%M:%S', + time.gmtime(self.timestamp)) + + def get_summary(self): + s = '' + + s = s + 'FileSize: %s\n' % (len(self.file)) + s = s + 'FileCRC32: %08x\n' % (_crc32(self.file)) + s = s + 'FileMD5: %s\n' % (hashlib.md5(self.file).hexdigest()) + + if self.get_timestamp(): + s = s + 'Timestamp: %s\n' % (self.get_timestamp_as_iso8601()) + + if self.get_features(): + s = s + 'Features: %s\n' % (self.get_features()) + + if self.get_amss_image(): + s = s + 'AMSSImageSize: %s\n' % (len(self.get_amss_image())) + s = s + 'AMSSImageCRC32: %08x\n' % (_crc32(self.get_amss_image())) + + if self.get_amss_dualmac_image(): + s = s + 'AMSSDualMacImageSize: %s\n' % (len(self.get_amss_dualmac_image())) + s = s + 'AMSSDualMacImageCRC32: %08x\n' % (_crc32(self.get_amss_dualmac_image())) + + if self.get_m3_image(): + s = s + 'M3ImageSize: %s\n' % (len(self.get_m3_image())) + s = s + 'M3ImageCRC32: %08x\n' % (_crc32(self.get_m3_image())) + + return s.strip() + + def load(self, filename): + c = FirmwareContainer(ATH12K_SIGNATURE) + c.open(filename) + + self.file = c.buf + + for e in c.elements: + if e == ATH12K_FW_IE_TIMESTAMP: + self.timestamp = c.read_u32(e) + elif e == ATH12K_FW_IE_M3_IMAGE: + self.m3_image = c.elements[e] + elif e == ATH12K_FW_IE_AMSS_IMAGE: + self.amss_image = c.elements[e] + elif e == ATH11K_FW_IE_AMSS_DUALMAC_IMAGE: + self.amss_dualmac_image = c.elements[e] + elif e == ATH12K_FW_IE_FEATURES: + self.features_bitmap = c.read_bitmap(ATH12K_FW_IE_FEATURES) + else: + print("Unknown IE: ", e) + + def save(self, filename): + self.container = FirmwareContainer(ATH12K_SIGNATURE) + + if self.timestamp: + self.container.add_u32(ATH12K_FW_IE_TIMESTAMP, self.timestamp) + + # FIXME: m3 should be after amss_image but that breaks the + # current tests + if self.m3_image: + self.container.add_element(ATH12K_FW_IE_M3_IMAGE, self.m3_image) + + if self.amss_image: + self.container.add_element(ATH12K_FW_IE_AMSS_IMAGE, self.amss_image) + + if self.amss_dualmac_image: + self.container.add_element(ATH11K_FW_IE_AMSS_DUALMAC_IMAGE, self.amss_dualmac_image) + + # FIXME: features should be before amss_image but that breaks + # the current tests + if self.features_bitmap: + self.container.add_bitmap(ATH12K_FW_IE_FEATURES, + self.features_bitmap, + ATH12K_FW_FEATURE_MAX) + + return self.container.write(filename) + + def __init__(self): + self.timestamp = None + self.features = None + self.features_bitmap = None + self.amss_image = None + self.m3_image = None + self.amss_dualmac_image = None + + +# to workaround annoying python feature of returning negative hex values +def hex32(val): + return val & 0xffffffff + + +# match with kernel crc32_le(0, buf, buf_len) implementation +def _crc32(buf): + return hex32(~(hex32(binascii.crc32(buf, 0xffffffff)))) + + +def padding_needed(length): + if length % 4 != 0: + return 4 - length % 4 + + return 0 + + +def is_int(val): + try: + int(val) + return True + except ValueError: + return False + + +def write_file(filename, buf): + f = open(filename, 'wb') + f.write(buf) + f.close + + +def info(options, args): + + if len(args) != 1: + print('Filename missing') + return 1 + + filename = args[0] + + c = Ath12kFirmwareContainer() + c.load(filename) + + print(c.get_summary()) + + +def dump(options, args): + + if len(args) != 1: + print('Filename missing') + return 1 + + filename = args[0] + + c = Ath12kFirmwareContainer() + c.load(filename) + + print("ath12k-fwencoder --create \\") + + if c.get_timestamp() and options.show_timestamp: + print("--timestamp=%u \\" % c.get_timestamp()) + + if c.get_features(): + print("--features=%s \\" % c.get_features()) + + if c.get_amss_image(): + name = "amss.bin" + print("--amss=%s \\" % name) + + if c.get_amss_dualmac_image(): + name = "amss_dualmac.bin" + print("--amss_dualmac=%s \\" % name) + + if c.get_m3_image(): + name = "m3.bin" + print("--m3=%s \\" % name) + + print() + + +def extract(options, args): + + if len(args) != 1: + print('Filename missing') + return 1 + + filename = args[0] + + c = Ath12kFirmwareContainer() + c.load(filename) + + if c.get_amss_image(): + name = "amss.bin" + write_file(name, c.get_amss_image()) + print('%s extracted: %d B' % (name, len(c.get_amss_image()))) + + if c.get_amss_dualmac_image(): + name = "amss_dualmac.bin" + write_file(name, c.get_amss_dualmac_image()) + print('%s extracted: %d B' % (name, len(c.get_amss_dualmac_image()))) + + if c.get_m3_image(): + name = "m3.bin" + write_file(name, c.get_m3_image()) + print('%s extracted: %d B' % (name, len(c.get_m3_image()))) + + print() + + +def modify(options, args): + if len(args) != 1: + print('Filename missing') + return 1 + + filename = args[0] + + c = Ath12kFirmwareContainer() + c.load(filename) + + if options.timestamp: + stamp = str(int(options.timestamp)) + else: + # if no timestamp provided use the current time so that the + # timestamp shows the time of last modication + stamp = int(time.time()) + + c.set_timestamp(stamp) + + if options.m3: + c.set_m3_image(options.m3) + + if options.amss: + c.set_amss_image(options.amss) + + if options.amss_dualmac: + c.set_amss_dualmac_image(options.amss_dualmac) + + if options.features: + c.set_features(options.features) + + file_len = c.save(filename) + + print('%s modified: %d B' % (filename, file_len)) + + +def create(options): + output = get_output_name(options.fw_api) + + if options.output: + output = options.output + + c = Ath12kFirmwareContainer() + + # always add a timestamp + if options.timestamp: + stamp = int(options.timestamp) + else: + stamp = int(time.time()) + + c.set_timestamp(stamp) + + if options.m3: + c.set_m3_image(options.m3) + + if options.amss: + c.set_amss_image(options.amss) + + if options.amss_dualmac: + c.set_amss_dualmac_image(options.amss_dualmac) + + if options.features: + c.set_features(options.features) + + file_len = c.save(output) + + print('%s created: %d B' % (output, file_len)) + + +def cmd_crc32(options, args): + if len(args) != 1: + print('Filename missing') + return 1 + + filename = args[0] + + f = open(filename, 'rb') + buf = f.read() + print('%08x' % (_crc32(buf))) + f.close() + + +def cmd_diff(options, args): + if len(args) != 2: + print('Usage: ath12k-fwencoder --diff FILE FILE') + return 1 + + filename1 = args[0] + filename2 = args[1] + + c1 = Ath12kFirmwareContainer() + c1.load(filename1) + (temp1_fd, temp1_pathname) = tempfile.mkstemp(text=True) + + # for some reason text=True is not working with mkstemp() so open + # the file manually + f = open(temp1_pathname, 'w') + f.write(c1.get_summary()) + f.close() + + c2 = Ath12kFirmwareContainer() + c2.load(filename2) + (temp2_fd, temp2_pathname) = tempfile.mkstemp(text=True) + + # for some reason text=True is not working with mkstemp() so open + # the file manually + f = open(temp2_pathname, 'w') + f.write(c2.get_summary()) + f.close() + + # '--less-mode' and '--auto-page' would be nice when running on + # terminal but don't know how to get the control character + # through. For terminal detection sys.stdout.isatty() can be used. + cmd = ['wdiff', temp1_pathname, temp2_pathname] + + # wdiff is braindead and returns 1 in a succesfull case + try: + output = subprocess.check_output(cmd, universal_newlines=True) + except subprocess.CalledProcessError as e: + if e.returncode == 1: + output = e.output + else: + logger.error('Failed to run wdiff: %d\n%s' % (e.returncode, e.output)) + return 1 + except OSError as e: + logger.error('Failed to run wdiff: %s' % (e)) + return 1 + + print(output) + + os.close(temp1_fd) + os.close(temp2_fd) + + +def main(): + global logger + + logger = logging.getLogger('ath12k-fwencoder') + logging.basicConfig(format='%(levelname)s: %(message)s') + + parser = optparse.OptionParser() + + # actions + parser.add_option("-c", "--create", action="store_true", dest="create", + help='Create container file ' + 'for ath12k (%s)' % get_output_name()) + parser.add_option("-D", "--dump-cmdline", action="store_true", dest="dump", + help='Show the cmdline used to create ' + 'this container file') + parser.add_option('--dump', action='store_true', dest='dump', + help='Same as --dump-cmdline ' + '(for backwards compatibility)') + parser.add_option("-e", "--extract", action="store_true", dest="extract", + help='Extract binary files from the container file ' + 'and dump cmdline as well') + parser.add_option("--info", action="store_true", dest="info", + help='Show information about the container file') + parser.add_option("--modify", action="store_true", dest="modify", + help='Modify the container file') + parser.add_option('--crc32', action='store_true', dest='crc32', + help='Count crc32 checksum for a file') + parser.add_option('--diff', action='store_true', dest='diff', + help='Show differences between two firmware files') + + # parameters + parser.add_option("-o", "--output", action="store", type="string", + dest="output", help='Name of output file') + + # FW IEs, only use long style of option names! + parser.add_option("--m3", action="store", type="string", + dest="m3", + help='Name of m3.bin file') + parser.add_option("--amss", action="store", type="string", + dest="amss", + help='Name of amss.bin file') + parser.add_option("--amss_dualmac", action="store", type="string", + dest="amss_dualmac", + help='Name of amss_dualmac.bin file') + parser.add_option("--timestamp", action="store", + type="string", dest="timestamp", + help='Timestamp to be used (seconds)') + + parser.add_option("--features", action="store", + type="string", dest="features", + help='feature bits to be enabled: %s' % + list(feature_map.keys())) + parser.add_option("--set-fw-api", action="store", + type="string", dest="fw_api", + help='Set firmware API used in creating the name for ' + 'output file (Default: %s)' % + DEFAULT_FW_API_VERSION) + + # debug etc + parser.add_option('--show-timestamp', action='store_true', + dest='show_timestamp', + help='Show timestamp in --dump-cmdline action. ' + 'It is not shown by default so that the timestamp would be correct') + parser.add_option('-d', '--debug', action='store_true', dest='debug', + help='Enable debug messages') + + (options, args) = parser.parse_args() + + if options.debug: + logger.setLevel(logging.DEBUG) + + if options.create: + try: + return create(options) + except FWEncoderError as e: + print('Create failed: %s' % e) + sys.exit(2) + except Exception as e: + print('Create failed: %s' % e) + traceback.print_exc() + sys.exit(3) + elif options.dump: + return dump(options, args) + elif options.extract: + return extract(options, args) + elif options.info: + return info(options, args) + elif options.modify: + return modify(options, args) + elif options.crc32: + return cmd_crc32(options, args) + elif options.diff: + return cmd_diff(options, args) + else: + print('Action command missing') + return 1 + +if __name__ == "__main__": + main()