mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-03 13:41:12 +00:00

Now incorporate is fixed to copy across the properties we need in the superclass, we don't need to do the subclass ->probe(). Note, we still need to do the subclassed ->probe() when using FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT or when looking at properties on the parent device. This also removes the spurious 'already set GType to FuVliUsbhubDevice, ignoring FuVliUsbhubDevice' messages when running the daemon.
323 lines
9.1 KiB
C
323 lines
9.1 KiB
C
/*
|
|
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <scsi/sg.h>
|
|
|
|
#include "fu-scsi-device.h"
|
|
|
|
struct _FuScsiDevice {
|
|
FuUdevDevice parent_instance;
|
|
guint64 ffu_timeout;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuScsiDevice, fu_scsi_device, FU_TYPE_UDEV_DEVICE)
|
|
|
|
#define BUFFER_VENDOR_MODE 0x01
|
|
#define BUFFER_DUFS_MODE 0x02
|
|
#define BUFFER_FFU_MODE 0x0E
|
|
#define BUFFER_EHS_MODE 0x1C
|
|
|
|
#define SENSE_BUFF_LEN 18
|
|
#define WRITE_BUF_CMDLEN 10
|
|
#define READ_BUF_CMDLEN 10
|
|
#define WRITE_BUFFER_CMD 0x3B
|
|
#define READ_BUFFER_CMD 0x3C
|
|
|
|
#define FU_SCSI_DEVICE_IOCTL_TIMEOUT 5000 /* ms */
|
|
|
|
static void
|
|
fu_scsi_device_to_string(FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuScsiDevice *self = FU_SCSI_DEVICE(device);
|
|
FU_DEVICE_CLASS(fu_scsi_device_parent_class)->to_string(device, idt, str);
|
|
fu_string_append_kx(str, idt, "FfuTimeout", self->ffu_timeout);
|
|
}
|
|
|
|
static gboolean
|
|
fu_scsi_device_probe(FuDevice *device, GError **error)
|
|
{
|
|
FuScsiDevice *self = FU_SCSI_DEVICE(device);
|
|
GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device));
|
|
guint64 removable = 0;
|
|
g_autofree gchar *vendor_id = NULL;
|
|
g_autoptr(FuUdevDevice) ufshci_parent = NULL;
|
|
const gchar *subsystem_parents[] = {"pci", "platform", NULL};
|
|
|
|
/* check is valid */
|
|
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;
|
|
}
|
|
if (!g_udev_device_get_property_as_boolean(udev_device, "ID_SCSI")) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"has no ID_SCSI");
|
|
return FALSE;
|
|
}
|
|
|
|
/* vendor sanity */
|
|
if (g_strcmp0(fu_device_get_vendor(device), "ATA") == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"no assigned vendor");
|
|
return FALSE;
|
|
}
|
|
|
|
vendor_id = g_strdup_printf("SCSI:%s", fu_device_get_vendor(device));
|
|
fu_device_add_vendor_id(device, vendor_id);
|
|
|
|
/* the ufshci controller could really be on any bus... search in order of priority */
|
|
for (guint i = 0; subsystem_parents[i] != NULL && ufshci_parent == NULL; i++) {
|
|
ufshci_parent = fu_udev_device_get_parent_with_subsystem(FU_UDEV_DEVICE(device),
|
|
subsystem_parents[i]);
|
|
}
|
|
if (ufshci_parent != NULL) {
|
|
guint64 ufs_features = 0;
|
|
|
|
/* check if this is a UFS device */
|
|
g_debug("found ufshci controller at %s",
|
|
fu_udev_device_get_sysfs_path(ufshci_parent));
|
|
if (fu_udev_device_get_sysfs_attr_uint64(ufshci_parent,
|
|
"device_descriptor/ufs_features",
|
|
&ufs_features,
|
|
NULL)) {
|
|
fu_device_set_summary(device, "UFS device");
|
|
/* least significant bit specifies FFU capability */
|
|
if (ufs_features & 0x1) {
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_add_internal_flag(FU_DEVICE(self),
|
|
FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED);
|
|
fu_device_add_protocol(device, "org.jedec.ufs");
|
|
}
|
|
if (!fu_udev_device_get_sysfs_attr_uint64(ufshci_parent,
|
|
"device_descriptor/ffu_timeout",
|
|
&self->ffu_timeout,
|
|
error)) {
|
|
g_prefix_error(error, "no ffu timeout specified: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* add GUIDs */
|
|
fu_device_add_instance_strsafe(device, "VEN", fu_device_get_vendor(device));
|
|
fu_device_add_instance_strsafe(device, "DEV", fu_device_get_name(device));
|
|
fu_device_add_instance_strsafe(device, "REV", fu_device_get_version(device));
|
|
if (!fu_device_build_instance_id_quirk(device, error, "SCSI", "VEN", NULL))
|
|
return FALSE;
|
|
if (!fu_device_build_instance_id(device, error, "SCSI", "VEN", "DEV", NULL))
|
|
return FALSE;
|
|
if (!fu_device_build_instance_id(device, error, "SCSI", "VEN", "DEV", "REV", NULL))
|
|
return FALSE;
|
|
|
|
/* is internal? */
|
|
if (fu_udev_device_get_sysfs_attr_uint64(FU_UDEV_DEVICE(device),
|
|
"removable",
|
|
&removable,
|
|
NULL)) {
|
|
if (removable == 0x0)
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL);
|
|
}
|
|
|
|
/* set the physical ID */
|
|
return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "scsi:scsi_target", error);
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_scsi_device_prepare_firmware(FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
g_autoptr(FuFirmware) firmware = fu_firmware_new();
|
|
fu_firmware_set_alignment(firmware, FU_FIRMWARE_ALIGNMENT_4K);
|
|
if (!fu_firmware_parse(firmware, fw, flags, error))
|
|
return NULL;
|
|
return g_steal_pointer(&firmware);
|
|
}
|
|
|
|
static const gchar *
|
|
fu_scsi_device_sense_key_to_string(guint8 key)
|
|
{
|
|
if (key == 0x00)
|
|
return "no-sense";
|
|
if (key == 0x01)
|
|
return "recovered-error";
|
|
if (key == 0x02)
|
|
return "not-ready";
|
|
if (key == 0x03)
|
|
return "medium-error";
|
|
if (key == 0x04)
|
|
return "hardware-error";
|
|
if (key == 0x05)
|
|
return "illegal-request";
|
|
if (key == 0x06)
|
|
return "unit-attention";
|
|
if (key == 0x07)
|
|
return "data-protect";
|
|
if (key == 0x08)
|
|
return "blank-check";
|
|
if (key == 0x09)
|
|
return "vendor-specific";
|
|
if (key == 0x0A)
|
|
return "copy-aborted";
|
|
if (key == 0x0B)
|
|
return "aborted-command";
|
|
if (key == 0x0C)
|
|
return "equal";
|
|
if (key == 0x0D)
|
|
return "volume-overflow";
|
|
if (key == 0x0E)
|
|
return "miscompare";
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
fu_scsi_device_send_scsi_cmd_v3(FuScsiDevice *self,
|
|
const guint8 *cdb,
|
|
guint8 cdbsz,
|
|
const guint8 *buf,
|
|
guint32 bufsz,
|
|
gint dir,
|
|
GError **error)
|
|
{
|
|
guint8 sense_buffer[SENSE_BUFF_LEN] = {0};
|
|
struct sg_io_hdr io_hdr = {.interface_id = 'S'};
|
|
|
|
io_hdr.cmd_len = cdbsz;
|
|
io_hdr.mx_sb_len = sizeof(sense_buffer);
|
|
io_hdr.dxfer_direction = dir;
|
|
io_hdr.dxfer_len = bufsz;
|
|
io_hdr.dxferp = (guint8 *)buf;
|
|
/* pointer to command buf */
|
|
io_hdr.cmdp = (guint8 *)cdb;
|
|
io_hdr.sbp = sense_buffer;
|
|
io_hdr.timeout = 60000; /* ms */
|
|
|
|
if (g_getenv("FWUPD_SCSI_VERBOSE") != NULL)
|
|
g_debug("cmd=0x%x len=0x%x", cdb[0], (guint)bufsz);
|
|
if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self),
|
|
SG_IO,
|
|
(guint8 *)&io_hdr,
|
|
NULL,
|
|
FU_SCSI_DEVICE_IOCTL_TIMEOUT,
|
|
error))
|
|
return FALSE;
|
|
|
|
if (io_hdr.status) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Command fail with status %x, senseKey %s, asc 0x%02x, ascq 0x%02x",
|
|
io_hdr.status,
|
|
fu_scsi_device_sense_key_to_string(sense_buffer[2]),
|
|
sense_buffer[12],
|
|
sense_buffer[13]);
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_scsi_device_write_firmware(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuScsiDevice *self = FU_SCSI_DEVICE(device);
|
|
guint32 chunksz = 0x1000;
|
|
guint32 offset = 0;
|
|
g_autoptr(GBytes) fw = NULL;
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
/* get default image */
|
|
fw = fu_firmware_get_bytes(firmware, error);
|
|
if (fw == NULL)
|
|
return FALSE;
|
|
|
|
/* prepare chunks */
|
|
chunks = fu_chunk_array_new_from_bytes(fw, 0x00, 0x00, chunksz);
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE);
|
|
fu_progress_set_steps(progress, chunks->len);
|
|
|
|
/* write each block */
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index(chunks, i);
|
|
guint8 cdb[WRITE_BUF_CMDLEN] = {WRITE_BUFFER_CMD,
|
|
BUFFER_FFU_MODE,
|
|
0x0 /* buf_id */};
|
|
|
|
fu_memwrite_uint24(cdb + 3, offset, G_BIG_ENDIAN);
|
|
fu_memwrite_uint24(cdb + 6, fu_chunk_get_data_sz(chk), G_BIG_ENDIAN);
|
|
if (!fu_scsi_device_send_scsi_cmd_v3(self,
|
|
cdb,
|
|
sizeof(cdb),
|
|
fu_chunk_get_data(chk),
|
|
fu_chunk_get_data_sz(chk),
|
|
SG_DXFER_TO_DEV,
|
|
error)) {
|
|
g_prefix_error(error,
|
|
"SG_IO WRITE BUFFER data error for v3 chunk 0x%x: ",
|
|
fu_chunk_get_idx(chk));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* chunk done */
|
|
fu_progress_step_done(progress);
|
|
offset += fu_chunk_get_data_sz(chk);
|
|
}
|
|
|
|
/* success! */
|
|
fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_UPDATE_PENDING);
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_scsi_device_set_progress(FuDevice *self, FuProgress *progress)
|
|
{
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99, "write");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload");
|
|
}
|
|
|
|
static void
|
|
fu_scsi_device_init(FuScsiDevice *self)
|
|
{
|
|
fu_device_add_icon(FU_DEVICE(self), "drive-harddisk");
|
|
fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN);
|
|
fu_device_set_summary(FU_DEVICE(self), "SCSI device");
|
|
fu_udev_device_set_flags(FU_UDEV_DEVICE(self),
|
|
FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_SYNC |
|
|
FU_UDEV_DEVICE_FLAG_IOCTL_RETRY);
|
|
}
|
|
|
|
static void
|
|
fu_scsi_device_class_init(FuScsiDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
klass_device->to_string = fu_scsi_device_to_string;
|
|
klass_device->probe = fu_scsi_device_probe;
|
|
klass_device->prepare_firmware = fu_scsi_device_prepare_firmware;
|
|
klass_device->write_firmware = fu_scsi_device_write_firmware;
|
|
klass_device->set_progress = fu_scsi_device_set_progress;
|
|
}
|