Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Fan Gong 1705 100.00% 1 100.00%
Total 1705 1


// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.

#include <linux/delay.h>
#include <linux/iopoll.h>

#include "hinic3_hw_cfg.h"
#include "hinic3_hwdev.h"
#include "hinic3_lld.h"
#include "hinic3_mgmt.h"

#define HINIC3_VF_PCI_CFG_REG_BAR  0
#define HINIC3_PCI_INTR_REG_BAR    2
#define HINIC3_PCI_DB_BAR          4

#define HINIC3_EVENT_POLL_SLEEP_US   1000
#define HINIC3_EVENT_POLL_TIMEOUT_US 10000000

static struct hinic3_adev_device {
	const char *name;
} hinic3_adev_devices[HINIC3_SERVICE_T_MAX] = {
	[HINIC3_SERVICE_T_NIC] = {
		.name = "nic",
	},
};

static bool hinic3_adev_svc_supported(struct hinic3_hwdev *hwdev,
				      enum hinic3_service_type svc_type)
{
	switch (svc_type) {
	case HINIC3_SERVICE_T_NIC:
		return hinic3_support_nic(hwdev);
	default:
		break;
	}

	return false;
}

static void hinic3_comm_adev_release(struct device *dev)
{
	struct hinic3_adev *hadev = container_of(dev, struct hinic3_adev,
						 adev.dev);

	kfree(hadev);
}

static struct hinic3_adev *hinic3_add_one_adev(struct hinic3_hwdev *hwdev,
					       enum hinic3_service_type svc_type)
{
	struct hinic3_adev *hadev;
	const char *svc_name;
	int ret;

	hadev = kzalloc(sizeof(*hadev), GFP_KERNEL);
	if (!hadev)
		return NULL;

	svc_name = hinic3_adev_devices[svc_type].name;
	hadev->adev.name = svc_name;
	hadev->adev.id = hwdev->dev_id;
	hadev->adev.dev.parent = hwdev->dev;
	hadev->adev.dev.release = hinic3_comm_adev_release;
	hadev->svc_type = svc_type;
	hadev->hwdev = hwdev;

	ret = auxiliary_device_init(&hadev->adev);
	if (ret) {
		dev_err(hwdev->dev, "failed init adev %s %u\n",
			svc_name, hwdev->dev_id);
		kfree(hadev);
		return NULL;
	}

	ret = auxiliary_device_add(&hadev->adev);
	if (ret) {
		dev_err(hwdev->dev, "failed to add adev %s %u\n",
			svc_name, hwdev->dev_id);
		auxiliary_device_uninit(&hadev->adev);
		return NULL;
	}

	return hadev;
}

static void hinic3_del_one_adev(struct hinic3_hwdev *hwdev,
				enum hinic3_service_type svc_type)
{
	struct hinic3_pcidev *pci_adapter = hwdev->adapter;
	struct hinic3_adev *hadev;
	int timeout;
	bool state;

	timeout = read_poll_timeout(test_and_set_bit, state, !state,
				    HINIC3_EVENT_POLL_SLEEP_US,
				    HINIC3_EVENT_POLL_TIMEOUT_US,
				    false, svc_type, &pci_adapter->state);

	hadev = pci_adapter->hadev[svc_type];
	auxiliary_device_delete(&hadev->adev);
	auxiliary_device_uninit(&hadev->adev);
	pci_adapter->hadev[svc_type] = NULL;
	if (!timeout)
		clear_bit(svc_type, &pci_adapter->state);
}

static int hinic3_attach_aux_devices(struct hinic3_hwdev *hwdev)
{
	struct hinic3_pcidev *pci_adapter = hwdev->adapter;
	enum hinic3_service_type svc_type;

	mutex_lock(&pci_adapter->pdev_mutex);

	for (svc_type = 0; svc_type < HINIC3_SERVICE_T_MAX; svc_type++) {
		if (!hinic3_adev_svc_supported(hwdev, svc_type))
			continue;

		pci_adapter->hadev[svc_type] = hinic3_add_one_adev(hwdev,
								   svc_type);
		if (!pci_adapter->hadev[svc_type])
			goto err_del_adevs;
	}
	mutex_unlock(&pci_adapter->pdev_mutex);
	return 0;

err_del_adevs:
	while (svc_type > 0) {
		svc_type--;
		if (pci_adapter->hadev[svc_type]) {
			hinic3_del_one_adev(hwdev, svc_type);
			pci_adapter->hadev[svc_type] = NULL;
		}
	}
	mutex_unlock(&pci_adapter->pdev_mutex);
	return -ENOMEM;
}

static void hinic3_detach_aux_devices(struct hinic3_hwdev *hwdev)
{
	struct hinic3_pcidev *pci_adapter = hwdev->adapter;
	int i;

	mutex_lock(&pci_adapter->pdev_mutex);
	for (i = 0; i < ARRAY_SIZE(hinic3_adev_devices); i++) {
		if (pci_adapter->hadev[i])
			hinic3_del_one_adev(hwdev, i);
	}
	mutex_unlock(&pci_adapter->pdev_mutex);
}

struct hinic3_hwdev *hinic3_adev_get_hwdev(struct auxiliary_device *adev)
{
	struct hinic3_adev *hadev;

	hadev = container_of(adev, struct hinic3_adev, adev);
	return hadev->hwdev;
}

void hinic3_adev_event_register(struct auxiliary_device *adev,
				void (*event_handler)(struct auxiliary_device *adev,
						      struct hinic3_event_info *event))
{
	struct hinic3_adev *hadev;

	hadev = container_of(adev, struct hinic3_adev, adev);
	hadev->event = event_handler;
}

void hinic3_adev_event_unregister(struct auxiliary_device *adev)
{
	struct hinic3_adev *hadev;

	hadev = container_of(adev, struct hinic3_adev, adev);
	hadev->event = NULL;
}

static int hinic3_mapping_bar(struct pci_dev *pdev,
			      struct hinic3_pcidev *pci_adapter)
{
	pci_adapter->cfg_reg_base = pci_ioremap_bar(pdev,
						    HINIC3_VF_PCI_CFG_REG_BAR);
	if (!pci_adapter->cfg_reg_base) {
		dev_err(&pdev->dev, "Failed to map configuration regs\n");
		return -ENOMEM;
	}

	pci_adapter->intr_reg_base = pci_ioremap_bar(pdev,
						     HINIC3_PCI_INTR_REG_BAR);
	if (!pci_adapter->intr_reg_base) {
		dev_err(&pdev->dev, "Failed to map interrupt regs\n");
		goto err_unmap_cfg_reg_base;
	}

	pci_adapter->db_base_phy = pci_resource_start(pdev, HINIC3_PCI_DB_BAR);
	pci_adapter->db_dwqe_len = pci_resource_len(pdev, HINIC3_PCI_DB_BAR);
	pci_adapter->db_base = pci_ioremap_bar(pdev, HINIC3_PCI_DB_BAR);
	if (!pci_adapter->db_base) {
		dev_err(&pdev->dev, "Failed to map doorbell regs\n");
		goto err_unmap_intr_reg_base;
	}

	return 0;

err_unmap_intr_reg_base:
	iounmap(pci_adapter->intr_reg_base);

err_unmap_cfg_reg_base:
	iounmap(pci_adapter->cfg_reg_base);

	return -ENOMEM;
}

static void hinic3_unmapping_bar(struct hinic3_pcidev *pci_adapter)
{
	iounmap(pci_adapter->db_base);
	iounmap(pci_adapter->intr_reg_base);
	iounmap(pci_adapter->cfg_reg_base);
}

static int hinic3_pci_init(struct pci_dev *pdev)
{
	struct hinic3_pcidev *pci_adapter;
	int err;

	pci_adapter = kzalloc(sizeof(*pci_adapter), GFP_KERNEL);
	if (!pci_adapter)
		return -ENOMEM;

	pci_adapter->pdev = pdev;
	mutex_init(&pci_adapter->pdev_mutex);

	pci_set_drvdata(pdev, pci_adapter);

	err = pci_enable_device(pdev);
	if (err) {
		dev_err(&pdev->dev, "Failed to enable PCI device\n");
		goto err_free_pci_adapter;
	}

	err = pci_request_regions(pdev, HINIC3_NIC_DRV_NAME);
	if (err) {
		dev_err(&pdev->dev, "Failed to request regions\n");
		goto err_disable_device;
	}

	pci_set_master(pdev);

	err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
	if (err) {
		dev_err(&pdev->dev, "Failed to set DMA mask\n");
		goto err_release_regions;
	}

	return 0;

err_release_regions:
	pci_clear_master(pdev);
	pci_release_regions(pdev);

err_disable_device:
	pci_disable_device(pdev);

err_free_pci_adapter:
	pci_set_drvdata(pdev, NULL);
	mutex_destroy(&pci_adapter->pdev_mutex);
	kfree(pci_adapter);

	return err;
}

static void hinic3_pci_uninit(struct pci_dev *pdev)
{
	struct hinic3_pcidev *pci_adapter = pci_get_drvdata(pdev);

	pci_clear_master(pdev);
	pci_release_regions(pdev);
	pci_disable_device(pdev);
	pci_set_drvdata(pdev, NULL);
	mutex_destroy(&pci_adapter->pdev_mutex);
	kfree(pci_adapter);
}

static int hinic3_func_init(struct pci_dev *pdev,
			    struct hinic3_pcidev *pci_adapter)
{
	int err;

	err = hinic3_init_hwdev(pdev);
	if (err) {
		dev_err(&pdev->dev, "Failed to initialize hardware device\n");
		return err;
	}

	err = hinic3_attach_aux_devices(pci_adapter->hwdev);
	if (err)
		goto err_free_hwdev;

	return 0;

err_free_hwdev:
	hinic3_free_hwdev(pci_adapter->hwdev);

	return err;
}

static void hinic3_func_uninit(struct pci_dev *pdev)
{
	struct hinic3_pcidev *pci_adapter = pci_get_drvdata(pdev);

	hinic3_detach_aux_devices(pci_adapter->hwdev);
	hinic3_free_hwdev(pci_adapter->hwdev);
}

static int hinic3_probe_func(struct hinic3_pcidev *pci_adapter)
{
	struct pci_dev *pdev = pci_adapter->pdev;
	int err;

	err = hinic3_mapping_bar(pdev, pci_adapter);
	if (err) {
		dev_err(&pdev->dev, "Failed to map bar\n");
		goto err_out;
	}

	err = hinic3_func_init(pdev, pci_adapter);
	if (err)
		goto err_unmap_bar;

	return 0;

err_unmap_bar:
	hinic3_unmapping_bar(pci_adapter);

err_out:
	dev_err(&pdev->dev, "PCIe device probe function failed\n");
	return err;
}

static void hinic3_remove_func(struct hinic3_pcidev *pci_adapter)
{
	struct pci_dev *pdev = pci_adapter->pdev;

	hinic3_func_uninit(pdev);
	hinic3_unmapping_bar(pci_adapter);
}

static int hinic3_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	struct hinic3_pcidev *pci_adapter;
	int err;

	err = hinic3_pci_init(pdev);
	if (err)
		goto err_out;

	pci_adapter = pci_get_drvdata(pdev);
	err = hinic3_probe_func(pci_adapter);
	if (err)
		goto err_uninit_pci;

	return 0;

err_uninit_pci:
	hinic3_pci_uninit(pdev);

err_out:
	dev_err(&pdev->dev, "PCIe device probe failed\n");
	return err;
}

static void hinic3_remove(struct pci_dev *pdev)
{
	struct hinic3_pcidev *pci_adapter = pci_get_drvdata(pdev);

	hinic3_remove_func(pci_adapter);
	hinic3_pci_uninit(pdev);
}

static const struct pci_device_id hinic3_pci_table[] = {
	/* Completed by later submission due to LoC limit. */
	{0, 0}

};

MODULE_DEVICE_TABLE(pci, hinic3_pci_table);

static void hinic3_shutdown(struct pci_dev *pdev)
{
	struct hinic3_pcidev *pci_adapter = pci_get_drvdata(pdev);

	pci_disable_device(pdev);

	if (pci_adapter)
		hinic3_set_api_stop(pci_adapter->hwdev);
}

static struct pci_driver hinic3_driver = {
	.name            = HINIC3_NIC_DRV_NAME,
	.id_table        = hinic3_pci_table,
	.probe           = hinic3_probe,
	.remove          = hinic3_remove,
	.shutdown        = hinic3_shutdown,
	.sriov_configure = pci_sriov_configure_simple
};

int hinic3_lld_init(void)
{
	return pci_register_driver(&hinic3_driver);
}

void hinic3_lld_exit(void)
{
	pci_unregister_driver(&hinic3_driver);
}