X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=drivers%2Fnet%2Fmlx5%2Fmlx5_mr.c;h=c3410a62947ef275dbcee4fb27287635ab5ff944;hb=597cb1874068054d4c0be41f161a72ef37888930;hp=a50c520880372e58b2ceaac3352d0174969faed3;hpb=6e7cbd63706f3435b9d9a2057a37db1da01db9a7;p=deb_dpdk.git diff --git a/drivers/net/mlx5/mlx5_mr.c b/drivers/net/mlx5/mlx5_mr.c index a50c5208..c3410a62 100644 --- a/drivers/net/mlx5/mlx5_mr.c +++ b/drivers/net/mlx5/mlx5_mr.c @@ -47,355 +47,398 @@ #include "mlx5.h" #include "mlx5_rxtx.h" -struct mlx5_check_mempool_data { - int ret; - char *start; - char *end; +struct mr_update_mempool_data { + struct rte_eth_dev *dev; + struct mlx5_mr_cache *lkp_tbl; + uint16_t tbl_sz; }; -/* Called by mlx5_check_mempool() when iterating the memory chunks. */ -static void -mlx5_check_mempool_cb(struct rte_mempool *mp __rte_unused, - void *opaque, struct rte_mempool_memhdr *memhdr, - unsigned int mem_idx __rte_unused) +/** + * Look up LKEY from given lookup table by Binary Search, store the last index + * and return searched LKEY. + * + * @param lkp_tbl + * Pointer to lookup table. + * @param n + * Size of lookup table. + * @param[out] idx + * Pointer to index. Even on searh failure, returns index where it stops + * searching so that index can be used when inserting a new entry. + * @param addr + * Search key. + * + * @return + * Searched LKEY on success, UINT32_MAX on no match. + */ +static uint32_t +mlx5_mr_lookup(struct mlx5_mr_cache *lkp_tbl, uint16_t n, uint16_t *idx, + uintptr_t addr) { - struct mlx5_check_mempool_data *data = opaque; + uint16_t base = 0; - /* It already failed, skip the next chunks. */ - if (data->ret != 0) - return; - /* It is the first chunk. */ - if (data->start == NULL && data->end == NULL) { - data->start = memhdr->addr; - data->end = data->start + memhdr->len; - return; - } - if (data->end == memhdr->addr) { - data->end += memhdr->len; - return; - } - if (data->start == (char *)memhdr->addr + memhdr->len) { - data->start -= memhdr->len; - return; - } - /* Error, mempool is not virtually contiguous. */ - data->ret = -1; + /* First entry must be NULL for comparison. */ + assert(n == 0 || (lkp_tbl[0].start == 0 && + lkp_tbl[0].lkey == UINT32_MAX)); + /* Binary search. */ + do { + register uint16_t delta = n >> 1; + + if (addr < lkp_tbl[base + delta].start) { + n = delta; + } else { + base += delta; + n -= delta; + } + } while (n > 1); + assert(addr >= lkp_tbl[base].start); + *idx = base; + if (addr < lkp_tbl[base].end) + return lkp_tbl[base].lkey; + /* Not found. */ + return UINT32_MAX; } /** - * Check if a mempool can be used: it must be virtually contiguous. + * Insert an entry to LKEY lookup table. * - * @param[in] mp - * Pointer to memory pool. - * @param[out] start - * Pointer to the start address of the mempool virtual memory area - * @param[out] end - * Pointer to the end address of the mempool virtual memory area + * @param lkp_tbl + * Pointer to lookup table. The size of array must be enough to add one more + * entry. + * @param n + * Size of lookup table. + * @param entry + * Pointer to new entry to insert. * * @return - * 0 on success (mempool is virtually contiguous), -1 on error. + * Size of returning lookup table. */ static int -mlx5_check_mempool(struct rte_mempool *mp, uintptr_t *start, - uintptr_t *end) +mlx5_mr_insert(struct mlx5_mr_cache *lkp_tbl, uint16_t n, + struct mlx5_mr_cache *entry) { - struct mlx5_check_mempool_data data; + uint16_t idx = 0; + size_t shift; - memset(&data, 0, sizeof(data)); - rte_mempool_mem_iter(mp, mlx5_check_mempool_cb, &data); - *start = (uintptr_t)data.start; - *end = (uintptr_t)data.end; - return data.ret; + /* Check if entry exist. */ + if (mlx5_mr_lookup(lkp_tbl, n, &idx, entry->start) != UINT32_MAX) + return n; + /* Insert entry. */ + ++idx; + shift = (n - idx) * sizeof(struct mlx5_mr_cache); + if (shift) + memmove(&lkp_tbl[idx + 1], &lkp_tbl[idx], shift); + lkp_tbl[idx] = *entry; + DRV_LOG(DEBUG, "%p: inserted lkp_tbl[%u], start = 0x%lx, end = 0x%lx", + (void *)lkp_tbl, idx, lkp_tbl[idx].start, lkp_tbl[idx].end); + return n + 1; } /** - * Register a Memory Region (MR) <-> Memory Pool (MP) association in - * txq->mp2mr[]. If mp2mr[] is full, remove an entry first. + * Incrementally update LKEY lookup table for a specific address from registered + * Memory Regions. * - * @param txq - * Pointer to TX queue structure. - * @param[in] mp - * Memory Pool for which a Memory Region lkey must be returned. - * @param idx - * Index of the next available entry. + * @param dev + * Pointer to Ethernet device structure. + * @param lkp_tbl + * Pointer to lookup table to fill. The size of array must be at least + * (priv->mr_n + 1). + * @param n + * Size of lookup table. + * @param addr + * Search key. * * @return - * mr on success, NULL on failure and rte_errno is set. + * Size of returning lookup table. */ -struct mlx5_mr * -mlx5_txq_mp2mr_reg(struct mlx5_txq_data *txq, struct rte_mempool *mp, - unsigned int idx) +static int +mlx5_mr_update_addr(struct rte_eth_dev *dev, struct mlx5_mr_cache *lkp_tbl, + uint16_t n, uintptr_t addr) { - struct mlx5_txq_ctrl *txq_ctrl = - container_of(txq, struct mlx5_txq_ctrl, txq); - struct rte_eth_dev *dev; - struct mlx5_mr *mr; + struct priv *priv = dev->data->dev_private; + uint16_t idx; + uint32_t ret __rte_unused; - rte_spinlock_lock(&txq_ctrl->priv->mr_lock); - /* Add a new entry, register MR first. */ - DRV_LOG(DEBUG, "port %u discovered new memory pool \"%s\" (%p)", - PORT_ID(txq_ctrl->priv), mp->name, (void *)mp); - dev = ETH_DEV(txq_ctrl->priv); - mr = mlx5_mr_get(dev, mp); - if (mr == NULL) { - if (rte_eal_process_type() != RTE_PROC_PRIMARY) { - DRV_LOG(DEBUG, - "port %u using unregistered mempool 0x%p(%s)" - " in secondary process, please create mempool" - " before rte_eth_dev_start()", - PORT_ID(txq_ctrl->priv), (void *)mp, mp->name); - rte_spinlock_unlock(&txq_ctrl->priv->mr_lock); - rte_errno = ENOTSUP; - return NULL; - } - mr = mlx5_mr_new(dev, mp); - } - if (unlikely(mr == NULL)) { - DRV_LOG(DEBUG, - "port %u unable to configure memory region," - " ibv_reg_mr() failed.", - PORT_ID(txq_ctrl->priv)); - rte_spinlock_unlock(&txq_ctrl->priv->mr_lock); - return NULL; + if (n == 0) { + /* First entry must be NULL for comparison. */ + lkp_tbl[n++] = (struct mlx5_mr_cache) { + .lkey = UINT32_MAX, + }; } - if (unlikely(idx == RTE_DIM(txq->mp2mr))) { - /* Table is full, remove oldest entry. */ - DRV_LOG(DEBUG, - "port %u memory region <-> memory pool table full, " - " dropping oldest entry", - PORT_ID(txq_ctrl->priv)); - --idx; - mlx5_mr_release(txq->mp2mr[0]); - memmove(&txq->mp2mr[0], &txq->mp2mr[1], - (sizeof(txq->mp2mr) - sizeof(txq->mp2mr[0]))); - } - /* Store the new entry. */ - txq_ctrl->txq.mp2mr[idx] = mr; - DRV_LOG(DEBUG, - "port %u new memory region lkey for MP \"%s\" (%p): 0x%08" - PRIu32, - PORT_ID(txq_ctrl->priv), mp->name, (void *)mp, - txq_ctrl->txq.mp2mr[idx]->lkey); - rte_spinlock_unlock(&txq_ctrl->priv->mr_lock); - return mr; + ret = mlx5_mr_lookup(*priv->mr_cache, MR_TABLE_SZ(priv->mr_n), + &idx, addr); + /* Lookup must succeed, the global cache is all-inclusive. */ + assert(ret != UINT32_MAX); + DRV_LOG(DEBUG, "port %u adding LKEY (0x%x) for addr 0x%lx", + dev->data->port_id, (*priv->mr_cache)[idx].lkey, addr); + return mlx5_mr_insert(lkp_tbl, n, &(*priv->mr_cache)[idx]); } -struct mlx5_mp2mr_mbuf_check_data { - int ret; -}; - /** - * Callback function for rte_mempool_obj_iter() to check whether a given - * mempool object looks like a mbuf. + * Bottom-half of LKEY search on datapath. Firstly search in cache_bh[] and if + * misses, search in the global MR cache table and update the new entry to + * per-queue local caches. * - * @param[in] mp - * The mempool pointer - * @param[in] arg - * Context data (struct txq_mp2mr_mbuf_check_data). Contains the - * return value. - * @param[in] obj - * Object address. - * @param index - * Object index, unused. + * @param dev + * Pointer to Ethernet device structure. + * @param mr_ctrl + * Pointer to per-queue MR control structure. + * @param addr + * Search key. + * + * @return + * LKEY on success. */ -static void -txq_mp2mr_mbuf_check(struct rte_mempool *mp, void *arg, void *obj, - uint32_t index __rte_unused) +static inline uint32_t +mlx5_mr_mb2mr_bh(struct rte_eth_dev *dev, struct mlx5_mr_ctrl *mr_ctrl, + uintptr_t addr) { - struct mlx5_mp2mr_mbuf_check_data *data = arg; - struct rte_mbuf *buf = obj; + uint32_t lkey; + uint16_t bh_idx = 0; + struct mlx5_mr_cache *mr_cache = &mr_ctrl->cache[mr_ctrl->head]; - /* - * Check whether mbuf structure fits element size and whether mempool - * pointer is valid. - */ - if (sizeof(*buf) > mp->elt_size || buf->pool != mp) - data->ret = -1; + /* Binary-search MR translation table. */ + lkey = mlx5_mr_lookup(*mr_ctrl->cache_bh, mr_ctrl->bh_n, &bh_idx, addr); + if (likely(lkey != UINT32_MAX)) { + /* Update cache. */ + *mr_cache = (*mr_ctrl->cache_bh)[bh_idx]; + mr_ctrl->mru = mr_ctrl->head; + /* Point to the next victim, the oldest. */ + mr_ctrl->head = (mr_ctrl->head + 1) % MLX5_MR_CACHE_N; + return lkey; + } + /* Missed in the per-queue lookup table. Search in the global cache. */ + mr_ctrl->bh_n = mlx5_mr_update_addr(dev, *mr_ctrl->cache_bh, + mr_ctrl->bh_n, addr); + /* Search again with updated entries. */ + lkey = mlx5_mr_lookup(*mr_ctrl->cache_bh, mr_ctrl->bh_n, &bh_idx, addr); + /* Must always succeed. */ + assert(lkey != UINT32_MAX); + /* Update cache. */ + *mr_cache = (*mr_ctrl->cache_bh)[bh_idx]; + mr_ctrl->mru = mr_ctrl->head; + /* Point to the next victim, the oldest. */ + mr_ctrl->head = (mr_ctrl->head + 1) % MLX5_MR_CACHE_N; + return lkey; } /** - * Iterator function for rte_mempool_walk() to register existing mempools and - * fill the MP to MR cache of a TX queue. + * Bottom-half of mlx5_rx_mb2mr() if search on mr_cache_bh[] fails. * - * @param[in] mp - * Memory Pool to register. - * @param *arg - * Pointer to TX queue structure. + * @param rxq + * Pointer to Rx queue structure. + * @param addr + * Search key. + * + * @return + * LKEY on success. */ -void -mlx5_mp2mr_iter(struct rte_mempool *mp, void *arg) +uint32_t +mlx5_rx_mb2mr_bh(struct mlx5_rxq_data *rxq, uintptr_t addr) { - struct priv *priv = (struct priv *)arg; - struct mlx5_mp2mr_mbuf_check_data data = { - .ret = 0, - }; - struct mlx5_mr *mr; + struct mlx5_rxq_ctrl *rxq_ctrl = + container_of(rxq, struct mlx5_rxq_ctrl, rxq); - /* Register mempool only if the first element looks like a mbuf. */ - if (rte_mempool_obj_iter(mp, txq_mp2mr_mbuf_check, &data) == 0 || - data.ret == -1) - return; - mr = mlx5_mr_get(ETH_DEV(priv), mp); - if (mr) { - mlx5_mr_release(mr); - return; - } - mr = mlx5_mr_new(ETH_DEV(priv), mp); - if (!mr) - DRV_LOG(ERR, "port %u cannot create memory region: %s", - PORT_ID(priv), strerror(rte_errno)); + DRV_LOG(DEBUG, + "port %u not found in rxq->mr_cache[], last-hit=%u, head=%u", + PORT_ID(rxq_ctrl->priv), rxq->mr_ctrl.mru, rxq->mr_ctrl.head); + return mlx5_mr_mb2mr_bh(ETH_DEV(rxq_ctrl->priv), &rxq->mr_ctrl, addr); } /** - * Register a new memory region from the mempool and store it in the memory - * region list. + * Bottom-half of mlx5_tx_mb2mr() if search on cache_bh[] fails. * - * @param dev - * Pointer to Ethernet device. - * @param mp - * Pointer to the memory pool to register. + * @param txq + * Pointer to Tx queue structure. + * @param addr + * Search key. * * @return - * The memory region on success, NULL on failure and rte_errno is set. + * LKEY on success. */ -struct mlx5_mr * -mlx5_mr_new(struct rte_eth_dev *dev, struct rte_mempool *mp) +uint32_t +mlx5_tx_mb2mr_bh(struct mlx5_txq_data *txq, uintptr_t addr) { - struct priv *priv = dev->data->dev_private; - const struct rte_memseg *ms = rte_eal_get_physmem_layout(); - uintptr_t start; - uintptr_t end; - unsigned int i; - struct mlx5_mr *mr; - - mr = rte_zmalloc_socket(__func__, sizeof(*mr), 0, mp->socket_id); - if (!mr) { - DRV_LOG(DEBUG, - "port %u unable to configure memory region," - " ibv_reg_mr() failed.", - dev->data->port_id); - rte_errno = ENOMEM; - return NULL; - } - if (mlx5_check_mempool(mp, &start, &end) != 0) { - DRV_LOG(ERR, "port %u mempool %p: not virtually contiguous", - dev->data->port_id, (void *)mp); - rte_errno = ENOMEM; - return NULL; - } - DRV_LOG(DEBUG, "port %u mempool %p area start=%p end=%p size=%zu", - dev->data->port_id, (void *)mp, (void *)start, (void *)end, - (size_t)(end - start)); - /* Save original addresses for exact MR lookup. */ - mr->start = start; - mr->end = end; - /* Round start and end to page boundary if found in memory segments. */ - for (i = 0; (i < RTE_MAX_MEMSEG) && (ms[i].addr != NULL); ++i) { - uintptr_t addr = (uintptr_t)ms[i].addr; - size_t len = ms[i].len; - unsigned int align = ms[i].hugepage_sz; + struct mlx5_txq_ctrl *txq_ctrl = + container_of(txq, struct mlx5_txq_ctrl, txq); - if ((start > addr) && (start < addr + len)) - start = RTE_ALIGN_FLOOR(start, align); - if ((end > addr) && (end < addr + len)) - end = RTE_ALIGN_CEIL(end, align); - } DRV_LOG(DEBUG, - "port %u mempool %p using start=%p end=%p size=%zu for memory" - " region", - dev->data->port_id, (void *)mp, (void *)start, (void *)end, - (size_t)(end - start)); - mr->mr = ibv_reg_mr(priv->pd, (void *)start, end - start, - IBV_ACCESS_LOCAL_WRITE); - if (!mr->mr) { - rte_errno = ENOMEM; - return NULL; - } - mr->mp = mp; - mr->lkey = rte_cpu_to_be_32(mr->mr->lkey); - rte_atomic32_inc(&mr->refcnt); - DRV_LOG(DEBUG, "port %u new memory Region %p refcnt: %d", - dev->data->port_id, (void *)mr, rte_atomic32_read(&mr->refcnt)); - LIST_INSERT_HEAD(&priv->mr, mr, next); - return mr; + "port %u not found in txq->mr_cache[], last-hit=%u, head=%u", + PORT_ID(txq_ctrl->priv), txq->mr_ctrl.mru, txq->mr_ctrl.head); + return mlx5_mr_mb2mr_bh(ETH_DEV(txq_ctrl->priv), &txq->mr_ctrl, addr); +} + +/* Called by mr_update_mempool() when iterating the memory chunks. */ +static void +mr_update_mempool_cb(struct rte_mempool *mp __rte_unused, + void *opaque, struct rte_mempool_memhdr *memhdr, + unsigned int mem_idx __rte_unused) +{ + struct mr_update_mempool_data *data = opaque; + + DRV_LOG(DEBUG, "port %u adding chunk[%u] of %s", + data->dev->data->port_id, mem_idx, mp->name); + data->tbl_sz = + mlx5_mr_update_addr(data->dev, data->lkp_tbl, data->tbl_sz, + (uintptr_t)memhdr->addr); } /** - * Search the memory region object in the memory region list. + * Incrementally update LKEY lookup table for a specific Memory Pool from + * registered Memory Regions. * * @param dev * Pointer to Ethernet device. - * @param mp - * Pointer to the memory pool to register. + * @param[out] lkp_tbl + * Pointer to lookup table to fill. The size of array must be at least + * (priv->static_mr_n + 1). + * @param n + * Size of lookup table. + * @param[in] mp + * Pointer to Memory Pool. * * @return - * The memory region on success. + * Size of returning lookup table. */ -struct mlx5_mr * -mlx5_mr_get(struct rte_eth_dev *dev, struct rte_mempool *mp) +int +mlx5_mr_update_mp(struct rte_eth_dev *dev, struct mlx5_mr_cache *lkp_tbl, + uint16_t n, struct rte_mempool *mp) { - struct priv *priv = dev->data->dev_private; - struct mlx5_mr *mr; + struct mr_update_mempool_data data = { + .dev = dev, + .lkp_tbl = lkp_tbl, + .tbl_sz = n + }; - assert(mp); - if (LIST_EMPTY(&priv->mr)) - return NULL; - LIST_FOREACH(mr, &priv->mr, next) { - if (mr->mp == mp) { - rte_atomic32_inc(&mr->refcnt); - DRV_LOG(DEBUG, "port %u memory region %p refcnt: %d", - dev->data->port_id, (void *)mr, - rte_atomic32_read(&mr->refcnt)); - return mr; - } - } - return NULL; + rte_mempool_mem_iter(mp, mr_update_mempool_cb, &data); + return data.tbl_sz; +} + +/* Called by qsort() to compare MR entries. */ +static int +mr_comp_addr(const void *m1, const void *m2) +{ + const struct mlx5_mr *mi1 = m1; + const struct mlx5_mr *mi2 = m2; + + if (mi1->memseg->addr < mi2->memseg->addr) + return -1; + else if (mi1->memseg->addr > mi2->memseg->addr) + return 1; + else + return 0; } /** - * Release the memory region object. + * Register entire physical memory to Verbs. * - * @param mr - * Pointer to memory region to release. + * @param dev + * Pointer to Ethernet device. * * @return - * 1 while a reference on it exists, 0 when freed. + * 0 on success, a negative errno value otherwise and rte_errno is set. */ int -mlx5_mr_release(struct mlx5_mr *mr) +mlx5_mr_register_memseg(struct rte_eth_dev *dev) { - assert(mr); - DRV_LOG(DEBUG, "memory region %p refcnt: %d", (void *)mr, - rte_atomic32_read(&mr->refcnt)); - if (rte_atomic32_dec_and_test(&mr->refcnt)) { - claim_zero(ibv_dereg_mr(mr->mr)); - LIST_REMOVE(mr, next); - rte_free(mr); + struct priv *priv = dev->data->dev_private; + const struct rte_memseg *ms = rte_eal_get_physmem_layout(); + struct mlx5_mr *mr; + struct mlx5_mr_cache *mr_cache; + unsigned int i; + + if (priv->mr_n != 0) return 0; + /* Count the existing memsegs in the system. */ + for (i = 0; (i < RTE_MAX_MEMSEG) && (ms[i].addr != NULL); ++i) + ++priv->mr_n; + priv->mr = rte_calloc(__func__, priv->mr_n, sizeof(*mr), 0); + if (priv->mr == NULL) { + DRV_LOG(ERR, + "port %u cannot allocate memory for array of static MR", + dev->data->port_id); + rte_errno = ENOMEM; + return -rte_errno; + } + priv->mr_cache = rte_calloc(__func__, MR_TABLE_SZ(priv->mr_n), + sizeof(*mr_cache), 0); + if (priv->mr_cache == NULL) { + DRV_LOG(ERR, + "port %u cannot allocate memory for array of MR cache", + dev->data->port_id); + rte_free(priv->mr); + rte_errno = ENOMEM; + return -rte_errno; } - return 1; + for (i = 0; i < priv->mr_n; ++i) { + mr = &(*priv->mr)[i]; + mr->memseg = &ms[i]; + mr->ibv_mr = ibv_reg_mr(priv->pd, + mr->memseg->addr, mr->memseg->len, + IBV_ACCESS_LOCAL_WRITE); + if (mr->ibv_mr == NULL) { + rte_dump_physmem_layout(stderr); + DRV_LOG(ERR, "port %u cannot register memseg[%u]", + dev->data->port_id, i); + goto error; + } + } + /* Sort by virtual address. */ + qsort(*priv->mr, priv->mr_n, sizeof(struct mlx5_mr), mr_comp_addr); + /* First entry must be NULL for comparison. */ + (*priv->mr_cache)[0] = (struct mlx5_mr_cache) { + .lkey = UINT32_MAX, + }; + /* Compile global all-inclusive MR cache table. */ + for (i = 0; i < priv->mr_n; ++i) { + mr = &(*priv->mr)[i]; + mr_cache = &(*priv->mr_cache)[i + 1]; + /* Paranoid, mr[] must be sorted. */ + assert(i == 0 || mr->memseg->addr > (mr - 1)->memseg->addr); + *mr_cache = (struct mlx5_mr_cache) { + .start = (uintptr_t)mr->memseg->addr, + .end = (uintptr_t)mr->memseg->addr + mr->memseg->len, + .lkey = rte_cpu_to_be_32(mr->ibv_mr->lkey) + }; + } + return 0; +error: + for (i = 0; i < priv->mr_n; ++i) { + mr = &(*priv->mr)[i]; + if (mr->ibv_mr != NULL) + ibv_dereg_mr(mr->ibv_mr); + } + rte_free(priv->mr); + rte_free(priv->mr_cache); + rte_errno = ENOMEM; + return -rte_errno; } /** - * Verify the flow list is empty + * Deregister all Memory Regions. * * @param dev * Pointer to Ethernet device. - * - * @return - * The number of object not released. */ -int -mlx5_mr_verify(struct rte_eth_dev *dev) +void +mlx5_mr_deregister_memseg(struct rte_eth_dev *dev) { struct priv *priv = dev->data->dev_private; - int ret = 0; - struct mlx5_mr *mr; + unsigned int i; + + if (priv->mr_n == 0) + return; + for (i = 0; i < priv->mr_n; ++i) { + struct mlx5_mr *mr; - LIST_FOREACH(mr, &priv->mr, next) { - DRV_LOG(DEBUG, "port %u memory region %p still referenced", - dev->data->port_id, (void *)mr); - ++ret; + mr = &(*priv->mr)[i]; + /* Physical memory can't be changed dynamically. */ + assert(mr->memseg != NULL); + assert(mr->ibv_mr != NULL); + ibv_dereg_mr(mr->ibv_mr); } - return ret; + rte_free(priv->mr); + rte_free(priv->mr_cache); + priv->mr = NULL; + priv->mr_cache = NULL; + priv->mr_n = 0; }