Add ethtool operations and statistics operations. Signed-off-by: Aviad Krawczyk <aviad.krawc...@huawei.com> Signed-off-by: Zhaochen <zhaoch...@huawei.com> --- MAINTAINERS | 7 + drivers/net/ethernet/huawei/hinic/hinic_dev.h | 3 + drivers/net/ethernet/huawei/hinic/hinic_main.c | 262 ++++++++++++++++++++++++- drivers/net/ethernet/huawei/hinic/hinic_port.c | 29 +++ drivers/net/ethernet/huawei/hinic/hinic_port.h | 45 +++++ drivers/net/ethernet/huawei/hinic/hinic_rx.c | 19 ++ drivers/net/ethernet/huawei/hinic/hinic_rx.h | 2 + drivers/net/ethernet/huawei/hinic/hinic_tx.c | 22 +++ drivers/net/ethernet/huawei/hinic/hinic_tx.h | 2 + 9 files changed, 390 insertions(+), 1 deletion(-)
diff --git a/MAINTAINERS b/MAINTAINERS index 4f4057c..5c27965 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6238,6 +6238,13 @@ L: linux-in...@vger.kernel.org S: Maintained F: drivers/input/touchscreen/htcpen.c +HUAWEI ETHERNET DRIVER +M: Aviad Krawczyk <aviad.krawc...@huawei.com> +L: netdev@vger.kernel.org +S: Supported +F: Documentation/networking/hinic.txt +F: drivers/net/ethernet/huawei/* + HUGETLB FILESYSTEM M: Nadia Yvette Chambers <n...@holomorphy.com> S: Maintained diff --git a/drivers/net/ethernet/huawei/hinic/hinic_dev.h b/drivers/net/ethernet/huawei/hinic/hinic_dev.h index f59c90d..08918a8 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_dev.h +++ b/drivers/net/ethernet/huawei/hinic/hinic_dev.h @@ -57,6 +57,9 @@ struct hinic_dev { struct hinic_txq *txqs; struct hinic_rxq *rxqs; + + struct hinic_txq_stats tx_stats; + struct hinic_rxq_stats rx_stats; }; #endif diff --git a/drivers/net/ethernet/huawei/hinic/hinic_main.c b/drivers/net/ethernet/huawei/hinic/hinic_main.c index fac0249..21f53fc 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_main.c +++ b/drivers/net/ethernet/huawei/hinic/hinic_main.c @@ -70,6 +70,179 @@ static int change_mac_addr(struct net_device *netdev, const u8 *addr); +static int hinic_get_link_ksettings(struct net_device *netdev, + struct ethtool_link_ksettings + *link_ksettings) +{ + struct hinic_dev *nic_dev = netdev_priv(netdev); + struct hinic_port_cap port_cap; + enum hinic_autoneg_cap autoneg_cap; + enum hinic_autoneg_state autoneg_state; + enum hinic_port_link_state link_state; + int err; + + ethtool_link_ksettings_zero_link_mode(link_ksettings, advertising); + ethtool_link_ksettings_add_link_mode(link_ksettings, supported, + Autoneg); + + link_ksettings->base.speed = SPEED_UNKNOWN; + link_ksettings->base.autoneg = AUTONEG_DISABLE; + link_ksettings->base.duplex = DUPLEX_UNKNOWN; + + err = hinic_port_get_cap(nic_dev, &port_cap); + if (err) { + netif_err(nic_dev, drv, netdev, "Failed to get port capabilities\n"); + return err; + } + + err = hinic_port_link_state(nic_dev, &link_state); + if (err) { + netif_err(nic_dev, drv, netdev, "Failed to get port link state\n"); + return err; + } + + if (link_state != HINIC_LINK_STATE_UP) { + netif_info(nic_dev, drv, netdev, "No link\n"); + return err; + } + + switch (port_cap.speed) { + case HINIC_SPEED_10MB_LINK: + link_ksettings->base.speed = SPEED_10; + break; + + case HINIC_SPEED_100MB_LINK: + link_ksettings->base.speed = SPEED_100; + break; + + case HINIC_SPEED_1000MB_LINK: + link_ksettings->base.speed = SPEED_1000; + break; + + case HINIC_SPEED_10GB_LINK: + link_ksettings->base.speed = SPEED_10000; + break; + + case HINIC_SPEED_25GB_LINK: + link_ksettings->base.speed = SPEED_25000; + break; + + case HINIC_SPEED_40GB_LINK: + link_ksettings->base.speed = SPEED_40000; + break; + + case HINIC_SPEED_100GB_LINK: + link_ksettings->base.speed = SPEED_100000; + break; + + default: + link_ksettings->base.speed = SPEED_UNKNOWN; + break; + } + + autoneg_cap = port_cap.autoneg_cap; + autoneg_state = port_cap.autoneg_state; + + if (!!(autoneg_cap & HINIC_AUTONEG_SUPPORTED)) + ethtool_link_ksettings_add_link_mode(link_ksettings, + advertising, Autoneg); + + link_ksettings->base.autoneg = (autoneg_state == HINIC_AUTONEG_ACTIVE) ? + AUTONEG_ENABLE : AUTONEG_DISABLE; + link_ksettings->base.duplex = (port_cap.duplex == HINIC_DUPLEX_FULL) ? + DUPLEX_FULL : DUPLEX_HALF; + + return 0; +} + +static void hinic_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *info) +{ + struct hinic_dev *nic_dev = netdev_priv(netdev); + struct hinic_hwdev *hwdev = nic_dev->hwdev; + struct hinic_hwif *hwif = hwdev->hwif; + struct pci_dev *pdev = hwif->pdev; + + strlcpy(info->driver, HINIC_DRV_NAME, sizeof(info->driver)); + strlcpy(info->version, HINIC_DRV_VERSION, sizeof(info->driver)); + strlcpy(info->bus_info, pci_name(pdev), sizeof(info->bus_info)); +} + +static void hinic_get_ringparam(struct net_device *netdev, + struct ethtool_ringparam *ring) +{ + ring->rx_max_pending = HINIC_RQ_DEPTH; + ring->tx_max_pending = HINIC_SQ_DEPTH; + ring->rx_pending = HINIC_RQ_DEPTH; + ring->tx_pending = HINIC_SQ_DEPTH; +} + +static void hinic_get_channels(struct net_device *netdev, + struct ethtool_channels *channels) +{ + struct hinic_dev *nic_dev = netdev_priv(netdev); + struct hinic_hwdev *hwdev = nic_dev->hwdev; + + channels->max_rx = hwdev->nic_cap.max_qps; + channels->max_tx = hwdev->nic_cap.max_qps; + channels->max_other = 0; + channels->max_combined = 0; + channels->rx_count = hinic_hwdev_num_qps(hwdev); + channels->tx_count = hinic_hwdev_num_qps(hwdev); + channels->other_count = 0; + channels->combined_count = 0; +} + +static const struct ethtool_ops hinic_ethtool_ops = { + .get_link_ksettings = hinic_get_link_ksettings, + .get_drvinfo = hinic_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_ringparam = hinic_get_ringparam, + .get_channels = hinic_get_channels, +}; + +static void update_nic_stats(struct hinic_dev *nic_dev) +{ + struct hinic_rxq_stats *nic_rx_stats = &nic_dev->rx_stats; + struct hinic_txq_stats *nic_tx_stats = &nic_dev->tx_stats; + struct hinic_hwdev *hwdev = nic_dev->hwdev; + int i, num_qps = hinic_hwdev_num_qps(hwdev); + struct hinic_rxq_stats rx_stats; + struct hinic_txq_stats tx_stats; + + u64_stats_init(&tx_stats.syncp); + u64_stats_init(&rx_stats.syncp); + + for (i = 0; i < num_qps; i++) { + struct hinic_rxq *rxq = &nic_dev->rxqs[i]; + + hinic_rxq_get_stats(rxq, &rx_stats); + + u64_stats_update_begin(&nic_rx_stats->syncp); + nic_rx_stats->bytes += rx_stats.bytes; + nic_rx_stats->pkts += rx_stats.pkts; + u64_stats_update_end(&nic_rx_stats->syncp); + + hinic_rxq_clean_stats(rxq); + } + + for (i = 0; i < num_qps; i++) { + struct hinic_txq *txq = &nic_dev->txqs[i]; + + hinic_txq_get_stats(txq, &tx_stats); + + u64_stats_update_begin(&nic_tx_stats->syncp); + nic_tx_stats->bytes += tx_stats.bytes; + nic_tx_stats->pkts += tx_stats.pkts; + nic_tx_stats->tx_busy += tx_stats.tx_busy; + nic_tx_stats->tx_wake += tx_stats.tx_wake; + nic_tx_stats->tx_dropped += tx_stats.tx_dropped; + u64_stats_update_end(&nic_tx_stats->syncp); + + hinic_txq_clean_stats(txq); + } +} + /** * create_txqs - Create the Logical Tx Queues of specific NIC device * @nic_dev: the specific NIC device @@ -300,6 +473,8 @@ static int hinic_close(struct net_device *netdev) netif_carrier_off(netdev); netif_tx_disable(netdev); + update_nic_stats(nic_dev); + up(&nic_dev->mgmt_lock); err = hinic_port_set_func_state(nic_dev, HINIC_FUNC_PORT_DISABLE); @@ -580,6 +755,77 @@ static void hinic_set_rx_mode(struct net_device *netdev) queue_work(nic_dev->workq, &rx_mode_work->work); } +static u16 hinic_select_queue(struct net_device *netdev, struct sk_buff *skb, + void *accel_priv, + select_queue_fallback_t fallback) +{ + u16 qid; + + if (skb_rx_queue_recorded(skb)) + qid = skb_get_rx_queue(skb); + else + qid = fallback(netdev, skb); + + return qid; +} + +static void hinic_get_stats64(struct net_device *netdev, + struct rtnl_link_stats64 *stats) +{ + struct hinic_dev *nic_dev = netdev_priv(netdev); + struct hinic_rxq_stats *nic_rx_stats = &nic_dev->rx_stats; + struct hinic_txq_stats *nic_tx_stats = &nic_dev->tx_stats; + + stats->rx_bytes = nic_rx_stats->bytes; + stats->rx_packets = nic_rx_stats->pkts; + + stats->tx_bytes = nic_tx_stats->bytes; + stats->tx_packets = nic_tx_stats->pkts; + stats->tx_errors = nic_tx_stats->tx_dropped; + + down(&nic_dev->mgmt_lock); + + if (!(nic_dev->flags & HINIC_INTF_UP)) { + up(&nic_dev->mgmt_lock); + return; + } + + update_nic_stats(nic_dev); + + up(&nic_dev->mgmt_lock); + + stats->rx_bytes = nic_rx_stats->bytes; + stats->rx_packets = nic_rx_stats->pkts; + + stats->tx_bytes = nic_tx_stats->bytes; + stats->tx_packets = nic_tx_stats->pkts; + stats->tx_errors = nic_tx_stats->tx_dropped; +} + +static void hinic_tx_timeout(struct net_device *netdev) +{ + struct hinic_dev *nic_dev = netdev_priv(netdev); + + netif_err(nic_dev, drv, netdev, "Tx timeout\n"); +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void hinic_netpoll(struct net_device *netdev) +{ + struct hinic_dev *nic_dev = netdev_priv(netdev); + struct hinic_hwdev *hwdev = nic_dev->hwdev; + int i, num_qps = hinic_hwdev_num_qps(hwdev); + + for (i = 0; i < num_qps; i++) { + struct hinic_txq *txq = &nic_dev->txqs[i]; + struct hinic_rxq *rxq = &nic_dev->rxqs[i]; + + napi_schedule(&txq->napi); + napi_schedule(&rxq->napi); + } +} +#endif + static const struct net_device_ops hinic_netdev_ops = { .ndo_open = hinic_open, .ndo_stop = hinic_close, @@ -590,7 +836,12 @@ static void hinic_set_rx_mode(struct net_device *netdev) .ndo_vlan_rx_kill_vid = hinic_vlan_rx_kill_vid, .ndo_set_rx_mode = hinic_set_rx_mode, .ndo_start_xmit = hinic_xmit_frame, - /* more operations should be filled */ + .ndo_select_queue = hinic_select_queue, + .ndo_get_stats64 = hinic_get_stats64, + .ndo_tx_timeout = hinic_tx_timeout, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = hinic_netpoll, +#endif }; static void netdev_features_init(struct net_device *netdev) @@ -665,6 +916,8 @@ static int nic_dev_init(struct pci_dev *pdev) struct hinic_dev *nic_dev; struct net_device *netdev; struct hinic_hwdev *hwdev; + struct hinic_txq_stats *tx_stats; + struct hinic_rxq_stats *rx_stats; struct hinic_rx_mode_work *rx_mode_work; int err, num_qps; @@ -689,6 +942,7 @@ static int nic_dev_init(struct pci_dev *pdev) } netdev->netdev_ops = &hinic_netdev_ops; + netdev->ethtool_ops = &hinic_ethtool_ops; nic_dev = (struct hinic_dev *)netdev_priv(netdev); nic_dev->hwdev = hwdev; @@ -702,6 +956,12 @@ static int nic_dev_init(struct pci_dev *pdev) sema_init(&nic_dev->mgmt_lock, 1); + tx_stats = &nic_dev->tx_stats; + rx_stats = &nic_dev->rx_stats; + + u64_stats_init(&tx_stats->syncp); + u64_stats_init(&rx_stats->syncp); + nic_dev->vlan_bitmap = kzalloc(VLAN_BITMAP_SIZE(nic_dev), GFP_KERNEL); if (!nic_dev->vlan_bitmap) { err = -ENOMEM; diff --git a/drivers/net/ethernet/huawei/hinic/hinic_port.c b/drivers/net/ethernet/huawei/hinic/hinic_port.c index 2c97ece..8da5762 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_port.c +++ b/drivers/net/ethernet/huawei/hinic/hinic_port.c @@ -372,3 +372,32 @@ int hinic_port_set_func_state(struct hinic_dev *nic_dev, return 0; } + +/** + * hinic_port_get_cap - get port capabilities + * @nic_dev: nic device + * @port_cap: returned port capabilities + * + * Return 0 - Success, negative - Failure + **/ +int hinic_port_get_cap(struct hinic_dev *nic_dev, + struct hinic_port_cap *port_cap) +{ + struct hinic_hwdev *hwdev = nic_dev->hwdev; + struct hinic_hwif *hwif = hwdev->hwif; + struct pci_dev *pdev = hwif->pdev; + u16 out_size; + int err; + + port_cap->func_idx = HINIC_HWIF_GLOB_IDX(hwif); + + err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_CAP, + port_cap, sizeof(*port_cap), + port_cap, &out_size); + if (err || (out_size != sizeof(*port_cap)) || port_cap->status) { + dev_err(&pdev->dev, "Failed to get port capabilities\n"); + return -EINVAL; + } + + return 0; +} diff --git a/drivers/net/ethernet/huawei/hinic/hinic_port.h b/drivers/net/ethernet/huawei/hinic/hinic_port.h index 754bac5..15095ab 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_port.h +++ b/drivers/net/ethernet/huawei/hinic/hinic_port.h @@ -45,6 +45,33 @@ enum hinic_func_port_state { HINIC_FUNC_PORT_ENABLE = 2, }; +enum hinic_autoneg_cap { + HINIC_AUTONEG_UNSUPPORTED, + HINIC_AUTONEG_SUPPORTED, +}; + +enum hinic_autoneg_state { + HINIC_AUTONEG_DISABLED, + HINIC_AUTONEG_ACTIVE, +}; + +enum hinic_duplex { + HINIC_DUPLEX_HALF, + HINIC_DUPLEX_FULL, +}; + +enum hinic_speed { + HINIC_SPEED_10MB_LINK = 0, + HINIC_SPEED_100MB_LINK, + HINIC_SPEED_1000MB_LINK, + HINIC_SPEED_10GB_LINK, + HINIC_SPEED_25GB_LINK, + HINIC_SPEED_40GB_LINK, + HINIC_SPEED_100GB_LINK, + + HINIC_SPEED_UNKNOWN = 0xFF, +}; + struct hinic_port_mac_cmd { u8 status; u8 version; @@ -125,6 +152,21 @@ struct hinic_port_func_state_cmd { u8 rsvd2[3]; }; +struct hinic_port_cap { + u8 status; + u8 version; + u8 rsvd0[6]; + + u16 func_idx; + u16 rsvd1; + u8 port_type; + u8 autoneg_cap; + u8 autoneg_state; + u8 duplex; + u8 speed; + u8 resv2[3]; +}; + int hinic_port_add_mac(struct hinic_dev *nic_dev, const u8 *addr, u16 vlan_id); @@ -150,4 +192,7 @@ int hinic_port_set_state(struct hinic_dev *nic_dev, int hinic_port_set_func_state(struct hinic_dev *nic_dev, enum hinic_func_port_state state); +int hinic_port_get_cap(struct hinic_dev *nic_dev, + struct hinic_port_cap *port_cap); + #endif diff --git a/drivers/net/ethernet/huawei/hinic/hinic_rx.c b/drivers/net/ethernet/huawei/hinic/hinic_rx.c index 85eb989..7396d57 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_rx.c +++ b/drivers/net/ethernet/huawei/hinic/hinic_rx.c @@ -59,6 +59,25 @@ void hinic_rxq_clean_stats(struct hinic_rxq *rxq) } /** + * hinic_rxq_get_stats - get statistics of Rx Queue + * @rxq: Logical Rx Queue + * @stats: return updated stats here + **/ +void hinic_rxq_get_stats(struct hinic_rxq *rxq, struct hinic_rxq_stats *stats) +{ + struct hinic_rxq_stats *rxq_stats = &rxq->rxq_stats; + unsigned int start; + + u64_stats_update_begin(&stats->syncp); + do { + start = u64_stats_fetch_begin(&rxq_stats->syncp); + stats->pkts = rxq_stats->pkts; + stats->bytes = rxq_stats->bytes; + } while (u64_stats_fetch_retry(&rxq_stats->syncp, start)); + u64_stats_update_end(&stats->syncp); +} + +/** * rxq_stats_init - Initialize the statistics of specific queue * @rxq: Logical Rx Queue **/ diff --git a/drivers/net/ethernet/huawei/hinic/hinic_rx.h b/drivers/net/ethernet/huawei/hinic/hinic_rx.h index 17c4842..6215528 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_rx.h +++ b/drivers/net/ethernet/huawei/hinic/hinic_rx.h @@ -45,6 +45,8 @@ struct hinic_rxq { void hinic_rxq_clean_stats(struct hinic_rxq *rxq); +void hinic_rxq_get_stats(struct hinic_rxq *rxq, struct hinic_rxq_stats *stats); + int hinic_init_rxq(struct hinic_rxq *rxq, struct hinic_rq *rq, struct net_device *netdev); diff --git a/drivers/net/ethernet/huawei/hinic/hinic_tx.c b/drivers/net/ethernet/huawei/hinic/hinic_tx.c index 62051c4..7642150 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_tx.c +++ b/drivers/net/ethernet/huawei/hinic/hinic_tx.c @@ -68,6 +68,28 @@ void hinic_txq_clean_stats(struct hinic_txq *txq) } /** + * hinic_txq_get_stats - get statistics of Tx Queue + * @txq: Logical Tx Queue + * @stats: return updated stats here + **/ +void hinic_txq_get_stats(struct hinic_txq *txq, struct hinic_txq_stats *stats) +{ + struct hinic_txq_stats *txq_stats = &txq->txq_stats; + unsigned int start; + + u64_stats_update_begin(&stats->syncp); + do { + start = u64_stats_fetch_begin(&txq_stats->syncp); + stats->pkts = txq_stats->pkts; + stats->bytes = txq_stats->bytes; + stats->tx_busy = txq_stats->tx_busy; + stats->tx_wake = txq_stats->tx_wake; + stats->tx_dropped = txq_stats->tx_dropped; + } while (u64_stats_fetch_retry(&txq_stats->syncp, start)); + u64_stats_update_end(&stats->syncp); +} + +/** * txq_stats_init - Initialize the statistics of specific queue * @txq: Logical Tx Queue **/ diff --git a/drivers/net/ethernet/huawei/hinic/hinic_tx.h b/drivers/net/ethernet/huawei/hinic/hinic_tx.h index 9b5a665..b5d580c 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_tx.h +++ b/drivers/net/ethernet/huawei/hinic/hinic_tx.h @@ -50,6 +50,8 @@ struct hinic_txq { void hinic_txq_clean_stats(struct hinic_txq *txq); +void hinic_txq_get_stats(struct hinic_txq *txq, struct hinic_txq_stats *stats); + netdev_tx_t hinic_xmit_frame(struct sk_buff *skb, struct net_device *netdev); int hinic_init_txq(struct hinic_txq *txq, struct hinic_sq *sq, -- 1.9.1