Index: sys/ofed/drivers/net/mlx4/en_netdev.c =================================================================== --- sys/ofed/drivers/net/mlx4/en_netdev.c +++ sys/ofed/drivers/net/mlx4/en_netdev.c @@ -451,6 +451,107 @@ } +/* + * When VLANs are added/removed, the VLAN ioctl path acquires a non-sleepable + * rmlock. As part of this ioctl path, a hook for the underlying device driver + * is called so the card can possibly register the vlan with the controller. + * The mlx4 hook acquires a per-port state Linux mutex, which is a sxlock on + * FreeBSD. This can sleep, and if it does it will cause a panic. This avoids + * that panic by utilizing mlx4's per-port workqueue (which is backed by a + * FreeBSD taskqueue) to defer all add/remove operations. + * Note: In the time between a remove ioctl and when the remove actually + * happens, the tagged packets will still be seen by bpf listeners. + */ +static void mlx4_en_add_kill_vlans(struct mlx4_en_priv *priv) +{ + u16 vid; + long *pending_adds = &priv->pending_vlan_adds[0]; + long *pending_kills = &priv->pending_vlan_kills[0]; + while (1) { + spin_lock(&priv->pending_vlan_lock); + vid = find_first_bit(pending_adds, MLX4_VLAN_PENDING_SIZE); + if (vid >= MLX4_VLAN_PENDING_SIZE) { + spin_unlock(&priv->pending_vlan_lock); + break; + } + clear_bit(vid, pending_adds); + spin_unlock(&priv->pending_vlan_lock); + mlx4_en_vlan_rx_add_vid(priv, priv->dev, vid); + } + while (1) { + spin_lock(&priv->pending_vlan_lock); + vid = find_first_bit(pending_kills, MLX4_VLAN_PENDING_SIZE); + if (vid >= MLX4_VLAN_PENDING_SIZE) { + spin_unlock(&priv->pending_vlan_lock); + break; + } + clear_bit(vid, pending_kills); + spin_unlock(&priv->pending_vlan_lock); + mlx4_en_vlan_rx_kill_vid(priv, priv->dev, vid); + } +} + +static void mlx4_en_vlan_vid_fn(struct work_struct *work) +{ + struct mlx4_en_priv *priv; + priv = container_of(work, struct mlx4_en_priv, vid_task); + mlx4_en_add_kill_vlans(priv); +} + +static void mlx4_en_vlan_add_vid(void *arg, struct net_device *dev, u16 vid) +{ + struct mlx4_en_priv *priv = netdev_priv(dev); + /* + * Since the vlan_config handler is registered for both ports it will + * fire twice. This check ensures that we only actually do the add for + * the port that triggered the event. + */ + if (arg != priv) + return; + spin_lock(&priv->pending_vlan_lock); + long *pending_adds = &priv->pending_vlan_adds[0]; + long *pending_kills = &priv->pending_vlan_kills[0]; + if (test_bit(vid, pending_kills)) { + /* + * If the vlan is going to be killed and re-added, there's + * nothing to do. + */ + clear_bit(vid, pending_kills); + spin_unlock(&priv->pending_vlan_lock); + return; + } + set_bit(vid, pending_adds); + spin_unlock(&priv->pending_vlan_lock); + queue_work(priv->mdev->workqueue, &priv->vid_task); +} + +static void mlx4_en_vlan_kill_vid(void *arg, struct net_device *dev, u16 vid) +{ + struct mlx4_en_priv *priv = netdev_priv(dev); + /* + * Since the vlan_unconfig handler is registered for both ports it will + * fire twice. This check ensures that we only actually do the kill for + * the port that triggered the event. + */ + if (arg != priv) + return; + spin_lock(&priv->pending_vlan_lock); + long *pending_adds = &priv->pending_vlan_adds[0]; + long *pending_kills = &priv->pending_vlan_kills[0]; + if (test_bit(vid, pending_adds)) { + /* + * If the vlan is going to be added and re-killed, there's + * nothing to do. + */ + clear_bit(vid, pending_adds); + spin_unlock(&priv->pending_vlan_lock); + return; + } + set_bit(vid, pending_kills); + spin_unlock(&priv->pending_vlan_lock); + queue_work(priv->mdev->workqueue, &priv->vid_task); +} + static int mlx4_en_uc_steer_add(struct mlx4_en_priv *priv, unsigned char *mac, int *qpn, u64 *reg_id) { @@ -2082,6 +2183,11 @@ spin_lock_init(&priv->filters_lock); #endif + INIT_WORK(&priv->vid_task, mlx4_en_vlan_vid_fn); + spin_lock_init(&priv->pending_vlan_lock); + memset(&priv->pending_vlan_adds, 0, sizeof(priv->pending_vlan_adds)); + memset(&priv->pending_vlan_kills, 0, sizeof(priv->pending_vlan_kills)); + priv->msg_enable = MLX4_EN_MSG_LEVEL; priv->dev = dev; priv->mdev = mdev; @@ -2193,9 +2299,9 @@ /* Register for VLAN events */ priv->vlan_attach = EVENTHANDLER_REGISTER(vlan_config, - mlx4_en_vlan_rx_add_vid, priv, EVENTHANDLER_PRI_FIRST); + mlx4_en_vlan_add_vid, priv, EVENTHANDLER_PRI_FIRST); priv->vlan_detach = EVENTHANDLER_REGISTER(vlan_unconfig, - mlx4_en_vlan_rx_kill_vid, priv, EVENTHANDLER_PRI_FIRST); + mlx4_en_vlan_kill_vid, priv, EVENTHANDLER_PRI_FIRST); mdev->pndev[priv->port] = dev; Index: sys/ofed/drivers/net/mlx4/mlx4_en.h =================================================================== --- sys/ofed/drivers/net/mlx4/mlx4_en.h +++ sys/ofed/drivers/net/mlx4/mlx4_en.h @@ -480,6 +480,7 @@ #endif +#define MLX4_VLAN_PENDING_SIZE (NBLONG * BITS_TO_LONGS(VLAN_N_VID)) enum { MLX4_EN_FLAG_PROMISC = (1 << 0), @@ -570,6 +571,7 @@ struct work_struct linkstate_task; struct delayed_work stats_task; struct delayed_work service_task; + struct work_struct vid_task; struct mlx4_en_perf_stats pstats; struct mlx4_en_pkt_stats pkstats; struct mlx4_en_pkt_stats pkstats_last; @@ -583,6 +585,9 @@ u64 broadcast_id; struct mlx4_en_stat_out_mbox hw_stats; int vids[128]; + long pending_vlan_adds[BITS_TO_LONGS(VLAN_N_VID)]; + long pending_vlan_kills[BITS_TO_LONGS(VLAN_N_VID)]; + spinlock_t pending_vlan_lock; bool wol; struct device *ddev; struct dentry *dev_root;