net: replace NIPQUAD() in net/*/
[deliverable/linux.git] / net / sunrpc / xprtrdma / verbs.c
index 0f3b43148b7f441c1da47722a43ba6711d184192..78f7f728ef10733706326ce297a7335588295a23 100644 (file)
@@ -284,6 +284,7 @@ rpcrdma_conn_upcall(struct rdma_cm_id *id, struct rdma_cm_event *event)
        switch (event->event) {
        case RDMA_CM_EVENT_ADDR_RESOLVED:
        case RDMA_CM_EVENT_ROUTE_RESOLVED:
+               ia->ri_async_rc = 0;
                complete(&ia->ri_done);
                break;
        case RDMA_CM_EVENT_ADDR_ERROR:
@@ -322,12 +323,11 @@ rpcrdma_conn_upcall(struct rdma_cm_id *id, struct rdma_cm_event *event)
        case RDMA_CM_EVENT_DEVICE_REMOVAL:
                connstate = -ENODEV;
 connected:
-               dprintk("RPC:       %s: %s: %u.%u.%u.%u:%u"
-                       " (ep 0x%p event 0x%x)\n",
+               dprintk("RPC:       %s: %s: %pI4:%u (ep 0x%p event 0x%x)\n",
                        __func__,
                        (event->event <= 11) ? conn[event->event] :
                                                "unknown connection error",
-                       NIPQUAD(addr->sin_addr.s_addr),
+                       &addr->sin_addr.s_addr,
                        ntohs(addr->sin_port),
                        ep, event->event);
                atomic_set(&rpcx_to_rdmax(ep->rep_xprt)->rx_buf.rb_credits, 1);
@@ -338,13 +338,31 @@ connected:
                wake_up_all(&ep->rep_connect_wait);
                break;
        default:
-               ia->ri_async_rc = -EINVAL;
-               dprintk("RPC:       %s: unexpected CM event %X\n",
+               dprintk("RPC:       %s: unexpected CM event %d\n",
                        __func__, event->event);
-               complete(&ia->ri_done);
                break;
        }
 
+#ifdef RPC_DEBUG
+       if (connstate == 1) {
+               int ird = attr.max_dest_rd_atomic;
+               int tird = ep->rep_remote_cma.responder_resources;
+               printk(KERN_INFO "rpcrdma: connection to %pI4:%u "
+                       "on %s, memreg %d slots %d ird %d%s\n",
+                       &addr->sin_addr.s_addr,
+                       ntohs(addr->sin_port),
+                       ia->ri_id->device->name,
+                       ia->ri_memreg_strategy,
+                       xprt->rx_buf.rb_max_requests,
+                       ird, ird < 4 && ird < tird / 2 ? " (low!)" : "");
+       } else if (connstate < 0) {
+               printk(KERN_INFO "rpcrdma: connection to %pI4:%u closed (%d)\n",
+                       &addr->sin_addr.s_addr,
+                       ntohs(addr->sin_port),
+                       connstate);
+       }
+#endif
+
        return 0;
 }
 
@@ -355,6 +373,8 @@ rpcrdma_create_id(struct rpcrdma_xprt *xprt,
        struct rdma_cm_id *id;
        int rc;
 
+       init_completion(&ia->ri_done);
+
        id = rdma_create_id(rpcrdma_conn_upcall, xprt, RDMA_PS_TCP);
        if (IS_ERR(id)) {
                rc = PTR_ERR(id);
@@ -363,26 +383,28 @@ rpcrdma_create_id(struct rpcrdma_xprt *xprt,
                return id;
        }
 
-       ia->ri_async_rc = 0;
+       ia->ri_async_rc = -ETIMEDOUT;
        rc = rdma_resolve_addr(id, NULL, addr, RDMA_RESOLVE_TIMEOUT);
        if (rc) {
                dprintk("RPC:       %s: rdma_resolve_addr() failed %i\n",
                        __func__, rc);
                goto out;
        }
-       wait_for_completion(&ia->ri_done);
+       wait_for_completion_interruptible_timeout(&ia->ri_done,
+                               msecs_to_jiffies(RDMA_RESOLVE_TIMEOUT) + 1);
        rc = ia->ri_async_rc;
        if (rc)
                goto out;
 
-       ia->ri_async_rc = 0;
+       ia->ri_async_rc = -ETIMEDOUT;
        rc = rdma_resolve_route(id, RDMA_RESOLVE_TIMEOUT);
        if (rc) {
                dprintk("RPC:       %s: rdma_resolve_route() failed %i\n",
                        __func__, rc);
                goto out;
        }
-       wait_for_completion(&ia->ri_done);
+       wait_for_completion_interruptible_timeout(&ia->ri_done,
+                               msecs_to_jiffies(RDMA_RESOLVE_TIMEOUT) + 1);
        rc = ia->ri_async_rc;
        if (rc)
                goto out;
@@ -427,8 +449,6 @@ rpcrdma_ia_open(struct rpcrdma_xprt *xprt, struct sockaddr *addr, int memreg)
        struct ib_device_attr devattr;
        struct rpcrdma_ia *ia = &xprt->rx_ia;
 
-       init_completion(&ia->ri_done);
-
        ia->ri_id = rpcrdma_create_id(xprt, ia, addr);
        if (IS_ERR(ia->ri_id)) {
                rc = PTR_ERR(ia->ri_id);
@@ -485,6 +505,26 @@ rpcrdma_ia_open(struct rpcrdma_xprt *xprt, struct sockaddr *addr, int memreg)
                                "using slower RPCRDMA_REGISTER\n",
                                __func__);
                        memreg = RPCRDMA_REGISTER;
+#endif
+               }
+               break;
+       case RPCRDMA_FRMR:
+               /* Requires both frmr reg and local dma lkey */
+               if ((devattr.device_cap_flags &
+                    (IB_DEVICE_MEM_MGT_EXTENSIONS|IB_DEVICE_LOCAL_DMA_LKEY)) !=
+                   (IB_DEVICE_MEM_MGT_EXTENSIONS|IB_DEVICE_LOCAL_DMA_LKEY)) {
+#if RPCRDMA_PERSISTENT_REGISTRATION
+                       dprintk("RPC:       %s: FRMR registration "
+                               "specified but not supported by adapter, "
+                               "using riskier RPCRDMA_ALLPHYSICAL\n",
+                               __func__);
+                       memreg = RPCRDMA_ALLPHYSICAL;
+#else
+                       dprintk("RPC:       %s: FRMR registration "
+                               "specified but not supported by adapter, "
+                               "using slower RPCRDMA_REGISTER\n",
+                               __func__);
+                       memreg = RPCRDMA_REGISTER;
 #endif
                }
                break;
@@ -501,6 +541,7 @@ rpcrdma_ia_open(struct rpcrdma_xprt *xprt, struct sockaddr *addr, int memreg)
        switch (memreg) {
        case RPCRDMA_BOUNCEBUFFERS:
        case RPCRDMA_REGISTER:
+       case RPCRDMA_FRMR:
                break;
 #if RPCRDMA_PERSISTENT_REGISTRATION
        case RPCRDMA_ALLPHYSICAL:
@@ -544,6 +585,7 @@ rpcrdma_ia_open(struct rpcrdma_xprt *xprt, struct sockaddr *addr, int memreg)
        return 0;
 out2:
        rdma_destroy_id(ia->ri_id);
+       ia->ri_id = NULL;
 out1:
        return rc;
 }
@@ -564,15 +606,17 @@ rpcrdma_ia_close(struct rpcrdma_ia *ia)
                dprintk("RPC:       %s: ib_dereg_mr returned %i\n",
                        __func__, rc);
        }
-       if (ia->ri_id != NULL && !IS_ERR(ia->ri_id) && ia->ri_id->qp)
-               rdma_destroy_qp(ia->ri_id);
+       if (ia->ri_id != NULL && !IS_ERR(ia->ri_id)) {
+               if (ia->ri_id->qp)
+                       rdma_destroy_qp(ia->ri_id);
+               rdma_destroy_id(ia->ri_id);
+               ia->ri_id = NULL;
+       }
        if (ia->ri_pd != NULL && !IS_ERR(ia->ri_pd)) {
                rc = ib_dealloc_pd(ia->ri_pd);
                dprintk("RPC:       %s: ib_dealloc_pd returned %i\n",
                        __func__, rc);
        }
-       if (ia->ri_id != NULL && !IS_ERR(ia->ri_id))
-               rdma_destroy_id(ia->ri_id);
 }
 
 /*
@@ -602,6 +646,12 @@ rpcrdma_ep_create(struct rpcrdma_ep *ep, struct rpcrdma_ia *ia,
        ep->rep_attr.srq = NULL;
        ep->rep_attr.cap.max_send_wr = cdata->max_requests;
        switch (ia->ri_memreg_strategy) {
+       case RPCRDMA_FRMR:
+               /* Add room for frmr register and invalidate WRs */
+               ep->rep_attr.cap.max_send_wr *= 3;
+               if (ep->rep_attr.cap.max_send_wr > devattr.max_qp_wr)
+                       return -EINVAL;
+               break;
        case RPCRDMA_MEMWINDOWS_ASYNC:
        case RPCRDMA_MEMWINDOWS:
                /* Add room for mw_binds+unbinds - overkill! */
@@ -678,29 +728,13 @@ rpcrdma_ep_create(struct rpcrdma_ep *ep, struct rpcrdma_ia *ia,
        ep->rep_remote_cma.private_data_len = 0;
 
        /* Client offers RDMA Read but does not initiate */
-       switch (ia->ri_memreg_strategy) {
-       case RPCRDMA_BOUNCEBUFFERS:
+       ep->rep_remote_cma.initiator_depth = 0;
+       if (ia->ri_memreg_strategy == RPCRDMA_BOUNCEBUFFERS)
                ep->rep_remote_cma.responder_resources = 0;
-               break;
-       case RPCRDMA_MTHCAFMR:
-       case RPCRDMA_REGISTER:
-               ep->rep_remote_cma.responder_resources = cdata->max_requests *
-                               (RPCRDMA_MAX_DATA_SEGS / 8);
-               break;
-       case RPCRDMA_MEMWINDOWS:
-       case RPCRDMA_MEMWINDOWS_ASYNC:
-#if RPCRDMA_PERSISTENT_REGISTRATION
-       case RPCRDMA_ALLPHYSICAL:
-#endif
-               ep->rep_remote_cma.responder_resources = cdata->max_requests *
-                               (RPCRDMA_MAX_DATA_SEGS / 2);
-               break;
-       default:
-               break;
-       }
-       if (ep->rep_remote_cma.responder_resources > devattr.max_qp_rd_atom)
+       else if (devattr.max_qp_rd_atom > 32)   /* arbitrary but <= 255 */
+               ep->rep_remote_cma.responder_resources = 32;
+       else
                ep->rep_remote_cma.responder_resources = devattr.max_qp_rd_atom;
-       ep->rep_remote_cma.initiator_depth = 0;
 
        ep->rep_remote_cma.retry_count = 7;
        ep->rep_remote_cma.flow_control = 0;
@@ -740,21 +774,16 @@ rpcrdma_ep_destroy(struct rpcrdma_ep *ep, struct rpcrdma_ia *ia)
                if (rc)
                        dprintk("RPC:       %s: rpcrdma_ep_disconnect"
                                " returned %i\n", __func__, rc);
+               rdma_destroy_qp(ia->ri_id);
+               ia->ri_id->qp = NULL;
        }
 
-       ep->rep_func = NULL;
-
        /* padding - could be done in rpcrdma_buffer_destroy... */
        if (ep->rep_pad_mr) {
                rpcrdma_deregister_internal(ia, ep->rep_pad_mr, &ep->rep_pad);
                ep->rep_pad_mr = NULL;
        }
 
-       if (ia->ri_id->qp) {
-               rdma_destroy_qp(ia->ri_id);
-               ia->ri_id->qp = NULL;
-       }
-
        rpcrdma_clean_cq(ep->rep_cq);
        rc = ib_destroy_cq(ep->rep_cq);
        if (rc)
@@ -773,9 +802,8 @@ rpcrdma_ep_connect(struct rpcrdma_ep *ep, struct rpcrdma_ia *ia)
        struct rdma_cm_id *id;
        int rc = 0;
        int retry_count = 0;
-       int reconnect = (ep->rep_connected != 0);
 
-       if (reconnect) {
+       if (ep->rep_connected != 0) {
                struct rpcrdma_xprt *xprt;
 retry:
                rc = rpcrdma_ep_disconnect(ep, ia);
@@ -806,6 +834,7 @@ retry:
                        goto out;
                }
                /* END TEMP */
+               rdma_destroy_qp(ia->ri_id);
                rdma_destroy_id(ia->ri_id);
                ia->ri_id = id;
        }
@@ -830,14 +859,6 @@ if (strnicmp(ia->ri_id->device->dma_device->bus->name, "pci", 3) == 0) {
        }
 }
 
-       /* Theoretically a client initiator_depth > 0 is not needed,
-        * but many peers fail to complete the connection unless they
-        * == responder_resources! */
-       if (ep->rep_remote_cma.initiator_depth !=
-                               ep->rep_remote_cma.responder_resources)
-               ep->rep_remote_cma.initiator_depth =
-                       ep->rep_remote_cma.responder_resources;
-
        ep->rep_connected = 0;
 
        rc = rdma_connect(ia->ri_id, &ep->rep_remote_cma);
@@ -847,9 +868,6 @@ if (strnicmp(ia->ri_id->device->dma_device->bus->name, "pci", 3) == 0) {
                goto out;
        }
 
-       if (reconnect)
-               return 0;
-
        wait_event_interruptible(ep->rep_connect_wait, ep->rep_connected != 0);
 
        /*
@@ -866,14 +884,16 @@ if (strnicmp(ia->ri_id->device->dma_device->bus->name, "pci", 3) == 0) {
        if (ep->rep_connected <= 0) {
                /* Sometimes, the only way to reliably connect to remote
                 * CMs is to use same nonzero values for ORD and IRD. */
-               ep->rep_remote_cma.initiator_depth =
-                                       ep->rep_remote_cma.responder_resources;
-               if (ep->rep_remote_cma.initiator_depth == 0)
-                       ++ep->rep_remote_cma.initiator_depth;
-               if (ep->rep_remote_cma.responder_resources == 0)
-                       ++ep->rep_remote_cma.responder_resources;
-               if (retry_count++ == 0)
+               if (retry_count++ <= RDMA_CONNECT_RETRY_MAX + 1 &&
+                   (ep->rep_remote_cma.responder_resources == 0 ||
+                    ep->rep_remote_cma.initiator_depth !=
+                               ep->rep_remote_cma.responder_resources)) {
+                       if (ep->rep_remote_cma.responder_resources == 0)
+                               ep->rep_remote_cma.responder_resources = 1;
+                       ep->rep_remote_cma.initiator_depth =
+                               ep->rep_remote_cma.responder_resources;
                        goto retry;
+               }
                rc = ep->rep_connected;
        } else {
                dprintk("RPC:       %s: connected\n", __func__);
@@ -935,7 +955,7 @@ rpcrdma_buffer_create(struct rpcrdma_buffer *buf, struct rpcrdma_ep *ep,
         *   2.  arrays of struct rpcrdma_req to fill in pointers
         *   3.  array of struct rpcrdma_rep for replies
         *   4.  padding, if any
-        *   5.  mw's or fmr's, if any
+        *   5.  mw's, fmr's or frmr's, if any
         * Send/recv buffers in req/rep need to be registered
         */
 
@@ -943,6 +963,10 @@ rpcrdma_buffer_create(struct rpcrdma_buffer *buf, struct rpcrdma_ep *ep,
                (sizeof(struct rpcrdma_req *) + sizeof(struct rpcrdma_rep *));
        len += cdata->padding;
        switch (ia->ri_memreg_strategy) {
+       case RPCRDMA_FRMR:
+               len += buf->rb_max_requests * RPCRDMA_MAX_SEGS *
+                               sizeof(struct rpcrdma_mw);
+               break;
        case RPCRDMA_MTHCAFMR:
                /* TBD we are perhaps overallocating here */
                len += (buf->rb_max_requests + 1) * RPCRDMA_MAX_SEGS *
@@ -991,6 +1015,30 @@ rpcrdma_buffer_create(struct rpcrdma_buffer *buf, struct rpcrdma_ep *ep,
        INIT_LIST_HEAD(&buf->rb_mws);
        r = (struct rpcrdma_mw *)p;
        switch (ia->ri_memreg_strategy) {
+       case RPCRDMA_FRMR:
+               for (i = buf->rb_max_requests * RPCRDMA_MAX_SEGS; i; i--) {
+                       r->r.frmr.fr_mr = ib_alloc_fast_reg_mr(ia->ri_pd,
+                                                        RPCRDMA_MAX_SEGS);
+                       if (IS_ERR(r->r.frmr.fr_mr)) {
+                               rc = PTR_ERR(r->r.frmr.fr_mr);
+                               dprintk("RPC:       %s: ib_alloc_fast_reg_mr"
+                                       " failed %i\n", __func__, rc);
+                               goto out;
+                       }
+                       r->r.frmr.fr_pgl =
+                               ib_alloc_fast_reg_page_list(ia->ri_id->device,
+                                                           RPCRDMA_MAX_SEGS);
+                       if (IS_ERR(r->r.frmr.fr_pgl)) {
+                               rc = PTR_ERR(r->r.frmr.fr_pgl);
+                               dprintk("RPC:       %s: "
+                                       "ib_alloc_fast_reg_page_list "
+                                       "failed %i\n", __func__, rc);
+                               goto out;
+                       }
+                       list_add(&r->mw_list, &buf->rb_mws);
+                       ++r;
+               }
+               break;
        case RPCRDMA_MTHCAFMR:
                /* TBD we are perhaps overallocating here */
                for (i = (buf->rb_max_requests+1) * RPCRDMA_MAX_SEGS; i; i--) {
@@ -1126,6 +1174,15 @@ rpcrdma_buffer_destroy(struct rpcrdma_buffer *buf)
                                        struct rpcrdma_mw, mw_list);
                                list_del(&r->mw_list);
                                switch (ia->ri_memreg_strategy) {
+                               case RPCRDMA_FRMR:
+                                       rc = ib_dereg_mr(r->r.frmr.fr_mr);
+                                       if (rc)
+                                               dprintk("RPC:       %s:"
+                                                       " ib_dereg_mr"
+                                                       " failed %i\n",
+                                                       __func__, rc);
+                                       ib_free_fast_reg_page_list(r->r.frmr.fr_pgl);
+                                       break;
                                case RPCRDMA_MTHCAFMR:
                                        rc = ib_dealloc_fmr(r->r.fmr);
                                        if (rc)
@@ -1228,6 +1285,7 @@ rpcrdma_buffer_put(struct rpcrdma_req *req)
                req->rl_reply = NULL;
        }
        switch (ia->ri_memreg_strategy) {
+       case RPCRDMA_FRMR:
        case RPCRDMA_MTHCAFMR:
        case RPCRDMA_MEMWINDOWS_ASYNC:
        case RPCRDMA_MEMWINDOWS:
@@ -1390,6 +1448,96 @@ rpcrdma_unmap_one(struct rpcrdma_ia *ia, struct rpcrdma_mr_seg *seg)
                                seg->mr_dma, seg->mr_dmalen, seg->mr_dir);
 }
 
+static int
+rpcrdma_register_frmr_external(struct rpcrdma_mr_seg *seg,
+                       int *nsegs, int writing, struct rpcrdma_ia *ia,
+                       struct rpcrdma_xprt *r_xprt)
+{
+       struct rpcrdma_mr_seg *seg1 = seg;
+       struct ib_send_wr frmr_wr, *bad_wr;
+       u8 key;
+       int len, pageoff;
+       int i, rc;
+
+       pageoff = offset_in_page(seg1->mr_offset);
+       seg1->mr_offset -= pageoff;     /* start of page */
+       seg1->mr_len += pageoff;
+       len = -pageoff;
+       if (*nsegs > RPCRDMA_MAX_DATA_SEGS)
+               *nsegs = RPCRDMA_MAX_DATA_SEGS;
+       for (i = 0; i < *nsegs;) {
+               rpcrdma_map_one(ia, seg, writing);
+               seg1->mr_chunk.rl_mw->r.frmr.fr_pgl->page_list[i] = seg->mr_dma;
+               len += seg->mr_len;
+               ++seg;
+               ++i;
+               /* Check for holes */
+               if ((i < *nsegs && offset_in_page(seg->mr_offset)) ||
+                   offset_in_page((seg-1)->mr_offset + (seg-1)->mr_len))
+                       break;
+       }
+       dprintk("RPC:       %s: Using frmr %p to map %d segments\n",
+               __func__, seg1->mr_chunk.rl_mw, i);
+
+       /* Bump the key */
+       key = (u8)(seg1->mr_chunk.rl_mw->r.frmr.fr_mr->rkey & 0x000000FF);
+       ib_update_fast_reg_key(seg1->mr_chunk.rl_mw->r.frmr.fr_mr, ++key);
+
+       /* Prepare FRMR WR */
+       memset(&frmr_wr, 0, sizeof frmr_wr);
+       frmr_wr.opcode = IB_WR_FAST_REG_MR;
+       frmr_wr.send_flags = 0;                 /* unsignaled */
+       frmr_wr.wr.fast_reg.iova_start = (unsigned long)seg1->mr_dma;
+       frmr_wr.wr.fast_reg.page_list = seg1->mr_chunk.rl_mw->r.frmr.fr_pgl;
+       frmr_wr.wr.fast_reg.page_list_len = i;
+       frmr_wr.wr.fast_reg.page_shift = PAGE_SHIFT;
+       frmr_wr.wr.fast_reg.length = i << PAGE_SHIFT;
+       frmr_wr.wr.fast_reg.access_flags = (writing ?
+                               IB_ACCESS_REMOTE_WRITE : IB_ACCESS_REMOTE_READ);
+       frmr_wr.wr.fast_reg.rkey = seg1->mr_chunk.rl_mw->r.frmr.fr_mr->rkey;
+       DECR_CQCOUNT(&r_xprt->rx_ep);
+
+       rc = ib_post_send(ia->ri_id->qp, &frmr_wr, &bad_wr);
+
+       if (rc) {
+               dprintk("RPC:       %s: failed ib_post_send for register,"
+                       " status %i\n", __func__, rc);
+               while (i--)
+                       rpcrdma_unmap_one(ia, --seg);
+       } else {
+               seg1->mr_rkey = seg1->mr_chunk.rl_mw->r.frmr.fr_mr->rkey;
+               seg1->mr_base = seg1->mr_dma + pageoff;
+               seg1->mr_nsegs = i;
+               seg1->mr_len = len;
+       }
+       *nsegs = i;
+       return rc;
+}
+
+static int
+rpcrdma_deregister_frmr_external(struct rpcrdma_mr_seg *seg,
+                       struct rpcrdma_ia *ia, struct rpcrdma_xprt *r_xprt)
+{
+       struct rpcrdma_mr_seg *seg1 = seg;
+       struct ib_send_wr invalidate_wr, *bad_wr;
+       int rc;
+
+       while (seg1->mr_nsegs--)
+               rpcrdma_unmap_one(ia, seg++);
+
+       memset(&invalidate_wr, 0, sizeof invalidate_wr);
+       invalidate_wr.opcode = IB_WR_LOCAL_INV;
+       invalidate_wr.send_flags = 0;                   /* unsignaled */
+       invalidate_wr.ex.invalidate_rkey = seg1->mr_chunk.rl_mw->r.frmr.fr_mr->rkey;
+       DECR_CQCOUNT(&r_xprt->rx_ep);
+
+       rc = ib_post_send(ia->ri_id->qp, &invalidate_wr, &bad_wr);
+       if (rc)
+               dprintk("RPC:       %s: failed ib_post_send for invalidate,"
+                       " status %i\n", __func__, rc);
+       return rc;
+}
+
 static int
 rpcrdma_register_fmr_external(struct rpcrdma_mr_seg *seg,
                        int *nsegs, int writing, struct rpcrdma_ia *ia)
@@ -1600,6 +1748,11 @@ rpcrdma_register_external(struct rpcrdma_mr_seg *seg,
                break;
 #endif
 
+       /* Registration using frmr registration */
+       case RPCRDMA_FRMR:
+               rc = rpcrdma_register_frmr_external(seg, &nsegs, writing, ia, r_xprt);
+               break;
+
        /* Registration using fmr memory registration */
        case RPCRDMA_MTHCAFMR:
                rc = rpcrdma_register_fmr_external(seg, &nsegs, writing, ia);
@@ -1639,6 +1792,10 @@ rpcrdma_deregister_external(struct rpcrdma_mr_seg *seg,
                break;
 #endif
 
+       case RPCRDMA_FRMR:
+               rc = rpcrdma_deregister_frmr_external(seg, ia, r_xprt);
+               break;
+
        case RPCRDMA_MTHCAFMR:
                rc = rpcrdma_deregister_fmr_external(seg, ia);
                break;
This page took 0.112732 seconds and 5 git commands to generate.