diff options
author | Frank Praznik <frank.praznik@oh.rr.com> | 2014-02-20 11:36:03 -0500 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2014-02-24 11:38:46 -0500 |
commit | d2d782fccee4f699a35e2d0cdbb2b19bdaec95a4 (patch) | |
tree | ea947847d645a59d102f487b04fe5e63d209d436 /drivers/hid/hid-sony.c | |
parent | ac3c9a94094b515ab135886eb4547bb889d5b31a (diff) |
HID: sony: Prevent duplicate controller connections.
If a Sixaxis or Dualshock 4 controller is connected via USB while already
connected via Bluetooth it will cause duplicate devices to be added to the
input device list.
To prevent this a global list of controllers and their MAC addresses is
maintained and new controllers are checked against this list. If a duplicate
is found, the probe function will exit with -EEXIST.
On USB the MAC is retrieved via a feature report. On Bluetooth neither
controller reports the MAC address in a feature report so the MAC is parsed from
the uniq string. As uniq cannot be guaranteed to be a MAC address in every case
(uHID or the behavior of HIDP changing) a parsing failure will not prevent the
connection.
Signed-off-by: Frank Praznik <frank.praznik@oh.rr.com>
Reviewed-by: David Herrmann <dh.herrmann@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid/hid-sony.c')
-rw-r--r-- | drivers/hid/hid-sony.c | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index b39e3abd6cdd..b1aa6f00c827 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c | |||
@@ -33,6 +33,7 @@ | |||
33 | #include <linux/leds.h> | 33 | #include <linux/leds.h> |
34 | #include <linux/power_supply.h> | 34 | #include <linux/power_supply.h> |
35 | #include <linux/spinlock.h> | 35 | #include <linux/spinlock.h> |
36 | #include <linux/list.h> | ||
36 | #include <linux/input/mt.h> | 37 | #include <linux/input/mt.h> |
37 | 38 | ||
38 | #include "hid-ids.h" | 39 | #include "hid-ids.h" |
@@ -717,8 +718,12 @@ static enum power_supply_property sony_battery_props[] = { | |||
717 | POWER_SUPPLY_PROP_STATUS, | 718 | POWER_SUPPLY_PROP_STATUS, |
718 | }; | 719 | }; |
719 | 720 | ||
721 | static spinlock_t sony_dev_list_lock; | ||
722 | static LIST_HEAD(sony_device_list); | ||
723 | |||
720 | struct sony_sc { | 724 | struct sony_sc { |
721 | spinlock_t lock; | 725 | spinlock_t lock; |
726 | struct list_head list_node; | ||
722 | struct hid_device *hdev; | 727 | struct hid_device *hdev; |
723 | struct led_classdev *leds[MAX_LEDS]; | 728 | struct led_classdev *leds[MAX_LEDS]; |
724 | unsigned long quirks; | 729 | unsigned long quirks; |
@@ -730,6 +735,7 @@ struct sony_sc { | |||
730 | __u8 right; | 735 | __u8 right; |
731 | #endif | 736 | #endif |
732 | 737 | ||
738 | __u8 mac_address[6]; | ||
733 | __u8 worker_initialized; | 739 | __u8 worker_initialized; |
734 | __u8 cable_state; | 740 | __u8 cable_state; |
735 | __u8 battery_charging; | 741 | __u8 battery_charging; |
@@ -1489,6 +1495,133 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count, | |||
1489 | return 0; | 1495 | return 0; |
1490 | } | 1496 | } |
1491 | 1497 | ||
1498 | /* | ||
1499 | * If a controller is plugged in via USB while already connected via Bluetooth | ||
1500 | * it will show up as two devices. A global list of connected controllers and | ||
1501 | * their MAC addresses is maintained to ensure that a device is only connected | ||
1502 | * once. | ||
1503 | */ | ||
1504 | static int sony_check_add_dev_list(struct sony_sc *sc) | ||
1505 | { | ||
1506 | struct sony_sc *entry; | ||
1507 | unsigned long flags; | ||
1508 | int ret; | ||
1509 | |||
1510 | spin_lock_irqsave(&sony_dev_list_lock, flags); | ||
1511 | |||
1512 | list_for_each_entry(entry, &sony_device_list, list_node) { | ||
1513 | ret = memcmp(sc->mac_address, entry->mac_address, | ||
1514 | sizeof(sc->mac_address)); | ||
1515 | if (!ret) { | ||
1516 | ret = -EEXIST; | ||
1517 | hid_info(sc->hdev, "controller with MAC address %pMR already connected\n", | ||
1518 | sc->mac_address); | ||
1519 | goto unlock; | ||
1520 | } | ||
1521 | } | ||
1522 | |||
1523 | ret = 0; | ||
1524 | list_add(&(sc->list_node), &sony_device_list); | ||
1525 | |||
1526 | unlock: | ||
1527 | spin_unlock_irqrestore(&sony_dev_list_lock, flags); | ||
1528 | return ret; | ||
1529 | } | ||
1530 | |||
1531 | static void sony_remove_dev_list(struct sony_sc *sc) | ||
1532 | { | ||
1533 | unsigned long flags; | ||
1534 | |||
1535 | if (sc->list_node.next) { | ||
1536 | spin_lock_irqsave(&sony_dev_list_lock, flags); | ||
1537 | list_del(&(sc->list_node)); | ||
1538 | spin_unlock_irqrestore(&sony_dev_list_lock, flags); | ||
1539 | } | ||
1540 | } | ||
1541 | |||
1542 | static int sony_get_bt_devaddr(struct sony_sc *sc) | ||
1543 | { | ||
1544 | int ret; | ||
1545 | |||
1546 | /* HIDP stores the device MAC address as a string in the uniq field. */ | ||
1547 | ret = strlen(sc->hdev->uniq); | ||
1548 | if (ret != 17) | ||
1549 | return -EINVAL; | ||
1550 | |||
1551 | ret = sscanf(sc->hdev->uniq, | ||
1552 | "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", | ||
1553 | &sc->mac_address[5], &sc->mac_address[4], &sc->mac_address[3], | ||
1554 | &sc->mac_address[2], &sc->mac_address[1], &sc->mac_address[0]); | ||
1555 | |||
1556 | if (ret != 6) | ||
1557 | return -EINVAL; | ||
1558 | |||
1559 | return 0; | ||
1560 | } | ||
1561 | |||
1562 | static int sony_check_add(struct sony_sc *sc) | ||
1563 | { | ||
1564 | int n, ret; | ||
1565 | |||
1566 | if ((sc->quirks & DUALSHOCK4_CONTROLLER_BT) || | ||
1567 | (sc->quirks & SIXAXIS_CONTROLLER_BT)) { | ||
1568 | /* | ||
1569 | * sony_get_bt_devaddr() attempts to parse the Bluetooth MAC | ||
1570 | * address from the uniq string where HIDP stores it. | ||
1571 | * As uniq cannot be guaranteed to be a MAC address in all cases | ||
1572 | * a failure of this function should not prevent the connection. | ||
1573 | */ | ||
1574 | if (sony_get_bt_devaddr(sc) < 0) { | ||
1575 | hid_warn(sc->hdev, "UNIQ does not contain a MAC address; duplicate check skipped\n"); | ||
1576 | return 0; | ||
1577 | } | ||
1578 | } else if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) { | ||
1579 | __u8 buf[7]; | ||
1580 | |||
1581 | /* | ||
1582 | * The MAC address of a DS4 controller connected via USB can be | ||
1583 | * retrieved with feature report 0x81. The address begins at | ||
1584 | * offset 1. | ||
1585 | */ | ||
1586 | ret = hid_hw_raw_request(sc->hdev, 0x81, buf, sizeof(buf), | ||
1587 | HID_FEATURE_REPORT, HID_REQ_GET_REPORT); | ||
1588 | |||
1589 | if (ret != 7) { | ||
1590 | hid_err(sc->hdev, "failed to retrieve feature report 0x81 with the DualShock 4 MAC address\n"); | ||
1591 | return ret < 0 ? ret : -EINVAL; | ||
1592 | } | ||
1593 | |||
1594 | memcpy(sc->mac_address, &buf[1], sizeof(sc->mac_address)); | ||
1595 | } else if (sc->quirks & SIXAXIS_CONTROLLER_USB) { | ||
1596 | __u8 buf[18]; | ||
1597 | |||
1598 | /* | ||
1599 | * The MAC address of a Sixaxis controller connected via USB can | ||
1600 | * be retrieved with feature report 0xf2. The address begins at | ||
1601 | * offset 4. | ||
1602 | */ | ||
1603 | ret = hid_hw_raw_request(sc->hdev, 0xf2, buf, sizeof(buf), | ||
1604 | HID_FEATURE_REPORT, HID_REQ_GET_REPORT); | ||
1605 | |||
1606 | if (ret != 18) { | ||
1607 | hid_err(sc->hdev, "failed to retrieve feature report 0xf2 with the Sixaxis MAC address\n"); | ||
1608 | return ret < 0 ? ret : -EINVAL; | ||
1609 | } | ||
1610 | |||
1611 | /* | ||
1612 | * The Sixaxis device MAC in the report is big-endian and must | ||
1613 | * be byte-swapped. | ||
1614 | */ | ||
1615 | for (n = 0; n < 6; n++) | ||
1616 | sc->mac_address[5-n] = buf[4+n]; | ||
1617 | } else { | ||
1618 | return 0; | ||
1619 | } | ||
1620 | |||
1621 | return sony_check_add_dev_list(sc); | ||
1622 | } | ||
1623 | |||
1624 | |||
1492 | static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) | 1625 | static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) |
1493 | { | 1626 | { |
1494 | int ret; | 1627 | int ret; |
@@ -1559,6 +1692,10 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) | |||
1559 | if (ret < 0) | 1692 | if (ret < 0) |
1560 | goto err_stop; | 1693 | goto err_stop; |
1561 | 1694 | ||
1695 | ret = sony_check_add(sc); | ||
1696 | if (ret < 0) | ||
1697 | goto err_stop; | ||
1698 | |||
1562 | if (sc->quirks & SONY_LED_SUPPORT) { | 1699 | if (sc->quirks & SONY_LED_SUPPORT) { |
1563 | ret = sony_leds_init(hdev); | 1700 | ret = sony_leds_init(hdev); |
1564 | if (ret < 0) | 1701 | if (ret < 0) |
@@ -1594,6 +1731,7 @@ err_stop: | |||
1594 | sony_battery_remove(sc); | 1731 | sony_battery_remove(sc); |
1595 | if (sc->worker_initialized) | 1732 | if (sc->worker_initialized) |
1596 | cancel_work_sync(&sc->state_worker); | 1733 | cancel_work_sync(&sc->state_worker); |
1734 | sony_remove_dev_list(sc); | ||
1597 | hid_hw_stop(hdev); | 1735 | hid_hw_stop(hdev); |
1598 | return ret; | 1736 | return ret; |
1599 | } | 1737 | } |
@@ -1613,6 +1751,8 @@ static void sony_remove(struct hid_device *hdev) | |||
1613 | if (sc->worker_initialized) | 1751 | if (sc->worker_initialized) |
1614 | cancel_work_sync(&sc->state_worker); | 1752 | cancel_work_sync(&sc->state_worker); |
1615 | 1753 | ||
1754 | sony_remove_dev_list(sc); | ||
1755 | |||
1616 | hid_hw_stop(hdev); | 1756 | hid_hw_stop(hdev); |
1617 | } | 1757 | } |
1618 | 1758 | ||