ALSA: firewire-lib: add throttle for MIDI data rate
authorTakashi Sakamoto <o-takashi@sakamocchi.jp>
Thu, 8 Oct 2015 23:10:28 +0000 (08:10 +0900)
committerTakashi Iwai <tiwai@suse.de>
Fri, 9 Oct 2015 07:57:06 +0000 (09:57 +0200)
Typically, the target devices have internal buffer to adjust output of
received MIDI messages for MIDI serial bus, while the capacity of the
buffer is limited. IEEE 1394 transactions can transfer more MIDI messages
than MIDI serial bus can. This can cause buffer over flow in device side.

This commit adds throttle to limit MIDI data rate by counting intervals
between two MIDI messages. Usual MIDI messages consists of two or three
bytes. This requires 1.302 to 1.953 mili-seconds interval between these
messages. This commit uses kernel monotonic time service to calculate the
time of next transaction.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/firewire/lib.c
sound/firewire/lib.h

index 3e9afd7d402ca1d5832d45a3f01cd251e3aeb68c..9a98c7cd8744878cba39fcaf139b6d2ff1007646 100644 (file)
@@ -76,6 +76,9 @@ static void async_midi_port_callback(struct fw_card *card, int rcode,
 
        if (rcode == RCODE_COMPLETE && substream != NULL)
                snd_rawmidi_transmit_ack(substream, port->consume_bytes);
+       else if (!rcode_is_permanent_error(rcode))
+               /* To start next transaction immediately for recovery. */
+               port->next_ktime = ktime_set(0, 0);
 
        port->idling = true;
 
@@ -99,6 +102,12 @@ static void midi_port_work(struct work_struct *work)
        if (substream == NULL || snd_rawmidi_transmit_empty(substream))
                return;
 
+       /* Do it in next chance. */
+       if (ktime_after(port->next_ktime, ktime_get())) {
+               schedule_work(&port->work);
+               return;
+       }
+
        /*
         * Fill the buffer. The callee must use snd_rawmidi_transmit_peek().
         * Later, snd_rawmidi_transmit_ack() is called.
@@ -107,8 +116,10 @@ static void midi_port_work(struct work_struct *work)
        port->consume_bytes = port->fill(substream, port->buf);
        if (port->consume_bytes <= 0) {
                /* Do it in next chance, immediately. */
-               if (port->consume_bytes == 0)
+               if (port->consume_bytes == 0) {
+                       port->next_ktime = ktime_set(0, 0);
                        schedule_work(&port->work);
+               }
                return;
        }
 
@@ -118,6 +129,10 @@ static void midi_port_work(struct work_struct *work)
        else
                type = TCODE_WRITE_BLOCK_REQUEST;
 
+       /* Set interval to next transaction. */
+       port->next_ktime = ktime_add_ns(ktime_get(),
+                               port->consume_bytes * 8 * NSEC_PER_SEC / 31250);
+
        /* Start this transaction. */
        port->idling = false;
 
@@ -162,6 +177,7 @@ int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port,
        port->addr = addr;
        port->fill = fill;
        port->idling = true;
+       port->next_ktime = ktime_set(0, 0);
 
        INIT_WORK(&port->work, midi_port_work);
 
index 0af06f44e8c27bd71a94744ee354815c9a5628a8..59e08658721268e24e17562b58f73a03f18ec060 100644 (file)
@@ -31,6 +31,7 @@ struct snd_fw_async_midi_port {
        struct fw_device *parent;
        struct work_struct work;
        bool idling;
+       ktime_t next_ktime;
 
        u64 addr;
        struct fw_transaction transaction;