diff options
| author | Yotam Gigi <yotamg@mellanox.com> | 2016-07-05 05:27:43 -0400 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2016-07-05 12:06:29 -0400 |
| commit | c723c735fa6bacfdb01c4697c2cfeba142990d18 (patch) | |
| tree | 52ca53d6e985d9c6853683d5fddab37941f32654 | |
| parent | 2a4501ae18b52fcdf553404286e6cefabd1d17ec (diff) | |
mlxsw: spectrum_router: Periodically update the kernel's neigh table
As previously explained, the driver should periodically poll the device
for neighbours activity according to the configured DELAY_PROBE_TIME.
This will prevent active neighbours from staying in STALE state for long
periods of time.
During init configure the polling interval according to the
DELAY_PROBE_TIME used in the default table. In addition, register a
netevent notification block, so that the interval is updated whenever
DELAY_PROBE_TIME changes.
Using the computed interval schedule a delayed work, which will update
the kernel via neigh_event_send() on any active neighbour since the last
delayed work.
Signed-off-by: Yotam Gigi <yotamg@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
| -rw-r--r-- | drivers/net/ethernet/mellanox/mlxsw/spectrum.h | 4 | ||||
| -rw-r--r-- | drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c | 192 |
2 files changed, 194 insertions, 2 deletions
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h index 734c5baffaf1..9c2a60f01517 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h | |||
| @@ -214,6 +214,10 @@ struct mlxsw_sp_router { | |||
| 214 | struct mlxsw_sp_lpm_tree lpm_trees[MLXSW_SP_LPM_TREE_COUNT]; | 214 | struct mlxsw_sp_lpm_tree lpm_trees[MLXSW_SP_LPM_TREE_COUNT]; |
| 215 | struct mlxsw_sp_vr vrs[MLXSW_SP_VIRTUAL_ROUTER_MAX]; | 215 | struct mlxsw_sp_vr vrs[MLXSW_SP_VIRTUAL_ROUTER_MAX]; |
| 216 | struct rhashtable neigh_ht; | 216 | struct rhashtable neigh_ht; |
| 217 | struct { | ||
| 218 | struct delayed_work dw; | ||
| 219 | unsigned long interval; /* ms */ | ||
| 220 | } neighs_update; | ||
| 217 | }; | 221 | }; |
| 218 | 222 | ||
| 219 | struct mlxsw_sp { | 223 | struct mlxsw_sp { |
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c index 90d382a5d6a7..db1c2c42cd3b 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | * Copyright (c) 2016 Mellanox Technologies. All rights reserved. | 3 | * Copyright (c) 2016 Mellanox Technologies. All rights reserved. |
| 4 | * Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com> | 4 | * Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com> |
| 5 | * Copyright (c) 2016 Ido Schimmel <idosch@mellanox.com> | 5 | * Copyright (c) 2016 Ido Schimmel <idosch@mellanox.com> |
| 6 | * Copyright (c) 2016 Yotam Gigi <yotamg@mellanox.com> | ||
| 6 | * | 7 | * |
| 7 | * Redistribution and use in source and binary forms, with or without | 8 | * Redistribution and use in source and binary forms, with or without |
| 8 | * modification, are permitted provided that the following conditions are met: | 9 | * modification, are permitted provided that the following conditions are met: |
| @@ -38,6 +39,8 @@ | |||
| 38 | #include <linux/rhashtable.h> | 39 | #include <linux/rhashtable.h> |
| 39 | #include <linux/bitops.h> | 40 | #include <linux/bitops.h> |
| 40 | #include <linux/in6.h> | 41 | #include <linux/in6.h> |
| 42 | #include <linux/notifier.h> | ||
| 43 | #include <net/netevent.h> | ||
| 41 | #include <net/neighbour.h> | 44 | #include <net/neighbour.h> |
| 42 | #include <net/arp.h> | 45 | #include <net/arp.h> |
| 43 | 46 | ||
| @@ -676,14 +679,199 @@ void mlxsw_sp_router_neigh_destroy(struct net_device *dev, | |||
| 676 | mlxsw_sp_neigh_entry_destroy(neigh_entry); | 679 | mlxsw_sp_neigh_entry_destroy(neigh_entry); |
| 677 | } | 680 | } |
| 678 | 681 | ||
| 682 | static void | ||
| 683 | mlxsw_sp_router_neighs_update_interval_init(struct mlxsw_sp *mlxsw_sp) | ||
| 684 | { | ||
| 685 | unsigned long interval = NEIGH_VAR(&arp_tbl.parms, DELAY_PROBE_TIME); | ||
| 686 | |||
| 687 | mlxsw_sp->router.neighs_update.interval = jiffies_to_msecs(interval); | ||
| 688 | } | ||
| 689 | |||
| 690 | static void mlxsw_sp_router_neigh_ent_ipv4_process(struct mlxsw_sp *mlxsw_sp, | ||
| 691 | char *rauhtd_pl, | ||
| 692 | int ent_index) | ||
| 693 | { | ||
| 694 | struct net_device *dev; | ||
| 695 | struct neighbour *n; | ||
| 696 | __be32 dipn; | ||
| 697 | u32 dip; | ||
| 698 | u16 rif; | ||
| 699 | |||
| 700 | mlxsw_reg_rauhtd_ent_ipv4_unpack(rauhtd_pl, ent_index, &rif, &dip); | ||
| 701 | |||
| 702 | if (!mlxsw_sp->rifs[rif]) { | ||
| 703 | dev_err_ratelimited(mlxsw_sp->bus_info->dev, "Incorrect RIF in neighbour entry\n"); | ||
| 704 | return; | ||
| 705 | } | ||
| 706 | |||
| 707 | dipn = htonl(dip); | ||
| 708 | dev = mlxsw_sp->rifs[rif]->dev; | ||
| 709 | n = neigh_lookup(&arp_tbl, &dipn, dev); | ||
| 710 | if (!n) { | ||
| 711 | netdev_err(dev, "Failed to find matching neighbour for IP=%pI4h\n", | ||
| 712 | &dip); | ||
| 713 | return; | ||
| 714 | } | ||
| 715 | |||
| 716 | netdev_dbg(dev, "Updating neighbour with IP=%pI4h\n", &dip); | ||
| 717 | neigh_event_send(n, NULL); | ||
| 718 | neigh_release(n); | ||
| 719 | } | ||
| 720 | |||
| 721 | static void mlxsw_sp_router_neigh_rec_ipv4_process(struct mlxsw_sp *mlxsw_sp, | ||
| 722 | char *rauhtd_pl, | ||
| 723 | int rec_index) | ||
| 724 | { | ||
| 725 | u8 num_entries; | ||
| 726 | int i; | ||
| 727 | |||
| 728 | num_entries = mlxsw_reg_rauhtd_ipv4_rec_num_entries_get(rauhtd_pl, | ||
| 729 | rec_index); | ||
| 730 | /* Hardware starts counting at 0, so add 1. */ | ||
| 731 | num_entries++; | ||
| 732 | |||
| 733 | /* Each record consists of several neighbour entries. */ | ||
| 734 | for (i = 0; i < num_entries; i++) { | ||
| 735 | int ent_index; | ||
| 736 | |||
| 737 | ent_index = rec_index * MLXSW_REG_RAUHTD_IPV4_ENT_PER_REC + i; | ||
| 738 | mlxsw_sp_router_neigh_ent_ipv4_process(mlxsw_sp, rauhtd_pl, | ||
| 739 | ent_index); | ||
| 740 | } | ||
| 741 | |||
| 742 | } | ||
| 743 | |||
| 744 | static void mlxsw_sp_router_neigh_rec_process(struct mlxsw_sp *mlxsw_sp, | ||
| 745 | char *rauhtd_pl, int rec_index) | ||
| 746 | { | ||
| 747 | switch (mlxsw_reg_rauhtd_rec_type_get(rauhtd_pl, rec_index)) { | ||
| 748 | case MLXSW_REG_RAUHTD_TYPE_IPV4: | ||
| 749 | mlxsw_sp_router_neigh_rec_ipv4_process(mlxsw_sp, rauhtd_pl, | ||
| 750 | rec_index); | ||
| 751 | break; | ||
| 752 | case MLXSW_REG_RAUHTD_TYPE_IPV6: | ||
| 753 | WARN_ON_ONCE(1); | ||
| 754 | break; | ||
| 755 | } | ||
| 756 | } | ||
| 757 | |||
| 758 | static void | ||
| 759 | mlxsw_sp_router_neighs_update_work_schedule(struct mlxsw_sp *mlxsw_sp) | ||
| 760 | { | ||
| 761 | unsigned long interval = mlxsw_sp->router.neighs_update.interval; | ||
| 762 | |||
| 763 | mlxsw_core_schedule_dw(&mlxsw_sp->router.neighs_update.dw, | ||
| 764 | msecs_to_jiffies(interval)); | ||
| 765 | } | ||
| 766 | |||
| 767 | static void mlxsw_sp_router_neighs_update_work(struct work_struct *work) | ||
| 768 | { | ||
| 769 | struct mlxsw_sp *mlxsw_sp; | ||
| 770 | char *rauhtd_pl; | ||
| 771 | u8 num_rec; | ||
| 772 | int i, err; | ||
| 773 | |||
| 774 | rauhtd_pl = kmalloc(MLXSW_REG_RAUHTD_LEN, GFP_KERNEL); | ||
| 775 | if (!rauhtd_pl) | ||
| 776 | return; | ||
| 777 | |||
| 778 | mlxsw_sp = container_of(work, struct mlxsw_sp, | ||
| 779 | router.neighs_update.dw.work); | ||
| 780 | |||
| 781 | /* Make sure the neighbour's netdev isn't removed in the | ||
| 782 | * process. | ||
| 783 | */ | ||
| 784 | rtnl_lock(); | ||
| 785 | do { | ||
| 786 | mlxsw_reg_rauhtd_pack(rauhtd_pl, MLXSW_REG_RAUHTD_TYPE_IPV4); | ||
| 787 | err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(rauhtd), | ||
| 788 | rauhtd_pl); | ||
| 789 | if (err) { | ||
| 790 | dev_err_ratelimited(mlxsw_sp->bus_info->dev, "Failed to dump neighbour talbe\n"); | ||
| 791 | break; | ||
| 792 | } | ||
| 793 | num_rec = mlxsw_reg_rauhtd_num_rec_get(rauhtd_pl); | ||
| 794 | for (i = 0; i < num_rec; i++) | ||
| 795 | mlxsw_sp_router_neigh_rec_process(mlxsw_sp, rauhtd_pl, | ||
| 796 | i); | ||
| 797 | } while (num_rec); | ||
| 798 | rtnl_unlock(); | ||
| 799 | |||
| 800 | kfree(rauhtd_pl); | ||
| 801 | mlxsw_sp_router_neighs_update_work_schedule(mlxsw_sp); | ||
| 802 | } | ||
| 803 | |||
| 804 | static int mlxsw_sp_router_netevent_event(struct notifier_block *unused, | ||
| 805 | unsigned long event, void *ptr) | ||
| 806 | { | ||
| 807 | struct mlxsw_sp_port *mlxsw_sp_port; | ||
| 808 | struct mlxsw_sp *mlxsw_sp; | ||
| 809 | unsigned long interval; | ||
| 810 | struct neigh_parms *p; | ||
| 811 | |||
| 812 | switch (event) { | ||
| 813 | case NETEVENT_DELAY_PROBE_TIME_UPDATE: | ||
| 814 | p = ptr; | ||
| 815 | |||
| 816 | /* We don't care about changes in the default table. */ | ||
| 817 | if (!p->dev || p->tbl != &arp_tbl) | ||
| 818 | return NOTIFY_DONE; | ||
| 819 | |||
| 820 | /* We are in atomic context and can't take RTNL mutex, | ||
| 821 | * so use RCU variant to walk the device chain. | ||
| 822 | */ | ||
| 823 | mlxsw_sp_port = mlxsw_sp_port_lower_dev_hold(p->dev); | ||
| 824 | if (!mlxsw_sp_port) | ||
| 825 | return NOTIFY_DONE; | ||
| 826 | |||
| 827 | mlxsw_sp = mlxsw_sp_port->mlxsw_sp; | ||
| 828 | interval = jiffies_to_msecs(NEIGH_VAR(p, DELAY_PROBE_TIME)); | ||
| 829 | mlxsw_sp->router.neighs_update.interval = interval; | ||
| 830 | |||
| 831 | mlxsw_sp_port_dev_put(mlxsw_sp_port); | ||
| 832 | break; | ||
| 833 | } | ||
| 834 | |||
| 835 | return NOTIFY_DONE; | ||
| 836 | } | ||
| 837 | |||
| 838 | static struct notifier_block mlxsw_sp_router_netevent_nb __read_mostly = { | ||
| 839 | .notifier_call = mlxsw_sp_router_netevent_event, | ||
| 840 | }; | ||
| 841 | |||
| 679 | static int mlxsw_sp_neigh_init(struct mlxsw_sp *mlxsw_sp) | 842 | static int mlxsw_sp_neigh_init(struct mlxsw_sp *mlxsw_sp) |
| 680 | { | 843 | { |
| 681 | return rhashtable_init(&mlxsw_sp->router.neigh_ht, | 844 | int err; |
| 682 | &mlxsw_sp_neigh_ht_params); | 845 | |
| 846 | err = rhashtable_init(&mlxsw_sp->router.neigh_ht, | ||
| 847 | &mlxsw_sp_neigh_ht_params); | ||
| 848 | if (err) | ||
| 849 | return err; | ||
| 850 | |||
| 851 | /* Initialize the polling interval according to the default | ||
| 852 | * table. | ||
| 853 | */ | ||
| 854 | mlxsw_sp_router_neighs_update_interval_init(mlxsw_sp); | ||
| 855 | |||
| 856 | err = register_netevent_notifier(&mlxsw_sp_router_netevent_nb); | ||
| 857 | if (err) | ||
| 858 | goto err_register_netevent_notifier; | ||
| 859 | |||
| 860 | INIT_DELAYED_WORK(&mlxsw_sp->router.neighs_update.dw, | ||
| 861 | mlxsw_sp_router_neighs_update_work); | ||
| 862 | mlxsw_core_schedule_dw(&mlxsw_sp->router.neighs_update.dw, 0); | ||
| 863 | |||
| 864 | return 0; | ||
| 865 | |||
| 866 | err_register_netevent_notifier: | ||
| 867 | rhashtable_destroy(&mlxsw_sp->router.neigh_ht); | ||
| 868 | return err; | ||
| 683 | } | 869 | } |
| 684 | 870 | ||
| 685 | static void mlxsw_sp_neigh_fini(struct mlxsw_sp *mlxsw_sp) | 871 | static void mlxsw_sp_neigh_fini(struct mlxsw_sp *mlxsw_sp) |
| 686 | { | 872 | { |
| 873 | cancel_delayed_work_sync(&mlxsw_sp->router.neighs_update.dw); | ||
| 874 | unregister_netevent_notifier(&mlxsw_sp_router_netevent_nb); | ||
| 687 | rhashtable_destroy(&mlxsw_sp->router.neigh_ht); | 875 | rhashtable_destroy(&mlxsw_sp->router.neigh_ht); |
| 688 | } | 876 | } |
| 689 | 877 | ||
