diff options
Diffstat (limited to 'drivers/net/wireless/iwlwifi/mvm/d3.c')
| -rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/d3.c | 258 | 
1 files changed, 258 insertions, 0 deletions
diff --git a/drivers/net/wireless/iwlwifi/mvm/d3.c b/drivers/net/wireless/iwlwifi/mvm/d3.c index 376a5776db9..d4578cefe44 100644 --- a/drivers/net/wireless/iwlwifi/mvm/d3.c +++ b/drivers/net/wireless/iwlwifi/mvm/d3.c @@ -62,8 +62,10 @@   *****************************************************************************/  #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);  |