| // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB |
| /* Copyright (c) 2019 Mellanox Technologies. */ |
| |
| #include "rsc_dump.h" |
| #include "lib/mlx5.h" |
| |
| #define MLX5_SGMT_TYPE(SGMT) MLX5_SGMT_TYPE_##SGMT |
| #define MLX5_SGMT_STR_ASSING(SGMT)[MLX5_SGMT_TYPE(SGMT)] = #SGMT |
| static const char *const mlx5_rsc_sgmt_name[] = { |
| MLX5_SGMT_STR_ASSING(HW_CQPC), |
| MLX5_SGMT_STR_ASSING(HW_SQPC), |
| MLX5_SGMT_STR_ASSING(HW_RQPC), |
| MLX5_SGMT_STR_ASSING(FULL_SRQC), |
| MLX5_SGMT_STR_ASSING(FULL_CQC), |
| MLX5_SGMT_STR_ASSING(FULL_EQC), |
| MLX5_SGMT_STR_ASSING(FULL_QPC), |
| MLX5_SGMT_STR_ASSING(SND_BUFF), |
| MLX5_SGMT_STR_ASSING(RCV_BUFF), |
| MLX5_SGMT_STR_ASSING(SRQ_BUFF), |
| MLX5_SGMT_STR_ASSING(CQ_BUFF), |
| MLX5_SGMT_STR_ASSING(EQ_BUFF), |
| MLX5_SGMT_STR_ASSING(SX_SLICE), |
| MLX5_SGMT_STR_ASSING(SX_SLICE_ALL), |
| MLX5_SGMT_STR_ASSING(RDB), |
| MLX5_SGMT_STR_ASSING(RX_SLICE_ALL), |
| MLX5_SGMT_STR_ASSING(PRM_QUERY_QP), |
| MLX5_SGMT_STR_ASSING(PRM_QUERY_CQ), |
| MLX5_SGMT_STR_ASSING(PRM_QUERY_MKEY), |
| }; |
| |
| struct mlx5_rsc_dump { |
| u32 pdn; |
| u32 mkey; |
| u16 fw_segment_type[MLX5_SGMT_TYPE_NUM]; |
| }; |
| |
| struct mlx5_rsc_dump_cmd { |
| u64 mem_size; |
| u8 cmd[MLX5_ST_SZ_BYTES(resource_dump)]; |
| }; |
| |
| static int mlx5_rsc_dump_sgmt_get_by_name(char *name) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(mlx5_rsc_sgmt_name); i++) |
| if (!strcmp(name, mlx5_rsc_sgmt_name[i])) |
| return i; |
| |
| return -EINVAL; |
| } |
| |
| static void mlx5_rsc_dump_read_menu_sgmt(struct mlx5_rsc_dump *rsc_dump, struct page *page) |
| { |
| void *data = page_address(page); |
| enum mlx5_sgmt_type sgmt_idx; |
| int num_of_items; |
| char *sgmt_name; |
| void *member; |
| void *menu; |
| int i; |
| |
| menu = MLX5_ADDR_OF(menu_resource_dump_response, data, menu); |
| num_of_items = MLX5_GET(resource_dump_menu_segment, menu, num_of_records); |
| |
| for (i = 0; i < num_of_items; i++) { |
| member = MLX5_ADDR_OF(resource_dump_menu_segment, menu, record[i]); |
| sgmt_name = MLX5_ADDR_OF(resource_dump_menu_record, member, segment_name); |
| sgmt_idx = mlx5_rsc_dump_sgmt_get_by_name(sgmt_name); |
| if (sgmt_idx == -EINVAL) |
| continue; |
| rsc_dump->fw_segment_type[sgmt_idx] = MLX5_GET(resource_dump_menu_record, |
| member, segment_type); |
| } |
| } |
| |
| static int mlx5_rsc_dump_trigger(struct mlx5_core_dev *dev, struct mlx5_rsc_dump_cmd *cmd, |
| struct page *page) |
| { |
| struct mlx5_rsc_dump *rsc_dump = dev->rsc_dump; |
| struct device *ddev = mlx5_core_dma_dev(dev); |
| u32 out_seq_num; |
| u32 in_seq_num; |
| dma_addr_t dma; |
| int err; |
| |
| dma = dma_map_page(ddev, page, 0, cmd->mem_size, DMA_FROM_DEVICE); |
| if (unlikely(dma_mapping_error(ddev, dma))) |
| return -ENOMEM; |
| |
| in_seq_num = MLX5_GET(resource_dump, cmd->cmd, seq_num); |
| MLX5_SET(resource_dump, cmd->cmd, mkey, rsc_dump->mkey); |
| MLX5_SET64(resource_dump, cmd->cmd, address, dma); |
| |
| err = mlx5_core_access_reg(dev, cmd->cmd, sizeof(cmd->cmd), cmd->cmd, |
| sizeof(cmd->cmd), MLX5_REG_RESOURCE_DUMP, 0, 1); |
| if (err) { |
| mlx5_core_err(dev, "Resource dump: Failed to access err %d\n", err); |
| goto out; |
| } |
| out_seq_num = MLX5_GET(resource_dump, cmd->cmd, seq_num); |
| if (out_seq_num && (in_seq_num + 1 != out_seq_num)) |
| err = -EIO; |
| out: |
| dma_unmap_page(ddev, dma, cmd->mem_size, DMA_FROM_DEVICE); |
| return err; |
| } |
| |
| struct mlx5_rsc_dump_cmd *mlx5_rsc_dump_cmd_create(struct mlx5_core_dev *dev, |
| struct mlx5_rsc_key *key) |
| { |
| struct mlx5_rsc_dump_cmd *cmd; |
| int sgmt_type; |
| |
| if (IS_ERR_OR_NULL(dev->rsc_dump)) |
| return ERR_PTR(-EOPNOTSUPP); |
| |
| sgmt_type = dev->rsc_dump->fw_segment_type[key->rsc]; |
| if (!sgmt_type && key->rsc != MLX5_SGMT_TYPE_MENU) |
| return ERR_PTR(-EOPNOTSUPP); |
| |
| cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); |
| if (!cmd) { |
| mlx5_core_err(dev, "Resource dump: Failed to allocate command\n"); |
| return ERR_PTR(-ENOMEM); |
| } |
| MLX5_SET(resource_dump, cmd->cmd, segment_type, sgmt_type); |
| MLX5_SET(resource_dump, cmd->cmd, index1, key->index1); |
| MLX5_SET(resource_dump, cmd->cmd, index2, key->index2); |
| MLX5_SET(resource_dump, cmd->cmd, num_of_obj1, key->num_of_obj1); |
| MLX5_SET(resource_dump, cmd->cmd, num_of_obj2, key->num_of_obj2); |
| MLX5_SET(resource_dump, cmd->cmd, size, key->size); |
| cmd->mem_size = key->size; |
| return cmd; |
| } |
| EXPORT_SYMBOL(mlx5_rsc_dump_cmd_create); |
| |
| void mlx5_rsc_dump_cmd_destroy(struct mlx5_rsc_dump_cmd *cmd) |
| { |
| kfree(cmd); |
| } |
| EXPORT_SYMBOL(mlx5_rsc_dump_cmd_destroy); |
| |
| int mlx5_rsc_dump_next(struct mlx5_core_dev *dev, struct mlx5_rsc_dump_cmd *cmd, |
| struct page *page, int *size) |
| { |
| bool more_dump; |
| int err; |
| |
| if (IS_ERR_OR_NULL(dev->rsc_dump)) |
| return -EOPNOTSUPP; |
| |
| err = mlx5_rsc_dump_trigger(dev, cmd, page); |
| if (err) { |
| mlx5_core_err(dev, "Resource dump: Failed to trigger dump, %d\n", err); |
| return err; |
| } |
| *size = MLX5_GET(resource_dump, cmd->cmd, size); |
| more_dump = MLX5_GET(resource_dump, cmd->cmd, more_dump); |
| |
| return more_dump; |
| } |
| EXPORT_SYMBOL(mlx5_rsc_dump_next); |
| |
| #define MLX5_RSC_DUMP_MENU_SEGMENT 0xffff |
| static int mlx5_rsc_dump_menu(struct mlx5_core_dev *dev) |
| { |
| struct mlx5_rsc_dump_cmd *cmd = NULL; |
| struct mlx5_rsc_key key = {}; |
| struct page *page; |
| int size; |
| int err; |
| |
| page = alloc_page(GFP_KERNEL); |
| if (!page) |
| return -ENOMEM; |
| |
| key.rsc = MLX5_SGMT_TYPE_MENU; |
| key.size = PAGE_SIZE; |
| cmd = mlx5_rsc_dump_cmd_create(dev, &key); |
| if (IS_ERR(cmd)) { |
| err = PTR_ERR(cmd); |
| goto free_page; |
| } |
| MLX5_SET(resource_dump, cmd->cmd, segment_type, MLX5_RSC_DUMP_MENU_SEGMENT); |
| |
| do { |
| err = mlx5_rsc_dump_next(dev, cmd, page, &size); |
| if (err < 0) |
| goto destroy_cmd; |
| |
| mlx5_rsc_dump_read_menu_sgmt(dev->rsc_dump, page); |
| |
| } while (err > 0); |
| |
| destroy_cmd: |
| mlx5_rsc_dump_cmd_destroy(cmd); |
| free_page: |
| __free_page(page); |
| |
| return err; |
| } |
| |
| static int mlx5_rsc_dump_create_mkey(struct mlx5_core_dev *mdev, u32 pdn, |
| u32 *mkey) |
| { |
| int inlen = MLX5_ST_SZ_BYTES(create_mkey_in); |
| void *mkc; |
| u32 *in; |
| int err; |
| |
| in = kvzalloc(inlen, GFP_KERNEL); |
| if (!in) |
| return -ENOMEM; |
| |
| mkc = MLX5_ADDR_OF(create_mkey_in, in, memory_key_mkey_entry); |
| MLX5_SET(mkc, mkc, access_mode_1_0, MLX5_MKC_ACCESS_MODE_PA); |
| MLX5_SET(mkc, mkc, lw, 1); |
| MLX5_SET(mkc, mkc, lr, 1); |
| |
| MLX5_SET(mkc, mkc, pd, pdn); |
| MLX5_SET(mkc, mkc, length64, 1); |
| MLX5_SET(mkc, mkc, qpn, 0xffffff); |
| |
| err = mlx5_core_create_mkey(mdev, mkey, in, inlen); |
| |
| kvfree(in); |
| return err; |
| } |
| |
| struct mlx5_rsc_dump *mlx5_rsc_dump_create(struct mlx5_core_dev *dev) |
| { |
| struct mlx5_rsc_dump *rsc_dump; |
| |
| if (!MLX5_CAP_DEBUG(dev, resource_dump)) { |
| mlx5_core_dbg(dev, "Resource dump: capability not present\n"); |
| return NULL; |
| } |
| rsc_dump = kzalloc(sizeof(*rsc_dump), GFP_KERNEL); |
| if (!rsc_dump) |
| return ERR_PTR(-ENOMEM); |
| |
| return rsc_dump; |
| } |
| |
| void mlx5_rsc_dump_destroy(struct mlx5_core_dev *dev) |
| { |
| if (IS_ERR_OR_NULL(dev->rsc_dump)) |
| return; |
| kfree(dev->rsc_dump); |
| } |
| |
| int mlx5_rsc_dump_init(struct mlx5_core_dev *dev) |
| { |
| struct mlx5_rsc_dump *rsc_dump = dev->rsc_dump; |
| int err; |
| |
| if (IS_ERR_OR_NULL(dev->rsc_dump)) |
| return 0; |
| |
| err = mlx5_core_alloc_pd(dev, &rsc_dump->pdn); |
| if (err) { |
| mlx5_core_warn(dev, "Resource dump: Failed to allocate PD %d\n", err); |
| return err; |
| } |
| err = mlx5_rsc_dump_create_mkey(dev, rsc_dump->pdn, &rsc_dump->mkey); |
| if (err) { |
| mlx5_core_err(dev, "Resource dump: Failed to create mkey, %d\n", err); |
| goto free_pd; |
| } |
| err = mlx5_rsc_dump_menu(dev); |
| if (err) { |
| mlx5_core_err(dev, "Resource dump: Failed to read menu, %d\n", err); |
| goto destroy_mkey; |
| } |
| return err; |
| |
| destroy_mkey: |
| mlx5_core_destroy_mkey(dev, rsc_dump->mkey); |
| free_pd: |
| mlx5_core_dealloc_pd(dev, rsc_dump->pdn); |
| return err; |
| } |
| |
| void mlx5_rsc_dump_cleanup(struct mlx5_core_dev *dev) |
| { |
| if (IS_ERR_OR_NULL(dev->rsc_dump)) |
| return; |
| |
| mlx5_core_destroy_mkey(dev, dev->rsc_dump->mkey); |
| mlx5_core_dealloc_pd(dev, dev->rsc_dump->pdn); |
| } |