<commit_before>/* virtpci.c
 *
 * Copyright (C) 2010 - 2013 UNISYS CORPORATION
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * 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 <linux/kernel.h>
#ifdef CONFIG_MODVERSIONS
#include <config/modversions.h>
#endif
#include "uniklog.h"
#include "diagnostics/appos_subsystems.h"
#include "uisutils.h"
#include "vbuschannel.h"
#include "vbushelper.h"
#include <linux/types.h>
#include <linux/io.h>
#include <linux/uuid.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/mod_devicetable.h>
#include <linux/if_ether.h>
#include <linux/version.h>
#include <linux/debugfs.h>
#include "version.h"
#include "guestlinuxdebug.h"
#include "timskmod.h"

struct driver_private {
	struct kobject kobj;
	struct klist klist_devices;
	struct klist_node knode_bus;
	struct module_kobject *mkobj;
	struct device_driver *driver;
};

#define to_driver(obj) container_of(obj, struct driver_private, kobj)

/* bus_id went away in 2.6.30 - the size was 20 bytes, so we'll define
 * it ourselves, and a macro to make getting the field a bit simpler.
 */
#ifndef BUS_ID_SIZE
#define BUS_ID_SIZE 20
#endif

#define BUS_ID(x) dev_name(x)

/* MAX_BUF = 4 busses x ( 32 devices/bus + 1 busline) x 80 characters
 *         = 10,560 bytes ~ 2^14 = 16,384 bytes
 */
#define MAX_BUF 16384

#include "virtpci.h"

/* this is shorter than using __FILE__ (full path name) in
 * debug/info/error messages
 */
#define CURRENT_FILE_PC VIRT_PCI_PC_virtpci_c
#define __MYFILE__ "virtpci.c"

#define VIRTPCI_VERSION "01.00"

/*****************************************************/
/* Forward declarations                              */
/*****************************************************/

static int delete_vbus_device(struct device *vbus, void *data);
static int match_busid(struct device *dev, void *data);
static void virtpci_bus_release(struct device *dev);
static void virtpci_device_release(struct device *dev);
static int virtpci_device_add(struct device *parentbus, int devtype,
			      struct add_virt_guestpart *addparams,
			      struct scsi_adap_info *scsi,
			      struct net_adap_info *net);
static int virtpci_device_del(struct device *parentbus, int devtype,
			      struct vhba_wwnn *wwnn, unsigned char macaddr[]);
static int virtpci_device_serverdown(struct device *parentbus, int devtype,
				     struct vhba_wwnn *wwnn,
				     unsigned char macaddr[]);
static int virtpci_device_serverup(struct device *parentbus, int devtype,
				   struct vhba_wwnn *wwnn,
				   unsigned char macaddr[]);
static ssize_t virtpci_driver_attr_show(struct kobject *kobj,
					struct attribute *attr, char *buf);
static ssize_t virtpci_driver_attr_store(struct kobject *kobj,
					 struct attribute *attr,
					 const char *buf, size_t count);
static int virtpci_bus_match(struct device *dev, struct device_driver *drv);
static int virtpci_uevent(struct device *dev, struct kobj_uevent_env *env);
static int virtpci_device_suspend(struct device *dev, pm_message_t state);
static int virtpci_device_resume(struct device *dev);
static int virtpci_device_probe(struct device *dev);
static int virtpci_device_remove(struct device *dev);

static ssize_t info_debugfs_read(struct file *file, char __user *buf,
			      size_t len, loff_t *offset);

static const struct file_operations debugfs_info_fops = {
	.read = info_debugfs_read,
};

/*****************************************************/
/* Globals                                           */
/*****************************************************/

/* methods in bus_type struct allow the bus code to serve as an
 * intermediary between the device core and individual device core and
 * individual drivers
 */
static struct bus_type virtpci_bus_type = {
	.name = "uisvirtpci",
	.match = virtpci_bus_match,
	.uevent = virtpci_uevent,
	.suspend = virtpci_device_suspend,
	.resume = virtpci_device_resume,
};

static struct device virtpci_rootbus_device = {
	.init_name = "vbusroot",	/* root bus */
	.release = virtpci_bus_release
};

/* filled in with info about parent chipset driver when we register with it */
static struct ultra_vbus_deviceinfo Chipset_DriverInfo;

static const struct sysfs_ops virtpci_driver_sysfs_ops = {
	.show = virtpci_driver_attr_show,
	.store = virtpci_driver_attr_store,
};

static struct kobj_type virtpci_driver_kobj_type = {
	.sysfs_ops = &virtpci_driver_sysfs_ops,
};

static struct virtpci_dev *VpcidevListHead;
static DEFINE_RWLOCK(VpcidevListLock);

/* filled in with info about this driver, wrt it servicing client busses */
static struct ultra_vbus_deviceinfo Bus_DriverInfo;

/*****************************************************/
/* debugfs entries                                   */
/*****************************************************/
/* dentry is used to create the debugfs entry directory
 * for virtpci
 */
static struct dentry *virtpci_debugfs_dir;

struct virtpci_busdev {
	struct device virtpci_bus_device;
};

/*****************************************************/
/* Local functions                                   */
/*****************************************************/

static inline
int WAIT_FOR_IO_CHANNEL(struct spar_io_channel_protocol __iomem  *chanptr)
{
	int count = 120;

	while (count > 0) {
		if (SPAR_CHANNEL_SERVER_READY(&chanptr->channel_header))
			return 1;
		UIS_THREAD_WAIT_SEC(1);
		count--;
	}
	return 0;
}

/* Write the contents of <info> to the ULTRA_VBUS_CHANNEL_PROTOCOL.ChpInfo. */
static int write_vbus_chpInfo(struct spar_vbus_channel_protocol *chan,
			      struct ultra_vbus_deviceinfo *info)
{
	int off;

	if (!chan) {
		LOGERR("vbus channel not present");
		return -1;
	}
	off = sizeof(struct channel_header) + chan->hdr_info.chp_info_offset;
	if (chan->hdr_info.chp_info_offset == 0) {
		LOGERR("vbus channel not used, because chp_info_offset == 0");
		return -1;
	}
	memcpy(((u8 *)(chan)) + off, info, sizeof(*info));
	return 0;
}

/* Write the contents of <info> to the ULTRA_VBUS_CHANNEL_PROTOCOL.BusInfo. */
static int write_vbus_busInfo(struct spar_vbus_channel_protocol *chan,
			      struct ultra_vbus_deviceinfo *info)
{
	int off;

	if (!chan) {
		LOGERR("vbus channel not present");
		return -1;
	}
	off = sizeof(struct channel_header) + chan->hdr_info.bus_info_offset;
	if (chan->hdr_info.bus_info_offset == 0) {
		LOGERR("vbus channel not used, because bus_info_offset == 0");
		return -1;
	}
	memcpy(((u8 *)(chan)) + off, info, sizeof(*info));
	return 0;
}

/* Write the contents of <info> to the
 * ULTRA_VBUS_CHANNEL_PROTOCOL.DevInfo[<devix>].
 */
static int
write_vbus_devInfo(struct spar_vbus_channel_protocol *chan,
		   struct ultra_vbus_deviceinfo *info, int devix)
{
	int off;

	if (!chan) {
		LOGERR("vbus channel not present");
		return -1;
	}
	off =
	    (sizeof(struct channel_header) +
	     chan->hdr_info.dev_info_offset) +
	    (chan->hdr_info.device_info_struct_bytes * devix);
	if (chan->hdr_info.dev_info_offset == 0) {
		LOGERR("vbus channel not used, because dev_info_offset == 0");
		return -1;
	}
	memcpy(((u8 *)(chan)) + off, info, sizeof(*info));
	return 0;
}

/* adds a vbus
 * returns 0 failure, 1 success,
 */
static int add_vbus(struct add_vbus_guestpart *addparams)
{
	int ret;
	struct device *vbus;

	vbus = kzalloc(sizeof(struct device), GFP_ATOMIC);

	POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
	if (!vbus)
		return 0;

	dev_set_name(vbus, "vbus%d", addparams->bus_no);
	vbus->release = virtpci_bus_release;
	vbus->parent = &virtpci_rootbus_device;	/* root bus is parent */
	vbus->bus = &virtpci_bus_type;	/* bus type */
	vbus->platform_data = (__force void *)addparams->chanptr;

	/* register a virt bus device -
	 * this bus shows up under /sys/devices with .name value
	 * "virtpci%d" any devices added to this bus then show up under
	 * /sys/devices/virtpci0
	 */
	ret = device_register(vbus);
	if (ret) {
		LOGERR("device_register FAILED:%d\n", ret);
		POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
		return 0;
	}
	write_vbus_chpInfo(vbus->platform_data /* chanptr */ ,
			   &Chipset_DriverInfo);
	write_vbus_busInfo(vbus->platform_data /* chanptr */ , &Bus_DriverInfo);
	LOGINF("Added vbus %d; device %s created successfully\n",
	       addparams->bus_no, BUS_ID(vbus));
	POSTCODE_LINUX_2(VPCI_CREATE_EXIT_PC, POSTCODE_SEVERITY_INFO);
	return 1;
}

/* for CHANSOCK wwwnn/max are AUTO-GENERATED; for normal channels,
 * wwnn/max are in the channel header.
 */
#define GET_SCSIADAPINFO_FROM_CHANPTR(chanptr) {			\
	memcpy_fromio(&scsi.wwnn,					\
		      &((struct spar_io_channel_protocol __iomem *)	\
			chanptr)->vhba.wwnn,				\
		      sizeof(struct vhba_wwnn));			\
	memcpy_fromio(&scsi.max,					\
		      &((struct spar_io_channel_protocol __iomem *)	\
			chanptr)->vhba.max,				\
		      sizeof(struct vhba_config_max));			\
	}

/* find bus device with the busid that matches - match_busid matches bus_id */
#define GET_BUS_DEV(busno) { \
	sprintf(busid, "vbus%d", busno); \
	vbus = bus_find_device(&virtpci_bus_type, NULL, \
			       (void *)busid, match_busid);	\
	if (!vbus) { \
		LOGERR("**** FAILED to find vbus %s\n", busid); \
		return 0; \
	} \
}

/* adds a vhba
 * returns 0 failure, 1 success,
 */
static int add_vhba(struct add_virt_guestpart *addparams)
{
	int i;
	struct scsi_adap_info scsi;
	struct device *vbus;
	unsigned char busid[BUS_ID_SIZE];

	POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
	if (!WAIT_FOR_IO_CHANNEL
	    ((struct spar_io_channel_protocol __iomem *)addparams->chanptr)) {
		LOGERR("Timed out.  Channel not ready\n");
		POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
		return 0;
	}

	GET_SCSIADAPINFO_FROM_CHANPTR(addparams->chanptr);

	GET_BUS_DEV(addparams->bus_no);

	LOGINF("Adding vhba wwnn:%x:%x config:%d-%d-%d-%d chanptr:%p\n",
	       scsi.wwnn.wwnn1, scsi.wwnn.wwnn2,
	       scsi.max.max_channel, scsi.max.max_id, scsi.max.max_lun,
	       scsi.max.cmd_per_lun, addparams->chanptr);
	i = virtpci_device_add(vbus, VIRTHBA_TYPE, addparams, &scsi, NULL);
	if (i) {
		LOGINF("Added vhba wwnn:%x:%x chanptr:%p\n", scsi.wwnn.wwnn1,
		       scsi.wwnn.wwnn2, addparams->chanptr);
		POSTCODE_LINUX_3(VPCI_CREATE_EXIT_PC, i,
				 POSTCODE_SEVERITY_INFO);
	}
	return i;
}

/* for CHANSOCK macaddr is AUTO-GENERATED; for normal channels,
 * macaddr is in the channel header.
 */
#define GET_NETADAPINFO_FROM_CHANPTR(chanptr) {				\
		memcpy_fromio(net.mac_addr,				\
		       ((struct spar_io_channel_protocol __iomem *)	\
		       chanptr)->vnic.macaddr,				\
		       MAX_MACADDR_LEN);				\
		net.num_rcv_bufs =					\
			readl(&((struct spar_io_channel_protocol __iomem *)\
			      chanptr)->vnic.num_rcv_bufs);		\
		net.mtu = readl(&((struct spar_io_channel_protocol __iomem *) \
				chanptr)->vnic.mtu);			\
		memcpy_fromio(&net.zone_uuid, \
			      &((struct spar_io_channel_protocol __iomem *)\
			      chanptr)->vnic.zone_uuid,		\
			      sizeof(uuid_le));				\
}

/* adds a vnic
 * returns 0 failure, 1 success,
 */
static int
add_vnic(struct add_virt_guestpart *addparams)
{
	int i;
	struct net_adap_info net;
	struct device *vbus;
	unsigned char busid[BUS_ID_SIZE];

	POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
	if (!WAIT_FOR_IO_CHANNEL
	    ((struct spar_io_channel_protocol __iomem *)addparams->chanptr)) {
		LOGERR("Timed out, channel not ready\n");
		POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
		return 0;
	}

	GET_NETADAPINFO_FROM_CHANPTR(addparams->chanptr);

	GET_BUS_DEV(addparams->bus_no);

	LOGINF("Adding vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x rcvbufs:%d mtu:%d chanptr:%p%pUL\n",
	     net.mac_addr[0], net.mac_addr[1], net.mac_addr[2], net.mac_addr[3],
	     net.mac_addr[4], net.mac_addr[5], net.num_rcv_bufs, net.mtu,
	     addparams->chanptr, &net.zone_uuid);
	i = virtpci_device_add(vbus, VIRTNIC_TYPE, addparams, NULL, &net);
	if (i) {
		LOGINF("Added vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
		       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
		       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
		POSTCODE_LINUX_3(VPCI_CREATE_EXIT_PC, i,
				 POSTCODE_SEVERITY_INFO);
		return 1;
	}
	return 0;
}

/* delete vbus
 * returns 0 failure, 1 success,
 */
static int
delete_vbus(struct del_vbus_guestpart *delparams)
{
	struct device *vbus;
	unsigned char busid[BUS_ID_SIZE];

	GET_BUS_DEV(delparams->bus_no);
	/* ensure that bus has no devices? -- TBD */
	LOGINF("Deleting %s\n", BUS_ID(vbus));
	if (delete_vbus_device(vbus, NULL))
		return 0;	/* failure */
	LOGINF("Deleted vbus %d\n", delparams->bus_no);
	return 1;
}

static int
delete_vbus_device(struct device *vbus, void *data)
{
	int checkforroot = (data != NULL);
	struct device *pDev = &virtpci_rootbus_device;

	if ((checkforroot) && match_busid(vbus, (void *)BUS_ID(pDev))) {
		/* skip it - don't delete root bus */
		LOGINF("skipping root bus\n");
		return 0;	/* pretend no error */
	}
	LOGINF("Calling unregister for %s\n", BUS_ID(vbus));
	device_unregister(vbus);
	kfree(vbus);
	LOGINF("VBus unregister and freed\n");
	return 0;		/* no error */
}

/* pause vhba
* returns 0 failure, 1 success,
*/
static int pause_vhba(struct pause_virt_guestpart *pauseparams)
{
	int i;
	struct scsi_adap_info scsi;

	GET_SCSIADAPINFO_FROM_CHANPTR(pauseparams->chanptr);

	LOGINF("Pausing vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1, scsi.wwnn.wwnn2);
	i = virtpci_device_serverdown(NULL /*no parent bus */ , VIRTHBA_TYPE,
				      &scsi.wwnn, NULL);
	if (i)
		LOGINF("Paused vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1,
		       scsi.wwnn.wwnn2);
	return i;
}

/* pause vnic
 * returns 0 failure, 1 success,
 */
static int pause_vnic(struct pause_virt_guestpart *pauseparams)
{
	int i;
	struct net_adap_info net;

	GET_NETADAPINFO_FROM_CHANPTR(pauseparams->chanptr);

	LOGINF("Pausing vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
	       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
	       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	i = virtpci_device_serverdown(NULL /*no parent bus */ , VIRTNIC_TYPE,
				      NULL, net.mac_addr);
	if (i) {
		LOGINF(" Paused vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
		       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
		       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	}
	return i;
}

/* resume vhba
 * returns 0 failure, 1 success,
 */
static int resume_vhba(struct resume_virt_guestpart *resumeparams)
{
	int i;
	struct scsi_adap_info scsi;

	GET_SCSIADAPINFO_FROM_CHANPTR(resumeparams->chanptr);

	LOGINF("Resuming vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1, scsi.wwnn.wwnn2);
	i = virtpci_device_serverup(NULL /*no parent bus */ , VIRTHBA_TYPE,
				    &scsi.wwnn, NULL);
	if (i)
		LOGINF("Resumed vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1,
		       scsi.wwnn.wwnn2);
	return i;
}

/* resume vnic
* returns 0 failure, 1 success,
*/
static int
resume_vnic(struct resume_virt_guestpart *resumeparams)
{
	int i;
	struct net_adap_info net;

	GET_NETADAPINFO_FROM_CHANPTR(resumeparams->chanptr);

	LOGINF("Resuming vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
	       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
	       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	i = virtpci_device_serverup(NULL /*no parent bus */ , VIRTNIC_TYPE,
				    NULL, net.mac_addr);
	if (i) {
		LOGINF(" Resumed vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
		       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
		       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	}
	return i;
}

/* delete vhba
* returns 0 failure, 1 success,
*/
static int delete_vhba(struct del_virt_guestpart *delparams)
{
	int i;
	struct scsi_adap_info scsi;

	GET_SCSIADAPINFO_FROM_CHANPTR(delparams->chanptr);

	LOGINF("Deleting vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1, scsi.wwnn.wwnn2);
	i = virtpci_device_del(NULL /*no parent bus */ , VIRTHBA_TYPE,
			       &scsi.wwnn, NULL);
	if (i) {
		LOGINF("Deleted vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1,
		       scsi.wwnn.wwnn2);
		return 1;
	}
	return 0;
}

/* deletes a vnic
 * returns 0 failure, 1 success,
 */
static int delete_vnic(struct del_virt_guestpart *delparams)
{
	int i;
	struct net_adap_info net;

	GET_NETADAPINFO_FROM_CHANPTR(delparams->chanptr);

	LOGINF("Deleting vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
	       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
	       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	i = virtpci_device_del(NULL /*no parent bus */ , VIRTNIC_TYPE, NULL,
			       net.mac_addr);
	if (i) {
		LOGINF("Deleted vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
		       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
		       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	}
	return i;
}

#define DELETE_ONE_VPCIDEV(vpcidev) { \
	LOGINF("calling device_unregister:%p\n", &vpcidev->generic_dev); \
	device_unregister(&vpcidev->generic_dev); \
	LOGINF("Deleted %p\n", vpcidev); \
	kfree(vpcidev); \
}

/* deletes all vhbas and vnics
 * returns 0 failure, 1 success,
 */
static void delete_all(void)
{
	int count = 0;
	unsigned long flags;
	struct virtpci_dev *tmpvpcidev, *nextvpcidev;

	/* delete the entire vhba/vnic list in one shot */
	write_lock_irqsave(&VpcidevListLock, flags);
	tmpvpcidev = VpcidevListHead;
	VpcidevListHead = NULL;
	write_unlock_irqrestore(&VpcidevListLock, flags);

	/* delete one vhba/vnic at a time */
	while (tmpvpcidev) {
		nextvpcidev = tmpvpcidev->next;
		/* delete the vhba/vnic at tmpvpcidev */
		DELETE_ONE_VPCIDEV(tmpvpcidev);
		tmpvpcidev = nextvpcidev;
		count++;
	}
	LOGINF("Deleted %d vhbas/vnics.\n", count);

	/* now delete each vbus */
	if (bus_for_each_dev
	    (&virtpci_bus_type, NULL, (void *)1, delete_vbus_device))
		LOGERR("delete of all vbus failed\n");
}

/* deletes all vnics or vhbas
 * returns 0 failure, 1 success,
 */
static int delete_all_virt(enum virtpci_dev_type devtype,
			   struct del_vbus_guestpart *delparams)
{
	int i;
	unsigned char busid[BUS_ID_SIZE];
	struct device *vbus;

	GET_BUS_DEV(delparams->bus_no);

	if ((devtype != VIRTHBA_TYPE) && (devtype != VIRTNIC_TYPE)) {
		LOGERR("**** FAILED to delete all devices; devtype:%d not vhba:%d or vnic:%d\n",
		     devtype, VIRTHBA_TYPE, VIRTNIC_TYPE);
		return 0;
	}

	LOGINF("Deleting all %s in vbus %s\n",
	       devtype == VIRTHBA_TYPE ? "vhbas" : "vnics", busid);
	/* delete all vhbas/vnics */
	i = virtpci_device_del(vbus, devtype, NULL, NULL);
	if (i > 0)
		LOGINF("Deleted %d %s\n", i,
		       devtype == VIRTHBA_TYPE ? "vhbas" : "vnics");
	return 1;
}

static int virtpci_ctrlchan_func(struct guest_msgs *msg)
{
	switch (msg->msgtype) {
	case GUEST_ADD_VBUS:
		return add_vbus(&msg->add_vbus);
	case GUEST_ADD_VHBA:
		return add_vhba(&msg->add_vhba);
	case GUEST_ADD_VNIC:
		return add_vnic(&msg->add_vnic);
	case GUEST_DEL_VBUS:
		return delete_vbus(&msg->del_vbus);
	case GUEST_DEL_VHBA:
		return delete_vhba(&msg->del_vhba);
	case GUEST_DEL_VNIC:
		return delete_vnic(&msg->del_vhba);
	case GUEST_DEL_ALL_VHBAS:
		return delete_all_virt(VIRTHBA_TYPE, &msg->del_all_vhbas);
	case GUEST_DEL_ALL_VNICS:
		return delete_all_virt(VIRTNIC_TYPE, &msg->del_all_vnics);
	case GUEST_DEL_ALL_VBUSES:
		delete_all();
		return 1;
	case GUEST_PAUSE_VHBA:
		return pause_vhba(&msg->pause_vhba);
	case GUEST_PAUSE_VNIC:
		return pause_vnic(&msg->pause_vnic);
	case GUEST_RESUME_VHBA:
		return resume_vhba(&msg->resume_vhba);
	case GUEST_RESUME_VNIC:
		return resume_vnic(&msg->resume_vnic);
	default:
		LOGERR("invalid message type %d.\n", msg->msgtype);
		return 0;
	}
}

/* same as driver_helper in bus.c linux */
static int match_busid(struct device *dev, void *data)
{
	const char *name = data;

	if (strcmp(name, BUS_ID(dev)) == 0)
		return 1;
	return 0;
}

/*****************************************************/
/*  Bus functions                                    */
/*****************************************************/

static const struct pci_device_id *
virtpci_match_device(const struct pci_device_id *ids,
		     const struct virtpci_dev *dev)
{
	while (ids->vendor || ids->subvendor || ids->class_mask) {
		DBGINF("ids->vendor:%x dev->vendor:%x ids->device:%x dev->device:%x\n",
		     ids->vendor, dev->vendor, ids->device, dev->device);

		if ((ids->vendor == dev->vendor)
		    && (ids->device == dev->device))
			return ids;

		ids++;
	}
	return NULL;
}

/* NOTE: !!!!!!  This function is called when a new device is added
* for this bus.  Or, it is called for existing devices when a new
* driver is added for this bus.  It returns nonzero if a given device
* can be handled by the given driver.
*/
static int virtpci_bus_match(struct device *dev, struct device_driver *drv)
{
	struct virtpci_dev *virtpcidev = device_to_virtpci_dev(dev);
	struct virtpci_driver *virtpcidrv = driver_to_virtpci_driver(drv);
	int match = 0;

	DBGINF("In virtpci_bus_match dev->bus_id:%s drv->name:%s\n",
	       dev->bus_id, drv->name);

	/* check ids list for a match */
	if (virtpci_match_device(virtpcidrv->id_table, virtpcidev))
		match = 1;

	DBGINF("returning match:%d\n", match);
	return match;		/* 0 - no match; 1 - yes it matches */
}

static int virtpci_uevent(struct device *dev, struct kobj_uevent_env *env)
{
	DBGINF("In virtpci_hotplug\n");
	/* add variables to the environment prior to the generation of
	 * hotplug events to user space
	 */
	if (add_uevent_var(env, "VIRTPCI_VERSION=%s", VIRTPCI_VERSION))
		return -ENOMEM;
	return 0;
}

static int virtpci_device_suspend(struct device *dev, pm_message_t state)
{
	DBGINF("In virtpci_device_suspend -NYI ****\n");
	return 0;
}

static int virtpci_device_resume(struct device *dev)
{
	DBGINF("In virtpci_device_resume -NYI ****\n");
	return 0;
}

/* For a child device just created on a client bus, fill in
 * information about the driver that is controlling this device into
 * the appropriate slot within the vbus channel of the bus
 * instance.
 */
static void fix_vbus_devInfo(struct device *dev, int devNo, int devType,
			     struct virtpci_driver *virtpcidrv)
{
	struct device *vbus;
	void *pChan;
	struct ultra_vbus_deviceinfo devInfo;
	const char *stype;

	if (!dev) {
		LOGERR("%s dev is NULL", __func__);
		return;
	}
	if (!virtpcidrv) {
		LOGERR("%s driver is NULL", __func__);
		return;
	}
	vbus = dev->parent;
	if (!vbus) {
		LOGERR("%s dev has no parent bus", __func__);
		return;
	}
	pChan = vbus->platform_data;
	if (!pChan) {
		LOGERR("%s dev bus has no channel", __func__);
		return;
	}
	switch (devType) {
	case PCI_DEVICE_ID_VIRTHBA:
		stype = "vHBA";
		break;
	case PCI_DEVICE_ID_VIRTNIC:
		stype = "vNIC";
		break;
	default:
		stype = "unknown";
		break;
	}
	bus_device_info_init(&devInfo, stype,
			   virtpcidrv->name,
			   virtpcidrv->version,
			   virtpcidrv->vertag);
	write_vbus_devInfo(pChan, &devInfo, devNo);

	/* Re-write bus+chipset info, because it is possible that this
	* was previously written by our good counterpart, visorbus.
	*/
	write_vbus_chpInfo(pChan, &Chipset_DriverInfo);
	write_vbus_busInfo(pChan, &Bus_DriverInfo);
}

/* This function is called to query the existence of a specific device
* and whether this driver can work with it.  It should return -ENODEV
* in case of failure.
*/
static int virtpci_device_probe(struct device *dev)
{
	struct virtpci_dev *virtpcidev = device_to_virtpci_dev(dev);
	struct virtpci_driver *virtpcidrv =
	    driver_to_virtpci_driver(dev->driver);
	const struct pci_device_id *id;
	int error = 0;

	LOGINF("In virtpci_device_probe dev:%p virtpcidev:%p virtpcidrv:%p\n",
	       dev, virtpcidev, virtpcidrv);	/* VERBOSE/DEBUG ? */
	POSTCODE_LINUX_2(VPCI_PROBE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
	/* static match and static probe vs dynamic match & dynamic
	 * probe - do we care?.
	 */
	if (!virtpcidrv->id_table)
		return -ENODEV;

	id = virtpci_match_device(virtpcidrv->id_table, virtpcidev);
	if (!id)
		return -ENODEV;

	/* increment reference count */
	get_device(dev);

	/* if virtpcidev is not already claimed & probe function is
	 * valid, probe it
	 */
	if (!virtpcidev->mydriver && virtpcidrv->probe) {
		/* call the probe function - virthba or virtnic probe
		 * is what it should be
		 */
		error = virtpcidrv->probe(virtpcidev, id);
		if (!error) {
			fix_vbus_devInfo(dev, virtpcidev->device_no,
					 virtpcidev->device, virtpcidrv);
			virtpcidev->mydriver = virtpcidrv;
			POSTCODE_LINUX_2(VPCI_PROBE_EXIT_PC,
					 POSTCODE_SEVERITY_INFO);
		} else
			put_device(dev);
	}
	POSTCODE_LINUX_2(VPCI_PROBE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
	return error;		/* -ENODEV for probe failure */
}

static int virtpci_device_remove(struct device *dev_)
{
	/* dev_ passed in is the HBA device which we called
	* generic_dev in our virtpcidev struct
	*/
	struct virtpci_dev *virtpcidev = device_to_virtpci_dev(dev_);
	struct virtpci_driver *virtpcidrv = virtpcidev->mydriver;

	LOGINF("In virtpci_device_remove bus_id:%s dev_:%p virtpcidev:%p dev->driver:%p drivername:%s\n",
	       BUS_ID(dev_), dev_, virtpcidev, dev_->driver,
	       dev_->driver->name);	/* VERBOSE/DEBUG */
	if (virtpcidrv) {
		/* TEMP: assuming we have only one such driver for now */
		if (virtpcidrv->remove)
			virtpcidrv->remove(virtpcidev);
		virtpcidev->mydriver = NULL;
	}

	DBGINF("calling putdevice\n");
	put_device(dev_);

	DBGINF("Leaving\n");
	return 0;
}

/*****************************************************/
/* Bus functions                                     */
/*****************************************************/

static void virtpci_bus_release(struct device *dev)
{
	/* this function is called when the last reference to the
	 * device is removed
	 */
	DBGINF("In virtpci_bus_release\n");
	/* what else is supposed to happen here? */
}

/*****************************************************/
/* Adapter functions                                 */
/*****************************************************/

static int virtpci_device_add(struct device *parentbus, int devtype,
			      struct add_virt_guestpart *addparams,
			      struct scsi_adap_info *scsi, /* NULL for VNIC add */
			      struct net_adap_info *net	/* NULL for VHBA add */)
{
	struct virtpci_dev *virtpcidev = NULL;
	struct virtpci_dev *tmpvpcidev = NULL, *prev;
	unsigned long flags;
	int ret;
	struct spar_io_channel_protocol __iomem *pIoChan = NULL;
	struct device *pDev;

	LOGINF("virtpci_device_add parentbus:%p chanptr:%p\n", parentbus,
	       addparams->chanptr);

	POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);

	if ((devtype != VIRTHBA_TYPE) && (devtype != VIRTNIC_TYPE)) {
		LOGERR("**** FAILED to add device; devtype:%d not vhba:%d or vnic:%d\n",
		     devtype, VIRTHBA_TYPE, VIRTNIC_TYPE);
		POSTCODE_LINUX_3(VPCI_CREATE_FAILURE_PC, devtype,
				 POSTCODE_SEVERITY_ERR);
		return 0;
	}

	/* add a Virtual Device */
	virtpcidev = kzalloc(sizeof(struct virtpci_dev), GFP_ATOMIC);
	if (virtpcidev == NULL) {
		LOGERR("can't add device - malloc FALLED\n");
		POSTCODE_LINUX_2(MALLOC_FAILURE_PC, POSTCODE_SEVERITY_ERR);
		return 0;
	}

	/* initialize stuff unique to virtpci_dev struct */
	virtpcidev->devtype = devtype;
	if (devtype == VIRTHBA_TYPE) {
		virtpcidev->device = PCI_DEVICE_ID_VIRTHBA;
		virtpcidev->scsi = *scsi;
	} else {
		virtpcidev->device = PCI_DEVICE_ID_VIRTNIC;
		virtpcidev->net = *net;
	}
	virtpcidev->vendor = PCI_VENDOR_ID_UNISYS;
	virtpcidev->bus_no = addparams->bus_no;
	virtpcidev->device_no = addparams->device_no;

	virtpcidev->queueinfo.chan = addparams->chanptr;
	virtpcidev->queueinfo.send_int_if_needed = NULL;

	/* Set up safe queue... */
	pIoChan = (struct spar_io_channel_protocol __iomem *)<commit_msg>staging: unisys: virtpci: Allocate memory using the size of the variable<commit_after>/* virtpci.c
 *
 * Copyright (C) 2010 - 2013 UNISYS CORPORATION
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * 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 <linux/kernel.h>
#ifdef CONFIG_MODVERSIONS
#include <config/modversions.h>
#endif
#include "uniklog.h"
#include "diagnostics/appos_subsystems.h"
#include "uisutils.h"
#include "vbuschannel.h"
#include "vbushelper.h"
#include <linux/types.h>
#include <linux/io.h>
#include <linux/uuid.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/mod_devicetable.h>
#include <linux/if_ether.h>
#include <linux/version.h>
#include <linux/debugfs.h>
#include "version.h"
#include "guestlinuxdebug.h"
#include "timskmod.h"

struct driver_private {
	struct kobject kobj;
	struct klist klist_devices;
	struct klist_node knode_bus;
	struct module_kobject *mkobj;
	struct device_driver *driver;
};

#define to_driver(obj) container_of(obj, struct driver_private, kobj)

/* bus_id went away in 2.6.30 - the size was 20 bytes, so we'll define
 * it ourselves, and a macro to make getting the field a bit simpler.
 */
#ifndef BUS_ID_SIZE
#define BUS_ID_SIZE 20
#endif

#define BUS_ID(x) dev_name(x)

/* MAX_BUF = 4 busses x ( 32 devices/bus + 1 busline) x 80 characters
 *         = 10,560 bytes ~ 2^14 = 16,384 bytes
 */
#define MAX_BUF 16384

#include "virtpci.h"

/* this is shorter than using __FILE__ (full path name) in
 * debug/info/error messages
 */
#define CURRENT_FILE_PC VIRT_PCI_PC_virtpci_c
#define __MYFILE__ "virtpci.c"

#define VIRTPCI_VERSION "01.00"

/*****************************************************/
/* Forward declarations                              */
/*****************************************************/

static int delete_vbus_device(struct device *vbus, void *data);
static int match_busid(struct device *dev, void *data);
static void virtpci_bus_release(struct device *dev);
static void virtpci_device_release(struct device *dev);
static int virtpci_device_add(struct device *parentbus, int devtype,
			      struct add_virt_guestpart *addparams,
			      struct scsi_adap_info *scsi,
			      struct net_adap_info *net);
static int virtpci_device_del(struct device *parentbus, int devtype,
			      struct vhba_wwnn *wwnn, unsigned char macaddr[]);
static int virtpci_device_serverdown(struct device *parentbus, int devtype,
				     struct vhba_wwnn *wwnn,
				     unsigned char macaddr[]);
static int virtpci_device_serverup(struct device *parentbus, int devtype,
				   struct vhba_wwnn *wwnn,
				   unsigned char macaddr[]);
static ssize_t virtpci_driver_attr_show(struct kobject *kobj,
					struct attribute *attr, char *buf);
static ssize_t virtpci_driver_attr_store(struct kobject *kobj,
					 struct attribute *attr,
					 const char *buf, size_t count);
static int virtpci_bus_match(struct device *dev, struct device_driver *drv);
static int virtpci_uevent(struct device *dev, struct kobj_uevent_env *env);
static int virtpci_device_suspend(struct device *dev, pm_message_t state);
static int virtpci_device_resume(struct device *dev);
static int virtpci_device_probe(struct device *dev);
static int virtpci_device_remove(struct device *dev);

static ssize_t info_debugfs_read(struct file *file, char __user *buf,
			      size_t len, loff_t *offset);

static const struct file_operations debugfs_info_fops = {
	.read = info_debugfs_read,
};

/*****************************************************/
/* Globals                                           */
/*****************************************************/

/* methods in bus_type struct allow the bus code to serve as an
 * intermediary between the device core and individual device core and
 * individual drivers
 */
static struct bus_type virtpci_bus_type = {
	.name = "uisvirtpci",
	.match = virtpci_bus_match,
	.uevent = virtpci_uevent,
	.suspend = virtpci_device_suspend,
	.resume = virtpci_device_resume,
};

static struct device virtpci_rootbus_device = {
	.init_name = "vbusroot",	/* root bus */
	.release = virtpci_bus_release
};

/* filled in with info about parent chipset driver when we register with it */
static struct ultra_vbus_deviceinfo Chipset_DriverInfo;

static const struct sysfs_ops virtpci_driver_sysfs_ops = {
	.show = virtpci_driver_attr_show,
	.store = virtpci_driver_attr_store,
};

static struct kobj_type virtpci_driver_kobj_type = {
	.sysfs_ops = &virtpci_driver_sysfs_ops,
};

static struct virtpci_dev *VpcidevListHead;
static DEFINE_RWLOCK(VpcidevListLock);

/* filled in with info about this driver, wrt it servicing client busses */
static struct ultra_vbus_deviceinfo Bus_DriverInfo;

/*****************************************************/
/* debugfs entries                                   */
/*****************************************************/
/* dentry is used to create the debugfs entry directory
 * for virtpci
 */
static struct dentry *virtpci_debugfs_dir;

struct virtpci_busdev {
	struct device virtpci_bus_device;
};

/*****************************************************/
/* Local functions                                   */
/*****************************************************/

static inline
int WAIT_FOR_IO_CHANNEL(struct spar_io_channel_protocol __iomem  *chanptr)
{
	int count = 120;

	while (count > 0) {
		if (SPAR_CHANNEL_SERVER_READY(&chanptr->channel_header))
			return 1;
		UIS_THREAD_WAIT_SEC(1);
		count--;
	}
	return 0;
}

/* Write the contents of <info> to the ULTRA_VBUS_CHANNEL_PROTOCOL.ChpInfo. */
static int write_vbus_chpInfo(struct spar_vbus_channel_protocol *chan,
			      struct ultra_vbus_deviceinfo *info)
{
	int off;

	if (!chan) {
		LOGERR("vbus channel not present");
		return -1;
	}
	off = sizeof(struct channel_header) + chan->hdr_info.chp_info_offset;
	if (chan->hdr_info.chp_info_offset == 0) {
		LOGERR("vbus channel not used, because chp_info_offset == 0");
		return -1;
	}
	memcpy(((u8 *)(chan)) + off, info, sizeof(*info));
	return 0;
}

/* Write the contents of <info> to the ULTRA_VBUS_CHANNEL_PROTOCOL.BusInfo. */
static int write_vbus_busInfo(struct spar_vbus_channel_protocol *chan,
			      struct ultra_vbus_deviceinfo *info)
{
	int off;

	if (!chan) {
		LOGERR("vbus channel not present");
		return -1;
	}
	off = sizeof(struct channel_header) + chan->hdr_info.bus_info_offset;
	if (chan->hdr_info.bus_info_offset == 0) {
		LOGERR("vbus channel not used, because bus_info_offset == 0");
		return -1;
	}
	memcpy(((u8 *)(chan)) + off, info, sizeof(*info));
	return 0;
}

/* Write the contents of <info> to the
 * ULTRA_VBUS_CHANNEL_PROTOCOL.DevInfo[<devix>].
 */
static int
write_vbus_devInfo(struct spar_vbus_channel_protocol *chan,
		   struct ultra_vbus_deviceinfo *info, int devix)
{
	int off;

	if (!chan) {
		LOGERR("vbus channel not present");
		return -1;
	}
	off =
	    (sizeof(struct channel_header) +
	     chan->hdr_info.dev_info_offset) +
	    (chan->hdr_info.device_info_struct_bytes * devix);
	if (chan->hdr_info.dev_info_offset == 0) {
		LOGERR("vbus channel not used, because dev_info_offset == 0");
		return -1;
	}
	memcpy(((u8 *)(chan)) + off, info, sizeof(*info));
	return 0;
}

/* adds a vbus
 * returns 0 failure, 1 success,
 */
static int add_vbus(struct add_vbus_guestpart *addparams)
{
	int ret;
	struct device *vbus;

	vbus = kzalloc(sizeof(*vbus), GFP_ATOMIC);

	POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
	if (!vbus)
		return 0;

	dev_set_name(vbus, "vbus%d", addparams->bus_no);
	vbus->release = virtpci_bus_release;
	vbus->parent = &virtpci_rootbus_device;	/* root bus is parent */
	vbus->bus = &virtpci_bus_type;	/* bus type */
	vbus->platform_data = (__force void *)addparams->chanptr;

	/* register a virt bus device -
	 * this bus shows up under /sys/devices with .name value
	 * "virtpci%d" any devices added to this bus then show up under
	 * /sys/devices/virtpci0
	 */
	ret = device_register(vbus);
	if (ret) {
		LOGERR("device_register FAILED:%d\n", ret);
		POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
		return 0;
	}
	write_vbus_chpInfo(vbus->platform_data /* chanptr */ ,
			   &Chipset_DriverInfo);
	write_vbus_busInfo(vbus->platform_data /* chanptr */ , &Bus_DriverInfo);
	LOGINF("Added vbus %d; device %s created successfully\n",
	       addparams->bus_no, BUS_ID(vbus));
	POSTCODE_LINUX_2(VPCI_CREATE_EXIT_PC, POSTCODE_SEVERITY_INFO);
	return 1;
}

/* for CHANSOCK wwwnn/max are AUTO-GENERATED; for normal channels,
 * wwnn/max are in the channel header.
 */
#define GET_SCSIADAPINFO_FROM_CHANPTR(chanptr) {			\
	memcpy_fromio(&scsi.wwnn,					\
		      &((struct spar_io_channel_protocol __iomem *)	\
			chanptr)->vhba.wwnn,				\
		      sizeof(struct vhba_wwnn));			\
	memcpy_fromio(&scsi.max,					\
		      &((struct spar_io_channel_protocol __iomem *)	\
			chanptr)->vhba.max,				\
		      sizeof(struct vhba_config_max));			\
	}

/* find bus device with the busid that matches - match_busid matches bus_id */
#define GET_BUS_DEV(busno) { \
	sprintf(busid, "vbus%d", busno); \
	vbus = bus_find_device(&virtpci_bus_type, NULL, \
			       (void *)busid, match_busid);	\
	if (!vbus) { \
		LOGERR("**** FAILED to find vbus %s\n", busid); \
		return 0; \
	} \
}

/* adds a vhba
 * returns 0 failure, 1 success,
 */
static int add_vhba(struct add_virt_guestpart *addparams)
{
	int i;
	struct scsi_adap_info scsi;
	struct device *vbus;
	unsigned char busid[BUS_ID_SIZE];

	POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
	if (!WAIT_FOR_IO_CHANNEL
	    ((struct spar_io_channel_protocol __iomem *)addparams->chanptr)) {
		LOGERR("Timed out.  Channel not ready\n");
		POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
		return 0;
	}

	GET_SCSIADAPINFO_FROM_CHANPTR(addparams->chanptr);

	GET_BUS_DEV(addparams->bus_no);

	LOGINF("Adding vhba wwnn:%x:%x config:%d-%d-%d-%d chanptr:%p\n",
	       scsi.wwnn.wwnn1, scsi.wwnn.wwnn2,
	       scsi.max.max_channel, scsi.max.max_id, scsi.max.max_lun,
	       scsi.max.cmd_per_lun, addparams->chanptr);
	i = virtpci_device_add(vbus, VIRTHBA_TYPE, addparams, &scsi, NULL);
	if (i) {
		LOGINF("Added vhba wwnn:%x:%x chanptr:%p\n", scsi.wwnn.wwnn1,
		       scsi.wwnn.wwnn2, addparams->chanptr);
		POSTCODE_LINUX_3(VPCI_CREATE_EXIT_PC, i,
				 POSTCODE_SEVERITY_INFO);
	}
	return i;
}

/* for CHANSOCK macaddr is AUTO-GENERATED; for normal channels,
 * macaddr is in the channel header.
 */
#define GET_NETADAPINFO_FROM_CHANPTR(chanptr) {				\
		memcpy_fromio(net.mac_addr,				\
		       ((struct spar_io_channel_protocol __iomem *)	\
		       chanptr)->vnic.macaddr,				\
		       MAX_MACADDR_LEN);				\
		net.num_rcv_bufs =					\
			readl(&((struct spar_io_channel_protocol __iomem *)\
			      chanptr)->vnic.num_rcv_bufs);		\
		net.mtu = readl(&((struct spar_io_channel_protocol __iomem *) \
				chanptr)->vnic.mtu);			\
		memcpy_fromio(&net.zone_uuid, \
			      &((struct spar_io_channel_protocol __iomem *)\
			      chanptr)->vnic.zone_uuid,		\
			      sizeof(uuid_le));				\
}

/* adds a vnic
 * returns 0 failure, 1 success,
 */
static int
add_vnic(struct add_virt_guestpart *addparams)
{
	int i;
	struct net_adap_info net;
	struct device *vbus;
	unsigned char busid[BUS_ID_SIZE];

	POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
	if (!WAIT_FOR_IO_CHANNEL
	    ((struct spar_io_channel_protocol __iomem *)addparams->chanptr)) {
		LOGERR("Timed out, channel not ready\n");
		POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
		return 0;
	}

	GET_NETADAPINFO_FROM_CHANPTR(addparams->chanptr);

	GET_BUS_DEV(addparams->bus_no);

	LOGINF("Adding vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x rcvbufs:%d mtu:%d chanptr:%p%pUL\n",
	     net.mac_addr[0], net.mac_addr[1], net.mac_addr[2], net.mac_addr[3],
	     net.mac_addr[4], net.mac_addr[5], net.num_rcv_bufs, net.mtu,
	     addparams->chanptr, &net.zone_uuid);
	i = virtpci_device_add(vbus, VIRTNIC_TYPE, addparams, NULL, &net);
	if (i) {
		LOGINF("Added vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
		       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
		       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
		POSTCODE_LINUX_3(VPCI_CREATE_EXIT_PC, i,
				 POSTCODE_SEVERITY_INFO);
		return 1;
	}
	return 0;
}

/* delete vbus
 * returns 0 failure, 1 success,
 */
static int
delete_vbus(struct del_vbus_guestpart *delparams)
{
	struct device *vbus;
	unsigned char busid[BUS_ID_SIZE];

	GET_BUS_DEV(delparams->bus_no);
	/* ensure that bus has no devices? -- TBD */
	LOGINF("Deleting %s\n", BUS_ID(vbus));
	if (delete_vbus_device(vbus, NULL))
		return 0;	/* failure */
	LOGINF("Deleted vbus %d\n", delparams->bus_no);
	return 1;
}

static int
delete_vbus_device(struct device *vbus, void *data)
{
	int checkforroot = (data != NULL);
	struct device *pDev = &virtpci_rootbus_device;

	if ((checkforroot) && match_busid(vbus, (void *)BUS_ID(pDev))) {
		/* skip it - don't delete root bus */
		LOGINF("skipping root bus\n");
		return 0;	/* pretend no error */
	}
	LOGINF("Calling unregister for %s\n", BUS_ID(vbus));
	device_unregister(vbus);
	kfree(vbus);
	LOGINF("VBus unregister and freed\n");
	return 0;		/* no error */
}

/* pause vhba
* returns 0 failure, 1 success,
*/
static int pause_vhba(struct pause_virt_guestpart *pauseparams)
{
	int i;
	struct scsi_adap_info scsi;

	GET_SCSIADAPINFO_FROM_CHANPTR(pauseparams->chanptr);

	LOGINF("Pausing vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1, scsi.wwnn.wwnn2);
	i = virtpci_device_serverdown(NULL /*no parent bus */ , VIRTHBA_TYPE,
				      &scsi.wwnn, NULL);
	if (i)
		LOGINF("Paused vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1,
		       scsi.wwnn.wwnn2);
	return i;
}

/* pause vnic
 * returns 0 failure, 1 success,
 */
static int pause_vnic(struct pause_virt_guestpart *pauseparams)
{
	int i;
	struct net_adap_info net;

	GET_NETADAPINFO_FROM_CHANPTR(pauseparams->chanptr);

	LOGINF("Pausing vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
	       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
	       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	i = virtpci_device_serverdown(NULL /*no parent bus */ , VIRTNIC_TYPE,
				      NULL, net.mac_addr);
	if (i) {
		LOGINF(" Paused vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
		       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
		       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	}
	return i;
}

/* resume vhba
 * returns 0 failure, 1 success,
 */
static int resume_vhba(struct resume_virt_guestpart *resumeparams)
{
	int i;
	struct scsi_adap_info scsi;

	GET_SCSIADAPINFO_FROM_CHANPTR(resumeparams->chanptr);

	LOGINF("Resuming vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1, scsi.wwnn.wwnn2);
	i = virtpci_device_serverup(NULL /*no parent bus */ , VIRTHBA_TYPE,
				    &scsi.wwnn, NULL);
	if (i)
		LOGINF("Resumed vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1,
		       scsi.wwnn.wwnn2);
	return i;
}

/* resume vnic
* returns 0 failure, 1 success,
*/
static int
resume_vnic(struct resume_virt_guestpart *resumeparams)
{
	int i;
	struct net_adap_info net;

	GET_NETADAPINFO_FROM_CHANPTR(resumeparams->chanptr);

	LOGINF("Resuming vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
	       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
	       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	i = virtpci_device_serverup(NULL /*no parent bus */ , VIRTNIC_TYPE,
				    NULL, net.mac_addr);
	if (i) {
		LOGINF(" Resumed vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
		       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
		       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	}
	return i;
}

/* delete vhba
* returns 0 failure, 1 success,
*/
static int delete_vhba(struct del_virt_guestpart *delparams)
{
	int i;
	struct scsi_adap_info scsi;

	GET_SCSIADAPINFO_FROM_CHANPTR(delparams->chanptr);

	LOGINF("Deleting vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1, scsi.wwnn.wwnn2);
	i = virtpci_device_del(NULL /*no parent bus */ , VIRTHBA_TYPE,
			       &scsi.wwnn, NULL);
	if (i) {
		LOGINF("Deleted vhba wwnn:%x:%x\n", scsi.wwnn.wwnn1,
		       scsi.wwnn.wwnn2);
		return 1;
	}
	return 0;
}

/* deletes a vnic
 * returns 0 failure, 1 success,
 */
static int delete_vnic(struct del_virt_guestpart *delparams)
{
	int i;
	struct net_adap_info net;

	GET_NETADAPINFO_FROM_CHANPTR(delparams->chanptr);

	LOGINF("Deleting vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
	       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
	       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	i = virtpci_device_del(NULL /*no parent bus */ , VIRTNIC_TYPE, NULL,
			       net.mac_addr);
	if (i) {
		LOGINF("Deleted vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x\n",
		       net.mac_addr[0], net.mac_addr[1], net.mac_addr[2],
		       net.mac_addr[3], net.mac_addr[4], net.mac_addr[5]);
	}
	return i;
}

#define DELETE_ONE_VPCIDEV(vpcidev) { \
	LOGINF("calling device_unregister:%p\n", &vpcidev->generic_dev); \
	device_unregister(&vpcidev->generic_dev); \
	LOGINF("Deleted %p\n", vpcidev); \
	kfree(vpcidev); \
}

/* deletes all vhbas and vnics
 * returns 0 failure, 1 success,
 */
static void delete_all(void)
{
	int count = 0;
	unsigned long flags;
	struct virtpci_dev *tmpvpcidev, *nextvpcidev;

	/* delete the entire vhba/vnic list in one shot */
	write_lock_irqsave(&VpcidevListLock, flags);
	tmpvpcidev = VpcidevListHead;
	VpcidevListHead = NULL;
	write_unlock_irqrestore(&VpcidevListLock, flags);

	/* delete one vhba/vnic at a time */
	while (tmpvpcidev) {
		nextvpcidev = tmpvpcidev->next;
		/* delete the vhba/vnic at tmpvpcidev */
		DELETE_ONE_VPCIDEV(tmpvpcidev);
		tmpvpcidev = nextvpcidev;
		count++;
	}
	LOGINF("Deleted %d vhbas/vnics.\n", count);

	/* now delete each vbus */
	if (bus_for_each_dev
	    (&virtpci_bus_type, NULL, (void *)1, delete_vbus_device))
		LOGERR("delete of all vbus failed\n");
}

/* deletes all vnics or vhbas
 * returns 0 failure, 1 success,
 */
static int delete_all_virt(enum virtpci_dev_type devtype,
			   struct del_vbus_guestpart *delparams)
{
	int i;
	unsigned char busid[BUS_ID_SIZE];
	struct device *vbus;

	GET_BUS_DEV(delparams->bus_no);

	if ((devtype != VIRTHBA_TYPE) && (devtype != VIRTNIC_TYPE)) {
		LOGERR("**** FAILED to delete all devices; devtype:%d not vhba:%d or vnic:%d\n",
		     devtype, VIRTHBA_TYPE, VIRTNIC_TYPE);
		return 0;
	}

	LOGINF("Deleting all %s in vbus %s\n",
	       devtype == VIRTHBA_TYPE ? "vhbas" : "vnics", busid);
	/* delete all vhbas/vnics */
	i = virtpci_device_del(vbus, devtype, NULL, NULL);
	if (i > 0)
		LOGINF("Deleted %d %s\n", i,
		       devtype == VIRTHBA_TYPE ? "vhbas" : "vnics");
	return 1;
}

static int virtpci_ctrlchan_func(struct guest_msgs *msg)
{
	switch (msg->msgtype) {
	case GUEST_ADD_VBUS:
		return add_vbus(&msg->add_vbus);
	case GUEST_ADD_VHBA:
		return add_vhba(&msg->add_vhba);
	case GUEST_ADD_VNIC:
		return add_vnic(&msg->add_vnic);
	case GUEST_DEL_VBUS:
		return delete_vbus(&msg->del_vbus);
	case GUEST_DEL_VHBA:
		return delete_vhba(&msg->del_vhba);
	case GUEST_DEL_VNIC:
		return delete_vnic(&msg->del_vhba);
	case GUEST_DEL_ALL_VHBAS:
		return delete_all_virt(VIRTHBA_TYPE, &msg->del_all_vhbas);
	case GUEST_DEL_ALL_VNICS:
		return delete_all_virt(VIRTNIC_TYPE, &msg->del_all_vnics);
	case GUEST_DEL_ALL_VBUSES:
		delete_all();
		return 1;
	case GUEST_PAUSE_VHBA:
		return pause_vhba(&msg->pause_vhba);
	case GUEST_PAUSE_VNIC:
		return pause_vnic(&msg->pause_vnic);
	case GUEST_RESUME_VHBA:
		return resume_vhba(&msg->resume_vhba);
	case GUEST_RESUME_VNIC:
		return resume_vnic(&msg->resume_vnic);
	default:
		LOGERR("invalid message type %d.\n", msg->msgtype);
		return 0;
	}
}

/* same as driver_helper in bus.c linux */
static int match_busid(struct device *dev, void *data)
{
	const char *name = data;

	if (strcmp(name, BUS_ID(dev)) == 0)
		return 1;
	return 0;
}

/*****************************************************/
/*  Bus functions                                    */
/*****************************************************/

static const struct pci_device_id *
virtpci_match_device(const struct pci_device_id *ids,
		     const struct virtpci_dev *dev)
{
	while (ids->vendor || ids->subvendor || ids->class_mask) {
		DBGINF("ids->vendor:%x dev->vendor:%x ids->device:%x dev->device:%x\n",
		     ids->vendor, dev->vendor, ids->device, dev->device);

		if ((ids->vendor == dev->vendor)
		    && (ids->device == dev->device))
			return ids;

		ids++;
	}
	return NULL;
}

/* NOTE: !!!!!!  This function is called when a new device is added
* for this bus.  Or, it is called for existing devices when a new
* driver is added for this bus.  It returns nonzero if a given device
* can be handled by the given driver.
*/
static int virtpci_bus_match(struct device *dev, struct device_driver *drv)
{
	struct virtpci_dev *virtpcidev = device_to_virtpci_dev(dev);
	struct virtpci_driver *virtpcidrv = driver_to_virtpci_driver(drv);
	int match = 0;

	DBGINF("In virtpci_bus_match dev->bus_id:%s drv->name:%s\n",
	       dev->bus_id, drv->name);

	/* check ids list for a match */
	if (virtpci_match_device(virtpcidrv->id_table, virtpcidev))
		match = 1;

	DBGINF("returning match:%d\n", match);
	return match;		/* 0 - no match; 1 - yes it matches */
}

static int virtpci_uevent(struct device *dev, struct kobj_uevent_env *env)
{
	DBGINF("In virtpci_hotplug\n");
	/* add variables to the environment prior to the generation of
	 * hotplug events to user space
	 */
	if (add_uevent_var(env, "VIRTPCI_VERSION=%s", VIRTPCI_VERSION))
		return -ENOMEM;
	return 0;
}

static int virtpci_device_suspend(struct device *dev, pm_message_t state)
{
	DBGINF("In virtpci_device_suspend -NYI ****\n");
	return 0;
}

static int virtpci_device_resume(struct device *dev)
{
	DBGINF("In virtpci_device_resume -NYI ****\n");
	return 0;
}

/* For a child device just created on a client bus, fill in
 * information about the driver that is controlling this device into
 * the appropriate slot within the vbus channel of the bus
 * instance.
 */
static void fix_vbus_devInfo(struct device *dev, int devNo, int devType,
			     struct virtpci_driver *virtpcidrv)
{
	struct device *vbus;
	void *pChan;
	struct ultra_vbus_deviceinfo devInfo;
	const char *stype;

	if (!dev) {
		LOGERR("%s dev is NULL", __func__);
		return;
	}
	if (!virtpcidrv) {
		LOGERR("%s driver is NULL", __func__);
		return;
	}
	vbus = dev->parent;
	if (!vbus) {
		LOGERR("%s dev has no parent bus", __func__);
		return;
	}
	pChan = vbus->platform_data;
	if (!pChan) {
		LOGERR("%s dev bus has no channel", __func__);
		return;
	}
	switch (devType) {
	case PCI_DEVICE_ID_VIRTHBA:
		stype = "vHBA";
		break;
	case PCI_DEVICE_ID_VIRTNIC:
		stype = "vNIC";
		break;
	default:
		stype = "unknown";
		break;
	}
	bus_device_info_init(&devInfo, stype,
			   virtpcidrv->name,
			   virtpcidrv->version,
			   virtpcidrv->vertag);
	write_vbus_devInfo(pChan, &devInfo, devNo);

	/* Re-write bus+chipset info, because it is possible that this
	* was previously written by our good counterpart, visorbus.
	*/
	write_vbus_chpInfo(pChan, &Chipset_DriverInfo);
	write_vbus_busInfo(pChan, &Bus_DriverInfo);
}

/* This function is called to query the existence of a specific device
* and whether this driver can work with it.  It should return -ENODEV
* in case of failure.
*/
static int virtpci_device_probe(struct device *dev)
{
	struct virtpci_dev *virtpcidev = device_to_virtpci_dev(dev);
	struct virtpci_driver *virtpcidrv =
	    driver_to_virtpci_driver(dev->driver);
	const struct pci_device_id *id;
	int error = 0;

	LOGINF("In virtpci_device_probe dev:%p virtpcidev:%p virtpcidrv:%p\n",
	       dev, virtpcidev, virtpcidrv);	/* VERBOSE/DEBUG ? */
	POSTCODE_LINUX_2(VPCI_PROBE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
	/* static match and static probe vs dynamic match & dynamic
	 * probe - do we care?.
	 */
	if (!virtpcidrv->id_table)
		return -ENODEV;

	id = virtpci_match_device(virtpcidrv->id_table, virtpcidev);
	if (!id)
		return -ENODEV;

	/* increment reference count */
	get_device(dev);

	/* if virtpcidev is not already claimed & probe function is
	 * valid, probe it
	 */
	if (!virtpcidev->mydriver && virtpcidrv->probe) {
		/* call the probe function - virthba or virtnic probe
		 * is what it should be
		 */
		error = virtpcidrv->probe(virtpcidev, id);
		if (!error) {
			fix_vbus_devInfo(dev, virtpcidev->device_no,
					 virtpcidev->device, virtpcidrv);
			virtpcidev->mydriver = virtpcidrv;
			POSTCODE_LINUX_2(VPCI_PROBE_EXIT_PC,
					 POSTCODE_SEVERITY_INFO);
		} else
			put_device(dev);
	}
	POSTCODE_LINUX_2(VPCI_PROBE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
	return error;		/* -ENODEV for probe failure */
}

static int virtpci_device_remove(struct device *dev_)
{
	/* dev_ passed in is the HBA device which we called
	* generic_dev in our virtpcidev struct
	*/
	struct virtpci_dev *virtpcidev = device_to_virtpci_dev(dev_);
	struct virtpci_driver *virtpcidrv = virtpcidev->mydriver;

	LOGINF("In virtpci_device_remove bus_id:%s dev_:%p virtpcidev:%p dev->driver:%p drivername:%s\n",
	       BUS_ID(dev_), dev_, virtpcidev, dev_->driver,
	       dev_->driver->name);	/* VERBOSE/DEBUG */
	if (virtpcidrv) {
		/* TEMP: assuming we have only one such driver for now */
		if (virtpcidrv->remove)
			virtpcidrv->remove(virtpcidev);
		virtpcidev->mydriver = NULL;
	}

	DBGINF("calling putdevice\n");
	put_device(dev_);

	DBGINF("Leaving\n");
	return 0;
}

/*****************************************************/
/* Bus functions                                     */
/*****************************************************/

static void virtpci_bus_release(struct device *dev)
{
	/* this function is called when the last reference to the
	 * device is removed
	 */
	DBGINF("In virtpci_bus_release\n");
	/* what else is supposed to happen here? */
}

/*****************************************************/
/* Adapter functions                                 */
/*****************************************************/

static int virtpci_device_add(struct device *parentbus, int devtype,
			      struct add_virt_guestpart *addparams,
			      struct scsi_adap_info *scsi, /* NULL for VNIC add */
			      struct net_adap_info *net	/* NULL for VHBA add */)
{
	struct virtpci_dev *virtpcidev = NULL;
	struct virtpci_dev *tmpvpcidev = NULL, *prev;
	unsigned long flags;
	int ret;
	struct spar_io_channel_protocol __iomem *pIoChan = NULL;
	struct device *pDev;

	LOGINF("virtpci_device_add parentbus:%p chanptr:%p\n", parentbus,
	       addparams->chanptr);

	POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);

	if ((devtype != VIRTHBA_TYPE) && (devtype != VIRTNIC_TYPE)) {
		LOGERR("**** FAILED to add device; devtype:%d not vhba:%d or vnic:%d\n",
		     devtype, VIRTHBA_TYPE, VIRTNIC_TYPE);
		POSTCODE_LINUX_3(VPCI_CREATE_FAILURE_PC, devtype,
				 POSTCODE_SEVERITY_ERR);
		return 0;
	}

	/* add a Virtual Device */
	virtpcidev = kzalloc(sizeof(*virtpcidev), GFP_ATOMIC);
	if (virtpcidev == NULL) {
		LOGERR("can't add device - malloc FALLED\n");
		POSTCODE_LINUX_2(MALLOC_FAILURE_PC, POSTCODE_SEVERITY_ERR);
		return 0;
	}

	/* initialize stuff unique to virtpci_dev struct */
	virtpcidev->devtype = devtype;
	if (devtype == VIRTHBA_TYPE) {
		virtpcidev->device = PCI_DEVICE_ID_VIRTHBA;
		virtpcidev->scsi = *scsi;
	} else {
		virtpcidev->device = PCI_DEVICE_ID_VIRTNIC;
		virtpcidev->net = *net;
	}
	virtpcidev->vendor = PCI_VENDOR_ID_UNISYS;
	virtpcidev->bus_no = addparams->bus_no;
	virtpcidev->device_no = addparams->device_no;

	virtpcidev->queueinfo.chan = addparams->chanptr;
	virtpcidev->queueinfo.send_int_if_needed = NULL;

	/* Set up safe queue... */
	pIoChan = (struct spar_io_channel_protocol __iomem *)
{'fraction_non_alphanumeric': 0.1460351952123112, 'fraction_numerical': 0.010864918780279282, 'mean_word_length': 4.2398506766215585, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 273, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5701463', 'n_tokens_mistral': 22270, 'n_tokens_neox': 21009, 'n_words': 4774}