From 01b1cb87d37fb19cdaa5e7002416fdde156873d0 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Mon, 16 Nov 2015 12:52:21 +0200 Subject: Bluetooth: Run page scan updates through hdev->req_workqueue Since Add/Remove Device perform the page scan updates independently from the HCI command completion we've introduced a potential race when multiple mgmt commands are queued. Doing the page scan updates through the req_workqueue ensures that the state changes are performed in a race-free manner. At the same time, to make the request helper more widely usable, extend it to also cover Inquiry Scan changes since those are behind the same HCI command. This is also reflected in the new name of the API as well as the work struct name. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 55ce209..eda809a 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -329,6 +329,7 @@ struct hci_dev { struct work_struct discov_update; struct work_struct bg_scan_update; + struct work_struct scan_update; struct delayed_work le_scan_disable; struct delayed_work le_scan_restart; diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index d57c11c..703e37f 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -2176,7 +2176,7 @@ static void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) hci_send_cmd(hdev, HCI_OP_READ_REMOTE_FEATURES, sizeof(cp), &cp); - hci_update_page_scan(hdev); + hci_req_update_scan(hdev); } /* Set packet type for incoming connection */ @@ -2362,7 +2362,7 @@ static void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) if (test_bit(HCI_CONN_FLUSH_KEY, &conn->flags)) hci_remove_link_key(hdev, &conn->dst); - hci_update_page_scan(hdev); + hci_req_update_scan(hdev); } params = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type); diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index e639671..78c026b 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -637,7 +637,7 @@ static bool disconnected_whitelist_entries(struct hci_dev *hdev) return false; } -void __hci_update_page_scan(struct hci_request *req) +void __hci_req_update_scan(struct hci_request *req) { struct hci_dev *hdev = req->hdev; u8 scan; @@ -657,22 +657,29 @@ void __hci_update_page_scan(struct hci_request *req) else scan = SCAN_DISABLED; - if (test_bit(HCI_PSCAN, &hdev->flags) == !!(scan & SCAN_PAGE)) - return; - if (hci_dev_test_flag(hdev, HCI_DISCOVERABLE)) scan |= SCAN_INQUIRY; + if (test_bit(HCI_PSCAN, &hdev->flags) == !!(scan & SCAN_PAGE) && + test_bit(HCI_ISCAN, &hdev->flags) == !!(scan & SCAN_INQUIRY)) + return; + hci_req_add(req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan); } -void hci_update_page_scan(struct hci_dev *hdev) +static int update_scan(struct hci_request *req, unsigned long opt) { - struct hci_request req; + hci_dev_lock(req->hdev); + __hci_req_update_scan(req); + hci_dev_unlock(req->hdev); + return 0; +} - hci_req_init(&req, hdev); - __hci_update_page_scan(&req); - hci_req_run(&req, NULL); +static void scan_update_work(struct work_struct *work) +{ + struct hci_dev *hdev = container_of(work, struct hci_dev, scan_update); + + hci_req_sync(hdev, update_scan, 0, HCI_CMD_TIMEOUT, NULL); } /* This function controls the background scanning based on hdev->pend_le_conns @@ -1270,6 +1277,7 @@ void hci_request_setup(struct hci_dev *hdev) { INIT_WORK(&hdev->discov_update, discov_update); INIT_WORK(&hdev->bg_scan_update, bg_scan_update); + INIT_WORK(&hdev->scan_update, scan_update_work); INIT_DELAYED_WORK(&hdev->le_scan_disable, le_scan_disable_work); INIT_DELAYED_WORK(&hdev->le_scan_restart, le_scan_restart_work); } @@ -1280,6 +1288,7 @@ void hci_request_cancel_all(struct hci_dev *hdev) cancel_work_sync(&hdev->discov_update); cancel_work_sync(&hdev->bg_scan_update); + cancel_work_sync(&hdev->scan_update); cancel_delayed_work_sync(&hdev->le_scan_disable); cancel_delayed_work_sync(&hdev->le_scan_restart); } diff --git a/net/bluetooth/hci_request.h b/net/bluetooth/hci_request.h index 6b9e59f..cc82755 100644 --- a/net/bluetooth/hci_request.h +++ b/net/bluetooth/hci_request.h @@ -61,8 +61,12 @@ void hci_req_add_le_passive_scan(struct hci_request *req); /* Returns true if HCI commands were queued */ bool hci_req_stop_discovery(struct hci_request *req); -void hci_update_page_scan(struct hci_dev *hdev); -void __hci_update_page_scan(struct hci_request *req); +static inline void hci_req_update_scan(struct hci_dev *hdev) +{ + queue_work(hdev->req_workqueue, &hdev->scan_update); +} + +void __hci_req_update_scan(struct hci_request *req); int hci_update_random_address(struct hci_request *req, bool require_privacy, u8 *own_addr_type); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 3d9d2e4..0d20e13 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1810,7 +1810,7 @@ static void set_discoverable_complete(struct hci_dev *hdev, u8 status, * entries. */ hci_req_init(&req, hdev); - __hci_update_page_scan(&req); + __hci_req_update_scan(&req); update_class(&req); hci_req_run(&req, NULL); @@ -2058,7 +2058,7 @@ static void set_connectable_complete(struct hci_dev *hdev, u8 status, if (conn_changed || discov_changed) { new_settings(hdev, cmd->sk); - hci_update_page_scan(hdev); + hci_req_update_scan(hdev); if (discov_changed) mgmt_update_adv_data(hdev); hci_update_background_scan(hdev); @@ -2092,7 +2092,7 @@ static int set_connectable_update_settings(struct hci_dev *hdev, return err; if (changed) { - hci_update_page_scan(hdev); + hci_req_update_scan(hdev); hci_update_background_scan(hdev); return new_settings(hdev, sk); } @@ -5041,7 +5041,7 @@ static int set_bredr(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) hci_req_init(&req, hdev); write_fast_connectable(&req, false); - __hci_update_page_scan(&req); + __hci_req_update_scan(&req); /* Since only the advertising data flags will change, there * is no need to update the scan response data. @@ -5927,7 +5927,7 @@ static int add_device(struct sock *sk, struct hci_dev *hdev, if (err) goto unlock; - hci_update_page_scan(hdev); + hci_req_update_scan(hdev); goto added; } @@ -6024,7 +6024,7 @@ static int remove_device(struct sock *sk, struct hci_dev *hdev, goto unlock; } - hci_update_page_scan(hdev); + hci_req_update_scan(hdev); device_removed(sk, hdev, &cp->addr.bdaddr, cp->addr.type); @@ -6089,7 +6089,7 @@ static int remove_device(struct sock *sk, struct hci_dev *hdev, kfree(b); } - hci_update_page_scan(hdev); + hci_req_update_scan(hdev); list_for_each_entry_safe(p, tmp, &hdev->le_conn_params, list) { if (p->auto_connect == HCI_AUTO_CONN_DISABLED) @@ -7397,7 +7397,7 @@ static int powered_update_hci(struct hci_dev *hdev) write_fast_connectable(&req, true); else write_fast_connectable(&req, false); - __hci_update_page_scan(&req); + __hci_req_update_scan(&req); update_class(&req); update_name(&req); update_eir(&req); -- cgit v0.10.2 From 196a5e97d13092f783e41001c1112d7f31518ea2 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Sun, 22 Nov 2015 18:55:44 +0200 Subject: Bluetooth: Move __hci_update_background_scan up in hci_request.c This way we avoid the need to do a forward declaration in later patches. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index 78c026b..7c85435 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -346,6 +346,79 @@ void hci_req_add(struct hci_request *req, u16 opcode, u32 plen, hci_req_add_ev(req, opcode, plen, param, 0); } +/* This function controls the background scanning based on hdev->pend_le_conns + * list. If there are pending LE connection we start the background scanning, + * otherwise we stop it. + * + * This function requires the caller holds hdev->lock. + */ +static void __hci_update_background_scan(struct hci_request *req) +{ + struct hci_dev *hdev = req->hdev; + + if (!test_bit(HCI_UP, &hdev->flags) || + test_bit(HCI_INIT, &hdev->flags) || + hci_dev_test_flag(hdev, HCI_SETUP) || + hci_dev_test_flag(hdev, HCI_CONFIG) || + hci_dev_test_flag(hdev, HCI_AUTO_OFF) || + hci_dev_test_flag(hdev, HCI_UNREGISTER)) + return; + + /* No point in doing scanning if LE support hasn't been enabled */ + if (!hci_dev_test_flag(hdev, HCI_LE_ENABLED)) + return; + + /* If discovery is active don't interfere with it */ + if (hdev->discovery.state != DISCOVERY_STOPPED) + return; + + /* Reset RSSI and UUID filters when starting background scanning + * since these filters are meant for service discovery only. + * + * The Start Discovery and Start Service Discovery operations + * ensure to set proper values for RSSI threshold and UUID + * filter list. So it is safe to just reset them here. + */ + hci_discovery_filter_clear(hdev); + + if (list_empty(&hdev->pend_le_conns) && + list_empty(&hdev->pend_le_reports)) { + /* If there is no pending LE connections or devices + * to be scanned for, we should stop the background + * scanning. + */ + + /* If controller is not scanning we are done. */ + if (!hci_dev_test_flag(hdev, HCI_LE_SCAN)) + return; + + hci_req_add_le_scan_disable(req); + + BT_DBG("%s stopping background scanning", hdev->name); + } else { + /* If there is at least one pending LE connection, we should + * keep the background scan running. + */ + + /* If controller is connecting, we should not start scanning + * since some controllers are not able to scan and connect at + * the same time. + */ + if (hci_lookup_le_connect(hdev)) + return; + + /* If controller is currently scanning, we stop it to ensure we + * don't miss any advertising (due to duplicates filter). + */ + if (hci_dev_test_flag(hdev, HCI_LE_SCAN)) + hci_req_add_le_scan_disable(req); + + hci_req_add_le_passive_scan(req); + + BT_DBG("%s starting background scanning", hdev->name); + } +} + void hci_req_add_le_scan_disable(struct hci_request *req) { struct hci_cp_le_set_scan_enable cp; @@ -682,79 +755,6 @@ static void scan_update_work(struct work_struct *work) hci_req_sync(hdev, update_scan, 0, HCI_CMD_TIMEOUT, NULL); } -/* This function controls the background scanning based on hdev->pend_le_conns - * list. If there are pending LE connection we start the background scanning, - * otherwise we stop it. - * - * This function requires the caller holds hdev->lock. - */ -static void __hci_update_background_scan(struct hci_request *req) -{ - struct hci_dev *hdev = req->hdev; - - if (!test_bit(HCI_UP, &hdev->flags) || - test_bit(HCI_INIT, &hdev->flags) || - hci_dev_test_flag(hdev, HCI_SETUP) || - hci_dev_test_flag(hdev, HCI_CONFIG) || - hci_dev_test_flag(hdev, HCI_AUTO_OFF) || - hci_dev_test_flag(hdev, HCI_UNREGISTER)) - return; - - /* No point in doing scanning if LE support hasn't been enabled */ - if (!hci_dev_test_flag(hdev, HCI_LE_ENABLED)) - return; - - /* If discovery is active don't interfere with it */ - if (hdev->discovery.state != DISCOVERY_STOPPED) - return; - - /* Reset RSSI and UUID filters when starting background scanning - * since these filters are meant for service discovery only. - * - * The Start Discovery and Start Service Discovery operations - * ensure to set proper values for RSSI threshold and UUID - * filter list. So it is safe to just reset them here. - */ - hci_discovery_filter_clear(hdev); - - if (list_empty(&hdev->pend_le_conns) && - list_empty(&hdev->pend_le_reports)) { - /* If there is no pending LE connections or devices - * to be scanned for, we should stop the background - * scanning. - */ - - /* If controller is not scanning we are done. */ - if (!hci_dev_test_flag(hdev, HCI_LE_SCAN)) - return; - - hci_req_add_le_scan_disable(req); - - BT_DBG("%s stopping background scanning", hdev->name); - } else { - /* If there is at least one pending LE connection, we should - * keep the background scan running. - */ - - /* If controller is connecting, we should not start scanning - * since some controllers are not able to scan and connect at - * the same time. - */ - if (hci_lookup_le_connect(hdev)) - return; - - /* If controller is currently scanning, we stop it to ensure we - * don't miss any advertising (due to duplicates filter). - */ - if (hci_dev_test_flag(hdev, HCI_LE_SCAN)) - hci_req_add_le_scan_disable(req); - - hci_req_add_le_passive_scan(req); - - BT_DBG("%s starting background scanning", hdev->name); - } -} - void __hci_abort_conn(struct hci_request *req, struct hci_conn *conn, u8 reason) { -- cgit v0.10.2 From f22525700b2ae34eb97a29a91e2eee902062b484 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Wed, 18 Nov 2015 12:49:20 +0200 Subject: Bluetooth: Move advertising instance management to hci_request.c This paves the way for eventually performing advertising changes through the hdev->req_workqueue. Some new APIs need to be exposed from mgmt.c to hci_request.c and vice-versa, but many of them will go away once hdev->req_workqueue gets used. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index eda809a..b56085b 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1434,9 +1434,7 @@ void mgmt_index_added(struct hci_dev *hdev); void mgmt_index_removed(struct hci_dev *hdev); void mgmt_set_powered_failed(struct hci_dev *hdev, int err); int mgmt_powered(struct hci_dev *hdev, u8 powered); -int mgmt_update_adv_data(struct hci_dev *hdev); void mgmt_discoverable_timeout(struct hci_dev *hdev); -void mgmt_adv_timeout_expired(struct hci_dev *hdev); void mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key, bool persistent); void mgmt_device_connected(struct hci_dev *hdev, struct hci_conn *conn, @@ -1491,8 +1489,13 @@ void mgmt_new_csrk(struct hci_dev *hdev, struct smp_csrk *csrk, void mgmt_new_conn_param(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 bdaddr_type, u8 store_hint, u16 min_interval, u16 max_interval, u16 latency, u16 timeout); -void mgmt_reenable_advertising(struct hci_dev *hdev); void mgmt_smp_complete(struct hci_conn *conn, bool complete); +bool mgmt_get_connectable(struct hci_dev *hdev); +u8 mgmt_get_adv_discov_flags(struct hci_dev *hdev); +void mgmt_advertising_added(struct sock *sk, struct hci_dev *hdev, + u8 instance); +void mgmt_advertising_removed(struct sock *sk, struct hci_dev *hdev, + u8 instance); u8 hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency, u16 to_multiplier); diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 2d334e0..e260021 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -683,7 +683,7 @@ void hci_le_conn_failed(struct hci_conn *conn, u8 status) /* Re-enable advertising in case this was a failed connection * attempt as a peripheral. */ - mgmt_reenable_advertising(hdev); + hci_req_reenable_advertising(hdev); } static void create_le_conn_complete(struct hci_dev *hdev, u8 status, u16 opcode) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 89af7e4..bab8958 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -1549,11 +1549,6 @@ int hci_dev_do_close(struct hci_dev *hdev) if (hci_dev_test_flag(hdev, HCI_MGMT)) cancel_delayed_work_sync(&hdev->rpa_expired); - if (hdev->adv_instance_timeout) { - cancel_delayed_work_sync(&hdev->adv_instance_expire); - hdev->adv_instance_timeout = 0; - } - /* Avoid potential lockdep warnings from the *_flush() calls by * ensuring the workqueue is empty up front. */ @@ -1774,7 +1769,7 @@ static void hci_update_scan_state(struct hci_dev *hdev, u8 scan) hci_dev_set_flag(hdev, HCI_BREDR_ENABLED); if (hci_dev_test_flag(hdev, HCI_LE_ENABLED)) - mgmt_update_adv_data(hdev); + hci_req_update_adv_data(hdev, HCI_ADV_CURRENT); mgmt_new_settings(hdev); } @@ -2112,17 +2107,6 @@ static void hci_discov_off(struct work_struct *work) mgmt_discoverable_timeout(hdev); } -static void hci_adv_timeout_expire(struct work_struct *work) -{ - struct hci_dev *hdev; - - hdev = container_of(work, struct hci_dev, adv_instance_expire.work); - - BT_DBG("%s", hdev->name); - - mgmt_adv_timeout_expired(hdev); -} - void hci_uuids_clear(struct hci_dev *hdev) { struct bt_uuid *uuid, *tmp; @@ -3003,7 +2987,6 @@ struct hci_dev *hci_alloc_dev(void) INIT_DELAYED_WORK(&hdev->power_off, hci_power_off); INIT_DELAYED_WORK(&hdev->discov_off, hci_discov_off); - INIT_DELAYED_WORK(&hdev->adv_instance_expire, hci_adv_timeout_expire); skb_queue_head_init(&hdev->rx_q); skb_queue_head_init(&hdev->cmd_q); diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 703e37f..7554da5 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -1183,7 +1183,7 @@ static void hci_cc_le_set_scan_enable(struct hci_dev *hdev, hci_discovery_set_state(hdev, DISCOVERY_STOPPED); else if (!hci_dev_test_flag(hdev, HCI_LE_ADV) && hdev->discovery.state == DISCOVERY_FINDING) - mgmt_reenable_advertising(hdev); + hci_req_reenable_advertising(hdev); break; @@ -2401,7 +2401,7 @@ static void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) * is timed out due to Directed Advertising." */ if (type == LE_LINK) - mgmt_reenable_advertising(hdev); + hci_req_reenable_advertising(hdev); unlock: hci_dev_unlock(hdev); diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index 7c85435..e6622bd1 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -23,6 +23,7 @@ #include #include +#include #include "smp.h" #include "hci_request.h" @@ -580,6 +581,524 @@ void hci_req_add_le_passive_scan(struct hci_request *req) &enable_cp); } +static u8 get_current_adv_instance(struct hci_dev *hdev) +{ + /* The "Set Advertising" setting supersedes the "Add Advertising" + * setting. Here we set the advertising data based on which + * setting was set. When neither apply, default to the global settings, + * represented by instance "0". + */ + if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) && + !hci_dev_test_flag(hdev, HCI_ADVERTISING)) + return hdev->cur_adv_instance; + + return 0x00; +} + +static u8 get_cur_adv_instance_scan_rsp_len(struct hci_dev *hdev) +{ + u8 instance = get_current_adv_instance(hdev); + struct adv_info *adv_instance; + + /* Ignore instance 0 */ + if (instance == 0x00) + return 0; + + adv_instance = hci_find_adv_instance(hdev, instance); + if (!adv_instance) + return 0; + + /* TODO: Take into account the "appearance" and "local-name" flags here. + * These are currently being ignored as they are not supported. + */ + return adv_instance->scan_rsp_len; +} + +void __hci_req_disable_advertising(struct hci_request *req) +{ + u8 enable = 0x00; + + hci_req_add(req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(enable), &enable); +} + +static u32 get_adv_instance_flags(struct hci_dev *hdev, u8 instance) +{ + u32 flags; + struct adv_info *adv_instance; + + if (instance == 0x00) { + /* Instance 0 always manages the "Tx Power" and "Flags" + * fields + */ + flags = MGMT_ADV_FLAG_TX_POWER | MGMT_ADV_FLAG_MANAGED_FLAGS; + + /* For instance 0, the HCI_ADVERTISING_CONNECTABLE setting + * corresponds to the "connectable" instance flag. + */ + if (hci_dev_test_flag(hdev, HCI_ADVERTISING_CONNECTABLE)) + flags |= MGMT_ADV_FLAG_CONNECTABLE; + + return flags; + } + + adv_instance = hci_find_adv_instance(hdev, instance); + + /* Return 0 when we got an invalid instance identifier. */ + if (!adv_instance) + return 0; + + return adv_instance->flags; +} + +void __hci_req_enable_advertising(struct hci_request *req) +{ + struct hci_dev *hdev = req->hdev; + struct hci_cp_le_set_adv_param cp; + u8 own_addr_type, enable = 0x01; + bool connectable; + u8 instance; + u32 flags; + + if (hci_conn_num(hdev, LE_LINK) > 0) + return; + + if (hci_dev_test_flag(hdev, HCI_LE_ADV)) + __hci_req_disable_advertising(req); + + /* Clear the HCI_LE_ADV bit temporarily so that the + * hci_update_random_address knows that it's safe to go ahead + * and write a new random address. The flag will be set back on + * as soon as the SET_ADV_ENABLE HCI command completes. + */ + hci_dev_clear_flag(hdev, HCI_LE_ADV); + + instance = get_current_adv_instance(hdev); + flags = get_adv_instance_flags(hdev, instance); + + /* If the "connectable" instance flag was not set, then choose between + * ADV_IND and ADV_NONCONN_IND based on the global connectable setting. + */ + connectable = (flags & MGMT_ADV_FLAG_CONNECTABLE) || + mgmt_get_connectable(hdev); + + /* Set require_privacy to true only when non-connectable + * advertising is used. In that case it is fine to use a + * non-resolvable private address. + */ + if (hci_update_random_address(req, !connectable, &own_addr_type) < 0) + return; + + memset(&cp, 0, sizeof(cp)); + cp.min_interval = cpu_to_le16(hdev->le_adv_min_interval); + cp.max_interval = cpu_to_le16(hdev->le_adv_max_interval); + + if (connectable) + cp.type = LE_ADV_IND; + else if (get_cur_adv_instance_scan_rsp_len(hdev)) + cp.type = LE_ADV_SCAN_IND; + else + cp.type = LE_ADV_NONCONN_IND; + + cp.own_address_type = own_addr_type; + cp.channel_map = hdev->le_adv_channel_map; + + hci_req_add(req, HCI_OP_LE_SET_ADV_PARAM, sizeof(cp), &cp); + + hci_req_add(req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(enable), &enable); +} + +static u8 create_default_scan_rsp_data(struct hci_dev *hdev, u8 *ptr) +{ + u8 ad_len = 0; + size_t name_len; + + name_len = strlen(hdev->dev_name); + if (name_len > 0) { + size_t max_len = HCI_MAX_AD_LENGTH - ad_len - 2; + + if (name_len > max_len) { + name_len = max_len; + ptr[1] = EIR_NAME_SHORT; + } else + ptr[1] = EIR_NAME_COMPLETE; + + ptr[0] = name_len + 1; + + memcpy(ptr + 2, hdev->dev_name, name_len); + + ad_len += (name_len + 2); + ptr += (name_len + 2); + } + + return ad_len; +} + +static u8 create_instance_scan_rsp_data(struct hci_dev *hdev, u8 instance, + u8 *ptr) +{ + struct adv_info *adv_instance; + + adv_instance = hci_find_adv_instance(hdev, instance); + if (!adv_instance) + return 0; + + /* TODO: Set the appropriate entries based on advertising instance flags + * here once flags other than 0 are supported. + */ + memcpy(ptr, adv_instance->scan_rsp_data, + adv_instance->scan_rsp_len); + + return adv_instance->scan_rsp_len; +} + +static void update_inst_scan_rsp_data(struct hci_request *req, u8 instance) +{ + struct hci_dev *hdev = req->hdev; + struct hci_cp_le_set_scan_rsp_data cp; + u8 len; + + if (!hci_dev_test_flag(hdev, HCI_LE_ENABLED)) + return; + + memset(&cp, 0, sizeof(cp)); + + if (instance) + len = create_instance_scan_rsp_data(hdev, instance, cp.data); + else + len = create_default_scan_rsp_data(hdev, cp.data); + + if (hdev->scan_rsp_data_len == len && + !memcmp(cp.data, hdev->scan_rsp_data, len)) + return; + + memcpy(hdev->scan_rsp_data, cp.data, sizeof(cp.data)); + hdev->scan_rsp_data_len = len; + + cp.length = len; + + hci_req_add(req, HCI_OP_LE_SET_SCAN_RSP_DATA, sizeof(cp), &cp); +} + +void __hci_req_update_scan_rsp_data(struct hci_request *req, int instance) +{ + if (instance == HCI_ADV_CURRENT) + instance = get_current_adv_instance(req->hdev); + + update_inst_scan_rsp_data(req, get_current_adv_instance(req->hdev)); +} + +static u8 create_instance_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr) +{ + struct adv_info *adv_instance = NULL; + u8 ad_len = 0, flags = 0; + u32 instance_flags; + + /* Return 0 when the current instance identifier is invalid. */ + if (instance) { + adv_instance = hci_find_adv_instance(hdev, instance); + if (!adv_instance) + return 0; + } + + instance_flags = get_adv_instance_flags(hdev, instance); + + /* The Add Advertising command allows userspace to set both the general + * and limited discoverable flags. + */ + if (instance_flags & MGMT_ADV_FLAG_DISCOV) + flags |= LE_AD_GENERAL; + + if (instance_flags & MGMT_ADV_FLAG_LIMITED_DISCOV) + flags |= LE_AD_LIMITED; + + if (flags || (instance_flags & MGMT_ADV_FLAG_MANAGED_FLAGS)) { + /* If a discovery flag wasn't provided, simply use the global + * settings. + */ + if (!flags) + flags |= mgmt_get_adv_discov_flags(hdev); + + if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) + flags |= LE_AD_NO_BREDR; + + /* If flags would still be empty, then there is no need to + * include the "Flags" AD field". + */ + if (flags) { + ptr[0] = 0x02; + ptr[1] = EIR_FLAGS; + ptr[2] = flags; + + ad_len += 3; + ptr += 3; + } + } + + if (adv_instance) { + memcpy(ptr, adv_instance->adv_data, + adv_instance->adv_data_len); + ad_len += adv_instance->adv_data_len; + ptr += adv_instance->adv_data_len; + } + + /* Provide Tx Power only if we can provide a valid value for it */ + if (hdev->adv_tx_power != HCI_TX_POWER_INVALID && + (instance_flags & MGMT_ADV_FLAG_TX_POWER)) { + ptr[0] = 0x02; + ptr[1] = EIR_TX_POWER; + ptr[2] = (u8)hdev->adv_tx_power; + + ad_len += 3; + ptr += 3; + } + + return ad_len; +} + +static void update_inst_adv_data(struct hci_request *req, u8 instance) +{ + struct hci_dev *hdev = req->hdev; + struct hci_cp_le_set_adv_data cp; + u8 len; + + if (!hci_dev_test_flag(hdev, HCI_LE_ENABLED)) + return; + + memset(&cp, 0, sizeof(cp)); + + len = create_instance_adv_data(hdev, instance, cp.data); + + /* There's nothing to do if the data hasn't changed */ + if (hdev->adv_data_len == len && + memcmp(cp.data, hdev->adv_data, len) == 0) + return; + + memcpy(hdev->adv_data, cp.data, sizeof(cp.data)); + hdev->adv_data_len = len; + + cp.length = len; + + hci_req_add(req, HCI_OP_LE_SET_ADV_DATA, sizeof(cp), &cp); +} + +void __hci_req_update_adv_data(struct hci_request *req, int instance) +{ + if (instance == HCI_ADV_CURRENT) + instance = get_current_adv_instance(req->hdev); + + update_inst_adv_data(req, instance); +} + +int hci_req_update_adv_data(struct hci_dev *hdev, int instance) +{ + struct hci_request req; + + hci_req_init(&req, hdev); + __hci_req_update_adv_data(&req, instance); + + return hci_req_run(&req, NULL); +} + +static void adv_enable_complete(struct hci_dev *hdev, u8 status, u16 opcode) +{ + BT_DBG("%s status %u", hdev->name, status); +} + +void hci_req_reenable_advertising(struct hci_dev *hdev) +{ + struct hci_request req; + u8 instance; + + if (!hci_dev_test_flag(hdev, HCI_ADVERTISING) && + !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) + return; + + instance = get_current_adv_instance(hdev); + + hci_req_init(&req, hdev); + + if (instance) { + __hci_req_schedule_adv_instance(&req, instance, true); + } else { + __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); + __hci_req_update_scan_rsp_data(&req, HCI_ADV_CURRENT); + __hci_req_enable_advertising(&req); + } + + hci_req_run(&req, adv_enable_complete); +} + +static void adv_timeout_expire(struct work_struct *work) +{ + struct hci_dev *hdev = container_of(work, struct hci_dev, + adv_instance_expire.work); + + struct hci_request req; + u8 instance; + + BT_DBG("%s", hdev->name); + + hci_dev_lock(hdev); + + hdev->adv_instance_timeout = 0; + + instance = get_current_adv_instance(hdev); + if (instance == 0x00) + goto unlock; + + hci_req_init(&req, hdev); + + hci_req_clear_adv_instance(hdev, &req, instance, false); + + if (list_empty(&hdev->adv_instances)) + __hci_req_disable_advertising(&req); + + if (!skb_queue_empty(&req.cmd_q)) + hci_req_run(&req, NULL); + +unlock: + hci_dev_unlock(hdev); +} + +int __hci_req_schedule_adv_instance(struct hci_request *req, u8 instance, + bool force) +{ + struct hci_dev *hdev = req->hdev; + struct adv_info *adv_instance = NULL; + u16 timeout; + + if (hci_dev_test_flag(hdev, HCI_ADVERTISING) || + !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) + return -EPERM; + + if (hdev->adv_instance_timeout) + return -EBUSY; + + adv_instance = hci_find_adv_instance(hdev, instance); + if (!adv_instance) + return -ENOENT; + + /* A zero timeout means unlimited advertising. As long as there is + * only one instance, duration should be ignored. We still set a timeout + * in case further instances are being added later on. + * + * If the remaining lifetime of the instance is more than the duration + * then the timeout corresponds to the duration, otherwise it will be + * reduced to the remaining instance lifetime. + */ + if (adv_instance->timeout == 0 || + adv_instance->duration <= adv_instance->remaining_time) + timeout = adv_instance->duration; + else + timeout = adv_instance->remaining_time; + + /* The remaining time is being reduced unless the instance is being + * advertised without time limit. + */ + if (adv_instance->timeout) + adv_instance->remaining_time = + adv_instance->remaining_time - timeout; + + hdev->adv_instance_timeout = timeout; + queue_delayed_work(hdev->req_workqueue, + &hdev->adv_instance_expire, + msecs_to_jiffies(timeout * 1000)); + + /* If we're just re-scheduling the same instance again then do not + * execute any HCI commands. This happens when a single instance is + * being advertised. + */ + if (!force && hdev->cur_adv_instance == instance && + hci_dev_test_flag(hdev, HCI_LE_ADV)) + return 0; + + hdev->cur_adv_instance = instance; + __hci_req_update_adv_data(req, HCI_ADV_CURRENT); + __hci_req_update_scan_rsp_data(req, HCI_ADV_CURRENT); + __hci_req_enable_advertising(req); + + return 0; +} + +static void cancel_adv_timeout(struct hci_dev *hdev) +{ + if (hdev->adv_instance_timeout) { + hdev->adv_instance_timeout = 0; + cancel_delayed_work(&hdev->adv_instance_expire); + } +} + +/* For a single instance: + * - force == true: The instance will be removed even when its remaining + * lifetime is not zero. + * - force == false: the instance will be deactivated but kept stored unless + * the remaining lifetime is zero. + * + * For instance == 0x00: + * - force == true: All instances will be removed regardless of their timeout + * setting. + * - force == false: Only instances that have a timeout will be removed. + */ +void hci_req_clear_adv_instance(struct hci_dev *hdev, struct hci_request *req, + u8 instance, bool force) +{ + struct adv_info *adv_instance, *n, *next_instance = NULL; + int err; + u8 rem_inst; + + /* Cancel any timeout concerning the removed instance(s). */ + if (!instance || hdev->cur_adv_instance == instance) + cancel_adv_timeout(hdev); + + /* Get the next instance to advertise BEFORE we remove + * the current one. This can be the same instance again + * if there is only one instance. + */ + if (instance && hdev->cur_adv_instance == instance) + next_instance = hci_get_next_instance(hdev, instance); + + if (instance == 0x00) { + list_for_each_entry_safe(adv_instance, n, &hdev->adv_instances, + list) { + if (!(force || adv_instance->timeout)) + continue; + + rem_inst = adv_instance->instance; + err = hci_remove_adv_instance(hdev, rem_inst); + if (!err) + mgmt_advertising_removed(NULL, hdev, rem_inst); + } + hdev->cur_adv_instance = 0x00; + } else { + adv_instance = hci_find_adv_instance(hdev, instance); + + if (force || (adv_instance && adv_instance->timeout && + !adv_instance->remaining_time)) { + /* Don't advertise a removed instance. */ + if (next_instance && + next_instance->instance == instance) + next_instance = NULL; + + err = hci_remove_adv_instance(hdev, instance); + if (!err) + mgmt_advertising_removed(NULL, hdev, instance); + } + } + + if (list_empty(&hdev->adv_instances)) { + hdev->cur_adv_instance = 0x00; + hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE); + } + + if (!req || !hdev_is_powered(hdev) || + hci_dev_test_flag(hdev, HCI_ADVERTISING)) + return; + + if (next_instance) + __hci_req_schedule_adv_instance(req, next_instance->instance, + false); +} + static void set_random_addr(struct hci_request *req, bdaddr_t *rpa) { struct hci_dev *hdev = req->hdev; @@ -1031,14 +1550,6 @@ unlock: hci_dev_unlock(hdev); } -static void cancel_adv_timeout(struct hci_dev *hdev) -{ - if (hdev->adv_instance_timeout) { - hdev->adv_instance_timeout = 0; - cancel_delayed_work(&hdev->adv_instance_expire); - } -} - static void disable_advertising(struct hci_request *req) { u8 enable = 0x00; @@ -1280,6 +1791,7 @@ void hci_request_setup(struct hci_dev *hdev) INIT_WORK(&hdev->scan_update, scan_update_work); INIT_DELAYED_WORK(&hdev->le_scan_disable, le_scan_disable_work); INIT_DELAYED_WORK(&hdev->le_scan_restart, le_scan_restart_work); + INIT_DELAYED_WORK(&hdev->adv_instance_expire, adv_timeout_expire); } void hci_request_cancel_all(struct hci_dev *hdev) @@ -1291,4 +1803,9 @@ void hci_request_cancel_all(struct hci_dev *hdev) cancel_work_sync(&hdev->scan_update); cancel_delayed_work_sync(&hdev->le_scan_disable); cancel_delayed_work_sync(&hdev->le_scan_restart); + + if (hdev->adv_instance_timeout) { + cancel_delayed_work_sync(&hdev->adv_instance_expire); + hdev->adv_instance_timeout = 0; + } } diff --git a/net/bluetooth/hci_request.h b/net/bluetooth/hci_request.h index cc82755..5358b1b 100644 --- a/net/bluetooth/hci_request.h +++ b/net/bluetooth/hci_request.h @@ -58,6 +58,20 @@ struct sk_buff *hci_prepare_cmd(struct hci_dev *hdev, u16 opcode, u32 plen, void hci_req_add_le_scan_disable(struct hci_request *req); void hci_req_add_le_passive_scan(struct hci_request *req); +#define HCI_ADV_CURRENT (-1) + +void hci_req_reenable_advertising(struct hci_dev *hdev); +void __hci_req_enable_advertising(struct hci_request *req); +void __hci_req_disable_advertising(struct hci_request *req); +void __hci_req_update_adv_data(struct hci_request *req, int instance); +int hci_req_update_adv_data(struct hci_dev *hdev, int instance); +void __hci_req_update_scan_rsp_data(struct hci_request *req, int instance); + +int __hci_req_schedule_adv_instance(struct hci_request *req, u8 instance, + bool force); +void hci_req_clear_adv_instance(struct hci_dev *hdev, struct hci_request *req, + u8 instance, bool force); + /* Returns true if HCI commands were queued */ bool hci_req_stop_discovery(struct hci_request *req); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 0d20e13..6d0f002 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -841,98 +841,7 @@ static struct mgmt_pending_cmd *pending_find_data(u16 opcode, return mgmt_pending_find_data(HCI_CHANNEL_CONTROL, opcode, hdev, data); } -static u8 get_current_adv_instance(struct hci_dev *hdev) -{ - /* The "Set Advertising" setting supersedes the "Add Advertising" - * setting. Here we set the advertising data based on which - * setting was set. When neither apply, default to the global settings, - * represented by instance "0". - */ - if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) && - !hci_dev_test_flag(hdev, HCI_ADVERTISING)) - return hdev->cur_adv_instance; - - return 0x00; -} - -static u8 create_default_scan_rsp_data(struct hci_dev *hdev, u8 *ptr) -{ - u8 ad_len = 0; - size_t name_len; - - name_len = strlen(hdev->dev_name); - if (name_len > 0) { - size_t max_len = HCI_MAX_AD_LENGTH - ad_len - 2; - - if (name_len > max_len) { - name_len = max_len; - ptr[1] = EIR_NAME_SHORT; - } else - ptr[1] = EIR_NAME_COMPLETE; - - ptr[0] = name_len + 1; - - memcpy(ptr + 2, hdev->dev_name, name_len); - - ad_len += (name_len + 2); - ptr += (name_len + 2); - } - - return ad_len; -} - -static u8 create_instance_scan_rsp_data(struct hci_dev *hdev, u8 instance, - u8 *ptr) -{ - struct adv_info *adv_instance; - - adv_instance = hci_find_adv_instance(hdev, instance); - if (!adv_instance) - return 0; - - /* TODO: Set the appropriate entries based on advertising instance flags - * here once flags other than 0 are supported. - */ - memcpy(ptr, adv_instance->scan_rsp_data, - adv_instance->scan_rsp_len); - - return adv_instance->scan_rsp_len; -} - -static void update_inst_scan_rsp_data(struct hci_request *req, u8 instance) -{ - struct hci_dev *hdev = req->hdev; - struct hci_cp_le_set_scan_rsp_data cp; - u8 len; - - if (!hci_dev_test_flag(hdev, HCI_LE_ENABLED)) - return; - - memset(&cp, 0, sizeof(cp)); - - if (instance) - len = create_instance_scan_rsp_data(hdev, instance, cp.data); - else - len = create_default_scan_rsp_data(hdev, cp.data); - - if (hdev->scan_rsp_data_len == len && - !memcmp(cp.data, hdev->scan_rsp_data, len)) - return; - - memcpy(hdev->scan_rsp_data, cp.data, sizeof(cp.data)); - hdev->scan_rsp_data_len = len; - - cp.length = len; - - hci_req_add(req, HCI_OP_LE_SET_SCAN_RSP_DATA, sizeof(cp), &cp); -} - -static void update_scan_rsp_data(struct hci_request *req) -{ - update_inst_scan_rsp_data(req, get_current_adv_instance(req->hdev)); -} - -static u8 get_adv_discov_flags(struct hci_dev *hdev) +u8 mgmt_get_adv_discov_flags(struct hci_dev *hdev) { struct mgmt_pending_cmd *cmd; @@ -956,7 +865,7 @@ static u8 get_adv_discov_flags(struct hci_dev *hdev) return 0; } -static bool get_connectable(struct hci_dev *hdev) +bool mgmt_get_connectable(struct hci_dev *hdev) { struct mgmt_pending_cmd *cmd; @@ -973,163 +882,6 @@ static bool get_connectable(struct hci_dev *hdev) return hci_dev_test_flag(hdev, HCI_CONNECTABLE); } -static u32 get_adv_instance_flags(struct hci_dev *hdev, u8 instance) -{ - u32 flags; - struct adv_info *adv_instance; - - if (instance == 0x00) { - /* Instance 0 always manages the "Tx Power" and "Flags" - * fields - */ - flags = MGMT_ADV_FLAG_TX_POWER | MGMT_ADV_FLAG_MANAGED_FLAGS; - - /* For instance 0, the HCI_ADVERTISING_CONNECTABLE setting - * corresponds to the "connectable" instance flag. - */ - if (hci_dev_test_flag(hdev, HCI_ADVERTISING_CONNECTABLE)) - flags |= MGMT_ADV_FLAG_CONNECTABLE; - - return flags; - } - - adv_instance = hci_find_adv_instance(hdev, instance); - - /* Return 0 when we got an invalid instance identifier. */ - if (!adv_instance) - return 0; - - return adv_instance->flags; -} - -static u8 get_cur_adv_instance_scan_rsp_len(struct hci_dev *hdev) -{ - u8 instance = get_current_adv_instance(hdev); - struct adv_info *adv_instance; - - /* Ignore instance 0 */ - if (instance == 0x00) - return 0; - - adv_instance = hci_find_adv_instance(hdev, instance); - if (!adv_instance) - return 0; - - /* TODO: Take into account the "appearance" and "local-name" flags here. - * These are currently being ignored as they are not supported. - */ - return adv_instance->scan_rsp_len; -} - -static u8 create_instance_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr) -{ - struct adv_info *adv_instance = NULL; - u8 ad_len = 0, flags = 0; - u32 instance_flags; - - /* Return 0 when the current instance identifier is invalid. */ - if (instance) { - adv_instance = hci_find_adv_instance(hdev, instance); - if (!adv_instance) - return 0; - } - - instance_flags = get_adv_instance_flags(hdev, instance); - - /* The Add Advertising command allows userspace to set both the general - * and limited discoverable flags. - */ - if (instance_flags & MGMT_ADV_FLAG_DISCOV) - flags |= LE_AD_GENERAL; - - if (instance_flags & MGMT_ADV_FLAG_LIMITED_DISCOV) - flags |= LE_AD_LIMITED; - - if (flags || (instance_flags & MGMT_ADV_FLAG_MANAGED_FLAGS)) { - /* If a discovery flag wasn't provided, simply use the global - * settings. - */ - if (!flags) - flags |= get_adv_discov_flags(hdev); - - if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) - flags |= LE_AD_NO_BREDR; - - /* If flags would still be empty, then there is no need to - * include the "Flags" AD field". - */ - if (flags) { - ptr[0] = 0x02; - ptr[1] = EIR_FLAGS; - ptr[2] = flags; - - ad_len += 3; - ptr += 3; - } - } - - if (adv_instance) { - memcpy(ptr, adv_instance->adv_data, - adv_instance->adv_data_len); - ad_len += adv_instance->adv_data_len; - ptr += adv_instance->adv_data_len; - } - - /* Provide Tx Power only if we can provide a valid value for it */ - if (hdev->adv_tx_power != HCI_TX_POWER_INVALID && - (instance_flags & MGMT_ADV_FLAG_TX_POWER)) { - ptr[0] = 0x02; - ptr[1] = EIR_TX_POWER; - ptr[2] = (u8)hdev->adv_tx_power; - - ad_len += 3; - ptr += 3; - } - - return ad_len; -} - -static void update_inst_adv_data(struct hci_request *req, u8 instance) -{ - struct hci_dev *hdev = req->hdev; - struct hci_cp_le_set_adv_data cp; - u8 len; - - if (!hci_dev_test_flag(hdev, HCI_LE_ENABLED)) - return; - - memset(&cp, 0, sizeof(cp)); - - len = create_instance_adv_data(hdev, instance, cp.data); - - /* There's nothing to do if the data hasn't changed */ - if (hdev->adv_data_len == len && - memcmp(cp.data, hdev->adv_data, len) == 0) - return; - - memcpy(hdev->adv_data, cp.data, sizeof(cp.data)); - hdev->adv_data_len = len; - - cp.length = len; - - hci_req_add(req, HCI_OP_LE_SET_ADV_DATA, sizeof(cp), &cp); -} - -static void update_adv_data(struct hci_request *req) -{ - update_inst_adv_data(req, get_current_adv_instance(req->hdev)); -} - -int mgmt_update_adv_data(struct hci_dev *hdev) -{ - struct hci_request req; - - hci_req_init(&req, hdev); - update_adv_data(&req); - - return hci_req_run(&req, NULL); -} - static void create_eir(struct hci_dev *hdev, u8 *data) { u8 *ptr = data; @@ -1247,70 +999,6 @@ static void update_class(struct hci_request *req) hci_req_add(req, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod); } -static void disable_advertising(struct hci_request *req) -{ - u8 enable = 0x00; - - hci_req_add(req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(enable), &enable); -} - -static void enable_advertising(struct hci_request *req) -{ - struct hci_dev *hdev = req->hdev; - struct hci_cp_le_set_adv_param cp; - u8 own_addr_type, enable = 0x01; - bool connectable; - u8 instance; - u32 flags; - - if (hci_conn_num(hdev, LE_LINK) > 0) - return; - - if (hci_dev_test_flag(hdev, HCI_LE_ADV)) - disable_advertising(req); - - /* Clear the HCI_LE_ADV bit temporarily so that the - * hci_update_random_address knows that it's safe to go ahead - * and write a new random address. The flag will be set back on - * as soon as the SET_ADV_ENABLE HCI command completes. - */ - hci_dev_clear_flag(hdev, HCI_LE_ADV); - - instance = get_current_adv_instance(hdev); - flags = get_adv_instance_flags(hdev, instance); - - /* If the "connectable" instance flag was not set, then choose between - * ADV_IND and ADV_NONCONN_IND based on the global connectable setting. - */ - connectable = (flags & MGMT_ADV_FLAG_CONNECTABLE) || - get_connectable(hdev); - - /* Set require_privacy to true only when non-connectable - * advertising is used. In that case it is fine to use a - * non-resolvable private address. - */ - if (hci_update_random_address(req, !connectable, &own_addr_type) < 0) - return; - - memset(&cp, 0, sizeof(cp)); - cp.min_interval = cpu_to_le16(hdev->le_adv_min_interval); - cp.max_interval = cpu_to_le16(hdev->le_adv_max_interval); - - if (connectable) - cp.type = LE_ADV_IND; - else if (get_cur_adv_instance_scan_rsp_len(hdev)) - cp.type = LE_ADV_SCAN_IND; - else - cp.type = LE_ADV_NONCONN_IND; - - cp.own_address_type = own_addr_type; - cp.channel_map = hdev->le_adv_channel_map; - - hci_req_add(req, HCI_OP_LE_SET_ADV_PARAM, sizeof(cp), &cp); - - hci_req_add(req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(enable), &enable); -} - static void service_cache_off(struct work_struct *work) { struct hci_dev *hdev = container_of(work, struct hci_dev, @@ -1346,10 +1034,11 @@ static void rpa_expired(struct work_struct *work) return; /* The generation of a new RPA and programming it into the - * controller happens in the enable_advertising() function. + * controller happens in the hci_req_enable_advertising() + * function. */ hci_req_init(&req, hdev); - enable_advertising(&req); + __hci_req_enable_advertising(&req); hci_req_run(&req, NULL); } @@ -1417,8 +1106,7 @@ static void clean_up_hci_complete(struct hci_dev *hdev, u8 status, u16 opcode) } } -static void advertising_added(struct sock *sk, struct hci_dev *hdev, - u8 instance) +void mgmt_advertising_added(struct sock *sk, struct hci_dev *hdev, u8 instance) { struct mgmt_ev_advertising_added ev; @@ -1427,8 +1115,8 @@ static void advertising_added(struct sock *sk, struct hci_dev *hdev, mgmt_event(MGMT_EV_ADVERTISING_ADDED, hdev, &ev, sizeof(ev), sk); } -static void advertising_removed(struct sock *sk, struct hci_dev *hdev, - u8 instance) +void mgmt_advertising_removed(struct sock *sk, struct hci_dev *hdev, + u8 instance) { struct mgmt_ev_advertising_removed ev; @@ -1437,65 +1125,6 @@ static void advertising_removed(struct sock *sk, struct hci_dev *hdev, mgmt_event(MGMT_EV_ADVERTISING_REMOVED, hdev, &ev, sizeof(ev), sk); } -static int schedule_adv_instance(struct hci_request *req, u8 instance, - bool force) { - struct hci_dev *hdev = req->hdev; - struct adv_info *adv_instance = NULL; - u16 timeout; - - if (hci_dev_test_flag(hdev, HCI_ADVERTISING) || - !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) - return -EPERM; - - if (hdev->adv_instance_timeout) - return -EBUSY; - - adv_instance = hci_find_adv_instance(hdev, instance); - if (!adv_instance) - return -ENOENT; - - /* A zero timeout means unlimited advertising. As long as there is - * only one instance, duration should be ignored. We still set a timeout - * in case further instances are being added later on. - * - * If the remaining lifetime of the instance is more than the duration - * then the timeout corresponds to the duration, otherwise it will be - * reduced to the remaining instance lifetime. - */ - if (adv_instance->timeout == 0 || - adv_instance->duration <= adv_instance->remaining_time) - timeout = adv_instance->duration; - else - timeout = adv_instance->remaining_time; - - /* The remaining time is being reduced unless the instance is being - * advertised without time limit. - */ - if (adv_instance->timeout) - adv_instance->remaining_time = - adv_instance->remaining_time - timeout; - - hdev->adv_instance_timeout = timeout; - queue_delayed_work(hdev->workqueue, - &hdev->adv_instance_expire, - msecs_to_jiffies(timeout * 1000)); - - /* If we're just re-scheduling the same instance again then do not - * execute any HCI commands. This happens when a single instance is - * being advertised. - */ - if (!force && hdev->cur_adv_instance == instance && - hci_dev_test_flag(hdev, HCI_LE_ADV)) - return 0; - - hdev->cur_adv_instance = instance; - update_adv_data(req); - update_scan_rsp_data(req); - enable_advertising(req); - - return 0; -} - static void cancel_adv_timeout(struct hci_dev *hdev) { if (hdev->adv_instance_timeout) { @@ -1504,76 +1133,6 @@ static void cancel_adv_timeout(struct hci_dev *hdev) } } -/* For a single instance: - * - force == true: The instance will be removed even when its remaining - * lifetime is not zero. - * - force == false: the instance will be deactivated but kept stored unless - * the remaining lifetime is zero. - * - * For instance == 0x00: - * - force == true: All instances will be removed regardless of their timeout - * setting. - * - force == false: Only instances that have a timeout will be removed. - */ -static void clear_adv_instance(struct hci_dev *hdev, struct hci_request *req, - u8 instance, bool force) -{ - struct adv_info *adv_instance, *n, *next_instance = NULL; - int err; - u8 rem_inst; - - /* Cancel any timeout concerning the removed instance(s). */ - if (!instance || hdev->cur_adv_instance == instance) - cancel_adv_timeout(hdev); - - /* Get the next instance to advertise BEFORE we remove - * the current one. This can be the same instance again - * if there is only one instance. - */ - if (instance && hdev->cur_adv_instance == instance) - next_instance = hci_get_next_instance(hdev, instance); - - if (instance == 0x00) { - list_for_each_entry_safe(adv_instance, n, &hdev->adv_instances, - list) { - if (!(force || adv_instance->timeout)) - continue; - - rem_inst = adv_instance->instance; - err = hci_remove_adv_instance(hdev, rem_inst); - if (!err) - advertising_removed(NULL, hdev, rem_inst); - } - hdev->cur_adv_instance = 0x00; - } else { - adv_instance = hci_find_adv_instance(hdev, instance); - - if (force || (adv_instance && adv_instance->timeout && - !adv_instance->remaining_time)) { - /* Don't advertise a removed instance. */ - if (next_instance && - next_instance->instance == instance) - next_instance = NULL; - - err = hci_remove_adv_instance(hdev, instance); - if (!err) - advertising_removed(NULL, hdev, instance); - } - } - - if (list_empty(&hdev->adv_instances)) { - hdev->cur_adv_instance = 0x00; - hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE); - } - - if (!req || !hdev_is_powered(hdev) || - hci_dev_test_flag(hdev, HCI_ADVERTISING)) - return; - - if (next_instance) - schedule_adv_instance(req, next_instance->instance, false); -} - static int clean_up_hci_state(struct hci_dev *hdev) { struct hci_request req; @@ -1589,10 +1148,10 @@ static int clean_up_hci_state(struct hci_dev *hdev) hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan); } - clear_adv_instance(hdev, NULL, 0x00, false); + hci_req_clear_adv_instance(hdev, NULL, 0x00, false); if (hci_dev_test_flag(hdev, HCI_LE_ADV)) - disable_advertising(&req); + __hci_req_disable_advertising(&req); discov_stopped = hci_req_stop_discovery(&req); @@ -1975,7 +1534,7 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data, hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, sizeof(scan), &scan); update_ad: - update_adv_data(&req); + __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); err = hci_req_run(&req, set_discoverable_complete); if (err < 0) @@ -2060,7 +1619,7 @@ static void set_connectable_complete(struct hci_dev *hdev, u8 status, new_settings(hdev, cmd->sk); hci_req_update_scan(hdev); if (discov_changed) - mgmt_update_adv_data(hdev); + hci_req_update_adv_data(hdev, HCI_ADV_CURRENT); hci_update_background_scan(hdev); } @@ -2151,7 +1710,7 @@ static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data, hci_dev_clear_flag(hdev, HCI_LIMITED_DISCOVERABLE); hci_dev_clear_flag(hdev, HCI_DISCOVERABLE); } - update_adv_data(&req); + __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); } else if (cp->val != test_bit(HCI_PSCAN, &hdev->flags)) { if (cp->val) { scan = SCAN_PAGE; @@ -2181,7 +1740,7 @@ no_scan_update: /* Update the advertising parameters if necessary */ if (hci_dev_test_flag(hdev, HCI_ADVERTISING) || hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) - enable_advertising(&req); + __hci_req_enable_advertising(&req); err = hci_req_run(&req, set_connectable_complete); if (err < 0) { @@ -2466,8 +2025,8 @@ static void le_enable_complete(struct hci_dev *hdev, u8 status, u16 opcode) struct hci_request req; hci_req_init(&req, hdev); - update_adv_data(&req); - update_scan_rsp_data(&req); + __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); + __hci_req_update_scan_rsp_data(&req, HCI_ADV_CURRENT); hci_req_run(&req, NULL); hci_update_background_scan(hdev); } @@ -2518,7 +2077,7 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) enabled = lmp_host_le_capable(hdev); if (!val) - clear_adv_instance(hdev, NULL, 0x00, true); + hci_req_clear_adv_instance(hdev, NULL, 0x00, true); if (!hdev_is_powered(hdev) || val == enabled) { bool changed = false; @@ -2565,7 +2124,7 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) hci_cp.simul = 0x00; } else { if (hci_dev_test_flag(hdev, HCI_LE_ADV)) - disable_advertising(&req); + __hci_req_disable_advertising(&req); } hci_req_add(&req, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(hci_cp), @@ -3856,7 +3415,7 @@ static int set_local_name(struct sock *sk, struct hci_dev *hdev, void *data, * no need to udpate the advertising data here. */ if (lmp_le_capable(hdev)) - update_scan_rsp_data(&req); + __hci_req_update_scan_rsp_data(&req, HCI_ADV_CURRENT); err = hci_req_run(&req, set_name_complete); if (err < 0) @@ -4600,7 +4159,7 @@ static void set_advertising_complete(struct hci_dev *hdev, u8 status, hci_req_init(&req, hdev); - err = schedule_adv_instance(&req, instance, true); + err = __hci_req_schedule_adv_instance(&req, instance, true); if (!err) err = hci_req_run(&req, enable_advertising_instance); @@ -4697,11 +4256,11 @@ static int set_advertising(struct sock *sk, struct hci_dev *hdev, void *data, * We cannot use update_[adv|scan_rsp]_data() here as the * HCI_ADVERTISING flag is not yet set. */ - update_inst_adv_data(&req, 0x00); - update_inst_scan_rsp_data(&req, 0x00); - enable_advertising(&req); + __hci_req_update_adv_data(&req, 0x00); + __hci_req_update_scan_rsp_data(&req, 0x00); + __hci_req_enable_advertising(&req); } else { - disable_advertising(&req); + __hci_req_disable_advertising(&req); } err = hci_req_run(&req, set_advertising_complete); @@ -5033,8 +4592,8 @@ static int set_bredr(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) goto unlock; } - /* We need to flip the bit already here so that update_adv_data - * generates the correct flags. + /* We need to flip the bit already here so that + * hci_req_update_adv_data generates the correct flags. */ hci_dev_set_flag(hdev, HCI_BREDR_ENABLED); @@ -5046,7 +4605,7 @@ static int set_bredr(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) /* Since only the advertising data flags will change, there * is no need to update the scan response data. */ - update_adv_data(&req); + __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); err = hci_req_run(&req, set_bredr_complete); if (err < 0) @@ -6583,7 +6142,7 @@ static int read_local_oob_ext_data(struct sock *sk, struct hci_dev *hdev, rand, sizeof(rand)); } - flags = get_adv_discov_flags(hdev); + flags = mgmt_get_adv_discov_flags(hdev); if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) flags |= LE_AD_NO_BREDR; @@ -6772,7 +6331,7 @@ static void add_advertising_complete(struct hci_dev *hdev, u8 status, cancel_adv_timeout(hdev); hci_remove_adv_instance(hdev, instance); - advertising_removed(cmd ? cmd->sk : NULL, hdev, instance); + mgmt_advertising_removed(cmd ? cmd->sk : NULL, hdev, instance); } if (!cmd) @@ -6794,31 +6353,6 @@ unlock: hci_dev_unlock(hdev); } -void mgmt_adv_timeout_expired(struct hci_dev *hdev) -{ - u8 instance; - struct hci_request req; - - hdev->adv_instance_timeout = 0; - - instance = get_current_adv_instance(hdev); - if (instance == 0x00) - return; - - hci_dev_lock(hdev); - hci_req_init(&req, hdev); - - clear_adv_instance(hdev, &req, instance, false); - - if (list_empty(&hdev->adv_instances)) - disable_advertising(&req); - - if (!skb_queue_empty(&req.cmd_q)) - hci_req_run(&req, NULL); - - hci_dev_unlock(hdev); -} - static int add_advertising(struct sock *sk, struct hci_dev *hdev, void *data, u16 data_len) { @@ -6897,7 +6431,7 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev, * actually added. */ if (hdev->adv_instance_cnt > prev_instance_cnt) - advertising_added(sk, hdev, cp->instance); + mgmt_advertising_added(sk, hdev, cp->instance); hci_dev_set_flag(hdev, HCI_ADVERTISING_INSTANCE); @@ -6944,7 +6478,7 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev, hci_req_init(&req, hdev); - err = schedule_adv_instance(&req, schedule_instance, true); + err = __hci_req_schedule_adv_instance(&req, schedule_instance, true); if (!err) err = hci_req_run(&req, add_advertising_complete); @@ -7024,10 +6558,10 @@ static int remove_advertising(struct sock *sk, struct hci_dev *hdev, hci_req_init(&req, hdev); - clear_adv_instance(hdev, &req, cp->instance, true); + hci_req_clear_adv_instance(hdev, &req, cp->instance, true); if (list_empty(&hdev->adv_instances)) - disable_advertising(&req); + __hci_req_disable_advertising(&req); /* If no HCI commands have been collected so far or the HCI_ADVERTISING * flag is set or the device isn't powered then we have no HCI @@ -7367,8 +6901,8 @@ static int powered_update_hci(struct hci_dev *hdev) if (hci_dev_test_flag(hdev, HCI_LE_ENABLED) && (hci_dev_test_flag(hdev, HCI_ADVERTISING) || !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))) { - update_adv_data(&req); - update_scan_rsp_data(&req); + __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); + __hci_req_update_scan_rsp_data(&req, HCI_ADV_CURRENT); } if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) && @@ -7380,11 +6914,12 @@ static int powered_update_hci(struct hci_dev *hdev) } if (hci_dev_test_flag(hdev, HCI_ADVERTISING)) - enable_advertising(&req); + __hci_req_enable_advertising(&req); else if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) && hdev->cur_adv_instance) - schedule_adv_instance(&req, hdev->cur_adv_instance, - true); + __hci_req_schedule_adv_instance(&req, + hdev->cur_adv_instance, + true); } link_sec = hci_dev_test_flag(hdev, HCI_LINK_SECURITY); @@ -7505,7 +7040,7 @@ void mgmt_discoverable_timeout(struct hci_dev *hdev) * only update AD if advertising was enabled using Set Advertising. */ if (hci_dev_test_flag(hdev, HCI_ADVERTISING)) - update_adv_data(&req); + __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); hci_req_run(&req, NULL); @@ -8352,35 +7887,6 @@ void mgmt_discovering(struct hci_dev *hdev, u8 discovering) mgmt_event(MGMT_EV_DISCOVERING, hdev, &ev, sizeof(ev), NULL); } -static void adv_enable_complete(struct hci_dev *hdev, u8 status, u16 opcode) -{ - BT_DBG("%s status %u", hdev->name, status); -} - -void mgmt_reenable_advertising(struct hci_dev *hdev) -{ - struct hci_request req; - u8 instance; - - if (!hci_dev_test_flag(hdev, HCI_ADVERTISING) && - !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) - return; - - instance = get_current_adv_instance(hdev); - - hci_req_init(&req, hdev); - - if (instance) { - schedule_adv_instance(&req, instance, true); - } else { - update_adv_data(&req); - update_scan_rsp_data(&req); - enable_advertising(&req); - } - - hci_req_run(&req, adv_enable_complete); -} - static struct hci_mgmt_chan chan = { .channel = HCI_CHANNEL_CONTROL, .handler_count = ARRAY_SIZE(mgmt_handlers), -- cgit v0.10.2 From 53c0ba74510c1182786dcd1e3710215467777601 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Sun, 22 Nov 2015 16:43:43 +0300 Subject: Bluetooth: Move connectable changes to hdev->req_workqueue This way the connectable changes are synchronized against each other, which helps avoid potential races. The connectable mode is also linked together with LE advertising which makes is more convenient to have it behind the same workqueue. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index b56085b..a855e41 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -330,6 +330,7 @@ struct hci_dev { struct work_struct discov_update; struct work_struct bg_scan_update; struct work_struct scan_update; + struct work_struct connectable_update; struct delayed_work le_scan_disable; struct delayed_work le_scan_restart; @@ -1491,6 +1492,7 @@ void mgmt_new_conn_param(struct hci_dev *hdev, bdaddr_t *bdaddr, u16 max_interval, u16 latency, u16 timeout); void mgmt_smp_complete(struct hci_conn *conn, bool complete); bool mgmt_get_connectable(struct hci_dev *hdev); +void mgmt_set_connectable_complete(struct hci_dev *hdev, u8 status); u8 mgmt_get_adv_discov_flags(struct hci_dev *hdev); void mgmt_advertising_added(struct sock *sk, struct hci_dev *hdev, u8 instance); diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index e6622bd1..167c906 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -1274,6 +1274,43 @@ static void scan_update_work(struct work_struct *work) hci_req_sync(hdev, update_scan, 0, HCI_CMD_TIMEOUT, NULL); } +static int connectable_update(struct hci_request *req, unsigned long opt) +{ + struct hci_dev *hdev = req->hdev; + + hci_dev_lock(hdev); + + __hci_req_update_scan(req); + + /* If BR/EDR is not enabled and we disable advertising as a + * by-product of disabling connectable, we need to update the + * advertising flags. + */ + if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) + __hci_req_update_adv_data(req, HCI_ADV_CURRENT); + + /* Update the advertising parameters if necessary */ + if (hci_dev_test_flag(hdev, HCI_ADVERTISING) || + hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) + __hci_req_enable_advertising(req); + + __hci_update_background_scan(req); + + hci_dev_unlock(hdev); + + return 0; +} + +static void connectable_update_work(struct work_struct *work) +{ + struct hci_dev *hdev = container_of(work, struct hci_dev, + connectable_update); + u8 status; + + hci_req_sync(hdev, connectable_update, 0, HCI_CMD_TIMEOUT, &status); + mgmt_set_connectable_complete(hdev, status); +} + void __hci_abort_conn(struct hci_request *req, struct hci_conn *conn, u8 reason) { @@ -1789,6 +1826,7 @@ void hci_request_setup(struct hci_dev *hdev) INIT_WORK(&hdev->discov_update, discov_update); INIT_WORK(&hdev->bg_scan_update, bg_scan_update); INIT_WORK(&hdev->scan_update, scan_update_work); + INIT_WORK(&hdev->connectable_update, connectable_update_work); INIT_DELAYED_WORK(&hdev->le_scan_disable, le_scan_disable_work); INIT_DELAYED_WORK(&hdev->le_scan_restart, le_scan_restart_work); INIT_DELAYED_WORK(&hdev->adv_instance_expire, adv_timeout_expire); @@ -1801,6 +1839,7 @@ void hci_request_cancel_all(struct hci_dev *hdev) cancel_work_sync(&hdev->discov_update); cancel_work_sync(&hdev->bg_scan_update); cancel_work_sync(&hdev->scan_update); + cancel_work_sync(&hdev->connectable_update); cancel_delayed_work_sync(&hdev->le_scan_disable); cancel_delayed_work_sync(&hdev->le_scan_restart); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 6d0f002..d8b76ca 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1580,12 +1580,9 @@ static void write_fast_connectable(struct hci_request *req, bool enable) hci_req_add(req, HCI_OP_WRITE_PAGE_SCAN_TYPE, 1, &type); } -static void set_connectable_complete(struct hci_dev *hdev, u8 status, - u16 opcode) +void mgmt_set_connectable_complete(struct hci_dev *hdev, u8 status) { struct mgmt_pending_cmd *cmd; - struct mgmt_mode *cp; - bool conn_changed, discov_changed; BT_DBG("status 0x%02x", status); @@ -1601,27 +1598,8 @@ static void set_connectable_complete(struct hci_dev *hdev, u8 status, goto remove_cmd; } - cp = cmd->param; - if (cp->val) { - conn_changed = !hci_dev_test_and_set_flag(hdev, - HCI_CONNECTABLE); - discov_changed = false; - } else { - conn_changed = hci_dev_test_and_clear_flag(hdev, - HCI_CONNECTABLE); - discov_changed = hci_dev_test_and_clear_flag(hdev, - HCI_DISCOVERABLE); - } - send_settings_rsp(cmd->sk, MGMT_OP_SET_CONNECTABLE, hdev); - - if (conn_changed || discov_changed) { - new_settings(hdev, cmd->sk); - hci_req_update_scan(hdev); - if (discov_changed) - hci_req_update_adv_data(hdev, HCI_ADV_CURRENT); - hci_update_background_scan(hdev); - } + new_settings(hdev, cmd->sk); remove_cmd: mgmt_pending_remove(cmd); @@ -1664,8 +1642,6 @@ static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data, { struct mgmt_mode *cp = data; struct mgmt_pending_cmd *cmd; - struct hci_request req; - u8 scan; int err; BT_DBG("request for %s", hdev->name); @@ -1699,57 +1675,19 @@ static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data, goto failed; } - hci_req_init(&req, hdev); - - /* If BR/EDR is not enabled and we disable advertising as a - * by-product of disabling connectable, we need to update the - * advertising flags. - */ - if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) { - if (!cp->val) { - hci_dev_clear_flag(hdev, HCI_LIMITED_DISCOVERABLE); - hci_dev_clear_flag(hdev, HCI_DISCOVERABLE); - } - __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); - } else if (cp->val != test_bit(HCI_PSCAN, &hdev->flags)) { - if (cp->val) { - scan = SCAN_PAGE; - } else { - /* If we don't have any whitelist entries just - * disable all scanning. If there are entries - * and we had both page and inquiry scanning - * enabled then fall back to only page scanning. - * Otherwise no changes are needed. - */ - if (list_empty(&hdev->whitelist)) - scan = SCAN_DISABLED; - else if (test_bit(HCI_ISCAN, &hdev->flags)) - scan = SCAN_PAGE; - else - goto no_scan_update; - - if (test_bit(HCI_ISCAN, &hdev->flags) && - hdev->discov_timeout > 0) - cancel_delayed_work(&hdev->discov_off); - } + if (cp->val) { + hci_dev_set_flag(hdev, HCI_CONNECTABLE); + } else { + if (hdev->discov_timeout > 0) + cancel_delayed_work(&hdev->discov_off); - hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan); + hci_dev_clear_flag(hdev, HCI_LIMITED_DISCOVERABLE); + hci_dev_clear_flag(hdev, HCI_DISCOVERABLE); + hci_dev_clear_flag(hdev, HCI_CONNECTABLE); } -no_scan_update: - /* Update the advertising parameters if necessary */ - if (hci_dev_test_flag(hdev, HCI_ADVERTISING) || - hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) - __hci_req_enable_advertising(&req); - - err = hci_req_run(&req, set_connectable_complete); - if (err < 0) { - mgmt_pending_remove(cmd); - if (err == -ENODATA) - err = set_connectable_update_settings(hdev, sk, - cp->val); - goto failed; - } + queue_work(hdev->req_workqueue, &hdev->connectable_update); + err = 0; failed: hci_dev_unlock(hdev); -- cgit v0.10.2 From 14bf5eac7a4f4bf0729ff8eb358de4fab967cee1 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Sun, 22 Nov 2015 19:00:22 +0200 Subject: Bluetooth: Perform Class of Device changes through hdev->req_workqueue The Class of Device needs to be changed e.g. for limited discoverable mode. In preparation of moving the discoverable mode to hci_request.c and hdev->req_workqueue, move the Class of Device helpers there first. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index 167c906..e5e827b 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -1311,6 +1311,46 @@ static void connectable_update_work(struct work_struct *work) mgmt_set_connectable_complete(hdev, status); } +static u8 get_service_classes(struct hci_dev *hdev) +{ + struct bt_uuid *uuid; + u8 val = 0; + + list_for_each_entry(uuid, &hdev->uuids, list) + val |= uuid->svc_hint; + + return val; +} + +void __hci_req_update_class(struct hci_request *req) +{ + struct hci_dev *hdev = req->hdev; + u8 cod[3]; + + BT_DBG("%s", hdev->name); + + if (!hdev_is_powered(hdev)) + return; + + if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) + return; + + if (hci_dev_test_flag(hdev, HCI_SERVICE_CACHE)) + return; + + cod[0] = hdev->minor_class; + cod[1] = hdev->major_class; + cod[2] = get_service_classes(hdev); + + if (hci_dev_test_flag(hdev, HCI_LIMITED_DISCOVERABLE)) + cod[1] |= 0x20; + + if (memcmp(cod, hdev->dev_class, 3) == 0) + return; + + hci_req_add(req, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod); +} + void __hci_abort_conn(struct hci_request *req, struct hci_conn *conn, u8 reason) { diff --git a/net/bluetooth/hci_request.h b/net/bluetooth/hci_request.h index 5358b1b..4192034 100644 --- a/net/bluetooth/hci_request.h +++ b/net/bluetooth/hci_request.h @@ -72,6 +72,8 @@ int __hci_req_schedule_adv_instance(struct hci_request *req, u8 instance, void hci_req_clear_adv_instance(struct hci_dev *hdev, struct hci_request *req, u8 instance, bool force); +void __hci_req_update_class(struct hci_request *req); + /* Returns true if HCI commands were queued */ bool hci_req_stop_discovery(struct hci_request *req); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index d8b76ca..f5a4ee9 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -959,46 +959,6 @@ static void update_eir(struct hci_request *req) hci_req_add(req, HCI_OP_WRITE_EIR, sizeof(cp), &cp); } -static u8 get_service_classes(struct hci_dev *hdev) -{ - struct bt_uuid *uuid; - u8 val = 0; - - list_for_each_entry(uuid, &hdev->uuids, list) - val |= uuid->svc_hint; - - return val; -} - -static void update_class(struct hci_request *req) -{ - struct hci_dev *hdev = req->hdev; - u8 cod[3]; - - BT_DBG("%s", hdev->name); - - if (!hdev_is_powered(hdev)) - return; - - if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) - return; - - if (hci_dev_test_flag(hdev, HCI_SERVICE_CACHE)) - return; - - cod[0] = hdev->minor_class; - cod[1] = hdev->major_class; - cod[2] = get_service_classes(hdev); - - if (hci_dev_test_flag(hdev, HCI_LIMITED_DISCOVERABLE)) - cod[1] |= 0x20; - - if (memcmp(cod, hdev->dev_class, 3) == 0) - return; - - hci_req_add(req, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod); -} - static void service_cache_off(struct work_struct *work) { struct hci_dev *hdev = container_of(work, struct hci_dev, @@ -1013,7 +973,7 @@ static void service_cache_off(struct work_struct *work) hci_dev_lock(hdev); update_eir(&req); - update_class(&req); + __hci_req_update_class(&req); hci_dev_unlock(hdev); @@ -1370,7 +1330,7 @@ static void set_discoverable_complete(struct hci_dev *hdev, u8 status, */ hci_req_init(&req, hdev); __hci_req_update_scan(&req); - update_class(&req); + __hci_req_update_class(&req); hci_req_run(&req, NULL); remove_cmd: @@ -2177,7 +2137,7 @@ static int add_uuid(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) hci_req_init(&req, hdev); - update_class(&req); + __hci_req_update_class(&req); update_eir(&req); err = hci_req_run(&req, add_uuid_complete); @@ -2277,7 +2237,7 @@ static int remove_uuid(struct sock *sk, struct hci_dev *hdev, void *data, update_class: hci_req_init(&req, hdev); - update_class(&req); + __hci_req_update_class(&req); update_eir(&req); err = hci_req_run(&req, remove_uuid_complete); @@ -2356,7 +2316,7 @@ static int set_dev_class(struct sock *sk, struct hci_dev *hdev, void *data, update_eir(&req); } - update_class(&req); + __hci_req_update_class(&req); err = hci_req_run(&req, set_class_complete); if (err < 0) { @@ -6871,7 +6831,7 @@ static int powered_update_hci(struct hci_dev *hdev) else write_fast_connectable(&req, false); __hci_req_update_scan(&req); - update_class(&req); + __hci_req_update_class(&req); update_name(&req); update_eir(&req); } @@ -6972,7 +6932,7 @@ void mgmt_discoverable_timeout(struct hci_dev *hdev) hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, sizeof(scan), &scan); } - update_class(&req); + __hci_req_update_class(&req); /* Advertising instances don't use the global discoverable setting, so * only update AD if advertising was enabled using Set Advertising. -- cgit v0.10.2 From aed1a8851db022c3bd22af41a343068b8c6e40c1 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Sun, 22 Nov 2015 17:24:44 +0300 Subject: Bluetooth: Move discoverable changes to hdev->req_workqueue The discoverable mode is intrinsically linked with the connectable mode e.g. through sharing the same HCI command (Write Scan Enable) for BR/EDR. It makes therefore sense to move it to hci_request.c and run the changes through the same hdev->req_workqueue. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index a855e41..0a6966e 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -331,6 +331,7 @@ struct hci_dev { struct work_struct bg_scan_update; struct work_struct scan_update; struct work_struct connectable_update; + struct work_struct discoverable_update; struct delayed_work le_scan_disable; struct delayed_work le_scan_restart; @@ -1493,6 +1494,7 @@ void mgmt_new_conn_param(struct hci_dev *hdev, bdaddr_t *bdaddr, void mgmt_smp_complete(struct hci_conn *conn, bool complete); bool mgmt_get_connectable(struct hci_dev *hdev); void mgmt_set_connectable_complete(struct hci_dev *hdev, u8 status); +void mgmt_set_discoverable_complete(struct hci_dev *hdev, u8 status); u8 mgmt_get_adv_discov_flags(struct hci_dev *hdev); void mgmt_advertising_added(struct sock *sk, struct hci_dev *hdev, u8 instance); diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index e5e827b..8f72218 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -1351,6 +1351,68 @@ void __hci_req_update_class(struct hci_request *req) hci_req_add(req, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod); } +static void write_iac(struct hci_request *req) +{ + struct hci_dev *hdev = req->hdev; + struct hci_cp_write_current_iac_lap cp; + + if (!hci_dev_test_flag(hdev, HCI_DISCOVERABLE)) + return; + + if (hci_dev_test_flag(hdev, HCI_LIMITED_DISCOVERABLE)) { + /* Limited discoverable mode */ + cp.num_iac = min_t(u8, hdev->num_iac, 2); + cp.iac_lap[0] = 0x00; /* LIAC */ + cp.iac_lap[1] = 0x8b; + cp.iac_lap[2] = 0x9e; + cp.iac_lap[3] = 0x33; /* GIAC */ + cp.iac_lap[4] = 0x8b; + cp.iac_lap[5] = 0x9e; + } else { + /* General discoverable mode */ + cp.num_iac = 1; + cp.iac_lap[0] = 0x33; /* GIAC */ + cp.iac_lap[1] = 0x8b; + cp.iac_lap[2] = 0x9e; + } + + hci_req_add(req, HCI_OP_WRITE_CURRENT_IAC_LAP, + (cp.num_iac * 3) + 1, &cp); +} + +static int discoverable_update(struct hci_request *req, unsigned long opt) +{ + struct hci_dev *hdev = req->hdev; + + hci_dev_lock(hdev); + + if (hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) { + write_iac(req); + __hci_req_update_scan(req); + __hci_req_update_class(req); + } + + /* Advertising instances don't use the global discoverable setting, so + * only update AD if advertising was enabled using Set Advertising. + */ + if (hci_dev_test_flag(hdev, HCI_ADVERTISING)) + __hci_req_update_adv_data(req, HCI_ADV_CURRENT); + + hci_dev_unlock(hdev); + + return 0; +} + +static void discoverable_update_work(struct work_struct *work) +{ + struct hci_dev *hdev = container_of(work, struct hci_dev, + discoverable_update); + u8 status; + + hci_req_sync(hdev, discoverable_update, 0, HCI_CMD_TIMEOUT, &status); + mgmt_set_discoverable_complete(hdev, status); +} + void __hci_abort_conn(struct hci_request *req, struct hci_conn *conn, u8 reason) { @@ -1867,6 +1929,7 @@ void hci_request_setup(struct hci_dev *hdev) INIT_WORK(&hdev->bg_scan_update, bg_scan_update); INIT_WORK(&hdev->scan_update, scan_update_work); INIT_WORK(&hdev->connectable_update, connectable_update_work); + INIT_WORK(&hdev->discoverable_update, discoverable_update_work); INIT_DELAYED_WORK(&hdev->le_scan_disable, le_scan_disable_work); INIT_DELAYED_WORK(&hdev->le_scan_restart, le_scan_restart_work); INIT_DELAYED_WORK(&hdev->adv_instance_expire, adv_timeout_expire); @@ -1880,6 +1943,7 @@ void hci_request_cancel_all(struct hci_dev *hdev) cancel_work_sync(&hdev->bg_scan_update); cancel_work_sync(&hdev->scan_update); cancel_work_sync(&hdev->connectable_update); + cancel_work_sync(&hdev->discoverable_update); cancel_delayed_work_sync(&hdev->le_scan_disable); cancel_delayed_work_sync(&hdev->le_scan_restart); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index f5a4ee9..8846cb3 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1282,13 +1282,9 @@ static u8 mgmt_le_support(struct hci_dev *hdev) return MGMT_STATUS_SUCCESS; } -static void set_discoverable_complete(struct hci_dev *hdev, u8 status, - u16 opcode) +void mgmt_set_discoverable_complete(struct hci_dev *hdev, u8 status) { struct mgmt_pending_cmd *cmd; - struct mgmt_mode *cp; - struct hci_request req; - bool changed; BT_DBG("status 0x%02x", status); @@ -1305,33 +1301,14 @@ static void set_discoverable_complete(struct hci_dev *hdev, u8 status, goto remove_cmd; } - cp = cmd->param; - if (cp->val) { - changed = !hci_dev_test_and_set_flag(hdev, HCI_DISCOVERABLE); - - if (hdev->discov_timeout > 0) { - int to = msecs_to_jiffies(hdev->discov_timeout * 1000); - queue_delayed_work(hdev->workqueue, &hdev->discov_off, - to); - } - } else { - changed = hci_dev_test_and_clear_flag(hdev, HCI_DISCOVERABLE); + if (hci_dev_test_flag(hdev, HCI_DISCOVERABLE) && + hdev->discov_timeout > 0) { + int to = msecs_to_jiffies(hdev->discov_timeout * 1000); + queue_delayed_work(hdev->req_workqueue, &hdev->discov_off, to); } send_settings_rsp(cmd->sk, MGMT_OP_SET_DISCOVERABLE, hdev); - - if (changed) - new_settings(hdev, cmd->sk); - - /* When the discoverable mode gets changed, make sure - * that class of device has the limited discoverable - * bit correctly set. Also update page scan based on whitelist - * entries. - */ - hci_req_init(&req, hdev); - __hci_req_update_scan(&req); - __hci_req_update_class(&req); - hci_req_run(&req, NULL); + new_settings(hdev, cmd->sk); remove_cmd: mgmt_pending_remove(cmd); @@ -1345,9 +1322,7 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data, { struct mgmt_cp_set_discoverable *cp = data; struct mgmt_pending_cmd *cmd; - struct hci_request req; u16 timeout; - u8 scan; int err; BT_DBG("request for %s", hdev->name); @@ -1447,58 +1422,19 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data, cancel_delayed_work(&hdev->discov_off); hdev->discov_timeout = timeout; + if (cp->val) + hci_dev_set_flag(hdev, HCI_DISCOVERABLE); + else + hci_dev_clear_flag(hdev, HCI_DISCOVERABLE); + /* Limited discoverable mode */ if (cp->val == 0x02) hci_dev_set_flag(hdev, HCI_LIMITED_DISCOVERABLE); else hci_dev_clear_flag(hdev, HCI_LIMITED_DISCOVERABLE); - hci_req_init(&req, hdev); - - /* The procedure for LE-only controllers is much simpler - just - * update the advertising data. - */ - if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) - goto update_ad; - - scan = SCAN_PAGE; - - if (cp->val) { - struct hci_cp_write_current_iac_lap hci_cp; - - if (cp->val == 0x02) { - /* Limited discoverable mode */ - hci_cp.num_iac = min_t(u8, hdev->num_iac, 2); - hci_cp.iac_lap[0] = 0x00; /* LIAC */ - hci_cp.iac_lap[1] = 0x8b; - hci_cp.iac_lap[2] = 0x9e; - hci_cp.iac_lap[3] = 0x33; /* GIAC */ - hci_cp.iac_lap[4] = 0x8b; - hci_cp.iac_lap[5] = 0x9e; - } else { - /* General discoverable mode */ - hci_cp.num_iac = 1; - hci_cp.iac_lap[0] = 0x33; /* GIAC */ - hci_cp.iac_lap[1] = 0x8b; - hci_cp.iac_lap[2] = 0x9e; - } - - hci_req_add(&req, HCI_OP_WRITE_CURRENT_IAC_LAP, - (hci_cp.num_iac * 3) + 1, &hci_cp); - - scan |= SCAN_INQUIRY; - } else { - hci_dev_clear_flag(hdev, HCI_LIMITED_DISCOVERABLE); - } - - hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, sizeof(scan), &scan); - -update_ad: - __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); - - err = hci_req_run(&req, set_discoverable_complete); - if (err < 0) - mgmt_pending_remove(cmd); + queue_work(hdev->req_workqueue, &hdev->discoverable_update); + err = 0; failed: hci_dev_unlock(hdev); -- cgit v0.10.2 From c366f555b8df67633b849a5088bb897d6c63aaa5 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Mon, 23 Nov 2015 15:43:06 +0200 Subject: Bluetooth: Move discoverable timeout behind hdev->req_workqueue Since the other discoverable changes are behind req_workqueue now it only makes sense to move the discoverable timeout there as well. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 0a6966e..319bf02 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1436,7 +1436,6 @@ void mgmt_index_added(struct hci_dev *hdev); void mgmt_index_removed(struct hci_dev *hdev); void mgmt_set_powered_failed(struct hci_dev *hdev, int err); int mgmt_powered(struct hci_dev *hdev, u8 powered); -void mgmt_discoverable_timeout(struct hci_dev *hdev); void mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key, bool persistent); void mgmt_device_connected(struct hci_dev *hdev, struct hci_conn *conn, diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index bab8958..484c75f 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -1537,7 +1537,6 @@ int hci_dev_do_close(struct hci_dev *hdev) flush_work(&hdev->rx_work); if (hdev->discov_timeout > 0) { - cancel_delayed_work(&hdev->discov_off); hdev->discov_timeout = 0; hci_dev_clear_flag(hdev, HCI_DISCOVERABLE); hci_dev_clear_flag(hdev, HCI_LIMITED_DISCOVERABLE); @@ -2096,17 +2095,6 @@ static void hci_error_reset(struct work_struct *work) hci_dev_do_open(hdev); } -static void hci_discov_off(struct work_struct *work) -{ - struct hci_dev *hdev; - - hdev = container_of(work, struct hci_dev, discov_off.work); - - BT_DBG("%s", hdev->name); - - mgmt_discoverable_timeout(hdev); -} - void hci_uuids_clear(struct hci_dev *hdev) { struct bt_uuid *uuid, *tmp; @@ -2986,7 +2974,6 @@ struct hci_dev *hci_alloc_dev(void) INIT_WORK(&hdev->error_reset, hci_error_reset); INIT_DELAYED_WORK(&hdev->power_off, hci_power_off); - INIT_DELAYED_WORK(&hdev->discov_off, hci_discov_off); skb_queue_head_init(&hdev->rx_q); skb_queue_head_init(&hdev->cmd_q); diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index 8f72218..fe14fd1 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -1923,6 +1923,30 @@ static void discov_update(struct work_struct *work) } } +static void discov_off(struct work_struct *work) +{ + struct hci_dev *hdev = container_of(work, struct hci_dev, + discov_off.work); + + BT_DBG("%s", hdev->name); + + hci_dev_lock(hdev); + + /* When discoverable timeout triggers, then just make sure + * the limited discoverable flag is cleared. Even in the case + * of a timeout triggered from general discoverable, it is + * safe to unconditionally clear the flag. + */ + hci_dev_clear_flag(hdev, HCI_LIMITED_DISCOVERABLE); + hci_dev_clear_flag(hdev, HCI_DISCOVERABLE); + hdev->discov_timeout = 0; + + hci_dev_unlock(hdev); + + hci_req_sync(hdev, discoverable_update, 0, HCI_CMD_TIMEOUT, NULL); + mgmt_new_settings(hdev); +} + void hci_request_setup(struct hci_dev *hdev) { INIT_WORK(&hdev->discov_update, discov_update); @@ -1930,6 +1954,7 @@ void hci_request_setup(struct hci_dev *hdev) INIT_WORK(&hdev->scan_update, scan_update_work); INIT_WORK(&hdev->connectable_update, connectable_update_work); INIT_WORK(&hdev->discoverable_update, discoverable_update_work); + INIT_DELAYED_WORK(&hdev->discov_off, discov_off); INIT_DELAYED_WORK(&hdev->le_scan_disable, le_scan_disable_work); INIT_DELAYED_WORK(&hdev->le_scan_restart, le_scan_restart_work); INIT_DELAYED_WORK(&hdev->adv_instance_expire, adv_timeout_expire); @@ -1944,6 +1969,7 @@ void hci_request_cancel_all(struct hci_dev *hdev) cancel_work_sync(&hdev->scan_update); cancel_work_sync(&hdev->connectable_update); cancel_work_sync(&hdev->discoverable_update); + cancel_delayed_work_sync(&hdev->discov_off); cancel_delayed_work_sync(&hdev->le_scan_disable); cancel_delayed_work_sync(&hdev->le_scan_restart); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 8846cb3..29b3bb7 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1401,8 +1401,8 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data, if (cp->val && hdev->discov_timeout > 0) { int to = msecs_to_jiffies(hdev->discov_timeout * 1000); - queue_delayed_work(hdev->workqueue, &hdev->discov_off, - to); + queue_delayed_work(hdev->req_workqueue, + &hdev->discov_off, to); } err = send_settings_rsp(sk, MGMT_OP_SET_DISCOVERABLE, hdev); @@ -6848,43 +6848,6 @@ void mgmt_set_powered_failed(struct hci_dev *hdev, int err) mgmt_pending_remove(cmd); } -void mgmt_discoverable_timeout(struct hci_dev *hdev) -{ - struct hci_request req; - - hci_dev_lock(hdev); - - /* When discoverable timeout triggers, then just make sure - * the limited discoverable flag is cleared. Even in the case - * of a timeout triggered from general discoverable, it is - * safe to unconditionally clear the flag. - */ - hci_dev_clear_flag(hdev, HCI_LIMITED_DISCOVERABLE); - hci_dev_clear_flag(hdev, HCI_DISCOVERABLE); - - hci_req_init(&req, hdev); - if (hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) { - u8 scan = SCAN_PAGE; - hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, - sizeof(scan), &scan); - } - __hci_req_update_class(&req); - - /* Advertising instances don't use the global discoverable setting, so - * only update AD if advertising was enabled using Set Advertising. - */ - if (hci_dev_test_flag(hdev, HCI_ADVERTISING)) - __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); - - hci_req_run(&req, NULL); - - hdev->discov_timeout = 0; - - new_settings(hdev, NULL); - - hci_dev_unlock(hdev); -} - void mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key, bool persistent) { -- cgit v0.10.2 From 00cf5040b39638588cd10ae4ffcc76a1be6ecf2c Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Wed, 25 Nov 2015 16:15:41 +0200 Subject: Bluetooth: HCI name update to hci_request.c We'll soon need this both from hci_request.c and mgmt.c so move it as a request helper function to hci_request.c. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index fe14fd1..3150461 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -420,6 +420,16 @@ static void __hci_update_background_scan(struct hci_request *req) } } +void __hci_req_update_name(struct hci_request *req) +{ + struct hci_dev *hdev = req->hdev; + struct hci_cp_write_local_name cp; + + memcpy(cp.name, hdev->dev_name, sizeof(cp.name)); + + hci_req_add(req, HCI_OP_WRITE_LOCAL_NAME, sizeof(cp), &cp); +} + void hci_req_add_le_scan_disable(struct hci_request *req) { struct hci_cp_le_set_scan_enable cp; diff --git a/net/bluetooth/hci_request.h b/net/bluetooth/hci_request.h index 4192034..4e65a9c 100644 --- a/net/bluetooth/hci_request.h +++ b/net/bluetooth/hci_request.h @@ -55,6 +55,8 @@ void hci_req_sync_cancel(struct hci_dev *hdev, int err); struct sk_buff *hci_prepare_cmd(struct hci_dev *hdev, u16 opcode, u32 plen, const void *param); +void __hci_req_update_name(struct hci_request *req); + void hci_req_add_le_scan_disable(struct hci_request *req); void hci_req_add_le_passive_scan(struct hci_request *req); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 29b3bb7..001a29a 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -3153,16 +3153,6 @@ static int user_passkey_neg_reply(struct sock *sk, struct hci_dev *hdev, HCI_OP_USER_PASSKEY_NEG_REPLY, 0); } -static void update_name(struct hci_request *req) -{ - struct hci_dev *hdev = req->hdev; - struct hci_cp_write_local_name cp; - - memcpy(cp.name, hdev->dev_name, sizeof(cp.name)); - - hci_req_add(req, HCI_OP_WRITE_LOCAL_NAME, sizeof(cp), &cp); -} - static void set_name_complete(struct hci_dev *hdev, u8 status, u16 opcode) { struct mgmt_cp_set_local_name *cp; @@ -3241,7 +3231,7 @@ static int set_local_name(struct sock *sk, struct hci_dev *hdev, void *data, hci_req_init(&req, hdev); if (lmp_bredr_capable(hdev)) { - update_name(&req); + __hci_req_update_name(&req); update_eir(&req); } @@ -6768,7 +6758,7 @@ static int powered_update_hci(struct hci_dev *hdev) write_fast_connectable(&req, false); __hci_req_update_scan(&req); __hci_req_update_class(&req); - update_name(&req); + __hci_req_update_name(&req); update_eir(&req); } -- cgit v0.10.2 From b1a8917c9bcbf42113dfacb6492228e094c96862 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Wed, 25 Nov 2015 16:15:42 +0200 Subject: Bluetooth: Move EIR update to hci_request.c We'll soon need to update the EIR both from hci_request.c and mgmt.c so move update_eir() as a more generic request helper to hci_request.c. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index 3150461..030a1bb 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -21,6 +21,8 @@ SOFTWARE IS DISCLAIMED. */ +#include + #include #include #include @@ -430,6 +432,193 @@ void __hci_req_update_name(struct hci_request *req) hci_req_add(req, HCI_OP_WRITE_LOCAL_NAME, sizeof(cp), &cp); } +#define PNP_INFO_SVCLASS_ID 0x1200 + +static u8 *create_uuid16_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len) +{ + u8 *ptr = data, *uuids_start = NULL; + struct bt_uuid *uuid; + + if (len < 4) + return ptr; + + list_for_each_entry(uuid, &hdev->uuids, list) { + u16 uuid16; + + if (uuid->size != 16) + continue; + + uuid16 = get_unaligned_le16(&uuid->uuid[12]); + if (uuid16 < 0x1100) + continue; + + if (uuid16 == PNP_INFO_SVCLASS_ID) + continue; + + if (!uuids_start) { + uuids_start = ptr; + uuids_start[0] = 1; + uuids_start[1] = EIR_UUID16_ALL; + ptr += 2; + } + + /* Stop if not enough space to put next UUID */ + if ((ptr - data) + sizeof(u16) > len) { + uuids_start[1] = EIR_UUID16_SOME; + break; + } + + *ptr++ = (uuid16 & 0x00ff); + *ptr++ = (uuid16 & 0xff00) >> 8; + uuids_start[0] += sizeof(uuid16); + } + + return ptr; +} + +static u8 *create_uuid32_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len) +{ + u8 *ptr = data, *uuids_start = NULL; + struct bt_uuid *uuid; + + if (len < 6) + return ptr; + + list_for_each_entry(uuid, &hdev->uuids, list) { + if (uuid->size != 32) + continue; + + if (!uuids_start) { + uuids_start = ptr; + uuids_start[0] = 1; + uuids_start[1] = EIR_UUID32_ALL; + ptr += 2; + } + + /* Stop if not enough space to put next UUID */ + if ((ptr - data) + sizeof(u32) > len) { + uuids_start[1] = EIR_UUID32_SOME; + break; + } + + memcpy(ptr, &uuid->uuid[12], sizeof(u32)); + ptr += sizeof(u32); + uuids_start[0] += sizeof(u32); + } + + return ptr; +} + +static u8 *create_uuid128_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len) +{ + u8 *ptr = data, *uuids_start = NULL; + struct bt_uuid *uuid; + + if (len < 18) + return ptr; + + list_for_each_entry(uuid, &hdev->uuids, list) { + if (uuid->size != 128) + continue; + + if (!uuids_start) { + uuids_start = ptr; + uuids_start[0] = 1; + uuids_start[1] = EIR_UUID128_ALL; + ptr += 2; + } + + /* Stop if not enough space to put next UUID */ + if ((ptr - data) + 16 > len) { + uuids_start[1] = EIR_UUID128_SOME; + break; + } + + memcpy(ptr, uuid->uuid, 16); + ptr += 16; + uuids_start[0] += 16; + } + + return ptr; +} + +static void create_eir(struct hci_dev *hdev, u8 *data) +{ + u8 *ptr = data; + size_t name_len; + + name_len = strlen(hdev->dev_name); + + if (name_len > 0) { + /* EIR Data type */ + if (name_len > 48) { + name_len = 48; + ptr[1] = EIR_NAME_SHORT; + } else + ptr[1] = EIR_NAME_COMPLETE; + + /* EIR Data length */ + ptr[0] = name_len + 1; + + memcpy(ptr + 2, hdev->dev_name, name_len); + + ptr += (name_len + 2); + } + + if (hdev->inq_tx_power != HCI_TX_POWER_INVALID) { + ptr[0] = 2; + ptr[1] = EIR_TX_POWER; + ptr[2] = (u8) hdev->inq_tx_power; + + ptr += 3; + } + + if (hdev->devid_source > 0) { + ptr[0] = 9; + ptr[1] = EIR_DEVICE_ID; + + put_unaligned_le16(hdev->devid_source, ptr + 2); + put_unaligned_le16(hdev->devid_vendor, ptr + 4); + put_unaligned_le16(hdev->devid_product, ptr + 6); + put_unaligned_le16(hdev->devid_version, ptr + 8); + + ptr += 10; + } + + ptr = create_uuid16_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data)); + ptr = create_uuid32_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data)); + ptr = create_uuid128_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data)); +} + +void __hci_req_update_eir(struct hci_request *req) +{ + struct hci_dev *hdev = req->hdev; + struct hci_cp_write_eir cp; + + if (!hdev_is_powered(hdev)) + return; + + if (!lmp_ext_inq_capable(hdev)) + return; + + if (!hci_dev_test_flag(hdev, HCI_SSP_ENABLED)) + return; + + if (hci_dev_test_flag(hdev, HCI_SERVICE_CACHE)) + return; + + memset(&cp, 0, sizeof(cp)); + + create_eir(hdev, cp.data); + + if (memcmp(cp.data, hdev->eir, sizeof(cp.data)) == 0) + return; + + memcpy(hdev->eir, cp.data, sizeof(cp.data)); + + hci_req_add(req, HCI_OP_WRITE_EIR, sizeof(cp), &cp); +} + void hci_req_add_le_scan_disable(struct hci_request *req) { struct hci_cp_le_set_scan_enable cp; diff --git a/net/bluetooth/hci_request.h b/net/bluetooth/hci_request.h index 4e65a9c..5af4039 100644 --- a/net/bluetooth/hci_request.h +++ b/net/bluetooth/hci_request.h @@ -56,6 +56,7 @@ struct sk_buff *hci_prepare_cmd(struct hci_dev *hdev, u16 opcode, u32 plen, const void *param); void __hci_req_update_name(struct hci_request *req); +void __hci_req_update_eir(struct hci_request *req); void hci_req_add_le_scan_disable(struct hci_request *req); void hci_req_add_le_passive_scan(struct hci_request *req); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 001a29a..fa5dc67 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -719,116 +719,6 @@ static u32 get_current_settings(struct hci_dev *hdev) return settings; } -#define PNP_INFO_SVCLASS_ID 0x1200 - -static u8 *create_uuid16_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len) -{ - u8 *ptr = data, *uuids_start = NULL; - struct bt_uuid *uuid; - - if (len < 4) - return ptr; - - list_for_each_entry(uuid, &hdev->uuids, list) { - u16 uuid16; - - if (uuid->size != 16) - continue; - - uuid16 = get_unaligned_le16(&uuid->uuid[12]); - if (uuid16 < 0x1100) - continue; - - if (uuid16 == PNP_INFO_SVCLASS_ID) - continue; - - if (!uuids_start) { - uuids_start = ptr; - uuids_start[0] = 1; - uuids_start[1] = EIR_UUID16_ALL; - ptr += 2; - } - - /* Stop if not enough space to put next UUID */ - if ((ptr - data) + sizeof(u16) > len) { - uuids_start[1] = EIR_UUID16_SOME; - break; - } - - *ptr++ = (uuid16 & 0x00ff); - *ptr++ = (uuid16 & 0xff00) >> 8; - uuids_start[0] += sizeof(uuid16); - } - - return ptr; -} - -static u8 *create_uuid32_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len) -{ - u8 *ptr = data, *uuids_start = NULL; - struct bt_uuid *uuid; - - if (len < 6) - return ptr; - - list_for_each_entry(uuid, &hdev->uuids, list) { - if (uuid->size != 32) - continue; - - if (!uuids_start) { - uuids_start = ptr; - uuids_start[0] = 1; - uuids_start[1] = EIR_UUID32_ALL; - ptr += 2; - } - - /* Stop if not enough space to put next UUID */ - if ((ptr - data) + sizeof(u32) > len) { - uuids_start[1] = EIR_UUID32_SOME; - break; - } - - memcpy(ptr, &uuid->uuid[12], sizeof(u32)); - ptr += sizeof(u32); - uuids_start[0] += sizeof(u32); - } - - return ptr; -} - -static u8 *create_uuid128_list(struct hci_dev *hdev, u8 *data, ptrdiff_t len) -{ - u8 *ptr = data, *uuids_start = NULL; - struct bt_uuid *uuid; - - if (len < 18) - return ptr; - - list_for_each_entry(uuid, &hdev->uuids, list) { - if (uuid->size != 128) - continue; - - if (!uuids_start) { - uuids_start = ptr; - uuids_start[0] = 1; - uuids_start[1] = EIR_UUID128_ALL; - ptr += 2; - } - - /* Stop if not enough space to put next UUID */ - if ((ptr - data) + 16 > len) { - uuids_start[1] = EIR_UUID128_SOME; - break; - } - - memcpy(ptr, uuid->uuid, 16); - ptr += 16; - uuids_start[0] += 16; - } - - return ptr; -} - static struct mgmt_pending_cmd *pending_find(u16 opcode, struct hci_dev *hdev) { return mgmt_pending_find(HCI_CHANNEL_CONTROL, opcode, hdev); @@ -882,83 +772,6 @@ bool mgmt_get_connectable(struct hci_dev *hdev) return hci_dev_test_flag(hdev, HCI_CONNECTABLE); } -static void create_eir(struct hci_dev *hdev, u8 *data) -{ - u8 *ptr = data; - size_t name_len; - - name_len = strlen(hdev->dev_name); - - if (name_len > 0) { - /* EIR Data type */ - if (name_len > 48) { - name_len = 48; - ptr[1] = EIR_NAME_SHORT; - } else - ptr[1] = EIR_NAME_COMPLETE; - - /* EIR Data length */ - ptr[0] = name_len + 1; - - memcpy(ptr + 2, hdev->dev_name, name_len); - - ptr += (name_len + 2); - } - - if (hdev->inq_tx_power != HCI_TX_POWER_INVALID) { - ptr[0] = 2; - ptr[1] = EIR_TX_POWER; - ptr[2] = (u8) hdev->inq_tx_power; - - ptr += 3; - } - - if (hdev->devid_source > 0) { - ptr[0] = 9; - ptr[1] = EIR_DEVICE_ID; - - put_unaligned_le16(hdev->devid_source, ptr + 2); - put_unaligned_le16(hdev->devid_vendor, ptr + 4); - put_unaligned_le16(hdev->devid_product, ptr + 6); - put_unaligned_le16(hdev->devid_version, ptr + 8); - - ptr += 10; - } - - ptr = create_uuid16_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data)); - ptr = create_uuid32_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data)); - ptr = create_uuid128_list(hdev, ptr, HCI_MAX_EIR_LENGTH - (ptr - data)); -} - -static void update_eir(struct hci_request *req) -{ - struct hci_dev *hdev = req->hdev; - struct hci_cp_write_eir cp; - - if (!hdev_is_powered(hdev)) - return; - - if (!lmp_ext_inq_capable(hdev)) - return; - - if (!hci_dev_test_flag(hdev, HCI_SSP_ENABLED)) - return; - - if (hci_dev_test_flag(hdev, HCI_SERVICE_CACHE)) - return; - - memset(&cp, 0, sizeof(cp)); - - create_eir(hdev, cp.data); - - if (memcmp(cp.data, hdev->eir, sizeof(cp.data)) == 0) - return; - - memcpy(hdev->eir, cp.data, sizeof(cp.data)); - - hci_req_add(req, HCI_OP_WRITE_EIR, sizeof(cp), &cp); -} - static void service_cache_off(struct work_struct *work) { struct hci_dev *hdev = container_of(work, struct hci_dev, @@ -972,7 +785,7 @@ static void service_cache_off(struct work_struct *work) hci_dev_lock(hdev); - update_eir(&req); + __hci_req_update_eir(&req); __hci_req_update_class(&req); hci_dev_unlock(hdev); @@ -2074,7 +1887,7 @@ static int add_uuid(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) hci_req_init(&req, hdev); __hci_req_update_class(&req); - update_eir(&req); + __hci_req_update_eir(&req); err = hci_req_run(&req, add_uuid_complete); if (err < 0) { @@ -2174,7 +1987,7 @@ update_class: hci_req_init(&req, hdev); __hci_req_update_class(&req); - update_eir(&req); + __hci_req_update_eir(&req); err = hci_req_run(&req, remove_uuid_complete); if (err < 0) { @@ -2249,7 +2062,7 @@ static int set_dev_class(struct sock *sk, struct hci_dev *hdev, void *data, hci_dev_unlock(hdev); cancel_delayed_work_sync(&hdev->service_cache); hci_dev_lock(hdev); - update_eir(&req); + __hci_req_update_eir(&req); } __hci_req_update_class(&req); @@ -3232,7 +3045,7 @@ static int set_local_name(struct sock *sk, struct hci_dev *hdev, void *data, if (lmp_bredr_capable(hdev)) { __hci_req_update_name(&req); - update_eir(&req); + __hci_req_update_eir(&req); } /* The name is stored in the scan response data and so @@ -3917,7 +3730,7 @@ static int set_device_id(struct sock *sk, struct hci_dev *hdev, void *data, NULL, 0); hci_req_init(&req, hdev); - update_eir(&req); + __hci_req_update_eir(&req); hci_req_run(&req, NULL); hci_dev_unlock(hdev); @@ -6759,7 +6572,7 @@ static int powered_update_hci(struct hci_dev *hdev) __hci_req_update_scan(&req); __hci_req_update_class(&req); __hci_req_update_name(&req); - update_eir(&req); + __hci_req_update_eir(&req); } return hci_req_run(&req, powered_complete); @@ -7380,7 +7193,7 @@ void mgmt_ssp_enable_complete(struct hci_dev *hdev, u8 enable, u8 status) if (hci_dev_test_flag(hdev, HCI_USE_DEBUG_KEYS)) hci_req_add(&req, HCI_OP_WRITE_SSP_DEBUG_MODE, sizeof(enable), &enable); - update_eir(&req); + __hci_req_update_eir(&req); } else { clear_eir(&req); } -- cgit v0.10.2 From bf943cbf76ecd3b9838a80d5e08777b0f4ccc665 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Wed, 25 Nov 2015 16:15:43 +0200 Subject: Bluetooth: Move fast connectable code to hci_request.c We'll soon need this both in hci_request.c and mgmt.c so move it to hci_request.c as a generic helper. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index 030a1bb..0abd83d 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -349,6 +349,41 @@ void hci_req_add(struct hci_request *req, u16 opcode, u32 plen, hci_req_add_ev(req, opcode, plen, param, 0); } +void __hci_req_write_fast_connectable(struct hci_request *req, bool enable) +{ + struct hci_dev *hdev = req->hdev; + struct hci_cp_write_page_scan_activity acp; + u8 type; + + if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) + return; + + if (hdev->hci_ver < BLUETOOTH_VER_1_2) + return; + + if (enable) { + type = PAGE_SCAN_TYPE_INTERLACED; + + /* 160 msec page scan interval */ + acp.interval = cpu_to_le16(0x0100); + } else { + type = PAGE_SCAN_TYPE_STANDARD; /* default */ + + /* default 1.28 sec page scan */ + acp.interval = cpu_to_le16(0x0800); + } + + acp.window = cpu_to_le16(0x0012); + + if (__cpu_to_le16(hdev->page_scan_interval) != acp.interval || + __cpu_to_le16(hdev->page_scan_window) != acp.window) + hci_req_add(req, HCI_OP_WRITE_PAGE_SCAN_ACTIVITY, + sizeof(acp), &acp); + + if (hdev->page_scan_type != type) + hci_req_add(req, HCI_OP_WRITE_PAGE_SCAN_TYPE, 1, &type); +} + /* This function controls the background scanning based on hdev->pend_le_conns * list. If there are pending LE connection we start the background scanning, * otherwise we stop it. diff --git a/net/bluetooth/hci_request.h b/net/bluetooth/hci_request.h index 5af4039..d3dd24d 100644 --- a/net/bluetooth/hci_request.h +++ b/net/bluetooth/hci_request.h @@ -55,6 +55,7 @@ void hci_req_sync_cancel(struct hci_dev *hdev, int err); struct sk_buff *hci_prepare_cmd(struct hci_dev *hdev, u16 opcode, u32 plen, const void *param); +void __hci_req_write_fast_connectable(struct hci_request *req, bool enable); void __hci_req_update_name(struct hci_request *req); void __hci_req_update_eir(struct hci_request *req); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index fa5dc67..0a7e6f4 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1254,41 +1254,6 @@ failed: return err; } -static void write_fast_connectable(struct hci_request *req, bool enable) -{ - struct hci_dev *hdev = req->hdev; - struct hci_cp_write_page_scan_activity acp; - u8 type; - - if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) - return; - - if (hdev->hci_ver < BLUETOOTH_VER_1_2) - return; - - if (enable) { - type = PAGE_SCAN_TYPE_INTERLACED; - - /* 160 msec page scan interval */ - acp.interval = cpu_to_le16(0x0100); - } else { - type = PAGE_SCAN_TYPE_STANDARD; /* default */ - - /* default 1.28 sec page scan */ - acp.interval = cpu_to_le16(0x0800); - } - - acp.window = cpu_to_le16(0x0012); - - if (__cpu_to_le16(hdev->page_scan_interval) != acp.interval || - __cpu_to_le16(hdev->page_scan_window) != acp.window) - hci_req_add(req, HCI_OP_WRITE_PAGE_SCAN_ACTIVITY, - sizeof(acp), &acp); - - if (hdev->page_scan_type != type) - hci_req_add(req, HCI_OP_WRITE_PAGE_SCAN_TYPE, 1, &type); -} - void mgmt_set_connectable_complete(struct hci_dev *hdev, u8 status) { struct mgmt_pending_cmd *cmd; @@ -4094,7 +4059,7 @@ static int set_fast_connectable(struct sock *sk, struct hci_dev *hdev, hci_req_init(&req, hdev); - write_fast_connectable(&req, cp->val); + __hci_req_write_fast_connectable(&req, cp->val); err = hci_req_run(&req, fast_connectable_complete); if (err < 0) { @@ -4236,7 +4201,7 @@ static int set_bredr(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) hci_req_init(&req, hdev); - write_fast_connectable(&req, false); + __hci_req_write_fast_connectable(&req, false); __hci_req_update_scan(&req); /* Since only the advertising data flags will change, there @@ -6566,9 +6531,9 @@ static int powered_update_hci(struct hci_dev *hdev) if (lmp_bredr_capable(hdev)) { if (hci_dev_test_flag(hdev, HCI_FAST_CONNECTABLE)) - write_fast_connectable(&req, true); + __hci_req_write_fast_connectable(&req, true); else - write_fast_connectable(&req, false); + __hci_req_write_fast_connectable(&req, false); __hci_req_update_scan(&req); __hci_req_update_class(&req); __hci_req_update_name(&req); -- cgit v0.10.2 From 2ff13894cfb877cb3d02d96a8402202f0a6f3efd Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Wed, 25 Nov 2015 16:15:44 +0200 Subject: Bluetooth: Perform HCI update for power on synchronously The request to update HCI during power on is always coming either from hdev->req_workqueue or through an ioctl, so it's safe to use hci_req_sync for it. This way we also eliminate potential races with incoming mgmt commands or other actions while powering on. Part of this refactoring is the splitting of mgmt_powered() into mgmt_power_on() and __mgmt_power_off() functions. The main reason is the different requirements as far as hdev locking is concerned, as highlighted with the __ prefix of the power off API. Since the power on in the case of clearing the AUTO_OFF flag cannot be done synchronously in the set_powered mgmt handler, the hci_power_on work callback is extended to cover this (which also simplifies the set_powered helper a lot). Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 319bf02..c95e032 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1435,7 +1435,8 @@ int mgmt_new_settings(struct hci_dev *hdev); void mgmt_index_added(struct hci_dev *hdev); void mgmt_index_removed(struct hci_dev *hdev); void mgmt_set_powered_failed(struct hci_dev *hdev, int err); -int mgmt_powered(struct hci_dev *hdev, u8 powered); +void mgmt_power_on(struct hci_dev *hdev, int err); +void __mgmt_power_off(struct hci_dev *hdev); void mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key, bool persistent); void mgmt_device_connected(struct hci_dev *hdev, struct hci_conn *conn, diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 484c75f..eac3f6f 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -1399,10 +1399,10 @@ static int hci_dev_do_open(struct hci_dev *hdev) !hci_dev_test_flag(hdev, HCI_CONFIG) && !hci_dev_test_flag(hdev, HCI_UNCONFIGURED) && !hci_dev_test_flag(hdev, HCI_USER_CHANNEL) && + hci_dev_test_flag(hdev, HCI_MGMT) && hdev->dev_type == HCI_BREDR) { - hci_dev_lock(hdev); - mgmt_powered(hdev, 1); - hci_dev_unlock(hdev); + ret = __hci_req_hci_power_on(hdev); + mgmt_power_on(hdev, ret); } } else { /* Init failed, cleanup */ @@ -1559,8 +1559,9 @@ int hci_dev_do_close(struct hci_dev *hdev) auto_off = hci_dev_test_and_clear_flag(hdev, HCI_AUTO_OFF); - if (!auto_off && hdev->dev_type == HCI_BREDR) - mgmt_powered(hdev, 0); + if (!auto_off && hdev->dev_type == HCI_BREDR && + hci_dev_test_flag(hdev, HCI_MGMT)) + __mgmt_power_off(hdev); hci_inquiry_cache_flush(hdev); hci_pend_le_actions_clear(hdev); @@ -2013,6 +2014,16 @@ static void hci_power_on(struct work_struct *work) BT_DBG("%s", hdev->name); + if (test_bit(HCI_UP, &hdev->flags) && + hci_dev_test_flag(hdev, HCI_MGMT) && + hci_dev_test_and_clear_flag(hdev, HCI_AUTO_OFF)) { + hci_req_sync_lock(hdev); + err = __hci_req_hci_power_on(hdev); + hci_req_sync_unlock(hdev); + mgmt_power_on(hdev, err); + return; + } + err = hci_dev_do_open(hdev); if (err < 0) { hci_dev_lock(hdev); diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index 0abd83d..7cc24f1 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -2181,6 +2181,106 @@ static void discov_off(struct work_struct *work) mgmt_new_settings(hdev); } +static int powered_update_hci(struct hci_request *req, unsigned long opt) +{ + struct hci_dev *hdev = req->hdev; + struct adv_info *adv_instance; + u8 link_sec; + + hci_dev_lock(hdev); + + if (hci_dev_test_flag(hdev, HCI_SSP_ENABLED) && + !lmp_host_ssp_capable(hdev)) { + u8 mode = 0x01; + + hci_req_add(req, HCI_OP_WRITE_SSP_MODE, sizeof(mode), &mode); + + if (bredr_sc_enabled(hdev) && !lmp_host_sc_capable(hdev)) { + u8 support = 0x01; + + hci_req_add(req, HCI_OP_WRITE_SC_SUPPORT, + sizeof(support), &support); + } + } + + if (hci_dev_test_flag(hdev, HCI_LE_ENABLED) && + lmp_bredr_capable(hdev)) { + struct hci_cp_write_le_host_supported cp; + + cp.le = 0x01; + cp.simul = 0x00; + + /* Check first if we already have the right + * host state (host features set) + */ + if (cp.le != lmp_host_le_capable(hdev) || + cp.simul != lmp_host_le_br_capable(hdev)) + hci_req_add(req, HCI_OP_WRITE_LE_HOST_SUPPORTED, + sizeof(cp), &cp); + } + + if (lmp_le_capable(hdev)) { + /* Make sure the controller has a good default for + * advertising data. This also applies to the case + * where BR/EDR was toggled during the AUTO_OFF phase. + */ + if (hci_dev_test_flag(hdev, HCI_LE_ENABLED) && + (hci_dev_test_flag(hdev, HCI_ADVERTISING) || + !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))) { + __hci_req_update_adv_data(req, HCI_ADV_CURRENT); + __hci_req_update_scan_rsp_data(req, HCI_ADV_CURRENT); + } + + if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) && + hdev->cur_adv_instance == 0x00 && + !list_empty(&hdev->adv_instances)) { + adv_instance = list_first_entry(&hdev->adv_instances, + struct adv_info, list); + hdev->cur_adv_instance = adv_instance->instance; + } + + if (hci_dev_test_flag(hdev, HCI_ADVERTISING)) + __hci_req_enable_advertising(req); + else if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) && + hdev->cur_adv_instance) + __hci_req_schedule_adv_instance(req, + hdev->cur_adv_instance, + true); + } + + link_sec = hci_dev_test_flag(hdev, HCI_LINK_SECURITY); + if (link_sec != test_bit(HCI_AUTH, &hdev->flags)) + hci_req_add(req, HCI_OP_WRITE_AUTH_ENABLE, + sizeof(link_sec), &link_sec); + + if (lmp_bredr_capable(hdev)) { + if (hci_dev_test_flag(hdev, HCI_FAST_CONNECTABLE)) + __hci_req_write_fast_connectable(req, true); + else + __hci_req_write_fast_connectable(req, false); + __hci_req_update_scan(req); + __hci_req_update_class(req); + __hci_req_update_name(req); + __hci_req_update_eir(req); + } + + hci_dev_unlock(hdev); + return 0; +} + +int __hci_req_hci_power_on(struct hci_dev *hdev) +{ + /* Register the available SMP channels (BR/EDR and LE) only when + * successfully powering on the controller. This late + * registration is required so that LE SMP can clearly decide if + * the public address or static address is used. + */ + smp_register(hdev); + + return __hci_req_sync(hdev, powered_update_hci, 0, HCI_CMD_TIMEOUT, + NULL); +} + void hci_request_setup(struct hci_dev *hdev) { INIT_WORK(&hdev->discov_update, discov_update); diff --git a/net/bluetooth/hci_request.h b/net/bluetooth/hci_request.h index d3dd24d..a24d3b5 100644 --- a/net/bluetooth/hci_request.h +++ b/net/bluetooth/hci_request.h @@ -55,6 +55,8 @@ void hci_req_sync_cancel(struct hci_dev *hdev, int err); struct sk_buff *hci_prepare_cmd(struct hci_dev *hdev, u16 opcode, u32 plen, const void *param); +int __hci_req_hci_power_on(struct hci_dev *hdev); + void __hci_req_write_fast_connectable(struct hci_request *req, bool enable); void __hci_req_update_name(struct hci_request *req); void __hci_req_update_eir(struct hci_request *req); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 0a7e6f4..468402a 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -961,17 +961,6 @@ static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data, goto failed; } - if (hci_dev_test_and_clear_flag(hdev, HCI_AUTO_OFF)) { - cancel_delayed_work(&hdev->power_off); - - if (cp->val) { - mgmt_pending_add(sk, MGMT_OP_SET_POWERED, hdev, - data, len); - err = mgmt_powered(hdev, 1); - goto failed; - } - } - if (!!cp->val == hdev_is_powered(hdev)) { err = send_settings_rsp(sk, MGMT_OP_SET_POWERED, hdev); goto failed; @@ -6434,139 +6423,33 @@ static void restart_le_actions(struct hci_dev *hdev) } } -static void powered_complete(struct hci_dev *hdev, u8 status, u16 opcode) +void mgmt_power_on(struct hci_dev *hdev, int err) { struct cmd_lookup match = { NULL, hdev }; - BT_DBG("status 0x%02x", status); + BT_DBG("err %d", err); - if (!status) { + hci_dev_lock(hdev); + + if (!err) { restart_le_actions(hdev); hci_update_background_scan(hdev); } - hci_dev_lock(hdev); - mgmt_pending_foreach(MGMT_OP_SET_POWERED, hdev, settings_rsp, &match); new_settings(hdev, match.sk); - hci_dev_unlock(hdev); - if (match.sk) sock_put(match.sk); -} -static int powered_update_hci(struct hci_dev *hdev) -{ - struct hci_request req; - struct adv_info *adv_instance; - u8 link_sec; - - hci_req_init(&req, hdev); - - if (hci_dev_test_flag(hdev, HCI_SSP_ENABLED) && - !lmp_host_ssp_capable(hdev)) { - u8 mode = 0x01; - - hci_req_add(&req, HCI_OP_WRITE_SSP_MODE, sizeof(mode), &mode); - - if (bredr_sc_enabled(hdev) && !lmp_host_sc_capable(hdev)) { - u8 support = 0x01; - - hci_req_add(&req, HCI_OP_WRITE_SC_SUPPORT, - sizeof(support), &support); - } - } - - if (hci_dev_test_flag(hdev, HCI_LE_ENABLED) && - lmp_bredr_capable(hdev)) { - struct hci_cp_write_le_host_supported cp; - - cp.le = 0x01; - cp.simul = 0x00; - - /* Check first if we already have the right - * host state (host features set) - */ - if (cp.le != lmp_host_le_capable(hdev) || - cp.simul != lmp_host_le_br_capable(hdev)) - hci_req_add(&req, HCI_OP_WRITE_LE_HOST_SUPPORTED, - sizeof(cp), &cp); - } - - if (lmp_le_capable(hdev)) { - /* Make sure the controller has a good default for - * advertising data. This also applies to the case - * where BR/EDR was toggled during the AUTO_OFF phase. - */ - if (hci_dev_test_flag(hdev, HCI_LE_ENABLED) && - (hci_dev_test_flag(hdev, HCI_ADVERTISING) || - !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))) { - __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); - __hci_req_update_scan_rsp_data(&req, HCI_ADV_CURRENT); - } - - if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) && - hdev->cur_adv_instance == 0x00 && - !list_empty(&hdev->adv_instances)) { - adv_instance = list_first_entry(&hdev->adv_instances, - struct adv_info, list); - hdev->cur_adv_instance = adv_instance->instance; - } - - if (hci_dev_test_flag(hdev, HCI_ADVERTISING)) - __hci_req_enable_advertising(&req); - else if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) && - hdev->cur_adv_instance) - __hci_req_schedule_adv_instance(&req, - hdev->cur_adv_instance, - true); - } - - link_sec = hci_dev_test_flag(hdev, HCI_LINK_SECURITY); - if (link_sec != test_bit(HCI_AUTH, &hdev->flags)) - hci_req_add(&req, HCI_OP_WRITE_AUTH_ENABLE, - sizeof(link_sec), &link_sec); - - if (lmp_bredr_capable(hdev)) { - if (hci_dev_test_flag(hdev, HCI_FAST_CONNECTABLE)) - __hci_req_write_fast_connectable(&req, true); - else - __hci_req_write_fast_connectable(&req, false); - __hci_req_update_scan(&req); - __hci_req_update_class(&req); - __hci_req_update_name(&req); - __hci_req_update_eir(&req); - } - - return hci_req_run(&req, powered_complete); + hci_dev_unlock(hdev); } -int mgmt_powered(struct hci_dev *hdev, u8 powered) +void __mgmt_power_off(struct hci_dev *hdev) { struct cmd_lookup match = { NULL, hdev }; u8 status, zero_cod[] = { 0, 0, 0 }; - int err; - - if (!hci_dev_test_flag(hdev, HCI_MGMT)) - return 0; - - if (powered) { - /* Register the available SMP channels (BR/EDR and LE) only - * when successfully powering on the controller. This late - * registration is required so that LE SMP can clearly - * decide if the public address or static address is used. - */ - smp_register(hdev); - - if (powered_update_hci(hdev) == 0) - return 0; - - mgmt_pending_foreach(MGMT_OP_SET_POWERED, hdev, settings_rsp, - &match); - goto new_settings; - } mgmt_pending_foreach(MGMT_OP_SET_POWERED, hdev, settings_rsp, &match); @@ -6588,13 +6471,10 @@ int mgmt_powered(struct hci_dev *hdev, u8 powered) mgmt_generic_event(MGMT_EV_CLASS_OF_DEV_CHANGED, hdev, zero_cod, sizeof(zero_cod), NULL); -new_settings: - err = new_settings(hdev, match.sk); + new_settings(hdev, match.sk); if (match.sk) sock_put(match.sk); - - return err; } void mgmt_set_powered_failed(struct hci_dev *hdev, int err) -- cgit v0.10.2 From 02c04afea93fbba7925984df455bc63e7d92da97 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Thu, 26 Nov 2015 12:15:58 +0200 Subject: Bluetooth: Simplify read_adv_features code The code in the Read Advertising Features mgmt command handler is unnecessarily complicated. Clean it up and remove unnecessary variables & branches. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 468402a..9ce2bb2 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -5788,10 +5788,10 @@ static int read_adv_features(struct sock *sk, struct hci_dev *hdev, { struct mgmt_rp_read_adv_features *rp; size_t rp_len; - int err, i; - bool instance; + int err; struct adv_info *adv_instance; u32 supported_flags; + u8 *instance; BT_DBG("%s", hdev->name); @@ -5801,12 +5801,7 @@ static int read_adv_features(struct sock *sk, struct hci_dev *hdev, hci_dev_lock(hdev); - rp_len = sizeof(*rp); - - instance = hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE); - if (instance) - rp_len += hdev->adv_instance_cnt; - + rp_len = sizeof(*rp) + hdev->adv_instance_cnt; rp = kmalloc(rp_len, GFP_ATOMIC); if (!rp) { hci_dev_unlock(hdev); @@ -5819,19 +5814,12 @@ static int read_adv_features(struct sock *sk, struct hci_dev *hdev, rp->max_adv_data_len = HCI_MAX_AD_LENGTH; rp->max_scan_rsp_len = HCI_MAX_AD_LENGTH; rp->max_instances = HCI_MAX_ADV_INSTANCES; + rp->num_instances = hdev->adv_instance_cnt; - if (instance) { - i = 0; - list_for_each_entry(adv_instance, &hdev->adv_instances, list) { - if (i >= hdev->adv_instance_cnt) - break; - - rp->instance[i] = adv_instance->instance; - i++; - } - rp->num_instances = hdev->adv_instance_cnt; - } else { - rp->num_instances = 0; + instance = rp->instance; + list_for_each_entry(adv_instance, &hdev->adv_instances, list) { + *instance = adv_instance->instance; + instance++; } hci_dev_unlock(hdev); -- cgit v0.10.2 From 17fd08ffb5981cff2c921eb479f46b872b02b2b9 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Thu, 26 Nov 2015 12:15:59 +0200 Subject: Bluetooth: Remove unnecessary HCI_ADVERTISING_INSTANCE flag This flag just tells us whether hdev->adv_instances is empty or not. We can equally well use the list_empty() function to get this information. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index cc22167..339ea57 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -239,7 +239,6 @@ enum { HCI_LE_ENABLED, HCI_ADVERTISING, HCI_ADVERTISING_CONNECTABLE, - HCI_ADVERTISING_INSTANCE, HCI_CONNECTABLE, HCI_DISCOVERABLE, HCI_LIMITED_DISCOVERABLE, diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index 7cc24f1..adfcd6f 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -822,7 +822,7 @@ static u8 get_current_adv_instance(struct hci_dev *hdev) * setting was set. When neither apply, default to the global settings, * represented by instance "0". */ - if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) && + if (!list_empty(&hdev->adv_instances) && !hci_dev_test_flag(hdev, HCI_ADVERTISING)) return hdev->cur_adv_instance; @@ -1144,7 +1144,7 @@ void hci_req_reenable_advertising(struct hci_dev *hdev) u8 instance; if (!hci_dev_test_flag(hdev, HCI_ADVERTISING) && - !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) + list_empty(&hdev->adv_instances)) return; instance = get_current_adv_instance(hdev); @@ -1202,7 +1202,7 @@ int __hci_req_schedule_adv_instance(struct hci_request *req, u8 instance, u16 timeout; if (hci_dev_test_flag(hdev, HCI_ADVERTISING) || - !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) + list_empty(&hdev->adv_instances)) return -EPERM; if (hdev->adv_instance_timeout) @@ -1319,10 +1319,8 @@ void hci_req_clear_adv_instance(struct hci_dev *hdev, struct hci_request *req, } } - if (list_empty(&hdev->adv_instances)) { + if (list_empty(&hdev->adv_instances)) hdev->cur_adv_instance = 0x00; - hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE); - } if (!req || !hdev_is_powered(hdev) || hci_dev_test_flag(hdev, HCI_ADVERTISING)) @@ -1525,7 +1523,7 @@ static int connectable_update(struct hci_request *req, unsigned long opt) /* Update the advertising parameters if necessary */ if (hci_dev_test_flag(hdev, HCI_ADVERTISING) || - hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) + !list_empty(&hdev->adv_instances)) __hci_req_enable_advertising(req); __hci_update_background_scan(req); @@ -2226,13 +2224,12 @@ static int powered_update_hci(struct hci_request *req, unsigned long opt) */ if (hci_dev_test_flag(hdev, HCI_LE_ENABLED) && (hci_dev_test_flag(hdev, HCI_ADVERTISING) || - !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))) { + list_empty(&hdev->adv_instances))) { __hci_req_update_adv_data(req, HCI_ADV_CURRENT); __hci_req_update_scan_rsp_data(req, HCI_ADV_CURRENT); } - if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) && - hdev->cur_adv_instance == 0x00 && + if (hdev->cur_adv_instance == 0x00 && !list_empty(&hdev->adv_instances)) { adv_instance = list_first_entry(&hdev->adv_instances, struct adv_info, list); @@ -2241,7 +2238,7 @@ static int powered_update_hci(struct hci_request *req, unsigned long opt) if (hci_dev_test_flag(hdev, HCI_ADVERTISING)) __hci_req_enable_advertising(req); - else if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) && + else if (!list_empty(&hdev->adv_instances) && hdev->cur_adv_instance) __hci_req_schedule_adv_instance(req, hdev->cur_adv_instance, diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 9ce2bb2..03a65e8 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -3734,7 +3734,6 @@ static void set_advertising_complete(struct hci_dev *hdev, u8 status, * set up earlier, then re-enable multi-instance advertising. */ if (hci_dev_test_flag(hdev, HCI_ADVERTISING) || - !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) || list_empty(&hdev->adv_instances)) goto unlock; @@ -5892,9 +5891,6 @@ static void add_advertising_complete(struct hci_dev *hdev, u8 status, cmd = pending_find(MGMT_OP_ADD_ADVERTISING, hdev); - if (status) - hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE); - list_for_each_entry_safe(adv_instance, n, &hdev->adv_instances, list) { if (!adv_instance->pending) continue; @@ -6012,8 +6008,6 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev, if (hdev->adv_instance_cnt > prev_instance_cnt) mgmt_advertising_added(sk, hdev, cp->instance); - hci_dev_set_flag(hdev, HCI_ADVERTISING_INSTANCE); - if (hdev->cur_adv_instance == cp->instance) { /* If the currently advertised instance is being changed then * cancel the current advertising and schedule the next @@ -6129,7 +6123,7 @@ static int remove_advertising(struct sock *sk, struct hci_dev *hdev, goto unlock; } - if (!hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE)) { + if (list_empty(&hdev->adv_instances)) { err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_REMOVE_ADVERTISING, MGMT_STATUS_INVALID_PARAMS); goto unlock; -- cgit v0.10.2 From 1d3a1e69adde1f854d3fdeda64019b54189a390c Mon Sep 17 00:00:00 2001 From: Andrei Emeltchenko Date: Thu, 26 Nov 2015 16:49:33 +0200 Subject: Bluetooth: Use hexadecimal notation for mask Using hexadecimal notation for mask makes code easier to read Signed-off-by: Andrei Emeltchenko Signed-off-by: Marcel Holtmann diff --git a/drivers/bluetooth/hci_h5.c b/drivers/bluetooth/hci_h5.c index db039f2..fc1a499 100644 --- a/drivers/bluetooth/hci_h5.c +++ b/drivers/bluetooth/hci_h5.c @@ -317,7 +317,7 @@ static void h5_handle_internal_rx(struct hci_uart *hu) h5_link_control(hu, conf_req, 3); } else if (memcmp(data, conf_rsp, 2) == 0) { if (H5_HDR_LEN(hdr) > 2) - h5->tx_win = (data[2] & 7); + h5->tx_win = (data[2] & 0x07); BT_DBG("Three-wire init complete. tx_win %u", h5->tx_win); h5->state = H5_ACTIVE; hci_uart_init_ready(hu); -- cgit v0.10.2 From 742c59516822f4a4bc23b0961d88c569a7f1bf71 Mon Sep 17 00:00:00 2001 From: Andrei Emeltchenko Date: Thu, 26 Nov 2015 16:49:34 +0200 Subject: Bluetooth: Simplify setting Configuration Field Only Sliding Window Size is used at the moment for H5 Bluetooth Configuration messages. Signed-off-by: Andrei Emeltchenko Signed-off-by: Marcel Holtmann diff --git a/drivers/bluetooth/hci_h5.c b/drivers/bluetooth/hci_h5.c index fc1a499..2d1c9f6 100644 --- a/drivers/bluetooth/hci_h5.c +++ b/drivers/bluetooth/hci_h5.c @@ -116,12 +116,8 @@ static void h5_link_control(struct hci_uart *hu, const void *data, size_t len) static u8 h5_cfg_field(struct h5 *h5) { - u8 field = 0; - /* Sliding window size (first 3 bits) */ - field |= (h5->tx_win & 0x07); - - return field; + return h5->tx_win & 0x07; } static void h5_timed_event(unsigned long arg) -- cgit v0.10.2 From d6dac32e84e407ba15f257b5df2f4cb263005ab4 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Fri, 27 Nov 2015 10:52:39 +0200 Subject: Bluetooth: Fix updating wrong instance's scan_rsp data The __hci_req_update_scan_rsp_data gets the instance to be updated which should get passed to update_inst_scan_rsp_data() instead of always enabling the current instance. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index adfcd6f..edf2199 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -1018,7 +1018,7 @@ void __hci_req_update_scan_rsp_data(struct hci_request *req, int instance) if (instance == HCI_ADV_CURRENT) instance = get_current_adv_instance(req->hdev); - update_inst_scan_rsp_data(req, get_current_adv_instance(req->hdev)); + update_inst_scan_rsp_data(req, instance); } static u8 create_instance_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr) -- cgit v0.10.2 From 550a8ca765a154ca38dcd888b4f12a173e761bdc Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Fri, 27 Nov 2015 11:11:52 +0200 Subject: Bluetooth: Remove redundant check for req.cmd_q The hci_req_run() function already checks for empty cmd_q and bails out if necessary. Also, req.cmd_q should really be treated as private data of the request and not accessed directly. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index edf2199..f1529d7 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -1187,8 +1187,7 @@ static void adv_timeout_expire(struct work_struct *work) if (list_empty(&hdev->adv_instances)) __hci_req_disable_advertising(&req); - if (!skb_queue_empty(&req.cmd_q)) - hci_req_run(&req, NULL); + hci_req_run(&req, NULL); unlock: hci_dev_unlock(hdev); -- cgit v0.10.2 From d6b7e2cddb72a87c2597af43ba9f5f2b03a2208b Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Mon, 30 Nov 2015 11:21:44 +0200 Subject: Bluetooth: Clean up advertising initialization in powered_update_hci() The logic in powered_update_hci() to initialize the advertising data & state is a bit more complicated than it needs to be. It was previously not doing anything if HCI_LE_ENABLED wasn't set, but this was not obvious by quickly looking at the code. Now the conditions for the various actions are more explicit. Another simplification is due to the fact that __hci_req_schedule_adv_instance() takes care of setting hdev->cur_adv_instance so there's no need to set it before calling the function. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index f1529d7..14db777 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -2181,7 +2181,6 @@ static void discov_off(struct work_struct *work) static int powered_update_hci(struct hci_request *req, unsigned long opt) { struct hci_dev *hdev = req->hdev; - struct adv_info *adv_instance; u8 link_sec; hci_dev_lock(hdev); @@ -2216,32 +2215,27 @@ static int powered_update_hci(struct hci_request *req, unsigned long opt) sizeof(cp), &cp); } - if (lmp_le_capable(hdev)) { + if (hci_dev_test_flag(hdev, HCI_LE_ENABLED)) { /* Make sure the controller has a good default for * advertising data. This also applies to the case * where BR/EDR was toggled during the AUTO_OFF phase. */ - if (hci_dev_test_flag(hdev, HCI_LE_ENABLED) && - (hci_dev_test_flag(hdev, HCI_ADVERTISING) || - list_empty(&hdev->adv_instances))) { - __hci_req_update_adv_data(req, HCI_ADV_CURRENT); - __hci_req_update_scan_rsp_data(req, HCI_ADV_CURRENT); - } + if (hci_dev_test_flag(hdev, HCI_ADVERTISING) || + list_empty(&hdev->adv_instances)) { + __hci_req_update_adv_data(req, 0x00); + __hci_req_update_scan_rsp_data(req, 0x00); + + if (hci_dev_test_flag(hdev, HCI_ADVERTISING)) + __hci_req_enable_advertising(req); + } else if (!list_empty(&hdev->adv_instances)) { + struct adv_info *adv_instance; - if (hdev->cur_adv_instance == 0x00 && - !list_empty(&hdev->adv_instances)) { adv_instance = list_first_entry(&hdev->adv_instances, struct adv_info, list); - hdev->cur_adv_instance = adv_instance->instance; - } - - if (hci_dev_test_flag(hdev, HCI_ADVERTISING)) - __hci_req_enable_advertising(req); - else if (!list_empty(&hdev->adv_instances) && - hdev->cur_adv_instance) __hci_req_schedule_adv_instance(req, - hdev->cur_adv_instance, + adv_instance->instance, true); + } } link_sec = hci_dev_test_flag(hdev, HCI_LINK_SECURITY); -- cgit v0.10.2 From cab054ab47fa3fdf1c597a9874363680bfdab33e Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Mon, 30 Nov 2015 11:21:45 +0200 Subject: Bluetooth: Clean up current advertising instance tracking We can simplify a lot of code by making sure hdev->cur_adv_instance is always up-to-date. This allows e.g. the removal of the get_current_adv_instance() helper function and the special HCI_ADV_CURRENT value. This patch also makes selecting instance 0x00 explicit in the various calls where advertising instances aren't enabled, e.g. when HCI_ADVERTISING is set or we've just finished enabling LE. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index eac3f6f..9fb443a 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -1769,7 +1769,7 @@ static void hci_update_scan_state(struct hci_dev *hdev, u8 scan) hci_dev_set_flag(hdev, HCI_BREDR_ENABLED); if (hci_dev_test_flag(hdev, HCI_LE_ENABLED)) - hci_req_update_adv_data(hdev, HCI_ADV_CURRENT); + hci_req_update_adv_data(hdev, hdev->cur_adv_instance); mgmt_new_settings(hdev); } @@ -2610,9 +2610,12 @@ int hci_remove_adv_instance(struct hci_dev *hdev, u8 instance) BT_DBG("%s removing %dMR", hdev->name, instance); - if (hdev->cur_adv_instance == instance && hdev->adv_instance_timeout) { - cancel_delayed_work(&hdev->adv_instance_expire); - hdev->adv_instance_timeout = 0; + if (hdev->cur_adv_instance == instance) { + if (hdev->adv_instance_timeout) { + cancel_delayed_work(&hdev->adv_instance_expire); + hdev->adv_instance_timeout = 0; + } + hdev->cur_adv_instance = 0x00; } list_del(&adv_instance->list); @@ -2639,6 +2642,7 @@ void hci_adv_instances_clear(struct hci_dev *hdev) } hdev->adv_instance_cnt = 0; + hdev->cur_adv_instance = 0x00; } /* This function requires the caller holds hdev->lock */ diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index 14db777..9997c31 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -815,23 +815,9 @@ void hci_req_add_le_passive_scan(struct hci_request *req) &enable_cp); } -static u8 get_current_adv_instance(struct hci_dev *hdev) -{ - /* The "Set Advertising" setting supersedes the "Add Advertising" - * setting. Here we set the advertising data based on which - * setting was set. When neither apply, default to the global settings, - * represented by instance "0". - */ - if (!list_empty(&hdev->adv_instances) && - !hci_dev_test_flag(hdev, HCI_ADVERTISING)) - return hdev->cur_adv_instance; - - return 0x00; -} - static u8 get_cur_adv_instance_scan_rsp_len(struct hci_dev *hdev) { - u8 instance = get_current_adv_instance(hdev); + u8 instance = hdev->cur_adv_instance; struct adv_info *adv_instance; /* Ignore instance 0 */ @@ -890,7 +876,6 @@ void __hci_req_enable_advertising(struct hci_request *req) struct hci_cp_le_set_adv_param cp; u8 own_addr_type, enable = 0x01; bool connectable; - u8 instance; u32 flags; if (hci_conn_num(hdev, LE_LINK) > 0) @@ -906,8 +891,7 @@ void __hci_req_enable_advertising(struct hci_request *req) */ hci_dev_clear_flag(hdev, HCI_LE_ADV); - instance = get_current_adv_instance(hdev); - flags = get_adv_instance_flags(hdev, instance); + flags = get_adv_instance_flags(hdev, hdev->cur_adv_instance); /* If the "connectable" instance flag was not set, then choose between * ADV_IND and ADV_NONCONN_IND based on the global connectable setting. @@ -985,7 +969,7 @@ static u8 create_instance_scan_rsp_data(struct hci_dev *hdev, u8 instance, return adv_instance->scan_rsp_len; } -static void update_inst_scan_rsp_data(struct hci_request *req, u8 instance) +void __hci_req_update_scan_rsp_data(struct hci_request *req, u8 instance) { struct hci_dev *hdev = req->hdev; struct hci_cp_le_set_scan_rsp_data cp; @@ -1013,14 +997,6 @@ static void update_inst_scan_rsp_data(struct hci_request *req, u8 instance) hci_req_add(req, HCI_OP_LE_SET_SCAN_RSP_DATA, sizeof(cp), &cp); } -void __hci_req_update_scan_rsp_data(struct hci_request *req, int instance) -{ - if (instance == HCI_ADV_CURRENT) - instance = get_current_adv_instance(req->hdev); - - update_inst_scan_rsp_data(req, instance); -} - static u8 create_instance_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr) { struct adv_info *adv_instance = NULL; @@ -1089,7 +1065,7 @@ static u8 create_instance_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr) return ad_len; } -static void update_inst_adv_data(struct hci_request *req, u8 instance) +void __hci_req_update_adv_data(struct hci_request *req, u8 instance) { struct hci_dev *hdev = req->hdev; struct hci_cp_le_set_adv_data cp; @@ -1115,15 +1091,7 @@ static void update_inst_adv_data(struct hci_request *req, u8 instance) hci_req_add(req, HCI_OP_LE_SET_ADV_DATA, sizeof(cp), &cp); } -void __hci_req_update_adv_data(struct hci_request *req, int instance) -{ - if (instance == HCI_ADV_CURRENT) - instance = get_current_adv_instance(req->hdev); - - update_inst_adv_data(req, instance); -} - -int hci_req_update_adv_data(struct hci_dev *hdev, int instance) +int hci_req_update_adv_data(struct hci_dev *hdev, u8 instance) { struct hci_request req; @@ -1141,21 +1109,19 @@ static void adv_enable_complete(struct hci_dev *hdev, u8 status, u16 opcode) void hci_req_reenable_advertising(struct hci_dev *hdev) { struct hci_request req; - u8 instance; if (!hci_dev_test_flag(hdev, HCI_ADVERTISING) && list_empty(&hdev->adv_instances)) return; - instance = get_current_adv_instance(hdev); - hci_req_init(&req, hdev); - if (instance) { - __hci_req_schedule_adv_instance(&req, instance, true); + if (hdev->cur_adv_instance) { + __hci_req_schedule_adv_instance(&req, hdev->cur_adv_instance, + true); } else { - __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); - __hci_req_update_scan_rsp_data(&req, HCI_ADV_CURRENT); + __hci_req_update_adv_data(&req, 0x00); + __hci_req_update_scan_rsp_data(&req, 0x00); __hci_req_enable_advertising(&req); } @@ -1176,7 +1142,7 @@ static void adv_timeout_expire(struct work_struct *work) hdev->adv_instance_timeout = 0; - instance = get_current_adv_instance(hdev); + instance = hdev->cur_adv_instance; if (instance == 0x00) goto unlock; @@ -1246,8 +1212,8 @@ int __hci_req_schedule_adv_instance(struct hci_request *req, u8 instance, return 0; hdev->cur_adv_instance = instance; - __hci_req_update_adv_data(req, HCI_ADV_CURRENT); - __hci_req_update_scan_rsp_data(req, HCI_ADV_CURRENT); + __hci_req_update_adv_data(req, instance); + __hci_req_update_scan_rsp_data(req, instance); __hci_req_enable_advertising(req); return 0; @@ -1301,7 +1267,6 @@ void hci_req_clear_adv_instance(struct hci_dev *hdev, struct hci_request *req, if (!err) mgmt_advertising_removed(NULL, hdev, rem_inst); } - hdev->cur_adv_instance = 0x00; } else { adv_instance = hci_find_adv_instance(hdev, instance); @@ -1318,9 +1283,6 @@ void hci_req_clear_adv_instance(struct hci_dev *hdev, struct hci_request *req, } } - if (list_empty(&hdev->adv_instances)) - hdev->cur_adv_instance = 0x00; - if (!req || !hdev_is_powered(hdev) || hci_dev_test_flag(hdev, HCI_ADVERTISING)) return; @@ -1518,7 +1480,7 @@ static int connectable_update(struct hci_request *req, unsigned long opt) * advertising flags. */ if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED)) - __hci_req_update_adv_data(req, HCI_ADV_CURRENT); + __hci_req_update_adv_data(req, hdev->cur_adv_instance); /* Update the advertising parameters if necessary */ if (hci_dev_test_flag(hdev, HCI_ADVERTISING) || @@ -1627,7 +1589,7 @@ static int discoverable_update(struct hci_request *req, unsigned long opt) * only update AD if advertising was enabled using Set Advertising. */ if (hci_dev_test_flag(hdev, HCI_ADVERTISING)) - __hci_req_update_adv_data(req, HCI_ADV_CURRENT); + __hci_req_update_adv_data(req, 0x00); hci_dev_unlock(hdev); diff --git a/net/bluetooth/hci_request.h b/net/bluetooth/hci_request.h index a24d3b5..64ff8c0 100644 --- a/net/bluetooth/hci_request.h +++ b/net/bluetooth/hci_request.h @@ -64,14 +64,12 @@ void __hci_req_update_eir(struct hci_request *req); void hci_req_add_le_scan_disable(struct hci_request *req); void hci_req_add_le_passive_scan(struct hci_request *req); -#define HCI_ADV_CURRENT (-1) - void hci_req_reenable_advertising(struct hci_dev *hdev); void __hci_req_enable_advertising(struct hci_request *req); void __hci_req_disable_advertising(struct hci_request *req); -void __hci_req_update_adv_data(struct hci_request *req, int instance); -int hci_req_update_adv_data(struct hci_dev *hdev, int instance); -void __hci_req_update_scan_rsp_data(struct hci_request *req, int instance); +void __hci_req_update_adv_data(struct hci_request *req, u8 instance); +int hci_req_update_adv_data(struct hci_dev *hdev, u8 instance); +void __hci_req_update_scan_rsp_data(struct hci_request *req, u8 instance); int __hci_req_schedule_adv_instance(struct hci_request *req, u8 instance, bool force); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 03a65e8..621f6fd 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1626,8 +1626,8 @@ static void le_enable_complete(struct hci_dev *hdev, u8 status, u16 opcode) struct hci_request req; hci_req_init(&req, hdev); - __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); - __hci_req_update_scan_rsp_data(&req, HCI_ADV_CURRENT); + __hci_req_update_adv_data(&req, 0x00); + __hci_req_update_scan_rsp_data(&req, 0x00); hci_req_run(&req, NULL); hci_update_background_scan(hdev); } @@ -3006,7 +3006,7 @@ static int set_local_name(struct sock *sk, struct hci_dev *hdev, void *data, * no need to udpate the advertising data here. */ if (lmp_le_capable(hdev)) - __hci_req_update_scan_rsp_data(&req, HCI_ADV_CURRENT); + __hci_req_update_scan_rsp_data(&req, hdev->cur_adv_instance); err = hci_req_run(&req, set_name_complete); if (err < 0) @@ -3799,6 +3799,7 @@ static int set_advertising(struct sock *sk, struct hci_dev *hdev, void *data, bool changed; if (cp->val) { + hdev->cur_adv_instance = 0x00; changed = !hci_dev_test_and_set_flag(hdev, HCI_ADVERTISING); if (cp->val == 0x02) hci_dev_set_flag(hdev, HCI_ADVERTISING_CONNECTABLE); @@ -3846,6 +3847,7 @@ static int set_advertising(struct sock *sk, struct hci_dev *hdev, void *data, * We cannot use update_[adv|scan_rsp]_data() here as the * HCI_ADVERTISING flag is not yet set. */ + hdev->cur_adv_instance = 0x00; __hci_req_update_adv_data(&req, 0x00); __hci_req_update_scan_rsp_data(&req, 0x00); __hci_req_enable_advertising(&req); @@ -4195,7 +4197,7 @@ static int set_bredr(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) /* Since only the advertising data flags will change, there * is no need to update the scan response data. */ - __hci_req_update_adv_data(&req, HCI_ADV_CURRENT); + __hci_req_update_adv_data(&req, hdev->cur_adv_instance); err = hci_req_run(&req, set_bredr_complete); if (err < 0) -- cgit v0.10.2 From e6e981c74234a6f4e1414ba11ca9746850f261a9 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Fri, 27 Nov 2015 16:39:00 -0500 Subject: Bluetooth: hci_ldisc: Remove dead code The N_HCI ldisc does not define a flush_buffer() ldisc method, so the check when opening the ldisc is always false. Signed-off-by: Peter Hurley Signed-off-by: Marcel Holtmann diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c index 03146d7..73202624 100644 --- a/drivers/bluetooth/hci_ldisc.c +++ b/drivers/bluetooth/hci_ldisc.c @@ -462,13 +462,7 @@ static int hci_uart_tty_open(struct tty_struct *tty) INIT_WORK(&hu->init_ready, hci_uart_init_work); INIT_WORK(&hu->write_work, hci_uart_write_work); - /* Flush any pending characters in the driver and line discipline. */ - - /* FIXME: why is this needed. Note don't use ldisc_ref here as the - open path is before the ldisc is referencable */ - - if (tty->ldisc->ops->flush_buffer) - tty->ldisc->ops->flush_buffer(tty); + /* Flush any pending characters in the driver */ tty_driver_flush_buffer(tty); return 0; -- cgit v0.10.2 From a0c38245153abe1fd844af9b166d1a5d5dafe7b1 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Fri, 27 Nov 2015 19:55:59 +0100 Subject: Bluetooth: hci_intel: Use shorter timeout for HCI commands Use the standard HCI_CMD_TIMEOUT(1s) for HCI command instead of HCI_INIT_TIMEOUT(10s) which is not justified in these cases. Signed-off-by: Loic Poulain Signed-off-by: Marcel Holtmann diff --git a/drivers/bluetooth/hci_intel.c b/drivers/bluetooth/hci_intel.c index 69760e2..3c28c4a 100644 --- a/drivers/bluetooth/hci_intel.c +++ b/drivers/bluetooth/hci_intel.c @@ -502,7 +502,7 @@ static int intel_set_baudrate(struct hci_uart *hu, unsigned int speed) /* Device will not accept speed change if Intel version has not been * previously requested. */ - skb = __hci_cmd_sync(hdev, 0xfc05, 0, NULL, HCI_INIT_TIMEOUT); + skb = __hci_cmd_sync(hdev, 0xfc05, 0, NULL, HCI_CMD_TIMEOUT); if (IS_ERR(skb)) { bt_dev_err(hdev, "Reading Intel version information failed (%ld)", PTR_ERR(skb)); @@ -590,7 +590,7 @@ static int intel_setup(struct hci_uart *hu) * is in bootloader mode or if it already has operational firmware * loaded. */ - skb = __hci_cmd_sync(hdev, 0xfc05, 0, NULL, HCI_INIT_TIMEOUT); + skb = __hci_cmd_sync(hdev, 0xfc05, 0, NULL, HCI_CMD_TIMEOUT); if (IS_ERR(skb)) { bt_dev_err(hdev, "Reading Intel version information failed (%ld)", PTR_ERR(skb)); @@ -671,7 +671,7 @@ static int intel_setup(struct hci_uart *hu) /* Read the secure boot parameters to identify the operating * details of the bootloader. */ - skb = __hci_cmd_sync(hdev, 0xfc0d, 0, NULL, HCI_INIT_TIMEOUT); + skb = __hci_cmd_sync(hdev, 0xfc0d, 0, NULL, HCI_CMD_TIMEOUT); if (IS_ERR(skb)) { bt_dev_err(hdev, "Reading Intel boot parameters failed (%ld)", PTR_ERR(skb)); @@ -881,7 +881,7 @@ done: set_bit(STATE_BOOTING, &intel->flags); skb = __hci_cmd_sync(hdev, 0xfc01, sizeof(reset_param), reset_param, - HCI_INIT_TIMEOUT); + HCI_CMD_TIMEOUT); if (IS_ERR(skb)) return PTR_ERR(skb); -- cgit v0.10.2 From 1623d0bf847d3b38d8cf24367b3689ba0e3fe2aa Mon Sep 17 00:00:00 2001 From: Dmitry Tunin Date: Sat, 5 Dec 2015 14:09:36 +0300 Subject: Bluetooth: Add support of Toshiba Broadcom based devices BugLink: https://bugs.launchpad.net/bugs/1522949 T: Bus=03 Lev=02 Prnt=02 Port=05 Cnt=02 Dev#= 4 Spd=12 MxCh= 0 D: Ver= 2.00 Cls=ff(vend.) Sub=01 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=0930 ProdID=0225 Rev=01.12 S: Manufacturer=Broadcom Corp S: Product=BCM43142A0 S: SerialNumber=4CBB58034671 C: #Ifs= 4 Cfg#= 1 Atr=e0 MxPwr=0mA I: If#= 0 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=01 Prot=01 Driver=(none) I: If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=01 Prot=01 Driver=(none) I: If#= 2 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=ff Driver=(none) I: If#= 3 Alt= 0 #EPs= 0 Cls=fe(app. ) Sub=01 Prot=01 Driver=(none) Signed-off-by: Dmitry Tunin Signed-off-by: Marcel Holtmann Cc: stable@vger.kernel.org diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 8063534..25beb3a 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -153,6 +153,10 @@ static const struct usb_device_id btusb_table[] = { { USB_VENDOR_AND_INTERFACE_INFO(0x13d3, 0xff, 0x01, 0x01), .driver_info = BTUSB_BCM_PATCHRAM }, + /* Toshiba Corp - Broadcom based */ + { USB_VENDOR_AND_INTERFACE_INFO(0x0930, 0xff, 0x01, 0x01), + .driver_info = BTUSB_BCM_PATCHRAM }, + /* Intel Bluetooth USB Bootloader (RAM module) */ { USB_DEVICE(0x8087, 0x0a5a), .driver_info = BTUSB_INTEL_BOOT | BTUSB_BROKEN_ISOC }, -- cgit v0.10.2 From 28dc4b92e20e0fd18be2d8356abf959d58c7346a Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Thu, 3 Dec 2015 16:10:22 +0100 Subject: Bluetooth: btintel: Add manufacturing enter/exit helpers Older Intel controllers need to enter manufacturing mode to perform some vendor specific operations (patching, configuration...). Add enter/exit manufaturing methods and refactor existing manufacturing code. Exit can be configured to perform a reset. Reset can be performed either with patches activated or deactivated. Signed-off-by: Loic Poulain Signed-off-by: Marcel Holtmann diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 1f13e61..5441047 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -73,6 +73,48 @@ int btintel_check_bdaddr(struct hci_dev *hdev) } EXPORT_SYMBOL_GPL(btintel_check_bdaddr); +int btintel_enter_mfg(struct hci_dev *hdev) +{ + const u8 param[] = { 0x01, 0x00 }; + struct sk_buff *skb; + + skb = __hci_cmd_sync(hdev, 0xfc11, 2, param, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + bt_dev_err(hdev, "Entering manufacturer mode failed (%ld)", + PTR_ERR(skb)); + return PTR_ERR(skb); + } + kfree_skb(skb); + + return 0; +} +EXPORT_SYMBOL_GPL(btintel_enter_mfg); + +int btintel_exit_mfg(struct hci_dev *hdev, bool reset, bool patched) +{ + u8 param[] = { 0x00, 0x00 }; + struct sk_buff *skb; + + /* The 2nd command parameter specifies the manufacturing exit method: + * 0x00: Just disable the manufacturing mode (0x00). + * 0x01: Disable manufacturing mode and reset with patches deactivated. + * 0x02: Disable manufacturing mode and reset with patches activated. + */ + if (reset) + param[1] |= patched ? 0x02 : 0x01; + + skb = __hci_cmd_sync(hdev, 0xfc11, 2, param, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + bt_dev_err(hdev, "Exiting manufacturer mode failed (%ld)", + PTR_ERR(skb)); + return PTR_ERR(skb); + } + kfree_skb(skb); + + return 0; +} +EXPORT_SYMBOL_GPL(btintel_exit_mfg); + int btintel_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr) { struct sk_buff *skb; @@ -126,37 +168,19 @@ EXPORT_SYMBOL_GPL(btintel_set_diag); int btintel_set_diag_mfg(struct hci_dev *hdev, bool enable) { - struct sk_buff *skb; - u8 param[2]; - int err; - - param[0] = 0x01; - param[1] = 0x00; - - skb = __hci_cmd_sync(hdev, 0xfc11, 2, param, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - err = PTR_ERR(skb); - BT_ERR("%s: Entering Intel manufacturer mode failed (%d)", - hdev->name, err); - return PTR_ERR(skb); - } - kfree_skb(skb); + int err, ret; - err = btintel_set_diag(hdev, enable); + err = btintel_enter_mfg(hdev); + if (err) + return err; - param[0] = 0x00; - param[1] = 0x00; + ret = btintel_set_diag(hdev, enable); - skb = __hci_cmd_sync(hdev, 0xfc11, 2, param, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - err = PTR_ERR(skb); - BT_ERR("%s: Leaving Intel manufacturer mode failed (%d)", - hdev->name, err); - return PTR_ERR(skb); - } - kfree_skb(skb); + err = btintel_exit_mfg(hdev, false, false); + if (err) + return err; - return err; + return ret; } EXPORT_SYMBOL_GPL(btintel_set_diag_mfg); @@ -309,37 +333,19 @@ EXPORT_SYMBOL_GPL(btintel_set_event_mask); int btintel_set_event_mask_mfg(struct hci_dev *hdev, bool debug) { - struct sk_buff *skb; - u8 param[2]; - int err; + int err, ret; - param[0] = 0x01; - param[1] = 0x00; - - skb = __hci_cmd_sync(hdev, 0xfc11, 2, param, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - err = PTR_ERR(skb); - BT_ERR("%s: Entering Intel manufacturer mode failed (%d)", - hdev->name, err); - return PTR_ERR(skb); - } - kfree_skb(skb); - - err = btintel_set_event_mask(hdev, debug); + err = btintel_enter_mfg(hdev); + if (err) + return err; - param[0] = 0x00; - param[1] = 0x00; + ret = btintel_set_event_mask(hdev, debug); - skb = __hci_cmd_sync(hdev, 0xfc11, 2, param, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - err = PTR_ERR(skb); - BT_ERR("%s: Leaving Intel manufacturer mode failed (%d)", - hdev->name, err); - return PTR_ERR(skb); - } - kfree_skb(skb); + err = btintel_exit_mfg(hdev, false, false); + if (err) + return err; - return err; + return ret; } EXPORT_SYMBOL_GPL(btintel_set_event_mask_mfg); diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h index 07e58e0..fa72eae 100644 --- a/drivers/bluetooth/btintel.h +++ b/drivers/bluetooth/btintel.h @@ -72,6 +72,8 @@ struct intel_secure_send_result { #if IS_ENABLED(CONFIG_BT_INTEL) int btintel_check_bdaddr(struct hci_dev *hdev); +int btintel_enter_mfg(struct hci_dev *hdev); +int btintel_exit_mfg(struct hci_dev *hdev, bool reset, bool patched); int btintel_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr); int btintel_set_diag(struct hci_dev *hdev, bool enable); int btintel_set_diag_mfg(struct hci_dev *hdev, bool enable); @@ -94,6 +96,16 @@ static inline int btintel_check_bdaddr(struct hci_dev *hdev) return -EOPNOTSUPP; } +static inline int btintel_enter_mfg(struct hci_dev *hdev) +{ + return -EOPNOTSUPP; +} + +static inline int btintel_exit_mfg(struct hci_dev *hdev, bool reset, bool patched) +{ + return -EOPNOTSUPP; +} + static inline int btintel_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr) { return -EOPNOTSUPP; diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 25beb3a..6e141cd 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -1646,14 +1646,9 @@ static int btusb_setup_intel(struct hci_dev *hdev) struct sk_buff *skb; const struct firmware *fw; const u8 *fw_ptr; - int disable_patch; + int disable_patch, err; struct intel_version *ver; - const u8 mfg_enable[] = { 0x01, 0x00 }; - const u8 mfg_disable[] = { 0x00, 0x00 }; - const u8 mfg_reset_deactivate[] = { 0x00, 0x01 }; - const u8 mfg_reset_activate[] = { 0x00, 0x02 }; - BT_DBG("%s", hdev->name); /* The controller has a bug with the first HCI command sent to it @@ -1725,22 +1720,16 @@ static int btusb_setup_intel(struct hci_dev *hdev) kfree_skb(skb); - /* This Intel specific command enables the manufacturer mode of the - * controller. - * + /* Enable the manufacturer mode of the controller. * Only while this mode is enabled, the driver can download the * firmware patch data and configuration parameters. */ - skb = __hci_cmd_sync(hdev, 0xfc11, 2, mfg_enable, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - BT_ERR("%s entering Intel manufacturer mode failed (%ld)", - hdev->name, PTR_ERR(skb)); + err = btintel_enter_mfg(hdev); + if (err) { release_firmware(fw); - return PTR_ERR(skb); + return err; } - kfree_skb(skb); - disable_patch = 1; /* The firmware data file consists of list of Intel specific HCI @@ -1780,14 +1769,9 @@ static int btusb_setup_intel(struct hci_dev *hdev) /* Patching completed successfully and disable the manufacturer mode * with reset and activate the downloaded firmware patches. */ - skb = __hci_cmd_sync(hdev, 0xfc11, sizeof(mfg_reset_activate), - mfg_reset_activate, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - BT_ERR("%s exiting Intel manufacturer mode failed (%ld)", - hdev->name, PTR_ERR(skb)); - return PTR_ERR(skb); - } - kfree_skb(skb); + err = btintel_exit_mfg(hdev, true, true); + if (err) + return err; BT_INFO("%s: Intel Bluetooth firmware patch completed and activated", hdev->name); @@ -1796,14 +1780,9 @@ static int btusb_setup_intel(struct hci_dev *hdev) exit_mfg_disable: /* Disable the manufacturer mode without reset */ - skb = __hci_cmd_sync(hdev, 0xfc11, sizeof(mfg_disable), mfg_disable, - HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - BT_ERR("%s exiting Intel manufacturer mode failed (%ld)", - hdev->name, PTR_ERR(skb)); - return PTR_ERR(skb); - } - kfree_skb(skb); + err = btintel_exit_mfg(hdev, false, false); + if (err) + return err; BT_INFO("%s: Intel Bluetooth firmware patch completed", hdev->name); @@ -1815,14 +1794,9 @@ exit_mfg_deactivate: /* Patching failed. Disable the manufacturer mode with reset and * deactivate the downloaded firmware patches. */ - skb = __hci_cmd_sync(hdev, 0xfc11, sizeof(mfg_reset_deactivate), - mfg_reset_deactivate, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - BT_ERR("%s exiting Intel manufacturer mode failed (%ld)", - hdev->name, PTR_ERR(skb)); - return PTR_ERR(skb); - } - kfree_skb(skb); + err = btintel_exit_mfg(hdev, true, false); + if (err) + return err; BT_INFO("%s: Intel Bluetooth firmware patch completed and deactivated", hdev->name); -- cgit v0.10.2 From 2f99536a5b34d5b0f54723067d68f6cef3f0fdc6 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Thu, 3 Dec 2015 12:45:19 +0200 Subject: Bluetooth: Use continuous scanning when creating LE connections All LE connections are now triggered through a preceding passive scan and waiting for a connectable advertising report. This means we've got the best possible guarantee that the device is within range and should be able to request the controller to perform continuous scanning. This way we minimize the risk that we miss out on any advertising packets. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann Cc: stable@vger.kernel.org # 4.3+ diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index e260021..48a7eac 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -726,8 +726,12 @@ static void hci_req_add_le_create_conn(struct hci_request *req, if (hci_update_random_address(req, false, &own_addr_type)) return; + /* Set window to be the same value as the interval to enable + * continuous scanning. + */ cp.scan_interval = cpu_to_le16(hdev->le_scan_interval); - cp.scan_window = cpu_to_le16(hdev->le_scan_window); + cp.scan_window = cp.scan_interval; + bacpy(&cp.peer_addr, &conn->dst); cp.peer_addr_type = conn->dst_type; cp.own_address_type = own_addr_type; -- cgit v0.10.2 From acb9f911ea1f828822001d72b21f7cc06e6718c7 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Thu, 3 Dec 2015 12:45:20 +0200 Subject: Bluetooth: Don't treat connection timeout as a failure When we're doing background scanning and connection attempts it's possible we timeout trying to connect and go back to scanning again. The timeout triggers a HCI_LE_Create_Connection_Cancel which will trigger a Connection Complete with "Unknown Connection Identifier" error status. Since we go back to scanning this isn't really a failure and shouldn't be presented as such to user space through mgmt. The exception to this is if the connection attempt was due to an explicit request on an L2CAP socket (indicated by params->explicit_connect being true). Since the socket will get an error it's consistent to also notify the failure on mgmt in this case. Signed-off-by: Johan Hedberg Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 48a7eac..32575b4 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -668,8 +668,16 @@ void hci_le_conn_failed(struct hci_conn *conn, u8 status) conn->state = BT_CLOSED; - mgmt_connect_failed(hdev, &conn->dst, conn->type, conn->dst_type, - status); + /* If the status indicates successful cancellation of + * the attempt (i.e. Unkown Connection Id) there's no point of + * notifying failure since we'll go back to keep trying to + * connect. The only exception is explicit connect requests + * where a timeout + cancel does indicate an actual failure. + */ + if (status != HCI_ERROR_UNKNOWN_CONN_ID || + (params && params->explicit_connect)) + mgmt_connect_failed(hdev, &conn->dst, conn->type, + conn->dst_type, status); hci_connect_cfm(conn, status); -- cgit v0.10.2 From 1a11ec89dba7bcfb1cb502fc5945533f317ddd03 Mon Sep 17 00:00:00 2001 From: Yichen Zhao Date: Tue, 1 Dec 2015 11:11:01 -0800 Subject: Bluetooth: Fix locking in bt_accept_dequeue after disconnection Fix a crash that may happen when bt_accept_dequeue is run after a Bluetooth connection has been disconnected. bt_accept_unlink was called after release_sock, permitting bt_accept_unlink to run twice on the same socket and cause a NULL pointer dereference. [50510.241632] BUG: unable to handle kernel NULL pointer dereference at 00000000000001a8 [50510.241694] IP: [] bt_accept_unlink+0x47/0xa0 [bluetooth] [50510.241759] PGD 0 [50510.241776] Oops: 0002 [#1] SMP [50510.241802] Modules linked in: rtl8192cu rtl_usb rtlwifi rtl8192c_common 8021q garp stp mrp llc rfcomm bnep nls_iso8859_1 intel_rapl x86_pkg_temp_thermal intel_powerclamp coretemp arc4 ath9k ath9k_common ath9k_hw ath kvm eeepc_wmi asus_wmi mac80211 snd_hda_codec_hdmi snd_hda_codec_realtek sparse_keymap crct10dif_pclmul snd_hda_codec_generic crc32_pclmul snd_hda_intel snd_hda_controller cfg80211 snd_hda_codec i915 snd_hwdep snd_pcm ghash_clmulni_intel snd_timer snd soundcore serio_raw cryptd drm_kms_helper drm i2c_algo_bit shpchp ath3k mei_me lpc_ich btusb bluetooth 6lowpan_iphc mei lp parport wmi video mac_hid psmouse ahci libahci r8169 mii [50510.242279] CPU: 0 PID: 934 Comm: krfcommd Not tainted 3.16.0-49-generic #65~14.04.1-Ubuntu [50510.242327] Hardware name: ASUSTeK Computer INC. VM40B/VM40B, BIOS 1501 12/09/2014 [50510.242370] task: ffff8800d9068a30 ti: ffff8800d7a54000 task.ti: ffff8800d7a54000 [50510.242413] RIP: 0010:[] [] bt_accept_unlink+0x47/0xa0 [bluetooth] [50510.242480] RSP: 0018:ffff8800d7a57d58 EFLAGS: 00010246 [50510.242511] RAX: 0000000000000000 RBX: ffff880119bb8c00 RCX: ffff880119bb8eb0 [50510.242552] RDX: ffff880119bb8eb0 RSI: 00000000fffffe01 RDI: ffff880119bb8c00 [50510.242592] RBP: ffff8800d7a57d60 R08: 0000000000000283 R09: 0000000000000001 [50510.242633] R10: 0000000000000000 R11: 0000000000000000 R12: ffff8800d8da9eb0 [50510.242673] R13: ffff8800d74fdb80 R14: ffff880119bb8c00 R15: ffff8800d8da9c00 [50510.242715] FS: 0000000000000000(0000) GS:ffff88011fa00000(0000) knlGS:0000000000000000 [50510.242761] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [50510.242794] CR2: 00000000000001a8 CR3: 0000000001c13000 CR4: 00000000001407f0 [50510.242835] Stack: [50510.242849] ffff880119bb8eb0 ffff8800d7a57da0 ffffffffc0124506 ffff8800d8da9eb0 [50510.242899] ffff8800d8da9c00 ffff8800d9068a30 0000000000000000 ffff8800d74fdb80 [50510.242949] ffff8800d6f85208 ffff8800d7a57e08 ffffffffc0159985 000000000000001f [50510.242999] Call Trace: [50510.243027] [] bt_accept_dequeue+0xb6/0x180 [bluetooth] [50510.243085] [] l2cap_sock_accept+0x125/0x220 [bluetooth] [50510.243128] [] ? wake_up_state+0x20/0x20 [50510.243163] [] kernel_accept+0x4e/0xa0 [50510.243200] [] rfcomm_run+0x1ad/0x890 [rfcomm] [50510.243238] [] ? rfcomm_process_rx+0x8a0/0x8a0 [rfcomm] [50510.243281] [] kthread+0xd2/0xf0 [50510.243312] [] ? kthread_create_on_node+0x1c0/0x1c0 [50510.243353] [] ret_from_fork+0x58/0x90 [50510.243387] [] ? kthread_create_on_node+0x1c0/0x1c0 [50510.243424] Code: 00 48 8b 93 b8 02 00 00 48 8d 83 b0 02 00 00 48 89 51 08 48 89 0a 48 89 83 b0 02 00 00 48 89 83 b8 02 00 00 48 8b 83 c0 02 00 00 <66> 83 a8 a8 01 00 00 01 48 c7 83 c0 02 00 00 00 00 00 00 f0 ff [50510.243685] RIP [] bt_accept_unlink+0x47/0xa0 [bluetooth] [50510.243737] RSP [50510.243758] CR2: 00000000000001a8 [50510.249457] ---[ end trace bb984f932c4e3ab3 ]--- Signed-off-by: Yichen Zhao Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c index 5785e8e..cb4e8d4 100644 --- a/net/bluetooth/af_bluetooth.c +++ b/net/bluetooth/af_bluetooth.c @@ -186,8 +186,8 @@ struct sock *bt_accept_dequeue(struct sock *parent, struct socket *newsock) /* FIXME: Is this check still needed */ if (sk->sk_state == BT_CLOSED) { - release_sock(sk); bt_accept_unlink(sk); + release_sock(sk); continue; } -- cgit v0.10.2 From 6c483de1b3c41e939b735df1861759815e125304 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Sun, 6 Dec 2015 16:18:34 +0100 Subject: Bluetooth: btintel: Create common Intel Version Read function The Intel Version Read command is used to retrieve information about hardware and firmware version/revision of Intel Bluetooth controllers. This is an Intel generic command used in USB and UART drivers. Signed-off-by: Loic Poulain Signed-off-by: Marcel Holtmann diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 5441047..fce1548 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -349,6 +349,31 @@ int btintel_set_event_mask_mfg(struct hci_dev *hdev, bool debug) } EXPORT_SYMBOL_GPL(btintel_set_event_mask_mfg); +int btintel_read_version(struct hci_dev *hdev, struct intel_version *ver) +{ + struct sk_buff *skb; + + skb = __hci_cmd_sync(hdev, 0xfc05, 0, NULL, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + bt_dev_err(hdev, "Reading Intel version information failed (%ld)", + PTR_ERR(skb)); + return PTR_ERR(skb); + } + + if (skb->len != sizeof(*ver)) { + bt_dev_err(hdev, "Intel version event size mismatch"); + kfree_skb(skb); + return -EILSEQ; + } + + memcpy(ver, skb->data, sizeof(*ver)); + + kfree_skb(skb); + + return 0; +} +EXPORT_SYMBOL_GPL(btintel_read_version); + /* ------- REGMAP IBT SUPPORT ------- */ #define IBT_REG_MODE_8BIT 0x00 diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h index fa72eae..1e8955a 100644 --- a/drivers/bluetooth/btintel.h +++ b/drivers/bluetooth/btintel.h @@ -85,6 +85,7 @@ int btintel_secure_send(struct hci_dev *hdev, u8 fragment_type, u32 plen, int btintel_load_ddc_config(struct hci_dev *hdev, const char *ddc_name); int btintel_set_event_mask(struct hci_dev *hdev, bool debug); int btintel_set_event_mask_mfg(struct hci_dev *hdev, bool debug); +int btintel_read_version(struct hci_dev *hdev, struct intel_version *ver); struct regmap *btintel_regmap_init(struct hci_dev *hdev, u16 opcode_read, u16 opcode_write); @@ -152,6 +153,12 @@ static inline int btintel_set_event_mask_mfg(struct hci_dev *hdev, bool debug) return -EOPNOTSUPP; } +static inline int btintel_read_version(struct hci_dev *hdev, + struct intel_version *ver) +{ + return -EOPNOTSUPP; +} + static inline struct regmap *btintel_regmap_init(struct hci_dev *hdev, u16 opcode_read, u16 opcode_write) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 6e141cd..a191e31 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -1647,7 +1647,7 @@ static int btusb_setup_intel(struct hci_dev *hdev) const struct firmware *fw; const u8 *fw_ptr; int disable_patch, err; - struct intel_version *ver; + struct intel_version ver; BT_DBG("%s", hdev->name); @@ -1673,35 +1673,22 @@ static int btusb_setup_intel(struct hci_dev *hdev) * The returned information are hardware variant and revision plus * firmware variant, revision and build number. */ - skb = __hci_cmd_sync(hdev, 0xfc05, 0, NULL, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - BT_ERR("%s reading Intel fw version command failed (%ld)", - hdev->name, PTR_ERR(skb)); - return PTR_ERR(skb); - } - - if (skb->len != sizeof(*ver)) { - BT_ERR("%s Intel version event length mismatch", hdev->name); - kfree_skb(skb); - return -EIO; - } - - ver = (struct intel_version *)skb->data; + err = btintel_read_version(hdev, &ver); + if (err) + return err; BT_INFO("%s: read Intel version: %02x%02x%02x%02x%02x%02x%02x%02x%02x", - hdev->name, ver->hw_platform, ver->hw_variant, - ver->hw_revision, ver->fw_variant, ver->fw_revision, - ver->fw_build_num, ver->fw_build_ww, ver->fw_build_yy, - ver->fw_patch_num); + hdev->name, ver.hw_platform, ver.hw_variant, ver.hw_revision, + ver.fw_variant, ver.fw_revision, ver.fw_build_num, + ver.fw_build_ww, ver.fw_build_yy, ver.fw_patch_num); /* fw_patch_num indicates the version of patch the device currently * have. If there is no patch data in the device, it is always 0x00. * So, if it is other than 0x00, no need to patch the device again. */ - if (ver->fw_patch_num) { + if (ver.fw_patch_num) { BT_INFO("%s: Intel device is already patched. patch num: %02x", - hdev->name, ver->fw_patch_num); - kfree_skb(skb); + hdev->name, ver.fw_patch_num); goto complete; } @@ -1711,15 +1698,11 @@ static int btusb_setup_intel(struct hci_dev *hdev) * If no patch file is found, allow the device to operate without * a patch. */ - fw = btusb_setup_intel_get_fw(hdev, ver); - if (!fw) { - kfree_skb(skb); + fw = btusb_setup_intel_get_fw(hdev, &ver); + if (!fw) goto complete; - } fw_ptr = fw->data; - kfree_skb(skb); - /* Enable the manufacturer mode of the controller. * Only while this mode is enabled, the driver can download the * firmware patch data and configuration parameters. @@ -1983,7 +1966,7 @@ static int btusb_setup_intel_new(struct hci_dev *hdev) 0x00, 0x08, 0x04, 0x00 }; struct btusb_data *data = hci_get_drvdata(hdev); struct sk_buff *skb; - struct intel_version *ver; + struct intel_version ver; struct intel_boot_params *params; const struct firmware *fw; const u8 *fw_ptr; @@ -2001,28 +1984,16 @@ static int btusb_setup_intel_new(struct hci_dev *hdev) * is in bootloader mode or if it already has operational firmware * loaded. */ - skb = __hci_cmd_sync(hdev, 0xfc05, 0, NULL, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - BT_ERR("%s: Reading Intel version information failed (%ld)", - hdev->name, PTR_ERR(skb)); - return PTR_ERR(skb); - } - - if (skb->len != sizeof(*ver)) { - BT_ERR("%s: Intel version event size mismatch", hdev->name); - kfree_skb(skb); - return -EILSEQ; - } - - ver = (struct intel_version *)skb->data; + err = btintel_read_version(hdev, &ver); + if (err) + return err; /* The hardware platform number has a fixed value of 0x37 and * for now only accept this single value. */ - if (ver->hw_platform != 0x37) { + if (ver.hw_platform != 0x37) { BT_ERR("%s: Unsupported Intel hardware platform (%u)", - hdev->name, ver->hw_platform); - kfree_skb(skb); + hdev->name, ver.hw_platform); return -EINVAL; } @@ -2031,14 +2002,13 @@ static int btusb_setup_intel_new(struct hci_dev *hdev) * put in place to ensure correct forward compatibility options * when newer hardware variants come along. */ - if (ver->hw_variant != 0x0b) { + if (ver.hw_variant != 0x0b) { BT_ERR("%s: Unsupported Intel hardware variant (%u)", - hdev->name, ver->hw_variant); - kfree_skb(skb); + hdev->name, ver.hw_variant); return -EINVAL; } - btintel_version_info(hdev, ver); + btintel_version_info(hdev, &ver); /* The firmware variant determines if the device is in bootloader * mode or is running operational firmware. The value 0x06 identifies @@ -2053,8 +2023,7 @@ static int btusb_setup_intel_new(struct hci_dev *hdev) * It is not possible to use the Secure Boot Parameters in this * case since that command is only available in bootloader mode. */ - if (ver->fw_variant == 0x23) { - kfree_skb(skb); + if (ver.fw_variant == 0x23) { clear_bit(BTUSB_BOOTLOADER, &data->flags); btintel_check_bdaddr(hdev); return 0; @@ -2063,15 +2032,12 @@ static int btusb_setup_intel_new(struct hci_dev *hdev) /* If the device is not in bootloader mode, then the only possible * choice is to return an error and abort the device initialization. */ - if (ver->fw_variant != 0x06) { + if (ver.fw_variant != 0x06) { BT_ERR("%s: Unsupported Intel firmware variant (%u)", - hdev->name, ver->fw_variant); - kfree_skb(skb); + hdev->name, ver.fw_variant); return -ENODEV; } - kfree_skb(skb); - /* Read the secure boot parameters to identify the operating * details of the bootloader. */ diff --git a/drivers/bluetooth/hci_intel.c b/drivers/bluetooth/hci_intel.c index 3c28c4a..3d63ea3 100644 --- a/drivers/bluetooth/hci_intel.c +++ b/drivers/bluetooth/hci_intel.c @@ -542,7 +542,7 @@ static int intel_setup(struct hci_uart *hu) struct intel_device *idev = NULL; struct hci_dev *hdev = hu->hdev; struct sk_buff *skb; - struct intel_version *ver; + struct intel_version ver; struct intel_boot_params *params; struct list_head *p; const struct firmware *fw; @@ -590,35 +590,16 @@ static int intel_setup(struct hci_uart *hu) * is in bootloader mode or if it already has operational firmware * loaded. */ - skb = __hci_cmd_sync(hdev, 0xfc05, 0, NULL, HCI_CMD_TIMEOUT); - if (IS_ERR(skb)) { - bt_dev_err(hdev, "Reading Intel version information failed (%ld)", - PTR_ERR(skb)); - return PTR_ERR(skb); - } - - if (skb->len != sizeof(*ver)) { - bt_dev_err(hdev, "Intel version event size mismatch"); - kfree_skb(skb); - return -EILSEQ; - } - - ver = (struct intel_version *)skb->data; - if (ver->status) { - bt_dev_err(hdev, "Intel version command failure (%02x)", - ver->status); - err = -bt_to_errno(ver->status); - kfree_skb(skb); + err = btintel_read_version(hdev, &ver); + if (err) return err; - } /* The hardware platform number has a fixed value of 0x37 and * for now only accept this single value. */ - if (ver->hw_platform != 0x37) { + if (ver.hw_platform != 0x37) { bt_dev_err(hdev, "Unsupported Intel hardware platform (%u)", - ver->hw_platform); - kfree_skb(skb); + ver.hw_platform); return -EINVAL; } @@ -627,14 +608,13 @@ static int intel_setup(struct hci_uart *hu) * put in place to ensure correct forward compatibility options * when newer hardware variants come along. */ - if (ver->hw_variant != 0x0b) { + if (ver.hw_variant != 0x0b) { bt_dev_err(hdev, "Unsupported Intel hardware variant (%u)", - ver->hw_variant); - kfree_skb(skb); + ver.hw_variant); return -EINVAL; } - btintel_version_info(hdev, ver); + btintel_version_info(hdev, &ver); /* The firmware variant determines if the device is in bootloader * mode or is running operational firmware. The value 0x06 identifies @@ -649,8 +629,7 @@ static int intel_setup(struct hci_uart *hu) * It is not possible to use the Secure Boot Parameters in this * case since that command is only available in bootloader mode. */ - if (ver->fw_variant == 0x23) { - kfree_skb(skb); + if (ver.fw_variant == 0x23) { clear_bit(STATE_BOOTLOADER, &intel->flags); btintel_check_bdaddr(hdev); return 0; @@ -659,15 +638,12 @@ static int intel_setup(struct hci_uart *hu) /* If the device is not in bootloader mode, then the only possible * choice is to return an error and abort the device initialization. */ - if (ver->fw_variant != 0x06) { + if (ver.fw_variant != 0x06) { bt_dev_err(hdev, "Unsupported Intel firmware variant (%u)", - ver->fw_variant); - kfree_skb(skb); + ver.fw_variant); return -ENODEV; } - kfree_skb(skb); - /* Read the secure boot parameters to identify the operating * details of the bootloader. */ -- cgit v0.10.2 From 5e5c08cbee7d75d026ff50a5051f2ed19b4ba301 Mon Sep 17 00:00:00 2001 From: Stefan Schmidt Date: Wed, 9 Dec 2015 22:46:22 +0100 Subject: 6lowpan: clarify Kconfig entries for upcoming GHC support Acked-by: Jukka Rissanen Signed-off-by: Stefan Schmidt Signed-off-by: Alexander Aring Signed-off-by: Marcel Holtmann diff --git a/net/6lowpan/Kconfig b/net/6lowpan/Kconfig index 7fa0f38..6af7a46 100644 --- a/net/6lowpan/Kconfig +++ b/net/6lowpan/Kconfig @@ -6,11 +6,12 @@ menuconfig 6LOWPAN "6LoWPAN" which is supported by IEEE 802.15.4 or Bluetooth stacks. menuconfig 6LOWPAN_NHC - tristate "Next Header Compression Support" + tristate "Next Header and Generic Header Compression Support" depends on 6LOWPAN default y ---help--- - Support for next header compression. + Support for next header and generic header compression defined in + RFC6282 and RFC7400. if 6LOWPAN_NHC -- cgit v0.10.2 From 7e568f50c19c731938fee24a0f048f35120080f3 Mon Sep 17 00:00:00 2001 From: Stefan Schmidt Date: Wed, 9 Dec 2015 22:46:23 +0100 Subject: 6lowpan: add nhc module for GHC hop-by-hopextension header detection Acked-by: Jukka Rissanen Signed-off-by: Stefan Schmidt Signed-off-by: Alexander Aring Signed-off-by: Marcel Holtmann diff --git a/net/6lowpan/Kconfig b/net/6lowpan/Kconfig index 6af7a46..1bd49eb 100644 --- a/net/6lowpan/Kconfig +++ b/net/6lowpan/Kconfig @@ -59,4 +59,10 @@ config 6LOWPAN_NHC_UDP ---help--- 6LoWPAN IPv6 UDP Header compression according to RFC6282. +config 6LOWPAN_GHC_EXT_HDR_HOP + tristate "GHC Hop-by-Hop Options Header Support" + ---help--- + 6LoWPAN IPv6 Hop-by-Hop option generic header compression according + to RFC7400. + endif diff --git a/net/6lowpan/Makefile b/net/6lowpan/Makefile index c6ffc55..ba20e01 100644 --- a/net/6lowpan/Makefile +++ b/net/6lowpan/Makefile @@ -10,3 +10,6 @@ obj-$(CONFIG_6LOWPAN_NHC_IPV6) += nhc_ipv6.o obj-$(CONFIG_6LOWPAN_NHC_MOBILITY) += nhc_mobility.o obj-$(CONFIG_6LOWPAN_NHC_ROUTING) += nhc_routing.o obj-$(CONFIG_6LOWPAN_NHC_UDP) += nhc_udp.o + +#rfc7400 ghcs +obj-$(CONFIG_6LOWPAN_GHC_EXT_HDR_HOP) += nhc_ghc_ext_hop.o diff --git a/net/6lowpan/nhc_ghc_ext_hop.c b/net/6lowpan/nhc_ghc_ext_hop.c new file mode 100644 index 0000000..baec86f --- /dev/null +++ b/net/6lowpan/nhc_ghc_ext_hop.c @@ -0,0 +1,27 @@ +/* + * 6LoWPAN Extension Header compression according to RFC7400 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include "nhc.h" + +#define LOWPAN_GHC_EXT_HOP_IDLEN 1 +#define LOWPAN_GHC_EXT_HOP_ID_0 0xb0 +#define LOWPAN_GHC_EXT_HOP_MASK_0 0xfe + +static void hop_ghid_setup(struct lowpan_nhc *nhc) +{ + nhc->id[0] = LOWPAN_GHC_EXT_HOP_ID_0; + nhc->idmask[0] = LOWPAN_GHC_EXT_HOP_MASK_0; +} + +LOWPAN_NHC(ghc_ext_hop, "RFC7400 Hop-by-Hop Extension Header", NEXTHDR_HOP, 0, + hop_ghid_setup, LOWPAN_GHC_EXT_HOP_IDLEN, NULL, NULL); + +module_lowpan_nhc(ghc_ext_hop); +MODULE_DESCRIPTION("6LoWPAN generic header hop-by-hop extension compression"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From 70cc86752e59ec26fcd31679b1eef23e8cb4b516 Mon Sep 17 00:00:00 2001 From: Stefan Schmidt Date: Wed, 9 Dec 2015 22:46:24 +0100 Subject: 6lowpan: add nhc module for GHC UDP detection Acked-by: Jukka Rissanen Signed-off-by: Stefan Schmidt Signed-off-by: Alexander Aring Signed-off-by: Marcel Holtmann diff --git a/net/6lowpan/Kconfig b/net/6lowpan/Kconfig index 1bd49eb..94d5178 100644 --- a/net/6lowpan/Kconfig +++ b/net/6lowpan/Kconfig @@ -65,4 +65,9 @@ config 6LOWPAN_GHC_EXT_HDR_HOP 6LoWPAN IPv6 Hop-by-Hop option generic header compression according to RFC7400. +config 6LOWPAN_GHC_UDP + tristate "GHC UDP Support" + ---help--- + 6LoWPAN IPv6 UDP generic header compression according to RFC7400. + endif diff --git a/net/6lowpan/Makefile b/net/6lowpan/Makefile index ba20e01..5e4f2f3 100644 --- a/net/6lowpan/Makefile +++ b/net/6lowpan/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_6LOWPAN_NHC_UDP) += nhc_udp.o #rfc7400 ghcs obj-$(CONFIG_6LOWPAN_GHC_EXT_HDR_HOP) += nhc_ghc_ext_hop.o +obj-$(CONFIG_6LOWPAN_GHC_UDP) += nhc_ghc_udp.o diff --git a/net/6lowpan/nhc_ghc_udp.c b/net/6lowpan/nhc_ghc_udp.c new file mode 100644 index 0000000..17beefa --- /dev/null +++ b/net/6lowpan/nhc_ghc_udp.c @@ -0,0 +1,27 @@ +/* + * 6LoWPAN UDP compression according to RFC7400 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include "nhc.h" + +#define LOWPAN_GHC_UDP_IDLEN 1 +#define LOWPAN_GHC_UDP_ID_0 0xd0 +#define LOWPAN_GHC_UDP_MASK_0 0xf8 + +static void udp_ghid_setup(struct lowpan_nhc *nhc) +{ + nhc->id[0] = LOWPAN_GHC_UDP_ID_0; + nhc->idmask[0] = LOWPAN_GHC_UDP_MASK_0; +} + +LOWPAN_NHC(ghc_udp, "RFC7400 UDP", NEXTHDR_UDP, 0, + udp_ghid_setup, LOWPAN_GHC_UDP_IDLEN, NULL, NULL); + +module_lowpan_nhc(ghc_udp); +MODULE_DESCRIPTION("6LoWPAN generic header UDP compression"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From c39da3bb5b978ca03f1702c99965f3db1204516a Mon Sep 17 00:00:00 2001 From: Stefan Schmidt Date: Wed, 9 Dec 2015 22:46:25 +0100 Subject: 6lowpan: add nhc module for GHC ICMPv6 detection Acked-by: Jukka Rissanen Signed-off-by: Stefan Schmidt Signed-off-by: Alexander Aring Signed-off-by: Marcel Holtmann diff --git a/net/6lowpan/Kconfig b/net/6lowpan/Kconfig index 94d5178..0a3f5a8 100644 --- a/net/6lowpan/Kconfig +++ b/net/6lowpan/Kconfig @@ -70,4 +70,9 @@ config 6LOWPAN_GHC_UDP ---help--- 6LoWPAN IPv6 UDP generic header compression according to RFC7400. +config 6LOWPAN_GHC_ICMPV6 + tristate "GHC ICMPv6 Support" + ---help--- + 6LoWPAN IPv6 ICMPv6 generic header compression according to RFC7400. + endif diff --git a/net/6lowpan/Makefile b/net/6lowpan/Makefile index 5e4f2f3..86af3fd 100644 --- a/net/6lowpan/Makefile +++ b/net/6lowpan/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_6LOWPAN_NHC_UDP) += nhc_udp.o #rfc7400 ghcs obj-$(CONFIG_6LOWPAN_GHC_EXT_HDR_HOP) += nhc_ghc_ext_hop.o obj-$(CONFIG_6LOWPAN_GHC_UDP) += nhc_ghc_udp.o +obj-$(CONFIG_6LOWPAN_GHC_ICMPV6) += nhc_ghc_icmpv6.o diff --git a/net/6lowpan/nhc_ghc_icmpv6.c b/net/6lowpan/nhc_ghc_icmpv6.c new file mode 100644 index 0000000..32e7c2c --- /dev/null +++ b/net/6lowpan/nhc_ghc_icmpv6.c @@ -0,0 +1,27 @@ +/* + * 6LoWPAN ICMPv6 compression according to RFC7400 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include "nhc.h" + +#define LOWPAN_GHC_ICMPV6_IDLEN 1 +#define LOWPAN_GHC_ICMPV6_ID_0 0xdf +#define LOWPAN_GHC_ICMPV6_MASK_0 0xff + +static void icmpv6_ghid_setup(struct lowpan_nhc *nhc) +{ + nhc->id[0] = LOWPAN_GHC_ICMPV6_ID_0; + nhc->idmask[0] = LOWPAN_GHC_ICMPV6_MASK_0; +} + +LOWPAN_NHC(ghc_icmpv6, "RFC7400 ICMPv6", NEXTHDR_ICMP, 0, + icmpv6_ghid_setup, LOWPAN_GHC_ICMPV6_IDLEN, NULL, NULL); + +module_lowpan_nhc(ghc_icmpv6); +MODULE_DESCRIPTION("6LoWPAN generic header ICMPv6 compression"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From 20616a5a1e3bb47c385c6d5f27520e7a3cc82864 Mon Sep 17 00:00:00 2001 From: Stefan Schmidt Date: Wed, 9 Dec 2015 22:46:26 +0100 Subject: 6lowpan: add nhc module for GHC destination extension header detection Acked-by: Jukka Rissanen Signed-off-by: Stefan Schmidt Signed-off-by: Alexander Aring Signed-off-by: Marcel Holtmann diff --git a/net/6lowpan/Kconfig b/net/6lowpan/Kconfig index 0a3f5a8..e5184e6 100644 --- a/net/6lowpan/Kconfig +++ b/net/6lowpan/Kconfig @@ -75,4 +75,10 @@ config 6LOWPAN_GHC_ICMPV6 ---help--- 6LoWPAN IPv6 ICMPv6 generic header compression according to RFC7400. +config 6LOWPAN_GHC_EXT_HDR_DEST + tristate "GHC Destination Options Header Support" + ---help--- + 6LoWPAN IPv6 destination option generic header compression according + to RFC7400. + endif diff --git a/net/6lowpan/Makefile b/net/6lowpan/Makefile index 86af3fd..fc4bac0 100644 --- a/net/6lowpan/Makefile +++ b/net/6lowpan/Makefile @@ -15,3 +15,4 @@ obj-$(CONFIG_6LOWPAN_NHC_UDP) += nhc_udp.o obj-$(CONFIG_6LOWPAN_GHC_EXT_HDR_HOP) += nhc_ghc_ext_hop.o obj-$(CONFIG_6LOWPAN_GHC_UDP) += nhc_ghc_udp.o obj-$(CONFIG_6LOWPAN_GHC_ICMPV6) += nhc_ghc_icmpv6.o +obj-$(CONFIG_6LOWPAN_GHC_EXT_HDR_DEST) += nhc_ghc_ext_dest.o diff --git a/net/6lowpan/nhc_ghc_ext_dest.c b/net/6lowpan/nhc_ghc_ext_dest.c new file mode 100644 index 0000000..9887b3a --- /dev/null +++ b/net/6lowpan/nhc_ghc_ext_dest.c @@ -0,0 +1,27 @@ +/* + * 6LoWPAN Extension Header compression according to RFC7400 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include "nhc.h" + +#define LOWPAN_GHC_EXT_DEST_IDLEN 1 +#define LOWPAN_GHC_EXT_DEST_ID_0 0xb6 +#define LOWPAN_GHC_EXT_DEST_MASK_0 0xfe + +static void dest_ghid_setup(struct lowpan_nhc *nhc) +{ + nhc->id[0] = LOWPAN_GHC_EXT_DEST_ID_0; + nhc->idmask[0] = LOWPAN_GHC_EXT_DEST_MASK_0; +} + +LOWPAN_NHC(ghc_ext_dest, "RFC7400 Destination Extension Header", NEXTHDR_DEST, + 0, dest_ghid_setup, LOWPAN_GHC_EXT_DEST_IDLEN, NULL, NULL); + +module_lowpan_nhc(ghc_ext_dest); +MODULE_DESCRIPTION("6LoWPAN generic header destination extension compression"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From 2f4799478c94928802c79edd12711a0e9e8b6f1b Mon Sep 17 00:00:00 2001 From: Stefan Schmidt Date: Wed, 9 Dec 2015 22:46:27 +0100 Subject: 6lowpan: add nhc module for GHC fragmentation extension header detection Acked-by: Jukka Rissanen Signed-off-by: Stefan Schmidt Signed-off-by: Alexander Aring Signed-off-by: Marcel Holtmann diff --git a/net/6lowpan/Kconfig b/net/6lowpan/Kconfig index e5184e6..13abcc5 100644 --- a/net/6lowpan/Kconfig +++ b/net/6lowpan/Kconfig @@ -81,4 +81,10 @@ config 6LOWPAN_GHC_EXT_HDR_DEST 6LoWPAN IPv6 destination option generic header compression according to RFC7400. +config 6LOWPAN_GHC_EXT_HDR_FRAG + tristate "GHC Fragmentation Options Header Support" + ---help--- + 6LoWPAN IPv6 fragmentation option generic header compression + according to RFC7400. + endif diff --git a/net/6lowpan/Makefile b/net/6lowpan/Makefile index fc4bac0..fb3f48d 100644 --- a/net/6lowpan/Makefile +++ b/net/6lowpan/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_6LOWPAN_GHC_EXT_HDR_HOP) += nhc_ghc_ext_hop.o obj-$(CONFIG_6LOWPAN_GHC_UDP) += nhc_ghc_udp.o obj-$(CONFIG_6LOWPAN_GHC_ICMPV6) += nhc_ghc_icmpv6.o obj-$(CONFIG_6LOWPAN_GHC_EXT_HDR_DEST) += nhc_ghc_ext_dest.o +obj-$(CONFIG_6LOWPAN_GHC_EXT_HDR_FRAG) += nhc_ghc_ext_frag.o diff --git a/net/6lowpan/nhc_ghc_ext_frag.c b/net/6lowpan/nhc_ghc_ext_frag.c new file mode 100644 index 0000000..1308b79 --- /dev/null +++ b/net/6lowpan/nhc_ghc_ext_frag.c @@ -0,0 +1,28 @@ +/* + * 6LoWPAN Extension Header compression according to RFC7400 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include "nhc.h" + +#define LOWPAN_GHC_EXT_FRAG_IDLEN 1 +#define LOWPAN_GHC_EXT_FRAG_ID_0 0xb4 +#define LOWPAN_GHC_EXT_FRAG_MASK_0 0xfe + +static void frag_ghid_setup(struct lowpan_nhc *nhc) +{ + nhc->id[0] = LOWPAN_GHC_EXT_FRAG_ID_0; + nhc->idmask[0] = LOWPAN_GHC_EXT_FRAG_MASK_0; +} + +LOWPAN_NHC(ghc_ext_frag, "RFC7400 Fragmentation Extension Header", + NEXTHDR_FRAGMENT, 0, frag_ghid_setup, + LOWPAN_GHC_EXT_FRAG_IDLEN, NULL, NULL); + +module_lowpan_nhc(ghc_ext_frag); +MODULE_DESCRIPTION("6LoWPAN generic header fragmentation extension compression"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From 43f26e17d02f5c772cedc3ee16b192ed79764474 Mon Sep 17 00:00:00 2001 From: Stefan Schmidt Date: Wed, 9 Dec 2015 22:46:28 +0100 Subject: 6lowpan: add nhc module for GHC routing extension header detection Acked-by: Jukka Rissanen Signed-off-by: Stefan Schmidt Signed-off-by: Alexander Aring Signed-off-by: Marcel Holtmann diff --git a/net/6lowpan/Kconfig b/net/6lowpan/Kconfig index 13abcc5..bcb9d8a 100644 --- a/net/6lowpan/Kconfig +++ b/net/6lowpan/Kconfig @@ -87,4 +87,10 @@ config 6LOWPAN_GHC_EXT_HDR_FRAG 6LoWPAN IPv6 fragmentation option generic header compression according to RFC7400. +config 6LOWPAN_GHC_EXT_HDR_ROUTE + tristate "GHC Routing Options Header Support" + ---help--- + 6LoWPAN IPv6 routing option generic header compression according + to RFC7400. + endif diff --git a/net/6lowpan/Makefile b/net/6lowpan/Makefile index fb3f48d..9e35a5d 100644 --- a/net/6lowpan/Makefile +++ b/net/6lowpan/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_6LOWPAN_GHC_UDP) += nhc_ghc_udp.o obj-$(CONFIG_6LOWPAN_GHC_ICMPV6) += nhc_ghc_icmpv6.o obj-$(CONFIG_6LOWPAN_GHC_EXT_HDR_DEST) += nhc_ghc_ext_dest.o obj-$(CONFIG_6LOWPAN_GHC_EXT_HDR_FRAG) += nhc_ghc_ext_frag.o +obj-$(CONFIG_6LOWPAN_GHC_EXT_HDR_ROUTE) += nhc_ghc_ext_route.o diff --git a/net/6lowpan/nhc_ghc_ext_route.c b/net/6lowpan/nhc_ghc_ext_route.c new file mode 100644 index 0000000..d7e5bd7 --- /dev/null +++ b/net/6lowpan/nhc_ghc_ext_route.c @@ -0,0 +1,27 @@ +/* + * 6LoWPAN Extension Header compression according to RFC7400 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include "nhc.h" + +#define LOWPAN_GHC_EXT_ROUTE_IDLEN 1 +#define LOWPAN_GHC_EXT_ROUTE_ID_0 0xb2 +#define LOWPAN_GHC_EXT_ROUTE_MASK_0 0xfe + +static void route_ghid_setup(struct lowpan_nhc *nhc) +{ + nhc->id[0] = LOWPAN_GHC_EXT_ROUTE_ID_0; + nhc->idmask[0] = LOWPAN_GHC_EXT_ROUTE_MASK_0; +} + +LOWPAN_NHC(ghc_ext_route, "RFC7400 Routing Extension Header", NEXTHDR_ROUTING, + 0, route_ghid_setup, LOWPAN_GHC_EXT_ROUTE_IDLEN, NULL, NULL); + +module_lowpan_nhc(ghc_ext_route); +MODULE_DESCRIPTION("6LoWPAN generic header routing extension compression"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From 00f59314111a6b18ee65b238b38c470dbdbf3be5 Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Wed, 9 Dec 2015 22:46:29 +0100 Subject: 6lowpan: add lowpan dev register helpers This patch introduces register and unregister functionality for lowpan interfaces. While register a lowpan interface there are several things which need to be initialize by the 6lowpan subsystem. Upcoming functionality need to register/unregister per interface components e.g. debugfs entry. Reviewed-by: Stefan Schmidt Signed-off-by: Alexander Aring Signed-off-by: Marcel Holtmann diff --git a/include/net/6lowpan.h b/include/net/6lowpan.h index cf3bc56..730211f 100644 --- a/include/net/6lowpan.h +++ b/include/net/6lowpan.h @@ -185,7 +185,12 @@ static inline void lowpan_push_hc_data(u8 **hc_ptr, const void *data, *hc_ptr += len; } -void lowpan_netdev_setup(struct net_device *dev, enum lowpan_lltypes lltype); +int lowpan_register_netdevice(struct net_device *dev, + enum lowpan_lltypes lltype); +int lowpan_register_netdev(struct net_device *dev, + enum lowpan_lltypes lltype); +void lowpan_unregister_netdevice(struct net_device *dev); +void lowpan_unregister_netdev(struct net_device *dev); /** * lowpan_header_decompress - replace 6LoWPAN header with IPv6 header diff --git a/net/6lowpan/core.c b/net/6lowpan/core.c index 83b19e0..80fc509 100644 --- a/net/6lowpan/core.c +++ b/net/6lowpan/core.c @@ -15,7 +15,8 @@ #include -void lowpan_netdev_setup(struct net_device *dev, enum lowpan_lltypes lltype) +int lowpan_register_netdevice(struct net_device *dev, + enum lowpan_lltypes lltype) { dev->addr_len = EUI64_ADDR_LEN; dev->type = ARPHRD_6LOWPAN; @@ -23,8 +24,36 @@ void lowpan_netdev_setup(struct net_device *dev, enum lowpan_lltypes lltype) dev->priv_flags |= IFF_NO_QUEUE; lowpan_priv(dev)->lltype = lltype; + + return register_netdevice(dev); +} +EXPORT_SYMBOL(lowpan_register_netdevice); + +int lowpan_register_netdev(struct net_device *dev, + enum lowpan_lltypes lltype) +{ + int ret; + + rtnl_lock(); + ret = lowpan_register_netdevice(dev, lltype); + rtnl_unlock(); + return ret; +} +EXPORT_SYMBOL(lowpan_register_netdev); + +void lowpan_unregister_netdevice(struct net_device *dev) +{ + unregister_netdevice(dev); +} +EXPORT_SYMBOL(lowpan_unregister_netdevice); + +void lowpan_unregister_netdev(struct net_device *dev) +{ + rtnl_lock(); + lowpan_unregister_netdevice(dev); + rtnl_unlock(); } -EXPORT_SYMBOL(lowpan_netdev_setup); +EXPORT_SYMBOL(lowpan_unregister_netdev); static int __init lowpan_module_init(void) { diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c index 9e9cca3..d040365 100644 --- a/net/bluetooth/6lowpan.c +++ b/net/bluetooth/6lowpan.c @@ -825,9 +825,7 @@ 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); + err = lowpan_register_netdev(netdev, LOWPAN_LLTYPE_BTLE); if (err < 0) { BT_INFO("register_netdev failed %d", err); spin_lock(&devices_lock); @@ -890,7 +888,7 @@ static void delete_netdev(struct work_struct *work) struct lowpan_dev *entry = container_of(work, struct lowpan_dev, delete_netdev); - unregister_netdev(entry->netdev); + lowpan_unregister_netdev(entry->netdev); /* The entry pointer is deleted by the netdev destructor. */ } @@ -1348,7 +1346,7 @@ static void disconnect_devices(void) ifdown(entry->netdev); BT_DBG("Unregistering netdev %s %p", entry->netdev->name, entry->netdev); - unregister_netdev(entry->netdev); + lowpan_unregister_netdev(entry->netdev); kfree(entry); } } diff --git a/net/ieee802154/6lowpan/core.c b/net/ieee802154/6lowpan/core.c index 20c49c7..737c87a 100644 --- a/net/ieee802154/6lowpan/core.c +++ b/net/ieee802154/6lowpan/core.c @@ -161,9 +161,7 @@ static int lowpan_newlink(struct net *src_net, struct net_device *ldev, wdev->needed_headroom; ldev->needed_tailroom = wdev->needed_tailroom; - lowpan_netdev_setup(ldev, LOWPAN_LLTYPE_IEEE802154); - - ret = register_netdevice(ldev); + ret = lowpan_register_netdevice(ldev, LOWPAN_LLTYPE_IEEE802154); if (ret < 0) { dev_put(wdev); return ret; @@ -180,7 +178,7 @@ static void lowpan_dellink(struct net_device *ldev, struct list_head *head) ASSERT_RTNL(); wdev->ieee802154_ptr->lowpan_dev = NULL; - unregister_netdevice(ldev); + lowpan_unregister_netdevice(ldev); dev_put(wdev); } -- cgit v0.10.2 From b1815fd949e5bd06d118019acf68f87c9414f705 Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Wed, 9 Dec 2015 22:46:30 +0100 Subject: 6lowpan: add debugfs support This patch will introduce a 6lowpan entry into the debugfs if enabled. Inside this 6lowpan directory we create a subdirectories of all 6lowpan interfaces to offer a per interface debugfs support. Reviewed-by: Stefan Schmidt Signed-off-by: Alexander Aring Signed-off-by: Marcel Holtmann diff --git a/include/net/6lowpan.h b/include/net/6lowpan.h index 730211f..2f6a3f2 100644 --- a/include/net/6lowpan.h +++ b/include/net/6lowpan.h @@ -53,6 +53,8 @@ #ifndef __6LOWPAN_H__ #define __6LOWPAN_H__ +#include + #include #include @@ -98,6 +100,7 @@ enum lowpan_lltypes { struct lowpan_priv { enum lowpan_lltypes lltype; + struct dentry *iface_debugfs; /* must be last */ u8 priv[0] __aligned(sizeof(void *)); diff --git a/net/6lowpan/6lowpan_i.h b/net/6lowpan/6lowpan_i.h new file mode 100644 index 0000000..d16bb4b --- /dev/null +++ b/net/6lowpan/6lowpan_i.h @@ -0,0 +1,28 @@ +#ifndef __6LOWPAN_I_H +#define __6LOWPAN_I_H + +#include + +#ifdef CONFIG_6LOWPAN_DEBUGFS +int lowpan_dev_debugfs_init(struct net_device *dev); +void lowpan_dev_debugfs_exit(struct net_device *dev); + +int __init lowpan_debugfs_init(void); +void lowpan_debugfs_exit(void); +#else +static inline int lowpan_dev_debugfs_init(struct net_device *dev) +{ + return 0; +} + +static inline void lowpan_dev_debugfs_exit(struct net_device *dev) { } + +static inline int __init lowpan_debugfs_init(void) +{ + return 0; +} + +static inline void lowpan_debugfs_exit(void) { } +#endif /* CONFIG_6LOWPAN_DEBUGFS */ + +#endif /* __6LOWPAN_I_H */ diff --git a/net/6lowpan/Kconfig b/net/6lowpan/Kconfig index bcb9d8a..9c05151 100644 --- a/net/6lowpan/Kconfig +++ b/net/6lowpan/Kconfig @@ -5,6 +5,14 @@ menuconfig 6LOWPAN This enables IPv6 over Low power Wireless Personal Area Network - "6LoWPAN" which is supported by IEEE 802.15.4 or Bluetooth stacks. +config 6LOWPAN_DEBUGFS + bool "6LoWPAN debugfs support" + depends on 6LOWPAN + depends on DEBUG_FS + ---help--- + This enables 6LoWPAN debugfs support. For example to manipulate + IPHC context information at runtime. + menuconfig 6LOWPAN_NHC tristate "Next Header and Generic Header Compression Support" depends on 6LOWPAN diff --git a/net/6lowpan/Makefile b/net/6lowpan/Makefile index 9e35a5d..e44f3bf 100644 --- a/net/6lowpan/Makefile +++ b/net/6lowpan/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_6LOWPAN) += 6lowpan.o 6lowpan-y := core.o iphc.o nhc.o +6lowpan-$(CONFIG_6LOWPAN_DEBUGFS) += debugfs.o #rfc6282 nhcs obj-$(CONFIG_6LOWPAN_NHC_DEST) += nhc_dest.o diff --git a/net/6lowpan/core.c b/net/6lowpan/core.c index 80fc509..c7f06f5 100644 --- a/net/6lowpan/core.c +++ b/net/6lowpan/core.c @@ -15,9 +15,13 @@ #include +#include "6lowpan_i.h" + int lowpan_register_netdevice(struct net_device *dev, enum lowpan_lltypes lltype) { + int ret; + dev->addr_len = EUI64_ADDR_LEN; dev->type = ARPHRD_6LOWPAN; dev->mtu = IPV6_MIN_MTU; @@ -25,7 +29,15 @@ int lowpan_register_netdevice(struct net_device *dev, lowpan_priv(dev)->lltype = lltype; - return register_netdevice(dev); + ret = lowpan_dev_debugfs_init(dev); + if (ret < 0) + return ret; + + ret = register_netdevice(dev); + if (ret < 0) + lowpan_dev_debugfs_exit(dev); + + return ret; } EXPORT_SYMBOL(lowpan_register_netdevice); @@ -44,6 +56,7 @@ EXPORT_SYMBOL(lowpan_register_netdev); void lowpan_unregister_netdevice(struct net_device *dev) { unregister_netdevice(dev); + lowpan_dev_debugfs_exit(dev); } EXPORT_SYMBOL(lowpan_unregister_netdevice); @@ -57,6 +70,12 @@ EXPORT_SYMBOL(lowpan_unregister_netdev); static int __init lowpan_module_init(void) { + int ret; + + ret = lowpan_debugfs_init(); + if (ret < 0) + return ret; + request_module_nowait("ipv6"); request_module_nowait("nhc_dest"); @@ -69,6 +88,13 @@ static int __init lowpan_module_init(void) return 0; } + +static void __exit lowpan_module_exit(void) +{ + lowpan_debugfs_exit(); +} + module_init(lowpan_module_init); +module_exit(lowpan_module_exit); MODULE_LICENSE("GPL"); diff --git a/net/6lowpan/debugfs.c b/net/6lowpan/debugfs.c new file mode 100644 index 0000000..88eef84 --- /dev/null +++ b/net/6lowpan/debugfs.c @@ -0,0 +1,53 @@ +/* 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 + * Copyright (c) 2015 Nordic Semiconductor. All Rights Reserved. + */ + +#include + +#include "6lowpan_i.h" + +static struct dentry *lowpan_debugfs; + +int lowpan_dev_debugfs_init(struct net_device *dev) +{ + struct lowpan_priv *lpriv = lowpan_priv(dev); + + /* creating the root */ + lpriv->iface_debugfs = debugfs_create_dir(dev->name, lowpan_debugfs); + if (!lpriv->iface_debugfs) + goto fail; + + return 0; + +fail: + return -EINVAL; +} + +void lowpan_dev_debugfs_exit(struct net_device *dev) +{ + debugfs_remove_recursive(lowpan_priv(dev)->iface_debugfs); +} + +int __init lowpan_debugfs_init(void) +{ + lowpan_debugfs = debugfs_create_dir("6lowpan", NULL); + if (!lowpan_debugfs) + return -EINVAL; + + return 0; +} + +void lowpan_debugfs_exit(void) +{ + debugfs_remove_recursive(lowpan_debugfs); +} -- cgit v0.10.2 From 818f1f3e70dd4c8e6f8d59c617857be0fa0fce7c Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Wed, 9 Dec 2015 22:46:31 +0100 Subject: ipv6: add ipv6_addr_prefix_copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a static inline function ipv6_addr_prefix_copy which copies a ipv6 address prefix(argument pfx) into the ipv6 address prefix. The prefix len is given by plen as bits. This function mainly based on ipv6_addr_prefix which copies one address prefix from address into a new ipv6 address destination and zero all other address bits. The difference is that ipv6_addr_prefix_copy don't get a prefix from an ipv6 address, it sets a prefix to an ipv6 address with keeping other address bits. The use case is for context based address compression inside 6LoWPAN IPHC header which keeping ipv6 prefixes inside a context table to lookup address-bits without sending them. Cc: Alexey Kuznetsov Cc: James Morris Cc: Hideaki YOSHIFUJI Cc: Patrick McHardy Acked-by: Łukasz Duda Acked-by: Hannes Frederic Sowa Acked-by: YOSHIFUJI Hideaki Acked-by: David S. Miller Reviewed-by: Stefan Schmidt Signed-off-by: Alexander Aring Signed-off-by: Marcel Holtmann diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 9a5c9f0..6570f379 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -401,6 +401,21 @@ static inline void ipv6_addr_prefix(struct in6_addr *pfx, pfx->s6_addr[o] = addr->s6_addr[o] & (0xff00 >> b); } +static inline void ipv6_addr_prefix_copy(struct in6_addr *addr, + const struct in6_addr *pfx, + int plen) +{ + /* caller must guarantee 0 <= plen <= 128 */ + int o = plen >> 3, + b = plen & 0x7; + + memcpy(addr->s6_addr, pfx, o); + if (b != 0) { + addr->s6_addr[o] &= ~(0xff00 >> b); + addr->s6_addr[o] |= (pfx->s6_addr[o] & (0xff00 >> b)); + } +} + static inline void __ipv6_addr_set_half(__be32 *addr, __be32 wh, __be32 wl) { -- cgit v0.10.2 From c38383530fb5e160b739aff4bf08c1cc2dfcc659 Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Wed, 9 Dec 2015 23:23:56 +0100 Subject: mac802154: tx: fix synced xmit deadlock This patch reverts 6001d52 ("mac802154: tx: don't allow if down while sync tx"). This has side effects with stop callback which flush the transmit workqueue. The stop callback will wait until the workqueue is flushed and holding the rtnl lock. That means it can happen that the stop callback waits forever because it try to lock the rtnl mutex which is already hold by stop callback. Cc: Michael Hennerich Signed-off-by: Alexander Aring Signed-off-by: Marcel Holtmann diff --git a/net/mac802154/driver-ops.h b/net/mac802154/driver-ops.h index 0550f33..fd9daf2 100644 --- a/net/mac802154/driver-ops.h +++ b/net/mac802154/driver-ops.h @@ -18,9 +18,6 @@ drv_xmit_async(struct ieee802154_local *local, struct sk_buff *skb) static inline int drv_xmit_sync(struct ieee802154_local *local, struct sk_buff *skb) { - /* don't allow other operations while sync xmit */ - ASSERT_RTNL(); - might_sleep(); return local->ops->xmit_sync(&local->hw, skb); diff --git a/net/mac802154/tx.c b/net/mac802154/tx.c index 3827f35..7e25345 100644 --- a/net/mac802154/tx.c +++ b/net/mac802154/tx.c @@ -38,12 +38,6 @@ void ieee802154_xmit_worker(struct work_struct *work) struct net_device *dev = skb->dev; int res; - rtnl_lock(); - - /* check if ifdown occurred while schedule */ - if (!netif_running(dev)) - goto err_tx; - res = drv_xmit_sync(local, skb); if (res) goto err_tx; @@ -53,14 +47,11 @@ void ieee802154_xmit_worker(struct work_struct *work) dev->stats.tx_packets++; dev->stats.tx_bytes += skb->len; - rtnl_unlock(); - return; err_tx: /* Restart the netif queue on each sub_if_data object. */ ieee802154_wake_queue(&local->hw); - rtnl_unlock(); kfree_skb(skb); netdev_dbg(dev, "transmission failed\n"); } -- cgit v0.10.2 From 87a6b9bd6306e0efd5eedbd68042eee36184dbd7 Mon Sep 17 00:00:00 2001 From: Andrei Emeltchenko Date: Thu, 10 Dec 2015 17:03:43 +0200 Subject: Bluetooth: h5: Do not initialize Configuration field Initializing Configuration field in H5 Config message to 0x01 gives wrong impression that the value is used and needed. Later on the whole field is rewritten with h5_cfg_field(). Signed-off-by: Andrei Emeltchenko Signed-off-by: Marcel Holtmann diff --git a/drivers/bluetooth/hci_h5.c b/drivers/bluetooth/hci_h5.c index 2d1c9f6..0879d64 100644 --- a/drivers/bluetooth/hci_h5.c +++ b/drivers/bluetooth/hci_h5.c @@ -123,7 +123,7 @@ static u8 h5_cfg_field(struct h5 *h5) static void h5_timed_event(unsigned long arg) { const unsigned char sync_req[] = { 0x01, 0x7e }; - unsigned char conf_req[] = { 0x03, 0xfc, 0x01 }; + unsigned char conf_req[3] = { 0x03, 0xfc }; struct hci_uart *hu = (struct hci_uart *)arg; struct h5 *h5 = hu->priv; struct sk_buff *skb; @@ -281,7 +281,7 @@ static void h5_handle_internal_rx(struct hci_uart *hu) struct h5 *h5 = hu->priv; const unsigned char sync_req[] = { 0x01, 0x7e }; const unsigned char sync_rsp[] = { 0x02, 0x7d }; - unsigned char conf_req[] = { 0x03, 0xfc, 0x01 }; + unsigned char conf_req[3] = { 0x03, 0xfc }; const unsigned char conf_rsp[] = { 0x04, 0x7b }; const unsigned char wakeup_req[] = { 0x05, 0xfa }; const unsigned char woken_req[] = { 0x06, 0xf9 }; -- cgit v0.10.2 From 4188146566a9f1d57dfce77fd9457f7304b69dfa Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Thu, 10 Dec 2015 23:44:34 +0100 Subject: ieee802154-atusb: Delete an unnecessary check before the function call "kfree_skb" The kfree_skb() function tests whether its argument is NULL and then returns immediately. Thus the test around the call is not needed. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Acked-by: Stefan Schmidt Signed-off-by: Marcel Holtmann diff --git a/drivers/net/ieee802154/atusb.c b/drivers/net/ieee802154/atusb.c index 199a94a..b1cd865 100644 --- a/drivers/net/ieee802154/atusb.c +++ b/drivers/net/ieee802154/atusb.c @@ -310,8 +310,7 @@ static void atusb_free_urbs(struct atusb *atusb) urb = usb_get_from_anchor(&atusb->idle_urbs); if (!urb) break; - if (urb->context) - kfree_skb(urb->context); + kfree_skb(urb->context); usb_free_urb(urb); } } -- cgit v0.10.2 From 4ada1282d86865671abdfcf9410b895af8491213 Mon Sep 17 00:00:00 2001 From: Danny Schweizer Date: Fri, 11 Dec 2015 10:04:54 +0100 Subject: Bluetooth: Do not filter multicast addresses by default A Linux PC is connected with another device over Bluetooth PAN using a BNEP interface. Whenever a packet is tried to be sent over the BNEP interface, the function "bnep_net_xmit()" in "net/bluetooth/bnep/netdev.c" is called. This function calls "bnep_net_mc_filter()", which checks (if the destination address is multicast) if the address is set in a certain multicast filter (&s->mc_filter). If it is not, then it is not sent out. This filter is only changed in two other functions, found in net/bluetooth/bnep/core.c": in "bnep_ctrl_set_mc_filter()", which is only called if a message of type "BNEP_FILTER_MULTI_ADDR_SET" is received. Otherwise, it is set in "bnep_add_connection()", where it is set to a default value which only adds the broadcast address to the filter: set_bit(bnep_mc_hash(dev->broadcast), (ulong *) &s->mc_filter); To sum up, if the BNEP interface does not receive any message of type "BNEP_FILTER_MULTI_ADDR_SET", it will not send out any messages with multicast destination addresses except for broadcast. However, in the BNEP specification (page 27 in http://grouper.ieee.org/groups/802/15/Bluetooth/BNEP.pdf), it is said that per default, all multicast addresses should not be filtered, i.e. the BNEP interface should be able to send packets with any multicast destination address. It seems that the default case is wrong: the multicast filter should not block almost all multicast addresses, but should not filter out any. This leads to the problem that e.g. Neighbor Solicitation messages sent with Bluetooth PAN over the BNEP interface to a multicast destination address other than broadcast are blocked and not sent out. Therefore, in the default case, we set the mc_filter to ~0LL to not filter out any multicast addresses. Signed-off-by: Danny Schweizer Signed-off-by: Marcel Holtmann diff --git a/net/bluetooth/bnep/core.c b/net/bluetooth/bnep/core.c index 1641367..fbf251f 100644 --- a/net/bluetooth/bnep/core.c +++ b/net/bluetooth/bnep/core.c @@ -608,8 +608,11 @@ int bnep_add_connection(struct bnep_connadd_req *req, struct socket *sock) s->msg.msg_flags = MSG_NOSIGNAL; #ifdef CONFIG_BT_BNEP_MC_FILTER - /* Set default mc filter */ - set_bit(bnep_mc_hash(dev->broadcast), (ulong *) &s->mc_filter); + /* Set default mc filter to not filter out any mc addresses + * as defined in the BNEP specification (revision 0.95a) + * http://grouper.ieee.org/groups/802/15/Bluetooth/BNEP.pdf + */ + s->mc_filter = ~0LL; #endif #ifdef CONFIG_BT_BNEP_PROTO_FILTER -- cgit v0.10.2 From 7302b9d90117496049dd4bfa28755f7c2ed55b27 Mon Sep 17 00:00:00 2001 From: Michael Hennerich Date: Fri, 11 Dec 2015 11:17:15 +0100 Subject: ieee802154/adf7242: Driver for ADF7242 MAC IEEE802154 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This driver has been sitting in the linux-zigbee[2] repository for a long time. We updated it from time to time and made it available via our github kernel repository. The Linux MAC802.15.4 support has improved a lot since then. Thanks to all! So it’s finally time to upstream this driver. The ADF7242 requires an add-on firmware for the automatic IEEE 802.15.4 operating modes. The firmware file is currently made available on the ADF7242 wiki page here [1] [1] http://wiki.analog.com/resources/tools-software/linux-drivers/networking-mac802154/adf7242 [2] http://sourceforge.net/p/linux-zigbee/kernel/ci/devel/tree/drivers/ieee802154/adf7242.c Signed-off-by: Michael Hennerich Reviewed-by: Stefan Schmidt Signed-off-by: Marcel Holtmann diff --git a/Documentation/devicetree/bindings/net/ieee802154/adf7242.txt b/Documentation/devicetree/bindings/net/ieee802154/adf7242.txt new file mode 100644 index 0000000..dea5124 --- /dev/null +++ b/Documentation/devicetree/bindings/net/ieee802154/adf7242.txt @@ -0,0 +1,18 @@ +* ADF7242 IEEE 802.15.4 * + +Required properties: + - compatible: should be "adi,adf7242" + - spi-max-frequency: maximal bus speed (12.5 MHz) + - reg: the chipselect index + - interrupts: the interrupt generated by the device via pin IRQ1. + IRQ_TYPE_LEVEL_HIGH (4) or IRQ_TYPE_EDGE_FALLING (1) + +Example: + + adf7242@0 { + compatible = "adi,adf7242"; + spi-max-frequency = <10000000>; + reg = <0>; + interrupts = <98 IRQ_TYPE_LEVEL_HIGH>; + interrupt-parent = <&gpio3>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 8eba565..4366c53 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -371,6 +371,15 @@ ADDRESS SPACE LAYOUT RANDOMIZATION (ASLR) M: Jiri Kosina S: Maintained +ADF7242 IEEE 802.15.4 RADIO DRIVER +M: Michael Hennerich +W: https://wiki.analog.com/ADF7242 +W: http://ez.analog.com/community/linux-device-drivers +L: linux-wpan@vger.kernel.org +S: Supported +F: drivers/net/ieee802154/adf7242.c +F: Documentation/devicetree/bindings/net/ieee802154/adf7242.txt + ADM1025 HARDWARE MONITOR DRIVER M: Jean Delvare L: lm-sensors@lm-sensors.org diff --git a/drivers/net/ieee802154/Kconfig b/drivers/net/ieee802154/Kconfig index ce5f1a2..3057a8d 100644 --- a/drivers/net/ieee802154/Kconfig +++ b/drivers/net/ieee802154/Kconfig @@ -71,3 +71,14 @@ config IEEE802154_ATUSB This driver can also be built as a module. To do so say M here. The module will be called 'atusb'. + +config IEEE802154_ADF7242 + tristate "ADF7242 transceiver driver" + depends on IEEE802154_DRIVERS && MAC802154 + depends on SPI + ---help--- + Say Y here to enable the ADF7242 SPI 802.15.4 wireless + controller. + + This driver can also be built as a module. To do so, say M here. + the module will be called 'adf7242'. diff --git a/drivers/net/ieee802154/Makefile b/drivers/net/ieee802154/Makefile index cf1d2a6..3a923d3 100644 --- a/drivers/net/ieee802154/Makefile +++ b/drivers/net/ieee802154/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_IEEE802154_AT86RF230) += at86rf230.o obj-$(CONFIG_IEEE802154_MRF24J40) += mrf24j40.o obj-$(CONFIG_IEEE802154_CC2520) += cc2520.o obj-$(CONFIG_IEEE802154_ATUSB) += atusb.o +obj-$(CONFIG_IEEE802154_ADF7242) += adf7242.o diff --git a/drivers/net/ieee802154/adf7242.c b/drivers/net/ieee802154/adf7242.c new file mode 100644 index 0000000..89154c0 --- /dev/null +++ b/drivers/net/ieee802154/adf7242.c @@ -0,0 +1,1285 @@ +/* + * Analog Devices ADF7242 Low-Power IEEE 802.15.4 Transceiver + * + * Copyright 2009-2015 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + * + * http://www.analog.com/ADF7242 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FIRMWARE "adf7242_firmware.bin" +#define MAX_POLL_LOOPS 200 + +/* All Registers */ + +#define REG_EXT_CTRL 0x100 /* RW External LNA/PA and internal PA control */ +#define REG_TX_FSK_TEST 0x101 /* RW TX FSK test mode configuration */ +#define REG_CCA1 0x105 /* RW RSSI threshold for CCA */ +#define REG_CCA2 0x106 /* RW CCA mode configuration */ +#define REG_BUFFERCFG 0x107 /* RW RX_BUFFER overwrite control */ +#define REG_PKT_CFG 0x108 /* RW FCS evaluation configuration */ +#define REG_DELAYCFG0 0x109 /* RW RC_RX command to SFD or sync word delay */ +#define REG_DELAYCFG1 0x10A /* RW RC_TX command to TX state */ +#define REG_DELAYCFG2 0x10B /* RW Mac delay extension */ +#define REG_SYNC_WORD0 0x10C /* RW sync word bits [7:0] of [23:0] */ +#define REG_SYNC_WORD1 0x10D /* RW sync word bits [15:8] of [23:0] */ +#define REG_SYNC_WORD2 0x10E /* RW sync word bits [23:16] of [23:0] */ +#define REG_SYNC_CONFIG 0x10F /* RW sync word configuration */ +#define REG_RC_CFG 0x13E /* RW RX / TX packet configuration */ +#define REG_RC_VAR44 0x13F /* RW RESERVED */ +#define REG_CH_FREQ0 0x300 /* RW Channel Frequency Settings - Low */ +#define REG_CH_FREQ1 0x301 /* RW Channel Frequency Settings - Middle */ +#define REG_CH_FREQ2 0x302 /* RW Channel Frequency Settings - High */ +#define REG_TX_FD 0x304 /* RW TX Frequency Deviation Register */ +#define REG_DM_CFG0 0x305 /* RW RX Discriminator BW Register */ +#define REG_TX_M 0x306 /* RW TX Mode Register */ +#define REG_RX_M 0x307 /* RW RX Mode Register */ +#define REG_RRB 0x30C /* R RSSI Readback Register */ +#define REG_LRB 0x30D /* R Link Quality Readback Register */ +#define REG_DR0 0x30E /* RW bits [15:8] of [15:0] data rate setting */ +#define REG_DR1 0x30F /* RW bits [7:0] of [15:0] data rate setting */ +#define REG_PRAMPG 0x313 /* RW RESERVED */ +#define REG_TXPB 0x314 /* RW TX Packet Storage Base Address */ +#define REG_RXPB 0x315 /* RW RX Packet Storage Base Address */ +#define REG_TMR_CFG0 0x316 /* RW Wake up Timer Conf Register - High */ +#define REG_TMR_CFG1 0x317 /* RW Wake up Timer Conf Register - Low */ +#define REG_TMR_RLD0 0x318 /* RW Wake up Timer Value Register - High */ +#define REG_TMR_RLD1 0x319 /* RW Wake up Timer Value Register - Low */ +#define REG_TMR_CTRL 0x31A /* RW Wake up Timer Timeout flag */ +#define REG_PD_AUX 0x31E /* RW Battmon enable */ +#define REG_GP_CFG 0x32C /* RW GPIO Configuration */ +#define REG_GP_OUT 0x32D /* RW GPIO Configuration */ +#define REG_GP_IN 0x32E /* R GPIO Configuration */ +#define REG_SYNT 0x335 /* RW bandwidth calibration timers */ +#define REG_CAL_CFG 0x33D /* RW Calibration Settings */ +#define REG_PA_BIAS 0x36E /* RW PA BIAS */ +#define REG_SYNT_CAL 0x371 /* RW Oscillator and Doubler Configuration */ +#define REG_IIRF_CFG 0x389 /* RW BB Filter Decimation Rate */ +#define REG_CDR_CFG 0x38A /* RW CDR kVCO */ +#define REG_DM_CFG1 0x38B /* RW Postdemodulator Filter */ +#define REG_AGCSTAT 0x38E /* R RXBB Ref Osc Calibration Engine Readback */ +#define REG_RXCAL0 0x395 /* RW RX BB filter tuning, LSB */ +#define REG_RXCAL1 0x396 /* RW RX BB filter tuning, MSB */ +#define REG_RXFE_CFG 0x39B /* RW RXBB Ref Osc & RXFE Calibration */ +#define REG_PA_RR 0x3A7 /* RW Set PA ramp rate */ +#define REG_PA_CFG 0x3A8 /* RW PA enable */ +#define REG_EXTPA_CFG 0x3A9 /* RW External PA BIAS DAC */ +#define REG_EXTPA_MSC 0x3AA /* RW PA Bias Mode */ +#define REG_ADC_RBK 0x3AE /* R Readback temp */ +#define REG_AGC_CFG1 0x3B2 /* RW GC Parameters */ +#define REG_AGC_MAX 0x3B4 /* RW Slew rate */ +#define REG_AGC_CFG2 0x3B6 /* RW RSSI Parameters */ +#define REG_AGC_CFG3 0x3B7 /* RW RSSI Parameters */ +#define REG_AGC_CFG4 0x3B8 /* RW RSSI Parameters */ +#define REG_AGC_CFG5 0x3B9 /* RW RSSI & NDEC Parameters */ +#define REG_AGC_CFG6 0x3BA /* RW NDEC Parameters */ +#define REG_OCL_CFG1 0x3C4 /* RW OCL System Parameters */ +#define REG_IRQ1_EN0 0x3C7 /* RW Interrupt Mask set bits for IRQ1 */ +#define REG_IRQ1_EN1 0x3C8 /* RW Interrupt Mask set bits for IRQ1 */ +#define REG_IRQ2_EN0 0x3C9 /* RW Interrupt Mask set bits for IRQ2 */ +#define REG_IRQ2_EN1 0x3CA /* RW Interrupt Mask set bits for IRQ2 */ +#define REG_IRQ1_SRC0 0x3CB /* RW Interrupt Source bits for IRQ */ +#define REG_IRQ1_SRC1 0x3CC /* RW Interrupt Source bits for IRQ */ +#define REG_OCL_BW0 0x3D2 /* RW OCL System Parameters */ +#define REG_OCL_BW1 0x3D3 /* RW OCL System Parameters */ +#define REG_OCL_BW2 0x3D4 /* RW OCL System Parameters */ +#define REG_OCL_BW3 0x3D5 /* RW OCL System Parameters */ +#define REG_OCL_BW4 0x3D6 /* RW OCL System Parameters */ +#define REG_OCL_BWS 0x3D7 /* RW OCL System Parameters */ +#define REG_OCL_CFG13 0x3E0 /* RW OCL System Parameters */ +#define REG_GP_DRV 0x3E3 /* RW I/O pads Configuration and bg trim */ +#define REG_BM_CFG 0x3E6 /* RW Batt. Monitor Threshold Voltage setting */ +#define REG_SFD_15_4 0x3F4 /* RW Option to set non standard SFD */ +#define REG_AFC_CFG 0x3F7 /* RW AFC mode and polarity */ +#define REG_AFC_KI_KP 0x3F8 /* RW AFC ki and kp */ +#define REG_AFC_RANGE 0x3F9 /* RW AFC range */ +#define REG_AFC_READ 0x3FA /* RW Readback frequency error */ + +/* REG_EXTPA_MSC */ +#define PA_PWR(x) (((x) & 0xF) << 4) +#define EXTPA_BIAS_SRC BIT(3) +#define EXTPA_BIAS_MODE(x) (((x) & 0x7) << 0) + +/* REG_PA_CFG */ +#define PA_BRIDGE_DBIAS(x) (((x) & 0x1F) << 0) +#define PA_DBIAS_HIGH_POWER 21 +#define PA_DBIAS_LOW_POWER 13 + +/* REG_PA_BIAS */ +#define PA_BIAS_CTRL(x) (((x) & 0x1F) << 1) +#define REG_PA_BIAS_DFL BIT(0) +#define PA_BIAS_HIGH_POWER 63 +#define PA_BIAS_LOW_POWER 55 + +#define REG_PAN_ID0 0x112 +#define REG_PAN_ID1 0x113 +#define REG_SHORT_ADDR_0 0x114 +#define REG_SHORT_ADDR_1 0x115 +#define REG_IEEE_ADDR_0 0x116 +#define REG_IEEE_ADDR_1 0x117 +#define REG_IEEE_ADDR_2 0x118 +#define REG_IEEE_ADDR_3 0x119 +#define REG_IEEE_ADDR_4 0x11A +#define REG_IEEE_ADDR_5 0x11B +#define REG_IEEE_ADDR_6 0x11C +#define REG_IEEE_ADDR_7 0x11D +#define REG_FFILT_CFG 0x11E +#define REG_AUTO_CFG 0x11F +#define REG_AUTO_TX1 0x120 +#define REG_AUTO_TX2 0x121 +#define REG_AUTO_STATUS 0x122 + +/* REG_FFILT_CFG */ +#define ACCEPT_BEACON_FRAMES BIT(0) +#define ACCEPT_DATA_FRAMES BIT(1) +#define ACCEPT_ACK_FRAMES BIT(2) +#define ACCEPT_MACCMD_FRAMES BIT(3) +#define ACCEPT_RESERVED_FRAMES BIT(4) +#define ACCEPT_ALL_ADDRESS BIT(5) + +/* REG_AUTO_CFG */ +#define AUTO_ACK_FRAMEPEND BIT(0) +#define IS_PANCOORD BIT(1) +#define RX_AUTO_ACK_EN BIT(3) +#define CSMA_CA_RX_TURNAROUND BIT(4) + +/* REG_AUTO_TX1 */ +#define MAX_FRAME_RETRIES(x) ((x) & 0xF) +#define MAX_CCA_RETRIES(x) (((x) & 0x7) << 4) + +/* REG_AUTO_TX2 */ +#define CSMA_MAX_BE(x) ((x) & 0xF) +#define CSMA_MIN_BE(x) (((x) & 0xF) << 4) + +#define CMD_SPI_NOP 0xFF /* No operation. Use for dummy writes */ +#define CMD_SPI_PKT_WR 0x10 /* Write telegram to the Packet RAM + * starting from the TX packet base address + * pointer tx_packet_base + */ +#define CMD_SPI_PKT_RD 0x30 /* Read telegram from the Packet RAM + * starting from RX packet base address + * pointer rxpb.rx_packet_base + */ +#define CMD_SPI_MEM_WR(x) (0x18 + (x >> 8)) /* Write data to MCR or + * Packet RAM sequentially + */ +#define CMD_SPI_MEM_RD(x) (0x38 + (x >> 8)) /* Read data from MCR or + * Packet RAM sequentially + */ +#define CMD_SPI_MEMR_WR(x) (0x08 + (x >> 8)) /* Write data to MCR or Packet + * RAM as random block + */ +#define CMD_SPI_MEMR_RD(x) (0x28 + (x >> 8)) /* Read data from MCR or + * Packet RAM random block + */ +#define CMD_SPI_PRAM_WR 0x1E /* Write data sequentially to current + * PRAM page selected + */ +#define CMD_SPI_PRAM_RD 0x3E /* Read data sequentially from current + * PRAM page selected + */ +#define CMD_RC_SLEEP 0xB1 /* Invoke transition of radio controller + * into SLEEP state + */ +#define CMD_RC_IDLE 0xB2 /* Invoke transition of radio controller + * into IDLE state + */ +#define CMD_RC_PHY_RDY 0xB3 /* Invoke transition of radio controller + * into PHY_RDY state + */ +#define CMD_RC_RX 0xB4 /* Invoke transition of radio controller + * into RX state + */ +#define CMD_RC_TX 0xB5 /* Invoke transition of radio controller + * into TX state + */ +#define CMD_RC_MEAS 0xB6 /* Invoke transition of radio controller + * into MEAS state + */ +#define CMD_RC_CCA 0xB7 /* Invoke Clear channel assessment */ +#define CMD_RC_CSMACA 0xC1 /* initiates CSMA-CA channel access + * sequence and frame transmission + */ +#define CMD_RC_PC_RESET 0xC7 /* Program counter reset */ +#define CMD_RC_RESET 0xC8 /* Resets the ADF7242 and puts it in + * the sleep state + */ +#define CMD_RC_PC_RESET_NO_WAIT (CMD_RC_PC_RESET | BIT(31)) + +/* STATUS */ + +#define STAT_SPI_READY BIT(7) +#define STAT_IRQ_STATUS BIT(6) +#define STAT_RC_READY BIT(5) +#define STAT_CCA_RESULT BIT(4) +#define RC_STATUS_IDLE 1 +#define RC_STATUS_MEAS 2 +#define RC_STATUS_PHY_RDY 3 +#define RC_STATUS_RX 4 +#define RC_STATUS_TX 5 +#define RC_STATUS_MASK 0xF + +/* AUTO_STATUS */ + +#define SUCCESS 0 +#define SUCCESS_DATPEND 1 +#define FAILURE_CSMACA 2 +#define FAILURE_NOACK 3 +#define AUTO_STATUS_MASK 0x3 + +#define PRAM_PAGESIZE 256 + +/* IRQ1 */ + +#define IRQ_CCA_COMPLETE BIT(0) +#define IRQ_SFD_RX BIT(1) +#define IRQ_SFD_TX BIT(2) +#define IRQ_RX_PKT_RCVD BIT(3) +#define IRQ_TX_PKT_SENT BIT(4) +#define IRQ_FRAME_VALID BIT(5) +#define IRQ_ADDRESS_VALID BIT(6) +#define IRQ_CSMA_CA BIT(7) + +#define AUTO_TX_TURNAROUND BIT(3) +#define ADDON_EN BIT(4) + +#define FLAG_XMIT 0 +#define FLAG_START 1 + +#define ADF7242_REPORT_CSMA_CA_STAT 0 /* framework doesn't handle yet */ + +struct adf7242_local { + struct spi_device *spi; + struct completion tx_complete; + struct ieee802154_hw *hw; + struct mutex bmux; /* protect SPI messages */ + struct spi_message stat_msg; + struct spi_transfer stat_xfer; + struct dentry *debugfs_root; + unsigned long flags; + int tx_stat; + bool promiscuous; + s8 rssi; + u8 max_frame_retries; + u8 max_cca_retries; + u8 max_be; + u8 min_be; + + /* DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + + u8 buf[3] ____cacheline_aligned; + u8 buf_reg_tx[3]; + u8 buf_read_tx[4]; + u8 buf_read_rx[4]; + u8 buf_stat_rx; + u8 buf_stat_tx; + u8 buf_cmd; +}; + +static int adf7242_soft_reset(struct adf7242_local *lp, int line); + +static int adf7242_status(struct adf7242_local *lp, u8 *stat) +{ + int status; + + mutex_lock(&lp->bmux); + status = spi_sync(lp->spi, &lp->stat_msg); + *stat = lp->buf_stat_rx; + mutex_unlock(&lp->bmux); + + return status; +} + +static int adf7242_wait_status(struct adf7242_local *lp, unsigned status, + unsigned mask, int line) +{ + int cnt = 0, ret = 0; + u8 stat; + + do { + adf7242_status(lp, &stat); + cnt++; + } while (((stat & mask) != status) && (cnt < MAX_POLL_LOOPS)); + + if (cnt >= MAX_POLL_LOOPS) { + ret = -ETIMEDOUT; + + if (!(stat & STAT_RC_READY)) { + adf7242_soft_reset(lp, line); + adf7242_status(lp, &stat); + + if ((stat & mask) == status) + ret = 0; + } + + if (ret < 0) + dev_warn(&lp->spi->dev, + "%s:line %d Timeout status 0x%x (%d)\n", + __func__, line, stat, cnt); + } + + dev_vdbg(&lp->spi->dev, "%s : loops=%d line %d\n", __func__, cnt, line); + + return ret; +} + +static int adf7242_wait_ready(struct adf7242_local *lp, int line) +{ + return adf7242_wait_status(lp, STAT_RC_READY | STAT_SPI_READY, + STAT_RC_READY | STAT_SPI_READY, line); +} + +static int adf7242_write_fbuf(struct adf7242_local *lp, u8 *data, u8 len) +{ + u8 *buf = lp->buf; + int status; + struct spi_message msg; + struct spi_transfer xfer_head = { + .len = 2, + .tx_buf = buf, + + }; + struct spi_transfer xfer_buf = { + .len = len, + .tx_buf = data, + }; + + spi_message_init(&msg); + spi_message_add_tail(&xfer_head, &msg); + spi_message_add_tail(&xfer_buf, &msg); + + adf7242_wait_ready(lp, __LINE__); + + mutex_lock(&lp->bmux); + buf[0] = CMD_SPI_PKT_WR; + buf[1] = len + 2; + + status = spi_sync(lp->spi, &msg); + mutex_unlock(&lp->bmux); + + return status; +} + +static int adf7242_read_fbuf(struct adf7242_local *lp, + u8 *data, size_t len, bool packet_read) +{ + u8 *buf = lp->buf; + int status; + struct spi_message msg; + struct spi_transfer xfer_head = { + .len = 3, + .tx_buf = buf, + .rx_buf = buf, + }; + struct spi_transfer xfer_buf = { + .len = len, + .rx_buf = data, + }; + + spi_message_init(&msg); + spi_message_add_tail(&xfer_head, &msg); + spi_message_add_tail(&xfer_buf, &msg); + + adf7242_wait_ready(lp, __LINE__); + + mutex_lock(&lp->bmux); + if (packet_read) { + buf[0] = CMD_SPI_PKT_RD; + buf[1] = CMD_SPI_NOP; + buf[2] = 0; /* PHR */ + } else { + buf[0] = CMD_SPI_PRAM_RD; + buf[1] = 0; + buf[2] = CMD_SPI_NOP; + } + + status = spi_sync(lp->spi, &msg); + + mutex_unlock(&lp->bmux); + + return status; +} + +static int adf7242_read_reg(struct adf7242_local *lp, u16 addr, u8 *data) +{ + int status; + struct spi_message msg; + + struct spi_transfer xfer = { + .len = 4, + .tx_buf = lp->buf_read_tx, + .rx_buf = lp->buf_read_rx, + }; + + adf7242_wait_ready(lp, __LINE__); + + mutex_lock(&lp->bmux); + lp->buf_read_tx[0] = CMD_SPI_MEM_RD(addr); + lp->buf_read_tx[1] = addr; + lp->buf_read_tx[2] = CMD_SPI_NOP; + lp->buf_read_tx[3] = CMD_SPI_NOP; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + status = spi_sync(lp->spi, &msg); + if (msg.status) + status = msg.status; + + if (!status) + *data = lp->buf_read_rx[3]; + + mutex_unlock(&lp->bmux); + + dev_vdbg(&lp->spi->dev, "%s : REG 0x%X, VAL 0x%X\n", __func__, + addr, *data); + + return status; +} + +static int adf7242_write_reg(struct adf7242_local *lp, u16 addr, u8 data) +{ + int status; + + adf7242_wait_ready(lp, __LINE__); + + mutex_lock(&lp->bmux); + lp->buf_reg_tx[0] = CMD_SPI_MEM_WR(addr); + lp->buf_reg_tx[1] = addr; + lp->buf_reg_tx[2] = data; + status = spi_write(lp->spi, lp->buf_reg_tx, 3); + mutex_unlock(&lp->bmux); + + dev_vdbg(&lp->spi->dev, "%s : REG 0x%X, VAL 0x%X\n", + __func__, addr, data); + + return status; +} + +static int adf7242_cmd(struct adf7242_local *lp, unsigned cmd) +{ + int status; + + dev_vdbg(&lp->spi->dev, "%s : CMD=0x%X\n", __func__, cmd); + + if (cmd != CMD_RC_PC_RESET_NO_WAIT) + adf7242_wait_ready(lp, __LINE__); + + mutex_lock(&lp->bmux); + lp->buf_cmd = cmd; + status = spi_write(lp->spi, &lp->buf_cmd, 1); + mutex_unlock(&lp->bmux); + + return status; +} + +static int adf7242_upload_firmware(struct adf7242_local *lp, u8 *data, u16 len) +{ + struct spi_message msg; + struct spi_transfer xfer_buf = { }; + int status, i, page = 0; + u8 *buf = lp->buf; + + struct spi_transfer xfer_head = { + .len = 2, + .tx_buf = buf, + }; + + buf[0] = CMD_SPI_PRAM_WR; + buf[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfer_head, &msg); + spi_message_add_tail(&xfer_buf, &msg); + + for (i = len; i >= 0; i -= PRAM_PAGESIZE) { + adf7242_write_reg(lp, REG_PRAMPG, page); + + xfer_buf.len = (i >= PRAM_PAGESIZE) ? PRAM_PAGESIZE : i; + xfer_buf.tx_buf = &data[page * PRAM_PAGESIZE]; + + mutex_lock(&lp->bmux); + status = spi_sync(lp->spi, &msg); + mutex_unlock(&lp->bmux); + page++; + } + + return status; +} + +static int adf7242_verify_firmware(struct adf7242_local *lp, + const u8 *data, size_t len) +{ +#ifdef DEBUG + int i, j; + unsigned int page; + u8 *buf = kmalloc(PRAM_PAGESIZE, GFP_KERNEL); + + if (!buf) + return -ENOMEM; + + for (page = 0, i = len; i >= 0; i -= PRAM_PAGESIZE, page++) { + size_t nb = (i >= PRAM_PAGESIZE) ? PRAM_PAGESIZE : i; + + adf7242_write_reg(lp, REG_PRAMPG, page); + adf7242_read_fbuf(lp, buf, nb, false); + + for (j = 0; j < nb; j++) { + if (buf[j] != data[page * PRAM_PAGESIZE + j]) { + kfree(buf); + return -EIO; + } + } + } + kfree(buf); +#endif + return 0; +} + +static int adf7242_set_txpower(struct ieee802154_hw *hw, int mbm) +{ + struct adf7242_local *lp = hw->priv; + u8 pwr, bias_ctrl, dbias, tmp; + int db = mbm / 100; + + dev_vdbg(&lp->spi->dev, "%s : Power %d dB\n", __func__, db); + + if (db > 5 || db < -26) + return -EINVAL; + + db = DIV_ROUND_CLOSEST(db + 29, 2); + + if (db > 15) { + dbias = PA_DBIAS_HIGH_POWER; + bias_ctrl = PA_BIAS_HIGH_POWER; + } else { + dbias = PA_DBIAS_LOW_POWER; + bias_ctrl = PA_BIAS_LOW_POWER; + } + + pwr = clamp_t(u8, db, 3, 15); + + adf7242_read_reg(lp, REG_PA_CFG, &tmp); + tmp &= ~PA_BRIDGE_DBIAS(~0); + tmp |= PA_BRIDGE_DBIAS(dbias); + adf7242_write_reg(lp, REG_PA_CFG, tmp); + + adf7242_read_reg(lp, REG_PA_BIAS, &tmp); + tmp &= ~PA_BIAS_CTRL(~0); + tmp |= PA_BIAS_CTRL(bias_ctrl); + adf7242_write_reg(lp, REG_PA_BIAS, tmp); + + adf7242_read_reg(lp, REG_EXTPA_MSC, &tmp); + tmp &= ~PA_PWR(~0); + tmp |= PA_PWR(pwr); + + return adf7242_write_reg(lp, REG_EXTPA_MSC, tmp); +} + +static int adf7242_set_csma_params(struct ieee802154_hw *hw, u8 min_be, + u8 max_be, u8 retries) +{ + struct adf7242_local *lp = hw->priv; + int ret; + + dev_vdbg(&lp->spi->dev, "%s : min_be=%d max_be=%d retries=%d\n", + __func__, min_be, max_be, retries); + + if (min_be > max_be || max_be > 8 || retries > 5) + return -EINVAL; + + ret = adf7242_write_reg(lp, REG_AUTO_TX1, + MAX_FRAME_RETRIES(lp->max_frame_retries) | + MAX_CCA_RETRIES(retries)); + if (ret) + return ret; + + lp->max_cca_retries = retries; + lp->max_be = max_be; + lp->min_be = min_be; + + return adf7242_write_reg(lp, REG_AUTO_TX2, CSMA_MAX_BE(max_be) | + CSMA_MIN_BE(min_be)); +} + +static int adf7242_set_frame_retries(struct ieee802154_hw *hw, s8 retries) +{ + struct adf7242_local *lp = hw->priv; + int ret = 0; + + dev_vdbg(&lp->spi->dev, "%s : Retries = %d\n", __func__, retries); + + if (retries < -1 || retries > 15) + return -EINVAL; + + if (retries >= 0) + ret = adf7242_write_reg(lp, REG_AUTO_TX1, + MAX_FRAME_RETRIES(retries) | + MAX_CCA_RETRIES(lp->max_cca_retries)); + + lp->max_frame_retries = retries; + + return ret; +} + +static int adf7242_ed(struct ieee802154_hw *hw, u8 *level) +{ + struct adf7242_local *lp = hw->priv; + + *level = lp->rssi; + + dev_vdbg(&lp->spi->dev, "%s :Exit level=%d\n", + __func__, *level); + + return 0; +} + +static int adf7242_start(struct ieee802154_hw *hw) +{ + struct adf7242_local *lp = hw->priv; + + adf7242_cmd(lp, CMD_RC_PHY_RDY); + adf7242_write_reg(lp, REG_IRQ1_SRC1, 0xFF); + enable_irq(lp->spi->irq); + set_bit(FLAG_START, &lp->flags); + + return adf7242_cmd(lp, CMD_RC_RX); +} + +static void adf7242_stop(struct ieee802154_hw *hw) +{ + struct adf7242_local *lp = hw->priv; + + adf7242_cmd(lp, CMD_RC_IDLE); + clear_bit(FLAG_START, &lp->flags); + disable_irq(lp->spi->irq); + adf7242_write_reg(lp, REG_IRQ1_SRC1, 0xFF); +} + +static int adf7242_channel(struct ieee802154_hw *hw, u8 page, u8 channel) +{ + struct adf7242_local *lp = hw->priv; + unsigned long freq; + + dev_dbg(&lp->spi->dev, "%s :Channel=%d\n", __func__, channel); + + might_sleep(); + + WARN_ON(page != 0); + WARN_ON(channel < 11); + WARN_ON(channel > 26); + + freq = (2405 + 5 * (channel - 11)) * 100; + adf7242_cmd(lp, CMD_RC_PHY_RDY); + + adf7242_write_reg(lp, REG_CH_FREQ0, freq); + adf7242_write_reg(lp, REG_CH_FREQ1, freq >> 8); + adf7242_write_reg(lp, REG_CH_FREQ2, freq >> 16); + + return adf7242_cmd(lp, CMD_RC_RX); +} + +static int adf7242_set_hw_addr_filt(struct ieee802154_hw *hw, + struct ieee802154_hw_addr_filt *filt, + unsigned long changed) +{ + struct adf7242_local *lp = hw->priv; + u8 reg; + + dev_dbg(&lp->spi->dev, "%s :Changed=0x%lX\n", __func__, changed); + + might_sleep(); + + if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) { + u8 addr[8], i; + + memcpy(addr, &filt->ieee_addr, 8); + + for (i = 0; i < 8; i++) + adf7242_write_reg(lp, REG_IEEE_ADDR_0 + i, addr[i]); + } + + if (changed & IEEE802154_AFILT_SADDR_CHANGED) { + u16 saddr = le16_to_cpu(filt->short_addr); + + adf7242_write_reg(lp, REG_SHORT_ADDR_0, saddr); + adf7242_write_reg(lp, REG_SHORT_ADDR_1, saddr >> 8); + } + + if (changed & IEEE802154_AFILT_PANID_CHANGED) { + u16 pan_id = le16_to_cpu(filt->pan_id); + + adf7242_write_reg(lp, REG_PAN_ID0, pan_id); + adf7242_write_reg(lp, REG_PAN_ID1, pan_id >> 8); + } + + if (changed & IEEE802154_AFILT_PANC_CHANGED) { + adf7242_read_reg(lp, REG_AUTO_CFG, ®); + if (filt->pan_coord) + reg |= IS_PANCOORD; + else + reg &= ~IS_PANCOORD; + adf7242_write_reg(lp, REG_AUTO_CFG, reg); + } + + return 0; +} + +static int adf7242_set_promiscuous_mode(struct ieee802154_hw *hw, bool on) +{ + struct adf7242_local *lp = hw->priv; + + dev_dbg(&lp->spi->dev, "%s : mode %d\n", __func__, on); + + lp->promiscuous = on; + + if (on) { + adf7242_write_reg(lp, REG_AUTO_CFG, 0); + return adf7242_write_reg(lp, REG_FFILT_CFG, + ACCEPT_BEACON_FRAMES | + ACCEPT_DATA_FRAMES | + ACCEPT_MACCMD_FRAMES | + ACCEPT_ALL_ADDRESS | + ACCEPT_ACK_FRAMES | + ACCEPT_RESERVED_FRAMES); + } else { + adf7242_write_reg(lp, REG_FFILT_CFG, + ACCEPT_BEACON_FRAMES | + ACCEPT_DATA_FRAMES | + ACCEPT_MACCMD_FRAMES | + ACCEPT_RESERVED_FRAMES); + + return adf7242_write_reg(lp, REG_AUTO_CFG, RX_AUTO_ACK_EN); + } +} + +static int adf7242_set_cca_ed_level(struct ieee802154_hw *hw, s32 mbm) +{ + struct adf7242_local *lp = hw->priv; + s8 level = clamp_t(s8, mbm / 100, S8_MIN, S8_MAX); + + dev_dbg(&lp->spi->dev, "%s : level %d\n", __func__, level); + + return adf7242_write_reg(lp, REG_CCA1, level); +} + +static int adf7242_xmit(struct ieee802154_hw *hw, struct sk_buff *skb) +{ + struct adf7242_local *lp = hw->priv; + int ret; + + set_bit(FLAG_XMIT, &lp->flags); + reinit_completion(&lp->tx_complete); + adf7242_cmd(lp, CMD_RC_PHY_RDY); + + ret = adf7242_write_fbuf(lp, skb->data, skb->len); + if (ret) + goto err; + + ret = adf7242_cmd(lp, CMD_RC_CSMACA); + if (ret) + goto err; + + ret = wait_for_completion_interruptible_timeout(&lp->tx_complete, + HZ / 10); + if (ret < 0) + goto err; + if (ret == 0) { + dev_dbg(&lp->spi->dev, "Timeout waiting for TX interrupt\n"); + ret = -ETIMEDOUT; + goto err; + } + + if (lp->tx_stat != SUCCESS) { + dev_dbg(&lp->spi->dev, + "Error xmit: Retry count exceeded Status=0x%x\n", + lp->tx_stat); + ret = -ECOMM; + } else { + ret = 0; + } + +err: + clear_bit(FLAG_XMIT, &lp->flags); + adf7242_cmd(lp, CMD_RC_RX); + + return ret; +} + +static int adf7242_rx(struct adf7242_local *lp) +{ + struct sk_buff *skb; + size_t len; + int ret; + u8 lqi, len_u8, *data; + + adf7242_read_reg(lp, 0, &len_u8); + + len = len_u8; + + if (!ieee802154_is_valid_psdu_len(len)) { + dev_dbg(&lp->spi->dev, + "corrupted frame received len %d\n", (int)len); + len = IEEE802154_MTU; + } + + skb = dev_alloc_skb(len); + if (!skb) { + adf7242_cmd(lp, CMD_RC_RX); + return -ENOMEM; + } + + data = skb_put(skb, len); + ret = adf7242_read_fbuf(lp, data, len, true); + if (ret < 0) { + kfree_skb(skb); + adf7242_cmd(lp, CMD_RC_RX); + return ret; + } + + lqi = data[len - 2]; + lp->rssi = data[len - 1]; + + adf7242_cmd(lp, CMD_RC_RX); + + skb_trim(skb, len - 2); /* Don't put RSSI/LQI or CRC into the frame */ + + ieee802154_rx_irqsafe(lp->hw, skb, lqi); + + dev_dbg(&lp->spi->dev, "%s: ret=%d len=%d lqi=%d rssi=%d\n", + __func__, ret, (int)len, (int)lqi, lp->rssi); + + return 0; +} + +static struct ieee802154_ops adf7242_ops = { + .owner = THIS_MODULE, + .xmit_sync = adf7242_xmit, + .ed = adf7242_ed, + .set_channel = adf7242_channel, + .set_hw_addr_filt = adf7242_set_hw_addr_filt, + .start = adf7242_start, + .stop = adf7242_stop, + .set_csma_params = adf7242_set_csma_params, + .set_frame_retries = adf7242_set_frame_retries, + .set_txpower = adf7242_set_txpower, + .set_promiscuous_mode = adf7242_set_promiscuous_mode, + .set_cca_ed_level = adf7242_set_cca_ed_level, +}; + +static void adf7242_debug(u8 irq1) +{ +#ifdef DEBUG + u8 stat; + + adf7242_status(lp, &stat); + + dev_dbg(&lp->spi->dev, "%s IRQ1 = %X:\n%s%s%s%s%s%s%s%s\n", + __func__, irq1, + irq1 & IRQ_CCA_COMPLETE ? "IRQ_CCA_COMPLETE\n" : "", + irq1 & IRQ_SFD_RX ? "IRQ_SFD_RX\n" : "", + irq1 & IRQ_SFD_TX ? "IRQ_SFD_TX\n" : "", + irq1 & IRQ_RX_PKT_RCVD ? "IRQ_RX_PKT_RCVD\n" : "", + irq1 & IRQ_TX_PKT_SENT ? "IRQ_TX_PKT_SENT\n" : "", + irq1 & IRQ_CSMA_CA ? "IRQ_CSMA_CA\n" : "", + irq1 & IRQ_FRAME_VALID ? "IRQ_FRAME_VALID\n" : "", + irq1 & IRQ_ADDRESS_VALID ? "IRQ_ADDRESS_VALID\n" : ""); + + dev_dbg(&lp->spi->dev, "%s STATUS = %X:\n%s\n%s%s%s%s%s\n", + __func__, stat, + stat & STAT_RC_READY ? "RC_READY" : "RC_BUSY", + (stat & 0xf) == RC_STATUS_IDLE ? "RC_STATUS_IDLE" : "", + (stat & 0xf) == RC_STATUS_MEAS ? "RC_STATUS_MEAS" : "", + (stat & 0xf) == RC_STATUS_PHY_RDY ? "RC_STATUS_PHY_RDY" : "", + (stat & 0xf) == RC_STATUS_RX ? "RC_STATUS_RX" : "", + (stat & 0xf) == RC_STATUS_TX ? "RC_STATUS_TX" : ""); + } +#endif +} + +static irqreturn_t adf7242_isr(int irq, void *data) +{ + struct adf7242_local *lp = data; + unsigned xmit; + u8 irq1; + + adf7242_wait_status(lp, RC_STATUS_PHY_RDY, RC_STATUS_MASK, __LINE__); + + adf7242_read_reg(lp, REG_IRQ1_SRC1, &irq1); + adf7242_write_reg(lp, REG_IRQ1_SRC1, irq1); + + if (!(irq1 & (IRQ_RX_PKT_RCVD | IRQ_CSMA_CA))) + dev_err(&lp->spi->dev, "%s :ERROR IRQ1 = 0x%X\n", + __func__, irq1); + + adf7242_debug(irq1); + + xmit = test_bit(FLAG_XMIT, &lp->flags); + + if (xmit && (irq1 & IRQ_CSMA_CA)) { + if (ADF7242_REPORT_CSMA_CA_STAT) { + u8 astat; + + adf7242_read_reg(lp, REG_AUTO_STATUS, &astat); + astat &= AUTO_STATUS_MASK; + + dev_dbg(&lp->spi->dev, "AUTO_STATUS = %X:\n%s%s%s%s\n", + astat, + astat == SUCCESS ? "SUCCESS" : "", + astat == + SUCCESS_DATPEND ? "SUCCESS_DATPEND" : "", + astat == FAILURE_CSMACA ? "FAILURE_CSMACA" : "", + astat == FAILURE_NOACK ? "FAILURE_NOACK" : ""); + + /* save CSMA-CA completion status */ + lp->tx_stat = astat; + } else { + lp->tx_stat = SUCCESS; + } + complete(&lp->tx_complete); + } else if (!xmit && (irq1 & IRQ_RX_PKT_RCVD) && + (irq1 & IRQ_FRAME_VALID)) { + adf7242_rx(lp); + } else if (!xmit && test_bit(FLAG_START, &lp->flags)) { + /* Invalid packet received - drop it and restart */ + dev_dbg(&lp->spi->dev, "%s:%d : ERROR IRQ1 = 0x%X\n", + __func__, __LINE__, irq1); + adf7242_cmd(lp, CMD_RC_PHY_RDY); + adf7242_write_reg(lp, REG_IRQ1_SRC1, 0xFF); + adf7242_cmd(lp, CMD_RC_RX); + } else { + /* This can only be xmit without IRQ, likely a RX packet. + * we get an TX IRQ shortly - do nothing or let the xmit + * timeout handle this + */ + dev_dbg(&lp->spi->dev, "%s:%d : ERROR IRQ1 = 0x%X, xmit %d\n", + __func__, __LINE__, irq1, xmit); + complete(&lp->tx_complete); + } + + return IRQ_HANDLED; +} + +static int adf7242_soft_reset(struct adf7242_local *lp, int line) +{ + dev_warn(&lp->spi->dev, "%s (line %d)\n", __func__, line); + + if (test_bit(FLAG_START, &lp->flags)) + disable_irq_nosync(lp->spi->irq); + + adf7242_cmd(lp, CMD_RC_PC_RESET_NO_WAIT); + usleep_range(200, 250); + adf7242_write_reg(lp, REG_PKT_CFG, ADDON_EN | BIT(2)); + adf7242_cmd(lp, CMD_RC_PHY_RDY); + adf7242_set_promiscuous_mode(lp->hw, lp->promiscuous); + adf7242_set_csma_params(lp->hw, lp->min_be, lp->max_be, + lp->max_cca_retries); + adf7242_write_reg(lp, REG_IRQ1_SRC1, 0xFF); + + if (test_bit(FLAG_START, &lp->flags)) { + enable_irq(lp->spi->irq); + return adf7242_cmd(lp, CMD_RC_RX); + } + + return 0; +} + +static int adf7242_hw_init(struct adf7242_local *lp) +{ + int ret; + const struct firmware *fw; + + adf7242_cmd(lp, CMD_RC_RESET); + adf7242_cmd(lp, CMD_RC_IDLE); + + /* get ADF7242 addon firmware + * build this driver as module + * and place under /lib/firmware/adf7242_firmware.bin + * or compile firmware into the kernel. + */ + ret = request_firmware(&fw, FIRMWARE, &lp->spi->dev); + if (ret) { + dev_err(&lp->spi->dev, + "request_firmware() failed with %d\n", ret); + return ret; + } + + ret = adf7242_upload_firmware(lp, (u8 *)fw->data, fw->size); + if (ret) { + dev_err(&lp->spi->dev, + "upload firmware failed with %d\n", ret); + return ret; + } + + ret = adf7242_verify_firmware(lp, (u8 *)fw->data, fw->size); + if (ret) { + dev_err(&lp->spi->dev, + "verify firmware failed with %d\n", ret); + return ret; + } + + adf7242_cmd(lp, CMD_RC_PC_RESET); + + release_firmware(fw); + + adf7242_write_reg(lp, REG_FFILT_CFG, + ACCEPT_BEACON_FRAMES | + ACCEPT_DATA_FRAMES | + ACCEPT_MACCMD_FRAMES | + ACCEPT_RESERVED_FRAMES); + + adf7242_write_reg(lp, REG_AUTO_CFG, RX_AUTO_ACK_EN); + + adf7242_write_reg(lp, REG_PKT_CFG, ADDON_EN | BIT(2)); + + adf7242_write_reg(lp, REG_EXTPA_MSC, 0xF1); + adf7242_write_reg(lp, REG_RXFE_CFG, 0x1D); + + adf7242_write_reg(lp, REG_IRQ1_EN0, 0); + adf7242_write_reg(lp, REG_IRQ1_EN1, IRQ_RX_PKT_RCVD | IRQ_CSMA_CA); + + adf7242_write_reg(lp, REG_IRQ1_SRC1, 0xFF); + adf7242_write_reg(lp, REG_IRQ1_SRC0, 0xFF); + + adf7242_cmd(lp, CMD_RC_IDLE); + + return 0; +} + +static int adf7242_stats_show(struct seq_file *file, void *offset) +{ + struct adf7242_local *lp = spi_get_drvdata(file->private); + u8 stat, irq1; + + adf7242_status(lp, &stat); + adf7242_read_reg(lp, REG_IRQ1_SRC1, &irq1); + + seq_printf(file, "IRQ1 = %X:\n%s%s%s%s%s%s%s%s\n", irq1, + irq1 & IRQ_CCA_COMPLETE ? "IRQ_CCA_COMPLETE\n" : "", + irq1 & IRQ_SFD_RX ? "IRQ_SFD_RX\n" : "", + irq1 & IRQ_SFD_TX ? "IRQ_SFD_TX\n" : "", + irq1 & IRQ_RX_PKT_RCVD ? "IRQ_RX_PKT_RCVD\n" : "", + irq1 & IRQ_TX_PKT_SENT ? "IRQ_TX_PKT_SENT\n" : "", + irq1 & IRQ_CSMA_CA ? "IRQ_CSMA_CA\n" : "", + irq1 & IRQ_FRAME_VALID ? "IRQ_FRAME_VALID\n" : "", + irq1 & IRQ_ADDRESS_VALID ? "IRQ_ADDRESS_VALID\n" : ""); + + seq_printf(file, "STATUS = %X:\n%s\n%s%s%s%s%s\n", stat, + stat & STAT_RC_READY ? "RC_READY" : "RC_BUSY", + (stat & 0xf) == RC_STATUS_IDLE ? "RC_STATUS_IDLE" : "", + (stat & 0xf) == RC_STATUS_MEAS ? "RC_STATUS_MEAS" : "", + (stat & 0xf) == RC_STATUS_PHY_RDY ? "RC_STATUS_PHY_RDY" : "", + (stat & 0xf) == RC_STATUS_RX ? "RC_STATUS_RX" : "", + (stat & 0xf) == RC_STATUS_TX ? "RC_STATUS_TX" : ""); + + seq_printf(file, "RSSI = %d\n", lp->rssi); + + return 0; +} + +static int adf7242_debugfs_init(struct adf7242_local *lp) +{ + char debugfs_dir_name[DNAME_INLINE_LEN + 1] = "adf7242-"; + struct dentry *stats; + + strncat(debugfs_dir_name, dev_name(&lp->spi->dev), DNAME_INLINE_LEN); + + lp->debugfs_root = debugfs_create_dir(debugfs_dir_name, NULL); + if (IS_ERR_OR_NULL(lp->debugfs_root)) + return PTR_ERR_OR_ZERO(lp->debugfs_root); + + stats = debugfs_create_devm_seqfile(&lp->spi->dev, "status", + lp->debugfs_root, + adf7242_stats_show); + return PTR_ERR_OR_ZERO(stats); + + return 0; +} + +static const s32 adf7242_powers[] = { + 500, 400, 300, 200, 100, 0, -100, -200, -300, -400, -500, -600, -700, + -800, -900, -1000, -1100, -1200, -1300, -1400, -1500, -1600, -1700, + -1800, -1900, -2000, -2100, -2200, -2300, -2400, -2500, -2600, +}; + +static const s32 adf7242_ed_levels[] = { + -9000, -8900, -8800, -8700, -8600, -8500, -8400, -8300, -8200, -8100, + -8000, -7900, -7800, -7700, -7600, -7500, -7400, -7300, -7200, -7100, + -7000, -6900, -6800, -6700, -6600, -6500, -6400, -6300, -6200, -6100, + -6000, -5900, -5800, -5700, -5600, -5500, -5400, -5300, -5200, -5100, + -5000, -4900, -4800, -4700, -4600, -4500, -4400, -4300, -4200, -4100, + -4000, -3900, -3800, -3700, -3600, -3500, -3400, -3200, -3100, -3000 +}; + +static int adf7242_probe(struct spi_device *spi) +{ + struct ieee802154_hw *hw; + struct adf7242_local *lp; + int ret, irq_type; + + if (!spi->irq) { + dev_err(&spi->dev, "no IRQ specified\n"); + return -EINVAL; + } + + hw = ieee802154_alloc_hw(sizeof(*lp), &adf7242_ops); + if (!hw) + return -ENOMEM; + + lp = hw->priv; + lp->hw = hw; + lp->spi = spi; + + hw->priv = lp; + hw->parent = &spi->dev; + hw->extra_tx_headroom = 0; + + /* We support only 2.4 Ghz */ + hw->phy->supported.channels[0] = 0x7FFF800; + + hw->flags = IEEE802154_HW_OMIT_CKSUM | + IEEE802154_HW_CSMA_PARAMS | + IEEE802154_HW_FRAME_RETRIES | IEEE802154_HW_AFILT | + IEEE802154_HW_PROMISCUOUS; + + hw->phy->flags = WPAN_PHY_FLAG_TXPOWER | + WPAN_PHY_FLAG_CCA_ED_LEVEL | + WPAN_PHY_FLAG_CCA_MODE; + + hw->phy->supported.cca_modes = BIT(NL802154_CCA_ENERGY); + + hw->phy->supported.cca_ed_levels = adf7242_ed_levels; + hw->phy->supported.cca_ed_levels_size = ARRAY_SIZE(adf7242_ed_levels); + + hw->phy->cca.mode = NL802154_CCA_ENERGY; + + hw->phy->supported.tx_powers = adf7242_powers; + hw->phy->supported.tx_powers_size = ARRAY_SIZE(adf7242_powers); + + hw->phy->supported.min_minbe = 0; + hw->phy->supported.max_minbe = 8; + + hw->phy->supported.min_maxbe = 3; + hw->phy->supported.max_maxbe = 8; + + hw->phy->supported.min_frame_retries = 0; + hw->phy->supported.max_frame_retries = 15; + + hw->phy->supported.min_csma_backoffs = 0; + hw->phy->supported.max_csma_backoffs = 5; + + ieee802154_random_extended_addr(&hw->phy->perm_extended_addr); + + mutex_init(&lp->bmux); + init_completion(&lp->tx_complete); + + /* Setup Status Message */ + lp->stat_xfer.len = 1; + lp->stat_xfer.tx_buf = &lp->buf_stat_tx; + lp->stat_xfer.rx_buf = &lp->buf_stat_rx; + lp->buf_stat_tx = CMD_SPI_NOP; + + spi_message_init(&lp->stat_msg); + spi_message_add_tail(&lp->stat_xfer, &lp->stat_msg); + + spi_set_drvdata(spi, lp); + + ret = adf7242_hw_init(lp); + if (ret) + goto err_hw_init; + + irq_type = irq_get_trigger_type(spi->irq); + if (!irq_type) + irq_type = IRQF_TRIGGER_HIGH; + + ret = devm_request_threaded_irq(&spi->dev, spi->irq, NULL, adf7242_isr, + irq_type | IRQF_ONESHOT, + dev_name(&spi->dev), lp); + if (ret) + goto err_hw_init; + + disable_irq(spi->irq); + + ret = ieee802154_register_hw(lp->hw); + if (ret) + goto err_hw_init; + + dev_set_drvdata(&spi->dev, lp); + + adf7242_debugfs_init(lp); + + dev_info(&spi->dev, "mac802154 IRQ-%d registered\n", spi->irq); + + return ret; + +err_hw_init: + mutex_destroy(&lp->bmux); + ieee802154_free_hw(lp->hw); + + return ret; +} + +static int adf7242_remove(struct spi_device *spi) +{ + struct adf7242_local *lp = spi_get_drvdata(spi); + + if (!IS_ERR_OR_NULL(lp->debugfs_root)) + debugfs_remove_recursive(lp->debugfs_root); + + ieee802154_unregister_hw(lp->hw); + mutex_destroy(&lp->bmux); + ieee802154_free_hw(lp->hw); + + return 0; +} + +static const struct of_device_id adf7242_of_match[] = { + { .compatible = "adi,adf7242", }, + { }, +}; +MODULE_DEVICE_TABLE(of, adf7242_of_match); + +static const struct spi_device_id adf7242_device_id[] = { + { .name = "adf7242", }, + { }, +}; +MODULE_DEVICE_TABLE(spi, adf7242_device_id); + +static struct spi_driver adf7242_driver = { + .id_table = adf7242_device_id, + .driver = { + .of_match_table = of_match_ptr(adf7242_of_match), + .name = "adf7242", + .owner = THIS_MODULE, + }, + .probe = adf7242_probe, + .remove = adf7242_remove, +}; + +module_spi_driver(adf7242_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("ADF7242 IEEE802.15.4 Transceiver Driver"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2