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:
Richard Hughes 2019-01-28 15:31:45 +00:00
parent 4fef28d0ac
commit 0bf8ee810b
10 changed files with 860 additions and 0 deletions

View File

@ -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
View 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
View File

@ -0,0 +1 @@

617
plugins/ata/fu-ata-device.c Normal file
View 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);
}

View 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 */

View 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);
}

View 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
View 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

Binary file not shown.

View File

@ -1,3 +1,4 @@
subdir('ata')
subdir('dfu')
subdir('colorhug')
subdir('ebitdo')