Преглед на файлове

qcacld-3.0: Process vendor cmd for BEACON_REPORTING_OP_STOP

Userspace request driver to report details of each beacon
received whose bssid is same as currently connected BSS's
mac address. The driver encapsulates the details of these
beacons as an asynchronous event within vendor command:
QCA_NL80211_VENDOR_SUBCMD_BEACON_REPORTING with operation
type QCA_WLAN_VENDOR_BEACON_REPORTING_OP_STOP until
userspace requests to stop sending beacons.

When driver gets stop indication from userspace, it does
the following things:
1. De-register all callback which is registered while handling
start indication
2. Add beacon filter and send it to fw

If driver is in WOW mode and WMI_ADD_BCN_FILTER_CMDID is
NOT configured, fw wakeup HOST and sends connected AP beacon.
Fw should not wakeup host if host is in wow mode. In order
to support this, configure WOW_BEACON_EVENT for STA and P2P.

Change-Id: Ie7c768fa957d02e1361e1ecb95435ba3f06034b0
CRs-Fixed: 2431360
Abhinav Kumar преди 5 години
родител
ревизия
392221599a

+ 6 - 0
components/pmo/core/src/wlan_pmo_static_config.c

@@ -60,6 +60,12 @@ void pmo_register_wow_wakeup_events(struct wlan_objmgr_vdev *vdev)
 					PMO_WOW_MAX_EVENT_BM_LEN,
 					event_bitmap);
 		}
+
+		/* Configure WOW_BEACON_EVENT */
+		pmo_set_wow_event_bitmap(WOW_BEACON_EVENT,
+					 PMO_WOW_MAX_EVENT_BM_LEN,
+					 event_bitmap);
+
 	/* fallthrough */
 	case QDF_P2P_DEVICE_MODE:
 	case QDF_OCB_MODE:

+ 8 - 0
core/hdd/inc/wlan_hdd_assoc.h

@@ -444,6 +444,14 @@ static inline void hdd_save_gtk_params(struct hdd_adapter *adapter,
 void hdd_copy_ht_caps(struct ieee80211_ht_cap *hdd_ht_cap,
 		      tDot11fIEHTCaps *roam_ht_cap);
 
+/**
+ * hdd_add_beacon_filter() - add beacon filter
+ * @adapter: Pointer to the hdd adapter
+ *
+ * Return: 0 on success and errno on failure
+ */
+int hdd_add_beacon_filter(struct hdd_adapter *adapter);
+
 /**
  * hdd_copy_vht_caps()- copy vht caps info from roam vht caps
  * info to source vht_cap info of type ieee80211_vht_cap.

+ 23 - 0
core/hdd/inc/wlan_hdd_bcn_recv.h

@@ -42,6 +42,21 @@ int wlan_hdd_cfg80211_bcn_rcv_start(struct wiphy *wiphy,
 				    struct wireless_dev *wdev,
 				    const void *data, int data_len);
 
+/**
+ * hdd_beacon_recv_pause_indication()- Send vendor event to user space
+ * to inform SCAN started indication
+ * @hdd_handle: hdd handler
+ * @vdev_id: vdev id
+ * @type: scan event type
+ * @is_disconnected: Connection state of driver
+ *
+ * Return: None
+ */
+void hdd_beacon_recv_pause_indication(hdd_handle_t hdd_handle,
+				      uint8_t vdev_id,
+				      enum scan_event_type type,
+				      bool is_disconnected);
+
 #define BCN_RECV_FEATURE_VENDOR_COMMANDS				\
 {									\
 	.info.vendor_id = QCA_NL80211_VENDOR_ID,			\
@@ -59,5 +74,13 @@ int wlan_hdd_cfg80211_bcn_rcv_start(struct wiphy *wiphy,
 #else
 #define BCN_RECV_FEATURE_VENDOR_COMMANDS
 #define BCN_RECV_FEATURE_VENDOR_EVENTS
+
+static inline
+void hdd_beacon_recv_pause_indication(hdd_handle_t hdd_handle,
+				      uint8_t vdev_id,
+				      enum scan_event_type type,
+				      bool is_disconnected)
+{
+}
 #endif
 

+ 11 - 9
core/hdd/src/wlan_hdd_assoc.c

@@ -63,6 +63,7 @@
 #include "wlan_hdd_stats.h"
 #include "wlan_hdd_scan.h"
 #include "wlan_crypto_global_api.h"
+#include "wlan_hdd_bcn_recv.h"
 
 #include "wlan_hdd_nud_tracking.h"
 #include <wlan_cfg80211_crypto.h>
@@ -442,13 +443,7 @@ static int hdd_remove_beacon_filter(struct hdd_adapter *adapter)
 	return 0;
 }
 
-/**
- * hdd_add_beacon_filter() - add beacon filter
- * @adapter: Pointer to the hdd adapter
- *
- * Return: 0 on success and errno on failure
- */
-static int hdd_add_beacon_filter(struct hdd_adapter *adapter)
+int hdd_add_beacon_filter(struct hdd_adapter *adapter)
 {
 	int i;
 	uint32_t ie_map[SIR_BCN_FLT_MAX_ELEMS_IE_LIST] = {0};
@@ -1807,8 +1802,15 @@ static QDF_STATUS hdd_dis_connect_handler(struct hdd_adapter *adapter,
 	mac_handle = hdd_ctx->mac_handle;
 	sme_ft_reset(mac_handle, adapter->vdev_id);
 	sme_reset_key(mac_handle, adapter->vdev_id);
-	if (hdd_remove_beacon_filter(adapter) != 0)
-		hdd_err("hdd_remove_beacon_filter() failed");
+	if (!hdd_remove_beacon_filter(adapter)) {
+		if (sme_is_beacon_report_started(mac_handle,
+						 adapter->vdev_id)) {
+			hdd_beacon_recv_pause_indication((hdd_handle_t)hdd_ctx,
+							 adapter->vdev_id,
+							 SCAN_EVENT_TYPE_MAX,
+							 true);
+		}
+	}
 
 	if (eCSR_ROAM_IBSS_LEAVE == roam_status) {
 		uint8_t i;

+ 201 - 95
core/hdd/src/wlan_hdd_bcn_recv.c

@@ -78,13 +78,13 @@ int get_beacon_report_data_len(struct wlan_beacon_report *report)
 
 /**
  * get_pause_ind_data_len() - Calculate skb buffer length
- * @report: Required beacon report
+ * @is_disconnected: Connection state
  *
  * Calculate length for pause indication to allocate skb buffer
  *
  * Return: skb buffer length
  */
-static int get_pause_ind_data_len(void)
+static int get_pause_ind_data_len(bool is_disconnected)
 {
 	uint32_t data_len = NLMSG_HDRLEN;
 
@@ -94,72 +94,11 @@ static int get_pause_ind_data_len(void)
 	/* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_PAUSE_REASON */
 	data_len += nla_total_size(sizeof(u32));
 
-	return data_len;
-}
-
-/**
- * hdd_beacon_recv_pause_indication()- Send vendor event to user space
- * to inform SCAN started indication
- * @hdd_handle: hdd handler
- * @vdev_id: vdev id
- * @type: scan event type
- *
- * Return: None
- */
-static void hdd_beacon_recv_pause_indication(hdd_handle_t hdd_handle,
-					     uint8_t vdev_id,
-					     enum scan_event_type type)
-{
-	struct hdd_context *hdd_ctx = hdd_handle_to_context(hdd_handle);
-	struct hdd_adapter *adapter;
-	struct sk_buff *vendor_event;
-	uint32_t data_len;
-	int flags;
-	uint32_t abort_reason;
-
-	if (wlan_hdd_validate_context(hdd_ctx))
-		return;
-
-	adapter = hdd_get_adapter_by_vdev(hdd_ctx, vdev_id);
-	if (hdd_validate_adapter(adapter))
-		return;
-
-	data_len = get_pause_ind_data_len();
-	flags = cds_get_gfp_flags();
-
-	vendor_event =
-		cfg80211_vendor_event_alloc(
-			hdd_ctx->wiphy, NULL,
-			data_len,
-			QCA_NL80211_VENDOR_SUBCMD_BEACON_REPORTING_INDEX,
-			flags);
-	if (!vendor_event) {
-		hdd_err("cfg80211_vendor_event_alloc failed");
-		return;
-	}
-
-	switch (type) {
-	case SCAN_EVENT_TYPE_STARTED:
-		abort_reason =
-		  QCA_WLAN_VENDOR_BEACON_REPORTING_PAUSE_REASON_SCAN_STARTED;
-		break;
-	default:
-		abort_reason =
-		     QCA_WLAN_VENDOR_BEACON_REPORTING_PAUSE_REASON_UNSPECIFIED;
-	}
-	/* Send vendor event to user space to inform ABORT */
-	if (nla_put_u32(vendor_event,
-			QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_OP_TYPE,
-			QCA_WLAN_VENDOR_BEACON_REPORTING_OP_PAUSE) ||
-	    nla_put_u32(vendor_event,
-			QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_PAUSE_REASON,
-			abort_reason)) {
-		hdd_err("QCA_WLAN_VENDOR_ATTR put fail");
-		kfree_skb(vendor_event);
-		return;
-	}
+	/* QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_AUTO_RESUMES */
+	if (!is_disconnected)
+		data_len += nla_total_size(sizeof(u8));
 
-	cfg80211_vendor_event(vendor_event, flags);
+	return data_len;
 }
 
 /**
@@ -221,6 +160,101 @@ static void hdd_send_bcn_recv_info(hdd_handle_t hdd_handle,
 	cfg80211_vendor_event(vendor_event, flags);
 }
 
+/**
+ * hdd_handle_beacon_reporting_start_op() - Process bcn recv start op
+ * @hdd_ctx: Pointer to hdd context
+ * @adapter: Pointer to network adapter
+ * @active_report: Active reporting flag
+ *
+ * This function process beacon reporting start operation.
+ */
+static int hdd_handle_beacon_reporting_start_op(struct hdd_context *hdd_ctx,
+						struct hdd_adapter *adapter,
+						bool active_report)
+{
+	QDF_STATUS qdf_status = QDF_STATUS_SUCCESS;
+	int errno;
+
+	if (active_report) {
+		/* Register beacon report callback */
+		qdf_status =
+			sme_register_bcn_report_pe_cb(hdd_ctx->mac_handle,
+						      hdd_send_bcn_recv_info);
+		if (QDF_IS_STATUS_ERROR(qdf_status)) {
+			hdd_err("bcn recv info cb reg failed = %d", qdf_status);
+			errno = qdf_status_to_os_return(qdf_status);
+			return errno;
+		}
+
+		/* Register pause indication callback */
+		qdf_status =
+			sme_register_bcn_recv_pause_ind_cb(hdd_ctx->mac_handle,
+					hdd_beacon_recv_pause_indication);
+		if (QDF_IS_STATUS_ERROR(qdf_status)) {
+			hdd_err("pause_ind_cb reg failed = %d", qdf_status);
+			errno = qdf_status_to_os_return(qdf_status);
+			return errno;
+		}
+	}
+	/* Handle beacon receive start indication */
+	qdf_status = sme_handle_bcn_recv_start(hdd_ctx->mac_handle,
+					       adapter->vdev_id);
+	if (QDF_IS_STATUS_ERROR(qdf_status)) {
+		hdd_err("bcn rcv start failed with status=%d", qdf_status);
+		errno = qdf_status_to_os_return(qdf_status);
+		return errno;
+	}
+
+	errno = qdf_status_to_os_return(qdf_status);
+
+	return errno;
+}
+
+/**
+ * hdd_handle_beacon_reporting_stop_op() - Process bcn recv stop op
+ * @hdd_ctx: Pointer to hdd context
+ * @adapter: Pointer to network adapter
+ *
+ * This function process beacon reporting stop operation.
+ */
+static int hdd_handle_beacon_reporting_stop_op(struct hdd_context *hdd_ctx,
+					       struct hdd_adapter *adapter)
+{
+	QDF_STATUS qdf_status = QDF_STATUS_SUCCESS;
+	int errno;
+
+	/* Reset bcn recv start flag */
+	sme_stop_beacon_report(hdd_ctx->mac_handle, adapter->vdev_id);
+
+	/* Deregister beacon report callback */
+	qdf_status = sme_register_bcn_report_pe_cb(hdd_ctx->mac_handle, NULL);
+	if (QDF_IS_STATUS_ERROR(qdf_status)) {
+		hdd_err("Callback de-registration failed = %d", qdf_status);
+		errno = qdf_status_to_os_return(qdf_status);
+		return errno;
+	}
+
+	/* Deregister pause indication callback */
+	qdf_status = sme_register_bcn_recv_pause_ind_cb(hdd_ctx->mac_handle,
+							NULL);
+	if (QDF_IS_STATUS_ERROR(qdf_status)) {
+		hdd_err("scan even deregister failed = %d", qdf_status);
+		errno = qdf_status_to_os_return(qdf_status);
+		return errno;
+	}
+
+	if (hdd_adapter_is_connected_sta(adapter))
+		/* Add beacon filter */
+		if (hdd_add_beacon_filter(adapter)) {
+			hdd_err("Beacon filter addition failed");
+			return -EINVAL;
+		}
+
+	errno = qdf_status_to_os_return(qdf_status);
+
+	return errno;
+}
+
 /**
  * __wlan_hdd_cfg80211_bcn_rcv_start() - enable/disable beacon reporting
  * indication
@@ -244,7 +278,6 @@ static int __wlan_hdd_cfg80211_bcn_rcv_start(struct wiphy *wiphy,
 	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_MAX + 1];
 	uint32_t bcn_report;
 	int errno;
-	QDF_STATUS qdf_status = QDF_STATUS_SUCCESS;
 	bool active_report;
 
 	hdd_enter_dev(dev);
@@ -285,40 +318,113 @@ static int __wlan_hdd_cfg80211_bcn_rcv_start(struct wiphy *wiphy,
 		nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_OP_TYPE]);
 	hdd_debug("Bcn Report: OP type:%d", bcn_report);
 
-	if (bcn_report == QCA_WLAN_VENDOR_BEACON_REPORTING_OP_START) {
-		if (active_report) {
-			qdf_status = sme_register_bcn_report_pe_cb(
-							hdd_ctx->mac_handle,
-							hdd_send_bcn_recv_info);
-			if (QDF_IS_STATUS_ERROR(qdf_status)) {
-				hdd_err("bcn recv info cb reg failed = %d",
-					qdf_status);
-				errno = qdf_status_to_os_return(qdf_status);
-				return errno;
-			}
-		}
-		qdf_status =
-			sme_register_bcn_recv_pause_ind_cb(hdd_ctx->mac_handle,
-					hdd_beacon_recv_pause_indication);
-		if (QDF_IS_STATUS_ERROR(qdf_status)) {
-			hdd_err("bcn_recv_abort_ind cb reg failed = %d",
-				qdf_status);
-			errno = qdf_status_to_os_return(qdf_status);
+	switch (bcn_report) {
+	case QCA_WLAN_VENDOR_BEACON_REPORTING_OP_START:
+		if (sme_is_beacon_report_started(hdd_ctx->mac_handle,
+						 adapter->vdev_id)) {
+			hdd_debug("Start cmd already in progress");
 			return errno;
 		}
-
-		qdf_status =
-			sme_handle_bcn_recv_start(hdd_ctx->mac_handle,
-						  adapter->vdev_id);
-		if (QDF_IS_STATUS_ERROR(qdf_status))
-			hdd_err("beacon receive start failed with status=%d",
-				qdf_status);
+		errno = hdd_handle_beacon_reporting_start_op(hdd_ctx,
+							     adapter,
+							     active_report);
+		break;
+	case QCA_WLAN_VENDOR_BEACON_REPORTING_OP_STOP:
+		if (sme_is_beacon_report_started(hdd_ctx->mac_handle,
+						 adapter->vdev_id)) {
+			errno = hdd_handle_beacon_reporting_stop_op(hdd_ctx,
+								    adapter);
+		} else {
+			hdd_err_rl("BCN_RCV_STOP rej as no START CMD active");
+		}
+		break;
+	default:
+		hdd_debug("Invalid bcn report type %d", bcn_report);
 	}
 
-	errno = qdf_status_to_os_return(qdf_status);
 	return errno;
 }
 
+void hdd_beacon_recv_pause_indication(hdd_handle_t hdd_handle,
+				      uint8_t vdev_id,
+				      enum scan_event_type type,
+				      bool is_disconnected)
+{
+	struct hdd_context *hdd_ctx = hdd_handle_to_context(hdd_handle);
+	struct hdd_adapter *adapter;
+	struct sk_buff *vendor_event;
+	uint32_t data_len;
+	int flags;
+	uint32_t abort_reason;
+
+	if (wlan_hdd_validate_context(hdd_ctx))
+		return;
+
+	adapter = hdd_get_adapter_by_vdev(hdd_ctx, vdev_id);
+	if (hdd_validate_adapter(adapter))
+		return;
+
+	data_len = get_pause_ind_data_len(is_disconnected);
+	flags = cds_get_gfp_flags();
+
+	vendor_event =
+		cfg80211_vendor_event_alloc(
+			hdd_ctx->wiphy, NULL,
+			data_len,
+			QCA_NL80211_VENDOR_SUBCMD_BEACON_REPORTING_INDEX,
+			flags);
+	if (!vendor_event) {
+		hdd_err("cfg80211_vendor_event_alloc failed");
+		return;
+	}
+
+	if (is_disconnected) {
+		abort_reason =
+		     QCA_WLAN_VENDOR_BEACON_REPORTING_PAUSE_REASON_DISCONNECTED;
+		/* Deregister callbacks and Reset bcn recv start flag */
+		if (sme_is_beacon_report_started(hdd_ctx->mac_handle,
+						 adapter->vdev_id))
+			hdd_handle_beacon_reporting_stop_op(hdd_ctx,
+							    adapter);
+	} else {
+		switch (type) {
+		case SCAN_EVENT_TYPE_STARTED:
+			abort_reason =
+		     QCA_WLAN_VENDOR_BEACON_REPORTING_PAUSE_REASON_SCAN_STARTED;
+			break;
+		default:
+			abort_reason =
+		      QCA_WLAN_VENDOR_BEACON_REPORTING_PAUSE_REASON_UNSPECIFIED;
+		}
+	}
+	/* Send vendor event to user space to inform ABORT */
+	if (nla_put_u32(vendor_event,
+			QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_OP_TYPE,
+			QCA_WLAN_VENDOR_BEACON_REPORTING_OP_PAUSE) ||
+	    nla_put_u32(vendor_event,
+			QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_PAUSE_REASON,
+			abort_reason)) {
+		hdd_err("QCA_WLAN_VENDOR_ATTR put fail");
+		kfree_skb(vendor_event);
+		return;
+	}
+
+	/*
+	 * Send auto resume flag to user space to specify the driver will
+	 * automatically resume reporting beacon events only in case of
+	 * pause indication due to scan started.
+	 */
+	if (!is_disconnected)
+		if (nla_put_flag(vendor_event,
+			QCA_WLAN_VENDOR_ATTR_BEACON_REPORTING_AUTO_RESUMES)) {
+			hdd_err("QCA_WLAN_VENDOR_ATTR put fail");
+			kfree_skb(vendor_event);
+			return;
+		}
+
+	cfg80211_vendor_event(vendor_event, flags);
+}
+
 int wlan_hdd_cfg80211_bcn_rcv_start(struct wiphy *wiphy,
 				    struct wireless_dev *wdev,
 				    const void *data, int data_len)

+ 37 - 0
core/sme/inc/sme_api.h

@@ -1580,6 +1580,43 @@ QDF_STATUS sme_update_mimo_power_save(mac_handle_t mac_handle,
  */
 QDF_STATUS sme_handle_bcn_recv_start(mac_handle_t mac_handle,
 				     uint32_t vdev_id);
+
+/**
+ * sme_is_beacon_report_started() - Check bcn recv started
+ * @mac_handle: Opaque handle to the global MAC context
+ * @session_id: SME session id
+ *
+ * This function is to check beacon report started or not.
+ *
+ * Return: true on success
+ */
+bool sme_is_beacon_report_started(mac_handle_t mac_handle,
+				  uint32_t session_id);
+
+/**
+ * stop_beacon_report() - To stop beacon report
+ * @mac_handle: Opaque handle to the global MAC context
+ * @session_id: SME session id
+ *
+ * Return: None
+ */
+void sme_stop_beacon_report(mac_handle_t mac_handle,
+			    uint32_t session_id);
+
+#else
+static inline
+bool sme_is_beacon_report_started(mac_handle_t mac_handle,
+				  uint32_t session_id)
+{
+	return true;
+}
+
+static inline
+void sme_stop_beacon_report(mac_handle_t mac_handle,
+			    uint32_t session_id)
+{
+}
+
 #endif
 
 QDF_STATUS sme_add_beacon_filter(mac_handle_t mac_handle,

+ 3 - 1
core/sme/inc/sme_internal.h

@@ -270,10 +270,12 @@ typedef void (*beacon_report_cb)(hdd_handle_t hdd_handle,
  * @hdd_handler: HDD handler
  * @vdev_id: vdev id
  * @type: scan event type
+ * @is_disconnected: Driver is in dis connected state or not
  */
 typedef void (*beacon_pause_cb)(hdd_handle_t hdd_handle,
 				uint8_t vdev_id,
-				enum scan_event_type type);
+				enum scan_event_type type,
+				bool is_disconnected);
 
 #ifdef WLAN_FEATURE_MOTION_DETECTION
 typedef QDF_STATUS (*md_host_evt_cb)(void *hdd_ctx, struct sir_md_evt *event);

+ 49 - 1
core/sme/src/common/sme_api.c

@@ -12656,6 +12656,48 @@ QDF_STATUS sme_handle_bcn_recv_start(mac_handle_t mac_handle,
 
 	return status;
 }
+
+void sme_stop_beacon_report(mac_handle_t mac_handle, uint32_t session_id)
+{
+	struct mac_context *mac_ctx = MAC_CONTEXT(mac_handle);
+	struct csr_roam_session *session;
+	QDF_STATUS status;
+
+	if (!CSR_IS_SESSION_VALID(mac_ctx, session_id)) {
+		sme_err("CSR session not valid: %d", session_id);
+		return;
+	}
+
+	session = CSR_GET_SESSION(mac_ctx, session_id);
+	if (!session) {
+		sme_err("vdev_id %d not found", session_id);
+		return;
+	}
+	status = sme_acquire_global_lock(&mac_ctx->sme);
+	if (QDF_IS_STATUS_SUCCESS(status)) {
+		session->is_bcn_recv_start = false;
+		sme_release_global_lock(&mac_ctx->sme);
+	}
+}
+
+bool sme_is_beacon_report_started(mac_handle_t mac_handle, uint32_t session_id)
+{
+	struct mac_context *mac_ctx = MAC_CONTEXT(mac_handle);
+	struct csr_roam_session *session;
+
+	if (!CSR_IS_SESSION_VALID(mac_ctx, session_id)) {
+		sme_err("CSR session not valid: %d", session_id);
+		return false;
+	}
+
+	session = CSR_GET_SESSION(mac_ctx, session_id);
+	if (!session) {
+		sme_err("vdev_id %d not found", session_id);
+		return false;
+	}
+
+	return session->is_bcn_recv_start;
+}
 #endif
 
 /**
@@ -15536,7 +15578,7 @@ static void sme_scan_event_handler(struct wlan_objmgr_vdev *vdev,
 	if (event->type == SCAN_EVENT_TYPE_STARTED) {
 		if (mac->sme.beacon_pause_cb)
 			mac->sme.beacon_pause_cb(mac->hdd_handle,
-				vdev->vdev_objmgr.vdev_id, event->type);
+				vdev->vdev_objmgr.vdev_id, event->type, false);
 	}
 }
 
@@ -15551,6 +15593,12 @@ QDF_STATUS sme_register_bcn_recv_pause_ind_cb(mac_handle_t mac_handle,
 		return QDF_STATUS_E_NOMEM;
 	}
 
+	/* scan event de-registration */
+	if (!cb) {
+		ucfg_scan_unregister_event_handler(mac->pdev,
+						   sme_scan_event_handler, mac);
+		return QDF_STATUS_SUCCESS;
+	}
 	status = sme_acquire_global_lock(&mac->sme);
 	if (QDF_IS_STATUS_SUCCESS(status)) {
 		mac->sme.beacon_pause_cb = cb;