diff --git a/drivers/net/ethernet/sfc/mae.c b/drivers/net/ethernet/sfc/mae.c index 0cab508f2f9d..33ae2c852b44 100644 --- a/drivers/net/ethernet/sfc/mae.c +++ b/drivers/net/ethernet/sfc/mae.c @@ -227,6 +227,256 @@ void efx_mae_counters_grant_credits(struct work_struct *work) rx_queue->granted_count += credits; } +static int efx_mae_table_get_desc(struct efx_nic *efx, + struct efx_tc_table_desc *desc, + u32 table_id) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_TABLE_DESCRIPTOR_OUT_LEN(16)); + MCDI_DECLARE_BUF(inbuf, MC_CMD_TABLE_DESCRIPTOR_IN_LEN); + unsigned int offset = 0, i; + size_t outlen; + int rc; + + memset(desc, 0, sizeof(*desc)); + + MCDI_SET_DWORD(inbuf, TABLE_DESCRIPTOR_IN_TABLE_ID, table_id); +more: + MCDI_SET_DWORD(inbuf, TABLE_DESCRIPTOR_IN_FIRST_FIELDS_INDEX, offset); + rc = efx_mcdi_rpc(efx, MC_CMD_TABLE_DESCRIPTOR, inbuf, sizeof(inbuf), + outbuf, sizeof(outbuf), &outlen); + if (rc) + goto fail; + if (outlen < MC_CMD_TABLE_DESCRIPTOR_OUT_LEN(1)) { + rc = -EIO; + goto fail; + } + if (!offset) { /* first iteration: get metadata */ + desc->type = MCDI_WORD(outbuf, TABLE_DESCRIPTOR_OUT_TYPE); + desc->key_width = MCDI_WORD(outbuf, TABLE_DESCRIPTOR_OUT_KEY_WIDTH); + desc->resp_width = MCDI_WORD(outbuf, TABLE_DESCRIPTOR_OUT_RESP_WIDTH); + desc->n_keys = MCDI_WORD(outbuf, TABLE_DESCRIPTOR_OUT_N_KEY_FIELDS); + desc->n_resps = MCDI_WORD(outbuf, TABLE_DESCRIPTOR_OUT_N_RESP_FIELDS); + desc->n_prios = MCDI_WORD(outbuf, TABLE_DESCRIPTOR_OUT_N_PRIORITIES); + desc->flags = MCDI_BYTE(outbuf, TABLE_DESCRIPTOR_OUT_FLAGS); + rc = -EOPNOTSUPP; + if (desc->flags) + goto fail; + desc->scheme = MCDI_BYTE(outbuf, TABLE_DESCRIPTOR_OUT_SCHEME); + if (desc->scheme) + goto fail; + rc = -ENOMEM; + desc->keys = kcalloc(desc->n_keys, + sizeof(struct efx_tc_table_field_fmt), + GFP_KERNEL); + if (!desc->keys) + goto fail; + desc->resps = kcalloc(desc->n_resps, + sizeof(struct efx_tc_table_field_fmt), + GFP_KERNEL); + if (!desc->resps) + goto fail; + } + /* FW could have returned more than the 16 field_descrs we + * made room for in our outbuf + */ + outlen = min(outlen, sizeof(outbuf)); + for (i = 0; i + offset < desc->n_keys + desc->n_resps; i++) { + struct efx_tc_table_field_fmt *field; + MCDI_DECLARE_STRUCT_PTR(fdesc); + + if (outlen < MC_CMD_TABLE_DESCRIPTOR_OUT_LEN(i + 1)) { + offset += i; + goto more; + } + if (i + offset < desc->n_keys) + field = desc->keys + i + offset; + else + field = desc->resps + (i + offset - desc->n_keys); + fdesc = MCDI_ARRAY_STRUCT_PTR(outbuf, + TABLE_DESCRIPTOR_OUT_FIELDS, i); + field->field_id = MCDI_STRUCT_WORD(fdesc, + TABLE_FIELD_DESCR_FIELD_ID); + field->lbn = MCDI_STRUCT_WORD(fdesc, TABLE_FIELD_DESCR_LBN); + field->width = MCDI_STRUCT_WORD(fdesc, TABLE_FIELD_DESCR_WIDTH); + field->masking = MCDI_STRUCT_BYTE(fdesc, TABLE_FIELD_DESCR_MASK_TYPE); + field->scheme = MCDI_STRUCT_BYTE(fdesc, TABLE_FIELD_DESCR_SCHEME); + } + return 0; + +fail: + kfree(desc->keys); + kfree(desc->resps); + return rc; +} + +static int efx_mae_table_hook_find(u16 n_fields, + struct efx_tc_table_field_fmt *fields, + u16 field_id) +{ + unsigned int i; + + for (i = 0; i < n_fields; i++) { + if (fields[i].field_id == field_id) + return i; + } + return -EPROTO; +} + +#define TABLE_FIND_KEY(_desc, _id) \ + efx_mae_table_hook_find((_desc)->n_keys, (_desc)->keys, _id) +#define TABLE_FIND_RESP(_desc, _id) \ + efx_mae_table_hook_find((_desc)->n_resps, (_desc)->resps, _id) + +#define TABLE_HOOK_KEY(_meta, _name, _mcdi_name) ({ \ + int _rc = TABLE_FIND_KEY(&_meta->desc, TABLE_FIELD_ID_##_mcdi_name); \ + \ + if (_rc > U8_MAX) \ + _rc = -EOPNOTSUPP; \ + if (_rc >= 0) { \ + _meta->keys._name##_idx = _rc; \ + _rc = 0; \ + } \ + _rc; \ +}) +#define TABLE_HOOK_RESP(_meta, _name, _mcdi_name) ({ \ + int _rc = TABLE_FIND_RESP(&_meta->desc, TABLE_FIELD_ID_##_mcdi_name); \ + \ + if (_rc > U8_MAX) \ + _rc = -EOPNOTSUPP; \ + if (_rc >= 0) { \ + _meta->resps._name##_idx = _rc; \ + _rc = 0; \ + } \ + _rc; \ +}) + +static int efx_mae_table_hook_ct(struct efx_nic *efx, + struct efx_tc_table_ct *meta_ct) +{ + int rc; + + rc = TABLE_HOOK_KEY(meta_ct, eth_proto, ETHER_TYPE); + if (rc) + return rc; + rc = TABLE_HOOK_KEY(meta_ct, ip_proto, IP_PROTO); + if (rc) + return rc; + rc = TABLE_HOOK_KEY(meta_ct, src_ip, SRC_IP); + if (rc) + return rc; + rc = TABLE_HOOK_KEY(meta_ct, dst_ip, DST_IP); + if (rc) + return rc; + rc = TABLE_HOOK_KEY(meta_ct, l4_sport, SRC_PORT); + if (rc) + return rc; + rc = TABLE_HOOK_KEY(meta_ct, l4_dport, DST_PORT); + if (rc) + return rc; + rc = TABLE_HOOK_KEY(meta_ct, zone, DOMAIN); + if (rc) + return rc; + rc = TABLE_HOOK_RESP(meta_ct, dnat, NAT_DIR); + if (rc) + return rc; + rc = TABLE_HOOK_RESP(meta_ct, nat_ip, NAT_IP); + if (rc) + return rc; + rc = TABLE_HOOK_RESP(meta_ct, l4_natport, NAT_PORT); + if (rc) + return rc; + rc = TABLE_HOOK_RESP(meta_ct, mark, CT_MARK); + if (rc) + return rc; + rc = TABLE_HOOK_RESP(meta_ct, counter_id, COUNTER_ID); + if (rc) + return rc; + meta_ct->hooked = true; + return 0; +} + +static void efx_mae_table_free_desc(struct efx_tc_table_desc *desc) +{ + kfree(desc->keys); + kfree(desc->resps); + memset(desc, 0, sizeof(*desc)); +} + +static bool efx_mae_check_table_exists(struct efx_nic *efx, u32 tbl_req) +{ + MCDI_DECLARE_BUF(outbuf, MC_CMD_TABLE_LIST_OUT_LEN(16)); + MCDI_DECLARE_BUF(inbuf, MC_CMD_TABLE_LIST_IN_LEN); + u32 tbl_id, tbl_total, tbl_cnt, pos = 0; + size_t outlen, msg_max; + bool ct_tbl = false; + int rc, idx; + + msg_max = sizeof(outbuf); + efx->tc->meta_ct.hooked = false; +more: + memset(outbuf, 0, sizeof(*outbuf)); + MCDI_SET_DWORD(inbuf, TABLE_LIST_IN_FIRST_TABLE_ID_INDEX, pos); + rc = efx_mcdi_rpc(efx, MC_CMD_TABLE_LIST, inbuf, sizeof(inbuf), outbuf, + msg_max, &outlen); + if (rc) + return false; + + if (outlen < MC_CMD_TABLE_LIST_OUT_LEN(1)) + return false; + + tbl_total = MCDI_DWORD(outbuf, TABLE_LIST_OUT_N_TABLES); + tbl_cnt = MC_CMD_TABLE_LIST_OUT_TABLE_ID_NUM(min(outlen, msg_max)); + + for (idx = 0; idx < tbl_cnt; idx++) { + tbl_id = MCDI_ARRAY_DWORD(outbuf, TABLE_LIST_OUT_TABLE_ID, idx); + if (tbl_id == tbl_req) { + ct_tbl = true; + break; + } + } + + pos += tbl_cnt; + if (!ct_tbl && pos < tbl_total) + goto more; + + return ct_tbl; +} + +int efx_mae_get_tables(struct efx_nic *efx) +{ + int rc; + + efx->tc->meta_ct.hooked = false; + if (efx_mae_check_table_exists(efx, TABLE_ID_CONNTRACK_TABLE)) { + rc = efx_mae_table_get_desc(efx, &efx->tc->meta_ct.desc, + TABLE_ID_CONNTRACK_TABLE); + if (rc) { + pci_info(efx->pci_dev, + "FW does not support conntrack desc rc %d\n", + rc); + return 0; + } + + rc = efx_mae_table_hook_ct(efx, &efx->tc->meta_ct); + if (rc) { + pci_info(efx->pci_dev, + "FW does not support conntrack hook rc %d\n", + rc); + return 0; + } + } else { + pci_info(efx->pci_dev, + "FW does not support conntrack table\n"); + } + return 0; +} + +void efx_mae_free_tables(struct efx_nic *efx) +{ + efx_mae_table_free_desc(&efx->tc->meta_ct.desc); + efx->tc->meta_ct.hooked = false; +} + static int efx_mae_get_basic_caps(struct efx_nic *efx, struct mae_caps *caps) { MCDI_DECLARE_BUF(outbuf, MC_CMD_MAE_GET_CAPS_OUT_LEN); diff --git a/drivers/net/ethernet/sfc/mae.h b/drivers/net/ethernet/sfc/mae.h index 24abfe509690..afdf738254b2 100644 --- a/drivers/net/ethernet/sfc/mae.h +++ b/drivers/net/ethernet/sfc/mae.h @@ -66,6 +66,9 @@ int efx_mae_start_counters(struct efx_nic *efx, struct efx_rx_queue *rx_queue); int efx_mae_stop_counters(struct efx_nic *efx, struct efx_rx_queue *rx_queue); void efx_mae_counters_grant_credits(struct work_struct *work); +int efx_mae_get_tables(struct efx_nic *efx); +void efx_mae_free_tables(struct efx_nic *efx); + #define MAE_NUM_FIELDS (MAE_FIELD_ENC_VNET_ID + 1) struct mae_caps { diff --git a/drivers/net/ethernet/sfc/mcdi.h b/drivers/net/ethernet/sfc/mcdi.h index 454e9d51a4c2..995a26686fd8 100644 --- a/drivers/net/ethernet/sfc/mcdi.h +++ b/drivers/net/ethernet/sfc/mcdi.h @@ -221,6 +221,9 @@ void efx_mcdi_sensor_event(struct efx_nic *efx, efx_qword_t *ev); #define MCDI_BYTE(_buf, _field) \ ((void)BUILD_BUG_ON_ZERO(MC_CMD_ ## _field ## _LEN != 1), \ *MCDI_PTR(_buf, _field)) +#define MCDI_STRUCT_BYTE(_buf, _field) \ + ((void)BUILD_BUG_ON_ZERO(_field ## _LEN != 1), \ + *MCDI_STRUCT_PTR(_buf, _field)) #define MCDI_SET_WORD(_buf, _field, _value) do { \ BUILD_BUG_ON(MC_CMD_ ## _field ## _LEN != 2); \ BUILD_BUG_ON(MC_CMD_ ## _field ## _OFST & 1); \ diff --git a/drivers/net/ethernet/sfc/tc.c b/drivers/net/ethernet/sfc/tc.c index 4dc881159246..4dc979fdc968 100644 --- a/drivers/net/ethernet/sfc/tc.c +++ b/drivers/net/ethernet/sfc/tc.c @@ -1658,13 +1658,19 @@ int efx_init_tc(struct efx_nic *efx) if (rc) return rc; rc = efx_tc_configure_fallback_acts_reps(efx); + if (rc) + return rc; + rc = efx_mae_get_tables(efx); if (rc) return rc; efx->tc->up = true; rc = flow_indr_dev_register(efx_tc_indr_setup_cb, efx); if (rc) - return rc; + goto out_free; return 0; +out_free: + efx_mae_free_tables(efx); + return rc; } void efx_fini_tc(struct efx_nic *efx) @@ -1680,6 +1686,7 @@ void efx_fini_tc(struct efx_nic *efx) efx_tc_deconfigure_fallback_acts(efx, &efx->tc->facts.pf); efx_tc_deconfigure_fallback_acts(efx, &efx->tc->facts.reps); efx->tc->up = false; + efx_mae_free_tables(efx); } /* At teardown time, all TC filter rules (and thus all resources they created) diff --git a/drivers/net/ethernet/sfc/tc.h b/drivers/net/ethernet/sfc/tc.h index 1549c3df43bb..27592f10b536 100644 --- a/drivers/net/ethernet/sfc/tc.h +++ b/drivers/net/ethernet/sfc/tc.h @@ -143,6 +143,48 @@ enum efx_tc_rule_prios { EFX_TC_PRIO__NUM }; +struct efx_tc_table_field_fmt { + u16 field_id; + u16 lbn; + u16 width; + u8 masking; + u8 scheme; +}; + +struct efx_tc_table_desc { + u16 type; + u16 key_width; + u16 resp_width; + u16 n_keys; + u16 n_resps; + u16 n_prios; + u8 flags; + u8 scheme; + struct efx_tc_table_field_fmt *keys; + struct efx_tc_table_field_fmt *resps; +}; + +struct efx_tc_table_ct { /* TABLE_ID_CONNTRACK_TABLE */ + struct efx_tc_table_desc desc; + bool hooked; + struct { /* indices of named fields within @desc.keys */ + u8 eth_proto_idx; + u8 ip_proto_idx; + u8 src_ip_idx; /* either v4 or v6 */ + u8 dst_ip_idx; + u8 l4_sport_idx; + u8 l4_dport_idx; + u8 zone_idx; /* for TABLE_FIELD_ID_DOMAIN */ + } keys; + struct { /* indices of named fields within @desc.resps */ + u8 dnat_idx; + u8 nat_ip_idx; + u8 l4_natport_idx; + u8 mark_idx; + u8 counter_id_idx; + } resps; +}; + /** * struct efx_tc_state - control plane data for TC offload * @@ -155,6 +197,7 @@ enum efx_tc_rule_prios { * @encap_match_ht: Hashtable of TC encap matches * @match_action_ht: Hashtable of TC match-action rules * @neigh_ht: Hashtable of neighbour watches (&struct efx_neigh_binder) + * @meta_ct: MAE table layout for conntrack table * @reps_mport_id: MAE port allocated for representor RX * @reps_filter_uc: VNIC filter for representor unicast RX (promisc) * @reps_filter_mc: VNIC filter for representor multicast RX (allmulti) @@ -186,6 +229,7 @@ struct efx_tc_state { struct rhashtable encap_match_ht; struct rhashtable match_action_ht; struct rhashtable neigh_ht; + struct efx_tc_table_ct meta_ct; u32 reps_mport_id, reps_mport_vport_id; s32 reps_filter_uc, reps_filter_mc; bool flush_counters;