Skip to content

Commit

Permalink
usb: gadget: f_midi: pre-allocate IN requests
Browse files Browse the repository at this point in the history
This patch introduces pre-allocation of IN endpoint USB requests. This
improves on latency (requires no usb request allocation on transmit) and avoid
several potential probles on allocating too many usb requests (which involves
DMA pool allocation problems).

This implementation also handles better multiple MIDI Gadget ports, always
processing the last processed MIDI substream if the last USB request wasn't
enought to handle the whole stream.

Signed-off-by: Felipe F. Tonello <eu@felipetonello.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
  • Loading branch information
Felipe F. Tonello authored and Felipe Balbi committed Dec 16, 2015
1 parent f0f1b8c commit e1e3d7e
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 39 deletions.
166 changes: 128 additions & 38 deletions drivers/usb/gadget/function/f_midi.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/kfifo.h>

#include <sound/core.h>
#include <sound/initval.h>
Expand Down Expand Up @@ -88,14 +89,17 @@ struct f_midi {
int index;
char *id;
unsigned int buflen, qlen;
/* 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;
};

static inline struct f_midi *func_to_midi(struct usb_function *f)
{
return container_of(f, struct f_midi, func);
}

static void f_midi_transmit(struct f_midi *midi, struct usb_request *req);
static void f_midi_transmit(struct f_midi *midi);

DECLARE_UAC_AC_HEADER_DESCRIPTOR(1);
DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1);
Expand Down Expand Up @@ -253,7 +257,8 @@ f_midi_complete(struct usb_ep *ep, struct usb_request *req)
} else if (ep == midi->in_ep) {
/* Our transmit completed. See if there's more to go.
* f_midi_transmit eats req, don't queue it again. */
f_midi_transmit(midi, req);
req->length = 0;
f_midi_transmit(midi);
return;
}
break;
Expand All @@ -264,10 +269,12 @@ f_midi_complete(struct usb_ep *ep, struct usb_request *req)
case -ESHUTDOWN: /* disconnect from host */
VDBG(cdev, "%s gone (%d), %d/%d\n", ep->name, status,
req->actual, req->length);
if (ep == midi->out_ep)
if (ep == midi->out_ep) {
f_midi_handle_out_data(ep, req);

free_ep_req(ep, req);
/* We don't need to free IN requests because it's handled
* by the midi->in_req_fifo. */
free_ep_req(ep, req);
}
return;

case -EOVERFLOW: /* buffer overrun on read means that
Expand Down Expand Up @@ -334,6 +341,20 @@ static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
if (err)
return err;

/* pre-allocate write usb requests to use on f_midi_transmit. */
while (kfifo_avail(&midi->in_req_fifo)) {
struct usb_request *req =
midi_alloc_ep_req(midi->in_ep, midi->buflen);

if (req == NULL)
return -ENOMEM;

req->length = 0;
req->complete = f_midi_complete;

kfifo_put(&midi->in_req_fifo, req);
}

/* allocate a bunch of read buffers and queue them all at once. */
for (i = 0; i < midi->qlen && err == 0; i++) {
struct usb_request *req =
Expand All @@ -358,6 +379,7 @@ static void f_midi_disable(struct usb_function *f)
{
struct f_midi *midi = func_to_midi(f);
struct usb_composite_dev *cdev = f->config->cdev;
struct usb_request *req = NULL;

DBG(cdev, "disable\n");

Expand All @@ -367,6 +389,10 @@ static void f_midi_disable(struct usb_function *f)
*/
usb_ep_disable(midi->in_ep);
usb_ep_disable(midi->out_ep);

/* release IN requests */
while (kfifo_get(&midi->in_req_fifo, &req))
free_ep_req(midi->in_ep, req);
}

static int f_midi_snd_free(struct snd_device *device)
Expand Down Expand Up @@ -488,57 +514,113 @@ static void f_midi_transmit_byte(struct usb_request *req,
}
}

static void f_midi_transmit(struct f_midi *midi, struct usb_request *req)
static void f_midi_drop_out_substreams(struct f_midi *midi)
{
struct usb_ep *ep = midi->in_ep;
int i;

if (!ep)
return;

if (!req)
req = midi_alloc_ep_req(ep, midi->buflen);

if (!req) {
ERROR(midi, "%s: alloc_ep_request failed\n", __func__);
return;
}
req->length = 0;
req->complete = f_midi_complete;
unsigned int i;

for (i = 0; i < MAX_PORTS; i++) {
struct gmidi_in_port *port = midi->in_port[i];
struct snd_rawmidi_substream *substream = midi->in_substream[i];

if (!port || !port->active || !substream)
if (!port)
break;

if (!port->active || !substream)
continue;

while (req->length + 3 < midi->buflen) {
uint8_t b;
if (snd_rawmidi_transmit(substream, &b, 1) != 1) {
port->active = 0;
snd_rawmidi_drop_output(substream);
}
}

static void f_midi_transmit(struct f_midi *midi)
{
struct usb_ep *ep = midi->in_ep;
bool active;

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

do {
struct usb_request *req = NULL;
unsigned int len, i;

active = false;

/* We peek the request in order to reuse it if it fails
* to enqueue on its endpoint */
len = kfifo_peek(&midi->in_req_fifo, &req);
if (len != 1) {
ERROR(midi, "%s: Couldn't get usb request\n", __func__);
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)
return;

for (i = midi->in_last_port; i < MAX_PORTS; i++) {
struct gmidi_in_port *port = midi->in_port[i];
struct snd_rawmidi_substream *substream = midi->in_substream[i];

if (!port) {
/* Reset counter when we reach the last available port */
midi->in_last_port = 0;
break;
}

if (!port->active || !substream)
continue;

while (req->length + 3 < midi->buflen) {
uint8_t b;

if (snd_rawmidi_transmit(substream, &b, 1) != 1) {
port->active = 0;
break;
}
f_midi_transmit_byte(req, port, b);
}

active = !!port->active;
/* Check if last port is still active, which means that
* there is still data on that substream but this current
* request run out of space. */
if (active) {
midi->in_last_port = i;
/* There is no need to re-iterate though midi ports. */
break;
}
f_midi_transmit_byte(req, port, b);
}
}

if (req->length > 0 && ep->enabled) {
int err;
if (req->length > 0) {
int err;

err = usb_ep_queue(ep, req, GFP_ATOMIC);
if (err < 0)
ERROR(midi, "%s queue req: %d\n",
midi->in_ep->name, err);
} else {
free_ep_req(ep, req);
}
err = usb_ep_queue(ep, req, GFP_ATOMIC);
if (err < 0) {
ERROR(midi, "%s failed to queue req: %d\n",
midi->in_ep->name, err);
req->length = 0; /* Re-use request next time. */
} else {
/* Upon success, put request at the back of the queue. */
kfifo_skip(&midi->in_req_fifo);
kfifo_put(&midi->in_req_fifo, req);
}
}
} while (active);

return;

drop_out:
f_midi_drop_out_substreams(midi);
}

static void f_midi_in_tasklet(unsigned long data)
{
struct f_midi *midi = (struct f_midi *) data;
f_midi_transmit(midi, NULL);
f_midi_transmit(midi);
}

static int f_midi_in_open(struct snd_rawmidi_substream *substream)
Expand Down Expand Up @@ -664,6 +746,7 @@ static int f_midi_register_card(struct f_midi *midi)
goto fail;
}
midi->rmidi = rmidi;
midi->in_last_port = 0;
strcpy(rmidi->name, card->shortname);
rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
Expand Down Expand Up @@ -1053,6 +1136,7 @@ static void f_midi_free(struct usb_function *f)
mutex_lock(&opts->lock);
for (i = opts->in_ports - 1; i >= 0; --i)
kfree(midi->in_port[i]);
kfifo_free(&midi->in_req_fifo);
kfree(midi);
--opts->refcnt;
mutex_unlock(&opts->lock);
Expand Down Expand Up @@ -1126,6 +1210,12 @@ static struct usb_function *f_midi_alloc(struct usb_function_instance *fi)
midi->index = opts->index;
midi->buflen = opts->buflen;
midi->qlen = opts->qlen;
midi->in_last_port = 0;

status = kfifo_alloc(&midi->in_req_fifo, midi->qlen, GFP_KERNEL);
if (status)
goto setup_fail;

++opts->refcnt;
mutex_unlock(&opts->lock);

Expand Down
2 changes: 1 addition & 1 deletion drivers/usb/gadget/legacy/gmidi.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ MODULE_PARM_DESC(buflen, "MIDI buffer length");

static unsigned int qlen = 32;
module_param(qlen, uint, S_IRUGO);
MODULE_PARM_DESC(qlen, "USB read request queue length");
MODULE_PARM_DESC(qlen, "USB read and write request queue length");

static unsigned int in_ports = 1;
module_param(in_ports, uint, S_IRUGO);
Expand Down

0 comments on commit e1e3d7e

Please sign in to comment.