| /* |
| * 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 |
| }; |