Linux Serial subsystem development
 help / color / mirror / Atom feed
* Re: [PATCH] serial: base: do not disable TX on hangup for console ports
From: kpursoty @ 2026-04-16  7:19 UTC (permalink / raw)
  To: linux-serial@vger.kernel.org, gregkh@linuxfoundation.org,
	jirislaby@kernel.org
  Cc: linux-kernel@vger.kernel.org, linux-mips@vger.kernel.org,
	tsbogend@alpha.franken.de
In-Reply-To: <1fpGAFo1KYyRDeGSGmaDWx6gXbV00C83qU_tep62Zu_cl9JfjpjWg8SKhw6z0owZ9eEMzXR2GnZ_U3V2PVi2R1fuGAmzgK43GYbWkCIBFf8=@proton.me>

Thanks for your notes. Patch withdrawn.

On Wednesday, 15 April 2026 at 09:01, kpursoty@proton.me <kpursoty@proton.me> wrote:

> serial_base_port_shutdown() calls serial_base_port_set_tx(false)
> unconditionally. When a TTY hangup occurs on a port that is also a
> registered kernel console, this permanently disables the console
> transmitter.
>
> uart_hangup() already guards against uart_change_pm(PM_OFF) for console
> ports (see the uart_console() check in uart_hangup()). Apply the same
> guard in serial_base_port_shutdown(): skip TX-disable if the port is a
> registered console.
>
> Without this fix, any path that calls uart_shutdown() on a console port
> - including TIOCSCTTY with force=1 (as used by init systems such as
> procd) - permanently silences all subsequent kernel console output.
>
> Signed-off-by: Kervin Pursoty <kpursoty@proton.me>
> ---
>  drivers/tty/serial/serial_port.c | 11 ++++++++++-
>  1 file changed, 10 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/tty/serial/serial_port.c b/drivers/tty/serial/serial_port.c
> --- a/drivers/tty/serial/serial_port.c
> +++ b/drivers/tty/serial/serial_port.c
> @@ -110,5 +110,14 @@
>  {
>  	struct serial_port_device *port_dev = port->port_dev;
>
> -	serial_base_port_set_tx(port, port_dev, false);
> +	/*
> +	 * Do not disable TX on a console port. When an init system calls
> +	 * TIOCSCTTY with force=1, the kernel hangs up the previous session,
> +	 * triggering uart_hangup() -> uart_shutdown() -> here. Stopping TX
> +	 * kills all kernel console output permanently.
> +	 * uart_hangup() already skips uart_change_pm(PM_OFF) for consoles
> +	 * via uart_console(); apply the same guard here.
> +	 */
> +	if (!uart_console(port))
> +		serial_base_port_set_tx(port, port_dev, false);
>  }
>
>  static DEFINE_RUNTIME_DEV_PM_OPS(serial_port_pm,
>

^ permalink raw reply

* [RFC net-next 2/3] ppp: unify two channel structs
From: Qingfang Deng @ 2026-04-16  8:26 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jiri Kosina, David Sterba, Greg Kroah-Hartman,
	Jiri Slaby, Mitchell Blank Jr, Simon Horman, James Chapman,
	Qingfang Deng, Kees Cook, Yue Haibing, Sebastian Andrzej Siewior,
	Taegu Ha, Kuniyuki Iwashima, Guillaume Nault, Eric Woudstra,
	Arnd Bergmann, Dawid Osuchowski, Breno Leitao, linux-ppp, netdev,
	linux-kernel, linux-serial
  Cc: Paul Mackerras, Jaco Kroon, James Carlson
In-Reply-To: <20260416082656.86963-1-qingfang.deng@linux.dev>

Historically, PPP maintained two separate structures for a channel:
'struct channel' was internal to ppp_generic.c, while 'struct ppp_channel'
was the public interface that drivers were required to embed. This
duplication was redundant and forced drivers to manage the lifecycle of
the public structure.

Unify these two structures into a single 'struct ppp_channel', which is
now internal to ppp_generic.c. Drivers now use a 'ppp_channel_conf'
structure to specify registration parameters and receive an opaque
pointer to the allocated channel.

Key changes:
- ppp_register_channel() and ppp_register_net_channel() now return
  a 'struct ppp_channel *' instead of taking a pointer to a driver-
  embedded structure.
- 'struct ppp_channel_ops' methods now take the driver's 'private'
  pointer directly as their first argument, simplifying driver logic.
- ppp_unregister_channel() now takes the opaque pointer.
- Multilink-specific fields are unified and handled via the new
  configuration structure.

This cleanup simplifies the driver interface and makes the channel
lifecycle management more robust by centralizing allocation in the PPP
generic layer.

Assisted-by: Gemini:gemini-3-flash
Signed-off-by: Qingfang Deng <qingfang.deng@linux.dev>
---
 drivers/net/ppp/ppp_async.c      |  51 +++++-----
 drivers/net/ppp/ppp_generic.c    | 161 +++++++++++++++----------------
 drivers/net/ppp/ppp_synctty.c    |  51 +++++-----
 drivers/net/ppp/pppoe.c          |  34 ++++---
 drivers/net/ppp/pppox.c          |   4 +-
 drivers/net/ppp/pptp.c           |  40 ++++----
 drivers/tty/ipwireless/network.c |  30 +++---
 include/linux/if_pppox.h         |   2 +-
 include/linux/ppp_channel.h      |  49 ++++++----
 net/atm/pppoatm.c                |  61 ++++++------
 net/l2tp/l2tp_ppp.c              |  34 ++++---
 11 files changed, 271 insertions(+), 246 deletions(-)

diff --git a/drivers/net/ppp/ppp_async.c b/drivers/net/ppp/ppp_async.c
index 93a7b0f6c4e7..faa299cc3db9 100644
--- a/drivers/net/ppp/ppp_async.c
+++ b/drivers/net/ppp/ppp_async.c
@@ -67,7 +67,7 @@ struct asyncppp {
 
 	refcount_t	refcnt;
 	struct completion dead;
-	struct ppp_channel chan;	/* interface to generic ppp layer */
+	struct ppp_channel *chan;	/* interface to generic ppp layer */
 	unsigned char	obuf[OBUFSIZE];
 };
 
@@ -95,12 +95,12 @@ MODULE_ALIAS_LDISC(N_PPP);
  * Prototypes.
  */
 static int ppp_async_encode(struct asyncppp *ap);
-static int ppp_async_send(struct ppp_channel *chan, struct sk_buff *skb);
+static int ppp_async_send(void *private, struct sk_buff *skb);
 static int ppp_async_push(struct asyncppp *ap);
 static void ppp_async_flush_output(struct asyncppp *ap);
 static void ppp_async_input(struct asyncppp *ap, const unsigned char *buf,
 			    const u8 *flags, int count);
-static int ppp_async_ioctl(struct ppp_channel *chan, unsigned int cmd,
+static int ppp_async_ioctl(void *private, unsigned int cmd,
 			   unsigned long arg);
 static void ppp_async_process(struct tasklet_struct *t);
 
@@ -155,9 +155,10 @@ static void ap_put(struct asyncppp *ap)
 static int
 ppp_asynctty_open(struct tty_struct *tty)
 {
+	struct ppp_channel_conf conf = {};
+	struct ppp_channel *chan;
 	struct asyncppp *ap;
 	int err;
-	int speed;
 
 	if (tty->ops->write == NULL)
 		return -EOPNOTSUPP;
@@ -185,14 +186,18 @@ ppp_asynctty_open(struct tty_struct *tty)
 	refcount_set(&ap->refcnt, 1);
 	init_completion(&ap->dead);
 
-	ap->chan.private = ap;
-	ap->chan.ops = &async_ops;
-	ap->chan.mtu = PPP_MRU;
-	speed = tty_get_baud_rate(tty);
-	ap->chan.speed = speed;
-	err = ppp_register_channel(&ap->chan);
-	if (err)
+	conf.private = ap;
+	conf.ops = &async_ops;
+#ifdef CONFIG_PPP_MULTILINK
+	conf.mtu = PPP_MRU;
+	conf.speed = tty_get_baud_rate(tty);
+#endif
+	chan = ppp_register_channel(&conf);
+	if (!chan) {
+		err = -ENOMEM;
 		goto out_free;
+	}
+	ap->chan = chan;
 
 	tty->disc_data = ap;
 	tty->receive_room = 65536;
@@ -235,7 +240,7 @@ ppp_asynctty_close(struct tty_struct *tty)
 		wait_for_completion(&ap->dead);
 	tasklet_kill(&ap->tsk);
 
-	ppp_unregister_channel(&ap->chan);
+	ppp_unregister_channel(ap->chan);
 	kfree_skb(ap->rpkt);
 	skb_queue_purge(&ap->rqueue);
 	kfree_skb(ap->tpkt);
@@ -293,14 +298,14 @@ ppp_asynctty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
 	switch (cmd) {
 	case PPPIOCGCHAN:
 		err = -EFAULT;
-		if (put_user(ppp_channel_index(&ap->chan), p))
+		if (put_user(ppp_channel_index(ap->chan), p))
 			break;
 		err = 0;
 		break;
 
 	case PPPIOCGUNIT:
 		err = -EFAULT;
-		if (put_user(ppp_unit_number(&ap->chan), p))
+		if (put_user(ppp_unit_number(ap->chan), p))
 			break;
 		err = 0;
 		break;
@@ -391,9 +396,9 @@ ppp_async_init(void)
  * The following routines provide the PPP channel interface.
  */
 static int
-ppp_async_ioctl(struct ppp_channel *chan, unsigned int cmd, unsigned long arg)
+ppp_async_ioctl(void *private, unsigned int cmd, unsigned long arg)
 {
-	struct asyncppp *ap = chan->private;
+	struct asyncppp *ap = private;
 	void __user *argp = (void __user *)arg;
 	int __user *p = argp;
 	int err, val;
@@ -491,13 +496,13 @@ static void ppp_async_process(struct tasklet_struct *t)
 	/* process received packets */
 	while ((skb = skb_dequeue(&ap->rqueue)) != NULL) {
 		if (skb->cb[0])
-			ppp_input_error(&ap->chan);
-		ppp_input(&ap->chan, skb);
+			ppp_input_error(ap->chan);
+		ppp_input(ap->chan, skb);
 	}
 
 	/* try to push more stuff out */
 	if (test_bit(XMIT_WAKEUP, &ap->xmit_flags) && ppp_async_push(ap))
-		ppp_output_wakeup(&ap->chan);
+		ppp_output_wakeup(ap->chan);
 }
 
 /*
@@ -620,9 +625,9 @@ ppp_async_encode(struct asyncppp *ap)
  * at some later time.
  */
 static int
-ppp_async_send(struct ppp_channel *chan, struct sk_buff *skb)
+ppp_async_send(void *private, struct sk_buff *skb)
 {
-	struct asyncppp *ap = chan->private;
+	struct asyncppp *ap = private;
 
 	ppp_async_push(ap);
 
@@ -733,7 +738,7 @@ ppp_async_flush_output(struct asyncppp *ap)
 	}
 	spin_unlock_bh(&ap->xmit_lock);
 	if (done)
-		ppp_output_wakeup(&ap->chan);
+		ppp_output_wakeup(ap->chan);
 }
 
 /*
@@ -992,7 +997,7 @@ static void async_lcp_peek(struct asyncppp *ap, unsigned char *data,
 			if (inbound)
 				ap->mru = val;
 			else
-				ap->chan.mtu = val;
+				ppp_channel_update_mtu(ap->chan, val);
 			break;
 		case LCP_ASYNCMAP:
 			val = get_unaligned_be32(data + 2);
diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c
index fd2889e374c9..882709551bbd 100644
--- a/drivers/net/ppp/ppp_generic.c
+++ b/drivers/net/ppp/ppp_generic.c
@@ -106,7 +106,7 @@ struct ppp_file {
 #define PF_TO_X(pf, X)		container_of(pf, X, file)
 
 #define PF_TO_PPP(pf)		PF_TO_X(pf, struct ppp)
-#define PF_TO_CHANNEL(pf)	PF_TO_X(pf, struct channel)
+#define PF_TO_CHANNEL(pf)	PF_TO_X(pf, struct ppp_channel)
 
 struct ppp_xmit_recursion {
 	struct task_struct *owner;
@@ -172,10 +172,11 @@ struct ppp {
  * Private data structure for each channel.
  * This includes the data structure used for multilink.
  */
-struct channel {
+struct ppp_channel {
 	struct ppp_file	file;		/* stuff for read/write/poll */
 	struct list_head list;		/* link in all/new_channels list */
-	struct ppp_channel *chan;	/* public channel data structure */
+	const struct ppp_channel_ops *ops; /* operations for this channel */
+	void *private;			/* channel private data */
 	struct rw_semaphore chan_sem;	/* protects `chan' during chan ioctl */
 	spinlock_t	downl;		/* protects `chan', file.xq dequeue */
 	struct ppp __rcu *ppp;		/* ppp unit we're connected to */
@@ -183,11 +184,13 @@ struct channel {
 	netns_tracker	ns_tracker;
 	struct list_head clist;		/* link in list of channels per unit */
 	spinlock_t	upl;		/* protects `ppp' and 'bridge' */
-	struct channel __rcu *bridge;	/* "bridged" ppp channel */
+	struct ppp_channel __rcu *bridge;	/* "bridged" ppp channel */
+	bool direct_xmit;		/* no qdisc, xmit directly */
 #ifdef CONFIG_PPP_MULTILINK
 	u8		avail;		/* flag used in multilink stuff */
 	u8		had_frag;	/* >= 1 fragments have been sent */
 	u32		lastseq;	/* MP: last sequence # received */
+	int		mtu;		/* max transmit packet size */
 	int		speed;		/* speed of the corresponding ppp channel*/
 #endif /* CONFIG_PPP_MULTILINK */
 };
@@ -265,16 +268,16 @@ static int ppp_unattached_ioctl(struct net *net, struct ppp_file *pf,
 static void ppp_xmit_process(struct ppp *ppp, struct sk_buff *skb);
 static int ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb);
 static int ppp_push(struct ppp *ppp, struct sk_buff *skb);
-static void ppp_channel_push(struct channel *pch);
+static void ppp_channel_push(struct ppp_channel *pch);
 static void ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb,
-			      struct channel *pch);
+			      struct ppp_channel *pch);
 static void ppp_receive_error(struct ppp *ppp);
 static void ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb);
 static struct sk_buff *ppp_decompress_frame(struct ppp *ppp,
 					    struct sk_buff *skb);
 #ifdef CONFIG_PPP_MULTILINK
 static void ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb,
-				struct channel *pch);
+				struct ppp_channel *pch);
 static void ppp_mp_insert(struct ppp *ppp, struct sk_buff *skb);
 static struct sk_buff *ppp_mp_reconstruct(struct ppp *ppp);
 static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb);
@@ -288,10 +291,10 @@ static int ppp_create_interface(struct net *net, struct file *file, int *unit);
 static void init_ppp_file(struct ppp_file *pf, int kind);
 static void ppp_release_interface(struct ppp *ppp);
 static struct ppp *ppp_find_unit(struct ppp_net *pn, int unit);
-static struct channel *ppp_find_channel(struct ppp_net *pn, int unit);
-static int ppp_connect_channel(struct channel *pch, int unit);
-static int ppp_disconnect_channel(struct channel *pch);
-static void ppp_release_channel(struct channel *pch);
+static struct ppp_channel *ppp_find_channel(struct ppp_net *pn, int unit);
+static int ppp_connect_channel(struct ppp_channel *pch, int unit);
+static int ppp_disconnect_channel(struct ppp_channel *pch);
+static void ppp_release_channel(struct ppp_channel *pch);
 static int unit_get(struct idr *p, void *ptr, int min);
 static int unit_set(struct idr *p, void *ptr, int n);
 static void unit_put(struct idr *p, int n);
@@ -638,7 +641,7 @@ static struct bpf_prog *compat_ppp_get_filter(struct sock_fprog32 __user *p)
  * Once successfully bridged, each channel holds a reference on the other
  * to prevent it being freed while the bridge is extant.
  */
-static int ppp_bridge_channels(struct channel *pch, struct channel *pchb)
+static int ppp_bridge_channels(struct ppp_channel *pch, struct ppp_channel *pchb)
 {
 	spin_lock(&pch->upl);
 	if (rcu_dereference_protected(pch->ppp, lockdep_is_held(&pch->upl)) ||
@@ -676,9 +679,9 @@ static int ppp_bridge_channels(struct channel *pch, struct channel *pchb)
 	return -EALREADY;
 }
 
-static int ppp_unbridge_channels(struct channel *pch)
+static int ppp_unbridge_channels(struct ppp_channel *pch)
 {
-	struct channel *pchb, *pchbb;
+	struct ppp_channel *pchb, *pchbb;
 
 	spin_lock(&pch->upl);
 	pchb = rcu_dereference_protected(pch->bridge, lockdep_is_held(&pch->upl));
@@ -745,8 +748,7 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 	}
 
 	if (pf->kind == CHANNEL) {
-		struct channel *pch, *pchb;
-		struct ppp_channel *chan;
+		struct ppp_channel *pch, *pchb;
 		struct ppp_net *pn;
 
 		pch = PF_TO_CHANNEL(pf);
@@ -788,10 +790,9 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 
 		default:
 			down_read(&pch->chan_sem);
-			chan = pch->chan;
 			err = -ENOTTY;
-			if (!pch->file.dead && chan->ops->ioctl)
-				err = chan->ops->ioctl(chan, cmd, arg);
+			if (!pch->file.dead && pch->ops->ioctl)
+				err = pch->ops->ioctl(pch->private, cmd, arg);
 			up_read(&pch->chan_sem);
 		}
 		goto out;
@@ -1044,7 +1045,7 @@ static int ppp_unattached_ioctl(struct net *net, struct ppp_file *pf,
 {
 	int unit, err = -EFAULT;
 	struct ppp *ppp;
-	struct channel *chan;
+	struct ppp_channel *chan;
 	struct ppp_net *pn;
 	int __user *p = (int __user *)arg;
 
@@ -1586,21 +1587,19 @@ static int ppp_fill_forward_path(struct net_device_path_ctx *ctx,
 				 struct net_device_path *path)
 {
 	struct ppp *ppp = netdev_priv(ctx->dev);
-	struct ppp_channel *chan;
-	struct channel *pch;
+	struct ppp_channel *pch;
 
 	if (ppp->flags & SC_MULTILINK)
 		return -EOPNOTSUPP;
 
-	pch = list_first_or_null_rcu(&ppp->channels, struct channel, clist);
+	pch = list_first_or_null_rcu(&ppp->channels, struct ppp_channel, clist);
 	if (!pch)
 		return -ENODEV;
 
-	chan = pch->chan;
-	if (!chan->ops->fill_forward_path)
+	if (!pch->ops->fill_forward_path)
 		return -EOPNOTSUPP;
 
-	return chan->ops->fill_forward_path(ctx, path, chan);
+	return pch->ops->fill_forward_path(ctx, path, pch->private);
 }
 
 static const struct net_device_ops ppp_netdev_ops = {
@@ -1901,7 +1900,6 @@ static int
 ppp_push(struct ppp *ppp, struct sk_buff *skb)
 {
 	struct list_head *list;
-	struct channel *pch;
 
 	list = &ppp->channels;
 	if (list_empty(list)) {
@@ -1911,15 +1909,14 @@ ppp_push(struct ppp *ppp, struct sk_buff *skb)
 	}
 
 	if ((ppp->flags & SC_MULTILINK) == 0) {
-		struct ppp_channel *chan;
+		struct ppp_channel *pch;
 		int ret;
 		/* not doing multilink: send it down the first channel */
 		list = list->next;
-		pch = list_entry(list, struct channel, clist);
+		pch = list_entry(list, struct ppp_channel, clist);
 
 		spin_lock(&pch->downl);
-		chan = pch->chan;
-		if (unlikely(!chan->direct_xmit && skb_linearize(skb))) {
+		if (unlikely(!pch->direct_xmit && skb_linearize(skb))) {
 			/* channel requires a linear skb but linearization
 			 * failed
 			 */
@@ -1928,7 +1925,7 @@ ppp_push(struct ppp *ppp, struct sk_buff *skb)
 			goto out;
 		}
 
-		ret = chan->ops->start_xmit(chan, skb);
+		ret = pch->ops->start_xmit(pch->private, skb);
 
 out:
 		spin_unlock(&pch->downl);
@@ -1967,9 +1964,8 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb)
 	int totfree;
 	unsigned char *p, *q;
 	struct list_head *list;
-	struct channel *pch;
+	struct ppp_channel *pch;
 	struct sk_buff *frag;
-	struct ppp_channel *chan;
 
 	totspeed = 0; /*total bitrate of the bundle*/
 	nfree = 0; /* # channels which have no packet already queued */
@@ -1984,8 +1980,6 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb)
 	list_for_each_entry(pch, &ppp->channels, clist) {
 		pch->avail = 1;
 		navail++;
-		pch->speed = pch->chan->speed;
-
 		if (skb_queue_empty(&pch->file.xq) || !pch->had_frag) {
 			if (pch->speed == 0)
 				nzero++;
@@ -2041,7 +2035,7 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb)
 			i = 0;
 			continue;
 		}
-		pch = list_entry(list, struct channel, clist);
+		pch = list_entry(list, struct ppp_channel, clist);
 		++i;
 		if (!pch->avail)
 			continue;
@@ -2108,7 +2102,7 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb)
 		 * MTU counts only the payload excluding the protocol field.
 		 * (RFC1661 Section 2)
 		 */
-		mtu = pch->chan->mtu - (hdrlen - 2);
+		mtu = pch->mtu - (hdrlen - 2);
 		if (mtu < 4)
 			mtu = 4;
 		if (flen > mtu)
@@ -2135,9 +2129,8 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb)
 		memcpy(q + hdrlen, p, flen);
 
 		/* try to send it down the channel */
-		chan = pch->chan;
 		if (!skb_queue_empty(&pch->file.xq) ||
-			!chan->ops->start_xmit(chan, frag))
+			!pch->ops->start_xmit(pch->private, frag))
 			skb_queue_tail(&pch->file.xq, frag);
 		pch->had_frag = 1;
 		p += flen;
@@ -2162,7 +2155,7 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb)
 #endif /* CONFIG_PPP_MULTILINK */
 
 /* Try to send data out on a channel */
-static void __ppp_channel_push(struct channel *pch, struct ppp *ppp)
+static void __ppp_channel_push(struct ppp_channel *pch, struct ppp *ppp)
 {
 	struct sk_buff *skb;
 
@@ -2170,7 +2163,7 @@ static void __ppp_channel_push(struct channel *pch, struct ppp *ppp)
 	if (!pch->file.dead) {
 		while (!skb_queue_empty(&pch->file.xq)) {
 			skb = skb_dequeue(&pch->file.xq);
-			if (!pch->chan->ops->start_xmit(pch->chan, skb)) {
+			if (!pch->ops->start_xmit(pch->private, skb)) {
 				/* put the packet back and try again later */
 				skb_queue_head(&pch->file.xq, skb);
 				break;
@@ -2192,7 +2185,7 @@ static void __ppp_channel_push(struct channel *pch, struct ppp *ppp)
 	}
 }
 
-static void ppp_channel_push(struct channel *pch)
+static void ppp_channel_push(struct ppp_channel *pch)
 {
 	struct ppp_xmit_recursion *xmit_recursion;
 	struct ppp *ppp;
@@ -2223,7 +2216,7 @@ struct ppp_mp_skb_parm {
 #define PPP_MP_CB(skb)	((struct ppp_mp_skb_parm *)((skb)->cb))
 
 static inline void
-ppp_do_recv(struct ppp *ppp, struct sk_buff *skb, struct channel *pch)
+ppp_do_recv(struct ppp *ppp, struct sk_buff *skb, struct ppp_channel *pch)
 {
 	ppp_recv_lock(ppp);
 	if (!ppp->closing)
@@ -2278,9 +2271,9 @@ static bool ppp_decompress_proto(struct sk_buff *skb)
  * If not, the caller must handle the frame by normal recv mechanisms.
  * Returns true if the frame is consumed, false otherwise.
  */
-static bool ppp_channel_bridge_input(struct channel *pch, struct sk_buff *skb)
+static bool ppp_channel_bridge_input(struct ppp_channel *pch, struct sk_buff *skb)
 {
-	struct channel *pchb;
+	struct ppp_channel *pchb;
 
 	rcu_read_lock();
 	pchb = rcu_dereference(pch->bridge);
@@ -2295,7 +2288,7 @@ static bool ppp_channel_bridge_input(struct channel *pch, struct sk_buff *skb)
 	}
 
 	skb_scrub_packet(skb, !net_eq(pch->chan_net, pchb->chan_net));
-	if (!pchb->chan->ops->start_xmit(pchb->chan, skb))
+	if (!pchb->ops->start_xmit(pchb->private, skb))
 		kfree_skb(skb);
 
 outl:
@@ -2308,9 +2301,8 @@ static bool ppp_channel_bridge_input(struct channel *pch, struct sk_buff *skb)
 }
 
 void
-ppp_input(struct ppp_channel *chan, struct sk_buff *skb)
+ppp_input(struct ppp_channel *pch, struct sk_buff *skb)
 {
-	struct channel *pch = chan->ppp;
 	struct ppp *ppp;
 	int proto;
 
@@ -2352,9 +2344,8 @@ ppp_input(struct ppp_channel *chan, struct sk_buff *skb)
 }
 
 void
-ppp_input_error(struct ppp_channel *chan)
+ppp_input_error(struct ppp_channel *pch)
 {
-	struct channel *pch = chan->ppp;
 	struct ppp *ppp;
 
 	if (!pch)
@@ -2375,7 +2366,7 @@ ppp_input_error(struct ppp_channel *chan)
  * The receive side of the ppp unit is locked.
  */
 static void
-ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch)
+ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb, struct ppp_channel *pch)
 {
 	skb_checksum_complete_unset(skb);
 #ifdef CONFIG_PPP_MULTILINK
@@ -2611,10 +2602,10 @@ ppp_decompress_frame(struct ppp *ppp, struct sk_buff *skb)
  * as many completed frames as we can.
  */
 static void
-ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch)
+ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb, struct ppp_channel *pch)
 {
 	u32 mask, seq;
-	struct channel *ch;
+	struct ppp_channel *ch;
 	int mphdrlen = (ppp->flags & SC_MP_SHORTSEQ)? MPHDRLEN_SSN: MPHDRLEN;
 
 	if (!pskb_may_pull(skb, mphdrlen + 1) || ppp->mrru == 0)
@@ -2885,6 +2876,13 @@ ppp_mp_reconstruct(struct ppp *ppp)
 
 	return skb;
 }
+
+/* Update the MTU of a multilink channel */
+void ppp_channel_update_mtu(struct ppp_channel *pch, int mtu)
+{
+	pch->mtu = mtu;
+}
+EXPORT_SYMBOL(ppp_channel_update_mtu);
 #endif /* CONFIG_PPP_MULTILINK */
 
 /*
@@ -2892,29 +2890,33 @@ ppp_mp_reconstruct(struct ppp *ppp)
  */
 
 /* Create a new, unattached ppp channel. */
-int ppp_register_channel(struct ppp_channel *chan)
+struct ppp_channel *ppp_register_channel(const struct ppp_channel_conf *conf)
 {
-	return ppp_register_net_channel(current->nsproxy->net_ns, chan);
+	return ppp_register_net_channel(current->nsproxy->net_ns, conf);
 }
 
 /* Create a new, unattached ppp channel for specified net. */
-int ppp_register_net_channel(struct net *net, struct ppp_channel *chan)
+struct ppp_channel *ppp_register_net_channel(struct net *net,
+					     const struct ppp_channel_conf *conf)
 {
-	struct channel *pch;
+	struct ppp_channel *pch;
 	struct ppp_net *pn;
 
-	pch = kzalloc_obj(struct channel);
+	pch = kzalloc_obj(struct ppp_channel);
 	if (!pch)
-		return -ENOMEM;
+		return NULL;
 
 	pn = ppp_pernet(net);
 
-	pch->chan = chan;
 	pch->chan_net = get_net_track(net, &pch->ns_tracker, GFP_KERNEL);
-	chan->ppp = pch;
 	init_ppp_file(&pch->file, CHANNEL);
-	pch->file.hdrlen = chan->hdrlen;
+	pch->file.hdrlen = conf->hdrlen;
+	pch->ops = conf->ops;
+	pch->private = conf->private;
+	pch->direct_xmit = conf->direct_xmit;
 #ifdef CONFIG_PPP_MULTILINK
+	pch->speed = conf->speed;
+	pch->mtu = conf->mtu;
 	pch->lastseq = -1;
 #endif /* CONFIG_PPP_MULTILINK */
 	init_rwsem(&pch->chan_sem);
@@ -2927,16 +2929,14 @@ int ppp_register_net_channel(struct net *net, struct ppp_channel *chan)
 	atomic_inc(&channel_count);
 	spin_unlock_bh(&pn->all_channels_lock);
 
-	return 0;
+	return pch;
 }
 
 /*
  * Return the index of a channel.
  */
-int ppp_channel_index(struct ppp_channel *chan)
+int ppp_channel_index(struct ppp_channel *pch)
 {
-	struct channel *pch = chan->ppp;
-
 	if (pch)
 		return pch->file.index;
 	return -1;
@@ -2945,9 +2945,8 @@ int ppp_channel_index(struct ppp_channel *chan)
 /*
  * Return the PPP unit number to which a channel is connected.
  */
-int ppp_unit_number(struct ppp_channel *chan)
+int ppp_unit_number(struct ppp_channel *pch)
 {
-	struct channel *pch = chan->ppp;
 	struct ppp *ppp;
 	int unit = -1;
 
@@ -2965,9 +2964,8 @@ int ppp_unit_number(struct ppp_channel *chan)
  * Return the PPP device interface name of a channel.
  * Caller must hold RCU read lock.
  */
-char *ppp_dev_name(struct ppp_channel *chan)
+char *ppp_dev_name(struct ppp_channel *pch)
 {
-	struct channel *pch = chan->ppp;
 	char *name = NULL;
 	struct ppp *ppp;
 
@@ -2985,16 +2983,13 @@ char *ppp_dev_name(struct ppp_channel *chan)
  * This must be called in process context.
  */
 void
-ppp_unregister_channel(struct ppp_channel *chan)
+ppp_unregister_channel(struct ppp_channel *pch)
 {
-	struct channel *pch = chan->ppp;
 	struct ppp_net *pn;
 
 	if (!pch)
 		return;		/* should never happen */
 
-	chan->ppp = NULL;
-
 	/*
 	 * This ensures that we have returned from any calls into
 	 * the channel's start_xmit or ioctl routine before we proceed.
@@ -3023,10 +3018,8 @@ ppp_unregister_channel(struct ppp_channel *chan)
  * This should be called at BH/softirq level, not interrupt level.
  */
 void
-ppp_output_wakeup(struct ppp_channel *chan)
+ppp_output_wakeup(struct ppp_channel *pch)
 {
-	struct channel *pch = chan->ppp;
-
 	if (!pch)
 		return;
 	ppp_channel_push(pch);
@@ -3459,10 +3452,10 @@ ppp_find_unit(struct ppp_net *pn, int unit)
  * we move it to the all_channels list.  This is for speed
  * when we have a lot of channels in use.
  */
-static struct channel *
+static struct ppp_channel *
 ppp_find_channel(struct ppp_net *pn, int unit)
 {
-	struct channel *pch;
+	struct ppp_channel *pch;
 
 	list_for_each_entry(pch, &pn->new_channels, list) {
 		if (pch->file.index == unit) {
@@ -3483,7 +3476,7 @@ ppp_find_channel(struct ppp_net *pn, int unit)
  * Connect a PPP channel to a PPP interface unit.
  */
 static int
-ppp_connect_channel(struct channel *pch, int unit)
+ppp_connect_channel(struct ppp_channel *pch, int unit)
 {
 	struct ppp *ppp;
 	struct ppp_net *pn;
@@ -3511,7 +3504,7 @@ ppp_connect_channel(struct channel *pch, int unit)
 		ret = -ENOTCONN;
 		goto outl;
 	}
-	if (pch->chan->direct_xmit)
+	if (pch->direct_xmit)
 		ppp->dev->priv_flags |= IFF_NO_QUEUE;
 	else
 		ppp->dev->priv_flags &= ~IFF_NO_QUEUE;
@@ -3539,7 +3532,7 @@ ppp_connect_channel(struct channel *pch, int unit)
  * Disconnect a channel from its ppp unit.
  */
 static int
-ppp_disconnect_channel(struct channel *pch)
+ppp_disconnect_channel(struct ppp_channel *pch)
 {
 	struct ppp *ppp;
 	int err = -EINVAL;
@@ -3565,7 +3558,7 @@ ppp_disconnect_channel(struct channel *pch)
  * Drop a reference to a ppp channel and free its memory if the refcount reaches
  * zero.
  */
-static void ppp_release_channel(struct channel *pch)
+static void ppp_release_channel(struct ppp_channel *pch)
 {
 	if (!refcount_dec_and_test(&pch->file.refcnt))
 		return;
diff --git a/drivers/net/ppp/ppp_synctty.c b/drivers/net/ppp/ppp_synctty.c
index b7f243b416f8..d84c267f4da1 100644
--- a/drivers/net/ppp/ppp_synctty.c
+++ b/drivers/net/ppp/ppp_synctty.c
@@ -71,7 +71,7 @@ struct syncppp {
 
 	refcount_t	refcnt;
 	struct completion dead_cmp;
-	struct ppp_channel chan;	/* interface to generic ppp layer */
+	struct ppp_channel *chan;	/* interface to generic ppp layer */
 };
 
 /* Bit numbers in xmit_flags */
@@ -87,8 +87,8 @@ struct syncppp {
  * Prototypes.
  */
 static struct sk_buff* ppp_sync_txmunge(struct syncppp *ap, struct sk_buff *);
-static int ppp_sync_send(struct ppp_channel *chan, struct sk_buff *skb);
-static int ppp_sync_ioctl(struct ppp_channel *chan, unsigned int cmd,
+static int ppp_sync_send(void *private, struct sk_buff *skb);
+static int ppp_sync_ioctl(void *private, unsigned int cmd,
 			  unsigned long arg);
 static void ppp_sync_process(struct tasklet_struct *t);
 static int ppp_sync_push(struct syncppp *ap);
@@ -155,9 +155,10 @@ static void sp_put(struct syncppp *ap)
 static int
 ppp_sync_open(struct tty_struct *tty)
 {
+	struct ppp_channel_conf conf = {};
+	struct ppp_channel *chan;
 	struct syncppp *ap;
 	int err;
-	int speed;
 
 	if (tty->ops->write == NULL)
 		return -EOPNOTSUPP;
@@ -182,15 +183,19 @@ ppp_sync_open(struct tty_struct *tty)
 	refcount_set(&ap->refcnt, 1);
 	init_completion(&ap->dead_cmp);
 
-	ap->chan.private = ap;
-	ap->chan.ops = &sync_ops;
-	ap->chan.mtu = PPP_MRU;
-	ap->chan.hdrlen = 2;	/* for A/C bytes */
-	speed = tty_get_baud_rate(tty);
-	ap->chan.speed = speed;
-	err = ppp_register_channel(&ap->chan);
-	if (err)
+	conf.private = ap;
+	conf.ops = &sync_ops;
+	conf.hdrlen = 2;	/* for A/C bytes */
+#ifdef CONFIG_PPP_MULTILINK
+	conf.mtu = PPP_MRU;
+	conf.speed = tty_get_baud_rate(tty);
+#endif
+	chan = ppp_register_channel(&conf);
+	if (!chan) {
+		err = -ENOMEM;
 		goto out_free;
+	}
+	ap->chan = chan;
 
 	tty->disc_data = ap;
 	tty->receive_room = 65536;
@@ -233,7 +238,7 @@ ppp_sync_close(struct tty_struct *tty)
 		wait_for_completion(&ap->dead_cmp);
 	tasklet_kill(&ap->tsk);
 
-	ppp_unregister_channel(&ap->chan);
+	ppp_unregister_channel(ap->chan);
 	skb_queue_purge(&ap->rqueue);
 	kfree_skb(ap->tpkt);
 	kfree(ap);
@@ -285,14 +290,14 @@ ppp_synctty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
 	switch (cmd) {
 	case PPPIOCGCHAN:
 		err = -EFAULT;
-		if (put_user(ppp_channel_index(&ap->chan), p))
+		if (put_user(ppp_channel_index(ap->chan), p))
 			break;
 		err = 0;
 		break;
 
 	case PPPIOCGUNIT:
 		err = -EFAULT;
-		if (put_user(ppp_unit_number(&ap->chan), p))
+		if (put_user(ppp_unit_number(ap->chan), p))
 			break;
 		err = 0;
 		break;
@@ -383,9 +388,9 @@ ppp_sync_init(void)
  * The following routines provide the PPP channel interface.
  */
 static int
-ppp_sync_ioctl(struct ppp_channel *chan, unsigned int cmd, unsigned long arg)
+ppp_sync_ioctl(void *private, unsigned int cmd, unsigned long arg)
 {
-	struct syncppp *ap = chan->private;
+	struct syncppp *ap = private;
 	int err, val;
 	u32 accm[8];
 	void __user *argp = (void __user *)arg;
@@ -483,16 +488,16 @@ static void ppp_sync_process(struct tasklet_struct *t)
 	while ((skb = skb_dequeue(&ap->rqueue)) != NULL) {
 		if (skb->len == 0) {
 			/* zero length buffers indicate error */
-			ppp_input_error(&ap->chan);
+			ppp_input_error(ap->chan);
 			kfree_skb(skb);
 		}
 		else
-			ppp_input(&ap->chan, skb);
+			ppp_input(ap->chan, skb);
 	}
 
 	/* try to push more stuff out */
 	if (test_bit(XMIT_WAKEUP, &ap->xmit_flags) && ppp_sync_push(ap))
-		ppp_output_wakeup(&ap->chan);
+		ppp_output_wakeup(ap->chan);
 }
 
 /*
@@ -562,9 +567,9 @@ ppp_sync_txmunge(struct syncppp *ap, struct sk_buff *skb)
  * at some later time.
  */
 static int
-ppp_sync_send(struct ppp_channel *chan, struct sk_buff *skb)
+ppp_sync_send(void *private, struct sk_buff *skb)
 {
-	struct syncppp *ap = chan->private;
+	struct syncppp *ap = private;
 
 	ppp_sync_push(ap);
 
@@ -649,7 +654,7 @@ ppp_sync_flush_output(struct syncppp *ap)
 	}
 	spin_unlock_bh(&ap->xmit_lock);
 	if (done)
-		ppp_output_wakeup(&ap->chan);
+		ppp_output_wakeup(ap->chan);
 }
 
 /*
diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c
index d546a7af0d54..47d36d071775 100644
--- a/drivers/net/ppp/pppoe.c
+++ b/drivers/net/ppp/pppoe.c
@@ -357,7 +357,7 @@ static int pppoe_rcv_core(struct sock *sk, struct sk_buff *skb)
 	 */
 
 	if (sk->sk_state & PPPOX_BOUND) {
-		ppp_input(&po->chan, skb);
+		ppp_input(po->chan, skb);
 	} else {
 		if (sock_queue_rcv_skb(sk, skb))
 			goto abort_kfree;
@@ -625,7 +625,7 @@ static int pppoe_connect(struct socket *sock, struct sockaddr_unsized *uservaddr
 
 		po->pppoe_ifindex = 0;
 		memset(&po->pppoe_pa, 0, sizeof(po->pppoe_pa));
-		memset(&po->chan, 0, sizeof(po->chan));
+		po->chan = NULL;
 		po->next = NULL;
 		po->num = 0;
 
@@ -634,6 +634,9 @@ static int pppoe_connect(struct socket *sock, struct sockaddr_unsized *uservaddr
 
 	/* Re-bind in session stage only */
 	if (stage_session(sp->sa_addr.pppoe.sid)) {
+		struct ppp_channel_conf conf = {};
+		struct ppp_channel *chan;
+
 		error = -ENODEV;
 		net = sock_net(sk);
 		dev = dev_get_by_name(net, sp->sa_addr.pppoe.dev);
@@ -657,20 +660,23 @@ static int pppoe_connect(struct socket *sock, struct sockaddr_unsized *uservaddr
 		if (error < 0)
 			goto err_put;
 
-		po->chan.hdrlen = (sizeof(struct pppoe_hdr) +
+		conf.hdrlen = (sizeof(struct pppoe_hdr) +
 				   dev->hard_header_len);
+#ifdef CONFIG_PPP_MULTILINK
+		conf.mtu = dev->mtu - sizeof(struct pppoe_hdr) - 2;
+#endif
+		conf.private = sk;
+		conf.ops = &pppoe_chan_ops;
+		conf.direct_xmit = true;
 
-		po->chan.mtu = dev->mtu - sizeof(struct pppoe_hdr) - 2;
-		po->chan.private = sk;
-		po->chan.ops = &pppoe_chan_ops;
-		po->chan.direct_xmit = true;
-
-		error = ppp_register_net_channel(dev_net(dev), &po->chan);
-		if (error) {
+		chan = ppp_register_net_channel(dev_net(dev), &conf);
+		if (!chan) {
+			error = -ENOMEM;
 			delete_item(pn, po->pppoe_pa.sid,
 				    po->pppoe_pa.remote, po->pppoe_ifindex);
 			goto err_put;
 		}
+		po->chan = chan;
 
 		sk->sk_state = PPPOX_CONNECTED;
 	}
@@ -891,17 +897,17 @@ static int __pppoe_xmit(struct sock *sk, struct sk_buff *skb)
  * sends PPP frame over PPPoE socket
  *
  ***********************************************************************/
-static int pppoe_xmit(struct ppp_channel *chan, struct sk_buff *skb)
+static int pppoe_xmit(void *private, struct sk_buff *skb)
 {
-	struct sock *sk = chan->private;
+	struct sock *sk = private;
 	return __pppoe_xmit(sk, skb);
 }
 
 static int pppoe_fill_forward_path(struct net_device_path_ctx *ctx,
 				   struct net_device_path *path,
-				   const struct ppp_channel *chan)
+				   void *private)
 {
-	struct sock *sk = chan->private;
+	struct sock *sk = private;
 	struct pppox_sock *po = pppox_sk(sk);
 	struct net_device *dev = po->pppoe_dev;
 
diff --git a/drivers/net/ppp/pppox.c b/drivers/net/ppp/pppox.c
index 5861a2f6ce3e..df4fb23a926d 100644
--- a/drivers/net/ppp/pppox.c
+++ b/drivers/net/ppp/pppox.c
@@ -55,7 +55,7 @@ void pppox_unbind_sock(struct sock *sk)
 	/* Clear connection to ppp device, if attached. */
 
 	if (sk->sk_state & (PPPOX_BOUND | PPPOX_CONNECTED)) {
-		ppp_unregister_channel(&pppox_sk(sk)->chan);
+		ppp_unregister_channel(pppox_sk(sk)->chan);
 		sk->sk_state = PPPOX_DEAD;
 	}
 }
@@ -80,7 +80,7 @@ int pppox_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
 			break;
 
 		rc = -EINVAL;
-		index = ppp_channel_index(&po->chan);
+		index = ppp_channel_index(po->chan);
 		if (put_user(index , (int __user *) arg))
 			break;
 
diff --git a/drivers/net/ppp/pptp.c b/drivers/net/ppp/pptp.c
index cc8c102122d8..e49abbe63bf1 100644
--- a/drivers/net/ppp/pptp.c
+++ b/drivers/net/ppp/pptp.c
@@ -146,9 +146,9 @@ static struct rtable *pptp_route_output(const struct pppox_sock *po,
 	return ip_route_output_flow(net, fl4, sk);
 }
 
-static int pptp_xmit(struct ppp_channel *chan, struct sk_buff *skb)
+static int pptp_xmit(void *private, struct sk_buff *skb)
 {
-	struct sock *sk = chan->private;
+	struct sock *sk = private;
 	struct pppox_sock *po = pppox_sk(sk);
 	struct net *net = sock_net(sk);
 	struct pptp_opt *opt = &po->proto.pptp;
@@ -338,7 +338,7 @@ static int pptp_rcv_core(struct sock *sk, struct sk_buff *skb)
 
 		skb->ip_summed = CHECKSUM_NONE;
 		skb_set_network_header(skb, skb->head-skb->data);
-		ppp_input(&po->chan, skb);
+		ppp_input(po->chan, skb);
 
 		return NET_RX_SUCCESS;
 	}
@@ -422,6 +422,8 @@ static int pptp_connect(struct socket *sock, struct sockaddr_unsized *uservaddr,
 	struct sockaddr_pppox *sp = (struct sockaddr_pppox *) uservaddr;
 	struct pppox_sock *po = pppox_sk(sk);
 	struct pptp_opt *opt = &po->proto.pptp;
+	struct ppp_channel_conf conf = {};
+	struct ppp_channel *chan;
 	struct rtable *rt;
 	struct flowi4 fl4;
 	int error = 0;
@@ -453,9 +455,6 @@ static int pptp_connect(struct socket *sock, struct sockaddr_unsized *uservaddr,
 		goto end;
 	}
 
-	po->chan.private = sk;
-	po->chan.ops = &pptp_chan_ops;
-
 	rt = pptp_route_output(po, &fl4);
 	if (IS_ERR(rt)) {
 		error = -EHOSTUNREACH;
@@ -463,18 +462,23 @@ static int pptp_connect(struct socket *sock, struct sockaddr_unsized *uservaddr,
 	}
 	sk_setup_caps(sk, &rt->dst);
 
-	po->chan.mtu = dst_mtu(&rt->dst);
-	if (!po->chan.mtu)
-		po->chan.mtu = PPP_MRU;
-	po->chan.mtu -= PPTP_HEADER_OVERHEAD;
-
-	po->chan.hdrlen = 2 + sizeof(struct pptp_gre_header);
-	po->chan.direct_xmit = true;
-	error = ppp_register_channel(&po->chan);
-	if (error) {
+	conf.private = sk;
+	conf.ops = &pptp_chan_ops;
+	conf.hdrlen = 2 + sizeof(struct pptp_gre_header);
+	conf.direct_xmit = true;
+#ifdef CONFIG_PPP_MULTILINK
+	conf.mtu = dst_mtu(&rt->dst);
+	if (!conf.mtu)
+		conf.mtu = PPP_MRU;
+	conf.mtu -= PPTP_HEADER_OVERHEAD;
+#endif
+	chan = ppp_register_channel(&conf);
+	if (!chan) {
+		error = -ENOMEM;
 		pr_err("PPTP: failed to register PPP channel (%d)\n", error);
 		goto end;
 	}
+	po->chan = chan;
 
 	opt->dst_addr = sp->sa_addr.pptp;
 	sk->sk_state |= PPPOX_CONNECTED;
@@ -577,10 +581,10 @@ static int pptp_create(struct net *net, struct socket *sock, int kern)
 	return error;
 }
 
-static int pptp_ppp_ioctl(struct ppp_channel *chan, unsigned int cmd,
-	unsigned long arg)
+static int pptp_ppp_ioctl(void *private, unsigned int cmd,
+			  unsigned long arg)
 {
-	struct sock *sk = chan->private;
+	struct sock *sk = private;
 	struct pppox_sock *po = pppox_sk(sk);
 	struct pptp_opt *opt = &po->proto.pptp;
 	void __user *argp = (void __user *)arg;
diff --git a/drivers/tty/ipwireless/network.c b/drivers/tty/ipwireless/network.c
index ad2c5157a018..7ac5a2d02d44 100644
--- a/drivers/tty/ipwireless/network.c
+++ b/drivers/tty/ipwireless/network.c
@@ -88,10 +88,10 @@ static void notify_packet_sent(void *callback_data, unsigned int packet_length)
 /*
  * Called by the ppp system when it has a packet to send to the hardware.
  */
-static int ipwireless_ppp_start_xmit(struct ppp_channel *ppp_channel,
+static int ipwireless_ppp_start_xmit(void *private,
 				     struct sk_buff *skb)
 {
-	struct ipw_network *network = ppp_channel->private;
+	struct ipw_network *network = private;
 	unsigned long flags;
 
 	spin_lock_irqsave(&network->lock, flags);
@@ -153,10 +153,10 @@ static int ipwireless_ppp_start_xmit(struct ppp_channel *ppp_channel,
 }
 
 /* Handle an ioctl call that has come in via ppp. (copy of ppp_async_ioctl() */
-static int ipwireless_ppp_ioctl(struct ppp_channel *ppp_channel,
+static int ipwireless_ppp_ioctl(void *private,
 				unsigned int cmd, unsigned long arg)
 {
-	struct ipw_network *network = ppp_channel->private;
+	struct ipw_network *network = private;
 	int err, val;
 	u32 accm[8];
 	int __user *user_arg = (int __user *) arg;
@@ -254,19 +254,17 @@ static void do_go_online(struct work_struct *work_go_online)
 
 	spin_lock_irqsave(&network->lock, flags);
 	if (!network->ppp_channel) {
+		struct ppp_channel_conf conf = {};
 		struct ppp_channel *channel;
 
 		spin_unlock_irqrestore(&network->lock, flags);
-		channel = kzalloc_obj(struct ppp_channel);
-		if (!channel) {
-			printk(KERN_ERR IPWIRELESS_PCCARD_NAME
-					": unable to allocate PPP channel\n");
-			return;
-		}
-		channel->private = network;
-		channel->mtu = 16384;	/* Wild guess */
-		channel->hdrlen = 2;
-		channel->ops = &ipwireless_ppp_channel_ops;
+
+		conf.private = network;
+		conf.hdrlen = 2;
+		conf.ops = &ipwireless_ppp_channel_ops;
+#ifdef CONFIG_PPP_MULTILINK
+		conf.mtu = 16384;	/* Wild guess */
+#endif
 
 		network->flags = 0;
 		network->rbits = 0;
@@ -275,10 +273,10 @@ static void do_go_online(struct work_struct *work_go_online)
 		network->xaccm[0] = ~0U;
 		network->xaccm[3] = 0x60000000U;
 		network->raccm = ~0U;
-		if (ppp_register_channel(channel) < 0) {
+		channel = ppp_register_channel(&conf);
+		if (!channel) {
 			printk(KERN_ERR IPWIRELESS_PCCARD_NAME
 					": unable to register PPP channel\n");
-			kfree(channel);
 			return;
 		}
 		spin_lock_irqsave(&network->lock, flags);
diff --git a/include/linux/if_pppox.h b/include/linux/if_pppox.h
index 594d6dc3f4c9..a1d7c11182ec 100644
--- a/include/linux/if_pppox.h
+++ b/include/linux/if_pppox.h
@@ -40,7 +40,7 @@ struct pptp_opt {
 struct pppox_sock {
 	/* struct sock must be the first member of pppox_sock */
 	struct sock sk;
-	struct ppp_channel chan;
+	struct ppp_channel *chan;
 	struct pppox_sock __rcu	*next;	  /* for hash table */
 	union {
 		struct pppoe_opt pppoe;
diff --git a/include/linux/ppp_channel.h b/include/linux/ppp_channel.h
index 2f63e9a6cc88..e3a3d59d40dc 100644
--- a/include/linux/ppp_channel.h
+++ b/include/linux/ppp_channel.h
@@ -27,55 +27,64 @@ struct ppp_channel;
 struct ppp_channel_ops {
 	/* Send a packet (or multilink fragment) on this channel.
 	   Returns 1 if it was accepted, 0 if not. */
-	int	(*start_xmit)(struct ppp_channel *, struct sk_buff *);
+	int	(*start_xmit)(void *private, struct sk_buff *skb);
 	/* Handle an ioctl call that has come in via /dev/ppp. */
-	int	(*ioctl)(struct ppp_channel *, unsigned int, unsigned long);
-	int	(*fill_forward_path)(struct net_device_path_ctx *,
-				     struct net_device_path *,
-				     const struct ppp_channel *);
+	int	(*ioctl)(void *private, unsigned int cmd, unsigned long arg);
+	int	(*fill_forward_path)(struct net_device_path_ctx *ctx,
+				     struct net_device_path *path,
+				     void *private);
 };
 
-struct ppp_channel {
+struct ppp_channel_conf {
 	void		*private;	/* channel private data */
 	const struct ppp_channel_ops *ops; /* operations for this channel */
-	int		mtu;		/* max transmit packet size */
 	int		hdrlen;		/* amount of headroom channel needs */
-	void		*ppp;		/* opaque to channel */
-	int		speed;		/* transfer rate (bytes/second) */
 	bool		direct_xmit;	/* no qdisc, xmit directly */
+#ifdef CONFIG_PPP_MULTILINK
+	int		speed;		/* transfer rate (bytes/second) */
+	int		mtu;		/* max transmit packet size */
+#endif
 };
 
 #ifdef __KERNEL__
 /* Called by the channel when it can send some more data. */
-extern void ppp_output_wakeup(struct ppp_channel *);
+void ppp_output_wakeup(struct ppp_channel *pch);
 
 /* Called by the channel to process a received PPP packet.
    The packet should have just the 2-byte PPP protocol header. */
-extern void ppp_input(struct ppp_channel *, struct sk_buff *);
+void ppp_input(struct ppp_channel *pch, struct sk_buff *skb);
 
 /* Called by the channel when an input error occurs, indicating
    that we may have missed a packet. */
-extern void ppp_input_error(struct ppp_channel *);
+void ppp_input_error(struct ppp_channel *pch);
 
-/* Attach a channel to a given PPP unit in specified net. */
-extern int ppp_register_net_channel(struct net *, struct ppp_channel *);
+/* Create a new, unattached ppp channel for specified net. */
+struct ppp_channel *ppp_register_net_channel(struct net *net,
+					 const struct ppp_channel_conf *chan);
 
-/* Attach a channel to a given PPP unit. */
-extern int ppp_register_channel(struct ppp_channel *);
+/* Create a new, unattached ppp channel. */
+struct ppp_channel *ppp_register_channel(const struct ppp_channel_conf *chan);
 
 /* Detach a channel from its PPP unit (e.g. on hangup). */
-extern void ppp_unregister_channel(struct ppp_channel *);
+void ppp_unregister_channel(struct ppp_channel *pch);
 
 /* Get the channel number for a channel */
-extern int ppp_channel_index(struct ppp_channel *);
+int ppp_channel_index(struct ppp_channel *pch);
 
 /* Get the unit number associated with a channel, or -1 if none */
-extern int ppp_unit_number(struct ppp_channel *);
+int ppp_unit_number(struct ppp_channel *pch);
 
 /* Get the device name associated with a channel, or NULL if none.
  * Caller must hold RCU read lock.
  */
-extern char *ppp_dev_name(struct ppp_channel *);
+char *ppp_dev_name(struct ppp_channel *pch);
+
+/* Update the MTU of a multilink channel */
+#ifdef CONFIG_PPP_MULTILINK
+void ppp_channel_update_mtu(struct ppp_channel *pch, int mtu);
+#else
+static inline void ppp_channel_update_mtu(struct ppp_channel *pch, int mtu) {}
+#endif
 
 /*
  * SMP locking notes:
diff --git a/net/atm/pppoatm.c b/net/atm/pppoatm.c
index e3c422dc533a..d801233700e7 100644
--- a/net/atm/pppoatm.c
+++ b/net/atm/pppoatm.c
@@ -64,7 +64,7 @@ struct pppoatm_vcc {
 	atomic_t inflight;
 	unsigned long blocked;
 	int flags;			/* SC_COMP_PROT - compress protocol */
-	struct ppp_channel chan;	/* interface to generic ppp layer */
+	struct ppp_channel *chan;	/* interface to generic ppp layer */
 	struct tasklet_struct wakeup_tasklet;
 };
 
@@ -91,11 +91,6 @@ static inline struct pppoatm_vcc *atmvcc_to_pvcc(const struct atm_vcc *atmvcc)
 	return (struct pppoatm_vcc *) (atmvcc->user_back);
 }
 
-static inline struct pppoatm_vcc *chan_to_pvcc(const struct ppp_channel *chan)
-{
-	return (struct pppoatm_vcc *) (chan->private);
-}
-
 /*
  * We can't do this directly from our _pop handler, since the ppp code
  * doesn't want to be called in interrupt context, so we do it from
@@ -105,7 +100,7 @@ static void pppoatm_wakeup_sender(struct tasklet_struct *t)
 {
 	struct pppoatm_vcc *pvcc = from_tasklet(pvcc, t, wakeup_tasklet);
 
-	ppp_output_wakeup(&pvcc->chan);
+	ppp_output_wakeup(pvcc->chan);
 }
 
 static void pppoatm_release_cb(struct atm_vcc *atmvcc)
@@ -172,7 +167,7 @@ static void pppoatm_unassign_vcc(struct atm_vcc *atmvcc)
 	atmvcc->pop = pvcc->old_pop;
 	atmvcc->release_cb = pvcc->old_release_cb;
 	tasklet_kill(&pvcc->wakeup_tasklet);
-	ppp_unregister_channel(&pvcc->chan);
+	ppp_unregister_channel(pvcc->chan);
 	atmvcc->user_back = NULL;
 	kfree(pvcc);
 }
@@ -201,7 +196,7 @@ static void pppoatm_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
 		skb_pull(skb, LLC_LEN);
 		break;
 	case e_autodetect:
-		if (pvcc->chan.ppp == NULL) {	/* Not bound yet! */
+		if (!pvcc->chan) {	/* Not bound yet! */
 			kfree_skb(skb);
 			return;
 		}
@@ -215,7 +210,8 @@ static void pppoatm_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
 		    !memcmp(skb->data, &pppllc[LLC_LEN],
 		    sizeof(pppllc) - LLC_LEN)) {
 			pvcc->encaps = e_vc;
-			pvcc->chan.mtu += LLC_LEN;
+			ppp_channel_update_mtu(pvcc->chan,
+					       atmvcc->qos.txtp.max_sdu - PPP_HDRLEN);
 			break;
 		}
 		pr_debug("Couldn't autodetect yet (skb: %6ph)\n", skb->data);
@@ -223,12 +219,12 @@ static void pppoatm_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
 	case e_vc:
 		break;
 	}
-	ppp_input(&pvcc->chan, skb);
+	ppp_input(pvcc->chan, skb);
 	return;
 
 error:
 	kfree_skb(skb);
-	ppp_input_error(&pvcc->chan);
+	ppp_input_error(pvcc->chan);
 }
 
 static int pppoatm_may_send(struct pppoatm_vcc *pvcc, int size)
@@ -286,9 +282,9 @@ static int pppoatm_may_send(struct pppoatm_vcc *pvcc, int size)
  * as success, just to be clear what we're really doing.
  */
 #define DROP_PACKET 1
-static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb)
+static int pppoatm_send(void *private, struct sk_buff *skb)
 {
-	struct pppoatm_vcc *pvcc = chan_to_pvcc(chan);
+	struct pppoatm_vcc *pvcc = private;
 	struct atm_vcc *vcc;
 	int ret;
 
@@ -367,16 +363,15 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb)
 }
 
 /* This handles ioctls sent to the /dev/ppp interface */
-static int pppoatm_devppp_ioctl(struct ppp_channel *chan, unsigned int cmd,
-	unsigned long arg)
+static int pppoatm_devppp_ioctl(void *private, unsigned int cmd,
+				unsigned long arg)
 {
+	struct pppoatm_vcc *pvcc = private;
 	switch (cmd) {
 	case PPPIOCGFLAGS:
-		return put_user(chan_to_pvcc(chan)->flags, (int __user *) arg)
-		    ? -EFAULT : 0;
+		return put_user(pvcc->flags, (int __user *)arg) ? -EFAULT : 0;
 	case PPPIOCSFLAGS:
-		return get_user(chan_to_pvcc(chan)->flags, (int __user *) arg)
-		    ? -EFAULT : 0;
+		return get_user(pvcc->flags, (int __user *)arg) ? -EFAULT : 0;
 	}
 	return -ENOTTY;
 }
@@ -388,9 +383,10 @@ static const struct ppp_channel_ops pppoatm_ops = {
 
 static int pppoatm_assign_vcc(struct atm_vcc *atmvcc, void __user *arg)
 {
+	struct ppp_channel_conf conf = {};
 	struct atm_backend_ppp be;
 	struct pppoatm_vcc *pvcc;
-	int err;
+	struct ppp_channel *chan;
 
 	if (copy_from_user(&be, arg, sizeof be))
 		return -EFAULT;
@@ -409,16 +405,19 @@ static int pppoatm_assign_vcc(struct atm_vcc *atmvcc, void __user *arg)
 	pvcc->old_owner = atmvcc->owner;
 	pvcc->old_release_cb = atmvcc->release_cb;
 	pvcc->encaps = (enum pppoatm_encaps) be.encaps;
-	pvcc->chan.private = pvcc;
-	pvcc->chan.ops = &pppoatm_ops;
-	pvcc->chan.mtu = atmvcc->qos.txtp.max_sdu - PPP_HDRLEN -
+	conf.private = pvcc;
+	conf.ops = &pppoatm_ops;
+#ifdef CONFIG_PPP_MULTILINK
+	conf.mtu = atmvcc->qos.txtp.max_sdu - PPP_HDRLEN -
 	    (be.encaps == e_vc ? 0 : LLC_LEN);
+#endif
 	tasklet_setup(&pvcc->wakeup_tasklet, pppoatm_wakeup_sender);
-	err = ppp_register_channel(&pvcc->chan);
-	if (err != 0) {
+	chan = ppp_register_channel(&conf);
+	if (!chan) {
 		kfree(pvcc);
-		return err;
+		return -ENOMEM;
 	}
+	pvcc->chan = chan;
 	atmvcc->user_back = pvcc;
 	atmvcc->push = pppoatm_push;
 	atmvcc->pop = pppoatm_pop;
@@ -458,11 +457,11 @@ static int pppoatm_ioctl(struct socket *sock, unsigned int cmd,
 		return pppoatm_assign_vcc(atmvcc, argp);
 		}
 	case PPPIOCGCHAN:
-		return put_user(ppp_channel_index(&atmvcc_to_pvcc(atmvcc)->
-		    chan), (int __user *) argp) ? -EFAULT : 0;
+		return put_user(ppp_channel_index(atmvcc_to_pvcc(atmvcc)->chan),
+		    (int __user *)argp) ? -EFAULT : 0;
 	case PPPIOCGUNIT:
-		return put_user(ppp_unit_number(&atmvcc_to_pvcc(atmvcc)->
-		    chan), (int __user *) argp) ? -EFAULT : 0;
+		return put_user(ppp_unit_number(atmvcc_to_pvcc(atmvcc)->chan),
+		    (int __user *)argp) ? -EFAULT : 0;
 	}
 	return -ENOIOCTLCMD;
 }
diff --git a/net/l2tp/l2tp_ppp.c b/net/l2tp/l2tp_ppp.c
index 99d6582f41de..6c7b08f4e49a 100644
--- a/net/l2tp/l2tp_ppp.c
+++ b/net/l2tp/l2tp_ppp.c
@@ -121,7 +121,7 @@ struct pppol2tp_session {
 	struct sock		*__sk;		/* Copy of .sk, for cleanup */
 };
 
-static int pppol2tp_xmit(struct ppp_channel *chan, struct sk_buff *skb);
+static int pppol2tp_xmit(void *private, struct sk_buff *skb);
 
 static const struct ppp_channel_ops pppol2tp_chan_ops = {
 	.start_xmit =  pppol2tp_xmit,
@@ -221,7 +221,7 @@ static void pppol2tp_recv(struct l2tp_session *session, struct sk_buff *skb, int
 		struct pppox_sock *po;
 
 		po = pppox_sk(sk);
-		ppp_input(&po->chan, skb);
+		ppp_input(po->chan, skb);
 	} else {
 		if (sock_queue_rcv_skb(sk, skb) < 0) {
 			atomic_long_inc(&session->stats.rx_errors);
@@ -326,9 +326,9 @@ static int pppol2tp_sendmsg(struct socket *sock, struct msghdr *m,
  * the skb it supplied, not our cloned skb. So we take care to always
  * leave the original skb unfreed if we return an error.
  */
-static int pppol2tp_xmit(struct ppp_channel *chan, struct sk_buff *skb)
+static int pppol2tp_xmit(void *private, struct sk_buff *skb)
 {
-	struct sock *sk = (struct sock *)chan->private;
+	struct sock *sk = private;
 	struct l2tp_session *session;
 	struct l2tp_tunnel *tunnel;
 	int uhlen, headroom;
@@ -504,7 +504,7 @@ static void pppol2tp_show(struct seq_file *m, void *arg)
 	if (sk) {
 		struct pppox_sock *po = pppox_sk(sk);
 
-		seq_printf(m, "   interface %s\n", ppp_dev_name(&po->chan));
+		seq_printf(m, "   interface %s\n", ppp_dev_name(po->chan));
 	}
 	rcu_read_unlock();
 }
@@ -612,7 +612,7 @@ static int pppol2tp_sockaddr_get_info(const void *sa, int sa_len,
  * numbers and no IP option. Not quite accurate, but the result is mostly
  * unused anyway.
  */
-static int pppol2tp_tunnel_mtu(const struct l2tp_tunnel *tunnel)
+static int __maybe_unused pppol2tp_tunnel_mtu(const struct l2tp_tunnel *tunnel)
 {
 	int mtu;
 
@@ -694,6 +694,8 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr_unsized *userva
 	struct l2tp_tunnel *tunnel;
 	struct pppol2tp_session *ps;
 	struct l2tp_session_cfg cfg = { 0, };
+	struct ppp_channel_conf conf = {};
+	struct ppp_channel *chan;
 	bool drop_refcnt = false;
 	bool new_session = false;
 	bool new_tunnel = false;
@@ -792,18 +794,22 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr_unsized *userva
 	 * the net device's hard_header_len at registration, which must be
 	 * sufficient regardless of whether sequence numbers are enabled later.
 	 */
-	po->chan.hdrlen = PPPOL2TP_L2TP_HDR_SIZE_SEQ;
+	conf.hdrlen = PPPOL2TP_L2TP_HDR_SIZE_SEQ;
 
-	po->chan.private = sk;
-	po->chan.ops	 = &pppol2tp_chan_ops;
-	po->chan.mtu	 = pppol2tp_tunnel_mtu(tunnel);
-	po->chan.direct_xmit	= true;
+	conf.private = sk;
+	conf.ops	 = &pppol2tp_chan_ops;
+#ifdef CONFIG_PPP_MULTILINK
+	conf.mtu	 = pppol2tp_tunnel_mtu(tunnel);
+#endif
+	conf.direct_xmit	= true;
 
-	error = ppp_register_net_channel(sock_net(sk), &po->chan);
-	if (error) {
+	chan = ppp_register_net_channel(sock_net(sk), &conf);
+	if (!chan) {
+		error = -ENOMEM;
 		mutex_unlock(&ps->sk_lock);
 		goto end;
 	}
+	po->chan = chan;
 
 out_no_ppp:
 	/* This is how we get the session context from the socket. */
@@ -1550,7 +1556,7 @@ static void pppol2tp_seq_session_show(struct seq_file *m, void *v)
 	if (sk) {
 		struct pppox_sock *po = pppox_sk(sk);
 
-		seq_printf(m, "   interface %s\n", ppp_dev_name(&po->chan));
+		seq_printf(m, "   interface %s\n", ppp_dev_name(po->chan));
 	}
 	rcu_read_unlock();
 }
-- 
2.43.0


^ permalink raw reply related

* [PATCH] serial: mxs-auart: Compare the return value of gpiod_get_direction against GPIO_LINE_DIRECTION_IN
From: Nikola Z. Ivanov @ 2026-04-16  8:32 UTC (permalink / raw)
  To: gregkh, jirislaby, Frank.Li, s.hauer, kernel, festevam
  Cc: linux-kernel, linux-serial, imx, linux-arm-kernel,
	Nikola Z. Ivanov

The GPIO_LINE_DIRECTION_* definitions have just recently been exposed to
gpio consumers.h by breaking them out in a separate defs.h file.

Use this to validate the gpio direction instead of the hard-coded literal.

Signed-off-by: Nikola Z. Ivanov <zlatistiv@gmail.com>
---
 drivers/tty/serial/mxs-auart.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c
index cc65c9fb6446..6c6df4d5c21f 100644
--- a/drivers/tty/serial/mxs-auart.c
+++ b/drivers/tty/serial/mxs-auart.c
@@ -1519,7 +1519,7 @@ static int mxs_auart_init_gpios(struct mxs_auart_port *s, struct device *dev)
 
 	for (i = 0; i < UART_GPIO_MAX; i++) {
 		gpiod = mctrl_gpio_to_gpiod(s->gpios, i);
-		if (gpiod && (gpiod_get_direction(gpiod) == 1))
+		if (gpiod && (gpiod_get_direction(gpiod) == GPIO_LINE_DIRECTION_IN))
 			s->gpio_irq[i] = gpiod_to_irq(gpiod);
 		else
 			s->gpio_irq[i] = -EINVAL;
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH v7 0/8] Add support for handling PCIe M.2 Key E connectors in devicetree
From: Manivannan Sadhasivam @ 2026-04-16  8:34 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Andy Shevchenko, Manivannan Sadhasivam, Rob Herring,
	Greg Kroah-Hartman, Jiri Slaby, Nathan Chancellor, Nicolas Schier,
	Hans de Goede, Ilpo Järvinen, Mark Pearson, Derek J. Clark,
	Krzysztof Kozlowski, Conor Dooley, Marcel Holtmann,
	Luiz Augusto von Dentz, Bartosz Golaszewski, Bartosz Golaszewski,
	linux-serial, linux-kernel, linux-kbuild, platform-driver-x86,
	linux-pci, devicetree, linux-arm-msm, linux-bluetooth, linux-pm,
	Stephan Gerhold, Dmitry Baryshkov, linux-acpi, Hans de Goede,
	Bartosz Golaszewski, Luca Ceresoli
In-Reply-To: <CAGXv+5EPA29G-fsH=wWOD8AK6TZFezFhsE0NHPYj_Pt3nT+d_w@mail.gmail.com>

On Wed, Apr 15, 2026 at 04:31:24PM +0800, Chen-Yu Tsai wrote:
> On Tue, Apr 14, 2026 at 8:03 PM Andy Shevchenko
> <andriy.shevchenko@linux.intel.com> wrote:
> >
> > On Tue, Apr 14, 2026 at 06:29:02PM +0800, Chen-Yu Tsai wrote:
> > > On Tue, Apr 14, 2026 at 4:28 PM Andy Shevchenko
> > > <andriy.shevchenko@linux.intel.com> wrote:
> > > > On Tue, Apr 14, 2026 at 01:03:19PM +0800, Chen-Yu Tsai wrote:
> > > > > On Tue, Apr 14, 2026 at 12:08 AM Manivannan Sadhasivam <mani@kernel.org> wrote:
> > > > > > On Mon, Apr 13, 2026 at 07:33:12PM +0530, Manivannan Sadhasivam wrote:
> > > > > > > On Mon, Apr 13, 2026 at 03:54:59PM +0800, Chen-Yu Tsai wrote:
> > > > > > > > On Thu, Mar 26, 2026 at 01:36:28PM +0530, Manivannan Sadhasivam wrote:
> >
> > ...
> >
> > > > > > > > - Given that this connector actually represents two devices, how do I
> > > > > > > >   say I want the BT part to be a wakeup source, but not the WiFi part?
> > > > > > > >   Does wakeup-source even work at this point?
> > > > > > >
> > > > > > > You can't use the DT property since the devices are not described in DT
> > > > > > > statically. But you can still use the per-device 'wakeup' sysfs knob to enable
> > > > > > > wakeup.
> > > > >
> > > > > I see. I think not being able to specify generic properties for the devices
> > > > > on the connector is going to be a bit problematic.
> > > >
> > > > This is nature of the open-connectors, especially on the busses that are
> > > > hotpluggable, like PCIe. We never know what is connected there _ahead_.
> > >
> > > I believe what you mean by "hotpluggable" is "user replaceable".
> >
> > From the OS perspective it's the same. From platform perspective
> > there is a difference, granted.
> 
> Yes. I just wanted to clarify.
> 
> > > > In other words you can't describe in DT something that may not exist.
> > >
> > > But this is actually doable with the PCIe slot representation. The
> > > properties are put in the device node for the slot. If no card is
> > > actually inserted in the slot, then no device is created, and the
> > > device node is left as not associated with anything.
> >
> > But you need to list all devices in the world if you want to support this
> 
> Why would I need to? The PCIe slot representation just describes a
> PCIe bridge. Granted this might not be entirely correct, but it's
> what we currently have.
> 
> And even then, there are properties like memory-region or wakeup-source
> that are generic and aren't tied to specific devices.
> 
> > somehow. Yes, probably many of them (or majority) will be enumerated as is,
> > but some may need an assistance via (dynamic) properties or similar mechanisms.
> 
> Even if we wanted to add dynamic properties, there is currently no proper
> device node to attach them to.
> 

There are dynamic device nodes that we need to create for hotpluggable devices,
if we need to pass the DT properties to the driver. Like how we do it for PCIe,
serdev. You cannot describe static device nodes in DT for devices attached to
swappable cards like M.2.

> > > It's just that for this new M.2 E-key connector, there aren't separate
> > > nodes for each interface. And the system doesn't associate the device
> > > node with the device, because it's no longer a child node of the
> > > controller or hierarchy, but connected over the OF graph.
> > >
> > > Moving over to the E-key connector representation seems like one step
> > > forward and one step backward in descriptive ability. We gain proper
> > > power sequencing, but lose generic properties.
> >
> > The "key" is property of the connector. Hence if you have an idea what can be
> > common for ALL "key":s, that's probably can be abstracted. Note, I'm not
> > familiar with the connector framework in the Linux kernel, perhaps it's already
> > that kind of abstraction.
> 
> I'm not arguing for a even more generic "M.2" connector. The "key" is
> already described in the compatible. I'm saying we should have some way
> of describing the individual interfaces (PCIe, SDIO, USB, UART, I2S, I2C)
> on the connector so further nodes or properties can be attached to them,
> either with overlays or dynamically within the kernel. Right now the
> are only described as individual ports, but we can't actually tie a
> device to a OF graph port.
> 

If there are properties that apply to the interfaces, *not devices*, then you
can add them to the relevant endpoint node:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml#n167

> But maybe I'm overthinking the representation part. AFAICT for Qualcomm's
> UART-based BT bit part, Mani just had the driver create a device node
> under the UART (by traversing the OF graph to find the UART). If that's
> the desired way then the connector binding should mention it.

What do you mean by 'connector binding should mention it'? You cannot hardcode
the device node in the connector binding, because it is not part of the
connector binding itself. For example, the UART device node that is created for
the WCN7850, already has a binding:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/devicetree/bindings/net/bluetooth/qcom,wcn7850-bt.yaml

What the driver does is, just creating that node under the serdev controller as
if the device was described statically in the DT.

For the wakeup property you asked earlier, it depends on the interface. If the
interface supports "in-band" wakeup like USB/SDIO, you can add the property in
the relevant controller node statically itself. For example, with USB XHCI
controller, you would know that the controller will support wakeup or not
statically. So you will add that property to the controller node. Then, if the
user has enabled the wakeup for a specific USB device like keyboard/mouse
through sysfs, then wakeup will just work. Here, we don't need to create the
USB device node at all.

But if the interface supports physical wakeup, like UART_WAKE# in M.2 SDIO Key
E, then based on the presence of that property, you would configure wakeup in
the connector driver itself. But this is not present as of now.

> And that
> works for me. But I think it's messier and also we're missing an
> opportunity to make the M.2 connector a standardized attachment point
> for overlays.
> 

I don't envision using overlays for the M.2 connectors. It is not strictly
needed, unless the connector is non-standard.

- Mani

-- 
மணிவண்ணன் சதாசிவம்

^ permalink raw reply

* Re: [PATCH v7 0/8] Add support for handling PCIe M.2 Key E connectors in devicetree
From: Manivannan Sadhasivam @ 2026-04-16  8:55 UTC (permalink / raw)
  To: Herve Codina
  Cc: Chen-Yu Tsai, Andy Shevchenko, Manivannan Sadhasivam, Rob Herring,
	Greg Kroah-Hartman, Jiri Slaby, Nathan Chancellor, Nicolas Schier,
	Hans de Goede, Ilpo Järvinen, Mark Pearson, Derek J. Clark,
	Krzysztof Kozlowski, Conor Dooley, Marcel Holtmann,
	Luiz Augusto von Dentz, Bartosz Golaszewski, Bartosz Golaszewski,
	linux-serial, linux-kernel, linux-kbuild, platform-driver-x86,
	linux-pci, devicetree, linux-arm-msm, linux-bluetooth, linux-pm,
	Stephan Gerhold, Dmitry Baryshkov, linux-acpi, Hans de Goede,
	Bartosz Golaszewski, Luca Ceresoli
In-Reply-To: <20260415165651.153b573d@bootlin.com>

On Wed, Apr 15, 2026 at 04:56:51PM +0200, Herve Codina wrote:
> Hi Chen, all,
> 
> ...
>  
> > 
> > I'm not arguing for a even more generic "M.2" connector. The "key" is
> > already described in the compatible. I'm saying we should have some way
> > of describing the individual interfaces (PCIe, SDIO, USB, UART, I2S, I2C)
> > on the connector so further nodes or properties can be attached to them,
> > either with overlays or dynamically within the kernel. Right now the
> > are only described as individual ports, but we can't actually tie a
> > device to a OF graph port.
> > 
> > But maybe I'm overthinking the representation part. AFAICT for Qualcomm's
> > UART-based BT bit part, Mani just had the driver create a device node
> > under the UART (by traversing the OF graph to find the UART). If that's
> > the desired way then the connector binding should mention it. And that
> > works for me. But I think it's messier and also we're missing an
> > opportunity to make the M.2 connector a standardized attachment point
> > for overlays.
> > 
> > Mani, could you also chime in a bit on what you envisioned?
> > 
> > (Added Luca from Bootlin to CC, as I think there are parallels to the
> >  "Hotplug of Non-discoverable Hardware" work)
> >
> 
> Related to "Hotplug of Non-discoverable Hardware",
> 
> I would add entries for busses in the connector without using an OF graph.
> 

I don't think this is a correct representation. It is non-standard to describe
the device nodes in some other connectors. While it may work with your series in
the future, not something I would bet-on at this point.

Using OF graph to link the connector nodes look like the cleaner solution to me.

> For I2C and later SPI, this was is done.
> 
> You already have an i2c-parent property but no node where an i2c device
> can be added.
> 
> The last discussion related to hotplug, connectors and DT led to the RFC
> series [1].
> 
> It is a huge series. The last patch give a real example of representation:
>   https://lore.kernel.org/all/20260112142009.1006236-78-herve.codina@bootlin.com/
> 
> In your case I would see some thing like:
> 
>     connector {
>         compatible = "pcie-m2-e-connector";
>         vpcie3v3-supply = <&vreg_wcn_3p3>;
>         vpcie1v8-supply = <&vreg_l15b_1p8>;
> 
> 	/*
> 	 * If those GPIOs have to be used by components available in
> 	 * the connected board, a Nexus node should be used.
>          */
>         w-disable1-gpios = <&tlmm 115 GPIO_ACTIVE_LOW>;
>         w-disable2-gpios = <&tlmm 116 GPIO_ACTIVE_LOW>;
>         viocfg-gpios = <&tlmm 117 GPIO_ACTIVE_HIGH>;
>         uart-wake-gpios = <&tlmm 118 GPIO_ACTIVE_LOW>;
>         sdio-wake-gpios = <&tlmm 119 GPIO_ACTIVE_LOW>;
>         sdio-reset-gpios = <&tlmm 120 GPIO_ACTIVE_LOW>;
> 
> 	conn-i2c {
> 		i2c-parent = <&i2c0>;
> 
> 		/*
>  		 * Here i2c devices available on the board
> 		 * connected to the connector can be described.
> 		 */
> 	};
> 
> 	/* Same kind to description for other busses */
> 	conn-pcie {
> 		pci-parent = <&xxxxx>;
> 
> 		/*
> 		 * The PCIe bus has abilities to discover devices.
> 		 * Not sure this node is needed.
> 		 *
> 		 * If a PCI device need a DT description to describe
> 		 * stuffs behind the device, what has been done for LAN966x
> 		 * could be re-used [2] and [3]
> 		 */

I don't think anyone would connect something like LAN966x to the M.2 connector.
M.2 cards have a defined purpose, like NVMe, WLAN etc... If anyone wants to
connect another SoC like LAN966x, they would use non-M.2 connectors.

- Mani

-- 
மணிவண்ணன் சதாசிவம்

^ permalink raw reply

* [PATCH] serial: 8250: Clear CON_PRINTBUFFER on port re-registration
From: Fushuai Wang @ 2026-04-16  9:29 UTC (permalink / raw)
  To: gregkh, jirislaby, ilpo.jarvinen, osama.abdelkader,
	andy.shevchenko, kees
  Cc: linux-kernel, linux-serial, wangfushuai

From: Fushuai Wang <wangfushuai@baidu.com>

When two PnP devices map to the same physical port, the serial8250 driver
removes and re-registers the console structure for the same port.

During re-registration, the console structure still has CON_PRINTBUFFER set
from the initial registration, which causes console_init_seq() to set
console->seq to syslog_seq. This results in re-printing the entire
system log buffer, which may lead to RCU stall on slow serial consoles.

Clear CON_PRINTBUFFER when re-registering a port to prevent duplicate
log printing.

Signed-off-by: Fushuai Wang <wangfushuai@baidu.com>
---
 drivers/tty/serial/8250/8250_core.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
index d2e2c5dfef99..3465a4d93b50 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -694,6 +694,7 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
 {
 	struct uart_8250_port *uart;
 	int ret;
+	bool was_removed = false;
 
 	if (up->port.uartclk == 0)
 		return -EINVAL;
@@ -716,8 +717,10 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
 	if (uart->port.type == PORT_8250_CIR)
 		return -ENODEV;
 
-	if (uart->port.dev)
+	if (uart->port.dev) {
 		uart_remove_one_port(&serial8250_reg, &uart->port);
+		was_removed = true;
+	}
 
 	uart->port.ctrl_id	= up->port.ctrl_id;
 	uart->port.port_id	= up->port.port_id;
@@ -819,6 +822,10 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
 					&uart->capabilities);
 
 		serial8250_apply_quirks(uart);
+
+		if (was_removed && uart_console(&uart->port))
+			uart->port.cons->flags &= ~CON_PRINTBUFFER;
+
 		ret = uart_add_one_port(&serial8250_reg,
 					&uart->port);
 		if (ret)
-- 
2.36.1


^ permalink raw reply related

* Re: [PATCH] serial: 8250_accent: fix reference leak on failed device registration
From: Guangshuo Li @ 2026-04-16  9:37 UTC (permalink / raw)
  To: Jiri Slaby
  Cc: Greg Kroah-Hartman, Russell King, linux-kernel, linux-serial,
	stable
In-Reply-To: <463cec4f-a038-4bd0-90df-76e0ef48381c@kernel.org>

Hi Jiri,

Thanks for the review.

On Thu, 16 Apr 2026 at 14:14, Jiri Slaby <jirislaby@kernel.org> wrote:
>
> Hi,
>
>
> What reference exactly?
I was referring to the device reference initialized by
device_initialize() inside
platform_device_register(). My reasoning was that when
platform_device_add() fails, platform_device_register() returns the
error directly and does not drop that reference on the failure path.

>
> How did you verify you did the right change?

After my tool reported this case, I manually audited the relevant
source code and
checked the related core API definitions. However, I did miss the
special handling needed for a static device in this case.

> In particular, what does put_device() do on a static device, even
> initialized, ie. with no device::release? Try it...

Sorry, I should have considered and verified that
more carefully before sending the patch.

Thanks,
Guangshuo

^ permalink raw reply

* Re: [PATCH] serial: 8250: Clear CON_PRINTBUFFER on port re-registration
From: Andy Shevchenko @ 2026-04-16  9:40 UTC (permalink / raw)
  To: Fushuai Wang
  Cc: gregkh, jirislaby, ilpo.jarvinen, osama.abdelkader, kees,
	linux-kernel, linux-serial, wangfushuai
In-Reply-To: <20260416092917.27301-1-fushuai.wang@linux.dev>

On Thu, Apr 16, 2026 at 12:29 PM Fushuai Wang <fushuai.wang@linux.dev> wrote:
>
> From: Fushuai Wang <wangfushuai@baidu.com>
>
> When two PnP devices map to the same physical port, the serial8250 driver
> removes and re-registers the console structure for the same port.

Is it a real device out of there? Can you share what that is?

> During re-registration, the console structure still has CON_PRINTBUFFER set
> from the initial registration, which causes console_init_seq() to set
> console->seq to syslog_seq. This results in re-printing the entire
> system log buffer, which may lead to RCU stall on slow serial consoles.
>
> Clear CON_PRINTBUFFER when re-registering a port to prevent duplicate
> log printing.

Seems like the Fixes tag is missing.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* Re: [PATCH] serial: 8250: Clear CON_PRINTBUFFER on port re-registration
From: Fushuai Wang @ 2026-04-16 10:02 UTC (permalink / raw)
  To: andy.shevchenko
  Cc: fushuai.wang, gregkh, ilpo.jarvinen, jirislaby, kees,
	linux-kernel, linux-serial, osama.abdelkader, wangfushuai
In-Reply-To: <CAHp75Vfrw6jLWOm6gC3gjMnUrvyd5nxvS16sJj5TP_JxaDSDNQ@mail.gmail.com>

>>
>> From: Fushuai Wang <wangfushuai@baidu.com>
>>
>> When two PnP devices map to the same physical port, the serial8250 driver
>> removes and re-registers the console structure for the same port.
>
> Is it a real device out of there? Can you share what that is?

Yes, it's a real device.
In my Intel(R) Xeon(R) 6971P-C machine, the boot log shows:
	[   17.242984] 00:04: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
	[   17.251352] printk: console [ttyS0] disabled
	[   17.257934] serial 00:04: Runtime PM usage count underflow!
	[   17.258360] 00:05: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
	[   17.258516] printk: console [ttyS0] enabled
	[   29.643013] serial8250: ttyS1 at I/O 0x2f8 (irq = 3, base_baud = 115200) is a 16550A

The issue occurs when BIOS "Serial Device" option is set to BMC:
	  Setup Question  = Serial Device
  	  Help String     = Sets the Serial Device used to output bios serial log
  	  Token   = 9003   // Do NOT change this line
  	  Offset  = 2F8
  	  Width   = 01
  	  BIOS Default =[01]BMC
  	  Options = *[01]BMC       // Move "*" to desired Option
            	    [02]S3M

So 00:05 may be the BMC serial port device. From ACPI paths:
	/sys/bus/pnp/devices/00:04/firmware_node/path: \_SB_.LPC0.UAR1
	/sys/bus/pnp/devices/00:05/firmware_node/path: \_SB_.UAR1

>> During re-registration, the console structure still has CON_PRINTBUFFER set
>> from the initial registration, which causes console_init_seq() to set
>> console->seq to syslog_seq. This results in re-printing the entire
>> system log buffer, which may lead to RCU stall on slow serial consoles.
>>
>> Clear CON_PRINTBUFFER when re-registering a port to prevent duplicate
>> log printing.
>
>Seems like the Fixes tag is missing.

Yes, I will add Fixes tag lately.

-- 
Regards,
WANG

^ permalink raw reply

* Re: [PATCH tty v2 2/2] serial: 8250: Add support for console hardware flow control
From: John Ogness @ 2026-04-16 10:12 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-kernel, Ilpo Järvinen, Ingo Molnar, Thomas Gleixner,
	Osama Abdelkader, Andy Shevchenko, Jiayuan Chen, Gerhard Engleder,
	Dr. David Alan Gilbert, Joseph Tilahun, linux-serial
In-Reply-To: <20260410144949.16581-3-john.ogness@linutronix.de>

Hi,

Responding to Sashiko:

https://sashiko.dev/#/patchset/20260410144949.16581-1-john.ogness%40linutronix.de


On 2026-04-10, Sashiko wrote:
>> diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
>> index a739350e634f9..6c8830943b0c0 100644
>> --- a/drivers/tty/serial/8250/8250_port.c
>> +++ b/drivers/tty/serial/8250/8250_port.c
>> @@ -3425,6 +3431,10 @@ int serial8250_console_setup(struct uart_port *port, char *options, bool probe)
>>  	if (ret)
>>  		return ret;
>>  
>> +	/* Allow user-specified hardware flow control. */
>> +	if (flow == 'r')
>> +		port->flags |= UPF_CONS_FLOW;
>> +
>
> Could this lead to a data race if port->flags is updated without
> holding the port mutex? include/linux/serial_core.h specifies that
> flags must be updated while holding the port mutex.

Yes. However, taking that lock makes no sense in the general context
(the lock probably does not even exist yet). The problem stems from the
fact that this console-only policy flag is being stored in a variable
used for tty properties. It does not belong there. For v3 I will move
this console-only policy flag into its own boolean.

> Also, if the console is dynamically reconfigured at runtime to disable flow
> control (e.g., flow == 'n'), should this code clear UPF_CONS_FLOW so the
> port does not erroneously retain its previous state?

Ack.

> diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
> index 89cebdd278410..a9ea10df4fb8b 100644
> --- a/drivers/tty/serial/serial_core.c
> +++ b/drivers/tty/serial/serial_core.c
> @@ -2235,6 +2235,18 @@ uart_set_options(struct uart_port *port, struct console *co,
>  	port->mctrl |= TIOCM_DTR;
>  
>  	port->ops->set_termios(port, &termios, &dummy);
> +
> +	/*
> +	 * If console hardware flow control was specified and is supported,
> +	 * the related policy UPSTAT_CTS_ENABLE must be set to allow console
> +	 * drivers to identify if CTS should be used for polling.
> +	 */
> +	if (flow == 'r' && (termios.c_cflag & CRTSCTS)) {
> +		/* Synchronize @status RMW update against the console. */
> +		guard(uart_port_lock_irq)(port);
> +		port->status |= UPSTAT_CTS_ENABLE;
> +	}
> +

> Will this unconditionally enable interrupts during early boot and
> cause a crash?
>
> uart_set_options() is called by serial8250_console_setup() during
> early boot console registration, which occurs when local interrupts
> are disabled.  When the guard goes out of scope, spin_unlock_irq()
> will unconditionally enable interrupts before the system is fully
> initialized.  Is it possible to use the IRQ-saving variant here, such
> as scoped_guard(uart_port_lock_irqsave, port), to safely restore the
> previous interrupt state?

Ack. v3 will switch to irqsave.

> Additionally, if the console is dynamically reconfigured at runtime to
> disable flow control, should UPSTAT_CTS_ENABLE be cleared in an else
> branch to prevent the console from getting stuck polling CTS?

I do not want to restrict drivers from setting the UPSTAT_CTS_ENABLE
policy bit when console flow control is disabled. For the case that
console flow control is requested, UPSTAT_CTS_ENABLE _must_ be set in
order for the CTS polling to function.

John Ogness

^ permalink raw reply

* Re: [PATCH] serial: 8250: Clear CON_PRINTBUFFER on port re-registration
From: Andy Shevchenko @ 2026-04-16 10:15 UTC (permalink / raw)
  To: Fushuai Wang
  Cc: gregkh, ilpo.jarvinen, jirislaby, kees, linux-kernel,
	linux-serial, osama.abdelkader, wangfushuai
In-Reply-To: <20260416100254.30581-1-fushuai.wang@linux.dev>

On Thu, Apr 16, 2026 at 1:03 PM Fushuai Wang <fushuai.wang@linux.dev> wrote:

> >> When two PnP devices map to the same physical port, the serial8250 driver
> >> removes and re-registers the console structure for the same port.
> >
> > Is it a real device out of there? Can you share what that is?
>
> Yes, it's a real device.
> In my Intel(R) Xeon(R) 6971P-C machine, the boot log shows:
>         [   17.242984] 00:04: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
>         [   17.251352] printk: console [ttyS0] disabled

>         [   17.257934] serial 00:04: Runtime PM usage count underflow!

This is strange, what kernel version is this?

>         [   17.258360] 00:05: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
>         [   17.258516] printk: console [ttyS0] enabled
>         [   29.643013] serial8250: ttyS1 at I/O 0x2f8 (irq = 3, base_baud = 115200) is a 16550A
>
> The issue occurs when BIOS "Serial Device" option is set to BMC:
>           Setup Question  = Serial Device
>           Help String     = Sets the Serial Device used to output bios serial log
>           Token   = 9003   // Do NOT change this line

>           Offset  = 2F8

Is it an IO port?

>           Width   = 01
>           BIOS Default =[01]BMC
>           Options = *[01]BMC       // Move "*" to desired Option
>                     [02]S3M
>
> So 00:05 may be the BMC serial port device. From ACPI paths:
>         /sys/bus/pnp/devices/00:04/firmware_node/path: \_SB_.LPC0.UAR1
>         /sys/bus/pnp/devices/00:05/firmware_node/path: \_SB_.UAR1

Do I understand correctly that both devices refer to the same physical
device?! How on earth is it supposed to work?

> >> During re-registration, the console structure still has CON_PRINTBUFFER set
> >> from the initial registration, which causes console_init_seq() to set
> >> console->seq to syslog_seq. This results in re-printing the entire
> >> system log buffer, which may lead to RCU stall on slow serial consoles.
> >>
> >> Clear CON_PRINTBUFFER when re-registering a port to prevent duplicate
> >> log printing.
> >
> >Seems like the Fixes tag is missing.
>
> Yes, I will add Fixes tag lately.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* Re: [PATCH] serial: 8250_accent: fix reference leak on failed device registration
From: Guangshuo Li @ 2026-04-16 10:23 UTC (permalink / raw)
  To: Jiri Slaby
  Cc: Greg Kroah-Hartman, Russell King, linux-kernel, linux-serial,
	stable
In-Reply-To: <CANUHTR9toK-PS8qrTd-=ATpSi8xbnXmF87sfRaMDp_jG_eiVMg@mail.gmail.com>

Hi Jiri,

Thanks.

On Thu, 16 Apr 2026 at 17:37, Guangshuo Li <lgs201920130244@gmail.com> wrote:
>
> Hi Jiri,
>
> Thanks for the review.
>
> On Thu, 16 Apr 2026 at 14:14, Jiri Slaby <jirislaby@kernel.org> wrote:
> >
> > Hi,
> >
> >
> > What reference exactly?
> I was referring to the device reference initialized by
> device_initialize() inside
> platform_device_register(). My reasoning was that when
> platform_device_add() fails, platform_device_register() returns the
> error directly and does not drop that reference on the failure path.
>
> >
> > How did you verify you did the right change?
>
> After my tool reported this case, I manually audited the relevant
> source code and
> checked the related core API definitions. However, I did miss the
> special handling needed for a static device in this case.
>
> > In particular, what does put_device() do on a static device, even
> > initialized, ie. with no device::release? Try it...
>
> Sorry, I should have considered and verified that
> more carefully before sending the patch.
>
> Thanks,
> Guangshuo

We are also discussing in another similar patch whether the
better fix, if any, should be in the API/core code rather than in
individual callers:

https://patchew.org/linux/20260415174159.3625777-1-lgs201920130244@gmail.com/

Thanks,
Guangshuo

^ permalink raw reply

* Re: [PATCH] serial: 8250_accent: fix reference leak on failed device registration
From: Jiri Slaby @ 2026-04-16 10:43 UTC (permalink / raw)
  To: Guangshuo Li
  Cc: Greg Kroah-Hartman, Russell King, linux-kernel, linux-serial,
	stable
In-Reply-To: <CANUHTR8Hje-pP=7=2hLwkhPMb6cMWosDzdY4oLneFpH5-hfS-Q@mail.gmail.com>

On 16. 04. 26, 12:23, Guangshuo Li wrote:
> We are also discussing in another similar patch whether the
> better fix, if any, should be in the API/core code rather than in
> individual callers:
> 
> https://patchew.org/linux/20260415174159.3625777-1-lgs201920130244@gmail.com/

Agreed, if anything needs a fix, it's platform_device_register() or the 
functions underneath...

thanks,
-- 
js
suse labs

^ permalink raw reply

* Re: [PATCH] serial: 8250: Clear CON_PRINTBUFFER on port re-registration
From: Fushuai Wang @ 2026-04-16 11:32 UTC (permalink / raw)
  To: andy.shevchenko
  Cc: fushuai.wang, gregkh, ilpo.jarvinen, jirislaby, kees,
	linux-kernel, linux-serial, osama.abdelkader, wangfushuai
In-Reply-To: <CAHp75Vc7sdDT+nWZX7u7aSsUhUquYNAiYo9HrxHhjXnGz5g55Q@mail.gmail.com>

>> >> When two PnP devices map to the same physical port, the serial8250 driver
>> >> removes and re-registers the console structure for the same port.
>> >
>> > Is it a real device out of there? Can you share what that is?
>>
>> Yes, it's a real device.
>> In my Intel(R) Xeon(R) 6971P-C machine, the boot log shows:
>>         [   17.242984] 00:04: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
>>         [   17.251352] printk: console [ttyS0] disabled
>
>>         [   17.257934] serial 00:04: Runtime PM usage count underflow!
>
> This is strange, what kernel version is this?

Please ingore this. I just test the patch in kernel 6.6. And this log
has nothing to do with this problem. It was fixed in patch ed2761958ad7
("tty: serial: 8250: Fix another runtime PM usage counter underflow").

>
>>         [   17.258360] 00:05: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
>>         [   17.258516] printk: console [ttyS0] enabled
>>         [   29.643013] serial8250: ttyS1 at I/O 0x2f8 (irq = 3, base_baud = 115200) is a 16550A
>>
>> The issue occurs when BIOS "Serial Device" option is set to BMC:
>>           Setup Question  = Serial Device
>>           Help String     = Sets the Serial Device used to output bios serial log
>>           Token   = 9003   // Do NOT change this line
>>           Offset  = 2F8
>
> Is it an IO port?

No, Offset = 2F8 is the offset in BIOS configuration space, not the I/O port.

>>           Width   = 01
>>           BIOS Default =[01]BMC
>>           Options = *[01]BMC       // Move "*" to desired Option
>>                     [02]S3M
>>
>> So 00:05 may be the BMC serial port device. From ACPI paths:
>>         /sys/bus/pnp/devices/00:04/firmware_node/path: \_SB_.LPC0.UAR1
>>         /sys/bus/pnp/devices/00:05/firmware_node/path: \_SB_.UAR1
>
> Do I understand correctly that both devices refer to the same physical
> device?! How on earth is it supposed to work?

Not exactly the same physical device, but they map to the same I/O port (0x3f8).
They are different PnP devices with different ACPI paths:

  00:04: \_SB_.LPC0.UAR1  (LPC COM1)
  00:05: \_SB_.UAR1       (BMC serial port)

When BIOS "Serial Device" option is set to BMC, both 00:04 and 00:05 are mapped to
the same physical I/O port 0x3f8. And then:

  1.00:04 is probed first and registers the console for port 0x3f8
  2.00:05 is detected and also needs to use port 0x3f8
  3.Since port 0x3f8 is already in use, the driver must remove 00:04 first before
    adding 00:05
  4.This process involves console re-registration, which causes the entire 
    log buffer to be reprinted

Sorry if my explanation is not clear - I don't work with serial subsystem often. :-)

-- 
Regards,
WANG

^ permalink raw reply

* Re: [PATCH v1] serial: sh-sci: fix memory region release in error path
From: Geert Uytterhoeven @ 2026-04-16 11:59 UTC (permalink / raw)
  To: zenghongling
  Cc: gregkh, jirislaby, biju.das.jz, wsa+renesas,
	prabhakar.mahadev-lad.rj, linux-kernel, linux-serial,
	zhongling0719, kernel test robot, Dan Carpenter
In-Reply-To: <20260413040807.40546-1-zenghongling@kylinos.cn>

Hi Zeng,

On Mon, 13 Apr 2026 at 06:08, zenghongling <zenghongling@kylinos.cn> wrote:
> From: Hongling Zeng <zenghongling@kylinos.cn>
>
> The sci_request_port() function uses request_mem_region() to reserve
> I/O memory, but in the error path when sci_remap_port() fails, it
> incorrectly calls release_resource() instead of release_mem_region().
>
> This mismatch can cause resource accounting issues. Fix it by using
> the correct release function, consistent with sci_release_port().

Thanks for your patch!

> Fixes: 2667bd6673eb ("serial: 8250_port: simplify serial8250_request_std_resource()")

That commit does not touch the sh-sci driver.  Shouldn't that be
Fixes: e2651647080930a1 ("serial: sh-sci: Handle port memory region
reservations.")
instead?

"git log --follow" does a really bad job here, and doesn't even see the
offending commit, due to the actual change happening in parallel with
the move from drivers/serial to drivers/tty/serial.  Fortunately the
"git blame"-style output in the linked report below does show the
correct commit.

> Reported-by: kernel test robot <lkp@intel.com>
> Reported-by: Dan Carpenter <error27@gmail.com>
> Closes: https://lore.kernel.org/r/202604032356.SzEjYkBC-lkp@intel.com/
> Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn>

> --- a/drivers/tty/serial/sh-sci.c
> +++ b/drivers/tty/serial/sh-sci.c
> @@ -3024,7 +3024,7 @@ int sci_request_port(struct uart_port *port)
>
>         ret = sci_remap_port(port);
>         if (unlikely(ret != 0)) {
> -               release_resource(res);
> +               release_mem_region(port->mapbase, sport->reg_size);
>                 return ret;
>         }
>

My first thought was "Isn't release_resource() just a shorthand in
case you already have a pointer to the resource?", but that indeed
seems to leak the resource, as it doesn't call free_resource()
(see __release_region()).

Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Still, I'd like to see a second review opinion.

Gr{oetje,eeting}s,

                        Geert

-- 
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

^ permalink raw reply

* [PATCH] sysrq: add optional logging of caller info on /proc/sysrq-trigger write
From: Xiang Gao @ 2026-04-16 13:14 UTC (permalink / raw)
  To: gregkh, jirislaby; +Cc: akpm, linux-kernel, linux-serial, Xiang Gao

From: Xiang Gao <gaoxiang17@xiaomi.com>

When /proc/sysrq-trigger is written to, there is no record of which
process triggered the sysrq operation. This makes it difficult to audit
or debug who initiated a sysrq action, especially when the write comes
from a shell spawned by system()/popen() where the immediate caller is
"sh" rather than the originating application.

Add CONFIG_MAGIC_SYSRQ_TRIGGER_LOG (default n) and a runtime toggle via
module parameter sysrq.trigger_log (default off). When both are enabled,
the kernel logs the triggering process's comm, pid, tgid, uid, and walks
up to 5 levels of the parent process chain. This allows tracing the
original initiator even through system()/popen()/fork+exec indirection.

Example output:
  sysrq: proc trigger: comm=sh pid=68 tgid=68 uid=0
  sysrq:   parent[0]: comm=my_app pid=67 tgid=67
  sysrq:   parent[1]: comm=init pid=1 tgid=1

Usage:
  # Compile-time: enable CONFIG_MAGIC_SYSRQ_TRIGGER_LOG=y
  # Runtime: echo 1 > /sys/module/sysrq/parameters/trigger_log
  # Or boot parameter: sysrq.trigger_log=1

Signed-off-by: Xiang Gao <gaoxiang17@xiaomi.com>
---
 drivers/tty/sysrq.c | 29 +++++++++++++++++++++++++++++
 lib/Kconfig.debug   | 16 ++++++++++++++++
 2 files changed, 45 insertions(+)

diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c
index c2e4b31b699a..e9277e7de35b 100644
--- a/drivers/tty/sysrq.c
+++ b/drivers/tty/sysrq.c
@@ -48,6 +48,9 @@
 #include <linux/uaccess.h>
 #include <linux/moduleparam.h>
 #include <linux/jiffies.h>
+#ifdef CONFIG_MAGIC_SYSRQ_TRIGGER_LOG
+#include <linux/cred.h>
+#endif
 #include <linux/syscalls.h>
 #include <linux/of.h>
 #include <linux/rcupdate.h>
@@ -59,6 +62,12 @@
 static int __read_mostly sysrq_enabled = CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE;
 static bool __read_mostly sysrq_always_enabled;
 
+#ifdef CONFIG_MAGIC_SYSRQ_TRIGGER_LOG
+static bool sysrq_trigger_log;
+module_param_named(trigger_log, sysrq_trigger_log, bool, 0644);
+MODULE_PARM_DESC(trigger_log, "Log caller info on /proc/sysrq-trigger write");
+#endif
+
 static bool sysrq_on(void)
 {
 	return sysrq_enabled || sysrq_always_enabled;
@@ -1209,6 +1218,26 @@ static ssize_t write_sysrq_trigger(struct file *file, const char __user *buf,
 	bool bulk = false;
 	size_t i;
 
+#ifdef CONFIG_MAGIC_SYSRQ_TRIGGER_LOG
+	if (sysrq_trigger_log) {
+		struct task_struct *task;
+		int depth = 0;
+
+		pr_info("proc trigger: comm=%s pid=%d tgid=%d uid=%u\n",
+			current->comm, current->pid, current->tgid,
+			from_kuid(&init_user_ns, current_uid()));
+
+		rcu_read_lock();
+		task = current;
+		while (task->pid > 1 && depth < 5) {
+			task = rcu_dereference(task->real_parent);
+			pr_info("  parent[%d]: comm=%s pid=%d tgid=%d\n",
+				depth++, task->comm, task->pid, task->tgid);
+		}
+		rcu_read_unlock();
+	}
+#endif
+
 	for (i = 0; i < count; i++) {
 		char c;
 
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index aac60b6cfa4b..46bd361decd0 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -705,6 +705,22 @@ config MAGIC_SYSRQ_SERIAL_SEQUENCE
 
 	  If unsure, leave an empty string and the option will not be enabled.
 
+config MAGIC_SYSRQ_TRIGGER_LOG
+	bool "Log caller info on /proc/sysrq-trigger write"
+	depends on MAGIC_SYSRQ
+	default n
+	help
+	  If you say Y here, the kernel can log the process name, pid,
+	  tgid, uid and parent process chain when /proc/sysrq-trigger
+	  is written to. This is useful for auditing who triggered a
+	  sysrq operation.
+
+	  The logging is controlled at runtime via module parameter
+	  sysrq.trigger_log (default off). Enable it with:
+	    echo 1 > /sys/module/sysrq/parameters/trigger_log
+
+	  If unsure, say N.
+
 config DEBUG_FS
 	bool "Debug Filesystem"
 	help
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH v7 0/8] Add support for handling PCIe M.2 Key E connectors in devicetree
From: Herve Codina @ 2026-04-16 13:34 UTC (permalink / raw)
  To: Manivannan Sadhasivam
  Cc: Chen-Yu Tsai, Andy Shevchenko, Manivannan Sadhasivam, Rob Herring,
	Greg Kroah-Hartman, Jiri Slaby, Nathan Chancellor, Nicolas Schier,
	Hans de Goede, Ilpo Järvinen, Mark Pearson, Derek J. Clark,
	Krzysztof Kozlowski, Conor Dooley, Marcel Holtmann,
	Luiz Augusto von Dentz, Bartosz Golaszewski, Bartosz Golaszewski,
	linux-serial, linux-kernel, linux-kbuild, platform-driver-x86,
	linux-pci, devicetree, linux-arm-msm, linux-bluetooth, linux-pm,
	Stephan Gerhold, Dmitry Baryshkov, linux-acpi, Hans de Goede,
	Bartosz Golaszewski, Luca Ceresoli
In-Reply-To: <4yockfx5rjcvfh2n2excrgsknnhi72rv2w7wf7onks2ryt33sm@w7zkcxuc6vem>

Hi Manivannan,

On Thu, 16 Apr 2026 14:25:39 +0530
Manivannan Sadhasivam <mani@kernel.org> wrote:

> On Wed, Apr 15, 2026 at 04:56:51PM +0200, Herve Codina wrote:
> > Hi Chen, all,
> > 
> > ...
> >    
> > > 
> > > I'm not arguing for a even more generic "M.2" connector. The "key" is
> > > already described in the compatible. I'm saying we should have some way
> > > of describing the individual interfaces (PCIe, SDIO, USB, UART, I2S, I2C)
> > > on the connector so further nodes or properties can be attached to them,
> > > either with overlays or dynamically within the kernel. Right now the
> > > are only described as individual ports, but we can't actually tie a
> > > device to a OF graph port.
> > > 
> > > But maybe I'm overthinking the representation part. AFAICT for Qualcomm's
> > > UART-based BT bit part, Mani just had the driver create a device node
> > > under the UART (by traversing the OF graph to find the UART). If that's
> > > the desired way then the connector binding should mention it. And that
> > > works for me. But I think it's messier and also we're missing an
> > > opportunity to make the M.2 connector a standardized attachment point
> > > for overlays.
> > > 
> > > Mani, could you also chime in a bit on what you envisioned?
> > > 
> > > (Added Luca from Bootlin to CC, as I think there are parallels to the
> > >  "Hotplug of Non-discoverable Hardware" work)
> > >  
> > 
> > Related to "Hotplug of Non-discoverable Hardware",
> > 
> > I would add entries for busses in the connector without using an OF graph.
> >   
> 
> I don't think this is a correct representation. It is non-standard to describe
> the device nodes in some other connectors. While it may work with your series in
> the future, not something I would bet-on at this point.
> 
> Using OF graph to link the connector nodes look like the cleaner solution to me.

In your DT binding, there is the i2c-parent property at connector level.

How it is used or planned to be used in order to handle devices available on the
board connected to the M2 connector ?

> 
> > For I2C and later SPI, this was is done.
> > 
> > You already have an i2c-parent property but no node where an i2c device
> > can be added.
> > 
> > The last discussion related to hotplug, connectors and DT led to the RFC
> > series [1].
> > 
> > It is a huge series. The last patch give a real example of representation:
> >   https://lore.kernel.org/all/20260112142009.1006236-78-herve.codina@bootlin.com/
> > 
> > In your case I would see some thing like:
> > 
> >     connector {
> >         compatible = "pcie-m2-e-connector";
> >         vpcie3v3-supply = <&vreg_wcn_3p3>;
> >         vpcie1v8-supply = <&vreg_l15b_1p8>;
> > 
> > 	/*
> > 	 * If those GPIOs have to be used by components available in
> > 	 * the connected board, a Nexus node should be used.
> >          */
> >         w-disable1-gpios = <&tlmm 115 GPIO_ACTIVE_LOW>;
> >         w-disable2-gpios = <&tlmm 116 GPIO_ACTIVE_LOW>;
> >         viocfg-gpios = <&tlmm 117 GPIO_ACTIVE_HIGH>;
> >         uart-wake-gpios = <&tlmm 118 GPIO_ACTIVE_LOW>;
> >         sdio-wake-gpios = <&tlmm 119 GPIO_ACTIVE_LOW>;
> >         sdio-reset-gpios = <&tlmm 120 GPIO_ACTIVE_LOW>;
> > 
> > 	conn-i2c {
> > 		i2c-parent = <&i2c0>;
> > 
> > 		/*
> >  		 * Here i2c devices available on the board
> > 		 * connected to the connector can be described.
> > 		 */
> > 	};
> > 
> > 	/* Same kind to description for other busses */
> > 	conn-pcie {
> > 		pci-parent = <&xxxxx>;
> > 
> > 		/*
> > 		 * The PCIe bus has abilities to discover devices.
> > 		 * Not sure this node is needed.
> > 		 *
> > 		 * If a PCI device need a DT description to describe
> > 		 * stuffs behind the device, what has been done for LAN966x
> > 		 * could be re-used [2] and [3]
> > 		 */  
> 
> I don't think anyone would connect something like LAN966x to the M.2 connector.
> M.2 cards have a defined purpose, like NVMe, WLAN etc... If anyone wants to
> connect another SoC like LAN966x, they would use non-M.2 connectors.

Hum, maybe yes, maybe not. I don't know what kind of hardware could be available
on a M2 connector.

One think sure is that a PCIe bus is available on the connector and so, potentially
all PCI devices could be wired on a M2 form factor.

LAN966x in PCI version is just a PCI device.

Anyway, Is it allowed to have sub-nodes in a port node ?

I mean, can we describe devices connected to a bus described by the port node ?

For USB and PCI it is probably not needed for common use cases but having a DT
description for devices on PCI and/or USB exists and is supported in the current
kernel

On SDIO, wireless devices can be described:
  Documentation/devicetree/bindings/net/wireless/

and so, sub-nodes will be needed.

As those devices need an address (reg property), #address-cells and
#size-cells will be needed.

This will end in a mix of sub-nodes describing devices and a sub-node
describing the endpoint connection.

#address-cells and #size-cells will conflict at some points.

Is a reg value for endpoints has the same characteristics as a reg value
for a SDIO device ?

How a BT devices is described under the UART available in the M2
connector ?

Best regards,
Hervé

^ permalink raw reply

* RE: [PATCH v2 1/2] serial: sh-sci: Avoid divide-by-zero fault
From: Biju Das @ 2026-04-16 16:31 UTC (permalink / raw)
  To: geert
  Cc: Hugo Villeneuve, biju.das.au, Greg Kroah-Hartman, Jiri Slaby,
	Thierry Bultel, wsa+renesas, Prabhakar Mahadev Lad,
	linux-kernel@vger.kernel.org, linux-serial@vger.kernel.org,
	linux-renesas-soc@vger.kernel.org
In-Reply-To: <CAMuHMdW770zLQt5NiUZffhg3ztXzvM8iT=byBzKJEU9Gm8OykQ@mail.gmail.com>

Hi Geert,

Thanks for the feedback.

> -----Original Message-----
> From: Geert Uytterhoeven <geert@linux-m68k.org>
> Sent: 13 April 2026 11:25
> Subject: Re: [PATCH v2 1/2] serial: sh-sci: Avoid divide-by-zero fault
> 
> Hi Biju,
> 
> On Wed, 8 Apr 2026 at 19:25, Biju Das <biju.das.jz@bp.renesas.com> wrote:
> > > From: Hugo Villeneuve <hugo@hugovil.com> On Wed, 8 Apr 2026 16:35:44
> > > +0000 Biju Das <biju.das.jz@bp.renesas.com> wrote:
> > > > > From: Hugo Villeneuve <hugo@hugovil.com> Biju
> > > > > <biju.das.au@gmail.com> wrote:
> > > > > > From: Biju Das <biju.das.jz@bp.renesas.com>
> > > > > >
> > > > > > uart_update_timeout() computes a timeout value by dividing by
> > > > > > the baud rate. If baud is zero — which can occur when the
> > > > > > hardware returns an unsupported or invalid rate — this results in a divide-by-zero fault.
> > > > >
> > > > > baud is returned by uart_get_baud_rate(), so this is not returned by the hardware?
> > > >
> > > > You are tight, Will update commit description.
> > >
> > > How can uart_get_baud_rate() return a zero value? If I am not
> > > mistaken even for the B0 case, it will return 9600?
> >
> > As per the comment and code, this API can return 0.
> >
> > * If the new baud rate is invalid, try the @old termios setting. If
> > it's still
> > * invalid, we try 9600 baud. If that is also invalid 0 is returned.
> >
> > In drives/tty currently only 1 driver is checking the return value and
> > it calls panic
> >
> > https://elixir.bootlin.com/linux/v7.0-rc7/source/drivers/tty/serial/ap
> > buart.c#L214
> >
> >
> > I believe we should call panic, if baud =0, instead of proceeding.
> >
> > Geert, any thoughts??
> 
> IIRC, baud == 0 can (only?) happen when using earlyprintk on a non-DT system, where the serial console
> should just keep on using the settings programmed by the firmware.  So any config register writes
> should be skipped.
> 
> On DT systems, even earlycon uses the bitrate from chosen/stdout-path.

In that case, I will drop the zero baud check.

I will add a new patch with below change which is algebraically equivalent
but eliminates the intermediate division, making a zero divisor impossible
for any valid baud rate.

-	s->rx_frame = (10000 * bits) / (baud / 100);
+	s->rx_frame = (10000 * bits) * 100 / baud;

Cheers,
Biju

^ permalink raw reply

* Re: [PATCH] serial: 8250: Clear CON_PRINTBUFFER on port re-registration
From: Andy Shevchenko @ 2026-04-16 18:30 UTC (permalink / raw)
  To: Fushuai Wang
  Cc: gregkh, ilpo.jarvinen, jirislaby, kees, linux-kernel,
	linux-serial, osama.abdelkader, wangfushuai
In-Reply-To: <20260416113200.38244-1-fushuai.wang@linux.dev>

On Thu, Apr 16, 2026 at 2:32 PM Fushuai Wang <fushuai.wang@linux.dev> wrote:

> >> >> When two PnP devices map to the same physical port, the serial8250 driver
> >> >> removes and re-registers the console structure for the same port.
> >> >
> >> > Is it a real device out of there? Can you share what that is?
> >>
> >> Yes, it's a real device.
> >> In my Intel(R) Xeon(R) 6971P-C machine, the boot log shows:
> >>         [   17.242984] 00:04: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
> >>         [   17.251352] printk: console [ttyS0] disabled
> >
> >>         [   17.257934] serial 00:04: Runtime PM usage count underflow!
> >
> > This is strange, what kernel version is this?
>
> Please ingore this. I just test the patch in kernel 6.6. And this log
> has nothing to do with this problem. It was fixed in patch ed2761958ad7
> ("tty: serial: 8250: Fix another runtime PM usage counter underflow").

Thanks for clarification.

> >>         [   17.258360] 00:05: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
> >>         [   17.258516] printk: console [ttyS0] enabled
> >>         [   29.643013] serial8250: ttyS1 at I/O 0x2f8 (irq = 3, base_baud = 115200) is a 16550A
> >>
> >> The issue occurs when BIOS "Serial Device" option is set to BMC:
> >>           Setup Question  = Serial Device
> >>           Help String     = Sets the Serial Device used to output bios serial log
> >>           Token   = 9003   // Do NOT change this line
> >>           Offset  = 2F8
> >
> > Is it an IO port?
>
> No, Offset = 2F8 is the offset in BIOS configuration space, not the I/O port.
>
> >>           Width   = 01
> >>           BIOS Default =[01]BMC
> >>           Options = *[01]BMC       // Move "*" to desired Option
> >>                     [02]S3M
> >>
> >> So 00:05 may be the BMC serial port device. From ACPI paths:
> >>         /sys/bus/pnp/devices/00:04/firmware_node/path: \_SB_.LPC0.UAR1
> >>         /sys/bus/pnp/devices/00:05/firmware_node/path: \_SB_.UAR1
> >
> > Do I understand correctly that both devices refer to the same physical
> > device?! How on earth is it supposed to work?
>
> Not exactly the same physical device, but they map to the same I/O port (0x3f8).
> They are different PnP devices with different ACPI paths:
>
>   00:04: \_SB_.LPC0.UAR1  (LPC COM1)
>   00:05: \_SB_.UAR1       (BMC serial port)
>
> When BIOS "Serial Device" option is set to BMC, both 00:04 and 00:05 are mapped to
> the same physical I/O port 0x3f8. And then:
>
>   1.00:04 is probed first and registers the console for port 0x3f8
>   2.00:05 is detected and also needs to use port 0x3f8

This is simply wrong. Do we ever support such a FW configuration?
Why on earth are there two devices for the same resource exposed to
the OS? It smells like a bug in BIOS.

>   3.Since port 0x3f8 is already in use, the driver must remove 00:04 first before
>     adding 00:05
>   4.This process involves console re-registration, which causes the entire
>     log buffer to be reprinted
>
> Sorry if my explanation is not clear - I don't work with serial subsystem often. :-)

Now it's elaborated well enough.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* [PATCH v4 0/8] Add support for ZTE zx297520v3
From: Stefan Dösinger @ 2026-04-16 20:19 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
	Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
	Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
	linux-serial, Stefan Dösinger

Hi,

This is a follow-up on my RFC patches from January [0] for ZTE's 
zx297520v3 chipset. This chipset is popular in cheap LTE-to-wifi routers 
sold in developing countries. My goal is to run OpenWRT on them. I made 
more progress in more work on this SoC and it is time to get serious 
about code review and upstreaming.

Since my version in January I managed to get more hardware running: SPI, 
I2C, PMIC with real time clock and voltage regulators, Watchdog. LTE is 
not working yet, but I am able to start the coprocessor that handles it 
and talk to it via mailbox + shared memory. Wifi is working on a few 
more devices. Since WiFi, USB and Ethernet are working, the devices can 
have actual use with OpenWRT even without LTE.

Another hacker created a free software program to talk to the USB loader 
[1] and boot U-Boot and Linux without modifying the on disk files. At 
the moment it needs a proprietary blob, so my documentation is 
emphasising booting with the on-device U-Boot.

This patchset here is mostly unmodified from the version I sent in 
January. It is the bare minimum to get an interactive shell working on 
the UART. Future patches can be found on my git repository [2] for those 
curious to peek ahead. The first 30 patches are in reasonable shape, but 
the further you go the more cleanup is necessary. I expect all of the 
patches go require a few rounds of feedback though.

My plan for upstreaming is largly this:

1) This bare minimum boot patchset
2) Add clock and pinctrl drivers
3) Add standard hardware to the device tree
4) Add zx29 specific drivers one by one: Watchdog, spi, i2c, DMA, PMIC, 
battery
5) SDIO backend for rtl8xxxu
6) rproc, mailbox and rpmsg

I am willing to maintain support for the SoC within reason. My patches 
add myself as maintainer. This is a hobby project for me though, keep 
that in mind if you want to ship a commercial product with these SoCs 
and upstreaming Linux.

Cheers,
Stefan

0: https://lists.infradead.org/pipermail/linux-arm-kernel/2026-January/1099306.html
1: https://github.com/zx297520v3-mainline/zx297520v3-loader
2: https://gitlab.com/stefandoesinger/zx297520-kernel/

Patch changelog:

v4: rename zx29.yaml to zte.yaml and add board enums
v3: Remove [RFC] tag, add defconfig
v2: checkpatch.pl fixes

Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Stefan Dösinger (8):
      ARM: zte: Add zx297520v3 platform support
      dt-bindings: arm: Add zx297520v3 board binding
      ARM: dts: Add D-Link DWR-932M support
      ARM: zte: Add support for zx29 low level debug
      ARM: dts: Add an armv7 timer for zx297520v3
      ARM: zte: Bring back zx29 UART support
      ARM: dts: Declare UART1 on zx297520v3 boards
      ARM: defconfig: Add a zx29 defconfig file

 Documentation/arch/arm/zte/zx297520v3.rst      | 158 +++++++++++++++++++++++++
 Documentation/devicetree/bindings/arm/zte.yaml |  25 ++++
 MAINTAINERS                                    |   6 +
 arch/arm/Kconfig                               |   2 +
 arch/arm/Kconfig.debug                         |  12 ++
 arch/arm/Makefile                              |   1 +
 arch/arm/boot/dts/Makefile                     |   1 +
 arch/arm/boot/dts/zte/Makefile                 |   3 +
 arch/arm/boot/dts/zte/dlink-dwr-932m.dts       |  21 ++++
 arch/arm/boot/dts/zte/zx297520v3.dtsi          |  83 +++++++++++++
 arch/arm/configs/zx29_defconfig                |  90 ++++++++++++++
 arch/arm/include/debug/pl01x.S                 |   7 ++
 arch/arm/mach-zte/Kconfig                      |  24 ++++
 arch/arm/mach-zte/Makefile                     |   2 +
 arch/arm/mach-zte/zx297520v3.c                 |  19 +++
 drivers/tty/serial/amba-pl011.c                |  37 ++++++
 include/linux/amba/bus.h                       |   6 +
 17 files changed, 497 insertions(+)
---
base-commit: 028ef9c96e96197026887c0f092424679298aae8
change-id: 20260416-send-5c08e095e5c9

Best regards,
-- 
Stefan Dösinger <stefandoesinger@gmail.com>


^ permalink raw reply

* [PATCH v4 1/8] ARM: zte: Add zx297520v3 platform support
From: Stefan Dösinger @ 2026-04-16 20:19 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
	Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
	Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
	linux-serial, Stefan Dösinger
In-Reply-To: <20260416-send-v4-0-e19d02b944ec@gmail.com>

This SoC is used in low end LTE-to-WiFi routers, for example some D-Link
DWR 932 revisions, ZTE K10, ZLT S10 4G, but also models that are branded
and sold by ISPs themselves. They are widespread in Africa, China,
Russia and Eastern Europe.

This SoC is a relative of the zx296702 and zx296718 that had some
upstream support until commit 89d4f98ae90d ("ARM: remove zte zx
platform"). My eventual goal is to enable OpenWRT to run on these
devices.

Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
 Documentation/arch/arm/zte/zx297520v3.rst | 158 ++++++++++++++++++++++++++++++
 MAINTAINERS                               |   4 +
 arch/arm/Kconfig                          |   2 +
 arch/arm/Makefile                         |   1 +
 arch/arm/mach-zte/Kconfig                 |  24 +++++
 arch/arm/mach-zte/Makefile                |   2 +
 arch/arm/mach-zte/zx297520v3.c            |  19 ++++
 7 files changed, 210 insertions(+)

diff --git a/Documentation/arch/arm/zte/zx297520v3.rst b/Documentation/arch/arm/zte/zx297520v3.rst
new file mode 100644
index 000000000000..a0f25ade0a3d
--- /dev/null
+++ b/Documentation/arch/arm/zte/zx297520v3.rst
@@ -0,0 +1,158 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================================
+Booting Linux on ZTE zx297520v3 SoCs
+====================================
+
+...............................................................................
+
+Author:	Stefan Dösinger
+
+Date  : 27 Jan 2026
+
+1. Hardware description
+---------------------------
+Zx297520v3 SoCs use a 64 bit capable Cortex-A53 CPU and GICv3, although they
+run in aarch32 mode only. The CPU has support EL3, but no hypervisor (EL2) and
+it seems to lack VFP and NEON.
+
+The SoC is used in a number of cheap LTE to Wifi routers, both battery powered
+MiFis and stationary CPEs. In addition to the CPU these devices usually have
+64 MB Ram (although some is shared with the LTE chip), 128 MB NAND flash, an
+SDIO connected RTL8192-type Wifi chip limited to 2.4 ghz operation, USB 2,
+and buttons. Devices with as low as 32 MB or as high as 128 MB ram exist, as
+do devices with 8 or 16 MB of NOR flash.
+
+Some devices, especially the stationary ones, have 100 mbit Ethernet and an
+Ethernet switch.
+
+Usually the devices have LEDs for status indication, although some have SPI or
+i2c connected displays
+
+Some have an SD card slot. If it exists, it is a better choice for the root
+file system because it easily outperforms the built-in NAND.
+
+The LTE interface runs on a separate DSP called ZSP880. It is probably derived
+from LSI ZSPs and has an undocumented instruction set. The ZSP communicates
+with the main CPU via SRAM and DRAM and a mailbox hardware that can generate
+IRQs on either ends.
+
+There is also a Cortex M0 CPU, which is responsible for early HW initialization
+and starting the Cortex A53 CPU. It does not have any essential purpose once
+U-Boot is started. A SRAM-Based handover protocol exists to run custom code on
+this CPU.
+
+2. Booting via USB
+---------------------------
+
+The Boot ROM has support for booting custom code via USB. This mode can be
+entered by connecting a Boot PIN to GND or by modifying the third byte on NAND
+(set it to anything other than 0x5A aka 'Z'). A free software tool to start
+custom uboot and kernels can be found here:
+
+https://github.com/zx297520v3-mainline/zx297520v3-loader
+
+If USB download mode is entered but no boot commands are sent through USB, the
+device will proceed to boot normally after a few seconds. It is therefore
+possible to enable USB boot permanently and still leave the default boot files
+in place.
+
+3. Building for built-in U-Boot
+---------------------------
+The devices come with an ancient U-Boot that loads legacy uImages from NAND and
+boots them without a chance for the user to interrupt. The images are stored in
+files ap_cpuap.bin and ap_recovery.bin on a jffs2 partition named imagefs,
+usually mtd4. A file named "fotaflag" switches between the two modes.
+
+In addition to the uImage header, those files have a 384 byte signature header,
+which is used for authenticating the images on some devices. Most devices have
+this authentication disabled and it is enough to pad the uImage files with 384
+zero bytes.
+
+Builtin U-Boot also poorly sets up the CPU. Read the next section for details
+on this. It has no support for loading DTBs, so CONFIG_ARM_APPENDED_DTB is
+needed.
+
+So to build an image that boots from NAND the following steps are necessary:
+
+1) Patch the assembly code from section 3 into arch/arm/kernel/head.S.
+2) make zx29_defconfig
+3) make [-j x]
+4) cat arch/arm/boot/zImage arch/arm/boot/dts/zte/[device].dtb > kernel+dtb
+5) mkimage -A arm -O linux -T kernel -C none -a 0x20008000 -d kernel+dtb uimg
+6) dd if=/dev/zero bs=1 count=384 of=ap_recovery.bin
+7) cat uimg >> ap_recovery.bin
+8) Place this file onto imagefs on the device. Delete ap_cpuap.bin if the
+free space is not enough.
+9) Create the file fotaflag: echo -n FOTA-RECOVERY > fotaflag
+
+For development, booting ap_recovery.bin is recommended because the normal boot
+mode arms the watchdog before starting the kernel.
+
+4. CPU and GIC Setup
+---------------------------
+
+Generally CPU and GICv3 need to be set up according to the requirements spelled
+out in Documentation/arch/arm64/booting.rst. For zx297520v3 this means:
+
+1. GICD_CTLR.DS=1 to disable GIC security
+2. Enable access to ICC_SRE
+3. Disable trapping IRQs into monitor mode
+4. Configure EL2 and below to run in insecure mode.
+5. Configure timer PPIs to active-low.
+
+The kernel sources provided by ZTE do not boot either (interrupts do not work
+at all). They are incomplete in other aspects too, so it is assumed that there
+is some workaround similar to the one described in this document somewhere in
+the binary blobs.
+
+The assembly code below is given as an example of how to achieve this:
+
+```
+#include <linux/irqchip/arm-gic-v3.h>
+#include <asm/assembler.h>
+#include <asm/cp15.h>
+
+@ This allows EL1 to handle ints hat are normally handled by EL2/3.
+ldr     r3, =0xf2000000
+ldr     r4, =#(GICD_CTLR_ARE_NS | GICD_CTLR_DS)
+str     r4, [r3]
+
+cps     #MON_MODE
+
+@ Work in non-secure physical address space: SCR_EL3.NS = 1. At least the UART
+@ seems to respond only to non-secure addresses. I have taken insipiration from
+@ Raspberry pi's armstub7.S here.
+@
+@ ARM docs say modify this bit in monitor mode only...
+mov	r3, #0x131			@ non-secure, Make F, A bits in CPSR writeable
+					@ Allow hypervisor call.
+mcr     p15, 0, r3, c1, c1, 0
+
+@ AP_PPI_MODE_REG: Configure timer PPIs (10, 11, 13, 14) to active-low.
+ldr	r3, =0xF22020a8
+ldr	r4, =0x50
+str	r4, [r3]
+ldr	r3, =0xF22020ac
+ldr	r4, =0x14
+str	r4, [r3]
+
+@ Enable EL2 access to ICC_SRE (bit 3, ICC_SRE_EL3.Enable). Enable system reg
+@ access to GICv3 registers (bit 0, ICC_SRE_EL3.SRE) for EL1 and EL3.
+mrc     p15, 6, r3, c12, c12, 5         @ ICC_SRE_EL3
+orr     r3, #0x9                        @ FIXME: No defines for SRE_EL3 values?
+mcr     p15, 6, r3, c12, c12, 5
+mrc     p15, 0, r3, c12, c12, 5         @ ICC_SRE_EL1
+orr     r3, #(ICC_SRE_EL1_SRE)
+mcr     p15, 0, r3, c12, c12, 5
+
+@ Like ICC_SRE_EL3, enable EL1 access to ICC_SRE and system register access
+@ for EL2.
+mrc     p15, 4, r3, c12, c9, 5          @ ICC_SRE_EL2 aka ICC_HSRE
+orr     r3, r3, #(ICC_SRE_EL2_ENABLE | ICC_SRE_EL2_SRE)
+mcr     p15, 4, r3, c12, c9, 5
+isb
+
+@ Back to SVC mode. TODO: Doesn't safe_svcmode_maskall do this for us anyway?
+cps     #SVC_MODE
+```
diff --git a/MAINTAINERS b/MAINTAINERS
index d1cc0e12fe1f..974d7a98956a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29200,6 +29200,10 @@ F:	include/linux/zswap.h
 F:	mm/zswap.c
 F:	tools/testing/selftests/cgroup/test_zswap.c
 
+ZX29
+M:	Stefan Dösinger <stefandoesinger@gmail.com>
+F:	arch/arm/mach-zte/
+
 SENARYTECH AUDIO CODEC DRIVER
 M:	bo liu <bo.liu@senarytech.com>
 S:	Maintained
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index ec33376f8e2b..4217ed704e48 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -464,6 +464,8 @@ source "arch/arm/mach-versatile/Kconfig"
 
 source "arch/arm/mach-vt8500/Kconfig"
 
+source "arch/arm/mach-zte/Kconfig"
+
 source "arch/arm/mach-zynq/Kconfig"
 
 # ARMv7-M architecture
diff --git a/arch/arm/Makefile b/arch/arm/Makefile
index b7de4b6b284c..573813ef5e77 100644
--- a/arch/arm/Makefile
+++ b/arch/arm/Makefile
@@ -223,6 +223,7 @@ machine-$(CONFIG_ARCH_SUNXI)		+= sunxi
 machine-$(CONFIG_ARCH_TEGRA)		+= tegra
 machine-$(CONFIG_ARCH_U8500)		+= ux500
 machine-$(CONFIG_ARCH_VT8500)		+= vt8500
+machine-$(CONFIG_ARCH_ZTE)		+= zte
 machine-$(CONFIG_ARCH_ZYNQ)		+= zynq
 machine-$(CONFIG_PLAT_VERSATILE)	+= versatile
 machine-$(CONFIG_PLAT_SPEAR)		+= spear
diff --git a/arch/arm/mach-zte/Kconfig b/arch/arm/mach-zte/Kconfig
new file mode 100644
index 000000000000..24699256863b
--- /dev/null
+++ b/arch/arm/mach-zte/Kconfig
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0
+menuconfig ARCH_ZTE
+	bool "ZTE zx family"
+	depends on ARCH_MULTI_V7
+	help
+	  Support for ZTE zx-based family of processors.
+
+if ARCH_ZTE
+
+config SOC_ZX297520V3
+	default y if ARCH_ZTE
+	bool "ZX297520v3"
+	select ARM_GIC_V3
+	select ARM_AMBA
+	select HAVE_ARM_ARCH_TIMER
+	select PM_GENERIC_DOMAINS if PM
+	help
+	  Support for ZTE zx297520v3 SoC. It a single core SoC used in cheap LTE to WiFi routers.
+	  These devices can be Identified by the occurrence of the string "zx297520v3" in the boot
+	  output and /proc/cpuinfo of their stock firmware.
+
+	  Please read Documentation/arch/arm/zte/zx297520v3.rst on how to boot the kernel.
+
+endif
diff --git a/arch/arm/mach-zte/Makefile b/arch/arm/mach-zte/Makefile
new file mode 100644
index 000000000000..1bfe4fddd6af
--- /dev/null
+++ b/arch/arm/mach-zte/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SOC_ZX297520V3) += zx297520v3.o
diff --git a/arch/arm/mach-zte/zx297520v3.c b/arch/arm/mach-zte/zx297520v3.c
new file mode 100644
index 000000000000..c11c7e836f91
--- /dev/null
+++ b/arch/arm/mach-zte/zx297520v3.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2026 Stefan Dösinger
+ */
+
+#include <asm/mach/arch.h>
+#include <asm/mach/map.h>
+
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+
+static const char *const zx297520v3_dt_compat[] __initconst = {
+	"zte,zx297520v3",
+	NULL,
+};
+
+DT_MACHINE_START(ZX, "ZTE zx297520v3 (Device Tree)")
+	.dt_compat	= zx297520v3_dt_compat,
+MACHINE_END

-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 2/8] dt-bindings: arm: Add zx297520v3 board binding
From: Stefan Dösinger @ 2026-04-16 20:19 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
	Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
	Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
	linux-serial, Stefan Dösinger
In-Reply-To: <20260416-send-v4-0-e19d02b944ec@gmail.com>

Add a compatible for boards based on the ZTE zx297520v3 SoC.

Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>

---

The list of devices is the devices I have access to for testing. There
are many more devices based on this board and it is not always easy to
identify them. Often they are sold without any branding ("4G home
router") or with mobile carrier branding.
---
 Documentation/devicetree/bindings/arm/zte.yaml | 25 +++++++++++++++++++++++++
 MAINTAINERS                                    |  1 +
 2 files changed, 26 insertions(+)

diff --git a/Documentation/devicetree/bindings/arm/zte.yaml b/Documentation/devicetree/bindings/arm/zte.yaml
new file mode 100644
index 000000000000..6eba09edd2c5
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/zte.yaml
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/zte.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ZTE zx29
+
+maintainers:
+  - Stefan Dösinger <stefandoesinger@gmail.com>
+
+properties:
+  $nodename:
+    const: "/"
+  compatible:
+    oneOf:
+      - items:
+          - enum:
+            - dlink,dwr932m
+            - hgsd,r310
+            - tecno,tr118
+            - zte,k10
+          - const: zte,zx297520v3
+
+additionalProperties: true
diff --git a/MAINTAINERS b/MAINTAINERS
index 974d7a98956a..bcade90ca14e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29202,6 +29202,7 @@ F:	tools/testing/selftests/cgroup/test_zswap.c
 
 ZX29
 M:	Stefan Dösinger <stefandoesinger@gmail.com>
+F:	Documentation/devicetree/bindings/arm/zte.yaml
 F:	arch/arm/mach-zte/
 
 SENARYTECH AUDIO CODEC DRIVER

-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 3/8] ARM: dts: Add D-Link DWR-932M support
From: Stefan Dösinger @ 2026-04-16 20:19 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
	Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
	Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
	linux-serial, Stefan Dösinger
In-Reply-To: <20260416-send-v4-0-e19d02b944ec@gmail.com>

This adds DT bindings for zx297520v3 and one board that consumes it.

Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
 MAINTAINERS                              |  1 +
 arch/arm/boot/dts/Makefile               |  1 +
 arch/arm/boot/dts/zte/Makefile           |  3 +++
 arch/arm/boot/dts/zte/dlink-dwr-932m.dts | 21 ++++++++++++++++++
 arch/arm/boot/dts/zte/zx297520v3.dtsi    | 37 ++++++++++++++++++++++++++++++++
 5 files changed, 63 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index bcade90ca14e..f7ca0d478e81 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29203,6 +29203,7 @@ F:	tools/testing/selftests/cgroup/test_zswap.c
 ZX29
 M:	Stefan Dösinger <stefandoesinger@gmail.com>
 F:	Documentation/devicetree/bindings/arm/zte.yaml
+F:	arch/arm/boot/dts/zte
 F:	arch/arm/mach-zte/
 
 SENARYTECH AUDIO CODEC DRIVER
diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
index efe38eb25301..28fba538d552 100644
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
@@ -39,3 +39,4 @@ subdir-y += unisoc
 subdir-y += vt8500
 subdir-y += xen
 subdir-y += xilinx
+subdir-y += zte
diff --git a/arch/arm/boot/dts/zte/Makefile b/arch/arm/boot/dts/zte/Makefile
new file mode 100644
index 000000000000..416c24a489cd
--- /dev/null
+++ b/arch/arm/boot/dts/zte/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+dtb-$(CONFIG_SOC_ZX297520V3) += \
+	dlink-dwr-932m.dtb
diff --git a/arch/arm/boot/dts/zte/dlink-dwr-932m.dts b/arch/arm/boot/dts/zte/dlink-dwr-932m.dts
new file mode 100644
index 000000000000..7b2a26aaaecb
--- /dev/null
+++ b/arch/arm/boot/dts/zte/dlink-dwr-932m.dts
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * D-Link DWR-932M Board
+ *
+ * (C) Copyright 2026 Stefan Dösinger
+ *
+ */
+
+/dts-v1/;
+
+#include "zx297520v3.dtsi"
+
+/ {
+	model = "D-Link DWR-932M";
+	compatible = "dlink,dwr932m", "zte,zx297520v3";
+
+	memory@20000000 {
+		device_type = "memory";
+		reg = <0x20000000 0x04000000>;
+	};
+};
diff --git a/arch/arm/boot/dts/zte/zx297520v3.dtsi b/arch/arm/boot/dts/zte/zx297520v3.dtsi
new file mode 100644
index 000000000000..d6c71d52b26c
--- /dev/null
+++ b/arch/arm/boot/dts/zte/zx297520v3.dtsi
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		cpu@0 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a53";
+			reg = <0>;
+		};
+	};
+
+	soc {
+		#address-cells = <1>;
+		#size-cells = <1>;
+		compatible = "simple-bus";
+		interrupt-parent = <&gic>;
+		ranges;
+
+		gic: interrupt-controller@f2000000 {
+			compatible = "arm,gic-v3";
+			interrupt-controller;
+			#interrupt-cells = <3>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+			reg = <0xf2000000 0x10000>,
+			      <0xf2040000 0x20000>;
+		};
+	};
+};

-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 4/8] ARM: zte: Add support for zx29 low level debug
From: Stefan Dösinger @ 2026-04-16 20:19 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
	Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
	Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
	linux-serial, Stefan Dösinger
In-Reply-To: <20260416-send-v4-0-e19d02b944ec@gmail.com>

This is based on the removed zx29 code. A separate (more complicated)
patch will re-add the register map to the pl011 serial driver.

Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>

---

I am unsure about the virtual address. It doesn't seem to matter, as
long as it is a valid address. This address is based on the old removed
code. Is there a rule-of-thumb physical to virtual mapping I can use to
give a sensible default value?
---
 arch/arm/Kconfig.debug         | 12 ++++++++++++
 arch/arm/include/debug/pl01x.S |  7 +++++++
 2 files changed, 19 insertions(+)

diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug
index 366f162e147d..98d8a5a60048 100644
--- a/arch/arm/Kconfig.debug
+++ b/arch/arm/Kconfig.debug
@@ -1331,6 +1331,16 @@ choice
 		  This option selects UART0 on VIA/Wondermedia System-on-a-chip
 		  devices, including VT8500, WM8505, WM8650 and WM8850.
 
+	config DEBUG_ZTE_ZX
+		bool "Kernel low-level debugging via zx29 UART"
+		select DEBUG_UART_PL01X
+		depends on ARCH_ZTE
+		help
+		  Say Y here if you are enabling ZTE zx297520v3 SOC and need
+		  debug UART support. This UART is a PL011 with different
+		  register addresses. The UART for boot messages on zx29 boards
+		  is usually UART1 and is operating at 921600 8N1.
+
 	config DEBUG_ZYNQ_UART0
 		bool "Kernel low-level debugging on Xilinx Zynq using UART0"
 		depends on ARCH_ZYNQ
@@ -1545,6 +1555,7 @@ config DEBUG_UART_8250
 
 config DEBUG_UART_PHYS
 	hex "Physical base address of debug UART"
+	default 0x01408000 if DEBUG_ZTE_ZX
 	default 0x01c28000 if DEBUG_SUNXI_UART0
 	default 0x01c28400 if DEBUG_SUNXI_UART1
 	default 0x01d0c000 if DEBUG_DAVINCI_DA8XX_UART1
@@ -1701,6 +1712,7 @@ config DEBUG_UART_VIRT
 	default 0xf31004c0 if DEBUG_MESON_UARTAO
 	default 0xf4090000 if DEBUG_LPC32XX
 	default 0xf4200000 if DEBUG_GEMINI
+	default 0xf4708000 if DEBUG_ZTE_ZX
 	default 0xf6200000 if DEBUG_PXA_UART1
 	default 0xf7000000 if DEBUG_SUN9I_UART0
 	default 0xf7000000 if DEBUG_S3C64XX_UART && DEBUG_S3C_UART0
diff --git a/arch/arm/include/debug/pl01x.S b/arch/arm/include/debug/pl01x.S
index c7e02d0628bf..0c7bfa4c10db 100644
--- a/arch/arm/include/debug/pl01x.S
+++ b/arch/arm/include/debug/pl01x.S
@@ -8,6 +8,13 @@
 */
 #include <linux/amba/serial.h>
 
+#ifdef CONFIG_DEBUG_ZTE_ZX
+#undef UART01x_DR
+#undef UART01x_FR
+#define UART01x_DR     0x04
+#define UART01x_FR     0x14
+#endif
+
 #ifdef CONFIG_DEBUG_UART_PHYS
 		.macro	addruart, rp, rv, tmp
 		ldr	\rp, =CONFIG_DEBUG_UART_PHYS

-- 
2.52.0


^ permalink raw reply related

* [PATCH v4 5/8] ARM: dts: Add an armv7 timer for zx297520v3
From: Stefan Dösinger @ 2026-04-16 20:19 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
	Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
	Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
	linux-serial, Stefan Dösinger
In-Reply-To: <20260416-send-v4-0-e19d02b944ec@gmail.com>

The stock kernel does not use this timer, but it seems to work fine. The
board has other board-specific timers that would need a driver and I see
no reason to bother with them since the arm standard timer works.

The caveat is the non-standard GIC setup needed to handle the timer's
level-low PPI. This is the responsibility of the boot loader and
documented in Documentation/arch/arm/zte/zx297520v3.rst.

Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
 arch/arm/boot/dts/zte/zx297520v3.dtsi | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/arch/arm/boot/dts/zte/zx297520v3.dtsi b/arch/arm/boot/dts/zte/zx297520v3.dtsi
index d6c71d52b26c..ecd07f3fb8b3 100644
--- a/arch/arm/boot/dts/zte/zx297520v3.dtsi
+++ b/arch/arm/boot/dts/zte/zx297520v3.dtsi
@@ -24,6 +24,15 @@ soc {
 		interrupt-parent = <&gic>;
 		ranges;
 
+		/* The GIC has a non-standard way of configuring ints between level-low/level
+		 * high or rising edge/falling edge at 0xf2202070 and onwards. See AP_INT_MODE_BASE
+		 * and AP_PPI_MODE_REG in the ZTE kernel, although the offsets in the kernel source
+		 * seem wrong.
+		 *
+		 * Everything defaults to active-high/rising edge, but the timer is active-low. We
+		 * currently rely on the boot loader to change timer IRQs to active-low for us for
+		 * now.
+		 */
 		gic: interrupt-controller@f2000000 {
 			compatible = "arm,gic-v3";
 			interrupt-controller;
@@ -33,5 +42,20 @@ gic: interrupt-controller@f2000000 {
 			reg = <0xf2000000 0x10000>,
 			      <0xf2040000 0x20000>;
 		};
+
+		timer {
+			compatible = "arm,armv7-timer";
+			interrupts = <GIC_PPI 13 IRQ_TYPE_LEVEL_LOW>,
+				<GIC_PPI 14 IRQ_TYPE_LEVEL_LOW>,
+				<GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
+				<GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
+			clock-frequency = <26000000>;
+			interrupt-parent = <&gic>;
+			/* I don't think uboot sets CNTVOFF and the stock kernel doesn't use the
+			 * arm timer at all. Since this is a single CPU system I don't think it
+			 * really matters that the offset is random though.
+			 */
+			arm,cpu-registers-not-fw-configured;
+		};
 	};
 };

-- 
2.52.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox