1 /* 2 * Copyright (c) 2016, Mellanox Technologies. All rights reserved. 3 * 4 * This software is available to you under a choice of one of two 5 * licenses. You may choose to be licensed under the terms of the GNU 6 * General Public License (GPL) Version 2, available from the file 7 * COPYING in the main directory of this source tree, or the 8 * OpenIB.org BSD license below: 9 * 10 * Redistribution and use in source and binary forms, with or 11 * without modification, are permitted provided that the following 12 * conditions are met: 13 * 14 * - Redistributions of source code must retain the above 15 * copyright notice, this list of conditions and the following 16 * disclaimer. 17 * 18 * - Redistributions in binary form must reproduce the above 19 * copyright notice, this list of conditions and the following 20 * disclaimer in the documentation and/or other materials 21 * provided with the distribution. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 27 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 28 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 29 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 * SOFTWARE. 31 */ 32 33 #include <linux/mlx5/driver.h> 34 #include <linux/mlx5/fs.h> 35 #include <linux/rbtree.h> 36 #include "mlx5_core.h" 37 #include "fs_core.h" 38 #include "fs_cmd.h" 39 40 #define MLX5_FC_STATS_PERIOD msecs_to_jiffies(1000) 41 /* Max number of counters to query in bulk read is 32K */ 42 #define MLX5_SW_MAX_COUNTERS_BULK BIT(15) 43 44 /* locking scheme: 45 * 46 * It is the responsibility of the user to prevent concurrent calls or bad 47 * ordering to mlx5_fc_create(), mlx5_fc_destroy() and accessing a reference 48 * to struct mlx5_fc. 49 * e.g en_tc.c is protected by RTNL lock of its caller, and will never call a 50 * dump (access to struct mlx5_fc) after a counter is destroyed. 51 * 52 * access to counter list: 53 * - create (user context) 54 * - mlx5_fc_create() only adds to an addlist to be used by 55 * mlx5_fc_stats_query_work(). addlist is protected by a spinlock. 56 * - spawn thread to do the actual destroy 57 * 58 * - destroy (user context) 59 * - mark a counter as deleted 60 * - spawn thread to do the actual del 61 * 62 * - dump (user context) 63 * user should not call dump after destroy 64 * 65 * - query (single thread workqueue context) 66 * destroy/dump - no conflict (see destroy) 67 * query/dump - packets and bytes might be inconsistent (since update is not 68 * atomic) 69 * query/create - no conflict (see create) 70 * since every create/destroy spawn the work, only after necessary time has 71 * elapsed, the thread will actually query the hardware. 72 */ 73 74 static void mlx5_fc_stats_insert(struct rb_root *root, struct mlx5_fc *counter) 75 { 76 struct rb_node **new = &root->rb_node; 77 struct rb_node *parent = NULL; 78 79 while (*new) { 80 struct mlx5_fc *this = rb_entry(*new, struct mlx5_fc, node); 81 int result = counter->id - this->id; 82 83 parent = *new; 84 if (result < 0) 85 new = &((*new)->rb_left); 86 else 87 new = &((*new)->rb_right); 88 } 89 90 /* Add new node and rebalance tree. */ 91 rb_link_node(&counter->node, parent, new); 92 rb_insert_color(&counter->node, root); 93 } 94 95 /* The function returns the last node that was queried so the caller 96 * function can continue calling it till all counters are queried. 97 */ 98 static struct rb_node *mlx5_fc_stats_query(struct mlx5_core_dev *dev, 99 struct mlx5_fc *first, 100 u32 last_id) 101 { 102 struct mlx5_cmd_fc_bulk *b; 103 struct rb_node *node = NULL; 104 u32 afirst_id; 105 int num; 106 int err; 107 108 int max_bulk = min_t(int, MLX5_SW_MAX_COUNTERS_BULK, 109 (1 << MLX5_CAP_GEN(dev, log_max_flow_counter_bulk))); 110 111 /* first id must be aligned to 4 when using bulk query */ 112 afirst_id = first->id & ~0x3; 113 114 /* number of counters to query inc. the last counter */ 115 num = ALIGN(last_id - afirst_id + 1, 4); 116 if (num > max_bulk) { 117 num = max_bulk; 118 last_id = afirst_id + num - 1; 119 } 120 121 b = mlx5_cmd_fc_bulk_alloc(dev, afirst_id, num); 122 if (!b) { 123 mlx5_core_err(dev, "Error allocating resources for bulk query\n"); 124 return NULL; 125 } 126 127 err = mlx5_cmd_fc_bulk_query(dev, b); 128 if (err) { 129 mlx5_core_err(dev, "Error doing bulk query: %d\n", err); 130 goto out; 131 } 132 133 for (node = &first->node; node; node = rb_next(node)) { 134 struct mlx5_fc *counter = rb_entry(node, struct mlx5_fc, node); 135 struct mlx5_fc_cache *c = &counter->cache; 136 u64 packets; 137 u64 bytes; 138 139 if (counter->id > last_id) 140 break; 141 142 mlx5_cmd_fc_bulk_get(dev, b, 143 counter->id, &packets, &bytes); 144 145 if (c->packets == packets) 146 continue; 147 148 c->packets = packets; 149 c->bytes = bytes; 150 c->lastuse = jiffies; 151 } 152 153 out: 154 mlx5_cmd_fc_bulk_free(b); 155 156 return node; 157 } 158 159 static void mlx5_fc_stats_work(struct work_struct *work) 160 { 161 struct mlx5_core_dev *dev = container_of(work, struct mlx5_core_dev, 162 priv.fc_stats.work.work); 163 struct mlx5_fc_stats *fc_stats = &dev->priv.fc_stats; 164 unsigned long now = jiffies; 165 struct mlx5_fc *counter = NULL; 166 struct mlx5_fc *last = NULL; 167 struct rb_node *node; 168 LIST_HEAD(tmplist); 169 170 spin_lock(&fc_stats->addlist_lock); 171 172 list_splice_tail_init(&fc_stats->addlist, &tmplist); 173 174 if (!list_empty(&tmplist) || !RB_EMPTY_ROOT(&fc_stats->counters)) 175 queue_delayed_work(fc_stats->wq, &fc_stats->work, 176 fc_stats->sampling_interval); 177 178 spin_unlock(&fc_stats->addlist_lock); 179 180 list_for_each_entry(counter, &tmplist, list) 181 mlx5_fc_stats_insert(&fc_stats->counters, counter); 182 183 node = rb_first(&fc_stats->counters); 184 while (node) { 185 counter = rb_entry(node, struct mlx5_fc, node); 186 187 node = rb_next(node); 188 189 if (counter->deleted) { 190 rb_erase(&counter->node, &fc_stats->counters); 191 192 mlx5_cmd_fc_free(dev, counter->id); 193 194 kfree(counter); 195 continue; 196 } 197 198 last = counter; 199 } 200 201 if (time_before(now, fc_stats->next_query) || !last) 202 return; 203 204 node = rb_first(&fc_stats->counters); 205 while (node) { 206 counter = rb_entry(node, struct mlx5_fc, node); 207 208 node = mlx5_fc_stats_query(dev, counter, last->id); 209 } 210 211 fc_stats->next_query = now + fc_stats->sampling_interval; 212 } 213 214 struct mlx5_fc *mlx5_fc_create(struct mlx5_core_dev *dev, bool aging) 215 { 216 struct mlx5_fc_stats *fc_stats = &dev->priv.fc_stats; 217 struct mlx5_fc *counter; 218 int err; 219 220 counter = kzalloc(sizeof(*counter), GFP_KERNEL); 221 if (!counter) 222 return ERR_PTR(-ENOMEM); 223 224 err = mlx5_cmd_fc_alloc(dev, &counter->id); 225 if (err) 226 goto err_out; 227 228 if (aging) { 229 counter->cache.lastuse = jiffies; 230 counter->aging = true; 231 232 spin_lock(&fc_stats->addlist_lock); 233 list_add(&counter->list, &fc_stats->addlist); 234 spin_unlock(&fc_stats->addlist_lock); 235 236 mod_delayed_work(fc_stats->wq, &fc_stats->work, 0); 237 } 238 239 return counter; 240 241 err_out: 242 kfree(counter); 243 244 return ERR_PTR(err); 245 } 246 EXPORT_SYMBOL(mlx5_fc_create); 247 248 void mlx5_fc_destroy(struct mlx5_core_dev *dev, struct mlx5_fc *counter) 249 { 250 struct mlx5_fc_stats *fc_stats = &dev->priv.fc_stats; 251 252 if (!counter) 253 return; 254 255 if (counter->aging) { 256 counter->deleted = true; 257 mod_delayed_work(fc_stats->wq, &fc_stats->work, 0); 258 return; 259 } 260 261 mlx5_cmd_fc_free(dev, counter->id); 262 kfree(counter); 263 } 264 EXPORT_SYMBOL(mlx5_fc_destroy); 265 266 int mlx5_init_fc_stats(struct mlx5_core_dev *dev) 267 { 268 struct mlx5_fc_stats *fc_stats = &dev->priv.fc_stats; 269 270 fc_stats->counters = RB_ROOT; 271 INIT_LIST_HEAD(&fc_stats->addlist); 272 spin_lock_init(&fc_stats->addlist_lock); 273 274 fc_stats->wq = create_singlethread_workqueue("mlx5_fc"); 275 if (!fc_stats->wq) 276 return -ENOMEM; 277 278 fc_stats->sampling_interval = MLX5_FC_STATS_PERIOD; 279 INIT_DELAYED_WORK(&fc_stats->work, mlx5_fc_stats_work); 280 281 return 0; 282 } 283 284 void mlx5_cleanup_fc_stats(struct mlx5_core_dev *dev) 285 { 286 struct mlx5_fc_stats *fc_stats = &dev->priv.fc_stats; 287 struct mlx5_fc *counter; 288 struct mlx5_fc *tmp; 289 struct rb_node *node; 290 291 cancel_delayed_work_sync(&dev->priv.fc_stats.work); 292 destroy_workqueue(dev->priv.fc_stats.wq); 293 dev->priv.fc_stats.wq = NULL; 294 295 list_for_each_entry_safe(counter, tmp, &fc_stats->addlist, list) { 296 list_del(&counter->list); 297 298 mlx5_cmd_fc_free(dev, counter->id); 299 300 kfree(counter); 301 } 302 303 node = rb_first(&fc_stats->counters); 304 while (node) { 305 counter = rb_entry(node, struct mlx5_fc, node); 306 307 node = rb_next(node); 308 309 rb_erase(&counter->node, &fc_stats->counters); 310 311 mlx5_cmd_fc_free(dev, counter->id); 312 313 kfree(counter); 314 } 315 } 316 317 int mlx5_fc_query(struct mlx5_core_dev *dev, struct mlx5_fc *counter, 318 u64 *packets, u64 *bytes) 319 { 320 return mlx5_cmd_fc_query(dev, counter->id, packets, bytes); 321 } 322 EXPORT_SYMBOL(mlx5_fc_query); 323 324 void mlx5_fc_query_cached(struct mlx5_fc *counter, 325 u64 *bytes, u64 *packets, u64 *lastuse) 326 { 327 struct mlx5_fc_cache c; 328 329 c = counter->cache; 330 331 *bytes = c.bytes - counter->lastbytes; 332 *packets = c.packets - counter->lastpackets; 333 *lastuse = c.lastuse; 334 335 counter->lastbytes = c.bytes; 336 counter->lastpackets = c.packets; 337 } 338 339 void mlx5_fc_queue_stats_work(struct mlx5_core_dev *dev, 340 struct delayed_work *dwork, 341 unsigned long delay) 342 { 343 struct mlx5_fc_stats *fc_stats = &dev->priv.fc_stats; 344 345 queue_delayed_work(fc_stats->wq, dwork, delay); 346 } 347 348 void mlx5_fc_update_sampling_interval(struct mlx5_core_dev *dev, 349 unsigned long interval) 350 { 351 struct mlx5_fc_stats *fc_stats = &dev->priv.fc_stats; 352 353 fc_stats->sampling_interval = min_t(unsigned long, interval, 354 fc_stats->sampling_interval); 355 } 356