root/drivers/net/wireless/ti/wlcore/vendor_cmd.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * This file is part of wlcore
 *
 * Copyright (C) 2014 Texas Instruments. All rights reserved.
 */

#include <linux/pm_runtime.h>

#include <net/mac80211.h>
#include <net/netlink.h>

#include "wlcore.h"
#include "debug.h"
#include "hw_ops.h"
#include "vendor_cmd.h"

static const
struct nla_policy wlcore_vendor_attr_policy[NUM_WLCORE_VENDOR_ATTR] = {
        [WLCORE_VENDOR_ATTR_FREQ]               = { .type = NLA_U32 },
        [WLCORE_VENDOR_ATTR_GROUP_ID]           = { .type = NLA_U32 },
        [WLCORE_VENDOR_ATTR_GROUP_KEY]          = { .type = NLA_BINARY,
                                                    .len = WLAN_MAX_KEY_LEN },
};

static int
wlcore_vendor_cmd_smart_config_start(struct wiphy *wiphy,
                                     struct wireless_dev *wdev,
                                     const void *data, int data_len)
{
        struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
        struct wl1271 *wl = hw->priv;
        struct nlattr *tb[NUM_WLCORE_VENDOR_ATTR];
        int ret;

        wl1271_debug(DEBUG_CMD, "vendor cmd smart config start");

        if (!data)
                return -EINVAL;

        ret = nla_parse_deprecated(tb, MAX_WLCORE_VENDOR_ATTR, data, data_len,
                                   wlcore_vendor_attr_policy, NULL);
        if (ret)
                return ret;

        if (!tb[WLCORE_VENDOR_ATTR_GROUP_ID])
                return -EINVAL;

        mutex_lock(&wl->mutex);

        if (unlikely(wl->state != WLCORE_STATE_ON)) {
                ret = -EINVAL;
                goto out;
        }

        ret = pm_runtime_resume_and_get(wl->dev);
        if (ret < 0)
                goto out;

        ret = wlcore_smart_config_start(wl,
                        nla_get_u32(tb[WLCORE_VENDOR_ATTR_GROUP_ID]));

        pm_runtime_put_autosuspend(wl->dev);
out:
        mutex_unlock(&wl->mutex);

        return ret;
}

static int
wlcore_vendor_cmd_smart_config_stop(struct wiphy *wiphy,
                                    struct wireless_dev *wdev,
                                    const void *data, int data_len)
{
        struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
        struct wl1271 *wl = hw->priv;
        int ret;

        wl1271_debug(DEBUG_CMD, "testmode cmd smart config stop");

        mutex_lock(&wl->mutex);

        if (unlikely(wl->state != WLCORE_STATE_ON)) {
                ret = -EINVAL;
                goto out;
        }

        ret = pm_runtime_resume_and_get(wl->dev);
        if (ret < 0)
                goto out;

        ret = wlcore_smart_config_stop(wl);

        pm_runtime_put_autosuspend(wl->dev);
out:
        mutex_unlock(&wl->mutex);

        return ret;
}

static int
wlcore_vendor_cmd_smart_config_set_group_key(struct wiphy *wiphy,
                                             struct wireless_dev *wdev,
                                             const void *data, int data_len)
{
        struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
        struct wl1271 *wl = hw->priv;
        struct nlattr *tb[NUM_WLCORE_VENDOR_ATTR];
        int ret;

        wl1271_debug(DEBUG_CMD, "testmode cmd smart config set group key");

        if (!data)
                return -EINVAL;

        ret = nla_parse_deprecated(tb, MAX_WLCORE_VENDOR_ATTR, data, data_len,
                                   wlcore_vendor_attr_policy, NULL);
        if (ret)
                return ret;

        if (!tb[WLCORE_VENDOR_ATTR_GROUP_ID] ||
            !tb[WLCORE_VENDOR_ATTR_GROUP_KEY])
                return -EINVAL;

        mutex_lock(&wl->mutex);

        if (unlikely(wl->state != WLCORE_STATE_ON)) {
                ret = -EINVAL;
                goto out;
        }

        ret = pm_runtime_resume_and_get(wl->dev);
        if (ret < 0)
                goto out;

        ret = wlcore_smart_config_set_group_key(wl,
                        nla_get_u32(tb[WLCORE_VENDOR_ATTR_GROUP_ID]),
                        nla_len(tb[WLCORE_VENDOR_ATTR_GROUP_KEY]),
                        nla_data(tb[WLCORE_VENDOR_ATTR_GROUP_KEY]));

        pm_runtime_put_autosuspend(wl->dev);
out:
        mutex_unlock(&wl->mutex);

        return ret;
}

static const struct wiphy_vendor_command wlcore_vendor_commands[] = {
        {
                .info = {
                        .vendor_id = TI_OUI,
                        .subcmd = WLCORE_VENDOR_CMD_SMART_CONFIG_START,
                },
                .flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
                         WIPHY_VENDOR_CMD_NEED_RUNNING,
                .doit = wlcore_vendor_cmd_smart_config_start,
                .policy = wlcore_vendor_attr_policy,
        },
        {
                .info = {
                        .vendor_id = TI_OUI,
                        .subcmd = WLCORE_VENDOR_CMD_SMART_CONFIG_STOP,
                },
                .flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
                         WIPHY_VENDOR_CMD_NEED_RUNNING,
                .doit = wlcore_vendor_cmd_smart_config_stop,
                .policy = wlcore_vendor_attr_policy,
        },
        {
                .info = {
                        .vendor_id = TI_OUI,
                        .subcmd = WLCORE_VENDOR_CMD_SMART_CONFIG_SET_GROUP_KEY,
                },
                .flags = WIPHY_VENDOR_CMD_NEED_NETDEV |
                         WIPHY_VENDOR_CMD_NEED_RUNNING,
                .doit = wlcore_vendor_cmd_smart_config_set_group_key,
                .policy = wlcore_vendor_attr_policy,
        },
};

static const struct nl80211_vendor_cmd_info wlcore_vendor_events[] = {
        {
                .vendor_id = TI_OUI,
                .subcmd = WLCORE_VENDOR_EVENT_SC_SYNC,
        },
        {
                .vendor_id = TI_OUI,
                .subcmd = WLCORE_VENDOR_EVENT_SC_DECODE,
        },
};

void wlcore_set_vendor_commands(struct wiphy *wiphy)
{
        wiphy->vendor_commands = wlcore_vendor_commands;
        wiphy->n_vendor_commands = ARRAY_SIZE(wlcore_vendor_commands);
        wiphy->vendor_events = wlcore_vendor_events;
        wiphy->n_vendor_events = ARRAY_SIZE(wlcore_vendor_events);
}