linux/drivers/net/ethernet/chelsio/cxgb4/cxgb4_tc_u32.c
<<
>>
Prefs
   1/*
   2 * This file is part of the Chelsio T4 Ethernet driver for Linux.
   3 *
   4 * Copyright (c) 2016 Chelsio Communications, Inc. All rights reserved.
   5 *
   6 * This software is available to you under a choice of one of two
   7 * licenses.  You may choose to be licensed under the terms of the GNU
   8 * General Public License (GPL) Version 2, available from the file
   9 * COPYING in the main directory of this source tree, or the
  10 * OpenIB.org BSD license below:
  11 *
  12 *     Redistribution and use in source and binary forms, with or
  13 *     without modification, are permitted provided that the following
  14 *     conditions are met:
  15 *
  16 *      - Redistributions of source code must retain the above
  17 *        copyright notice, this list of conditions and the following
  18 *        disclaimer.
  19 *
  20 *      - Redistributions in binary form must reproduce the above
  21 *        copyright notice, this list of conditions and the following
  22 *        disclaimer in the documentation and/or other materials
  23 *        provided with the distribution.
  24 *
  25 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  26 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  27 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  28 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  29 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  30 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  31 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  32 * SOFTWARE.
  33 */
  34
  35#include <net/tc_act/tc_gact.h>
  36#include <net/tc_act/tc_mirred.h>
  37
  38#include "cxgb4.h"
  39#include "cxgb4_tc_u32_parse.h"
  40#include "cxgb4_tc_u32.h"
  41
  42/* Fill ch_filter_specification with parsed match value/mask pair. */
  43static int fill_match_fields(struct adapter *adap,
  44                             struct ch_filter_specification *fs,
  45                             struct tc_cls_u32_offload *cls,
  46                             const struct cxgb4_match_field *entry,
  47                             bool next_header)
  48{
  49        unsigned int i, j;
  50        u32 val, mask;
  51        int off, err;
  52        bool found;
  53
  54        for (i = 0; i < cls->knode.sel->nkeys; i++) {
  55                off = cls->knode.sel->keys[i].off;
  56                val = cls->knode.sel->keys[i].val;
  57                mask = cls->knode.sel->keys[i].mask;
  58
  59                if (next_header) {
  60                        /* For next headers, parse only keys with offmask */
  61                        if (!cls->knode.sel->keys[i].offmask)
  62                                continue;
  63                } else {
  64                        /* For the remaining, parse only keys without offmask */
  65                        if (cls->knode.sel->keys[i].offmask)
  66                                continue;
  67                }
  68
  69                found = false;
  70
  71                for (j = 0; entry[j].val; j++) {
  72                        if (off == entry[j].off) {
  73                                found = true;
  74                                err = entry[j].val(fs, val, mask);
  75                                if (err)
  76                                        return err;
  77                                break;
  78                        }
  79                }
  80
  81                if (!found)
  82                        return -EINVAL;
  83        }
  84
  85        return 0;
  86}
  87
  88/* Fill ch_filter_specification with parsed action. */
  89static int fill_action_fields(struct adapter *adap,
  90                              struct ch_filter_specification *fs,
  91                              struct tc_cls_u32_offload *cls)
  92{
  93        unsigned int num_actions = 0;
  94        const struct tc_action *a;
  95        struct tcf_exts *exts;
  96        LIST_HEAD(actions);
  97
  98        exts = cls->knode.exts;
  99        if (!tcf_exts_has_actions(exts))
 100                return -EINVAL;
 101
 102        tcf_exts_to_list(exts, &actions);
 103        list_for_each_entry(a, &actions, list) {
 104                /* Don't allow more than one action per rule. */
 105                if (num_actions)
 106                        return -EINVAL;
 107
 108                /* Drop in hardware. */
 109                if (is_tcf_gact_shot(a)) {
 110                        fs->action = FILTER_DROP;
 111                        num_actions++;
 112                        continue;
 113                }
 114
 115                /* Re-direct to specified port in hardware. */
 116                if (is_tcf_mirred_egress_redirect(a)) {
 117                        struct net_device *n_dev;
 118                        unsigned int i, index;
 119                        bool found = false;
 120
 121                        index = tcf_mirred_ifindex(a);
 122                        for_each_port(adap, i) {
 123                                n_dev = adap->port[i];
 124                                if (index == n_dev->ifindex) {
 125                                        fs->action = FILTER_SWITCH;
 126                                        fs->eport = i;
 127                                        found = true;
 128                                        break;
 129                                }
 130                        }
 131
 132                        /* Interface doesn't belong to any port of
 133                         * the underlying hardware.
 134                         */
 135                        if (!found)
 136                                return -EINVAL;
 137
 138                        num_actions++;
 139                        continue;
 140                }
 141
 142                /* Un-supported action. */
 143                return -EINVAL;
 144        }
 145
 146        return 0;
 147}
 148
 149int cxgb4_config_knode(struct net_device *dev, struct tc_cls_u32_offload *cls)
 150{
 151        const struct cxgb4_match_field *start, *link_start = NULL;
 152        struct adapter *adapter = netdev2adap(dev);
 153        __be16 protocol = cls->common.protocol;
 154        struct ch_filter_specification fs;
 155        struct cxgb4_tc_u32_table *t;
 156        struct cxgb4_link *link;
 157        unsigned int filter_id;
 158        u32 uhtid, link_uhtid;
 159        bool is_ipv6 = false;
 160        int ret;
 161
 162        if (!can_tc_u32_offload(dev))
 163                return -EOPNOTSUPP;
 164
 165        if (protocol != htons(ETH_P_IP) && protocol != htons(ETH_P_IPV6))
 166                return -EOPNOTSUPP;
 167
 168        /* Fetch the location to insert the filter. */
 169        filter_id = cls->knode.handle & 0xFFFFF;
 170
 171        if (filter_id > adapter->tids.nftids) {
 172                dev_err(adapter->pdev_dev,
 173                        "Location %d out of range for insertion. Max: %d\n",
 174                        filter_id, adapter->tids.nftids);
 175                return -ERANGE;
 176        }
 177
 178        t = adapter->tc_u32;
 179        uhtid = TC_U32_USERHTID(cls->knode.handle);
 180        link_uhtid = TC_U32_USERHTID(cls->knode.link_handle);
 181
 182        /* Ensure that uhtid is either root u32 (i.e. 0x800)
 183         * or a a valid linked bucket.
 184         */
 185        if (uhtid != 0x800 && uhtid >= t->size)
 186                return -EINVAL;
 187
 188        /* Ensure link handle uhtid is sane, if specified. */
 189        if (link_uhtid >= t->size)
 190                return -EINVAL;
 191
 192        memset(&fs, 0, sizeof(fs));
 193
 194        if (protocol == htons(ETH_P_IPV6)) {
 195                start = cxgb4_ipv6_fields;
 196                is_ipv6 = true;
 197        } else {
 198                start = cxgb4_ipv4_fields;
 199                is_ipv6 = false;
 200        }
 201
 202        if (uhtid != 0x800) {
 203                /* Link must exist from root node before insertion. */
 204                if (!t->table[uhtid - 1].link_handle)
 205                        return -EINVAL;
 206
 207                /* Link must have a valid supported next header. */
 208                link_start = t->table[uhtid - 1].match_field;
 209                if (!link_start)
 210                        return -EINVAL;
 211        }
 212
 213        /* Parse links and record them for subsequent jumps to valid
 214         * next headers.
 215         */
 216        if (link_uhtid) {
 217                const struct cxgb4_next_header *next;
 218                bool found = false;
 219                unsigned int i, j;
 220                u32 val, mask;
 221                int off;
 222
 223                if (t->table[link_uhtid - 1].link_handle) {
 224                        dev_err(adapter->pdev_dev,
 225                                "Link handle exists for: 0x%x\n",
 226                                link_uhtid);
 227                        return -EINVAL;
 228                }
 229
 230                next = is_ipv6 ? cxgb4_ipv6_jumps : cxgb4_ipv4_jumps;
 231
 232                /* Try to find matches that allow jumps to next header. */
 233                for (i = 0; next[i].jump; i++) {
 234                        if (next[i].offoff != cls->knode.sel->offoff ||
 235                            next[i].shift != cls->knode.sel->offshift ||
 236                            next[i].mask != cls->knode.sel->offmask ||
 237                            next[i].offset != cls->knode.sel->off)
 238                                continue;
 239
 240                        /* Found a possible candidate.  Find a key that
 241                         * matches the corresponding offset, value, and
 242                         * mask to jump to next header.
 243                         */
 244                        for (j = 0; j < cls->knode.sel->nkeys; j++) {
 245                                off = cls->knode.sel->keys[j].off;
 246                                val = cls->knode.sel->keys[j].val;
 247                                mask = cls->knode.sel->keys[j].mask;
 248
 249                                if (next[i].match_off == off &&
 250                                    next[i].match_val == val &&
 251                                    next[i].match_mask == mask) {
 252                                        found = true;
 253                                        break;
 254                                }
 255                        }
 256
 257                        if (!found)
 258                                continue; /* Try next candidate. */
 259
 260                        /* Candidate to jump to next header found.
 261                         * Translate all keys to internal specification
 262                         * and store them in jump table. This spec is copied
 263                         * later to set the actual filters.
 264                         */
 265                        ret = fill_match_fields(adapter, &fs, cls,
 266                                                start, false);
 267                        if (ret)
 268                                goto out;
 269
 270                        link = &t->table[link_uhtid - 1];
 271                        link->match_field = next[i].jump;
 272                        link->link_handle = cls->knode.handle;
 273                        memcpy(&link->fs, &fs, sizeof(fs));
 274                        break;
 275                }
 276
 277                /* No candidate found to jump to next header. */
 278                if (!found)
 279                        return -EINVAL;
 280
 281                return 0;
 282        }
 283
 284        /* Fill ch_filter_specification match fields to be shipped to hardware.
 285         * Copy the linked spec (if any) first.  And then update the spec as
 286         * needed.
 287         */
 288        if (uhtid != 0x800 && t->table[uhtid - 1].link_handle) {
 289                /* Copy linked ch_filter_specification */
 290                memcpy(&fs, &t->table[uhtid - 1].fs, sizeof(fs));
 291                ret = fill_match_fields(adapter, &fs, cls,
 292                                        link_start, true);
 293                if (ret)
 294                        goto out;
 295        }
 296
 297        ret = fill_match_fields(adapter, &fs, cls, start, false);
 298        if (ret)
 299                goto out;
 300
 301        /* Fill ch_filter_specification action fields to be shipped to
 302         * hardware.
 303         */
 304        ret = fill_action_fields(adapter, &fs, cls);
 305        if (ret)
 306                goto out;
 307
 308        /* The filter spec has been completely built from the info
 309         * provided from u32.  We now set some default fields in the
 310         * spec for sanity.
 311         */
 312
 313        /* Match only packets coming from the ingress port where this
 314         * filter will be created.
 315         */
 316        fs.val.iport = netdev2pinfo(dev)->port_id;
 317        fs.mask.iport = ~0;
 318
 319        /* Enable filter hit counts. */
 320        fs.hitcnts = 1;
 321
 322        /* Set type of filter - IPv6 or IPv4 */
 323        fs.type = is_ipv6 ? 1 : 0;
 324
 325        /* Set the filter */
 326        ret = cxgb4_set_filter(dev, filter_id, &fs);
 327        if (ret)
 328                goto out;
 329
 330        /* If this is a linked bucket, then set the corresponding
 331         * entry in the bitmap to mark it as belonging to this linked
 332         * bucket.
 333         */
 334        if (uhtid != 0x800 && t->table[uhtid - 1].link_handle)
 335                set_bit(filter_id, t->table[uhtid - 1].tid_map);
 336
 337out:
 338        return ret;
 339}
 340
 341int cxgb4_delete_knode(struct net_device *dev, struct tc_cls_u32_offload *cls)
 342{
 343        struct adapter *adapter = netdev2adap(dev);
 344        unsigned int filter_id, max_tids, i, j;
 345        struct cxgb4_link *link = NULL;
 346        struct cxgb4_tc_u32_table *t;
 347        u32 handle, uhtid;
 348        int ret;
 349
 350        if (!can_tc_u32_offload(dev))
 351                return -EOPNOTSUPP;
 352
 353        /* Fetch the location to delete the filter. */
 354        filter_id = cls->knode.handle & 0xFFFFF;
 355
 356        if (filter_id > adapter->tids.nftids) {
 357                dev_err(adapter->pdev_dev,
 358                        "Location %d out of range for deletion. Max: %d\n",
 359                        filter_id, adapter->tids.nftids);
 360                return -ERANGE;
 361        }
 362
 363        t = adapter->tc_u32;
 364        handle = cls->knode.handle;
 365        uhtid = TC_U32_USERHTID(cls->knode.handle);
 366
 367        /* Ensure that uhtid is either root u32 (i.e. 0x800)
 368         * or a a valid linked bucket.
 369         */
 370        if (uhtid != 0x800 && uhtid >= t->size)
 371                return -EINVAL;
 372
 373        /* Delete the specified filter */
 374        if (uhtid != 0x800) {
 375                link = &t->table[uhtid - 1];
 376                if (!link->link_handle)
 377                        return -EINVAL;
 378
 379                if (!test_bit(filter_id, link->tid_map))
 380                        return -EINVAL;
 381        }
 382
 383        ret = cxgb4_del_filter(dev, filter_id);
 384        if (ret)
 385                goto out;
 386
 387        if (link)
 388                clear_bit(filter_id, link->tid_map);
 389
 390        /* If a link is being deleted, then delete all filters
 391         * associated with the link.
 392         */
 393        max_tids = adapter->tids.nftids;
 394        for (i = 0; i < t->size; i++) {
 395                link = &t->table[i];
 396
 397                if (link->link_handle == handle) {
 398                        for (j = 0; j < max_tids; j++) {
 399                                if (!test_bit(j, link->tid_map))
 400                                        continue;
 401
 402                                ret = __cxgb4_del_filter(dev, j, NULL);
 403                                if (ret)
 404                                        goto out;
 405
 406                                clear_bit(j, link->tid_map);
 407                        }
 408
 409                        /* Clear the link state */
 410                        link->match_field = NULL;
 411                        link->link_handle = 0;
 412                        memset(&link->fs, 0, sizeof(link->fs));
 413                        break;
 414                }
 415        }
 416
 417out:
 418        return ret;
 419}
 420
 421void cxgb4_cleanup_tc_u32(struct adapter *adap)
 422{
 423        struct cxgb4_tc_u32_table *t;
 424        unsigned int i;
 425
 426        if (!adap->tc_u32)
 427                return;
 428
 429        /* Free up all allocated memory. */
 430        t = adap->tc_u32;
 431        for (i = 0; i < t->size; i++) {
 432                struct cxgb4_link *link = &t->table[i];
 433
 434                kvfree(link->tid_map);
 435        }
 436        kvfree(adap->tc_u32);
 437}
 438
 439struct cxgb4_tc_u32_table *cxgb4_init_tc_u32(struct adapter *adap)
 440{
 441        unsigned int max_tids = adap->tids.nftids;
 442        struct cxgb4_tc_u32_table *t;
 443        unsigned int i;
 444
 445        if (!max_tids)
 446                return NULL;
 447
 448        t = kvzalloc(sizeof(*t) +
 449                         (max_tids * sizeof(struct cxgb4_link)), GFP_KERNEL);
 450        if (!t)
 451                return NULL;
 452
 453        t->size = max_tids;
 454
 455        for (i = 0; i < t->size; i++) {
 456                struct cxgb4_link *link = &t->table[i];
 457                unsigned int bmap_size;
 458
 459                bmap_size = BITS_TO_LONGS(max_tids);
 460                link->tid_map = kvzalloc(sizeof(unsigned long) * bmap_size, GFP_KERNEL);
 461                if (!link->tid_map)
 462                        goto out_no_mem;
 463                bitmap_zero(link->tid_map, max_tids);
 464        }
 465
 466        return t;
 467
 468out_no_mem:
 469        for (i = 0; i < t->size; i++) {
 470                struct cxgb4_link *link = &t->table[i];
 471
 472                if (link->tid_map)
 473                        kvfree(link->tid_map);
 474        }
 475
 476        if (t)
 477                kvfree(t);
 478
 479        return NULL;
 480}
 481