iwlwifi: mvm: implement remote wake
authorJohannes Berg <johannes.berg@intel.com>
Tue, 22 Jan 2013 19:41:58 +0000 (20:41 +0100)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 6 Mar 2013 15:47:51 +0000 (16:47 +0100)
With remote wake, the firmware creates a TCP connection
and sends some configurable data on it, until a special
TCP data packet from the server is received that triggers
a wakeup. The configuration is a bit tricky because it is
based on packet pattern matching but this is hidden in
the driver and the exposed API in cfg80211 is just based
on the required TCP connection parameters.

Reviewed-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
drivers/net/wireless/iwlwifi/mvm/d3.c
drivers/net/wireless/iwlwifi/mvm/fw-api-d3.h
drivers/net/wireless/iwlwifi/mvm/mac80211.c

index 376a577..d4578ce 100644 (file)
  *****************************************************************************/
 
 #include <linux/etherdevice.h>
+#include <linux/ip.h>
 #include <net/cfg80211.h>
 #include <net/ipv6.h>
+#include <net/tcp.h>
 #include "iwl-modparams.h"
 #include "fw-api.h"
 #include "mvm.h"
@@ -402,6 +404,233 @@ static int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm,
                                    sizeof(cmd), &cmd);
 }
 
+enum iwl_mvm_tcp_packet_type {
+       MVM_TCP_TX_SYN,
+       MVM_TCP_RX_SYNACK,
+       MVM_TCP_TX_DATA,
+       MVM_TCP_RX_ACK,
+       MVM_TCP_RX_WAKE,
+       MVM_TCP_TX_FIN,
+};
+
+static __le16 pseudo_hdr_check(int len, __be32 saddr, __be32 daddr)
+{
+       __sum16 check = tcp_v4_check(len, saddr, daddr, 0);
+       return cpu_to_le16(be16_to_cpu((__force __be16)check));
+}
+
+static void iwl_mvm_build_tcp_packet(struct iwl_mvm *mvm,
+                                    struct ieee80211_vif *vif,
+                                    struct cfg80211_wowlan_tcp *tcp,
+                                    void *_pkt, u8 *mask,
+                                    __le16 *pseudo_hdr_csum,
+                                    enum iwl_mvm_tcp_packet_type ptype)
+{
+       struct {
+               struct ethhdr eth;
+               struct iphdr ip;
+               struct tcphdr tcp;
+               u8 data[];
+       } __packed *pkt = _pkt;
+       u16 ip_tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);
+       int i;
+
+       pkt->eth.h_proto = cpu_to_be16(ETH_P_IP),
+       pkt->ip.version = 4;
+       pkt->ip.ihl = 5;
+       pkt->ip.protocol = IPPROTO_TCP;
+
+       switch (ptype) {
+       case MVM_TCP_TX_SYN:
+       case MVM_TCP_TX_DATA:
+       case MVM_TCP_TX_FIN:
+               memcpy(pkt->eth.h_dest, tcp->dst_mac, ETH_ALEN);
+               memcpy(pkt->eth.h_source, vif->addr, ETH_ALEN);
+               pkt->ip.ttl = 128;
+               pkt->ip.saddr = tcp->src;
+               pkt->ip.daddr = tcp->dst;
+               pkt->tcp.source = cpu_to_be16(tcp->src_port);
+               pkt->tcp.dest = cpu_to_be16(tcp->dst_port);
+               /* overwritten for TX SYN later */
+               pkt->tcp.doff = sizeof(struct tcphdr) / 4;
+               pkt->tcp.window = cpu_to_be16(65000);
+               break;
+       case MVM_TCP_RX_SYNACK:
+       case MVM_TCP_RX_ACK:
+       case MVM_TCP_RX_WAKE:
+               memcpy(pkt->eth.h_dest, vif->addr, ETH_ALEN);
+               memcpy(pkt->eth.h_source, tcp->dst_mac, ETH_ALEN);
+               pkt->ip.saddr = tcp->dst;
+               pkt->ip.daddr = tcp->src;
+               pkt->tcp.source = cpu_to_be16(tcp->dst_port);
+               pkt->tcp.dest = cpu_to_be16(tcp->src_port);
+               break;
+       default:
+               WARN_ON(1);
+               return;
+       }
+
+       switch (ptype) {
+       case MVM_TCP_TX_SYN:
+               /* firmware assumes 8 option bytes - 8 NOPs for now */
+               memset(pkt->data, 0x01, 8);
+               ip_tot_len += 8;
+               pkt->tcp.doff = (sizeof(struct tcphdr) + 8) / 4;
+               pkt->tcp.syn = 1;
+               break;
+       case MVM_TCP_TX_DATA:
+               ip_tot_len += tcp->payload_len;
+               memcpy(pkt->data, tcp->payload, tcp->payload_len);
+               pkt->tcp.psh = 1;
+               pkt->tcp.ack = 1;
+               break;
+       case MVM_TCP_TX_FIN:
+               pkt->tcp.fin = 1;
+               pkt->tcp.ack = 1;
+               break;
+       case MVM_TCP_RX_SYNACK:
+               pkt->tcp.syn = 1;
+               pkt->tcp.ack = 1;
+               break;
+       case MVM_TCP_RX_ACK:
+               pkt->tcp.ack = 1;
+               break;
+       case MVM_TCP_RX_WAKE:
+               ip_tot_len += tcp->wake_len;
+               pkt->tcp.psh = 1;
+               pkt->tcp.ack = 1;
+               memcpy(pkt->data, tcp->wake_data, tcp->wake_len);
+               break;
+       }
+
+       switch (ptype) {
+       case MVM_TCP_TX_SYN:
+       case MVM_TCP_TX_DATA:
+       case MVM_TCP_TX_FIN:
+               pkt->ip.tot_len = cpu_to_be16(ip_tot_len);
+               pkt->ip.check = ip_fast_csum(&pkt->ip, pkt->ip.ihl);
+               break;
+       case MVM_TCP_RX_WAKE:
+               for (i = 0; i < DIV_ROUND_UP(tcp->wake_len, 8); i++) {
+                       u8 tmp = tcp->wake_mask[i];
+                       mask[i + 6] |= tmp << 6;
+                       if (i + 1 < DIV_ROUND_UP(tcp->wake_len, 8))
+                               mask[i + 7] = tmp >> 2;
+               }
+               /* fall through for ethernet/IP/TCP headers mask */
+       case MVM_TCP_RX_SYNACK:
+       case MVM_TCP_RX_ACK:
+               mask[0] = 0xff; /* match ethernet */
+               /*
+                * match ethernet, ip.version, ip.ihl
+                * the ip.ihl half byte is really masked out by firmware
+                */
+               mask[1] = 0x7f;
+               mask[2] = 0x80; /* match ip.protocol */
+               mask[3] = 0xfc; /* match ip.saddr, ip.daddr */
+               mask[4] = 0x3f; /* match ip.daddr, tcp.source, tcp.dest */
+               mask[5] = 0x80; /* match tcp flags */
+               /* leave rest (0 or set for MVM_TCP_RX_WAKE) */
+               break;
+       };
+
+       *pseudo_hdr_csum = pseudo_hdr_check(ip_tot_len - sizeof(struct iphdr),
+                                           pkt->ip.saddr, pkt->ip.daddr);
+}
+
+static int iwl_mvm_send_remote_wake_cfg(struct iwl_mvm *mvm,
+                                       struct ieee80211_vif *vif,
+                                       struct cfg80211_wowlan_tcp *tcp)
+{
+       struct iwl_wowlan_remote_wake_config *cfg;
+       struct iwl_host_cmd cmd = {
+               .id = REMOTE_WAKE_CONFIG_CMD,
+               .len = { sizeof(*cfg), },
+               .dataflags = { IWL_HCMD_DFL_NOCOPY, },
+               .flags = CMD_SYNC,
+       };
+       int ret;
+
+       if (!tcp)
+               return 0;
+
+       cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
+       if (!cfg)
+               return -ENOMEM;
+       cmd.data[0] = cfg;
+
+       cfg->max_syn_retries = 10;
+       cfg->max_data_retries = 10;
+       cfg->tcp_syn_ack_timeout = 1; /* seconds */
+       cfg->tcp_ack_timeout = 1; /* seconds */
+
+       /* SYN (TX) */
+       iwl_mvm_build_tcp_packet(
+               mvm, vif, tcp, cfg->syn_tx.data, NULL,
+               &cfg->syn_tx.info.tcp_pseudo_header_checksum,
+               MVM_TCP_TX_SYN);
+       cfg->syn_tx.info.tcp_payload_length = 0;
+
+       /* SYN/ACK (RX) */
+       iwl_mvm_build_tcp_packet(
+               mvm, vif, tcp, cfg->synack_rx.data, cfg->synack_rx.rx_mask,
+               &cfg->synack_rx.info.tcp_pseudo_header_checksum,
+               MVM_TCP_RX_SYNACK);
+       cfg->synack_rx.info.tcp_payload_length = 0;
+
+       /* KEEPALIVE/ACK (TX) */
+       iwl_mvm_build_tcp_packet(
+               mvm, vif, tcp, cfg->keepalive_tx.data, NULL,
+               &cfg->keepalive_tx.info.tcp_pseudo_header_checksum,
+               MVM_TCP_TX_DATA);
+       cfg->keepalive_tx.info.tcp_payload_length =
+               cpu_to_le16(tcp->payload_len);
+       cfg->sequence_number_offset = tcp->payload_seq.offset;
+       /* length must be 0..4, the field is little endian */
+       cfg->sequence_number_length = tcp->payload_seq.len;
+       cfg->initial_sequence_number = cpu_to_le32(tcp->payload_seq.start);
+       cfg->keepalive_interval = cpu_to_le16(tcp->data_interval);
+       if (tcp->payload_tok.len) {
+               cfg->token_offset = tcp->payload_tok.offset;
+               cfg->token_length = tcp->payload_tok.len;
+               cfg->num_tokens =
+                       cpu_to_le16(tcp->tokens_size % tcp->payload_tok.len);
+               memcpy(cfg->tokens, tcp->payload_tok.token_stream,
+                      tcp->tokens_size);
+       } else {
+               /* set tokens to max value to almost never run out */
+               cfg->num_tokens = cpu_to_le16(65535);
+       }
+
+       /* ACK (RX) */
+       iwl_mvm_build_tcp_packet(
+               mvm, vif, tcp, cfg->keepalive_ack_rx.data,
+               cfg->keepalive_ack_rx.rx_mask,
+               &cfg->keepalive_ack_rx.info.tcp_pseudo_header_checksum,
+               MVM_TCP_RX_ACK);
+       cfg->keepalive_ack_rx.info.tcp_payload_length = 0;
+
+       /* WAKEUP (RX) */
+       iwl_mvm_build_tcp_packet(
+               mvm, vif, tcp, cfg->wake_rx.data, cfg->wake_rx.rx_mask,
+               &cfg->wake_rx.info.tcp_pseudo_header_checksum,
+               MVM_TCP_RX_WAKE);
+       cfg->wake_rx.info.tcp_payload_length =
+               cpu_to_le16(tcp->wake_len);
+
+       /* FIN */
+       iwl_mvm_build_tcp_packet(
+               mvm, vif, tcp, cfg->fin_tx.data, NULL,
+               &cfg->fin_tx.info.tcp_pseudo_header_checksum,
+               MVM_TCP_TX_FIN);
+       cfg->fin_tx.info.tcp_payload_length = 0;
+
+       ret = iwl_mvm_send_cmd(mvm, &cmd);
+       kfree(cfg);
+
+       return ret;
+}
+
 struct iwl_d3_iter_data {
        struct iwl_mvm *mvm;
        struct ieee80211_vif *vif;
@@ -640,6 +869,22 @@ int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
                d3_cfg_cmd.wakeup_flags |=
                        cpu_to_le32(IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT);
 
+       if (wowlan->tcp) {
+               /*
+                * The firmware currently doesn't really look at these, only
+                * the IWL_WOWLAN_WAKEUP_LINK_CHANGE bit. We have to set that
+                * reason bit since losing the connection to the AP implies
+                * losing the TCP connection.
+                * Set the flags anyway as long as they exist, in case this
+                * will be changed in the firmware.
+                */
+               wowlan_config_cmd.wakeup_filter |=
+                       cpu_to_le32(IWL_WOWLAN_WAKEUP_REMOTE_LINK_LOSS |
+                                   IWL_WOWLAN_WAKEUP_REMOTE_SIGNATURE_TABLE |
+                                   IWL_WOWLAN_WAKEUP_REMOTE_WAKEUP_PACKET |
+                                   IWL_WOWLAN_WAKEUP_LINK_CHANGE);
+       }
+
        iwl_mvm_cancel_scan(mvm);
 
        iwl_trans_stop_device(mvm->trans);
@@ -755,6 +1000,10 @@ int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
        if (ret)
                goto out;
 
+       ret = iwl_mvm_send_remote_wake_cfg(mvm, vif, wowlan->tcp);
+       if (ret)
+               goto out;
+
        /* must be last -- this switches firmware state */
        ret = iwl_mvm_send_cmd_pdu(mvm, D3_CONFIG_CMD, CMD_SYNC,
                                   sizeof(d3_cfg_cmd), &d3_cfg_cmd);
@@ -874,6 +1123,15 @@ static void iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm,
        if (reasons & IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE)
                wakeup.four_way_handshake = true;
 
+       if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS)
+               wakeup.tcp_connlost = true;
+
+       if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE)
+               wakeup.tcp_nomoretokens = true;
+
+       if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET)
+               wakeup.tcp_match = true;
+
        if (status->wake_packet_bufsize) {
                int pktsize = le32_to_cpu(status->wake_packet_bufsize);
                int pktlen = le32_to_cpu(status->wake_packet_length);
index a442ee1..51e015d 100644 (file)
@@ -258,7 +258,7 @@ enum iwl_wowlan_wakeup_reason {
        IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE                 = BIT(8),
        IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS                 = BIT(9),
        IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE           = BIT(10),
-       IWL_WOWLAN_WAKEUP_BY_REM_WAKE_TCP_EXTERNAL              = BIT(11),
+       /* BIT(11) reserved */
        IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET             = BIT(12),
 }; /* WOWLAN_WAKE_UP_REASON_API_E_VER_2 */
 
@@ -277,6 +277,55 @@ struct iwl_wowlan_status {
        u8 wake_packet[]; /* can be truncated from _length to _bufsize */
 } __packed; /* WOWLAN_STATUSES_API_S_VER_4 */
 
+#define IWL_WOWLAN_TCP_MAX_PACKET_LEN          64
+#define IWL_WOWLAN_REMOTE_WAKE_MAX_PACKET_LEN  128
+#define IWL_WOWLAN_REMOTE_WAKE_MAX_TOKENS      2048
+
+struct iwl_tcp_packet_info {
+       __le16 tcp_pseudo_header_checksum;
+       __le16 tcp_payload_length;
+} __packed; /* TCP_PACKET_INFO_API_S_VER_2 */
+
+struct iwl_tcp_packet {
+       struct iwl_tcp_packet_info info;
+       u8 rx_mask[IWL_WOWLAN_MAX_PATTERN_LEN / 8];
+       u8 data[IWL_WOWLAN_TCP_MAX_PACKET_LEN];
+} __packed; /* TCP_PROTOCOL_PACKET_API_S_VER_1 */
+
+struct iwl_remote_wake_packet {
+       struct iwl_tcp_packet_info info;
+       u8 rx_mask[IWL_WOWLAN_MAX_PATTERN_LEN / 8];
+       u8 data[IWL_WOWLAN_REMOTE_WAKE_MAX_PACKET_LEN];
+} __packed; /* TCP_PROTOCOL_PACKET_API_S_VER_1 */
+
+struct iwl_wowlan_remote_wake_config {
+       __le32 connection_max_time; /* unused */
+       /* TCP_PROTOCOL_CONFIG_API_S_VER_1 */
+       u8 max_syn_retries;
+       u8 max_data_retries;
+       u8 tcp_syn_ack_timeout;
+       u8 tcp_ack_timeout;
+
+       struct iwl_tcp_packet syn_tx;
+       struct iwl_tcp_packet synack_rx;
+       struct iwl_tcp_packet keepalive_ack_rx;
+       struct iwl_tcp_packet fin_tx;
+
+       struct iwl_remote_wake_packet keepalive_tx;
+       struct iwl_remote_wake_packet wake_rx;
+
+       /* REMOTE_WAKE_OFFSET_INFO_API_S_VER_1 */
+       u8 sequence_number_offset;
+       u8 sequence_number_length;
+       u8 token_offset;
+       u8 token_length;
+       /* REMOTE_WAKE_PROTOCOL_PARAMS_API_S_VER_1 */
+       __le32 initial_sequence_number;
+       __le16 keepalive_interval;
+       __le16 num_tokens;
+       u8 tokens[IWL_WOWLAN_REMOTE_WAKE_MAX_TOKENS];
+} __packed; /* REMOTE_WAKE_CONFIG_API_S_VER_2 */
+
 /* TODO: NetDetect API */
 
 #endif /* __fw_api_d3_h__ */
index 924cabf..ed2d875 100644 (file)
@@ -65,7 +65,9 @@
 #include <linux/skbuff.h>
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
+#include <linux/ip.h>
 #include <net/mac80211.h>
+#include <net/tcp.h>
 
 #include "iwl-op-mode.h"
 #include "iwl-io.h"
@@ -102,6 +104,29 @@ static const struct ieee80211_iface_combination iwl_mvm_iface_combinations[] = {
        },
 };
 
+#ifdef CONFIG_PM_SLEEP
+static const struct nl80211_wowlan_tcp_data_token_feature
+iwl_mvm_wowlan_tcp_token_feature = {
+       .min_len = 0,
+       .max_len = 255,
+       .bufsize = IWL_WOWLAN_REMOTE_WAKE_MAX_TOKENS,
+};
+
+static const struct wiphy_wowlan_tcp_support iwl_mvm_wowlan_tcp_support = {
+       .tok = &iwl_mvm_wowlan_tcp_token_feature,
+       .data_payload_max = IWL_WOWLAN_TCP_MAX_PACKET_LEN -
+                           sizeof(struct ethhdr) -
+                           sizeof(struct iphdr) -
+                           sizeof(struct tcphdr),
+       .data_interval_max = 65535, /* __le16 in API */
+       .wake_payload_max = IWL_WOWLAN_REMOTE_WAKE_MAX_PACKET_LEN -
+                           sizeof(struct ethhdr) -
+                           sizeof(struct iphdr) -
+                           sizeof(struct tcphdr),
+       .seq = true,
+};
+#endif
+
 int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
 {
        struct ieee80211_hw *hw = mvm->hw;
@@ -210,6 +235,7 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
                hw->wiphy->wowlan.n_patterns = IWL_WOWLAN_MAX_PATTERNS;
                hw->wiphy->wowlan.pattern_min_len = IWL_WOWLAN_MIN_PATTERN_LEN;
                hw->wiphy->wowlan.pattern_max_len = IWL_WOWLAN_MAX_PATTERN_LEN;
+               hw->wiphy->wowlan.tcp = &iwl_mvm_wowlan_tcp_support;
        }
 #endif