Date: Fri, 23 Mar 2018 18:02:20 +0000 (UTC) From: Hans Petter Selasky <hselasky@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r331445 - in head/sys/dev/mlx5: . mlx5_core Message-ID: <201803231802.w2NI2Kfm047409@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: hselasky Date: Fri Mar 23 18:02:20 2018 New Revision: 331445 URL: https://svnweb.freebsd.org/changeset/base/331445 Log: Add support for fast unload in shutdown flow in mlx5core. This patch accumulates the following Linux commits: - 8812c24d28f4972c4f2b9998bf30b1f2a1b62adf net/mlx5: Add fast unload support in shutdown flow - 59211bd3b6329c3e5f4a90ac3d7f87ffa7867073 net/mlx5: Split the load/unload flow into hardware and software flows - 4525abeaae54560254a1bb8970b3d4c225d32ef4 net/mlx5: Expose command polling interface Submitted by: Matthew Finlay <matt@mellanox.com> MFC after: 1 week Sponsored by: Mellanox Technologies Modified: head/sys/dev/mlx5/driver.h head/sys/dev/mlx5/mlx5_core/mlx5_cmd.c head/sys/dev/mlx5/mlx5_core/mlx5_core.h head/sys/dev/mlx5/mlx5_core/mlx5_fw.c head/sys/dev/mlx5/mlx5_core/mlx5_health.c head/sys/dev/mlx5/mlx5_core/mlx5_main.c head/sys/dev/mlx5/mlx5_ifc.h Modified: head/sys/dev/mlx5/driver.h ============================================================================== --- head/sys/dev/mlx5/driver.h Fri Mar 23 17:58:33 2018 (r331444) +++ head/sys/dev/mlx5/driver.h Fri Mar 23 18:02:20 2018 (r331445) @@ -779,6 +779,7 @@ struct mlx5_cmd_work_ent { u64 ts2; u16 op; u8 busy; + bool polling; }; struct mlx5_pas { @@ -866,6 +867,7 @@ static inline u32 mlx5_base_mkey(const u32 key) return key & 0xffffff00u; } +void mlx5_enter_error_state(struct mlx5_core_dev *dev, bool force); int mlx5_cmd_init(struct mlx5_core_dev *dev); void mlx5_cmd_cleanup(struct mlx5_core_dev *dev); void mlx5_cmd_use_events(struct mlx5_core_dev *dev); @@ -877,6 +879,8 @@ int mlx5_cmd_exec(struct mlx5_core_dev *dev, void *in, int mlx5_cmd_exec_cb(struct mlx5_core_dev *dev, void *in, int in_size, void *out, int out_size, mlx5_cmd_cbk_t callback, void *context); +int mlx5_cmd_exec_polling(struct mlx5_core_dev *dev, void *in, int in_size, + void *out, int out_size); int mlx5_cmd_alloc_uar(struct mlx5_core_dev *dev, u32 *uarn); int mlx5_cmd_free_uar(struct mlx5_core_dev *dev, u32 uarn); int mlx5_alloc_uuars(struct mlx5_core_dev *dev, struct mlx5_uuar_info *uuari); Modified: head/sys/dev/mlx5/mlx5_core/mlx5_cmd.c ============================================================================== --- head/sys/dev/mlx5/mlx5_core/mlx5_cmd.c Fri Mar 23 17:58:33 2018 (r331444) +++ head/sys/dev/mlx5/mlx5_core/mlx5_cmd.c Fri Mar 23 18:02:20 2018 (r331445) @@ -859,6 +859,7 @@ static void cmd_work_handler(struct work_struct *work) unsigned long cb_timeout = msecs_to_jiffies(MLX5_CMD_TIMEOUT_MSEC); struct mlx5_cmd_layout *lay; struct semaphore *sem; + bool poll_cmd = ent->polling; sem = ent->page_queue ? &cmd->pages_sem : &cmd->sem; down(sem); @@ -897,7 +898,7 @@ static void cmd_work_handler(struct work_struct *work) iowrite32be(1 << ent->idx, &dev->iseg->cmd_dbell); mmiowb(); /* if not in polling don't use ent after this point*/ - if (cmd->mode == CMD_MODE_POLLING) { + if (cmd->mode == CMD_MODE_POLLING || poll_cmd) { poll_timeout(ent); /* make sure we read the descriptor after ownership is SW */ mlx5_cmd_comp_handler(dev, 1U << ent->idx); @@ -940,7 +941,7 @@ static int wait_func(struct mlx5_core_dev *dev, struct struct mlx5_cmd *cmd = &dev->cmd; int err; - if (cmd->mode == CMD_MODE_POLLING) { + if (cmd->mode == CMD_MODE_POLLING || ent->polling) { wait_for_completion(&ent->done); err = ent->ret; } else if (!wait_for_completion_timeout(&ent->done, timeout)) { @@ -969,7 +970,8 @@ static int mlx5_cmd_invoke(struct mlx5_core_dev *dev, int uin_size, struct mlx5_cmd_msg *out, void *uout, int uout_size, mlx5_cmd_cbk_t callback, - void *context, int page_queue, u8 *status) + void *context, int page_queue, u8 *status, + bool force_polling) { struct mlx5_cmd *cmd = &dev->cmd; struct mlx5_cmd_work_ent *ent; @@ -986,6 +988,8 @@ static int mlx5_cmd_invoke(struct mlx5_core_dev *dev, if (IS_ERR(ent)) return PTR_ERR(ent); + ent->polling = force_polling; + if (!callback) init_completion(&ent->done); @@ -1260,7 +1264,8 @@ static int is_manage_pages(void *in) static int cmd_exec_helper(struct mlx5_core_dev *dev, void *in, int in_size, void *out, int out_size, - mlx5_cmd_cbk_t callback, void *context) + mlx5_cmd_cbk_t callback, void *context, + bool force_polling) { struct mlx5_cmd_msg *inb; struct mlx5_cmd_msg *outb; @@ -1300,7 +1305,7 @@ static int cmd_exec_helper(struct mlx5_core_dev *dev, } err = mlx5_cmd_invoke(dev, inb, in_size, outb, out, out_size, callback, - context, pages_queue, &status); + context, pages_queue, &status, force_polling); if (err) { if (err == -ETIMEDOUT) return err; @@ -1331,7 +1336,7 @@ int mlx5_cmd_exec(struct mlx5_core_dev *dev, void *in, { int err; - err = cmd_exec_helper(dev, in, in_size, out, out_size, NULL, NULL); + err = cmd_exec_helper(dev, in, in_size, out, out_size, NULL, NULL, false); return err ? : mlx5_cmd_check(dev, in, out); } EXPORT_SYMBOL(mlx5_cmd_exec); @@ -1340,9 +1345,19 @@ int mlx5_cmd_exec_cb(struct mlx5_core_dev *dev, void * void *out, int out_size, mlx5_cmd_cbk_t callback, void *context) { - return cmd_exec_helper(dev, in, in_size, out, out_size, callback, context); + return cmd_exec_helper(dev, in, in_size, out, out_size, callback, context, false); } EXPORT_SYMBOL(mlx5_cmd_exec_cb); + +int mlx5_cmd_exec_polling(struct mlx5_core_dev *dev, void *in, int in_size, + void *out, int out_size) +{ + int err; + + err = cmd_exec_helper(dev, in, in_size, out, out_size, NULL, NULL, true); + return err ? : mlx5_cmd_check(dev, in, out); +} +EXPORT_SYMBOL(mlx5_cmd_exec_polling); static void destroy_msg_cache(struct mlx5_core_dev *dev) { Modified: head/sys/dev/mlx5/mlx5_core/mlx5_core.h ============================================================================== --- head/sys/dev/mlx5/mlx5_core/mlx5_core.h Fri Mar 23 17:58:33 2018 (r331444) +++ head/sys/dev/mlx5/mlx5_core/mlx5_core.h Fri Mar 23 18:02:20 2018 (r331445) @@ -70,9 +70,10 @@ int mlx5_query_hca_caps(struct mlx5_core_dev *dev); int mlx5_query_board_id(struct mlx5_core_dev *dev); int mlx5_cmd_init_hca(struct mlx5_core_dev *dev); int mlx5_cmd_teardown_hca(struct mlx5_core_dev *dev); +int mlx5_cmd_force_teardown_hca(struct mlx5_core_dev *dev); void mlx5_core_event(struct mlx5_core_dev *dev, enum mlx5_dev_event event, unsigned long param); -void mlx5_enter_error_state(struct mlx5_core_dev *dev); +void mlx5_enter_error_state(struct mlx5_core_dev *dev, bool force); void mlx5_disable_device(struct mlx5_core_dev *dev); void mlx5_recover_device(struct mlx5_core_dev *dev); Modified: head/sys/dev/mlx5/mlx5_core/mlx5_fw.c ============================================================================== --- head/sys/dev/mlx5/mlx5_core/mlx5_fw.c Fri Mar 23 17:58:33 2018 (r331444) +++ head/sys/dev/mlx5/mlx5_core/mlx5_fw.c Fri Mar 23 18:02:20 2018 (r331445) @@ -218,6 +218,34 @@ int mlx5_cmd_teardown_hca(struct mlx5_core_dev *dev) return mlx5_cmd_exec(dev, in, sizeof(in), out, sizeof(out)); } +int mlx5_cmd_force_teardown_hca(struct mlx5_core_dev *dev) +{ + u32 out[MLX5_ST_SZ_DW(teardown_hca_out)] = {0}; + u32 in[MLX5_ST_SZ_DW(teardown_hca_in)] = {0}; + int force_state; + int ret; + + if (!MLX5_CAP_GEN(dev, force_teardown)) { + mlx5_core_dbg(dev, "force teardown is not supported in the firmware\n"); + return -EOPNOTSUPP; + } + + MLX5_SET(teardown_hca_in, in, opcode, MLX5_CMD_OP_TEARDOWN_HCA); + MLX5_SET(teardown_hca_in, in, profile, MLX5_TEARDOWN_HCA_IN_PROFILE_FORCE_CLOSE); + + ret = mlx5_cmd_exec_polling(dev, in, sizeof(in), out, sizeof(out)); + if (ret) + return ret; + + force_state = MLX5_GET(teardown_hca_out, out, force_state); + if (force_state == MLX5_TEARDOWN_HCA_OUT_FORCE_STATE_FAIL) { + mlx5_core_err(dev, "teardown with force mode failed\n"); + return -EIO; + } + + return 0; +} + int mlx5_core_set_dc_cnak_trace(struct mlx5_core_dev *dev, int enable, u64 addr) { Modified: head/sys/dev/mlx5/mlx5_core/mlx5_health.c ============================================================================== --- head/sys/dev/mlx5/mlx5_core/mlx5_health.c Fri Mar 23 17:58:33 2018 (r331444) +++ head/sys/dev/mlx5/mlx5_core/mlx5_health.c Fri Mar 23 18:02:20 2018 (r331445) @@ -90,7 +90,7 @@ static int in_fatal(struct mlx5_core_dev *dev) return 0; } -void mlx5_enter_error_state(struct mlx5_core_dev *dev) +void mlx5_enter_error_state(struct mlx5_core_dev *dev, bool force) { mutex_lock(&dev->intf_state_mutex); if (dev->state == MLX5_DEVICE_STATE_INTERNAL_ERROR) { @@ -99,7 +99,7 @@ void mlx5_enter_error_state(struct mlx5_core_dev *dev) } mlx5_core_err(dev, "start\n"); - if (pci_channel_offline(dev->pdev) || in_fatal(dev)) { + if (pci_channel_offline(dev->pdev) || in_fatal(dev) || force) { dev->state = MLX5_DEVICE_STATE_INTERNAL_ERROR; mlx5_trigger_cmd_completions(dev); } Modified: head/sys/dev/mlx5/mlx5_core/mlx5_main.c ============================================================================== --- head/sys/dev/mlx5/mlx5_core/mlx5_main.c Fri Mar 23 17:58:33 2018 (r331444) +++ head/sys/dev/mlx5/mlx5_core/mlx5_main.c Fri Mar 23 18:02:20 2018 (r331445) @@ -868,11 +868,65 @@ static void mlx5_pci_close(struct mlx5_core_dev *dev, mlx5_pci_disable_device(dev); } -static int mlx5_load_one(struct mlx5_core_dev *dev, struct mlx5_priv *priv) +static int mlx5_init_once(struct mlx5_core_dev *dev, struct mlx5_priv *priv) { struct pci_dev *pdev = dev->pdev; int err; + err = mlx5_query_hca_caps(dev); + if (err) { + dev_err(&pdev->dev, "query hca failed\n"); + goto out; + } + + err = mlx5_query_board_id(dev); + if (err) { + dev_err(&pdev->dev, "query board id failed\n"); + goto out; + } + + err = mlx5_eq_init(dev); + if (err) { + dev_err(&pdev->dev, "failed to initialize eq\n"); + goto out; + } + + MLX5_INIT_DOORBELL_LOCK(&priv->cq_uar_lock); + + err = mlx5_init_cq_table(dev); + if (err) { + dev_err(&pdev->dev, "failed to initialize cq table\n"); + goto err_eq_cleanup; + } + + mlx5_init_qp_table(dev); + mlx5_init_srq_table(dev); + mlx5_init_mr_table(dev); + + return 0; + +err_eq_cleanup: + mlx5_eq_cleanup(dev); + +out: + return err; +} + +static void mlx5_cleanup_once(struct mlx5_core_dev *dev) +{ + mlx5_cleanup_mr_table(dev); + mlx5_cleanup_srq_table(dev); + mlx5_cleanup_qp_table(dev); + mlx5_cleanup_cq_table(dev); + mlx5_eq_cleanup(dev); +} + +static int mlx5_load_one(struct mlx5_core_dev *dev, struct mlx5_priv *priv, + bool boot) +{ + struct pci_dev *pdev = dev->pdev; + int err; + mutex_lock(&dev->intf_state_mutex); if (test_bit(MLX5_INTERFACE_STATE_UP, &dev->intf_state)) { dev_warn(&dev->pdev->dev, "%s: interface is up, NOP\n", @@ -900,12 +954,10 @@ static int mlx5_load_one(struct mlx5_core_dev *dev, st goto err_cmd_cleanup; } - mlx5_pagealloc_init(dev); - err = mlx5_core_enable_hca(dev); if (err) { device_printf((&pdev->dev)->bsddev, "ERR: ""enable hca failed\n"); - goto err_pagealloc_cleanup; + goto err_cmd_cleanup; } err = mlx5_core_set_issi(dev); @@ -958,34 +1010,21 @@ static int mlx5_load_one(struct mlx5_core_dev *dev, st mlx5_start_health_poll(dev); - err = mlx5_query_hca_caps(dev); - if (err) { - device_printf((&pdev->dev)->bsddev, "ERR: ""query hca failed\n"); + if (boot && mlx5_init_once(dev, priv)) { + dev_err(&pdev->dev, "sw objs init failed\n"); goto err_stop_poll; } - err = mlx5_query_board_id(dev); - if (err) { - device_printf((&pdev->dev)->bsddev, "ERR: ""query board id failed\n"); - goto err_stop_poll; - } - err = mlx5_enable_msix(dev); if (err) { device_printf((&pdev->dev)->bsddev, "ERR: ""enable msix failed\n"); - goto err_stop_poll; + goto err_cleanup_once; } - err = mlx5_eq_init(dev); - if (err) { - device_printf((&pdev->dev)->bsddev, "ERR: ""failed to initialize eq\n"); - goto disable_msix; - } - err = mlx5_alloc_uuars(dev, &priv->uuari); if (err) { device_printf((&pdev->dev)->bsddev, "ERR: ""Failed allocating uar, aborting\n"); - goto err_eq_cleanup; + goto err_disable_msix; } err = mlx5_start_eqs(dev); @@ -1003,23 +1042,16 @@ static int mlx5_load_one(struct mlx5_core_dev *dev, st if (map_bf_area(dev)) device_printf((&pdev->dev)->bsddev, "ERR: ""Failed to map blue flame area\n"); - MLX5_INIT_DOORBELL_LOCK(&priv->cq_uar_lock); - - mlx5_init_cq_table(dev); - mlx5_init_qp_table(dev); - mlx5_init_srq_table(dev); - mlx5_init_mr_table(dev); - err = mlx5_init_fs(dev); if (err) { mlx5_core_err(dev, "flow steering init %d\n", err); - goto err_init_tables; + goto err_free_comp_eqs; } err = mlx5_register_device(dev); if (err) { dev_err(&pdev->dev, "mlx5_register_device failed %d\n", err); - goto err_reg_dev; + goto err_fs; } mlx5_fwdump_prep(dev); @@ -1031,13 +1063,11 @@ out: mutex_unlock(&dev->intf_state_mutex); return 0; -err_reg_dev: +err_fs: mlx5_cleanup_fs(dev); -err_init_tables: - mlx5_cleanup_mr_table(dev); - mlx5_cleanup_srq_table(dev); - mlx5_cleanup_qp_table(dev); - mlx5_cleanup_cq_table(dev); + +err_free_comp_eqs: + free_comp_eqs(dev); unmap_bf_area(dev); err_stop_eqs: @@ -1046,12 +1076,13 @@ err_stop_eqs: err_free_uar: mlx5_free_uuars(dev, &priv->uuari); -err_eq_cleanup: - mlx5_eq_cleanup(dev); - -disable_msix: +err_disable_msix: mlx5_disable_msix(dev); +err_cleanup_once: + if (boot) + mlx5_cleanup_once(dev); + err_stop_poll: mlx5_stop_health_poll(dev); if (mlx5_cmd_teardown_hca(dev)) { @@ -1068,9 +1099,6 @@ err_pagealloc_stop: err_disable_hca: mlx5_core_disable_hca(dev); -err_pagealloc_cleanup: - mlx5_pagealloc_cleanup(dev); - err_cmd_cleanup: mlx5_cmd_cleanup(dev); @@ -1081,13 +1109,16 @@ out_err: return err; } -static int mlx5_unload_one(struct mlx5_core_dev *dev, struct mlx5_priv *priv) +static int mlx5_unload_one(struct mlx5_core_dev *dev, struct mlx5_priv *priv, + bool cleanup) { int err = 0; mutex_lock(&dev->intf_state_mutex); if (test_bit(MLX5_INTERFACE_STATE_DOWN, &dev->intf_state)) { dev_warn(&dev->pdev->dev, "%s: interface is down, NOP\n", __func__); + if (cleanup) + mlx5_cleanup_once(dev); goto out; } @@ -1095,17 +1126,14 @@ static int mlx5_unload_one(struct mlx5_core_dev *dev, mlx5_unregister_device(dev); mlx5_cleanup_fs(dev); - mlx5_cleanup_mr_table(dev); - mlx5_cleanup_srq_table(dev); - mlx5_cleanup_qp_table(dev); - mlx5_cleanup_cq_table(dev); unmap_bf_area(dev); mlx5_wait_for_reclaim_vfs_pages(dev); free_comp_eqs(dev); mlx5_stop_eqs(dev); mlx5_free_uuars(dev, &priv->uuari); - mlx5_eq_cleanup(dev); mlx5_disable_msix(dev); + if (cleanup) + mlx5_cleanup_once(dev); mlx5_stop_health_poll(dev); err = mlx5_cmd_teardown_hca(dev); if (err) { @@ -1115,7 +1143,6 @@ static int mlx5_unload_one(struct mlx5_core_dev *dev, mlx5_pagealloc_stop(dev); mlx5_reclaim_startup_pages(dev); mlx5_core_disable_hca(dev); - mlx5_pagealloc_cleanup(dev); mlx5_cmd_cleanup(dev); out: @@ -1184,7 +1211,9 @@ static int init_one(struct pci_dev *pdev, goto close_pci; } - err = mlx5_load_one(dev, priv); + mlx5_pagealloc_init(dev); + + err = mlx5_load_one(dev, priv, true); if (err) { device_printf((&pdev->dev)->bsddev, "ERR: ""mlx5_register_device failed %d\n", err); goto clean_health; @@ -1193,6 +1222,7 @@ static int init_one(struct pci_dev *pdev, return 0; clean_health: + mlx5_pagealloc_cleanup(dev); mlx5_health_cleanup(dev); close_pci: mlx5_pci_close(dev, priv); @@ -1206,12 +1236,13 @@ static void remove_one(struct pci_dev *pdev) struct mlx5_core_dev *dev = pci_get_drvdata(pdev); struct mlx5_priv *priv = &dev->priv; - if (mlx5_unload_one(dev, priv)) { + if (mlx5_unload_one(dev, priv, true)) { dev_err(&dev->pdev->dev, "mlx5_unload_one failed\n"); mlx5_health_cleanup(dev); return; } + mlx5_pagealloc_cleanup(dev); mlx5_health_cleanup(dev); mlx5_pci_close(dev, priv); pci_set_drvdata(pdev, NULL); @@ -1225,8 +1256,8 @@ static pci_ers_result_t mlx5_pci_err_detected(struct p struct mlx5_priv *priv = &dev->priv; dev_info(&pdev->dev, "%s was called\n", __func__); - mlx5_enter_error_state(dev); - mlx5_unload_one(dev, priv); + mlx5_enter_error_state(dev, false); + mlx5_unload_one(dev, priv, false); if (state) { pci_save_state(pdev->dev.bsddev); mlx5_drain_health_wq(dev); @@ -1310,7 +1341,7 @@ static void mlx5_pci_resume(struct pci_dev *pdev) pci_save_state(pdev->dev.bsddev); wait_vital(pdev); - err = mlx5_load_one(dev, priv); + err = mlx5_load_one(dev, priv, false); if (err) dev_err(&pdev->dev, "%s: mlx5_load_one failed with error code: %d\n" , __func__, err); @@ -1324,13 +1355,41 @@ static const struct pci_error_handlers mlx5_err_handle .resume = mlx5_pci_resume }; +static int mlx5_try_fast_unload(struct mlx5_core_dev *dev) +{ + int err; + + if (!MLX5_CAP_GEN(dev, force_teardown)) { + mlx5_core_dbg(dev, "force teardown is not supported in the firmware\n"); + return -EOPNOTSUPP; + } + + if (dev->state == MLX5_DEVICE_STATE_INTERNAL_ERROR) { + mlx5_core_dbg(dev, "Device in internal error state, giving up\n"); + return -EAGAIN; + } + + err = mlx5_cmd_force_teardown_hca(dev); + if (err) { + mlx5_core_dbg(dev, "Firmware couldn't do fast unload error: %d\n", err); + return err; + } + + mlx5_enter_error_state(dev, true); + + return 0; +} + static void shutdown_one(struct pci_dev *pdev) { struct mlx5_core_dev *dev = pci_get_drvdata(pdev); struct mlx5_priv *priv = &dev->priv; + int err; set_bit(MLX5_INTERFACE_STATE_SHUTDOWN, &dev->intf_state); - mlx5_unload_one(dev, priv); + err = mlx5_try_fast_unload(dev); + if (err) + mlx5_unload_one(dev, priv, false); mlx5_pci_disable_device(dev); } Modified: head/sys/dev/mlx5/mlx5_ifc.h ============================================================================== --- head/sys/dev/mlx5/mlx5_ifc.h Fri Mar 23 17:58:33 2018 (r331444) +++ head/sys/dev/mlx5/mlx5_ifc.h Fri Mar 23 18:02:20 2018 (r331445) @@ -941,7 +941,8 @@ struct mlx5_ifc_cmd_hca_cap_bits { u8 max_indirection[0x8]; u8 reserved_8[0x1]; u8 log_max_mrw_sz[0x7]; - u8 reserved_9[0x2]; + u8 force_teardown[0x1]; + u8 reserved_9[0x1]; u8 log_max_bsf_list_size[0x6]; u8 reserved_10[0x2]; u8 log_max_klm_list_size[0x6]; @@ -3149,18 +3150,25 @@ struct mlx5_ifc_icmd_access_reg_in_bits { u8 register_data[0][0x20]; }; +enum { + MLX5_TEARDOWN_HCA_OUT_FORCE_STATE_SUCCESS = 0x0, + MLX5_TEARDOWN_HCA_OUT_FORCE_STATE_FAIL = 0x1, +}; + struct mlx5_ifc_teardown_hca_out_bits { u8 status[0x8]; u8 reserved_0[0x18]; u8 syndrome[0x20]; - u8 reserved_1[0x40]; + u8 reserved_1[0x3f]; + + u8 force_state[0x1]; }; enum { MLX5_TEARDOWN_HCA_IN_PROFILE_GRACEFUL_CLOSE = 0x0, - MLX5_TEARDOWN_HCA_IN_PROFILE_PANIC_CLOSE = 0x1, + MLX5_TEARDOWN_HCA_IN_PROFILE_FORCE_CLOSE = 0x1, }; struct mlx5_ifc_teardown_hca_in_bits {
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201803231802.w2NI2Kfm047409>