/* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) 2018 Marvell International Ltd. * Copyright(c) 2018 Semihalf. * All rights reserved. */ #include #include #include "mrvl_mtr.h" /** Maximum meter rate */ #define MRVL_SRTCM_RFC2697_CIR_MAX 1023000 /** Invalid plcr bit */ #define MRVL_PLCR_BIT_INVALID -1 /** * Return meter object capabilities. * * @param dev Pointer to the device (unused). * @param cap Pointer to the meter object capabilities. * @param error Pointer to the error (unused). * @returns 0 always. */ static int mrvl_capabilities_get(struct rte_eth_dev *dev __rte_unused, struct rte_mtr_capabilities *cap, struct rte_mtr_error *error __rte_unused) { struct rte_mtr_capabilities capa = { .n_max = PP2_CLS_PLCR_NUM, .n_shared_max = PP2_CLS_PLCR_NUM, .shared_n_flows_per_mtr_max = -1, .meter_srtcm_rfc2697_n_max = PP2_CLS_PLCR_NUM, .meter_rate_max = MRVL_SRTCM_RFC2697_CIR_MAX, }; memcpy(cap, &capa, sizeof(capa)); return 0; } /** * Get profile using it's id. * * @param priv Pointer to the port's private data. * @param meter_profile_id Profile id used by the meter. * @returns Pointer to the profile if exists, NULL otherwise. */ static struct mrvl_mtr_profile * mrvl_mtr_profile_from_id(struct mrvl_priv *priv, uint32_t meter_profile_id) { struct mrvl_mtr_profile *profile = NULL; LIST_FOREACH(profile, &priv->profiles, next) if (profile->profile_id == meter_profile_id) break; return profile; } /** * Add profile to the list of profiles. * * @param dev Pointer to the device. * @param meter_profile_id Id of the new profile. * @param profile Pointer to the profile configuration. * @param error Pointer to the error. * @returns 0 on success, negative value otherwise. */ static int mrvl_meter_profile_add(struct rte_eth_dev *dev, uint32_t meter_profile_id, struct rte_mtr_meter_profile *profile, struct rte_mtr_error *error) { struct mrvl_priv *priv = dev->data->dev_private; struct mrvl_mtr_profile *prof; if (!profile) return -rte_mtr_error_set(error, EINVAL, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, NULL); if (profile->alg != RTE_MTR_SRTCM_RFC2697) return -rte_mtr_error_set(error, EINVAL, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, "Only srTCM RFC 2697 is supported\n"); if (profile->packet_mode) return -rte_mtr_error_set(error, EINVAL, RTE_MTR_ERROR_TYPE_METER_PROFILE_PACKET_MODE, NULL, "Packet mode is not supported\n"); prof = mrvl_mtr_profile_from_id(priv, meter_profile_id); if (prof) return -rte_mtr_error_set(error, EEXIST, RTE_MTR_ERROR_TYPE_METER_PROFILE_ID, NULL, "Profile id already exists\n"); prof = rte_zmalloc_socket(NULL, sizeof(*prof), 0, rte_socket_id()); if (!prof) return -rte_mtr_error_set(error, ENOMEM, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, NULL); prof->profile_id = meter_profile_id; memcpy(&prof->profile, profile, sizeof(*profile)); LIST_INSERT_HEAD(&priv->profiles, prof, next); return 0; } /** * Remove profile from the list of profiles. * * @param dev Pointer to the device. * @param meter_profile_id Id of the profile to remove. * @param error Pointer to the error. * @returns 0 on success, negative value otherwise. */ static int mrvl_meter_profile_delete(struct rte_eth_dev *dev, uint32_t meter_profile_id, struct rte_mtr_error *error) { struct mrvl_priv *priv = dev->data->dev_private; struct mrvl_mtr_profile *profile; profile = mrvl_mtr_profile_from_id(priv, meter_profile_id); if (!profile) return -rte_mtr_error_set(error, ENODEV, RTE_MTR_ERROR_TYPE_METER_PROFILE_ID, NULL, "Profile id does not exist\n"); if (profile->refcnt) return -rte_mtr_error_set(error, EPERM, RTE_MTR_ERROR_TYPE_METER_PROFILE_ID, NULL, "Profile is used\n"); LIST_REMOVE(profile, next); rte_free(profile); return 0; } /** * Get meter using it's id. * * @param priv Pointer to port's private data. * @param mtr_id Id of the meter. * @returns Pointer to the meter if exists, NULL otherwise. */ static struct mrvl_mtr * mrvl_mtr_from_id(struct mrvl_priv *priv, uint32_t mtr_id) { struct mrvl_mtr *mtr = NULL; LIST_FOREACH(mtr, &priv->mtrs, next) if (mtr->mtr_id == mtr_id) break; return mtr; } /** * Reserve a policer bit in a bitmap. * * @param plcrs Pointer to the policers bitmap. * @returns Reserved bit number on success, negative value otherwise. */ static int mrvl_reserve_plcr(uint32_t *plcrs) { uint32_t i, num; num = PP2_CLS_PLCR_NUM; if (num > sizeof(uint32_t) * 8) { num = sizeof(uint32_t) * 8; MRVL_LOG(WARNING, "Plcrs number was limited to 32."); } for (i = 0; i < num; i++) { uint32_t bit = BIT(i); if (!(*plcrs & bit)) { *plcrs |= bit; return i; } } return -1; } /** * Enable meter object. * * @param dev Pointer to the device. * @param mtr_id Id of the meter. * @param error Pointer to the error. * @returns 0 in success, negative value otherwise. */ static int mrvl_meter_enable(struct rte_eth_dev *dev, uint32_t mtr_id, struct rte_mtr_error *error) { struct mrvl_priv *priv = dev->data->dev_private; struct mrvl_mtr *mtr = mrvl_mtr_from_id(priv, mtr_id); struct pp2_cls_plcr_params params; char match[MRVL_MATCH_LEN]; struct rte_flow *flow; int ret; if (!priv->ppio) return -rte_mtr_error_set(error, EPERM, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, "Port is uninitialized\n"); if (!mtr) return -rte_mtr_error_set(error, ENODEV, RTE_MTR_ERROR_TYPE_MTR_ID, NULL, "Meter id does not exist\n"); if (mtr->plcr) goto skip; mtr->plcr_bit = mrvl_reserve_plcr(&priv->used_plcrs); if (mtr->plcr_bit < 0) return -rte_mtr_error_set(error, ENOSPC, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, "Failed to reserve plcr entry\n"); memset(¶ms, 0, sizeof(params)); snprintf(match, sizeof(match), "policer-%d:%d", priv->pp_id, mtr->plcr_bit); params.match = match; params.token_unit = PP2_CLS_PLCR_BYTES_TOKEN_UNIT; params.color_mode = PP2_CLS_PLCR_COLOR_BLIND_MODE; params.cir = mtr->profile->profile.srtcm_rfc2697.cir; params.cbs = mtr->profile->profile.srtcm_rfc2697.cbs; params.ebs = mtr->profile->profile.srtcm_rfc2697.ebs; ret = pp2_cls_plcr_init(¶ms, &mtr->plcr); if (ret) { priv->used_plcrs &= ~BIT(mtr->plcr_bit); mtr->plcr_bit = MRVL_PLCR_BIT_INVALID; return -rte_mtr_error_set(error, -ret, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, "Failed to setup policer\n"); } mtr->enabled = 1; skip: /* iterate over flows that have this mtr attached */ LIST_FOREACH(flow, &priv->flows, next) { if (flow->mtr != mtr) continue; flow->action.plcr = mtr->plcr; ret = pp2_cls_tbl_modify_rule(priv->cls_tbl, &flow->rule, &flow->action); if (ret) return -rte_mtr_error_set(error, -ret, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, "Failed to update cls rule\n"); } return 0; } /** * Disable meter object. * * @param dev Pointer to the device. * @param mtr Id of the meter. * @param error Pointer to the error. * @returns 0 on success, negative value otherwise. */ static int mrvl_meter_disable(struct rte_eth_dev *dev, uint32_t mtr_id, struct rte_mtr_error *error) { struct mrvl_priv *priv = dev->data->dev_private; struct mrvl_mtr *mtr = mrvl_mtr_from_id(priv, mtr_id); struct rte_flow *flow; int ret; if (!mtr) return -rte_mtr_error_set(error, ENODEV, RTE_MTR_ERROR_TYPE_MTR_ID, NULL, "Meter id does not exist\n"); LIST_FOREACH(flow, &priv->flows, next) { if (flow->mtr != mtr) continue; flow->action.plcr = NULL; ret = pp2_cls_tbl_modify_rule(priv->cls_tbl, &flow->rule, &flow->action); if (ret) return -rte_mtr_error_set(error, -ret, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, "Failed to disable meter\n"); } mtr->enabled = 0; return 0; } /** * Create new meter. * * @param dev Pointer to the device. * @param mtr_id Id of the meter. * @param params Pointer to the meter parameters. * @param shared Flags indicating whether meter is shared. * @param error Pointer to the error. * @returns 0 on success, negative value otherwise. */ static int mrvl_create(struct rte_eth_dev *dev, uint32_t mtr_id, struct rte_mtr_params *params, int shared, struct rte_mtr_error *error) { struct mrvl_priv *priv = dev->data->dev_private; struct mrvl_mtr_profile *profile; struct mrvl_mtr *mtr; profile = mrvl_mtr_profile_from_id(priv, params->meter_profile_id); if (!profile) return -rte_mtr_error_set(error, EINVAL, RTE_MTR_ERROR_TYPE_METER_PROFILE_ID, NULL, "Profile id does not exist\n"); mtr = mrvl_mtr_from_id(priv, mtr_id); if (mtr) return -rte_mtr_error_set(error, EEXIST, RTE_MTR_ERROR_TYPE_MTR_ID, NULL, "Meter id already exists\n"); mtr = rte_zmalloc_socket(NULL, sizeof(*mtr), 0, rte_socket_id()); if (!mtr) return -rte_mtr_error_set(error, ENOMEM, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, NULL); mtr->shared = shared; mtr->mtr_id = mtr_id; mtr->plcr_bit = MRVL_PLCR_BIT_INVALID; mtr->profile = profile; profile->refcnt++; LIST_INSERT_HEAD(&priv->mtrs, mtr, next); if (params->meter_enable) return mrvl_meter_enable(dev, mtr_id, error); return 0; } /** * Destroy meter object. * * @param dev Pointer to the device. * @param mtr_id Id of the meter object. * @param error Pointer to the error. * @returns 0 on success, negative value otherwise. */ static int mrvl_destroy(struct rte_eth_dev *dev, uint32_t mtr_id, struct rte_mtr_error *error) { struct mrvl_priv *priv = dev->data->dev_private; struct mrvl_mtr *mtr; if (!priv->ppio) return -rte_mtr_error_set(error, EPERM, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, "Port is uninitialized\n"); mtr = mrvl_mtr_from_id(priv, mtr_id); if (!mtr) return -rte_mtr_error_set(error, EEXIST, RTE_MTR_ERROR_TYPE_MTR_ID, NULL, "Meter id does not exist\n"); if (mtr->refcnt) return -rte_mtr_error_set(error, EPERM, RTE_MTR_ERROR_TYPE_MTR_ID, NULL, "Meter is used\n"); LIST_REMOVE(mtr, next); mtr->profile->refcnt--; if (mtr->plcr_bit != MRVL_PLCR_BIT_INVALID) priv->used_plcrs &= ~BIT(mtr->plcr_bit); if (mtr->plcr) pp2_cls_plcr_deinit(mtr->plcr); rte_free(mtr); return 0; } /** * Update profile used by the meter. * * @param dev Pointer to the device. * @param mtr_id Id of the meter object. * @param error Pointer to the error. * @returns 0 on success, negative value otherwise. */ static int mrvl_meter_profile_update(struct rte_eth_dev *dev, uint32_t mtr_id, uint32_t meter_profile_id, struct rte_mtr_error *error) { struct mrvl_priv *priv = dev->data->dev_private; struct mrvl_mtr_profile *profile; struct mrvl_mtr *mtr; int ret, enabled = 0; if (!priv->ppio) return -rte_mtr_error_set(error, EPERM, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, "Port is uninitialized\n"); mtr = mrvl_mtr_from_id(priv, mtr_id); if (!mtr) return -rte_mtr_error_set(error, EEXIST, RTE_MTR_ERROR_TYPE_MTR_ID, NULL, "Meter id does not exist\n"); profile = mrvl_mtr_profile_from_id(priv, meter_profile_id); if (!profile) return -rte_mtr_error_set(error, EINVAL, RTE_MTR_ERROR_TYPE_METER_PROFILE_ID, NULL, "Profile id does not exist\n"); ret = mrvl_meter_disable(dev, mtr_id, error); if (ret) return -rte_mtr_error_set(error, EPERM, RTE_MTR_ERROR_TYPE_UNSPECIFIED, NULL, NULL); if (mtr->plcr) { enabled = 1; pp2_cls_plcr_deinit(mtr->plcr); mtr->plcr = NULL; } mtr->profile->refcnt--; mtr->profile = profile; profile->refcnt++; if (enabled) return mrvl_meter_enable(dev, mtr_id, error); return 0; } const struct rte_mtr_ops mrvl_mtr_ops = { .capabilities_get = mrvl_capabilities_get, .meter_profile_add = mrvl_meter_profile_add, .meter_profile_delete = mrvl_meter_profile_delete, .create = mrvl_create, .destroy = mrvl_destroy, .meter_enable = mrvl_meter_enable, .meter_disable = mrvl_meter_disable, .meter_profile_update = mrvl_meter_profile_update, }; /** * Initialize metering resources. * * @param dev Pointer to the device. */ void mrvl_mtr_init(struct rte_eth_dev *dev) { struct mrvl_priv *priv = dev->data->dev_private; LIST_INIT(&priv->profiles); LIST_INIT(&priv->mtrs); } /** * Cleanup metering resources. * * @param dev Pointer to the device. */ void mrvl_mtr_deinit(struct rte_eth_dev *dev) { struct mrvl_priv *priv = dev->data->dev_private; struct mrvl_mtr_profile *profile, *tmp_profile; struct mrvl_mtr *mtr, *tmp_mtr; for (mtr = LIST_FIRST(&priv->mtrs); mtr && (tmp_mtr = LIST_NEXT(mtr, next), 1); mtr = tmp_mtr) mrvl_destroy(dev, mtr->mtr_id, NULL); for (profile = LIST_FIRST(&priv->profiles); profile && (tmp_profile = LIST_NEXT(profile, next), 1); profile = tmp_profile) mrvl_meter_profile_delete(dev, profile->profile_id, NULL); }