Merge branch 'netpoll-move-out-netconsole-specific-functions'

Breno Leitao says:

====================
netpoll: move out netconsole-specific functions

netpoll and netconsole were created together and their code has
been intermixed in net/core/netpoll.c for decades. The result is
that netpoll exposes two send-side interfaces:

 * a generic "give me an sk_buff" path used by every stacked-device
   driver (bonding, team, vlan, bridge, macvlan, dsa),

 * a second path that takes raw bytes and builds a UDP/IP/Ethernet
   packet -- exclusively for netconsole.

The packet builder, an skb pool allocator, and several
netconsole-specific helpers all live next to the generic plumbing even
though no other consumer ever touches them.

Worse, every netpoll user pays for that overlap: struct netpoll carries
an skb_pool and a refill work_struct that only netconsole's find_skb()
ever reads from, and net-core has to review unrelated changes (TTL, hop
limit, IP ID generation, source MAC selection, pool sizing) just because
they happen to be coded inside netpoll.

This is a waste of memory for something useless.

This series splits the netconsole-specific code out:

 * netpoll_send_udp() and its private helpers (push_ipv6, push_ipv4,
push_eth, push_udp, netpoll_udp_checksum, find_skb) move into
drivers/net/netconsole.c, leaving netpoll with a single skb-only
send interface that is the same for every user.

The moves are one function per patch for reviewability; helpers are
temporarily EXPORT_SYMBOL_GPL'd while netpoll_send_udp() is still in
netpoll calling them, then those exports are dropped together once
netpoll_send_udp() itself moves.

The only new permanent export is zap_completion_queue(), needed because
find_skb() still drains the per-CPU TX completion queue before
allocating.

struct netpoll is unchanged in this series; making the pool itself
netconsole-private (and reclaiming the skb_pool / refill_wq fields for
the rest of netpoll's users) is the natural follow-up, once this patchset
lands.
====================

Link: https://patch.msgid.link/20260512-netconsole_split-v2-0-1191d14ad66d@debian.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-05-13 18:09:15 -07:00
commit 6bbc313bcb
3 changed files with 180 additions and 179 deletions

View File

@ -32,8 +32,13 @@
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/udp.h>
#include <linux/netpoll.h>
#include <linux/inet.h>
#include <linux/unaligned.h>
#include <net/ip6_checksum.h>
#include <linux/configfs.h>
#include <linux/etherdevice.h>
#include <linux/hex.h>
@ -45,6 +50,7 @@
MODULE_AUTHOR("Matt Mackall <mpm@selenic.com>");
MODULE_DESCRIPTION("Console driver for network interfaces");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("NETDEV_INTERNAL");
#define MAX_PARAM_LENGTH 256
#define MAX_EXTRADATA_ENTRY_LEN 256
@ -1648,6 +1654,175 @@ static struct notifier_block netconsole_netdev_notifier = {
.notifier_call = netconsole_netdev_event,
};
static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve)
{
int count = 0;
struct sk_buff *skb;
netpoll_zap_completion_queue();
repeat:
skb = alloc_skb(len, GFP_ATOMIC);
if (!skb) {
skb = skb_dequeue(&np->skb_pool);
schedule_work(&np->refill_wq);
}
if (!skb) {
if (++count < 10) {
netpoll_poll_dev(np->dev);
goto repeat;
}
return NULL;
}
refcount_set(&skb->users, 1);
skb_reserve(skb, reserve);
return skb;
}
static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb,
int len)
{
struct udphdr *udph;
int udp_len;
udp_len = len + sizeof(struct udphdr);
udph = udp_hdr(skb);
/* check needs to be set, since it will be consumed in csum_partial */
udph->check = 0;
if (np->ipv6)
udph->check = csum_ipv6_magic(&np->local_ip.in6,
&np->remote_ip.in6,
udp_len, IPPROTO_UDP,
csum_partial(udph, udp_len, 0));
else
udph->check = csum_tcpudp_magic(np->local_ip.ip,
np->remote_ip.ip,
udp_len, IPPROTO_UDP,
csum_partial(udph, udp_len, 0));
if (udph->check == 0)
udph->check = CSUM_MANGLED_0;
}
static void push_udp(struct netpoll *np, struct sk_buff *skb, int len)
{
struct udphdr *udph;
int udp_len;
udp_len = len + sizeof(struct udphdr);
skb_push(skb, sizeof(struct udphdr));
skb_reset_transport_header(skb);
udph = udp_hdr(skb);
udph->source = htons(np->local_port);
udph->dest = htons(np->remote_port);
udph->len = htons(udp_len);
netpoll_udp_checksum(np, skb, len);
}
static void push_eth(struct netpoll *np, struct sk_buff *skb)
{
struct ethhdr *eth;
eth = skb_push(skb, ETH_HLEN);
skb_reset_mac_header(skb);
ether_addr_copy(eth->h_source, np->dev->dev_addr);
ether_addr_copy(eth->h_dest, np->remote_mac);
if (np->ipv6)
eth->h_proto = htons(ETH_P_IPV6);
else
eth->h_proto = htons(ETH_P_IP);
}
static void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len)
{
static atomic_t ip_ident;
struct iphdr *iph;
int ip_len;
ip_len = len + sizeof(struct udphdr) + sizeof(struct iphdr);
skb_push(skb, sizeof(struct iphdr));
skb_reset_network_header(skb);
iph = ip_hdr(skb);
/* iph->version = 4; iph->ihl = 5; */
*(unsigned char *)iph = 0x45;
iph->tos = 0;
put_unaligned(htons(ip_len), &iph->tot_len);
iph->id = htons(atomic_inc_return(&ip_ident));
iph->frag_off = 0;
iph->ttl = 64;
iph->protocol = IPPROTO_UDP;
iph->check = 0;
put_unaligned(np->local_ip.ip, &iph->saddr);
put_unaligned(np->remote_ip.ip, &iph->daddr);
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
skb->protocol = htons(ETH_P_IP);
}
static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len)
{
struct ipv6hdr *ip6h;
skb_push(skb, sizeof(struct ipv6hdr));
skb_reset_network_header(skb);
ip6h = ipv6_hdr(skb);
/* ip6h->version = 6; ip6h->priority = 0; */
*(unsigned char *)ip6h = 0x60;
ip6h->flow_lbl[0] = 0;
ip6h->flow_lbl[1] = 0;
ip6h->flow_lbl[2] = 0;
ip6h->payload_len = htons(sizeof(struct udphdr) + len);
ip6h->nexthdr = IPPROTO_UDP;
ip6h->hop_limit = 32;
ip6h->saddr = np->local_ip.in6;
ip6h->daddr = np->remote_ip.in6;
skb->protocol = htons(ETH_P_IPV6);
}
static int netpoll_send_udp(struct netpoll *np, const char *msg, int len)
{
int total_len, ip_len, udp_len;
struct sk_buff *skb;
if (!IS_ENABLED(CONFIG_PREEMPT_RT))
WARN_ON_ONCE(!irqs_disabled());
udp_len = len + sizeof(struct udphdr);
if (np->ipv6)
ip_len = udp_len + sizeof(struct ipv6hdr);
else
ip_len = udp_len + sizeof(struct iphdr);
total_len = ip_len + LL_RESERVED_SPACE(np->dev);
skb = find_skb(np, total_len + np->dev->needed_tailroom,
total_len - len);
if (!skb)
return -ENOMEM;
skb_copy_to_linear_data(skb, msg, len);
skb_put(skb, len);
push_udp(np, skb, len);
if (np->ipv6)
push_ipv6(np, skb, len);
else
push_ipv4(np, skb, len);
push_eth(np, skb);
skb->dev = np->dev;
return (int)netpoll_send_skb(np, skb);
}
/**
* send_udp - Wrapper for netpoll_send_udp that counts errors
* @nt: target to send message to

View File

@ -67,13 +67,13 @@ static inline void netpoll_poll_disable(struct net_device *dev) { return; }
static inline void netpoll_poll_enable(struct net_device *dev) { return; }
#endif
int netpoll_send_udp(struct netpoll *np, const char *msg, int len);
int __netpoll_setup(struct netpoll *np, struct net_device *ndev);
int netpoll_setup(struct netpoll *np);
void __netpoll_free(struct netpoll *np);
void netpoll_cleanup(struct netpoll *np);
void do_netpoll_cleanup(struct netpoll *np);
netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb);
void netpoll_zap_completion_queue(void);
#ifdef CONFIG_NETPOLL
static inline void *netpoll_poll_lock(struct napi_struct *napi)

View File

@ -29,12 +29,10 @@
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/if_vlan.h>
#include <linux/udp.h>
#include <net/tcp.h>
#include <net/udp.h>
#include <net/addrconf.h>
#include <net/ndisc.h>
#include <net/ip6_checksum.h>
#include <linux/unaligned.h>
#include <trace/events/napi.h>
#include <linux/kconfig.h>
@ -53,8 +51,6 @@
sizeof(struct udphdr) + \
MAX_UDP_CHUNK)
static void zap_completion_queue(void);
static unsigned int carrier_timeout = 4;
module_param(carrier_timeout, uint, 0644);
@ -201,7 +197,7 @@ void netpoll_poll_dev(struct net_device *dev)
up(&ni->dev_lock);
zap_completion_queue();
netpoll_zap_completion_queue();
}
EXPORT_SYMBOL(netpoll_poll_dev);
@ -240,7 +236,7 @@ static void refill_skbs(struct netpoll *np)
}
}
static void zap_completion_queue(void)
void netpoll_zap_completion_queue(void)
{
unsigned long flags;
struct softnet_data *sd = &get_cpu_var(softnet_data);
@ -267,33 +263,7 @@ static void zap_completion_queue(void)
put_cpu_var(softnet_data);
}
static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve)
{
int count = 0;
struct sk_buff *skb;
zap_completion_queue();
repeat:
skb = alloc_skb(len, GFP_ATOMIC);
if (!skb) {
skb = skb_dequeue(&np->skb_pool);
schedule_work(&np->refill_wq);
}
if (!skb) {
if (++count < 10) {
netpoll_poll_dev(np->dev);
goto repeat;
}
return NULL;
}
refcount_set(&skb->users, 1);
skb_reserve(skb, reserve);
return skb;
}
EXPORT_SYMBOL_NS_GPL(netpoll_zap_completion_queue, "NETDEV_INTERNAL");
static int netpoll_owner_active(struct net_device *dev)
{
@ -369,31 +339,6 @@ static netdev_tx_t __netpoll_send_skb(struct netpoll *np, struct sk_buff *skb)
return ret;
}
static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb,
int len)
{
struct udphdr *udph;
int udp_len;
udp_len = len + sizeof(struct udphdr);
udph = udp_hdr(skb);
/* check needs to be set, since it will be consumed in csum_partial */
udph->check = 0;
if (np->ipv6)
udph->check = csum_ipv6_magic(&np->local_ip.in6,
&np->remote_ip.in6,
udp_len, IPPROTO_UDP,
csum_partial(udph, udp_len, 0));
else
udph->check = csum_tcpudp_magic(np->local_ip.ip,
np->remote_ip.ip,
udp_len, IPPROTO_UDP,
csum_partial(udph, udp_len, 0));
if (udph->check == 0)
udph->check = CSUM_MANGLED_0;
}
netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb)
{
unsigned long flags;
@ -411,125 +356,6 @@ netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb)
}
EXPORT_SYMBOL(netpoll_send_skb);
static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len)
{
struct ipv6hdr *ip6h;
skb_push(skb, sizeof(struct ipv6hdr));
skb_reset_network_header(skb);
ip6h = ipv6_hdr(skb);
/* ip6h->version = 6; ip6h->priority = 0; */
*(unsigned char *)ip6h = 0x60;
ip6h->flow_lbl[0] = 0;
ip6h->flow_lbl[1] = 0;
ip6h->flow_lbl[2] = 0;
ip6h->payload_len = htons(sizeof(struct udphdr) + len);
ip6h->nexthdr = IPPROTO_UDP;
ip6h->hop_limit = 32;
ip6h->saddr = np->local_ip.in6;
ip6h->daddr = np->remote_ip.in6;
skb->protocol = htons(ETH_P_IPV6);
}
static void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len)
{
static atomic_t ip_ident;
struct iphdr *iph;
int ip_len;
ip_len = len + sizeof(struct udphdr) + sizeof(struct iphdr);
skb_push(skb, sizeof(struct iphdr));
skb_reset_network_header(skb);
iph = ip_hdr(skb);
/* iph->version = 4; iph->ihl = 5; */
*(unsigned char *)iph = 0x45;
iph->tos = 0;
put_unaligned(htons(ip_len), &iph->tot_len);
iph->id = htons(atomic_inc_return(&ip_ident));
iph->frag_off = 0;
iph->ttl = 64;
iph->protocol = IPPROTO_UDP;
iph->check = 0;
put_unaligned(np->local_ip.ip, &iph->saddr);
put_unaligned(np->remote_ip.ip, &iph->daddr);
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
skb->protocol = htons(ETH_P_IP);
}
static void push_udp(struct netpoll *np, struct sk_buff *skb, int len)
{
struct udphdr *udph;
int udp_len;
udp_len = len + sizeof(struct udphdr);
skb_push(skb, sizeof(struct udphdr));
skb_reset_transport_header(skb);
udph = udp_hdr(skb);
udph->source = htons(np->local_port);
udph->dest = htons(np->remote_port);
udph->len = htons(udp_len);
netpoll_udp_checksum(np, skb, len);
}
static void push_eth(struct netpoll *np, struct sk_buff *skb)
{
struct ethhdr *eth;
eth = skb_push(skb, ETH_HLEN);
skb_reset_mac_header(skb);
ether_addr_copy(eth->h_source, np->dev->dev_addr);
ether_addr_copy(eth->h_dest, np->remote_mac);
if (np->ipv6)
eth->h_proto = htons(ETH_P_IPV6);
else
eth->h_proto = htons(ETH_P_IP);
}
int netpoll_send_udp(struct netpoll *np, const char *msg, int len)
{
int total_len, ip_len, udp_len;
struct sk_buff *skb;
if (!IS_ENABLED(CONFIG_PREEMPT_RT))
WARN_ON_ONCE(!irqs_disabled());
udp_len = len + sizeof(struct udphdr);
if (np->ipv6)
ip_len = udp_len + sizeof(struct ipv6hdr);
else
ip_len = udp_len + sizeof(struct iphdr);
total_len = ip_len + LL_RESERVED_SPACE(np->dev);
skb = find_skb(np, total_len + np->dev->needed_tailroom,
total_len - len);
if (!skb)
return -ENOMEM;
skb_copy_to_linear_data(skb, msg, len);
skb_put(skb, len);
push_udp(np, skb, len);
if (np->ipv6)
push_ipv6(np, skb, len);
else
push_ipv4(np, skb, len);
push_eth(np, skb);
skb->dev = np->dev;
return (int)netpoll_send_skb(np, skb);
}
EXPORT_SYMBOL(netpoll_send_udp);
static void skb_pool_flush(struct netpoll *np)
{
struct sk_buff_head *skb_pool;