summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/microchip/lan966x/lan966x_police.c
blob: 7d66fe75cd3bfcd1eb980d9929a68b55cfdbbfe4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// SPDX-License-Identifier: GPL-2.0+

#include "lan966x_main.h"

/* 0-8 : 9 port policers */
#define POL_IDX_PORT	0

/* Policer order: Serial (QoS -> Port -> VCAP) */
#define POL_ORDER	0x1d3

struct lan966x_tc_policer {
	/* kilobit per second */
	u32 rate;
	/* bytes */
	u32 burst;
};

static int lan966x_police_add(struct lan966x_port *port,
			      struct lan966x_tc_policer *pol,
			      u16 pol_idx)
{
	struct lan966x *lan966x = port->lan966x;

	/* Rate unit is 33 1/3 kpps */
	pol->rate = DIV_ROUND_UP(pol->rate * 3, 100);
	/* Avoid zero burst size */
	pol->burst = pol->burst ?: 1;
	/* Unit is 4kB */
	pol->burst = DIV_ROUND_UP(pol->burst, 4096);

	if (pol->rate > GENMASK(15, 0) ||
	    pol->burst > GENMASK(6, 0))
		return -EINVAL;

	lan_wr(ANA_POL_MODE_DROP_ON_YELLOW_ENA_SET(0) |
	       ANA_POL_MODE_MARK_ALL_FRMS_RED_ENA_SET(0) |
	       ANA_POL_MODE_IPG_SIZE_SET(20) |
	       ANA_POL_MODE_FRM_MODE_SET(1) |
	       ANA_POL_MODE_OVERSHOOT_ENA_SET(1),
	       lan966x, ANA_POL_MODE(pol_idx));

	lan_wr(ANA_POL_PIR_STATE_PIR_LVL_SET(0),
	       lan966x, ANA_POL_PIR_STATE(pol_idx));

	lan_wr(ANA_POL_PIR_CFG_PIR_RATE_SET(pol->rate) |
	       ANA_POL_PIR_CFG_PIR_BURST_SET(pol->burst),
	       lan966x, ANA_POL_PIR_CFG(pol_idx));

	return 0;
}

static int lan966x_police_del(struct lan966x_port *port,
			      u16 pol_idx)
{
	struct lan966x *lan966x = port->lan966x;

	lan_wr(ANA_POL_MODE_DROP_ON_YELLOW_ENA_SET(0) |
	       ANA_POL_MODE_MARK_ALL_FRMS_RED_ENA_SET(0) |
	       ANA_POL_MODE_IPG_SIZE_SET(20) |
	       ANA_POL_MODE_FRM_MODE_SET(2) |
	       ANA_POL_MODE_OVERSHOOT_ENA_SET(1),
	       lan966x, ANA_POL_MODE(pol_idx));

	lan_wr(ANA_POL_PIR_STATE_PIR_LVL_SET(0),
	       lan966x, ANA_POL_PIR_STATE(pol_idx));

	lan_wr(ANA_POL_PIR_CFG_PIR_RATE_SET(GENMASK(14, 0)) |
	       ANA_POL_PIR_CFG_PIR_BURST_SET(0),
	       lan966x, ANA_POL_PIR_CFG(pol_idx));

	return 0;
}

static int lan966x_police_validate(struct lan966x_port *port,
				   const struct flow_action *action,
				   const struct flow_action_entry *act,
				   unsigned long police_id,
				   bool ingress,
				   struct netlink_ext_ack *extack)
{
	if (act->police.exceed.act_id != FLOW_ACTION_DROP) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Offload not supported when exceed action is not drop");
		return -EOPNOTSUPP;
	}

	if (act->police.notexceed.act_id != FLOW_ACTION_PIPE &&
	    act->police.notexceed.act_id != FLOW_ACTION_ACCEPT) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Offload not supported when conform action is not pipe or ok");
		return -EOPNOTSUPP;
	}

	if (act->police.notexceed.act_id == FLOW_ACTION_ACCEPT &&
	    !flow_action_is_last_entry(action, act)) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Offload not supported when conform action is ok, but action is not last");
		return -EOPNOTSUPP;
	}

	if (act->police.peakrate_bytes_ps ||
	    act->police.avrate || act->police.overhead) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Offload not supported when peakrate/avrate/overhead is configured");
		return -EOPNOTSUPP;
	}

	if (act->police.rate_pkt_ps) {
		NL_SET_ERR_MSG_MOD(extack,
				   "QoS offload not support packets per second");
		return -EOPNOTSUPP;
	}

	if (!ingress) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Policer is not supported on egress");
		return -EOPNOTSUPP;
	}

	if (port->tc.ingress_shared_block) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Policer is not supported on shared ingress blocks");
		return -EOPNOTSUPP;
	}

	if (port->tc.police_id && port->tc.police_id != police_id) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Only one policer per port is supported");
		return -EEXIST;
	}

	return 0;
}

int lan966x_police_port_add(struct lan966x_port *port,
			    struct flow_action *action,
			    struct flow_action_entry *act,
			    unsigned long police_id,
			    bool ingress,
			    struct netlink_ext_ack *extack)
{
	struct lan966x *lan966x = port->lan966x;
	struct rtnl_link_stats64 new_stats;
	struct lan966x_tc_policer pol;
	struct flow_stats *old_stats;
	int err;

	err = lan966x_police_validate(port, action, act, police_id, ingress,
				      extack);
	if (err)
		return err;

	memset(&pol, 0, sizeof(pol));

	pol.rate = div_u64(act->police.rate_bytes_ps, 1000) * 8;
	pol.burst = act->police.burst;

	err = lan966x_police_add(port, &pol, POL_IDX_PORT + port->chip_port);
	if (err) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Failed to add policer to port");
		return err;
	}

	lan_rmw(ANA_POL_CFG_PORT_POL_ENA_SET(1) |
		ANA_POL_CFG_POL_ORDER_SET(POL_ORDER),
		ANA_POL_CFG_PORT_POL_ENA |
		ANA_POL_CFG_POL_ORDER,
		lan966x, ANA_POL_CFG(port->chip_port));

	port->tc.police_id = police_id;

	/* Setup initial stats */
	old_stats = &port->tc.police_stat;
	lan966x_stats_get(port->dev, &new_stats);
	old_stats->bytes = new_stats.rx_bytes;
	old_stats->pkts = new_stats.rx_packets;
	old_stats->drops = new_stats.rx_dropped;
	old_stats->lastused = jiffies;

	return 0;
}

int lan966x_police_port_del(struct lan966x_port *port,
			    unsigned long police_id,
			    struct netlink_ext_ack *extack)
{
	struct lan966x *lan966x = port->lan966x;
	int err;

	if (port->tc.police_id != police_id) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Invalid policer id");
		return -EINVAL;
	}

	err = lan966x_police_del(port, POL_IDX_PORT + port->chip_port);
	if (err) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Failed to add policer to port");
		return err;
	}

	lan_rmw(ANA_POL_CFG_PORT_POL_ENA_SET(0) |
		ANA_POL_CFG_POL_ORDER_SET(POL_ORDER),
		ANA_POL_CFG_PORT_POL_ENA |
		ANA_POL_CFG_POL_ORDER,
		lan966x, ANA_POL_CFG(port->chip_port));

	port->tc.police_id = 0;

	return 0;
}

void lan966x_police_port_stats(struct lan966x_port *port,
			       struct flow_stats *stats)
{
	struct rtnl_link_stats64 new_stats;
	struct flow_stats *old_stats;

	old_stats = &port->tc.police_stat;
	lan966x_stats_get(port->dev, &new_stats);

	flow_stats_update(stats,
			  new_stats.rx_bytes - old_stats->bytes,
			  new_stats.rx_packets - old_stats->pkts,
			  new_stats.rx_dropped - old_stats->drops,
			  old_stats->lastused,
			  FLOW_ACTION_HW_STATS_IMMEDIATE);

	old_stats->bytes = new_stats.rx_bytes;
	old_stats->pkts = new_stats.rx_packets;
	old_stats->drops = new_stats.rx_dropped;
	old_stats->lastused = jiffies;
}