1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * OSS compatible sequencer driver |
4 | * |
5 | * read/write/select interface to device file |
6 | * |
7 | * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> |
8 | */ |
9 | |
10 | #include "seq_oss_device.h" |
11 | #include "seq_oss_readq.h" |
12 | #include "seq_oss_writeq.h" |
13 | #include "seq_oss_synth.h" |
14 | #include <sound/seq_oss_legacy.h> |
15 | #include "seq_oss_event.h" |
16 | #include "seq_oss_timer.h" |
17 | #include "../seq_clientmgr.h" |
18 | |
19 | |
20 | /* |
21 | * protoypes |
22 | */ |
23 | static int insert_queue(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt); |
24 | |
25 | |
26 | /* |
27 | * read interface |
28 | */ |
29 | |
30 | int |
31 | snd_seq_oss_read(struct seq_oss_devinfo *dp, char __user *buf, int count) |
32 | { |
33 | struct seq_oss_readq *readq = dp->readq; |
34 | int result = 0, err = 0; |
35 | int ev_len; |
36 | union evrec rec; |
37 | unsigned long flags; |
38 | |
39 | if (readq == NULL || ! is_read_mode(dp->file_mode)) |
40 | return -ENXIO; |
41 | |
42 | while (count >= SHORT_EVENT_SIZE) { |
43 | snd_seq_oss_readq_lock(readq, flags); |
44 | err = snd_seq_oss_readq_pick(q: readq, rec: &rec); |
45 | if (err == -EAGAIN && |
46 | !is_nonblock_mode(dp->file_mode) && result == 0) { |
47 | snd_seq_oss_readq_unlock(readq, flags); |
48 | snd_seq_oss_readq_wait(q: readq); |
49 | snd_seq_oss_readq_lock(readq, flags); |
50 | if (signal_pending(current)) |
51 | err = -ERESTARTSYS; |
52 | else |
53 | err = snd_seq_oss_readq_pick(q: readq, rec: &rec); |
54 | } |
55 | if (err < 0) { |
56 | snd_seq_oss_readq_unlock(readq, flags); |
57 | break; |
58 | } |
59 | ev_len = ev_length(&rec); |
60 | if (ev_len < count) { |
61 | snd_seq_oss_readq_unlock(readq, flags); |
62 | break; |
63 | } |
64 | snd_seq_oss_readq_free(q: readq); |
65 | snd_seq_oss_readq_unlock(readq, flags); |
66 | if (copy_to_user(to: buf, from: &rec, n: ev_len)) { |
67 | err = -EFAULT; |
68 | break; |
69 | } |
70 | result += ev_len; |
71 | buf += ev_len; |
72 | count -= ev_len; |
73 | } |
74 | return result > 0 ? result : err; |
75 | } |
76 | |
77 | |
78 | /* |
79 | * write interface |
80 | */ |
81 | |
82 | int |
83 | snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt) |
84 | { |
85 | int result = 0, err = 0; |
86 | int ev_size, fmt; |
87 | union evrec rec; |
88 | |
89 | if (! is_write_mode(dp->file_mode) || dp->writeq == NULL) |
90 | return -ENXIO; |
91 | |
92 | while (count >= SHORT_EVENT_SIZE) { |
93 | if (copy_from_user(to: &rec, from: buf, SHORT_EVENT_SIZE)) { |
94 | err = -EFAULT; |
95 | break; |
96 | } |
97 | if (rec.s.code == SEQ_FULLSIZE) { |
98 | /* load patch */ |
99 | if (result > 0) { |
100 | err = -EINVAL; |
101 | break; |
102 | } |
103 | fmt = (*(unsigned short *)rec.c) & 0xffff; |
104 | /* FIXME the return value isn't correct */ |
105 | return snd_seq_oss_synth_load_patch(dp, dev: rec.s.dev, |
106 | fmt, buf, p: 0, c: count); |
107 | } |
108 | if (ev_is_long(&rec)) { |
109 | /* extended code */ |
110 | if (rec.s.code == SEQ_EXTENDED && |
111 | dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) { |
112 | err = -EINVAL; |
113 | break; |
114 | } |
115 | ev_size = LONG_EVENT_SIZE; |
116 | if (count < ev_size) |
117 | break; |
118 | /* copy the reset 4 bytes */ |
119 | if (copy_from_user(to: rec.c + SHORT_EVENT_SIZE, |
120 | from: buf + SHORT_EVENT_SIZE, |
121 | LONG_EVENT_SIZE - SHORT_EVENT_SIZE)) { |
122 | err = -EFAULT; |
123 | break; |
124 | } |
125 | } else { |
126 | /* old-type code */ |
127 | if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) { |
128 | err = -EINVAL; |
129 | break; |
130 | } |
131 | ev_size = SHORT_EVENT_SIZE; |
132 | } |
133 | |
134 | /* insert queue */ |
135 | err = insert_queue(dp, rec: &rec, opt); |
136 | if (err < 0) |
137 | break; |
138 | |
139 | result += ev_size; |
140 | buf += ev_size; |
141 | count -= ev_size; |
142 | } |
143 | return result > 0 ? result : err; |
144 | } |
145 | |
146 | |
147 | /* |
148 | * insert event record to write queue |
149 | * return: 0 = OK, non-zero = NG |
150 | */ |
151 | static int |
152 | insert_queue(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt) |
153 | { |
154 | int rc = 0; |
155 | struct snd_seq_event event; |
156 | |
157 | /* if this is a timing event, process the current time */ |
158 | if (snd_seq_oss_process_timer_event(rec: dp->timer, q: rec)) |
159 | return 0; /* no need to insert queue */ |
160 | |
161 | /* parse this event */ |
162 | memset(&event, 0, sizeof(event)); |
163 | /* set dummy -- to be sure */ |
164 | event.type = SNDRV_SEQ_EVENT_NOTEOFF; |
165 | snd_seq_oss_fill_addr(dp, ev: &event, dest_client: dp->addr.client, dest_port: dp->addr.port); |
166 | |
167 | if (snd_seq_oss_process_event(dp, q: rec, ev: &event)) |
168 | return 0; /* invalid event - no need to insert queue */ |
169 | |
170 | event.time.tick = snd_seq_oss_timer_cur_tick(timer: dp->timer); |
171 | if (dp->timer->realtime || !dp->timer->running) |
172 | snd_seq_oss_dispatch(dp, ev: &event, atomic: 0, hop: 0); |
173 | else |
174 | rc = snd_seq_kernel_client_enqueue(client: dp->cseq, ev: &event, file: opt, |
175 | blocking: !is_nonblock_mode(dp->file_mode)); |
176 | return rc; |
177 | } |
178 | |
179 | |
180 | /* |
181 | * select / poll |
182 | */ |
183 | |
184 | __poll_t |
185 | snd_seq_oss_poll(struct seq_oss_devinfo *dp, struct file *file, poll_table * wait) |
186 | { |
187 | __poll_t mask = 0; |
188 | |
189 | /* input */ |
190 | if (dp->readq && is_read_mode(dp->file_mode)) { |
191 | if (snd_seq_oss_readq_poll(readq: dp->readq, file, wait)) |
192 | mask |= EPOLLIN | EPOLLRDNORM; |
193 | } |
194 | |
195 | /* output */ |
196 | if (dp->writeq && is_write_mode(dp->file_mode)) { |
197 | if (snd_seq_kernel_client_write_poll(clientid: dp->cseq, file, wait)) |
198 | mask |= EPOLLOUT | EPOLLWRNORM; |
199 | } |
200 | return mask; |
201 | } |
202 | |