/*
 * ifdtool - dump Intel Firmware Descriptor information
 *
 * Copyright (C) 2011 The ChromiumOS Authors.  All rights reserved.
 *
 * 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; version 2 of the License.
 *
 * 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.
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "ifdtool.h"

#ifndef O_BINARY
#define O_BINARY 0
#endif

static int ifd_version;

static const struct region_name region_names[MAX_REGIONS] = {
	{ "Flash Descriptor", "fd" },
	{ "BIOS", "bios" },
	{ "Intel ME", "me" },
	{ "GbE", "gbe" },
	{ "Platform Data", "pd" },
	{ "Reserved", "res1" },
	{ "Reserved", "res2" },
	{ "Reserved", "res3" },
	{ "EC", "ec" },
};

static fdbar_t *find_fd(char *image, int size)
{
	int i, found = 0;

	/* Scan for FD signature */
	for (i = 0; i < (size - 4); i += 4) {
		if (*(uint32_t *) (image + i) == 0x0FF0A55A) {
			found = 1;
			break;	// signature found.
		}
	}

	if (!found) {
		printf("No Flash Descriptor found in this image\n");
		return NULL;
	}

	return (fdbar_t *) (image + i);
}

/*
 * There is no version field in the descriptor so to determine
 * if this is a new descriptor format we check the hardcoded SPI
 * read frequency to see if it is fixed at 20MHz or 17MHz.
 */
static void check_ifd_version(char *image, int size)
{
	fdbar_t *fdb = find_fd(image, size);
	fcba_t *fcba;
	int read_freq;

	if (!fdb)
		exit(EXIT_FAILURE);

	fcba = (fcba_t *) (image + (((fdb->flmap0) & 0xff) << 4));
	if (!fcba)
		exit(EXIT_FAILURE);

	read_freq = (fcba->flcomp >> 17) & 7;

	switch (read_freq) {
	case SPI_FREQUENCY_20MHZ:
		ifd_version = IFD_VERSION_1;
		break;
	case SPI_FREQUENCY_17MHZ:
		ifd_version = IFD_VERSION_2;
		break;
	default:
		fprintf(stderr, "Unknown descriptor version: %d\n",
			read_freq);
		exit(EXIT_FAILURE);
	}
}

static region_t get_region(frba_t *frba, int region_type)
{
	int base_mask;
	int limit_mask;
	uint32_t *flreg;
	region_t region;

	if (ifd_version >= IFD_VERSION_2)
		base_mask = 0x7fff;
	else
		base_mask = 0xfff;

	limit_mask = base_mask << 16;

	switch (region_type) {
	case 0:
		flreg = &frba->flreg0;
		break;
	case 1:
		flreg = &frba->flreg1;
		break;
	case 2:
		flreg = &frba->flreg2;
		break;
	case 3:
		flreg = &frba->flreg3;
		break;
	case 4:
		flreg = &frba->flreg4;
		break;
	case 5:
		flreg = &frba->flreg5;
		break;
	case 6:
		flreg = &frba->flreg6;
		break;
	case 7:
		flreg = &frba->flreg7;
		break;
	case 8:
		flreg = &frba->flreg8;
		break;
	default:
		fprintf(stderr, "Invalid region type %d.\n", region_type);
		exit (EXIT_FAILURE);
	}

	region.base = (*flreg & base_mask) << 12;
	region.limit = ((*flreg & limit_mask) >> 4) | 0xfff;
	region.size = region.limit - region.base + 1;

	if (region.size < 0)
		region.size = 0;

	return region;
}

static void set_region(frba_t *frba, int region_type, region_t region)
{
	switch (region_type) {
	case 0:
		frba->flreg0 = (((region.limit >> 12) & 0x7fff) << 16)
			| ((region.base >> 12) & 0x7fff);
		break;
	case 1:
		frba->flreg1 = (((region.limit >> 12) & 0x7fff) << 16)
			| ((region.base >> 12) & 0x7fff);
		break;
	case 2:
		frba->flreg2 = (((region.limit >> 12) & 0x7fff) << 16)
			| ((region.base >> 12) & 0x7fff);
		break;
	case 3:
		frba->flreg3 = (((region.limit >> 12) & 0x7fff) << 16)
			| ((region.base >> 12) & 0x7fff);
		break;
	case 4:
		frba->flreg4 = (((region.limit >> 12) & 0x7fff) << 16)
			| ((region.base >> 12) & 0x7fff);
		break;
	default:
		fprintf(stderr, "Invalid region type.\n");
		exit (EXIT_FAILURE);
	}
}

static const char *region_name(int region_type)
{
	if (region_type < 0 || region_type >= MAX_REGIONS) {
		fprintf(stderr, "Invalid region type.\n");
		exit (EXIT_FAILURE);
	}

	return region_names[region_type].pretty;
}

static const char *region_name_short(int region_type)
{
	if (region_type < 0 || region_type >= MAX_REGIONS) {
		fprintf(stderr, "Invalid region type.\n");
		exit (EXIT_FAILURE);
	}

	return region_names[region_type].terse;
}

static int region_num(const char *name)
{
	int i;

	for (i = 0; i < MAX_REGIONS; i++) {
		if (strcasecmp(name, region_names[i].pretty) == 0)
			return i;
		if (strcasecmp(name, region_names[i].terse) == 0)
			return i;
	}

	return -1;
}

static const char *region_filename(int region_type)
{
	static const char *region_filenames[MAX_REGIONS] = {
		"flashregion_0_flashdescriptor.bin",
		"flashregion_1_bios.bin",
		"flashregion_2_intel_me.bin",
		"flashregion_3_gbe.bin",
		"flashregion_4_platform_data.bin",
		"flashregion_5_reserved.bin",
		"flashregion_6_reserved.bin",
		"flashregion_7_reserved.bin",
		"flashregion_8_ec.bin",
	};

	if (region_type < 0 || region_type >= MAX_REGIONS) {
		fprintf(stderr, "Invalid region type %d.\n", region_type);
		exit (EXIT_FAILURE);
	}

	return region_filenames[region_type];
}

static void dump_region(int num, frba_t *frba)
{
	region_t region = get_region(frba, num);
	printf("  Flash Region %d (%s): %08x - %08x %s\n",
		       num, region_name(num), region.base, region.limit,
		       region.size < 1 ? "(unused)" : "");
}

static void dump_region_layout(char *buf, size_t bufsize, int num, frba_t *frba)
{
	region_t region = get_region(frba, num);
	snprintf(buf, bufsize, "%08x:%08x %s\n",
		region.base, region.limit, region_name_short(num));
}

static void dump_frba(frba_t * frba)
{
	printf("Found Region Section\n");
	printf("FLREG0:    0x%08x\n", frba->flreg0);
	dump_region(0, frba);
	printf("FLREG1:    0x%08x\n", frba->flreg1);
	dump_region(1, frba);
	printf("FLREG2:    0x%08x\n", frba->flreg2);
	dump_region(2, frba);
	printf("FLREG3:    0x%08x\n", frba->flreg3);
	dump_region(3, frba);
	printf("FLREG4:    0x%08x\n", frba->flreg4);
	dump_region(4, frba);

	if (ifd_version >= IFD_VERSION_2) {
		printf("FLREG5:    0x%08x\n", frba->flreg5);
		dump_region(5, frba);
		printf("FLREG6:    0x%08x\n", frba->flreg6);
		dump_region(6, frba);
		printf("FLREG7:    0x%08x\n", frba->flreg7);
		dump_region(7, frba);
		printf("FLREG8:    0x%08x\n", frba->flreg8);
		dump_region(8, frba);
	}
}

static void dump_frba_layout(frba_t * frba, char *layout_fname)
{
	char buf[LAYOUT_LINELEN];
	size_t bufsize = LAYOUT_LINELEN;
	int i;

	int layout_fd = open(layout_fname, O_WRONLY | O_CREAT | O_TRUNC,
			S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	if (layout_fd == -1) {
		perror("Could not open file");
		exit(EXIT_FAILURE);
	}

	for (i = 0; i < MAX_REGIONS; i++) {
		dump_region_layout(buf, bufsize, i, frba);
		if (write(layout_fd, buf, strlen(buf)) < 0) {
			perror("Could not write to file");
			exit(EXIT_FAILURE);
		}
	}
	close(layout_fd);
	printf("Wrote layout to %s\n", layout_fname);
}

static void decode_spi_frequency(unsigned int freq)
{
	switch (freq) {
	case SPI_FREQUENCY_20MHZ:
		printf("20MHz");
		break;
	case SPI_FREQUENCY_33MHZ:
		printf("33MHz");
		break;
	case SPI_FREQUENCY_48MHZ:
		printf("48MHz");
		break;
	case SPI_FREQUENCY_50MHZ_30MHZ:
		switch (ifd_version) {
		case IFD_VERSION_1:
			printf("50MHz");
			break;
		case IFD_VERSION_2:
			printf("30MHz");
			break;
		}
		break;
	case SPI_FREQUENCY_17MHZ:
		printf("17MHz");
		break;
	default:
		printf("unknown<%x>MHz", freq);
	}
}

static void decode_component_density(unsigned int density)
{
	switch (density) {
	case COMPONENT_DENSITY_512KB:
		printf("512KB");
		break;
	case COMPONENT_DENSITY_1MB:
		printf("1MB");
		break;
	case COMPONENT_DENSITY_2MB:
		printf("2MB");
		break;
	case COMPONENT_DENSITY_4MB:
		printf("4MB");
		break;
	case COMPONENT_DENSITY_8MB:
		printf("8MB");
		break;
	case COMPONENT_DENSITY_16MB:
		printf("16MB");
		break;
	case COMPONENT_DENSITY_32MB:
		printf("32MB");
		break;
	case COMPONENT_DENSITY_64MB:
		printf("64MB");
		break;
	case COMPONENT_DENSITY_UNUSED:
		printf("UNUSED");
		break;
	default:
		printf("unknown<%x>MB", density);
	}
}

static void dump_fcba(fcba_t * fcba)
{
	printf("\nFound Component Section\n");
	printf("FLCOMP     0x%08x\n", fcba->flcomp);
	printf("  Dual Output Fast Read Support:       %ssupported\n",
		(fcba->flcomp & (1 << 30))?"":"not ");
	printf("  Read ID/Read Status Clock Frequency: ");
	decode_spi_frequency((fcba->flcomp >> 27) & 7);
	printf("\n  Write/Erase Clock Frequency:         ");
	decode_spi_frequency((fcba->flcomp >> 24) & 7);
	printf("\n  Fast Read Clock Frequency:           ");
	decode_spi_frequency((fcba->flcomp >> 21) & 7);
	printf("\n  Fast Read Support:                   %ssupported",
		(fcba->flcomp & (1 << 20))?"":"not ");
	printf("\n  Read Clock Frequency:                ");
	decode_spi_frequency((fcba->flcomp >> 17) & 7);

	switch (ifd_version) {
	case IFD_VERSION_1:
		printf("\n  Component 2 Density:                 ");
		decode_component_density((fcba->flcomp >> 3) & 7);
		printf("\n  Component 1 Density:                 ");
		decode_component_density(fcba->flcomp & 7);
		break;
	case IFD_VERSION_2:
		printf("\n  Component 2 Density:                 ");
		decode_component_density((fcba->flcomp >> 4) & 0xf);
		printf("\n  Component 1 Density:                 ");
		decode_component_density(fcba->flcomp & 0xf);
		break;
	}

	printf("\n");
	printf("FLILL      0x%08x\n", fcba->flill);
	printf("  Invalid Instruction 3: 0x%02x\n",
		(fcba->flill >> 24) & 0xff);
	printf("  Invalid Instruction 2: 0x%02x\n",
		(fcba->flill >> 16) & 0xff);
	printf("  Invalid Instruction 1: 0x%02x\n",
		(fcba->flill >> 8) & 0xff);
	printf("  Invalid Instruction 0: 0x%02x\n",
		fcba->flill & 0xff);
	printf("FLPB       0x%08x\n", fcba->flpb);
	printf("  Flash Partition Boundary Address: 0x%06x\n\n",
		(fcba->flpb & 0xfff) << 12);
}

static void dump_fpsba(fpsba_t * fpsba)
{
	printf("Found PCH Strap Section\n");
	printf("PCHSTRP0:  0x%08x\n", fpsba->pchstrp0);
	printf("PCHSTRP1:  0x%08x\n", fpsba->pchstrp1);
	printf("PCHSTRP2:  0x%08x\n", fpsba->pchstrp2);
	printf("PCHSTRP3:  0x%08x\n", fpsba->pchstrp3);
	printf("PCHSTRP4:  0x%08x\n", fpsba->pchstrp4);
	printf("PCHSTRP5:  0x%08x\n", fpsba->pchstrp5);
	printf("PCHSTRP6:  0x%08x\n", fpsba->pchstrp6);
	printf("PCHSTRP7:  0x%08x\n", fpsba->pchstrp7);
	printf("PCHSTRP8:  0x%08x\n", fpsba->pchstrp8);
	printf("PCHSTRP9:  0x%08x\n", fpsba->pchstrp9);
	printf("PCHSTRP10: 0x%08x\n", fpsba->pchstrp10);
	printf("PCHSTRP11: 0x%08x\n", fpsba->pchstrp11);
	printf("PCHSTRP12: 0x%08x\n", fpsba->pchstrp12);
	printf("PCHSTRP13: 0x%08x\n", fpsba->pchstrp13);
	printf("PCHSTRP14: 0x%08x\n", fpsba->pchstrp14);
	printf("PCHSTRP15: 0x%08x\n", fpsba->pchstrp15);
	printf("PCHSTRP16: 0x%08x\n", fpsba->pchstrp16);
	printf("PCHSTRP17: 0x%08x\n\n", fpsba->pchstrp17);
}

static void decode_flmstr(uint32_t flmstr)
{
	int wr_shift, rd_shift;
	if (ifd_version >= IFD_VERSION_2) {
		wr_shift = FLMSTR_WR_SHIFT_V2;
		rd_shift = FLMSTR_RD_SHIFT_V2;
	} else {
		wr_shift = FLMSTR_WR_SHIFT_V1;
		rd_shift = FLMSTR_RD_SHIFT_V1;
	}

	/* EC region access only available on v2+ */
	if (ifd_version >= IFD_VERSION_2)
		printf("  EC Region Write Access:            %s\n",
		       (flmstr & (1 << (wr_shift + 8))) ?
		       "enabled" : "disabled");
	printf("  Platform Data Region Write Access: %s\n",
		(flmstr & (1 << (wr_shift + 4))) ? "enabled" : "disabled");
	printf("  GbE Region Write Access:           %s\n",
		(flmstr & (1 << (wr_shift + 3))) ? "enabled" : "disabled");
	printf("  Intel ME Region Write Access:      %s\n",
		(flmstr & (1 << (wr_shift + 2))) ? "enabled" : "disabled");
	printf("  Host CPU/BIOS Region Write Access: %s\n",
		(flmstr & (1 << (wr_shift + 1))) ? "enabled" : "disabled");
	printf("  Flash Descriptor Write Access:     %s\n",
		(flmstr & (1 << wr_shift)) ? "enabled" : "disabled");

	if (ifd_version >= IFD_VERSION_2)
		printf("  EC Region Read Access:             %s\n",
		       (flmstr & (1 << (rd_shift + 8))) ?
		       "enabled" : "disabled");
	printf("  Platform Data Region Read Access:  %s\n",
		(flmstr & (1 << (rd_shift + 4))) ? "enabled" : "disabled");
	printf("  GbE Region Read Access:            %s\n",
		(flmstr & (1 << (rd_shift + 3))) ? "enabled" : "disabled");
	printf("  Intel ME Region Read Access:       %s\n",
		(flmstr & (1 << (rd_shift + 2))) ? "enabled" : "disabled");
	printf("  Host CPU/BIOS Region Read Access:  %s\n",
		(flmstr & (1 << (rd_shift + 1))) ? "enabled" : "disabled");
	printf("  Flash Descriptor Read Access:      %s\n",
		(flmstr & (1 << rd_shift)) ? "enabled" : "disabled");

	/* Requestor ID doesn't exist for ifd 2 */
	if (ifd_version < IFD_VERSION_2)
		printf("  Requester ID:                      0x%04x\n\n",
			flmstr & 0xffff);
}

static void dump_fmba(fmba_t * fmba)
{
	printf("Found Master Section\n");
	printf("FLMSTR1:   0x%08x (Host CPU/BIOS)\n", fmba->flmstr1);
	decode_flmstr(fmba->flmstr1);
	printf("FLMSTR2:   0x%08x (Intel ME)\n", fmba->flmstr2);
	decode_flmstr(fmba->flmstr2);
	printf("FLMSTR3:   0x%08x (GbE)\n", fmba->flmstr3);
	decode_flmstr(fmba->flmstr3);
	if (ifd_version >= IFD_VERSION_2) {
		printf("FLMSTR5:   0x%08x (EC)\n", fmba->flmstr5);
		decode_flmstr(fmba->flmstr5);
	}
}

static void dump_fmsba(fmsba_t * fmsba)
{
	printf("Found Processor Strap Section\n");
	printf("????:      0x%08x\n", fmsba->data[0]);
	printf("????:      0x%08x\n", fmsba->data[1]);
	printf("????:      0x%08x\n", fmsba->data[2]);
	printf("????:      0x%08x\n", fmsba->data[3]);
}

static void dump_jid(uint32_t jid)
{
	printf("    SPI Componend Device ID 1:          0x%02x\n",
		(jid >> 16) & 0xff);
	printf("    SPI Componend Device ID 0:          0x%02x\n",
		(jid >> 8) & 0xff);
	printf("    SPI Componend Vendor ID:            0x%02x\n",
		jid & 0xff);
}

static void dump_vscc(uint32_t vscc)
{
	printf("    Lower Erase Opcode:                 0x%02x\n",
		vscc >> 24);
	printf("    Lower Write Enable on Write Status: 0x%02x\n",
		vscc & (1 << 20) ? 0x06 : 0x50);
	printf("    Lower Write Status Required:        %s\n",
		vscc & (1 << 19) ? "Yes" : "No");
	printf("    Lower Write Granularity:            %d bytes\n",
		vscc & (1 << 18) ? 64 : 1);
	printf("    Lower Block / Sector Erase Size:    ");
	switch ((vscc >> 16) & 0x3) {
	case 0:
		printf("256 Byte\n");
		break;
	case 1:
		printf("4KB\n");
		break;
	case 2:
		printf("8KB\n");
		break;
	case 3:
		printf("64KB\n");
		break;
	}

	printf("    Upper Erase Opcode:                 0x%02x\n",
		(vscc >> 8) & 0xff);
	printf("    Upper Write Enable on Write Status: 0x%02x\n",
		vscc & (1 << 4) ? 0x06 : 0x50);
	printf("    Upper Write Status Required:        %s\n",
		vscc & (1 << 3) ? "Yes" : "No");
	printf("    Upper Write Granularity:            %d bytes\n",
		vscc & (1 << 2) ? 64 : 1);
	printf("    Upper Block / Sector Erase Size:    ");
	switch (vscc & 0x3) {
	case 0:
		printf("256 Byte\n");
		break;
	case 1:
		printf("4KB\n");
		break;
	case 2:
		printf("8KB\n");
		break;
	case 3:
		printf("64KB\n");
		break;
	}
}

static void dump_vtba(vtba_t *vtba, int vtl)
{
	int i;
	int num = (vtl >> 1) < 8 ? (vtl >> 1) : 8;

	printf("ME VSCC table:\n");
	for (i = 0; i < num; i++) {
		printf("  JID%d:  0x%08x\n", i, vtba->entry[i].jid);
		dump_jid(vtba->entry[i].jid);
		printf("  VSCC%d: 0x%08x\n", i, vtba->entry[i].vscc);
		dump_vscc(vtba->entry[i].vscc);
	}
	printf("\n");
}

static void dump_oem(uint8_t *oem)
{
	int i, j;
	printf("OEM Section:\n");
	for (i = 0; i < 4; i++) {
		printf("%02x:", i << 4);
		for (j = 0; j < 16; j++)
			printf(" %02x", oem[(i<<4)+j]);
		printf ("\n");
	}
	printf ("\n");
}

static void dump_fd(char *image, int size)
{
	fdbar_t *fdb = find_fd(image, size);
	if (!fdb)
		exit(EXIT_FAILURE);

	printf("FLMAP0:    0x%08x\n", fdb->flmap0);
	printf("  NR:      %d\n", (fdb->flmap0 >> 24) & 7);
	printf("  FRBA:    0x%x\n", ((fdb->flmap0 >> 16) & 0xff) << 4);
	printf("  NC:      %d\n", ((fdb->flmap0 >> 8) & 3) + 1);
	printf("  FCBA:    0x%x\n", ((fdb->flmap0) & 0xff) << 4);

	printf("FLMAP1:    0x%08x\n", fdb->flmap1);
	printf("  ISL:     0x%02x\n", (fdb->flmap1 >> 24) & 0xff);
	printf("  FPSBA:   0x%x\n", ((fdb->flmap1 >> 16) & 0xff) << 4);
	printf("  NM:      %d\n", (fdb->flmap1 >> 8) & 3);
	printf("  FMBA:    0x%x\n", ((fdb->flmap1) & 0xff) << 4);

	printf("FLMAP2:    0x%08x\n", fdb->flmap2);
	printf("  PSL:     0x%04x\n", (fdb->flmap2 >> 8) & 0xffff);
	printf("  FMSBA:   0x%x\n", ((fdb->flmap2) & 0xff) << 4);

	printf("FLUMAP1:   0x%08x\n", fdb->flumap1);
	printf("  Intel ME VSCC Table Length (VTL):        %d\n",
		(fdb->flumap1 >> 8) & 0xff);
	printf("  Intel ME VSCC Table Base Address (VTBA): 0x%06x\n\n",
		(fdb->flumap1 & 0xff) << 4);
	dump_vtba((vtba_t *)
			(image + ((fdb->flumap1 & 0xff) << 4)),
			(fdb->flumap1 >> 8) & 0xff);
	dump_oem((uint8_t *)image + 0xf00);
	dump_frba((frba_t *)
			(image + (((fdb->flmap0 >> 16) & 0xff) << 4)));
	dump_fcba((fcba_t *) (image + (((fdb->flmap0) & 0xff) << 4)));
	dump_fpsba((fpsba_t *)
			(image + (((fdb->flmap1 >> 16) & 0xff) << 4)));
	dump_fmba((fmba_t *) (image + (((fdb->flmap1) & 0xff) << 4)));
	dump_fmsba((fmsba_t *) (image + (((fdb->flmap2) & 0xff) << 4)));
}

static void dump_layout(char *image, int size, char *layout_fname)
{
	fdbar_t *fdb = find_fd(image, size);
	if (!fdb)
		exit(EXIT_FAILURE);

	dump_frba_layout((frba_t *)
			(image + (((fdb->flmap0 >> 16) & 0xff) << 4)),
			layout_fname);
}

static void write_regions(char *image, int size)
{
	int i;
	int max_regions = MAX_REGIONS;

	fdbar_t *fdb = find_fd(image, size);
	if (!fdb)
		exit(EXIT_FAILURE);

	frba_t *frba =
	    (frba_t *) (image + (((fdb->flmap0 >> 16) & 0xff) << 4));

	/* Older descriptor images have fewer regions */
	if (ifd_version < IFD_VERSION_2)
		max_regions = MAX_REGIONS_OLD;

	for (i = 0; i < max_regions; i++) {
		region_t region = get_region(frba, i);
		dump_region(i, frba);
		if (region.size > 0) {
			int region_fd;
			region_fd = open(region_filename(i),
					 O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
					 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
			if (region_fd < 0) {
				perror("Error while trying to open file");
				exit(EXIT_FAILURE);
			}
			if (write(region_fd, image + region.base, region.size) != region.size)
				perror("Error while writing");
			close(region_fd);
		}
	}
}

static void write_image(char *filename, char *image, int size)
{
	char new_filename[FILENAME_MAX]; // allow long file names
	int new_fd;

	// - 5: leave room for ".new\0"
	strncpy(new_filename, filename, FILENAME_MAX - 5);
	strncat(new_filename, ".new", FILENAME_MAX - strlen(filename));

	printf("Writing new image to %s\n", new_filename);

	// Now write out new image
	new_fd = open(new_filename,
			 O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
			 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	if (new_fd < 0) {
		perror("Error while trying to open file");
		exit(EXIT_FAILURE);
	}
	if (write(new_fd, image, size) != size)
		perror("Error while writing");
	close(new_fd);
}

static void set_spi_frequency(char *filename, char *image, int size,
			      enum spi_frequency freq)
{
	fdbar_t *fdb = find_fd(image, size);
	fcba_t *fcba = (fcba_t *) (image + (((fdb->flmap0) & 0xff) << 4));

	/* clear bits 21-29 */
	fcba->flcomp &= ~0x3fe00000;
	/* Read ID and Read Status Clock Frequency */
	fcba->flcomp |= freq << 27;
	/* Write and Erase Clock Frequency */
	fcba->flcomp |= freq << 24;
	/* Fast Read Clock Frequency */
	fcba->flcomp |= freq << 21;

	write_image(filename, image, size);
}

static void set_em100_mode(char *filename, char *image, int size)
{
	fdbar_t *fdb = find_fd(image, size);
	fcba_t *fcba = (fcba_t *) (image + (((fdb->flmap0) & 0xff) << 4));
	int freq;

	switch (ifd_version) {
	case IFD_VERSION_1:
		freq = SPI_FREQUENCY_20MHZ;
		break;
	case IFD_VERSION_2:
		freq = SPI_FREQUENCY_17MHZ;
		break;
	default:
		freq = SPI_FREQUENCY_17MHZ;
		break;
	}

	fcba->flcomp &= ~(1 << 30);
	set_spi_frequency(filename, image, size, freq);
}

static void lock_descriptor(char *filename, char *image, int size)
{
	int wr_shift, rd_shift;
	fdbar_t *fdb = find_fd(image, size);
	fmba_t *fmba = (fmba_t *) (image + (((fdb->flmap1) & 0xff) << 4));
	/* TODO: Dynamically take Platform Data Region and GbE Region
	 * into regard.
	 */

	if (ifd_version >= IFD_VERSION_2) {
		wr_shift = FLMSTR_WR_SHIFT_V2;
		rd_shift = FLMSTR_RD_SHIFT_V2;

		/* Clear non-reserved bits */
		fmba->flmstr1 &= 0xff;
		fmba->flmstr2 &= 0xff;
		fmba->flmstr3 &= 0xff;
	} else {
		wr_shift = FLMSTR_WR_SHIFT_V1;
		rd_shift = FLMSTR_RD_SHIFT_V1;

		fmba->flmstr1 = 0;
		fmba->flmstr2 = 0;
		/* Requestor ID */
		fmba->flmstr3 = 0x118;
	}

	/* CPU/BIOS can read descriptor, BIOS, and GbE. */
	fmba->flmstr1 |= 0xb << rd_shift;
	/* CPU/BIOS can write BIOS and GbE. */
	fmba->flmstr1 |= 0xa << wr_shift;
	/* ME can read descriptor, ME, and GbE. */
	fmba->flmstr2 |= 0xd << rd_shift;
	/* ME can write ME and GbE. */
	fmba->flmstr2 |= 0xc << wr_shift;
	/* GbE can write only GbE. */
	fmba->flmstr3 |= 0x8 << rd_shift;
	/* GbE can read only GbE. */
	fmba->flmstr3 |= 0x8 << wr_shift;

	write_image(filename, image, size);
}

static void unlock_descriptor(char *filename, char *image, int size)
{
	fdbar_t *fdb = find_fd(image, size);
	fmba_t *fmba = (fmba_t *) (image + (((fdb->flmap1) & 0xff) << 4));

	if (ifd_version >= IFD_VERSION_2) {
		/* Access bits for each region are read: 19:8 write: 31:20 */
		fmba->flmstr1 = 0xffffff00 | (fmba->flmstr1 & 0xff);
		fmba->flmstr2 = 0xffffff00 | (fmba->flmstr2 & 0xff);
		fmba->flmstr3 = 0xffffff00 | (fmba->flmstr3 & 0xff);
	} else {
		fmba->flmstr1 = 0xffff0000;
		fmba->flmstr2 = 0xffff0000;
		fmba->flmstr3 = 0x08080118;
	}

	write_image(filename, image, size);
}

void inject_region(char *filename, char *image, int size, int region_type,
		   char *region_fname)
{
	fdbar_t *fdb = find_fd(image, size);
	if (!fdb)
		exit(EXIT_FAILURE);
	frba_t *frba =
	    (frba_t *) (image + (((fdb->flmap0 >> 16) & 0xff) << 4));

	region_t region = get_region(frba, region_type);
	if (region.size <= 0xfff) {
		fprintf(stderr, "Region %s is disabled in target. Not injecting.\n",
				region_name(region_type));
		exit(EXIT_FAILURE);
	}

	int region_fd = open(region_fname, O_RDONLY | O_BINARY);
	if (region_fd == -1) {
		perror("Could not open file");
		exit(EXIT_FAILURE);
	}
	struct stat buf;
	if (fstat(region_fd, &buf) == -1) {
		perror("Could not stat file");
		exit(EXIT_FAILURE);
	}
	int region_size = buf.st_size;

	printf("File %s is %d bytes\n", region_fname, region_size);

	if ( (region_size > region.size) || ((region_type != 1) &&
		(region_size > region.size))) {
		fprintf(stderr, "Region %s is %d(0x%x) bytes. File is %d(0x%x)"
				" bytes. Not injecting.\n",
				region_name(region_type), region.size,
				region.size, region_size, region_size);
		exit(EXIT_FAILURE);
	}

	int offset = 0;
	if ((region_type == 1) && (region_size < region.size)) {
		fprintf(stderr, "Region %s is %d(0x%x) bytes. File is %d(0x%x)"
				" bytes. Padding before injecting.\n",
				region_name(region_type), region.size,
				region.size, region_size, region_size);
		offset = region.size - region_size;
		memset(image + region.base, 0xff, offset);
	}

	if (size < region.base + offset + region_size) {
		fprintf(stderr, "Output file is too small. (%d < %d)\n",
			size, region.base + offset + region_size);
		exit(EXIT_FAILURE);
	}

	if (read(region_fd, image + region.base + offset, region_size)
							!= region_size) {
		perror("Could not read file");
		exit(EXIT_FAILURE);
	}

	close(region_fd);

	printf("Adding %s as the %s section of %s\n",
	       region_fname, region_name(region_type), filename);
	write_image(filename, image, size);
}

unsigned int next_pow2(unsigned int x)
{
	unsigned int y = 1;
	if (x == 0)
		return 0;
	while (y <= x)
		y = y << 1;

	return y;
}

/**
 * Determine if two memory regions overlap.
 *
 * @param r1, r2 Memory regions to compare.
 * @return 0 if the two regions are seperate
 * @return 1 if the two regions overlap
 */
static int regions_collide(region_t r1, region_t r2)
{
	if ((r1.size == 0) || (r2.size == 0))
		return 0;

	if ( ((r1.base >= r2.base) && (r1.base <= r2.limit)) ||
	     ((r1.limit >= r2.base) && (r1.limit <= r2.limit)) )
		return 1;

	return 0;
}

void new_layout(char *filename, char *image, int size, char *layout_fname)
{
	FILE *romlayout;
	char tempstr[256];
	char layout_region_name[256];
	int i, j;
	int region_number;
	region_t current_regions[MAX_REGIONS];
	region_t new_regions[MAX_REGIONS];
	int new_extent = 0;
	char *new_image;

	/* load current descriptor map and regions */
	fdbar_t *fdb = find_fd(image, size);
	if (!fdb)
		exit(EXIT_FAILURE);

	frba_t *frba =
	    (frba_t *) (image + (((fdb->flmap0 >> 16) & 0xff) << 4));

	for (i = 0; i < MAX_REGIONS; i++) {
		current_regions[i] = get_region(frba, i);
		new_regions[i] = get_region(frba, i);
	}

	/* read new layout */
	romlayout = fopen(layout_fname, "r");

	if (!romlayout) {
		perror("Could not read layout file.\n");
		exit(EXIT_FAILURE);
	}

	while (!feof(romlayout)) {
		char *tstr1, *tstr2;

		if (2 != fscanf(romlayout, "%255s %255s\n", tempstr,
					layout_region_name))
			continue;

		region_number = region_num(layout_region_name);
		if (region_number < 0)
			continue;

		tstr1 = strtok(tempstr, ":");
		tstr2 = strtok(NULL, ":");
		if (!tstr1 || !tstr2) {
			fprintf(stderr, "Could not parse layout file.\n");
			exit(EXIT_FAILURE);
		}
		new_regions[region_number].base = strtol(tstr1,
				(char **)NULL, 16);
		new_regions[region_number].limit = strtol(tstr2,
				(char **)NULL, 16);
		new_regions[region_number].size =
			new_regions[region_number].limit -
			new_regions[region_number].base + 1;

		if (new_regions[region_number].size < 0)
			new_regions[region_number].size = 0;
	}
	fclose(romlayout);

	/* check new layout */
	for (i = 0; i < MAX_REGIONS; i++) {
		if (new_regions[i].size == 0)
			continue;

		if (new_regions[i].size < current_regions[i].size) {
			printf("DANGER: Region %s is shrinking.\n",
					region_name(i));
			printf("    The region will be truncated to fit.\n");
			printf("    This may result in an unusable image.\n");
		}

		for (j = i + 1; j < MAX_REGIONS; j++) {
			if (regions_collide(new_regions[i], new_regions[j])) {
				fprintf(stderr, "Regions would overlap.\n");
				exit(EXIT_FAILURE);
			}
		}

		/* detect if the image size should grow */
		if (new_extent < new_regions[i].limit)
			new_extent = new_regions[i].limit;
	}

	new_extent = next_pow2(new_extent - 1);
	if (new_extent != size) {
		printf("The image has changed in size.\n");
		printf("The old image is %d bytes.\n", size);
		printf("The new image is %d bytes.\n", new_extent);
	}

	/* copy regions to a new image */
	new_image = malloc(new_extent);
	memset(new_image, 0xff, new_extent);
	for (i = 0; i < MAX_REGIONS; i++) {
		int copy_size = new_regions[i].size;
		int offset_current = 0, offset_new = 0;
		region_t current = current_regions[i];
		region_t new = new_regions[i];

		if (new.size == 0)
			continue;

		if (new.size > current.size) {
			/* copy from the end of the current region */
			copy_size = current.size;
			offset_new = new.size - current.size;
		}

		if (new.size < current.size) {
			/* copy to the end of the new region */
			offset_current = current.size - new.size;
		}

		printf("Copy Descriptor %d (%s) (%d bytes)\n", i,
				region_name(i), copy_size);
		printf("   from %08x+%08x:%08x (%10d)\n", current.base,
				offset_current, current.limit, current.size);
		printf("     to %08x+%08x:%08x (%10d)\n", new.base,
				offset_new, new.limit, new.size);

		memcpy(new_image + new.base + offset_new,
				image + current.base + offset_current,
				copy_size);
	}

	/* update new descriptor regions */
	fdb = find_fd(new_image, new_extent);
	if (!fdb)
		exit(EXIT_FAILURE);

	frba = (frba_t *) (new_image + (((fdb->flmap0 >> 16) & 0xff) << 4));
	for (i = 1; i < MAX_REGIONS; i++) {
		set_region(frba, i, new_regions[i]);
	}

	write_image(filename, new_image, new_extent);
	free(new_image);
}

static void print_version(void)
{
	printf("ifdtool v%s -- ", IFDTOOL_VERSION);
	printf("Copyright (C) 2011 Google Inc.\n\n");
	printf
	    ("This program is free software: you can redistribute it and/or modify\n"
	     "it under the terms of the GNU General Public License as published by\n"
	     "the Free Software Foundation, version 2 of the License.\n\n"
	     "This program is distributed in the hope that it will be useful,\n"
	     "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
	     "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
	     "GNU General Public License for more details.\n\n"
	     "You should have received a copy of the GNU General Public License\n"
	     "along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n");
}

static void print_usage(const char *name)
{
	printf("usage: %s [-vhdix?] <filename>\n", name);
	printf("\n"
	       "   -d | --dump:                       dump intel firmware descriptor\n"
	       "   -f | --layout <filename>           dump regions into a flashrom layout file\n"
	       "   -x | --extract:                    extract intel fd modules\n"
	       "   -i | --inject <region>:<module>    inject file <module> into region <region>\n"
	       "   -n | --newlayout <filename>        update regions using a flashrom layout file\n"
	       "   -s | --spifreq <17|20|30|33|48|50> set the SPI frequency\n"
	       "   -e | --em100                       set SPI frequency to 20MHz and disable\n"
	       "                                      Dual Output Fast Read Support\n"
	       "   -l | --lock                        Lock firmware descriptor and ME region\n"
	       "   -u | --unlock                      Unlock firmware descriptor and ME region\n"
	       "   -v | --version:                    print the version\n"
	       "   -h | --help:                       print this help\n\n"
	       "<region> is one of Descriptor, BIOS, ME, GbE, Platform\n"
	       "\n");
}

int main(int argc, char *argv[])
{
	int opt, option_index = 0;
	int mode_dump = 0, mode_extract = 0, mode_inject = 0, mode_spifreq = 0;
	int mode_em100 = 0, mode_locked = 0, mode_unlocked = 0;
	int mode_layout = 0, mode_newlayout = 0;
	char *region_type_string = NULL, *region_fname = NULL, *layout_fname = NULL;
	int region_type = -1, inputfreq = 0;
	enum spi_frequency spifreq = SPI_FREQUENCY_20MHZ;

	static struct option long_options[] = {
		{"dump", 0, NULL, 'd'},
		{"layout", 1, NULL, 'f'},
		{"extract", 0, NULL, 'x'},
		{"inject", 1, NULL, 'i'},
		{"newlayout", 1, NULL, 'n'},
		{"spifreq", 1, NULL, 's'},
		{"em100", 0, NULL, 'e'},
		{"lock", 0, NULL, 'l'},
		{"unlock", 0, NULL, 'u'},
		{"version", 0, NULL, 'v'},
		{"help", 0, NULL, 'h'},
		{0, 0, 0, 0}
	};

	while ((opt = getopt_long(argc, argv, "df:xi:n:s:eluvh?",
				  long_options, &option_index)) != EOF) {
		switch (opt) {
		case 'd':
			mode_dump = 1;
			break;
		case 'f':
			mode_layout = 1;
			layout_fname = strdup(optarg);
			if (!layout_fname) {
				fprintf(stderr, "No layout file specified\n");
				print_usage(argv[0]);
				exit(EXIT_FAILURE);
			}
			break;
		case 'x':
			mode_extract = 1;
			break;
		case 'i':
			// separate type and file name
			region_type_string = strdup(optarg);
			region_fname = strchr(region_type_string, ':');
			if (!region_fname) {
				print_usage(argv[0]);
				exit(EXIT_FAILURE);
			}
			region_fname[0] = '\0';
			region_fname++;
			// Descriptor, BIOS, ME, GbE, Platform
			// valid type?
			if (!strcasecmp("Descriptor", region_type_string))
				region_type = 0;
			else if (!strcasecmp("BIOS", region_type_string))
				region_type = 1;
			else if (!strcasecmp("ME", region_type_string))
				region_type = 2;
			else if (!strcasecmp("GbE", region_type_string))
				region_type = 3;
			else if (!strcasecmp("Platform", region_type_string))
				region_type = 4;
			else if (!strcasecmp("EC", region_type_string))
				region_type = 8;
			if (region_type == -1) {
				fprintf(stderr, "No such region type: '%s'\n\n",
					region_type_string);
				print_usage(argv[0]);
				exit(EXIT_FAILURE);
			}
			mode_inject = 1;
			break;
		case 'n':
			mode_newlayout = 1;
			layout_fname = strdup(optarg);
			if (!layout_fname) {
				fprintf(stderr, "No layout file specified\n");
				print_usage(argv[0]);
				exit(EXIT_FAILURE);
			}
			break;
		case 's':
			// Parse the requested SPI frequency
			inputfreq = strtol(optarg, NULL, 0);
			switch (inputfreq) {
			case 17:
				spifreq = SPI_FREQUENCY_17MHZ;
				break;
			case 20:
				spifreq = SPI_FREQUENCY_20MHZ;
				break;
			case 30:
				spifreq = SPI_FREQUENCY_50MHZ_30MHZ;
				break;
			case 33:
				spifreq = SPI_FREQUENCY_33MHZ;
				break;
			case 48:
				spifreq = SPI_FREQUENCY_48MHZ;
				break;
			case 50:
				spifreq = SPI_FREQUENCY_50MHZ_30MHZ;
				break;
			default:
				fprintf(stderr, "Invalid SPI Frequency: %d\n",
					inputfreq);
				print_usage(argv[0]);
				exit(EXIT_FAILURE);
			}
			mode_spifreq = 1;
			break;
		case 'e':
			mode_em100 = 1;
			break;
		case 'l':
			mode_locked = 1;
			if (mode_unlocked == 1) {
				fprintf(stderr, "Locking/Unlocking FD and ME are mutually exclusive\n");
				exit(EXIT_FAILURE);
			}
			break;
		case 'u':
			mode_unlocked = 1;
			if (mode_locked == 1) {
				fprintf(stderr, "Locking/Unlocking FD and ME are mutually exclusive\n");
				exit(EXIT_FAILURE);
			}
			break;
		case 'v':
			print_version();
			exit(EXIT_SUCCESS);
			break;
		case 'h':
		case '?':
		default:
			print_usage(argv[0]);
			exit(EXIT_SUCCESS);
			break;
		}
	}

	if ((mode_dump + mode_layout + mode_extract + mode_inject +
		mode_newlayout + (mode_spifreq | mode_em100 | mode_unlocked |
		 mode_locked)) > 1) {
		fprintf(stderr, "You may not specify more than one mode.\n\n");
		print_usage(argv[0]);
		exit(EXIT_FAILURE);
	}

	if ((mode_dump + mode_layout + mode_extract + mode_inject +
	     mode_newlayout + mode_spifreq + mode_em100 + mode_locked +
	     mode_unlocked) == 0) {
		fprintf(stderr, "You need to specify a mode.\n\n");
		print_usage(argv[0]);
		exit(EXIT_FAILURE);
	}

	if (optind + 1 != argc) {
		fprintf(stderr, "You need to specify a file.\n\n");
		print_usage(argv[0]);
		exit(EXIT_FAILURE);
	}

	char *filename = argv[optind];
	int bios_fd = open(filename, O_RDONLY | O_BINARY);
	if (bios_fd == -1) {
		perror("Could not open file");
		exit(EXIT_FAILURE);
	}
	struct stat buf;
	if (fstat(bios_fd, &buf) == -1) {
		perror("Could not stat file");
		exit(EXIT_FAILURE);
	}
	int size = buf.st_size;

	printf("File %s is %d bytes\n", filename, size);

	char *image = malloc(size);
	if (!image) {
		printf("Out of memory.\n");
		exit(EXIT_FAILURE);
	}

	if (read(bios_fd, image, size) != size) {
		perror("Could not read file");
		exit(EXIT_FAILURE);
	}

	close(bios_fd);

	check_ifd_version(image, size);

	if (mode_dump)
		dump_fd(image, size);

	if (mode_layout)
		dump_layout(image, size, layout_fname);

	if (mode_extract)
		write_regions(image, size);

	if (mode_inject)
		inject_region(filename, image, size, region_type,
				region_fname);

	if (mode_newlayout)
		new_layout(filename, image, size, layout_fname);

	if (mode_spifreq)
		set_spi_frequency(filename, image, size, spifreq);

	if (mode_em100)
		set_em100_mode(filename, image, size);

	if(mode_locked)
		lock_descriptor(filename, image, size);

	if (mode_unlocked)
		unlock_descriptor(filename, image, size);

	free(image);

	return 0;
}