| // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB |
| /* |
| * Copyright (c) 2019-2020, Mellanox Technologies Ltd. All rights reserved. |
| */ |
| |
| #include <uapi/rdma/rdma_netlink.h> |
| #include <linux/mlx5/rsc_dump.h> |
| #include <rdma/ib_umem_odp.h> |
| #include <rdma/restrack.h> |
| #include "mlx5_ib.h" |
| #include "restrack.h" |
| |
| #define MAX_DUMP_SIZE 1024 |
| |
| static int dump_rsc(struct mlx5_core_dev *dev, enum mlx5_sgmt_type type, |
| int index, void *data, int *data_len) |
| { |
| struct mlx5_core_dev *mdev = dev; |
| struct mlx5_rsc_dump_cmd *cmd; |
| struct mlx5_rsc_key key = {}; |
| struct page *page; |
| int offset = 0; |
| int err = 0; |
| int cmd_err; |
| int size; |
| |
| page = alloc_page(GFP_KERNEL); |
| if (!page) |
| return -ENOMEM; |
| |
| key.size = PAGE_SIZE; |
| key.rsc = type; |
| key.index1 = index; |
| key.num_of_obj1 = 1; |
| |
| cmd = mlx5_rsc_dump_cmd_create(mdev, &key); |
| if (IS_ERR(cmd)) { |
| err = PTR_ERR(cmd); |
| goto free_page; |
| } |
| |
| do { |
| cmd_err = mlx5_rsc_dump_next(mdev, cmd, page, &size); |
| if (cmd_err < 0 || size + offset > MAX_DUMP_SIZE) { |
| err = cmd_err; |
| goto destroy_cmd; |
| } |
| memcpy(data + offset, page_address(page), size); |
| offset += size; |
| } while (cmd_err > 0); |
| *data_len = offset; |
| |
| destroy_cmd: |
| mlx5_rsc_dump_cmd_destroy(cmd); |
| free_page: |
| __free_page(page); |
| return err; |
| } |
| |
| static int fill_res_raw(struct sk_buff *msg, struct mlx5_ib_dev *dev, |
| enum mlx5_sgmt_type type, u32 key) |
| { |
| int len = 0; |
| void *data; |
| int err; |
| |
| data = kzalloc(MAX_DUMP_SIZE, GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| err = dump_rsc(dev->mdev, type, key, data, &len); |
| if (err) |
| goto out; |
| |
| err = nla_put(msg, RDMA_NLDEV_ATTR_RES_RAW, len, data); |
| out: |
| kfree(data); |
| return err; |
| } |
| |
| static int fill_stat_mr_entry(struct sk_buff *msg, struct ib_mr *ibmr) |
| { |
| struct mlx5_ib_mr *mr = to_mmr(ibmr); |
| struct nlattr *table_attr; |
| |
| if (!(mr->access_flags & IB_ACCESS_ON_DEMAND)) |
| return 0; |
| |
| table_attr = nla_nest_start(msg, |
| RDMA_NLDEV_ATTR_STAT_HWCOUNTERS); |
| |
| if (!table_attr) |
| goto err; |
| |
| if (rdma_nl_stat_hwcounter_entry(msg, "page_faults", |
| atomic64_read(&mr->odp_stats.faults))) |
| goto err_table; |
| if (rdma_nl_stat_hwcounter_entry( |
| msg, "page_invalidations", |
| atomic64_read(&mr->odp_stats.invalidations))) |
| goto err_table; |
| if (rdma_nl_stat_hwcounter_entry(msg, "page_prefetch", |
| atomic64_read(&mr->odp_stats.prefetch))) |
| goto err_table; |
| |
| nla_nest_end(msg, table_attr); |
| return 0; |
| |
| err_table: |
| nla_nest_cancel(msg, table_attr); |
| err: |
| return -EMSGSIZE; |
| } |
| |
| static int fill_res_mr_entry_raw(struct sk_buff *msg, struct ib_mr *ibmr) |
| { |
| struct mlx5_ib_mr *mr = to_mmr(ibmr); |
| |
| return fill_res_raw(msg, mr_to_mdev(mr), MLX5_SGMT_TYPE_PRM_QUERY_MKEY, |
| mlx5_mkey_to_idx(mr->mmkey.key)); |
| } |
| |
| static int fill_res_mr_entry(struct sk_buff *msg, struct ib_mr *ibmr) |
| { |
| struct mlx5_ib_mr *mr = to_mmr(ibmr); |
| struct nlattr *table_attr; |
| |
| if (!(mr->access_flags & IB_ACCESS_ON_DEMAND)) |
| return 0; |
| |
| table_attr = nla_nest_start(msg, RDMA_NLDEV_ATTR_DRIVER); |
| if (!table_attr) |
| goto err; |
| |
| if (mr->is_odp_implicit) { |
| if (rdma_nl_put_driver_string(msg, "odp", "implicit")) |
| goto err; |
| } else { |
| if (rdma_nl_put_driver_string(msg, "odp", "explicit")) |
| goto err; |
| } |
| |
| nla_nest_end(msg, table_attr); |
| return 0; |
| |
| err: |
| nla_nest_cancel(msg, table_attr); |
| return -EMSGSIZE; |
| } |
| |
| static int fill_res_cq_entry_raw(struct sk_buff *msg, struct ib_cq *ibcq) |
| { |
| struct mlx5_ib_dev *dev = to_mdev(ibcq->device); |
| struct mlx5_ib_cq *cq = to_mcq(ibcq); |
| |
| return fill_res_raw(msg, dev, MLX5_SGMT_TYPE_PRM_QUERY_CQ, cq->mcq.cqn); |
| } |
| |
| static int fill_res_qp_entry(struct sk_buff *msg, struct ib_qp *ibqp) |
| { |
| struct mlx5_ib_qp *qp = to_mqp(ibqp); |
| int ret; |
| |
| if (qp->type < IB_QPT_DRIVER) |
| return 0; |
| |
| switch (qp->type) { |
| case MLX5_IB_QPT_REG_UMR: |
| ret = nla_put_string(msg, RDMA_NLDEV_ATTR_RES_SUBTYPE, |
| "REG_UMR"); |
| break; |
| case MLX5_IB_QPT_DCT: |
| ret = nla_put_string(msg, RDMA_NLDEV_ATTR_RES_SUBTYPE, "DCT"); |
| break; |
| case MLX5_IB_QPT_DCI: |
| ret = nla_put_string(msg, RDMA_NLDEV_ATTR_RES_SUBTYPE, "DCI"); |
| break; |
| default: |
| return 0; |
| } |
| if (ret) |
| return ret; |
| |
| return nla_put_u8(msg, RDMA_NLDEV_ATTR_RES_TYPE, IB_QPT_DRIVER); |
| } |
| |
| static int fill_res_qp_entry_raw(struct sk_buff *msg, struct ib_qp *ibqp) |
| { |
| struct mlx5_ib_dev *dev = to_mdev(ibqp->device); |
| |
| return fill_res_raw(msg, dev, MLX5_SGMT_TYPE_PRM_QUERY_QP, |
| ibqp->qp_num); |
| } |
| |
| static const struct ib_device_ops restrack_ops = { |
| .fill_res_cq_entry_raw = fill_res_cq_entry_raw, |
| .fill_res_mr_entry = fill_res_mr_entry, |
| .fill_res_mr_entry_raw = fill_res_mr_entry_raw, |
| .fill_res_qp_entry = fill_res_qp_entry, |
| .fill_res_qp_entry_raw = fill_res_qp_entry_raw, |
| .fill_stat_mr_entry = fill_stat_mr_entry, |
| }; |
| |
| int mlx5_ib_restrack_init(struct mlx5_ib_dev *dev) |
| { |
| ib_set_device_ops(&dev->ib_dev, &restrack_ops); |
| return 0; |
| } |