summaryrefslogtreecommitdiff
path: root/net/mac802154
diff options
context:
space:
mode:
Diffstat (limited to 'net/mac802154')
-rw-r--r--net/mac802154/cfg.c6
-rw-r--r--net/mac802154/driver-ops.h253
-rw-r--r--net/mac802154/ieee802154_i.h56
-rw-r--r--net/mac802154/iface.c44
-rw-r--r--net/mac802154/main.c2
-rw-r--r--net/mac802154/rx.c29
-rw-r--r--net/mac802154/tx.c132
-rw-r--r--net/mac802154/util.c71
8 files changed, 441 insertions, 152 deletions
diff --git a/net/mac802154/cfg.c b/net/mac802154/cfg.c
index 1e4a9f74ed43..dc2d918fac68 100644
--- a/net/mac802154/cfg.c
+++ b/net/mac802154/cfg.c
@@ -46,7 +46,7 @@ static int ieee802154_suspend(struct wpan_phy *wpan_phy)
if (!local->open_count)
goto suspend;
- ieee802154_stop_queue(&local->hw);
+ ieee802154_sync_and_hold_queue(local);
synchronize_net();
/* stop hardware - this must stop RX */
@@ -67,12 +67,12 @@ static int ieee802154_resume(struct wpan_phy *wpan_phy)
goto wake_up;
/* restart hardware */
- ret = drv_start(local);
+ ret = drv_start(local, local->phy->filtering, &local->addr_filt);
if (ret)
return ret;
wake_up:
- ieee802154_wake_queue(&local->hw);
+ ieee802154_release_queue(local);
local->suspended = false;
return 0;
}
diff --git a/net/mac802154/driver-ops.h b/net/mac802154/driver-ops.h
index d23f0db98015..a7af3f0ddb3e 100644
--- a/net/mac802154/driver-ops.h
+++ b/net/mac802154/driver-ops.h
@@ -24,203 +24,290 @@ drv_xmit_sync(struct ieee802154_local *local, struct sk_buff *skb)
return local->ops->xmit_sync(&local->hw, skb);
}
-static inline int drv_start(struct ieee802154_local *local)
+static inline int drv_set_pan_id(struct ieee802154_local *local, __le16 pan_id)
{
+ struct ieee802154_hw_addr_filt filt;
int ret;
might_sleep();
- trace_802154_drv_start(local);
- local->started = true;
- smp_mb();
- ret = local->ops->start(&local->hw);
+ if (!local->ops->set_hw_addr_filt) {
+ WARN_ON(1);
+ return -EOPNOTSUPP;
+ }
+
+ filt.pan_id = pan_id;
+
+ trace_802154_drv_set_pan_id(local, pan_id);
+ ret = local->ops->set_hw_addr_filt(&local->hw, &filt,
+ IEEE802154_AFILT_PANID_CHANGED);
trace_802154_drv_return_int(local, ret);
return ret;
}
-static inline void drv_stop(struct ieee802154_local *local)
+static inline int
+drv_set_extended_addr(struct ieee802154_local *local, __le64 extended_addr)
{
- might_sleep();
+ struct ieee802154_hw_addr_filt filt;
+ int ret;
- trace_802154_drv_stop(local);
- local->ops->stop(&local->hw);
- trace_802154_drv_return_void(local);
+ might_sleep();
- /* sync away all work on the tasklet before clearing started */
- tasklet_disable(&local->tasklet);
- tasklet_enable(&local->tasklet);
+ if (!local->ops->set_hw_addr_filt) {
+ WARN_ON(1);
+ return -EOPNOTSUPP;
+ }
- barrier();
+ filt.ieee_addr = extended_addr;
- local->started = false;
+ trace_802154_drv_set_extended_addr(local, extended_addr);
+ ret = local->ops->set_hw_addr_filt(&local->hw, &filt,
+ IEEE802154_AFILT_IEEEADDR_CHANGED);
+ trace_802154_drv_return_int(local, ret);
+ return ret;
}
static inline int
-drv_set_channel(struct ieee802154_local *local, u8 page, u8 channel)
+drv_set_short_addr(struct ieee802154_local *local, __le16 short_addr)
{
+ struct ieee802154_hw_addr_filt filt;
int ret;
might_sleep();
- trace_802154_drv_set_channel(local, page, channel);
- ret = local->ops->set_channel(&local->hw, page, channel);
+ if (!local->ops->set_hw_addr_filt) {
+ WARN_ON(1);
+ return -EOPNOTSUPP;
+ }
+
+ filt.short_addr = short_addr;
+
+ trace_802154_drv_set_short_addr(local, short_addr);
+ ret = local->ops->set_hw_addr_filt(&local->hw, &filt,
+ IEEE802154_AFILT_SADDR_CHANGED);
trace_802154_drv_return_int(local, ret);
return ret;
}
-static inline int drv_set_tx_power(struct ieee802154_local *local, s32 mbm)
+static inline int
+drv_set_pan_coord(struct ieee802154_local *local, bool is_coord)
{
+ struct ieee802154_hw_addr_filt filt;
int ret;
might_sleep();
- if (!local->ops->set_txpower) {
+ if (!local->ops->set_hw_addr_filt) {
WARN_ON(1);
return -EOPNOTSUPP;
}
- trace_802154_drv_set_tx_power(local, mbm);
- ret = local->ops->set_txpower(&local->hw, mbm);
+ filt.pan_coord = is_coord;
+
+ trace_802154_drv_set_pan_coord(local, is_coord);
+ ret = local->ops->set_hw_addr_filt(&local->hw, &filt,
+ IEEE802154_AFILT_PANC_CHANGED);
trace_802154_drv_return_int(local, ret);
return ret;
}
-static inline int drv_set_cca_mode(struct ieee802154_local *local,
- const struct wpan_phy_cca *cca)
+static inline int
+drv_set_promiscuous_mode(struct ieee802154_local *local, bool on)
{
int ret;
might_sleep();
- if (!local->ops->set_cca_mode) {
+ if (!local->ops->set_promiscuous_mode) {
WARN_ON(1);
return -EOPNOTSUPP;
}
- trace_802154_drv_set_cca_mode(local, cca);
- ret = local->ops->set_cca_mode(&local->hw, cca);
+ trace_802154_drv_set_promiscuous_mode(local, on);
+ ret = local->ops->set_promiscuous_mode(&local->hw, on);
trace_802154_drv_return_int(local, ret);
return ret;
}
-static inline int drv_set_lbt_mode(struct ieee802154_local *local, bool mode)
+static inline int drv_start(struct ieee802154_local *local,
+ enum ieee802154_filtering_level level,
+ const struct ieee802154_hw_addr_filt *addr_filt)
{
int ret;
might_sleep();
- if (!local->ops->set_lbt) {
+ /* setup receive mode parameters e.g. address mode */
+ if (local->hw.flags & IEEE802154_HW_AFILT) {
+ ret = drv_set_pan_id(local, addr_filt->pan_id);
+ if (ret < 0)
+ return ret;
+
+ ret = drv_set_short_addr(local, addr_filt->short_addr);
+ if (ret < 0)
+ return ret;
+
+ ret = drv_set_extended_addr(local, addr_filt->ieee_addr);
+ if (ret < 0)
+ return ret;
+ }
+
+ switch (level) {
+ case IEEE802154_FILTERING_NONE:
+ fallthrough;
+ case IEEE802154_FILTERING_1_FCS:
+ fallthrough;
+ case IEEE802154_FILTERING_2_PROMISCUOUS:
+ /* TODO: Requires a different receive mode setup e.g.
+ * at86rf233 hardware.
+ */
+ fallthrough;
+ case IEEE802154_FILTERING_3_SCAN:
+ if (local->hw.flags & IEEE802154_HW_PROMISCUOUS) {
+ ret = drv_set_promiscuous_mode(local, true);
+ if (ret < 0)
+ return ret;
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ /* In practice other filtering levels can be requested, but as
+ * for now most hardware/drivers only support
+ * IEEE802154_FILTERING_NONE, we fallback to this actual
+ * filtering level in hardware and make our own additional
+ * filtering in mac802154 receive path.
+ *
+ * TODO: Move this logic to the device drivers as hardware may
+ * support more higher level filters. Hardware may also require
+ * a different order how register are set, which could currently
+ * be buggy, so all received parameters need to be moved to the
+ * start() callback and let the driver go into the mode before
+ * it will turn on receive handling.
+ */
+ local->phy->filtering = IEEE802154_FILTERING_NONE;
+ break;
+ case IEEE802154_FILTERING_4_FRAME_FIELDS:
+ /* Do not error out if IEEE802154_HW_PROMISCUOUS because we
+ * expect the hardware to operate at the level
+ * IEEE802154_FILTERING_4_FRAME_FIELDS anyway.
+ */
+ if (local->hw.flags & IEEE802154_HW_PROMISCUOUS) {
+ ret = drv_set_promiscuous_mode(local, false);
+ if (ret < 0)
+ return ret;
+ }
+
+ local->phy->filtering = IEEE802154_FILTERING_4_FRAME_FIELDS;
+ break;
+ default:
WARN_ON(1);
- return -EOPNOTSUPP;
+ return -EINVAL;
}
- trace_802154_drv_set_lbt_mode(local, mode);
- ret = local->ops->set_lbt(&local->hw, mode);
+ trace_802154_drv_start(local);
+ local->started = true;
+ smp_mb();
+ ret = local->ops->start(&local->hw);
trace_802154_drv_return_int(local, ret);
return ret;
}
+static inline void drv_stop(struct ieee802154_local *local)
+{
+ might_sleep();
+
+ trace_802154_drv_stop(local);
+ local->ops->stop(&local->hw);
+ trace_802154_drv_return_void(local);
+
+ /* sync away all work on the tasklet before clearing started */
+ tasklet_disable(&local->tasklet);
+ tasklet_enable(&local->tasklet);
+
+ barrier();
+
+ local->started = false;
+}
+
static inline int
-drv_set_cca_ed_level(struct ieee802154_local *local, s32 mbm)
+drv_set_channel(struct ieee802154_local *local, u8 page, u8 channel)
{
int ret;
might_sleep();
- if (!local->ops->set_cca_ed_level) {
- WARN_ON(1);
- return -EOPNOTSUPP;
- }
-
- trace_802154_drv_set_cca_ed_level(local, mbm);
- ret = local->ops->set_cca_ed_level(&local->hw, mbm);
+ trace_802154_drv_set_channel(local, page, channel);
+ ret = local->ops->set_channel(&local->hw, page, channel);
trace_802154_drv_return_int(local, ret);
return ret;
}
-static inline int drv_set_pan_id(struct ieee802154_local *local, __le16 pan_id)
+static inline int drv_set_tx_power(struct ieee802154_local *local, s32 mbm)
{
- struct ieee802154_hw_addr_filt filt;
int ret;
might_sleep();
- if (!local->ops->set_hw_addr_filt) {
+ if (!local->ops->set_txpower) {
WARN_ON(1);
return -EOPNOTSUPP;
}
- filt.pan_id = pan_id;
-
- trace_802154_drv_set_pan_id(local, pan_id);
- ret = local->ops->set_hw_addr_filt(&local->hw, &filt,
- IEEE802154_AFILT_PANID_CHANGED);
+ trace_802154_drv_set_tx_power(local, mbm);
+ ret = local->ops->set_txpower(&local->hw, mbm);
trace_802154_drv_return_int(local, ret);
return ret;
}
-static inline int
-drv_set_extended_addr(struct ieee802154_local *local, __le64 extended_addr)
+static inline int drv_set_cca_mode(struct ieee802154_local *local,
+ const struct wpan_phy_cca *cca)
{
- struct ieee802154_hw_addr_filt filt;
int ret;
might_sleep();
- if (!local->ops->set_hw_addr_filt) {
+ if (!local->ops->set_cca_mode) {
WARN_ON(1);
return -EOPNOTSUPP;
}
- filt.ieee_addr = extended_addr;
-
- trace_802154_drv_set_extended_addr(local, extended_addr);
- ret = local->ops->set_hw_addr_filt(&local->hw, &filt,
- IEEE802154_AFILT_IEEEADDR_CHANGED);
+ trace_802154_drv_set_cca_mode(local, cca);
+ ret = local->ops->set_cca_mode(&local->hw, cca);
trace_802154_drv_return_int(local, ret);
return ret;
}
-static inline int
-drv_set_short_addr(struct ieee802154_local *local, __le16 short_addr)
+static inline int drv_set_lbt_mode(struct ieee802154_local *local, bool mode)
{
- struct ieee802154_hw_addr_filt filt;
int ret;
might_sleep();
- if (!local->ops->set_hw_addr_filt) {
+ if (!local->ops->set_lbt) {
WARN_ON(1);
return -EOPNOTSUPP;
}
- filt.short_addr = short_addr;
-
- trace_802154_drv_set_short_addr(local, short_addr);
- ret = local->ops->set_hw_addr_filt(&local->hw, &filt,
- IEEE802154_AFILT_SADDR_CHANGED);
+ trace_802154_drv_set_lbt_mode(local, mode);
+ ret = local->ops->set_lbt(&local->hw, mode);
trace_802154_drv_return_int(local, ret);
return ret;
}
static inline int
-drv_set_pan_coord(struct ieee802154_local *local, bool is_coord)
+drv_set_cca_ed_level(struct ieee802154_local *local, s32 mbm)
{
- struct ieee802154_hw_addr_filt filt;
int ret;
might_sleep();
- if (!local->ops->set_hw_addr_filt) {
+ if (!local->ops->set_cca_ed_level) {
WARN_ON(1);
return -EOPNOTSUPP;
}
- filt.pan_coord = is_coord;
-
- trace_802154_drv_set_pan_coord(local, is_coord);
- ret = local->ops->set_hw_addr_filt(&local->hw, &filt,
- IEEE802154_AFILT_PANC_CHANGED);
+ trace_802154_drv_set_cca_ed_level(local, mbm);
+ ret = local->ops->set_cca_ed_level(&local->hw, mbm);
trace_802154_drv_return_int(local, ret);
return ret;
}
@@ -264,22 +351,4 @@ drv_set_max_frame_retries(struct ieee802154_local *local, s8 max_frame_retries)
return ret;
}
-static inline int
-drv_set_promiscuous_mode(struct ieee802154_local *local, bool on)
-{
- int ret;
-
- might_sleep();
-
- if (!local->ops->set_promiscuous_mode) {
- WARN_ON(1);
- return -EOPNOTSUPP;
- }
-
- trace_802154_drv_set_promiscuous_mode(local, on);
- ret = local->ops->set_promiscuous_mode(&local->hw, on);
- trace_802154_drv_return_int(local, ret);
- return ret;
-}
-
#endif /* __MAC802154_DRIVER_OPS */
diff --git a/net/mac802154/ieee802154_i.h b/net/mac802154/ieee802154_i.h
index 1381e6a5e180..509e0172fe82 100644
--- a/net/mac802154/ieee802154_i.h
+++ b/net/mac802154/ieee802154_i.h
@@ -26,6 +26,8 @@ struct ieee802154_local {
struct ieee802154_hw hw;
const struct ieee802154_ops *ops;
+ /* hardware address filter */
+ struct ieee802154_hw_addr_filt addr_filt;
/* ieee802154 phy */
struct wpan_phy *phy;
@@ -55,7 +57,7 @@ struct ieee802154_local {
struct sk_buff_head skb_queue;
struct sk_buff *tx_skb;
- struct work_struct tx_work;
+ struct work_struct sync_tx_work;
/* A negative Linux error code or a null/positive MLME error status */
int tx_result;
};
@@ -82,6 +84,16 @@ struct ieee802154_sub_if_data {
struct ieee802154_local *local;
struct net_device *dev;
+ /* Each interface starts and works in nominal state at a given filtering
+ * level given by iface_default_filtering, which is set once for all at
+ * the interface creation and should not evolve over time. For some MAC
+ * operations however, the filtering level may change temporarily, as
+ * reflected in the required_filtering field. The actual filtering at
+ * the PHY level may be different and is shown in struct wpan_phy.
+ */
+ enum ieee802154_filtering_level iface_default_filtering;
+ enum ieee802154_filtering_level required_filtering;
+
unsigned long state;
char name[IFNAMSIZ];
@@ -123,13 +135,53 @@ ieee802154_sdata_running(struct ieee802154_sub_if_data *sdata)
extern struct ieee802154_mlme_ops mac802154_mlme_wpan;
void ieee802154_rx(struct ieee802154_local *local, struct sk_buff *skb);
-void ieee802154_xmit_worker(struct work_struct *work);
+void ieee802154_xmit_sync_worker(struct work_struct *work);
+int ieee802154_sync_and_hold_queue(struct ieee802154_local *local);
+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);
+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);
netdev_tx_t
ieee802154_monitor_start_xmit(struct sk_buff *skb, struct net_device *dev);
netdev_tx_t
ieee802154_subif_start_xmit(struct sk_buff *skb, struct net_device *dev);
enum hrtimer_restart ieee802154_xmit_ifs_timer(struct hrtimer *timer);
+/**
+ * ieee802154_hold_queue - hold ieee802154 queue
+ * @local: main mac object
+ *
+ * Hold a queue by incrementing an atomic counter and requesting the netif
+ * queues to be stopped. The queues cannot be woken up while the counter has not
+ * been reset with as any ieee802154_release_queue() calls as needed.
+ */
+void ieee802154_hold_queue(struct ieee802154_local *local);
+
+/**
+ * ieee802154_release_queue - release ieee802154 queue
+ * @local: main mac object
+ *
+ * Release a queue which is held by decrementing an atomic counter and wake it
+ * up only if the counter reaches 0.
+ */
+void ieee802154_release_queue(struct ieee802154_local *local);
+
+/**
+ * ieee802154_disable_queue - disable ieee802154 queue
+ * @local: main mac object
+ *
+ * When trying to sync the Tx queue, we cannot just stop the queue
+ * (which is basically a bit being set without proper lock handling)
+ * because it would be racy. We actually need to call netif_tx_disable()
+ * instead, which is done by this helper. Restarting the queue can
+ * however still be done with a regular wake call.
+ */
+void ieee802154_disable_queue(struct ieee802154_local *local);
+
/* MIB callbacks */
void mac802154_dev_set_page_channel(struct net_device *dev, u8 page, u8 chan);
diff --git a/net/mac802154/iface.c b/net/mac802154/iface.c
index 500ed1b81250..d9b50884d34e 100644
--- a/net/mac802154/iface.c
+++ b/net/mac802154/iface.c
@@ -147,25 +147,12 @@ static int ieee802154_setup_hw(struct ieee802154_sub_if_data *sdata)
struct wpan_dev *wpan_dev = &sdata->wpan_dev;
int ret;
- if (local->hw.flags & IEEE802154_HW_PROMISCUOUS) {
- ret = drv_set_promiscuous_mode(local,
- wpan_dev->promiscuous_mode);
- if (ret < 0)
- return ret;
- }
+ sdata->required_filtering = sdata->iface_default_filtering;
if (local->hw.flags & IEEE802154_HW_AFILT) {
- ret = drv_set_pan_id(local, wpan_dev->pan_id);
- if (ret < 0)
- return ret;
-
- ret = drv_set_extended_addr(local, wpan_dev->extended_addr);
- if (ret < 0)
- return ret;
-
- ret = drv_set_short_addr(local, wpan_dev->short_addr);
- if (ret < 0)
- return ret;
+ local->addr_filt.pan_id = wpan_dev->pan_id;
+ local->addr_filt.ieee_addr = wpan_dev->extended_addr;
+ local->addr_filt.short_addr = wpan_dev->short_addr;
}
if (local->hw.flags & IEEE802154_HW_LBT) {
@@ -206,7 +193,8 @@ static int mac802154_slave_open(struct net_device *dev)
if (res)
goto err;
- res = drv_start(local);
+ res = drv_start(local, sdata->required_filtering,
+ &local->addr_filt);
if (res)
goto err;
}
@@ -223,15 +211,16 @@ err:
static int
ieee802154_check_mac_settings(struct ieee802154_local *local,
- struct wpan_dev *wpan_dev,
- struct wpan_dev *nwpan_dev)
+ struct ieee802154_sub_if_data *sdata,
+ struct ieee802154_sub_if_data *nsdata)
{
+ struct wpan_dev *nwpan_dev = &nsdata->wpan_dev;
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+
ASSERT_RTNL();
- if (local->hw.flags & IEEE802154_HW_PROMISCUOUS) {
- if (wpan_dev->promiscuous_mode != nwpan_dev->promiscuous_mode)
- return -EBUSY;
- }
+ if (sdata->iface_default_filtering != nsdata->iface_default_filtering)
+ return -EBUSY;
if (local->hw.flags & IEEE802154_HW_AFILT) {
if (wpan_dev->pan_id != nwpan_dev->pan_id ||
@@ -285,8 +274,7 @@ ieee802154_check_concurrent_iface(struct ieee802154_sub_if_data *sdata,
/* check all phy mac sublayer settings are the same.
* We have only one phy, different values makes trouble.
*/
- ret = ieee802154_check_mac_settings(local, wpan_dev,
- &nsdata->wpan_dev);
+ ret = ieee802154_check_mac_settings(local, sdata, nsdata);
if (ret < 0)
return ret;
}
@@ -586,7 +574,7 @@ ieee802154_setup_sdata(struct ieee802154_sub_if_data *sdata,
sdata->dev->priv_destructor = mac802154_wpan_free;
sdata->dev->netdev_ops = &mac802154_wpan_ops;
sdata->dev->ml_priv = &mac802154_mlme_wpan;
- wpan_dev->promiscuous_mode = false;
+ sdata->iface_default_filtering = IEEE802154_FILTERING_4_FRAME_FIELDS;
wpan_dev->header_ops = &ieee802154_header_ops;
mutex_init(&sdata->sec_mtx);
@@ -600,7 +588,7 @@ ieee802154_setup_sdata(struct ieee802154_sub_if_data *sdata,
case NL802154_IFTYPE_MONITOR:
sdata->dev->needs_free_netdev = true;
sdata->dev->netdev_ops = &mac802154_monitor_ops;
- wpan_dev->promiscuous_mode = true;
+ sdata->iface_default_filtering = IEEE802154_FILTERING_NONE;
break;
default:
BUG();
diff --git a/net/mac802154/main.c b/net/mac802154/main.c
index bd7bdb1219dd..40fab08df24b 100644
--- a/net/mac802154/main.c
+++ b/net/mac802154/main.c
@@ -95,7 +95,7 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops)
skb_queue_head_init(&local->skb_queue);
- INIT_WORK(&local->tx_work, ieee802154_xmit_worker);
+ INIT_WORK(&local->sync_tx_work, ieee802154_xmit_sync_worker);
/* init supported flags with 802.15.4 default ranges */
phy->supported.max_minbe = 8;
diff --git a/net/mac802154/rx.c b/net/mac802154/rx.c
index c439125ef2b9..2ae23a2f4a09 100644
--- a/net/mac802154/rx.c
+++ b/net/mac802154/rx.c
@@ -34,6 +34,7 @@ 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;
__le16 span, sshort;
int rc;
@@ -42,6 +43,17 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata,
span = wpan_dev->pan_id;
sshort = wpan_dev->short_addr;
+ /* Level 3 filtering: Only beacons are accepted during scans */
+ if (sdata->required_filtering == IEEE802154_FILTERING_3_SCAN &&
+ sdata->required_filtering > wpan_phy->filtering) {
+ if (mac_cb(skb)->type != IEEE802154_FC_TYPE_BEACON) {
+ dev_dbg(&sdata->dev->dev,
+ "drop non-beacon frame (0x%x) during scan\n",
+ mac_cb(skb)->type);
+ goto fail;
+ }
+ }
+
switch (mac_cb(skb)->dest.mode) {
case IEEE802154_ADDR_NONE:
if (hdr->source.mode != IEEE802154_ADDR_NONE)
@@ -114,8 +126,10 @@ fail:
static void
ieee802154_print_addr(const char *name, const struct ieee802154_addr *addr)
{
- if (addr->mode == IEEE802154_ADDR_NONE)
+ if (addr->mode == IEEE802154_ADDR_NONE) {
pr_debug("%s not present\n", name);
+ return;
+ }
pr_debug("%s PAN ID: %04x\n", name, le16_to_cpu(addr->pan_id));
if (addr->mode == IEEE802154_ADDR_SHORT) {
@@ -209,6 +223,13 @@ __ieee802154_rx_handle_packet(struct ieee802154_local *local,
if (!ieee802154_sdata_running(sdata))
continue;
+ /* Do not deliver packets received on interfaces expecting
+ * AACK=1 if the address filters where disabled.
+ */
+ if (local->hw.phy->filtering < IEEE802154_FILTERING_4_FRAME_FIELDS &&
+ sdata->required_filtering == IEEE802154_FILTERING_4_FRAME_FIELDS)
+ continue;
+
ieee802154_subif_frame(sdata, skb, &hdr);
skb = NULL;
break;
@@ -268,10 +289,8 @@ void ieee802154_rx(struct ieee802154_local *local, struct sk_buff *skb)
ieee802154_monitors_rx(local, skb);
- /* Check if transceiver doesn't validate the checksum.
- * If not we validate the checksum here.
- */
- if (local->hw.flags & IEEE802154_HW_RX_DROP_BAD_CKSUM) {
+ /* Level 1 filtering: Check the FCS by software when relevant */
+ if (local->hw.phy->filtering == IEEE802154_FILTERING_NONE) {
crc = crc_ccitt(0, skb->data, skb->len);
if (crc) {
rcu_read_unlock();
diff --git a/net/mac802154/tx.c b/net/mac802154/tx.c
index c829e4a75325..9d8d43cf1e64 100644
--- a/net/mac802154/tx.c
+++ b/net/mac802154/tx.c
@@ -22,10 +22,10 @@
#include "ieee802154_i.h"
#include "driver-ops.h"
-void ieee802154_xmit_worker(struct work_struct *work)
+void ieee802154_xmit_sync_worker(struct work_struct *work)
{
struct ieee802154_local *local =
- container_of(work, struct ieee802154_local, tx_work);
+ container_of(work, struct ieee802154_local, sync_tx_work);
struct sk_buff *skb = local->tx_skb;
struct net_device *dev = skb->dev;
int res;
@@ -43,7 +43,9 @@ void ieee802154_xmit_worker(struct work_struct *work)
err_tx:
/* Restart the netif queue on each sub_if_data object. */
- ieee802154_wake_queue(&local->hw);
+ ieee802154_release_queue(local);
+ if (atomic_dec_and_test(&local->phy->ongoing_txs))
+ wake_up(&local->phy->sync_txq);
kfree_skb(skb);
netdev_dbg(dev, "transmission failed\n");
}
@@ -65,7 +67,7 @@ ieee802154_tx(struct ieee802154_local *local, struct sk_buff *skb)
consume_skb(skb);
skb = nskb;
} else {
- goto err_tx;
+ goto err_free_skb;
}
}
@@ -74,32 +76,134 @@ ieee802154_tx(struct ieee802154_local *local, struct sk_buff *skb)
}
/* Stop the netif queue on each sub_if_data object. */
- ieee802154_stop_queue(&local->hw);
+ ieee802154_hold_queue(local);
+ atomic_inc(&local->phy->ongoing_txs);
- /* async is priority, otherwise sync is fallback */
+ /* Drivers should preferably implement the async callback. In some rare
+ * cases they only provide a sync callback which we will use as a
+ * fallback.
+ */
if (local->ops->xmit_async) {
unsigned int len = skb->len;
ret = drv_xmit_async(local, skb);
- if (ret) {
- ieee802154_wake_queue(&local->hw);
- goto err_tx;
- }
+ if (ret)
+ goto err_wake_netif_queue;
dev->stats.tx_packets++;
dev->stats.tx_bytes += len;
} else {
local->tx_skb = skb;
- queue_work(local->workqueue, &local->tx_work);
+ queue_work(local->workqueue, &local->sync_tx_work);
}
return NETDEV_TX_OK;
-err_tx:
+err_wake_netif_queue:
+ ieee802154_release_queue(local);
+ if (atomic_dec_and_test(&local->phy->ongoing_txs))
+ wake_up(&local->phy->sync_txq);
+err_free_skb:
kfree_skb(skb);
return NETDEV_TX_OK;
}
+static int ieee802154_sync_queue(struct ieee802154_local *local)
+{
+ int ret;
+
+ ieee802154_hold_queue(local);
+ ieee802154_disable_queue(local);
+ wait_event(local->phy->sync_txq, !atomic_read(&local->phy->ongoing_txs));
+ ret = local->tx_result;
+ ieee802154_release_queue(local);
+
+ return ret;
+}
+
+int ieee802154_sync_and_hold_queue(struct ieee802154_local *local)
+{
+ int ret;
+
+ ieee802154_hold_queue(local);
+ ret = ieee802154_sync_queue(local);
+ set_bit(WPAN_PHY_FLAG_STATE_QUEUE_STOPPED, &local->phy->flags);
+
+ return ret;
+}
+
+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 ret;
+
+ /* Avoid possible calls to ->ndo_stop() when we asynchronously perform
+ * MLME transmissions.
+ */
+ rtnl_lock();
+
+ /* Ensure the device was not stopped, otherwise error out */
+ if (!local->open_count) {
+ rtnl_unlock();
+ 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();
+ return -ENETDOWN;
+ }
+
+ ieee802154_tx(local, skb);
+ ret = ieee802154_sync_queue(local);
+
+ rtnl_unlock();
+
+ return ret;
+}
+
+void ieee802154_mlme_op_post(struct ieee802154_local *local)
+{
+ ieee802154_release_queue(local);
+}
+
+int ieee802154_mlme_tx_one(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(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);
+}
+
+static netdev_tx_t
+ieee802154_hot_tx(struct ieee802154_local *local, struct sk_buff *skb)
+{
+ /* Warn if the net interface tries to transmit frames while the
+ * ieee802154 core assumes the queue is stopped.
+ */
+ WARN_ON_ONCE(ieee802154_queue_is_stopped(local));
+
+ return ieee802154_tx(local, skb);
+}
+
netdev_tx_t
ieee802154_monitor_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
@@ -107,7 +211,7 @@ ieee802154_monitor_start_xmit(struct sk_buff *skb, struct net_device *dev)
skb->skb_iif = dev->ifindex;
- return ieee802154_tx(sdata->local, skb);
+ return ieee802154_hot_tx(sdata->local, skb);
}
netdev_tx_t
@@ -129,5 +233,5 @@ ieee802154_subif_start_xmit(struct sk_buff *skb, struct net_device *dev)
skb->skb_iif = dev->ifindex;
- return ieee802154_tx(sdata->local, skb);
+ return ieee802154_hot_tx(sdata->local, skb);
}
diff --git a/net/mac802154/util.c b/net/mac802154/util.c
index 9f024d85563b..f08605f59b60 100644
--- a/net/mac802154/util.c
+++ b/net/mac802154/util.c
@@ -13,12 +13,23 @@
/* privid for wpan_phys to determine whether they belong to us or not */
const void *const mac802154_wpan_phy_privid = &mac802154_wpan_phy_privid;
-void ieee802154_wake_queue(struct ieee802154_hw *hw)
+/**
+ * ieee802154_wake_queue - wake ieee802154 queue
+ * @local: main mac object
+ *
+ * Tranceivers usually have either one transmit framebuffer or one framebuffer
+ * for both transmitting and receiving. Hence, the core currently only handles
+ * one frame at a time for each phy, which means we had to stop the queue to
+ * avoid new skb to come during the transmission. The queue then needs to be
+ * woken up after the operation.
+ */
+static void ieee802154_wake_queue(struct ieee802154_hw *hw)
{
struct ieee802154_local *local = hw_to_local(hw);
struct ieee802154_sub_if_data *sdata;
rcu_read_lock();
+ clear_bit(WPAN_PHY_FLAG_STATE_QUEUE_STOPPED, &local->phy->flags);
list_for_each_entry_rcu(sdata, &local->interfaces, list) {
if (!sdata->dev)
continue;
@@ -27,9 +38,18 @@ void ieee802154_wake_queue(struct ieee802154_hw *hw)
}
rcu_read_unlock();
}
-EXPORT_SYMBOL(ieee802154_wake_queue);
-void ieee802154_stop_queue(struct ieee802154_hw *hw)
+/**
+ * ieee802154_stop_queue - stop ieee802154 queue
+ * @local: main mac object
+ *
+ * Tranceivers usually have either one transmit framebuffer or one framebuffer
+ * for both transmitting and receiving. Hence, the core currently only handles
+ * one frame at a time for each phy, which means we need to tell upper layers to
+ * stop giving us new skbs while we are busy with the transmitted one. The queue
+ * must then be stopped before transmitting.
+ */
+static void ieee802154_stop_queue(struct ieee802154_hw *hw)
{
struct ieee802154_local *local = hw_to_local(hw);
struct ieee802154_sub_if_data *sdata;
@@ -43,14 +63,47 @@ void ieee802154_stop_queue(struct ieee802154_hw *hw)
}
rcu_read_unlock();
}
-EXPORT_SYMBOL(ieee802154_stop_queue);
+
+void ieee802154_hold_queue(struct ieee802154_local *local)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&local->phy->queue_lock, flags);
+ if (!atomic_fetch_inc(&local->phy->hold_txs))
+ ieee802154_stop_queue(&local->hw);
+ spin_unlock_irqrestore(&local->phy->queue_lock, flags);
+}
+
+void ieee802154_release_queue(struct ieee802154_local *local)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&local->phy->queue_lock, flags);
+ if (atomic_dec_and_test(&local->phy->hold_txs))
+ ieee802154_wake_queue(&local->hw);
+ spin_unlock_irqrestore(&local->phy->queue_lock, flags);
+}
+
+void ieee802154_disable_queue(struct ieee802154_local *local)
+{
+ struct ieee802154_sub_if_data *sdata;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+ if (!sdata->dev)
+ continue;
+
+ netif_tx_disable(sdata->dev);
+ }
+ rcu_read_unlock();
+}
enum hrtimer_restart ieee802154_xmit_ifs_timer(struct hrtimer *timer)
{
struct ieee802154_local *local =
container_of(timer, struct ieee802154_local, ifs_timer);
- ieee802154_wake_queue(&local->hw);
+ ieee802154_release_queue(local);
return HRTIMER_NORESTART;
}
@@ -84,10 +137,12 @@ void ieee802154_xmit_complete(struct ieee802154_hw *hw, struct sk_buff *skb,
hw->phy->sifs_period * NSEC_PER_USEC,
HRTIMER_MODE_REL);
} else {
- ieee802154_wake_queue(hw);
+ ieee802154_release_queue(local);
}
dev_consume_skb_any(skb);
+ if (atomic_dec_and_test(&hw->phy->ongoing_txs))
+ wake_up(&hw->phy->sync_txq);
}
EXPORT_SYMBOL(ieee802154_xmit_complete);
@@ -97,8 +152,10 @@ void ieee802154_xmit_error(struct ieee802154_hw *hw, struct sk_buff *skb,
struct ieee802154_local *local = hw_to_local(hw);
local->tx_result = reason;
- ieee802154_wake_queue(hw);
+ ieee802154_release_queue(local);
dev_kfree_skb_any(skb);
+ if (atomic_dec_and_test(&hw->phy->ongoing_txs))
+ wake_up(&hw->phy->sync_txq);
}
EXPORT_SYMBOL(ieee802154_xmit_error);