On Sun, Nov 09, 2025 at 11:05:55AM +0000, Andre Carvalho wrote:
> Attempt to resume a previously deactivated target when the associated
> interface comes back (NETDEV_UP event is received) by calling
> __netpoll_setup_hold on the device.
>
> Depending on how the target was setup (by mac or interface name), the
> corresponding field is compared with the device being brought up.
>
> Targets that are candidates for resuming are removed from the target list
> and added to a temporarily list, as __netpoll_setup_hold might allocate.
> __netpoll_setup_hold assumes RTNL is held (which is guaranteed to be the
> case when handling the event) and holds a reference to the device in case
> of success. This reference will be removed upon target (or netconsole)
> removal by netpoll_cleanup.
>
> Target transitions to STATE_DISABLED in case of failures resuming it to
> avoid retrying the same target indefinitely.
>
> Signed-off-by: Andre Carvalho <[email protected]>
> ---
> drivers/net/netconsole.c | 62
> +++++++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 56 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c
> index 5a374e6d178d..50d6df101c20 100644
> --- a/drivers/net/netconsole.c
> +++ b/drivers/net/netconsole.c
> @@ -135,10 +135,12 @@ enum target_state {
> * @stats: Packet send stats for the target. Used for debugging.
> * @state: State of the target.
> * Visible from userspace (read-write).
> - * We maintain a strict 1:1 correspondence between this and
> - * whether the corresponding netpoll is active or inactive.
> + * From a userspace perspective, the target is either enabled or
> + * disabled. Internally, although both STATE_DISABLED and
> + * STATE_DEACTIVATED correspond to inactive netpoll the latter is>
> + * due to interface state changes and may recover automatically.
* disabled. Internally, although both STATE_DISABLED and
* STATE_DEACTIVATED correspond to inactive targets, the latter is
* due to automatic interface state changes and will try
* recover automatically, if the interface comes back
* online.
> * Also, other parameters of a target may be modified at
> - * runtime only when it is disabled (state == STATE_DISABLED).
> + * runtime only when it is disabled (state != STATE_ENABLED).
> * @extended: Denotes whether console is extended or not.
> * @release: Denotes whether kernel release version should be prepended
> * to the message. Depends on extended console.
> @@ -1445,17 +1447,50 @@ static int prepare_extradata(struct netconsole_target
> *nt)
> }
> #endif /* CONFIG_NETCONSOLE_DYNAMIC */
>
> +/* Attempts to resume logging to a deactivated target. */
> +static void maybe_resume_target(struct netconsole_target *nt,
> + struct net_device *ndev)
> +{
> + int ret;
> +
> + ret = __netpoll_setup_hold(&nt->np, ndev);
> + if (ret) {
> + /* netpoll fails setup once, do not try again. */
> + nt->state = STATE_DISABLED;
> + } else {
> + nt->state = STATE_ENABLED;
> + pr_info("network logging resumed on interface %s\n",
> + nt->np.dev_name);
> + }
> +}
I am not sure that helper is useful, I would simplify the last patch
with this one and write something like:
/* Attempts to resume logging to a deactivated target. */
static void maybe_resume_target(struct netconsole_target *nt,
struct net_device *ndev)
{
int ret;
ret = __netpoll_setup_hold(&nt->np, ndev);
if (ret) {
/* netpoll fails setup once, do not try again. */
nt->state = STATE_DISABLED;
return;
}
netdev_hold(ndev, &np->dev_tracker, GFP_KERNEL);
nt->state = STATE_ENABLED;
pr_info("network logging resumed on interface %s\n",
nt->np.dev_name);
}
> +
> +/* Check if the target was bound by mac address. */
> +static bool bound_by_mac(struct netconsole_target *nt)
> +{
> + return is_valid_ether_addr(nt->np.dev_mac);
> +}
Awesome. I liked this helper. It might be useful it some other places, and
eventually transformed into a specific type in the target (in case we need to
in the future)
Can we use it egress_dev also? If so, please separate this in a separate patch.
> +/* Checks if a target matches a device. */
> +static bool target_match(struct netconsole_target *nt, struct net_device
> *ndev)
> +{
> + if (bound_by_mac(nt))
> + return !memcmp(nt->np.dev_mac, ndev->dev_addr, ETH_ALEN);
> + return !strncmp(nt->np.dev_name, ndev->name, IFNAMSIZ);
> +}
> +
> /* Handle network interface device notifications */
> static int netconsole_netdev_event(struct notifier_block *this,
> unsigned long event, void *ptr)
> {
> - unsigned long flags;
> - struct netconsole_target *nt, *tmp;
> struct net_device *dev = netdev_notifier_info_to_dev(ptr);
> + struct netconsole_target *nt, *tmp;
> + LIST_HEAD(resume_list);
> bool stopped = false;
> + unsigned long flags;
>
> if (!(event == NETDEV_CHANGENAME || event == NETDEV_UNREGISTER ||
> - event == NETDEV_RELEASE || event == NETDEV_JOIN))
> + event == NETDEV_RELEASE || event == NETDEV_JOIN ||
> + event == NETDEV_UP))
> goto done;
>
> mutex_lock(&target_cleanup_list_lock);
> @@ -1475,11 +1510,26 @@ static int netconsole_netdev_event(struct
> notifier_block *this,
> stopped = true;
> }
> }
> + if (nt->state == STATE_DEACTIVATED && event == NETDEV_UP &&
> + target_match(nt, dev))
> + list_move(&nt->list, &resume_list);
I think it would be better to move the nt->state == STATE_DEACTIVATED to
target_match and use
the case above. As the following:
if (nt->np.dev == dev) {
switch (event) {
case NETDEV_CHANGENAME:
....
case NETDEV_UP:
if (target_match(nt, dev))
list_move(&nt->list, &resume_list);
> netconsole_target_put(nt);
> }
> spin_unlock_irqrestore(&target_list_lock, flags);
> mutex_unlock(&target_cleanup_list_lock);
>
Write a comment saying that maybe_resume_target() might be called with IRQ
enabled.
> + list_for_each_entry_safe(nt, tmp, &resume_list, list) {
> + maybe_resume_target(nt, dev);
> +
> + /* At this point the target is either enabled or disabled and
> + * was cleaned up before getting deactivated. Either way, add it
> + * back to target list.
> + */
> + spin_lock_irqsave(&target_list_lock, flags);
> + list_move(&nt->list, &target_list);
> + spin_unlock_irqrestore(&target_list_lock, flags);
> + }
> +
> if (stopped) {
> const char *msg = "had an event";
>
Also, extract the code below in a static function. Similar to
netconsole_process_cleanups_core(), but passing resume_list argument.
Let's try to keep netconsole_netdev_event() simple to read and reason about.