Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net
authorDavid S. Miller <davem@davemloft.net>
Tue, 31 Dec 2019 21:37:13 +0000 (13:37 -0800)
committerDavid S. Miller <davem@davemloft.net>
Tue, 31 Dec 2019 21:37:13 +0000 (13:37 -0800)
Simple overlapping changes in bpf land wrt. bpf_helper_defs.h
handling.

Signed-off-by: David S. Miller <davem@davemloft.net>
18 files changed:
1  2 
MAINTAINERS
drivers/net/dsa/sja1105/sja1105_ptp.c
drivers/net/ethernet/chelsio/cxgb4/cxgb4.h
drivers/net/ethernet/freescale/dpaa/dpaa_eth.c
drivers/net/ethernet/mellanox/mlxsw/reg.h
drivers/net/ethernet/mellanox/mlxsw/spectrum.c
drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
drivers/ptp/ptp_clock.c
drivers/s390/net/qeth_core_main.c
drivers/s390/net/qeth_l2_main.c
drivers/s390/net/qeth_l3_main.c
drivers/s390/net/qeth_l3_sys.c
net/ipv6/route.c
net/sctp/stream.c
net/sctp/transport.c
tools/lib/bpf/Makefile
tools/testing/selftests/bpf/.gitignore
tools/testing/selftests/bpf/Makefile

diff --combined MAINTAINERS
@@@ -771,6 -771,8 +771,8 @@@ F: drivers/thermal/thermal_mmio.
  
  AMAZON ETHERNET DRIVERS
  M:    Netanel Belgazal <netanel@amazon.com>
+ M:    Arthur Kiyanovski <akiyano@amazon.com>
+ R:    Guy Tzalik <gtzalik@amazon.com>
  R:    Saeed Bishara <saeedb@amazon.com>
  R:    Zorik Machulsky <zorik@amazon.com>
  L:    netdev@vger.kernel.org
@@@ -7034,6 -7036,7 +7036,7 @@@ L:      linux-acpi@vger.kernel.or
  S:    Maintained
  F:    Documentation/firmware-guide/acpi/gpio-properties.rst
  F:    drivers/gpio/gpiolib-acpi.c
+ F:    drivers/gpio/gpiolib-acpi.h
  
  GPIO IR Transmitter
  M:    Sean Young <sean@mess.org>
@@@ -9957,7 -9960,7 +9960,7 @@@ F:      drivers/net/ethernet/marvell/mvneta.
  MARVELL MWIFIEX WIRELESS DRIVER
  M:    Amitkumar Karwar <amitkarwar@gmail.com>
  M:    Nishant Sarmukadam <nishants@marvell.com>
 -M:    Ganapathi Bhat <gbhat@marvell.com>
 +M:    Ganapathi Bhat <ganapathi.bhat@nxp.com>
  M:    Xinming Hu <huxinming820@gmail.com>
  L:    linux-wireless@vger.kernel.org
  S:    Maintained
@@@ -13646,13 -13649,6 +13649,13 @@@ T: git git://git.kernel.org/pub/scm/lin
  S:    Supported
  F:    drivers/net/wireless/ath/ath10k/
  
 +QUALCOMM ATHEROS ATH11K WIRELESS DRIVER
 +M:    Kalle Valo <kvalo@codeaurora.org>
 +L:    ath11k@lists.infradead.org
 +T:    git git://git.kernel.org/pub/scm/linux/kernel/git/kvalo/ath.git
 +S:    Supported
 +F:    drivers/net/wireless/ath/ath11k/
 +
  QUALCOMM ATHEROS ATH9K WIRELESS DRIVER
  M:    QCA ath9k Development <ath9k-devel@qca.qualcomm.com>
  L:    linux-wireless@vger.kernel.org
@@@ -17503,7 -17499,6 +17506,7 @@@ F:   net/vmw_vsock/diag.
  F:    net/vmw_vsock/af_vsock_tap.c
  F:    net/vmw_vsock/virtio_transport_common.c
  F:    net/vmw_vsock/virtio_transport.c
 +F:    net/vmw_vsock/vsock_loopback.c
  F:    drivers/net/vsockmon.c
  F:    drivers/vhost/vsock.c
  F:    tools/testing/vsock/
@@@ -17874,14 -17869,6 +17877,14 @@@ L: linux-gpio@vger.kernel.or
  S:    Maintained
  F:    drivers/gpio/gpio-ws16c48.c
  
 +WIREGUARD SECURE NETWORK TUNNEL
 +M:    Jason A. Donenfeld <Jason@zx2c4.com>
 +S:    Maintained
 +F:    drivers/net/wireguard/
 +F:    tools/testing/selftests/wireguard/
 +L:    wireguard@lists.zx2c4.com
 +L:    netdev@vger.kernel.org
 +
  WISTRON LAPTOP BUTTON DRIVER
  M:    Miloslav Trmac <mitr@volny.cz>
  S:    Maintained
@@@ -83,7 -83,6 +83,7 @@@ static int sja1105_init_avb_params(stru
  static int sja1105_change_rxtstamping(struct sja1105_private *priv,
                                      bool on)
  {
 +      struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
        struct sja1105_general_params_entry *general_params;
        struct sja1105_table *table;
        int rc;
                kfree_skb(priv->tagger_data.stampable_skb);
                priv->tagger_data.stampable_skb = NULL;
        }
 +      ptp_cancel_worker_sync(ptp_data->clock);
 +      skb_queue_purge(&ptp_data->skb_rxtstamp_queue);
  
        return sja1105_static_config_reload(priv, SJA1105_RX_HWTSTAMPING);
  }
@@@ -237,7 -234,7 +237,7 @@@ int sja1105_ptp_commit(struct dsa_switc
        if (rw == SPI_WRITE)
                priv->info->ptp_cmd_packing(buf, cmd, PACK);
  
-       rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->ptp_control, buf,
+       rc = sja1105_xfer_buf(priv, rw, regs->ptp_control, buf,
                              SJA1105_SIZE_PTP_CMD);
  
        if (rw == SPI_READ)
@@@ -370,16 -367,22 +370,16 @@@ static int sja1105_ptpclkval_write(stru
                                ptp_sts);
  }
  
 -#define rxtstamp_to_tagger(d) \
 -      container_of((d), struct sja1105_tagger_data, rxtstamp_work)
 -#define tagger_to_sja1105(d) \
 -      container_of((d), struct sja1105_private, tagger_data)
 -
 -static void sja1105_rxtstamp_work(struct work_struct *work)
 +static long sja1105_rxtstamp_work(struct ptp_clock_info *ptp)
  {
 -      struct sja1105_tagger_data *tagger_data = rxtstamp_to_tagger(work);
 -      struct sja1105_private *priv = tagger_to_sja1105(tagger_data);
 -      struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
 +      struct sja1105_ptp_data *ptp_data = ptp_caps_to_data(ptp);
 +      struct sja1105_private *priv = ptp_data_to_sja1105(ptp_data);
        struct dsa_switch *ds = priv->ds;
        struct sk_buff *skb;
  
        mutex_lock(&ptp_data->lock);
  
 -      while ((skb = skb_dequeue(&tagger_data->skb_rxtstamp_queue)) != NULL) {
 +      while ((skb = skb_dequeue(&ptp_data->skb_rxtstamp_queue)) != NULL) {
                struct skb_shared_hwtstamps *shwt = skb_hwtstamps(skb);
                u64 ticks, ts;
                int rc;
        }
  
        mutex_unlock(&ptp_data->lock);
 +
 +      /* Don't restart */
 +      return -1;
  }
  
  /* Called from dsa_skb_defer_rx_timestamp */
@@@ -411,16 -411,16 +411,16 @@@ bool sja1105_port_rxtstamp(struct dsa_s
                           struct sk_buff *skb, unsigned int type)
  {
        struct sja1105_private *priv = ds->priv;
 -      struct sja1105_tagger_data *tagger_data = &priv->tagger_data;
 +      struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
  
 -      if (!test_bit(SJA1105_HWTS_RX_EN, &tagger_data->state))
 +      if (!test_bit(SJA1105_HWTS_RX_EN, &priv->tagger_data.state))
                return false;
  
        /* We need to read the full PTP clock to reconstruct the Rx
         * timestamp. For that we need a sleepable context.
         */
 -      skb_queue_tail(&tagger_data->skb_rxtstamp_queue, skb);
 -      schedule_work(&tagger_data->rxtstamp_work);
 +      skb_queue_tail(&ptp_data->skb_rxtstamp_queue, skb);
 +      ptp_schedule_worker(ptp_data->clock, 0);
        return true;
  }
  
@@@ -628,11 -628,11 +628,11 @@@ int sja1105_ptp_clock_register(struct d
                .adjtime        = sja1105_ptp_adjtime,
                .gettimex64     = sja1105_ptp_gettimex,
                .settime64      = sja1105_ptp_settime,
 +              .do_aux_work    = sja1105_rxtstamp_work,
                .max_adj        = SJA1105_MAX_ADJ_PPB,
        };
  
 -      skb_queue_head_init(&tagger_data->skb_rxtstamp_queue);
 -      INIT_WORK(&tagger_data->rxtstamp_work, sja1105_rxtstamp_work);
 +      skb_queue_head_init(&ptp_data->skb_rxtstamp_queue);
        spin_lock_init(&tagger_data->meta_lock);
  
        ptp_data->clock = ptp_clock_register(&ptp_data->caps, ds->dev);
@@@ -653,13 -653,13 +653,13 @@@ void sja1105_ptp_clock_unregister(struc
        if (IS_ERR_OR_NULL(ptp_data->clock))
                return;
  
 -      cancel_work_sync(&priv->tagger_data.rxtstamp_work);
 -      skb_queue_purge(&priv->tagger_data.skb_rxtstamp_queue);
 +      ptp_cancel_worker_sync(ptp_data->clock);
 +      skb_queue_purge(&ptp_data->skb_rxtstamp_queue);
        ptp_clock_unregister(ptp_data->clock);
        ptp_data->clock = NULL;
  }
  
- void sja1105_ptp_txtstamp_skb(struct dsa_switch *ds, int slot,
+ void sja1105_ptp_txtstamp_skb(struct dsa_switch *ds, int port,
                              struct sk_buff *skb)
  {
        struct sja1105_private *priv = ds->priv;
                goto out;
        }
  
-       rc = sja1105_ptpegr_ts_poll(ds, slot, &ts);
+       rc = sja1105_ptpegr_ts_poll(ds, port, &ts);
        if (rc < 0) {
                dev_err(ds->dev, "timed out polling for tstamp\n");
                kfree_skb(skb);
@@@ -56,7 -56,6 +56,7 @@@
  #include <asm/io.h>
  #include "t4_chip_type.h"
  #include "cxgb4_uld.h"
 +#include "t4fw_api.h"
  
  #define CH_WARN(adap, fmt, ...) dev_warn(adap->pdev_dev, fmt, ## __VA_ARGS__)
  extern struct list_head adapter_list;
@@@ -69,16 -68,6 +69,16 @@@ extern struct mutex uld_mutex
  #define ETHTXQ_STOP_THRES \
        (1 + DIV_ROUND_UP((3 * MAX_SKB_FRAGS) / 2 + (MAX_SKB_FRAGS & 1), 8))
  
 +#define FW_PARAM_DEV(param) \
 +      (FW_PARAMS_MNEM_V(FW_PARAMS_MNEM_DEV) | \
 +       FW_PARAMS_PARAM_X_V(FW_PARAMS_PARAM_DEV_##param))
 +
 +#define FW_PARAM_PFVF(param) \
 +      (FW_PARAMS_MNEM_V(FW_PARAMS_MNEM_PFVF) | \
 +       FW_PARAMS_PARAM_X_V(FW_PARAMS_PARAM_PFVF_##param) |  \
 +       FW_PARAMS_PARAM_Y_V(0) | \
 +       FW_PARAMS_PARAM_Z_V(0))
 +
  enum {
        MAX_NPORTS      = 4,     /* max # of ports */
        SERNUM_LEN      = 24,    /* Serial # length */
@@@ -515,6 -504,7 +515,7 @@@ struct link_config 
  
        enum cc_pause  requested_fc;     /* flow control user has requested */
        enum cc_pause  fc;               /* actual link flow control */
+       enum cc_pause  advertised_fc;    /* actual advertised flow control */
  
        enum cc_fec    requested_fec;    /* Forward Error Correction: */
        enum cc_fec    fec;              /* requested and actual in use */
@@@ -288,7 -288,7 +288,7 @@@ static int dpaa_stop(struct net_device 
        return err;
  }
  
 -static void dpaa_tx_timeout(struct net_device *net_dev)
 +static void dpaa_tx_timeout(struct net_device *net_dev, unsigned int txqueue)
  {
        struct dpaa_percpu_priv *percpu_priv;
        const struct dpaa_priv  *priv;
@@@ -1719,7 -1719,7 +1719,7 @@@ static struct sk_buff *sg_fd_to_skb(con
        int page_offset;
        unsigned int sz;
        int *count_ptr;
-       int i;
+       int i, j;
  
        vaddr = phys_to_virt(addr);
        WARN_ON(!IS_ALIGNED((unsigned long)vaddr, SMP_CACHE_BYTES));
                WARN_ON(!IS_ALIGNED((unsigned long)sg_vaddr,
                                    SMP_CACHE_BYTES));
  
+               dma_unmap_page(priv->rx_dma_dev, sg_addr,
+                              DPAA_BP_RAW_SIZE, DMA_FROM_DEVICE);
                /* We may use multiple Rx pools */
                dpaa_bp = dpaa_bpid2pool(sgt[i].bpid);
                if (!dpaa_bp)
                        goto free_buffers;
  
-               count_ptr = this_cpu_ptr(dpaa_bp->percpu_count);
-               dma_unmap_page(priv->rx_dma_dev, sg_addr,
-                              DPAA_BP_RAW_SIZE, DMA_FROM_DEVICE);
                if (!skb) {
                        sz = dpaa_bp->size +
                                SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
                        skb_add_rx_frag(skb, i - 1, head_page, frag_off,
                                        frag_len, dpaa_bp->size);
                }
                /* Update the pool count for the current {cpu x bpool} */
+               count_ptr = this_cpu_ptr(dpaa_bp->percpu_count);
                (*count_ptr)--;
  
                if (qm_sg_entry_is_final(&sgt[i]))
        return skb;
  
  free_buffers:
-       /* compensate sw bpool counter changes */
-       for (i--; i >= 0; i--) {
-               dpaa_bp = dpaa_bpid2pool(sgt[i].bpid);
-               if (dpaa_bp) {
-                       count_ptr = this_cpu_ptr(dpaa_bp->percpu_count);
-                       (*count_ptr)++;
-               }
-       }
        /* free all the SG entries */
-       for (i = 0; i < DPAA_SGT_MAX_ENTRIES ; i++) {
-               sg_addr = qm_sg_addr(&sgt[i]);
+       for (j = 0; j < DPAA_SGT_MAX_ENTRIES ; j++) {
+               sg_addr = qm_sg_addr(&sgt[j]);
                sg_vaddr = phys_to_virt(sg_addr);
+               /* all pages 0..i were unmaped */
+               if (j > i)
+                       dma_unmap_page(priv->rx_dma_dev, qm_sg_addr(&sgt[j]),
+                                      DPAA_BP_RAW_SIZE, DMA_FROM_DEVICE);
                free_pages((unsigned long)sg_vaddr, 0);
-               dpaa_bp = dpaa_bpid2pool(sgt[i].bpid);
-               if (dpaa_bp) {
-                       count_ptr = this_cpu_ptr(dpaa_bp->percpu_count);
-                       (*count_ptr)--;
+               /* counters 0..i-1 were decremented */
+               if (j >= i) {
+                       dpaa_bp = dpaa_bpid2pool(sgt[j].bpid);
+                       if (dpaa_bp) {
+                               count_ptr = this_cpu_ptr(dpaa_bp->percpu_count);
+                               (*count_ptr)--;
+                       }
                }
  
-               if (qm_sg_entry_is_final(&sgt[i]))
+               if (qm_sg_entry_is_final(&sgt[j]))
                        break;
        }
        /* free the SGT fragment */
@@@ -3477,10 -3477,10 +3477,10 @@@ MLXSW_REG_DEFINE(qeec, MLXSW_REG_QEEC_I
  MLXSW_ITEM32(reg, qeec, local_port, 0x00, 16, 8);
  
  enum mlxsw_reg_qeec_hr {
 -      MLXSW_REG_QEEC_HIERARCY_PORT,
 -      MLXSW_REG_QEEC_HIERARCY_GROUP,
 -      MLXSW_REG_QEEC_HIERARCY_SUBGROUP,
 -      MLXSW_REG_QEEC_HIERARCY_TC,
 +      MLXSW_REG_QEEC_HR_PORT,
 +      MLXSW_REG_QEEC_HR_GROUP,
 +      MLXSW_REG_QEEC_HR_SUBGROUP,
 +      MLXSW_REG_QEEC_HR_TC,
  };
  
  /* reg_qeec_element_hierarchy
@@@ -3618,7 -3618,8 +3618,7 @@@ static inline void mlxsw_reg_qeec_ptps_
  {
        MLXSW_REG_ZERO(qeec, payload);
        mlxsw_reg_qeec_local_port_set(payload, local_port);
 -      mlxsw_reg_qeec_element_hierarchy_set(payload,
 -                                           MLXSW_REG_QEEC_HIERARCY_PORT);
 +      mlxsw_reg_qeec_element_hierarchy_set(payload, MLXSW_REG_QEEC_HR_PORT);
        mlxsw_reg_qeec_ptps_set(payload, ptps);
  }
  
@@@ -5471,6 -5472,7 +5471,7 @@@ enum mlxsw_reg_htgt_trap_group 
        MLXSW_REG_HTGT_TRAP_GROUP_SP_LBERROR,
        MLXSW_REG_HTGT_TRAP_GROUP_SP_PTP0,
        MLXSW_REG_HTGT_TRAP_GROUP_SP_PTP1,
+       MLXSW_REG_HTGT_TRAP_GROUP_SP_VRRP,
  
        __MLXSW_REG_HTGT_TRAP_GROUP_MAX,
        MLXSW_REG_HTGT_TRAP_GROUP_MAX = __MLXSW_REG_HTGT_TRAP_GROUP_MAX - 1
@@@ -1796,8 -1796,6 +1796,8 @@@ static int mlxsw_sp_setup_tc(struct net
                return mlxsw_sp_setup_tc_red(mlxsw_sp_port, type_data);
        case TC_SETUP_QDISC_PRIO:
                return mlxsw_sp_setup_tc_prio(mlxsw_sp_port, type_data);
 +      case TC_SETUP_QDISC_ETS:
 +              return mlxsw_sp_setup_tc_ets(mlxsw_sp_port, type_data);
        default:
                return -EOPNOTSUPP;
        }
@@@ -3604,25 -3602,26 +3604,25 @@@ static int mlxsw_sp_port_ets_init(struc
         * one subgroup, which are all member in the same group.
         */
        err = mlxsw_sp_port_ets_set(mlxsw_sp_port,
 -                                  MLXSW_REG_QEEC_HIERARCY_GROUP, 0, 0, false,
 -                                  0);
 +                                  MLXSW_REG_QEEC_HR_GROUP, 0, 0, false, 0);
        if (err)
                return err;
        for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
                err = mlxsw_sp_port_ets_set(mlxsw_sp_port,
 -                                          MLXSW_REG_QEEC_HIERARCY_SUBGROUP, i,
 +                                          MLXSW_REG_QEEC_HR_SUBGROUP, i,
                                            0, false, 0);
                if (err)
                        return err;
        }
        for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
                err = mlxsw_sp_port_ets_set(mlxsw_sp_port,
 -                                          MLXSW_REG_QEEC_HIERARCY_TC, i, i,
 +                                          MLXSW_REG_QEEC_HR_TC, i, i,
                                            false, 0);
                if (err)
                        return err;
  
                err = mlxsw_sp_port_ets_set(mlxsw_sp_port,
 -                                          MLXSW_REG_QEEC_HIERARCY_TC,
 +                                          MLXSW_REG_QEEC_HR_TC,
                                            i + 8, i,
                                            true, 100);
                if (err)
         * for the initial configuration.
         */
        err = mlxsw_sp_port_ets_maxrate_set(mlxsw_sp_port,
 -                                          MLXSW_REG_QEEC_HIERARCY_PORT, 0, 0,
 +                                          MLXSW_REG_QEEC_HR_PORT, 0, 0,
                                            MLXSW_REG_QEEC_MAS_DIS);
        if (err)
                return err;
        for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
                err = mlxsw_sp_port_ets_maxrate_set(mlxsw_sp_port,
 -                                                  MLXSW_REG_QEEC_HIERARCY_SUBGROUP,
 +                                                  MLXSW_REG_QEEC_HR_SUBGROUP,
                                                    i, 0,
                                                    MLXSW_REG_QEEC_MAS_DIS);
                if (err)
        }
        for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
                err = mlxsw_sp_port_ets_maxrate_set(mlxsw_sp_port,
 -                                                  MLXSW_REG_QEEC_HIERARCY_TC,
 +                                                  MLXSW_REG_QEEC_HR_TC,
                                                    i, i,
                                                    MLXSW_REG_QEEC_MAS_DIS);
                if (err)
                        return err;
  
                err = mlxsw_sp_port_ets_maxrate_set(mlxsw_sp_port,
 -                                                  MLXSW_REG_QEEC_HIERARCY_TC,
 +                                                  MLXSW_REG_QEEC_HR_TC,
                                                    i + 8, i,
                                                    MLXSW_REG_QEEC_MAS_DIS);
                if (err)
        /* Configure the min shaper for multicast TCs. */
        for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
                err = mlxsw_sp_port_min_bw_set(mlxsw_sp_port,
 -                                             MLXSW_REG_QEEC_HIERARCY_TC,
 +                                             MLXSW_REG_QEEC_HR_TC,
                                               i + 8, i,
                                               MLXSW_REG_QEEC_MIS_MIN);
                if (err)
@@@ -4543,8 -4542,8 +4543,8 @@@ static const struct mlxsw_listener mlxs
        MLXSW_SP_RXL_MARK(ROUTER_ALERT_IPV6, TRAP_TO_CPU, ROUTER_EXP, false),
        MLXSW_SP_RXL_MARK(IPIP_DECAP_ERROR, TRAP_TO_CPU, ROUTER_EXP, false),
        MLXSW_SP_RXL_MARK(DECAP_ECN0, TRAP_TO_CPU, ROUTER_EXP, false),
-       MLXSW_SP_RXL_MARK(IPV4_VRRP, TRAP_TO_CPU, ROUTER_EXP, false),
-       MLXSW_SP_RXL_MARK(IPV6_VRRP, TRAP_TO_CPU, ROUTER_EXP, false),
+       MLXSW_SP_RXL_MARK(IPV4_VRRP, TRAP_TO_CPU, VRRP, false),
+       MLXSW_SP_RXL_MARK(IPV6_VRRP, TRAP_TO_CPU, VRRP, false),
        /* PKT Sample trap */
        MLXSW_RXL(mlxsw_sp_rx_listener_sample_func, PKT_SAMPLE, MIRROR_TO_CPU,
                  false, SP_IP2ME, DISCARD),
@@@ -4627,6 -4626,10 +4627,10 @@@ static int mlxsw_sp_cpu_policers_set(st
                        rate = 19 * 1024;
                        burst_size = 12;
                        break;
+               case MLXSW_REG_HTGT_TRAP_GROUP_SP_VRRP:
+                       rate = 360;
+                       burst_size = 7;
+                       break;
                default:
                        continue;
                }
@@@ -4666,6 -4669,7 +4670,7 @@@ static int mlxsw_sp_trap_groups_set(str
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_OSPF:
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_PIM:
                case MLXSW_REG_HTGT_TRAP_GROUP_SP_PTP0:
+               case MLXSW_REG_HTGT_TRAP_GROUP_SP_VRRP:
                        priority = 5;
                        tc = 5;
                        break;
@@@ -382,10 -382,9 +382,10 @@@ enum mlxsw_sp_fib_entry_type 
  };
  
  struct mlxsw_sp_nexthop_group;
 +struct mlxsw_sp_fib_entry;
  
  struct mlxsw_sp_fib_node {
 -      struct list_head entry_list;
 +      struct mlxsw_sp_fib_entry *fib_entry;
        struct list_head list;
        struct rhash_head ht_node;
        struct mlxsw_sp_fib *fib;
@@@ -398,6 -397,7 +398,6 @@@ struct mlxsw_sp_fib_entry_decap 
  };
  
  struct mlxsw_sp_fib_entry {
 -      struct list_head list;
        struct mlxsw_sp_fib_node *fib_node;
        enum mlxsw_sp_fib_entry_type type;
        struct list_head nexthop_group_node;
@@@ -1162,6 -1162,7 +1162,6 @@@ mlxsw_sp_router_ip2me_fib_entry_find(st
                                     const union mlxsw_sp_l3addr *addr,
                                     enum mlxsw_sp_fib_entry_type type)
  {
 -      struct mlxsw_sp_fib_entry *fib_entry;
        struct mlxsw_sp_fib_node *fib_node;
        unsigned char addr_prefix_len;
        struct mlxsw_sp_fib *fib;
  
        fib_node = mlxsw_sp_fib_node_lookup(fib, addrp, addr_len,
                                            addr_prefix_len);
 -      if (!fib_node || list_empty(&fib_node->entry_list))
 -              return NULL;
 -
 -      fib_entry = list_first_entry(&fib_node->entry_list,
 -                                   struct mlxsw_sp_fib_entry, list);
 -      if (fib_entry->type != type)
 +      if (!fib_node || fib_node->fib_entry->type != type)
                return NULL;
  
 -      return fib_entry;
 +      return fib_node->fib_entry;
  }
  
  /* Given an IPIP entry, find the corresponding decap route. */
@@@ -1203,6 -1209,7 +1203,6 @@@ mlxsw_sp_ipip_entry_find_decap(struct m
  {
        static struct mlxsw_sp_fib_node *fib_node;
        const struct mlxsw_sp_ipip_ops *ipip_ops;
 -      struct mlxsw_sp_fib_entry *fib_entry;
        unsigned char saddr_prefix_len;
        union mlxsw_sp_l3addr saddr;
        struct mlxsw_sp_fib *ul_fib;
  
        fib_node = mlxsw_sp_fib_node_lookup(ul_fib, saddrp, saddr_len,
                                            saddr_prefix_len);
 -      if (!fib_node || list_empty(&fib_node->entry_list))
 -              return NULL;
 -
 -      fib_entry = list_first_entry(&fib_node->entry_list,
 -                                   struct mlxsw_sp_fib_entry, list);
 -      if (fib_entry->type != MLXSW_SP_FIB_ENTRY_TYPE_TRAP)
 +      if (!fib_node ||
 +          fib_node->fib_entry->type != MLXSW_SP_FIB_ENTRY_TYPE_TRAP)
                return NULL;
  
 -      return fib_entry;
 +      return fib_node->fib_entry;
  }
  
  static struct mlxsw_sp_ipip_entry *
@@@ -3220,6 -3231,10 +3220,6 @@@ mlxsw_sp_nexthop_group_update(struct ml
        return 0;
  }
  
 -static bool
 -mlxsw_sp_fib_node_entry_is_first(const struct mlxsw_sp_fib_node *fib_node,
 -                               const struct mlxsw_sp_fib_entry *fib_entry);
 -
  static int
  mlxsw_sp_nexthop_fib_entries_update(struct mlxsw_sp *mlxsw_sp,
                                    struct mlxsw_sp_nexthop_group *nh_grp)
        int err;
  
        list_for_each_entry(fib_entry, &nh_grp->fib_list, nexthop_group_node) {
 -              if (!mlxsw_sp_fib_node_entry_is_first(fib_entry->fib_node,
 -                                                    fib_entry))
 -                      continue;
                err = mlxsw_sp_fib_entry_update(mlxsw_sp, fib_entry);
                if (err)
                        return err;
@@@ -3245,8 -3263,12 +3245,8 @@@ mlxsw_sp_nexthop_fib_entries_refresh(st
        enum mlxsw_reg_ralue_op op = MLXSW_REG_RALUE_OP_WRITE_WRITE;
        struct mlxsw_sp_fib_entry *fib_entry;
  
 -      list_for_each_entry(fib_entry, &nh_grp->fib_list, nexthop_group_node) {
 -              if (!mlxsw_sp_fib_node_entry_is_first(fib_entry->fib_node,
 -                                                    fib_entry))
 -                      continue;
 +      list_for_each_entry(fib_entry, &nh_grp->fib_list, nexthop_group_node)
                mlxsw_sp_fib_entry_offload_refresh(fib_entry, op, 0);
 -      }
  }
  
  static void mlxsw_sp_adj_grp_size_round_up(u16 *p_adj_grp_size)
@@@ -3823,7 -3845,7 +3823,7 @@@ static void mlxsw_sp_nexthop4_event(str
  
        key.fib_nh = fib_nh;
        nh = mlxsw_sp_nexthop_lookup(mlxsw_sp, key);
 -      if (WARN_ON_ONCE(!nh))
 +      if (!nh)
                return;
  
        switch (event) {
@@@ -4469,19 -4491,6 +4469,19 @@@ mlxsw_sp_fib4_entry_type_set(struct mlx
        }
  }
  
 +static void
 +mlxsw_sp_fib4_entry_type_unset(struct mlxsw_sp *mlxsw_sp,
 +                             struct mlxsw_sp_fib_entry *fib_entry)
 +{
 +      switch (fib_entry->type) {
 +      case MLXSW_SP_FIB_ENTRY_TYPE_IPIP_DECAP:
 +              mlxsw_sp_fib_entry_decap_fini(mlxsw_sp, fib_entry);
 +              break;
 +      default:
 +              break;
 +      }
 +}
 +
  static struct mlxsw_sp_fib4_entry *
  mlxsw_sp_fib4_entry_create(struct mlxsw_sp *mlxsw_sp,
                           struct mlxsw_sp_fib_node *fib_node,
        return fib4_entry;
  
  err_nexthop4_group_get:
 +      mlxsw_sp_fib4_entry_type_unset(mlxsw_sp, fib_entry);
  err_fib4_entry_type_set:
        kfree(fib4_entry);
        return ERR_PTR(err);
@@@ -4524,7 -4532,6 +4524,7 @@@ static void mlxsw_sp_fib4_entry_destroy
                                        struct mlxsw_sp_fib4_entry *fib4_entry)
  {
        mlxsw_sp_nexthop4_group_put(mlxsw_sp, &fib4_entry->common);
 +      mlxsw_sp_fib4_entry_type_unset(mlxsw_sp, &fib4_entry->common);
        kfree(fib4_entry);
  }
  
@@@ -4548,14 -4555,15 +4548,14 @@@ mlxsw_sp_fib4_entry_lookup(struct mlxsw
        if (!fib_node)
                return NULL;
  
 -      list_for_each_entry(fib4_entry, &fib_node->entry_list, common.list) {
 -              if (fib4_entry->tb_id == fen_info->tb_id &&
 -                  fib4_entry->tos == fen_info->tos &&
 -                  fib4_entry->type == fen_info->type &&
 -                  mlxsw_sp_nexthop4_group_fi(fib4_entry->common.nh_group) ==
 -                  fen_info->fi) {
 -                      return fib4_entry;
 -              }
 -      }
 +      fib4_entry = container_of(fib_node->fib_entry,
 +                                struct mlxsw_sp_fib4_entry, common);
 +      if (fib4_entry->tb_id == fen_info->tb_id &&
 +          fib4_entry->tos == fen_info->tos &&
 +          fib4_entry->type == fen_info->type &&
 +          mlxsw_sp_nexthop4_group_fi(fib4_entry->common.nh_group) ==
 +          fen_info->fi)
 +              return fib4_entry;
  
        return NULL;
  }
@@@ -4603,6 -4611,7 +4603,6 @@@ mlxsw_sp_fib_node_create(struct mlxsw_s
        if (!fib_node)
                return NULL;
  
 -      INIT_LIST_HEAD(&fib_node->entry_list);
        list_add(&fib_node->list, &fib->node_list);
        memcpy(fib_node->key.addr, addr, addr_len);
        fib_node->key.prefix_len = prefix_len;
  static void mlxsw_sp_fib_node_destroy(struct mlxsw_sp_fib_node *fib_node)
  {
        list_del(&fib_node->list);
 -      WARN_ON(!list_empty(&fib_node->entry_list));
        kfree(fib_node);
  }
  
 -static bool
 -mlxsw_sp_fib_node_entry_is_first(const struct mlxsw_sp_fib_node *fib_node,
 -                               const struct mlxsw_sp_fib_entry *fib_entry)
 -{
 -      return list_first_entry(&fib_node->entry_list,
 -                              struct mlxsw_sp_fib_entry, list) == fib_entry;
 -}
 -
  static int mlxsw_sp_fib_lpm_tree_link(struct mlxsw_sp *mlxsw_sp,
                                      struct mlxsw_sp_fib_node *fib_node)
  {
@@@ -4755,48 -4773,200 +4755,48 @@@ static void mlxsw_sp_fib_node_put(struc
  {
        struct mlxsw_sp_vr *vr = fib_node->fib->vr;
  
 -      if (!list_empty(&fib_node->entry_list))
 +      if (fib_node->fib_entry)
                return;
        mlxsw_sp_fib_node_fini(mlxsw_sp, fib_node);
        mlxsw_sp_fib_node_destroy(fib_node);
        mlxsw_sp_vr_put(mlxsw_sp, vr);
  }
  
 -static struct mlxsw_sp_fib4_entry *
 -mlxsw_sp_fib4_node_entry_find(const struct mlxsw_sp_fib_node *fib_node,
 -                            const struct mlxsw_sp_fib4_entry *new4_entry)
 -{
 -      struct mlxsw_sp_fib4_entry *fib4_entry;
 -
 -      list_for_each_entry(fib4_entry, &fib_node->entry_list, common.list) {
 -              if (fib4_entry->tb_id > new4_entry->tb_id)
 -                      continue;
 -              if (fib4_entry->tb_id != new4_entry->tb_id)
 -                      break;
 -              if (fib4_entry->tos > new4_entry->tos)
 -                      continue;
 -              if (fib4_entry->prio >= new4_entry->prio ||
 -                  fib4_entry->tos < new4_entry->tos)
 -                      return fib4_entry;
 -      }
 -
 -      return NULL;
 -}
 -
 -static int
 -mlxsw_sp_fib4_node_list_append(struct mlxsw_sp_fib4_entry *fib4_entry,
 -                             struct mlxsw_sp_fib4_entry *new4_entry)
 -{
 -      struct mlxsw_sp_fib_node *fib_node;
 -
 -      if (WARN_ON(!fib4_entry))
 -              return -EINVAL;
 -
 -      fib_node = fib4_entry->common.fib_node;
 -      list_for_each_entry_from(fib4_entry, &fib_node->entry_list,
 -                               common.list) {
 -              if (fib4_entry->tb_id != new4_entry->tb_id ||
 -                  fib4_entry->tos != new4_entry->tos ||
 -                  fib4_entry->prio != new4_entry->prio)
 -                      break;
 -      }
 -
 -      list_add_tail(&new4_entry->common.list, &fib4_entry->common.list);
 -      return 0;
 -}
 -
 -static int
 -mlxsw_sp_fib4_node_list_insert(struct mlxsw_sp_fib4_entry *new4_entry,
 -                             bool replace, bool append)
 -{
 -      struct mlxsw_sp_fib_node *fib_node = new4_entry->common.fib_node;
 -      struct mlxsw_sp_fib4_entry *fib4_entry;
 -
 -      fib4_entry = mlxsw_sp_fib4_node_entry_find(fib_node, new4_entry);
 -
 -      if (append)
 -              return mlxsw_sp_fib4_node_list_append(fib4_entry, new4_entry);
 -      if (replace && WARN_ON(!fib4_entry))
 -              return -EINVAL;
 -
 -      /* Insert new entry before replaced one, so that we can later
 -       * remove the second.
 -       */
 -      if (fib4_entry) {
 -              list_add_tail(&new4_entry->common.list,
 -                            &fib4_entry->common.list);
 -      } else {
 -              struct mlxsw_sp_fib4_entry *last;
 -
 -              list_for_each_entry(last, &fib_node->entry_list, common.list) {
 -                      if (new4_entry->tb_id > last->tb_id)
 -                              break;
 -                      fib4_entry = last;
 -              }
 -
 -              if (fib4_entry)
 -                      list_add(&new4_entry->common.list,
 -                               &fib4_entry->common.list);
 -              else
 -                      list_add(&new4_entry->common.list,
 -                               &fib_node->entry_list);
 -      }
 -
 -      return 0;
 -}
 -
 -static void
 -mlxsw_sp_fib4_node_list_remove(struct mlxsw_sp_fib4_entry *fib4_entry)
 -{
 -      list_del(&fib4_entry->common.list);
 -}
 -
 -static int mlxsw_sp_fib_node_entry_add(struct mlxsw_sp *mlxsw_sp,
 -                                     struct mlxsw_sp_fib_entry *fib_entry)
 -{
 -      struct mlxsw_sp_fib_node *fib_node = fib_entry->fib_node;
 -
 -      if (!mlxsw_sp_fib_node_entry_is_first(fib_node, fib_entry))
 -              return 0;
 -
 -      /* To prevent packet loss, overwrite the previously offloaded
 -       * entry.
 -       */
 -      if (!list_is_singular(&fib_node->entry_list)) {
 -              enum mlxsw_reg_ralue_op op = MLXSW_REG_RALUE_OP_WRITE_DELETE;
 -              struct mlxsw_sp_fib_entry *n = list_next_entry(fib_entry, list);
 -
 -              mlxsw_sp_fib_entry_offload_refresh(n, op, 0);
 -      }
 -
 -      return mlxsw_sp_fib_entry_update(mlxsw_sp, fib_entry);
 -}
 -
 -static void mlxsw_sp_fib_node_entry_del(struct mlxsw_sp *mlxsw_sp,
 +static int mlxsw_sp_fib_node_entry_link(struct mlxsw_sp *mlxsw_sp,
                                        struct mlxsw_sp_fib_entry *fib_entry)
  {
        struct mlxsw_sp_fib_node *fib_node = fib_entry->fib_node;
 -
 -      if (!mlxsw_sp_fib_node_entry_is_first(fib_node, fib_entry))
 -              return;
 -
 -      /* Promote the next entry by overwriting the deleted entry */
 -      if (!list_is_singular(&fib_node->entry_list)) {
 -              struct mlxsw_sp_fib_entry *n = list_next_entry(fib_entry, list);
 -              enum mlxsw_reg_ralue_op op = MLXSW_REG_RALUE_OP_WRITE_DELETE;
 -
 -              mlxsw_sp_fib_entry_update(mlxsw_sp, n);
 -              mlxsw_sp_fib_entry_offload_refresh(fib_entry, op, 0);
 -              return;
 -      }
 -
 -      mlxsw_sp_fib_entry_del(mlxsw_sp, fib_entry);
 -}
 -
 -static int mlxsw_sp_fib4_node_entry_link(struct mlxsw_sp *mlxsw_sp,
 -                                       struct mlxsw_sp_fib4_entry *fib4_entry,
 -                                       bool replace, bool append)
 -{
        int err;
  
 -      err = mlxsw_sp_fib4_node_list_insert(fib4_entry, replace, append);
 -      if (err)
 -              return err;
 +      fib_node->fib_entry = fib_entry;
  
 -      err = mlxsw_sp_fib_node_entry_add(mlxsw_sp, &fib4_entry->common);
 +      err = mlxsw_sp_fib_entry_update(mlxsw_sp, fib_entry);
        if (err)
 -              goto err_fib_node_entry_add;
 +              goto err_fib_entry_update;
  
        return 0;
  
 -err_fib_node_entry_add:
 -      mlxsw_sp_fib4_node_list_remove(fib4_entry);
 +err_fib_entry_update:
 +      fib_node->fib_entry = NULL;
        return err;
  }
  
  static void
 -mlxsw_sp_fib4_node_entry_unlink(struct mlxsw_sp *mlxsw_sp,
 -                              struct mlxsw_sp_fib4_entry *fib4_entry)
 +mlxsw_sp_fib_node_entry_unlink(struct mlxsw_sp *mlxsw_sp,
 +                             struct mlxsw_sp_fib_entry *fib_entry)
  {
 -      mlxsw_sp_fib_node_entry_del(mlxsw_sp, &fib4_entry->common);
 -      mlxsw_sp_fib4_node_list_remove(fib4_entry);
 -
 -      if (fib4_entry->common.type == MLXSW_SP_FIB_ENTRY_TYPE_IPIP_DECAP)
 -              mlxsw_sp_fib_entry_decap_fini(mlxsw_sp, &fib4_entry->common);
 -}
 -
 -static void mlxsw_sp_fib4_entry_replace(struct mlxsw_sp *mlxsw_sp,
 -                                      struct mlxsw_sp_fib4_entry *fib4_entry,
 -                                      bool replace)
 -{
 -      struct mlxsw_sp_fib_node *fib_node = fib4_entry->common.fib_node;
 -      struct mlxsw_sp_fib4_entry *replaced;
 -
 -      if (!replace)
 -              return;
 -
 -      /* We inserted the new entry before replaced one */
 -      replaced = list_next_entry(fib4_entry, common.list);
 +      struct mlxsw_sp_fib_node *fib_node = fib_entry->fib_node;
  
 -      mlxsw_sp_fib4_node_entry_unlink(mlxsw_sp, replaced);
 -      mlxsw_sp_fib4_entry_destroy(mlxsw_sp, replaced);
 -      mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
 +      mlxsw_sp_fib_entry_del(mlxsw_sp, fib_entry);
 +      fib_node->fib_entry = NULL;
  }
  
  static int
 -mlxsw_sp_router_fib4_add(struct mlxsw_sp *mlxsw_sp,
 -                       const struct fib_entry_notifier_info *fen_info,
 -                       bool replace, bool append)
 +mlxsw_sp_router_fib4_replace(struct mlxsw_sp *mlxsw_sp,
 +                           const struct fib_entry_notifier_info *fen_info)
  {
 -      struct mlxsw_sp_fib4_entry *fib4_entry;
 +      struct mlxsw_sp_fib4_entry *fib4_entry, *fib4_replaced;
 +      struct mlxsw_sp_fib_entry *replaced;
        struct mlxsw_sp_fib_node *fib_node;
        int err;
  
                goto err_fib4_entry_create;
        }
  
 -      err = mlxsw_sp_fib4_node_entry_link(mlxsw_sp, fib4_entry, replace,
 -                                          append);
 +      replaced = fib_node->fib_entry;
 +      err = mlxsw_sp_fib_node_entry_link(mlxsw_sp, &fib4_entry->common);
        if (err) {
                dev_warn(mlxsw_sp->bus_info->dev, "Failed to link FIB entry to node\n");
 -              goto err_fib4_node_entry_link;
 +              goto err_fib_node_entry_link;
        }
  
 -      mlxsw_sp_fib4_entry_replace(mlxsw_sp, fib4_entry, replace);
 +      /* Nothing to replace */
 +      if (!replaced)
 +              return 0;
 +
 +      mlxsw_sp_fib_entry_offload_unset(replaced);
 +      fib4_replaced = container_of(replaced, struct mlxsw_sp_fib4_entry,
 +                                   common);
 +      mlxsw_sp_fib4_entry_destroy(mlxsw_sp, fib4_replaced);
  
        return 0;
  
 -err_fib4_node_entry_link:
 +err_fib_node_entry_link:
 +      fib_node->fib_entry = replaced;
        mlxsw_sp_fib4_entry_destroy(mlxsw_sp, fib4_entry);
  err_fib4_entry_create:
        mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
@@@ -4859,7 -5021,7 +4859,7 @@@ static void mlxsw_sp_router_fib4_del(st
                return;
        fib_node = fib4_entry->common.fib_node;
  
 -      mlxsw_sp_fib4_node_entry_unlink(mlxsw_sp, fib4_entry);
 +      mlxsw_sp_fib_node_entry_unlink(mlxsw_sp, &fib4_entry->common);
        mlxsw_sp_fib4_entry_destroy(mlxsw_sp, fib4_entry);
        mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
  }
@@@ -4921,6 -5083,13 +4921,6 @@@ static void mlxsw_sp_rt6_destroy(struc
        kfree(mlxsw_sp_rt6);
  }
  
 -static bool mlxsw_sp_fib6_rt_can_mp(const struct fib6_info *rt)
 -{
 -      /* RTF_CACHE routes are ignored */
 -      return !(rt->fib6_flags & RTF_ADDRCONF) &&
 -              rt->fib6_nh->fib_nh_gw_family;
 -}
 -
  static struct fib6_info *
  mlxsw_sp_fib6_entry_rt(const struct mlxsw_sp_fib6_entry *fib6_entry)
  {
                                list)->rt;
  }
  
 -static struct mlxsw_sp_fib6_entry *
 -mlxsw_sp_fib6_node_mp_entry_find(const struct mlxsw_sp_fib_node *fib_node,
 -                               const struct fib6_info *nrt, bool replace)
 -{
 -      struct mlxsw_sp_fib6_entry *fib6_entry;
 -
 -      if (!mlxsw_sp_fib6_rt_can_mp(nrt) || replace)
 -              return NULL;
 -
 -      list_for_each_entry(fib6_entry, &fib_node->entry_list, common.list) {
 -              struct fib6_info *rt = mlxsw_sp_fib6_entry_rt(fib6_entry);
 -
 -              /* RT6_TABLE_LOCAL and RT6_TABLE_MAIN share the same
 -               * virtual router.
 -               */
 -              if (rt->fib6_table->tb6_id > nrt->fib6_table->tb6_id)
 -                      continue;
 -              if (rt->fib6_table->tb6_id != nrt->fib6_table->tb6_id)
 -                      break;
 -              if (rt->fib6_metric < nrt->fib6_metric)
 -                      continue;
 -              if (rt->fib6_metric == nrt->fib6_metric &&
 -                  mlxsw_sp_fib6_rt_can_mp(rt))
 -                      return fib6_entry;
 -              if (rt->fib6_metric > nrt->fib6_metric)
 -                      break;
 -      }
 -
 -      return NULL;
 -}
 -
  static struct mlxsw_sp_rt6 *
  mlxsw_sp_fib6_entry_rt_find(const struct mlxsw_sp_fib6_entry *fib6_entry,
                            const struct fib6_info *rt)
@@@ -5145,16 -5345,16 +5145,16 @@@ mlxsw_sp_nexthop6_group_update(struct m
         * currently associated with it in the device's table is that
         * of the old group. Start using the new one instead.
         */
 -      err = mlxsw_sp_fib_node_entry_add(mlxsw_sp, &fib6_entry->common);
 +      err = mlxsw_sp_fib_entry_update(mlxsw_sp, &fib6_entry->common);
        if (err)
 -              goto err_fib_node_entry_add;
 +              goto err_fib_entry_update;
  
        if (list_empty(&old_nh_grp->fib_list))
                mlxsw_sp_nexthop6_group_destroy(mlxsw_sp, old_nh_grp);
  
        return 0;
  
 -err_fib_node_entry_add:
 +err_fib_entry_update:
        mlxsw_sp_nexthop6_group_put(mlxsw_sp, &fib6_entry->common);
  err_nexthop6_group_get:
        list_add_tail(&fib6_entry->common.nexthop_group_node,
@@@ -5318,6 -5518,106 +5318,6 @@@ static void mlxsw_sp_fib6_entry_destroy
        kfree(fib6_entry);
  }
  
 -static struct mlxsw_sp_fib6_entry *
 -mlxsw_sp_fib6_node_entry_find(const struct mlxsw_sp_fib_node *fib_node,
 -                            const struct fib6_info *nrt, bool replace)
 -{
 -      struct mlxsw_sp_fib6_entry *fib6_entry, *fallback = NULL;
 -
 -      list_for_each_entry(fib6_entry, &fib_node->entry_list, common.list) {
 -              struct fib6_info *rt = mlxsw_sp_fib6_entry_rt(fib6_entry);
 -
 -              if (rt->fib6_table->tb6_id > nrt->fib6_table->tb6_id)
 -                      continue;
 -              if (rt->fib6_table->tb6_id != nrt->fib6_table->tb6_id)
 -                      break;
 -              if (replace && rt->fib6_metric == nrt->fib6_metric) {
 -                      if (mlxsw_sp_fib6_rt_can_mp(rt) ==
 -                          mlxsw_sp_fib6_rt_can_mp(nrt))
 -                              return fib6_entry;
 -                      if (mlxsw_sp_fib6_rt_can_mp(nrt))
 -                              fallback = fallback ?: fib6_entry;
 -              }
 -              if (rt->fib6_metric > nrt->fib6_metric)
 -                      return fallback ?: fib6_entry;
 -      }
 -
 -      return fallback;
 -}
 -
 -static int
 -mlxsw_sp_fib6_node_list_insert(struct mlxsw_sp_fib6_entry *new6_entry,
 -                             bool *p_replace)
 -{
 -      struct mlxsw_sp_fib_node *fib_node = new6_entry->common.fib_node;
 -      struct fib6_info *nrt = mlxsw_sp_fib6_entry_rt(new6_entry);
 -      struct mlxsw_sp_fib6_entry *fib6_entry;
 -
 -      fib6_entry = mlxsw_sp_fib6_node_entry_find(fib_node, nrt, *p_replace);
 -
 -      if (*p_replace && !fib6_entry)
 -              *p_replace = false;
 -
 -      if (fib6_entry) {
 -              list_add_tail(&new6_entry->common.list,
 -                            &fib6_entry->common.list);
 -      } else {
 -              struct mlxsw_sp_fib6_entry *last;
 -
 -              list_for_each_entry(last, &fib_node->entry_list, common.list) {
 -                      struct fib6_info *rt = mlxsw_sp_fib6_entry_rt(last);
 -
 -                      if (nrt->fib6_table->tb6_id > rt->fib6_table->tb6_id)
 -                              break;
 -                      fib6_entry = last;
 -              }
 -
 -              if (fib6_entry)
 -                      list_add(&new6_entry->common.list,
 -                               &fib6_entry->common.list);
 -              else
 -                      list_add(&new6_entry->common.list,
 -                               &fib_node->entry_list);
 -      }
 -
 -      return 0;
 -}
 -
 -static void
 -mlxsw_sp_fib6_node_list_remove(struct mlxsw_sp_fib6_entry *fib6_entry)
 -{
 -      list_del(&fib6_entry->common.list);
 -}
 -
 -static int mlxsw_sp_fib6_node_entry_link(struct mlxsw_sp *mlxsw_sp,
 -                                       struct mlxsw_sp_fib6_entry *fib6_entry,
 -                                       bool *p_replace)
 -{
 -      int err;
 -
 -      err = mlxsw_sp_fib6_node_list_insert(fib6_entry, p_replace);
 -      if (err)
 -              return err;
 -
 -      err = mlxsw_sp_fib_node_entry_add(mlxsw_sp, &fib6_entry->common);
 -      if (err)
 -              goto err_fib_node_entry_add;
 -
 -      return 0;
 -
 -err_fib_node_entry_add:
 -      mlxsw_sp_fib6_node_list_remove(fib6_entry);
 -      return err;
 -}
 -
 -static void
 -mlxsw_sp_fib6_node_entry_unlink(struct mlxsw_sp *mlxsw_sp,
 -                              struct mlxsw_sp_fib6_entry *fib6_entry)
 -{
 -      mlxsw_sp_fib_node_entry_del(mlxsw_sp, &fib6_entry->common);
 -      mlxsw_sp_fib6_node_list_remove(fib6_entry);
 -}
 -
  static struct mlxsw_sp_fib6_entry *
  mlxsw_sp_fib6_entry_lookup(struct mlxsw_sp *mlxsw_sp,
                           const struct fib6_info *rt)
        struct mlxsw_sp_fib6_entry *fib6_entry;
        struct mlxsw_sp_fib_node *fib_node;
        struct mlxsw_sp_fib *fib;
 +      struct fib6_info *cmp_rt;
        struct mlxsw_sp_vr *vr;
  
        vr = mlxsw_sp_vr_find(mlxsw_sp, rt->fib6_table->tb6_id);
        if (!fib_node)
                return NULL;
  
 -      list_for_each_entry(fib6_entry, &fib_node->entry_list, common.list) {
 -              struct fib6_info *iter_rt = mlxsw_sp_fib6_entry_rt(fib6_entry);
 -
 -              if (rt->fib6_table->tb6_id == iter_rt->fib6_table->tb6_id &&
 -                  rt->fib6_metric == iter_rt->fib6_metric &&
 -                  mlxsw_sp_fib6_entry_rt_find(fib6_entry, rt))
 -                      return fib6_entry;
 -      }
 +      fib6_entry = container_of(fib_node->fib_entry,
 +                                struct mlxsw_sp_fib6_entry, common);
 +      cmp_rt = mlxsw_sp_fib6_entry_rt(fib6_entry);
 +      if (rt->fib6_table->tb6_id == cmp_rt->fib6_table->tb6_id &&
 +          rt->fib6_metric == cmp_rt->fib6_metric &&
 +          mlxsw_sp_fib6_entry_rt_find(fib6_entry, rt))
 +              return fib6_entry;
  
        return NULL;
  }
  
 -static void mlxsw_sp_fib6_entry_replace(struct mlxsw_sp *mlxsw_sp,
 -                                      struct mlxsw_sp_fib6_entry *fib6_entry,
 -                                      bool replace)
 -{
 -      struct mlxsw_sp_fib_node *fib_node = fib6_entry->common.fib_node;
 -      struct mlxsw_sp_fib6_entry *replaced;
 -
 -      if (!replace)
 -              return;
 -
 -      replaced = list_next_entry(fib6_entry, common.list);
 -
 -      mlxsw_sp_fib6_node_entry_unlink(mlxsw_sp, replaced);
 -      mlxsw_sp_fib6_entry_destroy(mlxsw_sp, replaced);
 -      mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
 -}
 -
 -static int mlxsw_sp_router_fib6_add(struct mlxsw_sp *mlxsw_sp,
 -                                  struct fib6_info **rt_arr,
 -                                  unsigned int nrt6, bool replace)
 +static int mlxsw_sp_router_fib6_replace(struct mlxsw_sp *mlxsw_sp,
 +                                      struct fib6_info **rt_arr,
 +                                      unsigned int nrt6)
  {
 -      struct mlxsw_sp_fib6_entry *fib6_entry;
 +      struct mlxsw_sp_fib6_entry *fib6_entry, *fib6_replaced;
 +      struct mlxsw_sp_fib_entry *replaced;
        struct mlxsw_sp_fib_node *fib_node;
        struct fib6_info *rt = rt_arr[0];
        int err;
        if (IS_ERR(fib_node))
                return PTR_ERR(fib_node);
  
 -      /* Before creating a new entry, try to append route to an existing
 -       * multipath entry.
 -       */
 -      fib6_entry = mlxsw_sp_fib6_node_mp_entry_find(fib_node, rt, replace);
 -      if (fib6_entry) {
 -              err = mlxsw_sp_fib6_entry_nexthop_add(mlxsw_sp, fib6_entry,
 -                                                    rt_arr, nrt6);
 -              if (err)
 -                      goto err_fib6_entry_nexthop_add;
 -              return 0;
 -      }
 -
        fib6_entry = mlxsw_sp_fib6_entry_create(mlxsw_sp, fib_node, rt_arr,
                                                nrt6);
        if (IS_ERR(fib6_entry)) {
                goto err_fib6_entry_create;
        }
  
 -      err = mlxsw_sp_fib6_node_entry_link(mlxsw_sp, fib6_entry, &replace);
 +      replaced = fib_node->fib_entry;
 +      err = mlxsw_sp_fib_node_entry_link(mlxsw_sp, &fib6_entry->common);
        if (err)
 -              goto err_fib6_node_entry_link;
 +              goto err_fib_node_entry_link;
 +
 +      /* Nothing to replace */
 +      if (!replaced)
 +              return 0;
  
 -      mlxsw_sp_fib6_entry_replace(mlxsw_sp, fib6_entry, replace);
 +      mlxsw_sp_fib_entry_offload_unset(replaced);
 +      fib6_replaced = container_of(replaced, struct mlxsw_sp_fib6_entry,
 +                                   common);
 +      mlxsw_sp_fib6_entry_destroy(mlxsw_sp, fib6_replaced);
  
        return 0;
  
 -err_fib6_node_entry_link:
 +err_fib_node_entry_link:
 +      fib_node->fib_entry = replaced;
        mlxsw_sp_fib6_entry_destroy(mlxsw_sp, fib6_entry);
  err_fib6_entry_create:
 +      mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
 +      return err;
 +}
 +
 +static int mlxsw_sp_router_fib6_append(struct mlxsw_sp *mlxsw_sp,
 +                                     struct fib6_info **rt_arr,
 +                                     unsigned int nrt6)
 +{
 +      struct mlxsw_sp_fib6_entry *fib6_entry;
 +      struct mlxsw_sp_fib_node *fib_node;
 +      struct fib6_info *rt = rt_arr[0];
 +      int err;
 +
 +      if (mlxsw_sp->router->aborted)
 +              return 0;
 +
 +      if (rt->fib6_src.plen)
 +              return -EINVAL;
 +
 +      if (mlxsw_sp_fib6_rt_should_ignore(rt))
 +              return 0;
 +
 +      fib_node = mlxsw_sp_fib_node_get(mlxsw_sp, rt->fib6_table->tb6_id,
 +                                       &rt->fib6_dst.addr,
 +                                       sizeof(rt->fib6_dst.addr),
 +                                       rt->fib6_dst.plen,
 +                                       MLXSW_SP_L3_PROTO_IPV6);
 +      if (IS_ERR(fib_node))
 +              return PTR_ERR(fib_node);
 +
 +      if (WARN_ON_ONCE(!fib_node->fib_entry)) {
 +              mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
 +              return -EINVAL;
 +      }
 +
 +      fib6_entry = container_of(fib_node->fib_entry,
 +                                struct mlxsw_sp_fib6_entry, common);
 +      err = mlxsw_sp_fib6_entry_nexthop_add(mlxsw_sp, fib6_entry, rt_arr,
 +                                            nrt6);
 +      if (err)
 +              goto err_fib6_entry_nexthop_add;
 +
 +      return 0;
 +
  err_fib6_entry_nexthop_add:
        mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
        return err;
@@@ -5487,7 -5762,7 +5487,7 @@@ static void mlxsw_sp_router_fib6_del(st
  
        fib_node = fib6_entry->common.fib_node;
  
 -      mlxsw_sp_fib6_node_entry_unlink(mlxsw_sp, fib6_entry);
 +      mlxsw_sp_fib_node_entry_unlink(mlxsw_sp, &fib6_entry->common);
        mlxsw_sp_fib6_entry_destroy(mlxsw_sp, fib6_entry);
        mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
  }
@@@ -5641,25 -5916,39 +5641,25 @@@ static int mlxsw_sp_router_set_abort_tr
  static void mlxsw_sp_fib4_node_flush(struct mlxsw_sp *mlxsw_sp,
                                     struct mlxsw_sp_fib_node *fib_node)
  {
 -      struct mlxsw_sp_fib4_entry *fib4_entry, *tmp;
 -
 -      list_for_each_entry_safe(fib4_entry, tmp, &fib_node->entry_list,
 -                               common.list) {
 -              bool do_break = &tmp->common.list == &fib_node->entry_list;
 +      struct mlxsw_sp_fib4_entry *fib4_entry;
  
 -              mlxsw_sp_fib4_node_entry_unlink(mlxsw_sp, fib4_entry);
 -              mlxsw_sp_fib4_entry_destroy(mlxsw_sp, fib4_entry);
 -              mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
 -              /* Break when entry list is empty and node was freed.
 -               * Otherwise, we'll access freed memory in the next
 -               * iteration.
 -               */
 -              if (do_break)
 -                      break;
 -      }
 +      fib4_entry = container_of(fib_node->fib_entry,
 +                                struct mlxsw_sp_fib4_entry, common);
 +      mlxsw_sp_fib_node_entry_unlink(mlxsw_sp, fib_node->fib_entry);
 +      mlxsw_sp_fib4_entry_destroy(mlxsw_sp, fib4_entry);
 +      mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
  }
  
  static void mlxsw_sp_fib6_node_flush(struct mlxsw_sp *mlxsw_sp,
                                     struct mlxsw_sp_fib_node *fib_node)
  {
 -      struct mlxsw_sp_fib6_entry *fib6_entry, *tmp;
 -
 -      list_for_each_entry_safe(fib6_entry, tmp, &fib_node->entry_list,
 -                               common.list) {
 -              bool do_break = &tmp->common.list == &fib_node->entry_list;
 +      struct mlxsw_sp_fib6_entry *fib6_entry;
  
 -              mlxsw_sp_fib6_node_entry_unlink(mlxsw_sp, fib6_entry);
 -              mlxsw_sp_fib6_entry_destroy(mlxsw_sp, fib6_entry);
 -              mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
 -              if (do_break)
 -                      break;
 -      }
 +      fib6_entry = container_of(fib_node->fib_entry,
 +                                struct mlxsw_sp_fib6_entry, common);
 +      mlxsw_sp_fib_node_entry_unlink(mlxsw_sp, fib_node->fib_entry);
 +      mlxsw_sp_fib6_entry_destroy(mlxsw_sp, fib6_entry);
 +      mlxsw_sp_fib_node_put(mlxsw_sp, fib_node);
  }
  
  static void mlxsw_sp_fib_node_flush(struct mlxsw_sp *mlxsw_sp,
@@@ -5810,6 -6099,7 +5810,6 @@@ static void mlxsw_sp_router_fib4_event_
        struct mlxsw_sp_fib_event_work *fib_work =
                container_of(work, struct mlxsw_sp_fib_event_work, work);
        struct mlxsw_sp *mlxsw_sp = fib_work->mlxsw_sp;
 -      bool replace, append;
        int err;
  
        /* Protect internal structures from changes */
        mlxsw_sp_span_respin(mlxsw_sp);
  
        switch (fib_work->event) {
 -      case FIB_EVENT_ENTRY_REPLACE: /* fall through */
 -      case FIB_EVENT_ENTRY_APPEND: /* fall through */
 -      case FIB_EVENT_ENTRY_ADD:
 -              replace = fib_work->event == FIB_EVENT_ENTRY_REPLACE;
 -              append = fib_work->event == FIB_EVENT_ENTRY_APPEND;
 -              err = mlxsw_sp_router_fib4_add(mlxsw_sp, &fib_work->fen_info,
 -                                             replace, append);
 +      case FIB_EVENT_ENTRY_REPLACE:
 +              err = mlxsw_sp_router_fib4_replace(mlxsw_sp,
 +                                                 &fib_work->fen_info);
                if (err)
                        mlxsw_sp_router_fib_abort(mlxsw_sp);
                fib_info_put(fib_work->fen_info.fi);
@@@ -5844,24 -6138,20 +5844,24 @@@ static void mlxsw_sp_router_fib6_event_
        struct mlxsw_sp_fib_event_work *fib_work =
                container_of(work, struct mlxsw_sp_fib_event_work, work);
        struct mlxsw_sp *mlxsw_sp = fib_work->mlxsw_sp;
 -      bool replace;
        int err;
  
        rtnl_lock();
        mlxsw_sp_span_respin(mlxsw_sp);
  
        switch (fib_work->event) {
 -      case FIB_EVENT_ENTRY_REPLACE: /* fall through */
 -      case FIB_EVENT_ENTRY_ADD:
 -              replace = fib_work->event == FIB_EVENT_ENTRY_REPLACE;
 -              err = mlxsw_sp_router_fib6_add(mlxsw_sp,
 -                                             fib_work->fib6_work.rt_arr,
 -                                             fib_work->fib6_work.nrt6,
 -                                             replace);
 +      case FIB_EVENT_ENTRY_REPLACE:
 +              err = mlxsw_sp_router_fib6_replace(mlxsw_sp,
 +                                                 fib_work->fib6_work.rt_arr,
 +                                                 fib_work->fib6_work.nrt6);
 +              if (err)
 +                      mlxsw_sp_router_fib_abort(mlxsw_sp);
 +              mlxsw_sp_router_fib6_work_fini(&fib_work->fib6_work);
 +              break;
 +      case FIB_EVENT_ENTRY_APPEND:
 +              err = mlxsw_sp_router_fib6_append(mlxsw_sp,
 +                                                fib_work->fib6_work.rt_arr,
 +                                                fib_work->fib6_work.nrt6);
                if (err)
                        mlxsw_sp_router_fib_abort(mlxsw_sp);
                mlxsw_sp_router_fib6_work_fini(&fib_work->fib6_work);
@@@ -5926,6 -6216,8 +5926,6 @@@ static void mlxsw_sp_router_fib4_event(
  
        switch (fib_work->event) {
        case FIB_EVENT_ENTRY_REPLACE: /* fall through */
 -      case FIB_EVENT_ENTRY_APPEND: /* fall through */
 -      case FIB_EVENT_ENTRY_ADD: /* fall through */
        case FIB_EVENT_ENTRY_DEL:
                fen_info = container_of(info, struct fib_entry_notifier_info,
                                        info);
@@@ -5953,7 -6245,7 +5953,7 @@@ static int mlxsw_sp_router_fib6_event(s
  
        switch (fib_work->event) {
        case FIB_EVENT_ENTRY_REPLACE: /* fall through */
 -      case FIB_EVENT_ENTRY_ADD: /* fall through */
 +      case FIB_EVENT_ENTRY_APPEND: /* fall through */
        case FIB_EVENT_ENTRY_DEL:
                fen6_info = container_of(info, struct fib6_entry_notifier_info,
                                         info);
@@@ -6056,9 -6348,9 +6056,9 @@@ static int mlxsw_sp_router_fib_event(st
                err = mlxsw_sp_router_fib_rule_event(event, info,
                                                     router->mlxsw_sp);
                return notifier_from_errno(err);
 -      case FIB_EVENT_ENTRY_ADD:
 +      case FIB_EVENT_ENTRY_ADD: /* fall through */
        case FIB_EVENT_ENTRY_REPLACE: /* fall through */
 -      case FIB_EVENT_ENTRY_APPEND:  /* fall through */
 +      case FIB_EVENT_ENTRY_APPEND:
                if (router->aborted) {
                        NL_SET_ERR_MSG_MOD(info->extack, "FIB offload was aborted. Not configuring route");
                        return notifier_from_errno(-EINVAL);
@@@ -6787,6 -7079,9 +6787,9 @@@ static int mlxsw_sp_router_port_check_r
  
        for (i = 0; i < MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_RIFS); i++) {
                rif = mlxsw_sp->router->rifs[i];
+               if (rif && rif->ops &&
+                   rif->ops->type == MLXSW_SP_RIF_TYPE_IPIP_LB)
+                       continue;
                if (rif && rif->dev && rif->dev != dev &&
                    !ether_addr_equal_masked(rif->dev->dev_addr, dev_addr,
                                             mlxsw_sp->mac_mask)) {
diff --combined drivers/ptp/ptp_clock.c
@@@ -166,9 -166,9 +166,9 @@@ static struct posix_clock_operations pt
        .read           = ptp_read,
  };
  
- static void delete_ptp_clock(struct posix_clock *pc)
+ static void ptp_clock_release(struct device *dev)
  {
-       struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+       struct ptp_clock *ptp = container_of(dev, struct ptp_clock, dev);
  
        mutex_destroy(&ptp->tsevq_mux);
        mutex_destroy(&ptp->pincfg_mux);
@@@ -213,7 -213,6 +213,6 @@@ struct ptp_clock *ptp_clock_register(st
        }
  
        ptp->clock.ops = ptp_clock_ops;
-       ptp->clock.release = delete_ptp_clock;
        ptp->info = info;
        ptp->devid = MKDEV(major, index);
        ptp->index = index;
        if (err)
                goto no_pin_groups;
  
-       /* Create a new device in our class. */
-       ptp->dev = device_create_with_groups(ptp_class, parent, ptp->devid,
-                                            ptp, ptp->pin_attr_groups,
-                                            "ptp%d", ptp->index);
-       if (IS_ERR(ptp->dev)) {
-               err = PTR_ERR(ptp->dev);
-               goto no_device;
-       }
        /* Register a new PPS source. */
        if (info->pps) {
                struct pps_source_info pps;
                }
        }
  
-       /* Create a posix clock. */
-       err = posix_clock_register(&ptp->clock, ptp->devid);
+       /* Initialize a new device of our class in our clock structure. */
+       device_initialize(&ptp->dev);
+       ptp->dev.devt = ptp->devid;
+       ptp->dev.class = ptp_class;
+       ptp->dev.parent = parent;
+       ptp->dev.groups = ptp->pin_attr_groups;
+       ptp->dev.release = ptp_clock_release;
+       dev_set_drvdata(&ptp->dev, ptp);
+       dev_set_name(&ptp->dev, "ptp%d", ptp->index);
+       /* Create a posix clock and link it to the device. */
+       err = posix_clock_register(&ptp->clock, &ptp->dev);
        if (err) {
                pr_err("failed to create posix clock\n");
                goto no_clock;
@@@ -273,8 -273,6 +273,6 @@@ no_clock
        if (ptp->pps_source)
                pps_unregister_source(ptp->pps_source);
  no_pps:
-       device_destroy(ptp_class, ptp->devid);
- no_device:
        ptp_cleanup_pin_groups(ptp);
  no_pin_groups:
        if (ptp->kworker)
@@@ -304,7 -302,6 +302,6 @@@ int ptp_clock_unregister(struct ptp_clo
        if (ptp->pps_source)
                pps_unregister_source(ptp->pps_source);
  
-       device_destroy(ptp_class, ptp->devid);
        ptp_cleanup_pin_groups(ptp);
  
        posix_clock_unregister(&ptp->clock);
@@@ -371,12 -368,6 +368,12 @@@ int ptp_schedule_worker(struct ptp_cloc
  }
  EXPORT_SYMBOL(ptp_schedule_worker);
  
 +void ptp_cancel_worker_sync(struct ptp_clock *ptp)
 +{
 +      kthread_cancel_delayed_work_sync(&ptp->aux_work);
 +}
 +EXPORT_SYMBOL(ptp_cancel_worker_sync);
 +
  /* module operations */
  
  static void __exit ptp_exit(void)
@@@ -520,10 -520,11 +520,10 @@@ static int __qeth_issue_next_read(struc
        } else {
                QETH_DBF_MESSAGE(2, "error %i on device %x when starting next read ccw!\n",
                                 rc, CARD_DEVID(card));
 -              atomic_set(&channel->irq_pending, 0);
 +              qeth_unlock_channel(card, channel);
                qeth_put_cmd(iob);
                card->read_or_write_problem = 1;
                qeth_schedule_recovery(card);
 -              wake_up(&card->wait_q);
        }
        return rc;
  }
@@@ -971,6 -972,8 +971,6 @@@ static void qeth_irq(struct ccw_device 
        /* while we hold the ccwdev lock, this stays valid: */
        gdev = dev_get_drvdata(&cdev->dev);
        card = dev_get_drvdata(&gdev->dev);
 -      if (!card)
 -              return;
  
        QETH_CARD_TEXT(card, 5, "irq");
  
        }
  
        channel->active_cmd = NULL;
 +      qeth_unlock_channel(card, channel);
  
        rc = qeth_check_irb_error(card, cdev, irb);
        if (rc) {
                /* IO was terminated, free its resources. */
                if (iob)
                        qeth_cancel_cmd(iob, rc);
 -              atomic_set(&channel->irq_pending, 0);
 -              wake_up(&card->wait_q);
                return;
        }
  
 -      atomic_set(&channel->irq_pending, 0);
 -
 -      if (irb->scsw.cmd.fctl & (SCSW_FCTL_CLEAR_FUNC))
 +      if (irb->scsw.cmd.fctl & SCSW_FCTL_CLEAR_FUNC) {
                channel->state = CH_STATE_STOPPED;
 +              wake_up(&card->wait_q);
 +      }
  
 -      if (irb->scsw.cmd.fctl & (SCSW_FCTL_HALT_FUNC))
 +      if (irb->scsw.cmd.fctl & SCSW_FCTL_HALT_FUNC) {
                channel->state = CH_STATE_HALTED;
 +              wake_up(&card->wait_q);
 +      }
  
        if (iob && (irb->scsw.cmd.fctl & (SCSW_FCTL_CLEAR_FUNC |
                                          SCSW_FCTL_HALT_FUNC))) {
                                qeth_cancel_cmd(iob, rc);
                        qeth_clear_ipacmd_list(card);
                        qeth_schedule_recovery(card);
 -                      goto out;
 +                      return;
                }
        }
  
                /* sanity check: */
                if (irb->scsw.cmd.count > iob->length) {
                        qeth_cancel_cmd(iob, -EIO);
 -                      goto out;
 +                      return;
                }
                if (iob->callback)
                        iob->callback(card, iob,
                                      iob->length - irb->scsw.cmd.count);
        }
 -
 -out:
 -      wake_up(&card->wait_q);
 -      return;
  }
  
  static void qeth_notify_skbs(struct qeth_qdio_out_q *q,
@@@ -1192,6 -1198,31 +1192,6 @@@ static void qeth_free_buffer_pool(struc
        }
  }
  
 -static void qeth_clean_channel(struct qeth_channel *channel)
 -{
 -      struct ccw_device *cdev = channel->ccwdev;
 -
 -      QETH_DBF_TEXT(SETUP, 2, "freech");
 -
 -      spin_lock_irq(get_ccwdev_lock(cdev));
 -      cdev->handler = NULL;
 -      spin_unlock_irq(get_ccwdev_lock(cdev));
 -}
 -
 -static void qeth_setup_channel(struct qeth_channel *channel)
 -{
 -      struct ccw_device *cdev = channel->ccwdev;
 -
 -      QETH_DBF_TEXT(SETUP, 2, "setupch");
 -
 -      channel->state = CH_STATE_DOWN;
 -      atomic_set(&channel->irq_pending, 0);
 -
 -      spin_lock_irq(get_ccwdev_lock(cdev));
 -      cdev->handler = qeth_irq;
 -      spin_unlock_irq(get_ccwdev_lock(cdev));
 -}
 -
  static int qeth_osa_set_output_queues(struct qeth_card *card, bool single)
  {
        unsigned int count = single ? 1 : card->dev->num_tx_queues;
@@@ -1364,6 -1395,9 +1364,6 @@@ static struct qeth_card *qeth_alloc_car
        if (!card->read_cmd)
                goto out_read_cmd;
  
 -      qeth_setup_channel(&card->read);
 -      qeth_setup_channel(&card->write);
 -      qeth_setup_channel(&card->data);
        card->qeth_service_level.seq_print = qeth_core_sl_print;
        register_service_level(&card->qeth_service_level);
        return card;
@@@ -1433,38 -1467,12 +1433,38 @@@ int qeth_stop_channel(struct qeth_chann
                        channel->active_cmd);
                channel->active_cmd = NULL;
        }
 +      cdev->handler = NULL;
        spin_unlock_irq(get_ccwdev_lock(cdev));
  
        return rc;
  }
  EXPORT_SYMBOL_GPL(qeth_stop_channel);
  
 +static int qeth_start_channel(struct qeth_channel *channel)
 +{
 +      struct ccw_device *cdev = channel->ccwdev;
 +      int rc;
 +
 +      channel->state = CH_STATE_DOWN;
 +      atomic_set(&channel->irq_pending, 0);
 +
 +      spin_lock_irq(get_ccwdev_lock(cdev));
 +      cdev->handler = qeth_irq;
 +      spin_unlock_irq(get_ccwdev_lock(cdev));
 +
 +      rc = ccw_device_set_online(cdev);
 +      if (rc)
 +              goto err;
 +
 +      return 0;
 +
 +err:
 +      spin_lock_irq(get_ccwdev_lock(cdev));
 +      cdev->handler = NULL;
 +      spin_unlock_irq(get_ccwdev_lock(cdev));
 +      return rc;
 +}
 +
  static int qeth_halt_channels(struct qeth_card *card)
  {
        int rc1 = 0, rc2 = 0, rc3 = 0;
@@@ -1776,7 -1784,8 +1776,7 @@@ static int qeth_send_control_data(struc
                QETH_CARD_TEXT_(card, 2, " err%d", rc);
                qeth_dequeue_cmd(card, iob);
                qeth_put_cmd(iob);
 -              atomic_set(&channel->irq_pending, 0);
 -              wake_up(&card->wait_q);
 +              qeth_unlock_channel(card, channel);
                goto out;
        }
  
@@@ -2473,50 -2482,46 +2473,46 @@@ static int qeth_mpc_initialize(struct q
        rc = qeth_cm_enable(card);
        if (rc) {
                QETH_CARD_TEXT_(card, 2, "2err%d", rc);
-               goto out_qdio;
+               return rc;
        }
        rc = qeth_cm_setup(card);
        if (rc) {
                QETH_CARD_TEXT_(card, 2, "3err%d", rc);
-               goto out_qdio;
+               return rc;
        }
        rc = qeth_ulp_enable(card);
        if (rc) {
                QETH_CARD_TEXT_(card, 2, "4err%d", rc);
-               goto out_qdio;
+               return rc;
        }
        rc = qeth_ulp_setup(card);
        if (rc) {
                QETH_CARD_TEXT_(card, 2, "5err%d", rc);
-               goto out_qdio;
+               return rc;
        }
        rc = qeth_alloc_qdio_queues(card);
        if (rc) {
                QETH_CARD_TEXT_(card, 2, "5err%d", rc);
-               goto out_qdio;
+               return rc;
        }
        rc = qeth_qdio_establish(card);
        if (rc) {
                QETH_CARD_TEXT_(card, 2, "6err%d", rc);
                qeth_free_qdio_queues(card);
-               goto out_qdio;
+               return rc;
        }
        rc = qeth_qdio_activate(card);
        if (rc) {
                QETH_CARD_TEXT_(card, 2, "7err%d", rc);
-               goto out_qdio;
+               return rc;
        }
        rc = qeth_dm_act(card);
        if (rc) {
                QETH_CARD_TEXT_(card, 2, "8err%d", rc);
-               goto out_qdio;
+               return rc;
        }
  
        return 0;
- out_qdio:
-       qeth_qdio_clear_card(card, !IS_IQD(card));
-       qdio_free(CARD_DDEV(card));
-       return rc;
  }
  
  void qeth_print_status_message(struct qeth_card *card)
@@@ -2627,8 -2632,7 +2623,8 @@@ static int qeth_init_input_buffer(struc
  
        if ((card->options.cq == QETH_CQ_ENABLED) && (!buf->rx_skb)) {
                buf->rx_skb = netdev_alloc_skb(card->dev,
 -                                             QETH_RX_PULL_LEN + ETH_HLEN);
 +                                             ETH_HLEN +
 +                                             sizeof(struct ipv6hdr));
                if (!buf->rx_skb)
                        return 1;
        }
@@@ -2863,7 -2867,7 +2859,7 @@@ static int qeth_query_setadapterparms_c
                      cmd->data.setadapterparms.data.query_cmds_supp.lan_type;
                QETH_CARD_TEXT_(card, 2, "lnk %d", card->info.link_type);
        }
 -      card->options.adp.supported_funcs =
 +      card->options.adp.supported =
                cmd->data.setadapterparms.data.query_cmds_supp.supported_cmds;
        return 0;
  }
@@@ -2919,8 -2923,8 +2915,8 @@@ static int qeth_query_ipassists_cb(stru
        case IPA_RC_NOTSUPP:
        case IPA_RC_L2_UNSUPPORTED_CMD:
                QETH_CARD_TEXT(card, 2, "ipaunsup");
 -              card->options.ipa4.supported_funcs |= IPA_SETADAPTERPARMS;
 -              card->options.ipa6.supported_funcs |= IPA_SETADAPTERPARMS;
 +              card->options.ipa4.supported |= IPA_SETADAPTERPARMS;
 +              card->options.ipa6.supported |= IPA_SETADAPTERPARMS;
                return -EOPNOTSUPP;
        default:
                QETH_DBF_MESSAGE(1, "IPA_CMD_QIPASSIST on device %x: Unhandled rc=%#x\n",
                return -EIO;
        }
  
 -      if (cmd->hdr.prot_version == QETH_PROT_IPV4) {
 -              card->options.ipa4.supported_funcs = cmd->hdr.ipa_supported;
 -              card->options.ipa4.enabled_funcs = cmd->hdr.ipa_enabled;
 -      } else if (cmd->hdr.prot_version == QETH_PROT_IPV6) {
 -              card->options.ipa6.supported_funcs = cmd->hdr.ipa_supported;
 -              card->options.ipa6.enabled_funcs = cmd->hdr.ipa_enabled;
 -      } else
 +      if (cmd->hdr.prot_version == QETH_PROT_IPV4)
 +              card->options.ipa4 = cmd->hdr.assists;
 +      else if (cmd->hdr.prot_version == QETH_PROT_IPV6)
 +              card->options.ipa6 = cmd->hdr.assists;
 +      else
                QETH_DBF_MESSAGE(1, "IPA_CMD_QIPASSIST on device %x: Flawed LIC detected\n",
                                 CARD_DEVID(card));
        return 0;
@@@ -3403,7 -3409,7 +3399,7 @@@ static void qeth_qdio_start_poll(struc
        struct qeth_card *card = (struct qeth_card *)card_ptr;
  
        if (card->dev->flags & IFF_UP)
 -              napi_schedule(&card->napi);
 +              napi_schedule_irqoff(&card->napi);
  }
  
  int qeth_configure_cq(struct qeth_card *card, enum qeth_cq cq)
                        goto out;
                }
  
-               if (card->state != CARD_STATE_DOWN) {
-                       rc = -1;
-                       goto out;
-               }
                qeth_free_qdio_queues(card);
                card->options.cq = cq;
                rc = 0;
@@@ -4315,7 -4316,7 +4306,7 @@@ int qeth_set_access_ctrl_online(struct 
        return rc;
  }
  
 -void qeth_tx_timeout(struct net_device *dev)
 +void qeth_tx_timeout(struct net_device *dev, unsigned int txqueue)
  {
        struct qeth_card *card;
  
@@@ -4696,7 -4697,7 +4687,7 @@@ static void qeth_determine_capabilities
        QETH_CARD_TEXT(card, 2, "detcapab");
        if (!ddev->online) {
                ddev_offline = 1;
 -              rc = ccw_device_set_online(ddev);
 +              rc = qeth_start_channel(channel);
                if (rc) {
                        QETH_CARD_TEXT_(card, 2, "3err%d", rc);
                        goto out;
@@@ -4871,6 -4872,9 +4862,6 @@@ out_free_nothing
  static void qeth_core_free_card(struct qeth_card *card)
  {
        QETH_CARD_TEXT(card, 2, "freecrd");
 -      qeth_clean_channel(&card->read);
 -      qeth_clean_channel(&card->write);
 -      qeth_clean_channel(&card->data);
        qeth_put_cmd(card->read_cmd);
        destroy_workqueue(card->event_wq);
        unregister_service_level(&card->qeth_service_level);
@@@ -4933,14 -4937,13 +4924,14 @@@ retry
        qeth_stop_channel(&card->write);
        qeth_stop_channel(&card->read);
        qdio_free(CARD_DDEV(card));
 -      rc = ccw_device_set_online(CARD_RDEV(card));
 +
 +      rc = qeth_start_channel(&card->read);
        if (rc)
                goto retriable;
 -      rc = ccw_device_set_online(CARD_WDEV(card));
 +      rc = qeth_start_channel(&card->write);
        if (rc)
                goto retriable;
 -      rc = ccw_device_set_online(CARD_DDEV(card));
 +      rc = qeth_start_channel(&card->data);
        if (rc)
                goto retriable;
  retriable:
                *carrier_ok = true;
        }
  
 -      card->options.ipa4.supported_funcs = 0;
 -      card->options.ipa6.supported_funcs = 0;
 -      card->options.adp.supported_funcs = 0;
 +      card->options.ipa4.supported = 0;
 +      card->options.ipa6.supported = 0;
 +      card->options.adp.supported = 0;
        card->options.sbp.supported_funcs = 0;
        card->info.diagass_support = 0;
        rc = qeth_query_ipassists(card, QETH_PROT_IPV4);
        }
        if (qeth_adp_supported(card, IPA_SETADP_SET_DIAG_ASSIST)) {
                rc = qeth_query_setdiagass(card);
-               if (rc < 0) {
+               if (rc)
                        QETH_CARD_TEXT_(card, 2, "8err%d", rc);
-                       goto out;
-               }
        }
  
        if (!qeth_is_diagass_supported(card, QETH_DIAGS_CMD_TRAP) ||
  }
  EXPORT_SYMBOL_GPL(qeth_core_hardsetup_card);
  
 +#if IS_ENABLED(CONFIG_QETH_L3)
 +static void qeth_l3_rebuild_skb(struct qeth_card *card, struct sk_buff *skb,
 +                              struct qeth_hdr *hdr)
 +{
 +      struct af_iucv_trans_hdr *iucv = (struct af_iucv_trans_hdr *) skb->data;
 +      struct qeth_hdr_layer3 *l3_hdr = &hdr->hdr.l3;
 +      struct net_device *dev = skb->dev;
 +
 +      if (IS_IQD(card) && iucv->magic == ETH_P_AF_IUCV) {
 +              dev_hard_header(skb, dev, ETH_P_AF_IUCV, dev->dev_addr,
 +                              "FAKELL", skb->len);
 +              return;
 +      }
 +
 +      if (!(l3_hdr->flags & QETH_HDR_PASSTHRU)) {
 +              u16 prot = (l3_hdr->flags & QETH_HDR_IPV6) ? ETH_P_IPV6 :
 +                                                           ETH_P_IP;
 +              unsigned char tg_addr[ETH_ALEN];
 +
 +              skb_reset_network_header(skb);
 +              switch (l3_hdr->flags & QETH_HDR_CAST_MASK) {
 +              case QETH_CAST_MULTICAST:
 +                      if (prot == ETH_P_IP)
 +                              ip_eth_mc_map(ip_hdr(skb)->daddr, tg_addr);
 +                      else
 +                              ipv6_eth_mc_map(&ipv6_hdr(skb)->daddr, tg_addr);
 +                      QETH_CARD_STAT_INC(card, rx_multicast);
 +                      break;
 +              case QETH_CAST_BROADCAST:
 +                      ether_addr_copy(tg_addr, dev->broadcast);
 +                      QETH_CARD_STAT_INC(card, rx_multicast);
 +                      break;
 +              default:
 +                      if (card->options.sniffer)
 +                              skb->pkt_type = PACKET_OTHERHOST;
 +                      ether_addr_copy(tg_addr, dev->dev_addr);
 +              }
 +
 +              if (l3_hdr->ext_flags & QETH_HDR_EXT_SRC_MAC_ADDR)
 +                      dev_hard_header(skb, dev, prot, tg_addr,
 +                                      &l3_hdr->next_hop.rx.src_mac, skb->len);
 +              else
 +                      dev_hard_header(skb, dev, prot, tg_addr, "FAKELL",
 +                                      skb->len);
 +      }
 +
 +      /* copy VLAN tag from hdr into skb */
 +      if (!card->options.sniffer &&
 +          (l3_hdr->ext_flags & (QETH_HDR_EXT_VLAN_FRAME |
 +                                QETH_HDR_EXT_INCLUDE_VLAN_TAG))) {
 +              u16 tag = (l3_hdr->ext_flags & QETH_HDR_EXT_VLAN_FRAME) ?
 +                              l3_hdr->vlan_id :
 +                              l3_hdr->next_hop.rx.vlan_id;
 +
 +              __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), tag);
 +      }
 +}
 +#endif
 +
 +static void qeth_receive_skb(struct qeth_card *card, struct sk_buff *skb,
 +                           struct qeth_hdr *hdr, bool uses_frags)
 +{
 +      struct napi_struct *napi = &card->napi;
 +      bool is_cso;
 +
 +      switch (hdr->hdr.l2.id) {
 +      case QETH_HEADER_TYPE_OSN:
 +              skb_push(skb, sizeof(*hdr));
 +              skb_copy_to_linear_data(skb, hdr, sizeof(*hdr));
 +              QETH_CARD_STAT_ADD(card, rx_bytes, skb->len);
 +              QETH_CARD_STAT_INC(card, rx_packets);
 +
 +              card->osn_info.data_cb(skb);
 +              return;
 +#if IS_ENABLED(CONFIG_QETH_L3)
 +      case QETH_HEADER_TYPE_LAYER3:
 +              qeth_l3_rebuild_skb(card, skb, hdr);
 +              is_cso = hdr->hdr.l3.ext_flags & QETH_HDR_EXT_CSUM_TRANSP_REQ;
 +              break;
 +#endif
 +      case QETH_HEADER_TYPE_LAYER2:
 +              is_cso = hdr->hdr.l2.flags[1] & QETH_HDR_EXT_CSUM_TRANSP_REQ;
 +              break;
 +      default:
 +              /* never happens */
 +              if (uses_frags)
 +                      napi_free_frags(napi);
 +              else
 +                      dev_kfree_skb_any(skb);
 +              return;
 +      }
 +
 +      if (is_cso && (card->dev->features & NETIF_F_RXCSUM)) {
 +              skb->ip_summed = CHECKSUM_UNNECESSARY;
 +              QETH_CARD_STAT_INC(card, rx_skb_csum);
 +      } else {
 +              skb->ip_summed = CHECKSUM_NONE;
 +      }
 +
 +      QETH_CARD_STAT_ADD(card, rx_bytes, skb->len);
 +      QETH_CARD_STAT_INC(card, rx_packets);
 +      if (skb_is_nonlinear(skb)) {
 +              QETH_CARD_STAT_INC(card, rx_sg_skbs);
 +              QETH_CARD_STAT_ADD(card, rx_sg_frags,
 +                                 skb_shinfo(skb)->nr_frags);
 +      }
 +
 +      if (uses_frags) {
 +              napi_gro_frags(napi);
 +      } else {
 +              skb->protocol = eth_type_trans(skb, skb->dev);
 +              napi_gro_receive(napi, skb);
 +      }
 +}
 +
  static void qeth_create_skb_frag(struct sk_buff *skb, char *data, int data_len)
  {
        struct page *page = virt_to_page(data);
@@@ -5178,20 -5064,17 +5167,20 @@@ static inline int qeth_is_last_sbale(st
        return (sbale->eflags & SBAL_EFLAGS_LAST_ENTRY);
  }
  
 -struct sk_buff *qeth_core_get_next_skb(struct qeth_card *card,
 -              struct qeth_qdio_buffer *qethbuffer,
 -              struct qdio_buffer_element **__element, int *__offset,
 -              struct qeth_hdr **hdr)
 +static int qeth_extract_skb(struct qeth_card *card,
 +                          struct qeth_qdio_buffer *qethbuffer,
 +                          struct qdio_buffer_element **__element,
 +                          int *__offset)
  {
        struct qdio_buffer_element *element = *__element;
        struct qdio_buffer *buffer = qethbuffer->buffer;
 +      struct napi_struct *napi = &card->napi;
        unsigned int linear_len = 0;
 +      bool uses_frags = false;
        int offset = *__offset;
        bool use_rx_sg = false;
        unsigned int headroom;
 +      struct qeth_hdr *hdr;
        struct sk_buff *skb;
        int skb_len = 0;
  
@@@ -5199,42 -5082,42 +5188,42 @@@ next_packet
        /* qeth_hdr must not cross element boundaries */
        while (element->length < offset + sizeof(struct qeth_hdr)) {
                if (qeth_is_last_sbale(element))
 -                      return NULL;
 +                      return -ENODATA;
                element++;
                offset = 0;
        }
 -      *hdr = element->addr + offset;
  
 -      offset += sizeof(struct qeth_hdr);
 +      hdr = element->addr + offset;
 +      offset += sizeof(*hdr);
        skb = NULL;
  
 -      switch ((*hdr)->hdr.l2.id) {
 +      switch (hdr->hdr.l2.id) {
        case QETH_HEADER_TYPE_LAYER2:
 -              skb_len = (*hdr)->hdr.l2.pkt_length;
 +              skb_len = hdr->hdr.l2.pkt_length;
                linear_len = ETH_HLEN;
                headroom = 0;
                break;
        case QETH_HEADER_TYPE_LAYER3:
 -              skb_len = (*hdr)->hdr.l3.length;
 +              skb_len = hdr->hdr.l3.length;
                if (!IS_LAYER3(card)) {
                        QETH_CARD_STAT_INC(card, rx_dropped_notsupp);
                        goto walk_packet;
                }
  
 -              if ((*hdr)->hdr.l3.flags & QETH_HDR_PASSTHRU) {
 +              if (hdr->hdr.l3.flags & QETH_HDR_PASSTHRU) {
                        linear_len = ETH_HLEN;
                        headroom = 0;
                        break;
                }
  
 -              if ((*hdr)->hdr.l3.flags & QETH_HDR_IPV6)
 +              if (hdr->hdr.l3.flags & QETH_HDR_IPV6)
                        linear_len = sizeof(struct ipv6hdr);
                else
                        linear_len = sizeof(struct iphdr);
                headroom = ETH_HLEN;
                break;
        case QETH_HEADER_TYPE_OSN:
 -              skb_len = (*hdr)->hdr.osn.pdu_length;
 +              skb_len = hdr->hdr.osn.pdu_length;
                if (!IS_OSN(card)) {
                        QETH_CARD_STAT_INC(card, rx_dropped_notsupp);
                        goto walk_packet;
                headroom = sizeof(struct qeth_hdr);
                break;
        default:
 -              if ((*hdr)->hdr.l2.id & QETH_HEADER_MASK_INVAL)
 +              if (hdr->hdr.l2.id & QETH_HEADER_MASK_INVAL)
                        QETH_CARD_STAT_INC(card, rx_frame_errors);
                else
                        QETH_CARD_STAT_INC(card, rx_dropped_notsupp);
  
                /* Can't determine packet length, drop the whole buffer. */
 -              return NULL;
 +              return -EPROTONOSUPPORT;
        }
  
        if (skb_len < linear_len) {
                     !atomic_read(&card->force_alloc_skb) &&
                     !IS_OSN(card));
  
 -      if (use_rx_sg && qethbuffer->rx_skb) {
 +      if (use_rx_sg) {
                /* QETH_CQ_ENABLED only: */
 -              skb = qethbuffer->rx_skb;
 -              qethbuffer->rx_skb = NULL;
 -      } else {
 -              if (!use_rx_sg)
 -                      linear_len = skb_len;
 -              skb = napi_alloc_skb(&card->napi, linear_len + headroom);
 +              if (qethbuffer->rx_skb &&
 +                  skb_tailroom(qethbuffer->rx_skb) >= linear_len + headroom) {
 +                      skb = qethbuffer->rx_skb;
 +                      qethbuffer->rx_skb = NULL;
 +                      goto use_skb;
 +              }
 +
 +              skb = napi_get_frags(napi);
 +              if (!skb) {
 +                      /* -ENOMEM, no point in falling back further. */
 +                      QETH_CARD_STAT_INC(card, rx_dropped_nomem);
 +                      goto walk_packet;
 +              }
 +
 +              if (skb_tailroom(skb) >= linear_len + headroom) {
 +                      uses_frags = true;
 +                      goto use_skb;
 +              }
 +
 +              netdev_info_once(card->dev,
 +                               "Insufficient linear space in NAPI frags skb, need %u but have %u\n",
 +                               linear_len + headroom, skb_tailroom(skb));
 +              /* Shouldn't happen. Don't optimize, fall back to linear skb. */
        }
  
 -      if (!skb)
 +      linear_len = skb_len;
 +      skb = napi_alloc_skb(napi, linear_len + headroom);
 +      if (!skb) {
                QETH_CARD_STAT_INC(card, rx_dropped_nomem);
 -      else if (headroom)
 -              skb_reserve(skb, headroom);
 +              goto walk_packet;
 +      }
  
 +use_skb:
 +      if (headroom)
 +              skb_reserve(skb, headroom);
  walk_packet:
        while (skb_len) {
                int data_len = min(skb_len, (int)(element->length - offset));
                                QETH_CARD_TEXT(card, 4, "unexeob");
                                QETH_CARD_HEX(card, 2, buffer, sizeof(void *));
                                if (skb) {
 -                                      dev_kfree_skb_any(skb);
 +                                      if (uses_frags)
 +                                              napi_free_frags(napi);
 +                                      else
 +                                              dev_kfree_skb_any(skb);
                                        QETH_CARD_STAT_INC(card,
                                                           rx_length_errors);
                                }
 -                              return NULL;
 +                              return -EMSGSIZE;
                        }
                        element++;
                        offset = 0;
  
        *__element = element;
        *__offset = offset;
 -      if (use_rx_sg) {
 -              QETH_CARD_STAT_INC(card, rx_sg_skbs);
 -              QETH_CARD_STAT_ADD(card, rx_sg_frags,
 -                                 skb_shinfo(skb)->nr_frags);
 +
 +      qeth_receive_skb(card, skb, hdr, uses_frags);
 +      return 0;
 +}
 +
 +static int qeth_extract_skbs(struct qeth_card *card, int budget,
 +                           struct qeth_qdio_buffer *buf, bool *done)
 +{
 +      int work_done = 0;
 +
 +      WARN_ON_ONCE(!budget);
 +      *done = false;
 +
 +      while (budget) {
 +              if (qeth_extract_skb(card, buf, &card->rx.b_element,
 +                                   &card->rx.e_offset)) {
 +                      *done = true;
 +                      break;
 +              }
 +
 +              work_done++;
 +              budget--;
        }
 -      return skb;
 +
 +      return work_done;
  }
 -EXPORT_SYMBOL_GPL(qeth_core_get_next_skb);
  
  int qeth_poll(struct napi_struct *napi, int budget)
  {
        struct qeth_card *card = container_of(napi, struct qeth_card, napi);
        int work_done = 0;
        struct qeth_qdio_buffer *buffer;
 -      int done;
        int new_budget = budget;
 +      bool done;
  
        while (1) {
                if (!card->rx.b_count) {
                        if (!(card->rx.qdio_err &&
                            qeth_check_qdio_errors(card, buffer->buffer,
                            card->rx.qdio_err, "qinerr")))
 -                              work_done +=
 -                                      card->discipline->process_rx_buffer(
 -                                              card, new_budget, &done);
 +                              work_done += qeth_extract_skbs(card, new_budget,
 +                                                             buffer, &done);
                        else
 -                              done = 1;
 +                              done = true;
  
                        if (done) {
                                QETH_CARD_STAT_INC(card, rx_bufs);
@@@ -5580,9 -5421,9 +5569,9 @@@ int qeth_setassparms_cb(struct qeth_car
  
        cmd->hdr.return_code = cmd->data.setassparms.hdr.return_code;
        if (cmd->hdr.prot_version == QETH_PROT_IPV4)
 -              card->options.ipa4.enabled_funcs = cmd->hdr.ipa_enabled;
 +              card->options.ipa4.enabled = cmd->hdr.assists.enabled;
        if (cmd->hdr.prot_version == QETH_PROT_IPV6)
 -              card->options.ipa6.enabled_funcs = cmd->hdr.ipa_enabled;
 +              card->options.ipa6.enabled = cmd->hdr.assists.enabled;
        return 0;
  }
  EXPORT_SYMBOL_GPL(qeth_setassparms_cb);
@@@ -287,17 -287,56 +287,17 @@@ static void qeth_l2_stop_card(struct qe
                card->state = CARD_STATE_HARDSETUP;
        }
        if (card->state == CARD_STATE_HARDSETUP) {
-               qeth_qdio_clear_card(card, 0);
                qeth_drain_output_queues(card);
                qeth_clear_working_pool_list(card);
                card->state = CARD_STATE_DOWN;
        }
  
+       qeth_qdio_clear_card(card, 0);
        flush_workqueue(card->event_wq);
        card->info.mac_bits &= ~QETH_LAYER2_MAC_REGISTERED;
        card->info.promisc_mode = 0;
  }
  
 -static int qeth_l2_process_inbound_buffer(struct qeth_card *card,
 -                              int budget, int *done)
 -{
 -      int work_done = 0;
 -      struct sk_buff *skb;
 -      struct qeth_hdr *hdr;
 -      unsigned int len;
 -
 -      *done = 0;
 -      WARN_ON_ONCE(!budget);
 -      while (budget) {
 -              skb = qeth_core_get_next_skb(card,
 -                      &card->qdio.in_q->bufs[card->rx.b_index],
 -                      &card->rx.b_element, &card->rx.e_offset, &hdr);
 -              if (!skb) {
 -                      *done = 1;
 -                      break;
 -              }
 -
 -              if (hdr->hdr.l2.id == QETH_HEADER_TYPE_LAYER2) {
 -                      skb->protocol = eth_type_trans(skb, skb->dev);
 -                      qeth_rx_csum(card, skb, hdr->hdr.l2.flags[1]);
 -                      len = skb->len;
 -                      napi_gro_receive(&card->napi, skb);
 -              } else {
 -                      skb_push(skb, sizeof(*hdr));
 -                      skb_copy_to_linear_data(skb, hdr, sizeof(*hdr));
 -                      len = skb->len;
 -                      card->osn_info.data_cb(skb);
 -              }
 -
 -              work_done++;
 -              budget--;
 -              QETH_CARD_STAT_INC(card, rx_packets);
 -              QETH_CARD_STAT_ADD(card, rx_bytes, len);
 -      }
 -      return work_done;
 -}
 -
  static int qeth_l2_request_initial_mac(struct qeth_card *card)
  {
        int rc = 0;
@@@ -922,6 -961,7 +922,6 @@@ static int qeth_l2_control_event(struc
  
  struct qeth_discipline qeth_l2_discipline = {
        .devtype = &qeth_l2_devtype,
 -      .process_rx_buffer = qeth_l2_process_inbound_buffer,
        .recover = qeth_l2_recover,
        .setup = qeth_l2_probe_device,
        .remove = qeth_l2_remove_device,
@@@ -1912,8 -1952,7 +1912,7 @@@ int qeth_l2_vnicc_get_timeout(struct qe
  /* check if VNICC is currently enabled */
  bool qeth_l2_vnicc_is_in_use(struct qeth_card *card)
  {
-       /* if everything is turned off, VNICC is not active */
-       if (!card->options.vnicc.cur_chars)
+       if (!card->options.vnicc.sup_chars)
                return false;
        /* default values are only OK if rx_bcast was not enabled by user
         * or the card is offline.
@@@ -2000,8 -2039,9 +1999,9 @@@ static void qeth_l2_vnicc_init(struct q
        /* enforce assumed default values and recover settings, if changed  */
        error |= qeth_l2_vnicc_recover_timeout(card, QETH_VNICC_LEARNING,
                                               timeout);
-       chars_tmp = card->options.vnicc.wanted_chars ^ QETH_VNICC_DEFAULT;
-       chars_tmp |= QETH_VNICC_BRIDGE_INVISIBLE;
+       /* Change chars, if necessary  */
+       chars_tmp = card->options.vnicc.wanted_chars ^
+                   card->options.vnicc.cur_chars;
        chars_len = sizeof(card->options.vnicc.wanted_chars) * BITS_PER_BYTE;
        for_each_set_bit(i, &chars_tmp, chars_len) {
                vnicc = BIT(i);
@@@ -44,13 -44,23 +44,13 @@@ static int qeth_l3_register_addr_entry(
  static int qeth_l3_deregister_addr_entry(struct qeth_card *,
                struct qeth_ipaddr *);
  
 -static void qeth_l3_ipaddr4_to_string(const __u8 *addr, char *buf)
 -{
 -      sprintf(buf, "%pI4", addr);
 -}
 -
 -static void qeth_l3_ipaddr6_to_string(const __u8 *addr, char *buf)
 -{
 -      sprintf(buf, "%pI6", addr);
 -}
 -
 -void qeth_l3_ipaddr_to_string(enum qeth_prot_versions proto, const __u8 *addr,
 -                              char *buf)
 +int qeth_l3_ipaddr_to_string(enum qeth_prot_versions proto, const u8 *addr,
 +                           char *buf)
  {
        if (proto == QETH_PROT_IPV4)
 -              qeth_l3_ipaddr4_to_string(addr, buf);
 -      else if (proto == QETH_PROT_IPV6)
 -              qeth_l3_ipaddr6_to_string(addr, buf);
 +              return sprintf(buf, "%pI4", addr);
 +      else
 +              return sprintf(buf, "%pI6", addr);
  }
  
  static struct qeth_ipaddr *qeth_l3_find_addr_by_ip(struct qeth_card *card,
@@@ -151,6 -161,8 +151,6 @@@ static int qeth_l3_delete_ip(struct qet
        addr->ref_counter--;
        if (addr->type == QETH_IP_TYPE_NORMAL && addr->ref_counter > 0)
                return rc;
 -      if (addr->in_progress)
 -              return -EINPROGRESS;
  
        if (qeth_card_hw_is_reachable(card))
                rc = qeth_l3_deregister_addr_entry(card, addr);
@@@ -211,10 -223,29 +211,10 @@@ static int qeth_l3_add_ip(struct qeth_c
                        return 0;
                }
  
 -              /* qeth_l3_register_addr_entry can go to sleep
 -               * if we add a IPV4 addr. It is caused by the reason
 -               * that SETIP ipa cmd starts ARP staff for IPV4 addr.
 -               * Thus we should unlock spinlock, and make a protection
 -               * using in_progress variable to indicate that there is
 -               * an hardware operation with this IPV4 address
 -               */
 -              if (addr->proto == QETH_PROT_IPV4) {
 -                      addr->in_progress = 1;
 -                      mutex_unlock(&card->ip_lock);
 -                      rc = qeth_l3_register_addr_entry(card, addr);
 -                      mutex_lock(&card->ip_lock);
 -                      addr->in_progress = 0;
 -              } else
 -                      rc = qeth_l3_register_addr_entry(card, addr);
 +              rc = qeth_l3_register_addr_entry(card, addr);
  
                if (!rc || rc == -EADDRINUSE || rc == -ENETDOWN) {
                        addr->disp_flag = QETH_DISP_ADDR_DO_NOTHING;
 -                      if (addr->ref_counter < 1) {
 -                              qeth_l3_deregister_addr_entry(card, addr);
 -                              hash_del(&addr->hnode);
 -                              kfree(addr);
 -                      }
                } else {
                        hash_del(&addr->hnode);
                        kfree(addr);
@@@ -282,10 -313,19 +282,10 @@@ static void qeth_l3_recover_ip(struct q
  
        hash_for_each_safe(card->ip_htable, i, tmp, addr, hnode) {
                if (addr->disp_flag == QETH_DISP_ADDR_ADD) {
 -                      if (addr->proto == QETH_PROT_IPV4) {
 -                              addr->in_progress = 1;
 -                              mutex_unlock(&card->ip_lock);
 -                              rc = qeth_l3_register_addr_entry(card, addr);
 -                              mutex_lock(&card->ip_lock);
 -                              addr->in_progress = 0;
 -                      } else
 -                              rc = qeth_l3_register_addr_entry(card, addr);
 +                      rc = qeth_l3_register_addr_entry(card, addr);
  
                        if (!rc) {
                                addr->disp_flag = QETH_DISP_ADDR_DO_NOTHING;
 -                              if (addr->ref_counter < 1)
 -                                      qeth_l3_delete_ip(card, addr);
                        } else {
                                hash_del(&addr->hnode);
                                kfree(addr);
@@@ -339,16 -379,17 +339,16 @@@ static int qeth_l3_send_setdelmc(struc
        return qeth_send_ipa_cmd(card, iob, qeth_l3_setdelip_cb, NULL);
  }
  
 -static void qeth_l3_fill_netmask(u8 *netmask, unsigned int len)
 +static void qeth_l3_set_ipv6_prefix(struct in6_addr *prefix, unsigned int len)
  {
 -      int i, j;
 -      for (i = 0; i < 16; i++) {
 -              j = (len) - (i * 8);
 -              if (j >= 8)
 -                      netmask[i] = 0xff;
 -              else if (j > 0)
 -                      netmask[i] = (u8)(0xFF00 >> j);
 -              else
 -                      netmask[i] = 0;
 +      unsigned int i = 0;
 +
 +      while (len && i < 4) {
 +              int mask_len = min_t(int, len, 32);
 +
 +              prefix->s6_addr32[i] = inet_make_mask(mask_len);
 +              len -= mask_len;
 +              i++;
        }
  }
  
@@@ -371,6 -412,7 +371,6 @@@ static int qeth_l3_send_setdelip(struc
  {
        struct qeth_cmd_buffer *iob;
        struct qeth_ipa_cmd *cmd;
 -      __u8 netmask[16];
        u32 flags;
  
        QETH_CARD_TEXT(card, 4, "setdelip");
        QETH_CARD_TEXT_(card, 4, "flags%02X", flags);
  
        if (addr->proto == QETH_PROT_IPV6) {
 -              memcpy(cmd->data.setdelip6.ip_addr, &addr->u.a6.addr,
 -                     sizeof(struct in6_addr));
 -              qeth_l3_fill_netmask(netmask, addr->u.a6.pfxlen);
 -              memcpy(cmd->data.setdelip6.mask, netmask,
 -                     sizeof(struct in6_addr));
 +              cmd->data.setdelip6.addr = addr->u.a6.addr;
 +              qeth_l3_set_ipv6_prefix(&cmd->data.setdelip6.prefix,
 +                                      addr->u.a6.pfxlen);
                cmd->data.setdelip6.flags = flags;
        } else {
 -              memcpy(cmd->data.setdelip4.ip_addr, &addr->u.a4.addr, 4);
 -              memcpy(cmd->data.setdelip4.mask, &addr->u.a4.mask, 4);
 +              cmd->data.setdelip4.addr = addr->u.a4.addr;
 +              cmd->data.setdelip4.mask = addr->u.a4.mask;
                cmd->data.setdelip4.flags = flags;
        }
  
@@@ -537,7 -581,6 +537,7 @@@ int qeth_l3_add_ipato_entry(struct qeth
  
        QETH_CARD_TEXT(card, 2, "addipato");
  
 +      mutex_lock(&card->conf_mutex);
        mutex_lock(&card->ip_lock);
  
        list_for_each_entry(ipatoe, &card->ipato.entries, entry) {
        }
  
        mutex_unlock(&card->ip_lock);
 +      mutex_unlock(&card->conf_mutex);
  
        return rc;
  }
@@@ -571,7 -613,6 +571,7 @@@ int qeth_l3_del_ipato_entry(struct qeth
  
        QETH_CARD_TEXT(card, 2, "delipato");
  
 +      mutex_lock(&card->conf_mutex);
        mutex_lock(&card->ip_lock);
  
        list_for_each_entry_safe(ipatoe, tmp, &card->ipato.entries, entry) {
        }
  
        mutex_unlock(&card->ip_lock);
 +      mutex_unlock(&card->conf_mutex);
 +
        return rc;
  }
  
@@@ -598,7 -637,6 +598,7 @@@ int qeth_l3_modify_rxip_vipa(struct qet
                             enum qeth_prot_versions proto)
  {
        struct qeth_ipaddr addr;
 +      int rc;
  
        qeth_l3_init_ipaddr(&addr, type, proto);
        if (proto == QETH_PROT_IPV4)
        else
                memcpy(&addr.u.a6.addr, ip, 16);
  
 -      return qeth_l3_modify_ip(card, &addr, add);
 +      mutex_lock(&card->conf_mutex);
 +      rc = qeth_l3_modify_ip(card, &addr, add);
 +      mutex_unlock(&card->conf_mutex);
 +
 +      return rc;
  }
  
  int qeth_l3_modify_hsuid(struct qeth_card *card, bool add)
@@@ -1164,6 -1198,96 +1164,6 @@@ static int qeth_l3_vlan_rx_kill_vid(str
        return 0;
  }
  
 -static void qeth_l3_rebuild_skb(struct qeth_card *card, struct sk_buff *skb,
 -                              struct qeth_hdr *hdr)
 -{
 -      struct af_iucv_trans_hdr *iucv = (struct af_iucv_trans_hdr *) skb->data;
 -      struct net_device *dev = skb->dev;
 -
 -      if (IS_IQD(card) && iucv->magic == ETH_P_AF_IUCV) {
 -              dev_hard_header(skb, dev, ETH_P_AF_IUCV, dev->dev_addr,
 -                              "FAKELL", skb->len);
 -              return;
 -      }
 -
 -      if (!(hdr->hdr.l3.flags & QETH_HDR_PASSTHRU)) {
 -              u16 prot = (hdr->hdr.l3.flags & QETH_HDR_IPV6) ? ETH_P_IPV6 :
 -                                                               ETH_P_IP;
 -              unsigned char tg_addr[ETH_ALEN];
 -
 -              skb_reset_network_header(skb);
 -              switch (hdr->hdr.l3.flags & QETH_HDR_CAST_MASK) {
 -              case QETH_CAST_MULTICAST:
 -                      if (prot == ETH_P_IP)
 -                              ip_eth_mc_map(ip_hdr(skb)->daddr, tg_addr);
 -                      else
 -                              ipv6_eth_mc_map(&ipv6_hdr(skb)->daddr, tg_addr);
 -                      QETH_CARD_STAT_INC(card, rx_multicast);
 -                      break;
 -              case QETH_CAST_BROADCAST:
 -                      ether_addr_copy(tg_addr, card->dev->broadcast);
 -                      QETH_CARD_STAT_INC(card, rx_multicast);
 -                      break;
 -              default:
 -                      if (card->options.sniffer)
 -                              skb->pkt_type = PACKET_OTHERHOST;
 -                      ether_addr_copy(tg_addr, card->dev->dev_addr);
 -              }
 -
 -              if (hdr->hdr.l3.ext_flags & QETH_HDR_EXT_SRC_MAC_ADDR)
 -                      card->dev->header_ops->create(skb, card->dev, prot,
 -                              tg_addr, &hdr->hdr.l3.next_hop.rx.src_mac,
 -                              skb->len);
 -              else
 -                      card->dev->header_ops->create(skb, card->dev, prot,
 -                              tg_addr, "FAKELL", skb->len);
 -      }
 -
 -      /* copy VLAN tag from hdr into skb */
 -      if (!card->options.sniffer &&
 -          (hdr->hdr.l3.ext_flags & (QETH_HDR_EXT_VLAN_FRAME |
 -                                    QETH_HDR_EXT_INCLUDE_VLAN_TAG))) {
 -              u16 tag = (hdr->hdr.l3.ext_flags & QETH_HDR_EXT_VLAN_FRAME) ?
 -                              hdr->hdr.l3.vlan_id :
 -                              hdr->hdr.l3.next_hop.rx.vlan_id;
 -              __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), tag);
 -      }
 -
 -      qeth_rx_csum(card, skb, hdr->hdr.l3.ext_flags);
 -}
 -
 -static int qeth_l3_process_inbound_buffer(struct qeth_card *card,
 -                              int budget, int *done)
 -{
 -      int work_done = 0;
 -      struct sk_buff *skb;
 -      struct qeth_hdr *hdr;
 -
 -      *done = 0;
 -      WARN_ON_ONCE(!budget);
 -      while (budget) {
 -              skb = qeth_core_get_next_skb(card,
 -                      &card->qdio.in_q->bufs[card->rx.b_index],
 -                      &card->rx.b_element, &card->rx.e_offset, &hdr);
 -              if (!skb) {
 -                      *done = 1;
 -                      break;
 -              }
 -
 -              if (hdr->hdr.l3.id == QETH_HEADER_TYPE_LAYER3)
 -                      qeth_l3_rebuild_skb(card, skb, hdr);
 -
 -              skb->protocol = eth_type_trans(skb, skb->dev);
 -              QETH_CARD_STAT_INC(card, rx_packets);
 -              QETH_CARD_STAT_ADD(card, rx_bytes, skb->len);
 -
 -              napi_gro_receive(&card->napi, skb);
 -              work_done++;
 -              budget--;
 -      }
 -      return work_done;
 -}
 -
  static void qeth_l3_stop_card(struct qeth_card *card)
  {
        QETH_CARD_TEXT(card, 2, "stopcard");
                card->state = CARD_STATE_HARDSETUP;
        }
        if (card->state == CARD_STATE_HARDSETUP) {
-               qeth_qdio_clear_card(card, 0);
                qeth_drain_output_queues(card);
                qeth_clear_working_pool_list(card);
                card->state = CARD_STATE_DOWN;
        }
  
+       qeth_qdio_clear_card(card, 0);
        flush_workqueue(card->event_wq);
        card->info.promisc_mode = 0;
  }
@@@ -2169,6 -2293,12 +2169,6 @@@ static int __qeth_l3_set_offline(struc
        rtnl_unlock();
  
        qeth_l3_stop_card(card);
 -      if (card->options.cq == QETH_CQ_ENABLED) {
 -              rtnl_lock();
 -              call_netdevice_notifiers(NETDEV_REBOOT, card->dev);
 -              rtnl_unlock();
 -      }
 -
        rc  = qeth_stop_channel(&card->data);
        rc2 = qeth_stop_channel(&card->write);
        rc3 = qeth_stop_channel(&card->read);
@@@ -2227,6 -2357,7 +2227,6 @@@ static int qeth_l3_control_event(struc
  
  struct qeth_discipline qeth_l3_discipline = {
        .devtype = &qeth_l3_devtype,
 -      .process_rx_buffer = qeth_l3_process_inbound_buffer,
        .recover = qeth_l3_recover,
        .setup = qeth_l3_probe_device,
        .remove = qeth_l3_remove_device,
@@@ -2306,7 -2437,7 +2306,7 @@@ static int qeth_l3_ip_event(struct noti
  
        qeth_l3_init_ipaddr(&addr, QETH_IP_TYPE_NORMAL, QETH_PROT_IPV4);
        addr.u.a4.addr = ifa->ifa_address;
 -      addr.u.a4.mask = be32_to_cpu(ifa->ifa_mask);
 +      addr.u.a4.mask = ifa->ifa_mask;
  
        return qeth_l3_handle_ip_event(card, &addr, event);
  }
@@@ -242,21 -242,33 +242,33 @@@ static ssize_t qeth_l3_dev_hsuid_store(
                struct device_attribute *attr, const char *buf, size_t count)
  {
        struct qeth_card *card = dev_get_drvdata(dev);
+       int rc = 0;
        char *tmp;
-       int rc;
  
        if (!IS_IQD(card))
                return -EPERM;
-       if (card->state != CARD_STATE_DOWN)
-               return -EPERM;
-       if (card->options.sniffer)
-               return -EPERM;
-       if (card->options.cq == QETH_CQ_NOTAVAILABLE)
-               return -EPERM;
+       mutex_lock(&card->conf_mutex);
+       if (card->state != CARD_STATE_DOWN) {
+               rc = -EPERM;
+               goto out;
+       }
+       if (card->options.sniffer) {
+               rc = -EPERM;
+               goto out;
+       }
+       if (card->options.cq == QETH_CQ_NOTAVAILABLE) {
+               rc = -EPERM;
+               goto out;
+       }
  
        tmp = strsep((char **)&buf, "\n");
-       if (strlen(tmp) > 8)
-               return -EINVAL;
+       if (strlen(tmp) > 8) {
+               rc = -EINVAL;
+               goto out;
+       }
  
        if (card->options.hsuid[0])
                /* delete old ip address */
                card->options.hsuid[0] = '\0';
                memcpy(card->dev->perm_addr, card->options.hsuid, 9);
                qeth_configure_cq(card, QETH_CQ_DISABLED);
-               return count;
+               goto out;
        }
  
-       if (qeth_configure_cq(card, QETH_CQ_ENABLED))
-               return -EPERM;
+       if (qeth_configure_cq(card, QETH_CQ_ENABLED)) {
+               rc = -EPERM;
+               goto out;
+       }
  
        snprintf(card->options.hsuid, sizeof(card->options.hsuid),
                 "%-8s", tmp);
  
        rc = qeth_l3_modify_hsuid(card, true);
  
+ out:
+       mutex_unlock(&card->conf_mutex);
        return rc ? rc : count;
  }
  
@@@ -386,35 -402,30 +402,35 @@@ static ssize_t qeth_l3_dev_ipato_add_sh
                        enum qeth_prot_versions proto)
  {
        struct qeth_ipato_entry *ipatoe;
 -      char addr_str[40];
 -      int entry_len; /* length of 1 entry string, differs between v4 and v6 */
 -      int i = 0;
 +      int str_len = 0;
  
 -      entry_len = (proto == QETH_PROT_IPV4)? 12 : 40;
 -      /* add strlen for "/<mask>\n" */
 -      entry_len += (proto == QETH_PROT_IPV4)? 5 : 6;
        mutex_lock(&card->ip_lock);
        list_for_each_entry(ipatoe, &card->ipato.entries, entry) {
 +              char addr_str[40];
 +              int entry_len;
 +
                if (ipatoe->proto != proto)
                        continue;
 -              /* String must not be longer than PAGE_SIZE. So we check if
 -               * string length gets near PAGE_SIZE. Then we can savely display
 -               * the next IPv6 address (worst case, compared to IPv4) */
 -              if ((PAGE_SIZE - i) <= entry_len)
 +
 +              entry_len = qeth_l3_ipaddr_to_string(proto, ipatoe->addr,
 +                                                   addr_str);
 +              if (entry_len < 0)
 +                      continue;
 +
 +              /* Append /%mask to the entry: */
 +              entry_len += 1 + ((proto == QETH_PROT_IPV4) ? 2 : 3);
 +              /* Enough room to format %entry\n into null terminated page? */
 +              if (entry_len + 1 > PAGE_SIZE - str_len - 1)
                        break;
 -              qeth_l3_ipaddr_to_string(proto, ipatoe->addr, addr_str);
 -              i += snprintf(buf + i, PAGE_SIZE - i,
 -                            "%s/%i\n", addr_str, ipatoe->mask_bits);
 +
 +              entry_len = scnprintf(buf, PAGE_SIZE - str_len,
 +                                    "%s/%i\n", addr_str, ipatoe->mask_bits);
 +              str_len += entry_len;
 +              buf += entry_len;
        }
        mutex_unlock(&card->ip_lock);
 -      i += snprintf(buf + i, PAGE_SIZE - i, "\n");
  
 -      return i;
 +      return str_len ? str_len : scnprintf(buf, PAGE_SIZE, "\n");
  }
  
  static ssize_t qeth_l3_dev_ipato_add4_show(struct device *dev,
@@@ -460,14 -471,16 +476,14 @@@ static ssize_t qeth_l3_dev_ipato_add_st
        int mask_bits;
        int rc = 0;
  
 -      mutex_lock(&card->conf_mutex);
        rc = qeth_l3_parse_ipatoe(buf, proto, addr, &mask_bits);
        if (rc)
 -              goto out;
 +              return rc;
  
        ipatoe = kzalloc(sizeof(struct qeth_ipato_entry), GFP_KERNEL);
 -      if (!ipatoe) {
 -              rc = -ENOMEM;
 -              goto out;
 -      }
 +      if (!ipatoe)
 +              return -ENOMEM;
 +
        ipatoe->proto = proto;
        memcpy(ipatoe->addr, addr, (proto == QETH_PROT_IPV4)? 4:16);
        ipatoe->mask_bits = mask_bits;
        rc = qeth_l3_add_ipato_entry(card, ipatoe);
        if (rc)
                kfree(ipatoe);
 -out:
 -      mutex_unlock(&card->conf_mutex);
 +
        return rc ? rc : count;
  }
  
@@@ -498,9 -512,11 +514,9 @@@ static ssize_t qeth_l3_dev_ipato_del_st
        int mask_bits;
        int rc = 0;
  
 -      mutex_lock(&card->conf_mutex);
        rc = qeth_l3_parse_ipatoe(buf, proto, addr, &mask_bits);
        if (!rc)
                rc = qeth_l3_del_ipato_entry(card, proto, addr, mask_bits);
 -      mutex_unlock(&card->conf_mutex);
        return rc ? rc : count;
  }
  
@@@ -607,34 -623,31 +623,34 @@@ static ssize_t qeth_l3_dev_ip_add_show(
  {
        struct qeth_card *card = dev_get_drvdata(dev);
        struct qeth_ipaddr *ipaddr;
 -      char addr_str[40];
        int str_len = 0;
 -      int entry_len; /* length of 1 entry string, differs between v4 and v6 */
        int i;
  
 -      entry_len = (proto == QETH_PROT_IPV4)? 12 : 40;
 -      entry_len += 2; /* \n + terminator */
        mutex_lock(&card->ip_lock);
        hash_for_each(card->ip_htable, i, ipaddr, hnode) {
 +              char addr_str[40];
 +              int entry_len;
 +
                if (ipaddr->proto != proto || ipaddr->type != type)
                        continue;
 -              /* String must not be longer than PAGE_SIZE. So we check if
 -               * string length gets near PAGE_SIZE. Then we can savely display
 -               * the next IPv6 address (worst case, compared to IPv4) */
 -              if ((PAGE_SIZE - str_len) <= entry_len)
 +
 +              entry_len = qeth_l3_ipaddr_to_string(proto, (u8 *)&ipaddr->u,
 +                                                   addr_str);
 +              if (entry_len < 0)
 +                      continue;
 +
 +              /* Enough room to format %addr\n into null terminated page? */
 +              if (entry_len + 1 > PAGE_SIZE - str_len - 1)
                        break;
 -              qeth_l3_ipaddr_to_string(proto, (const u8 *)&ipaddr->u,
 -                      addr_str);
 -              str_len += snprintf(buf + str_len, PAGE_SIZE - str_len, "%s\n",
 -                                  addr_str);
 +
 +              entry_len = scnprintf(buf, PAGE_SIZE - str_len, "%s\n",
 +                                    addr_str);
 +              str_len += entry_len;
 +              buf += entry_len;
        }
        mutex_unlock(&card->ip_lock);
 -      str_len += snprintf(buf + str_len, PAGE_SIZE - str_len, "\n");
  
 -      return str_len;
 +      return str_len ? str_len : scnprintf(buf, PAGE_SIZE, "\n");
  }
  
  static ssize_t qeth_l3_dev_vipa_add4_show(struct device *dev,
                                       QETH_IP_TYPE_VIPA);
  }
  
 -static int qeth_l3_parse_vipae(const char *buf, enum qeth_prot_versions proto,
 -               u8 *addr)
 -{
 -      if (qeth_l3_string_to_ipaddr(buf, proto, addr)) {
 -              return -EINVAL;
 -      }
 -      return 0;
 -}
 -
 -static ssize_t qeth_l3_dev_vipa_add_store(const char *buf, size_t count,
 -                      struct qeth_card *card, enum qeth_prot_versions proto)
 +static ssize_t qeth_l3_vipa_store(struct device *dev, const char *buf, bool add,
 +                                size_t count, enum qeth_prot_versions proto)
  {
 +      struct qeth_card *card = dev_get_drvdata(dev);
        u8 addr[16] = {0, };
        int rc;
  
 -      mutex_lock(&card->conf_mutex);
 -      rc = qeth_l3_parse_vipae(buf, proto, addr);
 +      rc = qeth_l3_string_to_ipaddr(buf, proto, addr);
        if (!rc)
 -              rc = qeth_l3_modify_rxip_vipa(card, true, addr,
 +              rc = qeth_l3_modify_rxip_vipa(card, add, addr,
                                              QETH_IP_TYPE_VIPA, proto);
 -      mutex_unlock(&card->conf_mutex);
        return rc ? rc : count;
  }
  
  static ssize_t qeth_l3_dev_vipa_add4_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t count)
  {
 -      struct qeth_card *card = dev_get_drvdata(dev);
 -
 -      return qeth_l3_dev_vipa_add_store(buf, count, card, QETH_PROT_IPV4);
 +      return qeth_l3_vipa_store(dev, buf, true, count, QETH_PROT_IPV4);
  }
  
  static QETH_DEVICE_ATTR(vipa_add4, add4, 0644,
                        qeth_l3_dev_vipa_add4_show,
                        qeth_l3_dev_vipa_add4_store);
  
 -static ssize_t qeth_l3_dev_vipa_del_store(const char *buf, size_t count,
 -                       struct qeth_card *card, enum qeth_prot_versions proto)
 -{
 -      u8 addr[16];
 -      int rc;
 -
 -      mutex_lock(&card->conf_mutex);
 -      rc = qeth_l3_parse_vipae(buf, proto, addr);
 -      if (!rc)
 -              rc = qeth_l3_modify_rxip_vipa(card, false, addr,
 -                                            QETH_IP_TYPE_VIPA, proto);
 -      mutex_unlock(&card->conf_mutex);
 -      return rc ? rc : count;
 -}
 -
  static ssize_t qeth_l3_dev_vipa_del4_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t count)
  {
 -      struct qeth_card *card = dev_get_drvdata(dev);
 -
 -      return qeth_l3_dev_vipa_del_store(buf, count, card, QETH_PROT_IPV4);
 +      return qeth_l3_vipa_store(dev, buf, true, count, QETH_PROT_IPV4);
  }
  
  static QETH_DEVICE_ATTR(vipa_del4, del4, 0200, NULL,
@@@ -689,7 -731,9 +705,7 @@@ static ssize_t qeth_l3_dev_vipa_add6_sh
  static ssize_t qeth_l3_dev_vipa_add6_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t count)
  {
 -      struct qeth_card *card = dev_get_drvdata(dev);
 -
 -      return qeth_l3_dev_vipa_add_store(buf, count, card, QETH_PROT_IPV6);
 +      return qeth_l3_vipa_store(dev, buf, true, count, QETH_PROT_IPV6);
  }
  
  static QETH_DEVICE_ATTR(vipa_add6, add6, 0644,
  static ssize_t qeth_l3_dev_vipa_del6_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t count)
  {
 -      struct qeth_card *card = dev_get_drvdata(dev);
 -
 -      return qeth_l3_dev_vipa_del_store(buf, count, card, QETH_PROT_IPV6);
 +      return qeth_l3_vipa_store(dev, buf, false, count, QETH_PROT_IPV6);
  }
  
  static QETH_DEVICE_ATTR(vipa_del6, del6, 0200, NULL,
@@@ -752,34 -798,54 +768,34 @@@ static int qeth_l3_parse_rxipe(const ch
        return 0;
  }
  
 -static ssize_t qeth_l3_dev_rxip_add_store(const char *buf, size_t count,
 -                      struct qeth_card *card, enum qeth_prot_versions proto)
 +static ssize_t qeth_l3_rxip_store(struct device *dev, const char *buf, bool add,
 +                                size_t count, enum qeth_prot_versions proto)
  {
 +      struct qeth_card *card = dev_get_drvdata(dev);
        u8 addr[16] = {0, };
        int rc;
  
 -      mutex_lock(&card->conf_mutex);
        rc = qeth_l3_parse_rxipe(buf, proto, addr);
        if (!rc)
 -              rc = qeth_l3_modify_rxip_vipa(card, true, addr,
 +              rc = qeth_l3_modify_rxip_vipa(card, add, addr,
                                              QETH_IP_TYPE_RXIP, proto);
 -      mutex_unlock(&card->conf_mutex);
        return rc ? rc : count;
  }
  
  static ssize_t qeth_l3_dev_rxip_add4_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t count)
  {
 -      struct qeth_card *card = dev_get_drvdata(dev);
 -
 -      return qeth_l3_dev_rxip_add_store(buf, count, card, QETH_PROT_IPV4);
 +      return qeth_l3_rxip_store(dev, buf, true, count, QETH_PROT_IPV4);
  }
  
  static QETH_DEVICE_ATTR(rxip_add4, add4, 0644,
                        qeth_l3_dev_rxip_add4_show,
                        qeth_l3_dev_rxip_add4_store);
  
 -static ssize_t qeth_l3_dev_rxip_del_store(const char *buf, size_t count,
 -                      struct qeth_card *card, enum qeth_prot_versions proto)
 -{
 -      u8 addr[16];
 -      int rc;
 -
 -      mutex_lock(&card->conf_mutex);
 -      rc = qeth_l3_parse_rxipe(buf, proto, addr);
 -      if (!rc)
 -              rc = qeth_l3_modify_rxip_vipa(card, false, addr,
 -                                            QETH_IP_TYPE_RXIP, proto);
 -      mutex_unlock(&card->conf_mutex);
 -      return rc ? rc : count;
 -}
 -
  static ssize_t qeth_l3_dev_rxip_del4_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t count)
  {
 -      struct qeth_card *card = dev_get_drvdata(dev);
 -
 -      return qeth_l3_dev_rxip_del_store(buf, count, card, QETH_PROT_IPV4);
 +      return qeth_l3_rxip_store(dev, buf, false, count, QETH_PROT_IPV4);
  }
  
  static QETH_DEVICE_ATTR(rxip_del4, del4, 0200, NULL,
@@@ -796,7 -862,9 +812,7 @@@ static ssize_t qeth_l3_dev_rxip_add6_sh
  static ssize_t qeth_l3_dev_rxip_add6_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t count)
  {
 -      struct qeth_card *card = dev_get_drvdata(dev);
 -
 -      return qeth_l3_dev_rxip_add_store(buf, count, card, QETH_PROT_IPV6);
 +      return qeth_l3_rxip_store(dev, buf, true, count, QETH_PROT_IPV6);
  }
  
  static QETH_DEVICE_ATTR(rxip_add6, add6, 0644,
  static ssize_t qeth_l3_dev_rxip_del6_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t count)
  {
 -      struct qeth_card *card = dev_get_drvdata(dev);
 -
 -      return qeth_l3_dev_rxip_del_store(buf, count, card, QETH_PROT_IPV6);
 +      return qeth_l3_rxip_store(dev, buf, false, count, QETH_PROT_IPV6);
  }
  
  static QETH_DEVICE_ATTR(rxip_del6, del6, 0200, NULL,
diff --combined net/ipv6/route.c
@@@ -95,7 -95,8 +95,8 @@@ static int            ip6_pkt_prohibit(struct sk_
  static int            ip6_pkt_prohibit_out(struct net *net, struct sock *sk, struct sk_buff *skb);
  static void           ip6_link_failure(struct sk_buff *skb);
  static void           ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,
-                                          struct sk_buff *skb, u32 mtu);
+                                          struct sk_buff *skb, u32 mtu,
+                                          bool confirm_neigh);
  static void           rt6_do_redirect(struct dst_entry *dst, struct sock *sk,
                                        struct sk_buff *skb);
  static int rt6_score_route(const struct fib6_nh *nh, u32 fib6_flags, int oif,
@@@ -264,7 -265,8 +265,8 @@@ static unsigned int ip6_blackhole_mtu(c
  }
  
  static void ip6_rt_blackhole_update_pmtu(struct dst_entry *dst, struct sock *sk,
-                                        struct sk_buff *skb, u32 mtu)
+                                        struct sk_buff *skb, u32 mtu,
+                                        bool confirm_neigh)
  {
  }
  
@@@ -2692,7 -2694,8 +2694,8 @@@ static bool rt6_cache_allowed_for_pmtu(
  }
  
  static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
-                                const struct ipv6hdr *iph, u32 mtu)
+                                const struct ipv6hdr *iph, u32 mtu,
+                                bool confirm_neigh)
  {
        const struct in6_addr *daddr, *saddr;
        struct rt6_info *rt6 = (struct rt6_info *)dst;
                daddr = NULL;
                saddr = NULL;
        }
-       dst_confirm_neigh(dst, daddr);
+       if (confirm_neigh)
+               dst_confirm_neigh(dst, daddr);
        mtu = max_t(u32, mtu, IPV6_MIN_MTU);
        if (mtu >= dst_mtu(dst))
                return;
@@@ -2764,9 -2770,11 +2770,11 @@@ out_unlock
  }
  
  static void ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,
-                              struct sk_buff *skb, u32 mtu)
+                              struct sk_buff *skb, u32 mtu,
+                              bool confirm_neigh)
  {
-       __ip6_rt_update_pmtu(dst, sk, skb ? ipv6_hdr(skb) : NULL, mtu);
+       __ip6_rt_update_pmtu(dst, sk, skb ? ipv6_hdr(skb) : NULL, mtu,
+                            confirm_neigh);
  }
  
  void ip6_update_pmtu(struct sk_buff *skb, struct net *net, __be32 mtu,
  
        dst = ip6_route_output(net, NULL, &fl6);
        if (!dst->error)
-               __ip6_rt_update_pmtu(dst, NULL, iph, ntohl(mtu));
+               __ip6_rt_update_pmtu(dst, NULL, iph, ntohl(mtu), true);
        dst_release(dst);
  }
  EXPORT_SYMBOL_GPL(ip6_update_pmtu);
@@@ -3749,7 -3757,6 +3757,7 @@@ static int __ip6_del_rt_siblings(struc
  
        if (rt->fib6_nsiblings && cfg->fc_delete_all_nh) {
                struct fib6_info *sibling, *next_sibling;
 +              struct fib6_node *fn;
  
                /* prefer to send a single notification with all hops */
                skb = nlmsg_new(rt6_nlmsg_size(rt), gfp_any());
                                info->skip_notify = 1;
                }
  
 +              /* 'rt' points to the first sibling route. If it is not the
 +               * leaf, then we do not need to send a notification. Otherwise,
 +               * we need to check if the last sibling has a next route or not
 +               * and emit a replace or delete notification, respectively.
 +               */
                info->skip_notify_kernel = 1;
 -              call_fib6_multipath_entry_notifiers(net,
 -                                                  FIB_EVENT_ENTRY_DEL,
 -                                                  rt,
 -                                                  rt->fib6_nsiblings,
 -                                                  NULL);
 +              fn = rcu_dereference_protected(rt->fib6_node,
 +                                          lockdep_is_held(&table->tb6_lock));
 +              if (rcu_access_pointer(fn->leaf) == rt) {
 +                      struct fib6_info *last_sibling, *replace_rt;
 +
 +                      last_sibling = list_last_entry(&rt->fib6_siblings,
 +                                                     struct fib6_info,
 +                                                     fib6_siblings);
 +                      replace_rt = rcu_dereference_protected(
 +                                          last_sibling->fib6_next,
 +                                          lockdep_is_held(&table->tb6_lock));
 +                      if (replace_rt)
 +                              call_fib6_entry_notifiers_replace(net,
 +                                                                replace_rt);
 +                      else
 +                              call_fib6_multipath_entry_notifiers(net,
 +                                                     FIB_EVENT_ENTRY_DEL,
 +                                                     rt, rt->fib6_nsiblings,
 +                                                     NULL);
 +              }
                list_for_each_entry_safe(sibling, next_sibling,
                                         &rt->fib6_siblings,
                                         fib6_siblings) {
@@@ -5038,37 -5025,12 +5046,37 @@@ static void ip6_route_mpath_notify(stru
                inet6_rt_notify(RTM_NEWROUTE, rt, info, nlflags);
  }
  
 +static bool ip6_route_mpath_should_notify(const struct fib6_info *rt)
 +{
 +      bool rt_can_ecmp = rt6_qualify_for_ecmp(rt);
 +      bool should_notify = false;
 +      struct fib6_info *leaf;
 +      struct fib6_node *fn;
 +
 +      rcu_read_lock();
 +      fn = rcu_dereference(rt->fib6_node);
 +      if (!fn)
 +              goto out;
 +
 +      leaf = rcu_dereference(fn->leaf);
 +      if (!leaf)
 +              goto out;
 +
 +      if (rt == leaf ||
 +          (rt_can_ecmp && rt->fib6_metric == leaf->fib6_metric &&
 +           rt6_qualify_for_ecmp(leaf)))
 +              should_notify = true;
 +out:
 +      rcu_read_unlock();
 +
 +      return should_notify;
 +}
 +
  static int ip6_route_multipath_add(struct fib6_config *cfg,
                                   struct netlink_ext_ack *extack)
  {
        struct fib6_info *rt_notif = NULL, *rt_last = NULL;
        struct nl_info *info = &cfg->fc_nlinfo;
 -      enum fib_event_type event_type;
        struct fib6_config r_cfg;
        struct rtnexthop *rtnh;
        struct fib6_info *rt;
                nhn++;
        }
  
 -      event_type = replace ? FIB_EVENT_ENTRY_REPLACE : FIB_EVENT_ENTRY_ADD;
 -      err = call_fib6_multipath_entry_notifiers(info->nl_net, event_type,
 -                                                rt_notif, nhn - 1, extack);
 -      if (err) {
 -              /* Delete all the siblings that were just added */
 -              err_nh = NULL;
 -              goto add_errout;
 +      /* An in-kernel notification should only be sent in case the new
 +       * multipath route is added as the first route in the node, or if
 +       * it was appended to it. We pass 'rt_notif' since it is the first
 +       * sibling and might allow us to skip some checks in the replace case.
 +       */
 +      if (ip6_route_mpath_should_notify(rt_notif)) {
 +              enum fib_event_type fib_event;
 +
 +              if (rt_notif->fib6_nsiblings != nhn - 1)
 +                      fib_event = FIB_EVENT_ENTRY_APPEND;
 +              else
 +                      fib_event = FIB_EVENT_ENTRY_REPLACE;
 +
 +              err = call_fib6_multipath_entry_notifiers(info->nl_net,
 +                                                        fib_event, rt_notif,
 +                                                        nhn - 1, extack);
 +              if (err) {
 +                      /* Delete all the siblings that were just added */
 +                      err_nh = NULL;
 +                      goto add_errout;
 +              }
        }
  
        /* success ... tell user about new route */
diff --combined net/sctp/stream.c
@@@ -84,10 -84,8 +84,8 @@@ static int sctp_stream_alloc_out(struc
                return 0;
  
        ret = genradix_prealloc(&stream->out, outcnt, gfp);
-       if (ret) {
-               genradix_free(&stream->out);
+       if (ret)
                return ret;
-       }
  
        stream->outcnt = outcnt;
        return 0;
@@@ -102,10 -100,8 +100,8 @@@ static int sctp_stream_alloc_in(struct 
                return 0;
  
        ret = genradix_prealloc(&stream->in, incnt, gfp);
-       if (ret) {
-               genradix_free(&stream->in);
+       if (ret)
                return ret;
-       }
  
        stream->incnt = incnt;
        return 0;
@@@ -123,7 -119,7 +119,7 @@@ int sctp_stream_init(struct sctp_strea
         * a new one with new outcnt to save memory if needed.
         */
        if (outcnt == stream->outcnt)
-               goto in;
+               goto handle_in;
  
        /* Filter out chunks queued on streams that won't exist anymore */
        sched->unsched_all(stream);
  
        ret = sctp_stream_alloc_out(stream, outcnt, gfp);
        if (ret)
-               goto out;
+               goto out_err;
  
        for (i = 0; i < stream->outcnt; i++)
                SCTP_SO(stream, i)->state = SCTP_STREAM_OPEN;
  
- in:
handle_in:
        sctp_stream_interleave_init(stream);
        if (!incnt)
                goto out;
  
        ret = sctp_stream_alloc_in(stream, incnt, gfp);
-       if (ret) {
-               sched->free(stream);
-               genradix_free(&stream->out);
-               stream->outcnt = 0;
-               goto out;
-       }
+       if (ret)
+               goto in_err;
+       goto out;
  
+ in_err:
+       sched->free(stream);
+       genradix_free(&stream->in);
+ out_err:
+       genradix_free(&stream->out);
+       stream->outcnt = 0;
  out:
        return ret;
  }
@@@ -222,9 -222,10 +222,9 @@@ void sctp_stream_update(struct sctp_str
  static int sctp_send_reconf(struct sctp_association *asoc,
                            struct sctp_chunk *chunk)
  {
 -      struct net *net = sock_net(asoc->base.sk);
        int retval = 0;
  
 -      retval = sctp_primitive_RECONF(net, asoc, chunk);
 +      retval = sctp_primitive_RECONF(asoc->base.net, asoc, chunk);
        if (retval)
                sctp_chunk_free(chunk);
  
diff --combined net/sctp/transport.c
@@@ -263,7 -263,7 +263,7 @@@ bool sctp_transport_update_pmtu(struct 
  
                pf->af->from_sk(&addr, sk);
                pf->to_sk_daddr(&t->ipaddr, sk);
-               dst->ops->update_pmtu(dst, sk, NULL, pmtu);
+               dst->ops->update_pmtu(dst, sk, NULL, pmtu, true);
                pf->to_sk_daddr(&addr, sk);
  
                dst = sctp_transport_dst_check(t);
@@@ -334,7 -334,7 +334,7 @@@ void sctp_transport_update_rto(struct s
                pr_debug("%s: rto_pending not set on transport %p!\n", __func__, tp);
  
        if (tp->rttvar || tp->srtt) {
 -              struct net *net = sock_net(tp->asoc->base.sk);
 +              struct net *net = tp->asoc->base.net;
                /* 6.3.1 C3) When a new RTT measurement R' is made, set
                 * RTTVAR <- (1 - RTO.Beta) * RTTVAR + RTO.Beta * |SRTT - R'|
                 * SRTT <- (1 - RTO.Alpha) * SRTT + RTO.Alpha * R'
diff --combined tools/lib/bpf/Makefile
@@@ -56,8 -56,8 +56,8 @@@ ifndef VERBOS
  endif
  
  FEATURE_USER = .libbpf
 -FEATURE_TESTS = libelf libelf-mmap bpf reallocarray
 -FEATURE_DISPLAY = libelf bpf
 +FEATURE_TESTS = libelf libelf-mmap zlib bpf reallocarray
 +FEATURE_DISPLAY = libelf zlib bpf
  
  INCLUDES = -I. -I$(srctree)/tools/include -I$(srctree)/tools/arch/$(ARCH)/include/uapi -I$(srctree)/tools/include/uapi
  FEATURE_CHECK_CFLAGS-bpf = $(INCLUDES)
@@@ -138,6 -138,7 +138,7 @@@ STATIC_OBJDIR      := $(OUTPUT)staticobjs
  BPF_IN_SHARED := $(SHARED_OBJDIR)libbpf-in.o
  BPF_IN_STATIC := $(STATIC_OBJDIR)libbpf-in.o
  VERSION_SCRIPT        := libbpf.map
+ BPF_HELPER_DEFS       := $(OUTPUT)bpf_helper_defs.h
  
  LIB_TARGET    := $(addprefix $(OUTPUT),$(LIB_TARGET))
  LIB_FILE      := $(addprefix $(OUTPUT),$(LIB_FILE))
@@@ -147,7 -148,6 +148,7 @@@ TAGS_PROG := $(if $(shell which etags 2
  
  GLOBAL_SYM_COUNT = $(shell readelf -s --wide $(BPF_IN_SHARED) | \
                           cut -d "@" -f1 | sed 's/_v[0-9]_[0-9]_[0-9].*//' | \
 +                         sed 's/\[.*\]//' | \
                           awk '/GLOBAL/ && /DEFAULT/ && !/UND/ {print $$NF}' | \
                           sort -u | wc -l)
  VERSIONED_SYM_COUNT = $(shell readelf -s --wide $(OUTPUT)libbpf.so | \
@@@ -160,7 -160,7 +161,7 @@@ all: fixde
  
  all_cmd: $(CMD_TARGETS) check
  
- $(BPF_IN_SHARED): force elfdep zdep bpfdep bpf_helper_defs.h
 -$(BPF_IN_SHARED): force elfdep bpfdep $(BPF_HELPER_DEFS)
++$(BPF_IN_SHARED): force elfdep zdep bpfdep $(BPF_HELPER_DEFS)
        @(test -f ../../include/uapi/linux/bpf.h -a -f ../../../include/uapi/linux/bpf.h && ( \
        (diff -B ../../include/uapi/linux/bpf.h ../../../include/uapi/linux/bpf.h >/dev/null) || \
        echo "Warning: Kernel ABI header at 'tools/include/uapi/linux/bpf.h' differs from latest version at 'include/uapi/linux/bpf.h'" >&2 )) || true
        echo "Warning: Kernel ABI header at 'tools/include/uapi/linux/if_xdp.h' differs from latest version at 'include/uapi/linux/if_xdp.h'" >&2 )) || true
        $(Q)$(MAKE) $(build)=libbpf OUTPUT=$(SHARED_OBJDIR) CFLAGS="$(CFLAGS) $(SHLIB_FLAGS)"
  
- $(BPF_IN_STATIC): force elfdep zdep bpfdep bpf_helper_defs.h
 -$(BPF_IN_STATIC): force elfdep bpfdep $(BPF_HELPER_DEFS)
++$(BPF_IN_STATIC): force elfdep zdep bpfdep $(BPF_HELPER_DEFS)
        $(Q)$(MAKE) $(build)=libbpf OUTPUT=$(STATIC_OBJDIR)
  
bpf_helper_defs.h: $(srctree)/tools/include/uapi/linux/bpf.h
$(BPF_HELPER_DEFS): $(srctree)/tools/include/uapi/linux/bpf.h
        $(Q)$(srctree)/scripts/bpf_helpers_doc.py --header              \
-               --file $(srctree)/tools/include/uapi/linux/bpf.h > bpf_helper_defs.h
+               --file $(srctree)/tools/include/uapi/linux/bpf.h > $(BPF_HELPER_DEFS)
  
  $(OUTPUT)libbpf.so: $(OUTPUT)libbpf.so.$(LIBBPF_VERSION)
  
  $(OUTPUT)libbpf.so.$(LIBBPF_VERSION): $(BPF_IN_SHARED)
        $(QUIET_LINK)$(CC) $(LDFLAGS) \
                --shared -Wl,-soname,libbpf.so.$(LIBBPF_MAJOR_VERSION) \
 -              -Wl,--version-script=$(VERSION_SCRIPT) $^ -lelf -o $@
 +              -Wl,--version-script=$(VERSION_SCRIPT) $^ -lelf -lz -o $@
        @ln -sf $(@F) $(OUTPUT)libbpf.so
        @ln -sf $(@F) $(OUTPUT)libbpf.so.$(LIBBPF_MAJOR_VERSION)
  
@@@ -214,7 -214,6 +215,7 @@@ check_abi: $(OUTPUT)libbpf.s
                     "versioned in $(VERSION_SCRIPT)." >&2;              \
                readelf -s --wide $(BPF_IN_SHARED) |                     \
                    cut -d "@" -f1 | sed 's/_v[0-9]_[0-9]_[0-9].*//' |   \
 +                  sed 's/\[.*\]//' |                                   \
                    awk '/GLOBAL/ && /DEFAULT/ && !/UND/ {print $$NF}'|  \
                    sort -u > $(OUTPUT)libbpf_global_syms.tmp;           \
                readelf -s --wide $(OUTPUT)libbpf.so |                   \
@@@ -245,16 -244,15 +246,16 @@@ install_lib: all_cm
                $(call do_install_mkdir,$(libdir_SQ)); \
                cp -fpR $(LIB_FILE) $(DESTDIR)$(libdir_SQ)
  
- install_headers: bpf_helper_defs.h
+ install_headers: $(BPF_HELPER_DEFS)
        $(call QUIET_INSTALL, headers) \
                $(call do_install,bpf.h,$(prefix)/include/bpf,644); \
                $(call do_install,libbpf.h,$(prefix)/include/bpf,644); \
                $(call do_install,btf.h,$(prefix)/include/bpf,644); \
                $(call do_install,libbpf_util.h,$(prefix)/include/bpf,644); \
 +              $(call do_install,libbpf_common.h,$(prefix)/include/bpf,644); \
                $(call do_install,xsk.h,$(prefix)/include/bpf,644); \
                $(call do_install,bpf_helpers.h,$(prefix)/include/bpf,644); \
-               $(call do_install,bpf_helper_defs.h,$(prefix)/include/bpf,644); \
+               $(call do_install,$(BPF_HELPER_DEFS),$(prefix)/include/bpf,644); \
                $(call do_install,bpf_tracing.h,$(prefix)/include/bpf,644); \
                $(call do_install,bpf_endian.h,$(prefix)/include/bpf,644); \
                $(call do_install,bpf_core_read.h,$(prefix)/include/bpf,644);
@@@ -274,21 -272,18 +275,21 @@@ config-clean
  clean:
        $(call QUIET_CLEAN, libbpf) $(RM) -rf $(CMD_TARGETS) \
                *.o *~ *.a *.so *.so.$(LIBBPF_MAJOR_VERSION) .*.d .*.cmd \
-               *.pc LIBBPF-CFLAGS bpf_helper_defs.h \
+               *.pc LIBBPF-CFLAGS $(BPF_HELPER_DEFS) \
                $(SHARED_OBJDIR) $(STATIC_OBJDIR)
        $(call QUIET_CLEAN, core-gen) $(RM) $(OUTPUT)FEATURE-DUMP.libbpf
  
  
  
 -PHONY += force elfdep bpfdep cscope tags
 +PHONY += force elfdep zdep bpfdep cscope tags
  force:
  
  elfdep:
        @if [ "$(feature-libelf)" != "1" ]; then echo "No libelf found"; exit 1 ; fi
  
 +zdep:
 +      @if [ "$(feature-zlib)" != "1" ]; then echo "No zlib found"; exit 1 ; fi
 +
  bpfdep:
        @if [ "$(feature-bpf)" != "1" ]; then echo "BPF API too old"; exit 1 ; fi
  
@@@ -21,6 -21,7 +21,6 @@@ test_lirc_mode2_use
  get_cgroup_id_user
  test_skb_cgroup_id_user
  test_socket_cookie
 -test_cgroup_attach
  test_cgroup_storage
  test_select_reuseport
  test_flow_dissector
@@@ -37,7 -38,6 +37,8 @@@ test_hashma
  test_btf_dump
  xdping
  test_cpp
 +*.skel.h
  /no_alu32
  /bpf_gcc
 +/tools
+ bpf_helper_defs.h
@@@ -3,12 -3,10 +3,12 @@@ include ../../../../scripts/Kbuild.incl
  include ../../../scripts/Makefile.arch
  
  CURDIR := $(abspath .)
 -LIBDIR := $(abspath ../../../lib)
 +TOOLSDIR := $(abspath ../../..)
 +LIBDIR := $(TOOLSDIR)/lib
  BPFDIR := $(LIBDIR)/bpf
 -TOOLSDIR := $(abspath ../../../include)
 -APIDIR := $(TOOLSDIR)/uapi
 +TOOLSINCDIR := $(TOOLSDIR)/include
 +BPFTOOLDIR := $(TOOLSDIR)/bpf/bpftool
 +APIDIR := $(TOOLSINCDIR)/uapi
  GENDIR := $(abspath ../../../../include/generated)
  GENHDR := $(GENDIR)/autoconf.h
  
@@@ -21,18 -19,18 +21,18 @@@ LLC                ?= ll
  LLVM_OBJCOPY  ?= llvm-objcopy
  BPF_GCC               ?= $(shell command -v bpf-gcc;)
  CFLAGS += -g -Wall -O2 $(GENFLAGS) -I$(APIDIR) -I$(LIBDIR) -I$(BPFDIR)        \
 -        -I$(GENDIR) -I$(TOOLSDIR) -I$(CURDIR)                         \
 +        -I$(GENDIR) -I$(TOOLSINCDIR) -I$(CURDIR)                      \
          -Dbpf_prog_load=bpf_prog_test_load                            \
          -Dbpf_load_program=bpf_test_load_program
 -LDLIBS += -lcap -lelf -lrt -lpthread
 +LDLIBS += -lcap -lelf -lz -lrt -lpthread
  
  # Order correspond to 'make run_tests' order
  TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test_progs \
        test_align test_verifier_log test_dev_cgroup test_tcpbpf_user \
        test_sock test_btf test_sockmap get_cgroup_id_user test_socket_cookie \
 -      test_cgroup_storage test_select_reuseport \
 +      test_cgroup_storage \
        test_netcnt test_tcpnotify_user test_sock_fields test_sysctl test_hashmap \
 -      test_cgroup_attach test_progs-no_alu32
 +      test_progs-no_alu32
  
  # Also test bpf-gcc, if present
  ifneq ($(BPF_GCC),)
@@@ -77,24 -75,6 +77,24 @@@ TEST_GEN_PROGS_EXTENDED = test_sock_add
  
  TEST_CUSTOM_PROGS = urandom_read
  
 +# Emit succinct information message describing current building step
 +# $1 - generic step name (e.g., CC, LINK, etc);
 +# $2 - optional "flavor" specifier; if provided, will be emitted as [flavor];
 +# $3 - target (assumed to be file); only file name will be emitted;
 +# $4 - optional extra arg, emitted as-is, if provided.
 +ifeq ($(V),1)
 +msg =
 +else
 +msg = @$(info $(1)$(if $(2), [$(2)]) $(notdir $(3)))$(if $(4), $(4))
 +endif
 +
 +# override lib.mk's default rules
 +OVERRIDE_TARGETS := 1
 +override define CLEAN
 +      $(call msg,    CLEAN)
 +      $(RM) -r $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED) $(TEST_GEN_FILES) $(EXTRA_CLEAN)
 +endef
 +
  include ../lib.mk
  
  # Define simple and short `make test_progs`, `make test_sysctl`, etc targets
@@@ -107,16 -87,10 +107,16 @@@ $(notdir $(TEST_GEN_PROGS)                                               
         $(TEST_GEN_PROGS_EXTENDED)                                     \
         $(TEST_CUSTOM_PROGS)): %: $(OUTPUT)/% ;
  
 +$(OUTPUT)/%:%.c
 +      $(call msg,     BINARY,,$@)
 +      $(LINK.c) $^ $(LDLIBS) -o $@
 +
  $(OUTPUT)/urandom_read: urandom_read.c
 +      $(call msg,     BINARY,,$@)
        $(CC) -o $@ $< -Wl,--build-id
  
  $(OUTPUT)/test_stub.o: test_stub.c
 +      $(call msg,         CC,,$@)
        $(CC) -c $(CFLAGS) -o $@ $<
  
  BPFOBJ := $(OUTPUT)/libbpf.a
@@@ -136,24 -110,19 +136,24 @@@ $(OUTPUT)/test_cgroup_storage: cgroup_h
  $(OUTPUT)/test_netcnt: cgroup_helpers.c
  $(OUTPUT)/test_sock_fields: cgroup_helpers.c
  $(OUTPUT)/test_sysctl: cgroup_helpers.c
 -$(OUTPUT)/test_cgroup_attach: cgroup_helpers.c
  
  .PHONY: force
  
  # force a rebuild of BPFOBJ when its dependencies are updated
  force:
  
 +DEFAULT_BPFTOOL := $(OUTPUT)/tools/usr/local/sbin/bpftool
 +BPFTOOL ?= $(DEFAULT_BPFTOOL)
 +
 +$(DEFAULT_BPFTOOL): force
 +      $(MAKE) -C $(BPFTOOLDIR) DESTDIR=$(OUTPUT)/tools install
 +
  $(BPFOBJ): force
        $(MAKE) -C $(BPFDIR) OUTPUT=$(OUTPUT)/
  
- BPF_HELPERS := $(BPFDIR)/bpf_helper_defs.h $(wildcard $(BPFDIR)/bpf_*.h)
- $(BPFDIR)/bpf_helper_defs.h:
-       $(MAKE) -C $(BPFDIR) OUTPUT=$(OUTPUT)/ bpf_helper_defs.h
+ BPF_HELPERS := $(OUTPUT)/bpf_helper_defs.h $(wildcard $(BPFDIR)/bpf_*.h)
+ $(OUTPUT)/bpf_helper_defs.h:
+       $(MAKE) -C $(BPFDIR) OUTPUT=$(OUTPUT)/ $(OUTPUT)/bpf_helper_defs.h
  
  # Get Clang's default includes on this system, as opposed to those seen by
  # '-target bpf'. This fixes "missing" files on some architectures/distros,
@@@ -190,33 -159,27 +190,33 @@@ $(OUTPUT)/flow_dissector_load.o: flow_d
  # $3 - CFLAGS
  # $4 - LDFLAGS
  define CLANG_BPF_BUILD_RULE
 +      $(call msg,  CLANG-LLC,$(TRUNNER_BINARY),$2)
        ($(CLANG) $3 -O2 -target bpf -emit-llvm                         \
                -c $1 -o - || echo "BPF obj compilation failed") |      \
        $(LLC) -mattr=dwarfris -march=bpf -mcpu=probe $4 -filetype=obj -o $2
  endef
  # Similar to CLANG_BPF_BUILD_RULE, but with disabled alu32
  define CLANG_NOALU32_BPF_BUILD_RULE
 +      $(call msg,  CLANG-LLC,$(TRUNNER_BINARY),$2)
        ($(CLANG) $3 -O2 -target bpf -emit-llvm                         \
                -c $1 -o - || echo "BPF obj compilation failed") |      \
        $(LLC) -march=bpf -mcpu=v2 $4 -filetype=obj -o $2
  endef
  # Similar to CLANG_BPF_BUILD_RULE, but using native Clang and bpf LLC
  define CLANG_NATIVE_BPF_BUILD_RULE
 +      $(call msg,  CLANG-BPF,$(TRUNNER_BINARY),$2)
        ($(CLANG) $3 -O2 -emit-llvm                                     \
                -c $1 -o - || echo "BPF obj compilation failed") |      \
        $(LLC) -march=bpf -mcpu=probe $4 -filetype=obj -o $2
  endef
  # Build BPF object using GCC
  define GCC_BPF_BUILD_RULE
 +      $(call msg,    GCC-BPF,$(TRUNNER_BINARY),$2)
        $(BPF_GCC) $3 $4 -O2 -c $1 -o $2
  endef
  
 +SKEL_BLACKLIST := btf__% test_pinning_invalid.c
 +
  # Set up extra TRUNNER_XXX "temporary" variables in the environment (relies on
  # $eval()) and pass control to DEFINE_TEST_RUNNER_RULES.
  # Parameters:
@@@ -232,11 -195,8 +232,11 @@@ TRUNNER_EXTRA_OBJS := $$(patsubst %.c,$
                                 $$(filter %.c,$(TRUNNER_EXTRA_SOURCES)))
  TRUNNER_EXTRA_HDRS := $$(filter %.h,$(TRUNNER_EXTRA_SOURCES))
  TRUNNER_TESTS_HDR := $(TRUNNER_TESTS_DIR)/tests.h
 -TRUNNER_BPF_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.o,           \
 -                              $$(notdir $$(wildcard $(TRUNNER_BPF_PROGS_DIR)/*.c)))
 +TRUNNER_BPF_SRCS := $$(notdir $$(wildcard $(TRUNNER_BPF_PROGS_DIR)/*.c))
 +TRUNNER_BPF_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.o, $$(TRUNNER_BPF_SRCS))
 +TRUNNER_BPF_SKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.skel.h,     \
 +                               $$(filter-out $(SKEL_BLACKLIST),       \
 +                                             $$(TRUNNER_BPF_SRCS)))
  
  # Evaluate rules now with extra TRUNNER_XXX variables above already defined
  $$(eval $$(call DEFINE_TEST_RUNNER_RULES,$1,$2))
@@@ -266,19 -226,12 +266,19 @@@ $(TRUNNER_BPF_OBJS): $(TRUNNER_OUTPUT)/
        $$(call $(TRUNNER_BPF_BUILD_RULE),$$<,$$@,                      \
                                          $(TRUNNER_BPF_CFLAGS),        \
                                          $(TRUNNER_BPF_LDFLAGS))
 +
 +$(TRUNNER_BPF_SKELS): $(TRUNNER_OUTPUT)/%.skel.h:                     \
 +                    $(TRUNNER_OUTPUT)/%.o                             \
 +                    | $(BPFTOOL) $(TRUNNER_OUTPUT)
 +      $$(call msg,   GEN-SKEL,$(TRUNNER_BINARY),$$@)
 +      $$(BPFTOOL) gen skeleton $$< > $$@
  endif
  
  # ensure we set up tests.h header generation rule just once
  ifeq ($($(TRUNNER_TESTS_DIR)-tests-hdr),)
  $(TRUNNER_TESTS_DIR)-tests-hdr := y
  $(TRUNNER_TESTS_HDR): $(TRUNNER_TESTS_DIR)/*.c
 +      $$(call msg,   TEST-HDR,$(TRUNNER_BINARY),$$@)
        $$(shell ( cd $(TRUNNER_TESTS_DIR);                             \
                  echo '/* Generated header, do not edit */';           \
                  ls *.c 2> /dev/null |                                 \
@@@ -292,9 -245,7 +292,9 @@@ $(TRUNNER_TEST_OBJS): $(TRUNNER_OUTPUT)
                      $(TRUNNER_TESTS_DIR)/%.c                          \
                      $(TRUNNER_EXTRA_HDRS)                             \
                      $(TRUNNER_BPF_OBJS)                               \
 +                    $(TRUNNER_BPF_SKELS)                              \
                      $$(BPFOBJ) | $(TRUNNER_OUTPUT)
 +      $$(call msg,   TEST-OBJ,$(TRUNNER_BINARY),$$@)
        cd $$(@D) && $$(CC) $$(CFLAGS) -c $(CURDIR)/$$< $$(LDLIBS) -o $$(@F)
  
  $(TRUNNER_EXTRA_OBJS): $(TRUNNER_OUTPUT)/%.o:                         \
                       $(TRUNNER_EXTRA_HDRS)                            \
                       $(TRUNNER_TESTS_HDR)                             \
                       $$(BPFOBJ) | $(TRUNNER_OUTPUT)
 +      $$(call msg,  EXTRA-OBJ,$(TRUNNER_BINARY),$$@)
        $$(CC) $$(CFLAGS) -c $$< $$(LDLIBS) -o $$@
  
 +# only copy extra resources if in flavored build
  $(TRUNNER_BINARY)-extras: $(TRUNNER_EXTRA_FILES) | $(TRUNNER_OUTPUT)
  ifneq ($2,)
 -      # only copy extra resources if in flavored build
 +      $$(call msg,  EXTRAS-CP,$(TRUNNER_BINARY),$(TRUNNER_EXTRA_FILES))
        cp -a $$^ $(TRUNNER_OUTPUT)/
  endif
  
  $(OUTPUT)/$(TRUNNER_BINARY): $(TRUNNER_TEST_OBJS)                     \
                             $(TRUNNER_EXTRA_OBJS) $$(BPFOBJ)           \
                             | $(TRUNNER_BINARY)-extras
 +      $$(call msg,     BINARY,,$$@)
        $$(CC) $$(CFLAGS) $$(filter %.a %.o,$$^) $$(LDLIBS) -o $$@
  
  endef
@@@ -367,15 -315,12 +367,15 @@@ verifier/tests.h: verifier/*.
                  echo '#endif' \
                ) > verifier/tests.h)
  $(OUTPUT)/test_verifier: test_verifier.c verifier/tests.h $(BPFOBJ) | $(OUTPUT)
 +      $(call msg,     BINARY,,$@)
        $(CC) $(CFLAGS) $(filter %.a %.o %.c,$^) $(LDLIBS) -o $@
  
  # Make sure we are able to include and link libbpf against c++.
 -$(OUTPUT)/test_cpp: test_cpp.cpp $(BPFOBJ)
 +$(OUTPUT)/test_cpp: test_cpp.cpp $(OUTPUT)/test_core_extern.skel.h $(BPFOBJ)
 +      $(call msg,        CXX,,$@)
        $(CXX) $(CFLAGS) $^ $(LDLIBS) -o $@
  
  EXTRA_CLEAN := $(TEST_CUSTOM_PROGS)                                   \
        prog_tests/tests.h map_tests/tests.h verifier/tests.h           \
 -      feature $(OUTPUT)/*.o $(OUTPUT)/no_alu32 $(OUTPUT)/bpf_gcc
 +      feature $(OUTPUT)/*.o $(OUTPUT)/no_alu32 $(OUTPUT)/bpf_gcc      \
 +      tools *.skel.h