summaryrefslogtreecommitdiff
path: root/drivers/staging/unisys/virtpci/virtpci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/unisys/virtpci/virtpci.c')
-rw-r--r--drivers/staging/unisys/virtpci/virtpci.c1394
1 files changed, 1394 insertions, 0 deletions
diff --git a/drivers/staging/unisys/virtpci/virtpci.c b/drivers/staging/unisys/virtpci/virtpci.c
new file mode 100644
index 000000000..d5ad01783
--- /dev/null
+++ b/drivers/staging/unisys/virtpci/virtpci.c
@@ -0,0 +1,1394 @@
+/* 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 "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_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,
+};
+
+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_driver_info;
+
+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 *vpcidev_list_head;
+static DEFINE_RWLOCK(vpcidev_list_lock);
+
+/* filled in with info about this driver, wrt it servicing client busses */
+static struct ultra_vbus_deviceinfo bus_driver_info;
+
+/*****************************************************/
+/* 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_chp_info(struct spar_vbus_channel_protocol *chan,
+ struct ultra_vbus_deviceinfo *info)
+{
+ int off;
+
+ if (!chan)
+ return -1;
+
+ off = sizeof(struct channel_header) + chan->hdr_info.chp_info_offset;
+ if (chan->hdr_info.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_bus_info(struct spar_vbus_channel_protocol *chan,
+ struct ultra_vbus_deviceinfo *info)
+{
+ int off;
+
+ if (!chan)
+ return -1;
+
+ off = sizeof(struct channel_header) + chan->hdr_info.bus_info_offset;
+ if (chan->hdr_info.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_dev_info(struct spar_vbus_channel_protocol *chan,
+ struct ultra_vbus_deviceinfo *info, int devix)
+{
+ int off;
+
+ if (!chan)
+ 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)
+ 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) {
+ POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
+ return 0;
+ }
+ write_vbus_chp_info(vbus->platform_data /* chanptr */,
+ &chipset_driver_info);
+ write_vbus_bus_info(vbus->platform_data /* chanptr */,
+ &bus_driver_info);
+ 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)); \
+ }
+
+/* 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)) {
+ POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
+ return 0;
+ }
+
+ GET_SCSIADAPINFO_FROM_CHANPTR(addparams->chanptr);
+
+ /* find bus device with the busid that matches match_busid */
+ sprintf(busid, "vbus%d", addparams->bus_no);
+ vbus = bus_find_device(&virtpci_bus_type, NULL,
+ (void *)busid, match_busid);
+ if (!vbus)
+ return 0;
+
+ i = virtpci_device_add(vbus, VIRTHBA_TYPE, addparams, &scsi, NULL);
+ if (i) {
+ 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)) {
+ POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
+ return 0;
+ }
+
+ GET_NETADAPINFO_FROM_CHANPTR(addparams->chanptr);
+
+ /* find bus device with the busid that matches match_busid */
+ sprintf(busid, "vbus%d", addparams->bus_no);
+ vbus = bus_find_device(&virtpci_bus_type, NULL,
+ (void *)busid, match_busid);
+ if (!vbus)
+ return 0;
+
+ i = virtpci_device_add(vbus, VIRTNIC_TYPE, addparams, NULL, &net);
+ if (i) {
+ 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];
+
+ /* find bus device with the busid that matches match_busid */
+ sprintf(busid, "vbus%d", delparams->bus_no);
+ vbus = bus_find_device(&virtpci_bus_type, NULL,
+ (void *)busid, match_busid);
+ if (!vbus)
+ return 0;
+
+ /* ensure that bus has no devices? -- TBD */
+ return 1;
+}
+
+static int
+delete_vbus_device(struct device *vbus, void *data)
+{
+ struct device *dev = &virtpci_rootbus_device;
+
+ if ((data) && match_busid(vbus, (void *)BUS_ID(dev))) {
+ /* skip it - don't delete root bus */
+ return 0; /* pretend no error */
+ }
+ device_unregister(vbus);
+ kfree(vbus);
+ 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);
+
+ i = virtpci_device_serverdown(NULL /*no parent bus */, VIRTHBA_TYPE,
+ &scsi.wwnn, NULL);
+ 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);
+
+ i = virtpci_device_serverdown(NULL /*no parent bus */, VIRTNIC_TYPE,
+ NULL, net.mac_addr);
+ 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);
+
+ i = virtpci_device_serverup(NULL /*no parent bus */, VIRTHBA_TYPE,
+ &scsi.wwnn, NULL);
+ 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);
+
+ i = virtpci_device_serverup(NULL /*no parent bus */, VIRTNIC_TYPE,
+ NULL, net.mac_addr);
+ 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);
+
+ i = virtpci_device_del(NULL /*no parent bus */, VIRTHBA_TYPE,
+ &scsi.wwnn, NULL);
+ if (i) {
+ 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);
+
+ i = virtpci_device_del(NULL /*no parent bus */, VIRTNIC_TYPE, NULL,
+ net.mac_addr);
+ return i;
+}
+
+#define DELETE_ONE_VPCIDEV(vpcidev) { \
+ device_unregister(&vpcidev->generic_dev); \
+ 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(&vpcidev_list_lock, flags);
+ tmpvpcidev = vpcidev_list_head;
+ vpcidev_list_head = NULL;
+ write_unlock_irqrestore(&vpcidev_list_lock, 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++;
+ }
+
+ /* now delete each vbus */
+ bus_for_each_dev(&virtpci_bus_type, NULL, (void *)1,
+ delete_vbus_device);
+}
+
+/* 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;
+
+ /* find bus device with the busid that matches match_busid */
+ sprintf(busid, "vbus%d", delparams->bus_no);
+ vbus = bus_find_device(&virtpci_bus_type, NULL,
+ (void *)busid, match_busid);
+ if (!vbus)
+ return 0;
+
+ if ((devtype != VIRTHBA_TYPE) && (devtype != VIRTNIC_TYPE))
+ return 0;
+
+ /* delete all vhbas/vnics */
+ i = virtpci_device_del(vbus, devtype, NULL, NULL);
+ 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:
+ 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) {
+ 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;
+
+ /* check ids list for a match */
+ if (virtpci_match_device(virtpcidrv->id_table, virtpcidev))
+ match = 1;
+
+ return match; /* 0 - no match; 1 - yes it matches */
+}
+
+static int virtpci_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ /* 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;
+}
+
+/* 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_dev_info(struct device *dev, int dev_no, int dev_type,
+ struct virtpci_driver *virtpcidrv)
+{
+ struct device *vbus;
+ void *chan;
+ struct ultra_vbus_deviceinfo dev_info;
+ const char *stype;
+
+ if (!dev)
+ return;
+ if (!virtpcidrv)
+ return;
+
+ vbus = dev->parent;
+ if (!vbus)
+ return;
+
+ chan = vbus->platform_data;
+ if (!chan)
+ return;
+
+ switch (dev_type) {
+ case PCI_DEVICE_ID_VIRTHBA:
+ stype = "vHBA";
+ break;
+ case PCI_DEVICE_ID_VIRTNIC:
+ stype = "vNIC";
+ break;
+ default:
+ stype = "unknown";
+ break;
+ }
+ bus_device_info_init(&dev_info, stype,
+ virtpcidrv->name,
+ virtpcidrv->version,
+ virtpcidrv->vertag);
+ write_vbus_dev_info(chan, &dev_info, dev_no);
+
+ /* Re-write bus+chipset info, because it is possible that this
+ * was previously written by our good counterpart, visorbus.
+ */
+ write_vbus_chp_info(chan, &chipset_driver_info);
+ write_vbus_bus_info(chan, &bus_driver_info);
+}
+
+/* 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;
+
+ 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_dev_info(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;
+
+ if (virtpcidrv) {
+ /* TEMP: assuming we have only one such driver for now */
+ if (virtpcidrv->remove)
+ virtpcidrv->remove(virtpcidev);
+ virtpcidev->mydriver = NULL;
+ }
+
+ put_device(dev_);
+ return 0;
+}
+
+/*****************************************************/
+/* Bus functions */
+/*****************************************************/
+
+static void virtpci_bus_release(struct device *dev)
+{
+}
+
+/*****************************************************/
+/* Adapter functions */
+/*****************************************************/
+
+/* scsi is expected to be NULL for VNIC add
+ * net is expected to be NULL for VHBA add
+ */
+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)
+{
+ struct virtpci_dev *virtpcidev = NULL;
+ struct virtpci_dev *tmpvpcidev = NULL, *prev;
+ unsigned long flags;
+ int ret;
+ struct spar_io_channel_protocol __iomem *io_chan = NULL;
+ struct device *dev;
+
+ POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
+
+ if ((devtype != VIRTHBA_TYPE) && (devtype != 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) {
+ 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... */
+ io_chan = (struct spar_io_channel_protocol __iomem *)
+ virtpcidev->queueinfo.chan;
+
+ virtpcidev->intr = addparams->intr;
+
+ /* initialize stuff in the device portion of the struct */
+ virtpcidev->generic_dev.bus = &virtpci_bus_type;
+ virtpcidev->generic_dev.parent = parentbus;
+ virtpcidev->generic_dev.release = virtpci_device_release;
+
+ dev_set_name(&virtpcidev->generic_dev, "%x:%x",
+ addparams->bus_no, addparams->device_no);
+
+ /* add the vhba/vnic to virtpci device list - but check for
+ * duplicate wwnn/macaddr first
+ */
+ write_lock_irqsave(&vpcidev_list_lock, flags);
+ for (tmpvpcidev = vpcidev_list_head; tmpvpcidev;
+ tmpvpcidev = tmpvpcidev->next) {
+ if (devtype == VIRTHBA_TYPE) {
+ if ((tmpvpcidev->scsi.wwnn.wwnn1 == scsi->wwnn.wwnn1) &&
+ (tmpvpcidev->scsi.wwnn.wwnn2 == scsi->wwnn.wwnn2)) {
+ /* duplicate - already have vpcidev
+ with this wwnn */
+ break;
+ }
+ } else
+ if (memcmp
+ (tmpvpcidev->net.mac_addr, net->mac_addr,
+ MAX_MACADDR_LEN) == 0) {
+ /* duplicate - already have vnic with this wwnn */
+ break;
+ }
+ }
+ if (tmpvpcidev) {
+ /* found a vhba/vnic already in the list with same
+ * wwnn or macaddr - reject add
+ */
+ write_unlock_irqrestore(&vpcidev_list_lock, flags);
+ kfree(virtpcidev);
+ POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
+ return 0;
+ }
+
+ /* add it at the head */
+ if (!vpcidev_list_head) {
+ vpcidev_list_head = virtpcidev;
+ } else {
+ /* insert virtpcidev at the head of our linked list of
+ * vpcidevs
+ */
+ virtpcidev->next = vpcidev_list_head;
+ vpcidev_list_head = virtpcidev;
+ }
+
+ write_unlock_irqrestore(&vpcidev_list_lock, flags);
+
+ /* Must transition channel to ATTACHED state BEFORE
+ * registering the device, because polling of the channel
+ * queues can begin at any time after device_register().
+ */
+ dev = &virtpcidev->generic_dev;
+ SPAR_CHANNEL_CLIENT_TRANSITION(addparams->chanptr,
+ BUS_ID(dev),
+ CHANNELCLI_ATTACHED, NULL);
+
+ /* don't register until device has been added to
+ * list. Otherwise, a device_unregister from this function can
+ * cause a "scheduling while atomic".
+ */
+ ret = device_register(&virtpcidev->generic_dev);
+ /* NOTE: THIS IS CALLING HOTPLUG virtpci_hotplug!!!
+ * This call to device_register results in virtpci_bus_match
+ * being called !!!!! And, if match returns success, then
+ * virtpcidev->generic_dev.driver is setup to core_driver,
+ * i.e., virtpci and the probe function
+ * virtpcidev->generic_dev.driver->probe is called which
+ * results in virtpci_device_probe being called. And if
+ * virtpci_device_probe is successful
+ */
+ if (ret) {
+ dev = &virtpcidev->generic_dev;
+ SPAR_CHANNEL_CLIENT_TRANSITION(addparams->chanptr,
+ BUS_ID(dev),
+ CHANNELCLI_DETACHED, NULL);
+ /* remove virtpcidev, the one we just added, from the list */
+ write_lock_irqsave(&vpcidev_list_lock, flags);
+ for (tmpvpcidev = vpcidev_list_head, prev = NULL;
+ tmpvpcidev;
+ prev = tmpvpcidev, tmpvpcidev = tmpvpcidev->next) {
+ if (tmpvpcidev == virtpcidev) {
+ if (prev)
+ prev->next = tmpvpcidev->next;
+ else
+ vpcidev_list_head = tmpvpcidev->next;
+ break;
+ }
+ }
+ write_unlock_irqrestore(&vpcidev_list_lock, flags);
+ kfree(virtpcidev);
+ return 0;
+ }
+
+ POSTCODE_LINUX_2(VPCI_CREATE_EXIT_PC, POSTCODE_SEVERITY_INFO);
+ return 1;
+}
+
+static int virtpci_device_serverdown(struct device *parentbus,
+ int devtype,
+ struct vhba_wwnn *wwnn,
+ unsigned char macaddr[])
+{
+ int pausethisone = 0;
+ bool found = false;
+ struct virtpci_dev *tmpvpcidev, *prevvpcidev;
+ struct virtpci_driver *vpcidriver;
+ unsigned long flags;
+ int rc = 0;
+
+ if ((devtype != VIRTHBA_TYPE) && (devtype != VIRTNIC_TYPE))
+ return 0;
+
+ /* find the vhba or vnic in virtpci device list */
+ write_lock_irqsave(&vpcidev_list_lock, flags);
+
+ for (tmpvpcidev = vpcidev_list_head, prevvpcidev = NULL;
+ (tmpvpcidev && !found);
+ prevvpcidev = tmpvpcidev, tmpvpcidev = tmpvpcidev->next) {
+ if (tmpvpcidev->devtype != devtype)
+ continue;
+
+ if (devtype == VIRTHBA_TYPE) {
+ pausethisone =
+ ((tmpvpcidev->scsi.wwnn.wwnn1 == wwnn->wwnn1) &&
+ (tmpvpcidev->scsi.wwnn.wwnn2 == wwnn->wwnn2));
+ /* devtype is vhba, we're pausing vhba whose
+ * wwnn matches the current device's wwnn
+ */
+ } else { /* VIRTNIC_TYPE */
+ pausethisone =
+ memcmp(tmpvpcidev->net.mac_addr, macaddr,
+ MAX_MACADDR_LEN) == 0;
+ /* devtype is vnic, we're pausing vnic whose
+ * macaddr matches the current device's macaddr */
+ }
+
+ if (!pausethisone)
+ continue;
+
+ found = true;
+ vpcidriver = tmpvpcidev->mydriver;
+ rc = vpcidriver->suspend(tmpvpcidev, 0);
+ }
+ write_unlock_irqrestore(&vpcidev_list_lock, flags);
+
+ if (!found)
+ return 0;
+
+ return rc;
+}
+
+static int virtpci_device_serverup(struct device *parentbus,
+ int devtype,
+ struct vhba_wwnn *wwnn,
+ unsigned char macaddr[])
+{
+ int resumethisone = 0;
+ bool found = false;
+ struct virtpci_dev *tmpvpcidev, *prevvpcidev;
+ struct virtpci_driver *vpcidriver;
+ unsigned long flags;
+ int rc = 0;
+
+ if ((devtype != VIRTHBA_TYPE) && (devtype != VIRTNIC_TYPE))
+ return 0;
+
+
+ /* find the vhba or vnic in virtpci device list */
+ write_lock_irqsave(&vpcidev_list_lock, flags);
+
+ for (tmpvpcidev = vpcidev_list_head, prevvpcidev = NULL;
+ (tmpvpcidev && !found);
+ prevvpcidev = tmpvpcidev, tmpvpcidev = tmpvpcidev->next) {
+ if (tmpvpcidev->devtype != devtype)
+ continue;
+
+ if (devtype == VIRTHBA_TYPE) {
+ resumethisone =
+ ((tmpvpcidev->scsi.wwnn.wwnn1 == wwnn->wwnn1) &&
+ (tmpvpcidev->scsi.wwnn.wwnn2 == wwnn->wwnn2));
+ /* devtype is vhba, we're resuming vhba whose
+ * wwnn matches the current device's wwnn */
+ } else { /* VIRTNIC_TYPE */
+ resumethisone =
+ memcmp(tmpvpcidev->net.mac_addr, macaddr,
+ MAX_MACADDR_LEN) == 0;
+ /* devtype is vnic, we're resuming vnic whose
+ * macaddr matches the current device's macaddr */
+ }
+
+ if (!resumethisone)
+ continue;
+
+ found = true;
+ vpcidriver = tmpvpcidev->mydriver;
+ /* This should be done at BUS resume time, but an
+ * existing problem prevents us from ever getting a bus
+ * resume... This hack would fail to work should we
+ * ever have a bus that contains NO devices, since we
+ * would never even get here in that case.
+ */
+ fix_vbus_dev_info(&tmpvpcidev->generic_dev,
+ tmpvpcidev->device_no,
+ tmpvpcidev->device, vpcidriver);
+ rc = vpcidriver->resume(tmpvpcidev);
+ }
+
+ write_unlock_irqrestore(&vpcidev_list_lock, flags);
+
+ if (!found)
+ return 0;
+
+ return rc;
+}
+
+static int virtpci_device_del(struct device *parentbus,
+ int devtype, struct vhba_wwnn *wwnn,
+ unsigned char macaddr[])
+{
+ int count = 0, all = 0, delthisone;
+ struct virtpci_dev *tmpvpcidev, *prevvpcidev, *dellist = NULL;
+ unsigned long flags;
+
+#define DEL_CONTINUE { \
+ prevvpcidev = tmpvpcidev;\
+ tmpvpcidev = tmpvpcidev->next;\
+ continue; \
+}
+
+ if ((devtype != VIRTHBA_TYPE) && (devtype != VIRTNIC_TYPE))
+ return 0;
+
+ /* see if we are to delete all - NOTE: all implies we have a
+ * valid parentbus
+ */
+ all = ((devtype == VIRTHBA_TYPE) && (!wwnn)) ||
+ ((devtype == VIRTNIC_TYPE) && (!macaddr));
+
+ /* find all the vhba or vnic or both in virtpci device list
+ * keep list of ones we are deleting so we can call
+ * device_unregister after we release the lock; otherwise we
+ * encounter "schedule while atomic"
+ */
+ write_lock_irqsave(&vpcidev_list_lock, flags);
+ for (tmpvpcidev = vpcidev_list_head, prevvpcidev = NULL; tmpvpcidev;) {
+ if (tmpvpcidev->devtype != devtype)
+ DEL_CONTINUE;
+
+ if (all) {
+ delthisone =
+ (tmpvpcidev->generic_dev.parent == parentbus);
+ /* we're deleting all vhbas or vnics on the
+ * specified parent bus
+ */
+ } else if (devtype == VIRTHBA_TYPE) {
+ delthisone =
+ ((tmpvpcidev->scsi.wwnn.wwnn1 == wwnn->wwnn1) &&
+ (tmpvpcidev->scsi.wwnn.wwnn2 == wwnn->wwnn2));
+ /* devtype is vhba, we're deleting vhba whose
+ * wwnn matches the current device's wwnn
+ */
+ } else { /* VIRTNIC_TYPE */
+ delthisone =
+ memcmp(tmpvpcidev->net.mac_addr, macaddr,
+ MAX_MACADDR_LEN) == 0;
+ /* devtype is vnic, we're deleting vnic whose
+ * macaddr matches the current device's macaddr
+ */
+ }
+
+ if (!delthisone)
+ DEL_CONTINUE;
+
+ /* take vhba/vnic out of the list */
+ if (prevvpcidev)
+ /* not at head */
+ prevvpcidev->next = tmpvpcidev->next;
+ else
+ vpcidev_list_head = tmpvpcidev->next;
+
+ /* add it to our deletelist */
+ tmpvpcidev->next = dellist;
+ dellist = tmpvpcidev;
+
+ count++;
+ if (!all)
+ break; /* done */
+ /* going to top of loop again - set tmpvpcidev to next
+ * one we're to process
+ */
+ if (prevvpcidev)
+ tmpvpcidev = prevvpcidev->next;
+ else
+ tmpvpcidev = vpcidev_list_head;
+ }
+ write_unlock_irqrestore(&vpcidev_list_lock, flags);
+
+ if (!all && (count == 0))
+ return 0;
+
+ /* now delete each one from delete list */
+ while (dellist) {
+ /* save next */
+ tmpvpcidev = dellist->next;
+ /* delete the vhba/vnic at dellist */
+ DELETE_ONE_VPCIDEV(dellist);
+ /* do next */
+ dellist = tmpvpcidev;
+ }
+
+ return count;
+}
+
+static void virtpci_device_release(struct device *dev_)
+{
+ /* this function is called when the last reference to the
+ * device is removed
+ */
+}
+
+/*****************************************************/
+/* Driver functions */
+/*****************************************************/
+
+#define kobj_to_device_driver(obj) container_of(obj, struct device_driver, kobj)
+#define attribute_to_driver_attribute(obj) \
+ container_of(obj, struct driver_attribute, attr)
+
+static ssize_t virtpci_driver_attr_show(struct kobject *kobj,
+ struct attribute *attr,
+ char *buf)
+{
+ struct driver_attribute *dattr = attribute_to_driver_attribute(attr);
+ ssize_t ret = 0;
+
+ struct driver_private *dprivate = to_driver(kobj);
+ struct device_driver *driver = dprivate->driver;
+
+ if (dattr->show)
+ ret = dattr->show(driver, buf);
+
+ return ret;
+}
+
+static ssize_t virtpci_driver_attr_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buf, size_t count)
+{
+ struct driver_attribute *dattr = attribute_to_driver_attribute(attr);
+ ssize_t ret = 0;
+
+ struct driver_private *dprivate = to_driver(kobj);
+ struct device_driver *driver = dprivate->driver;
+
+ if (dattr->store)
+ ret = dattr->store(driver, buf, count);
+
+ return ret;
+}
+
+/* register a new virtpci driver */
+int virtpci_register_driver(struct virtpci_driver *drv)
+{
+ int result = 0;
+
+ if (!drv->id_table)
+ return 1;
+ /* initialize core driver fields needed to call driver_register */
+ drv->core_driver.name = drv->name; /* name of driver in sysfs */
+ drv->core_driver.bus = &virtpci_bus_type; /* type of bus this
+ * driver works with */
+ drv->core_driver.probe = virtpci_device_probe; /* called to query the
+ * existence of a
+ * specific device and
+ * whether this driver
+ *can work with it */
+ drv->core_driver.remove = virtpci_device_remove; /* called when the
+ * device is removed
+ * from the system */
+ /* register with core */
+ result = driver_register(&drv->core_driver);
+ /* calls bus_add_driver which calls driver_attach and
+ * module_add_driver
+ */
+ if (result)
+ return result; /* failed */
+
+ drv->core_driver.p->kobj.ktype = &virtpci_driver_kobj_type;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(virtpci_register_driver);
+
+void virtpci_unregister_driver(struct virtpci_driver *drv)
+{
+ driver_unregister(&drv->core_driver);
+ /* driver_unregister calls bus_remove_driver
+ * bus_remove_driver calls device_detach
+ * device_detach calls device_release_driver for each of the
+ * driver's devices
+ * device_release driver calls drv->remove which is
+ * virtpci_device_remove
+ * virtpci_device_remove calls virthba_remove
+ */
+}
+EXPORT_SYMBOL_GPL(virtpci_unregister_driver);
+
+/*****************************************************/
+/* debugfs filesystem functions */
+/*****************************************************/
+struct print_vbus_info {
+ int *str_pos;
+ char *buf;
+ size_t *len;
+};
+
+static int print_vbus(struct device *vbus, void *data)
+{
+ struct print_vbus_info *p = (struct print_vbus_info *)data;
+
+ *p->str_pos += scnprintf(p->buf + *p->str_pos, *p->len - *p->str_pos,
+ "bus_id:%s\n", dev_name(vbus));
+ return 0;
+}
+
+static ssize_t info_debugfs_read(struct file *file, char __user *buf,
+ size_t len, loff_t *offset)
+{
+ ssize_t bytes_read = 0;
+ int str_pos = 0;
+ struct virtpci_dev *tmpvpcidev;
+ unsigned long flags;
+ struct print_vbus_info printparam;
+ char *vbuf;
+
+ if (len > MAX_BUF)
+ len = MAX_BUF;
+ vbuf = kzalloc(len, GFP_KERNEL);
+ if (!vbuf)
+ return -ENOMEM;
+
+ str_pos += scnprintf(vbuf + str_pos, len - str_pos,
+ " Virtual PCI Bus devices\n");
+ printparam.str_pos = &str_pos;
+ printparam.buf = vbuf;
+ printparam.len = &len;
+ bus_for_each_dev(&virtpci_bus_type, NULL, (void *)&printparam,
+ print_vbus);
+
+ str_pos += scnprintf(vbuf + str_pos, len - str_pos,
+ "\n Virtual PCI devices\n");
+ read_lock_irqsave(&vpcidev_list_lock, flags);
+ tmpvpcidev = vpcidev_list_head;
+ while (tmpvpcidev) {
+ if (tmpvpcidev->devtype == VIRTHBA_TYPE) {
+ str_pos += scnprintf(vbuf + str_pos, len - str_pos,
+ "[%d:%d] VHba:%08x:%08x max-config:%d-%d-%d-%d",
+ tmpvpcidev->bus_no,
+ tmpvpcidev->device_no,
+ tmpvpcidev->scsi.wwnn.wwnn1,
+ tmpvpcidev->scsi.wwnn.wwnn2,
+ tmpvpcidev->scsi.max.max_channel,
+ tmpvpcidev->scsi.max.max_id,
+ tmpvpcidev->scsi.max.max_lun,
+ tmpvpcidev->scsi.max.cmd_per_lun);
+ } else {
+ str_pos += scnprintf(vbuf + str_pos, len - str_pos,
+ "[%d:%d] VNic:%pM num_rcv_bufs:%d mtu:%d",
+ tmpvpcidev->bus_no,
+ tmpvpcidev->device_no,
+ tmpvpcidev->net.mac_addr,
+ tmpvpcidev->net.num_rcv_bufs,
+ tmpvpcidev->net.mtu);
+ }
+ str_pos += scnprintf(vbuf + str_pos,
+ len - str_pos, " chanptr:%p\n",
+ tmpvpcidev->queueinfo.chan);
+ tmpvpcidev = tmpvpcidev->next;
+ }
+ read_unlock_irqrestore(&vpcidev_list_lock, flags);
+
+ str_pos += scnprintf(vbuf + str_pos, len - str_pos, "\n");
+ bytes_read = simple_read_from_buffer(buf, len, offset, vbuf, str_pos);
+ kfree(vbuf);
+ return bytes_read;
+}
+
+/*****************************************************/
+/* Module Init & Exit functions */
+/*****************************************************/
+
+static int __init virtpci_mod_init(void)
+{
+ int ret;
+
+ if (!unisys_spar_platform)
+ return -ENODEV;
+
+ POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
+
+ ret = bus_register(&virtpci_bus_type);
+ /* creates /sys/bus/uisvirtpci which contains devices &
+ * drivers directory
+ */
+ if (ret) {
+ POSTCODE_LINUX_3(VPCI_CREATE_FAILURE_PC, ret,
+ POSTCODE_SEVERITY_ERR);
+ return ret;
+ }
+ bus_device_info_init(&bus_driver_info, "clientbus", "virtpci",
+ VERSION, NULL);
+
+ /* create a root bus used to parent all the virtpci buses. */
+ ret = device_register(&virtpci_rootbus_device);
+ if (ret) {
+ bus_unregister(&virtpci_bus_type);
+ POSTCODE_LINUX_3(VPCI_CREATE_FAILURE_PC, ret,
+ POSTCODE_SEVERITY_ERR);
+ return ret;
+ }
+
+ if (!uisctrl_register_req_handler(2, (void *)&virtpci_ctrlchan_func,
+ &chipset_driver_info)) {
+ POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
+ device_unregister(&virtpci_rootbus_device);
+ bus_unregister(&virtpci_bus_type);
+ return -1;
+ }
+
+ /* create debugfs directory and info file inside. */
+ virtpci_debugfs_dir = debugfs_create_dir("virtpci", NULL);
+ debugfs_create_file("info", S_IRUSR, virtpci_debugfs_dir,
+ NULL, &debugfs_info_fops);
+ POSTCODE_LINUX_2(VPCI_CREATE_EXIT_PC, POSTCODE_SEVERITY_INFO);
+ return 0;
+}
+
+static void __exit virtpci_mod_exit(void)
+{
+ /* unregister the callback function */
+ device_unregister(&virtpci_rootbus_device);
+ bus_unregister(&virtpci_bus_type);
+ debugfs_remove_recursive(virtpci_debugfs_dir);
+}
+
+module_init(virtpci_mod_init);
+module_exit(virtpci_mod_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Usha Srinivasan");
+MODULE_ALIAS("uisvirtpci");
+