mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-12 01:05:09 +00:00

This allows us to 'nest' firmware formats, and removes a ton of duplication. The aim here is to deprecate FuFirmwareImage -- it's almost always acting as a 'child' FuFirmware instance, and even copies most of the vfuncs to allow custom types. If I'm struggling to work out what should be a FuFirmware and what should be a FuFirmwareImage then a plugin author has no hope. For simple payloads we were adding bytes into an image and then the image into a firmware. This gets really messy when most plugins are treating the FuFirmware *as* the binary firmware file. The GBytes saved in the FuFirmware would be considered the payload with the aim of not using FuFirmwareImage in the single-image case.
520 lines
15 KiB
C
520 lines
15 KiB
C
/*
|
|
* Copyright (C) 2019 Mario Limonciello <mario.limonciello@dell.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <linux/mmc/ioctl.h>
|
|
|
|
#include "fu-chunk.h"
|
|
#include "fu-emmc-device.h"
|
|
|
|
/* From kernel linux/major.h */
|
|
#define MMC_BLOCK_MAJOR 179
|
|
|
|
/* From kernel linux/mmc/mmc.h */
|
|
#define MMC_SWITCH 6 /* ac [31:0] See below R1b */
|
|
#define MMC_SEND_EXT_CSD 8 /* adtc R1 */
|
|
#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */
|
|
#define MMC_WRITE_BLOCK 24 /* adtc [31:0] data addr R1 */
|
|
|
|
/* From kernel linux/mmc/core.h */
|
|
#define MMC_RSP_PRESENT (1 << 0)
|
|
#define MMC_RSP_CRC (1 << 2) /* expect valid crc */
|
|
#define MMC_RSP_BUSY (1 << 3) /* card may send busy */
|
|
#define MMC_RSP_OPCODE (1 << 4) /* response contains opcode */
|
|
#define MMC_RSP_SPI_S1 (1 << 7) /* one status byte */
|
|
#define MMC_CMD_AC (0 << 5)
|
|
#define MMC_CMD_ADTC (1 << 5)
|
|
#define MMC_RSP_SPI_BUSY (1 << 10) /* card may send busy */
|
|
#define MMC_RSP_SPI_R1 (MMC_RSP_SPI_S1)
|
|
#define MMC_RSP_SPI_R1B (MMC_RSP_SPI_S1|MMC_RSP_SPI_BUSY)
|
|
#define MMC_RSP_R1 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
|
|
#define MMC_RSP_R1B (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_RSP_BUSY)
|
|
|
|
/* EXT_CSD fields */
|
|
#define EXT_CSD_SUPPORTED_MODES 493 /* RO */
|
|
#define EXT_CSD_FFU_FEATURES 492 /* RO */
|
|
#define EXT_CSD_FFU_ARG_3 490 /* RO */
|
|
#define EXT_CSD_FFU_ARG_2 489 /* RO */
|
|
#define EXT_CSD_FFU_ARG_1 488 /* RO */
|
|
#define EXT_CSD_FFU_ARG_0 487 /* RO */
|
|
#define EXT_CSD_NUM_OF_FW_SEC_PROG_3 305 /* RO */
|
|
#define EXT_CSD_NUM_OF_FW_SEC_PROG_2 304 /* RO */
|
|
#define EXT_CSD_NUM_OF_FW_SEC_PROG_1 303 /* RO */
|
|
#define EXT_CSD_NUM_OF_FW_SEC_PROG_0 302 /* RO */
|
|
#define EXT_CSD_REV 192
|
|
#define EXT_CSD_FW_CONFIG 169 /* R/W */
|
|
#define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */
|
|
#define EXT_CSD_MODE_CONFIG 30
|
|
#define EXT_CSD_MODE_OPERATION_CODES 29 /* W */
|
|
#define EXT_CSD_FFU_STATUS 26 /* R */
|
|
#define EXT_CSD_REV_V5_1 8
|
|
#define EXT_CSD_REV_V5_0 7
|
|
|
|
/* EXT_CSD field definitions */
|
|
#define EXT_CSD_NORMAL_MODE (0x00)
|
|
#define EXT_CSD_FFU_MODE (0x01)
|
|
#define EXT_CSD_FFU_INSTALL (0x01)
|
|
#define EXT_CSD_FFU (1<<0)
|
|
#define EXT_CSD_UPDATE_DISABLE (1<<0)
|
|
#define EXT_CSD_CMD_SET_NORMAL (1<<0)
|
|
|
|
struct _FuEmmcDevice {
|
|
FuUdevDevice parent_instance;
|
|
guint32 sect_size;
|
|
};
|
|
|
|
G_DEFINE_TYPE (FuEmmcDevice, fu_emmc_device, FU_TYPE_UDEV_DEVICE)
|
|
|
|
static void
|
|
fu_emmc_device_to_string (FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuEmmcDevice *self = FU_EMMC_DEVICE (device);
|
|
FU_DEVICE_CLASS (fu_emmc_device_parent_class)->to_string (device, idt, str);
|
|
fu_common_string_append_ku (str, idt, "SectorSize", self->sect_size);
|
|
}
|
|
|
|
static const gchar *
|
|
fu_emmc_device_get_manufacturer (guint64 mmc_id)
|
|
{
|
|
switch (mmc_id) {
|
|
case 0x00:
|
|
case 0x44:
|
|
return "SanDisk";
|
|
case 0x02:
|
|
return "Kingston/Sandisk";
|
|
case 0x03:
|
|
case 0x11:
|
|
return "Toshiba";
|
|
case 0x13:
|
|
return "Micron";
|
|
case 0x15:
|
|
return "Samsung/Sandisk/LG";
|
|
case 0x37:
|
|
return "Kingmax";
|
|
case 0x70:
|
|
case 0x2c:
|
|
return "Kingston";
|
|
default:
|
|
return NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
fu_emmc_device_get_sysattr_guint64 (GUdevDevice *device,
|
|
const gchar *name,
|
|
guint64 *val_out,
|
|
GError **error)
|
|
{
|
|
const gchar *sysfs;
|
|
|
|
sysfs = g_udev_device_get_sysfs_attr (device, name);
|
|
if (sysfs == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed get %s for %s", name, sysfs);
|
|
return FALSE;
|
|
}
|
|
|
|
*val_out = g_ascii_strtoull (sysfs, NULL, 16);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_emmc_device_probe (FuDevice *device, GError **error)
|
|
{
|
|
GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (device));
|
|
guint64 flag;
|
|
guint64 oemid = 0;
|
|
guint64 manfid = 0;
|
|
const gchar *tmp;
|
|
g_autoptr(GUdevDevice) udev_parent = NULL;
|
|
g_autofree gchar *name_only = NULL;
|
|
g_autofree gchar *man_oem = NULL;
|
|
g_autofree gchar *man_oem_name = NULL;
|
|
g_autofree gchar *vendor_id = NULL;
|
|
|
|
/* FuUdevDevice->probe */
|
|
if (!FU_DEVICE_CLASS (fu_emmc_device_parent_class)->probe (device, error))
|
|
return FALSE;
|
|
|
|
udev_parent = g_udev_device_get_parent_with_subsystem (udev_device, "mmc", NULL);
|
|
if (udev_parent == NULL) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"no MMC parent");
|
|
return FALSE;
|
|
}
|
|
|
|
/* look for only the parent node */
|
|
if (g_strcmp0 (g_udev_device_get_devtype (udev_device), "disk") != 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"is not correct devtype=%s, expected disk",
|
|
g_udev_device_get_devtype (udev_device));
|
|
return FALSE;
|
|
}
|
|
|
|
/* doesn't support FFU */
|
|
if (!fu_emmc_device_get_sysattr_guint64 (udev_parent, "ffu_capable", &flag, error))
|
|
return FALSE;
|
|
if (flag == 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"%s does not support field firmware updates",
|
|
fu_device_get_name (device));
|
|
return FALSE;
|
|
}
|
|
|
|
/* add instance IDs */
|
|
tmp = g_udev_device_get_property (udev_device, "ID_NAME");
|
|
if (tmp == NULL) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"has no ID_NAME");
|
|
return FALSE;
|
|
}
|
|
|
|
/* name */
|
|
fu_device_set_name (device, tmp);
|
|
name_only = g_strdup_printf ("EMMC\\%s", fu_device_get_name (device));
|
|
fu_device_add_instance_id (device, name_only);
|
|
|
|
/* manfid + oemid, manfid + oemid + name */
|
|
if (!fu_emmc_device_get_sysattr_guint64 (udev_parent, "manfid", &manfid, error))
|
|
return FALSE;
|
|
if (!fu_emmc_device_get_sysattr_guint64 (udev_parent, "oemid", &oemid, error))
|
|
return FALSE;
|
|
man_oem = g_strdup_printf ("EMMC\\%04" G_GUINT64_FORMAT "&%04" G_GUINT64_FORMAT,
|
|
manfid, oemid);
|
|
fu_device_add_instance_id (device, man_oem);
|
|
man_oem_name = g_strdup_printf ("EMMC\\%04" G_GUINT64_FORMAT "&%04" G_GUINT64_FORMAT "&%s",
|
|
manfid, oemid, fu_device_get_name (device));
|
|
fu_device_add_instance_id (device, man_oem_name);
|
|
|
|
/* set the vendor */
|
|
tmp = g_udev_device_get_sysfs_attr (udev_parent, "manfid");
|
|
vendor_id = g_strdup_printf ("EMMC:%s", tmp);
|
|
fu_device_add_vendor_id (device, vendor_id);
|
|
fu_device_set_vendor (device, fu_emmc_device_get_manufacturer (manfid));
|
|
|
|
/* set the physical ID */
|
|
if (!fu_udev_device_set_physical_id (FU_UDEV_DEVICE (device), "mmc", error))
|
|
return FALSE;
|
|
|
|
/* internal */
|
|
if (!fu_emmc_device_get_sysattr_guint64 (udev_device, "removable", &flag, error))
|
|
return FALSE;
|
|
if (flag == 0)
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_INTERNAL);
|
|
|
|
/* firmware version */
|
|
tmp = g_udev_device_get_sysfs_attr (udev_parent, "fwrev");
|
|
if (tmp != NULL) {
|
|
fu_device_set_version_format (device, FWUPD_VERSION_FORMAT_NUMBER);
|
|
fu_device_set_version (device, tmp);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_emmc_read_extcsd (FuEmmcDevice *self, guint8 *ext_csd, gsize ext_csd_sz, GError **error)
|
|
{
|
|
struct mmc_ioc_cmd idata = { 0x0 };
|
|
idata.write_flag = 0;
|
|
idata.opcode = MMC_SEND_EXT_CSD;
|
|
idata.arg = 0;
|
|
idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
|
idata.blksz = 512;
|
|
idata.blocks = 1;
|
|
mmc_ioc_cmd_set_data (idata, ext_csd);
|
|
return fu_udev_device_ioctl (FU_UDEV_DEVICE (self),
|
|
MMC_IOC_CMD, (guint8 *) &idata,
|
|
NULL, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_emmc_validate_extcsd (FuDevice *device, GError **error)
|
|
{
|
|
FuEmmcDevice *self = FU_EMMC_DEVICE (device);
|
|
guint8 ext_csd[512] = { 0x0 };
|
|
|
|
if (!fu_emmc_read_extcsd (FU_EMMC_DEVICE (device), ext_csd, sizeof (ext_csd), error))
|
|
return FALSE;
|
|
if (ext_csd[EXT_CSD_REV] < EXT_CSD_REV_V5_0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"FFU is only available on devices >= "
|
|
"MMC 5.0, not supported in %s",
|
|
fu_device_get_name (device));
|
|
return FALSE;
|
|
}
|
|
if ((ext_csd[EXT_CSD_SUPPORTED_MODES] & EXT_CSD_FFU) == 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"FFU is not supported in %s",
|
|
fu_device_get_name (device));
|
|
return FALSE;
|
|
}
|
|
if (ext_csd[EXT_CSD_FW_CONFIG] & EXT_CSD_UPDATE_DISABLE) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"firmware update was disabled in %s",
|
|
fu_device_get_name (device));
|
|
return FALSE;
|
|
}
|
|
self->sect_size = (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] == 0) ? 512 : 4096;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_emmc_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
g_autoptr(GError) error_validate = NULL;
|
|
if (!fu_emmc_validate_extcsd (device, &error_validate))
|
|
g_debug ("%s", error_validate->message);
|
|
else
|
|
fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_emmc_device_prepare_firmware (FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuEmmcDevice *self = FU_EMMC_DEVICE (device);
|
|
gsize fw_size = g_bytes_get_size (fw);
|
|
|
|
/* check alignment */
|
|
if ((fw_size % self->sect_size) > 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"firmware data size (%" G_GSIZE_FORMAT ") is not aligned", fw_size);
|
|
return NULL;
|
|
}
|
|
|
|
return fu_firmware_new_from_bytes (fw);
|
|
}
|
|
|
|
static gboolean
|
|
fu_emmc_device_write_firmware (FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuEmmcDevice *self= FU_EMMC_DEVICE (device);
|
|
gsize fw_size = 0;
|
|
gsize total_done;
|
|
guint32 arg;
|
|
guint32 sect_done = 0;
|
|
guint8 ext_csd[512];
|
|
guint failure_cnt = 0;
|
|
g_autofree struct mmc_ioc_multi_cmd *multi_cmd = NULL;
|
|
g_autoptr(GBytes) fw = NULL;
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
if (!fu_emmc_read_extcsd (FU_EMMC_DEVICE (device), ext_csd, sizeof (ext_csd), error))
|
|
return FALSE;
|
|
|
|
fw = fu_firmware_get_bytes (firmware, error);
|
|
if (fw == NULL)
|
|
return FALSE;
|
|
fw_size = g_bytes_get_size (fw);
|
|
|
|
/* set CMD ARG */
|
|
arg = ext_csd[EXT_CSD_FFU_ARG_0] |
|
|
ext_csd[EXT_CSD_FFU_ARG_1] << 8 |
|
|
ext_csd[EXT_CSD_FFU_ARG_2] << 16 |
|
|
ext_csd[EXT_CSD_FFU_ARG_3] << 24;
|
|
|
|
/* prepare multi_cmd to be sent */
|
|
multi_cmd = g_malloc0 (sizeof(struct mmc_ioc_multi_cmd) +
|
|
3 * sizeof(struct mmc_ioc_cmd));
|
|
multi_cmd->num_of_cmds = 3;
|
|
|
|
/* put device into ffu mode */
|
|
multi_cmd->cmds[0].opcode = MMC_SWITCH;
|
|
multi_cmd->cmds[0].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
|
|
(EXT_CSD_MODE_CONFIG << 16) |
|
|
(EXT_CSD_FFU_MODE << 8) |
|
|
EXT_CSD_CMD_SET_NORMAL;
|
|
multi_cmd->cmds[0].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
|
|
multi_cmd->cmds[0].write_flag = 1;
|
|
|
|
/* send image chunk */
|
|
multi_cmd->cmds[1].opcode = MMC_WRITE_BLOCK;
|
|
multi_cmd->cmds[1].blksz = self->sect_size;
|
|
multi_cmd->cmds[1].blocks = 1;
|
|
multi_cmd->cmds[1].arg = arg;
|
|
multi_cmd->cmds[1].flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
|
multi_cmd->cmds[1].write_flag = 1;
|
|
|
|
/* return device into normal mode */
|
|
multi_cmd->cmds[2].opcode = MMC_SWITCH;
|
|
multi_cmd->cmds[2].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
|
|
(EXT_CSD_MODE_CONFIG << 16) |
|
|
(EXT_CSD_NORMAL_MODE << 8) |
|
|
EXT_CSD_CMD_SET_NORMAL;
|
|
multi_cmd->cmds[2].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
|
|
multi_cmd->cmds[2].write_flag = 1;
|
|
|
|
/* build packets */
|
|
chunks = fu_chunk_array_new_from_bytes (fw,
|
|
0x00, /* start addr */
|
|
0x00, /* page_sz */
|
|
self->sect_size);
|
|
while (sect_done == 0) {
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index (chunks, i);
|
|
|
|
mmc_ioc_cmd_set_data (multi_cmd->cmds[1], fu_chunk_get_data (chk));
|
|
|
|
if (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self),
|
|
MMC_IOC_MULTI_CMD, (guint8 *) multi_cmd,
|
|
NULL, error)) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_prefix_error (error, "multi-cmd failed: ");
|
|
/* multi-cmd ioctl failed before exiting from ffu mode */
|
|
if (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self),
|
|
MMC_IOC_CMD, (guint8 *) &multi_cmd->cmds[2],
|
|
NULL, &error_local)) {
|
|
g_prefix_error (error, "%s: ",
|
|
error_local->message);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
if (!fu_emmc_read_extcsd (self, ext_csd, sizeof (ext_csd), error))
|
|
return FALSE;
|
|
|
|
/* if we need to restart the download */
|
|
sect_done = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_0] |
|
|
ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_1] << 8 |
|
|
ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_2] << 16 |
|
|
ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_3] << 24;
|
|
if (sect_done == 0) {
|
|
if (failure_cnt >= 3) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"programming failed");
|
|
return FALSE;
|
|
}
|
|
failure_cnt++;
|
|
g_debug ("programming failed: retrying (%u)", failure_cnt);
|
|
break;
|
|
}
|
|
|
|
/* update progress */
|
|
fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len - 1);
|
|
}
|
|
}
|
|
|
|
/* sanity check */
|
|
total_done = (gsize) sect_done * (gsize) self->sect_size;
|
|
if (total_done != fw_size) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"firmware size and number of sectors written "
|
|
"mismatch (%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT "):",
|
|
total_done, fw_size);
|
|
return FALSE;
|
|
}
|
|
|
|
/* check mode operation for ffu install*/
|
|
if (!ext_csd[EXT_CSD_FFU_FEATURES]) {
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT);
|
|
} else {
|
|
/* re-enter ffu mode and install the firmware */
|
|
multi_cmd->num_of_cmds = 2;
|
|
|
|
/* set ext_csd to install mode */
|
|
multi_cmd->cmds[1].opcode = MMC_SWITCH;
|
|
multi_cmd->cmds[1].blksz = 0;
|
|
multi_cmd->cmds[1].blocks = 0;
|
|
multi_cmd->cmds[1].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
|
|
(EXT_CSD_MODE_OPERATION_CODES << 16) |
|
|
(EXT_CSD_FFU_INSTALL << 8) |
|
|
EXT_CSD_CMD_SET_NORMAL;
|
|
multi_cmd->cmds[1].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
|
|
multi_cmd->cmds[1].write_flag = 1;
|
|
|
|
/* send ioctl with multi-cmd */
|
|
if (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self),
|
|
MMC_IOC_MULTI_CMD, (guint8 *) multi_cmd,
|
|
NULL, error)) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
/* In case multi-cmd ioctl failed before exiting from ffu mode */
|
|
g_prefix_error (error, "multi-cmd failed setting install mode: ");
|
|
if (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self),
|
|
MMC_IOC_CMD, (guint8 *) &multi_cmd->cmds[2],
|
|
NULL, &error_local)) {
|
|
g_prefix_error (error, "%s: ",
|
|
error_local->message);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* return status */
|
|
if (!fu_emmc_read_extcsd (self, ext_csd, sizeof (ext_csd), error))
|
|
return FALSE;
|
|
if (ext_csd[EXT_CSD_FFU_STATUS] != 0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"FFU install failed: %d",
|
|
ext_csd[EXT_CSD_FFU_STATUS]);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_emmc_device_init (FuEmmcDevice *self)
|
|
{
|
|
fu_device_add_protocol (FU_DEVICE (self), "org.jedec.mmc");
|
|
fu_device_add_icon (FU_DEVICE (self), "media-memory");
|
|
}
|
|
|
|
static void
|
|
fu_emmc_device_finalize (GObject *object)
|
|
{
|
|
G_OBJECT_CLASS (fu_emmc_device_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
fu_emmc_device_class_init (FuEmmcDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
object_class->finalize = fu_emmc_device_finalize;
|
|
klass_device->setup = fu_emmc_device_setup;
|
|
klass_device->to_string = fu_emmc_device_to_string;
|
|
klass_device->prepare_firmware = fu_emmc_device_prepare_firmware;
|
|
klass_device->probe = fu_emmc_device_probe;
|
|
klass_device->write_firmware = fu_emmc_device_write_firmware;
|
|
}
|