blob: 20bb174870ecd0ed08d10be4a229b17417e50538 [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/timer.h>
#include <net/mcps802154.h>
MODULE_AUTHOR("Saad Zouiten <saad.zouiten@qorvo.com>");
MODULE_DESCRIPTION("stubbed 802.15.4 mcps driver");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL v2");
static const int g_time_interval = 1000;
static struct timer_list g_timer;
static struct mcps802154_llhw *driver_llhw;
static bool rx_enabled;
static bool tx_queued;
static bool started;
static bool stop_timer;
static bool scanning;
static u8 seq;
static u8 curchan;
const char *const calib_strings[] = { "calib.param1", NULL };
static u8 calib;
static void periodic_task(struct timer_list *unused)
{
if (stop_timer)
return;
pr_info("fake_mcps: %s called\n", __func__);
/*Restarting the timer...*/
mod_timer(&g_timer, jiffies + msecs_to_jiffies(g_time_interval));
if (rx_enabled) {
rx_enabled = false;
mcps802154_rx_frame(driver_llhw);
}
if (tx_queued) {
tx_queued = false;
mcps802154_tx_done(driver_llhw);
}
}
static int start(struct mcps802154_llhw *llhw)
{
pr_info("fake_mcps: %s called\n", __func__);
started = true;
return 0;
}
static void stop(struct mcps802154_llhw *llhw)
{
pr_info("fake_mcps: %s called\n", __func__);
started = false;
}
static int tx_frame(struct mcps802154_llhw *llhw, struct sk_buff *skb,
const struct mcps802154_tx_frame_info *info, int frame_idx,
int next_delay_dtu)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: tx_frame called skb len=%d\n", skb->len);
print_hex_dump(KERN_CONT, " ", DUMP_PREFIX_OFFSET, 16, 1, skb->data,
skb->len, false);
tx_queued = true;
return 0;
}
static int rx_enable(struct mcps802154_llhw *llhw,
const struct mcps802154_rx_info *info, int frame_idx,
int next_delay_dtu)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
rx_enabled = true;
return 0;
}
static int rx_disable(struct mcps802154_llhw *llhw)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
rx_enabled = false;
return 0;
}
static struct sk_buff *create_fake_data_frame(void)
{
unsigned char *data;
struct sk_buff *skb = dev_alloc_skb(20 + 2);
if (!skb)
return NULL;
data = skb_put(skb, 20);
data[0] = 0x21; /* Frame Control Field */
data[1] = 0xc8; /* Frame Control Field */
data[2] = 0x8b; /* Sequence number */
data[3] = 0xff; /* Destination PAN ID 0xffff */
data[4] = 0xff; /* Destination PAN ID */
data[5] = 0x02; /* Destination short address 0x0002 */
data[6] = 0x00; /* Destination short address */
data[7] = 0x23; /* Source PAN ID 0x0023 */
data[8] = 0x00; /* */
data[9] = 0x60; /* Source extended address ae:c2:4a:1c:21:16:e2:60 */
data[10] = 0xe2; /* */
data[11] = 0x16; /* */
data[12] = 0x21; /* */
data[13] = 0x1c; /* */
data[14] = 0x4a; /* */
data[15] = 0xc2; /* */
data[16] = 0xae; /* */
data[17] = 0xaa; /* Payload */
data[18] = 0xbb; /* */
data[19] = 0xcc; /* */
return skb;
}
static struct sk_buff *create_fake_beacon_frame(void)
{
unsigned char *data;
struct sk_buff *skb = dev_alloc_skb(17 + 2);
if (!skb)
return NULL;
data = skb_put(skb, 17);
data[0] = 0x00; /* Frame Control Field: 0b00000000 */
data[1] = 0xd0; /* Frame Control Field: 0b11010000 */
data[2] = seq; /* Sequence number */
data[3] = curchan; /* Source PAN ID 0x0100 + channel */
data[4] = 0x01; /* */
data[5] = curchan; /* Source extended address ae:c2:4a:1c:21:16:e2:xx */
data[6] = 0xe2; /* */
data[7] = 0x16; /* */
data[8] = 0x21; /* */
data[9] = 0x1c; /* */
data[10] = 0x4a; /* */
data[11] = 0xc2; /* */
data[12] = 0xae; /* */
data[13] = 0x77; /* Superframe spec */
data[14] = 0xcf; /* */
data[15] = 0x00; /* GTS spec */
data[16] = 0x00; /* Pending address spec */
/* TODO: CFP Alloc */
/* TODO: CFP Usage */
/* Update seq for next beacon */
seq = (seq + 1) & 0x3f;
return skb;
}
static int rx_get_frame(struct mcps802154_llhw *llhw, struct sk_buff **skb,
struct mcps802154_rx_frame_info *info)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
if (scanning)
*skb = create_fake_beacon_frame();
else
*skb = create_fake_data_frame();
if (!*skb) {
pr_err("RX buffer allocation failed\n");
return -ENOMEM;
}
return 0;
}
static int rx_get_error_frame(struct mcps802154_llhw *llhw,
struct mcps802154_rx_frame_info *info)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
return 0;
}
static int idle(struct mcps802154_llhw *llhw, bool timestamp, u32 timestamp_dtu)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
return 0;
}
static int reset(struct mcps802154_llhw *llhw)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
return 0;
}
static int get_current_timestamp_dtu(struct mcps802154_llhw *llhw,
u32 *timestamp_dtu)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
*timestamp_dtu = 0;
return 0;
}
static u64 tx_timestamp_dtu_to_rmarker_rctu(struct mcps802154_llhw *llhw,
u32 tx_timestamp_dtu, int ant_id)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
return 0;
}
static s64 difference_timestamp_rctu(struct mcps802154_llhw *llhw,
u64 timestamp_a_rctu, u64 timestamp_b_rctu)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
return 0;
}
static int compute_frame_duration_dtu(struct mcps802154_llhw *llhw,
int payload_bytes)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
return 0;
}
static int set_channel(struct mcps802154_llhw *llhw, u8 page, u8 channel,
u8 preamble_code)
{
pr_info("fake_mcps: %s called\n", __func__);
seq = 0; /* reset beacon sequence number */
curchan = channel; /* save current channel */
return 0;
}
static int set_hrp_uwb_params(struct mcps802154_llhw *llhw, int prf, int psr,
int sfd_selector, int phr_rate, int data_rate)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
return 0;
}
static int set_hw_addr_filt(struct mcps802154_llhw *llhw,
struct ieee802154_hw_addr_filt *filt,
unsigned long changed)
{
if (changed & IEEE802154_AFILT_SADDR_CHANGED) {
pr_info("fake_mcps: new short addr=%x", filt->short_addr);
}
if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
pr_info("fake_mcps: new extended addr=%llx", filt->ieee_addr);
}
if (changed & IEEE802154_AFILT_PANID_CHANGED) {
pr_info("fake_mcps: new pan id=%x", filt->pan_id);
}
if (changed & IEEE802154_AFILT_PANC_CHANGED) {
pr_info("fake_mcps: new pan coordinator=%x", filt->pan_coord);
}
return 0;
}
static int set_txpower(struct mcps802154_llhw *llhw, s32 mbm)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
return 0;
}
static int set_cca_mode(struct mcps802154_llhw *llhw,
const struct wpan_phy_cca *cca)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
return 0;
}
static int set_cca_ed_level(struct mcps802154_llhw *llhw, s32 mbm)
{
if (!started) {
pr_err("fake_mcps: %s called and not started\n", __func__);
return -EIO;
}
pr_info("fake_mcps: %s called\n", __func__);
return 0;
}
static int set_promiscuous_mode(struct mcps802154_llhw *llhw, bool on)
{
pr_info("fake_mcps: %s called on=%d\n", __func__, on);
return 0;
}
static int set_scanning_mode(struct mcps802154_llhw *llhw, bool mode)
{
pr_info("fake_mcps: %s called\n", __func__);
scanning = mode;
return 0;
}
static int set_calibration(struct mcps802154_llhw *llhw, const char *key,
void *value, size_t length)
{
pr_info("fake_mcps: %s called\n", __func__);
if (!key || !value || length != 1)
return -EINVAL;
if (strcmp(key, calib_strings[0]) == 0)
calib = *(u8 *)value;
else
return -ENOENT;
return 0;
}
static int get_calibration(struct mcps802154_llhw *llhw, const char *key,
void *value, size_t length)
{
pr_info("fake_mcps: %s called\n", __func__);
if (!key || !value || length < 1)
return -EINVAL;
if (strcmp(key, calib_strings[0]) == 0)
*(u8 *)value = calib;
else
return -ENOENT;
return 1;
}
static const char *const *list_calibration(struct mcps802154_llhw *llhw)
{
pr_info("fake_mcps: %s called\n", __func__);
return calib_strings;
}
static int vendor_cmd(struct mcps802154_llhw *llhw, u32 vendor_id, u32 subcmd,
void *data, size_t data_len)
{
pr_info("fake_mcps: %s called\n", __func__);
return 0;
}
static const struct mcps802154_ops fake_ops = {
.start = start,
.stop = stop,
.tx_frame = tx_frame,
.rx_enable = rx_enable,
.rx_disable = rx_disable,
.rx_get_frame = rx_get_frame,
.rx_get_error_frame = rx_get_error_frame,
.idle = idle,
.reset = reset,
.get_current_timestamp_dtu = get_current_timestamp_dtu,
.tx_timestamp_dtu_to_rmarker_rctu = tx_timestamp_dtu_to_rmarker_rctu,
.difference_timestamp_rctu = difference_timestamp_rctu,
.compute_frame_duration_dtu = compute_frame_duration_dtu,
.set_channel = set_channel,
.set_hrp_uwb_params = set_hrp_uwb_params,
.set_hw_addr_filt = set_hw_addr_filt,
.set_txpower = set_txpower,
.set_cca_mode = set_cca_mode,
.set_cca_ed_level = set_cca_ed_level,
.set_promiscuous_mode = set_promiscuous_mode,
.set_scanning_mode = set_scanning_mode,
.set_calibration = set_calibration,
.get_calibration = get_calibration,
.list_calibration = list_calibration,
.vendor_cmd = vendor_cmd,
};
static int __init fake_init(void)
{
int r;
pr_info("fake_mcps: init\n");
driver_llhw = mcps802154_alloc_llhw(0, &fake_ops);
if (driver_llhw == NULL) {
return -ENOMEM;
}
driver_llhw->hw->flags =
(IEEE802154_HW_TX_OMIT_CKSUM | IEEE802154_HW_AFILT |
IEEE802154_HW_PROMISCUOUS | IEEE802154_HW_RX_OMIT_CKSUM);
/* UWB High band 802.15.4a-2007. */
driver_llhw->hw->phy->supported.channels[4] |= 0xffe0;
/* UWB symbol duration at PRF16 & PRF64 is ~1us */
driver_llhw->hw->phy->symbol_duration = 1;
/* Set extended address. */
driver_llhw->hw->phy->perm_extended_addr = 0xd6552cd6e41ceb57;
/* fake driver phy channel 5 as default */
driver_llhw->hw->phy->current_page = 4;
driver_llhw->hw->phy->current_channel = 5;
driver_llhw->current_preamble_code = 9;
r = mcps802154_register_llhw(driver_llhw);
if (r) {
mcps802154_free_llhw(driver_llhw);
driver_llhw = NULL;
return r;
}
/*Starting the periodic task.*/
timer_setup(&g_timer, periodic_task, 0);
mod_timer(&g_timer, jiffies + msecs_to_jiffies(g_time_interval));
return 0;
}
static void __exit fake_exit(void)
{
pr_info("fake_mcps: Exit\n");
mcps802154_unregister_llhw(driver_llhw);
mcps802154_free_llhw(driver_llhw);
driver_llhw = NULL;
/*Deleting the timer aka the periodic task.*/
stop_timer = true;
del_timer(&g_timer);
}
module_init(fake_init);
module_exit(fake_exit);