Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752933AbbKYQyQ (ORCPT ); Wed, 25 Nov 2015 11:54:16 -0500 Received: from mail-wm0-f43.google.com ([74.125.82.43]:36794 "EHLO mail-wm0-f43.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752156AbbKYQyM (ORCPT ); Wed, 25 Nov 2015 11:54:12 -0500 Subject: Re: [PATCH v5 7/7] usb: gadget: f_midi: pre-allocate IN requests To: Robert Baldyga , linux-usb@vger.kernel.org References: <1447177929-22252-1-git-send-email-eu@felipetonello.com> <1447177929-22252-8-git-send-email-eu@felipetonello.com> <5645D9C1.30006@samsung.com> Cc: linux-kernel@vger.kernel.org, Felipe Balbi , Greg Kroah-Hartman , Clemens Ladisch From: Felipe Ferreri Tonello X-Enigmail-Draft-Status: N1111 Message-ID: <5655E7B1.1050600@felipetonello.com> Date: Wed, 25 Nov 2015 16:54:09 +0000 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.3.0 MIME-Version: 1.0 In-Reply-To: <5645D9C1.30006@samsung.com> Content-Type: multipart/mixed; boundary="------------090207080108000402040906" Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 16053 Lines: 430 This is a multi-part message in MIME format. --------------090207080108000402040906 Content-Type: text/plain; charset=windows-1252 Content-Transfer-Encoding: 8bit Hi Robert, On 13/11/15 12:38, Robert Baldyga wrote: > Hi Felipe, > > On 11/10/2015 06:52 PM, Felipe F. Tonello wrote: >> 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 >> --- >> drivers/usb/gadget/function/f_midi.c | 174 +++++++++++++++++++++++++++-------- >> drivers/usb/gadget/legacy/gmidi.c | 2 +- >> 2 files changed, 137 insertions(+), 39 deletions(-) >> >> diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c >> index 615d632..6ac39f6 100644 >> --- a/drivers/usb/gadget/function/f_midi.c >> +++ b/drivers/usb/gadget/function/f_midi.c >> @@ -23,6 +23,7 @@ >> #include >> #include >> #include >> +#include >> >> #include >> #include >> @@ -88,6 +89,9 @@ struct f_midi { >> int index; >> char *id; >> unsigned int buflen, qlen; >> + DECLARE_KFIFO_PTR(in_req_fifo, struct usb_request *); >> + unsigned int in_req_num; >> + unsigned int in_last_port; >> }; >> >> static inline struct f_midi *func_to_midi(struct usb_function *f) >> @@ -95,7 +99,7 @@ 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); >> @@ -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; >> @@ -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 >> @@ -334,6 +341,21 @@ 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; > > We need to free previously allocated requests in case of failure. > >> + >> + req->length = 0; >> + req->complete = f_midi_complete; >> + >> + kfifo_put(&midi->in_req_fifo, req); >> + midi->in_req_num++; >> + } >> + >> /* 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 = >> @@ -366,6 +388,20 @@ 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_is_empty(&midi->in_req_fifo)) { >> + struct usb_request *req = NULL; >> + unsigned int len; >> + >> + len = kfifo_get(&midi->in_req_fifo, &req); >> + if (len == 1) >> + free_ep_req(midi->in_ep, req); >> + else >> + ERROR(midi, "%s couldn't free usb request: something went wrong with kfifo\n", >> + midi->in_ep->name); >> + } >> + midi->in_req_num = 0; >> } >> >> static int f_midi_snd_free(struct snd_device *device) >> @@ -487,57 +523,111 @@ 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 (req->length > 0) { >> + WARNING(midi, "%s: All USB requests have been used. Current queue size " >> + "is %u, consider increasing it.\n", __func__, midi->in_req_num); >> + goto drop_out; >> + } >> + >> + 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); > > There are simpler and clearer ways to implement cyclic buffer than using > kfifo. To be honest, it took ma a while to realize what's going on. As > long as you mark unused requests by setting req->length to zero you only > need to store index of last used req to be able to achieve the same effect. That is true but that's exactly what I am trying to avoid here. I didn't want to add a counter and deal with counter++ and counter-- all the time. kfifo is fast and has a clean and nice interface to deal with that. I can write a comment right next to it just to make things clearer. > >> + } >> + } >> + } while (active); >> + >> +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) >> @@ -663,6 +753,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 | >> @@ -1057,6 +1148,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); >> @@ -1130,6 +1222,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_req_num = 0; >> + midi->in_last_port = 0; >> + >> + if (kfifo_alloc(&midi->in_req_fifo, midi->qlen, GFP_KERNEL)) >> + goto setup_fail; >> + >> ++opts->refcnt; >> mutex_unlock(&opts->lock); >> >> diff --git a/drivers/usb/gadget/legacy/gmidi.c b/drivers/usb/gadget/legacy/gmidi.c >> index be8e91d..f68c188 100644 >> --- a/drivers/usb/gadget/legacy/gmidi.c >> +++ b/drivers/usb/gadget/legacy/gmidi.c >> @@ -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); >> > > Best regards, > Robert > Felipe --------------090207080108000402040906 Content-Type: application/pgp-keys; name="0x92698E6A.asc" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="0x92698E6A.asc" -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 mQINBFYedIcBEACVKGKoEjb3zlvAz5SUvBej7Sx13BPd8hVulQD+mqjfuRFPmZA5 LBXPX1zTRWwGEbbZegP3tLfKP+XekzO6BQhDihMmKuRusdgDsdMtldwhjHuUwKn7 kxB2k79jSG802lAjIv2l5hijOfKIGTATKwiMijuXho54DGltIgNyN/Onwk9HnM6d jsV5uubaI468JRH6j8HXXievo24BDvsimIE75ImiM53ruiwPwEry1hi1CnE5OpqG oe/lt27+nLXijfNZOpBZ3Q+RPVBdqPTkMlBJAa4sg+qwZoSMvQJFAGROiJ7+ICCW O4GPMrAn8CRcCI9ENKBj2dQ4bBEP1a+f16GNMUUU37wocqtyNHo0Pa/DnFh91kcu /2dvUX4XPeEimEoSKroRLOXC9RGSFYB/r9UXqFgbmyQ4TZLx0mAWIjoUQtbIJNRz Pt46UeznCVLJTg7CzIvtv8vwmMFvaepr8ONoZn+tpX8VW4dgzsMZDrVspE0Vg3oo K9JRi1nN3GcJJK4zG2ShvEkPffRHuBuyX5wR8MPRYTShKnJR5qd1cCSK73fCv4DP bjywmGjucqcLiyYbByjmHaqzRaKJclmT/jhs6qZHs+pVLkmHkHdf/WLP6Xgcvmo8 c+SATrJwRsyW9riyMB7uNg3T84umbQrl00nAhcq73rem/602H+Qrh8rEfQARAQAB tDFGZWxpcGUgRmVycmVyaSBUb25lbGxvIDxmZWxpcGUudG9uZWxsb0BnbWFpbC5j b20+iQI/BBMBCAApAhsjBQkJZgGABwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AF AlYyWesACgkQzES00JJpjmqfNw/9ELfxQvSyFbETmUzeCUV410snjN/fnLSdJl86 9FnG3PQ+jkAD8Y2VDTjqBpH7UxyYHiaR7XmSY0AYNYJlhgbdEmmBiPS2YXwM3Io4 f5362/AYlW5YXsQm1EAtLwT0+Q6ZABUjkW9G2nkgwnOwVUDw2kVBLY4XID6MUDSW 5DFSmadgkHX2VlNypY//EynTFaDHd6xTbxbKDAsDJPDybXFkjFwyOKOQSjN8giyI Em8Sl0fayArsjITDzgC0dZ6EH6Rcl0x3tkf5NjmJWVZjFia0zGqiiLjcE9aeg3Ca 59Ll3X8fxAxetIoadd3lj0AcYUMzKVrBVSzHXL2HM6u5rCDbbWvUpD7MDfiVD+8k 6EaJe+465EPUzQYRIYObitZGUJVkvBQTe/Zd26/aWtbi0pBIl0oF2cQuN45ruFYj paDmiiEEiD6owTCXKg1QM+iE6RCAt+gOya8kpprKnRH9wqmlANuMRSmNubyH815y /vfZTnZX/QlMQUptOZQQKF1fovq5/e3Z1v2DTfWE51U3EPfNlAQWvnEKxLdB+8MY UFUCiXLilCfYX9di+0UQn6oA/xpz/X/9g1hBsk+xwkPnzCFRTEPb/iJ1armQPr4Q M6DchqsSCUh6hFIVZB8m68hbSdK9lgZoftwPVwXz5JHhQqkDmqRG7T5FawPcM2nb kKMD9j60MEZlbGlwZSBGZXJyZXJpIFRvbmVsbG8gPGZlbGlwZS50b25lbGxvQHJv bGkuY29tPokCPwQTAQgAKQUCVjJZfgIbIwUJCWYBgAcLCQgHAwIBBhUIAgkKCwQW AgMBAh4BAheAAAoJEMxEtNCSaY5qYooP/3OF2K+l3xzQhMMozfHrNu80slsrq83r UuaUuwXWTA+78fi1riLJPKatoFXD1prwDLxMehyvTf6Du6LD5ADUUsh5I+3hSPdu 9Sp5n+ZD5MTx14I0Bquar5pBqoyvS3P1+50SPbFdNbYH3neu0L6jahK9wIt86V6c HEaLPq7+lpXe7p6Qoh7Y6WYTXgAR4E6qBBOhGZ2hsA9owGVXGMaQ5ExXfZQ5CQN4 JIZSwwzxqikLWzdpYvC7jxcrKlCX5ssxR70xGeEc88RLxeZ3ll6UW8Txlk+u/y/J k6U3X/Mdlz2ZufBzs4cM4bz6YjU4Y8HWL/y4JewtnfjgSsn4tvzYYi5Sc/8uYFJ6 nSsNHOECVnoyAjdN//Ise55GKhBcdojQmNlEPpqEOvC74D13XHL4NdeqWdp4RPOr 5HwDXTd6darGy5PjXFEivO74waaZnXqT4IgqNAIAvaZZryi7O6kDVKp4zeB9zRF+ KkLOYlXPy3DmbM+LR+L+rHIEGnhrrrytYgFU1+3wAmXrggDe+qIPxMPlqNxi1DCo 84ABfNLmSdLlqARPdocPw2JXB7gkN1GHVzxpwf0K+zlEYdl+Y2ufuGQQ1fGFH2Lh 0jStcV5EIsB9hIF0Z8pUWCluOxNNXFbl/rt99q3Ohm3gnhPhFl/PX073+yEe9C5f doxqomHb/sGstC1GZWxpcGUgRmVycmVyaSBUb25lbGxvIDxldUBmZWxpcGV0b25l bGxvLmNvbT6JAkIEEwEIACwCGyMFCQlmAYAHCwkIBwMCAQYVCAIJCgsEFgIDAQIe AQIXgAUCVjJZ6wIZAQAKCRDMRLTQkmmOalv1D/sGkOyOpDOnwv+JDaYs7uPiiUq6 4QumHqAH8rELDjZGzMlcWRYl6egAXeUE1+WG6G5Q61kI8pEAi4IhhvyaYLHqpmfL c5N0dP27JSaDjN6JqMcZqFDKXLk1tKEKTc+Etw/lT9kaE8a7IpZrTeFWFOPypFyN 22vIeMx9DLLv5cFZRzks0YfSCSi/4jkKDxPg1L469gkrfcVwmjVcJVI2uZ/BJ8TY Jw79alj3NlBXSbsk7ERfCpL5AP6X7dgXBxgpw55rVALSnEzujthPbPbI7Z/zoJI9 LsT5uWGzGBXIJuvxwYsT83A8DJheqYCX+XmRdem9t7Rp6iL2ri5gbCzWu1d6oA4V RE8iZkNg5/y6rliMCvy9Sa3MQN32bsupTmKn0YmarBnMKHjEzI9LTJMxLOxPRBR+ nisCwPH2K7xMpFEMHLX9buMjbJLl3o3QI0PvJRH1hWI9hN5bH5QvHPrslNkWNyQa LLLNDTLnq30s4tnvdbTYIDj0RAFgYhXanH7V1jlCP/Ny+1DbICB9QFAuKPkKsPog 57Aci0FMD7qIm6mvjg1YSqOCFJMPJBr8dmTYHXBarueAw9bxlOnYJDhXeVI1/lvY mGapq3tcuQ0hvha6IS37CFqsWBXXicPjD+Ss4RXxo93XhlytiB6RL0eJjEAXMmBU ediFXto+c7AyCQzrh7kCDQRWHnSHARAAyrLAhPO4JiqRk2sem+8bweimfnKmIm+t tTqjDni1DdBKtCZFUxPwEKzuOpU4aals9Ohk4rQMnm6Q2XaJxIs0lijQJjVFbExt m9G2R2gkPJ5fnk4+k2mvps5F/iJjQk0kzWMITEA6cJzt9B8YC4dfsIq3lhCInOvS MBIVtDapruDGU4OskFBiKfzIq8diq/EpqNfwCxZX2IcPhFv2+SJjph60oUC4WJ4z gINfJWdUGlIZrQp8sr/aEa77BVtLnTsuMHwqOF7P7yk4qpb3EFuyNCsJVAirYTqZ eMWEv/pYiwtAgYOewdwptP8+5lbcXAorY8Cs0GdW0r7LUzQjfhXl67EQWJuvGDBK MQ35LyUJtOm5m+qAnalz9Eyb0xRc9ArlrzH2GfCIr4ga+2RPw7fq7fZcbBcJDs77 0Mz0kVrLv3IAcc6fmnvuo30TFeIPB62/c4xIe8njJ8RxbfwYAtd2KoAzwRQEqJQE CyNnqENFHj2cimEueXf7NAw+z1nl1HIl7MrHUeA0FQTw4WdGCahTuFRaJCjmODmK CFAooDWEek6jwNv18hCDJytSDc7uLAvHD2b9Sd40P1V2ochSf/DS/osfEIEeBf5m kG/MHBBbNxvSGP1h2yUM5C7g4nc6B+nz5bIULhw0ojZx+U1i63gJkMsVMjKDAa+m GsWu0vAI3v0AEQEAAYkCJQQYAQgADwUCVh50hwIbDAUJCWYBgAAKCRDMRLTQkmmO agteD/9gD9qXx9ZNXvN/ygCZfudKAfiJzw5776yMj3+lH87/i561Eclr5nuF1mNo TWmW3d/p2JUogYD/xy4okf+3982U2KTNuWPAt3BTKRsVi/H4iApZrabyxCrpf6Q4 GT8Dn3/1ks6bj0bF8pvKZ25jCrpLdHAUhcy/yLyXvIaZUmezyAydspDlaFoXRzoC ztpo6eTohCPcKMMaI3yYKurWNzLenbuJXdkM+yjRIYfsOEt/cqqW7pUhr7lg59XQ R0/GsAqALggMhLDWJiPNAghL0TgRY8GAbWt8h86Aj/+FlsyZkGpnDbWHY5mpEf2R aH+CCZcfecN3LXckM3nYKQPWvSKYU7fBAcs44CPu1mUd/UgbemFIE2aAQJ3q/422 1e55OcRaIqZAYcqLQqytPaF1ZivEzqC+3opDEhTdxsAsHkCMIV1tPEMwv4WdyuaM H88y3tJJpcSMzAn6QXxaZVpuEV9SGPw11t0P0oqAVt2JoSmaTr4B/wPb+eb82Pf6 3UHazUq0j8WpsombsHf+nxQBu1f/0ThCDtmgnzRuVMPnicJVbWEKigekoaN+uL3X F6BrhLqkj7QKsY8PxpFbZj+JRoDQbKVT+h6SWezG6c+NjXZb03Ixp8FJqC1J0zmf ZuN5R5HKZ8sYChEjSyDov6M3C8FshUq7WxK9ucUPIX7Xjqzkrw=3D=3D =3DVNJV -----END PGP PUBLIC KEY BLOCK----- --------------090207080108000402040906-- -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/