Commit 0aa65cc0 authored by David S. Miller's avatar David S. Miller

Merge branch 'for-upstream' of...

Merge branch 'for-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next

Johan Hedberg says:

====================
pull request: bluetooth-next 2015-08-16

Here's what's likely the last bluetooth-next pull request for 4.3:

 - 6lowpan/802.15.4 refactoring, cleanups & fixes
 - Document 6lowpan netdev usage in Documentation/networking/6lowpan.txt
 - Support for UART based QCA Bluetooth controllers
 - Power management support for Broeadcom Bluetooth controllers
 - Change LE connection initiation to always use passive scanning first
 - Support for new Silicon Wave USB ID

Please let me know if there are any issues pulling. Thanks.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 863960b4 c0015bf3
Netdev private dataroom for 6lowpan interfaces:
All 6lowpan able net devices, means all interfaces with ARPHRD_6LOWPAN,
must have "struct lowpan_priv" placed at beginning of netdev_priv.
The priv_size of each interface should be calculate by:
dev->priv_size = LOWPAN_PRIV_SIZE(LL_6LOWPAN_PRIV_DATA);
Where LL_PRIV_6LOWPAN_DATA is sizeof linklayer 6lowpan private data struct.
To access the LL_PRIV_6LOWPAN_DATA structure you can cast:
lowpan_priv(dev)-priv;
to your LL_6LOWPAN_PRIV_DATA structure.
Before registering the lowpan netdev interface you must run:
lowpan_netdev_setup(dev, LOWPAN_LLTYPE_FOOBAR);
wheres LOWPAN_LLTYPE_FOOBAR is a define for your 6LoWPAN linklayer type of
enum lowpan_lltypes.
Example to evaluate the private usually you can do:
static inline sturct lowpan_priv_foobar *
lowpan_foobar_priv(struct net_device *dev)
{
return (sturct lowpan_priv_foobar *)lowpan_priv(dev)->priv;
}
switch (dev->type) {
case ARPHRD_6LOWPAN:
lowpan_priv = lowpan_priv(dev);
/* do great stuff which is ARPHRD_6LOWPAN related */
switch (lowpan_priv->lltype) {
case LOWPAN_LLTYPE_FOOBAR:
/* do 802.15.4 6LoWPAN handling here */
lowpan_foobar_priv(dev)->bar = foo;
break;
...
}
break;
...
}
In case of generic 6lowpan branch ("net/6lowpan") you can remove the check
on ARPHRD_6LOWPAN, because you can be sure that these function are called
by ARPHRD_6LOWPAN interfaces.
......@@ -158,6 +158,7 @@ L: linux-wpan@vger.kernel.org
S: Maintained
F: net/6lowpan/
F: include/net/6lowpan.h
F: Documentation/networking/6lowpan.txt
6PACK NETWORK DRIVER FOR AX.25
M: Andreas Koensgen <ajk@comnets.uni-bremen.de>
......
......@@ -13,6 +13,10 @@ config BT_RTL
tristate
select FW_LOADER
config BT_QCA
tristate
select FW_LOADER
config BT_HCIBTUSB
tristate "HCI USB driver"
depends on USB
......@@ -151,6 +155,19 @@ config BT_HCIUART_BCM
Say Y here to compile support for Broadcom protocol.
config BT_HCIUART_QCA
bool "Qualcomm Atheros protocol support"
depends on BT_HCIUART
select BT_HCIUART_H4
select BT_QCA
help
The Qualcomm Atheros protocol supports HCI In-Band Sleep feature
over serial port interface(H4) between controller and host.
This protocol is required for UART clock control for QCA Bluetooth
devices.
Say Y here to compile support for QCA protocol.
config BT_HCIBCM203X
tristate "HCI BCM203x USB driver"
depends on USB
......
......@@ -22,6 +22,7 @@ obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o
obj-$(CONFIG_BT_WILINK) += btwilink.o
obj-$(CONFIG_BT_BCM) += btbcm.o
obj-$(CONFIG_BT_RTL) += btrtl.o
obj-$(CONFIG_BT_QCA) += btqca.o
btmrvl-y := btmrvl_main.o
btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o
......@@ -34,6 +35,7 @@ hci_uart-$(CONFIG_BT_HCIUART_ATH3K) += hci_ath.o
hci_uart-$(CONFIG_BT_HCIUART_3WIRE) += hci_h5.o
hci_uart-$(CONFIG_BT_HCIUART_INTEL) += hci_intel.o
hci_uart-$(CONFIG_BT_HCIUART_BCM) += hci_bcm.o
hci_uart-$(CONFIG_BT_HCIUART_QCA) += hci_qca.o
hci_uart-objs := $(hci_uart-y)
ccflags-y += -D__CHECK_ENDIAN__
......@@ -1071,8 +1071,6 @@ static int btmrvl_sdio_download_fw(struct btmrvl_sdio_card *card)
}
}
sdio_release_host(card->func);
/*
* winner or not, with this test the FW synchronizes when the
* module can continue its initialization
......@@ -1082,6 +1080,8 @@ static int btmrvl_sdio_download_fw(struct btmrvl_sdio_card *card)
return -ETIMEDOUT;
}
sdio_release_host(card->func);
return 0;
done:
......
This diff is collapsed.
/*
* Bluetooth supports for Qualcomm Atheros ROME chips
*
* Copyright (c) 2015 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#define EDL_PATCH_CMD_OPCODE (0xFC00)
#define EDL_NVM_ACCESS_OPCODE (0xFC0B)
#define EDL_PATCH_CMD_LEN (1)
#define EDL_PATCH_VER_REQ_CMD (0x19)
#define EDL_PATCH_TLV_REQ_CMD (0x1E)
#define EDL_NVM_ACCESS_SET_REQ_CMD (0x01)
#define MAX_SIZE_PER_TLV_SEGMENT (243)
#define EDL_CMD_REQ_RES_EVT (0x00)
#define EDL_PATCH_VER_RES_EVT (0x19)
#define EDL_APP_VER_RES_EVT (0x02)
#define EDL_TVL_DNLD_RES_EVT (0x04)
#define EDL_CMD_EXE_STATUS_EVT (0x00)
#define EDL_SET_BAUDRATE_RSP_EVT (0x92)
#define EDL_NVM_ACCESS_CODE_EVT (0x0B)
#define EDL_TAG_ID_HCI (17)
#define EDL_TAG_ID_DEEP_SLEEP (27)
enum qca_bardrate {
QCA_BAUDRATE_115200 = 0,
QCA_BAUDRATE_57600,
QCA_BAUDRATE_38400,
QCA_BAUDRATE_19200,
QCA_BAUDRATE_9600,
QCA_BAUDRATE_230400,
QCA_BAUDRATE_250000,
QCA_BAUDRATE_460800,
QCA_BAUDRATE_500000,
QCA_BAUDRATE_720000,
QCA_BAUDRATE_921600,
QCA_BAUDRATE_1000000,
QCA_BAUDRATE_1250000,
QCA_BAUDRATE_2000000,
QCA_BAUDRATE_3000000,
QCA_BAUDRATE_4000000,
QCA_BAUDRATE_1600000,
QCA_BAUDRATE_3200000,
QCA_BAUDRATE_3500000,
QCA_BAUDRATE_AUTO = 0xFE,
QCA_BAUDRATE_RESERVED
};
enum rome_tlv_type {
TLV_TYPE_PATCH = 1,
TLV_TYPE_NVM
};
struct rome_config {
u8 type;
char fwname[64];
uint8_t user_baud_rate;
};
struct edl_event_hdr {
__u8 cresp;
__u8 rtype;
__u8 data[0];
} __packed;
struct rome_version {
__le32 product_id;
__le16 patch_ver;
__le16 rome_ver;
__le32 soc_id;
} __packed;
struct tlv_seg_resp {
__u8 result;
} __packed;
struct tlv_type_patch {
__le32 total_size;
__le32 data_length;
__u8 format_version;
__u8 signature;
__le16 reserved1;
__le16 product_id;
__le16 rom_build;
__le16 patch_version;
__le16 reserved2;
__le32 entry;
} __packed;
struct tlv_type_nvm {
__le16 tag_id;
__le16 tag_len;
__le32 reserve1;
__le32 reserve2;
__u8 data[0];
} __packed;
struct tlv_type_hdr {
__le32 type_len;
__u8 data[0];
} __packed;
#if IS_ENABLED(CONFIG_BT_QCA)
int qca_set_bdaddr_rome(struct hci_dev *hdev, const bdaddr_t *bdaddr);
int qca_uart_setup_rome(struct hci_dev *hdev, uint8_t baudrate);
#else
static inline int qca_set_bdaddr_rome(struct hci_dev *hdev, const bdaddr_t *bdaddr)
{
return -EOPNOTSUPP;
}
static inline int qca_uart_setup_rome(struct hci_dev *hdev, int speed)
{
return -EOPNOTSUPP;
}
#endif
......@@ -322,6 +322,9 @@ static const struct usb_device_id blacklist_table[] = {
{ USB_DEVICE(0x13d3, 0x3461), .driver_info = BTUSB_REALTEK },
{ USB_DEVICE(0x13d3, 0x3462), .driver_info = BTUSB_REALTEK },
/* Silicon Wave based devices */
{ USB_DEVICE(0x0c10, 0x0000), .driver_info = BTUSB_SWAVE },
{ } /* Terminating entry */
};
......
......@@ -25,6 +25,12 @@
#include <linux/errno.h>
#include <linux/skbuff.h>
#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/tty.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
......@@ -32,11 +38,37 @@
#include "btbcm.h"
#include "hci_uart.h"
struct bcm_device {
struct list_head list;
struct platform_device *pdev;
const char *name;
struct gpio_desc *device_wakeup;
struct gpio_desc *shutdown;
struct clk *clk;
bool clk_enabled;
u32 init_speed;
#ifdef CONFIG_PM_SLEEP
struct hci_uart *hu;
bool is_suspended; /* suspend/resume flag */
#endif
};
struct bcm_data {
struct sk_buff *rx_skb;
struct sk_buff_head txq;
struct sk_buff *rx_skb;
struct sk_buff_head txq;
struct bcm_device *dev;
};
/* List of BCM BT UART devices */
static DEFINE_SPINLOCK(bcm_device_list_lock);
static LIST_HEAD(bcm_device_list);
static int bcm_set_baudrate(struct hci_uart *hu, unsigned int speed)
{
struct hci_dev *hdev = hu->hdev;
......@@ -86,9 +118,41 @@ static int bcm_set_baudrate(struct hci_uart *hu, unsigned int speed)
return 0;
}
/* bcm_device_exists should be protected by bcm_device_list_lock */
static bool bcm_device_exists(struct bcm_device *device)
{
struct list_head *p;
list_for_each(p, &bcm_device_list) {
struct bcm_device *dev = list_entry(p, struct bcm_device, list);
if (device == dev)
return true;
}
return false;
}
static int bcm_gpio_set_power(struct bcm_device *dev, bool powered)
{
if (powered && !IS_ERR(dev->clk) && !dev->clk_enabled)
clk_enable(dev->clk);
gpiod_set_value_cansleep(dev->shutdown, powered);
gpiod_set_value_cansleep(dev->device_wakeup, powered);
if (!powered && !IS_ERR(dev->clk) && dev->clk_enabled)
clk_disable(dev->clk);
dev->clk_enabled = powered;
return 0;
}
static int bcm_open(struct hci_uart *hu)
{
struct bcm_data *bcm;
struct list_head *p;
BT_DBG("hu %p", hu);
......@@ -99,6 +163,30 @@ static int bcm_open(struct hci_uart *hu)
skb_queue_head_init(&bcm->txq);
hu->priv = bcm;
spin_lock(&bcm_device_list_lock);
list_for_each(p, &bcm_device_list) {
struct bcm_device *dev = list_entry(p, struct bcm_device, list);
/* Retrieve saved bcm_device based on parent of the
* platform device (saved during device probe) and
* parent of tty device used by hci_uart
*/
if (hu->tty->dev->parent == dev->pdev->dev.parent) {
bcm->dev = dev;
hu->init_speed = dev->init_speed;
#ifdef CONFIG_PM_SLEEP
dev->hu = hu;
#endif
break;
}
}
if (bcm->dev)
bcm_gpio_set_power(bcm->dev, true);
spin_unlock(&bcm_device_list_lock);
return 0;
}
......@@ -108,6 +196,16 @@ static int bcm_close(struct hci_uart *hu)
BT_DBG("hu %p", hu);
/* Protect bcm->dev against removal of the device or driver */
spin_lock(&bcm_device_list_lock);
if (bcm_device_exists(bcm->dev)) {
bcm_gpio_set_power(bcm->dev, false);
#ifdef CONFIG_PM_SLEEP
bcm->dev->hu = NULL;
#endif
}
spin_unlock(&bcm_device_list_lock);
skb_queue_purge(&bcm->txq);
kfree_skb(bcm->rx_skb);
kfree(bcm);
......@@ -232,6 +330,188 @@ static struct sk_buff *bcm_dequeue(struct hci_uart *hu)
return skb_dequeue(&bcm->txq);
}
#ifdef CONFIG_PM_SLEEP
/* Platform suspend callback */
static int bcm_suspend(struct device *dev)
{
struct bcm_device *bdev = platform_get_drvdata(to_platform_device(dev));
BT_DBG("suspend (%p): is_suspended %d", bdev, bdev->is_suspended);
if (!bdev->is_suspended) {
hci_uart_set_flow_control(bdev->hu, true);
/* Once this callback returns, driver suspends BT via GPIO */
bdev->is_suspended = true;
}
/* Suspend the device */
if (bdev->device_wakeup) {
gpiod_set_value(bdev->device_wakeup, false);
BT_DBG("suspend, delaying 15 ms");
mdelay(15);
}
return 0;
}
/* Platform resume callback */
static int bcm_resume(struct device *dev)
{
struct bcm_device *bdev = platform_get_drvdata(to_platform_device(dev));
BT_DBG("resume (%p): is_suspended %d", bdev, bdev->is_suspended);
if (bdev->device_wakeup) {
gpiod_set_value(bdev->device_wakeup, true);
BT_DBG("resume, delaying 15 ms");
mdelay(15);
}
/* When this callback executes, the device has woken up already */
if (bdev->is_suspended) {
bdev->is_suspended = false;
hci_uart_set_flow_control(bdev->hu, false);
}
return 0;
}
#endif
static const struct acpi_gpio_params device_wakeup_gpios = { 0, 0, false };
static const struct acpi_gpio_params shutdown_gpios = { 1, 0, false };
static const struct acpi_gpio_mapping acpi_bcm_default_gpios[] = {
{ "device-wakeup-gpios", &device_wakeup_gpios, 1 },
{ "shutdown-gpios", &shutdown_gpios, 1 },
{ },
};
#ifdef CONFIG_ACPI
static int bcm_resource(struct acpi_resource *ares, void *data)
{
struct bcm_device *dev = data;
if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) {
struct acpi_resource_uart_serialbus *sb;
sb = &ares->data.uart_serial_bus;
if (sb->type == ACPI_RESOURCE_SERIAL_TYPE_UART)
dev->init_speed = sb->default_baud_rate;
}
/* Always tell the ACPI core to skip this resource */
return 1;
}
static int bcm_acpi_probe(struct bcm_device *dev)
{
struct platform_device *pdev = dev->pdev;
const struct acpi_device_id *id;
struct acpi_device *adev;
LIST_HEAD(resources);
int ret;
id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev);
if (!id)
return -ENODEV;
/* Retrieve GPIO data */
dev->name = dev_name(&pdev->dev);
ret = acpi_dev_add_driver_gpios(ACPI_COMPANION(&pdev->dev),
acpi_bcm_default_gpios);
if (ret)
return ret;
dev->clk = devm_clk_get(&pdev->dev, NULL);
dev->device_wakeup = devm_gpiod_get_optional(&pdev->dev,
"device-wakeup",
GPIOD_OUT_LOW);
if (IS_ERR(dev->device_wakeup))
return PTR_ERR(dev->device_wakeup);
dev->shutdown = devm_gpiod_get_optional(&pdev->dev, "shutdown",
GPIOD_OUT_LOW);
if (IS_ERR(dev->shutdown))
return PTR_ERR(dev->shutdown);
/* Make sure at-least one of the GPIO is defined and that
* a name is specified for this instance
*/
if ((!dev->device_wakeup && !dev->shutdown) || !dev->name) {
dev_err(&pdev->dev, "invalid platform data\n");
return -EINVAL;
}
/* Retrieve UART ACPI info */
adev = ACPI_COMPANION(&dev->pdev->dev);
if (!adev)
return 0;
acpi_dev_get_resources(adev, &resources, bcm_resource, dev);
return 0;
}
#else
static int bcm_acpi_probe(struct bcm_device *dev)
{
return -EINVAL;
}
#endif /* CONFIG_ACPI */
static int bcm_probe(struct platform_device *pdev)
{
struct bcm_device *dev;
struct acpi_device_id *pdata = pdev->dev.platform_data;
int ret;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->pdev = pdev;
if (ACPI_HANDLE(&pdev->dev)) {
ret = bcm_acpi_probe(dev);
if (ret)
return ret;
} else if (pdata) {
dev->name = pdata->id;
} else {
return -ENODEV;
}
platform_set_drvdata(pdev, dev);
dev_info(&pdev->dev, "%s device registered.\n", dev->name);
/* Place this instance on the device list */
spin_lock(&bcm_device_list_lock);
list_add_tail(&dev->list, &bcm_device_list);
spin_unlock(&bcm_device_list_lock);
bcm_gpio_set_power(dev, false);
return 0;
}
static int bcm_remove(struct platform_device *pdev)
{
struct bcm_device *dev = platform_get_drvdata(pdev);
spin_lock(&bcm_device_list_lock);
list_del(&dev->list);
spin_unlock(&bcm_device_list_lock);
acpi_dev_remove_driver_gpios(ACPI_COMPANION(&pdev->dev));
dev_info(&pdev->dev, "%s device unregistered.\n", dev->name);
return 0;
}
static const struct hci_uart_proto bcm_proto = {
.id = HCI_UART_BCM,
.name = "BCM",
......@@ -247,12 +527,38 @@ static const struct hci_uart_proto bcm_proto = {
.dequeue = bcm_dequeue,
};
#ifdef CONFIG_ACPI
static const struct acpi_device_id bcm_acpi_match[] = {
{ "BCM2E39", 0 },
{ "BCM2E67", 0 },
{ },
};
MODULE_DEVICE_TABLE(acpi, bcm_acpi_match);
#endif
/* Platform suspend and resume callbacks */
static SIMPLE_DEV_PM_OPS(bcm_pm_ops, bcm_suspend, bcm_resume);
static struct platform_driver bcm_driver = {
.probe = bcm_probe,
.remove = bcm_remove,
.driver = {
.name = "hci_bcm",
.acpi_match_table = ACPI_PTR(bcm_acpi_match),
.pm = &bcm_pm_ops,
},
};
int __init bcm_init(void)
{
platform_driver_register(&bcm_driver);
return hci_uart_register_proto(&bcm_proto);
}
int __exit bcm_deinit(void)
{
platform_driver_unregister(&bcm_driver);
return hci_uart_unregister_proto(&bcm_proto);
}
......@@ -810,6 +810,9 @@ static int __init hci_uart_init(void)
#ifdef CONFIG_BT_HCIUART_BCM
bcm_init();
#endif
#ifdef CONFIG_BT_HCIUART_QCA
qca_init();
#endif
return 0;
}
......@@ -839,6 +842,9 @@ static void __exit hci_uart_exit(void)
#ifdef CONFIG_BT_HCIUART_BCM
bcm_deinit();
#endif
#ifdef CONFIG_BT_HCIUART_QCA
qca_deinit();
#endif
/* Release tty registration of line discipline */
err = tty_unregister_ldisc(N_HCI);
......
This diff is collapsed.
......@@ -35,7 +35,7 @@
#define HCIUARTGETFLAGS _IOR('U', 204, int)
/* UART protocols */
#define HCI_UART_MAX_PROTO 8
#define HCI_UART_MAX_PROTO 9
#define HCI_UART_H4 0
#define HCI_UART_BCSP 1
......@@ -45,6 +45,7 @@
#define HCI_UART_ATH3K 5
#define HCI_UART_INTEL 6
#define HCI_UART_BCM 7
#define HCI_UART_QCA 8
#define HCI_UART_RAW_DEVICE 0
#define HCI_UART_RESET_ON_INIT 1
......@@ -176,3 +177,8 @@ int intel_deinit(void);
int bcm_init(void);
int bcm_deinit(void);
#endif
#ifdef CONFIG_BT_HCIUART_QCA
int qca_init(void);
int qca_deinit(void);
#endif
......@@ -97,9 +97,7 @@ struct at86rf230_local {
struct at86rf230_state_change irq;
bool tx_aret;
unsigned long cal_timeout;
s8 max_frame_retries;
bool is_tx;
bool is_tx_from_off;
u8 tx_retry;
......@@ -651,7 +649,7 @@ at86rf230_tx_complete(void *context)
enable_irq(ctx->irq);
ieee802154_xmit_complete(lp->hw, lp->tx_skb, !lp->tx_aret);
ieee802154_xmit_complete(lp->hw, lp->tx_skb, false);
}
static void
......@@ -760,17 +758,10 @@ at86rf230_irq_trx_end(struct at86rf230_local *lp)
{
if (lp->is_tx) {
lp->is_tx = 0;
if (lp->tx_aret)
at86rf230_async_state_change(lp, &lp->irq,
STATE_FORCE_TX_ON,
at86rf230_tx_trac_status,
true);
else
at86rf230_async_state_change(lp, &lp->irq,
STATE_RX_AACK_ON,
at86rf230_tx_complete,
true);
at86rf230_async_state_change(lp, &lp->irq,
STATE_FORCE_TX_ON,
at86rf230_tx_trac_status,
true);
} else {
at86rf230_async_read_reg(lp, RG_TRX_STATE, &lp->irq,
at86rf230_rx_trac_check, true);
......@@ -876,24 +867,16 @@ at86rf230_xmit_start(void *context)
struct at86rf230_state_change *ctx = context;
struct at86rf230_local *lp = ctx->lp;
/* In ARET mode we need to go into STATE_TX_ARET_ON after we
* are in STATE_TX_ON. The pfad differs here, so we change
* the complete handler.
*/
if (lp->tx_aret) {
if (lp->is_tx_from_off) {
lp->is_tx_from_off = false;
at86rf230_async_state_change(lp, ctx, STATE_TX_ARET_ON,
at86rf230_write_frame,
false);
} else {
at86rf230_async_state_change(lp, ctx, STATE_TX_ON,
at86rf230_xmit_tx_on,
false);
}
/* check if we change from off state */
if (lp->is_tx_from_off) {
lp->is_tx_from_off = false;
at86rf230_async_state_change(lp, ctx, STATE_TX_ARET_ON,
at86rf230_write_frame,
false);
} else {
at86rf230_async_state_change(lp, ctx, STATE_TX_ON,
at86rf230_write_frame, false);
at86rf230_xmit_tx_on,
false);
}
}
......@@ -1267,15 +1250,8 @@ static int
at86rf230_set_frame_retries(struct ieee802154_hw *hw, s8 retries)
{
struct at86rf230_local *lp = hw->priv;
int rc = 0;
lp->tx_aret = retries >= 0;
lp->max_frame_retries = retries;
if (retries >= 0)
rc = at86rf230_write_subreg(lp, SR_MAX_FRAME_RETRIES, retries);
return rc;
return at86rf230_write_subreg(lp, SR_MAX_FRAME_RETRIES, retries);
}
static int
......
......@@ -833,6 +833,7 @@ static int cc2520_get_platform_data(struct spi_device *spi,
if (!spi_pdata)
return -ENOENT;
*pdata = *spi_pdata;
priv->fifo_pin = pdata->fifo;
return 0;
}
......
......@@ -197,6 +197,27 @@
#define LOWPAN_NHC_UDP_CS_P_11 0xF3 /* source & dest = 0xF0B + 4bit inline */
#define LOWPAN_NHC_UDP_CS_C 0x04 /* checksum elided */
#define LOWPAN_PRIV_SIZE(llpriv_size) \
(sizeof(struct lowpan_priv) + llpriv_size)
enum lowpan_lltypes {
LOWPAN_LLTYPE_BTLE,
LOWPAN_LLTYPE_IEEE802154,
};
struct lowpan_priv {
enum lowpan_lltypes lltype;
/* must be last */
u8 priv[0] __aligned(sizeof(void *));
};
static inline
struct lowpan_priv *lowpan_priv(const struct net_device *dev)
{
return netdev_priv(dev);
}
#ifdef DEBUG
/* print data in line */
static inline void raw_dump_inline(const char *caller, char *msg,
......@@ -372,6 +393,8 @@ lowpan_uncompress_size(const struct sk_buff *skb, u16 *dgram_offset)
return skb->len + uncomp_header - ret;
}
void lowpan_netdev_setup(struct net_device *dev, enum lowpan_lltypes lltype);
int
lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
const u8 *saddr, const u8 saddr_type,
......
......@@ -512,9 +512,11 @@ struct hci_conn_params {
HCI_AUTO_CONN_DIRECT,
HCI_AUTO_CONN_ALWAYS,
HCI_AUTO_CONN_LINK_LOSS,
HCI_AUTO_CONN_EXPLICIT,
} auto_connect;
struct hci_conn *conn;
bool explicit_connect;
};
extern struct list_head hci_dev_list;
......@@ -639,6 +641,7 @@ enum {
HCI_CONN_DROP,
HCI_CONN_PARAM_REMOVAL_PEND,
HCI_CONN_NEW_LINK_KEY,
HCI_CONN_SCANNING,
};
static inline bool hci_conn_ssp_enabled(struct hci_conn *conn)
......@@ -808,6 +811,26 @@ static inline struct hci_conn *hci_conn_hash_lookup_state(struct hci_dev *hdev,
return NULL;
}
static inline struct hci_conn *hci_lookup_le_connect(struct hci_dev *hdev)
{
struct hci_conn_hash *h = &hdev->conn_hash;
struct hci_conn *c;
rcu_read_lock();
list_for_each_entry_rcu(c, &h->list, list) {
if (c->type == LE_LINK && c->state == BT_CONNECT &&
!test_bit(HCI_CONN_SCANNING, &c->flags)) {
rcu_read_unlock();
return c;
}
}
rcu_read_unlock();
return NULL;
}
int hci_disconnect(struct hci_conn *conn, __u8 reason);
bool hci_setup_sync(struct hci_conn *conn, __u16 handle);
void hci_sco_setup(struct hci_conn *conn, __u8 status);
......@@ -823,6 +846,9 @@ void hci_chan_del(struct hci_chan *chan);
void hci_chan_list_flush(struct hci_conn *conn);
struct hci_chan *hci_chan_lookup_handle(struct hci_dev *hdev, __u16 handle);
struct hci_conn *hci_connect_le_scan(struct hci_dev *hdev, bdaddr_t *dst,
u8 dst_type, u8 sec_level,
u16 conn_timeout, u8 role);
struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst,
u8 dst_type, u8 sec_level, u16 conn_timeout,
u8 role);
......@@ -988,6 +1014,9 @@ void hci_conn_params_clear_disabled(struct hci_dev *hdev);
struct hci_conn_params *hci_pend_le_action_lookup(struct list_head *list,
bdaddr_t *addr,
u8 addr_type);
struct hci_conn_params *hci_explicit_connect_lookup(struct hci_dev *hdev,
bdaddr_t *addr,
u8 addr_type);
void hci_uuids_clear(struct hci_dev *hdev);
......
......@@ -63,6 +63,8 @@ struct cfg802154_ops {
s8 max_frame_retries);
int (*set_lbt_mode)(struct wpan_phy *wpan_phy,
struct wpan_dev *wpan_dev, bool mode);
int (*set_ackreq_default)(struct wpan_phy *wpan_phy,
struct wpan_dev *wpan_dev, bool ackreq);
};
static inline bool
......@@ -173,6 +175,9 @@ struct wpan_dev {
struct list_head list;
struct net_device *netdev;
/* lowpan interface, set when the wpan_dev belongs to one lowpan_dev */
struct net_device *lowpan_dev;
u32 identifier;
/* MAC PIB */
......@@ -193,6 +198,9 @@ struct wpan_dev {
bool lbt;
bool promiscuous_mode;
/* fallback for acknowledgment bit setting */
bool ackreq;
};
#define to_phy(_dev) container_of(_dev, struct wpan_phy, dev)
......
......@@ -52,6 +52,8 @@ enum nl802154_commands {
NL802154_CMD_SET_LBT_MODE,
NL802154_CMD_SET_ACKREQ_DEFAULT,
/* add new commands above here */
/* used to define NL802154_CMD_MAX below */
......@@ -104,6 +106,8 @@ enum nl802154_attrs {
NL802154_ATTR_SUPPORTED_COMMANDS,
NL802154_ATTR_ACKREQ_DEFAULT,
/* add attributes here, update the policy in nl802154.c */
__NL802154_ATTR_AFTER_LAST,
......
obj-$(CONFIG_6LOWPAN) += 6lowpan.o
6lowpan-y := iphc.o nhc.o
6lowpan-y := core.o iphc.o nhc.o
#rfc6282 nhcs
obj-$(CONFIG_6LOWPAN_NHC_DEST) += nhc_dest.o
......
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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.
*
* Authors:
* (C) 2015 Pengutronix, Alexander Aring <aar@pengutronix.de>
*/
#include <linux/module.h>
#include <net/6lowpan.h>
void lowpan_netdev_setup(struct net_device *dev, enum lowpan_lltypes lltype)
{
lowpan_priv(dev)->lltype = lltype;
}
EXPORT_SYMBOL(lowpan_netdev_setup);
static int __init lowpan_module_init(void)
{
request_module_nowait("ipv6");
request_module_nowait("nhc_dest");
request_module_nowait("nhc_fragment");
request_module_nowait("nhc_hop");
request_module_nowait("nhc_ipv6");
request_module_nowait("nhc_mobility");
request_module_nowait("nhc_routing");
request_module_nowait("nhc_udp");
return 0;
}
module_init(lowpan_module_init);
MODULE_LICENSE("GPL");
......@@ -48,7 +48,6 @@
#include <linux/bitops.h>
#include <linux/if_arp.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <net/6lowpan.h>
#include <net/ipv6.h>
......@@ -284,7 +283,7 @@ lowpan_header_decompress(struct sk_buff *skb, struct net_device *dev,
if (lowpan_fetch_skb(skb, &tmp, sizeof(tmp)))
return -EINVAL;
hdr.flow_lbl[0] = (skb->data[0] & 0x0F) | ((tmp >> 2) & 0x30);
hdr.flow_lbl[0] = (tmp & 0x0F) | ((tmp >> 2) & 0x30);
memcpy(&hdr.flow_lbl[1], &skb->data[0], 2);
skb_pull(skb, 2);
break;
......@@ -610,21 +609,3 @@ int lowpan_header_compress(struct sk_buff *skb, struct net_device *dev,
return 0;
}
EXPORT_SYMBOL_GPL(lowpan_header_compress);
static int __init lowpan_module_init(void)
{
request_module_nowait("ipv6");
request_module_nowait("nhc_dest");
request_module_nowait("nhc_fragment");
request_module_nowait("nhc_hop");
request_module_nowait("nhc_ipv6");
request_module_nowait("nhc_mobility");
request_module_nowait("nhc_routing");
request_module_nowait("nhc_udp");
return 0;
}
module_init(lowpan_module_init);
MODULE_LICENSE("GPL");
......@@ -85,7 +85,7 @@ struct lowpan_dev {
static inline struct lowpan_dev *lowpan_dev(const struct net_device *netdev)
{
return netdev_priv(netdev);
return (struct lowpan_dev *)lowpan_priv(netdev)->priv;
}
static inline void peer_add(struct lowpan_dev *dev, struct lowpan_peer *peer)
......@@ -848,8 +848,9 @@ static int setup_netdev(struct l2cap_chan *chan, struct lowpan_dev **dev)
struct net_device *netdev;
int err = 0;
netdev = alloc_netdev(sizeof(struct lowpan_dev), IFACE_NAME_TEMPLATE,
NET_NAME_UNKNOWN, netdev_setup);
netdev = alloc_netdev(LOWPAN_PRIV_SIZE(sizeof(struct lowpan_dev)),
IFACE_NAME_TEMPLATE, NET_NAME_UNKNOWN,
netdev_setup);
if (!netdev)
return -ENOMEM;
......@@ -859,7 +860,7 @@ static int setup_netdev(struct l2cap_chan *chan, struct lowpan_dev **dev)
SET_NETDEV_DEV(netdev, &chan->conn->hcon->hdev->dev);
SET_NETDEV_DEVTYPE(netdev, &bt_type);
*dev = netdev_priv(netdev);
*dev = lowpan_dev(netdev);
(*dev)->netdev = netdev;
(*dev)->hdev = chan->conn->hcon->hdev;
INIT_LIST_HEAD(&(*dev)->peers);
......@@ -869,6 +870,8 @@ static int setup_netdev(struct l2cap_chan *chan, struct lowpan_dev **dev)
list_add_rcu(&(*dev)->list, &bt_6lowpan_devices);
spin_unlock(&devices_lock);
lowpan_netdev_setup(netdev, LOWPAN_LLTYPE_BTLE);
err = register_netdev(netdev);
if (err < 0) {
BT_INFO("register_netdev failed %d", err);
......
......@@ -379,7 +379,7 @@ static bool amp_write_rem_assoc_frag(struct hci_dev *hdev,
amp_ctrl_put(ctrl);
hci_req_init(&req, hdev);
hci_req_add(&req, HCI_OP_WRITE_REMOTE_AMP_ASSOC, sizeof(cp), &cp);
hci_req_add(&req, HCI_OP_WRITE_REMOTE_AMP_ASSOC, len, cp);
hci_req_run_skb(&req, write_remote_amp_assoc_complete);
kfree(cp);
......
......@@ -64,6 +64,48 @@ static void hci_le_create_connection_cancel(struct hci_conn *conn)
hci_send_cmd(conn->hdev, HCI_OP_LE_CREATE_CONN_CANCEL, 0, NULL);
}
/* This function requires the caller holds hdev->lock */
static void hci_connect_le_scan_cleanup(struct hci_conn *conn)
{
struct hci_conn_params *params;
struct smp_irk *irk;
bdaddr_t *bdaddr;
u8 bdaddr_type;
bdaddr = &conn->dst;
bdaddr_type = conn->dst_type;
/* Check if we need to convert to identity address */
irk = hci_get_irk(conn->hdev, bdaddr, bdaddr_type);
if (irk) {
bdaddr = &irk->bdaddr;
bdaddr_type = irk->addr_type;
}
params = hci_explicit_connect_lookup(conn->hdev, bdaddr, bdaddr_type);
if (!params)
return;
/* The connection attempt was doing scan for new RPA, and is
* in scan phase. If params are not associated with any other
* autoconnect action, remove them completely. If they are, just unmark
* them as waiting for connection, by clearing explicit_connect field.
*/
if (params->auto_connect == HCI_AUTO_CONN_EXPLICIT)
hci_conn_params_del(conn->hdev, bdaddr, bdaddr_type);
else
params->explicit_connect = false;
}
/* This function requires the caller holds hdev->lock */
static void hci_connect_le_scan_remove(struct hci_conn *conn)
{
hci_connect_le_scan_cleanup(conn);
hci_conn_hash_del(conn->hdev, conn);
hci_update_background_scan(conn->hdev);
}
static void hci_acl_create_connection(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
......@@ -340,8 +382,12 @@ static void hci_conn_timeout(struct work_struct *work)
if (conn->out) {
if (conn->type == ACL_LINK)
hci_acl_create_connection_cancel(conn);
else if (conn->type == LE_LINK)
hci_le_create_connection_cancel(conn);
else if (conn->type == LE_LINK) {
if (test_bit(HCI_CONN_SCANNING, &conn->flags))
hci_connect_le_scan_remove(conn);
else
hci_le_create_connection_cancel(conn);
}
} else if (conn->type == SCO_LINK || conn->type == ESCO_LINK) {
hci_reject_sco(conn);
}
......@@ -637,15 +683,18 @@ static void create_le_conn_complete(struct hci_dev *hdev, u8 status, u16 opcode)
{
struct hci_conn *conn;
if (status == 0)
return;
hci_dev_lock(hdev);
conn = hci_lookup_le_connect(hdev);
if (!status) {
hci_connect_le_scan_cleanup(conn);
goto done;
}
BT_ERR("HCI request failed to create LE connection: status 0x%2.2x",
status);
hci_dev_lock(hdev);
conn = hci_conn_hash_lookup_state(hdev, LE_LINK, BT_CONNECT);
if (!conn)
goto done;
......@@ -685,6 +734,7 @@ static void hci_req_add_le_create_conn(struct hci_request *req,
hci_req_add(req, HCI_OP_LE_CREATE_CONN, sizeof(cp), &cp);
conn->state = BT_CONNECT;
clear_bit(HCI_CONN_SCANNING, &conn->flags);
}
static void hci_req_directed_advertising(struct hci_request *req,
......@@ -728,7 +778,7 @@ struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst,
u8 role)
{
struct hci_conn_params *params;
struct hci_conn *conn;
struct hci_conn *conn, *conn_unfinished;
struct smp_irk *irk;
struct hci_request req;
int err;
......@@ -751,26 +801,29 @@ struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst,
* and return the object found.
*/
conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, dst);
conn_unfinished = NULL;
if (conn) {
conn->pending_sec_level = sec_level;
goto done;
if (conn->state == BT_CONNECT &&
test_bit(HCI_CONN_SCANNING, &conn->flags)) {
BT_DBG("will continue unfinished conn %pMR", dst);
conn_unfinished = conn;
} else {
if (conn->pending_sec_level < sec_level)
conn->pending_sec_level = sec_level;
goto done;
}
}
/* Since the controller supports only one LE connection attempt at a
* time, we return -EBUSY if there is any connection attempt running.
*/
conn = hci_conn_hash_lookup_state(hdev, LE_LINK, BT_CONNECT);
if (conn)
if (hci_lookup_le_connect(hdev))
return ERR_PTR(-EBUSY);
/* When given an identity address with existing identity
* resolving key, the connection needs to be established
* to a resolvable random address.
*
* This uses the cached random resolvable address from
* a previous scan. When no cached address is available,
* try connecting to the identity address instead.
*
* Storing the resolvable random address is required here
* to handle connection failures. The address will later
* be resolved back into the original identity address
......@@ -782,15 +835,23 @@ struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst,
dst_type = ADDR_LE_DEV_RANDOM;
}
conn = hci_conn_add(hdev, LE_LINK, dst, role);
if (conn_unfinished) {
conn = conn_unfinished;
bacpy(&conn->dst, dst);
} else {
conn = hci_conn_add(hdev, LE_LINK, dst, role);
}
if (!conn)
return ERR_PTR(-ENOMEM);
conn->dst_type = dst_type;
conn->sec_level = BT_SECURITY_LOW;
conn->pending_sec_level = sec_level;
conn->conn_timeout = conn_timeout;
if (!conn_unfinished)
conn->pending_sec_level = sec_level;
hci_req_init(&req, hdev);
/* Disable advertising if we're active. For master role
......@@ -854,6 +915,144 @@ struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst,
return ERR_PTR(err);
}
done:
/* If this is continuation of connect started by hci_connect_le_scan,
* it already called hci_conn_hold and calling it again would mess the
* counter.
*/
if (!conn_unfinished)
hci_conn_hold(conn);
return conn;
}
static void hci_connect_le_scan_complete(struct hci_dev *hdev, u8 status,
u16 opcode)
{
struct hci_conn *conn;
if (!status)
return;
BT_ERR("Failed to add device to auto conn whitelist: status 0x%2.2x",
status);
hci_dev_lock(hdev);
conn = hci_conn_hash_lookup_state(hdev, LE_LINK, BT_CONNECT);
if (conn)
hci_le_conn_failed(conn, status);
hci_dev_unlock(hdev);
}
static bool is_connected(struct hci_dev *hdev, bdaddr_t *addr, u8 type)
{
struct hci_conn *conn;
conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, addr);
if (!conn)
return false;
if (conn->dst_type != type)
return false;
if (conn->state != BT_CONNECTED)
return false;
return true;
}
/* This function requires the caller holds hdev->lock */
static int hci_explicit_conn_params_set(struct hci_request *req,
bdaddr_t *addr, u8 addr_type)
{
struct hci_dev *hdev = req->hdev;
struct hci_conn_params *params;
if (is_connected(hdev, addr, addr_type))
return -EISCONN;
params = hci_conn_params_add(hdev, addr, addr_type);
if (!params)
return -EIO;
/* If we created new params, or existing params were marked as disabled,
* mark them to be used just once to connect.
*/
if (params->auto_connect == HCI_AUTO_CONN_DISABLED) {
params->auto_connect = HCI_AUTO_CONN_EXPLICIT;
list_del_init(&params->action);
list_add(&params->action, &hdev->pend_le_conns);
}
params->explicit_connect = true;
__hci_update_background_scan(req);
BT_DBG("addr %pMR (type %u) auto_connect %u", addr, addr_type,
params->auto_connect);
return 0;
}
/* This function requires the caller holds hdev->lock */
struct hci_conn *hci_connect_le_scan(struct hci_dev *hdev, bdaddr_t *dst,
u8 dst_type, u8 sec_level,
u16 conn_timeout, u8 role)
{
struct hci_conn *conn;
struct hci_request req;
int err;
/* Let's make sure that le is enabled.*/
if (!hci_dev_test_flag(hdev, HCI_LE_ENABLED)) {
if (lmp_le_capable(hdev))
return ERR_PTR(-ECONNREFUSED);
return ERR_PTR(-EOPNOTSUPP);
}
/* Some devices send ATT messages as soon as the physical link is
* established. To be able to handle these ATT messages, the user-
* space first establishes the connection and then starts the pairing
* process.
*
* So if a hci_conn object already exists for the following connection
* attempt, we simply update pending_sec_level and auth_type fields
* and return the object found.
*/
conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, dst);
if (conn) {
if (conn->pending_sec_level < sec_level)
conn->pending_sec_level = sec_level;
goto done;
}
BT_DBG("requesting refresh of dst_addr");
conn = hci_conn_add(hdev, LE_LINK, dst, role);
if (!conn)
return ERR_PTR(-ENOMEM);
hci_req_init(&req, hdev);
if (hci_explicit_conn_params_set(&req, dst, dst_type) < 0)
return ERR_PTR(-EBUSY);
conn->state = BT_CONNECT;
set_bit(HCI_CONN_SCANNING, &conn->flags);
err = hci_req_run(&req, hci_connect_le_scan_complete);
if (err && err != -ENODATA) {
hci_conn_del(conn);
return ERR_PTR(err);
}
conn->dst_type = dst_type;
conn->sec_level = BT_SECURITY_LOW;
conn->pending_sec_level = sec_level;
conn->conn_timeout = conn_timeout;
done:
hci_conn_hold(conn);
return conn;
......
......@@ -2847,6 +2847,30 @@ struct hci_conn_params *hci_pend_le_action_lookup(struct list_head *list,
return NULL;
}
/* This function requires the caller holds hdev->lock */
struct hci_conn_params *hci_explicit_connect_lookup(struct hci_dev *hdev,
bdaddr_t *addr,
u8 addr_type)
{
struct hci_conn_params *param;
list_for_each_entry(param, &hdev->pend_le_conns, action) {
if (bacmp(&param->addr, addr) == 0 &&
param->addr_type == addr_type &&
param->explicit_connect)
return param;
}
list_for_each_entry(param, &hdev->pend_le_reports, action) {
if (bacmp(&param->addr, addr) == 0 &&
param->addr_type == addr_type &&
param->explicit_connect)
return param;
}
return NULL;
}
/* This function requires the caller holds hdev->lock */
struct hci_conn_params *hci_conn_params_add(struct hci_dev *hdev,
bdaddr_t *addr, u8 addr_type)
......@@ -2916,6 +2940,15 @@ void hci_conn_params_clear_disabled(struct hci_dev *hdev)
list_for_each_entry_safe(params, tmp, &hdev->le_conn_params, list) {
if (params->auto_connect != HCI_AUTO_CONN_DISABLED)
continue;
/* If trying to estabilish one time connection to disabled
* device, leave the params, but mark them as just once.
*/
if (params->explicit_connect) {
params->auto_connect = HCI_AUTO_CONN_EXPLICIT;
continue;
}
list_del(&params->list);
kfree(params);
}
......
......@@ -1059,7 +1059,7 @@ static void hci_cc_le_set_adv_enable(struct hci_dev *hdev, struct sk_buff *skb)
hci_dev_set_flag(hdev, HCI_LE_ADV);
conn = hci_conn_hash_lookup_state(hdev, LE_LINK, BT_CONNECT);
conn = hci_lookup_le_connect(hdev);
if (conn)
queue_delayed_work(hdev->workqueue,
&conn->le_conn_timeout,
......@@ -4447,7 +4447,7 @@ static void hci_le_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
*/
hci_dev_clear_flag(hdev, HCI_LE_ADV);
conn = hci_conn_hash_lookup_state(hdev, LE_LINK, BT_CONNECT);
conn = hci_lookup_le_connect(hdev);
if (!conn) {
conn = hci_conn_add(hdev, LE_LINK, &ev->bdaddr, ev->role);
if (!conn) {
......@@ -4640,42 +4640,49 @@ static struct hci_conn *check_pending_le_conn(struct hci_dev *hdev,
/* If we're not connectable only connect devices that we have in
* our pend_le_conns list.
*/
params = hci_pend_le_action_lookup(&hdev->pend_le_conns,
addr, addr_type);
params = hci_explicit_connect_lookup(hdev, addr, addr_type);
if (!params)
return NULL;
switch (params->auto_connect) {
case HCI_AUTO_CONN_DIRECT:
/* Only devices advertising with ADV_DIRECT_IND are
* triggering a connection attempt. This is allowing
* incoming connections from slave devices.
*/
if (adv_type != LE_ADV_DIRECT_IND)
if (!params->explicit_connect) {
switch (params->auto_connect) {
case HCI_AUTO_CONN_DIRECT:
/* Only devices advertising with ADV_DIRECT_IND are
* triggering a connection attempt. This is allowing
* incoming connections from slave devices.
*/
if (adv_type != LE_ADV_DIRECT_IND)
return NULL;
break;
case HCI_AUTO_CONN_ALWAYS:
/* Devices advertising with ADV_IND or ADV_DIRECT_IND
* are triggering a connection attempt. This means
* that incoming connectioms from slave device are
* accepted and also outgoing connections to slave
* devices are established when found.
*/
break;
default:
return NULL;
break;
case HCI_AUTO_CONN_ALWAYS:
/* Devices advertising with ADV_IND or ADV_DIRECT_IND
* are triggering a connection attempt. This means
* that incoming connectioms from slave device are
* accepted and also outgoing connections to slave
* devices are established when found.
*/
break;
default:
return NULL;
}
}
conn = hci_connect_le(hdev, addr, addr_type, BT_SECURITY_LOW,
HCI_LE_AUTOCONN_TIMEOUT, HCI_ROLE_MASTER);
if (!IS_ERR(conn)) {
/* Store the pointer since we don't really have any
/* If HCI_AUTO_CONN_EXPLICIT is set, conn is already owned
* by higher layer that tried to connect, if no then
* store the pointer since we don't really have any
* other owner of the object besides the params that
* triggered it. This way we can abort the connection if
* the parameters get removed and keep the reference
* count consistent once the connection is established.
*/
params->conn = hci_conn_get(conn);
if (!params->explicit_connect)
params->conn = hci_conn_get(conn);
return conn;
}
......
......@@ -317,7 +317,7 @@ static void set_random_addr(struct hci_request *req, bdaddr_t *rpa)
* address be updated at the next cycle.
*/
if (hci_dev_test_flag(hdev, HCI_LE_ADV) ||
hci_conn_hash_lookup_state(hdev, LE_LINK, BT_CONNECT)) {
hci_lookup_le_connect(hdev)) {
BT_DBG("Deferring random address update");
hci_dev_set_flag(hdev, HCI_RPA_EXPIRED);
return;
......@@ -479,7 +479,6 @@ void hci_update_page_scan(struct hci_dev *hdev)
void __hci_update_background_scan(struct hci_request *req)
{
struct hci_dev *hdev = req->hdev;
struct hci_conn *conn;
if (!test_bit(HCI_UP, &hdev->flags) ||
test_bit(HCI_INIT, &hdev->flags) ||
......@@ -529,8 +528,7 @@ void __hci_update_background_scan(struct hci_request *req)
* since some controllers are not able to scan and connect at
* the same time.
*/
conn = hci_conn_hash_lookup_state(hdev, LE_LINK, BT_CONNECT);
if (conn)
if (hci_lookup_le_connect(hdev))
return;
/* If controller is currently scanning, we stop it to ensure we
......
......@@ -7113,8 +7113,10 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
else
role = HCI_ROLE_MASTER;
hcon = hci_connect_le(hdev, dst, dst_type, chan->sec_level,
HCI_LE_CONN_TIMEOUT, role);
hcon = hci_connect_le_scan(hdev, dst, dst_type,
chan->sec_level,
HCI_LE_CONN_TIMEOUT,
role);
} else {
u8 auth_type = l2cap_get_auth_type(chan);
hcon = hci_connect_acl(hdev, dst, chan->sec_level, auth_type);
......
......@@ -3564,9 +3564,10 @@ static int pair_device(struct sock *sk, struct hci_dev *hdev, void *data,
*/
hci_conn_params_add(hdev, &cp->addr.bdaddr, addr_type);
conn = hci_connect_le(hdev, &cp->addr.bdaddr, addr_type,
sec_level, HCI_LE_CONN_TIMEOUT,
HCI_ROLE_MASTER);
conn = hci_connect_le_scan(hdev, &cp->addr.bdaddr,
addr_type, sec_level,
HCI_LE_CONN_TIMEOUT,
HCI_ROLE_MASTER);
}
if (IS_ERR(conn)) {
......@@ -4210,7 +4211,7 @@ static bool trigger_le_scan(struct hci_request *req, u16 interval, u8 *status)
/* Don't let discovery abort an outgoing connection attempt
* that's using directed advertising.
*/
if (hci_conn_hash_lookup_state(hdev, LE_LINK, BT_CONNECT)) {
if (hci_lookup_le_connect(hdev)) {
*status = MGMT_STATUS_REJECTED;
return false;
}
......@@ -6107,6 +6108,12 @@ static int hci_conn_params_set(struct hci_request *req, bdaddr_t *addr,
switch (auto_connect) {
case HCI_AUTO_CONN_DISABLED:
case HCI_AUTO_CONN_LINK_LOSS:
/* If auto connect is being disabled when we're trying to
* connect to device, keep connecting.
*/
if (params->explicit_connect)
list_add(&params->action, &hdev->pend_le_conns);
__hci_update_background_scan(req);
break;
case HCI_AUTO_CONN_REPORT:
......
......@@ -5,6 +5,7 @@
#include <net/ieee802154_netdev.h>
#include <net/inet_frag.h>
#include <net/6lowpan.h>
struct lowpan_create_arg {
u16 tag;
......@@ -37,26 +38,18 @@ static inline u32 ieee802154_addr_hash(const struct ieee802154_addr *a)
}
}
struct lowpan_dev_record {
struct net_device *ldev;
struct list_head list;
};
/* private device info */
struct lowpan_dev_info {
struct net_device *real_dev; /* real WPAN device ptr */
struct mutex dev_list_mtx; /* mutex for list ops */
u16 fragment_tag;
};
static inline struct
lowpan_dev_info *lowpan_dev_info(const struct net_device *dev)
{
return netdev_priv(dev);
return (struct lowpan_dev_info *)lowpan_priv(dev)->priv;
}
extern struct list_head lowpan_devices;
int lowpan_frag_rcv(struct sk_buff *skb, const u8 frag_type);
void lowpan_net_frag_exit(void);
int lowpan_net_frag_init(void);
......
......@@ -52,8 +52,7 @@
#include "6lowpan_i.h"
LIST_HEAD(lowpan_devices);
static int lowpan_open_count;
static int open_count;
static struct header_ops lowpan_header_ops = {
.create = lowpan_header_create,
......@@ -114,7 +113,6 @@ static int lowpan_newlink(struct net *src_net, struct net_device *dev,
struct nlattr *tb[], struct nlattr *data[])
{
struct net_device *real_dev;
struct lowpan_dev_record *entry;
int ret;
ASSERT_RTNL();
......@@ -133,67 +131,52 @@ static int lowpan_newlink(struct net *src_net, struct net_device *dev,
return -EINVAL;
}
lowpan_dev_info(dev)->real_dev = real_dev;
mutex_init(&lowpan_dev_info(dev)->dev_list_mtx);
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry) {
if (real_dev->ieee802154_ptr->lowpan_dev) {
dev_put(real_dev);
lowpan_dev_info(dev)->real_dev = NULL;
return -ENOMEM;
return -EBUSY;
}
entry->ldev = dev;
lowpan_dev_info(dev)->real_dev = real_dev;
/* Set the lowpan hardware address to the wpan hardware address. */
memcpy(dev->dev_addr, real_dev->dev_addr, IEEE802154_ADDR_LEN);
mutex_lock(&lowpan_dev_info(dev)->dev_list_mtx);
INIT_LIST_HEAD(&entry->list);
list_add_tail(&entry->list, &lowpan_devices);
mutex_unlock(&lowpan_dev_info(dev)->dev_list_mtx);
lowpan_netdev_setup(dev, LOWPAN_LLTYPE_IEEE802154);
ret = register_netdevice(dev);
if (ret >= 0) {
if (!lowpan_open_count)
lowpan_rx_init();
lowpan_open_count++;
if (ret < 0) {
dev_put(real_dev);
return ret;
}
return ret;
real_dev->ieee802154_ptr->lowpan_dev = dev;
if (!open_count)
lowpan_rx_init();
open_count++;
return 0;
}
static void lowpan_dellink(struct net_device *dev, struct list_head *head)
{
struct lowpan_dev_info *lowpan_dev = lowpan_dev_info(dev);
struct net_device *real_dev = lowpan_dev->real_dev;
struct lowpan_dev_record *entry, *tmp;
ASSERT_RTNL();
lowpan_open_count--;
if (!lowpan_open_count)
lowpan_rx_exit();
mutex_lock(&lowpan_dev_info(dev)->dev_list_mtx);
list_for_each_entry_safe(entry, tmp, &lowpan_devices, list) {
if (entry->ldev == dev) {
list_del(&entry->list);
kfree(entry);
}
}
mutex_unlock(&lowpan_dev_info(dev)->dev_list_mtx);
open_count--;
mutex_destroy(&lowpan_dev_info(dev)->dev_list_mtx);
unregister_netdevice_queue(dev, head);
if (!open_count)
lowpan_rx_exit();
real_dev->ieee802154_ptr->lowpan_dev = NULL;
unregister_netdevice(dev);
dev_put(real_dev);
}
static struct rtnl_link_ops lowpan_link_ops __read_mostly = {
.kind = "lowpan",
.priv_size = sizeof(struct lowpan_dev_info),
.priv_size = LOWPAN_PRIV_SIZE(sizeof(struct lowpan_dev_info)),
.setup = lowpan_setup,
.newlink = lowpan_newlink,
.dellink = lowpan_dellink,
......@@ -214,19 +197,21 @@ static int lowpan_device_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
LIST_HEAD(del_list);
struct lowpan_dev_record *entry, *tmp;
if (dev->type != ARPHRD_IEEE802154)
goto out;
if (event == NETDEV_UNREGISTER) {
list_for_each_entry_safe(entry, tmp, &lowpan_devices, list) {
if (lowpan_dev_info(entry->ldev)->real_dev == dev)
lowpan_dellink(entry->ldev, &del_list);
}
unregister_netdevice_many(&del_list);
switch (event) {
case NETDEV_UNREGISTER:
/* Check if wpan interface is unregistered that we
* also delete possible lowpan interfaces which belongs
* to the wpan interface.
*/
if (dev->ieee802154_ptr && dev->ieee802154_ptr->lowpan_dev)
lowpan_dellink(dev->ieee802154_ptr->lowpan_dev, NULL);
break;
default:
break;
}
out:
......
......@@ -15,36 +15,14 @@
#include "6lowpan_i.h"
static int lowpan_give_skb_to_devices(struct sk_buff *skb,
struct net_device *dev)
static int lowpan_give_skb_to_device(struct sk_buff *skb,
struct net_device *dev)
{
struct lowpan_dev_record *entry;
struct sk_buff *skb_cp;
int stat = NET_RX_SUCCESS;
skb->dev = dev->ieee802154_ptr->lowpan_dev;
skb->protocol = htons(ETH_P_IPV6);
skb->pkt_type = PACKET_HOST;
rcu_read_lock();
list_for_each_entry_rcu(entry, &lowpan_devices, list)
if (lowpan_dev_info(entry->ldev)->real_dev == skb->dev) {
skb_cp = skb_copy(skb, GFP_ATOMIC);
if (!skb_cp) {
kfree_skb(skb);
rcu_read_unlock();
return NET_RX_DROP;
}
skb_cp->dev = entry->ldev;
stat = netif_rx(skb_cp);
if (stat == NET_RX_DROP)
break;
}
rcu_read_unlock();
consume_skb(skb);
return stat;
return netif_rx(skb);
}
static int
......@@ -89,6 +67,10 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev,
struct ieee802154_hdr hdr;
int ret;
if (dev->type != ARPHRD_IEEE802154 ||
!dev->ieee802154_ptr->lowpan_dev)
goto drop;
skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb)
goto drop;
......@@ -99,9 +81,6 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev,
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop_skb;
if (dev->type != ARPHRD_IEEE802154)
goto drop_skb;
if (ieee802154_hdr_peek_addrs(skb, &hdr) < 0)
goto drop_skb;
......@@ -109,7 +88,7 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev,
if (skb->data[0] == LOWPAN_DISPATCH_IPV6) {
/* Pull off the 1-byte of 6lowpan header. */
skb_pull(skb, 1);
return lowpan_give_skb_to_devices(skb, NULL);
return lowpan_give_skb_to_device(skb, dev);
} else {
switch (skb->data[0] & 0xe0) {
case LOWPAN_DISPATCH_IPHC: /* ipv6 datagram */
......@@ -117,7 +96,7 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev,
if (ret < 0)
goto drop_skb;
return lowpan_give_skb_to_devices(skb, NULL);
return lowpan_give_skb_to_device(skb, dev);
case LOWPAN_DISPATCH_FRAG1: /* first fragment header */
ret = lowpan_frag_rcv(skb, LOWPAN_DISPATCH_FRAG1);
if (ret == 1) {
......@@ -125,7 +104,7 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev,
if (ret < 0)
goto drop_skb;
return lowpan_give_skb_to_devices(skb, NULL);
return lowpan_give_skb_to_device(skb, dev);
} else if (ret == -1) {
return NET_RX_DROP;
} else {
......@@ -138,7 +117,7 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev,
if (ret < 0)
goto drop_skb;
return lowpan_give_skb_to_devices(skb, NULL);
return lowpan_give_skb_to_device(skb, dev);
} else if (ret == -1) {
return NET_RX_DROP;
} else {
......
......@@ -112,7 +112,7 @@ lowpan_xmit_fragment(struct sk_buff *skb, const struct ieee802154_hdr *wpan_hdr,
frag = lowpan_alloc_frag(skb, frag_hdrlen + len, wpan_hdr);
if (IS_ERR(frag))
return -PTR_ERR(frag);
return PTR_ERR(frag);
memcpy(skb_put(frag, frag_hdrlen), frag_hdr, frag_hdrlen);
memcpy(skb_put(frag, len), skb_network_header(skb) + offset, len);
......@@ -224,7 +224,7 @@ static int lowpan_header(struct sk_buff *skb, struct net_device *dev)
} else {
da.mode = IEEE802154_ADDR_LONG;
da.extended_addr = ieee802154_devaddr_from_raw(daddr);
cb->ackreq = wpan_dev->frame_retries >= 0;
cb->ackreq = wpan_dev->ackreq;
}
return dev_hard_header(skb, lowpan_dev_info(dev)->real_dev,
......
......@@ -230,6 +230,8 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
[NL802154_ATTR_WPAN_PHY_CAPS] = { .type = NLA_NESTED },
[NL802154_ATTR_SUPPORTED_COMMANDS] = { .type = NLA_NESTED },
[NL802154_ATTR_ACKREQ_DEFAULT] = { .type = NLA_U8 },
};
/* message building helper */
......@@ -458,6 +460,7 @@ static int nl802154_send_wpan_phy(struct cfg802154_registered_device *rdev,
CMD(set_max_csma_backoffs, SET_MAX_CSMA_BACKOFFS);
CMD(set_max_frame_retries, SET_MAX_FRAME_RETRIES);
CMD(set_lbt_mode, SET_LBT_MODE);
CMD(set_ackreq_default, SET_ACKREQ_DEFAULT);
if (rdev->wpan_phy.flags & WPAN_PHY_FLAG_TXPOWER)
CMD(set_tx_power, SET_TX_POWER);
......@@ -656,6 +659,10 @@ nl802154_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flags,
if (nla_put_u8(msg, NL802154_ATTR_LBT_MODE, wpan_dev->lbt))
goto nla_put_failure;
/* ackreq default behaviour */
if (nla_put_u8(msg, NL802154_ATTR_ACKREQ_DEFAULT, wpan_dev->ackreq))
goto nla_put_failure;
genlmsg_end(msg, hdr);
return 0;
......@@ -1042,6 +1049,24 @@ static int nl802154_set_lbt_mode(struct sk_buff *skb, struct genl_info *info)
return rdev_set_lbt_mode(rdev, wpan_dev, mode);
}
static int
nl802154_set_ackreq_default(struct sk_buff *skb, struct genl_info *info)
{
struct cfg802154_registered_device *rdev = info->user_ptr[0];
struct net_device *dev = info->user_ptr[1];
struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
bool ackreq;
if (netif_running(dev))
return -EBUSY;
if (!info->attrs[NL802154_ATTR_ACKREQ_DEFAULT])
return -EINVAL;
ackreq = !!nla_get_u8(info->attrs[NL802154_ATTR_ACKREQ_DEFAULT]);
return rdev_set_ackreq_default(rdev, wpan_dev, ackreq);
}
#define NL802154_FLAG_NEED_WPAN_PHY 0x01
#define NL802154_FLAG_NEED_NETDEV 0x02
#define NL802154_FLAG_NEED_RTNL 0x04
......@@ -1248,6 +1273,14 @@ static const struct genl_ops nl802154_ops[] = {
.internal_flags = NL802154_FLAG_NEED_NETDEV |
NL802154_FLAG_NEED_RTNL,
},
{
.cmd = NL802154_CMD_SET_ACKREQ_DEFAULT,
.doit = nl802154_set_ackreq_default,
.policy = nl802154_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL802154_FLAG_NEED_NETDEV |
NL802154_FLAG_NEED_RTNL,
},
};
/* initialisation/exit functions */
......
......@@ -195,4 +195,17 @@ rdev_set_lbt_mode(struct cfg802154_registered_device *rdev,
return ret;
}
static inline int
rdev_set_ackreq_default(struct cfg802154_registered_device *rdev,
struct wpan_dev *wpan_dev, bool ackreq)
{
int ret;
trace_802154_rdev_set_ackreq_default(&rdev->wpan_phy, wpan_dev,
ackreq);
ret = rdev->ops->set_ackreq_default(&rdev->wpan_phy, wpan_dev, ackreq);
trace_802154_rdev_return_int(&rdev->wpan_phy, ret);
return ret;
}
#endif /* __CFG802154_RDEV_OPS */
......@@ -275,6 +275,25 @@ TRACE_EVENT(802154_rdev_set_lbt_mode,
WPAN_DEV_PR_ARG, BOOL_TO_STR(__entry->mode))
);
TRACE_EVENT(802154_rdev_set_ackreq_default,
TP_PROTO(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
bool ackreq),
TP_ARGS(wpan_phy, wpan_dev, ackreq),
TP_STRUCT__entry(
WPAN_PHY_ENTRY
WPAN_DEV_ENTRY
__field(bool, ackreq)
),
TP_fast_assign(
WPAN_PHY_ASSIGN;
WPAN_DEV_ASSIGN;
__entry->ackreq = ackreq;
),
TP_printk(WPAN_PHY_PR_FMT ", " WPAN_DEV_PR_FMT
", ackreq default: %s", WPAN_PHY_PR_ARG,
WPAN_DEV_PR_ARG, BOOL_TO_STR(__entry->ackreq))
);
TRACE_EVENT(802154_rdev_return_int,
TP_PROTO(struct wpan_phy *wpan_phy, int ret),
TP_ARGS(wpan_phy, ret),
......
......@@ -209,10 +209,6 @@ ieee802154_set_backoff_exponent(struct wpan_phy *wpan_phy,
{
ASSERT_RTNL();
if (wpan_dev->min_be == min_be &&
wpan_dev->max_be == max_be)
return 0;
wpan_dev->min_be = min_be;
wpan_dev->max_be = max_be;
return 0;
......@@ -224,9 +220,6 @@ ieee802154_set_short_addr(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
{
ASSERT_RTNL();
if (wpan_dev->short_addr == short_addr)
return 0;
wpan_dev->short_addr = short_addr;
return 0;
}
......@@ -238,9 +231,6 @@ ieee802154_set_max_csma_backoffs(struct wpan_phy *wpan_phy,
{
ASSERT_RTNL();
if (wpan_dev->csma_retries == max_csma_backoffs)
return 0;
wpan_dev->csma_retries = max_csma_backoffs;
return 0;
}
......@@ -252,9 +242,6 @@ ieee802154_set_max_frame_retries(struct wpan_phy *wpan_phy,
{
ASSERT_RTNL();
if (wpan_dev->frame_retries == max_frame_retries)
return 0;
wpan_dev->frame_retries = max_frame_retries;
return 0;
}
......@@ -265,13 +252,20 @@ ieee802154_set_lbt_mode(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
{
ASSERT_RTNL();
if (wpan_dev->lbt == mode)
return 0;
wpan_dev->lbt = mode;
return 0;
}
static int
ieee802154_set_ackreq_default(struct wpan_phy *wpan_phy,
struct wpan_dev *wpan_dev, bool ackreq)
{
ASSERT_RTNL();
wpan_dev->ackreq = ackreq;
return 0;
}
const struct cfg802154_ops mac802154_config_ops = {
.add_virtual_intf_deprecated = ieee802154_add_iface_deprecated,
.del_virtual_intf_deprecated = ieee802154_del_iface_deprecated,
......@@ -289,4 +283,5 @@ const struct cfg802154_ops mac802154_config_ops = {
.set_max_csma_backoffs = ieee802154_set_max_csma_backoffs,
.set_max_frame_retries = ieee802154_set_max_frame_retries,
.set_lbt_mode = ieee802154_set_lbt_mode,
.set_ackreq_default = ieee802154_set_ackreq_default,
};
......@@ -125,6 +125,14 @@ static int mac802154_wpan_mac_addr(struct net_device *dev, void *p)
if (netif_running(dev))
return -EBUSY;
/* lowpan need to be down for update
* SLAAC address after ifup
*/
if (sdata->wpan_dev.lowpan_dev) {
if (netif_running(sdata->wpan_dev.lowpan_dev))
return -EBUSY;
}
ieee802154_be64_to_le64(&extended_addr, addr->sa_data);
if (!ieee802154_is_valid_extended_unicast_addr(extended_addr))
return -EINVAL;
......@@ -132,6 +140,13 @@ static int mac802154_wpan_mac_addr(struct net_device *dev, void *p)
memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
sdata->wpan_dev.extended_addr = extended_addr;
/* update lowpan interface mac address when
* wpan mac has been changed
*/
if (sdata->wpan_dev.lowpan_dev)
memcpy(sdata->wpan_dev.lowpan_dev->dev_addr, dev->dev_addr,
dev->addr_len);
return mac802154_wpan_update_llsec(dev);
}
......@@ -483,8 +498,7 @@ ieee802154_setup_sdata(struct ieee802154_sub_if_data *sdata,
wpan_dev->min_be = 3;
wpan_dev->max_be = 5;
wpan_dev->csma_retries = 4;
/* for compatibility, actual default is 3 */
wpan_dev->frame_retries = -1;
wpan_dev->frame_retries = 3;
wpan_dev->pan_id = cpu_to_le16(IEEE802154_PANID_BROADCAST);
wpan_dev->short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST);
......
......@@ -111,7 +111,7 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops)
phy->supported.max_minbe = 8;
phy->supported.min_maxbe = 3;
phy->supported.max_maxbe = 8;
phy->supported.min_frame_retries = -1;
phy->supported.min_frame_retries = 0;
phy->supported.max_frame_retries = 7;
phy->supported.max_csma_backoffs = 5;
phy->supported.lbt = NL802154_SUPPORTED_BOOL_FALSE;
......@@ -177,11 +177,8 @@ int ieee802154_register_hw(struct ieee802154_hw *hw)
}
if (!(hw->flags & IEEE802154_HW_FRAME_RETRIES)) {
/* TODO should be 3, but our default value is -1 which means
* no ARET handling.
*/
local->phy->supported.min_frame_retries = -1;
local->phy->supported.max_frame_retries = -1;
local->phy->supported.min_frame_retries = 3;
local->phy->supported.max_frame_retries = 3;
}
if (hw->flags & IEEE802154_HW_PROMISCUOUS)
......
......@@ -164,7 +164,6 @@ static int rfkill_gpio_remove(struct platform_device *pdev)
#ifdef CONFIG_ACPI
static const struct acpi_device_id rfkill_acpi_match[] = {
{ "BCM2E1A", RFKILL_TYPE_BLUETOOTH },
{ "BCM2E39", RFKILL_TYPE_BLUETOOTH },
{ "BCM2E3D", RFKILL_TYPE_BLUETOOTH },
{ "BCM2E40", RFKILL_TYPE_BLUETOOTH },
{ "BCM2E64", RFKILL_TYPE_BLUETOOTH },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment