#include <linux/in6.h>
 #include <linux/ip.h>
 #include <linux/ipv6.h>
+#include <linux/jiffies.h>
 #include <linux/kernel.h>
 #include <linux/kref.h>
 #include <linux/list.h>
 #include <linux/stddef.h>
 #include <linux/string.h>
 #include <linux/types.h>
+#include <linux/workqueue.h>
 #include <net/addrconf.h>
 #include <net/if_inet6.h>
 #include <net/ip.h>
 #include "translation-table.h"
 #include "tvlv.h"
 
+static void batadv_mcast_mla_update(struct work_struct *work);
+
+/**
+ * batadv_mcast_start_timer - schedule the multicast periodic worker
+ * @bat_priv: the bat priv with all the soft interface information
+ */
+static void batadv_mcast_start_timer(struct batadv_priv *bat_priv)
+{
+       queue_delayed_work(batadv_event_workqueue, &bat_priv->mcast.work,
+                          msecs_to_jiffies(BATADV_MCAST_WORK_PERIOD));
+}
+
 /**
  * batadv_mcast_get_bridge - get the bridge on top of the softif if it exists
  * @soft_iface: netdev struct of the mesh interface
  * translation table except the ones listed in the given mcast_list.
  *
  * If mcast_list is NULL then all are retracted.
+ *
+ * Do not call outside of the mcast worker! (or cancel mcast worker first)
  */
 static void batadv_mcast_mla_tt_retract(struct batadv_priv *bat_priv,
                                        struct hlist_head *mcast_list)
        struct batadv_hw_addr *mcast_entry;
        struct hlist_node *tmp;
 
-       lockdep_assert_held(&bat_priv->tt.commit_lock);
+       WARN_ON(delayed_work_pending(&bat_priv->mcast.work));
 
        hlist_for_each_entry_safe(mcast_entry, tmp, &bat_priv->mcast.mla_list,
                                  list) {
  *
  * Adds multicast listener announcements from the given mcast_list to the
  * translation table if they have not been added yet.
+ *
+ * Do not call outside of the mcast worker! (or cancel mcast worker first)
  */
 static void batadv_mcast_mla_tt_add(struct batadv_priv *bat_priv,
                                    struct hlist_head *mcast_list)
        struct batadv_hw_addr *mcast_entry;
        struct hlist_node *tmp;
 
-       lockdep_assert_held(&bat_priv->tt.commit_lock);
+       WARN_ON(delayed_work_pending(&bat_priv->mcast.work));
 
        if (!mcast_list)
                return;
 }
 
 /**
- * batadv_mcast_mla_update - update the own MLAs
+ * __batadv_mcast_mla_update - update the own MLAs
  * @bat_priv: the bat priv with all the soft interface information
  *
  * Updates the own multicast listener announcements in the translation
  * table as well as the own, announced multicast tvlv container.
+ *
+ * Note that non-conflicting reads and writes to bat_priv->mcast.mla_list
+ * in batadv_mcast_mla_tt_retract() and batadv_mcast_mla_tt_add() are
+ * ensured by the non-parallel execution of the worker this function
+ * belongs to.
  */
-void batadv_mcast_mla_update(struct batadv_priv *bat_priv)
+static void __batadv_mcast_mla_update(struct batadv_priv *bat_priv)
 {
        struct net_device *soft_iface = bat_priv->soft_iface;
        struct hlist_head mcast_list = HLIST_HEAD_INIT;
        batadv_mcast_mla_list_free(&mcast_list);
 }
 
+/**
+ * batadv_mcast_mla_update - update the own MLAs
+ * @work: kernel work struct
+ *
+ * Updates the own multicast listener announcements in the translation
+ * table as well as the own, announced multicast tvlv container.
+ *
+ * In the end, reschedules the work timer.
+ */
+static void batadv_mcast_mla_update(struct work_struct *work)
+{
+       struct delayed_work *delayed_work;
+       struct batadv_priv_mcast *priv_mcast;
+       struct batadv_priv *bat_priv;
+
+       delayed_work = to_delayed_work(work);
+       priv_mcast = container_of(delayed_work, struct batadv_priv_mcast, work);
+       bat_priv = container_of(priv_mcast, struct batadv_priv, mcast);
+
+       __batadv_mcast_mla_update(bat_priv);
+       batadv_mcast_start_timer(bat_priv);
+}
+
 /**
  * batadv_mcast_is_report_ipv4 - check for IGMP reports
  * @skb: the ethernet frame destined for the mesh
        batadv_tvlv_handler_register(bat_priv, batadv_mcast_tvlv_ogm_handler,
                                     NULL, BATADV_TVLV_MCAST, 2,
                                     BATADV_TVLV_HANDLER_OGM_CIFNOTFND);
+
+       INIT_DELAYED_WORK(&bat_priv->mcast.work, batadv_mcast_mla_update);
+       batadv_mcast_start_timer(bat_priv);
 }
 
 #ifdef CONFIG_BATMAN_ADV_DEBUGFS
  */
 void batadv_mcast_free(struct batadv_priv *bat_priv)
 {
+       cancel_delayed_work_sync(&bat_priv->mcast.work);
+
        batadv_tvlv_container_unregister(bat_priv, BATADV_TVLV_MCAST, 2);
        batadv_tvlv_handler_unregister(bat_priv, BATADV_TVLV_MCAST, 2);
 
-       spin_lock_bh(&bat_priv->tt.commit_lock);
+       /* safely calling outside of worker, as worker was canceled above */
        batadv_mcast_mla_tt_retract(bat_priv, NULL);
-       spin_unlock_bh(&bat_priv->tt.commit_lock);
 }
 
 /**