mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-14 21:57:34 +00:00
511 lines
12 KiB
C
511 lines
12 KiB
C
/*
|
|
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "FuSmbios"
|
|
|
|
#include "config.h"
|
|
|
|
#include <gio/gio.h>
|
|
#include <string.h>
|
|
|
|
#include "fu-common.h"
|
|
#include "fu-smbios-private.h"
|
|
#include "fwupd-error.h"
|
|
|
|
struct _FuSmbios {
|
|
GObject parent_instance;
|
|
gchar *smbios_ver;
|
|
guint32 structure_table_len;
|
|
GPtrArray *items;
|
|
};
|
|
|
|
/* little endian */
|
|
typedef struct __attribute__((packed)) {
|
|
gchar anchor_str[4];
|
|
guint8 entry_point_csum;
|
|
guint8 entry_point_len;
|
|
guint8 smbios_major_ver;
|
|
guint8 smbios_minor_ver;
|
|
guint16 max_structure_sz;
|
|
guint8 entry_point_rev;
|
|
guint8 formatted_area[5];
|
|
gchar intermediate_anchor_str[5];
|
|
guint8 intermediate_csum;
|
|
guint16 structure_table_len;
|
|
guint32 structure_table_addr;
|
|
guint16 number_smbios_structs;
|
|
guint8 smbios_bcd_rev;
|
|
} FuSmbiosStructureEntryPoint32;
|
|
|
|
/* little endian */
|
|
typedef struct __attribute__((packed)) {
|
|
gchar anchor_str[5];
|
|
guint8 entry_point_csum;
|
|
guint8 entry_point_len;
|
|
guint8 smbios_major_ver;
|
|
guint8 smbios_minor_ver;
|
|
guint8 smbios_docrev;
|
|
guint8 entry_point_rev;
|
|
guint8 reserved0;
|
|
guint32 structure_table_len;
|
|
guint64 structure_table_addr;
|
|
} FuSmbiosStructureEntryPoint64;
|
|
|
|
/* little endian */
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 type;
|
|
guint8 len;
|
|
guint16 handle;
|
|
} FuSmbiosStructure;
|
|
|
|
typedef struct {
|
|
guint8 type;
|
|
guint16 handle;
|
|
GBytes *data;
|
|
GPtrArray *strings;
|
|
} FuSmbiosItem;
|
|
|
|
G_DEFINE_TYPE (FuSmbios, fu_smbios, G_TYPE_OBJECT)
|
|
|
|
static gboolean
|
|
fu_smbios_setup_from_data (FuSmbios *self, const guint8 *buf, gsize sz, GError **error)
|
|
{
|
|
/* go through each structure */
|
|
for (gsize i = 0; i < sz; i++) {
|
|
FuSmbiosStructure *str = (FuSmbiosStructure *) &buf[i];
|
|
FuSmbiosItem *item;
|
|
|
|
/* invalid */
|
|
if (str->len == 0x00)
|
|
break;
|
|
if (str->len >= sz) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"structure larger than available data");
|
|
return FALSE;
|
|
}
|
|
|
|
/* create a new result */
|
|
item = g_new0 (FuSmbiosItem, 1);
|
|
item->type = str->type;
|
|
item->handle = GUINT16_FROM_LE (str->handle);
|
|
item->data = g_bytes_new (buf + i, str->len);
|
|
item->strings = g_ptr_array_new_with_free_func (g_free);
|
|
g_ptr_array_add (self->items, item);
|
|
|
|
/* jump to the end of the struct */
|
|
i += str->len;
|
|
if (buf[i] == '\0' && buf[i+1] == '\0') {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
/* add strings from table */
|
|
for (gsize start_offset = i; i < sz; i++) {
|
|
if (buf[i] == '\0') {
|
|
if (start_offset == i)
|
|
break;
|
|
g_ptr_array_add (item->strings,
|
|
g_strdup ((const gchar *) &buf[start_offset]));
|
|
start_offset = i + 1;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_smbios_setup_from_file:
|
|
* @self: A #FuSmbios
|
|
* @filename: A filename
|
|
* @error: A #GError or %NULL
|
|
*
|
|
* Reads all the SMBIOS values from a DMI blob.
|
|
*
|
|
* Returns: %TRUE for success
|
|
*
|
|
* Since: 1.0.0
|
|
**/
|
|
gboolean
|
|
fu_smbios_setup_from_file (FuSmbios *self, const gchar *filename, GError **error)
|
|
{
|
|
gsize sz = 0;
|
|
g_autofree gchar *buf = NULL;
|
|
if (!g_file_get_contents (filename, &buf, &sz, error))
|
|
return FALSE;
|
|
return fu_smbios_setup_from_data (self, (guint8 *) buf, sz, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_smbios_parse_ep32 (FuSmbios *self, const gchar *buf, gsize sz, GError **error)
|
|
{
|
|
FuSmbiosStructureEntryPoint32 *ep;
|
|
guint8 csum = 0;
|
|
|
|
/* verify size */
|
|
if (sz != sizeof(FuSmbiosStructureEntryPoint32)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"invalid smbios entry point got %" G_GSIZE_FORMAT
|
|
" bytes, expected %" G_GSIZE_FORMAT,
|
|
sz, sizeof(FuSmbiosStructureEntryPoint32));
|
|
return FALSE;
|
|
}
|
|
|
|
/* verify checksum */
|
|
for (guint i = 0; i < sz; i++)
|
|
csum += buf[i];
|
|
if (csum != 0x00) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"entry point checksum invalid");
|
|
return FALSE;
|
|
}
|
|
|
|
/* verify intermediate section */
|
|
ep = (FuSmbiosStructureEntryPoint32 *) buf;
|
|
if (memcmp (ep->intermediate_anchor_str, "_DMI_", 5) != 0) {
|
|
g_autofree gchar *tmp = g_strndup (ep->intermediate_anchor_str, 5);
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"intermediate anchor signature invalid, got %s", tmp);
|
|
return FALSE;
|
|
}
|
|
for (guint i = 10; i < sz; i++)
|
|
csum += buf[i];
|
|
if (csum != 0x00) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"intermediate checksum invalid");
|
|
return FALSE;
|
|
}
|
|
self->structure_table_len = GUINT16_FROM_LE (ep->structure_table_len);
|
|
self->smbios_ver = g_strdup_printf ("%u.%u",
|
|
ep->smbios_major_ver,
|
|
ep->smbios_minor_ver);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_smbios_parse_ep64 (FuSmbios *self, const gchar *buf, gsize sz, GError **error)
|
|
{
|
|
FuSmbiosStructureEntryPoint64 *ep;
|
|
guint8 csum = 0;
|
|
|
|
/* verify size */
|
|
if (sz != sizeof(FuSmbiosStructureEntryPoint64)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"invalid smbios3 entry point got %" G_GSIZE_FORMAT
|
|
" bytes, expected %" G_GSIZE_FORMAT,
|
|
sz, sizeof(FuSmbiosStructureEntryPoint32));
|
|
return FALSE;
|
|
}
|
|
|
|
/* verify checksum */
|
|
for (guint i = 0; i < sz; i++)
|
|
csum += buf[i];
|
|
if (csum != 0x00) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"entry point checksum invalid");
|
|
return FALSE;
|
|
}
|
|
ep = (FuSmbiosStructureEntryPoint64 *) buf;
|
|
self->structure_table_len = GUINT32_FROM_LE (ep->structure_table_len);
|
|
self->smbios_ver = g_strdup_printf ("%u.%u",
|
|
ep->smbios_major_ver,
|
|
ep->smbios_minor_ver);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_smbios_setup_from_path:
|
|
* @self: A #FuSmbios
|
|
* @path: A path, e.g. `/sys/firmware/dmi/tables`
|
|
* @error: A #GError or %NULL
|
|
*
|
|
* Reads all the SMBIOS values from a specific path.
|
|
*
|
|
* Returns: %TRUE for success
|
|
*
|
|
* Since: 1.0.0
|
|
**/
|
|
gboolean
|
|
fu_smbios_setup_from_path (FuSmbios *self, const gchar *path, GError **error)
|
|
{
|
|
gsize sz = 0;
|
|
g_autofree gchar *dmi_fn = NULL;
|
|
g_autofree gchar *dmi_raw = NULL;
|
|
g_autofree gchar *ep_fn = NULL;
|
|
g_autofree gchar *ep_raw = NULL;
|
|
|
|
g_return_val_if_fail (FU_IS_SMBIOS (self), FALSE);
|
|
|
|
/* get the smbios entry point */
|
|
ep_fn = g_build_filename (path, "smbios_entry_point", NULL);
|
|
if (!g_file_get_contents (ep_fn, &ep_raw, &sz, error))
|
|
return FALSE;
|
|
|
|
/* check we got enough data to read the signature */
|
|
if (sz < 5) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"invalid smbios entry point got %" G_GSIZE_FORMAT
|
|
" bytes, expected %" G_GSIZE_FORMAT
|
|
" or %" G_GSIZE_FORMAT, sz,
|
|
sizeof(FuSmbiosStructureEntryPoint32),
|
|
sizeof(FuSmbiosStructureEntryPoint64));
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse 32 bit structure */
|
|
if (memcmp (ep_raw, "_SM_", 4) == 0) {
|
|
if (!fu_smbios_parse_ep32 (self, ep_raw, sz, error))
|
|
return FALSE;
|
|
} else if (memcmp (ep_raw, "_SM3_", 5) == 0) {
|
|
if (!fu_smbios_parse_ep64 (self, ep_raw, sz, error))
|
|
return FALSE;
|
|
} else {
|
|
g_autofree gchar *tmp = g_strndup (ep_raw, 4);
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"SMBIOS signature invalid, got %s", tmp);
|
|
return FALSE;
|
|
}
|
|
|
|
/* get the DMI data */
|
|
dmi_fn = g_build_filename (path, "DMI", NULL);
|
|
if (!g_file_get_contents (dmi_fn, &dmi_raw, &sz, error))
|
|
return FALSE;
|
|
if (sz != self->structure_table_len) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"invalid DMI data size, got %" G_GSIZE_FORMAT
|
|
" bytes, expected %" G_GUINT32_FORMAT,
|
|
sz, self->structure_table_len);
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse blob */
|
|
return fu_smbios_setup_from_data (self, (guint8 *) dmi_raw, sz, error);
|
|
}
|
|
|
|
/**
|
|
* fu_smbios_setup:
|
|
* @self: A #FuSmbios
|
|
* @error: A #GError or %NULL
|
|
*
|
|
* Reads all the SMBIOS values from the hardware.
|
|
*
|
|
* Returns: %TRUE for success
|
|
*
|
|
* Since: 1.0.0
|
|
**/
|
|
gboolean
|
|
fu_smbios_setup (FuSmbios *self, GError **error)
|
|
{
|
|
g_autofree gchar *path = NULL;
|
|
g_autofree gchar *sysfsfwdir = NULL;
|
|
g_return_val_if_fail (FU_IS_SMBIOS (self), FALSE);
|
|
sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW);
|
|
path = g_build_filename (sysfsfwdir, "dmi", "tables", NULL);
|
|
return fu_smbios_setup_from_path (self, path, error);
|
|
}
|
|
|
|
/**
|
|
* fu_smbios_to_string:
|
|
* @self: A #FuSmbios
|
|
*
|
|
* Dumps the parsed SMBIOS data to a string.
|
|
*
|
|
* Returns: a UTF-8 string
|
|
*
|
|
* Since: 1.0.0
|
|
**/
|
|
gchar *
|
|
fu_smbios_to_string (FuSmbios *self)
|
|
{
|
|
GString *str;
|
|
|
|
g_return_val_if_fail (FU_IS_SMBIOS (self), NULL);
|
|
|
|
str = g_string_new (NULL);
|
|
g_string_append_printf (str, "SmbiosVersion: %s\n", self->smbios_ver);
|
|
for (guint i = 0; i < self->items->len; i++) {
|
|
FuSmbiosItem *item = g_ptr_array_index (self->items, i);
|
|
g_string_append_printf (str, "Type: %02x\n", item->type);
|
|
g_string_append_printf (str, " Length: %" G_GSIZE_FORMAT "\n",
|
|
g_bytes_get_size (item->data));
|
|
g_string_append_printf (str, " Handle: 0x%04x\n", item->handle);
|
|
for (guint j = 0; j < item->strings->len; j++) {
|
|
const gchar *tmp = g_ptr_array_index (item->strings, j);
|
|
g_string_append_printf (str, " String[%02u]: %s\n", j, tmp);
|
|
}
|
|
}
|
|
return g_string_free (str, FALSE);
|
|
}
|
|
|
|
static FuSmbiosItem *
|
|
fu_smbios_get_item_for_type (FuSmbios *self, guint8 type)
|
|
{
|
|
for (guint i = 0; i < self->items->len; i++) {
|
|
FuSmbiosItem *item = g_ptr_array_index (self->items, i);
|
|
if (item->type == type)
|
|
return item;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* fu_smbios_get_data:
|
|
* @self: A #FuSmbios
|
|
* @type: A structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS
|
|
* @error: A #GError or %NULL
|
|
*
|
|
* Reads a SMBIOS data blob, which includes the SMBIOS section header.
|
|
*
|
|
* Returns: (transfer full): a #GBytes, or %NULL if invalid or not found
|
|
*
|
|
* Since: 1.0.0
|
|
**/
|
|
GBytes *
|
|
fu_smbios_get_data (FuSmbios *self, guint8 type, GError **error)
|
|
{
|
|
FuSmbiosItem *item;
|
|
g_return_val_if_fail (FU_IS_SMBIOS (self), NULL);
|
|
item = fu_smbios_get_item_for_type (self, type);
|
|
if (item == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"no structure with type %02x", type);
|
|
return NULL;
|
|
}
|
|
return g_bytes_ref (item->data);
|
|
}
|
|
|
|
/**
|
|
* fu_smbios_get_string:
|
|
* @self: A #FuSmbios
|
|
* @type: A structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS
|
|
* @offset: A structure offset
|
|
* @error: A #GError or %NULL
|
|
*
|
|
* Reads a string from the SMBIOS string table of a specific structure.
|
|
*
|
|
* The @type and @offset can be referenced from the DMTF SMBIOS specification:
|
|
* https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf
|
|
*
|
|
* Returns: a string, or %NULL if invalid or not found
|
|
*
|
|
* Since: 1.0.0
|
|
**/
|
|
const gchar *
|
|
fu_smbios_get_string (FuSmbios *self, guint8 type, guint8 offset, GError **error)
|
|
{
|
|
FuSmbiosItem *item;
|
|
const guint8 *data;
|
|
gsize sz;
|
|
|
|
g_return_val_if_fail (FU_IS_SMBIOS (self), NULL);
|
|
|
|
/* get item */
|
|
item = fu_smbios_get_item_for_type (self, type);
|
|
if (item == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"no structure with type %02x", type);
|
|
return NULL;
|
|
}
|
|
|
|
/* check offset valid */
|
|
data = g_bytes_get_data (item->data, &sz);
|
|
if (offset >= sz) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"offset bigger than size %" G_GSIZE_FORMAT, sz);
|
|
return NULL;
|
|
}
|
|
if (data[offset] == 0x00) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"no data available");
|
|
return NULL;
|
|
}
|
|
|
|
/* check string index valid */
|
|
if (data[offset] > item->strings->len) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"index larger than string table %u",
|
|
data[offset]);
|
|
return NULL;
|
|
}
|
|
return g_ptr_array_index (item->strings, data[offset] - 1);
|
|
}
|
|
|
|
static void
|
|
fu_smbios_item_free (FuSmbiosItem *item)
|
|
{
|
|
g_bytes_unref (item->data);
|
|
g_ptr_array_unref (item->strings);
|
|
g_free (item);
|
|
}
|
|
|
|
static void
|
|
fu_smbios_finalize (GObject *object)
|
|
{
|
|
FuSmbios *self = FU_SMBIOS (object);
|
|
g_free (self->smbios_ver);
|
|
g_ptr_array_unref (self->items);
|
|
G_OBJECT_CLASS (fu_smbios_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
fu_smbios_class_init (FuSmbiosClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = fu_smbios_finalize;
|
|
}
|
|
|
|
static void
|
|
fu_smbios_init (FuSmbios *self)
|
|
{
|
|
self->items = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_smbios_item_free);
|
|
}
|
|
|
|
/**
|
|
* fu_smbios_new:
|
|
*
|
|
* Creates a new object to parse SMBIOS data.
|
|
*
|
|
* Returns: a #FuSmbios
|
|
*
|
|
* Since: 1.0.0
|
|
**/
|
|
FuSmbios *
|
|
fu_smbios_new (void)
|
|
{
|
|
FuSmbios *self;
|
|
self = g_object_new (FU_TYPE_SMBIOS, NULL);
|
|
return FU_SMBIOS (self);
|
|
}
|