2026-01-29 22:25:33 +08:00

640 lines
15 KiB
C

// SPDX-License-Identifier: GPL-2.0+
// Copyright (c) 2022 Hisilicon Limited.
#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/debugfs.h>
#include "core.h"
#include "hnae3.h"
#include "hns3_device.h"
#include "hns3_common.h"
#include "hns3_cmdq.h"
#include "hns3_verbs.h"
#include "hns3_intr.h"
static struct workqueue_struct *hns3_roh_wq;
static struct dentry *hns3_roh_dfx_root;
static const struct pci_device_id hns3_roh_pci_tbl[] = {
{ PCI_VDEVICE(HUAWEI, HNAE3_DEV_ID_100G_ROH), 0 },
{ PCI_VDEVICE(HUAWEI, HNAE3_DEV_ID_200G_ROH), 0 },
{ PCI_VDEVICE(HUAWEI, HNAE3_DEV_ID_400G_ROH), 0 },
{
0,
}
};
MODULE_DEVICE_TABLE(pci, hns3_roh_pci_tbl);
static int hns3_roh_get_intr_cap(struct hns3_roh_device *hroh_dev)
{
struct hns3_roh_get_intr_info *resp;
struct hns3_roh_desc desc;
int ret;
hns3_roh_cmdq_setup_basic_desc(&desc, HNS3_ROH_OPC_GET_INTR_INFO, true);
ret = hns3_roh_cmdq_send(hroh_dev, &desc, 1);
if (ret) {
dev_err(hroh_dev->dev, "failed to get intr info, ret = %d\n", ret);
return ret;
}
resp = (struct hns3_roh_get_intr_info *)desc.data;
hroh_dev->intr_info.vector_offset =
le16_to_cpu(resp->msixcap_localid_number_nic) +
le16_to_cpu(resp->pf_intr_vector_number_roce);
hroh_dev->intr_info.vector_num =
le16_to_cpu(resp->pf_intr_vector_number_roh);
if (hroh_dev->intr_info.vector_num < HNS3_ROH_MIN_VECTOR_NUM) {
dev_err(hroh_dev->dev,
"just %d intr resources, not enough(min: %d).\n",
hroh_dev->intr_info.vector_num, HNS3_ROH_MIN_VECTOR_NUM);
return -EINVAL;
}
return 0;
}
static void hns3_roh_unregister_device(struct hns3_roh_device *hroh_dev)
{
hroh_dev->active = false;
roh_unregister_device(&hroh_dev->roh_dev);
}
static int hns3_roh_register_device(struct hns3_roh_device *hroh_dev)
{
struct roh_device *rohdev = &hroh_dev->roh_dev;
struct device *dev = hroh_dev->dev;
int ret;
if (!strlen(rohdev->name))
strscpy(rohdev->name, "hns3_%d", ROH_DEVICE_NAME_MAX);
rohdev->owner = THIS_MODULE;
rohdev->dev.parent = dev;
rohdev->netdev = hroh_dev->netdev;
rohdev->ops.set_eid = hns3_roh_set_eid;
rohdev->ops.alloc_hw_stats = hns3_roh_alloc_hw_stats;
rohdev->ops.get_hw_stats = hns3_roh_get_hw_stats;
ret = hns3_roh_get_link_status(hroh_dev, &rohdev->link_status);
if (ret) {
dev_err(dev, "failed to get link status, ret = %d\n", ret);
return ret;
}
ret = roh_register_device(rohdev);
if (ret) {
dev_err(dev, "failed to register roh device, ret = %d\n", ret);
return ret;
}
hroh_dev->active = true;
return 0;
}
void hns3_roh_mbx_task_schedule(struct hns3_roh_device *hroh_dev)
{
if (!test_and_set_bit(HNS3_ROH_SW_STATE_MBX_SERVICE_SCHED, &hroh_dev->state))
mod_delayed_work(hns3_roh_wq, &hroh_dev->srv_task, 0);
}
void hns3_roh_task_schedule(struct hns3_roh_device *hroh_dev, unsigned long delay_time)
{
mod_delayed_work(hns3_roh_wq, &hroh_dev->srv_task, delay_time);
}
static void hns3_roh_mbx_service_task(struct hns3_roh_device *hroh_dev)
{
if (!test_and_clear_bit(HNS3_ROH_SW_STATE_MBX_SERVICE_SCHED,
&hroh_dev->state) ||
test_and_set_bit(HNS3_ROH_SW_STATE_MBX_HANDLING, &hroh_dev->state))
return;
hns3_roh_mbx_handler(hroh_dev);
clear_bit(HNS3_ROH_SW_STATE_MBX_HANDLING, &hroh_dev->state);
}
static void hns3_roh_poll_service_task(struct hns3_roh_device *hroh_dev)
{
unsigned long delta = round_jiffies_relative(HZ);
hns3_roh_update_link_status(hroh_dev);
if (time_is_after_jiffies(hroh_dev->last_processed + HZ)) {
delta = jiffies - hroh_dev->last_processed;
if (delta < round_jiffies_relative(HZ)) {
delta = round_jiffies_relative(HZ) - delta;
goto out;
}
}
hroh_dev->last_processed = jiffies;
out:
hns3_roh_task_schedule(hroh_dev, delta);
}
static void hns3_roh_service_task(struct work_struct *work)
{
struct hns3_roh_device *hroh_dev =
container_of(work, struct hns3_roh_device, srv_task.work);
hns3_roh_mbx_service_task(hroh_dev);
hns3_roh_poll_service_task(hroh_dev);
}
static void hns3_roh_dev_sw_state_init(struct hns3_roh_device *hroh_dev)
{
clear_bit(HNS3_ROH_SW_STATE_MBX_SERVICE_SCHED, &hroh_dev->state);
clear_bit(HNS3_ROH_SW_STATE_MBX_HANDLING, &hroh_dev->state);
}
static int hns3_roh_init_hw(struct hns3_roh_device *hroh_dev)
{
struct device *dev = hroh_dev->dev;
int ret;
ret = hroh_dev->hw->cmdq_init(hroh_dev);
if (ret) {
dev_err(dev, "failed to init cmdq, ret = %d\n", ret);
return ret;
}
ret = hroh_dev->hw->get_intr_cap(hroh_dev);
if (ret) {
dev_err(dev, "failed to get intr cap, ret = %d\n", ret);
goto err_free_cmdq;
}
ret = hns3_roh_init_irq(hroh_dev);
if (ret) {
dev_err(dev, "failed to init irq, ret = %d\n", ret);
goto err_free_cmdq;
}
return 0;
err_free_cmdq:
hroh_dev->hw->cmdq_exit(hroh_dev);
return ret;
}
static void hns3_roh_uninit_hw(struct hns3_roh_device *hroh_dev)
{
hns3_roh_uninit_irq(hroh_dev);
hroh_dev->hw->cmdq_exit(hroh_dev);
}
static int hns3_roh_init(struct hns3_roh_device *hroh_dev)
{
struct device *dev = hroh_dev->dev;
int ret;
ret = hns3_roh_init_hw(hroh_dev);
if (ret) {
dev_err(dev, "failed to init hw resources, ret = %d\n", ret);
return ret;
}
ret = hns3_roh_register_device(hroh_dev);
if (ret) {
dev_err(dev, "failed to register roh device, ret = %d\n", ret);
goto err_uninit_hw;
}
INIT_DELAYED_WORK(&hroh_dev->srv_task, hns3_roh_service_task);
hns3_roh_enable_vector(&hroh_dev->abn_vector, true);
hns3_roh_dev_sw_state_init(hroh_dev);
hns3_roh_task_schedule(hroh_dev, round_jiffies_relative(HZ));
dev_info(dev, "%s driver init success.\n", HNS3_ROH_NAME);
return 0;
err_uninit_hw:
hns3_roh_uninit_hw(hroh_dev);
return ret;
}
static void hns3_roh_exit(struct hns3_roh_device *hroh_dev)
{
cancel_delayed_work_sync(&hroh_dev->srv_task);
hns3_roh_unregister_device(hroh_dev);
hns3_roh_uninit_hw(hroh_dev);
dev_info(&hroh_dev->pdev->dev,
"%s driver uninit success.\n", HNS3_ROH_NAME);
}
static const struct hns3_roh_hw hns3_roh_hw = {
.cmdq_init = hns3_roh_cmdq_init,
.cmdq_exit = hns3_roh_cmdq_exit,
.get_intr_cap = hns3_roh_get_intr_cap,
};
static void hns3_roh_get_cfg_from_frame(struct hns3_roh_device *hroh_dev,
struct hnae3_handle *handle)
{
hroh_dev->pdev = handle->pdev;
hroh_dev->dev = &handle->pdev->dev;
hroh_dev->netdev = handle->rohinfo.netdev;
hroh_dev->reg_base = handle->rohinfo.roh_io_base;
hroh_dev->hw = &hns3_roh_hw;
hroh_dev->priv->handle = handle;
}
static void hns3_roh_dfx_init(struct hns3_roh_device *hroh_dev);
static int __hns3_roh_init_instance(struct hnae3_handle *handle)
{
struct hns3_roh_device *hroh_dev;
int ret;
hroh_dev = (struct hns3_roh_device *)roh_alloc_device(sizeof(*hroh_dev));
if (!hroh_dev) {
dev_err(&handle->pdev->dev, "failed to alloc roh dev.\n");
return -ENOMEM;
}
hroh_dev->priv = kzalloc(sizeof(*hroh_dev->priv), GFP_KERNEL);
if (!hroh_dev->priv) {
ret = -ENOMEM;
goto err_roh_alloc_device;
}
hns3_roh_get_cfg_from_frame(hroh_dev, handle);
ret = hns3_roh_init(hroh_dev);
if (ret) {
dev_err(hroh_dev->dev, "failed to init roh, ret = %d\n", ret);
goto err_kzalloc;
}
handle->priv = hroh_dev;
set_bit(HNS3_ROH_STATE_INITED, &handle->rohinfo.reset_state);
hns3_roh_dfx_init(hroh_dev);
return 0;
err_kzalloc:
kfree(hroh_dev->priv);
err_roh_alloc_device:
roh_dealloc_device(&hroh_dev->roh_dev);
return ret;
}
static void hns3_roh_dfx_uninit(struct hns3_roh_device *hroh_dev);
static void __hns3_roh_uninit_instance(struct hnae3_handle *handle)
{
struct hns3_roh_device *hroh_dev = (struct hns3_roh_device *)handle->priv;
if (!hroh_dev)
return;
hns3_roh_dfx_uninit(hroh_dev);
if (!test_and_clear_bit(HNS3_ROH_STATE_INITED, &handle->rohinfo.reset_state))
netdev_warn(hroh_dev->netdev, "already uninitialized\n");
hns3_roh_enable_vector(&hroh_dev->abn_vector, false);
handle->priv = NULL;
hns3_roh_exit(hroh_dev);
kfree(hroh_dev->priv);
roh_dealloc_device(&hroh_dev->roh_dev);
}
static int hns3_roh_init_instance(struct hnae3_handle *handle)
{
struct device *dev = &handle->pdev->dev;
const struct pci_device_id *id;
int ret;
id = pci_match_id(hns3_roh_pci_tbl, handle->pdev);
if (!id)
return 0;
if (id->driver_data) {
dev_err(dev, "not support vf.\n");
return -EINVAL;
}
ret = __hns3_roh_init_instance(handle);
if (ret) {
dev_err(dev, "failed to init instance, ret = %d\n", ret);
return ret;
}
return 0;
}
static void hns3_roh_uninit_instance(struct hnae3_handle *handle, bool reset)
{
__hns3_roh_uninit_instance(handle);
}
static int hns3_roh_reset_notify_init(struct hnae3_handle *handle)
{
struct device *dev = &handle->pdev->dev;
int ret;
ret = __hns3_roh_init_instance(handle);
if (ret) {
dev_err(dev, "failed to reinit in roh reset process, ret = %d\n", ret);
handle->priv = NULL;
clear_bit(HNS3_ROH_STATE_INITED, &handle->rohinfo.reset_state);
}
return 0;
}
static int hns3_roh_reset_notify_uninit(struct hnae3_handle *handle)
{
msleep(HNS3_ROH_HW_RST_UNINT_DELAY);
__hns3_roh_uninit_instance(handle);
return 0;
}
static int hns3_roh_reset_notify(struct hnae3_handle *handle,
enum hnae3_reset_notify_type type)
{
int ret = 0;
switch (type) {
case HNAE3_INIT_CLIENT:
ret = hns3_roh_reset_notify_init(handle);
break;
case HNAE3_UNINIT_CLIENT:
ret = hns3_roh_reset_notify_uninit(handle);
break;
case HNAE3_DOWN_CLIENT:
set_bit(HNS3_ROH_STATE_CMD_DISABLE, &handle->rohinfo.reset_state);
break;
default:
break;
}
return ret;
}
static const struct hnae3_client_ops hns3_roh_ops = {
.init_instance = hns3_roh_init_instance,
.uninit_instance = hns3_roh_uninit_instance,
.reset_notify = hns3_roh_reset_notify,
};
static struct hnae3_client hns3_roh_client = {
.name = "hns3_roh_hw",
.type = HNAE3_CLIENT_ROH,
.ops = &hns3_roh_ops,
};
static ssize_t hns3_roh_dfx_cmd_read(struct file *filp, char __user *buffer,
size_t count, loff_t *pos)
{
#define HNS3_ROH_DFX_READ_LEN 256
int uncopy_bytes;
char *buf;
int len;
if (*pos != 0)
return 0;
if (count < HNS3_ROH_DFX_READ_LEN)
return -ENOSPC;
buf = kzalloc(HNS3_ROH_DFX_READ_LEN, GFP_KERNEL);
if (!buf)
return -ENOMEM;
len = scnprintf(buf, HNS3_ROH_DFX_READ_LEN, "%s\n", "echo help to cmd to get help info");
uncopy_bytes = copy_to_user(buffer, buf, len);
kfree(buf);
if (uncopy_bytes)
return -EFAULT;
return (*pos = len);
}
static void hns3_roh_dfx_help(struct hns3_roh_device *hroh_dev)
{
dev_info(hroh_dev->dev, "dev info\n");
}
static void hns3_roh_dfx_get_vector_cap(struct hns3_roh_device *hroh_dev)
{
u16 roce_vector_num, nic_vector_num, roh_vector_num;
struct hns3_roh_get_intr_info *resp;
struct device *dev = hroh_dev->dev;
struct hns3_roh_desc desc;
int ret;
hns3_roh_cmdq_setup_basic_desc(&desc, HNS3_ROH_OPC_GET_INTR_INFO, true);
ret = hns3_roh_cmdq_send(hroh_dev, &desc, 1);
if (ret)
dev_warn(hroh_dev->dev, "failed to get intr info, ret = %d\n", ret);
resp = (struct hns3_roh_get_intr_info *)desc.data;
nic_vector_num = le16_to_cpu(resp->msixcap_localid_number_nic);
roce_vector_num = le16_to_cpu(resp->pf_intr_vector_number_roce);
roh_vector_num = le16_to_cpu(resp->pf_intr_vector_number_roh);
dev_info(dev, "NIC vector num: %d\n", nic_vector_num);
dev_info(dev, "RoCE vector num: %d\n", roce_vector_num);
dev_info(dev, "ROH vector num: %d\n", roh_vector_num);
dev_info(dev, "ROH vector offset: %d\n", hroh_dev->intr_info.vector_offset);
}
static void hns3_roh_dfx_dump_dev_info(struct hns3_roh_device *hroh_dev)
{
struct device *dev = hroh_dev->dev;
dev_info(dev, "PCIe device id: 0x%x\n", hroh_dev->pdev->device);
dev_info(dev, "PCIe device name: %s\n", pci_name(hroh_dev->pdev));
dev_info(dev, "Network device name: %s\n", netdev_name(hroh_dev->netdev));
dev_info(dev, "BAR2~3 base addr: 0x%llx\n", (u64)hroh_dev->reg_base);
dev_info(dev, "Base vector: %d\n", hroh_dev->intr_info.base_vecotr);
hns3_roh_dfx_get_vector_cap(hroh_dev);
dev_info(dev, "ABN vector0 irq: %d\n", hroh_dev->abn_vector.vector_irq);
dev_info(dev, "ABN vector0 addr: 0x%llx\n", (u64)hroh_dev->abn_vector.addr);
dev_info(dev, "ABN vector0 name: %s\n", hroh_dev->abn_vector.name);
}
static int hns3_roh_dfx_check_cmd(struct hns3_roh_device *hroh_dev, char *cmd_buf)
{
int ret = 0;
if (strncmp(cmd_buf, "help", strlen("help")) == 0)
hns3_roh_dfx_help(hroh_dev);
else if (strncmp(cmd_buf, "dev info", strlen("dev info")) == 0)
hns3_roh_dfx_dump_dev_info(hroh_dev);
else
ret = -EOPNOTSUPP;
return ret;
}
static ssize_t hns3_roh_dfx_cmd_write(struct file *filp, const char __user *buffer,
size_t count, loff_t *pos)
{
#define HNS3_ROH_DFX_WRITE_LEN 1024
struct hns3_roh_device *hroh_dev = filp->private_data;
char *cmd_buf, *cmd_buf_tmp;
int uncopied_bytes;
int ret;
if (*pos != 0)
return 0;
if (count > HNS3_ROH_DFX_WRITE_LEN)
return -ENOSPC;
cmd_buf = kzalloc(count + 1, GFP_KERNEL);
if (!cmd_buf)
return count;
uncopied_bytes = copy_from_user(cmd_buf, buffer, count);
if (uncopied_bytes) {
kfree(cmd_buf);
return -EFAULT;
}
cmd_buf[count] = '\0';
cmd_buf_tmp = strchr(cmd_buf, '\n');
if (cmd_buf_tmp) {
*cmd_buf_tmp = '\0';
count = cmd_buf_tmp - cmd_buf + 1;
}
ret = hns3_roh_dfx_check_cmd(hroh_dev, cmd_buf);
if (ret)
hns3_roh_dfx_help(hroh_dev);
kfree(cmd_buf);
cmd_buf = NULL;
return count;
}
static const struct file_operations hns3_roh_dfx_cmd_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = hns3_roh_dfx_cmd_read,
.write = hns3_roh_dfx_cmd_write,
};
static void hns3_roh_dfx_init(struct hns3_roh_device *hroh_dev)
{
const char *name = pci_name(hroh_dev->pdev);
struct dentry *entry;
if (IS_ERR_OR_NULL(hns3_roh_dfx_root))
return;
hroh_dev->dfx_debugfs = debugfs_create_dir(name, hns3_roh_dfx_root);
if (IS_ERR_OR_NULL(hroh_dev->dfx_debugfs))
return;
entry = debugfs_create_file("hns3_roh_dfx", 0600,
hroh_dev->dfx_debugfs, hroh_dev,
&hns3_roh_dfx_cmd_fops);
if (IS_ERR_OR_NULL(entry)) {
debugfs_remove_recursive(hroh_dev->dfx_debugfs);
hroh_dev->dfx_debugfs = NULL;
return;
}
}
static void hns3_roh_dfx_uninit(struct hns3_roh_device *hroh_dev)
{
if (IS_ERR_OR_NULL(hroh_dev->dfx_debugfs))
return;
debugfs_remove_recursive(hroh_dev->dfx_debugfs);
hroh_dev->dfx_debugfs = NULL;
}
static void hns3_roh_dfx_register_debugfs(const char *dir_name)
{
hns3_roh_dfx_root = debugfs_create_dir(dir_name, NULL);
if (IS_ERR_OR_NULL(hns3_roh_dfx_root))
return;
}
static void hns3_roh_dfx_unregister_debugfs(void)
{
if (IS_ERR_OR_NULL(hns3_roh_dfx_root))
return;
debugfs_remove_recursive(hns3_roh_dfx_root);
hns3_roh_dfx_root = NULL;
}
static int __init hns3_roh_module_init(void)
{
int ret;
hns3_roh_wq = alloc_workqueue("%s", 0, 0, HNS3_ROH_NAME);
if (!hns3_roh_wq) {
pr_err("%s: failed to create wq.\n", HNS3_ROH_NAME);
return -ENOMEM;
}
hns3_roh_dfx_register_debugfs(HNS3_ROH_NAME);
ret = hnae3_register_client(&hns3_roh_client);
if (ret)
goto out;
return 0;
out:
hns3_roh_dfx_unregister_debugfs();
destroy_workqueue(hns3_roh_wq);
return ret;
}
static void __exit hns3_roh_module_cleanup(void)
{
hnae3_unregister_client(&hns3_roh_client);
hns3_roh_dfx_unregister_debugfs();
destroy_workqueue(hns3_roh_wq);
}
module_init(hns3_roh_module_init);
module_exit(hns3_roh_module_cleanup);
MODULE_LICENSE("GPL");
MODULE_VERSION(HNS3_ROH_VERSION);
MODULE_AUTHOR("Huawei Tech. Co., Ltd.");
MODULE_DESCRIPTION("Hisilicon Hip09 Family ROH Driver");