diff options
| author | Matan Barak <matanb@mellanox.com> | 2014-12-11 03:57:53 -0500 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2014-12-11 14:47:34 -0500 |
| commit | 3dca0f42c7baaa4e01699629da13d6556f001ebe (patch) | |
| tree | 783a6fd1ba3285481503056ce2e69337fa46ef59 | |
| parent | 383677da43fa83b390888cf7d25885166b2a6812 (diff) | |
net/mlx4_core: Use tasklet for user-space CQ completion events
Previously, we've fired all our completion callbacks straight from our ISR.
Some of those callbacks were lightweight (for example, mlx4_en's and
IPoIB napi callbacks), but some of them did more work (for example,
the user-space RDMA stack uverbs' completion handler). Besides that,
doing more than the minimal work in ISR is generally considered wrong,
it could even lead to a hard lockup of the system. Since when a lot
of completion events are generated by the hardware, the loop over those
events could be so long, that we'll get into a hard lockup by the system
watchdog.
In order to avoid that, add a new way of invoking completion events
callbacks. In the interrupt itself, we add the CQs which receive completion
event to a per-EQ list and schedule a tasklet. In the tasklet context
we loop over all the CQs in the list and invoke the user callback.
Signed-off-by: Matan Barak <matanb@mellanox.com>
Signed-off-by: Or Gerlitz <ogerlitz@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
| -rw-r--r-- | drivers/infiniband/hw/mlx4/cq.c | 5 | ||||
| -rw-r--r-- | drivers/net/ethernet/mellanox/mlx4/cq.c | 50 | ||||
| -rw-r--r-- | drivers/net/ethernet/mellanox/mlx4/eq.c | 16 | ||||
| -rw-r--r-- | drivers/net/ethernet/mellanox/mlx4/mlx4.h | 12 | ||||
| -rw-r--r-- | include/linux/mlx4/device.h | 5 |
5 files changed, 86 insertions, 2 deletions
diff --git a/drivers/infiniband/hw/mlx4/cq.c b/drivers/infiniband/hw/mlx4/cq.c index 1066eec854a9..a3b70f6c4035 100644 --- a/drivers/infiniband/hw/mlx4/cq.c +++ b/drivers/infiniband/hw/mlx4/cq.c | |||
| @@ -233,7 +233,10 @@ struct ib_cq *mlx4_ib_create_cq(struct ib_device *ibdev, int entries, int vector | |||
| 233 | if (err) | 233 | if (err) |
| 234 | goto err_dbmap; | 234 | goto err_dbmap; |
| 235 | 235 | ||
| 236 | cq->mcq.comp = mlx4_ib_cq_comp; | 236 | if (context) |
| 237 | cq->mcq.tasklet_ctx.comp = mlx4_ib_cq_comp; | ||
| 238 | else | ||
| 239 | cq->mcq.comp = mlx4_ib_cq_comp; | ||
| 237 | cq->mcq.event = mlx4_ib_cq_event; | 240 | cq->mcq.event = mlx4_ib_cq_event; |
| 238 | 241 | ||
| 239 | if (context) | 242 | if (context) |
diff --git a/drivers/net/ethernet/mellanox/mlx4/cq.c b/drivers/net/ethernet/mellanox/mlx4/cq.c index 56022d647837..e71f31387ac6 100644 --- a/drivers/net/ethernet/mellanox/mlx4/cq.c +++ b/drivers/net/ethernet/mellanox/mlx4/cq.c | |||
| @@ -52,6 +52,51 @@ | |||
| 52 | #define MLX4_CQ_STATE_ARMED_SOL ( 6 << 8) | 52 | #define MLX4_CQ_STATE_ARMED_SOL ( 6 << 8) |
| 53 | #define MLX4_EQ_STATE_FIRED (10 << 8) | 53 | #define MLX4_EQ_STATE_FIRED (10 << 8) |
| 54 | 54 | ||
| 55 | #define TASKLET_MAX_TIME 2 | ||
| 56 | #define TASKLET_MAX_TIME_JIFFIES msecs_to_jiffies(TASKLET_MAX_TIME) | ||
| 57 | |||
| 58 | void mlx4_cq_tasklet_cb(unsigned long data) | ||
| 59 | { | ||
| 60 | unsigned long flags; | ||
| 61 | unsigned long end = jiffies + TASKLET_MAX_TIME_JIFFIES; | ||
| 62 | struct mlx4_eq_tasklet *ctx = (struct mlx4_eq_tasklet *)data; | ||
| 63 | struct mlx4_cq *mcq, *temp; | ||
| 64 | |||
| 65 | spin_lock_irqsave(&ctx->lock, flags); | ||
| 66 | list_splice_tail_init(&ctx->list, &ctx->process_list); | ||
| 67 | spin_unlock_irqrestore(&ctx->lock, flags); | ||
| 68 | |||
| 69 | list_for_each_entry_safe(mcq, temp, &ctx->process_list, tasklet_ctx.list) { | ||
| 70 | list_del_init(&mcq->tasklet_ctx.list); | ||
| 71 | mcq->tasklet_ctx.comp(mcq); | ||
| 72 | if (atomic_dec_and_test(&mcq->refcount)) | ||
| 73 | complete(&mcq->free); | ||
| 74 | if (time_after(jiffies, end)) | ||
| 75 | break; | ||
| 76 | } | ||
| 77 | |||
| 78 | if (!list_empty(&ctx->process_list)) | ||
| 79 | tasklet_schedule(&ctx->task); | ||
| 80 | } | ||
| 81 | |||
| 82 | static void mlx4_add_cq_to_tasklet(struct mlx4_cq *cq) | ||
| 83 | { | ||
| 84 | unsigned long flags; | ||
| 85 | struct mlx4_eq_tasklet *tasklet_ctx = cq->tasklet_ctx.priv; | ||
| 86 | |||
| 87 | spin_lock_irqsave(&tasklet_ctx->lock, flags); | ||
| 88 | /* When migrating CQs between EQs will be implemented, please note | ||
| 89 | * that you need to sync this point. It is possible that | ||
| 90 | * while migrating a CQ, completions on the old EQs could | ||
| 91 | * still arrive. | ||
| 92 | */ | ||
| 93 | if (list_empty_careful(&cq->tasklet_ctx.list)) { | ||
| 94 | atomic_inc(&cq->refcount); | ||
| 95 | list_add_tail(&cq->tasklet_ctx.list, &tasklet_ctx->list); | ||
| 96 | } | ||
| 97 | spin_unlock_irqrestore(&tasklet_ctx->lock, flags); | ||
| 98 | } | ||
| 99 | |||
| 55 | void mlx4_cq_completion(struct mlx4_dev *dev, u32 cqn) | 100 | void mlx4_cq_completion(struct mlx4_dev *dev, u32 cqn) |
| 56 | { | 101 | { |
| 57 | struct mlx4_cq *cq; | 102 | struct mlx4_cq *cq; |
| @@ -292,6 +337,11 @@ int mlx4_cq_alloc(struct mlx4_dev *dev, int nent, | |||
| 292 | cq->uar = uar; | 337 | cq->uar = uar; |
| 293 | atomic_set(&cq->refcount, 1); | 338 | atomic_set(&cq->refcount, 1); |
| 294 | init_completion(&cq->free); | 339 | init_completion(&cq->free); |
| 340 | cq->comp = mlx4_add_cq_to_tasklet; | ||
| 341 | cq->tasklet_ctx.priv = | ||
| 342 | &priv->eq_table.eq[cq->vector].tasklet_ctx; | ||
| 343 | INIT_LIST_HEAD(&cq->tasklet_ctx.list); | ||
| 344 | |||
| 295 | 345 | ||
| 296 | cq->irq = priv->eq_table.eq[cq->vector].irq; | 346 | cq->irq = priv->eq_table.eq[cq->vector].irq; |
| 297 | return 0; | 347 | return 0; |
diff --git a/drivers/net/ethernet/mellanox/mlx4/eq.c b/drivers/net/ethernet/mellanox/mlx4/eq.c index d68b264cee4d..3d275fbaf0eb 100644 --- a/drivers/net/ethernet/mellanox/mlx4/eq.c +++ b/drivers/net/ethernet/mellanox/mlx4/eq.c | |||
| @@ -450,7 +450,7 @@ static int mlx4_eq_int(struct mlx4_dev *dev, struct mlx4_eq *eq) | |||
| 450 | { | 450 | { |
| 451 | struct mlx4_priv *priv = mlx4_priv(dev); | 451 | struct mlx4_priv *priv = mlx4_priv(dev); |
| 452 | struct mlx4_eqe *eqe; | 452 | struct mlx4_eqe *eqe; |
| 453 | int cqn; | 453 | int cqn = -1; |
| 454 | int eqes_found = 0; | 454 | int eqes_found = 0; |
| 455 | int set_ci = 0; | 455 | int set_ci = 0; |
| 456 | int port; | 456 | int port; |
| @@ -758,6 +758,13 @@ static int mlx4_eq_int(struct mlx4_dev *dev, struct mlx4_eq *eq) | |||
| 758 | 758 | ||
| 759 | eq_set_ci(eq, 1); | 759 | eq_set_ci(eq, 1); |
| 760 | 760 | ||
| 761 | /* cqn is 24bit wide but is initialized such that its higher bits | ||
| 762 | * are ones too. Thus, if we got any event, cqn's high bits should be off | ||
| 763 | * and we need to schedule the tasklet. | ||
| 764 | */ | ||
| 765 | if (!(cqn & ~0xffffff)) | ||
| 766 | tasklet_schedule(&eq->tasklet_ctx.task); | ||
| 767 | |||
| 761 | return eqes_found; | 768 | return eqes_found; |
| 762 | } | 769 | } |
| 763 | 770 | ||
| @@ -971,6 +978,12 @@ static int mlx4_create_eq(struct mlx4_dev *dev, int nent, | |||
| 971 | 978 | ||
| 972 | eq->cons_index = 0; | 979 | eq->cons_index = 0; |
| 973 | 980 | ||
| 981 | INIT_LIST_HEAD(&eq->tasklet_ctx.list); | ||
| 982 | INIT_LIST_HEAD(&eq->tasklet_ctx.process_list); | ||
| 983 | spin_lock_init(&eq->tasklet_ctx.lock); | ||
| 984 | tasklet_init(&eq->tasklet_ctx.task, mlx4_cq_tasklet_cb, | ||
| 985 | (unsigned long)&eq->tasklet_ctx); | ||
| 986 | |||
| 974 | return err; | 987 | return err; |
| 975 | 988 | ||
| 976 | err_out_free_mtt: | 989 | err_out_free_mtt: |
| @@ -1027,6 +1040,7 @@ static void mlx4_free_eq(struct mlx4_dev *dev, | |||
| 1027 | } | 1040 | } |
| 1028 | } | 1041 | } |
| 1029 | synchronize_irq(eq->irq); | 1042 | synchronize_irq(eq->irq); |
| 1043 | tasklet_disable(&eq->tasklet_ctx.task); | ||
| 1030 | 1044 | ||
| 1031 | mlx4_mtt_cleanup(dev, &eq->mtt); | 1045 | mlx4_mtt_cleanup(dev, &eq->mtt); |
| 1032 | for (i = 0; i < npages; ++i) | 1046 | for (i = 0; i < npages; ++i) |
diff --git a/drivers/net/ethernet/mellanox/mlx4/mlx4.h b/drivers/net/ethernet/mellanox/mlx4/mlx4.h index f48e7c3eecf8..b67ef488c30c 100644 --- a/drivers/net/ethernet/mellanox/mlx4/mlx4.h +++ b/drivers/net/ethernet/mellanox/mlx4/mlx4.h | |||
| @@ -43,6 +43,8 @@ | |||
| 43 | #include <linux/timer.h> | 43 | #include <linux/timer.h> |
| 44 | #include <linux/semaphore.h> | 44 | #include <linux/semaphore.h> |
| 45 | #include <linux/workqueue.h> | 45 | #include <linux/workqueue.h> |
| 46 | #include <linux/interrupt.h> | ||
| 47 | #include <linux/spinlock.h> | ||
| 46 | 48 | ||
| 47 | #include <linux/mlx4/device.h> | 49 | #include <linux/mlx4/device.h> |
| 48 | #include <linux/mlx4/driver.h> | 50 | #include <linux/mlx4/driver.h> |
| @@ -373,6 +375,14 @@ struct mlx4_srq_context { | |||
| 373 | __be64 db_rec_addr; | 375 | __be64 db_rec_addr; |
| 374 | }; | 376 | }; |
| 375 | 377 | ||
| 378 | struct mlx4_eq_tasklet { | ||
| 379 | struct list_head list; | ||
| 380 | struct list_head process_list; | ||
| 381 | struct tasklet_struct task; | ||
| 382 | /* lock on completion tasklet list */ | ||
| 383 | spinlock_t lock; | ||
| 384 | }; | ||
| 385 | |||
| 376 | struct mlx4_eq { | 386 | struct mlx4_eq { |
| 377 | struct mlx4_dev *dev; | 387 | struct mlx4_dev *dev; |
| 378 | void __iomem *doorbell; | 388 | void __iomem *doorbell; |
| @@ -383,6 +393,7 @@ struct mlx4_eq { | |||
| 383 | int nent; | 393 | int nent; |
| 384 | struct mlx4_buf_list *page_list; | 394 | struct mlx4_buf_list *page_list; |
| 385 | struct mlx4_mtt mtt; | 395 | struct mlx4_mtt mtt; |
| 396 | struct mlx4_eq_tasklet tasklet_ctx; | ||
| 386 | }; | 397 | }; |
| 387 | 398 | ||
| 388 | struct mlx4_slave_eqe { | 399 | struct mlx4_slave_eqe { |
| @@ -1146,6 +1157,7 @@ void mlx4_cmd_use_polling(struct mlx4_dev *dev); | |||
| 1146 | int mlx4_comm_cmd(struct mlx4_dev *dev, u8 cmd, u16 param, | 1157 | int mlx4_comm_cmd(struct mlx4_dev *dev, u8 cmd, u16 param, |
| 1147 | unsigned long timeout); | 1158 | unsigned long timeout); |
| 1148 | 1159 | ||
| 1160 | void mlx4_cq_tasklet_cb(unsigned long data); | ||
| 1149 | void mlx4_cq_completion(struct mlx4_dev *dev, u32 cqn); | 1161 | void mlx4_cq_completion(struct mlx4_dev *dev, u32 cqn); |
| 1150 | void mlx4_cq_event(struct mlx4_dev *dev, u32 cqn, int event_type); | ||
