blob: 3f481a389015b0ccd297b4be5ead3228e63c8126 [file] [log] [blame]
/*
*
* 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 <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <unistd.h>
#include <glib.h>
#include "lib/bluetooth.h"
#include "lib/sdp.h"
#include "lib/uuid.h"
#include "src/adapter.h"
#include "src/device.h"
#include "src/log.h"
#include "src/plugin.h"
#include "src/profile.h"
#include "src/service.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 OAD_MAJOR_VERSION(x) ((x) >> 8)
#define OAD_MINOR_VERSION(x) ((x) & 0xff)
/* these codes may be returned instead of GP versions */
#define OAD_VERSION_BAD 0xfefe
#define OAD_VERSION_BUSY 0xffff
/* remote has 2 processors, 2 firmwares. */
#define OAD_GP_FIRMWARE_GFRM200 "gfrm200.gp.bin"
#define OAD_TI_FIRMWARE_GFRM200 "gfrm200.ti.bin"
#define OAD_GP_FIRMWARE_GFRM210 "gfrm210.gp.bin"
#define OAD_TI_FIRMWARE_GFRM210 "gfrm210.ti.bin"
/* remote checks in every 15 minutes */
#define OAD_UPGRADE_DELAY_SECS 1 // delay between wake and upgrade check
#define OAD_DELAY_NO_UPGRADE (8*60*60) // check for new ACS files (not likely)
#define OAD_DELAY_NO_UPGRADE_DEBUG 150 // check faster when /tmp firmware exists
#define OAD_DELAY_UPGRADING 60 // recheck in 1 minute (in case of 2 firmware update)
#define OAD_HEADER_LEN 8 // on-disk header. See OAD_Formatv7.xlsx
enum OAD_Status {
OADSTATUS_None = 0,
OADSTATUS_DataTransfer = 1,
OADSTATUS_TransferSuccess = 2,
OADSTATUS_TIActive = 0x81, // maybe flashing
OADSTATUS_GPActive = 0x82, // maybe rebooting
OADSTATUS_CRCError = 0xE0,
OADSTATUS_LVD = 0xE1, // low voltage detection
OADSTATUS_KeyPress = 0xE2, // user interrupted (not idle)
OADSTATUS_GPWrite = 0xE3, // GP write failed?
OADSTATUS_Bootloader = 0xE5, // Bootloader rejected?
OADSTATUS_ImageError = 0xE6, // Image format error?
};
enum OAD_Action {
OADACTION_GetRemoteInfo = 0,
OADACTION_TransferOADData = 1,
OADACTION_StartUpdate = 2,
};
/* firmware is upgraded in this order */
enum OAD_FirmwareType {
OADFW_Unknown = -1,
OADFW_TI = 0,
OADFW_GP = 1,
OADFW_Total = 2,
};
struct oad_fwinfo {
uint16_t version;
uint32_t size;
uint8_t header[OAD_HEADER_LEN];
char file[MAXPATHLEN];
};
struct oad_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 oad_service identity;
struct oad_service block;
time_t nextCheck;
struct oad_fwinfo disk[OADFW_Total];
struct oad_fwinfo remote[OADFW_Total];
int force_upgrade;
int retries;
int num_blocks;
int progress;
uint8_t header[OAD_HEADER_LEN];
int fd;
guint tid; // Timer id to delay starting upgrade
};
struct oad_characteristic {
struct oad_device *oaddev;
char uuid[MAX_LEN_UUID_STR + 1];
};
static GSList *oad_devices = NULL;
static struct oad_device *oad_new_device(struct btd_device *device, uint16_t id)
{
struct oad_device *oaddev;
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)
{
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 oad_block_char_write_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
{
struct oad_device *oaddev = user_data;
if (status != 0) {
error("OAD %s failed: %s", __func__, att_ecode2str(status));
return;
}
oaddev->retries = 0; // successful write
}
static void oad_identity_char_write_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
{
struct oad_device *oaddev = user_data;
if (status != 0) {
error("OAD %s failed: %s", __func__, att_ecode2str(status));
return;
}
}
static void oad_start_transfer(struct oad_device* oaddev)
{
uint8_t attr_val[OAD_HEADER_LEN+1]; // + 1 byte action
memcpy(attr_val, oaddev->header, OAD_HEADER_LEN);
attr_val[OAD_HEADER_LEN] = OADACTION_TransferOADData; /* start OAD image transfer */
gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
sizeof(attr_val), oad_identity_char_write_cb, oaddev);
}
static void oad_block_notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
{
struct oad_device *oaddev = user_data;
int retry = 0;
/* 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 firmware seek");
goto error;
}
if (read(oaddev->fd, block+2, sizeof block - 2) < 0) {
perror("OAD firmware read");
goto error;
}
int progress = 0;
if (oaddev->num_blocks > 0) progress = 100 * block_num / oaddev->num_blocks;
if (oaddev->progress != progress) {
oaddev->progress = progress;
DBG("OAD sending block %d (%d%%)", block_num, progress);
}
gatt_write_char(oaddev->attrib, oaddev->block.value_handle, block,
sizeof(block), oad_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[OAD_HEADER_LEN+1]; // + 1 byte action
memcpy(attr_val, oaddev->header, OAD_HEADER_LEN);
attr_val[OAD_HEADER_LEN] = OADACTION_StartUpdate; /* commit fw image */
DBG("OAD committing firmware");
gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
sizeof(attr_val), oad_identity_char_write_cb, oaddev);
}
break;
case OADSTATUS_GPActive:
DBG("OAD GP is active (good)");
break;
case OADSTATUS_TIActive:
DBG("OAD TI is active (good)");
break;
case OADSTATUS_CRCError:
DBG("OAD firmware transfer checksum error");
goto error;
case OADSTATUS_LVD:
DBG("OAD firmware upgrade low battery error");
goto error;
case OADSTATUS_KeyPress:
DBG("OAD firmware upgrade user interrupted");
goto error;
case OADSTATUS_GPWrite:
DBG("OAD firmware upgrade GP write error");
goto error;
case OADSTATUS_Bootloader:
DBG("OAD firmware upgrade Bootloader error");
goto error;
case OADSTATUS_ImageError:
DBG("OAD firmware upgrade Image error");
goto error;
default:
/* 0xe0 or greater appear to be unrecoverable errors */
if (status >= OADSTATUS_CRCError) {
DBG("OAD unexpected block notify error 0x%02x", status);
goto error;
}
DBG("OAD unexpected block notify status 0x%02x (ignoring)", status);
break;
}
/* success so far*/
return;
error:
DBG("OAD upgrade stopping due to error %d", status);
close(oaddev->fd);
oaddev->fd = -1;
}
static time_t oad_wallclock(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
return 0;
}
return ts.tv_sec;
}
static int oad_is_time_for_upgrade_check(struct oad_device* oaddev)
{
time_t now = oad_wallclock();
if (now < oaddev->nextCheck) {
int next = oaddev->nextCheck - now;
DBG("OAD not time for upgrade check yet (%d seconds left)", next);
return 0;
}
DBG("OAD time for upgrade check");
return 1;
}
static void oad_get_firmware_info(struct oad_device *oaddev, const char* file)
{
uint8_t header[OAD_HEADER_LEN];
int fd = open(file, O_RDONLY);
if (fd < 0) {
error("OAD open: %s: %s", file, strerror(errno));
return;
}
int len = read(fd, header, sizeof header);
if (len < 0) {
error("OAD read: %s: %s", file, strerror(errno));
close(fd);
return;
}
if (len < sizeof header) {
error("OAD short read: %s: wanted %lu, got %d", file, sizeof header, len);
close(fd);
return;
}
close(fd);
fd = -1;
/* See OAD_Formatv7.xlsx, 'Image Info' tab. 8 bytes are:
* 2 bytes checksum
* 2 bytes version
* 3 bytes size
* 1 byte id: "T" TI image, "G" GP image
*/
uint16_t version = get_le16(header + 2);
uint32_t size = get_le32(header+4) & 0x00ffffff;
int id = header[7];
enum OAD_FirmwareType type;
int index = (int)type;
switch(id) {
case 'T':
case 'U':
type = OADFW_TI;
break;
case 'G':
type = OADFW_GP;
break;
default:
type = OADFW_Unknown;
break;
}
if (type == OADFW_Unknown) {
error("OAD unknown firmware id '%c': file=%s version=%d.%d",
id, file, OAD_MAJOR_VERSION(version), OAD_MINOR_VERSION(version));
return;
}
if (version <= oaddev->disk[index].version) {
error("OAD ignoring older firmware: file=%s type=%d version=%d.%d",
file, type, OAD_MAJOR_VERSION(version), OAD_MINOR_VERSION(version));
return;
}
if (strlen(file) >= sizeof oaddev->disk[index].file) {
error("OAD firmware file name too long: %s", file);
return;
}
oaddev->disk[index].version = version;
oaddev->disk[index].size = size;
memcpy(oaddev->disk[index].header, header, sizeof oaddev->disk[index].header);
strncpy(oaddev->disk[index].file, file, sizeof oaddev->disk[index].file);
oaddev->disk[index].file[sizeof oaddev->disk[index].file - 1] = '\0';
}
static const char* oad_firmware_type_string(enum OAD_FirmwareType type)
{
switch (type) {
case OADFW_GP: return "GP";
case OADFW_TI: return "TI";
}
return "Unknown";
}
static void oad_find_firmware(struct oad_device *oaddev)
{
char name[MAX_NAME_LENGTH + 1] = "";
const char *tmp_gp;
const char *tmp_ti;
const char *etc_gp;
const char *etc_ti;
device_get_name(oaddev->device, name, sizeof(name));
if (g_strcmp0(name, "GFRM200") == 0) {
tmp_gp = "/tmp/" OAD_GP_FIRMWARE_GFRM200;
tmp_ti = "/tmp/" OAD_TI_FIRMWARE_GFRM200;
etc_gp = "/etc/remote/" OAD_GP_FIRMWARE_GFRM200;
etc_ti = "/etc/remote/" OAD_TI_FIRMWARE_GFRM200;
} else if (g_strcmp0(name, "GFRM201") == 0 ||
g_strcmp0(name, "GFRM210") == 0) {
tmp_gp = "/tmp/" OAD_GP_FIRMWARE_GFRM210;
tmp_ti = "/tmp/" OAD_TI_FIRMWARE_GFRM210;
etc_gp = "/etc/remote/" OAD_GP_FIRMWARE_GFRM210;
etc_ti = "/etc/remote/" OAD_TI_FIRMWARE_GFRM210;
} else {
error("OAD unknown model %s, skipping", name);
}
memset(oaddev->disk, 0, sizeof oaddev->disk);
/* check in tmp first, to make debugging easier */
if (access(tmp_gp, R_OK) == 0 || access(tmp_ti, R_OK) == 0) {
DBG("OAD found firmware in /tmp, ignoring /etc/remote, enabling downgrade.");
oaddev->force_upgrade = 1; // allow downgrade
oad_get_firmware_info(oaddev, tmp_gp);
oad_get_firmware_info(oaddev, tmp_ti);
} else {
oaddev->force_upgrade = 0;
oad_get_firmware_info(oaddev, etc_gp);
oad_get_firmware_info(oaddev, etc_ti);
}
int i;
for (i = 0; i < OADFW_Total; i++) {
enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
const char* typeStr = oad_firmware_type_string(type);
if (strlen(oaddev->disk[type].file) == 0) {
error("OAD no %s firmware found on disk", typeStr);
} else {
DBG("OAD found %s firmware: file=%s version=%d.%d",
typeStr, oaddev->disk[i].file,
OAD_MAJOR_VERSION(oaddev->disk[i].version),
OAD_MINOR_VERSION(oaddev->disk[i].version));
}
}
}
static void oad_check_for_upgrade(struct oad_device *oaddev)
{
if (!oad_is_time_for_upgrade_check(oaddev)) {
return;
}
oad_find_firmware(oaddev);
struct oad_fwinfo* fp = NULL;
int i;
for (i = 0; i < OADFW_Total; i++) {
enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
const char* typeStr = oad_firmware_type_string(type);
if (strlen(oaddev->disk[i].file) == 0) {
continue;
}
if (oaddev->disk[i].version == oaddev->remote[i].version ||
(!oaddev->force_upgrade &&
oaddev->disk[i].version < oaddev->remote[i].version &&
oaddev->remote[i].version != OAD_VERSION_BAD)) {
DBG("OAD %s firmware is up to date", typeStr);
} else {
fp = &oaddev->disk[i];
break;
}
}
if (fp == NULL) {
int next = oaddev->force_upgrade
? OAD_DELAY_NO_UPGRADE_DEBUG
: OAD_DELAY_NO_UPGRADE;
DBG("OAD next check is in %d seconds", next);
oaddev->nextCheck = oad_wallclock() + next;
return;
}
oaddev->nextCheck = oad_wallclock() + OAD_DELAY_UPGRADING;
DBG("OAD starting upgrade with %s", fp->file);
if (oaddev->fd != -1) {
close(oaddev->fd);
}
oaddev->fd = open(fp->file, O_RDONLY);
if (oaddev->fd < 0) {
error("OAD open: %s: %s", fp->file, strerror(errno));
return;
}
if (oaddev->tid) {
g_source_remove(oaddev->tid);
oaddev->tid = 0;
}
oaddev->progress = 0;
oaddev->retries = 0;
oaddev->num_blocks = (fp->size + 15) / 16;
memcpy(oaddev->header, fp->header, sizeof oaddev->header);
oad_start_transfer(oaddev);
}
static void oad_identity_notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
{
struct oad_device *oaddev = user_data;
/* should be at least opcode (1b) + handle (76 00) */
if (len < 3) {
error("OAD Invalid PDU received");
return;
}
/* See OAD_Formatv7.xlsx, 'Image Info' tab. 10 bytes are:
* 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[OADFW_GP].version = get_le16(pdu+3);
oaddev->remote[OADFW_GP].size = get_le32(pdu+5) & 0x00ffffff;
oaddev->remote[OADFW_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[OADFW_TI].size = get_le32(size) & 0x00ffffff;
int i;
for (i = 0; i < OADFW_Total; i++) {
enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
const char* typeStr = oad_firmware_type_string(type);
DBG("OAD %s firmware on remote: version=%d.%d",
typeStr, OAD_MAJOR_VERSION(oaddev->remote[i].version),
OAD_MINOR_VERSION(oaddev->remote[i].version));
}
oad_check_for_upgrade(oaddev);
}
static void oad_check_fwversion(struct oad_device *oaddev)
{
uint8_t attr_val[9]; // 8 byte header + 1 command byte (all zeros)
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), oad_identity_char_write_cb, oaddev);
}
static void oad_ccc_char_write_identity_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
{
struct oad_device *oaddev = user_data;
if (status != 0) {
error("OAD %s failed: %s", __func__, att_ecode2str(status));
return;
}
oaddev->identity.notify_enabled = 1;
oad_check_fwversion(oaddev);
}
static void oad_ccc_char_write_block_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
{
struct oad_device *oaddev = user_data;
if (status != 0) {
error("OAD %s failed: %s", __func__, att_ecode2str(status));
return;
}
oaddev->block.notify_enabled = 1;
oad_check_fwversion(oaddev);
}
static void oad_enable_notify(struct oad_device* oaddev)
{
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,
oad_identity_notify_handler, oaddev, NULL);
oaddev->block.attio_id =
g_attrib_register(oaddev->attrib,
ATT_OP_HANDLE_NOTIFY, oaddev->block.value_handle,
oad_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), oad_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), oad_ccc_char_write_block_cb, oaddev);
}
static gboolean oad_upgrade_timer(gpointer data)
{
struct oad_device *oaddev = data;
g_source_remove(oaddev->tid);
oaddev->tid = 0;
DBG("OAD timer fired");
oad_enable_notify(oaddev);
}
static void oad_desc_discovered_cb(uint8_t status, GSList *descs, void* user_data)
{
struct oad_characteristic *ch = user_data;
struct oad_device *oaddev = ch->oaddev;
struct GSList *list = NULL;
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(OAD_UPGRADE_DELAY_SECS, oad_upgrade_timer, oaddev);
done:
g_free(ch);
}
static void oad_discover_desc(struct oad_device *oaddev, struct gatt_char *c, struct gatt_char *c_next)
{
struct gatt_primary *prim = oaddev->oad_primary;
struct oad_characteristic *ch;
uint16_t start, end;
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 oad_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, oad_desc_discovered_cb, ch);
}
static void oad_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);
oad_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);
oad_discover_desc(oaddev, chr, next);
}
}
}
static void oad_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 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,
oad_char_discovered_cb, oaddev);
} else {
if (oad_is_time_for_upgrade_check(oaddev)) {
oad_enable_notify(oaddev);
}
}
}
static void oad_attio_disconnected_cb(gpointer user_data)
{
struct oad_device *oaddev = user_data;
GSList *l;
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;
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,
oad_attio_connected_cb, oad_attio_disconnected_cb, oaddev);
return oaddev;
}
static int oad_unregister_device(struct oad_device *oaddev)
{
if (oaddev->tid) {
g_source_remove(oaddev->tid);
oaddev->tid = 0;
}
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 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");
oaddev = oad_register_device(device, prim);
if (oaddev == NULL)
continue;
oad_devices = g_slist_append(oad_devices, oaddev);
}
return 0;
}
static void oad_remove_device(gpointer a, gpointer b)
{
struct oad_device *oaddev = a;
struct btd_device *device = b;
if (oaddev->device != device)
return;
oad_devices = g_slist_remove(oad_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 path %s", path);
g_slist_foreach(oad_devices, oad_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;
return btd_profile_register(&oad_profile);
}
static void oad_exit(void)
{
btd_profile_unregister(&oad_profile);
}
BLUETOOTH_PLUGIN_DEFINE(oad, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW, oad_init, oad_exit)