#!/usr/bin/env python2
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from __future__ import print_function
import argparse
import ctypes
import os


class Header(ctypes.Structure):
  _pack_ = 1
  _fields_ = [
      ('signature', ctypes.c_uint32),
      ('ftb_ver', ctypes.c_uint32),
      ('chip_id', ctypes.c_uint32),
      ('svn_ver', ctypes.c_uint32),
      ('fw_ver', ctypes.c_uint32),
      ('config_id', ctypes.c_uint32),
      ('config_ver', ctypes.c_uint32),
      ('reserved', ctypes.c_uint8 * 8),
      ('release_info', ctypes.c_ulonglong),
      ('sec_size', ctypes.c_uint32 * 4),
      ('crc', ctypes.c_uint32),
  ]

FW_HEADER_SIZE = 64
FW_HEADER_SIGNATURE = 0xAA55AA55
FW_FTB_VER = 0x00000001
FW_CHIP_ID = 0x3936
FW_BYTES_ALIGN = 4
FW_BIN_VER_OFFSET = 16
FW_BIN_CONFIG_ID_OFFSET = 20

# Starting address in flash for each section
FLASH_SEC_ADDR = [
    0x0000 * 4,  # CODE
    0x7C00 * 4,  # CONFIG
    0x7000 * 4,  # CX
    None  # This section shouldn't exist
]

UPDATE_PDU_SIZE = 4096

# Bin file format:
#   FTB header (padded to `UPDATE_PDU_SIZE`)
#   Flash sections
#     CODE
#     CX
#     CONFIG
OUTPUT_FILE_SIZE = UPDATE_PDU_SIZE + 128 * 1024


def main():
  parser = argparse.ArgumentParser()
  parser.add_argument('--input', '-i', required=True)
  parser.add_argument('--output', '-o', required=True)
  args = parser.parse_args()

  with open(args.input) as f:
    bs = f.read()

  size = len(bs)
  if size < FW_HEADER_SIZE + FW_BYTES_ALIGN:
    raise Exception('FW size too small')

  print('FTB file size:', size)

  header = Header()
  assert ctypes.sizeof(header) == FW_HEADER_SIZE

  ctypes.memmove(ctypes.addressof(header), bs, ctypes.sizeof(header))
  if (header.signature != FW_HEADER_SIGNATURE or
      header.ftb_ver != FW_FTB_VER or
      header.chip_id != FW_CHIP_ID):
    raise Exception('Invalid header')

  for key, _ in header._fields_:
    v = getattr(header, key)
    if isinstance(v, ctypes.Array):
      print(key, map(hex, v))
    else:
      print(key, hex(v))

  dimension = sum(header.sec_size)

  assert dimension + FW_HEADER_SIZE + FW_BYTES_ALIGN == size
  data = bs[FW_HEADER_SIZE:FW_HEADER_SIZE + dimension]

  with open(args.output, 'wb') as f:
    # ensure the file size
    f.seek(OUTPUT_FILE_SIZE - 1, os.SEEK_SET)
    f.write('\x00')

    f.seek(0, os.SEEK_SET)
    f.write(bs[0 : ctypes.sizeof(header)])

    offset = 0
    # write each sections
    for i, addr in enumerate(FLASH_SEC_ADDR):
      size = header.sec_size[i]
      assert addr is not None or size == 0

      if size == 0:
        continue

      f.seek(UPDATE_PDU_SIZE + addr, os.SEEK_SET)
      f.write(data[offset : offset + size])
      offset += size

    f.flush()


if __name__ == '__main__':
  main()
