mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-25 22:40:50 +00:00
483 lines
13 KiB
C
483 lines
13 KiB
C
/*
|
|
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include "fu-redfish-common.h"
|
|
#include "fu-redfish-smbios.h"
|
|
|
|
struct _FuRedfishSmbios {
|
|
FuFirmwareClass parent_instance;
|
|
guint16 port;
|
|
gchar *hostname;
|
|
gchar *mac_addr;
|
|
gchar *ip_addr;
|
|
guint16 vid;
|
|
guint16 pid;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuRedfishSmbios, fu_redfish_smbios, FU_TYPE_FIRMWARE)
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 service_uuid[16];
|
|
guint8 host_ip_assignment_type;
|
|
guint8 host_ip_address_format;
|
|
guint8 host_ip_address[16];
|
|
guint8 host_ip_mask[16];
|
|
guint8 service_ip_assignment_type;
|
|
guint8 service_ip_address_format;
|
|
guint8 service_ip_address[16];
|
|
guint8 service_ip_mask[16];
|
|
guint16 service_ip_port;
|
|
guint32 service_ip_vlan_id;
|
|
guint8 service_hostname_len;
|
|
/* optional service_hostname goes here */
|
|
} FuRedfishProtocolOverIp;
|
|
|
|
guint16
|
|
fu_redfish_smbios_get_port(FuRedfishSmbios *self)
|
|
{
|
|
return self->port;
|
|
}
|
|
|
|
guint16
|
|
fu_redfish_smbios_get_vid(FuRedfishSmbios *self)
|
|
{
|
|
return self->vid;
|
|
}
|
|
|
|
guint16
|
|
fu_redfish_smbios_get_pid(FuRedfishSmbios *self)
|
|
{
|
|
return self->pid;
|
|
}
|
|
|
|
const gchar *
|
|
fu_redfish_smbios_get_hostname(FuRedfishSmbios *self)
|
|
{
|
|
return self->hostname;
|
|
}
|
|
|
|
const gchar *
|
|
fu_redfish_smbios_get_mac_addr(FuRedfishSmbios *self)
|
|
{
|
|
return self->mac_addr;
|
|
}
|
|
|
|
const gchar *
|
|
fu_redfish_smbios_get_ip_addr(FuRedfishSmbios *self)
|
|
{
|
|
return self->ip_addr;
|
|
}
|
|
|
|
static void
|
|
fu_redfish_smbios_set_hostname(FuRedfishSmbios *self, const gchar *hostname)
|
|
{
|
|
g_free(self->hostname);
|
|
self->hostname = g_strdup(hostname);
|
|
}
|
|
|
|
static void
|
|
fu_redfish_smbios_set_mac_addr(FuRedfishSmbios *self, const gchar *mac_addr)
|
|
{
|
|
g_free(self->mac_addr);
|
|
self->mac_addr = g_strdup(mac_addr);
|
|
}
|
|
|
|
static void
|
|
fu_redfish_smbios_set_ip_addr(FuRedfishSmbios *self, const gchar *ip_addr)
|
|
{
|
|
g_free(self->ip_addr);
|
|
self->ip_addr = g_strdup(ip_addr);
|
|
}
|
|
|
|
static void
|
|
fu_redfish_smbios_set_port(FuRedfishSmbios *self, guint16 port)
|
|
{
|
|
self->port = port;
|
|
}
|
|
|
|
static void
|
|
fu_redfish_smbios_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
|
|
{
|
|
FuRedfishSmbios *self = FU_REDFISH_SMBIOS(firmware);
|
|
fu_xmlb_builder_insert_kx(bn, "port", self->port);
|
|
fu_xmlb_builder_insert_kv(bn, "hostname", self->hostname);
|
|
fu_xmlb_builder_insert_kv(bn, "mac_addr", self->mac_addr);
|
|
fu_xmlb_builder_insert_kv(bn, "ip_addr", self->ip_addr);
|
|
fu_xmlb_builder_insert_kx(bn, "vid", self->vid);
|
|
fu_xmlb_builder_insert_kx(bn, "pid", self->pid);
|
|
}
|
|
|
|
static gboolean
|
|
fu_redfish_smbios_build(FuFirmware *firmware, XbNode *n, GError **error)
|
|
{
|
|
FuRedfishSmbios *self = FU_REDFISH_SMBIOS(firmware);
|
|
const gchar *tmp;
|
|
guint64 tmpu;
|
|
|
|
/* optional properties */
|
|
tmpu = xb_node_query_text_as_uint(n, "port", NULL);
|
|
if (tmpu != G_MAXUINT64)
|
|
fu_redfish_smbios_set_port(self, (guint16)tmpu);
|
|
tmpu = xb_node_query_text_as_uint(n, "vid", NULL);
|
|
if (tmpu != G_MAXUINT64)
|
|
self->vid = (guint16)tmpu;
|
|
tmpu = xb_node_query_text_as_uint(n, "pid", NULL);
|
|
if (tmpu != G_MAXUINT64)
|
|
self->pid = (guint16)tmpu;
|
|
tmp = xb_node_query_text(n, "hostname", NULL);
|
|
if (tmp != NULL)
|
|
fu_redfish_smbios_set_hostname(self, tmp);
|
|
tmp = xb_node_query_text(n, "mac_addr", NULL);
|
|
if (tmp != NULL)
|
|
fu_redfish_smbios_set_mac_addr(self, tmp);
|
|
tmp = xb_node_query_text(n, "ip_addr", NULL);
|
|
if (tmp != NULL)
|
|
fu_redfish_smbios_set_ip_addr(self, tmp);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_redfish_smbios_parse_interface_data(FuRedfishSmbios *self,
|
|
GBytes *fw,
|
|
gsize offset,
|
|
GError **error)
|
|
{
|
|
gsize bufsz = 0;
|
|
gsize offset_mac_addr = G_MAXSIZE;
|
|
gsize offset_vid_pid = G_MAXSIZE;
|
|
guint8 interface_type = 0x0;
|
|
const guint8 *buf = g_bytes_get_data(fw, &bufsz);
|
|
|
|
/* parse the data depending on the interface type */
|
|
if (!fu_common_read_uint8_safe(buf, bufsz, offset, &interface_type, error))
|
|
return FALSE;
|
|
offset++;
|
|
switch (interface_type) {
|
|
case REDFISH_INTERFACE_TYPE_USB_NETWORK:
|
|
case REDFISH_INTERFACE_TYPE_PCI_NETWORK:
|
|
offset_vid_pid = 0x00;
|
|
break;
|
|
case REDFISH_INTERFACE_TYPE_USB_NETWORK_V2:
|
|
offset_vid_pid = 0x01;
|
|
offset_mac_addr = 0x06;
|
|
break;
|
|
case REDFISH_INTERFACE_TYPE_PCI_NETWORK_V2:
|
|
offset_vid_pid = 0x01;
|
|
offset_mac_addr = 0x09;
|
|
break;
|
|
default:
|
|
g_debug("unknown Network Interface");
|
|
break;
|
|
}
|
|
|
|
/* MAC address */
|
|
if (offset_mac_addr != G_MAXSIZE) {
|
|
guint8 mac_addr[6] = {0x0};
|
|
g_autofree gchar *mac_addr_str = NULL;
|
|
if (!fu_memcpy_safe(mac_addr,
|
|
sizeof(mac_addr),
|
|
0x0, /* dst */
|
|
buf,
|
|
bufsz,
|
|
offset + offset_mac_addr, /* src */
|
|
sizeof(mac_addr),
|
|
error))
|
|
return FALSE;
|
|
mac_addr_str = fu_redfish_common_buffer_to_mac(mac_addr);
|
|
fu_redfish_smbios_set_mac_addr(self, mac_addr_str);
|
|
}
|
|
|
|
/* VID:PID */
|
|
if (offset_vid_pid != G_MAXSIZE) {
|
|
if (!fu_common_read_uint16_safe(buf,
|
|
bufsz,
|
|
offset + offset_vid_pid,
|
|
&self->vid,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return FALSE;
|
|
if (!fu_common_read_uint16_safe(buf,
|
|
bufsz,
|
|
offset + offset_vid_pid + 0x02,
|
|
&self->pid,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_redfish_smbios_parse_over_ip(FuRedfishSmbios *self, GBytes *fw, gsize offset, GError **error)
|
|
{
|
|
gsize bufsz = 0;
|
|
const guint8 *buf = g_bytes_get_data(fw, &bufsz);
|
|
guint8 hostname_length = 0x0;
|
|
guint8 service_ip_address_format = 0x0;
|
|
guint16 service_ip_port = 0x0;
|
|
guint8 service_ip_address[16] = {0x0};
|
|
|
|
/* port */
|
|
if (!fu_common_read_uint16_safe(
|
|
buf,
|
|
bufsz,
|
|
offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_port),
|
|
&service_ip_port,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return FALSE;
|
|
fu_redfish_smbios_set_port(self, service_ip_port);
|
|
|
|
/* IP address */
|
|
if (!fu_memcpy_safe(
|
|
service_ip_address,
|
|
sizeof(service_ip_address),
|
|
0x0, /* dst */
|
|
buf,
|
|
bufsz,
|
|
offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_address), /* src */
|
|
sizeof(service_ip_address),
|
|
error))
|
|
return FALSE;
|
|
if (!fu_common_read_uint8_safe(
|
|
buf,
|
|
bufsz,
|
|
offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_address_format),
|
|
&service_ip_address_format,
|
|
error))
|
|
return FALSE;
|
|
if (service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V4) {
|
|
g_autofree gchar *tmp = NULL;
|
|
tmp = fu_redfish_common_buffer_to_ipv4(service_ip_address);
|
|
fu_redfish_smbios_set_ip_addr(self, tmp);
|
|
} else if (service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V6) {
|
|
g_autofree gchar *tmp = NULL;
|
|
tmp = fu_redfish_common_buffer_to_ipv6(service_ip_address);
|
|
fu_redfish_smbios_set_ip_addr(self, tmp);
|
|
} else {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"address format is invalid");
|
|
return FALSE;
|
|
}
|
|
|
|
/* hostname */
|
|
if (!fu_common_read_uint8_safe(
|
|
buf,
|
|
bufsz,
|
|
offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_hostname_len),
|
|
&hostname_length,
|
|
error))
|
|
return FALSE;
|
|
if (hostname_length > 0) {
|
|
g_autofree gchar *hostname = g_malloc0(hostname_length + 1);
|
|
if (!fu_memcpy_safe((guint8 *)hostname,
|
|
hostname_length,
|
|
0x0, /* dst */
|
|
buf,
|
|
bufsz,
|
|
offset + sizeof(FuRedfishProtocolOverIp), /* src */
|
|
hostname_length,
|
|
error))
|
|
return FALSE;
|
|
fu_redfish_smbios_set_hostname(self, hostname);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_redfish_smbios_parse(FuFirmware *firmware,
|
|
GBytes *fw,
|
|
guint64 addr_start,
|
|
guint64 addr_end,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuRedfishSmbios *self = FU_REDFISH_SMBIOS(firmware);
|
|
gsize bufsz = 0;
|
|
gsize offset = 0;
|
|
guint8 protocol_rcds = 0;
|
|
const guint8 *buf = g_bytes_get_data(fw, &bufsz);
|
|
|
|
/* check size */
|
|
if (bufsz < 0x09) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"SMBIOS entry too small: %" G_GSIZE_FORMAT,
|
|
bufsz);
|
|
return FALSE;
|
|
}
|
|
|
|
/* check type */
|
|
if (buf[0x0] != REDFISH_SMBIOS_TABLE_TYPE) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"not Management Controller Host Interface");
|
|
return FALSE;
|
|
}
|
|
if (buf[0x1] != bufsz) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"size of table 0x%x does not match binary 0x%x",
|
|
buf[0x1],
|
|
(guint)bufsz);
|
|
return FALSE;
|
|
}
|
|
|
|
/* check interface type */
|
|
if (buf[0x04] != REDFISH_CONTROLLER_INTERFACE_TYPE_NETWORK_HOST) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"only Network Host Interface supported");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check length */
|
|
if (buf[0x05] > 0) {
|
|
if (!fu_redfish_smbios_parse_interface_data(self, fw, 0x06, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse protocol records */
|
|
if (!fu_common_read_uint8_safe(buf, bufsz, 0x06 + buf[0x05], &protocol_rcds, error))
|
|
return FALSE;
|
|
offset = 0x07 + buf[0x05];
|
|
g_debug("protocol_rcds: %u", protocol_rcds);
|
|
for (guint i = 0; i < protocol_rcds; i++) {
|
|
guint8 protocol_id = 0;
|
|
guint8 protocol_sz = 0;
|
|
if (!fu_common_read_uint8_safe(buf, bufsz, offset, &protocol_id, error))
|
|
return FALSE;
|
|
if (!fu_common_read_uint8_safe(buf, bufsz, offset + 0x1, &protocol_sz, error))
|
|
return FALSE;
|
|
if (protocol_id == REDFISH_PROTOCOL_REDFISH_OVER_IP) {
|
|
if (!fu_redfish_smbios_parse_over_ip(self, fw, offset + 0x2, error))
|
|
return FALSE;
|
|
} else {
|
|
g_debug("ignoring protocol ID 0x%02x", protocol_id);
|
|
}
|
|
offset += protocol_sz + 1;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static GBytes *
|
|
fu_redfish_smbios_write(FuFirmware *firmware, GError **error)
|
|
{
|
|
FuRedfishProtocolOverIp proto = {0x0};
|
|
FuRedfishSmbios *self = FU_REDFISH_SMBIOS(firmware);
|
|
gsize hostname_sz = 0;
|
|
g_autoptr(GByteArray) buf = g_byte_array_new();
|
|
|
|
if (self->hostname != NULL)
|
|
hostname_sz = strlen(self->hostname);
|
|
fu_byte_array_append_uint8(buf, REDFISH_SMBIOS_TABLE_TYPE);
|
|
fu_byte_array_append_uint8(buf, 0x6D + hostname_sz); /* length */
|
|
fu_byte_array_append_uint16(buf, 0x1234, G_LITTLE_ENDIAN); /* handle */
|
|
fu_byte_array_append_uint8(buf, REDFISH_CONTROLLER_INTERFACE_TYPE_NETWORK_HOST);
|
|
fu_byte_array_append_uint8(buf, 0x09); /* iface datalen */
|
|
fu_byte_array_append_uint8(buf, REDFISH_INTERFACE_TYPE_USB_NETWORK); /* iface */
|
|
fu_byte_array_append_uint16(buf, self->vid, G_LITTLE_ENDIAN); /* iface:VID */
|
|
fu_byte_array_append_uint16(buf, self->pid, G_LITTLE_ENDIAN); /* iface:PID */
|
|
fu_byte_array_append_uint8(buf, 0x02); /* iface:serialsz */
|
|
fu_byte_array_append_uint8(buf, 0x03); /* iType */
|
|
fu_byte_array_append_uint8(buf, 'S'); /* iface:serial */
|
|
fu_byte_array_append_uint8(buf, 'n'); /* iface:serial */
|
|
fu_byte_array_append_uint8(buf, 0x1); /* nr protocol rcds */
|
|
|
|
/* protocol record */
|
|
fu_byte_array_append_uint8(buf, REDFISH_PROTOCOL_REDFISH_OVER_IP);
|
|
fu_byte_array_append_uint8(buf, sizeof(FuRedfishProtocolOverIp) + hostname_sz);
|
|
if (!fu_common_write_uint16_safe((guint8 *)&proto,
|
|
sizeof(proto),
|
|
G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_port),
|
|
self->port,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return NULL;
|
|
if (!fu_common_write_uint8_safe(
|
|
(guint8 *)&proto,
|
|
sizeof(proto),
|
|
G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_address_format),
|
|
REDFISH_IP_ADDRESS_FORMAT_V4,
|
|
error))
|
|
return NULL;
|
|
if (!fu_common_write_uint8_safe(
|
|
(guint8 *)&proto,
|
|
sizeof(proto),
|
|
G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_assignment_type),
|
|
REDFISH_IP_ASSIGNMENT_TYPE_STATIC,
|
|
error))
|
|
return NULL;
|
|
if (self->hostname != NULL)
|
|
hostname_sz = strlen(self->hostname);
|
|
if (hostname_sz > 0) {
|
|
if (!fu_common_write_uint8_safe(
|
|
(guint8 *)&proto,
|
|
sizeof(proto),
|
|
G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_hostname_len),
|
|
hostname_sz,
|
|
error)) {
|
|
g_prefix_error(error, "cannot write length: ");
|
|
return NULL;
|
|
}
|
|
}
|
|
g_byte_array_append(buf, (guint8 *)&proto, sizeof(proto));
|
|
if (hostname_sz > 0)
|
|
g_byte_array_append(buf, (guint8 *)self->hostname, hostname_sz);
|
|
return g_byte_array_free_to_bytes(g_steal_pointer(&buf));
|
|
}
|
|
|
|
static void
|
|
fu_redfish_smbios_finalize(GObject *object)
|
|
{
|
|
FuRedfishSmbios *self = FU_REDFISH_SMBIOS(object);
|
|
g_free(self->hostname);
|
|
g_free(self->mac_addr);
|
|
g_free(self->ip_addr);
|
|
G_OBJECT_CLASS(fu_redfish_smbios_parent_class)->finalize(object);
|
|
}
|
|
|
|
static void
|
|
fu_redfish_smbios_init(FuRedfishSmbios *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fu_redfish_smbios_class_init(FuRedfishSmbiosClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass);
|
|
object_class->finalize = fu_redfish_smbios_finalize;
|
|
klass_firmware->parse = fu_redfish_smbios_parse;
|
|
klass_firmware->write = fu_redfish_smbios_write;
|
|
klass_firmware->build = fu_redfish_smbios_build;
|
|
klass_firmware->export = fu_redfish_smbios_export;
|
|
}
|
|
|
|
FuRedfishSmbios *
|
|
fu_redfish_smbios_new(void)
|
|
{
|
|
return FU_REDFISH_SMBIOS(g_object_new(FU_TYPE_REDFISH_SMBIOS, NULL));
|
|
}
|