From 0bf8ee810b8b9896f971ddbb35b1e366ee45f269 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Mon, 28 Jan 2019 15:31:45 +0000 Subject: [PATCH] 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 --- contrib/fwupd.spec.in | 1 + plugins/ata/README.md | 44 ++ plugins/ata/ata.quirk | 1 + plugins/ata/fu-ata-device.c | 617 +++++++++++++++++++++++ plugins/ata/fu-ata-device.h | 28 + plugins/ata/fu-plugin-ata.c | 60 +++ plugins/ata/fu-self-test.c | 50 ++ plugins/ata/meson.build | 58 +++ plugins/ata/tests/StarDrive-SBFM61.2.bin | Bin 0 -> 512 bytes plugins/meson.build | 1 + 10 files changed, 860 insertions(+) create mode 100644 plugins/ata/README.md create mode 100644 plugins/ata/ata.quirk create mode 100644 plugins/ata/fu-ata-device.c create mode 100644 plugins/ata/fu-ata-device.h create mode 100644 plugins/ata/fu-plugin-ata.c create mode 100644 plugins/ata/fu-self-test.c create mode 100644 plugins/ata/meson.build create mode 100644 plugins/ata/tests/StarDrive-SBFM61.2.bin diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index b2354b5d6..e17976c4e 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -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} diff --git a/plugins/ata/README.md b/plugins/ata/README.md new file mode 100644 index 000000000..decbfe28c --- /dev/null +++ b/plugins/ata/README.md @@ -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 | diff --git a/plugins/ata/ata.quirk b/plugins/ata/ata.quirk new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/plugins/ata/ata.quirk @@ -0,0 +1 @@ + diff --git a/plugins/ata/fu-ata-device.c b/plugins/ata/fu-ata-device.c new file mode 100644 index 000000000..c91bf514e --- /dev/null +++ b/plugins/ata/fu-ata-device.c @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2019 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/plugins/ata/fu-ata-device.h b/plugins/ata/fu-ata-device.h new file mode 100644 index 000000000..ce0619aa4 --- /dev/null +++ b/plugins/ata/fu-ata-device.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Richard Hughes + * + * 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 */ diff --git a/plugins/ata/fu-plugin-ata.c b/plugins/ata/fu-plugin-ata.c new file mode 100644 index 000000000..9a8ab1c34 --- /dev/null +++ b/plugins/ata/fu-plugin-ata.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 Richard Hughes + * + * 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); +} diff --git a/plugins/ata/fu-self-test.c b/plugins/ata/fu-self-test.c new file mode 100644 index 000000000..805cf90a0 --- /dev/null +++ b/plugins/ata/fu-self-test.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#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 (); +} diff --git a/plugins/ata/meson.build b/plugins/ata/meson.build new file mode 100644 index 000000000..cd26e395d --- /dev/null +++ b/plugins/ata/meson.build @@ -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 diff --git a/plugins/ata/tests/StarDrive-SBFM61.2.bin b/plugins/ata/tests/StarDrive-SBFM61.2.bin new file mode 100644 index 0000000000000000000000000000000000000000..201cceca295cd57a8d3e4d840d5527291e4a1352 GIT binary patch literal 512 zcmZ=@_-}81LVy7T?7<`hgNdV~skwopg{7gHg@J*&iK&^T0Yu6v*w@X_%t+5M*fAtn z!6jIM01#+kaA42}Vjy4#+9Uw9L*UPE1_8$Z|NrxY#F!Z>ppZcxVh4znXW(mPWAI^c zVfX=5m0iMEo9vib!nh~dvEG4;K@O