/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2009 Bastien Nocera * Copyright (C) 2011 Antonio Ospite * Copyright (C) 2013 Szymon Janc * * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include "lib/bluetooth.h" #include "lib/uuid.h" #include "src/adapter.h" #include "src/device.h" #include "src/plugin.h" #include "src/log.h" static const struct { const char *name; uint16_t source; uint16_t vid; uint16_t pid; uint16_t version; } devices[] = { { .name = "PLAYSTATION(R)3 Controller", .source = 0x0002, .vid = 0x054c, .pid = 0x0268, .version = 0x0000, }, }; static struct udev *ctx = NULL; static struct udev_monitor *monitor = NULL; static guint watch_id = 0; static int get_device_bdaddr(int fd, bdaddr_t *bdaddr) { uint8_t buf[18]; int ret; memset(buf, 0, sizeof(buf)); buf[0] = 0xf2; ret = ioctl(fd, HIDIOCGFEATURE(sizeof(buf)), buf); if (ret < 0) { error("sixaxis: failed to read device address (%s)", strerror(errno)); return ret; } baswap(bdaddr, (bdaddr_t *) (buf + 4)); return 0; } static int get_master_bdaddr(int fd, bdaddr_t *bdaddr) { uint8_t buf[8]; int ret; memset(buf, 0, sizeof(buf)); buf[0] = 0xf5; ret = ioctl(fd, HIDIOCGFEATURE(sizeof(buf)), buf); if (ret < 0) { error("sixaxis: failed to read master address (%s)", strerror(errno)); return ret; } baswap(bdaddr, (bdaddr_t *) (buf + 2)); return 0; } static int set_master_bdaddr(int fd, const bdaddr_t *bdaddr) { uint8_t buf[8]; int ret; buf[0] = 0xf5; buf[1] = 0x01; baswap((bdaddr_t *) (buf + 2), bdaddr); ret = ioctl(fd, HIDIOCSFEATURE(sizeof(buf)), buf); if (ret < 0) error("sixaxis: failed to write master address (%s)", strerror(errno)); return ret; } static uint8_t calc_leds_bitmap(int number) { uint8_t bitmap = 0; /* TODO we could support up to 10 (1 + 2 + 3 + 4) */ if (number > 7) return bitmap; if (number > 4) { bitmap |= 0x10; number -= 4; } bitmap |= 0x01 << number; return bitmap; } static void set_leds_hidraw(int fd, uint8_t leds_bitmap) { /* * the total time the led is active (0xff means forever) * | duty_length: cycle time in deciseconds (0 - "blink very fast") * | | ??? (Maybe a phase shift or duty_length multiplier?) * | | | % of duty_length led is off (0xff means 100%) * | | | | % of duty_length led is on (0xff means 100%) * | | | | | * 0xff, 0x27, 0x10, 0x00, 0x32, */ uint8_t leds_report[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, /* rumble values TBD */ 0x00, 0x00, 0x00, 0x00, 0x00, /* LED_1=0x02, LED_2=0x04 ... */ 0xff, 0x27, 0x10, 0x00, 0x32, /* LED_4 */ 0xff, 0x27, 0x10, 0x00, 0x32, /* LED_3 */ 0xff, 0x27, 0x10, 0x00, 0x32, /* LED_2 */ 0xff, 0x27, 0x10, 0x00, 0x32, /* LED_1 */ 0x00, 0x00, 0x00, 0x00, 0x00, }; int ret; leds_report[10] = leds_bitmap; ret = write(fd, leds_report, sizeof(leds_report)); if (ret == sizeof(leds_report)) return; if (ret < 0) error("sixaxis: failed to set LEDS (%s)", strerror(errno)); else error("sixaxis: failed to set LEDS (%d bytes written)", ret); } static gboolean setup_leds(GIOChannel *channel, GIOCondition cond, gpointer user_data) { int number = GPOINTER_TO_INT(user_data); uint8_t bitmap; int fd; if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) return FALSE; DBG("number %d", number); fd = g_io_channel_unix_get_fd(channel); bitmap = calc_leds_bitmap(number); if (bitmap != 0) set_leds_hidraw(fd, bitmap); return FALSE; } static bool setup_device(int fd, int index, struct btd_adapter *adapter) { char device_addr[18], master_addr[18], adapter_addr[18]; bdaddr_t device_bdaddr, master_bdaddr; const bdaddr_t *adapter_bdaddr; struct btd_device *device; if (get_device_bdaddr(fd, &device_bdaddr) < 0) return false; if (get_master_bdaddr(fd, &master_bdaddr) < 0) return false; /* This can happen if controller was plugged while already connected * eg. to charge up battery. * Don't set LEDs in that case, hence return false */ device = btd_adapter_find_device(adapter, &device_bdaddr, BDADDR_BREDR); if (device && btd_device_is_connected(device)) return false; adapter_bdaddr = btd_adapter_get_address(adapter); if (bacmp(adapter_bdaddr, &master_bdaddr)) { if (set_master_bdaddr(fd, adapter_bdaddr) < 0) return false; } ba2str(&device_bdaddr, device_addr); ba2str(&master_bdaddr, master_addr); ba2str(adapter_bdaddr, adapter_addr); DBG("remote %s old_master %s new_master %s", device_addr, master_addr, adapter_addr); device = btd_adapter_get_device(adapter, &device_bdaddr, BDADDR_BREDR); if (g_slist_find_custom(btd_device_get_uuids(device), HID_UUID, (GCompareFunc)strcasecmp)) { DBG("device %s already known, skipping", device_addr); return true; } info("sixaxis: setting up new device"); btd_device_device_set_name(device, devices[index].name); btd_device_set_pnpid(device, devices[index].source, devices[index].vid, devices[index].pid, devices[index].version); btd_device_set_temporary(device, FALSE); return true; } static int get_js_number(struct udev_device *udevice) { struct udev_list_entry *devices, *dev_list_entry; struct udev_enumerate *enumerate; struct udev_device *hid_parent; const char *hidraw_node; const char *hid_phys; int number = 0; hid_parent = udev_device_get_parent_with_subsystem_devtype(udevice, "hid", NULL); hid_phys = udev_device_get_property_value(hid_parent, "HID_PHYS"); hidraw_node = udev_device_get_devnode(udevice); if (!hid_phys || !hidraw_node) return 0; enumerate = udev_enumerate_new(udev_device_get_udev(udevice)); udev_enumerate_add_match_sysname(enumerate, "js*"); udev_enumerate_scan_devices(enumerate); devices = udev_enumerate_get_list_entry(enumerate); udev_list_entry_foreach(dev_list_entry, devices) { struct udev_device *input_parent; struct udev_device *js_dev; const char *input_phys; const char *devname; devname = udev_list_entry_get_name(dev_list_entry); js_dev = udev_device_new_from_syspath( udev_device_get_udev(udevice), devname); input_parent = udev_device_get_parent_with_subsystem_devtype( js_dev, "input", NULL); if (!input_parent) goto next; /* check if this is the joystick relative to the hidraw device * above */ input_phys = udev_device_get_sysattr_value(input_parent, "phys"); if (!input_phys) goto next; if (!strcmp(input_phys, hid_phys)) { number = atoi(udev_device_get_sysnum(js_dev)); /* joystick numbers start from 0, leds from 1 */ number++; udev_device_unref(js_dev); break; } next: udev_device_unref(js_dev); } udev_enumerate_unref(enumerate); return number; } static int get_supported_device(struct udev_device *udevice, uint16_t *bus) { struct udev_device *hid_parent; uint16_t vid, pid; const char *hid_id; guint i; hid_parent = udev_device_get_parent_with_subsystem_devtype(udevice, "hid", NULL); if (!hid_parent) return -1; hid_id = udev_device_get_property_value(hid_parent, "HID_ID"); if (sscanf(hid_id, "%hx:%hx:%hx", bus, &vid, &pid) != 3) return -1; for (i = 0; i < G_N_ELEMENTS(devices); i++) { if (devices[i].vid == vid && devices[i].pid == pid) return i; } return -1; } static void device_added(struct udev_device *udevice) { struct btd_adapter *adapter; GIOChannel *io; uint16_t bus; int index; int fd; adapter = btd_adapter_get_default(); if (!adapter) return; index = get_supported_device(udevice, &bus); if (index < 0) return; info("sixaxis: compatible device connected: %s (%04X:%04X)", devices[index].name, devices[index].vid, devices[index].pid); fd = open(udev_device_get_devnode(udevice), O_RDWR); if (fd < 0) return; io = g_io_channel_unix_new(fd); switch (bus) { case BUS_USB: if (!setup_device(fd, index, adapter)) break; /* fall through */ case BUS_BLUETOOTH: /* wait for events before setting leds */ g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, setup_leds, GINT_TO_POINTER(get_js_number(udevice))); break; default: DBG("uknown bus type (%u)", bus); break; } g_io_channel_set_close_on_unref(io, TRUE); g_io_channel_unref(io); } static gboolean monitor_watch(GIOChannel *source, GIOCondition condition, gpointer data) { struct udev_device *udevice; udevice = udev_monitor_receive_device(monitor); if (!udevice) return TRUE; if (!g_strcmp0(udev_device_get_action(udevice), "add")) device_added(udevice); udev_device_unref(udevice); return TRUE; } static int sixaxis_init(void) { GIOChannel *channel; DBG(""); ctx = udev_new(); if (!ctx) return -EIO; monitor = udev_monitor_new_from_netlink(ctx, "udev"); if (!monitor) { udev_unref(ctx); ctx = NULL; return -EIO; } /* Listen for newly connected hidraw interfaces */ udev_monitor_filter_add_match_subsystem_devtype(monitor, "hidraw", NULL); udev_monitor_enable_receiving(monitor); channel = g_io_channel_unix_new(udev_monitor_get_fd(monitor)); watch_id = g_io_add_watch(channel, G_IO_IN, monitor_watch, NULL); g_io_channel_unref(channel); return 0; } static void sixaxis_exit(void) { DBG(""); g_source_remove(watch_id); watch_id = 0; udev_monitor_unref(monitor); monitor = NULL; udev_unref(ctx); ctx = NULL; } BLUETOOTH_PLUGIN_DEFINE(sixaxis, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW, sixaxis_init, sixaxis_exit)