From 57f0f512b273f60d52568b8c6b77e17f5636edc0 Mon Sep 17 00:00:00 2001 From: André Fabian Silva Delgado Date: Wed, 5 Aug 2015 17:04:01 -0300 Subject: Initial import --- drivers/staging/unisys/virtpci/Kconfig | 10 + drivers/staging/unisys/virtpci/Makefile | 10 + drivers/staging/unisys/virtpci/virtpci.c | 1394 ++++++++++++++++++++++++++++++ drivers/staging/unisys/virtpci/virtpci.h | 103 +++ 4 files changed, 1517 insertions(+) create mode 100644 drivers/staging/unisys/virtpci/Kconfig create mode 100644 drivers/staging/unisys/virtpci/Makefile create mode 100644 drivers/staging/unisys/virtpci/virtpci.c create mode 100644 drivers/staging/unisys/virtpci/virtpci.h (limited to 'drivers/staging/unisys/virtpci') diff --git a/drivers/staging/unisys/virtpci/Kconfig b/drivers/staging/unisys/virtpci/Kconfig new file mode 100644 index 000000000..6d19482ce --- /dev/null +++ b/drivers/staging/unisys/virtpci/Kconfig @@ -0,0 +1,10 @@ +# +# Unisys virtpci configuration +# + +config UNISYS_VIRTPCI + tristate "Unisys virtpci driver" + select UNISYS_UISLIB + ---help--- + If you say Y here, you will enable the Unisys virtpci driver. + diff --git a/drivers/staging/unisys/virtpci/Makefile b/drivers/staging/unisys/virtpci/Makefile new file mode 100644 index 000000000..a26c69621 --- /dev/null +++ b/drivers/staging/unisys/virtpci/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for Unisys virtpci +# + +obj-$(CONFIG_UNISYS_VIRTPCI) += virtpci.o + +ccflags-y += -Idrivers/staging/unisys/include +ccflags-y += -Idrivers/staging/unisys/uislib +ccflags-y += -Idrivers/staging/unisys/common-spar/include +ccflags-y += -Idrivers/staging/unisys/common-spar/include/channels 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 +#ifdef CONFIG_MODVERSIONS +#include +#endif +#include "diagnostics/appos_subsystems.h" +#include "uisutils.h" +#include "vbuschannel.h" +#include "vbushelper.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 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 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 to the + * ULTRA_VBUS_CHANNEL_PROTOCOL.DevInfo[]. + */ +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"); + diff --git a/drivers/staging/unisys/virtpci/virtpci.h b/drivers/staging/unisys/virtpci/virtpci.h new file mode 100644 index 000000000..9d85f55e8 --- /dev/null +++ b/drivers/staging/unisys/virtpci/virtpci.h @@ -0,0 +1,103 @@ +/* virtpci.h + * + * 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. + */ + +/* + * Unisys Virtual PCI driver header + */ + +#ifndef __VIRTPCI_H__ +#define __VIRTPCI_H__ + +#include "uisqueue.h" +#include +#include + +#define PCI_DEVICE_ID_VIRTHBA 0xAA00 +#define PCI_DEVICE_ID_VIRTNIC 0xAB00 + +struct scsi_adap_info { + void *scsihost; /* scsi host if this device is a scsi hba */ + struct vhba_wwnn wwnn; /* the world wide node name of vhba */ + struct vhba_config_max max; /* various max specifications used + * to config vhba */ +}; + +struct net_adap_info { + struct net_device *netdev; /* network device if this + * device is a NIC */ + u8 mac_addr[MAX_MACADDR_LEN]; + int num_rcv_bufs; + unsigned mtu; + uuid_le zone_uuid; +}; + +enum virtpci_dev_type { + VIRTHBA_TYPE = 0, + VIRTNIC_TYPE = 1, + VIRTBUS_TYPE = 6, +}; + +struct virtpci_dev { + enum virtpci_dev_type devtype; /* indicates type of the + * virtual pci device */ + struct virtpci_driver *mydriver; /* which driver has allocated + * this device */ + unsigned short vendor; /* vendor id for device */ + unsigned short device; /* device id for device */ + u32 bus_no; /* number of bus on which device exists */ + u32 device_no; /* device's number on the bus */ + struct irq_info intr; /* interrupt info */ + struct device generic_dev; /* generic device */ + union { + struct scsi_adap_info scsi; + struct net_adap_info net; + }; + + struct uisqueue_info queueinfo; /* holds ptr to channel where cmds & + * rsps are queued & retrieved */ + struct virtpci_dev *next; /* points to next virtpci device */ +}; + +struct virtpci_driver { + struct list_head node; + const char *name; /* the name of the driver in sysfs */ + const char *version; + const char *vertag; + const struct pci_device_id *id_table; /* must be non-NULL for probe + * to be called */ + int (*probe)(struct virtpci_dev *dev, + const struct pci_device_id *id); /* device inserted */ + void (*remove)(struct virtpci_dev *dev); /* Device removed (NULL if + * not a hot-plug capable + * driver) */ + int (*suspend)(struct virtpci_dev *dev, + u32 state); /* Device suspended */ + int (*resume)(struct virtpci_dev *dev); /* Device woken up */ + int (*enable_wake)(struct virtpci_dev *dev, + u32 state, int enable); /* Enable wake event */ + struct device_driver core_driver; /* VIRTPCI core fills this in */ +}; + +#define driver_to_virtpci_driver(in_drv) \ + container_of(in_drv, struct virtpci_driver, core_driver) +#define device_to_virtpci_dev(in_dev) \ + container_of(in_dev, struct virtpci_dev, generic_dev) + +int virtpci_register_driver(struct virtpci_driver *); +void virtpci_unregister_driver(struct virtpci_driver *); + +#endif /* __VIRTPCI_H__ */ -- cgit v1.2.3-54-g00ecf