hailuck: Add an plugin for the Pinebook Pro laptop

This is not complete enough for LVFS-usage, but good enough to use with commands
such as fwupdtool. It's likely newer kbd and tp firmware will be required to
integrate with the fwupd in all required ways.
This commit is contained in:
Richard Hughes 2020-01-07 12:27:50 +00:00
parent fa11ceae9c
commit db4894e863
18 changed files with 1062 additions and 0 deletions

View File

@ -408,6 +408,7 @@ mkdir -p --mode=0700 $RPM_BUILD_ROOT%{_localstatedir}/lib/fwupd/gnupg
%{_libdir}/fwupd-plugins-3/libfu_plugin_ep963x.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_fastboot.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_fresco_pd.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_hailuck.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_iommu.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_jabra.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_linux_lockdown.so

35
plugins/hailuck/README.md Normal file
View File

@ -0,0 +1,35 @@
Hailuck Support
===============
Introduction
------------
Hailuck produce the firmware used on the keyboard and trackpad used in the
Pinebook Pro laptops.
Firmware Format
---------------
The daemon will decompress the cabinet archive and extract a firmware blob in
a packed binary file format.
This plugin supports the following protocol ID:
* com.hailuck.kbd
* com.hailuck.tp
GUID Generation
---------------
These devices use the standard USB DeviceInstanceId values, e.g.
* `USB\VID_0603&PID_1020`
Vendor ID Security
------------------
The vendor ID is set from the USB vendor, in this instance set to `USB:0x0603`
External interface access
-------------------------
This plugin requires read/write access to `/dev/bus/usb`.

View File

@ -0,0 +1,47 @@
Bus 003 Device 003: ID 0603:1020 Novatek Microelectronics Corp.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0603 Novatek Microelectronics Corp.
idProduct 0x1020
bcdDevice 3.01
iManufacturer 0
iProduct 0
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0022
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 1 Boot Interface Subclass
bInterfaceProtocol 1 Keyboard
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 10

View File

@ -0,0 +1,89 @@
Bus 003 Device 008: ID 258a:001e HAILUCK CO.,LTD USB KEYBOARD
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x258a
idProduct 0x001e
bcdDevice 1.00
iManufacturer 1 HAILUCK CO.,LTD
iProduct 2 USB KEYBOARD
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x003b
bNumInterfaces 2
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 1 Boot Interface Subclass
bInterfaceProtocol 1 Keyboard
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.10
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 65
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 10
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.10
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 487
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 10
Device Status: 0x0000
(Bus Powered)

View File

@ -0,0 +1,272 @@
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-chunk.h"
#include "fu-hailuck-common.h"
#include "fu-hailuck-bl-device.h"
#include "fu-hailuck-kbd-firmware.h"
struct _FuHaiLuckBlDevice {
FuHidDevice parent_instance;
};
G_DEFINE_TYPE (FuHaiLuckBlDevice, fu_hailuck_bl_device, FU_TYPE_HID_DEVICE)
static gboolean
fu_hailuck_bl_device_attach (FuDevice *device, GError **error)
{
guint8 buf[6] = {
FU_HAILUCK_REPORT_ID_SHORT,
FU_HAILUCK_CMD_ATTACH,
};
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
if (!fu_hid_device_set_report (FU_HID_DEVICE (device),
buf[0], buf, sizeof(buf), 1000,
FU_HID_DEVICE_FLAG_IS_FEATURE, error))
return FALSE;
if (!g_usb_device_reset (fu_usb_device_get_dev (FU_USB_DEVICE (device)), error))
return FALSE;
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
return TRUE;
}
static gboolean
fu_hailuck_bl_device_probe (FuUsbDevice *device, GError **error)
{
g_autofree gchar *devid = NULL;
/* add extra keyboard-specific GUID */
devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X&MODE_KBD",
fu_usb_device_get_vid (device),
fu_usb_device_get_pid (device));
fu_device_add_instance_id (FU_DEVICE (device), devid);
/* success */
return TRUE;
}
static gboolean
fu_hailuck_bl_device_read_block_start (FuHaiLuckBlDevice *self,
guint32 length,
GError **error)
{
guint8 buf[6] = {
FU_HAILUCK_REPORT_ID_SHORT,
FU_HAILUCK_CMD_READ_BLOCK_START,
};
fu_common_write_uint16 (buf + 4, length, G_LITTLE_ENDIAN);
return fu_hid_device_set_report (FU_HID_DEVICE (self), buf[0],
buf, sizeof(buf), 100,
FU_HID_DEVICE_FLAG_IS_FEATURE, error);
}
static gboolean
fu_hailuck_bl_device_read_block (FuHaiLuckBlDevice *self,
guint8 *data, gsize data_sz,
GError **error)
{
gsize bufsz = data_sz + 2;
g_autofree guint8 *buf = g_malloc0 (bufsz);
buf[0] = FU_HAILUCK_REPORT_ID_LONG;
buf[1] = FU_HAILUCK_CMD_READ_BLOCK;
if (!fu_hid_device_get_report (FU_HID_DEVICE (self), buf[0],
buf, bufsz, 2000,
FU_HID_DEVICE_FLAG_IS_FEATURE,
error))
return FALSE;
if (!fu_memcpy_safe (data, data_sz, 0x0, /* dst */
buf, bufsz, 0x02, /* src */
data_sz, error))
return FALSE;
/* success */
g_usleep (10000);
return TRUE;
}
static GBytes *
fu_hailuck_bl_device_dump_firmware (FuDevice *device, GError **error)
{
FuHaiLuckBlDevice *self = FU_HAILUCK_BL_DEVICE (device);
gsize fwsz = fu_device_get_firmware_size_max (device);
g_autoptr(GByteArray) fwbuf = g_byte_array_new ();
g_autoptr(GPtrArray) chunks = NULL;
/* tell device amount of data to send */
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_READ);
if (!fu_hailuck_bl_device_read_block_start (self, fwsz, error))
return NULL;
/* recieve data back */
fu_byte_array_set_size (fwbuf, fwsz);
chunks = fu_chunk_array_new (fwbuf->data, fwbuf->len, 0x0, 0x0, 2048);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index (chunks, i);
if (!fu_hailuck_bl_device_read_block (self,
(guint8 *) chk->data,
chk->data_sz,
error))
return NULL;
fu_device_set_progress_full (device, i, chunks->len - 1);
}
/* success */
return g_byte_array_free_to_bytes (g_steal_pointer (&fwbuf));
}
static gboolean
fu_hailuck_bl_device_erase (FuHaiLuckBlDevice *self, GError **error)
{
guint8 buf[6] = {
FU_HAILUCK_REPORT_ID_SHORT,
FU_HAILUCK_CMD_ERASE,
};
if (!fu_hid_device_set_report (FU_HID_DEVICE (self), buf[0],
buf, sizeof(buf), 100,
FU_HID_DEVICE_FLAG_IS_FEATURE, error))
return FALSE;
fu_device_sleep_with_progress (FU_DEVICE (self), 2);
return TRUE;
}
static gboolean
fu_hailuck_bl_device_write_block_start (FuHaiLuckBlDevice *self,
guint32 length, GError **error)
{
guint8 buf[6] = {
FU_HAILUCK_REPORT_ID_SHORT,
FU_HAILUCK_CMD_WRITE_BLOCK_START,
};
fu_common_write_uint16 (buf + 4, length, G_LITTLE_ENDIAN);
return fu_hid_device_set_report (FU_HID_DEVICE (self), buf[0],
buf, sizeof(buf), 100,
FU_HID_DEVICE_FLAG_IS_FEATURE, error);
}
static gboolean
fu_hailuck_bl_device_write_block (FuHaiLuckBlDevice *self,
const guint8 *data, gsize data_sz,
GError **error)
{
gsize bufsz = data_sz + 2;
g_autofree guint8 *buf = g_malloc0 (bufsz);
buf[0] = FU_HAILUCK_REPORT_ID_LONG;
buf[1] = FU_HAILUCK_CMD_WRITE_BLOCK;
if (!fu_memcpy_safe (buf, bufsz, 0x02, /* dst */
data, data_sz, 0x0, /* src */
data_sz, error))
return FALSE;
if (!fu_hid_device_set_report (FU_HID_DEVICE (self), buf[0],
buf, bufsz, 2000,
FU_HID_DEVICE_FLAG_IS_FEATURE,
error))
return FALSE;
/* success */
g_usleep (10000);
return TRUE;
}
static FuFirmware *
fu_hailuck_bl_device_prepare_firmware (FuDevice *device,
GBytes *fw,
FwupdInstallFlags flags,
GError **error)
{
g_autoptr(FuFirmware) firmware = fu_hailuck_kbd_firmware_new ();
if (!fu_firmware_parse (firmware, fw, flags, error))
return NULL;
return g_steal_pointer (&firmware);
}
static gboolean
fu_hailuck_bl_device_write_firmware (FuDevice *device,
FuFirmware *firmware,
FwupdInstallFlags flags,
GError **error)
{
FuHaiLuckBlDevice *self = FU_HAILUCK_BL_DEVICE (device);
FuChunk *chk0;
g_autoptr(GBytes) fw = NULL;
g_autoptr(GBytes) fw_new = NULL;
g_autoptr(GPtrArray) chunks = NULL;
g_autofree guint8 *chk0_data = NULL;
/* get default image */
fw = fu_firmware_get_image_default_bytes (firmware, error);
if (fw == NULL)
return FALSE;
/* erase all contents */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE);
if (!fu_hailuck_bl_device_erase (self, error))
return FALSE;
/* tell device amount of data to expect */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
if (!fu_hailuck_bl_device_write_block_start (self, g_bytes_get_size (fw), error))
return FALSE;
/* build packets */
chunks = fu_chunk_array_new_from_bytes (fw, 0x0, 0x00, 2048);
/* intentionally corrupt first chunk so that CRC fails */
chk0 = g_ptr_array_index (chunks, 0);
chk0_data = g_memdup (chk0->data, chk0->data_sz);
chk0_data[0] = 0x00;
if (!fu_hailuck_bl_device_write_block (self, chk0_data, chk0->data_sz, error))
return FALSE;
/* send the rest of the chunks */
for (guint i = 1; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index (chunks, i);
if (!fu_hailuck_bl_device_write_block (self, chk->data, chk->data_sz, error))
return FALSE;
fu_device_set_progress_full (device, i, chunks->len);
}
/* retry write of first block */
if (!fu_hailuck_bl_device_write_block_start (self, g_bytes_get_size (fw), error))
return FALSE;
if (!fu_hailuck_bl_device_write_block (self, chk0->data, chk0->data_sz, error))
return FALSE;
fu_device_set_progress_full (device, chunks->len, chunks->len);
/* verify */
fw_new = fu_hailuck_bl_device_dump_firmware (device, error);
return fu_common_bytes_compare (fw, fw_new, error);
}
static void
fu_hailuck_bl_device_init (FuHaiLuckBlDevice *self)
{
fu_device_set_firmware_size (FU_DEVICE (self), 0x4000);
fu_device_set_protocol (FU_DEVICE (self), "com.hailuck.kbd");
fu_device_set_name (FU_DEVICE (self), "Keyboard [bootloader]");
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_icon (FU_DEVICE (self), "input-keyboard");
fu_hid_device_add_flag (FU_HID_DEVICE (self), FU_HID_DEVICE_FLAG_NO_KERNEL_REBIND);
fu_device_set_remove_delay (FU_DEVICE (self),
FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
}
static void
fu_hailuck_bl_device_class_init (FuHaiLuckBlDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass);
klass_device->dump_firmware = fu_hailuck_bl_device_dump_firmware;
klass_device->prepare_firmware = fu_hailuck_bl_device_prepare_firmware;
klass_device->write_firmware = fu_hailuck_bl_device_write_firmware;
klass_device->attach = fu_hailuck_bl_device_attach;
klass_usb_device->probe = fu_hailuck_bl_device_probe;
}

View File

@ -0,0 +1,13 @@
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include "fu-plugin.h"
#include "fu-hid-device.h"
#define FU_TYPE_HAILUCK_BL_DEVICE (fu_hailuck_bl_device_get_type ())
G_DECLARE_FINAL_TYPE (FuHaiLuckBlDevice, fu_hailuck_bl_device, FU, HAILUCK_BL_DEVICE, FuHidDevice)

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-hailuck-common.h"
const gchar *
fu_hailuck_cmd_to_string (guint8 cmd)
{
if (cmd == FU_HAILUCK_CMD_ERASE)
return "erase";
if (cmd == FU_HAILUCK_CMD_READ_BLOCK_START)
return "read-block-start";
if (cmd == FU_HAILUCK_CMD_WRITE_BLOCK_START)
return "write-block-start";
if (cmd == FU_HAILUCK_CMD_READ_BLOCK)
return "read-block";
if (cmd == FU_HAILUCK_CMD_WRITE_BLOCK)
return "write-block";
if (cmd == FU_HAILUCK_CMD_GET_STATUS)
return "get-status";
if (cmd == FU_HAILUCK_CMD_DETACH)
return "detach";
if (cmd == FU_HAILUCK_CMD_ATTACH)
return "attach";
if (cmd == FU_HAILUCK_CMD_WRITE_TP)
return "write-tp";
if (cmd == FU_HAILUCK_CMD_I2C_CHECK_CHECKSUM)
return "i2c-check-checksum";
if (cmd == FU_HAILUCK_CMD_I2C_ENTER_BL)
return "i2c-enter-bl";
if (cmd == FU_HAILUCK_CMD_I2C_ERASE)
return "i2c-erase";
if (cmd == FU_HAILUCK_CMD_I2C_PROGRAM)
return "i2c-program";
if (cmd == FU_HAILUCK_CMD_I2C_VERIFY_BLOCK)
return "i2c-verify-block";
if (cmd == FU_HAILUCK_CMD_I2C_VERIFY_CHECKSUM)
return "i2c-verify-checksum";
if (cmd == FU_HAILUCK_CMD_I2C_PROGRAMPASS)
return "i2c-programpass";
if (cmd == FU_HAILUCK_CMD_I2C_END_PROGRAM)
return "i2c-end-program";
return NULL;
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <glib-object.h>
#define FU_HAILUCK_REPORT_ID_SHORT 0x05
#define FU_HAILUCK_REPORT_ID_LONG 0x06
#define FU_HAILUCK_CMD_ERASE 0x45
#define FU_HAILUCK_CMD_READ_BLOCK_START 0x52
#define FU_HAILUCK_CMD_ATTACH 0x55 /* guessed */
#define FU_HAILUCK_CMD_WRITE_BLOCK_START 0x57
#define FU_HAILUCK_CMD_READ_BLOCK 0x72
#define FU_HAILUCK_CMD_DETACH 0x75 /* guessed */
#define FU_HAILUCK_CMD_WRITE_BLOCK 0x77
#define FU_HAILUCK_CMD_GET_STATUS 0xA1
#define FU_HAILUCK_CMD_WRITE_TP 0xD0 /* guessed */
#define FU_HAILUCK_CMD_I2C_CHECK_CHECKSUM 0xF0
#define FU_HAILUCK_CMD_I2C_ENTER_BL 0xF1
#define FU_HAILUCK_CMD_I2C_ERASE 0xF2
#define FU_HAILUCK_CMD_I2C_PROGRAM 0xF3
#define FU_HAILUCK_CMD_I2C_VERIFY_BLOCK 0xF4
#define FU_HAILUCK_CMD_I2C_VERIFY_CHECKSUM 0xF5
#define FU_HAILUCK_CMD_I2C_PROGRAMPASS 0xF6
#define FU_HAILUCK_CMD_I2C_END_PROGRAM 0xF7
const gchar *fu_hailuck_cmd_to_string (guint8 cmd);

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-hailuck-common.h"
#include "fu-hailuck-kbd-device.h"
#include "fu-hailuck-tp-device.h"
struct _FuHaiLuckKbdDevice {
FuHidDevice parent_instance;
};
G_DEFINE_TYPE (FuHaiLuckKbdDevice, fu_hailuck_kbd_device, FU_TYPE_HID_DEVICE)
static gboolean
fu_hailuck_kbd_device_detach (FuDevice *device, GError **error)
{
guint8 buf[6] = {
FU_HAILUCK_REPORT_ID_SHORT,
FU_HAILUCK_CMD_DETACH
};
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
if (!fu_hid_device_set_report (FU_HID_DEVICE (device),
buf[0], buf, sizeof(buf), 1000,
FU_HID_DEVICE_FLAG_IS_FEATURE, error))
return FALSE;
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
return TRUE;
}
static gboolean
fu_hailuck_kbd_device_probe (FuUsbDevice *device, GError **error)
{
g_autofree gchar *devid = NULL;
g_autoptr(FuHaiLuckTpDevice) tp_device = fu_hailuck_tp_device_new (FU_DEVICE (device));
/* add extra keyboard-specific GUID */
devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X&MODE_KBD",
fu_usb_device_get_vid (device),
fu_usb_device_get_pid (device));
fu_device_add_instance_id (FU_DEVICE (device), devid);
/* add touchpad */
if (!fu_device_probe (FU_DEVICE (tp_device), error))
return FALSE;
/* assume the TP has the same version as the keyboard */
fu_device_set_version (FU_DEVICE (tp_device),
fu_device_get_version (FU_DEVICE (device)));
fu_device_set_version_format (FU_DEVICE (tp_device),
fu_device_get_version_format (FU_DEVICE (device)));
fu_device_add_child (FU_DEVICE (device), FU_DEVICE (tp_device));
/* success */
return TRUE;
}
static void
fu_hailuck_kbd_device_init (FuHaiLuckKbdDevice *self)
{
fu_device_set_firmware_size (FU_DEVICE (self), 0x4000);
fu_device_set_protocol (FU_DEVICE (self), "com.hailuck.kbd");
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_icon (FU_DEVICE (self), "input-keyboard");
fu_hid_device_set_interface (FU_HID_DEVICE (self), 0x1);
fu_device_set_remove_delay (FU_DEVICE (self),
FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
}
static void
fu_hailuck_kbd_device_class_init (FuHaiLuckKbdDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass);
klass_device->detach = fu_hailuck_kbd_device_detach;
klass_usb_device->probe = fu_hailuck_kbd_device_probe;
}

View File

@ -0,0 +1,13 @@
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include "fu-plugin.h"
#include "fu-hid-device.h"
#define FU_TYPE_HAILUCK_KBD_DEVICE (fu_hailuck_kbd_device_get_type ())
G_DECLARE_FINAL_TYPE (FuHaiLuckKbdDevice, fu_hailuck_kbd_device, FU, HAILUCK_KBD_DEVICE, FuHidDevice)

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-common.h"
#include "fu-hailuck-kbd-firmware.h"
struct _FuHaiLuckKbdFirmware {
FuIhexFirmwareClass parent_instance;
};
G_DEFINE_TYPE (FuHaiLuckKbdFirmware, fu_hailuck_kbd_firmware, FU_TYPE_IHEX_FIRMWARE)
static gboolean
fu_hailuck_kbd_firmware_parse (FuFirmware *firmware,
GBytes *fw,
guint64 addr_start,
guint64 addr_end,
FwupdInstallFlags flags,
GError **error)
{
GPtrArray *records = fu_ihex_firmware_get_records (FU_IHEX_FIRMWARE (firmware));
g_autoptr(FuFirmwareImage) img = NULL;
g_autoptr(GByteArray) buf = g_byte_array_new ();
g_autoptr(GBytes) fw_new = NULL;
for (guint j = 0; j < records->len; j++) {
FuIhexFirmwareRecord *rcd = g_ptr_array_index (records, j);
if (rcd->record_type == FU_IHEX_FIRMWARE_RECORD_TYPE_EOF)
break;
if (rcd->record_type != FU_IHEX_FIRMWARE_RECORD_TYPE_DATA) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"only record 0x0 supported, got 0x%02x",
rcd->record_type);
return FALSE;
}
if (rcd->addr + rcd->data->len > buf->len)
fu_byte_array_set_size (buf, rcd->addr + rcd->data->len);
if (!fu_memcpy_safe (buf->data, buf->len, rcd->addr,
rcd->data->data, rcd->data->len, 0x0,
rcd->data->len, error))
return FALSE;
}
/* set the main function executed on system init */
if (buf->len > 0x37FD && buf->data[1] == 0x38 && buf->data[2] == 0x00) {
buf->data[0] = buf->data[0x37FB];
buf->data[1] = buf->data[0x37FC];
buf->data[2] = buf->data[0x37FD];
buf->data[0x37FB] = 0x00;
buf->data[0x37FC] = 0x00;
buf->data[0x37FD] = 0x00;
}
/* whole image */
fw_new = g_byte_array_free_to_bytes (g_steal_pointer (&buf));
img = fu_firmware_image_new (fw_new);
fu_firmware_add_image (firmware, img);
return TRUE;
}
static void
fu_hailuck_kbd_firmware_init (FuHaiLuckKbdFirmware *self)
{
}
static void
fu_hailuck_kbd_firmware_class_init (FuHaiLuckKbdFirmwareClass *klass)
{
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass);
klass_firmware->parse = fu_hailuck_kbd_firmware_parse;
}
FuFirmware *
fu_hailuck_kbd_firmware_new (void)
{
return FU_FIRMWARE (g_object_new (FU_TYPE_HAILUCK_KBD_FIRMWARE, NULL));
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include "fu-ihex-firmware.h"
#define FU_TYPE_HAILUCK_KBD_FIRMWARE (fu_hailuck_kbd_firmware_get_type ())
G_DECLARE_FINAL_TYPE (FuHaiLuckKbdFirmware, fu_hailuck_kbd_firmware, FU, HAILUCK_KBD_FIRMWARE, FuIhexFirmware)
FuFirmware *fu_hailuck_kbd_firmware_new (void);

View File

@ -0,0 +1,233 @@
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-chunk.h"
#include "fu-hid-device.h"
#include "fu-hailuck-common.h"
#include "fu-hailuck-tp-device.h"
struct _FuHaiLuckTpDevice {
FuDevice parent_instance;
};
G_DEFINE_TYPE (FuHaiLuckTpDevice, fu_hailuck_tp_device, FU_TYPE_DEVICE)
static gboolean
fu_hailuck_tp_device_probe (FuDevice *device, GError **error)
{
FuDevice *parent = fu_device_get_parent (device);
g_autofree gchar *devid1 = NULL;
g_autofree gchar *devid2 = NULL;
devid1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
fu_usb_device_get_vid (FU_USB_DEVICE (parent)),
fu_usb_device_get_pid (FU_USB_DEVICE (parent)));
fu_device_add_instance_id (device, devid1);
devid2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&MODE_TP",
fu_usb_device_get_vid (FU_USB_DEVICE (parent)),
fu_usb_device_get_pid (FU_USB_DEVICE (parent)));
fu_device_add_instance_id (device, devid2);
return TRUE;
}
static gboolean
fu_hailuck_tp_device_open (FuDevice *device, GError **error)
{
FuDevice *parent = fu_device_get_parent (device);
return fu_device_open (parent, error);
}
static gboolean
fu_hailuck_tp_device_close (FuDevice *device, GError **error)
{
FuDevice *parent = fu_device_get_parent (device);
return fu_device_close (parent, error);
}
typedef struct {
guint8 type;
guint8 success; /* if 0xff, then cmd-0x10 */
} FuHailuckTpDeviceReq;
static gboolean
fu_hailuck_tp_device_cmd_cb (FuDevice *device, gpointer user_data, GError **error)
{
FuDevice *parent = fu_device_get_parent (device);
FuHailuckTpDeviceReq *req = (FuHailuckTpDeviceReq *) user_data;
guint8 buf[6] = {
FU_HAILUCK_REPORT_ID_SHORT,
FU_HAILUCK_CMD_GET_STATUS,
req->type,
};
guint8 success_tmp = req->success;
if (!fu_hid_device_set_report (FU_HID_DEVICE (parent), buf[0],
buf, sizeof(buf), 1000,
FU_HID_DEVICE_FLAG_IS_FEATURE, error))
return FALSE;
if (!fu_hid_device_get_report (FU_HID_DEVICE (parent), buf[0],
buf, sizeof(buf), 2000,
FU_HID_DEVICE_FLAG_IS_FEATURE |
FU_HID_DEVICE_FLAG_ALLOW_TRUNC,
error))
return FALSE;
if (success_tmp == 0xff)
success_tmp = req->type - 0x10;
if (buf[0] != FU_HAILUCK_REPORT_ID_SHORT || buf[1] != success_tmp) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"report mismatch for type=0x%02x[%s]: "
"expected=0x%02x, received=0x%02x",
req->type,
fu_hailuck_cmd_to_string (req->type),
success_tmp, buf[1]);
return FALSE;
}
return TRUE;
}
static gboolean
fu_hailuck_tp_device_write_firmware (FuDevice *device,
FuFirmware *firmware,
FwupdInstallFlags flags,
GError **error)
{
FuDevice *parent = fu_device_get_parent (device);
const guint block_size = 1024;
g_autoptr(GBytes) fw = NULL;
g_autoptr(GPtrArray) chunks = NULL;
FuHailuckTpDeviceReq req = {
.type = 0xff,
.success = 0xff,
};
/* get default image */
fw = fu_firmware_get_image_default_bytes (firmware, error);
if (fw == NULL)
return FALSE;
/* erase */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE);
req.type = FU_HAILUCK_CMD_I2C_ERASE;
if (!fu_device_retry (device, fu_hailuck_tp_device_cmd_cb, 100, &req, error)) {
g_prefix_error (error, "failed to erase: ");
return FALSE;
}
g_usleep (10000);
/* write */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
chunks = fu_chunk_array_new_from_bytes (fw, 0x0, 0x0, block_size);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index (chunks, i);
g_autoptr(GByteArray) buf = g_byte_array_new ();
/* write block */
fu_byte_array_append_uint8 (buf, FU_HAILUCK_REPORT_ID_LONG);
fu_byte_array_append_uint8 (buf, FU_HAILUCK_CMD_WRITE_TP);
fu_byte_array_append_uint16 (buf, 0xCCCC, G_LITTLE_ENDIAN);
fu_byte_array_append_uint16 (buf, chk->address, G_LITTLE_ENDIAN);
fu_byte_array_append_uint16 (buf, 0xCCCC, G_LITTLE_ENDIAN);
g_byte_array_append (buf, chk->data, chk->data_sz);
fu_byte_array_append_uint8 (buf, 0xEE);
fu_byte_array_append_uint8 (buf, 0xD2);
fu_byte_array_append_uint16 (buf, 0xCCCC, G_LITTLE_ENDIAN);
fu_byte_array_append_uint16 (buf, 0xCCCC, G_LITTLE_ENDIAN);
fu_byte_array_append_uint16 (buf, 0xCCCC, G_LITTLE_ENDIAN);
if (buf->len != block_size + 16) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"packet mismatch: len=0x%04x, expected=0x%04x",
buf->len, block_size + 16);
return FALSE;
}
if (!fu_hid_device_set_report (FU_HID_DEVICE (parent), buf->data[0],
buf->data, buf->len, 1000,
FU_HID_DEVICE_FLAG_IS_FEATURE, error)) {
g_prefix_error (error, "failed to write block 0x%x: ", i);
return FALSE;
}
g_usleep (150 * 1000);
/* verify block */
req.type = FU_HAILUCK_CMD_I2C_VERIFY_BLOCK;
if (!fu_device_retry (device, fu_hailuck_tp_device_cmd_cb, 100, &req, error)) {
g_prefix_error (error, "failed to verify block 0x%x: ", i);
return FALSE;
}
/* update progress */
fu_device_set_progress_full (device, i, chunks->len - 1);
}
g_usleep (50 * 1000);
/* end-program */
req.type = FU_HAILUCK_CMD_I2C_END_PROGRAM;
if (!fu_device_retry (device, fu_hailuck_tp_device_cmd_cb, 100, &req, error)) {
g_prefix_error (error, "failed to end program: ");
return FALSE;
}
g_usleep (50 * 1000);
/* verify checksum */
req.type = FU_HAILUCK_CMD_I2C_VERIFY_CHECKSUM;
if (!fu_device_retry (device, fu_hailuck_tp_device_cmd_cb, 100, &req, error)) {
g_prefix_error (error, "failed to verify: ");
return FALSE;
}
g_usleep (50 * 1000);
/* signal that programming has completed */
req.type = FU_HAILUCK_CMD_I2C_PROGRAMPASS;
req.success = 0x0;
if (!fu_device_retry (device, fu_hailuck_tp_device_cmd_cb, 100, &req, error)) {
g_prefix_error (error, "failed to program: ");
return FALSE;
}
/* success! */
return TRUE;
}
static void
fu_hailuck_tp_device_init (FuHaiLuckTpDevice *self)
{
fu_device_retry_set_delay (FU_DEVICE (self), 50); /* ms */
fu_device_set_firmware_size (FU_DEVICE (self), 0x6018);
fu_device_set_protocol (FU_DEVICE (self), "com.hailuck.tp");
fu_device_set_logical_id (FU_DEVICE (self), "TP");
fu_device_set_name (FU_DEVICE (self), "Touchpad");
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_icon (FU_DEVICE (self), "input-touchpad");
fu_device_set_remove_delay (FU_DEVICE (self),
FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
}
static void
fu_hailuck_tp_device_class_init (FuHaiLuckTpDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
klass_device->write_firmware = fu_hailuck_tp_device_write_firmware;
klass_device->open = fu_hailuck_tp_device_open;
klass_device->close = fu_hailuck_tp_device_close;
klass_device->probe = fu_hailuck_tp_device_probe;
}
FuHaiLuckTpDevice *
fu_hailuck_tp_device_new (FuDevice *device)
{
FuHaiLuckTpDevice *self;
self = g_object_new (FU_TYPE_HAILUCK_TP_DEVICE,
"parent", device,
NULL);
return FU_HAILUCK_TP_DEVICE (self);
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include "fu-plugin.h"
#define FU_TYPE_HAILUCK_TP_DEVICE (fu_hailuck_tp_device_get_type ())
G_DECLARE_FINAL_TYPE (FuHaiLuckTpDevice, fu_hailuck_tp_device, FU, HAILUCK_TP_DEVICE, FuDevice)
FuHaiLuckTpDevice *fu_hailuck_tp_device_new (FuDevice *parent);

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-plugin-vfuncs.h"
#include "fu-hash.h"
#include "fu-hailuck-bl-device.h"
#include "fu-hailuck-kbd-device.h"
#include "fu-hailuck-kbd-firmware.h"
void
fu_plugin_init (FuPlugin *plugin)
{
fu_plugin_set_build_hash (plugin, FU_BUILD_HASH);
fu_plugin_add_firmware_gtype (plugin, "hailuck", FU_TYPE_HAILUCK_KBD_FIRMWARE);
fu_plugin_set_device_gtype (plugin, FU_TYPE_HAILUCK_BL_DEVICE);
fu_plugin_set_device_gtype (plugin, FU_TYPE_HAILUCK_KBD_DEVICE);
}

View File

@ -0,0 +1,25 @@
# bootloader
[DeviceInstanceId=USB\VID_0603&PID_1020]
Plugin = hailuck
GType = FuHaiLuckBlDevice
#Flags = is-bootloader
[DeviceInstanceId=USB\VID_258A&PID_001E]
Plugin = hailuck
GType = FuHaiLuckKbdDevice
Vendor = PINE64
CounterpartGuid = USB\VID_0603&PID_1020
[DeviceInstanceId=USB\VID_258A&PID_001E&MODE_KBD]
Name = Keyboard
[DeviceInstanceId=USB\VID_258A&PID_001F]
Plugin = hailuck
GType = FuHaiLuckKbdDevice
CounterpartGuid = USB\VID_0603&PID_1020
[DeviceInstanceId=USB\VID_258A&PID_000D]
Plugin = hailuck
GType = FuHaiLuckKbdDevice
CounterpartGuid = USB\VID_0603&PID_1020

View File

@ -0,0 +1,34 @@
cargs = ['-DG_LOG_DOMAIN="FuPluginNovatek"']
install_data([
'hailuck.quirk',
],
install_dir: join_paths(datadir, 'fwupd', 'quirks.d')
)
shared_module('fu_plugin_hailuck',
fu_hash,
sources : [
'fu-hailuck-common.c',
'fu-hailuck-bl-device.c',
'fu-hailuck-kbd-device.c',
'fu-hailuck-kbd-firmware.c',
'fu-hailuck-tp-device.c',
'fu-plugin-hailuck.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,
],
)

View File

@ -11,6 +11,7 @@ subdir('ebitdo')
subdir('ep963x')
subdir('fastboot')
subdir('fresco-pd')
subdir('hailuck')
subdir('iommu')
subdir('jabra')
subdir('linux-lockdown')