1/*
2 * Line6 Linux USB driver - 0.9.1beta
3 *
4 * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
5 *
6 *	This program is free software; you can redistribute it and/or
7 *	modify it under the terms of the GNU General Public License as
8 *	published by the Free Software Foundation, version 2.
9 *
10 */
11
12#include <linux/slab.h>
13
14#include "midibuf.h"
15
16static int midibuf_message_length(unsigned char code)
17{
18	int message_length;
19
20	if (code < 0x80)
21		message_length = -1;
22	else if (code < 0xf0) {
23		static const int length[] = { 3, 3, 3, 3, 2, 2, 3 };
24
25		message_length = length[(code >> 4) - 8];
26	} else {
27		/*
28		   Note that according to the MIDI specification 0xf2 is
29		   the "Song Position Pointer", but this is used by Line6
30		   to send sysex messages to the host.
31		 */
32		static const int length[] = { -1, 2, -1, 2, -1, -1, 1, 1, 1, 1,
33			1, 1, 1, -1, 1, 1
34		};
35		message_length = length[code & 0x0f];
36	}
37
38	return message_length;
39}
40
41static int midibuf_is_empty(struct midi_buffer *this)
42{
43	return (this->pos_read == this->pos_write) && !this->full;
44}
45
46static int midibuf_is_full(struct midi_buffer *this)
47{
48	return this->full;
49}
50
51void line6_midibuf_reset(struct midi_buffer *this)
52{
53	this->pos_read = this->pos_write = this->full = 0;
54	this->command_prev = -1;
55}
56
57int line6_midibuf_init(struct midi_buffer *this, int size, int split)
58{
59	this->buf = kmalloc(size, GFP_KERNEL);
60
61	if (this->buf == NULL)
62		return -ENOMEM;
63
64	this->size = size;
65	this->split = split;
66	line6_midibuf_reset(this);
67	return 0;
68}
69
70void line6_midibuf_status(struct midi_buffer *this)
71{
72	pr_debug("midibuf size=%d split=%d pos_read=%d pos_write=%d full=%d command_prev=%02x\n",
73		 this->size, this->split, this->pos_read, this->pos_write,
74		 this->full, this->command_prev);
75}
76
77int line6_midibuf_bytes_free(struct midi_buffer *this)
78{
79	return
80	    midibuf_is_full(this) ?
81	    0 :
82	    (this->pos_read - this->pos_write + this->size - 1) % this->size +
83	    1;
84}
85
86int line6_midibuf_bytes_used(struct midi_buffer *this)
87{
88	return
89	    midibuf_is_empty(this) ?
90	    0 :
91	    (this->pos_write - this->pos_read + this->size - 1) % this->size +
92	    1;
93}
94
95int line6_midibuf_write(struct midi_buffer *this, unsigned char *data,
96			int length)
97{
98	int bytes_free;
99	int length1, length2;
100	int skip_active_sense = 0;
101
102	if (midibuf_is_full(this) || (length <= 0))
103		return 0;
104
105	/* skip trailing active sense */
106	if (data[length - 1] == 0xfe) {
107		--length;
108		skip_active_sense = 1;
109	}
110
111	bytes_free = line6_midibuf_bytes_free(this);
112
113	if (length > bytes_free)
114		length = bytes_free;
115
116	if (length > 0) {
117		length1 = this->size - this->pos_write;
118
119		if (length < length1) {
120			/* no buffer wraparound */
121			memcpy(this->buf + this->pos_write, data, length);
122			this->pos_write += length;
123		} else {
124			/* buffer wraparound */
125			length2 = length - length1;
126			memcpy(this->buf + this->pos_write, data, length1);
127			memcpy(this->buf, data + length1, length2);
128			this->pos_write = length2;
129		}
130
131		if (this->pos_write == this->pos_read)
132			this->full = 1;
133	}
134
135	return length + skip_active_sense;
136}
137
138int line6_midibuf_read(struct midi_buffer *this, unsigned char *data,
139		       int length)
140{
141	int bytes_used;
142	int length1, length2;
143	int command;
144	int midi_length;
145	int repeat = 0;
146	int i;
147
148	/* we need to be able to store at least a 3 byte MIDI message */
149	if (length < 3)
150		return -EINVAL;
151
152	if (midibuf_is_empty(this))
153		return 0;
154
155	bytes_used = line6_midibuf_bytes_used(this);
156
157	if (length > bytes_used)
158		length = bytes_used;
159
160	length1 = this->size - this->pos_read;
161
162	/* check MIDI command length */
163	command = this->buf[this->pos_read];
164
165	if (command & 0x80) {
166		midi_length = midibuf_message_length(command);
167		this->command_prev = command;
168	} else {
169		if (this->command_prev > 0) {
170			int midi_length_prev =
171			    midibuf_message_length(this->command_prev);
172
173			if (midi_length_prev > 0) {
174				midi_length = midi_length_prev - 1;
175				repeat = 1;
176			} else
177				midi_length = -1;
178		} else
179			midi_length = -1;
180	}
181
182	if (midi_length < 0) {
183		/* search for end of message */
184		if (length < length1) {
185			/* no buffer wraparound */
186			for (i = 1; i < length; ++i)
187				if (this->buf[this->pos_read + i] & 0x80)
188					break;
189
190			midi_length = i;
191		} else {
192			/* buffer wraparound */
193			length2 = length - length1;
194
195			for (i = 1; i < length1; ++i)
196				if (this->buf[this->pos_read + i] & 0x80)
197					break;
198
199			if (i < length1)
200				midi_length = i;
201			else {
202				for (i = 0; i < length2; ++i)
203					if (this->buf[i] & 0x80)
204						break;
205
206				midi_length = length1 + i;
207			}
208		}
209
210		if (midi_length == length)
211			midi_length = -1;	/* end of message not found */
212	}
213
214	if (midi_length < 0) {
215		if (!this->split)
216			return 0;	/* command is not yet complete */
217	} else {
218		if (length < midi_length)
219			return 0;	/* command is not yet complete */
220
221		length = midi_length;
222	}
223
224	if (length < length1) {
225		/* no buffer wraparound */
226		memcpy(data + repeat, this->buf + this->pos_read, length);
227		this->pos_read += length;
228	} else {
229		/* buffer wraparound */
230		length2 = length - length1;
231		memcpy(data + repeat, this->buf + this->pos_read, length1);
232		memcpy(data + repeat + length1, this->buf, length2);
233		this->pos_read = length2;
234	}
235
236	if (repeat)
237		data[0] = this->command_prev;
238
239	this->full = 0;
240	return length + repeat;
241}
242
243int line6_midibuf_ignore(struct midi_buffer *this, int length)
244{
245	int bytes_used = line6_midibuf_bytes_used(this);
246
247	if (length > bytes_used)
248		length = bytes_used;
249
250	this->pos_read = (this->pos_read + length) % this->size;
251	this->full = 0;
252	return length;
253}
254
255int line6_midibuf_skip_message(struct midi_buffer *this, unsigned short mask)
256{
257	int cmd = this->command_prev;
258
259	if ((cmd >= 0x80) && (cmd < 0xf0))
260		if ((mask & (1 << (cmd & 0x0f))) == 0)
261			return 1;
262
263	return 0;
264}
265
266void line6_midibuf_destroy(struct midi_buffer *this)
267{
268	kfree(this->buf);
269	this->buf = NULL;
270}
271