blob: 6643022b2bcaaeab0d337c6f7b0c7d5557af7171 [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2014 Google Inc.
*
*
* 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
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.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"
#define BD_ADDR_FIFO "/tmp/bd-addr-fifo"
struct gfrm_device {
char *name;
uint8_t bdaddr_type;
uint16_t vid;
uint16_t pid;
};
static struct gfrm_device gfrm_devs[] = {
{ "GFRM100", BDADDR_BREDR, 0x58, 0x2000 },
{ "GFRM200", BDADDR_LE_PUBLIC, 0x471, 0x2210 },
{ "HID AdvRemote", BDADDR_LE_PUBLIC, 0xD, 0x0 },
};
static struct gfrm_device *gfrm_find_dev_by_bdaddr_type(uint8_t bdaddr_type)
{
uint16_t i;
for (i = 0; i < G_N_ELEMENTS(gfrm_devs); ++i) {
if (bdaddr_type == gfrm_devs[i].bdaddr_type) {
return &gfrm_devs[i];
}
}
return NULL;
}
static struct gfrm_device *gfrm_find_dev_by_vid_pid(uint16_t vid, uint16_t pid)
{
uint16_t i;
for (i = 0; i < G_N_ELEMENTS(gfrm_devs); ++i) {
if (vid == gfrm_devs[i].vid && pid == gfrm_devs[i].pid) {
return &gfrm_devs[i];
}
}
return NULL;
}
static ssize_t gfrm_pincb(struct btd_adapter *adapter, struct btd_device *device,
char *pinbuf, bool *display, unsigned int attempt)
{
uint16_t vid, pid;
char addr[18], name[25];
/*
* Only try the pin code once per device.
* If it's not correct then it's an unknown device.
*/
if (attempt > 1)
return 0;
ba2str(device_get_address(device), addr);
vid = btd_device_get_vendor(device);
pid = btd_device_get_product(device);
DBG("vendor 0x%x product 0x%x", vid, pid);
device_get_name(device, name, sizeof(name));
name[sizeof(name) - 1] = 0;
if (gfrm_find_dev_by_vid_pid(vid, pid)) {
DBG("Forcing PIN 0000 on %s at %s", name, addr);
memcpy(pinbuf, "0000", 4);
return 4;
}
return 0;
}
static void gfrm_disconnect_cb(struct btd_device *device, uint8_t reason)
{
bdaddr_t bdaddr;
uint8_t bdaddr_type;
uint16_t vid, pid;
char addr[18], name[25];
memcpy(&bdaddr, device_get_address(device), sizeof(bdaddr));
ba2str(&bdaddr, addr);
bdaddr_type = btd_device_get_bdaddr_type(device);
vid = btd_device_get_vendor(device);
pid = btd_device_get_product(device);
device_get_name(device, name, sizeof(name));
name[sizeof(name) - 1] = 0;
if (gfrm_find_dev_by_vid_pid(vid, pid)) {
DBG("%s [%s] disconnected: reason %u", name, addr, reason);
}
}
static void gfrm_conn_fail_cb(struct btd_device *device, uint8_t reason)
{
bdaddr_t bdaddr;
uint8_t bdaddr_type;
uint16_t vid, pid;
char addr[18], name[25];
memcpy(&bdaddr, device_get_address(device), sizeof(bdaddr));
ba2str(&bdaddr, addr);
bdaddr_type = btd_device_get_bdaddr_type(device);
vid = btd_device_get_vendor(device);
pid = btd_device_get_product(device);
device_get_name(device, name, sizeof(name));
name[sizeof(name) - 1] = 0;
if (gfrm_find_dev_by_vid_pid(vid, pid)) {
DBG("%s [%s] connection failed: reason %u", name, addr, reason);
}
}
static void gfrm_start_bd_addr_fifo_watch(
gboolean (*callback)(GIOChannel *, GIOCondition, gpointer))
{
int fd;
GIOChannel *io;
if ((fd = open(BD_ADDR_FIFO, O_RDONLY | O_NONBLOCK)) < 0) {
DBG("open failed");
return;
}
if ((io = g_io_channel_unix_new(fd)) == NULL) {
DBG("g_io_channel_unix_new failed");
close(fd);
return;
}
if (!g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
callback, NULL)) {
DBG("g_io_add_watch failed");
}
g_io_channel_set_close_on_unref(io, TRUE);
g_io_channel_unref(io);
}
static gboolean gfrm_add_device(GIOChannel *io, GIOCondition condition,
gpointer data)
{
int fd;
uint8_t bdaddr_and_type[7] = { 0 };
uint8_t bdaddr_type;
bdaddr_t bdaddr;
char bdaddr_str[18];
struct gfrm_device *gfrm_dev;
struct btd_adapter *adapter;
struct btd_device *device;
if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
DBG("io error: condition %u", condition);
}
if (!(condition & (G_IO_IN))) {
DBG("pipe has no data to read");
goto reopen;
}
/* Read BD address from FIFO */
fd = g_io_channel_unix_get_fd(io);
if (read(fd, &bdaddr_and_type, sizeof(bdaddr_and_type)) != 7) {
DBG("read failed");
goto reopen;
}
bdaddr_type = bdaddr_and_type[6];
baswap(&bdaddr, (bdaddr_t*) bdaddr_and_type);
ba2str(&bdaddr, bdaddr_str);
gfrm_dev = gfrm_find_dev_by_bdaddr_type(bdaddr_type);
if (!gfrm_dev)
goto reopen;
DBG("Discovered %s at %s", gfrm_dev->name, bdaddr_str);
/* Add device to BlueZ stack */
adapter = btd_adapter_get_default();
if (!adapter) {
DBG("btd_adapter_get_default failed");
goto reopen;
}
device = btd_adapter_find_device(adapter, &bdaddr, bdaddr_type);
if (device) {
DBG("device %p paired %d bonded %d bonding %d", device,
device_is_paired(device, bdaddr_type),
device_is_bonded(device, bdaddr_type),
device_is_bonding(device, NULL));
if (device_is_bonding(device, NULL))
goto reopen;
DBG("Removing %s @ %s", gfrm_dev->name, bdaddr_str);
btd_device_set_temporary(device, TRUE);
btd_adapter_remove_device(adapter, device);
}
DBG("Adding %s @ %s", gfrm_dev->name, bdaddr_str);
device = btd_adapter_get_device(adapter, &bdaddr, bdaddr_type);
if (device) {
btd_device_device_set_name(device, gfrm_dev->name);
btd_device_set_pnpid(device, 0x1, gfrm_dev->vid,
gfrm_dev->pid, 0x0);
btd_device_set_temporary(device, FALSE);
}
/*
* Pairing the remote is handled in Python script:
* test/gfiber-agent
*/
reopen:
/* Reopen the FIFO (new fd, io channel, and watch) */
gfrm_start_bd_addr_fifo_watch(gfrm_add_device);
/*
* Return FALSE, so that the watch on the old io channel is removed.
* That, in turn, triggers closing of io channel and file descriptor.
*/
return FALSE;
}
static int gfrm_probe(struct btd_adapter *adapter)
{
btd_adapter_register_pin_cb(adapter, gfrm_pincb);
}
static void gfrm_remove(struct btd_adapter *adapter)
{
btd_adapter_unregister_pin_cb(adapter, gfrm_pincb);
}
static struct btd_adapter_driver gfrm_driver = {
.name = "gfrm",
.probe = gfrm_probe,
.remove = gfrm_remove,
};
static int gfrm_init(void)
{
btd_add_disconnect_cb(gfrm_disconnect_cb);
btd_add_conn_fail_cb(gfrm_conn_fail_cb);
btd_register_adapter_driver(&gfrm_driver);
/* Create FIFO for IR-assisted pairing */
if (mkfifo(BD_ADDR_FIFO, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
S_IROTH | S_IWOTH) < 0 && errno != EEXIST) {
DBG("mkfifo failed");
return 0;
}
/* Set file mode bits on the fifo since umask clears group/other */
chmod(BD_ADDR_FIFO, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
S_IROTH | S_IWOTH);
/* Start watching the FIFO */
gfrm_start_bd_addr_fifo_watch(gfrm_add_device);
return 0;
}
static void gfrm_exit(void)
{
btd_unregister_adapter_driver(&gfrm_driver);
btd_remove_conn_fail_cb(gfrm_conn_fail_cb);
btd_remove_disconnect_cb(gfrm_disconnect_cb);
}
BLUETOOTH_PLUGIN_DEFINE(gfrm, VERSION,
BLUETOOTH_PLUGIN_PRIORITY_HIGH, gfrm_init, gfrm_exit)