1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2023 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> |
3 | |
4 | #include "ksz9477.h" |
5 | #include "ksz9477_reg.h" |
6 | #include "ksz_common.h" |
7 | |
8 | #define ETHER_TYPE_FULL_MASK cpu_to_be16(~0) |
9 | #define KSZ9477_MAX_TC 7 |
10 | |
11 | /** |
12 | * ksz9477_flower_parse_key_l2 - Parse Layer 2 key from flow rule and configure |
13 | * ACL entries accordingly. |
14 | * @dev: Pointer to the ksz_device. |
15 | * @port: Port number. |
16 | * @extack: Pointer to the netlink_ext_ack. |
17 | * @rule: Pointer to the flow_rule. |
18 | * @cookie: The cookie to associate with the entry. |
19 | * @prio: The priority of the entry. |
20 | * |
21 | * This function parses the Layer 2 key from the flow rule and configures |
22 | * the corresponding ACL entries. It checks for unsupported offloads and |
23 | * available entries before proceeding with the configuration. |
24 | * |
25 | * Returns: 0 on success or a negative error code on failure. |
26 | */ |
27 | static int ksz9477_flower_parse_key_l2(struct ksz_device *dev, int port, |
28 | struct netlink_ext_ack *extack, |
29 | struct flow_rule *rule, |
30 | unsigned long cookie, u32 prio) |
31 | { |
32 | struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv; |
33 | struct flow_match_eth_addrs ematch; |
34 | struct ksz9477_acl_entries *acles; |
35 | int required_entries; |
36 | u8 *src_mac = NULL; |
37 | u8 *dst_mac = NULL; |
38 | u16 ethtype = 0; |
39 | |
40 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_BASIC)) { |
41 | struct flow_match_basic match; |
42 | |
43 | flow_rule_match_basic(rule, out: &match); |
44 | |
45 | if (match.key->n_proto) { |
46 | if (match.mask->n_proto != ETHER_TYPE_FULL_MASK) { |
47 | NL_SET_ERR_MSG_MOD(extack, |
48 | "ethernet type mask must be a full mask" ); |
49 | return -EINVAL; |
50 | } |
51 | |
52 | ethtype = be16_to_cpu(match.key->n_proto); |
53 | } |
54 | } |
55 | |
56 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_ETH_ADDRS)) { |
57 | flow_rule_match_eth_addrs(rule, out: &ematch); |
58 | |
59 | if (!is_zero_ether_addr(addr: ematch.key->src)) { |
60 | if (!is_broadcast_ether_addr(addr: ematch.mask->src)) |
61 | goto not_full_mask_err; |
62 | |
63 | src_mac = ematch.key->src; |
64 | } |
65 | |
66 | if (!is_zero_ether_addr(addr: ematch.key->dst)) { |
67 | if (!is_broadcast_ether_addr(addr: ematch.mask->dst)) |
68 | goto not_full_mask_err; |
69 | |
70 | dst_mac = ematch.key->dst; |
71 | } |
72 | } |
73 | |
74 | acles = &acl->acles; |
75 | /* ACL supports only one MAC per entry */ |
76 | required_entries = src_mac && dst_mac ? 2 : 1; |
77 | |
78 | /* Check if there are enough available entries */ |
79 | if (acles->entries_count + required_entries > KSZ9477_ACL_MAX_ENTRIES) { |
80 | NL_SET_ERR_MSG_MOD(extack, "ACL entry limit reached" ); |
81 | return -EOPNOTSUPP; |
82 | } |
83 | |
84 | ksz9477_acl_match_process_l2(dev, port, ethtype, src_mac, dst_mac, |
85 | cookie, prio); |
86 | |
87 | return 0; |
88 | |
89 | not_full_mask_err: |
90 | NL_SET_ERR_MSG_MOD(extack, "MAC address mask must be a full mask" ); |
91 | return -EOPNOTSUPP; |
92 | } |
93 | |
94 | /** |
95 | * ksz9477_flower_parse_key - Parse flow rule keys for a specified port on a |
96 | * ksz_device. |
97 | * @dev: The ksz_device instance. |
98 | * @port: The port number to parse the flow rule keys for. |
99 | * @extack: The netlink extended ACK for reporting errors. |
100 | * @rule: The flow_rule to parse. |
101 | * @cookie: The cookie to associate with the entry. |
102 | * @prio: The priority of the entry. |
103 | * |
104 | * This function checks if the used keys in the flow rule are supported by |
105 | * the device and parses the L2 keys if they match. If unsupported keys are |
106 | * used, an error message is set in the extended ACK. |
107 | * |
108 | * Returns: 0 on success or a negative error code on failure. |
109 | */ |
110 | static int ksz9477_flower_parse_key(struct ksz_device *dev, int port, |
111 | struct netlink_ext_ack *extack, |
112 | struct flow_rule *rule, |
113 | unsigned long cookie, u32 prio) |
114 | { |
115 | struct flow_dissector *dissector = rule->match.dissector; |
116 | int ret; |
117 | |
118 | if (dissector->used_keys & |
119 | ~(BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | |
120 | BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) | |
121 | BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL))) { |
122 | NL_SET_ERR_MSG_MOD(extack, |
123 | "Unsupported keys used" ); |
124 | return -EOPNOTSUPP; |
125 | } |
126 | |
127 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_BASIC) || |
128 | flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_ETH_ADDRS)) { |
129 | ret = ksz9477_flower_parse_key_l2(dev, port, extack, rule, |
130 | cookie, prio); |
131 | if (ret) |
132 | return ret; |
133 | } |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | /** |
139 | * ksz9477_flower_parse_action - Parse flow rule actions for a specified port |
140 | * on a ksz_device. |
141 | * @dev: The ksz_device instance. |
142 | * @port: The port number to parse the flow rule actions for. |
143 | * @extack: The netlink extended ACK for reporting errors. |
144 | * @cls: The flow_cls_offload instance containing the flow rule. |
145 | * @entry_idx: The index of the ACL entry to store the action. |
146 | * |
147 | * This function checks if the actions in the flow rule are supported by |
148 | * the device. Currently, only actions that change priorities are supported. |
149 | * If unsupported actions are encountered, an error message is set in the |
150 | * extended ACK. |
151 | * |
152 | * Returns: 0 on success or a negative error code on failure. |
153 | */ |
154 | static int ksz9477_flower_parse_action(struct ksz_device *dev, int port, |
155 | struct netlink_ext_ack *extack, |
156 | struct flow_cls_offload *cls, |
157 | int entry_idx) |
158 | { |
159 | struct flow_rule *rule = flow_cls_offload_flow_rule(flow_cmd: cls); |
160 | struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv; |
161 | const struct flow_action_entry *act; |
162 | struct ksz9477_acl_entry *entry; |
163 | bool prio_force = false; |
164 | u8 prio_val = 0; |
165 | int i; |
166 | |
167 | if (TC_H_MIN(cls->classid)) { |
168 | NL_SET_ERR_MSG_MOD(extack, "hw_tc is not supported. Use: action skbedit prio" ); |
169 | return -EOPNOTSUPP; |
170 | } |
171 | |
172 | flow_action_for_each(i, act, &rule->action) { |
173 | switch (act->id) { |
174 | case FLOW_ACTION_PRIORITY: |
175 | if (act->priority > KSZ9477_MAX_TC) { |
176 | NL_SET_ERR_MSG_MOD(extack, "Priority value is too high" ); |
177 | return -EOPNOTSUPP; |
178 | } |
179 | prio_force = true; |
180 | prio_val = act->priority; |
181 | break; |
182 | default: |
183 | NL_SET_ERR_MSG_MOD(extack, "action not supported" ); |
184 | return -EOPNOTSUPP; |
185 | } |
186 | } |
187 | |
188 | /* pick entry to store action */ |
189 | entry = &acl->acles.entries[entry_idx]; |
190 | |
191 | ksz9477_acl_action_rule_cfg(entry: entry->entry, force_prio: prio_force, prio_val); |
192 | ksz9477_acl_processing_rule_set_action(entry: entry->entry, action_idx: entry_idx); |
193 | |
194 | return 0; |
195 | } |
196 | |
197 | /** |
198 | * ksz9477_cls_flower_add - Add a flow classification rule for a specified port |
199 | * on a ksz_device. |
200 | * @ds: The DSA switch instance. |
201 | * @port: The port number to add the flow classification rule to. |
202 | * @cls: The flow_cls_offload instance containing the flow rule. |
203 | * @ingress: A flag indicating if the rule is applied on the ingress path. |
204 | * |
205 | * This function adds a flow classification rule for a specified port on a |
206 | * ksz_device. It checks if the ACL offloading is supported and parses the flow |
207 | * keys and actions. If the ACL is not supported, it returns an error. If there |
208 | * are unprocessed entries, it parses the action for the rule. |
209 | * |
210 | * Returns: 0 on success or a negative error code on failure. |
211 | */ |
212 | int ksz9477_cls_flower_add(struct dsa_switch *ds, int port, |
213 | struct flow_cls_offload *cls, bool ingress) |
214 | { |
215 | struct flow_rule *rule = flow_cls_offload_flow_rule(flow_cmd: cls); |
216 | struct netlink_ext_ack *extack = cls->common.extack; |
217 | struct ksz_device *dev = ds->priv; |
218 | struct ksz9477_acl_priv *acl; |
219 | int action_entry_idx; |
220 | int ret; |
221 | |
222 | acl = dev->ports[port].acl_priv; |
223 | |
224 | if (!acl) { |
225 | NL_SET_ERR_MSG_MOD(extack, "ACL offloading is not supported" ); |
226 | return -EOPNOTSUPP; |
227 | } |
228 | |
229 | /* A complex rule set can take multiple entries. Use first entry |
230 | * to store the action. |
231 | */ |
232 | action_entry_idx = acl->acles.entries_count; |
233 | |
234 | ret = ksz9477_flower_parse_key(dev, port, extack, rule, cookie: cls->cookie, |
235 | prio: cls->common.prio); |
236 | if (ret) |
237 | return ret; |
238 | |
239 | ret = ksz9477_flower_parse_action(dev, port, extack, cls, |
240 | entry_idx: action_entry_idx); |
241 | if (ret) |
242 | return ret; |
243 | |
244 | ret = ksz9477_sort_acl_entries(dev, port); |
245 | if (ret) |
246 | return ret; |
247 | |
248 | return ksz9477_acl_write_list(dev, port); |
249 | } |
250 | |
251 | /** |
252 | * ksz9477_cls_flower_del - Remove a flow classification rule for a specified |
253 | * port on a ksz_device. |
254 | * @ds: The DSA switch instance. |
255 | * @port: The port number to remove the flow classification rule from. |
256 | * @cls: The flow_cls_offload instance containing the flow rule. |
257 | * @ingress: A flag indicating if the rule is applied on the ingress path. |
258 | * |
259 | * This function removes a flow classification rule for a specified port on a |
260 | * ksz_device. It checks if the ACL is initialized, and if not, returns an |
261 | * error. If the ACL is initialized, it removes entries with the specified |
262 | * cookie and rewrites the ACL list. |
263 | * |
264 | * Returns: 0 on success or a negative error code on failure. |
265 | */ |
266 | int ksz9477_cls_flower_del(struct dsa_switch *ds, int port, |
267 | struct flow_cls_offload *cls, bool ingress) |
268 | { |
269 | unsigned long cookie = cls->cookie; |
270 | struct ksz_device *dev = ds->priv; |
271 | struct ksz9477_acl_priv *acl; |
272 | |
273 | acl = dev->ports[port].acl_priv; |
274 | |
275 | if (!acl) |
276 | return -EOPNOTSUPP; |
277 | |
278 | ksz9477_acl_remove_entries(dev, port, acles: &acl->acles, cookie); |
279 | |
280 | return ksz9477_acl_write_list(dev, port); |
281 | } |
282 | |