Commit 66bd095a authored by Archie Pusaka's avatar Archie Pusaka Committed by Marcel Holtmann

Bluetooth: advmon offload MSFT remove monitor

Implements the monitor removal functionality for advertising monitor
offloading to MSFT controllers. Supply handle = 0 to remove all
monitors.
Signed-off-by: default avatarArchie Pusaka <apusaka@chromium.org>
Reviewed-by: default avatarMiao-chen Chou <mcchou@chromium.org>
Reviewed-by: default avatarYun-Hao Chung <howardchung@google.com>
Reported-by: default avatarkernel test robot <lkp@intel.com>
Reported-by: default avatarDan Carpenter <dan.carpenter@oracle.com>
Signed-off-by: default avatarMarcel Holtmann <marcel@holtmann.org>
parent a2a4dedf
...@@ -1332,11 +1332,13 @@ int hci_remove_adv_instance(struct hci_dev *hdev, u8 instance); ...@@ -1332,11 +1332,13 @@ int hci_remove_adv_instance(struct hci_dev *hdev, u8 instance);
void hci_adv_instances_set_rpa_expired(struct hci_dev *hdev, bool rpa_expired); void hci_adv_instances_set_rpa_expired(struct hci_dev *hdev, bool rpa_expired);
void hci_adv_monitors_clear(struct hci_dev *hdev); void hci_adv_monitors_clear(struct hci_dev *hdev);
void hci_free_adv_monitor(struct adv_monitor *monitor); void hci_free_adv_monitor(struct hci_dev *hdev, struct adv_monitor *monitor);
int hci_add_adv_patterns_monitor_complete(struct hci_dev *hdev, u8 status); int hci_add_adv_patterns_monitor_complete(struct hci_dev *hdev, u8 status);
int hci_remove_adv_monitor_complete(struct hci_dev *hdev, u8 status);
bool hci_add_adv_monitor(struct hci_dev *hdev, struct adv_monitor *monitor, bool hci_add_adv_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
int *err); int *err);
int hci_remove_adv_monitor(struct hci_dev *hdev, u16 handle); bool hci_remove_single_adv_monitor(struct hci_dev *hdev, u16 handle, int *err);
bool hci_remove_all_adv_monitor(struct hci_dev *hdev, int *err);
bool hci_is_adv_monitoring(struct hci_dev *hdev); bool hci_is_adv_monitoring(struct hci_dev *hdev);
int hci_get_adv_monitor_offload_ext(struct hci_dev *hdev); int hci_get_adv_monitor_offload_ext(struct hci_dev *hdev);
...@@ -1813,8 +1815,10 @@ void mgmt_advertising_added(struct sock *sk, struct hci_dev *hdev, ...@@ -1813,8 +1815,10 @@ void mgmt_advertising_added(struct sock *sk, struct hci_dev *hdev,
u8 instance); u8 instance);
void mgmt_advertising_removed(struct sock *sk, struct hci_dev *hdev, void mgmt_advertising_removed(struct sock *sk, struct hci_dev *hdev,
u8 instance); u8 instance);
void mgmt_adv_monitor_removed(struct hci_dev *hdev, u16 handle);
int mgmt_phy_configuration_changed(struct hci_dev *hdev, struct sock *skip); int mgmt_phy_configuration_changed(struct hci_dev *hdev, struct sock *skip);
int mgmt_add_adv_patterns_monitor_complete(struct hci_dev *hdev, u8 status); int mgmt_add_adv_patterns_monitor_complete(struct hci_dev *hdev, u8 status);
int mgmt_remove_adv_monitor_complete(struct hci_dev *hdev, u8 status);
u8 hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency, u8 hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency,
u16 to_multiplier); u16 to_multiplier);
......
...@@ -3051,12 +3051,15 @@ void hci_adv_monitors_clear(struct hci_dev *hdev) ...@@ -3051,12 +3051,15 @@ void hci_adv_monitors_clear(struct hci_dev *hdev)
int handle; int handle;
idr_for_each_entry(&hdev->adv_monitors_idr, monitor, handle) idr_for_each_entry(&hdev->adv_monitors_idr, monitor, handle)
hci_free_adv_monitor(monitor); hci_free_adv_monitor(hdev, monitor);
idr_destroy(&hdev->adv_monitors_idr); idr_destroy(&hdev->adv_monitors_idr);
} }
void hci_free_adv_monitor(struct adv_monitor *monitor) /* Frees the monitor structure and do some bookkeepings.
* This function requires the caller holds hdev->lock.
*/
void hci_free_adv_monitor(struct hci_dev *hdev, struct adv_monitor *monitor)
{ {
struct adv_pattern *pattern; struct adv_pattern *pattern;
struct adv_pattern *tmp; struct adv_pattern *tmp;
...@@ -3064,8 +3067,18 @@ void hci_free_adv_monitor(struct adv_monitor *monitor) ...@@ -3064,8 +3067,18 @@ void hci_free_adv_monitor(struct adv_monitor *monitor)
if (!monitor) if (!monitor)
return; return;
list_for_each_entry_safe(pattern, tmp, &monitor->patterns, list) list_for_each_entry_safe(pattern, tmp, &monitor->patterns, list) {
list_del(&pattern->list);
kfree(pattern); kfree(pattern);
}
if (monitor->handle)
idr_remove(&hdev->adv_monitors_idr, monitor->handle);
if (monitor->state != ADV_MONITOR_STATE_NOT_REGISTERED) {
hdev->adv_monitors_cnt--;
mgmt_adv_monitor_removed(hdev, monitor->handle);
}
kfree(monitor); kfree(monitor);
} }
...@@ -3075,6 +3088,11 @@ int hci_add_adv_patterns_monitor_complete(struct hci_dev *hdev, u8 status) ...@@ -3075,6 +3088,11 @@ int hci_add_adv_patterns_monitor_complete(struct hci_dev *hdev, u8 status)
return mgmt_add_adv_patterns_monitor_complete(hdev, status); return mgmt_add_adv_patterns_monitor_complete(hdev, status);
} }
int hci_remove_adv_monitor_complete(struct hci_dev *hdev, u8 status)
{
return mgmt_remove_adv_monitor_complete(hdev, status);
}
/* Assigns handle to a monitor, and if offloading is supported and power is on, /* Assigns handle to a monitor, and if offloading is supported and power is on,
* also attempts to forward the request to the controller. * also attempts to forward the request to the controller.
* Returns true if request is forwarded (result is pending), false otherwise. * Returns true if request is forwarded (result is pending), false otherwise.
...@@ -3122,39 +3140,94 @@ bool hci_add_adv_monitor(struct hci_dev *hdev, struct adv_monitor *monitor, ...@@ -3122,39 +3140,94 @@ bool hci_add_adv_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
return (*err == 0); return (*err == 0);
} }
static int free_adv_monitor(int id, void *ptr, void *data) /* Attempts to tell the controller and free the monitor. If somehow the
* controller doesn't have a corresponding handle, remove anyway.
* Returns true if request is forwarded (result is pending), false otherwise.
* This function requires the caller holds hdev->lock.
*/
static bool hci_remove_adv_monitor(struct hci_dev *hdev,
struct adv_monitor *monitor,
u16 handle, int *err)
{ {
struct hci_dev *hdev = data; *err = 0;
struct adv_monitor *monitor = ptr;
idr_remove(&hdev->adv_monitors_idr, monitor->handle); switch (hci_get_adv_monitor_offload_ext(hdev)) {
hci_free_adv_monitor(monitor); case HCI_ADV_MONITOR_EXT_NONE: /* also goes here when powered off */
hdev->adv_monitors_cnt--; goto free_monitor;
case HCI_ADV_MONITOR_EXT_MSFT:
*err = msft_remove_monitor(hdev, monitor, handle);
break;
}
return 0; /* In case no matching handle registered, just free the monitor */
if (*err == -ENOENT)
goto free_monitor;
return (*err == 0);
free_monitor:
if (*err == -ENOENT)
bt_dev_warn(hdev, "Removing monitor with no matching handle %d",
monitor->handle);
hci_free_adv_monitor(hdev, monitor);
*err = 0;
return false;
} }
/* This function requires the caller holds hdev->lock */ /* Returns true if request is forwarded (result is pending), false otherwise.
int hci_remove_adv_monitor(struct hci_dev *hdev, u16 handle) * This function requires the caller holds hdev->lock.
*/
bool hci_remove_single_adv_monitor(struct hci_dev *hdev, u16 handle, int *err)
{
struct adv_monitor *monitor = idr_find(&hdev->adv_monitors_idr, handle);
bool pending;
if (!monitor) {
*err = -EINVAL;
return false;
}
pending = hci_remove_adv_monitor(hdev, monitor, handle, err);
if (!*err && !pending)
hci_update_background_scan(hdev);
bt_dev_dbg(hdev, "%s remove monitor handle %d, status %d, %spending",
hdev->name, handle, *err, pending ? "" : "not ");
return pending;
}
/* Returns true if request is forwarded (result is pending), false otherwise.
* This function requires the caller holds hdev->lock.
*/
bool hci_remove_all_adv_monitor(struct hci_dev *hdev, int *err)
{ {
struct adv_monitor *monitor; struct adv_monitor *monitor;
int idr_next_id = 0;
bool pending = false;
bool update = false;
*err = 0;
if (handle) { while (!*err && !pending) {
monitor = idr_find(&hdev->adv_monitors_idr, handle); monitor = idr_get_next(&hdev->adv_monitors_idr, &idr_next_id);
if (!monitor) if (!monitor)
return -ENOENT; break;
idr_remove(&hdev->adv_monitors_idr, monitor->handle); pending = hci_remove_adv_monitor(hdev, monitor, 0, err);
hci_free_adv_monitor(monitor);
hdev->adv_monitors_cnt--; if (!*err && !pending)
} else { update = true;
/* Remove all monitors if handle is 0. */
idr_for_each(&hdev->adv_monitors_idr, &free_adv_monitor, hdev);
} }
hci_update_background_scan(hdev); if (update)
hci_update_background_scan(hdev);
return 0; bt_dev_dbg(hdev, "%s remove all monitors status %d, %spending",
hdev->name, *err, pending ? "" : "not ");
return pending;
} }
/* This function requires the caller holds hdev->lock */ /* This function requires the caller holds hdev->lock */
......
...@@ -4167,14 +4167,24 @@ static void mgmt_adv_monitor_added(struct sock *sk, struct hci_dev *hdev, ...@@ -4167,14 +4167,24 @@ static void mgmt_adv_monitor_added(struct sock *sk, struct hci_dev *hdev,
mgmt_event(MGMT_EV_ADV_MONITOR_ADDED, hdev, &ev, sizeof(ev), sk); mgmt_event(MGMT_EV_ADV_MONITOR_ADDED, hdev, &ev, sizeof(ev), sk);
} }
static void mgmt_adv_monitor_removed(struct sock *sk, struct hci_dev *hdev, void mgmt_adv_monitor_removed(struct hci_dev *hdev, u16 handle)
u16 handle)
{ {
struct mgmt_ev_adv_monitor_added ev; struct mgmt_ev_adv_monitor_removed ev;
struct mgmt_pending_cmd *cmd;
struct sock *sk_skip = NULL;
struct mgmt_cp_remove_adv_monitor *cp;
cmd = pending_find(MGMT_OP_REMOVE_ADV_MONITOR, hdev);
if (cmd) {
cp = cmd->param;
if (cp->monitor_handle)
sk_skip = cmd->sk;
}
ev.monitor_handle = cpu_to_le16(handle); ev.monitor_handle = cpu_to_le16(handle);
mgmt_event(MGMT_EV_ADV_MONITOR_REMOVED, hdev, &ev, sizeof(ev), sk); mgmt_event(MGMT_EV_ADV_MONITOR_REMOVED, hdev, &ev, sizeof(ev), sk_skip);
} }
static int read_adv_mon_features(struct sock *sk, struct hci_dev *hdev, static int read_adv_mon_features(struct sock *sk, struct hci_dev *hdev,
...@@ -4324,8 +4334,8 @@ static int __add_adv_patterns_monitor(struct sock *sk, struct hci_dev *hdev, ...@@ -4324,8 +4334,8 @@ static int __add_adv_patterns_monitor(struct sock *sk, struct hci_dev *hdev,
return 0; return 0;
unlock: unlock:
hci_free_adv_monitor(hdev, m);
hci_dev_unlock(hdev); hci_dev_unlock(hdev);
hci_free_adv_monitor(m);
return mgmt_cmd_status(sk, hdev->id, op, status); return mgmt_cmd_status(sk, hdev->id, op, status);
} }
...@@ -4459,42 +4469,100 @@ static int add_adv_patterns_monitor_rssi(struct sock *sk, struct hci_dev *hdev, ...@@ -4459,42 +4469,100 @@ static int add_adv_patterns_monitor_rssi(struct sock *sk, struct hci_dev *hdev,
MGMT_OP_ADD_ADV_PATTERNS_MONITOR_RSSI); MGMT_OP_ADD_ADV_PATTERNS_MONITOR_RSSI);
} }
int mgmt_remove_adv_monitor_complete(struct hci_dev *hdev, u8 status)
{
struct mgmt_rp_remove_adv_monitor rp;
struct mgmt_cp_remove_adv_monitor *cp;
struct mgmt_pending_cmd *cmd;
int err = 0;
hci_dev_lock(hdev);
cmd = pending_find(MGMT_OP_REMOVE_ADV_MONITOR, hdev);
if (!cmd)
goto done;
cp = cmd->param;
rp.monitor_handle = cp->monitor_handle;
if (!status)
hci_update_background_scan(hdev);
err = mgmt_cmd_complete(cmd->sk, cmd->index, cmd->opcode,
mgmt_status(status), &rp, sizeof(rp));
mgmt_pending_remove(cmd);
done:
hci_dev_unlock(hdev);
bt_dev_dbg(hdev, "remove monitor %d complete, status %d",
rp.monitor_handle, status);
return err;
}
static int remove_adv_monitor(struct sock *sk, struct hci_dev *hdev, static int remove_adv_monitor(struct sock *sk, struct hci_dev *hdev,
void *data, u16 len) void *data, u16 len)
{ {
struct mgmt_cp_remove_adv_monitor *cp = data; struct mgmt_cp_remove_adv_monitor *cp = data;
struct mgmt_rp_remove_adv_monitor rp; struct mgmt_rp_remove_adv_monitor rp;
unsigned int prev_adv_monitors_cnt; struct mgmt_pending_cmd *cmd;
u16 handle; u16 handle = __le16_to_cpu(cp->monitor_handle);
int err; int err, status;
bool pending;
BT_DBG("request for %s", hdev->name); BT_DBG("request for %s", hdev->name);
rp.monitor_handle = cp->monitor_handle;
hci_dev_lock(hdev); hci_dev_lock(hdev);
handle = __le16_to_cpu(cp->monitor_handle); if (pending_find(MGMT_OP_SET_LE, hdev) ||
prev_adv_monitors_cnt = hdev->adv_monitors_cnt; pending_find(MGMT_OP_REMOVE_ADV_MONITOR, hdev) ||
pending_find(MGMT_OP_ADD_ADV_PATTERNS_MONITOR, hdev) ||
pending_find(MGMT_OP_ADD_ADV_PATTERNS_MONITOR_RSSI, hdev)) {
status = MGMT_STATUS_BUSY;
goto unlock;
}
err = hci_remove_adv_monitor(hdev, handle); cmd = mgmt_pending_add(sk, MGMT_OP_REMOVE_ADV_MONITOR, hdev, data, len);
if (err == -ENOENT) { if (!cmd) {
err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_REMOVE_ADV_MONITOR, status = MGMT_STATUS_NO_RESOURCES;
MGMT_STATUS_INVALID_INDEX);
goto unlock; goto unlock;
} }
if (hdev->adv_monitors_cnt < prev_adv_monitors_cnt) if (handle)
mgmt_adv_monitor_removed(sk, hdev, handle); pending = hci_remove_single_adv_monitor(hdev, handle, &err);
else
pending = hci_remove_all_adv_monitor(hdev, &err);
hci_dev_unlock(hdev); if (err) {
mgmt_pending_remove(cmd);
rp.monitor_handle = cp->monitor_handle; if (err == -ENOENT)
status = MGMT_STATUS_INVALID_INDEX;
else
status = MGMT_STATUS_FAILED;
goto unlock;
}
/* monitor can be removed without forwarding request to controller */
if (!pending) {
mgmt_pending_remove(cmd);
hci_dev_unlock(hdev);
return mgmt_cmd_complete(sk, hdev->id,
MGMT_OP_REMOVE_ADV_MONITOR,
MGMT_STATUS_SUCCESS,
&rp, sizeof(rp));
}
return mgmt_cmd_complete(sk, hdev->id, MGMT_OP_REMOVE_ADV_MONITOR, hci_dev_unlock(hdev);
MGMT_STATUS_SUCCESS, &rp, sizeof(rp)); return 0;
unlock: unlock:
hci_dev_unlock(hdev); hci_dev_unlock(hdev);
return err; return mgmt_cmd_status(sk, hdev->id, MGMT_OP_REMOVE_ADV_MONITOR,
status);
} }
static void read_local_oob_data_complete(struct hci_dev *hdev, u8 status, static void read_local_oob_data_complete(struct hci_dev *hdev, u8 status,
......
...@@ -58,6 +58,17 @@ struct msft_rp_le_monitor_advertisement { ...@@ -58,6 +58,17 @@ struct msft_rp_le_monitor_advertisement {
__u8 handle; __u8 handle;
} __packed; } __packed;
#define MSFT_OP_LE_CANCEL_MONITOR_ADVERTISEMENT 0x04
struct msft_cp_le_cancel_monitor_advertisement {
__u8 sub_opcode;
__u8 handle;
} __packed;
struct msft_rp_le_cancel_monitor_advertisement {
__u8 status;
__u8 sub_opcode;
} __packed;
struct msft_monitor_advertisement_handle_data { struct msft_monitor_advertisement_handle_data {
__u8 msft_handle; __u8 msft_handle;
__u16 mgmt_handle; __u16 mgmt_handle;
...@@ -70,6 +81,7 @@ struct msft_data { ...@@ -70,6 +81,7 @@ struct msft_data {
__u8 *evt_prefix; __u8 *evt_prefix;
struct list_head handle_map; struct list_head handle_map;
__u16 pending_add_handle; __u16 pending_add_handle;
__u16 pending_remove_handle;
}; };
bool msft_monitor_supported(struct hci_dev *hdev) bool msft_monitor_supported(struct hci_dev *hdev)
...@@ -205,6 +217,26 @@ __u64 msft_get_features(struct hci_dev *hdev) ...@@ -205,6 +217,26 @@ __u64 msft_get_features(struct hci_dev *hdev)
return msft ? msft->features : 0; return msft ? msft->features : 0;
} }
/* is_mgmt = true matches the handle exposed to userspace via mgmt.
* is_mgmt = false matches the handle used by the msft controller.
* This function requires the caller holds hdev->lock
*/
static struct msft_monitor_advertisement_handle_data *msft_find_handle_data
(struct hci_dev *hdev, u16 handle, bool is_mgmt)
{
struct msft_monitor_advertisement_handle_data *entry;
struct msft_data *msft = hdev->msft_data;
list_for_each_entry(entry, &msft->handle_map, list) {
if (is_mgmt && entry->mgmt_handle == handle)
return entry;
if (!is_mgmt && entry->msft_handle == handle)
return entry;
}
return NULL;
}
static void msft_le_monitor_advertisement_cb(struct hci_dev *hdev, static void msft_le_monitor_advertisement_cb(struct hci_dev *hdev,
u8 status, u16 opcode, u8 status, u16 opcode,
struct sk_buff *skb) struct sk_buff *skb)
...@@ -247,16 +279,71 @@ static void msft_le_monitor_advertisement_cb(struct hci_dev *hdev, ...@@ -247,16 +279,71 @@ static void msft_le_monitor_advertisement_cb(struct hci_dev *hdev,
monitor->state = ADV_MONITOR_STATE_OFFLOADED; monitor->state = ADV_MONITOR_STATE_OFFLOADED;
unlock: unlock:
if (status && monitor) { if (status && monitor)
idr_remove(&hdev->adv_monitors_idr, monitor->handle); hci_free_adv_monitor(hdev, monitor);
hci_free_adv_monitor(monitor);
}
hci_dev_unlock(hdev); hci_dev_unlock(hdev);
hci_add_adv_patterns_monitor_complete(hdev, status); hci_add_adv_patterns_monitor_complete(hdev, status);
} }
static void msft_le_cancel_monitor_advertisement_cb(struct hci_dev *hdev,
u8 status, u16 opcode,
struct sk_buff *skb)
{
struct msft_cp_le_cancel_monitor_advertisement *cp;
struct msft_rp_le_cancel_monitor_advertisement *rp;
struct adv_monitor *monitor;
struct msft_monitor_advertisement_handle_data *handle_data;
struct msft_data *msft = hdev->msft_data;
int err;
bool pending;
if (status)
goto done;
rp = (struct msft_rp_le_cancel_monitor_advertisement *)skb->data;
if (skb->len < sizeof(*rp)) {
status = HCI_ERROR_UNSPECIFIED;
goto done;
}
hci_dev_lock(hdev);
cp = hci_sent_cmd_data(hdev, hdev->msft_opcode);
handle_data = msft_find_handle_data(hdev, cp->handle, false);
if (handle_data) {
monitor = idr_find(&hdev->adv_monitors_idr,
handle_data->mgmt_handle);
if (monitor)
hci_free_adv_monitor(hdev, monitor);
list_del(&handle_data->list);
kfree(handle_data);
}
/* If remove all monitors is required, we need to continue the process
* here because the earlier it was paused when waiting for the
* response from controller.
*/
if (msft->pending_remove_handle == 0) {
pending = hci_remove_all_adv_monitor(hdev, &err);
if (pending) {
hci_dev_unlock(hdev);
return;
}
if (err)
status = HCI_ERROR_UNSPECIFIED;
}
hci_dev_unlock(hdev);
done:
hci_remove_adv_monitor_complete(hdev, status);
}
static bool msft_monitor_rssi_valid(struct adv_monitor *monitor) static bool msft_monitor_rssi_valid(struct adv_monitor *monitor)
{ {
struct adv_rssi_thresholds *r = &monitor->rssi; struct adv_rssi_thresholds *r = &monitor->rssi;
...@@ -346,3 +433,35 @@ int msft_add_monitor_pattern(struct hci_dev *hdev, struct adv_monitor *monitor) ...@@ -346,3 +433,35 @@ int msft_add_monitor_pattern(struct hci_dev *hdev, struct adv_monitor *monitor)
return err; return err;
} }
/* This function requires the caller holds hdev->lock */
int msft_remove_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
u16 handle)
{
struct msft_cp_le_cancel_monitor_advertisement cp;
struct msft_monitor_advertisement_handle_data *handle_data;
struct hci_request req;
struct msft_data *msft = hdev->msft_data;
int err = 0;
if (!msft)
return -EOPNOTSUPP;
handle_data = msft_find_handle_data(hdev, monitor->handle, true);
/* If no matched handle, just remove without telling controller */
if (!handle_data)
return -ENOENT;
cp.sub_opcode = MSFT_OP_LE_CANCEL_MONITOR_ADVERTISEMENT;
cp.handle = handle_data->msft_handle;
hci_req_init(&req, hdev);
hci_req_add(&req, hdev->msft_opcode, sizeof(cp), &cp);
err = hci_req_run_skb(&req, msft_le_cancel_monitor_advertisement_cb);
if (!err)
msft->pending_remove_handle = handle;
return err;
}
...@@ -18,6 +18,8 @@ void msft_do_close(struct hci_dev *hdev); ...@@ -18,6 +18,8 @@ void msft_do_close(struct hci_dev *hdev);
void msft_vendor_evt(struct hci_dev *hdev, struct sk_buff *skb); void msft_vendor_evt(struct hci_dev *hdev, struct sk_buff *skb);
__u64 msft_get_features(struct hci_dev *hdev); __u64 msft_get_features(struct hci_dev *hdev);
int msft_add_monitor_pattern(struct hci_dev *hdev, struct adv_monitor *monitor); int msft_add_monitor_pattern(struct hci_dev *hdev, struct adv_monitor *monitor);
int msft_remove_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
u16 handle);
#else #else
...@@ -36,4 +38,11 @@ static inline int msft_add_monitor_pattern(struct hci_dev *hdev, ...@@ -36,4 +38,11 @@ static inline int msft_add_monitor_pattern(struct hci_dev *hdev,
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
static inline int msft_remove_monitor(struct hci_dev *hdev,
struct adv_monitor *monitor,
u16 handle)
{
return -EOPNOTSUPP;
}
#endif #endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment