/* * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for * any purpose with or without fee is hereby granted, provided that the * above copyright notice and this permission notice appear in all * copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /** * DOC: wlan_hdd_mcc_quota.c * * WLAN Host Device Driver MCC quota feature cfg80211 APIs implementation * */ #include #include #include #include #include #include #include "osif_sync.h" #include #include #include "sme_api.h" #include "wlan_hdd_cfg80211.h" #include "wlan_hdd_hostapd.h" #include "wlan_hdd_main.h" #include "wlan_hdd_mcc_quota.h" #include "wlan_hdd_trace.h" #include "qdf_str.h" #include "qdf_trace.h" #include "qdf_types.h" #include "wlan_policy_mgr_api.h" #include #include "wlan_utility.h" #include "wlan_policy_mgr_ucfg.h" #include "wlan_mlme_ucfg_api.h" #include "wlan_mlme_public_struct.h" #include "wlan_hdd_object_manager.h" #include "sme_api.h" #include "wlan_p2p_ucfg_api.h" #include "wlan_osif_priv.h" #include "wlan_p2p_mcc_quota_public_struct.h" #include "wma.h" const struct nla_policy set_mcc_quota_policy[QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_MAX + 1] = { [QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_TYPE] = { .type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_ENTRIES] = VENDOR_NLA_POLICY_NESTED(set_mcc_quota_policy), [QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_CHAN_FREQ] = { .type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_CHAN_TIME_PERCENTAGE] = { .type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_IFINDEX] = { .type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_LOW_LATENCY_MODE_ENABLE] = { .type = NLA_U8 }, }; int wlan_hdd_set_mcc_adaptive_sched(struct wlan_objmgr_psoc *psoc, bool enable) { bool enable_mcc_adaptive_sch; hdd_debug("enable : %d", enable); ucfg_policy_mgr_get_mcc_adaptive_sch(psoc, &enable_mcc_adaptive_sch); if (enable_mcc_adaptive_sch) { ucfg_policy_mgr_set_dynamic_mcc_adaptive_sch(psoc, enable); if (QDF_IS_STATUS_ERROR(sme_set_mas(enable))) { hdd_err("Fail to config mcc adaptive sched."); return -EINVAL; } } return 0; } /** * wlan_hdd_set_mcc_fixed_quota() - Set/Clear MCC fix quota * @hdd_ctx: hdd context * @quota_type: quota type * @tb: attribute information * * Return: 0 on success, negative errno on failure */ static int wlan_hdd_set_mcc_fixed_quota(struct hdd_context *hdd_ctx, enum qca_wlan_vendor_mcc_quota_type quota_type, struct nlattr *tb[]) { struct hdd_adapter *if_adapter; struct nlattr *quota_entries[QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_MAX + 1]; struct nlattr *curr_attr; struct wlan_objmgr_psoc *psoc; uint32_t duty_cycle, cmd_id, rem_bytes, entries, if_idx; struct wlan_user_mcc_quota mcc_quota; int att_id, rc; hdd_enter(); if (wlan_hdd_validate_context(hdd_ctx)) return -EINVAL; psoc = hdd_ctx->psoc; if (!psoc) return -EINVAL; if (quota_type != QCA_WLAN_VENDOR_MCC_QUOTA_TYPE_FIXED && quota_type != QCA_WLAN_VENDOR_MCC_QUOTA_TYPE_CLEAR) { hdd_err("Quota type is not valid %u", quota_type); return -EINVAL; } if (quota_type == QCA_WLAN_VENDOR_MCC_QUOTA_TYPE_CLEAR) { /* Remove quota, enable MCC adaptive scheduling */ if (wlan_hdd_set_mcc_adaptive_sched(hdd_ctx->psoc, true)) return -EAGAIN; mcc_quota.op_mode = QDF_MAX_NO_OF_MODE; mcc_quota.vdev_id = WLAN_UMAC_VDEV_ID_MAX; ucfg_mlme_set_user_mcc_quota(psoc, &mcc_quota); return 0; } cmd_id = QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_ENTRIES; if (!tb[cmd_id]) { hdd_err("No entries present"); return -EINVAL; } entries = 0; nla_for_each_nested(curr_attr, tb[cmd_id], rem_bytes) { if (entries > 0) { hdd_debug("Only one entry permitted"); hdd_debug("Entry (%d) for (%u) is ignored", entries, nla_type(curr_attr)); entries++; continue; } rc = wlan_cfg80211_nla_parse_nested(quota_entries, QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_MAX, curr_attr, set_mcc_quota_policy); if (rc) { hdd_err("Entry parse error %d", rc); return -EINVAL; } att_id = QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_IFINDEX; if (!quota_entries[att_id]) { hdd_err("if_index not specified"); return -EINVAL; } if_idx = nla_get_u32(quota_entries[att_id]); if (if_idx == 0) { hdd_debug("Invalid if_index"); return -EINVAL; } if_adapter = hdd_get_adapter_by_ifindex(hdd_ctx, if_idx); if (!if_adapter) { hdd_err("interface (%u) not found", if_idx); return -EINVAL; } if (wlan_hdd_validate_vdev_id(if_adapter->deflink->vdev_id)) return -EINVAL; att_id = QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_CHAN_TIME_PERCENTAGE; if (!quota_entries[att_id]) { hdd_err("Quota not specified"); return -EINVAL; } mcc_quota.quota = nla_get_u32(quota_entries[att_id]); mcc_quota.vdev_id = if_adapter->deflink->vdev_id; mcc_quota.op_mode = if_adapter->device_mode; entries++; } if (entries == 0) { hdd_err("No entries found"); return -EINVAL; } if (mcc_quota.op_mode != QDF_P2P_GO_MODE) { hdd_debug("Support only P2P GO mode now"); return -EOPNOTSUPP; } ucfg_mlme_set_user_mcc_quota(psoc, &mcc_quota); duty_cycle = ucfg_mlme_get_user_mcc_quota_percentage(psoc); if (duty_cycle == 0) { hdd_debug("Quota will be configured when MCC scenario exists"); return 0; } if (wlan_hdd_set_mcc_adaptive_sched(hdd_ctx->psoc, false)) return -EAGAIN; if (wlan_hdd_send_mcc_vdev_quota(if_adapter, duty_cycle)) return -EINVAL; return 0; } /** * wlan_hdd_set_mcc_low_latency_quota() - Enable/disable MCC low latency * mode * @hdd_ctx: hdd context * @wdev: wdev object * @quota_type: quota type * @tb: attribute information * * Return: 0 on success, negative errno on failure */ static int wlan_hdd_set_mcc_low_latency_quota( struct hdd_context *hdd_ctx, struct wireless_dev *wdev, enum qca_wlan_vendor_mcc_quota_type quota_type, struct nlattr *tb[]) { struct net_device *dev = wdev->netdev; struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev); uint32_t cmd_id; uint8_t ll_enable; int rc; uint32_t ll_mode = 0; if (wlan_hdd_validate_context(hdd_ctx)) return -EINVAL; if (quota_type != QCA_WLAN_VENDOR_MCC_QUOTA_TYPE_LOW_LATENCY) { hdd_err("Quota type %u is not expected %d", quota_type, QCA_WLAN_VENDOR_MCC_QUOTA_TYPE_LOW_LATENCY); return -EINVAL; } cmd_id = QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_LOW_LATENCY_MODE_ENABLE; if (!tb[cmd_id]) { hdd_err("No MCC LL mode attr id %d", cmd_id); return -EINVAL; } ll_enable = nla_get_u8(tb[cmd_id]); if (ll_enable) ll_mode = 1; hdd_debug("set conc ll mode 0x%08x", ll_mode); rc = wma_cli_set_command(adapter->deflink->vdev_id, wmi_pdev_param_set_conc_low_latency_mode, ll_mode, PDEV_CMD); if (rc) hdd_err("Failed to set conc low latency mode, %d", rc); return 0; } int wlan_hdd_cfg80211_set_mcc_quota(struct wiphy *wiphy, struct wireless_dev *wdev, const void *attr, int attr_len) { struct hdd_context *hdd_ctx = wiphy_priv(wiphy); struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_MAX + 1]; struct wlan_objmgr_psoc *psoc; uint32_t cmd_id, quota_type; int rc; hdd_enter(); if (wlan_hdd_validate_context(hdd_ctx)) return -EINVAL; psoc = hdd_ctx->psoc; if (!psoc) return -EINVAL; if (wlan_cfg80211_nla_parse(tb, QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_MAX, attr, attr_len, set_mcc_quota_policy)) { hdd_err("Error parsing attributes"); return -EINVAL; } cmd_id = QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_TYPE; if (!tb[cmd_id]) { hdd_err("Quota type not specified"); return -EINVAL; } quota_type = nla_get_u32(tb[cmd_id]); if (quota_type == QCA_WLAN_VENDOR_MCC_QUOTA_TYPE_FIXED || quota_type == QCA_WLAN_VENDOR_MCC_QUOTA_TYPE_CLEAR) { rc = wlan_hdd_set_mcc_fixed_quota(hdd_ctx, quota_type, tb); } else if (quota_type == QCA_WLAN_VENDOR_MCC_QUOTA_TYPE_LOW_LATENCY) { rc = wlan_hdd_set_mcc_low_latency_quota(hdd_ctx, wdev, quota_type, tb); } else { hdd_err("Quota type is not valid %u", quota_type); return -EINVAL; } return rc; } int wlan_hdd_apply_user_mcc_quota(struct hdd_adapter *adapter) { struct hdd_context *hdd_ctx; uint32_t quota_val; hdd_ctx = WLAN_HDD_GET_CTX(adapter); if (!hdd_ctx) return -EINVAL; quota_val = ucfg_mlme_get_user_mcc_quota_percentage(hdd_ctx->psoc); if (quota_val == 0) { hdd_debug("no mcc/quota for mode %d, vdev_id : %u", adapter->device_mode, adapter->deflink->vdev_id); return 0; } if (wlan_hdd_set_mcc_adaptive_sched(hdd_ctx->psoc, false)) return 0; if (wlan_hdd_send_mcc_vdev_quota(adapter, quota_val)) { hdd_info("Could not send quota"); wlan_hdd_set_mcc_adaptive_sched(hdd_ctx->psoc, true); } return 0; } /** * wlan_cfg80211_indicate_mcc_quota() - Callback to indicate mcc quota * event to upper layer * @psoc: pointer to soc object * @vdev: vdev object * @quota_info: quota info * * This callback will be used to indicate mcc quota info to upper layer * * Return: QDF_STATUS_SUCCESS if event is indicated to OS successfully. */ static QDF_STATUS wlan_cfg80211_indicate_mcc_quota(struct wlan_objmgr_psoc *psoc, struct wlan_objmgr_vdev *vdev, struct mcc_quota_info *quota_info) { uint32_t data_len; struct sk_buff *vendor_event; QDF_STATUS status; struct vdev_osif_priv *vdev_osif_priv; struct wireless_dev *wdev; struct pdev_osif_priv *pdev_osif_priv; struct wlan_objmgr_pdev *pdev; uint32_t idx; uint32_t vdev_id; struct nlattr *quota_attrs, *quota_element; if (!vdev) { hdd_debug("null vdev"); return QDF_STATUS_E_INVAL; } vdev_osif_priv = wlan_vdev_get_ospriv(vdev); if (!vdev_osif_priv || !vdev_osif_priv->wdev) { hdd_debug("null wdev"); return QDF_STATUS_E_INVAL; } wdev = vdev_osif_priv->wdev; vdev_id = wlan_vdev_get_id(vdev); pdev = wlan_vdev_get_pdev(vdev); if (!pdev) { hdd_debug("null pdev"); return QDF_STATUS_E_INVAL; } pdev_osif_priv = wlan_pdev_get_ospriv(pdev); if (!pdev_osif_priv || !pdev_osif_priv->wiphy) { hdd_debug("null wiphy"); return QDF_STATUS_E_INVAL; } /* nested element of QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_CHAN_FREQ and * QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_CHAN_TIME_PERCENTAGE */ data_len = nla_total_size(nla_total_size(sizeof(uint32_t)) + nla_total_size(sizeof(uint32_t))); /* nested array of quota element */ data_len = nla_total_size(data_len * quota_info->num_chan_quota); /* QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_TYPE and NL msg header */ data_len += nla_total_size(sizeof(uint32_t)) + NLMSG_HDRLEN; vendor_event = wlan_cfg80211_vendor_event_alloc(pdev_osif_priv->wiphy, wdev, data_len, QCA_NL80211_VENDOR_SUBCMD_MCC_QUOTA_INDEX, GFP_KERNEL); if (!vendor_event) { hdd_debug("wlan_cfg80211_vendor_event_alloc failed"); return QDF_STATUS_E_NOMEM; } if (nla_put_u32(vendor_event, QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_TYPE, quota_info->type)) { status = QDF_STATUS_E_NOMEM; hdd_debug("add QUOTA_TYPE failed"); goto err; } quota_attrs = nla_nest_start(vendor_event, QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_ENTRIES); if (!quota_attrs) { status = QDF_STATUS_E_NOMEM; hdd_debug("add QUOTA_ENTRIES failed"); goto err; } hdd_debug("mcc quota vdev %d type %d num %d", vdev_id, quota_info->type, quota_info->num_chan_quota); for (idx = 0; idx < quota_info->num_chan_quota; idx++) { quota_element = nla_nest_start(vendor_event, idx); if (!quota_element) { status = QDF_STATUS_E_NOMEM; hdd_debug("add quota idx failed"); goto err; } if (nla_put_u32(vendor_event, QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_CHAN_FREQ, quota_info->chan_quota[idx].chan_mhz)) { status = QDF_STATUS_E_NOMEM; hdd_debug("add QUOTA_CHAN_FREQ failed"); goto err; } if (nla_put_u32(vendor_event, QCA_WLAN_VENDOR_ATTR_MCC_QUOTA_CHAN_TIME_PERCENTAGE, quota_info->chan_quota[idx].channel_time_quota)) { status = QDF_STATUS_E_NOMEM; hdd_debug("add QUOTA_CHAN_TIME_PERCENTAGE failed"); goto err; } nla_nest_end(vendor_event, quota_element); hdd_debug("mcc quota vdev %d [%d] %d quota %d", vdev_id, idx, quota_info->chan_quota[idx].chan_mhz, quota_info->chan_quota[idx].channel_time_quota); } nla_nest_end(vendor_event, quota_attrs); wlan_cfg80211_vendor_event(vendor_event, GFP_KERNEL); return QDF_STATUS_SUCCESS; err: wlan_cfg80211_vendor_free_skb(vendor_event); return status; } /** * wlan_hdd_register_mcc_quota_event_callback() - Register hdd callback to get * mcc quota event to upper layer * @hdd_ctx: pointer to hdd context * * Return: void */ void wlan_hdd_register_mcc_quota_event_callback(struct hdd_context *hdd_ctx) { ucfg_p2p_register_mcc_quota_event_os_if_cb(hdd_ctx->psoc, wlan_cfg80211_indicate_mcc_quota); }