From: Ken Cox Date: Tue, 4 Mar 2014 13:58:10 +0000 (-0600) Subject: staging: virtpci driver X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=af86526b22dd52edc6a7827217315415801d1622;p=linux-beck.git staging: virtpci driver The virtpci module handles the bus functions for virthba, and virtnic. Signed-off-by: Ken Cox Cc: Ben Romer Signed-off-by: Greg Kroah-Hartman --- diff --git a/drivers/staging/unisys/Kconfig b/drivers/staging/unisys/Kconfig index 0b1e0c52efbb..52e3725c1232 100644 --- a/drivers/staging/unisys/Kconfig +++ b/drivers/staging/unisys/Kconfig @@ -14,5 +14,6 @@ source "drivers/staging/unisys/visorchannel/Kconfig" source "drivers/staging/unisys/visorchipset/Kconfig" source "drivers/staging/unisys/channels/Kconfig" source "drivers/staging/unisys/uislib/Kconfig" +source "drivers/staging/unisys/virtpci/Kconfig" endif # UNISYSSPAR diff --git a/drivers/staging/unisys/Makefile b/drivers/staging/unisys/Makefile index f9174f580ac3..56ee16c1deeb 100644 --- a/drivers/staging/unisys/Makefile +++ b/drivers/staging/unisys/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_UNISYS_VISORCHANNEL) += visorchannel/ obj-$(CONFIG_UNISYS_VISORCHIPSET) += visorchipset/ obj-$(CONFIG_UNISYS_CHANNELSTUB) += channels/ obj-$(CONFIG_UNISYS_UISLIB) += uislib/ +obj-$(CONFIG_UNISYS_VIRTPCI) += virtpci/ diff --git a/drivers/staging/unisys/virtpci/Kconfig b/drivers/staging/unisys/virtpci/Kconfig new file mode 100644 index 000000000000..e59efcbc4d3b --- /dev/null +++ b/drivers/staging/unisys/virtpci/Kconfig @@ -0,0 +1,10 @@ +# +# Unisys virtpci configuration +# + +config UNISYS_VIRTPCI + tristate "Unisys virtpci driver" + depends on UNISYSSPAR && 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 000000000000..f9399aabddd1 --- /dev/null +++ b/drivers/staging/unisys/virtpci/Makefile @@ -0,0 +1,13 @@ +# +# 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 + +ccflags-y += -DCONFIG_SPAR_GUEST -DGUESTDRIVERBUILD -DNOAUTOVERSION + diff --git a/drivers/staging/unisys/virtpci/virtpci.c b/drivers/staging/unisys/virtpci/virtpci.c new file mode 100644 index 000000000000..5700e4b36860 --- /dev/null +++ b/drivers/staging/unisys/virtpci/virtpci.c @@ -0,0 +1,1755 @@ +/* virtpci.c + * + * Copyright © 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 "uniklog.h" +#include "diagnostics/appos_subsystems.h" +#include "uisutils.h" +#include "commontypes.h" +#include "vbuschannel.h" +#include "vbushelper.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "version.h" +#include "guestlinuxdebug.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) + +#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 virt_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos); +static ssize_t info_proc_read(struct file *file, char __user *buf, + size_t len, loff_t *offset); + +static const struct file_operations proc_virt_fops = { + .write = virt_proc_write, +}; + +static const struct file_operations proc_info_fops = { + .read = info_proc_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 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 ULTRA_VBUS_DEVICEINFO Bus_DriverInfo; + +/* virtpci_proc_dir_entry is used to create the proc entry directory + * for virtpci + */ +static struct proc_dir_entry *virtpci_proc_dir; +/* virt_proc_entry is used to tell virtpci to add/delete vhbas/vnics/vbuses */ +static struct proc_dir_entry *virt_proc_entry; +/* info_proc_entry is used to tell virtpci to display current info + * kept in the driver + */ +static struct proc_dir_entry *info_proc_entry; +#define VIRT_PROC_ENTRY_FN "virt" +#define INFO_PROC_ENTRY_FN "info" +#define DIR_PROC_ENTRY "virtpci" + +struct virtpci_busdev { + struct device virtpci_bus_device; +}; + +/*****************************************************/ +/* Local functions */ +/*****************************************************/ + +static inline int WAIT_FOR_IO_CHANNEL(ULTRA_IO_CHANNEL_PROTOCOL *chanptr) +{ + int count = 120; + while (count > 0) { + + if (ULTRA_CHANNEL_SERVER_READY(&chanptr->ChannelHeader)) + 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_chpInfo(ULTRA_VBUS_CHANNEL_PROTOCOL *chan, + ULTRA_VBUS_DEVICEINFO *info) +{ + int off; + if (!chan) { + LOGERR("vbus channel not present"); + return -1; + } + off = sizeof(ULTRA_CHANNEL_PROTOCOL) + chan->HdrInfo.chpInfoByteOffset; + if (chan->HdrInfo.chpInfoByteOffset == 0) { + LOGERR("vbus channel not used, because chpInfoByteOffset == 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_busInfo(ULTRA_VBUS_CHANNEL_PROTOCOL *chan, + ULTRA_VBUS_DEVICEINFO *info) +{ + int off; + if (!chan) { + LOGERR("vbus channel not present"); + return -1; + } + off = sizeof(ULTRA_CHANNEL_PROTOCOL) + chan->HdrInfo.busInfoByteOffset; + if (chan->HdrInfo.busInfoByteOffset == 0) { + LOGERR("vbus channel not used, because busInfoByteOffset == 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_devInfo(ULTRA_VBUS_CHANNEL_PROTOCOL *chan, + ULTRA_VBUS_DEVICEINFO *info, int devix) +{ + int off; + if (!chan) { + LOGERR("vbus channel not present"); + return -1; + } + off = + (sizeof(ULTRA_CHANNEL_PROTOCOL) + + chan->HdrInfo.devInfoByteOffset) + + (chan->HdrInfo.deviceInfoStructBytes * devix); + if (chan->HdrInfo.devInfoByteOffset == 0) { + LOGERR("vbus channel not used, because devInfoByteOffset == 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 = kmalloc(sizeof(struct device), GFP_ATOMIC); + + POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO); + if (!vbus) + return 0; + + memset(vbus, 0, sizeof(struct device)); + dev_set_name(vbus, "vbus%d", addparams->busNo); + vbus->release = virtpci_bus_release; + vbus->parent = &virtpci_rootbus_device; /* root bus is parent */ + vbus->bus = &virtpci_bus_type; /* bus type */ + vbus->platform_data = 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->busNo, 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) { \ + scsi.wwnn = ((ULTRA_IO_CHANNEL_PROTOCOL *) chanptr)->vhba.wwnn; \ + scsi.max = ((ULTRA_IO_CHANNEL_PROTOCOL *) chanptr)->vhba.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 + ((ULTRA_IO_CHANNEL_PROTOCOL *) 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->busNo); + + 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(net.mac_addr, ((ULTRA_IO_CHANNEL_PROTOCOL *) chanptr)->vnic.macaddr, MAX_MACADDR_LEN); \ + net.num_rcv_bufs = ((ULTRA_IO_CHANNEL_PROTOCOL *) chanptr)->vnic.num_rcv_bufs; \ + net.mtu = ((ULTRA_IO_CHANNEL_PROTOCOL *) chanptr)->vnic.mtu; \ + net.zoneGuid = ((ULTRA_IO_CHANNEL_PROTOCOL *) chanptr)->vnic.zoneGuid; \ +} + +/* 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 + ((ULTRA_IO_CHANNEL_PROTOCOL *) 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->busNo); + + LOGINF("Adding vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x rcvbufs:%d mtu:%d chanptr:%p{%-8.8lx-%-4.4x-%-4.4x-%-2.2x%-2.2x%-2.2x%-2.2x%-2.2x%-2.2x%-2.2x%-2.2x}\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, (ulong) net.zoneGuid.data1, net.zoneGuid.data2, + net.zoneGuid.data3, net.zoneGuid.data4[0], net.zoneGuid.data4[1], + net.zoneGuid.data4[2], net.zoneGuid.data4[3], + net.zoneGuid.data4[4], net.zoneGuid.data4[5], + net.zoneGuid.data4[6], net.zoneGuid.data4[7]); + 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->busNo); + /* 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->busNo); + 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(VIRTPCI_DEV_TYPE devtype, struct del_vbus_guestpart *delparams) +{ + int i; + unsigned char busid[BUS_ID_SIZE]; + struct device *vbus; + + GET_BUS_DEV(delparams->busNo); + + 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 */ +/*****************************************************/ + +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 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; + 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; + } + BusDeviceInfo_Init(&devInfo, stype, + virtpcidrv->name, + virtpcidrv->version, + virtpcidrv->vertag, + virtpcidrv->build_date, virtpcidrv->build_time); + 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->deviceNo, + 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; + ULTRA_IO_CHANNEL_PROTOCOL *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 = kmalloc(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; + } + + memset(virtpcidev, 0, sizeof(struct virtpci_dev)); + + /* 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->busNo = addparams->busNo; + virtpcidev->deviceNo = addparams->deviceNo; + + virtpcidev->queueinfo.chan = addparams->chanptr; + virtpcidev->queueinfo.send_int_if_needed = NULL; + + /* Set up safe queue... */ + pIoChan = (ULTRA_IO_CHANNEL_PROTOCOL *) 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->busNo, addparams->deviceNo); + + /* add the vhba/vnic to virtpci device list - but check for + * duplicate wwnn/macaddr first + */ + write_lock_irqsave(&VpcidevListLock, flags); + for (tmpvpcidev = VpcidevListHead; 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(&VpcidevListLock, flags); + kfree(virtpcidev); + LOGERR("**** FAILED vhba/vnic already exists in the list\n"); + POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR); + return 0; + } + + /* add it at the head */ + if (!VpcidevListHead) + VpcidevListHead = virtpcidev; + else { + /* insert virtpcidev at the head of our linked list of + * vpcidevs + */ + virtpcidev->next = VpcidevListHead; + VpcidevListHead = virtpcidev; + } + + write_unlock_irqrestore(&VpcidevListLock, 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(). + */ + pDev = &virtpcidev->generic_dev; + ULTRA_CHANNEL_CLIENT_TRANSITION(addparams->chanptr, + BUS_ID(pDev), + CliStateOS, 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". + */ + DBGINF("registering device:%p with bus_id:%s\n", + &virtpcidev->generic_dev, virtpcidev->generic_dev.bus_id); + 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) { + LOGERR("device_register returned %d\n", ret); + pDev = &virtpcidev->generic_dev; + ULTRA_CHANNEL_CLIENT_TRANSITION(addparams->chanptr, + BUS_ID(pDev), + CliStateOS, + CHANNELCLI_DETACHED, NULL); + /* remove virtpcidev, the one we just added, from the list */ + write_lock_irqsave(&VpcidevListLock, flags); + for (tmpvpcidev = VpcidevListHead, prev = NULL; + tmpvpcidev; + prev = tmpvpcidev, tmpvpcidev = tmpvpcidev->next) { + if (tmpvpcidev == virtpcidev) { + if (prev) + prev->next = tmpvpcidev->next; + else + VpcidevListHead = tmpvpcidev->next; + break; + } + } + write_unlock_irqrestore(&VpcidevListLock, flags); + kfree(virtpcidev); + return 0; + } + + LOGINF("Added %s:%d:%d &virtpcidev->generic_dev:%p\n", + (devtype == VIRTHBA_TYPE) ? "virthba" : "virtnic", + addparams->busNo, addparams->deviceNo, &virtpcidev->generic_dev); + 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)) { + LOGERR("**** FAILED to pause device; devtype:%d not vhba:%d or vnic:%d\n", + devtype, VIRTHBA_TYPE, VIRTNIC_TYPE); + return 0; + } + + /* find the vhba or vnic in virtpci device list */ + write_lock_irqsave(&VpcidevListLock, flags); + + for (tmpvpcidev = VpcidevListHead, 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(&VpcidevListLock, flags); + + if (!found) { + LOGERR("**** FAILED to find vhba/vnic in the list\n"); + 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)) { + LOGERR("**** FAILED to resume device; devtype:%d not vhba:%d or vnic:%d\n", + devtype, VIRTHBA_TYPE, VIRTNIC_TYPE); + return 0; + } + + /* find the vhba or vnic in virtpci device list */ + write_lock_irqsave(&VpcidevListLock, flags); + + for (tmpvpcidev = VpcidevListHead, 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_devInfo(&tmpvpcidev->generic_dev, tmpvpcidev->deviceNo, + tmpvpcidev->device, vpcidriver); + rc = vpcidriver->resume(tmpvpcidev); + } + + write_unlock_irqrestore(&VpcidevListLock, flags); + + if (!found) { + LOGERR("**** FAILED to find vhba/vnic in the list\n"); + 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)) { + LOGERR("**** FAILED to delete device; devtype:%d not vhba:%d or vnic:%d\n", + devtype, VIRTHBA_TYPE, VIRTNIC_TYPE); + return 0; + } + + /* see if we are to delete all - NOTE: all implies we have a + * valid parentbus + */ + all = ((devtype == VIRTHBA_TYPE) && (wwnn == NULL)) || + ((devtype == VIRTNIC_TYPE) && (macaddr == NULL)); + + /* 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(&VpcidevListLock, flags); + for (tmpvpcidev = VpcidevListHead, 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 + VpcidevListHead = 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 = VpcidevListHead; + } + write_unlock_irqrestore(&VpcidevListLock, flags); + + if (!all && (count == 0)) { + LOGERR("**** FAILED to find vhba/vnic in the list\n"); + 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 + */ + LOGINF("In virtpci_device_release:%p - NOT YET IMPLEMENTED\n", dev_); +} + +/*****************************************************/ +/* 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; + if (dprivate != NULL) + driver = dprivate->driver; + else + driver = NULL; + + DBGINF("In virtpci_driver_attr_show driver->name:%s\n", driver->name); + if (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; + if (dprivate != NULL) + driver = dprivate->driver; + else + driver = NULL; + + DBGINF("In virtpci_driver_attr_store driver->name:%s\n", driver->name); + + if (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; + + DBGINF("In virtpci_register_driver\n"); + + if (drv->id_table == NULL) { + LOGERR("id_table missing\n"); + 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) +{ + DBGINF("In virtpci_unregister_driver drv:%p\n", 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 + */ + DBGINF("Leaving\n"); +} +EXPORT_SYMBOL_GPL(virtpci_unregister_driver); + +/*****************************************************/ +/* proc filesystem functions */ +/*****************************************************/ +struct print_vbus_info { + int *length; + char *buf; +}; + +static int print_vbus(struct device *vbus, void *data) +{ + struct print_vbus_info *p = (struct print_vbus_info *) data; + int l = *(p->length); + + *(p->length) = l + sprintf(p->buf + l, "bus_id:%s\n", dev_name(vbus)); + return 0; /* no error */ +} + +static ssize_t info_proc_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + int length = 0; + struct virtpci_dev *tmpvpcidev; + unsigned long flags; + struct print_vbus_info printparam; + char *vbuf; + loff_t pos = *offset; + + if (pos < 0) + return -EINVAL; + + if (pos > 0 || !len) + return 0; + + vbuf = kzalloc(len, GFP_KERNEL); + if (!vbuf) + return -ENOMEM; + + length += sprintf(vbuf + length, "CHANSOCK is not defined.\n"); + + length += sprintf(vbuf + length, "\n Virtual PCI Bus devices\n"); + printparam.length = &length; + printparam.buf = vbuf; + if (bus_for_each_dev(&virtpci_bus_type, NULL, + (void *) &printparam, print_vbus)) + LOGERR("delete of all vbus failed\n"); + + length += sprintf(vbuf + length, "\n Virtual PCI devices\n"); + read_lock_irqsave(&VpcidevListLock, flags); + tmpvpcidev = VpcidevListHead; + while (tmpvpcidev) { + if (tmpvpcidev->devtype == VIRTHBA_TYPE) { + length += sprintf(vbuf + length, "[%d:%d] VHba:%08x:%08x max-config:%d-%d-%d-%d", + tmpvpcidev->busNo, tmpvpcidev->deviceNo, + 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 { + length += sprintf(vbuf + length, "[%d:%d] VNic:%02x:%02x:%02x:%02x:%02x:%02x num_rcv_bufs:%d mtu:%d", + tmpvpcidev->busNo, tmpvpcidev->deviceNo, + tmpvpcidev->net.mac_addr[0], + tmpvpcidev->net.mac_addr[1], + tmpvpcidev->net.mac_addr[2], + tmpvpcidev->net.mac_addr[3], + tmpvpcidev->net.mac_addr[4], + tmpvpcidev->net.mac_addr[5], + tmpvpcidev->net.num_rcv_bufs, + tmpvpcidev->net.mtu); + } + length += + sprintf(vbuf + length, " chanptr:%p\n", + tmpvpcidev->queueinfo.chan); + tmpvpcidev = tmpvpcidev->next; + } + read_unlock_irqrestore(&VpcidevListLock, flags); + + length += + sprintf(vbuf + length, "\nModule build: Date:%s Time:%s\n", __DATE__, + __TIME__); + + length += sprintf(vbuf + length, "\n"); + if (copy_to_user(buf, vbuf, length)) { + kfree(vbuf); + return -EFAULT; + } + + kfree(vbuf); + *offset += length; + return length; +} + +static ssize_t virt_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[count]; + int type, i, action = 0xffff; + unsigned int busno, deviceno; + void *chanptr; + struct add_vbus_guestpart busaddparams; + struct add_virt_guestpart addparams; + struct del_vbus_guestpart busdelparams; + struct del_virt_guestpart delparams; + GUID dummyGuid = GUID0; +#ifdef STORAGE_CHANNEL + U64 storagechannel; +#endif + +#define PRINT_USAGE_RETURN {\ + LOGERR("usage: 0-0- ==> delete vhba\n"); \ + LOGERR("usage: 0-1--- ==> add vhba\n"); \ + LOGERR("usage: 0-f- ==> delete all vhbas\n"); \ + LOGERR("\n"); \ + LOGERR("usage: 1-0- ==> delete vnic\n"); \ + LOGERR("usage: 1-1--- ==> add vnic\n"); \ + LOGERR("usage: 1-f- ==> delete all vnics\n"); \ + LOGERR("\n"); \ + LOGERR("usage: 6-0- ==> delete vbus\n"); \ + LOGERR("usage: 6-1- ==> add vbus\n"); \ + LOGERR("usage: 6-f ==> delete all vbuses\n"); \ + LOGERR("usage: 98-- ==> INJECT Client delete vnic\n"); \ + LOGERR("usage: 99--- ==> INJECT Client add vnic\n"); \ + return -EINVAL; \ +} + + if (copy_from_user(buf, buffer, count)) { + LOGERR("copy_from_user failed.\n"); + return -EFAULT; + } + + i = sscanf(buf, "%x-%x", &type, &action); + if (i < 2) + PRINT_USAGE_RETURN; + + if (type == 0x98) { + /* client inject delete vnic */ + i = sscanf(buf, "%x-%d-%d", &type, &busno, &deviceno); + if (i != 3) + PRINT_USAGE_RETURN; + uislib_client_inject_del_vnic(busno, deviceno); + return count; /* success */ + } else if (type == 0x99) { + /* client inject add vnic */ + i = sscanf(buf, "%x-%p-%d-%d", &type, &chanptr, &busno, + &deviceno); + if (i != 4) + PRINT_USAGE_RETURN; + if (!uislib_client_inject_add_vnic(busno, deviceno, + __pa(chanptr), + MIN_IO_CHANNEL_SIZE, + 1, /* test msg */ + dummyGuid, /* inst guid */ + NULL)) { /*interrupt info */ + LOGERR("FAILED to inject add vnic\n"); + return -EFAULT; + } + return count; /* success */ + } + + if ((type != VIRTHBA_TYPE) && (type != VIRTNIC_TYPE) + && (type != VIRTBUS_TYPE)) + PRINT_USAGE_RETURN; + + if (type == VIRTBUS_TYPE) { + i = sscanf(buf, "%x-%x-%d", &type, &action, &busno); + switch (action) { + case 0: + /* delete vbus */ + if (i != 3) + break; + busdelparams.busNo = busno; + if (delete_vbus(&busdelparams)) + return count; /* success */ + return -EFAULT; + + case 1: + /* add vbus */ + if (i != 3) + break; + busaddparams.chanptr = NULL; /* NOT YET USED */ + busaddparams.busNo = busno; + if (add_vbus(&busaddparams)) + return count; /* success */ + return -EFAULT; + + case 0xf: + /* delete all vbuses and all vhbas/vnics on the buses */ + if (i != 2) + break; + delete_all(); + return count; /* success */ + default: + break; + } + PRINT_USAGE_RETURN; + } + + /* if (type == VIRTNIC_TYPE) or if (type == VIRTHBA_TYPE) */ + switch (action) { + case 0: + /* delete vhba/vnic */ + i = sscanf(buf, "%x-%x-%p", &type, &action, &chanptr); + if (i != 3) + break; + delparams.chanptr = chanptr; + if (type == VIRTHBA_TYPE) { + if (delete_vhba(&delparams)) + return count; /* success */ + } else { + if (delete_vnic(&delparams)) + return count; /* success */ + } + return -EFAULT; + + case 1: + /* add vhba/vnic */ + i = sscanf(buf, "%x-%x-%p-%d-%d", &type, &action, &chanptr, + &busno, &deviceno); + if (i != 5) + break; + addparams.chanptr = chanptr; + addparams.busNo = busno; + addparams.deviceNo = deviceno; + if (type == VIRTHBA_TYPE) { + if (add_vhba(&addparams)) + return count; /* success */ + } else { + if (add_vnic(&addparams)) + return count; /* success */ + } + return -EFAULT; + +#ifdef STORAGE_CHANNEL + case 2: + /* add vhba */ + i = sscanf(buf, "%x-%x-%d-%d", &type, &action, &busno, + &deviceno); + if (i != 4) + break; + storagechannel = uislib_storage_channel(0); /* Get my storage channel */ + /* ioremap_cache it now */ + addparams.chanptr = + (void *) ioremap_cache(storagechannel, IO_CHANNEL_SIZE); + if (addparams.chanptr == NULL) { + LOGERR("Failure to get remap storage channel.\n"); + return -EFAULT; + } + addparams.busNo = busno; + addparams.deviceNo = deviceno; + if (type == VIRTHBA_TYPE) { + if (add_vhba(&addparams)) + return count; /* success */ + } + return -EFAULT; +#endif + case 0xf: + /* delete all vhbas/vnics */ + i = sscanf(buf, "%x-%x-%d", &type, &action, &busno); + if (i != 3) + break; + busdelparams.busNo = busno; + delete_all_virt(type, &busdelparams); + return count; /* success */ + default: + break; + } + PRINT_USAGE_RETURN; +} + +/*****************************************************/ +/* Module Init & Exit functions */ +/*****************************************************/ + +static int __init virtpci_mod_init(void) +{ + int ret; + + + LOGINF("Module build: Date:%s Time:%s...\n", __DATE__, __TIME__); + + 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) { + LOGERR("bus_register ****FAILED:%d\n", ret); + POSTCODE_LINUX_3(VPCI_CREATE_FAILURE_PC, ret, + POSTCODE_SEVERITY_ERR); + return ret; + } + DBGINF("bus_register successful\n"); + BusDeviceInfo_Init(&Bus_DriverInfo, + "clientbus", "virtpci", + VERSION, NULL, __DATE__, __TIME__); + + /* create a root bus used to parent all the virtpci buses. */ + ret = device_register(&virtpci_rootbus_device); + if (ret) { + LOGERR("device_register FAILED:%d\n", ret); + bus_unregister(&virtpci_bus_type); + POSTCODE_LINUX_3(VPCI_CREATE_FAILURE_PC, ret, + POSTCODE_SEVERITY_ERR); + return ret; + } + DBGINF("device_register successful ret:%x\n", ret); + + if (!uisctrl_register_req_handler(2, (void *) &virtpci_ctrlchan_func, + &Chipset_DriverInfo)) { + LOGERR("uisctrl_register_req_handler ****FAILED.\n"); + POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR); + device_unregister(&virtpci_rootbus_device); + bus_unregister(&virtpci_bus_type); + return -1; + } + + LOGINF("successfully registered virtpci_ctrlchan_func (0x%p) as callback.\n", + (void *) &virtpci_ctrlchan_func); + /* create the proc directories */ + virtpci_proc_dir = proc_mkdir(DIR_PROC_ENTRY, NULL); + virt_proc_entry = proc_create(VIRT_PROC_ENTRY_FN, 0, virtpci_proc_dir, + &proc_virt_fops); + info_proc_entry = proc_create(INFO_PROC_ENTRY_FN, 0, virtpci_proc_dir, + &proc_info_fops); + LOGINF("Leaving\n"); + POSTCODE_LINUX_2(VPCI_CREATE_EXIT_PC, POSTCODE_SEVERITY_INFO); + return 0; +} + +static void __exit virtpci_mod_exit(void) +{ + LOGINF("virtpci_mod_exit...\n"); + + /* unregister the callback function */ + if (!uisctrl_register_req_handler(2, NULL, NULL)) + LOGERR("uisctrl_register_req_handler ****FAILED.\n"); + + device_unregister(&virtpci_rootbus_device); + bus_unregister(&virtpci_bus_type); + + if (virt_proc_entry) + remove_proc_entry(VIRT_PROC_ENTRY_FN, virtpci_proc_dir); + + if (info_proc_entry) + remove_proc_entry(INFO_PROC_ENTRY_FN, virtpci_proc_dir); + + if (virtpci_proc_dir) + remove_proc_entry(DIR_PROC_ENTRY, NULL); + + LOGINF("Leaving\n"); + +} + +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 000000000000..b8fd07bc5435 --- /dev/null +++ b/drivers/staging/unisys/virtpci/virtpci.h @@ -0,0 +1,104 @@ +/* virtpci.h + * + * Copyright © 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 + +#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; + GUID zoneGuid; +}; + +typedef enum { + VIRTHBA_TYPE = 0, + VIRTNIC_TYPE = 1, + VIRTBUS_TYPE = 6, +} VIRTPCI_DEV_TYPE; + +struct virtpci_dev { + 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 busNo; /* number of bus on which device exists */ + U32 deviceNo; /* device's number on the bus */ + struct InterruptInfo 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 char *build_date; + const char *build_time; + 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__ */