mac802154: Handle disassociations
authorMiquel Raynal <miquel.raynal@bootlin.com>
Wed, 27 Sep 2023 18:12:09 +0000 (20:12 +0200)
committerMiquel Raynal <miquel.raynal@bootlin.com>
Mon, 20 Nov 2023 10:42:55 +0000 (11:42 +0100)
Devices may decide to disassociate from their coordinator for different
reasons (device turning off, coordinator signal strength too low, etc),
the MAC layer just has to send a disassociation notification.

If the ack of the disassociation notification is not received, the
device may consider itself disassociated anyway.

Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Acked-by: Stefan Schmidt <stefan@datenfreihafen.org>
Acked-by: Alexander Aring <aahringo@redhat.com>
Link: https://lore.kernel.org/linux-wpan/20230927181214.129346-7-miquel.raynal@bootlin.com
net/ieee802154/pan.c
net/mac802154/cfg.c
net/mac802154/ieee802154_i.h
net/mac802154/scan.c

index 2b30e7b19ac328e2c5ad747bef9b3af2084d0bf2..43b8d2df2186a8fdbb63b7c7bfa5fe29128902e2 100644 (file)
@@ -46,6 +46,7 @@ bool cfg802154_device_is_parent(struct wpan_dev *wpan_dev,
 
        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,
@@ -61,3 +62,4 @@ cfg802154_device_is_child(struct wpan_dev *wpan_dev,
 
        return NULL;
 }
+EXPORT_SYMBOL_GPL(cfg802154_device_is_child);
index 0e8bb1486430b096cd49ae641f8ae2dc48b65285..083de2d3fe375d27092f9865224757c7fc05ce76 100644 (file)
@@ -383,6 +383,105 @@ free_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;
+       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.
+        */
+       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)
+                       return ret;
+
+               ret = drv_set_short_addr(local, wpan_dev->short_addr);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+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);
+       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,
@@ -595,6 +694,7 @@ const struct cfg802154_ops mac802154_config_ops = {
        .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,
index fff67676b400dcebca85fbd9387a5ea2e7f73bc7..92252f86c69c087faa1d775c3cf0ed2b24d8c077 100644 (file)
@@ -315,6 +315,10 @@ 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);
+
 /* interface handling */
 int ieee802154_iface_init(void);
 void ieee802154_iface_exit(void);
index 5dd50e1ce329333f8e8e164b165038716f8f6f60..e2f2e1235ec6e533161a0f662d63d5301af30174 100644 (file)
@@ -637,3 +637,63 @@ int mac802154_process_association_resp(struct ieee802154_sub_if_data *sdata,
 
        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;
+}