| /* |
| * 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/atomic.h> |
| #include <linux/errno.h> |
| #include <linux/ieee802154.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/netdevice.h> |
| #include <net/rtnetlink.h> |
| |
| #include "mcps802154_i.h" |
| #include "llhw-ops.h" |
| #include "default_region.h" |
| #include "simple_ranging_region.h" |
| #include "endless_scheduler.h" |
| #include "on_demand_scheduler.h" |
| #ifdef CONFIG_MCPS802154_TESTMODE |
| #include "ping_pong_region.h" |
| #endif |
| #include "nl.h" |
| #include "warn_return.h" |
| |
| static LIST_HEAD(registered_llhw); |
| static DEFINE_MUTEX(registered_llhw_lock); |
| |
| static void mcps802154_tx_event(struct work_struct *work) |
| { |
| struct mcps802154_local *local = txwork_to_local(work); |
| |
| mutex_lock(&local->fsm_lock); |
| if (likely(local->started)) |
| mcps802154_ca_may_reschedule(local); |
| mutex_unlock(&local->fsm_lock); |
| } |
| |
| struct mcps802154_llhw *mcps802154_alloc_llhw(size_t priv_data_len, |
| const struct mcps802154_ops *ops) |
| { |
| static atomic_t llhw_counter = ATOMIC_INIT(0); |
| int idx; |
| struct ieee802154_hw *hw; |
| struct mcps802154_local *local; |
| size_t priv_size; |
| |
| if (WARN_ON(!ops || !ops->start || !ops->stop || !ops->tx_frame || |
| !ops->rx_enable || !ops->rx_disable || !ops->rx_get_frame || |
| !ops->rx_get_error_frame)) |
| return NULL; |
| |
| priv_size = ALIGN(sizeof(*local), NETDEV_ALIGN) + priv_data_len; |
| hw = ieee802154_alloc_hw(priv_size, &mcps802154_ops); |
| if (!hw) |
| return NULL; |
| |
| idx = atomic_inc_return(&llhw_counter); |
| if (idx < 0) { |
| /* Wrapped! */ |
| atomic_dec(&llhw_counter); |
| ieee802154_free_hw(hw); |
| return NULL; |
| } |
| |
| local = hw->priv; |
| local->hw = hw; |
| local->llhw.hw = hw; |
| local->llhw.priv = (char *)local + ALIGN(sizeof(*local), NETDEV_ALIGN); |
| local->ops = ops; |
| local->hw_idx = idx - 1; |
| init_waitqueue_head(&local->wq); |
| mutex_init(&local->fsm_lock); |
| INIT_WORK(&local->tx_work, mcps802154_tx_event); |
| mutex_lock(&local->fsm_lock); |
| mcps802154_ca_init(local); |
| mcps802154_fproc_init(local); |
| mutex_unlock(&local->fsm_lock); |
| |
| return &local->llhw; |
| } |
| EXPORT_SYMBOL(mcps802154_alloc_llhw); |
| |
| void mcps802154_free_llhw(struct mcps802154_llhw *llhw) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| |
| mutex_lock(&local->fsm_lock); |
| mcps802154_fproc_uninit(local); |
| mcps802154_ca_uninit(local); |
| mutex_unlock(&local->fsm_lock); |
| mutex_destroy(&local->fsm_lock); |
| |
| WARN_ON(waitqueue_active(&local->wq)); |
| #ifndef __KERNEL__ |
| destroy_waitqueue_head(&local->wq); |
| #endif |
| |
| ieee802154_free_hw(local->hw); |
| } |
| EXPORT_SYMBOL(mcps802154_free_llhw); |
| |
| int mcps802154_register_llhw(struct mcps802154_llhw *llhw) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| int r; |
| |
| llhw->hw->flags |= IEEE802154_HW_FRAME_RETRIES; |
| |
| r = ieee802154_register_hw(local->hw); |
| if (r) |
| return r; |
| |
| local->pib.mac_extended_addr = local->hw->phy->perm_extended_addr; |
| local->pib.mac_pan_id = IEEE802154_PAN_ID_BROADCAST; |
| local->pib.mac_short_addr = IEEE802154_ADDR_SHORT_BROADCAST; |
| local->pib.phy_current_channel.page = local->hw->phy->current_page; |
| local->pib.phy_current_channel.channel = |
| local->hw->phy->current_channel; |
| local->pib.phy_current_channel.preamble_code = |
| llhw->current_preamble_code; |
| |
| mutex_lock(®istered_llhw_lock); |
| list_add(&local->registered_entry, ®istered_llhw); |
| mutex_unlock(®istered_llhw_lock); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(mcps802154_register_llhw); |
| |
| void mcps802154_unregister_llhw(struct mcps802154_llhw *llhw) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| |
| mutex_lock(®istered_llhw_lock); |
| list_del(&local->registered_entry); |
| mutex_unlock(®istered_llhw_lock); |
| ieee802154_unregister_hw(local->hw); |
| mutex_lock(&local->fsm_lock); |
| mcps802154_ca_close(local); |
| mutex_unlock(&local->fsm_lock); |
| } |
| EXPORT_SYMBOL(mcps802154_unregister_llhw); |
| |
| __le64 mcps802154_get_extended_addr(struct mcps802154_llhw *llhw) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| |
| return local->pib.mac_extended_addr; |
| } |
| EXPORT_SYMBOL(mcps802154_get_extended_addr); |
| |
| __le16 mcps802154_get_pan_id(struct mcps802154_llhw *llhw) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| |
| return local->pib.mac_pan_id; |
| } |
| EXPORT_SYMBOL(mcps802154_get_pan_id); |
| |
| __le16 mcps802154_get_short_addr(struct mcps802154_llhw *llhw) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| |
| return local->pib.mac_short_addr; |
| } |
| EXPORT_SYMBOL(mcps802154_get_short_addr); |
| |
| const struct mcps802154_channel * |
| mcps802154_get_current_channel(struct mcps802154_llhw *llhw) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| |
| return &local->pib.phy_current_channel; |
| } |
| EXPORT_SYMBOL(mcps802154_get_current_channel); |
| |
| int mcps802154_get_current_timestamp_dtu(struct mcps802154_llhw *llhw, |
| u32 *timestamp_dtu) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| |
| if (!local->started) |
| return -ENETDOWN; |
| |
| return llhw_get_current_timestamp_dtu(local, timestamp_dtu); |
| } |
| EXPORT_SYMBOL(mcps802154_get_current_timestamp_dtu); |
| |
| u64 mcps802154_tx_timestamp_dtu_to_rmarker_rctu(struct mcps802154_llhw *llhw, |
| u32 tx_timestamp_dtu, |
| int ant_id) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| |
| return llhw_tx_timestamp_dtu_to_rmarker_rctu(local, tx_timestamp_dtu, |
| ant_id); |
| } |
| EXPORT_SYMBOL(mcps802154_tx_timestamp_dtu_to_rmarker_rctu); |
| |
| s64 mcps802154_difference_timestamp_rctu(struct mcps802154_llhw *llhw, |
| u64 timestamp_a_rctu, |
| u64 timestamp_b_rctu) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| |
| return llhw_difference_timestamp_rctu(local, timestamp_a_rctu, |
| timestamp_b_rctu); |
| } |
| EXPORT_SYMBOL(mcps802154_difference_timestamp_rctu); |
| |
| struct mcps802154_local *mcps802154_get_first_by_idx(int hw_idx) |
| { |
| struct mcps802154_local *result = NULL, *local; |
| |
| ASSERT_RTNL(); |
| |
| mutex_lock(®istered_llhw_lock); |
| list_for_each_entry (local, ®istered_llhw, registered_entry) { |
| if (local->hw_idx >= hw_idx) { |
| result = local; |
| break; |
| } |
| } |
| mutex_unlock(®istered_llhw_lock); |
| |
| return result; |
| } |
| |
| int __init mcps802154_init(void) |
| { |
| int r; |
| |
| r = mcps802154_nl_init(); |
| if (r) |
| return r; |
| r = mcps802154_default_region_init(); |
| WARN_RETURN(r); |
| r = simple_ranging_region_init(); |
| WARN_ON(r); |
| r = mcps802154_endless_scheduler_init(); |
| WARN_ON(r); |
| r = mcps802154_on_demand_scheduler_init(); |
| WARN_ON(r); |
| #ifdef CONFIG_MCPS802154_TESTMODE |
| r = ping_pong_region_init(); |
| WARN_ON(r); |
| #endif |
| return r; |
| } |
| |
| void __exit mcps802154_exit(void) |
| { |
| #ifdef CONFIG_MCPS802154_TESTMODE |
| ping_pong_region_exit(); |
| #endif |
| mcps802154_on_demand_scheduler_exit(); |
| mcps802154_endless_scheduler_exit(); |
| simple_ranging_region_exit(); |
| mcps802154_default_region_exit(); |
| mcps802154_nl_exit(); |
| } |
| |
| module_init(mcps802154_init); |
| module_exit(mcps802154_exit); |
| |
| MODULE_DESCRIPTION("IEEE 802.15.4 MAC common part sublayer"); |
| MODULE_AUTHOR("Nicolas Schodet <nicolas.schodet@qorvo.com>"); |
| MODULE_VERSION("1.0"); |
| MODULE_LICENSE("GPL v2"); |