# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
# All Rights Reserved.
# Confidential and Proprietary - Qualcomm Technologies, Inc.


'''
Purpose of the script:
1. This is to detect cmnlib APIs not supported scenario at compile 
   time if particular SDK version doesn't support that API.
2. For unified QTEE SDKs, mainline SDK will behave
like some branched out SDK.
cmnlib used to build TAs would be present from latest SDK
(i.e. some extra support in CMNLIB might be present).
But on older branches, all APIs of mainline cmnlib might not be 
present. If any unsupported API is used, then this script will fail the build.

Note: This file comprises of:
  1. A builder which will only get plugged from TZ APPS component
     when built with deploy keyword. 
     After all the TA ELFs are generated, this script will identify
     the cmnlib APIs being used by the TAs and compare with the
     APIs present in env['CMNLIB_SUPPORTED_APIS'] which are APIs
     supported by the particular SDK. Generates a file named sdk_log.txt.
  2. check_cmnlb_supported_apis_external function which is called
     when building TA in external build. This is called after building
     TA ELF and before building TA MBN. This marks dependency on a dummy
     TA ELF file which is copy of original ELF file. This dummy ELF file
     is not used any further.
'''

import os
from optparse import OptionParser
import signal
import shutil


def exists(env):
  return True

# Register the builder and expose methods in environment.
def generate(env):
  env.AddMethod(check_cmnlib_supported_apis, 'CheckCmnlibSupportedAPIs')
  my_builder = env.Builder(action=verify_apis)
  env.Append(BUILDERS = {'VerifyCmnlibAPIs' : my_builder})

def check_cmnlib_supported_apis(env, target, source):
  source = env.Flatten(source)
  op = env.VerifyCmnlibAPIs(target, source)
  return op

def verify_apis(target, source, env):

  # This validation is only required for ontarget TA build.
  # In offtarget scenario, we don't have dynamic cmnlib linking,
  # hence no gap in TA using cmnlib APIs and actual cmnlib APIs supported.
  if env.OfftargetTesting():
    return

  import SCons.Script
  # Early return if SDK version is not passed/ latest.
  if (SCons.Script.ARGUMENTS.get('sdk_version') is None) or str(SCons.Script.ARGUMENTS.get('sdk_version')).lower() in ["tz.xf.5.0", "latest"]:
    return

  # O/P would be generated to support successful builder execution. But this code 
  # needs to be present for SCons to identify that the target is being generated.
  try:
    file_handle = open(str(target[0]), "w")
    file_message = "sdk version = " + str(SCons.Script.ARGUMENTS.get('sdk_version'))
    file_handle.write(file_message)
    file_handle.close()
  except:
    raise Exception("Failed to create file: " + str(target[0]))

  if 'CMNLIB_SUPPORTED_APIS' not in env:
    raise Exception("CMNLIB_SUPPORTED_APIS list not present in env")

  # CMNLIB_SUPPORTED_APIS override in environment is an optional feature.
  # Its not a mandatory feature. So, if this list is empty, we do not check
  # anything further. It means, all cmnlib APIs must be supported whichever
  # are available.
  if len(env['CMNLIB_SUPPORTED_APIS']) < 1:
    return;

  cmnlib_api_used_by_ta_elf = {}
  for ta in env['TA_ELF_INFO']:
    ta_dynamic_symbols = os.popen(os.path.join(env.subst(env.get('LLVMROOT')), "bin", "llvm-readelf") + " --dyn-syms " + env['TA_ELF_INFO'][ta]).read()
    ta_dynamic_symbols = ta_dynamic_symbols.split("\n")
    # In dynamic symbols, cmnlib APIs are present under header of undefined functions.
    for symbol in ta_dynamic_symbols:
      if "FUNC" in symbol and "UND" in symbol:
        # symbol entry is of this type:
        # "  1397: 000000000003463c     0 FUNC    GLOBAL DEFAULT  UND qsee_SW_Hmac_Init"
        api_name = symbol.split(" ")[-1]
        if api_name in cmnlib_api_used_by_ta_elf:
          cmnlib_api_used_by_ta_elf[api_name].append(ta)
        else:
          cmnlib_api_used_by_ta_elf[api_name] = [ta]

  error_msg = "Failed to find following APIs support in cmmnlib with QTEE SDK version=" + str(SCons.Script.ARGUMENTS.get('sdk_version')) + " used by TAs:\n"
  error_msg = error_msg + "For ex: <cmnlib_api_name>: <TA1 TA2>\n"
  unsupported_cmnlib_api_used_by_ta_elf = False

  for api in cmnlib_api_used_by_ta_elf:
    if api not in env['CMNLIB_SUPPORTED_APIS']:
      unsupported_cmnlib_api_used_by_ta_elf = True
      error_msg = error_msg + api + ": "
      for ta in cmnlib_api_used_by_ta_elf[api]:
        error_msg = error_msg + ta + " "
    error_msg = error_msg + "\n"

  if (unsupported_cmnlib_api_used_by_ta_elf):
    raise Exception(error_msg)

  return None


def check_cmnlb_supported_apis_external(in_ta_elf_path, cmnlib_symbol_file_path, out_ta_elf_path, sdk_version, llvm_readelf_path):
  cmnlib_symbols = []
  cmnlib_symbol_file_handle = open(cmnlib_symbol_file_path, 'r')
  supported_cmnlib_symbols = cmnlib_symbol_file_handle.read().split("\n")
  cmnlib_symbol_file_handle.close()
  cmnlib_api_used_by_ta_elf = []
  unsupported_cmnlib_api_used_by_ta_elf = False

  # CMNLIB_SUPPORTED_APIS override in environment is an optional feature.
  # Its not a mandatory feature. So, if this list is empty, we do not check
  # anything further. It means, all cmnlib APIs must be supported whichever
  # are available.
  # While reading an empty file, first empty line can be considered as valid
  # data. Hence, checking on length greater than 1 for valid set of APIs.
  if len(supported_cmnlib_symbols) <= 1:
    return;

  ta_dynamic_symbols = os.popen(llvm_readelf_path + " --dyn-syms " + in_ta_elf_path).read()
  ta_dynamic_symbols = ta_dynamic_symbols.split("\n")
  # In dynamic symbols, cmnlib APIs are present under header of undefined functions.
  for symbol in ta_dynamic_symbols:
    if "FUNC" in symbol and "UND" in symbol:
      # symbol entry is of this type:
      # "  1397: 000000000003463c     0 FUNC    GLOBAL DEFAULT  UND qsee_SW_Hmac_Init"
      api_name = symbol.split(" ")[-1]
      cmnlib_api_used_by_ta_elf.append(api_name)

  error_msg = "Error: Failed to find following APIs support in cmmnlib with QTEE SDK version=" + sdk_version + "\n"

  for api in cmnlib_api_used_by_ta_elf:
    if api not in supported_cmnlib_symbols:
      unsupported_cmnlib_api_used_by_ta_elf = True
      error_msg = error_msg + api + "\n"

  if (unsupported_cmnlib_api_used_by_ta_elf):
    raise Exception(error_msg)
  shutil.copy(in_ta_elf_path, out_ta_elf_path)

def main():
  # Basic argument & parsing
  parser = OptionParser()
  parser.add_option(
    "-i", "--in_ta_elf_path",
    action = "store",
    dest = "in_ta_elf_path",
    default = "",
    help = "Pass the elf path of TA for which cmnlib symbols are to be matched"
  )
  parser.add_option(
    "-c", "--cmnlib_symbol_file_path",
    action = "store",
    dest = "cmnlib_symbol_file_path",
    default = "",
    help = "Pass the path of cmnlib_symbol file applicable for SDK version"
  )
  parser.add_option(
    "-o", "--out_ta_elf_path",
    action = "store",
    dest = "out_ta_elf_path",
    default = "",
    help = "Pass the path of TA elf to be generated"
  )
  parser.add_option(
    "-s", "--sdk_version",
    action = "store",
    dest = "sdk_version",
    default = "",
    help = "Pass the current SDK version"
  )
  parser.add_option(
    "-l", "--llvm_readelf_path",
    action = "store",
    dest = "llvm_readelf_path",
    default = "",
    help = "Pass the LLVM readelf path"
  )

  # Parse the arguments passed via command line.
  (options, _) = parser.parse_args()

  in_ta_elf_path = options.in_ta_elf_path
  cmnlib_symbol_file_path = options.cmnlib_symbol_file_path
  out_ta_elf_path = options.out_ta_elf_path
  sdk_version = options.sdk_version
  llvm_readelf_path = options.llvm_readelf_path

  check_cmnlb_supported_apis_external(in_ta_elf_path, cmnlib_symbol_file_path, out_ta_elf_path, sdk_version, llvm_readelf_path)

if __name__ == '__main__':
  try:
    main()
  except Exception as e:
    print(e)
    os.kill(os.getppid(),signal.SIGTERM)
