blob: 41378477441dd5126164b43f5ff6fe2f64cce936 [file] [log] [blame]
/*
* This file is part of the UWB stack for linux.
*
* Copyright (c) 2020-2021 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo. Please contact Qorvo to inquire about licensing terms.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <net/rtnetlink.h>
#include "mcps802154_i.h"
#include "llhw-ops.h"
static int mcps802154_start(struct ieee802154_hw *hw)
{
struct mcps802154_local *local = hw->priv;
int r;
ASSERT_RTNL();
WARN_ON(local->started);
mutex_lock(&local->fsm_lock);
r = llhw_set_channel(local, local->pib.phy_current_channel.page,
local->pib.phy_current_channel.channel,
local->pib.phy_current_channel.preamble_code);
if (!r)
r = mcps802154_ca_start(local);
mutex_unlock(&local->fsm_lock);
return r;
}
static void mcps802154_stop(struct ieee802154_hw *hw)
{
struct mcps802154_local *local = hw->priv;
ASSERT_RTNL();
WARN_ON(!local->started);
mutex_lock(&local->fsm_lock);
mcps802154_ca_stop(local);
mutex_unlock(&local->fsm_lock);
wait_event(local->wq, !local->started);
}
static int mcps802154_xmit_async(struct ieee802154_hw *hw, struct sk_buff *skb)
{
struct mcps802154_local *local = hw->priv;
int r;
bool wake_queue = false;
if (unlikely(!local->started)) {
r = -EPIPE;
} else {
int n_queued;
skb_queue_tail(&local->ca.queue, skb);
n_queued = atomic_inc_return(&local->ca.n_queued);
wake_queue = n_queued < MCPS802154_CA_QUEUE_SIZE;
r = 0;
}
if (wake_queue)
ieee802154_wake_queue(hw);
schedule_work(&local->tx_work);
return r;
}
static int mcps802154_ed(struct ieee802154_hw *hw, u8 *level)
{
/* Not supported for the moment (and not used in Linux SoftMAC). */
return -EOPNOTSUPP;
}
static int mcps802154_set_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
{
struct mcps802154_local *local = hw->priv;
int r;
if (!local->ops->set_channel)
return -EOPNOTSUPP;
mutex_lock(&local->fsm_lock);
r = llhw_set_channel(local, page, channel,
local->pib.phy_current_channel.preamble_code);
if (!r) {
local->pib.phy_current_channel.page = page;
local->pib.phy_current_channel.channel = channel;
}
mutex_unlock(&local->fsm_lock);
return r;
}
static int mcps802154_set_hw_addr_filt(struct ieee802154_hw *hw,
struct ieee802154_hw_addr_filt *filt,
unsigned long changed)
{
struct mcps802154_local *local = hw->priv;
int r;
if (!local->ops->set_hw_addr_filt)
return -EOPNOTSUPP;
mutex_lock(&local->fsm_lock);
if (local->started) {
r = -EBUSY;
} else {
r = llhw_set_hw_addr_filt(local, filt, changed);
if (!r) {
if (changed & IEEE802154_AFILT_PANID_CHANGED)
local->pib.mac_pan_id = filt->pan_id;
if (changed & IEEE802154_AFILT_SADDR_CHANGED)
local->pib.mac_short_addr = filt->short_addr;
if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED)
local->pib.mac_extended_addr = filt->ieee_addr;
}
}
mutex_unlock(&local->fsm_lock);
return r;
}
static int mcps802154_set_frame_retries(struct ieee802154_hw *hw, s8 retries)
{
struct mcps802154_local *local = hw->priv;
if (retries < 0 || retries > 7)
return -EINVAL;
local->pib.mac_max_frame_retries = retries;
return 0;
}
static int mcps802154_set_promiscuous_mode(struct ieee802154_hw *hw, bool on)
{
struct mcps802154_local *local = hw->priv;
int r;
if (!local->ops->set_promiscuous_mode)
return -EOPNOTSUPP;
mutex_lock(&local->fsm_lock);
r = llhw_set_promiscuous_mode(local, on);
mutex_unlock(&local->fsm_lock);
return r;
}
#ifdef CONFIG_HAVE_IEEE802154_SCANNING
static void mcps802154_sw_scan_start(struct ieee802154_hw *hw, __le64 addr)
{
struct mcps802154_local *local = hw->priv;
if (!local->ops->set_scanning_mode)
return;
mutex_lock(&local->fsm_lock);
llhw_set_scanning_mode(local, true);
mutex_unlock(&local->fsm_lock);
}
static void mcps802154_sw_scan_complete(struct ieee802154_hw *hw)
{
struct mcps802154_local *local = hw->priv;
if (!local->ops->set_scanning_mode)
return;
mutex_lock(&local->fsm_lock);
llhw_set_scanning_mode(local, false);
mutex_unlock(&local->fsm_lock);
}
#endif
const struct ieee802154_ops mcps802154_ops = {
.owner = THIS_MODULE,
.start = mcps802154_start,
.stop = mcps802154_stop,
.xmit_async = mcps802154_xmit_async,
.ed = mcps802154_ed,
.set_channel = mcps802154_set_channel,
.set_hw_addr_filt = mcps802154_set_hw_addr_filt,
.set_frame_retries = mcps802154_set_frame_retries,
.set_promiscuous_mode = mcps802154_set_promiscuous_mode,
#ifdef CONFIG_HAVE_IEEE802154_SCANNING
.sw_scan_start = mcps802154_sw_scan_start,
.sw_scan_complete = mcps802154_sw_scan_complete,
#endif
};