usb: gadget: f_midi: refactor state machine
authorFelipe F. Tonello <eu@felipetonello.com>
Mon, 8 Aug 2016 20:30:09 +0000 (21:30 +0100)
committerFelipe Balbi <felipe.balbi@linux.intel.com>
Thu, 25 Aug 2016 09:13:15 +0000 (12:13 +0300)
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: Felipe F. Tonello <eu@felipetonello.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
drivers/usb/gadget/function/f_midi.c

index a7b50ac947f865e50b3df73a3545d1a689392ff1..09d769e18b50511b8bead86c47e3c6d1ec691b6d 100644 (file)
@@ -51,6 +51,19 @@ static const char f_midi_longname[] = "MIDI Gadget";
  */
 #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.
  * USB -> OUT endpoint -> rawmidi
@@ -61,13 +74,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];
 };
 
@@ -403,118 +409,166 @@ 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;
+       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) {
-               f_midi_transmit_packet(req, p0 | 0x0f, b, 0, 0);
-       } else if (b >= 0xf0) {
+       case 0xf0 ... 0xf6:
+               /* System Common Messages */
+               port->data[0] = port->data[1] = 0;
+               port->state = STATE_INITIAL;
                switch (b) {
                case 0xf0:
                        port->data[0] = b;
-                       port->state = STATE_SYSEX_1;
+                       port->data[1] = 0;
+                       next_state = STATE_SYSEX_1;
                        break;
                case 0xf1:
                case 0xf3:
                        port->data[0] = b;
-                       port->state = STATE_1PARAM;
+                       next_state = STATE_1PARAM;
                        break;
                case 0xf2:
                        port->data[0] = b;
-                       port->state = STATE_2PARAM_1;
+                       next_state = STATE_2PARAM_1;
                        break;
                case 0xf4:
                case 0xf5:
-                       port->state = STATE_UNKNOWN;
+                       next_state = STATE_INITIAL;
                        break;
                case 0xf6:
-                       f_midi_transmit_packet(req, p0 | 0x05, 0xf6, 0, 0);
-                       port->state = STATE_UNKNOWN;
-                       break;
-               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;
+                       p[0] |= 0x05;
+                       p[1] = 0xf6;
+                       next_state = STATE_FINISHED;
                        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[1] = 0;
+               port->state = STATE_INITIAL;
                if (b >= 0xc0 && b <= 0xdf)
-                       port->state = STATE_1PARAM;
+                       next_state = STATE_1PARAM;
                else
-                       port->state = STATE_2PARAM_1;
-       } else { /* b < 0x80 */
+                       next_state = STATE_2PARAM_1;
+               break;
+
+       case 0x00 ... 0x7f:
+               /* Message parameters */
                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);
+                       if (port->data[0] < 0xf0)
+                               p[0] |= port->data[0] >> 4;
+                       else
+                               p[0] |= 0x02;
+
+                       p[1] = port->data[0];
+                       p[2] = b;
+                       /* This is to allow Running State Messages */
+                       next_state = STATE_1PARAM;
                        break;
                case STATE_2PARAM_1:
                        port->data[1] = b;
-                       port->state = STATE_2PARAM_2;
+                       next_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;
-                       }
-                       f_midi_transmit_packet(req,
-                               p0, port->data[0], port->data[1], b);
+                       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;
+                       /* This is to allow Running State Messages */
+                       next_state = STATE_2PARAM_1;
                        break;
                case STATE_SYSEX_0:
                        port->data[0] = b;
-                       port->state = STATE_SYSEX_1;
+                       next_state = STATE_SYSEX_1;
                        break;
                case STATE_SYSEX_1:
                        port->data[1] = b;
-                       port->state = STATE_SYSEX_2;
+                       next_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;
+                       p[0] |= 0x04;
+                       p[1] = port->data[0];
+                       p[2] = port->data[1];
+                       p[3] = b;
+                       next_state = STATE_SYSEX_0;
                        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)
@@ -641,7 +695,7 @@ static int f_midi_in_open(struct snd_rawmidi_substream *substream)
        VDBG(midi, "%s()\n", __func__);
        port = midi->in_ports_array + substream->number;
        port->substream = substream;
-       port->state = STATE_UNKNOWN;
+       port->state = STATE_INITIAL;
        return 0;
 }