| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (C) 2019 Chelsio Communications. All rights reserved. */ |
| |
| #include "cxgb4.h" |
| #include "cxgb4_tc_matchall.h" |
| #include "sched.h" |
| #include "cxgb4_uld.h" |
| #include "cxgb4_filter.h" |
| #include "cxgb4_tc_flower.h" |
| |
| static int cxgb4_policer_validate(const struct flow_action *action, |
| const struct flow_action_entry *act, |
| 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; |
| } |
| |
| return 0; |
| } |
| |
| static int cxgb4_matchall_egress_validate(struct net_device *dev, |
| struct tc_cls_matchall_offload *cls) |
| { |
| struct netlink_ext_ack *extack = cls->common.extack; |
| struct flow_action *actions = &cls->rule->action; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct flow_action_entry *entry; |
| struct ch_sched_queue qe; |
| struct sched_class *e; |
| u64 max_link_rate; |
| u32 i, speed; |
| int ret; |
| |
| if (!flow_action_has_entries(actions)) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Egress MATCHALL offload needs at least 1 policing action"); |
| return -EINVAL; |
| } else if (!flow_offload_has_one_action(actions)) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Egress MATCHALL offload only supports 1 policing action"); |
| return -EINVAL; |
| } else if (pi->tc_block_shared) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Egress MATCHALL offload not supported with shared blocks"); |
| return -EINVAL; |
| } |
| |
| ret = t4_get_link_params(pi, NULL, &speed, NULL); |
| if (ret) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Failed to get max speed supported by the link"); |
| return -EINVAL; |
| } |
| |
| /* Convert from Mbps to bps */ |
| max_link_rate = (u64)speed * 1000 * 1000; |
| |
| flow_action_for_each(i, entry, actions) { |
| switch (entry->id) { |
| case FLOW_ACTION_POLICE: |
| ret = cxgb4_policer_validate(actions, entry, extack); |
| if (ret) |
| return ret; |
| |
| /* Convert bytes per second to bits per second */ |
| if (entry->police.rate_bytes_ps * 8 > max_link_rate) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Specified policing max rate is larger than underlying link speed"); |
| return -ERANGE; |
| } |
| break; |
| default: |
| NL_SET_ERR_MSG_MOD(extack, |
| "Only policing action supported with Egress MATCHALL offload"); |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| for (i = 0; i < pi->nqsets; i++) { |
| memset(&qe, 0, sizeof(qe)); |
| qe.queue = i; |
| |
| e = cxgb4_sched_queue_lookup(dev, &qe); |
| if (e && e->info.u.params.level != SCHED_CLASS_LEVEL_CH_RL) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Some queues are already bound to different class"); |
| return -EBUSY; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int cxgb4_matchall_tc_bind_queues(struct net_device *dev, u32 tc) |
| { |
| struct port_info *pi = netdev2pinfo(dev); |
| struct ch_sched_queue qe; |
| int ret; |
| u32 i; |
| |
| for (i = 0; i < pi->nqsets; i++) { |
| qe.queue = i; |
| qe.class = tc; |
| ret = cxgb4_sched_class_bind(dev, &qe, SCHED_QUEUE); |
| if (ret) |
| goto out_free; |
| } |
| |
| return 0; |
| |
| out_free: |
| while (i--) { |
| qe.queue = i; |
| qe.class = SCHED_CLS_NONE; |
| cxgb4_sched_class_unbind(dev, &qe, SCHED_QUEUE); |
| } |
| |
| return ret; |
| } |
| |
| static void cxgb4_matchall_tc_unbind_queues(struct net_device *dev) |
| { |
| struct port_info *pi = netdev2pinfo(dev); |
| struct ch_sched_queue qe; |
| u32 i; |
| |
| for (i = 0; i < pi->nqsets; i++) { |
| qe.queue = i; |
| qe.class = SCHED_CLS_NONE; |
| cxgb4_sched_class_unbind(dev, &qe, SCHED_QUEUE); |
| } |
| } |
| |
| static int cxgb4_matchall_alloc_tc(struct net_device *dev, |
| struct tc_cls_matchall_offload *cls) |
| { |
| struct ch_sched_params p = { |
| .type = SCHED_CLASS_TYPE_PACKET, |
| .u.params.level = SCHED_CLASS_LEVEL_CH_RL, |
| .u.params.mode = SCHED_CLASS_MODE_CLASS, |
| .u.params.rateunit = SCHED_CLASS_RATEUNIT_BITS, |
| .u.params.ratemode = SCHED_CLASS_RATEMODE_ABS, |
| .u.params.class = SCHED_CLS_NONE, |
| .u.params.minrate = 0, |
| .u.params.weight = 0, |
| .u.params.pktsize = dev->mtu, |
| }; |
| struct netlink_ext_ack *extack = cls->common.extack; |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| struct flow_action_entry *entry; |
| struct sched_class *e; |
| int ret; |
| u32 i; |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| |
| flow_action_for_each(i, entry, &cls->rule->action) |
| if (entry->id == FLOW_ACTION_POLICE) |
| break; |
| |
| ret = cxgb4_policer_validate(&cls->rule->action, entry, extack); |
| if (ret) |
| return ret; |
| |
| /* Convert from bytes per second to Kbps */ |
| p.u.params.maxrate = div_u64(entry->police.rate_bytes_ps * 8, 1000); |
| p.u.params.channel = pi->tx_chan; |
| e = cxgb4_sched_class_alloc(dev, &p); |
| if (!e) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "No free traffic class available for policing action"); |
| return -ENOMEM; |
| } |
| |
| ret = cxgb4_matchall_tc_bind_queues(dev, e->idx); |
| if (ret) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Could not bind queues to traffic class"); |
| goto out_free; |
| } |
| |
| tc_port_matchall->egress.hwtc = e->idx; |
| tc_port_matchall->egress.cookie = cls->cookie; |
| tc_port_matchall->egress.state = CXGB4_MATCHALL_STATE_ENABLED; |
| return 0; |
| |
| out_free: |
| cxgb4_sched_class_free(dev, e->idx); |
| return ret; |
| } |
| |
| static void cxgb4_matchall_free_tc(struct net_device *dev) |
| { |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| cxgb4_matchall_tc_unbind_queues(dev); |
| cxgb4_sched_class_free(dev, tc_port_matchall->egress.hwtc); |
| |
| tc_port_matchall->egress.hwtc = SCHED_CLS_NONE; |
| tc_port_matchall->egress.cookie = 0; |
| tc_port_matchall->egress.state = CXGB4_MATCHALL_STATE_DISABLED; |
| } |
| |
| static int cxgb4_matchall_mirror_alloc(struct net_device *dev, |
| struct tc_cls_matchall_offload *cls) |
| { |
| struct netlink_ext_ack *extack = cls->common.extack; |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| struct flow_action_entry *act; |
| int ret; |
| u32 i; |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| flow_action_for_each(i, act, &cls->rule->action) { |
| if (act->id == FLOW_ACTION_MIRRED) { |
| ret = cxgb4_port_mirror_alloc(dev); |
| if (ret) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Couldn't allocate mirror"); |
| return ret; |
| } |
| |
| tc_port_matchall->ingress.viid_mirror = pi->viid_mirror; |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void cxgb4_matchall_mirror_free(struct net_device *dev) |
| { |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| if (!tc_port_matchall->ingress.viid_mirror) |
| return; |
| |
| cxgb4_port_mirror_free(dev); |
| tc_port_matchall->ingress.viid_mirror = 0; |
| } |
| |
| static int cxgb4_matchall_del_filter(struct net_device *dev, u8 filter_type) |
| { |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| int ret; |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| ret = cxgb4_del_filter(dev, tc_port_matchall->ingress.tid[filter_type], |
| &tc_port_matchall->ingress.fs[filter_type]); |
| if (ret) |
| return ret; |
| |
| tc_port_matchall->ingress.tid[filter_type] = 0; |
| return 0; |
| } |
| |
| static int cxgb4_matchall_add_filter(struct net_device *dev, |
| struct tc_cls_matchall_offload *cls, |
| u8 filter_type) |
| { |
| struct netlink_ext_ack *extack = cls->common.extack; |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| struct ch_filter_specification *fs; |
| int ret, fidx; |
| |
| /* Get a free filter entry TID, where we can insert this new |
| * rule. Only insert rule if its prio doesn't conflict with |
| * existing rules. |
| */ |
| fidx = cxgb4_get_free_ftid(dev, filter_type ? PF_INET6 : PF_INET, |
| false, cls->common.prio); |
| if (fidx < 0) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "No free LETCAM index available"); |
| return -ENOMEM; |
| } |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| fs = &tc_port_matchall->ingress.fs[filter_type]; |
| memset(fs, 0, sizeof(*fs)); |
| |
| if (fidx < adap->tids.nhpftids) |
| fs->prio = 1; |
| fs->tc_prio = cls->common.prio; |
| fs->tc_cookie = cls->cookie; |
| fs->type = filter_type; |
| fs->hitcnts = 1; |
| |
| fs->val.pfvf_vld = 1; |
| fs->val.pf = adap->pf; |
| fs->val.vf = pi->vin; |
| |
| cxgb4_process_flow_actions(dev, &cls->rule->action, fs); |
| |
| ret = cxgb4_set_filter(dev, fidx, fs); |
| if (ret) |
| return ret; |
| |
| tc_port_matchall->ingress.tid[filter_type] = fidx; |
| return 0; |
| } |
| |
| static int cxgb4_matchall_alloc_filter(struct net_device *dev, |
| struct tc_cls_matchall_offload *cls) |
| { |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| int ret, i; |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| |
| ret = cxgb4_matchall_mirror_alloc(dev, cls); |
| if (ret) |
| return ret; |
| |
| for (i = 0; i < CXGB4_FILTER_TYPE_MAX; i++) { |
| ret = cxgb4_matchall_add_filter(dev, cls, i); |
| if (ret) |
| goto out_free; |
| } |
| |
| tc_port_matchall->ingress.state = CXGB4_MATCHALL_STATE_ENABLED; |
| return 0; |
| |
| out_free: |
| while (i-- > 0) |
| cxgb4_matchall_del_filter(dev, i); |
| |
| cxgb4_matchall_mirror_free(dev); |
| return ret; |
| } |
| |
| static int cxgb4_matchall_free_filter(struct net_device *dev) |
| { |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| int ret; |
| u8 i; |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| |
| for (i = 0; i < CXGB4_FILTER_TYPE_MAX; i++) { |
| ret = cxgb4_matchall_del_filter(dev, i); |
| if (ret) |
| return ret; |
| } |
| |
| cxgb4_matchall_mirror_free(dev); |
| |
| tc_port_matchall->ingress.packets = 0; |
| tc_port_matchall->ingress.bytes = 0; |
| tc_port_matchall->ingress.last_used = 0; |
| tc_port_matchall->ingress.state = CXGB4_MATCHALL_STATE_DISABLED; |
| return 0; |
| } |
| |
| int cxgb4_tc_matchall_replace(struct net_device *dev, |
| struct tc_cls_matchall_offload *cls_matchall, |
| bool ingress) |
| { |
| struct netlink_ext_ack *extack = cls_matchall->common.extack; |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| int ret; |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| if (ingress) { |
| if (tc_port_matchall->ingress.state == |
| CXGB4_MATCHALL_STATE_ENABLED) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Only 1 Ingress MATCHALL can be offloaded"); |
| return -ENOMEM; |
| } |
| |
| ret = cxgb4_validate_flow_actions(dev, |
| &cls_matchall->rule->action, |
| extack, 1); |
| if (ret) |
| return ret; |
| |
| return cxgb4_matchall_alloc_filter(dev, cls_matchall); |
| } |
| |
| if (tc_port_matchall->egress.state == CXGB4_MATCHALL_STATE_ENABLED) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Only 1 Egress MATCHALL can be offloaded"); |
| return -ENOMEM; |
| } |
| |
| ret = cxgb4_matchall_egress_validate(dev, cls_matchall); |
| if (ret) |
| return ret; |
| |
| return cxgb4_matchall_alloc_tc(dev, cls_matchall); |
| } |
| |
| int cxgb4_tc_matchall_destroy(struct net_device *dev, |
| struct tc_cls_matchall_offload *cls_matchall, |
| bool ingress) |
| { |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| if (ingress) { |
| /* All the filter types of this matchall rule save the |
| * same cookie. So, checking for the first one is |
| * enough. |
| */ |
| if (cls_matchall->cookie != |
| tc_port_matchall->ingress.fs[0].tc_cookie) |
| return -ENOENT; |
| |
| return cxgb4_matchall_free_filter(dev); |
| } |
| |
| if (cls_matchall->cookie != tc_port_matchall->egress.cookie) |
| return -ENOENT; |
| |
| cxgb4_matchall_free_tc(dev); |
| return 0; |
| } |
| |
| int cxgb4_tc_matchall_stats(struct net_device *dev, |
| struct tc_cls_matchall_offload *cls_matchall) |
| { |
| u64 tmp_packets, tmp_bytes, packets = 0, bytes = 0; |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct cxgb4_matchall_ingress_entry *ingress; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| int ret; |
| u8 i; |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| if (tc_port_matchall->ingress.state == CXGB4_MATCHALL_STATE_DISABLED) |
| return -ENOENT; |
| |
| ingress = &tc_port_matchall->ingress; |
| for (i = 0; i < CXGB4_FILTER_TYPE_MAX; i++) { |
| ret = cxgb4_get_filter_counters(dev, ingress->tid[i], |
| &tmp_packets, &tmp_bytes, |
| ingress->fs[i].hash); |
| if (ret) |
| return ret; |
| |
| packets += tmp_packets; |
| bytes += tmp_bytes; |
| } |
| |
| if (tc_port_matchall->ingress.packets != packets) { |
| flow_stats_update(&cls_matchall->stats, |
| bytes - tc_port_matchall->ingress.bytes, |
| packets - tc_port_matchall->ingress.packets, |
| 0, tc_port_matchall->ingress.last_used, |
| FLOW_ACTION_HW_STATS_IMMEDIATE); |
| |
| tc_port_matchall->ingress.packets = packets; |
| tc_port_matchall->ingress.bytes = bytes; |
| tc_port_matchall->ingress.last_used = jiffies; |
| } |
| |
| return 0; |
| } |
| |
| static void cxgb4_matchall_disable_offload(struct net_device *dev) |
| { |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct port_info *pi = netdev2pinfo(dev); |
| struct adapter *adap = netdev2adap(dev); |
| |
| tc_port_matchall = &adap->tc_matchall->port_matchall[pi->port_id]; |
| if (tc_port_matchall->egress.state == CXGB4_MATCHALL_STATE_ENABLED) |
| cxgb4_matchall_free_tc(dev); |
| |
| if (tc_port_matchall->ingress.state == CXGB4_MATCHALL_STATE_ENABLED) |
| cxgb4_matchall_free_filter(dev); |
| } |
| |
| int cxgb4_init_tc_matchall(struct adapter *adap) |
| { |
| struct cxgb4_tc_port_matchall *tc_port_matchall; |
| struct cxgb4_tc_matchall *tc_matchall; |
| int ret; |
| |
| tc_matchall = kzalloc(sizeof(*tc_matchall), GFP_KERNEL); |
| if (!tc_matchall) |
| return -ENOMEM; |
| |
| tc_port_matchall = kcalloc(adap->params.nports, |
| sizeof(*tc_port_matchall), |
| GFP_KERNEL); |
| if (!tc_port_matchall) { |
| ret = -ENOMEM; |
| goto out_free_matchall; |
| } |
| |
| tc_matchall->port_matchall = tc_port_matchall; |
| adap->tc_matchall = tc_matchall; |
| return 0; |
| |
| out_free_matchall: |
| kfree(tc_matchall); |
| return ret; |
| } |
| |
| void cxgb4_cleanup_tc_matchall(struct adapter *adap) |
| { |
| u8 i; |
| |
| if (adap->tc_matchall) { |
| if (adap->tc_matchall->port_matchall) { |
| for (i = 0; i < adap->params.nports; i++) { |
| struct net_device *dev = adap->port[i]; |
| |
| if (dev) |
| cxgb4_matchall_disable_offload(dev); |
| } |
| kfree(adap->tc_matchall->port_matchall); |
| } |
| kfree(adap->tc_matchall); |
| } |
| } |