]>
Commit | Line | Data |
---|---|---|
2c1d029a JA |
1 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
2 | /* Microsemi Ocelot Switch TC driver | |
3 | * | |
4 | * Copyright (c) 2019 Microsemi Corporation | |
5 | */ | |
6 | ||
7 | #include "ocelot_tc.h" | |
8 | #include "ocelot_police.h" | |
fe3490e6 | 9 | #include "ocelot_ace.h" |
2c1d029a JA |
10 | #include <net/pkt_cls.h> |
11 | ||
004d44f6 | 12 | static int ocelot_setup_tc_cls_matchall(struct ocelot_port_private *priv, |
2c1d029a JA |
13 | struct tc_cls_matchall_offload *f, |
14 | bool ingress) | |
15 | { | |
16 | struct netlink_ext_ack *extack = f->common.extack; | |
004d44f6 | 17 | struct ocelot *ocelot = priv->port.ocelot; |
2c1d029a JA |
18 | struct ocelot_policer pol = { 0 }; |
19 | struct flow_action_entry *action; | |
004d44f6 | 20 | int port = priv->chip_port; |
2c1d029a JA |
21 | int err; |
22 | ||
004d44f6 VO |
23 | netdev_dbg(priv->dev, "%s: port %u command %d cookie %lu\n", |
24 | __func__, port, f->command, f->cookie); | |
2c1d029a JA |
25 | |
26 | if (!ingress) { | |
27 | NL_SET_ERR_MSG_MOD(extack, "Only ingress is supported"); | |
28 | return -EOPNOTSUPP; | |
29 | } | |
30 | ||
31 | switch (f->command) { | |
32 | case TC_CLSMATCHALL_REPLACE: | |
33 | if (!flow_offload_has_one_action(&f->rule->action)) { | |
34 | NL_SET_ERR_MSG_MOD(extack, | |
35 | "Only one action is supported"); | |
36 | return -EOPNOTSUPP; | |
37 | } | |
38 | ||
004d44f6 | 39 | if (priv->tc.block_shared) { |
2c1d029a JA |
40 | NL_SET_ERR_MSG_MOD(extack, |
41 | "Rate limit is not supported on shared blocks"); | |
42 | return -EOPNOTSUPP; | |
43 | } | |
44 | ||
45 | action = &f->rule->action.entries[0]; | |
46 | ||
47 | if (action->id != FLOW_ACTION_POLICE) { | |
48 | NL_SET_ERR_MSG_MOD(extack, "Unsupported action"); | |
49 | return -EOPNOTSUPP; | |
50 | } | |
51 | ||
004d44f6 | 52 | if (priv->tc.police_id && priv->tc.police_id != f->cookie) { |
2c1d029a JA |
53 | NL_SET_ERR_MSG_MOD(extack, |
54 | "Only one policer per port is supported\n"); | |
55 | return -EEXIST; | |
56 | } | |
57 | ||
58 | pol.rate = (u32)div_u64(action->police.rate_bytes_ps, 1000) * 8; | |
59 | pol.burst = (u32)div_u64(action->police.rate_bytes_ps * | |
60 | PSCHED_NS2TICKS(action->police.burst), | |
61 | PSCHED_TICKS_PER_SEC); | |
62 | ||
004d44f6 | 63 | err = ocelot_port_policer_add(ocelot, port, &pol); |
2c1d029a JA |
64 | if (err) { |
65 | NL_SET_ERR_MSG_MOD(extack, "Could not add policer\n"); | |
66 | return err; | |
67 | } | |
68 | ||
004d44f6 VO |
69 | priv->tc.police_id = f->cookie; |
70 | priv->tc.offload_cnt++; | |
2c1d029a JA |
71 | return 0; |
72 | case TC_CLSMATCHALL_DESTROY: | |
004d44f6 | 73 | if (priv->tc.police_id != f->cookie) |
2c1d029a JA |
74 | return -ENOENT; |
75 | ||
004d44f6 | 76 | err = ocelot_port_policer_del(ocelot, port); |
2c1d029a JA |
77 | if (err) { |
78 | NL_SET_ERR_MSG_MOD(extack, | |
79 | "Could not delete policer\n"); | |
80 | return err; | |
81 | } | |
004d44f6 VO |
82 | priv->tc.police_id = 0; |
83 | priv->tc.offload_cnt--; | |
2c1d029a JA |
84 | return 0; |
85 | case TC_CLSMATCHALL_STATS: /* fall through */ | |
86 | default: | |
87 | return -EOPNOTSUPP; | |
88 | } | |
89 | } | |
90 | ||
91 | static int ocelot_setup_tc_block_cb(enum tc_setup_type type, | |
92 | void *type_data, | |
93 | void *cb_priv, bool ingress) | |
94 | { | |
004d44f6 | 95 | struct ocelot_port_private *priv = cb_priv; |
2c1d029a | 96 | |
004d44f6 | 97 | if (!tc_cls_can_offload_and_chain0(priv->dev, type_data)) |
2c1d029a JA |
98 | return -EOPNOTSUPP; |
99 | ||
100 | switch (type) { | |
101 | case TC_SETUP_CLSMATCHALL: | |
004d44f6 | 102 | netdev_dbg(priv->dev, "tc_block_cb: TC_SETUP_CLSMATCHALL %s\n", |
2c1d029a JA |
103 | ingress ? "ingress" : "egress"); |
104 | ||
004d44f6 | 105 | return ocelot_setup_tc_cls_matchall(priv, type_data, ingress); |
2c1d029a | 106 | case TC_SETUP_CLSFLOWER: |
fe3490e6 | 107 | return 0; |
2c1d029a | 108 | default: |
004d44f6 | 109 | netdev_dbg(priv->dev, "tc_block_cb: type %d %s\n", |
2c1d029a JA |
110 | type, |
111 | ingress ? "ingress" : "egress"); | |
112 | ||
113 | return -EOPNOTSUPP; | |
114 | } | |
115 | } | |
116 | ||
117 | static int ocelot_setup_tc_block_cb_ig(enum tc_setup_type type, | |
118 | void *type_data, | |
119 | void *cb_priv) | |
120 | { | |
121 | return ocelot_setup_tc_block_cb(type, type_data, | |
122 | cb_priv, true); | |
123 | } | |
124 | ||
125 | static int ocelot_setup_tc_block_cb_eg(enum tc_setup_type type, | |
126 | void *type_data, | |
127 | void *cb_priv) | |
128 | { | |
129 | return ocelot_setup_tc_block_cb(type, type_data, | |
130 | cb_priv, false); | |
131 | } | |
132 | ||
955bcb6e PNA |
133 | static LIST_HEAD(ocelot_block_cb_list); |
134 | ||
004d44f6 | 135 | static int ocelot_setup_tc_block(struct ocelot_port_private *priv, |
955bcb6e | 136 | struct flow_block_offload *f) |
2c1d029a | 137 | { |
955bcb6e | 138 | struct flow_block_cb *block_cb; |
a7323311 | 139 | flow_setup_cb_t *cb; |
955bcb6e | 140 | int err; |
2c1d029a | 141 | |
004d44f6 | 142 | netdev_dbg(priv->dev, "tc_block command %d, binder_type %d\n", |
2c1d029a JA |
143 | f->command, f->binder_type); |
144 | ||
32f8c409 | 145 | if (f->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS) { |
2c1d029a | 146 | cb = ocelot_setup_tc_block_cb_ig; |
004d44f6 | 147 | priv->tc.block_shared = f->block_shared; |
32f8c409 | 148 | } else if (f->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS) { |
2c1d029a JA |
149 | cb = ocelot_setup_tc_block_cb_eg; |
150 | } else { | |
151 | return -EOPNOTSUPP; | |
152 | } | |
153 | ||
955bcb6e PNA |
154 | f->driver_block_list = &ocelot_block_cb_list; |
155 | ||
2c1d029a | 156 | switch (f->command) { |
9c0e189e | 157 | case FLOW_BLOCK_BIND: |
004d44f6 | 158 | if (flow_block_cb_is_busy(cb, priv, &ocelot_block_cb_list)) |
0d4fd02e PNA |
159 | return -EBUSY; |
160 | ||
004d44f6 | 161 | block_cb = flow_block_cb_alloc(cb, priv, priv, NULL); |
955bcb6e PNA |
162 | if (IS_ERR(block_cb)) |
163 | return PTR_ERR(block_cb); | |
fe3490e6 | 164 | |
004d44f6 | 165 | err = ocelot_setup_tc_block_flower_bind(priv, f); |
955bcb6e PNA |
166 | if (err < 0) { |
167 | flow_block_cb_free(block_cb); | |
168 | return err; | |
169 | } | |
170 | flow_block_cb_add(block_cb, f); | |
171 | list_add_tail(&block_cb->driver_list, f->driver_block_list); | |
172 | return 0; | |
9c0e189e | 173 | case FLOW_BLOCK_UNBIND: |
004d44f6 | 174 | block_cb = flow_block_cb_lookup(f->block, cb, priv); |
955bcb6e PNA |
175 | if (!block_cb) |
176 | return -ENOENT; | |
177 | ||
004d44f6 | 178 | ocelot_setup_tc_block_flower_unbind(priv, f); |
955bcb6e PNA |
179 | flow_block_cb_remove(block_cb, f); |
180 | list_del(&block_cb->driver_list); | |
2c1d029a JA |
181 | return 0; |
182 | default: | |
183 | return -EOPNOTSUPP; | |
184 | } | |
185 | } | |
186 | ||
187 | int ocelot_setup_tc(struct net_device *dev, enum tc_setup_type type, | |
188 | void *type_data) | |
189 | { | |
004d44f6 | 190 | struct ocelot_port_private *priv = netdev_priv(dev); |
2c1d029a JA |
191 | |
192 | switch (type) { | |
193 | case TC_SETUP_BLOCK: | |
004d44f6 | 194 | return ocelot_setup_tc_block(priv, type_data); |
2c1d029a JA |
195 | default: |
196 | return -EOPNOTSUPP; | |
197 | } | |
198 | return 0; | |
199 | } |