/*
 * avrdude - A Downloader/Uploader for AVR device programmers
 *
 * Copyright (C) 2007 Colin O'Flynn <coflynn@newae.com>
 *
 * This file borrows heavily from the avarice project, which is:
 *  avarice - The "avarice" program.
 *  Copyright (C) 2001 Scott Finneran
 *      Copyright (C) 2002 Intel Corporation
 *  Copyright (C) 2005 Joerg Wunsch
 *
 *      File format support using BFD contributed and copyright 2003
 *      Nils Kr. Strom to avarice project
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
This file IO uses LIB BFD. Currently JUST used for doing the ELF work, but
could be made to encompass the other file types which would be nice...
*/


#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <termios.h>
#include <fcntl.h>
#include <bfd.h>
#include <stdbool.h>
#include "avrdude.h"

// The API changed for this in bfd.h. This is a work around for old versions
#ifndef bfd_get_section_size_before_reloc
#  define bfd_get_section_size_before_reloc bfd_get_section_size
#endif

//Private
static void map_sections_func(bfd *abfd, asection *sect, PTR obj);

static bfd * bfd_file = NULL;

// Check if file format is supported.
// return nonzero on errors.
int avrbfd_check_file_format(void)
{
    char **matching;
    int err = 1;

    // Check if archive, not plain file.
    if (bfd_check_format(bfd_file, bfd_archive) == true)
    {
        fprintf(stderr, "%s: input file is archive\n", progname);
    }

    else if (bfd_check_format_matches (bfd_file, bfd_object, &matching))
        err = 0;
    
    else if (bfd_get_error () == bfd_error_file_ambiguously_recognized)
    {
        fprintf(stderr, "%s: file format ambiguous: %s\n", progname,
                bfd_errmsg(bfd_get_error()));
    }
    
    else if (bfd_get_error () != bfd_error_file_not_recognized)
    {
        fprintf(stderr, "%s: file format not supported: %s\n", progname,
                bfd_errmsg(bfd_get_error()));
    }
    
    else if (bfd_check_format_matches (bfd_file, bfd_core, &matching))
        err = 0;

    return err;
}

/* Open associated 'filename' with the BFD lib. You must call this routine before
   any of the others, as beside just opening the file it also assigns the file
   descriptor to an internal variable the other routines will use.

   Returns 0 on success. */
int avrbfd_openr(char * filename)
    {
    struct stat ifstat;
    char *target = "default";

    if (stat(filename, &ifstat) != 0)
		{
		fprintf(stderr,"%s: can't stat() file %s\n", progname, filename);
		return -1;
		}

    // Open the input file.
    bfd_init();

        bfd_file = bfd_openr(filename, target);
        if (! bfd_file)
        {
            fprintf( stderr, "%s: could not open input file %s:%s\n", progname, filename,
                     bfd_errmsg(bfd_get_error()) );
            return -1;
        }
        
        // Check if file format is supported.
        else if (avrbfd_check_file_format())
        {
            // File format detection failed.
            fprintf(stderr, "%s: BFD File format unknown.\n", progname);
            return -1;
        }


    return 0;
    }


/* Closes off the open file. NOTE: The 'filename' argument is JUST used for
   printing a descriptive error message. The actual file closed is whichever one
   was opened with avrbfd_openr() last!!!!*/
int avrbfd_close(char * filename)
    {
    if (bfd_close(bfd_file) == false)
        {
        bfd_file = NULL;
        fprintf(stderr, "%s: error closing %s\n", progname, filename);
        return -1;
        }

    bfd_file = NULL;
    return 0;
    }

long avrbfd_get_section_size(const char * sectionname)
    {
    asection * section;
    
    section = bfd_get_section_by_name(bfd_file, sectionname);

    if (section == NULL)
        {
        return -1;
        }

    return bfd_get_section_size_before_reloc(section);
    }

/* Copies 'count' bytes from the beginning of section 'sectionname' into 'buffer', which
   must be big enough or else.... */
unsigned long avrbfd_get_section_data(const char * sectionname, long count, unsigned char * buffer)
    {
    int rc;
    asection * section;
    
    section = bfd_get_section_by_name(bfd_file, sectionname);

    if (section == NULL)
        {
        return -1;
        }

    rc =  bfd_get_section_contents(bfd_file, section, buffer, 0, count);   
    if (rc < 0)
        return -1;
    
    return avrbfd_get_section_size(sectionname);
    }

/* Copies all sections between address 0 and 'gcc_address_end' into the buffer.
   Buffer must be big enough!

   Returns 0 on success
 */

struct map_secs_struct {
				unsigned char * buf;
				unsigned long addr_end;
                unsigned long real_end;
				};

typedef struct map_secs_struct map_secs_t;


unsigned long avrbfd_make_flash_image(unsigned char * buffer, unsigned long gcc_address_end)
	{
	map_secs_t map_secs;

	map_secs.buf = buffer;
	map_secs.addr_end = gcc_address_end;
    map_secs.real_end = 0;

	bfd_map_over_sections(bfd_file, map_sections_func, &map_secs);

	return map_secs.real_end;
	}

static void map_sections_func(bfd *abfd, asection *sect, PTR obj)
	{
	
	if (verbose > 10)
		{
		fprintf(stderr,"%s: Section %s exists.\n", progname, sect->name);
		}	

	//Check we care
	if ((sect->flags & SEC_HAS_CONTENTS) && 
        ((sect->flags & SEC_ALLOC) || (sect->flags & SEC_LOAD)))
		{
		//Check address starts in our zone
		if ((sect->lma) < ((map_secs_t *)obj)->addr_end)
			{
			//Check that this doesn't somehow extend PAST the end of our size
			if ((sect->lma + sect->size) > ((map_secs_t *)obj)->addr_end)
				{
				fprintf(stderr, "%s: WARNING: Section %s starts in FLASH area but extends outside it!\n" 
						"Check type of chip. Will skip this section and continue with rest.\n", progname, sect->name);
				//POSSIBLE SHOULD ABORT PROGRAMMING HERE?
				return;
				}
			if (verbose > 10)
				{
				fprintf(stderr, "%s: Section %s starts at %ld and has size %ld\n", progname, sect->name,
						sect->lma, sect->size);
				}


			if (verbose > 1)
				{
				fprintf(stderr,"%s: Section %s being added to FLASH image.\n", progname, sect->name);
				}			

			//Error checking? Don't really report anything back though....
            bfd_get_section_contents(abfd, sect, (((map_secs_t *)obj)->buf) + sect->lma, 0, sect->size);
            ((map_secs_t *)obj)->real_end = sect->lma + sect->size;
			}

		}

	return;
	}
