mirror of
				https://git.proxmox.com/git/fwupd
				synced 2025-11-04 12:15:41 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			870 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			870 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
 | 
						|
 *
 | 
						|
 * SPDX-License-Identifier: LGPL-2.1+
 | 
						|
 */
 | 
						|
 | 
						|
#include "config.h"
 | 
						|
 | 
						|
#include <fwupd.h>
 | 
						|
#include <appstream-glib.h>
 | 
						|
#include <glib-object.h>
 | 
						|
#include <gio/gio.h>
 | 
						|
#include <glib/gstdio.h>
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
#include "fu-rom.h"
 | 
						|
 | 
						|
static void fu_rom_finalize			 (GObject *object);
 | 
						|
 | 
						|
/* data from http://resources.infosecinstitute.com/pci-expansion-rom/ */
 | 
						|
typedef struct {
 | 
						|
	guint8		*rom_data;
 | 
						|
	guint32		 rom_len;
 | 
						|
	guint32		 rom_offset;
 | 
						|
	guint32		 entry_point;
 | 
						|
	guint8		 reserved[18];
 | 
						|
	guint16		 cpi_ptr;
 | 
						|
	guint16		 vendor_id;
 | 
						|
	guint16		 device_id;
 | 
						|
	guint16		 device_list_ptr;
 | 
						|
	guint16		 data_len;
 | 
						|
	guint8		 data_rev;
 | 
						|
	guint32		 class_code;
 | 
						|
	guint32		 image_len;
 | 
						|
	guint16		 revision_level;
 | 
						|
	guint8		 code_type;
 | 
						|
	guint8		 last_image;
 | 
						|
	guint32		 max_runtime_len;
 | 
						|
	guint16		 config_header_ptr;
 | 
						|
	guint16		 dmtf_clp_ptr;
 | 
						|
} FuRomPciHeader;
 | 
						|
 | 
						|
struct _FuRom {
 | 
						|
	GObject				 parent_instance;
 | 
						|
	GPtrArray			*checksums;
 | 
						|
	GInputStream			*stream;
 | 
						|
	FuRomKind			 kind;
 | 
						|
	gchar				*version;
 | 
						|
	gchar				*guid;
 | 
						|
	guint16				 vendor_id;
 | 
						|
	guint16				 device_id;
 | 
						|
	GPtrArray			*hdrs; /* of FuRomPciHeader */
 | 
						|
};
 | 
						|
 | 
						|
G_DEFINE_TYPE (FuRom, fu_rom, G_TYPE_OBJECT)
 | 
						|
 | 
						|
static void
 | 
						|
fu_rom_pci_header_free (FuRomPciHeader *hdr)
 | 
						|
{
 | 
						|
	g_free (hdr->rom_data);
 | 
						|
	g_free (hdr);
 | 
						|
}
 | 
						|
 | 
						|
const gchar *
 | 
						|
fu_rom_kind_to_string (FuRomKind kind)
 | 
						|
{
 | 
						|
	if (kind == FU_ROM_KIND_UNKNOWN)
 | 
						|
		return "unknown";
 | 
						|
	if (kind == FU_ROM_KIND_ATI)
 | 
						|
		return "ati";
 | 
						|
	if (kind == FU_ROM_KIND_NVIDIA)
 | 
						|
		return "nvidia";
 | 
						|
	if (kind == FU_ROM_KIND_INTEL)
 | 
						|
		return "intel";
 | 
						|
	if (kind == FU_ROM_KIND_PCI)
 | 
						|
		return "pci";
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static guint8 *
 | 
						|
fu_rom_pci_strstr (FuRomPciHeader *hdr, const gchar *needle)
 | 
						|
{
 | 
						|
	gsize needle_len;
 | 
						|
	guint8 *haystack;
 | 
						|
	gsize haystack_len;
 | 
						|
 | 
						|
	if (needle == NULL || needle[0] == '\0')
 | 
						|
		return NULL;
 | 
						|
	if (hdr->rom_data == NULL)
 | 
						|
		return NULL;
 | 
						|
	if (hdr->data_len > hdr->rom_len)
 | 
						|
		return NULL;
 | 
						|
	haystack = &hdr->rom_data[hdr->data_len];
 | 
						|
	haystack_len = hdr->rom_len - hdr->data_len;
 | 
						|
	needle_len = strlen (needle);
 | 
						|
	if (needle_len > haystack_len)
 | 
						|
		return NULL;
 | 
						|
	for (guint i = 0; i < haystack_len - needle_len; i++) {
 | 
						|
		if (memcmp (haystack + i, needle, needle_len) == 0)
 | 
						|
			return &haystack[i];
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static guint
 | 
						|
fu_rom_blank_serial_numbers (guint8 *buffer, guint buffer_sz)
 | 
						|
{
 | 
						|
	guint i;
 | 
						|
	for (i = 0; i < buffer_sz; i++) {
 | 
						|
		if (buffer[i] == 0xff ||
 | 
						|
		    buffer[i] == '\0' ||
 | 
						|
		    buffer[i] == '\n' ||
 | 
						|
		    buffer[i] == '\r')
 | 
						|
			break;
 | 
						|
		buffer[i] = '\0';
 | 
						|
	}
 | 
						|
	return i;
 | 
						|
}
 | 
						|
 | 
						|
static gchar *
 | 
						|
fu_rom_get_hex_dump (guint8 *buffer, guint32 sz)
 | 
						|
{
 | 
						|
	GString *str = g_string_new ("");
 | 
						|
	for (guint32 i = 0; i < sz; i++)
 | 
						|
		g_string_append_printf (str, "%02x ", buffer[i]);
 | 
						|
	g_string_append (str, "   ");
 | 
						|
	for (guint32 i = 0; i < sz; i++) {
 | 
						|
		gchar tmp = '?';
 | 
						|
		if (g_ascii_isprint (buffer[i]))
 | 
						|
			tmp = (gchar) buffer[i];
 | 
						|
		g_string_append_printf (str, "%c", tmp);
 | 
						|
	}
 | 
						|
	return g_string_free (str, FALSE);
 | 
						|
}
 | 
						|
 | 
						|
typedef struct {
 | 
						|
	guint8		 segment_kind;
 | 
						|
	guint8		*data;
 | 
						|
	guint16		 data_len;
 | 
						|
	guint16		 next_offset;
 | 
						|
} FooRomPciCertificateHdr;
 | 
						|
 | 
						|
static void
 | 
						|
fu_rom_pci_print_certificate_data (guint8 *buffer, gssize sz)
 | 
						|
{
 | 
						|
	guint16 off = 0;
 | 
						|
	g_autofree gchar *hdr_str = NULL;
 | 
						|
 | 
						|
	/* 27 byte header, unknown purpose */
 | 
						|
	hdr_str = fu_rom_get_hex_dump (buffer+off, 27);
 | 
						|
	g_debug ("    ISBN header: %s", hdr_str);
 | 
						|
	buffer += 27;
 | 
						|
 | 
						|
	while (TRUE) {
 | 
						|
		/* 29 byte header to the segment, then data:
 | 
						|
		 * 0x01      = type. 0x1 = certificate, 0x2 = hashes?
 | 
						|
		 * 0x13,0x14 = offset to next segment */
 | 
						|
		FooRomPciCertificateHdr h;
 | 
						|
		g_autofree gchar *segment_str = NULL;
 | 
						|
		segment_str = fu_rom_get_hex_dump (buffer+off, 29);
 | 
						|
		g_debug ("     ISBN segment @%02x: %s", off, segment_str);
 | 
						|
		h.segment_kind = buffer[off+1];
 | 
						|
		h.next_offset = (guint16) (((guint16) buffer[off+14] << 8) + buffer[off+13]);
 | 
						|
		h.data = &buffer[off+29];
 | 
						|
 | 
						|
		/* calculate last block length automatically */
 | 
						|
		if (h.next_offset == 0)
 | 
						|
			h.data_len = (guint16) (sz - off - 29 - 27);
 | 
						|
		else
 | 
						|
			h.data_len = (guint16) (h.next_offset - off - 29);
 | 
						|
 | 
						|
		/* print the certificate */
 | 
						|
		if (h.segment_kind == 0x01) {
 | 
						|
			g_autofree gchar *tmp = NULL;
 | 
						|
			tmp = fu_rom_get_hex_dump (h.data, h.data_len);
 | 
						|
			g_debug ("%s(%i)", tmp, h.data_len);
 | 
						|
		} else if (h.segment_kind == 0x02) {
 | 
						|
			g_autofree gchar *tmp = NULL;
 | 
						|
			tmp = fu_rom_get_hex_dump (h.data,
 | 
						|
						   h.data_len < 32 ? h.data_len : 32);
 | 
						|
			g_debug ("%s(%i)", tmp, h.data_len);
 | 
						|
		} else {
 | 
						|
			g_warning ("unknown segment kind %i", h.segment_kind);
 | 
						|
		}
 | 
						|
 | 
						|
		/* last block */
 | 
						|
		if (h.next_offset == 0x0000)
 | 
						|
			break;
 | 
						|
		off = h.next_offset;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static const gchar *
 | 
						|
fu_rom_pci_code_type_to_string (guint8 code_type)
 | 
						|
{
 | 
						|
	if (code_type == 0)
 | 
						|
		return "Intel86";
 | 
						|
	if (code_type == 1)
 | 
						|
		return "OpenFirmware";
 | 
						|
	if (code_type == 2)
 | 
						|
		return "PA-RISC";
 | 
						|
	if (code_type == 3)
 | 
						|
		return "EFI";
 | 
						|
	return "reserved";
 | 
						|
}
 | 
						|
 | 
						|
static guint8
 | 
						|
fu_rom_pci_header_get_checksum (FuRomPciHeader *hdr)
 | 
						|
{
 | 
						|
	guint8 chksum_check = 0x00;
 | 
						|
	for (guint i = 0; i < hdr->rom_len; i++)
 | 
						|
		chksum_check += hdr->rom_data[i];
 | 
						|
	return chksum_check;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fu_rom_pci_print_header (FuRomPciHeader *hdr)
 | 
						|
{
 | 
						|
	guint8 chksum_check;
 | 
						|
	guint8 *buffer;
 | 
						|
	g_autofree gchar *data_str = NULL;
 | 
						|
	g_autofree gchar *reserved_str = NULL;
 | 
						|
 | 
						|
	g_debug ("PCI Header");
 | 
						|
	g_debug (" RomOffset: 0x%04x", hdr->rom_offset);
 | 
						|
	g_debug (" RomSize:   0x%04x", hdr->rom_len);
 | 
						|
	g_debug (" EntryPnt:  0x%06x", hdr->entry_point);
 | 
						|
	reserved_str = fu_rom_get_hex_dump (hdr->reserved, 18);
 | 
						|
	g_debug (" Reserved:  %s", reserved_str);
 | 
						|
	g_debug (" CpiPtr:    0x%04x", hdr->cpi_ptr);
 | 
						|
 | 
						|
	/* sanity check */
 | 
						|
	if (hdr->cpi_ptr > hdr->rom_len) {
 | 
						|
		g_debug ("  PCI DATA: Invalid as cpi_ptr > rom_len");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (hdr->data_len > hdr->rom_len) {
 | 
						|
		g_debug ("  PCI DATA: Invalid as data_len > rom_len");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	/* print the data */
 | 
						|
	buffer = &hdr->rom_data[hdr->cpi_ptr];
 | 
						|
	g_debug ("  PCI Data");
 | 
						|
	g_debug ("   VendorID:  0x%04x", hdr->vendor_id);
 | 
						|
	g_debug ("   DeviceID:  0x%04x", hdr->device_id);
 | 
						|
	g_debug ("   DevList:   0x%04x", hdr->device_list_ptr);
 | 
						|
	g_debug ("   DataLen:   0x%04x", hdr->data_len);
 | 
						|
	g_debug ("   DataRev:   0x%04x", hdr->data_rev);
 | 
						|
	if (hdr->image_len < 0x0f) {
 | 
						|
		data_str = fu_rom_get_hex_dump (&buffer[hdr->data_len], hdr->image_len);
 | 
						|
		g_debug ("   ImageLen:  0x%04x [%s]", hdr->image_len, data_str);
 | 
						|
	} else if (hdr->image_len >= 0x0f) {
 | 
						|
		data_str = fu_rom_get_hex_dump (&buffer[hdr->data_len], 0x0f);
 | 
						|
		g_debug ("   ImageLen:  0x%04x [%s...]", hdr->image_len, data_str);
 | 
						|
	} else {
 | 
						|
		g_debug ("   ImageLen:  0x%04x", hdr->image_len);
 | 
						|
	}
 | 
						|
	g_debug ("   RevLevel:  0x%04x", hdr->revision_level);
 | 
						|
	g_debug ("   CodeType:  0x%02x [%s]", hdr->code_type,
 | 
						|
		 fu_rom_pci_code_type_to_string (hdr->code_type));
 | 
						|
	g_debug ("   LastImg:   0x%02x [%s]", hdr->last_image,
 | 
						|
		 hdr->last_image == 0x80 ? "yes" : "no");
 | 
						|
	g_debug ("   MaxRunLen: 0x%04x", hdr->max_runtime_len);
 | 
						|
	g_debug ("   ConfigHdr: 0x%04x", hdr->config_header_ptr);
 | 
						|
	g_debug ("   ClpPtr:    0x%04x", hdr->dmtf_clp_ptr);
 | 
						|
 | 
						|
	/* dump the ISBN */
 | 
						|
	if (hdr->code_type == 0x70 &&
 | 
						|
	    memcmp (&buffer[hdr->data_len], "ISBN", 4) == 0) {
 | 
						|
		fu_rom_pci_print_certificate_data (&buffer[hdr->data_len],
 | 
						|
						   hdr->image_len);
 | 
						|
	}
 | 
						|
 | 
						|
	/* verify the checksum byte */
 | 
						|
	if (hdr->image_len <= hdr->rom_len && hdr->image_len > 0) {
 | 
						|
		buffer = hdr->rom_data;
 | 
						|
		chksum_check = fu_rom_pci_header_get_checksum (hdr);
 | 
						|
		if (chksum_check == 0x00) {
 | 
						|
			g_debug ("   ChkSum:    0x%02x [valid]",
 | 
						|
				 buffer[hdr->image_len-1]);
 | 
						|
		} else {
 | 
						|
			g_debug ("   ChkSum:    0x%02x [failed, got 0x%02x]",
 | 
						|
				 buffer[hdr->image_len-1],
 | 
						|
				 chksum_check);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		g_debug ("   ChkSum:    0x?? [unknown]");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
fu_rom_extract_all (FuRom *self, const gchar *path, GError **error)
 | 
						|
{
 | 
						|
	FuRomPciHeader *hdr;
 | 
						|
 | 
						|
	for (guint i = 0; i < self->hdrs->len; i++) {
 | 
						|
		g_autofree gchar *fn = NULL;
 | 
						|
		hdr = g_ptr_array_index (self->hdrs, i);
 | 
						|
		fn = g_strdup_printf ("%s/%02u.bin", path, i);
 | 
						|
		g_debug ("dumping ROM #%u at 0x%04x [0x%02x] to %s",
 | 
						|
			 i, hdr->rom_offset, hdr->rom_len, fn);
 | 
						|
		if (hdr->rom_len == 0)
 | 
						|
			continue;
 | 
						|
		if (!g_file_set_contents (fn,
 | 
						|
					  (const gchar *) hdr->rom_data,
 | 
						|
					  (gssize) hdr->rom_len, error))
 | 
						|
			return FALSE;
 | 
						|
	}
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fu_rom_find_and_blank_serial_numbers (FuRom *self)
 | 
						|
{
 | 
						|
	FuRomPciHeader *hdr;
 | 
						|
	guint8 *tmp;
 | 
						|
 | 
						|
	/* bail if not likely */
 | 
						|
	if (self->kind == FU_ROM_KIND_PCI ||
 | 
						|
	    self->kind == FU_ROM_KIND_INTEL) {
 | 
						|
		g_debug ("no serial numbers likely");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	for (guint i = 0; i < self->hdrs->len; i++) {
 | 
						|
		hdr = g_ptr_array_index (self->hdrs, i);
 | 
						|
		g_debug ("looking for PPID at 0x%04x", hdr->rom_offset);
 | 
						|
		tmp = fu_rom_pci_strstr (hdr, "PPID");
 | 
						|
		if (tmp != NULL) {
 | 
						|
			guint len;
 | 
						|
			guint8 chk;
 | 
						|
			len = fu_rom_blank_serial_numbers (tmp, hdr->rom_len - hdr->data_len);
 | 
						|
			g_debug ("cleared %u chars @ 0x%04lx",
 | 
						|
				 len, (gulong) (tmp - &hdr->rom_data[hdr->data_len]));
 | 
						|
 | 
						|
			/* we have to fix the checksum */
 | 
						|
			chk = fu_rom_pci_header_get_checksum (hdr);
 | 
						|
			hdr->rom_data[hdr->rom_len - 1] -= chk;
 | 
						|
			fu_rom_pci_print_header (hdr);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
fu_rom_pci_parse_data (FuRomPciHeader *hdr)
 | 
						|
{
 | 
						|
	guint8 *buffer;
 | 
						|
 | 
						|
	/* check valid */
 | 
						|
	if (hdr->cpi_ptr == 0x0000) {
 | 
						|
		g_debug ("No PCI DATA @ 0x%04x", hdr->rom_offset);
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
	if (hdr->rom_len > 0 && hdr->cpi_ptr > hdr->rom_len) {
 | 
						|
		g_debug ("Invalid PCI DATA @ 0x%04x", hdr->rom_offset);
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* gahh, CPI is out of the first chunk */
 | 
						|
	if (hdr->cpi_ptr > hdr->rom_len) {
 | 
						|
		g_debug ("No available PCI DATA @ 0x%04x : 0x%04x > 0x%04x",
 | 
						|
			 hdr->rom_offset, hdr->cpi_ptr, hdr->rom_len);
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* check signature */
 | 
						|
	buffer = &hdr->rom_data[hdr->cpi_ptr];
 | 
						|
	if (memcmp (buffer, "PCIR", 4) != 0) {
 | 
						|
		if (memcmp (buffer, "RGIS", 4) == 0 ||
 | 
						|
		    memcmp (buffer, "NPDS", 4) == 0 ||
 | 
						|
		    memcmp (buffer, "NPDE", 4) == 0) {
 | 
						|
			g_debug ("-- using NVIDIA DATA quirk");
 | 
						|
		} else {
 | 
						|
			g_debug ("Not PCI DATA: %02x%02x%02x%02x [%c%c%c%c]",
 | 
						|
				 buffer[0], buffer[1],
 | 
						|
				 buffer[2], buffer[3],
 | 
						|
				 buffer[0], buffer[1],
 | 
						|
				 buffer[2], buffer[3]);
 | 
						|
			return FALSE;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* parse */
 | 
						|
	hdr->vendor_id = ((guint16) buffer[0x05] << 8) + buffer[0x04];
 | 
						|
	hdr->device_id = ((guint16) buffer[0x07] << 8) + buffer[0x06];
 | 
						|
	hdr->device_list_ptr = ((guint16) buffer[0x09] << 8) + buffer[0x08];
 | 
						|
	hdr->data_len = ((guint16) buffer[0x0b] << 8) + buffer[0x0a];
 | 
						|
	hdr->data_rev = buffer[0x0c];
 | 
						|
	hdr->class_code = ((guint16) buffer[0x0f] << 16) +
 | 
						|
			  ((guint16) buffer[0x0e] << 8) +
 | 
						|
			  buffer[0x0d];
 | 
						|
	hdr->image_len = (((guint16) buffer[0x11] << 8) + buffer[0x10]) * 512;
 | 
						|
	hdr->revision_level = ((guint16) buffer[0x13] << 8) + buffer[0x12];
 | 
						|
	hdr->code_type = buffer[0x14];
 | 
						|
	hdr->last_image = buffer[0x15];
 | 
						|
	hdr->max_runtime_len = (((guint16) buffer[0x17] << 8) +
 | 
						|
				buffer[0x16]) * 512;
 | 
						|
	hdr->config_header_ptr = ((guint16) buffer[0x19] << 8) + buffer[0x18];
 | 
						|
	hdr->dmtf_clp_ptr = ((guint16) buffer[0x1b] << 8) + buffer[0x1a];
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static FuRomPciHeader *
 | 
						|
fu_rom_pci_get_header (guint8 *buffer, guint32 sz)
 | 
						|
{
 | 
						|
	FuRomPciHeader *hdr;
 | 
						|
 | 
						|
	/* check signature */
 | 
						|
	if (memcmp (buffer, "\x55\xaa", 2) != 0) {
 | 
						|
		if (memcmp (buffer, "\x56\x4e", 2) == 0) {
 | 
						|
			g_debug ("-- using NVIDIA ROM quirk");
 | 
						|
		} else {
 | 
						|
			g_autofree gchar *sig_str = NULL;
 | 
						|
			sig_str = fu_rom_get_hex_dump (buffer, MIN (16, sz));
 | 
						|
			g_debug ("Not PCI ROM %s", sig_str);
 | 
						|
			return NULL;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* decode structure */
 | 
						|
	hdr = g_new0 (FuRomPciHeader, 1);
 | 
						|
	hdr->rom_len = buffer[0x02] * 512;
 | 
						|
 | 
						|
	/* fix up misreporting */
 | 
						|
	if (hdr->rom_len == 0) {
 | 
						|
		g_debug ("fixing up last image size");
 | 
						|
		hdr->rom_len = sz;
 | 
						|
	}
 | 
						|
 | 
						|
	/* copy this locally to the header */
 | 
						|
	hdr->rom_data = g_memdup (buffer, hdr->rom_len);
 | 
						|
 | 
						|
	/* parse out CPI */
 | 
						|
	hdr->entry_point = ((guint32) buffer[0x05] << 16) +
 | 
						|
			   ((guint16) buffer[0x04] << 8) +
 | 
						|
			   buffer[0x03];
 | 
						|
	memcpy (&hdr->reserved, &buffer[6], 18);
 | 
						|
	hdr->cpi_ptr = ((guint16) buffer[0x19] << 8) + buffer[0x18];
 | 
						|
 | 
						|
	/* parse the header data */
 | 
						|
	g_debug ("looking for PCI DATA @ 0x%04x", hdr->cpi_ptr);
 | 
						|
	fu_rom_pci_parse_data (hdr);
 | 
						|
	return hdr;
 | 
						|
}
 | 
						|
 | 
						|
static gchar *
 | 
						|
fu_rom_find_version_pci (FuRomPciHeader *hdr)
 | 
						|
{
 | 
						|
	gchar *str;
 | 
						|
 | 
						|
	/* ARC storage */
 | 
						|
	if (memcmp (hdr->reserved, "\0\0ARC", 5) == 0) {
 | 
						|
		str = (gchar *) fu_rom_pci_strstr (hdr, "BIOS: ");
 | 
						|
		if (str != NULL)
 | 
						|
			return g_strdup (str + 6);
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static gchar *
 | 
						|
fu_rom_find_version_nvidia (FuRomPciHeader *hdr)
 | 
						|
{
 | 
						|
	gchar *str;
 | 
						|
 | 
						|
	/* static location for some firmware */
 | 
						|
	if (memcmp (hdr->rom_data + 0x013d, "Version ", 8) == 0)
 | 
						|
		return g_strdup ((gchar *) &hdr->rom_data[0x013d + 8]);
 | 
						|
 | 
						|
	/* usual search string */
 | 
						|
	str = (gchar *) fu_rom_pci_strstr (hdr, "Version ");
 | 
						|
	if (str != NULL)
 | 
						|
		return g_strdup (str + 8);
 | 
						|
 | 
						|
	/* broken */
 | 
						|
	str = (gchar *) fu_rom_pci_strstr (hdr, "Vension:");
 | 
						|
	if (str != NULL)
 | 
						|
		return g_strdup (str + 8);
 | 
						|
	str = (gchar *) fu_rom_pci_strstr (hdr, "Version");
 | 
						|
	if (str != NULL)
 | 
						|
		return g_strdup (str + 7);
 | 
						|
 | 
						|
	/* fallback to VBIOS */
 | 
						|
	if (memcmp (hdr->rom_data + 0xfa, "VBIOS Ver", 9) == 0)
 | 
						|
		return g_strdup ((gchar *) &hdr->rom_data[0xfa + 9]);
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static gchar *
 | 
						|
fu_rom_find_version_intel (FuRomPciHeader *hdr)
 | 
						|
{
 | 
						|
	gchar *str;
 | 
						|
 | 
						|
	/* 2175_RYan PC 14.34  06/06/2013  21:27:53 */
 | 
						|
	str = (gchar *) fu_rom_pci_strstr (hdr, "Build Number:");
 | 
						|
	if (str != NULL) {
 | 
						|
		g_auto(GStrv) split = NULL;
 | 
						|
		split = g_strsplit (str + 14, " ", -1);
 | 
						|
		for (guint i = 0; split[i] != NULL; i++) {
 | 
						|
			if (g_strstr_len (split[i], -1, ".") == NULL)
 | 
						|
				continue;
 | 
						|
			return g_strdup (split[i]);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* fallback to VBIOS */
 | 
						|
	str = (gchar *) fu_rom_pci_strstr (hdr, "VBIOS ");
 | 
						|
	if (str != NULL)
 | 
						|
		return g_strdup (str + 6);
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static gchar *
 | 
						|
fu_rom_find_version_ati (FuRomPciHeader *hdr)
 | 
						|
{
 | 
						|
	gchar *str;
 | 
						|
 | 
						|
	str = (gchar *) fu_rom_pci_strstr (hdr, " VER0");
 | 
						|
	if (str != NULL)
 | 
						|
		return g_strdup (str + 4);
 | 
						|
 | 
						|
	/* broken */
 | 
						|
	str = (gchar *) fu_rom_pci_strstr (hdr, " VR");
 | 
						|
	if (str != NULL)
 | 
						|
		return g_strdup (str + 4);
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static gchar *
 | 
						|
fu_rom_find_version (FuRomKind kind, FuRomPciHeader *hdr)
 | 
						|
{
 | 
						|
	if (kind == FU_ROM_KIND_PCI)
 | 
						|
		return fu_rom_find_version_pci (hdr);
 | 
						|
	if (kind == FU_ROM_KIND_NVIDIA)
 | 
						|
		return fu_rom_find_version_nvidia (hdr);
 | 
						|
	if (kind == FU_ROM_KIND_INTEL)
 | 
						|
		return fu_rom_find_version_intel (hdr);
 | 
						|
	if (kind == FU_ROM_KIND_ATI)
 | 
						|
		return fu_rom_find_version_ati (hdr);
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
fu_rom_load_data (FuRom *self,
 | 
						|
		  guint8 *buffer, gsize buffer_sz,
 | 
						|
		  FuRomLoadFlags flags,
 | 
						|
		  GCancellable *cancellable,
 | 
						|
		  GError **error)
 | 
						|
{
 | 
						|
	FuRomPciHeader *hdr = NULL;
 | 
						|
	guint32 sz = buffer_sz;
 | 
						|
	guint32 jump = 0;
 | 
						|
	guint32 hdr_sz = 0;
 | 
						|
	g_autofree gchar *id = NULL;
 | 
						|
	g_autoptr(GChecksum) checksum_sha1 = g_checksum_new (G_CHECKSUM_SHA1);
 | 
						|
	g_autoptr(GChecksum) checksum_sha256 = g_checksum_new (G_CHECKSUM_SHA256);
 | 
						|
 | 
						|
	g_return_val_if_fail (FU_IS_ROM (self), FALSE);
 | 
						|
 | 
						|
	/* detect optional IFR header and skip to option ROM */
 | 
						|
	if (memcmp (buffer, "NVGI", 4) == 0) {
 | 
						|
		guint16 ifr_sz_raw;
 | 
						|
		memcpy (&ifr_sz_raw, &buffer[0x15], 2);
 | 
						|
		hdr_sz = GUINT16_FROM_BE (ifr_sz_raw);
 | 
						|
		g_debug ("detected IFR header, skipping %x bytes", hdr_sz);
 | 
						|
	}
 | 
						|
 | 
						|
	/* read all the ROM headers */
 | 
						|
	while (sz > hdr_sz + jump) {
 | 
						|
		guint32 jump_sz;
 | 
						|
		g_debug ("looking for PCI ROM @ 0x%04x", hdr_sz + jump);
 | 
						|
		hdr = fu_rom_pci_get_header (&buffer[hdr_sz + jump], sz - hdr_sz - jump);
 | 
						|
		if (hdr == NULL) {
 | 
						|
			gboolean found_data = FALSE;
 | 
						|
 | 
						|
			/* check it's not just NUL padding */
 | 
						|
			for (guint i = jump + hdr_sz; i < buffer_sz; i++) {
 | 
						|
				if (buffer[i] != 0x00) {
 | 
						|
					found_data = TRUE;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (found_data) {
 | 
						|
				g_debug ("found junk data, adding fake");
 | 
						|
				hdr = g_new0 (FuRomPciHeader, 1);
 | 
						|
				hdr->vendor_id = 0x0000;
 | 
						|
				hdr->device_id = 0x0000;
 | 
						|
				hdr->code_type = 0x00;
 | 
						|
				hdr->last_image = 0x80;
 | 
						|
				hdr->rom_offset = hdr_sz + jump;
 | 
						|
				hdr->rom_len = sz - hdr->rom_offset;
 | 
						|
				hdr->rom_data = g_memdup (&buffer[hdr->rom_offset], hdr->rom_len);
 | 
						|
				hdr->image_len = hdr->rom_len;
 | 
						|
				g_ptr_array_add (self->hdrs, hdr);
 | 
						|
			} else {
 | 
						|
				g_debug ("ignoring 0x%04x bytes of padding",
 | 
						|
					 (guint) (buffer_sz - (jump + hdr_sz)));
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		/* save this so we can fix checksums */
 | 
						|
		hdr->rom_offset = hdr_sz + jump;
 | 
						|
 | 
						|
		/* we can't break on hdr->last_image as
 | 
						|
		 * NVIDIA uses packed but not merged extended headers */
 | 
						|
		g_ptr_array_add (self->hdrs, hdr);
 | 
						|
 | 
						|
		/* NVIDIA don't always set a ROM size for extensions */
 | 
						|
		jump_sz = hdr->rom_len;
 | 
						|
		if (jump_sz == 0)
 | 
						|
			jump_sz = hdr->image_len;
 | 
						|
		if (jump_sz == 0x0)
 | 
						|
			break;
 | 
						|
		jump += jump_sz;
 | 
						|
	}
 | 
						|
 | 
						|
	/* we found nothing */
 | 
						|
	if (self->hdrs->len == 0) {
 | 
						|
		g_set_error (error,
 | 
						|
			     FWUPD_ERROR,
 | 
						|
			     FWUPD_ERROR_INVALID_FILE,
 | 
						|
			     "Failed to detect firmware header [%02x%02x]",
 | 
						|
			     buffer[0], buffer[1]);
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* print all headers */
 | 
						|
	for (guint i = 0; i < self->hdrs->len; i++) {
 | 
						|
		hdr = g_ptr_array_index (self->hdrs, i);
 | 
						|
		fu_rom_pci_print_header (hdr);
 | 
						|
	}
 | 
						|
 | 
						|
	/* find first ROM header */
 | 
						|
	hdr = g_ptr_array_index (self->hdrs, 0);
 | 
						|
	self->vendor_id = hdr->vendor_id;
 | 
						|
	self->device_id = hdr->device_id;
 | 
						|
	self->kind = FU_ROM_KIND_PCI;
 | 
						|
 | 
						|
	/* detect intel header */
 | 
						|
	if (memcmp (hdr->reserved, "00000000000", 11) == 0)
 | 
						|
		hdr_sz = (guint32) (((guint16) buffer[0x1b] << 8) + buffer[0x1a]);
 | 
						|
	if (hdr_sz > sz) {
 | 
						|
		g_set_error_literal (error,
 | 
						|
				     FWUPD_ERROR,
 | 
						|
				     FWUPD_ERROR_INVALID_FILE,
 | 
						|
				     "firmware corrupt (overflow)");
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	if (hdr->entry_point == 0x374beb) {
 | 
						|
		self->kind = FU_ROM_KIND_NVIDIA;
 | 
						|
	} else if (memcmp (buffer + hdr_sz, "$VBT", 4) == 0) {
 | 
						|
		self->kind = FU_ROM_KIND_INTEL;
 | 
						|
	} else if (memcmp(buffer + 0x30, " 761295520", 10) == 0) {
 | 
						|
		self->kind = FU_ROM_KIND_ATI;
 | 
						|
	}
 | 
						|
 | 
						|
	/* nothing */
 | 
						|
	if (self->kind == FU_ROM_KIND_UNKNOWN) {
 | 
						|
		g_autofree gchar *str = NULL;
 | 
						|
		str = fu_rom_get_hex_dump (buffer + hdr_sz, 0x32);
 | 
						|
		g_set_error (error,
 | 
						|
			     FWUPD_ERROR,
 | 
						|
			     FWUPD_ERROR_INVALID_FILE,
 | 
						|
			     "Failed to detect firmware kind from [%s]",
 | 
						|
			     str);
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* find version string */
 | 
						|
	self->version = fu_rom_find_version (self->kind, hdr);
 | 
						|
	if (self->version != NULL) {
 | 
						|
		g_strstrip (self->version);
 | 
						|
		g_strdelimit (self->version, "\r\n ", '\0');
 | 
						|
	}
 | 
						|
 | 
						|
	/* update checksum */
 | 
						|
	if (flags & FU_ROM_LOAD_FLAG_BLANK_PPID)
 | 
						|
		fu_rom_find_and_blank_serial_numbers (self);
 | 
						|
	for (guint i = 0; i < self->hdrs->len; i++) {
 | 
						|
		hdr = g_ptr_array_index (self->hdrs, i);
 | 
						|
		g_checksum_update (checksum_sha1, hdr->rom_data, hdr->rom_len);
 | 
						|
		g_checksum_update (checksum_sha256, hdr->rom_data, hdr->rom_len);
 | 
						|
	}
 | 
						|
 | 
						|
	/* done updating checksums */
 | 
						|
	g_ptr_array_add (self->checksums, g_strdup (g_checksum_get_string (checksum_sha1)));
 | 
						|
	g_ptr_array_add (self->checksums, g_strdup (g_checksum_get_string (checksum_sha256)));
 | 
						|
 | 
						|
	/* update guid */
 | 
						|
	id = g_strdup_printf ("PCI\\VEN_%04X&DEV_%04X",
 | 
						|
			      self->vendor_id, self->device_id);
 | 
						|
	self->guid = as_utils_guid_from_string (id);
 | 
						|
	g_debug ("using %s for %s", self->guid, id);
 | 
						|
 | 
						|
	/* not known */
 | 
						|
	if (self->version == NULL) {
 | 
						|
		g_set_error_literal (error,
 | 
						|
				     FWUPD_ERROR,
 | 
						|
				     FWUPD_ERROR_NOT_SUPPORTED,
 | 
						|
				     "Firmware version extractor not known");
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
fu_rom_load_file (FuRom *self, GFile *file, FuRomLoadFlags flags,
 | 
						|
		  GCancellable *cancellable, GError **error)
 | 
						|
{
 | 
						|
	const gssize buffer_sz = 0x400000;
 | 
						|
	gssize sz;
 | 
						|
	guint number_reads = 0;
 | 
						|
	g_autoptr(GError) error_local = NULL;
 | 
						|
	g_autofree gchar *fn = NULL;
 | 
						|
	g_autofree guint8 *buffer = NULL;
 | 
						|
	g_autoptr(GFileOutputStream) output_stream = NULL;
 | 
						|
	g_autoptr(AsProfile) profile = as_profile_new ();
 | 
						|
	g_autoptr(AsProfileTask) ptask = NULL;
 | 
						|
 | 
						|
	g_return_val_if_fail (FU_IS_ROM (self), FALSE);
 | 
						|
 | 
						|
	/* open file */
 | 
						|
	ptask = as_profile_start_literal (profile, "FuRom:reading-data");
 | 
						|
	g_assert (ptask != NULL);
 | 
						|
	self->stream = G_INPUT_STREAM (g_file_read (file, cancellable, &error_local));
 | 
						|
	if (self->stream == NULL) {
 | 
						|
		g_set_error_literal (error,
 | 
						|
				     FWUPD_ERROR,
 | 
						|
				     FWUPD_ERROR_AUTH_FAILED,
 | 
						|
				     error_local->message);
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* we have to enable the read for devices */
 | 
						|
	fn = g_file_get_path (file);
 | 
						|
	if (g_str_has_prefix (fn, "/sys")) {
 | 
						|
		output_stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE,
 | 
						|
						cancellable, error);
 | 
						|
		if (output_stream == NULL)
 | 
						|
			return FALSE;
 | 
						|
		if (g_output_stream_write (G_OUTPUT_STREAM (output_stream), "1", 1,
 | 
						|
					   cancellable, error) < 0)
 | 
						|
			return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* read out the header */
 | 
						|
	buffer = g_malloc ((gsize) buffer_sz);
 | 
						|
	sz = g_input_stream_read (self->stream, buffer, buffer_sz,
 | 
						|
				  cancellable, error);
 | 
						|
	if (sz < 0)
 | 
						|
		return FALSE;
 | 
						|
	if (sz < 512) {
 | 
						|
		g_set_error (error,
 | 
						|
			     FWUPD_ERROR,
 | 
						|
			     FWUPD_ERROR_INVALID_FILE,
 | 
						|
			     "Firmware too small: %" G_GSSIZE_FORMAT " bytes", sz);
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* ensure we got enough data to fill the buffer */
 | 
						|
	while (sz < buffer_sz) {
 | 
						|
		gssize sz_chunk;
 | 
						|
		sz_chunk = g_input_stream_read (self->stream,
 | 
						|
						buffer + sz,
 | 
						|
						buffer_sz - sz,
 | 
						|
						cancellable,
 | 
						|
						error);
 | 
						|
		if (sz_chunk == 0)
 | 
						|
			break;
 | 
						|
		g_debug ("ROM returned 0x%04x bytes, adding 0x%04x...",
 | 
						|
			 (guint) sz, (guint) sz_chunk);
 | 
						|
		if (sz_chunk < 0)
 | 
						|
			return FALSE;
 | 
						|
		sz += sz_chunk;
 | 
						|
 | 
						|
		/* check the firmware isn't serving us small chunks */
 | 
						|
		if (number_reads++ > 16) {
 | 
						|
			g_set_error_literal (error,
 | 
						|
					     FWUPD_ERROR,
 | 
						|
					     FWUPD_ERROR_INVALID_FILE,
 | 
						|
					     "firmware not fulfilling requests");
 | 
						|
			return FALSE;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	g_debug ("ROM buffer filled %" G_GSSIZE_FORMAT "kb/%" G_GSSIZE_FORMAT "kb",
 | 
						|
		 sz / 0x400, buffer_sz / 0x400);
 | 
						|
	return fu_rom_load_data (self, buffer, sz, flags, cancellable, error);
 | 
						|
}
 | 
						|
 | 
						|
FuRomKind
 | 
						|
fu_rom_get_kind (FuRom *self)
 | 
						|
{
 | 
						|
	g_return_val_if_fail (FU_IS_ROM (self), FU_ROM_KIND_UNKNOWN);
 | 
						|
	return self->kind;
 | 
						|
}
 | 
						|
 | 
						|
const gchar *
 | 
						|
fu_rom_get_version (FuRom *self)
 | 
						|
{
 | 
						|
	g_return_val_if_fail (FU_IS_ROM (self), NULL);
 | 
						|
	return self->version;
 | 
						|
}
 | 
						|
 | 
						|
const gchar *
 | 
						|
fu_rom_get_guid (FuRom *self)
 | 
						|
{
 | 
						|
	g_return_val_if_fail (FU_IS_ROM (self), NULL);
 | 
						|
	return self->guid;
 | 
						|
}
 | 
						|
 | 
						|
guint16
 | 
						|
fu_rom_get_vendor (FuRom *self)
 | 
						|
{
 | 
						|
	g_return_val_if_fail (FU_IS_ROM (self), 0x0000);
 | 
						|
	return self->vendor_id;
 | 
						|
}
 | 
						|
 | 
						|
guint16
 | 
						|
fu_rom_get_model (FuRom *self)
 | 
						|
{
 | 
						|
	g_return_val_if_fail (FU_IS_ROM (self), 0x0000);
 | 
						|
	return self->device_id;
 | 
						|
}
 | 
						|
 | 
						|
GPtrArray *
 | 
						|
fu_rom_get_checksums (FuRom *self)
 | 
						|
{
 | 
						|
	return self->checksums;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fu_rom_class_init (FuRomClass *klass)
 | 
						|
{
 | 
						|
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 | 
						|
	object_class->finalize = fu_rom_finalize;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fu_rom_init (FuRom *self)
 | 
						|
{
 | 
						|
	self->checksums = g_ptr_array_new_with_free_func (g_free);
 | 
						|
	self->hdrs = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_rom_pci_header_free);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fu_rom_finalize (GObject *object)
 | 
						|
{
 | 
						|
	FuRom *self = FU_ROM (object);
 | 
						|
 | 
						|
	g_free (self->version);
 | 
						|
	g_free (self->guid);
 | 
						|
	g_ptr_array_unref (self->checksums);
 | 
						|
	g_ptr_array_unref (self->hdrs);
 | 
						|
	if (self->stream != NULL)
 | 
						|
		g_object_unref (self->stream);
 | 
						|
 | 
						|
	G_OBJECT_CLASS (fu_rom_parent_class)->finalize (object);
 | 
						|
}
 | 
						|
 | 
						|
FuRom *
 | 
						|
fu_rom_new (void)
 | 
						|
{
 | 
						|
	FuRom *self;
 | 
						|
	self = g_object_new (FU_TYPE_ROM, NULL);
 | 
						|
	return FU_ROM (self);
 | 
						|
}
 |