/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package firmware.ifd;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.compress.utils.BitInputStream;

import ghidra.app.util.bin.BinaryReader;
import ghidra.util.Msg;

/**
 * Parser for Intel Flash/Firmware Descriptors.
 *
 * The IFD header (placed at the start of the IFD) contains the following fields:
 *
 * <pre>
 *   Intel Flash Descriptor Header
 *   +--------+------+---------------------------------------+
 *   | Type   | Size | Description                           |
 *   +--------+------+---------------------------------------+
 *   | u8[16] |   16 | Reset Vector (0xFF bytes)             |
 *   | u32    |    4 | Signature (0x0FF0A55A, little endian) |
 *   +--------+------+---------------------------------------+
 * </pre>
 *
 * This is immediately followed by the flash descriptor map. This parser only handles FLMAP0, which
 * has the following bit fields:
 *
 * <pre>
 *   FLMAP0
 *   +------+--------------------------------+
 *   | Bits | Description                    |
 *   +------+--------------------------------+
 *   |    8 | Component Section Base Address |
 *   |    2 | Number of Flash Chips          |
 *   |    6 | Reserved                       |
 *   |    8 | Region Section Base Address    |
 *   |    3 | Number of Regions              |
 *   |    5 | Reserved                       |
 *   +------+--------------------------------+
 * </pre>
 *
 * The component/region section base addresses are truncated; multiply them by 0x10 to get the
 * actual section base address.
 *
 * The component section begins with the following bit fields (describing the flash parameters):
 *
 * <pre>
 *   Intel Flash Parameters
 *   +------+---------------------------------+
 *   | Bits | Description                     |
 *   +------+---------------------------------+
 *   |    4 | First Flash Chip Density        |
 *   |    4 | Second Flash Chip Density       |
 *   |    9 | Reserved                        |
 *   |    3 | Read Frequency                  |
 *   |    1 | Fast Read Enabled               |
 *   |    3 | Fast Read Frequency             |
 *   |    3 | Write Frequency                 |
 *   |    3 | Flash Read Status Frequency     |
 *   |    1 | Dual Output Fast Read Supported |
 *   +------+---------------------------------+
 * </pre>
 *
 * See IntelFlashDescriptorConstants.FlashFrequency for some possible Read Frequency values. This
 * field can be used to determine the IFD version - IFD v1 always has a read frequency of 20 MHz.
 *
 * The region section contains the base address and length for each flash region:
 *
 * <pre>
 *   Region Component Section
 *   +------+------+------------------------------------------------------------+
 *   | Type | Size | Description                                                |
 *   +------+------+------------------------------------------------------------+
 *   | u16  |    2 | Flash Descriptor Region Base Address                       |
 *   | u16  |    2 | Flash Descriptor Region Limit Address                      |
 *   | u16  |    2 | BIOS Region Base Address                                   |
 *   | u16  |    2 | BIOS Region Limit Address                                  |
 *   | u16  |    2 | Intel Management Engine Region Base Address                |
 *   | u16  |    2 | Intel Management Engine Region Limit Address               |
 *   | u16  |    2 | Gigabit Ethernet Region Base Address                       |
 *   | u16  |    2 | Gigabit Ethernet Region Limit Address                      |
 *   | u16  |    2 | Platform Data Region Base Address                          |
 *   | u16  |    2 | Platform Data Region Limit Address                         |
 *   | u16  |    2 | Device Expansion Region 1 Base Address (IFD v2 only)       |
 *   | u16  |    2 | Device Expansion Region 1 Limit Address (IFD v2 only)      |
 *   | u16  |    2 | Secondary BIOS Region Base Address (IFD v2 only)           |
 *   | u16  |    2 | Secondary BIOS Region Limit Address (IFD v2 only)          |
 *   | u16  |    2 | CPU Microcode Base Address (IFD v2 only)                   |
 *   | u16  |    2 | CPU Microcode Region Limit Address (IFD v2 only)           |
 *   | u16  |    2 | Embedded Controller Region Base Address (IFD v2 only)      |
 *   | u16  |    2 | Embedded Controller Region Limit Address (IFD v2 only)     |
 *   | u16  |    2 | Device Expansion Region 2 Base Address (IFD v2 only)       |
 *   | u16  |    2 | Device Expansion Region 2 Limit Address (IFD v2 only)      |
 *   | u16  |    2 | Intel Innovation Engine Region Base Address (IFD v2 only)  |
 *   | u16  |    2 | Intel Innovation Engine Region Limit Address (IFD v2 only) |
 *   | u16  |    2 | 10 Gigabit Ethernet Region 1 Base Address (IFD v2 only)    |
 *   | u16  |    2 | 10 Gigabit Ethernet Region 1 Limit Address (IFD v2 only)   |
 *   | u16  |    2 | 10 Gigabit Ethernet Region 2 Base Address (IFD v2 only)    |
 *   | u16  |    2 | 10 Gigabit Ethernet Region 2 Limit Address (IFD v2 only)   |
 *   | u16  |    2 | Region 13 Base Address (reserved, IFD v2 only)             |
 *   | u16  |    2 | Region 13 Limit Address (reserved, IFD v2 only)            |
 *   | u16  |    2 | Region 14 Base Address (reserved, IFD v2 only)             |
 *   | u16  |    2 | Region 14 Limit Address (reserved, IFD v2 only)            |
 *   | u16  |    2 | Platform Trust Technology Base Address (IFD v2 only)       |
 *   | u16  |    2 | Platform Trust Technology Limit Address (IFD v2 only)      |
 *   +------+------+------------------------------------------------------------+
 * </pre>
 *
 * Each region's base address is truncated; multiply the value by 0x1000 to get the actual base
 * address. The same applies for the limit address.
 *
 * For additional information, see UEFITool's descriptor.h:
 * https://github.com/LongSoft/UEFITool/blob/ec3809159997bb7cc39a746a9d17f8385866d2a2/common/descriptor.h
 */
public class IntelFlashDescriptor {
	// Original header fields
	private final int signature;

	// FLMAP0 fields
	private final short componentBase;
	private final byte numFlashChips;
	private final short regionBase;
	private final byte numRegions;

	private final long headerOffset;
	private final int ifdVersion;
	private final List<IntelFlashRegion> regions;

	/**
	 * Constructs an IntelFlashDescriptor from a specified BinaryReader.
	 *
	 * @param reader the specified BinaryReader
	 */
	public IntelFlashDescriptor(BinaryReader reader, boolean hasZeroVector) throws IOException {
		// Skip the IFD vector (16 0xFF bytes) if it's present.
		// Some BIOSes may not include this (observed on HP Compaq dc7900).
		headerOffset = reader.getPointerIndex();
		if (hasZeroVector) {
			reader.setPointerIndex(reader.getPointerIndex() + 16);
		}

		signature = reader.readNextInt();
		if (signature != IntelFlashDescriptorConstants.IFD_SIGNATURE) {
			throw new IOException("Not a valid Intel flash descriptor");
		}

		// Read the fields in FLMAP0.
		InputStream inputStream = reader.getByteProvider().getInputStream(reader.getPointerIndex());
		BitInputStream bitInputStream = new BitInputStream(inputStream, ByteOrder.LITTLE_ENDIAN);
		componentBase = (short) bitInputStream.readBits(8);
		numFlashChips = (byte) bitInputStream.readBits(2);
		bitInputStream.readBits(6);
		regionBase = (short) bitInputStream.readBits(8);
		numRegions = (byte) bitInputStream.readBits(3);
		bitInputStream.readBits(5);

		Msg.debug(this, String.format("Component section base address = 0x%X", componentBase));
		Msg.debug(this, "Number of flash chips = " + numFlashChips);
		Msg.debug(this, String.format("Region section base address = 0x%X", regionBase));
		Msg.debug(this, "Number of regions = " + numRegions);

		// Read the read clock frequency in the component section to determine the IFD version.
		inputStream = reader.getByteProvider().getInputStream(headerOffset + componentBase * 0x10);
		bitInputStream = new BitInputStream(inputStream, ByteOrder.LITTLE_ENDIAN);
		bitInputStream.readBits(4 + 4 + 9);
		byte readClockFrequency = (byte) bitInputStream.readBits(3);

		// IFD v1 has a hardcoded flash read frequency of 20 MHz.
		if (readClockFrequency == IntelFlashDescriptorConstants.FlashFrequency.FREQ_20_MHZ) {
			ifdVersion = 1;
		} else {
			ifdVersion = 2;
		}

		Msg.debug(this, "IFD version = " + ifdVersion);

		// Read the region section.
		reader.setPointerIndex(headerOffset + regionBase * 0x10);
		int descriptorBase = reader.readNextUnsignedShort();
		int descriptorLimit = reader.readNextUnsignedShort();
		int biosBase = reader.readNextUnsignedShort();
		int biosLimit = reader.readNextUnsignedShort();
		int meBase = reader.readNextUnsignedShort();
		int meLimit = reader.readNextUnsignedShort();
		int gbeBase = reader.readNextUnsignedShort();
		int gbeLimit = reader.readNextUnsignedShort();
		int pdrBase = reader.readNextUnsignedShort();
		int pdrLimit = reader.readNextUnsignedShort();
		int devExp1Base = reader.readNextUnsignedShort();
		int devExp1Limit = reader.readNextUnsignedShort();
		int secondaryBIOSBase = reader.readNextUnsignedShort();
		int secondaryBIOSLimit = reader.readNextUnsignedShort();
		int microcodeBase = reader.readNextUnsignedShort();
		int microcodeLimit = reader.readNextUnsignedShort();
		int ecBase = reader.readNextUnsignedShort();
		int ecLimit = reader.readNextUnsignedShort();
		int devExp2Base = reader.readNextUnsignedShort();
		int devExp2Limit = reader.readNextUnsignedShort();
		int ieBase = reader.readNextUnsignedShort();
		int ieLimit = reader.readNextUnsignedShort();
		int tenGbe1Base = reader.readNextUnsignedShort();
		int tenGbe1Limit = reader.readNextUnsignedShort();
		int tenGbe2Base = reader.readNextUnsignedShort();
		int tenGbe2Limit = reader.readNextUnsignedShort();
		int region13Base = reader.readNextUnsignedShort();
		int region13Limit = reader.readNextUnsignedShort();
		int region14Base = reader.readNextUnsignedShort();
		int region14Limit = reader.readNextUnsignedShort();
		int pttBase = reader.readNextUnsignedShort();
		int pttLimit = reader.readNextUnsignedShort();

		// Add each flash region.
		regions = new ArrayList<>();
		reader.setPointerIndex(headerOffset);
		regions.add(new IntelFlashRegion(reader, IntelFlashDescriptorConstants.DESCRIPTOR_SIZE,
				IntelFlashDescriptorConstants.FlashRegionType.FLASH_DESCRIPTOR));
		if ((biosLimit + 1 - biosBase) * 0x1000L == reader.length() - headerOffset) {
			// For some older Gigabyte systems, the BIOS region in the flash descriptor spans the
			// whole chip. Manually calculate the BIOS region base address using the ME region's
			// limit address (which should be correct).
			if (meLimit != 0) {
				biosBase = meLimit + 1;
			}
		}

		addRegion(reader, biosBase, biosLimit, IntelFlashDescriptorConstants.FlashRegionType.BIOS);
		addRegion(reader, meBase, meLimit,
				IntelFlashDescriptorConstants.FlashRegionType.MANAGEMENT_ENGINE);
		addRegion(reader, gbeBase, gbeLimit,
				IntelFlashDescriptorConstants.FlashRegionType.GIGABIT_ETHERNET);
		addRegion(reader, pdrBase, pdrLimit,
				IntelFlashDescriptorConstants.FlashRegionType.PLATFORM_DATA);

		if (ifdVersion == 2) {
			addRegion(reader, devExp1Base, devExp1Limit,
					IntelFlashDescriptorConstants.FlashRegionType.DEVICE_EXPANSION_1);
			addRegion(reader, secondaryBIOSBase, secondaryBIOSLimit,
					IntelFlashDescriptorConstants.FlashRegionType.SECONDARY_BIOS);
			addRegion(reader, microcodeBase, microcodeLimit,
					IntelFlashDescriptorConstants.FlashRegionType.MICROCODE);
			addRegion(reader, ecBase, ecLimit,
					IntelFlashDescriptorConstants.FlashRegionType.EMBEDDED_CONTROLLER);
			addRegion(reader, devExp2Base, devExp2Limit,
					IntelFlashDescriptorConstants.FlashRegionType.DEVICE_EXPANSION_2);
			addRegion(reader, ieBase, ieLimit,
					IntelFlashDescriptorConstants.FlashRegionType.INNOVATION_ENGINE);
			addRegion(reader, tenGbe1Base, tenGbe1Limit,
					IntelFlashDescriptorConstants.FlashRegionType.TEN_GIGABIT_ETHERNET_1);
			addRegion(reader, tenGbe2Base, tenGbe2Limit,
					IntelFlashDescriptorConstants.FlashRegionType.TEN_GIGABIT_ETHERNET_2);
			addRegion(reader, pttBase, pttLimit,
					IntelFlashDescriptorConstants.FlashRegionType.PLATFORM_TRUST_TECHNOLOGY);
		}
	}

	/**
	 * Returns the flash regions defined in the current flash descriptor.
	 *
	 * @return the flash regions defined in the current flash descriptor
	 */
	public List<IntelFlashRegion> getRegions() {
		return regions;
	}

	/**
	 * Adds a flash region to the list of flash regions using a specified BinaryReader.
	 *
	 * @param reader the specified BinaryReader
	 * @param base   the region's base address (truncated)
	 * @param limit  the region's limit address (truncated)
	 * @param type   the region's type
	 */
	private void addRegion(BinaryReader reader, int base, int limit, int type) throws IOException {
		int size = limit > 0 ? (limit + 1 - base) * 0x1000 : 0;
		if (size == 0) {
			return;
		}

		if (base * 0x1000L + size > reader.length()) {
			throw new IOException("Out-of-bounds flash region");
		}

		reader.setPointerIndex(headerOffset + base * 0x1000L);
		regions.add(new IntelFlashRegion(reader, size, type));
		Msg.debug(this, String.format("Adding %s region (base = 0x%X, size = 0x%X",
				IntelFlashDescriptorConstants.FlashRegionType.toString(type), base * 0x1000,
				size));
	}
}
