summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2024-01-05 01:20:14 +0300
committerJakub Kicinski <kuba@kernel.org>2024-01-05 01:20:14 +0300
commit172b3fccf574eb760a2d88f19971c7e26d1441f0 (patch)
treea93d9968046cfcb2c61f92101f99e11bed259dad
parent6c8e2407100e4ff1db86e4af65b74be7895031a2 (diff)
parent2373699560a754079579b7722b50d1d38de1960e (diff)
downloadlinux-172b3fccf574eb760a2d88f19971c7e26d1441f0.tar.xz
Merge tag 'ieee802154-for-net-next-2023-12-20' of gitolite.kernel.org:pub/scm/linux/kernel/git/wpan/wpan-next
Miquel Raynal says: ==================== This pull request mainly brings support for dynamic associations in the WPAN world. Thanks to the recent improvements it was possible to discover nearby devices, it is now also possible to associate with them to form a sub-network using a specific PAN ID. The support includes several functions, such as: * Requesting an association to a coordinator, waiting for the response * Sending a disassociation notification to a coordinator * Receiving an association request when we are coordinator, answering the request (for now all devices are accepted up to a limit, to be refined) * Sending a disassociation notification to a child * Users may request the list of associated devices (the parent and the children). Here are a few example of userspace calls that can be made: # iwpan dev <dev> associate pan_id 2 coord $COORD # iwpan dev <dev> list_associations # iwpan dev <dev> disassociate ext_addr $COORD There are as well two patches from Uwe turning remove callbacks into void functions. * tag 'ieee802154-for-net-next-2023-12-20' of gitolite.kernel.org:pub/scm/linux/kernel/git/wpan/wpan-next: mac802154: Avoid new associations while disassociating ieee802154: Avoid confusing changes after associating mac802154: Only allow PAN controllers to process association requests mac802154: Use the PAN coordinator parameter when stamping packets mac80254: Provide real PAN coordinator info in beacons ieee802154: Give the user the association list mac802154: Handle disassociation notifications from peers mac802154: Follow the number of associated devices ieee802154: Add support for limiting the number of associated devices mac802154: Handle association requests from peers mac802154: Handle disassociations ieee802154: Add support for user disassociation requests mac802154: Handle associating ieee802154: Add support for user association requests ieee802154: Internal PAN management ieee802154: Let PAN IDs be reset ieee802154: hwsim: Convert to platform remove callback returning void ieee802154: fakelb: Convert to platform remove callback returning void ==================== Link: https://lore.kernel.org/r/20231220095556.4d9cef91@xps-13 Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--drivers/net/ieee802154/fakelb.c5
-rw-r--r--drivers/net/ieee802154/mac802154_hwsim.c6
-rw-r--r--include/net/cfg802154.h72
-rw-r--r--include/net/ieee802154_netdev.h60
-rw-r--r--include/net/nl802154.h22
-rw-r--r--net/ieee802154/Makefile2
-rw-r--r--net/ieee802154/core.c24
-rw-r--r--net/ieee802154/nl802154.c249
-rw-r--r--net/ieee802154/pan.c109
-rw-r--r--net/ieee802154/rdev-ops.h30
-rw-r--r--net/ieee802154/trace.h38
-rw-r--r--net/mac802154/cfg.c175
-rw-r--r--net/mac802154/ieee802154_i.h27
-rw-r--r--net/mac802154/main.c2
-rw-r--r--net/mac802154/rx.c36
-rw-r--r--net/mac802154/scan.c407
16 files changed, 1227 insertions, 37 deletions
diff --git a/drivers/net/ieee802154/fakelb.c b/drivers/net/ieee802154/fakelb.c
index 523d13ee02bf..35e55f198e05 100644
--- a/drivers/net/ieee802154/fakelb.c
+++ b/drivers/net/ieee802154/fakelb.c
@@ -221,7 +221,7 @@ err_slave:
return err;
}
-static int fakelb_remove(struct platform_device *pdev)
+static void fakelb_remove(struct platform_device *pdev)
{
struct fakelb_phy *phy, *tmp;
@@ -229,14 +229,13 @@ static int fakelb_remove(struct platform_device *pdev)
list_for_each_entry_safe(phy, tmp, &fakelb_phys, list)
fakelb_del(phy);
mutex_unlock(&fakelb_phys_lock);
- return 0;
}
static struct platform_device *ieee802154fake_dev;
static struct platform_driver ieee802154fake_driver = {
.probe = fakelb_probe,
- .remove = fakelb_remove,
+ .remove_new = fakelb_remove,
.driver = {
.name = "ieee802154fakelb",
},
diff --git a/drivers/net/ieee802154/mac802154_hwsim.c b/drivers/net/ieee802154/mac802154_hwsim.c
index 31cba9aa7636..2c2483bbe780 100644
--- a/drivers/net/ieee802154/mac802154_hwsim.c
+++ b/drivers/net/ieee802154/mac802154_hwsim.c
@@ -1035,7 +1035,7 @@ err_slave:
return err;
}
-static int hwsim_remove(struct platform_device *pdev)
+static void hwsim_remove(struct platform_device *pdev)
{
struct hwsim_phy *phy, *tmp;
@@ -1043,13 +1043,11 @@ static int hwsim_remove(struct platform_device *pdev)
list_for_each_entry_safe(phy, tmp, &hwsim_phys, list)
hwsim_del(phy);
mutex_unlock(&hwsim_phys_lock);
-
- return 0;
}
static struct platform_driver mac802154hwsim_driver = {
.probe = hwsim_probe,
- .remove = hwsim_remove,
+ .remove_new = hwsim_remove,
.driver = {
.name = "mac802154_hwsim",
},
diff --git a/include/net/cfg802154.h b/include/net/cfg802154.h
index f79ce133e51a..cd95711b12b8 100644
--- a/include/net/cfg802154.h
+++ b/include/net/cfg802154.h
@@ -20,6 +20,7 @@ struct wpan_phy;
struct wpan_phy_cca;
struct cfg802154_scan_request;
struct cfg802154_beacon_request;
+struct ieee802154_addr;
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
struct ieee802154_llsec_device_key;
@@ -77,6 +78,12 @@ struct cfg802154_ops {
struct cfg802154_beacon_request *request);
int (*stop_beacons)(struct wpan_phy *wpan_phy,
struct wpan_dev *wpan_dev);
+ int (*associate)(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *coord);
+ int (*disassociate)(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *target);
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
void (*get_llsec_table)(struct wpan_phy *wpan_phy,
struct wpan_dev *wpan_dev,
@@ -304,6 +311,22 @@ struct ieee802154_coord_desc {
};
/**
+ * struct ieee802154_pan_device - PAN device information
+ * @pan_id: the PAN ID of this device
+ * @mode: the preferred mode to reach the device
+ * @short_addr: the short address of this device
+ * @extended_addr: the extended address of this device
+ * @node: the list node
+ */
+struct ieee802154_pan_device {
+ __le16 pan_id;
+ u8 mode;
+ __le16 short_addr;
+ __le64 extended_addr;
+ struct list_head node;
+};
+
+/**
* struct cfg802154_scan_request - Scan request
*
* @type: type of scan to be performed
@@ -478,6 +501,13 @@ struct wpan_dev {
/* fallback for acknowledgment bit setting */
bool ackreq;
+
+ /* Associations */
+ struct mutex association_lock;
+ struct ieee802154_pan_device *parent;
+ struct list_head children;
+ unsigned int max_associations;
+ unsigned int nchildren;
};
#define to_phy(_dev) container_of(_dev, struct wpan_phy, dev)
@@ -529,4 +559,46 @@ static inline const char *wpan_phy_name(struct wpan_phy *phy)
void ieee802154_configure_durations(struct wpan_phy *phy,
unsigned int page, unsigned int channel);
+/**
+ * cfg802154_device_is_associated - Checks whether we are associated to any device
+ * @wpan_dev: the wpan device
+ * @return: true if we are associated
+ */
+bool cfg802154_device_is_associated(struct wpan_dev *wpan_dev);
+
+/**
+ * cfg802154_device_is_parent - Checks if a device is our coordinator
+ * @wpan_dev: the wpan device
+ * @target: the expected parent
+ * @return: true if @target is our coordinator
+ */
+bool cfg802154_device_is_parent(struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *target);
+
+/**
+ * cfg802154_device_is_child - Checks whether a device is associated to us
+ * @wpan_dev: the wpan device
+ * @target: the expected child
+ * @return: the PAN device
+ */
+struct ieee802154_pan_device *
+cfg802154_device_is_child(struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *target);
+
+/**
+ * cfg802154_set_max_associations - Limit the number of future associations
+ * @wpan_dev: the wpan device
+ * @max: the maximum number of devices we accept to associate
+ * @return: the old maximum value
+ */
+unsigned int cfg802154_set_max_associations(struct wpan_dev *wpan_dev,
+ unsigned int max);
+
+/**
+ * cfg802154_get_free_short_addr - Get a free address among the known devices
+ * @wpan_dev: the wpan device
+ * @return: a random short address expectedly unused on our PAN
+ */
+__le16 cfg802154_get_free_short_addr(struct wpan_dev *wpan_dev);
+
#endif /* __NET_CFG802154_H */
diff --git a/include/net/ieee802154_netdev.h b/include/net/ieee802154_netdev.h
index 063313df447d..4de858f9929e 100644
--- a/include/net/ieee802154_netdev.h
+++ b/include/net/ieee802154_netdev.h
@@ -125,6 +125,35 @@ struct ieee802154_hdr_fc {
#endif
};
+struct ieee802154_assoc_req_pl {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 reserved1:1,
+ device_type:1,
+ power_source:1,
+ rx_on_when_idle:1,
+ assoc_type:1,
+ reserved2:1,
+ security_cap:1,
+ alloc_addr:1;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 alloc_addr:1,
+ security_cap:1,
+ reserved2:1,
+ assoc_type:1,
+ rx_on_when_idle:1,
+ power_source:1,
+ device_type:1,
+ reserved1:1;
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+} __packed;
+
+struct ieee802154_assoc_resp_pl {
+ __le16 short_addr;
+ u8 status;
+} __packed;
+
enum ieee802154_frame_version {
IEEE802154_2003_STD,
IEEE802154_2006_STD,
@@ -140,6 +169,19 @@ enum ieee802154_addressing_mode {
IEEE802154_EXTENDED_ADDRESSING,
};
+enum ieee802154_association_status {
+ IEEE802154_ASSOCIATION_SUCCESSFUL = 0x00,
+ IEEE802154_PAN_AT_CAPACITY = 0x01,
+ IEEE802154_PAN_ACCESS_DENIED = 0x02,
+ IEEE802154_HOPPING_SEQUENCE_OFFSET_DUP = 0x03,
+ IEEE802154_FAST_ASSOCIATION_SUCCESSFUL = 0x80,
+};
+
+enum ieee802154_disassociation_reason {
+ IEEE802154_COORD_WISHES_DEVICE_TO_LEAVE = 0x1,
+ IEEE802154_DEVICE_WISHES_TO_LEAVE = 0x2,
+};
+
struct ieee802154_hdr {
struct ieee802154_hdr_fc fc;
u8 seq;
@@ -163,6 +205,24 @@ struct ieee802154_beacon_req_frame {
struct ieee802154_mac_cmd_pl mac_pl;
};
+struct ieee802154_association_req_frame {
+ struct ieee802154_hdr mhr;
+ struct ieee802154_mac_cmd_pl mac_pl;
+ struct ieee802154_assoc_req_pl assoc_req_pl;
+};
+
+struct ieee802154_association_resp_frame {
+ struct ieee802154_hdr mhr;
+ struct ieee802154_mac_cmd_pl mac_pl;
+ struct ieee802154_assoc_resp_pl assoc_resp_pl;
+};
+
+struct ieee802154_disassociation_notif_frame {
+ struct ieee802154_hdr mhr;
+ struct ieee802154_mac_cmd_pl mac_pl;
+ u8 disassoc_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
diff --git a/include/net/nl802154.h b/include/net/nl802154.h
index 8cd9d141f5af..4c752f799957 100644
--- a/include/net/nl802154.h
+++ b/include/net/nl802154.h
@@ -78,6 +78,10 @@ enum nl802154_commands {
NL802154_CMD_SCAN_DONE,
NL802154_CMD_SEND_BEACONS,
NL802154_CMD_STOP_BEACONS,
+ NL802154_CMD_ASSOCIATE,
+ NL802154_CMD_DISASSOCIATE,
+ NL802154_CMD_SET_MAX_ASSOCIATIONS,
+ NL802154_CMD_LIST_ASSOCIATIONS,
/* add new commands above here */
@@ -147,6 +151,8 @@ enum nl802154_attrs {
NL802154_ATTR_SCAN_DURATION,
NL802154_ATTR_SCAN_DONE_REASON,
NL802154_ATTR_BEACON_INTERVAL,
+ NL802154_ATTR_MAX_ASSOCIATIONS,
+ NL802154_ATTR_PEER,
/* add attributes here, update the policy in nl802154.c */
@@ -385,8 +391,6 @@ enum nl802154_supported_bool_states {
NL802154_SUPPORTED_BOOL_MAX = __NL802154_SUPPORTED_BOOL_AFTER_LAST - 1
};
-#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
-
enum nl802154_dev_addr_modes {
NL802154_DEV_ADDR_NONE,
__NL802154_DEV_ADDR_INVALID,
@@ -406,12 +410,26 @@ enum nl802154_dev_addr_attrs {
NL802154_DEV_ADDR_ATTR_SHORT,
NL802154_DEV_ADDR_ATTR_EXTENDED,
NL802154_DEV_ADDR_ATTR_PAD,
+ NL802154_DEV_ADDR_ATTR_PEER_TYPE,
/* keep last */
__NL802154_DEV_ADDR_ATTR_AFTER_LAST,
NL802154_DEV_ADDR_ATTR_MAX = __NL802154_DEV_ADDR_ATTR_AFTER_LAST - 1
};
+enum nl802154_peer_type {
+ NL802154_PEER_TYPE_UNSPEC,
+
+ NL802154_PEER_TYPE_PARENT,
+ NL802154_PEER_TYPE_CHILD,
+
+ /* keep last */
+ __NL802154_PEER_TYPE_AFTER_LAST,
+ NL802154_PEER_TYPE_MAX = __NL802154_PEER_TYPE_AFTER_LAST - 1
+};
+
+#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
+
enum nl802154_key_id_modes {
NL802154_KEY_ID_MODE_IMPLICIT,
NL802154_KEY_ID_MODE_INDEX,
diff --git a/net/ieee802154/Makefile b/net/ieee802154/Makefile
index f05b7bdae2aa..7bce67673e83 100644
--- a/net/ieee802154/Makefile
+++ b/net/ieee802154/Makefile
@@ -4,7 +4,7 @@ obj-$(CONFIG_IEEE802154_SOCKET) += ieee802154_socket.o
obj-y += 6lowpan/
ieee802154-y := netlink.o nl-mac.o nl-phy.o nl_policy.o core.o \
- header_ops.o sysfs.o nl802154.o trace.o
+ header_ops.o sysfs.o nl802154.o trace.o pan.o
ieee802154_socket-y := socket.o
CFLAGS_trace.o := -I$(src)
diff --git a/net/ieee802154/core.c b/net/ieee802154/core.c
index 57546e07e06a..60e8fff1347e 100644
--- a/net/ieee802154/core.c
+++ b/net/ieee802154/core.c
@@ -198,6 +198,25 @@ void wpan_phy_free(struct wpan_phy *phy)
}
EXPORT_SYMBOL(wpan_phy_free);
+static void cfg802154_free_peer_structures(struct wpan_dev *wpan_dev)
+{
+ struct ieee802154_pan_device *child, *tmp;
+
+ mutex_lock(&wpan_dev->association_lock);
+
+ kfree(wpan_dev->parent);
+ wpan_dev->parent = NULL;
+
+ list_for_each_entry_safe(child, tmp, &wpan_dev->children, node) {
+ list_del(&child->node);
+ kfree(child);
+ }
+
+ wpan_dev->nchildren = 0;
+
+ mutex_unlock(&wpan_dev->association_lock);
+}
+
int cfg802154_switch_netns(struct cfg802154_registered_device *rdev,
struct net *net)
{
@@ -276,6 +295,9 @@ static int cfg802154_netdev_notifier_call(struct notifier_block *nb,
wpan_dev->identifier = ++rdev->wpan_dev_id;
list_add_rcu(&wpan_dev->list, &rdev->wpan_dev_list);
rdev->devlist_generation++;
+ mutex_init(&wpan_dev->association_lock);
+ INIT_LIST_HEAD(&wpan_dev->children);
+ wpan_dev->max_associations = SZ_16K;
wpan_dev->netdev = dev;
break;
@@ -291,6 +313,8 @@ static int cfg802154_netdev_notifier_call(struct notifier_block *nb,
rdev->opencount++;
break;
case NETDEV_UNREGISTER:
+ cfg802154_free_peer_structures(wpan_dev);
+
/* It is possible to get NETDEV_UNREGISTER
* multiple times. To detect that, check
* that the interface is still on the list
diff --git a/net/ieee802154/nl802154.c b/net/ieee802154/nl802154.c
index 1a265a421308..7eb37de3add2 100644
--- a/net/ieee802154/nl802154.c
+++ b/net/ieee802154/nl802154.c
@@ -234,6 +234,8 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
NL802154_SCAN_DONE_REASON_ABORTED),
[NL802154_ATTR_BEACON_INTERVAL] =
NLA_POLICY_MAX(NLA_U8, IEEE802154_ACTIVE_SCAN_DURATION),
+ [NL802154_ATTR_MAX_ASSOCIATIONS] = { .type = NLA_U32 },
+ [NL802154_ATTR_PEER] = { .type = NLA_NESTED },
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
[NL802154_ATTR_SEC_ENABLED] = { .type = NLA_U8, },
@@ -248,7 +250,6 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
};
-#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
static int
nl802154_prepare_wpan_dev_dump(struct sk_buff *skb,
struct netlink_callback *cb,
@@ -307,7 +308,6 @@ nl802154_finish_wpan_dev_dump(struct cfg802154_registered_device *rdev)
{
rtnl_unlock();
}
-#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
/* message building helper */
static inline void *nl802154hdr_put(struct sk_buff *skb, u32 portid, u32 seq,
@@ -1087,15 +1087,14 @@ static int nl802154_set_pan_id(struct sk_buff *skb, struct genl_info *info)
pan_id = nla_get_le16(info->attrs[NL802154_ATTR_PAN_ID]);
- /* TODO
- * I am not sure about to check here on broadcast pan_id.
- * Broadcast is a valid setting, comment from 802.15.4:
- * If this value is 0xffff, the device is not associated.
- *
- * This could useful to simple deassociate an device.
+ /* Only allow changing the PAN ID when the device has no more
+ * associations ongoing to avoid confusing peers.
*/
- if (pan_id == cpu_to_le16(IEEE802154_PAN_ID_BROADCAST))
+ if (cfg802154_device_is_associated(wpan_dev)) {
+ NL_SET_ERR_MSG(info->extack,
+ "Existing associations, changing PAN ID forbidden");
return -EINVAL;
+ }
return rdev_set_pan_id(rdev, wpan_dev, pan_id);
}
@@ -1123,20 +1122,17 @@ static int nl802154_set_short_addr(struct sk_buff *skb, struct genl_info *info)
short_addr = nla_get_le16(info->attrs[NL802154_ATTR_SHORT_ADDR]);
- /* TODO
- * I am not sure about to check here on broadcast short_addr.
- * Broadcast is a valid setting, comment from 802.15.4:
- * A value of 0xfffe indicates that the device has
- * associated but has not been allocated an address. A
- * value of 0xffff indicates that the device does not
- * have a short address.
- *
- * I think we should allow to set these settings but
- * don't allow to allow socket communication with it.
+ /* The short address only has a meaning when part of a PAN, after a
+ * proper association procedure. However, we want to still offer the
+ * possibility to create static networks so changing the short address
+ * is only allowed when not already associated to other devices with
+ * the official handshake.
*/
- if (short_addr == cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC) ||
- short_addr == cpu_to_le16(IEEE802154_ADDR_SHORT_BROADCAST))
+ if (cfg802154_device_is_associated(wpan_dev)) {
+ NL_SET_ERR_MSG(info->extack,
+ "Existing associations, changing short address forbidden");
return -EINVAL;
+ }
return rdev_set_short_addr(rdev, wpan_dev, short_addr);
}
@@ -1638,6 +1634,189 @@ nl802154_stop_beacons(struct sk_buff *skb, struct genl_info *info)
return rdev_stop_beacons(rdev, wpan_dev);
}
+static int nl802154_associate(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;
+ struct wpan_phy *wpan_phy;
+ struct ieee802154_addr coord;
+ int err;
+
+ wpan_dev = dev->ieee802154_ptr;
+ wpan_phy = &rdev->wpan_phy;
+
+ if (wpan_phy->flags & WPAN_PHY_FLAG_DATAGRAMS_ONLY) {
+ NL_SET_ERR_MSG(info->extack, "PHY only supports datagrams");
+ return -EOPNOTSUPP;
+ }
+
+ if (!info->attrs[NL802154_ATTR_PAN_ID] ||
+ !info->attrs[NL802154_ATTR_EXTENDED_ADDR])
+ return -EINVAL;
+
+ coord.pan_id = nla_get_le16(info->attrs[NL802154_ATTR_PAN_ID]);
+ coord.mode = IEEE802154_ADDR_LONG;
+ coord.extended_addr = nla_get_le64(info->attrs[NL802154_ATTR_EXTENDED_ADDR]);
+
+ mutex_lock(&wpan_dev->association_lock);
+ err = rdev_associate(rdev, wpan_dev, &coord);
+ mutex_unlock(&wpan_dev->association_lock);
+ if (err)
+ pr_err("Association with PAN ID 0x%x failed (%d)\n",
+ le16_to_cpu(coord.pan_id), err);
+
+ return err;
+}
+
+static int nl802154_disassociate(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 ieee802154_addr target;
+
+ if (wpan_phy->flags & WPAN_PHY_FLAG_DATAGRAMS_ONLY) {
+ NL_SET_ERR_MSG(info->extack, "PHY only supports datagrams");
+ return -EOPNOTSUPP;
+ }
+
+ target.pan_id = wpan_dev->pan_id;
+
+ if (info->attrs[NL802154_ATTR_EXTENDED_ADDR]) {
+ target.mode = IEEE802154_ADDR_LONG;
+ target.extended_addr = nla_get_le64(info->attrs[NL802154_ATTR_EXTENDED_ADDR]);
+ } else if (info->attrs[NL802154_ATTR_SHORT_ADDR]) {
+ target.mode = IEEE802154_ADDR_SHORT;
+ target.short_addr = nla_get_le16(info->attrs[NL802154_ATTR_SHORT_ADDR]);
+ } else {
+ NL_SET_ERR_MSG(info->extack, "Device address is missing");
+ return -EINVAL;
+ }
+
+ mutex_lock(&wpan_dev->association_lock);
+ rdev_disassociate(rdev, wpan_dev, &target);
+ mutex_unlock(&wpan_dev->association_lock);
+
+ return 0;
+}
+
+static int nl802154_set_max_associations(struct sk_buff *skb, struct genl_info *info)
+{
+ struct net_device *dev = info->user_ptr[1];
+ struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+ unsigned int max_assoc;
+
+ if (!info->attrs[NL802154_ATTR_MAX_ASSOCIATIONS]) {
+ NL_SET_ERR_MSG(info->extack, "No maximum number of association given");
+ return -EINVAL;
+ }
+
+ max_assoc = nla_get_u32(info->attrs[NL802154_ATTR_MAX_ASSOCIATIONS]);
+
+ mutex_lock(&wpan_dev->association_lock);
+ cfg802154_set_max_associations(wpan_dev, max_assoc);
+ mutex_unlock(&wpan_dev->association_lock);
+
+ return 0;
+}
+
+static int nl802154_send_peer_info(struct sk_buff *msg,
+ struct netlink_callback *cb,
+ u32 seq, int flags,
+ struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev,
+ struct ieee802154_pan_device *peer,
+ enum nl802154_peer_type type)
+{
+ struct nlattr *nla;
+ void *hdr;
+
+ ASSERT_RTNL();
+
+ hdr = nl802154hdr_put(msg, NETLINK_CB(cb->skb).portid, seq, flags,
+ NL802154_CMD_LIST_ASSOCIATIONS);
+ if (!hdr)
+ return -ENOBUFS;
+
+ genl_dump_check_consistent(cb, hdr);
+
+ nla = nla_nest_start_noflag(msg, NL802154_ATTR_PEER);
+ if (!nla)
+ goto nla_put_failure;
+
+ if (nla_put_u8(msg, NL802154_DEV_ADDR_ATTR_PEER_TYPE, type))
+ goto nla_put_failure;
+
+ if (nla_put_u8(msg, NL802154_DEV_ADDR_ATTR_MODE, peer->mode))
+ goto nla_put_failure;
+
+ if (nla_put(msg, NL802154_DEV_ADDR_ATTR_SHORT,
+ IEEE802154_SHORT_ADDR_LEN, &peer->short_addr))
+ goto nla_put_failure;
+
+ if (nla_put(msg, NL802154_DEV_ADDR_ATTR_EXTENDED,
+ IEEE802154_EXTENDED_ADDR_LEN, &peer->extended_addr))
+ goto nla_put_failure;
+
+ nla_nest_end(msg, nla);
+
+ genlmsg_end(msg, hdr);
+
+ return 0;
+
+ nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+ return -EMSGSIZE;
+}
+
+static int nl802154_list_associations(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct cfg802154_registered_device *rdev;
+ struct ieee802154_pan_device *child;
+ struct wpan_dev *wpan_dev;
+ int err;
+
+ err = nl802154_prepare_wpan_dev_dump(skb, cb, &rdev, &wpan_dev);
+ if (err)
+ return err;
+
+ mutex_lock(&wpan_dev->association_lock);
+
+ if (cb->args[2])
+ goto out;
+
+ if (wpan_dev->parent) {
+ err = nl802154_send_peer_info(skb, cb, cb->nlh->nlmsg_seq,
+ NLM_F_MULTI, rdev, wpan_dev,
+ wpan_dev->parent,
+ NL802154_PEER_TYPE_PARENT);
+ if (err < 0)
+ goto out_err;
+ }
+
+ list_for_each_entry(child, &wpan_dev->children, node) {
+ err = nl802154_send_peer_info(skb, cb, cb->nlh->nlmsg_seq,
+ NLM_F_MULTI, rdev, wpan_dev,
+ child,
+ NL802154_PEER_TYPE_CHILD);
+ if (err < 0)
+ goto out_err;
+ }
+
+ cb->args[2] = 1;
+out:
+ err = skb->len;
+out_err:
+ mutex_unlock(&wpan_dev->association_lock);
+
+ nl802154_finish_wpan_dev_dump(rdev);
+
+ return err;
+}
+
#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 },
@@ -2759,6 +2938,34 @@ static const struct genl_ops nl802154_ops[] = {
NL802154_FLAG_CHECK_NETDEV_UP |
NL802154_FLAG_NEED_RTNL,
},
+ {
+ .cmd = NL802154_CMD_ASSOCIATE,
+ .doit = nl802154_associate,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_CHECK_NETDEV_UP |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_DISASSOCIATE,
+ .doit = nl802154_disassociate,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_CHECK_NETDEV_UP |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_SET_MAX_ASSOCIATIONS,
+ .doit = nl802154_set_max_associations,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL802154_FLAG_NEED_NETDEV |
+ NL802154_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL802154_CMD_LIST_ASSOCIATIONS,
+ .dumpit = nl802154_list_associations,
+ /* can be retrieved by unprivileged users */
+ },
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
{
.cmd = NL802154_CMD_SET_SEC_PARAMS,
diff --git a/net/ieee802154/pan.c b/net/ieee802154/pan.c
new file mode 100644
index 000000000000..249df7364b3e
--- /dev/null
+++ b/net/ieee802154/pan.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * IEEE 802.15.4 PAN management
+ *
+ * Copyright (C) 2023 Qorvo US, Inc
+ * Authors:
+ * - David Girault <david.girault@qorvo.com>
+ * - Miquel Raynal <miquel.raynal@bootlin.com>
+ */
+
+#include <linux/kernel.h>
+#include <net/cfg802154.h>
+#include <net/af_ieee802154.h>
+
+/* Checks whether a device address matches one from the PAN list.
+ * This helper is meant to be used only during PAN management, when we expect
+ * extended addresses to be used.
+ */
+static bool cfg802154_pan_device_is_matching(struct ieee802154_pan_device *pan_dev,
+ struct ieee802154_addr *ext_dev)
+{
+ if (!pan_dev || !ext_dev)
+ return false;
+
+ if (ext_dev->mode == IEEE802154_ADDR_SHORT)
+ return false;
+
+ return pan_dev->extended_addr == ext_dev->extended_addr;
+}
+
+bool cfg802154_device_is_associated(struct wpan_dev *wpan_dev)
+{
+ bool is_assoc;
+
+ mutex_lock(&wpan_dev->association_lock);
+ is_assoc = !list_empty(&wpan_dev->children) || wpan_dev->parent;
+ mutex_unlock(&wpan_dev->association_lock);
+
+ return is_assoc;
+}
+
+bool cfg802154_device_is_parent(struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *target)
+{
+ lockdep_assert_held(&wpan_dev->association_lock);
+
+ return cfg802154_pan_device_is_matching(wpan_dev->parent, target);
+}
+EXPORT_SYMBOL_GPL(cfg802154_device_is_parent);
+
+struct ieee802154_pan_device *
+cfg802154_device_is_child(struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *target)
+{
+ struct ieee802154_pan_device *child;
+
+ lockdep_assert_held(&wpan_dev->association_lock);
+
+ list_for_each_entry(child, &wpan_dev->children, node)
+ if (cfg802154_pan_device_is_matching(child, target))
+ return child;
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(cfg802154_device_is_child);
+
+__le16 cfg802154_get_free_short_addr(struct wpan_dev *wpan_dev)
+{
+ struct ieee802154_pan_device *child;
+ __le16 addr;
+
+ lockdep_assert_held(&wpan_dev->association_lock);
+
+ do {
+ get_random_bytes(&addr, 2);
+ if (addr == cpu_to_le16(IEEE802154_ADDR_SHORT_BROADCAST) ||
+ addr == cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC))
+ continue;
+
+ if (wpan_dev->short_addr == addr)
+ continue;
+
+ if (wpan_dev->parent && wpan_dev->parent->short_addr == addr)
+ continue;
+
+ list_for_each_entry(child, &wpan_dev->children, node)
+ if (child->short_addr == addr)
+ continue;
+
+ break;
+ } while (1);
+
+ return addr;
+}
+EXPORT_SYMBOL_GPL(cfg802154_get_free_short_addr);
+
+unsigned int cfg802154_set_max_associations(struct wpan_dev *wpan_dev,
+ unsigned int max)
+{
+ unsigned int old_max;
+
+ lockdep_assert_held(&wpan_dev->association_lock);
+
+ old_max = wpan_dev->max_associations;
+ wpan_dev->max_associations = max;
+
+ return old_max;
+}
+EXPORT_SYMBOL_GPL(cfg802154_set_max_associations);
diff --git a/net/ieee802154/rdev-ops.h b/net/ieee802154/rdev-ops.h
index 5eaae15c610e..64071ef6f57b 100644
--- a/net/ieee802154/rdev-ops.h
+++ b/net/ieee802154/rdev-ops.h
@@ -265,6 +265,36 @@ static inline int rdev_stop_beacons(struct cfg802154_registered_device *rdev,
return ret;
}
+static inline int rdev_associate(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *coord)
+{
+ int ret;
+
+ if (!rdev->ops->associate)
+ return -EOPNOTSUPP;
+
+ trace_802154_rdev_associate(&rdev->wpan_phy, wpan_dev, coord);
+ ret = rdev->ops->associate(&rdev->wpan_phy, wpan_dev, coord);
+ trace_802154_rdev_return_int(&rdev->wpan_phy, ret);
+ return ret;
+}
+
+static inline int rdev_disassociate(struct cfg802154_registered_device *rdev,
+ struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *target)
+{
+ int ret;
+
+ if (!rdev->ops->disassociate)
+ return -EOPNOTSUPP;
+
+ trace_802154_rdev_disassociate(&rdev->wpan_phy, wpan_dev, target);
+ ret = rdev->ops->disassociate(&rdev->wpan_phy, wpan_dev, target);
+ 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 c16db0b326fa..62aa6465253a 100644
--- a/net/ieee802154/trace.h
+++ b/net/ieee802154/trace.h
@@ -356,6 +356,44 @@ DEFINE_EVENT(802154_wdev_template, 802154_rdev_stop_beacons,
TP_ARGS(wpan_phy, wpan_dev)
);
+TRACE_EVENT(802154_rdev_associate,
+ TP_PROTO(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *coord),
+ TP_ARGS(wpan_phy, wpan_dev, coord),
+ TP_STRUCT__entry(
+ WPAN_PHY_ENTRY
+ WPAN_DEV_ENTRY
+ __field(__le64, addr)
+ ),
+ TP_fast_assign(
+ WPAN_PHY_ASSIGN;
+ WPAN_DEV_ASSIGN;
+ __entry->addr = coord->extended_addr;
+ ),
+ TP_printk(WPAN_PHY_PR_FMT ", " WPAN_DEV_PR_FMT ", associating with: 0x%llx",
+ WPAN_PHY_PR_ARG, WPAN_DEV_PR_ARG, __entry->addr)
+);
+
+TRACE_EVENT(802154_rdev_disassociate,
+ TP_PROTO(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *target),
+ TP_ARGS(wpan_phy, wpan_dev, target),
+ TP_STRUCT__entry(
+ WPAN_PHY_ENTRY
+ WPAN_DEV_ENTRY
+ __field(__le64, addr)
+ ),
+ TP_fast_assign(
+ WPAN_PHY_ASSIGN;
+ WPAN_DEV_ASSIGN;
+ __entry->addr = target->extended_addr;
+ ),
+ TP_printk(WPAN_PHY_PR_FMT ", " WPAN_DEV_PR_FMT ", disassociating with: 0x%llx",
+ WPAN_PHY_PR_ARG, WPAN_DEV_PR_ARG, __entry->addr)
+);
+
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/cfg.c b/net/mac802154/cfg.c
index 5c3cb019f751..ef7f23af043f 100644
--- a/net/mac802154/cfg.c
+++ b/net/mac802154/cfg.c
@@ -315,6 +315,179 @@ static int mac802154_stop_beacons(struct wpan_phy *wpan_phy,
return mac802154_stop_beacons_locked(local, sdata);
}
+static int mac802154_associate(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *coord)
+{
+ struct ieee802154_local *local = wpan_phy_priv(wpan_phy);
+ u64 ceaddr = swab64((__force u64)coord->extended_addr);
+ struct ieee802154_sub_if_data *sdata;
+ struct ieee802154_pan_device *parent;
+ __le16 short_addr;
+ int ret;
+
+ ASSERT_RTNL();
+
+ sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(wpan_dev);
+
+ if (wpan_dev->parent) {
+ dev_err(&sdata->dev->dev,
+ "Device %8phC is already associated\n", &ceaddr);
+ return -EPERM;
+ }
+
+ if (coord->mode == IEEE802154_SHORT_ADDRESSING)
+ return -EINVAL;
+
+ parent = kzalloc(sizeof(*parent), GFP_KERNEL);
+ if (!parent)
+ return -ENOMEM;
+
+ parent->pan_id = coord->pan_id;
+ parent->mode = coord->mode;
+ parent->extended_addr = coord->extended_addr;
+ parent->short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_BROADCAST);
+
+ /* Set the PAN ID hardware address filter beforehand to avoid dropping
+ * the association response with a destination PAN ID field set to the
+ * "new" PAN ID.
+ */
+ if (local->hw.flags & IEEE802154_HW_AFILT) {
+ ret = drv_set_pan_id(local, coord->pan_id);
+ if (ret < 0)
+ goto free_parent;
+ }
+
+ ret = mac802154_perform_association(sdata, parent, &short_addr);
+ if (ret)
+ goto reset_panid;
+
+ if (local->hw.flags & IEEE802154_HW_AFILT) {
+ ret = drv_set_short_addr(local, short_addr);
+ if (ret < 0)
+ goto reset_panid;
+ }
+
+ wpan_dev->pan_id = coord->pan_id;
+ wpan_dev->short_addr = short_addr;
+ wpan_dev->parent = parent;
+
+ return 0;
+
+reset_panid:
+ if (local->hw.flags & IEEE802154_HW_AFILT)
+ drv_set_pan_id(local, cpu_to_le16(IEEE802154_PAN_ID_BROADCAST));
+
+free_parent:
+ kfree(parent);
+ return ret;
+}
+
+static int mac802154_disassociate_from_parent(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev)
+{
+ struct ieee802154_local *local = wpan_phy_priv(wpan_phy);
+ struct ieee802154_pan_device *child, *tmp;
+ struct ieee802154_sub_if_data *sdata;
+ unsigned int max_assoc;
+ u64 eaddr;
+ int ret;
+
+ sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(wpan_dev);
+
+ /* Start by disassociating all the children and preventing new ones to
+ * attempt associations.
+ */
+ max_assoc = cfg802154_set_max_associations(wpan_dev, 0);
+ list_for_each_entry_safe(child, tmp, &wpan_dev->children, node) {
+ ret = mac802154_send_disassociation_notif(sdata, child,
+ IEEE802154_COORD_WISHES_DEVICE_TO_LEAVE);
+ if (ret) {
+ eaddr = swab64((__force u64)child->extended_addr);
+ dev_err(&sdata->dev->dev,
+ "Disassociation with %8phC may have failed (%d)\n",
+ &eaddr, ret);
+ }
+
+ list_del(&child->node);
+ }
+
+ ret = mac802154_send_disassociation_notif(sdata, wpan_dev->parent,
+ IEEE802154_DEVICE_WISHES_TO_LEAVE);
+ if (ret) {
+ eaddr = swab64((__force u64)wpan_dev->parent->extended_addr);
+ dev_err(&sdata->dev->dev,
+ "Disassociation from %8phC may have failed (%d)\n",
+ &eaddr, ret);
+ }
+
+ ret = 0;
+
+ kfree(wpan_dev->parent);
+ wpan_dev->parent = NULL;
+ wpan_dev->pan_id = cpu_to_le16(IEEE802154_PAN_ID_BROADCAST);
+ wpan_dev->short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_BROADCAST);
+
+ if (local->hw.flags & IEEE802154_HW_AFILT) {
+ ret = drv_set_pan_id(local, wpan_dev->pan_id);
+ if (ret < 0)
+ goto reset_mac_assoc;
+
+ ret = drv_set_short_addr(local, wpan_dev->short_addr);
+ if (ret < 0)
+ goto reset_mac_assoc;
+ }
+
+reset_mac_assoc:
+ cfg802154_set_max_associations(wpan_dev, max_assoc);
+
+ return ret;
+}
+
+static int mac802154_disassociate_child(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev,
+ struct ieee802154_pan_device *child)
+{
+ struct ieee802154_sub_if_data *sdata;
+ int ret;
+
+ sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(wpan_dev);
+
+ ret = mac802154_send_disassociation_notif(sdata, child,
+ IEEE802154_COORD_WISHES_DEVICE_TO_LEAVE);
+ if (ret)
+ return ret;
+
+ list_del(&child->node);
+ wpan_dev->nchildren--;
+ kfree(child);
+
+ return 0;
+}
+
+static int mac802154_disassociate(struct wpan_phy *wpan_phy,
+ struct wpan_dev *wpan_dev,
+ struct ieee802154_addr *target)
+{
+ u64 teaddr = swab64((__force u64)target->extended_addr);
+ struct ieee802154_pan_device *pan_device;
+
+ ASSERT_RTNL();
+
+ if (cfg802154_device_is_parent(wpan_dev, target))
+ return mac802154_disassociate_from_parent(wpan_phy, wpan_dev);
+
+ pan_device = cfg802154_device_is_child(wpan_dev, target);
+ if (pan_device)
+ return mac802154_disassociate_child(wpan_phy, wpan_dev,
+ pan_device);
+
+ dev_err(&wpan_dev->netdev->dev,
+ "Device %8phC is not associated with us\n", &teaddr);
+
+ return -EINVAL;
+}
+
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
static void
ieee802154_get_llsec_table(struct wpan_phy *wpan_phy,
@@ -526,6 +699,8 @@ const struct cfg802154_ops mac802154_config_ops = {
.abort_scan = mac802154_abort_scan,
.send_beacons = mac802154_send_beacons,
.stop_beacons = mac802154_stop_beacons,
+ .associate = mac802154_associate,
+ .disassociate = mac802154_disassociate,
#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 c347ec9ff8c9..08dd521a51a5 100644
--- a/net/mac802154/ieee802154_i.h
+++ b/net/mac802154/ieee802154_i.h
@@ -24,6 +24,7 @@
enum ieee802154_ongoing {
IEEE802154_IS_SCANNING = BIT(0),
IEEE802154_IS_BEACONING = BIT(1),
+ IEEE802154_IS_ASSOCIATING = BIT(2),
};
/* mac802154 device private data */
@@ -74,6 +75,13 @@ struct ieee802154_local {
struct list_head rx_mac_cmd_list;
struct work_struct rx_mac_cmd_work;
+ /* Association */
+ struct ieee802154_pan_device *assoc_dev;
+ struct completion assoc_done;
+ __le16 assoc_addr;
+ u8 assoc_status;
+ struct work_struct assoc_work;
+
bool started;
bool suspended;
unsigned long ongoing;
@@ -296,6 +304,25 @@ static inline bool mac802154_is_beaconing(struct ieee802154_local *local)
void mac802154_rx_mac_cmd_worker(struct work_struct *work);
+int mac802154_perform_association(struct ieee802154_sub_if_data *sdata,
+ struct ieee802154_pan_device *coord,
+ __le16 *short_addr);
+int mac802154_process_association_resp(struct ieee802154_sub_if_data *sdata,
+ struct sk_buff *skb);
+
+static inline bool mac802154_is_associating(struct ieee802154_local *local)
+{
+ return test_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing);
+}
+
+int mac802154_send_disassociation_notif(struct ieee802154_sub_if_data *sdata,
+ struct ieee802154_pan_device *target,
+ u8 reason);
+int mac802154_process_disassociation_notif(struct ieee802154_sub_if_data *sdata,
+ struct sk_buff *skb);
+int mac802154_process_association_req(struct ieee802154_sub_if_data *sdata,
+ struct sk_buff *skb);
+
/* interface handling */
int ieee802154_iface_init(void);
void ieee802154_iface_exit(void);
diff --git a/net/mac802154/main.c b/net/mac802154/main.c
index 357ece67432b..9ab7396668d2 100644
--- a/net/mac802154/main.c
+++ b/net/mac802154/main.c
@@ -103,6 +103,8 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops)
INIT_DELAYED_WORK(&local->beacon_work, mac802154_beacon_worker);
INIT_WORK(&local->rx_mac_cmd_work, mac802154_rx_mac_cmd_worker);
+ init_completion(&local->assoc_done);
+
/* init supported flags with 802.15.4 default ranges */
phy->supported.max_minbe = 8;
phy->supported.min_maxbe = 3;
diff --git a/net/mac802154/rx.c b/net/mac802154/rx.c
index e2434b4fe514..e40a988d6c80 100644
--- a/net/mac802154/rx.c
+++ b/net/mac802154/rx.c
@@ -93,6 +93,31 @@ void mac802154_rx_mac_cmd_worker(struct work_struct *work)
queue_delayed_work(local->mac_wq, &local->beacon_work, 0);
break;
+
+ case IEEE802154_CMD_ASSOCIATION_RESP:
+ dev_dbg(&mac_pkt->sdata->dev->dev, "processing ASSOC RESP\n");
+ if (!mac802154_is_associating(local))
+ break;
+
+ mac802154_process_association_resp(mac_pkt->sdata, mac_pkt->skb);
+ break;
+
+ case IEEE802154_CMD_ASSOCIATION_REQ:
+ dev_dbg(&mac_pkt->sdata->dev->dev, "processing ASSOC REQ\n");
+ if (mac_pkt->sdata->wpan_dev.iftype != NL802154_IFTYPE_COORD)
+ break;
+
+ mac802154_process_association_req(mac_pkt->sdata, mac_pkt->skb);
+ break;
+
+ case IEEE802154_CMD_DISASSOCIATION_NOTIFY:
+ dev_dbg(&mac_pkt->sdata->dev->dev, "processing DISASSOC NOTIF\n");
+ if (mac_pkt->sdata->wpan_dev.iftype != NL802154_IFTYPE_COORD)
+ break;
+
+ mac802154_process_disassociation_notif(mac_pkt->sdata, mac_pkt->skb);
+ break;
+
default:
break;
}
@@ -131,12 +156,15 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata,
switch (mac_cb(skb)->dest.mode) {
case IEEE802154_ADDR_NONE:
- if (hdr->source.mode != IEEE802154_ADDR_NONE)
- /* FIXME: check if we are PAN coordinator */
- skb->pkt_type = PACKET_OTHERHOST;
- else
+ if (hdr->source.mode == IEEE802154_ADDR_NONE)
/* ACK comes with both addresses empty */
skb->pkt_type = PACKET_HOST;
+ else if (!wpan_dev->parent)
+ /* No dest means PAN coordinator is the recipient */
+ skb->pkt_type = PACKET_HOST;
+ else
+ /* We are not the PAN coordinator, just relaying */
+ skb->pkt_type = PACKET_OTHERHOST;
break;
case IEEE802154_ADDR_LONG:
if (mac_cb(skb)->dest.pan_id != span &&
diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c
index d9658f2c4ae6..1c0eeaa76560 100644
--- a/net/mac802154/scan.c
+++ b/net/mac802154/scan.c
@@ -466,6 +466,7 @@ int mac802154_send_beacons_locked(struct ieee802154_sub_if_data *sdata,
struct cfg802154_beacon_request *request)
{
struct ieee802154_local *local = sdata->local;
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
ASSERT_RTNL();
@@ -495,8 +496,7 @@ int mac802154_send_beacons_locked(struct ieee802154_sub_if_data *sdata,
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 with the coordinator situation in the network */
- local->beacon.mac_pl.pan_coordinator = 1;
+ local->beacon.mac_pl.pan_coordinator = !wpan_dev->parent;
local->beacon.mac_pl.assoc_permit = 1;
if (request->interval == IEEE802154_ACTIVE_SCAN_DURATION)
@@ -510,3 +510,406 @@ int mac802154_send_beacons_locked(struct ieee802154_sub_if_data *sdata,
return 0;
}
+
+int mac802154_perform_association(struct ieee802154_sub_if_data *sdata,
+ struct ieee802154_pan_device *coord,
+ __le16 *short_addr)
+{
+ u64 ceaddr = swab64((__force u64)coord->extended_addr);
+ struct ieee802154_association_req_frame frame = {};
+ struct ieee802154_local *local = sdata->local;
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+ struct sk_buff *skb;
+ int ret;
+
+ frame.mhr.fc.type = IEEE802154_FC_TYPE_MAC_CMD;
+ frame.mhr.fc.security_enabled = 0;
+ frame.mhr.fc.frame_pending = 0;
+ frame.mhr.fc.ack_request = 1; /* We always expect an ack here */
+ frame.mhr.fc.intra_pan = 0;
+ frame.mhr.fc.dest_addr_mode = (coord->mode == IEEE802154_ADDR_LONG) ?
+ IEEE802154_EXTENDED_ADDRESSING : IEEE802154_SHORT_ADDRESSING;
+ frame.mhr.fc.version = IEEE802154_2003_STD;
+ frame.mhr.fc.source_addr_mode = IEEE802154_EXTENDED_ADDRESSING;
+ frame.mhr.source.mode = IEEE802154_ADDR_LONG;
+ frame.mhr.source.pan_id = cpu_to_le16(IEEE802154_PANID_BROADCAST);
+ frame.mhr.source.extended_addr = wpan_dev->extended_addr;
+ frame.mhr.dest.mode = coord->mode;
+ frame.mhr.dest.pan_id = coord->pan_id;
+ if (coord->mode == IEEE802154_ADDR_LONG)
+ frame.mhr.dest.extended_addr = coord->extended_addr;
+ else
+ frame.mhr.dest.short_addr = coord->short_addr;
+ frame.mhr.seq = atomic_inc_return(&wpan_dev->dsn) & 0xFF;
+ frame.mac_pl.cmd_id = IEEE802154_CMD_ASSOCIATION_REQ;
+ frame.assoc_req_pl.device_type = 1;
+ frame.assoc_req_pl.power_source = 1;
+ frame.assoc_req_pl.rx_on_when_idle = 1;
+ frame.assoc_req_pl.alloc_addr = 1;
+
+ skb = alloc_skb(IEEE802154_MAC_CMD_SKB_SZ + sizeof(frame.assoc_req_pl),
+ GFP_KERNEL);
+ if (!skb)
+ return -ENOBUFS;
+
+ skb->dev = sdata->dev;
+
+ ret = ieee802154_mac_cmd_push(skb, &frame, &frame.assoc_req_pl,
+ sizeof(frame.assoc_req_pl));
+ if (ret) {
+ kfree_skb(skb);
+ return ret;
+ }
+
+ local->assoc_dev = coord;
+ reinit_completion(&local->assoc_done);
+ set_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing);
+
+ ret = ieee802154_mlme_tx_one_locked(local, sdata, skb);
+ if (ret) {
+ if (ret > 0)
+ ret = (ret == IEEE802154_NO_ACK) ? -EREMOTEIO : -EIO;
+ dev_warn(&sdata->dev->dev,
+ "No ASSOC REQ ACK received from %8phC\n", &ceaddr);
+ goto clear_assoc;
+ }
+
+ ret = wait_for_completion_killable_timeout(&local->assoc_done, 10 * HZ);
+ if (ret <= 0) {
+ dev_warn(&sdata->dev->dev,
+ "No ASSOC RESP received from %8phC\n", &ceaddr);
+ ret = -ETIMEDOUT;
+ goto clear_assoc;
+ }
+
+ if (local->assoc_status != IEEE802154_ASSOCIATION_SUCCESSFUL) {
+ if (local->assoc_status == IEEE802154_PAN_AT_CAPACITY)
+ ret = -ERANGE;
+ else
+ ret = -EPERM;
+
+ dev_warn(&sdata->dev->dev,
+ "Negative ASSOC RESP received from %8phC: %s\n", &ceaddr,
+ local->assoc_status == IEEE802154_PAN_AT_CAPACITY ?
+ "PAN at capacity" : "access denied");
+ }
+
+ ret = 0;
+ *short_addr = local->assoc_addr;
+
+clear_assoc:
+ clear_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing);
+ local->assoc_dev = NULL;
+
+ return ret;
+}
+
+int mac802154_process_association_resp(struct ieee802154_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct ieee802154_addr *src = &mac_cb(skb)->source;
+ struct ieee802154_addr *dest = &mac_cb(skb)->dest;
+ u64 deaddr = swab64((__force u64)dest->extended_addr);
+ struct ieee802154_local *local = sdata->local;
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+ struct ieee802154_assoc_resp_pl resp_pl = {};
+
+ if (skb->len != sizeof(resp_pl))
+ return -EINVAL;
+
+ if (unlikely(src->mode != IEEE802154_EXTENDED_ADDRESSING ||
+ dest->mode != IEEE802154_EXTENDED_ADDRESSING))
+ return -EINVAL;
+
+ if (unlikely(dest->extended_addr != wpan_dev->extended_addr ||
+ src->extended_addr != local->assoc_dev->extended_addr))
+ return -ENODEV;
+
+ memcpy(&resp_pl, skb->data, sizeof(resp_pl));
+ local->assoc_addr = resp_pl.short_addr;
+ local->assoc_status = resp_pl.status;
+
+ dev_dbg(&skb->dev->dev,
+ "ASSOC RESP 0x%x received from %8phC, getting short address %04x\n",
+ local->assoc_status, &deaddr, local->assoc_addr);
+
+ complete(&local->assoc_done);
+
+ return 0;
+}
+
+int mac802154_send_disassociation_notif(struct ieee802154_sub_if_data *sdata,
+ struct ieee802154_pan_device *target,
+ u8 reason)
+{
+ struct ieee802154_disassociation_notif_frame frame = {};
+ u64 teaddr = swab64((__force u64)target->extended_addr);
+ struct ieee802154_local *local = sdata->local;
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+ struct sk_buff *skb;
+ int ret;
+
+ frame.mhr.fc.type = IEEE802154_FC_TYPE_MAC_CMD;
+ frame.mhr.fc.security_enabled = 0;
+ frame.mhr.fc.frame_pending = 0;
+ frame.mhr.fc.ack_request = 1;
+ frame.mhr.fc.intra_pan = 1;
+ frame.mhr.fc.dest_addr_mode = (target->mode == IEEE802154_ADDR_LONG) ?
+ IEEE802154_EXTENDED_ADDRESSING : IEEE802154_SHORT_ADDRESSING;
+ frame.mhr.fc.version = IEEE802154_2003_STD;
+ frame.mhr.fc.source_addr_mode = IEEE802154_EXTENDED_ADDRESSING;
+ frame.mhr.source.mode = IEEE802154_ADDR_LONG;
+ frame.mhr.source.pan_id = wpan_dev->pan_id;
+ frame.mhr.source.extended_addr = wpan_dev->extended_addr;
+ frame.mhr.dest.mode = target->mode;
+ frame.mhr.dest.pan_id = wpan_dev->pan_id;
+ if (target->mode == IEEE802154_ADDR_LONG)
+ frame.mhr.dest.extended_addr = target->extended_addr;
+ else
+ frame.mhr.dest.short_addr = target->short_addr;
+ frame.mhr.seq = atomic_inc_return(&wpan_dev->dsn) & 0xFF;
+ frame.mac_pl.cmd_id = IEEE802154_CMD_DISASSOCIATION_NOTIFY;
+ frame.disassoc_pl = reason;
+
+ skb = alloc_skb(IEEE802154_MAC_CMD_SKB_SZ + sizeof(frame.disassoc_pl),
+ GFP_KERNEL);
+ if (!skb)
+ return -ENOBUFS;
+
+ skb->dev = sdata->dev;
+
+ ret = ieee802154_mac_cmd_push(skb, &frame, &frame.disassoc_pl,
+ sizeof(frame.disassoc_pl));
+ if (ret) {
+ kfree_skb(skb);
+ return ret;
+ }
+
+ ret = ieee802154_mlme_tx_one_locked(local, sdata, skb);
+ if (ret) {
+ dev_warn(&sdata->dev->dev,
+ "No DISASSOC ACK received from %8phC\n", &teaddr);
+ if (ret > 0)
+ ret = (ret == IEEE802154_NO_ACK) ? -EREMOTEIO : -EIO;
+ return ret;
+ }
+
+ dev_dbg(&sdata->dev->dev, "DISASSOC ACK received from %8phC\n", &teaddr);
+ return 0;
+}
+
+static int
+mac802154_send_association_resp_locked(struct ieee802154_sub_if_data *sdata,
+ struct ieee802154_pan_device *target,
+ struct ieee802154_assoc_resp_pl *assoc_resp_pl)
+{
+ u64 teaddr = swab64((__force u64)target->extended_addr);
+ struct ieee802154_association_resp_frame frame = {};
+ struct ieee802154_local *local = sdata->local;
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+ struct sk_buff *skb;
+ int ret;
+
+ frame.mhr.fc.type = IEEE802154_FC_TYPE_MAC_CMD;
+ frame.mhr.fc.security_enabled = 0;
+ frame.mhr.fc.frame_pending = 0;
+ frame.mhr.fc.ack_request = 1; /* We always expect an ack here */
+ frame.mhr.fc.intra_pan = 1;
+ frame.mhr.fc.dest_addr_mode = IEEE802154_EXTENDED_ADDRESSING;
+ frame.mhr.fc.version = IEEE802154_2003_STD;
+ frame.mhr.fc.source_addr_mode = IEEE802154_EXTENDED_ADDRESSING;
+ frame.mhr.source.mode = IEEE802154_ADDR_LONG;
+ frame.mhr.source.extended_addr = wpan_dev->extended_addr;
+ frame.mhr.dest.mode = IEEE802154_ADDR_LONG;
+ frame.mhr.dest.pan_id = wpan_dev->pan_id;
+ frame.mhr.dest.extended_addr = target->extended_addr;
+ frame.mhr.seq = atomic_inc_return(&wpan_dev->dsn) & 0xFF;
+ frame.mac_pl.cmd_id = IEEE802154_CMD_ASSOCIATION_RESP;
+
+ skb = alloc_skb(IEEE802154_MAC_CMD_SKB_SZ + sizeof(*assoc_resp_pl),
+ GFP_KERNEL);
+ if (!skb)
+ return -ENOBUFS;
+
+ skb->dev = sdata->dev;
+
+ ret = ieee802154_mac_cmd_push(skb, &frame, assoc_resp_pl,
+ sizeof(*assoc_resp_pl));
+ if (ret) {
+ kfree_skb(skb);
+ return ret;
+ }
+
+ ret = ieee802154_mlme_tx_locked(local, sdata, skb);
+ if (ret) {
+ dev_warn(&sdata->dev->dev,
+ "No ASSOC RESP ACK received from %8phC\n", &teaddr);
+ if (ret > 0)
+ ret = (ret == IEEE802154_NO_ACK) ? -EREMOTEIO : -EIO;
+ return ret;
+ }
+
+ return 0;
+}
+
+int mac802154_process_association_req(struct ieee802154_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+ struct ieee802154_addr *src = &mac_cb(skb)->source;
+ struct ieee802154_addr *dest = &mac_cb(skb)->dest;
+ struct ieee802154_assoc_resp_pl assoc_resp_pl = {};
+ struct ieee802154_assoc_req_pl assoc_req_pl;
+ struct ieee802154_pan_device *child, *exchild;
+ struct ieee802154_addr tmp = {};
+ u64 ceaddr;
+ int ret;
+
+ if (skb->len != sizeof(assoc_req_pl))
+ return -EINVAL;
+
+ if (unlikely(src->mode != IEEE802154_EXTENDED_ADDRESSING))
+ return -EINVAL;
+
+ if (unlikely(dest->pan_id != wpan_dev->pan_id))
+ return -ENODEV;
+
+ if (dest->mode == IEEE802154_EXTENDED_ADDRESSING &&
+ unlikely(dest->extended_addr != wpan_dev->extended_addr))
+ return -ENODEV;
+ else if (dest->mode == IEEE802154_SHORT_ADDRESSING &&
+ unlikely(dest->short_addr != wpan_dev->short_addr))
+ return -ENODEV;
+
+ if (wpan_dev->parent) {
+ dev_dbg(&sdata->dev->dev,
+ "Ignoring ASSOC REQ, not the PAN coordinator\n");
+ return -ENODEV;
+ }
+
+ mutex_lock(&wpan_dev->association_lock);
+
+ memcpy(&assoc_req_pl, skb->data, sizeof(assoc_req_pl));
+ if (assoc_req_pl.assoc_type) {
+ dev_err(&skb->dev->dev, "Fast associations not supported yet\n");
+ ret = -EOPNOTSUPP;
+ goto unlock;
+ }
+
+ child = kzalloc(sizeof(*child), GFP_KERNEL);
+ if (!child) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ child->extended_addr = src->extended_addr;
+ child->mode = IEEE802154_EXTENDED_ADDRESSING;
+ ceaddr = swab64((__force u64)child->extended_addr);
+
+ if (wpan_dev->nchildren >= wpan_dev->max_associations) {
+ if (!wpan_dev->max_associations)
+ assoc_resp_pl.status = IEEE802154_PAN_ACCESS_DENIED;
+ else
+ assoc_resp_pl.status = IEEE802154_PAN_AT_CAPACITY;
+ assoc_resp_pl.short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_BROADCAST);
+ dev_dbg(&sdata->dev->dev,
+ "Refusing ASSOC REQ from child %8phC, %s\n", &ceaddr,
+ assoc_resp_pl.status == IEEE802154_PAN_ACCESS_DENIED ?
+ "access denied" : "too many children");
+ } else {
+ assoc_resp_pl.status = IEEE802154_ASSOCIATION_SUCCESSFUL;
+ if (assoc_req_pl.alloc_addr) {
+ assoc_resp_pl.short_addr = cfg802154_get_free_short_addr(wpan_dev);
+ child->mode = IEEE802154_SHORT_ADDRESSING;
+ } else {
+ assoc_resp_pl.short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
+ }
+ child->short_addr = assoc_resp_pl.short_addr;
+ dev_dbg(&sdata->dev->dev,
+ "Accepting ASSOC REQ from child %8phC, providing short address 0x%04x\n",
+ &ceaddr, le16_to_cpu(child->short_addr));
+ }
+
+ ret = mac802154_send_association_resp_locked(sdata, child, &assoc_resp_pl);
+ if (ret || assoc_resp_pl.status != IEEE802154_ASSOCIATION_SUCCESSFUL) {
+ kfree(child);
+ goto unlock;
+ }
+
+ dev_dbg(&sdata->dev->dev,
+ "Successful association with new child %8phC\n", &ceaddr);
+
+ /* Ensure this child is not already associated (might happen due to
+ * retransmissions), in this case drop the ex structure.
+ */
+ tmp.mode = child->mode;
+ tmp.extended_addr = child->extended_addr;
+ exchild = cfg802154_device_is_child(wpan_dev, &tmp);
+ if (exchild) {
+ dev_dbg(&sdata->dev->dev,
+ "Child %8phC was already known\n", &ceaddr);
+ list_del(&exchild->node);
+ }
+
+ list_add(&child->node, &wpan_dev->children);
+ wpan_dev->nchildren++;
+
+unlock:
+ mutex_unlock(&wpan_dev->association_lock);
+ return ret;
+}
+
+int mac802154_process_disassociation_notif(struct ieee802154_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct ieee802154_addr *src = &mac_cb(skb)->source;
+ struct ieee802154_addr *dest = &mac_cb(skb)->dest;
+ struct wpan_dev *wpan_dev = &sdata->wpan_dev;
+ struct ieee802154_pan_device *child;
+ struct ieee802154_addr target;
+ bool parent;
+ u64 teaddr;
+
+ if (skb->len != sizeof(u8))
+ return -EINVAL;
+
+ if (unlikely(src->mode != IEEE802154_EXTENDED_ADDRESSING))
+ return -EINVAL;
+
+ if (dest->mode == IEEE802154_EXTENDED_ADDRESSING &&
+ unlikely(dest->extended_addr != wpan_dev->extended_addr))
+ return -ENODEV;
+ else if (dest->mode == IEEE802154_SHORT_ADDRESSING &&
+ unlikely(dest->short_addr != wpan_dev->short_addr))
+ return -ENODEV;
+
+ if (dest->pan_id != wpan_dev->pan_id)
+ return -ENODEV;
+
+ target.mode = IEEE802154_EXTENDED_ADDRESSING;
+ target.extended_addr = src->extended_addr;
+ teaddr = swab64((__force u64)target.extended_addr);
+ dev_dbg(&skb->dev->dev, "Processing DISASSOC NOTIF from %8phC\n", &teaddr);
+
+ mutex_lock(&wpan_dev->association_lock);
+ parent = cfg802154_device_is_parent(wpan_dev, &target);
+ if (!parent)
+ child = cfg802154_device_is_child(wpan_dev, &target);
+ if (!parent && !child) {
+ mutex_unlock(&wpan_dev->association_lock);
+ return -EINVAL;
+ }
+
+ if (parent) {
+ kfree(wpan_dev->parent);
+ wpan_dev->parent = NULL;
+ } else {
+ list_del(&child->node);
+ kfree(child);
+ wpan_dev->nchildren--;
+ }
+
+ mutex_unlock(&wpan_dev->association_lock);
+
+ return 0;
+}