xref: /freebsd/sys/contrib/dev/iwlwifi/mvm/offloading.c (revision a4128aad8503277614f2d214011ef60a19447b83)
1bfcc09ddSBjoern A. Zeeb // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2bfcc09ddSBjoern A. Zeeb /*
3*a4128aadSBjoern A. Zeeb  * Copyright (C) 2012-2014, 2021-2022, 2024 Intel Corporation
4bfcc09ddSBjoern A. Zeeb  * Copyright (C) 2013-2014 Intel Mobile Communications GmbH
5bfcc09ddSBjoern A. Zeeb  * Copyright (C) 2015 Intel Deutschland GmbH
6bfcc09ddSBjoern A. Zeeb  */
7bfcc09ddSBjoern A. Zeeb #include <net/ipv6.h>
8bfcc09ddSBjoern A. Zeeb #include <net/addrconf.h>
9bfcc09ddSBjoern A. Zeeb #include <linux/bitops.h>
10bfcc09ddSBjoern A. Zeeb #include "mvm.h"
11bfcc09ddSBjoern A. Zeeb 
12bfcc09ddSBjoern A. Zeeb void iwl_mvm_set_wowlan_qos_seq(struct iwl_mvm_sta *mvm_ap_sta,
13bfcc09ddSBjoern A. Zeeb 				struct iwl_wowlan_config_cmd *cmd)
14bfcc09ddSBjoern A. Zeeb {
15bfcc09ddSBjoern A. Zeeb 	int i;
16bfcc09ddSBjoern A. Zeeb 
17bfcc09ddSBjoern A. Zeeb 	/*
18bfcc09ddSBjoern A. Zeeb 	 * For QoS counters, we store the one to use next, so subtract 0x10
19bfcc09ddSBjoern A. Zeeb 	 * since the uCode will add 0x10 *before* using the value while we
20bfcc09ddSBjoern A. Zeeb 	 * increment after using the value (i.e. store the next value to use).
21bfcc09ddSBjoern A. Zeeb 	 */
22bfcc09ddSBjoern A. Zeeb 	for (i = 0; i < IWL_MAX_TID_COUNT; i++) {
23bfcc09ddSBjoern A. Zeeb 		u16 seq = mvm_ap_sta->tid_data[i].seq_number;
24bfcc09ddSBjoern A. Zeeb 		seq -= 0x10;
25bfcc09ddSBjoern A. Zeeb 		cmd->qos_seq[i] = cpu_to_le16(seq);
26bfcc09ddSBjoern A. Zeeb 	}
27bfcc09ddSBjoern A. Zeeb }
28bfcc09ddSBjoern A. Zeeb 
29bfcc09ddSBjoern A. Zeeb int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm,
30bfcc09ddSBjoern A. Zeeb 			       struct ieee80211_vif *vif,
31bfcc09ddSBjoern A. Zeeb 			       bool disable_offloading,
32bfcc09ddSBjoern A. Zeeb 			       bool offload_ns,
33*a4128aadSBjoern A. Zeeb 			       u32 cmd_flags,
34*a4128aadSBjoern A. Zeeb 			       u8 sta_id)
35bfcc09ddSBjoern A. Zeeb {
36bfcc09ddSBjoern A. Zeeb 	union {
37bfcc09ddSBjoern A. Zeeb 		struct iwl_proto_offload_cmd_v1 v1;
38bfcc09ddSBjoern A. Zeeb 		struct iwl_proto_offload_cmd_v2 v2;
39bfcc09ddSBjoern A. Zeeb 		struct iwl_proto_offload_cmd_v3_small v3s;
40bfcc09ddSBjoern A. Zeeb 		struct iwl_proto_offload_cmd_v4 v4;
41bfcc09ddSBjoern A. Zeeb 	} cmd = {};
42bfcc09ddSBjoern A. Zeeb 	struct iwl_host_cmd hcmd = {
43bfcc09ddSBjoern A. Zeeb 		.id = PROT_OFFLOAD_CONFIG_CMD,
44bfcc09ddSBjoern A. Zeeb 		.flags = cmd_flags,
45bfcc09ddSBjoern A. Zeeb 		.data[0] = &cmd,
46bfcc09ddSBjoern A. Zeeb 		.dataflags[0] = IWL_HCMD_DFL_DUP,
47bfcc09ddSBjoern A. Zeeb 	};
48bfcc09ddSBjoern A. Zeeb 	struct iwl_proto_offload_cmd_common *common;
49bfcc09ddSBjoern A. Zeeb 	u32 enabled = 0, size;
50bfcc09ddSBjoern A. Zeeb 	u32 capa_flags = mvm->fw->ucode_capa.flags;
51d9836fb4SBjoern A. Zeeb 	int ver = iwl_fw_lookup_cmd_ver(mvm->fw, hcmd.id, 0);
52bfcc09ddSBjoern A. Zeeb 
53bfcc09ddSBjoern A. Zeeb #if IS_ENABLED(CONFIG_IPV6)
54bfcc09ddSBjoern A. Zeeb 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
55bfcc09ddSBjoern A. Zeeb 	int i;
56bfcc09ddSBjoern A. Zeeb 	/*
57bfcc09ddSBjoern A. Zeeb 	 * Skip tentative address when ns offload is enabled to avoid
58bfcc09ddSBjoern A. Zeeb 	 * violating RFC4862.
59bfcc09ddSBjoern A. Zeeb 	 * Keep tentative address when ns offload is disabled so the NS packets
60bfcc09ddSBjoern A. Zeeb 	 * will not be filtered out and will wake up the host.
61bfcc09ddSBjoern A. Zeeb 	 */
62bfcc09ddSBjoern A. Zeeb 	bool skip_tentative = offload_ns;
63bfcc09ddSBjoern A. Zeeb 
64bfcc09ddSBjoern A. Zeeb 	if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL ||
65bfcc09ddSBjoern A. Zeeb 	    capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE) {
66bfcc09ddSBjoern A. Zeeb 		struct iwl_ns_config *nsc;
67bfcc09ddSBjoern A. Zeeb 		struct iwl_targ_addr *addrs;
68bfcc09ddSBjoern A. Zeeb 		int n_nsc, n_addrs;
69bfcc09ddSBjoern A. Zeeb 		int c;
70bfcc09ddSBjoern A. Zeeb 		int num_skipped = 0;
71bfcc09ddSBjoern A. Zeeb 
72bfcc09ddSBjoern A. Zeeb 		if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL) {
73bfcc09ddSBjoern A. Zeeb 			nsc = cmd.v3s.ns_config;
74bfcc09ddSBjoern A. Zeeb 			n_nsc = IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3S;
75bfcc09ddSBjoern A. Zeeb 			addrs = cmd.v3s.targ_addrs;
76bfcc09ddSBjoern A. Zeeb 			n_addrs = IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3S;
77bfcc09ddSBjoern A. Zeeb 		} else {
78bfcc09ddSBjoern A. Zeeb 			nsc = cmd.v4.ns_config;
79bfcc09ddSBjoern A. Zeeb 			n_nsc = IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3L;
80bfcc09ddSBjoern A. Zeeb 			addrs = cmd.v4.targ_addrs;
81bfcc09ddSBjoern A. Zeeb 			n_addrs = IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3L;
82bfcc09ddSBjoern A. Zeeb 		}
83bfcc09ddSBjoern A. Zeeb 
84bfcc09ddSBjoern A. Zeeb 		/*
85bfcc09ddSBjoern A. Zeeb 		 * For each address we have (and that will fit) fill a target
86bfcc09ddSBjoern A. Zeeb 		 * address struct and combine for NS offload structs with the
87bfcc09ddSBjoern A. Zeeb 		 * solicited node addresses.
88bfcc09ddSBjoern A. Zeeb 		 */
89bfcc09ddSBjoern A. Zeeb 		for (i = 0, c = 0;
90bfcc09ddSBjoern A. Zeeb 		     i < mvmvif->num_target_ipv6_addrs &&
91bfcc09ddSBjoern A. Zeeb 		     i < n_addrs && c < n_nsc; i++) {
92bfcc09ddSBjoern A. Zeeb 			struct in6_addr solicited_addr;
93bfcc09ddSBjoern A. Zeeb 			int j;
94bfcc09ddSBjoern A. Zeeb 
95bfcc09ddSBjoern A. Zeeb 			if (skip_tentative &&
96bfcc09ddSBjoern A. Zeeb 			    test_bit(i, mvmvif->tentative_addrs)) {
97bfcc09ddSBjoern A. Zeeb 				num_skipped++;
98bfcc09ddSBjoern A. Zeeb 				continue;
99bfcc09ddSBjoern A. Zeeb 			}
100bfcc09ddSBjoern A. Zeeb 
101bfcc09ddSBjoern A. Zeeb 			addrconf_addr_solict_mult(&mvmvif->target_ipv6_addrs[i],
102bfcc09ddSBjoern A. Zeeb 						  &solicited_addr);
103bfcc09ddSBjoern A. Zeeb 			for (j = 0; j < c; j++)
104bfcc09ddSBjoern A. Zeeb 				if (ipv6_addr_cmp(&nsc[j].dest_ipv6_addr,
105bfcc09ddSBjoern A. Zeeb 						  &solicited_addr) == 0)
106bfcc09ddSBjoern A. Zeeb 					break;
107bfcc09ddSBjoern A. Zeeb 			if (j == c)
108bfcc09ddSBjoern A. Zeeb 				c++;
109bfcc09ddSBjoern A. Zeeb 			addrs[i].addr = mvmvif->target_ipv6_addrs[i];
110bfcc09ddSBjoern A. Zeeb 			addrs[i].config_num = cpu_to_le32(j);
111bfcc09ddSBjoern A. Zeeb 			nsc[j].dest_ipv6_addr = solicited_addr;
112bfcc09ddSBjoern A. Zeeb 			memcpy(nsc[j].target_mac_addr, vif->addr, ETH_ALEN);
113bfcc09ddSBjoern A. Zeeb 		}
114bfcc09ddSBjoern A. Zeeb 
115bfcc09ddSBjoern A. Zeeb 		if (mvmvif->num_target_ipv6_addrs - num_skipped)
116bfcc09ddSBjoern A. Zeeb 			enabled |= IWL_D3_PROTO_IPV6_VALID;
117bfcc09ddSBjoern A. Zeeb 
118bfcc09ddSBjoern A. Zeeb 		if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL)
119bfcc09ddSBjoern A. Zeeb 			cmd.v3s.num_valid_ipv6_addrs =
120bfcc09ddSBjoern A. Zeeb 				cpu_to_le32(i - num_skipped);
121bfcc09ddSBjoern A. Zeeb 		else
122bfcc09ddSBjoern A. Zeeb 			cmd.v4.num_valid_ipv6_addrs =
123bfcc09ddSBjoern A. Zeeb 				cpu_to_le32(i - num_skipped);
124bfcc09ddSBjoern A. Zeeb 	} else if (capa_flags & IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS) {
125bfcc09ddSBjoern A. Zeeb 		bool found = false;
126bfcc09ddSBjoern A. Zeeb 
127bfcc09ddSBjoern A. Zeeb 		BUILD_BUG_ON(sizeof(cmd.v2.target_ipv6_addr[0]) !=
128bfcc09ddSBjoern A. Zeeb 			     sizeof(mvmvif->target_ipv6_addrs[0]));
129bfcc09ddSBjoern A. Zeeb 
130bfcc09ddSBjoern A. Zeeb 		for (i = 0; i < min(mvmvif->num_target_ipv6_addrs,
131bfcc09ddSBjoern A. Zeeb 				    IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V2); i++) {
132bfcc09ddSBjoern A. Zeeb 			if (skip_tentative &&
133bfcc09ddSBjoern A. Zeeb 			    test_bit(i, mvmvif->tentative_addrs))
134bfcc09ddSBjoern A. Zeeb 				continue;
135bfcc09ddSBjoern A. Zeeb 
136bfcc09ddSBjoern A. Zeeb 			memcpy(cmd.v2.target_ipv6_addr[i],
137bfcc09ddSBjoern A. Zeeb 			       &mvmvif->target_ipv6_addrs[i],
138bfcc09ddSBjoern A. Zeeb 			       sizeof(cmd.v2.target_ipv6_addr[i]));
139bfcc09ddSBjoern A. Zeeb 
140bfcc09ddSBjoern A. Zeeb 			found = true;
141bfcc09ddSBjoern A. Zeeb 		}
142bfcc09ddSBjoern A. Zeeb 		if (found) {
143bfcc09ddSBjoern A. Zeeb 			enabled |= IWL_D3_PROTO_IPV6_VALID;
144bfcc09ddSBjoern A. Zeeb 			memcpy(cmd.v2.ndp_mac_addr, vif->addr, ETH_ALEN);
145bfcc09ddSBjoern A. Zeeb 		}
146bfcc09ddSBjoern A. Zeeb 	} else {
147bfcc09ddSBjoern A. Zeeb 		bool found = false;
148bfcc09ddSBjoern A. Zeeb 		BUILD_BUG_ON(sizeof(cmd.v1.target_ipv6_addr[0]) !=
149bfcc09ddSBjoern A. Zeeb 			     sizeof(mvmvif->target_ipv6_addrs[0]));
150bfcc09ddSBjoern A. Zeeb 
151bfcc09ddSBjoern A. Zeeb 		for (i = 0; i < min(mvmvif->num_target_ipv6_addrs,
152bfcc09ddSBjoern A. Zeeb 				    IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V1); i++) {
153bfcc09ddSBjoern A. Zeeb 			if (skip_tentative &&
154bfcc09ddSBjoern A. Zeeb 			    test_bit(i, mvmvif->tentative_addrs))
155bfcc09ddSBjoern A. Zeeb 				continue;
156bfcc09ddSBjoern A. Zeeb 
157bfcc09ddSBjoern A. Zeeb 			memcpy(cmd.v1.target_ipv6_addr[i],
158bfcc09ddSBjoern A. Zeeb 			       &mvmvif->target_ipv6_addrs[i],
159bfcc09ddSBjoern A. Zeeb 			       sizeof(cmd.v1.target_ipv6_addr[i]));
160bfcc09ddSBjoern A. Zeeb 
161bfcc09ddSBjoern A. Zeeb 			found = true;
162bfcc09ddSBjoern A. Zeeb 		}
163bfcc09ddSBjoern A. Zeeb 
164bfcc09ddSBjoern A. Zeeb 		if (found) {
165bfcc09ddSBjoern A. Zeeb 			enabled |= IWL_D3_PROTO_IPV6_VALID;
166bfcc09ddSBjoern A. Zeeb 			memcpy(cmd.v1.ndp_mac_addr, vif->addr, ETH_ALEN);
167bfcc09ddSBjoern A. Zeeb 		}
168bfcc09ddSBjoern A. Zeeb 	}
169bfcc09ddSBjoern A. Zeeb 
170bfcc09ddSBjoern A. Zeeb 	if (offload_ns && (enabled & IWL_D3_PROTO_IPV6_VALID))
171bfcc09ddSBjoern A. Zeeb 		enabled |= IWL_D3_PROTO_OFFLOAD_NS;
172bfcc09ddSBjoern A. Zeeb #endif
173bfcc09ddSBjoern A. Zeeb 	if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL) {
174bfcc09ddSBjoern A. Zeeb 		common = &cmd.v3s.common;
175bfcc09ddSBjoern A. Zeeb 		size = sizeof(cmd.v3s);
176bfcc09ddSBjoern A. Zeeb 	} else if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE) {
177bfcc09ddSBjoern A. Zeeb 		common = &cmd.v4.common;
178bfcc09ddSBjoern A. Zeeb 		size = sizeof(cmd.v4);
179bfcc09ddSBjoern A. Zeeb 		if (ver < 4) {
180bfcc09ddSBjoern A. Zeeb 			/*
181bfcc09ddSBjoern A. Zeeb 			 * This basically uses iwl_proto_offload_cmd_v3_large
182bfcc09ddSBjoern A. Zeeb 			 * which doesn't have the sta_id parameter before the
183bfcc09ddSBjoern A. Zeeb 			 * common part.
184bfcc09ddSBjoern A. Zeeb 			 */
185bfcc09ddSBjoern A. Zeeb 			size -= sizeof(cmd.v4.sta_id);
186bfcc09ddSBjoern A. Zeeb 			hcmd.data[0] = common;
187bfcc09ddSBjoern A. Zeeb 		}
188bfcc09ddSBjoern A. Zeeb 	} else if (capa_flags & IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS) {
189bfcc09ddSBjoern A. Zeeb 		common = &cmd.v2.common;
190bfcc09ddSBjoern A. Zeeb 		size = sizeof(cmd.v2);
191bfcc09ddSBjoern A. Zeeb 	} else {
192bfcc09ddSBjoern A. Zeeb 		common = &cmd.v1.common;
193bfcc09ddSBjoern A. Zeeb 		size = sizeof(cmd.v1);
194bfcc09ddSBjoern A. Zeeb 	}
195bfcc09ddSBjoern A. Zeeb 
1969af1bba4SBjoern A. Zeeb 	if (vif->cfg.arp_addr_cnt) {
197bfcc09ddSBjoern A. Zeeb 		enabled |= IWL_D3_PROTO_OFFLOAD_ARP | IWL_D3_PROTO_IPV4_VALID;
1989af1bba4SBjoern A. Zeeb 		common->host_ipv4_addr = vif->cfg.arp_addr_list[0];
199bfcc09ddSBjoern A. Zeeb 		memcpy(common->arp_mac_addr, vif->addr, ETH_ALEN);
200bfcc09ddSBjoern A. Zeeb 	}
201bfcc09ddSBjoern A. Zeeb 
2029af1bba4SBjoern A. Zeeb 	if (fw_has_capa(&mvm->fw->ucode_capa,
2039af1bba4SBjoern A. Zeeb 			IWL_UCODE_TLV_CAPA_OFFLOAD_BTM_SUPPORT))
2049af1bba4SBjoern A. Zeeb 		enabled |= IWL_D3_PROTO_OFFLOAD_BTM;
2059af1bba4SBjoern A. Zeeb 
206bfcc09ddSBjoern A. Zeeb 	if (!disable_offloading)
207bfcc09ddSBjoern A. Zeeb 		common->enabled = cpu_to_le32(enabled);
208bfcc09ddSBjoern A. Zeeb 
209*a4128aadSBjoern A. Zeeb 	if (ver >= 4)
210*a4128aadSBjoern A. Zeeb 		cmd.v4.sta_id = cpu_to_le32(sta_id);
211*a4128aadSBjoern A. Zeeb 
212bfcc09ddSBjoern A. Zeeb 	hcmd.len[0] = size;
213bfcc09ddSBjoern A. Zeeb 	return iwl_mvm_send_cmd(mvm, &hcmd);
214bfcc09ddSBjoern A. Zeeb }
215