[ALSA] fix potential NULL pointer deref in snd_sb8dsp_midi_interrupt()
[linux-2.6.git] / sound / isa / sb / sb8_midi.c
1 /*
2  *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
3  *  Routines for control of SoundBlaster cards - MIDI interface
4  *
5  *   This program is free software; you can redistribute it and/or modify
6  *   it under the terms of the GNU General Public License as published by
7  *   the Free Software Foundation; either version 2 of the License, or
8  *   (at your option) any later version.
9  *
10  *   This program is distributed in the hope that it will be useful,
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *   GNU General Public License for more details.
14  *
15  *   You should have received a copy of the GNU General Public License
16  *   along with this program; if not, write to the Free Software
17  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18  *
19  * --
20  *
21  * Sun May  9 22:54:38 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
22  *   Fixed typo in snd_sb8dsp_midi_new_device which prevented midi from 
23  *   working.
24  *
25  * Sun May 11 12:34:56 UTC 2003 Clemens Ladisch <clemens@ladisch.de>
26  *   Added full duplex UART mode for DSP version 2.0 and later.
27  */
28
29 #include <sound/driver.h>
30 #include <asm/io.h>
31 #include <linux/time.h>
32 #include <sound/core.h>
33 #include <sound/sb.h>
34
35
36 irqreturn_t snd_sb8dsp_midi_interrupt(struct snd_sb *chip)
37 {
38         struct snd_rawmidi *rmidi;
39         int max = 64;
40         char byte;
41
42         if (!chip)
43                 return IRQ_NONE;
44         
45         rmidi = chip->rmidi;
46         if (!rmidi) {
47                 inb(SBP(chip, DATA_AVAIL));     /* ack interrupt */
48                 return IRQ_NONE;
49         }
50
51         spin_lock(&chip->midi_input_lock);
52         while (max-- > 0) {
53                 if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
54                         byte = inb(SBP(chip, READ));
55                         if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
56                                 snd_rawmidi_receive(chip->midi_substream_input, &byte, 1);
57                         }
58                 }
59         }
60         spin_unlock(&chip->midi_input_lock);
61         return IRQ_HANDLED;
62 }
63
64 static int snd_sb8dsp_midi_input_open(struct snd_rawmidi_substream *substream)
65 {
66         unsigned long flags;
67         struct snd_sb *chip;
68         unsigned int valid_open_flags;
69
70         chip = substream->rmidi->private_data;
71         valid_open_flags = chip->hardware >= SB_HW_20
72                 ? SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER : 0;
73         spin_lock_irqsave(&chip->open_lock, flags);
74         if (chip->open & ~valid_open_flags) {
75                 spin_unlock_irqrestore(&chip->open_lock, flags);
76                 return -EAGAIN;
77         }
78         chip->open |= SB_OPEN_MIDI_INPUT;
79         chip->midi_substream_input = substream;
80         if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
81                 spin_unlock_irqrestore(&chip->open_lock, flags);
82                 snd_sbdsp_reset(chip);          /* reset DSP */
83                 if (chip->hardware >= SB_HW_20)
84                         snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
85         } else {
86                 spin_unlock_irqrestore(&chip->open_lock, flags);
87         }
88         return 0;
89 }
90
91 static int snd_sb8dsp_midi_output_open(struct snd_rawmidi_substream *substream)
92 {
93         unsigned long flags;
94         struct snd_sb *chip;
95         unsigned int valid_open_flags;
96
97         chip = substream->rmidi->private_data;
98         valid_open_flags = chip->hardware >= SB_HW_20
99                 ? SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER : 0;
100         spin_lock_irqsave(&chip->open_lock, flags);
101         if (chip->open & ~valid_open_flags) {
102                 spin_unlock_irqrestore(&chip->open_lock, flags);
103                 return -EAGAIN;
104         }
105         chip->open |= SB_OPEN_MIDI_OUTPUT;
106         chip->midi_substream_output = substream;
107         if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
108                 spin_unlock_irqrestore(&chip->open_lock, flags);
109                 snd_sbdsp_reset(chip);          /* reset DSP */
110                 if (chip->hardware >= SB_HW_20)
111                         snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
112         } else {
113                 spin_unlock_irqrestore(&chip->open_lock, flags);
114         }
115         return 0;
116 }
117
118 static int snd_sb8dsp_midi_input_close(struct snd_rawmidi_substream *substream)
119 {
120         unsigned long flags;
121         struct snd_sb *chip;
122
123         chip = substream->rmidi->private_data;
124         spin_lock_irqsave(&chip->open_lock, flags);
125         chip->open &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER);
126         chip->midi_substream_input = NULL;
127         if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
128                 spin_unlock_irqrestore(&chip->open_lock, flags);
129                 snd_sbdsp_reset(chip);          /* reset DSP */
130         } else {
131                 spin_unlock_irqrestore(&chip->open_lock, flags);
132         }
133         return 0;
134 }
135
136 static int snd_sb8dsp_midi_output_close(struct snd_rawmidi_substream *substream)
137 {
138         unsigned long flags;
139         struct snd_sb *chip;
140
141         chip = substream->rmidi->private_data;
142         spin_lock_irqsave(&chip->open_lock, flags);
143         chip->open &= ~(SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER);
144         chip->midi_substream_output = NULL;
145         if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
146                 spin_unlock_irqrestore(&chip->open_lock, flags);
147                 snd_sbdsp_reset(chip);          /* reset DSP */
148         } else {
149                 spin_unlock_irqrestore(&chip->open_lock, flags);
150         }
151         return 0;
152 }
153
154 static void snd_sb8dsp_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
155 {
156         unsigned long flags;
157         struct snd_sb *chip;
158
159         chip = substream->rmidi->private_data;
160         spin_lock_irqsave(&chip->open_lock, flags);
161         if (up) {
162                 if (!(chip->open & SB_OPEN_MIDI_INPUT_TRIGGER)) {
163                         if (chip->hardware < SB_HW_20)
164                                 snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
165                         chip->open |= SB_OPEN_MIDI_INPUT_TRIGGER;
166                 }
167         } else {
168                 if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
169                         if (chip->hardware < SB_HW_20)
170                                 snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
171                         chip->open &= ~SB_OPEN_MIDI_INPUT_TRIGGER;
172                 }
173         }
174         spin_unlock_irqrestore(&chip->open_lock, flags);
175 }
176
177 static void snd_sb8dsp_midi_output_write(struct snd_rawmidi_substream *substream)
178 {
179         unsigned long flags;
180         struct snd_sb *chip;
181         char byte;
182         int max = 32;
183
184         /* how big is Tx FIFO? */
185         chip = substream->rmidi->private_data;
186         while (max-- > 0) {
187                 spin_lock_irqsave(&chip->open_lock, flags);
188                 if (snd_rawmidi_transmit_peek(substream, &byte, 1) != 1) {
189                         chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
190                         del_timer(&chip->midi_timer);
191                         spin_unlock_irqrestore(&chip->open_lock, flags);
192                         break;
193                 }
194                 if (chip->hardware >= SB_HW_20) {
195                         int timeout = 8;
196                         while ((inb(SBP(chip, STATUS)) & 0x80) != 0 && --timeout > 0)
197                                 ;
198                         if (timeout == 0) {
199                                 /* Tx FIFO full - try again later */
200                                 spin_unlock_irqrestore(&chip->open_lock, flags);
201                                 break;
202                         }
203                         outb(byte, SBP(chip, WRITE));
204                 } else {
205                         snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT);
206                         snd_sbdsp_command(chip, byte);
207                 }
208                 snd_rawmidi_transmit_ack(substream, 1);
209                 spin_unlock_irqrestore(&chip->open_lock, flags);
210         }
211 }
212
213 static void snd_sb8dsp_midi_output_timer(unsigned long data)
214 {
215         struct snd_rawmidi_substream *substream = (struct snd_rawmidi_substream *) data;
216         struct snd_sb * chip = substream->rmidi->private_data;
217         unsigned long flags;
218
219         spin_lock_irqsave(&chip->open_lock, flags);
220         chip->midi_timer.expires = 1 + jiffies;
221         add_timer(&chip->midi_timer);
222         spin_unlock_irqrestore(&chip->open_lock, flags);        
223         snd_sb8dsp_midi_output_write(substream);
224 }
225
226 static void snd_sb8dsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
227 {
228         unsigned long flags;
229         struct snd_sb *chip;
230
231         chip = substream->rmidi->private_data;
232         spin_lock_irqsave(&chip->open_lock, flags);
233         if (up) {
234                 if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) {
235                         init_timer(&chip->midi_timer);
236                         chip->midi_timer.function = snd_sb8dsp_midi_output_timer;
237                         chip->midi_timer.data = (unsigned long) substream;
238                         chip->midi_timer.expires = 1 + jiffies;
239                         add_timer(&chip->midi_timer);
240                         chip->open |= SB_OPEN_MIDI_OUTPUT_TRIGGER;
241                 }
242         } else {
243                 if (chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER) {
244                         chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
245                 }
246         }
247         spin_unlock_irqrestore(&chip->open_lock, flags);
248
249         if (up)
250                 snd_sb8dsp_midi_output_write(substream);
251 }
252
253 static struct snd_rawmidi_ops snd_sb8dsp_midi_output =
254 {
255         .open =         snd_sb8dsp_midi_output_open,
256         .close =        snd_sb8dsp_midi_output_close,
257         .trigger =      snd_sb8dsp_midi_output_trigger,
258 };
259
260 static struct snd_rawmidi_ops snd_sb8dsp_midi_input =
261 {
262         .open =         snd_sb8dsp_midi_input_open,
263         .close =        snd_sb8dsp_midi_input_close,
264         .trigger =      snd_sb8dsp_midi_input_trigger,
265 };
266
267 int snd_sb8dsp_midi(struct snd_sb *chip, int device, struct snd_rawmidi ** rrawmidi)
268 {
269         struct snd_rawmidi *rmidi;
270         int err;
271
272         if (rrawmidi)
273                 *rrawmidi = NULL;
274         if ((err = snd_rawmidi_new(chip->card, "SB8 MIDI", device, 1, 1, &rmidi)) < 0)
275                 return err;
276         strcpy(rmidi->name, "SB8 MIDI");
277         snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_sb8dsp_midi_output);
278         snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_sb8dsp_midi_input);
279         rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT;
280         if (chip->hardware >= SB_HW_20)
281                 rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
282         rmidi->private_data = chip;
283         chip->rmidi = rmidi;
284         if (rrawmidi)
285                 *rrawmidi = rmidi;
286         return 0;
287 }