| 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 | |