net/enic: support flow counter action

Support counter action for 1400 series adapters.

The adapter API for allocating and freeing counters is independent of
the adapter match/action API. If the filter action is requested, a
counter is first allocated and then assigned to the filter, and when
the filter is deleted, the counter must also be deleted.

Counters are DMAd to pre-allocated consistent memory periodically,
controlled by the define VNIC_FLOW_COUNTER_UPDATE_MSECS. The default is
100 milliseconds.

Signed-off-by: John Daley <johndale@cisco.com>
Reviewed-by: Hyong Youb Kim <hyonkim@cisco.com>
This commit is contained in:
John Daley 2018-09-27 20:08:37 -07:00 committed by Ferruh Yigit
parent 85b0ccec38
commit 86df6c4e2f
8 changed files with 337 additions and 25 deletions

View File

@ -260,6 +260,12 @@ Generic Flow API is supported. The baseline support is:
- Selectors: 'is', 'spec' and 'mask'. 'last' is not supported
- In total, up to 64 bytes of mask is allowed across all headers
- **1400 and later series VICS with advanced filters enabled**
All the above plus:
- Action: count
More features may be added in future firmware and new versions of the VIC.
Please refer to the release notes.

View File

@ -57,6 +57,8 @@ struct vnic_dev {
void (*free_consistent)(void *priv,
size_t size, void *vaddr,
dma_addr_t dma_handle);
struct vnic_counter_counts *flow_counters;
dma_addr_t flow_counters_pa;
};
#define VNIC_MAX_RES_HDR_SIZE \
@ -64,6 +66,8 @@ struct vnic_dev {
sizeof(struct vnic_resource) * RES_TYPE_MAX)
#define VNIC_RES_STRIDE 128
#define VNIC_MAX_FLOW_COUNTERS 2048
void *vnic_dev_priv(struct vnic_dev *vdev)
{
return vdev->priv;
@ -611,6 +615,23 @@ int vnic_dev_stats_dump(struct vnic_dev *vdev, struct vnic_stats **stats)
return vnic_dev_cmd(vdev, CMD_STATS_DUMP, &a0, &a1, wait);
}
/*
* Configure counter DMA
*/
int vnic_dev_counter_dma_cfg(struct vnic_dev *vdev, u32 period, u32 counter_idx)
{
u64 args[3];
int wait = 1000;
if (!vdev->flow_counters || counter_idx >= VNIC_MAX_FLOW_COUNTERS)
return -ENOMEM;
args[0] = counter_idx + 1;
args[1] = vdev->flow_counters_pa;
args[2] = period;
return vnic_dev_cmd_args(vdev, CMD_COUNTER_DMA_CONFIG, args, 3, wait);
}
int vnic_dev_close(struct vnic_dev *vdev)
{
u64 a0 = 0, a1 = 0;
@ -939,6 +960,23 @@ int vnic_dev_alloc_stats_mem(struct vnic_dev *vdev)
return vdev->stats == NULL ? -ENOMEM : 0;
}
/*
* Initialize for up to VNIC_MAX_FLOW_COUNTERS
*/
int vnic_dev_alloc_counter_mem(struct vnic_dev *vdev)
{
char name[NAME_MAX];
static u32 instance;
snprintf((char *)name, sizeof(name), "vnic_flow_ctrs-%u", instance++);
vdev->flow_counters = vdev->alloc_consistent(vdev->priv,
sizeof(struct vnic_counter_counts)
* VNIC_MAX_FLOW_COUNTERS,
&vdev->flow_counters_pa,
(u8 *)name);
return vdev->flow_counters == NULL ? -ENOMEM : 0;
}
void vnic_dev_unregister(struct vnic_dev *vdev)
{
if (vdev) {
@ -951,6 +989,15 @@ void vnic_dev_unregister(struct vnic_dev *vdev)
vdev->free_consistent(vdev->priv,
sizeof(struct vnic_stats),
vdev->stats, vdev->stats_pa);
if (vdev->flow_counters) {
/* turn off counter DMAs before freeing memory */
vnic_dev_counter_dma_cfg(vdev, 0, 0);
vdev->free_consistent(vdev->priv,
sizeof(struct vnic_counter_counts)
* VNIC_MAX_FLOW_COUNTERS,
vdev->flow_counters, vdev->flow_counters_pa);
}
if (vdev->fw_info)
vdev->free_consistent(vdev->priv,
sizeof(struct vnic_devcmd_fw_info),
@ -1094,3 +1141,49 @@ int vnic_dev_capable_vxlan(struct vnic_dev *vdev)
(a1 & (FEATURE_VXLAN_IPV6 | FEATURE_VXLAN_MULTI_WQ)) ==
(FEATURE_VXLAN_IPV6 | FEATURE_VXLAN_MULTI_WQ);
}
bool vnic_dev_counter_alloc(struct vnic_dev *vdev, uint32_t *idx)
{
u64 a0 = 0;
u64 a1 = 0;
int wait = 1000;
if (vnic_dev_cmd(vdev, CMD_COUNTER_ALLOC, &a0, &a1, wait))
return false;
*idx = (uint32_t)a0;
return true;
}
bool vnic_dev_counter_free(struct vnic_dev *vdev, uint32_t idx)
{
u64 a0 = idx;
u64 a1 = 0;
int wait = 1000;
return vnic_dev_cmd(vdev, CMD_COUNTER_FREE, &a0, &a1,
wait) == 0;
}
bool vnic_dev_counter_query(struct vnic_dev *vdev, uint32_t idx,
bool reset, uint64_t *packets, uint64_t *bytes)
{
u64 a0 = idx;
u64 a1 = reset ? 1 : 0;
int wait = 1000;
if (vdev->flow_counters) {
/* Using counter DMA API, so counters avail in host memory */
*packets = vdev->flow_counters[idx].vcc_packets;
*bytes = vdev->flow_counters[idx].vcc_bytes;
if (reset)
if (vnic_dev_cmd(vdev, CMD_COUNTER_QUERY, &a0, &a1,
wait))
return false;
} else {
if (vnic_dev_cmd(vdev, CMD_COUNTER_QUERY, &a0, &a1, wait))
return false;
*packets = a0;
*bytes = a1;
}
return true;
}

View File

@ -118,6 +118,8 @@ int vnic_dev_spec(struct vnic_dev *vdev, unsigned int offset, size_t size,
void *value);
int vnic_dev_stats_clear(struct vnic_dev *vdev);
int vnic_dev_stats_dump(struct vnic_dev *vdev, struct vnic_stats **stats);
int vnic_dev_counter_dma_cfg(struct vnic_dev *vdev, u32 period,
u32 counter_idx);
int vnic_dev_hang_notify(struct vnic_dev *vdev);
int vnic_dev_packet_filter(struct vnic_dev *vdev, int directed, int multicast,
int broadcast, int promisc, int allmulti);
@ -170,6 +172,7 @@ struct vnic_dev *vnic_dev_register(struct vnic_dev *vdev,
unsigned int num_bars);
struct rte_pci_device *vnic_dev_get_pdev(struct vnic_dev *vdev);
int vnic_dev_alloc_stats_mem(struct vnic_dev *vdev);
int vnic_dev_alloc_counter_mem(struct vnic_dev *vdev);
int vnic_dev_cmd_init(struct vnic_dev *vdev, int fallback);
int vnic_dev_get_size(void);
int vnic_dev_int13(struct vnic_dev *vdev, u64 arg, u32 op);
@ -187,4 +190,9 @@ int vnic_dev_overlay_offload_ctrl(struct vnic_dev *vdev,
int vnic_dev_overlay_offload_cfg(struct vnic_dev *vdev, u8 overlay,
u16 vxlan_udp_port_number);
int vnic_dev_capable_vxlan(struct vnic_dev *vdev);
bool vnic_dev_counter_alloc(struct vnic_dev *vdev, uint32_t *idx);
bool vnic_dev_counter_free(struct vnic_dev *vdev, uint32_t idx);
bool vnic_dev_counter_query(struct vnic_dev *vdev, uint32_t idx,
bool reset, uint64_t *packets, uint64_t *bytes);
#endif /* _VNIC_DEV_H_ */

View File

@ -598,6 +598,47 @@ enum vnic_devcmd_cmd {
* a3 = bitmask of supported actions
*/
CMD_ADD_ADV_FILTER = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ENET, 77),
/*
* Allocate a counter for use with CMD_ADD_FILTER
* out:(u32) a0 = counter index
*/
CMD_COUNTER_ALLOC = _CMDC(_CMD_DIR_READ, _CMD_VTYPE_ENET, 85),
/*
* Free a counter
* in: (u32) a0 = counter_id
*/
CMD_COUNTER_FREE = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 86),
/*
* Read a counter
* in: (u32) a0 = counter_id
* (u32) a1 = clear counter if non-zero
* out:(u64) a0 = packet count
* (u64) a1 = byte count
*/
CMD_COUNTER_QUERY = _CMDC(_CMD_DIR_RW, _CMD_VTYPE_ENET, 87),
/*
* Configure periodic counter DMA. This will trigger an immediate
* DMA of the counters (unless period == 0), and then schedule a DMA
* of the counters every <period> seconds until disdabled.
* Each new COUNTER_DMA_CONFIG will override all previous commands on
* this vnic.
* Setting a2 (period) = 0 will disable periodic DMAs
* If a0 (num_counters) != 0, an immediate DMA will always be done,
* irrespective of the value in a2.
* in: (u32) a0 = number of counters to DMA
* (u64) a1 = host target DMA address
* (u32) a2 = DMA period in milliseconds (0 to disable)
*/
CMD_COUNTER_DMA_CONFIG = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 88),
/*
* Clear all counters on a vnic
*/
CMD_COUNTER_CLEAR_ALL = _CMDC(_CMD_DIR_NONE, _CMD_VTYPE_ENET, 89),
};
/* Modes for exchanging advanced filter capabilities. The modes supported by
@ -863,9 +904,11 @@ struct filter_action {
#define FILTER_ACTION_RQ_STEERING_FLAG (1 << 0)
#define FILTER_ACTION_FILTER_ID_FLAG (1 << 1)
#define FILTER_ACTION_DROP_FLAG (1 << 2)
#define FILTER_ACTION_COUNTER_FLAG (1 << 3)
#define FILTER_ACTION_V2_ALL (FILTER_ACTION_RQ_STEERING_FLAG \
| FILTER_ACTION_FILTER_ID_FLAG \
| FILTER_ACTION_DROP_FLAG \
| FILTER_ACTION_FILTER_ID_FLAG)
| FILTER_ACTION_COUNTER_FLAG)
/* Version 2 of filter action must be a strict extension of struct filter_action
* where the first fields exactly match in size and meaning.
@ -875,7 +918,8 @@ struct filter_action_v2 {
u32 rq_idx;
u32 flags; /* use FILTER_ACTION_XXX_FLAG defines */
u16 filter_id;
uint8_t reserved[32]; /* for future expansion */
u32 counter_index;
uint8_t reserved[28]; /* for future expansion */
} __attribute__((packed));
/* Specifies the filter type. */
@ -1122,4 +1166,13 @@ typedef enum {
GRPINTR_UPD_VECT,
} grpintr_subcmd_t;
/*
* Structure for counter DMA
* (DMAed by CMD_COUNTER_DMA_CONFIG)
*/
struct vnic_counter_counts {
u64 vcc_packets;
u64 vcc_bytes;
};
#endif /* _VNIC_DEVCMD_H_ */

View File

@ -38,6 +38,7 @@
#define ENIC_PAGE_SIZE 4096
#define PAGE_ROUND_UP(x) \
((((unsigned long)(x)) + ENIC_PAGE_SIZE-1) & (~(ENIC_PAGE_SIZE-1)))
#define VNIC_FLOW_COUNTER_UPDATE_MSECS 100
#define ENICPMD_VFIO_PATH "/dev/vfio/vfio"
/*#define ENIC_DESC_COUNT_MAKE_ODD (x) do{if ((~(x)) & 1) { (x)--; } }while(0)*/
@ -94,6 +95,7 @@ struct rte_flow {
LIST_ENTRY(rte_flow) next;
u16 enic_filter_id;
struct filter_v2 enic_filter;
int counter_idx; /* NIC allocated counter index (-1 = invalid) */
};
/* Per-instance private data structure */
@ -165,6 +167,7 @@ struct enic {
rte_spinlock_t mtu_lock;
LIST_HEAD(enic_flows, rte_flow) flows;
int max_flow_counter;
rte_spinlock_t flows_lock;
/* RSS */

View File

@ -289,6 +289,15 @@ static const enum rte_flow_action_type enic_supported_actions_v2_drop[] = {
RTE_FLOW_ACTION_TYPE_END,
};
static const enum rte_flow_action_type enic_supported_actions_v2_count[] = {
RTE_FLOW_ACTION_TYPE_QUEUE,
RTE_FLOW_ACTION_TYPE_MARK,
RTE_FLOW_ACTION_TYPE_FLAG,
RTE_FLOW_ACTION_TYPE_DROP,
RTE_FLOW_ACTION_TYPE_COUNT,
RTE_FLOW_ACTION_TYPE_END,
};
/** Action capabilities indexed by NIC version information */
static const struct enic_action_cap enic_action_cap[] = {
[FILTER_ACTION_RQ_STEERING_FLAG] = {
@ -303,6 +312,10 @@ static const struct enic_action_cap enic_action_cap[] = {
.actions = enic_supported_actions_v2_drop,
.copy_fn = enic_copy_action_v2,
},
[FILTER_ACTION_COUNTER_FLAG] = {
.actions = enic_supported_actions_v2_count,
.copy_fn = enic_copy_action_v2,
},
};
static int
@ -1068,6 +1081,10 @@ enic_copy_action_v2(const struct rte_flow_action actions[],
enic_action->flags |= FILTER_ACTION_DROP_FLAG;
break;
}
case RTE_FLOW_ACTION_TYPE_COUNT: {
enic_action->flags |= FILTER_ACTION_COUNTER_FLAG;
break;
}
case RTE_FLOW_ACTION_TYPE_VOID:
continue;
default:
@ -1112,7 +1129,9 @@ enic_get_action_cap(struct enic *enic)
uint8_t actions;
actions = enic->filter_actions;
if (actions & FILTER_ACTION_DROP_FLAG)
if (actions & FILTER_ACTION_COUNTER_FLAG)
ea = &enic_action_cap[FILTER_ACTION_COUNTER_FLAG];
else if (actions & FILTER_ACTION_DROP_FLAG)
ea = &enic_action_cap[FILTER_ACTION_DROP_FLAG];
else if (actions & FILTER_ACTION_FILTER_ID_FLAG)
ea = &enic_action_cap[FILTER_ACTION_FILTER_ID_FLAG];
@ -1395,8 +1414,10 @@ enic_flow_add_filter(struct enic *enic, struct filter_v2 *enic_filter,
struct rte_flow_error *error)
{
struct rte_flow *flow;
int ret;
u16 entry;
int err;
uint16_t entry;
int ctr_idx;
int last_max_flow_ctr;
FLOW_TRACE();
@ -1407,20 +1428,64 @@ enic_flow_add_filter(struct enic *enic, struct filter_v2 *enic_filter,
return NULL;
}
flow->counter_idx = -1;
last_max_flow_ctr = -1;
if (enic_action->flags & FILTER_ACTION_COUNTER_FLAG) {
if (!vnic_dev_counter_alloc(enic->vdev, (uint32_t *)&ctr_idx)) {
rte_flow_error_set(error, ENOMEM,
RTE_FLOW_ERROR_TYPE_ACTION_CONF,
NULL, "cannot allocate counter");
goto unwind_flow_alloc;
}
flow->counter_idx = ctr_idx;
enic_action->counter_index = ctr_idx;
/* If index is the largest, increase the counter DMA size */
if (ctr_idx > enic->max_flow_counter) {
err = vnic_dev_counter_dma_cfg(enic->vdev,
VNIC_FLOW_COUNTER_UPDATE_MSECS,
ctr_idx);
if (err) {
rte_flow_error_set(error, -err,
RTE_FLOW_ERROR_TYPE_ACTION_CONF,
NULL, "counter DMA config failed");
goto unwind_ctr_alloc;
}
last_max_flow_ctr = enic->max_flow_counter;
enic->max_flow_counter = ctr_idx;
}
}
/* entry[in] is the queue id, entry[out] is the filter Id for delete */
entry = enic_action->rq_idx;
ret = vnic_dev_classifier(enic->vdev, CLSF_ADD, &entry, enic_filter,
err = vnic_dev_classifier(enic->vdev, CLSF_ADD, &entry, enic_filter,
enic_action);
if (!ret) {
flow->enic_filter_id = entry;
flow->enic_filter = *enic_filter;
} else {
rte_flow_error_set(error, ret, RTE_FLOW_ERROR_TYPE_HANDLE,
if (err) {
rte_flow_error_set(error, -err, RTE_FLOW_ERROR_TYPE_HANDLE,
NULL, "vnic_dev_classifier error");
rte_free(flow);
return NULL;
goto unwind_ctr_dma_cfg;
}
flow->enic_filter_id = entry;
flow->enic_filter = *enic_filter;
return flow;
/* unwind if there are errors */
unwind_ctr_dma_cfg:
if (last_max_flow_ctr != -1) {
/* reduce counter DMA size */
vnic_dev_counter_dma_cfg(enic->vdev,
VNIC_FLOW_COUNTER_UPDATE_MSECS,
last_max_flow_ctr);
enic->max_flow_counter = last_max_flow_ctr;
}
unwind_ctr_alloc:
if (flow->counter_idx != -1)
vnic_dev_counter_free(enic->vdev, ctr_idx);
unwind_flow_alloc:
rte_free(flow);
return NULL;
}
/**
@ -1435,18 +1500,29 @@ enic_flow_add_filter(struct enic *enic, struct filter_v2 *enic_filter,
* @param error[out]
*/
static int
enic_flow_del_filter(struct enic *enic, u16 filter_id,
enic_flow_del_filter(struct enic *enic, struct rte_flow *flow,
struct rte_flow_error *error)
{
int ret;
u16 filter_id;
int err;
FLOW_TRACE();
ret = vnic_dev_classifier(enic->vdev, CLSF_DEL, &filter_id, NULL, NULL);
if (!ret)
rte_flow_error_set(error, ret, RTE_FLOW_ERROR_TYPE_HANDLE,
filter_id = flow->enic_filter_id;
err = vnic_dev_classifier(enic->vdev, CLSF_DEL, &filter_id, NULL, NULL);
if (err) {
rte_flow_error_set(error, -err, RTE_FLOW_ERROR_TYPE_HANDLE,
NULL, "vnic_dev_classifier failed");
return ret;
return -err;
}
if (flow->counter_idx != -1) {
if (!vnic_dev_counter_free(enic->vdev, flow->counter_idx))
dev_err(enic, "counter free failed, idx: %d\n",
flow->counter_idx);
flow->counter_idx = -1;
}
return 0;
}
/*
@ -1529,7 +1605,7 @@ enic_flow_destroy(struct rte_eth_dev *dev, struct rte_flow *flow,
FLOW_TRACE();
rte_spinlock_lock(&enic->flows_lock);
enic_flow_del_filter(enic, flow->enic_filter_id, error);
enic_flow_del_filter(enic, flow, error);
LIST_REMOVE(flow, next);
rte_spinlock_unlock(&enic->flows_lock);
rte_free(flow);
@ -1554,7 +1630,7 @@ enic_flow_flush(struct rte_eth_dev *dev, struct rte_flow_error *error)
while (!LIST_EMPTY(&enic->flows)) {
flow = LIST_FIRST(&enic->flows);
enic_flow_del_filter(enic, flow->enic_filter_id, error);
enic_flow_del_filter(enic, flow, error);
LIST_REMOVE(flow, next);
rte_free(flow);
}
@ -1562,6 +1638,69 @@ enic_flow_flush(struct rte_eth_dev *dev, struct rte_flow_error *error)
return 0;
}
static int
enic_flow_query_count(struct rte_eth_dev *dev,
struct rte_flow *flow, void *data,
struct rte_flow_error *error)
{
struct enic *enic = pmd_priv(dev);
struct rte_flow_query_count *query;
uint64_t packets, bytes;
FLOW_TRACE();
if (flow->counter_idx == -1) {
return rte_flow_error_set(error, ENOTSUP,
RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
NULL,
"flow does not have counter");
}
query = (struct rte_flow_query_count *)data;
if (!vnic_dev_counter_query(enic->vdev, flow->counter_idx,
!!query->reset, &packets, &bytes)) {
return rte_flow_error_set
(error, EINVAL,
RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
NULL,
"cannot read counter");
}
query->hits_set = 1;
query->bytes_set = 1;
query->hits = packets;
query->bytes = bytes;
return 0;
}
static int
enic_flow_query(struct rte_eth_dev *dev,
struct rte_flow *flow,
const struct rte_flow_action *actions,
void *data,
struct rte_flow_error *error)
{
int ret = 0;
FLOW_TRACE();
for (; actions->type != RTE_FLOW_ACTION_TYPE_END; actions++) {
switch (actions->type) {
case RTE_FLOW_ACTION_TYPE_VOID:
break;
case RTE_FLOW_ACTION_TYPE_COUNT:
ret = enic_flow_query_count(dev, flow, data, error);
break;
default:
return rte_flow_error_set(error, ENOTSUP,
RTE_FLOW_ERROR_TYPE_ACTION,
actions,
"action not supported");
}
if (ret < 0)
return ret;
}
return 0;
}
/**
* Flow callback registration.
*
@ -1572,4 +1711,5 @@ const struct rte_flow_ops enic_flow_ops = {
.create = enic_flow_create,
.destroy = enic_flow_destroy,
.flush = enic_flow_flush,
.query = enic_flow_query,
};

View File

@ -1647,6 +1647,7 @@ static int enic_dev_init(struct enic *enic)
LIST_INIT(&enic->flows);
rte_spinlock_init(&enic->flows_lock);
enic->max_flow_counter = -1;
/* set up link status checking */
vnic_dev_notify_set(enic->vdev, -1); /* No Intr for notify */
@ -1729,14 +1730,20 @@ int enic_probe(struct enic *enic)
enic_free_consistent);
/*
* Allocate the consistent memory for stats upfront so both primary and
* secondary processes can dump stats.
* Allocate the consistent memory for stats and counters upfront so
* both primary and secondary processes can dump stats.
*/
err = vnic_dev_alloc_stats_mem(enic->vdev);
if (err) {
dev_err(enic, "Failed to allocate cmd memory, aborting\n");
goto err_out_unregister;
}
err = vnic_dev_alloc_counter_mem(enic->vdev);
if (err) {
dev_err(enic, "Failed to allocate counter memory, aborting\n");
goto err_out_unregister;
}
/* Issue device open to get device in known state */
err = enic_dev_open(enic);
if (err) {

View File

@ -85,7 +85,7 @@ int enic_get_vnic_config(struct enic *enic)
vnic_dev_capable_udp_rss_weak(enic->vdev, &enic->nic_cfg_chk,
&enic->udp_rss_weak);
dev_info(enic, "Flow api filter mode: %s Actions: %s%s%s\n",
dev_info(enic, "Flow api filter mode: %s Actions: %s%s%s%s\n",
((enic->flow_filter_mode == FILTER_DPDK_1) ? "DPDK" :
((enic->flow_filter_mode == FILTER_USNIC_IP) ? "USNIC" :
((enic->flow_filter_mode == FILTER_IPV4_5TUPLE) ? "5TUPLE" :
@ -95,7 +95,9 @@ int enic_get_vnic_config(struct enic *enic)
((enic->filter_actions & FILTER_ACTION_FILTER_ID_FLAG) ?
"tag " : ""),
((enic->filter_actions & FILTER_ACTION_DROP_FLAG) ?
"drop " : ""));
"drop " : ""),
((enic->filter_actions & FILTER_ACTION_COUNTER_FLAG) ?
"count " : ""));
c->wq_desc_count =
min_t(u32, ENIC_MAX_WQ_DESCS,