The Design and Implementation of the FreeBSD Operating System, Second Edition
Now available: The Design and Implementation of the FreeBSD Operating System (Second Edition)


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]

FreeBSD/Linux Kernel Cross Reference
sys/compat/linsysfs/linsysfs.c

Version: -  FREEBSD  -  FREEBSD-13-STABLE  -  FREEBSD-13-0  -  FREEBSD-12-STABLE  -  FREEBSD-12-0  -  FREEBSD-11-STABLE  -  FREEBSD-11-0  -  FREEBSD-10-STABLE  -  FREEBSD-10-0  -  FREEBSD-9-STABLE  -  FREEBSD-9-0  -  FREEBSD-8-STABLE  -  FREEBSD-8-0  -  FREEBSD-7-STABLE  -  FREEBSD-7-0  -  FREEBSD-6-STABLE  -  FREEBSD-6-0  -  FREEBSD-5-STABLE  -  FREEBSD-5-0  -  FREEBSD-4-STABLE  -  FREEBSD-3-STABLE  -  FREEBSD22  -  l41  -  OPENBSD  -  linux-2.6  -  MK84  -  PLAN9  -  xnu-8792 
SearchContext: -  none  -  3  -  10 

    1 /*-
    2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
    3  *
    4  * Copyright (c) 2006 IronPort Systems
    5  * All rights reserved.
    6  *
    7  * Redistribution and use in source and binary forms, with or without
    8  * modification, are permitted provided that the following conditions
    9  * are met:
   10  * 1. Redistributions of source code must retain the above copyright
   11  *    notice, this list of conditions and the following disclaimer.
   12  * 2. Redistributions in binary form must reproduce the above copyright
   13  *    notice, this list of conditions and the following disclaimer in the
   14  *    documentation and/or other materials provided with the distribution.
   15  *
   16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
   17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
   20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   26  * SUCH DAMAGE.
   27  */
   28 
   29 #include <sys/cdefs.h>
   30 __FBSDID("$FreeBSD: releng/12.0/sys/compat/linsysfs/linsysfs.c 336066 2018-07-07 15:25:11Z imp $");
   31 
   32 #include <sys/param.h>
   33 #include <sys/systm.h>
   34 #include <sys/queue.h>
   35 #include <sys/blist.h>
   36 #include <sys/conf.h>
   37 #include <sys/exec.h>
   38 #include <sys/filedesc.h>
   39 #include <sys/kernel.h>
   40 #include <sys/linker.h>
   41 #include <sys/malloc.h>
   42 #include <sys/mount.h>
   43 #include <sys/mutex.h>
   44 #include <sys/proc.h>
   45 #include <sys/resourcevar.h>
   46 #include <sys/sbuf.h>
   47 #include <sys/smp.h>
   48 #include <sys/socket.h>
   49 #include <sys/vnode.h>
   50 #include <sys/bus.h>
   51 #include <sys/pciio.h>
   52 
   53 #include <dev/pci/pcivar.h>
   54 #include <dev/pci/pcireg.h>
   55 
   56 #include <net/if.h>
   57 
   58 #include <vm/vm.h>
   59 #include <vm/pmap.h>
   60 #include <vm/vm_map.h>
   61 #include <vm/vm_param.h>
   62 #include <vm/vm_object.h>
   63 #include <vm/swap_pager.h>
   64 
   65 #include <machine/bus.h>
   66 
   67 #include <compat/linux/linux_ioctl.h>
   68 #include <compat/linux/linux_mib.h>
   69 #include <compat/linux/linux_util.h>
   70 #include <fs/pseudofs/pseudofs.h>
   71 
   72 struct scsi_host_queue {
   73         TAILQ_ENTRY(scsi_host_queue) scsi_host_next;
   74         char *path;
   75         char *name;
   76 };
   77 
   78 TAILQ_HEAD(,scsi_host_queue) scsi_host_q;
   79 
   80 static int host_number = 0;
   81 
   82 static int
   83 atoi(const char *str)
   84 {
   85         return (int)strtol(str, (char **)NULL, 10);
   86 }
   87 
   88 /*
   89  * Filler function for proc_name
   90  */
   91 static int
   92 linsysfs_scsiname(PFS_FILL_ARGS)
   93 {
   94         struct scsi_host_queue *scsi_host;
   95         int index;
   96 
   97         if (strncmp(pn->pn_parent->pn_name, "host", 4) == 0) {
   98                 index = atoi(&pn->pn_parent->pn_name[4]);
   99         } else {
  100                 sbuf_printf(sb, "unknown\n");
  101                 return (0);
  102         }
  103         TAILQ_FOREACH(scsi_host, &scsi_host_q, scsi_host_next) {
  104                 if (index-- == 0) {
  105                         sbuf_printf(sb, "%s\n", scsi_host->name);
  106                         return (0);
  107                 }
  108         }
  109         sbuf_printf(sb, "unknown\n");
  110         return (0);
  111 }
  112 
  113 /*
  114  * Filler function for device sym-link
  115  */
  116 static int
  117 linsysfs_link_scsi_host(PFS_FILL_ARGS)
  118 {
  119         struct scsi_host_queue *scsi_host;
  120         int index;
  121 
  122         if (strncmp(pn->pn_parent->pn_name, "host", 4) == 0) {
  123                 index = atoi(&pn->pn_parent->pn_name[4]);
  124         } else {
  125                 sbuf_printf(sb, "unknown\n");
  126                 return (0);
  127         }
  128         TAILQ_FOREACH(scsi_host, &scsi_host_q, scsi_host_next) {
  129                 if (index-- == 0) {
  130                         sbuf_printf(sb, "../../../devices%s", scsi_host->path);
  131                         return(0);
  132                 }
  133         }
  134         sbuf_printf(sb, "unknown\n");
  135         return (0);
  136 }
  137 
  138 static int
  139 linsysfs_fill_data(PFS_FILL_ARGS)
  140 {
  141         sbuf_printf(sb, "%s", (char *)pn->pn_data);
  142         return (0);
  143 }
  144 
  145 static int
  146 linsysfs_fill_vendor(PFS_FILL_ARGS)
  147 {
  148         sbuf_printf(sb, "0x%04x\n", pci_get_vendor((device_t)pn->pn_data));
  149         return (0);
  150 }
  151 
  152 static int
  153 linsysfs_fill_device(PFS_FILL_ARGS)
  154 {
  155         sbuf_printf(sb, "0x%04x\n", pci_get_device((device_t)pn->pn_data));
  156         return (0);
  157 }
  158 
  159 static int
  160 linsysfs_fill_subvendor(PFS_FILL_ARGS)
  161 {
  162         sbuf_printf(sb, "0x%04x\n", pci_get_subvendor((device_t)pn->pn_data));
  163         return (0);
  164 }
  165 
  166 static int
  167 linsysfs_fill_subdevice(PFS_FILL_ARGS)
  168 {
  169         sbuf_printf(sb, "0x%04x\n", pci_get_subdevice((device_t)pn->pn_data));
  170         return (0);
  171 }
  172 
  173 static int
  174 linsysfs_fill_revid(PFS_FILL_ARGS)
  175 {
  176         sbuf_printf(sb, "0x%x\n", pci_get_revid((device_t)pn->pn_data));
  177         return (0);
  178 }
  179 
  180 static int
  181 linsysfs_fill_config(PFS_FILL_ARGS)
  182 {
  183         uint8_t config[48];
  184         device_t dev;
  185         uint32_t reg;
  186 
  187         dev = (device_t)pn->pn_data;
  188         bzero(config, sizeof(config));
  189         reg = pci_get_vendor(dev);
  190         config[0] = reg;
  191         config[1] = reg >> 8;
  192         reg = pci_get_device(dev);
  193         config[2] = reg;
  194         config[3] = reg >> 8;
  195         reg = pci_get_revid(dev);
  196         config[8] = reg;
  197         reg = pci_get_subvendor(dev);
  198         config[44] = reg;
  199         config[45] = reg >> 8;
  200         reg = pci_get_subdevice(dev);
  201         config[46] = reg;
  202         config[47] = reg >> 8;
  203         sbuf_bcat(sb, config, sizeof(config));
  204         return (0);
  205 }
  206 
  207 /*
  208  * Filler function for PCI uevent file
  209  */
  210 static int
  211 linsysfs_fill_uevent_pci(PFS_FILL_ARGS)
  212 {
  213         device_t dev;
  214 
  215         dev = (device_t)pn->pn_data;
  216         sbuf_printf(sb, "DRIVER=%s\nPCI_CLASS=%X\nPCI_ID=%04X:%04X\n"
  217             "PCI_SUBSYS_ID=%04X:%04X\nPCI_SLOT_NAME=%04d:%02x:%02x.%x\n",
  218             linux_driver_get_name_dev(dev), pci_get_class(dev),
  219             pci_get_vendor(dev), pci_get_device(dev), pci_get_subvendor(dev),
  220             pci_get_subdevice(dev), pci_get_domain(dev), pci_get_bus(dev),
  221             pci_get_slot(dev), pci_get_function(dev));
  222         return (0);
  223 }
  224 
  225 /*
  226  * Filler function for drm uevent file
  227  */
  228 static int
  229 linsysfs_fill_uevent_drm(PFS_FILL_ARGS)
  230 {
  231         device_t dev;
  232         int unit;
  233 
  234         dev = (device_t)pn->pn_data;
  235         unit = device_get_unit(dev);
  236         sbuf_printf(sb,
  237             "MAJOR=226\nMINOR=%d\nDEVNAME=dri/card%d\nDEVTYPE=dri_minor\n",
  238             unit, unit);
  239         return (0);
  240 }
  241 
  242 static char *
  243 get_full_pfs_path(struct pfs_node *cur)
  244 {
  245         char *temp, *path;
  246 
  247         temp = malloc(MAXPATHLEN, M_TEMP, M_WAITOK);
  248         path = malloc(MAXPATHLEN, M_TEMP, M_WAITOK);
  249         path[0] = '\0';
  250 
  251         do {
  252                 snprintf(temp, MAXPATHLEN, "%s/%s", cur->pn_name, path);
  253                 strlcpy(path, temp, MAXPATHLEN);
  254                 cur = cur->pn_parent;
  255         } while (cur->pn_parent != NULL);
  256 
  257         path[strlen(path) - 1] = '\0'; /* remove extra slash */
  258         free(temp, M_TEMP);
  259         return (path);
  260 }
  261 
  262 /*
  263  * Filler function for symlink from drm char device to PCI device
  264  */
  265 static int
  266 linsysfs_fill_vgapci(PFS_FILL_ARGS)
  267 {
  268         char *path;
  269 
  270         path = get_full_pfs_path((struct pfs_node*)pn->pn_data);
  271         sbuf_printf(sb, "../../../%s", path);
  272         free(path, M_TEMP);
  273         return (0);
  274 }
  275 
  276 #undef PCI_DEV
  277 #define PCI_DEV "pci"
  278 #define DRMN_DEV "drmn"
  279 static int
  280 linsysfs_run_bus(device_t dev, struct pfs_node *dir, struct pfs_node *scsi,
  281     struct pfs_node *chardev, struct pfs_node *drm, char *path, char *prefix)
  282 {
  283         struct scsi_host_queue *scsi_host;
  284         struct pfs_node *sub_dir, *cur_file;
  285         int i, nchildren, error;
  286         device_t *children, parent;
  287         devclass_t devclass;
  288         const char *name = NULL;
  289         struct pci_devinfo *dinfo;
  290         char *device, *host, *new_path, *devname;
  291 
  292         new_path = path;
  293         devname = malloc(16, M_TEMP, M_WAITOK);
  294 
  295         parent = device_get_parent(dev);
  296         if (parent) {
  297                 devclass = device_get_devclass(parent);
  298                 if (devclass != NULL)
  299                         name = devclass_get_name(devclass);
  300                 if (name && strcmp(name, PCI_DEV) == 0) {
  301                         dinfo = device_get_ivars(dev);
  302                         if (dinfo) {
  303                                 device = malloc(MAXPATHLEN, M_TEMP, M_WAITOK);
  304                                 new_path = malloc(MAXPATHLEN, M_TEMP,
  305                                     M_WAITOK);
  306                                 new_path[0] = '\000';
  307                                 strcpy(new_path, path);
  308                                 host = malloc(MAXPATHLEN, M_TEMP, M_WAITOK);
  309                                 device[0] = '\000';
  310                                 sprintf(device, "%s:%02x:%02x.%x",
  311                                     prefix,
  312                                     dinfo->cfg.bus,
  313                                     dinfo->cfg.slot,
  314                                     dinfo->cfg.func);
  315                                 strcat(new_path, "/");
  316                                 strcat(new_path, device);
  317                                 dir = pfs_create_dir(dir, device,
  318                                     NULL, NULL, NULL, 0);
  319                                 cur_file = pfs_create_file(dir, "vendor",
  320                                     &linsysfs_fill_vendor, NULL, NULL, NULL,
  321                                     PFS_RD);
  322                                 cur_file->pn_data = (void*)dev;
  323                                 cur_file = pfs_create_file(dir, "device",
  324                                     &linsysfs_fill_device, NULL, NULL, NULL,
  325                                     PFS_RD);
  326                                 cur_file->pn_data = (void*)dev;
  327                                 cur_file = pfs_create_file(dir,
  328                                     "subsystem_vendor",
  329                                     &linsysfs_fill_subvendor, NULL, NULL, NULL,
  330                                     PFS_RD);
  331                                 cur_file->pn_data = (void*)dev;
  332                                 cur_file = pfs_create_file(dir,
  333                                     "subsystem_device",
  334                                     &linsysfs_fill_subdevice, NULL, NULL, NULL,
  335                                     PFS_RD);
  336                                 cur_file->pn_data = (void*)dev;
  337                                 cur_file = pfs_create_file(dir, "revision",
  338                                     &linsysfs_fill_revid, NULL, NULL, NULL,
  339                                     PFS_RD);
  340                                 cur_file->pn_data = (void*)dev;
  341                                 cur_file = pfs_create_file(dir, "config",
  342                                     &linsysfs_fill_config, NULL, NULL, NULL,
  343                                     PFS_RD);
  344                                 cur_file->pn_data = (void*)dev;
  345                                 cur_file = pfs_create_file(dir, "uevent",
  346                                     &linsysfs_fill_uevent_pci, NULL, NULL,
  347                                     NULL, PFS_RD);
  348                                 cur_file->pn_data = (void*)dev;
  349                                 cur_file = pfs_create_link(dir, "subsystem",
  350                                     &linsysfs_fill_data, NULL, NULL, NULL, 0);
  351                                 /* libdrm just checks that the link ends in "/pci" */
  352                                 cur_file->pn_data = "/sys/bus/pci";
  353 
  354                                 if (dinfo->cfg.baseclass == PCIC_STORAGE) {
  355                                         /* DJA only make this if needed */
  356                                         sprintf(host, "host%d", host_number++);
  357                                         strcat(new_path, "/");
  358                                         strcat(new_path, host);
  359                                         pfs_create_dir(dir, host,
  360                                             NULL, NULL, NULL, 0);
  361                                         scsi_host = malloc(sizeof(
  362                                             struct scsi_host_queue),
  363                                             M_DEVBUF, M_NOWAIT);
  364                                         scsi_host->path = malloc(
  365                                             strlen(new_path) + 1,
  366                                             M_DEVBUF, M_NOWAIT);
  367                                         scsi_host->path[0] = '\000';
  368                                         bcopy(new_path, scsi_host->path,
  369                                             strlen(new_path) + 1);
  370                                         scsi_host->name = "unknown";
  371 
  372                                         sub_dir = pfs_create_dir(scsi, host,
  373                                             NULL, NULL, NULL, 0);
  374                                         pfs_create_link(sub_dir, "device",
  375                                             &linsysfs_link_scsi_host,
  376                                             NULL, NULL, NULL, 0);
  377                                         pfs_create_file(sub_dir, "proc_name",
  378                                             &linsysfs_scsiname,
  379                                             NULL, NULL, NULL, PFS_RD);
  380                                         scsi_host->name
  381                                             = linux_driver_get_name_dev(dev);
  382                                         TAILQ_INSERT_TAIL(&scsi_host_q,
  383                                             scsi_host, scsi_host_next);
  384                                 }
  385                                 free(device, M_TEMP);
  386                                 free(host, M_TEMP);
  387                         }
  388                 }
  389 
  390                 devclass = device_get_devclass(dev);
  391                 if (devclass != NULL)
  392                         name = devclass_get_name(devclass);
  393                 else
  394                         name = NULL;
  395                 if (name != NULL && strcmp(name, DRMN_DEV) == 0 &&
  396                     device_get_unit(dev) >= 0) {
  397                         dinfo = device_get_ivars(parent);
  398                         if (dinfo != NULL && dinfo->cfg.baseclass == PCIC_DISPLAY) {
  399                                 sprintf(devname, "226:%d",
  400                                     device_get_unit(dev));
  401                                 sub_dir = pfs_create_dir(chardev,
  402                                     devname, NULL, NULL, NULL, 0);
  403                                 cur_file = pfs_create_link(sub_dir,
  404                                     "device", &linsysfs_fill_vgapci, NULL,
  405                                     NULL, NULL, PFS_RD);
  406                                 cur_file->pn_data = (void*)dir;
  407                                 cur_file = pfs_create_file(sub_dir,
  408                                     "uevent", &linsysfs_fill_uevent_drm, NULL,
  409                                     NULL, NULL, PFS_RD);
  410                                 cur_file->pn_data = (void*)dev;
  411                                 sprintf(devname, "card%d",
  412                                     device_get_unit(dev));
  413                                 sub_dir = pfs_create_dir(drm,
  414                                     devname, NULL, NULL, NULL, 0);
  415                                 cur_file = pfs_create_link(sub_dir,
  416                                     "device", &linsysfs_fill_vgapci, NULL,
  417                                     NULL, NULL, PFS_RD);
  418                                 cur_file->pn_data = (void*)dir;
  419                         }
  420                 }
  421         }
  422 
  423         error = device_get_children(dev, &children, &nchildren);
  424         if (error == 0) {
  425                 for (i = 0; i < nchildren; i++)
  426                         if (children[i])
  427                                 linsysfs_run_bus(children[i], dir, scsi,
  428                                     chardev, drm, new_path, prefix);
  429                 free(children, M_TEMP);
  430         }
  431         if (new_path != path)
  432                 free(new_path, M_TEMP);
  433         free(devname, M_TEMP);
  434 
  435         return (1);
  436 }
  437 
  438 /*
  439  * Filler function for sys/devices/system/cpu/online
  440  */
  441 static int
  442 linsysfs_cpuonline(PFS_FILL_ARGS)
  443 {
  444 
  445         sbuf_printf(sb, "%d-%d\n", CPU_FIRST(), mp_maxid);
  446         return (0);
  447 }
  448 
  449 /*
  450  * Filler function for sys/devices/system/cpu/cpuX/online
  451  */
  452 static int
  453 linsysfs_cpuxonline(PFS_FILL_ARGS)
  454 {
  455 
  456         sbuf_printf(sb, "1\n");
  457         return (0);
  458 }
  459 
  460 static void
  461 linsysfs_listcpus(struct pfs_node *dir)
  462 {
  463         struct pfs_node *cpu;
  464         char *name;
  465         int i, count, len;
  466 
  467         len = 1;
  468         count = mp_maxcpus;
  469         while (count > 10) {
  470                 count /= 10;
  471                 len++;
  472         }
  473         len += sizeof("cpu");
  474         name = malloc(len, M_TEMP, M_WAITOK);
  475 
  476         for (i = 0; i < mp_ncpus; ++i) {
  477                 /* /sys/devices/system/cpu/cpuX */
  478                 sprintf(name, "cpu%d", i);
  479                 cpu = pfs_create_dir(dir, name, NULL, NULL, NULL, 0);
  480 
  481                 pfs_create_file(cpu, "online", &linsysfs_cpuxonline,
  482                     NULL, NULL, NULL, PFS_RD);
  483         }
  484         free(name, M_TEMP);
  485 }
  486 
  487 /*
  488  * Constructor
  489  */
  490 static int
  491 linsysfs_init(PFS_INIT_ARGS)
  492 {
  493         struct pfs_node *root;
  494         struct pfs_node *class;
  495         struct pfs_node *dir, *sys, *cpu;
  496         struct pfs_node *drm;
  497         struct pfs_node *pci;
  498         struct pfs_node *scsi;
  499         struct pfs_node *devdir, *chardev;
  500         devclass_t devclass;
  501         device_t dev;
  502 
  503         TAILQ_INIT(&scsi_host_q);
  504 
  505         root = pi->pi_root;
  506 
  507         /* /sys/class/... */
  508         class = pfs_create_dir(root, "class", NULL, NULL, NULL, 0);
  509         scsi = pfs_create_dir(class, "scsi_host", NULL, NULL, NULL, 0);
  510         drm = pfs_create_dir(class, "drm", NULL, NULL, NULL, 0);
  511 
  512         /* /sys/dev/... */
  513         devdir = pfs_create_dir(root, "dev", NULL, NULL, NULL, 0);
  514         chardev = pfs_create_dir(devdir, "char", NULL, NULL, NULL, 0);
  515 
  516         /* /sys/devices/... */
  517         dir = pfs_create_dir(root, "devices", NULL, NULL, NULL, 0);
  518         pci = pfs_create_dir(dir, "pci0000:00", NULL, NULL, NULL, 0);
  519 
  520         devclass = devclass_find("root");
  521         if (devclass == NULL) {
  522                 return (0);
  523         }
  524 
  525         dev = devclass_get_device(devclass, 0);
  526         linsysfs_run_bus(dev, pci, scsi, chardev, drm, "/pci0000:00", "0000");
  527 
  528         /* /sys/devices/system */
  529         sys = pfs_create_dir(dir, "system", NULL, NULL, NULL, 0);
  530 
  531         /* /sys/devices/system/cpu */
  532         cpu = pfs_create_dir(sys, "cpu", NULL, NULL, NULL, 0);
  533 
  534         pfs_create_file(cpu, "online", &linsysfs_cpuonline,
  535             NULL, NULL, NULL, PFS_RD);
  536 
  537         linsysfs_listcpus(cpu);
  538 
  539         return (0);
  540 }
  541 
  542 /*
  543  * Destructor
  544  */
  545 static int
  546 linsysfs_uninit(PFS_INIT_ARGS)
  547 {
  548         struct scsi_host_queue *scsi_host, *scsi_host_tmp;
  549 
  550         TAILQ_FOREACH_SAFE(scsi_host, &scsi_host_q, scsi_host_next,
  551             scsi_host_tmp) {
  552                 TAILQ_REMOVE(&scsi_host_q, scsi_host, scsi_host_next);
  553                 free(scsi_host->path, M_TEMP);
  554                 free(scsi_host, M_TEMP);
  555         }
  556 
  557         return (0);
  558 }
  559 
  560 PSEUDOFS(linsysfs, 1, VFCF_JAIL);
  561 #if defined(__aarch64__) || defined(__amd64__)
  562 MODULE_DEPEND(linsysfs, linux_common, 1, 1, 1);
  563 #else
  564 MODULE_DEPEND(linsysfs, linux, 1, 1, 1);
  565 #endif

Cache object: 2e87c54591ffe76883de0e978418fe57


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]


This page is part of the FreeBSD/Linux Linux Kernel Cross-Reference, and was automatically generated using a modified version of the LXR engine.