162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* sunvnet.c: Sun LDOM Virtual Network Driver.
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2007, 2008 David S. Miller <davem@davemloft.net>
562306a36Sopenharmony_ci * Copyright (C) 2016-2017 Oracle. All rights reserved.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/types.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/delay.h>
1562306a36Sopenharmony_ci#include <linux/init.h>
1662306a36Sopenharmony_ci#include <linux/netdevice.h>
1762306a36Sopenharmony_ci#include <linux/ethtool.h>
1862306a36Sopenharmony_ci#include <linux/etherdevice.h>
1962306a36Sopenharmony_ci#include <linux/mutex.h>
2062306a36Sopenharmony_ci#include <linux/highmem.h>
2162306a36Sopenharmony_ci#include <linux/if_vlan.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
2462306a36Sopenharmony_ci#include <linux/icmpv6.h>
2562306a36Sopenharmony_ci#endif
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#include <net/ip.h>
2862306a36Sopenharmony_ci#include <net/icmp.h>
2962306a36Sopenharmony_ci#include <net/route.h>
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#include <asm/vio.h>
3262306a36Sopenharmony_ci#include <asm/ldc.h>
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#include "sunvnet_common.h"
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/* length of time before we decide the hardware is borked,
3762306a36Sopenharmony_ci * and dev->tx_timeout() should be called to fix the problem
3862306a36Sopenharmony_ci */
3962306a36Sopenharmony_ci#define VNET_TX_TIMEOUT			(5 * HZ)
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#define DRV_MODULE_NAME		"sunvnet"
4262306a36Sopenharmony_ci#define DRV_MODULE_VERSION	"2.0"
4362306a36Sopenharmony_ci#define DRV_MODULE_RELDATE	"February 3, 2017"
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic char version[] =
4662306a36Sopenharmony_ci	DRV_MODULE_NAME " " DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")";
4762306a36Sopenharmony_ciMODULE_AUTHOR("David S. Miller (davem@davemloft.net)");
4862306a36Sopenharmony_ciMODULE_DESCRIPTION("Sun LDOM virtual network driver");
4962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
5062306a36Sopenharmony_ciMODULE_VERSION(DRV_MODULE_VERSION);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci/* Ordered from largest major to lowest */
5362306a36Sopenharmony_cistatic struct vio_version vnet_versions[] = {
5462306a36Sopenharmony_ci	{ .major = 1, .minor = 8 },
5562306a36Sopenharmony_ci	{ .major = 1, .minor = 7 },
5662306a36Sopenharmony_ci	{ .major = 1, .minor = 6 },
5762306a36Sopenharmony_ci	{ .major = 1, .minor = 0 },
5862306a36Sopenharmony_ci};
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic void vnet_get_drvinfo(struct net_device *dev,
6162306a36Sopenharmony_ci			     struct ethtool_drvinfo *info)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	strscpy(info->driver, DRV_MODULE_NAME, sizeof(info->driver));
6462306a36Sopenharmony_ci	strscpy(info->version, DRV_MODULE_VERSION, sizeof(info->version));
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic u32 vnet_get_msglevel(struct net_device *dev)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	struct vnet *vp = netdev_priv(dev);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	return vp->msg_enable;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic void vnet_set_msglevel(struct net_device *dev, u32 value)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	struct vnet *vp = netdev_priv(dev);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	vp->msg_enable = value;
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic const struct {
8262306a36Sopenharmony_ci	const char string[ETH_GSTRING_LEN];
8362306a36Sopenharmony_ci} ethtool_stats_keys[] = {
8462306a36Sopenharmony_ci	{ "rx_packets" },
8562306a36Sopenharmony_ci	{ "tx_packets" },
8662306a36Sopenharmony_ci	{ "rx_bytes" },
8762306a36Sopenharmony_ci	{ "tx_bytes" },
8862306a36Sopenharmony_ci	{ "rx_errors" },
8962306a36Sopenharmony_ci	{ "tx_errors" },
9062306a36Sopenharmony_ci	{ "rx_dropped" },
9162306a36Sopenharmony_ci	{ "tx_dropped" },
9262306a36Sopenharmony_ci	{ "multicast" },
9362306a36Sopenharmony_ci	{ "rx_length_errors" },
9462306a36Sopenharmony_ci	{ "rx_frame_errors" },
9562306a36Sopenharmony_ci	{ "rx_missed_errors" },
9662306a36Sopenharmony_ci	{ "tx_carrier_errors" },
9762306a36Sopenharmony_ci	{ "nports" },
9862306a36Sopenharmony_ci};
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic int vnet_get_sset_count(struct net_device *dev, int sset)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	struct vnet *vp = (struct vnet *)netdev_priv(dev);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	switch (sset) {
10562306a36Sopenharmony_ci	case ETH_SS_STATS:
10662306a36Sopenharmony_ci		return ARRAY_SIZE(ethtool_stats_keys)
10762306a36Sopenharmony_ci			+ (NUM_VNET_PORT_STATS * vp->nports);
10862306a36Sopenharmony_ci	default:
10962306a36Sopenharmony_ci		return -EOPNOTSUPP;
11062306a36Sopenharmony_ci	}
11162306a36Sopenharmony_ci}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic void vnet_get_strings(struct net_device *dev, u32 stringset, u8 *buf)
11462306a36Sopenharmony_ci{
11562306a36Sopenharmony_ci	struct vnet *vp = (struct vnet *)netdev_priv(dev);
11662306a36Sopenharmony_ci	struct vnet_port *port;
11762306a36Sopenharmony_ci	char *p = (char *)buf;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	switch (stringset) {
12062306a36Sopenharmony_ci	case ETH_SS_STATS:
12162306a36Sopenharmony_ci		memcpy(buf, &ethtool_stats_keys, sizeof(ethtool_stats_keys));
12262306a36Sopenharmony_ci		p += sizeof(ethtool_stats_keys);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci		rcu_read_lock();
12562306a36Sopenharmony_ci		list_for_each_entry_rcu(port, &vp->port_list, list) {
12662306a36Sopenharmony_ci			snprintf(p, ETH_GSTRING_LEN, "p%u.%s-%pM",
12762306a36Sopenharmony_ci				 port->q_index, port->switch_port ? "s" : "q",
12862306a36Sopenharmony_ci				 port->raddr);
12962306a36Sopenharmony_ci			p += ETH_GSTRING_LEN;
13062306a36Sopenharmony_ci			snprintf(p, ETH_GSTRING_LEN, "p%u.rx_packets",
13162306a36Sopenharmony_ci				 port->q_index);
13262306a36Sopenharmony_ci			p += ETH_GSTRING_LEN;
13362306a36Sopenharmony_ci			snprintf(p, ETH_GSTRING_LEN, "p%u.tx_packets",
13462306a36Sopenharmony_ci				 port->q_index);
13562306a36Sopenharmony_ci			p += ETH_GSTRING_LEN;
13662306a36Sopenharmony_ci			snprintf(p, ETH_GSTRING_LEN, "p%u.rx_bytes",
13762306a36Sopenharmony_ci				 port->q_index);
13862306a36Sopenharmony_ci			p += ETH_GSTRING_LEN;
13962306a36Sopenharmony_ci			snprintf(p, ETH_GSTRING_LEN, "p%u.tx_bytes",
14062306a36Sopenharmony_ci				 port->q_index);
14162306a36Sopenharmony_ci			p += ETH_GSTRING_LEN;
14262306a36Sopenharmony_ci			snprintf(p, ETH_GSTRING_LEN, "p%u.event_up",
14362306a36Sopenharmony_ci				 port->q_index);
14462306a36Sopenharmony_ci			p += ETH_GSTRING_LEN;
14562306a36Sopenharmony_ci			snprintf(p, ETH_GSTRING_LEN, "p%u.event_reset",
14662306a36Sopenharmony_ci				 port->q_index);
14762306a36Sopenharmony_ci			p += ETH_GSTRING_LEN;
14862306a36Sopenharmony_ci		}
14962306a36Sopenharmony_ci		rcu_read_unlock();
15062306a36Sopenharmony_ci		break;
15162306a36Sopenharmony_ci	default:
15262306a36Sopenharmony_ci		WARN_ON(1);
15362306a36Sopenharmony_ci		break;
15462306a36Sopenharmony_ci	}
15562306a36Sopenharmony_ci}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic void vnet_get_ethtool_stats(struct net_device *dev,
15862306a36Sopenharmony_ci				   struct ethtool_stats *estats, u64 *data)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	struct vnet *vp = (struct vnet *)netdev_priv(dev);
16162306a36Sopenharmony_ci	struct vnet_port *port;
16262306a36Sopenharmony_ci	int i = 0;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	data[i++] = dev->stats.rx_packets;
16562306a36Sopenharmony_ci	data[i++] = dev->stats.tx_packets;
16662306a36Sopenharmony_ci	data[i++] = dev->stats.rx_bytes;
16762306a36Sopenharmony_ci	data[i++] = dev->stats.tx_bytes;
16862306a36Sopenharmony_ci	data[i++] = dev->stats.rx_errors;
16962306a36Sopenharmony_ci	data[i++] = dev->stats.tx_errors;
17062306a36Sopenharmony_ci	data[i++] = dev->stats.rx_dropped;
17162306a36Sopenharmony_ci	data[i++] = dev->stats.tx_dropped;
17262306a36Sopenharmony_ci	data[i++] = dev->stats.multicast;
17362306a36Sopenharmony_ci	data[i++] = dev->stats.rx_length_errors;
17462306a36Sopenharmony_ci	data[i++] = dev->stats.rx_frame_errors;
17562306a36Sopenharmony_ci	data[i++] = dev->stats.rx_missed_errors;
17662306a36Sopenharmony_ci	data[i++] = dev->stats.tx_carrier_errors;
17762306a36Sopenharmony_ci	data[i++] = vp->nports;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	rcu_read_lock();
18062306a36Sopenharmony_ci	list_for_each_entry_rcu(port, &vp->port_list, list) {
18162306a36Sopenharmony_ci		data[i++] = port->q_index;
18262306a36Sopenharmony_ci		data[i++] = port->stats.rx_packets;
18362306a36Sopenharmony_ci		data[i++] = port->stats.tx_packets;
18462306a36Sopenharmony_ci		data[i++] = port->stats.rx_bytes;
18562306a36Sopenharmony_ci		data[i++] = port->stats.tx_bytes;
18662306a36Sopenharmony_ci		data[i++] = port->stats.event_up;
18762306a36Sopenharmony_ci		data[i++] = port->stats.event_reset;
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci	rcu_read_unlock();
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic const struct ethtool_ops vnet_ethtool_ops = {
19362306a36Sopenharmony_ci	.get_drvinfo		= vnet_get_drvinfo,
19462306a36Sopenharmony_ci	.get_msglevel		= vnet_get_msglevel,
19562306a36Sopenharmony_ci	.set_msglevel		= vnet_set_msglevel,
19662306a36Sopenharmony_ci	.get_link		= ethtool_op_get_link,
19762306a36Sopenharmony_ci	.get_sset_count		= vnet_get_sset_count,
19862306a36Sopenharmony_ci	.get_strings		= vnet_get_strings,
19962306a36Sopenharmony_ci	.get_ethtool_stats	= vnet_get_ethtool_stats,
20062306a36Sopenharmony_ci};
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_cistatic LIST_HEAD(vnet_list);
20362306a36Sopenharmony_cistatic DEFINE_MUTEX(vnet_list_mutex);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_cistatic struct vnet_port *__tx_port_find(struct vnet *vp, struct sk_buff *skb)
20662306a36Sopenharmony_ci{
20762306a36Sopenharmony_ci	unsigned int hash = vnet_hashfn(skb->data);
20862306a36Sopenharmony_ci	struct hlist_head *hp = &vp->port_hash[hash];
20962306a36Sopenharmony_ci	struct vnet_port *port;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	hlist_for_each_entry_rcu(port, hp, hash) {
21262306a36Sopenharmony_ci		if (!sunvnet_port_is_up_common(port))
21362306a36Sopenharmony_ci			continue;
21462306a36Sopenharmony_ci		if (ether_addr_equal(port->raddr, skb->data))
21562306a36Sopenharmony_ci			return port;
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci	list_for_each_entry_rcu(port, &vp->port_list, list) {
21862306a36Sopenharmony_ci		if (!port->switch_port)
21962306a36Sopenharmony_ci			continue;
22062306a36Sopenharmony_ci		if (!sunvnet_port_is_up_common(port))
22162306a36Sopenharmony_ci			continue;
22262306a36Sopenharmony_ci		return port;
22362306a36Sopenharmony_ci	}
22462306a36Sopenharmony_ci	return NULL;
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci/* func arg to vnet_start_xmit_common() to get the proper tx port */
22862306a36Sopenharmony_cistatic struct vnet_port *vnet_tx_port_find(struct sk_buff *skb,
22962306a36Sopenharmony_ci					   struct net_device *dev)
23062306a36Sopenharmony_ci{
23162306a36Sopenharmony_ci	struct vnet *vp = netdev_priv(dev);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	return __tx_port_find(vp, skb);
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic u16 vnet_select_queue(struct net_device *dev, struct sk_buff *skb,
23762306a36Sopenharmony_ci			     struct net_device *sb_dev)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	struct vnet *vp = netdev_priv(dev);
24062306a36Sopenharmony_ci	struct vnet_port *port = __tx_port_find(vp, skb);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	if (!port)
24362306a36Sopenharmony_ci		return 0;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	return port->q_index;
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci/* Wrappers to common functions */
24962306a36Sopenharmony_cistatic netdev_tx_t vnet_start_xmit(struct sk_buff *skb, struct net_device *dev)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	return sunvnet_start_xmit_common(skb, dev, vnet_tx_port_find);
25262306a36Sopenharmony_ci}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_cistatic void vnet_set_rx_mode(struct net_device *dev)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	struct vnet *vp = netdev_priv(dev);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	return sunvnet_set_rx_mode_common(dev, vp);
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci#ifdef CONFIG_NET_POLL_CONTROLLER
26262306a36Sopenharmony_cistatic void vnet_poll_controller(struct net_device *dev)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	struct vnet *vp = netdev_priv(dev);
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	return sunvnet_poll_controller_common(dev, vp);
26762306a36Sopenharmony_ci}
26862306a36Sopenharmony_ci#endif
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic const struct net_device_ops vnet_ops = {
27162306a36Sopenharmony_ci	.ndo_open		= sunvnet_open_common,
27262306a36Sopenharmony_ci	.ndo_stop		= sunvnet_close_common,
27362306a36Sopenharmony_ci	.ndo_set_rx_mode	= vnet_set_rx_mode,
27462306a36Sopenharmony_ci	.ndo_set_mac_address	= sunvnet_set_mac_addr_common,
27562306a36Sopenharmony_ci	.ndo_validate_addr	= eth_validate_addr,
27662306a36Sopenharmony_ci	.ndo_tx_timeout		= sunvnet_tx_timeout_common,
27762306a36Sopenharmony_ci	.ndo_start_xmit		= vnet_start_xmit,
27862306a36Sopenharmony_ci	.ndo_select_queue	= vnet_select_queue,
27962306a36Sopenharmony_ci#ifdef CONFIG_NET_POLL_CONTROLLER
28062306a36Sopenharmony_ci	.ndo_poll_controller	= vnet_poll_controller,
28162306a36Sopenharmony_ci#endif
28262306a36Sopenharmony_ci};
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_cistatic struct vnet *vnet_new(const u64 *local_mac,
28562306a36Sopenharmony_ci			     struct vio_dev *vdev)
28662306a36Sopenharmony_ci{
28762306a36Sopenharmony_ci	struct net_device *dev;
28862306a36Sopenharmony_ci	u8 addr[ETH_ALEN];
28962306a36Sopenharmony_ci	struct vnet *vp;
29062306a36Sopenharmony_ci	int err, i;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	dev = alloc_etherdev_mqs(sizeof(*vp), VNET_MAX_TXQS, 1);
29362306a36Sopenharmony_ci	if (!dev)
29462306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
29562306a36Sopenharmony_ci	dev->needed_headroom = VNET_PACKET_SKIP + 8;
29662306a36Sopenharmony_ci	dev->needed_tailroom = 8;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	for (i = 0; i < ETH_ALEN; i++)
29962306a36Sopenharmony_ci		addr[i] = (*local_mac >> (5 - i) * 8) & 0xff;
30062306a36Sopenharmony_ci	eth_hw_addr_set(dev, addr);
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	vp = netdev_priv(dev);
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	spin_lock_init(&vp->lock);
30562306a36Sopenharmony_ci	vp->dev = dev;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	INIT_LIST_HEAD(&vp->port_list);
30862306a36Sopenharmony_ci	for (i = 0; i < VNET_PORT_HASH_SIZE; i++)
30962306a36Sopenharmony_ci		INIT_HLIST_HEAD(&vp->port_hash[i]);
31062306a36Sopenharmony_ci	INIT_LIST_HEAD(&vp->list);
31162306a36Sopenharmony_ci	vp->local_mac = *local_mac;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	dev->netdev_ops = &vnet_ops;
31462306a36Sopenharmony_ci	dev->ethtool_ops = &vnet_ethtool_ops;
31562306a36Sopenharmony_ci	dev->watchdog_timeo = VNET_TX_TIMEOUT;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	dev->hw_features = NETIF_F_TSO | NETIF_F_GSO | NETIF_F_ALL_TSO |
31862306a36Sopenharmony_ci			   NETIF_F_HW_CSUM | NETIF_F_SG;
31962306a36Sopenharmony_ci	dev->features = dev->hw_features;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	/* MTU range: 68 - 65535 */
32262306a36Sopenharmony_ci	dev->min_mtu = ETH_MIN_MTU;
32362306a36Sopenharmony_ci	dev->max_mtu = VNET_MAX_MTU;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	SET_NETDEV_DEV(dev, &vdev->dev);
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	err = register_netdev(dev);
32862306a36Sopenharmony_ci	if (err) {
32962306a36Sopenharmony_ci		pr_err("Cannot register net device, aborting\n");
33062306a36Sopenharmony_ci		goto err_out_free_dev;
33162306a36Sopenharmony_ci	}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	netdev_info(dev, "Sun LDOM vnet %pM\n", dev->dev_addr);
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	list_add(&vp->list, &vnet_list);
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci	return vp;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_cierr_out_free_dev:
34062306a36Sopenharmony_ci	free_netdev(dev);
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	return ERR_PTR(err);
34362306a36Sopenharmony_ci}
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_cistatic struct vnet *vnet_find_or_create(const u64 *local_mac,
34662306a36Sopenharmony_ci					struct vio_dev *vdev)
34762306a36Sopenharmony_ci{
34862306a36Sopenharmony_ci	struct vnet *iter, *vp;
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	mutex_lock(&vnet_list_mutex);
35162306a36Sopenharmony_ci	vp = NULL;
35262306a36Sopenharmony_ci	list_for_each_entry(iter, &vnet_list, list) {
35362306a36Sopenharmony_ci		if (iter->local_mac == *local_mac) {
35462306a36Sopenharmony_ci			vp = iter;
35562306a36Sopenharmony_ci			break;
35662306a36Sopenharmony_ci		}
35762306a36Sopenharmony_ci	}
35862306a36Sopenharmony_ci	if (!vp)
35962306a36Sopenharmony_ci		vp = vnet_new(local_mac, vdev);
36062306a36Sopenharmony_ci	mutex_unlock(&vnet_list_mutex);
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	return vp;
36362306a36Sopenharmony_ci}
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_cistatic void vnet_cleanup(void)
36662306a36Sopenharmony_ci{
36762306a36Sopenharmony_ci	struct vnet *vp;
36862306a36Sopenharmony_ci	struct net_device *dev;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	mutex_lock(&vnet_list_mutex);
37162306a36Sopenharmony_ci	while (!list_empty(&vnet_list)) {
37262306a36Sopenharmony_ci		vp = list_first_entry(&vnet_list, struct vnet, list);
37362306a36Sopenharmony_ci		list_del(&vp->list);
37462306a36Sopenharmony_ci		dev = vp->dev;
37562306a36Sopenharmony_ci		/* vio_unregister_driver() should have cleaned up port_list */
37662306a36Sopenharmony_ci		BUG_ON(!list_empty(&vp->port_list));
37762306a36Sopenharmony_ci		unregister_netdev(dev);
37862306a36Sopenharmony_ci		free_netdev(dev);
37962306a36Sopenharmony_ci	}
38062306a36Sopenharmony_ci	mutex_unlock(&vnet_list_mutex);
38162306a36Sopenharmony_ci}
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_cistatic const char *local_mac_prop = "local-mac-address";
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_cistatic struct vnet *vnet_find_parent(struct mdesc_handle *hp,
38662306a36Sopenharmony_ci				     u64 port_node,
38762306a36Sopenharmony_ci				     struct vio_dev *vdev)
38862306a36Sopenharmony_ci{
38962306a36Sopenharmony_ci	const u64 *local_mac = NULL;
39062306a36Sopenharmony_ci	u64 a;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	mdesc_for_each_arc(a, hp, port_node, MDESC_ARC_TYPE_BACK) {
39362306a36Sopenharmony_ci		u64 target = mdesc_arc_target(hp, a);
39462306a36Sopenharmony_ci		const char *name;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci		name = mdesc_get_property(hp, target, "name", NULL);
39762306a36Sopenharmony_ci		if (!name || strcmp(name, "network"))
39862306a36Sopenharmony_ci			continue;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci		local_mac = mdesc_get_property(hp, target,
40162306a36Sopenharmony_ci					       local_mac_prop, NULL);
40262306a36Sopenharmony_ci		if (local_mac)
40362306a36Sopenharmony_ci			break;
40462306a36Sopenharmony_ci	}
40562306a36Sopenharmony_ci	if (!local_mac)
40662306a36Sopenharmony_ci		return ERR_PTR(-ENODEV);
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	return vnet_find_or_create(local_mac, vdev);
40962306a36Sopenharmony_ci}
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_cistatic struct ldc_channel_config vnet_ldc_cfg = {
41262306a36Sopenharmony_ci	.event		= sunvnet_event_common,
41362306a36Sopenharmony_ci	.mtu		= 64,
41462306a36Sopenharmony_ci	.mode		= LDC_MODE_UNRELIABLE,
41562306a36Sopenharmony_ci};
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_cistatic struct vio_driver_ops vnet_vio_ops = {
41862306a36Sopenharmony_ci	.send_attr		= sunvnet_send_attr_common,
41962306a36Sopenharmony_ci	.handle_attr		= sunvnet_handle_attr_common,
42062306a36Sopenharmony_ci	.handshake_complete	= sunvnet_handshake_complete_common,
42162306a36Sopenharmony_ci};
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ciconst char *remote_macaddr_prop = "remote-mac-address";
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_cistatic int vnet_port_probe(struct vio_dev *vdev, const struct vio_device_id *id)
42662306a36Sopenharmony_ci{
42762306a36Sopenharmony_ci	struct mdesc_handle *hp;
42862306a36Sopenharmony_ci	struct vnet_port *port;
42962306a36Sopenharmony_ci	unsigned long flags;
43062306a36Sopenharmony_ci	struct vnet *vp;
43162306a36Sopenharmony_ci	const u64 *rmac;
43262306a36Sopenharmony_ci	int len, i, err, switch_port;
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	hp = mdesc_grab();
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	if (!hp)
43762306a36Sopenharmony_ci		return -ENODEV;
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	vp = vnet_find_parent(hp, vdev->mp, vdev);
44062306a36Sopenharmony_ci	if (IS_ERR(vp)) {
44162306a36Sopenharmony_ci		pr_err("Cannot find port parent vnet\n");
44262306a36Sopenharmony_ci		err = PTR_ERR(vp);
44362306a36Sopenharmony_ci		goto err_out_put_mdesc;
44462306a36Sopenharmony_ci	}
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	rmac = mdesc_get_property(hp, vdev->mp, remote_macaddr_prop, &len);
44762306a36Sopenharmony_ci	err = -ENODEV;
44862306a36Sopenharmony_ci	if (!rmac) {
44962306a36Sopenharmony_ci		pr_err("Port lacks %s property\n", remote_macaddr_prop);
45062306a36Sopenharmony_ci		goto err_out_put_mdesc;
45162306a36Sopenharmony_ci	}
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	port = kzalloc(sizeof(*port), GFP_KERNEL);
45462306a36Sopenharmony_ci	err = -ENOMEM;
45562306a36Sopenharmony_ci	if (!port)
45662306a36Sopenharmony_ci		goto err_out_put_mdesc;
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	for (i = 0; i < ETH_ALEN; i++)
45962306a36Sopenharmony_ci		port->raddr[i] = (*rmac >> (5 - i) * 8) & 0xff;
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	port->vp = vp;
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	err = vio_driver_init(&port->vio, vdev, VDEV_NETWORK,
46462306a36Sopenharmony_ci			      vnet_versions, ARRAY_SIZE(vnet_versions),
46562306a36Sopenharmony_ci			      &vnet_vio_ops, vp->dev->name);
46662306a36Sopenharmony_ci	if (err)
46762306a36Sopenharmony_ci		goto err_out_free_port;
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	err = vio_ldc_alloc(&port->vio, &vnet_ldc_cfg, port);
47062306a36Sopenharmony_ci	if (err)
47162306a36Sopenharmony_ci		goto err_out_free_port;
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	netif_napi_add(port->vp->dev, &port->napi, sunvnet_poll_common);
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	INIT_HLIST_NODE(&port->hash);
47662306a36Sopenharmony_ci	INIT_LIST_HEAD(&port->list);
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	switch_port = 0;
47962306a36Sopenharmony_ci	if (mdesc_get_property(hp, vdev->mp, "switch-port", NULL))
48062306a36Sopenharmony_ci		switch_port = 1;
48162306a36Sopenharmony_ci	port->switch_port = switch_port;
48262306a36Sopenharmony_ci	port->tso = true;
48362306a36Sopenharmony_ci	port->tsolen = 0;
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	spin_lock_irqsave(&vp->lock, flags);
48662306a36Sopenharmony_ci	if (switch_port)
48762306a36Sopenharmony_ci		list_add_rcu(&port->list, &vp->port_list);
48862306a36Sopenharmony_ci	else
48962306a36Sopenharmony_ci		list_add_tail_rcu(&port->list, &vp->port_list);
49062306a36Sopenharmony_ci	hlist_add_head_rcu(&port->hash,
49162306a36Sopenharmony_ci			   &vp->port_hash[vnet_hashfn(port->raddr)]);
49262306a36Sopenharmony_ci	sunvnet_port_add_txq_common(port);
49362306a36Sopenharmony_ci	spin_unlock_irqrestore(&vp->lock, flags);
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	dev_set_drvdata(&vdev->dev, port);
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	pr_info("%s: PORT ( remote-mac %pM%s )\n",
49862306a36Sopenharmony_ci		vp->dev->name, port->raddr, switch_port ? " switch-port" : "");
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	timer_setup(&port->clean_timer, sunvnet_clean_timer_expire_common, 0);
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	napi_enable(&port->napi);
50362306a36Sopenharmony_ci	vio_port_up(&port->vio);
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	mdesc_release(hp);
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_ci	return 0;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_cierr_out_free_port:
51062306a36Sopenharmony_ci	kfree(port);
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_cierr_out_put_mdesc:
51362306a36Sopenharmony_ci	mdesc_release(hp);
51462306a36Sopenharmony_ci	return err;
51562306a36Sopenharmony_ci}
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_cistatic void vnet_port_remove(struct vio_dev *vdev)
51862306a36Sopenharmony_ci{
51962306a36Sopenharmony_ci	struct vnet_port *port = dev_get_drvdata(&vdev->dev);
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	if (port) {
52262306a36Sopenharmony_ci		del_timer_sync(&port->vio.timer);
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci		napi_disable(&port->napi);
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci		list_del_rcu(&port->list);
52762306a36Sopenharmony_ci		hlist_del_rcu(&port->hash);
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci		synchronize_rcu();
53062306a36Sopenharmony_ci		timer_shutdown_sync(&port->clean_timer);
53162306a36Sopenharmony_ci		sunvnet_port_rm_txq_common(port);
53262306a36Sopenharmony_ci		netif_napi_del(&port->napi);
53362306a36Sopenharmony_ci		sunvnet_port_free_tx_bufs_common(port);
53462306a36Sopenharmony_ci		vio_ldc_free(&port->vio);
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci		dev_set_drvdata(&vdev->dev, NULL);
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci		kfree(port);
53962306a36Sopenharmony_ci	}
54062306a36Sopenharmony_ci}
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_cistatic const struct vio_device_id vnet_port_match[] = {
54362306a36Sopenharmony_ci	{
54462306a36Sopenharmony_ci		.type = "vnet-port",
54562306a36Sopenharmony_ci	},
54662306a36Sopenharmony_ci	{},
54762306a36Sopenharmony_ci};
54862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(vio, vnet_port_match);
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_cistatic struct vio_driver vnet_port_driver = {
55162306a36Sopenharmony_ci	.id_table	= vnet_port_match,
55262306a36Sopenharmony_ci	.probe		= vnet_port_probe,
55362306a36Sopenharmony_ci	.remove		= vnet_port_remove,
55462306a36Sopenharmony_ci	.name		= "vnet_port",
55562306a36Sopenharmony_ci};
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_cistatic int __init vnet_init(void)
55862306a36Sopenharmony_ci{
55962306a36Sopenharmony_ci	pr_info("%s\n", version);
56062306a36Sopenharmony_ci	return vio_register_driver(&vnet_port_driver);
56162306a36Sopenharmony_ci}
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_cistatic void __exit vnet_exit(void)
56462306a36Sopenharmony_ci{
56562306a36Sopenharmony_ci	vio_unregister_driver(&vnet_port_driver);
56662306a36Sopenharmony_ci	vnet_cleanup();
56762306a36Sopenharmony_ci}
56862306a36Sopenharmony_ci
56962306a36Sopenharmony_cimodule_init(vnet_init);
57062306a36Sopenharmony_cimodule_exit(vnet_exit);
571