Commit 172b3fcc authored by Jakub Kicinski's avatar Jakub Kicinski

Merge tag 'ieee802154-for-net-next-2023-12-20' of...

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-13Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 6c8e2407 23736995
...@@ -221,7 +221,7 @@ static int fakelb_probe(struct platform_device *pdev) ...@@ -221,7 +221,7 @@ static int fakelb_probe(struct platform_device *pdev)
return err; return err;
} }
static int fakelb_remove(struct platform_device *pdev) static void fakelb_remove(struct platform_device *pdev)
{ {
struct fakelb_phy *phy, *tmp; struct fakelb_phy *phy, *tmp;
...@@ -229,14 +229,13 @@ static int fakelb_remove(struct platform_device *pdev) ...@@ -229,14 +229,13 @@ static int fakelb_remove(struct platform_device *pdev)
list_for_each_entry_safe(phy, tmp, &fakelb_phys, list) list_for_each_entry_safe(phy, tmp, &fakelb_phys, list)
fakelb_del(phy); fakelb_del(phy);
mutex_unlock(&fakelb_phys_lock); mutex_unlock(&fakelb_phys_lock);
return 0;
} }
static struct platform_device *ieee802154fake_dev; static struct platform_device *ieee802154fake_dev;
static struct platform_driver ieee802154fake_driver = { static struct platform_driver ieee802154fake_driver = {
.probe = fakelb_probe, .probe = fakelb_probe,
.remove = fakelb_remove, .remove_new = fakelb_remove,
.driver = { .driver = {
.name = "ieee802154fakelb", .name = "ieee802154fakelb",
}, },
......
...@@ -1035,7 +1035,7 @@ static int hwsim_probe(struct platform_device *pdev) ...@@ -1035,7 +1035,7 @@ static int hwsim_probe(struct platform_device *pdev)
return err; return err;
} }
static int hwsim_remove(struct platform_device *pdev) static void hwsim_remove(struct platform_device *pdev)
{ {
struct hwsim_phy *phy, *tmp; struct hwsim_phy *phy, *tmp;
...@@ -1043,13 +1043,11 @@ static int hwsim_remove(struct platform_device *pdev) ...@@ -1043,13 +1043,11 @@ static int hwsim_remove(struct platform_device *pdev)
list_for_each_entry_safe(phy, tmp, &hwsim_phys, list) list_for_each_entry_safe(phy, tmp, &hwsim_phys, list)
hwsim_del(phy); hwsim_del(phy);
mutex_unlock(&hwsim_phys_lock); mutex_unlock(&hwsim_phys_lock);
return 0;
} }
static struct platform_driver mac802154hwsim_driver = { static struct platform_driver mac802154hwsim_driver = {
.probe = hwsim_probe, .probe = hwsim_probe,
.remove = hwsim_remove, .remove_new = hwsim_remove,
.driver = { .driver = {
.name = "mac802154_hwsim", .name = "mac802154_hwsim",
}, },
......
...@@ -20,6 +20,7 @@ struct wpan_phy; ...@@ -20,6 +20,7 @@ struct wpan_phy;
struct wpan_phy_cca; struct wpan_phy_cca;
struct cfg802154_scan_request; struct cfg802154_scan_request;
struct cfg802154_beacon_request; struct cfg802154_beacon_request;
struct ieee802154_addr;
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
struct ieee802154_llsec_device_key; struct ieee802154_llsec_device_key;
...@@ -77,6 +78,12 @@ struct cfg802154_ops { ...@@ -77,6 +78,12 @@ struct cfg802154_ops {
struct cfg802154_beacon_request *request); struct cfg802154_beacon_request *request);
int (*stop_beacons)(struct wpan_phy *wpan_phy, int (*stop_beacons)(struct wpan_phy *wpan_phy,
struct wpan_dev *wpan_dev); 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 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
void (*get_llsec_table)(struct wpan_phy *wpan_phy, void (*get_llsec_table)(struct wpan_phy *wpan_phy,
struct wpan_dev *wpan_dev, struct wpan_dev *wpan_dev,
...@@ -303,6 +310,22 @@ struct ieee802154_coord_desc { ...@@ -303,6 +310,22 @@ struct ieee802154_coord_desc {
bool gts_permit; bool gts_permit;
}; };
/**
* 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 * struct cfg802154_scan_request - Scan request
* *
...@@ -478,6 +501,13 @@ struct wpan_dev { ...@@ -478,6 +501,13 @@ struct wpan_dev {
/* fallback for acknowledgment bit setting */ /* fallback for acknowledgment bit setting */
bool ackreq; 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) #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) ...@@ -529,4 +559,46 @@ static inline const char *wpan_phy_name(struct wpan_phy *phy)
void ieee802154_configure_durations(struct wpan_phy *phy, void ieee802154_configure_durations(struct wpan_phy *phy,
unsigned int page, unsigned int channel); 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 */ #endif /* __NET_CFG802154_H */
...@@ -125,6 +125,35 @@ struct ieee802154_hdr_fc { ...@@ -125,6 +125,35 @@ struct ieee802154_hdr_fc {
#endif #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 { enum ieee802154_frame_version {
IEEE802154_2003_STD, IEEE802154_2003_STD,
IEEE802154_2006_STD, IEEE802154_2006_STD,
...@@ -140,6 +169,19 @@ enum ieee802154_addressing_mode { ...@@ -140,6 +169,19 @@ enum ieee802154_addressing_mode {
IEEE802154_EXTENDED_ADDRESSING, 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 {
struct ieee802154_hdr_fc fc; struct ieee802154_hdr_fc fc;
u8 seq; u8 seq;
...@@ -163,6 +205,24 @@ struct ieee802154_beacon_req_frame { ...@@ -163,6 +205,24 @@ struct ieee802154_beacon_req_frame {
struct ieee802154_mac_cmd_pl mac_pl; 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 /* 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 * 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 * hdr->fc will be ignored. this includes the INTRA_PAN bit and the frame
......
...@@ -78,6 +78,10 @@ enum nl802154_commands { ...@@ -78,6 +78,10 @@ enum nl802154_commands {
NL802154_CMD_SCAN_DONE, NL802154_CMD_SCAN_DONE,
NL802154_CMD_SEND_BEACONS, NL802154_CMD_SEND_BEACONS,
NL802154_CMD_STOP_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 */ /* add new commands above here */
...@@ -147,6 +151,8 @@ enum nl802154_attrs { ...@@ -147,6 +151,8 @@ enum nl802154_attrs {
NL802154_ATTR_SCAN_DURATION, NL802154_ATTR_SCAN_DURATION,
NL802154_ATTR_SCAN_DONE_REASON, NL802154_ATTR_SCAN_DONE_REASON,
NL802154_ATTR_BEACON_INTERVAL, NL802154_ATTR_BEACON_INTERVAL,
NL802154_ATTR_MAX_ASSOCIATIONS,
NL802154_ATTR_PEER,
/* add attributes here, update the policy in nl802154.c */ /* add attributes here, update the policy in nl802154.c */
...@@ -385,8 +391,6 @@ enum nl802154_supported_bool_states { ...@@ -385,8 +391,6 @@ enum nl802154_supported_bool_states {
NL802154_SUPPORTED_BOOL_MAX = __NL802154_SUPPORTED_BOOL_AFTER_LAST - 1 NL802154_SUPPORTED_BOOL_MAX = __NL802154_SUPPORTED_BOOL_AFTER_LAST - 1
}; };
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
enum nl802154_dev_addr_modes { enum nl802154_dev_addr_modes {
NL802154_DEV_ADDR_NONE, NL802154_DEV_ADDR_NONE,
__NL802154_DEV_ADDR_INVALID, __NL802154_DEV_ADDR_INVALID,
...@@ -406,12 +410,26 @@ enum nl802154_dev_addr_attrs { ...@@ -406,12 +410,26 @@ enum nl802154_dev_addr_attrs {
NL802154_DEV_ADDR_ATTR_SHORT, NL802154_DEV_ADDR_ATTR_SHORT,
NL802154_DEV_ADDR_ATTR_EXTENDED, NL802154_DEV_ADDR_ATTR_EXTENDED,
NL802154_DEV_ADDR_ATTR_PAD, NL802154_DEV_ADDR_ATTR_PAD,
NL802154_DEV_ADDR_ATTR_PEER_TYPE,
/* keep last */ /* keep last */
__NL802154_DEV_ADDR_ATTR_AFTER_LAST, __NL802154_DEV_ADDR_ATTR_AFTER_LAST,
NL802154_DEV_ADDR_ATTR_MAX = __NL802154_DEV_ADDR_ATTR_AFTER_LAST - 1 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 { enum nl802154_key_id_modes {
NL802154_KEY_ID_MODE_IMPLICIT, NL802154_KEY_ID_MODE_IMPLICIT,
NL802154_KEY_ID_MODE_INDEX, NL802154_KEY_ID_MODE_INDEX,
......
...@@ -4,7 +4,7 @@ obj-$(CONFIG_IEEE802154_SOCKET) += ieee802154_socket.o ...@@ -4,7 +4,7 @@ obj-$(CONFIG_IEEE802154_SOCKET) += ieee802154_socket.o
obj-y += 6lowpan/ obj-y += 6lowpan/
ieee802154-y := netlink.o nl-mac.o nl-phy.o nl_policy.o core.o \ 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 ieee802154_socket-y := socket.o
CFLAGS_trace.o := -I$(src) CFLAGS_trace.o := -I$(src)
...@@ -198,6 +198,25 @@ void wpan_phy_free(struct wpan_phy *phy) ...@@ -198,6 +198,25 @@ void wpan_phy_free(struct wpan_phy *phy)
} }
EXPORT_SYMBOL(wpan_phy_free); 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, int cfg802154_switch_netns(struct cfg802154_registered_device *rdev,
struct net *net) struct net *net)
{ {
...@@ -276,6 +295,9 @@ static int cfg802154_netdev_notifier_call(struct notifier_block *nb, ...@@ -276,6 +295,9 @@ static int cfg802154_netdev_notifier_call(struct notifier_block *nb,
wpan_dev->identifier = ++rdev->wpan_dev_id; wpan_dev->identifier = ++rdev->wpan_dev_id;
list_add_rcu(&wpan_dev->list, &rdev->wpan_dev_list); list_add_rcu(&wpan_dev->list, &rdev->wpan_dev_list);
rdev->devlist_generation++; 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; wpan_dev->netdev = dev;
break; break;
...@@ -291,6 +313,8 @@ static int cfg802154_netdev_notifier_call(struct notifier_block *nb, ...@@ -291,6 +313,8 @@ static int cfg802154_netdev_notifier_call(struct notifier_block *nb,
rdev->opencount++; rdev->opencount++;
break; break;
case NETDEV_UNREGISTER: case NETDEV_UNREGISTER:
cfg802154_free_peer_structures(wpan_dev);
/* It is possible to get NETDEV_UNREGISTER /* It is possible to get NETDEV_UNREGISTER
* multiple times. To detect that, check * multiple times. To detect that, check
* that the interface is still on the list * that the interface is still on the list
......
...@@ -234,6 +234,8 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = { ...@@ -234,6 +234,8 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
NL802154_SCAN_DONE_REASON_ABORTED), NL802154_SCAN_DONE_REASON_ABORTED),
[NL802154_ATTR_BEACON_INTERVAL] = [NL802154_ATTR_BEACON_INTERVAL] =
NLA_POLICY_MAX(NLA_U8, IEEE802154_ACTIVE_SCAN_DURATION), 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 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
[NL802154_ATTR_SEC_ENABLED] = { .type = NLA_U8, }, [NL802154_ATTR_SEC_ENABLED] = { .type = NLA_U8, },
...@@ -248,7 +250,6 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = { ...@@ -248,7 +250,6 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */ #endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
}; };
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
static int static int
nl802154_prepare_wpan_dev_dump(struct sk_buff *skb, nl802154_prepare_wpan_dev_dump(struct sk_buff *skb,
struct netlink_callback *cb, struct netlink_callback *cb,
...@@ -307,7 +308,6 @@ nl802154_finish_wpan_dev_dump(struct cfg802154_registered_device *rdev) ...@@ -307,7 +308,6 @@ nl802154_finish_wpan_dev_dump(struct cfg802154_registered_device *rdev)
{ {
rtnl_unlock(); rtnl_unlock();
} }
#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
/* message building helper */ /* message building helper */
static inline void *nl802154hdr_put(struct sk_buff *skb, u32 portid, u32 seq, 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) ...@@ -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]); pan_id = nla_get_le16(info->attrs[NL802154_ATTR_PAN_ID]);
/* TODO /* Only allow changing the PAN ID when the device has no more
* I am not sure about to check here on broadcast pan_id. * associations ongoing to avoid confusing peers.
* 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.
*/ */
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 -EINVAL;
}
return rdev_set_pan_id(rdev, wpan_dev, pan_id); 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) ...@@ -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]); short_addr = nla_get_le16(info->attrs[NL802154_ATTR_SHORT_ADDR]);
/* TODO /* The short address only has a meaning when part of a PAN, after a
* I am not sure about to check here on broadcast short_addr. * proper association procedure. However, we want to still offer the
* Broadcast is a valid setting, comment from 802.15.4: * possibility to create static networks so changing the short address
* A value of 0xfffe indicates that the device has * is only allowed when not already associated to other devices with
* associated but has not been allocated an address. A * the official handshake.
* 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.
*/ */
if (short_addr == cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC) || if (cfg802154_device_is_associated(wpan_dev)) {
short_addr == cpu_to_le16(IEEE802154_ADDR_SHORT_BROADCAST)) NL_SET_ERR_MSG(info->extack,
"Existing associations, changing short address forbidden");
return -EINVAL; return -EINVAL;
}
return rdev_set_short_addr(rdev, wpan_dev, short_addr); 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) ...@@ -1638,6 +1634,189 @@ nl802154_stop_beacons(struct sk_buff *skb, struct genl_info *info)
return rdev_stop_beacons(rdev, wpan_dev); 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 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
static const struct nla_policy nl802154_dev_addr_policy[NL802154_DEV_ADDR_ATTR_MAX + 1] = { static const struct nla_policy nl802154_dev_addr_policy[NL802154_DEV_ADDR_ATTR_MAX + 1] = {
[NL802154_DEV_ADDR_ATTR_PAN_ID] = { .type = NLA_U16 }, [NL802154_DEV_ADDR_ATTR_PAN_ID] = { .type = NLA_U16 },
...@@ -2759,6 +2938,34 @@ static const struct genl_ops nl802154_ops[] = { ...@@ -2759,6 +2938,34 @@ static const struct genl_ops nl802154_ops[] = {
NL802154_FLAG_CHECK_NETDEV_UP | NL802154_FLAG_CHECK_NETDEV_UP |
NL802154_FLAG_NEED_RTNL, 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 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
{ {
.cmd = NL802154_CMD_SET_SEC_PARAMS, .cmd = NL802154_CMD_SET_SEC_PARAMS,
......
// 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);
...@@ -265,6 +265,36 @@ static inline int rdev_stop_beacons(struct cfg802154_registered_device *rdev, ...@@ -265,6 +265,36 @@ static inline int rdev_stop_beacons(struct cfg802154_registered_device *rdev,
return ret; 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 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
/* TODO this is already a nl802154, so move into ieee802154 */ /* TODO this is already a nl802154, so move into ieee802154 */
static inline void static inline void
......
...@@ -356,6 +356,44 @@ DEFINE_EVENT(802154_wdev_template, 802154_rdev_stop_beacons, ...@@ -356,6 +356,44 @@ DEFINE_EVENT(802154_wdev_template, 802154_rdev_stop_beacons,
TP_ARGS(wpan_phy, wpan_dev) 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, TRACE_EVENT(802154_rdev_return_int,
TP_PROTO(struct wpan_phy *wpan_phy, int ret), TP_PROTO(struct wpan_phy *wpan_phy, int ret),
TP_ARGS(wpan_phy, ret), TP_ARGS(wpan_phy, ret),
......
...@@ -315,6 +315,179 @@ static int mac802154_stop_beacons(struct wpan_phy *wpan_phy, ...@@ -315,6 +315,179 @@ static int mac802154_stop_beacons(struct wpan_phy *wpan_phy,
return mac802154_stop_beacons_locked(local, sdata); 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 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
static void static void
ieee802154_get_llsec_table(struct wpan_phy *wpan_phy, ieee802154_get_llsec_table(struct wpan_phy *wpan_phy,
...@@ -526,6 +699,8 @@ const struct cfg802154_ops mac802154_config_ops = { ...@@ -526,6 +699,8 @@ const struct cfg802154_ops mac802154_config_ops = {
.abort_scan = mac802154_abort_scan, .abort_scan = mac802154_abort_scan,
.send_beacons = mac802154_send_beacons, .send_beacons = mac802154_send_beacons,
.stop_beacons = mac802154_stop_beacons, .stop_beacons = mac802154_stop_beacons,
.associate = mac802154_associate,
.disassociate = mac802154_disassociate,
#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
.get_llsec_table = ieee802154_get_llsec_table, .get_llsec_table = ieee802154_get_llsec_table,
.lock_llsec_table = ieee802154_lock_llsec_table, .lock_llsec_table = ieee802154_lock_llsec_table,
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
enum ieee802154_ongoing { enum ieee802154_ongoing {
IEEE802154_IS_SCANNING = BIT(0), IEEE802154_IS_SCANNING = BIT(0),
IEEE802154_IS_BEACONING = BIT(1), IEEE802154_IS_BEACONING = BIT(1),
IEEE802154_IS_ASSOCIATING = BIT(2),
}; };
/* mac802154 device private data */ /* mac802154 device private data */
...@@ -74,6 +75,13 @@ struct ieee802154_local { ...@@ -74,6 +75,13 @@ struct ieee802154_local {
struct list_head rx_mac_cmd_list; struct list_head rx_mac_cmd_list;
struct work_struct rx_mac_cmd_work; 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 started;
bool suspended; bool suspended;
unsigned long ongoing; unsigned long ongoing;
...@@ -296,6 +304,25 @@ static inline bool mac802154_is_beaconing(struct ieee802154_local *local) ...@@ -296,6 +304,25 @@ static inline bool mac802154_is_beaconing(struct ieee802154_local *local)
void mac802154_rx_mac_cmd_worker(struct work_struct *work); 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 */ /* interface handling */
int ieee802154_iface_init(void); int ieee802154_iface_init(void);
void ieee802154_iface_exit(void); void ieee802154_iface_exit(void);
......
...@@ -103,6 +103,8 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops) ...@@ -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_DELAYED_WORK(&local->beacon_work, mac802154_beacon_worker);
INIT_WORK(&local->rx_mac_cmd_work, mac802154_rx_mac_cmd_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 */ /* init supported flags with 802.15.4 default ranges */
phy->supported.max_minbe = 8; phy->supported.max_minbe = 8;
phy->supported.min_maxbe = 3; phy->supported.min_maxbe = 3;
......
...@@ -93,6 +93,31 @@ void mac802154_rx_mac_cmd_worker(struct work_struct *work) ...@@ -93,6 +93,31 @@ void mac802154_rx_mac_cmd_worker(struct work_struct *work)
queue_delayed_work(local->mac_wq, &local->beacon_work, 0); queue_delayed_work(local->mac_wq, &local->beacon_work, 0);
break; 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: default:
break; break;
} }
...@@ -131,12 +156,15 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata, ...@@ -131,12 +156,15 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata,
switch (mac_cb(skb)->dest.mode) { switch (mac_cb(skb)->dest.mode) {
case IEEE802154_ADDR_NONE: case IEEE802154_ADDR_NONE:
if (hdr->source.mode != IEEE802154_ADDR_NONE) if (hdr->source.mode == IEEE802154_ADDR_NONE)
/* FIXME: check if we are PAN coordinator */
skb->pkt_type = PACKET_OTHERHOST;
else
/* ACK comes with both addresses empty */ /* ACK comes with both addresses empty */
skb->pkt_type = PACKET_HOST; 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; break;
case IEEE802154_ADDR_LONG: case IEEE802154_ADDR_LONG:
if (mac_cb(skb)->dest.pan_id != span && if (mac_cb(skb)->dest.pan_id != span &&
......
...@@ -466,6 +466,7 @@ int mac802154_send_beacons_locked(struct ieee802154_sub_if_data *sdata, ...@@ -466,6 +466,7 @@ int mac802154_send_beacons_locked(struct ieee802154_sub_if_data *sdata,
struct cfg802154_beacon_request *request) struct cfg802154_beacon_request *request)
{ {
struct ieee802154_local *local = sdata->local; struct ieee802154_local *local = sdata->local;
struct wpan_dev *wpan_dev = &sdata->wpan_dev;
ASSERT_RTNL(); ASSERT_RTNL();
...@@ -495,8 +496,7 @@ int mac802154_send_beacons_locked(struct ieee802154_sub_if_data *sdata, ...@@ -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.superframe_order = request->interval;
local->beacon.mac_pl.final_cap_slot = 0xf; local->beacon.mac_pl.final_cap_slot = 0xf;
local->beacon.mac_pl.battery_life_ext = 0; 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 = !wpan_dev->parent;
local->beacon.mac_pl.pan_coordinator = 1;
local->beacon.mac_pl.assoc_permit = 1; local->beacon.mac_pl.assoc_permit = 1;
if (request->interval == IEEE802154_ACTIVE_SCAN_DURATION) if (request->interval == IEEE802154_ACTIVE_SCAN_DURATION)
...@@ -510,3 +510,406 @@ int mac802154_send_beacons_locked(struct ieee802154_sub_if_data *sdata, ...@@ -510,3 +510,406 @@ int mac802154_send_beacons_locked(struct ieee802154_sub_if_data *sdata,
return 0; 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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment