mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 10:49:33 +00:00
![Bhaktipriya Shridhar](/assets/img/avatar_default.png)
The workqueue health->wq was used as per device private health thread. This was done to perform delayed work. The workqueue has a single workitem(&health->work) and hence doesn't require ordering. It is involved in handling the health of the device and is not being used on a memory reclaim path. Hence, the singlethreaded workqueue has been replaced with the use of system_wq. Work item has been flushed in mlx5_health_cleanup() to ensure that there are no pending tasks while disconnecting the driver. Signed-off-by: Bhaktipriya Shridhar <bhaktipriya96@gmail.com> Acked-by: Leon Romanovsky <leonro@mellanox.com> Signed-off-by: David S. Miller <davem@davemloft.net>
324 lines
8.7 KiB
C
324 lines
8.7 KiB
C
/*
|
|
* Copyright (c) 2013-2015, Mellanox Technologies. All rights reserved.
|
|
*
|
|
* This software is available to you under a choice of one of two
|
|
* licenses. You may choose to be licensed under the terms of the GNU
|
|
* General Public License (GPL) Version 2, available from the file
|
|
* COPYING in the main directory of this source tree, or the
|
|
* OpenIB.org BSD license below:
|
|
*
|
|
* Redistribution and use in source and binary forms, with or
|
|
* without modification, are permitted provided that the following
|
|
* conditions are met:
|
|
*
|
|
* - Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer.
|
|
*
|
|
* - Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer in the documentation and/or other materials
|
|
* provided with the distribution.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/random.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/hardirq.h>
|
|
#include <linux/mlx5/driver.h>
|
|
#include <linux/mlx5/cmd.h>
|
|
#include "mlx5_core.h"
|
|
|
|
enum {
|
|
MLX5_HEALTH_POLL_INTERVAL = 2 * HZ,
|
|
MAX_MISSES = 3,
|
|
};
|
|
|
|
enum {
|
|
MLX5_HEALTH_SYNDR_FW_ERR = 0x1,
|
|
MLX5_HEALTH_SYNDR_IRISC_ERR = 0x7,
|
|
MLX5_HEALTH_SYNDR_HW_UNRECOVERABLE_ERR = 0x8,
|
|
MLX5_HEALTH_SYNDR_CRC_ERR = 0x9,
|
|
MLX5_HEALTH_SYNDR_FETCH_PCI_ERR = 0xa,
|
|
MLX5_HEALTH_SYNDR_HW_FTL_ERR = 0xb,
|
|
MLX5_HEALTH_SYNDR_ASYNC_EQ_OVERRUN_ERR = 0xc,
|
|
MLX5_HEALTH_SYNDR_EQ_ERR = 0xd,
|
|
MLX5_HEALTH_SYNDR_EQ_INV = 0xe,
|
|
MLX5_HEALTH_SYNDR_FFSER_ERR = 0xf,
|
|
MLX5_HEALTH_SYNDR_HIGH_TEMP = 0x10
|
|
};
|
|
|
|
enum {
|
|
MLX5_NIC_IFC_FULL = 0,
|
|
MLX5_NIC_IFC_DISABLED = 1,
|
|
MLX5_NIC_IFC_NO_DRAM_NIC = 2
|
|
};
|
|
|
|
static u8 get_nic_interface(struct mlx5_core_dev *dev)
|
|
{
|
|
return (ioread32be(&dev->iseg->cmdq_addr_l_sz) >> 8) & 3;
|
|
}
|
|
|
|
static void trigger_cmd_completions(struct mlx5_core_dev *dev)
|
|
{
|
|
unsigned long flags;
|
|
u64 vector;
|
|
|
|
/* wait for pending handlers to complete */
|
|
synchronize_irq(dev->priv.msix_arr[MLX5_EQ_VEC_CMD].vector);
|
|
spin_lock_irqsave(&dev->cmd.alloc_lock, flags);
|
|
vector = ~dev->cmd.bitmask & ((1ul << (1 << dev->cmd.log_sz)) - 1);
|
|
if (!vector)
|
|
goto no_trig;
|
|
|
|
vector |= MLX5_TRIGGERED_CMD_COMP;
|
|
spin_unlock_irqrestore(&dev->cmd.alloc_lock, flags);
|
|
|
|
mlx5_core_dbg(dev, "vector 0x%llx\n", vector);
|
|
mlx5_cmd_comp_handler(dev, vector);
|
|
return;
|
|
|
|
no_trig:
|
|
spin_unlock_irqrestore(&dev->cmd.alloc_lock, flags);
|
|
}
|
|
|
|
static int in_fatal(struct mlx5_core_dev *dev)
|
|
{
|
|
struct mlx5_core_health *health = &dev->priv.health;
|
|
struct health_buffer __iomem *h = health->health;
|
|
|
|
if (get_nic_interface(dev) == MLX5_NIC_IFC_DISABLED)
|
|
return 1;
|
|
|
|
if (ioread32be(&h->fw_ver) == 0xffffffff)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mlx5_enter_error_state(struct mlx5_core_dev *dev)
|
|
{
|
|
mutex_lock(&dev->intf_state_mutex);
|
|
if (dev->state == MLX5_DEVICE_STATE_INTERNAL_ERROR)
|
|
goto unlock;
|
|
|
|
mlx5_core_err(dev, "start\n");
|
|
if (pci_channel_offline(dev->pdev) || in_fatal(dev)) {
|
|
dev->state = MLX5_DEVICE_STATE_INTERNAL_ERROR;
|
|
trigger_cmd_completions(dev);
|
|
}
|
|
|
|
mlx5_core_event(dev, MLX5_DEV_EVENT_SYS_ERROR, 0);
|
|
mlx5_core_err(dev, "end\n");
|
|
|
|
unlock:
|
|
mutex_unlock(&dev->intf_state_mutex);
|
|
}
|
|
|
|
static void mlx5_handle_bad_state(struct mlx5_core_dev *dev)
|
|
{
|
|
u8 nic_interface = get_nic_interface(dev);
|
|
|
|
switch (nic_interface) {
|
|
case MLX5_NIC_IFC_FULL:
|
|
mlx5_core_warn(dev, "Expected to see disabled NIC but it is full driver\n");
|
|
break;
|
|
|
|
case MLX5_NIC_IFC_DISABLED:
|
|
mlx5_core_warn(dev, "starting teardown\n");
|
|
break;
|
|
|
|
case MLX5_NIC_IFC_NO_DRAM_NIC:
|
|
mlx5_core_warn(dev, "Expected to see disabled NIC but it is no dram nic\n");
|
|
break;
|
|
default:
|
|
mlx5_core_warn(dev, "Expected to see disabled NIC but it is has invalid value %d\n",
|
|
nic_interface);
|
|
}
|
|
|
|
mlx5_disable_device(dev);
|
|
}
|
|
|
|
static void health_care(struct work_struct *work)
|
|
{
|
|
struct mlx5_core_health *health;
|
|
struct mlx5_core_dev *dev;
|
|
struct mlx5_priv *priv;
|
|
|
|
health = container_of(work, struct mlx5_core_health, work);
|
|
priv = container_of(health, struct mlx5_priv, health);
|
|
dev = container_of(priv, struct mlx5_core_dev, priv);
|
|
mlx5_core_warn(dev, "handling bad device here\n");
|
|
mlx5_handle_bad_state(dev);
|
|
}
|
|
|
|
static const char *hsynd_str(u8 synd)
|
|
{
|
|
switch (synd) {
|
|
case MLX5_HEALTH_SYNDR_FW_ERR:
|
|
return "firmware internal error";
|
|
case MLX5_HEALTH_SYNDR_IRISC_ERR:
|
|
return "irisc not responding";
|
|
case MLX5_HEALTH_SYNDR_HW_UNRECOVERABLE_ERR:
|
|
return "unrecoverable hardware error";
|
|
case MLX5_HEALTH_SYNDR_CRC_ERR:
|
|
return "firmware CRC error";
|
|
case MLX5_HEALTH_SYNDR_FETCH_PCI_ERR:
|
|
return "ICM fetch PCI error";
|
|
case MLX5_HEALTH_SYNDR_HW_FTL_ERR:
|
|
return "HW fatal error\n";
|
|
case MLX5_HEALTH_SYNDR_ASYNC_EQ_OVERRUN_ERR:
|
|
return "async EQ buffer overrun";
|
|
case MLX5_HEALTH_SYNDR_EQ_ERR:
|
|
return "EQ error";
|
|
case MLX5_HEALTH_SYNDR_EQ_INV:
|
|
return "Invalid EQ referenced";
|
|
case MLX5_HEALTH_SYNDR_FFSER_ERR:
|
|
return "FFSER error";
|
|
case MLX5_HEALTH_SYNDR_HIGH_TEMP:
|
|
return "High temperature";
|
|
default:
|
|
return "unrecognized error";
|
|
}
|
|
}
|
|
|
|
static u16 get_maj(u32 fw)
|
|
{
|
|
return fw >> 28;
|
|
}
|
|
|
|
static u16 get_min(u32 fw)
|
|
{
|
|
return fw >> 16 & 0xfff;
|
|
}
|
|
|
|
static u16 get_sub(u32 fw)
|
|
{
|
|
return fw & 0xffff;
|
|
}
|
|
|
|
static void print_health_info(struct mlx5_core_dev *dev)
|
|
{
|
|
struct mlx5_core_health *health = &dev->priv.health;
|
|
struct health_buffer __iomem *h = health->health;
|
|
char fw_str[18];
|
|
u32 fw;
|
|
int i;
|
|
|
|
/* If the syndrom is 0, the device is OK and no need to print buffer */
|
|
if (!ioread8(&h->synd))
|
|
return;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(h->assert_var); i++)
|
|
dev_err(&dev->pdev->dev, "assert_var[%d] 0x%08x\n", i, ioread32be(h->assert_var + i));
|
|
|
|
dev_err(&dev->pdev->dev, "assert_exit_ptr 0x%08x\n", ioread32be(&h->assert_exit_ptr));
|
|
dev_err(&dev->pdev->dev, "assert_callra 0x%08x\n", ioread32be(&h->assert_callra));
|
|
fw = ioread32be(&h->fw_ver);
|
|
sprintf(fw_str, "%d.%d.%d", get_maj(fw), get_min(fw), get_sub(fw));
|
|
dev_err(&dev->pdev->dev, "fw_ver %s\n", fw_str);
|
|
dev_err(&dev->pdev->dev, "hw_id 0x%08x\n", ioread32be(&h->hw_id));
|
|
dev_err(&dev->pdev->dev, "irisc_index %d\n", ioread8(&h->irisc_index));
|
|
dev_err(&dev->pdev->dev, "synd 0x%x: %s\n", ioread8(&h->synd), hsynd_str(ioread8(&h->synd)));
|
|
dev_err(&dev->pdev->dev, "ext_synd 0x%04x\n", ioread16be(&h->ext_synd));
|
|
}
|
|
|
|
static unsigned long get_next_poll_jiffies(void)
|
|
{
|
|
unsigned long next;
|
|
|
|
get_random_bytes(&next, sizeof(next));
|
|
next %= HZ;
|
|
next += jiffies + MLX5_HEALTH_POLL_INTERVAL;
|
|
|
|
return next;
|
|
}
|
|
|
|
static void poll_health(unsigned long data)
|
|
{
|
|
struct mlx5_core_dev *dev = (struct mlx5_core_dev *)data;
|
|
struct mlx5_core_health *health = &dev->priv.health;
|
|
u32 count;
|
|
|
|
if (dev->state == MLX5_DEVICE_STATE_INTERNAL_ERROR) {
|
|
mod_timer(&health->timer, get_next_poll_jiffies());
|
|
return;
|
|
}
|
|
|
|
count = ioread32be(health->health_counter);
|
|
if (count == health->prev)
|
|
++health->miss_counter;
|
|
else
|
|
health->miss_counter = 0;
|
|
|
|
health->prev = count;
|
|
if (health->miss_counter == MAX_MISSES) {
|
|
dev_err(&dev->pdev->dev, "device's health compromised - reached miss count\n");
|
|
print_health_info(dev);
|
|
} else {
|
|
mod_timer(&health->timer, get_next_poll_jiffies());
|
|
}
|
|
|
|
if (in_fatal(dev) && !health->sick) {
|
|
health->sick = true;
|
|
print_health_info(dev);
|
|
schedule_work(&health->work);
|
|
}
|
|
}
|
|
|
|
void mlx5_start_health_poll(struct mlx5_core_dev *dev)
|
|
{
|
|
struct mlx5_core_health *health = &dev->priv.health;
|
|
|
|
init_timer(&health->timer);
|
|
health->health = &dev->iseg->health;
|
|
health->health_counter = &dev->iseg->health_counter;
|
|
|
|
health->timer.data = (unsigned long)dev;
|
|
health->timer.function = poll_health;
|
|
health->timer.expires = round_jiffies(jiffies + MLX5_HEALTH_POLL_INTERVAL);
|
|
add_timer(&health->timer);
|
|
}
|
|
|
|
void mlx5_stop_health_poll(struct mlx5_core_dev *dev)
|
|
{
|
|
struct mlx5_core_health *health = &dev->priv.health;
|
|
|
|
del_timer_sync(&health->timer);
|
|
}
|
|
|
|
void mlx5_health_cleanup(struct mlx5_core_dev *dev)
|
|
{
|
|
struct mlx5_core_health *health = &dev->priv.health;
|
|
|
|
flush_work(&health->work);
|
|
}
|
|
|
|
int mlx5_health_init(struct mlx5_core_dev *dev)
|
|
{
|
|
struct mlx5_core_health *health;
|
|
char *name;
|
|
|
|
health = &dev->priv.health;
|
|
name = kmalloc(64, GFP_KERNEL);
|
|
if (!name)
|
|
return -ENOMEM;
|
|
|
|
strcpy(name, "mlx5_health");
|
|
strcat(name, dev_name(&dev->pdev->dev));
|
|
kfree(name);
|
|
|
|
INIT_WORK(&health->work, health_care);
|
|
|
|
return 0;
|
|
}
|