1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * OSS compatible sequencer driver
4 *
5 * seq_oss_writeq.c - write queue and sync
6 *
7 * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
8 */
9
10#include "seq_oss_writeq.h"
11#include "seq_oss_event.h"
12#include "seq_oss_timer.h"
13#include <sound/seq_oss_legacy.h>
14#include "../seq_lock.h"
15#include "../seq_clientmgr.h"
16#include <linux/wait.h>
17#include <linux/slab.h>
18#include <linux/sched/signal.h>
19
20
21/*
22 * create a write queue record
23 */
24struct seq_oss_writeq *
25snd_seq_oss_writeq_new(struct seq_oss_devinfo *dp, int maxlen)
26{
27	struct seq_oss_writeq *q;
28	struct snd_seq_client_pool pool;
29
30	q = kzalloc(sizeof(*q), GFP_KERNEL);
31	if (!q)
32		return NULL;
33	q->dp = dp;
34	q->maxlen = maxlen;
35	spin_lock_init(&q->sync_lock);
36	q->sync_event_put = 0;
37	q->sync_time = 0;
38	init_waitqueue_head(&q->sync_sleep);
39
40	memset(&pool, 0, sizeof(pool));
41	pool.client = dp->cseq;
42	pool.output_pool = maxlen;
43	pool.output_room = maxlen / 2;
44
45	snd_seq_oss_control(dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool);
46
47	return q;
48}
49
50/*
51 * delete the write queue
52 */
53void
54snd_seq_oss_writeq_delete(struct seq_oss_writeq *q)
55{
56	if (q) {
57		snd_seq_oss_writeq_clear(q);	/* to be sure */
58		kfree(q);
59	}
60}
61
62
63/*
64 * reset the write queue
65 */
66void
67snd_seq_oss_writeq_clear(struct seq_oss_writeq *q)
68{
69	struct snd_seq_remove_events reset;
70
71	memset(&reset, 0, sizeof(reset));
72	reset.remove_mode = SNDRV_SEQ_REMOVE_OUTPUT; /* remove all */
73	snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_REMOVE_EVENTS, &reset);
74
75	/* wake up sleepers if any */
76	snd_seq_oss_writeq_wakeup(q, 0);
77}
78
79/*
80 * wait until the write buffer has enough room
81 */
82int
83snd_seq_oss_writeq_sync(struct seq_oss_writeq *q)
84{
85	struct seq_oss_devinfo *dp = q->dp;
86	abstime_t time;
87
88	time = snd_seq_oss_timer_cur_tick(dp->timer);
89	if (q->sync_time >= time)
90		return 0; /* already finished */
91
92	if (! q->sync_event_put) {
93		struct snd_seq_event ev;
94		union evrec *rec;
95
96		/* put echoback event */
97		memset(&ev, 0, sizeof(ev));
98		ev.flags = 0;
99		ev.type = SNDRV_SEQ_EVENT_ECHO;
100		ev.time.tick = time;
101		/* echo back to itself */
102		snd_seq_oss_fill_addr(dp, &ev, dp->addr.client, dp->addr.port);
103		rec = (union evrec *)&ev.data;
104		rec->t.code = SEQ_SYNCTIMER;
105		rec->t.time = time;
106		q->sync_event_put = 1;
107		snd_seq_kernel_client_enqueue(dp->cseq, &ev, NULL, true);
108	}
109
110	wait_event_interruptible_timeout(q->sync_sleep, ! q->sync_event_put, HZ);
111	if (signal_pending(current))
112		/* interrupted - return 0 to finish sync */
113		q->sync_event_put = 0;
114	if (! q->sync_event_put || q->sync_time >= time)
115		return 0;
116	return 1;
117}
118
119/*
120 * wake up sync - echo event was catched
121 */
122void
123snd_seq_oss_writeq_wakeup(struct seq_oss_writeq *q, abstime_t time)
124{
125	unsigned long flags;
126
127	spin_lock_irqsave(&q->sync_lock, flags);
128	q->sync_time = time;
129	q->sync_event_put = 0;
130	wake_up(&q->sync_sleep);
131	spin_unlock_irqrestore(&q->sync_lock, flags);
132}
133
134
135/*
136 * return the unused pool size
137 */
138int
139snd_seq_oss_writeq_get_free_size(struct seq_oss_writeq *q)
140{
141	struct snd_seq_client_pool pool;
142	pool.client = q->dp->cseq;
143	snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool);
144	return pool.output_free;
145}
146
147
148/*
149 * set output threshold size from ioctl
150 */
151void
152snd_seq_oss_writeq_set_output(struct seq_oss_writeq *q, int val)
153{
154	struct snd_seq_client_pool pool;
155	pool.client = q->dp->cseq;
156	snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool);
157	pool.output_room = val;
158	snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool);
159}
160
161