mirror of
https://github.com/torvalds/linux.git
synced 2026-09-14 16:10:02 +02:00
bluetooth pull request for net:
Core: - hci_core: use skb_get() instead of skb_clone() for req_skb - hci_conn: re-enable advertising only for peripheral role - hci_event: clear HCI_LE_ADV only on a created connection - hci_sync: Clear HCI_CMD_PENDING when dropping the last request - hci_sync: add conditional locking annotations - hci_sync: do not leak an hci_conn when a second LE connect is rejected - eir: Fix OOB read in eir_get_service_data() - mgmt: fix 'hdev->discovery.uuids' NULL dereference - L2CAP: access chan->conn safely in get/setsockopt - L2CAP: reject accept queue add unless BT_LISTEN - L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan - RFCOMM: serialize security confirmation handling - RFCOMM: serialize session teardown - RFCOMM: Validate MTU in rfcomm_apply_pn() to prevent infinite loop - ISO: fix use-after-free of listener socket in iso_conn_ready Drivers: - btnxpuart: Validate the FW dump header length - btnxpuart: Check remote M.2 connector availability before pwrseq - btmtksdio: Take exclusive ownership of the SKB before TX - btmtksdio: Fix out-of-bounds DMA read in the TX path - hci_uart: Fix false success return in hci_uart_setup() - hci_bcm: fix usage_count leak when autosuspend_delay is negative - hci_h5: fix usage_count leak when autosuspend_delay is negative - hci_intel: fix usage_count leak when autosuspend_delay is negative - btmtk: Do not report success when subsys reset fails - btmtk: Do not discard the subsystem reset timeout - btusb: limit RTL8761B BROKEN_EXT_SCAN quirk to 0bda:a728 - hci_bcm4377: Ignore reserved PHY in ext adv reports on BCM4378 -----BEGIN PGP SIGNATURE----- iQJNBAABCgA3FiEE7E6oRXp8w05ovYr/9JCA4xAyCykFAmqMhYgZHGx1aXoudm9u LmRlbnR6QGludGVsLmNvbQAKCRD0kIDjEDILKcEvD/9aWqXXH3K6WMwGtRnYUdjd LMqf8U/qA4h/hu+zk8+YIcpWkORDG4EWx01ZTZDnOeHi0/XREtEaKNZ6AbP91CXR J9ZGUVMAuhJ4SrE3gIBaAqw/1NA1m4ygL5i57tIfIdfgkye6nHPagyVUGWJmBAMr QLF8M6WmRghHhvElX69zk640o7v21/XadmpA02/78o/I73BNHBvBdZH/Dd8rGwAI +Z4TJ/tSRVJQO15MiutWu92tGMiFt7AN7Bzj1Xv6E+0yiOqCwOfXpGXq6ALGofZy 5iZ8BHWhvRTtj/4sSU2M9p/sH/dwbBW6CA69QL8CwAMnKPbARHwlx/nI5vXEVWKK Cb3ItdG65AoMV4+/vg0VuhbUmXDTk2ZI44KW2sunXcY/aVjA1O6afnIyFcQZ/JSr Fp+F/C8ZYtBDJqenX3d6KuHRUJtierkCSjWovetI3ty2BJ07jsb0RLb3BuEeJDtx RK1YiOlLTfFVaJCDgGcJ7CcOLlAN6EYv45WtlH395uLdSwNqNbS3GdvwyX2qSTjs 2MNXLYL0EnnjSBBKuvjl0Z50H5t8OYt+UDcZRiwPO1r4rWlilUC8jS19kH4qiTcm PXjtjtfc0PZiKnE5lF7aWY0gNbaPvUHHG/PCZQCd55s3DgKE8y+nap1v7f/5gvMD wV4Wga1ecjWjJUbMzGkAoA== =q5DT -----END PGP SIGNATURE----- Merge tag 'for-net-2026-08-24' of git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth Luiz Augusto von Dentz says: ==================== bluetooth pull request for net: Core: - hci_core: use skb_get() instead of skb_clone() for req_skb - hci_conn: re-enable advertising only for peripheral role - hci_event: clear HCI_LE_ADV only on a created connection - hci_sync: Clear HCI_CMD_PENDING when dropping the last request - hci_sync: add conditional locking annotations - hci_sync: do not leak an hci_conn when a second LE connect is rejected - eir: Fix OOB read in eir_get_service_data() - mgmt: fix 'hdev->discovery.uuids' NULL dereference - L2CAP: access chan->conn safely in get/setsockopt - L2CAP: reject accept queue add unless BT_LISTEN - L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan - RFCOMM: serialize security confirmation handling - RFCOMM: serialize session teardown - RFCOMM: Validate MTU in rfcomm_apply_pn() to prevent infinite loop - ISO: fix use-after-free of listener socket in iso_conn_ready Drivers: - btnxpuart: Validate the FW dump header length - btnxpuart: Check remote M.2 connector availability before pwrseq - btmtksdio: Take exclusive ownership of the SKB before TX - btmtksdio: Fix out-of-bounds DMA read in the TX path - hci_uart: Fix false success return in hci_uart_setup() - hci_bcm: fix usage_count leak when autosuspend_delay is negative - hci_h5: fix usage_count leak when autosuspend_delay is negative - hci_intel: fix usage_count leak when autosuspend_delay is negative - btmtk: Do not report success when subsys reset fails - btmtk: Do not discard the subsystem reset timeout - btusb: limit RTL8761B BROKEN_EXT_SCAN quirk to 0bda:a728 - hci_bcm4377: Ignore reserved PHY in ext adv reports on BCM4378 * tag 'for-net-2026-08-24' of git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth: (27 commits) Bluetooth: RFCOMM: serialize session teardown Bluetooth: do not leak an hci_conn when a second LE connect is rejected Bluetooth: RFCOMM: serialize security confirmation handling Bluetooth: btusb: limit RTL8761B BROKEN_EXT_SCAN quirk to 0bda:a728 Bluetooth: hci_uart: Fix false success return in hci_uart_setup() Bluetooth: RFCOMM: Validate MTU in rfcomm_apply_pn() to prevent infinite loop Bluetooth: ISO: fix use-after-free of listener socket in iso_conn_ready Bluetooth: hci_core: use skb_get() instead of skb_clone() for req_skb Bluetooth: hci_event: clear HCI_LE_ADV only on a created connection Bluetooth: hci_conn: re-enable advertising only for peripheral role Bluetooth: hci_bcm4377: Ignore reserved PHY in ext adv reports on BCM4378 Bluetooth: eir: Fix OOB read in eir_get_service_data() Bluetooth: btnxpuart: Validate the FW dump header length Bluetooth: hci_sync: add conditional locking annotations Bluetooth: btnxpuart: Check remote M.2 connector availability before pwrseq Bluetooth: btmtksdio: Fix out-of-bounds DMA read in the TX path Bluetooth: btmtksdio: Take exclusive ownership of the SKB before TX Bluetooth: btmtk: Do not discard the subsystem reset timeout Bluetooth: btmtk: Do not report success when subsys reset fails Bluetooth: L2CAP: fix race l2cap_sock_cleanup_listen() vs. put_chan ... ==================== Link: https://patch.msgid.link/20260824180639.3570348-1-luiz.dentz@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
b70b7b7156
|
|
@ -860,6 +860,7 @@ static u32 btmtk_usb_reset_done(struct hci_dev *hdev)
|
|||
|
||||
int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id)
|
||||
{
|
||||
int reset_err = 0;
|
||||
u32 val;
|
||||
int err;
|
||||
|
||||
|
|
@ -958,8 +959,10 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id)
|
|||
|
||||
err = readx_poll_timeout(btmtk_usb_reset_done, hdev, val,
|
||||
val & MTK_BT_RST_DONE, 20000, 1000000);
|
||||
if (err < 0)
|
||||
if (err < 0) {
|
||||
bt_dev_err(hdev, "Reset timeout");
|
||||
reset_err = err;
|
||||
}
|
||||
|
||||
if (dev_id == 0x7922) {
|
||||
err = btmtk_usb_uhw_reg_write(hdev, MTK_UDMA_INT_STA_BT, 0x000000FF);
|
||||
|
|
@ -968,10 +971,12 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id)
|
|||
}
|
||||
|
||||
err = btmtk_usb_id_get(hdev, 0x70010200, &val);
|
||||
if (err || (!val && dev_id != 0x6639))
|
||||
if (err || (!val && dev_id != 0x6639)) {
|
||||
bt_dev_err(hdev, "Can't get device id, subsys reset fail.");
|
||||
return err ? err : -ENODEV;
|
||||
}
|
||||
|
||||
return err;
|
||||
return reset_err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(btmtk_usb_subsys_reset);
|
||||
|
||||
|
|
|
|||
|
|
@ -272,12 +272,24 @@ static int btmtksdio_tx_packet(struct btmtksdio_dev *bdev,
|
|||
struct sk_buff *skb)
|
||||
{
|
||||
struct mtkbtsdio_hdr *sdio_hdr;
|
||||
unsigned int len, pad_len;
|
||||
int err;
|
||||
|
||||
/* Make sure that there are enough rooms for SDIO header */
|
||||
if (unlikely(skb_headroom(skb) < sizeof(*sdio_hdr))) {
|
||||
err = pskb_expand_head(skb, sizeof(*sdio_hdr), 0,
|
||||
GFP_ATOMIC);
|
||||
/* Make sure that the data buffer is not shared with anyone else and
|
||||
* that there is enough room for the SDIO header
|
||||
*/
|
||||
err = skb_cow_head(skb, sizeof(*sdio_hdr));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* The transfer is rounded up to the SDIO block size, so the buffer
|
||||
* has to provide tailroom for the padding as well
|
||||
*/
|
||||
len = skb->len + sizeof(*sdio_hdr);
|
||||
pad_len = round_up(len, MTK_SDIO_BLOCK_SIZE) - len;
|
||||
|
||||
if (unlikely(skb_tailroom(skb) < pad_len)) {
|
||||
err = pskb_expand_head(skb, 0, pad_len, GFP_ATOMIC);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
|
@ -290,19 +302,22 @@ static int btmtksdio_tx_packet(struct btmtksdio_dev *bdev,
|
|||
sdio_hdr->reserved = cpu_to_le16(0);
|
||||
sdio_hdr->bt_type = hci_skb_pkt_type(skb);
|
||||
|
||||
clear_bit(BTMTKSDIO_HW_TX_READY, &bdev->tx_state);
|
||||
err = sdio_writesb(bdev->func, MTK_REG_CTDR, skb->data,
|
||||
round_up(skb->len, MTK_SDIO_BLOCK_SIZE));
|
||||
if (err < 0)
|
||||
goto err_skb_pull;
|
||||
/* Zero the padding so that no uninitialised memory is sent out */
|
||||
skb_put_zero(skb, pad_len);
|
||||
|
||||
bdev->hdev->stat.byte_tx += skb->len;
|
||||
clear_bit(BTMTKSDIO_HW_TX_READY, &bdev->tx_state);
|
||||
err = sdio_writesb(bdev->func, MTK_REG_CTDR, skb->data, skb->len);
|
||||
if (err < 0)
|
||||
goto err_skb_restore;
|
||||
|
||||
bdev->hdev->stat.byte_tx += len;
|
||||
|
||||
kfree_skb(skb);
|
||||
|
||||
return 0;
|
||||
|
||||
err_skb_pull:
|
||||
err_skb_restore:
|
||||
skb_trim(skb, len);
|
||||
skb_pull(skb, sizeof(*sdio_hdr));
|
||||
|
||||
return err;
|
||||
|
|
|
|||
|
|
@ -1359,12 +1359,21 @@ static int nxp_process_fw_dump(struct hci_dev *hdev, struct sk_buff *skb)
|
|||
{
|
||||
struct hci_acl_hdr *acl_hdr = (struct hci_acl_hdr *)skb_pull_data(skb,
|
||||
sizeof(*acl_hdr));
|
||||
struct nxp_fw_dump_hdr *fw_dump_hdr = (struct nxp_fw_dump_hdr *)skb->data;
|
||||
struct nxp_fw_dump_hdr *fw_dump_hdr;
|
||||
struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
|
||||
__u16 seq_num = __le16_to_cpu(fw_dump_hdr->seq_num);
|
||||
__u16 buf_len = __le16_to_cpu(fw_dump_hdr->buf_len);
|
||||
__u16 seq_num;
|
||||
__u16 buf_len;
|
||||
int err;
|
||||
|
||||
fw_dump_hdr = skb_pull_data(skb, sizeof(*fw_dump_hdr));
|
||||
if (!fw_dump_hdr) {
|
||||
bt_dev_warn(hdev, "FW dump: invalid or corrupt fw dump chunk");
|
||||
goto free_skb;
|
||||
}
|
||||
|
||||
seq_num = __le16_to_cpu(fw_dump_hdr->seq_num);
|
||||
buf_len = __le16_to_cpu(fw_dump_hdr->buf_len);
|
||||
|
||||
if (seq_num == 0x0001) {
|
||||
if (test_and_set_bit(BTNXPUART_FW_DUMP_IN_PROGRESS, &nxpdev->tx_state)) {
|
||||
bt_dev_err(hdev, "FW dump already in progress");
|
||||
|
|
@ -1809,6 +1818,28 @@ static void nxp_coredump_notify(struct hci_dev *hdev, int state)
|
|||
kobject_uevent_env(&serdev->dev.kobj, KOBJ_CHANGE, envp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the remote M.2 connector device linked via OF graph is present
|
||||
* and available. This is used to determine whether the pwrseq path should
|
||||
* be taken. When the remote connector node is disabled (e.g., by a DT
|
||||
* overlay switching from PCIe WiFi to SDIO WiFi), the pwrseq path is
|
||||
* skipped, allowing the BT driver to use a direct bluetooth child node
|
||||
* instead.
|
||||
*/
|
||||
static bool nxp_m2_connector_is_available(struct device *dev)
|
||||
{
|
||||
struct device_node *ep __free(device_node) =
|
||||
of_graph_get_next_endpoint(dev_of_node(dev), NULL);
|
||||
|
||||
if (!ep)
|
||||
return false;
|
||||
|
||||
struct device_node *remote __free(device_node) =
|
||||
of_graph_get_remote_port_parent(ep);
|
||||
|
||||
return remote && of_device_is_available(remote);
|
||||
}
|
||||
|
||||
static int nxp_serdev_probe(struct serdev_device *serdev)
|
||||
{
|
||||
struct hci_dev *hdev;
|
||||
|
|
@ -1863,7 +1894,7 @@ static int nxp_serdev_probe(struct serdev_device *serdev)
|
|||
return err;
|
||||
}
|
||||
|
||||
if (of_graph_is_present(dev_of_node(&serdev->ctrl->dev))) {
|
||||
if (nxp_m2_connector_is_available(&serdev->ctrl->dev)) {
|
||||
struct pwrseq_desc *pwrseq;
|
||||
|
||||
pwrseq = pwrseq_get(&serdev->ctrl->dev, "uart");
|
||||
|
|
|
|||
|
|
@ -1343,19 +1343,6 @@ void btrtl_set_quirks(struct hci_dev *hdev, struct btrtl_device_info *btrtl_dev)
|
|||
if (!btrtl_dev->ic_info)
|
||||
return;
|
||||
|
||||
switch (btrtl_dev->project_id) {
|
||||
case CHIP_ID_8761B:
|
||||
/* RTL8761B/BU reports HCI version 5.1 but does not support
|
||||
* the LE Extended Scan commands (Opcode 0x2042), causing
|
||||
* repeated -EBUSY failures when BlueZ attempts extended
|
||||
* scanning while a connection is active.
|
||||
*/
|
||||
hci_set_quirk(hdev, HCI_QUIRK_BROKEN_EXT_SCAN);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (btrtl_dev->ic_info->lmp_subver) {
|
||||
case RTL_ROM_LMP_8703B:
|
||||
/* 8723CS reports two pages for local ext features,
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ static struct usb_driver btusb_driver;
|
|||
#define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26)
|
||||
#define BTUSB_ACTIONS_SEMI BIT(27)
|
||||
#define BTUSB_BARROT BIT(28)
|
||||
#define BTUSB_BROKEN_EXT_SCAN BIT(29)
|
||||
|
||||
static const struct usb_device_id btusb_table[] = {
|
||||
/* Generic Bluetooth USB device */
|
||||
|
|
@ -617,6 +618,10 @@ static const struct usb_device_id quirks_table[] = {
|
|||
{ USB_DEVICE(0x0489, 0xe130), .driver_info = BTUSB_REALTEK |
|
||||
BTUSB_WIDEBAND_SPEECH },
|
||||
|
||||
/* Realtek 8761BU Bluetooth devices */
|
||||
{ USB_DEVICE(0x0bda, 0xa728), .driver_info = BTUSB_REALTEK |
|
||||
BTUSB_BROKEN_EXT_SCAN },
|
||||
|
||||
/* Realtek Bluetooth devices */
|
||||
{ USB_VENDOR_AND_INTERFACE_INFO(0x0bda, 0xe0, 0x01, 0x01),
|
||||
.driver_info = BTUSB_REALTEK },
|
||||
|
|
@ -4401,6 +4406,9 @@ static int btusb_probe(struct usb_interface *intf,
|
|||
if (id->driver_info & BTUSB_INVALID_LE_STATES)
|
||||
hci_set_quirk(hdev, HCI_QUIRK_BROKEN_LE_STATES);
|
||||
|
||||
if (id->driver_info & BTUSB_BROKEN_EXT_SCAN)
|
||||
hci_set_quirk(hdev, HCI_QUIRK_BROKEN_EXT_SCAN);
|
||||
|
||||
if (id->driver_info & BTUSB_DIGIANSWER) {
|
||||
data->cmdreq_type = USB_TYPE_VENDOR;
|
||||
hci_set_quirk(hdev, HCI_QUIRK_RESET_ON_CLOSE);
|
||||
|
|
|
|||
|
|
@ -547,6 +547,7 @@ static int bcm_close(struct hci_uart *hu)
|
|||
if (IS_ENABLED(CONFIG_PM) && bdev->irq_acquired) {
|
||||
devm_free_irq(bdev->dev, bdev->irq, bdev);
|
||||
device_init_wakeup(bdev->dev, false);
|
||||
pm_runtime_dont_use_autosuspend(bdev->dev);
|
||||
pm_runtime_disable(bdev->dev);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2490,6 +2490,7 @@ static const struct bcm4377_hw bcm4377_hw_variants[] = {
|
|||
.has_bar0_core2_window2 = true,
|
||||
.broken_mws_transport_config = true,
|
||||
.broken_le_coded = true,
|
||||
.broken_le_ext_adv_report_phy = true,
|
||||
.send_calibration = bcm4378_send_calibration,
|
||||
.send_ptb = bcm4378_send_ptb,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1023,8 +1023,10 @@ static void h5_btrtl_open(struct h5 *h5)
|
|||
|
||||
static void h5_btrtl_close(struct h5 *h5)
|
||||
{
|
||||
if (!test_bit(H5_WAKEUP_DISABLE, &h5->flags))
|
||||
if (!test_bit(H5_WAKEUP_DISABLE, &h5->flags)) {
|
||||
pm_runtime_dont_use_autosuspend(&h5->hu->serdev->dev);
|
||||
pm_runtime_disable(&h5->hu->serdev->dev);
|
||||
}
|
||||
|
||||
gpiod_set_value_cansleep(h5->device_wake_gpio, 0);
|
||||
gpiod_set_value_cansleep(h5->enable_gpio, 0);
|
||||
|
|
|
|||
|
|
@ -345,6 +345,7 @@ static int intel_set_power(struct hci_uart *hu, bool powered)
|
|||
devm_free_irq(&idev->pdev->dev, idev->irq, idev);
|
||||
device_wakeup_disable(&idev->pdev->dev);
|
||||
|
||||
pm_runtime_dont_use_autosuspend(&idev->pdev->dev);
|
||||
pm_runtime_disable(&idev->pdev->dev);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -457,7 +457,7 @@ static int hci_uart_setup(struct hci_dev *hdev)
|
|||
if (IS_ERR(skb)) {
|
||||
BT_ERR("%s: Reading local version information failed (%ld)",
|
||||
hdev->name, PTR_ERR(skb));
|
||||
return 0;
|
||||
return PTR_ERR(skb);
|
||||
}
|
||||
|
||||
if (skb->len != sizeof(*ver)) {
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ static int hci_uart_setup(struct hci_dev *hdev)
|
|||
if (IS_ERR(skb)) {
|
||||
bt_dev_err(hdev, "Reading local version info failed (%ld)",
|
||||
PTR_ERR(skb));
|
||||
return 0;
|
||||
return PTR_ERR(skb);
|
||||
}
|
||||
|
||||
if (skb->len != sizeof(*ver))
|
||||
|
|
|
|||
|
|
@ -935,9 +935,9 @@ static inline void hci_discovery_filter_clear(struct hci_dev *hdev)
|
|||
hdev->discovery.result_filtering = false;
|
||||
hdev->discovery.report_invalid_rssi = true;
|
||||
hdev->discovery.rssi = HCI_RSSI_INVALID;
|
||||
hdev->discovery.uuid_count = 0;
|
||||
|
||||
spin_lock(&hdev->discovery.lock);
|
||||
hdev->discovery.uuid_count = 0;
|
||||
kfree(hdev->discovery.uuids);
|
||||
hdev->discovery.uuids = NULL;
|
||||
spin_unlock(&hdev->discovery.lock);
|
||||
|
|
|
|||
|
|
@ -699,7 +699,12 @@ struct l2cap_rx_busy {
|
|||
|
||||
struct l2cap_pinfo {
|
||||
struct bt_sock bt;
|
||||
|
||||
/* With owning sk_socket chan may be read without lock, other access
|
||||
* should hold lock_sock.
|
||||
*/
|
||||
struct l2cap_chan *chan;
|
||||
|
||||
struct list_head rx_busy;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -369,6 +369,7 @@ u8 eir_create_scan_rsp(struct hci_dev *hdev, u8 instance, u8 *ptr)
|
|||
|
||||
void *eir_get_service_data(u8 *eir, size_t eir_len, u16 uuid, size_t *len)
|
||||
{
|
||||
const u8 *eir_end = eir + eir_len;
|
||||
size_t dlen;
|
||||
|
||||
while ((eir = eir_get_data(eir, eir_len, EIR_SERVICE_DATA, &dlen))) {
|
||||
|
|
@ -381,7 +382,7 @@ void *eir_get_service_data(u8 *eir, size_t eir_len, u16 uuid, size_t *len)
|
|||
}
|
||||
|
||||
eir += dlen;
|
||||
eir_len -= dlen;
|
||||
eir_len = eir_end - eir;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
|
|
|||
|
|
@ -1391,7 +1391,8 @@ static void hci_le_conn_failed(struct hci_conn *conn, u8 status)
|
|||
/* Enable advertising in case this was a failed connection
|
||||
* attempt as a peripheral.
|
||||
*/
|
||||
hci_enable_advertising(hdev);
|
||||
if (conn->role == HCI_ROLE_SLAVE)
|
||||
hci_enable_advertising(hdev);
|
||||
}
|
||||
|
||||
/* This function requires the caller holds hdev->lock */
|
||||
|
|
|
|||
|
|
@ -4093,7 +4093,7 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb)
|
|||
if (READ_ONCE(hdev->req_status) == HCI_REQ_PEND &&
|
||||
!hci_dev_test_and_set_flag(hdev, HCI_CMD_PENDING)) {
|
||||
kfree_skb(hdev->req_skb);
|
||||
hdev->req_skb = skb_clone(hdev->sent_cmd, GFP_KERNEL);
|
||||
hdev->req_skb = skb_get(hdev->sent_cmd);
|
||||
}
|
||||
|
||||
return err;
|
||||
|
|
|
|||
|
|
@ -5763,10 +5763,11 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status,
|
|||
hci_dev_lock(hdev);
|
||||
hci_store_wake_reason(hdev, bdaddr, bdaddr_type);
|
||||
|
||||
/* All controllers implicitly stop advertising in the event of a
|
||||
* connection, so ensure that the state bit is cleared.
|
||||
/* Advertising stops when a connection is created. On a failed
|
||||
* connection it keeps running, so leave the state bit alone.
|
||||
*/
|
||||
hci_dev_clear_flag(hdev, HCI_LE_ADV);
|
||||
if (!status)
|
||||
hci_dev_clear_flag(hdev, HCI_LE_ADV);
|
||||
|
||||
/* Check for existing connection:
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1287,6 +1287,7 @@ hci_set_ext_adv_params_sync(struct hci_dev *hdev, u8 instance,
|
|||
}
|
||||
|
||||
static int hci_set_ext_adv_data_sync(struct hci_dev *hdev, u8 instance)
|
||||
__context_unsafe(/* conditional locking */)
|
||||
{
|
||||
DEFINE_FLEX(struct hci_cp_le_set_ext_adv_data, pdu, data, length,
|
||||
HCI_MAX_EXT_AD_LENGTH);
|
||||
|
|
@ -1375,6 +1376,7 @@ int hci_update_adv_data_sync(struct hci_dev *hdev, u8 instance)
|
|||
}
|
||||
|
||||
int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance)
|
||||
__context_unsafe(/* conditional locking */)
|
||||
{
|
||||
struct hci_cp_le_set_ext_adv_params cp;
|
||||
struct hci_rp_le_set_ext_adv_params rp;
|
||||
|
|
@ -1535,6 +1537,7 @@ int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance)
|
|||
}
|
||||
|
||||
static int hci_set_ext_scan_rsp_data_sync(struct hci_dev *hdev, u8 instance)
|
||||
__context_unsafe(/* conditional locking */)
|
||||
{
|
||||
DEFINE_FLEX(struct hci_cp_le_set_ext_scan_rsp_data, pdu, data, length,
|
||||
HCI_MAX_EXT_AD_LENGTH);
|
||||
|
|
@ -1588,6 +1591,7 @@ static int hci_set_ext_scan_rsp_data_sync(struct hci_dev *hdev, u8 instance)
|
|||
}
|
||||
|
||||
static int __hci_set_scan_rsp_data_sync(struct hci_dev *hdev, u8 instance)
|
||||
__context_unsafe(/* conditional locking */)
|
||||
{
|
||||
struct hci_cp_le_set_scan_rsp_data cp;
|
||||
u8 len;
|
||||
|
|
@ -1729,6 +1733,7 @@ static int hci_set_per_adv_params_sync(struct hci_dev *hdev, u8 instance,
|
|||
}
|
||||
|
||||
static int hci_set_per_adv_data_sync(struct hci_dev *hdev, u8 instance)
|
||||
__context_unsafe(/* conditional locking */)
|
||||
{
|
||||
DEFINE_FLEX(struct hci_cp_le_set_per_adv_data, pdu, data, length,
|
||||
HCI_MAX_PER_AD_LENGTH);
|
||||
|
|
@ -5448,6 +5453,7 @@ int hci_dev_open_sync(struct hci_dev *hdev)
|
|||
if (hdev->req_skb) {
|
||||
kfree_skb(hdev->req_skb);
|
||||
hdev->req_skb = NULL;
|
||||
hci_dev_clear_flag(hdev, HCI_CMD_PENDING);
|
||||
}
|
||||
|
||||
clear_bit(HCI_RUNNING, &hdev->flags);
|
||||
|
|
@ -5632,6 +5638,7 @@ int hci_dev_close_sync(struct hci_dev *hdev)
|
|||
if (hdev->req_skb) {
|
||||
kfree_skb(hdev->req_skb);
|
||||
hdev->req_skb = NULL;
|
||||
hci_dev_clear_flag(hdev, HCI_CMD_PENDING);
|
||||
}
|
||||
|
||||
clear_bit(HCI_RUNNING, &hdev->flags);
|
||||
|
|
@ -7282,8 +7289,13 @@ static void create_le_conn_complete(struct hci_dev *hdev, void *data, int err)
|
|||
goto unlock;
|
||||
}
|
||||
|
||||
/* Check if connection is still pending */
|
||||
if (conn != hci_lookup_le_connect(hdev))
|
||||
/* Check if this connection is still pending.
|
||||
*
|
||||
* hci_lookup_le_connect() returns only the first LE connection
|
||||
* in BT_CONNECT, which is not necessarily this one when two are
|
||||
* pending at once, so ask the connection itself.
|
||||
*/
|
||||
if (conn->state != BT_CONNECT)
|
||||
goto unlock;
|
||||
|
||||
/* Flush to make sure we send create conn cancel command if needed */
|
||||
|
|
|
|||
|
|
@ -2277,6 +2277,14 @@ static void iso_conn_ready(struct iso_conn *conn)
|
|||
|
||||
lock_sock(parent);
|
||||
|
||||
/* The listener may have been closed concurrently. */
|
||||
if (parent->sk_state != BT_LISTEN ||
|
||||
sock_flag(parent, SOCK_ZAPPED)) {
|
||||
release_sock(parent);
|
||||
sock_put(parent);
|
||||
return;
|
||||
}
|
||||
|
||||
sk = iso_sock_alloc(sock_net(parent), NULL,
|
||||
BTPROTO_ISO, GFP_ATOMIC, 0);
|
||||
if (!sk) {
|
||||
|
|
|
|||
|
|
@ -436,11 +436,26 @@ static int l2cap_get_mode(struct l2cap_chan *chan)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct l2cap_conn *l2cap_chan_conn(struct l2cap_chan *chan)
|
||||
{
|
||||
lockdep_assert_held(&chan->lock);
|
||||
|
||||
/* l2cap_conn_del() sets FLAG_DEL while holding chan->lock before
|
||||
* conn->hcon is deleted. If not set and conn is non-NULL, conn->hcon
|
||||
* remains alive during this chan->lock critical section.
|
||||
*/
|
||||
if (test_bit(FLAG_DEL, &chan->flags))
|
||||
return NULL;
|
||||
|
||||
return chan->conn;
|
||||
}
|
||||
|
||||
static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
|
||||
sockopt_t *sopt)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct l2cap_chan *chan = l2cap_pi(sk)->chan;
|
||||
struct l2cap_conn *conn;
|
||||
struct l2cap_options opts;
|
||||
struct l2cap_conninfo cinfo;
|
||||
int err = 0;
|
||||
|
|
@ -451,6 +466,7 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
|
|||
|
||||
len = sopt->optlen;
|
||||
|
||||
l2cap_chan_lock(chan);
|
||||
lock_sock(sk);
|
||||
|
||||
switch (optname) {
|
||||
|
|
@ -537,9 +553,15 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
|
|||
break;
|
||||
}
|
||||
|
||||
conn = l2cap_chan_conn(chan);
|
||||
if (!conn) {
|
||||
err = -ENOTCONN;
|
||||
break;
|
||||
}
|
||||
|
||||
memset(&cinfo, 0, sizeof(cinfo));
|
||||
cinfo.hci_handle = chan->conn->hcon->handle;
|
||||
memcpy(cinfo.dev_class, chan->conn->hcon->dev_class, 3);
|
||||
cinfo.hci_handle = conn->hcon->handle;
|
||||
memcpy(cinfo.dev_class, conn->hcon->dev_class, 3);
|
||||
|
||||
len = min(len, sizeof(cinfo));
|
||||
if (copy_to_iter(&cinfo, len, &sopt->iter_out) != len)
|
||||
|
|
@ -553,6 +575,8 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
|
|||
}
|
||||
|
||||
release_sock(sk);
|
||||
l2cap_chan_unlock(chan);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -561,6 +585,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
|
|||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct l2cap_chan *chan = l2cap_pi(sk)->chan;
|
||||
struct l2cap_conn *conn;
|
||||
struct bt_security sec;
|
||||
struct bt_power pwr;
|
||||
int len, mode, err = 0;
|
||||
|
|
@ -578,6 +603,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
|
|||
|
||||
len = sopt->optlen;
|
||||
|
||||
l2cap_chan_lock(chan);
|
||||
lock_sock(sk);
|
||||
|
||||
switch (optname) {
|
||||
|
|
@ -589,12 +615,14 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
|
|||
break;
|
||||
}
|
||||
|
||||
conn = l2cap_chan_conn(chan);
|
||||
|
||||
memset(&sec, 0, sizeof(sec));
|
||||
if (chan->conn) {
|
||||
sec.level = chan->conn->hcon->sec_level;
|
||||
if (conn) {
|
||||
sec.level = conn->hcon->sec_level;
|
||||
|
||||
if (sk->sk_state == BT_CONNECTED)
|
||||
sec.key_size = chan->conn->hcon->enc_key_size;
|
||||
sec.key_size = conn->hcon->enc_key_size;
|
||||
} else {
|
||||
sec.level = chan->sec_level;
|
||||
}
|
||||
|
|
@ -678,12 +706,14 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
|
|||
break;
|
||||
|
||||
case BT_PHY:
|
||||
if (sk->sk_state != BT_CONNECTED) {
|
||||
conn = l2cap_chan_conn(chan);
|
||||
|
||||
if (sk->sk_state != BT_CONNECTED || !conn) {
|
||||
err = -ENOTCONN;
|
||||
break;
|
||||
}
|
||||
|
||||
opt = hci_conn_get_phy(chan->conn->hcon);
|
||||
opt = hci_conn_get_phy(conn->hcon);
|
||||
|
||||
if (copy_to_iter(&opt, sizeof(opt), &sopt->iter_out) !=
|
||||
sizeof(opt))
|
||||
|
|
@ -719,6 +749,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname,
|
|||
}
|
||||
|
||||
release_sock(sk);
|
||||
l2cap_chan_unlock(chan);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -749,6 +780,7 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
|
|||
|
||||
BT_DBG("sk %p", sk);
|
||||
|
||||
l2cap_chan_lock(chan);
|
||||
lock_sock(sk);
|
||||
|
||||
switch (optname) {
|
||||
|
|
@ -850,6 +882,7 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
|
|||
}
|
||||
|
||||
release_sock(sk);
|
||||
l2cap_chan_unlock(chan);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -913,6 +946,7 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
|
|||
if (level != SOL_BLUETOOTH)
|
||||
return -ENOPROTOOPT;
|
||||
|
||||
l2cap_chan_lock(chan);
|
||||
lock_sock(sk);
|
||||
|
||||
switch (optname) {
|
||||
|
|
@ -938,11 +972,10 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
|
|||
|
||||
chan->sec_level = sec.level;
|
||||
|
||||
if (!chan->conn)
|
||||
conn = l2cap_chan_conn(chan);
|
||||
if (!conn)
|
||||
break;
|
||||
|
||||
conn = chan->conn;
|
||||
|
||||
/* change security for LE channels */
|
||||
if (chan->scid == L2CAP_CID_ATT) {
|
||||
if (smp_conn_security(conn->hcon, sec.level)) {
|
||||
|
|
@ -997,7 +1030,8 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
|
|||
}
|
||||
|
||||
if (opt == BT_FLUSHABLE_OFF) {
|
||||
conn = chan->conn;
|
||||
conn = l2cap_chan_conn(chan);
|
||||
|
||||
/* proceed further only when we have l2cap_conn and
|
||||
No Flush support in the LM */
|
||||
if (!conn || !lmp_no_flush_capable(conn->hcon->hdev)) {
|
||||
|
|
@ -1083,7 +1117,8 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
|
|||
break;
|
||||
|
||||
case BT_PHY:
|
||||
if (sk->sk_state != BT_CONNECTED) {
|
||||
conn = l2cap_chan_conn(chan);
|
||||
if (sk->sk_state != BT_CONNECTED || !conn) {
|
||||
err = -ENOTCONN;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1093,10 +1128,6 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
|
|||
if (err)
|
||||
break;
|
||||
|
||||
if (!chan->conn)
|
||||
break;
|
||||
|
||||
conn = chan->conn;
|
||||
err = hci_conn_set_phy(conn->hcon, phys);
|
||||
break;
|
||||
|
||||
|
|
@ -1139,6 +1170,7 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
|
|||
}
|
||||
|
||||
release_sock(sk);
|
||||
l2cap_chan_unlock(chan);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -1312,7 +1344,12 @@ static void l2cap_sock_kill(struct sock *sk)
|
|||
|
||||
BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state));
|
||||
|
||||
/* Take lock to synchronize against access without owning sk->sk_socket,
|
||||
* eg. in l2cap_sock_cleanup_listen(). proto_ops etc. don't need lock.
|
||||
*/
|
||||
lock_sock(sk);
|
||||
l2cap_sock_put_chan(sk);
|
||||
release_sock(sk);
|
||||
|
||||
/* Kill poor orphan */
|
||||
sock_set_flag(sk, SOCK_DEAD);
|
||||
|
|
@ -1516,14 +1553,10 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
|
|||
* establish sk_lock -> conn->lock and invert the established
|
||||
* conn->lock -> chan->lock -> sk_lock order (lockdep deadlock).
|
||||
*
|
||||
* Instead, briefly take the child sk lock to fetch and pin its chan.
|
||||
* l2cap_conn_del() reaches the chan free only via
|
||||
* l2cap_chan_del() -> l2cap_sock_teardown_cb(), which itself takes
|
||||
* the child sk lock; holding it across l2cap_chan_hold_unless_zero()
|
||||
* therefore guarantees the chan cannot be freed while we read and
|
||||
* pin it (hold_unless_zero() additionally skips a chan already past
|
||||
* its last reference). We then drop the sk lock before taking
|
||||
* chan->lock, so sk and chan locks are never held together.
|
||||
* Instead, briefly take the child sk lock to synchronize vs.
|
||||
* l2cap_sock_kill that puts l2cap_pi(sk)->chan. We then drop the sk
|
||||
* lock before taking chan->lock, so sk and chan locks are never held
|
||||
* together.
|
||||
*
|
||||
* Since we cannot call l2cap_chan_close() without conn->lock,
|
||||
* schedule l2cap_chan_timeout to close the channel; it already
|
||||
|
|
@ -1533,10 +1566,12 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
|
|||
struct l2cap_chan *chan;
|
||||
|
||||
lock_sock_nested(sk, L2CAP_NESTING_NORMAL);
|
||||
chan = l2cap_chan_hold_unless_zero(l2cap_pi(sk)->chan);
|
||||
chan = l2cap_pi(sk)->chan;
|
||||
if (chan)
|
||||
l2cap_chan_hold(chan);
|
||||
release_sock(sk);
|
||||
if (!chan) {
|
||||
/* l2cap_conn_del() already tearing this child down */
|
||||
/* Already torn down */
|
||||
sock_put(sk);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1568,6 +1603,11 @@ static int l2cap_sock_new_connection_cb(struct l2cap_chan *chan,
|
|||
|
||||
lock_sock(parent);
|
||||
|
||||
if (parent->sk_state != BT_LISTEN) {
|
||||
release_sock(parent);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Check for backlog size */
|
||||
if (sk_acceptq_is_full(parent)) {
|
||||
BT_DBG("backlog full %d", parent->sk_ack_backlog);
|
||||
|
|
@ -1731,10 +1771,14 @@ static void l2cap_sock_state_change_cb(struct l2cap_chan *chan, int state,
|
|||
if (!sk)
|
||||
return;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
sk->sk_state = state;
|
||||
|
||||
if (err)
|
||||
sk->sk_err = err;
|
||||
|
||||
release_sock(sk);
|
||||
}
|
||||
|
||||
static struct sk_buff *l2cap_sock_alloc_skb_cb(struct l2cap_chan *chan,
|
||||
|
|
@ -1810,6 +1854,8 @@ static void l2cap_sock_resume_cb(struct l2cap_chan *chan)
|
|||
if (!sk)
|
||||
return;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
if (test_and_clear_bit(FLAG_PENDING_SECURITY, &chan->flags)) {
|
||||
sk->sk_state = BT_CONNECTED;
|
||||
chan->state = BT_CONNECTED;
|
||||
|
|
@ -1817,6 +1863,8 @@ static void l2cap_sock_resume_cb(struct l2cap_chan *chan)
|
|||
|
||||
clear_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags);
|
||||
sk->sk_state_change(sk);
|
||||
|
||||
release_sock(sk);
|
||||
}
|
||||
|
||||
static void l2cap_sock_set_shutdown_cb(struct l2cap_chan *chan)
|
||||
|
|
|
|||
|
|
@ -6171,6 +6171,7 @@ static int start_service_discovery(struct sock *sk, struct hci_dev *hdev,
|
|||
struct mgmt_pending_cmd *cmd;
|
||||
const u16 max_uuid_count = ((U16_MAX - sizeof(*cp)) / 16);
|
||||
u16 uuid_count, expected_len;
|
||||
u8 (*uuids)[16] = NULL;
|
||||
u8 status;
|
||||
int err;
|
||||
|
||||
|
|
@ -6247,12 +6248,10 @@ static int start_service_discovery(struct sock *sk, struct hci_dev *hdev,
|
|||
hdev->discovery.result_filtering = true;
|
||||
hdev->discovery.type = cp->type;
|
||||
hdev->discovery.rssi = cp->rssi;
|
||||
hdev->discovery.uuid_count = uuid_count;
|
||||
|
||||
if (uuid_count > 0) {
|
||||
hdev->discovery.uuids = kmemdup(cp->uuids, uuid_count * 16,
|
||||
GFP_KERNEL);
|
||||
if (!hdev->discovery.uuids) {
|
||||
uuids = kmemdup(cp->uuids, uuid_count * sizeof(*uuids), GFP_KERNEL);
|
||||
if (!uuids) {
|
||||
err = mgmt_cmd_complete(sk, hdev->id,
|
||||
MGMT_OP_START_SERVICE_DISCOVERY,
|
||||
MGMT_STATUS_FAILED,
|
||||
|
|
@ -6262,6 +6261,11 @@ static int start_service_discovery(struct sock *sk, struct hci_dev *hdev,
|
|||
}
|
||||
}
|
||||
|
||||
spin_lock(&hdev->discovery.lock);
|
||||
hdev->discovery.uuids = uuids;
|
||||
hdev->discovery.uuid_count = uuid_count;
|
||||
spin_unlock(&hdev->discovery.lock);
|
||||
|
||||
err = hci_cmd_sync_queue(hdev, start_discovery_sync, cmd,
|
||||
start_discovery_complete);
|
||||
if (err < 0) {
|
||||
|
|
@ -10505,6 +10509,7 @@ static bool is_filter_match(struct hci_dev *hdev, s8 rssi, u8 *eir,
|
|||
!hci_test_quirk(hdev, HCI_QUIRK_STRICT_DUPLICATE_FILTER))))
|
||||
return false;
|
||||
|
||||
spin_lock(&hdev->discovery.lock);
|
||||
if (hdev->discovery.uuid_count != 0) {
|
||||
/* If a list of UUIDs is provided in filter, results with no
|
||||
* matching UUID should be dropped.
|
||||
|
|
@ -10513,9 +10518,12 @@ static bool is_filter_match(struct hci_dev *hdev, s8 rssi, u8 *eir,
|
|||
hdev->discovery.uuids) &&
|
||||
!eir_has_uuids(scan_rsp, scan_rsp_len,
|
||||
hdev->discovery.uuid_count,
|
||||
hdev->discovery.uuids))
|
||||
hdev->discovery.uuids)) {
|
||||
spin_unlock(&hdev->discovery.lock);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
spin_unlock(&hdev->discovery.lock);
|
||||
|
||||
/* If duplicate filtering does not report RSSI changes, then restart
|
||||
* scanning to ensure updated result with updated RSSI values.
|
||||
|
|
|
|||
|
|
@ -1455,6 +1455,10 @@ static int rfcomm_apply_pn(struct rfcomm_dlc *d, int cr, struct rfcomm_pn *pn)
|
|||
|
||||
d->mtu = __le16_to_cpu(pn->mtu);
|
||||
|
||||
/* MTU 0 causes an infinite loop when fragmenting in sendmsg */
|
||||
if (!d->mtu)
|
||||
d->mtu = RFCOMM_DEFAULT_MTU;
|
||||
|
||||
if (cr && d->mtu > s->mtu)
|
||||
d->mtu = s->mtu;
|
||||
|
||||
|
|
@ -2178,8 +2182,10 @@ static void rfcomm_kill_listener(void)
|
|||
|
||||
BT_DBG("");
|
||||
|
||||
rfcomm_lock();
|
||||
list_for_each_entry_safe(s, n, &session_list, list)
|
||||
rfcomm_session_del(s);
|
||||
rfcomm_unlock();
|
||||
}
|
||||
|
||||
static int rfcomm_run(void *unused)
|
||||
|
|
@ -2213,9 +2219,13 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt)
|
|||
|
||||
BT_DBG("conn %p status 0x%02x encrypt 0x%02x", conn, status, encrypt);
|
||||
|
||||
rfcomm_lock();
|
||||
|
||||
s = rfcomm_session_get(&conn->hdev->bdaddr, &conn->dst);
|
||||
if (!s)
|
||||
if (!s) {
|
||||
rfcomm_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
list_for_each_entry_safe(d, n, &s->dlcs, list) {
|
||||
if (test_and_clear_bit(RFCOMM_SEC_PENDING, &d->flags)) {
|
||||
|
|
@ -2247,6 +2257,8 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt)
|
|||
set_bit(RFCOMM_AUTH_REJECT, &d->flags);
|
||||
}
|
||||
|
||||
rfcomm_unlock();
|
||||
|
||||
rfcomm_schedule();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user