| /* |
| * 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/slab.h> |
| #include <linux/errno.h> |
| #include <linux/math64.h> |
| #include <linux/printk.h> |
| |
| #include <linux/netdevice.h> |
| |
| #include <net/mcps802154_schedule.h> |
| #include "net/nfcc_coex_region_nl.h" |
| #include "mcps802154_i.h" |
| |
| #include "nfcc_coex_region.h" |
| #include "nfcc_coex_region_call.h" |
| #include "nfcc_coex_access.h" |
| #include "nfcc_coex_session.h" |
| |
| static struct mcps802154_region_ops nfcc_coex_region_ops; |
| |
| static struct mcps802154_region *nfcc_coex_open(struct mcps802154_llhw *llhw) |
| { |
| struct nfcc_coex_local *local; |
| |
| local = kmalloc(sizeof(*local), GFP_KERNEL); |
| if (!local) |
| return NULL; |
| |
| local->llhw = llhw; |
| local->region.ops = &nfcc_coex_region_ops; |
| local->state = NFCC_COEX_STATE_UNUSED; |
| |
| return &local->region; |
| } |
| |
| static void nfcc_coex_close(struct mcps802154_region *region) |
| { |
| struct nfcc_coex_local *local = region_to_local(region); |
| |
| kfree(local); |
| } |
| |
| static void nfcc_coex_notify_stop(struct mcps802154_region *region) |
| { |
| struct nfcc_coex_local *local = region_to_local(region); |
| |
| nfcc_coex_session_control(local, NFCC_COEX_CALL_CCC_SESSION_STOP, NULL, |
| NULL); |
| if (local->state != NFCC_COEX_STATE_UNUSED) { |
| pr_err("device stopped while nfcc coex not stopped state=%d", |
| local->state); |
| local->state = NFCC_COEX_STATE_UNUSED; |
| } |
| } |
| |
| static int nfcc_coex_call(struct mcps802154_region *region, u32 call_id, |
| const struct nlattr *attrs, |
| const struct genl_info *info) |
| { |
| struct nfcc_coex_local *local = region_to_local(region); |
| |
| switch (call_id) { |
| case NFCC_COEX_CALL_CCC_SESSION_START: |
| case NFCC_COEX_CALL_CCC_SESSION_STOP: |
| return nfcc_coex_session_control(local, call_id, attrs, info); |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int nfcc_coex_get_demand(struct mcps802154_region *region, |
| u32 next_timestamp_dtu, |
| struct mcps802154_region_demand *demand) |
| { |
| struct nfcc_coex_local *local = region_to_local(region); |
| const struct nfcc_coex_session *session = &local->session; |
| |
| switch (local->state) { |
| case NFCC_COEX_STATE_UNUSED: |
| return 0; |
| default: |
| if (session->first_access) { |
| /* region_demand have never been set. |
| * Duration is set at 12ms, value catched during test. */ |
| demand->timestamp_dtu = next_timestamp_dtu; |
| demand->duration_dtu = 187200; |
| } else if (is_before_dtu(session->region_demand.timestamp_dtu, |
| next_timestamp_dtu)) { |
| /* Date is late. */ |
| int shift = next_timestamp_dtu - |
| session->region_demand.timestamp_dtu; |
| int duration = |
| session->region_demand.duration_dtu - shift; |
| demand->timestamp_dtu = next_timestamp_dtu; |
| demand->duration_dtu = duration > 0 ? duration : 1; |
| } else { |
| memcpy(demand, &session->region_demand, |
| sizeof(*demand)); |
| } |
| return 1; |
| } |
| return 0; |
| } |
| |
| void nfcc_coex_report(struct nfcc_coex_local *local) |
| { |
| struct nfcc_coex_session *session = &local->session; |
| const struct dw3000_vendor_cmd_nfcc_coex_get_access_info |
| *get_access_info = &session->get_access_info; |
| struct sk_buff *msg; |
| int r; |
| |
| msg = mcps802154_region_event_alloc_skb( |
| local->llhw, &local->region, |
| NFCC_COEX_CALL_CCC_SESSION_NOTIFICATION, session->event_portid, |
| NLMSG_DEFAULT_SIZE, GFP_KERNEL); |
| if (!msg) |
| return; |
| |
| #define P(attr, type, value) \ |
| do { \ |
| if (nla_put_##type(msg, NFCC_COEX_CALL_ATTR_CCC_##attr, \ |
| value)) { \ |
| goto nla_put_failure; \ |
| } \ |
| } while (0) |
| |
| P(WATCHDOG_TIMEOUT, u8, get_access_info->watchdog_timeout); |
| P(STOPPED, u8, get_access_info->stop); |
| #undef P |
| |
| r = mcps802154_region_event(local->llhw, msg); |
| if (r == -ECONNREFUSED) |
| /* TODO stop. */ |
| ; |
| return; |
| |
| nla_put_failure: |
| kfree_skb(msg); |
| } |
| |
| static struct mcps802154_region_ops nfcc_coex_region_ops = { |
| /* clang-format off */ |
| .owner = THIS_MODULE, |
| .name = "nfcc_coex", |
| .open = nfcc_coex_open, |
| .close = nfcc_coex_close, |
| .notify_stop = nfcc_coex_notify_stop, |
| .call = nfcc_coex_call, |
| .get_access = nfcc_coex_get_access, |
| .get_demand = nfcc_coex_get_demand, |
| /* clang-format on */ |
| }; |
| |
| int __init nfcc_coex_region_init(void) |
| { |
| return mcps802154_region_register(&nfcc_coex_region_ops); |
| } |
| |
| void __exit nfcc_coex_region_exit(void) |
| { |
| mcps802154_region_unregister(&nfcc_coex_region_ops); |
| } |
| |
| module_init(nfcc_coex_region_init); |
| module_exit(nfcc_coex_region_exit); |
| |
| MODULE_DESCRIPTION("Vendor Region for IEEE 802.15.4 MCPS"); |
| MODULE_VERSION("1.0"); |
| MODULE_LICENSE("GPL v2"); |