| 0/0 |
2026/05/20 05:07 |
flow |
assessment-security |
8m
Results: map[DenialOfService:true Explanation:Based on the analysis of the provided bug report and the kernel source code,
here is the assessment of the vulnerability:
### Root Cause Analysis
The bug is a **Hung Task / Denial of Service (DoS)** caused by holding a mutex
while performing a user-space memory access (`copy_from_user`).
In `sound/core/seq/seq_clientmgr.c`, the `snd_seq_write()` function processes
events written to `/dev/snd/seq`. It acquires the `client->ioctl_mutex` and then
loops over the provided user buffer, calling `copy_from_user()` to read the
events:
```c
mutex_lock(&client->ioctl_mutex);
...
while (count >= sizeof(struct snd_seq_event)) {
/* Read in the event header from the user */
len = sizeof(struct snd_seq_event);
if (copy_from_user(ev, buf, len)) {
...
```
An unprivileged attacker can exploit this by passing a buffer backed by
`userfaultfd` or a malicious FUSE filesystem. When `copy_from_user()` is called,
the page fault is intercepted by the attacker's user-space process, which can
intentionally pause the fault handling indefinitely. This causes the kernel
thread to hang forever while holding the `client->ioctl_mutex`.
Meanwhile, other legitimate kernel paths need to acquire this mutex. For
instance, reading `/proc/asound/seq/clients` (handled by
`snd_seq_info_clients_read()`) iterates over all sequencer clients and acquires
their `ioctl_mutex` one by one to dump their state:
```c
for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) {
struct snd_seq_client *client __free(snd_seq_client) =
client_load_and_use_ptr(c);
...
guard(mutex)(&client->ioctl_mutex);
...
```
If a system monitoring daemon, an administrator running `cat
/proc/asound/seq/clients`, or another process attempts to read this procfs file,
it will eventually reach the attacker's client, attempt to acquire the held
`ioctl_mutex`, and hang indefinitely. This results in the `khungtaskd` warnings
seen in the report and causes a localized Denial of Service.
### Security Impact
* **Exploitable:** No. This is a synchronization issue leading to a thread hang.
It does not result in memory corruption, out-of-bounds access, or privilege
escalation.
* **Denial Of Service:** Yes. It can permanently hang processes that interact
with the ALSA sequencer subsystem or read its procfs entries.
* **Accessible From Unprivileged Processes:** Yes. Any local user with
permissions to open `/dev/snd/seq` (typically granted via the `audio` group or
`systemd-logind` ACLs for the active session) can trigger this.
* **Accessible From User Namespaces:** Yes, provided the user namespace has
access to the `/dev/snd/seq` device node.
* **Other Triggers (Network, VM, Peripheral, Filesystem):** No. This requires
local execution of a program interacting with the ALSA sequencer character
device. Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/20 05:07 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/05/20 05:07 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/e94be9e63df30e296da31f0cd33d4ed624c2c54f]
|
| 3/1 |
2026/05/20 05:07 |
action |
kernel-builder |
0m
Results: map[KernelObj:/app/workdir/cache/build/c62432286e78ba04665f1c14b9f58cd5d4f94565]
|
| 4/1 |
2026/05/20 05:07 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/20 05:08 |
agent |
expert |
7m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Instruction:
You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by greeping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Don't make assumptions; verify them with source code access. Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption or elevated privileges.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completly wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control tranfers, that's also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitabability depends on how exactly the uninit value
is used in the code, and what it affects.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidetial Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
INFO: task syz.5.990:9676 blocked for more than 143 seconds.
Tainted: G L syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:syz.5.990 state:D stack:28088 pid:9676 tgid:9673 ppid:8598 task_flags:0x400040 flags:0x00080002
Call Trace:
<TASK>
context_switch kernel/sched/core.c:5388 [inline]
__schedule+0x17b4/0x5680 kernel/sched/core.c:7189
__schedule_loop kernel/sched/core.c:7268 [inline]
schedule+0x164/0x360 kernel/sched/core.c:7283
schedule_preempt_disabled+0x13/0x30 kernel/sched/core.c:7340
__mutex_lock_common kernel/locking/mutex.c:726 [inline]
__mutex_lock+0x7f7/0x1550 kernel/locking/mutex.c:820
class_mutex_constructor include/linux/mutex.h:253 [inline]
snd_seq_info_clients_read+0x14a/0x820 sound/core/seq/seq_clientmgr.c:2609
snd_info_seq_show+0xd9/0x100 sound/core/info.c:342
traverse+0x1ee/0x580 fs/seq_file.c:112
seq_read_iter+0xd08/0xe10 fs/seq_file.c:196
seq_read+0x367/0x480 fs/seq_file.c:163
pde_read fs/proc/inode.c:308 [inline]
proc_reg_read+0x1e9/0x2e0 fs/proc/inode.c:320
do_loop_readv_writev fs/read_write.c:849 [inline]
vfs_readv+0x587/0x840 fs/read_write.c:1022
do_preadv fs/read_write.c:1134 [inline]
__do_sys_preadv fs/read_write.c:1181 [inline]
__se_sys_preadv fs/read_write.c:1176 [inline]
__x64_sys_preadv+0x19f/0x2a0 fs/read_write.c:1176
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f479719cdd9
RSP: 002b:00007f4798027028 EFLAGS: 00000246 ORIG_RAX: 0000000000000127
RAX: ffffffffffffffda RBX: 00007f4797416180 RCX: 00007f479719cdd9
RDX: 0000000000000001 RSI: 0000200000004ec0 RDI: 0000000000000007
RBP: 00007f4797232d69 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000008000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f4797416218 R14: 00007f4797416180 R15: 00007f479753fa48
</TASK>
Showing all locks held in the system:
1 lock held by pool_workqueue_/3:
#0: ffffffff8e963068 (rcu_state.exp_mutex){+.+.}-{4:4}, at: exp_funnel_lock kernel/rcu/tree_exp.h:343 [inline]
#0: ffffffff8e963068 (rcu_state.exp_mutex){+.+.}-{4:4}, at: synchronize_rcu_expedited+0x38d/0x770 kernel/rcu/tree_exp.h:961
1 lock held by khungtaskd/30:
#0: ffffffff8e95cd60 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
#0: ffffffff8e95cd60 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:838 [inline]
#0: ffffffff8e95cd60 (rcu_read_lock){....}-{1:3}, at: debug_show_all_locks+0x2e/0x180 kernel/locking/lockdep.c:6775
3 locks held by kworker/u8:4/62:
#0: ffff88801be8e140 ((wq_completion)netns){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3277 [inline]
#0: ffff88801be8e140 ((wq_completion)netns){+.+.}-{0:0}, at: process_scheduled_works+0xa35/0x1860 kernel/workqueue.c:3385
#1: ffffc9000202fc40 (net_cleanup_work){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3278 [inline]
#1: ffffc9000202fc40 (net_cleanup_work){+.+.}-{0:0}, at: process_scheduled_works+0xa70/0x1860 kernel/workqueue.c:3385
#2: ffffffff8fdc0968 (pernet_ops_rwsem){++++}-{4:4}, at: cleanup_net+0xf4/0x800 net/core/net_namespace.c:673
2 locks held by getty/5380:
#0: ffff888035dec0a0 (&tty->ldisc_sem){++++}-{0:0}, at: tty_ldisc_ref_wait+0x25/0x70 drivers/tty/tty_ldisc.c:243
#1: ffffc9000322b2e8 (&ldata->atomic_read_lock){+.+.}-{4:4}, at: n_tty_read+0x45c/0x13a0 drivers/tty/n_tty.c:2211
1 lock held by syz.1.669/8406:
2 locks held by syz.5.990/9676:
#0: ffff888057b47878 (&p->lock){+.+.}-{4:4}, at: seq_read_iter+0xb7/0xe10 fs/seq_file.c:183
#1: ffff888028f9c9c0 (&client->ioctl_mutex){+.+.}-{4:4}, at: class_mutex_constructor include/linux/mutex.h:253 [inline]
#1: ffff888028f9c9c0 (&client->ioctl_mutex){+.+.}-{4:4}, at: snd_seq_info_clients_read+0x14a/0x820 sound/core/seq/seq_clientmgr.c:2609
2 locks held by syz.7.1478/11803:
1 lock held by syz.6.1489/11845:
#0: ffffffff8e962f38 (rcu_state.barrier_mutex){+.+.}-{4:4}, at: rcu_barrier+0x4c/0x580 kernel/rcu/tree.c:3828
3 locks held by syz.6.1489/11846:
#0: ffff888026b0f3a0 (&set->update_nr_hwq_lock){++++}-{4:4}, at: del_gendisk+0xdf/0x160 block/genhd.c:822
#1: ffff888026ac3d68 (&q->elevator_lock){+.+.}-{4:4}, at: elevator_change+0x1b3/0x450 block/elevator.c:679
#2: ffffffff8e963068 (rcu_state.exp_mutex){+.+.}-{4:4}, at: exp_funnel_lock kernel/rcu/tree_exp.h:343 [inline]
#2: ffffffff8e963068 (rcu_state.exp_mutex){+.+.}-{4:4}, at: synchronize_rcu_expedited+0x38d/0x770 kernel/rcu/tree_exp.h:961
1 lock held by syz.1.1491/11857:
#0: ffffffff8e962f38 (rcu_state.barrier_mutex){+.+.}-{4:4}, at: rcu_barrier+0x4c/0x580 kernel/rcu/tree.c:3828
1 lock held by dhcpcd-run-hook/11871:
=============================================
NMI backtrace for cpu 0
CPU: 0 UID: 0 PID: 30 Comm: khungtaskd Tainted: G L syzkaller #0 PREEMPT(full)
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
nmi_cpu_backtrace+0x274/0x2d0 lib/nmi_backtrace.c:113
nmi_trigger_cpumask_backtrace+0x17a/0x300 lib/nmi_backtrace.c:62
trigger_all_cpu_backtrace include/linux/nmi.h:162 [inline]
__sys_info lib/sys_info.c:157 [inline]
sys_info+0x135/0x170 lib/sys_info.c:165
check_hung_uninterruptible_tasks kernel/hung_task.c:353 [inline]
watchdog+0xfd3/0x1030 kernel/hung_task.c:561
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Sending NMI from CPU 0 to CPUs 1:
NMI backtrace for cpu 1
CPU: 1 UID: 0 PID: 0 Comm: swapper/1 Tainted: G L syzkaller #0 PREEMPT(full)
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
RIP: 0010:pv_native_safe_halt+0xf/0x20 arch/x86/kernel/paravirt.c:63
Code: 1b 7e 02 e9 93 f6 02 00 cc cc cc 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 f3 0f 1e fa 66 90 0f 00 2d 13 31 21 00 fb f4 <c3> cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc 90 90 90 90 90
RSP: 0018:ffffc90000197e20 EFLAGS: 00000246
RAX: 000000000080fb33 RBX: ffffffff819a811a RCX: 0000000080000001
RDX: 0000000000000001 RSI: ffffffff8dfa5a7b RDI: ffffffff8c28ac60
RBP: ffffc90000197f10 R08: ffff8880b87339db R09: 1ffff110170e673b
R10: dffffc0000000000 R11: ffffed10170e673c R12: 0000000000000001
R13: 1ffff11003b5b000 R14: 0000000000000001 R15: 1ffff11003b5b000
FS: 0000000000000000(0000) GS:ffff888125390000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000056219dd95078 CR3: 000000000e74a000 CR4: 00000000003526f0
DR0: 0000000000000008 DR1: 0000000000000002 DR2: 0000000000000404
DR3: ffffffffefffff14 DR6: 00000000ffff0ff0 DR7: 0000000000000400
Call Trace:
<TASK>
arch_safe_halt arch/x86/kernel/process.c:766 [inline]
default_idle+0x9/0x20 arch/x86/kernel/process.c:767
default_idle_call+0x72/0xb0 kernel/sched/idle.c:122
cpuidle_idle_call kernel/sched/idle.c:199 [inline]
do_idle+0x36a/0x5f0 kernel/sched/idle.c:352
cpu_startup_entry+0x43/0x60 kernel/sched/idle.c:451
start_secondary+0x101/0x110 arch/x86/kernel/smpboot.c:312
common_startup_64+0x13e/0x147
</TASK>
Reply: Based on the analysis of the provided bug report and the kernel source code, here is the assessment of the vulnerability:
### Root Cause Analysis
The bug is a **Hung Task / Denial of Service (DoS)** caused by holding a mutex while performing a user-space memory access (`copy_from_user`).
In `sound/core/seq/seq_clientmgr.c`, the `snd_seq_write()` function processes events written to `/dev/snd/seq`. It acquires the `client->ioctl_mutex` and then loops over the provided user buffer, calling `copy_from_user()` to read the events:
```c
mutex_lock(&client->ioctl_mutex);
...
while (count >= sizeof(struct snd_seq_event)) {
/* Read in the event header from the user */
len = sizeof(struct snd_seq_event);
if (copy_from_user(ev, buf, len)) {
...
```
An unprivileged attacker can exploit this by passing a buffer backed by `userfaultfd` or a malicious FUSE filesystem. When `copy_from_user()` is called, the page fault is intercepted by the attacker's user-space process, which can intentionally pause the fault handling indefinitely. This causes the kernel thread to hang forever while holding the `client->ioctl_mutex`.
Meanwhile, other legitimate kernel paths need to acquire this mutex. For instance, reading `/proc/asound/seq/clients` (handled by `snd_seq_info_clients_read()`) iterates over all sequencer clients and acquires their `ioctl_mutex` one by one to dump their state:
```c
for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) {
struct snd_seq_client *client __free(snd_seq_client) =
client_load_and_use_ptr(c);
...
guard(mutex)(&client->ioctl_mutex);
...
```
If a system monitoring daemon, an administrator running `cat /proc/asound/seq/clients`, or another process attempts to read this procfs file, it will eventually reach the attacker's client, attempt to acquire the held `ioctl_mutex`, and hang indefinitely. This results in the `khungtaskd` warnings seen in the report and causes a localized Denial of Service.
### Security Impact
* **Exploitable:** No. This is a synchronization issue leading to a thread hang. It does not result in memory corruption, out-of-bounds access, or privilege escalation.
* **Denial Of Service:** Yes. It can permanently hang processes that interact with the ALSA sequencer subsystem or read its procfs entries.
* **Accessible From Unprivileged Processes:** Yes. Any local user with permissions to open `/dev/snd/seq` (typically granted via the `audio` group or `systemd-logind` ACLs for the active session) can trigger this.
* **Accessible From User Namespaces:** Yes, provided the user namespace has access to the `/dev/snd/seq` device node.
* **Other Triggers (Network, VM, Peripheral, Filesystem):** No. This requires local execution of a program interacting with the ALSA sequencer character device.
|
| 6/2 |
2026/05/20 05:08 |
llm |
expert |
0m
Model:
Tokens:
input: 8478
output: 50
thoughts: 726
Thoughts: **Analyzing a Hung Task in the Sound Subsystem**
Alright, I've got a hung task report here, `syz.5.990:9676`, and it's been blocked for a significant amount of time β 143 seconds, to be exact. The call trace points directly to `snd_seq_info_clients_read` within `sound/core/seq/seq_clientmgr.c`. This isn't immediately alarming, but a prolonged block is definitely something to investigate.
Looking at the trace, it looks like it's getting hung up within `snd_seq_info_clients_read` and more specifically that it's contending on `client->ioctl_mutex`. That mutex is very likely protecting access to the client list or some state associated with it, particularly in relation to an ioctl.
This could mean a few things: either this particular task is waiting on an ioctl call that's stalled, or perhaps another task is holding the mutex for an extended period, preventing this one from proceeding. We're looking at a potential lock contention issue within the sound subsystem. My next step will be to dive into `snd_seq_info_clients_read` and the surrounding code, focusing on how that mutex is acquired and released, and what potential scenarios could lead to this long blocking time. I want to see how the read is implemented as well, which will allow me to assess if this is a blocking read, or something else is happening. It's time to dig into the details and find out what's causing this bottleneck.
|
| 7/2 |
2026/05/20 05:08 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:snd_seq_info_clients_read]
Results: map[SourceCode:2587: /* exported to seq_info.c */
2588: void snd_seq_info_clients_read(struct snd_info_entry *entry,
2589: struct snd_info_buffer *buffer)
2590: {
2591: int c;
2592:
2593: snd_iprintf(buffer, "Client info\n");
2594: snd_iprintf(buffer, " cur clients : %d\n", client_usage.cur);
2595: snd_iprintf(buffer, " peak clients : %d\n", client_usage.peak);
2596: snd_iprintf(buffer, " max clients : %d\n", SNDRV_SEQ_MAX_CLIENTS);
2597: snd_iprintf(buffer, "\n");
2598:
2599: /* list the client table */
2600: for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) {
2601: struct snd_seq_client *client __free(snd_seq_client) =
2602: client_load_and_use_ptr(c);
2603:
2604: if (client == NULL)
2605: continue;
2606: if (client->type == NO_CLIENT)
2607: continue;
2608:
2609: guard(mutex)(&client->ioctl_mutex);
2610: snd_iprintf(buffer, "Client %3d : \"%s\" [%s %s]\n",
2611: c, client->name,
2612: client->type == USER_CLIENT ? "User" : "Kernel",
2613: midi_version_string(client->midi_version));
2614: #if IS_ENABLED(CONFIG_SND_SEQ_UMP)
2615: dump_ump_info(buffer, client);
2616: #endif
2617: snd_seq_info_dump_ports(buffer, client);
2618: if (snd_seq_write_pool_allocated(client)) {
2619: snd_iprintf(buffer, " Output pool :\n");
2620: snd_seq_info_pool(buffer, client->pool, " ");
2621: }
2622: if (client->type == USER_CLIENT && client->data.user.fifo &&
2623: client->data.user.fifo->pool) {
2624: snd_iprintf(buffer, " Input pool :\n");
2625: snd_seq_info_pool(buffer, client->data.user.fifo->pool, " ");
2626: }
2627: }
2628: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 8/2 |
2026/05/20 05:08 |
llm |
expert |
0m
Model:
Tokens:
input: 10373
output: 58
thoughts: 52
Thoughts: **Debugging a Mutex Block**
Okay, so I'm stuck, it looks like the current task is blocked at line 2609 because of `guard(mutex)(&client->ioctl_mutex)`. That means another thread or process already has a lock on `client->ioctl_mutex`. Alright, time to trace back and figure out where this mutex is being acquired. I need to understand the call stack that leads to the other holding thread. This will help me figure out *why* it's being held and if there's any potential deadlock scenario or long-running critical section that's preventing the current task from proceeding. Time to dig in and unravel this.
|
| 9/2 |
2026/05/20 05:08 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeSnippetLines:0 Name:snd_seq_client::ioctl_mutex SourceTreePrefix:]
Results: map[References:[map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:seq_create_client1 SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:218] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_info_clients_read SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:2609] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_info_clients_read SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:2609] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_ioctl SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:2248] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_ioctl SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:2248] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_kernel_client_enqueue SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:2372] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_kernel_client_enqueue SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:2372] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_kernel_client_enqueue SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:2375] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_kernel_client_ioctl SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:2459] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_kernel_client_ioctl SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:2459] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_write SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:985] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_write SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:1057] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_write SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:1070] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_write SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:1076]] TruncatedOutput:false]
|
| 10/2 |
2026/05/20 05:08 |
llm |
expert |
0m
Model:
Tokens:
input: 11498
output: 46
thoughts: 52
Thoughts: Okay, here's my take on those thoughts, framed as my own mental processing as an audio expert:
**Tracing the Sequence Event Injection**
Alright, so I'm trying to pinpoint where sequence events are getting injected into the kernel. My initial focus is on the kernel's handling of these events, and I need to figure out the entry points. The code suggests a few key functions: `snd_seq_ioctl`, `snd_seq_kernel_client_enqueue`, `snd_seq_kernel_client_ioctl`, and `snd_seq_write`. I'm seeing a pattern here, and I suspect `snd_seq_write` is a strong candidate, so I will start there.
|
| 11/2 |
2026/05/20 05:08 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:snd_seq_write]
Results: map[SourceCode: 952: /* handle write() */
953: /* possible error values:
954: * -ENXIO invalid client or file open mode
955: * -ENOMEM malloc failed
956: * -EFAULT seg. fault during copy from user space
957: * -EINVAL invalid event
958: * -EAGAIN no space in output pool
959: * -EINTR interrupts while sleep
960: * -EMLINK too many hops
961: * others depends on return value from driver callback
962: */
963: static ssize_t snd_seq_write(struct file *file, const char __user *buf,
964: size_t count, loff_t *offset)
965: {
966: struct snd_seq_client *client = file->private_data;
967: int written = 0, len;
968: int err, handled;
969: union __snd_seq_event __event;
970: struct snd_seq_event *ev = &__event.legacy;
971:
972: if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT))
973: return -ENXIO;
974:
975: /* check client structures are in place */
976: if (snd_BUG_ON(!client))
977: return -ENXIO;
978:
979: if (!client->accept_output || client->pool == NULL)
980: return -ENXIO;
981:
982: repeat:
983: handled = 0;
984: /* allocate the pool now if the pool is not allocated yet */
985: mutex_lock(&client->ioctl_mutex);
986: if (client->pool->size > 0 && !snd_seq_write_pool_allocated(client)) {
987: err = snd_seq_pool_init(client->pool);
988: if (err < 0)
989: goto out;
990: }
991:
992: /* only process whole events */
993: err = -EINVAL;
994: while (count >= sizeof(struct snd_seq_event)) {
995: /* Read in the event header from the user */
996: len = sizeof(struct snd_seq_event);
997: if (copy_from_user(ev, buf, len)) {
998: err = -EFAULT;
999: break;
1000: }
1001: /* read in the rest bytes for UMP events */
1002: if (snd_seq_ev_is_ump(ev)) {
1003: if (count < sizeof(struct snd_seq_ump_event))
1004: break;
1005: if (copy_from_user((char *)ev + len, buf + len,
1006: sizeof(struct snd_seq_ump_event) - len)) {
1007: err = -EFAULT;
1008: break;
1009: }
1010: len = sizeof(struct snd_seq_ump_event);
1011: }
1012:
1013: ev->source.client = client->number; /* fill in client number */
1014: /* Check for extension data length */
1015: if (check_event_type_and_length(ev)) {
1016: err = -EINVAL;
1017: break;
1018: }
1019:
1020: if (!event_is_compatible(client, ev)) {
1021: err = -EINVAL;
1022: break;
1023: }
1024:
1025: /* check for special events */
1026: if (!snd_seq_ev_is_ump(ev)) {
1027: if (ev->type == SNDRV_SEQ_EVENT_NONE)
1028: goto __skip_event;
1029: else if (snd_seq_ev_is_reserved(ev)) {
1030: err = -EINVAL;
1031: break;
1032: }
1033: }
1034:
1035: if (snd_seq_ev_is_variable(ev)) {
1036: int extlen = ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
1037: if ((size_t)(extlen + len) > count) {
1038: /* back out, will get an error this time or next */
1039: err = -EINVAL;
1040: break;
1041: }
1042: /* set user space pointer */
1043: ev->data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR;
1044: ev->data.ext.ptr = (char __force *)buf + len;
1045: len += extlen; /* increment data length */
1046: } else {
1047: #ifdef CONFIG_COMPAT
1048: if (client->convert32 && snd_seq_ev_is_varusr(ev))
1049: ev->data.ext.ptr =
1050: (void __force *)compat_ptr(ev->data.raw32.d[1]);
1051: #endif
1052: }
1053:
1054: /* ok, enqueue it */
1055: err = snd_seq_client_enqueue_event(client, ev, file,
1056: !(file->f_flags & O_NONBLOCK),
1057: 0, 0, &client->ioctl_mutex);
1058: if (err < 0)
1059: break;
1060: handled++;
1061:
1062: __skip_event:
1063: /* Update pointers and counts */
1064: count -= len;
1065: buf += len;
1066: written += len;
1067:
1068: /* let's have a coffee break if too many events are queued */
1069: if (++handled >= 200) {
1070: mutex_unlock(&client->ioctl_mutex);
1071: goto repeat;
1072: }
1073: }
1074:
1075: out:
1076: mutex_unlock(&client->ioctl_mutex);
1077: return written ? written : err;
1078: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 12/2 |
2026/05/20 05:08 |
llm |
expert |
0m
Model:
Tokens:
input: 13654
output: 50
thoughts: 101
Thoughts: **Analyzing the Sequencer Write Function**
Okay, let's break down this `snd_seq_write` function... I'm seeing a `mutex_lock` call right at line 985, specifically locking the `client->ioctl_mutex`. Standard practice for protecting shared resources, makes sense. Then, the code loops through a series of events, and for each, it calls `snd_seq_client_enqueue_event` at line 1055. Ah, I remember that function; it handles enqueuing the events within the client.
Now, wait a minute... I'm scanning this loop and notice a `handled >= 200` check. If that condition is met, the mutex is unlocked, and the execution jumps to the label `repeat`. My focus zeroes in on the argument passed to `snd_seq_client_enqueue_event`. It takes the address of `&client->ioctl_mutex`. That is the same mutex that is locked, and unlocked. I wonder why. I need to understand how the mutex is used in `snd_seq_client_enqueue_event` itself. It seems odd, and potentially problematic, to pass a mutex that's already held by the calling function. Maybe this is related to some re-entrancy issues? Or perhaps some specific event ordering requirements? Either way, I'm going to have to dive into the implementation of `snd_seq_client_enqueue_event` to get a clearer picture of what's really going on here.
|
| 13/2 |
2026/05/20 05:08 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:snd_seq_client_enqueue_event]
Results: map[SourceCode: 871: /* Allocate a cell from client pool and enqueue it to queue:
872: * if pool is empty and blocking is TRUE, sleep until a new cell is
873: * available.
874: */
875: static int snd_seq_client_enqueue_event(struct snd_seq_client *client,
876: struct snd_seq_event *event,
877: struct file *file, int blocking,
878: int atomic, int hop,
879: struct mutex *mutexp)
880: {
881: struct snd_seq_event_cell *cell;
882: int err;
883:
884: /* special queue values - force direct passing */
885: if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
886: event->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
887: event->queue = SNDRV_SEQ_QUEUE_DIRECT;
888: } else if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
889: /* check presence of source port */
890: struct snd_seq_client_port *src_port __free(snd_seq_port) =
891: snd_seq_port_use_ptr(client, event->source.port);
892: if (!src_port)
893: return -EINVAL;
894: }
895:
896: /* direct event processing without enqueued */
897: if (snd_seq_ev_is_direct(event)) {
898: if (!snd_seq_ev_is_ump(event) &&
899: event->type == SNDRV_SEQ_EVENT_NOTE)
900: return -EINVAL; /* this event must be enqueued! */
901: return snd_seq_deliver_event(client, event, atomic, hop);
902: }
903:
904: /* Not direct, normal queuing */
905: if (snd_seq_queue_is_used(event->queue, client->number) <= 0)
906: return -EINVAL; /* invalid queue */
907: if (! snd_seq_write_pool_allocated(client))
908: return -ENXIO; /* queue is not allocated */
909:
910: /* allocate an event cell */
911: err = snd_seq_event_dup(client->pool, event, &cell, !blocking || atomic,
912: file, mutexp);
913: if (err < 0)
914: return err;
915:
916: /* we got a cell. enqueue it. */
917: err = snd_seq_enqueue_event(cell, atomic, hop);
918: if (err < 0) {
919: snd_seq_cell_free(cell);
920: return err;
921: }
922:
923: return 0;
924: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 14/2 |
2026/05/20 05:08 |
llm |
expert |
0m
Model:
Tokens:
input: 15027
output: 48
thoughts: 29
Thoughts: Here's a summary of my immediate thought process, focusing on the provided text, from an expert's perspective:
**Debugging a Sequence Event Duplication**
Okay, so `snd_seq_event_dup` has been called with `mutexp`. That's the key. My immediate thought is, "Why *now*? What's the context?" This strongly suggests a potential race condition or locking issue within the ALSA sequencer. The `mutexp` argument is the giveaway. We're dealing with a mutex, which indicates shared resource protection. Duplication of an event suggests that either the event itself is problematic, the handling of the copy is flawed or, more likely given the mutex context, the locking around the event's data or memory management isn't behaving as expected during duplication. My next step will be a deep dive into the implementation of `snd_seq_event_dup` itself, specifically focusing on how it interacts with the `mutexp`. I need to understand what that mutex protects. Is it the event itself, the memory it occupies, or some broader sequence state? I will also be looking to see what other functions may call `snd_seq_event_dup`, in order to get a clearer picture of the data-flow.
|
| 15/2 |
2026/05/20 05:08 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:snd_seq_event_dup]
Results: map[SourceCode: 334: /*
335: * duplicate the event to a cell.
336: * if the event has external data, the data is decomposed to additional
337: * cells.
338: */
339: int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event,
340: struct snd_seq_event_cell **cellp, int nonblock,
341: struct file *file, struct mutex *mutexp)
342: {
343: int ncells, err;
344: unsigned int extlen;
345: struct snd_seq_event_cell *cell;
346: int size;
347:
348: *cellp = NULL;
349:
350: ncells = 0;
351: extlen = 0;
352: if (snd_seq_ev_is_variable(event)) {
353: extlen = event->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
354: ncells = DIV_ROUND_UP(extlen, sizeof(struct snd_seq_event));
355: }
356: if (ncells >= pool->total_elements)
357: return -ENOMEM;
358:
359: err = snd_seq_cell_alloc(pool, &cell, nonblock, file, mutexp);
360: if (err < 0)
361: return err;
362:
363: /* copy the event */
364: size = snd_seq_event_packet_size(event);
365: memcpy(&cell->ump, event, size);
366: #if IS_ENABLED(CONFIG_SND_SEQ_UMP)
367: if (size < sizeof(cell->event))
368: cell->ump.raw.extra = 0;
369: #endif
370:
371: /* decompose */
372: if (snd_seq_ev_is_variable(event)) {
373: int len = extlen;
374: int is_chained = event->data.ext.len & SNDRV_SEQ_EXT_CHAINED;
375: int is_usrptr = event->data.ext.len & SNDRV_SEQ_EXT_USRPTR;
376: struct snd_seq_event_cell *src, *tmp, *tail;
377: char *buf;
378:
379: cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED;
380: cell->event.data.ext.ptr = NULL;
381:
382: src = (struct snd_seq_event_cell *)event->data.ext.ptr;
383: buf = (char *)event->data.ext.ptr;
384: tail = NULL;
385:
386: while (ncells-- > 0) {
387: size = sizeof(struct snd_seq_event);
388: if (len < size)
389: size = len;
390: err = snd_seq_cell_alloc(pool, &tmp, nonblock, file,
391: mutexp);
392: if (err < 0)
393: goto __error;
394: if (cell->event.data.ext.ptr == NULL)
395: cell->event.data.ext.ptr = tmp;
396: if (tail)
397: tail->next = tmp;
398: tail = tmp;
399: /* copy chunk */
400: if (is_chained && src) {
401: tmp->event = src->event;
402: src = src->next;
403: } else if (is_usrptr) {
404: if (copy_from_user(&tmp->event, (char __force __user *)buf, size)) {
405: err = -EFAULT;
406: goto __error;
407: }
408: } else {
409: memcpy(&tmp->event, buf, size);
410: }
411: buf += size;
412: len -= size;
413: }
414: }
415:
416: *cellp = cell;
417: return 0;
418:
419: __error:
420: snd_seq_cell_free(cell);
421: return err;
422: }
SourceFile:sound/core/seq/seq_memory.c]
|
| 16/2 |
2026/05/20 05:08 |
llm |
expert |
0m
Model:
Tokens:
input: 16720
output: 47
thoughts: 29
Thoughts: Here's a summary of my thought process, as requested:
**Deconstructing a Sequence Cell Allocation**
Okay, so I'm looking at this code and I see `snd_seq_cell_alloc` being called with `mutexp`. That immediately flags something interesting. `mutexp`β¦ right, that's a mutex, probably protecting some shared resource. I need to understand what's happening within this allocation function, and how it interacts with the mutex. Let's dig into `snd_seq_cell_alloc` and see what's going on under the hood. I suspect it's dealing with memory management related to sequencer events and that mutex is probably in place to make sure that these events are properly handled, but I should verify this guess, though, by looking at the function itself.
|
| 17/2 |
2026/05/20 05:08 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_memory.c IncludeLines:true Name:snd_seq_cell_alloc]
Results: map[SourceCode: 264: /*
265: * allocate an event cell.
266: */
267: static int snd_seq_cell_alloc(struct snd_seq_pool *pool,
268: struct snd_seq_event_cell **cellp,
269: int nonblock, struct file *file,
270: struct mutex *mutexp)
271: {
272: struct snd_seq_event_cell *cell;
273: unsigned long flags;
274: int err = -EAGAIN;
275: wait_queue_entry_t wait;
276:
277: if (pool == NULL)
278: return -EINVAL;
279:
280: *cellp = NULL;
281:
282: init_waitqueue_entry(&wait, current);
283: spin_lock_irqsave(&pool->lock, flags);
284: if (pool->ptr == NULL) { /* not initialized */
285: pr_debug("ALSA: seq: pool is not initialized\n");
286: err = -EINVAL;
287: goto __error;
288: }
289: while (pool->free == NULL && ! nonblock && ! pool->closing) {
290:
291: set_current_state(TASK_INTERRUPTIBLE);
292: add_wait_queue(&pool->output_sleep, &wait);
293: spin_unlock_irqrestore(&pool->lock, flags);
294: if (mutexp)
295: mutex_unlock(mutexp);
296: schedule();
297: if (mutexp)
298: mutex_lock(mutexp);
299: spin_lock_irqsave(&pool->lock, flags);
300: remove_wait_queue(&pool->output_sleep, &wait);
301: /* interrupted? */
302: if (signal_pending(current)) {
303: err = -ERESTARTSYS;
304: goto __error;
305: }
306: }
307: if (pool->closing) { /* closing.. */
308: err = -ENOMEM;
309: goto __error;
310: }
311:
312: cell = pool->free;
313: if (cell) {
314: int used;
315: pool->free = cell->next;
316: atomic_inc(&pool->counter);
317: used = atomic_read(&pool->counter);
318: if (pool->max_used < used)
319: pool->max_used = used;
320: pool->event_alloc_success++;
321: /* clear cell pointers */
322: cell->next = NULL;
323: err = 0;
324: } else
325: pool->event_alloc_failures++;
326: *cellp = cell;
327:
328: __error:
329: spin_unlock_irqrestore(&pool->lock, flags);
330: return err;
331: }
SourceFile:sound/core/seq/seq_memory.c]
|
| 18/2 |
2026/05/20 05:08 |
llm |
expert |
0m
Model:
Tokens:
input: 17933
output: 50
thoughts: 1417
Thoughts: **Analyzing a Potential Deadlock in snd_seq_cell_alloc and snd_seq_info_clients_read**
Okay, so I'm looking at this issue where a task seems to be hung, specifically `syz.5.990/9676`, and it's blocked in `snd_seq_info_clients_read` at `mutex_lock`. The initial thought was about `snd_seq_cell_alloc` potentially sleeping and unlocking then locking a mutex, but it doesn't seem directly related. The real hang is in the client information read.
The crucial point is that `syz.5.990/9676` is blocked on `client->ioctl_mutex`. The lockdep output confirms it holds that same mutex *and* is blocked trying to acquire it again. This initially seems impossible β a task can't lock the same mutex twice. However, the code in `snd_seq_info_clients_read` is key: it iterates through clients, and for *each* client acquires `client->ioctl_mutex`. The `guard(mutex)` macro is used, suggesting RAII-like behavior, but it's *within* the loop, meaning the mutex is released at the end of *each* iteration.
This looks like a classic deadlock scenario where the task is trying to reacquire a mutex it *already* holds for the *same* client in the next iteration of the loop. If the task is blocked while holding the mutex, then there is an issue with the lock release. This suggests something else is occurring in the client's scope that could prevent the release.
The crucial question is why the task is blocking on the *same* mutex it holds, inside a loop that should release the mutex at the end of each iteration, meaning `syz.5.990/9676` is blocked on the same mutex it already holds.
The root cause must be within the loop body. The guard macro should have released the lock for the client before re-acquiring for a new client. So, if the task is blocked, then the guard is not working as expected. I need to closely examine the code *within* that loop, especially the scope of `guard(mutex)`. Is there a nested lock, an early return, or some other operation that's preventing the mutex from being properly unlocked before the next iteration attempts to acquire it again?
|
| 19/2 |
2026/05/20 05:08 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:snd_seq_info_clients_read]
Results: map[SourceCode:2587: /* exported to seq_info.c */
2588: void snd_seq_info_clients_read(struct snd_info_entry *entry,
2589: struct snd_info_buffer *buffer)
2590: {
2591: int c;
2592:
2593: snd_iprintf(buffer, "Client info\n");
2594: snd_iprintf(buffer, " cur clients : %d\n", client_usage.cur);
2595: snd_iprintf(buffer, " peak clients : %d\n", client_usage.peak);
2596: snd_iprintf(buffer, " max clients : %d\n", SNDRV_SEQ_MAX_CLIENTS);
2597: snd_iprintf(buffer, "\n");
2598:
2599: /* list the client table */
2600: for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) {
2601: struct snd_seq_client *client __free(snd_seq_client) =
2602: client_load_and_use_ptr(c);
2603:
2604: if (client == NULL)
2605: continue;
2606: if (client->type == NO_CLIENT)
2607: continue;
2608:
2609: guard(mutex)(&client->ioctl_mutex);
2610: snd_iprintf(buffer, "Client %3d : \"%s\" [%s %s]\n",
2611: c, client->name,
2612: client->type == USER_CLIENT ? "User" : "Kernel",
2613: midi_version_string(client->midi_version));
2614: #if IS_ENABLED(CONFIG_SND_SEQ_UMP)
2615: dump_ump_info(buffer, client);
2616: #endif
2617: snd_seq_info_dump_ports(buffer, client);
2618: if (snd_seq_write_pool_allocated(client)) {
2619: snd_iprintf(buffer, " Output pool :\n");
2620: snd_seq_info_pool(buffer, client->pool, " ");
2621: }
2622: if (client->type == USER_CLIENT && client->data.user.fifo &&
2623: client->data.user.fifo->pool) {
2624: snd_iprintf(buffer, " Input pool :\n");
2625: snd_seq_info_pool(buffer, client->data.user.fifo->pool, " ");
2626: }
2627: }
2628: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 20/2 |
2026/05/20 05:08 |
llm |
expert |
0m
Model:
Tokens:
input: 20700
output: 49
thoughts: 123
Thoughts: **Analyzing Potential Mutex Lock Issues**
Okay, so I'm looking at this code, specifically around line 2609, and I see `guard(mutex)(&client->ioctl_mutex);`. My initial thought is, "Is there any possibility that `client->ioctl_mutex` is being locked more than once?" I mean, that's a classic source of headaches in multi-threaded environments. I need to make sure this is being handled correctly.
The `guard` function is supposed to handle the locking and unlocking. I can see it locks the mutex at the start, and unlocks it at the end of the loop iteration. So far, so good. But that's just in this immediate context. What I need to consider are any *other* functions called within this loop, especially if they might try to acquire the same mutex.
Specifically, I'm thinking about `snd_seq_info_dump_ports`, `snd_seq_info_pool`, and `dump_ump_info`. Any of those could potentially be problematic. I think the best starting point is `snd_seq_info_dump_ports`. I'm going to have to check that one.
|
| 21/2 |
2026/05/20 05:08 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_ports.c IncludeLines:true Name:snd_seq_info_dump_ports]
Results: map[SourceCode:2547: static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer,
2548: struct snd_seq_client *client)
2549: {
2550: struct snd_seq_client_port *p;
2551:
2552: guard(mutex)(&client->ports_mutex);
2553: list_for_each_entry(p, &client->ports_list_head, list) {
2554: if (p->capability & SNDRV_SEQ_PORT_CAP_INACTIVE)
2555: continue;
2556: snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c) [%s]",
2557: p->addr.port, p->name,
2558: FLAG_PERM_RD(p->capability),
2559: FLAG_PERM_WR(p->capability),
2560: FLAG_PERM_EX(p->capability),
2561: FLAG_PERM_DUPLEX(p->capability),
2562: port_direction_name(p->direction));
2563: #if IS_ENABLED(CONFIG_SND_SEQ_UMP)
2564: if (snd_seq_client_is_midi2(client) && p->is_midi1)
2565: snd_iprintf(buffer, " [MIDI1]");
2566: #endif
2567: snd_iprintf(buffer, "\n");
2568: snd_seq_info_dump_subscribers(buffer, &p->c_src, 1, " Connecting To: ");
2569: snd_seq_info_dump_subscribers(buffer, &p->c_dest, 0, " Connected From: ");
2570: }
2571: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 22/2 |
2026/05/20 05:08 |
llm |
expert |
0m
Model:
Tokens:
input: 21653
output: 47
thoughts: 181
Thoughts: **Debugging a Mismatched File Location**
Okay, so I'm digging into this `snd_seq_info_dump_ports` function, and my initial thought was, "Wait, this is in `sound/core/seq/seq_ports.c`, right?" The tool's response is pointing me to `sound/core/seq/seq_clientmgr.c`, which feels off. I'm pretty sure it's in `seq_ports.c`. Okay, the response **does** say `SourceFile: sound/core/seq/seq_clientmgr.c`, which makes me question myself. But now I'm back to thinking it's in `seq_ports.c`. I need to confirm.
Let's check `snd_seq_info_dump_ports` in `sound/core/seq/seq_ports.c`.
Now, while I'm here, I need to keep in mind that `snd_seq_info_dump_ports` locks `client->ports_mutex`. Good to know; I need to remember that. And, I wonder, what about `snd_seq_info_pool`? I should probably look into that too, as long as I'm in this region of the code.
|
| 23/2 |
2026/05/20 05:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_memory.c IncludeLines:true Name:snd_seq_info_pool]
Results: map[SourceCode: 551: /* exported to seq_clientmgr.c */
552: void snd_seq_info_pool(struct snd_info_buffer *buffer,
553: struct snd_seq_pool *pool, char *space)
554: {
555: if (pool == NULL)
556: return;
557: snd_iprintf(buffer, "%sPool size : %d\n", space, pool->total_elements);
558: snd_iprintf(buffer, "%sCells in use : %d\n", space, atomic_read(&pool->counter));
559: snd_iprintf(buffer, "%sPeak cells in use : %d\n", space, pool->max_used);
560: snd_iprintf(buffer, "%sAlloc success : %d\n", space, pool->event_alloc_success);
561: snd_iprintf(buffer, "%sAlloc failures : %d\n", space, pool->event_alloc_failures);
562: }
SourceFile:sound/core/seq/seq_memory.c]
|
| 24/2 |
2026/05/20 05:09 |
llm |
expert |
0m
Model:
Tokens:
input: 22466
output: 46
thoughts: 15
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Current Assessment: Security & Debugging**
Alright, so I'm focusing on security vulnerabilities and potential debugging strategies. My initial thought is that there are no obvious locks in this area β thatβs good, but it's just the starting point, not an indication of overall security. This raises some questions about concurrency and how to ensure data integrity without explicit locking.
Now, my mind shifts to a specific function I need to look into - `dump_ump_info`. I'm immediately trying to assess its potential impact and what data it accesses. This function feels like a critical point of inspection because of the access it may grant. It could potentially expose sensitive information. My investigation must now center on this.
|
| 25/2 |
2026/05/20 05:09 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:dump_ump_info]
Results: map[SourceCode: SourceFile:]
|
| 26/2 |
2026/05/20 05:09 |
llm |
expert |
0m
Model:
Tokens:
input: 22714
output: 19
thoughts: 27
Thoughts: Okay, here's my summary, taking on the persona you've requested, targeting an expert audience:
**Investigating `dump_ump_info` in the ALSA Sequencer Client Manager**
Alright, let's dive into this. I need to get the low-down on the `dump_ump_info` function and its context within the ALSA sequencer client management code. My immediate thought is to zero in on `sound/core/seq/seq_clientmgr.c`. That's where I'd expect to find the core logic related to client management, and by extension, functions involved in debugging or informational dumps. I'm assuming 'ump' likely refers to Universal MIDI Packets, so a dump of this data is of immediate interest. This suggests to me, we're likely dealing with some level of debugging output, which I suspect it has something to do with monitoring the UMP data associated with a specific client.
|
| 27/2 |
2026/05/20 05:09 |
tool |
grepper |
0m
Args: map[Expression:dump_ump_info]
Results: map[Output:sound/core/seq/seq_clientmgr.c=2027=static void terminate_ump_info_strings(void *p, int type)
--
sound/core/seq/seq_clientmgr.c-2038-#ifdef CONFIG_SND_PROC_FS
sound/core/seq/seq_clientmgr.c:2039:static void dump_ump_info(struct snd_info_buffer *buffer,
sound/core/seq/seq_clientmgr.c-2040- struct snd_seq_client *client)
--
sound/core/seq/seq_clientmgr.c=2588=void snd_seq_info_clients_read(struct snd_info_entry *entry,
--
sound/core/seq/seq_clientmgr.c-2614-#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
sound/core/seq/seq_clientmgr.c:2615: dump_ump_info(buffer, client);
sound/core/seq/seq_clientmgr.c-2616-#endif
]
|
| 28/2 |
2026/05/20 05:09 |
llm |
expert |
0m
Model:
Tokens:
input: 23229
output: 46
thoughts: 11
Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own:
**Analyzing the Call to `dump_ump_info`**
Alright, time to get to it. My initial thought is to read that `dump_ump_info` function. No real preamble, just straight to the source. I'm expecting this to be a core piece of information, probably related to memory management, perhaps kernel internals. I'm hoping it's well-documented, because, frankly, understanding the Universal Memory Pool (UMP) is critical for performance and debugging in this system. I'm anticipating insights into how UMP is structured, managed, or accessed. Time to get my hands dirty.
|
| 29/2 |
2026/05/20 05:09 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:dump_ump_info]
Results: map[SourceCode: SourceFile:]
|
| 30/2 |
2026/05/20 05:09 |
llm |
expert |
0m
Model:
Tokens:
input: 23463
output: 29
thoughts: 37
Thoughts: Okay, here we go.
**Navigating the Static and Tracking Down the Source**
Ugh, that's just static. Annoying, but not unexpected. Time to get down to brass tacks. Let me just zero in on the suspected area... okay, let's pull up lines 2039-2060 of `sound/core/seq/seq_clientmgr.c`. That should give me the relevant context. I need to understand what's happening at that level to address this specific kind of problem and I think that's where I need to be.
|
| 31/2 |
2026/05/20 05:09 |
tool |
grepper |
0m
Args: map[Expression:dump_ump_info\(struct snd_info_buffer \*buffer,]
Results: map[Output:sound/core/seq/seq_clientmgr.c=2027=static void terminate_ump_info_strings(void *p, int type)
--
sound/core/seq/seq_clientmgr.c-2038-#ifdef CONFIG_SND_PROC_FS
sound/core/seq/seq_clientmgr.c:2039:static void dump_ump_info(struct snd_info_buffer *buffer,
sound/core/seq/seq_clientmgr.c-2040- struct snd_seq_client *client)
]
|
| 32/2 |
2026/05/20 05:09 |
llm |
expert |
0m
Model:
Tokens:
input: 23799
output: 27
thoughts: 33
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deep in the weeds of debugging and understanding the ALSA sequencer code:
**Diving into the UMP Details**
Right, let's get down to brass tacks. I need to understand how UMP (Universal MIDI Packets) data is handled, specifically within the sequencer's client manager. The best place to start is likely with `read-file` on `sound/core/seq/seq_clientmgr.c`. I expect this file will be central to how clients interact with the sequencer. My gut tells me that `dump_ump_info` will likely be the crucial function within that file. It is most likely involved in actually revealing and logging the packet details, so I need to extract this function. This is where the core information on the actual UMP data, format, and structure is probably going to be. Time to dig in and see what's what.
|
| 33/2 |
2026/05/20 05:09 |
tool |
read-file |
0m
Args: map[File:sound/core/seq/seq_clientmgr.c]
Results: map[Contents:// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer Client Manager
* Copyright (c) 1998-2001 by Frank van de Pol <fvdpol@coil.demon.nl>
* Jaroslav Kysela <perex@perex.cz>
* Takashi Iwai <tiwai@suse.de>
*/
#include <linux/init.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <linux/kmod.h>
#include <sound/seq_kernel.h>
#include <sound/ump.h>
#include "seq_clientmgr.h"
#include "seq_memory.h"
#include "seq_queue.h"
#include "seq_timer.h"
#include "seq_info.h"
#include "seq_system.h"
#include "seq_ump_convert.h"
#include <sound/seq_device.h>
#ifdef CONFIG_COMPAT
#include <linux/compat.h>
#endif
/* Client Manager
* this module handles the connections of userland and kernel clients
*
*/
/*
* There are four ranges of client numbers (last two shared):
* 0..15: global clients
* 16..127: statically allocated client numbers for cards 0..27
* 128..191: dynamically allocated client numbers for cards 28..31
* 128..191: dynamically allocated client numbers for applications
*/
/* number of kernel non-card clients */
#define SNDRV_SEQ_GLOBAL_CLIENTS 16
/* clients per cards, for static clients */
#define SNDRV_SEQ_CLIENTS_PER_CARD 4
/* dynamically allocated client numbers (both kernel drivers and user space) */
#define SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN 128
#define SNDRV_SEQ_LFLG_INPUT 0x0001
#define SNDRV_SEQ_LFLG_OUTPUT 0x0002
#define SNDRV_SEQ_LFLG_OPEN (SNDRV_SEQ_LFLG_INPUT|SNDRV_SEQ_LFLG_OUTPUT)
static DEFINE_SPINLOCK(clients_lock);
static DEFINE_MUTEX(register_mutex);
/*
* client table
*/
static char clienttablock[SNDRV_SEQ_MAX_CLIENTS];
static struct snd_seq_client *clienttab[SNDRV_SEQ_MAX_CLIENTS];
static struct snd_seq_usage client_usage;
/*
* prototypes
*/
static int bounce_error_event(struct snd_seq_client *client,
struct snd_seq_event *event,
int err, int atomic, int hop);
static int snd_seq_deliver_single_event(struct snd_seq_client *client,
struct snd_seq_event *event,
int atomic, int hop);
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
static void free_ump_info(struct snd_seq_client *client);
#endif
/*
*/
static inline unsigned short snd_seq_file_flags(struct file *file)
{
switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) {
case FMODE_WRITE:
return SNDRV_SEQ_LFLG_OUTPUT;
case FMODE_READ:
return SNDRV_SEQ_LFLG_INPUT;
default:
return SNDRV_SEQ_LFLG_OPEN;
}
}
static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client)
{
return snd_seq_total_cells(client->pool) > 0;
}
/* return pointer to client structure for specified id */
static struct snd_seq_client *clientptr(int clientid)
{
if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) {
pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n",
clientid);
return NULL;
}
return clienttab[clientid];
}
static struct snd_seq_client *client_use_ptr(int clientid, bool load_module)
{
struct snd_seq_client *client;
if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) {
pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n",
clientid);
return NULL;
}
scoped_guard(spinlock_irqsave, &clients_lock) {
client = clientptr(clientid);
if (client)
return snd_seq_client_ref(client);
if (clienttablock[clientid])
return NULL;
}
#ifdef CONFIG_MODULES
if (load_module) {
static DECLARE_BITMAP(client_requested, SNDRV_SEQ_GLOBAL_CLIENTS);
static DECLARE_BITMAP(card_requested, SNDRV_CARDS);
if (clientid < SNDRV_SEQ_GLOBAL_CLIENTS) {
int idx;
if (!test_and_set_bit(clientid, client_requested)) {
for (idx = 0; idx < 15; idx++) {
if (seq_client_load[idx] < 0)
break;
if (seq_client_load[idx] == clientid) {
request_module("snd-seq-client-%i",
clientid);
break;
}
}
}
} else if (clientid < SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN) {
int card = (clientid - SNDRV_SEQ_GLOBAL_CLIENTS) /
SNDRV_SEQ_CLIENTS_PER_CARD;
if (card < snd_ecards_limit) {
if (!test_and_set_bit(card, card_requested))
snd_request_card(card);
snd_seq_device_load_drivers();
}
}
scoped_guard(spinlock_irqsave, &clients_lock) {
client = clientptr(clientid);
if (client)
return snd_seq_client_ref(client);
}
}
#endif
return NULL;
}
/* get snd_seq_client object for the given id quickly */
struct snd_seq_client *snd_seq_client_use_ptr(int clientid)
{
return client_use_ptr(clientid, false);
}
/* get snd_seq_client object for the given id;
* if not found, retry after loading the modules
*/
static struct snd_seq_client *client_load_and_use_ptr(int clientid)
{
return client_use_ptr(clientid, IS_ENABLED(CONFIG_MODULES));
}
static void usage_alloc(struct snd_seq_usage *res, int num)
{
res->cur += num;
if (res->cur > res->peak)
res->peak = res->cur;
}
static void usage_free(struct snd_seq_usage *res, int num)
{
res->cur -= num;
}
/* initialise data structures */
int __init client_init_data(void)
{
/* zap out the client table */
memset(&clienttablock, 0, sizeof(clienttablock));
memset(&clienttab, 0, sizeof(clienttab));
return 0;
}
static struct snd_seq_client *seq_create_client1(int client_index, int poolsize)
{
int c;
struct snd_seq_client *client;
/* init client data */
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (client == NULL)
return NULL;
client->pool = snd_seq_pool_new(poolsize);
if (client->pool == NULL) {
kfree(client);
return NULL;
}
client->type = NO_CLIENT;
snd_use_lock_init(&client->use_lock);
rwlock_init(&client->ports_lock);
mutex_init(&client->ports_mutex);
INIT_LIST_HEAD(&client->ports_list_head);
mutex_init(&client->ioctl_mutex);
client->ump_endpoint_port = -1;
/* find free slot in the client table */
scoped_guard(spinlock_irq, &clients_lock) {
if (client_index < 0) {
for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN;
c < SNDRV_SEQ_MAX_CLIENTS;
c++) {
if (clienttab[c] || clienttablock[c])
continue;
clienttab[client->number = c] = client;
return client;
}
} else {
if (clienttab[client_index] == NULL && !clienttablock[client_index]) {
clienttab[client->number = client_index] = client;
return client;
}
}
}
snd_seq_pool_delete(&client->pool);
kfree(client);
return NULL; /* no free slot found or busy, return failure code */
}
static int seq_free_client1(struct snd_seq_client *client)
{
if (!client)
return 0;
scoped_guard(spinlock_irq, &clients_lock) {
clienttablock[client->number] = 1;
clienttab[client->number] = NULL;
}
snd_seq_delete_all_ports(client);
snd_seq_queue_client_leave(client->number);
snd_use_lock_sync(&client->use_lock);
if (client->pool)
snd_seq_pool_delete(&client->pool);
scoped_guard(spinlock_irq, &clients_lock) {
clienttablock[client->number] = 0;
}
return 0;
}
static void seq_free_client(struct snd_seq_client * client)
{
scoped_guard(mutex, ®ister_mutex) {
switch (client->type) {
case NO_CLIENT:
pr_warn("ALSA: seq: Trying to free unused client %d\n",
client->number);
break;
case USER_CLIENT:
case KERNEL_CLIENT:
seq_free_client1(client);
usage_free(&client_usage, 1);
break;
default:
pr_err("ALSA: seq: Trying to free client %d with undefined type = %d\n",
client->number, client->type);
}
}
snd_seq_system_client_ev_client_exit(client->number);
}
/* -------------------------------------------------------- */
/* create a user client */
static int snd_seq_open(struct inode *inode, struct file *file)
{
int c, mode; /* client id */
struct snd_seq_client *client;
struct snd_seq_user_client *user;
int err;
err = stream_open(inode, file);
if (err < 0)
return err;
scoped_guard(mutex, ®ister_mutex) {
client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS);
if (!client)
return -ENOMEM; /* failure code */
mode = snd_seq_file_flags(file);
if (mode & SNDRV_SEQ_LFLG_INPUT)
client->accept_input = 1;
if (mode & SNDRV_SEQ_LFLG_OUTPUT)
client->accept_output = 1;
user = &client->data.user;
user->fifo = NULL;
user->fifo_pool_size = 0;
if (mode & SNDRV_SEQ_LFLG_INPUT) {
user->fifo_pool_size = SNDRV_SEQ_DEFAULT_CLIENT_EVENTS;
user->fifo = snd_seq_fifo_new(user->fifo_pool_size);
if (user->fifo == NULL) {
seq_free_client1(client);
kfree(client);
return -ENOMEM;
}
}
usage_alloc(&client_usage, 1);
client->type = USER_CLIENT;
}
c = client->number;
file->private_data = client;
/* fill client data */
user->file = file;
sprintf(client->name, "Client-%d", c);
client->data.user.owner = get_pid(task_pid(current));
/* make others aware this new client */
snd_seq_system_client_ev_client_start(c);
return 0;
}
/* delete a user client */
static int snd_seq_release(struct inode *inode, struct file *file)
{
struct snd_seq_client *client = file->private_data;
if (client) {
seq_free_client(client);
if (client->data.user.fifo)
snd_seq_fifo_delete(&client->data.user.fifo);
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
free_ump_info(client);
#endif
put_pid(client->data.user.owner);
kfree(client);
}
return 0;
}
static bool event_is_compatible(const struct snd_seq_client *client,
const struct snd_seq_event *ev)
{
if (snd_seq_ev_is_ump(ev) && !client->midi_version)
return false;
if (snd_seq_ev_is_ump(ev) && snd_seq_ev_is_variable(ev))
return false;
return true;
}
/* handle client read() */
/* possible error values:
* -ENXIO invalid client or file open mode
* -ENOSPC FIFO overflow (the flag is cleared after this error report)
* -EINVAL no enough user-space buffer to write the whole event
* -EFAULT seg. fault during copy to user space
*/
static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count,
loff_t *offset)
{
struct snd_seq_client *client = file->private_data;
struct snd_seq_fifo *fifo;
size_t aligned_size;
int err;
long result = 0;
struct snd_seq_event_cell *cell;
if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT))
return -ENXIO;
if (!access_ok(buf, count))
return -EFAULT;
/* check client structures are in place */
if (snd_BUG_ON(!client))
return -ENXIO;
if (!client->accept_input)
return -ENXIO;
fifo = client->data.user.fifo;
if (!fifo)
return -ENXIO;
if (atomic_read(&fifo->overflow) > 0) {
/* buffer overflow is detected */
snd_seq_fifo_clear(fifo);
/* return error code */
return -ENOSPC;
}
cell = NULL;
err = 0;
guard(snd_seq_fifo)(fifo);
if (IS_ENABLED(CONFIG_SND_SEQ_UMP) && client->midi_version > 0)
aligned_size = sizeof(struct snd_seq_ump_event);
else
aligned_size = sizeof(struct snd_seq_event);
/* while data available in queue */
while (count >= aligned_size) {
int nonblock;
nonblock = (file->f_flags & O_NONBLOCK) || result > 0;
err = snd_seq_fifo_cell_out(fifo, &cell, nonblock);
if (err < 0)
break;
if (!event_is_compatible(client, &cell->event)) {
snd_seq_cell_free(cell);
cell = NULL;
continue;
}
if (snd_seq_ev_is_variable(&cell->event)) {
struct snd_seq_ump_event tmpev;
memcpy(&tmpev, &cell->event, aligned_size);
tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK;
if (copy_to_user(buf, &tmpev, aligned_size)) {
err = -EFAULT;
break;
}
count -= aligned_size;
buf += aligned_size;
err = snd_seq_expand_var_event(&cell->event, count,
(char __force *)buf, 0,
aligned_size);
if (err < 0)
break;
result += err;
count -= err;
buf += err;
} else {
if (copy_to_user(buf, &cell->event, aligned_size)) {
err = -EFAULT;
break;
}
count -= aligned_size;
buf += aligned_size;
}
snd_seq_cell_free(cell);
cell = NULL; /* to be sure */
result += aligned_size;
}
if (err < 0) {
if (cell)
snd_seq_fifo_cell_putback(fifo, cell);
if (err == -EAGAIN && result > 0)
err = 0;
}
return (err < 0) ? err : result;
}
/*
* check access permission to the port
*/
static int check_port_perm(struct snd_seq_client_port *port, unsigned int flags)
{
if ((port->capability & flags) != flags)
return 0;
return flags;
}
/*
* check if the destination client is available, and return the pointer
*/
static struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event)
{
struct snd_seq_client *dest __free(snd_seq_client) =
snd_seq_client_use_ptr(event->dest.client);
if (dest == NULL)
return NULL;
if (! dest->accept_input)
return NULL;
if (snd_seq_ev_is_ump(event))
return no_free_ptr(dest); /* ok - no filter checks */
if ((dest->filter & SNDRV_SEQ_FILTER_USE_EVENT) &&
! test_bit(event->type, dest->event_filter))
return NULL;
return no_free_ptr(dest); /* ok - accessible */
}
/*
* Return the error event.
*
* If the receiver client is a user client, the original event is
* encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. If
* the original event is also variable length, the external data is
* copied after the event record.
* If the receiver client is a kernel client, the original event is
* quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra
* kmalloc.
*/
static int bounce_error_event(struct snd_seq_client *client,
struct snd_seq_event *event,
int err, int atomic, int hop)
{
struct snd_seq_event bounce_ev;
int result;
if (client == NULL ||
! (client->filter & SNDRV_SEQ_FILTER_BOUNCE) ||
! client->accept_input)
return 0; /* ignored */
/* set up quoted error */
memset(&bounce_ev, 0, sizeof(bounce_ev));
bounce_ev.type = SNDRV_SEQ_EVENT_KERNEL_ERROR;
bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
bounce_ev.queue = SNDRV_SEQ_QUEUE_DIRECT;
bounce_ev.source.client = SNDRV_SEQ_CLIENT_SYSTEM;
bounce_ev.source.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
bounce_ev.dest.client = client->number;
bounce_ev.dest.port = event->source.port;
bounce_ev.data.quote.origin = event->dest;
bounce_ev.data.quote.event = event;
bounce_ev.data.quote.value = -err; /* use positive value */
result = snd_seq_deliver_single_event(NULL, &bounce_ev, atomic, hop + 1);
if (result < 0) {
client->event_lost++;
return result;
}
return result;
}
/*
* rewrite the time-stamp of the event record with the curren time
* of the given queue.
* return non-zero if updated.
*/
static int update_timestamp_of_queue(struct snd_seq_event *event,
int queue, int real_time)
{
struct snd_seq_queue *q __free(snd_seq_queue) =
queueptr(queue);
if (! q)
return 0;
event->queue = queue;
event->flags &= ~SNDRV_SEQ_TIME_STAMP_MASK;
if (real_time) {
event->time.time = snd_seq_timer_get_cur_time(q->timer, true);
event->flags |= SNDRV_SEQ_TIME_STAMP_REAL;
} else {
event->time.tick = snd_seq_timer_get_cur_tick(q->timer);
event->flags |= SNDRV_SEQ_TIME_STAMP_TICK;
}
return 1;
}
/* deliver a single event; called from below and UMP converter */
int __snd_seq_deliver_single_event(struct snd_seq_client *dest,
struct snd_seq_client_port *dest_port,
struct snd_seq_event *event,
int atomic, int hop)
{
switch (dest->type) {
case USER_CLIENT:
if (!dest->data.user.fifo)
return 0;
return snd_seq_fifo_event_in(dest->data.user.fifo, event);
case KERNEL_CLIENT:
if (!dest_port->event_input)
return 0;
return dest_port->event_input(event,
snd_seq_ev_is_direct(event),
dest_port->private_data,
atomic, hop);
}
return 0;
}
/* deliver a single event; called from snd_seq_deliver_single_event() */
static int _snd_seq_deliver_single_event(struct snd_seq_client *client,
struct snd_seq_event *event,
int atomic, int hop)
{
struct snd_seq_client *dest __free(snd_seq_client) =
get_event_dest_client(event);
if (dest == NULL)
return -ENOENT;
struct snd_seq_client_port *dest_port __free(snd_seq_port) =
snd_seq_port_use_ptr(dest, event->dest.port);
if (dest_port == NULL)
return -ENOENT;
/* check permission */
if (!check_port_perm(dest_port, SNDRV_SEQ_PORT_CAP_WRITE))
return -EPERM;
if (dest_port->timestamping)
update_timestamp_of_queue(event, dest_port->time_queue,
dest_port->time_real);
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
if (snd_seq_ev_is_ump(event)) {
if (!(dest->filter & SNDRV_SEQ_FILTER_NO_CONVERT))
return snd_seq_deliver_from_ump(client, dest, dest_port,
event, atomic, hop);
else if (dest->type == USER_CLIENT &&
!snd_seq_client_is_ump(dest))
return 0; // drop the event
} else if (snd_seq_client_is_ump(dest)) {
if (!(dest->filter & SNDRV_SEQ_FILTER_NO_CONVERT))
return snd_seq_deliver_to_ump(client, dest, dest_port,
event, atomic, hop);
}
#endif /* CONFIG_SND_SEQ_UMP */
return __snd_seq_deliver_single_event(dest, dest_port, event,
atomic, hop);
}
/*
* deliver an event to the specified destination.
* if filter is non-zero, client filter bitmap is tested.
*
* RETURN VALUE: 0 : if succeeded
* <0 : error
*/
static int snd_seq_deliver_single_event(struct snd_seq_client *client,
struct snd_seq_event *event,
int atomic, int hop)
{
int result = _snd_seq_deliver_single_event(client, event, atomic, hop);
if (result < 0 && !snd_seq_ev_is_direct(event))
return bounce_error_event(client, event, result, atomic, hop);
return result;
}
/*
* send the event to all subscribers:
*/
static int __deliver_to_subscribers(struct snd_seq_client *client,
struct snd_seq_event *event,
int port, int atomic, int hop)
{
struct snd_seq_subscribers *subs;
int err, result = 0, num_ev = 0;
union __snd_seq_event event_saved;
size_t saved_size;
struct snd_seq_port_subs_info *grp;
if (port < 0)
return 0;
struct snd_seq_client_port *src_port __free(snd_seq_port) =
snd_seq_port_use_ptr(client, port);
if (!src_port)
return 0;
/* save original event record */
saved_size = snd_seq_event_packet_size(event);
memcpy(&event_saved, event, saved_size);
grp = &src_port->c_src;
/* lock list */
if (atomic)
read_lock(&grp->list_lock);
else
down_read_nested(&grp->list_mutex, hop);
list_for_each_entry(subs, &grp->list_head, src_list) {
/* both ports ready? */
if (atomic_read(&subs->ref_count) != 2)
continue;
event->dest = subs->info.dest;
if (subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP)
/* convert time according to flag with subscription */
update_timestamp_of_queue(event, subs->info.queue,
subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL);
err = snd_seq_deliver_single_event(client, event, atomic, hop);
if (err < 0) {
/* save first error that occurs and continue */
if (!result)
result = err;
continue;
}
num_ev++;
/* restore original event record */
memcpy(event, &event_saved, saved_size);
}
if (atomic)
read_unlock(&grp->list_lock);
else
up_read(&grp->list_mutex);
memcpy(event, &event_saved, saved_size);
return (result < 0) ? result : num_ev;
}
static int deliver_to_subscribers(struct snd_seq_client *client,
struct snd_seq_event *event,
int atomic, int hop)
{
int ret;
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
int ret2;
#endif
ret = __deliver_to_subscribers(client, event,
event->source.port, atomic, hop);
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
if (!snd_seq_client_is_ump(client) || client->ump_endpoint_port < 0)
return ret;
/* If it's an event from EP port (and with a UMP group),
* deliver to subscribers of the corresponding UMP group port, too.
* Or, if it's from non-EP port, deliver to subscribers of EP port, too.
*/
if (event->source.port == client->ump_endpoint_port)
ret2 = __deliver_to_subscribers(client, event,
snd_seq_ump_group_port(event),
atomic, hop);
else
ret2 = __deliver_to_subscribers(client, event,
client->ump_endpoint_port,
atomic, hop);
if (ret2 < 0)
return ret2;
#endif
return ret;
}
/* deliver an event to the destination port(s).
* if the event is to subscribers or broadcast, the event is dispatched
* to multiple targets.
*
* RETURN VALUE: n > 0 : the number of delivered events.
* n == 0 : the event was not passed to any client.
* n < 0 : error - event was not processed.
*/
static int snd_seq_deliver_event(struct snd_seq_client *client, struct snd_seq_event *event,
int atomic, int hop)
{
int result;
hop++;
if (hop >= SNDRV_SEQ_MAX_HOPS) {
pr_debug("ALSA: seq: too long delivery path (%d:%d->%d:%d)\n",
event->source.client, event->source.port,
event->dest.client, event->dest.port);
return -EMLINK;
}
if (snd_seq_ev_is_variable(event) &&
snd_BUG_ON(atomic && (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR)))
return -EINVAL;
if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS ||
event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS)
result = deliver_to_subscribers(client, event, atomic, hop);
else
result = snd_seq_deliver_single_event(client, event, atomic, hop);
return result;
}
/*
* dispatch an event cell:
* This function is called only from queue check routines in timer
* interrupts or after enqueued.
* The event cell shall be released or re-queued in this function.
*
* RETURN VALUE: n > 0 : the number of delivered events.
* n == 0 : the event was not passed to any client.
* n < 0 : error - event was not processed.
*/
int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop)
{
int result;
if (snd_BUG_ON(!cell))
return -EINVAL;
struct snd_seq_client *client __free(snd_seq_client) =
snd_seq_client_use_ptr(cell->event.source.client);
if (client == NULL) {
snd_seq_cell_free(cell); /* release this cell */
return -EINVAL;
}
if (!snd_seq_ev_is_ump(&cell->event) &&
cell->event.type == SNDRV_SEQ_EVENT_NOTE) {
/* NOTE event:
* the event cell is re-used as a NOTE-OFF event and
* enqueued again.
*/
struct snd_seq_event tmpev, *ev;
/* reserve this event to enqueue note-off later */
tmpev = cell->event;
tmpev.type = SNDRV_SEQ_EVENT_NOTEON;
result = snd_seq_deliver_event(client, &tmpev, atomic, hop);
/*
* This was originally a note event. We now re-use the
* cell for the note-off event.
*/
ev = &cell->event;
ev->type = SNDRV_SEQ_EVENT_NOTEOFF;
ev->flags |= SNDRV_SEQ_PRIORITY_HIGH;
/* add the duration time */
switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) {
case SNDRV_SEQ_TIME_STAMP_TICK:
cell->event.time.tick += ev->data.note.duration;
break;
case SNDRV_SEQ_TIME_STAMP_REAL:
/* unit for duration is ms */
ev->time.time.tv_nsec += 1000000 * (ev->data.note.duration % 1000);
ev->time.time.tv_sec += ev->data.note.duration / 1000 +
ev->time.time.tv_nsec / 1000000000;
ev->time.time.tv_nsec %= 1000000000;
break;
}
ev->data.note.velocity = ev->data.note.off_velocity;
/* Now queue this cell as the note off event */
if (snd_seq_enqueue_event(cell, atomic, hop) < 0)
snd_seq_cell_free(cell); /* release this cell */
} else {
/* Normal events:
* event cell is freed after processing the event
*/
result = snd_seq_deliver_event(client, &cell->event, atomic, hop);
snd_seq_cell_free(cell);
}
return result;
}
/* Allocate a cell from client pool and enqueue it to queue:
* if pool is empty and blocking is TRUE, sleep until a new cell is
* available.
*/
static int snd_seq_client_enqueue_event(struct snd_seq_client *client,
struct snd_seq_event *event,
struct file *file, int blocking,
int atomic, int hop,
struct mutex *mutexp)
{
struct snd_seq_event_cell *cell;
int err;
/* special queue values - force direct passing */
if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
event->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
event->queue = SNDRV_SEQ_QUEUE_DIRECT;
} else if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
/* check presence of source port */
struct snd_seq_client_port *src_port __free(snd_seq_port) =
snd_seq_port_use_ptr(client, event->source.port);
if (!src_port)
return -EINVAL;
}
/* direct event processing without enqueued */
if (snd_seq_ev_is_direct(event)) {
if (!snd_seq_ev_is_ump(event) &&
event->type == SNDRV_SEQ_EVENT_NOTE)
return -EINVAL; /* this event must be enqueued! */
return snd_seq_deliver_event(client, event, atomic, hop);
}
/* Not direct, normal queuing */
if (snd_seq_queue_is_used(event->queue, client->number) <= 0)
return -EINVAL; /* invalid queue */
if (! snd_seq_write_pool_allocated(client))
return -ENXIO; /* queue is not allocated */
/* allocate an event cell */
err = snd_seq_event_dup(client->pool, event, &cell, !blocking || atomic,
file, mutexp);
if (err < 0)
return err;
/* we got a cell. enqueue it. */
err = snd_seq_enqueue_event(cell, atomic, hop);
if (err < 0) {
snd_seq_cell_free(cell);
return err;
}
return 0;
}
/*
* check validity of event type and data length.
* return non-zero if invalid.
*/
static int check_event_type_and_length(struct snd_seq_event *ev)
{
switch (snd_seq_ev_length_type(ev)) {
case SNDRV_SEQ_EVENT_LENGTH_FIXED:
if (snd_seq_ev_is_variable_type(ev))
return -EINVAL;
break;
case SNDRV_SEQ_EVENT_LENGTH_VARIABLE:
if (! snd_seq_ev_is_variable_type(ev) ||
(ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK) >= SNDRV_SEQ_MAX_EVENT_LEN)
return -EINVAL;
break;
case SNDRV_SEQ_EVENT_LENGTH_VARUSR:
if (! snd_seq_ev_is_direct(ev))
return -EINVAL;
break;
}
return 0;
}
/* handle write() */
/* possible error values:
* -ENXIO invalid client or file open mode
* -ENOMEM malloc failed
* -EFAULT seg. fault during copy from user space
* -EINVAL invalid event
* -EAGAIN no space in output pool
* -EINTR interrupts while sleep
* -EMLINK too many hops
* others depends on return value from driver callback
*/
static ssize_t snd_seq_write(struct file *file, const char __user *buf,
size_t count, loff_t *offset)
{
struct snd_seq_client *client = file->private_data;
int written = 0, len;
int err, handled;
union __snd_seq_event __event;
struct snd_seq_event *ev = &__event.legacy;
if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT))
return -ENXIO;
/* check client structures are in place */
if (snd_BUG_ON(!client))
return -ENXIO;
if (!client->accept_output || client->pool == NULL)
return -ENXIO;
repeat:
handled = 0;
/* allocate the pool now if the pool is not allocated yet */
mutex_lock(&client->ioctl_mutex);
if (client->pool->size > 0 && !snd_seq_write_pool_allocated(client)) {
err = snd_seq_pool_init(client->pool);
if (err < 0)
goto out;
}
/* only process whole events */
err = -EINVAL;
while (count >= sizeof(struct snd_seq_event)) {
/* Read in the event header from the user */
len = sizeof(struct snd_seq_event);
if (copy_from_user(ev, buf, len)) {
err = -EFAULT;
break;
}
/* read in the rest bytes for UMP events */
if (snd_seq_ev_is_ump(ev)) {
if (count < sizeof(struct snd_seq_ump_event))
break;
if (copy_from_user((char *)ev + len, buf + len,
sizeof(struct snd_seq_ump_event) - len)) {
err = -EFAULT;
break;
}
len = sizeof(struct snd_seq_ump_event);
}
ev->source.client = client->number; /* fill in client number */
/* Check for extension data length */
if (check_event_type_and_length(ev)) {
err = -EINVAL;
break;
}
if (!event_is_compatible(client, ev)) {
err = -EINVAL;
break;
}
/* check for special events */
if (!snd_seq_ev_is_ump(ev)) {
if (ev->type == SNDRV_SEQ_EVENT_NONE)
goto __skip_event;
else if (snd_seq_ev_is_reserved(ev)) {
err = -EINVAL;
break;
}
}
if (snd_seq_ev_is_variable(ev)) {
int extlen = ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
if ((size_t)(extlen + len) > count) {
/* back out, will get an error this time or next */
err = -EINVAL;
break;
}
/* set user space pointer */
ev->data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR;
ev->data.ext.ptr = (char __force *)buf + len;
len += extlen; /* increment data length */
} else {
#ifdef CONFIG_COMPAT
if (client->convert32 && snd_seq_ev_is_varusr(ev))
ev->data.ext.ptr =
(void __force *)compat_ptr(ev->data.raw32.d[1]);
#endif
}
/* ok, enqueue it */
err = snd_seq_client_enqueue_event(client, ev, file,
!(file->f_flags & O_NONBLOCK),
0, 0, &client->ioctl_mutex);
if (err < 0)
break;
handled++;
__skip_event:
/* Update pointers and counts */
count -= len;
buf += len;
written += len;
/* let's have a coffee break if too many events are queued */
if (++handled >= 200) {
mutex_unlock(&client->ioctl_mutex);
goto repeat;
}
}
out:
mutex_unlock(&client->ioctl_mutex);
return written ? written : err;
}
/*
* handle polling
*/
static __poll_t snd_seq_poll(struct file *file, poll_table * wait)
{
struct snd_seq_client *client = file->private_data;
__poll_t mask = 0;
/* check client structures are in place */
if (snd_BUG_ON(!client))
return EPOLLERR;
if ((snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT) &&
client->data.user.fifo) {
/* check if data is available in the outqueue */
if (snd_seq_fifo_poll_wait(client->data.user.fifo, file, wait))
mask |= EPOLLIN | EPOLLRDNORM;
}
if (snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT) {
/* check if data is available in the pool */
if (snd_seq_pool_poll_wait(client->pool, file, wait))
mask |= EPOLLOUT | EPOLLWRNORM;
}
return mask;
}
/*-----------------------------------------------------*/
static int snd_seq_ioctl_pversion(struct snd_seq_client *client, void *arg)
{
int *pversion = arg;
*pversion = SNDRV_SEQ_VERSION;
return 0;
}
static int snd_seq_ioctl_user_pversion(struct snd_seq_client *client, void *arg)
{
client->user_pversion = *(unsigned int *)arg;
return 0;
}
static int snd_seq_ioctl_client_id(struct snd_seq_client *client, void *arg)
{
int *client_id = arg;
*client_id = client->number;
return 0;
}
/* SYSTEM_INFO ioctl() */
static int snd_seq_ioctl_system_info(struct snd_seq_client *client, void *arg)
{
struct snd_seq_system_info *info = arg;
memset(info, 0, sizeof(*info));
/* fill the info fields */
info->queues = SNDRV_SEQ_MAX_QUEUES;
info->clients = SNDRV_SEQ_MAX_CLIENTS;
info->ports = SNDRV_SEQ_MAX_PORTS;
info->channels = 256; /* fixed limit */
info->cur_clients = client_usage.cur;
info->cur_queues = snd_seq_queue_get_cur_queues();
return 0;
}
/* RUNNING_MODE ioctl() */
static int snd_seq_ioctl_running_mode(struct snd_seq_client *client, void *arg)
{
struct snd_seq_running_info *info = arg;
/* requested client number */
struct snd_seq_client *cptr __free(snd_seq_client) =
client_load_and_use_ptr(info->client);
if (cptr == NULL)
return -ENOENT; /* don't change !!! */
#ifdef SNDRV_BIG_ENDIAN
if (!info->big_endian)
return -EINVAL;
#else
if (info->big_endian)
return -EINVAL;
#endif
if (info->cpu_mode > sizeof(long))
return -EINVAL;
cptr->convert32 = (info->cpu_mode < sizeof(long));
return 0;
}
/* CLIENT_INFO ioctl() */
static void get_client_info(struct snd_seq_client *cptr,
struct snd_seq_client_info *info)
{
info->client = cptr->number;
/* fill the info fields */
info->type = cptr->type;
strscpy(info->name, cptr->name);
info->filter = cptr->filter;
info->event_lost = cptr->event_lost;
memcpy(info->event_filter, cptr->event_filter, 32);
info->group_filter = cptr->group_filter;
info->num_ports = cptr->num_ports;
if (cptr->type == USER_CLIENT)
info->pid = pid_vnr(cptr->data.user.owner);
else
info->pid = -1;
if (cptr->type == KERNEL_CLIENT)
info->card = cptr->data.kernel.card ? cptr->data.kernel.card->number : -1;
else
info->card = -1;
info->midi_version = cptr->midi_version;
memset(info->reserved, 0, sizeof(info->reserved));
}
static int snd_seq_ioctl_get_client_info(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_client_info *client_info = arg;
/* requested client number */
struct snd_seq_client *cptr __free(snd_seq_client) =
client_load_and_use_ptr(client_info->client);
if (cptr == NULL)
return -ENOENT; /* don't change !!! */
get_client_info(cptr, client_info);
return 0;
}
/* CLIENT_INFO ioctl() */
static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_client_info *client_info = arg;
/* it is not allowed to set the info fields for an another client */
if (client->number != client_info->client)
return -EPERM;
/* also client type must be set now */
if (client->type != client_info->type)
return -EINVAL;
if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3)) {
/* check validity of midi_version field */
if (client_info->midi_version > SNDRV_SEQ_CLIENT_UMP_MIDI_2_0)
return -EINVAL;
/* check if UMP is supported in kernel */
if (!IS_ENABLED(CONFIG_SND_SEQ_UMP) &&
client_info->midi_version > 0)
return -EINVAL;
}
/* fill the info fields */
if (client_info->name[0])
strscpy(client->name, client_info->name, sizeof(client->name));
client->filter = client_info->filter;
client->event_lost = client_info->event_lost;
if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3))
client->midi_version = client_info->midi_version;
memcpy(client->event_filter, client_info->event_filter, 32);
client->group_filter = client_info->group_filter;
/* notify the change */
snd_seq_system_client_ev_client_change(client->number);
return 0;
}
/*
* CREATE PORT ioctl()
*/
static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg)
{
struct snd_seq_port_info *info = arg;
struct snd_seq_client_port *port;
struct snd_seq_port_callback *callback;
int port_idx, err;
/* it is not allowed to create the port for an another client */
if (info->addr.client != client->number)
return -EPERM;
if (client->type == USER_CLIENT && info->kernel)
return -EINVAL;
if ((info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT) &&
client->ump_endpoint_port >= 0)
return -EBUSY;
if (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT)
port_idx = info->addr.port;
else
port_idx = -1;
if (port_idx >= SNDRV_SEQ_ADDRESS_UNKNOWN)
return -EINVAL;
err = snd_seq_create_port(client, port_idx, &port);
if (err < 0)
return err;
if (client->type == KERNEL_CLIENT) {
callback = info->kernel;
if (callback) {
if (callback->owner)
port->owner = callback->owner;
port->private_data = callback->private_data;
port->private_free = callback->private_free;
port->event_input = callback->event_input;
port->c_src.open = callback->subscribe;
port->c_src.close = callback->unsubscribe;
port->c_dest.open = callback->use;
port->c_dest.close = callback->unuse;
}
}
info->addr = port->addr;
snd_seq_set_port_info(port, info);
if (info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT)
client->ump_endpoint_port = port->addr.port;
snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port);
snd_seq_port_unlock(port);
return 0;
}
/*
* DELETE PORT ioctl()
*/
static int snd_seq_ioctl_delete_port(struct snd_seq_client *client, void *arg)
{
struct snd_seq_port_info *info = arg;
int err;
/* it is not allowed to remove the port for an another client */
if (info->addr.client != client->number)
return -EPERM;
err = snd_seq_delete_port(client, info->addr.port);
if (err >= 0) {
if (client->ump_endpoint_port == info->addr.port)
client->ump_endpoint_port = -1;
snd_seq_system_client_ev_port_exit(client->number, info->addr.port);
}
return err;
}
/*
* GET_PORT_INFO ioctl() (on any client)
*/
static int snd_seq_ioctl_get_port_info(struct snd_seq_client *client, void *arg)
{
struct snd_seq_port_info *info = arg;
struct snd_seq_client *cptr __free(snd_seq_client) =
client_load_and_use_ptr(info->addr.client);
if (cptr == NULL)
return -ENXIO;
struct snd_seq_client_port *port __free(snd_seq_port) =
snd_seq_port_use_ptr(cptr, info->addr.port);
if (port == NULL)
return -ENOENT; /* don't change */
/* get port info */
snd_seq_get_port_info(port, info);
return 0;
}
/*
* SET_PORT_INFO ioctl() (only ports on this/own client)
*/
static int snd_seq_ioctl_set_port_info(struct snd_seq_client *client, void *arg)
{
struct snd_seq_port_info *info = arg;
if (info->addr.client != client->number) /* only set our own ports ! */
return -EPERM;
struct snd_seq_client_port *port __free(snd_seq_port) =
snd_seq_port_use_ptr(client, info->addr.port);
if (port) {
snd_seq_set_port_info(port, info);
/* notify the change */
snd_seq_system_client_ev_port_change(info->addr.client,
info->addr.port);
}
return 0;
}
/*
* port subscription (connection)
*/
#define PERM_RD (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ)
#define PERM_WR (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE)
static int check_subscription_permission(struct snd_seq_client *client,
struct snd_seq_client_port *sport,
struct snd_seq_client_port *dport,
struct snd_seq_port_subscribe *subs)
{
if (client->number != subs->sender.client &&
client->number != subs->dest.client) {
/* connection by third client - check export permission */
if (check_port_perm(sport, SNDRV_SEQ_PORT_CAP_NO_EXPORT))
return -EPERM;
if (check_port_perm(dport, SNDRV_SEQ_PORT_CAP_NO_EXPORT))
return -EPERM;
}
/* check read permission */
/* if sender or receiver is the subscribing client itself,
* no permission check is necessary
*/
if (client->number != subs->sender.client) {
if (! check_port_perm(sport, PERM_RD))
return -EPERM;
}
/* check write permission */
if (client->number != subs->dest.client) {
if (! check_port_perm(dport, PERM_WR))
return -EPERM;
}
return 0;
}
/*
* send an subscription notify event to user client:
* client must be user client.
*/
int snd_seq_client_notify_subscription(int client, int port,
struct snd_seq_port_subscribe *info,
int evtype)
{
struct snd_seq_event event;
memset(&event, 0, sizeof(event));
event.type = evtype;
event.data.connect.dest = info->dest;
event.data.connect.sender = info->sender;
return snd_seq_system_notify(client, port, &event, false); /* non-atomic */
}
/*
* add to port's subscription list IOCTL interface
*/
static int snd_seq_ioctl_subscribe_port(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_port_subscribe *subs = arg;
int result;
struct snd_seq_client *receiver __free(snd_seq_client) =
client_load_and_use_ptr(subs->dest.client);
if (!receiver)
return -EINVAL;
struct snd_seq_client *sender __free(snd_seq_client) =
client_load_and_use_ptr(subs->sender.client);
if (!sender)
return -EINVAL;
struct snd_seq_client_port *sport __free(snd_seq_port) =
snd_seq_port_use_ptr(sender, subs->sender.port);
if (!sport)
return -EINVAL;
struct snd_seq_client_port *dport __free(snd_seq_port) =
snd_seq_port_use_ptr(receiver, subs->dest.port);
if (!dport)
return -EINVAL;
result = check_subscription_permission(client, sport, dport, subs);
if (result < 0)
return result;
/* connect them */
result = snd_seq_port_connect(client, sender, sport, receiver, dport, subs);
if (! result) /* broadcast announce */
snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0,
subs, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED);
return result;
}
/*
* remove from port's subscription list
*/
static int snd_seq_ioctl_unsubscribe_port(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_port_subscribe *subs = arg;
int result;
struct snd_seq_client *receiver __free(snd_seq_client) =
snd_seq_client_use_ptr(subs->dest.client);
if (!receiver)
return -ENXIO;
struct snd_seq_client *sender __free(snd_seq_client) =
snd_seq_client_use_ptr(subs->sender.client);
if (!sender)
return -ENXIO;
struct snd_seq_client_port *sport __free(snd_seq_port) =
snd_seq_port_use_ptr(sender, subs->sender.port);
if (!sport)
return -ENXIO;
struct snd_seq_client_port *dport __free(snd_seq_port) =
snd_seq_port_use_ptr(receiver, subs->dest.port);
if (!dport)
return -ENXIO;
result = check_subscription_permission(client, sport, dport, subs);
if (result < 0)
return result;
result = snd_seq_port_disconnect(client, sender, sport, receiver, dport, subs);
if (! result) /* broadcast announce */
snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0,
subs, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED);
return result;
}
/* CREATE_QUEUE ioctl() */
static int snd_seq_ioctl_create_queue(struct snd_seq_client *client, void *arg)
{
struct snd_seq_queue_info *info = arg;
struct snd_seq_queue *q __free(snd_seq_queue) =
snd_seq_queue_alloc(client->number, info->locked, info->flags);
if (IS_ERR(q))
return PTR_ERR(q);
info->queue = q->queue;
info->locked = q->locked;
info->owner = q->owner;
/* set queue name */
if (!info->name[0])
snprintf(info->name, sizeof(info->name), "Queue-%d", q->queue);
strscpy(q->name, info->name, sizeof(q->name));
return 0;
}
/* DELETE_QUEUE ioctl() */
static int snd_seq_ioctl_delete_queue(struct snd_seq_client *client, void *arg)
{
struct snd_seq_queue_info *info = arg;
return snd_seq_queue_delete(client->number, info->queue);
}
/* GET_QUEUE_INFO ioctl() */
static int snd_seq_ioctl_get_queue_info(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_queue_info *info = arg;
struct snd_seq_queue *q __free(snd_seq_queue) =
queueptr(info->queue);
if (q == NULL)
return -EINVAL;
memset(info, 0, sizeof(*info));
info->queue = q->queue;
info->owner = q->owner;
info->locked = q->locked;
strscpy(info->name, q->name, sizeof(info->name));
return 0;
}
/* SET_QUEUE_INFO ioctl() */
static int snd_seq_ioctl_set_queue_info(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_queue_info *info = arg;
if (info->owner != client->number)
return -EINVAL;
/* change owner/locked permission */
if (snd_seq_queue_check_access(info->queue, client->number)) {
if (snd_seq_queue_set_owner(info->queue, client->number, info->locked) < 0)
return -EPERM;
if (info->locked)
snd_seq_queue_use(info->queue, client->number, 1);
} else {
return -EPERM;
}
struct snd_seq_queue *q __free(snd_seq_queue) =
queueptr(info->queue);
if (! q)
return -EINVAL;
if (q->owner != client->number)
return -EPERM;
strscpy(q->name, info->name, sizeof(q->name));
return 0;
}
/* GET_NAMED_QUEUE ioctl() */
static int snd_seq_ioctl_get_named_queue(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_queue_info *info = arg;
struct snd_seq_queue *q __free(snd_seq_queue) =
snd_seq_queue_find_name(info->name);
if (q == NULL)
return -EINVAL;
info->queue = q->queue;
info->owner = q->owner;
info->locked = q->locked;
return 0;
}
/* GET_QUEUE_STATUS ioctl() */
static int snd_seq_ioctl_get_queue_status(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_queue_status *status = arg;
struct snd_seq_timer *tmr;
struct snd_seq_queue *queue __free(snd_seq_queue) =
queueptr(status->queue);
if (queue == NULL)
return -EINVAL;
memset(status, 0, sizeof(*status));
status->queue = queue->queue;
tmr = queue->timer;
status->events = queue->tickq->cells + queue->timeq->cells;
status->time = snd_seq_timer_get_cur_time(tmr, true);
status->tick = snd_seq_timer_get_cur_tick(tmr);
status->running = tmr->running;
status->flags = queue->flags;
return 0;
}
/* GET_QUEUE_TEMPO ioctl() */
static int snd_seq_ioctl_get_queue_tempo(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_queue_tempo *tempo = arg;
struct snd_seq_timer *tmr;
struct snd_seq_queue *queue __free(snd_seq_queue) =
queueptr(tempo->queue);
if (queue == NULL)
return -EINVAL;
memset(tempo, 0, sizeof(*tempo));
tempo->queue = queue->queue;
tmr = queue->timer;
tempo->tempo = tmr->tempo;
tempo->ppq = tmr->ppq;
tempo->skew_value = tmr->skew;
tempo->skew_base = tmr->skew_base;
if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 4))
tempo->tempo_base = tmr->tempo_base;
return 0;
}
/* SET_QUEUE_TEMPO ioctl() */
int snd_seq_set_queue_tempo(int client, struct snd_seq_queue_tempo *tempo)
{
if (!snd_seq_queue_check_access(tempo->queue, client))
return -EPERM;
return snd_seq_queue_timer_set_tempo(tempo->queue, client, tempo);
}
EXPORT_SYMBOL(snd_seq_set_queue_tempo);
static int snd_seq_ioctl_set_queue_tempo(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_queue_tempo *tempo = arg;
int result;
if (client->user_pversion < SNDRV_PROTOCOL_VERSION(1, 0, 4))
tempo->tempo_base = 0;
result = snd_seq_set_queue_tempo(client->number, tempo);
return result < 0 ? result : 0;
}
/* GET_QUEUE_TIMER ioctl() */
static int snd_seq_ioctl_get_queue_timer(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_queue_timer *timer = arg;
struct snd_seq_timer *tmr;
struct snd_seq_queue *queue __free(snd_seq_queue) =
queueptr(timer->queue);
if (queue == NULL)
return -EINVAL;
guard(mutex)(&queue->timer_mutex);
tmr = queue->timer;
memset(timer, 0, sizeof(*timer));
timer->queue = queue->queue;
timer->type = tmr->type;
if (tmr->type == SNDRV_SEQ_TIMER_ALSA) {
timer->u.alsa.id = tmr->alsa_id;
timer->u.alsa.resolution = tmr->preferred_resolution;
}
return 0;
}
/* SET_QUEUE_TIMER ioctl() */
static int snd_seq_ioctl_set_queue_timer(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_queue_timer *timer = arg;
int result = 0;
if (timer->type != SNDRV_SEQ_TIMER_ALSA)
return -EINVAL;
if (snd_seq_queue_check_access(timer->queue, client->number)) {
struct snd_seq_timer *tmr;
struct snd_seq_queue *q __free(snd_seq_queue) =
queueptr(timer->queue);
if (q == NULL)
return -ENXIO;
guard(mutex)(&q->timer_mutex);
tmr = q->timer;
snd_seq_queue_timer_close(timer->queue);
tmr->type = timer->type;
if (tmr->type == SNDRV_SEQ_TIMER_ALSA) {
tmr->alsa_id = timer->u.alsa.id;
tmr->preferred_resolution = timer->u.alsa.resolution;
}
result = snd_seq_queue_timer_open(timer->queue);
} else {
return -EPERM;
}
return result;
}
/* GET_QUEUE_CLIENT ioctl() */
static int snd_seq_ioctl_get_queue_client(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_queue_client *info = arg;
int used;
used = snd_seq_queue_is_used(info->queue, client->number);
if (used < 0)
return -EINVAL;
info->used = used;
info->client = client->number;
return 0;
}
/* SET_QUEUE_CLIENT ioctl() */
static int snd_seq_ioctl_set_queue_client(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_queue_client *info = arg;
int err;
if (info->used >= 0) {
err = snd_seq_queue_use(info->queue, client->number, info->used);
if (err < 0)
return err;
}
return snd_seq_ioctl_get_queue_client(client, arg);
}
/* GET_CLIENT_POOL ioctl() */
static int snd_seq_ioctl_get_client_pool(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_client_pool *info = arg;
struct snd_seq_client *cptr __free(snd_seq_client) =
client_load_and_use_ptr(info->client);
if (cptr == NULL)
return -ENOENT;
memset(info, 0, sizeof(*info));
info->client = cptr->number;
info->output_pool = cptr->pool->size;
info->output_room = cptr->pool->room;
info->output_free = info->output_pool;
info->output_free = snd_seq_unused_cells(cptr->pool);
if (cptr->type == USER_CLIENT) {
info->input_pool = cptr->data.user.fifo_pool_size;
info->input_free = info->input_pool;
info->input_free = snd_seq_fifo_unused_cells(cptr->data.user.fifo);
} else {
info->input_pool = 0;
info->input_free = 0;
}
return 0;
}
/* SET_CLIENT_POOL ioctl() */
static int snd_seq_ioctl_set_client_pool(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_client_pool *info = arg;
int rc;
if (client->number != info->client)
return -EINVAL; /* can't change other clients */
if (info->output_pool >= 1 && info->output_pool <= SNDRV_SEQ_MAX_EVENTS &&
(! snd_seq_write_pool_allocated(client) ||
info->output_pool != client->pool->size)) {
if (snd_seq_write_pool_allocated(client)) {
/* is the pool in use? */
if (atomic_read(&client->pool->counter))
return -EBUSY;
/* remove all existing cells */
snd_seq_pool_mark_closing(client->pool);
snd_seq_pool_done(client->pool);
}
client->pool->size = info->output_pool;
rc = snd_seq_pool_init(client->pool);
if (rc < 0)
return rc;
}
if (client->type == USER_CLIENT && client->data.user.fifo != NULL &&
info->input_pool >= 1 &&
info->input_pool <= SNDRV_SEQ_MAX_CLIENT_EVENTS &&
info->input_pool != client->data.user.fifo_pool_size) {
/* change pool size */
rc = snd_seq_fifo_resize(client->data.user.fifo, info->input_pool);
if (rc < 0)
return rc;
client->data.user.fifo_pool_size = info->input_pool;
}
if (info->output_room >= 1 &&
info->output_room <= client->pool->size) {
client->pool->room = info->output_room;
}
return snd_seq_ioctl_get_client_pool(client, arg);
}
/* REMOVE_EVENTS ioctl() */
static int snd_seq_ioctl_remove_events(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_remove_events *info = arg;
/*
* Input mostly not implemented XXX.
*/
if (info->remove_mode & SNDRV_SEQ_REMOVE_INPUT) {
/*
* No restrictions so for a user client we can clear
* the whole fifo
*/
if (client->type == USER_CLIENT && client->data.user.fifo)
snd_seq_fifo_clear(client->data.user.fifo);
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_OUTPUT)
snd_seq_queue_remove_cells(client->number, info);
return 0;
}
/*
* get subscription info
*/
static int snd_seq_ioctl_get_subscription(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_port_subscribe *subs = arg;
struct snd_seq_client *sender __free(snd_seq_client) =
client_load_and_use_ptr(subs->sender.client);
if (!sender)
return -EINVAL;
struct snd_seq_client_port *sport __free(snd_seq_port) =
snd_seq_port_use_ptr(sender, subs->sender.port);
if (!sport)
return -EINVAL;
return snd_seq_port_get_subscription(&sport->c_src, &subs->dest, subs);
}
/*
* get subscription info - check only its presence
*/
static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg)
{
struct snd_seq_query_subs *subs = arg;
struct snd_seq_port_subs_info *group;
struct list_head *p;
int i;
struct snd_seq_client *cptr __free(snd_seq_client) =
client_load_and_use_ptr(subs->root.client);
if (!cptr)
return -ENXIO;
struct snd_seq_client_port *port __free(snd_seq_port) =
snd_seq_port_use_ptr(cptr, subs->root.port);
if (!port)
return -ENXIO;
switch (subs->type) {
case SNDRV_SEQ_QUERY_SUBS_READ:
group = &port->c_src;
break;
case SNDRV_SEQ_QUERY_SUBS_WRITE:
group = &port->c_dest;
break;
default:
return -ENXIO;
}
guard(rwsem_read)(&group->list_mutex);
/* search for the subscriber */
subs->num_subs = group->count;
i = 0;
list_for_each(p, &group->list_head) {
if (i++ == subs->index) {
/* found! */
struct snd_seq_subscribers *s;
if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) {
s = list_entry(p, struct snd_seq_subscribers, src_list);
subs->addr = s->info.dest;
} else {
s = list_entry(p, struct snd_seq_subscribers, dest_list);
subs->addr = s->info.sender;
}
subs->flags = s->info.flags;
subs->queue = s->info.queue;
return 0;
}
}
return -ENOENT;
}
/*
* query next client
*/
static int snd_seq_ioctl_query_next_client(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_client_info *info = arg;
/* search for next client */
if (info->client < INT_MAX)
info->client++;
if (info->client < 0)
info->client = 0;
for (; info->client < SNDRV_SEQ_MAX_CLIENTS; info->client++) {
struct snd_seq_client *cptr __free(snd_seq_client) =
client_load_and_use_ptr(info->client);
if (cptr) {
get_client_info(cptr, info);
return 0; /* found */
}
}
return -ENOENT;
}
/*
* query next port
*/
static int snd_seq_ioctl_query_next_port(struct snd_seq_client *client,
void *arg)
{
struct snd_seq_port_info *info = arg;
struct snd_seq_client *cptr __free(snd_seq_client) =
client_load_and_use_ptr(info->addr.client);
if (cptr == NULL)
return -ENXIO;
/* search for next port */
info->addr.port++;
struct snd_seq_client_port *port __free(snd_seq_port) =
snd_seq_port_query_nearest(cptr, info);
if (port == NULL)
return -ENOENT;
/* get port info */
info->addr = port->addr;
snd_seq_get_port_info(port, info);
return 0;
}
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
#define NUM_UMP_INFOS (SNDRV_UMP_MAX_BLOCKS + 1)
static void free_ump_info(struct snd_seq_client *client)
{
int i;
if (!client->ump_info)
return;
for (i = 0; i < NUM_UMP_INFOS; i++)
kfree(client->ump_info[i]);
kfree(client->ump_info);
client->ump_info = NULL;
}
static void terminate_ump_info_strings(void *p, int type)
{
if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT) {
struct snd_ump_endpoint_info *ep = p;
ep->name[sizeof(ep->name) - 1] = 0;
} else {
struct snd_ump_block_info *bp = p;
bp->name[sizeof(bp->name) - 1] = 0;
}
}
#ifdef CONFIG_SND_PROC_FS
static void dump_ump_info(struct snd_info_buffer *buffer,
struct snd_seq_client *client)
{
struct snd_ump_endpoint_info *ep;
struct snd_ump_block_info *bp;
int i;
if (!client->ump_info)
return;
ep = client->ump_info[SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT];
if (ep && *ep->name)
snd_iprintf(buffer, " UMP Endpoint: \"%s\"\n", ep->name);
for (i = 0; i < SNDRV_UMP_MAX_BLOCKS; i++) {
bp = client->ump_info[i + 1];
if (bp && *bp->name) {
snd_iprintf(buffer, " UMP Block %d: \"%s\" [%s]\n",
i, bp->name,
bp->active ? "Active" : "Inactive");
snd_iprintf(buffer, " Groups: %d-%d\n",
bp->first_group + 1,
bp->first_group + bp->num_groups);
}
}
}
#endif
/* UMP-specific ioctls -- called directly without data copy */
static int snd_seq_ioctl_client_ump_info(struct snd_seq_client *caller,
unsigned int cmd,
unsigned long arg)
{
struct snd_seq_client_ump_info __user *argp =
(struct snd_seq_client_ump_info __user *)arg;
int client, type, err = 0;
size_t size;
void *p;
if (get_user(client, &argp->client) || get_user(type, &argp->type))
return -EFAULT;
if (cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO &&
caller->number != client)
return -EPERM;
if (type < 0 || type >= NUM_UMP_INFOS)
return -EINVAL;
if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT)
size = sizeof(struct snd_ump_endpoint_info);
else
size = sizeof(struct snd_ump_block_info);
struct snd_seq_client *cptr __free(snd_seq_client) =
client_load_and_use_ptr(client);
if (!cptr)
return -ENOENT;
scoped_guard(mutex, &cptr->ioctl_mutex) {
if (!cptr->midi_version) {
err = -EBADFD;
break;
}
if (cmd == SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO) {
if (!cptr->ump_info)
p = NULL;
else
p = cptr->ump_info[type];
if (!p) {
err = -ENODEV;
break;
}
if (copy_to_user(argp->info, p, size)) {
err = -EFAULT;
break;
}
} else {
if (cptr->type != USER_CLIENT) {
err = -EBADFD;
break;
}
if (!cptr->ump_info) {
cptr->ump_info = kcalloc(NUM_UMP_INFOS,
sizeof(void *), GFP_KERNEL);
if (!cptr->ump_info) {
err = -ENOMEM;
break;
}
}
p = memdup_user(argp->info, size);
if (IS_ERR(p)) {
err = PTR_ERR(p);
break;
}
kfree(cptr->ump_info[type]);
terminate_ump_info_strings(p, type);
cptr->ump_info[type] = p;
}
}
if (!err && cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO) {
if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT)
snd_seq_system_ump_notify(client, 0,
SNDRV_SEQ_EVENT_UMP_EP_CHANGE,
false);
else
snd_seq_system_ump_notify(client, type - 1,
SNDRV_SEQ_EVENT_UMP_BLOCK_CHANGE,
false);
}
return err;
}
#endif
/* -------------------------------------------------------- */
static const struct ioctl_handler {
unsigned int cmd;
int (*func)(struct snd_seq_client *client, void *arg);
} ioctl_handlers[] = {
{ SNDRV_SEQ_IOCTL_PVERSION, snd_seq_ioctl_pversion },
{ SNDRV_SEQ_IOCTL_USER_PVERSION, snd_seq_ioctl_user_pversion },
{ SNDRV_SEQ_IOCTL_CLIENT_ID, snd_seq_ioctl_client_id },
{ SNDRV_SEQ_IOCTL_SYSTEM_INFO, snd_seq_ioctl_system_info },
{ SNDRV_SEQ_IOCTL_RUNNING_MODE, snd_seq_ioctl_running_mode },
{ SNDRV_SEQ_IOCTL_GET_CLIENT_INFO, snd_seq_ioctl_get_client_info },
{ SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, snd_seq_ioctl_set_client_info },
{ SNDRV_SEQ_IOCTL_CREATE_PORT, snd_seq_ioctl_create_port },
{ SNDRV_SEQ_IOCTL_DELETE_PORT, snd_seq_ioctl_delete_port },
{ SNDRV_SEQ_IOCTL_GET_PORT_INFO, snd_seq_ioctl_get_port_info },
{ SNDRV_SEQ_IOCTL_SET_PORT_INFO, snd_seq_ioctl_set_port_info },
{ SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, snd_seq_ioctl_subscribe_port },
{ SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, snd_seq_ioctl_unsubscribe_port },
{ SNDRV_SEQ_IOCTL_CREATE_QUEUE, snd_seq_ioctl_create_queue },
{ SNDRV_SEQ_IOCTL_DELETE_QUEUE, snd_seq_ioctl_delete_queue },
{ SNDRV_SEQ_IOCTL_GET_QUEUE_INFO, snd_seq_ioctl_get_queue_info },
{ SNDRV_SEQ_IOCTL_SET_QUEUE_INFO, snd_seq_ioctl_set_queue_info },
{ SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE, snd_seq_ioctl_get_named_queue },
{ SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS, snd_seq_ioctl_get_queue_status },
{ SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO, snd_seq_ioctl_get_queue_tempo },
{ SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO, snd_seq_ioctl_set_queue_tempo },
{ SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER, snd_seq_ioctl_get_queue_timer },
{ SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER, snd_seq_ioctl_set_queue_timer },
{ SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT, snd_seq_ioctl_get_queue_client },
{ SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT, snd_seq_ioctl_set_queue_client },
{ SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, snd_seq_ioctl_get_client_pool },
{ SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, snd_seq_ioctl_set_client_pool },
{ SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION, snd_seq_ioctl_get_subscription },
{ SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, snd_seq_ioctl_query_next_client },
{ SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, snd_seq_ioctl_query_next_port },
{ SNDRV_SEQ_IOCTL_REMOVE_EVENTS, snd_seq_ioctl_remove_events },
{ SNDRV_SEQ_IOCTL_QUERY_SUBS, snd_seq_ioctl_query_subs },
{ 0, NULL },
};
static long snd_seq_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct snd_seq_client *client = file->private_data;
/* To use kernel stack for ioctl data. */
union {
int pversion;
int client_id;
struct snd_seq_system_info system_info;
struct snd_seq_running_info running_info;
struct snd_seq_client_info client_info;
struct snd_seq_port_info port_info;
struct snd_seq_port_subscribe port_subscribe;
struct snd_seq_queue_info queue_info;
struct snd_seq_queue_status queue_status;
struct snd_seq_queue_tempo tempo;
struct snd_seq_queue_timer queue_timer;
struct snd_seq_queue_client queue_client;
struct snd_seq_client_pool client_pool;
struct snd_seq_remove_events remove_events;
struct snd_seq_query_subs query_subs;
} buf;
const struct ioctl_handler *handler;
unsigned long size;
int err;
if (snd_BUG_ON(!client))
return -ENXIO;
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
/* exception - handling large data */
switch (cmd) {
case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO:
case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO:
return snd_seq_ioctl_client_ump_info(client, cmd, arg);
}
#endif
for (handler = ioctl_handlers; handler->cmd > 0; ++handler) {
if (handler->cmd == cmd)
break;
}
if (handler->cmd == 0)
return -ENOTTY;
memset(&buf, 0, sizeof(buf));
/*
* All of ioctl commands for ALSA sequencer get an argument of size
* within 13 bits. We can safely pick up the size from the command.
*/
size = _IOC_SIZE(handler->cmd);
if (handler->cmd & IOC_IN) {
if (copy_from_user(&buf, (const void __user *)arg, size))
return -EFAULT;
}
scoped_guard(mutex, &client->ioctl_mutex) {
err = handler->func(client, &buf);
}
if (err >= 0) {
/* Some commands includes a bug in 'dir' field. */
if (handler->cmd == SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT ||
handler->cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_POOL ||
(handler->cmd & IOC_OUT))
if (copy_to_user((void __user *)arg, &buf, size))
return -EFAULT;
}
return err;
}
#ifdef CONFIG_COMPAT
#include "seq_compat.c"
#else
#define snd_seq_ioctl_compat NULL
#endif
/* -------------------------------------------------------- */
/* exported to kernel modules */
int snd_seq_create_kernel_client(struct snd_card *card, int client_index,
const char *name_fmt, ...)
{
struct snd_seq_client *client;
va_list args;
if (snd_BUG_ON(in_interrupt()))
return -EBUSY;
if (card && client_index >= SNDRV_SEQ_CLIENTS_PER_CARD)
return -EINVAL;
if (card == NULL && client_index >= SNDRV_SEQ_GLOBAL_CLIENTS)
return -EINVAL;
scoped_guard(mutex, ®ister_mutex) {
if (card) {
client_index += SNDRV_SEQ_GLOBAL_CLIENTS
+ card->number * SNDRV_SEQ_CLIENTS_PER_CARD;
if (client_index >= SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN)
client_index = -1;
}
/* empty write queue as default */
client = seq_create_client1(client_index, 0);
if (client == NULL)
return -EBUSY; /* failure code */
usage_alloc(&client_usage, 1);
client->accept_input = 1;
client->accept_output = 1;
client->data.kernel.card = card;
client->user_pversion = SNDRV_SEQ_VERSION;
va_start(args, name_fmt);
vsnprintf(client->name, sizeof(client->name), name_fmt, args);
va_end(args);
client->type = KERNEL_CLIENT;
}
/* make others aware this new client */
snd_seq_system_client_ev_client_start(client->number);
/* return client number to caller */
return client->number;
}
EXPORT_SYMBOL(snd_seq_create_kernel_client);
/* exported to kernel modules */
int snd_seq_delete_kernel_client(int client)
{
struct snd_seq_client *ptr;
if (snd_BUG_ON(in_interrupt()))
return -EBUSY;
ptr = clientptr(client);
if (ptr == NULL)
return -EINVAL;
seq_free_client(ptr);
kfree(ptr);
return 0;
}
EXPORT_SYMBOL(snd_seq_delete_kernel_client);
/*
* exported, called by kernel clients to enqueue events (w/o blocking)
*
* RETURN VALUE: zero if succeed, negative if error
*/
int snd_seq_kernel_client_enqueue(int client, struct snd_seq_event *ev,
struct file *file, bool blocking)
{
if (snd_BUG_ON(!ev))
return -EINVAL;
if (!snd_seq_ev_is_ump(ev)) {
if (ev->type == SNDRV_SEQ_EVENT_NONE)
return 0; /* ignore this */
if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
return -EINVAL; /* quoted events can't be enqueued */
}
/* fill in client number */
ev->source.client = client;
if (check_event_type_and_length(ev))
return -EINVAL;
struct snd_seq_client *cptr __free(snd_seq_client) =
client_load_and_use_ptr(client);
if (cptr == NULL)
return -EINVAL;
if (!cptr->accept_output) {
return -EPERM;
} else { /* send it */
guard(mutex)(&cptr->ioctl_mutex);
return snd_seq_client_enqueue_event(cptr, ev, file, blocking,
false, 0,
&cptr->ioctl_mutex);
}
}
EXPORT_SYMBOL(snd_seq_kernel_client_enqueue);
/*
* exported, called by kernel clients to dispatch events directly to other
* clients, bypassing the queues. Event time-stamp will be updated.
*
* RETURN VALUE: negative = delivery failed,
* zero, or positive: the number of delivered events
*/
int snd_seq_kernel_client_dispatch(int client, struct snd_seq_event * ev,
int atomic, int hop)
{
if (snd_BUG_ON(!ev))
return -EINVAL;
/* fill in client number */
ev->queue = SNDRV_SEQ_QUEUE_DIRECT;
ev->source.client = client;
if (check_event_type_and_length(ev))
return -EINVAL;
struct snd_seq_client *cptr __free(snd_seq_client) =
snd_seq_client_use_ptr(client);
if (cptr == NULL)
return -EINVAL;
if (!cptr->accept_output)
return -EPERM;
else
return snd_seq_deliver_event(cptr, ev, atomic, hop);
}
EXPORT_SYMBOL(snd_seq_kernel_client_dispatch);
static int call_seq_client_ctl(struct snd_seq_client *client,
unsigned int cmd, void *arg)
{
const struct ioctl_handler *handler;
for (handler = ioctl_handlers; handler->cmd > 0; ++handler) {
if (handler->cmd == cmd)
return handler->func(client, arg);
}
pr_debug("ALSA: seq unknown ioctl() 0x%x (type='%c', number=0x%02x)\n",
cmd, _IOC_TYPE(cmd), _IOC_NR(cmd));
return -ENOTTY;
}
/**
* snd_seq_kernel_client_ctl - operate a command for a client with data in
* kernel space.
* @clientid: A numerical ID for a client.
* @cmd: An ioctl(2) command for ALSA sequencer operation.
* @arg: A pointer to data in kernel space.
*
* Against its name, both kernel/application client can be handled by this
* kernel API. A pointer of 'arg' argument should be in kernel space.
*
* Return: 0 at success. Negative error code at failure.
*/
int snd_seq_kernel_client_ctl(int clientid, unsigned int cmd, void *arg)
{
struct snd_seq_client *client;
client = clientptr(clientid);
if (client == NULL)
return -ENXIO;
return call_seq_client_ctl(client, cmd, arg);
}
EXPORT_SYMBOL(snd_seq_kernel_client_ctl);
/* a similar like above but taking locks; used only from OSS sequencer layer */
int snd_seq_kernel_client_ioctl(int clientid, unsigned int cmd, void *arg)
{
struct snd_seq_client *client __free(snd_seq_client) =
client_load_and_use_ptr(clientid);
if (!client)
return -ENXIO;
guard(mutex)(&client->ioctl_mutex);
return call_seq_client_ctl(client, cmd, arg);
}
EXPORT_SYMBOL_GPL(snd_seq_kernel_client_ioctl);
/* exported (for OSS emulator) */
int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait)
{
struct snd_seq_client *client;
client = clientptr(clientid);
if (client == NULL)
return -ENXIO;
if (snd_seq_pool_poll_wait(client->pool, file, wait))
return 1;
return 0;
}
EXPORT_SYMBOL(snd_seq_kernel_client_write_poll);
/* get a sequencer client object; for internal use from a kernel client */
struct snd_seq_client *snd_seq_kernel_client_get(int id)
{
return snd_seq_client_use_ptr(id);
}
EXPORT_SYMBOL_GPL(snd_seq_kernel_client_get);
/* put a sequencer client object; for internal use from a kernel client */
void snd_seq_kernel_client_put(struct snd_seq_client *cptr)
{
if (cptr)
snd_seq_client_unref(cptr);
}
EXPORT_SYMBOL_GPL(snd_seq_kernel_client_put);
/*---------------------------------------------------------------------------*/
#ifdef CONFIG_SND_PROC_FS
/*
* /proc interface
*/
static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer,
struct snd_seq_port_subs_info *group,
int is_src, char *msg)
{
struct list_head *p;
struct snd_seq_subscribers *s;
int count = 0;
guard(rwsem_read)(&group->list_mutex);
if (list_empty(&group->list_head))
return;
snd_iprintf(buffer, msg);
list_for_each(p, &group->list_head) {
if (is_src)
s = list_entry(p, struct snd_seq_subscribers, src_list);
else
s = list_entry(p, struct snd_seq_subscribers, dest_list);
if (count++)
snd_iprintf(buffer, ", ");
snd_iprintf(buffer, "%d:%d",
is_src ? s->info.dest.client : s->info.sender.client,
is_src ? s->info.dest.port : s->info.sender.port);
if (s->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP)
snd_iprintf(buffer, "[%c:%d]", ((s->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL) ? 'r' : 't'), s->info.queue);
if (group->exclusive)
snd_iprintf(buffer, "[ex]");
}
snd_iprintf(buffer, "\n");
}
#define FLAG_PERM_RD(perm) ((perm) & SNDRV_SEQ_PORT_CAP_READ ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_READ ? 'R' : 'r') : '-')
#define FLAG_PERM_WR(perm) ((perm) & SNDRV_SEQ_PORT_CAP_WRITE ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_WRITE ? 'W' : 'w') : '-')
#define FLAG_PERM_EX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_NO_EXPORT ? '-' : 'e')
#define FLAG_PERM_DUPLEX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_DUPLEX ? 'X' : '-')
static const char *port_direction_name(unsigned char dir)
{
static const char *names[4] = {
"-", "In", "Out", "In/Out"
};
if (dir > SNDRV_SEQ_PORT_DIR_BIDIRECTION)
return "Invalid";
return names[dir];
}
static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer,
struct snd_seq_client *client)
{
struct snd_seq_client_port *p;
guard(mutex)(&client->ports_mutex);
list_for_each_entry(p, &client->ports_list_head, list) {
if (p->capability & SNDRV_SEQ_PORT_CAP_INACTIVE)
continue;
snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c) [%s]",
p->addr.port, p->name,
FLAG_PERM_RD(p->capability),
FLAG_PERM_WR(p->capability),
FLAG_PERM_EX(p->capability),
FLAG_PERM_DUPLEX(p->capability),
port_direction_name(p->direction));
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
if (snd_seq_client_is_midi2(client) && p->is_midi1)
snd_iprintf(buffer, " [MIDI1]");
#endif
snd_iprintf(buffer, "\n");
snd_seq_info_dump_subscribers(buffer, &p->c_src, 1, " Connecting To: ");
snd_seq_info_dump_subscribers(buffer, &p->c_dest, 0, " Connected From: ");
}
}
static const char *midi_version_string(unsigned int version)
{
switch (version) {
case SNDRV_SEQ_CLIENT_LEGACY_MIDI:
return "Legacy";
case SNDRV_SEQ_CLIENT_UMP_MIDI_1_0:
return "UMP MIDI1";
case SNDRV_SEQ_CLIENT_UMP_MIDI_2_0:
return "UMP MIDI2";
default:
return "Unknown";
}
}
/* exported to seq_info.c */
void snd_seq_info_clients_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
int c;
snd_iprintf(buffer, "Client info\n");
snd_iprintf(buffer, " cur clients : %d\n", client_usage.cur);
snd_iprintf(buffer, " peak clients : %d\n", client_usage.peak);
snd_iprintf(buffer, " max clients : %d\n", SNDRV_SEQ_MAX_CLIENTS);
snd_iprintf(buffer, "\n");
/* list the client table */
for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) {
struct snd_seq_client *client __free(snd_seq_client) =
client_load_and_use_ptr(c);
if (client == NULL)
continue;
if (client->type == NO_CLIENT)
continue;
guard(mutex)(&client->ioctl_mutex);
snd_iprintf(buffer, "Client %3d : \"%s\" [%s %s]\n",
c, client->name,
client->type == USER_CLIENT ? "User" : "Kernel",
midi_version_string(client->midi_version));
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
dump_ump_info(buffer, client);
#endif
snd_seq_info_dump_ports(buffer, client);
if (snd_seq_write_pool_allocated(client)) {
snd_iprintf(buffer, " Output pool :\n");
snd_seq_info_pool(buffer, client->pool, " ");
}
if (client->type == USER_CLIENT && client->data.user.fifo &&
client->data.user.fifo->pool) {
snd_iprintf(buffer, " Input pool :\n");
snd_seq_info_pool(buffer, client->data.user.fifo->pool, " ");
}
}
}
#endif /* CONFIG_SND_PROC_FS */
/*---------------------------------------------------------------------------*/
/*
* REGISTRATION PART
*/
static const struct file_operations snd_seq_f_ops =
{
.owner = THIS_MODULE,
.read = snd_seq_read,
.write = snd_seq_write,
.open = snd_seq_open,
.release = snd_seq_release,
.poll = snd_seq_poll,
.unlocked_ioctl = snd_seq_ioctl,
.compat_ioctl = snd_seq_ioctl_compat,
};
static struct device *seq_dev;
/*
* register sequencer device
*/
int __init snd_sequencer_device_init(void)
{
int err;
err = snd_device_alloc(&seq_dev, NULL);
if (err < 0)
return err;
dev_set_name(seq_dev, "seq");
scoped_guard(mutex, ®ister_mutex) {
err = snd_register_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0,
&snd_seq_f_ops, NULL, seq_dev);
}
if (err < 0) {
put_device(seq_dev);
return err;
}
return 0;
}
/*
* unregister sequencer device
*/
void snd_sequencer_device_done(void)
{
snd_unregister_device(seq_dev);
put_device(seq_dev);
}
]
|
| 34/2 |
2026/05/20 05:09 |
llm |
expert |
0m
Model:
Tokens:
input: 50456
output: 58
thoughts: 165
Thoughts: **Deadlock Detection: A Sequence Analysis**
Okay, let me think this through. I see `snd_seq_info_clients_read` is grabbing `client->ioctl_mutex` using the `guard(mutex)` construct. Makes sense, that's typical for protecting an I/O path. And then, it calls `snd_seq_info_dump_ports(buffer, client)`. Right. And *that* function, `snd_seq_info_dump_ports`, also uses a `guard(mutex)` to acquire `client->ports_mutex`. Hmm...
Is there a potential for deadlock between `ioctl_mutex` and `ports_mutex` here? That's what I need to figure out immediately. If `snd_seq_info_clients_read` takes `ioctl_mutex` and then `ports_mutex`, what about another thread or process acquiring `ports_mutex` first and *then* trying to get `ioctl_mutex`? That's the classic recipe for a deadlock scenario. My expert intuition is screaming "check the lock acquisition order!"
Alright, time to trace where else `ports_mutex` is being held. We need to identify all the points where `ports_mutex` is acquired and see if the locking order is consistent.
|
| 35/2 |
2026/05/20 05:09 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeSnippetLines:0 Name:snd_seq_client::ports_mutex SourceTreePrefix:]
Results: map[References:[map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:seq_create_client1 SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:216] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_create_port SourceFile:sound/core/seq/seq_ports.c SourceLine:145] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_create_port SourceFile:sound/core/seq/seq_ports.c SourceLine:145] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_delete_all_ports SourceFile:sound/core/seq/seq_ports.c SourceLine:294] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_delete_all_ports SourceFile:sound/core/seq/seq_ports.c SourceLine:294] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_delete_port SourceFile:sound/core/seq/seq_ports.c SourceLine:267] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_delete_port SourceFile:sound/core/seq/seq_ports.c SourceLine:267] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_info_dump_ports SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:2552] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:snd_seq_info_dump_ports SourceFile:sound/core/seq/seq_clientmgr.c SourceLine:2552]] TruncatedOutput:false]
|
| 36/2 |
2026/05/20 05:09 |
llm |
expert |
0m
Model:
Tokens:
input: 51513
output: 47
thoughts: 43
Thoughts: Okay, here's my take on those functions, putting myself in the expert's shoes:
**Sequencer Port Management: My Initial Assessment**
Alright, let's see. The functions mentioned β `snd_seq_create_port`, `snd_seq_delete_all_ports`, and `snd_seq_delete_port` β scream "sequencer port management" to me. I immediately think: these are the building blocks for how we, as developers, interact with the ALSA sequencer to define, use, and ultimately clean up our connections.
The order suggests a logical workflow. I'd *expect* to create a port first, then have options for deleting a specific port or clearing the whole deck so to speak. Now, I should start by looking at `snd_seq_create_port`. This is the core, right? Understanding the parameters and return values of that function will tell me *everything* I need to know about setting up my application's connection point into the sequencer's world. I want to see if the interface will allow me to set up port name, type (input, output, duplex?), MIDI interface, any available options to define the port, and how I'm going to manage these with my own application. From there, I'll be able to decide how to use the other functions to manage ports and clean them up when I'm done.
|
| 37/2 |
2026/05/20 05:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_ports.c IncludeLines:true Name:snd_seq_create_port]
Results: map[SourceCode: 111: /* create a port, port number or a negative error code is returned
112: * the caller needs to unref the port via snd_seq_port_unlock() appropriately
113: */
114: int snd_seq_create_port(struct snd_seq_client *client, int port,
115: struct snd_seq_client_port **port_ret)
116: {
117: struct snd_seq_client_port *new_port, *p;
118: int num;
119:
120: *port_ret = NULL;
121:
122: /* sanity check */
123: if (snd_BUG_ON(!client))
124: return -EINVAL;
125:
126: if (client->num_ports >= SNDRV_SEQ_MAX_PORTS) {
127: pr_warn("ALSA: seq: too many ports for client %d\n", client->number);
128: return -EINVAL;
129: }
130:
131: /* create a new port */
132: new_port = kzalloc_obj(*new_port);
133: if (!new_port)
134: return -ENOMEM; /* failure, out of memory */
135: /* init port data */
136: new_port->addr.client = client->number;
137: new_port->addr.port = -1;
138: new_port->owner = THIS_MODULE;
139: snd_use_lock_init(&new_port->use_lock);
140: port_subs_info_init(&new_port->c_src);
141: port_subs_info_init(&new_port->c_dest);
142: snd_use_lock_use(&new_port->use_lock);
143:
144: num = max(port, 0);
145: guard(mutex)(&client->ports_mutex);
146: guard(write_lock_irq)(&client->ports_lock);
147: list_for_each_entry(p, &client->ports_list_head, list) {
148: if (p->addr.port == port) {
149: kfree(new_port);
150: return -EBUSY;
151: }
152: if (p->addr.port > num)
153: break;
154: if (port < 0) /* auto-probe mode */
155: num = p->addr.port + 1;
156: }
157: /* insert the new port */
158: list_add_tail(&new_port->list, &p->list);
159: client->num_ports++;
160: new_port->addr.port = num; /* store the port number in the port */
161: sprintf(new_port->name, "port-%d", num);
162: *port_ret = new_port;
163:
164: return num;
165: }
SourceFile:sound/core/seq/seq_ports.c]
|
| 38/2 |
2026/05/20 05:09 |
llm |
expert |
0m
Model:
Tokens:
input: 52812
output: 50
thoughts: 39
Thoughts: Here's my analysis of the provided snippet, framed as my own thought process:
**Diving into the ALSA Sequence Port Creation**
Okay, so I see `snd_seq_create_port` is the ultimate function being called to create a port in the ALSA sequencer system, and that call is originating from `snd_seq_ioctl_create_port`. Right. Now, given my expertise, I immediately want to dig deeper into `snd_seq_ioctl_create_port`. I need to understand the *why* and *how* of that interface. My intuition tells me `snd_seq_ioctl_create_port` is the user-space entry point, the system call, that actually *requests* the port creation, and it likely handles parameter validation, permissions checks, and other housekeeping tasks before finally delegating the real work to `snd_seq_create_port`. I want to see what happens in that system call, so I'm going to follow the trail. I'm keen to see if it makes use of any of the ALSA core interfaces, like mutexes or the like. That would be useful background for future projects.
|
| 39/2 |
2026/05/20 05:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:snd_seq_ioctl_create_port]
Results: map[SourceCode:1265: /*
1266: * CREATE PORT ioctl()
1267: */
1268: static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg)
1269: {
1270: struct snd_seq_port_info *info = arg;
1271: struct snd_seq_client_port *port;
1272: struct snd_seq_port_callback *callback;
1273: int port_idx, err;
1274:
1275: /* it is not allowed to create the port for an another client */
1276: if (info->addr.client != client->number)
1277: return -EPERM;
1278: if (client->type == USER_CLIENT && info->kernel)
1279: return -EINVAL;
1280: if ((info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT) &&
1281: client->ump_endpoint_port >= 0)
1282: return -EBUSY;
1283:
1284: if (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT)
1285: port_idx = info->addr.port;
1286: else
1287: port_idx = -1;
1288: if (port_idx >= SNDRV_SEQ_ADDRESS_UNKNOWN)
1289: return -EINVAL;
1290: err = snd_seq_create_port(client, port_idx, &port);
1291: if (err < 0)
1292: return err;
1293:
1294: if (client->type == KERNEL_CLIENT) {
1295: callback = info->kernel;
1296: if (callback) {
1297: if (callback->owner)
1298: port->owner = callback->owner;
1299: port->private_data = callback->private_data;
1300: port->private_free = callback->private_free;
1301: port->event_input = callback->event_input;
1302: port->c_src.open = callback->subscribe;
1303: port->c_src.close = callback->unsubscribe;
1304: port->c_dest.open = callback->use;
1305: port->c_dest.close = callback->unuse;
1306: }
1307: }
1308:
1309: info->addr = port->addr;
1310:
1311: snd_seq_set_port_info(port, info);
1312: if (info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT)
1313: client->ump_endpoint_port = port->addr.port;
1314: snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port);
1315: snd_seq_port_unlock(port);
1316:
1317: return 0;
1318: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 40/2 |
2026/05/20 05:09 |
llm |
expert |
0m
Model:
Tokens:
input: 54034
output: 49
thoughts: 300
Thoughts: **Analyzing Potential Deadlock in ALSA Sequencer Port Management**
Okay, let's break this down. I'm looking at the ALSA sequencer code, specifically concerning mutex acquisition, and trying to spot a potential deadlock scenario. I'm focusing on the interaction between client IOCTLs and port management.
First, I see `snd_seq_ioctl_create_port` called from `snd_seq_ioctl`. Makes sense. Now, `snd_seq_ioctl` has a `scoped_guard` that uses `client->ioctl_mutex`, so this is where the critical section starts. It means the execution of the `handler->func` within `snd_seq_ioctl` is protected.
Specifically, that means `snd_seq_ioctl_create_port` will be called with `ioctl_mutex` held. And from the code, I see that this then calls `snd_seq_create_port`, which acquires `ports_mutex`. Thus, we have a clear lock order: `ioctl_mutex` *before* `ports_mutex`.
Now, I'm checking the other side of this. In `snd_seq_info_clients_read`, there's also an `ioctl_mutex` guard, followed by a call to `snd_seq_info_dump_ports`. And I remember from the code that `snd_seq_info_dump_ports` itself acquires `ports_mutex`. So, `snd_seq_info_clients_read` effectively acquires locks in the same order: `ioctl_mutex` *then* `ports_mutex`. That's good; it's consistent.
But I'm not quite satisfied. I need to make sure this lock order is consistent *everywhere*. The key is to see if any other function could acquire the locks in the *reverse* order. I need to make sure no other code path reverses this order and possibly causes a deadlock.
So now, let's check `snd_seq_delete_all_ports`... I'll need to look at that next to see if there's any hint of a reversed lock order.
|
| 41/2 |
2026/05/20 05:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_ports.c IncludeLines:true Name:snd_seq_delete_all_ports]
Results: map[SourceCode: 285: /* delete the all ports belonging to the given client */
286: int snd_seq_delete_all_ports(struct snd_seq_client *client)
287: {
288: struct list_head deleted_list;
289: struct snd_seq_client_port *port, *tmp;
290:
291: /* move the port list to deleted_list, and
292: * clear the port list in the client data.
293: */
294: guard(mutex)(&client->ports_mutex);
295: scoped_guard(write_lock_irq, &client->ports_lock) {
296: if (!list_empty(&client->ports_list_head)) {
297: list_add(&deleted_list, &client->ports_list_head);
298: list_del_init(&client->ports_list_head);
299: } else {
300: INIT_LIST_HEAD(&deleted_list);
301: }
302: client->num_ports = 0;
303: }
304:
305: /* remove each port in deleted_list */
306: list_for_each_entry_safe(port, tmp, &deleted_list, list) {
307: list_del(&port->list);
308: snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port);
309: port_delete(client, port);
310: }
311: return 0;
312: }
SourceFile:sound/core/seq/seq_ports.c]
|
| 42/2 |
2026/05/20 05:10 |
llm |
expert |
0m
Model:
Tokens:
input: 55318
output: 47
thoughts: 22
Thoughts: Here's a summary of my understanding:
**Analyzing the `snd_seq_delete_all_ports` Call**
Okay, so I'm looking at `snd_seq_delete_all_ports`, and it's being called from `seq_free_client1`. This is immediately interesting because `seq_free_client1` clearly deals with freeing up resources associated with a sequence client, which I would expect to clean up a lot of things. The fact that `snd_seq_delete_all_ports` is part of *that* routine strongly suggests it's a critical step in completely releasing all ports associated with a client. I'm going to want to verify that the port deletion is complete and correct so there are no lingering resource issues, no dangling pointers, or memory leaks after a client disconnects. Iβll need to trace back from `snd_seq_delete_all_ports` to confirm that it's handling port cleanup appropriately based on the client ID. The implications are that if this call *fails* or isn't robust, then we could have orphan ports lingering in the system, which is a significant problem. I'll need to dig into the details of the arguments and the logic inside both functions to ensure proper sequencing and avoid potential race conditions, or similar problems.
|
| 43/2 |
2026/05/20 05:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:seq_free_client1]
Results: map[SourceCode: 246: static int seq_free_client1(struct snd_seq_client *client)
247: {
248: if (!client)
249: return 0;
250: scoped_guard(spinlock_irq, &clients_lock) {
251: clienttablock[client->number] = 1;
252: clienttab[client->number] = NULL;
253: }
254: snd_seq_delete_all_ports(client);
255: snd_seq_queue_client_leave(client->number);
256: snd_use_lock_sync(&client->use_lock);
257: if (client->pool)
258: snd_seq_pool_delete(&client->pool);
259: scoped_guard(spinlock_irq, &clients_lock) {
260: clienttablock[client->number] = 0;
261: }
262: return 0;
263: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 44/2 |
2026/05/20 05:10 |
llm |
expert |
0m
Model:
Tokens:
input: 55980
output: 46
thoughts: 18
Thoughts: Okay, here's my interpretation of that.
**Analyzing the Function Call Relationship**
Right, so I'm looking at this code and immediately I see a specific pattern emerging. We have `seq_free_client1` being called directly from `seq_free_client`. This tells me a few things, based on my experience.
First, I'm expecting `seq_free_client` to be the primary function responsible for releasing resources associated with a client, likely some kind of connection or data structure. The fact that it calls `seq_free_client1` suggests a delegation of responsibility. It's almost certain `seq_free_client` is acting as a manager or coordinator of the client's resource release process.
Second, `seq_free_client1` is likely a helper function. It's probably handling a specific sub-task within the overall cleanup operation. This separation implies a modular design, which is good. It potentially allows for easier maintenance, testing, and even future expansion, should we need to add other free functions later. It also helps to keep the original function cleaner and easier to read.
Finally, I'm already starting to think about what the relationship might be. Is `seq_free_client1` freeing the *main* client resource? Or is it dealing with a supporting structure *held* by the client? The name `seq_free_client1` has an odd suffix and I wonder whether the system will utilize `seq_free_client2`, `seq_free_client3`, etc. It's something I will be watching as I explore the rest of the code.
In short, it's a typical delegation scenario and I'll need to understand the specifics of what resources are being freed by each function to get a complete picture of the client's resource management strategy.
|
| 45/2 |
2026/05/20 05:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:seq_free_client]
Results: map[SourceCode: 266: static void seq_free_client(struct snd_seq_client * client)
267: {
268: scoped_guard(mutex, ®ister_mutex) {
269: switch (client->type) {
270: case NO_CLIENT:
271: pr_warn("ALSA: seq: Trying to free unused client %d\n",
272: client->number);
273: break;
274: case USER_CLIENT:
275: case KERNEL_CLIENT:
276: seq_free_client1(client);
277: usage_free(&client_usage, 1);
278: break;
279:
280: default:
281: pr_err("ALSA: seq: Trying to free client %d with undefined type = %d\n",
282: client->number, client->type);
283: }
284: }
285:
286: snd_seq_system_client_ev_client_exit(client->number);
287: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 46/2 |
2026/05/20 05:10 |
llm |
expert |
0m
Model:
Tokens:
input: 56777
output: 46
thoughts: 17
Thoughts: Here's a summary of my understanding:
**Dissecting the Sequence Release Process**
Okay, so I'm looking at `snd_seq_release`, and it's clear that it's the gateway for freeing up resources associated with a sequence client. The key takeaway is the function `seq_free_client` is directly called from within `snd_seq_release`. This is important, because it tells me where the actual resource deallocation takes place. Therefore, `seq_free_client` is a critical function within the sequence release process, likely responsible for cleaning up the data structures and resources that were allocated when the client was initialized or connected. I'd need to dive deeper into `seq_free_client`'s implementation to see exactly what gets freed (memory, file descriptors, etc.), and how it handles potential race conditions or dependencies between different parts of the client's state. Understanding this call is paramount to understanding how the sequence client resources are correctly and safely handled upon disconnection or shutdown.
|
| 47/2 |
2026/05/20 05:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:snd_seq_release]
Results: map[SourceCode: 348: /* delete a user client */
349: static int snd_seq_release(struct inode *inode, struct file *file)
350: {
351: struct snd_seq_client *client = file->private_data;
352:
353: if (client) {
354: seq_free_client(client);
355: if (client->data.user.fifo)
356: snd_seq_fifo_delete(&client->data.user.fifo);
357: #if IS_ENABLED(CONFIG_SND_SEQ_UMP)
358: free_ump_info(client);
359: #endif
360: put_pid(client->data.user.owner);
361: kfree(client);
362: }
363:
364: return 0;
365: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 48/2 |
2026/05/20 05:10 |
llm |
expert |
0m
Model:
Tokens:
input: 57340
output: 50
thoughts: 859
Thoughts: **Analyzing a Potential Deadlock in ALSA's Sequencer Implementation**
Okay, so I'm looking at this ALSA sequencer issue, and something feels off. I was tracing through the code, and realized `snd_seq_release` doesn't grab the `ioctl_mutex`. But, `snd_seq_write` *does* grab the `ioctl_mutex`. `snd_seq_write` calls `snd_seq_client_enqueue_event`, which in turn calls `snd_seq_event_dup`, which finally calls `snd_seq_cell_alloc`. Now, `snd_seq_cell_alloc` is the problematic function here because it can sleep:
```c
while (pool->free == NULL && ! nonblock && ! pool->closing) {
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&pool->output_sleep, &wait);
spin_unlock_irqrestore(&pool->lock, flags);
if (mutexp)
mutex_unlock(mutexp);
schedule();
if (mutexp)
mutex_lock(mutexp);
spin_lock_irqsave(&pool->lock, flags);
remove_wait_queue(&pool->output_sleep, &wait);
```
The key observation is that if `snd_seq_cell_alloc` sleeps, it unlocks `mutexp` (which is `ioctl_mutex`), and *then* re-locks it when it wakes up. Now, what if it gets blocked in that `mutex_lock(mutexp)` after waking up? Yes, it *can* be blocked if another task already has acquired `ioctl_mutex` while it was sleeping.
The problem here is that the hung task is reported as being blocked in `snd_seq_info_clients_read` on `mutex_lock(&client->ioctl_mutex)`. If `snd_seq_info_clients_read` is blocked on `ioctl_mutex`, then it *should* mean another task holds `ioctl_mutex`. So, who's the culprit? I went back to the lockdep output to double-check:
```
2 locks held by syz.5.990/9676:
#0: ffff888057b47878 (&p->lock){+.+.}-{4:4}, at: seq_read_iter+0xb7/0xe10 fs/seq_file.c:183
#1: ffff888028f9c9c0 (&client->ioctl_mutex){+.+.}-{4:4}, at: class_mutex_constructor include/linux/mutex.h:253 [inline]
#1: ffff888028f9c9c0 (&client->ioctl_mutex){+.+.}-{4:4}, at: snd_seq_info_clients_read+0x14a/0x820 sound/core/seq/seq_clientmgr.c:2609
```
And there it is! Lockdep indicates that `syz.5.990/9676` is holding `&client->ioctl_mutex` *and* is blocked on the *same* `&client->ioctl_mutex`! That makes no sense, how can a task hold a mutex and be blocked on the same mutex? I have seen this before. It is possible in a re-entrant locking situation.
I reread the code and notice that `snd_seq_info_clients_read` is looping over clients:
```c
for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) {
struct snd_seq_client *client __free(snd_seq_client) =
client_load_and_use_ptr(c);
...
guard(mutex)(&client->ioctl_mutex);
```
Could it be that `client_load_and_use_ptr(c)` returns the *same* client for *different* values of `c`? No, that's unlikely since `clienttab` should guarantee different clients. But wait... what if `client_load_and_use_ptr(c)` returns a client, and then, for `c+1`, it returns the *same* client? That *could* explain it. I better check `client_load_and_use_ptr`.
|
| 49/2 |
2026/05/20 05:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:client_load_and_use_ptr]
Results: map[SourceCode: 169: /* get snd_seq_client object for the given id;
170: * if not found, retry after loading the modules
171: */
172: static struct snd_seq_client *client_load_and_use_ptr(int clientid)
173: {
174: return client_use_ptr(clientid, IS_ENABLED(CONFIG_MODULES));
175: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 50/2 |
2026/05/20 05:10 |
llm |
expert |
0m
Model:
Tokens:
input: 59380
output: 46
thoughts: 11
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, and written as if they were my own:
**Analyzing `client_use_ptr`**
Right, let's dive in. I need to get a handle on this `client_use_ptr` variable. Before I go any further, I need to understand how this piece of code is using this pointer, what it points to, and most importantly, how its lifetime is managed. Is it a raw pointer, a smart pointer, what's going on with the memory ownership? Given the name, I'm expecting this to be fairly fundamental to the client's operation, so potential issues here could be widespread. I want to check for potential dangling pointer situations, memory leaks, or double-free scenarios, the usual suspects. Iβm thinking Iβll trace the places where the pointer is assigned, where it is used, and where it is potentially deleted. I'll need to look for any potential thread safety issues if the pointer might be accessed concurrently. This is the starting point for a deeper dive. Let's see what we've got.
|
| 51/2 |
2026/05/20 05:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:client_use_ptr]
Results: map[SourceCode: 109: static struct snd_seq_client *client_use_ptr(int clientid, bool load_module)
110: {
111: struct snd_seq_client *client;
112:
113: if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) {
114: pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n",
115: clientid);
116: return NULL;
117: }
118: scoped_guard(spinlock_irqsave, &clients_lock) {
119: client = clientptr(clientid);
120: if (client)
121: return snd_seq_client_ref(client);
122: if (clienttablock[clientid])
123: return NULL;
124: }
125: #ifdef CONFIG_MODULES
126: if (load_module) {
127: static DECLARE_BITMAP(client_requested, SNDRV_SEQ_GLOBAL_CLIENTS);
128: static DECLARE_BITMAP(card_requested, SNDRV_CARDS);
129:
130: if (clientid < SNDRV_SEQ_GLOBAL_CLIENTS) {
131: int idx;
132:
133: if (!test_and_set_bit(clientid, client_requested)) {
134: for (idx = 0; idx < 15; idx++) {
135: if (seq_client_load[idx] < 0)
136: break;
137: if (seq_client_load[idx] == clientid) {
138: request_module("snd-seq-client-%i",
139: clientid);
140: break;
141: }
142: }
143: }
144: } else if (clientid < SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN) {
145: int card = (clientid - SNDRV_SEQ_GLOBAL_CLIENTS) /
146: SNDRV_SEQ_CLIENTS_PER_CARD;
147: if (card < snd_ecards_limit) {
148: if (!test_and_set_bit(card, card_requested))
149: snd_request_card(card);
150: snd_seq_device_load_drivers();
151: }
152: }
153: scoped_guard(spinlock_irqsave, &clients_lock) {
154: client = clientptr(clientid);
155: if (client)
156: return snd_seq_client_ref(client);
157: }
158: }
159: #endif
160: return NULL;
161: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 52/2 |
2026/05/20 05:10 |
llm |
expert |
0m
Model:
Tokens:
input: 60518
output: 47
thoughts: 69
Thoughts: **Analyzing Client Pointer Behavior**
Okay, so `clientptr` is essentially just a shortcut β it's returning elements from the `clienttab` array based on the `clientid`, right? Seems straightforward: `clientptr` is giving me a different client for each iteration of `c`. But wait a minute... a red flag is going up in my expert brain. What if the array indices `c` and `c+1` *don't* return distinct clients? What if `clienttab[c]` and `clienttab[c+1]` actually point to the *exact same* client object? That could lead to some seriously unexpected behavior down the line. We need to confirm if this is even possible, which means I need to dig into the client creation process to see how this `clienttab` is populated in the first place. Let's start by scrutinizing `seq_create_client1` β that function is the key to understanding this.
|
| 53/2 |
2026/05/20 05:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_clientmgr.c IncludeLines:true Name:seq_create_client1]
Results: map[SourceCode: 199: static struct snd_seq_client *seq_create_client1(int client_index, int poolsize)
200: {
201: int c;
202: struct snd_seq_client *client;
203:
204: /* init client data */
205: client = kzalloc(sizeof(*client), GFP_KERNEL);
206: if (client == NULL)
207: return NULL;
208: client->pool = snd_seq_pool_new(poolsize);
209: if (client->pool == NULL) {
210: kfree(client);
211: return NULL;
212: }
213: client->type = NO_CLIENT;
214: snd_use_lock_init(&client->use_lock);
215: rwlock_init(&client->ports_lock);
216: mutex_init(&client->ports_mutex);
217: INIT_LIST_HEAD(&client->ports_list_head);
218: mutex_init(&client->ioctl_mutex);
219: client->ump_endpoint_port = -1;
220:
221: /* find free slot in the client table */
222: scoped_guard(spinlock_irq, &clients_lock) {
223: if (client_index < 0) {
224: for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN;
225: c < SNDRV_SEQ_MAX_CLIENTS;
226: c++) {
227: if (clienttab[c] || clienttablock[c])
228: continue;
229: clienttab[client->number = c] = client;
230: return client;
231: }
232: } else {
233: if (clienttab[client_index] == NULL && !clienttablock[client_index]) {
234: clienttab[client->number = client_index] = client;
235: return client;
236: }
237: }
238: }
239:
240: snd_seq_pool_delete(&client->pool);
241: kfree(client);
242: return NULL; /* no free slot found or busy, return failure code */
243: }
SourceFile:sound/core/seq/seq_clientmgr.c]
|
| 54/2 |
2026/05/20 05:10 |
llm |
expert |
0m
Model:
Tokens:
input: 61539
output: 37
thoughts: 2379
Thoughts: **Analyzing a Kernel Lock Depletion in `snd_seq_info_clients_read`**
Okay, so I'm looking at this lockdep output and a hung task report, and something's not adding up. We have this process, `syz.5.990/9676`, and it seems to be blocked. The initial thought was that there are unique clients. But then, it looks like it's holding `&client->ioctl_mutex` and simultaneously blocked on the *same* mutex. That sounds impossible. The lockdep output confirms it's the *same* lock, `&client->ioctl_mutex`, appearing twice with different stack traces, one indicating acquisition and the other, where the task is blocked. The second entry shows the lock at `snd_seq_info_clients_read`.
This is weird because lockdep should only show locks the task actually *holds*, not ones it's waiting for. The hung task report says the task is blocked in `mutex_lock` in `snd_seq_info_clients_read`, which means it's *not* acquired the mutex yet.
Wait a second, maybe it's holding one `ioctl_mutex` for client A and blocked on another `ioctl_mutex` for client B? `snd_seq_info_clients_read` iterates through clients. It locks one client's mutex, maybe then releases it, and tries to lock another client's.
However, lockdep *shouldn't* show it holding the lock for one client while blocked on *another*. The critical part is the loop in `snd_seq_info_clients_read`. The use of `guard(mutex)(&client->ioctl_mutex)` in the loop is the key, and I know that expands to a locally scoped `class_mutex_t` variable.
If the declaration is in a loop, what's the scope? In C89, variables are declared at the beginning of the block. In C99, variables are scoped to the block where they are defined. However, `guard(mutex)` expands to a declaration with a destructor, `class_mutex_t __UNIQUE_ID(guard) __cleanup(class_mutex_destructor) = class_mutex_constructor(&client->ioctl_mutex);`. It means that the scope of this guard is from the point of declaration to the end of the ENCLOSING block, which is the loop body.
So, in the for loop, each `client` variable will have its own guard. There's no early `continue` or `break` after the guard; it should release at the end of the loop body!
But then the question is, why is it blocked on the same lock it supposedly *holds*? Is it possible that the loop body is not the enclosing block? It *is* the enclosing block! Then it means it's *definitely* blocked on the *same* lock. I see!
The crucial point is that line 2609, which is a `guard(mutex)` of `client->ioctl_mutex`! It must be the `ioctl_mutex` of the same client! If the task is blocked on the `ioctl_mutex`, it should be the same `ioctl_mutex`. Could it be? Is it possible that `client_load_and_use_ptr(c)` returns the same client for different loop iterations? We have to check the loop's logic again.
Looking again at `client_load_and_use_ptr`, it calls `clientptr(clientid)`, which just returns `clienttab[clientid]`. So, *can* `clienttab[c]` and `clienttab[c+1]` be the same pointer? Maybe, if someone corrupted `clienttab`.
But what if `SNDRV_SEQ_MAX_CLIENTS` is large, and the loop is doing something weird? But the `guard(mutex)` scope is definitely not the whole function, it's the loop body! The lockdep confirms it: blocked, while holding the same mutex, in the loop!
|
| 55/2 |
2026/05/20 05:11 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:include/linux/cleanup.h IncludeLines:true Name:guard]
Results: map[SourceCode: SourceFile:]
|
| 56/2 |
2026/05/20 05:11 |
llm |
expert |
0m
Model:
Tokens:
input: 64849
output: 17
thoughts: 19
Thoughts: Okay, here's my thought process summarized as requested:
**Investigating a `#define guard` in `include/linux/cleanup.h`**
Alright, let's dive into this. I'm thinking about the `#define guard` within `include/linux/cleanup.h`. My immediate instinct is to *grep* for it. Itβs a classic defensive programming technique. I want to understand how it's being used, its context, and what's being protected by this particular guard. Is it ensuring that the header file is included only once during compilation? Is there anything noteworthy about *this* particular instance in the `cleanup.h` file, or is it behaving as expected? I need to analyze its usage within the surrounding code to determine its purpose and efficiency. Let's see what the grep reveals.
|
| 57/2 |
2026/05/20 05:11 |
tool |
grepper |
0m
Args: map[Expression:#define guard]
Results: map[Output:drivers/hid/bpf/progs/hid_bpf_helpers.h=125=struct __useless_struct_to_allow_trailing_semicolon__
drivers/hid/bpf/progs/hid_bpf_helpers.h-126-
drivers/hid/bpf/progs/hid_bpf_helpers.h:127:#define guard(_name) \
drivers/hid/bpf/progs/hid_bpf_helpers.h-128- struct _name##_guard COMBINE(guard, __LINE__) __attribute__((cleanup(_name##_guard_cleanup))) = \
--
include/linux/cleanup.h=357=static __maybe_unused const bool class_##_name##_is_conditional = _is_cond
--
include/linux/cleanup.h-420-
include/linux/cleanup.h:421:#define guard(_name) \
include/linux/cleanup.h-422- CLASS(_name, __UNIQUE_ID(guard))
--
sound/hda/common/controller.c-35-#ifdef CONFIG_SND_HDA_DSP_LOADER
sound/hda/common/controller.c:36:#define guard_dsp_lock(dev) guard(snd_hdac_dsp_lock)(azx_stream(dev))
sound/hda/common/controller.c-37-#else
sound/hda/common/controller.c:38:#define guard_dsp_lock(dev) do {} while (0)
sound/hda/common/controller.c-39-#endif
--
tools/testing/memblock/linux/mutex.h=7=static inline void dummy_mutex_guard(int *name)
--
tools/testing/memblock/linux/mutex.h-10-
tools/testing/memblock/linux/mutex.h:11:#define guard(mutex) \
tools/testing/memblock/linux/mutex.h-12- dummy_##mutex##_guard
]
|
| 58/2 |
2026/05/20 05:11 |
llm |
expert |
0m
Model:
Tokens:
input: 65551
output: 17
thoughts: 40
Thoughts: **Deconstructing the `guard` Macro**
Okay, so I'm looking at this `guard(_name)` thing and immediately my brain jumps to thinking about macro expansion. It's clearly a preprocessor macro, and I suspect it's doing something clever with unique identifiers to avoid collisions, probably related to resource management or cleanup. My gut tells me it's likely defining a class, given the name `CLASS` is used in the expansion.
Right, so to understand the "how" and the "why" of this, my next step is obvious: I need to see the definition. Let's grep for `#define CLASS` within the kernel's include files. Specifically, I'll start with `include/linux/cleanup.h`. That's a likely place to find this, given the obvious association with resource cleanup. Let's see what `CLASS` resolves to!
|
| 59/2 |
2026/05/20 05:11 |
tool |
grepper |
0m
Args: map[Expression:#define CLASS]
Results: map[Output:arch/powerpc/include/asm/spu.h=442=struct spu_priv1 {
--
arch/powerpc/include/asm/spu.h-467- u64 int_mask_RW[3]; /* 0x100 */
arch/powerpc/include/asm/spu.h:468:#define CLASS0_ENABLE_DMA_ALIGNMENT_INTR 0x1L
arch/powerpc/include/asm/spu.h:469:#define CLASS0_ENABLE_INVALID_DMA_COMMAND_INTR 0x2L
arch/powerpc/include/asm/spu.h:470:#define CLASS0_ENABLE_SPU_ERROR_INTR 0x4L
arch/powerpc/include/asm/spu.h:471:#define CLASS0_ENABLE_MFC_FIR_INTR 0x8L
arch/powerpc/include/asm/spu.h:472:#define CLASS1_ENABLE_SEGMENT_FAULT_INTR 0x1L
arch/powerpc/include/asm/spu.h:473:#define CLASS1_ENABLE_STORAGE_FAULT_INTR 0x2L
arch/powerpc/include/asm/spu.h:474:#define CLASS1_ENABLE_LS_COMPARE_SUSPEND_ON_GET_INTR 0x4L
arch/powerpc/include/asm/spu.h:475:#define CLASS1_ENABLE_LS_COMPARE_SUSPEND_ON_PUT_INTR 0x8L
arch/powerpc/include/asm/spu.h:476:#define CLASS2_ENABLE_MAILBOX_INTR 0x1L
arch/powerpc/include/asm/spu.h:477:#define CLASS2_ENABLE_SPU_STOP_INTR 0x2L
arch/powerpc/include/asm/spu.h:478:#define CLASS2_ENABLE_SPU_HALT_INTR 0x4L
arch/powerpc/include/asm/spu.h:479:#define CLASS2_ENABLE_SPU_DMA_TAG_GROUP_COMPLETE_INTR 0x8L
arch/powerpc/include/asm/spu.h:480:#define CLASS2_ENABLE_MAILBOX_THRESHOLD_INTR 0x10L
arch/powerpc/include/asm/spu.h-481- u8 pad_0x118_0x140[0x28]; /* 0x118 */
arch/powerpc/include/asm/spu.h-482- u64 int_stat_RW[3]; /* 0x140 */
arch/powerpc/include/asm/spu.h:483:#define CLASS0_DMA_ALIGNMENT_INTR 0x1L
arch/powerpc/include/asm/spu.h:484:#define CLASS0_INVALID_DMA_COMMAND_INTR 0x2L
arch/powerpc/include/asm/spu.h:485:#define CLASS0_SPU_ERROR_INTR 0x4L
arch/powerpc/include/asm/spu.h:486:#define CLASS0_INTR_MASK 0x7L
arch/powerpc/include/asm/spu.h:487:#define CLASS1_SEGMENT_FAULT_INTR 0x1L
arch/powerpc/include/asm/spu.h:488:#define CLASS1_STORAGE_FAULT_INTR 0x2L
arch/powerpc/include/asm/spu.h:489:#define CLASS1_LS_COMPARE_SUSPEND_ON_GET_INTR 0x4L
arch/powerpc/include/asm/spu.h:490:#define CLASS1_LS_COMPARE_SUSPEND_ON_PUT_INTR 0x8L
arch/powerpc/include/asm/spu.h:491:#define CLASS1_INTR_MASK 0xfL
arch/powerpc/include/asm/spu.h:492:#define CLASS2_MAILBOX_INTR 0x1L
arch/powerpc/include/asm/spu.h:493:#define CLASS2_SPU_STOP_INTR 0x2L
arch/powerpc/include/asm/spu.h:494:#define CLASS2_SPU_HALT_INTR 0x4L
arch/powerpc/include/asm/spu.h:495:#define CLASS2_SPU_DMA_TAG_GROUP_COMPLETE_INTR 0x8L
arch/powerpc/include/asm/spu.h:496:#define CLASS2_MAILBOX_THRESHOLD_INTR 0x10L
arch/powerpc/include/asm/spu.h:497:#define CLASS2_INTR_MASK 0x1fL
arch/powerpc/include/asm/spu.h-498- u8 pad_0x158_0x180[0x28]; /* 0x158 */
--
drivers/clk/clk-nspire.c-23-
drivers/clk/clk-nspire.c:24:#define CLASSIC_BASE_SHIFT 16
drivers/clk/clk-nspire.c:25:#define CLASSIC_BASE_MASK 0x1F
drivers/clk/clk-nspire.c-26-
--
drivers/comedi/drivers/comedi_test.c-57-#define DEV_NAME "comedi_testd"
drivers/comedi/drivers/comedi_test.c:58:#define CLASS_NAME "comedi_test"
drivers/comedi/drivers/comedi_test.c-59-
--
drivers/cpufreq/amd_freq_sensitivity.c-26-#define MSR_AMD64_FREQ_SENSITIVITY_REFERENCE 0xc0010081
drivers/cpufreq/amd_freq_sensitivity.c:27:#define CLASS_CODE_SHIFT 56
drivers/cpufreq/amd_freq_sensitivity.c-28-#define POWERSAVE_BIAS_MAX 1000
--
drivers/crypto/caam/desc.h-61-/* General-purpose class selector for all commands */
drivers/crypto/caam/desc.h:62:#define CLASS_SHIFT 25
drivers/crypto/caam/desc.h:63:#define CLASS_MASK (0x03 << CLASS_SHIFT)
drivers/crypto/caam/desc.h-64-
drivers/crypto/caam/desc.h:65:#define CLASS_NONE (0x00 << CLASS_SHIFT)
drivers/crypto/caam/desc.h:66:#define CLASS_1 (0x01 << CLASS_SHIFT)
drivers/crypto/caam/desc.h:67:#define CLASS_2 (0x02 << CLASS_SHIFT)
drivers/crypto/caam/desc.h:68:#define CLASS_BOTH (0x03 << CLASS_SHIFT)
drivers/crypto/caam/desc.h-69-
--
drivers/infiniband/core/sa_query.c=431=static const struct ib_field mcmember_rec_table[] = {
--
drivers/infiniband/core/sa_query.c-509-
drivers/infiniband/core/sa_query.c:510:#define CLASSPORTINFO_REC_FIELD(field) \
drivers/infiniband/core/sa_query.c-511- .struct_offset_bytes = offsetof(struct ib_class_port_info, field), \
--
drivers/media/rc/fintek-cir.h=46=struct fintek_dev {
--
drivers/media/rc/fintek-cir.h-185-
drivers/media/rc/fintek-cir.h:186:#define CLASS_RX_ONLY 0xff
drivers/media/rc/fintek-cir.h:187:#define CLASS_RX_2TX 0x66
drivers/media/rc/fintek-cir.h:188:#define CLASS_RX_1TX 0x33
drivers/media/rc/fintek-cir.h-189-
--
drivers/media/usb/cx231xx/cx231xx-pcb-cfg.h-18-***************************************************************************/
drivers/media/usb/cx231xx/cx231xx-pcb-cfg.h:19:#define CLASS_DEFAULT 0xFF
drivers/media/usb/cx231xx/cx231xx-pcb-cfg.h-20-
--
drivers/net/ethernet/broadcom/bnx2x/bnx2x_fw_defs.h-285-#define MULTICAST_RULES_COUNT 16
drivers/net/ethernet/broadcom/bnx2x/bnx2x_fw_defs.h:286:#define CLASSIFY_RULES_COUNT 16
drivers/net/ethernet/broadcom/bnx2x/bnx2x_fw_defs.h-287-
--
drivers/net/ethernet/cortina/gemini.h=188=union dma_rwptr {
--
drivers/net/ethernet/cortina/gemini.h-252-#define GMAC0_HWTQ00_EOF_INT_BIT BIT(16)
drivers/net/ethernet/cortina/gemini.h:253:#define CLASS_RX_INT_BIT(x) BIT((x + 2))
drivers/net/ethernet/cortina/gemini.h-254-#define DEFAULT_Q1_INT_BIT BIT(1)
--
drivers/net/ethernet/cortina/gemini.h-295-#define GMAC0_STATUS_CHANGE_INT_BIT BIT(16)
drivers/net/ethernet/cortina/gemini.h:296:#define CLASS_RX_FULL_INT_BIT(x) BIT(x + 2)
drivers/net/ethernet/cortina/gemini.h-297-#define HWFQ_EMPTY_INT_BIT BIT(1)
--
drivers/net/ethernet/cortina/gemini.h-314-
drivers/net/ethernet/cortina/gemini.h:315:#define CLASS_RX_FULL_INT_BITS 0xfffc
drivers/net/ethernet/cortina/gemini.h-316-
--
drivers/net/ethernet/sun/niu.h=1507=struct fcram_hash_ipv6 {
--
drivers/net/ethernet/sun/niu.h-2499-
drivers/net/ethernet/sun/niu.h:2500:#define CLASS_CODE_UNRECOG 0x00
drivers/net/ethernet/sun/niu.h:2501:#define CLASS_CODE_DUMMY1 0x01
drivers/net/ethernet/sun/niu.h:2502:#define CLASS_CODE_ETHERTYPE1 0x02
drivers/net/ethernet/sun/niu.h:2503:#define CLASS_CODE_ETHERTYPE2 0x03
drivers/net/ethernet/sun/niu.h:2504:#define CLASS_CODE_USER_PROG1 0x04
drivers/net/ethernet/sun/niu.h:2505:#define CLASS_CODE_USER_PROG2 0x05
drivers/net/ethernet/sun/niu.h:2506:#define CLASS_CODE_USER_PROG3 0x06
drivers/net/ethernet/sun/niu.h:2507:#define CLASS_CODE_USER_PROG4 0x07
drivers/net/ethernet/sun/niu.h:2508:#define CLASS_CODE_TCP_IPV4 0x08
drivers/net/ethernet/sun/niu.h:2509:#define CLASS_CODE_UDP_IPV4 0x09
drivers/net/ethernet/sun/niu.h:2510:#define CLASS_CODE_AH_ESP_IPV4 0x0a
drivers/net/ethernet/sun/niu.h:2511:#define CLASS_CODE_SCTP_IPV4 0x0b
drivers/net/ethernet/sun/niu.h:2512:#define CLASS_CODE_TCP_IPV6 0x0c
drivers/net/ethernet/sun/niu.h:2513:#define CLASS_CODE_UDP_IPV6 0x0d
drivers/net/ethernet/sun/niu.h:2514:#define CLASS_CODE_AH_ESP_IPV6 0x0e
drivers/net/ethernet/sun/niu.h:2515:#define CLASS_CODE_SCTP_IPV6 0x0f
drivers/net/ethernet/sun/niu.h:2516:#define CLASS_CODE_ARP 0x10
drivers/net/ethernet/sun/niu.h:2517:#define CLASS_CODE_RARP 0x11
drivers/net/ethernet/sun/niu.h:2518:#define CLASS_CODE_DUMMY2 0x12
drivers/net/ethernet/sun/niu.h:2519:#define CLASS_CODE_DUMMY3 0x13
drivers/net/ethernet/sun/niu.h:2520:#define CLASS_CODE_DUMMY4 0x14
drivers/net/ethernet/sun/niu.h:2521:#define CLASS_CODE_DUMMY5 0x15
drivers/net/ethernet/sun/niu.h:2522:#define CLASS_CODE_DUMMY6 0x16
drivers/net/ethernet/sun/niu.h:2523:#define CLASS_CODE_DUMMY7 0x17
drivers/net/ethernet/sun/niu.h:2524:#define CLASS_CODE_DUMMY8 0x18
drivers/net/ethernet/sun/niu.h:2525:#define CLASS_CODE_DUMMY9 0x19
drivers/net/ethernet/sun/niu.h:2526:#define CLASS_CODE_DUMMY10 0x1a
drivers/net/ethernet/sun/niu.h:2527:#define CLASS_CODE_DUMMY11 0x1b
drivers/net/ethernet/sun/niu.h:2528:#define CLASS_CODE_DUMMY12 0x1c
drivers/net/ethernet/sun/niu.h:2529:#define CLASS_CODE_DUMMY13 0x1d
drivers/net/ethernet/sun/niu.h:2530:#define CLASS_CODE_DUMMY14 0x1e
drivers/net/ethernet/sun/niu.h:2531:#define CLASS_CODE_DUMMY15 0x1f
drivers/net/ethernet/sun/niu.h-2532-
--
drivers/net/ethernet/ti/icssg/icssg_classifier.c=41=enum ft1_cfg_type {
--
drivers/net/ethernet/ti/icssg/icssg_classifier.c-119-/* HSR/PRP classifier indices */
drivers/net/ethernet/ti/icssg/icssg_classifier.c:120:#define CLASSIFIER_PTP_DUP 10
drivers/net/ethernet/ti/icssg/icssg_classifier.c:121:#define CLASSIFIER_HSR_TAG 11
drivers/net/ethernet/ti/icssg/icssg_classifier.c-122-#define FT3_PTP_SLOT 14
--
drivers/platform/x86/dell/dell-smbios.h-19-/* Classes and selects used only in kernel drivers */
drivers/platform/x86/dell/dell-smbios.h:20:#define CLASS_KBD_BACKLIGHT 4
drivers/platform/x86/dell/dell-smbios.h-21-#define SELECT_KBD_BACKLIGHT 11
--
drivers/scsi/lpfc/lpfc_hw.h=4307=typedef struct _IOCB { /* IOCB structure */
--
drivers/scsi/lpfc/lpfc_hw.h-4407-#define PARM_NPIV_DID 3
drivers/scsi/lpfc/lpfc_hw.h:4408:#define CLASS1 0 /* Class 1 */
drivers/scsi/lpfc/lpfc_hw.h:4409:#define CLASS2 1 /* Class 2 */
drivers/scsi/lpfc/lpfc_hw.h:4410:#define CLASS3 2 /* Class 3 */
drivers/scsi/lpfc/lpfc_hw.h:4411:#define CLASS_FCP_INTERMIX 7 /* FCP Data->Cls 1, all else->Cls 2 */
drivers/scsi/lpfc/lpfc_hw.h-4412-
--
include/linux/cleanup.h=294=static __always_inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \
--
include/linux/cleanup.h-300-
include/linux/cleanup.h:301:#define CLASS(_name, var) \
include/linux/cleanup.h-302- class_##_name##_t var __cleanup(class_##_name##_destructor) = \
--
include/linux/cleanup.h-304-
include/linux/cleanup.h:305:#define CLASS_INIT(_name, _var, _init_expr) \
include/linux/cleanup.h-306- class_##_name##_t _var __cleanup(class_##_name##_destructor) = (_init_expr)
--
include/linux/device/class.h=167=struct class_attribute {
--
include/linux/device/class.h-174-
include/linux/device/class.h:175:#define CLASS_ATTR_RW(_name) \
include/linux/device/class.h-176- struct class_attribute class_attr_##_name = __ATTR_RW(_name)
include/linux/device/class.h:177:#define CLASS_ATTR_RO(_name) \
include/linux/device/class.h-178- struct class_attribute class_attr_##_name = __ATTR_RO(_name)
include/linux/device/class.h:179:#define CLASS_ATTR_WO(_name) \
include/linux/device/class.h-180- struct class_attribute class_attr_##_name = __ATTR_WO(_name)
--
include/linux/device/class.h=200=struct class_attribute_string {
--
include/linux/device/class.h-207- { __ATTR(_name, _mode, show_class_attr_string, NULL), _str }
include/linux/device/class.h:208:#define CLASS_ATTR_STRING(_name, _mode, _str) \
include/linux/device/class.h-209- struct class_attribute_string class_attr_##_name = \
--
include/uapi/linux/wmi.h=40=struct dell_wmi_smbios_buffer {
--
include/uapi/linux/wmi.h-46-/* Whitelisted smbios class/select commands */
include/uapi/linux/wmi.h:47:#define CLASS_TOKEN_READ 0
include/uapi/linux/wmi.h:48:#define CLASS_TOKEN_WRITE 1
include/uapi/linux/wmi.h-49-#define SELECT_TOKEN_STD 0
--
include/uapi/linux/wmi.h-51-#define SELECT_TOKEN_AC 2
include/uapi/linux/wmi.h:52:#define CLASS_FLASH_INTERFACE 7
include/uapi/linux/wmi.h-53-#define SELECT_FLASH_INTERFACE 3
include/uapi/linux/wmi.h:54:#define CLASS_ADMIN_PROP 10
include/uapi/linux/wmi.h-55-#define SELECT_ADMIN_PROP 3
include/uapi/linux/wmi.h:56:#define CLASS_INFO 17
include/uapi/linux/wmi.h-57-#define SELECT_RFKILL 11
--
kernel/locking/lockdep.c=398=static struct delayed_free {
--
kernel/locking/lockdep.c-407- */
kernel/locking/lockdep.c:408:#define CLASSHASH_BITS (MAX_LOCKDEP_KEYS_BITS - 1)
kernel/locking/lockdep.c:409:#define CLASSHASH_SIZE (1UL << CLASSHASH_BITS)
kernel/locking/lockdep.c-410-#define __classhashfn(key) hash_long((unsigned long)key, CLASSHASH_BITS)
--
lib/dynamic_debug.c=606=static int ddebug_apply_class_bitmap(const struct ddebug_class_param *dcp,
--
lib/dynamic_debug.c-635-
lib/dynamic_debug.c:636:#define CLASSMAP_BITMASK(width) ((1UL << (width)) - 1)
lib/dynamic_debug.c-637-
--
mm/zsmalloc.c-95-#define FULLNESS_BITS 4
mm/zsmalloc.c:96:#define CLASS_BITS 8
mm/zsmalloc.c-97-#define MAGIC_VAL_BITS 8
--
security/selinux/selinuxfs.c=98=static void selinux_fs_info_free(struct super_block *sb)
--
security/selinux/selinuxfs.c-119-#define BOOL_DIR_NAME "booleans"
security/selinux/selinuxfs.c:120:#define CLASS_DIR_NAME "class"
security/selinux/selinuxfs.c-121-
--
sound/pci/lx6464es/lx_defs.h=166=enum stream_flags {
--
sound/pci/lx6464es/lx_defs.h-223-
sound/pci/lx6464es/lx_defs.h:224:#define CLASS_MASK 0x0700
sound/pci/lx6464es/lx_defs.h-225-
--
sound/soc/atmel/atmel-classd.c=292=static int atmel_classd_cpu_dai_mute_stream(struct snd_soc_dai *cpu_dai,
--
sound/soc/atmel/atmel-classd.c-309-
sound/soc/atmel/atmel-classd.c:310:#define CLASSD_GCLK_RATE_11M2896_MPY_8 (112896 * 100 * 8)
sound/soc/atmel/atmel-classd.c:311:#define CLASSD_GCLK_RATE_12M288_MPY_8 (12288 * 1000 * 8)
sound/soc/atmel/atmel-classd.c-312-
--
sound/soc/atmel/atmel-classd.h-4-
sound/soc/atmel/atmel-classd.h:5:#define CLASSD_CR 0x00000000
sound/soc/atmel/atmel-classd.h:6:#define CLASSD_CR_RESET 0x1
sound/soc/atmel/atmel-classd.h-7-
sound/soc/atmel/atmel-classd.h:8:#define CLASSD_MR 0x00000004
sound/soc/atmel/atmel-classd.h-9-
sound/soc/atmel/atmel-classd.h:10:#define CLASSD_MR_LEN_DIS 0x0
sound/soc/atmel/atmel-classd.h:11:#define CLASSD_MR_LEN_EN 0x1
sound/soc/atmel/atmel-classd.h:12:#define CLASSD_MR_LEN_MASK (0x1 << 0)
sound/soc/atmel/atmel-classd.h:13:#define CLASSD_MR_LEN_SHIFT (0)
sound/soc/atmel/atmel-classd.h-14-
sound/soc/atmel/atmel-classd.h:15:#define CLASSD_MR_LMUTE_DIS 0x0
sound/soc/atmel/atmel-classd.h:16:#define CLASSD_MR_LMUTE_EN 0x1
sound/soc/atmel/atmel-classd.h:17:#define CLASSD_MR_LMUTE_SHIFT (0x1)
sound/soc/atmel/atmel-classd.h:18:#define CLASSD_MR_LMUTE_MASK (0x1 << 1)
sound/soc/atmel/atmel-classd.h-19-
sound/soc/atmel/atmel-classd.h:20:#define CLASSD_MR_REN_DIS 0x0
sound/soc/atmel/atmel-classd.h:21:#define CLASSD_MR_REN_EN 0x1
sound/soc/atmel/atmel-classd.h:22:#define CLASSD_MR_REN_MASK (0x1 << 4)
sound/soc/atmel/atmel-classd.h:23:#define CLASSD_MR_REN_SHIFT (4)
sound/soc/atmel/atmel-classd.h-24-
sound/soc/atmel/atmel-classd.h:25:#define CLASSD_MR_RMUTE_DIS 0x0
sound/soc/atmel/atmel-classd.h:26:#define CLASSD_MR_RMUTE_EN 0x1
sound/soc/atmel/atmel-classd.h:27:#define CLASSD_MR_RMUTE_SHIFT (0x5)
sound/soc/atmel/atmel-classd.h:28:#define CLASSD_MR_RMUTE_MASK (0x1 << 5)
sound/soc/atmel/atmel-classd.h-29-
sound/soc/atmel/atmel-classd.h:30:#define CLASSD_MR_PWMTYP_SINGLE 0x0
sound/soc/atmel/atmel-classd.h:31:#define CLASSD_MR_PWMTYP_DIFF 0x1
sound/soc/atmel/atmel-classd.h:32:#define CLASSD_MR_PWMTYP_MASK (0x1 << 8)
sound/soc/atmel/atmel-classd.h:33:#define CLASSD_MR_PWMTYP_SHIFT (8)
sound/soc/atmel/atmel-classd.h-34-
sound/soc/atmel/atmel-classd.h:35:#define CLASSD_MR_NON_OVERLAP_DIS 0x0
sound/soc/atmel/atmel-classd.h:36:#define CLASSD_MR_NON_OVERLAP_EN 0x1
sound/soc/atmel/atmel-classd.h:37:#define CLASSD_MR_NON_OVERLAP_MASK (0x1 << 16)
sound/soc/atmel/atmel-classd.h:38:#define CLASSD_MR_NON_OVERLAP_SHIFT (16)
sound/soc/atmel/atmel-classd.h-39-
sound/soc/atmel/atmel-classd.h:40:#define CLASSD_MR_NOVR_VAL_5NS 0x0
sound/soc/atmel/atmel-classd.h:41:#define CLASSD_MR_NOVR_VAL_10NS 0x1
sound/soc/atmel/atmel-classd.h:42:#define CLASSD_MR_NOVR_VAL_15NS 0x2
sound/soc/atmel/atmel-classd.h:43:#define CLASSD_MR_NOVR_VAL_20NS 0x3
sound/soc/atmel/atmel-classd.h:44:#define CLASSD_MR_NOVR_VAL_MASK (0x3 << 20)
sound/soc/atmel/atmel-classd.h:45:#define CLASSD_MR_NOVR_VAL_SHIFT (20)
sound/soc/atmel/atmel-classd.h-46-
sound/soc/atmel/atmel-classd.h:47:#define CLASSD_INTPMR 0x00000008
sound/soc/atmel/atmel-classd.h-48-
sound/soc/atmel/atmel-classd.h:49:#define CLASSD_INTPMR_ATTL_MASK (0x3f << 0)
sound/soc/atmel/atmel-classd.h:50:#define CLASSD_INTPMR_ATTL_SHIFT (0)
sound/soc/atmel/atmel-classd.h:51:#define CLASSD_INTPMR_ATTR_MASK (0x3f << 8)
sound/soc/atmel/atmel-classd.h:52:#define CLASSD_INTPMR_ATTR_SHIFT (8)
sound/soc/atmel/atmel-classd.h-53-
sound/soc/atmel/atmel-classd.h:54:#define CLASSD_INTPMR_DSP_CLK_FREQ_12M288 0x0
sound/soc/atmel/atmel-classd.h:55:#define CLASSD_INTPMR_DSP_CLK_FREQ_11M2896 0x1
sound/soc/atmel/atmel-classd.h:56:#define CLASSD_INTPMR_DSP_CLK_FREQ_MASK (0x1 << 16)
sound/soc/atmel/atmel-classd.h:57:#define CLASSD_INTPMR_DSP_CLK_FREQ_SHIFT (16)
sound/soc/atmel/atmel-classd.h-58-
sound/soc/atmel/atmel-classd.h:59:#define CLASSD_INTPMR_DEEMP_DIS 0x0
sound/soc/atmel/atmel-classd.h:60:#define CLASSD_INTPMR_DEEMP_EN 0x1
sound/soc/atmel/atmel-classd.h:61:#define CLASSD_INTPMR_DEEMP_MASK (0x1 << 18)
sound/soc/atmel/atmel-classd.h:62:#define CLASSD_INTPMR_DEEMP_SHIFT (18)
sound/soc/atmel/atmel-classd.h-63-
sound/soc/atmel/atmel-classd.h:64:#define CLASSD_INTPMR_SWAP_LEFT_ON_LSB 0x0
sound/soc/atmel/atmel-classd.h:65:#define CLASSD_INTPMR_SWAP_RIGHT_ON_LSB 0x1
sound/soc/atmel/atmel-classd.h:66:#define CLASSD_INTPMR_SWAP_MASK (0x1 << 19)
sound/soc/atmel/atmel-classd.h:67:#define CLASSD_INTPMR_SWAP_SHIFT (19)
sound/soc/atmel/atmel-classd.h-68-
sound/soc/atmel/atmel-classd.h:69:#define CLASSD_INTPMR_FRAME_8K 0x0
sound/soc/atmel/atmel-classd.h:70:#define CLASSD_INTPMR_FRAME_16K 0x1
sound/soc/atmel/atmel-classd.h:71:#define CLASSD_INTPMR_FRAME_32K 0x2
sound/soc/atmel/atmel-classd.h:72:#define CLASSD_INTPMR_FRAME_48K 0x3
sound/soc/atmel/atmel-classd.h:73:#define CLASSD_INTPMR_FRAME_96K 0x4
sound/soc/atmel/atmel-classd.h:74:#define CLASSD_INTPMR_FRAME_22K 0x5
sound/soc/atmel/atmel-classd.h:75:#define CLASSD_INTPMR_FRAME_44K 0x6
sound/soc/atmel/atmel-classd.h:76:#define CLASSD_INTPMR_FRAME_88K 0x7
sound/soc/atmel/atmel-classd.h:77:#define CLASSD_INTPMR_FRAME_MASK (0x7 << 20)
sound/soc/atmel/atmel-classd.h:78:#define CLASSD_INTPMR_FRAME_SHIFT (20)
sound/soc/atmel/atmel-classd.h-79-
sound/soc/atmel/atmel-classd.h:80:#define CLASSD_INTPMR_EQCFG_FLAT 0x0
sound/soc/atmel/atmel-classd.h:81:#define CLASSD_INTPMR_EQCFG_B_BOOST_12 0x1
sound/soc/atmel/atmel-classd.h:82:#define CLASSD_INTPMR_EQCFG_B_BOOST_6 0x2
sound/soc/atmel/atmel-classd.h:83:#define CLASSD_INTPMR_EQCFG_B_CUT_12 0x3
sound/soc/atmel/atmel-classd.h:84:#define CLASSD_INTPMR_EQCFG_B_CUT_6 0x4
sound/soc/atmel/atmel-classd.h:85:#define CLASSD_INTPMR_EQCFG_M_BOOST_3 0x5
sound/soc/atmel/atmel-classd.h:86:#define CLASSD_INTPMR_EQCFG_M_BOOST_8 0x6
sound/soc/atmel/atmel-classd.h:87:#define CLASSD_INTPMR_EQCFG_M_CUT_3 0x7
sound/soc/atmel/atmel-classd.h:88:#define CLASSD_INTPMR_EQCFG_M_CUT_8 0x8
sound/soc/atmel/atmel-classd.h:89:#define CLASSD_INTPMR_EQCFG_T_BOOST_12 0x9
sound/soc/atmel/atmel-classd.h:90:#define CLASSD_INTPMR_EQCFG_T_BOOST_6 0xa
sound/soc/atmel/atmel-classd.h:91:#define CLASSD_INTPMR_EQCFG_T_CUT_12 0xb
sound/soc/atmel/atmel-classd.h:92:#define CLASSD_INTPMR_EQCFG_T_CUT_6 0xc
sound/soc/atmel/atmel-classd.h:93:#define CLASSD_INTPMR_EQCFG_SHIFT (24)
sound/soc/atmel/atmel-classd.h-94-
sound/soc/atmel/atmel-classd.h:95:#define CLASSD_INTPMR_MONO_DIS 0x0
sound/soc/atmel/atmel-classd.h:96:#define CLASSD_INTPMR_MONO_EN 0x1
sound/soc/atmel/atmel-classd.h:97:#define CLASSD_INTPMR_MONO_MASK (0x1 << 28)
sound/soc/atmel/atmel-classd.h:98:#define CLASSD_INTPMR_MONO_SHIFT (28)
sound/soc/atmel/atmel-classd.h-99-
sound/soc/atmel/atmel-classd.h:100:#define CLASSD_INTPMR_MONO_MODE_MIX 0x0
sound/soc/atmel/atmel-classd.h:101:#define CLASSD_INTPMR_MONO_MODE_SAT 0x1
sound/soc/atmel/atmel-classd.h:102:#define CLASSD_INTPMR_MONO_MODE_LEFT 0x2
sound/soc/atmel/atmel-classd.h:103:#define CLASSD_INTPMR_MONO_MODE_RIGHT 0x3
sound/soc/atmel/atmel-classd.h:104:#define CLASSD_INTPMR_MONO_MODE_MASK (0x3 << 29)
sound/soc/atmel/atmel-classd.h:105:#define CLASSD_INTPMR_MONO_MODE_SHIFT (29)
sound/soc/atmel/atmel-classd.h-106-
sound/soc/atmel/atmel-classd.h:107:#define CLASSD_INTSR 0x0000000c
sound/soc/atmel/atmel-classd.h-108-
sound/soc/atmel/atmel-classd.h:109:#define CLASSD_THR 0x00000010
sound/soc/atmel/atmel-classd.h-110-
sound/soc/atmel/atmel-classd.h:111:#define CLASSD_IER 0x00000014
sound/soc/atmel/atmel-classd.h-112-
sound/soc/atmel/atmel-classd.h:113:#define CLASSD_IDR 0x00000018
sound/soc/atmel/atmel-classd.h-114-
sound/soc/atmel/atmel-classd.h:115:#define CLASSD_IMR 0x0000001c
sound/soc/atmel/atmel-classd.h-116-
sound/soc/atmel/atmel-classd.h:117:#define CLASSD_ISR 0x00000020
sound/soc/atmel/atmel-classd.h-118-
sound/soc/atmel/atmel-classd.h:119:#define CLASSD_WPMR 0x000000e4
sound/soc/atmel/atmel-classd.h-120-
--
sound/soc/codecs/tlv320aic3x.h=17=void aic3x_remove(struct device *dev);
--
sound/soc/codecs/tlv320aic3x.h-136-/* Class-D speaker driver on tlv320aic3007 */
sound/soc/codecs/tlv320aic3x.h:137:#define CLASSD_CTRL 73
sound/soc/codecs/tlv320aic3x.h-138-/* Left Line Output Plus/Minus control registers */
--
sound/soc/sdca/sdca_class.c-25-
sound/soc/sdca/sdca_class.c:26:#define CLASS_SDW_ATTACH_TIMEOUT_MS 5000
sound/soc/sdca/sdca_class.c-27-
--
tools/thermal/thermometer/thermometer.c-23-
tools/thermal/thermometer/thermometer.c:24:#define CLASS_THERMAL "/sys/class/thermal"
tools/thermal/thermometer/thermometer.c-25-
]
|
| 60/2 |
2026/05/20 05:11 |
llm |
expert |
0m
Model:
Tokens:
input: 75885
output: 43
thoughts: 1215
Thoughts: **Analyzing a Potential Mutex Deadlock**
Okay, let's break this down. I'm looking at a lockdep issue, specifically related to `snd_seq_info_clients_read`. The initial suspicion was that a `guard(mutex)` construct from `include/linux/cleanup.h` was somehow the culprit, expanding to a `class_mutex_t` and destructor that might be unlocking the mutex too early. The first thought was that since this cleanup variable is declared inside the loop block, the mutex unlock happens at the end of each loop iteration. This led me down a confusing rabbit hole: if lockdep shows the mutex held and the stack shows `mutex_lock` is blocking, something's amiss.
Initially, I pondered: if it's blocking in `mutex_lock`, it hasn't acquired the lock, and lockdep shouldn't consider it held, unless it's a recursive locking scenario. I then saw a hint from the stack trace `snd_seq_info_clients_read` and suspected the mutex was held and attempted to be acquired again causing the blocking.
I re-examined the code within `snd_seq_info_clients_read`, specifically that loop. I questioned if `client_load_and_use_ptr(c)` could return the same client pointer more than once. The potential for `clienttab` corruption occurred to me, however if the mutex unlock was done before the next iteration that would not be an issue.
Then I realized something crucial: `__free(snd_seq_client)` is *also* a cleanup attribute! Crucially, the order of cleanup functions in C is reversed. So, the `guard(mutex)` is destroyed *before* the client is freed. This means the mutex is unlocked *before* the client pointer is dereferenced. That eliminates the possibility of the loop causing this deadlock.
But that still leaves the question, why is lockdep complaining? I needed to consider other places where `ioctl_mutex` could be locked. Reviewing `snd_seq_info_clients_read`, there's no other locking. The next thought was, what if `snd_seq_info_clients_read` is called while the `ioctl_mutex` is already held? That's when I had to remember who calls this function: `snd_info_seq_show`. Now I know where to focus.
|
| 61/2 |
2026/05/20 05:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/info.c IncludeLines:true Name:snd_info_seq_show]
Results: map[SourceCode: 333: static int snd_info_seq_show(struct seq_file *seq, void *p)
334: {
335: struct snd_info_private_data *data = seq->private;
336: struct snd_info_entry *entry = data->entry;
337:
338: if (!entry->c.text.read) {
339: return -EIO;
340: } else {
341: data->rbuffer->buffer = (char *)seq; /* XXX hack! */
342: entry->c.text.read(entry, data->rbuffer);
343: }
344: return 0;
345: }
SourceFile:sound/core/info.c]
|
| 62/2 |
2026/05/20 05:11 |
llm |
expert |
1m
Model:
Tokens:
input: 77877
output: 42
thoughts: 7791
Thoughts: **Analyzing a Kernel Lockdep Deadlock in ALSA's MIDI Sequencer**
Okay, so I'm digging into this lockdep report, trying to understand a deadlock in the ALSA MIDI sequencer (`snd-seq`). The issue is this: a task (`syz.5.990/9676`) is being reported as *both* holding and being blocked on `client->ioctl_mutex` within `snd_seq_info_clients_read`. That's a classic deadlock situation, but the call stack doesn't immediately reveal *why* it's happening. The function is called in `seq_read_iter` which reads `/proc/asound/seq/clients`.
The core logic reads all the clients and accesses the `/proc` info for each client within a loop. Now, the key line is: `guard(mutex)(&client->ioctl_mutex);`. The problem is that `guard(mutex)` creates a temporary variable scoped within the *loop body*. After each iteration, the macro should release the lock. This is where I start by looking for recursive calls, or other obvious code paths that are double-locking the `ioctl_mutex`. I'm going through the code. I'm checking every function called within the loop to make sure it doesn't try to lock the same mutex (or others that could lead to a dependency cycle). I check the following:
* `snd_iprintf`
* `midi_version_string`
* `dump_ump_info`
* `snd_seq_info_dump_ports`
* `snd_seq_write_pool_allocated`
* `snd_seq_info_pool`
`snd_seq_info_dump_ports` *does* lock the `ports_mutex`. But this isn't the issue.
After careful code analysis, *within* the loop, there are no other obvious locks being acquired that could be the source of the problem.
The loop itself does *not* recursively lock the mutex or try to acquire the same lock multiple times on the same client. The `guard` macro *should* release the `ioctl_mutex` at the end of each iteration!
This tells me that it's possible the same client isn't being used within the loop. The `client_load_and_use_ptr(c)` could return the *same* pointer multiple times, which would allow the task to lock the mutex, unlock it, then lock it again *without* the lock being destroyed. But I'm going to set this idea aside for now, because even if the same pointer is returned, the loop must still release the lock.
It is possible that it is blocked on `ports_mutex`. However, the line number in the stack trace indicates that the task is blocked at the beginning of the ioctl mutex declaration.
At this point, I started checking the disassembly to ensure no inline code was creating unexpected behaviour or scope.
So, I'm left with the conundrum: the task *seems* to be blocked on a mutex it *already* holds, acquired at line 2609 of `sound/core/seq/seq_clientmgr.c`. This is impossible. The line number is the `guard` macro, so the *only* way this could happen is if something calls this function recursively and the original call doesn't release the lock.
I realized I need to focus on what the lockdep output *means*. The blocked task holds the lock *at the moment it tries to acquire it again*.
So, `snd_seq_info_clients_read` must be called from elsewhere while the task holds the `ioctl_mutex`, but this doesn't appear to be the case. I'm going to look at other tasks, but the report says it *is* the task.
I've thoroughly checked for recursive calls, double-locking within the function, and I'm still stumped. The issue remains: how can a task be blocked on a mutex it *claims* to hold, acquired at the exact point it's trying to re-acquire it?
|
| 63/2 |
2026/05/20 05:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/locking/mutex.c IncludeLines:true Name:__mutex_lock_common]
Results: map[SourceCode: 605: /*
606: * Lock a mutex (possibly interruptible), slowpath:
607: */
608: static __always_inline int __sched
609: __mutex_lock_common(struct mutex *lock, unsigned int state, unsigned int subclass,
610: struct lockdep_map *nest_lock, unsigned long ip,
611: struct ww_acquire_ctx *ww_ctx, const bool use_ww_ctx)
612: __cond_acquires(0, lock)
613: {
614: DEFINE_WAKE_Q(wake_q);
615: struct mutex_waiter waiter;
616: struct ww_mutex *ww;
617: unsigned long flags;
618: int ret;
619:
620: if (!use_ww_ctx)
621: ww_ctx = NULL;
622:
623: might_sleep();
624:
625: MUTEX_WARN_ON(lock->magic != lock);
626:
627: ww = container_of(lock, struct ww_mutex, base);
628: if (ww_ctx) {
629: if (unlikely(ww_ctx == READ_ONCE(ww->ctx)))
630: return -EALREADY;
631:
632: /*
633: * Reset the wounded flag after a kill. No other process can
634: * race and wound us here since they can't have a valid owner
635: * pointer if we don't have any locks held.
636: */
637: if (ww_ctx->acquired == 0)
638: ww_ctx->wounded = 0;
639:
640: #ifdef CONFIG_DEBUG_LOCK_ALLOC
641: nest_lock = &ww_ctx->dep_map;
642: #endif
643: }
644:
645: preempt_disable();
646: mutex_acquire_nest(&lock->dep_map, subclass, 0, nest_lock, ip);
647:
648: trace_contention_begin(lock, LCB_F_MUTEX | LCB_F_SPIN);
649: if (__mutex_trylock(lock) ||
650: mutex_optimistic_spin(lock, ww_ctx, NULL)) {
651: /* got the lock, yay! */
652: lock_acquired(&lock->dep_map, ip);
653: if (ww_ctx)
654: ww_mutex_set_context_fastpath(ww, ww_ctx);
655: trace_contention_end(lock, 0);
656: preempt_enable();
657: return 0;
658: }
659:
660: raw_spin_lock_irqsave(&lock->wait_lock, flags);
661: /*
662: * After waiting to acquire the wait_lock, try again.
663: */
664: if (__mutex_trylock(lock)) {
665: if (ww_ctx)
666: __ww_mutex_check_waiters(lock, ww_ctx, &wake_q);
667:
668: goto skip_wait;
669: }
670:
671: debug_mutex_lock_common(lock, &waiter);
672: waiter.task = current;
673: if (use_ww_ctx)
674: waiter.ww_ctx = ww_ctx;
675:
676: lock_contended(&lock->dep_map, ip);
677:
678: if (!use_ww_ctx) {
679: /* add waiting tasks to the end of the waitqueue (FIFO): */
680: __mutex_add_waiter(lock, &waiter, NULL);
681: } else {
682: /*
683: * Add in stamp order, waking up waiters that must kill
684: * themselves.
685: */
686: ret = __ww_mutex_add_waiter(&waiter, lock, ww_ctx, &wake_q);
687: if (ret)
688: goto err_early_kill;
689: }
690:
691: raw_spin_lock(¤t->blocked_lock);
692: __set_task_blocked_on(current, lock);
693: set_current_state(state);
694: trace_contention_begin(lock, LCB_F_MUTEX);
695: for (;;) {
696: bool first;
697:
698: /*
699: * Once we hold wait_lock, we're serialized against
700: * mutex_unlock() handing the lock off to us, do a trylock
701: * before testing the error conditions to make sure we pick up
702: * the handoff.
703: */
704: if (__mutex_trylock(lock))
705: break;
706:
707: raw_spin_unlock(¤t->blocked_lock);
708: /*
709: * Check for signals and kill conditions while holding
710: * wait_lock. This ensures the lock cancellation is ordered
711: * against mutex_unlock() and wake-ups do not go missing.
712: */
713: if (signal_pending_state(state, current)) {
714: ret = -EINTR;
715: goto err;
716: }
717:
718: if (ww_ctx) {
719: ret = __ww_mutex_check_kill(lock, &waiter, ww_ctx);
720: if (ret)
721: goto err;
722: }
723:
724: raw_spin_unlock_irqrestore_wake(&lock->wait_lock, flags, &wake_q);
725:
726: schedule_preempt_disabled();
727:
728: first = lock->first_waiter == &waiter;
729:
730: raw_spin_lock_irqsave(&lock->wait_lock, flags);
731: raw_spin_lock(¤t->blocked_lock);
732: /*
733: * As we likely have been woken up by task
734: * that has cleared our blocked_on state, re-set
735: * it to the lock we are trying to acquire.
736: */
737: __set_task_blocked_on(current, lock);
738: set_current_state(state);
739: /*
740: * Here we order against unlock; we must either see it change
741: * state back to RUNNING and fall through the next schedule(),
742: * or we must see its unlock and acquire.
743: */
744: if (__mutex_trylock_or_handoff(lock, first))
745: break;
746:
747: if (first) {
748: bool opt_acquired;
749:
750: /*
751: * mutex_optimistic_spin() can call schedule(), so
752: * we need to release these locks before calling it,
753: * and clear blocked on so we don't become unselectable
754: * to run.
755: */
756: __clear_task_blocked_on(current, lock);
757: raw_spin_unlock(¤t->blocked_lock);
758: raw_spin_unlock_irqrestore(&lock->wait_lock, flags);
759:
760: trace_contention_begin(lock, LCB_F_MUTEX | LCB_F_SPIN);
761: opt_acquired = mutex_optimistic_spin(lock, ww_ctx, &waiter);
762:
763: raw_spin_lock_irqsave(&lock->wait_lock, flags);
764: raw_spin_lock(¤t->blocked_lock);
765: __set_task_blocked_on(current, lock);
766:
767: if (opt_acquired)
768: break;
769: trace_contention_begin(lock, LCB_F_MUTEX);
770: }
771: }
772: __clear_task_blocked_on(current, lock);
773: __set_current_state(TASK_RUNNING);
774: raw_spin_unlock(¤t->blocked_lock);
775:
776: if (ww_ctx) {
777: /*
778: * Wound-Wait; we stole the lock (!first_waiter), check the
779: * waiters as anyone might want to wound us.
780: */
781: if (!ww_ctx->is_wait_die && lock->first_waiter != &waiter)
782: __ww_mutex_check_waiters(lock, ww_ctx, &wake_q);
783: }
784:
785: __mutex_remove_waiter(lock, &waiter);
786:
787: debug_mutex_free_waiter(&waiter);
788:
789: skip_wait:
790: /* got the lock - cleanup and rejoice! */
791: lock_acquired(&lock->dep_map, ip);
792: trace_contention_end(lock, 0);
793:
794: if (ww_ctx)
795: ww_mutex_lock_acquired(ww, ww_ctx);
796:
797: raw_spin_unlock_irqrestore_wake(&lock->wait_lock, flags, &wake_q);
798: preempt_enable();
799: return 0;
800:
801: err:
802: clear_task_blocked_on(current, lock);
803: __set_current_state(TASK_RUNNING);
804: __mutex_remove_waiter(lock, &waiter);
805: err_early_kill:
806: WARN_ON(get_task_blocked_on(current));
807: trace_contention_end(lock, ret);
808: raw_spin_unlock_irqrestore_wake(&lock->wait_lock, flags, &wake_q);
809: debug_mutex_free_waiter(&waiter);
810: mutex_release(&lock->dep_map, ip);
811: preempt_enable();
812: return ret;
813: }
SourceFile:kernel/locking/mutex.c]
|
| 64/2 |
2026/05/20 05:12 |
llm |
expert |
0m
Model:
Tokens:
input: 89730
output: 47
thoughts: 2776
Thoughts: **Analyzing a Lockdep Issue: The Mystery of the Blocked ioctl_mutex**
Okay, so I'm digging into this lockdep output, and it's telling me `syz.5.990/9676` is blocked trying to acquire `ioctl_mutex`, specifically for client `c`. The crucial point is that lockdep registers the lock *before* a task actually blocks, which explains why it flags the attempt even before the wait. But here's the kicker: it's *not* a recursive lock β it's just the *current* lock being sought.
My immediate thought is, who *else* has this mutex? The lockdep output should show me, but when I looked at the output, *nobody* seems to be holding it! This is very strange. A mutex is designed to be exclusive!
Now, the lockdep output seems incomplete for some other tasks. It looks like it might have been truncated by the hung task detector. So, `syz.1.669/8406`, `syz.7.1478/11803`, or `dhcpcd-run-hook/11871` might be the culprit.
So, here's my line of thinking: if the task *isn't* holding it, it must be sleeping, potentially in a way that *releases* the mutex. I'm thinking about the audio sequencer code because that's where `ioctl_mutex` is used. I see `snd_seq_client_enqueue_event` can call `snd_seq_cell_alloc`, which has a sleep in it. But! `snd_seq_cell_alloc` releases the mutex, and re-acquires it! So, that's not it.
Okay, so what *is* holding `ioctl_mutex` while sleeping? I then go through the `snd_seq_ioctl` handlers. I see `snd_seq_ioctl_set_client_pool`, and it looks like a long running `snd_seq_pool_done` function might be the issue. I find this.
The `snd_seq_pool_done` function loops with `while (atomic_read(&pool->counter) > 0)` and calls `schedule_timeout_uninterruptible(1)`. This means it *could* loop forever and hold the `ioctl_mutex` in this loop. This could explain the issue. So it looks like a resource leak, or a race condition, or a deadlock, is the issue. That's where I need to focus next.
|
| 65/2 |
2026/05/20 05:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_memory.c IncludeLines:true Name:snd_seq_pool_done]
Results: map[SourceCode: 481: /* remove events */
482: int snd_seq_pool_done(struct snd_seq_pool *pool)
483: {
484: struct snd_seq_event_cell *ptr;
485:
486: if (snd_BUG_ON(!pool))
487: return -EINVAL;
488:
489: /* wait for closing all threads */
490: if (waitqueue_active(&pool->output_sleep))
491: wake_up(&pool->output_sleep);
492:
493: while (atomic_read(&pool->counter) > 0)
494: schedule_timeout_uninterruptible(1);
495:
496: /* release all resources */
497: scoped_guard(spinlock_irq, &pool->lock) {
498: ptr = pool->ptr;
499: pool->ptr = NULL;
500: pool->free = NULL;
501: pool->total_elements = 0;
502: }
503:
504: kvfree(ptr);
505:
506: guard(spinlock_irq)(&pool->lock);
507: pool->closing = 0;
508:
509: return 0;
510: }
SourceFile:sound/core/seq/seq_memory.c]
|
| 66/2 |
2026/05/20 05:13 |
llm |
expert |
0m
Model:
Tokens:
input: 93533
output: 47
thoughts: 48
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Decoding the `snd_seq_pool_done` Logic**
Right, so `snd_seq_pool_done` is clearly stuck in a loop. It's hanging there as long as `atomic_read(&pool->counter)` is greater than zero. That's the key. Now, the question is, *where* is that counter being decremented? I know it's not going to decrement itself. Itβs `snd_seq_cell_free`. Makes sense. If that's the only place it's touched, that explains the loop condition.
|
| 67/2 |
2026/05/20 05:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_memory.c IncludeLines:true Name:snd_seq_cell_free]
Results: map[SourceCode: 233: void snd_seq_cell_free(struct snd_seq_event_cell * cell)
234: {
235: struct snd_seq_pool *pool;
236:
237: if (snd_BUG_ON(!cell))
238: return;
239: pool = cell->pool;
240: if (snd_BUG_ON(!pool))
241: return;
242:
243: guard(spinlock_irqsave)(&pool->lock);
244: free_cell(pool, cell);
245: if (snd_seq_ev_is_variable(&cell->event)) {
246: if (cell->event.data.ext.len & SNDRV_SEQ_EXT_CHAINED) {
247: struct snd_seq_event_cell *curp, *nextptr;
248: curp = cell->event.data.ext.ptr;
249: for (; curp; curp = nextptr) {
250: nextptr = curp->next;
251: curp->next = pool->free;
252: free_cell(pool, curp);
253: }
254: }
255: }
256: if (waitqueue_active(&pool->output_sleep)) {
257: /* has enough space now? */
258: if (snd_seq_output_ok(pool))
259: wake_up(&pool->output_sleep);
260: }
261: }
SourceFile:sound/core/seq/seq_memory.c]
|
| 68/2 |
2026/05/20 05:13 |
llm |
expert |
0m
Model:
Tokens:
input: 94244
output: 47
thoughts: 5587
Thoughts: **Debugging a Kernel Deadlock in the ALSA Sequencer**
Okay, so I'm staring at this lockdep output, and I've got a hung task report. It's telling me a process (`syz.5.990/9676`) is blocked, and crucially, it's blocked *trying* to acquire a mutex (`ioctl_mutex`) that it *appears* to already hold. That's a classic deadlock scenario, but lockdep isn't screaming AB-BA deadlock, which is weird. The core of the problem looks to be in the ALSA sequencer code.
My initial thought was `free_cell` might be the culprit due to a leaked cell causing a forever loop within `snd_seq_pool_done`. However, `snd_seq_ioctl_set_client_pool` should prevent that. I verified that its check using `atomic_read(&client->pool->counter)` correctly detects pool usage *before* calling `snd_seq_pool_done`, and this function holds the `ioctl_mutex`, so concurrent allocation should not be possible. So, that's not it.
I then started to check for other places that might call `snd_seq_pool_done` while *not* holding the mutex. `snd_seq_pool_delete` looked like a potential issue, as it's called by `snd_seq_release`. The concern was this release could concurrently call `snd_seq_pool_done` while a write was occurring. However, `snd_seq_release` is only called when the file descriptor is closed, so concurrent execution is not possible in standard scenarios.
But, a kernel client could have concurrent calls and thus call `snd_seq_pool_delete` while `snd_seq_kernel_client_enqueue` is running. This could deadlock, but I need to figure out why. This looks like a potential point of interest. A kernel client enqueues an event and concurrently `snd_seq_delete_kernel_client` is called.
Okay, let's step back to the lockdep output. The report showed *one* client's `ioctl_mutex` being held, and `syz.5.990/9676` is trying to acquire the *same* mutex for *another* client. Lockdep groups locks by *class*, meaning the identical mutexes could be in play. The question is: Could there be duplicated pointers? No, the code does not allow for it.
If the *same* client's mutex were held, it would be a recursive lock, but lockdep should be able to detect this. And it isn't.
If the client is stuck in `mutex_lock`, it means it's not held yet. I am now certain of it!
I've got a crucial realization: Lockdep reports the locks *it thinks* are held, due to the calls to `mutex_acquire_nest` within the mutex code. The thread is actually BLOCKED trying to *acquire* the lock, but the instrumentation thinks it's *holding* it.
So, the deadlock is that `syz.5.990/9676` is blocked in `mutex_lock` for a particular client's `ioctl_mutex`. And *another* task is holding *that same* `ioctl_mutex` and not releasing it! Now, my job is to figure out HOW a task can hold the `ioctl_mutex` *and* sleep forever.
I need to meticulously review the code to find potential sleep paths with the `ioctl_mutex` held. `snd_seq_ioctl_set_client_pool` is checked and seems OK. Next, I checked `snd_seq_write`, where enqueuing an event could cause a sleep via `snd_seq_cell_alloc`, but that unlocks the mutex before sleeping, so that's not the root cause. This has the effect of protecting the resources.
Finally, I'm at the right answer. The issue is that another task is sleeping in some code that holds the `ioctl_mutex` on *that client*, and that's the core of the problem.
|
| 69/2 |
2026/05/20 05:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_memory.c IncludeLines:true Name:snd_seq_enqueue_event]
Results: map[SourceCode: 285: /* enqueue a event to singe queue */
286: int snd_seq_enqueue_event(struct snd_seq_event_cell *cell, int atomic, int hop)
287: {
288: int dest, err;
289:
290: if (snd_BUG_ON(!cell))
291: return -EINVAL;
292: dest = cell->event.queue; /* destination queue */
293:
294: struct snd_seq_queue *q __free(snd_seq_queue) =
295: queueptr(dest);
296: if (q == NULL)
297: return -EINVAL;
298: /* handle relative time stamps, convert them into absolute */
299: if ((cell->event.flags & SNDRV_SEQ_TIME_MODE_MASK) == SNDRV_SEQ_TIME_MODE_REL) {
300: switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
301: case SNDRV_SEQ_TIME_STAMP_TICK:
302: cell->event.time.tick += q->timer->tick.cur_tick;
303: break;
304:
305: case SNDRV_SEQ_TIME_STAMP_REAL:
306: snd_seq_inc_real_time(&cell->event.time.time,
307: &q->timer->cur_time);
308: break;
309: }
310: cell->event.flags &= ~SNDRV_SEQ_TIME_MODE_MASK;
311: cell->event.flags |= SNDRV_SEQ_TIME_MODE_ABS;
312: }
313: /* enqueue event in the real-time or midi queue */
314: switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
315: case SNDRV_SEQ_TIME_STAMP_TICK:
316: err = snd_seq_prioq_cell_in(q->tickq, cell);
317: break;
318:
319: case SNDRV_SEQ_TIME_STAMP_REAL:
320: default:
321: err = snd_seq_prioq_cell_in(q->timeq, cell);
322: break;
323: }
324:
325: if (err < 0)
326: return err;
327:
328: /* trigger dispatching */
329: snd_seq_check_queue(q, atomic, hop);
330:
331: return 0;
332: }
SourceFile:sound/core/seq/seq_queue.c]
|
| 70/2 |
2026/05/20 05:14 |
llm |
expert |
0m
Model:
Tokens:
input: 101570
output: 43
thoughts: 700
Thoughts: **Analyzing Potential Deadlocks in the ALSA Sequencer**
Okay, so I'm thinking about the sequencer's locking. I know `snd_seq_prioq_cell_in` and `snd_seq_check_queue` use spinlocks, meaning `snd_seq_enqueue_event` itself doesn't sleep. That's good. But here's the kicker: `snd_seq_client_enqueue_event` *can* bypass the queue and call `snd_seq_deliver_event` directly if it's a direct event. Right, if the event is direct, it goes straight to the delivery path. This bypasses the usual locking path which I'm familiar with.
Now, `snd_seq_deliver_event` subsequently calls either `deliver_to_subscribers` or `snd_seq_deliver_single_event`. That calls `_snd_seq_deliver_single_event`, which in turn calls `__snd_seq_deliver_single_event`. This is where it gets interesting, because that final function calls into either a USER_CLIENT (using a FIFO) or a KERNEL_CLIENT. If it's a kernel client, it calls the `dest_port->event_input` function.
My concern is: Can `event_input` sleep? If it *can* sleep, and it's called from a context where `ioctl_mutex` is held (like from `snd_seq_write`), thatβs a potential deadlock! I need to be careful here, as I know that some driver implementations can be less than perfect.
The question then becomes: Is there any kernel client whose `event_input` might sleep indefinitely? Kernel clients are typically drivers, like those for MIDI or the dummy sequencer. My immediate thought is, if one of these drivers blocks for any reason in their `event_input` function, while holding `ioctl_mutex`, it's game over.
Ah, but wait! There's the `atomic` flag passed all the way down. In the `snd_seq_write` context that I'm considering, `atomic` is 0. That *means* `event_input` is *allowed* to sleep. So, the possibility of deadlock is there. The only question is which kernel clients might be problematic. I need to be sure the assumption that all clients will return quickly in their `event_input` function. It seems that `snd-seq-dummy` may be the obvious place to start my investigation. I should check its `event_input` function to verify.
|
| 71/2 |
2026/05/20 05:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_dummy.c IncludeLines:true Name:dummy_input]
Results: map[SourceCode: 76: /*
77: * event input callback - just redirect events to subscribers
78: */
79: static int
80: dummy_input(struct snd_seq_event *ev, int direct, void *private_data,
81: int atomic, int hop)
82: {
83: struct snd_seq_dummy_port *p;
84: struct snd_seq_event tmpev;
85:
86: p = private_data;
87: if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM ||
88: ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
89: return 0; /* ignore system messages */
90: tmpev = *ev;
91: if (p->duplex)
92: tmpev.source.port = p->connect;
93: else
94: tmpev.source.port = p->port;
95: tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
96: return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop);
97: }
SourceFile:sound/core/seq/seq_dummy.c]
|
| 72/2 |
2026/05/20 05:14 |
llm |
expert |
0m
Model:
Tokens:
input: 103219
output: 47
thoughts: 25
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Kernel Interaction in the Sequence Subsystem**
Okay, so I'm looking at this code, and it seems pretty straightforward β the core function here is just calling `snd_seq_kernel_client_dispatch`. That's the entry point, the central dispatch for handling messages. Nothing too fancy on the surface.
Now, my mind wanders to the wider system. I wonder how this relates to the different modules. Specifically, I'm thinking about `snd-seq-midi`. What's the specific interaction? Where does it fit into this dispatch process? I need to quickly recall how the MIDI events flow through the stack and how that impacts this core kernel function. I want to quickly evaluate the implications of the dispatch call.
|
| 73/2 |
2026/05/20 05:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:sound/core/seq/seq_midi.c IncludeLines:true Name:snd_midi_input_event]
Results: map[SourceCode: 61: /* handle rawmidi input event (MIDI v1.0 stream) */
62: static void snd_midi_input_event(struct snd_rawmidi_substream *substream)
63: {
64: struct snd_rawmidi_runtime *runtime;
65: struct seq_midisynth *msynth;
66: struct snd_seq_event ev;
67: char buf[16], *pbuf;
68: long res;
69:
70: if (substream == NULL)
71: return;
72: runtime = substream->runtime;
73: msynth = runtime->private_data;
74: if (msynth == NULL)
75: return;
76: memset(&ev, 0, sizeof(ev));
77: while (runtime->avail > 0) {
78: res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf));
79: if (res <= 0)
80: continue;
81: if (msynth->parser == NULL)
82: continue;
83: pbuf = buf;
84: while (res-- > 0) {
85: if (!snd_midi_event_encode_byte(msynth->parser,
86: *pbuf++, &ev))
87: continue;
88: ev.source.port = msynth->seq_port;
89: ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
90: snd_seq_kernel_client_dispatch(msynth->seq_client, &ev, 1, 0);
91: /* clear event and reset header */
92: memset(&ev, 0, sizeof(ev));
93: }
94: }
95: }
SourceFile:sound/core/seq/seq_midi.c]
|
| 74/2 |
2026/05/20 05:14 |
llm |
expert |
0m
Model:
Tokens:
input: 103997
output: 49
thoughts: 26
Thoughts: Here's your summary:
**Raw MIDI: Input vs. Output - A Quick Check**
Okay, so I'm looking at this `snd_midi_input_event` function... right, that's definitely for dealing with incoming MIDI data *from* a raw MIDI device, makes sense. But what about the other side of the coin? Where's the equivalent function for sending MIDI data *to* a raw MIDI device? I need to quickly confirm whether the API mirrors the input side with a comparable output function, or if a different approach is needed here. It's important to not miss any functions, or, overlook the method of output!
|
| 75/2 |
2026/05/20 05:14 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:sound/core/seq/seq_midi.c IncludeLines:true Name:snd_seq_midisynth_events]
Results: map[SourceCode: SourceFile:]
|
| 76/2 |
2026/05/20 05:14 |
llm |
expert |
0m
Model:
Tokens:
input: 104231
output: 17
thoughts: 23
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Diving into `seq_midi.c` for `event_input`**
Alright, I need to get a handle on the MIDI event input handling within the ALSA sequencer core. My immediate instinct is to jump straight into `sound/core/seq/seq_midi.c`. That's where I expect to find the core logic for translating MIDI messages into the sequencer's internal event representation. Specifically, I'm going to focus on the `event_input` function. It's the most likely entry point for examining how raw MIDI data is parsed, validated (or not!), and then dispatched to the relevant sequencer ports or clients. I need to understand the nuances of its handling to trace specific issues. Let's see what we've got in there. I'm hoping the code is reasonably well-structured, but you never know. Let's start digging and see what secrets this section of code reveals.
|
| 77/2 |
2026/05/20 05:14 |
tool |
grepper |
0m
Args: map[Expression:event_input]
Results: map[Output:include/hyperv/hvhdk_mini.h=347=struct hv_crashdump_area { /* HV_CRASHDUMP_AREA */
--
include/hyperv/hvhdk_mini.h-358-
include/hyperv/hvhdk_mini.h:359:union hv_partition_event_input {
include/hyperv/hvhdk_mini.h-360- struct hv_partition_event_root_crashdump_input crashdump_input;
--
include/hyperv/hvhdk_mini.h=368=struct hv_input_notify_partition_event {
include/hyperv/hvhdk_mini.h-369- u32 event; /* enum hv_partition_event */
include/hyperv/hvhdk_mini.h:370: union hv_partition_event_input input;
include/hyperv/hvhdk_mini.h-371-} __packed;
--
include/sound/seq_kernel.h=44=struct snd_seq_port_callback {
--
include/sound/seq_kernel.h-50- int (*unuse)(void *private_data, struct snd_seq_port_subscribe *info);
include/sound/seq_kernel.h:51: int (*event_input)(struct snd_seq_event *ev, int direct, void *private_data, int atomic, int hop);
include/sound/seq_kernel.h-52- void (*private_free)(void *private_data);
--
sound/core/seq/oss/seq_oss_event.c=419=int
sound/core/seq/oss/seq_oss_event.c:420:snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data,
sound/core/seq/oss/seq_oss_event.c-421- int atomic, int hop)
--
sound/core/seq/oss/seq_oss_event.h=95=int snd_seq_oss_process_timer_event(struct seq_oss_timer *rec, union evrec *q);
sound/core/seq/oss/seq_oss_event.h:96:int snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data, int atomic, int hop);
sound/core/seq/oss/seq_oss_event.h-97-
--
sound/core/seq/oss/seq_oss_init.c=63=snd_seq_oss_create_client(void)
--
sound/core/seq/oss/seq_oss_init.c-90- */
sound/core/seq/oss/seq_oss_init.c:91: port_callback.event_input = receive_announce;
sound/core/seq/oss/seq_oss_init.c-92- port->kernel = &port_callback;
--
sound/core/seq/oss/seq_oss_init.c=294=create_port(struct seq_oss_devinfo *dp)
--
sound/core/seq/oss/seq_oss_init.c-310- callback.private_data = dp;
sound/core/seq/oss/seq_oss_init.c:311: callback.event_input = snd_seq_oss_event_input;
sound/core/seq/oss/seq_oss_init.c-312- callback.private_free = free_devinfo;
--
sound/core/seq/seq_clientmgr.c=586=int __snd_seq_deliver_single_event(struct snd_seq_client *dest,
--
sound/core/seq/seq_clientmgr.c-596- case KERNEL_CLIENT:
sound/core/seq/seq_clientmgr.c:597: if (!dest_port->event_input)
sound/core/seq/seq_clientmgr.c-598- return 0;
sound/core/seq/seq_clientmgr.c:599: return dest_port->event_input(event,
sound/core/seq/seq_clientmgr.c-600- snd_seq_ev_is_direct(event),
--
sound/core/seq/seq_clientmgr.c=1268=static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg)
--
sound/core/seq/seq_clientmgr.c-1300- port->private_free = callback->private_free;
sound/core/seq/seq_clientmgr.c:1301: port->event_input = callback->event_input;
sound/core/seq/seq_clientmgr.c-1302- port->c_src.open = callback->subscribe;
--
sound/core/seq/seq_dummy.c=112=create_port(int idx, int type)
--
sound/core/seq/seq_dummy.c-141- pcb.owner = THIS_MODULE;
sound/core/seq/seq_dummy.c:142: pcb.event_input = dummy_input;
sound/core/seq/seq_dummy.c-143- pcb.private_free = dummy_free;
--
sound/core/seq/seq_midi.c=268=snd_seq_midisynth_probe(struct snd_seq_device *dev)
--
sound/core/seq/seq_midi.c-384- pcallbacks.unuse = midisynth_unuse;
sound/core/seq/seq_midi.c:385: pcallbacks.event_input = event_process_midi;
sound/core/seq/seq_midi.c-386- port->kernel = &pcallbacks;
--
sound/core/seq/seq_ports.h=46=struct snd_seq_client_port {
--
sound/core/seq/seq_ports.h-57-
sound/core/seq/seq_ports.h:58: int (*event_input)(struct snd_seq_event *ev, int direct, void *private_data,
sound/core/seq/seq_ports.h-59- int atomic, int hop);
--
sound/core/seq/seq_system.c=93=int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev,
--
sound/core/seq/seq_system.c-104-/* call-back handler for timer events */
sound/core/seq/seq_system.c:105:static int event_input_timer(struct snd_seq_event * ev, int direct, void *private_data, int atomic, int hop)
sound/core/seq/seq_system.c-106-{
--
sound/core/seq/seq_system.c=127=int __init snd_seq_system_client_init(void)
--
sound/core/seq/seq_system.c-138- pcallbacks.owner = THIS_MODULE;
sound/core/seq/seq_system.c:139: pcallbacks.event_input = event_input_timer;
sound/core/seq/seq_system.c-140-
--
sound/core/seq/seq_system.c-164- port->capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast only */
sound/core/seq/seq_system.c:165: pcallbacks.event_input = NULL;
sound/core/seq/seq_system.c-166- pcallbacks.subscribe = sys_announce_subscribe;
--
sound/core/seq/seq_ump_client.c=214=static int seq_ump_group_init(struct seq_ump_client *client, int group_index)
--
sound/core/seq/seq_ump_client.c-235- pcallbacks.unuse = seq_ump_unuse;
sound/core/seq/seq_ump_client.c:236: pcallbacks.event_input = seq_ump_process_event;
sound/core/seq/seq_ump_client.c-237- port->kernel = &pcallbacks;
--
sound/core/seq/seq_ump_client.c=279=static int create_ump_endpoint_port(struct seq_ump_client *client)
--
sound/core/seq/seq_ump_client.c-323- pcallbacks.unuse = seq_ump_unuse;
sound/core/seq/seq_ump_client.c:324: pcallbacks.event_input = seq_ump_process_event;
sound/core/seq/seq_ump_client.c-325- }
--
sound/core/seq/seq_virmidi.c=72=static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev,
--
sound/core/seq/seq_virmidi.c-108- */
sound/core/seq/seq_virmidi.c:109:static int snd_virmidi_event_input(struct snd_seq_event *ev, int direct,
sound/core/seq/seq_virmidi.c-110- void *private_data, int atomic, int hop)
--
sound/core/seq/seq_virmidi.c=360=static int snd_virmidi_dev_attach_seq(struct snd_virmidi_dev *rdev)
--
sound/core/seq/seq_virmidi.c-400- pcallbacks.unuse = snd_virmidi_unuse;
sound/core/seq/seq_virmidi.c:401: pcallbacks.event_input = snd_virmidi_event_input;
sound/core/seq/seq_virmidi.c-402- pinfo->kernel = &pcallbacks;
--
sound/drivers/opl3/opl3_oss.c=19=static const struct snd_seq_oss_callback oss_callback = {
--
sound/drivers/opl3/opl3_oss.c-27-
sound/drivers/opl3/opl3_oss.c:28:static int snd_opl3_oss_event_input(struct snd_seq_event *ev, int direct,
sound/drivers/opl3/opl3_oss.c-29- void *private_data, int atomic, int hop)
--
sound/drivers/opl3/opl3_oss.c=47=static int snd_opl3_oss_create_port(struct snd_opl3 * opl3)
--
sound/drivers/opl3/opl3_oss.c-61- callbacks.owner = THIS_MODULE;
sound/drivers/opl3/opl3_oss.c:62: callbacks.event_input = snd_opl3_oss_event_input;
sound/drivers/opl3/opl3_oss.c-63- callbacks.private_free = snd_opl3_oss_free_port;
--
sound/drivers/opl3/opl3_seq.c=130=const struct snd_midi_op opl3_ops = {
--
sound/drivers/opl3/opl3_seq.c-139-
sound/drivers/opl3/opl3_seq.c:140:static int snd_opl3_synth_event_input(struct snd_seq_event * ev, int direct,
sound/drivers/opl3/opl3_seq.c-141- void *private_data, int atomic, int hop)
--
sound/drivers/opl3/opl3_seq.c=158=static int snd_opl3_synth_create_port(struct snd_opl3 * opl3)
--
sound/drivers/opl3/opl3_seq.c-174- callbacks.unuse = snd_opl3_synth_unuse;
sound/drivers/opl3/opl3_seq.c:175: callbacks.event_input = snd_opl3_synth_event_input;
sound/drivers/opl3/opl3_seq.c-176- callbacks.private_free = snd_opl3_synth_free_port;
--
sound/drivers/opl4/opl4_seq.c=97=static const struct snd_midi_op opl4_ops = {
--
sound/drivers/opl4/opl4_seq.c-104-
sound/drivers/opl4/opl4_seq.c:105:static int snd_opl4_seq_event_input(struct snd_seq_event *ev, int direct,
sound/drivers/opl4/opl4_seq.c-106- void *private_data, int atomic, int hop)
--
sound/drivers/opl4/opl4_seq.c=121=static int snd_opl4_seq_probe(struct snd_seq_device *dev)
--
sound/drivers/opl4/opl4_seq.c-153- pcallbacks.unuse = snd_opl4_seq_unuse;
sound/drivers/opl4/opl4_seq.c:154: pcallbacks.event_input = snd_opl4_seq_event_input;
sound/drivers/opl4/opl4_seq.c-155- pcallbacks.private_free = snd_opl4_seq_free_port;
--
sound/synth/emux/emux_oss.c=96=snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure)
--
sound/synth/emux/emux_oss.c-111- callback.owner = THIS_MODULE;
sound/synth/emux/emux_oss.c:112: callback.event_input = snd_emux_event_oss_input;
sound/synth/emux/emux_oss.c-113-
--
sound/synth/emux/emux_oss.c=289=snd_emux_event_oss_input(struct snd_seq_event *ev, int direct, void *private_data,
--
sound/synth/emux/emux_oss.c-302- if (ev->type != SNDRV_SEQ_EVENT_OSS)
sound/synth/emux/emux_oss.c:303: return snd_emux_event_input(ev, direct, private_data, atomic, hop);
sound/synth/emux/emux_oss.c-304-
--
sound/synth/emux/emux_oss.c=483=fake_event(struct snd_emux *emu, struct snd_emux_port *port, int ch, int param, int val, int atomic, int hop)
--
sound/synth/emux/emux_oss.c-490- ev.data.control.value = val;
sound/synth/emux/emux_oss.c:491: snd_emux_event_input(&ev, 0, port, atomic, hop);
sound/synth/emux/emux_oss.c-492-}
--
sound/synth/emux/emux_seq.c=55=snd_emux_init_seq(struct snd_emux *emu, struct snd_card *card, int index)
--
sound/synth/emux/emux_seq.c-81- pinfo.unuse = snd_emux_unuse;
sound/synth/emux/emux_seq.c:82: pinfo.event_input = snd_emux_event_input;
sound/synth/emux/emux_seq.c-83-
--
sound/synth/emux/emux_seq.c=238=int
sound/synth/emux/emux_seq.c:239:snd_emux_event_input(struct snd_seq_event *ev, int direct, void *private_data,
sound/synth/emux/emux_seq.c-240- int atomic, int hop)
--
sound/synth/emux/emux_voice.h=23=void snd_emux_reset_port(struct snd_emux_port *port);
sound/synth/emux/emux_voice.h:24:int snd_emux_event_input(struct snd_seq_event *ev, int direct, void *private,
sound/synth/emux/emux_voice.h-25- int atomic, int hop);
]
|
| 78/2 |
2026/05/20 05:14 |
llm |
expert |
0m
Model:
Tokens:
input: 108574
output: 66
thoughts: 5166
Thoughts: **Analysis of a Potential Denial of Service Vulnerability in ALSA Sequencer (snd-seq)**
Okay, so I've been digging into this ALSA sequencer issue, specifically the hung task report from syzbot, and it's looking like a classic mutex-held-during-`copy_from_user` scenario. Initially, I was going through all the `event_input` callbacks and other potential long-running functions within the sequence code, wondering if any of them could be the culprit, but nothing seemed to hang indefinitely on its own. The stack trace pointed to a process stuck trying to acquire `ioctl_mutex`, but the trace *itself* showed the process wasn't actually *holding* the lock, which didn't make sense at first. Then I remembered lockdepβit can incorrectly flag a mutex as held if a lock attempt fails immediately after a successful one, which makes more sense.
I started looking at the code paths that *do* hold `ioctl_mutex`. I considered whether `snd_seq_pool_done` might cause a hang, but quickly ruled that out by tracing the calls and the conditions under which it's called. Next, I focused on places that hold `ioctl_mutex` for a significant period. I examined `snd_seq_write` and found the critical piece: `copy_from_user` is called while *already* holding `ioctl_mutex`. That's where the problem lies.
If user-space memory is backed by something like a FUSE filesystem, and the FUSE daemon goes belly-up or is malicious, `copy_from_user` can block forever. And because the mutex is held during this operation, any other task that tries to access the `ioctl_mutex` also blocks forever.
I verified the code path, confirming that `copy_from_user` is indeed called within the critical section protected by `ioctl_mutex`. I also checked other `copy_from_user` calls within `snd_seq_write` and `snd_seq_event_dup`. This means an unprivileged user can open `/dev/snd/seq`, make a client, trigger a `write()` to a malicious buffer.
The killer blow? This hangs *any* process that needs that mutex. The syzbot report specifically mentions `snd_seq_info_clients_read`, which reads `/proc/asound/seq/clients`. This is a guaranteed DoS, because `/proc/asound/seq/clients` iterates through *all* clients and thus attempts to acquire `ioctl_mutex` for each. Since the attacker's `copy_from_user` will lock that mutex, other monitoring services reading `/proc/asound/seq/clients` can get a hang on all the following `/dev/snd/seq` clients.
I've checked the code for other, subtle issues (e.g., missed unlocks on error paths) but didn't find any. It's simply the blocking `copy_from_user` inside a held mutex. It isn't a privilege escalation, it's a denial of service, but it's easily triggered by local users and can be very bad. I'm going to report this as a DoS vulnerability.
|
| 79/2 |
2026/05/20 05:15 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 80/2 |
2026/05/20 05:15 |
llm |
expert |
0m
Model:
Tokens:
input: 114564
output: 706
thoughts: 312
Thoughts: **Final Thoughts on the Sequence Device Vulnerability**
Okay, the analysis is complete, and it's time to put together the final assessment. The root cause, as I see it, boils down to a hung task stemming from a mutex issue within the `snd_seq_write()` function. Specifically, the code holds the `client->ioctl_mutex` *while* calling `copy_from_user()`. This is the crux.
The vulnerability's impact is a Denial of Service. An attacker, leveraging `userfaultfd` or perhaps a FUSE filesystem, can indefinitely stall that `copy_from_user()` call. The consequence is that any process trying to acquire that `ioctl_mutex` will hang. Think about the implications. Imagine a monitoring daemon or an administrator's tool reading `/proc/asound/seq/clients`. That routine iterates, sequentially acquiring the mutex of each client. If it hits the attacker's stalled client, the entire system grinds to a halt. We're talking about a system-wide DoS, plain and simple.
The good news, or perhaps the less *bad* news, is that it's not exploitable for memory corruption or privilege escalation. This is a hung task, nothing more, nothing less. It does, however, give unprivileged local users a pathway to disrupt the system, as long as they have access to `/dev/snd/seq`. That usually means members of the `audio` group or the active session user, depending on ACLs, have a problem.
Let's summarize the dimensions for a final time. It's a straight DoS. No exploitation beyond a hang. Accessible from unprivileged processes and user namespaces (assuming the device node is accessible). No virtual machine guests or hosts are directly involved. It's a local vulnerability, not something remotely triggered, and doesn't rely on malicious filesystems or peripherals. The focus has to be on prevention of userfaultfd or FUSE interaction with the sequence device.
|
| 81/1 |
2026/05/20 05:15 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the provided bug report and the kernel source code,
here is the assessment of the vulnerability:
### Root Cause Analysis
The bug is a **Hung Task / Denial of Service (DoS)** caused by holding a mutex
while performing a user-space memory access (`copy_from_user`).
In `sound/core/seq/seq_clientmgr.c`, the `snd_seq_write()` function processes
events written to `/dev/snd/seq`. It acquires the `client->ioctl_mutex` and then
loops over the provided user buffer, calling `copy_from_user()` to read the
events:
```c
mutex_lock(&client->ioctl_mutex);
...
while (count >= sizeof(struct snd_seq_event)) {
/* Read in the event header from the user */
len = sizeof(struct snd_seq_event);
if (copy_from_user(ev, buf, len)) {
...
```
An unprivileged attacker can exploit this by passing a buffer backed by
`userfaultfd` or a malicious FUSE filesystem. When `copy_from_user()` is called,
the page fault is intercepted by the attacker's user-space process, which can
intentionally pause the fault handling indefinitely. This causes the kernel
thread to hang forever while holding the `client->ioctl_mutex`.
Meanwhile, other legitimate kernel paths need to acquire this mutex. For
instance, reading `/proc/asound/seq/clients` (handled by
`snd_seq_info_clients_read()`) iterates over all sequencer clients and acquires
their `ioctl_mutex` one by one to dump their state:
```c
for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) {
struct snd_seq_client *client __free(snd_seq_client) =
client_load_and_use_ptr(c);
...
guard(mutex)(&client->ioctl_mutex);
...
```
If a system monitoring daemon, an administrator running `cat
/proc/asound/seq/clients`, or another process attempts to read this procfs file,
it will eventually reach the attacker's client, attempt to acquire the held
`ioctl_mutex`, and hang indefinitely. This results in the `khungtaskd` warnings
seen in the report and causes a localized Denial of Service.
### Security Impact
* **Exploitable:** No. This is a synchronization issue leading to a thread hang.
It does not result in memory corruption, out-of-bounds access, or privilege
escalation.
* **Denial Of Service:** Yes. It can permanently hang processes that interact
with the ALSA sequencer subsystem or read its procfs entries.
* **Accessible From Unprivileged Processes:** Yes. Any local user with
permissions to open `/dev/snd/seq` (typically granted via the `audio` group or
`systemd-logind` ACLs for the active session) can trigger this.
* **Accessible From User Namespaces:** Yes, provided the user namespace has
access to the `/dev/snd/seq` device node.
* **Other Triggers (Network, VM, Peripheral, Filesystem):** No. This requires
local execution of a program interacting with the ALSA sequencer character
device.]
|