2 * SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0
4 * Copyright (c) 2016, Mellanox Technologies. All rights reserved.
5 * Copyright (c) 2017-2018, Broadcom Limited. All rights reserved.
7 * This software is available to you under a choice of one of two
8 * licenses. You may choose to be licensed under the terms of the GNU
9 * General Public License (GPL) Version 2, available from the file
10 * COPYING in the main directory of this source tree, or the
11 * OpenIB.org BSD license below:
13 * Redistribution and use in source and binary forms, with or
14 * without modification, are permitted provided that the following
17 * - Redistributions of source code must retain the above
18 * copyright notice, this list of conditions and the following
21 * - Redistributions in binary form must reproduce the above
22 * copyright notice, this list of conditions and the following
23 * disclaimer in the documentation and/or other materials
24 * provided with the distribution.
26 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
30 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
31 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
32 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38 /* This file implements Dynamic Interrupt Moderation, DIM */
43 #include <asm/types.h>
45 #include <linux/workqueue.h>
46 #include <linux/ktime.h>
48 struct net_dim_cq_moder {
54 struct net_dim_sample {
61 struct net_dim_stats {
62 int ppms; /* packets per msec */
63 int bpms; /* bytes per msec */
64 int epms; /* events per msec */
67 struct net_dim { /* Adaptive Moderation */
69 struct net_dim_stats prev_stats;
70 struct net_dim_sample start_sample;
71 struct work_struct work;
82 NET_DIM_CQ_PERIOD_MODE_START_FROM_EQE = 0x0,
83 NET_DIM_CQ_PERIOD_MODE_START_FROM_CQE = 0x1,
84 NET_DIM_CQ_PERIOD_NUM_MODES = 0x2,
85 NET_DIM_CQ_PERIOD_MODE_DISABLED = 0xFF,
88 /* Adaptive moderation logic */
90 NET_DIM_START_MEASURE,
91 NET_DIM_MEASURE_IN_PROGRESS,
92 NET_DIM_APPLY_NEW_PROFILE,
96 NET_DIM_PARKING_ON_TOP,
97 NET_DIM_PARKING_TIRED,
105 NET_DIM_STATS_BETTER,
114 #define NET_DIM_PARAMS_NUM_PROFILES 5
115 /* Adaptive moderation profiles */
116 #define NET_DIM_DEFAULT_RX_CQ_MODERATION_PKTS_FROM_EQE 256
117 #define NET_DIM_DEF_PROFILE_CQE 1
118 #define NET_DIM_DEF_PROFILE_EQE 1
120 /* All profiles sizes must be NET_PARAMS_DIM_NUM_PROFILES */
121 #define NET_DIM_EQE_PROFILES { \
122 {1, NET_DIM_DEFAULT_RX_CQ_MODERATION_PKTS_FROM_EQE}, \
123 {8, NET_DIM_DEFAULT_RX_CQ_MODERATION_PKTS_FROM_EQE}, \
124 {64, NET_DIM_DEFAULT_RX_CQ_MODERATION_PKTS_FROM_EQE}, \
125 {128, NET_DIM_DEFAULT_RX_CQ_MODERATION_PKTS_FROM_EQE}, \
126 {256, NET_DIM_DEFAULT_RX_CQ_MODERATION_PKTS_FROM_EQE}, \
129 #define NET_DIM_CQE_PROFILES { \
137 static const struct net_dim_cq_moder
138 net_dim_profile[NET_DIM_CQ_PERIOD_NUM_MODES][NET_DIM_PARAMS_NUM_PROFILES] = {
139 NET_DIM_EQE_PROFILES,
140 NET_DIM_CQE_PROFILES,
143 static inline struct net_dim_cq_moder
144 net_dim_get_profile(u8 cq_period_mode,
147 struct net_dim_cq_moder cq_moder;
149 cq_moder = net_dim_profile[cq_period_mode][ix];
150 cq_moder.cq_period_mode = cq_period_mode;
154 static inline struct net_dim_cq_moder
155 net_dim_get_def_profile(u8 rx_cq_period_mode)
157 int default_profile_ix;
159 if (rx_cq_period_mode == NET_DIM_CQ_PERIOD_MODE_START_FROM_CQE)
160 default_profile_ix = NET_DIM_DEF_PROFILE_CQE;
161 else /* NET_DIM_CQ_PERIOD_MODE_START_FROM_EQE */
162 default_profile_ix = NET_DIM_DEF_PROFILE_EQE;
164 return net_dim_get_profile(rx_cq_period_mode, default_profile_ix);
168 net_dim_on_top(struct net_dim *dim)
170 switch (dim->tune_state) {
171 case NET_DIM_PARKING_ON_TOP:
172 case NET_DIM_PARKING_TIRED:
174 case NET_DIM_GOING_RIGHT:
175 return (dim->steps_left > 1) && (dim->steps_right == 1);
176 default: /* NET_DIM_GOING_LEFT */
177 return (dim->steps_right > 1) && (dim->steps_left == 1);
182 net_dim_turn(struct net_dim *dim)
184 switch (dim->tune_state) {
185 case NET_DIM_PARKING_ON_TOP:
186 case NET_DIM_PARKING_TIRED:
188 case NET_DIM_GOING_RIGHT:
189 dim->tune_state = NET_DIM_GOING_LEFT;
192 case NET_DIM_GOING_LEFT:
193 dim->tune_state = NET_DIM_GOING_RIGHT;
194 dim->steps_right = 0;
200 net_dim_step(struct net_dim *dim)
202 if (dim->tired == (NET_DIM_PARAMS_NUM_PROFILES * 2))
203 return NET_DIM_TOO_TIRED;
205 switch (dim->tune_state) {
206 case NET_DIM_PARKING_ON_TOP:
207 case NET_DIM_PARKING_TIRED:
209 case NET_DIM_GOING_RIGHT:
210 if (dim->profile_ix == (NET_DIM_PARAMS_NUM_PROFILES - 1))
211 return NET_DIM_ON_EDGE;
215 case NET_DIM_GOING_LEFT:
216 if (dim->profile_ix == 0)
217 return NET_DIM_ON_EDGE;
224 return NET_DIM_STEPPED;
228 net_dim_park_on_top(struct net_dim *dim)
230 dim->steps_right = 0;
233 dim->tune_state = NET_DIM_PARKING_ON_TOP;
237 net_dim_park_tired(struct net_dim *dim)
239 dim->steps_right = 0;
241 dim->tune_state = NET_DIM_PARKING_TIRED;
245 net_dim_exit_parking(struct net_dim *dim)
247 dim->tune_state = dim->profile_ix ? NET_DIM_GOING_LEFT :
252 #define IS_SIGNIFICANT_DIFF(val, ref) \
253 (((100UL * abs((val) - (ref))) / (ref)) > 10) /* more than 10%
257 net_dim_stats_compare(struct net_dim_stats *curr,
258 struct net_dim_stats *prev)
261 return curr->bpms ? NET_DIM_STATS_BETTER :
264 if (IS_SIGNIFICANT_DIFF(curr->bpms, prev->bpms))
265 return (curr->bpms > prev->bpms) ? NET_DIM_STATS_BETTER :
269 return curr->ppms ? NET_DIM_STATS_BETTER :
272 if (IS_SIGNIFICANT_DIFF(curr->ppms, prev->ppms))
273 return (curr->ppms > prev->ppms) ? NET_DIM_STATS_BETTER :
277 return NET_DIM_STATS_SAME;
279 if (IS_SIGNIFICANT_DIFF(curr->epms, prev->epms))
280 return (curr->epms < prev->epms) ? NET_DIM_STATS_BETTER :
283 return NET_DIM_STATS_SAME;
287 net_dim_decision(struct net_dim_stats *curr_stats,
290 int prev_state = dim->tune_state;
291 int prev_ix = dim->profile_ix;
295 switch (dim->tune_state) {
296 case NET_DIM_PARKING_ON_TOP:
297 stats_res = net_dim_stats_compare(curr_stats, &dim->prev_stats);
298 if (stats_res != NET_DIM_STATS_SAME)
299 net_dim_exit_parking(dim);
302 case NET_DIM_PARKING_TIRED:
305 net_dim_exit_parking(dim);
308 case NET_DIM_GOING_RIGHT:
309 case NET_DIM_GOING_LEFT:
310 stats_res = net_dim_stats_compare(curr_stats, &dim->prev_stats);
311 if (stats_res != NET_DIM_STATS_BETTER)
314 if (net_dim_on_top(dim)) {
315 net_dim_park_on_top(dim);
318 step_res = net_dim_step(dim);
320 case NET_DIM_ON_EDGE:
321 net_dim_park_on_top(dim);
323 case NET_DIM_TOO_TIRED:
324 net_dim_park_tired(dim);
331 if ((prev_state != NET_DIM_PARKING_ON_TOP) ||
332 (dim->tune_state != NET_DIM_PARKING_ON_TOP))
333 dim->prev_stats = *curr_stats;
335 return dim->profile_ix != prev_ix;
339 net_dim_sample(u16 event_ctr,
342 struct net_dim_sample *s)
344 s->time = ktime_get();
345 s->pkt_ctr = packets;
347 s->event_ctr = event_ctr;
350 #define NET_DIM_NEVENTS 64
351 #define BIT_GAP(bits, end, start) ((((end) - (start)) + BIT_ULL(bits)) & (BIT_ULL(bits) - 1))
354 net_dim_calc_stats(struct net_dim_sample *start,
355 struct net_dim_sample *end,
356 struct net_dim_stats *curr_stats)
358 /* u32 holds up to 71 minutes, should be enough */
359 u32 delta_us = ktime_us_delta(end->time, start->time);
360 u32 npkts = BIT_GAP(BITS_PER_TYPE(u32), end->pkt_ctr, start->pkt_ctr);
361 u32 nbytes = BIT_GAP(BITS_PER_TYPE(u32), end->byte_ctr,
367 curr_stats->ppms = DIV_ROUND_UP(npkts * USEC_PER_MSEC, delta_us);
368 curr_stats->bpms = DIV_ROUND_UP(nbytes * USEC_PER_MSEC, delta_us);
369 curr_stats->epms = DIV_ROUND_UP(NET_DIM_NEVENTS * USEC_PER_MSEC,
374 net_dim(struct net_dim *dim,
375 u64 packets, u64 bytes)
377 struct net_dim_stats curr_stats;
378 struct net_dim_sample end_sample;
383 switch (dim->state) {
384 case NET_DIM_MEASURE_IN_PROGRESS:
385 nevents = BIT_GAP(BITS_PER_TYPE(u16),
387 dim->start_sample.event_ctr);
388 if (nevents < NET_DIM_NEVENTS)
390 net_dim_sample(dim->event_ctr, packets, bytes, &end_sample);
391 net_dim_calc_stats(&dim->start_sample, &end_sample,
393 if (net_dim_decision(&curr_stats, dim)) {
394 dim->state = NET_DIM_APPLY_NEW_PROFILE;
395 schedule_work(&dim->work);
399 case NET_DIM_START_MEASURE:
400 net_dim_sample(dim->event_ctr, packets, bytes, &dim->start_sample);
401 dim->state = NET_DIM_MEASURE_IN_PROGRESS;
403 case NET_DIM_APPLY_NEW_PROFILE:
410 #endif /* NET_DIM_H */