Commit f42ab18c authored by Felipe F. Tonello's avatar Felipe F. Tonello Committed by Felipe Balbi

usb: gadget: f_midi: refactor state machine

This refactor results in a cleaner state machine code and promotes
consistency, readability, and maintanability of this driver.

This refactor state machine was well tested and it is currently running in
production code and devices.
Signed-off-by: default avatarFelipe F. Tonello <eu@felipetonello.com>
Signed-off-by: default avatarFelipe Balbi <felipe.balbi@linux.intel.com>
parent 7ea9fde7
...@@ -51,6 +51,19 @@ static const char f_midi_longname[] = "MIDI Gadget"; ...@@ -51,6 +51,19 @@ static const char f_midi_longname[] = "MIDI Gadget";
*/ */
#define MAX_PORTS 16 #define MAX_PORTS 16
/* MIDI message states */
enum {
STATE_INITIAL = 0, /* pseudo state */
STATE_1PARAM,
STATE_2PARAM_1,
STATE_2PARAM_2,
STATE_SYSEX_0,
STATE_SYSEX_1,
STATE_SYSEX_2,
STATE_REAL_TIME,
STATE_FINISHED, /* pseudo state */
};
/* /*
* This is a gadget, and the IN/OUT naming is from the host's perspective. * This is a gadget, and the IN/OUT naming is from the host's perspective.
* USB -> OUT endpoint -> rawmidi * USB -> OUT endpoint -> rawmidi
...@@ -61,13 +74,6 @@ struct gmidi_in_port { ...@@ -61,13 +74,6 @@ struct gmidi_in_port {
int active; int active;
uint8_t cable; uint8_t cable;
uint8_t state; 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]; uint8_t data[2];
}; };
...@@ -403,118 +409,166 @@ static int f_midi_snd_free(struct snd_device *device) ...@@ -403,118 +409,166 @@ static int f_midi_snd_free(struct snd_device *device)
return 0; 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. * Converts MIDI commands to USB MIDI packets.
*/ */
static void f_midi_transmit_byte(struct usb_request *req, static void f_midi_transmit_byte(struct usb_request *req,
struct gmidi_in_port *port, uint8_t b) struct gmidi_in_port *port, uint8_t b)
{ {
uint8_t p0 = port->cable << 4; uint8_t p[4] = { port->cable << 4, 0, 0, 0 };
uint8_t next_state = STATE_INITIAL;
switch (b) {
case 0xf8 ... 0xff:
/* System Real-Time Messages */
p[0] |= 0x0f;
p[1] = b;
next_state = port->state;
port->state = STATE_REAL_TIME;
break;
case 0xf7:
/* End of SysEx */
switch (port->state) {
case STATE_SYSEX_0:
p[0] |= 0x05;
p[1] = 0xf7;
next_state = STATE_FINISHED;
break;
case STATE_SYSEX_1:
p[0] |= 0x06;
p[1] = port->data[0];
p[2] = 0xf7;
next_state = STATE_FINISHED;
break;
case STATE_SYSEX_2:
p[0] |= 0x07;
p[1] = port->data[0];
p[2] = port->data[1];
p[3] = 0xf7;
next_state = STATE_FINISHED;
break;
default:
/* Ignore byte */
next_state = port->state;
port->state = STATE_INITIAL;
}
break;
if (b >= 0xf8) { case 0xf0 ... 0xf6:
f_midi_transmit_packet(req, p0 | 0x0f, b, 0, 0); /* System Common Messages */
} else if (b >= 0xf0) { port->data[0] = port->data[1] = 0;
port->state = STATE_INITIAL;
switch (b) { switch (b) {
case 0xf0: case 0xf0:
port->data[0] = b; port->data[0] = b;
port->state = STATE_SYSEX_1; port->data[1] = 0;
next_state = STATE_SYSEX_1;
break; break;
case 0xf1: case 0xf1:
case 0xf3: case 0xf3:
port->data[0] = b; port->data[0] = b;
port->state = STATE_1PARAM; next_state = STATE_1PARAM;
break; break;
case 0xf2: case 0xf2:
port->data[0] = b; port->data[0] = b;
port->state = STATE_2PARAM_1; next_state = STATE_2PARAM_1;
break; break;
case 0xf4: case 0xf4:
case 0xf5: case 0xf5:
port->state = STATE_UNKNOWN; next_state = STATE_INITIAL;
break; break;
case 0xf6: case 0xf6:
f_midi_transmit_packet(req, p0 | 0x05, 0xf6, 0, 0); p[0] |= 0x05;
port->state = STATE_UNKNOWN; p[1] = 0xf6;
break; next_state = STATE_FINISHED;
case 0xf7:
switch (port->state) {
case STATE_SYSEX_0:
f_midi_transmit_packet(req,
p0 | 0x05, 0xf7, 0, 0);
break;
case STATE_SYSEX_1:
f_midi_transmit_packet(req,
p0 | 0x06, port->data[0], 0xf7, 0);
break;
case STATE_SYSEX_2:
f_midi_transmit_packet(req,
p0 | 0x07, port->data[0],
port->data[1], 0xf7);
break;
}
port->state = STATE_UNKNOWN;
break; break;
} }
} else if (b >= 0x80) { break;
case 0x80 ... 0xef:
/*
* Channel Voice Messages, Channel Mode Messages
* and Control Change Messages.
*/
port->data[0] = b; port->data[0] = b;
port->data[1] = 0;
port->state = STATE_INITIAL;
if (b >= 0xc0 && b <= 0xdf) if (b >= 0xc0 && b <= 0xdf)
port->state = STATE_1PARAM; next_state = STATE_1PARAM;
else else
port->state = STATE_2PARAM_1; next_state = STATE_2PARAM_1;
} else { /* b < 0x80 */ break;
case 0x00 ... 0x7f:
/* Message parameters */
switch (port->state) { switch (port->state) {
case STATE_1PARAM: case STATE_1PARAM:
if (port->data[0] < 0xf0) { if (port->data[0] < 0xf0)
p0 |= port->data[0] >> 4; p[0] |= port->data[0] >> 4;
} else { else
p0 |= 0x02; p[0] |= 0x02;
port->state = STATE_UNKNOWN;
} p[1] = port->data[0];
f_midi_transmit_packet(req, p0, port->data[0], b, 0); p[2] = b;
/* This is to allow Running State Messages */
next_state = STATE_1PARAM;
break; break;
case STATE_2PARAM_1: case STATE_2PARAM_1:
port->data[1] = b; port->data[1] = b;
port->state = STATE_2PARAM_2; next_state = STATE_2PARAM_2;
break; break;
case STATE_2PARAM_2: case STATE_2PARAM_2:
if (port->data[0] < 0xf0) { if (port->data[0] < 0xf0)
p0 |= port->data[0] >> 4; p[0] |= port->data[0] >> 4;
port->state = STATE_2PARAM_1; else
} else { p[0] |= 0x03;
p0 |= 0x03;
port->state = STATE_UNKNOWN; p[1] = port->data[0];
} p[2] = port->data[1];
f_midi_transmit_packet(req, p[3] = b;
p0, port->data[0], port->data[1], b); /* This is to allow Running State Messages */
next_state = STATE_2PARAM_1;
break; break;
case STATE_SYSEX_0: case STATE_SYSEX_0:
port->data[0] = b; port->data[0] = b;
port->state = STATE_SYSEX_1; next_state = STATE_SYSEX_1;
break; break;
case STATE_SYSEX_1: case STATE_SYSEX_1:
port->data[1] = b; port->data[1] = b;
port->state = STATE_SYSEX_2; next_state = STATE_SYSEX_2;
break; break;
case STATE_SYSEX_2: case STATE_SYSEX_2:
f_midi_transmit_packet(req, p[0] |= 0x04;
p0 | 0x04, port->data[0], port->data[1], b); p[1] = port->data[0];
port->state = STATE_SYSEX_0; p[2] = port->data[1];
p[3] = b;
next_state = STATE_SYSEX_0;
break; break;
} }
break;
}
/* States where we have to write into the USB request */
if (next_state == STATE_FINISHED ||
port->state == STATE_SYSEX_2 ||
port->state == STATE_1PARAM ||
port->state == STATE_2PARAM_2 ||
port->state == STATE_REAL_TIME) {
unsigned int 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) static void f_midi_drop_out_substreams(struct f_midi *midi)
...@@ -641,7 +695,7 @@ static int f_midi_in_open(struct snd_rawmidi_substream *substream) ...@@ -641,7 +695,7 @@ static int f_midi_in_open(struct snd_rawmidi_substream *substream)
VDBG(midi, "%s()\n", __func__); VDBG(midi, "%s()\n", __func__);
port = midi->in_ports_array + substream->number; port = midi->in_ports_array + substream->number;
port->substream = substream; port->substream = substream;
port->state = STATE_UNKNOWN; port->state = STATE_INITIAL;
return 0; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment