mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-14 05:57:41 +00:00
Add a new plugin to handle MTD devices
This commit is contained in:
parent
54f1557a81
commit
36d7077f65
@ -38,6 +38,7 @@ meson .. \
|
||||
-Dsystemd=false \
|
||||
-Dplugin_emmc=false \
|
||||
-Dplugin_amt=false \
|
||||
-Dplugin_mtd=false \
|
||||
-Dintrospection=false \
|
||||
-Dplugin_thunderbolt=false \
|
||||
-Dplugin_synaptics_mst=false \
|
||||
|
@ -22,6 +22,7 @@ meson .. \
|
||||
-Dplugin_dell=false \
|
||||
-Dplugin_modem_manager=false \
|
||||
-Dplugin_msr=false \
|
||||
-Dplugin_mtd=false \
|
||||
-Dplugin_redfish=false \
|
||||
-Dintrospection=false \
|
||||
-Ddocs=none \
|
||||
|
@ -450,6 +450,7 @@ done
|
||||
%if 0%{?have_msr}
|
||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_msr.so
|
||||
%endif
|
||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_mtd.so
|
||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_nitrokey.so
|
||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_nvme.so
|
||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_optionrom.so
|
||||
|
@ -1104,7 +1104,7 @@ fu_udev_device_set_physical_id(FuUdevDevice *self, const gchar *subsystems, GErr
|
||||
physical_id = g_strdup_printf("PCI_SLOT_NAME=%s", tmp);
|
||||
} else if (g_strcmp0(subsystem, "usb") == 0 || g_strcmp0(subsystem, "mmc") == 0 ||
|
||||
g_strcmp0(subsystem, "i2c") == 0 || g_strcmp0(subsystem, "platform") == 0 ||
|
||||
g_strcmp0(subsystem, "scsi") == 0) {
|
||||
g_strcmp0(subsystem, "scsi") == 0 || g_strcmp0(subsystem, "mtd") == 0) {
|
||||
tmp = g_udev_device_get_property(udev_device, "DEVPATH");
|
||||
if (tmp == NULL) {
|
||||
g_set_error_literal(error,
|
||||
|
@ -314,6 +314,9 @@ endif
|
||||
if cc.has_header('linux/ethtool.h')
|
||||
conf.set('HAVE_ETHTOOL_H', '1')
|
||||
endif
|
||||
if cc.has_header('mtd/mtd-user.h')
|
||||
conf.set('HAVE_MTD_USER_H', '1')
|
||||
endif
|
||||
if cc.has_header('linux/hidraw.h')
|
||||
conf.set('HAVE_HIDRAW_H', '1')
|
||||
endif
|
||||
|
@ -32,6 +32,7 @@ option('plugin_uefi_pk', type : 'boolean', value : true, description : 'enable U
|
||||
option('plugin_nvme', type : 'boolean', value : true, description : 'enable NVMe support')
|
||||
option('plugin_modem_manager', type : 'boolean', value : false, description : 'enable ModemManager support')
|
||||
option('plugin_msr', type : 'boolean', value : true, description : 'enable MSR support')
|
||||
option('plugin_mtd', type : 'boolean', value : true, description : 'enable MTD support')
|
||||
option('plugin_flashrom', type : 'boolean', value : false, description : 'enable libflashrom support')
|
||||
option('plugin_platform_integrity', type : 'boolean', value : false, description : 'enable platform integrity support')
|
||||
option('plugin_intel_spi', type : 'boolean', value : false, description : 'enable Intel SPI support')
|
||||
|
@ -40,6 +40,7 @@ subdir('logitech-hidpp')
|
||||
subdir('logitech-bulkcontroller')
|
||||
subdir('modem-manager')
|
||||
subdir('msr')
|
||||
subdir('mtd')
|
||||
subdir('nitrokey')
|
||||
subdir('nvme')
|
||||
subdir('optionrom')
|
||||
|
30
plugins/mtd/README.md
Normal file
30
plugins/mtd/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# MTD
|
||||
|
||||
## Introduction
|
||||
|
||||
The Memory Technology Device (MTD) interface is a way of abstracting flash devices as if they were
|
||||
normal block devices.
|
||||
|
||||
See <http://www.linux-mtd.infradead.org/doc/general.html> for more details.
|
||||
|
||||
This plugin supports the following protocol ID:
|
||||
|
||||
* org.infradead.mtd
|
||||
|
||||
## GUID Generation
|
||||
|
||||
These devices use custom DeviceInstanceId values built from the device `NAME`, e.g.
|
||||
|
||||
* `MTD\NAME_Factory`
|
||||
|
||||
## Update Behavior
|
||||
|
||||
The MTD device is erased in chunks, written and then read back to verify.
|
||||
|
||||
## Vendor ID Security
|
||||
|
||||
The vendor ID is set from the system vendor, for example `DMI:LENOVO`
|
||||
|
||||
## External Interface Access
|
||||
|
||||
This plugin requires read/write access to `/dev/mtd`.
|
273
plugins/mtd/fu-mtd-device.c
Normal file
273
plugins/mtd/fu-mtd-device.c
Normal file
@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_MTD_USER_H
|
||||
#include <mtd/mtd-user.h>
|
||||
#endif
|
||||
|
||||
#include "fu-mtd-device.h"
|
||||
|
||||
struct _FuMtdDevice {
|
||||
FuUdevDevice parent_instance;
|
||||
guint64 erasesize;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(FuMtdDevice, fu_mtd_device, FU_TYPE_UDEV_DEVICE)
|
||||
|
||||
static void
|
||||
fu_mtd_device_to_string(FuDevice *device, guint idt, GString *str)
|
||||
{
|
||||
FuMtdDevice *self = FU_MTD_DEVICE(device);
|
||||
if (self->erasesize > 0)
|
||||
fu_common_string_append_kx(str, idt, "EraseSize", self->erasesize);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_mtd_device_probe(FuDevice *device, GError **error)
|
||||
{
|
||||
FuMtdDevice *self = FU_MTD_DEVICE(device);
|
||||
const gchar *name;
|
||||
guint64 flags = 0;
|
||||
guint64 size = 0;
|
||||
|
||||
/* FuUdevDevice->probe */
|
||||
if (!FU_DEVICE_CLASS(fu_mtd_device_parent_class)->probe(device, error))
|
||||
return FALSE;
|
||||
|
||||
/* set physical ID */
|
||||
if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "mtd", error))
|
||||
return FALSE;
|
||||
|
||||
/* get name */
|
||||
name = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "name", NULL);
|
||||
if (name != NULL) {
|
||||
g_autofree gchar *devid = NULL;
|
||||
g_autofree gchar *name_safe = g_strdup(name);
|
||||
g_strdelimit(name_safe, " /\\\"", '-');
|
||||
devid = g_strdup_printf("MTD\\NAME_%s", name_safe);
|
||||
fu_device_add_instance_id(FU_DEVICE(self), devid);
|
||||
fu_device_set_name(FU_DEVICE(self), name);
|
||||
}
|
||||
|
||||
/* get properties about the device */
|
||||
if (!fu_udev_device_get_sysfs_attr_uint64(FU_UDEV_DEVICE(device), "size", &size, error))
|
||||
return FALSE;
|
||||
fu_device_set_firmware_size_max(device, size);
|
||||
if (!fu_udev_device_get_sysfs_attr_uint64(FU_UDEV_DEVICE(device), "flags", &flags, error))
|
||||
return FALSE;
|
||||
#ifdef HAVE_MTD_USER_H
|
||||
if ((flags & MTD_NO_ERASE) == 0) {
|
||||
if (!fu_udev_device_get_sysfs_attr_uint64(FU_UDEV_DEVICE(device),
|
||||
"erasesize",
|
||||
&self->erasesize,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
if (flags & MTD_WRITEABLE)
|
||||
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
#endif
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_mtd_device_erase(FuMtdDevice *self, GBytes *fw, FuProgress *progress, GError **error)
|
||||
{
|
||||
#ifdef HAVE_MTD_USER_H
|
||||
g_autoptr(GPtrArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, self->erasesize);
|
||||
|
||||
/* progress */
|
||||
fu_progress_set_id(progress, G_STRLOC);
|
||||
fu_progress_set_steps(progress, chunks->len);
|
||||
|
||||
/* erase each chunk */
|
||||
for (guint i = 0; i < chunks->len; i++) {
|
||||
FuChunk *chk = g_ptr_array_index(chunks, i);
|
||||
struct erase_info_user erase = {
|
||||
.start = fu_chunk_get_address(chk),
|
||||
.length = fu_chunk_get_data_sz(chk),
|
||||
};
|
||||
if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), 2, (guint8 *)&erase, NULL, error)) {
|
||||
g_prefix_error(error, "failed to erase @0x%x: ", (guint)erase.start);
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
#else
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"Not supported as mtd-user.h is unavailable");
|
||||
return FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_mtd_device_write(FuMtdDevice *self, GPtrArray *chunks, FuProgress *progress, GError **error)
|
||||
{
|
||||
/* progress */
|
||||
fu_progress_set_id(progress, G_STRLOC);
|
||||
fu_progress_set_steps(progress, chunks->len);
|
||||
|
||||
/* rewind */
|
||||
if (!fu_udev_device_seek(FU_UDEV_DEVICE(self), 0x0, error)) {
|
||||
g_prefix_error(error, "failed to rewind: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* write each chunk */
|
||||
for (guint i = 0; i < chunks->len; i++) {
|
||||
FuChunk *chk = g_ptr_array_index(chunks, i);
|
||||
if (!fu_udev_device_pwrite_full(FU_UDEV_DEVICE(self),
|
||||
0x0,
|
||||
fu_chunk_get_data(chk),
|
||||
fu_chunk_get_data_sz(chk),
|
||||
error)) {
|
||||
g_prefix_error(error,
|
||||
"failed to write @0x%x: ",
|
||||
(guint)fu_chunk_get_address(chk));
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_mtd_device_verify(FuMtdDevice *self, GPtrArray *chunks, FuProgress *progress, GError **error)
|
||||
{
|
||||
/* progress */
|
||||
fu_progress_set_id(progress, G_STRLOC);
|
||||
fu_progress_set_steps(progress, chunks->len);
|
||||
|
||||
/* verify each chunk */
|
||||
for (guint i = 0; i < chunks->len; i++) {
|
||||
FuChunk *chk = g_ptr_array_index(chunks, i);
|
||||
g_autofree guint8 *buf = g_malloc0(fu_chunk_get_data_sz(chk));
|
||||
g_autoptr(GBytes) blob1 = fu_chunk_get_bytes(chk);
|
||||
g_autoptr(GBytes) blob2 = NULL;
|
||||
|
||||
if (!fu_udev_device_pread_full(FU_UDEV_DEVICE(self),
|
||||
0x0,
|
||||
buf,
|
||||
fu_chunk_get_data_sz(chk),
|
||||
error)) {
|
||||
g_prefix_error(error,
|
||||
"failed to read @0x%x: ",
|
||||
(guint)fu_chunk_get_address(chk));
|
||||
return FALSE;
|
||||
}
|
||||
blob2 = g_bytes_new_static(buf, fu_chunk_get_data_sz(chk));
|
||||
if (!fu_common_bytes_compare(blob1, blob2, error)) {
|
||||
g_prefix_error(error,
|
||||
"failed to verify @0x%x: ",
|
||||
(guint)fu_chunk_get_address(chk));
|
||||
return FALSE;
|
||||
}
|
||||
fu_progress_step_done(progress);
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_mtd_device_write_verify(FuMtdDevice *self, GBytes *fw, FuProgress *progress, GError **error)
|
||||
{
|
||||
g_autoptr(GPtrArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, 10 * 1024);
|
||||
|
||||
/* 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_WRITE, 50);
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 50);
|
||||
|
||||
/* write */
|
||||
if (!fu_mtd_device_write(self, chunks, fu_progress_get_child(progress), error))
|
||||
return FALSE;
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
/* verify */
|
||||
if (!fu_mtd_device_verify(self, chunks, fu_progress_get_child(progress), error))
|
||||
return FALSE;
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_mtd_device_write_firmware(FuDevice *device,
|
||||
FuFirmware *firmware,
|
||||
FuProgress *progress,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuMtdDevice *self = FU_MTD_DEVICE(device);
|
||||
g_autoptr(GBytes) fw = NULL;
|
||||
|
||||
/* get data to write */
|
||||
fw = fu_firmware_get_bytes(firmware, error);
|
||||
if (fw == NULL)
|
||||
return FALSE;
|
||||
if (g_bytes_get_size(fw) > fu_device_get_firmware_size_max(device)) {
|
||||
g_set_error(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"firmware too large, got 0x%x, expected <= 0x%x",
|
||||
(guint)g_bytes_get_size(fw),
|
||||
(guint)fu_device_get_firmware_size_max(device));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* just one step required */
|
||||
if (self->erasesize == 0)
|
||||
return fu_mtd_device_write_verify(self, fw, progress, error);
|
||||
|
||||
/* 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_ERASE, 50);
|
||||
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50);
|
||||
|
||||
/* erase */
|
||||
if (!fu_mtd_device_erase(self, fw, fu_progress_get_child(progress), error))
|
||||
return FALSE;
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
/* write */
|
||||
if (!fu_mtd_device_write_verify(self, fw, fu_progress_get_child(progress), error))
|
||||
return FALSE;
|
||||
fu_progress_step_done(progress);
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_mtd_device_init(FuMtdDevice *self)
|
||||
{
|
||||
fu_udev_device_set_flags(FU_UDEV_DEVICE(self),
|
||||
FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE |
|
||||
FU_UDEV_DEVICE_FLAG_OPEN_SYNC);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_mtd_device_class_init(FuMtdDeviceClass *klass)
|
||||
{
|
||||
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
||||
klass_device->probe = fu_mtd_device_probe;
|
||||
klass_device->to_string = fu_mtd_device_to_string;
|
||||
klass_device->write_firmware = fu_mtd_device_write_firmware;
|
||||
}
|
12
plugins/mtd/fu-mtd-device.h
Normal file
12
plugins/mtd/fu-mtd-device.h
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#define FU_TYPE_MTD_DEVICE (fu_mtd_device_get_type())
|
||||
G_DECLARE_FINAL_TYPE(FuMtdDevice, fu_mtd_device, FU, MTD_DEVICE, FuUdevDevice)
|
54
plugins/mtd/fu-plugin-mtd.c
Normal file
54
plugins/mtd/fu-plugin-mtd.c
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#include "fu-mtd-device.h"
|
||||
|
||||
void
|
||||
fu_plugin_init(FuPlugin *plugin)
|
||||
{
|
||||
fu_plugin_add_udev_subsystem(plugin, "mtd");
|
||||
fu_plugin_set_build_hash(plugin, FU_BUILD_HASH);
|
||||
fu_plugin_add_device_gtype(plugin, FU_TYPE_MTD_DEVICE);
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_plugin_startup(FuPlugin *plugin, GError **error)
|
||||
{
|
||||
#ifndef HAVE_MTD_USER_H
|
||||
g_set_error_literal(error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"Not compiled with mtd support");
|
||||
return FALSE;
|
||||
#endif
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_plugin_device_created(FuPlugin *plugin, FuDevice *dev, GError **error)
|
||||
{
|
||||
FuContext *ctx = fu_plugin_get_context(plugin);
|
||||
const gchar *vendor;
|
||||
|
||||
fu_device_set_summary(dev, "Memory Technology Device");
|
||||
fu_device_add_protocol(dev, "org.infradead.mtd");
|
||||
fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_INTERNAL);
|
||||
fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT);
|
||||
fu_device_add_icon(dev, "drive-harddisk-solidstate");
|
||||
|
||||
/* set vendor ID as the BIOS vendor */
|
||||
vendor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_MANUFACTURER);
|
||||
if (vendor != NULL) {
|
||||
g_autofree gchar *vendor_id = g_strdup_printf("DMI:%s", vendor);
|
||||
fu_device_add_vendor_id(dev, vendor_id);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
32
plugins/mtd/meson.build
Normal file
32
plugins/mtd/meson.build
Normal file
@ -0,0 +1,32 @@
|
||||
if get_option('plugin_mtd')
|
||||
cargs = ['-DG_LOG_DOMAIN="FuPluginMtd"']
|
||||
|
||||
install_data([
|
||||
'mtd.quirk',
|
||||
],
|
||||
install_dir: join_paths(datadir, 'fwupd', 'quirks.d')
|
||||
)
|
||||
|
||||
shared_module('fu_plugin_mtd',
|
||||
fu_hash,
|
||||
sources : [
|
||||
'fu-plugin-mtd.c',
|
||||
'fu-mtd-device.c',
|
||||
],
|
||||
include_directories : [
|
||||
root_incdir,
|
||||
fwupd_incdir,
|
||||
fwupdplugin_incdir,
|
||||
],
|
||||
install : true,
|
||||
install_dir: plugin_dir,
|
||||
link_with : [
|
||||
fwupd,
|
||||
fwupdplugin,
|
||||
],
|
||||
c_args : cargs,
|
||||
dependencies : [
|
||||
plugin_deps,
|
||||
],
|
||||
)
|
||||
endif
|
2
plugins/mtd/mtd.quirk
Normal file
2
plugins/mtd/mtd.quirk
Normal file
@ -0,0 +1,2 @@
|
||||
[MTD]
|
||||
Plugin = mtd
|
Loading…
Reference in New Issue
Block a user