struct wmi_rdy_ev_arg *arg);
        int (*pull_fw_stats)(struct ath10k *ar, struct sk_buff *skb,
                             struct ath10k_fw_stats *stats);
+       int (*pull_roam_ev)(struct ath10k *ar, struct sk_buff *skb,
+                           struct wmi_roam_ev_arg *arg);
 
        struct sk_buff *(*gen_pdev_suspend)(struct ath10k *ar, u32 suspend_opt);
        struct sk_buff *(*gen_pdev_resume)(struct ath10k *ar);
        return ar->wmi.ops->pull_fw_stats(ar, skb, stats);
 }
 
+static inline int
+ath10k_wmi_pull_roam_ev(struct ath10k *ar, struct sk_buff *skb,
+                       struct wmi_roam_ev_arg *arg)
+{
+       if (!ar->wmi.ops->pull_roam_ev)
+               return -EOPNOTSUPP;
+
+       return ar->wmi.ops->pull_roam_ev(ar, skb, arg);
+}
+
 static inline int
 ath10k_wmi_mgmt_tx(struct ath10k *ar, struct sk_buff *msdu)
 {
 
                = { .min_len = sizeof(struct wmi_tlv_diag_data_ev) },
        [WMI_TLV_TAG_STRUCT_P2P_NOA_EVENT]
                = { .min_len = sizeof(struct wmi_tlv_p2p_noa_ev) },
+       [WMI_TLV_TAG_STRUCT_ROAM_EVENT]
+               = { .min_len = sizeof(struct wmi_tlv_roam_ev) },
 };
 
 static int
        return 0;
 }
 
+static int ath10k_wmi_tlv_op_pull_roam_ev(struct ath10k *ar,
+                                         struct sk_buff *skb,
+                                         struct wmi_roam_ev_arg *arg)
+{
+       const void **tb;
+       const struct wmi_tlv_roam_ev *ev;
+       int ret;
+
+       tb = ath10k_wmi_tlv_parse_alloc(ar, skb->data, skb->len, GFP_ATOMIC);
+       if (IS_ERR(tb)) {
+               ret = PTR_ERR(tb);
+               ath10k_warn(ar, "failed to parse tlv: %d\n", ret);
+               return ret;
+       }
+
+       ev = tb[WMI_TLV_TAG_STRUCT_ROAM_EVENT];
+       if (!ev) {
+               kfree(tb);
+               return -EPROTO;
+       }
+
+       arg->vdev_id = ev->vdev_id;
+       arg->reason = ev->reason;
+       arg->rssi = ev->rssi;
+
+       kfree(tb);
+       return 0;
+}
+
 static struct sk_buff *
 ath10k_wmi_tlv_op_gen_pdev_suspend(struct ath10k *ar, u32 opt)
 {
        .pull_svc_rdy = ath10k_wmi_tlv_op_pull_svc_rdy_ev,
        .pull_rdy = ath10k_wmi_tlv_op_pull_rdy_ev,
        .pull_fw_stats = ath10k_wmi_tlv_op_pull_fw_stats,
+       .pull_roam_ev = ath10k_wmi_tlv_op_pull_roam_ev,
 
        .gen_pdev_suspend = ath10k_wmi_tlv_op_gen_pdev_suspend,
        .gen_pdev_resume = ath10k_wmi_tlv_op_gen_pdev_resume,
 
 
 void ath10k_wmi_event_roam(struct ath10k *ar, struct sk_buff *skb)
 {
-       ath10k_dbg(ar, ATH10K_DBG_WMI, "WMI_ROAM_EVENTID\n");
+       struct wmi_roam_ev_arg arg = {};
+       int ret;
+       u32 vdev_id;
+       u32 reason;
+       s32 rssi;
+
+       ret = ath10k_wmi_pull_roam_ev(ar, skb, &arg);
+       if (ret) {
+               ath10k_warn(ar, "failed to parse roam event: %d\n", ret);
+               return;
+       }
+
+       vdev_id = __le32_to_cpu(arg.vdev_id);
+       reason = __le32_to_cpu(arg.reason);
+       rssi = __le32_to_cpu(arg.rssi);
+       rssi += WMI_SPECTRAL_NOISE_FLOOR_REF_DEFAULT;
+
+       ath10k_dbg(ar, ATH10K_DBG_WMI,
+                  "wmi roam event vdev %u reason 0x%08x rssi %d\n",
+                  vdev_id, reason, rssi);
+
+       if (reason >= WMI_ROAM_REASON_MAX)
+               ath10k_warn(ar, "ignoring unknown roam event reason %d on vdev %i\n",
+                           reason, vdev_id);
+
+       switch (reason) {
+       case WMI_ROAM_REASON_BETTER_AP:
+       case WMI_ROAM_REASON_BEACON_MISS:
+       case WMI_ROAM_REASON_LOW_RSSI:
+       case WMI_ROAM_REASON_SUITABLE_AP_FOUND:
+       case WMI_ROAM_REASON_HO_FAILED:
+               ath10k_warn(ar, "ignoring not implemented roam event reason %d on vdev %i\n",
+                           reason, vdev_id);
+               break;
+       }
 }
 
 void ath10k_wmi_event_profile_match(struct ath10k *ar, struct sk_buff *skb)
        return 0;
 }
 
+static int ath10k_wmi_op_pull_roam_ev(struct ath10k *ar, struct sk_buff *skb,
+                                     struct wmi_roam_ev_arg *arg)
+{
+       struct wmi_roam_ev *ev = (void *)skb->data;
+
+       if (skb->len < sizeof(*ev))
+               return -EPROTO;
+
+       skb_pull(skb, sizeof(*ev));
+       arg->vdev_id = ev->vdev_id;
+       arg->reason = ev->reason;
+
+       return 0;
+}
+
 int ath10k_wmi_event_ready(struct ath10k *ar, struct sk_buff *skb)
 {
        struct wmi_rdy_ev_arg arg = {};
        .pull_svc_rdy = ath10k_wmi_main_op_pull_svc_rdy_ev,
        .pull_rdy = ath10k_wmi_op_pull_rdy_ev,
        .pull_fw_stats = ath10k_wmi_main_op_pull_fw_stats,
+       .pull_roam_ev = ath10k_wmi_op_pull_roam_ev,
 
        .gen_pdev_suspend = ath10k_wmi_op_gen_pdev_suspend,
        .gen_pdev_resume = ath10k_wmi_op_gen_pdev_resume,
        .pull_swba = ath10k_wmi_op_pull_swba_ev,
        .pull_phyerr = ath10k_wmi_op_pull_phyerr_ev,
        .pull_rdy = ath10k_wmi_op_pull_rdy_ev,
+       .pull_roam_ev = ath10k_wmi_op_pull_roam_ev,
 
        .gen_pdev_suspend = ath10k_wmi_op_gen_pdev_suspend,
        .gen_pdev_resume = ath10k_wmi_op_gen_pdev_resume,
        .pull_swba = ath10k_wmi_op_pull_swba_ev,
        .pull_phyerr = ath10k_wmi_op_pull_phyerr_ev,
        .pull_rdy = ath10k_wmi_op_pull_rdy_ev,
+       .pull_roam_ev = ath10k_wmi_op_pull_roam_ev,
 
        .gen_pdev_suspend = ath10k_wmi_op_gen_pdev_suspend,
        .gen_pdev_resume = ath10k_wmi_op_gen_pdev_resume,
        .pull_swba = ath10k_wmi_op_pull_swba_ev,
        .pull_phyerr = ath10k_wmi_op_pull_phyerr_ev,
        .pull_rdy = ath10k_wmi_op_pull_rdy_ev,
+       .pull_roam_ev = ath10k_wmi_op_pull_roam_ev,
 
        .gen_pdev_suspend = ath10k_wmi_op_gen_pdev_suspend,
        .gen_pdev_resume = ath10k_wmi_op_gen_pdev_resume,