|
@@ -559,6 +559,10 @@ struct mlxsw_sp_neigh_entry {
|
|
|
struct mlxsw_sp_neigh_key key;
|
|
|
u16 rif;
|
|
|
struct neighbour *n;
|
|
|
+ bool offloaded;
|
|
|
+ struct delayed_work dw;
|
|
|
+ struct mlxsw_sp_port *mlxsw_sp_port;
|
|
|
+ unsigned char ha[ETH_ALEN];
|
|
|
};
|
|
|
|
|
|
static const struct rhashtable_params mlxsw_sp_neigh_ht_params = {
|
|
@@ -585,6 +589,8 @@ mlxsw_sp_neigh_entry_remove(struct mlxsw_sp *mlxsw_sp,
|
|
|
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(const void *addr, size_t addr_len,
|
|
|
struct net_device *dev, u16 rif,
|
|
@@ -599,6 +605,7 @@ mlxsw_sp_neigh_entry_create(const void *addr, size_t addr_len,
|
|
|
neigh_entry->key.dev = dev;
|
|
|
neigh_entry->rif = rif;
|
|
|
neigh_entry->n = n;
|
|
|
+ INIT_DELAYED_WORK(&neigh_entry->dw, mlxsw_sp_router_neigh_update_hw);
|
|
|
return neigh_entry;
|
|
|
}
|
|
|
|
|
@@ -801,13 +808,76 @@ static void mlxsw_sp_router_neighs_update_work(struct work_struct *work)
|
|
|
mlxsw_sp_router_neighs_update_work_schedule(mlxsw_sp);
|
|
|
}
|
|
|
|
|
|
+static void mlxsw_sp_router_neigh_update_hw(struct work_struct *work)
|
|
|
+{
|
|
|
+ struct mlxsw_sp_neigh_entry *neigh_entry =
|
|
|
+ container_of(work, struct mlxsw_sp_neigh_entry, dw.work);
|
|
|
+ struct neighbour *n = neigh_entry->n;
|
|
|
+ struct mlxsw_sp_port *mlxsw_sp_port = neigh_entry->mlxsw_sp_port;
|
|
|
+ struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
|
|
|
+ char rauht_pl[MLXSW_REG_RAUHT_LEN];
|
|
|
+ struct net_device *dev;
|
|
|
+ bool entry_connected;
|
|
|
+ u8 nud_state;
|
|
|
+ bool updating;
|
|
|
+ bool removing;
|
|
|
+ bool adding;
|
|
|
+ u32 dip;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ read_lock_bh(&n->lock);
|
|
|
+ dip = ntohl(*((__be32 *) n->primary_key));
|
|
|
+ memcpy(neigh_entry->ha, n->ha, sizeof(neigh_entry->ha));
|
|
|
+ nud_state = n->nud_state;
|
|
|
+ dev = n->dev;
|
|
|
+ read_unlock_bh(&n->lock);
|
|
|
+
|
|
|
+ entry_connected = nud_state & NUD_VALID;
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ } 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ neigh_release(n);
|
|
|
+ mlxsw_sp_port_dev_put(mlxsw_sp_port);
|
|
|
+}
|
|
|
+
|
|
|
static 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_port *mlxsw_sp_port;
|
|
|
struct mlxsw_sp *mlxsw_sp;
|
|
|
unsigned long interval;
|
|
|
+ struct net_device *dev;
|
|
|
struct neigh_parms *p;
|
|
|
+ struct neighbour *n;
|
|
|
+ u32 dip;
|
|
|
|
|
|
switch (event) {
|
|
|
case NETEVENT_DELAY_PROBE_TIME_UPDATE:
|
|
@@ -830,6 +900,39 @@ static int mlxsw_sp_router_netevent_event(struct notifier_block *unused,
|
|
|
|
|
|
mlxsw_sp_port_dev_put(mlxsw_sp_port);
|
|
|
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);
|
|
|
+ if (!mlxsw_sp_port)
|
|
|
+ return NOTIFY_DONE;
|
|
|
+
|
|
|
+ mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
|
|
|
+ dip = ntohl(*((__be32 *) n->primary_key));
|
|
|
+ neigh_entry = mlxsw_sp_neigh_entry_lookup(mlxsw_sp,
|
|
|
+ &dip,
|
|
|
+ sizeof(__be32),
|
|
|
+ dev);
|
|
|
+ if (WARN_ON(!neigh_entry) || WARN_ON(neigh_entry->n != n)) {
|
|
|
+ mlxsw_sp_port_dev_put(mlxsw_sp_port);
|
|
|
+ return NOTIFY_DONE;
|
|
|
+ }
|
|
|
+ neigh_entry->mlxsw_sp_port = mlxsw_sp_port;
|
|
|
+
|
|
|
+ /* 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_dw(&neigh_entry->dw, 0)) {
|
|
|
+ neigh_release(n);
|
|
|
+ mlxsw_sp_port_dev_put(mlxsw_sp_port);
|
|
|
+ }
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
return NOTIFY_DONE;
|