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 | |
| 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>
| -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; | ||
