mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-25 22:40:50 +00:00

It's actually quite hard to build a front-end for fwupd at the moment as you're never sure when the progress bar is going to zip back to 0% and start all over again. Some plugins go 0..100% for write, others go 0..100% for erase, then again for write, then *again* for verify. By creating a helper object we can easily split up the progress of the specific task, e.g. write_firmware(). We can encode at the plugin level "the erase takes 50% of the time, the write takes 40% and the read takes 10%". This means we can have a progressbar which goes up just once at a consistent speed.
568 lines
17 KiB
C
568 lines
17 KiB
C
/*
|
|
* Copyright (C) 2019 Mario Limonciello <mario.limonciello@dell.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include <linux/mmc/ioctl.h>
|
|
#include <sys/ioctl.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;
|
|
g_autoptr(GRegex) dev_regex = 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;
|
|
}
|
|
|
|
/* ignore *rpmb and *boot* mmc block devices */
|
|
dev_regex = g_regex_new("mmcblk\\d$", 0, 0, NULL);
|
|
tmp = g_udev_device_get_name(udev_device);
|
|
if (tmp == NULL) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"device has no name");
|
|
return FALSE;
|
|
}
|
|
if (!g_regex_match(dev_regex, tmp, 0, NULL)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"is not raw mmc block device, devname=%s",
|
|
g_udev_device_get_name(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;
|
|
}
|
|
|
|
/* name */
|
|
tmp = g_udev_device_get_sysfs_attr(udev_parent, "name");
|
|
if (tmp == NULL) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"%s does not have 'name' sysattr",
|
|
fu_device_get_name(device));
|
|
return FALSE;
|
|
}
|
|
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,
|
|
FuProgress *progress,
|
|
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;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5); /* ffu */
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 45);
|
|
|
|
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;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* 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_progress_set_percentage_full(fu_progress_get_child(progress),
|
|
(gsize)i + 1,
|
|
(gsize)chunks->len);
|
|
}
|
|
}
|
|
fu_progress_step_done(progress);
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
fu_progress_step_done(progress);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_emmc_device_set_progress(FuDevice *self, FuProgress *progress)
|
|
{
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* detach */
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98); /* write */
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* attach */
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2); /* reload */
|
|
}
|
|
|
|
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;
|
|
klass_device->set_progress = fu_emmc_device_set_progress;
|
|
}
|