| // SPDX-License-Identifier: GPL-2.0 |
| // Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. |
| |
| #include <linux/etherdevice.h> |
| #include <linux/netdevice.h> |
| |
| #include "hinic3_common.h" |
| #include "hinic3_hw_comm.h" |
| #include "hinic3_hwdev.h" |
| #include "hinic3_hwif.h" |
| #include "hinic3_lld.h" |
| #include "hinic3_nic_cfg.h" |
| #include "hinic3_nic_dev.h" |
| #include "hinic3_nic_io.h" |
| #include "hinic3_rx.h" |
| #include "hinic3_tx.h" |
| |
| #define HINIC3_NIC_DRV_DESC "Intelligent Network Interface Card Driver" |
| |
| #define HINIC3_RX_BUF_LEN 2048 |
| #define HINIC3_LRO_REPLENISH_THLD 256 |
| #define HINIC3_NIC_DEV_WQ_NAME "hinic3_nic_dev_wq" |
| |
| #define HINIC3_SQ_DEPTH 1024 |
| #define HINIC3_RQ_DEPTH 1024 |
| |
| static int hinic3_alloc_txrxqs(struct net_device *netdev) |
| { |
| struct hinic3_nic_dev *nic_dev = netdev_priv(netdev); |
| struct hinic3_hwdev *hwdev = nic_dev->hwdev; |
| int err; |
| |
| err = hinic3_alloc_txqs(netdev); |
| if (err) { |
| dev_err(hwdev->dev, "Failed to alloc txqs\n"); |
| return err; |
| } |
| |
| err = hinic3_alloc_rxqs(netdev); |
| if (err) { |
| dev_err(hwdev->dev, "Failed to alloc rxqs\n"); |
| goto err_free_txqs; |
| } |
| |
| return 0; |
| |
| err_free_txqs: |
| hinic3_free_txqs(netdev); |
| |
| return err; |
| } |
| |
| static void hinic3_free_txrxqs(struct net_device *netdev) |
| { |
| hinic3_free_rxqs(netdev); |
| hinic3_free_txqs(netdev); |
| } |
| |
| static int hinic3_init_nic_dev(struct net_device *netdev, |
| struct hinic3_hwdev *hwdev) |
| { |
| struct hinic3_nic_dev *nic_dev = netdev_priv(netdev); |
| struct pci_dev *pdev = hwdev->pdev; |
| |
| nic_dev->netdev = netdev; |
| SET_NETDEV_DEV(netdev, &pdev->dev); |
| nic_dev->hwdev = hwdev; |
| nic_dev->pdev = pdev; |
| |
| nic_dev->rx_buf_len = HINIC3_RX_BUF_LEN; |
| nic_dev->lro_replenish_thld = HINIC3_LRO_REPLENISH_THLD; |
| nic_dev->nic_svc_cap = hwdev->cfg_mgmt->cap.nic_svc_cap; |
| |
| return 0; |
| } |
| |
| static int hinic3_sw_init(struct net_device *netdev) |
| { |
| struct hinic3_nic_dev *nic_dev = netdev_priv(netdev); |
| struct hinic3_hwdev *hwdev = nic_dev->hwdev; |
| int err; |
| |
| nic_dev->q_params.sq_depth = HINIC3_SQ_DEPTH; |
| nic_dev->q_params.rq_depth = HINIC3_RQ_DEPTH; |
| |
| /* VF driver always uses random MAC address. During VM migration to a |
| * new device, the new device should learn the VMs old MAC rather than |
| * provide its own MAC. The product design assumes that every VF is |
| * suspectable to migration so the device avoids offering MAC address |
| * to VFs. |
| */ |
| eth_hw_addr_random(netdev); |
| err = hinic3_set_mac(hwdev, netdev->dev_addr, 0, |
| hinic3_global_func_id(hwdev)); |
| if (err) { |
| dev_err(hwdev->dev, "Failed to set default MAC\n"); |
| return err; |
| } |
| |
| err = hinic3_alloc_txrxqs(netdev); |
| if (err) { |
| dev_err(hwdev->dev, "Failed to alloc qps\n"); |
| goto err_del_mac; |
| } |
| |
| return 0; |
| |
| err_del_mac: |
| hinic3_del_mac(hwdev, netdev->dev_addr, 0, |
| hinic3_global_func_id(hwdev)); |
| |
| return err; |
| } |
| |
| static void hinic3_sw_uninit(struct net_device *netdev) |
| { |
| struct hinic3_nic_dev *nic_dev = netdev_priv(netdev); |
| |
| hinic3_free_txrxqs(netdev); |
| hinic3_del_mac(nic_dev->hwdev, netdev->dev_addr, 0, |
| hinic3_global_func_id(nic_dev->hwdev)); |
| } |
| |
| static void hinic3_assign_netdev_ops(struct net_device *netdev) |
| { |
| hinic3_set_netdev_ops(netdev); |
| } |
| |
| static void netdev_feature_init(struct net_device *netdev) |
| { |
| struct hinic3_nic_dev *nic_dev = netdev_priv(netdev); |
| netdev_features_t cso_fts = 0; |
| netdev_features_t tso_fts = 0; |
| netdev_features_t dft_fts; |
| |
| dft_fts = NETIF_F_SG | NETIF_F_HIGHDMA; |
| if (hinic3_test_support(nic_dev, HINIC3_NIC_F_CSUM)) |
| cso_fts |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM; |
| if (hinic3_test_support(nic_dev, HINIC3_NIC_F_SCTP_CRC)) |
| cso_fts |= NETIF_F_SCTP_CRC; |
| if (hinic3_test_support(nic_dev, HINIC3_NIC_F_TSO)) |
| tso_fts |= NETIF_F_TSO | NETIF_F_TSO6; |
| |
| netdev->features |= dft_fts | cso_fts | tso_fts; |
| } |
| |
| static int hinic3_set_default_hw_feature(struct net_device *netdev) |
| { |
| struct hinic3_nic_dev *nic_dev = netdev_priv(netdev); |
| struct hinic3_hwdev *hwdev = nic_dev->hwdev; |
| int err; |
| |
| err = hinic3_set_nic_feature_to_hw(nic_dev); |
| if (err) { |
| dev_err(hwdev->dev, "Failed to set nic features\n"); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static void hinic3_link_status_change(struct net_device *netdev, |
| bool link_status_up) |
| { |
| struct hinic3_nic_dev *nic_dev = netdev_priv(netdev); |
| |
| if (link_status_up) { |
| if (netif_carrier_ok(netdev)) |
| return; |
| |
| nic_dev->link_status_up = true; |
| netif_carrier_on(netdev); |
| netdev_dbg(netdev, "Link is up\n"); |
| } else { |
| if (!netif_carrier_ok(netdev)) |
| return; |
| |
| nic_dev->link_status_up = false; |
| netif_carrier_off(netdev); |
| netdev_dbg(netdev, "Link is down\n"); |
| } |
| } |
| |
| static void hinic3_nic_event(struct auxiliary_device *adev, |
| struct hinic3_event_info *event) |
| { |
| struct hinic3_nic_dev *nic_dev = dev_get_drvdata(&adev->dev); |
| struct net_device *netdev; |
| |
| netdev = nic_dev->netdev; |
| |
| switch (HINIC3_SRV_EVENT_TYPE(event->service, event->type)) { |
| case HINIC3_SRV_EVENT_TYPE(HINIC3_EVENT_SRV_NIC, |
| HINIC3_NIC_EVENT_LINK_UP): |
| hinic3_link_status_change(netdev, true); |
| break; |
| case HINIC3_SRV_EVENT_TYPE(HINIC3_EVENT_SRV_NIC, |
| HINIC3_NIC_EVENT_LINK_DOWN): |
| hinic3_link_status_change(netdev, false); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static int hinic3_nic_probe(struct auxiliary_device *adev, |
| const struct auxiliary_device_id *id) |
| { |
| struct hinic3_hwdev *hwdev = hinic3_adev_get_hwdev(adev); |
| struct pci_dev *pdev = hwdev->pdev; |
| struct hinic3_nic_dev *nic_dev; |
| struct net_device *netdev; |
| u16 max_qps, glb_func_id; |
| int err; |
| |
| if (!hinic3_support_nic(hwdev)) { |
| dev_dbg(&adev->dev, "HW doesn't support nic\n"); |
| return 0; |
| } |
| |
| hinic3_adev_event_register(adev, hinic3_nic_event); |
| |
| glb_func_id = hinic3_global_func_id(hwdev); |
| err = hinic3_func_reset(hwdev, glb_func_id, COMM_FUNC_RESET_BIT_NIC); |
| if (err) { |
| dev_err(&adev->dev, "Failed to reset function\n"); |
| goto err_unregister_adev_event; |
| } |
| |
| max_qps = hinic3_func_max_qnum(hwdev); |
| netdev = alloc_etherdev_mq(sizeof(*nic_dev), max_qps); |
| if (!netdev) { |
| dev_err(&adev->dev, "Failed to allocate netdev\n"); |
| err = -ENOMEM; |
| goto err_unregister_adev_event; |
| } |
| |
| nic_dev = netdev_priv(netdev); |
| dev_set_drvdata(&adev->dev, nic_dev); |
| err = hinic3_init_nic_dev(netdev, hwdev); |
| if (err) |
| goto err_free_netdev; |
| |
| err = hinic3_init_nic_io(nic_dev); |
| if (err) |
| goto err_free_netdev; |
| |
| err = hinic3_sw_init(netdev); |
| if (err) |
| goto err_free_nic_io; |
| |
| hinic3_assign_netdev_ops(netdev); |
| |
| netdev_feature_init(netdev); |
| err = hinic3_set_default_hw_feature(netdev); |
| if (err) |
| goto err_uninit_sw; |
| |
| netif_carrier_off(netdev); |
| |
| err = register_netdev(netdev); |
| if (err) |
| goto err_uninit_nic_feature; |
| |
| return 0; |
| |
| err_uninit_nic_feature: |
| hinic3_update_nic_feature(nic_dev, 0); |
| hinic3_set_nic_feature_to_hw(nic_dev); |
| |
| err_uninit_sw: |
| hinic3_sw_uninit(netdev); |
| |
| err_free_nic_io: |
| hinic3_free_nic_io(nic_dev); |
| |
| err_free_netdev: |
| free_netdev(netdev); |
| |
| err_unregister_adev_event: |
| hinic3_adev_event_unregister(adev); |
| dev_err(&pdev->dev, "NIC service probe failed\n"); |
| |
| return err; |
| } |
| |
| static void hinic3_nic_remove(struct auxiliary_device *adev) |
| { |
| struct hinic3_nic_dev *nic_dev = dev_get_drvdata(&adev->dev); |
| struct net_device *netdev; |
| |
| if (!hinic3_support_nic(nic_dev->hwdev)) |
| return; |
| |
| netdev = nic_dev->netdev; |
| unregister_netdev(netdev); |
| |
| hinic3_update_nic_feature(nic_dev, 0); |
| hinic3_set_nic_feature_to_hw(nic_dev); |
| hinic3_sw_uninit(netdev); |
| |
| hinic3_free_nic_io(nic_dev); |
| |
| free_netdev(netdev); |
| } |
| |
| static const struct auxiliary_device_id hinic3_nic_id_table[] = { |
| { |
| .name = HINIC3_NIC_DRV_NAME ".nic", |
| }, |
| {} |
| }; |
| |
| static struct auxiliary_driver hinic3_nic_driver = { |
| .probe = hinic3_nic_probe, |
| .remove = hinic3_nic_remove, |
| .suspend = NULL, |
| .resume = NULL, |
| .name = "nic", |
| .id_table = hinic3_nic_id_table, |
| }; |
| |
| static __init int hinic3_nic_lld_init(void) |
| { |
| int err; |
| |
| err = hinic3_lld_init(); |
| if (err) |
| return err; |
| |
| err = auxiliary_driver_register(&hinic3_nic_driver); |
| if (err) { |
| hinic3_lld_exit(); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static __exit void hinic3_nic_lld_exit(void) |
| { |
| auxiliary_driver_unregister(&hinic3_nic_driver); |
| |
| hinic3_lld_exit(); |
| } |
| |
| module_init(hinic3_nic_lld_init); |
| module_exit(hinic3_nic_lld_exit); |
| |
| MODULE_AUTHOR("Huawei Technologies CO., Ltd"); |
| MODULE_DESCRIPTION(HINIC3_NIC_DRV_DESC); |
| MODULE_LICENSE("GPL"); |