diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
| commit | 4352b8aee98005853aa63f57d6377282de17a33f (patch) | |
| tree | 27614d7a9ef05c32d089004e104364ce42324837 /drivers/net/ifb.c | |
| download | linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip | |
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni:
"A new driver this cycle, for the AMD AXI I3C controller.
There are also new features such as IBI wakeup support, SETAASA device
discovery and ACPI support for the the DAA methods, meaning we can now
communicate with the SPD devices on DDR5 modules.
New driver:
- AMD AXI I3C master controller
Subsystem:
- I3C Common Command Code (CCC) handling improvements, especially
around GET CCCs
- SETAASA device discovery support
- ACPI support for all existing DAA methods like SETDASA, SETNEWDA as
well as I2C devices on I3C bus
- IBI-based wakeup support
Drivers:
- dw: SETAASA support
- mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C
controller support, PIO queue management support for HCI v1.2
- renesas: improve suspend to RAM support, add runtime PM support"
* tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits)
i3c: dw: reduce do_daa time if there's no client
i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller
i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2
i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode
i3c: dw: rename "pclk" to "apb" to match dt-binding
i3c: dw: make struct dw_i3c_cmd smaller
i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding
i3c: renesas: Don't register devices when ENTDAA times out
i3c: master: dw-i3c-master: fix OD timing for first broadcast
i3c: mipi-i3c-hci: Advertise IBI wakeup capability
i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev()
i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI
i3c: master: Reject IBI requests from non-IBI-capable devices
i3c: master: Add helper to query bus wakeup requirements
i3c: master: Report wakeup events for IBIs
i3c: master: Support IBI-based wakeup capability
i3c: master: Fix potential UAF in i3c_device_match()
i3c: master: Fix potential UAF in i3c_device_uevent()
i3c: Make dev->desc locking assumptions explicit
i3c: master: Fix use-after-free of master->this
...
Diffstat (limited to 'drivers/net/ifb.c')
| -rw-r--r-- | drivers/net/ifb.c | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/drivers/net/ifb.c b/drivers/net/ifb.c new file mode 100644 index 000000000..43aa1bfd4 --- /dev/null +++ b/drivers/net/ifb.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* drivers/net/ifb.c: + + The purpose of this driver is to provide a device that allows + for sharing of resources: + + 1) qdiscs/policies that are per device as opposed to system wide. + ifb allows for a device which can be redirected to thus providing + an impression of sharing. + + 2) Allows for queueing incoming traffic for shaping instead of + dropping. + + The original concept is based on what is known as the IMQ + driver initially written by Martin Devera, later rewritten + by Patrick McHardy and then maintained by Andre Correa. + + You need the tc action mirror or redirect to feed this device + packets. + + + Authors: Jamal Hadi Salim (2005) + +*/ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/ethtool.h> +#include <linux/etherdevice.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/moduleparam.h> +#include <linux/netfilter_netdev.h> +#include <net/pkt_sched.h> +#include <net/net_namespace.h> + +#define TX_Q_LIMIT 32 + +struct ifb_q_stats { + u64_stats_t packets; + u64_stats_t bytes; + struct u64_stats_sync sync; +}; + +struct ifb_q_private { + struct net_device *dev; + struct tasklet_struct ifb_tasklet; + int tasklet_pending; + int txqnum; + struct sk_buff_head rq; + struct sk_buff_head tq; + struct ifb_q_stats rx_stats; + struct ifb_q_stats tx_stats; +} ____cacheline_aligned_in_smp; + +struct ifb_dev_private { + struct ifb_q_private *tx_private; +}; + +/* For ethtools stats. */ +struct ifb_q_stats_desc { + char desc[ETH_GSTRING_LEN]; + size_t offset; +}; + +#define IFB_Q_STAT(m) offsetof(struct ifb_q_stats, m) + +static const struct ifb_q_stats_desc ifb_q_stats_desc[] = { + { "packets", IFB_Q_STAT(packets) }, + { "bytes", IFB_Q_STAT(bytes) }, +}; + +#define IFB_Q_STATS_LEN ARRAY_SIZE(ifb_q_stats_desc) + +static netdev_tx_t ifb_xmit(struct sk_buff *skb, struct net_device *dev); +static int ifb_open(struct net_device *dev); +static int ifb_close(struct net_device *dev); + +static void ifb_update_q_stats(struct ifb_q_stats *stats, int len) +{ + u64_stats_update_begin(&stats->sync); + u64_stats_inc(&stats->packets); + u64_stats_add(&stats->bytes, len); + u64_stats_update_end(&stats->sync); +} + +static void ifb_ri_tasklet(struct tasklet_struct *t) +{ + struct ifb_q_private *txp = from_tasklet(txp, t, ifb_tasklet); + struct netdev_queue *txq; + struct sk_buff *skb; + + txq = netdev_get_tx_queue(txp->dev, txp->txqnum); + skb = skb_peek(&txp->tq); + if (!skb) { + if (!__netif_tx_trylock(txq)) + goto resched; + skb_queue_splice_tail_init(&txp->rq, &txp->tq); + __netif_tx_unlock(txq); + } + + while ((skb = __skb_dequeue(&txp->tq)) != NULL) { + /* Skip tc and netfilter to prevent redirection loop. */ + skb->redirected = 0; +#ifdef CONFIG_NET_CLS_ACT + skb->tc_skip_classify = 1; +#endif + nf_skip_egress(skb, true); + + ifb_update_q_stats(&txp->tx_stats, skb->len); + + rcu_read_lock(); + skb->dev = dev_get_by_index_rcu(dev_net(txp->dev), skb->skb_iif); + if (!skb->dev) { + rcu_read_unlock(); + dev_kfree_skb(skb); + txp->dev->stats.tx_dropped++; + if (skb_queue_len(&txp->tq) != 0) + goto resched; + break; + } + rcu_read_unlock(); + skb->skb_iif = txp->dev->ifindex; + + if (!skb->from_ingress) { + dev_queue_xmit(skb); + } else { + skb_pull_rcsum(skb, skb->mac_len); + netif_receive_skb(skb); + } + } + + if (__netif_tx_trylock(txq)) { + skb = skb_peek(&txp->rq); + if (!skb) { + txp->tasklet_pending = 0; + if (netif_tx_queue_stopped(txq)) + netif_tx_wake_queue(txq); + } else { + __netif_tx_unlock(txq); + goto resched; + } + __netif_tx_unlock(txq); + } else { +resched: + txp->tasklet_pending = 1; + tasklet_schedule(&txp->ifb_tasklet); + } + +} + +static void ifb_stats64(struct net_device *dev, + struct rtnl_link_stats64 *stats) +{ + struct ifb_dev_private *dp = netdev_priv(dev); + struct ifb_q_private *txp = dp->tx_private; + unsigned int start; + u64 packets, bytes; + int i; + + for (i = 0; i < dev->num_tx_queues; i++,txp++) { + do { + start = u64_stats_fetch_begin(&txp->rx_stats.sync); + packets = u64_stats_read(&txp->rx_stats.packets); + bytes = u64_stats_read(&txp->rx_stats.bytes); + } while (u64_stats_fetch_retry(&txp->rx_stats.sync, start)); + stats->rx_packets += packets; + stats->rx_bytes += bytes; + + do { + start = u64_stats_fetch_begin(&txp->tx_stats.sync); + packets = u64_stats_read(&txp->tx_stats.packets); + bytes = u64_stats_read(&txp->tx_stats.bytes); + } while (u64_stats_fetch_retry(&txp->tx_stats.sync, start)); + stats->tx_packets += packets; + stats->tx_bytes += bytes; + } + stats->rx_dropped = dev->stats.rx_dropped; + stats->tx_dropped = dev->stats.tx_dropped; +} + +static int ifb_dev_init(struct net_device *dev) +{ + struct ifb_dev_private *dp = netdev_priv(dev); + struct ifb_q_private *txp; + int i; + + txp = kzalloc_objs(*txp, dev->num_tx_queues); + if (!txp) + return -ENOMEM; + dp->tx_private = txp; + for (i = 0; i < dev->num_tx_queues; i++,txp++) { + txp->txqnum = i; + txp->dev = dev; + __skb_queue_head_init(&txp->rq); + __skb_queue_head_init(&txp->tq); + u64_stats_init(&txp->rx_stats.sync); + u64_stats_init(&txp->tx_stats.sync); + tasklet_setup(&txp->ifb_tasklet, ifb_ri_tasklet); + netif_tx_start_queue(netdev_get_tx_queue(dev, i)); + } + return 0; +} + +static void ifb_get_strings(struct net_device *dev, u32 stringset, u8 *buf) +{ + u8 *p = buf; + int i, j; + + switch (stringset) { + case ETH_SS_STATS: + for (i = 0; i < dev->num_tx_queues; i++) + for (j = 0; j < IFB_Q_STATS_LEN; j++) + ethtool_sprintf(&p, "rx_queue_%u_%.18s", + i, ifb_q_stats_desc[j].desc); + + for (i = 0; i < dev->num_tx_queues; i++) + for (j = 0; j < IFB_Q_STATS_LEN; j++) + ethtool_sprintf(&p, "tx_queue_%u_%.18s", + i, ifb_q_stats_desc[j].desc); + + break; + } +} + +static int ifb_get_sset_count(struct net_device *dev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return IFB_Q_STATS_LEN * dev->num_tx_queues * 2; + default: + return -EOPNOTSUPP; + } +} + +static void ifb_fill_stats_data(u64 **data, + struct ifb_q_stats *q_stats) +{ + void *stats_base = (void *)q_stats; + unsigned int start; + size_t offset; + int j; + + do { + start = u64_stats_fetch_begin(&q_stats->sync); + for (j = 0; j < IFB_Q_STATS_LEN; j++) { + offset = ifb_q_stats_desc[j].offset; + (*data)[j] = u64_stats_read((u64_stats_t *)(stats_base + offset)); + } + } while (u64_stats_fetch_retry(&q_stats->sync, start)); + + *data += IFB_Q_STATS_LEN; +} + +static void ifb_get_ethtool_stats(struct net_device *dev, + struct ethtool_stats *stats, u64 *data) +{ + struct ifb_dev_private *dp = netdev_priv(dev); + struct ifb_q_private *txp; + int i; + + for (i = 0; i < dev->num_tx_queues; i++) { + txp = dp->tx_private + i; + ifb_fill_stats_data(&data, &txp->rx_stats); + } + + for (i = 0; i < dev->num_tx_queues; i++) { + txp = dp->tx_private + i; + ifb_fill_stats_data(&data, &txp->tx_stats); + } +} + +static const struct net_device_ops ifb_netdev_ops = { + .ndo_open = ifb_open, + .ndo_stop = ifb_close, + .ndo_get_stats64 = ifb_stats64, + .ndo_start_xmit = ifb_xmit, + .ndo_validate_addr = eth_validate_addr, + .ndo_init = ifb_dev_init, +}; + +static const struct ethtool_ops ifb_ethtool_ops = { + .get_strings = ifb_get_strings, + .get_sset_count = ifb_get_sset_count, + .get_ethtool_stats = ifb_get_ethtool_stats, +}; + +#define IFB_FEATURES (NETIF_F_HW_CSUM | NETIF_F_SG | NETIF_F_FRAGLIST | \ + NETIF_F_GSO_SOFTWARE | NETIF_F_GSO_ENCAP_ALL | \ + NETIF_F_HIGHDMA | NETIF_F_HW_VLAN_CTAG_TX | \ + NETIF_F_HW_VLAN_STAG_TX) + +static void ifb_dev_free(struct net_device *dev) +{ + struct ifb_dev_private *dp = netdev_priv(dev); + struct ifb_q_private *txp = dp->tx_private; + int i; + + for (i = 0; i < dev->num_tx_queues; i++,txp++) { + tasklet_kill(&txp->ifb_tasklet); + __skb_queue_purge(&txp->rq); + __skb_queue_purge(&txp->tq); + } + kfree(dp->tx_private); +} + +static void ifb_setup(struct net_device *dev) +{ + /* Initialize the device structure. */ + dev->netdev_ops = &ifb_netdev_ops; + dev->ethtool_ops = &ifb_ethtool_ops; + + /* Fill in device structure with ethernet-generic values. */ + ether_setup(dev); + dev->tx_queue_len = TX_Q_LIMIT; + + dev->features |= IFB_FEATURES; + dev->hw_features |= dev->features; + dev->hw_enc_features |= dev->features; + dev->vlan_features |= IFB_FEATURES & ~(NETIF_F_HW_VLAN_CTAG_TX | + NETIF_F_HW_VLAN_STAG_TX); + + dev->flags |= IFF_NOARP; + dev->flags &= ~IFF_MULTICAST; + dev->priv_flags &= ~IFF_TX_SKB_SHARING; + netif_keep_dst(dev); + eth_hw_addr_random(dev); + dev->needs_free_netdev = true; + dev->priv_destructor = ifb_dev_free; + + dev->min_mtu = 0; + dev->max_mtu = 0; + netif_set_tso_max_size(dev, GSO_MAX_SIZE); +} + +static netdev_tx_t ifb_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ifb_dev_private *dp = netdev_priv(dev); + struct ifb_q_private *txp = dp->tx_private + skb_get_queue_mapping(skb); + + ifb_update_q_stats(&txp->rx_stats, skb->len); + + if (!skb->redirected || !skb->skb_iif) { + dev_kfree_skb(skb); + dev->stats.rx_dropped++; + return NETDEV_TX_OK; + } + + if (skb_queue_len(&txp->rq) >= dev->tx_queue_len) + netif_tx_stop_queue(netdev_get_tx_queue(dev, txp->txqnum)); + + __skb_queue_tail(&txp->rq, skb); + if (!txp->tasklet_pending) { + txp->tasklet_pending = 1; + tasklet_schedule(&txp->ifb_tasklet); + } + + return NETDEV_TX_OK; +} + +static int ifb_close(struct net_device *dev) +{ + netif_tx_stop_all_queues(dev); + return 0; +} + +static int ifb_open(struct net_device *dev) +{ + netif_tx_start_all_queues(dev); + return 0; +} + +static int ifb_validate(struct nlattr *tb[], struct nlattr *data[], + struct netlink_ext_ack *extack) +{ + if (tb[IFLA_ADDRESS]) { + if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN) + return -EINVAL; + if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) + return -EADDRNOTAVAIL; + } + return 0; +} + +static struct rtnl_link_ops ifb_link_ops __read_mostly = { + .kind = "ifb", + .priv_size = sizeof(struct ifb_dev_private), + .setup = ifb_setup, + .validate = ifb_validate, +}; + +/* Number of ifb devices to be set up by this module. + * Note that these legacy devices have one queue. + * Prefer something like : ip link add ifb10 numtxqueues 8 type ifb + */ +static int numifbs = 2; +module_param(numifbs, int, 0); +MODULE_PARM_DESC(numifbs, "Number of ifb devices"); + +static int __init ifb_init_one(int index) +{ + struct net_device *dev_ifb; + int err; + + dev_ifb = alloc_netdev(sizeof(struct ifb_dev_private), "ifb%d", + NET_NAME_UNKNOWN, ifb_setup); + + if (!dev_ifb) + return -ENOMEM; + + dev_ifb->rtnl_link_ops = &ifb_link_ops; + err = register_netdevice(dev_ifb); + if (err < 0) + goto err; + + return 0; + +err: + free_netdev(dev_ifb); + return err; +} + +static int __init ifb_init_module(void) +{ + int i, err; + + err = rtnl_link_register(&ifb_link_ops); + if (err < 0) + return err; + + rtnl_net_lock(&init_net); + + for (i = 0; i < numifbs && !err; i++) { + err = ifb_init_one(i); + cond_resched(); + } + + rtnl_net_unlock(&init_net); + + if (err) + rtnl_link_unregister(&ifb_link_ops); + + return err; +} + +static void __exit ifb_cleanup_module(void) +{ + rtnl_link_unregister(&ifb_link_ops); +} + +module_init(ifb_init_module); +module_exit(ifb_cleanup_module); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Intermediate Functional Block (ifb) netdevice driver for sharing of resources and ingress packet queuing"); +MODULE_AUTHOR("Jamal Hadi Salim"); +MODULE_ALIAS_RTNL_LINK("ifb"); |
