1 /*
2 * snull.c -- the Simple Network Utility
3 *
4 * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
5 * Copyright (C) 2001 O'Reilly & Associates
6 *
7 * The source code in this file can be freely used, adapted,
8 * and redistributed in source or binary form, so long as an
9 * acknowledgment appears in derived source files. The citation
10 * should list that the code comes from the book "Linux Device
11 * Drivers" by Alessandro Rubini and Jonathan Corbet, published
12 * by O'Reilly & Associates. No warranty is attached;
13 * we cannot take responsibility for errors or fitness for use.
14 *
15 * $Id: snull.c,v 1.21 2004/11/05 02:36:03 rubini Exp $
16 */
17
18 /* I have made an attempt to bring this code up to kernel 2.6.25,
19 * but am not certain it works, and am less convinced it is still
20 * a good example of how to write a network device driver.
21 * It seems that circa kernel 2.6.24 the NAPI interfaces were
22 * changed. Responsibility for managing the quote was move
23 * from individual drivers' poll functions to shared code.
24 * There also seems to be some new NAPI registration stuff.
25 * See http://lwn.net/Articles/244640/ for more info.
26 */
27
28 #include <linux/module.h>
29 #include <linux/init.h>
30 #include <linux/moduleparam.h>
31
32 #include <linux/sched.h>
33 #include <linux/kernel.h> /* printk() */
34 #include <linux/slab.h> /* kmalloc() */
35 #include <linux/errno.h> /* error codes */
36 #include <linux/types.h> /* size_t */
37 #include <linux/interrupt.h> /* mark_bh */
38
39 #include <linux/in.h>
40 #include <linux/netdevice.h> /* struct device, and other headers */
41 #include <linux/etherdevice.h> /* eth_type_trans */
42 #include <linux/ip.h> /* struct iphdr */
43 #include <linux/tcp.h> /* struct tcphdr */
44 #include <linux/skbuff.h>
45
46 #include "snull.h"
47
48 #include <linux/in6.h>
49 #include <asm/checksum.h>
50
51 MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet");
52 MODULE_LICENSE("Dual BSD/GPL");
53
54
55 /*
56 * Transmitter lockup simulation, normally disabled.
57 */
58 static int lockup = 0;
59 module_param(lockup, int, 0);
60
61 static int timeout = SNULL_TIMEOUT;
62 module_param(timeout, int, 0);
63
64 /*
65 * Do we run in NAPI mode?
66 */
67 static int use_napi = 0;
68 module_param(use_napi, int, 0);
69
70
71 /*
72 * A structure representing an in-flight packet.
73 */
74 struct snull_packet {
75 struct snull_packet *next;
76 struct net_device *dev;
77 int datalen;
78 u8 data[ETH_DATA_LEN];
79 };
80
81 int pool_size = 8;
82 module_param(pool_size, int, 0);
83
84 /*
85 * This structure is private to each device. It is used to pass
86 * packets in and out, so there is place for a packet
87 */
88
89 struct snull_priv {
90 struct net_device_stats stats;
91 int status;
92 struct snull_packet *ppool;
93 struct snull_packet *rx_queue; /* List of incoming packets */
94 int rx_int_enabled;
95 int tx_packetlen;
96 u8 *tx_packetdata;
97 struct sk_buff *skb;
98 spinlock_t lock;
99 struct net_device *dev;
100 struct napi_struct napi;
101 /* Consider creating new struct for snull device, and putting
102 * the struct net_dev in here.
103 */
104 };
105
106 static void snull_tx_timeout(struct net_device *dev);
107 static void (*snull_interrupt)(int, void *, struct pt_regs *);
108
109 /*
110 * Set up a device's packet pool.
111 */
112 void snull_setup_pool(struct net_device *dev)
113 {
114 struct snull_priv *priv = netdev_priv(dev);
115 int i;
116 struct snull_packet *pkt;
117
118 priv->ppool = NULL;
119 for (i = 0; i < pool_size; i++) {
120 pkt = kmalloc (sizeof (struct snull_packet), GFP_KERNEL);
121 if (pkt == NULL) {
122 printk (KERN_NOTICE "Ran out of memory allocating packet pool\n");
123 return;
124 }
125 pkt->dev = dev;
126 pkt->next = priv->ppool;
127 priv->ppool = pkt;
128 }
129 }
130
131 void snull_teardown_pool(struct net_device *dev)
132 {
133 struct snull_priv *priv = netdev_priv(dev);
134 struct snull_packet *pkt;
135
136 while ((pkt = priv->ppool)) {
137 priv->ppool = pkt->next;
138 kfree (pkt);
139 /* FIXME - in-flight packets ? */
140 }
141 }
142
143 /*
144 * Buffer/pool management.
145 */
146 struct snull_packet *snull_get_tx_buffer(struct net_device *dev)
147 {
148 struct snull_priv *priv = netdev_priv(dev);
149 unsigned long flags;
150 struct snull_packet *pkt;
151
152 spin_lock_irqsave(&priv->lock, flags);
153 pkt = priv->ppool;
154 priv->ppool = pkt->next;
155 if (priv->ppool == NULL) {
156 printk (KERN_INFO "Pool empty\n");
157 netif_stop_queue(dev);
158 }
159 spin_unlock_irqrestore(&priv->lock, flags);
160 return pkt;
161 }
162
163
164 void snull_release_buffer(struct snull_packet *pkt)
165 {
166 unsigned long flags;
167 struct snull_priv *priv = netdev_priv(pkt->dev);
168
169 spin_lock_irqsave(&priv->lock, flags);
170 pkt->next = priv->ppool;
171 priv->ppool = pkt;
172 spin_unlock_irqrestore(&priv->lock, flags);
173 if (netif_queue_stopped(pkt->dev) && pkt->next == NULL)
174 netif_wake_queue(pkt->dev);
175 }
176
177 void snull_enqueue_buf(struct net_device *dev, struct snull_packet *pkt)
178 {
179 unsigned long flags;
180 struct snull_priv *priv = netdev_priv(dev);
181
182 spin_lock_irqsave(&priv->lock, flags);
183 pkt->next = priv->rx_queue; /* FIXME - misorders packets */
184 priv->rx_queue = pkt;
185 spin_unlock_irqrestore(&priv->lock, flags);
186 }
187
188 struct snull_packet *snull_dequeue_buf(struct net_device *dev)
189 {
190 struct snull_priv *priv = netdev_priv(dev);
191 struct snull_packet *pkt;
192 unsigned long flags;
193
194 spin_lock_irqsave(&priv->lock, flags);
195 pkt = priv->rx_queue;
196 if (pkt != NULL)
197 priv->rx_queue = pkt->next;
198 spin_unlock_irqrestore(&priv->lock, flags);
199 return pkt;
200 }
201
202 /*
203 * Enable and disable receive interrupts.
204 */
205 static void snull_rx_ints(struct net_device *dev, int enable)
206 {
207 struct snull_priv *priv = netdev_priv(dev);
208 priv->rx_int_enabled = enable;
209 }
210
211
212 /*
213 * Open and close
214 */
215
216 int snull_open(struct net_device *dev)
217 {
218 /* request_region(), request_irq(), .... (like fops->open) */
219
220 /*
221 * Assign the hardware address of the board: use "\0SNULx", where
222 * x is 0 or 1. The first byte is '\0' to avoid being a multicast
223 * address (the first byte of multicast addrs is odd).
224 */
225 memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
226 if (dev == snull_devs[1])
227 dev->dev_addr[ETH_ALEN-1]++; /* \0SNUL1 */
228 netif_start_queue(dev);
229 return 0;
230 }
231
232 int snull_release(struct net_device *dev)
233 {
234 /* release ports, irq and such -- like fops->close */
235
236 netif_stop_queue(dev); /* can't transmit any more */
237 return 0;
238 }
239
240 /*
241 * Configuration changes (passed on by ifconfig)
242 */
243 int snull_config(struct net_device *dev, struct ifmap *map)
244 {
245 if (dev->flags & IFF_UP) /* can't act on a running interface */
246 return -EBUSY;
247
248 /* Don't allow changing the I/O address */
249 if (map->base_addr != dev->base_addr) {
250 printk(KERN_WARNING "snull: Can't change I/O address\n");
251 return -EOPNOTSUPP;
252 }
253
254 /* Allow changing the IRQ */
255 if (map->irq != dev->irq) {
256 dev->irq = map->irq;
257 /* request_irq() is delayed to open-time */
258 }
259
260 /* ignore other fields */
261 return 0;
262 }
263
264 /*
265 * Receive a packet: retrieve, encapsulate and pass over to upper levels
266 */
267 void snull_rx(struct net_device *dev, struct snull_packet *pkt)
268 {
269 struct sk_buff *skb;
270 struct snull_priv *priv = netdev_priv(dev);
271
272 /*
273 * The packet has been retrieved from the transmission
274 * medium. Build an skb around it, so upper layers can handle it
275 */
276 skb = dev_alloc_skb(pkt->datalen + 2);
277 if (!skb) {
278 if (printk_ratelimit())
279 printk(KERN_NOTICE "snull rx: low on mem - packet dropped\n");
280 priv->stats.rx_dropped++;
281 goto out;
282 }
283 skb_reserve(skb, 2); /* align IP on 16B boundary */
284 memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
285
286 /* Write metadata, and then pass to the receive level */
287 skb->dev = dev;
288 skb->protocol = eth_type_trans(skb, dev);
289 skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
290 priv->stats.rx_packets++;
291 priv->stats.rx_bytes += pkt->datalen;
292 netif_rx(skb);
293 out:
294 return;
295 }
296
297
298 /*
299 * The poll implementation.
300 */
301 static int snull_poll(struct napi_struct *napi, int budget)
302 {
303 int npackets = 0;
304 struct sk_buff *skb;
305 struct snull_priv *priv = container_of(napi, struct snull_priv, napi);
306 struct net_device *dev = priv->dev;
307 struct snull_packet *pkt;
308
309 while (npackets < budget && priv->rx_queue) {
310 pkt = snull_dequeue_buf(dev);
311 skb = dev_alloc_skb(pkt->datalen + 2);
312 if (! skb) {
313 if (printk_ratelimit())
314 printk(KERN_NOTICE "snull: packet dropped\n");
315 priv->stats.rx_dropped++;
316 snull_release_buffer(pkt);
317 continue;
318 }
319 skb_reserve(skb, 2); /* align IP on 16B boundary */
320 memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
321 skb->dev = dev;
322 skb->protocol = eth_type_trans(skb, dev);
323 skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
324 netif_receive_skb(skb);
325 /* Maintain stats */
326 npackets++;
327 priv->stats.rx_packets++;
328 priv->stats.rx_bytes += pkt->datalen;
329 snull_release_buffer(pkt);
330 }
331 /* If we processed all packets, we're done; tell the kernel and reenable ints */
332 if (npackets < budget) {
333 netif_rx_complete(dev, napi);
334 snull_rx_ints(dev, 1);
335 }
336 return npackets;
337 }
338
339
340 /*
341 * The typical interrupt entry point
342 */
343 static void snull_regular_interrupt(int irq, void *dev_id, struct pt_regs *regs)
344 {
345 int statusword;
346 struct snull_priv *priv;
347 struct snull_packet *pkt = NULL;
348 /*
349 * As usual, check the "device" pointer to be sure it is
350 * really interrupting.
351 * Then assign "struct device *dev"
352 */
353 struct net_device *dev = (struct net_device *)dev_id;
354 /* ... and check with hw if it's really ours */
355
356 /* paranoid */
357 if (!dev)
358 return;
359
360 /* Lock the device */
361 priv = netdev_priv(dev);
362 spin_lock(&priv->lock);
363
364 /* retrieve statusword: real netdevices use I/O instructions */
365 statusword = priv->status;
366 priv->status = 0;
367 if (statusword & SNULL_RX_INTR) {
368 /* send it to snull_rx for handling */
369 pkt = priv->rx_queue;
370 if (pkt) {
371 priv->rx_queue = pkt->next;
372 snull_rx(dev, pkt);
373 }
374 }
375 if (statusword & SNULL_TX_INTR) {
376 /* a transmission is over: free the skb */
377 priv->stats.tx_packets++;
378 priv->stats.tx_bytes += priv->tx_packetlen;
379 dev_kfree_skb(priv->skb);
380 }
381
382 /* Unlock the device and we are done */
383 spin_unlock(&priv->lock);
384 if (pkt) snull_release_buffer(pkt); /* Do this outside the lock! */
385 return;
386 }
387
388 /*
389 * A NAPI interrupt handler.
390 */
391 static void snull_napi_interrupt(int irq, void *dev_id, struct pt_regs *regs)
392 {
393 int statusword;
394 struct snull_priv *priv;
395
396 /*
397 * As usual, check the "device" pointer for shared handlers.
398 * Then assign "struct device *dev"
399 */
400 struct net_device *dev = (struct net_device *)dev_id;
401 /* ... and check with hw if it's really ours */
402
403 /* paranoid */
404 if (!dev)
405 return;
406
407 /* Lock the device */
408 priv = netdev_priv(dev);
409 spin_lock(&priv->lock);
410
411 /* retrieve statusword: real netdevices use I/O instructions */
412 statusword = priv->status;
413 priv->status = 0;
414 if (statusword & SNULL_RX_INTR) {
415 snull_rx_ints(dev, 0); /* Disable further interrupts */
416 netif_rx_schedule(dev, &(priv->napi));
417 }
418 if (statusword & SNULL_TX_INTR) {
419 /* a transmission is over: free the skb */
420 priv->stats.tx_packets++;
421 priv->stats.tx_bytes += priv->tx_packetlen;
422 dev_kfree_skb(priv->skb);
423 }
424
425 /* Unlock the device and we are done */
426 spin_unlock(&priv->lock);
427 return;
428 }
429
430
431
432 /*
433 * Transmit a packet (low level interface)
434 */
435 static void snull_hw_tx(char *buf, int len, struct net_device *dev)
436 {
437 /*
438 * This function deals with hw details. This interface loops
439 * back the packet to the other snull interface (if any).
440 * In other words, this function implements the snull behaviour,
441 * while all other procedures are rather device-independent
442 */
443 struct iphdr *ih;
444 struct net_device *dest;
445 struct snull_priv *priv;
446 u32 *saddr, *daddr;
447 struct snull_packet *tx_buffer;
448
449 /* I am paranoid. Ain't I? */
450 if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {
451 printk("snull: Hmm... packet too short (%i octets)\n",
452 len);
453 return;
454 }
455
456 if (0) { /* enable this conditional to look at the data */
457 int i;
458 PDEBUG("len is %i\n" KERN_DEBUG "data:",len);
459 for (i=14 ; i<len; i++)
460 printk(" %02x",buf[i]&0xff);
461 printk("\n");
462 }
463 /*
464 * Ethhdr is 14 bytes, but the kernel arranges for iphdr
465 * to be aligned (i.e., ethhdr is unaligned)
466 */
467 ih = (struct iphdr *)(buf+sizeof(struct ethhdr));
468 saddr = &ih->saddr;
469 daddr = &ih->daddr;
470
471 ((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
472 ((u8 *)daddr)[2] ^= 1;
473
474 ih->check = 0; /* and rebuild the checksum (ip needs it) */
475 ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
476
477 if (dev == snull_devs[0])
478 PDEBUGG("%08x:%05i --> %08x:%05i\n",
479 ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source),
480 ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest));
481 else
482 PDEBUGG("%08x:%05i <-- %08x:%05i\n",
483 ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest),
484 ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source));
485
486 /*
487 * Ok, now the packet is ready for transmission: first simulate a
488 * receive interrupt on the twin device, then a
489 * transmission-done on the transmitting device
490 */
491 dest = snull_devs[dev == snull_devs[0] ? 1 : 0];
492 priv = netdev_priv(dest);
493 tx_buffer = snull_get_tx_buffer(dev);
494 tx_buffer->datalen = len;
495 memcpy(tx_buffer->data, buf, len);
496 snull_enqueue_buf(dest, tx_buffer);
497 if (priv->rx_int_enabled) {
498 priv->status |= SNULL_RX_INTR;
499 snull_interrupt(0, dest, NULL);
500 }
501
502 priv = netdev_priv(dev);
503 priv->tx_packetlen = len;
504 priv->tx_packetdata = buf;
505 priv->status |= SNULL_TX_INTR;
506 if (lockup && ((priv->stats.tx_packets + 1) % lockup) == 0) {
507 /* Simulate a dropped transmit interrupt */
508 netif_stop_queue(dev);
509 PDEBUG("Simulate lockup at %ld, txp %ld\n", jiffies,
510 (unsigned long) priv->stats.tx_packets);
511 }
512 else
513 snull_interrupt(0, dev, NULL);
514 }
515
516 /*
517 * Transmit a packet (called by the kernel)
518 */
519 int snull_tx(struct sk_buff *skb, struct net_device *dev)
520 {
521 int len;
522 char *data, shortpkt[ETH_ZLEN];
523 struct snull_priv *priv = netdev_priv(dev);
524
525 data = skb->data;
526 len = skb->len;
527 if (len < ETH_ZLEN) {
528 memset(shortpkt, 0, ETH_ZLEN);
529 memcpy(shortpkt, skb->data, skb->len);
530 len = ETH_ZLEN;
531 data = shortpkt;
532 }
533 dev->trans_start = jiffies; /* save the timestamp */
534
535 /* Remember the skb, so we can free it at interrupt time */
536 priv->skb = skb;
537
538 /* actual deliver of data is device-specific, and not shown here */
539 snull_hw_tx(data, len, dev);
540
541 return 0; /* Our simple device can not fail */
542 }
543
544 /*
545 * Deal with a transmit timeout.
546 */
547 void snull_tx_timeout (struct net_device *dev)
548 {
549 struct snull_priv *priv = netdev_priv(dev);
550
551 PDEBUG("Transmit timeout at %ld, latency %ld\n", jiffies,
552 jiffies - dev->trans_start);
553 /* Simulate a transmission interrupt to get things moving */
554 priv->status = SNULL_TX_INTR;
555 snull_interrupt(0, dev, NULL);
556 priv->stats.tx_errors++;
557 netif_wake_queue(dev);
558 return;
559 }
560
561
562
563 /*
564 * Ioctl commands
565 */
566 int snull_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
567 {
568 PDEBUG("ioctl\n");
569 return 0;
570 }
571
572 /*
573 * Return statistics to the caller
574 */
575 struct net_device_stats *snull_stats(struct net_device *dev)
576 {
577 struct snull_priv *priv = netdev_priv(dev);
578 return &priv->stats;
579 }
580
581 /*
582 * This function is called to fill up an eth header, since arp is not
583 * available on the interface
584 */
585 int snull_rebuild_header(struct sk_buff *skb)
586 {
587 struct ethhdr *eth = (struct ethhdr *) skb->data;
588 struct net_device *dev = skb->dev;
589
590 memcpy(eth->h_source, dev->dev_addr, dev->addr_len);
591 memcpy(eth->h_dest, dev->dev_addr, dev->addr_len);
592 eth->h_dest[ETH_ALEN-1] ^= 0x01; /* dest is us xor 1 */
593 return 0;
594 }
595
596
597 int snull_header(struct sk_buff *skb, struct net_device *dev,
598 unsigned short type, const void *daddr, const void *saddr,
599 unsigned int len)
600 {
601 struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN);
602
603 eth->h_proto = htons(type);
604 memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len);
605 memcpy(eth->h_dest, daddr ? daddr : dev->dev_addr, dev->addr_len);
606 eth->h_dest[ETH_ALEN-1] ^= 0x01; /* dest is us xor 1 */
607 return (dev->hard_header_len);
608 }
609
610
611
612
613
614 /*
615 * The "change_mtu" method is usually not needed.
616 * If you need it, it must be like this.
617 */
618 int snull_change_mtu(struct net_device *dev, int new_mtu)
619 {
620 unsigned long flags;
621 struct snull_priv *priv = netdev_priv(dev);
622 spinlock_t *lock = &priv->lock;
623
624 /* check ranges */
625 if ((new_mtu < 68) || (new_mtu > 1500))
626 return -EINVAL;
627 /*
628 * Do anything you need, and the accept the value
629 */
630 spin_lock_irqsave(lock, flags);
631 dev->mtu = new_mtu;
632 spin_unlock_irqrestore(lock, flags);
633 return 0; /* success */
634 }
635
636 static const struct header_ops snull_header_ops = {
637 .create = snull_header,
638 .rebuild = snull_rebuild_header,
639 .cache = NULL, /* disable caching */
640 };
641
642 /*
643 * The init function (sometimes called probe).
644 * It is invoked by register_netdev()
645 */
646 void snull_init(struct net_device *dev)
647 {
648 struct snull_priv *priv;
649 #if 0
650 /*
651 * Make the usual checks: check_region(), probe irq, ... -ENODEV
652 * should be returned if no device found. No resource should be
653 * grabbed: this is done on open().
654 */
655 #endif
656
657 /*
658 * Then, assign other fields in dev, using ether_setup() and some
659 * hand assignments
660 */
661 ether_setup(dev); /* assign some of the fields */
662
663 dev->open = snull_open;
664 dev->stop = snull_release;
665 dev->set_config = snull_config;
666 dev->hard_start_xmit = snull_tx;
667 dev->do_ioctl = snull_ioctl;
668 dev->get_stats = snull_stats;
669 dev->change_mtu = snull_change_mtu;
670 dev->header_ops = &snull_header_ops;
671 dev->tx_timeout = snull_tx_timeout;
672 dev->watchdog_timeo = timeout;
673 /* keep the default flags, just add NOARP */
674 dev->flags |= IFF_NOARP;
675 dev->features |= NETIF_F_NO_CSUM;
676 /*
677 * Then, initialize the priv field. This encloses the statistics
678 * and a few private fields.
679 */
680 priv = netdev_priv(dev);
681 memset(priv, 0, sizeof(struct snull_priv));
682 priv->dev = dev;
683 netif_napi_add(dev, &priv->napi, snull_poll, 2);
684 /* The last parameter above is the NAPI "weight". */
685 spin_lock_init(&priv->lock);
686 snull_rx_ints(dev, 1); /* enable receive interrupts */
687 snull_setup_pool(dev);
688 }
689
690 /*
691 * The devices
692 */
693
694 struct net_device *snull_devs[2];
695
696
697
698 /*
699 * Finally, the module stuff
700 */
701
702 void snull_cleanup(void)
703 {
704 int i;
705
706 for (i = 0; i < 2; i++) {
707 if (snull_devs[i]) {
708 unregister_netdev(snull_devs[i]);
709 snull_teardown_pool(snull_devs[i]);
710 free_netdev(snull_devs[i]);
711 }
712 }
713 return;
714 }
715
716
717
718
719 int snull_init_module(void)
720 {
721 int result, i, ret = -ENOMEM;
722
723 snull_interrupt = use_napi ? snull_napi_interrupt : snull_regular_interrupt;
724
725 /* Allocate the devices */
726 snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
727 snull_init);
728 snull_devs[1] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
729 snull_init);
730 if (snull_devs[0] == NULL || snull_devs[1] == NULL)
731 goto out;
732
733 ret = -ENODEV;
734 for (i = 0; i < 2; i++)
735 if ((result = register_netdev(snull_devs[i])))
736 printk("snull: error %i registering device \"%s\"\n",
737 result, snull_devs[i]->name);
738 else
739 ret = 0;
740 out:
741 if (ret)
742 snull_cleanup();
743 return ret;
744 }
745
746
747 module_init(snull_init_module);
748 module_exit(snull_cleanup);
749
|
This page was automatically generated by the
LXR engine.
|