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/dev/mlx5/mlx5_ib/mlx5_ib_gsi.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  * Copyright (c) 2016-2020, Mellanox Technologies, Ltd.  All rights reserved.
    3  *
    4  * Redistribution and use in source and binary forms, with or without
    5  * modification, are permitted provided that the following conditions
    6  * are met:
    7  * 1. Redistributions of source code must retain the above copyright
    8  *    notice, this list of conditions and the following disclaimer.
    9  * 2. Redistributions in binary form must reproduce the above copyright
   10  *    notice, this list of conditions and the following disclaimer in the
   11  *    documentation and/or other materials provided with the distribution.
   12  *
   13  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS `AS IS' AND
   14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   16  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
   17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   23  * SUCH DAMAGE.
   24  *
   25  * $FreeBSD$
   26  */
   27 
   28 #include "opt_rss.h"
   29 #include "opt_ratelimit.h"
   30 
   31 #include <dev/mlx5/mlx5_ib/mlx5_ib.h>
   32 
   33 struct mlx5_ib_gsi_wr {
   34         struct ib_cqe cqe;
   35         struct ib_wc wc;
   36         int send_flags;
   37         bool completed:1;
   38 };
   39 
   40 struct mlx5_ib_gsi_qp {
   41         struct ib_qp ibqp;
   42         struct ib_qp *rx_qp;
   43         u8 port_num;
   44         struct ib_qp_cap cap;
   45         enum ib_sig_type sq_sig_type;
   46         /* Serialize qp state modifications */
   47         struct mutex mutex;
   48         struct ib_cq *cq;
   49         struct mlx5_ib_gsi_wr *outstanding_wrs;
   50         u32 outstanding_pi, outstanding_ci;
   51         int num_qps;
   52         /* Protects access to the tx_qps. Post send operations synchronize
   53          * with tx_qp creation in setup_qp(). Also protects the
   54          * outstanding_wrs array and indices.
   55          */
   56         spinlock_t lock;
   57         struct ib_qp **tx_qps;
   58 };
   59 
   60 static struct mlx5_ib_gsi_qp *gsi_qp(struct ib_qp *qp)
   61 {
   62         return container_of(qp, struct mlx5_ib_gsi_qp, ibqp);
   63 }
   64 
   65 static bool mlx5_ib_deth_sqpn_cap(struct mlx5_ib_dev *dev)
   66 {
   67         return MLX5_CAP_GEN(dev->mdev, set_deth_sqpn);
   68 }
   69 
   70 /* Call with gsi->lock locked */
   71 static void generate_completions(struct mlx5_ib_gsi_qp *gsi)
   72 {
   73         struct ib_cq *gsi_cq = gsi->ibqp.send_cq;
   74         struct mlx5_ib_gsi_wr *wr;
   75         u32 index;
   76 
   77         for (index = gsi->outstanding_ci; index != gsi->outstanding_pi;
   78              index++) {
   79                 wr = &gsi->outstanding_wrs[index % gsi->cap.max_send_wr];
   80 
   81                 if (!wr->completed)
   82                         break;
   83 
   84                 if (gsi->sq_sig_type == IB_SIGNAL_ALL_WR ||
   85                     wr->send_flags & IB_SEND_SIGNALED)
   86                         WARN_ON_ONCE(mlx5_ib_generate_wc(gsi_cq, &wr->wc));
   87 
   88                 wr->completed = false;
   89         }
   90 
   91         gsi->outstanding_ci = index;
   92 }
   93 
   94 static void handle_single_completion(struct ib_cq *cq, struct ib_wc *wc)
   95 {
   96         struct mlx5_ib_gsi_qp *gsi = cq->cq_context;
   97         struct mlx5_ib_gsi_wr *wr =
   98                 container_of(wc->wr_cqe, struct mlx5_ib_gsi_wr, cqe);
   99         u64 wr_id;
  100         unsigned long flags;
  101 
  102         spin_lock_irqsave(&gsi->lock, flags);
  103         wr->completed = true;
  104         wr_id = wr->wc.wr_id;
  105         wr->wc = *wc;
  106         wr->wc.wr_id = wr_id;
  107         wr->wc.qp = &gsi->ibqp;
  108 
  109         generate_completions(gsi);
  110         spin_unlock_irqrestore(&gsi->lock, flags);
  111 }
  112 
  113 struct ib_qp *mlx5_ib_gsi_create_qp(struct ib_pd *pd,
  114                                     struct ib_qp_init_attr *init_attr)
  115 {
  116         struct mlx5_ib_dev *dev = to_mdev(pd->device);
  117         struct mlx5_ib_gsi_qp *gsi;
  118         struct ib_qp_init_attr hw_init_attr = *init_attr;
  119         const u8 port_num = init_attr->port_num;
  120         const int num_pkeys = pd->device->attrs.max_pkeys;
  121         const int num_qps = mlx5_ib_deth_sqpn_cap(dev) ? num_pkeys : 0;
  122         int ret;
  123 
  124         mlx5_ib_dbg(dev, "creating GSI QP\n");
  125 
  126         if (port_num > ARRAY_SIZE(dev->devr.ports) || port_num < 1) {
  127                 mlx5_ib_warn(dev,
  128                              "invalid port number %d during GSI QP creation\n",
  129                              port_num);
  130                 return ERR_PTR(-EINVAL);
  131         }
  132 
  133         gsi = kzalloc(sizeof(*gsi), GFP_KERNEL);
  134         if (!gsi)
  135                 return ERR_PTR(-ENOMEM);
  136 
  137         gsi->tx_qps = kcalloc(num_qps, sizeof(*gsi->tx_qps), GFP_KERNEL);
  138         if (!gsi->tx_qps) {
  139                 ret = -ENOMEM;
  140                 goto err_free;
  141         }
  142 
  143         gsi->outstanding_wrs = kcalloc(init_attr->cap.max_send_wr,
  144                                        sizeof(*gsi->outstanding_wrs),
  145                                        GFP_KERNEL);
  146         if (!gsi->outstanding_wrs) {
  147                 ret = -ENOMEM;
  148                 goto err_free_tx;
  149         }
  150 
  151         mutex_init(&gsi->mutex);
  152 
  153         mutex_lock(&dev->devr.mutex);
  154 
  155         if (dev->devr.ports[port_num - 1].gsi) {
  156                 mlx5_ib_warn(dev, "GSI QP already exists on port %d\n",
  157                              port_num);
  158                 ret = -EBUSY;
  159                 goto err_free_wrs;
  160         }
  161         gsi->num_qps = num_qps;
  162         spin_lock_init(&gsi->lock);
  163 
  164         gsi->cap = init_attr->cap;
  165         gsi->sq_sig_type = init_attr->sq_sig_type;
  166         gsi->ibqp.qp_num = 1;
  167         gsi->port_num = port_num;
  168 
  169         gsi->cq = ib_alloc_cq(pd->device, gsi, init_attr->cap.max_send_wr, 0,
  170                               IB_POLL_SOFTIRQ);
  171         if (IS_ERR(gsi->cq)) {
  172                 mlx5_ib_warn(dev, "unable to create send CQ for GSI QP. error %ld\n",
  173                              PTR_ERR(gsi->cq));
  174                 ret = PTR_ERR(gsi->cq);
  175                 goto err_free_wrs;
  176         }
  177 
  178         hw_init_attr.qp_type = MLX5_IB_QPT_HW_GSI;
  179         hw_init_attr.send_cq = gsi->cq;
  180         if (num_qps) {
  181                 hw_init_attr.cap.max_send_wr = 0;
  182                 hw_init_attr.cap.max_send_sge = 0;
  183                 hw_init_attr.cap.max_inline_data = 0;
  184         }
  185         gsi->rx_qp = ib_create_qp(pd, &hw_init_attr);
  186         if (IS_ERR(gsi->rx_qp)) {
  187                 mlx5_ib_warn(dev, "unable to create hardware GSI QP. error %ld\n",
  188                              PTR_ERR(gsi->rx_qp));
  189                 ret = PTR_ERR(gsi->rx_qp);
  190                 goto err_destroy_cq;
  191         }
  192 
  193         dev->devr.ports[init_attr->port_num - 1].gsi = gsi;
  194 
  195         mutex_unlock(&dev->devr.mutex);
  196 
  197         return &gsi->ibqp;
  198 
  199 err_destroy_cq:
  200         ib_free_cq(gsi->cq);
  201 err_free_wrs:
  202         mutex_unlock(&dev->devr.mutex);
  203         kfree(gsi->outstanding_wrs);
  204 err_free_tx:
  205         kfree(gsi->tx_qps);
  206 err_free:
  207         kfree(gsi);
  208         return ERR_PTR(ret);
  209 }
  210 
  211 int mlx5_ib_gsi_destroy_qp(struct ib_qp *qp)
  212 {
  213         struct mlx5_ib_dev *dev = to_mdev(qp->device);
  214         struct mlx5_ib_gsi_qp *gsi = gsi_qp(qp);
  215         const int port_num = gsi->port_num;
  216         int qp_index;
  217         int ret;
  218 
  219         mlx5_ib_dbg(dev, "destroying GSI QP\n");
  220 
  221         mutex_lock(&dev->devr.mutex);
  222         ret = ib_destroy_qp(gsi->rx_qp);
  223         if (ret) {
  224                 mlx5_ib_warn(dev, "unable to destroy hardware GSI QP. error %d\n",
  225                              ret);
  226                 mutex_unlock(&dev->devr.mutex);
  227                 return ret;
  228         }
  229         dev->devr.ports[port_num - 1].gsi = NULL;
  230         mutex_unlock(&dev->devr.mutex);
  231         gsi->rx_qp = NULL;
  232 
  233         for (qp_index = 0; qp_index < gsi->num_qps; ++qp_index) {
  234                 if (!gsi->tx_qps[qp_index])
  235                         continue;
  236                 WARN_ON_ONCE(ib_destroy_qp(gsi->tx_qps[qp_index]));
  237                 gsi->tx_qps[qp_index] = NULL;
  238         }
  239 
  240         ib_free_cq(gsi->cq);
  241 
  242         kfree(gsi->outstanding_wrs);
  243         kfree(gsi->tx_qps);
  244         kfree(gsi);
  245 
  246         return 0;
  247 }
  248 
  249 static struct ib_qp *create_gsi_ud_qp(struct mlx5_ib_gsi_qp *gsi)
  250 {
  251         struct ib_pd *pd = gsi->rx_qp->pd;
  252         struct ib_qp_init_attr init_attr = {
  253                 .event_handler = gsi->rx_qp->event_handler,
  254                 .qp_context = gsi->rx_qp->qp_context,
  255                 .send_cq = gsi->cq,
  256                 .recv_cq = gsi->rx_qp->recv_cq,
  257                 .cap = {
  258                         .max_send_wr = gsi->cap.max_send_wr,
  259                         .max_send_sge = gsi->cap.max_send_sge,
  260                         .max_inline_data = gsi->cap.max_inline_data,
  261                 },
  262                 .sq_sig_type = gsi->sq_sig_type,
  263                 .qp_type = IB_QPT_UD,
  264                 .create_flags = MLX5_IB_QP_CREATE_SQPN_QP1,
  265         };
  266 
  267         return ib_create_qp(pd, &init_attr);
  268 }
  269 
  270 static int modify_to_rts(struct mlx5_ib_gsi_qp *gsi, struct ib_qp *qp,
  271                          u16 qp_index)
  272 {
  273         struct mlx5_ib_dev *dev = to_mdev(qp->device);
  274         struct ib_qp_attr attr;
  275         int mask;
  276         int ret;
  277 
  278         mask = IB_QP_STATE | IB_QP_PKEY_INDEX | IB_QP_QKEY | IB_QP_PORT;
  279         attr.qp_state = IB_QPS_INIT;
  280         attr.pkey_index = qp_index;
  281         attr.qkey = IB_QP1_QKEY;
  282         attr.port_num = gsi->port_num;
  283         ret = ib_modify_qp(qp, &attr, mask);
  284         if (ret) {
  285                 mlx5_ib_err(dev, "could not change QP%d state to INIT: %d\n",
  286                             qp->qp_num, ret);
  287                 return ret;
  288         }
  289 
  290         attr.qp_state = IB_QPS_RTR;
  291         ret = ib_modify_qp(qp, &attr, IB_QP_STATE);
  292         if (ret) {
  293                 mlx5_ib_err(dev, "could not change QP%d state to RTR: %d\n",
  294                             qp->qp_num, ret);
  295                 return ret;
  296         }
  297 
  298         attr.qp_state = IB_QPS_RTS;
  299         attr.sq_psn = 0;
  300         ret = ib_modify_qp(qp, &attr, IB_QP_STATE | IB_QP_SQ_PSN);
  301         if (ret) {
  302                 mlx5_ib_err(dev, "could not change QP%d state to RTS: %d\n",
  303                             qp->qp_num, ret);
  304                 return ret;
  305         }
  306 
  307         return 0;
  308 }
  309 
  310 static void setup_qp(struct mlx5_ib_gsi_qp *gsi, u16 qp_index)
  311 {
  312         struct ib_device *device = gsi->rx_qp->device;
  313         struct mlx5_ib_dev *dev = to_mdev(device);
  314         struct ib_qp *qp;
  315         unsigned long flags;
  316         u16 pkey;
  317         int ret;
  318 
  319         ret = ib_query_pkey(device, gsi->port_num, qp_index, &pkey);
  320         if (ret) {
  321                 mlx5_ib_warn(dev, "unable to read P_Key at port %d, index %d\n",
  322                              gsi->port_num, qp_index);
  323                 return;
  324         }
  325 
  326         if (!pkey) {
  327                 mlx5_ib_dbg(dev, "invalid P_Key at port %d, index %d.  Skipping.\n",
  328                             gsi->port_num, qp_index);
  329                 return;
  330         }
  331 
  332         spin_lock_irqsave(&gsi->lock, flags);
  333         qp = gsi->tx_qps[qp_index];
  334         spin_unlock_irqrestore(&gsi->lock, flags);
  335         if (qp) {
  336                 mlx5_ib_dbg(dev, "already existing GSI TX QP at port %d, index %d. Skipping\n",
  337                             gsi->port_num, qp_index);
  338                 return;
  339         }
  340 
  341         qp = create_gsi_ud_qp(gsi);
  342         if (IS_ERR(qp)) {
  343                 mlx5_ib_warn(dev, "unable to create hardware UD QP for GSI: %ld\n",
  344                              PTR_ERR(qp));
  345                 return;
  346         }
  347 
  348         ret = modify_to_rts(gsi, qp, qp_index);
  349         if (ret)
  350                 goto err_destroy_qp;
  351 
  352         spin_lock_irqsave(&gsi->lock, flags);
  353         WARN_ON_ONCE(gsi->tx_qps[qp_index]);
  354         gsi->tx_qps[qp_index] = qp;
  355         spin_unlock_irqrestore(&gsi->lock, flags);
  356 
  357         return;
  358 
  359 err_destroy_qp:
  360         WARN_ON_ONCE(qp);
  361 }
  362 
  363 static void setup_qps(struct mlx5_ib_gsi_qp *gsi)
  364 {
  365         u16 qp_index;
  366 
  367         for (qp_index = 0; qp_index < gsi->num_qps; ++qp_index)
  368                 setup_qp(gsi, qp_index);
  369 }
  370 
  371 int mlx5_ib_gsi_modify_qp(struct ib_qp *qp, struct ib_qp_attr *attr,
  372                           int attr_mask)
  373 {
  374         struct mlx5_ib_dev *dev = to_mdev(qp->device);
  375         struct mlx5_ib_gsi_qp *gsi = gsi_qp(qp);
  376         int ret;
  377 
  378         mlx5_ib_dbg(dev, "modifying GSI QP to state %d\n", attr->qp_state);
  379 
  380         mutex_lock(&gsi->mutex);
  381         ret = ib_modify_qp(gsi->rx_qp, attr, attr_mask);
  382         if (ret) {
  383                 mlx5_ib_warn(dev, "unable to modify GSI rx QP: %d\n", ret);
  384                 goto unlock;
  385         }
  386 
  387         if (to_mqp(gsi->rx_qp)->state == IB_QPS_RTS)
  388                 setup_qps(gsi);
  389 
  390 unlock:
  391         mutex_unlock(&gsi->mutex);
  392 
  393         return ret;
  394 }
  395 
  396 int mlx5_ib_gsi_query_qp(struct ib_qp *qp, struct ib_qp_attr *qp_attr,
  397                          int qp_attr_mask,
  398                          struct ib_qp_init_attr *qp_init_attr)
  399 {
  400         struct mlx5_ib_gsi_qp *gsi = gsi_qp(qp);
  401         int ret;
  402 
  403         mutex_lock(&gsi->mutex);
  404         ret = ib_query_qp(gsi->rx_qp, qp_attr, qp_attr_mask, qp_init_attr);
  405         qp_init_attr->cap = gsi->cap;
  406         mutex_unlock(&gsi->mutex);
  407 
  408         return ret;
  409 }
  410 
  411 /* Call with gsi->lock locked */
  412 static int mlx5_ib_add_outstanding_wr(struct mlx5_ib_gsi_qp *gsi,
  413                                       struct ib_ud_wr *wr, struct ib_wc *wc)
  414 {
  415         struct mlx5_ib_dev *dev = to_mdev(gsi->rx_qp->device);
  416         struct mlx5_ib_gsi_wr *gsi_wr;
  417 
  418         if (gsi->outstanding_pi == gsi->outstanding_ci + gsi->cap.max_send_wr) {
  419                 mlx5_ib_warn(dev, "no available GSI work request.\n");
  420                 return -ENOMEM;
  421         }
  422 
  423         gsi_wr = &gsi->outstanding_wrs[gsi->outstanding_pi %
  424                                        gsi->cap.max_send_wr];
  425         gsi->outstanding_pi++;
  426 
  427         if (!wc) {
  428                 memset(&gsi_wr->wc, 0, sizeof(gsi_wr->wc));
  429                 gsi_wr->wc.pkey_index = wr->pkey_index;
  430                 gsi_wr->wc.wr_id = wr->wr.wr_id;
  431         } else {
  432                 gsi_wr->wc = *wc;
  433                 gsi_wr->completed = true;
  434         }
  435 
  436         gsi_wr->cqe.done = &handle_single_completion;
  437         wr->wr.wr_cqe = &gsi_wr->cqe;
  438 
  439         return 0;
  440 }
  441 
  442 /* Call with gsi->lock locked */
  443 static int mlx5_ib_gsi_silent_drop(struct mlx5_ib_gsi_qp *gsi,
  444                                     struct ib_ud_wr *wr)
  445 {
  446         struct ib_wc wc = {
  447                 { .wr_id = wr->wr.wr_id },
  448                 .status = IB_WC_SUCCESS,
  449                 .opcode = IB_WC_SEND,
  450                 .qp = &gsi->ibqp,
  451         };
  452         int ret;
  453 
  454         ret = mlx5_ib_add_outstanding_wr(gsi, wr, &wc);
  455         if (ret)
  456                 return ret;
  457 
  458         generate_completions(gsi);
  459 
  460         return 0;
  461 }
  462 
  463 /* Call with gsi->lock locked */
  464 static struct ib_qp *get_tx_qp(struct mlx5_ib_gsi_qp *gsi, struct ib_ud_wr *wr)
  465 {
  466         struct mlx5_ib_dev *dev = to_mdev(gsi->rx_qp->device);
  467         int qp_index = wr->pkey_index;
  468 
  469         if (!mlx5_ib_deth_sqpn_cap(dev))
  470                 return gsi->rx_qp;
  471 
  472         if (qp_index >= gsi->num_qps)
  473                 return NULL;
  474 
  475         return gsi->tx_qps[qp_index];
  476 }
  477 
  478 int mlx5_ib_gsi_post_send(struct ib_qp *qp, const struct ib_send_wr *wr,
  479                           const struct ib_send_wr **bad_wr)
  480 {
  481         struct mlx5_ib_gsi_qp *gsi = gsi_qp(qp);
  482         struct ib_qp *tx_qp;
  483         unsigned long flags;
  484         int ret;
  485 
  486         for (; wr; wr = wr->next) {
  487                 struct ib_ud_wr cur_wr = *ud_wr(wr);
  488 
  489                 cur_wr.wr.next = NULL;
  490 
  491                 spin_lock_irqsave(&gsi->lock, flags);
  492                 tx_qp = get_tx_qp(gsi, &cur_wr);
  493                 if (!tx_qp) {
  494                         ret = mlx5_ib_gsi_silent_drop(gsi, &cur_wr);
  495                         if (ret)
  496                                 goto err;
  497                         spin_unlock_irqrestore(&gsi->lock, flags);
  498                         continue;
  499                 }
  500 
  501                 ret = mlx5_ib_add_outstanding_wr(gsi, &cur_wr, NULL);
  502                 if (ret)
  503                         goto err;
  504 
  505                 ret = ib_post_send(tx_qp, &cur_wr.wr, bad_wr);
  506                 if (ret) {
  507                         /* Undo the effect of adding the outstanding wr */
  508                         gsi->outstanding_pi = (gsi->outstanding_pi - 1) %
  509                                               gsi->cap.max_send_wr;
  510                         goto err;
  511                 }
  512                 spin_unlock_irqrestore(&gsi->lock, flags);
  513         }
  514 
  515         return 0;
  516 
  517 err:
  518         spin_unlock_irqrestore(&gsi->lock, flags);
  519         *bad_wr = wr;
  520         return ret;
  521 }
  522 
  523 int mlx5_ib_gsi_post_recv(struct ib_qp *qp, const struct ib_recv_wr *wr,
  524                           const struct ib_recv_wr **bad_wr)
  525 {
  526         struct mlx5_ib_gsi_qp *gsi = gsi_qp(qp);
  527 
  528         return ib_post_recv(gsi->rx_qp, wr, bad_wr);
  529 }
  530 
  531 void mlx5_ib_gsi_pkey_change(struct mlx5_ib_gsi_qp *gsi)
  532 {
  533         if (!gsi)
  534                 return;
  535 
  536         mutex_lock(&gsi->mutex);
  537         setup_qps(gsi);
  538         mutex_unlock(&gsi->mutex);
  539 }

Cache object: 0521682d4dec6d0eae12383153cb151f


[ 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.