summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS1
-rw-r--r--drivers/net/ieee802154/at86rf230.c90
-rw-r--r--drivers/net/ieee802154/cc2520.c136
-rw-r--r--include/linux/ieee802154.h7
-rw-r--r--include/linux/spi/at86rf230.h20
-rw-r--r--include/linux/spi/cc2520.h21
-rw-r--r--include/net/cfg802154.h78
-rw-r--r--include/net/ieee802154_netdev.h52
-rw-r--r--include/net/nl802154.h61
-rw-r--r--net/ieee802154/header_ops.c24
-rw-r--r--net/ieee802154/nl802154.c283
-rw-r--r--net/ieee802154/nl802154.h4
-rw-r--r--net/ieee802154/rdev-ops.h56
-rw-r--r--net/ieee802154/trace.h61
-rw-r--r--net/mac802154/Makefile2
-rw-r--r--net/mac802154/cfg.c60
-rw-r--r--net/mac802154/ieee802154_i.h61
-rw-r--r--net/mac802154/iface.c6
-rw-r--r--net/mac802154/llsec.c5
-rw-r--r--net/mac802154/main.c37
-rw-r--r--net/mac802154/rx.c36
-rw-r--r--net/mac802154/scan.c456
-rw-r--r--net/mac802154/tx.c42
23 files changed, 1368 insertions, 231 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 79614c1d0709..20b25511e18d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4819,7 +4819,6 @@ L: linux-wpan@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/ieee802154/cc2520.txt
F: drivers/net/ieee802154/cc2520.c
-F: include/linux/spi/cc2520.h
CCREE ARM TRUSTZONE CRYPTOCELL REE DRIVER
M: Gilad Ben-Yossef <gilad@benyossef.com>
diff --git a/drivers/net/ieee802154/at86rf230.c b/drivers/net/ieee802154/at86rf230.c
index 15f283b26721..62b984f84d9f 100644
--- a/drivers/net/ieee802154/at86rf230.c
+++ b/drivers/net/ieee802154/at86rf230.c
@@ -17,8 +17,8 @@
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/delay.h>
+#include <linux/property.h>
#include <linux/spi/spi.h>
-#include <linux/spi/at86rf230.h>
#include <linux/regmap.h>
#include <linux/skbuff.h>
#include <linux/of_gpio.h>
@@ -82,7 +82,7 @@ struct at86rf230_local {
struct ieee802154_hw *hw;
struct at86rf2xx_chip_data *data;
struct regmap *regmap;
- int slp_tr;
+ struct gpio_desc *slp_tr;
bool sleep;
struct completion state_complete;
@@ -107,8 +107,8 @@ at86rf230_async_state_change(struct at86rf230_local *lp,
static inline void
at86rf230_sleep(struct at86rf230_local *lp)
{
- if (gpio_is_valid(lp->slp_tr)) {
- gpio_set_value(lp->slp_tr, 1);
+ if (lp->slp_tr) {
+ gpiod_set_value(lp->slp_tr, 1);
usleep_range(lp->data->t_off_to_sleep,
lp->data->t_off_to_sleep + 10);
lp->sleep = true;
@@ -118,8 +118,8 @@ at86rf230_sleep(struct at86rf230_local *lp)
static inline void
at86rf230_awake(struct at86rf230_local *lp)
{
- if (gpio_is_valid(lp->slp_tr)) {
- gpio_set_value(lp->slp_tr, 0);
+ if (lp->slp_tr) {
+ gpiod_set_value(lp->slp_tr, 0);
usleep_range(lp->data->t_sleep_to_off,
lp->data->t_sleep_to_off + 100);
lp->sleep = false;
@@ -204,9 +204,9 @@ at86rf230_write_subreg(struct at86rf230_local *lp,
static inline void
at86rf230_slp_tr_rising_edge(struct at86rf230_local *lp)
{
- gpio_set_value(lp->slp_tr, 1);
+ gpiod_set_value(lp->slp_tr, 1);
udelay(1);
- gpio_set_value(lp->slp_tr, 0);
+ gpiod_set_value(lp->slp_tr, 0);
}
static bool
@@ -819,7 +819,7 @@ at86rf230_write_frame_complete(void *context)
ctx->trx.len = 2;
- if (gpio_is_valid(lp->slp_tr))
+ if (lp->slp_tr)
at86rf230_slp_tr_rising_edge(lp);
else
at86rf230_async_write_reg(lp, RG_TRX_STATE, STATE_BUSY_TX, ctx,
@@ -1416,32 +1416,6 @@ static int at86rf230_hw_init(struct at86rf230_local *lp, u8 xtal_trim)
}
static int
-at86rf230_get_pdata(struct spi_device *spi, int *rstn, int *slp_tr,
- u8 *xtal_trim)
-{
- struct at86rf230_platform_data *pdata = spi->dev.platform_data;
- int ret;
-
- if (!IS_ENABLED(CONFIG_OF) || !spi->dev.of_node) {
- if (!pdata)
- return -ENOENT;
-
- *rstn = pdata->rstn;
- *slp_tr = pdata->slp_tr;
- *xtal_trim = pdata->xtal_trim;
- return 0;
- }
-
- *rstn = of_get_named_gpio(spi->dev.of_node, "reset-gpio", 0);
- *slp_tr = of_get_named_gpio(spi->dev.of_node, "sleep-gpio", 0);
- ret = of_property_read_u8(spi->dev.of_node, "xtal-trim", xtal_trim);
- if (ret < 0 && ret != -EINVAL)
- return ret;
-
- return 0;
-}
-
-static int
at86rf230_detect_device(struct at86rf230_local *lp)
{
unsigned int part, version, val;
@@ -1546,41 +1520,47 @@ static int at86rf230_probe(struct spi_device *spi)
{
struct ieee802154_hw *hw;
struct at86rf230_local *lp;
+ struct gpio_desc *slp_tr;
+ struct gpio_desc *rstn;
unsigned int status;
- int rc, irq_type, rstn, slp_tr;
- u8 xtal_trim = 0;
+ int rc, irq_type;
+ u8 xtal_trim;
if (!spi->irq) {
dev_err(&spi->dev, "no IRQ specified\n");
return -EINVAL;
}
- rc = at86rf230_get_pdata(spi, &rstn, &slp_tr, &xtal_trim);
+ rc = device_property_read_u8(&spi->dev, "xtal-trim", &xtal_trim);
if (rc < 0) {
- dev_err(&spi->dev, "failed to parse platform_data: %d\n", rc);
- return rc;
- }
-
- if (gpio_is_valid(rstn)) {
- rc = devm_gpio_request_one(&spi->dev, rstn,
- GPIOF_OUT_INIT_HIGH, "rstn");
- if (rc)
+ if (rc != -EINVAL) {
+ dev_err(&spi->dev,
+ "failed to parse xtal-trim: %d\n", rc);
return rc;
+ }
+ xtal_trim = 0;
}
- if (gpio_is_valid(slp_tr)) {
- rc = devm_gpio_request_one(&spi->dev, slp_tr,
- GPIOF_OUT_INIT_LOW, "slp_tr");
- if (rc)
- return rc;
- }
+ rstn = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
+ rc = PTR_ERR_OR_ZERO(rstn);
+ if (rc)
+ return rc;
+
+ gpiod_set_consumer_name(rstn, "rstn");
+
+ slp_tr = devm_gpiod_get_optional(&spi->dev, "sleep", GPIOD_OUT_LOW);
+ rc = PTR_ERR_OR_ZERO(slp_tr);
+ if (rc)
+ return rc;
+
+ gpiod_set_consumer_name(slp_tr, "slp_tr");
/* Reset */
- if (gpio_is_valid(rstn)) {
+ if (rstn) {
udelay(1);
- gpio_set_value_cansleep(rstn, 0);
+ gpiod_set_value_cansleep(rstn, 1);
udelay(1);
- gpio_set_value_cansleep(rstn, 1);
+ gpiod_set_value_cansleep(rstn, 0);
usleep_range(120, 240);
}
diff --git a/drivers/net/ieee802154/cc2520.c b/drivers/net/ieee802154/cc2520.c
index edc769daad07..a94d8dd71aad 100644
--- a/drivers/net/ieee802154/cc2520.c
+++ b/drivers/net/ieee802154/cc2520.c
@@ -7,14 +7,13 @@
*/
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
-#include <linux/spi/cc2520.h>
+#include <linux/property.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/skbuff.h>
-#include <linux/of_gpio.h>
#include <linux/ieee802154.h>
#include <linux/crc-ccitt.h>
#include <asm/unaligned.h>
@@ -206,7 +205,7 @@ struct cc2520_private {
struct mutex buffer_mutex; /* SPI buffer mutex */
bool is_tx; /* Flag for sync b/w Tx and Rx */
bool amplified; /* Flag for CC2591 */
- int fifo_pin; /* FIFO GPIO pin number */
+ struct gpio_desc *fifo_pin; /* FIFO GPIO pin number */
struct work_struct fifop_irqwork;/* Workqueue for FIFOP */
spinlock_t lock; /* Lock for is_tx*/
struct completion tx_complete; /* Work completion for Tx */
@@ -875,7 +874,7 @@ static void cc2520_fifop_irqwork(struct work_struct *work)
dev_dbg(&priv->spi->dev, "fifop interrupt received\n");
- if (gpio_get_value(priv->fifo_pin))
+ if (gpiod_get_value(priv->fifo_pin))
cc2520_rx(priv);
else
dev_dbg(&priv->spi->dev, "rxfifo overflow\n");
@@ -912,49 +911,11 @@ static irqreturn_t cc2520_sfd_isr(int irq, void *data)
return IRQ_HANDLED;
}
-static int cc2520_get_platform_data(struct spi_device *spi,
- struct cc2520_platform_data *pdata)
-{
- struct device_node *np = spi->dev.of_node;
- struct cc2520_private *priv = spi_get_drvdata(spi);
-
- if (!np) {
- struct cc2520_platform_data *spi_pdata = spi->dev.platform_data;
-
- if (!spi_pdata)
- return -ENOENT;
- *pdata = *spi_pdata;
- priv->fifo_pin = pdata->fifo;
- return 0;
- }
-
- pdata->fifo = of_get_named_gpio(np, "fifo-gpio", 0);
- priv->fifo_pin = pdata->fifo;
-
- pdata->fifop = of_get_named_gpio(np, "fifop-gpio", 0);
-
- pdata->sfd = of_get_named_gpio(np, "sfd-gpio", 0);
- pdata->cca = of_get_named_gpio(np, "cca-gpio", 0);
- pdata->vreg = of_get_named_gpio(np, "vreg-gpio", 0);
- pdata->reset = of_get_named_gpio(np, "reset-gpio", 0);
-
- /* CC2591 front end for CC2520 */
- if (of_property_read_bool(np, "amplified"))
- priv->amplified = true;
-
- return 0;
-}
-
static int cc2520_hw_init(struct cc2520_private *priv)
{
u8 status = 0, state = 0xff;
int ret;
int timeout = 100;
- struct cc2520_platform_data pdata;
-
- ret = cc2520_get_platform_data(priv->spi, &pdata);
- if (ret)
- goto err_ret;
ret = cc2520_read_register(priv, CC2520_FSMSTAT1, &state);
if (ret)
@@ -1071,7 +1032,11 @@ err_ret:
static int cc2520_probe(struct spi_device *spi)
{
struct cc2520_private *priv;
- struct cc2520_platform_data pdata;
+ struct gpio_desc *fifop;
+ struct gpio_desc *cca;
+ struct gpio_desc *sfd;
+ struct gpio_desc *reset;
+ struct gpio_desc *vreg;
int ret;
priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
@@ -1080,11 +1045,11 @@ static int cc2520_probe(struct spi_device *spi)
spi_set_drvdata(spi, priv);
- ret = cc2520_get_platform_data(spi, &pdata);
- if (ret < 0) {
- dev_err(&spi->dev, "no platform data\n");
- return -EINVAL;
- }
+ /* CC2591 front end for CC2520 */
+ /* Assumption that CC2591 is not connected */
+ priv->amplified = false;
+ if (device_property_read_bool(&spi->dev, "amplified"))
+ priv->amplified = true;
priv->spi = spi;
@@ -1098,80 +1063,53 @@ static int cc2520_probe(struct spi_device *spi)
spin_lock_init(&priv->lock);
init_completion(&priv->tx_complete);
- /* Assumption that CC2591 is not connected */
- priv->amplified = false;
-
/* Request all the gpio's */
- if (!gpio_is_valid(pdata.fifo)) {
+ priv->fifo_pin = devm_gpiod_get(&spi->dev, "fifo", GPIOD_IN);
+ if (IS_ERR(priv->fifo_pin)) {
dev_err(&spi->dev, "fifo gpio is not valid\n");
- ret = -EINVAL;
+ ret = PTR_ERR(priv->fifo_pin);
goto err_hw_init;
}
- ret = devm_gpio_request_one(&spi->dev, pdata.fifo,
- GPIOF_IN, "fifo");
- if (ret)
- goto err_hw_init;
-
- if (!gpio_is_valid(pdata.cca)) {
+ cca = devm_gpiod_get(&spi->dev, "cca", GPIOD_IN);
+ if (IS_ERR(cca)) {
dev_err(&spi->dev, "cca gpio is not valid\n");
- ret = -EINVAL;
+ ret = PTR_ERR(cca);
goto err_hw_init;
}
- ret = devm_gpio_request_one(&spi->dev, pdata.cca,
- GPIOF_IN, "cca");
- if (ret)
- goto err_hw_init;
-
- if (!gpio_is_valid(pdata.fifop)) {
+ fifop = devm_gpiod_get(&spi->dev, "fifop", GPIOD_IN);
+ if (IS_ERR(fifop)) {
dev_err(&spi->dev, "fifop gpio is not valid\n");
- ret = -EINVAL;
+ ret = PTR_ERR(fifop);
goto err_hw_init;
}
- ret = devm_gpio_request_one(&spi->dev, pdata.fifop,
- GPIOF_IN, "fifop");
- if (ret)
- goto err_hw_init;
-
- if (!gpio_is_valid(pdata.sfd)) {
+ sfd = devm_gpiod_get(&spi->dev, "sfd", GPIOD_IN);
+ if (IS_ERR(sfd)) {
dev_err(&spi->dev, "sfd gpio is not valid\n");
- ret = -EINVAL;
+ ret = PTR_ERR(sfd);
goto err_hw_init;
}
- ret = devm_gpio_request_one(&spi->dev, pdata.sfd,
- GPIOF_IN, "sfd");
- if (ret)
- goto err_hw_init;
-
- if (!gpio_is_valid(pdata.reset)) {
+ reset = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(reset)) {
dev_err(&spi->dev, "reset gpio is not valid\n");
- ret = -EINVAL;
+ ret = PTR_ERR(reset);
goto err_hw_init;
}
- ret = devm_gpio_request_one(&spi->dev, pdata.reset,
- GPIOF_OUT_INIT_LOW, "reset");
- if (ret)
- goto err_hw_init;
-
- if (!gpio_is_valid(pdata.vreg)) {
+ vreg = devm_gpiod_get(&spi->dev, "vreg", GPIOD_OUT_LOW);
+ if (IS_ERR(vreg)) {
dev_err(&spi->dev, "vreg gpio is not valid\n");
- ret = -EINVAL;
+ ret = PTR_ERR(vreg);
goto err_hw_init;
}
- ret = devm_gpio_request_one(&spi->dev, pdata.vreg,
- GPIOF_OUT_INIT_LOW, "vreg");
- if (ret)
- goto err_hw_init;
-
- gpio_set_value(pdata.vreg, HIGH);
+ gpiod_set_value(vreg, HIGH);
usleep_range(100, 150);
- gpio_set_value(pdata.reset, HIGH);
+ gpiod_set_value(reset, HIGH);
usleep_range(200, 250);
ret = cc2520_hw_init(priv);
@@ -1180,7 +1118,7 @@ static int cc2520_probe(struct spi_device *spi)
/* Set up fifop interrupt */
ret = devm_request_irq(&spi->dev,
- gpio_to_irq(pdata.fifop),
+ gpiod_to_irq(fifop),
cc2520_fifop_isr,
IRQF_TRIGGER_RISING,
dev_name(&spi->dev),
@@ -1192,7 +1130,7 @@ static int cc2520_probe(struct spi_device *spi)
/* Set up sfd interrupt */
ret = devm_request_irq(&spi->dev,
- gpio_to_irq(pdata.sfd),
+ gpiod_to_irq(sfd),
cc2520_sfd_isr,
IRQF_TRIGGER_FALLING,
dev_name(&spi->dev),
@@ -1241,7 +1179,7 @@ MODULE_DEVICE_TABLE(of, cc2520_of_ids);
static struct spi_driver cc2520_driver = {
.driver = {
.name = "cc2520",
- .of_match_table = of_match_ptr(cc2520_of_ids),
+ .of_match_table = cc2520_of_ids,
},
.id_table = cc2520_ids,
.probe = cc2520_probe,
diff --git a/include/linux/ieee802154.h b/include/linux/ieee802154.h
index 0303eb84d596..140f61ec0f5f 100644
--- a/include/linux/ieee802154.h
+++ b/include/linux/ieee802154.h
@@ -44,6 +44,13 @@
#define IEEE802154_SHORT_ADDR_LEN 2
#define IEEE802154_PAN_ID_LEN 2
+/* Duration in superframe order */
+#define IEEE802154_MAX_SCAN_DURATION 14
+#define IEEE802154_ACTIVE_SCAN_DURATION 15
+/* Superframe duration in slots */
+#define IEEE802154_SUPERFRAME_PERIOD 16
+/* Various periods expressed in symbols */
+#define IEEE802154_SLOT_PERIOD 60
#define IEEE802154_LIFS_PERIOD 40
#define IEEE802154_SIFS_PERIOD 12
#define IEEE802154_MAX_SIFS_FRAME_SIZE 18
diff --git a/include/linux/spi/at86rf230.h b/include/linux/spi/at86rf230.h
deleted file mode 100644
index d278576ab692..000000000000
--- a/include/linux/spi/at86rf230.h
+++ /dev/null
@@ -1,20 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * AT86RF230/RF231 driver
- *
- * Copyright (C) 2009-2012 Siemens AG
- *
- * Written by:
- * Dmitry Eremin-Solenikov <dmitry.baryshkov@siemens.com>
- */
-#ifndef AT86RF230_H
-#define AT86RF230_H
-
-struct at86rf230_platform_data {
- int rstn;
- int slp_tr;
- int dig2;
- u8 xtal_trim;
-};
-
-#endif
diff --git a/include/linux/spi/cc2520.h b/include/linux/spi/cc2520.h
deleted file mode 100644
index 449bacf10700..000000000000
--- a/include/linux/spi/cc2520.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Header file for cc2520 radio driver
- *
- * Copyright (C) 2014 Varka Bhadram <varkab@cdac.in>
- * Md.Jamal Mohiuddin <mjmohiuddin@cdac.in>
- * P Sowjanya <sowjanyap@cdac.in>
- */
-
-#ifndef __CC2520_H
-#define __CC2520_H
-
-struct cc2520_platform_data {
- int fifo;
- int fifop;
- int cca;
- int sfd;
- int reset;
- int vreg;
-};
-
-#endif
diff --git a/include/net/cfg802154.h b/include/net/cfg802154.h
index d09c393d229f..0c2778a836db 100644
--- a/include/net/cfg802154.h
+++ b/include/net/cfg802154.h
@@ -18,6 +18,8 @@
struct wpan_phy;
struct wpan_phy_cca;
+struct cfg802154_scan_request;
+struct cfg802154_beacon_request;
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
struct ieee802154_llsec_device_key;
@@ -67,6 +69,14 @@ struct cfg802154_ops {
struct wpan_dev *wpan_dev, bool mode);
int (*set_ackreq_default)(struct wpan_phy *wpan_phy,
struct wpan_dev *wpan_dev, bool ackreq);
+ int (*trigger_scan)(struct wpan_phy *wpan_phy,
+ struct cfg802154_scan_request *request);
+ int (*abort_scan)(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev);
+ int (*send_beacons)(struct wpan_phy *wpan_phy,
+ struct cfg802154_beacon_request *request);
+ int (*stop_beacons)(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev);
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
void (*get_llsec_table)(struct wpan_phy *wpan_phy,
struct wpan_dev *wpan_dev,
@@ -241,6 +251,17 @@ static inline void wpan_phy_net_set(struct wpan_phy *wpan_phy, struct net *net)
write_pnet(&wpan_phy->_net, net);
}
+static inline bool ieee802154_chan_is_valid(struct wpan_phy *phy,
+ u8 page, u8 channel)
+{
+ if (page > IEEE802154_MAX_PAGE ||
+ channel > IEEE802154_MAX_CHANNEL ||
+ !(phy->supported.channels[page] & BIT(channel)))
+ return false;
+
+ return true;
+}
+
/**
* struct ieee802154_addr - IEEE802.15.4 device address
* @mode: Address mode from frame header. Can be one of:
@@ -278,6 +299,60 @@ struct ieee802154_coord_desc {
bool gts_permit;
};
+/**
+ * struct cfg802154_scan_request - Scan request
+ *
+ * @type: type of scan to be performed
+ * @page: page on which to perform the scan
+ * @channels: channels in te %page to be scanned
+ * @duration: time spent on each channel, calculated with:
+ * aBaseSuperframeDuration * (2 ^ duration + 1)
+ * @wpan_dev: the wpan device on which to perform the scan
+ * @wpan_phy: the wpan phy on which to perform the scan
+ */
+struct cfg802154_scan_request {
+ enum nl802154_scan_types type;
+ u8 page;
+ u32 channels;
+ u8 duration;
+ struct wpan_dev *wpan_dev;
+ struct wpan_phy *wpan_phy;
+};
+
+/**
+ * struct cfg802154_beacon_request - Beacon request descriptor
+ *
+ * @interval: interval n between sendings, in multiple order of the super frame
+ * duration: aBaseSuperframeDuration * (2^n) unless the interval
+ * order is greater or equal to 15, in this case beacons won't be
+ * passively sent out at a fixed rate but instead inform the device
+ * that it should answer beacon requests as part of active scan
+ * procedures
+ * @wpan_dev: the concerned wpan device
+ * @wpan_phy: the wpan phy this was for
+ */
+struct cfg802154_beacon_request {
+ u8 interval;
+ struct wpan_dev *wpan_dev;
+ struct wpan_phy *wpan_phy;
+};
+
+/**
+ * struct cfg802154_mac_pkt - MAC packet descriptor (beacon/command)
+ * @node: MAC packets to process list member
+ * @skb: the received sk_buff
+ * @sdata: the interface on which @skb was received
+ * @page: page configuration when @skb was received
+ * @channel: channel configuration when @skb was received
+ */
+struct cfg802154_mac_pkt {
+ struct list_head node;
+ struct sk_buff *skb;
+ struct ieee802154_sub_if_data *sdata;
+ u8 page;
+ u8 channel;
+};
+
struct ieee802154_llsec_key_id {
u8 mode;
u8 id;
@@ -447,6 +522,7 @@ static inline const char *wpan_phy_name(struct wpan_phy *phy)
return dev_name(&phy->dev);
}
-void ieee802154_configure_durations(struct wpan_phy *phy);
+void ieee802154_configure_durations(struct wpan_phy *phy,
+ unsigned int page, unsigned int channel);
#endif /* __NET_CFG802154_H */
diff --git a/include/net/ieee802154_netdev.h b/include/net/ieee802154_netdev.h
index 4c33a20ea57f..da8a3e648c7a 100644
--- a/include/net/ieee802154_netdev.h
+++ b/include/net/ieee802154_netdev.h
@@ -38,6 +38,42 @@
#include <net/cfg802154.h>
+struct ieee802154_beacon_hdr {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u16 beacon_order:4,
+ superframe_order:4,
+ final_cap_slot:4,
+ battery_life_ext:1,
+ reserved0:1,
+ pan_coordinator:1,
+ assoc_permit:1;
+ u8 gts_count:3,
+ gts_reserved:4,
+ gts_permit:1;
+ u8 pend_short_addr_count:3,
+ reserved1:1,
+ pend_ext_addr_count:3,
+ reserved2:1;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u16 assoc_permit:1,
+ pan_coordinator:1,
+ reserved0:1,
+ battery_life_ext:1,
+ final_cap_slot:4,
+ superframe_order:4,
+ beacon_order:4;
+ u8 gts_permit:1,
+ gts_reserved:4,
+ gts_count:3;
+ u8 reserved2:1,
+ pend_ext_addr_count:3,
+ reserved1:1,
+ pend_short_addr_count:3;
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+} __packed;
+
struct ieee802154_sechdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
u8 level:3,
@@ -93,6 +129,13 @@ enum ieee802154_frame_version {
IEEE802154_MULTIPURPOSE_STD = IEEE802154_2003_STD,
};
+enum ieee802154_addressing_mode {
+ IEEE802154_NO_ADDRESSING,
+ IEEE802154_RESERVED,
+ IEEE802154_SHORT_ADDRESSING,
+ IEEE802154_EXTENDED_ADDRESSING,
+};
+
struct ieee802154_hdr {
struct ieee802154_hdr_fc fc;
u8 seq;
@@ -101,6 +144,11 @@ struct ieee802154_hdr {
struct ieee802154_sechdr sec;
};
+struct ieee802154_beacon_frame {
+ struct ieee802154_hdr mhr;
+ struct ieee802154_beacon_hdr mac_pl;
+};
+
/* pushes hdr onto the skb. fields of hdr->fc that can be calculated from
* the contents of hdr will be, and the actual value of those bits in
* hdr->fc will be ignored. this includes the INTRA_PAN bit and the frame
@@ -126,6 +174,10 @@ int ieee802154_hdr_peek_addrs(const struct sk_buff *skb,
*/
int ieee802154_hdr_peek(const struct sk_buff *skb, struct ieee802154_hdr *hdr);
+/* pushes a beacon frame into an skb */
+int ieee802154_beacon_push(struct sk_buff *skb,
+ struct ieee802154_beacon_frame *beacon);
+
int ieee802154_max_payload(const struct ieee802154_hdr *hdr);
static inline int
diff --git a/include/net/nl802154.h b/include/net/nl802154.h
index b79a89d5207c..8cd9d141f5af 100644
--- a/include/net/nl802154.h
+++ b/include/net/nl802154.h
@@ -73,6 +73,11 @@ enum nl802154_commands {
NL802154_CMD_DEL_SEC_LEVEL,
NL802154_CMD_SCAN_EVENT,
+ NL802154_CMD_TRIGGER_SCAN,
+ NL802154_CMD_ABORT_SCAN,
+ NL802154_CMD_SCAN_DONE,
+ NL802154_CMD_SEND_BEACONS,
+ NL802154_CMD_STOP_BEACONS,
/* add new commands above here */
@@ -134,6 +139,14 @@ enum nl802154_attrs {
NL802154_ATTR_NETNS_FD,
NL802154_ATTR_COORDINATOR,
+ NL802154_ATTR_SCAN_TYPE,
+ NL802154_ATTR_SCAN_FLAGS,
+ NL802154_ATTR_SCAN_CHANNELS,
+ NL802154_ATTR_SCAN_PREAMBLE_CODES,
+ NL802154_ATTR_SCAN_MEAN_PRF,
+ NL802154_ATTR_SCAN_DURATION,
+ NL802154_ATTR_SCAN_DONE_REASON,
+ NL802154_ATTR_BEACON_INTERVAL,
/* add attributes here, update the policy in nl802154.c */
@@ -260,6 +273,54 @@ enum nl802154_coord {
};
/**
+ * enum nl802154_scan_types - Scan types
+ *
+ * @__NL802154_SCAN_INVALID: scan type number 0 is reserved
+ * @NL802154_SCAN_ED: An ED scan allows a device to obtain a measure of the peak
+ * energy in each requested channel
+ * @NL802154_SCAN_ACTIVE: Locate any coordinator transmitting Beacon frames using
+ * a Beacon Request command
+ * @NL802154_SCAN_PASSIVE: Locate any coordinator transmitting Beacon frames
+ * @NL802154_SCAN_ORPHAN: Relocate coordinator following a loss of synchronisation
+ * @NL802154_SCAN_ENHANCED_ACTIVE: Same as Active using Enhanced Beacon Request
+ * command instead of Beacon Request command
+ * @NL802154_SCAN_RIT_PASSIVE: Passive scan for RIT Data Request command frames
+ * instead of Beacon frames
+ * @NL802154_SCAN_ATTR_MAX: Maximum SCAN attribute number
+ */
+enum nl802154_scan_types {
+ __NL802154_SCAN_INVALID,
+ NL802154_SCAN_ED,
+ NL802154_SCAN_ACTIVE,
+ NL802154_SCAN_PASSIVE,
+ NL802154_SCAN_ORPHAN,
+ NL802154_SCAN_ENHANCED_ACTIVE,
+ NL802154_SCAN_RIT_PASSIVE,
+
+ /* keep last */
+ NL802154_SCAN_ATTR_MAX,
+};
+
+/**
+ * enum nl802154_scan_done_reasons - End of scan reasons
+ *
+ * @__NL802154_SCAN_DONE_REASON_INVALID: scan done reason number 0 is reserved.
+ * @NL802154_SCAN_DONE_REASON_FINISHED: The scan just finished naturally after
+ * going through all the requested and possible (complex) channels.
+ * @NL802154_SCAN_DONE_REASON_ABORTED: The scan was aborted upon user request.
+ * a Beacon Request command
+ * @NL802154_SCAN_DONE_REASON_MAX: Maximum scan done reason attribute number.
+ */
+enum nl802154_scan_done_reasons {
+ __NL802154_SCAN_DONE_REASON_INVALID,
+ NL802154_SCAN_DONE_REASON_FINISHED,
+ NL802154_SCAN_DONE_REASON_ABORTED,
+
+ /* keep last */
+ NL802154_SCAN_DONE_REASON_MAX,
+};
+
+/**
* enum nl802154_cca_modes - cca modes
*
* @__NL802154_CCA_INVALID: cca mode number 0 is reserved
diff --git a/net/ieee802154/header_ops.c b/net/ieee802154/header_ops.c
index af337cf62764..35d384dfe29d 100644
--- a/net/ieee802154/header_ops.c
+++ b/net/ieee802154/header_ops.c
@@ -120,6 +120,30 @@ ieee802154_hdr_push(struct sk_buff *skb, struct ieee802154_hdr *hdr)
}
EXPORT_SYMBOL_GPL(ieee802154_hdr_push);
+int ieee802154_beacon_push(struct sk_buff *skb,
+ struct ieee802154_beacon_frame *beacon)
+{
+ struct ieee802154_beacon_hdr *mac_pl = &beacon->mac_pl;
+ struct ieee802154_hdr *mhr = &beacon->mhr;
+ int ret;
+
+ skb_reserve(skb, sizeof(*mhr));
+ ret = ieee802154_hdr_push(skb, mhr);
+ if (ret < 0)
+ return ret;
+
+ skb_reset_mac_header(skb);
+ skb->mac_len = ret;
+
+ skb_put_data(skb, mac_pl, sizeof(*mac_pl));
+
+ if (mac_pl->pend_short_addr_count || mac_pl->pend_ext_addr_count)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ieee802154_beacon_push);
+
static int
ieee802154_hdr_get_addr(const u8 *buf, int mode, bool omit_pan,
struct ieee802154_addr *addr)
diff --git a/net/ieee802154/nl802154.c b/net/ieee802154/nl802154.c
index 248ad5e46969..2215f576ee37 100644
--- a/net/ieee802154/nl802154.c
+++ b/net/ieee802154/nl802154.c
@@ -187,8 +187,8 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
[NL802154_ATTR_WPAN_DEV] = { .type = NLA_U64 },
- [NL802154_ATTR_PAGE] = { .type = NLA_U8, },
- [NL802154_ATTR_CHANNEL] = { .type = NLA_U8, },
+ [NL802154_ATTR_PAGE] = NLA_POLICY_MAX(NLA_U8, IEEE802154_MAX_PAGE),
+ [NL802154_ATTR_CHANNEL] = NLA_POLICY_MAX(NLA_U8, IEEE802154_MAX_CHANNEL),
[NL802154_ATTR_TX_POWER] = { .type = NLA_S32, },
@@ -221,6 +221,20 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
[NL802154_ATTR_COORDINATOR] = { .type = NLA_NESTED },
+ [NL802154_ATTR_SCAN_TYPE] =
+ NLA_POLICY_RANGE(NLA_U8, NL802154_SCAN_ED, NL802154_SCAN_RIT_PASSIVE),
+ [NL802154_ATTR_SCAN_CHANNELS] =
+ NLA_POLICY_MASK(NLA_U32, GENMASK(IEEE802154_MAX_CHANNEL, 0)),
+ [NL802154_ATTR_SCAN_PREAMBLE_CODES] = { .type = NLA_REJECT },
+ [NL802154_ATTR_SCAN_MEAN_PRF] = { .type = NLA_REJECT },
+ [NL802154_ATTR_SCAN_DURATION] =
+ NLA_POLICY_MAX(NLA_U8, IEEE802154_MAX_SCAN_DURATION),
+ [NL802154_ATTR_SCAN_DONE_REASON] =
+ NLA_POLICY_RANGE(NLA_U8, NL802154_SCAN_DONE_REASON_FINISHED,
+ NL802154_SCAN_DONE_REASON_ABORTED),
+ [NL802154_ATTR_BEACON_INTERVAL] =
+ NLA_POLICY_MAX(NLA_U8, IEEE802154_MAX_SCAN_DURATION),
+
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
[NL802154_ATTR_SEC_ENABLED] = { .type = NLA_U8, },
[NL802154_ATTR_SEC_OUT_LEVEL] = { .type = NLA_U32, },
@@ -969,8 +983,7 @@ static int nl802154_set_channel(struct sk_buff *skb, struct genl_info *info)
channel = nla_get_u8(info->attrs[NL802154_ATTR_CHANNEL]);
/* check 802.15.4 constraints */
- if (page > IEEE802154_MAX_PAGE || channel > IEEE802154_MAX_CHANNEL ||
- !(rdev->wpan_phy.supported.channels[page] & BIT(channel)))
+ if (!ieee802154_chan_is_valid(&rdev->wpan_phy, page, channel))
return -EINVAL;
return rdev_set_channel(rdev, page, channel);
@@ -1384,6 +1397,236 @@ int nl802154_scan_event(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
}
EXPORT_SYMBOL_GPL(nl802154_scan_event);
+static int nl802154_trigger_scan(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+ struct wpan_phy *wpan_phy = &rdev->wpan_phy;
+ struct cfg802154_scan_request *request;
+ u8 type;
+ int err;
+
+ if (wpan_dev->iftype == NL802154_IFTYPE_MONITOR) {
+ NL_SET_ERR_MSG(info->extack, "Monitors are not allowed to perform scans");
+ return -EOPNOTSUPP;
+ }
+
+ if (!nla_get_u8(info->attrs[NL802154_ATTR_SCAN_TYPE])) {
+ NL_SET_ERR_MSG(info->extack, "Malformed request, missing scan type");
+ return -EINVAL;
+ }
+
+ request = kzalloc(sizeof(*request), GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ request->wpan_dev = wpan_dev;
+ request->wpan_phy = wpan_phy;
+
+ type = nla_get_u8(info->attrs[NL802154_ATTR_SCAN_TYPE]);
+ switch (type) {
+ case NL802154_SCAN_PASSIVE:
+ request->type = type;
+ break;
+ default:
+ NL_SET_ERR_MSG_FMT(info->extack, "Unsupported scan type: %d", type);
+ err = -EINVAL;
+ goto free_request;
+ }
+
+ /* Use current page by default */
+ if (info->attrs[NL802154_ATTR_PAGE])
+ request->page = nla_get_u8(info->attrs[NL802154_ATTR_PAGE]);
+ else
+ request->page = wpan_phy->current_page;
+
+ /* Scan all supported channels by default */
+ if (info->attrs[NL802154_ATTR_SCAN_CHANNELS])
+ request->channels = nla_get_u32(info->attrs[NL802154_ATTR_SCAN_CHANNELS]);
+ else
+ request->channels = wpan_phy->supported.channels[request->page];
+
+ /* Use maximum duration order by default */
+ if (info->attrs[NL802154_ATTR_SCAN_DURATION])
+ request->duration = nla_get_u8(info->attrs[NL802154_ATTR_SCAN_DURATION]);
+ else
+ request->duration = IEEE802154_MAX_SCAN_DURATION;
+
+ err = rdev_trigger_scan(rdev, request);
+ if (err) {
+ pr_err("Failure starting scanning (%d)\n", err);
+ goto free_request;
+ }
+
+ return 0;
+
+free_request:
+ kfree(request);
+
+ return err;
+}
+
+static int nl802154_prep_scan_msg(struct sk_buff *msg,
+ struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev, u32 portid,
+ u32 seq, int flags, u8 cmd, u8 arg)
+{
+ void *hdr;
+
+ hdr = nl802154hdr_put(msg, portid, seq, flags, cmd);
+ if (!hdr)
+ return -ENOBUFS;
+
+ if (nla_put_u32(msg, NL802154_ATTR_WPAN_PHY, rdev->wpan_phy_idx))
+ goto nla_put_failure;
+
+ if (wpan_dev->netdev &&
+ nla_put_u32(msg, NL802154_ATTR_IFINDEX, wpan_dev->netdev->ifindex))
+ goto nla_put_failure;
+
+ if (nla_put_u64_64bit(msg, NL802154_ATTR_WPAN_DEV,
+ wpan_dev_id(wpan_dev), NL802154_ATTR_PAD))
+ goto nla_put_failure;
+
+ if (cmd == NL802154_CMD_SCAN_DONE &&
+ nla_put_u8(msg, NL802154_ATTR_SCAN_DONE_REASON, arg))
+ goto nla_put_failure;
+
+ genlmsg_end(msg, hdr);
+
+ return 0;
+
+nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+
+ return -EMSGSIZE;
+}
+
+static int nl802154_send_scan_msg(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev, u8 cmd, u8 arg)
+{
+ struct sk_buff *msg;
+ int ret;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ ret = nl802154_prep_scan_msg(msg, rdev, wpan_dev, 0, 0, 0, cmd, arg);
+ if (ret < 0) {
+ nlmsg_free(msg);
+ return ret;
+ }
+
+ return genlmsg_multicast_netns(&nl802154_fam,
+ wpan_phy_net(&rdev->wpan_phy), msg, 0,
+ NL802154_MCGRP_SCAN, GFP_KERNEL);
+}
+
+int nl802154_scan_started(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev)
+{
+ struct cfg802154_registered_device *rdev = wpan_phy_to_rdev(wpan_phy);
+ int err;
+
+ /* Ignore errors when there are no listeners */
+ err = nl802154_send_scan_msg(rdev, wpan_dev, NL802154_CMD_TRIGGER_SCAN, 0);
+ if (err == -ESRCH)
+ err = 0;
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(nl802154_scan_started);
+
+int nl802154_scan_done(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
+ enum nl802154_scan_done_reasons reason)
+{
+ struct cfg802154_registered_device *rdev = wpan_phy_to_rdev(wpan_phy);
+ int err;
+
+ /* Ignore errors when there are no listeners */
+ err = nl802154_send_scan_msg(rdev, wpan_dev, NL802154_CMD_SCAN_DONE, reason);
+ if (err == -ESRCH)
+ err = 0;
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(nl802154_scan_done);
+
+static int nl802154_abort_scan(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+
+ /* Resources are released in the notification helper above */
+ return rdev_abort_scan(rdev, wpan_dev);
+}
+
+static int
+nl802154_send_beacons(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+ struct wpan_phy *wpan_phy = &rdev->wpan_phy;
+ struct cfg802154_beacon_request *request;
+ int err;
+
+ if (wpan_dev->iftype != NL802154_IFTYPE_COORD) {
+ NL_SET_ERR_MSG(info->extack, "Only coordinators can send beacons");
+ return -EOPNOTSUPP;
+ }
+
+ if (wpan_dev->pan_id == cpu_to_le16(IEEE802154_PANID_BROADCAST)) {
+ NL_SET_ERR_MSG(info->extack, "Device is not part of any PAN");
+ return -EPERM;
+ }
+
+ request = kzalloc(sizeof(*request), GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ request->wpan_dev = wpan_dev;
+ request->wpan_phy = wpan_phy;
+
+ /* Use maximum duration order by default */
+ if (info->attrs[NL802154_ATTR_BEACON_INTERVAL])
+ request->interval = nla_get_u8(info->attrs[NL802154_ATTR_BEACON_INTERVAL]);
+ else
+ request->interval = IEEE802154_MAX_SCAN_DURATION;
+
+ err = rdev_send_beacons(rdev, request);
+ if (err) {
+ pr_err("Failure starting sending beacons (%d)\n", err);
+ goto free_request;
+ }
+
+ return 0;
+
+free_request:
+ kfree(request);
+
+ return err;
+}
+
+void nl802154_beaconing_done(struct wpan_dev *wpan_dev)
+{
+ /* NOP */
+}
+EXPORT_SYMBOL_GPL(nl802154_beaconing_done);
+
+static int
+nl802154_stop_beacons(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg802154_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+
+ /* Resources are released in the notification helper above */
+ return rdev_stop_beacons(rdev, wpan_dev);
+}
+
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
static const struct nla_policy nl802154_dev_addr_policy[NL802154_DEV_ADDR_ATTR_MAX + 1] = {
[NL802154_DEV_ADDR_ATTR_PAN_ID] = { .type = NLA_U16 },
@@ -2474,6 +2717,38 @@ static const struct genl_ops nl802154_ops[] = {
.internal_flags = NL802154_FLAG_NEED_NETDEV |
NL802154_FLAG_NEED_RTNL,
},
+ {
+ .cmd = NL802154_CMD_TRIGGER_SCAN,
+ .doit = nl802154_trigger_scan,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_CHECK_NETDEV_UP |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_ABORT_SCAN,
+ .doit = nl802154_abort_scan,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_CHECK_NETDEV_UP |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_SEND_BEACONS,
+ .doit = nl802154_send_beacons,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_CHECK_NETDEV_UP |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_STOP_BEACONS,
+ .doit = nl802154_stop_beacons,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_CHECK_NETDEV_UP |
+ NL802154_FLAG_NEED_RTNL,
+ },
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
{
.cmd = NL802154_CMD_SET_SEC_PARAMS,
diff --git a/net/ieee802154/nl802154.h b/net/ieee802154/nl802154.h
index 89b805500032..d69d950f9a6a 100644
--- a/net/ieee802154/nl802154.h
+++ b/net/ieee802154/nl802154.h
@@ -6,5 +6,9 @@ int nl802154_init(void);
void nl802154_exit(void);
int nl802154_scan_event(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
struct ieee802154_coord_desc *desc);
+int nl802154_scan_started(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev);
+int nl802154_scan_done(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
+ enum nl802154_scan_done_reasons reason);
+void nl802154_beaconing_done(struct wpan_dev *wpan_dev);
#endif /* __IEEE802154_NL802154_H */
diff --git a/net/ieee802154/rdev-ops.h b/net/ieee802154/rdev-ops.h
index 598f5af49775..5eaae15c610e 100644
--- a/net/ieee802154/rdev-ops.h
+++ b/net/ieee802154/rdev-ops.h
@@ -209,6 +209,62 @@ rdev_set_ackreq_default(struct cfg802154_registered_device *rdev,
return ret;
}
+static inline int rdev_trigger_scan(struct cfg802154_registered_device *rdev,
+ struct cfg802154_scan_request *request)
+{
+ int ret;
+
+ if (!rdev->ops->trigger_scan)
+ return -EOPNOTSUPP;
+
+ trace_802154_rdev_trigger_scan(&rdev->wpan_phy, request);
+ ret = rdev->ops->trigger_scan(&rdev->wpan_phy, request);
+ trace_802154_rdev_return_int(&rdev->wpan_phy, ret);
+ return ret;
+}
+
+static inline int rdev_abort_scan(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev)
+{
+ int ret;
+
+ if (!rdev->ops->abort_scan)
+ return -EOPNOTSUPP;
+
+ trace_802154_rdev_abort_scan(&rdev->wpan_phy, wpan_dev);
+ ret = rdev->ops->abort_scan(&rdev->wpan_phy, wpan_dev);
+ trace_802154_rdev_return_int(&rdev->wpan_phy, ret);
+ return ret;
+}
+
+static inline int rdev_send_beacons(struct cfg802154_registered_device *rdev,
+ struct cfg802154_beacon_request *request)
+{
+ int ret;
+
+ if (!rdev->ops->send_beacons)
+ return -EOPNOTSUPP;
+
+ trace_802154_rdev_send_beacons(&rdev->wpan_phy, request);
+ ret = rdev->ops->send_beacons(&rdev->wpan_phy, request);
+ trace_802154_rdev_return_int(&rdev->wpan_phy, ret);
+ return ret;
+}
+
+static inline int rdev_stop_beacons(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev)
+{
+ int ret;
+
+ if (!rdev->ops->stop_beacons)
+ return -EOPNOTSUPP;
+
+ trace_802154_rdev_stop_beacons(&rdev->wpan_phy, wpan_dev);
+ ret = rdev->ops->stop_beacons(&rdev->wpan_phy, wpan_dev);
+ trace_802154_rdev_return_int(&rdev->wpan_phy, ret);
+ return ret;
+}
+
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
/* TODO this is already a nl802154, so move into ieee802154 */
static inline void
diff --git a/net/ieee802154/trace.h b/net/ieee802154/trace.h
index 19c2e5d60e76..e5d8439b9e45 100644
--- a/net/ieee802154/trace.h
+++ b/net/ieee802154/trace.h
@@ -295,6 +295,67 @@ TRACE_EVENT(802154_rdev_set_ackreq_default,
WPAN_DEV_PR_ARG, BOOL_TO_STR(__entry->ackreq))
);
+TRACE_EVENT(802154_rdev_trigger_scan,
+ TP_PROTO(struct wpan_phy *wpan_phy,
+ struct cfg802154_scan_request *request),
+ TP_ARGS(wpan_phy, request),
+ TP_STRUCT__entry(
+ WPAN_PHY_ENTRY
+ __field(u8, page)
+ __field(u32, channels)
+ __field(u8, duration)
+ ),
+ TP_fast_assign(
+ WPAN_PHY_ASSIGN;
+ __entry->page = request->page;
+ __entry->channels = request->channels;
+ __entry->duration = request->duration;
+ ),
+ TP_printk(WPAN_PHY_PR_FMT ", scan, page: %d, channels: %x, duration %d",
+ WPAN_PHY_PR_ARG, __entry->page, __entry->channels, __entry->duration)
+);
+
+TRACE_EVENT(802154_rdev_send_beacons,
+ TP_PROTO(struct wpan_phy *wpan_phy,
+ struct cfg802154_beacon_request *request),
+ TP_ARGS(wpan_phy, request),
+ TP_STRUCT__entry(
+ WPAN_PHY_ENTRY
+ __field(u8, interval)
+ ),
+ TP_fast_assign(
+ WPAN_PHY_ASSIGN;
+ __entry->interval = request->interval;
+ ),
+ TP_printk(WPAN_PHY_PR_FMT ", sending beacons (interval order: %d)",
+ WPAN_PHY_PR_ARG, __entry->interval)
+);
+
+DECLARE_EVENT_CLASS(802154_wdev_template,
+ TP_PROTO(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev),
+ TP_ARGS(wpan_phy, wpan_dev),
+ TP_STRUCT__entry(
+ WPAN_PHY_ENTRY
+ WPAN_DEV_ENTRY
+ ),
+ TP_fast_assign(
+ WPAN_PHY_ASSIGN;
+ WPAN_DEV_ASSIGN;
+ ),
+ TP_printk(WPAN_PHY_PR_FMT ", " WPAN_DEV_PR_FMT,
+ WPAN_PHY_PR_ARG, WPAN_DEV_PR_ARG)
+);
+
+DEFINE_EVENT(802154_wdev_template, 802154_rdev_abort_scan,
+ TP_PROTO(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev),
+ TP_ARGS(wpan_phy, wpan_dev)
+);
+
+DEFINE_EVENT(802154_wdev_template, 802154_rdev_stop_beacons,
+ TP_PROTO(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev),
+ TP_ARGS(wpan_phy, wpan_dev)
+);
+
TRACE_EVENT(802154_rdev_return_int,
TP_PROTO(struct wpan_phy *wpan_phy, int ret),
TP_ARGS(wpan_phy, ret),
diff --git a/net/mac802154/Makefile b/net/mac802154/Makefile
index 4059295fdbf8..43d1347b37ee 100644
--- a/net/mac802154/Makefile
+++ b/net/mac802154/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_MAC802154) += mac802154.o
mac802154-objs := main.o rx.o tx.o mac_cmd.o mib.o \
- iface.o llsec.o util.o cfg.o trace.o
+ iface.o llsec.o util.o cfg.o scan.o trace.o
CFLAGS_trace.o := -I$(src)
diff --git a/net/mac802154/cfg.c b/net/mac802154/cfg.c
index dc2d918fac68..5c3cb019f751 100644
--- a/net/mac802154/cfg.c
+++ b/net/mac802154/cfg.c
@@ -114,11 +114,15 @@ ieee802154_set_channel(struct wpan_phy *wpan_phy, u8 page, u8 channel)
wpan_phy->current_channel == channel)
return 0;
+ /* Refuse to change channels during scanning or beaconing */
+ if (mac802154_is_scanning(local) || mac802154_is_beaconing(local))
+ return -EBUSY;
+
ret = drv_set_channel(local, page, channel);
if (!ret) {
wpan_phy->current_page = page;
wpan_phy->current_channel = channel;
- ieee802154_configure_durations(wpan_phy);
+ ieee802154_configure_durations(wpan_phy, page, channel);
}
return ret;
@@ -261,6 +265,56 @@ ieee802154_set_ackreq_default(struct wpan_phy *wpan_phy,
return 0;
}
+static int mac802154_trigger_scan(struct wpan_phy *wpan_phy,
+ struct cfg802154_scan_request *request)
+{
+ struct ieee802154_sub_if_data *sdata;
+
+ sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(request->wpan_dev);
+
+ ASSERT_RTNL();
+
+ return mac802154_trigger_scan_locked(sdata, request);
+}
+
+static int mac802154_abort_scan(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev)
+{
+ struct ieee802154_local *local = wpan_phy_priv(wpan_phy);
+ struct ieee802154_sub_if_data *sdata;
+
+ sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(wpan_dev);
+
+ ASSERT_RTNL();
+
+ return mac802154_abort_scan_locked(local, sdata);
+}
+
+static int mac802154_send_beacons(struct wpan_phy *wpan_phy,
+ struct cfg802154_beacon_request *request)
+{
+ struct ieee802154_sub_if_data *sdata;
+
+ sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(request->wpan_dev);
+
+ ASSERT_RTNL();
+
+ return mac802154_send_beacons_locked(sdata, request);
+}
+
+static int mac802154_stop_beacons(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev)
+{
+ struct ieee802154_local *local = wpan_phy_priv(wpan_phy);
+ struct ieee802154_sub_if_data *sdata;
+
+ sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(wpan_dev);
+
+ ASSERT_RTNL();
+
+ return mac802154_stop_beacons_locked(local, sdata);
+}
+
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
static void
ieee802154_get_llsec_table(struct wpan_phy *wpan_phy,
@@ -468,6 +522,10 @@ const struct cfg802154_ops mac802154_config_ops = {
.set_max_frame_retries = ieee802154_set_max_frame_retries,
.set_lbt_mode = ieee802154_set_lbt_mode,
.set_ackreq_default = ieee802154_set_ackreq_default,
+ .trigger_scan = mac802154_trigger_scan,
+ .abort_scan = mac802154_abort_scan,
+ .send_beacons = mac802154_send_beacons,
+ .stop_beacons = mac802154_stop_beacons,
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
.get_llsec_table = ieee802154_get_llsec_table,
.lock_llsec_table = ieee802154_lock_llsec_table,
diff --git a/net/mac802154/ieee802154_i.h b/net/mac802154/ieee802154_i.h
index 509e0172fe82..63bab99ed368 100644
--- a/net/mac802154/ieee802154_i.h
+++ b/net/mac802154/ieee802154_i.h
@@ -21,6 +21,11 @@
#include "llsec.h"
+enum ieee802154_ongoing {
+ IEEE802154_IS_SCANNING = BIT(0),
+ IEEE802154_IS_BEACONING = BIT(1),
+};
+
/* mac802154 device private data */
struct ieee802154_local {
struct ieee802154_hw hw;
@@ -43,15 +48,32 @@ struct ieee802154_local {
struct list_head interfaces;
struct mutex iflist_mtx;
- /* This one is used for scanning and other jobs not to be interfered
- * with serial driver.
- */
+ /* Data related workqueue */
struct workqueue_struct *workqueue;
+ /* MAC commands related workqueue */
+ struct workqueue_struct *mac_wq;
struct hrtimer ifs_timer;
+ /* Scanning */
+ u8 scan_page;
+ u8 scan_channel;
+ struct cfg802154_scan_request __rcu *scan_req;
+ struct delayed_work scan_work;
+
+ /* Beaconing */
+ unsigned int beacon_interval;
+ struct ieee802154_beacon_frame beacon;
+ struct cfg802154_beacon_request __rcu *beacon_req;
+ struct delayed_work beacon_work;
+
+ /* Asynchronous tasks */
+ struct list_head rx_beacon_list;
+ struct work_struct rx_beacon_work;
+
bool started;
bool suspended;
+ unsigned long ongoing;
struct tasklet_struct tasklet;
struct sk_buff_head skb_queue;
@@ -141,10 +163,16 @@ int ieee802154_mlme_op_pre(struct ieee802154_local *local);
int ieee802154_mlme_tx(struct ieee802154_local *local,
struct ieee802154_sub_if_data *sdata,
struct sk_buff *skb);
+int ieee802154_mlme_tx_locked(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata,
+ struct sk_buff *skb);
void ieee802154_mlme_op_post(struct ieee802154_local *local);
int ieee802154_mlme_tx_one(struct ieee802154_local *local,
struct ieee802154_sub_if_data *sdata,
struct sk_buff *skb);
+int ieee802154_mlme_tx_one_locked(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata,
+ struct sk_buff *skb);
netdev_tx_t
ieee802154_monitor_start_xmit(struct sk_buff *skb, struct net_device *dev);
netdev_tx_t
@@ -220,6 +248,33 @@ void mac802154_unlock_table(struct net_device *dev);
int mac802154_wpan_update_llsec(struct net_device *dev);
+/* PAN management handling */
+void mac802154_scan_worker(struct work_struct *work);
+int mac802154_trigger_scan_locked(struct ieee802154_sub_if_data *sdata,
+ struct cfg802154_scan_request *request);
+int mac802154_abort_scan_locked(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata);
+int mac802154_process_beacon(struct ieee802154_local *local,
+ struct sk_buff *skb,
+ u8 page, u8 channel);
+void mac802154_rx_beacon_worker(struct work_struct *work);
+
+static inline bool mac802154_is_scanning(struct ieee802154_local *local)
+{
+ return test_bit(IEEE802154_IS_SCANNING, &local->ongoing);
+}
+
+void mac802154_beacon_worker(struct work_struct *work);
+int mac802154_send_beacons_locked(struct ieee802154_sub_if_data *sdata,
+ struct cfg802154_beacon_request *request);
+int mac802154_stop_beacons_locked(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata);
+
+static inline bool mac802154_is_beaconing(struct ieee802154_local *local)
+{
+ return test_bit(IEEE802154_IS_BEACONING, &local->ongoing);
+}
+
/* interface handling */
int ieee802154_iface_init(void);
void ieee802154_iface_exit(void);
diff --git a/net/mac802154/iface.c b/net/mac802154/iface.c
index ac0b28025fb0..c0e2da5072be 100644
--- a/net/mac802154/iface.c
+++ b/net/mac802154/iface.c
@@ -302,6 +302,12 @@ static int mac802154_slave_close(struct net_device *dev)
ASSERT_RTNL();
+ if (mac802154_is_scanning(local))
+ mac802154_abort_scan_locked(local, sdata);
+
+ if (mac802154_is_beaconing(local))
+ mac802154_stop_beacons_locked(local, sdata);
+
netif_stop_queue(dev);
local->open_count--;
diff --git a/net/mac802154/llsec.c b/net/mac802154/llsec.c
index 55550ead2ced..8d2eabc71bbe 100644
--- a/net/mac802154/llsec.c
+++ b/net/mac802154/llsec.c
@@ -707,7 +707,10 @@ int mac802154_llsec_encrypt(struct mac802154_llsec *sec, struct sk_buff *skb)
hlen = ieee802154_hdr_pull(skb, &hdr);
- if (hlen < 0 || hdr.fc.type != IEEE802154_FC_TYPE_DATA)
+ /* TODO: control frames security support */
+ if (hlen < 0 ||
+ (hdr.fc.type != IEEE802154_FC_TYPE_DATA &&
+ hdr.fc.type != IEEE802154_FC_TYPE_BEACON))
return -EINVAL;
if (!hdr.fc.security_enabled ||
diff --git a/net/mac802154/main.c b/net/mac802154/main.c
index 3ed31daf7b9c..ee23e234b998 100644
--- a/net/mac802154/main.c
+++ b/net/mac802154/main.c
@@ -89,6 +89,7 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops)
local->ops = ops;
INIT_LIST_HEAD(&local->interfaces);
+ INIT_LIST_HEAD(&local->rx_beacon_list);
mutex_init(&local->iflist_mtx);
tasklet_setup(&local->tasklet, ieee802154_tasklet_handler);
@@ -96,6 +97,9 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops)
skb_queue_head_init(&local->skb_queue);
INIT_WORK(&local->sync_tx_work, ieee802154_xmit_sync_worker);
+ INIT_DELAYED_WORK(&local->scan_work, mac802154_scan_worker);
+ INIT_WORK(&local->rx_beacon_work, mac802154_rx_beacon_worker);
+ INIT_DELAYED_WORK(&local->beacon_work, mac802154_beacon_worker);
/* init supported flags with 802.15.4 default ranges */
phy->supported.max_minbe = 8;
@@ -113,32 +117,33 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops)
}
EXPORT_SYMBOL(ieee802154_alloc_hw);
-void ieee802154_configure_durations(struct wpan_phy *phy)
+void ieee802154_configure_durations(struct wpan_phy *phy,
+ unsigned int page, unsigned int channel)
{
u32 duration = 0;
- switch (phy->current_page) {
+ switch (page) {
case 0:
- if (BIT(phy->current_channel) & 0x1)
+ if (BIT(channel) & 0x1)
/* 868 MHz BPSK 802.15.4-2003: 20 ksym/s */
duration = 50 * NSEC_PER_USEC;
- else if (BIT(phy->current_channel) & 0x7FE)
+ else if (BIT(channel) & 0x7FE)
/* 915 MHz BPSK 802.15.4-2003: 40 ksym/s */
duration = 25 * NSEC_PER_USEC;
- else if (BIT(phy->current_channel) & 0x7FFF800)
+ else if (BIT(channel) & 0x7FFF800)
/* 2400 MHz O-QPSK 802.15.4-2006: 62.5 ksym/s */
duration = 16 * NSEC_PER_USEC;
break;
case 2:
- if (BIT(phy->current_channel) & 0x1)
+ if (BIT(channel) & 0x1)
/* 868 MHz O-QPSK 802.15.4-2006: 25 ksym/s */
duration = 40 * NSEC_PER_USEC;
- else if (BIT(phy->current_channel) & 0x7FE)
+ else if (BIT(channel) & 0x7FE)
/* 915 MHz O-QPSK 802.15.4-2006: 62.5 ksym/s */
duration = 16 * NSEC_PER_USEC;
break;
case 3:
- if (BIT(phy->current_channel) & 0x3FFF)
+ if (BIT(channel) & 0x3FFF)
/* 2.4 GHz CSS 802.15.4a-2007: 1/6 Msym/s */
duration = 6 * NSEC_PER_USEC;
break;
@@ -184,6 +189,7 @@ static void ieee802154_setup_wpan_phy_pib(struct wpan_phy *wpan_phy)
int ieee802154_register_hw(struct ieee802154_hw *hw)
{
struct ieee802154_local *local = hw_to_local(hw);
+ char mac_wq_name[IFNAMSIZ + 10] = {};
struct net_device *dev;
int rc = -ENOSYS;
@@ -194,6 +200,13 @@ int ieee802154_register_hw(struct ieee802154_hw *hw)
goto out;
}
+ snprintf(mac_wq_name, IFNAMSIZ + 10, "%s-mac-cmds", wpan_phy_name(local->phy));
+ local->mac_wq = create_singlethread_workqueue(mac_wq_name);
+ if (!local->mac_wq) {
+ rc = -ENOMEM;
+ goto out_wq;
+ }
+
hrtimer_init(&local->ifs_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
local->ifs_timer.function = ieee802154_xmit_ifs_timer;
@@ -201,7 +214,8 @@ int ieee802154_register_hw(struct ieee802154_hw *hw)
ieee802154_setup_wpan_phy_pib(local->phy);
- ieee802154_configure_durations(local->phy);
+ ieee802154_configure_durations(local->phy, local->phy->current_page,
+ local->phy->current_channel);
if (!(hw->flags & IEEE802154_HW_CSMA_PARAMS)) {
local->phy->supported.min_csma_backoffs = 4;
@@ -222,7 +236,7 @@ int ieee802154_register_hw(struct ieee802154_hw *hw)
rc = wpan_phy_register(local->phy);
if (rc < 0)
- goto out_wq;
+ goto out_mac_wq;
rtnl_lock();
@@ -241,6 +255,8 @@ int ieee802154_register_hw(struct ieee802154_hw *hw)
out_phy:
wpan_phy_unregister(local->phy);
+out_mac_wq:
+ destroy_workqueue(local->mac_wq);
out_wq:
destroy_workqueue(local->workqueue);
out:
@@ -261,6 +277,7 @@ void ieee802154_unregister_hw(struct ieee802154_hw *hw)
rtnl_unlock();
+ destroy_workqueue(local->mac_wq);
destroy_workqueue(local->workqueue);
wpan_phy_unregister(local->phy);
}
diff --git a/net/mac802154/rx.c b/net/mac802154/rx.c
index 97bb4401dd3e..da0628ee3c89 100644
--- a/net/mac802154/rx.c
+++ b/net/mac802154/rx.c
@@ -29,12 +29,31 @@ static int ieee802154_deliver_skb(struct sk_buff *skb)
return netif_receive_skb(skb);
}
+void mac802154_rx_beacon_worker(struct work_struct *work)
+{
+ struct ieee802154_local *local =
+ container_of(work, struct ieee802154_local, rx_beacon_work);
+ struct cfg802154_mac_pkt *mac_pkt;
+
+ mac_pkt = list_first_entry_or_null(&local->rx_beacon_list,
+ struct cfg802154_mac_pkt, node);
+ if (!mac_pkt)
+ return;
+
+ mac802154_process_beacon(local, mac_pkt->skb, mac_pkt->page, mac_pkt->channel);
+
+ list_del(&mac_pkt->node);
+ kfree_skb(mac_pkt->skb);
+ kfree(mac_pkt);
+}
+
static int
ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata,
struct sk_buff *skb, const struct ieee802154_hdr *hdr)
{
- struct wpan_dev *wpan_dev = &sdata->wpan_dev;
struct wpan_phy *wpan_phy = sdata->local->hw.phy;
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+ struct cfg802154_mac_pkt *mac_pkt;
__le16 span, sshort;
int rc;
@@ -106,6 +125,21 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata,
switch (mac_cb(skb)->type) {
case IEEE802154_FC_TYPE_BEACON:
+ dev_dbg(&sdata->dev->dev, "BEACON received\n");
+ if (!mac802154_is_scanning(sdata->local))
+ goto fail;
+
+ mac_pkt = kzalloc(sizeof(*mac_pkt), GFP_ATOMIC);
+ if (!mac_pkt)
+ goto fail;
+
+ mac_pkt->skb = skb_get(skb);
+ mac_pkt->sdata = sdata;
+ mac_pkt->page = sdata->local->scan_page;
+ mac_pkt->channel = sdata->local->scan_channel;
+ list_add_tail(&mac_pkt->node, &sdata->local->rx_beacon_list);
+ queue_work(sdata->local->mac_wq, &sdata->local->rx_beacon_work);
+ return NET_RX_SUCCESS;
case IEEE802154_FC_TYPE_ACK:
case IEEE802154_FC_TYPE_MAC_CMD:
goto fail;
diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c
new file mode 100644
index 000000000000..9b0933a185eb
--- /dev/null
+++ b/net/mac802154/scan.c
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IEEE 802.15.4 scanning management
+ *
+ * Copyright (C) 2021 Qorvo US, Inc
+ * Authors:
+ * - David Girault <david.girault@qorvo.com>
+ * - Miquel Raynal <miquel.raynal@bootlin.com>
+ */
+
+#include <linux/module.h>
+#include <linux/rtnetlink.h>
+#include <net/mac802154.h>
+
+#include "ieee802154_i.h"
+#include "driver-ops.h"
+#include "../ieee802154/nl802154.h"
+
+#define IEEE802154_BEACON_MHR_SZ 13
+#define IEEE802154_BEACON_PL_SZ 4
+#define IEEE802154_BEACON_SKB_SZ (IEEE802154_BEACON_MHR_SZ + \
+ IEEE802154_BEACON_PL_SZ)
+
+/* mac802154_scan_cleanup_locked() must be called upon scan completion or abort.
+ * - Completions are asynchronous, not locked by the rtnl and decided by the
+ * scan worker.
+ * - Aborts are decided by userspace, and locked by the rtnl.
+ *
+ * Concurrent modifications to the PHY, the interfaces or the hardware is in
+ * general prevented by the rtnl. So in most cases we don't need additional
+ * protection.
+ *
+ * However, the scan worker get's triggered without anybody noticing and thus we
+ * must ensure the presence of the devices as well as data consistency:
+ * - The sub-interface and device driver module get both their reference
+ * counters incremented whenever we start a scan, so they cannot disappear
+ * during operation.
+ * - Data consistency is achieved by the use of rcu protected pointers.
+ */
+static int mac802154_scan_cleanup_locked(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata,
+ bool aborted)
+{
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+ struct wpan_phy *wpan_phy = local->phy;
+ struct cfg802154_scan_request *request;
+ u8 arg;
+
+ /* Prevent any further use of the scan request */
+ clear_bit(IEEE802154_IS_SCANNING, &local->ongoing);
+ cancel_delayed_work(&local->scan_work);
+ request = rcu_replace_pointer(local->scan_req, NULL, 1);
+ if (!request)
+ return 0;
+ kfree_rcu(request);
+
+ /* Advertize first, while we know the devices cannot be removed */
+ if (aborted)
+ arg = NL802154_SCAN_DONE_REASON_ABORTED;
+ else
+ arg = NL802154_SCAN_DONE_REASON_FINISHED;
+ nl802154_scan_done(wpan_phy, wpan_dev, arg);
+
+ /* Cleanup software stack */
+ ieee802154_mlme_op_post(local);
+
+ /* Set the hardware back in its original state */
+ drv_set_channel(local, wpan_phy->current_page,
+ wpan_phy->current_channel);
+ ieee802154_configure_durations(wpan_phy, wpan_phy->current_page,
+ wpan_phy->current_channel);
+ drv_stop(local);
+ synchronize_net();
+ sdata->required_filtering = sdata->iface_default_filtering;
+ drv_start(local, sdata->required_filtering, &local->addr_filt);
+
+ return 0;
+}
+
+int mac802154_abort_scan_locked(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata)
+{
+ ASSERT_RTNL();
+
+ if (!mac802154_is_scanning(local))
+ return -ESRCH;
+
+ return mac802154_scan_cleanup_locked(local, sdata, true);
+}
+
+static unsigned int mac802154_scan_get_channel_time(u8 duration_order,
+ u8 symbol_duration)
+{
+ u64 base_super_frame_duration = (u64)symbol_duration *
+ IEEE802154_SUPERFRAME_PERIOD * IEEE802154_SLOT_PERIOD;
+
+ return usecs_to_jiffies(base_super_frame_duration *
+ (BIT(duration_order) + 1));
+}
+
+static void mac802154_flush_queued_beacons(struct ieee802154_local *local)
+{
+ struct cfg802154_mac_pkt *mac_pkt, *tmp;
+
+ list_for_each_entry_safe(mac_pkt, tmp, &local->rx_beacon_list, node) {
+ list_del(&mac_pkt->node);
+ kfree_skb(mac_pkt->skb);
+ kfree(mac_pkt);
+ }
+}
+
+static void
+mac802154_scan_get_next_channel(struct ieee802154_local *local,
+ struct cfg802154_scan_request *scan_req,
+ u8 *channel)
+{
+ (*channel)++;
+ *channel = find_next_bit((const unsigned long *)&scan_req->channels,
+ IEEE802154_MAX_CHANNEL + 1,
+ *channel);
+}
+
+static int mac802154_scan_find_next_chan(struct ieee802154_local *local,
+ struct cfg802154_scan_request *scan_req,
+ u8 page, u8 *channel)
+{
+ mac802154_scan_get_next_channel(local, scan_req, channel);
+ if (*channel > IEEE802154_MAX_CHANNEL)
+ return -EINVAL;
+
+ return 0;
+}
+
+void mac802154_scan_worker(struct work_struct *work)
+{
+ struct ieee802154_local *local =
+ container_of(work, struct ieee802154_local, scan_work.work);
+ struct cfg802154_scan_request *scan_req;
+ struct ieee802154_sub_if_data *sdata;
+ unsigned int scan_duration = 0;
+ struct wpan_phy *wpan_phy;
+ u8 scan_req_duration;
+ u8 page, channel;
+ int ret;
+
+ /* Ensure the device receiver is turned off when changing channels
+ * because there is no atomic way to change the channel and know on
+ * which one a beacon might have been received.
+ */
+ drv_stop(local);
+ synchronize_net();
+ mac802154_flush_queued_beacons(local);
+
+ rcu_read_lock();
+ scan_req = rcu_dereference(local->scan_req);
+ if (unlikely(!scan_req)) {
+ rcu_read_unlock();
+ return;
+ }
+
+ sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(scan_req->wpan_dev);
+
+ /* Wait an arbitrary amount of time in case we cannot use the device */
+ if (local->suspended || !ieee802154_sdata_running(sdata)) {
+ rcu_read_unlock();
+ queue_delayed_work(local->mac_wq, &local->scan_work,
+ msecs_to_jiffies(1000));
+ return;
+ }
+
+ wpan_phy = scan_req->wpan_phy;
+ scan_req_duration = scan_req->duration;
+
+ /* Look for the next valid chan */
+ page = local->scan_page;
+ channel = local->scan_channel;
+ do {
+ ret = mac802154_scan_find_next_chan(local, scan_req, page, &channel);
+ if (ret) {
+ rcu_read_unlock();
+ goto end_scan;
+ }
+ } while (!ieee802154_chan_is_valid(scan_req->wpan_phy, page, channel));
+
+ rcu_read_unlock();
+
+ /* Bypass the stack on purpose when changing the channel */
+ rtnl_lock();
+ ret = drv_set_channel(local, page, channel);
+ rtnl_unlock();
+ if (ret) {
+ dev_err(&sdata->dev->dev,
+ "Channel change failure during scan, aborting (%d)\n", ret);
+ goto end_scan;
+ }
+
+ local->scan_page = page;
+ local->scan_channel = channel;
+
+ rtnl_lock();
+ ret = drv_start(local, IEEE802154_FILTERING_3_SCAN, &local->addr_filt);
+ rtnl_unlock();
+ if (ret) {
+ dev_err(&sdata->dev->dev,
+ "Restarting failure after channel change, aborting (%d)\n", ret);
+ goto end_scan;
+ }
+
+ ieee802154_configure_durations(wpan_phy, page, channel);
+ scan_duration = mac802154_scan_get_channel_time(scan_req_duration,
+ wpan_phy->symbol_duration);
+ dev_dbg(&sdata->dev->dev,
+ "Scan page %u channel %u for %ums\n",
+ page, channel, jiffies_to_msecs(scan_duration));
+ queue_delayed_work(local->mac_wq, &local->scan_work, scan_duration);
+ return;
+
+end_scan:
+ rtnl_lock();
+ mac802154_scan_cleanup_locked(local, sdata, false);
+ rtnl_unlock();
+}
+
+int mac802154_trigger_scan_locked(struct ieee802154_sub_if_data *sdata,
+ struct cfg802154_scan_request *request)
+{
+ struct ieee802154_local *local = sdata->local;
+
+ ASSERT_RTNL();
+
+ if (mac802154_is_scanning(local))
+ return -EBUSY;
+
+ /* TODO: support other scanning type */
+ if (request->type != NL802154_SCAN_PASSIVE)
+ return -EOPNOTSUPP;
+
+ /* Store scanning parameters */
+ rcu_assign_pointer(local->scan_req, request);
+
+ /* Software scanning requires to set promiscuous mode, so we need to
+ * pause the Tx queue during the entire operation.
+ */
+ ieee802154_mlme_op_pre(local);
+
+ sdata->required_filtering = IEEE802154_FILTERING_3_SCAN;
+ local->scan_page = request->page;
+ local->scan_channel = -1;
+ set_bit(IEEE802154_IS_SCANNING, &local->ongoing);
+
+ nl802154_scan_started(request->wpan_phy, request->wpan_dev);
+
+ queue_delayed_work(local->mac_wq, &local->scan_work, 0);
+
+ return 0;
+}
+
+int mac802154_process_beacon(struct ieee802154_local *local,
+ struct sk_buff *skb,
+ u8 page, u8 channel)
+{
+ struct ieee802154_beacon_hdr *bh = (void *)skb->data;
+ struct ieee802154_addr *src = &mac_cb(skb)->source;
+ struct cfg802154_scan_request *scan_req;
+ struct ieee802154_coord_desc desc;
+
+ if (skb->len != sizeof(*bh))
+ return -EINVAL;
+
+ if (unlikely(src->mode == IEEE802154_ADDR_NONE))
+ return -EINVAL;
+
+ dev_dbg(&skb->dev->dev,
+ "BEACON received on page %u channel %u\n",
+ page, channel);
+
+ memcpy(&desc.addr, src, sizeof(desc.addr));
+ desc.page = page;
+ desc.channel = channel;
+ desc.link_quality = mac_cb(skb)->lqi;
+ desc.superframe_spec = get_unaligned_le16(skb->data);
+ desc.gts_permit = bh->gts_permit;
+
+ trace_802154_scan_event(&desc);
+
+ rcu_read_lock();
+ scan_req = rcu_dereference(local->scan_req);
+ if (likely(scan_req))
+ nl802154_scan_event(scan_req->wpan_phy, scan_req->wpan_dev, &desc);
+ rcu_read_unlock();
+
+ return 0;
+}
+
+static int mac802154_transmit_beacon(struct ieee802154_local *local,
+ struct wpan_dev *wpan_dev)
+{
+ struct cfg802154_beacon_request *beacon_req;
+ struct ieee802154_sub_if_data *sdata;
+ struct sk_buff *skb;
+ int ret;
+
+ /* Update the sequence number */
+ local->beacon.mhr.seq = atomic_inc_return(&wpan_dev->bsn) & 0xFF;
+
+ skb = alloc_skb(IEEE802154_BEACON_SKB_SZ, GFP_KERNEL);
+ if (!skb)
+ return -ENOBUFS;
+
+ rcu_read_lock();
+ beacon_req = rcu_dereference(local->beacon_req);
+ if (unlikely(!beacon_req)) {
+ rcu_read_unlock();
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(beacon_req->wpan_dev);
+ skb->dev = sdata->dev;
+
+ rcu_read_unlock();
+
+ ret = ieee802154_beacon_push(skb, &local->beacon);
+ if (ret) {
+ kfree_skb(skb);
+ return ret;
+ }
+
+ /* Using the MLME transmission helper for sending beacons is a bit
+ * overkill because we do not really care about the final outcome.
+ *
+ * Even though, going through the whole net stack with a regular
+ * dev_queue_xmit() is not relevant either because we want beacons to be
+ * sent "now" rather than go through the whole net stack scheduling
+ * (qdisc & co).
+ *
+ * Finally, using ieee802154_subif_start_xmit() would only be an option
+ * if we had a generic transmit helper which would acquire the
+ * HARD_TX_LOCK() to prevent buffer handling conflicts with regular
+ * packets.
+ *
+ * So for now we keep it simple and send beacons with our MLME helper,
+ * even if it stops the ieee802154 queue entirely during these
+ * transmissions, wich anyway does not have a huge impact on the
+ * performances given the current design of the stack.
+ */
+ return ieee802154_mlme_tx(local, sdata, skb);
+}
+
+void mac802154_beacon_worker(struct work_struct *work)
+{
+ struct ieee802154_local *local =
+ container_of(work, struct ieee802154_local, beacon_work.work);
+ struct cfg802154_beacon_request *beacon_req;
+ struct ieee802154_sub_if_data *sdata;
+ struct wpan_dev *wpan_dev;
+ int ret;
+
+ rcu_read_lock();
+ beacon_req = rcu_dereference(local->beacon_req);
+ if (unlikely(!beacon_req)) {
+ rcu_read_unlock();
+ return;
+ }
+
+ sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(beacon_req->wpan_dev);
+
+ /* Wait an arbitrary amount of time in case we cannot use the device */
+ if (local->suspended || !ieee802154_sdata_running(sdata)) {
+ rcu_read_unlock();
+ queue_delayed_work(local->mac_wq, &local->beacon_work,
+ msecs_to_jiffies(1000));
+ return;
+ }
+
+ wpan_dev = beacon_req->wpan_dev;
+
+ rcu_read_unlock();
+
+ dev_dbg(&sdata->dev->dev, "Sending beacon\n");
+ ret = mac802154_transmit_beacon(local, wpan_dev);
+ if (ret)
+ dev_err(&sdata->dev->dev,
+ "Beacon could not be transmitted (%d)\n", ret);
+
+ queue_delayed_work(local->mac_wq, &local->beacon_work,
+ local->beacon_interval);
+}
+
+int mac802154_stop_beacons_locked(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata)
+{
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+ struct cfg802154_beacon_request *request;
+
+ ASSERT_RTNL();
+
+ if (!mac802154_is_beaconing(local))
+ return -ESRCH;
+
+ clear_bit(IEEE802154_IS_BEACONING, &local->ongoing);
+ cancel_delayed_work(&local->beacon_work);
+ request = rcu_replace_pointer(local->beacon_req, NULL, 1);
+ if (!request)
+ return 0;
+ kfree_rcu(request);
+
+ nl802154_beaconing_done(wpan_dev);
+
+ return 0;
+}
+
+int mac802154_send_beacons_locked(struct ieee802154_sub_if_data *sdata,
+ struct cfg802154_beacon_request *request)
+{
+ struct ieee802154_local *local = sdata->local;
+
+ ASSERT_RTNL();
+
+ if (mac802154_is_beaconing(local))
+ mac802154_stop_beacons_locked(local, sdata);
+
+ /* Store beaconing parameters */
+ rcu_assign_pointer(local->beacon_req, request);
+
+ set_bit(IEEE802154_IS_BEACONING, &local->ongoing);
+
+ memset(&local->beacon, 0, sizeof(local->beacon));
+ local->beacon.mhr.fc.type = IEEE802154_FC_TYPE_BEACON;
+ local->beacon.mhr.fc.security_enabled = 0;
+ local->beacon.mhr.fc.frame_pending = 0;
+ local->beacon.mhr.fc.ack_request = 0;
+ local->beacon.mhr.fc.intra_pan = 0;
+ local->beacon.mhr.fc.dest_addr_mode = IEEE802154_NO_ADDRESSING;
+ local->beacon.mhr.fc.version = IEEE802154_2003_STD;
+ local->beacon.mhr.fc.source_addr_mode = IEEE802154_EXTENDED_ADDRESSING;
+ atomic_set(&request->wpan_dev->bsn, -1);
+ local->beacon.mhr.source.mode = IEEE802154_ADDR_LONG;
+ local->beacon.mhr.source.pan_id = request->wpan_dev->pan_id;
+ local->beacon.mhr.source.extended_addr = request->wpan_dev->extended_addr;
+ local->beacon.mac_pl.beacon_order = request->interval;
+ local->beacon.mac_pl.superframe_order = request->interval;
+ local->beacon.mac_pl.final_cap_slot = 0xf;
+ local->beacon.mac_pl.battery_life_ext = 0;
+ /* TODO: Fill this field depending on the coordinator capacity */
+ local->beacon.mac_pl.pan_coordinator = 1;
+ local->beacon.mac_pl.assoc_permit = 1;
+
+ /* Start the beacon work */
+ local->beacon_interval =
+ mac802154_scan_get_channel_time(request->interval,
+ request->wpan_phy->symbol_duration);
+ queue_delayed_work(local->mac_wq, &local->beacon_work, 0);
+
+ return 0;
+}
diff --git a/net/mac802154/tx.c b/net/mac802154/tx.c
index 9d8d43cf1e64..2a6f1ed763c9 100644
--- a/net/mac802154/tx.c
+++ b/net/mac802154/tx.c
@@ -137,34 +137,37 @@ int ieee802154_mlme_op_pre(struct ieee802154_local *local)
return ieee802154_sync_and_hold_queue(local);
}
-int ieee802154_mlme_tx(struct ieee802154_local *local,
- struct ieee802154_sub_if_data *sdata,
- struct sk_buff *skb)
+int ieee802154_mlme_tx_locked(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata,
+ struct sk_buff *skb)
{
- int ret;
-
/* Avoid possible calls to ->ndo_stop() when we asynchronously perform
* MLME transmissions.
*/
- rtnl_lock();
+ ASSERT_RTNL();
/* Ensure the device was not stopped, otherwise error out */
- if (!local->open_count) {
- rtnl_unlock();
+ if (!local->open_count)
return -ENETDOWN;
- }
/* Warn if the ieee802154 core thinks MLME frames can be sent while the
* net interface expects this cannot happen.
*/
- if (WARN_ON_ONCE(!netif_running(sdata->dev))) {
- rtnl_unlock();
+ if (WARN_ON_ONCE(!netif_running(sdata->dev)))
return -ENETDOWN;
- }
ieee802154_tx(local, skb);
- ret = ieee802154_sync_queue(local);
+ return ieee802154_sync_queue(local);
+}
+
+int ieee802154_mlme_tx(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ int ret;
+ rtnl_lock();
+ ret = ieee802154_mlme_tx_locked(local, sdata, skb);
rtnl_unlock();
return ret;
@@ -188,6 +191,19 @@ int ieee802154_mlme_tx_one(struct ieee802154_local *local,
return ret;
}
+int ieee802154_mlme_tx_one_locked(struct ieee802154_local *local,
+ struct ieee802154_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ int ret;
+
+ ieee802154_mlme_op_pre(local);
+ ret = ieee802154_mlme_tx_locked(local, sdata, skb);
+ ieee802154_mlme_op_post(local);
+
+ return ret;
+}
+
static bool ieee802154_queue_is_stopped(struct ieee802154_local *local)
{
return test_bit(WPAN_PHY_FLAG_STATE_QUEUE_STOPPED, &local->phy->flags);