mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-13 21:35:02 +00:00
ata: Add a new plugin to upgrade firmware on ATA/ATAPI hardware
Some of the ATA12 fixup code is by Mark Lord, taken from the hdparm project. Fixes: https://github.com/hughsie/fwupd/issues/946
This commit is contained in:
parent
4fef28d0ac
commit
0bf8ee810b
@ -258,6 +258,7 @@ mkdir -p --mode=0700 $RPM_BUILD_ROOT%{_localstatedir}/lib/fwupd/gnupg
|
||||
%dir %{_libdir}/fwupd-plugins-3
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_altos.so
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_amt.so
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_ata.so
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_colorhug.so
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_csr.so
|
||||
%if 0%{?have_dell}
|
||||
|
44
plugins/ata/README.md
Normal file
44
plugins/ata/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
ATA
|
||||
===
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This plugin allows updating ATA/ATAPI storage hardware. Devices are enumerated
|
||||
from the block devices and if ID_ATA_DOWNLOAD_MICROCODE is supported they can
|
||||
be updated with appropriate firmware file.
|
||||
|
||||
Updating ATA devices is more dangerous than other hardware such as DFU or NVMe
|
||||
and should be tested carefully with the help of the drive vendor.
|
||||
|
||||
The device GUID is read from the trimmed model string.
|
||||
|
||||
Firmware Format
|
||||
---------------
|
||||
|
||||
The daemon will decompress the cabinet archive and extract a firmware blob in
|
||||
an unspecified binary file format.
|
||||
|
||||
This plugin supports the following protocol ID:
|
||||
|
||||
* org.t13.ata
|
||||
|
||||
GUID Generation
|
||||
---------------
|
||||
|
||||
These device use the Microsoft DeviceInstanceId values, e.g.
|
||||
|
||||
* `IDE\VENDOR[40]REVISION[8]`
|
||||
* `IDE\0VENDOR[40]`
|
||||
|
||||
See https://docs.microsoft.com/en-us/windows-hardware/drivers/install/identifiers-for-ide-devices
|
||||
for more details.
|
||||
|
||||
Quirk use
|
||||
---------
|
||||
This plugin uses the following plugin-specific quirks:
|
||||
|
||||
| Quirk | Description | Minimum fwupd version |
|
||||
|------------------------|-------------------------------------------|-----------------------|
|
||||
| `AtaTransferBlocks` | Blocks to transfer, or `0xffff` for max | 1.2.4 |
|
||||
| `AtaTransferMode` | The transfer mode, `0x3`, `0x7` or `0xe` | 1.2.4 |
|
1
plugins/ata/ata.quirk
Normal file
1
plugins/ata/ata.quirk
Normal file
@ -0,0 +1 @@
|
||||
|
617
plugins/ata/fu-ata-device.c
Normal file
617
plugins/ata/fu-ata-device.c
Normal file
@ -0,0 +1,617 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <scsi/sg.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#include "fu-ata-device.h"
|
||||
#include "fu-chunk.h"
|
||||
|
||||
#define FU_ATA_IDENTIFY_SIZE 512 /* bytes */
|
||||
#define FU_ATA_BLOCK_SIZE 512 /* bytes */
|
||||
|
||||
struct ata_tf {
|
||||
guint8 dev;
|
||||
guint8 command;
|
||||
guint8 error;
|
||||
guint8 status;
|
||||
guint8 feat;
|
||||
guint8 nsect;
|
||||
guint8 lbal;
|
||||
guint8 lbam;
|
||||
guint8 lbah;
|
||||
};
|
||||
|
||||
#define ATA_USING_LBA (1 << 6)
|
||||
#define ATA_STAT_DRQ (1 << 3)
|
||||
#define ATA_STAT_ERR (1 << 0)
|
||||
|
||||
#define ATA_OP_IDENTIFY 0xec
|
||||
#define ATA_OP_DOWNLOAD_MICROCODE 0x92
|
||||
|
||||
#define SG_CHECK_CONDITION 0x02
|
||||
#define SG_DRIVER_SENSE 0x08
|
||||
|
||||
#define SG_ATA_12 0xa1
|
||||
#define SG_ATA_12_LEN 12
|
||||
|
||||
#define SG_ATA_PROTO_NON_DATA (3 << 1)
|
||||
#define SG_ATA_PROTO_PIO_IN (4 << 1)
|
||||
#define SG_ATA_PROTO_PIO_OUT (5 << 1)
|
||||
|
||||
enum {
|
||||
SG_CDB2_TLEN_NODATA = 0 << 0,
|
||||
SG_CDB2_TLEN_FEAT = 1 << 0,
|
||||
SG_CDB2_TLEN_NSECT = 2 << 0,
|
||||
|
||||
SG_CDB2_TLEN_BYTES = 0 << 2,
|
||||
SG_CDB2_TLEN_SECTORS = 1 << 2,
|
||||
|
||||
SG_CDB2_TDIR_TO_DEV = 0 << 3,
|
||||
SG_CDB2_TDIR_FROM_DEV = 1 << 3,
|
||||
|
||||
SG_CDB2_CHECK_COND = 1 << 5,
|
||||
};
|
||||
|
||||
struct _FuAtaDevice {
|
||||
FuUdevDevice parent_instance;
|
||||
guint pci_depth;
|
||||
gint fd;
|
||||
guint16 transfer_blocks;
|
||||
guint8 transfer_mode;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (FuAtaDevice, fu_ata_device, FU_TYPE_UDEV_DEVICE)
|
||||
|
||||
#ifndef HAVE_GUDEV_232
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-function"
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
guint8
|
||||
fu_ata_device_get_transfer_mode (FuAtaDevice *self)
|
||||
{
|
||||
return self->transfer_mode;
|
||||
}
|
||||
|
||||
guint16
|
||||
fu_ata_device_get_transfer_blocks (FuAtaDevice *self)
|
||||
{
|
||||
return self->transfer_blocks;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
fu_ata_device_get_string (const guint16 *buf, guint start, guint end)
|
||||
{
|
||||
g_autoptr(GString) str = g_string_new (NULL);
|
||||
for (guint i = start; i <= end; i++) {
|
||||
g_string_append_c (str, (gchar) (buf[i] >> 8));
|
||||
g_string_append_c (str, (gchar) (buf[i] & 0xff));
|
||||
}
|
||||
|
||||
/* remove whitespace before returning */
|
||||
if (str->len > 0) {
|
||||
g_strchomp (str->str);
|
||||
if (str->str[0] == '\0')
|
||||
return NULL;
|
||||
}
|
||||
return g_string_free (g_steal_pointer (&str), FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_ata_device_to_string (FuDevice *device, GString *str)
|
||||
{
|
||||
FuAtaDevice *self = FU_ATA_DEVICE (device);
|
||||
g_string_append (str, " FuAtaDevice:\n");
|
||||
g_string_append_printf (str, " fd:\t\t\t%i\n", self->fd);
|
||||
g_string_append_printf (str, " transfer-mode:\t0x%x\n", (guint) self->transfer_mode);
|
||||
g_string_append_printf (str, " transfer-size:\t0x%x\n", (guint) self->transfer_blocks);
|
||||
g_string_append_printf (str, " pci-depth:\t\t%u\n", self->pci_depth);
|
||||
}
|
||||
|
||||
/* https://docs.microsoft.com/en-us/windows-hardware/drivers/install/identifiers-for-ide-devices */
|
||||
static gchar *
|
||||
fu_ata_device_pad_string_for_id (const gchar *name)
|
||||
{
|
||||
GString *str = g_string_new (name);
|
||||
fu_common_string_replace (str, " ", "_");
|
||||
for (guint i = str->len; i < 40; i++)
|
||||
g_string_append_c (str, '_');
|
||||
return g_string_free (str, FALSE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_ata_device_parse_id (FuAtaDevice *self, const guint8 *buf, gsize sz, GError **error)
|
||||
{
|
||||
FuDevice *device = FU_DEVICE (self);
|
||||
guint16 xfer_min = 1;
|
||||
guint16 xfer_max = 0xffff;
|
||||
guint16 id[FU_ATA_IDENTIFY_SIZE/2];
|
||||
g_autofree gchar *name_pad = NULL;
|
||||
g_autofree gchar *sku = NULL;
|
||||
|
||||
/* check size */
|
||||
if (sz != FU_ATA_IDENTIFY_SIZE) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"ID incorrect size, got 0x%02x",
|
||||
(guint) sz);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* read LE buffer */
|
||||
for (guint i = 0; i < sz / 2; i++)
|
||||
id[i] = fu_common_read_uint16 (buf + (i * 2), G_LITTLE_ENDIAN);
|
||||
|
||||
/* verify drive correctly supports DOWNLOAD_MICROCODE */
|
||||
if (!(id[83] & 1 && id[86] & 1)) {
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"DOWNLOAD_MICROCODE not supported by device");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* fallback to a sane default */
|
||||
if (self->transfer_mode == 0x0) {
|
||||
if ((id[119] & 0x10) && (id[120] & 0x10))
|
||||
self->transfer_mode = 0x3;
|
||||
else
|
||||
self->transfer_mode = 0x7;
|
||||
}
|
||||
|
||||
/* the newer, segmented transfer mode */
|
||||
if (self->transfer_mode == 0x3 || self->transfer_mode == 0xe) {
|
||||
xfer_min = id[234];
|
||||
if (xfer_min == 0x0 || xfer_min == 0xffff)
|
||||
xfer_min = 1;
|
||||
xfer_max = id[235];
|
||||
if (xfer_max == 0x0 || xfer_max == 0xffff)
|
||||
xfer_max = xfer_min;
|
||||
}
|
||||
|
||||
/* fall back to a sane block size */
|
||||
if (self->transfer_blocks == 0x0)
|
||||
self->transfer_blocks = xfer_min;
|
||||
else if (self->transfer_blocks == 0xffff)
|
||||
self->transfer_blocks = xfer_max;
|
||||
|
||||
/* get values in case the kernel didn't */
|
||||
if (fu_device_get_serial (device) == NULL) {
|
||||
g_autofree gchar *tmp = NULL;
|
||||
tmp = fu_ata_device_get_string (id, 10, 19);
|
||||
fu_device_set_serial (device, tmp);
|
||||
}
|
||||
if (fu_device_get_name (device) == NULL) {
|
||||
g_autofree gchar *tmp = NULL;
|
||||
tmp = fu_ata_device_get_string (id, 27, 46);
|
||||
fu_device_set_name (device, tmp);
|
||||
}
|
||||
if (fu_device_get_version (device) == NULL) {
|
||||
g_autofree gchar *tmp = NULL;
|
||||
tmp = fu_ata_device_get_string (id, 23, 26);
|
||||
fu_device_set_version (device, tmp);
|
||||
}
|
||||
|
||||
/* 8 byte additional product identifier == SKU? */
|
||||
sku = fu_ata_device_get_string (id, 170, 173);
|
||||
if (sku != NULL)
|
||||
g_debug ("SKU=%s", sku);
|
||||
|
||||
/* add extra GUIDs */
|
||||
name_pad = fu_ata_device_pad_string_for_id (fu_device_get_name (device));
|
||||
if (name_pad != NULL &&
|
||||
fu_device_get_version (device) != NULL) {
|
||||
g_autofree gchar *tmp = NULL;
|
||||
tmp = g_strdup_printf ("IDE\\%s%s", name_pad,
|
||||
fu_device_get_version (device));
|
||||
fu_device_add_guid (device, tmp);
|
||||
}
|
||||
if (name_pad != NULL) {
|
||||
g_autofree gchar *tmp = NULL;
|
||||
tmp = g_strdup_printf ("IDE\\0%s", name_pad);
|
||||
fu_device_add_guid (device, tmp);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_ata_device_open (FuDevice *device, GError **error)
|
||||
{
|
||||
FuAtaDevice *self = FU_ATA_DEVICE (device);
|
||||
GUdevDevice *udev_device = fu_udev_device_get_dev (FU_UDEV_DEVICE (device));
|
||||
|
||||
/* open device */
|
||||
self->fd = g_open (g_udev_device_get_device_file (udev_device), O_RDONLY);
|
||||
if (self->fd < 0) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"failed to open %s: %s",
|
||||
g_udev_device_get_device_file (udev_device),
|
||||
strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_ata_device_probe (FuUdevDevice *device, GError **error)
|
||||
{
|
||||
FuAtaDevice *self = FU_ATA_DEVICE (device);
|
||||
|
||||
/* set the physical ID */
|
||||
if (!fu_udev_device_set_physical_id (device, "scsi", error))
|
||||
return FALSE;
|
||||
|
||||
/* look at the PCI depth to work out if in an external enclosure */
|
||||
self->pci_depth = fu_udev_device_get_slot_depth (device, "pci");
|
||||
if (self->pci_depth <= 2)
|
||||
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static guint64
|
||||
fu_ata_device_tf_to_pack_id (struct ata_tf *tf)
|
||||
{
|
||||
guint32 lba24 = (tf->lbah << 16) | (tf->lbam << 8) | (tf->lbal);
|
||||
guint32 lbah = tf->dev & 0x0f;
|
||||
return (((guint64) lbah) << 24) | (guint64) lba24;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_ata_device_command (FuAtaDevice *self, struct ata_tf *tf,
|
||||
gint dxfer_direction, guint timeout_ms,
|
||||
guint8 *dxferp, gsize dxfer_len, GError **error)
|
||||
{
|
||||
guint8 cdb[SG_ATA_12_LEN] = { 0x0 };
|
||||
guint8 sb[32] = { 0x0 };
|
||||
sg_io_hdr_t io_hdr = { 0x0 };
|
||||
|
||||
/* map _TO_DEV to PIO mode */
|
||||
if (dxfer_direction == SG_DXFER_TO_DEV)
|
||||
cdb[1] = SG_ATA_PROTO_PIO_OUT;
|
||||
else if (dxfer_direction == SG_DXFER_FROM_DEV)
|
||||
cdb[1] = SG_ATA_PROTO_PIO_IN;
|
||||
else
|
||||
cdb[1] = SG_ATA_PROTO_NON_DATA;
|
||||
|
||||
/* libata workaround: don't demand sense data for IDENTIFY */
|
||||
if (dxfer_len > 0) {
|
||||
cdb[2] |= SG_CDB2_TLEN_NSECT | SG_CDB2_TLEN_SECTORS;
|
||||
cdb[2] |= dxfer_direction == SG_DXFER_TO_DEV ? SG_CDB2_TDIR_TO_DEV : SG_CDB2_TDIR_FROM_DEV;
|
||||
} else {
|
||||
cdb[2] = SG_CDB2_CHECK_COND;
|
||||
}
|
||||
|
||||
/* populate non-LBA48 CDB */
|
||||
cdb[0] = SG_ATA_12;
|
||||
cdb[3] = tf->feat;
|
||||
cdb[4] = tf->nsect;
|
||||
cdb[5] = tf->lbal;
|
||||
cdb[6] = tf->lbam;
|
||||
cdb[7] = tf->lbah;
|
||||
cdb[8] = tf->dev;
|
||||
cdb[9] = tf->command;
|
||||
fu_common_dump_raw (G_LOG_DOMAIN, "CBD", cdb, sizeof(cdb));
|
||||
if (dxfer_direction == SG_DXFER_TO_DEV && dxferp != NULL) {
|
||||
fu_common_dump_raw (G_LOG_DOMAIN, "outgoing_data",
|
||||
dxferp, dxfer_len);
|
||||
}
|
||||
|
||||
/* hit hardware */
|
||||
io_hdr.interface_id = 'S';
|
||||
io_hdr.mx_sb_len = sizeof(sb);
|
||||
io_hdr.dxfer_direction = dxfer_direction;
|
||||
io_hdr.dxfer_len = dxfer_len;
|
||||
io_hdr.dxferp = dxferp;
|
||||
io_hdr.cmdp = cdb;
|
||||
io_hdr.cmd_len = SG_ATA_12_LEN;
|
||||
io_hdr.sbp = sb;
|
||||
io_hdr.pack_id = fu_ata_device_tf_to_pack_id (tf);
|
||||
io_hdr.timeout = timeout_ms;
|
||||
if (ioctl (self->fd, SG_IO, &io_hdr) == -1) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_SUPPORTED,
|
||||
"SG_IO not supported: %s",
|
||||
strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
g_debug ("ATA_%u status=0x%x, host_status=0x%x, driver_status=0x%x",
|
||||
io_hdr.cmd_len, io_hdr.status, io_hdr.host_status, io_hdr.driver_status);
|
||||
fu_common_dump_raw (G_LOG_DOMAIN, "SB", sb, sizeof(sb));
|
||||
|
||||
/* error check */
|
||||
if (io_hdr.status && io_hdr.status != SG_CHECK_CONDITION) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"bad status: 0x%x", io_hdr.status);
|
||||
return FALSE;
|
||||
}
|
||||
if (io_hdr.host_status) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"bad host status: 0x%x", io_hdr.host_status);
|
||||
return FALSE;
|
||||
}
|
||||
if (io_hdr.driver_status && (io_hdr.driver_status != SG_DRIVER_SENSE)) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"bad driver status: 0x%x", io_hdr.driver_status);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* repopulate ata_tf */
|
||||
tf->error = sb[8 + 3];
|
||||
tf->nsect = sb[8 + 5];
|
||||
tf->lbal = sb[8 + 7];
|
||||
tf->lbam = sb[8 + 9];
|
||||
tf->lbah = sb[8 + 11];
|
||||
tf->dev = sb[8 + 12];
|
||||
tf->status = sb[8 + 13];
|
||||
g_debug ("ATA_%u stat=%02x err=%02x nsect=%02x lbal=%02x lbam=%02x lbah=%02x dev=%02x",
|
||||
io_hdr.cmd_len, tf->status, tf->error, tf->nsect, tf->lbal, tf->lbam, tf->lbah, tf->dev);
|
||||
|
||||
/* io error */
|
||||
if (tf->status & (ATA_STAT_ERR | ATA_STAT_DRQ)) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"I/O error, ata_op=0x%02x ata_status=0x%02x ata_error=0x%02x",
|
||||
tf->command, tf->status, tf->error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_ata_device_setup (FuDevice *device, GError **error)
|
||||
{
|
||||
FuAtaDevice *self = FU_ATA_DEVICE (device);
|
||||
struct ata_tf tf = { 0x0 };
|
||||
guint8 id[FU_ATA_IDENTIFY_SIZE];
|
||||
|
||||
/* get ID block */
|
||||
tf.dev = ATA_USING_LBA;
|
||||
tf.command = ATA_OP_IDENTIFY;
|
||||
tf.nsect = 1; /* 512 bytes */
|
||||
if (!fu_ata_device_command (self, &tf, SG_DXFER_FROM_DEV, 1000,
|
||||
id, sizeof(id), error)) {
|
||||
g_prefix_error (error, "failed to IDENTIFY");
|
||||
return FALSE;
|
||||
}
|
||||
if (!fu_ata_device_parse_id (self, id, sizeof(id), error))
|
||||
return FALSE;
|
||||
|
||||
/* add the name fallback */
|
||||
fu_device_add_guid (device, fu_device_get_name (device));
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_ata_device_close (FuDevice *device, GError **error)
|
||||
{
|
||||
FuAtaDevice *self = FU_ATA_DEVICE (device);
|
||||
if (!g_close (self->fd, error))
|
||||
return FALSE;
|
||||
self->fd = 0;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_ata_device_fw_download (FuAtaDevice *self,
|
||||
guint32 idx,
|
||||
guint32 addr,
|
||||
const guint8 *data,
|
||||
guint32 data_sz,
|
||||
GError **error)
|
||||
{
|
||||
struct ata_tf tf = { 0x0 };
|
||||
guint32 block_count = data_sz / FU_ATA_BLOCK_SIZE;
|
||||
guint32 buffer_offset = addr / FU_ATA_BLOCK_SIZE;
|
||||
|
||||
/* write block */
|
||||
tf.dev = 0xa0 | ATA_USING_LBA;
|
||||
tf.command = ATA_OP_DOWNLOAD_MICROCODE;
|
||||
tf.feat = self->transfer_mode;
|
||||
tf.nsect = block_count & 0xff;
|
||||
tf.lbal = block_count >> 8;
|
||||
tf.lbam = buffer_offset & 0xff;
|
||||
tf.lbah = buffer_offset >> 8;
|
||||
if (!fu_ata_device_command (self, &tf, SG_DXFER_TO_DEV,
|
||||
120 * 1000, /* a long time! */
|
||||
(guint8 *) data, data_sz, error)) {
|
||||
g_prefix_error (error, "failed to write firmware @0x%0x",
|
||||
(guint) addr);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* check drive status */
|
||||
if (tf.nsect == 0x0)
|
||||
return TRUE;
|
||||
|
||||
/* drive wants more data, or thinks it is all done */
|
||||
if (tf.nsect == 0x1 || tf.nsect == 0x2)
|
||||
return TRUE;
|
||||
|
||||
/* the offset was set up incorrectly */
|
||||
if (tf.nsect == 0x4) {
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"alignment error");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* other error */
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"unknown return code 0x%02x",
|
||||
tf.nsect);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_ata_device_write_firmware (FuDevice *device, GBytes *fw, GError **error)
|
||||
{
|
||||
FuAtaDevice *self = FU_ATA_DEVICE (device);
|
||||
guint32 chunksz = (guint32) self->transfer_blocks * FU_ATA_BLOCK_SIZE;
|
||||
guint max_size = 0xffff * FU_ATA_BLOCK_SIZE;
|
||||
g_autoptr(GPtrArray) chunks = NULL;
|
||||
|
||||
/* only one block allowed */
|
||||
if (self->transfer_mode == 0x7)
|
||||
max_size = 0xffff;
|
||||
|
||||
/* check is valid */
|
||||
if (g_bytes_get_size (fw) > max_size) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"firmware is too large, maximum size is %u",
|
||||
max_size);
|
||||
return FALSE;
|
||||
}
|
||||
if (g_bytes_get_size (fw) % FU_ATA_BLOCK_SIZE != 0) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"firmware is not multiple of block size %i",
|
||||
FU_ATA_BLOCK_SIZE);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* write each block */
|
||||
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
|
||||
chunks = fu_chunk_array_new_from_bytes (fw, 0x00, 0x00, chunksz);
|
||||
for (guint i = 0; i < chunks->len; i++) {
|
||||
FuChunk *chk = g_ptr_array_index (chunks, i);
|
||||
if (!fu_ata_device_fw_download (self,
|
||||
chk->idx,
|
||||
chk->address,
|
||||
chk->data,
|
||||
chk->data_sz,
|
||||
error)) {
|
||||
g_prefix_error (error, "failed to write chunk %u: ", i);
|
||||
return FALSE;
|
||||
}
|
||||
fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len + 1);
|
||||
}
|
||||
|
||||
/* success! */
|
||||
fu_device_set_progress (device, 100);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_ata_device_set_quirk_kv (FuDevice *device,
|
||||
const gchar *key,
|
||||
const gchar *value,
|
||||
GError **error)
|
||||
{
|
||||
FuAtaDevice *self = FU_ATA_DEVICE (device);
|
||||
if (g_strcmp0 (key, "AtaTransferMode") == 0) {
|
||||
guint64 tmp = fu_common_strtoull (value);
|
||||
if (tmp != 0x3 && tmp != 0x7 && tmp != 0xe) {
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_SUPPORTED,
|
||||
"AtaTransferMode only supports "
|
||||
"values 0x3, 0x7 or 0xe");
|
||||
return FALSE;
|
||||
}
|
||||
self->transfer_mode = (guint8) tmp;
|
||||
return TRUE;
|
||||
}
|
||||
if (g_strcmp0 (key, "AtaTransferBlocks") == 0) {
|
||||
guint64 tmp = fu_common_strtoull (value);
|
||||
if (tmp > 0xffff) {
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_SUPPORTED,
|
||||
"AtaTransferBlocks only supports "
|
||||
"values <= 0xffff");
|
||||
return FALSE;
|
||||
}
|
||||
self->transfer_blocks = (guint16) tmp;
|
||||
return TRUE;
|
||||
}
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_SUPPORTED,
|
||||
"quirk key not supported");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_ata_device_init (FuAtaDevice *self)
|
||||
{
|
||||
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_REQUIRE_AC);
|
||||
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
fu_device_set_summary (FU_DEVICE (self), "ATA Drive");
|
||||
fu_device_add_icon (FU_DEVICE (self), "drive-harddisk");
|
||||
}
|
||||
|
||||
static void
|
||||
fu_ata_device_finalize (GObject *object)
|
||||
{
|
||||
G_OBJECT_CLASS (fu_ata_device_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_ata_device_class_init (FuAtaDeviceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
||||
FuUdevDeviceClass *klass_udev_device = FU_UDEV_DEVICE_CLASS (klass);
|
||||
object_class->finalize = fu_ata_device_finalize;
|
||||
klass_device->to_string = fu_ata_device_to_string;
|
||||
klass_device->set_quirk_kv = fu_ata_device_set_quirk_kv;
|
||||
klass_device->open = fu_ata_device_open;
|
||||
klass_device->setup = fu_ata_device_setup;
|
||||
klass_device->close = fu_ata_device_close;
|
||||
klass_device->write_firmware = fu_ata_device_write_firmware;
|
||||
klass_udev_device->probe = fu_ata_device_probe;
|
||||
}
|
||||
|
||||
FuAtaDevice *
|
||||
fu_ata_device_new (FuUdevDevice *device)
|
||||
{
|
||||
FuAtaDevice *self = g_object_new (FU_TYPE_ATA_DEVICE, NULL);
|
||||
fu_device_incorporate (FU_DEVICE (self), FU_DEVICE (device));
|
||||
return self;
|
||||
}
|
||||
|
||||
FuAtaDevice *
|
||||
fu_ata_device_new_from_blob (const guint8 *buf, gsize sz, GError **error)
|
||||
{
|
||||
g_autoptr(FuAtaDevice) self = g_object_new (FU_TYPE_ATA_DEVICE, NULL);
|
||||
if (!fu_ata_device_parse_id (self, buf, sz, error))
|
||||
return NULL;
|
||||
return g_steal_pointer (&self);
|
||||
}
|
28
plugins/ata/fu-ata-device.h
Normal file
28
plugins/ata/fu-ata-device.h
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef __FU_ATA_DEVICE_H
|
||||
#define __FU_ATA_DEVICE_H
|
||||
|
||||
#include "fu-plugin.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FU_TYPE_ATA_DEVICE (fu_ata_device_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (FuAtaDevice, fu_ata_device, FU, ATA_DEVICE, FuUdevDevice)
|
||||
|
||||
FuAtaDevice *fu_ata_device_new (FuUdevDevice *device);
|
||||
FuAtaDevice *fu_ata_device_new_from_blob (const guint8 *buf,
|
||||
gsize sz,
|
||||
GError **error);
|
||||
|
||||
/* for self tests */
|
||||
guint8 fu_ata_device_get_transfer_mode (FuAtaDevice *self);
|
||||
guint16 fu_ata_device_get_transfer_blocks (FuAtaDevice *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __FU_ATA_DEVICE_H */
|
60
plugins/ata/fu-plugin-ata.c
Normal file
60
plugins/ata/fu-plugin-ata.c
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "fu-plugin-vfuncs.h"
|
||||
|
||||
#include "fu-ata-device.h"
|
||||
|
||||
gboolean
|
||||
fu_plugin_udev_device_added (FuPlugin *plugin, FuUdevDevice *device, GError **error)
|
||||
{
|
||||
GUdevDevice *udev_device = fu_udev_device_get_dev (device);
|
||||
g_autoptr(FuAtaDevice) dev = NULL;
|
||||
g_autoptr(FuDeviceLocker) locker = NULL;
|
||||
|
||||
/* interesting device? */
|
||||
if (udev_device == NULL)
|
||||
return TRUE;
|
||||
if (g_strcmp0 (g_udev_device_get_subsystem (udev_device), "block") != 0)
|
||||
return TRUE;
|
||||
if (g_strcmp0 (g_udev_device_get_devtype (udev_device), "disk") != 0)
|
||||
return TRUE;
|
||||
if (!g_udev_device_get_property_as_boolean (udev_device, "ID_ATA_SATA"))
|
||||
return TRUE;
|
||||
if (!g_udev_device_get_property_as_boolean (udev_device, "ID_ATA_DOWNLOAD_MICROCODE"))
|
||||
return TRUE;
|
||||
|
||||
dev = fu_ata_device_new (device);
|
||||
locker = fu_device_locker_new (dev, error);
|
||||
if (locker == NULL)
|
||||
return FALSE;
|
||||
fu_plugin_device_add (plugin, FU_DEVICE (dev));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
fu_plugin_init (FuPlugin *plugin)
|
||||
{
|
||||
fu_plugin_set_build_hash (plugin, FU_BUILD_HASH);
|
||||
fu_plugin_add_udev_subsystem (plugin, "block");
|
||||
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_SUPPORTS_PROTOCOL, "org.t13.ata");
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_plugin_update (FuPlugin *plugin,
|
||||
FuDevice *device,
|
||||
GBytes *blob_fw,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(FuDeviceLocker) locker = NULL;
|
||||
locker = fu_device_locker_new (device, error);
|
||||
if (locker == NULL)
|
||||
return FALSE;
|
||||
return fu_device_write_firmware (device, blob_fw, error);
|
||||
}
|
50
plugins/ata/fu-self-test.c
Normal file
50
plugins/ata/fu-self-test.c
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <fwupd.h>
|
||||
|
||||
#include "fu-ata-device.h"
|
||||
#include "fu-test.h"
|
||||
|
||||
static void
|
||||
fu_ata_id_func (void)
|
||||
{
|
||||
gboolean ret;
|
||||
gsize sz;
|
||||
g_autofree gchar *data = NULL;
|
||||
g_autofree gchar *path = NULL;
|
||||
g_autoptr(FuAtaDevice) dev = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
path = fu_test_get_filename (TESTDATADIR, "StarDrive-SBFM61.2.bin");
|
||||
g_assert_nonnull (path);
|
||||
ret = g_file_get_contents (path, &data, &sz, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret);
|
||||
dev = fu_ata_device_new_from_blob ((guint8 *)data, sz, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (dev);
|
||||
g_assert_cmpint (fu_ata_device_get_transfer_mode (dev), ==, 0x3);
|
||||
g_assert_cmpint (fu_ata_device_get_transfer_blocks (dev), ==, 0x1);
|
||||
g_assert_cmpstr (fu_device_get_serial (FU_DEVICE (dev)), ==, "A45A078A198600476509");
|
||||
g_assert_cmpstr (fu_device_get_name (FU_DEVICE (dev)), ==, "SATA SSD");
|
||||
g_assert_cmpstr (fu_device_get_version (FU_DEVICE (dev)), ==, "SBFM61.2");
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* only critical and error are fatal */
|
||||
g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
|
||||
|
||||
/* tests go here */
|
||||
g_test_add_func ("/fwupd/id", fu_ata_id_func);
|
||||
return g_test_run ();
|
||||
}
|
58
plugins/ata/meson.build
Normal file
58
plugins/ata/meson.build
Normal file
@ -0,0 +1,58 @@
|
||||
cargs = ['-DG_LOG_DOMAIN="FuPluginAta"']
|
||||
|
||||
install_data([
|
||||
'ata.quirk',
|
||||
],
|
||||
install_dir: join_paths(datadir, 'fwupd', 'quirks.d')
|
||||
)
|
||||
|
||||
shared_module('fu_plugin_ata',
|
||||
fu_hash,
|
||||
sources : [
|
||||
'fu-plugin-ata.c',
|
||||
'fu-ata-device.c',
|
||||
],
|
||||
include_directories : [
|
||||
include_directories('../..'),
|
||||
include_directories('../../src'),
|
||||
include_directories('../../libfwupd'),
|
||||
],
|
||||
install : true,
|
||||
install_dir: plugin_dir,
|
||||
c_args : [
|
||||
cargs,
|
||||
'-DLOCALSTATEDIR="' + localstatedir + '"',
|
||||
],
|
||||
link_with : [
|
||||
libfwupdprivate,
|
||||
],
|
||||
dependencies : [
|
||||
plugin_deps,
|
||||
],
|
||||
)
|
||||
|
||||
if get_option('tests')
|
||||
testdatadir = join_paths(meson.current_source_dir(), 'tests')
|
||||
cargs += '-DTESTDATADIR="' + testdatadir + '"'
|
||||
e = executable(
|
||||
'ata-self-test',
|
||||
sources : [
|
||||
'fu-self-test.c',
|
||||
'fu-ata-device.c',
|
||||
],
|
||||
include_directories : [
|
||||
include_directories('..'),
|
||||
include_directories('../..'),
|
||||
include_directories('../../libfwupd'),
|
||||
include_directories('../../src'),
|
||||
],
|
||||
dependencies : [
|
||||
plugin_deps,
|
||||
],
|
||||
link_with : [
|
||||
libfwupdprivate,
|
||||
],
|
||||
c_args : cargs
|
||||
)
|
||||
test('ata-self-test', e)
|
||||
endif
|
BIN
plugins/ata/tests/StarDrive-SBFM61.2.bin
Normal file
BIN
plugins/ata/tests/StarDrive-SBFM61.2.bin
Normal file
Binary file not shown.
@ -1,3 +1,4 @@
|
||||
subdir('ata')
|
||||
subdir('dfu')
|
||||
subdir('colorhug')
|
||||
subdir('ebitdo')
|
||||
|
Loading…
Reference in New Issue
Block a user