view fluid-mnf/machine.c @ 334:ea1e950c849f

frbl/reconst/serial.c: 0x288 static function reconstructed
author Mychaela Falconia <falcon@freecalypso.org>
date Sat, 07 Mar 2020 02:26:59 +0000
parents 9cecc930d78f
children 435e041897f2
line wrap: on
line source

/******************************************************************************
 * FLUID (Flash Loader Utility Independent of Device)
 *
 * Copyright Texas Instruments, 2001.
 * Mads Meisner-Jensen, mmj@ti.com.
 * Original state-machine logic by Delta Technologies, Copyright, 2001.
 *
 * Core functionality. State machines and support functions.
 *
 * $Id: machine.c 1.35.1.37 Mon, 28 Apr 2003 08:49:16 +0200 tsj $
 *
 ******************************************************************************/

#include "fluid.h"
#include "flash.h"
#include "target.h"
#include "fileio.h"
#include "../target/protocol.h"
#include "misc.h"
#include "lz.h"
#include "trace.h"
// Secure Calypso Plus
#include "../inc/ram_load.h"
#include "../inc/secure_types.h"

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>

/******************************************************************************
 * Constants and typedefs
 ******************************************************************************/

// Due to memory wrap/mirror used in flash_read_machine(), IMAGE_SIZE_MAX
// *must* be a power of two.
#define IMAGE_SIZE_MAX (8 * 1024 * 1024)

#define IMAGE_CHUNK_SIZE 8192

#define ERASE_LIST_SIZE_MAX 256
#define SECTOR_MAP_SIZE_MAX 256

#define TARGET_PROGRAM_SIZE_MAX (8 * 1024)

#define READ_LIST_SIZE_MAX 40

#define RETRIES_MAX 30

// Default target receive delay in milli-seconds.
#define TARGET_RECV_DELAY 250
#define TARGET_RECV_LONG_DELAY 1000

// Default target reset delay in milli-seconds.
#define TARGET_RESET_DELAY 200

// CS_MODE 0: Flash at 0x04000000 @ CS5 on E-Sample
#define CALP_OFFSET 0x04000000

/******************************************************************************
 * Globals
 ******************************************************************************/

// Secure Calypso Plus
T_RAM_LOADER d_ram_loader;
T_FRAME      d_frame;
UWORD16      d_certificate_length = 0;
char         a_certified_cmd_file_name[] = "cmdp_cert.m0";

struct target_s {
    uint32 cmd_load_addr;
    uint32 method_load_addr;
    const char type;
    const char *name;
    const char *prefix;
};
// Note that the ROM bootloader of Calypso rev. A devices are unable to
// write the bottom 128kB of internal RAM. Therefore the address have been
// changed from 0x804000 to 0x0820000
const struct target_s target[] =
{
    { 0x0000000, 0x0000000, 'u', "Unknown",  "oops"  },
    { 0x0000000, 0x0000000, 'u', "Unknown1", "oops1" },
    { 0x0000000, 0x0000000, 'G', "Gemini?",  "gem?"  },
    { 0x3004000, 0x3000000, 'U', "Ulysses",  "uly"   },
    { 0x0820000, 0x0800000, 'C', "Calypso",      "cal"   },
    { 0x0804000, 0x0800000, 'c', "Calypso Lite", "cal_l" },
    { 0x8020000, 0x8000100, 'P', "Calypso Plus", "cal_p" }
};
int target_type;
int target_clk; // 13000000 or 14745600

struct device_s *device;

uint8 target_program[TARGET_PROGRAM_SIZE_MAX];
int   target_program_size;
int   target_fifo_size;

uint8 image[IMAGE_SIZE_MAX];
int   image_size;           // size of image buffer
int   image_chunk_size;     // number of bytes in each chunk
int   image_size_in_chunks; // size of image buffer in chunks

// Each byte in the image_map represents one chunk of the image. Each byte
// can have one of the following values:
// '\0' = no data in this chunk
//  's' = skip this chunk when programming
//  'c' = chunk checksum ok, skip this chunk when programming
//  'x' = used
uint8 image_map[IMAGE_SIZE_MAX / IMAGE_CHUNK_SIZE];
int   image_map_size;       // number of entries in image_map

// Each byte in the sector_map represents one sector. Each byte can have one
// of the following values:
// '\0' = sector unused (don't erase)
//  'c' = sector already empty/erased (don't erase) (not used)
//  's' = skip this sector when erasing (due to erase override)
//  'X' = force erase this sector (due to erase override)
//  'x' = erase this sector (normal case --- sector is covered by image)
int sector_map[SECTOR_MAP_SIZE_MAX];
int sector_map_size;

int erase_list[ERASE_LIST_SIZE_MAX];
int erase_list_size;

struct image_part_s read_list[READ_LIST_SIZE_MAX];
int read_list_size;
int read_total_size;

int image_map_count_used_chunks(void);
int image_map_update(void);
int sector_map_init(void);

void image_map_show(void);
void sector_map_show(void);
void target_timers_show(void);

void parse_arg_erase_override(char *p);
void parse_arg_read(char *p);
void parse_arg_write(char *p);

void error_proto(char ch, char ch_expected);

int target_type_set(uint16 code);

// Secure Calypso Plus
void f_print_signalling_response(int level);
void f_print_certificate_platform_data(int level, T_MANUFACTURER_CERTIFICATE_PLATFORM_DATA *p_certificate);
void f_print_certificate(int level, T_MANUFACTURER_CERTIFICATE_FLASH_PROGRAMMER *p_certificate);
void f_print_parameter_nack_status(int level, UWORD8 d_parameter_nack_sts);
void f_print_write_nack_status(int level, UWORD8 d_write_nack_sts);
UWORD8 f_convert_uart_baud_rate(UWORD32 d_baud_rate);
void target_imei_protection(void);

/******************************************************************************
 * Main
 ******************************************************************************/

void bootloader_machine(void);
void cmd_machine(void);
void flash_checksum_machine(void);
void flash_detect_machine(void);
void method_download_machine(void);
void flash_program_machine(void);
void flash_read_machine(void);
void target_reset_machine(void);

void fluid_machine(void)
{
    int error;

    // If user has specifically asked fluid just to reset the target, we do
    // that and only that! This way, we can use fluid just to reset the
    // target without doing anything else!
    if (arg_target_reset == 2 && arg_dry_run) {
        flowf(VERBOSE, "Resetting target: ");
        target_reset(0);
        target_wait(0, TARGET_RESET_DELAY);
        target_reset(1);
        flowf(VERBOSE, "ok\n");
    }

    file_read_devices("devices.txt");

    if (arg_list_devices) {
        devices_list();
        exit(0);
    }

    image_size = IMAGE_SIZE_MAX;
    image_chunk_size = IMAGE_CHUNK_SIZE;
    image_map_size = IMAGE_SIZE_MAX / image_chunk_size;
    image_size_in_chunks =
        (image_size + image_chunk_size - 1) / image_chunk_size;

    // Read the flash image file(s) unless user wants to read from the flash
    if (*arg_read == 0) {
        file_read_image(image, IMAGE_SIZE_MAX, image_map, image_chunk_size);

        // Optionally overwrite image with values given on the command-line
        parse_arg_write(arg_write);

        if (arg_image_map_show)
            image_map_show();
    }

    if ((error = target_driver_init(arg_uart_port, 115200,
                                    arg_uart_flowcontrol)) < 0)
        main_fatal(error);

    if (target_type_set(arg_target_type) != 0)
        flowf(VERBOSE, "Target type '%s' selected.\n", target[target_type].name);

    // Conditionally disable tracing
    if (arg_debug_trace_pe)
        tr_enable(0);

    if (arg_debug_resume) {
        // If resuming in command interpreter, we should set the baudrate
        // because we do not pass through the code that normally sets it.
        if ((error = target_driver_baudrate(arg_uart_baudrate)) < 0)
            main_fatal(error);
    }
    else {
        bootloader_machine();
        cmd_machine();
    }
    if (*arg_read != 0) {
        parse_arg_read(arg_read);
        flash_read_machine();
        file_write_image(image, image_size, read_list);
    }
    else {
        if (arg_checksum)
            flash_checksum_machine();
        flash_detect_machine();
        if (!arg_debug_resume) {
            method_download_machine();
        }
        tr_enable(1); // Ensure full tracing is on
        flash_program_machine();
        if (*arg_imeisv && *arg_platform_certificate_addr)
            target_imei_protection();
        if (arg_timers_show)
            target_timers_show();
        if (arg_target_reset == 1)
            target_reset_machine();
    }
}


/******************************************************************************
 * Bootloader (TI Target Monitor) Access
 ******************************************************************************/

// Invoke the Fluid bootloader embedded in the TI bootloader/target-monitor
// code. The command to enter the loader is 0xAA 0x01 0xDD, which must be
// received by the phone within 50ms from reset.

int bootloader_is_rom;
int bootloader_is_secure_rom;   // Secure Calypso Plus
int bootloader_blocksize_max;

void bootloader_fluid_init(void);
void bootloader_rom_init(void);
void bootloader_secure_rom_init(void);   // Secure Calypso Plus

void bootloader_machine(void)
{
    // FIXME: why do we have to send an additional char (0) ?
    uint8 sendbuf_rom[3] = { '<', 'i', 0x0 };
    uint8 sendbuf_fluid[3] = { 0xAA, 0x01, 0xDD };
    int i, error;
    char ch1, ch2;
    // Secure Calypso Plus
    UWORD16 d_j;

    target_trace_enable(0);

    flowf(NORMAL, "Bootloader: ");

    // Wait a short while with power removed, then flush receive buffer.
    target_power(1);
    target_reset(0);
    target_wait(0, TARGET_RESET_DELAY);
    target_reset(1);
    target_recv_reset();

    // Continuously send Fluid Bootloader escape sequence until we get an
    // acknowledgement. Note that we have to establish contact within 50ms
    // from reset, otherwise we lose our chance! Note that we actually wait
    // up to 500ms because the target may take some time to reset.
    i = 0;
    flowf(DEBUG, "(ROM/fluid-delay = %d/%d, ",
          arg_boot_delay_rom, arg_boot_delay_fluid);

    switch (arg_rom_bootloader) {
    case -1:
        if ((error = target_driver_baudrate(115200)) < 0)
            main_fatal(error);
        break;
    case +1:
        if ((error = target_driver_baudrate(19200)) < 0)
            main_fatal(error);
        break;
    }
    while (1)
    {
        target_recv_reset();

        // If we are allowed to try fluid bootloader...
        if (arg_rom_bootloader != +1) {
            // If we should ONLY try fluid bootloader...
            flowf(DEBUG, "F");
            if (arg_rom_bootloader == 0)
                if ((error = target_driver_baudrate(115200)) < 0)
                    main_fatal(error);
            target_send(sendbuf_fluid, 3);
            if (target_wait(1, arg_boot_delay_fluid) > 0 &&
                target_getchar() == PROTO_HELLO) {
                bootloader_is_rom = 0;
                bootloader_is_secure_rom = 0;   // Secure Calypso Plus
                break;
            }
        }
        // If we are allowed to try ROM bootloader...
        if (arg_rom_bootloader != -1) {
            flowf(DEBUG, "R");
            if (arg_rom_bootloader == 0)
                if ((error = target_driver_baudrate(19200)) < 0)
                    main_fatal(error);
            target_send(sendbuf_rom, 3);

            if (target_wait(1, arg_boot_delay_rom) >= 1) {
                ch1 = target_getchar();
                if (ch1 == '>') {
                    if (target_wait(1, arg_boot_delay_rom) >= 1) {
                        ch2 = target_getchar();
                        if (ch2 == 'i') {
                            bootloader_is_rom = 1;

                            // Secure Calypso Plus
                            if (target_wait(2, arg_boot_delay_rom) < 2)
                                flowf(DEBUG, ", Non-secure boot ROM code");
                            else {
                                flowf(DEBUG, ", Secure Calypso Plus ROM)");
                                ch1 = target_getchar();
                                ch2 = target_getchar();
                                d_ram_loader.d_romcode_version = (UWORD16)(ch1) & 0xFF;
                                d_ram_loader.d_romcode_version |= ((UWORD16)(ch2 & 0xFF)) << 8;
                                //d_ram_loader.d_romcode_version |= (((UWORD16)ch2) << 8) & 0xFF00;

                                if (d_ram_loader.d_romcode_version == 0x0410 || d_ram_loader.d_romcode_version == 0x0411) {
                                    bootloader_is_secure_rom = 1;

                                    if (target_wait((C_WORD32LGB * C_MD5HASHLG), TARGET_RECV_DELAY) < (C_WORD32LGB * C_MD5HASHLG))
                                        main_fatal(E_RECV_TIMEOUT);

                                    for (d_j = 0 ; d_j < (C_WORD32LGB * C_MD5HASHLG) ; d_j++)
                                        d_ram_loader.a_hash_man_pub_key[d_j] = target_getchar();

                                    if (target_wait((C_WORD32LGB * C_DIE_ID_SIZE), TARGET_RECV_DELAY) < (C_WORD32LGB * C_DIE_ID_SIZE))
                                        main_fatal(E_RECV_TIMEOUT);

                                    for (d_j = 0 ; d_j < (C_WORD32LGB * C_DIE_ID_SIZE) ; d_j++)
                                        d_ram_loader.a_die_id[d_j] = target_getchar();

                                    // SIGNALLING RESPONSE
                                    f_print_signalling_response(VERBOSE);

                                    if (*arg_die_id_file_name != 0) {
                                        if ((error = file_write_die_id(&d_ram_loader.a_die_id[0], arg_die_id_file_name)) < 0) {
                                            flowf(NORMAL, "\n");
                                            main_fatal(error);
                                        }

                                        flowf(NORMAL, "\nDie id retrieved and written to %s.\n", arg_die_id_file_name);
                                        exit(0);
                                    }
                                }
                                else {
                                    flowf(NORMAL, "\nSecure Calypso Plus ROM Code Version 0x%4.4x is not supported by FLUID.\n", d_ram_loader.d_romcode_version);
                                    main_fatal(E_BOOTLOADER);
                                }
                            }
                            // End Secure Calypso Plus

                            break;
                        }
                        else
                            flowf(DEBUG, "?");
                    }
                }
            }
        }
        // If target is still not responding, we could not control the reset
        // line, so we ask the user to reset the target
        if (i++ == RETRIES_MAX)
            flowf (NORMAL, "(reset target) ");
    }
    if (!bootloader_is_secure_rom)
        flowf(DEBUG, ") ");

    // Read the command interpreter image file.
    if (bootloader_is_secure_rom)
        target_program_size = file_read_cmd(target_program, TARGET_PROGRAM_SIZE_MAX, a_certified_cmd_file_name);
    else
        target_program_size = file_read_cmd(target_program, TARGET_PROGRAM_SIZE_MAX, "cmd.m0");

    if (target_program_size == 0)
        main_error(E_FILE_EMPTY);

    if (bootloader_is_secure_rom)
        bootloader_secure_rom_init();
    else if (bootloader_is_rom)
        bootloader_rom_init();
    else
        bootloader_fluid_init();

    flowf(NORMAL, ") ok\n");
}

void bootloader_fluid_init(void)
{
    int divider;
    char version;
    uint8 data[4];
    uint16 chip_id_code;

    flowf(NORMAL, "(fluid");

    target_clk = 13000000;

    // Now send baudrate to make the target stop sending 'H'ello.
    divider = target_uart_baudrate_divider_get(target_clk, 115200);
    flowf(DEBUG, ", baudrate = ");
    target_putchar(0);
    target_putchar((char) divider);
    flowf(DEBUG, "%d, ", 115200);

    // Wait a short while before flushing receive buffer. Then make sure
    // that the target is indeed silent
    target_wait(0, 10);
    target_recv_reset();
    if (target_wait(1, 50) > 0)
        main_fatal(E_RECV_ANTITIMEOUT);

    // NOTEME: The sequence below to query the Bootloader VERSION *has* to
    // have 100ms + 100ms delays. It seems as unnecessarily large delays but
    // practice shows that they cannot be shorter. To be investigated...

    // Get version of bootloader
    target_putchar(PROTO_VERSION);
    if (target_wait(1, TARGET_RECV_DELAY) < 1)
        main_fatal(E_RECV_TIMEOUT);
    version = target_getchar();
    flowf(NORMAL, ", version %c", version);

    // Bootloader revision 3 and upwards supports generic query
    if (version < '3') {
        main_fatal(E_BOOTLOADER);
    }

    target_trace_enable(1);
    target_putchar(PROTO_QUERY);
    target_putchar(PROTO_QUERY_CHIP);
    if (target_wait(4, TARGET_RECV_DELAY) < 4)
        main_fatal(E_RECV_TIMEOUT);
    target_recv(data, 4);
    chip_id_code = data[0] + (data[1] << 8);
    if (target_type_set(chip_id_code) == 0 || arg_verbose >= VERBOSE) {
        flowf(VERBOSE, ", chipid = 0x%04X", chip_id_code);
        if (target_type_set(chip_id_code) == 0)
            main_fatal(E_TARGET_TYPE);
    }
    flowf(VERBOSE, ", %s", target[target_type].name);
}

void bootloader_rom_init(void)
{
    int error;
    char ch, version = '?';

    // Initialization: 115200 bps, 39 MHz DPLL, no timeout
    uint8 sendbuf[11] = { '<',  'p',  0x00, 0x0D, 0x14, 0x25,
                           0x22, 0x00, 0x00, 0x00, 0x00};
    // Initialization: 115200 bps, 13 MHz DPLL, no timeout.
    //uint8 sendbuf[11] = { '<',  'p',  0x00, 0x00, 0x1C, 0xE7,
    //                    0x22, 0x00, 0x00, 0x00, 0x00 };

    flowf(NORMAL, "(ROM");

    // Configure/init the ROM bootloader
    flowf(VERBOSE, ", baudrate = %d", 115200);
    target_send(sendbuf, 11);

    // Wait until DPLL is settled and target responds
    if (target_wait(4, 300) < 4)
        main_fatal(E_RECV_TIMEOUT);
    if ((ch = target_getchar ()) != '>')
        error_proto(ch, '>');
    if ((ch = target_getchar ()) != 'p')
        error_proto(ch, 'p');

    // Receive maximum blocksize
    bootloader_blocksize_max  = target_getchar();
    bootloader_blocksize_max += target_getchar() << 8;
    bootloader_blocksize_max -= 10; // Subtract the block header
    flowf(DEBUG, ", blocksize = %iB", bootloader_blocksize_max);

    if ((error = target_driver_baudrate (115200)) < 0)
        main_fatal(error);

    flowf(NORMAL, ", version %c", version);

    // NOTEME: Can we be sure that it is always a Calypso type?
    target_type_set('c');
}

// Secure Calypso Plus
void bootloader_secure_rom_init(void)
{
    int error;
    UWORD8 d_char;
    UWORD16 d_j;

    // cmdp_cert.m0 has been 16 bit aligned using hex470, so the memory width
    // parameter is set to 2 bytes.
    buffer_endian_convert(target_program, target_program_size, 2);

    // Check if firmware manufacturer certificate stored in flash is requested
    if (arg_request_certificate) {
        // Clear the request
        arg_request_certificate = 0;
        d_ram_loader.b_certificate_request = C_FALSE;

        // Certificate Request
        d_frame.a_data[0] = '<';
        d_frame.a_data[1] = 'c';
        d_frame.d_max_byte = 2;

        target_send(&d_frame.a_data[0], d_frame.d_max_byte);

        // Certificate Response
        target_expect_char('>', arg_boot_delay_rom);
        target_expect_char('c', arg_boot_delay_rom);

        if (target_wait(2, TARGET_RECV_DELAY) < 2)
            main_fatal(E_RECV_TIMEOUT);

        d_ram_loader.u_firm_cert.a_firm_cert[0] = target_getchar ();
        d_ram_loader.u_firm_cert.a_firm_cert[1] = target_getchar ();

        d_certificate_length = (UWORD16)(d_ram_loader.u_firm_cert.a_firm_cert[0]) & 0xFF;
        d_certificate_length |= ((UWORD16)(d_ram_loader.u_firm_cert.a_firm_cert[1]) << 8) & 0xFF00;

        // We have already read two bytes of the certificate for the CERT_SIZE
        // and the certificate signature is handled later.
        for (d_j = 2 ; d_j < d_certificate_length - (C_WORD32LGB * C_MANUF_SIG_SIZE); d_j++) {
            if (target_wait(1, arg_boot_delay_rom * 4) > 0)
                d_ram_loader.u_firm_cert.a_firm_cert[d_j] = target_getchar ();
            else
                main_fatal(E_RECV_TIMEOUT);
        }

        for (d_j = 0; d_j < (C_WORD32LGB * C_MANUF_SIG_SIZE); d_j++) {
            if (target_wait(1, arg_boot_delay_rom * 4) > 0)
                d_ram_loader.a_Certsig[d_j] = target_getchar ();
            else
                main_fatal(E_RECV_TIMEOUT);

            d_ram_loader.u_firm_cert.a_firm_cert[d_j + sizeof(T_MANUFACTURER_CERTIFICATE_PLATFORM_DATA) - (C_WORD32LGB * C_MANUF_SIG_SIZE)] = d_ram_loader.a_Certsig[d_j];
        }

        f_print_certificate_platform_data(NORMAL, &d_ram_loader.u_firm_cert.d_firm_cert);
    }

    // Parameter Request
    d_ram_loader.d_baud_rate = f_convert_uart_baud_rate(arg_uart_baudrate_during_cmd_download);
    d_ram_loader.d_uart_timeout = arg_uart_timeout_configuration;   // TODO: Verify that MSB byte is sent in first position.

    d_frame.a_data[0] = '<';
    d_frame.a_data[1] = 'p';
    d_frame.a_data[2] = d_ram_loader.d_baud_rate;
    memcpy(&d_frame.a_data[3], &d_ram_loader.d_uart_timeout, sizeof(UWORD32));

    d_frame.d_max_byte = 3 + sizeof(UWORD32);

    d_certificate_length = (UWORD16)(target_program[0]) & 0xFF;
    d_certificate_length |= ((UWORD16)(target_program[1]) & 0xFF) << 8;

    for (d_j = 0 ; d_j < d_certificate_length; d_j++)
        d_ram_loader.u_code_certificate.a_code_certificate[d_j] = target_program[d_j];

    f_print_certificate(DEBUG, &d_ram_loader.u_code_certificate.d_code_certificate);

    // Send the data
    target_send(&d_frame.a_data[0], d_frame.d_max_byte);
    target_send(&target_program[0], d_certificate_length);

    // Parameter Response
    if (target_wait(2, TARGET_RECV_DELAY) < 2)
        main_fatal(E_RECV_TIMEOUT);

    if ((d_char = target_getchar ()) != '>')
        error_proto(d_char, '>');

    if ((d_char = target_getchar ()) != 'p') {
        if (d_char == 'P') {
            // PARAMETER_NACK_RESPONSE
            if (target_wait(1, arg_boot_delay_rom) >= 1) {
                d_ram_loader.d_param_req_sts = target_getchar();
                f_print_parameter_nack_status(NORMAL, d_ram_loader.d_param_req_sts);
            }
        }
        error_proto(d_char, 'p');
    }

    // PARAMETER_ACK_RESPONSE

    flowf(NORMAL, "\n(Secure ROM");

    if ((error = target_driver_baudrate (arg_uart_baudrate_during_cmd_download)) < 0)
        main_fatal(error);

    // Configure/init the ROM bootloader
    flowf(VERBOSE, ", UART baud rate during download of flash programmer, %s = %d Kbps", a_certified_cmd_file_name, arg_uart_baudrate_during_cmd_download);
}
// End Secure Calypso Plus

/******************************************************************************
 * Command Interpreter Download
 ******************************************************************************/

int cmd_baudrate(int baudrate);
void cmd_machine_fluid(void);
void cmd_machine_rom(void);
void cmd_machine_secure_rom(void);   // Secure Calypso Plus

void cmd_machine(void)
{
    char ramcs0, ch;
    uint8 count = 0;
    uint16 chip_id;

    flowf(VERBOSE, "Command Interpreter: (");

    target_trace_enable(0);

    // Secure Calypso Plus
    if (bootloader_is_secure_rom)
        cmd_machine_secure_rom();
    else if (bootloader_is_rom)
        cmd_machine_rom();
    else
        cmd_machine_fluid();

    // Now send 'H'ello command to target and get the response, such as
    // hardware type etc.
    while (count < RETRIES_MAX) {
        target_putchar(PROTO_HELLO);
        if (target_wait(1, TARGET_RECV_DELAY) >= 1) {
            ch = target_getchar();
            flowf(DEBUG, ", received %c (0x%2.2x)", ch, ch);
            if (ch == PROTO_READY)
                break;
        }
        count++;
    }

    if (count == RETRIES_MAX)
        main_fatal(E_RECV_TIMEOUT);

    if (target_wait(5, TARGET_RECV_DELAY) < 4)
        main_fatal(E_RECV_TIMEOUT);

    chip_id  = (target_getchar() & 0xFF);
    chip_id |= (target_getchar() & 0xFF) << 8;
    ramcs0   = (target_getchar() == 'R');
    ch       = target_getchar();
    flowf(BLABBER, ", SRAM = %dk", (1 << ch) / 1024);
    target_fifo_size = target_getchar();
    flowf(DEBUG, ", fifo = %d", target_fifo_size);

    if (target_type_set(chip_id) == 0 || arg_verbose >= VERBOSE) {
        flowf(BLABBER, ", ");
        flowf(VERBOSE, "chipid = 0x%04X", chip_id);
        if (target_type_set(chip_id) == 0)
            main_fatal(E_TARGET_TYPE);
    }
    flowf(VERBOSE, ", %s", target[target_type].name);

    if (ramcs0)
        flowf(VERBOSE, ", RAM");

    // Configure target
    target_putchar(PROTO_INIT);
    target_putchar(target[target_type].type);
    target_expect_char(PROTO_READY, TARGET_RECV_DELAY);

    // Change baudrate. First try to change to new baudrate. If this fails,
    // try 115.2 Kbps. If this also fails, panic and bail out.
    if (arg_uart_baudrate != 115200) {
        flowf(VERBOSE, ", baudrate = ");
        flowf(VERBOSE, "%d", arg_uart_baudrate);
        if (!cmd_baudrate(arg_uart_baudrate)) {
            arg_uart_baudrate = 115200;
            flowf(VERBOSE, " %d", arg_uart_baudrate);
            if (!cmd_baudrate(arg_uart_baudrate))
                main_fatal(E_RECV_TIMEOUT);
        }
    }
    flowf(VERBOSE, ") ");
    flowf(VERBOSE, "ok\n");

    // Secure Calypso Plus

    // There is a bug in ROM code 0x0410 which means that the secure boot
    // loader cannot boot if the firmware certificate size is greater than the
    // max size of 0xFFF8. A workaround is to change the memory mapping on CS5
    // by initially setting DIP switches 9 and 10 to ON and thereby exchanging
    // the mapping of RAM and flash. While waiting, set the DIP switches back
    // to OFF. See SECURITY.txt and BUG03314 in CALPLUS228.
    if (arg_delay_for_changing_cs5 != 0) {
        flowf(NORMAL, "\nWaiting %d seconds for changing memory mapping on CS5 or connecting via JTAG...\n", arg_delay_for_changing_cs5);
        target_wait(0, arg_delay_for_changing_cs5 * 1000);
    }
}

int cmd_baudrate(int baudrate)
{
    int error;
    int divider;
    char xxo;

    target_putchar(PROTO_BAUDRATE);
    if (baudrate == 230400 || baudrate == 460800 || baudrate == 921600) {
        // Changing the target clock frequency to 14 MHz
        target_clk = 14745600;
        // If we are using a 14 MHz compatible baud rate, we should enable the
        // eXternal Xtal Oscillator of the target
        xxo = PROTO_BAUDRATE_XXO;
    }
    else {
        target_clk = 13000000;
        xxo = 0;
    }

    divider = target_uart_baudrate_divider_get(target_clk, baudrate);
    target_putchar(xxo);
    target_putchar((char) divider);

    if ((error = target_driver_baudrate(baudrate)) < 0)
        main_fatal(error);

    // Wait for acknowledgement
    if (target_wait(1, 2 * TARGET_RECV_DELAY) >= 1 &&
        target_getchar() == PROTO_READY) {
        return 1;
    }
    else {
        if ((error = target_driver_baudrate(115200)) < 0)
            main_fatal(error);
        return 0;
    }
}

void cmd_machine_fluid(void)
{
    uint8 sendbuf[1+4+2];
    int error;

    // Send 'Download' command header. Then wait for acknowledgement.
    buf_put1(&sendbuf[0],   PROTO_DOWNLOAD);
    buf_put4(&sendbuf[1],   target[target_type].cmd_load_addr);
    buf_put2(&sendbuf[1+4], (uint16) target_program_size / 2);
    target_send(sendbuf, 1+4+2);
    flowf(BLABBER, "0x%X", target[target_type].cmd_load_addr);
    if ((error = target_expect_char(PROTO_READY, TARGET_RECV_DELAY)) < 0)
        main_fatal(error);

    // Send data. Then wait for acknowledgement.
    target_trace_enable(1);
    target_send(target_program, target_program_size);
    flowf(BLABBER, ", %d", target_program_size);
}

void cmd_machine_rom(void)
{
    uint8 sendbuf[10], *buf;
    int error, i;
    int block_addr;
    int block_offset = 0;
    int block_size;
    uint8 ch, blksum, cksum = 0;

    // NOTEME: Can we be sure that it is always a Calypso type?
    target_type_set('c');

    block_addr = target[target_type].cmd_load_addr;

    buffer_endian_convert(target_program, target_program_size, 2);

    flowf(BLABBER, "0x%X, ", target[target_type].cmd_load_addr);

    // Transfer the target program as blocks
    while (block_offset < target_program_size)
    {
        block_size = target_program_size - block_offset;
        if (block_size > bootloader_blocksize_max)
            block_size = bootloader_blocksize_max;

        // Initialize block transfer
        buf  = sendbuf;
        buf += buf_put1(buf, '<');
        buf += buf_put1(buf, 'w');
        buf += buf_put1(buf, 0x01); // block index - just use #1 - not important
        buf += buf_put1(buf, 0x01); // block number - just use #1 - not important
        buf += buf_put2no(buf, (uint16) block_size);
        buf += buf_put4no(buf, block_addr + block_offset);
        target_send(sendbuf, 10);

        // Send the data
        target_send(&target_program[block_offset], block_size);

        // Calculate block check-sum
        blksum  = 5;
        blksum += block_size & 0x00ff;
        blksum += (((block_addr + block_offset) & 0xff000000) >> 24);
        blksum += (((block_addr + block_offset) & 0x00ff0000) >> 16);
        blksum += (((block_addr + block_offset) & 0x0000ff00) >> 8);
        blksum += ( (block_addr + block_offset) & 0x000000ff);
        for (i = block_offset; i < (block_offset + block_size); i++)
            blksum += target_program[i];
        cksum += ~blksum;

        block_offset = block_offset + block_size;

        if ((error = target_expect_char ('>', TARGET_RECV_DELAY)) < 0)
            main_fatal (error);
        if ((error = target_expect_char ('w', TARGET_RECV_DELAY)) < 0)
            main_fatal (error);

        flowf(BLABBER, ".");
    }
    flowf(BLABBER, ", %d", target_program_size);
    target_wait(0, 100);

    // Request compare of checksum
    target_putchar('<');
    target_putchar('c');
    target_putchar((char) ~cksum);

    if ((error = target_expect_char('>', TARGET_RECV_DELAY)) < 0)
        main_fatal(error);
    if ((error = target_expect_char('c', TARGET_RECV_DELAY)) < 0)
        main_fatal(error);
    if ((error = target_wait(1, TARGET_RECV_DELAY)) < 1)
        main_fatal(error);
    ch = target_getchar();
    flowf(DEBUG, ", cksum = 0x%x (0x%x)", ch, cksum);
    if (ch != cksum)
        main_fatal(E_SEND_CHECKSUM);

    // Branch to code
    buf  = sendbuf;
    buf += buf_put1(buf, '<');
    buf += buf_put1(buf, 'b');
    buf += buf_put4no(buf, target[target_type].cmd_load_addr);
    target_send(sendbuf, 6);

    if (target_wait(2, TARGET_RECV_DELAY) < 2)
        main_fatal(E_RECV_TIMEOUT);
    if ((ch = target_getchar()) != '>')
        error_proto(ch, '>');
    if ((ch = target_getchar()) != 'b')
        error_proto(ch, 'b');

    target_wait(0, 100);
}

// Secure Calypso Plus
void cmd_machine_secure_rom(void)
{
    UWORD8 d_char;
    UWORD16 d_i;

    target_type_set('p');
    flowf(BLABBER, "0x%X", d_ram_loader.u_code_certificate.d_code_certificate.d_Addcode);
    flowf(DEBUG, ")");

    // Prepare the write command
    d_ram_loader.d_nb_byte_in_block = (UWORD32) arg_block_size;
    d_ram_loader.d_nb_byte_sent = 0;
    d_ram_loader.d_block_address = d_ram_loader.u_code_certificate.d_code_certificate.d_Addcode;

    while (d_ram_loader.d_nb_byte_sent < d_ram_loader.u_code_certificate.d_code_certificate.d_Codesize) {
        flowf(DEBUG, "\nCurrent write parameters:\n");
        flowf(DEBUG, "        Max Number of Bytes in Block: %ld\n", d_ram_loader.d_nb_byte_in_block);
        flowf(DEBUG, "        Block Address               : 0x%8.8lx\n", d_ram_loader.d_block_address);
        flowf(DEBUG, "        Code Size                   : %ld\n", d_ram_loader.u_code_certificate.d_code_certificate.d_Codesize);
        flowf(DEBUG, "        Bytes sent                  : %ld\n", d_ram_loader.d_nb_byte_sent);

        // Set block size
        d_ram_loader.d_block_size = d_ram_loader.u_code_certificate.d_code_certificate.d_Codesize - d_ram_loader.d_nb_byte_sent;
        if (d_ram_loader.d_block_size > d_ram_loader.d_nb_byte_in_block)
            d_ram_loader.d_block_size = d_ram_loader.d_nb_byte_in_block;

        flowf(DEBUG, "        Block Size                  : %ld\n", d_ram_loader.d_block_size);

        // Write Request
        d_frame.a_data[0] = '<';
        d_frame.a_data[1] = 'w';

        // Send block size
        for (d_i = 0; d_i < 32; d_i += 8)
            d_frame.a_data[2 + d_i / 8] = (UWORD8)(d_ram_loader.d_block_size >> (24 - d_i));

        // Send block address
        for (d_i = 0; d_i < 32; d_i += 8)
            d_frame.a_data[2 + sizeof(UWORD32) + d_i / 8] = (UWORD8)(d_ram_loader.d_block_address >> (24 - d_i));

        d_frame.d_max_byte = 2 + sizeof(UWORD32) + sizeof(UWORD32);

        // Send the data
        target_send(&d_frame.a_data[0], d_frame.d_max_byte);
        target_send(&target_program[d_certificate_length + d_ram_loader.d_nb_byte_sent], d_ram_loader.d_block_size);

        // Update next block address
        d_ram_loader.d_block_address += d_ram_loader.d_block_size;
        d_ram_loader.d_nb_byte_sent  += d_ram_loader.d_block_size;

        // Write Response
        if (target_wait(2, TARGET_RECV_DELAY) < 2)
            main_fatal(E_RECV_TIMEOUT);

        if ((d_char = target_getchar ()) != '>')
            error_proto(d_char, '>');

        if ((d_char = target_getchar ()) != 'w') {
            if (d_char == 'W') {
                // WRITE_NACK_RESPONSE
                if (target_wait(1, arg_boot_delay_rom) >= 1) {
                    d_ram_loader.d_write_status = target_getchar();
                    f_print_write_nack_status(NORMAL, d_ram_loader.d_write_status);
                }
            }
            error_proto(d_char, 'w');
        }

        // WRITE_ACK_RESPONSE
    }  // End while
    if (arg_verbose == BLABBER)
        flowf(BLABBER, ", ");
    else
        flowf(DEBUG, "(");

    flowf(BLABBER, "%d", d_ram_loader.d_nb_byte_sent);
    target_wait(0, 100);

    // Abort Request
    d_frame.a_data[0] = '<';
    d_frame.a_data[1] = 'a';
    d_frame.d_max_byte = 2;

    target_send(&d_frame.a_data[0], d_frame.d_max_byte);
    target_wait(0, 100);
}

void f_print_signalling_response(int level) {
    UWORD16 d_i;

    flowf(level, "\nSignalling Response:\n");

    flowf(level, "        ROM Code Version: 0x%4.4x\n", d_ram_loader.d_romcode_version);

    flowf(level, "        Hash (ManPubKey): ");
    for (d_i = 0; d_i < (C_WORD32LGB * C_MD5HASHLG); d_i++) {
        if (d_i == (C_WORD32LGB * C_MD5HASHLG) / 2)
            flowf(level, "\n                          ");
        flowf(level, "0x%2.2x ", d_ram_loader.a_hash_man_pub_key[d_i]);
    }
    flowf(level, "\n");

    flowf(level, "        Die Id          : ");
    for (d_i = 0; d_i < (C_WORD32LGB * C_DIE_ID_SIZE); d_i++)
        flowf(level, "0x%2.2x ", d_ram_loader.a_die_id[d_i]);
    flowf(level, "\n");
} /* f_print_signalling_response() */

void f_print_certificate_platform_data(int level, T_MANUFACTURER_CERTIFICATE_PLATFORM_DATA *p_certificate) {
    UWORD16 d_i, d_j, d_max = 4;

    flowf(level, "\nFirmware Manufacturer Certificate:\n");
    flowf(level, "----------------------------------\n");

    flowf(level, "        Size of Certificate: %d bytes\n", p_certificate->d_manufacturer_certificate.d_Certsize);
    flowf(level, "        Type of Certificate: 0x%2.2x\n", p_certificate->d_manufacturer_certificate.d_Certtype);
    flowf(level, "        Emulation Request  : 0x%2.2x\n", p_certificate->d_manufacturer_certificate.d_Debugrequest);
    flowf(level, "        Address of Code    : 0x%8.8lx\n", p_certificate->d_manufacturer_certificate.d_Addcode);
    flowf(level, "        Size of Code       : %ld bytes\n", p_certificate->d_manufacturer_certificate.d_Codesize);
    flowf(level, "        Entry Point Address: 0x%8.8lx\n", p_certificate->d_manufacturer_certificate.d_CodeStartAdd);

    flowf(level, "        Manufacturer Public Key:\n");
    flowf(level, "                Public Modulus:");
    d_j = d_max;
    for (d_i = 0; d_i < (C_RSAKEYLG); d_i++) {
        if (d_j++ == d_max) {
            flowf(level, "\n                        ");
            d_j = 1;
        }
        flowf(level, "0x%8.8lx ", p_certificate->d_manufacturer_certificate.d_Manpubkey.a_Modulus[d_i]);
    }
    flowf(level, "\n");
    flowf(level, "                Public Modulus Length:\n");
    flowf(level, "                        0x%8.8lx\n", p_certificate->d_manufacturer_certificate.d_Manpubkey.d_ModulusLength);
    flowf(level, "                Public Exponent:\n");
    flowf(level, "                        0x%8.8lx\n", p_certificate->d_manufacturer_certificate.d_Manpubkey.d_Exponent);

    flowf(level, "        Originator Public Key:\n");
    flowf(level, "                Public Modulus:");
    d_j = d_max;
    for (d_i = 0; d_i < (C_RSAKEYLG); d_i++) {
        if (d_j++ == d_max) {
            flowf(level, "\n                        ");
            d_j = 1;
        }
        flowf(level, "0x%8.8lx ", p_certificate->d_manufacturer_certificate.d_Origpubkey.a_Modulus[d_i]);
    }
    flowf(level, "\n");
    flowf(level, "                Public Modulus Length:\n");
    flowf(level, "                        0x%8.8lx\n", p_certificate->d_manufacturer_certificate.d_Origpubkey.d_ModulusLength);
    flowf(level, "                Public Exponent:\n");
    flowf(level, "                        0x%8.8lx\n", p_certificate->d_manufacturer_certificate.d_Origpubkey.d_Exponent);

    flowf(level, "        Originator Public Key Signature:");
    d_j = d_max;
    for (d_i = 0; d_i < (C_RSASIGLG); d_i++) {
        if (d_j++ == d_max) {
            flowf(level, "\n                ");
            d_j = 1;
        }
        flowf(level, "0x%8.8lx ", p_certificate->d_manufacturer_certificate.a_Origpubkeysig[d_i]);
    }
    flowf(level, "\n");

    flowf(level, "        Software Signature:");
    d_j = d_max;
    for (d_i = 0; d_i < (C_RSASIGLG); d_i++) {
        if (d_j++ == d_max) {
            flowf(level, "\n                ");
            d_j = 1;
        }
        flowf(level, "0x%8.8lx ", p_certificate->d_manufacturer_certificate.a_Swsig[d_i]);
    }
    flowf(level, "\n");

    flowf(level, "        Configuration Parameters:\n");
    flowf(level, "                CONF_CS5 register: %4.4x\n",p_certificate->d_manufacturer_certificate.d_Confparam.d_conf_cs5);
    flowf(level, "                EXWS_CS5 register: %4.4x\n",p_certificate->d_manufacturer_certificate.d_Confparam.d_exws_cs5);
    flowf(level, "                EX_CTRL register : %4.4x\n",p_certificate->d_manufacturer_certificate.d_Confparam.d_ex_ctrl);
    flowf(level, "                CS image request : %4.4x\n",p_certificate->d_manufacturer_certificate.d_Confparam.d_cs_img_req);
    flowf(level, "                Flash size       : %ld bytes\n",p_certificate->d_manufacturer_certificate.d_Confparam.d_flash_size);
    flowf(level, "                Granularity      : %ld words\n",p_certificate->d_manufacturer_certificate.d_Confparam.d_granularity);

    flowf(level, "        Die Id: ");
    for(d_i = 0; d_i < (C_DIE_ID_SIZE); d_i++) {
        flowf(level, "0x%8.8lx ", p_certificate->d_manufacturer_certificate.a_die_id[d_i]);
    }
    flowf(level, "\n");

    if (p_certificate->d_manufacturer_certificate.d_Certsize > (sizeof(T_MANUFACTURER_CERTIFICATE) + (C_WORD32LGB * C_MANUF_SIG_SIZE))) {
        flowf(level, "        Platform Data:");
        d_j = d_max;
        for(d_i = 0; d_i < (p_certificate->d_manufacturer_certificate.d_Certsize - sizeof(T_MANUFACTURER_CERTIFICATE) - (C_WORD32LGB * C_MANUF_SIG_SIZE)) / sizeof(UWORD32); d_i++) {
            if (d_j++ == d_max) {
                flowf(level, "\n                ");
                d_j = 1;
            }
            flowf(level, "0x%8.8lx ", p_certificate->a_platform_data[d_i]);
        }
        flowf(level, "\n");
    }

    flowf(level, "        Certificate Signature:");
    d_j = d_max;
    for (d_i = 0; d_i < (C_MANUF_SIG_SIZE); d_i++) {
        if (d_j++ == d_max) {
            flowf(level, "\n                ");
            d_j = 1;
        }
        flowf(level, "0x%8.8lx ", p_certificate->a_Certsig[d_i]);
    }
    flowf(level, "\n");
} /* f_print_certificate_platform_data() */

void f_print_certificate(int level, T_MANUFACTURER_CERTIFICATE_FLASH_PROGRAMMER *p_certificate) {
    UWORD16 d_i, d_j, d_max = 4;

    flowf(level, "\nFlash Programmer Manufacturer Certificate:\n");
    flowf(level, "------------------------------------------\n");

    flowf(level, "        Size of Certificate: %d bytes\n", p_certificate->d_Certsize);
    flowf(level, "        Type of Certificate: 0x%2.2x\n", p_certificate->d_Certtype);
    flowf(level, "        Emulation Request  : 0x%2.2x\n", p_certificate->d_Debugrequest);
    flowf(level, "        Address of Code    : 0x%8.8lx\n", p_certificate->d_Addcode);
    flowf(level, "        Size of Code       : %ld bytes\n", p_certificate->d_Codesize);
    flowf(level, "        Entry Point Address: 0x%8.8lx\n", p_certificate->d_CodeStartAdd);

    flowf(level, "        Manufacturer Public key:\n");
    flowf(level, "                Public Modulus:");
    d_j = d_max;
    for (d_i = 0; d_i < (C_RSAKEYLG); d_i++) {
        if (d_j++ == d_max) {
            flowf(level, "\n                        ");
            d_j = 1;
        }
        flowf(level, "0x%8.8lx ", p_certificate->d_Manpubkey.a_Modulus[d_i]);
    }
    flowf(level, "\n");
    flowf(level, "                Public Modulus Length:\n");
    flowf(level, "                        0x%8.8lx\n", p_certificate->d_Manpubkey.d_ModulusLength);
    flowf(level, "                Public Exponent:\n");
    flowf(level, "                        0x%8.8lx\n", p_certificate->d_Manpubkey.d_Exponent);

    flowf(level, "        Originator Public key:\n");
    flowf(level, "                Public Modulus:");
    d_j = d_max;
    for (d_i = 0; d_i < (C_RSAKEYLG); d_i++) {
        if (d_j++ == d_max) {
            flowf(level, "\n                        ");
            d_j = 1;
        }
        flowf(level, "0x%8.8lx ", p_certificate->d_Origpubkey.a_Modulus[d_i]);
    }
    flowf(level, "\n");
    flowf(level, "                Public Modulus Length:\n");
    flowf(level, "                        0x%8.8lx\n", p_certificate->d_Origpubkey.d_ModulusLength);
    flowf(level, "                Public Exponent:\n");
    flowf(level, "                        0x%8.8lx\n", p_certificate->d_Origpubkey.d_Exponent);

    flowf(level, "        Originator Public Key Signature:");
    d_j = d_max;
    for (d_i = 0; d_i < (C_RSASIGLG); d_i++) {
        if (d_j++ == d_max) {
            flowf(level, "\n                ");
            d_j = 1;
        }
        flowf(level, "0x%8.8lx ", p_certificate->a_Origpubkeysig[d_i]);
    }
    flowf(level, "\n");

    flowf(level, "        Software Signature:");
    d_j = d_max;
    for (d_i = 0; d_i < (C_RSASIGLG); d_i++) {
        if (d_j++ == d_max) {
            flowf(level, "\n                ");
            d_j = 1;
        }
        flowf(level, "0x%8.8lx ", p_certificate->a_Swsig[d_i]);
    }
    flowf(level, "\n");

    flowf(level, "        Configuration Parameters:\n");
    flowf(level, "                CONF_CS5 register: %4.4x\n",p_certificate->d_Confparam.d_conf_cs5);
    flowf(level, "                EXWS_CS5 register: %4.4x\n",p_certificate->d_Confparam.d_exws_cs5);
    flowf(level, "                EX_CTRL register : %4.4x\n",p_certificate->d_Confparam.d_ex_ctrl);
    flowf(level, "                CS image request : %4.4x\n",p_certificate->d_Confparam.d_cs_img_req);
    flowf(level, "                Flash size       : %ld bytes\n",p_certificate->d_Confparam.d_flash_size);
    flowf(level, "                Granularity      : %ld words\n",p_certificate->d_Confparam.d_granularity);

    flowf(level, "        Die Id: ");
    for(d_i = 0; d_i < (C_DIE_ID_SIZE); d_i++) {
        flowf(level, "0x%8.8lx ", p_certificate->a_die_id[d_i]);
    }
    flowf(level, "\n");

    flowf(level, "        Certificate Signature:");
    d_j = d_max;
    for (d_i = 0; d_i < (C_MANUF_SIG_SIZE); d_i++) {
        if (d_j++ == d_max) {
            flowf(level, "\n                ");
            d_j = 1;
        }
        flowf(level, "0x%8.8lx ", p_certificate->a_Certsig[d_i]);
    }
    flowf(level, "\n");
} /* f_print_certificate() */

void f_print_parameter_nack_status(int level, UWORD8 d_parameter_nack_sts) {
    flowf(level, "\nParameter NAck Response Status:\n");
    flowf(level, "        0x%2.2x", d_parameter_nack_sts);

    switch(d_parameter_nack_sts) {
        case 0x01 : { flowf(level, "    Incorrect baud rate\n"); break; }
        case 0x02 : { flowf(level, "    Incorrect certificate\n"); break; }
        case 0x03 : { flowf(level, "    Incorrect code address\n"); break; }
    }

    flowf(level, "\n");
} /* f_print_parameter_nack_status() */

void f_print_write_nack_status(int level, UWORD8 d_write_nack_sts) {
    flowf(level, "\nWrite NAck Response Status:\n");
    flowf(level, "        0x%2.2x", d_write_nack_sts);

    switch(d_write_nack_sts) {
        case 0x01 : { flowf(level, "    Incorrect block address\n"); break; }
        case 0x02 : { flowf(level, "    Non-64 bytes block size\n"); break; }
        case 0x03 : { flowf(level, "    First block is not code address\n"); break; }
        case 0x04 : { flowf(level, "    Error in firmware signature check\n"); break; }
        case 0x05 : { flowf(level, "    Received code size does not match the code size in certificate\n"); break; }
        case 0x06 : { flowf(level, "    Error during block hashing\n"); break; }
    }

    flowf(level, "\n");
} /* f_print_write_nack_status() */

UWORD8 f_convert_uart_baud_rate(UWORD32 d_baud_rate) {
    UWORD8 d_converted_baud_rate;

    switch (d_baud_rate) {
        case 0:
        case 812:
        case 812500: {
            d_converted_baud_rate = 0x00;
            arg_uart_baudrate_during_cmd_download = 812500;
            break;
        }
        case 1:
        case 406:
        case 406250: {
            d_converted_baud_rate = 0x01;
            arg_uart_baudrate_during_cmd_download = 406250;
            break;
        }
        case 2:
        case 203:
        case 203125: {
            d_converted_baud_rate = 0x02;
            arg_uart_baudrate_during_cmd_download = 203125;
            break;
        }
        case 3:
        case 115:
        case 115200: {
            d_converted_baud_rate = 0x03;
            arg_uart_baudrate_during_cmd_download = 115200;
            break;
        }
        case 4:
        case 57:
        case 57600: {
            d_converted_baud_rate = 0x04;
            arg_uart_baudrate_during_cmd_download = 57600;
            break;
        }
        case 5:
        case 38:
        case 38400: {
            d_converted_baud_rate = 0x05;
            arg_uart_baudrate_during_cmd_download = 38400;
            break;
        }
        case 6:
        case 28:
        case 28800: {
            d_converted_baud_rate = 0x06;
            arg_uart_baudrate_during_cmd_download = 28800;
            break;
        }
        case 7:
        case 19:
        case 19200: {
            d_converted_baud_rate = 0x07;
            arg_uart_baudrate_during_cmd_download = 19200;
            break;
        }
        default: {
            d_converted_baud_rate = 0x03;
            arg_uart_baudrate_during_cmd_download = 115200;
            break;
        }
    }

    return d_converted_baud_rate;
} /* f_convert_uart_baud_rate() */

void target_imei_protection(void)
{
    uint8 a_imeisv[C_IMEISV_BYTES];
    uint8 a_platform_cert_addr[C_PLATFORM_CERT_ADDR_BYTES];
    uint8 d_i, d_digit, d_temp;
    uint8 d_error;

    flowf(NORMAL, "\nIMEI protection: ");

    if (arg_dry_run) {
        flowf(NORMAL, "(dry-run) ok\n");
        return;
    }

    target_putchar(PROTO_IMEI_PROTECT);

    // Send IMEI-SV
    for (d_i = 0; d_i < C_IMEISV_DIGITS; d_i++) {
        sscanf(arg_imeisv++, "%1d", &d_digit);
        if (!(d_i & 1))
            d_temp = d_digit << 4;
        else {
            d_temp |= d_digit;
            a_imeisv[d_i / 2] = d_temp;
        }
    }

    target_send(a_imeisv, C_IMEISV_BYTES);
    target_expect_char(PROTO_READY, TARGET_RECV_DELAY);

    // Send platform certificate address
    for (d_i = 0; d_i < C_PLATFORM_CERT_ADDR_DIGITS; d_i++) {
        sscanf(arg_platform_certificate_addr++, "%1x", &d_digit);
        if (!(d_i & 1))
            d_temp = d_digit << 4;
        else {
            d_temp |= d_digit;
            a_platform_cert_addr[d_i / 2] = d_temp;
        }
    }

    target_send(a_platform_cert_addr, C_PLATFORM_CERT_ADDR_BYTES);
    target_expect_char(PROTO_READY, TARGET_RECV_DELAY);

    // Check if address range is erased
    if (target_wait(1, TARGET_RECV_DELAY) < 1)
        main_fatal(E_RECV_TIMEOUT);

    d_error = target_getchar();
    if (d_error == PROTO_ERROR_VERIFY) {
        flowf(NORMAL, "\n The address range for platform certificate and IMEI-SV is not erased.\n");
        main_fatal(E_FLASH_VERIFY);
    }

    if (d_error != PROTO_READY)
        error_proto(d_error, PROTO_READY);

    // Verify that the binding service call in target has succeeded
    if (target_wait(1, TARGET_RECV_DELAY) < 1)
        main_fatal(E_RECV_TIMEOUT);

    d_error = target_getchar();
    if (d_error == PROTO_ERROR_ROM_SSERVICE) {
        flowf(NORMAL, "\n The Run-Time Loader (Binding) Service failed.\n");
        main_fatal(E_ROM_SSERVICE);
    }

    if (d_error != PROTO_FLASH_START)
        error_proto(d_error, PROTO_FLASH_START);

    // Receive acknowledgement for programming the platform certificate
    if (target_wait(1, TARGET_RECV_DELAY) < 1)
        main_fatal(E_RECV_TIMEOUT);

    d_error = target_getchar();
    if (d_error != PROTO_PROGRAM) {
        flowf(NORMAL, "\n Platform certificate was not stored correctly in flash.\n");
        error_proto(d_error, PROTO_PROGRAM);
    }

    // Receive acknowledgement for programming the IMEI-SV
    if (target_wait(1, TARGET_RECV_DELAY) < 1)
        main_fatal(E_RECV_TIMEOUT);

    d_error = target_getchar();
    if (d_error != PROTO_PROGRAM) {
        flowf(NORMAL, "\n IMEI-SV was not stored correctly in flash.\n");
        error_proto(d_error, PROTO_PROGRAM);
    }

    flowf(NORMAL, "Platform certificate and IMEI-SV stored in flash.\n");
}

// End Secure Calypso Plus

/******************************************************************************
 * Flash Detect
 ******************************************************************************/

void flash_detect_machine(void)
{
    uint8  data[12];
    uint16 m0, d0, m1, d1, d1ex1, d1ex2;

    flowf(NORMAL, "Flash Detect: ");
    target_trace_enable(0);

    if (arg_device_id0 != -1 || arg_device_id1 != -1) {
        // Device auto-detection is disabled
        m0 = m1 = arg_device_id0;
        d0 = d1 = arg_device_id1;
        flowf(VERBOSE, "(ID override) ");
    }
    else {
        target_putchar(PROTO_DETECT);
        if (target_wait(12, TARGET_RECV_DELAY) < 12)
            main_fatal(E_RECV_TIMEOUT);

        target_recv(data, 12);
        m0 = data[0] + (data[1] << 8); // Intel manufacturer id
        d0 = data[2] + (data[3] << 8); // Intel device id
        m1 = data[4] + (data[5] << 8); // AMD manufacturer id
        d1 = data[6] + (data[7] << 8); // AMD device di
        d1ex1 = data[8] + (data[9] << 8);   // AMD extended device id
        d1ex2 = data[10] + (data[11] << 8); // AMD extended device id
    }

    // Lookup multi-id device
    if ((m1 == MANUFACT_AMD || m1 == MANUFACT_FUJITSU) && d1 == 0x227E) {
        flowf(NORMAL, "Multi-id device detected: (0x%04X, 0x%04X, 0x%04X)\n"
              , d1, d1ex1, d1ex2);
        d1 = (d1ex1 << 8) | (d1ex2 & 0xFF);
        flowf(DEBUG, "Multi-id converted to: 0x%04X\n", d1);
        
        if ((device = device_lookup_by_id(m1, d1)) == NULL) {
            flowf(NORMAL, "Id not found, lookup default multi-id conf.\n");
            // Backward compatible/keep default multi-id configuration
            device = device_lookup_by_id(m1, 0x227E);
        }
    }
    
    // Lookup device with AMD and Intel ids
    else if ((device = device_lookup_by_id(m1, d1)) == NULL)
        device = device_lookup_by_id(m0, d0);

    if (device == NULL || arg_verbose >= DEBUG) {
        flowf(NORMAL, "(0x%02X, 0x%04X, 0x%02X, 0x%04X) ", m0, d0, m1, d1);
        if (device == NULL)
            main_fatal(E_FLASH_UNKNOWN);
    }

    if (arg_verbose >= NORMAL) {
        device_print(device, 's');
        flowf(NORMAL, " ok\n");
    }

    // Note that device_id is only 0x227E if the Multi-id configuration not
    // is found in device.txt (else device_id will be the 'converted' id)
    if (arg_device_id0 == -1 && arg_device_id1 == -1 &&
        (device->manufacturer_id == MANUFACT_FUJITSU || 
         device->manufacturer_id == MANUFACT_AMD ) 
        && device->device_id == 0x227E) {
        flowf(NORMAL, "Warning: Extended device codes are supported when detecting flash devices,\n");
        flowf(NORMAL, "         but the detected multi-id configuration is not found in device.txt. \n");
        flowf(NORMAL, "         Update the device.txt manually if the default flash device is invalid.\n");
        flowf(NORMAL, "         Currently, %s %s is used as default.\n\n",
            manufacturer_name_lookup_by_id(device->manufacturer_id), device->name);
    }
}


/******************************************************************************
 * Method Load
 ******************************************************************************/

void method_download_machine(void)
{
    char sendbuf[1+2+4];
    int error;

    target_program_size = file_read_method(target_program,
                                           TARGET_PROGRAM_SIZE_MAX,
                                           device);

    flowf(BLABBER, "Method Download: ");
    target_trace_enable(0);

    // Send 'Download' command header. Then wait for acknowledgement.
    buf_put1(&sendbuf[0],   PROTO_DOWNLOAD);
    buf_put4(&sendbuf[1],   target[target_type].method_load_addr);
    buf_put2(&sendbuf[1+4], (uint16) target_program_size / 2);
    target_send(sendbuf, 1+4+2);
    flowf(BLABBER, "(0x%X", target[target_type].method_load_addr);
    if ((error = target_expect_char(PROTO_READY, TARGET_RECV_DELAY)) < 0)
        main_fatal(error);

    // Send data. Then wait for acknowledgement.
    target_trace_enable(1);
    target_send(target_program, target_program_size);
    flowf(BLABBER, ", %d) ", target_program_size);
    if ((error = target_expect_char(PROTO_READY, TARGET_RECV_DELAY)) < 0)
        main_fatal(error);

    flowf(BLABBER, "ok\n");
}


/******************************************************************************
 * Flash Checksum
 ******************************************************************************/

int time_checksum;

#define CHECKSUMS 8

void flash_checksum_machine(void)
{
    unsigned char sendbuf[1+1+4+4*CHECKSUMS], *buf;
    uint32 addr, cksum, mycksum;
    uint16 word;
    uint8  data[4];
    int index = 0, chunks, n, i, j;
    int line = -1;
    struct {
        uint32 index;
        uint32 addr;
        uint32 mycksum;
    } block[CHECKSUMS];

    chunks = image_map_count_used_chunks();
    flowf(NORMAL, "Checksumming (%d * %dkB = %dkB): ",
          chunks, image_chunk_size / 1024,
          chunks * image_chunk_size / 1024);
    target_trace_enable(0);

    time_checksum = stopwatch_start();
    while (chunks > 0)
    {
        n = (chunks > CHECKSUMS ? CHECKSUMS : chunks);

        buf = sendbuf;
        buf += buf_put1(buf, PROTO_CHECKSUM);
        buf += buf_put1(buf, (unsigned char) n);
        buf += buf_put4(buf, image_chunk_size);

        for (i = 0; i < n; i++) {
            // Find next used entry in image_map
            while (image_map[index] != 'x' && index < image_map_size)
                index++;

            if (index == image_map_size)
                break;

            addr = index * image_chunk_size;
            block[i].index   = index;
            block[i].addr    = addr;

            buf += buf_put4(buf, addr);

            index++;
        }
        target_send(sendbuf, buf - sendbuf);

        // Compute checksums while we wait for reply
        for (i = 0; i < n; i++) {
            index = block[i].index;
            addr  = block[i].addr;
            for (j = 0, mycksum = 0; j < image_chunk_size; j += 2) {
                word = (image[addr + j + 1] << 8) | image[addr + j + 0];
                mycksum += word * ((addr + j) & 0xFFFF);
            }
            block[i].mycksum = mycksum;
        }

        if (target_wait(4 * n, TARGET_RECV_LONG_DELAY) <= 0)
            main_fatal(E_RECV_TIMEOUT);

        for (i = 0; i < n; i++) {
            target_recv(data, 4);
            cksum   = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24);
            mycksum = block[i].mycksum;
            index   = block[i].index;

            if (cksum == mycksum)
                image_map[index] = 'c';

            if (arg_checksum_show) {
                // This is a far from perfect dump of checksums... We don't know
                // the exact block number of each checksum in a line.
                if (line != (int) index / 4) {
                    flowf(NORMAL, "\n%4d:", index);
                    line = index / 4;
                }
                if (cksum == mycksum)
                    flowf(NORMAL, "%08X          ", cksum);
                else
                    flowf(NORMAL, "%08X/%08X ", cksum, mycksum);

            }
            else if (arg_verbose >= VERBOSE) {
                flowf(VERBOSE, "%c", (image_map[index] == 'c' ? 'c' : '.'));
            }
        }
        index++;
        chunks -= n;
    }
    time_checksum = (stopwatch_stop(time_checksum) + 50) / 100;
    flowf(BLABBER, " (%d.%ds)", time_checksum / 10, time_checksum % 10);
    flowf(NORMAL, " ok\n");
}


/******************************************************************************
 * Flash Program
 ******************************************************************************/

int time_program;
int programs_recv, programs_send, erasures;
int index, src_size;
uint32 dst;
char *src;

void flash_erase_machine(void);
int  flash_operation_wait(int delay);
int  flash_program_next(void);

void flash_program_machine(void)
{
    uint8 cksum, sendbuf[1+4+4];
    int i, te, tp, tt;
    int expected, chunks, total_size, sectors;
    int image_chunk_size_old = image_chunk_size;
    char ch;

    programs_recv = programs_send = erasures = 0;
    index = 0;

    // Prepare erase
    erase_list_size = sectors = sector_map_init();
    i = image_map_count_used_chunks();

    if (arg_skip_erase)
        erase_list_size = 0;

    time_compute(device, erase_list_size,
                 i * image_chunk_size, i, &te, &tp, &tt);

    flowf(BLABBER, "Estimated time (uncompressed) ");
    if (te + tp > tt)
        flowf(BLABBER, "(erase + program = total): %ds + %ds = %ds\n",
              te/1000, tp/1000, te/1000 + tp/1000);
    else
        flowf(BLABBER, "transfer: %ds\n", tt/1000);

    chunks = image_map_count_used_chunks();
    flowf(NORMAL, "Program: (%d sectors, %d*%dk=%dk) ",
          sectors,
          chunks, image_chunk_size / 1024,
          chunks * image_chunk_size / 1024);
    target_trace_enable(1);

    // Expected total number of acknowledgements
    expected = erase_list_size + chunks;

    if (arg_dry_run) {
        flowf(NORMAL, "(dry-run) ");
        if (arg_dry_run == 2)
            image_chunk_size = 0;
    }

    if (arg_skip_erase)
        flowf(NORMAL, "(skip erase) ");

    // Start erase
    flash_erase_machine();

    if (arg_dry_run == 0 || arg_dry_run == 2)
        progress_begin(expected);

    // Start the flash state machine in the target
    target_putchar(PROTO_FLASH_START);
    target_putchar(arg_compress ? PROTO_COMPRESS : 0);
    ch = flash_operation_wait(TARGET_RECV_DELAY);
    if (ch != PROTO_READY)
        error_proto(ch, PROTO_READY);
    if (arg_compress) {
        compress_init();
        total_size = 0;
        // Worst case "compressed" buffer is 9/8 of original data size.
        if ((src = malloc(9 * image_chunk_size / 8)) == NULL)
            main_fatal(E_MEMORY);
    }

    time_program = stopwatch_start();

    if (flash_program_next() && *arg_erase_override == 0) {
        //Nothing to program
        target_putchar(PROTO_FLASH_END);
        if ((ch = target_expect_char(PROTO_FLASH_END, TARGET_RECV_DELAY)) < 0)
            main_fatal(ch);
    }
    else {
        while (programs_recv + erasures < expected) {
            total_size += src_size;

            if (arg_progress == 'x')
                flowf(NORMAL, "(0x%06X, %d) ", dst, src_size);

            if (src_size > 0 &&
                (arg_dry_run == 0 || arg_dry_run == 2))
            {
                buf_put1(&sendbuf[0],   PROTO_PROGRAM);
                buf_put4(&sendbuf[1],   src_size);
                buf_put4(&sendbuf[1+4], dst);
                target_send(sendbuf, 1+4+4);

                for (i = 0, cksum = 0; i < src_size; i++)
                    cksum += src[i];
                cksum = (0x100 - cksum) & 0xFF;

                // Wait for acknowledgement of command start
                //while ((ch = flash_operation_wait(4000)) != PROTO_READY)
                //    ;

                // Make sure that the target FIFO not is full
                while (programs_send - programs_recv >= target_fifo_size)
                    flash_operation_wait(4000);

                // Send data bytes and checksum.
                target_send(src, src_size);
                target_putchar(cksum);
                programs_send++;

                if (arg_verbose >= DEBUG)
                    progress_update_simple('T');
            }

            // We compress and send next block while sending
            // current block to target
            if (arg_dry_run == 0 || arg_dry_run == 2) {
                if (flash_program_next()) {
                    // Wait for remaining acknowledgements
                    flowf(DEBUG, "W%d", expected - (programs_recv + erasures));
                    while (programs_recv + erasures < expected)
                        flash_operation_wait(4000);
                    target_putchar(PROTO_FLASH_END);
                    if ((ch = target_expect_char(PROTO_FLASH_END, TARGET_RECV_DELAY)) < 0)
                        main_fatal(ch);
                    break;
                }
                else {
                    // Wait for acknowledgement of data transfer
                    while ((ch = flash_operation_wait(4000)) != PROTO_READY)
                        ;
                }
            }
            else {
                if (flash_program_next())
                    break;
            }
        }
    }

    if (arg_dry_run == 0 || arg_dry_run == 2)
        progress_end(programs_recv + erasures);
    flowf(NORMAL, "ok\n");

    time_program = stopwatch_stop(time_program);
    flowf(VERBOSE, "Used time: ");
    if (arg_compress && chunks) {
        image_chunk_size = image_chunk_size_old;
        flowf(VERBOSE, "(compressed to %d%%) ",
              100 * total_size / (chunks * image_chunk_size));
    }
    flowf(VERBOSE, "%.1fs ok\n", (double) time_program / 1000);
}


/******************************************************************************
 * Flash Program sub functions
 ******************************************************************************/

void flash_erase_machine(void)
{
    uint8 sendbuf[1+2+4*256]; // NOTEME: static limit!
    int i;
    char ch;

    if (arg_dry_run)
        erase_list_size = 0;

    if (erase_list_size > 0) {
        buf_put1(&sendbuf[0], PROTO_ERASE);
        buf_put2(&sendbuf[1], (uint16) erase_list_size);
        for (i = 0; i < erase_list_size; i++)
            buf_put4(&sendbuf[1+2+4*i], erase_list[i]);
        target_send(sendbuf, 1+2+4*erase_list_size);

        if ((ch = target_expect_char(PROTO_READY, TARGET_RECV_DELAY)) < 0)
            main_fatal(ch);
    }
}

// Setup address and size of next block to transfer. Also compress the
// block. Return non-zero if this was the last block. Otherwise return zero.
int flash_program_next(void)
{
    int oldindex;

    // Find next used entry in image_map
    while (index < image_map_size && image_map[index] != 'x')
        index++;

    oldindex = index;

    if (index < image_map_size) {
        dst = index * image_chunk_size;
        if (arg_compress) {
            src_size = compress(src, &image[dst], image_chunk_size);
        }
        else {
            src = &image[dst];
            src_size = image_chunk_size;
        }
        index++;
    }
    else {
        dst = 0xFFFFFFFF;
        src_size = 0;
    }

    return (oldindex >= image_map_size);
}

int flash_operation_wait(int delay)
{
    int n;
    char ch;

    tr(TrMachines, "fow() ");

    if ((n = target_wait(1, delay)) < 1)
        main_fatal(E_RECV_TIMEOUT);

    // Minor optimization to avoid waiting for every char...
    while (n--) {
        ch = target_getchar();

        if (ch == PROTO_READY) {
            if (arg_verbose >= DEBUG)
                progress_update_simple('R');
            if (programs_send - programs_recv >= target_fifo_size)
                progress_update_simple('W');  // Wait target FIFO is full
            break;
        }

        switch (ch) {
        case PROTO_READY:
            break; // just return the char
        case PROTO_PROGRAM:
            programs_recv++;
            progress_update_simple('P');
            progress_update(programs_recv + erasures);
            break; // just return the char
        case PROTO_ERASE:
            erasures++;
            progress_update_simple('E');
            progress_update(programs_recv + erasures);
            if (arg_progress == 'x')
                flowf(NORMAL, "E ");
            break; // just return the char
        case PROTO_FLASH_END:
            progress_update_simple('Z');
            break;
        case PROTO_ERROR_CKSUM:
            main_fatal(E_SEND_CHECKSUM);
        case PROTO_ERROR_MEMORY:
            main_fatal(E_MEMORY);
        case PROTO_ERROR_VERIFY:
            main_fatal(E_FLASH_VERIFY);
        case PROTO_ERROR_FLASH_TIMEOUT:
            main_fatal(E_FLASH_TIMEOUT);
        case PROTO_ERROR_FLASH_COMMAND:
            main_fatal(E_FLASH_COMMAND);
        case PROTO_ERROR_FLASH_VPP:
            main_fatal(E_FLASH_VPPRANGE);
        case PROTO_ERROR_FLASH_LOCKED:
            main_fatal(E_FLASH_LOCKED);
        case PROTO_ERROR_FLASH_UNKNOWN:
            main_fatal(E_FLASH_ERROR);
        case PROTO_ERROR_INVALID:
            main_fatal(E_INVALID);
        case PROTO_ERROR_FIFO_OVERFLOW:
            main_fatal(E_FIFO_OVERFLOW);
        case PROTO_ERROR:
        default:
            flowf(NORMAL, "flash_operation_wait() got unexpected char '%c' 0x%02X (%d chars waiting)\n", (' ' <= ch  && ch  < 127 ? ch  : '.'), ch, n);
//            main_fatal(E_PROTO_ERROR);
        }
    }

    return ch;
}

void target_timers_show(void)
{
    int data[8], i;
    struct {
        double erase;
        double program;
        double recvonly;
        double recv;
        double comm;
        double setup;
        double overhead;
        double dezip;
        double erase_sector;
        double program_word;
    } timer;
    double tmp, total, resolution = (double) (16 * 32 * 1000 / 13e6);

    // Use only 14 MHz for D-/E-Sample specific rates (not for e.g. 812.5K).
    if ((arg_uart_baudrate == 230400) || (arg_uart_baudrate == 460800) || (arg_uart_baudrate == 921600))
        resolution = resolution * 13 / 14.746;

    flowf(NORMAL,
          "Target Timers:\n"
          "  (erase + program + recvonly +   recv +  comm + setup + overhead + dezip)\n");

    if (arg_dry_run)
        for (i = 0; i < 8; i++) data[i] = 0;
    else {
        target_putchar(PROTO_TIMERS);
        if (target_wait(sizeof(data), TARGET_RECV_DELAY) <= 0)
            main_fatal(E_RECV_TIMEOUT);
        target_recv(&data, sizeof(data));
    }

    // Convert all timers to milliseconds
    timer.erase    = resolution * data[0];
    timer.program  = resolution * data[1];
    timer.recvonly = resolution * data[2];
    timer.recv     = resolution * data[3];
    timer.comm     = resolution * data[4];
    timer.setup    = resolution * data[5];
    timer.overhead = resolution * data[6];
    timer.dezip    = resolution * data[7];

    // Because we do erase-while-transfer in the target the reported erase
    // time has some inherent tolerance, especially at low baudrates. It
    // might make sense to adjust the sector erase time by half the chunk
    // transfer time, although this is just another approximation!?

//    total = timer.erase + timer.program + timer.recvonly + timer.recv + timer.comm + timer.setup;
    flowf(NORMAL, "%8.0f + %7.0f + %8.0f + %6.0f + %5.0f + %5.0f + %8.0f + %5.0f\n",
          timer.erase, timer.program, timer.recvonly, timer.recv,
          timer.comm, timer.setup, timer.overhead, timer.dezip);

    flowf(DEBUG, "resolution = %f, %d, %d, %d, %d, %d, %d, %d, %d\n",
          resolution, data[0], data[1], data[2], data[3], data[4],
          data[5], data[6], data[7]);

    if (arg_timers_extended_show) {
        int chunks, size_total, not_zero = 1;
        flowf(NORMAL, "Target Timers Extended:\n");

        if (erase_list_size == 0 || arg_dry_run)
            flowf(NORMAL, "  Erase time         = 0.0ms/sector\n");
        else {
            timer.erase_sector = timer.erase / erase_list_size / 1000;
            flowf(NORMAL, "  Erase time         = %.1fs / %d sectors = %.0fms/sector\n",
                  timer.erase / 1000, erase_list_size, timer.erase_sector * 1000);
        }

        chunks = image_map_count_used_chunks();
        size_total = chunks * image_chunk_size;
        if (size_total == 0 || arg_dry_run) {
            size_total = 1;
            not_zero = 0;
            flowf(NORMAL, "  Program time       = 0.0us/word = 0ms/MB\n");
        }
        else {
            timer.program_word = timer.program / 1000 / (size_total / 2);
            flowf(NORMAL, "  Program time       = %.1fs / %dkwords = %.2fus/word = %.0fms/MB\n",
                  timer.program / 1000, size_total / 1024 / 2,
                  timer.program_word * 1000000,
                  timer.program_word * 1000 * 512 * 1024);
        }

        flowf(NORMAL, "  Receive-only time  = %.1fs = %.0fms/MB\n",
              timer.recvonly / 1000,
              not_zero * timer.recvonly * 1024 * 1024 / size_total);

        flowf(NORMAL, "  Overhead time      = %.1fs = %.0fms/MB\n",
              timer.overhead / 1000,
              not_zero * timer.overhead * 1024 * 1024 / size_total);

        flowf(NORMAL, "  Setup time         = %.1fs = %.0fms/MB\n",
              timer.setup / 1000,
              not_zero * timer.setup * 1024 * 1024 / size_total);

        total = timer.erase + timer.program + timer.recvonly + timer.overhead +
            timer.setup;
        flowf(NORMAL,
              "Total time: (erase + prog + recvonly + overhead + setup) = %.1fs\n",
              total / 1000);

        flowf(NORMAL, "  Receive time       = %.1fs = %.0fms/MB\n",
              timer.recv / 1000, timer.recv * 1024 * 1024 / size_total);

        flowf(NORMAL, "  Communication time = %.1fs = %.0fms/MB\n",
              timer.comm / 1000, timer.comm * 1024 * 1024 / size_total);

        flowf(NORMAL, "  Dezip time         = %.1fs = %.0fms/MB\n",
              timer.dezip / 1000, timer.dezip * 1024 * 1024 / size_total);

        // Now compute the overall performance. Note that the theoretical
        // limit compuation is somewhat flawed in that it assumes 64kB
        // sector sizes.

        tmp = (double) not_zero * total / 1000 * 1024 * 1024 / size_total;
        flowf(NORMAL, "  Performance = %.0fs/MB\n", tmp);
#if 0
        flowf(BLABBER, " (%.1f * min possible, %.1f * theoretical limit)",
              time_program / (timer.program + timer.erase),
              tmp / (16 * timer.erase_sector + 512 * 1024 * timer.program_word));
        flowf(NORMAL, "\n");
#endif
    }
}


/******************************************************************************
 * Flash Read
 ******************************************************************************/

int time_read;

void flash_read_machine(void)
{
    int i;
    unsigned char sendbuf[1+4+4];
    int size, read_size, done_size = 0;
    int read_size_max = image_chunk_size;
    uint32 addr, index = 0;
    uint8 cksum, mycksum;

    flowf(NORMAL, "Reading Flash: (%dkB) ", read_total_size / 1024);
    target_trace_enable(0);

    //read_size_max = 256;
    time_read = stopwatch_start();
    progress_begin(read_total_size / read_size_max);

    while (read_list[index].size > 0)
    {
        // Find next range to read
        size = read_list[index].size;
        addr = read_list[index].addr;
        index++;

        while (size > 0)
        {
            read_size = (size > read_size_max ? read_size_max : size);

            // Read address interval is [MIN..MAX[
            // If odd MAX address was specified round up to next even
            if ((read_size % 2 == 1) && (read_size < read_size_max)) read_size++;

            if (arg_progress == 'x')
                flowf(DEBUG, "(0x%06X, %d) ", addr, read_size);

            if (!arg_dry_run)
            {
                buf_put1(&sendbuf[0],   PROTO_READ);
                buf_put4(&sendbuf[1]  , read_size);
                buf_put4(&sendbuf[1+4], addr);
                target_send(sendbuf, 1+4+4);

                if (target_wait(read_size, TARGET_RECV_LONG_DELAY) <= 0)
                    main_fatal(E_RECV_TIMEOUT);
                // Note that we wrap/mirror memory each 'image_size' bytes
                target_recv(&image[addr & (image_size - 1)], read_size);
                for (i = 0, mycksum = 0; i < read_size; i++)
                    mycksum ^= image[(addr + i) & (image_size - 1)];

                if (target_wait(1, TARGET_RECV_DELAY) <= 0)
                    main_fatal(E_RECV_TIMEOUT);
                cksum = target_getchar();

                if (cksum != mycksum)
                    main_fatal(E_RECV_CHECKSUM);
            }
            done_size += read_size;
            size -= read_size;
            addr += read_size;

            progress_update(done_size / read_size_max);
            progress_update_simple('0' + read_size / 1024);
        }
    }
    progress_end(done_size / read_size_max);
    flowf(NORMAL, " ok\n");

    time_read = stopwatch_stop(time_read);
    flowf(VERBOSE, "Used time: %ds ok\n", time_read);
}


/******************************************************************************
 * Flash Reset
 ******************************************************************************/

void target_reset_machine(void)
{
    int error;

    flowf(VERBOSE, "Resetting target: ");

    if (arg_dry_run) {
        flowf(VERBOSE, "(dry-run) ");
    }
    else {
        target_putchar(PROTO_RESET);
        target_expect_char(PROTO_READY, TARGET_RECV_DELAY);
    }
    flowf(VERBOSE, "ok\n");
}


/******************************************************************************
 * Show Functions (dump internal data structures)
 ******************************************************************************/

void image_map_show(void)
{
    uint32 addr;
    int i;

    flowf(ALWAYS, "image map of %d * %dkB chunks (x = used, s = skip, c = checksum ok):",
           image_size_in_chunks, image_chunk_size / 1024);

    // For each chunk of the image usage map...
    for (i = 0, addr = 0; i < image_size_in_chunks; i++)
    {
        if ((i % 64) == 0) {
            flowf(ALWAYS, "\n%4dkB: ", (int) addr >> 10);
        }
        putchar(image_map[i] != 0 ? image_map[i] : '.');
        addr += image_chunk_size;
    }
    putchar('\n');
}

void sector_map_show(void)
{
    struct sector_s *sectors = device->memmap->sectors;
    int i;

    char n;
    uint32 addr = 0;

    flowf(ALWAYS, "sector map (x = used, s = skip, X = force erase):");

        // For each sector of the device definition...
    for (i = 0; i < device->memmap->size; i++)
    {
        if ((addr & 0xFFFFF) == 0) {
            flowf(ALWAYS, "\n%2dMB: ", (int) addr >> 20);
        }
        n = (sector_map[i] ? sector_map[i] : '.');
        putchar(n);

        addr += sectors[i].size;
    }
    putchar('\n');
}


/******************************************************************************
 * Utility Functions
 ******************************************************************************/

int image_is_within(int start, int end)
{
    return !(start < 0 || image_size <= start ||
             end < 0   || image_size < end ||
             end < start);
}

// Set the image usage map in the range [start..end[ as used
int image_map_set(int start, int end)
{
    if (!image_is_within(start, end))
        return -1;

    do {
        image_map[start / image_chunk_size] = 'x';
        start += image_chunk_size;
    } while (start < end);

    return 0;
}

int target_type_set(uint16 code)
{
    // If there is an override from the command-line, use that
    if (arg_target_type != 0)
        code = arg_target_type;

    switch (code)
    {
    case 'h': // Hercules
    case 'u': // Ulysses
    case '3': // Chipset 3
    case CHIP_ID_ULYSSES_0:
    case CHIP_ID_ULYSSES_A:
    case CHIP_ID_HERCULES_A:
    case CHIP_ID_HERCULES_B:
        target_type = 3;
        break;

    case 'c': // Calypso
    case '4': // Chipset 4
    // case 's': // Samson
    case CHIP_ID_CALYPSO_A:
    case CHIP_ID_CALYPSO_B:
    case CHIP_ID_CALYPSO_C:
        target_type = 4;
        break;

    case 'l':  // Calypso Lite
    case CHIP_ID_CALYPSO_L:
        target_type = 5;
        break;

    case 'p':  // Calypso Plus
    case CHIP_ID_CALYPSO_PLUS:
    case CHIP_ID_CALYPSO_PLUS_A:
        target_type = 6;
        break;

    default:
        target_type = 0;
    }

    return target_type;
}

int image_map_count_used_chunks(void)
{
    int used, i;

    // For each image chunk
    for (i = 0, used = 0; i < image_size_in_chunks; i++) {
        if (image_map[i] == 'x')
            used++;
    }
    return used;
}

// When the sector_map have been changed, we have to update the image_map
// such that we don't attempt to program within sectors that are not going
// to be programmed anyway (due to the erase override). We also have to
// program chunks contained in sectors that *are* going to be erased, even
// though the chunks have been check-summed ok.
int image_map_update(void)
{
    struct sector_s *sectors = device->memmap->sectors;
    int changed, chunks, i, j;
    int map_index;

    map_index = 0;
    changed = 0;

    // For each sector of the device definition...
    for (i = 0; i < device->memmap->size; i++) {
        chunks = sectors[i].size / image_chunk_size;
        // For each image--map-chunk contained in current sector...
        for (j = 0; j < chunks; j++) {
            if (!(sector_map[i] == 'x' || sector_map[i] == 'X')  &&
                 image_map[map_index + j] == 'x') {
                image_map[map_index + j] = 's'; // skip
                changed++;
            }
            else if ((sector_map[i] == 'x' || sector_map[i] == 'X')
                     && image_map[map_index + j] == 'c') {
                image_map[map_index + j] = 'x'; // include!
                changed++;
            }
        }
        map_index += chunks;
    }
    return changed;
}

int sector_map_init(void)
{
    struct sector_s *sectors = device->memmap->sectors;
    int map_index;
    int used_list_index;
    int used, chunks, i, j;

    sector_map_size = SECTOR_MAP_SIZE_MAX;

    memset(sector_map, 0, sector_map_size);

    // Generate the sector_map from the image_map
    map_index = 0;

    // For each sector of the device definition...
    for (i = 0; i < device->memmap->size; i++) {

        chunks = sectors[i].size / image_chunk_size;

        // For each image-usage-map-chunk contained in current sector...
        for (j = 0, used = 0; j < chunks; j++)
            if (image_map[map_index + j] == 'x')
                used++;

        sector_map[i] = (used ? 'x' : 0);
        map_index += chunks;
    }

    parse_arg_erase_override(arg_erase_override);

    // Generate the erase_list
    // For each sector of the device definition...
    used_list_index = 0;
    for (i = 0; i < device->memmap->size; i++) {
        if (sector_map[i] == 'x' || sector_map[i] == 'X')
            erase_list[used_list_index++] = sectors[i].addr;
    }

    if (arg_sector_map_show)
        sector_map_show();

    if (arg_sector_list_show) {
        flowf(ALWAYS, "sector used list: ");
        for (i = 0; i < used_list_index; i++)
            flowf(ALWAYS, "0x%06X ", erase_list[i]);
        putchar('\n');
    }

    return used_list_index;
}

int parse_range(char *p, char **p_end, int *n1, int *n2)
{
    char *my_end;
    int read_offset_calp = (*arg_read != 0 && target[target_type].type == 'P');

    *n2 = 0;

    *n1 = strtol(p, &my_end, 0);
    if (p == my_end)
        return 0; // error: no chars converted

    if (*my_end == 'k' || *my_end == 'K') {
        *n1 <<= 10;
        if (read_offset_calp) *n1 += CALP_OFFSET;
        my_end++;
    }
    else if (*my_end == 'M') {
        *n1 <<= 20;
        if (read_offset_calp) *n1 += CALP_OFFSET;
        my_end++;
    }

    *p_end = my_end;
    if (my_end[0] != '.' || my_end[1] != '.')
        return 1;
    p = my_end + 2;

    *n2 = strtol(p, &my_end, 0);
    if (p == my_end)
        return 0; // error: no chars converted

    if (*my_end == 'k' || *my_end == 'K') {
        *n2 <<= 10;
        if (read_offset_calp) *n2 += CALP_OFFSET;
        my_end++;
    }
    else if (*my_end == 'M') {
        *n2 <<= 20;
        if (read_offset_calp) *n2 += CALP_OFFSET;
        my_end++;
    }

    *p_end = my_end;
    return 2;
}

// Parse and decode the erase override command line option string. The
// sector_map is updated accordingly.
void parse_arg_erase_override(char *p)
{
    struct sector_s *sectors = device->memmap->sectors;
    int sector_bottom, sector_top;
    int changed, i;

    tr(TrBegin| TrArgParser, "erase_override:\n");
    while (*p)
    {
        char *p_end;
        int num, sign, n1 = 0, n2 = 0;

        if (*p != '-' && *p != '+')
            main_error(E_ERASE_SPEC);

        sign = (*p == '-' ? -1 : +1);
        p++;

        if (*p == '*') {
            sign = sign * 2;
            p++;
        }
        else {
            num = parse_range(p, &p_end, &n1, &n2);
            if (num == 0)
                main_error(E_ERASE_SPEC);
            p = p_end;
        }

        switch (sign) {
        case -1:
        case +1:
            if (num == 1) {
                // Support for absolute addresses on Calypso Plus
                if (n1 >= CALP_OFFSET) n1 -= CALP_OFFSET;

                if (n1 > sector_map_size) {
                    // For all sectors...
                    for (i = 0; i < device->memmap->size; i++) {
                        sector_bottom = sectors[i].addr;
                        sector_top    = sector_bottom + sectors[i].size - 1;

                        if (n1 >= sector_bottom && n1 < sector_top) {
                            sector_map[i] = (sign > 0 ? 'X' : 's');
                            break;
                        }
                    }
                }
                else
                    sector_map[n1] = (sign > 0 ? 'X' : 's');
                tr(TrArgParser, "%c%d\n", sign > 0 ? '+' : '-', n1);
            }
            else {
                // Support for absolute addresses on Calypso Plus
                if (n1 >= CALP_OFFSET && n2 >= CALP_OFFSET) {
                    n1 -= CALP_OFFSET;
                    n2 -= CALP_OFFSET;
                }

                if (n1 > sector_map_size || n2 > sector_map_size) {
                    // n1 and n2 must respresent an address range
                    if (n1 >= n2)
                        main_error(E_ERASE_SPEC);

                    // For all sectors...
                    for (i = 0; i < device->memmap->size; i++)
                    {
                        sector_bottom = sectors[i].addr;
                        sector_top    = sector_bottom + sectors[i].size - 1;

                        // If either sector bottom or top is contained in
                        // [n1..n2[
                        if ((n1 <= sector_bottom && sector_bottom < n2) ||
                            (n1 <= sector_top    && sector_top    < n2)) {
                            sector_map[i] = (sign > 0 ? 'X' : 's');
                        }
                    }
                }
                else {
                    // n1 and n2 must respresent a sector range
                    for (i = n1; i < n2; i++)
                        sector_map[i] = (sign > 0 ? 'X' : 's');
                }
                tr(TrArgParser,
                   "%c%d..%d\n", sign > 0 ? '+' : '-', n1, n2);
            }
            break;
        case -2:
        case +2:
            // Fill whole sector_map...
            for (i = 0; i < device->memmap->size; i++)
                sector_map[i] = (sign > 0 ? 'X' : 's');
            tr(TrArgParser, "%c*\n", sign > 0 ? '+' : '-');
            break;
        }
        // skip optional comma
        if (*p == ',')
            p++;
    }
    tr(TrEnd| TrArgParser, "");

    changed = image_map_update();

    // If image map was changed, we trace it again.
    if (changed && arg_image_map_show)
        image_map_show();
}

void parse_arg_read(char *p)
{
    read_total_size = 0;
    read_list_size  = 0;

    tr(TrArgParser, "parse_arg_read() {\n");
    while (*p)
    {
        char *p_end;
        int num, n1, n2;

        if (*p == '*') {
            n1 = 0;
            n2 = 0x1000000; // sufficiently large (16MB)
            p++;
        }
        else {
            num = parse_range(p, &p_end, &n1, &n2);
            tr(TrArgParser, "parse_range('%s', ...)\n"
               "    { #%d, p_end = '%s', n1 = 0x%x, n2 = 0x%x } %d\n",
               p, read_list_size, p_end, n1, n2, num);
            if (num != 2 || n1 > n2)
                main_error(E_ADDR_RANGE);
            p = p_end;
        }
        read_list[read_list_size].addr = n1;
        read_list[read_list_size].size = n2 - n1;
        read_total_size += n2 - n1;
        read_list_size++;
        if (read_list_size >= READ_LIST_SIZE_MAX)
            main_error(E_ARG_TOOMANY);

        if (*p == 0)
            break;

        if (*p == ',')
            p++;
        else
            main_error(E_READ_SPEC);
    }
    // Terminate the read_list with zeroes.
    read_list[read_list_size].addr = 0;
    read_list[read_list_size].size = 0;

    tr(TrArgParser, "}\n");
}

void parse_arg_write(char *p)
{
    tr(TrArgParser, "parse_arg_write() {\n");

    while (*p)
    {
        char bytes[1024];
        char *p_end;
        int num, n1, n2;
        int index, value, size, i;

        num = parse_range(p, &p_end, &n1, &n2);
        tr(TrArgParser, "parse_range('%s', ...)\n"
           "    { p_end = '%s', n1 = 0x%x, n2 = 0x%x } %d\n",
           p, p_end, n1, n2, num);

        if (num < 1 || num > 2)
            main_error(E_ADDR_RANGE);

        p = p_end;

        if (*p++ != '=')
            main_error(E_WRITE_SPEC);

        tr(TrArgParser, "    { ");
        if (*p == 0)
            main_error(E_WRITE_SPEC);

        index = 0;
        while (*p) {
            if (*p == '\"') {
                // Collect text string
                p++;
                tr(TrCont|TrArgParser, "'");
                while (*p) {
                    if (*p == '\"')
                        break;
                    if (p[0] == '\\' && p[1] == '\"')
                        p++; // skip leading backslash
                    if (p[0] == '\\' && p[1] == '\\')
                        p++; // skip leading backslash
                    tr(TrCont|TrArgParser, "%c", *p);
                    bytes[index] = *p++;
                    if (index++ >= sizeof(bytes))
                        main_error(E_WRITE_SPEC);
                }
                if (*p++ != '\"')
                    main_error(E_WRITE_SPEC);
                tr(TrCont|TrArgParser, "' ");
            }
            else {
                // Collect byte string
                value = strtol(p, &p_end, 0);
                if (p == p_end)
                    main_error(E_WRITE_SPEC);
                if (value < 0 || 255 < value)
                    main_error(E_WRITE_SPEC);
                bytes[index] = value;
                if (index++ >= sizeof(bytes))
                    main_error(E_WRITE_SPEC);
                p = p_end;

                tr(TrCont|TrArgParser, "0x%x ", value);
            }
            if (*p == ':' || *p == 0)
                break;
            if (*p == ',')
                p++;
        }
        size = index;
        tr(TrArgParser, "} %d\n", size);

        if (num == 1) {
            // Support for absolute addresses on Calypso Plus
            if (n1 >= CALP_OFFSET) n1 -= CALP_OFFSET;

            n2 = n1 + size;
        }
        else if (n1 >= CALP_OFFSET && n2 >= CALP_OFFSET) {
            n1 -= CALP_OFFSET;
            n2 -= CALP_OFFSET;
        }

        if (image_map_set(n1, n2) < 0)
            main_error(E_ADDR_RANGE);

        index = 0;
        for (i = n1; i < n2; i++) {
            image[i] = bytes[index++];
            if (index >= size)
                index = 0;
        }
        if (*p == ':')
            p++;
    }
    tr(TrArgParser, "}\n");
}