2015-12-22 16:08:23

by Felipe Ferreri Tonello

[permalink] [raw]
Subject: [PATCH 1/2] usb: gadget: f_midi: refactor state machine

This refactor includes the following:
* Cleaner state machine code;
* Reset state if MIDI message parsed is non-conformant;
* Fixed bug when a conformant MIDI message was followed by a non-conformant
causing the MIDI-USB message to use old temporary data (port->data[0..1]),
thus packing a wrong MIDI-USB request.

Signed-off-by: Felipe F. Tonello <[email protected]>
---
drivers/usb/gadget/function/f_midi.c | 243 ++++++++++++++++++++---------------
1 file changed, 141 insertions(+), 102 deletions(-)

diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c
index fb1fe96d..b70a830 100644
--- a/drivers/usb/gadget/function/f_midi.c
+++ b/drivers/usb/gadget/function/f_midi.c
@@ -50,6 +50,18 @@ static const char f_midi_longname[] = "MIDI Gadget";
*/
#define MAX_PORTS 16

+/* MIDI message states */
+enum {
+ STATE_INITIAL = 0,
+ STATE_1PARAM,
+ STATE_2PARAM_1,
+ STATE_2PARAM_2,
+ STATE_SYSEX_0,
+ STATE_SYSEX_1,
+ STATE_SYSEX_2,
+ STATE_FINISHED,
+};
+
/*
* This is a gadget, and the IN/OUT naming is from the host's perspective.
* USB -> OUT endpoint -> rawmidi
@@ -60,13 +72,6 @@ struct gmidi_in_port {
int active;
uint8_t cable;
uint8_t state;
-#define STATE_UNKNOWN 0
-#define STATE_1PARAM 1
-#define STATE_2PARAM_1 2
-#define STATE_2PARAM_2 3
-#define STATE_SYSEX_0 4
-#define STATE_SYSEX_1 5
-#define STATE_SYSEX_2 6
uint8_t data[2];
};

@@ -400,118 +405,152 @@ static int f_midi_snd_free(struct snd_device *device)
return 0;
}

-static void f_midi_transmit_packet(struct usb_request *req, uint8_t p0,
- uint8_t p1, uint8_t p2, uint8_t p3)
-{
- unsigned length = req->length;
- u8 *buf = (u8 *)req->buf + length;
-
- buf[0] = p0;
- buf[1] = p1;
- buf[2] = p2;
- buf[3] = p3;
- req->length = length + 4;
-}
-
/*
* Converts MIDI commands to USB MIDI packets.
*/
static void f_midi_transmit_byte(struct usb_request *req,
struct gmidi_in_port *port, uint8_t b)
{
- uint8_t p0 = port->cable << 4;
-
- if (b >= 0xf8) {
- f_midi_transmit_packet(req, p0 | 0x0f, b, 0, 0);
- } else if (b >= 0xf0) {
- switch (b) {
- case 0xf0:
- port->data[0] = b;
- port->state = STATE_SYSEX_1;
- break;
- case 0xf1:
- case 0xf3:
- port->data[0] = b;
- port->state = STATE_1PARAM;
- break;
- case 0xf2:
+ uint8_t p[4] = { port->cable << 4, 0, 0, 0 };
+ uint8_t next_state = STATE_INITIAL;
+
+ switch (port->state) {
+ case STATE_INITIAL: {
+ if (b >= 0xf8) {
+ p[0] |= 0x0f;
+ p[1] = b;
+ next_state = STATE_FINISHED;
+ } else if (b >= 0xf0) {
+ switch (b) {
+ case 0xf0:
+ port->data[0] = b;
+ next_state = STATE_SYSEX_1;
+ break;
+ case 0xf1:
+ case 0xf3:
+ port->data[0] = b;
+ next_state = STATE_1PARAM;
+ break;
+ case 0xf2:
+ port->data[0] = b;
+ next_state = STATE_2PARAM_1;
+ break;
+ case 0xf4:
+ case 0xf5:
+ next_state = STATE_INITIAL;
+ break;
+ case 0xf6:
+ p[0] |= 0x05;
+ p[1] = 0xf6;
+ next_state = STATE_FINISHED;
+ break;
+ }
+ } else if (b >= 0x80) {
port->data[0] = b;
- port->state = STATE_2PARAM_1;
- break;
- case 0xf4:
- case 0xf5:
- port->state = STATE_UNKNOWN;
- break;
- case 0xf6:
- f_midi_transmit_packet(req, p0 | 0x05, 0xf6, 0, 0);
- port->state = STATE_UNKNOWN;
- break;
- case 0xf7:
+ if (b >= 0xc0 && b <= 0xdf)
+ next_state = STATE_1PARAM;
+ else
+ next_state = STATE_2PARAM_1;
+ }
+ break;
+ }
+ case STATE_1PARAM:
+ case STATE_2PARAM_1:
+ case STATE_2PARAM_2:
+ case STATE_SYSEX_0:
+ case STATE_SYSEX_1:
+ case STATE_SYSEX_2: {
+ if (b == 0xf7) {
switch (port->state) {
case STATE_SYSEX_0:
- f_midi_transmit_packet(req,
- p0 | 0x05, 0xf7, 0, 0);
+ p[0] |= 0x05;
+ p[1] = 0xf7;
break;
case STATE_SYSEX_1:
- f_midi_transmit_packet(req,
- p0 | 0x06, port->data[0], 0xf7, 0);
+ p[0] |= 0x06;
+ p[1] = port->data[0];
+ p[2] = 0xf7;
break;
case STATE_SYSEX_2:
- f_midi_transmit_packet(req,
- p0 | 0x07, port->data[0],
- port->data[1], 0xf7);
+ p[0] |= 0x07;
+ p[1] = port->data[0];
+ p[2] = port->data[1];
+ p[3] = 0xf7;
break;
}
- port->state = STATE_UNKNOWN;
- break;
- }
- } else if (b >= 0x80) {
- port->data[0] = b;
- if (b >= 0xc0 && b <= 0xdf)
- port->state = STATE_1PARAM;
- else
- port->state = STATE_2PARAM_1;
- } else { /* b < 0x80 */
- switch (port->state) {
- case STATE_1PARAM:
- if (port->data[0] < 0xf0) {
- p0 |= port->data[0] >> 4;
- } else {
- p0 |= 0x02;
- port->state = STATE_UNKNOWN;
- }
- f_midi_transmit_packet(req, p0, port->data[0], b, 0);
- break;
- case STATE_2PARAM_1:
- port->data[1] = b;
- port->state = STATE_2PARAM_2;
- break;
- case STATE_2PARAM_2:
- if (port->data[0] < 0xf0) {
- p0 |= port->data[0] >> 4;
- port->state = STATE_2PARAM_1;
- } else {
- p0 |= 0x03;
- port->state = STATE_UNKNOWN;
+ next_state = STATE_FINISHED;
+ } else if (b < 0x80) {
+ switch (port->state) {
+ case STATE_1PARAM:
+ if (port->data[0] < 0xf0)
+ p[0] |= port->data[0] >> 4;
+ else
+ p[0] |= 0x02;
+
+ p[1] = port->data[0];
+ p[2] = b;
+ next_state = STATE_FINISHED;
+ break;
+ case STATE_2PARAM_1:
+ port->data[1] = b;
+ next_state = STATE_2PARAM_2;
+ break;
+ case STATE_2PARAM_2:
+ if (port->data[0] < 0xf0)
+ p[0] |= port->data[0] >> 4;
+ else
+ p[0] |= 0x03;
+
+ p[1] = port->data[0];
+ p[2] = port->data[1];
+ p[3] = b;
+ next_state = STATE_FINISHED;
+ break;
+ case STATE_SYSEX_0:
+ port->data[0] = b;
+ next_state = STATE_SYSEX_1;
+ break;
+ case STATE_SYSEX_1:
+ port->data[1] = b;
+ next_state = STATE_SYSEX_2;
+ break;
+ case STATE_SYSEX_2:
+ p[0] |= 0x04;
+ p[1] = port->data[0];
+ p[2] = port->data[1];
+ p[3] = b;
+ next_state = STATE_SYSEX_0;
+ break;
}
- f_midi_transmit_packet(req,
- p0, port->data[0], port->data[1], b);
- break;
- case STATE_SYSEX_0:
- port->data[0] = b;
- port->state = STATE_SYSEX_1;
- break;
- case STATE_SYSEX_1:
- port->data[1] = b;
- port->state = STATE_SYSEX_2;
- break;
- case STATE_SYSEX_2:
- f_midi_transmit_packet(req,
- p0 | 0x04, port->data[0], port->data[1], b);
- port->state = STATE_SYSEX_0;
- break;
+ } else {
+ /*
+ * Wrong MIDI message
+ * Reset state
+ */
+ next_state = STATE_INITIAL;
+ port->data[0] = 0;
+ port->data[1] = 0;
}
+ break;
+ }
}
+
+ /* States where we have to write into the USB request */
+ if (next_state == STATE_FINISHED ||
+ next_state == STATE_SYSEX_0) {
+ unsigned length = req->length;
+ u8 *buf = (u8 *)req->buf + length;
+
+ memcpy(buf, p, sizeof(p));
+ req->length = length + sizeof(p);
+
+ if (next_state == STATE_FINISHED)
+ next_state = STATE_INITIAL;
+
+ port->data[0] = port->data[1] = 0;
+ }
+
+ port->state = next_state;
}

static void f_midi_drop_out_substreams(struct f_midi *midi)
@@ -632,7 +671,7 @@ static int f_midi_in_open(struct snd_rawmidi_substream *substream)

VDBG(midi, "%s()\n", __func__);
midi->in_substream[substream->number] = substream;
- midi->in_port[substream->number]->state = STATE_UNKNOWN;
+ midi->in_port[substream->number]->state = STATE_INITIAL;
return 0;
}

--
2.5.0


2015-12-22 16:08:27

by Felipe Ferreri Tonello

[permalink] [raw]
Subject: [PATCH 2/2] usb: gadget: f_midi: added spinlock on transmit function

Since f_midi_transmit is called by both ALSA and USB frameworks, it can
potentially cause a race condition between both calls. This is bad because the
way f_midi_transmit is implemented can't handle concurrent calls. This is due
to the fact that the usb request fifo looks for the next element and only if
it has data to process it enqueues the request, otherwise re-uses it. If both
(ALSA and USB) frameworks calls this function at the same time, the
kfifo_seek() will return the same usb_request, which will cause a race
condition.

To solve this problem a syncronization mechanism is necessary. In this case it
is used a spinlock since f_midi_transmit is also called by usb_request->complete
callback in interrupt context.

On benchmarks realized by me, spinlocks were more efficient then scheduling
the f_midi_transmit tasklet in process context and using a mutex to
synchronize. Also it performs better then previous implementation that
allocated a usb_request for every new transmit made.

Signed-off-by: Felipe F. Tonello <[email protected]>
---
drivers/usb/gadget/function/f_midi.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c
index b70a830..00a15e9 100644
--- a/drivers/usb/gadget/function/f_midi.c
+++ b/drivers/usb/gadget/function/f_midi.c
@@ -24,6 +24,7 @@
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/kfifo.h>
+#include <linux/spinlock.h>

#include <sound/core.h>
#include <sound/initval.h>
@@ -97,6 +98,7 @@ struct f_midi {
/* This fifo is used as a buffer ring for pre-allocated IN usb_requests */
DECLARE_KFIFO_PTR(in_req_fifo, struct usb_request *);
unsigned int in_last_port;
+ spinlock_t transmit_lock;
};

static inline struct f_midi *func_to_midi(struct usb_function *f)
@@ -574,12 +576,15 @@ static void f_midi_drop_out_substreams(struct f_midi *midi)
static void f_midi_transmit(struct f_midi *midi)
{
struct usb_ep *ep = midi->in_ep;
+ unsigned long flags;
bool active;

/* We only care about USB requests if IN endpoint is enabled */
if (!ep || !ep->enabled)
goto drop_out;

+ spin_lock_irqsave(&midi->transmit_lock, flags);
+
do {
struct usb_request *req = NULL;
unsigned int len, i;
@@ -591,14 +596,17 @@ static void f_midi_transmit(struct f_midi *midi)
len = kfifo_peek(&midi->in_req_fifo, &req);
if (len != 1) {
ERROR(midi, "%s: Couldn't get usb request\n", __func__);
+ spin_unlock_irqrestore(&midi->transmit_lock, flags);
goto drop_out;
}

/* If buffer overrun, then we ignore this transmission.
* IMPORTANT: This will cause the user-space rawmidi device to block until a) usb
* requests have been completed or b) snd_rawmidi_write() times out. */
- if (req->length > 0)
+ if (req->length > 0) {
+ spin_unlock_irqrestore(&midi->transmit_lock, flags);
return;
+ }

for (i = midi->in_last_port; i < MAX_PORTS; i++) {
struct gmidi_in_port *port = midi->in_port[i];
@@ -650,6 +658,8 @@ static void f_midi_transmit(struct f_midi *midi)
}
} while (active);

+ spin_unlock_irqrestore(&midi->transmit_lock, flags);
+
return;

drop_out:
@@ -1255,6 +1265,8 @@ static struct usb_function *f_midi_alloc(struct usb_function_instance *fi)
if (status)
goto setup_fail;

+ spin_lock_init(&midi->transmit_lock);
+
++opts->refcnt;
mutex_unlock(&opts->lock);

--
2.5.0

2015-12-22 17:11:20

by Clemens Ladisch

[permalink] [raw]
Subject: Re: [PATCH 1/2] usb: gadget: f_midi: refactor state machine

Felipe F. Tonello wrote:
> This refactor includes the following:
> * Cleaner state machine code;

It does not correctly handle system real time messages inserted between
the status and data bytes of other messages.

> * Reset state if MIDI message parsed is non-conformant;

Why? In a byte stream like "C1 C3 44", where the data byte of the first
message was lost, the reset would also drop the second message.

> * Fixed bug when a conformant MIDI message was followed by a non-conformant
> causing the MIDI-USB message to use old temporary data (port->data[0..1]),
> thus packing a wrong MIDI-USB request.

Running status is feature.


Regards,
Clemens

2015-12-22 17:49:18

by Felipe Balbi

[permalink] [raw]
Subject: Re: [PATCH 1/2] usb: gadget: f_midi: refactor state machine


Hi,

"Felipe F. Tonello" <[email protected]> writes:
> This refactor includes the following:
> * Cleaner state machine code;
> * Reset state if MIDI message parsed is non-conformant;
> * Fixed bug when a conformant MIDI message was followed by a non-conformant
> causing the MIDI-USB message to use old temporary data (port->data[0..1]),
> thus packing a wrong MIDI-USB request.

we don't do more than one logical thing per patch. Please split this up.

--
balbi


Attachments:
signature.asc (818.00 B)

2015-12-23 09:20:08

by Felipe Ferreri Tonello

[permalink] [raw]
Subject: Re: [PATCH 1/2] usb: gadget: f_midi: refactor state machine

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Hi Balbi,

On 22/12/15 17:49, Felipe Balbi wrote:
>
> Hi,
>
> "Felipe F. Tonello" <[email protected]> writes:
>> This refactor includes the following: * Cleaner state machine
>> code; * Reset state if MIDI message parsed is non-conformant; *
>> Fixed bug when a conformant MIDI message was followed by a
>> non-conformant causing the MIDI-USB message to use old temporary
>> data (port->data[0..1]), thus packing a wrong MIDI-USB request.
>
> we don't do more than one logical thing per patch. Please split
> this up.

Actually this patch has only one logical change, the state machine
refactoring. But by doing it, those three items were a consequence.

Felipe
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQIcBAEBCAAGBQJWemc+AAoJEMxEtNCSaY5qpXwP/1B/sVsfapRtP4dw93YF6En5
w/ej9JatIyJIaXNxauCVzgRl9uuiXGyEqErRjJdodyvHar3yKvD9HEXE6MhowEp4
JMD/phN2v9Sdj1VxRf9Z0XDSWsg6huVhfQU47HBMRGiY8ezvEgir2bvg7dYbZMsv
ACgIx8oh6N/AEHPq9GoLbEpXfJ58Pl564Sq/2o6wWsJQS06A+jp+JmqK4Y3eB5M5
18rmLW7lQLcZPO08Pf/c6BExWQLbzY/mT8KofwycvC9hWxYp9LPwJY4oMzOWROe8
AZS1ayRqlabG3qx3dPRcV4j6c6uROEfQY+HejCn+Zbi0CfVYtsI+xO5LkwnZMUyc
0qvy90h0PUQ2e37/wo5YnnVLK0ce1Gm6gY50iFXwqE69m55KHTufsIVX4eTUBfcj
PtugPtTnKEsW171r/gHPYO9A+WKxycCvjPs9Wogsljly+NrRzgPMAI7Alx//4lwq
QhwRWF1BkNOoCPEpHnVlLkcIhgdcAbbnvWxlcAVlLAQ/oYl6ShbQFv96y/2IWCVO
Mwr7Ab8a/dnyJ/GWEQdpJUmfKGQbKNpM93H5yD4AQlmz4Hj620gzm3y2XNJY2bUt
8H37Q2VWtlcRy2UHjgBSeF0YKCvdDuDmZwRZbPpajkmbcYSGtJFDve1/L6bOtZSj
7nVfYIqvtIBWrDZ3PF9G
=3uC5
-----END PGP SIGNATURE-----


Attachments:
0x92698E6A.asc (7.03 kB)
0x92698E6A.asc.sig (543.00 B)
Download all attachments

2015-12-23 11:50:22

by Felipe Ferreri Tonello

[permalink] [raw]
Subject: Re: [PATCH 1/2] usb: gadget: f_midi: refactor state machine

Hi Clemens,

On 22/12/15 17:10, Clemens Ladisch wrote:
> Felipe F. Tonello wrote:
>> This refactor includes the following:
>> * Cleaner state machine code;
>
> It does not correctly handle system real time messages inserted between
> the status and data bytes of other messages.

True, thanks for pointing that out. I fixed that on next revision of
this patch.

>
>> * Reset state if MIDI message parsed is non-conformant;
>
> Why? In a byte stream like "C1 C3 44", where the data byte of the first
> message was lost, the reset would also drop the second message.

True. That was fixed as well.

>
>> * Fixed bug when a conformant MIDI message was followed by a non-conformant
>> causing the MIDI-USB message to use old temporary data (port->data[0..1]),
>> thus packing a wrong MIDI-USB request.
>
> Running status is feature.

What do you mean by that? I don't qualify writing a *wrong* MIDI-USB
packet because of a previous MIDI message as a feature.

For instance, try this MIDI message:
"8A 54 24 00 40"

It will be converted to MIDI-USB as "08 8A 54 24 08 8A 00 40" which is
wrong. It should only be "08 8A 54 24" and ignore the "00 40" MIDI bytes.

On every state byte the message should basically reset data[0..1] to
zero overwriting previous data. This should also be true when a MIDI-USB
packet is complete.

Felipe


Attachments:
0x92698E6A.asc (7.03 kB)

2015-12-23 21:53:28

by Clemens Ladisch

[permalink] [raw]
Subject: Re: [PATCH 1/2] usb: gadget: f_midi: refactor state machine

Felipe Ferreri Tonello wrote:
>> Running status is feature.
>
>What do you mean by that?

That this behavior is intended, and required.

> I don't qualify writing a *wrong* MIDI-USB
>packet because of a previous MIDI message as a feature.

The MIDI Specification qualifies Running Status as a feature.


Regards,
Clemens

2015-12-25 08:08:23

by Peter Chen

[permalink] [raw]
Subject: Re: [PATCH 2/2] usb: gadget: f_midi: added spinlock on transmit function

On Tue, Dec 22, 2015 at 04:08:06PM +0000, Felipe F. Tonello wrote:
> Since f_midi_transmit is called by both ALSA and USB frameworks, it can
> potentially cause a race condition between both calls. This is bad because the
> way f_midi_transmit is implemented can't handle concurrent calls. This is due
> to the fact that the usb request fifo looks for the next element and only if
> it has data to process it enqueues the request, otherwise re-uses it. If both
> (ALSA and USB) frameworks calls this function at the same time, the
> kfifo_seek() will return the same usb_request, which will cause a race
> condition.
>
> To solve this problem a syncronization mechanism is necessary. In this case it
> is used a spinlock since f_midi_transmit is also called by usb_request->complete
> callback in interrupt context.
>
> On benchmarks realized by me, spinlocks were more efficient then scheduling
> the f_midi_transmit tasklet in process context and using a mutex to
> synchronize. Also it performs better then previous implementation that

%s/then/than/

> allocated a usb_request for every new transmit made.
>
> Signed-off-by: Felipe F. Tonello <[email protected]>
> ---
> drivers/usb/gadget/function/f_midi.c | 14 +++++++++++++-
> 1 file changed, 13 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c
> index b70a830..00a15e9 100644
> --- a/drivers/usb/gadget/function/f_midi.c
> +++ b/drivers/usb/gadget/function/f_midi.c
> @@ -24,6 +24,7 @@
> #include <linux/slab.h>
> #include <linux/device.h>
> #include <linux/kfifo.h>
> +#include <linux/spinlock.h>
>
> #include <sound/core.h>
> #include <sound/initval.h>
> @@ -97,6 +98,7 @@ struct f_midi {
> /* This fifo is used as a buffer ring for pre-allocated IN usb_requests */
> DECLARE_KFIFO_PTR(in_req_fifo, struct usb_request *);
> unsigned int in_last_port;
> + spinlock_t transmit_lock;
> };
>
> static inline struct f_midi *func_to_midi(struct usb_function *f)
> @@ -574,12 +576,15 @@ static void f_midi_drop_out_substreams(struct f_midi *midi)
> static void f_midi_transmit(struct f_midi *midi)
> {
> struct usb_ep *ep = midi->in_ep;
> + unsigned long flags;
> bool active;
>
> /* We only care about USB requests if IN endpoint is enabled */
> if (!ep || !ep->enabled)
> goto drop_out;
>
> + spin_lock_irqsave(&midi->transmit_lock, flags);
> +
> do {
> struct usb_request *req = NULL;
> unsigned int len, i;
> @@ -591,14 +596,17 @@ static void f_midi_transmit(struct f_midi *midi)
> len = kfifo_peek(&midi->in_req_fifo, &req);
> if (len != 1) {
> ERROR(midi, "%s: Couldn't get usb request\n", __func__);
> + spin_unlock_irqrestore(&midi->transmit_lock, flags);
> goto drop_out;
> }
>
> /* If buffer overrun, then we ignore this transmission.
> * IMPORTANT: This will cause the user-space rawmidi device to block until a) usb
> * requests have been completed or b) snd_rawmidi_write() times out. */
> - if (req->length > 0)
> + if (req->length > 0) {
> + spin_unlock_irqrestore(&midi->transmit_lock, flags);
> return;
> + }
>
> for (i = midi->in_last_port; i < MAX_PORTS; i++) {
> struct gmidi_in_port *port = midi->in_port[i];
> @@ -650,6 +658,8 @@ static void f_midi_transmit(struct f_midi *midi)
> }
> } while (active);
>
> + spin_unlock_irqrestore(&midi->transmit_lock, flags);
> +
> return;
>
> drop_out:
> @@ -1255,6 +1265,8 @@ static struct usb_function *f_midi_alloc(struct usb_function_instance *fi)
> if (status)
> goto setup_fail;
>
> + spin_lock_init(&midi->transmit_lock);
> +
> ++opts->refcnt;
> mutex_unlock(&opts->lock);
>
> --
> 2.5.0
>
> --

--

Best Regards,
Peter Chen