#!/bin/sh

# Copyright (c) 2017 The DragonFly Project. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# Neither the name of The DragonFly Project nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific, prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS
# IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.

# Prefixes for a files generated by parsing ISP driver C header file
# with firmware. For their meaning see the parse_isp_firmware_header()
# function info.
license_file="license"
name_file="name_"
info_file="info_"
data_file="data_"
# Prefix for a binary file for firmware binary data. The binary file
# contains output of sh execution of printf commands in "data_N" file.
binary_file="binary_"
# Suffix for output files which this script produces
firmware_suffix=".fw.uu"
# This string (and a number) will be added to the names of the other
# variants of one firmware to form a unique filenames for the script
# output files
variant_string=".variant_"

# Function usage: parse_isp_firmware_header FILE
#
#    FILE - Path to ISP driver C header file with firmware data.
#
# Result of this function are files containing parts of the header
# file and generated sh commands. One header file can contain multiple
# firmware images. Because of that, each file which contains image
# specific info has a number N appended to its name. N is integer
# number starting from 0. Files with the same N belongs to the same
# firmware image.
#
#    license - Firmware license.
#    name_N  - Image name. This will be the name of the final firmware .uu file.
#    info_N  - Comment blocks with info about firmware image.
#    data_N  - Printf calls that convert image words hex numbers to their
#              values. This file is intended to be executed by sh and the stdout
#              to be redirected to a file.
#
# NOTE: Resulting image words values will be in little-endian format.
parse_isp_firmware_header()
{
    awk '
# This function converts chars from "a" to "f" to decimal numbers from
# 10 to 15 respectively. Every other character is returned as is.
# Parameters "chars" and "char_array" are local variables.
function hex_alpha_to_dec(a, chars, char_array) {
  chars = "a,b,c,d,e,f"
  split(chars, char_array, ",")
  
  if (a ~ /[abcdef]/) {
     for (j = 1; ; ++j) {
       if (char_array[j] == a) {
         return (10 + j - 1)
       }
     }  
  } else {
    return a
  }
}

# This function converts two-digit hex number to decimal number.
# The input hex number is without "0x" prefix and "h" suffix.
# Parameters "a" and "b" are local variables.
function hex_to_dec(h, a, b) {
  h = tolower(h)
  a = hex_alpha_to_dec(substr(h, 1, 1))
  b = hex_alpha_to_dec(substr(h, 2, 1))
  return ((a * 16) + b)
} 

BEGIN {
  state = "license"
  license_file = "'"${license_file}"'"
  name_file = "'"${name_file}"'"
  info_file = "'"${info_file}"'"
  data_file = "'"${data_file}"'"
  image_number = 0
}

# Filter out file information line(s) (file path, version, date, author, ...)
/^\/\* \$.*\$ \*\// {
  next
}

# Print the first line of the license contained in one comment block
(state == "license") && /\/\*/ {
  print $0 > license_file
  state = "inside_license"
  next
}

# Print the last line of the license contained in one comment block
(state == "inside_license") && /\*\// {
  print $0 > license_file
  # License has been read, process comment blocks which contains info
  # about the next firmware
  state = "info"
  next
}

# Print lines inside the license comment block
(state == "inside_license") {
  print $0 > license_file
  next
}

# Print info text of the next firmware contained in one comment block
(state == "info") && /\/\*/,/\*\// {
  out = sprintf("%s%s", info_file, image_number);
  print $0 > out
  next
}

# Process array declaration line, get the name of the firmware and data type of
# array values 
/\[\] = \{/ {
  state = "array"
  split($0, decl)
  split(decl[4], name, "_risc_code")
  array_name = name[1]
  array_type = decl[3]
  
  if (array_type ~ /int16/) {
    # Data type of the array values is 16-bit 
    state = "data16"
  } else {
    # Data type of the array values is 32-bit 
    state = "data32"
  }
  # Save the firmware name
  out = sprintf("%s%s", name_file, image_number);
  printf "%s", array_name > out
  next
}

# Current array has ended, process the next one
((state == "data16") || ((state == "data32"))) && /\}/ {
  state = "info"
  image_number++
  next
}

# Process one line of array content definition
# Generate shell printf calls for the array values
(state == "data16") || (state == "data32") {
  gsub("[ \t]", "", $0)
  split($0, values, ",")

  for (i = 1; length(values[i]) > 0; ++i) {
     # Get the single bytes
     a = substr(values[i], 3, 2)
     b = substr(values[i], 5, 2)
     if (state == "data32") {
       c = substr(values[i], 7, 2)
       d = substr(values[i], 9, 2)     
     }
     # POSIX printf needs the number to be in octal format in the escape
     # sequence
     # Swap the bytes to be in little-endian format
     v = ""
     if (state == "data32") {
       v = "\\" sprintf("%o", hex_to_dec(d)) "\\" sprintf("%o", hex_to_dec(c))
     }
     v = v "\\" sprintf("%o", hex_to_dec(b)) "\\" sprintf("%o", hex_to_dec(a))
     out = sprintf("%s%s", data_file, image_number);
     printf "printf \"%s\"\n", v > out
  }
  next
}
' "$1"
}

# Check input file and its existence
if [ $# -ne 1 ]; then
    printf "Usage: $0 HEADER_FILE\n" >&2
    exit 1
elif [ ! -f "$1" ]; then
    printf "Error: file '$1' does not exists\n" >&2
    exit 1
fi

parse_isp_firmware_header "$1"

# Concatenate the files to form a firmware .uu files
image_number=0

while [ -f "${name_file}${image_number}" ]; do
    output_file="$(cat ${name_file}${image_number})"
    # Some ISP firmware header files (asm_2100.h) contains more
    # variants of the same firmware image. Append _var_M to the second
    # and the following image's names, where 1 <= M. Name of the first
    # image in the header file will get nothing appended.
    if [ -f "${output_file}${firmware_suffix}" ]; then
	# Find unused variant number	
	variant_number=1
	tmp_name="${output_file}${variant_string}${variant_number}"
	while [ -f "$tmp_name" ];do
	    variant_number=$((variant_number + 1))
    	    tmp_name="${output_file}${variant_string}${variant_number}"
	done
	# Add the suffix
	output_file="$tmp_name"
    fi
    output_file="${output_file}${firmware_suffix}"
    
    # Add the license
    cat "$license_file" > "$output_file"
    # Add the firmware information if it exists
    [ -f "${info_file}${image_number}" ] && \
        cat "${info_file}${image_number}" >> "$output_file"
    # Generate firmware binary data
    cat "${data_file}${image_number}" | sh > "${binary_file}${image_number}"
    # uuencode the data and add it to the output file
    uuencode "${binary_file}${image_number}" \
             "${binary_file}${image_number}" >> "$output_file"
    
    image_number=$((image_number + 1))
done

# Remove temporary files generated by parsing the ISP driver C header
# file with firmware data
eval rm -f \"${license_file}\"\*  \
     \"${name_file}\"\*  \
     \"${info_file}\"\*  \
     \"${data_file}\"\*  \
     \"${binary_file}\"\*
