* Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.
  * Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH
  * Copyright(c) 2015 - 2017 Intel Deutschland GmbH
+ * Copyright(c) 2018        Intel Corporation
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of version 2 of the GNU General Public License as
  * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.
  * Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH
  * Copyright(c) 2015 - 2017 Intel Deutschland GmbH
+ * Copyright(c) 2018        Intel Corporation
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 
 }; /* WOWLAN_WAKE_UP_REASON_API_E_VER_2 */
 
-struct iwl_wowlan_gtk_status {
+struct iwl_wowlan_gtk_status_v1 {
        u8 key_index;
        u8 reserved[3];
        u8 decrypt_key[16];
        struct iwl_wowlan_rsc_tsc_params_cmd rsc;
 } __packed; /* WOWLAN_GTK_MATERIAL_VER_1 */
 
+#define WOWLAN_KEY_MAX_SIZE    32
+#define WOWLAN_GTK_KEYS_NUM     2
+#define WOWLAN_IGTK_KEYS_NUM   2
+
+/**
+ * struct iwl_wowlan_gtk_status - GTK status
+ * @key: GTK material
+ * @key_len: GTK legth, if set to 0, the key is not available
+ * @key_flags: information about the key:
+ *     bits[0:1]:  key index assigned by the AP
+ *     bits[2:6]:  GTK index of the key in the internal DB
+ *     bit[7]:     Set iff this is the currently used GTK
+ * @reserved: padding
+ * @tkip_mic_key: TKIP RX MIC key
+ * @rsc: TSC RSC counters
+ */
+struct iwl_wowlan_gtk_status {
+       u8 key[WOWLAN_KEY_MAX_SIZE];
+       u8 key_len;
+       u8 key_flags;
+       u8 reserved[2];
+       u8 tkip_mic_key[8];
+       struct iwl_wowlan_rsc_tsc_params_cmd rsc;
+} __packed; /* WOWLAN_GTK_MATERIAL_VER_2 */
+
+#define IWL_WOWLAN_GTK_IDX_MASK                (BIT(0) | BIT(1))
+
+/**
+ * struct iwl_wowlan_igtk_status - IGTK status
+ * @key: IGTK material
+ * @ipn: the IGTK packet number (replay counter)
+ * @key_len: IGTK length, if set to 0, the key is not available
+ * @key_flags: information about the key:
+ *     bits[0]:    key index assigned by the AP (0: index 4, 1: index 5)
+ *     bits[1:5]:  IGTK index of the key in the internal DB
+ *     bit[6]:     Set iff this is the currently used IGTK
+ */
+struct iwl_wowlan_igtk_status {
+       u8 key[WOWLAN_KEY_MAX_SIZE];
+       u8 ipn[6];
+       u8 key_len;
+       u8 key_flags;
+} __packed; /* WOWLAN_IGTK_MATERIAL_VER_1 */
+
+/**
+ * struct iwl_wowlan_status_v6 - WoWLAN status
+ * @gtk: GTK data
+ * @replay_ctr: GTK rekey replay counter
+ * @pattern_number: number of the matched pattern
+ * @non_qos_seq_ctr: non-QoS sequence counter to use next
+ * @qos_seq_ctr: QoS sequence counters to use next
+ * @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason
+ * @num_of_gtk_rekeys: number of GTK rekeys
+ * @transmitted_ndps: number of transmitted neighbor discovery packets
+ * @received_beacons: number of received beacons
+ * @wake_packet_length: wakeup packet length
+ * @wake_packet_bufsize: wakeup packet buffer size
+ * @wake_packet: wakeup packet
+ */
+struct iwl_wowlan_status_v6 {
+       struct iwl_wowlan_gtk_status_v1 gtk;
+       __le64 replay_ctr;
+       __le16 pattern_number;
+       __le16 non_qos_seq_ctr;
+       __le16 qos_seq_ctr[8];
+       __le32 wakeup_reasons;
+       __le32 num_of_gtk_rekeys;
+       __le32 transmitted_ndps;
+       __le32 received_beacons;
+       __le32 wake_packet_length;
+       __le32 wake_packet_bufsize;
+       u8 wake_packet[]; /* can be truncated from _length to _bufsize */
+} __packed; /* WOWLAN_STATUSES_API_S_VER_6 */
+
 /**
  * struct iwl_wowlan_status - WoWLAN status
  * @gtk: GTK data
+ * @igtk: IGTK data
  * @replay_ctr: GTK rekey replay counter
  * @pattern_number: number of the matched pattern
  * @non_qos_seq_ctr: non-QoS sequence counter to use next
  * @wake_packet: wakeup packet
  */
 struct iwl_wowlan_status {
-       struct iwl_wowlan_gtk_status gtk;
+       struct iwl_wowlan_gtk_status gtk[WOWLAN_GTK_KEYS_NUM];
+       struct iwl_wowlan_igtk_status igtk[WOWLAN_IGTK_KEYS_NUM];
        __le64 replay_ctr;
        __le16 pattern_number;
        __le16 non_qos_seq_ctr;
        __le32 wake_packet_length;
        __le32 wake_packet_bufsize;
        u8 wake_packet[]; /* can be truncated from _length to _bufsize */
-} __packed; /* WOWLAN_STATUSES_API_S_VER_6 */
+} __packed; /* WOWLAN_STATUSES_API_S_VER_7 */
+
+static inline u8 iwlmvm_wowlan_gtk_idx(struct iwl_wowlan_gtk_status *gtk)
+{
+       return gtk->key_flags & IWL_WOWLAN_GTK_IDX_MASK;
+}
 
 #define IWL_WOWLAN_TCP_MAX_PACKET_LEN          64
 #define IWL_WOWLAN_REMOTE_WAKE_MAX_PACKET_LEN  128
 
                                   struct ieee80211_key_conf *key,
                                   struct iwl_wowlan_status *status)
 {
-       union iwl_all_tsc_rsc *rsc = &status->gtk.rsc.all_tsc_rsc;
+       union iwl_all_tsc_rsc *rsc = &status->gtk[0].rsc.all_tsc_rsc;
 
        switch (key->cipher) {
        case WLAN_CIPHER_SUITE_CCMP:
         */
        if (sta) {
                struct ieee80211_key_seq seq = {};
-               union iwl_all_tsc_rsc *sc = &data->status->gtk.rsc.all_tsc_rsc;
+               union iwl_all_tsc_rsc *sc =
+                       &data->status->gtk[0].rsc.all_tsc_rsc;
 
                if (data->find_phase)
                        return;
                        u8 key[32];
                } conf = {
                        .conf.cipher = gtkdata.cipher,
-                       .conf.keyidx = status->gtk.key_index,
+                       .conf.keyidx =
+                               iwlmvm_wowlan_gtk_idx(&status->gtk[0]),
                };
                __be64 replay_ctr;
 
                switch (gtkdata.cipher) {
                case WLAN_CIPHER_SUITE_CCMP:
                        conf.conf.keylen = WLAN_KEY_LEN_CCMP;
-                       memcpy(conf.conf.key, status->gtk.decrypt_key,
+                       memcpy(conf.conf.key, status->gtk[0].key,
                               WLAN_KEY_LEN_CCMP);
                        break;
                case WLAN_CIPHER_SUITE_TKIP:
                        conf.conf.keylen = WLAN_KEY_LEN_TKIP;
-                       memcpy(conf.conf.key, status->gtk.decrypt_key, 16);
+                       memcpy(conf.conf.key, status->gtk[0].key, 16);
                        /* leave TX MIC key zeroed, we don't use it anyway */
                        memcpy(conf.conf.key +
                               NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY,
-                              status->gtk.tkip_mic_key, 8);
+                              status->gtk[0].tkip_mic_key, 8);
                        break;
                }
 
                        return false;
                iwl_mvm_set_key_rx_seq(mvm, key, status);
 
-               replay_ctr = cpu_to_be64(le64_to_cpu(status->replay_ctr));
+               replay_ctr =
+                       cpu_to_be64(le64_to_cpu(status->replay_ctr));
 
                ieee80211_gtk_rekey_notify(vif, vif->bss_conf.bssid,
                                           (void *)&replay_ctr, GFP_KERNEL);
        return true;
 }
 
+struct iwl_wowlan_status *iwl_mvm_send_wowlan_get_status(struct iwl_mvm *mvm)
+{
+       struct iwl_wowlan_status *v7, *status;
+       struct iwl_host_cmd cmd = {
+               .id = WOWLAN_GET_STATUSES,
+               .flags = CMD_WANT_SKB,
+       };
+       int ret, len, status_size;
+
+       lockdep_assert_held(&mvm->mutex);
+
+       ret = iwl_mvm_send_cmd(mvm, &cmd);
+       if (ret) {
+               IWL_ERR(mvm, "failed to query wakeup status (%d)\n", ret);
+               return ERR_PTR(ret);
+       }
+
+       if (!fw_has_api(&mvm->fw->ucode_capa,
+                       IWL_UCODE_TLV_API_WOWLAN_KEY_MATERIAL)) {
+               struct iwl_wowlan_status_v6 *v6 = (void *)cmd.resp_pkt->data;
+               int data_size;
+
+               status_size = sizeof(*v6);
+               len = iwl_rx_packet_payload_len(cmd.resp_pkt);
+
+               if (len < status_size) {
+                       IWL_ERR(mvm, "Invalid WoWLAN status response!\n");
+                       status = ERR_PTR(-EIO);
+                       goto out_free_resp;
+               }
+
+               data_size = ALIGN(le32_to_cpu(v6->wake_packet_bufsize), 4);
+
+               if (len != (status_size + data_size)) {
+                       IWL_ERR(mvm, "Invalid WoWLAN status response!\n");
+                       status = ERR_PTR(-EIO);
+                       goto out_free_resp;
+               }
+
+               status = kzalloc(sizeof(*status) + data_size, GFP_KERNEL);
+               if (!status)
+                       goto out_free_resp;
+
+               BUILD_BUG_ON(sizeof(v6->gtk.decrypt_key) >
+                            sizeof(status->gtk[0].key));
+               BUILD_BUG_ON(sizeof(v6->gtk.tkip_mic_key) >
+                            sizeof(status->gtk[0].tkip_mic_key));
+
+               /* copy GTK info to the right place */
+               memcpy(status->gtk[0].key, v6->gtk.decrypt_key,
+                      sizeof(v6->gtk.decrypt_key));
+               memcpy(status->gtk[0].tkip_mic_key, v6->gtk.tkip_mic_key,
+                      sizeof(v6->gtk.tkip_mic_key));
+               memcpy(&status->gtk[0].rsc, &v6->gtk.rsc,
+                      sizeof(status->gtk[0].rsc));
+
+               /* hardcode the key length to 16 since v6 only supports 16 */
+               status->gtk[0].key_len = 16;
+
+               /*
+                * The key index only uses 2 bits (values 0 to 3) and
+                * we always set bit 7 which means this is the
+                * currently used key.
+                */
+               status->gtk[0].key_flags = v6->gtk.key_index | BIT(7);
+
+               status->replay_ctr = v6->replay_ctr;
+
+               /* everything starting from pattern_number is identical */
+               memcpy(&status->pattern_number, &v6->pattern_number,
+                      offsetof(struct iwl_wowlan_status, wake_packet) -
+                      offsetof(struct iwl_wowlan_status, pattern_number) +
+                      data_size);
+
+               goto out_free_resp;
+       }
+
+       v7 = (void *)cmd.resp_pkt->data;
+       status_size = sizeof(*v7);
+       len = iwl_rx_packet_payload_len(cmd.resp_pkt);
+
+       if (len < status_size) {
+               IWL_ERR(mvm, "Invalid WoWLAN status response!\n");
+               status = ERR_PTR(-EIO);
+               goto out_free_resp;
+       }
+
+       if (len != (status_size +
+                   ALIGN(le32_to_cpu(v7->wake_packet_bufsize), 4))) {
+               IWL_ERR(mvm, "Invalid WoWLAN status response!\n");
+               status = ERR_PTR(-EIO);
+               goto out_free_resp;
+       }
+
+       status = kmemdup(v7, len, GFP_KERNEL);
+
+out_free_resp:
+       iwl_free_resp(&cmd);
+       return status;
+}
+
 static struct iwl_wowlan_status *
 iwl_mvm_get_wakeup_status(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
 {
                u32 valid;
                u32 error_id;
        } err_info;
-       struct iwl_host_cmd cmd = {
-               .id = WOWLAN_GET_STATUSES,
-               .flags = CMD_WANT_SKB,
-       };
-       struct iwl_wowlan_status *status, *fw_status;
-       int ret, len, status_size;
+       int ret;
 
        iwl_trans_read_mem_bytes(mvm->trans, base,
                                 &err_info, sizeof(err_info));
        if (ret)
                IWL_ERR(mvm, "failed to query offload statistics (%d)\n", ret);
 
-       ret = iwl_mvm_send_cmd(mvm, &cmd);
-       if (ret) {
-               IWL_ERR(mvm, "failed to query status (%d)\n", ret);
-               return ERR_PTR(ret);
-       }
-
-       status_size = sizeof(*fw_status);
-
-       len = iwl_rx_packet_payload_len(cmd.resp_pkt);
-       if (len < status_size) {
-               IWL_ERR(mvm, "Invalid WoWLAN status response!\n");
-               fw_status = ERR_PTR(-EIO);
-               goto out_free_resp;
-       }
-
-       status = (void *)cmd.resp_pkt->data;
-       if (len != (status_size +
-                   ALIGN(le32_to_cpu(status->wake_packet_bufsize), 4))) {
-               IWL_ERR(mvm, "Invalid WoWLAN status response!\n");
-               fw_status = ERR_PTR(-EIO);
-               goto out_free_resp;
-       }
-
-       fw_status = kmemdup(status, len, GFP_KERNEL);
-
-out_free_resp:
-       iwl_free_resp(&cmd);
-       return fw_status;
+       return iwl_mvm_send_wowlan_get_status(mvm);
 }
 
 /* releases the MVM mutex */