|
@@ -613,9 +613,7 @@ struct mlxsw_sp_neigh_entry {
|
|
|
struct rhash_head ht_node;
|
|
|
struct mlxsw_sp_neigh_key key;
|
|
|
u16 rif;
|
|
|
- bool offloaded;
|
|
|
- struct work_struct work;
|
|
|
- struct mlxsw_sp_port *mlxsw_sp_port;
|
|
|
+ bool connected;
|
|
|
unsigned char ha[ETH_ALEN];
|
|
|
struct list_head nexthop_list; /* list of nexthops using
|
|
|
* this neigh entry
|
|
@@ -629,105 +627,88 @@ static const struct rhashtable_params mlxsw_sp_neigh_ht_params = {
|
|
|
.key_len = sizeof(struct mlxsw_sp_neigh_key),
|
|
|
};
|
|
|
|
|
|
-static int
|
|
|
-mlxsw_sp_neigh_entry_insert(struct mlxsw_sp *mlxsw_sp,
|
|
|
- struct mlxsw_sp_neigh_entry *neigh_entry)
|
|
|
-{
|
|
|
- return rhashtable_insert_fast(&mlxsw_sp->router.neigh_ht,
|
|
|
- &neigh_entry->ht_node,
|
|
|
- mlxsw_sp_neigh_ht_params);
|
|
|
-}
|
|
|
-
|
|
|
-static void
|
|
|
-mlxsw_sp_neigh_entry_remove(struct mlxsw_sp *mlxsw_sp,
|
|
|
- struct mlxsw_sp_neigh_entry *neigh_entry)
|
|
|
-{
|
|
|
- rhashtable_remove_fast(&mlxsw_sp->router.neigh_ht,
|
|
|
- &neigh_entry->ht_node,
|
|
|
- mlxsw_sp_neigh_ht_params);
|
|
|
-}
|
|
|
-
|
|
|
-static void mlxsw_sp_router_neigh_update_hw(struct work_struct *work);
|
|
|
-
|
|
|
static struct mlxsw_sp_neigh_entry *
|
|
|
-mlxsw_sp_neigh_entry_create(struct neighbour *n, u16 rif)
|
|
|
+mlxsw_sp_neigh_entry_alloc(struct mlxsw_sp *mlxsw_sp, struct neighbour *n,
|
|
|
+ u16 rif)
|
|
|
{
|
|
|
struct mlxsw_sp_neigh_entry *neigh_entry;
|
|
|
|
|
|
- neigh_entry = kzalloc(sizeof(*neigh_entry), GFP_ATOMIC);
|
|
|
+ neigh_entry = kzalloc(sizeof(*neigh_entry), GFP_KERNEL);
|
|
|
if (!neigh_entry)
|
|
|
return NULL;
|
|
|
+
|
|
|
neigh_entry->key.n = n;
|
|
|
neigh_entry->rif = rif;
|
|
|
- INIT_WORK(&neigh_entry->work, mlxsw_sp_router_neigh_update_hw);
|
|
|
INIT_LIST_HEAD(&neigh_entry->nexthop_list);
|
|
|
+
|
|
|
return neigh_entry;
|
|
|
}
|
|
|
|
|
|
-static void
|
|
|
-mlxsw_sp_neigh_entry_destroy(struct mlxsw_sp_neigh_entry *neigh_entry)
|
|
|
+static void mlxsw_sp_neigh_entry_free(struct mlxsw_sp_neigh_entry *neigh_entry)
|
|
|
{
|
|
|
kfree(neigh_entry);
|
|
|
}
|
|
|
|
|
|
-static struct mlxsw_sp_neigh_entry *
|
|
|
-mlxsw_sp_neigh_entry_lookup(struct mlxsw_sp *mlxsw_sp, struct neighbour *n)
|
|
|
+static int
|
|
|
+mlxsw_sp_neigh_entry_insert(struct mlxsw_sp *mlxsw_sp,
|
|
|
+ struct mlxsw_sp_neigh_entry *neigh_entry)
|
|
|
{
|
|
|
- struct mlxsw_sp_neigh_key key;
|
|
|
+ return rhashtable_insert_fast(&mlxsw_sp->router.neigh_ht,
|
|
|
+ &neigh_entry->ht_node,
|
|
|
+ mlxsw_sp_neigh_ht_params);
|
|
|
+}
|
|
|
|
|
|
- key.n = n;
|
|
|
- return rhashtable_lookup_fast(&mlxsw_sp->router.neigh_ht,
|
|
|
- &key, mlxsw_sp_neigh_ht_params);
|
|
|
+static void
|
|
|
+mlxsw_sp_neigh_entry_remove(struct mlxsw_sp *mlxsw_sp,
|
|
|
+ struct mlxsw_sp_neigh_entry *neigh_entry)
|
|
|
+{
|
|
|
+ rhashtable_remove_fast(&mlxsw_sp->router.neigh_ht,
|
|
|
+ &neigh_entry->ht_node,
|
|
|
+ mlxsw_sp_neigh_ht_params);
|
|
|
}
|
|
|
|
|
|
-int mlxsw_sp_router_neigh_construct(struct net_device *dev,
|
|
|
- struct neighbour *n)
|
|
|
+static struct mlxsw_sp_neigh_entry *
|
|
|
+mlxsw_sp_neigh_entry_create(struct mlxsw_sp *mlxsw_sp, struct neighbour *n)
|
|
|
{
|
|
|
- struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
|
|
|
- struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
|
|
|
struct mlxsw_sp_neigh_entry *neigh_entry;
|
|
|
struct mlxsw_sp_rif *r;
|
|
|
int err;
|
|
|
|
|
|
- if (n->tbl != &arp_tbl)
|
|
|
- return 0;
|
|
|
-
|
|
|
- neigh_entry = mlxsw_sp_neigh_entry_lookup(mlxsw_sp, n);
|
|
|
- if (neigh_entry)
|
|
|
- return 0;
|
|
|
-
|
|
|
r = mlxsw_sp_rif_find_by_dev(mlxsw_sp, n->dev);
|
|
|
- if (WARN_ON(!r))
|
|
|
- return -EINVAL;
|
|
|
+ if (!r)
|
|
|
+ return ERR_PTR(-EINVAL);
|
|
|
|
|
|
- neigh_entry = mlxsw_sp_neigh_entry_create(n, r->rif);
|
|
|
+ neigh_entry = mlxsw_sp_neigh_entry_alloc(mlxsw_sp, n, r->rif);
|
|
|
if (!neigh_entry)
|
|
|
- return -ENOMEM;
|
|
|
+ return ERR_PTR(-ENOMEM);
|
|
|
+
|
|
|
err = mlxsw_sp_neigh_entry_insert(mlxsw_sp, neigh_entry);
|
|
|
if (err)
|
|
|
goto err_neigh_entry_insert;
|
|
|
- return 0;
|
|
|
+
|
|
|
+ return neigh_entry;
|
|
|
|
|
|
err_neigh_entry_insert:
|
|
|
- mlxsw_sp_neigh_entry_destroy(neigh_entry);
|
|
|
- return err;
|
|
|
+ mlxsw_sp_neigh_entry_free(neigh_entry);
|
|
|
+ return ERR_PTR(err);
|
|
|
}
|
|
|
|
|
|
-void mlxsw_sp_router_neigh_destroy(struct net_device *dev,
|
|
|
- struct neighbour *n)
|
|
|
+static void
|
|
|
+mlxsw_sp_neigh_entry_destroy(struct mlxsw_sp *mlxsw_sp,
|
|
|
+ struct mlxsw_sp_neigh_entry *neigh_entry)
|
|
|
{
|
|
|
- struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
|
|
|
- struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
|
|
|
- struct mlxsw_sp_neigh_entry *neigh_entry;
|
|
|
+ mlxsw_sp_neigh_entry_remove(mlxsw_sp, neigh_entry);
|
|
|
+ mlxsw_sp_neigh_entry_free(neigh_entry);
|
|
|
+}
|
|
|
|
|
|
- if (n->tbl != &arp_tbl)
|
|
|
- return;
|
|
|
+static struct mlxsw_sp_neigh_entry *
|
|
|
+mlxsw_sp_neigh_entry_lookup(struct mlxsw_sp *mlxsw_sp, struct neighbour *n)
|
|
|
+{
|
|
|
+ struct mlxsw_sp_neigh_key key;
|
|
|
|
|
|
- neigh_entry = mlxsw_sp_neigh_entry_lookup(mlxsw_sp, n);
|
|
|
- if (!neigh_entry)
|
|
|
- return;
|
|
|
- mlxsw_sp_neigh_entry_remove(mlxsw_sp, neigh_entry);
|
|
|
- mlxsw_sp_neigh_entry_destroy(neigh_entry);
|
|
|
+ key.n = n;
|
|
|
+ return rhashtable_lookup_fast(&mlxsw_sp->router.neigh_ht,
|
|
|
+ &key, mlxsw_sp_neigh_ht_params);
|
|
|
}
|
|
|
|
|
|
static void
|
|
@@ -932,76 +913,99 @@ mlxsw_sp_nexthop_neigh_update(struct mlxsw_sp *mlxsw_sp,
|
|
|
struct mlxsw_sp_neigh_entry *neigh_entry,
|
|
|
bool removing);
|
|
|
|
|
|
-static void mlxsw_sp_router_neigh_update_hw(struct work_struct *work)
|
|
|
+static enum mlxsw_reg_rauht_op mlxsw_sp_rauht_op(bool adding)
|
|
|
+{
|
|
|
+ return adding ? MLXSW_REG_RAUHT_OP_WRITE_ADD :
|
|
|
+ MLXSW_REG_RAUHT_OP_WRITE_DELETE;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+mlxsw_sp_router_neigh_entry_op4(struct mlxsw_sp *mlxsw_sp,
|
|
|
+ struct mlxsw_sp_neigh_entry *neigh_entry,
|
|
|
+ enum mlxsw_reg_rauht_op op)
|
|
|
{
|
|
|
- struct mlxsw_sp_neigh_entry *neigh_entry =
|
|
|
- container_of(work, struct mlxsw_sp_neigh_entry, work);
|
|
|
struct neighbour *n = neigh_entry->key.n;
|
|
|
- struct mlxsw_sp_port *mlxsw_sp_port = neigh_entry->mlxsw_sp_port;
|
|
|
- struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
|
|
|
+ u32 dip = ntohl(*((__be32 *) n->primary_key));
|
|
|
char rauht_pl[MLXSW_REG_RAUHT_LEN];
|
|
|
- struct net_device *dev;
|
|
|
+
|
|
|
+ mlxsw_reg_rauht_pack4(rauht_pl, op, neigh_entry->rif, neigh_entry->ha,
|
|
|
+ dip);
|
|
|
+ mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rauht), rauht_pl);
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+mlxsw_sp_neigh_entry_update(struct mlxsw_sp *mlxsw_sp,
|
|
|
+ struct mlxsw_sp_neigh_entry *neigh_entry,
|
|
|
+ bool adding)
|
|
|
+{
|
|
|
+ if (!adding && !neigh_entry->connected)
|
|
|
+ return;
|
|
|
+ neigh_entry->connected = adding;
|
|
|
+ if (neigh_entry->key.n->tbl == &arp_tbl)
|
|
|
+ mlxsw_sp_router_neigh_entry_op4(mlxsw_sp, neigh_entry,
|
|
|
+ mlxsw_sp_rauht_op(adding));
|
|
|
+ else
|
|
|
+ WARN_ON_ONCE(1);
|
|
|
+}
|
|
|
+
|
|
|
+struct mlxsw_sp_neigh_event_work {
|
|
|
+ struct work_struct work;
|
|
|
+ struct mlxsw_sp *mlxsw_sp;
|
|
|
+ struct neighbour *n;
|
|
|
+};
|
|
|
+
|
|
|
+static void mlxsw_sp_router_neigh_event_work(struct work_struct *work)
|
|
|
+{
|
|
|
+ struct mlxsw_sp_neigh_event_work *neigh_work =
|
|
|
+ container_of(work, struct mlxsw_sp_neigh_event_work, work);
|
|
|
+ struct mlxsw_sp *mlxsw_sp = neigh_work->mlxsw_sp;
|
|
|
+ struct mlxsw_sp_neigh_entry *neigh_entry;
|
|
|
+ struct neighbour *n = neigh_work->n;
|
|
|
+ unsigned char ha[ETH_ALEN];
|
|
|
bool entry_connected;
|
|
|
u8 nud_state, dead;
|
|
|
- bool updating;
|
|
|
- bool removing;
|
|
|
- bool adding;
|
|
|
- u32 dip;
|
|
|
- int err;
|
|
|
|
|
|
+ /* If these parameters are changed after we release the lock,
|
|
|
+ * then we are guaranteed to receive another event letting us
|
|
|
+ * know about it.
|
|
|
+ */
|
|
|
read_lock_bh(&n->lock);
|
|
|
- dip = ntohl(*((__be32 *) n->primary_key));
|
|
|
- memcpy(neigh_entry->ha, n->ha, sizeof(neigh_entry->ha));
|
|
|
+ memcpy(ha, n->ha, ETH_ALEN);
|
|
|
nud_state = n->nud_state;
|
|
|
dead = n->dead;
|
|
|
- dev = n->dev;
|
|
|
read_unlock_bh(&n->lock);
|
|
|
|
|
|
+ rtnl_lock();
|
|
|
entry_connected = nud_state & NUD_VALID && !dead;
|
|
|
- adding = (!neigh_entry->offloaded) && entry_connected;
|
|
|
- updating = neigh_entry->offloaded && entry_connected;
|
|
|
- removing = neigh_entry->offloaded && !entry_connected;
|
|
|
-
|
|
|
- if (adding || updating) {
|
|
|
- mlxsw_reg_rauht_pack4(rauht_pl, MLXSW_REG_RAUHT_OP_WRITE_ADD,
|
|
|
- neigh_entry->rif,
|
|
|
- neigh_entry->ha, dip);
|
|
|
- err = mlxsw_reg_write(mlxsw_sp->core,
|
|
|
- MLXSW_REG(rauht), rauht_pl);
|
|
|
- if (err) {
|
|
|
- netdev_err(dev, "Could not add neigh %pI4h\n", &dip);
|
|
|
- neigh_entry->offloaded = false;
|
|
|
- } else {
|
|
|
- neigh_entry->offloaded = true;
|
|
|
- }
|
|
|
- mlxsw_sp_nexthop_neigh_update(mlxsw_sp, neigh_entry, false);
|
|
|
- } else if (removing) {
|
|
|
- mlxsw_reg_rauht_pack4(rauht_pl, MLXSW_REG_RAUHT_OP_WRITE_DELETE,
|
|
|
- neigh_entry->rif,
|
|
|
- neigh_entry->ha, dip);
|
|
|
- err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rauht),
|
|
|
- rauht_pl);
|
|
|
- if (err) {
|
|
|
- netdev_err(dev, "Could not delete neigh %pI4h\n", &dip);
|
|
|
- neigh_entry->offloaded = true;
|
|
|
- } else {
|
|
|
- neigh_entry->offloaded = false;
|
|
|
- }
|
|
|
- mlxsw_sp_nexthop_neigh_update(mlxsw_sp, neigh_entry, true);
|
|
|
+ neigh_entry = mlxsw_sp_neigh_entry_lookup(mlxsw_sp, n);
|
|
|
+ if (!entry_connected && !neigh_entry)
|
|
|
+ goto out;
|
|
|
+ if (!neigh_entry) {
|
|
|
+ neigh_entry = mlxsw_sp_neigh_entry_create(mlxsw_sp, n);
|
|
|
+ if (IS_ERR(neigh_entry))
|
|
|
+ goto out;
|
|
|
}
|
|
|
|
|
|
+ memcpy(neigh_entry->ha, ha, ETH_ALEN);
|
|
|
+ mlxsw_sp_neigh_entry_update(mlxsw_sp, neigh_entry, entry_connected);
|
|
|
+ mlxsw_sp_nexthop_neigh_update(mlxsw_sp, neigh_entry, !entry_connected);
|
|
|
+
|
|
|
+ if (!neigh_entry->connected && list_empty(&neigh_entry->nexthop_list))
|
|
|
+ mlxsw_sp_neigh_entry_destroy(mlxsw_sp, neigh_entry);
|
|
|
+
|
|
|
+out:
|
|
|
+ rtnl_unlock();
|
|
|
neigh_release(n);
|
|
|
- mlxsw_sp_port_dev_put(mlxsw_sp_port);
|
|
|
+ kfree(neigh_work);
|
|
|
}
|
|
|
|
|
|
int mlxsw_sp_router_netevent_event(struct notifier_block *unused,
|
|
|
unsigned long event, void *ptr)
|
|
|
{
|
|
|
- struct mlxsw_sp_neigh_entry *neigh_entry;
|
|
|
+ struct mlxsw_sp_neigh_event_work *neigh_work;
|
|
|
struct mlxsw_sp_port *mlxsw_sp_port;
|
|
|
struct mlxsw_sp *mlxsw_sp;
|
|
|
unsigned long interval;
|
|
|
- struct net_device *dev;
|
|
|
struct neigh_parms *p;
|
|
|
struct neighbour *n;
|
|
|
|
|
@@ -1028,32 +1032,31 @@ int mlxsw_sp_router_netevent_event(struct notifier_block *unused,
|
|
|
break;
|
|
|
case NETEVENT_NEIGH_UPDATE:
|
|
|
n = ptr;
|
|
|
- dev = n->dev;
|
|
|
|
|
|
if (n->tbl != &arp_tbl)
|
|
|
return NOTIFY_DONE;
|
|
|
|
|
|
- mlxsw_sp_port = mlxsw_sp_port_lower_dev_hold(dev);
|
|
|
+ mlxsw_sp_port = mlxsw_sp_port_lower_dev_hold(n->dev);
|
|
|
if (!mlxsw_sp_port)
|
|
|
return NOTIFY_DONE;
|
|
|
|
|
|
- mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
|
|
|
- neigh_entry = mlxsw_sp_neigh_entry_lookup(mlxsw_sp, n);
|
|
|
- if (WARN_ON(!neigh_entry)) {
|
|
|
+ neigh_work = kzalloc(sizeof(*neigh_work), GFP_ATOMIC);
|
|
|
+ if (!neigh_work) {
|
|
|
mlxsw_sp_port_dev_put(mlxsw_sp_port);
|
|
|
- return NOTIFY_DONE;
|
|
|
+ return NOTIFY_BAD;
|
|
|
}
|
|
|
- neigh_entry->mlxsw_sp_port = mlxsw_sp_port;
|
|
|
+
|
|
|
+ INIT_WORK(&neigh_work->work, mlxsw_sp_router_neigh_event_work);
|
|
|
+ neigh_work->mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
|
|
|
+ neigh_work->n = n;
|
|
|
|
|
|
/* Take a reference to ensure the neighbour won't be
|
|
|
* destructed until we drop the reference in delayed
|
|
|
* work.
|
|
|
*/
|
|
|
neigh_clone(n);
|
|
|
- if (!mlxsw_core_schedule_work(&neigh_entry->work)) {
|
|
|
- neigh_release(n);
|
|
|
- mlxsw_sp_port_dev_put(mlxsw_sp_port);
|
|
|
- }
|
|
|
+ mlxsw_core_schedule_work(&neigh_work->work);
|
|
|
+ mlxsw_sp_port_dev_put(mlxsw_sp_port);
|
|
|
break;
|
|
|
}
|
|
|
|
|
@@ -1334,14 +1337,11 @@ mlxsw_sp_nexthop_neigh_update(struct mlxsw_sp *mlxsw_sp,
|
|
|
{
|
|
|
struct mlxsw_sp_nexthop *nh;
|
|
|
|
|
|
- /* Take RTNL mutex here to prevent lists from changes */
|
|
|
- rtnl_lock();
|
|
|
list_for_each_entry(nh, &neigh_entry->nexthop_list,
|
|
|
neigh_list_node) {
|
|
|
__mlxsw_sp_nexthop_neigh_update(nh, removing);
|
|
|
mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nh_grp);
|
|
|
}
|
|
|
- rtnl_unlock();
|
|
|
}
|
|
|
|
|
|
static int mlxsw_sp_nexthop_init(struct mlxsw_sp *mlxsw_sp,
|
|
@@ -1368,8 +1368,11 @@ static int mlxsw_sp_nexthop_init(struct mlxsw_sp *mlxsw_sp,
|
|
|
}
|
|
|
neigh_entry = mlxsw_sp_neigh_entry_lookup(mlxsw_sp, n);
|
|
|
if (!neigh_entry) {
|
|
|
- neigh_release(n);
|
|
|
- return -EINVAL;
|
|
|
+ neigh_entry = mlxsw_sp_neigh_entry_create(mlxsw_sp, n);
|
|
|
+ if (IS_ERR(neigh_entry)) {
|
|
|
+ neigh_release(n);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/* If that is the first nexthop connected to that neigh, add to
|
|
@@ -1395,6 +1398,7 @@ static void mlxsw_sp_nexthop_fini(struct mlxsw_sp *mlxsw_sp,
|
|
|
struct mlxsw_sp_nexthop *nh)
|
|
|
{
|
|
|
struct mlxsw_sp_neigh_entry *neigh_entry = nh->neigh_entry;
|
|
|
+ struct neighbour *n = neigh_entry->key.n;
|
|
|
|
|
|
__mlxsw_sp_nexthop_neigh_update(nh, true);
|
|
|
list_del(&nh->neigh_list_node);
|
|
@@ -1405,7 +1409,10 @@ static void mlxsw_sp_nexthop_fini(struct mlxsw_sp *mlxsw_sp,
|
|
|
if (list_empty(&nh->neigh_entry->nexthop_list))
|
|
|
list_del(&nh->neigh_entry->nexthop_neighs_list_node);
|
|
|
|
|
|
- neigh_release(neigh_entry->key.n);
|
|
|
+ if (!neigh_entry->connected && list_empty(&neigh_entry->nexthop_list))
|
|
|
+ mlxsw_sp_neigh_entry_destroy(mlxsw_sp, neigh_entry);
|
|
|
+
|
|
|
+ neigh_release(n);
|
|
|
}
|
|
|
|
|
|
static struct mlxsw_sp_nexthop_group *
|