From mboxrd@z Thu Jan 1 00:00:00 1970 From: Erik Arfvidson Subject: [PATCH] net: unisys: adding unisys virtnic driver Date: Wed, 17 Dec 2014 13:52:20 -0500 Message-ID: <1418842340-29894-1-git-send-email-earfvids@redhat.com> Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: QUOTED-PRINTABLE Cc: Erik Arfvidson To: benjamin.romer@unisys.com, netdev@vger.kernel.org, dzickus@redhat.com, davem@davemloft.net, Bruce.Vessey@unisys.com, sparmaintainer@unisys.com, prarit@redhat.com Return-path: Received: from mx1.redhat.com ([209.132.183.28]:48782 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751323AbaLQSwj (ORCPT ); Wed, 17 Dec 2014 13:52:39 -0500 Sender: netdev-owner@vger.kernel.org List-ID: The purpose of this patch is to add Unisys virtual network driver into the network directory and also to start a discussion about the requirements needed. Signed-off-by: Erik Arfvidson --- drivers/net/virtnic.c | 2475 +++++++++++++++++++++++++++++++++++++++++= ++++++++ 1 file changed, 2475 insertions(+) create mode 100644 drivers/net/virtnic.c diff --git a/drivers/net/virtnic.c b/drivers/net/virtnic.c new file mode 100644 index 0000000..0af48f3 --- /dev/null +++ b/drivers/net/virtnic.c @@ -0,0 +1,2475 @@ +/* virtnic.c + * + * Copyright =C2=A9 2010 - 2014 UNISYS CORPORATION + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modif= y + * it under the terms of the GNU General Public License as published b= y + * the Free Software Foundation; either version 2 of the License, or (= at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + */ + +#define EXPORT_SYMTAB + +#include +#ifdef CONFIG_MODVERSIONS +#include +#endif + +#include "uniklog.h" +#include "diagnostics/appos_subsystems.h" +#include "uisutils.h" +#include "uisthread.h" +#include "uisqueue.h" +#include "visorchipset.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "virtpci.h" +#include "version.h" + +/* this is shorter than using __FILE__ (full path name) in */ +/* debug/info/error messages */ +#define __MYFILE__ "virtnic.c" + +/* turn off collecting of debug statistics */ +#define VIRTNIC_STATS 0 + + /* MAX_BUF =3D 64 lines x 32 MAXVHBA x 80 characters + * =3D 163840 bytes ~ 40 pages + */ +#define MAX_BUF 163840 + +/* + * uisnic virtnic + * <---- xmit --- virtnic_xmit(hard-start-xmit) + * <-- rcvpost -- open, virtnic_rx + * <-- unpost --- close + * <-- enb/dis -- open, close + * + * open & close can't run at the same time as each other or rcv/xmit, = but + * virtnic_xmit and virtnic_rx could be running at the same time. + * and all messages being sent to uisnic MUST be sent so if the queue = is + * full we have to retry, but we don't want to retry with a spinlock h= eld. + */ + +/*****************************************************/ +/* Forward declarations */ +/*****************************************************/ +static int virtnic_probe(struct virtpci_dev *dev, + const struct pci_device_id *id); +static void virtnic_remove(struct virtpci_dev *dev); +static int virtnic_change_mtu(struct net_device *netdev, int new_mtu); +static int virtnic_close(struct net_device *netdev); +static struct net_device_stats *virtnic_get_stats(struct net_device *n= etdev); +static int virtnic_open(struct net_device *netdev); +static int virtnic_ioctl(struct net_device *netdev, struct ifreq *ifr, + int cmd); +static void virtnic_rx(struct uiscmdrsp *cmdrsp); +static int virtnic_xmit(struct sk_buff *skb, struct net_device *netdev= ); +static void virtnic_xmit_timeout(struct net_device *netdev); +static void virtnic_set_multi(struct net_device *netdev); +static int virtnic_serverdown(struct virtpci_dev *virtpcidev, u32 stat= e); +static int virtnic_serverup(struct virtpci_dev *virtpcidev); +static void virtnic_serverdown_complete(struct work_struct *work); +static void virtnic_timeout_reset(struct work_struct *work); +static int process_incoming_rsps(void *); +static ssize_t info_debugfs_read(struct file *file, char __user *buf, + size_t len, loff_t *offset); +static ssize_t enable_ints_write(struct file *file, + const char __user *buffer, + size_t count, loff_t *ppos); + +/*****************************************************/ +/* Globals */ +/*****************************************************/ + +#define VIRTNIC_XMIT_TIMEOUT (5 * HZ) /* Default timeout period in jif= fies */ +#define VIRTNIC_INFINITE_RESPONSE_WAIT 0 +#define INTERRUPT_VECTOR_MASK 0x3F + +static struct workqueue_struct *virtnic_serverdown_workqueue; +static struct workqueue_struct *virtnic_timeout_reset_workqueue; + +static const struct pci_device_id virtnic_id_table[] =3D { + { + PCI_DEVICE(PCI_VENDOR_ID_UNISYS, PCI_DEVICE_ID_VIRTNIC)}, { +0},}; +/* export virtnic_id_table */ +MODULE_DEVICE_TABLE(pci, virtnic_id_table); + +static struct virtpci_driver virtnic_driver =3D { + .name =3D "uisvirtnic", + .version =3D VERSION, + .vertag =3D NULL, + .id_table =3D virtnic_id_table, + .probe =3D virtnic_probe, + .remove =3D virtnic_remove, + .suspend =3D virtnic_serverdown, + .resume =3D virtnic_serverup +}; + +#define SEND_ENBDIS(ndev, state, cmdrsp, queue, insertlock, stats) { \ + DBGINF("sending rcv enb/dis netdev:%p state:%d\n", ndev, state); \ + cmdrsp->net.enbdis.enable =3D state; \ + cmdrsp->net.enbdis.context =3D ndev; \ + cmdrsp->net.type =3D NET_RCV_ENBDIS; \ + cmdrsp->cmdtype =3D CMD_NET_TYPE; \ + uisqueue_put_cmdrsp_with_lock_client(queue, cmdrsp, IOCHAN_TO_IOPART,= \ + (void *)insertlock, \ + DONT_ISSUE_INTERRUPT, \ + (uint64_t)NULL, \ + OK_TO_WAIT, "vnic"); \ + stats.sent_enbdis++;\ +} + +struct chanstat { + unsigned long got_rcv; /* count of NET_RCV received */ + unsigned long got_enbdisack; /* count of NET_RCV_ENBDIS_ACK rcvd */ + unsigned long got_xmit_done; /* count of NET_XMIT_DONE received */ + unsigned long xmit_fail; /* count of NET_XMIT_DONE failures */ + unsigned long sent_enbdis; /* count of NET_RCV_ENBDIS sent */ + unsigned long sent_promisc; /* count of NET_RCV_PROMISC sent */ + unsigned long sent_post; /* count of NET_RCV_POST sent */ + unsigned long sent_xmit; /* count of NET_XMIT sent */ + unsigned long reject_count; /* count of NET_XMIT rejected because */ + /* of BUSY/queue full */ + unsigned long extra_rcvbufs_sent; +#if VIRTNIC_STATS + unsigned long reject_jiffies_start; /* jiffie count at start of + NET_XMIT rejects */ +#endif /* VIRTNIC_STATS */ +}; + +struct datachan { + struct chaninfo chinfo; + struct chanstat chstat; +}; + +struct virtnic_info { + struct virtpci_dev *virtpcidev; + struct net_device *netdev; + struct net_device_stats net_stats; + spinlock_t priv_lock; /* spinlock check for private lock */ + struct datachan datachan; + struct sk_buff **rcvbuf; /* rcvbuf is the array of rcv buffer */ + /* we post to */ + unsigned long long uniquenum; + + /* the IOPART end */ + int num_rcv_bufs; /* indicates how many receive buffers the + vnic will post */ + int num_rcv_bufs_could_not_alloc; + atomic_t num_rcv_bufs_in_iovm; /* indicates how many receive buffers + have actully been sent to the iovm */ + unsigned long inner_loop_limit_reached_cnt; + unsigned long alloc_failed_in_if_needed_cnt; + unsigned long alloc_failed_in_repost_return_cnt; + + struct sk_buff_head xmitbufhead; /* xmitbufhead is the head of + the xmit buffer list that + have been sent to the IOPART + end */ + int max_outstanding_net_xmits; /* absolute max number of outstanding + xmits - should never hit this */ + int upper_threshold_net_xmits; /* high water mark for calling + netif_stop_queue() */ + int lower_threshold_net_xmits; /* high water mark for calling + netif_wake_queue() */ + uuid_le zoneguid; /* specifies the zone for the switch in + which this VNIC resides */ + struct uiscmdrsp *cmdrsp_rcv; /* cmdrsp_rcv is used for + posting/unposting rcv buffers */ + unsigned short enabled; /* 0 disabled 1 enabled to receive */ + unsigned short enab_dis_acked; /* NET_RCV_ENABLE/DISABLE acked by + uisnic */ + atomic_t usage; /* count of users */ + unsigned short old_flags; /* flags as they were prior to + set_multicast_list */ + struct uiscmdrsp *xmit_cmdrsp; /* used to issue NET_XMIT - there is + never more that one xmit in progress + at a time */ + struct dentry *eth_debugfs_dir; /* this points to /proc/eth? + directory */ + struct dentry *zone_debugfs_entry; /* this points to + /proc/virtnic/eth?/zone */ + /* file */ + struct dentry *clientstr_debugfs_entry;/* this points to + /proc/virtnic/eth?/clientstr + file */ + struct irq_info intr; /* use recvInterrupt info to connect + to this to receive interrupts when + IOs complete */ + int interrupt_vector; + int thread_wait_ms; + int queuefullmsg_logged; /* flag for throttling queue full */ + /* messages */ + /* some debug counters */ + ulong n_rcv0; /* # rcvs of 0 buffers */ + ulong n_rcv1; /* # rcvs of 1 buffer */ + ulong n_rcv2; /* # rcvs of 2 buffers */ + ulong n_rcvx; /* # rcvs of >2 buffers */ + ulong found_repost_rcvbuf_cnt; /* #time we called repost_rcvbuf_cnt *= / + ulong repost_found_skb_cnt; /* # times found the skb */ + ulong n_repost_deficit; /* # times we couldn't find all of the + rcv buffers */ + ulong bad_rcv_buf; /* # times we neglected to + free the rcv skb because + we didn't know where it + came from */ + ulong n_rcv_packet_not_accepted; /* # bogus recv packets */ + bool server_down; + bool server_change_state; + unsigned long long interrupts_rcvd; + unsigned long long interrupts_notme; + unsigned long long interrupts_disabled; + unsigned long long busy_cnt; + unsigned long long flow_control_upper_hits; + unsigned long long flow_control_lower_hits; + struct work_struct serverdown_completion; + struct work_struct timeout_reset; + uint64_t __iomem *flags_addr; + atomic_t interrupt_rcvd; + wait_queue_head_t rsp_queue; +}; + +struct virtnic_devices_open { + struct net_device *netdev; + struct virtnic_info *vnicinfo; +}; + +static ssize_t show_zone(struct device *dev, struct device_attribute *= attr, + char *buf) +{ + struct net_device *net =3D to_net_dev(dev); + struct virtnic_info *vnicinfo =3D netdev_priv(net); + + return scnprintf(buf, PAGE_SIZE, "%pUL\n", &vnicinfo->zoneguid); +} + +static ssize_t show_clientstr(struct device *dev, struct device_attrib= ute *attr, + char *buf) +{ + struct net_device *net =3D to_net_dev(dev); + struct virtnic_info *vnicinfo =3D netdev_priv(net); + struct spar_io_channel_protocol *chan =3D + (struct spar_io_channel_protocol *)vnicinfo-> + datachan.chinfo.queueinfo->chan; + + return scnprintf(buf, PAGE_SIZE, "%s\n", + (char *)&chan->client_string); +} +static DEVICE_ATTR(clientstr, S_IRUGO, show_clientstr, NULL); +static DEVICE_ATTR(zone, S_IRUGO, show_zone, NULL); + +#define VIRTNICSOPENMAX 32 +/* array of open devices maintained by open() and close() */ +static struct virtnic_devices_open num_virtnic_open[VIRTNICSOPENMAX]; +static struct dentry *virtnic_debugfs_dir; + +static const struct file_operations debugfs_info_fops =3D { + .read =3D info_debugfs_read, +}; + +static const struct file_operations debugfs_enable_ints_fops =3D { + .write =3D enable_ints_write, +}; + +/*****************************************************/ +/* Probe Remove Functions */ +/*****************************************************/ +/* set up net.rcvpost struct in cmdrsp. + * all rcv buf skb are allocated at RCVPOST_BUF_SIZE, so length is + * RCVPOST_BUF_SIZE by default. and since RCVPOST_BUF_SIZE < 2048, one + * phys_info struct can describe the rcv buf. + */ +static inline void +post_skb(struct uiscmdrsp *cmdrsp, + struct virtnic_info *vnicinfo, struct sk_buff *skb) +{ + cmdrsp->net.buf =3D skb; + cmdrsp->net.rcvpost.frag.pi_pfn =3D page_to_pfn(virt_to_page(skb->dat= a)); + cmdrsp->net.rcvpost.frag.pi_off =3D + (unsigned long)skb->data & PI_PAGE_MASK; + cmdrsp->net.rcvpost.frag.pi_len =3D skb->len; + cmdrsp->net.rcvpost.unique_num =3D vnicinfo->uniquenum; + + DBGINF("RCV_POST skb:%p pfn:%llu off:%x len:%d\n", skb, + cmdrsp->net.rcvpost.frag.pi_pfn, + cmdrsp->net.rcvpost.frag.pi_off, + cmdrsp->net.rcvpost.frag.pi_len); + if ((cmdrsp->net.rcvpost.frag.pi_off + skb->len) > PI_PAGE_SIZE) { + LOGERRNAME(vnicinfo->netdev, + "**** pi_off:0x%x pi_len:%d SPAN ACROSS A PAGE\n", + cmdrsp->net.rcvpost.frag.pi_off, skb->len); + } else { + cmdrsp->net.type =3D NET_RCV_POST; + cmdrsp->cmdtype =3D CMD_NET_TYPE; + uisqueue_put_cmdrsp_with_lock_client(vnicinfo->datachan.chinfo. + queueinfo, cmdrsp, + IOCHAN_TO_IOPART, + (void *)&vnicinfo-> + datachan.chinfo.insertlock, + DONT_ISSUE_INTERRUPT, + (uint64_t)NULL, + OK_TO_WAIT, + "vnic"); + atomic_inc(&vnicinfo->num_rcv_bufs_in_iovm); + vnicinfo->datachan.chstat.sent_post++; + } +} + +static irqreturn_t +virtnic_ISR(int irq, void *dev_id) +{ + struct virtnic_info *vnicinfo =3D (struct virtnic_info *)dev_id; + + struct channel_header __iomem *p_channel_header; + + struct signal_queue_header __iomem *pqhdr; + uint64_t mask; + unsigned long long rc1; + + if (vnicinfo =3D=3D NULL) + return IRQ_NONE; + vnicinfo->interrupts_rcvd++; + p_channel_header =3D vnicinfo->datachan.chinfo.queueinfo->chan; + if (((readq(&p_channel_header->features) & + ULTRA_IO_IOVM_IS_OK_WITH_DRIVER_DISABLING_INTS) !=3D 0) && + ((readq(&p_channel_header->features) & + ULTRA_IO_DRIVER_DISABLES_INTS) !=3D 0)) { + /* + * should not enter this path because we setup without + * DRIVER_DISABLES_INTS. + */ + vnicinfo->interrupts_disabled++; + mask =3D ~ULTRA_CHANNEL_ENABLE_INTS; + rc1 =3D uisqueue_interlocked_and(vnicinfo->flags_addr, mask); + } + if (spar_signalqueue_empty(p_channel_header, IOCHAN_FROM_IOPART)) { + vnicinfo->interrupts_notme++; + return IRQ_NONE; + } + pqhdr =3D (struct signal_queue_header __iomem *) + ((char __iomem *)p_channel_header + + readq(&p_channel_header->ch_space_offset)) + + IOCHAN_FROM_IOPART; + writeq(readq(&pqhdr->num_irq_received) + 1, + &pqhdr->num_irq_received); + atomic_set(&vnicinfo->interrupt_rcvd, 1); + wake_up_interruptible(&vnicinfo->rsp_queue); + return IRQ_HANDLED; +} + +static const struct net_device_ops virtnic_dev_ops =3D { + .ndo_open =3D virtnic_open, + .ndo_stop =3D virtnic_close, + .ndo_start_xmit =3D virtnic_xmit, + .ndo_get_stats =3D virtnic_get_stats, + .ndo_do_ioctl =3D virtnic_ioctl, + .ndo_change_mtu =3D virtnic_change_mtu, + .ndo_tx_timeout =3D virtnic_xmit_timeout, + .ndo_set_rx_mode =3D virtnic_set_multi, +}; + +static int +virtnic_probe(struct virtpci_dev *virtpcidev, const struct pci_device_= id *id) +{ + struct net_device *netdev =3D NULL; + struct virtnic_info *vnicinfo; + int err; + int rsp; + irq_handler_t handler =3D virtnic_ISR; + struct channel_header __iomem *p_channel_header; + struct signal_queue_header __iomem *pqhdr; + uint64_t mask; + +#define RETFAIL(res) {\ + kfree(vnicinfo->cmdrsp_rcv); \ + kfree(vnicinfo->xmit_cmdrsp); \ + kfree(vnicinfo->rcvbuf); \ + if (vnicinfo->interrupt_vector !=3D -1) \ + free_irq(vnicinfo->interrupt_vector, vnicinfo); \ + if (netdev) \ + free_netdev(netdev); \ + return res; \ +} + + DBGINF("virtpci_dev:%p\n", virtpcidev); + DBGINF("virtpcidev busNo<<%d>>devNo<<%d>>", + virtpcidev->busNo, virtpcidev->deviceNo); + netdev =3D alloc_etherdev(sizeof(struct virtnic_info)); + if (netdev =3D=3D NULL) { + LOGERR("**** FAILED to alloc etherdev\n"); + return -ENOMEM; + } + netdev->netdev_ops =3D &virtnic_dev_ops; + netdev->watchdog_timeo =3D VIRTNIC_XMIT_TIMEOUT; + + memcpy(netdev->dev_addr, virtpcidev->net.mac_addr, MAX_MACADDR_LEN); + netdev->addr_len =3D MAX_MACADDR_LEN; + /* netdev->name should be ethx already */ + netdev->dev.parent =3D &virtpcidev->generic_dev; + + /* setup our private struct */ + vnicinfo =3D netdev_priv(netdev); + memset(vnicinfo, 0, sizeof(struct virtnic_info)); + vnicinfo->interrupt_vector =3D -1; + vnicinfo->netdev =3D netdev; + vnicinfo->virtpcidev =3D virtpcidev; + init_waitqueue_head(&vnicinfo->rsp_queue); + spin_lock_init(&vnicinfo->priv_lock); + vnicinfo->datachan.chinfo.queueinfo =3D &virtpcidev->queueinfo; + spin_lock_init(&vnicinfo->datachan.chinfo.insertlock); + vnicinfo->enabled =3D 0; /* not yet */ + atomic_set(&vnicinfo->usage, 1); /* starting val */ + vnicinfo->zoneguid =3D virtpcidev->net.zone_uuid; + vnicinfo->num_rcv_bufs =3D virtpcidev->net.num_rcv_bufs; + LOGINFNAME(vnicinfo->netdev, "num_rcv_bufs =3D %d\n", + vnicinfo->num_rcv_bufs); + vnicinfo->rcvbuf =3D kmalloc(sizeof(struct sk_buff *) * + vnicinfo->num_rcv_bufs, GFP_ATOMIC); + if (vnicinfo->rcvbuf =3D=3D NULL) { + LOGERRNAME(vnicinfo->netdev, + "**** FAILED to allocate memory for %d receive buffers.\n", + vnicinfo->num_rcv_bufs); + RETFAIL(-ENOMEM); + } + memset(vnicinfo->rcvbuf, 0, + sizeof(struct sk_buff *) * vnicinfo->num_rcv_bufs); + /* set the net_xmit outstanding threshold */ + vnicinfo->max_outstanding_net_xmits =3D + max(3, ((vnicinfo->num_rcv_bufs / 3) - 2)); + /* always leave two slots open but you should have 3 at a minimum */ + LOGINFNAME(vnicinfo->netdev, "max_outstanding_net_xmits =3D %d\n", + vnicinfo->max_outstanding_net_xmits); + vnicinfo->upper_threshold_net_xmits =3D + max(2, vnicinfo->max_outstanding_net_xmits - 1); + LOGINFNAME(vnicinfo->netdev, "upper_threshold_net_xmits =3D %d\n", + vnicinfo->upper_threshold_net_xmits); + vnicinfo->lower_threshold_net_xmits =3D + max(1, vnicinfo->max_outstanding_net_xmits / 2); + LOGINFNAME(vnicinfo->netdev, "lower_threshold_net_xmits =3D %d\n", + vnicinfo->lower_threshold_net_xmits); + skb_queue_head_init(&vnicinfo->xmitbufhead); + + /* create a cmdrsp we can use to post and unpost rcv buffers */ + vnicinfo->cmdrsp_rcv =3D kmalloc(SIZEOF_CMDRSP, GFP_ATOMIC); + if (vnicinfo->cmdrsp_rcv =3D=3D NULL) { + LOGERRNAME(vnicinfo->netdev, + "**** FAILED to allocate cmdrsp to use for posting rcv buffers\n= "); + RETFAIL(-ENOMEM); + } + vnicinfo->xmit_cmdrsp =3D kmalloc(SIZEOF_CMDRSP, GFP_ATOMIC); + if (vnicinfo->xmit_cmdrsp =3D=3D NULL) { + LOGERRNAME(vnicinfo->netdev, + "**** FAILED to allocate cmdrsp to use for xmits\n"); + RETFAIL(-ENOMEM); + } + INIT_WORK(&vnicinfo->serverdown_completion, + virtnic_serverdown_complete); + INIT_WORK(&vnicinfo->timeout_reset, virtnic_timeout_reset); + vnicinfo->server_down =3D false; + vnicinfo->server_change_state =3D false; + + /* set the default mtu */ + netdev->mtu =3D virtpcidev->net.mtu; + + vnicinfo->intr =3D virtpcidev->intr; + /* buffers will be allocated in open using mtu */ + + /* save off netdev in virtpcidev */ + virtpcidev->net.netdev =3D netdev; + + /* start thread that will receive responses */ + writeq(readq(&vnicinfo->datachan.chinfo.queueinfo->chan->features) | + ULTRA_IO_CHANNEL_IS_POLLING, + &vnicinfo->datachan.chinfo.queueinfo->chan->features); + DBGINF("starting rsp thread queueinfo:%p threadinfo:%p\n", + vnicinfo->datachan.chinfo.queueinfo, + &vnicinfo->datachan.chinfo.threadinfo); + p_channel_header =3D vnicinfo->datachan.chinfo.queueinfo->chan; + pqhdr =3D (struct signal_queue_header __iomem *) + ((char __iomem *)p_channel_header + + readq(&p_channel_header->ch_space_offset)) + + IOCHAN_FROM_IOPART; + vnicinfo->flags_addr =3D (__force uint64_t __iomem *)&pqhdr->features= ; + vnicinfo->thread_wait_ms =3D 2; + if (!uisthread_start(&vnicinfo->datachan.chinfo.threadinfo, + process_incoming_rsps, &vnicinfo->datachan, + "vnic_incoming")) { + LOGERRNAME(vnicinfo->netdev, "**** FAILED to start thread\n"); + RETFAIL(-ENODEV); + } + + /* register_netdev */ + LOGINFNAME(vnicinfo->netdev, "sendInterruptHandle=3D0x%16llX", + (unsigned long long)vnicinfo->intr.send_irq_handle); + LOGINFNAME(vnicinfo->netdev, "recvInterruptHandle=3D0x%16llX", + (unsigned long long)vnicinfo->intr.recv_irq_handle); + LOGINFNAME(vnicinfo->netdev, "recvInterruptVector=3D0x%8X", + vnicinfo->intr.recv_irq_vector); + LOGINFNAME(vnicinfo->netdev, "recvInterruptShared=3D0x%2X", + vnicinfo->intr.recv_irq_shared); + LOGINFNAME(vnicinfo->netdev, "netdev->name=3D%s", netdev->name); + vnicinfo->interrupt_vector =3D vnicinfo->intr.recv_irq_handle & + INTERRUPT_VECTOR_MASK; + netdev->irq =3D vnicinfo->interrupt_vector; + err =3D register_netdev(netdev); + if (err) { + uisthread_stop(&vnicinfo->datachan.chinfo.threadinfo); + RETFAIL(err); + } + + /* create proc/ethx directory */ + vnicinfo->eth_debugfs_dir =3D debugfs_create_dir(netdev->name, + virtnic_debugfs_dir); + if (!vnicinfo->eth_debugfs_dir) { + LOGERRNAME(vnicinfo->netdev, + "****FAILED to create proc dir entry:%s\n", + netdev->name); + uisthread_stop(&vnicinfo->datachan.chinfo.threadinfo); + RETFAIL(-ENODEV); + } + + if (device_create_file(&netdev->dev, &dev_attr_zone) < 0) { + uisthread_stop(&vnicinfo->datachan.chinfo.threadinfo); + RETFAIL(-ENODEV); + } + if (device_create_file(&netdev->dev, &dev_attr_clientstr) < 0) { + device_remove_file(&netdev->dev, &dev_attr_zone); + uisthread_stop(&vnicinfo->datachan.chinfo.threadinfo); + RETFAIL(-ENODEV); + } + /* create proc/ethx directory */ + rsp =3D request_irq(vnicinfo->interrupt_vector, handler, IRQF_SHARED, + netdev->name, vnicinfo); + if (rsp !=3D 0) { + LOGERRNAME(vnicinfo->netdev, + "request_irq(%d) uislib_vnic_ISR request failed with rsp=3D%d\n"= , + vnicinfo->interrupt_vector, rsp); + vnicinfo->interrupt_vector =3D -1; + } else { + uint64_t __iomem *features_addr =3D + &vnicinfo->datachan.chinfo.queueinfo->chan->features; + LOGERRNAME(vnicinfo->netdev, + "request_irq(%d) uislib_vnic_ISR request succeeded\n", + vnicinfo->interrupt_vector); + mask =3D ~(ULTRA_IO_CHANNEL_IS_POLLING | + ULTRA_IO_DRIVER_DISABLES_INTS | + ULTRA_IO_DRIVER_SUPPORTS_ENHANCED_RCVBUF_CHECKING); + uisqueue_interlocked_and(features_addr, mask); + mask =3D ULTRA_IO_DRIVER_ENABLES_INTS | + ULTRA_IO_DRIVER_SUPPORTS_ENHANCED_RCVBUF_CHECKING; + uisqueue_interlocked_or(features_addr, mask); + + vnicinfo->thread_wait_ms =3D 2000; + } + + LOGINFNAME(vnicinfo->netdev, + "Added VirtNic:%p %s insertlock:%p %02x:%02x:%02x:%02x:%02x:%02x\= n", + netdev, netdev->name, &vnicinfo->datachan.chinfo.insertlock, + netdev->dev_addr[0], netdev->dev_addr[1], + netdev->dev_addr[2], netdev->dev_addr[3], + netdev->dev_addr[4], netdev->dev_addr[5]); + return 0; +} + +static void +virtnic_remove(struct virtpci_dev *virtpcidev) +{ + struct net_device *netdev =3D virtpcidev->net.netdev; + struct virtnic_info *vnicinfo; + + vnicinfo =3D netdev_priv(netdev); + + LOGINFNAME(vnicinfo->netdev, + "virtpcidev:%p netdev:%p name:%s vnicinfo:%p\n", + virtpcidev, netdev, netdev->name, vnicinfo); + LOGINFNAME(vnicinfo->netdev, + "virtpcidev busNo<<%d>>devNo<<%d>>", + virtpcidev->bus_no, virtpcidev->device_no); + /* REMOVE netdev */ + DBGINF("unregistering netdev\n"); + if (vnicinfo->interrupt_vector !=3D -1) + free_irq(vnicinfo->interrupt_vector, vnicinfo); + unregister_netdev(netdev); + /* this is going to call virtnic_close which will send out */ + /* disable don't take thread down until after that */ + uisthread_stop(&vnicinfo->datachan.chinfo.threadinfo); + + /* freeing of rcv bufs should have happened in close. */ + /* free cmdrsp we allocated for rcv post/unpost */ + kfree(vnicinfo->cmdrsp_rcv); + kfree(vnicinfo->xmit_cmdrsp); + + /* delete proc file entries */ + device_remove_file(&netdev->dev, &dev_attr_zone); + device_remove_file(&netdev->dev, &dev_attr_clientstr); + + debugfs_remove(vnicinfo->eth_debugfs_dir); + LOGINFNAME(vnicinfo->netdev, "removed dentry %s\n", + netdev->name); + + kfree(vnicinfo->rcvbuf); + free_netdev(netdev); + + LOGINF("virtnic removed\n"); +} + +/*****************************************************/ +/* NIC statistics handling */ +/*****************************************************/ + +/* update rcv stats - locking done by invoker */ +#define UPD_RCV_STATS { \ + vnicinfo->net_stats.rx_packets++; \ + vnicinfo->net_stats.rx_bytes +=3D skb->len; \ +} + +/* update xmt stats - locking done by invoker */ +#define UPD_XMT_STATS { \ + vnicinfo->net_stats.tx_packets++; \ + vnicinfo->net_stats.tx_bytes +=3D skb->len; \ +} + +static struct net_device_stats * +virtnic_get_stats(struct net_device *netdev) +{ + struct virtnic_info *vnicinfo =3D netdev_priv(netdev); + + /* take this opportunity to print out our internal stats */ + DBGINF + ("NET_RCV_ENBDIS sent: %ld NET_RCV_ENBDIS_ACK received: %ld\n= ", + vnicinfo->datachan.chstat.sent_enbdis, + vnicinfo->datachan.chstat.got_enbdisack); + + DBGINF("NET_RCV received: %ld NET_RCV_POST sent: %ld\n", + vnicinfo->datachan.chstat.got_rcv, + vnicinfo->datachan.chstat.sent_post); + + DBGINF("extra NET_RCV_POST sent: %ld\n", + vnicinfo->datachan.chstat.extra_rcvbufs_sent); + + DBGINF("NET_XMIT sent: %ld NET_XMIT_DONE received: %ld\n", + vnicinfo->datachan.chstat.sent_xmit, + vnicinfo->datachan.chstat.got_xmit_done); + + DBGINF("XMIT failures: %ld NET_RCV_PROMISC sent: %ld\n", + vnicinfo->datachan.chstat.xmit_fail, + vnicinfo->datachan.chstat.sent_promisc); + + DBGINF("XMIT reject/busy: %ld\n", + vnicinfo->datachan.chstat.reject_count); + + return &vnicinfo->net_stats; +} + +/*****************************************************/ +/* Local functions */ +/*****************************************************/ + +/* + * This function allocates skb, skb->data for first fragment. If Mtu + * size is > default, it allocates frags. + */ +static struct sk_buff * +alloc_rcv_buf(struct net_device *netdev) +{ + struct sk_buff *skb; + +/* + * NOTE: the first fragment in each rcv buffer is pointed to by rcvskb= ->data. + * For now all rcv buffers will be RCVPOST_BUF_SIZE in length, so the = firstfrag + * is large enough to hold 1514. + */ + DBGINF("netdev->name <<%s>>: allocating skb len:%d\n", netdev->name, + RCVPOST_BUF_SIZE); + skb =3D alloc_skb(RCVPOST_BUF_SIZE, GFP_ATOMIC | __GFP_NOWARN); + if (!skb) { + LOGVER("**** alloc_skb failed\n"); + return NULL; + } + skb->dev =3D netdev; + skb->len =3D RCVPOST_BUF_SIZE; + /* current value of mtu doesn't come into play here; large + * packets will just end up using multiple rcv buffers all of + * same size + */ + skb->data_len =3D 0; /* dev_alloc_skb already zeroes it out. + for clarification. */ + return skb; +} + +static int +init_rcv_bufs(struct net_device *netdev, struct virtnic_info *vnicinfo= ) +{ + int i, count; + + DBGINF("netdev->name <<%s>>", netdev->name); + /* + * allocate fixed number of receive buffers to post to uisnic + * post receive buffers after we've allocated a required + * amount + */ + for (i =3D 0; i < vnicinfo->num_rcv_bufs; i++) { + vnicinfo->rcvbuf[i] =3D alloc_rcv_buf(netdev); + if (!vnicinfo->rcvbuf[i]) + break; /* if we failed to allocate one let us stop */ + } + if (i < vnicinfo->num_rcv_bufs) { + LOGWRNNAME(vnicinfo->netdev, + "only allocated %d of %d receive buffers", i, + vnicinfo->num_rcv_bufs); + if (i =3D=3D 0) { + /* couldn't even allocate one - bail out */ + LOGERRNAME(vnicinfo->netdev, + "**** FAILED to allocate any rcv buffers\n"); + return -ENOMEM; + } + } + count =3D i; + /* Ensure we can alloc 2/3rd of the requested number of + * buffers. 2/3 is an arbitraty choice; used also in ndis + * init.c. + */ + if (count < ((2 * vnicinfo->num_rcv_bufs) / 3)) { + LOGERRNAME(vnicinfo->netdev, + "**** FAILED to allocate enough rcv bufs; allocated only:%d MAX_= NET_RCV_BUFS:%d\n", + count, MAX_NET_RCV_BUFS); + /* free receive buffers we did allocate and then bail out */ + for (i =3D 0; i < count; i++) { + kfree_skb(vnicinfo->rcvbuf[i]); + vnicinfo->rcvbuf[i] =3D NULL; + } + return -ENOMEM; + } + + /* post receive buffers to receive incoming input - without holding *= / + /* lock - we've not enabled nor started the queue so there shouldn't = */ + /* be any rcv or xmit activity */ + for (i =3D 0; i < count; i++) + post_skb(vnicinfo->cmdrsp_rcv, vnicinfo, vnicinfo->rcvbuf[i]); + + /* push through with what buffers we've got - unallocated ones will *= / + /* be null */ + LOGINFNAME(vnicinfo->netdev, "Allocated & posted %d rcv buffers\n", + count); + + return 0; +} + +/* Sends disable to IOVM and frees receive buffers that were posted to + * IOVM (cleared by IOVM when disable is received) + * returns 0 on success, negative number on failure + * + * timeout is defined in msecs (timeout of 0 specifies infinite wait) + */ +static int +virtnic_disable_with_timeout(struct net_device *netdev, const int time= out) +{ + struct virtnic_info *vnicinfo =3D netdev_priv(netdev); + int i, count =3D 0; + unsigned long flags; + int wait =3D 0; + + LOGINFNAME(vnicinfo->netdev, "netdev->name <<%s>>", netdev->name); + /* stop the transmit queue so nothing more can be transmitted */ + netif_stop_queue(netdev); + + /* send a msg telling the other end we are stopping incoming pkts */ + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + vnicinfo->enabled =3D 0; + vnicinfo->enab_dis_acked =3D 0; /* must wait for ack */ + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + + /* send disable and wait for ack - don't hold lock when + * sending disable because if the queue is full, insert might + * sleep. + */ + SEND_ENBDIS(netdev, 0, vnicinfo->cmdrsp_rcv, + vnicinfo->datachan.chinfo.queueinfo, + &vnicinfo->datachan.chinfo.insertlock, + vnicinfo->datachan.chstat); + + LOGINFNAME(vnicinfo->netdev, + "Waiting for ENBDIS ACK before freeing rcv buffers...\n"); + /* wait for ack to arrive before we try to free rcv buffers + * NOTE: the other end automatically unposts the rcv buffers + * when it gets a disable. + */ + while ((timeout =3D=3D VIRTNIC_INFINITE_RESPONSE_WAIT) || + (wait < timeout)) { + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + if (vnicinfo->n_rcv_packet_not_accepted) { + /* now we can continue with disable */ + break; + } else if (vnicinfo->server_down || + vnicinfo->server_change_state) { + LOGERRNAME(vnicinfo->netdev, + "IOVM is down so disable will not be acknowledged. Stopping wa= it.\n"); + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + return -1; + } + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + wait +=3D schedule_timeout(msecs_to_jiffies(10)); + } + if (!vnicinfo->n_rcv_packet_not_accepted) { + LOGERRNAME(vnicinfo->netdev, + "IOVM did not respond to Disable in allocated time (%d msecs).\n= ", + timeout); + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + return -1; + } + LOGINFNAME(vnicinfo->netdev, + "Got ENBDIS ACK; now waiting for 0 usage count...\n"); + + /* + * wait for usage to go to 1 (no other users) before freeing + * rcv buffers + */ + if (atomic_read(&vnicinfo->usage) > 1) { + /* wait for usage count to be 1 */ + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + schedule_timeout(msecs_to_jiffies(10)); + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + if (atomic_read(&vnicinfo->usage) =3D=3D 1) { + break; /* go do work and only after + that give up lock */ + } + } + } + /* we've set enabled to 0, so we can give up the lock. */ + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + LOGINFNAME(vnicinfo->netdev, + "Usage count is 0; freeing the rcv buffers now\n"); + + /* free rcv buffers - other end has automatically unposted + * them on disable + */ + for (i =3D 0; i < vnicinfo->num_rcv_bufs; i++) { + if (vnicinfo->rcvbuf[i]) { + kfree_skb(vnicinfo->rcvbuf[i]); + vnicinfo->rcvbuf[i] =3D NULL; + count++; + } + } + LOGINFNAME(vnicinfo->netdev, "Freed %d rcv bufs\n", count); + + /* remove references from debug array */ + for (i =3D 0; i < VIRTNICSOPENMAX; i++) { + if (num_virtnic_open[i].netdev =3D=3D netdev) { + num_virtnic_open[i].netdev =3D NULL; + num_virtnic_open[i].vnicinfo =3D NULL; + break; + } + } + + return 0; +} + +/* Wait indefinitely for IOVM to acknowledge disable request */ +static int +virtnic_disable(struct net_device *netdev) +{ + return virtnic_disable_with_timeout(netdev, + VIRTNIC_INFINITE_RESPONSE_WAIT); +} + +/* Sends enable to IOVM, inits, and posts receive buffers to IOVM + * returns 0 on success, negative number on failure + * + * timeout is defined in msecs (timeout of 0 specifies infinite wait) + */ +static int +virtnic_enable_with_timeout(struct net_device *netdev, const int timeo= ut) +{ + int i; + struct virtnic_info *vnicinfo =3D netdev_priv(netdev); + unsigned long flags; + int wait =3D 0; + + /* NOTE: the other end automatically unposts the rcv buffers when + * it gets a disable. + */ + i =3D init_rcv_bufs(netdev, vnicinfo); + if (i < 0) + return i; + + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + vnicinfo->enabled =3D 1; + /* now we're ready, let's send an ENB to uisnic but until we + * get an ACK back from uisnic, we'll drop the packets + */ + vnicinfo->n_rcv_packet_not_accepted =3D 0; + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + + /* send enable and wait for ack - don't hold lock when sending + * enable because if the queue is full, insert might sleep. + */ + SEND_ENBDIS(netdev, 1, vnicinfo->cmdrsp_rcv, + vnicinfo->datachan.chinfo.queueinfo, + &vnicinfo->datachan.chinfo.insertlock, + vnicinfo->datachan.chstat); + + LOGINFNAME(vnicinfo->netdev, "netdev->name <<%s>>", netdev->name); + LOGINFNAME(vnicinfo->netdev, + "Waiting for ENBDIS ACK before starting device queue...\n"); + while ((timeout =3D=3D VIRTNIC_INFINITE_RESPONSE_WAIT) || + (wait < timeout)) { + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + if (vnicinfo->enab_dis_acked) { + /* now we can continue */ + break; + } else if (vnicinfo->server_down || + vnicinfo->server_change_state) { + /* IOVM is going down so don't wait for a response */ + LOGERRNAME(vnicinfo->netdev, + "IOVM is down so enable will not be acknowledged. Stopping wai= t.\n"); + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + return -1; + } + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + wait +=3D schedule_timeout(msecs_to_jiffies(10)); + } + if (!vnicinfo->enab_dis_acked) { + LOGERRNAME(vnicinfo->netdev, + "IOVM did not respond to Enable in allocated time (%d msecs).\n"= , + timeout); + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + return -1; + } + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + LOGINFNAME(vnicinfo->netdev, "Got ENBDIS ACK\n"); + + /* find an open slot in the array to save off VirtNic + * references for debug + */ + for (i =3D 0; i < VIRTNICSOPENMAX; i++) { + if (num_virtnic_open[i].netdev =3D=3D NULL) { + num_virtnic_open[i].netdev =3D netdev; + num_virtnic_open[i].vnicinfo =3D vnicinfo; + break; + } + } + if (i =3D=3D VIRTNICSOPENMAX) + LOGINFNAME(vnicinfo->netdev, + "No storage for debug ref for netdev =3D 0x%p vnicinfo =3D 0x%p\= n", + netdev, vnicinfo); + + return 0; +} + +/* Wait indefinitely for IOVM to acknowledge enable request */ +static int +virtnic_enable(struct net_device *netdev) +{ + return virtnic_enable_with_timeout(netdev, + VIRTNIC_INFINITE_RESPONSE_WAIT); +} + +static void +send_rcv_posts_if_needed(struct virtnic_info *vnicinfo) +{ + int i; + struct net_device *netdev; + struct uiscmdrsp *cmdrsp =3D vnicinfo->cmdrsp_rcv; + int cur_num_rcv_bufs_to_alloc, rcv_bufs_allocated; + + if (!(vnicinfo->enabled && vnicinfo->enab_dis_acked)) { + /* dont do this until vnic is marked ready. */ + return; + } + netdev =3D vnicinfo->netdev; + rcv_bufs_allocated =3D 0; + /* this code is trying to prevent getting stuck here forever, + * but still retry it if you cant allocate them all this + * time. + */ + cur_num_rcv_bufs_to_alloc =3D vnicinfo->num_rcv_bufs_could_not_alloc; + while (cur_num_rcv_bufs_to_alloc > 0) { + cur_num_rcv_bufs_to_alloc--; + for (i =3D 0; i < vnicinfo->num_rcv_bufs; i++) { + if (vnicinfo->rcvbuf[i] !=3D NULL) + continue; + vnicinfo->rcvbuf[i] =3D alloc_rcv_buf(netdev); + if (!vnicinfo->rcvbuf[i]) { + LOGVER("**** %s FAILED to allocate new rcv buf - no REPOST\n", + netdev->name); + vnicinfo-> + alloc_failed_in_if_needed_cnt++; + break; + } else { + rcv_bufs_allocated++; + post_skb(cmdrsp, vnicinfo, + vnicinfo->rcvbuf[i]); + vnicinfo->datachan.chstat. + extra_rcvbufs_sent++; + } + } + } + vnicinfo->num_rcv_bufs_could_not_alloc -=3D rcv_bufs_allocated; + if (vnicinfo->num_rcv_bufs_could_not_alloc > 0) { + /* + * this path means you failed to alloc an skb in the + * normal path, and you are trying again later, and + * it still fails. + */ + LOGVER("attempted to recover buffers which could not be allocated an= d failed"); + LOGVER("rcv_bufs_allocated=3D%d, num_rcv_bufs_could_not_alloc=3D%d", + rcv_bufs_allocated, + vnicinfo->num_rcv_bufs_could_not_alloc); + } +} + +static void +drain_queue(struct datachan *dc, struct uiscmdrsp *cmdrsp, + struct virtnic_info *vnicinfo) +{ + unsigned long flags; + int qrslt; + struct net_device *netdev; + + /* drain queue */ + while (1) { + spin_lock_irqsave(&dc->chinfo.insertlock, flags); + if (!spar_channel_client_acquire_os(dc->chinfo.queueinfo->chan, + "vnic")) { + spin_unlock_irqrestore(&dc->chinfo.insertlock, + flags); + break; + } + qrslt =3D uisqueue_get_cmdrsp(dc->chinfo.queueinfo, cmdrsp, + IOCHAN_FROM_IOPART); + spar_channel_client_release_os(dc->chinfo.queueinfo->chan, + "vnic"); + spin_unlock_irqrestore(&dc->chinfo.insertlock, flags); + if (qrslt =3D=3D 0) + break; /* queue empty */ + DBGINF("%p cmdrsp->net.type:%d\n", + &dc->chinfo.queueinfo, cmdrsp->net.type); + switch (cmdrsp->net.type) { + case NET_RCV: + DBGINF("Got NET_RCV\n"); + dc->chstat.got_rcv++; + /* process incoming packet */ + virtnic_rx(cmdrsp); + break; + case NET_XMIT_DONE: + DBGINF("Got NET_XMIT_DONE %p\n", cmdrsp->net.buf); + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + dc->chstat.got_xmit_done++; + if (cmdrsp->net.xmtdone.xmt_done_result) { + LOGERRNAME(vnicinfo->netdev, + "XMIT_DONE failure buf:%p\n", + cmdrsp->net.buf); + dc->chstat.xmit_fail++; + } + /* only call queue wake if we stopped it */ + netdev =3D ((struct sk_buff *)cmdrsp->net.buf)->dev; + /* ASSERT netdev =3D=3D vnicinfo->netdev; */ + if (netdev !=3D vnicinfo->netdev) { + LOGERRNAME(vnicinfo->netdev, "NET_XMIT_DONE something wrong; vnici= nfo->netdev:%p !=3D cmdrsp->net.buf)->dev:%p\n", + vnicinfo->netdev, netdev); + } else if (netif_queue_stopped(netdev)) { + /* + * check to see if we have crossed + * the lower watermark for + * netif_wake_queue() + */ + if (((vnicinfo->datachan.chstat.sent_xmit >=3D + vnicinfo->datachan.chstat.got_xmit_done) && + (vnicinfo->datachan.chstat.sent_xmit - + vnicinfo->datachan.chstat.got_xmit_done <=3D + vnicinfo->lower_threshold_net_xmits)) || + ((vnicinfo->datachan.chstat.sent_xmit < + vnicinfo->datachan.chstat.got_xmit_done) && + (ULONG_MAX - + vnicinfo->datachan.chstat.got_xmit_done + + vnicinfo->datachan.chstat.sent_xmit <=3D + vnicinfo->lower_threshold_net_xmits))) { + /* + * enough NET_XMITs completed + * so can restart netif queue + */ + netif_wake_queue(netdev); + vnicinfo->flow_control_lower_hits++; + } + } + skb_unlink(cmdrsp->net.buf, &vnicinfo->xmitbufhead); + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + kfree_skb(cmdrsp->net.buf); + break; + case NET_RCV_ENBDIS_ACK: + DBGINF("Got NET_RCV_ENBDIS_ACK on:%p\n", + (struct net_device *) + cmdrsp->net.enbdis.context); + dc->chstat.got_enbdisack++; + netdev =3D (struct net_device *) + cmdrsp->net.enbdis.context; + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + vnicinfo->enab_dis_acked =3D 1; + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + + if (vnicinfo->server_down && + vnicinfo->server_change_state) { + /* Inform Linux that the link is up */ + vnicinfo->server_down =3D false; + vnicinfo->server_change_state =3D false; + netif_wake_queue(netdev); + netif_carrier_on(netdev); + } + break; + case NET_CONNECT_STATUS: + DBGINF("NET_CONNECT_STATUS, enable=3D:%d\n", + cmdrsp->net.enbdis.enable); + netdev =3D vnicinfo->netdev; + if (cmdrsp->net.enbdis.enable =3D=3D 1) { + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + vnicinfo->enabled =3D cmdrsp->net.enbdis.enable; + spin_unlock_irqrestore(&vnicinfo->priv_lock, + flags); + netif_wake_queue(netdev); + netif_carrier_on(netdev); + } else { + netif_stop_queue(netdev); + netif_carrier_off(netdev); + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + vnicinfo->enabled =3D cmdrsp->net.enbdis.enable; + spin_unlock_irqrestore(&vnicinfo->priv_lock, + flags); + } + break; + default: + LOGERRNAME(vnicinfo->netdev, + "Invalid net type:%d in cmdrsp\n", + cmdrsp->net.type); + break; + } + /* cmdrsp is now available for reuse */ + + if (dc->chinfo.threadinfo.should_stop) + break; + } +} + +static int +process_incoming_rsps(void *v) +{ + struct datachan *dc =3D v; + struct uiscmdrsp *cmdrsp =3D NULL; + const int SZ =3D SIZEOF_CMDRSP; + struct virtnic_info *vnicinfo; + struct channel_header __iomem *p_channel_header; + struct signal_queue_header __iomem *pqhdr; + uint64_t mask; + unsigned long long rc1; + + UIS_DAEMONIZE("vnic_incoming"); + DBGINF("In process_incoming_rsps pid:%d queueinfo:%p threadinfo:%p\n"= , + current->pid, dc->chinfo.queueinfo, &dc->chinfo.threadinfo); + /* alloc once and reuse */ + vnicinfo =3D container_of(dc, struct virtnic_info, datachan); + cmdrsp =3D kmalloc(SZ, GFP_ATOMIC); + if (cmdrsp =3D=3D NULL) { + LOGERRNAME(vnicinfo->netdev, + "**** FAILED to malloc - thread exiting\n"); + complete_and_exit(&dc->chinfo.threadinfo.has_stopped, 0); + } + p_channel_header =3D vnicinfo->datachan.chinfo.queueinfo->chan; + pqhdr =3D + (struct signal_queue_header __iomem *) + ((char __iomem *)p_channel_header + + readq(&p_channel_header->ch_space_offset)) + + IOCHAN_FROM_IOPART; + mask =3D ULTRA_CHANNEL_ENABLE_INTS; + while (1) { + wait_event_interruptible_timeout( + vnicinfo->rsp_queue, (atomic_read + (&vnicinfo->interrupt_rcvd) =3D=3D 1), + msecs_to_jiffies(vnicinfo->thread_wait_ms)); + /* + * periodically check to see if there any rcv bufs which + * need to get sent to the iovm. This can only happen if + * we run out of memory when trying to allocate skbs. + */ + atomic_set(&vnicinfo->interrupt_rcvd, 0); + send_rcv_posts_if_needed(vnicinfo); + drain_queue(dc, cmdrsp, vnicinfo); + rc1 =3D uisqueue_interlocked_or((uint64_t __iomem *) + vnicinfo->flags_addr, mask); + if (dc->chinfo.threadinfo.should_stop) + break; + } + + kfree(cmdrsp); + DBGINF("In process_incoming_nic_rsp exiting\n"); + complete_and_exit(&dc->chinfo.threadinfo.has_stopped, 0); +} + +/*****************************************************/ +/* NIC support functions called external */ +/*****************************************************/ + +static int +virtnic_change_mtu(struct net_device *netdev, int new_mtu) +{ + LOGERRNAME(netdev, "netdev->name <<%s>>", netdev->name); + LOGERRNAME(netdev, "**** FAILED: MTU cannot be changed at this end.\n= "); + LOGERRNAME(netdev, "The same MTU is used for all the PNICs and VNICs = in a switch.\n"); + LOGERRNAME(netdev, "Please change MTU from the Resource Partition\n")= ; + LOGERRNAME(netdev, "Current MTU is: %d\n", netdev->mtu); + return -EINVAL; + /* + * we cannot willy-nilly change the MTU; it has to come from + * CONTROL VM and all the vnics and pnics in a switch have to + * have the same MTU for everything to work. + */ +} + +/* + * Called by kernel when ifconfig down is run. + * Returns 0 on success, negative value on failure. + */ +static int +virtnic_close(struct net_device *netdev) +{ + /* this is called on ifconfig down but also if the device is + * being removed + */ + LOGINFNAME(netdev, "Closing %p name:%s\n", netdev, netdev->name); + + netif_stop_queue(netdev); + virtnic_disable(netdev); + + LOGINFNAME(netdev, "Closed:%p\n", netdev); + + return 0; +} + +static int +virtnic_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) +{ + return -EOPNOTSUPP; +} + +/* + * Called by kernel when ifconfig up is run. + * Returns 0 on success, negative value on failure. +*/ +static int +virtnic_open(struct net_device *netdev) +{ + struct virtnic_info *vnicinfo =3D netdev_priv(netdev); + void *p =3D (__force void *)netdev->ip_ptr; + + LOGINFNAME(vnicinfo->netdev, + "Opening %p name:%s allocating:%d rcvbufs mtu:%d\n", netdev, + netdev->name, vnicinfo->num_rcv_bufs, netdev->mtu); + + virtnic_enable(netdev); + /* start the interface's transmit queue, allowing it accept + * packets for transmission + */ + netif_start_queue(netdev); + + LOGINFNAME(vnicinfo->netdev, + "Opened %p netdev->ip_ptr:%p name:%s %02x:%02x:%02x:%02x:%02x:%02= x\n", + netdev, netdev->ip_ptr, netdev->name, netdev->dev_addr[0], + netdev->dev_addr[1], netdev->dev_addr[2], + netdev->dev_addr[3], netdev->dev_addr[4], + netdev->dev_addr[5]); + + /* + * temporary code to see trap to catch if vnic inet addresses + * are getting trashed + */ + if (p !=3D (__force void *)netdev->ip_ptr) { + LOGERRNAME(vnicinfo->netdev, "***********FAILURE HAPPENED\n"); + LOGERRNAME(vnicinfo->netdev, " Test to catch if vnic inet = addresses are getting trashed.\n"); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(1000)); + } + return 0; +} + +static inline int +repost_return( + struct uiscmdrsp *cmdrsp, + struct virtnic_info *vnicinfo, + struct sk_buff *skb, + struct net_device *netdev) +{ + struct net_pkt_rcv copy; + int i =3D 0, cc, numreposted; + int found_skb =3D 0; + int status =3D 0; + + copy =3D cmdrsp->net.rcv; + LOGVER("REPOST_RETURN: realloc rcv skbs to replace:%d rcvbufs\n", + copy.numrcvbufs); + switch (copy.numrcvbufs) { + case 0: + vnicinfo->n_rcv0++; + break; + case 1: + vnicinfo->n_rcv1++; + break; + case 2: + vnicinfo->n_rcv2++; + break; + default: + vnicinfo->n_rcvx++; + break; + } + for (cc =3D 0, numreposted =3D 0; cc < copy.numrcvbufs; cc++) { + for (i =3D 0; i < vnicinfo->num_rcv_bufs; i++) { + if (vnicinfo->rcvbuf[i] !=3D copy.rcvbuf[cc]) + continue; + + LOGVER("REPOST_RETURN: orphaning old rcvbuf[%d]:%p cc=3D%d", + i, vnicinfo->rcvbuf[i], cc); + vnicinfo->found_repost_rcvbuf_cnt++; + if ((skb) && vnicinfo->rcvbuf[i] =3D=3D skb) { + found_skb =3D 1; + vnicinfo->repost_found_skb_cnt++; + } + vnicinfo->rcvbuf[i] =3D alloc_rcv_buf(netdev); + if (!vnicinfo->rcvbuf[i]) { + LOGVER("**** %s FAILED to reallocate new rcv buf - no REPOST, foun= d_skb=3D%d, cc=3D%d, i=3D%d\n", + netdev->name, found_skb, cc, i); + vnicinfo->num_rcv_bufs_could_not_alloc++; + vnicinfo->alloc_failed_in_repost_return_cnt++; + status =3D -1; + break; + } + LOGVER("REPOST_RETURN: reposting new rcvbuf[%d]:%p\n", + i, vnicinfo->rcvbuf[i]); + post_skb(cmdrsp, vnicinfo, vnicinfo->rcvbuf[i]); + numreposted++; + break; + } + } + LOGVER("REPOST_RETURN: num rcvbufs posted:%d\n", numreposted); + if (numreposted !=3D copy.numrcvbufs) { + LOGVER("**** %s FAILED to repost all the rcv bufs; numreposted:%d rc= v.numrcvbufs:%d\n", + netdev->name, numreposted, copy.numrcvbufs); + vnicinfo->n_repost_deficit++; + status =3D -1; + } + if (skb) { + if (found_skb) { + LOGVER("REPOST_RETURN: skb is %p - freeing it", skb); + kfree_skb(skb); + } else { + LOGERRNAME(vnicinfo->netdev, "%s REPOST_RETURN: skb %p NOT found in= rcvbuf list!!", + netdev->name, skb); + status =3D -3; + vnicinfo->bad_rcv_buf++; + } + } + atomic_dec(&vnicinfo->usage); + return status; +} + +static void +virtnic_rx(struct uiscmdrsp *cmdrsp) +{ + struct virtnic_info *vnicinfo; + struct sk_buff *skb, *prev, *curr; + struct net_device *netdev; + int cc, currsize, off, status; + struct ethhdr *eth; + unsigned long flags; +#ifdef DEBUG + struct phys_info testfrags[MAX_PHYS_INFO]; +#endif + +/* + * post new rcv buf to the other end using the cmdrsp we have at hand + * post it without holding lock - but we'll use the signal lock to syn= chronize + * the queue insert the cmdrsp that contains the net.rcv is the one we= are + * using to repost, so copy the info we need from it. + */ + skb =3D cmdrsp->net.buf; + netdev =3D skb->dev; + + if (netdev) + DBGINF("in virtnic_rx %p %s len:%d\n", netdev, netdev->name, + cmdrsp->net.rcv.rcv_done_len); + else { + /* We must have previously downed this network device and + * this skb and device is no longer valid. This also means + * the skb reference was removed from virtnic->rcvbuf so no + * need to search for it. + * All we can do is free the skb and return. + * Note: We crash if we try to log this here. + */ + kfree_skb(skb); + return; + } + + vnicinfo =3D netdev_priv(netdev); + + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + atomic_dec(&vnicinfo->num_rcv_bufs_in_iovm); + + /* update rcv stats - call it with priv_lock held */ + UPD_RCV_STATS; + + atomic_inc(&vnicinfo->usage); /* don't want a close to happen before + we're done here */ + /* + * set length to how much was ACTUALLY received - + * NOTE: rcv_done_len includes actual length of data rcvd + * including ethhdr + */ + skb->len =3D cmdrsp->net.rcv.rcv_done_len; + + /* test enabled while holding lock */ + if (!(vnicinfo->enabled && vnicinfo->enab_dis_acked)) { + /* + * don't process it unless we're in enable mode and until + * we've gotten an ACK saying the other end got our RCV enable + */ + LOGERRNAME(vnicinfo->netdev, + "%s dropping packet - perhaps old\n", netdev->name); + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + if (repost_return(cmdrsp, vnicinfo, skb, netdev) < 0) + LOGERRNAME(vnicinfo->netdev, "repost_return failed"); + return; + } + + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + + /* + * when skb was allocated, skb->dev, skb->data, skb->len and + * skb->data_len were setup. AND, data has already put into the + * skb (both first frag and in frags pages) + * NOTE: firstfragslen is the amount of data in skb->data and that + * which is not in nr_frags or frag_list. This is now simply + * RCVPOST_BUF_SIZE. bump tail to show how much data is in + * firstfrag & set data_len to show rest see if we have to chain + * frag_list. + */ + if (skb->len > RCVPOST_BUF_SIZE) { /* do PRECAUTIONARY check */ + if (cmdrsp->net.rcv.numrcvbufs < 2) { + LOGERRNAME(vnicinfo->netdev, "**** %s Something is wrong; rcv_done_= len:%d > RCVPOST_BUF_SIZE:%d but numrcvbufs:%d < 2\n", + netdev->name, skb->len, RCVPOST_BUF_SIZE, + cmdrsp->net.rcv.numrcvbufs); + if (repost_return(cmdrsp, vnicinfo, skb, netdev) < 0) + LOGERRNAME(vnicinfo->netdev, + "repost_return failed"); + return; + } + /* length rcvd is greater than firstfrag in this skb rcv buf */ + skb->tail +=3D RCVPOST_BUF_SIZE; /* amount in skb->data */ + skb->data_len =3D skb->len - RCVPOST_BUF_SIZE; /* amount that + will be in + frag_list */ + DBGINF("len:%d data:%d\n", skb->len, skb->data_len); + } else { + /* + * data fits in this skb - no chaining - do PRECAUTIONARY check + */ + if (cmdrsp->net.rcv.numrcvbufs !=3D 1) { /* should be 1 */ + LOGERRNAME(vnicinfo->netdev, "**** %s Something is wrong; rcv_done_= len:%d <=3D RCVPOST_BUF_SIZE:%d but numrcvbufs:%d !=3D 1\n", + netdev->name, skb->len, RCVPOST_BUF_SIZE, + cmdrsp->net.rcv.numrcvbufs); + if (repost_return(cmdrsp, vnicinfo, skb, netdev) < 0) + LOGERRNAME(vnicinfo->netdev, + "repost_return failed"); + return; + } + skb->tail +=3D skb->len; + skb->data_len =3D 0; /* nothing rcvd in frag_list */ + } + off =3D skb_tail_pointer(skb) - skb->data; + /* + * amount we bumped tail by in the head skb + * it is used to calculate the size of each chained skb below + * it is also used to index into bufline to continue the copy + * (for chansocktwopc) + * if necessary chain the rcv skbs together. + * NOTE: index 0 has the same as cmdrsp->net.rcv.skb; we need to + * chain the rest to that one. + * - do PRECAUTIONARY check + */ + if (cmdrsp->net.rcv.rcvbuf[0] !=3D skb) { + LOGERRNAME(vnicinfo->netdev, "**** %s Something is wrong; rcvbuf[0]:= %p !=3D skb:%p\n", + netdev->name, cmdrsp->net.rcv.rcvbuf[0], skb); + if (repost_return(cmdrsp, vnicinfo, skb, netdev) < 0) + LOGERRNAME(vnicinfo->netdev, "repost_return failed"); + return; + } + + if (cmdrsp->net.rcv.numrcvbufs > 1) { + /* chain the various rcv buffers into the skb's frag_list. */ + /* Note: off was initialized above */ + for (cc =3D 1, prev =3D NULL; + cc < cmdrsp->net.rcv.numrcvbufs; cc++) { + curr =3D (struct sk_buff *)cmdrsp->net.rcv.rcvbuf[cc]; + curr->next =3D NULL; + DBGINF("chaining skb:%p data:%p to skb:%p data:%p\n", + curr, curr->data, skb, skb->data); + if (prev =3D=3D NULL) /* start of list- set head */ + skb_shinfo(skb)->frag_list =3D curr; + else + prev->next =3D curr; + prev =3D curr; + /* + * should we set skb->len and skb->data_len for each + * buffer being chained??? can't hurt! + */ + currsize =3D + min(skb->len - off, + (unsigned int)RCVPOST_BUF_SIZE); + curr->len =3D currsize; + curr->tail +=3D currsize; + curr->data_len =3D 0; + off +=3D currsize; + } +#ifdef DEBUG + /* assert skb->len =3D=3D off */ + if (skb->len !=3D off) { + LOGERRNAME(vnicinfo->netdev, "%s something wrong; skb->len:%d !=3D = off:%d\n", + netdev->name, skb->len, off); + } + /* test code */ + cc =3D util_copy_fragsinfo_from_skb("rcvchaintest", skb, + RCVPOST_BUF_SIZE, + MAX_PHYS_INFO, testfrags); + LOGINFNAME(vnicinfo->netdev, "rcvchaintest returned:%d\n", cc); + if (cc !=3D cmdrsp->net.rcv.numrcvbufs) { + LOGERRNAME(vnicinfo->netdev, "**** %s Something wrong; rcvd chain l= ength %d different from one we calculated %d\n", + netdev->name, cmdrsp->net.rcv.numrcvbufs, + cc); + } + for (i =3D 0; i < cc; i++) { + LOGINFNAME(vnicinfo->netdev, "test:RCVPOST_BUF_SIZE:%d[%d] pfn:%llu= off:0x%x len:%d\n", + RCVPOST_BUF_SIZE, i, testfrags[i].pi_pfn, + testfrags[i].pi_off, testfrags[i].pi_len); + } +#endif + } + + /* set up packet's protocl type using ethernet header - this + * sets up skb->pkt_type & it also PULLS out the eth header + */ + skb->protocol =3D eth_type_trans(skb, netdev); + + eth =3D eth_hdr(skb); + + DBGINF("%d Src:%02x:%02x:%02x:%02x:%02x:%02x Dest:%02x:%02x:%02x:%02x= :%02x:%02x proto:%x\n", + skb->pkt_type, eth->h_source[0], eth->h_source[1], + eth->h_source[2], eth->h_source[3], eth->h_source[4], + eth->h_source[5], eth->h_dest[0], eth->h_dest[1], eth->h_dest[= 2], + eth->h_dest[3], eth->h_dest[4], eth->h_dest[5], eth->h_proto); + + skb->csum =3D 0; + skb->ip_summed =3D CHECKSUM_NONE; /* trust me, the checksum has + been verified */ + + do { + if (netdev->flags & IFF_PROMISC) { + DBGINF("IFF_PROMISC is set.\n"); + break; /* accept all packets */ + } + if (skb->pkt_type =3D=3D PACKET_BROADCAST) { + DBGINF("packet is broadcast.\n"); + if (netdev->flags & IFF_BROADCAST) { + DBGINF("IFF_BROADCAST is set.\n"); + break; /* accept all broadcast packets */ + } + } else if (skb->pkt_type =3D=3D PACKET_MULTICAST) { + DBGINF("packet is multicast.\n"); + if (netdev->flags & IFF_ALLMULTI) + DBGINF("IFF_ALLMULTI is set.\n"); + if ((netdev->flags & IFF_MULTICAST) && + (netdev_mc_count(netdev))) { + struct netdev_hw_addr *ha; + int found_mc =3D 0; + + DBGINF("IFF_MULTICAST is set %d.\n", + netdev_mc_count(netdev)); + /* + * only accept multicast packets that we can + * find in our multicast address list + */ + netdev_for_each_mc_addr(ha, netdev) { + if (memcmp + (eth->h_dest, ha->addr, + MAX_MACADDR_LEN) =3D=3D 0) { + DBGINF("multicast address is in our list at index:%i.\n", i); + found_mc =3D 1; + break; + } + } + if (found_mc) { + break; /* accept packet, dest + matches a multicast + address */ + } + } + } else if (skb->pkt_type =3D=3D PACKET_HOST) { + DBGINF("packet is directed.\n"); + break; /* accept packet, h_dest must match vnic + mac address */ + } else if (skb->pkt_type =3D=3D PACKET_OTHERHOST) { + /* something is not right */ + LOGERRNAME(vnicinfo->netdev, "**** FAILED to deliver rcv packet to = OS; name:%s Dest:%02x:%02x:%02x:%02x:%02x:%02x VNIC:%02x:%02x:%02x:%02x= :%02x:%02x\n", + netdev->name, eth->h_dest[0], eth->h_dest[1], + eth->h_dest[2], eth->h_dest[3], + eth->h_dest[4], eth->h_dest[5], + netdev->dev_addr[0], netdev->dev_addr[1], + netdev->dev_addr[2], netdev->dev_addr[3], + netdev->dev_addr[4], netdev->dev_addr[5]); + } + /* drop packet - don't forward it up to OS */ + DBGINF("we cannot indicate this recv pkt! (netdev->flags:0x%04x, skb= ->pkt_type:0x%02x).\n", + netdev->flags, skb->pkt_type); + vnicinfo->n_rcv_packet_not_accepted++; + if (repost_return(cmdrsp, vnicinfo, skb, netdev) < 0) + LOGERRNAME(vnicinfo->netdev, "repost_return failed"); + return; + } while (0); + + DBGINF("Calling netif_rx skb:%p head:%p end:%p data:%p tail:%p len:%d= data_len:%d skb->nr_frags:%d\n", + skb, skb->head, skb->end, skb->data, skb->tail, skb->len, + skb->data_len, skb_shinfo(skb)->nr_frags); + + status =3D netif_rx(skb); + if (status !=3D NET_RX_SUCCESS) + LOGWRNNAME(vnicinfo->netdev, "status=3D%d\n", status); + /* + * netif_rx returns various values, but "in practice most drivers + * ignore the return value + */ + + skb =3D NULL; + /* + * whether the packet got dropped or handled, the skb is freed by + * kernel code, so we shouldn't free it. but we should repost a + * new rcv buffer. + */ + if (repost_return(cmdrsp, vnicinfo, skb, netdev) < 0) + LOGVER("repost_return failed"); + return; +} + +/* + * This function is protected from concurrent calls by a spinlock xmit= _lock + * in the net_device struct, but as soon as the function returns it c= an be + * called again. + * Return 0, OK, !0 for error. + */ +static int +virtnic_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + struct virtnic_info *vnicinfo; + int len, firstfraglen, padlen; + struct uiscmdrsp *cmdrsp =3D NULL; + unsigned long flags; + int qrslt; + +/* Note: NETDEV_TX_OK is 0, NETDEV_TX_BUSY is 1. */ +#define BUSY { \ + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); \ + vnicinfo->busy_cnt++; \ + return NETDEV_TX_BUSY; \ +} + +/* return value NETDEV_TX_OK =3D=3D 0 */ + DBGINF("got xmit for netdev:%p %s len:%d ip_summed:%d skb->data:%p da= ta_len:%d skb->h.raw:%p maxdatalen:%d\n", + netdev, netdev->name, skb->len, skb->ip_summed, skb->data, + skb->data_len, skb->h.raw, skb->end - skb->data); + + vnicinfo =3D netdev_priv(netdev); + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + /*Modified for Trac #2395 FIX TEL_CKS */ + if (netif_queue_stopped(netdev)) { + LOGINFNAME(vnicinfo->netdev, + "Returning Busy because queue is stopped\n"); + BUSY; + } + if (vnicinfo->server_down || vnicinfo->server_change_state) { + LOGINFNAME(vnicinfo->netdev, "Returning BUSY because server is down/= changing state\n"); + BUSY; + } + /* + * sk_buff struct is used to host network data throughout all the + * Linux network subsystems + */ + len =3D skb->len; + /* + * skb->len is the FULL length of data (including fragmentary portion= ) + * skb->data_len is the length of the fragment portion in frags + * skb->len - skb->data_len is the size of the 1st fragment in skb->d= ata + * calculate the length of the first fragment that skb->data is + * pointing to + */ + firstfraglen =3D skb->len - skb->data_len; + if (firstfraglen < ETH_HEADER_SIZE) { + LOGERRNAME(vnicinfo->netdev, "first fragment in skb->data too small = for ethernet header len:%d data_len:%d\n", + skb->len, skb->data_len); + BUSY; /* NOT LIKELY TO HAPPEN */ + } + + if ((len < ETH_MIN_PACKET_SIZE) && + ((skb_end_pointer(skb) - skb->data) >=3D ETH_MIN_PACKET_SIZE)) { + /* pad the packet out to minimum size */ + padlen =3D ETH_MIN_PACKET_SIZE - len; + DBGINF("padding %d\n", padlen); + memset(&skb->data[len], 0, padlen); + skb->tail +=3D padlen; + skb->len +=3D padlen; + len +=3D padlen; + firstfraglen +=3D padlen; + } + + cmdrsp =3D vnicinfo->xmit_cmdrsp; + /* clear cmdrsp */ + memset(cmdrsp, 0, SIZEOF_CMDRSP); + cmdrsp->net.type =3D NET_XMIT; + cmdrsp->cmdtype =3D CMD_NET_TYPE; + + /* save the pointer to skb - we'll need it for completion */ + cmdrsp->net.buf =3D skb; + + if (((vnicinfo->datachan.chstat.sent_xmit >=3D + vnicinfo->datachan.chstat.got_xmit_done) && + (vnicinfo->datachan.chstat.sent_xmit - + vnicinfo->datachan.chstat.got_xmit_done >=3D + vnicinfo->max_outstanding_net_xmits)) || + /* OR check wrap condition */ + ((vnicinfo->datachan.chstat.sent_xmit < + vnicinfo->datachan.chstat.got_xmit_done) && + (ULONG_MAX - vnicinfo->datachan.chstat.got_xmit_done + + vnicinfo->datachan.chstat.sent_xmit >=3D + vnicinfo->max_outstanding_net_xmits)) + ) { + /* + * too many NET_XMITs queued over to IOVM - need to wait + * Might need to remove the below message as these might be + * excessive under load. + */ + vnicinfo->datachan.chstat.reject_count++; + if (!vnicinfo->queuefullmsg_logged && + ((vnicinfo->datachan.chstat.reject_count & 0x3ff) =3D=3D + 1)) { + vnicinfo->queuefullmsg_logged =3D 1; +#if VIRTNIC_STATS + vnicinfo->datachan.chstat.reject_jiffies_start =3D + jiffies; +#endif + LOGINFNAME(vnicinfo->netdev, "**** REJECTING NET_XMIT - rejected co= unt=3D%ld chstat.sent_xmit=3D%lu chstat.got_xmit_done=3D%lu\n", + vnicinfo->datachan.chstat.reject_count, + vnicinfo->datachan.chstat.sent_xmit, + vnicinfo->datachan.chstat.got_xmit_done); + } + netif_stop_queue(netdev); /* calling stop queue */ + BUSY; /* return status that packet not accepted */ + } else if (vnicinfo->queuefullmsg_logged) { +#if VIRTNIC_STATS + LOGINFNAME(vnicinfo->netdev, "**** NET_XMITs now working again - rej= ected count =3D %ld msec =3D %ld\n", + vnicinfo->datachan.chstat.reject_count, + ((long)jiffies - + (long)(vnicinfo->datachan.chstat. + reject_jiffies_start)) * 1000 / HZ); +#else + LOGINFNAME(vnicinfo->netdev, "**** NET_XMITs now working again - rej= ected count =3D %ld\n", + vnicinfo->datachan.chstat.reject_count); +#endif + /* queue is not blocked so reset the logging flag */ + vnicinfo->queuefullmsg_logged =3D 0; + } + + if (skb->ip_summed =3D=3D CHECKSUM_UNNECESSARY) { + DBGINF("CHECKSUM_HW protocol:%x csum:%x tso_size:%x data:%p h.raw:%p= nh.raw:%p\n", + skb->protocol, skb->csum, skb_shinfo(skb)->tso_size, + skb->data, skb->h.raw, skb->nh.raw); + cmdrsp->net.xmt.lincsum.valid =3D 1; + cmdrsp->net.xmt.lincsum.protocol =3D skb->protocol; + if (skb_transport_header(skb) > skb->data) { + cmdrsp->net.xmt.lincsum.hrawoff =3D + skb_transport_header(skb) - skb->data; + cmdrsp->net.xmt.lincsum.hrawoffv =3D 1; + } + if (skb_network_header(skb) > skb->data) { + cmdrsp->net.xmt.lincsum.nhrawoff =3D + skb_network_header(skb) - skb->data; + cmdrsp->net.xmt.lincsum.nhrawoffv =3D 1; + } + cmdrsp->net.xmt.lincsum.csum =3D skb->csum; + } else { + cmdrsp->net.xmt.lincsum.valid =3D 0; + } + /* save off the length of the entire data packet */ + cmdrsp->net.xmt.len =3D len; /* total data length */ + /* + * copy ethernet header from first frag into cmdrsp + * - everything else will be passed in frags & DMA'ed + */ + memcpy(cmdrsp->net.xmt.ethhdr, skb->data, ETH_HEADER_SIZE); + /* + * copy frags info - from skb->data we need to only provide access + * beyond eth header + */ + cmdrsp->net.xmt.num_frags =3D + uisutil_copy_fragsinfo_from_skb("virtnic_xmit", skb, firstfraglen= , + MAX_PHYS_INFO, + cmdrsp->net.xmt.frags); + if (cmdrsp->net.xmt.num_frags =3D=3D -1) { + LOGERRNAME(vnicinfo->netdev, "**** FAILED to copy fragsinfo\n"); + BUSY; /* WILL HAPPEN ONLY IF FRAG ARRAY WITH + MAX_PHYS_INFO ENTRIES IS NOT ENOUGH */ + } + + DBGINF("Forwarding packet cmdrsp:%p\n", cmdrsp); + + /* + * don't hold lock when forwarding xmit - if queue is full insert + * might sleep + */ + qrslt =3D uisqueue_put_cmdrsp_with_lock_client( + vnicinfo->datachan.chinfo.queueinfo, cmdrsp, + IOCHAN_TO_IOPART, + (void *)&vnicinfo->datachan.chinfo.insertlock, + DONT_ISSUE_INTERRUPT, (uint64_t)NULL, + 0 /* don't wait */ , + "vnic"); + if (!qrslt) { + /* failed to queue xmit - return busy */ + LOGERRNAME(vnicinfo->netdev, + "**** FAILED to insert NET_XMIT\n"); + netif_stop_queue(netdev); /* calling stop queue */ + BUSY; /* return status that packet not accepted */ + } + /* Track the skbs that have been sent to the IOVM for XMIT */ + skb_queue_head(&vnicinfo->xmitbufhead, skb); + + /* + * set the last transmission start time + * linux docs says: Do not forget to update netdev->trans_start to + * jiffies after each new tx packet is given to the hardware. + */ + netdev->trans_start =3D jiffies; /* some code in Linux uses this. */ + + /* update xmt stats */ + UPD_XMT_STATS; + vnicinfo->datachan.chstat.sent_xmit++; + + /* + * check to see if we have hit the high watermark for + * netif_stop_queue() + */ + if (((vnicinfo->datachan.chstat.sent_xmit >=3D + vnicinfo->datachan.chstat.got_xmit_done) && + (vnicinfo->datachan.chstat.sent_xmit - + vnicinfo->datachan.chstat.got_xmit_done >=3D + vnicinfo->upper_threshold_net_xmits)) || + /* OR check wrap condition */ + ((vnicinfo->datachan.chstat.sent_xmit < + vnicinfo->datachan.chstat.got_xmit_done) && + (ULONG_MAX - vnicinfo->datachan.chstat.got_xmit_done + + vnicinfo->datachan.chstat.sent_xmit >=3D + vnicinfo->upper_threshold_net_xmits)) + ) { + /* too many NET_XMITs queued over to IOVM - need to wait */ + netif_stop_queue(netdev); /* calling stop queue - call + netif_wake_queue() after lower + threshold */ + vnicinfo->flow_control_upper_hits++; + } + + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + + /* skb will be freed when we get back NET_XMIT_DONE */ + return NETDEV_TX_OK; +} + +static void +virtnic_serverdown_complete(struct work_struct *work) +{ + struct virtnic_info *vnicinfo; + struct net_device *netdev; + struct virtpci_dev *virtpcidev; + unsigned long flags; + int i =3D 0, count =3D 0; + + vnicinfo =3D + container_of(work, struct virtnic_info, serverdown_completion); + netdev =3D vnicinfo->netdev; + virtpcidev =3D vnicinfo->virtpcidev; + + DBGINF("virtpcidev busNo<<%d>>devNo<<%d>>", virtpcidev->busNo, + virtpcidev->deviceNo); + DBGINF("net_device name<<%s>>", netdev->name); + /* Stop Using Datachan */ + uisthread_stop(&vnicinfo->datachan.chinfo.threadinfo); + + /* Inform Linux that the link is down */ + netif_carrier_off(netdev); + netif_stop_queue(netdev); + + /* + * Free the skb for XMITs that haven't been serviced by the server + * We shouldn't have to inform Linux about these IOs because they + * are "lost in the ethernet" + */ + skb_queue_purge(&vnicinfo->xmitbufhead); + + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + /* free rcv buffers */ + for (i =3D 0; i < vnicinfo->num_rcv_bufs; i++) { + if (vnicinfo->rcvbuf[i]) { + kfree_skb(vnicinfo->rcvbuf[i]); + vnicinfo->rcvbuf[i] =3D NULL; + count++; + } + } + atomic_set(&vnicinfo->num_rcv_bufs_in_iovm, 0); + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + + LOGINFNAME(vnicinfo->netdev, "Closed:%p Freed %d rcv bufs\n", netdev, + count); + + vnicinfo->server_down =3D true; + vnicinfo->server_change_state =3D false; + visorchipset_device_pause_response(virtpcidev->bus_no, + virtpcidev->device_no, 0); +} + +/* As per VirtpciFunc returns 1 for success and 0 for failure */ +static int +virtnic_serverdown(struct virtpci_dev *virtpcidev, u32 state) +{ + struct net_device *netdev =3D virtpcidev->net.netdev; + struct virtnic_info *vnicinfo =3D netdev_priv(netdev); + + DBGINF("virtpcidev busNo<<%d>>devNo<<%d>>", virtpcidev->busNo, + virtpcidev->deviceNo); + DBGINF("entering virtnic_serverdown"); + + if (!vnicinfo->server_down && !vnicinfo->server_change_state) { + vnicinfo->server_change_state =3D true; + queue_work(virtnic_serverdown_workqueue, + &vnicinfo->serverdown_completion); + } else if (vnicinfo->server_change_state) { + LOGERRNAME(vnicinfo->netdev, + "Server already processing change state message."); + return 0; + } else + LOGERRNAME(vnicinfo->netdev, + "Server already down, but another server down message received."= ); + DBGINF("exiting virtnic_serverdown"); + return 1; +} + +/* As per VirtpciFunc returns 1 for success and 0 for failure */ +static int +virtnic_serverup(struct virtpci_dev *virtpcidev) +{ + struct net_device *netdev =3D virtpcidev->net.netdev; + struct virtnic_info *vnicinfo =3D netdev_priv(netdev); + unsigned long flags; + + DBGINF("entering virtnic_serverup"); + DBGINF("virtpcidev busNo<<%d>>devNo<<%d>>", virtpcidev->busNo, + virtpcidev->deviceNo); + DBGINF("net_device name<<%s>>", netdev->name); + if (vnicinfo->server_down && !vnicinfo->server_change_state) { + vnicinfo->server_change_state =3D true; + /* + * Must transition channel to ATTACHED state BEFORE we can + * start using the device again + */ + SPAR_CHANNEL_CLIENT_TRANSITION(vnicinfo->datachan.chinfo. + queueinfo->chan, + dev_name(&virtpcidev-> + generic_dev), + CHANNELCLI_ATTACHED, NULL); + + if (!uisthread_start(&vnicinfo->datachan.chinfo.threadinfo, + process_incoming_rsps, + &vnicinfo->datachan, "vnic_incoming")) { + LOGERRNAME(vnicinfo->netdev, + "**** FAILED to start thread\n"); + return 0; + } + + init_rcv_bufs(netdev, vnicinfo); + + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + vnicinfo->enabled =3D 1; + /* + * now we're ready, let's send an ENB to uisnic + * but until we get an ACK back from uisnic, we'll drop + * the packets + */ + vnicinfo->enab_dis_acked =3D 0; + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + + /* + * send enable and wait for ack - don't hold lock when + * sending enable because if the queue is full, insert + * might sleep. + */ + SEND_ENBDIS(netdev, 1, vnicinfo->cmdrsp_rcv, + vnicinfo->datachan.chinfo.queueinfo, + &vnicinfo->datachan.chinfo.insertlock, + vnicinfo->datachan.chstat); + } else if (vnicinfo->server_change_state) { + LOGERRNAME(vnicinfo->netdev, + "Server already processing change state message."); + return 0; + } else { + DBGINF("Server up message received for server that was already up.")= ; + } + DBGINF("exiting virtnic_serverup"); + return 1; +} + +static void +virtnic_timeout_reset(struct work_struct *work) +{ + struct virtnic_info *vnicinfo; + struct net_device *netdev; + struct virtpci_dev *virtpcidev; + int response =3D 0; + + vnicinfo =3D container_of(work, struct virtnic_info, timeout_reset); + netdev =3D vnicinfo->netdev; + + DBGINF("net_device name<<%s>>", netdev->name); + /* Transmit Timeouts are typically handled by resetting the + * device for our virtual NIC we will send a Disable and + * Enable to the IOVM. If it doesn't respond we will trigger + * a serverdown + */ + DBGINF("Disabling connection to server.\n"); + netif_stop_queue(netdev); + response =3D virtnic_disable_with_timeout(netdev, 100); + if (response !=3D 0) + goto call_serverdown; + + DBGINF("Disable returned so reenable connection to server.\n"); + response =3D virtnic_enable_with_timeout(netdev, 100); + if (response !=3D 0) + goto call_serverdown; + netif_wake_queue(netdev); + + LOGWRNNAME(vnicinfo->netdev, "Virtual connection reset.\n"); + return; + +call_serverdown: + LOGERRNAME(vnicinfo->netdev, + "Disable/enabled Pair failed to return so start serverdown.\n"); + virtpcidev =3D vnicinfo->virtpcidev; + virtnic_serverdown(virtpcidev, 0); + return; +} + +static void +virtnic_xmit_timeout(struct net_device *netdev) +{ + struct virtnic_info *vnicinfo =3D netdev_priv(netdev); + unsigned long flags; + + LOGWRNNAME(vnicinfo->netdev, + "Transmit Timeout. Resetting virtual connection.\n"); + LOGWRNNAME(vnicinfo->netdev, "net_device name<<%s>>", netdev->name); + + spin_lock_irqsave(&vnicinfo->priv_lock, flags); + /* Ensure that a ServerDown message hasn't been received */ + if (!vnicinfo->enabled || + (vnicinfo->server_down && !vnicinfo->server_change_state)) { + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + return; + } + spin_unlock_irqrestore(&vnicinfo->priv_lock, flags); + + queue_work(virtnic_timeout_reset_workqueue, &vnicinfo->timeout_reset)= ; +} + +static void +virtnic_set_multi(struct net_device *netdev) +{ + struct uiscmdrsp *cmdrsp; + struct virtnic_info *vnicinfo =3D netdev_priv(netdev); + + DBGINF("net_device name<<%s>>", netdev->name); + DBGINF("entering virtnic_set_multi\n"); + + /* any filtering changes? */ + if (vnicinfo->old_flags !=3D netdev->flags) { + LOGINFNAME(vnicinfo->netdev, + "old filter =3D 0x%04x, new filter =3D 0x%04x.\n", + vnicinfo->old_flags, netdev->flags); + if ((netdev->flags & IFF_PROMISC) !=3D + (vnicinfo->old_flags & IFF_PROMISC)) { + LOGINFNAME(vnicinfo->netdev, + "we are %s promiscuous mode.\n", + (netdev-> + flags & IFF_PROMISC) ? "entering" : + "exiting"); + cmdrsp =3D kmalloc(SIZEOF_CMDRSP, GFP_ATOMIC); + if (cmdrsp =3D=3D NULL) { + LOGERRNAME(vnicinfo->netdev, + "**** FAILED to kmalloc cmdrsp.\n"); + return; + } + memset(cmdrsp, 0, SIZEOF_CMDRSP); + cmdrsp->cmdtype =3D CMD_NET_TYPE; + cmdrsp->net.type =3D NET_RCV_PROMISC; + cmdrsp->net.enbdis.context =3D netdev; + cmdrsp->net.enbdis.enable =3D + (netdev->flags & IFF_PROMISC); + if (uisqueue_put_cmdrsp_with_lock_client + (vnicinfo->datachan.chinfo.queueinfo, cmdrsp, + IOCHAN_TO_IOPART, + (void *)&vnicinfo->datachan.chinfo.insertlock, + DONT_ISSUE_INTERRUPT, (uint64_t)NULL, + 0 /* don't wait */ , "vnic")) { + vnicinfo->datachan.chstat.sent_promisc++; + } else + LOGERRNAME(vnicinfo->netdev, + "**** FAILED to insert NET_RCV_PROMISC.\n"); + kfree(cmdrsp); + } + + vnicinfo->old_flags =3D netdev->flags; + } + DBGINF("exiting virtnic_set_multi\n"); +} + +/*****************************************************/ +/* debugfs filesystem functions */ +/*****************************************************/ + +static ssize_t info_debugfs_read(struct file *file, + char __user *buf, size_t len, loff_t *offset) +{ + int i; + ssize_t bytes_read =3D 0; + int str_pos =3D 0; + struct virtnic_info *vni; + char *vbuf; + + if (len > MAX_BUF) + len =3D MAX_BUF; + vbuf =3D kzalloc(len, GFP_KERNEL); + if (!vbuf) + return -ENOMEM; + + /* for each vnic channel + * dump out channel specific data + */ + for (i =3D 0; i < VIRTNICSOPENMAX; i++) { + if (num_virtnic_open[i].netdev =3D=3D NULL) + continue; + + vni =3D num_virtnic_open[i].vnicinfo; + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, "Vnic i =3D %d\n", i); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, "netdev =3D %s (0x%p), MAC Addr: %02x:%02x:%02x:%02= x:%02x:%02x\n", + num_virtnic_open[i].netdev->name, + num_virtnic_open[i].netdev, + num_virtnic_open[i].netdev->dev_addr[0], + num_virtnic_open[i].netdev->dev_addr[1], + num_virtnic_open[i].netdev->dev_addr[2], + num_virtnic_open[i].netdev->dev_addr[3], + num_virtnic_open[i].netdev->dev_addr[4], + num_virtnic_open[i].netdev->dev_addr[5]); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, "vnicinfo =3D 0x%p\n", vni); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " num_rcv_bufs =3D %d\n", + vni->num_rcv_bufs); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " features =3D 0x%016llX\n", + (uint64_t)readq(&vni->datachan.chinfo.queueinfo->chan-> + features)); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " max_outstanding_net_xmits =3D %d\n", + vni->max_outstanding_net_xmits); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " upper_threshold_net_xmits =3D %d\n", + vni->upper_threshold_net_xmits); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " lower_threshold_net_xmits =3D %d\n", + vni->lower_threshold_net_xmits); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " queuefullmsg_logged =3D %d\n", + vni->queuefullmsg_logged); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " queueinfo->packets_sent =3D %lld\n", + vni->datachan.chinfo.queueinfo->packets_sent); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " queueinfo->packets_received =3D %lld\n", + vni->datachan.chinfo.queueinfo->packets_received); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " chstat.got_rcv =3D %lu\n", + vni->datachan.chstat.got_rcv); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " chstat.got_enbdisack =3D %lu\n", + vni->datachan.chstat.got_enbdisack); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " chstat.got_xmit_done =3D %lu\n", + vni->datachan.chstat.got_xmit_done); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " chstat.xmit_fail =3D %lu\n", + vni->datachan.chstat.xmit_fail); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " chstat.sent_enbdis =3D %lu\n", + vni->datachan.chstat.sent_enbdis); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " chstat.sent_promisc =3D %lu\n", + vni->datachan.chstat.sent_promisc); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " chstat.sent_post =3D %lu\n", + vni->datachan.chstat.sent_post); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " chstat.sent_xmit =3D %lu\n", + vni->datachan.chstat.sent_xmit); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " chstat.reject_count =3D %lu\n", + vni->datachan.chstat.reject_count); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " chstat.extra_rcvbufs_sent =3D %lu\n", + vni->datachan.chstat.extra_rcvbufs_sent); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " n_rcv0 =3D %lu\n", vni->n_rcv0); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " n_rcv1 =3D %lu\n", vni->n_rcv1); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " n_rcv2 =3D %lu\n", vni->n_rcv2); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " n_rcvx =3D %lu\n", vni->n_rcvx); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " num_rcv_bufs_in_iovm =3D %d\n", + atomic_read(&vni->num_rcv_bufs_in_iovm)); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " alloc_failed_in_if_needed_cnt =3D %lu\n", + vni->alloc_failed_in_if_needed_cnt); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " alloc_failed_in_repost_return_cnt =3D %lu\n", + vni->alloc_failed_in_repost_return_cnt); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " inner_loop_limit_reached_cnt =3D %lu\n", + vni->inner_loop_limit_reached_cnt); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " found_repost_rcvbuf_cnt =3D %lu\n", + vni->found_repost_rcvbuf_cnt); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " repost_found_skb_cnt =3D %lu\n", + vni->repost_found_skb_cnt); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " n_repost_deficit =3D %lu\n", + vni->n_repost_deficit); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " bad_rcv_buf =3D %lu\n", + vni->bad_rcv_buf); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " n_rcv_packet_not_accepted =3D %lu\n", + vni->n_rcv_packet_not_accepted); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " interrupts_rcvd =3D %llu\n", + vni->interrupts_rcvd); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " interrupts_notme =3D %llu\n", + vni->interrupts_notme); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " interrupts_disabled =3D %llu\n", + vni->interrupts_disabled); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " busy_cnt =3D %llu\n", + vni->busy_cnt); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " flow_control_upper_hits =3D %llu\n", + vni->flow_control_upper_hits); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " flow_control_lower_hits =3D %llu\n", + vni->flow_control_lower_hits); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " thread_wait_ms =3D %d\n", + vni->thread_wait_ms); + str_pos +=3D scnprintf(vbuf + str_pos, + len - str_pos, " netif_queue =3D %s\n", + netif_queue_stopped(vni->netdev) ? + "stopped" : "running"); + } + bytes_read =3D simple_read_from_buffer(buf, len, offset, vbuf, str_po= s); + kfree(vbuf); + return bytes_read; +} + +static ssize_t enable_ints_write(struct file *file, + const char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[4]; + int i, new_value; + struct virtnic_info *vnicinfo; + uint64_t __iomem *features_addr; + uint64_t mask; + + if (count >=3D ARRAY_SIZE(buf)) + return -EINVAL; + + buf[count] =3D '\0'; + if (copy_from_user(buf, buffer, count)) { + LOGERR("copy_from_user failed.\n"); + return -EFAULT; + } + + i =3D kstrtoint(buf, 10 , &new_value); + + if (i !=3D 0) { + LOGERR("Failed to scan value for enable_ints, buf<<%.*s>>", + (int)count, buf); + return -EFAULT; + } + + /* set all counts to new_value usually 0 */ + for (i =3D 0; i < VIRTNICSOPENMAX; i++) { + if (num_virtnic_open[i].vnicinfo !=3D NULL) { + vnicinfo =3D num_virtnic_open[i].vnicinfo; + features_addr =3D + &vnicinfo->datachan.chinfo.queueinfo->chan-> + features; + if (new_value =3D=3D 1) { + mask =3D + ~(ULTRA_IO_CHANNEL_IS_POLLING | + ULTRA_IO_DRIVER_DISABLES_INTS); + uisqueue_interlocked_and(features_addr, mask); + mask =3D ULTRA_IO_DRIVER_ENABLES_INTS; + uisqueue_interlocked_or(features_addr, mask); + vnicinfo->thread_wait_ms =3D 2000; + } else { + mask =3D + ~(ULTRA_IO_DRIVER_ENABLES_INTS | + ULTRA_IO_DRIVER_DISABLES_INTS); + uisqueue_interlocked_and(features_addr, mask); + mask =3D ULTRA_IO_CHANNEL_IS_POLLING; + uisqueue_interlocked_or(features_addr, mask); + vnicinfo->thread_wait_ms =3D 2; + } + } +} + +return count; +} + +/*****************************************************/ +/* Module init & exit functions */ +/*****************************************************/ + +static int __init +virtnic_mod_init(void) +{ + int error, i; + + LOGINF("entering virtnic_mod_init"); + /* ASSERT RCVPOST_BUF_SIZE < 4K */ + if (RCVPOST_BUF_SIZE > PI_PAGE_SIZE) { + LOGERR("**** FAILED RCVPOST_BUF_SIZE:%d larger than a page\n", + RCVPOST_BUF_SIZE); + return -1; + } + /* ASSERT RCVPOST_BUF_SIZE is big enough to hold eth header */ + if (RCVPOST_BUF_SIZE < ETH_HEADER_SIZE) { + LOGERR("**** FAILED RCVPOST_BUF_SIZE:%d is < ETH_HEADER_SIZE:%d\n", + RCVPOST_BUF_SIZE, ETH_HEADER_SIZE); + return -1; + } + + /* clear out array */ + for (i =3D 0; i < VIRTNICSOPENMAX; i++) { + num_virtnic_open[i].netdev =3D NULL; + num_virtnic_open[i].vnicinfo =3D NULL; + } + /* create workqueue for serverdown completion */ + virtnic_serverdown_workqueue =3D + create_singlethread_workqueue("virtnic_serverdown"); + if (virtnic_serverdown_workqueue =3D=3D NULL) { + LOGERR("**** FAILED virtnic_serverdown_workqueue creation\n"); + return -1; + } + /* create workqueue for tx timeout reset */ + virtnic_timeout_reset_workqueue =3D + create_singlethread_workqueue("virtnic_timeout_reset"); + if (virtnic_timeout_reset_workqueue =3D=3D NULL) { + LOGERR + ("**** FAILED virtnic_timeout_reset_workqueue creation\n"); + return -1; + } + virtnic_debugfs_dir =3D debugfs_create_dir("virtnic", NULL); + debugfs_create_file("info", S_IRUSR, virtnic_debugfs_dir, + NULL, &debugfs_info_fops); + debugfs_create_file("enable_ints", S_IWUSR, + virtnic_debugfs_dir, NULL, + &debugfs_enable_ints_fops); + + error =3D virtpci_register_driver(&virtnic_driver); + if (error < 0) { + LOGERR("**** FAILED to register driver %x\n", error); + debugfs_remove_recursive(virtnic_debugfs_dir); + return -1; + } + LOGINF("exiting virtnic_mod_init"); + return error; +} + +static void __exit +virtnic_mod_exit(void) +{ + LOGINF("entering virtnic_mod_exit...\n"); + virtpci_unregister_driver(&virtnic_driver); + /* unregister is going to call virtnic_remove for all devices */ + /* destroy serverdown completion workqueue */ + if (virtnic_serverdown_workqueue) { + destroy_workqueue(virtnic_serverdown_workqueue); + virtnic_serverdown_workqueue =3D NULL; + } + + /* destroy timeout reset workqueue */ + if (virtnic_timeout_reset_workqueue) { + destroy_workqueue(virtnic_timeout_reset_workqueue); + virtnic_timeout_reset_workqueue =3D NULL; + } + + debugfs_remove_recursive(virtnic_debugfs_dir); + LOGINF("exiting virtnic_mod_exit...\n"); +} + +module_init(virtnic_mod_init); +module_exit(virtnic_mod_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Usha Srinivasan"); +MODULE_ALIAS("uisvirtnic"); +/* this is extracted during depmod and kept in modules.dep */ --=20 1.8.3.1