root/drivers/net/wireless/intel/iwlwifi/mld/session-protect.c
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
 * Copyright (C) 2024 Intel Corporation
 */
#include "session-protect.h"
#include "fw/api/time-event.h"
#include "fw/api/context.h"
#include "iface.h"
#include <net/mac80211.h>

void iwl_mld_handle_session_prot_notif(struct iwl_mld *mld,
                                       struct iwl_rx_packet *pkt)
{
        struct iwl_session_prot_notif *notif = (void *)pkt->data;
        int fw_link_id = le32_to_cpu(notif->mac_link_id);
        struct ieee80211_bss_conf *link_conf =
                iwl_mld_fw_id_to_link_conf(mld, fw_link_id);
        struct ieee80211_vif *vif;
        struct iwl_mld_vif *mld_vif;
        struct iwl_mld_session_protect *session_protect;

        if (WARN_ON(!link_conf))
                return;

        vif = link_conf->vif;
        mld_vif = iwl_mld_vif_from_mac80211(vif);
        session_protect = &mld_vif->session_protect;

        if (!le32_to_cpu(notif->status)) {
                memset(session_protect, 0, sizeof(*session_protect));
        } else if (le32_to_cpu(notif->start)) {
                /* End_jiffies indicates an active session */
                session_protect->session_requested = false;
                session_protect->end_jiffies =
                        TU_TO_EXP_TIME(session_protect->duration);
                /* !session_protect->end_jiffies means inactive session */
                if (!session_protect->end_jiffies)
                        session_protect->end_jiffies = 1;
        } else {
                memset(session_protect, 0, sizeof(*session_protect));
        }
}

static int _iwl_mld_schedule_session_protection(struct iwl_mld *mld,
                                                struct ieee80211_vif *vif,
                                                u32 duration, u32 min_duration,
                                                int link_id)
{
        struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
        struct iwl_mld_link *link =
                iwl_mld_link_dereference_check(mld_vif, link_id);
        struct iwl_mld_session_protect *session_protect =
                &mld_vif->session_protect;
        struct iwl_session_prot_cmd cmd = {
                .id_and_color = cpu_to_le32(link->fw_id),
                .action = cpu_to_le32(FW_CTXT_ACTION_ADD),
                .conf_id = cpu_to_le32(SESSION_PROTECT_CONF_ASSOC),
                .duration_tu = cpu_to_le32(MSEC_TO_TU(duration)),
        };
        int ret;

        lockdep_assert_wiphy(mld->wiphy);

        WARN(hweight16(vif->active_links) > 1,
             "Session protection isn't allowed with more than one active link");

        if (session_protect->end_jiffies &&
            time_after(session_protect->end_jiffies,
                       TU_TO_EXP_TIME(min_duration))) {
                IWL_DEBUG_TE(mld, "We have ample in the current session: %u\n",
                             jiffies_to_msecs(session_protect->end_jiffies -
                                              jiffies));
                return -EALREADY;
        }

        IWL_DEBUG_TE(mld, "Add a new session protection, duration %d TU\n",
                     le32_to_cpu(cmd.duration_tu));

        ret = iwl_mld_send_cmd_pdu(mld, WIDE_ID(MAC_CONF_GROUP,
                                                SESSION_PROTECTION_CMD), &cmd);

        if (ret)
                return ret;

        /* end_jiffies will be updated when handling session_prot_notif */
        session_protect->end_jiffies = 0;
        session_protect->duration = duration;
        session_protect->session_requested = true;

        return 0;
}

void iwl_mld_schedule_session_protection(struct iwl_mld *mld,
                                         struct ieee80211_vif *vif,
                                         u32 duration, u32 min_duration,
                                         int link_id)
{
        int ret;

        ret = _iwl_mld_schedule_session_protection(mld, vif, duration,
                                                   min_duration, link_id);
        if (ret && ret != -EALREADY)
                IWL_ERR(mld,
                        "Couldn't send the SESSION_PROTECTION_CMD (%d)\n",
                        ret);
}

struct iwl_mld_session_start_data {
        struct iwl_mld *mld;
        struct ieee80211_bss_conf *link_conf;
        bool success;
};

static bool iwl_mld_session_start_fn(struct iwl_notif_wait_data *notif_wait,
                                     struct iwl_rx_packet *pkt, void *_data)
{
        struct iwl_session_prot_notif *notif = (void *)pkt->data;
        unsigned int pkt_len = iwl_rx_packet_payload_len(pkt);
        struct iwl_mld_session_start_data *data = _data;
        struct ieee80211_bss_conf *link_conf;
        struct iwl_mld *mld = data->mld;
        int fw_link_id;

        if (IWL_FW_CHECK(mld, pkt_len < sizeof(*notif),
                         "short session prot notif (%d)\n",
                         pkt_len))
                return false;

        fw_link_id = le32_to_cpu(notif->mac_link_id);
        link_conf = iwl_mld_fw_id_to_link_conf(mld, fw_link_id);

        if (link_conf != data->link_conf)
                return false;

        if (!le32_to_cpu(notif->status))
                return true;

        if (notif->start) {
                data->success = true;
                return true;
        }

        return false;
}

int iwl_mld_start_session_protection(struct iwl_mld *mld,
                                     struct ieee80211_vif *vif,
                                     u32 duration, u32 min_duration,
                                     int link_id, unsigned long timeout)
{
        static const u16 start_notif[] = { SESSION_PROTECTION_NOTIF };
        struct iwl_notification_wait start_wait;
        struct iwl_mld_session_start_data data = {
                .mld = mld,
                .link_conf = wiphy_dereference(mld->wiphy,
                                               vif->link_conf[link_id]),
        };
        int ret;

        if (WARN_ON(!data.link_conf))
                return -EINVAL;

        iwl_init_notification_wait(&mld->notif_wait, &start_wait,
                                   start_notif, ARRAY_SIZE(start_notif),
                                   iwl_mld_session_start_fn, &data);

        ret = _iwl_mld_schedule_session_protection(mld, vif, duration,
                                                   min_duration, link_id);

        if (ret) {
                iwl_remove_notification(&mld->notif_wait, &start_wait);
                return ret == -EALREADY ? 0 : ret;
        }

        ret = iwl_wait_notification(&mld->notif_wait, &start_wait, timeout);
        if (ret)
                return ret;
        return data.success ? 0 : -EIO;
}

int iwl_mld_cancel_session_protection(struct iwl_mld *mld,
                                      struct ieee80211_vif *vif,
                                      int link_id)
{
        struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
        struct iwl_mld_link *link =
                iwl_mld_link_dereference_check(mld_vif, link_id);
        struct iwl_mld_session_protect *session_protect =
                &mld_vif->session_protect;
        struct iwl_session_prot_cmd cmd = {
                .action = cpu_to_le32(FW_CTXT_ACTION_REMOVE),
                .conf_id = cpu_to_le32(SESSION_PROTECT_CONF_ASSOC),
        };
        int ret;

        lockdep_assert_wiphy(mld->wiphy);

        /* If there isn't an active session or a requested one for this
         * link do nothing
         */
        if (!session_protect->session_requested &&
            !session_protect->end_jiffies)
                return 0;

        if (WARN_ON(!link))
                return -EINVAL;

        cmd.id_and_color = cpu_to_le32(link->fw_id);

        ret = iwl_mld_send_cmd_pdu(mld,
                                   WIDE_ID(MAC_CONF_GROUP,
                                           SESSION_PROTECTION_CMD), &cmd);
        if (ret) {
                IWL_ERR(mld,
                        "Couldn't send the SESSION_PROTECTION_CMD\n");
                return ret;
        }

        memset(session_protect, 0, sizeof(*session_protect));

        return 0;
}