GFRM200: over-air-download (OAD) support
* mostly working code to detect and interact with OAD service
* still debugging; waiting for sample firmware to test download
* also: log hog/hid bytes (remote keypresses)
* also: battery level check
Change-Id: I309430252f7ae4314676f82648637758bb7fc52d
diff --git a/Makefile.plugins b/Makefile.plugins
index dbcb491..05c9a88 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -66,6 +66,12 @@
builtin_sources += profiles/input/hog.c profiles/input/uhid_copy.h \
profiles/input/suspend.h profiles/input/suspend-dummy.c
+builtin_modules += oad
+builtin_sources += profiles/oad/oad.c
+
+builtin_modules += battery
+builtin_sources += profiles/battery/battery.c
+
if EXPERIMENTAL
builtin_modules += health
builtin_sources += profiles/health/mcap_lib.h profiles/health/mcap_internal.h \
diff --git a/plugins/gfrm.c b/plugins/gfrm.c
index 4a542aa..780a3a4 100644
--- a/plugins/gfrm.c
+++ b/plugins/gfrm.c
@@ -165,7 +165,7 @@
}
bdaddr_type = bdaddr_and_type[6];
- baswap(&bdaddr, &bdaddr_and_type);
+ baswap(&bdaddr, (bdaddr_t*) bdaddr_and_type);
ba2str(&bdaddr, bdaddr_str);
gfrm_dev = gfrm_find_dev_by_bdaddr_type(bdaddr_type);
diff --git a/profiles/battery/battery.c b/profiles/battery/battery.c
new file mode 100644
index 0000000..1bbaa48
--- /dev/null
+++ b/profiles/battery/battery.c
@@ -0,0 +1,351 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Google, Inc. (edjames@google.com)
+ * Copyright (C) 2012 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2012 Nordic Semiconductor Inc.
+ * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+/* much lifted from input/hog.c */
+
+/* battery - read battery level */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+
+#include "src/log.h"
+
+#include "lib/uuid.h"
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/profile.h"
+#include "src/service.h"
+
+#include "src/plugin.h"
+#include "attrib/att.h"
+#include "attrib/gattrib.h"
+#include "src/attio.h"
+#include "attrib/gatt.h"
+#include "src/shared/util.h"
+
+#define BATT_UUID "0000180f-0000-1000-8000-00805f9b34fb"
+#define BATT_LEVEL_UUID "00002a19-0000-1000-8000-00805f9b34fb"
+
+//#define CHECK_TIME (24*60*60)
+#define CHECK_TIME 10
+
+struct service {
+ guint attio_id;
+ uint16_t value_handle;
+ int value;
+};
+
+struct batt_device {
+ uint16_t id;
+ struct btd_device *device;
+ GAttrib *attrib;
+ guint attioid;
+ struct gatt_primary *batt_primary;
+
+ struct service level;
+
+ time_t lastCheck;
+};
+
+static GSList *devices = NULL;
+
+static struct batt_device *batt_new_device(struct btd_device *device,
+ uint16_t id)
+{
+ struct batt_device *battdev;
+
+ DBG("BATT trace");
+ battdev = g_try_new0(struct batt_device, 1);
+ if (!battdev)
+ return NULL;
+
+ battdev->id = id;
+ battdev->device = btd_device_ref(device);
+
+ return battdev;
+}
+
+static void batt_free_device(struct batt_device *battdev)
+{
+ DBG("BATT trace");
+ btd_device_unref(battdev->device);
+ g_attrib_unref(battdev->attrib);
+ g_free(battdev->batt_primary);
+ g_free(battdev);
+}
+
+static time_t wallclock(void)
+{
+ struct timespec ts;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
+ return 0;
+ }
+ return ts.tv_sec;
+}
+
+static void level_read_char_cb(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct batt_device *battdev = user_data;
+
+ DBG("BATT trace");
+ if (status != 0) {
+ error("BATT %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+ int level = pdu[1];
+ DBG("BATT level=%d", level);
+}
+
+static void checkLevel(struct batt_device *battdev)
+{
+ battdev->lastCheck = wallclock();
+ if (battdev->level.value_handle == 0)
+ return;
+
+ gatt_read_char(battdev->attrib, battdev->level.value_handle,
+ level_read_char_cb, battdev);
+}
+
+static int isTimeForLevelCheck(struct batt_device* battdev)
+{
+ DBG("BATT trace");
+
+ time_t now = wallclock();
+ time_t dt = now - battdev->lastCheck;
+ if (dt < CHECK_TIME) {
+ DBG("BATT not time for level check yet");
+ return 0;
+ }
+ DBG("BATT time for battery check");
+ return 1;
+}
+
+static void char_discovered_cb(uint8_t status, GSList *chars, void* user_data)
+{
+ struct batt_device *battdev = user_data;
+ struct gatt_primary *prim = battdev->batt_primary;
+ bt_uuid_t img_identity_uuid, img_block_uuid;
+ GSList *l;
+ uint16_t info_handle = 0, proto_mode_handle = 0;
+
+ DBG("BATT inspecting characteristics");
+ if (status != 0) {
+ error("BATT %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+
+ for (l = chars; l; l = g_slist_next(l)) {
+ struct gatt_char *chr, *next;
+ bt_uuid_t uuid;
+ uint16_t start, end;
+
+ chr = l->data;
+ next = l->next ? l->next->data : NULL;
+
+ DBG("BATT 0x%04x UUID: %s properties: %02x",
+ chr->handle, chr->uuid, chr->properties);
+
+
+ start = chr->value_handle + 1;
+ end = (next ? next->handle - 1 : prim->range.end);
+
+ if (g_strcmp0(chr->uuid, BATT_LEVEL_UUID) == 0) {
+ battdev->level.value_handle = chr->value_handle;
+ DBG("BATT found BATT_LEVEL_UUID value_handle=0x%04x", chr->value_handle);
+ //discover_desc(battdev, chr, next);
+ if (isTimeForLevelCheck(battdev)) {
+ checkLevel(battdev);
+ }
+ }
+ }
+}
+
+static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
+{
+ struct batt_device *battdev = user_data;
+ struct gatt_primary *prim = battdev->batt_primary;
+ GSList *l;
+
+ DBG("BATT trace");
+ DBG("BATT connected");
+
+ battdev->attrib = g_attrib_ref(attrib);
+ battdev->level.value = -1;
+
+ if (battdev->level.value_handle == 0) {
+ DBG("BATT discovering characteristics");
+
+ gatt_discover_char(battdev->attrib, prim->range.start,
+ prim->range.end, NULL,
+ char_discovered_cb, battdev);
+ } else {
+ if (isTimeForLevelCheck(battdev)) {
+ checkLevel(battdev);
+ }
+ }
+}
+
+static void attio_disconnected_cb(gpointer user_data)
+{
+ struct batt_device *battdev = user_data;
+ GSList *l;
+
+ DBG("BATT trace");
+ DBG("BATT disconnected");
+
+ if (battdev->level.attio_id > 0) {
+ g_attrib_unregister(battdev->attrib, battdev->level.attio_id);
+ battdev->level.attio_id = 0;
+ }
+ if (battdev->attrib)
+ g_attrib_unref(battdev->attrib);
+ battdev->attrib = NULL;
+}
+
+static struct batt_device *batt_register_device(struct btd_device *device,
+ struct gatt_primary *prim)
+{
+ struct batt_device *battdev;
+ GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL;
+ GIOChannel *io;
+
+ DBG("BATT trace");
+ battdev = batt_new_device(device, prim->range.start);
+ if (!battdev)
+ return NULL;
+
+ battdev->batt_primary = g_memdup(prim, sizeof(*prim));
+
+ battdev->attioid = btd_device_add_attio_callback(device,
+ attio_connected_cb,
+ attio_disconnected_cb,
+ battdev);
+
+ return battdev;
+}
+
+static int batt_unregister_device(struct batt_device *battdev)
+{
+ DBG("BATT trace");
+ btd_device_remove_attio_callback(battdev->device, battdev->attioid);
+ batt_free_device(battdev);
+
+ return 0;
+}
+
+static int batt_probe(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ const char *path = device_get_path(device);
+ GSList *primaries, *l;
+
+ DBG("BATT trace");
+ DBG("BATT path %s", path);
+
+ primaries = btd_device_get_primaries(device);
+ if (primaries == NULL)
+ return -EINVAL;
+
+ for (l = primaries; l; l = g_slist_next(l)) {
+ struct gatt_primary *prim = l->data;
+ struct batt_device *battdev;
+
+ DBG("BATT uuid=%s", prim->uuid);
+ if (strcmp(prim->uuid, BATT_UUID) != 0)
+ continue;
+
+ DBG("BATT matched BATT uuid %s", prim->uuid);
+ battdev = batt_register_device(device, prim);
+ if (battdev == NULL)
+ continue;
+
+ devices = g_slist_append(devices, battdev);
+ }
+
+ return 0;
+}
+
+static void remove_device(gpointer a, gpointer b)
+{
+ struct batt_device *battdev = a;
+ struct btd_device *device = b;
+
+ DBG("BATT trace");
+ if (battdev->device != device)
+ return;
+
+ devices = g_slist_remove(devices, battdev);
+ batt_unregister_device(battdev);
+}
+
+static void batt_remove(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ const char *path = device_get_path(device);
+
+ DBG("BATT trace");
+ DBG("BATT path %s", path);
+
+ g_slist_foreach(devices, remove_device, device);
+}
+
+static struct btd_profile batt_profile = {
+ .name = "Battery",
+ .remote_uuid = BATT_UUID,
+ .device_probe = batt_probe,
+ .device_remove = batt_remove,
+};
+
+static int batt_init(void)
+{
+ int err;
+
+ DBG("BATT trace");
+ return btd_profile_register(&batt_profile);
+}
+
+static void batt_exit(void)
+{
+ DBG("BATT trace");
+ btd_profile_unregister(&batt_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(battery, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+ batt_init, batt_exit)
diff --git a/profiles/input/hog.c b/profiles/input/hog.c
index 3753343..24a1d90 100644
--- a/profiles/input/hog.c
+++ b/profiles/input/hog.c
@@ -103,6 +103,127 @@
static gboolean suspend_supported = FALSE;
static GSList *devices = NULL;
+static const char* uhidEventToButton(struct uhid_event* ep)
+{
+ if (ep->type != UHID_INPUT) {
+ error("unexpected uhid event type %d", ep->type);
+ return NULL;
+ }
+ if (ep->u.input.size < 1) {
+ error("empty uhid event");
+ return NULL;
+ }
+
+ /* see "SRS_RC1534059_V0 11.pdf" */
+ /* strings match vendor/broadcom/drivers/bt/3rdparty/embedded/brcm/linux/bthid/bthid.c */
+ switch (ep->u.input.data[0]) {
+ case 0x02: // usage page 0x01, 0x06
+ if (ep->u.input.size != 9) {
+ error("unexpected uhid size: wanted %d, got %d", 9, ep->u.input.size);
+ return NULL;
+ }
+ switch (ep->u.input.data[3]) {
+ case 0x00: return "RELEASE2";
+ case 0x1E: return "DIGIT_1";
+ case 0x1F: return "DIGIT_2";
+ case 0x20: return "DIGIT_3";
+ case 0x21: return "DIGIT_4";
+ case 0x22: return "DIGIT_5";
+ case 0x23: return "DIGIT_6";
+ case 0x24: return "DIGIT_7";
+ case 0x25: return "DIGIT_8";
+ case 0x26: return "DIGIT_9";
+ case 0x27: return "DIGIT_0";
+ case 0x28: return "OK";
+ case 0x4F: return "RIGHT";
+ case 0x50: return "LEFT";
+ case 0x51: return "DOWN";
+ case 0x52: return "UP";
+ }
+ break;
+
+ case 0x03: // usage page 0x01, 0x0c
+ if (ep->u.input.size != 3) {
+ error("unexpected uhid size: wanted %d, got %d", 3, ep->u.input.size);
+ return NULL;
+ }
+ switch (ep->u.input.data[1] | (ep->u.input.data[2] << 8)) {
+ case 0x00: return "RELEASE3";
+ case 0x30: return "TV_BOX_POWER";
+ case 0x40: return "MENU";
+ case 0x4C: return "DIGIT_DEL";
+ case 0x63: return "INPUT";
+ case 0x7F: return "TV_POWER";
+ case 0x83: return "BACK";
+ case 0x8D: return "GUIDE";
+ case 0x94: return "EXIT";
+ case 0x95: return "INFO";
+ case 0x9C: return "CH_UP";
+ case 0x9D: return "CH_DOWN";
+ case 0xB0: return "PLAY";
+ case 0xB1: return "PAUSE";
+ case 0xB2: return "RECORD";
+ case 0xB3: return "FAST_FORWARD";
+ case 0xB4: return "REWIND";
+ case 0xB5: return "SKIP_FORWARD";
+ case 0xB6: return "SKIP_BACKWARD";
+ case 0xB7: return "STOP";
+ case 0xE2: return "MUTE";
+ case 0xE9: return "VOL_UP";
+ case 0xEA: return "VOL_DOWN";
+ case 0x221: return "SEARCH";
+ case 0x224: return "PREV";
+ }
+ break;
+
+ case 0x81: // usage page 0xff, 0x00
+ if (ep->u.input.size != 2) {
+ error("unexpected uhid size: wanted %d, got %d", 2, ep->u.input.size);
+ return NULL;
+ }
+ switch (ep->u.input.data[1]) {
+ case 0x00: return "RELEASE81";
+ case 0x01: return "LIVE";
+ }
+ break;
+ }
+ return NULL;
+}
+
+static void dataToHexString(char* str, int strLen, uint8_t* data, int dataLen)
+{
+ static char* hex = "0123456789abcdef";
+ if (strLen < 1) return;
+
+ char* sp = str;
+ char* esp = str + strLen;
+ uint8_t* dp = data;
+ uint8_t* edp = data + dataLen;
+
+ while (sp + 3 < esp && dp < edp) {
+ *sp++ = hex[(*dp >> 4) & 0x0f];
+ *sp++ = hex[(*dp >> 0) & 0x0f];
+ dp++;
+ }
+ *sp = '\0';
+}
+
+static void logButton(const guint8* pdu, guint16 len, struct uhid_event* ep)
+{
+ char pduStr[1024];
+ char eventStr[1024];
+
+ dataToHexString(pduStr, sizeof pduStr, (uint8_t*) pdu, len);
+ dataToHexString(eventStr, sizeof eventStr, (uint8_t*) ep->u.input.data, ep->u.input.size);
+
+ const char* button = uhidEventToButton(ep);
+ if (button == NULL) button = "UNKNOWN"; // hthid.c again
+
+ // for backward compatibility with log parsers, use this format:
+ // BTHID bthid_write: [00241cb74c8f]Sending report to HID: {1f4102} -> KEY_DIGIT_2
+ info("BTHID bthid_write: [%s]Sending report to HID: {%s} -> KEY_%s", pduStr, eventStr, button);
+}
+
static void report_value_cb(const guint8 *pdu, guint16 len, gpointer user_data)
{
struct report *report = user_data;
@@ -134,6 +255,8 @@
ev.u.input.size = len;
}
+ logButton(pdu, len, &ev);
+
err = bt_uhid_send(hogdev->uhid, &ev);
if (err < 0) {
error("bt_uhid_send: %s (%d)", strerror(-err), -err);
diff --git a/profiles/oad/oad.c b/profiles/oad/oad.c
new file mode 100644
index 0000000..2936b6a
--- /dev/null
+++ b/profiles/oad/oad.c
@@ -0,0 +1,778 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Google, Inc. (edjames@google.com)
+ * Copyright (C) 2012 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2012 Nordic Semiconductor Inc.
+ * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+/* much lifted from input/hog.c */
+
+/* OAD - over air download of firmware to devices */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+
+#include "src/log.h"
+
+#include "lib/uuid.h"
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/profile.h"
+#include "src/service.h"
+
+#include "src/plugin.h"
+#include "attrib/att.h"
+#include "attrib/gattrib.h"
+#include "src/attio.h"
+#include "attrib/gatt.h"
+#include "src/shared/util.h"
+
+#define OAD_UUID "f000ffc0-0451-4000-b000-000000000000"
+#define OAD_IMG_IDENTITY_UUID "f000ffc1-0451-4000-b000-000000000000"
+#define OAD_IMG_BLOCK_UUID "f000ffc2-0451-4000-b000-000000000000"
+
+#define MAJOR_VERSION(x) ((x) >> 8)
+#define MINOR_VERSION(x) ((x) & 0xff)
+#define SLOT_VERSION(x) (!(MINOR_VERSION(x) & 0x1))
+
+#define FIRMWARE_SLOT0 "/tmp/gfrm200.slot0.bin"
+#define FIRMWARE_SLOT1 "/tmp/gfrm200.slot1.bin"
+
+//#define CHECK_TIME (24*60*60)
+#define CHECK_TIME 10
+
+enum OAD_Status {
+ OADSTATUS_None = 0,
+ OADSTATUS_DataTransfer = 1,
+ OADSTATUS_TransferSuccess = 2,
+ OADSTATUS_CRCError = 0xE0,
+ OADSTATUS_LVD = 0xE1, // low voltage detection
+ OADSTATUS_KeyPress = 0xE2, // user interrupted (not idle)
+ OADSTATUS_GP = 0xE3, // bootloader said no
+ OADSTATUS_Host = 0xE4, // host did something wrong (eg, timeout)
+};
+
+enum OAD_Action {
+ OADACTION_GetRemoteInfo = 0,
+ OADACTION_TransferOADData = 1,
+ OADACTION_StartUpdate = 2,
+};
+
+struct fwinfo {
+ uint16_t gp_version;
+ uint16_t ti_version;
+ uint32_t gp_size;
+ uint32_t ti_size;
+ int slot;
+};
+
+struct service {
+ guint attio_id;
+ uint16_t ccc_handle;
+ uint16_t value_handle;
+ int notify_enabled;
+};
+
+struct oad_device {
+ uint16_t id;
+ struct btd_device *device;
+ GAttrib *attrib;
+ guint attioid;
+ struct gatt_primary *oad_primary;
+
+ struct service identity;
+ struct service block;
+
+ time_t lastCheck;
+ struct fwinfo remote;
+
+ uint8_t header[8];
+ int fd;
+ guint tid; /* Timer id to delay starting upgrade */
+};
+
+struct characteristic {
+ struct oad_device *oaddev;
+ char uuid[MAX_LEN_UUID_STR + 1];
+};
+
+static GSList *devices = NULL;
+
+//#define FAKEFW 1
+#ifdef FAKEFW
+static unsigned char HEADER[] = { 0xaa, 0x55, 0x01, 0x02, 0x0d, 0x04, 0x00, 0x54, 0, 0, 0, 0, 0, 0, 0, 0, };
+#endif
+
+static struct oad_device *oad_new_device(struct btd_device *device,
+ uint16_t id)
+{
+ struct oad_device *oaddev;
+
+ DBG("OAD trace");
+ oaddev = g_try_new0(struct oad_device, 1);
+ if (!oaddev)
+ return NULL;
+
+ oaddev->id = id;
+ oaddev->device = btd_device_ref(device);
+ oaddev->fd = -1;
+
+ return oaddev;
+}
+
+static void oad_free_device(struct oad_device *oaddev)
+{
+ DBG("OAD trace");
+ if (oaddev->fd != -1) {
+ close(oaddev->fd);
+ oaddev->fd = -1;
+ }
+ btd_device_unref(oaddev->device);
+ g_attrib_unref(oaddev->attrib);
+ g_free(oaddev->oad_primary);
+ g_free(oaddev);
+}
+
+static void block_char_write_cb(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+
+ DBG("OAD trace");
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+}
+
+static void identity_char_write_cb(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+
+ DBG("OAD trace");
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+}
+
+static void block_notify_handler(const uint8_t *pdu, uint16_t len,
+ gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+
+ DBG("OAD trace");
+
+ /* should be at least opcode (1b) + handle (7a00) */
+ if (len < 3) {
+ error("OAD Invalid PDU received");
+ return;
+ }
+
+ // 2 bytes block_num + status byte
+ if (len != 6) {
+ error("OAD block notify format error: expected 6 bytes, got %d", len);
+ return;
+ }
+
+ /* request for block */
+ uint16_t block_num = get_le16(pdu + 3);
+ enum OAD_Status status = (enum OAD_Status) pdu[5];
+
+ switch (status) {
+ case OADSTATUS_DataTransfer: // send a block
+ {
+ uint8_t block[18]; // 2 bytes block_num + 16 data bytes
+ memset(block, 0, sizeof block);
+ put_le16(block_num, block);
+
+ if (lseek(oaddev->fd, 16 * block_num, SEEK_SET) < 0) {
+ perror("OAD seek");
+ close(oaddev->fd);
+ oaddev->fd = -1;
+ return;
+ }
+ if (read(oaddev->fd, block+2, sizeof block - 2) != sizeof block - 2) {
+ perror("OAD short read");
+ close(oaddev->fd);
+ oaddev->fd = -1;
+ return;
+ }
+#ifdef FAKEFW
+if (block_num == 0) {
+ memcpy(block+2, HEADER, sizeof block - 2);
+} else {
+ memset(block+2, 0, sizeof block - 2);
+}
+#endif
+
+ DBG("OAD sending block %d", block_num);
+ gatt_write_char(oaddev->attrib, oaddev->block.value_handle, block,
+ sizeof(block), block_char_write_cb, oaddev);
+ }
+ break;
+ case OADSTATUS_TransferSuccess: // OK, successful transfer
+ {
+ DBG("OAD firmware transfer successful");
+ close(oaddev->fd);
+ oaddev->fd = -1;
+
+ uint8_t attr_val[9];
+ memset(attr_val, 0, sizeof attr_val);
+ attr_val[8] = OADACTION_StartUpdate; /* commit fw image */
+ DBG("OAD committing firmware");
+ gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
+ sizeof(attr_val), identity_char_write_cb, oaddev);
+ }
+ break;
+ case OADSTATUS_CRCError: // checksum error
+ DBG("OAD firmware transfer checksum error");
+ close(oaddev->fd);
+ oaddev->fd = -1;
+ break;
+ case OADSTATUS_LVD: // ???
+ case OADSTATUS_KeyPress: // ???
+ case OADSTATUS_GP: // ???
+ case OADSTATUS_Host: // ???
+ default:
+ DBG("OAD unexpected block notify status 0x%02x", status);
+ close(oaddev->fd);
+ oaddev->fd = -1;
+ break;
+ }
+}
+
+static time_t wallclock(void)
+{
+ struct timespec ts;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
+ return 0;
+ }
+ return ts.tv_sec;
+}
+
+static int isTimeForUpgradeCheck(struct oad_device* oaddev)
+{
+ DBG("OAD trace");
+
+ time_t now = wallclock();
+
+ time_t dt = now - oaddev->lastCheck;
+ if (dt < CHECK_TIME) {
+ DBG("OAD not time for upgrade check yet");
+ return 0;
+ }
+ DBG("OAD time for upgrade check");
+ return 1;
+}
+
+static void checkForUpgrade(struct oad_device *oaddev)
+{
+ DBG("OAD trace");
+
+ if (!isTimeForUpgradeCheck(oaddev)) {
+ return;
+ }
+
+ oaddev->lastCheck = wallclock();
+
+ int slot2 = !oaddev->remote.slot; // alternate between slot 0 and 1
+
+ char* fw_file = slot2 ? FIRMWARE_SLOT1 : FIRMWARE_SLOT0;
+ int fd = open(fw_file, O_RDONLY);
+ if (fd < 0) {
+ error("OAD open: %s: %s", fw_file, strerror(errno));
+ return;
+ }
+ int len = read(fd, oaddev->header, sizeof (oaddev->header));
+#ifdef FAKEFW
+memcpy(oaddev->header, HEADER, sizeof oaddev->header);
+#endif
+ if (len < 0) {
+ error("OAD read: %s: %s", fw_file, strerror(errno));
+ close(fd);
+ return;
+ }
+
+ if (len < sizeof oaddev->header) {
+ error("OAD short read: %s: wanted %d, got %d", fw_file, sizeof oaddev->header, len);
+ close(fd);
+ return;
+ }
+
+ /* 8 bytes of header appear to be:
+ * 2 bytes checksum
+ * 2 bytes version
+ * 3 bytes size
+ * 1 byte type: "T" TI image, "G" GP image
+ */
+ uint16_t version = get_le16(oaddev->header + 2);
+ int type = oaddev->header[7];
+ if (type == 'T') {
+ int slot = SLOT_VERSION(version);
+ DBG("OAD firmware on disk: file=%s ti_version=%d.%d slot=%d",
+ fw_file, MAJOR_VERSION(version), MINOR_VERSION(version), slot);
+
+ if (version <= oaddev->remote.ti_version) {
+ DBG("OAD TI firmware is up to date");
+ close(fd);
+ return;
+ }
+ } else if (type == 'G') {
+ DBG("OAD firmware on disk: file=%s gp_version=%d.%d",
+ fw_file, MAJOR_VERSION(version), MINOR_VERSION(version));
+ if (version <= oaddev->remote.gp_version) {
+ DBG("OAD GP firmware is up to date");
+ close(fd);
+ return;
+ }
+ } else {
+ error("OAD firmware on disk: unknown type '%c': file=%s version=%d.%d",
+ type, fw_file, MAJOR_VERSION(version), MINOR_VERSION(version));
+ close(fd);
+ return;
+ }
+
+ DBG("OAD starting upgrade");
+ if (oaddev->fd != -1) {
+ close(oaddev->fd);
+ }
+ oaddev->fd = fd; /* fd is left open */
+ if (oaddev->tid) {
+ g_source_remove(oaddev->tid);
+ }
+
+ uint8_t attr_val[9];
+ memcpy(attr_val, oaddev->header, sizeof oaddev->header);
+ attr_val[8] = OADACTION_TransferOADData; /* start OAD image transfer */
+
+ gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
+ sizeof(attr_val), identity_char_write_cb, oaddev);
+}
+
+static void identity_notify_handler(const uint8_t *pdu, uint16_t len,
+ gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+
+ DBG("OAD trace");
+
+ /* should be at least opcode (1b) + handle (76 00) */
+ if (len < 3) {
+ error("OAD Invalid PDU received");
+ return;
+ }
+
+ /* Next 10 bytes appear to be:
+ * char gp_version[2]; // 09 00 is 0.9
+ * char gp_size[3]; // 00 00 01 is 0x010000 == 64k
+ * char ti_version[2]; // 2a 00 is 0.42
+ * char ti_size[3]; // 00 00 01 is 0x010000 == 64k
+ */
+
+ if (len != 13) {
+ error("OAD identity data format error: expected 13 bytes, got %d", len);
+ return;
+ }
+
+ oaddev->remote.gp_version = get_le16(pdu+3);
+ oaddev->remote.gp_size = get_le32(pdu+5) & 0x00ffffff;
+ oaddev->remote.slot = SLOT_VERSION(oaddev->remote.gp_version);
+ oaddev->remote.ti_version = get_le16(pdu+8);
+ unsigned char size[4];
+ memcpy(size, pdu+10, 3);
+ size[3] = 0; // need 4 bytes for get_le32
+ oaddev->remote.ti_size = get_le32(size);
+
+ DBG("OAD firmware on remote: gp_version=%d.%d gp_size=%d ti_version=%d.%d ti_size=%d slot=%d",
+ MAJOR_VERSION(oaddev->remote.gp_version), MINOR_VERSION(oaddev->remote.gp_version),
+ oaddev->remote.gp_size,
+ MAJOR_VERSION(oaddev->remote.ti_version), MINOR_VERSION(oaddev->remote.ti_version),
+ oaddev->remote.ti_size,
+ oaddev->remote.slot);
+
+ checkForUpgrade(oaddev);
+}
+
+static void check_fwversion(struct oad_device *oaddev)
+{
+ uint8_t attr_val[9]; // 8 byte header + 1 command byte (all zeros)
+
+ DBG("OAD trace");
+
+ if (!oaddev->identity.notify_enabled || !oaddev->block.notify_enabled) {
+ return;
+ }
+
+ DBG("OAD sending fw version request");
+ memset(attr_val, 0, sizeof attr_val);
+ attr_val[8] = OADACTION_GetRemoteInfo; /* trigger fw version notify */
+ gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
+ sizeof(attr_val), identity_char_write_cb, oaddev);
+}
+
+static void ccc_char_write_identity_cb(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+
+ DBG("OAD trace");
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+ oaddev->identity.notify_enabled = 1;
+
+ check_fwversion(oaddev);
+}
+
+static void ccc_char_write_block_cb(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+
+ DBG("OAD trace");
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+ oaddev->block.notify_enabled = 1;
+
+ check_fwversion(oaddev);
+}
+
+static void enableNotify(struct oad_device* oaddev)
+{
+ DBG("OAD trace");
+ if (oaddev->identity.ccc_handle == 0 || oaddev->block.ccc_handle == 0 ||
+ oaddev->identity.value_handle == 0 || oaddev->block.value_handle == 0) {
+ error("OAD discovery not complete, cannot enable notifiers");
+ return;
+ }
+
+ oaddev->identity.attio_id =
+ g_attrib_register(oaddev->attrib,
+ ATT_OP_HANDLE_NOTIFY, oaddev->identity.value_handle,
+ identity_notify_handler, oaddev, NULL);
+ oaddev->block.attio_id =
+ g_attrib_register(oaddev->attrib,
+ ATT_OP_HANDLE_NOTIFY, oaddev->block.value_handle,
+ block_notify_handler, oaddev, NULL);
+
+ uint8_t attr_val[2];
+ put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, attr_val);
+ DBG("OAD enabling notify for OAD_IMG_IDENTITY_UUID");
+ gatt_write_char(oaddev->attrib, oaddev->identity.ccc_handle, attr_val,
+ sizeof(attr_val), ccc_char_write_identity_cb, oaddev);
+ DBG("OAD enabling notify for OAD_IMG_BLOCK_UUID");
+ gatt_write_char(oaddev->attrib, oaddev->block.ccc_handle, attr_val,
+ sizeof(attr_val), ccc_char_write_block_cb, oaddev);
+}
+
+static gboolean upgrade_timer(gpointer data)
+{
+ struct oad_device *oaddev = data;
+
+ g_source_remove(oaddev->tid);
+ oaddev->tid = 0;
+
+ DBG("OAD timer fired");
+ enableNotify(oaddev);
+}
+
+static void desc_discovered_cb(uint8_t status, GSList *descs, void* user_data)
+{
+ struct characteristic *ch = user_data;
+ struct oad_device *oaddev = ch->oaddev;
+ struct GSList *list = NULL;
+
+ DBG("OAD trace");
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ goto done;
+ }
+
+ for (; descs; descs = descs->next) {
+ struct gatt_desc *desc = descs->data;
+
+ if (desc->uuid16 != GATT_CLIENT_CHARAC_CFG_UUID)
+ continue;
+
+ if (g_strcmp0(ch->uuid, OAD_IMG_IDENTITY_UUID) == 0) {
+ DBG("OAD found OAD_IMG_IDENTITY_UUID ccc_handle=0x%04x", desc->handle);
+ oaddev->identity.ccc_handle = desc->handle;
+
+ } else if (g_strcmp0(ch->uuid, OAD_IMG_BLOCK_UUID) == 0) {
+ DBG("OAD found OAD_IMG_BLOCK_UUID ccc_handle=0x%04x", desc->handle);
+ oaddev->block.ccc_handle = desc->handle;
+ }
+ }
+ if (oaddev->identity.ccc_handle && oaddev->block.ccc_handle)
+ oaddev->tid = g_timeout_add_seconds(10, upgrade_timer, oaddev);
+
+done:
+ g_free(ch);
+}
+
+static void discover_desc(struct oad_device *oaddev, struct gatt_char *c,
+ struct gatt_char *c_next)
+{
+ struct gatt_primary *prim = oaddev->oad_primary;
+ struct characteristic *ch;
+ uint16_t start, end;
+
+ DBG("OAD trace");
+ start = c->value_handle + 1;
+
+ if (c_next != NULL) {
+ if (start == c_next->handle)
+ return;
+ end = c_next->handle - 1;
+ } else if (c->value_handle != prim->range.end) {
+ end = prim->range.end;
+ } else {
+ return;
+ }
+
+ ch = g_new0(struct characteristic, 1);
+ ch->oaddev = oaddev;
+ memcpy(ch->uuid, c->uuid, sizeof(c->uuid));
+
+ DBG("OAD discovering descriptors start=0x%04x end=0x%04x", start, end);
+
+ gatt_discover_desc(oaddev->attrib, start, end, NULL, desc_discovered_cb, ch);
+}
+
+static void char_discovered_cb(uint8_t status, GSList *chars, void* user_data)
+{
+ struct oad_device *oaddev = user_data;
+ struct gatt_primary *prim = oaddev->oad_primary;
+ bt_uuid_t img_identity_uuid, img_block_uuid;
+ GSList *l;
+ uint16_t info_handle = 0, proto_mode_handle = 0;
+
+ DBG("OAD inspecting characteristics");
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+
+ for (l = chars; l; l = g_slist_next(l)) {
+ struct gatt_char *chr, *next;
+ bt_uuid_t uuid;
+ uint16_t start, end;
+
+ chr = l->data;
+ next = l->next ? l->next->data : NULL;
+
+ DBG("OAD 0x%04x UUID: %s properties: %02x",
+ chr->handle, chr->uuid, chr->properties);
+
+
+ start = chr->value_handle + 1;
+ end = (next ? next->handle - 1 : prim->range.end);
+
+ if (g_strcmp0(chr->uuid, OAD_IMG_IDENTITY_UUID) == 0) {
+ oaddev->identity.value_handle = chr->value_handle;
+ DBG("OAD found OAD_IMG_IDENTITY_UUID value_handle=0x%04x", chr->value_handle);
+ discover_desc(oaddev, chr, next);
+ } else if (g_strcmp0(chr->uuid, OAD_IMG_BLOCK_UUID) == 0) {
+ oaddev->block.value_handle = chr->value_handle;
+ DBG("OAD found OAD_IMG_BLOCK_UUID value_handle=0x%04x", chr->value_handle);
+ discover_desc(oaddev, chr, next);
+ }
+
+ }
+}
+
+static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+ struct gatt_primary *prim = oaddev->oad_primary;
+ GSList *l;
+
+ DBG("OAD trace");
+ DBG("OAD connected");
+
+ oaddev->attrib = g_attrib_ref(attrib);
+ oaddev->identity.notify_enabled = 0;
+ oaddev->block.notify_enabled = 0;
+
+ if (oaddev->identity.ccc_handle == 0 || oaddev->block.ccc_handle == 0 ||
+ oaddev->identity.value_handle == 0 || oaddev->block.value_handle == 0) {
+ DBG("OAD discovering characteristics");
+
+ gatt_discover_char(oaddev->attrib, prim->range.start,
+ prim->range.end, NULL,
+ char_discovered_cb, oaddev);
+ } else {
+ if (isTimeForUpgradeCheck(oaddev)) {
+ enableNotify(oaddev);
+ }
+ }
+}
+
+static void attio_disconnected_cb(gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+ GSList *l;
+
+ DBG("OAD trace");
+ DBG("OAD disconnected");
+
+ if (oaddev->identity.attio_id > 0) {
+ g_attrib_unregister(oaddev->attrib, oaddev->identity.attio_id);
+ oaddev->identity.attio_id = 0;
+ }
+ if (oaddev->block.attio_id > 0) {
+ g_attrib_unregister(oaddev->attrib, oaddev->block.attio_id);
+ oaddev->block.attio_id = 0;
+ }
+ if (oaddev->attrib)
+ g_attrib_unref(oaddev->attrib);
+ oaddev->attrib = NULL;
+}
+
+static struct oad_device *oad_register_device(struct btd_device *device,
+ struct gatt_primary *prim)
+{
+ struct oad_device *oaddev;
+ GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL;
+ GIOChannel *io;
+
+ DBG("OAD trace");
+ oaddev = oad_new_device(device, prim->range.start);
+ if (!oaddev)
+ return NULL;
+
+ oaddev->oad_primary = g_memdup(prim, sizeof(*prim));
+
+ oaddev->attioid = btd_device_add_attio_callback(device,
+ attio_connected_cb,
+ attio_disconnected_cb,
+ oaddev);
+
+ return oaddev;
+}
+
+static int oad_unregister_device(struct oad_device *oaddev)
+{
+ DBG("OAD trace");
+ btd_device_remove_attio_callback(oaddev->device, oaddev->attioid);
+ oad_free_device(oaddev);
+
+ return 0;
+}
+
+static int oad_probe(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ const char *path = device_get_path(device);
+ GSList *primaries, *l;
+
+ DBG("OAD trace");
+ DBG("OAD path %s", path);
+
+ primaries = btd_device_get_primaries(device);
+ if (primaries == NULL)
+ return -EINVAL;
+
+ for (l = primaries; l; l = g_slist_next(l)) {
+ struct gatt_primary *prim = l->data;
+ struct oad_device *oaddev;
+
+ DBG("OAD uuid=%s", prim->uuid);
+ if (strcmp(prim->uuid, OAD_UUID) != 0)
+ continue;
+
+ DBG("OAD matched OAD uuid", prim->uuid);
+ oaddev = oad_register_device(device, prim);
+ if (oaddev == NULL)
+ continue;
+
+ devices = g_slist_append(devices, oaddev);
+ }
+
+ return 0;
+}
+
+static void remove_device(gpointer a, gpointer b)
+{
+ struct oad_device *oaddev = a;
+ struct btd_device *device = b;
+
+ DBG("OAD trace");
+ if (oaddev->device != device)
+ return;
+
+ devices = g_slist_remove(devices, oaddev);
+ oad_unregister_device(oaddev);
+}
+
+static void oad_remove(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ const char *path = device_get_path(device);
+
+ DBG("OAD trace");
+ DBG("OAD path %s", path);
+
+ g_slist_foreach(devices, remove_device, device);
+}
+
+static struct btd_profile oad_profile = {
+ .name = "OAD",
+ .remote_uuid = OAD_UUID,
+ .device_probe = oad_probe,
+ .device_remove = oad_remove,
+};
+
+static int oad_init(void)
+{
+ int err;
+
+ DBG("OAD trace");
+ return btd_profile_register(&oad_profile);
+}
+
+static void oad_exit(void)
+{
+ DBG("OAD trace");
+ btd_profile_unregister(&oad_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(oad, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+ oad_init, oad_exit)