Version:  2.0.40 2.2.26 2.4.37 3.5 3.6 3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 4.0 4.1

Linux/drivers/net/irda/kingsun-sir.c

  1 /*****************************************************************************
  2 *
  3 * Filename:      kingsun-sir.c
  4 * Version:       0.1.1
  5 * Description:   Irda KingSun/DonShine USB Dongle
  6 * Status:        Experimental
  7 * Author:        Alex Villacís Lasso <a_villacis@palosanto.com>
  8 *
  9 *       Based on stir4200 and mcs7780 drivers, with (strange?) differences
 10 *
 11 *       This program is free software; you can redistribute it and/or modify
 12 *       it under the terms of the GNU General Public License as published by
 13 *       the Free Software Foundation; either version 2 of the License.
 14 *
 15 *       This program is distributed in the hope that it will be useful,
 16 *       but WITHOUT ANY WARRANTY; without even the implied warranty of
 17 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 18 *       GNU General Public License for more details.
 19 *
 20 *       You should have received a copy of the GNU General Public License
 21 *       along with this program; if not, write to the Free Software
 22 *       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 23 *
 24 *****************************************************************************/
 25 
 26 /*
 27  * This is my current (2007-04-25) understanding of how this dongle is supposed
 28  * to work. This is based on reverse-engineering and examination of the packet
 29  * data sent and received by the WinXP driver using USBSnoopy. Feel free to
 30  * update here as more of this dongle is known:
 31  *
 32  * General: Unlike the other USB IrDA dongles, this particular dongle exposes,
 33  * not two bulk (in and out) endpoints, but two *interrupt* ones. This dongle,
 34  * like the bulk based ones (stir4200.c and mcs7780.c), requires polling in
 35  * order to receive data.
 36  * Transmission: Just like stir4200, this dongle uses a raw stream of data,
 37  * which needs to be wrapped and escaped in a similar way as in stir4200.c.
 38  * Reception: Poll-based, as in stir4200. Each read returns the contents of a
 39  * 8-byte buffer, of which the first byte (LSB) indicates the number of bytes
 40  * (1-7) of valid data contained within the remaining 7 bytes. For example, if
 41  * the buffer had the following contents:
 42  *  06 ff ff ff c0 01 04 aa
 43  * This means that (06) there are 6 bytes of valid data. The byte 0xaa at the
 44  * end is garbage (left over from a previous reception) and is discarded.
 45  * If a read returns an "impossible" value as the length of valid data (such as
 46  * 0x36) in the first byte, then the buffer is uninitialized (as is the case of
 47  * first plug-in) and its contents should be discarded. There is currently no
 48  * evidence that the top 5 bits of the 1st byte of the buffer can have values
 49  * other than 0 once reception begins.
 50  * Once valid bytes are collected, the assembled stream is a sequence of
 51  * wrapped IrDA frames that is unwrapped and unescaped as in stir4200.c.
 52  * BIG FAT WARNING: the dongle does *not* reset the RX buffer in any way after
 53  * a successful read from the host, which means that in absence of further
 54  * reception, repeated reads from the dongle will return the exact same
 55  * contents repeatedly. Attempts to be smart and cache a previous read seem
 56  * to result in corrupted packets, so this driver depends on the unwrap logic
 57  * to sort out any repeated reads.
 58  * Speed change: no commands observed so far to change speed, assumed fixed
 59  * 9600bps (SIR).
 60  */
 61 
 62 #include <linux/module.h>
 63 #include <linux/moduleparam.h>
 64 #include <linux/kernel.h>
 65 #include <linux/types.h>
 66 #include <linux/errno.h>
 67 #include <linux/slab.h>
 68 #include <linux/usb.h>
 69 #include <linux/device.h>
 70 #include <linux/crc32.h>
 71 
 72 #include <asm/unaligned.h>
 73 #include <asm/byteorder.h>
 74 #include <asm/uaccess.h>
 75 
 76 #include <net/irda/irda.h>
 77 #include <net/irda/wrapper.h>
 78 #include <net/irda/crc.h>
 79 
 80 /*
 81  * According to lsusb, 0x07c0 is assigned to
 82  * "Code Mercenaries Hard- und Software GmbH"
 83  */
 84 #define KING_VENDOR_ID 0x07c0
 85 #define KING_PRODUCT_ID 0x4200
 86 
 87 /* These are the currently known USB ids */
 88 static struct usb_device_id dongles[] = {
 89     /* KingSun Co,Ltd  IrDA/USB Bridge */
 90     { USB_DEVICE(KING_VENDOR_ID, KING_PRODUCT_ID) },
 91     { }
 92 };
 93 
 94 MODULE_DEVICE_TABLE(usb, dongles);
 95 
 96 #define KINGSUN_MTT 0x07
 97 
 98 #define KINGSUN_FIFO_SIZE               4096
 99 #define KINGSUN_EP_IN                   0
100 #define KINGSUN_EP_OUT                  1
101 
102 struct kingsun_cb {
103         struct usb_device *usbdev;      /* init: probe_irda */
104         struct net_device *netdev;      /* network layer */
105         struct irlap_cb   *irlap;       /* The link layer we are binded to */
106 
107         struct qos_info   qos;
108 
109         __u8              *in_buf;      /* receive buffer */
110         __u8              *out_buf;     /* transmit buffer */
111         __u8              max_rx;       /* max. atomic read from dongle
112                                            (usually 8), also size of in_buf */
113         __u8              max_tx;       /* max. atomic write to dongle
114                                            (usually 8) */
115 
116         iobuff_t          rx_buff;      /* receive unwrap state machine */
117         spinlock_t lock;
118         int receiving;
119 
120         __u8 ep_in;
121         __u8 ep_out;
122 
123         struct urb       *tx_urb;
124         struct urb       *rx_urb;
125 };
126 
127 /* Callback transmission routine */
128 static void kingsun_send_irq(struct urb *urb)
129 {
130         struct kingsun_cb *kingsun = urb->context;
131         struct net_device *netdev = kingsun->netdev;
132 
133         /* in process of stopping, just drop data */
134         if (!netif_running(kingsun->netdev)) {
135                 dev_err(&kingsun->usbdev->dev,
136                         "kingsun_send_irq: Network not running!\n");
137                 return;
138         }
139 
140         /* unlink, shutdown, unplug, other nasties */
141         if (urb->status != 0) {
142                 dev_err(&kingsun->usbdev->dev,
143                         "kingsun_send_irq: urb asynchronously failed - %d\n",
144                         urb->status);
145         }
146         netif_wake_queue(netdev);
147 }
148 
149 /*
150  * Called from net/core when new frame is available.
151  */
152 static netdev_tx_t kingsun_hard_xmit(struct sk_buff *skb,
153                                            struct net_device *netdev)
154 {
155         struct kingsun_cb *kingsun;
156         int wraplen;
157         int ret = 0;
158 
159         netif_stop_queue(netdev);
160 
161         /* the IRDA wrapping routines don't deal with non linear skb */
162         SKB_LINEAR_ASSERT(skb);
163 
164         kingsun = netdev_priv(netdev);
165 
166         spin_lock(&kingsun->lock);
167 
168         /* Append data to the end of whatever data remains to be transmitted */
169         wraplen = async_wrap_skb(skb,
170                 kingsun->out_buf,
171                 KINGSUN_FIFO_SIZE);
172 
173         /* Calculate how much data can be transmitted in this urb */
174         usb_fill_int_urb(kingsun->tx_urb, kingsun->usbdev,
175                 usb_sndintpipe(kingsun->usbdev, kingsun->ep_out),
176                 kingsun->out_buf, wraplen, kingsun_send_irq,
177                 kingsun, 1);
178 
179         if ((ret = usb_submit_urb(kingsun->tx_urb, GFP_ATOMIC))) {
180                 dev_err(&kingsun->usbdev->dev,
181                         "kingsun_hard_xmit: failed tx_urb submit: %d\n", ret);
182                 switch (ret) {
183                 case -ENODEV:
184                 case -EPIPE:
185                         break;
186                 default:
187                         netdev->stats.tx_errors++;
188                         netif_start_queue(netdev);
189                 }
190         } else {
191                 netdev->stats.tx_packets++;
192                 netdev->stats.tx_bytes += skb->len;
193         }
194 
195         dev_kfree_skb(skb);
196         spin_unlock(&kingsun->lock);
197 
198         return NETDEV_TX_OK;
199 }
200 
201 /* Receive callback function */
202 static void kingsun_rcv_irq(struct urb *urb)
203 {
204         struct kingsun_cb *kingsun = urb->context;
205         int ret;
206 
207         /* in process of stopping, just drop data */
208         if (!netif_running(kingsun->netdev)) {
209                 kingsun->receiving = 0;
210                 return;
211         }
212 
213         /* unlink, shutdown, unplug, other nasties */
214         if (urb->status != 0) {
215                 dev_err(&kingsun->usbdev->dev,
216                         "kingsun_rcv_irq: urb asynchronously failed - %d\n",
217                         urb->status);
218                 kingsun->receiving = 0;
219                 return;
220         }
221 
222         if (urb->actual_length == kingsun->max_rx) {
223                 __u8 *bytes = urb->transfer_buffer;
224                 int i;
225 
226                 /* The very first byte in the buffer indicates the length of
227                    valid data in the read. This byte must be in the range
228                    1..kingsun->max_rx -1 . Values outside this range indicate
229                    an uninitialized Rx buffer when the dongle has just been
230                    plugged in. */
231                 if (bytes[0] >= 1 && bytes[0] < kingsun->max_rx) {
232                         for (i = 1; i <= bytes[0]; i++) {
233                                 async_unwrap_char(kingsun->netdev,
234                                                   &kingsun->netdev->stats,
235                                                   &kingsun->rx_buff, bytes[i]);
236                         }
237                         kingsun->receiving =
238                                 (kingsun->rx_buff.state != OUTSIDE_FRAME)
239                                 ? 1 : 0;
240                 }
241         } else if (urb->actual_length > 0) {
242                 dev_err(&kingsun->usbdev->dev,
243                         "%s(): Unexpected response length, expected %d got %d\n",
244                         __func__, kingsun->max_rx, urb->actual_length);
245         }
246         /* This urb has already been filled in kingsun_net_open */
247         ret = usb_submit_urb(urb, GFP_ATOMIC);
248 }
249 
250 /*
251  * Function kingsun_net_open (dev)
252  *
253  *    Network device is taken up. Usually this is done by "ifconfig irda0 up"
254  */
255 static int kingsun_net_open(struct net_device *netdev)
256 {
257         struct kingsun_cb *kingsun = netdev_priv(netdev);
258         int err = -ENOMEM;
259         char hwname[16];
260 
261         /* At this point, urbs are NULL, and skb is NULL (see kingsun_probe) */
262         kingsun->receiving = 0;
263 
264         /* Initialize for SIR to copy data directly into skb.  */
265         kingsun->rx_buff.in_frame = FALSE;
266         kingsun->rx_buff.state = OUTSIDE_FRAME;
267         kingsun->rx_buff.truesize = IRDA_SKB_MAX_MTU;
268         kingsun->rx_buff.skb = dev_alloc_skb(IRDA_SKB_MAX_MTU);
269         if (!kingsun->rx_buff.skb)
270                 goto free_mem;
271 
272         skb_reserve(kingsun->rx_buff.skb, 1);
273         kingsun->rx_buff.head = kingsun->rx_buff.skb->data;
274 
275         kingsun->rx_urb = usb_alloc_urb(0, GFP_KERNEL);
276         if (!kingsun->rx_urb)
277                 goto free_mem;
278 
279         kingsun->tx_urb = usb_alloc_urb(0, GFP_KERNEL);
280         if (!kingsun->tx_urb)
281                 goto free_mem;
282 
283         /*
284          * Now that everything should be initialized properly,
285          * Open new IrLAP layer instance to take care of us...
286          */
287         sprintf(hwname, "usb#%d", kingsun->usbdev->devnum);
288         kingsun->irlap = irlap_open(netdev, &kingsun->qos, hwname);
289         if (!kingsun->irlap) {
290                 dev_err(&kingsun->usbdev->dev, "irlap_open failed\n");
291                 goto free_mem;
292         }
293 
294         /* Start first reception */
295         usb_fill_int_urb(kingsun->rx_urb, kingsun->usbdev,
296                           usb_rcvintpipe(kingsun->usbdev, kingsun->ep_in),
297                           kingsun->in_buf, kingsun->max_rx,
298                           kingsun_rcv_irq, kingsun, 1);
299         kingsun->rx_urb->status = 0;
300         err = usb_submit_urb(kingsun->rx_urb, GFP_KERNEL);
301         if (err) {
302                 dev_err(&kingsun->usbdev->dev,
303                         "first urb-submit failed: %d\n", err);
304                 goto close_irlap;
305         }
306 
307         netif_start_queue(netdev);
308 
309         /* Situation at this point:
310            - all work buffers allocated
311            - urbs allocated and ready to fill
312            - max rx packet known (in max_rx)
313            - unwrap state machine initialized, in state outside of any frame
314            - receive request in progress
315            - IrLAP layer started, about to hand over packets to send
316          */
317 
318         return 0;
319 
320  close_irlap:
321         irlap_close(kingsun->irlap);
322  free_mem:
323         if (kingsun->tx_urb) {
324                 usb_free_urb(kingsun->tx_urb);
325                 kingsun->tx_urb = NULL;
326         }
327         if (kingsun->rx_urb) {
328                 usb_free_urb(kingsun->rx_urb);
329                 kingsun->rx_urb = NULL;
330         }
331         if (kingsun->rx_buff.skb) {
332                 kfree_skb(kingsun->rx_buff.skb);
333                 kingsun->rx_buff.skb = NULL;
334                 kingsun->rx_buff.head = NULL;
335         }
336         return err;
337 }
338 
339 /*
340  * Function kingsun_net_close (kingsun)
341  *
342  *    Network device is taken down. Usually this is done by
343  *    "ifconfig irda0 down"
344  */
345 static int kingsun_net_close(struct net_device *netdev)
346 {
347         struct kingsun_cb *kingsun = netdev_priv(netdev);
348 
349         /* Stop transmit processing */
350         netif_stop_queue(netdev);
351 
352         /* Mop up receive && transmit urb's */
353         usb_kill_urb(kingsun->tx_urb);
354         usb_kill_urb(kingsun->rx_urb);
355 
356         usb_free_urb(kingsun->tx_urb);
357         usb_free_urb(kingsun->rx_urb);
358 
359         kingsun->tx_urb = NULL;
360         kingsun->rx_urb = NULL;
361 
362         kfree_skb(kingsun->rx_buff.skb);
363         kingsun->rx_buff.skb = NULL;
364         kingsun->rx_buff.head = NULL;
365         kingsun->rx_buff.in_frame = FALSE;
366         kingsun->rx_buff.state = OUTSIDE_FRAME;
367         kingsun->receiving = 0;
368 
369         /* Stop and remove instance of IrLAP */
370         if (kingsun->irlap)
371                 irlap_close(kingsun->irlap);
372 
373         kingsun->irlap = NULL;
374 
375         return 0;
376 }
377 
378 /*
379  * IOCTLs : Extra out-of-band network commands...
380  */
381 static int kingsun_net_ioctl(struct net_device *netdev, struct ifreq *rq,
382                              int cmd)
383 {
384         struct if_irda_req *irq = (struct if_irda_req *) rq;
385         struct kingsun_cb *kingsun = netdev_priv(netdev);
386         int ret = 0;
387 
388         switch (cmd) {
389         case SIOCSBANDWIDTH: /* Set bandwidth */
390                 if (!capable(CAP_NET_ADMIN))
391                         return -EPERM;
392 
393                 /* Check if the device is still there */
394                 if (netif_device_present(kingsun->netdev))
395                         /* No observed commands for speed change */
396                         ret = -EOPNOTSUPP;
397                 break;
398 
399         case SIOCSMEDIABUSY: /* Set media busy */
400                 if (!capable(CAP_NET_ADMIN))
401                         return -EPERM;
402 
403                 /* Check if the IrDA stack is still there */
404                 if (netif_running(kingsun->netdev))
405                         irda_device_set_media_busy(kingsun->netdev, TRUE);
406                 break;
407 
408         case SIOCGRECEIVING:
409                 /* Only approximately true */
410                 irq->ifr_receiving = kingsun->receiving;
411                 break;
412 
413         default:
414                 ret = -EOPNOTSUPP;
415         }
416 
417         return ret;
418 }
419 
420 static const struct net_device_ops kingsun_ops = {
421         .ndo_start_xmit      = kingsun_hard_xmit,
422         .ndo_open            = kingsun_net_open,
423         .ndo_stop            = kingsun_net_close,
424         .ndo_do_ioctl        = kingsun_net_ioctl,
425 };
426 
427 /*
428  * This routine is called by the USB subsystem for each new device
429  * in the system. We need to check if the device is ours, and in
430  * this case start handling it.
431  */
432 static int kingsun_probe(struct usb_interface *intf,
433                       const struct usb_device_id *id)
434 {
435         struct usb_host_interface *interface;
436         struct usb_endpoint_descriptor *endpoint;
437 
438         struct usb_device *dev = interface_to_usbdev(intf);
439         struct kingsun_cb *kingsun = NULL;
440         struct net_device *net = NULL;
441         int ret = -ENOMEM;
442         int pipe, maxp_in, maxp_out;
443         __u8 ep_in;
444         __u8 ep_out;
445 
446         /* Check that there really are two interrupt endpoints.
447            Check based on the one in drivers/usb/input/usbmouse.c
448          */
449         interface = intf->cur_altsetting;
450         if (interface->desc.bNumEndpoints != 2) {
451                 dev_err(&intf->dev,
452                         "kingsun-sir: expected 2 endpoints, found %d\n",
453                         interface->desc.bNumEndpoints);
454                 return -ENODEV;
455         }
456         endpoint = &interface->endpoint[KINGSUN_EP_IN].desc;
457         if (!usb_endpoint_is_int_in(endpoint)) {
458                 dev_err(&intf->dev,
459                         "kingsun-sir: endpoint 0 is not interrupt IN\n");
460                 return -ENODEV;
461         }
462 
463         ep_in = endpoint->bEndpointAddress;
464         pipe = usb_rcvintpipe(dev, ep_in);
465         maxp_in = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
466         if (maxp_in > 255 || maxp_in <= 1) {
467                 dev_err(&intf->dev,
468                         "endpoint 0 has max packet size %d not in range\n",
469                         maxp_in);
470                 return -ENODEV;
471         }
472 
473         endpoint = &interface->endpoint[KINGSUN_EP_OUT].desc;
474         if (!usb_endpoint_is_int_out(endpoint)) {
475                 dev_err(&intf->dev,
476                         "kingsun-sir: endpoint 1 is not interrupt OUT\n");
477                 return -ENODEV;
478         }
479 
480         ep_out = endpoint->bEndpointAddress;
481         pipe = usb_sndintpipe(dev, ep_out);
482         maxp_out = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
483 
484         /* Allocate network device container. */
485         net = alloc_irdadev(sizeof(*kingsun));
486         if(!net)
487                 goto err_out1;
488 
489         SET_NETDEV_DEV(net, &intf->dev);
490         kingsun = netdev_priv(net);
491         kingsun->irlap = NULL;
492         kingsun->tx_urb = NULL;
493         kingsun->rx_urb = NULL;
494         kingsun->ep_in = ep_in;
495         kingsun->ep_out = ep_out;
496         kingsun->in_buf = NULL;
497         kingsun->out_buf = NULL;
498         kingsun->max_rx = (__u8)maxp_in;
499         kingsun->max_tx = (__u8)maxp_out;
500         kingsun->netdev = net;
501         kingsun->usbdev = dev;
502         kingsun->rx_buff.in_frame = FALSE;
503         kingsun->rx_buff.state = OUTSIDE_FRAME;
504         kingsun->rx_buff.skb = NULL;
505         kingsun->receiving = 0;
506         spin_lock_init(&kingsun->lock);
507 
508         /* Allocate input buffer */
509         kingsun->in_buf = kmalloc(kingsun->max_rx, GFP_KERNEL);
510         if (!kingsun->in_buf)
511                 goto free_mem;
512 
513         /* Allocate output buffer */
514         kingsun->out_buf = kmalloc(KINGSUN_FIFO_SIZE, GFP_KERNEL);
515         if (!kingsun->out_buf)
516                 goto free_mem;
517 
518         printk(KERN_INFO "KingSun/DonShine IRDA/USB found at address %d, "
519                 "Vendor: %x, Product: %x\n",
520                dev->devnum, le16_to_cpu(dev->descriptor.idVendor),
521                le16_to_cpu(dev->descriptor.idProduct));
522 
523         /* Initialize QoS for this device */
524         irda_init_max_qos_capabilies(&kingsun->qos);
525 
526         /* That's the Rx capability. */
527         kingsun->qos.baud_rate.bits       &= IR_9600;
528         kingsun->qos.min_turn_time.bits   &= KINGSUN_MTT;
529         irda_qos_bits_to_value(&kingsun->qos);
530 
531         /* Override the network functions we need to use */
532         net->netdev_ops = &kingsun_ops;
533 
534         ret = register_netdev(net);
535         if (ret != 0)
536                 goto free_mem;
537 
538         dev_info(&net->dev, "IrDA: Registered KingSun/DonShine device %s\n",
539                  net->name);
540 
541         usb_set_intfdata(intf, kingsun);
542 
543         /* Situation at this point:
544            - all work buffers allocated
545            - urbs not allocated, set to NULL
546            - max rx packet known (in max_rx)
547            - unwrap state machine (partially) initialized, but skb == NULL
548          */
549 
550         return 0;
551 
552 free_mem:
553         kfree(kingsun->out_buf);
554         kfree(kingsun->in_buf);
555         free_netdev(net);
556 err_out1:
557         return ret;
558 }
559 
560 /*
561  * The current device is removed, the USB layer tell us to shut it down...
562  */
563 static void kingsun_disconnect(struct usb_interface *intf)
564 {
565         struct kingsun_cb *kingsun = usb_get_intfdata(intf);
566 
567         if (!kingsun)
568                 return;
569 
570         unregister_netdev(kingsun->netdev);
571 
572         /* Mop up receive && transmit urb's */
573         if (kingsun->tx_urb != NULL) {
574                 usb_kill_urb(kingsun->tx_urb);
575                 usb_free_urb(kingsun->tx_urb);
576                 kingsun->tx_urb = NULL;
577         }
578         if (kingsun->rx_urb != NULL) {
579                 usb_kill_urb(kingsun->rx_urb);
580                 usb_free_urb(kingsun->rx_urb);
581                 kingsun->rx_urb = NULL;
582         }
583 
584         kfree(kingsun->out_buf);
585         kfree(kingsun->in_buf);
586         free_netdev(kingsun->netdev);
587 
588         usb_set_intfdata(intf, NULL);
589 }
590 
591 #ifdef CONFIG_PM
592 /* USB suspend, so power off the transmitter/receiver */
593 static int kingsun_suspend(struct usb_interface *intf, pm_message_t message)
594 {
595         struct kingsun_cb *kingsun = usb_get_intfdata(intf);
596 
597         netif_device_detach(kingsun->netdev);
598         if (kingsun->tx_urb != NULL) usb_kill_urb(kingsun->tx_urb);
599         if (kingsun->rx_urb != NULL) usb_kill_urb(kingsun->rx_urb);
600         return 0;
601 }
602 
603 /* Coming out of suspend, so reset hardware */
604 static int kingsun_resume(struct usb_interface *intf)
605 {
606         struct kingsun_cb *kingsun = usb_get_intfdata(intf);
607 
608         if (kingsun->rx_urb != NULL)
609                 usb_submit_urb(kingsun->rx_urb, GFP_KERNEL);
610         netif_device_attach(kingsun->netdev);
611 
612         return 0;
613 }
614 #endif
615 
616 /*
617  * USB device callbacks
618  */
619 static struct usb_driver irda_driver = {
620         .name           = "kingsun-sir",
621         .probe          = kingsun_probe,
622         .disconnect     = kingsun_disconnect,
623         .id_table       = dongles,
624 #ifdef CONFIG_PM
625         .suspend        = kingsun_suspend,
626         .resume         = kingsun_resume,
627 #endif
628 };
629 
630 module_usb_driver(irda_driver);
631 
632 MODULE_AUTHOR("Alex Villacís Lasso <a_villacis@palosanto.com>");
633 MODULE_DESCRIPTION("IrDA-USB Dongle Driver for KingSun/DonShine");
634 MODULE_LICENSE("GPL");
635 

This page was automatically generated by LXR 0.3.1 (source).  •  Linux is a registered trademark of Linus Torvalds  •  Contact us