Skip to content

Commit f0c2646

Browse files
committed
iwlwifi: mvm: implement remote wake
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 <[email protected]> Signed-off-by: Johannes Berg <[email protected]>
1 parent ba5295f commit f0c2646

File tree

3 files changed

+334
-1
lines changed

3 files changed

+334
-1
lines changed

drivers/net/wireless/iwlwifi/mvm/d3.c

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@
6262
*****************************************************************************/
6363

6464
#include <linux/etherdevice.h>
65+
#include <linux/ip.h>
6566
#include <net/cfg80211.h>
6667
#include <net/ipv6.h>
68+
#include <net/tcp.h>
6769
#include "iwl-modparams.h"
6870
#include "fw-api.h"
6971
#include "mvm.h"
@@ -402,6 +404,233 @@ static int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm,
402404
sizeof(cmd), &cmd);
403405
}
404406

407+
enum iwl_mvm_tcp_packet_type {
408+
MVM_TCP_TX_SYN,
409+
MVM_TCP_RX_SYNACK,
410+
MVM_TCP_TX_DATA,
411+
MVM_TCP_RX_ACK,
412+
MVM_TCP_RX_WAKE,
413+
MVM_TCP_TX_FIN,
414+
};
415+
416+
static __le16 pseudo_hdr_check(int len, __be32 saddr, __be32 daddr)
417+
{
418+
__sum16 check = tcp_v4_check(len, saddr, daddr, 0);
419+
return cpu_to_le16(be16_to_cpu((__force __be16)check));
420+
}
421+
422+
static void iwl_mvm_build_tcp_packet(struct iwl_mvm *mvm,
423+
struct ieee80211_vif *vif,
424+
struct cfg80211_wowlan_tcp *tcp,
425+
void *_pkt, u8 *mask,
426+
__le16 *pseudo_hdr_csum,
427+
enum iwl_mvm_tcp_packet_type ptype)
428+
{
429+
struct {
430+
struct ethhdr eth;
431+
struct iphdr ip;
432+
struct tcphdr tcp;
433+
u8 data[];
434+
} __packed *pkt = _pkt;
435+
u16 ip_tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);
436+
int i;
437+
438+
pkt->eth.h_proto = cpu_to_be16(ETH_P_IP),
439+
pkt->ip.version = 4;
440+
pkt->ip.ihl = 5;
441+
pkt->ip.protocol = IPPROTO_TCP;
442+
443+
switch (ptype) {
444+
case MVM_TCP_TX_SYN:
445+
case MVM_TCP_TX_DATA:
446+
case MVM_TCP_TX_FIN:
447+
memcpy(pkt->eth.h_dest, tcp->dst_mac, ETH_ALEN);
448+
memcpy(pkt->eth.h_source, vif->addr, ETH_ALEN);
449+
pkt->ip.ttl = 128;
450+
pkt->ip.saddr = tcp->src;
451+
pkt->ip.daddr = tcp->dst;
452+
pkt->tcp.source = cpu_to_be16(tcp->src_port);
453+
pkt->tcp.dest = cpu_to_be16(tcp->dst_port);
454+
/* overwritten for TX SYN later */
455+
pkt->tcp.doff = sizeof(struct tcphdr) / 4;
456+
pkt->tcp.window = cpu_to_be16(65000);
457+
break;
458+
case MVM_TCP_RX_SYNACK:
459+
case MVM_TCP_RX_ACK:
460+
case MVM_TCP_RX_WAKE:
461+
memcpy(pkt->eth.h_dest, vif->addr, ETH_ALEN);
462+
memcpy(pkt->eth.h_source, tcp->dst_mac, ETH_ALEN);
463+
pkt->ip.saddr = tcp->dst;
464+
pkt->ip.daddr = tcp->src;
465+
pkt->tcp.source = cpu_to_be16(tcp->dst_port);
466+
pkt->tcp.dest = cpu_to_be16(tcp->src_port);
467+
break;
468+
default:
469+
WARN_ON(1);
470+
return;
471+
}
472+
473+
switch (ptype) {
474+
case MVM_TCP_TX_SYN:
475+
/* firmware assumes 8 option bytes - 8 NOPs for now */
476+
memset(pkt->data, 0x01, 8);
477+
ip_tot_len += 8;
478+
pkt->tcp.doff = (sizeof(struct tcphdr) + 8) / 4;
479+
pkt->tcp.syn = 1;
480+
break;
481+
case MVM_TCP_TX_DATA:
482+
ip_tot_len += tcp->payload_len;
483+
memcpy(pkt->data, tcp->payload, tcp->payload_len);
484+
pkt->tcp.psh = 1;
485+
pkt->tcp.ack = 1;
486+
break;
487+
case MVM_TCP_TX_FIN:
488+
pkt->tcp.fin = 1;
489+
pkt->tcp.ack = 1;
490+
break;
491+
case MVM_TCP_RX_SYNACK:
492+
pkt->tcp.syn = 1;
493+
pkt->tcp.ack = 1;
494+
break;
495+
case MVM_TCP_RX_ACK:
496+
pkt->tcp.ack = 1;
497+
break;
498+
case MVM_TCP_RX_WAKE:
499+
ip_tot_len += tcp->wake_len;
500+
pkt->tcp.psh = 1;
501+
pkt->tcp.ack = 1;
502+
memcpy(pkt->data, tcp->wake_data, tcp->wake_len);
503+
break;
504+
}
505+
506+
switch (ptype) {
507+
case MVM_TCP_TX_SYN:
508+
case MVM_TCP_TX_DATA:
509+
case MVM_TCP_TX_FIN:
510+
pkt->ip.tot_len = cpu_to_be16(ip_tot_len);
511+
pkt->ip.check = ip_fast_csum(&pkt->ip, pkt->ip.ihl);
512+
break;
513+
case MVM_TCP_RX_WAKE:
514+
for (i = 0; i < DIV_ROUND_UP(tcp->wake_len, 8); i++) {
515+
u8 tmp = tcp->wake_mask[i];
516+
mask[i + 6] |= tmp << 6;
517+
if (i + 1 < DIV_ROUND_UP(tcp->wake_len, 8))
518+
mask[i + 7] = tmp >> 2;
519+
}
520+
/* fall through for ethernet/IP/TCP headers mask */
521+
case MVM_TCP_RX_SYNACK:
522+
case MVM_TCP_RX_ACK:
523+
mask[0] = 0xff; /* match ethernet */
524+
/*
525+
* match ethernet, ip.version, ip.ihl
526+
* the ip.ihl half byte is really masked out by firmware
527+
*/
528+
mask[1] = 0x7f;
529+
mask[2] = 0x80; /* match ip.protocol */
530+
mask[3] = 0xfc; /* match ip.saddr, ip.daddr */
531+
mask[4] = 0x3f; /* match ip.daddr, tcp.source, tcp.dest */
532+
mask[5] = 0x80; /* match tcp flags */
533+
/* leave rest (0 or set for MVM_TCP_RX_WAKE) */
534+
break;
535+
};
536+
537+
*pseudo_hdr_csum = pseudo_hdr_check(ip_tot_len - sizeof(struct iphdr),
538+
pkt->ip.saddr, pkt->ip.daddr);
539+
}
540+
541+
static int iwl_mvm_send_remote_wake_cfg(struct iwl_mvm *mvm,
542+
struct ieee80211_vif *vif,
543+
struct cfg80211_wowlan_tcp *tcp)
544+
{
545+
struct iwl_wowlan_remote_wake_config *cfg;
546+
struct iwl_host_cmd cmd = {
547+
.id = REMOTE_WAKE_CONFIG_CMD,
548+
.len = { sizeof(*cfg), },
549+
.dataflags = { IWL_HCMD_DFL_NOCOPY, },
550+
.flags = CMD_SYNC,
551+
};
552+
int ret;
553+
554+
if (!tcp)
555+
return 0;
556+
557+
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
558+
if (!cfg)
559+
return -ENOMEM;
560+
cmd.data[0] = cfg;
561+
562+
cfg->max_syn_retries = 10;
563+
cfg->max_data_retries = 10;
564+
cfg->tcp_syn_ack_timeout = 1; /* seconds */
565+
cfg->tcp_ack_timeout = 1; /* seconds */
566+
567+
/* SYN (TX) */
568+
iwl_mvm_build_tcp_packet(
569+
mvm, vif, tcp, cfg->syn_tx.data, NULL,
570+
&cfg->syn_tx.info.tcp_pseudo_header_checksum,
571+
MVM_TCP_TX_SYN);
572+
cfg->syn_tx.info.tcp_payload_length = 0;
573+
574+
/* SYN/ACK (RX) */
575+
iwl_mvm_build_tcp_packet(
576+
mvm, vif, tcp, cfg->synack_rx.data, cfg->synack_rx.rx_mask,
577+
&cfg->synack_rx.info.tcp_pseudo_header_checksum,
578+
MVM_TCP_RX_SYNACK);
579+
cfg->synack_rx.info.tcp_payload_length = 0;
580+
581+
/* KEEPALIVE/ACK (TX) */
582+
iwl_mvm_build_tcp_packet(
583+
mvm, vif, tcp, cfg->keepalive_tx.data, NULL,
584+
&cfg->keepalive_tx.info.tcp_pseudo_header_checksum,
585+
MVM_TCP_TX_DATA);
586+
cfg->keepalive_tx.info.tcp_payload_length =
587+
cpu_to_le16(tcp->payload_len);
588+
cfg->sequence_number_offset = tcp->payload_seq.offset;
589+
/* length must be 0..4, the field is little endian */
590+
cfg->sequence_number_length = tcp->payload_seq.len;
591+
cfg->initial_sequence_number = cpu_to_le32(tcp->payload_seq.start);
592+
cfg->keepalive_interval = cpu_to_le16(tcp->data_interval);
593+
if (tcp->payload_tok.len) {
594+
cfg->token_offset = tcp->payload_tok.offset;
595+
cfg->token_length = tcp->payload_tok.len;
596+
cfg->num_tokens =
597+
cpu_to_le16(tcp->tokens_size % tcp->payload_tok.len);
598+
memcpy(cfg->tokens, tcp->payload_tok.token_stream,
599+
tcp->tokens_size);
600+
} else {
601+
/* set tokens to max value to almost never run out */
602+
cfg->num_tokens = cpu_to_le16(65535);
603+
}
604+
605+
/* ACK (RX) */
606+
iwl_mvm_build_tcp_packet(
607+
mvm, vif, tcp, cfg->keepalive_ack_rx.data,
608+
cfg->keepalive_ack_rx.rx_mask,
609+
&cfg->keepalive_ack_rx.info.tcp_pseudo_header_checksum,
610+
MVM_TCP_RX_ACK);
611+
cfg->keepalive_ack_rx.info.tcp_payload_length = 0;
612+
613+
/* WAKEUP (RX) */
614+
iwl_mvm_build_tcp_packet(
615+
mvm, vif, tcp, cfg->wake_rx.data, cfg->wake_rx.rx_mask,
616+
&cfg->wake_rx.info.tcp_pseudo_header_checksum,
617+
MVM_TCP_RX_WAKE);
618+
cfg->wake_rx.info.tcp_payload_length =
619+
cpu_to_le16(tcp->wake_len);
620+
621+
/* FIN */
622+
iwl_mvm_build_tcp_packet(
623+
mvm, vif, tcp, cfg->fin_tx.data, NULL,
624+
&cfg->fin_tx.info.tcp_pseudo_header_checksum,
625+
MVM_TCP_TX_FIN);
626+
cfg->fin_tx.info.tcp_payload_length = 0;
627+
628+
ret = iwl_mvm_send_cmd(mvm, &cmd);
629+
kfree(cfg);
630+
631+
return ret;
632+
}
633+
405634
struct iwl_d3_iter_data {
406635
struct iwl_mvm *mvm;
407636
struct ieee80211_vif *vif;
@@ -640,6 +869,22 @@ int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
640869
d3_cfg_cmd.wakeup_flags |=
641870
cpu_to_le32(IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT);
642871

872+
if (wowlan->tcp) {
873+
/*
874+
* The firmware currently doesn't really look at these, only
875+
* the IWL_WOWLAN_WAKEUP_LINK_CHANGE bit. We have to set that
876+
* reason bit since losing the connection to the AP implies
877+
* losing the TCP connection.
878+
* Set the flags anyway as long as they exist, in case this
879+
* will be changed in the firmware.
880+
*/
881+
wowlan_config_cmd.wakeup_filter |=
882+
cpu_to_le32(IWL_WOWLAN_WAKEUP_REMOTE_LINK_LOSS |
883+
IWL_WOWLAN_WAKEUP_REMOTE_SIGNATURE_TABLE |
884+
IWL_WOWLAN_WAKEUP_REMOTE_WAKEUP_PACKET |
885+
IWL_WOWLAN_WAKEUP_LINK_CHANGE);
886+
}
887+
643888
iwl_mvm_cancel_scan(mvm);
644889

645890
iwl_trans_stop_device(mvm->trans);
@@ -755,6 +1000,10 @@ int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
7551000
if (ret)
7561001
goto out;
7571002

1003+
ret = iwl_mvm_send_remote_wake_cfg(mvm, vif, wowlan->tcp);
1004+
if (ret)
1005+
goto out;
1006+
7581007
/* must be last -- this switches firmware state */
7591008
ret = iwl_mvm_send_cmd_pdu(mvm, D3_CONFIG_CMD, CMD_SYNC,
7601009
sizeof(d3_cfg_cmd), &d3_cfg_cmd);
@@ -874,6 +1123,15 @@ static void iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm,
8741123
if (reasons & IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE)
8751124
wakeup.four_way_handshake = true;
8761125

1126+
if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS)
1127+
wakeup.tcp_connlost = true;
1128+
1129+
if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE)
1130+
wakeup.tcp_nomoretokens = true;
1131+
1132+
if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET)
1133+
wakeup.tcp_match = true;
1134+
8771135
if (status->wake_packet_bufsize) {
8781136
int pktsize = le32_to_cpu(status->wake_packet_bufsize);
8791137
int pktlen = le32_to_cpu(status->wake_packet_length);

drivers/net/wireless/iwlwifi/mvm/fw-api-d3.h

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ enum iwl_wowlan_wakeup_reason {
258258
IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE = BIT(8),
259259
IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS = BIT(9),
260260
IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE = BIT(10),
261-
IWL_WOWLAN_WAKEUP_BY_REM_WAKE_TCP_EXTERNAL = BIT(11),
261+
/* BIT(11) reserved */
262262
IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET = BIT(12),
263263
}; /* WOWLAN_WAKE_UP_REASON_API_E_VER_2 */
264264

@@ -277,6 +277,55 @@ struct iwl_wowlan_status {
277277
u8 wake_packet[]; /* can be truncated from _length to _bufsize */
278278
} __packed; /* WOWLAN_STATUSES_API_S_VER_4 */
279279

280+
#define IWL_WOWLAN_TCP_MAX_PACKET_LEN 64
281+
#define IWL_WOWLAN_REMOTE_WAKE_MAX_PACKET_LEN 128
282+
#define IWL_WOWLAN_REMOTE_WAKE_MAX_TOKENS 2048
283+
284+
struct iwl_tcp_packet_info {
285+
__le16 tcp_pseudo_header_checksum;
286+
__le16 tcp_payload_length;
287+
} __packed; /* TCP_PACKET_INFO_API_S_VER_2 */
288+
289+
struct iwl_tcp_packet {
290+
struct iwl_tcp_packet_info info;
291+
u8 rx_mask[IWL_WOWLAN_MAX_PATTERN_LEN / 8];
292+
u8 data[IWL_WOWLAN_TCP_MAX_PACKET_LEN];
293+
} __packed; /* TCP_PROTOCOL_PACKET_API_S_VER_1 */
294+
295+
struct iwl_remote_wake_packet {
296+
struct iwl_tcp_packet_info info;
297+
u8 rx_mask[IWL_WOWLAN_MAX_PATTERN_LEN / 8];
298+
u8 data[IWL_WOWLAN_REMOTE_WAKE_MAX_PACKET_LEN];
299+
} __packed; /* TCP_PROTOCOL_PACKET_API_S_VER_1 */
300+
301+
struct iwl_wowlan_remote_wake_config {
302+
__le32 connection_max_time; /* unused */
303+
/* TCP_PROTOCOL_CONFIG_API_S_VER_1 */
304+
u8 max_syn_retries;
305+
u8 max_data_retries;
306+
u8 tcp_syn_ack_timeout;
307+
u8 tcp_ack_timeout;
308+
309+
struct iwl_tcp_packet syn_tx;
310+
struct iwl_tcp_packet synack_rx;
311+
struct iwl_tcp_packet keepalive_ack_rx;
312+
struct iwl_tcp_packet fin_tx;
313+
314+
struct iwl_remote_wake_packet keepalive_tx;
315+
struct iwl_remote_wake_packet wake_rx;
316+
317+
/* REMOTE_WAKE_OFFSET_INFO_API_S_VER_1 */
318+
u8 sequence_number_offset;
319+
u8 sequence_number_length;
320+
u8 token_offset;
321+
u8 token_length;
322+
/* REMOTE_WAKE_PROTOCOL_PARAMS_API_S_VER_1 */
323+
__le32 initial_sequence_number;
324+
__le16 keepalive_interval;
325+
__le16 num_tokens;
326+
u8 tokens[IWL_WOWLAN_REMOTE_WAKE_MAX_TOKENS];
327+
} __packed; /* REMOTE_WAKE_CONFIG_API_S_VER_2 */
328+
280329
/* TODO: NetDetect API */
281330

282331
#endif /* __fw_api_d3_h__ */

0 commit comments

Comments
 (0)