|
|
@@ -45,8 +45,14 @@
|
|
|
|
|
|
#include "hyperv_net.h"
|
|
|
|
|
|
-#define RING_SIZE_MIN 64
|
|
|
+#define RING_SIZE_MIN 64
|
|
|
+#define NETVSC_MIN_TX_SECTIONS 10
|
|
|
+#define NETVSC_DEFAULT_TX 192 /* ~1M */
|
|
|
+#define NETVSC_MIN_RX_SECTIONS 10 /* ~64K */
|
|
|
+#define NETVSC_DEFAULT_RX 2048 /* ~4M */
|
|
|
+
|
|
|
#define LINKCHANGE_INT (2 * HZ)
|
|
|
+#define VF_TAKEOVER_INT (HZ / 10)
|
|
|
|
|
|
static int ring_size = 128;
|
|
|
module_param(ring_size, int, S_IRUGO);
|
|
|
@@ -164,7 +170,7 @@ static int netvsc_close(struct net_device *net)
|
|
|
}
|
|
|
|
|
|
static void *init_ppi_data(struct rndis_message *msg, u32 ppi_size,
|
|
|
- int pkt_type)
|
|
|
+ int pkt_type)
|
|
|
{
|
|
|
struct rndis_packet *rndis_pkt;
|
|
|
struct rndis_per_packet_info *ppi;
|
|
|
@@ -285,7 +291,7 @@ static u16 netvsc_select_queue(struct net_device *ndev, struct sk_buff *skb,
|
|
|
}
|
|
|
|
|
|
static u32 fill_pg_buf(struct page *page, u32 offset, u32 len,
|
|
|
- struct hv_page_buffer *pb)
|
|
|
+ struct hv_page_buffer *pb)
|
|
|
{
|
|
|
int j = 0;
|
|
|
|
|
|
@@ -332,10 +338,9 @@ static u32 init_page_array(void *hdr, u32 len, struct sk_buff *skb,
|
|
|
* 2. skb linear data
|
|
|
* 3. skb fragment data
|
|
|
*/
|
|
|
- if (hdr != NULL)
|
|
|
- slots_used += fill_pg_buf(virt_to_page(hdr),
|
|
|
- offset_in_page(hdr),
|
|
|
- len, &pb[slots_used]);
|
|
|
+ slots_used += fill_pg_buf(virt_to_page(hdr),
|
|
|
+ offset_in_page(hdr),
|
|
|
+ len, &pb[slots_used]);
|
|
|
|
|
|
packet->rmsg_size = len;
|
|
|
packet->rmsg_pgcnt = slots_used;
|
|
|
@@ -522,9 +527,9 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net)
|
|
|
|
|
|
rndis_msg_size += NDIS_VLAN_PPI_SIZE;
|
|
|
ppi = init_ppi_data(rndis_msg, NDIS_VLAN_PPI_SIZE,
|
|
|
- IEEE_8021Q_INFO);
|
|
|
- vlan = (struct ndis_pkt_8021q_info *)((void *)ppi +
|
|
|
- ppi->ppi_offset);
|
|
|
+ IEEE_8021Q_INFO);
|
|
|
+
|
|
|
+ vlan = (void *)ppi + ppi->ppi_offset;
|
|
|
vlan->vlanid = skb->vlan_tci & VLAN_VID_MASK;
|
|
|
vlan->pri = (skb->vlan_tci & VLAN_PRIO_MASK) >>
|
|
|
VLAN_PRIO_SHIFT;
|
|
|
@@ -537,8 +542,7 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net)
|
|
|
ppi = init_ppi_data(rndis_msg, NDIS_LSO_PPI_SIZE,
|
|
|
TCP_LARGESEND_PKTINFO);
|
|
|
|
|
|
- lso_info = (struct ndis_tcp_lso_info *)((void *)ppi +
|
|
|
- ppi->ppi_offset);
|
|
|
+ lso_info = (void *)ppi + ppi->ppi_offset;
|
|
|
|
|
|
lso_info->lso_v2_transmit.type = NDIS_TCP_LARGE_SEND_OFFLOAD_V2_TYPE;
|
|
|
if (skb->protocol == htons(ETH_P_IP)) {
|
|
|
@@ -625,6 +629,7 @@ no_memory:
|
|
|
++net_device_ctx->eth_stats.tx_no_memory;
|
|
|
goto drop;
|
|
|
}
|
|
|
+
|
|
|
/*
|
|
|
* netvsc_linkstatus_callback - Link up/down notification
|
|
|
*/
|
|
|
@@ -648,8 +653,8 @@ void netvsc_linkstatus_callback(struct hv_device *device_obj,
|
|
|
if (indicate->status == RNDIS_STATUS_LINK_SPEED_CHANGE) {
|
|
|
u32 speed;
|
|
|
|
|
|
- speed = *(u32 *)((void *)indicate + indicate->
|
|
|
- status_buf_offset) / 10000;
|
|
|
+ speed = *(u32 *)((void *)indicate
|
|
|
+ + indicate->status_buf_offset) / 10000;
|
|
|
ndev_ctx->speed = speed;
|
|
|
return;
|
|
|
}
|
|
|
@@ -831,11 +836,13 @@ static int netvsc_set_channels(struct net_device *net,
|
|
|
if (was_opened)
|
|
|
rndis_filter_close(nvdev);
|
|
|
|
|
|
- rndis_filter_device_remove(dev, nvdev);
|
|
|
-
|
|
|
memset(&device_info, 0, sizeof(device_info));
|
|
|
device_info.num_chn = count;
|
|
|
device_info.ring_size = ring_size;
|
|
|
+ device_info.send_sections = nvdev->send_section_cnt;
|
|
|
+ device_info.recv_sections = nvdev->recv_section_cnt;
|
|
|
+
|
|
|
+ rndis_filter_device_remove(dev, nvdev);
|
|
|
|
|
|
nvdev = rndis_filter_device_add(dev, &device_info);
|
|
|
if (!IS_ERR(nvdev)) {
|
|
|
@@ -844,7 +851,13 @@ static int netvsc_set_channels(struct net_device *net,
|
|
|
} else {
|
|
|
ret = PTR_ERR(nvdev);
|
|
|
device_info.num_chn = orig;
|
|
|
- rndis_filter_device_add(dev, &device_info);
|
|
|
+ nvdev = rndis_filter_device_add(dev, &device_info);
|
|
|
+
|
|
|
+ if (IS_ERR(nvdev)) {
|
|
|
+ netdev_err(net, "restoring channel setting failed: %ld\n",
|
|
|
+ PTR_ERR(nvdev));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (was_opened)
|
|
|
@@ -941,6 +954,8 @@ static int netvsc_change_mtu(struct net_device *ndev, int mtu)
|
|
|
memset(&device_info, 0, sizeof(device_info));
|
|
|
device_info.ring_size = ring_size;
|
|
|
device_info.num_chn = nvdev->num_chn;
|
|
|
+ device_info.send_sections = nvdev->send_section_cnt;
|
|
|
+ device_info.recv_sections = nvdev->recv_section_cnt;
|
|
|
|
|
|
rndis_filter_device_remove(hdev, nvdev);
|
|
|
|
|
|
@@ -952,10 +967,16 @@ static int netvsc_change_mtu(struct net_device *ndev, int mtu)
|
|
|
|
|
|
/* Attempt rollback to original MTU */
|
|
|
ndev->mtu = orig_mtu;
|
|
|
- rndis_filter_device_add(hdev, &device_info);
|
|
|
+ nvdev = rndis_filter_device_add(hdev, &device_info);
|
|
|
|
|
|
if (vf_netdev)
|
|
|
dev_set_mtu(vf_netdev, orig_mtu);
|
|
|
+
|
|
|
+ if (IS_ERR(nvdev)) {
|
|
|
+ netdev_err(ndev, "restoring mtu failed: %ld\n",
|
|
|
+ PTR_ERR(nvdev));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (was_opened)
|
|
|
@@ -1005,7 +1026,7 @@ static void netvsc_get_stats64(struct net_device *net,
|
|
|
struct net_device_context *ndev_ctx = netdev_priv(net);
|
|
|
struct netvsc_device *nvdev = rcu_dereference_rtnl(ndev_ctx->nvdev);
|
|
|
struct netvsc_vf_pcpu_stats vf_tot;
|
|
|
- int i;
|
|
|
+ int i;
|
|
|
|
|
|
if (!nvdev)
|
|
|
return;
|
|
|
@@ -1052,27 +1073,31 @@ static void netvsc_get_stats64(struct net_device *net,
|
|
|
static int netvsc_set_mac_addr(struct net_device *ndev, void *p)
|
|
|
{
|
|
|
struct net_device_context *ndc = netdev_priv(ndev);
|
|
|
+ struct net_device *vf_netdev = rtnl_dereference(ndc->vf_netdev);
|
|
|
struct netvsc_device *nvdev = rtnl_dereference(ndc->nvdev);
|
|
|
struct sockaddr *addr = p;
|
|
|
- char save_adr[ETH_ALEN];
|
|
|
- unsigned char save_aatype;
|
|
|
int err;
|
|
|
|
|
|
- memcpy(save_adr, ndev->dev_addr, ETH_ALEN);
|
|
|
- save_aatype = ndev->addr_assign_type;
|
|
|
-
|
|
|
- err = eth_mac_addr(ndev, p);
|
|
|
- if (err != 0)
|
|
|
+ err = eth_prepare_mac_addr_change(ndev, p);
|
|
|
+ if (err)
|
|
|
return err;
|
|
|
|
|
|
if (!nvdev)
|
|
|
return -ENODEV;
|
|
|
|
|
|
+ if (vf_netdev) {
|
|
|
+ err = dev_set_mac_address(vf_netdev, addr);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+
|
|
|
err = rndis_filter_set_device_mac(nvdev, addr->sa_data);
|
|
|
- if (err != 0) {
|
|
|
- /* roll back to saved MAC */
|
|
|
- memcpy(ndev->dev_addr, save_adr, ETH_ALEN);
|
|
|
- ndev->addr_assign_type = save_aatype;
|
|
|
+ if (!err) {
|
|
|
+ eth_commit_mac_addr_change(ndev, p);
|
|
|
+ } else if (vf_netdev) {
|
|
|
+ /* rollback change on VF */
|
|
|
+ memcpy(addr->sa_data, ndev->dev_addr, ETH_ALEN);
|
|
|
+ dev_set_mac_address(vf_netdev, addr);
|
|
|
}
|
|
|
|
|
|
return err;
|
|
|
@@ -1087,6 +1112,8 @@ static const struct {
|
|
|
{ "tx_no_space", offsetof(struct netvsc_ethtool_stats, tx_no_space) },
|
|
|
{ "tx_too_big", offsetof(struct netvsc_ethtool_stats, tx_too_big) },
|
|
|
{ "tx_busy", offsetof(struct netvsc_ethtool_stats, tx_busy) },
|
|
|
+ { "tx_send_full", offsetof(struct netvsc_ethtool_stats, tx_send_full) },
|
|
|
+ { "rx_comp_busy", offsetof(struct netvsc_ethtool_stats, rx_comp_busy) },
|
|
|
}, vf_stats[] = {
|
|
|
{ "vf_rx_packets", offsetof(struct netvsc_vf_pcpu_stats, rx_packets) },
|
|
|
{ "vf_rx_bytes", offsetof(struct netvsc_vf_pcpu_stats, rx_bytes) },
|
|
|
@@ -1335,6 +1362,104 @@ static int netvsc_set_rxfh(struct net_device *dev, const u32 *indir,
|
|
|
return rndis_filter_set_rss_param(rndis_dev, key, ndev->num_chn);
|
|
|
}
|
|
|
|
|
|
+/* Hyper-V RNDIS protocol does not have ring in the HW sense.
|
|
|
+ * It does have pre-allocated receive area which is divided into sections.
|
|
|
+ */
|
|
|
+static void __netvsc_get_ringparam(struct netvsc_device *nvdev,
|
|
|
+ struct ethtool_ringparam *ring)
|
|
|
+{
|
|
|
+ u32 max_buf_size;
|
|
|
+
|
|
|
+ ring->rx_pending = nvdev->recv_section_cnt;
|
|
|
+ ring->tx_pending = nvdev->send_section_cnt;
|
|
|
+
|
|
|
+ if (nvdev->nvsp_version <= NVSP_PROTOCOL_VERSION_2)
|
|
|
+ max_buf_size = NETVSC_RECEIVE_BUFFER_SIZE_LEGACY;
|
|
|
+ else
|
|
|
+ max_buf_size = NETVSC_RECEIVE_BUFFER_SIZE;
|
|
|
+
|
|
|
+ ring->rx_max_pending = max_buf_size / nvdev->recv_section_size;
|
|
|
+ ring->tx_max_pending = NETVSC_SEND_BUFFER_SIZE
|
|
|
+ / nvdev->send_section_size;
|
|
|
+}
|
|
|
+
|
|
|
+static void netvsc_get_ringparam(struct net_device *ndev,
|
|
|
+ struct ethtool_ringparam *ring)
|
|
|
+{
|
|
|
+ struct net_device_context *ndevctx = netdev_priv(ndev);
|
|
|
+ struct netvsc_device *nvdev = rtnl_dereference(ndevctx->nvdev);
|
|
|
+
|
|
|
+ if (!nvdev)
|
|
|
+ return;
|
|
|
+
|
|
|
+ __netvsc_get_ringparam(nvdev, ring);
|
|
|
+}
|
|
|
+
|
|
|
+static int netvsc_set_ringparam(struct net_device *ndev,
|
|
|
+ struct ethtool_ringparam *ring)
|
|
|
+{
|
|
|
+ struct net_device_context *ndevctx = netdev_priv(ndev);
|
|
|
+ struct netvsc_device *nvdev = rtnl_dereference(ndevctx->nvdev);
|
|
|
+ struct hv_device *hdev = ndevctx->device_ctx;
|
|
|
+ struct netvsc_device_info device_info;
|
|
|
+ struct ethtool_ringparam orig;
|
|
|
+ u32 new_tx, new_rx;
|
|
|
+ bool was_opened;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ if (!nvdev || nvdev->destroy)
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ memset(&orig, 0, sizeof(orig));
|
|
|
+ __netvsc_get_ringparam(nvdev, &orig);
|
|
|
+
|
|
|
+ new_tx = clamp_t(u32, ring->tx_pending,
|
|
|
+ NETVSC_MIN_TX_SECTIONS, orig.tx_max_pending);
|
|
|
+ new_rx = clamp_t(u32, ring->rx_pending,
|
|
|
+ NETVSC_MIN_RX_SECTIONS, orig.rx_max_pending);
|
|
|
+
|
|
|
+ if (new_tx == orig.tx_pending &&
|
|
|
+ new_rx == orig.rx_pending)
|
|
|
+ return 0; /* no change */
|
|
|
+
|
|
|
+ memset(&device_info, 0, sizeof(device_info));
|
|
|
+ device_info.num_chn = nvdev->num_chn;
|
|
|
+ device_info.ring_size = ring_size;
|
|
|
+ device_info.send_sections = new_tx;
|
|
|
+ device_info.recv_sections = new_rx;
|
|
|
+
|
|
|
+ netif_device_detach(ndev);
|
|
|
+ was_opened = rndis_filter_opened(nvdev);
|
|
|
+ if (was_opened)
|
|
|
+ rndis_filter_close(nvdev);
|
|
|
+
|
|
|
+ rndis_filter_device_remove(hdev, nvdev);
|
|
|
+
|
|
|
+ nvdev = rndis_filter_device_add(hdev, &device_info);
|
|
|
+ if (IS_ERR(nvdev)) {
|
|
|
+ ret = PTR_ERR(nvdev);
|
|
|
+
|
|
|
+ device_info.send_sections = orig.tx_pending;
|
|
|
+ device_info.recv_sections = orig.rx_pending;
|
|
|
+ nvdev = rndis_filter_device_add(hdev, &device_info);
|
|
|
+ if (IS_ERR(nvdev)) {
|
|
|
+ netdev_err(ndev, "restoring ringparam failed: %ld\n",
|
|
|
+ PTR_ERR(nvdev));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (was_opened)
|
|
|
+ rndis_filter_open(nvdev);
|
|
|
+ netif_device_attach(ndev);
|
|
|
+
|
|
|
+ /* We may have missed link change notifications */
|
|
|
+ ndevctx->last_reconfig = 0;
|
|
|
+ schedule_delayed_work(&ndevctx->dwork, 0);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
static const struct ethtool_ops ethtool_ops = {
|
|
|
.get_drvinfo = netvsc_get_drvinfo,
|
|
|
.get_link = ethtool_op_get_link,
|
|
|
@@ -1351,6 +1476,8 @@ static const struct ethtool_ops ethtool_ops = {
|
|
|
.set_rxfh = netvsc_set_rxfh,
|
|
|
.get_link_ksettings = netvsc_get_link_ksettings,
|
|
|
.set_link_ksettings = netvsc_set_link_ksettings,
|
|
|
+ .get_ringparam = netvsc_get_ringparam,
|
|
|
+ .set_ringparam = netvsc_set_ringparam,
|
|
|
};
|
|
|
|
|
|
static const struct net_device_ops device_ops = {
|
|
|
@@ -1559,7 +1686,9 @@ static int netvsc_vf_join(struct net_device *vf_netdev,
|
|
|
/* set slave flag before open to prevent IPv6 addrconf */
|
|
|
vf_netdev->flags |= IFF_SLAVE;
|
|
|
|
|
|
- schedule_work(&ndev_ctx->vf_takeover);
|
|
|
+ schedule_delayed_work(&ndev_ctx->vf_takeover, VF_TAKEOVER_INT);
|
|
|
+
|
|
|
+ call_netdevice_notifiers(NETDEV_JOIN, vf_netdev);
|
|
|
|
|
|
netdev_info(vf_netdev, "joined to %s\n", ndev->name);
|
|
|
return 0;
|
|
|
@@ -1575,8 +1704,6 @@ static void __netvsc_vf_setup(struct net_device *ndev,
|
|
|
{
|
|
|
int ret;
|
|
|
|
|
|
- call_netdevice_notifiers(NETDEV_JOIN, vf_netdev);
|
|
|
-
|
|
|
/* Align MTU of VF with master */
|
|
|
ret = dev_set_mtu(vf_netdev, ndev->mtu);
|
|
|
if (ret)
|
|
|
@@ -1597,12 +1724,12 @@ static void __netvsc_vf_setup(struct net_device *ndev,
|
|
|
static void netvsc_vf_setup(struct work_struct *w)
|
|
|
{
|
|
|
struct net_device_context *ndev_ctx
|
|
|
- = container_of(w, struct net_device_context, vf_takeover);
|
|
|
+ = container_of(w, struct net_device_context, vf_takeover.work);
|
|
|
struct net_device *ndev = hv_get_drvdata(ndev_ctx->device_ctx);
|
|
|
struct net_device *vf_netdev;
|
|
|
|
|
|
if (!rtnl_trylock()) {
|
|
|
- schedule_work(w);
|
|
|
+ schedule_delayed_work(&ndev_ctx->vf_takeover, 0);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -1706,7 +1833,7 @@ static int netvsc_unregister_vf(struct net_device *vf_netdev)
|
|
|
return NOTIFY_DONE;
|
|
|
|
|
|
net_device_ctx = netdev_priv(ndev);
|
|
|
- cancel_work_sync(&net_device_ctx->vf_takeover);
|
|
|
+ cancel_delayed_work_sync(&net_device_ctx->vf_takeover);
|
|
|
|
|
|
netdev_info(ndev, "VF unregistering: %s\n", vf_netdev->name);
|
|
|
|
|
|
@@ -1748,7 +1875,7 @@ static int netvsc_probe(struct hv_device *dev,
|
|
|
|
|
|
spin_lock_init(&net_device_ctx->lock);
|
|
|
INIT_LIST_HEAD(&net_device_ctx->reconfig_events);
|
|
|
- INIT_WORK(&net_device_ctx->vf_takeover, netvsc_vf_setup);
|
|
|
+ INIT_DELAYED_WORK(&net_device_ctx->vf_takeover, netvsc_vf_setup);
|
|
|
|
|
|
net_device_ctx->vf_stats
|
|
|
= netdev_alloc_pcpu_stats(struct netvsc_vf_pcpu_stats);
|
|
|
@@ -1766,6 +1893,8 @@ static int netvsc_probe(struct hv_device *dev,
|
|
|
memset(&device_info, 0, sizeof(device_info));
|
|
|
device_info.ring_size = ring_size;
|
|
|
device_info.num_chn = VRSS_CHANNEL_DEFAULT;
|
|
|
+ device_info.send_sections = NETVSC_DEFAULT_TX;
|
|
|
+ device_info.recv_sections = NETVSC_DEFAULT_RX;
|
|
|
|
|
|
nvdev = rndis_filter_device_add(dev, &device_info);
|
|
|
if (IS_ERR(nvdev)) {
|