diff options
Diffstat (limited to 'drivers/staging/unisys')
80 files changed, 19214 insertions, 0 deletions
diff --git a/drivers/staging/unisys/Documentation/overview.txt b/drivers/staging/unisys/Documentation/overview.txt new file mode 100644 index 00000000000..8d078e4de3b --- /dev/null +++ b/drivers/staging/unisys/Documentation/overview.txt @@ -0,0 +1,174 @@ + +Overview + +This document describes the driver set for Unisys Secure Partitioning (s-ParĀ®). + +s-Par is firmware that provides hardware partitioning capabilities for +splitting large-scale Intel x86 servers into multiple isolated +partitions. s-Par provides a set of para-virtualized device drivers to +allow guest partitions on the same server to share devices that would +normally be unsharable; specifically, PCI network interfaces and host +bus adapters that do not support shared access via SR-IOV. The shared +device is owned and managed by a small, single-purpose service +partition, which communicates with each guest partition sharing that +device through an area of shared memory called a channel. Additional +drivers provide support interfaces for communicating with s-Par +services, logging and diagnostics, and accessing the Linux console +from the s-Par user interface. + +The driver stack consists of a set of support modules, a set of bus +modules, and a set of device driver modules. The support modules +handle a number of common functions across each of the other +drivers. The bus modules provide organization for the device driver +modules, which provide the shared device functionality. + +These drivers are for the Unisys virtual PCI hardware model where the +hypervisor need not intervene (other than normal interrupt handling) +in the interactions between the client drivers and the virtual adapter +firmware in the adapter service partition. + +Driver Descriptions + +Device Modules + +The modules in this section handle shared devices and the virtual +buses required to support them. These modules use functions in and +depend on the modules described in the support modules section. + +visorchipset + +The visorchipset module receives device creation and destruction +events from the Command service partition of s-Par, as well as +controlling registration of shared device drivers with the s-Par +driver core. The events received are used to populate other s-Par +modules with their assigned shared devices. Visorchipset is required +for shared device drivers to function properly. Visorchipset also +stores information for handling dump disk device creation during +kdump. + +In operation, the visorchipset module processes device creation and +destruction messages sent by s-Par's Command service partition through +a channel. These messages result in creation (or destruction) of each +virtual bus and virtual device. Each bus and device is also associated +with a communication channel, which is used to communicate with one or +more IO service partitions to perform device IO on behalf of the +guest. + +virthba + +The virthba module provides access to a shared SCSI host bus adapter +and one or more disk devices, by proxying SCSI commands between the +guest and the service partition that owns the shared SCSI adapter, +using a channel between the guest and the service partition. The disks +that appear on the shared bus are defined by the s-Par configuration +and enforced by the service partition, while the guest driver handles +sending commands and handling responses. Each disk is shared as a +whole to a guest. Sharing the bus adapter in this way provides +resiliency; should the device encounter an error, only the service +partition is rebooted, and the device is reinitialized. This allows +guests to continue running and to recover from the error. + +virtnic + +The virtnic module provides a paravirtualized network interface to a +guest by proxying buffer information between the guest and the service +partition that owns the shared network interface, using a channel +between the guest and the service partition. The connectivity of this +interface with the shared interface and possibly other guest +partitions is defined by the s-Par configuration and enforced by the +service partition; the guest driver handles communication and link +status. + +visorserial + +The visorserial module allows the console of the linux guest to be +accessed via the s-Par console serial channel. It creates devices in +/dev/visorserialclientX which behave like a serial terminal and are +connected to the diagnostics system in s-Par. By assigning a getty to +the terminal in the guest, a user could log into and access the guest +from the s-Par diagnostics SWITCH RUN terminal. + +visorbus + +The visorbus module handles the bus functions for most functional +drivers except visorserial, visordiag, virthba, and virtnic. It +maintains the sysfs subtree /sys/devices/visorbus*/. It is responsible +for device creation and destruction of the devices on its bus. + +visorclientbus + +The visorclientbus module forwards the bus functions for virthba, and +virtnic to the virtpci driver. + +virtpci + +The virtpci module handles the bus functions for virthba, and virtnic. + +s-Par Integration Modules + +The modules in this section provide integration with s-Par guest +partition services like diagnostics and remote desktop. These modules +depend on functions in the modules described in the support modules +section. + +visorvideoclient + +The visorvideoclient module provides functionality for video support +for the Unisys s-Par Partition Desktop application. The guest OS must +also have the UEFI GOP protocol enabled for the partition desktop to +function. visorconinclient The visorconinclient module provides +keyboard and mouse support for the Unisys s-Par Partition Desktop +application. + +sparstop + +The sparstop module handles requests from the Unisys s-Par platform to +shutdown the linux guest. It allows a program on the guest to perform +clean-up functions on the guest before the guest is shut down or +rebooted using ACPI. + +visordiag + +This driver provides the ability for the guest to write information +into the s-Par diagnostics subsystem. It creates a set of devices +named /dev/visordiag.X which can be written to by the guest to add +text to the s-Par system log. + +Support Modules + +The modules described in this section provide functions and +abstractions to support the modules described in the previous +sections, to avoid having duplicated functionality. + +visornoop + +The visornoop module is a placeholder that responds to device +create/destroy messages that are currently not in use by linux guests. + +visoruislib + +The visoruislib module is a support library, used to handle requests +from virtpci. + +visorchannelstub + +The visorchannelstub module provides support routines for storing and +retrieving data from a channel. + +visorchannel + +The visorchannel module is a support library that abstracts reading +and writing a channel in memory. + +visorutil + +The visorutil module is a support library required by all other s-Par +driver modules. Among its features it abstracts reading, writing, and +manipulating a block of memory. + +Minimum Required Driver Set + +The drivers required to boot a Linux guest are visorchipset, visorbus, +visorvideoclient, visorconinclient, visoruislib, visorchannelstub, +visorchannel, and visorutil. The other drivers are required by the +product configurations that are currently being marketed. diff --git a/drivers/staging/unisys/Documentation/proc-entries.txt b/drivers/staging/unisys/Documentation/proc-entries.txt new file mode 100644 index 00000000000..426f92b1c57 --- /dev/null +++ b/drivers/staging/unisys/Documentation/proc-entries.txt @@ -0,0 +1,93 @@ + s-Par Proc Entries +This document describes the proc entries created by the Unisys s-Par modules. + +Support Module Entries +These entries are provided primarily for debugging. + +/proc/uislib/info: This entry contains debugging information for the +uislib module, including bus information and memory usage. + +/proc/visorchipset/controlvm: This directory contains debugging +entries for the controlvm channel used by visorchipset. + +/proc/uislib/platform: This entry is used to display the platform +number this node is in the system. For some guests, this may be +invalid. + +/proc/visorchipset/chipsetready: This entry is written to by scripts +to signify that any user level activity has been completed before the +guest can be considered running and is shown as running in the s-Par +UI. + +Device Entries +These entries provide status of the devices shared by a service partition. + +/proc/uislib/vbus: this is a directory containing entries for each +virtual bus. Each numbered sub-directory contains an info entry, which +describes the devices that appear on that bus. + +/proc/uislib/cycles_before_wait: This entry is used to tune +performance, by setting the number of cycles we wait before going idle +when in polling mode. A longer time will reduce message latency but +spend more processing time polling. + +/proc/uislib/smart_wakeup: This entry is used to tune performance, by +enabling or disabling smart wakeup. + +/proc/virthba/info: This entry contains debugging information for the +virthba module, including interrupt information and memory usage. + +/proc/virthba/enable_ints: This entry controls interrupt use by the +virthba module. Writing a 0 to this entry will disable interrupts. + +/proc/virtnic/info: This entry contains debugging information for the +virtnic module, including interrupt information, send and receive +counts, and other device information. + +/proc/virtnic/ethX: This is a directory containing entries for each +virtual NIC. Each named subdirectory contains two entries, +clientstring and zone. + +/proc/virtpci/info: This entry contains debugging information for the +virtpci module, including virtual PCI bus information and device +locations. + +/proc/virtnic/enable_ints: This entry controls interrupt use by the +virtnic module. Writing a 0 to this entry will disable interrupts. + +Visorconinclient, visordiag, visornoop, visorserialclient, and +visorvideoclient Entries + +The entries in proc for these modules all follow the same +pattern. Each module has its own proc directory with the same name, +e.g. visordiag presents a /proc/visordiag directory. Inside of the +module's directory are a device directory, which contains one numbered +directory for each device provided by that module. Each device has a +diag entry that presents the device number and visorbus name for that +device. The module directory also has a driver/diag entry, which +reports the corresponding s-Par version number of the driver. + +Automated Installation Entries + +These entries are used to pass information between the s-Par platform +and the Linux-based installation and recovery tool. These values are +read/write, however, the guest can only reset them to 0, or report an +error status through the installer entry. The values are only set via +s-Par's firmware interface, to help prevent accidentally booting into +the tool. + +/proc/visorchipset/boottotool: This entry instructs s-Par that the +next reboot will launch the installation and recovery tool. If set to +0, the next boot will happen according to the UEFI boot manager +settings. + +/proc/visorchipset/toolaction: This entry indicates the installation +and recovery tool mode requested for the next boot. + +/proc/visorchipset/installer: this entry is used by the installation +and recovery tool to pass status and result information back to the +s-Par firmware. + +/proc/visorchipset/partition: This directory contains the guest +partition configuration data for each virtual bus, for use during +installation and at runtime for s-Par service partitions. diff --git a/drivers/staging/unisys/Kconfig b/drivers/staging/unisys/Kconfig new file mode 100644 index 00000000000..6bae2afbaa1 --- /dev/null +++ b/drivers/staging/unisys/Kconfig @@ -0,0 +1,20 @@ +# +# Unisys SPAR driver configuration +# +menuconfig UNISYSSPAR + bool "Unisys SPAR driver support" + depends on X86_64 && BROKEN + ---help--- + Support for the Unisys SPAR drivers + +if UNISYSSPAR + +source "drivers/staging/unisys/visorutil/Kconfig" +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" +source "drivers/staging/unisys/virthba/Kconfig" + +endif # UNISYSSPAR diff --git a/drivers/staging/unisys/MAINTAINERS b/drivers/staging/unisys/MAINTAINERS new file mode 100644 index 00000000000..c9cef0b9153 --- /dev/null +++ b/drivers/staging/unisys/MAINTAINERS @@ -0,0 +1,6 @@ +Unisys s-Par drivers +M: Ben Romer <sparmaintainer@unisys.com> +S: Maintained +F: Documentation/s-Par/overview.txt +F: Documentation/s-Par/proc-entries.txt +F: drivers/staging/unisys/ diff --git a/drivers/staging/unisys/Makefile b/drivers/staging/unisys/Makefile new file mode 100644 index 00000000000..b988d6940aa --- /dev/null +++ b/drivers/staging/unisys/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for Unisys SPAR drivers +# +obj-$(CONFIG_UNISYS_VISORUTIL) += visorutil/ +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/ +obj-$(CONFIG_UNISYS_VIRTHBA) += virthba/ diff --git a/drivers/staging/unisys/TODO b/drivers/staging/unisys/TODO new file mode 100644 index 00000000000..034ac61c44f --- /dev/null +++ b/drivers/staging/unisys/TODO @@ -0,0 +1,21 @@ +TODO: + -checkpatch warnings + -move /proc entries to /sys + -proper major number(s) + -add other drivers needed for full functionality: + -visorclientbus + -visorbus + -visordiag + -virtnic + -visornoop + -visorserial + -visorvideoclient + -visorconinclient + -sparstop + -move individual drivers into proper driver subsystems + + +Patches to: + Greg Kroah-Hartman <gregkh@linuxfoundation.org> + Ken Cox <jkc@redhat.com> + Unisys s-Par maintainer mailing list <sparmaintainer@unisys.com> diff --git a/drivers/staging/unisys/channels/Kconfig b/drivers/staging/unisys/channels/Kconfig new file mode 100644 index 00000000000..47a23538556 --- /dev/null +++ b/drivers/staging/unisys/channels/Kconfig @@ -0,0 +1,10 @@ +# +# Unisys channels configuration +# + +config UNISYS_CHANNELSTUB + tristate "Unisys channelstub driver" + depends on UNISYSSPAR + ---help--- + If you say Y here, you will enable the Unisys channels driver. + diff --git a/drivers/staging/unisys/channels/Makefile b/drivers/staging/unisys/channels/Makefile new file mode 100644 index 00000000000..e60b0aef4dc --- /dev/null +++ b/drivers/staging/unisys/channels/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for Unisys channelstub +# + +obj-$(CONFIG_UNISYS_CHANNELSTUB) += visorchannelstub.o + +visorchannelstub-y := channel.o chanstub.o + +ccflags-y += -Idrivers/staging/unisys/include +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/channels/channel.c b/drivers/staging/unisys/channels/channel.c new file mode 100644 index 00000000000..7223a14082b --- /dev/null +++ b/drivers/staging/unisys/channels/channel.c @@ -0,0 +1,219 @@ +/* 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. + */ + +#include <linux/kernel.h> +#ifdef CONFIG_MODVERSIONS +#include <config/modversions.h> +#endif +#include <linux/module.h> +#include <linux/init.h> /* for module_init and module_exit */ +#include <linux/slab.h> /* for memcpy */ +#include <linux/types.h> + +/* Implementation of exported functions for Supervisor channels */ +#include "channel.h" + +/* + * Routine Description: + * Tries to insert the prebuilt signal pointed to by pSignal into the nth + * Queue of the Channel pointed to by pChannel + * + * Parameters: + * pChannel: (IN) points to the IO Channel + * Queue: (IN) nth Queue of the IO Channel + * pSignal: (IN) pointer to the signal + * + * Assumptions: + * - pChannel, Queue and pSignal are valid. + * - If insertion fails due to a full queue, the caller will determine the + * retry policy (e.g. wait & try again, report an error, etc.). + * + * Return value: + * 1 if the insertion succeeds, 0 if the queue was full. + */ +unsigned char +visor_signal_insert(CHANNEL_HEADER __iomem *pChannel, U32 Queue, void *pSignal) +{ + void __iomem *psignal; + unsigned int head, tail, nof; + + SIGNAL_QUEUE_HEADER __iomem *pqhdr = + (SIGNAL_QUEUE_HEADER __iomem *) + ((char __iomem *) pChannel + readq(&pChannel->oChannelSpace)) + + Queue; + + /* capture current head and tail */ + head = readl(&pqhdr->Head); + tail = readl(&pqhdr->Tail); + + /* queue is full if (head + 1) % n equals tail */ + if (((head + 1) % readl(&pqhdr->MaxSignalSlots)) == tail) { + nof = readq(&pqhdr->NumOverflows) + 1; + writeq(nof, &pqhdr->NumOverflows); + return 0; + } + + /* increment the head index */ + head = (head + 1) % readl(&pqhdr->MaxSignalSlots); + + /* copy signal to the head location from the area pointed to + * by pSignal + */ + psignal = (char __iomem *)pqhdr + readq(&pqhdr->oSignalBase) + + (head * readl(&pqhdr->SignalSize)); + MEMCPY_TOIO(psignal, pSignal, readl(&pqhdr->SignalSize)); + + VolatileBarrier(); + writel(head, &pqhdr->Head); + + writeq(readq(&pqhdr->NumSignalsSent) + 1, &pqhdr->NumSignalsSent); + return 1; +} +EXPORT_SYMBOL_GPL(visor_signal_insert); + +/* + * Routine Description: + * Removes one signal from Channel pChannel's nth Queue at the + * time of the call and copies it into the memory pointed to by + * pSignal. + * + * Parameters: + * pChannel: (IN) points to the IO Channel + * Queue: (IN) nth Queue of the IO Channel + * pSignal: (IN) pointer to where the signals are to be copied + * + * Assumptions: + * - pChannel and Queue are valid. + * - pSignal points to a memory area large enough to hold queue's SignalSize + * + * Return value: + * 1 if the removal succeeds, 0 if the queue was empty. + */ +unsigned char +visor_signal_remove(CHANNEL_HEADER __iomem *pChannel, U32 Queue, void *pSignal) +{ + void __iomem *psource; + unsigned int head, tail; + SIGNAL_QUEUE_HEADER __iomem *pqhdr = + (SIGNAL_QUEUE_HEADER __iomem *) ((char __iomem *) pChannel + + readq(&pChannel->oChannelSpace)) + Queue; + + /* capture current head and tail */ + head = readl(&pqhdr->Head); + tail = readl(&pqhdr->Tail); + + /* queue is empty if the head index equals the tail index */ + if (head == tail) { + writeq(readq(&pqhdr->NumEmptyCnt) + 1, &pqhdr->NumEmptyCnt); + return 0; + } + + /* advance past the 'empty' front slot */ + tail = (tail + 1) % readl(&pqhdr->MaxSignalSlots); + + /* copy signal from tail location to the area pointed to by pSignal */ + psource = (char __iomem *) pqhdr + readq(&pqhdr->oSignalBase) + + (tail * readl(&pqhdr->SignalSize)); + MEMCPY_FROMIO(pSignal, psource, readl(&pqhdr->SignalSize)); + + VolatileBarrier(); + writel(tail, &pqhdr->Tail); + + writeq(readq(&pqhdr->NumSignalsReceived) + 1, + &pqhdr->NumSignalsReceived); + return 1; +} +EXPORT_SYMBOL_GPL(visor_signal_remove); + +/* + * Routine Description: + * Removes all signals present in Channel pChannel's nth Queue at the + * time of the call and copies them into the memory pointed to by + * pSignal. Returns the # of signals copied as the value of the routine. + * + * Parameters: + * pChannel: (IN) points to the IO Channel + * Queue: (IN) nth Queue of the IO Channel + * pSignal: (IN) pointer to where the signals are to be copied + * + * Assumptions: + * - pChannel and Queue are valid. + * - pSignal points to a memory area large enough to hold Queue's MaxSignals + * # of signals, each of which is Queue's SignalSize. + * + * Return value: + * # of signals copied. + */ +unsigned int +SignalRemoveAll(pCHANNEL_HEADER pChannel, U32 Queue, void *pSignal) +{ + void *psource; + unsigned int head, tail, signalCount = 0; + pSIGNAL_QUEUE_HEADER pqhdr = + (pSIGNAL_QUEUE_HEADER) ((char *) pChannel + + pChannel->oChannelSpace) + Queue; + + /* capture current head and tail */ + head = pqhdr->Head; + tail = pqhdr->Tail; + + /* queue is empty if the head index equals the tail index */ + if (head == tail) + return 0; + + while (head != tail) { + /* advance past the 'empty' front slot */ + tail = (tail + 1) % pqhdr->MaxSignalSlots; + + /* copy signal from tail location to the area pointed + * to by pSignal + */ + psource = + (char *) pqhdr + pqhdr->oSignalBase + + (tail * pqhdr->SignalSize); + MEMCPY((char *) pSignal + (pqhdr->SignalSize * signalCount), + psource, pqhdr->SignalSize); + + VolatileBarrier(); + pqhdr->Tail = tail; + + signalCount++; + pqhdr->NumSignalsReceived++; + } + + return signalCount; +} + +/* + * Routine Description: + * Determine whether a signal queue is empty. + * + * Parameters: + * pChannel: (IN) points to the IO Channel + * Queue: (IN) nth Queue of the IO Channel + * + * Return value: + * 1 if the signal queue is empty, 0 otherwise. + */ +unsigned char +visor_signalqueue_empty(CHANNEL_HEADER __iomem *pChannel, U32 Queue) +{ + SIGNAL_QUEUE_HEADER __iomem *pqhdr = + (SIGNAL_QUEUE_HEADER __iomem *) ((char __iomem *) pChannel + + readq(&pChannel->oChannelSpace)) + Queue; + return readl(&pqhdr->Head) == readl(&pqhdr->Tail); +} +EXPORT_SYMBOL_GPL(visor_signalqueue_empty); + diff --git a/drivers/staging/unisys/channels/chanstub.c b/drivers/staging/unisys/channels/chanstub.c new file mode 100644 index 00000000000..1e7d6a78602 --- /dev/null +++ b/drivers/staging/unisys/channels/chanstub.c @@ -0,0 +1,73 @@ +/* Copyright (C) 2010 - 2013 UNISYS CORPORATION + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + */ + +#define EXPORT_SYMTAB +#include <linux/kernel.h> +#ifdef CONFIG_MODVERSIONS +#include <config/modversions.h> +#endif +#include <linux/module.h> +#include <linux/init.h> /* for module_init and module_exit */ +#include <linux/slab.h> /* for memcpy */ +#include <linux/types.h> + +#include "channel.h" +#include "chanstub.h" +#include "timskmodutils.h" +#include "version.h" + +static __init int +channel_mod_init(void) +{ + if (!unisys_spar_platform) + return -ENODEV; + return 0; +} + +static __exit void +channel_mod_exit(void) +{ +} + +unsigned char +SignalInsert_withLock(CHANNEL_HEADER __iomem *pChannel, U32 Queue, + void *pSignal, spinlock_t *lock) +{ + unsigned char result; + unsigned long flags; + spin_lock_irqsave(lock, flags); + result = visor_signal_insert(pChannel, Queue, pSignal); + spin_unlock_irqrestore(lock, flags); + return result; +} + +unsigned char +SignalRemove_withLock(CHANNEL_HEADER __iomem *pChannel, U32 Queue, + void *pSignal, spinlock_t *lock) +{ + unsigned char result; + spin_lock(lock); + result = visor_signal_remove(pChannel, Queue, pSignal); + spin_unlock(lock); + return result; +} + +module_init(channel_mod_init); +module_exit(channel_mod_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Bryan Glaudel"); +MODULE_ALIAS("uischan"); + /* this is extracted during depmod and kept in modules.dep */ diff --git a/drivers/staging/unisys/channels/chanstub.h b/drivers/staging/unisys/channels/chanstub.h new file mode 100644 index 00000000000..bdee5d529f6 --- /dev/null +++ b/drivers/staging/unisys/channels/chanstub.h @@ -0,0 +1,23 @@ +/* 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. + */ + +#ifndef __CHANSTUB_H__ +#define __CHANSTUB_H__ +unsigned char SignalInsert_withLock(CHANNEL_HEADER __iomem *pChannel, U32 Queue, + void *pSignal, spinlock_t *lock); +unsigned char SignalRemove_withLock(CHANNEL_HEADER __iomem *pChannel, U32 Queue, + void *pSignal, spinlock_t *lock); + +#endif diff --git a/drivers/staging/unisys/common-spar/include/channels/channel.h b/drivers/staging/unisys/common-spar/include/channels/channel.h new file mode 100644 index 00000000000..d19711de114 --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/channels/channel.h @@ -0,0 +1,652 @@ +/* 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. + */ + +#ifndef __CHANNEL_H__ +#define __CHANNEL_H__ + +#include <linux/uuid.h> + +/* +* Whenever this file is changed a corresponding change must be made in +* the Console/ServicePart/visordiag_early/supervisor_channel.h file +* which is needed for Linux kernel compiles. These two files must be +* in sync. +*/ + +/* define the following to prevent include nesting in kernel header + * files of similar abbreviated content + */ +#define __SUPERVISOR_CHANNEL_H__ + +#include "commontypes.h" + +#define SIGNATURE_16(A, B) ((A) | (B<<8)) +#define SIGNATURE_32(A, B, C, D) \ + (SIGNATURE_16(A, B) | (SIGNATURE_16(C, D) << 16)) +#define SIGNATURE_64(A, B, C, D, E, F, G, H) \ + (SIGNATURE_32(A, B, C, D) | ((U64)(SIGNATURE_32(E, F, G, H)) << 32)) + +#ifndef lengthof +#define lengthof(TYPE, MEMBER) (sizeof(((TYPE *)0)->MEMBER)) +#endif +#ifndef COVERQ +#define COVERQ(v, d) (((v)+(d)-1) / (d)) +#endif +#ifndef COVER +#define COVER(v, d) ((d)*COVERQ(v, d)) +#endif + +#define ULTRA_CHANNEL_PROTOCOL_SIGNATURE SIGNATURE_32('E', 'C', 'N', 'L') + +typedef enum { + CHANNELSRV_UNINITIALIZED = 0, /* channel is in an undefined state */ + CHANNELSRV_READY = 1 /* channel has been initialized by server */ +} CHANNEL_SERVERSTATE; + +typedef enum { + CHANNELCLI_DETACHED = 0, + CHANNELCLI_DISABLED = 1, /* client can see channel but is NOT + * allowed to use it unless given TBD + * explicit request (should actually be + * < DETACHED) */ + CHANNELCLI_ATTACHING = 2, /* legacy EFI client request + * for EFI server to attach */ + CHANNELCLI_ATTACHED = 3, /* idle, but client may want + * to use channel any time */ + CHANNELCLI_BUSY = 4, /* client either wants to use or is + * using channel */ + CHANNELCLI_OWNED = 5 /* "no worries" state - client can + * access channel anytime */ +} CHANNEL_CLIENTSTATE; +static inline const U8 * +ULTRA_CHANNELCLI_STRING(U32 v) +{ + switch (v) { + case CHANNELCLI_DETACHED: + return (const U8 *) ("DETACHED"); + case CHANNELCLI_DISABLED: + return (const U8 *) ("DISABLED"); + case CHANNELCLI_ATTACHING: + return (const U8 *) ("ATTACHING"); + case CHANNELCLI_ATTACHED: + return (const U8 *) ("ATTACHED"); + case CHANNELCLI_BUSY: + return (const U8 *) ("BUSY"); + case CHANNELCLI_OWNED: + return (const U8 *) ("OWNED"); + default: + break; + } + return (const U8 *) ("?"); +} + +#define ULTRA_CHANNELSRV_IS_READY(x) ((x) == CHANNELSRV_READY) +#define ULTRA_CHANNEL_SERVER_READY(pChannel) \ + (ULTRA_CHANNELSRV_IS_READY(readl(&(pChannel)->SrvState))) + +#define ULTRA_VALID_CHANNELCLI_TRANSITION(o, n) \ + (((((o) == CHANNELCLI_DETACHED) && ((n) == CHANNELCLI_DISABLED)) || \ + (((o) == CHANNELCLI_ATTACHING) && ((n) == CHANNELCLI_DISABLED)) || \ + (((o) == CHANNELCLI_ATTACHED) && ((n) == CHANNELCLI_DISABLED)) || \ + (((o) == CHANNELCLI_ATTACHING) && ((n) == CHANNELCLI_DETACHED)) || \ + (((o) == CHANNELCLI_ATTACHED) && ((n) == CHANNELCLI_DETACHED)) || \ + (((o) == CHANNELCLI_DETACHED) && ((n) == CHANNELCLI_ATTACHING)) || \ + (((o) == CHANNELCLI_ATTACHING) && ((n) == CHANNELCLI_ATTACHED)) || \ + (((o) == CHANNELCLI_DETACHED) && ((n) == CHANNELCLI_ATTACHED)) || \ + (((o) == CHANNELCLI_BUSY) && ((n) == CHANNELCLI_ATTACHED)) || \ + (((o) == CHANNELCLI_ATTACHED) && ((n) == CHANNELCLI_BUSY)) || \ + (((o) == CHANNELCLI_DETACHED) && ((n) == CHANNELCLI_OWNED)) || \ + (((o) == CHANNELCLI_DISABLED) && ((n) == CHANNELCLI_OWNED)) || \ + (((o) == CHANNELCLI_ATTACHING) && ((n) == CHANNELCLI_OWNED)) || \ + (((o) == CHANNELCLI_ATTACHED) && ((n) == CHANNELCLI_OWNED)) || \ + (((o) == CHANNELCLI_BUSY) && ((n) == CHANNELCLI_OWNED)) || (0)) \ + ? (1) : (0)) + +#define ULTRA_CHANNEL_CLIENT_CHK_TRANSITION(old, new, chanId, logCtx, \ + file, line) \ + do { \ + if (!ULTRA_VALID_CHANNELCLI_TRANSITION(old, new)) \ + UltraLogEvent(logCtx, \ + CHANNELSTATE_DIAG_EVENTID_TRANSITERR, \ + CHANNELSTATE_DIAG_SEVERITY, \ + CHANNELSTATE_DIAG_SUBSYS, \ + __func__, __LINE__, \ + "%s Channel StateTransition INVALID! (%s) %s(%d)-->%s(%d) @%s:%d\n", \ + chanId, "CliState<x>", \ + ULTRA_CHANNELCLI_STRING(old), \ + old, \ + ULTRA_CHANNELCLI_STRING(new), \ + new, \ + PathName_Last_N_Nodes((U8 *)file, 4), \ + line); \ + } while (0) + +#define ULTRA_CHANNEL_CLIENT_TRANSITION(pChan, chanId, \ + newstate, logCtx) \ + do { \ + ULTRA_CHANNEL_CLIENT_CHK_TRANSITION( \ + readl(&(((CHANNEL_HEADER __iomem *) \ + (pChan))->CliStateOS)), \ + newstate, \ + chanId, logCtx, __FILE__, __LINE__); \ + UltraLogEvent(logCtx, CHANNELSTATE_DIAG_EVENTID_TRANSITOK, \ + CHANNELSTATE_DIAG_SEVERITY, \ + CHANNELSTATE_DIAG_SUBSYS, \ + __func__, __LINE__, \ + "%s Channel StateTransition (%s) %s(%d)-->%s(%d) @%s:%d\n", \ + chanId, "CliStateOS", \ + ULTRA_CHANNELCLI_STRING( \ + readl(&((CHANNEL_HEADER __iomem *) \ + (pChan))->CliStateOS)), \ + readl(&((CHANNEL_HEADER __iomem *) \ + (pChan))->CliStateOS), \ + ULTRA_CHANNELCLI_STRING(newstate), \ + newstate, \ + PathName_Last_N_Nodes(__FILE__, 4), __LINE__); \ + writel(newstate, &((CHANNEL_HEADER __iomem *) \ + (pChan))->CliStateOS); \ + MEMORYBARRIER; \ + } while (0) + +#define ULTRA_CHANNEL_CLIENT_ACQUIRE_OS(pChan, chanId, logCtx) \ + ULTRA_channel_client_acquire_os(pChan, chanId, logCtx, \ + (char *)__FILE__, __LINE__, \ + (char *)__func__) +#define ULTRA_CHANNEL_CLIENT_RELEASE_OS(pChan, chanId, logCtx) \ + ULTRA_channel_client_release_os(pChan, chanId, logCtx, \ + (char *)__FILE__, __LINE__, (char *)__func__) + +/* Values for ULTRA_CHANNEL_PROTOCOL.CliErrorBoot: */ +/* throttling invalid boot channel statetransition error due to client + * disabled */ +#define ULTRA_CLIERRORBOOT_THROTTLEMSG_DISABLED 0x01 + +/* throttling invalid boot channel statetransition error due to client + * not attached */ +#define ULTRA_CLIERRORBOOT_THROTTLEMSG_NOTATTACHED 0x02 + +/* throttling invalid boot channel statetransition error due to busy channel */ +#define ULTRA_CLIERRORBOOT_THROTTLEMSG_BUSY 0x04 + +/* Values for ULTRA_CHANNEL_PROTOCOL.CliErrorOS: */ +/* throttling invalid guest OS channel statetransition error due to + * client disabled */ +#define ULTRA_CLIERROROS_THROTTLEMSG_DISABLED 0x01 + +/* throttling invalid guest OS channel statetransition error due to + * client not attached */ +#define ULTRA_CLIERROROS_THROTTLEMSG_NOTATTACHED 0x02 + +/* throttling invalid guest OS channel statetransition error due to + * busy channel */ +#define ULTRA_CLIERROROS_THROTTLEMSG_BUSY 0x04 + +/* Values for ULTRA_CHANNEL_PROTOCOL.Features: This define exists so +* that windows guest can look at the FeatureFlags in the io channel, +* and configure the windows driver to use interrupts or not based on +* this setting. This flag is set in uislib after the +* ULTRA_VHBA_init_channel is called. All feature bits for all +* channels should be defined here. The io channel feature bits are +* defined right here */ +#define ULTRA_IO_DRIVER_ENABLES_INTS (0x1ULL << 1) +#define ULTRA_IO_CHANNEL_IS_POLLING (0x1ULL << 3) +#define ULTRA_IO_IOVM_IS_OK_WITH_DRIVER_DISABLING_INTS (0x1ULL << 4) +#define ULTRA_IO_DRIVER_DISABLES_INTS (0x1ULL << 5) +#define ULTRA_IO_DRIVER_SUPPORTS_ENHANCED_RCVBUF_CHECKING (0x1ULL << 6) + +#pragma pack(push, 1) /* both GCC and VC now allow this pragma */ +/* Common Channel Header */ +typedef struct _CHANNEL_HEADER { + U64 Signature; /* Signature */ + U32 LegacyState; /* DEPRECATED - being replaced by */ + /* / SrvState, CliStateBoot, and CliStateOS below */ + U32 HeaderSize; /* sizeof(CHANNEL_HEADER) */ + U64 Size; /* Total size of this channel in bytes */ + U64 Features; /* Flags to modify behavior */ + uuid_le Type; /* Channel type: data, bus, control, etc. */ + U64 PartitionHandle; /* ID of guest partition */ + U64 Handle; /* Device number of this channel in client */ + U64 oChannelSpace; /* Offset in bytes to channel specific area */ + U32 VersionId; /* CHANNEL_HEADER Version ID */ + U32 PartitionIndex; /* Index of guest partition */ + uuid_le ZoneGuid; /* Guid of Channel's zone */ + U32 oClientString; /* offset from channel header to + * nul-terminated ClientString (0 if + * ClientString not present) */ + U32 CliStateBoot; /* CHANNEL_CLIENTSTATE of pre-boot + * EFI client of this channel */ + U32 CmdStateCli; /* CHANNEL_COMMANDSTATE (overloaded in + * Windows drivers, see ServerStateUp, + * ServerStateDown, etc) */ + U32 CliStateOS; /* CHANNEL_CLIENTSTATE of Guest OS + * client of this channel */ + U32 ChannelCharacteristics; /* CHANNEL_CHARACTERISTIC_<xxx> */ + U32 CmdStateSrv; /* CHANNEL_COMMANDSTATE (overloaded in + * Windows drivers, see ServerStateUp, + * ServerStateDown, etc) */ + U32 SrvState; /* CHANNEL_SERVERSTATE */ + U8 CliErrorBoot; /* bits to indicate err states for + * boot clients, so err messages can + * be throttled */ + U8 CliErrorOS; /* bits to indicate err states for OS + * clients, so err messages can be + * throttled */ + U8 Filler[1]; /* Pad out to 128 byte cacheline */ + /* Please add all new single-byte values below here */ + U8 RecoverChannel; +} CHANNEL_HEADER, *pCHANNEL_HEADER, ULTRA_CHANNEL_PROTOCOL; + +#define ULTRA_CHANNEL_ENABLE_INTS (0x1ULL << 0) + +/* Subheader for the Signal Type variation of the Common Channel */ +typedef struct _SIGNAL_QUEUE_HEADER { + /* 1st cache line */ + U32 VersionId; /* SIGNAL_QUEUE_HEADER Version ID */ + U32 Type; /* Queue type: storage, network */ + U64 Size; /* Total size of this queue in bytes */ + U64 oSignalBase; /* Offset to signal queue area */ + U64 FeatureFlags; /* Flags to modify behavior */ + U64 NumSignalsSent; /* Total # of signals placed in this queue */ + U64 NumOverflows; /* Total # of inserts failed due to + * full queue */ + U32 SignalSize; /* Total size of a signal for this queue */ + U32 MaxSignalSlots; /* Max # of slots in queue, 1 slot is + * always empty */ + U32 MaxSignals; /* Max # of signals in queue + * (MaxSignalSlots-1) */ + U32 Head; /* Queue head signal # */ + /* 2nd cache line */ + U64 NumSignalsReceived; /* Total # of signals removed from this queue */ + U32 Tail; /* Queue tail signal # (on separate + * cache line) */ + U32 Reserved1; /* Reserved field */ + U64 Reserved2; /* Resrved field */ + U64 ClientQueue; + U64 NumInterruptsReceived; /* Total # of Interrupts received. This + * is incremented by the ISR in the + * guest windows driver */ + U64 NumEmptyCnt; /* Number of times that visor_signal_remove + * is called and returned Empty + * Status. */ + U32 ErrorFlags; /* Error bits set during SignalReinit + * to denote trouble with client's + * fields */ + U8 Filler[12]; /* Pad out to 64 byte cacheline */ +} SIGNAL_QUEUE_HEADER, *pSIGNAL_QUEUE_HEADER; + +#pragma pack(pop) + +#define SignalInit(chan, QHDRFLD, QDATAFLD, QDATATYPE, ver, typ) \ + do { \ + MEMSET(&chan->QHDRFLD, 0, sizeof(chan->QHDRFLD)); \ + chan->QHDRFLD.VersionId = ver; \ + chan->QHDRFLD.Type = typ; \ + chan->QHDRFLD.Size = sizeof(chan->QDATAFLD); \ + chan->QHDRFLD.SignalSize = sizeof(QDATATYPE); \ + chan->QHDRFLD.oSignalBase = (UINTN)(chan->QDATAFLD)- \ + (UINTN)(&chan->QHDRFLD); \ + chan->QHDRFLD.MaxSignalSlots = \ + sizeof(chan->QDATAFLD)/sizeof(QDATATYPE); \ + chan->QHDRFLD.MaxSignals = chan->QHDRFLD.MaxSignalSlots-1; \ + } while (0) + +/* Generic function useful for validating any type of channel when it is + * received by the client that will be accessing the channel. + * Note that <logCtx> is only needed for callers in the EFI environment, and + * is used to pass the EFI_DIAG_CAPTURE_PROTOCOL needed to log messages. + */ +static inline int +ULTRA_check_channel_client(void __iomem *pChannel, + uuid_le expectedTypeGuid, + char *channelName, + U64 expectedMinBytes, + U32 expectedVersionId, + U64 expectedSignature, + char *fileName, int lineNumber, void *logCtx) +{ + if (uuid_le_cmp(expectedTypeGuid, NULL_UUID_LE) != 0) + /* caller wants us to verify type GUID */ + if (MEMCMP_IO(&(((CHANNEL_HEADER __iomem *) (pChannel))->Type), + &expectedTypeGuid, sizeof(uuid_le)) != 0) { + CHANNEL_GUID_MISMATCH(expectedTypeGuid, channelName, + "type", expectedTypeGuid, + ((CHANNEL_HEADER __iomem *) + (pChannel))->Type, fileName, + lineNumber, logCtx); + return 0; + } + if (expectedMinBytes > 0) /* caller wants us to verify + * channel size */ + if (readq(&((CHANNEL_HEADER __iomem *) + (pChannel))->Size) < expectedMinBytes) { + CHANNEL_U64_MISMATCH(expectedTypeGuid, channelName, + "size", expectedMinBytes, + readq(&((CHANNEL_HEADER __iomem *) + (pChannel))->Size), + fileName, + lineNumber, logCtx); + return 0; + } + if (expectedVersionId > 0) /* caller wants us to verify + * channel version */ + if (readl(&((CHANNEL_HEADER __iomem *) (pChannel))->VersionId) + != expectedVersionId) { + CHANNEL_U32_MISMATCH(expectedTypeGuid, channelName, + "version", expectedVersionId, + readl(&((CHANNEL_HEADER __iomem *) + (pChannel))->VersionId), + fileName, lineNumber, logCtx); + return 0; + } + if (expectedSignature > 0) /* caller wants us to verify + * channel signature */ + if (readq(&((CHANNEL_HEADER __iomem *) (pChannel))->Signature) + != expectedSignature) { + CHANNEL_U64_MISMATCH(expectedTypeGuid, channelName, + "signature", expectedSignature, + readq(&((CHANNEL_HEADER __iomem *) + (pChannel))->Signature), + fileName, + lineNumber, logCtx); + return 0; + } + return 1; +} + +/* Generic function useful for validating any type of channel when it is about + * to be initialized by the server of the channel. + * Note that <logCtx> is only needed for callers in the EFI environment, and + * is used to pass the EFI_DIAG_CAPTURE_PROTOCOL needed to log messages. + */ +static inline int +ULTRA_check_channel_server(uuid_le typeGuid, + char *channelName, + U64 expectedMinBytes, + U64 actualBytes, + char *fileName, int lineNumber, void *logCtx) +{ + if (expectedMinBytes > 0) /* caller wants us to verify + * channel size */ + if (actualBytes < expectedMinBytes) { + CHANNEL_U64_MISMATCH(typeGuid, channelName, "size", + expectedMinBytes, actualBytes, + fileName, lineNumber, logCtx); + return 0; + } + return 1; +} + +/* Given a file pathname <s> (with '/' or '\' separating directory nodes), + * returns a pointer to the beginning of a node within that pathname such + * that the number of nodes from that pointer to the end of the string is + * NOT more than <n>. Note that if the pathname has less than <n> nodes + * in it, the return pointer will be to the beginning of the string. + */ +static inline U8 * +PathName_Last_N_Nodes(U8 *s, unsigned int n) +{ + U8 *p = s; + unsigned int node_count = 0; + while (*p != '\0') { + if ((*p == '/') || (*p == '\\')) + node_count++; + p++; + } + if (node_count <= n) + return s; + while (n > 0) { + p--; + if (p == s) + break; /* should never happen, unless someone + * is changing the string while we are + * looking at it!! */ + if ((*p == '/') || (*p == '\\')) + n--; + } + return p + 1; +} + +static inline int +ULTRA_channel_client_acquire_os(void __iomem *pChannel, U8 *chanId, + void *logCtx, char *file, int line, char *func) +{ + CHANNEL_HEADER __iomem *pChan = pChannel; + + if (readl(&pChan->CliStateOS) == CHANNELCLI_DISABLED) { + if ((readb(&pChan->CliErrorOS) + & ULTRA_CLIERROROS_THROTTLEMSG_DISABLED) == 0) { + /* we are NOT throttling this message */ + writeb(readb(&pChan->CliErrorOS) | + ULTRA_CLIERROROS_THROTTLEMSG_DISABLED, + &pChan->CliErrorOS); + /* throttle until acquire successful */ + + UltraLogEvent(logCtx, + CHANNELSTATE_DIAG_EVENTID_TRANSITERR, + CHANNELSTATE_DIAG_SEVERITY, + CHANNELSTATE_DIAG_SUBSYS, func, line, + "%s Channel StateTransition INVALID! - acquire failed because OS client DISABLED @%s:%d\n", + chanId, PathName_Last_N_Nodes( + (U8 *) file, 4), line); + } + return 0; + } + if ((readl(&pChan->CliStateOS) != CHANNELCLI_OWNED) + && (readl(&pChan->CliStateBoot) == CHANNELCLI_DISABLED)) { + /* Our competitor is DISABLED, so we can transition to OWNED */ + UltraLogEvent(logCtx, CHANNELSTATE_DIAG_EVENTID_TRANSITOK, + CHANNELSTATE_DIAG_SEVERITY, + CHANNELSTATE_DIAG_SUBSYS, func, line, + "%s Channel StateTransition (%s) %s(%d)-->%s(%d) @%s:%d\n", + chanId, "CliStateOS", + ULTRA_CHANNELCLI_STRING( + readl(&pChan->CliStateOS)), + readl(&pChan->CliStateOS), + ULTRA_CHANNELCLI_STRING(CHANNELCLI_OWNED), + CHANNELCLI_OWNED, + PathName_Last_N_Nodes((U8 *) file, 4), line); + writel(CHANNELCLI_OWNED, &pChan->CliStateOS); + MEMORYBARRIER; + } + if (readl(&pChan->CliStateOS) == CHANNELCLI_OWNED) { + if (readb(&pChan->CliErrorOS) != 0) { + /* we are in an error msg throttling state; + * come out of it */ + UltraLogEvent(logCtx, + CHANNELSTATE_DIAG_EVENTID_TRANSITOK, + CHANNELSTATE_DIAG_SEVERITY, + CHANNELSTATE_DIAG_SUBSYS, func, line, + "%s Channel OS client acquire now successful @%s:%d\n", + chanId, PathName_Last_N_Nodes((U8 *) file, + 4), line); + writeb(0, &pChan->CliErrorOS); + } + return 1; + } + + /* We have to do it the "hard way". We transition to BUSY, + * and can use the channel iff our competitor has not also + * transitioned to BUSY. */ + if (readl(&pChan->CliStateOS) != CHANNELCLI_ATTACHED) { + if ((readb(&pChan->CliErrorOS) + & ULTRA_CLIERROROS_THROTTLEMSG_NOTATTACHED) == 0) { + /* we are NOT throttling this message */ + writeb(readb(&pChan->CliErrorOS) | + ULTRA_CLIERROROS_THROTTLEMSG_NOTATTACHED, + &pChan->CliErrorOS); + /* throttle until acquire successful */ + UltraLogEvent(logCtx, + CHANNELSTATE_DIAG_EVENTID_TRANSITERR, + CHANNELSTATE_DIAG_SEVERITY, + CHANNELSTATE_DIAG_SUBSYS, func, line, + "%s Channel StateTransition INVALID! - acquire failed because OS client NOT ATTACHED (state=%s(%d)) @%s:%d\n", + chanId, + ULTRA_CHANNELCLI_STRING( + readl(&pChan->CliStateOS)), + readl(&pChan->CliStateOS), + PathName_Last_N_Nodes((U8 *) file, 4), + line); + } + return 0; + } + writel(CHANNELCLI_BUSY, &pChan->CliStateOS); + MEMORYBARRIER; + if (readl(&pChan->CliStateBoot) == CHANNELCLI_BUSY) { + if ((readb(&pChan->CliErrorOS) + & ULTRA_CLIERROROS_THROTTLEMSG_BUSY) == 0) { + /* we are NOT throttling this message */ + writeb(readb(&pChan->CliErrorOS) | + ULTRA_CLIERROROS_THROTTLEMSG_BUSY, + &pChan->CliErrorOS); + /* throttle until acquire successful */ + UltraLogEvent(logCtx, + CHANNELSTATE_DIAG_EVENTID_TRANSITBUSY, + CHANNELSTATE_DIAG_SEVERITY, + CHANNELSTATE_DIAG_SUBSYS, func, line, + "%s Channel StateTransition failed - host OS acquire failed because boot BUSY @%s:%d\n", + chanId, PathName_Last_N_Nodes((U8 *) file, + 4), line); + } + /* reset busy */ + writel(CHANNELCLI_ATTACHED, &pChan->CliStateOS); + MEMORYBARRIER; + return 0; + } + if (readb(&pChan->CliErrorOS) != 0) { + /* we are in an error msg throttling state; come out of it */ + UltraLogEvent(logCtx, CHANNELSTATE_DIAG_EVENTID_TRANSITOK, + CHANNELSTATE_DIAG_SEVERITY, + CHANNELSTATE_DIAG_SUBSYS, func, line, + "%s Channel OS client acquire now successful @%s:%d\n", + chanId, PathName_Last_N_Nodes((U8 *) file, 4), + line); + writeb(0, &pChan->CliErrorOS); + } + return 1; +} + +static inline void +ULTRA_channel_client_release_os(void __iomem *pChannel, U8 *chanId, + void *logCtx, char *file, int line, char *func) +{ + CHANNEL_HEADER __iomem *pChan = pChannel; + if (readb(&pChan->CliErrorOS) != 0) { + /* we are in an error msg throttling state; come out of it */ + UltraLogEvent(logCtx, CHANNELSTATE_DIAG_EVENTID_TRANSITOK, + CHANNELSTATE_DIAG_SEVERITY, + CHANNELSTATE_DIAG_SUBSYS, func, line, + "%s Channel OS client error state cleared @%s:%d\n", + chanId, PathName_Last_N_Nodes((U8 *) file, 4), + line); + writeb(0, &pChan->CliErrorOS); + } + if (readl(&pChan->CliStateOS) == CHANNELCLI_OWNED) + return; + if (readl(&pChan->CliStateOS) != CHANNELCLI_BUSY) { + UltraLogEvent(logCtx, CHANNELSTATE_DIAG_EVENTID_TRANSITERR, + CHANNELSTATE_DIAG_SEVERITY, + CHANNELSTATE_DIAG_SUBSYS, func, line, + "%s Channel StateTransition INVALID! - release failed because OS client NOT BUSY (state=%s(%d)) @%s:%d\n", + chanId, + ULTRA_CHANNELCLI_STRING( + readl(&pChan->CliStateOS)), + readl(&pChan->CliStateOS), + PathName_Last_N_Nodes((U8 *) file, 4), line); + /* return; */ + } + writel(CHANNELCLI_ATTACHED, &pChan->CliStateOS); /* release busy */ +} + +/* +* Routine Description: +* Tries to insert the prebuilt signal pointed to by pSignal into the nth +* Queue of the Channel pointed to by pChannel +* +* Parameters: +* pChannel: (IN) points to the IO Channel +* Queue: (IN) nth Queue of the IO Channel +* pSignal: (IN) pointer to the signal +* +* Assumptions: +* - pChannel, Queue and pSignal are valid. +* - If insertion fails due to a full queue, the caller will determine the +* retry policy (e.g. wait & try again, report an error, etc.). +* +* Return value: 1 if the insertion succeeds, 0 if the queue was +* full. +*/ + +unsigned char visor_signal_insert(CHANNEL_HEADER __iomem *pChannel, U32 Queue, + void *pSignal); + +/* +* Routine Description: +* Removes one signal from Channel pChannel's nth Queue at the +* time of the call and copies it into the memory pointed to by +* pSignal. +* +* Parameters: +* pChannel: (IN) points to the IO Channel +* Queue: (IN) nth Queue of the IO Channel +* pSignal: (IN) pointer to where the signals are to be copied +* +* Assumptions: +* - pChannel and Queue are valid. +* - pSignal points to a memory area large enough to hold queue's SignalSize +* +* Return value: 1 if the removal succeeds, 0 if the queue was +* empty. +*/ + +unsigned char visor_signal_remove(CHANNEL_HEADER __iomem *pChannel, U32 Queue, + void *pSignal); + +/* +* Routine Description: +* Removes all signals present in Channel pChannel's nth Queue at the +* time of the call and copies them into the memory pointed to by +* pSignal. Returns the # of signals copied as the value of the routine. +* +* Parameters: +* pChannel: (IN) points to the IO Channel +* Queue: (IN) nth Queue of the IO Channel +* pSignal: (IN) pointer to where the signals are to be copied +* +* Assumptions: +* - pChannel and Queue are valid. +* - pSignal points to a memory area large enough to hold Queue's MaxSignals +* # of signals, each of which is Queue's SignalSize. +* +* Return value: +* # of signals copied. +*/ +unsigned int SignalRemoveAll(pCHANNEL_HEADER pChannel, U32 Queue, + void *pSignal); + +/* +* Routine Description: +* Determine whether a signal queue is empty. +* +* Parameters: +* pChannel: (IN) points to the IO Channel +* Queue: (IN) nth Queue of the IO Channel +* +* Return value: +* 1 if the signal queue is empty, 0 otherwise. +*/ +unsigned char visor_signalqueue_empty(CHANNEL_HEADER __iomem *pChannel, + U32 Queue); + +#endif diff --git a/drivers/staging/unisys/common-spar/include/channels/channel_guid.h b/drivers/staging/unisys/common-spar/include/channels/channel_guid.h new file mode 100644 index 00000000000..63c67ca4c9e --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/channels/channel_guid.h @@ -0,0 +1,64 @@ +/* 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. + */ + +/* + * CHANNEL Guids + */ + +/* Used in IOChannel + * {414815ed-c58c-11da-95a9-00e08161165f} + */ +#define ULTRA_VHBA_CHANNEL_PROTOCOL_GUID \ + UUID_LE(0x414815ed, 0xc58c, 0x11da, \ + 0x95, 0xa9, 0x0, 0xe0, 0x81, 0x61, 0x16, 0x5f) +static const uuid_le UltraVhbaChannelProtocolGuid = + ULTRA_VHBA_CHANNEL_PROTOCOL_GUID; + +/* Used in IOChannel + * {8cd5994d-c58e-11da-95a9-00e08161165f} + */ +#define ULTRA_VNIC_CHANNEL_PROTOCOL_GUID \ + UUID_LE(0x8cd5994d, 0xc58e, 0x11da, \ + 0x95, 0xa9, 0x0, 0xe0, 0x81, 0x61, 0x16, 0x5f) +static const uuid_le UltraVnicChannelProtocolGuid = + ULTRA_VNIC_CHANNEL_PROTOCOL_GUID; + +/* Used in IOChannel + * {72120008-4AAB-11DC-8530-444553544200} + */ +#define ULTRA_SIOVM_GUID \ + UUID_LE(0x72120008, 0x4AAB, 0x11DC, \ + 0x85, 0x30, 0x44, 0x45, 0x53, 0x54, 0x42, 0x00) +static const uuid_le UltraSIOVMGuid = ULTRA_SIOVM_GUID; + + +/* Used in visornoop/visornoop_main.c + * {5b52c5ac-e5f5-4d42-8dff-429eaecd221f} + */ +#define ULTRA_CONTROLDIRECTOR_CHANNEL_PROTOCOL_GUID \ + UUID_LE(0x5b52c5ac, 0xe5f5, 0x4d42, \ + 0x8d, 0xff, 0x42, 0x9e, 0xae, 0xcd, 0x22, 0x1f) + +static const uuid_le UltraControlDirectorChannelProtocolGuid = + ULTRA_CONTROLDIRECTOR_CHANNEL_PROTOCOL_GUID; + +/* Used in visorchipset/visorchipset_main.c + * {B4E79625-AEDE-4EAA-9E11-D3EDDCD4504C} + */ +#define ULTRA_DIAG_POOL_CHANNEL_PROTOCOL_GUID \ + UUID_LE(0xb4e79625, 0xaede, 0x4eaa, \ + 0x9e, 0x11, 0xd3, 0xed, 0xdc, 0xd4, 0x50, 0x4c) + + diff --git a/drivers/staging/unisys/common-spar/include/channels/controlframework.h b/drivers/staging/unisys/common-spar/include/channels/controlframework.h new file mode 100644 index 00000000000..1a1c5053fcf --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/channels/controlframework.h @@ -0,0 +1,77 @@ +/* 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. + */ + +/* + * Module Name: + * controlframework.h + * + * Abstract: This file defines common structures in the unmanaged + * Ultravisor (mostly EFI) space. + * + */ + +#ifndef _CONTROL_FRAMEWORK_H_ +#define _CONTROL_FRAMEWORK_H_ + +#include "commontypes.h" +#include "channel.h" + +#define ULTRA_MEMORY_COUNT_Ki 1024 + +/* Scale order 0 is one 32-bit (4-byte) word (in 64 or 128-bit + * architecture potentially 64 or 128-bit word) */ +#define ULTRA_MEMORY_PAGE_WORD 4 + +/* Define Ki scale page to be traditional 4KB page */ +#define ULTRA_MEMORY_PAGE_Ki (ULTRA_MEMORY_PAGE_WORD * ULTRA_MEMORY_COUNT_Ki) +typedef struct _ULTRA_SEGMENT_STATE { + U16 Enabled:1; /* Bit 0: May enter other states */ + U16 Active:1; /* Bit 1: Assigned to active partition */ + U16 Alive:1; /* Bit 2: Configure message sent to + * service/server */ + U16 Revoked:1; /* Bit 3: similar to partition state + * ShuttingDown */ + U16 Allocated:1; /* Bit 4: memory (device/port number) + * has been selected by Command */ + U16 Known:1; /* Bit 5: has been introduced to the + * service/guest partition */ + U16 Ready:1; /* Bit 6: service/Guest partition has + * responded to introduction */ + U16 Operating:1; /* Bit 7: resource is configured and + * operating */ + /* Note: don't use high bit unless we need to switch to ushort + * which is non-compliant */ +} ULTRA_SEGMENT_STATE; +static const ULTRA_SEGMENT_STATE SegmentStateRunning = { + 1, 1, 1, 0, 1, 1, 1, 1 +}; +static const ULTRA_SEGMENT_STATE SegmentStatePaused = { + 1, 1, 1, 0, 1, 1, 1, 0 +}; +static const ULTRA_SEGMENT_STATE SegmentStateStandby = { + 1, 1, 0, 0, 1, 1, 1, 0 +}; +typedef union { + U64 Full; + struct { + U8 Major; /* will be 1 for the first release and + * increment thereafter */ + U8 Minor; + U16 Maintenance; + U32 Revision; /* Subversion revision */ + } Part; +} ULTRA_COMPONENT_VERSION; + +#endif /* _CONTROL_FRAMEWORK_H_ not defined */ diff --git a/drivers/staging/unisys/common-spar/include/channels/controlvmchannel.h b/drivers/staging/unisys/common-spar/include/channels/controlvmchannel.h new file mode 100644 index 00000000000..d8b12a73348 --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/channels/controlvmchannel.h @@ -0,0 +1,620 @@ +/* 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. + */ + +#ifndef __CONTROLVMCHANNEL_H__ +#define __CONTROLVMCHANNEL_H__ + +#include <linux/uuid.h> +#include "commontypes.h" +#include "channel.h" +#include "controlframework.h" +enum { INVALID_GUEST_FIRMWARE, SAMPLE_GUEST_FIRMWARE, + TIANO32_GUEST_FIRMWARE, TIANO64_GUEST_FIRMWARE +}; + +/* {2B3C2D10-7EF5-4ad8-B966-3448B7386B3D} */ +#define ULTRA_CONTROLVM_CHANNEL_PROTOCOL_GUID \ + UUID_LE(0x2b3c2d10, 0x7ef5, 0x4ad8, \ + 0xb9, 0x66, 0x34, 0x48, 0xb7, 0x38, 0x6b, 0x3d) + +static const uuid_le UltraControlvmChannelProtocolGuid = + ULTRA_CONTROLVM_CHANNEL_PROTOCOL_GUID; + +#define ULTRA_CONTROLVM_CHANNEL_PROTOCOL_SIGNATURE \ + ULTRA_CHANNEL_PROTOCOL_SIGNATURE +#define CONTROLVM_MESSAGE_MAX 64 + +/* Must increment this whenever you insert or delete fields within +* this channel struct. Also increment whenever you change the meaning +* of fields within this channel struct so as to break pre-existing +* software. Note that you can usually add fields to the END of the +* channel struct withOUT needing to increment this. */ +#define ULTRA_CONTROLVM_CHANNEL_PROTOCOL_VERSIONID 1 + +#define ULTRA_CONTROLVM_CHANNEL_OK_CLIENT(pChannel, logCtx) \ + (ULTRA_check_channel_client(pChannel, \ + UltraControlvmChannelProtocolGuid, \ + "controlvm", \ + sizeof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL), \ + ULTRA_CONTROLVM_CHANNEL_PROTOCOL_VERSIONID, \ + ULTRA_CONTROLVM_CHANNEL_PROTOCOL_SIGNATURE, \ + __FILE__, __LINE__, logCtx)) +#define ULTRA_CONTROLVM_CHANNEL_OK_SERVER(actualBytes, logCtx) \ + (ULTRA_check_channel_server(UltraControlvmChannelProtocolGuid, \ + "controlvm", \ + sizeof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL), \ + actualBytes, __FILE__, __LINE__, logCtx)) + +#define MY_DEVICE_INDEX 0 +#define MAX_MACDATA_LEN 8 /* number of bytes for MAC address in config packet */ +#define MAX_SERIAL_NUM 32 + +#define DISK_ZERO_PUN_NUMBER 1 /* Target ID on the SCSI bus for LUN 0 */ +#define DISK_ZERO_LUN_NUMBER 3 /* Logical Unit Number */ + +/* Defines for various channel queues... */ +#define CONTROLVM_QUEUE_REQUEST 0 +#define CONTROLVM_QUEUE_RESPONSE 1 +#define CONTROLVM_QUEUE_EVENT 2 +#define CONTROLVM_QUEUE_ACK 3 + +/* Max number of messages stored during IOVM creation to be reused + * after crash */ +#define CONTROLVM_CRASHMSG_MAX 2 + +/** Ids for commands that may appear in either queue of a ControlVm channel. + * + * Commands that are initiated by the command partition (CP), by an IO or + * console service partition (SP), or by a guest partition (GP)are: + * - issued on the RequestQueue queue (q #0) in the ControlVm channel + * - responded to on the ResponseQueue queue (q #1) in the ControlVm channel + * + * Events that are initiated by an IO or console service partition (SP) or + * by a guest partition (GP) are: + * - issued on the EventQueue queue (q #2) in the ControlVm channel + * - responded to on the EventAckQueue queue (q #3) in the ControlVm channel + */ +typedef enum { + CONTROLVM_INVALID = 0, + /* SWITCH commands required Parameter: SwitchNumber */ + /* BUS commands required Parameter: BusNumber */ + CONTROLVM_BUS_CREATE = 0x101, /* CP --> SP, GP */ + CONTROLVM_BUS_DESTROY = 0x102, /* CP --> SP, GP */ + CONTROLVM_BUS_CONFIGURE = 0x104, /* CP --> SP */ + CONTROLVM_BUS_CHANGESTATE = 0x105, /* CP --> SP, GP */ + CONTROLVM_BUS_CHANGESTATE_EVENT = 0x106, /* SP, GP --> CP */ +/* DEVICE commands required Parameter: BusNumber, DeviceNumber */ + + CONTROLVM_DEVICE_CREATE = 0x201, /* CP --> SP, GP */ + CONTROLVM_DEVICE_DESTROY = 0x202, /* CP --> SP, GP */ + CONTROLVM_DEVICE_CONFIGURE = 0x203, /* CP --> SP */ + CONTROLVM_DEVICE_CHANGESTATE = 0x204, /* CP --> SP, GP */ + CONTROLVM_DEVICE_CHANGESTATE_EVENT = 0x205, /* SP, GP --> CP */ + CONTROLVM_DEVICE_RECONFIGURE = 0x206, /* CP --> Boot */ +/* DISK commands required Parameter: BusNumber, DeviceNumber */ + CONTROLVM_DISK_CREATE = 0x221, /* CP --> SP */ + CONTROLVM_DISK_DESTROY = 0x222, /* CP --> SP */ + CONTROLVM_DISK_CONFIGURE = 0x223, /* CP --> SP */ + CONTROLVM_DISK_CHANGESTATE = 0x224, /* CP --> SP */ +/* CHIPSET commands */ + CONTROLVM_CHIPSET_INIT = 0x301, /* CP --> SP, GP */ + CONTROLVM_CHIPSET_STOP = 0x302, /* CP --> SP, GP */ + CONTROLVM_CHIPSET_SHUTDOWN = 0x303, /* CP --> SP */ + CONTROLVM_CHIPSET_READY = 0x304, /* CP --> SP */ + CONTROLVM_CHIPSET_SELFTEST = 0x305, /* CP --> SP */ + +} CONTROLVM_ID; + +struct InterruptInfo { + /**< specifies interrupt info. It is used to send interrupts + * for this channel. The peer at the end of this channel + * who has registered an interrupt (using recv fields + * above) will receive the interrupt. Passed as a parameter + * to Issue_VMCALL_IO_QUEUE_TRANSITION, which generates the + * interrupt. Currently this is used by IOPart-SP to wake + * up GP when Data Channel transitions from empty to + * non-empty.*/ + U64 sendInterruptHandle; + + /**< specifies interrupt handle. It is used to retrieve the + * corresponding interrupt pin from Monitor; and the + * interrupt pin is used to connect to the corresponding + * intrrupt. Used by IOPart-GP only. */ + U64 recvInterruptHandle; + + /**< specifies interrupt vector. It, interrupt pin, and shared are + * used to connect to the corresponding interrupt. Used by + * IOPart-GP only. */ + U32 recvInterruptVector; + + /**< specifies if the recvInterrupt is shared. It, interrupt pin + * and vector are used to connect to 0 = not shared; 1 = shared. + * the corresponding interrupt. Used by IOPart-GP only. */ + U8 recvInterruptShared; + U8 reserved[3]; /* Natural alignment purposes */ +}; + +struct PciId { + U16 Domain; + U8 Bus; + U8 Slot; + U8 Func; + U8 Reserved[3]; /* Natural alignment purposes */ +}; + +struct PciConfigHdr { + U16 VendorId; + U16 SubSysVendor; + U16 DeviceId; + U16 SubSysDevice; + U32 ClassCode; + U32 Reserved; /* Natural alignment purposes */ +}; + +struct ScsiId { + U32 Bus; + U32 Target; + U32 Lun; + U32 Host; /* Command should ignore this for * + * DiskArrival/RemovalEvents */ +}; + +struct WWID { + U32 wwid1; + U32 wwid2; +}; + +struct virtDiskInfo { + U32 switchNo; /* defined by SWITCH_CREATE */ + U32 externalPortNo; /* 0 for SAS RAID provided (external) + * virtual disks, 1 for virtual disk + * images, 2 for gold disk images */ + U16 VirtualDiskIndex; /* Index of disk descriptor in the + * VirtualDisk segment associated with + * externalPortNo */ + U16 Reserved1; + U32 Reserved2; +}; + +typedef enum { + CONTROLVM_ACTION_NONE = 0, + CONTROLVM_ACTION_SET_RESTORE = 0x05E7, + CONTROLVM_ACTION_CLEAR_RESTORE = 0x0C18, + CONTROLVM_ACTION_RESTORING = 0x08E5, + CONTROLVM_ACTION_RESTORE_BUSY = 0x0999, + CONTROLVM_ACTION_CLEAR_NVRAM = 0xB01 +} CONTROLVM_ACTION; + +typedef enum _ULTRA_TOOL_ACTIONS { + /* enumeration that defines intended action */ + ULTRA_TOOL_ACTION_NONE = 0, /* normal boot of boot disk */ + ULTRA_TOOL_ACTION_INSTALL = 1, /* install source disk(s) to boot + * disk */ + ULTRA_TOOL_ACTION_CAPTURE = 2, /* capture boot disk to target disk(s) + * as 'gold image' */ + ULTRA_TOOL_ACTION_REPAIR = 3, /* use source disk(s) to repair + * installation on boot disk */ + ULTRA_TOOL_ACTION_CLEAN = 4, /* 'scrub' virtual disk before + * releasing back to storage pool */ + ULTRA_TOOL_ACTION_UPGRADE = 5, /* upgrade to use content of images + * referenced from newer blueprint */ + ULTRA_TOOL_ACTION_DIAG = 6, /* use tool to invoke diagnostic script + * provided by blueprint */ + ULTRA_TOOL_ACTION_FAILED = 7, /* used when tool fails installation + and cannot continue */ + ULTRA_TOOL_ACTION_COUNT = 8 +} ULTRA_TOOL_ACTIONS; + +typedef struct _ULTRA_EFI_SPAR_INDICATION { + U64 BootToFirmwareUI:1; /* Bit 0: Stop in uefi ui */ + U64 ClearNvram:1; /* Bit 1: Clear NVRAM */ + U64 ClearCmos:1; /* Bit 2: Clear CMOS */ + U64 BootToTool:1; /* Bit 3: Run install tool */ + /* remaining bits are available */ +} ULTRA_EFI_SPAR_INDICATION; + +typedef enum { + ULTRA_CHIPSET_FEATURE_REPLY = 0x00000001, + ULTRA_CHIPSET_FEATURE_PARA_HOTPLUG = 0x00000002, + ULTRA_CHIPSET_FEATURE_PCIVBUS = 0x00000004 +} ULTRA_CHIPSET_FEATURE; + +/** This is the common structure that is at the beginning of every + * ControlVm message (both commands and responses) in any ControlVm + * queue. Commands are easily distinguished from responses by + * looking at the flags.response field. + */ +typedef struct _CONTROLVM_MESSAGE_HEADER { + U32 Id; /* See CONTROLVM_ID. */ + /* For requests, indicates the message type. */ + /* For responses, indicates the type of message we are responding to. */ + + U32 MessageSize; /* Includes size of this struct + size + * of message */ + U32 SegmentIndex; /* Index of segment containing Vm + * message/information */ + U32 CompletionStatus; /* Error status code or result of + * message completion */ + struct { + U32 failed:1; /**< =1 in a response to * signify + * failure */ + U32 responseExpected:1; /**< =1 in all messages that expect a + * response (Control ignores this + * bit) */ + U32 server:1; /**< =1 in all bus & device-related + * messages where the message + * receiver is to act as the bus or + * device server */ + U32 testMessage:1; /**< =1 for testing use only + * (Control and Command ignore this + * bit) */ + U32 partialCompletion:1; /**< =1 if there are forthcoming + * responses/acks associated + * with this message */ + U32 preserve:1; /**< =1 this is to let us know to + * preserve channel contents + * (for running guests)*/ + U32 writerInDiag:1; /**< =1 the DiagWriter is active in the + * Diagnostic Partition*/ + + /* remaining bits in this 32-bit word are available */ + } Flags; + U32 Reserved; /* Natural alignment */ + U64 MessageHandle; /* Identifies the particular message instance, + * and is used to match particular */ + /* request instances with the corresponding response instance. */ + U64 PayloadVmOffset; /* Offset of payload area from start of this + * instance of ControlVm segment */ + U32 PayloadMaxBytes; /* Maximum bytes allocated in payload + * area of ControlVm segment */ + U32 PayloadBytes; /* Actual number of bytes of payload + * area to copy between IO/Command; */ + /* if non-zero, there is a payload to copy. */ +} CONTROLVM_MESSAGE_HEADER; + +typedef struct _CONTROLVM_PACKET_DEVICE_CREATE { + U32 busNo; /**< bus # (0..n-1) from the msg receiver's + * perspective */ + + /* Control uses header SegmentIndex field to access bus number... */ + U32 devNo; /**< bus-relative (0..n-1) device number */ + U64 channelAddr; /**< Guest physical address of the channel, which + * can be dereferenced by the receiver + * of this ControlVm command */ + U64 channelBytes; /**< specifies size of the channel in bytes */ + uuid_le dataTypeGuid;/**< specifies format of data in channel */ + uuid_le devInstGuid; /**< instance guid for the device */ + struct InterruptInfo intr; /**< specifies interrupt information */ +} CONTROLVM_PACKET_DEVICE_CREATE; /* for CONTROLVM_DEVICE_CREATE */ + +typedef struct _CONTROLVM_PACKET_DEVICE_CONFIGURE { + U32 busNo; /**< bus # (0..n-1) from the msg + * receiver's perspective */ + + /* Control uses header SegmentIndex field to access bus number... */ + U32 devNo; /**< bus-relative (0..n-1) device number */ +} CONTROLVM_PACKET_DEVICE_CONFIGURE; /* for CONTROLVM_DEVICE_CONFIGURE */ + +typedef struct _CONTROLVM_MESSAGE_DEVICE_CREATE { + CONTROLVM_MESSAGE_HEADER Header; + CONTROLVM_PACKET_DEVICE_CREATE Packet; +} CONTROLVM_MESSAGE_DEVICE_CREATE; /* total 128 bytes */ + +typedef struct _CONTROLVM_MESSAGE_DEVICE_CONFIGURE { + CONTROLVM_MESSAGE_HEADER Header; + CONTROLVM_PACKET_DEVICE_CONFIGURE Packet; +} CONTROLVM_MESSAGE_DEVICE_CONFIGURE; /* total 56 bytes */ + +/* This is the format for a message in any ControlVm queue. */ +typedef struct _CONTROLVM_MESSAGE_PACKET { + union { + + /* BEGIN Request messages */ + struct { + U32 busNo; /*< bus # (0..n-1) from the msg + * receiver's perspective */ + + /* Control uses header SegmentIndex field to access bus number... */ + U32 deviceCount; /*< indicates the max number of + * devices on this bus */ + U64 channelAddr; /*< Guest physical address of the + * channel, which can be + * dereferenced by the receiver + * of this ControlVm command */ + U64 channelBytes; /*< size of the channel in bytes */ + uuid_le busDataTypeGuid;/*< indicates format of data in + bus channel */ + uuid_le busInstGuid; /*< instance guid for the bus */ + } createBus; /* for CONTROLVM_BUS_CREATE */ + struct { + U32 busNo; /*< bus # (0..n-1) from the msg + * receiver's perspective */ + + /* Control uses header SegmentIndex field to access bus number... */ + U32 reserved; /* Natural alignment purposes */ + } destroyBus; /* for CONTROLVM_BUS_DESTROY */ + struct { + U32 busNo; /*< bus # (0..n-1) from the + * msg receiver's + * perspective */ + + /* Control uses header SegmentIndex field to access bus number... */ + U32 reserved1; /* for alignment purposes */ + U64 guestHandle; /* This is used to convert + * guest physical address to real + * physical address for DMA, for ex. */ + U64 recvBusInterruptHandle;/*< specifies interrupt + * info. It is used by SP to register + * to receive interrupts from the CP. + * This interrupt is used for bus + * level notifications. The + * corresponding + * sendBusInterruptHandle is kept in + * CP. */ + } configureBus; /* for CONTROLVM_BUS_CONFIGURE */ + + /* for CONTROLVM_DEVICE_CREATE */ + CONTROLVM_PACKET_DEVICE_CREATE createDevice; + struct { + U32 busNo; /*< bus # (0..n-1) from the msg + * receiver's perspective */ + + /* Control uses header SegmentIndex field to access bus number... */ + U32 devNo; /*< bus-relative (0..n-1) device + * number */ + } destroyDevice; /* for CONTROLVM_DEVICE_DESTROY */ + + /* for CONTROLVM_DEVICE_CONFIGURE */ + CONTROLVM_PACKET_DEVICE_CONFIGURE configureDevice; + struct { + U32 busNo; /*< bus # (0..n-1) from the msg + * receiver's perspective */ + + /* Control uses header SegmentIndex field to access bus number... */ + U32 devNo; /*< bus-relative (0..n-1) device + * number */ + } reconfigureDevice; /* for CONTROLVM_DEVICE_RECONFIGURE */ + struct { + U32 busNo; + ULTRA_SEGMENT_STATE state; + U8 reserved[2]; /* Natural alignment purposes */ + } busChangeState; /* for CONTROLVM_BUS_CHANGESTATE */ + struct { + U32 busNo; + U32 devNo; + ULTRA_SEGMENT_STATE state; + struct { + U32 physicalDevice:1; /* =1 if message is for + * a physical device */ + /* remaining bits in this 32-bit word are available */ + } flags; + U8 reserved[2]; /* Natural alignment purposes */ + } deviceChangeState; /* for CONTROLVM_DEVICE_CHANGESTATE */ + struct { + U32 busNo; + U32 devNo; + ULTRA_SEGMENT_STATE state; + U8 reserved[6]; /* Natural alignment purposes */ + } deviceChangeStateEvent; /* for CONTROLVM_DEVICE_CHANGESTATE_EVENT */ + struct { + U32 busCount; /*< indicates the max number of busses */ + U32 switchCount; /*< indicates the max number of + * switches (applicable for service + * partition only) */ + ULTRA_CHIPSET_FEATURE features; + U32 platformNumber; /* Platform Number */ + } initChipset; /* for CONTROLVM_CHIPSET_INIT */ + struct { + U32 Options; /*< reserved */ + U32 Test; /*< bit 0 set to run embedded selftest */ + } chipsetSelftest; /* for CONTROLVM_CHIPSET_SELFTEST */ + + /* END Request messages */ + + /* BEGIN Response messages */ + + /* END Response messages */ + + /* BEGIN Event messages */ + + /* END Event messages */ + + /* BEGIN Ack messages */ + + /* END Ack messages */ + U64 addr; /*< a physical address of something, that + * can be dereferenced by the receiver of + * this ControlVm command (depends on + * command id) */ + U64 handle; /*< a handle of something (depends on + * command id) */ + }; +} CONTROLVM_MESSAGE_PACKET; + +/* All messages in any ControlVm queue have this layout. */ +typedef struct _CONTROLVM_MESSAGE { + CONTROLVM_MESSAGE_HEADER hdr; + CONTROLVM_MESSAGE_PACKET cmd; +} CONTROLVM_MESSAGE; + +typedef struct _DEVICE_MAP { + GUEST_PHYSICAL_ADDRESS DeviceChannelAddress; + U64 DeviceChannelSize; + U32 CA_Index; + U32 Reserved; /* natural alignment */ + U64 Reserved2; /* Align structure on 32-byte boundary */ +} DEVICE_MAP; + +typedef struct _GUEST_DEVICES { + DEVICE_MAP VideoChannel; + DEVICE_MAP KeyboardChannel; + DEVICE_MAP NetworkChannel; + DEVICE_MAP StorageChannel; + DEVICE_MAP ConsoleChannel; + U32 PartitionIndex; + U32 Pad; +} GUEST_DEVICES; + +typedef struct _ULTRA_CONTROLVM_CHANNEL_PROTOCOL { + CHANNEL_HEADER Header; + GUEST_PHYSICAL_ADDRESS gpControlVm; /* guest physical address of + * this channel */ + GUEST_PHYSICAL_ADDRESS gpPartitionTables; /* guest physical address of + * partition tables */ + GUEST_PHYSICAL_ADDRESS gpDiagGuest; /* guest physical address of + * diagnostic channel */ + GUEST_PHYSICAL_ADDRESS gpBootRomDisk; /* guest phys addr of (read + * only) Boot ROM disk */ + GUEST_PHYSICAL_ADDRESS gpBootRamDisk; /* guest phys addr of writable + * Boot RAM disk */ + GUEST_PHYSICAL_ADDRESS gpAcpiTable; /* guest phys addr of acpi + * table */ + GUEST_PHYSICAL_ADDRESS gpControlChannel; /* guest phys addr of control + * channel */ + GUEST_PHYSICAL_ADDRESS gpDiagRomDisk; /* guest phys addr of diagnostic + * ROM disk */ + GUEST_PHYSICAL_ADDRESS gpNvram; /* guest phys addr of NVRAM + * channel */ + U64 RequestPayloadOffset; /* Offset to request payload area */ + U64 EventPayloadOffset; /* Offset to event payload area */ + U32 RequestPayloadBytes; /* Bytes available in request payload + * area */ + U32 EventPayloadBytes; /* Bytes available in event payload area */ + U32 ControlChannelBytes; + U32 NvramChannelBytes; /* Bytes in PartitionNvram segment */ + U32 MessageBytes; /* sizeof(CONTROLVM_MESSAGE) */ + U32 MessageCount; /* CONTROLVM_MESSAGE_MAX */ + GUEST_PHYSICAL_ADDRESS gpSmbiosTable; /* guest phys addr of SMBIOS + * tables */ + GUEST_PHYSICAL_ADDRESS gpPhysicalSmbiosTable; /* guest phys addr of + * SMBIOS table */ + /* ULTRA_MAX_GUESTS_PER_SERVICE */ + GUEST_DEVICES gpObsoleteGuestDevices[16]; + + /* guest physical address of EFI firmware image base */ + GUEST_PHYSICAL_ADDRESS VirtualGuestFirmwareImageBase; + + /* guest physical address of EFI firmware entry point */ + GUEST_PHYSICAL_ADDRESS VirtualGuestFirmwareEntryPoint; + + /* guest EFI firmware image size */ + U64 VirtualGuestFirmwareImageSize; + + /* GPA = 1MB where EFI firmware image is copied to */ + GUEST_PHYSICAL_ADDRESS VirtualGuestFirmwareBootBase; + GUEST_PHYSICAL_ADDRESS VirtualGuestImageBase; + GUEST_PHYSICAL_ADDRESS VirtualGuestImageSize; + U64 PrototypeControlChannelOffset; + GUEST_PHYSICAL_ADDRESS VirtualGuestPartitionHandle; + + U16 RestoreAction; /* Restore Action field to restore the guest + * partition */ + U16 DumpAction; /* For Windows guests it shows if the visordisk + * is running in dump mode */ + U16 NvramFailCount; + U16 SavedCrashMsgCount; /* = CONTROLVM_CRASHMSG_MAX */ + U32 SavedCrashMsgOffset; /* Offset to request payload area needed + * for crash dump */ + U32 InstallationError; /* Type of error encountered during + * installation */ + U32 InstallationTextId; /* Id of string to display */ + U16 InstallationRemainingSteps; /* Number of remaining installation + * steps (for progress bars) */ + U8 ToolAction; /* ULTRA_TOOL_ACTIONS Installation Action + * field */ + U8 Reserved; /* alignment */ + ULTRA_EFI_SPAR_INDICATION EfiSparIndication; + ULTRA_EFI_SPAR_INDICATION EfiSparIndicationSupported; + U32 SPReserved; + U8 Reserved2[28]; /* Force signals to begin on 128-byte cache + * line */ + SIGNAL_QUEUE_HEADER RequestQueue; /* Service or guest partition + * uses this queue to send + * requests to Control */ + SIGNAL_QUEUE_HEADER ResponseQueue; /* Control uses this queue to + * respond to service or guest + * partition requests */ + SIGNAL_QUEUE_HEADER EventQueue; /* Control uses this queue to + * send events to service or + * guest partition */ + SIGNAL_QUEUE_HEADER EventAckQueue; /* Service or guest partition + * uses this queue to ack + * Control events */ + + /* Request fixed-size message pool - does not include payload */ + CONTROLVM_MESSAGE RequestMsg[CONTROLVM_MESSAGE_MAX]; + + /* Response fixed-size message pool - does not include payload */ + CONTROLVM_MESSAGE ResponseMsg[CONTROLVM_MESSAGE_MAX]; + + /* Event fixed-size message pool - does not include payload */ + CONTROLVM_MESSAGE EventMsg[CONTROLVM_MESSAGE_MAX]; + + /* Ack fixed-size message pool - does not include payload */ + CONTROLVM_MESSAGE EventAckMsg[CONTROLVM_MESSAGE_MAX]; + + /* Message stored during IOVM creation to be reused after crash */ + CONTROLVM_MESSAGE SavedCrashMsg[CONTROLVM_CRASHMSG_MAX]; +} ULTRA_CONTROLVM_CHANNEL_PROTOCOL; + +/* Offsets for VM channel attributes... */ +#define VM_CH_REQ_QUEUE_OFFSET \ + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, RequestQueue) +#define VM_CH_RESP_QUEUE_OFFSET \ + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, ResponseQueue) +#define VM_CH_EVENT_QUEUE_OFFSET \ + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, EventQueue) +#define VM_CH_ACK_QUEUE_OFFSET \ + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, EventAckQueue) +#define VM_CH_REQ_MSG_OFFSET \ + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, RequestMsg) +#define VM_CH_RESP_MSG_OFFSET \ + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, ResponseMsg) +#define VM_CH_EVENT_MSG_OFFSET \ + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, EventMsg) +#define VM_CH_ACK_MSG_OFFSET \ + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, EventAckMsg) +#define VM_CH_CRASH_MSG_OFFSET \ + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, SavedCrashMsg) + +/* The following header will be located at the beginning of PayloadVmOffset for + * various ControlVm commands. The receiver of a ControlVm command with a + * PayloadVmOffset will dereference this address and then use ConnectionOffset, + * InitiatorOffset, and TargetOffset to get the location of UTF-8 formatted + * strings that can be parsed to obtain command-specific information. The value + * of TotalLength should equal PayloadBytes. The format of the strings at + * PayloadVmOffset will take different forms depending on the message. See the + * following Wiki page for more information: + * https://ustr-linux-1.na.uis.unisys.com/spar/index.php/ControlVm_Parameters_Area + */ +typedef struct _ULTRA_CONTROLVM_PARAMETERS_HEADER { + U32 TotalLength; + U32 HeaderLength; + U32 ConnectionOffset; + U32 ConnectionLength; + U32 InitiatorOffset; + U32 InitiatorLength; + U32 TargetOffset; + U32 TargetLength; + U32 ClientOffset; + U32 ClientLength; + U32 NameOffset; + U32 NameLength; + uuid_le Id; + U32 Revision; + U32 Reserved; /* Natural alignment */ +} ULTRA_CONTROLVM_PARAMETERS_HEADER; + +#endif /* __CONTROLVMCHANNEL_H__ */ diff --git a/drivers/staging/unisys/common-spar/include/channels/diagchannel.h b/drivers/staging/unisys/common-spar/include/channels/diagchannel.h new file mode 100644 index 00000000000..1bea2f720e4 --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/channels/diagchannel.h @@ -0,0 +1,427 @@ +/* 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. + */ + +/*++ + * + * Module Name: + * + * diagchannel.h + * + * Abstract: + * + * This file defines the DiagChannel protocol. This protocol is used to aid in + * preserving event data sent by external applications. This protocol provides + * a region for event data to reside in. This data will eventually be sent to + * the Boot Partition where it will be committed to memory and/or disk. This + * file contains platform-independent data that can be built using any + * Supervisor build environment (Windows, Linux, EFI). + * +*/ + +#ifndef _DIAG_CHANNEL_H_ +#define _DIAG_CHANNEL_H_ + +#include "commontypes.h" +#include "channel.h" + +/* {EEA7A573-DB82-447c-8716-EFBEAAAE4858} */ +#define ULTRA_DIAG_CHANNEL_PROTOCOL_GUID \ + UUID_LE(0xeea7a573, 0xdb82, 0x447c, \ + 0x87, 0x16, 0xef, 0xbe, 0xaa, 0xae, 0x48, 0x58) + +static const uuid_le UltraDiagChannelProtocolGuid = + ULTRA_DIAG_CHANNEL_PROTOCOL_GUID; + +/* {E850F968-3263-4484-8CA5-2A35D087A5A8} */ +#define ULTRA_DIAG_ROOT_CHANNEL_PROTOCOL_GUID \ + UUID_LE(0xe850f968, 0x3263, 0x4484, \ + 0x8c, 0xa5, 0x2a, 0x35, 0xd0, 0x87, 0xa5, 0xa8) + +#define ULTRA_DIAG_CHANNEL_PROTOCOL_SIGNATURE ULTRA_CHANNEL_PROTOCOL_SIGNATURE + +/* Must increment this whenever you insert or delete fields within this channel +* struct. Also increment whenever you change the meaning of fields within this +* channel struct so as to break pre-existing software. Note that you can +* usually add fields to the END of the channel struct withOUT needing to +* increment this. */ +#define ULTRA_DIAG_CHANNEL_PROTOCOL_VERSIONID 2 + +#define ULTRA_DIAG_CHANNEL_OK_CLIENT(pChannel, logCtx) \ + (ULTRA_check_channel_client(pChannel, \ + UltraDiagChannelProtocolGuid, \ + "diag", \ + sizeof(ULTRA_DIAG_CHANNEL_PROTOCOL), \ + ULTRA_DIAG_CHANNEL_PROTOCOL_VERSIONID, \ + ULTRA_DIAG_CHANNEL_PROTOCOL_SIGNATURE, \ + __FILE__, __LINE__, logCtx)) +#define ULTRA_DIAG_CHANNEL_OK_SERVER(actualBytes, logCtx) \ + (ULTRA_check_channel_server(UltraDiagChannelProtocolGuid, \ + "diag", \ + sizeof(ULTRA_DIAG_CHANNEL_PROTOCOL), \ + actualBytes, __FILE__, __LINE__, logCtx)) +#define MAX_MODULE_NAME_SIZE 128 /* Maximum length of module name... */ +#define MAX_ADDITIONAL_INFO_SIZE 256 /* Maximum length of any additional info + * accompanying event... */ +#define MAX_SUBSYSTEMS 64 /* Maximum number of subsystems allowed in + * DiagChannel... */ +#define LOW_SUBSYSTEMS 32 /* Half of MAX_SUBSYSTEMS to allow 64-bit + * math */ +#define SUBSYSTEM_DEBUG 0 /* Standard subsystem for debug events */ +#define SUBSYSTEM_DEFAULT 1 /* Default subsystem for legacy calls to + * ReportEvent */ + +/* few useful subsystem mask values */ +#define SUBSYSTEM_MASK_DEBUG 0x01 /* Standard subsystem for debug + * events */ +#define SUBSYSTEM_MASK_DEFAULT 0x02 /* Default subsystem for legacy calls to + * ReportEvents */ + +/* Event parameter "Severity" is overloaded with Cause in byte 2 and Severity in + * byte 0, bytes 1 and 3 are reserved */ +#define SEVERITY_MASK 0x0FF /* mask out all but the Severity in byte 0 */ +#define CAUSE_MASK 0x0FF0000 /* mask out all but the cause in byte 2 */ +#define CAUSE_SHIFT_AMT 16 /* shift 2 bytes to place it in byte 2 */ + +/* SubsystemSeverityFilter */ +#define SEVERITY_FILTER_MASK 0x0F /* mask out the Cause half, SeverityFilter is + * in the lower nibble */ +#define CAUSE_FILTER_MASK 0xF0 /* mask out the Severity half, CauseFilter is in + * the upper nibble */ +#define CAUSE_FILTER_SHIFT_AMT 4 /* shift amount to place it in lower or upper + * nibble */ + +/* Copied from EFI's EFI_TIME struct in efidef.h. EFI headers are not allowed +* in some of the Supervisor areas, such as Monitor, so it has been "ported" here +* for use in diagnostic event timestamps... */ +typedef struct _DIAG_EFI_TIME { + U16 Year; /* 1998 - 20XX */ + U8 Month; /* 1 - 12 */ + U8 Day; /* 1 - 31 */ + U8 Hour; /* 0 - 23 */ + U8 Minute; /* 0 - 59 */ + U8 Second; /* 0 - 59 */ + U8 Pad1; + U32 Nanosecond; /* 0 - 999, 999, 999 */ + S16 TimeZone; /* -1440 to 1440 or 2047 */ + U8 Daylight; + U8 Pad2; +} DIAG_EFI_TIME; + +typedef enum { + ULTRA_COMPONENT_GUEST = 0, + ULTRA_COMPONENT_MONITOR = 0x01, + ULTRA_COMPONENT_CCM = 0x02, /* Common Control module */ + /* RESERVED 0x03 - 0x7 */ + + /* Ultravisor Components */ + ULTRA_COMPONENT_BOOT = 0x08, + ULTRA_COMPONENT_IDLE = 0x09, + ULTRA_COMPONENT_CONTROL = 0x0A, + ULTRA_COMPONENT_LOGGER = 0x0B, + ULTRA_COMPONENT_ACPI = 0X0C, + /* RESERVED 0x0D - 0x0F */ + + /* sPAR Components */ + ULTRA_COMPONENT_COMMAND = 0x10, + ULTRA_COMPONENT_IODRIVER = 0x11, + ULTRA_COMPONENT_CONSOLE = 0x12, + ULTRA_COMPONENT_OPERATIONS = 0x13, + ULTRA_COMPONENT_MANAGEMENT = 0x14, + ULTRA_COMPONENT_DIAG = 0x15, + ULTRA_COMPONENT_HWDIAG = 0x16, + ULTRA_COMPONENT_PSERVICES = 0x17, + ULTRA_COMPONENT_PDIAG = 0x18 + /* RESERVED 0x18 - 0x1F */ +} ULTRA_COMPONENT_TYPES; + +/* Structure: DIAG_CHANNEL_EVENT Purpose: Contains attributes that make up an + * event to be written to the DIAG_CHANNEL memory. Attributes: EventId: Id of + * the diagnostic event to write to memory. Severity: Severity of the event + * (Error, Info, etc). ModuleName: Module/file name where event originated. + * LineNumber: Line number in module name where event originated. Timestamp: + * Date/time when event was received by ReportEvent, and written to DiagChannel. + * Reserved: Padding to align structure on a 64-byte cache line boundary. + * AdditionalInfo: Array of characters for additional event info (may be + * empty). */ +typedef struct _DIAG_CHANNEL_EVENT { + U32 EventId; + U32 Severity; + U8 ModuleName[MAX_MODULE_NAME_SIZE]; + U32 LineNumber; + DIAG_EFI_TIME Timestamp; /* Size = 16 bytes */ + U32 PartitionNumber; /* Filled in by Diag Switch as pool blocks are + * filled */ + U16 VirtualProcessorNumber; + U16 LogicalProcessorNumber; + U8 ComponentType; /* ULTRA_COMPONENT_TYPES */ + U8 Subsystem; + U16 Reserved0; /* pad to U64 alignment */ + U32 BlockNumber; /* filled in by DiagSwitch as pool blocks are + * filled */ + U32 BlockNumberHigh; + U32 EventNumber; /* filled in by DiagSwitch as pool blocks are + * filled */ + U32 EventNumberHigh; + + /* The BlockNumber and EventNumber fields are set only by DiagSwitch + * and referenced only by WinDiagDisplay formatting tool as + * additional diagnostic information. Other tools including + * WinDiagDisplay currently ignore these 'Reserved' bytes. */ + U8 Reserved[8]; + U8 AdditionalInfo[MAX_ADDITIONAL_INFO_SIZE]; + + /* NOTE: Changesto DIAG_CHANNEL_EVENT generally need to be reflected in + * existing copies * + * - for AppOS at + * GuestLinux/visordiag_early/supervisor_diagchannel.h * + * - for WinDiagDisplay at + * EFI/Ultra/Tools/WinDiagDisplay/WinDiagDisplay/diagstruct.h */ +} DIAG_CHANNEL_EVENT; + +/* Levels of severity for diagnostic events, in order from lowest severity to +* highest (i.e. fatal errors are the most severe, and should always be logged, +* but info events rarely need to be logged except during debugging). The values +* DIAG_SEVERITY_ENUM_BEGIN and DIAG_SEVERITY_ENUM_END are not valid severity +* values. They exist merely to dilineate the list, so that future additions +* won't require changes to the driver (i.e. when checking for out-of-range +* severities in SetSeverity). The values DIAG_SEVERITY_OVERRIDE and +* DIAG_SEVERITY_SHUTOFF are not valid severity values for logging events but +* they are valid for controlling the amount of event data. This enum is also +* defined in DotNet\sParFramework\ControlFramework\ControlFramework.cs. If a +* change is made to this enum, they should also be reflected in that file. */ +typedef enum { DIAG_SEVERITY_ENUM_BEGIN = 0, + DIAG_SEVERITY_OVERRIDE = DIAG_SEVERITY_ENUM_BEGIN, + DIAG_SEVERITY_VERBOSE = DIAG_SEVERITY_OVERRIDE, /* 0 */ + DIAG_SEVERITY_INFO = DIAG_SEVERITY_VERBOSE + 1, /* 1 */ + DIAG_SEVERITY_WARNING = DIAG_SEVERITY_INFO + 1, /* 2 */ + DIAG_SEVERITY_ERR = DIAG_SEVERITY_WARNING + 1, /* 3 */ + DIAG_SEVERITY_PRINT = DIAG_SEVERITY_ERR + 1, /* 4 */ + DIAG_SEVERITY_SHUTOFF = DIAG_SEVERITY_PRINT + 1, /* 5 */ + DIAG_SEVERITY_ENUM_END = DIAG_SEVERITY_SHUTOFF, /* 5 */ + DIAG_SEVERITY_NONFATAL_ERR = DIAG_SEVERITY_ERR, + DIAG_SEVERITY_FATAL_ERR = DIAG_SEVERITY_PRINT +} DIAG_SEVERITY; + +/* Event Cause enums +* +* Levels of cause for diagnostic events, in order from least to greatest cause +* Internal errors are most urgent since ideally they should never exist +* Invalid requests are preventable by avoiding invalid inputs +* Operations errors depend on environmental factors which may impact which +* requests are possible +* Manifest provides intermediate value to capture firmware and configuration +* version information +* Trace provides suplimental debug information in release firmware +* Unknown Log captures unclasified LogEvent calls. +* Debug is the least urgent since it provides suplimental debug information only +* in debug firmware +* Unknown Debug captures unclassified DebugEvent calls. +* This enum is also defined in +* DotNet\sParFramework\ControlFramework\ControlFramework.cs. +* If a change is made to this enum, they should also be reflected in that +* file. */ + + + +/* A cause value "DIAG_CAUSE_FILE_XFER" together with a severity value of +* "DIAG_SEVERITY_PRINT" (=4), is used for transferring text or binary file to +* the Diag partition. This cause-severity combination will be used by Logger +* DiagSwitch to segregate events into block types. The files are transferred in +* 256 byte chunks maximum, in the AdditionalInfo field of the DIAG_CHANNEL_EVENT +* structure. In the file transfer mode, some event fields will have different +* meaning: EventId specifies the file offset, severity specifies the block type, +* ModuleName specifies the filename, LineNumber specifies the number of valid +* data bytes in an event and AdditionalInfo contains up to 256 bytes of data. */ + +/* The Diag DiagWriter appends event blocks to events.raw as today, and for data + * blocks uses DIAG_CHANNEL_EVENT + * PartitionNumber to extract and append 'AdditionalInfo' to filename (specified + * by ModuleName). */ + +/* The Dell PDiag uses this new mechanism to stash DSET .zip onto the + * 'diagnostic' virtual disk. */ +typedef enum { + DIAG_CAUSE_UNKNOWN = 0, + DIAG_CAUSE_UNKNOWN_DEBUG = DIAG_CAUSE_UNKNOWN + 1, /* 1 */ + DIAG_CAUSE_DEBUG = DIAG_CAUSE_UNKNOWN_DEBUG + 1, /* 2 */ + DIAG_CAUSE_UNKNOWN_LOG = DIAG_CAUSE_DEBUG + 1, /* 3 */ + DIAG_CAUSE_TRACE = DIAG_CAUSE_UNKNOWN_LOG + 1, /* 4 */ + DIAG_CAUSE_MANIFEST = DIAG_CAUSE_TRACE + 1, /* 5 */ + DIAG_CAUSE_OPERATIONS_ERROR = DIAG_CAUSE_MANIFEST + 1, /* 6 */ + DIAG_CAUSE_INVALID_REQUEST = DIAG_CAUSE_OPERATIONS_ERROR + 1, /* 7 */ + DIAG_CAUSE_INTERNAL_ERROR = DIAG_CAUSE_INVALID_REQUEST + 1, /* 8 */ + DIAG_CAUSE_FILE_XFER = DIAG_CAUSE_INTERNAL_ERROR + 1, /* 9 */ + DIAG_CAUSE_ENUM_END = DIAG_CAUSE_FILE_XFER /* 9 */ +} DIAG_CAUSE; + +/* Event Cause category defined into the byte 2 of Severity */ +#define CAUSE_DEBUG (DIAG_CAUSE_DEBUG << CAUSE_SHIFT_AMT) +#define CAUSE_TRACE (DIAG_CAUSE_TRACE << CAUSE_SHIFT_AMT) +#define CAUSE_MANIFEST (DIAG_CAUSE_MANIFEST << CAUSE_SHIFT_AMT) +#define CAUSE_OPERATIONS_ERROR (DIAG_CAUSE_OPERATIONS_ERROR << CAUSE_SHIFT_AMT) +#define CAUSE_INVALID_REQUEST (DIAG_CAUSE_INVALID_REQUEST << CAUSE_SHIFT_AMT) +#define CAUSE_INTERNAL_ERROR (DIAG_CAUSE_INTERNAL_ERROR << CAUSE_SHIFT_AMT) +#define CAUSE_FILE_XFER (DIAG_CAUSE_FILE_XFER << CAUSE_SHIFT_AMT) +#define CAUSE_ENUM_END CAUSE_FILE_XFER + +/* Combine Cause and Severity categories into one */ +#define CAUSE_DEBUG_SEVERITY_VERBOSE \ + (CAUSE_DEBUG | DIAG_SEVERITY_VERBOSE) +#define CAUSE_TRACE_SEVERITY_VERBOSE \ + (CAUSE_TRACE | DIAG_SEVERITY_VERBOSE) +#define CAUSE_MANIFEST_SEVERITY_VERBOSE\ + (CAUSE_MANIFEST | DIAG_SEVERITY_VERBOSE) +#define CAUSE_OPERATIONS_SEVERITY_VERBOSE \ + (CAUSE_OPERATIONS_ERROR | DIAG_SEVERITY_VERBOSE) +#define CAUSE_INVALID_SEVERITY_VERBOSE \ + (CAUSE_INVALID_REQUEST | DIAG_SEVERITY_VERBOSE) +#define CAUSE_INTERNAL_SEVERITY_VERBOSE \ + (CAUSE_INTERNAL_ERROR | DIAG_SEVERITY_VERBOSE) + +#define CAUSE_DEBUG_SEVERITY_INFO \ + (CAUSE_DEBUG | DIAG_SEVERITY_INFO) +#define CAUSE_TRACE_SEVERITY_INFO \ + (CAUSE_TRACE | DIAG_SEVERITY_INFO) +#define CAUSE_MANIFEST_SEVERITY_INFO \ + (CAUSE_MANIFEST | DIAG_SEVERITY_INFO) +#define CAUSE_OPERATIONS_SEVERITY_INFO \ + (CAUSE_OPERATIONS_ERROR | DIAG_SEVERITY_INFO) +#define CAUSE_INVALID_SEVERITY_INFO \ + (CAUSE_INVALID_REQUEST | DIAG_SEVERITY_INFO) +#define CAUSE_INTERNAL_SEVERITY_INFO \ + (CAUSE_INTERNAL_ERROR | DIAG_SEVERITY_INFO) + +#define CAUSE_DEBUG_SEVERITY_WARN \ + (CAUSE_DEBUG | DIAG_SEVERITY_WARNING) +#define CAUSE_TRACE_SEVERITY_WARN \ + (CAUSE_TRACE | DIAG_SEVERITY_WARNING) +#define CAUSE_MANIFEST_SEVERITY_WARN \ + (CAUSE_MANIFEST | DIAG_SEVERITY_WARNING) +#define CAUSE_OPERATIONS_SEVERITY_WARN \ + (CAUSE_OPERATIONS_ERROR | DIAG_SEVERITY_WARNING) +#define CAUSE_INVALID_SEVERITY_WARN \ + (CAUSE_INVALID_REQUEST | DIAG_SEVERITY_WARNING) +#define CAUSE_INTERNAL_SEVERITY_WARN \ + (CAUSE_INTERNAL_ERROR | DIAG_SEVERITY_WARNING) + +#define CAUSE_DEBUG_SEVERITY_ERR \ + (CAUSE_DEBUG | DIAG_SEVERITY_ERR) +#define CAUSE_TRACE_SEVERITY_ERR \ + (CAUSE_TRACE | DIAG_SEVERITY_ERR) +#define CAUSE_MANIFEST_SEVERITY_ERR \ + (CAUSE_MANIFEST | DIAG_SEVERITY_ERR) +#define CAUSE_OPERATIONS_SEVERITY_ERR \ + (CAUSE_OPERATIONS_ERROR | DIAG_SEVERITY_ERR) +#define CAUSE_INVALID_SEVERITY_ERR \ + (CAUSE_INVALID_REQUEST | DIAG_SEVERITY_ERR) +#define CAUSE_INTERNAL_SEVERITY_ERR \ + (CAUSE_INTERNAL_ERROR | DIAG_SEVERITY_ERR) + +#define CAUSE_DEBUG_SEVERITY_PRINT \ + (CAUSE_DEBUG | DIAG_SEVERITY_PRINT) +#define CAUSE_TRACE_SEVERITY_PRINT \ + (CAUSE_TRACE | DIAG_SEVERITY_PRINT) +#define CAUSE_MANIFEST_SEVERITY_PRINT \ + (CAUSE_MANIFEST | DIAG_SEVERITY_PRINT) +#define CAUSE_OPERATIONS_SEVERITY_PRINT \ + (CAUSE_OPERATIONS_ERROR | DIAG_SEVERITY_PRINT) +#define CAUSE_INVALID_SEVERITY_PRINT \ + (CAUSE_INVALID_REQUEST | DIAG_SEVERITY_PRINT) +#define CAUSE_INTERNAL_SEVERITY_PRINT \ + (CAUSE_INTERNAL_ERROR | DIAG_SEVERITY_PRINT) +#define CAUSE_FILE_XFER_SEVERITY_PRINT \ + (CAUSE_FILE_XFER | DIAG_SEVERITY_PRINT) + +/* Structure: DIAG_CHANNEL_PROTOCOL_HEADER + * + * Purpose: Contains attributes that make up the header specific to the + * DIAG_CHANNEL area. + * + * Attributes: + * + * DiagLock: Diag Channel spinlock. + * + *IsChannelInitialized: 1 iff SignalInit was called for this channel; otherwise + * 0, and assume the channel is not ready for use yet. + * + * Reserved: Padding to align the fields in this structure. + * + *SubsystemSeverityFilter: Level of severity on a subsystem basis that controls + * whether events are logged. Any event's severity for a + * particular subsystem below this level will be discarded. + */ +typedef struct _DIAG_CHANNEL_PROTOCOL_HEADER { + volatile U32 DiagLock; + U8 IsChannelInitialized; + U8 Reserved[3]; + U8 SubsystemSeverityFilter[64]; +} DIAG_CHANNEL_PROTOCOL_HEADER; + +/* The Diagram for the Diagnostic Channel: */ +/* ----------------------- */ +/* | Channel Header | Defined by ULTRA_CHANNEL_PROTOCOL */ +/* ----------------------- */ +/* | Signal Queue Header | Defined by SIGNAL_QUEUE_HEADER */ +/* ----------------------- */ +/* | DiagChannel Header | Defined by DIAG_CHANNEL_PROTOCOL_HEADER */ +/* ----------------------- */ +/* | Channel Event Info | Defined by (DIAG_CHANNEL_EVENT * MAX_EVENTS) */ +/* ----------------------- */ +/* | Reserved | Reserved (pad out to 4MB) */ +/* ----------------------- */ + +/* Offsets/sizes for diagnostic channel attributes... */ +#define DIAG_CH_QUEUE_HEADER_OFFSET (sizeof(ULTRA_CHANNEL_PROTOCOL)) +#define DIAG_CH_QUEUE_HEADER_SIZE (sizeof(SIGNAL_QUEUE_HEADER)) +#define DIAG_CH_PROTOCOL_HEADER_OFFSET \ + (DIAG_CH_QUEUE_HEADER_OFFSET + DIAG_CH_QUEUE_HEADER_SIZE) +#define DIAG_CH_PROTOCOL_HEADER_SIZE (sizeof(DIAG_CHANNEL_PROTOCOL_HEADER)) +#define DIAG_CH_EVENT_OFFSET \ + (DIAG_CH_PROTOCOL_HEADER_OFFSET + DIAG_CH_PROTOCOL_HEADER_SIZE) +#define DIAG_CH_SIZE (4096 * 1024) + +/* For Control and Idle Partitions with larger (8 MB) diagnostic(root) + * channels */ +#define DIAG_CH_LRG_SIZE (2 * DIAG_CH_SIZE) /* 8 MB */ + +/* + * Structure: ULTRA_DIAG_CHANNEL_PROTOCOL + * + * Purpose: Contains attributes that make up the DIAG_CHANNEL memory. + * + * Attributes: + * + * CommonChannelHeader: Header info common to all channels. + * + * QueueHeader: Queue header common to all channels - used to determine where to + * store event. + * + * DiagChannelHeader: Diagnostic channel header info (see + * DIAG_CHANNEL_PROTOCOL_HEADER comments). + * + * Events: Area where diagnostic events (up to MAX_EVENTS) are written. + * + *Reserved: Reserved area to allow for correct channel size padding. +*/ +typedef struct _ULTRA_DIAG_CHANNEL_PROTOCOL { + ULTRA_CHANNEL_PROTOCOL CommonChannelHeader; + SIGNAL_QUEUE_HEADER QueueHeader; + DIAG_CHANNEL_PROTOCOL_HEADER DiagChannelHeader; + DIAG_CHANNEL_EVENT Events[(DIAG_CH_SIZE - DIAG_CH_EVENT_OFFSET) / + sizeof(DIAG_CHANNEL_EVENT)]; +} +ULTRA_DIAG_CHANNEL_PROTOCOL; + +#endif diff --git a/drivers/staging/unisys/common-spar/include/channels/iochannel.h b/drivers/staging/unisys/common-spar/include/channels/iochannel.h new file mode 100644 index 00000000000..6dcfa6e7837 --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/channels/iochannel.h @@ -0,0 +1,935 @@ +/* Copyright (C) 2010 - 2013 UNISYS CORPORATION */ +/* All rights reserved. */ +#ifndef __IOCHANNEL_H__ +#define __IOCHANNEL_H__ + +/* +* Everything needed for IOPart-GuestPart communication is define in +* this file. Note: Everything is OS-independent because this file is +* used by Windows, Linux and possible EFI drivers. */ + + +/* +* Communication flow between the IOPart and GuestPart uses the channel headers +* channel state. The following states are currently being used: +* UNINIT(All Zeroes), CHANNEL_ATTACHING, CHANNEL_ATTACHED, CHANNEL_OPENED +* +* additional states will be used later. No locking is needed to switch between +* states due to the following rules: +* +* 1. IOPart is only the only partition allowed to change from UNIT +* 2. IOPart is only the only partition allowed to change from +* CHANNEL_ATTACHING +* 3. GuestPart is only the only partition allowed to change from +* CHANNEL_ATTACHED +* +* The state changes are the following: IOPart sees the channel is in UNINIT, +* UNINIT -> CHANNEL_ATTACHING (performed only by IOPart) +* CHANNEL_ATTACHING -> CHANNEL_ATTACHED (performed only by IOPart) +* CHANNEL_ATTACHED -> CHANNEL_OPENED (performed only by GuestPart) +*/ + +#include <linux/uuid.h> + +#include "commontypes.h" +#include "vmcallinterface.h" + +#define _ULTRA_CONTROLVM_CHANNEL_INLINE_ +#include <linux/dma-direction.h> +#include "controlvmchannel.h" +#include "vbuschannel.h" +#undef _ULTRA_CONTROLVM_CHANNEL_INLINE_ +#include "channel.h" + +/* + * CHANNEL Guids + */ + +#include "channel_guid.h" + +#define ULTRA_VHBA_CHANNEL_PROTOCOL_SIGNATURE ULTRA_CHANNEL_PROTOCOL_SIGNATURE +#define ULTRA_VNIC_CHANNEL_PROTOCOL_SIGNATURE ULTRA_CHANNEL_PROTOCOL_SIGNATURE +#define ULTRA_VSWITCH_CHANNEL_PROTOCOL_SIGNATURE \ + ULTRA_CHANNEL_PROTOCOL_SIGNATURE + +/* Must increment these whenever you insert or delete fields within this channel +* struct. Also increment whenever you change the meaning of fields within this +* channel struct so as to break pre-existing software. Note that you can +* usually add fields to the END of the channel struct withOUT needing to +* increment this. */ +#define ULTRA_VHBA_CHANNEL_PROTOCOL_VERSIONID 2 +#define ULTRA_VNIC_CHANNEL_PROTOCOL_VERSIONID 2 +#define ULTRA_VSWITCH_CHANNEL_PROTOCOL_VERSIONID 1 + +#define ULTRA_VHBA_CHANNEL_OK_CLIENT(pChannel, logCtx) \ + (ULTRA_check_channel_client(pChannel, UltraVhbaChannelProtocolGuid, \ + "vhba", MIN_IO_CHANNEL_SIZE, \ + ULTRA_VHBA_CHANNEL_PROTOCOL_VERSIONID, \ + ULTRA_VHBA_CHANNEL_PROTOCOL_SIGNATURE, \ + __FILE__, __LINE__, logCtx)) +#define ULTRA_VHBA_CHANNEL_OK_SERVER(actualBytes, logCtx) \ + (ULTRA_check_channel_server(UltraVhbaChannelProtocolGuid, \ + "vhba", MIN_IO_CHANNEL_SIZE, actualBytes, \ + __FILE__, __LINE__, logCtx)) +#define ULTRA_VNIC_CHANNEL_OK_CLIENT(pChannel, logCtx) \ + (ULTRA_check_channel_client(pChannel, UltraVnicChannelProtocolGuid, \ + "vnic", MIN_IO_CHANNEL_SIZE, \ + ULTRA_VNIC_CHANNEL_PROTOCOL_VERSIONID, \ + ULTRA_VNIC_CHANNEL_PROTOCOL_SIGNATURE, \ + __FILE__, __LINE__, logCtx)) +#define ULTRA_VNIC_CHANNEL_OK_SERVER(actualBytes, logCtx) \ + (ULTRA_check_channel_server(UltraVnicChannelProtocolGuid, \ + "vnic", MIN_IO_CHANNEL_SIZE, actualBytes, \ + __FILE__, __LINE__, logCtx)) +#define ULTRA_VSWITCH_CHANNEL_OK_CLIENT(pChannel, logCtx) \ + (ULTRA_check_channel_client(pChannel, UltraVswitchChannelProtocolGuid, \ + "vswitch", MIN_IO_CHANNEL_SIZE, \ + ULTRA_VSWITCH_CHANNEL_PROTOCOL_VERSIONID, \ + ULTRA_VSWITCH_CHANNEL_PROTOCOL_SIGNATURE, \ + __FILE__, __LINE__, logCtx)) +#define ULTRA_VSWITCH_CHANNEL_OK_SERVER(actualBytes, logCtx) \ + (ULTRA_check_channel_server(UltraVswitchChannelProtocolGuid, \ + "vswitch", MIN_IO_CHANNEL_SIZE, \ + actualBytes, \ + __FILE__, __LINE__, logCtx)) +/* +* Everything necessary to handle SCSI & NIC traffic between Guest Partition and +* IO Partition is defined below. */ + + +/* +* Defines and enums. +*/ + +#define MINNUM(a, b) (((a) < (b)) ? (a) : (b)) +#define MAXNUM(a, b) (((a) > (b)) ? (a) : (b)) + +/* these define the two queues per data channel between iopart and + * ioguestparts */ +#define IOCHAN_TO_IOPART 0 /* used by ioguestpart to 'insert' signals to + * iopart */ +#define IOCHAN_FROM_GUESTPART 0 /* used by iopart to 'remove' signals from + * ioguestpart - same queue as previous queue */ + +#define IOCHAN_TO_GUESTPART 1 /* used by iopart to 'insert' signals to + * ioguestpart */ +#define IOCHAN_FROM_IOPART 1 /* used by ioguestpart to 'remove' signals from + * iopart - same queue as previous queue */ + +/* these define the two queues per control channel between controlpart and "its" + * guests, which includes the iopart */ +#define CTRLCHAN_TO_CTRLGUESTPART 0 /* used by ctrlguestpart to 'insert' signals + * to ctrlpart */ +#define CTLRCHAN_FROM_CTRLPART 0 /* used by ctrlpart to 'remove' signals from + * ctrlquestpart - same queue as previous + * queue */ + +#define CTRLCHAN_TO_CTRLPART 1 /* used by ctrlpart to 'insert' signals to + * ctrlguestpart */ +#define CTRLCHAN_FROM_CTRLGUESTPART 1 /* used by ctrguestpart to 'remove' + * signals from ctrlpart - same queue as + * previous queue */ + +/* these define the Event & Ack queues per control channel Events are generated +* by CTRLGUESTPART and sent to CTRLPART; Acks are generated by CTRLPART and sent +* to CTRLGUESTPART. */ +#define CTRLCHAN_EVENT_TO_CTRLPART 2 /* used by ctrlguestpart to 'insert' Events + * to ctrlpart */ +#define CTRLCHAN_EVENT_FROM_CTRLGUESTPART 2 /* used by ctrlpart to 'remove' + * Events from ctrlguestpart */ + +#define CTRLCHAN_ACK_TO_CTRLGUESTPART 3 /* used by ctrlpart to 'insert' Acks to + * ctrlguestpart */ +#define CTRLCHAN_ACK_FROM_CTRLPART 3 /* used by ctrlguestpart to 'remove' Events + * from ctrlpart */ + +/* size of cdb - i.e., scsi cmnd */ +#define MAX_CMND_SIZE 16 + +#define MAX_SENSE_SIZE 64 + +#define MAX_PHYS_INFO 64 + +/* Because GuestToGuestCopy is limited to 4KiB segments, and we have limited the +* Emulex Driver to 256 scatter list segments via the lpfc_sg_seg_cnt parameter +* to 256, the maximum I/O size is limited to 256 * 4 KiB = 1 MB */ +#define MAX_IO_SIZE (1024*1024) /* 1 MB */ + +/* NOTE 1: lpfc defines its support for segments in +* #define LPFC_SG_SEG_CNT 64 +* +* NOTE 2: In Linux, frags array in skb is currently allocated to be +* MAX_SKB_FRAGS size, which is 18 which is smaller than MAX_PHYS_INFO for +* now. */ + +#ifndef MAX_SERIAL_NUM +#define MAX_SERIAL_NUM 32 +#endif /* MAX_SERIAL_NUM */ + +#define MAX_SCSI_BUSES 1 +#define MAX_SCSI_TARGETS 8 +#define MAX_SCSI_LUNS 16 +#define MAX_SCSI_FROM_HOST 0xFFFFFFFF /* Indicator to use Physical HBA + * SCSI Host value */ + +/* various types of network packets that can be sent in cmdrsp */ +typedef enum { NET_RCV_POST = 0, /* submit buffer to hold receiving + * incoming packet */ + /* virtnic -> uisnic */ + NET_RCV, /* incoming packet received */ + /* uisnic -> virtpci */ + NET_XMIT, /* for outgoing net packets */ + /* virtnic -> uisnic */ + NET_XMIT_DONE, /* outgoing packet xmitted */ + /* uisnic -> virtpci */ + NET_RCV_ENBDIS, /* enable/disable packet reception */ + /* virtnic -> uisnic */ + NET_RCV_ENBDIS_ACK, /* acknowledge enable/disable packet + * reception */ + /* uisnic -> virtnic */ + NET_RCV_PROMISC, /* enable/disable promiscuous mode */ + /* virtnic -> uisnic */ + NET_CONNECT_STATUS, /* indicate the loss or restoration of a network + * connection */ + /* uisnic -> virtnic */ + NET_MACADDR, /* indicates the client has requested to update + * its MAC addr */ + NET_MACADDR_ACK, /* MAC address */ + +} NET_TYPES; + +#define ETH_HEADER_SIZE 14 /* size of ethernet header */ + +#define ETH_MIN_DATA_SIZE 46 /* minimum eth data size */ +#define ETH_MIN_PACKET_SIZE (ETH_HEADER_SIZE + ETH_MIN_DATA_SIZE) + +#define ETH_DEF_DATA_SIZE 1500 /* default data size */ +#define ETH_DEF_PACKET_SIZE (ETH_HEADER_SIZE + ETH_DEF_DATA_SIZE) + +#define ETH_MAX_MTU 16384 /* maximum data size */ + +#ifndef MAX_MACADDR_LEN +#define MAX_MACADDR_LEN 6 /* number of bytes in MAC address */ +#endif /* MAX_MACADDR_LEN */ + +#define ETH_IS_LOCALLY_ADMINISTERED(Address) \ + (((U8 *) (Address))[0] & ((U8) 0x02)) +#define NIC_VENDOR_ID 0x0008000B + +/* various types of scsi task mgmt commands */ +typedef enum { TASK_MGMT_ABORT_TASK = + 1, TASK_MGMT_BUS_RESET, TASK_MGMT_LUN_RESET, + TASK_MGMT_TARGET_RESET, +} TASK_MGMT_TYPES; + +/* various types of vdisk mgmt commands */ +typedef enum { VDISK_MGMT_ACQUIRE = 1, VDISK_MGMT_RELEASE, +} VDISK_MGMT_TYPES; + +/* this is used in the vdest field */ +#define VDEST_ALL 0xFFFF + +#define MIN_NUMSIGNALS 64 +#define MAX_NUMSIGNALS 4096 + +/* MAX_NET_RCV_BUF specifies the number of rcv buffers that are created by each +* guest's virtnic and posted to uisnic. Uisnic, for each channel, keeps the rcv +* buffers posted and uses them to receive data on behalf of the guest's virtnic. +* NOTE: the num_rcv_bufs is configurable for each VNIC. So the following is +* simply an upperlimit on what each VNIC can provide. Setting it to half of the +* NUMSIGNALS to prevent queue full deadlocks */ +#define MAX_NET_RCV_BUFS (MIN_NUMSIGNALS / 2) + +/* + * structs with pragma pack */ + + +/* ///////////// BEGIN PRAGMA PACK PUSH 1 ///////////////////////// */ +/* ///////////// ONLY STRUCT TYPE SHOULD BE BELOW */ + +#pragma pack(push, 1) + +struct guest_phys_info { + U64 address; + U64 length; +}; + +#define GPI_ENTRIES_PER_PAGE (PAGE_SIZE / sizeof(struct guest_phys_info)) + +struct uisscsi_dest { + U32 channel; /* channel == bus number */ + U32 id; /* id == target number */ + U32 lun; /* lun == logical unit number */ +}; + +struct vhba_wwnn { + U32 wwnn1; + U32 wwnn2; +}; + +/* WARNING: Values stired in this structure must contain maximum counts (not + * maximum values). */ +struct vhba_config_max { /* 20 bytes */ + U32 max_channel; /* maximum channel for devices attached to this + * bus */ + U32 max_id; /* maximum SCSI ID for devices attached to this + * bus */ + U32 max_lun; /* maximum SCSI LUN for devices attached to this + * bus */ + U32 cmd_per_lun; /* maximum number of outstanding commands per + * lun that are allowed at one time */ + U32 max_io_size; /* maximum io size for devices attached to this + * bus */ + /* max io size is often determined by the resource of the hba. e.g */ + /* max scatter gather list length * page size / sector size */ +}; + +struct uiscmdrsp_scsi { + void *scsicmd; /* the handle to the cmd that was received - + * send it back as is in the rsp packet. */ + U8 cmnd[MAX_CMND_SIZE]; /* the cdb for the command */ + U32 bufflen; /* length of data to be transferred out or in */ + U16 guest_phys_entries; /* Number of entries in scatter-gather (sg) + * list */ + struct guest_phys_info gpi_list[MAX_PHYS_INFO]; /* physical address + * information for each + * fragment */ + enum dma_data_direction data_dir; /* direction of the data, if any */ + struct uisscsi_dest vdest; /* identifies the virtual hba, id, + * channel, lun to which cmd was sent */ + + /* the following fields are needed to queue the rsp back to cmd + * originator */ + int linuxstat; /* the original Linux status - for use by linux + * vdisk code */ + U8 scsistat; /* the scsi status */ + U8 addlstat; /* non-scsi status - covers cases like timeout + * needed by windows guests */ +#define ADDL_RESET 1 +#define ADDL_TIMEOUT 2 +#define ADDL_INTERNAL_ERROR 3 +#define ADDL_SEL_TIMEOUT 4 +#define ADDL_CMD_TIMEOUT 5 +#define ADDL_BAD_TARGET 6 +#define ADDL_RETRY 7 + + /* the following fields are need to determine the result of command */ + U8 sensebuf[MAX_SENSE_SIZE]; /* sense info in case cmd failed; */ + /* it holds the sense_data struct; */ + /* see that struct for details. */ + void *vdisk; /* contains pointer to the vdisk so that we can clean up + * when the IO completes. */ + int no_disk_result; /* used to return no disk inquiry result */ + /* when no_disk_result is set to 1, */ + /* scsi.scsistat is SAM_STAT_GOOD */ + /* scsi.addlstat is 0 */ + /* scsi.linuxstat is SAM_STAT_GOOD */ + /* That is, there is NO error. */ +}; + +/* +* Defines to support sending correct inquiry result when no disk is +* configured. */ + +/* From SCSI SPC2 - + * + * If the target is not capable of supporting a device on this logical unit, the + * device server shall set this field to 7Fh (PERIPHERAL QUALIFIER set to 011b + * and PERIPHERAL DEVICE TYPE set to 1Fh). + * + *The device server is capable of supporting the specified peripheral device + *type on this logical unit. However, the physical device is not currently + *connected to this logical unit. + */ + +#define DEV_NOT_PRESENT 0x7f /* old name - compatibility */ +#define DEV_NOT_CAPABLE 0x7f /* peripheral qualifier of 0x3 */ + /* peripheral type of 0x1f */ + /* specifies no device but target present */ + +#define DEV_DISK_CAPABLE_NOT_PRESENT 0x20 /* peripheral qualifier of 0x1 */ + /* peripheral type of 0 - disk */ + /* specifies device capable, but not present */ + +#define DEV_PROC_CAPABLE_NOT_PRESENT 0x23 /* peripheral qualifier of 0x1 */ + /* peripheral type of 3 - processor */ + /* specifies device capable, but not present */ + +#define DEV_HISUPPORT 0x10; /* HiSup = 1; shows support for report luns */ + /* must be returned for lun 0. */ + +/* NOTE: Linux code assumes inquiry contains 36 bytes. Without checking length +* in buf[4] some linux code accesses bytes beyond 5 to retrieve vendor, product +* & revision. Yikes! So let us always send back 36 bytes, the minimum for +* inquiry result. */ +#define NO_DISK_INQUIRY_RESULT_LEN 36 + +#define MIN_INQUIRY_RESULT_LEN 5 /* we need at least 5 bytes minimum for inquiry + * result */ + +/* SCSI device version for no disk inquiry result */ +#define SCSI_SPC2_VER 4 /* indicates SCSI SPC2 (SPC3 is 5) */ + +/* Windows and Linux want different things for a non-existent lun. So, we'll let + * caller pass in the peripheral qualifier and type. + * NOTE:[4] SCSI returns (n-4); so we return length-1-4 or length-5. */ + +#define SET_NO_DISK_INQUIRY_RESULT(buf, len, lun, lun0notpresent, notpresent) \ + do { \ + MEMSET(buf, 0, \ + MINNUM(len, \ + (unsigned int) NO_DISK_INQUIRY_RESULT_LEN)); \ + buf[2] = (U8) SCSI_SPC2_VER; \ + if (lun == 0) { \ + buf[0] = (U8) lun0notpresent; \ + buf[3] = (U8) DEV_HISUPPORT; \ + } else \ + buf[0] = (U8) notpresent; \ + buf[4] = (U8) ( \ + MINNUM(len, \ + (unsigned int) NO_DISK_INQUIRY_RESULT_LEN) - 5); \ + if (len >= NO_DISK_INQUIRY_RESULT_LEN) { \ + buf[8] = 'D'; \ + buf[9] = 'E'; \ + buf[10] = 'L'; \ + buf[11] = 'L'; \ + buf[16] = 'P'; \ + buf[17] = 'S'; \ + buf[18] = 'E'; \ + buf[19] = 'U'; \ + buf[20] = 'D'; \ + buf[21] = 'O'; \ + buf[22] = ' '; \ + buf[23] = 'D'; \ + buf[24] = 'E'; \ + buf[25] = 'V'; \ + buf[26] = 'I'; \ + buf[27] = 'C'; \ + buf[28] = 'E'; \ + buf[30] = ' '; \ + buf[31] = '.'; \ + } \ + } while (0) + + +/* +* Struct & Defines to support sense information. +*/ + + +/* The following struct is returned in sensebuf field in uiscmdrsp_scsi. It is +* initialized in exactly the manner that is recommended in Windows (hence the +* odd values). +* When set, these fields will have the following values: +* ErrorCode = 0x70 indicates current error +* Valid = 1 indicates sense info is valid +* SenseKey contains sense key as defined by SCSI specs. +* AdditionalSenseCode contains sense key as defined by SCSI specs. +* AdditionalSenseCodeQualifier contains qualifier to sense code as defined by +* scsi docs. +* AdditionalSenseLength contains will be sizeof(sense_data)-8=10. +*/ +struct sense_data { + U8 ErrorCode:7; + U8 Valid:1; + U8 SegmentNumber; + U8 SenseKey:4; + U8 Reserved:1; + U8 IncorrectLength:1; + U8 EndOfMedia:1; + U8 FileMark:1; + U8 Information[4]; + U8 AdditionalSenseLength; + U8 CommandSpecificInformation[4]; + U8 AdditionalSenseCode; + U8 AdditionalSenseCodeQualifier; + U8 FieldReplaceableUnitCode; + U8 SenseKeySpecific[3]; +}; + +/* some SCSI ADSENSE codes */ +#ifndef SCSI_ADSENSE_LUN_NOT_READY +#define SCSI_ADSENSE_LUN_NOT_READY 0x04 +#endif /* */ +#ifndef SCSI_ADSENSE_ILLEGAL_COMMAND +#define SCSI_ADSENSE_ILLEGAL_COMMAND 0x20 +#endif /* */ +#ifndef SCSI_ADSENSE_ILLEGAL_BLOCK +#endif /* */ +#ifndef SCSI_ADSENSE_ILLEGAL_BLOCK +#define SCSI_ADSENSE_ILLEGAL_BLOCK 0x21 +#endif /* */ +#ifndef SCSI_ADSENSE_INVALID_CDB +#define SCSI_ADSENSE_INVALID_CDB 0x24 +#endif /* */ +#ifndef SCSI_ADSENSE_INVALID_LUN +#define SCSI_ADSENSE_INVALID_LUN 0x25 +#endif /* */ +#ifndef SCSI_ADWRITE_PROTECT +#define SCSI_ADWRITE_PROTECT 0x27 +#endif /* */ +#ifndef SCSI_ADSENSE_MEDIUM_CHANGED +#define SCSI_ADSENSE_MEDIUM_CHANGED 0x28 +#endif /* */ +#ifndef SCSI_ADSENSE_BUS_RESET +#define SCSI_ADSENSE_BUS_RESET 0x29 +#endif /* */ +#ifndef SCSI_ADSENSE_NO_MEDIA_IN_DEVICE +#define SCSI_ADSENSE_NO_MEDIA_IN_DEVICE 0x3a +#endif /* */ + +struct net_pkt_xmt { + int len; /* full length of data in the packet */ + int num_frags; /* number of fragments in frags containing data */ + struct phys_info frags[MAX_PHYS_INFO]; /* physical page information for + * each fragment */ + char ethhdr[ETH_HEADER_SIZE]; /* the ethernet header */ + struct { + + /* these are needed for csum at uisnic end */ + U8 valid; /* 1 = rest of this struct is valid - else + * ignore */ + U8 hrawoffv; /* 1 = hwrafoff is valid */ + U8 nhrawoffv; /* 1 = nhwrafoff is valid */ + U16 protocol; /* specifies packet protocol */ + U32 csum; /* value used to set skb->csum at IOPart */ + U32 hrawoff; /* value used to set skb->h.raw at IOPart */ + /* hrawoff points to the start of the TRANSPORT LAYER HEADER */ + U32 nhrawoff; /* value used to set skb->nh.raw at IOPart */ + /* nhrawoff points to the start of the NETWORK LAYER HEADER */ + } lincsum; + + /* **** NOTE **** + * The full packet is described in frags but the ethernet header is + * separately kept in ethhdr so that uisnic doesn't have "MAP" the + * guest memory to get to the header. uisnic needs ethhdr to + * determine how to route the packet. + */ +}; + +struct net_pkt_xmtdone { + U32 xmt_done_result; /* result of NET_XMIT */ +#define XMIT_SUCCESS 0 +#define XMIT_FAILED 1 +}; + +/* RCVPOST_BUF_SIZe must be at most page_size(4096) - cache_line_size (64) The +* reason is because dev_skb_alloc which is used to generate RCV_POST skbs in +* virtnic requires that there is "overhead" in the buffer, and pads 16 bytes. I +* prefer to use 1 full cache line size for "overhead" so that transfers are +* better. IOVM requires that a buffer be represented by 1 phys_info structure +* which can only cover page_size. */ +#define RCVPOST_BUF_SIZE 4032 +#define MAX_NET_RCV_CHAIN \ + ((ETH_MAX_MTU+ETH_HEADER_SIZE + RCVPOST_BUF_SIZE-1) / RCVPOST_BUF_SIZE) + +struct net_pkt_rcvpost { + /* rcv buf size must be large enough to include ethernet data len + + * ethernet header len - we are choosing 2K because it is guaranteed + * to be describable */ + struct phys_info frag; /* physical page information for the + * single fragment 2K rcv buf */ + U64 UniqueNum; /* This is used to make sure that + * receive posts are returned to */ + /* the Adapter which sent them origonally. */ +}; + +struct net_pkt_rcv { + + /* the number of receive buffers that can be chained */ + /* is based on max mtu and size of each rcv buf */ + U32 rcv_done_len; /* length of received data */ + U8 numrcvbufs; /* number of receive buffers that contain the */ + /* incoming data; guest end MUST chain these together. */ + void *rcvbuf[MAX_NET_RCV_CHAIN]; /* the list of receive buffers + * that must be chained; */ + /* each entry is a receive buffer provided by NET_RCV_POST. */ + /* NOTE: first rcvbuf in the chain will also be provided in net.buf. */ + U64 UniqueNum; + U32 RcvsDroppedDelta; +}; + +struct net_pkt_enbdis { + void *context; + U16 enable; /* 1 = enable, 0 = disable */ +}; + +struct net_pkt_macaddr { + void *context; + U8 macaddr[MAX_MACADDR_LEN]; /* 6 bytes */ +}; + +/* cmd rsp packet used for VNIC network traffic */ +struct uiscmdrsp_net { + NET_TYPES type; + void *buf; + union { + struct net_pkt_xmt xmt; /* used for NET_XMIT */ + struct net_pkt_xmtdone xmtdone; /* used for NET_XMIT_DONE */ + struct net_pkt_rcvpost rcvpost; /* used for NET_RCV_POST */ + struct net_pkt_rcv rcv; /* used for NET_RCV */ + struct net_pkt_enbdis enbdis; /* used for NET_RCV_ENBDIS, */ + /* NET_RCV_ENBDIS_ACK, */ + /* NET_RCV_PROMSIC, */ + /* and NET_CONNECT_STATUS */ + struct net_pkt_macaddr macaddr; + }; +}; + +struct uiscmdrsp_scsitaskmgmt { + TASK_MGMT_TYPES tasktype; + + /* the type of task */ + struct uisscsi_dest vdest; + + /* the vdisk for which this task mgmt is generated */ + void *scsicmd; + + /* This is some handle that the guest has saved off for its own use. + * Its value is preserved by iopart & returned as is in the task mgmt + * rsp. */ + void *notify; + + /* For linux guests, this is a pointer to wait_queue_head that a + * thread is waiting on to see if the taskmgmt command has completed. + * For windows guests, this is a pointer to a location that a waiting + * thread is testing to see if the taskmgmt command has completed. + * When the rsp is received by guest, the thread receiving the + * response uses this to notify the the thread waiting for taskmgmt + * command completion. Its value is preserved by iopart & returned + * as is in the task mgmt rsp. */ + void *notifyresult; + + /* this is a handle to location in guest where the result of the + * taskmgmt command (result field) is to saved off when the response + * is handled. Its value is preserved by iopart & returned as is in + * the task mgmt rsp. */ + char result; + + /* result of taskmgmt command - set by IOPart - values are: */ +#define TASK_MGMT_FAILED 0 +#define TASK_MGMT_SUCCESS 1 +}; + +/* The following is used by uissd to send disk add/remove notifications to + * Guest */ +/* Note that the vHba pointer is not used by the Client/Guest side. */ +struct uiscmdrsp_disknotify { + U8 add; /* 0-remove, 1-add */ + void *vHba; /* Pointer to vhba_info for channel info to + * route msg */ + U32 channel, id, lun; /* SCSI Path of Disk to added or removed */ +}; + +/* The following is used by virthba/vSCSI to send the Acquire/Release commands +* to the IOVM. */ +struct uiscmdrsp_vdiskmgmt { + VDISK_MGMT_TYPES vdisktype; + + /* the type of task */ + struct uisscsi_dest vdest; + + /* the vdisk for which this task mgmt is generated */ + void *scsicmd; + + /* This is some handle that the guest has saved off for its own use. + * Its value is preserved by iopart & returned as is in the task mgmt + * rsp. */ + void *notify; + + /* For linux guests, this is a pointer to wait_queue_head that a + * thread is waiting on to see if the taskmgmt command has completed. + * For windows guests, this is a pointer to a location that a waiting + * thread is testing to see if the taskmgmt command has completed. + * When the rsp is received by guest, the thread receiving the + * response uses this to notify the the thread waiting for taskmgmt + * command completion. Its value is preserved by iopart & returned + * as is in the task mgmt rsp. */ + void *notifyresult; + + /* this is a handle to location in guest where the result of the + * taskmgmt command (result field) is to saved off when the response + * is handled. Its value is preserved by iopart & returned as is in + * the task mgmt rsp. */ + char result; + + /* result of taskmgmt command - set by IOPart - values are: */ +#define VDISK_MGMT_FAILED 0 +#define VDISK_MGMT_SUCCESS 1 +}; + +/* keeping cmd & rsp info in one structure for now cmd rsp packet for scsi */ +struct uiscmdrsp { + char cmdtype; + + /* describes what type of information is in the struct */ +#define CMD_SCSI_TYPE 1 +#define CMD_NET_TYPE 2 +#define CMD_SCSITASKMGMT_TYPE 3 +#define CMD_NOTIFYGUEST_TYPE 4 +#define CMD_VDISKMGMT_TYPE 5 + union { + struct uiscmdrsp_scsi scsi; + struct uiscmdrsp_net net; + struct uiscmdrsp_scsitaskmgmt scsitaskmgmt; + struct uiscmdrsp_disknotify disknotify; + struct uiscmdrsp_vdiskmgmt vdiskmgmt; + }; + void *private_data; /* used to send the response when the cmd is + * done (scsi & scsittaskmgmt). */ + struct uiscmdrsp *next; /* General Purpose Queue Link */ + struct uiscmdrsp *activeQ_next; /* Used to track active commands */ + struct uiscmdrsp *activeQ_prev; /* Used to track active commands */ +}; + +/* This is just the header of the IO channel. It is assumed that directly after +* this header there is a large region of memory which contains the command and +* response queues as specified in cmdQ and rspQ SIGNAL_QUEUE_HEADERS. */ +typedef struct _ULTRA_IO_CHANNEL_PROTOCOL { + CHANNEL_HEADER ChannelHeader; + SIGNAL_QUEUE_HEADER cmdQ; + SIGNAL_QUEUE_HEADER rspQ; + union { + struct { + struct vhba_wwnn wwnn; /* 8 bytes */ + struct vhba_config_max max; /* 20 bytes */ + } vhba; /* 28 */ + struct { + U8 macaddr[MAX_MACADDR_LEN]; /* 6 bytes */ + U32 num_rcv_bufs; /* 4 */ + U32 mtu; /* 4 */ + uuid_le zoneGuid; /* 16 */ + } vnic; /* total 30 */ + }; + +#define MAX_CLIENTSTRING_LEN 1024 + U8 clientString[MAX_CLIENTSTRING_LEN]; /* NULL terminated - so holds + * max - 1 bytes */ +} ULTRA_IO_CHANNEL_PROTOCOL; + +#pragma pack(pop) +/* ///////////// END PRAGMA PACK PUSH 1 /////////////////////////// */ + +/* define offsets to members of struct uiscmdrsp */ +#define OFFSET_CMDTYPE OFFSETOF(struct uiscmdrsp, cmdtype) +#define OFFSET_SCSI OFFSETOF(struct uiscmdrsp, scsi) +#define OFFSET_NET OFFSETOF(struct uiscmdrsp, net) +#define OFFSET_SCSITASKMGMT OFFSETOF(struct uiscmdrsp, scsitaskmgmt) +#define OFFSET_NEXT OFFSETOF(struct uiscmdrsp, next) + +/* define offsets to members of struct uiscmdrsp_net */ +#define OFFSET_TYPE OFFSETOF(struct uiscmdrsp_net, type) +#define OFFSET_BUF OFFSETOF(struct uiscmdrsp_net, buf) +#define OFFSET_XMT OFFSETOF(struct uiscmdrsp_net, xmt) +#define OFFSET_XMT_DONE_RESULT OFFSETOF(struct uiscmdrsp_net, xmtdone) +#define OFFSET_RCVPOST OFFSETOF(struct uiscmdrsp_net, rcvpost) +#define OFFSET_RCV_DONE_LEN OFFSETOF(struct uiscmdrsp_net, rcv) +#define OFFSET_ENBDIS OFFSETOF(struct uiscmdrsp_net, enbdis) + +/* define offsets to members of struct net_pkt_rcvpost */ +#define OFFSET_TOTALLEN OFFSETOF(struct net_pkt_rcvpost, totallen) +#define OFFSET_FRAG OFFSETOF(struct net_pkt_rcvpost, frag) + +/* +* INLINE functions for initializing and accessing I/O data channels +*/ + + +#define NUMSIGNALS(x, q) (((ULTRA_IO_CHANNEL_PROTOCOL *)(x))->q.MaxSignalSlots) +#define SIZEOF_PROTOCOL (COVER(sizeof(ULTRA_IO_CHANNEL_PROTOCOL), 64)) +#define SIZEOF_CMDRSP (COVER(sizeof(struct uiscmdrsp), 64)) + +#define IO_CHANNEL_SIZE(x) COVER(SIZEOF_PROTOCOL + \ + (NUMSIGNALS(x, cmdQ) + \ + NUMSIGNALS(x, rspQ)) * SIZEOF_CMDRSP, 4096) +#define MIN_IO_CHANNEL_SIZE COVER(SIZEOF_PROTOCOL + \ + 2 * MIN_NUMSIGNALS * SIZEOF_CMDRSP, 4096) +#ifdef __GNUC__ +/* These defines should only ever be used in service partitons */ +/* because they rely on the size of uiscmdrsp */ +#define QSLOTSFROMBYTES(bytes) (((bytes-SIZEOF_PROTOCOL)/2)/SIZEOF_CMDRSP) +#define QSIZEFROMBYTES(bytes) (QSLOTSFROMBYTES(bytes)*SIZEOF_CMDRSP) +#define SignalQInit(x) \ + do { \ + x->cmdQ.Size = QSIZEFROMBYTES(x->ChannelHeader.Size); \ + x->cmdQ.oSignalBase = SIZEOF_PROTOCOL - \ + OFFSETOF(ULTRA_IO_CHANNEL_PROTOCOL, cmdQ); \ + x->cmdQ.SignalSize = SIZEOF_CMDRSP; \ + x->cmdQ.MaxSignalSlots = \ + QSLOTSFROMBYTES(x->ChannelHeader.Size); \ + x->cmdQ.MaxSignals = x->cmdQ.MaxSignalSlots - 1; \ + x->rspQ.Size = QSIZEFROMBYTES(x->ChannelHeader.Size); \ + x->rspQ.oSignalBase = \ + (SIZEOF_PROTOCOL + x->cmdQ.Size) - \ + OFFSETOF(ULTRA_IO_CHANNEL_PROTOCOL, rspQ); \ + x->rspQ.SignalSize = SIZEOF_CMDRSP; \ + x->rspQ.MaxSignalSlots = \ + QSLOTSFROMBYTES(x->ChannelHeader.Size); \ + x->rspQ.MaxSignals = x->rspQ.MaxSignalSlots - 1; \ + x->ChannelHeader.oChannelSpace = \ + OFFSETOF(ULTRA_IO_CHANNEL_PROTOCOL, cmdQ); \ + } while (0) + +#define INIT_CLIENTSTRING(chan, type, clientStr, clientStrLen) \ + do { \ + if (clientStr) { \ + chan->ChannelHeader.oClientString = \ + OFFSETOF(type, clientString); \ + MEMCPY(chan->clientString, clientStr, \ + MINNUM(clientStrLen, \ + (U32) (MAX_CLIENTSTRING_LEN - 1))); \ + chan->clientString[MINNUM(clientStrLen, \ + (U32) (MAX_CLIENTSTRING_LEN \ + - 1))] \ + = '\0'; \ + } \ + else \ + if (clientStrLen > 0) \ + return 0; \ + } while (0) + + +#define ULTRA_IO_CHANNEL_SERVER_READY(x, chanId, logCtx) \ + ULTRA_CHANNEL_SERVER_TRANSITION(x, chanId, SrvState, CHANNELSRV_READY, \ + logCtx); + +#define ULTRA_IO_CHANNEL_SERVER_NOTREADY(x, chanId, logCtx) \ + ULTRA_CHANNEL_SERVER_TRANSITION(x, chanId, SrvState, \ + CHANNELSRV_UNINITIALIZED, logCtx); + +static inline int ULTRA_VHBA_init_channel(ULTRA_IO_CHANNEL_PROTOCOL *x, + struct vhba_wwnn *wwnn, + struct vhba_config_max *max, + unsigned char *clientStr, + U32 clientStrLen, U64 bytes) { + MEMSET(x, 0, sizeof(ULTRA_IO_CHANNEL_PROTOCOL)); + x->ChannelHeader.VersionId = ULTRA_VHBA_CHANNEL_PROTOCOL_VERSIONID; + x->ChannelHeader.Signature = ULTRA_VHBA_CHANNEL_PROTOCOL_SIGNATURE; + x->ChannelHeader.SrvState = CHANNELSRV_UNINITIALIZED; + x->ChannelHeader.HeaderSize = sizeof(x->ChannelHeader); + x->ChannelHeader.Size = COVER(bytes, 4096); + x->ChannelHeader.Type = UltraVhbaChannelProtocolGuid; + x->ChannelHeader.ZoneGuid = NULL_UUID_LE; + x->vhba.wwnn = *wwnn; + x->vhba.max = *max; + INIT_CLIENTSTRING(x, ULTRA_IO_CHANNEL_PROTOCOL, clientStr, + clientStrLen); + SignalQInit(x); + if ((x->cmdQ.MaxSignalSlots > MAX_NUMSIGNALS) || + (x->rspQ.MaxSignalSlots > MAX_NUMSIGNALS)) { + return 0; + } + if ((x->cmdQ.MaxSignalSlots < MIN_NUMSIGNALS) || + (x->rspQ.MaxSignalSlots < MIN_NUMSIGNALS)) { + return 0; + } + return 1; +} + +static inline void ULTRA_VHBA_set_max(ULTRA_IO_CHANNEL_PROTOCOL *x, + struct vhba_config_max *max) { + x->vhba.max = *max; +} + +static inline int ULTRA_VNIC_init_channel(ULTRA_IO_CHANNEL_PROTOCOL *x, + unsigned char *macaddr, + U32 num_rcv_bufs, U32 mtu, + uuid_le zoneGuid, + unsigned char *clientStr, + U32 clientStrLen, + U64 bytes) { + MEMSET(x, 0, sizeof(ULTRA_IO_CHANNEL_PROTOCOL)); + x->ChannelHeader.VersionId = ULTRA_VNIC_CHANNEL_PROTOCOL_VERSIONID; + x->ChannelHeader.Signature = ULTRA_VNIC_CHANNEL_PROTOCOL_SIGNATURE; + x->ChannelHeader.SrvState = CHANNELSRV_UNINITIALIZED; + x->ChannelHeader.HeaderSize = sizeof(x->ChannelHeader); + x->ChannelHeader.Size = COVER(bytes, 4096); + x->ChannelHeader.Type = UltraVnicChannelProtocolGuid; + x->ChannelHeader.ZoneGuid = NULL_UUID_LE; + MEMCPY(x->vnic.macaddr, macaddr, MAX_MACADDR_LEN); + x->vnic.num_rcv_bufs = num_rcv_bufs; + x->vnic.mtu = mtu; + x->vnic.zoneGuid = zoneGuid; + INIT_CLIENTSTRING(x, ULTRA_IO_CHANNEL_PROTOCOL, clientStr, + clientStrLen); + SignalQInit(x); + if ((x->cmdQ.MaxSignalSlots > MAX_NUMSIGNALS) || + (x->rspQ.MaxSignalSlots > MAX_NUMSIGNALS)) { + return 0; + } + if ((x->cmdQ.MaxSignalSlots < MIN_NUMSIGNALS) || + (x->rspQ.MaxSignalSlots < MIN_NUMSIGNALS)) { + return 0; + } + return 1; +} + +#endif /* __GNUC__ */ + +/* +* INLINE function for expanding a guest's pfn-off-size into multiple 4K page +* pfn-off-size entires. +*/ + + +/* we deal with 4K page sizes when we it comes to passing page information + * between */ +/* Guest and IOPartition. */ +#define PI_PAGE_SIZE 0x1000 +#define PI_PAGE_MASK 0x0FFF +#define PI_PAGE_SHIFT 12 + +/* returns next non-zero index on success or zero on failure (i.e. out of + * room) + */ +static INLINE U16 +add_physinfo_entries(U32 inp_pfn, /* input - specifies the pfn to be used + * to add entries */ + U16 inp_off, /* input - specifies the off to be used + * to add entries */ + U32 inp_len, /* input - specifies the len to be used + * to add entries */ + U16 index, /* input - index in array at which new + * entries are added */ + U16 max_pi_arr_entries, /* input - specifies the maximum + * entries pi_arr can hold */ + struct phys_info pi_arr[]) /* input & output - array to + * which entries are added */ +{ + U32 len; + U16 i, firstlen; + + firstlen = PI_PAGE_SIZE - inp_off; + if (inp_len <= firstlen) { + + /* the input entry spans only one page - add as is */ + if (index >= max_pi_arr_entries) + return 0; + pi_arr[index].pi_pfn = inp_pfn; + pi_arr[index].pi_off = (U16) inp_off; + pi_arr[index].pi_len = (U16) inp_len; + return index + 1; + } + + /* this entry spans multiple pages */ + for (len = inp_len, i = 0; len; + len -= pi_arr[index + i].pi_len, i++) { + if (index + i >= max_pi_arr_entries) + return 0; + pi_arr[index + i].pi_pfn = inp_pfn + i; + if (i == 0) { + pi_arr[index].pi_off = inp_off; + pi_arr[index].pi_len = firstlen; + } + + else { + pi_arr[index + i].pi_off = 0; + pi_arr[index + i].pi_len = + (U16) MINNUM(len, (U32) PI_PAGE_SIZE); + } + + } + return index + i; +} + +#endif /* __IOCHANNEL_H__ */ diff --git a/drivers/staging/unisys/common-spar/include/channels/vbuschannel.h b/drivers/staging/unisys/common-spar/include/channels/vbuschannel.h new file mode 100644 index 00000000000..0dd3e2dd0e1 --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/channels/vbuschannel.h @@ -0,0 +1,135 @@ +/* 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. + */ + +#ifndef __VBUSCHANNEL_H__ +#define __VBUSCHANNEL_H__ + +/* The vbus channel is the channel area provided via the BUS_CREATE controlvm + * message for each virtual bus. This channel area is provided to both server + * and client ends of the bus. The channel header area is initialized by + * the server, and the remaining information is filled in by the client. + * We currently use this for the client to provide various information about + * the client devices and client drivers for the server end to see. + */ +#include <linux/uuid.h> +#include "commontypes.h" +#include "vbusdeviceinfo.h" +#include "channel.h" + +/* {193b331b-c58f-11da-95a9-00e08161165f} */ +#define ULTRA_VBUS_CHANNEL_PROTOCOL_GUID \ + UUID_LE(0x193b331b, 0xc58f, 0x11da, \ + 0x95, 0xa9, 0x0, 0xe0, 0x81, 0x61, 0x16, 0x5f) +static const uuid_le UltraVbusChannelProtocolGuid = + ULTRA_VBUS_CHANNEL_PROTOCOL_GUID; + +#define ULTRA_VBUS_CHANNEL_PROTOCOL_SIGNATURE ULTRA_CHANNEL_PROTOCOL_SIGNATURE + +/* Must increment this whenever you insert or delete fields within this channel +* struct. Also increment whenever you change the meaning of fields within this +* channel struct so as to break pre-existing software. Note that you can +* usually add fields to the END of the channel struct withOUT needing to +* increment this. */ +#define ULTRA_VBUS_CHANNEL_PROTOCOL_VERSIONID 1 + +#define ULTRA_VBUS_CHANNEL_OK_CLIENT(pChannel, logCtx) \ + (ULTRA_check_channel_client(pChannel, \ + UltraVbusChannelProtocolGuid, \ + "vbus", \ + sizeof(ULTRA_VBUS_CHANNEL_PROTOCOL), \ + ULTRA_VBUS_CHANNEL_PROTOCOL_VERSIONID, \ + ULTRA_VBUS_CHANNEL_PROTOCOL_SIGNATURE, \ + __FILE__, __LINE__, logCtx)) + +#define ULTRA_VBUS_CHANNEL_OK_SERVER(actualBytes, logCtx) \ + (ULTRA_check_channel_server(UltraVbusChannelProtocolGuid, \ + "vbus", \ + sizeof(ULTRA_VBUS_CHANNEL_PROTOCOL), \ + actualBytes, \ + __FILE__, __LINE__, logCtx)) + + +#pragma pack(push, 1) /* both GCC and VC now allow this pragma */ +typedef struct _ULTRA_VBUS_HEADERINFO { + U32 structBytes; /* size of this struct in bytes */ + U32 deviceInfoStructBytes; /* sizeof(ULTRA_VBUS_DEVICEINFO) */ + U32 devInfoCount; /* num of items in DevInfo member */ + /* (this is the allocated size) */ + U32 chpInfoByteOffset; /* byte offset from beginning of this struct */ + /* to the the ChpInfo struct (below) */ + U32 busInfoByteOffset; /* byte offset from beginning of this struct */ + /* to the the BusInfo struct (below) */ + U32 devInfoByteOffset; /* byte offset from beginning of this struct */ + /* to the the DevInfo array (below) */ + U8 reserved[104]; +} ULTRA_VBUS_HEADERINFO; + +typedef struct _ULTRA_VBUS_CHANNEL_PROTOCOL { + ULTRA_CHANNEL_PROTOCOL ChannelHeader; /* initialized by server */ + ULTRA_VBUS_HEADERINFO HdrInfo; /* initialized by server */ + /* the remainder of this channel is filled in by the client */ + ULTRA_VBUS_DEVICEINFO ChpInfo; /* describes client chipset device and + * driver */ + ULTRA_VBUS_DEVICEINFO BusInfo; /* describes client bus device and + * driver */ + ULTRA_VBUS_DEVICEINFO DevInfo[0]; /* describes client device and + * driver for */ + /* each device on the bus */ +} ULTRA_VBUS_CHANNEL_PROTOCOL; + +#define VBUS_CH_SIZE_EXACT(MAXDEVICES) \ + (sizeof(ULTRA_VBUS_CHANNEL_PROTOCOL) + ((MAXDEVICES) * \ + sizeof(ULTRA_VBUS_DEVICEINFO))) +#define VBUS_CH_SIZE(MAXDEVICES) COVER(VBUS_CH_SIZE_EXACT(MAXDEVICES), 4096) + +static INLINE void +ULTRA_VBUS_init_channel(ULTRA_VBUS_CHANNEL_PROTOCOL __iomem *x, + int bytesAllocated) +{ + /* Please note that the memory at <x> does NOT necessarily have space + * for DevInfo structs allocated at the end, which is why we do NOT use + * <bytesAllocated> to clear. */ + memset_io(x, 0, sizeof(ULTRA_VBUS_CHANNEL_PROTOCOL)); + if (bytesAllocated < (int) sizeof(ULTRA_VBUS_CHANNEL_PROTOCOL)) + return; + writel(ULTRA_VBUS_CHANNEL_PROTOCOL_VERSIONID, + &x->ChannelHeader.VersionId); + writeq(ULTRA_VBUS_CHANNEL_PROTOCOL_SIGNATURE, + &x->ChannelHeader.Signature); + writel(CHANNELSRV_READY, &x->ChannelHeader.SrvState); + writel(sizeof(x->ChannelHeader), &x->ChannelHeader.HeaderSize); + writeq(bytesAllocated, &x->ChannelHeader.Size); + memcpy_toio(&x->ChannelHeader.Type, &UltraVbusChannelProtocolGuid, + sizeof(x->ChannelHeader.Type)); + memcpy_toio(&x->ChannelHeader.ZoneGuid, &NULL_UUID_LE, sizeof(uuid_le)); + writel(sizeof(ULTRA_VBUS_HEADERINFO), &x->HdrInfo.structBytes); + writel(sizeof(ULTRA_VBUS_HEADERINFO), &x->HdrInfo.chpInfoByteOffset); + writel(readl(&x->HdrInfo.chpInfoByteOffset) + + sizeof(ULTRA_VBUS_DEVICEINFO), + &x->HdrInfo.busInfoByteOffset); + writel(readl(&x->HdrInfo.busInfoByteOffset) + + sizeof(ULTRA_VBUS_DEVICEINFO), + &x->HdrInfo.devInfoByteOffset); + writel(sizeof(ULTRA_VBUS_DEVICEINFO), + &x->HdrInfo.deviceInfoStructBytes); + bytesAllocated -= (sizeof(ULTRA_CHANNEL_PROTOCOL) + + readl(&x->HdrInfo.devInfoByteOffset)); + writel(bytesAllocated / readl(&x->HdrInfo.deviceInfoStructBytes), + &x->HdrInfo.devInfoCount); +} + +#pragma pack(pop) + +#endif diff --git a/drivers/staging/unisys/common-spar/include/controlvmcompletionstatus.h b/drivers/staging/unisys/common-spar/include/controlvmcompletionstatus.h new file mode 100644 index 00000000000..db77d6f626a --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/controlvmcompletionstatus.h @@ -0,0 +1,92 @@ +/* controlvmcompletionstatus.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. + */ + +/* Defines for all valid values returned in the response message header + * completionStatus field. See controlvmchannel.h for description of + * the header: _CONTROLVM_MESSAGE_HEADER. + */ + +#ifndef __CONTROLVMCOMPLETIONSTATUS_H__ +#define __CONTROLVMCOMPLETIONSTATUS_H__ + +/* General Errors------------------------------------------------------[0-99] */ +#define CONTROLVM_RESP_SUCCESS 0 +#define CONTROLVM_RESP_ERROR_ALREADY_DONE 1 +#define CONTROLVM_RESP_ERROR_IOREMAP_FAILED 2 +#define CONTROLVM_RESP_ERROR_KMALLOC_FAILED 3 +#define CONTROLVM_RESP_ERROR_MESSAGE_ID_UNKNOWN 4 +#define CONTROLVM_RESP_ERROR_MESSAGE_ID_INVALID_FOR_CLIENT 5 + +/* CONTROLVM_INIT_CHIPSET-------------------------------------------[100-199] */ +#define CONTROLVM_RESP_ERROR_CLIENT_SWITCHCOUNT_NONZERO 100 +#define CONTROLVM_RESP_ERROR_EXPECTED_CHIPSET_INIT 101 + +/* Maximum Limit----------------------------------------------------[200-299] */ +#define CONTROLVM_RESP_ERROR_MAX_BUSES 201 /* BUS_CREATE */ +#define CONTROLVM_RESP_ERROR_MAX_DEVICES 202 /* DEVICE_CREATE */ +/* Payload and Parameter Related------------------------------------[400-499] */ +#define CONTROLVM_RESP_ERROR_PAYLOAD_INVALID 400 /* SWITCH_ATTACHEXTPORT, + * DEVICE_CONFIGURE */ +#define CONTROLVM_RESP_ERROR_INITIATOR_PARAMETER_INVALID 401 /* Multiple */ +#define CONTROLVM_RESP_ERROR_TARGET_PARAMETER_INVALID 402 /* DEVICE_CONFIGURE */ +#define CONTROLVM_RESP_ERROR_CLIENT_PARAMETER_INVALID 403 /* DEVICE_CONFIGURE */ +/* Specified[Packet Structure] Value-------------------------------[500-599] */ +#define CONTROLVM_RESP_ERROR_BUS_INVALID 500 /* SWITCH_ATTACHINTPORT, + * BUS_CONFIGURE, + * DEVICE_CREATE, + * DEVICE_CONFIG + * DEVICE_DESTROY */ +#define CONTROLVM_RESP_ERROR_DEVICE_INVALID 501 /* SWITCH_ATTACHINTPORT */ + /* DEVICE_CREATE, + * DEVICE_CONFIGURE, + * DEVICE_DESTROY */ +#define CONTROLVM_RESP_ERROR_CHANNEL_INVALID 502 /* DEVICE_CREATE, + * DEVICE_CONFIGURE */ +/* Partition Driver Callback Interface----------------------[600-699] */ +#define CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE 604 /* BUS_CREATE, + * BUS_DESTROY, + * DEVICE_CREATE, + * DEVICE_DESTROY */ +/* Unable to invoke VIRTPCI callback */ +#define CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR 605 /* BUS_CREATE, + * BUS_DESTROY, + * DEVICE_CREATE, + * DEVICE_DESTROY */ +/* VIRTPCI Callback returned error */ +#define CONTROLVM_RESP_ERROR_GENERIC_DRIVER_CALLBACK_ERROR 606 /* SWITCH_ATTACHEXTPORT, + * SWITCH_DETACHEXTPORT + * DEVICE_CONFIGURE */ + +/* generic device callback returned error */ +/* Bus Related------------------------------------------------------[700-799] */ +#define CONTROLVM_RESP_ERROR_BUS_DEVICE_ATTACHED 700 /* BUS_DESTROY */ +/* Channel Related--------------------------------------------------[800-899] */ +#define CONTROLVM_RESP_ERROR_CHANNEL_TYPE_UNKNOWN 800 /* GET_CHANNELINFO, + * DEVICE_DESTROY */ +#define CONTROLVM_RESP_ERROR_CHANNEL_SIZE_TOO_SMALL 801 /* DEVICE_CREATE */ +/* Chipset Shutdown Related---------------------------------------[1000-1099] */ +#define CONTROLVM_RESP_ERROR_CHIPSET_SHUTDOWN_FAILED 1000 +#define CONTROLVM_RESP_ERROR_CHIPSET_SHUTDOWN_ALREADY_ACTIVE 1001 + +/* Chipset Stop Related-------------------------------------------[1100-1199] */ +#define CONTROLVM_RESP_ERROR_CHIPSET_STOP_FAILED_BUS 1100 +#define CONTROLVM_RESP_ERROR_CHIPSET_STOP_FAILED_SWITCH 1101 + +/* Device Related-------------------------------------------------[1400-1499] */ +#define CONTROLVM_RESP_ERROR_DEVICE_UDEV_TIMEOUT 1400 + +#endif /* __CONTROLVMCOMPLETIONSTATUS_H__ not defined */ diff --git a/drivers/staging/unisys/common-spar/include/diagnostics/appos_subsystems.h b/drivers/staging/unisys/common-spar/include/diagnostics/appos_subsystems.h new file mode 100644 index 00000000000..18cc9ed2748 --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/diagnostics/appos_subsystems.h @@ -0,0 +1,310 @@ +/* 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. + */ + +/* Please note that this file is to be used ONLY for defining diagnostic + * subsystem values for the appos (sPAR Linux service partitions) component. + */ +#ifndef __APPOS_SUBSYSTEMS_H__ +#define __APPOS_SUBSYSTEMS_H__ + +#ifdef __KERNEL__ +#include <linux/kernel.h> +#include <linux/string.h> +#else +#include <stdio.h> +#include <string.h> +#endif + +static inline char * +subsys_unknown_to_s(int subsys, char *s, int n) +{ + snprintf(s, n, "SUBSYS-%-2.2d", subsys); + s[n - 1] = '\0'; + return s; +} + +#define SUBSYS_TO_MASK(subsys) (1ULL << (subsys)) + +/* The first SUBSYS_APPOS_MAX subsystems are the same for each AppOS type + * (IOVM, SMS, etc.) The rest have unique values for each AppOS type. + */ +#define SUBSYS_APPOS_MAX 16 + +#define SUBSYS_APPOS_DEFAULT 1 /* or "other" */ +#define SUBSYS_APPOS_CHIPSET 2 /* controlvm and other */ + /* low-level sPAR activity */ +#define SUBSYS_APPOS_BUS 3 /* sPAR bus */ +/* DAK #define SUBSYS_APPOS_DIAG 4 // diagnostics and dump */ +#define SUBSYS_APPOS_CHANNELACCESS 5 /* generic channel access */ +#define SUBSYS_APPOS_NICCLIENT 6 /* virtual NIC client */ +#define SUBSYS_APPOS_HBACLIENT 7 /* virtual HBA client */ +#define SUBSYS_APPOS_CONSOLESERIAL 8 /* sPAR virtual serial console */ +#define SUBSYS_APPOS_UISLIB 9 /* */ +#define SUBSYS_APPOS_VRTCUPDD 10 /* */ +#define SUBSYS_APPOS_WATCHDOG 11 /* watchdog timer and healthcheck */ +#define SUBSYS_APPOS_13 13 /* available */ +#define SUBSYS_APPOS_14 14 /* available */ +#define SUBSYS_APPOS_15 15 /* available */ +#define SUBSYS_APPOS_16 16 /* available */ +static inline char * +subsys_generic_to_s(int subsys, char *s, int n) +{ + switch (subsys) { + case SUBSYS_APPOS_DEFAULT: + strncpy(s, "APPOS_DEFAULT", n); + break; + case SUBSYS_APPOS_CHIPSET: + strncpy(s, "APPOS_CHIPSET", n); + break; + case SUBSYS_APPOS_BUS: + strncpy(s, "APPOS_BUS", n); + break; + case SUBSYS_APPOS_CHANNELACCESS: + strncpy(s, "APPOS_CHANNELACCESS", n); + break; + case SUBSYS_APPOS_NICCLIENT: + strncpy(s, "APPOS_NICCLIENT", n); + break; + case SUBSYS_APPOS_HBACLIENT: + strncpy(s, "APPOS_HBACLIENT", n); + break; + case SUBSYS_APPOS_CONSOLESERIAL: + strncpy(s, "APPOS_CONSOLESERIAL", n); + break; + case SUBSYS_APPOS_UISLIB: + strncpy(s, "APPOS_UISLIB", n); + break; + case SUBSYS_APPOS_VRTCUPDD: + strncpy(s, "APPOS_VRTCUPDD", n); + break; + case SUBSYS_APPOS_WATCHDOG: + strncpy(s, "APPOS_WATCHDOG", n); + break; + case SUBSYS_APPOS_13: + strncpy(s, "APPOS_13", n); + break; + case SUBSYS_APPOS_14: + strncpy(s, "APPOS_14", n); + break; + case SUBSYS_APPOS_15: + strncpy(s, "APPOS_15", n); + break; + case SUBSYS_APPOS_16: + strncpy(s, "APPOS_16", n); + break; + default: + subsys_unknown_to_s(subsys, s, n); + break; + } + s[n - 1] = '\0'; + return s; +} + +/* CONSOLE */ + +#define SUBSYS_CONSOLE_VIDEO (SUBSYS_APPOS_MAX + 1) /* 17 */ +#define SUBSYS_CONSOLE_KBDMOU (SUBSYS_APPOS_MAX + 2) /* 18 */ +#define SUBSYS_CONSOLE_04 (SUBSYS_APPOS_MAX + 4) +#define SUBSYS_CONSOLE_05 (SUBSYS_APPOS_MAX + 5) +#define SUBSYS_CONSOLE_06 (SUBSYS_APPOS_MAX + 6) +#define SUBSYS_CONSOLE_07 (SUBSYS_APPOS_MAX + 7) +#define SUBSYS_CONSOLE_08 (SUBSYS_APPOS_MAX + 8) +#define SUBSYS_CONSOLE_09 (SUBSYS_APPOS_MAX + 9) +#define SUBSYS_CONSOLE_10 (SUBSYS_APPOS_MAX + 10) +#define SUBSYS_CONSOLE_11 (SUBSYS_APPOS_MAX + 11) +#define SUBSYS_CONSOLE_12 (SUBSYS_APPOS_MAX + 12) +#define SUBSYS_CONSOLE_13 (SUBSYS_APPOS_MAX + 13) +#define SUBSYS_CONSOLE_14 (SUBSYS_APPOS_MAX + 14) +#define SUBSYS_CONSOLE_15 (SUBSYS_APPOS_MAX + 15) +#define SUBSYS_CONSOLE_16 (SUBSYS_APPOS_MAX + 16) +#define SUBSYS_CONSOLE_17 (SUBSYS_APPOS_MAX + 17) +#define SUBSYS_CONSOLE_18 (SUBSYS_APPOS_MAX + 18) +#define SUBSYS_CONSOLE_19 (SUBSYS_APPOS_MAX + 19) +#define SUBSYS_CONSOLE_20 (SUBSYS_APPOS_MAX + 20) +#define SUBSYS_CONSOLE_21 (SUBSYS_APPOS_MAX + 21) +#define SUBSYS_CONSOLE_22 (SUBSYS_APPOS_MAX + 22) +#define SUBSYS_CONSOLE_23 (SUBSYS_APPOS_MAX + 23) +#define SUBSYS_CONSOLE_24 (SUBSYS_APPOS_MAX + 24) +#define SUBSYS_CONSOLE_25 (SUBSYS_APPOS_MAX + 25) +#define SUBSYS_CONSOLE_26 (SUBSYS_APPOS_MAX + 26) +#define SUBSYS_CONSOLE_27 (SUBSYS_APPOS_MAX + 27) +#define SUBSYS_CONSOLE_28 (SUBSYS_APPOS_MAX + 28) +#define SUBSYS_CONSOLE_29 (SUBSYS_APPOS_MAX + 29) +#define SUBSYS_CONSOLE_30 (SUBSYS_APPOS_MAX + 30) +#define SUBSYS_CONSOLE_31 (SUBSYS_APPOS_MAX + 31) +#define SUBSYS_CONSOLE_32 (SUBSYS_APPOS_MAX + 32) +#define SUBSYS_CONSOLE_33 (SUBSYS_APPOS_MAX + 33) +#define SUBSYS_CONSOLE_34 (SUBSYS_APPOS_MAX + 34) +#define SUBSYS_CONSOLE_35 (SUBSYS_APPOS_MAX + 35) +#define SUBSYS_CONSOLE_36 (SUBSYS_APPOS_MAX + 36) +#define SUBSYS_CONSOLE_37 (SUBSYS_APPOS_MAX + 37) +#define SUBSYS_CONSOLE_38 (SUBSYS_APPOS_MAX + 38) +#define SUBSYS_CONSOLE_39 (SUBSYS_APPOS_MAX + 39) +#define SUBSYS_CONSOLE_40 (SUBSYS_APPOS_MAX + 40) +#define SUBSYS_CONSOLE_41 (SUBSYS_APPOS_MAX + 41) +#define SUBSYS_CONSOLE_42 (SUBSYS_APPOS_MAX + 42) +#define SUBSYS_CONSOLE_43 (SUBSYS_APPOS_MAX + 43) +#define SUBSYS_CONSOLE_44 (SUBSYS_APPOS_MAX + 44) +#define SUBSYS_CONSOLE_45 (SUBSYS_APPOS_MAX + 45) +#define SUBSYS_CONSOLE_46 (SUBSYS_APPOS_MAX + 46) + +static inline char * +subsys_console_to_s(int subsys, char *s, int n) +{ + switch (subsys) { + case SUBSYS_CONSOLE_VIDEO: + strncpy(s, "CONSOLE_VIDEO", n); + break; + case SUBSYS_CONSOLE_KBDMOU: + strncpy(s, "CONSOLE_KBDMOU", n); + break; + case SUBSYS_CONSOLE_04: + strncpy(s, "CONSOLE_04", n); + break; + case SUBSYS_CONSOLE_05: + strncpy(s, "CONSOLE_05", n); + break; + case SUBSYS_CONSOLE_06: + strncpy(s, "CONSOLE_06", n); + break; + case SUBSYS_CONSOLE_07: + strncpy(s, "CONSOLE_07", n); + break; + case SUBSYS_CONSOLE_08: + strncpy(s, "CONSOLE_08", n); + break; + case SUBSYS_CONSOLE_09: + strncpy(s, "CONSOLE_09", n); + break; + case SUBSYS_CONSOLE_10: + strncpy(s, "CONSOLE_10", n); + break; + case SUBSYS_CONSOLE_11: + strncpy(s, "CONSOLE_11", n); + break; + case SUBSYS_CONSOLE_12: + strncpy(s, "CONSOLE_12", n); + break; + case SUBSYS_CONSOLE_13: + strncpy(s, "CONSOLE_13", n); + break; + case SUBSYS_CONSOLE_14: + strncpy(s, "CONSOLE_14", n); + break; + case SUBSYS_CONSOLE_15: + strncpy(s, "CONSOLE_15", n); + break; + case SUBSYS_CONSOLE_16: + strncpy(s, "CONSOLE_16", n); + break; + case SUBSYS_CONSOLE_17: + strncpy(s, "CONSOLE_17", n); + break; + case SUBSYS_CONSOLE_18: + strncpy(s, "CONSOLE_18", n); + break; + case SUBSYS_CONSOLE_19: + strncpy(s, "CONSOLE_19", n); + break; + case SUBSYS_CONSOLE_20: + strncpy(s, "CONSOLE_20", n); + break; + case SUBSYS_CONSOLE_21: + strncpy(s, "CONSOLE_21", n); + break; + case SUBSYS_CONSOLE_22: + strncpy(s, "CONSOLE_22", n); + break; + case SUBSYS_CONSOLE_23: + strncpy(s, "CONSOLE_23", n); + break; + case SUBSYS_CONSOLE_24: + strncpy(s, "CONSOLE_24", n); + break; + case SUBSYS_CONSOLE_25: + strncpy(s, "CONSOLE_25", n); + break; + case SUBSYS_CONSOLE_26: + strncpy(s, "CONSOLE_26", n); + break; + case SUBSYS_CONSOLE_27: + strncpy(s, "CONSOLE_27", n); + break; + case SUBSYS_CONSOLE_28: + strncpy(s, "CONSOLE_28", n); + break; + case SUBSYS_CONSOLE_29: + strncpy(s, "CONSOLE_29", n); + break; + case SUBSYS_CONSOLE_30: + strncpy(s, "CONSOLE_30", n); + break; + case SUBSYS_CONSOLE_31: + strncpy(s, "CONSOLE_31", n); + break; + case SUBSYS_CONSOLE_32: + strncpy(s, "CONSOLE_32", n); + break; + case SUBSYS_CONSOLE_33: + strncpy(s, "CONSOLE_33", n); + break; + case SUBSYS_CONSOLE_34: + strncpy(s, "CONSOLE_34", n); + break; + case SUBSYS_CONSOLE_35: + strncpy(s, "CONSOLE_35", n); + break; + case SUBSYS_CONSOLE_36: + strncpy(s, "CONSOLE_36", n); + break; + case SUBSYS_CONSOLE_37: + strncpy(s, "CONSOLE_37", n); + break; + case SUBSYS_CONSOLE_38: + strncpy(s, "CONSOLE_38", n); + break; + case SUBSYS_CONSOLE_39: + strncpy(s, "CONSOLE_39", n); + break; + case SUBSYS_CONSOLE_40: + strncpy(s, "CONSOLE_40", n); + break; + case SUBSYS_CONSOLE_41: + strncpy(s, "CONSOLE_41", n); + break; + case SUBSYS_CONSOLE_42: + strncpy(s, "CONSOLE_42", n); + break; + case SUBSYS_CONSOLE_43: + strncpy(s, "CONSOLE_43", n); + break; + case SUBSYS_CONSOLE_44: + strncpy(s, "CONSOLE_44", n); + break; + case SUBSYS_CONSOLE_45: + strncpy(s, "CONSOLE_45", n); + break; + case SUBSYS_CONSOLE_46: + strncpy(s, "CONSOLE_46", n); + break; + default: + subsys_unknown_to_s(subsys, s, n); + break; + } + s[n - 1] = '\0'; + return s; +} + +#endif diff --git a/drivers/staging/unisys/common-spar/include/iovmcall_gnuc.h b/drivers/staging/unisys/common-spar/include/iovmcall_gnuc.h new file mode 100644 index 00000000000..fe9598c941a --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/iovmcall_gnuc.h @@ -0,0 +1,53 @@ +/* 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. + */ + +/* Linux GCC Version (32-bit and 64-bit) */ +static inline unsigned long +__unisys_vmcall_gnuc(unsigned long tuple, unsigned long reg_ebx, + unsigned long reg_ecx) +{ + unsigned long result = 0; + + unsigned int cpuid_eax, cpuid_ebx, cpuid_ecx, cpuid_edx; + cpuid(0x00000001, &cpuid_eax, &cpuid_ebx, &cpuid_ecx, &cpuid_edx); + if (cpuid_ecx & 0x80000000) { + __asm__ __volatile__(".byte 0x00f, 0x001, 0x0c1" : "=a"(result) : + "a"(tuple), "b"(reg_ebx), "c"(reg_ecx) + ); + } else { + result = -1; + } + return result; +} + +static inline unsigned long +__unisys_extended_vmcall_gnuc(unsigned long long tuple, + unsigned long long reg_ebx, + unsigned long long reg_ecx, + unsigned long long reg_edx) +{ + unsigned long result = 0; + + unsigned int cpuid_eax, cpuid_ebx, cpuid_ecx, cpuid_edx; + cpuid(0x00000001, &cpuid_eax, &cpuid_ebx, &cpuid_ecx, &cpuid_edx); + if (cpuid_ecx & 0x80000000) { + __asm__ __volatile__(".byte 0x00f, 0x001, 0x0c1" : "=a"(result) : + "a"(tuple), "b"(reg_ebx), "c"(reg_ecx), + "d"(reg_edx)); + } else { + result = -1; + } + return result; + } diff --git a/drivers/staging/unisys/common-spar/include/vbusdeviceinfo.h b/drivers/staging/unisys/common-spar/include/vbusdeviceinfo.h new file mode 100644 index 00000000000..8c0259a2cbc --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/vbusdeviceinfo.h @@ -0,0 +1,209 @@ +/* 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. + */ + +#ifndef __VBUSDEVICEINFO_H__ +#define __VBUSDEVICEINFO_H__ + +#include "commontypes.h" + +#pragma pack(push, 1) /* both GCC and VC now allow this pragma */ + +/* An array of this struct is present in the channel area for each vbus. + * (See vbuschannel.h.) + * It is filled in by the client side to provide info about the device + * and driver from the client's perspective. + */ +typedef struct _ULTRA_VBUS_DEVICEINFO { + U8 devType[16]; /* short string identifying the device type */ + U8 drvName[16]; /* driver .sys file name */ + U8 infoStrings[96]; /* sequence of tab-delimited id strings: */ + /* <DRIVER_REV> <DRIVER_VERTAG> <DRIVER_COMPILETIME> */ + U8 reserved[128]; /* pad size to 256 bytes */ +} ULTRA_VBUS_DEVICEINFO; + +#pragma pack(pop) + +/* Reads chars from the buffer at <src> for <srcmax> bytes, and writes to + * the buffer at <p>, which is <remain> bytes long, ensuring never to + * overflow the buffer at <p>, using the following rules: + * - printable characters are simply copied from the buffer at <src> to the + * buffer at <p> + * - intervening streaks of non-printable characters in the buffer at <src> + * are replaced with a single space in the buffer at <p> + * Note that we pay no attention to '\0'-termination. + * Returns the number of bytes written to <p>. + * + * Pass <p> == NULL and <remain> == 0 for this special behavior. In this + * case, we simply return the number of bytes that WOULD HAVE been written + * to a buffer at <p>, had it been infinitely big. + */ +static inline int +VBUSCHANNEL_sanitize_buffer(char *p, int remain, char __iomem *src, int srcmax) +{ + int chars = 0; + int nonprintable_streak = 0; + while (srcmax > 0) { + if ((readb(src) >= ' ') && (readb(src) < 0x7f)) { + if (nonprintable_streak) { + if (remain > 0) { + *p = ' '; + p++; + remain--; + chars++; + } else if (p == NULL) + chars++; + nonprintable_streak = 0; + } + if (remain > 0) { + *p = readb(src); + p++; + remain--; + chars++; + } else if (p == NULL) + chars++; + } else + nonprintable_streak = 1; + src++; + srcmax--; + } + return chars; +} + +#define VBUSCHANNEL_ADDACHAR(ch, p, remain, chars) \ + do { \ + if (remain <= 0) \ + break; \ + *p = ch; \ + p++; chars++; remain--; \ + } while (0) + +/* Converts the non-negative value at <num> to an ascii decimal string + * at <p>, writing at most <remain> bytes. Note there is NO '\0' termination + * written to <p>. + * + * Returns the number of bytes written to <p>. + * + * Note that we create this function because we need to do this operation in + * an environment-independent way (since we are in a common header file). + */ +static inline int +VBUSCHANNEL_itoa(char *p, int remain, int num) +{ + int digits = 0; + char s[32]; + int i; + + if (num == 0) { + /* '0' is a special case */ + if (remain <= 0) + return 0; + *p = '0'; + return 1; + } + /* form a backwards decimal ascii string in <s> */ + while (num > 0) { + if (digits >= (int) sizeof(s)) + return 0; + s[digits++] = (num % 10) + '0'; + num = num / 10; + } + if (remain < digits) { + /* not enough room left at <p> to hold number, so fill with + * '?' */ + for (i = 0; i < remain; i++, p++) + *p = '?'; + return remain; + } + /* plug in the decimal ascii string representing the number, by */ + /* reversing the string we just built in <s> */ + i = digits; + while (i > 0) { + i--; + *p = s[i]; + p++; + } + return digits; +} + +/* Reads <devInfo>, and converts its contents to a printable string at <p>, + * writing at most <remain> bytes. Note there is NO '\0' termination + * written to <p>. + * + * Pass <devix> >= 0 if you want a device index presented. + * + * Returns the number of bytes written to <p>. + */ +static inline int +VBUSCHANNEL_devInfoToStringBuffer(ULTRA_VBUS_DEVICEINFO __iomem *devInfo, + char *p, int remain, int devix) +{ + char __iomem *psrc; + int nsrc, x, i, pad; + int chars = 0; + + psrc = &(devInfo->devType[0]); + nsrc = sizeof(devInfo->devType); + if (VBUSCHANNEL_sanitize_buffer(NULL, 0, psrc, nsrc) <= 0) + return 0; + + /* emit device index */ + if (devix >= 0) { + VBUSCHANNEL_ADDACHAR('[', p, remain, chars); + x = VBUSCHANNEL_itoa(p, remain, devix); + p += x; + remain -= x; + chars += x; + VBUSCHANNEL_ADDACHAR(']', p, remain, chars); + } else { + VBUSCHANNEL_ADDACHAR(' ', p, remain, chars); + VBUSCHANNEL_ADDACHAR(' ', p, remain, chars); + VBUSCHANNEL_ADDACHAR(' ', p, remain, chars); + } + + /* emit device type */ + x = VBUSCHANNEL_sanitize_buffer(p, remain, psrc, nsrc); + p += x; + remain -= x; + chars += x; + pad = 15 - x; /* pad device type to be exactly 15 chars */ + for (i = 0; i < pad; i++) + VBUSCHANNEL_ADDACHAR(' ', p, remain, chars); + VBUSCHANNEL_ADDACHAR(' ', p, remain, chars); + + /* emit driver name */ + psrc = &(devInfo->drvName[0]); + nsrc = sizeof(devInfo->drvName); + x = VBUSCHANNEL_sanitize_buffer(p, remain, psrc, nsrc); + p += x; + remain -= x; + chars += x; + pad = 15 - x; /* pad driver name to be exactly 15 chars */ + for (i = 0; i < pad; i++) + VBUSCHANNEL_ADDACHAR(' ', p, remain, chars); + VBUSCHANNEL_ADDACHAR(' ', p, remain, chars); + + /* emit strings */ + psrc = &(devInfo->infoStrings[0]); + nsrc = sizeof(devInfo->infoStrings); + x = VBUSCHANNEL_sanitize_buffer(p, remain, psrc, nsrc); + p += x; + remain -= x; + chars += x; + VBUSCHANNEL_ADDACHAR('\n', p, remain, chars); + + return chars; +} + +#endif diff --git a/drivers/staging/unisys/common-spar/include/version.h b/drivers/staging/unisys/common-spar/include/version.h new file mode 100644 index 00000000000..f25208fc3ed --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/version.h @@ -0,0 +1,46 @@ +/* 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. + */ + +/* version.h */ + +/* Common version/release info needed by all components goes here. + * (This file must compile cleanly in all environments.) + * Ultimately, this will be combined with defines generated dynamically as + * part of the sysgen, and some of the defines below may in fact end up + * being replaced with dynamically generated ones. + */ +#ifndef __VERSION_H__ +#define __VERSION_H__ + +#define SPARVER1 "1" +#define SPARVER2 "0" +#define SPARVER3 "0" +#define SPARVER4 "0" + +#define VERSION SPARVER1 "." SPARVER2 "." SPARVER3 "." SPARVER4 +#define VERSIONDATE __DATE__ + +/* Here are various version forms needed in Windows environments. + */ +#define VISOR_PRODUCTVERSION SPARVERCOMMA +#define VISOR_PRODUCTVERSION_STR SPARVER1 "." SPARVER2 "." SPARVER3 "." \ + SPARVER4 +#define VISOR_OBJECTVERSION_STR SPARVER1 "," SPARVER2 "," SPARVER3 "," \ + SPARVER4 + +#define COPYRIGHT "Unisys Corporation" +#define COPYRIGHTDATE "2010 - 2013" + +#endif diff --git a/drivers/staging/unisys/common-spar/include/vmcallinterface.h b/drivers/staging/unisys/common-spar/include/vmcallinterface.h new file mode 100644 index 00000000000..c5c10f3f1eb --- /dev/null +++ b/drivers/staging/unisys/common-spar/include/vmcallinterface.h @@ -0,0 +1,167 @@ +/* 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. + */ + +#ifndef __IOMONINTF_H__ +#define __IOMONINTF_H__ + +/* +* This file contains all structures needed to support the VMCALLs for IO +* Virtualization. The VMCALLs are provided by Monitor and used by IO code +* running on IO Partitions. +*/ + +#ifdef __GNUC__ +#include "iovmcall_gnuc.h" +#endif /* */ +#include "diagchannel.h" + +#ifdef VMCALL_IO_CONTROLVM_ADDR +#undef VMCALL_IO_CONTROLVM_ADDR +#endif /* */ + +/* define subsystem number for AppOS, used in uislib driver */ +#define MDS_APPOS 0x4000000000000000L /* subsystem = 62 - AppOS */ +typedef enum { /* VMCALL identification tuples */ + /* Note: when a new VMCALL is added: + * - the 1st 2 hex digits correspond to one of the + * VMCALL_MONITOR_INTERFACE types and + * - the next 2 hex digits are the nth relative instance of within a + * type + * E.G. for VMCALL_VIRTPART_RECYCLE_PART, + * - the 0x02 identifies it as a VMCALL_VIRTPART type and + * - the 0x01 identifies it as the 1st instance of a VMCALL_VIRTPART + * type of VMCALL + */ + + VMCALL_IO_CONTROLVM_ADDR = 0x0501, /* used by all Guests, not just + * IO */ + VMCALL_IO_DIAG_ADDR = 0x0508, + VMCALL_IO_VISORSERIAL_ADDR = 0x0509, + VMCALL_QUERY_GUEST_VIRTUAL_TIME_OFFSET = 0x0708, /* Allow caller to + * query virtual time + * offset */ + VMCALL_CHANNEL_VERSION_MISMATCH = 0x0709, + VMCALL_POST_CODE_LOGEVENT = 0x070B, /* LOGEVENT Post Code (RDX) with + * specified subsystem mask (RCX + * - monitor_subsystems.h) and + * severity (RDX) */ + VMCALL_GENERIC_SURRENDER_QUANTUM_FOREVER = 0x0802, /* Yield the + * remainder & all + * future quantums of + * the caller */ + VMCALL_MEASUREMENT_DO_NOTHING = 0x0901, + VMCALL_UPDATE_PHYSICAL_TIME = 0x0a02 /* Allow + * ULTRA_SERVICE_CAPABILITY_TIME + * capable guest to make + * VMCALL */ +} VMCALL_MONITOR_INTERFACE_METHOD_TUPLE; + +#define VMCALL_SUCCESS 0 +#define VMCALL_SUCCESSFUL(result) (result == 0) + +#ifdef __GNUC__ +#define unisys_vmcall(tuple, reg_ebx, reg_ecx) \ + __unisys_vmcall_gnuc(tuple, reg_ebx, reg_ecx) +#define unisys_extended_vmcall(tuple, reg_ebx, reg_ecx, reg_edx) \ + __unisys_extended_vmcall_gnuc(tuple, reg_ebx, reg_ecx, reg_edx) +#define ISSUE_IO_VMCALL(InterfaceMethod, param, result) \ + (result = unisys_vmcall(InterfaceMethod, (param) & 0xFFFFFFFF, \ + (param) >> 32)) +#define ISSUE_IO_EXTENDED_VMCALL(InterfaceMethod, param1, param2, \ + param3, result) \ + (result = unisys_extended_vmcall(InterfaceMethod, param1, \ + param2, param3)) + + /* The following uses VMCALL_POST_CODE_LOGEVENT interface but is currently + * not used much */ +#define ISSUE_IO_VMCALL_POSTCODE_SEVERITY(postcode, severity) \ +do { \ + U32 _tempresult = VMCALL_SUCCESS; \ + ISSUE_IO_EXTENDED_VMCALL(VMCALL_POST_CODE_LOGEVENT, severity, \ + MDS_APPOS, postcode, _tempresult); \ +} while (0) +#endif + +/* Structures for IO VMCALLs */ + +/* ///////////// BEGIN PRAGMA PACK PUSH 1 ///////////////////////// */ +/* ///////////// ONLY STRUCT TYPE SHOULD BE BELOW */ +#pragma pack(push, 1) +struct phys_info { + U64 pi_pfn; + U16 pi_off; + U16 pi_len; +}; + +#pragma pack(pop) +/* ///////////// END PRAGMA PACK PUSH 1 /////////////////////////// */ +typedef struct phys_info IO_DATA_STRUCTURE; + +/* ///////////// BEGIN PRAGMA PACK PUSH 1 ///////////////////////// */ +/* ///////////// ONLY STRUCT TYPE SHOULD BE BELOW */ +#pragma pack(push, 1) +/* Parameters to VMCALL_IO_CONTROLVM_ADDR interface */ +typedef struct _VMCALL_IO_CONTROLVM_ADDR_PARAMS { + /* The Guest-relative physical address of the ControlVm channel. + * This VMCall fills this in with the appropriate address. */ + U64 ChannelAddress; /* contents provided by this VMCALL (OUT) */ + /* the size of the ControlVm channel in bytes This VMCall fills this + * in with the appropriate address. */ + U32 ChannelBytes; /* contents provided by this VMCALL (OUT) */ + U8 Unused[4]; /* Unused Bytes in the 64-Bit Aligned Struct */ +} VMCALL_IO_CONTROLVM_ADDR_PARAMS; + +#pragma pack(pop) +/* ///////////// END PRAGMA PACK PUSH 1 /////////////////////////// */ + +/* ///////////// BEGIN PRAGMA PACK PUSH 1 ///////////////////////// */ +/* ///////////// ONLY STRUCT TYPE SHOULD BE BELOW */ +#pragma pack(push, 1) +/* Parameters to VMCALL_IO_DIAG_ADDR interface */ +typedef struct _VMCALL_IO_DIAG_ADDR_PARAMS { + /* The Guest-relative physical address of the diagnostic channel. + * This VMCall fills this in with the appropriate address. */ + U64 ChannelAddress; /* contents provided by this VMCALL (OUT) */ +} VMCALL_IO_DIAG_ADDR_PARAMS; + +#pragma pack(pop) +/* ///////////// END PRAGMA PACK PUSH 1 /////////////////////////// */ + +/* ///////////// BEGIN PRAGMA PACK PUSH 1 ///////////////////////// */ +/* ///////////// ONLY STRUCT TYPE SHOULD BE BELOW */ +#pragma pack(push, 1) +/* Parameters to VMCALL_IO_VISORSERIAL_ADDR interface */ +typedef struct _VMCALL_IO_VISORSERIAL_ADDR_PARAMS { + /* The Guest-relative physical address of the serial console + * channel. This VMCall fills this in with the appropriate + * address. */ + U64 ChannelAddress; /* contents provided by this VMCALL (OUT) */ +} VMCALL_IO_VISORSERIAL_ADDR_PARAMS; + +#pragma pack(pop) +/* ///////////// END PRAGMA PACK PUSH 1 /////////////////////////// */ + +/* Parameters to VMCALL_CHANNEL_MISMATCH interface */ +typedef struct _VMCALL_CHANNEL_VERSION_MISMATCH_PARAMS { + U8 ChannelName[32]; /* Null terminated string giving name of channel + * (IN) */ + U8 ItemName[32]; /* Null terminated string giving name of + * mismatched item (IN) */ + U32 SourceLineNumber; /* line# where invoked. (IN) */ + U8 SourceFileName[36]; /* source code where invoked - Null terminated + * string (IN) */ +} VMCALL_CHANNEL_VERSION_MISMATCH_PARAMS; + +#endif /* __IOMONINTF_H__ */ diff --git a/drivers/staging/unisys/include/commontypes.h b/drivers/staging/unisys/include/commontypes.h new file mode 100644 index 00000000000..9de6f9dc5fb --- /dev/null +++ b/drivers/staging/unisys/include/commontypes.h @@ -0,0 +1,155 @@ +/* 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. + */ + +#ifndef _COMMONTYPES_H_ +#define _COMMONTYPES_H_ + +/* define the following to prevent include nesting in kernel header files of + * similar abbreviated content */ +#define _SUPERVISOR_COMMONTYPES_H_ + +#ifdef __KERNEL__ +#include <linux/types.h> +#include <linux/version.h> +#include <linux/io.h> +#include <linux/uuid.h> +#else +#include <stdint.h> +#include <syslog.h> +#endif + +#define U8 uint8_t +#define U16 uint16_t +#define U32 uint32_t +#define U64 uint64_t +#define S8 int8_t +#define S16 int16_t +#define S32 int32_t +#define S64 int64_t + +#ifdef __KERNEL__ + +#ifdef CONFIG_X86_32 +#define UINTN U32 +#else +#define UINTN U64 +#endif + +#else + +#include <stdint.h> +#if __WORDSIZE == 32 +#define UINTN U32 +#elif __WORDSIZE == 64 +#define UINTN U64 +#else +#error Unsupported __WORDSIZE +#endif + +#endif + +typedef U64 GUEST_PHYSICAL_ADDRESS; + +#define MEMSET(ptr, val, len) memset(ptr, val, len) +#define MEMCMP(m1, m2, len) memcmp(m1, m2, len) +#define MEMCMP_IO(m1, m2, len) memcmp((void __force *)m1, m2, len) +#define STRLEN(s) ((UINTN)strlen((const char *)s)) +#define STRCPY(d, s) (strcpy((char *)d, (const char *)s)) + +#define INLINE inline +#define OFFSETOF offsetof + +#ifdef __KERNEL__ +#define MEMORYBARRIER mb() +#define MEMCPY(dest, src, len) memcpy(dest, src, len) +#define MEMCPY_TOIO(dest, src, len) memcpy_toio(dest, src, len) +#define MEMCPY_FROMIO(dest, src, len) memcpy_fromio(dest, src, len) + +#define CHANNEL_GUID_MISMATCH(chType, chName, field, expected, actual, fil, \ + lin, logCtx) \ + do { \ + pr_err("Channel mismatch on channel=%s(%pUL) field=%s expected=%pUL actual=%pUL @%s:%d\n", \ + chName, &chType, field, \ + &expected, &actual, \ + fil, lin); \ + } while (0) +#define CHANNEL_U32_MISMATCH(chType, chName, field, expected, actual, fil, \ + lin, logCtx) \ + do { \ + pr_err("Channel mismatch on channel=%s(%pUL) field=%s expected=0x%-8.8lx actual=0x%-8.8lx @%s:%d\n", \ + chName, &chType, field, \ + (unsigned long)expected, (unsigned long)actual, \ + fil, lin); \ + } while (0) + +#define CHANNEL_U64_MISMATCH(chType, chName, field, expected, actual, fil, \ + lin, logCtx) \ + do { \ + pr_err("Channel mismatch on channel=%s(%pUL) field=%s expected=0x%-8.8Lx actual=0x%-8.8Lx @%s:%d\n", \ + chName, &chType, field, \ + (unsigned long long)expected, \ + (unsigned long long)actual, \ + fil, lin); \ + } while (0) + +#define UltraLogEvent(logCtx, EventId, Severity, SubsystemMask, pFunctionName, \ + LineNumber, Str, args...) \ + pr_info(Str, ## args) + +#else +#define MEMCPY(dest, src, len) memcpy(dest, src, len) + +#define MEMORYBARRIER mb() + +#define CHANNEL_GUID_MISMATCH(chType, chName, field, expected, actual, fil, \ + lin, logCtx) \ + do { \ + syslog(LOG_USER | LOG_ERR, \ + "Channel mismatch on channel=%s(%pUL) field=%s expected=%pUL actual=%pUL @%s:%d", \ + chName, &chType, field, \ + &expected, &actual, \ + fil, lin); \ + } while (0) + +#define CHANNEL_U32_MISMATCH(chType, chName, field, expected, actual, fil, \ + lin, logCtx) \ + do { \ + syslog(LOG_USER | LOG_ERR, \ + "Channel mismatch on channel=%s(%pUL) field=%s expected=0x%-8.8lx actual=0x%-8.8lx @%s:%d", \ + chName, chType, field, \ + (unsigned long)expected, (unsigned long)actual, \ + fil, lin); \ + } while (0) + +#define CHANNEL_U64_MISMATCH(chType, chName, field, expected, actual, fil, \ + lin, logCtx) \ + do { \ + syslog(LOG_USER | LOG_ERR, \ + "Channel mismatch on channel=%s(%pUL) field=%s expected=0x%-8.8Lx actual=0x%-8.8Lx @%s:%d", \ + chName, chType, field, \ + (unsigned long long)expected, \ + (unsigned long long)actual, \ + fil, lin); \ + } while (0) + +#define UltraLogEvent(logCtx, EventId, Severity, SubsystemMask, pFunctionName, \ + LineNumber, Str, args...) \ + syslog(LOG_USER | LOG_INFO, Str, ## args) +#endif + +#define VolatileBarrier() MEMORYBARRIER + +#endif + diff --git a/drivers/staging/unisys/include/guestlinuxdebug.h b/drivers/staging/unisys/include/guestlinuxdebug.h new file mode 100644 index 00000000000..efc4005368b --- /dev/null +++ b/drivers/staging/unisys/include/guestlinuxdebug.h @@ -0,0 +1,182 @@ +/* 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. + */ + +#ifndef __GUESTLINUXDEBUG_H__ +#define __GUESTLINUXDEBUG_H__ + +/* +* This file contains supporting interface for "vmcallinterface.h", particularly +* regarding adding additional structure and functionality to linux +* ISSUE_IO_VMCALL_POSTCODE_SEVERITY */ + + +/******* INFO ON ISSUE_POSTCODE_LINUX() BELOW *******/ +#include "vmcallinterface.h" +typedef enum { /* POSTCODE driver identifier tuples */ + /* visorchipset driver files */ + VISOR_CHIPSET_PC = 0xA0, + VISOR_CHIPSET_PC_controlvm_c = 0xA1, + VISOR_CHIPSET_PC_controlvm_cm2 = 0xA2, + VISOR_CHIPSET_PC_controlvm_direct_c = 0xA3, + VISOR_CHIPSET_PC_file_c = 0xA4, + VISOR_CHIPSET_PC_parser_c = 0xA5, + VISOR_CHIPSET_PC_testing_c = 0xA6, + VISOR_CHIPSET_PC_visorchipset_main_c = 0xA7, + VISOR_CHIPSET_PC_visorswitchbus_c = 0xA8, + /* visorbus driver files */ + VISOR_BUS_PC = 0xB0, + VISOR_BUS_PC_businst_attr_c = 0xB1, + VISOR_BUS_PC_channel_attr_c = 0xB2, + VISOR_BUS_PC_devmajorminor_attr_c = 0xB3, + VISOR_BUS_PC_visorbus_main_c = 0xB4, + /* visorclientbus driver files */ + VISOR_CLIENT_BUS_PC = 0xC0, + VISOR_CLIENT_BUS_PC_visorclientbus_main_c = 0xC1, + /* virt hba driver files */ + VIRT_HBA_PC = 0xC2, + VIRT_HBA_PC_virthba_c = 0xC3, + /* virtpci driver files */ + VIRT_PCI_PC = 0xC4, + VIRT_PCI_PC_virtpci_c = 0xC5, + /* virtnic driver files */ + VIRT_NIC_PC = 0xC6, + VIRT_NIC_P_virtnic_c = 0xC7, + /* uislib driver files */ + UISLIB_PC = 0xD0, + UISLIB_PC_uislib_c = 0xD1, + UISLIB_PC_uisqueue_c = 0xD2, + UISLIB_PC_uisthread_c = 0xD3, + UISLIB_PC_uisutils_c = 0xD4, +} DRIVER_PC; + +typedef enum { /* POSTCODE event identifier tuples */ + ATTACH_PORT_ENTRY_PC = 0x001, + ATTACH_PORT_FAILURE_PC = 0x002, + ATTACH_PORT_SUCCESS_PC = 0x003, + BUS_FAILURE_PC = 0x004, + BUS_CREATE_ENTRY_PC = 0x005, + BUS_CREATE_FAILURE_PC = 0x006, + BUS_CREATE_EXIT_PC = 0x007, + BUS_CONFIGURE_ENTRY_PC = 0x008, + BUS_CONFIGURE_FAILURE_PC = 0x009, + BUS_CONFIGURE_EXIT_PC = 0x00A, + CHIPSET_INIT_ENTRY_PC = 0x00B, + CHIPSET_INIT_SUCCESS_PC = 0x00C, + CHIPSET_INIT_FAILURE_PC = 0x00D, + CHIPSET_INIT_EXIT_PC = 0x00E, + CREATE_WORKQUEUE_PC = 0x00F, + CREATE_WORKQUEUE_FAILED_PC = 0x0A0, + CONTROLVM_INIT_FAILURE_PC = 0x0A1, + DEVICE_CREATE_ENTRY_PC = 0x0A2, + DEVICE_CREATE_FAILURE_PC = 0x0A3, + DEVICE_CREATE_SUCCESS_PC = 0x0A4, + DEVICE_CREATE_EXIT_PC = 0x0A5, + DEVICE_ADD_PC = 0x0A6, + DEVICE_REGISTER_FAILURE_PC = 0x0A7, + DEVICE_CHANGESTATE_ENTRY_PC = 0x0A8, + DEVICE_CHANGESTATE_FAILURE_PC = 0x0A9, + DEVICE_CHANGESTATE_EXIT_PC = 0x0AA, + DRIVER_ENTRY_PC = 0x0AB, + DRIVER_EXIT_PC = 0x0AC, + MALLOC_FAILURE_PC = 0x0AD, + QUEUE_DELAYED_WORK_PC = 0x0AE, + UISLIB_THREAD_FAILURE_PC = 0x0B7, + VBUS_CHANNEL_ENTRY_PC = 0x0B8, + VBUS_CHANNEL_FAILURE_PC = 0x0B9, + VBUS_CHANNEL_EXIT_PC = 0x0BA, + VHBA_CREATE_ENTRY_PC = 0x0BB, + VHBA_CREATE_FAILURE_PC = 0x0BC, + VHBA_CREATE_EXIT_PC = 0x0BD, + VHBA_CREATE_SUCCESS_PC = 0x0BE, + VHBA_COMMAND_HANDLER_PC = 0x0BF, + VHBA_PROBE_ENTRY_PC = 0x0C0, + VHBA_PROBE_FAILURE_PC = 0x0C1, + VHBA_PROBE_EXIT_PC = 0x0C2, + VNIC_CREATE_ENTRY_PC = 0x0C3, + VNIC_CREATE_FAILURE_PC = 0x0C4, + VNIC_CREATE_SUCCESS_PC = 0x0C5, + VNIC_PROBE_ENTRY_PC = 0x0C6, + VNIC_PROBE_FAILURE_PC = 0x0C7, + VNIC_PROBE_EXIT_PC = 0x0C8, + VPCI_CREATE_ENTRY_PC = 0x0C9, + VPCI_CREATE_FAILURE_PC = 0x0CA, + VPCI_CREATE_EXIT_PC = 0x0CB, + VPCI_PROBE_ENTRY_PC = 0x0CC, + VPCI_PROBE_FAILURE_PC = 0x0CD, + VPCI_PROBE_EXIT_PC = 0x0CE, + CRASH_DEV_ENTRY_PC = 0x0CF, + CRASH_DEV_EXIT_PC = 0x0D0, + CRASH_DEV_HADDR_NULL = 0x0D1, + CRASH_DEV_CONTROLVM_NULL = 0x0D2, + CRASH_DEV_RD_BUS_FAIULRE_PC = 0x0D3, + CRASH_DEV_RD_DEV_FAIULRE_PC = 0x0D4, + CRASH_DEV_BUS_NULL_FAILURE_PC = 0x0D5, + CRASH_DEV_DEV_NULL_FAILURE_PC = 0x0D6, + CRASH_DEV_CTRL_RD_FAILURE_PC = 0x0D7, + CRASH_DEV_COUNT_FAILURE_PC = 0x0D8, + SAVE_MSG_BUS_FAILURE_PC = 0x0D9, + SAVE_MSG_DEV_FAILURE_PC = 0x0DA, + CALLHOME_INIT_FAILURE_PC = 0x0DB +} EVENT_PC; + +#ifdef __GNUC__ + +#define POSTCODE_SEVERITY_ERR DIAG_SEVERITY_ERR +#define POSTCODE_SEVERITY_WARNING DIAG_SEVERITY_WARNING +#define POSTCODE_SEVERITY_INFO DIAG_SEVERITY_PRINT /* TODO-> Info currently + * doesnt show, so we + * set info=warning */ +/* example call of POSTCODE_LINUX_2(VISOR_CHIPSET_PC, POSTCODE_SEVERITY_ERR); + * Please also note that the resulting postcode is in hex, so if you are + * searching for the __LINE__ number, convert it first to decimal. The line + * number combined with driver and type of call, will allow you to track down + * exactly what line an error occurred on, or where the last driver + * entered/exited from. + */ + +/* BASE FUNCTIONS */ +#define POSTCODE_LINUX_A(DRIVER_PC, EVENT_PC, pc32bit, severity) \ +do { \ + unsigned long long post_code_temp; \ + post_code_temp = (((U64)DRIVER_PC) << 56) | (((U64)EVENT_PC) << 44) | \ + ((((U64)__LINE__) & 0xFFF) << 32) | \ + (((U64)pc32bit) & 0xFFFFFFFF); \ + ISSUE_IO_VMCALL_POSTCODE_SEVERITY(post_code_temp, severity); \ +} while (0) + +#define POSTCODE_LINUX_B(DRIVER_PC, EVENT_PC, pc16bit1, pc16bit2, severity) \ +do { \ + unsigned long long post_code_temp; \ + post_code_temp = (((U64)DRIVER_PC) << 56) | (((U64)EVENT_PC) << 44) | \ + ((((U64)__LINE__) & 0xFFF) << 32) | \ + ((((U64)pc16bit1) & 0xFFFF) << 16) | \ + (((U64)pc16bit2) & 0xFFFF); \ + ISSUE_IO_VMCALL_POSTCODE_SEVERITY(post_code_temp, severity); \ +} while (0) + +/* MOST COMMON */ +#define POSTCODE_LINUX_2(EVENT_PC, severity) \ + POSTCODE_LINUX_A(CURRENT_FILE_PC, EVENT_PC, 0x0000, severity); + +#define POSTCODE_LINUX_3(EVENT_PC, pc32bit, severity) \ + POSTCODE_LINUX_A(CURRENT_FILE_PC, EVENT_PC, pc32bit, severity); + + +#define POSTCODE_LINUX_4(EVENT_PC, pc16bit1, pc16bit2, severity) \ + POSTCODE_LINUX_B(CURRENT_FILE_PC, EVENT_PC, pc16bit1, \ + pc16bit2, severity); + +#endif +#endif diff --git a/drivers/staging/unisys/include/periodic_work.h b/drivers/staging/unisys/include/periodic_work.h new file mode 100644 index 00000000000..418ba634e1a --- /dev/null +++ b/drivers/staging/unisys/include/periodic_work.h @@ -0,0 +1,40 @@ +/* periodic_work.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. + */ + +#ifndef __PERIODIC_WORK_H__ +#define __PERIODIC_WORK_H__ + +#include "timskmod.h" + + + +/* PERIODIC_WORK an opaque structure to users. + * Fields are declared only in the implementation .c files. + */ +typedef struct PERIODIC_WORK_Tag PERIODIC_WORK; + +PERIODIC_WORK *visor_periodic_work_create(ulong jiffy_interval, + struct workqueue_struct *workqueue, + void (*workfunc)(void *), + void *workfuncarg, + const char *devnam); +void visor_periodic_work_destroy(PERIODIC_WORK *periodic_work); +BOOL visor_periodic_work_nextperiod(PERIODIC_WORK *periodic_work); +BOOL visor_periodic_work_start(PERIODIC_WORK *periodic_work); +BOOL visor_periodic_work_stop(PERIODIC_WORK *periodic_work); + +#endif diff --git a/drivers/staging/unisys/include/procobjecttree.h b/drivers/staging/unisys/include/procobjecttree.h new file mode 100644 index 00000000000..1174056ec3d --- /dev/null +++ b/drivers/staging/unisys/include/procobjecttree.h @@ -0,0 +1,48 @@ +/* procobjecttree.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. + */ + +/** @file ********************************************************************* + * + * This describes the interfaces necessary for creating a tree of types, + * objects, and properties in /proc. + * + ****************************************************************************** + */ + +#ifndef __PROCOBJECTTREE_H__ +#define __PROCOBJECTTREE_H__ + +#include "uniklog.h" +#include "timskmod.h" + +/* These are opaque structures to users. + * Fields are declared only in the implementation .c files. + */ +typedef struct MYPROCOBJECT_Tag MYPROCOBJECT; +typedef struct MYPROCTYPE_Tag MYPROCTYPE; + +MYPROCOBJECT *visor_proc_CreateObject(MYPROCTYPE *type, const char *name, + void *context); +void visor_proc_DestroyObject(MYPROCOBJECT *obj); +MYPROCTYPE *visor_proc_CreateType(struct proc_dir_entry *procRootDir, + const char **name, + const char **propertyNames, + void (*show_property)(struct seq_file *, + void *, int)); +void visor_proc_DestroyType(MYPROCTYPE *type); + +#endif diff --git a/drivers/staging/unisys/include/sparstop.h b/drivers/staging/unisys/include/sparstop.h new file mode 100644 index 00000000000..05837399a74 --- /dev/null +++ b/drivers/staging/unisys/include/sparstop.h @@ -0,0 +1,30 @@ +/* sparstop.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. + */ + +#ifndef __SPARSTOP_H__ +#define __SPARSTOP_H__ + +#include "timskmod.h" +#include "version.h" +#include <linux/ctype.h> + +typedef void (*SPARSTOP_COMPLETE_FUNC) (void *context, int status); + +int sp_stop(void *context, SPARSTOP_COMPLETE_FUNC get_complete_func); +void test_remove_stop_device(void); + +#endif diff --git a/drivers/staging/unisys/include/timskmod.h b/drivers/staging/unisys/include/timskmod.h new file mode 100644 index 00000000000..ecf1a6fac7a --- /dev/null +++ b/drivers/staging/unisys/include/timskmod.h @@ -0,0 +1,324 @@ +/* timskmod.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. + */ + +#ifndef __TIMSKMOD_H__ +#define __TIMSKMOD_H__ + +#include <linux/version.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/kobject.h> +#include <linux/sysfs.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/vmalloc.h> +#include <linux/proc_fs.h> +#include <linux/cdev.h> +#include <linux/types.h> +#include <asm/irq.h> +#include <linux/io.h> +#include <asm/dma.h> +#include <linux/uaccess.h> +#include <linux/list.h> +#include <linux/poll.h> +/* #define EXPORT_SYMTAB */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/fcntl.h> +#include <linux/aio.h> +#include <linux/workqueue.h> +#include <linux/kthread.h> +#include <linux/seq_file.h> +#include <linux/mm.h> + +/* #define DEBUG */ +#ifndef BOOL +#define BOOL int +#endif +#define FALSE 0 +#define TRUE 1 +#if !defined SUCCESS +#define SUCCESS 0 +#endif +#define FAILURE (-1) +#define DRIVERNAMEMAX 50 +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define STRUCTSEQUAL(x, y) (memcmp(&x, &y, sizeof(x)) == 0) +#ifndef HOSTADDRESS +#define HOSTADDRESS unsigned long long +#endif + +typedef long VMMIO; /**< Virtual MMIO address (returned from ioremap), which + * is a virtual address pointer to a memory-mapped region. + * These are declared as "long" instead of u32* to force you to + * use readb()/writeb()/memcpy_fromio()/etc to access them. + * (On x86 we could probably get away with treating them as + * pointers.) + */ +typedef long VMMIO8; /**< #VMMIO pointing to 8-bit data */ +typedef long VMMIO16;/**< #VMMIO pointing to 16-bit data */ +typedef long VMMIO32;/**< #VMMIO pointing to 32-bit data */ + +#define LOCKSEM(sem) down_interruptible(sem) +#define LOCKSEM_UNINTERRUPTIBLE(sem) down(sem) +#define UNLOCKSEM(sem) up(sem) + +/** lock read/write semaphore for reading. + Note that all read/write semaphores are of the "uninterruptible" variety. + @param sem (rw_semaphore *) points to semaphore to lock + */ +#define LOCKREADSEM(sem) down_read(sem) + +/** unlock read/write semaphore for reading. + Note that all read/write semaphores are of the "uninterruptible" variety. + @param sem (rw_semaphore *) points to semaphore to unlock + */ +#define UNLOCKREADSEM(sem) up_read(sem) + +/** lock read/write semaphore for writing. + Note that all read/write semaphores are of the "uninterruptible" variety. + @param sem (rw_semaphore *) points to semaphore to lock + */ +#define LOCKWRITESEM(sem) down_write(sem) + +/** unlock read/write semaphore for writing. + Note that all read/write semaphores are of the "uninterruptible" variety. + @param sem (rw_semaphore *) points to semaphore to unlock + */ +#define UNLOCKWRITESEM(sem) up_write(sem) + +#ifdef ENABLE_RETURN_TRACE +#define RETTRACE(x) \ + do { \ + if (1) { \ + INFODRV("RET 0x%lx in %s", \ + (ulong)(x), __func__); \ + } \ + } while (0) +#else +#define RETTRACE(x) +#endif + +/** Try to evaulate the provided expression, and do a RETINT(x) iff + * the expression evaluates to < 0. + * @param x the expression to try + */ +#define ASSERT(cond) \ + do { if (!(cond)) \ + HUHDRV("ASSERT failed - %s", \ + __stringify(cond)); \ + } while (0) + +#define sizeofmember(TYPE, MEMBER) (sizeof(((TYPE *)0)->MEMBER)) +/** "Covered quotient" function */ +#define COVQ(v, d) (((v) + (d) - 1) / (d)) +#define SWAPPOINTERS(p1, p2) \ + do { \ + void *SWAPPOINTERS_TEMP = (void *)p1; \ + (void *)(p1) = (void *)(p2); \ + (void *)(p2) = SWAPPOINTERS_TEMP; \ + } while (0) + +/** + * @addtogroup driverlogging + * @{ + */ + +#define PRINTKDRV(fmt, args...) LOGINF(fmt, ## args) +#define TBDDRV(fmt, args...) LOGERR(fmt, ## args) +#define HUHDRV(fmt, args...) LOGERR(fmt, ## args) +#define ERRDRV(fmt, args...) LOGERR(fmt, ## args) +#define WARNDRV(fmt, args...) LOGWRN(fmt, ## args) +#define SECUREDRV(fmt, args...) LOGWRN(fmt, ## args) +#define INFODRV(fmt, args...) LOGINF(fmt, ## args) +#define DEBUGDRV(fmt, args...) DBGINF(fmt, ## args) + +#define PRINTKDEV(devname, fmt, args...) LOGINFDEV(devname, fmt, ## args) +#define TBDDEV(devname, fmt, args...) LOGERRDEV(devname, fmt, ## args) +#define HUHDEV(devname, fmt, args...) LOGERRDEV(devname, fmt, ## args) +#define ERRDEV(devname, fmt, args...) LOGERRDEV(devname, fmt, ## args) +#define ERRDEVX(devno, fmt, args...) LOGERRDEVX(devno, fmt, ## args) +#define WARNDEV(devname, fmt, args...) LOGWRNDEV(devname, fmt, ## args) +#define SECUREDEV(devname, fmt, args...) LOGWRNDEV(devname, fmt, ## args) +#define INFODEV(devname, fmt, args...) LOGINFDEV(devname, fmt, ## args) +#define INFODEVX(devno, fmt, args...) LOGINFDEVX(devno, fmt, ## args) +#define DEBUGDEV(devname, fmt, args...) DBGINFDEV(devname, fmt, ## args) + + +/* @} */ + +/** Verifies the consistency of your PRIVATEDEVICEDATA structure using + * conventional "signature" fields: + * <p> + * - sig1 should contain the size of the structure + * - sig2 should contain a pointer to the beginning of the structure + */ +#define DDLOOKSVALID(dd) \ + ((dd != NULL) && \ + ((dd)->sig1 == sizeof(PRIVATEDEVICEDATA)) && \ + ((dd)->sig2 == dd)) + +/** Verifies the consistency of your PRIVATEFILEDATA structure using + * conventional "signature" fields: + * <p> + * - sig1 should contain the size of the structure + * - sig2 should contain a pointer to the beginning of the structure + */ +#define FDLOOKSVALID(fd) \ + ((fd != NULL) && \ + ((fd)->sig1 == sizeof(PRIVATEFILEDATA)) && \ + ((fd)->sig2 == fd)) + +/** Locks dd->lockDev if you havn't already locked it */ +#define LOCKDEV(dd) \ + { \ + if (!lockedDev) { \ + spin_lock(&dd->lockDev); \ + lockedDev = TRUE; \ + } \ + } + +/** Unlocks dd->lockDev if you previously locked it */ +#define UNLOCKDEV(dd) \ + { \ + if (lockedDev) { \ + spin_unlock(&dd->lockDev); \ + lockedDev = FALSE; \ + } \ + } + +/** Locks dd->lockDevISR if you havn't already locked it */ +#define LOCKDEVISR(dd) \ + { \ + if (!lockedDevISR) { \ + spin_lock_irqsave(&dd->lockDevISR, flags); \ + lockedDevISR = TRUE; \ + } \ + } + +/** Unlocks dd->lockDevISR if you previously locked it */ +#define UNLOCKDEVISR(dd) \ + { \ + if (lockedDevISR) { \ + spin_unlock_irqrestore(&dd->lockDevISR, flags); \ + lockedDevISR = FALSE; \ + } \ + } + +/** Locks LockGlobalISR if you havn't already locked it */ +#define LOCKGLOBALISR \ + { \ + if (!lockedGlobalISR) { \ + spin_lock_irqsave(&LockGlobalISR, flags); \ + lockedGlobalISR = TRUE; \ + } \ + } + +/** Unlocks LockGlobalISR if you previously locked it */ +#define UNLOCKGLOBALISR \ + { \ + if (lockedGlobalISR) { \ + spin_unlock_irqrestore(&LockGlobalISR, flags); \ + lockedGlobalISR = FALSE; \ + } \ + } + +/** Locks LockGlobal if you havn't already locked it */ +#define LOCKGLOBAL \ + { \ + if (!lockedGlobal) { \ + spin_lock(&LockGlobal); \ + lockedGlobal = TRUE; \ + } \ + } + +/** Unlocks LockGlobal if you previously locked it */ +#define UNLOCKGLOBAL \ + { \ + if (lockedGlobal) { \ + spin_unlock(&LockGlobal); \ + lockedGlobal = FALSE; \ + } \ + } + +/** Use this at the beginning of functions where you intend to + * use #LOCKDEV/#UNLOCKDEV, #LOCKDEVISR/#UNLOCKDEVISR, + * #LOCKGLOBAL/#UNLOCKGLOBAL, #LOCKGLOBALISR/#UNLOCKGLOBALISR. + * + * Note that __attribute__((unused)) is how you tell GNU C to suppress + * any warning messages about the variable being unused. + */ +#define LOCKPREAMBLE \ + ulong flags __attribute__((unused)) = 0; \ + BOOL lockedDev __attribute__((unused)) = FALSE; \ + BOOL lockedDevISR __attribute__((unused)) = FALSE; \ + BOOL lockedGlobal __attribute__((unused)) = FALSE; \ + BOOL lockedGlobalISR __attribute__((unused)) = FALSE + + + +/** Sleep for an indicated number of seconds (for use in kernel mode). + * @param x the number of seconds to sleep. + */ +#define SLEEP(x) \ + do { current->state = TASK_INTERRUPTIBLE; \ + schedule_timeout((x)*HZ); \ + } while (0) + +/** Sleep for an indicated number of jiffies (for use in kernel mode). + * @param x the number of jiffies to sleep. + */ +#define SLEEPJIFFIES(x) \ + do { current->state = TASK_INTERRUPTIBLE; \ + schedule_timeout(x); \ + } while (0) + +#ifndef max +#define max(a, b) (((a) > (b)) ? (a):(b)) +#endif + +static inline struct cdev *cdev_alloc_init(struct module *owner, + const struct file_operations *fops) +{ + struct cdev *cdev = NULL; + cdev = cdev_alloc(); + if (!cdev) + return NULL; + cdev->ops = fops; + cdev->owner = owner; + + /* Note that the memory allocated for cdev will be deallocated + * when the usage count drops to 0, because it is controlled + * by a kobject of type ktype_cdev_dynamic. (This + * deallocation could very well happen outside of our kernel + * module, like via the cdev_put in __fput() for example.) + */ + return cdev; +} + +#include "timskmodutils.h" + +#endif diff --git a/drivers/staging/unisys/include/timskmodutils.h b/drivers/staging/unisys/include/timskmodutils.h new file mode 100644 index 00000000000..c316c94ea19 --- /dev/null +++ b/drivers/staging/unisys/include/timskmodutils.h @@ -0,0 +1,77 @@ +/* timskmodutils.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. + */ + +#ifndef __TIMSKMODUTILS_H__ +#define __TIMSKMODUTILS_H__ + +#include "timskmod.h" + +void *kmalloc_kernel(size_t siz); +void myprintk(const char *myDrvName, const char *devname, + const char *template, ...); + +/*--------------------------------* + *--- GENERAL MESSAGEQ STUFF ---* + *--------------------------------*/ + +struct MessageQEntry; + +/** the data structure used to hold an arbitrary data item that you want + * to place on a #MESSAGEQ. Declare and initialize as follows: + * + * This structure should be considered opaque; the client using it should + * never access the fields directly. + * Refer to these functions for more info: + * + * @ingroup messageq + */ +typedef struct MessageQEntry { + void *data; + struct MessageQEntry *qNext; + struct MessageQEntry *qPrev; +} MESSAGEQENTRY; + +/** the data structure used to hold a FIFO queue of #MESSAGEQENTRY<b></b>s. + * Declare and initialize as follows: + * @code + * MESSAGEQ myQueue; + * @endcode + * This structure should be considered opaque; the client using it should + * never access the fields directly. + * Refer to these functions for more info: + * + * @ingroup messageq + */ +typedef struct MessageQ { + MESSAGEQENTRY *qHead; + MESSAGEQENTRY *qTail; + struct semaphore nQEntries; + spinlock_t queueLock; +} MESSAGEQ; + +char *cyclesToSeconds(u64 cycles, u64 cyclesPerSecond, + char *buf, size_t bufsize); +char *cyclesToIterationSeconds(u64 cycles, u64 cyclesPerSecond, + u64 iterations, char *buf, size_t bufsize); +char *cyclesToSomethingsPerSecond(u64 cycles, u64 cyclesPerSecond, + u64 somethings, char *buf, size_t bufsize); +struct seq_file *visor_seq_file_new_buffer(void *buf, size_t buf_size); +void visor_seq_file_done_buffer(struct seq_file *m); + +extern int unisys_spar_platform; + +#endif diff --git a/drivers/staging/unisys/include/uisqueue.h b/drivers/staging/unisys/include/uisqueue.h new file mode 100644 index 00000000000..2a5bea3b332 --- /dev/null +++ b/drivers/staging/unisys/include/uisqueue.h @@ -0,0 +1,441 @@ +/* uisqueue.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 IO Virtualization header NOTE: This file contains only Linux + * specific structs. All OS-independent structs are in iochannel.h.xx + */ + +#ifndef __UISQUEUE_H__ +#define __UISQUEUE_H__ + +#include "linux/version.h" +#include "iochannel.h" +#include "uniklog.h" +#include <linux/atomic.h> +#include <linux/semaphore.h> +#include <linux/uuid.h> + +#include "controlvmchannel.h" +#include "controlvmcompletionstatus.h" + +struct uisqueue_info { + + CHANNEL_HEADER __iomem *chan; + /* channel containing queues in which scsi commands & + * responses are queued + */ + U64 packets_sent; + U64 packets_received; + U64 interrupts_sent; + U64 interrupts_received; + U64 max_not_empty_cnt; + U64 total_wakeup_cnt; + U64 non_empty_wakeup_cnt; + + struct { + SIGNAL_QUEUE_HEADER Reserved1; /* */ + SIGNAL_QUEUE_HEADER Reserved2; /* */ + } safe_uis_queue; + unsigned int (*send_int_if_needed)(struct uisqueue_info *info, + unsigned int whichcqueue, + unsigned char issueInterruptIfEmpty, + U64 interruptHandle, + unsigned char io_termination); +}; + +/* uisqueue_put_cmdrsp_with_lock_client queues a commmand or response + * to the specified queue, at the tail if the queue is full but + * oktowait == 0, then it return 0 indicating failure. otherwise it + * wait for the queue to become non-full. If command is queued, return + * 1 for success. + */ +#define DONT_ISSUE_INTERRUPT 0 +#define ISSUE_INTERRUPT 1 + +#define DONT_WAIT 0 +#define OK_TO_WAIT 1 +#define UISLIB_LOCK_PREFIX \ + ".section .smp_locks,\"a\"\n" \ + _ASM_ALIGN "\n" \ + _ASM_PTR "661f\n" /* address */ \ + ".previous\n" \ + "661:\n\tlock; " + +unsigned long long uisqueue_InterlockedOr(unsigned long long __iomem *Target, + unsigned long long Set); +unsigned long long uisqueue_InterlockedAnd(unsigned long long __iomem *Target, + unsigned long long Set); + +unsigned int uisqueue_send_int_if_needed(struct uisqueue_info *pqueueinfo, + unsigned int whichqueue, + unsigned char issueInterruptIfEmpty, + U64 interruptHandle, + unsigned char io_termination); + +int uisqueue_put_cmdrsp_with_lock_client(struct uisqueue_info *queueinfo, + struct uiscmdrsp *cmdrsp, + unsigned int queue, + void *insertlock, + unsigned char issueInterruptIfEmpty, + U64 interruptHandle, + char oktowait, + U8 *channelId); + +/* uisqueue_get_cmdrsp gets the cmdrsp entry at the head of the queue + * and copies it to the area pointed by cmdrsp param. + * returns 0 if queue is empty, 1 otherwise + */ +int + +uisqueue_get_cmdrsp(struct uisqueue_info *queueinfo, void *cmdrsp, + unsigned int queue); + +#define MAX_NAME_SIZE_UISQUEUE 64 + +struct extport_info { + U8 valid:1; + /* if 1, indicates this extport slot is occupied + * if 0, indicates that extport slot is unoccupied */ + + U32 num_devs_using; + /* When extport is added, this is set to 0. For exports + * located in NETWORK switches: + * Each time a VNIC, i.e., intport, is added to the switch this + * is used to assign a pref_pnic for the VNIC and when assigned + * to a VNIC this counter is incremented. When a VNIC is + * deleted, the extport corresponding to the VNIC's pref_pnic + * is located and its num_devs_using is decremented. For VNICs, + * num_devs_using is basically used to load-balance transmit + * traffic from VNICs. + */ + + struct switch_info *swtch; + struct PciId pci_id; + char name[MAX_NAME_SIZE_UISQUEUE]; + union { + struct vhba_wwnn wwnn; + unsigned char macaddr[MAX_MACADDR_LEN]; + }; +}; + +struct device_info { + void __iomem *chanptr; + U64 channelAddr; + U64 channelBytes; + uuid_le channelTypeGuid; + uuid_le devInstGuid; + struct InterruptInfo intr; + struct switch_info *swtch; + char devid[30]; /* "vbus<busno>:dev<devno>" */ + U16 polling; + struct semaphore interrupt_callback_lock; + U32 busNo; + U32 devNo; + int (*interrupt)(void *); + void *interrupt_context; + void *private_data; + struct list_head list_polling_device_channels; + unsigned long long moved_to_tail_cnt; + unsigned long long first_busy_cnt; + unsigned long long last_on_list_cnt; +}; + +typedef enum { + RECOVERY_LAN = 1, + IB_LAN = 2 +} SWITCH_TYPE; + +struct bus_info { + U32 busNo, deviceCount; + struct device_info **device; + U64 guestHandle, recvBusInterruptHandle; + uuid_le busInstGuid; + ULTRA_VBUS_CHANNEL_PROTOCOL __iomem *pBusChannel; + int busChannelBytes; + struct proc_dir_entry *proc_dir; /* proc/uislib/vbus/<x> */ + struct proc_dir_entry *proc_info; /* proc/uislib/vbus/<x>/info */ + char name[25]; + char partitionName[99]; + struct bus_info *next; + U8 localVnic; /* 1 if local vnic created internally + * by IOVM; 0 otherwise... */ +}; + +#define DEDICATED_SWITCH(pSwitch) ((pSwitch->extPortCount == 1) && \ + (pSwitch->intPortCount == 1)) + +struct sn_list_entry { + struct uisscsi_dest pdest; /* scsi bus, target, lun for + * phys disk */ + U8 sernum[MAX_SERIAL_NUM]; /* serial num of physical + * disk.. The length is always + * MAX_SERIAL_NUM, padded with + * spaces */ + struct sn_list_entry *next; +}; + +struct networkPolicy { + U32 promiscuous:1; + U32 macassign:1; + U32 peerforwarding:1; + U32 nonotify:1; + U32 standby:1; + U32 callhome:2; + char ip_addr[30]; +}; + +/* + * IO messages sent to UisnicControlChanFunc & UissdControlChanFunc by + * code that processes the ControlVm channel messages. + */ + + +typedef enum { + IOPART_ADD_VNIC, + IOPART_DEL_VNIC, + IOPART_DEL_ALL_VNICS, + IOPART_ADD_VHBA, + IOPART_ADD_VDISK, + IOPART_DEL_VHBA, + IOPART_DEL_VDISK, + IOPART_DEL_ALL_VDISKS_FOR_VHBA, + IOPART_DEL_ALL_VHBAS, + IOPART_ATTACH_PHBA, + IOPART_DETACH_PHBA, /* 10 */ + IOPART_ATTACH_PNIC, + IOPART_DETACH_PNIC, + IOPART_DETACH_VHBA, + IOPART_DETACH_VNIC, + IOPART_PAUSE_VDISK, + IOPART_RESUME_VDISK, + IOPART_ADD_DEVICE, /* add generic device */ + IOPART_DEL_DEVICE, /* del generic device */ +} IOPART_MSG_TYPE; + +struct add_virt_iopart { + void *chanptr; /* pointer to data channel */ + U64 guestHandle; /* used to convert guest physical + * address to real physical address + * for DMA, for ex. */ + U64 recvBusInterruptHandle; /* used to register to receive + * bus level interrupts. */ + struct InterruptInfo intr; /* contains recv & send + * interrupt info */ + /* recvInterruptHandle is used to register to receive + * interrupts on the data channel. Used by GuestLinux/Windows + * IO drivers to connect to interrupt. sendInterruptHandle is + * used by IOPart drivers as parameter to + * Issue_VMCALL_IO_QUEUE_TRANSITION to interrupt thread in + * guest linux/windows IO drivers when data channel queue for + * vhba/vnic goes from EMPTY to NON-EMPTY. */ + struct switch_info *swtch; /* pointer to the virtual + * switch to which the vnic is + * connected */ + + U8 useG2GCopy; /* Used to determine if a virtual HBA + * needs to use G2G copy. */ + U8 Filler[7]; + + U32 busNo; + U32 devNo; + char *params; + ulong params_bytes; + +}; + +struct add_vdisk_iopart { + void *chanptr; /* pointer to data channel */ + int implicit; + struct uisscsi_dest vdest; /* scsi bus, target, lun for virt disk */ + struct uisscsi_dest pdest; /* scsi bus, target, lun for phys disk */ + U8 sernum[MAX_SERIAL_NUM]; /* serial num of physical disk */ + U32 serlen; /* length of serial num */ + U32 busNo; + U32 devNo; +}; + +struct del_vdisk_iopart { + void *chanptr; /* pointer to data channel */ + struct uisscsi_dest vdest; /* scsi bus, target, lun for virt disk */ + U32 busNo; + U32 devNo; +}; + +struct del_virt_iopart { + void *chanptr; /* pointer to data channel */ + U32 busNo; + U32 devNo; +}; + +struct det_virt_iopart { /* detach internal port */ + void *chanptr; /* pointer to data channel */ + struct switch_info *swtch; +}; + +struct paures_vdisk_iopart { + void *chanptr; /* pointer to data channel */ + struct uisscsi_dest vdest; /* scsi bus, target, lun for virt disk */ +}; + +struct add_switch_iopart { /* add switch */ + struct switch_info *swtch; + char *params; + ulong params_bytes; +}; + +struct del_switch_iopart { /* destroy switch */ + struct switch_info *swtch; +}; + +struct io_msgs { + + IOPART_MSG_TYPE msgtype; + + /* additional params needed by some messages */ + union { + struct add_virt_iopart add_vhba; + struct add_virt_iopart add_vnic; + struct add_vdisk_iopart add_vdisk; + struct del_virt_iopart del_vhba; + struct del_virt_iopart del_vnic; + struct det_virt_iopart det_vhba; + struct det_virt_iopart det_vnic; + struct del_vdisk_iopart del_vdisk; + struct del_virt_iopart del_all_vdisks_for_vhba; + struct add_virt_iopart add_device; + struct del_virt_iopart del_device; + struct det_virt_iopart det_intport; + struct add_switch_iopart add_switch; + struct del_switch_iopart del_switch; + struct extport_info *extPort; /* for attach or detach + * pnic/generic delete all + * vhbas/allvnics need no + * parameters */ + struct paures_vdisk_iopart paures_vdisk; + }; +}; + +/* +* Guest messages sent to VirtControlChanFunc by code that processes +* the ControlVm channel messages. +*/ + +typedef enum { + GUEST_ADD_VBUS, + GUEST_ADD_VHBA, + GUEST_ADD_VNIC, + GUEST_DEL_VBUS, + GUEST_DEL_VHBA, + GUEST_DEL_VNIC, + GUEST_DEL_ALL_VHBAS, + GUEST_DEL_ALL_VNICS, + GUEST_DEL_ALL_VBUSES, /* deletes all vhbas & vnics on all + * buses and deletes all buses */ + GUEST_PAUSE_VHBA, + GUEST_PAUSE_VNIC, + GUEST_RESUME_VHBA, + GUEST_RESUME_VNIC +} GUESTPART_MSG_TYPE; + +struct add_vbus_guestpart { + void __iomem *chanptr; /* pointer to data channel for bus - + * NOT YET USED */ + U32 busNo; /* bus number to be created/deleted */ + U32 deviceCount; /* max num of devices on bus */ + uuid_le busTypeGuid; /* indicates type of bus */ + uuid_le busInstGuid; /* instance guid for device */ +}; + +struct del_vbus_guestpart { + U32 busNo; /* bus number to be deleted */ + /* once we start using the bus's channel, add can dump busNo + * into the channel header and then delete will need only one + * parameter, chanptr. */ +}; + +struct add_virt_guestpart { + void __iomem *chanptr; /* pointer to data channel */ + U32 busNo; /* bus number for the operation */ + U32 deviceNo; /* number of device on the bus */ + uuid_le devInstGuid; /* instance guid for device */ + struct InterruptInfo intr; /* recv/send interrupt info */ + /* recvInterruptHandle contains info needed in order to + * register to receive interrupts on the data channel. + * sendInterruptHandle contains handle which is provided to + * monitor VMCALL that will cause an interrupt to be generated + * for the other end. + */ +}; + +struct pause_virt_guestpart { + void __iomem *chanptr; /* pointer to data channel */ +}; + +struct resume_virt_guestpart { + void __iomem *chanptr; /* pointer to data channel */ +}; + +struct del_virt_guestpart { + void __iomem *chanptr; /* pointer to data channel */ +}; + +struct init_chipset_guestpart { + U32 busCount; /* indicates the max number of busses */ + U32 switchCount; /* indicates the max number of switches */ +}; + +struct guest_msgs { + + GUESTPART_MSG_TYPE msgtype; + + /* additional params needed by messages */ + union { + struct add_vbus_guestpart add_vbus; + struct add_virt_guestpart add_vhba; + struct add_virt_guestpart add_vnic; + struct pause_virt_guestpart pause_vhba; + struct pause_virt_guestpart pause_vnic; + struct resume_virt_guestpart resume_vhba; + struct resume_virt_guestpart resume_vnic; + struct del_vbus_guestpart del_vbus; + struct del_virt_guestpart del_vhba; + struct del_virt_guestpart del_vnic; + struct del_vbus_guestpart del_all_vhbas; + struct del_vbus_guestpart del_all_vnics; + /* del_all_vbuses needs no parameters */ + }; + struct init_chipset_guestpart init_chipset; + +}; + +#ifndef __xg +#define __xg(x) ((volatile long *)(x)) +#endif + +/* +* Below code is a copy of Linux kernel's cmpxchg function located at +* this place +* http://tcsxeon:8080/source/xref/00trunk-AppOS-linux/include/asm-x86/cmpxchg_64.h#84 +* Reason for creating our own version of cmpxchg along with +* UISLIB_LOCK_PREFIX is to make the operation atomic even for non SMP +* guests. +*/ + +#define uislibcmpxchg64(p, o, n, s) cmpxchg(p, o, n) + +#endif /* __UISQUEUE_H__ */ diff --git a/drivers/staging/unisys/include/uisthread.h b/drivers/staging/unisys/include/uisthread.h new file mode 100644 index 00000000000..26837771b43 --- /dev/null +++ b/drivers/staging/unisys/include/uisthread.h @@ -0,0 +1,46 @@ +/* uisthread.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 thread utilities header */ +/*****************************************************************************/ + + +#ifndef __UISTHREAD_H__ +#define __UISTHREAD_H__ + + +#include "linux/completion.h" + +struct uisthread_info { + struct task_struct *task; + int id; + int should_stop; + struct completion has_stopped; +}; + + +/* returns 0 for failure, 1 for success */ +int uisthread_start( + struct uisthread_info *thrinfo, + int (*threadfn)(void *), + void *thrcontext, + char *name); + +void uisthread_stop(struct uisthread_info *thrinfo); + +#endif /* __UISTHREAD_H__ */ diff --git a/drivers/staging/unisys/include/uisutils.h b/drivers/staging/unisys/include/uisutils.h new file mode 100644 index 00000000000..70776c93cc5 --- /dev/null +++ b/drivers/staging/unisys/include/uisutils.h @@ -0,0 +1,356 @@ +/* uisutils.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 HBA utilities header + */ + +#ifndef __UISUTILS__H__ +#define __UISUTILS__H__ +#include <linux/string.h> +#include <linux/io.h> +#include <linux/sched.h> +#include <linux/gfp.h> +#include <linux/uuid.h> + +#include "vmcallinterface.h" +#include "channel.h" +#include "uisthread.h" +#include "uisqueue.h" +#include "diagnostics/appos_subsystems.h" +#include "vbusdeviceinfo.h" +#include <linux/atomic.h> + +/* This is the MAGIC number stuffed by virthba in host->this_id. Used to + * identify virtual hbas. + */ +#define UIS_MAGIC_VHBA 707 + +/* global function pointers that act as callback functions into + * uisnicmod, uissdmod, and virtpcimod + */ +extern int (*UisnicControlChanFunc)(struct io_msgs *); +extern int (*UissdControlChanFunc)(struct io_msgs *); +extern int (*VirtControlChanFunc)(struct guest_msgs *); + +/* Return values of above callback functions: */ +#define CCF_ERROR 0 /* completed and failed */ +#define CCF_OK 1 /* completed successfully */ +#define CCF_PENDING 2 /* operation still pending */ +extern atomic_t UisUtils_Registered_Services; + +typedef unsigned int MACARRAY[MAX_MACADDR_LEN]; +typedef struct ReqHandlerInfo_struct { + uuid_le switchTypeGuid; + int (*controlfunc)(struct io_msgs *); + unsigned long min_channel_bytes; + int (*Server_Channel_Ok)(unsigned long channelBytes); + int (*Server_Channel_Init) + (void *x, unsigned char *clientStr, U32 clientStrLen, U64 bytes); + char switch_type_name[99]; + struct list_head list_link; /* links into ReqHandlerInfo_list */ +} ReqHandlerInfo_t; + +ReqHandlerInfo_t *ReqHandlerAdd(uuid_le switchTypeGuid, + const char *switch_type_name, + int (*controlfunc)(struct io_msgs *), + unsigned long min_channel_bytes, + int (*Server_Channel_Ok)(unsigned long + channelBytes), + int (*Server_Channel_Init) + (void *x, unsigned char *clientStr, + U32 clientStrLen, U64 bytes)); +ReqHandlerInfo_t *ReqHandlerFind(uuid_le switchTypeGuid); +int ReqHandlerDel(uuid_le switchTypeGuid); + +#define uislib_ioremap_cache(addr, size) \ + dbg_ioremap_cache(addr, size, __FILE__, __LINE__) + +static inline void __iomem * +dbg_ioremap_cache(U64 addr, unsigned long size, char *file, int line) +{ + void __iomem *new; + new = ioremap_cache(addr, size); + return new; +} + +#define uislib_ioremap(addr, size) dbg_ioremap(addr, size, __FILE__, __LINE__) + +static inline void * +dbg_ioremap(U64 addr, unsigned long size, char *file, int line) +{ + void *new; + new = ioremap(addr, size); + return new; +} + +#define uislib_iounmap(addr) dbg_iounmap(addr, __FILE__, __LINE__) + +static inline void +dbg_iounmap(void __iomem *addr, char *file, int line) +{ + iounmap(addr); +} + +#define PROC_READ_BUFFER_SIZE 131072 /* size of the buffer to allocate to + * hold all of /proc/XXX/info */ +int uisutil_add_proc_line_ex(int *total, char **buffer, int *buffer_remaining, + char *format, ...); + +int uisctrl_register_req_handler(int type, void *fptr, + ULTRA_VBUS_DEVICEINFO *chipset_DriverInfo); +int uisctrl_register_req_handler_ex(uuid_le switchTypeGuid, + const char *switch_type_name, + int (*fptr)(struct io_msgs *), + unsigned long min_channel_bytes, + int (*Server_Channel_Ok)(unsigned long + channelBytes), + int (*Server_Channel_Init) + (void *x, unsigned char *clientStr, + U32 clientStrLen, U64 bytes), + ULTRA_VBUS_DEVICEINFO *chipset_DriverInfo); + +int uisctrl_unregister_req_handler_ex(uuid_le switchTypeGuid); +unsigned char *util_map_virt(struct phys_info *sg); +void util_unmap_virt(struct phys_info *sg); +unsigned char *util_map_virt_atomic(struct phys_info *sg); +void util_unmap_virt_atomic(void *buf); +int uislib_server_inject_add_vnic(U32 switchNo, U32 BusNo, U32 numIntPorts, + U32 numExtPorts, MACARRAY pmac[], + pCHANNEL_HEADER **chan); +void uislib_server_inject_del_vnic(U32 switchNo, U32 busNo, U32 numIntPorts, + U32 numExtPorts); +int uislib_client_inject_add_bus(U32 busNo, uuid_le instGuid, + U64 channelAddr, ulong nChannelBytes); +int uislib_client_inject_del_bus(U32 busNo); + +int uislib_client_inject_add_vhba(U32 busNo, U32 devNo, + U64 phys_chan_addr, U32 chan_bytes, + int is_test_addr, uuid_le instGuid, + struct InterruptInfo *intr); +int uislib_client_inject_pause_vhba(U32 busNo, U32 devNo); +int uislib_client_inject_resume_vhba(U32 busNo, U32 devNo); +int uislib_client_inject_del_vhba(U32 busNo, U32 devNo); +int uislib_client_inject_add_vnic(U32 busNo, U32 devNo, + U64 phys_chan_addr, U32 chan_bytes, + int is_test_addr, uuid_le instGuid, + struct InterruptInfo *intr); +int uislib_client_inject_pause_vnic(U32 busNo, U32 devNo); +int uislib_client_inject_resume_vnic(U32 busNo, U32 devNo); +int uislib_client_inject_del_vnic(U32 busNo, U32 devNo); +#ifdef STORAGE_CHANNEL +U64 uislib_storage_channel(int client_id); +#endif +int uislib_get_owned_pdest(struct uisscsi_dest *pdest); + +int uislib_send_event(CONTROLVM_ID id, CONTROLVM_MESSAGE_PACKET *event); + +/* structure used by vhba & vnic to keep track of queue & thread info */ +struct chaninfo { + struct uisqueue_info *queueinfo; + /* this specifies the queue structures for a channel */ + /* ALLOCATED BY THE OTHER END - WE JUST GET A POINTER TO THE MEMORY */ + spinlock_t insertlock; + /* currently used only in virtnic when sending data to uisnic */ + /* to synchronize the inserts into the signal queue */ + struct uisthread_info threadinfo; + /* this specifies the thread structures used by the thread that */ + /* handles this channel */ +}; + +/* this is the wait code for all the threads - it is used to get +* something from a queue choices: wait_for_completion_interruptible, +* _timeout, interruptible_timeout +*/ +#define UIS_THREAD_WAIT_MSEC(x) { \ + set_current_state(TASK_INTERRUPTIBLE); \ + schedule_timeout(msecs_to_jiffies(x)); \ +} +#define UIS_THREAD_WAIT_USEC(x) { \ + set_current_state(TASK_INTERRUPTIBLE); \ + schedule_timeout(usecs_to_jiffies(x)); \ +} +#define UIS_THREAD_WAIT UIS_THREAD_WAIT_MSEC(5) +#define UIS_THREAD_WAIT_SEC(x) { \ + set_current_state(TASK_INTERRUPTIBLE); \ + schedule_timeout((x)*HZ); \ +} + +/* This is a hack until we fix IOVM to initialize the channel header + * correctly at DEVICE_CREATE time, INSTEAD OF waiting until + * DEVICE_CONFIGURE time. + */ +static inline void +wait_for_valid_guid(uuid_le __iomem *guid) +{ + uuid_le tmpguid; + + while (1) { + memcpy_fromio((void *)&tmpguid, + (void __iomem *)guid, sizeof(uuid_le)); + if (uuid_le_cmp(tmpguid, NULL_UUID_LE) != 0) + break; + LOGERR("Waiting for non-0 GUID (why???)...\n"); + UIS_THREAD_WAIT_SEC(5); + } + LOGERR("OK... GUID is non-0 now\n"); +} + +/* CopyFragsInfoFromSkb returns the number of entries added to frags array + * Returns -1 on failure. + */ +unsigned int uisutil_copy_fragsinfo_from_skb(unsigned char *calling_ctx, + void *skb_in, + unsigned int firstfraglen, + unsigned int frags_max, + struct phys_info frags[]); + +static inline unsigned int +Issue_VMCALL_IO_CONTROLVM_ADDR(U64 *ControlAddress, U32 *ControlBytes) +{ + VMCALL_IO_CONTROLVM_ADDR_PARAMS params; + int result = VMCALL_SUCCESS; + U64 physaddr; + + physaddr = virt_to_phys(¶ms); + ISSUE_IO_VMCALL(VMCALL_IO_CONTROLVM_ADDR, physaddr, result); + if (VMCALL_SUCCESSFUL(result)) { + *ControlAddress = params.ChannelAddress; + *ControlBytes = params.ChannelBytes; + } + return result; +} + +static inline unsigned int Issue_VMCALL_IO_DIAG_ADDR(U64 *DiagChannelAddress) +{ + VMCALL_IO_DIAG_ADDR_PARAMS params; + int result = VMCALL_SUCCESS; + U64 physaddr; + + physaddr = virt_to_phys(¶ms); + ISSUE_IO_VMCALL(VMCALL_IO_DIAG_ADDR, physaddr, result); + if (VMCALL_SUCCESSFUL(result)) + *DiagChannelAddress = params.ChannelAddress; + return result; +} + +static inline unsigned int +Issue_VMCALL_IO_VISORSERIAL_ADDR(U64 *DiagChannelAddress) +{ + VMCALL_IO_VISORSERIAL_ADDR_PARAMS params; + int result = VMCALL_SUCCESS; + U64 physaddr; + + physaddr = virt_to_phys(¶ms); + ISSUE_IO_VMCALL(VMCALL_IO_VISORSERIAL_ADDR, physaddr, result); + if (VMCALL_SUCCESSFUL(result)) + *DiagChannelAddress = params.ChannelAddress; + return result; +} + +static inline S64 Issue_VMCALL_QUERY_GUEST_VIRTUAL_TIME_OFFSET(void) +{ + U64 result = VMCALL_SUCCESS; + U64 physaddr = 0; + + ISSUE_IO_VMCALL(VMCALL_QUERY_GUEST_VIRTUAL_TIME_OFFSET, physaddr, + result); + return result; +} + +static inline S64 Issue_VMCALL_MEASUREMENT_DO_NOTHING(void) +{ + U64 result = VMCALL_SUCCESS; + U64 physaddr = 0; + + ISSUE_IO_VMCALL(VMCALL_MEASUREMENT_DO_NOTHING, physaddr, result); + return result; +} + +struct log_info_t { + volatile unsigned long long last_cycles; + unsigned long long delta_sum[64]; + unsigned long long delta_cnt[64]; + unsigned long long max_delta[64]; + unsigned long long min_delta[64]; +}; + +static inline int Issue_VMCALL_UPDATE_PHYSICAL_TIME(U64 adjustment) +{ + int result = VMCALL_SUCCESS; + + ISSUE_IO_VMCALL(VMCALL_UPDATE_PHYSICAL_TIME, adjustment, result); + return result; +} + +static inline unsigned int +Issue_VMCALL_CHANNEL_MISMATCH(const char *ChannelName, + const char *ItemName, + U32 SourceLineNumber, const char *path_n_fn) +{ + VMCALL_CHANNEL_VERSION_MISMATCH_PARAMS params; + int result = VMCALL_SUCCESS; + U64 physaddr; + char *last_slash = NULL; + + strncpy(params.ChannelName, ChannelName, + lengthof(VMCALL_CHANNEL_VERSION_MISMATCH_PARAMS, ChannelName)); + strncpy(params.ItemName, ItemName, + lengthof(VMCALL_CHANNEL_VERSION_MISMATCH_PARAMS, ItemName)); + params.SourceLineNumber = SourceLineNumber; + + last_slash = strrchr(path_n_fn, '/'); + if (last_slash != NULL) { + last_slash++; + strncpy(params.SourceFileName, last_slash, + lengthof(VMCALL_CHANNEL_VERSION_MISMATCH_PARAMS, + SourceFileName)); + } else + strncpy(params.SourceFileName, + "Cannot determine source filename", + lengthof(VMCALL_CHANNEL_VERSION_MISMATCH_PARAMS, + SourceFileName)); + + physaddr = virt_to_phys(¶ms); + ISSUE_IO_VMCALL(VMCALL_CHANNEL_VERSION_MISMATCH, physaddr, result); + return result; +} + +static inline unsigned int Issue_VMCALL_FATAL_BYE_BYE(void) +{ + int result = VMCALL_SUCCESS; + U64 physaddr = 0; + + ISSUE_IO_VMCALL(VMCALL_GENERIC_SURRENDER_QUANTUM_FOREVER, physaddr, + result); + return result; +} + +#define UIS_DAEMONIZE(nam) +void *uislib_cache_alloc(struct kmem_cache *cur_pool, char *fn, int ln); +#define UISCACHEALLOC(cur_pool) uislib_cache_alloc(cur_pool, __FILE__, __LINE__) +void uislib_cache_free(struct kmem_cache *cur_pool, void *p, char *fn, int ln); +#define UISCACHEFREE(cur_pool, p) \ + uislib_cache_free(cur_pool, p, __FILE__, __LINE__) + +void uislib_enable_channel_interrupts(U32 busNo, U32 devNo, + int (*interrupt)(void *), + void *interrupt_context); +void uislib_disable_channel_interrupts(U32 busNo, U32 devNo); +void uislib_force_channel_interrupt(U32 busNo, U32 devNo); + +#endif /* __UISUTILS__H__ */ diff --git a/drivers/staging/unisys/include/uniklog.h b/drivers/staging/unisys/include/uniklog.h new file mode 100644 index 00000000000..6178cc46f7c --- /dev/null +++ b/drivers/staging/unisys/include/uniklog.h @@ -0,0 +1,193 @@ +/* uniklog.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. + */ + +/* This module contains macros to aid developers in logging messages. + * + * This module is affected by the DEBUG compiletime option. + * + */ +#ifndef __UNIKLOG_H__ +#define __UNIKLOG_H__ + + +#include <linux/printk.h> + +/* + * # DBGINF + * + * \brief Log debug informational message - log a LOG_INFO message only + * if DEBUG compiletime option enabled + * + * \param devname the device name of the device reporting this message, or + * NULL if this message is NOT device-related. + * \param fmt printf()-style format string containing the message to log. + * \param args Optional arguments to be formatted and inserted into the + * format string. + * \return nothing + * + * Log a message at the LOG_INFO level, but only if DEBUG is enabled. If + * DEBUG is disabled, this expands to a no-op. + */ + +/* + * # DBGVER + * + * \brief Log debug verbose message - log a LOG_DEBUG message only if + * DEBUG compiletime option enabled + * + * \param devname the device name of the device reporting this message, or + * NULL if this message is NOT device-related. + * \param fmt printf()-style format string containing the message to log. + * \param args Optional arguments to be formatted and inserted into the + * format string. + * \return nothing + * + * Log a message at the LOG_DEBUG level, but only if DEBUG is enabled. If + * DEBUG is disabled, this expands to a no-op. Note also that LOG_DEBUG + * messages can be enabled/disabled at runtime as well. + */ +#define DBGINFDEV(devname, fmt, args...) do { } while (0) +#define DBGVERDEV(devname, fmt, args...) do { } while (0) +#define DBGINF(fmt, args...) do { } while (0) +#define DBGVER(fmt, args...) do { } while (0) + +/* + * # LOGINF + * + * \brief Log informational message - logs a message at the LOG_INFO level + * + * \param devname the device name of the device reporting this message, or + * NULL if this message is NOT device-related. + * \param fmt printf()-style format string containing the message to log. + * \param args Optional arguments to be formatted and inserted into the + * format string. + * \return nothing + * + * Logs the specified message at the LOG_INFO level. + */ + +#define LOGINF(fmt, args...) pr_info(fmt, ## args) +#define LOGINFDEV(devname, fmt, args...) \ + pr_info("%s " fmt, devname, ## args) +#define LOGINFDEVX(devno, fmt, args...) \ + pr_info("dev%d " fmt, devno, ## args) +#define LOGINFNAME(vnic, fmt, args...) \ + do { \ + if (vnic != NULL) { \ + pr_info("%s " fmt, vnic->name, ## args); \ + } else { \ + pr_info(fmt, ## args); \ + } \ + } while (0) + +/* + * # LOGVER + * + * \brief Log verbose message - logs a message at the LOG_DEBUG level, + * which can be disabled at runtime + * + * \param devname the device name of the device reporting this message, or + * NULL if this message is NOT device-related. + * \param fmt printf()-style format string containing the message to log. + * \param args Optional arguments to be formatted and inserted into the format + * \param string. + * \return nothing + * + * Logs the specified message at the LOG_DEBUG level. Note also that + * LOG_DEBUG messages can be enabled/disabled at runtime as well. + */ +#define LOGVER(fmt, args...) pr_debug(fmt, ## args) +#define LOGVERDEV(devname, fmt, args...) \ + pr_debug("%s " fmt, devname, ## args) +#define LOGVERNAME(vnic, fmt, args...) \ + do { \ + if (vnic != NULL) { \ + pr_debug("%s " fmt, vnic->name, ## args); \ + } else { \ + pr_debug(fmt, ## args); \ + } \ + } while (0) + + +/* + * # LOGERR + * + * \brief Log error message - logs a message at the LOG_ERR level, + * including source line number information + * + * \param devname the device name of the device reporting this message, or + * NULL if this message is NOT device-related. + * \param fmt printf()-style format string containing the message to log. + * \param args Optional arguments to be formatted and inserted into the format + * \param string. + * \return nothing + * + * Logs the specified error message at the LOG_ERR level. It will also + * include the file, line number, and function name of where the error + * originated in the log message. + */ +#define LOGERR(fmt, args...) pr_err(fmt, ## args) +#define LOGERRDEV(devname, fmt, args...) \ + pr_err("%s " fmt, devname, ## args) +#define LOGERRDEVX(devno, fmt, args...) \ + pr_err("dev%d " fmt, devno, ## args) +#define LOGERRNAME(vnic, fmt, args...) \ + do { \ + if (vnic != NULL) { \ + pr_err("%s " fmt, vnic->name, ## args); \ + } else { \ + pr_err(fmt, ## args); \ + } \ + } while (0) +#define LOGORDUMPERR(seqfile, fmt, args...) do { \ + if (seqfile) { \ + seq_printf(seqfile, fmt, ## args); \ + } else { \ + LOGERR(fmt, ## args); \ + } \ + } while (0) + +/* + * # LOGWRN + * + * \brief Log warning message - Logs a message at the LOG_WARNING level, + * including source line number information + * + * \param devname the device name of the device reporting this message, or + * NULL if this message is NOT device-related. + * \param fmt printf()-style format string containing the message to log. + * \param args Optional arguments to be formatted and inserted into the format + * \param string. + * \return nothing + * + * Logs the specified error message at the LOG_WARNING level. It will also + * include the file, line number, and function name of where the error + * originated in the log message. + */ +#define LOGWRN(fmt, args...) pr_warn(fmt, ## args) +#define LOGWRNDEV(devname, fmt, args...) \ + pr_warn("%s " fmt, devname, ## args) +#define LOGWRNNAME(vnic, fmt, args...) \ + do { \ + if (vnic != NULL) { \ + pr_warn("%s " fmt, vnic->name, ## args); \ + } else { \ + pr_warn(fmt, ## args); \ + } \ + } while (0) + +#endif /* __UNIKLOG_H__ */ diff --git a/drivers/staging/unisys/include/vbushelper.h b/drivers/staging/unisys/include/vbushelper.h new file mode 100644 index 00000000000..93e35f039de --- /dev/null +++ b/drivers/staging/unisys/include/vbushelper.h @@ -0,0 +1,47 @@ +/* vbushelper.h + * + * Copyright © 2011 - 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. + */ + +#ifndef __VBUSHELPER_H__ +#define __VBUSHELPER_H__ + +#include "vbusdeviceinfo.h" + +/* TARGET_HOSTNAME specified as -DTARGET_HOSTNAME=\"thename\" on the + * command line */ + +#define TARGET_HOSTNAME "linuxguest" + +static inline void +BusDeviceInfo_Init(ULTRA_VBUS_DEVICEINFO *pBusDeviceInfo, + const char *deviceType, const char *driverName, + const char *ver, const char *verTag, + const char *buildDate, const char *buildTime) +{ + memset(pBusDeviceInfo, 0, sizeof(ULTRA_VBUS_DEVICEINFO)); + snprintf(pBusDeviceInfo->devType, sizeof(pBusDeviceInfo->devType), + "%s", (deviceType) ? deviceType : "unknownType"); + snprintf(pBusDeviceInfo->drvName, sizeof(pBusDeviceInfo->drvName), + "%s", (driverName) ? driverName : "unknownDriver"); + snprintf(pBusDeviceInfo->infoStrings, + sizeof(pBusDeviceInfo->infoStrings), "%s\t%s\t%s %s\t%s", + (ver) ? ver : "unknownVer", + (verTag) ? verTag : "unknownVerTag", + (buildDate) ? buildDate : "noBuildDate", + (buildTime) ? buildTime : "nobuildTime", TARGET_HOSTNAME); +} + +#endif diff --git a/drivers/staging/unisys/uislib/Kconfig b/drivers/staging/unisys/uislib/Kconfig new file mode 100644 index 00000000000..8d87d9c81c6 --- /dev/null +++ b/drivers/staging/unisys/uislib/Kconfig @@ -0,0 +1,10 @@ +# +# Unisys uislib configuration +# + +config UNISYS_UISLIB + tristate "Unisys uislib driver" + depends on UNISYSSPAR && UNISYS_VISORCHIPSET && UNISYS_CHANNELSTUB + ---help--- + If you say Y here, you will enable the Unisys uislib driver. + diff --git a/drivers/staging/unisys/uislib/Makefile b/drivers/staging/unisys/uislib/Makefile new file mode 100644 index 00000000000..6e44d49458f --- /dev/null +++ b/drivers/staging/unisys/uislib/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for Unisys uislib +# + +obj-$(CONFIG_UNISYS_UISLIB) += visoruislib.o + +visoruislib-y := uislib.o uisqueue.o uisthread.o uisutils.o + +ccflags-y += -Idrivers/staging/unisys/include +ccflags-y += -Idrivers/staging/unisys/channels +ccflags-y += -Idrivers/staging/unisys/visorchipset +ccflags-y += -Idrivers/staging/unisys/sparstopdriver +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/uislib/uislib.c b/drivers/staging/unisys/uislib/uislib.c new file mode 100644 index 00000000000..d4a7ef821ba --- /dev/null +++ b/drivers/staging/unisys/uislib/uislib.c @@ -0,0 +1,1636 @@ +/* uislib.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. + */ + +/* @ALL_INSPECTED */ +#define EXPORT_SYMTAB +#include <linux/kernel.h> +#include <linux/highmem.h> +#ifdef CONFIG_MODVERSIONS +#include <config/modversions.h> +#endif +#include <linux/module.h> +#include <linux/debugfs.h> + +#include "commontypes.h" + +#include <linux/version.h> +#include "uniklog.h" +#include "diagnostics/appos_subsystems.h" +#include "uisutils.h" +#include "vbuschannel.h" + +#include <linux/proc_fs.h> +#include <linux/uaccess.h> /* for copy_from_user */ +#include <linux/ctype.h> /* for toupper */ +#include <linux/list.h> + +#include "sparstop.h" +#include "visorchipset.h" +#include "chanstub.h" +#include "version.h" +#include "guestlinuxdebug.h" + +#define SET_PROC_OWNER(x, y) + +#define POLLJIFFIES_NORMAL 1 +/* Choose whether or not you want to wakeup the request-polling thread + * after an IO termination: + * this is shorter than using __FILE__ (full path name) in + * debug/info/error messages + */ +#define CURRENT_FILE_PC UISLIB_PC_uislib_c +#define __MYFILE__ "uislib.c" + +/* global function pointers that act as callback functions into virtpcimod */ +int (*VirtControlChanFunc)(struct guest_msgs *); + +static int ProcReadBufferValid; +static char *ProcReadBuffer; /* Note this MUST be global, + * because the contents must */ +static unsigned int chipset_inited; + +#define WAIT_ON_CALLBACK(handle) \ + do { \ + if (handle) \ + break; \ + UIS_THREAD_WAIT; \ + } while (1) + +static struct bus_info *BusListHead; +static rwlock_t BusListLock; +static int BusListCount; /* number of buses in the list */ +static int MaxBusCount; /* maximum number of buses expected */ +static U64 PhysicalDataChan; +static int PlatformNumber; + +static struct uisthread_info Incoming_ThreadInfo; +static BOOL Incoming_Thread_Started = FALSE; +static LIST_HEAD(List_Polling_Device_Channels); +static unsigned long long tot_moved_to_tail_cnt; +static unsigned long long tot_wait_cnt; +static unsigned long long tot_wakeup_cnt; +static unsigned long long tot_schedule_cnt; +static int en_smart_wakeup = 1; +static DEFINE_SEMAPHORE(Lock_Polling_Device_Channels); /* unlocked */ +static DECLARE_WAIT_QUEUE_HEAD(Wakeup_Polling_Device_Channels); +static int Go_Polling_Device_Channels; + +#define CALLHOME_PROC_ENTRY_FN "callhome" +#define CALLHOME_THROTTLED_PROC_ENTRY_FN "callhome_throttled" + +#define DIR_DEBUGFS_ENTRY "uislib" +static struct dentry *dir_debugfs; + +#define PLATFORMNUMBER_DEBUGFS_ENTRY_FN "platform" +static struct dentry *platformnumber_debugfs_read; + +#define CYCLES_BEFORE_WAIT_DEBUGFS_ENTRY_FN "cycles_before_wait" +static struct dentry *cycles_before_wait_debugfs_read; + +#define SMART_WAKEUP_DEBUGFS_ENTRY_FN "smart_wakeup" +static struct dentry *smart_wakeup_debugfs_entry; + +#define INFO_DEBUGFS_ENTRY_FN "info" +static struct dentry *info_debugfs_entry; + +static unsigned long long cycles_before_wait, wait_cycles; + +/*****************************************************/ +/* local functions */ +/*****************************************************/ + +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, +}; + +static void +init_msg_header(CONTROLVM_MESSAGE *msg, U32 id, uint rsp, uint svr) +{ + memset(msg, 0, sizeof(CONTROLVM_MESSAGE)); + msg->hdr.Id = id; + msg->hdr.Flags.responseExpected = rsp; + msg->hdr.Flags.server = svr; +} + +static __iomem void * +init_vbus_channel(U64 channelAddr, U32 channelBytes, int isServer) +{ + void __iomem *rc = NULL; + void __iomem *pChan = uislib_ioremap_cache(channelAddr, channelBytes); + if (!pChan) { + LOGERR("CONTROLVM_BUS_CREATE error: ioremap_cache of channelAddr:%Lx for channelBytes:%llu failed", + (unsigned long long) channelAddr, + (unsigned long long) channelBytes); + rc = NULL; + goto Away; + } + if (isServer) { + memset_io(pChan, 0, channelBytes); + if (!ULTRA_VBUS_CHANNEL_OK_SERVER(channelBytes, NULL)) { + ERRDRV("%s channel cannot be used", __func__); + uislib_iounmap(pChan); + rc = NULL; + goto Away; + } + ULTRA_VBUS_init_channel(pChan, channelBytes); + } else { + if (!ULTRA_VBUS_CHANNEL_OK_CLIENT(pChan, NULL)) { + ERRDRV("%s channel cannot be used", __func__); + uislib_iounmap(pChan); + rc = NULL; + goto Away; + } + } + rc = pChan; +Away: + return rc; +} + +static int +create_bus(CONTROLVM_MESSAGE *msg, char *buf) +{ + U32 busNo, deviceCount; + struct bus_info *tmp, *bus; + size_t size; + + if (MaxBusCount == BusListCount) { + LOGERR("CONTROLVM_BUS_CREATE Failed: max buses:%d already created\n", + MaxBusCount); + POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, MaxBusCount, + POSTCODE_SEVERITY_ERR); + return CONTROLVM_RESP_ERROR_MAX_BUSES; + } + + busNo = msg->cmd.createBus.busNo; + deviceCount = msg->cmd.createBus.deviceCount; + + POSTCODE_LINUX_4(BUS_CREATE_ENTRY_PC, busNo, deviceCount, + POSTCODE_SEVERITY_INFO); + + size = + sizeof(struct bus_info) + + (deviceCount * sizeof(struct device_info *)); + bus = kzalloc(size, GFP_ATOMIC); + if (!bus) { + LOGERR("CONTROLVM_BUS_CREATE Failed: kmalloc for bus failed.\n"); + POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, busNo, + POSTCODE_SEVERITY_ERR); + return CONTROLVM_RESP_ERROR_KMALLOC_FAILED; + } + + /* Currently by default, the bus Number is the GuestHandle. + * Configure Bus message can override this. + */ + if (msg->hdr.Flags.testMessage) { + /* This implies we're the IOVM so set guest handle to 0... */ + bus->guestHandle = 0; + bus->busNo = busNo; + bus->localVnic = 1; + } else + bus->busNo = bus->guestHandle = busNo; + sprintf(bus->name, "%d", (int) bus->busNo); + bus->deviceCount = deviceCount; + bus->device = + (struct device_info **) ((char *) bus + sizeof(struct bus_info)); + bus->busInstGuid = msg->cmd.createBus.busInstGuid; + bus->busChannelBytes = 0; + bus->pBusChannel = NULL; + + /* add bus to our bus list - but check for duplicates first */ + read_lock(&BusListLock); + for (tmp = BusListHead; tmp; tmp = tmp->next) { + if (tmp->busNo == bus->busNo) + break; + } + read_unlock(&BusListLock); + if (tmp) { + /* found a bus already in the list with same busNo - + * reject add + */ + LOGERR("CONTROLVM_BUS_CREATE Failed: bus %d already exists.\n", + bus->busNo); + POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, bus->busNo, + POSTCODE_SEVERITY_ERR); + kfree(bus); + return CONTROLVM_RESP_ERROR_ALREADY_DONE; + } + if ((msg->cmd.createBus.channelAddr != 0) + && (msg->cmd.createBus.channelBytes != 0)) { + bus->busChannelBytes = msg->cmd.createBus.channelBytes; + bus->pBusChannel = + init_vbus_channel(msg->cmd.createBus.channelAddr, + msg->cmd.createBus.channelBytes, + msg->hdr.Flags.server); + } + /* the msg is bound for virtpci; send guest_msgs struct to callback */ + if (!msg->hdr.Flags.server) { + struct guest_msgs cmd; + cmd.msgtype = GUEST_ADD_VBUS; + cmd.add_vbus.busNo = busNo; + cmd.add_vbus.chanptr = bus->pBusChannel; + cmd.add_vbus.deviceCount = deviceCount; + cmd.add_vbus.busTypeGuid = msg->cmd.createBus.busDataTypeGuid; + cmd.add_vbus.busInstGuid = msg->cmd.createBus.busInstGuid; + if (!VirtControlChanFunc) { + LOGERR("CONTROLVM_BUS_CREATE Failed: virtpci callback not registered."); + POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, bus->busNo, + POSTCODE_SEVERITY_ERR); + kfree(bus); + return CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE; + } + if (!VirtControlChanFunc(&cmd)) { + LOGERR("CONTROLVM_BUS_CREATE Failed: virtpci GUEST_ADD_VBUS returned error."); + POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, bus->busNo, + POSTCODE_SEVERITY_ERR); + kfree(bus); + return + CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR; + } + } + + /* add bus at the head of our list */ + write_lock(&BusListLock); + if (!BusListHead) + BusListHead = bus; + else { + bus->next = BusListHead; + BusListHead = bus; + } + BusListCount++; + write_unlock(&BusListLock); + + POSTCODE_LINUX_3(BUS_CREATE_EXIT_PC, bus->busNo, + POSTCODE_SEVERITY_INFO); + return CONTROLVM_RESP_SUCCESS; +} + +static int +destroy_bus(CONTROLVM_MESSAGE *msg, char *buf) +{ + int i; + struct bus_info *bus, *prev = NULL; + U32 busNo; + + busNo = msg->cmd.destroyBus.busNo; + + /* find and delete the bus */ + read_lock(&BusListLock); + for (bus = BusListHead; bus; prev = bus, bus = bus->next) { + if (bus->busNo == busNo) { + /* found the bus - ensure that all device + * slots are NULL + */ + for (i = 0; i < bus->deviceCount; i++) { + if (bus->device[i] != NULL) { + LOGERR("CONTROLVM_BUS_DESTROY Failed: device %i attached to bus %d.", + i, busNo); + read_unlock(&BusListLock); + return CONTROLVM_RESP_ERROR_BUS_DEVICE_ATTACHED; + } + } + read_unlock(&BusListLock); + /* the msg is bound for virtpci; send + * guest_msgs struct to callback + */ + if (!msg->hdr.Flags.server) { + struct guest_msgs cmd; + cmd.msgtype = GUEST_DEL_VBUS; + cmd.del_vbus.busNo = busNo; + if (!VirtControlChanFunc) { + LOGERR("CONTROLVM_BUS_DESTROY Failed: virtpci callback not registered."); + return CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE; + } + if (!VirtControlChanFunc(&cmd)) { + LOGERR("CONTROLVM_BUS_DESTROY Failed: virtpci GUEST_DEL_VBUS returned error."); + return CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR; + } + } + /* remove the bus from the list */ + write_lock(&BusListLock); + if (prev) /* not at head */ + prev->next = bus->next; + else + BusListHead = bus->next; + BusListCount--; + write_unlock(&BusListLock); + break; + } + } + + if (!bus) { + LOGERR("CONTROLVM_BUS_DESTROY Failed: failed to find bus %d.\n", + busNo); + read_unlock(&BusListLock); + return CONTROLVM_RESP_ERROR_ALREADY_DONE; + } + if (bus->pBusChannel) { + uislib_iounmap(bus->pBusChannel); + bus->pBusChannel = NULL; + } + + kfree(bus); + return CONTROLVM_RESP_SUCCESS; +} + +static int +create_device(CONTROLVM_MESSAGE *msg, char *buf) +{ + struct device_info *dev; + struct bus_info *bus; + U32 busNo, devNo; + int result = CONTROLVM_RESP_SUCCESS; + U64 minSize = MIN_IO_CHANNEL_SIZE; + ReqHandlerInfo_t *pReqHandler; + + busNo = msg->cmd.createDevice.busNo; + devNo = msg->cmd.createDevice.devNo; + + POSTCODE_LINUX_4(DEVICE_CREATE_ENTRY_PC, devNo, busNo, + POSTCODE_SEVERITY_INFO); + + dev = kzalloc(sizeof(struct device_info), GFP_ATOMIC); + if (!dev) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: kmalloc for dev failed.\n"); + POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + return CONTROLVM_RESP_ERROR_KMALLOC_FAILED; + } + + dev->channelTypeGuid = msg->cmd.createDevice.dataTypeGuid; + dev->intr = msg->cmd.createDevice.intr; + dev->channelAddr = msg->cmd.createDevice.channelAddr; + dev->busNo = busNo; + dev->devNo = devNo; + sema_init(&dev->interrupt_callback_lock, 1); /* unlocked */ + sprintf(dev->devid, "vbus%u:dev%u", (unsigned) busNo, (unsigned) devNo); + /* map the channel memory for the device. */ + if (msg->hdr.Flags.testMessage) + dev->chanptr = (void __iomem *)__va(dev->channelAddr); + else { + pReqHandler = ReqHandlerFind(dev->channelTypeGuid); + if (pReqHandler) + /* generic service handler registered for this + * channel + */ + minSize = pReqHandler->min_channel_bytes; + if (minSize > msg->cmd.createDevice.channelBytes) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: channel size is too small, channel size:0x%lx, required size:0x%lx", + (ulong) msg->cmd.createDevice.channelBytes, + (ulong) minSize); + POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + result = CONTROLVM_RESP_ERROR_CHANNEL_SIZE_TOO_SMALL; + goto Away; + } + dev->chanptr = + uislib_ioremap_cache(dev->channelAddr, + msg->cmd.createDevice.channelBytes); + if (!dev->chanptr) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: ioremap_cache of channelAddr:%Lx for channelBytes:%llu failed", + dev->channelAddr, + msg->cmd.createDevice.channelBytes); + result = CONTROLVM_RESP_ERROR_IOREMAP_FAILED; + POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + goto Away; + } + } + dev->devInstGuid = msg->cmd.createDevice.devInstGuid; + dev->channelBytes = msg->cmd.createDevice.channelBytes; + + read_lock(&BusListLock); + for (bus = BusListHead; bus; bus = bus->next) { + if (bus->busNo == busNo) { + /* make sure the device number is valid */ + if (devNo >= bus->deviceCount) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: device (%d) >= deviceCount (%d).", + devNo, bus->deviceCount); + result = CONTROLVM_RESP_ERROR_MAX_DEVICES; + POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, + devNo, busNo, + POSTCODE_SEVERITY_ERR); + read_unlock(&BusListLock); + goto Away; + } + /* make sure this device is not already set */ + if (bus->device[devNo]) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: device %d is already exists.", + devNo); + POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, + devNo, busNo, + POSTCODE_SEVERITY_ERR); + result = CONTROLVM_RESP_ERROR_ALREADY_DONE; + read_unlock(&BusListLock); + goto Away; + } + read_unlock(&BusListLock); + /* the msg is bound for virtpci; send + * guest_msgs struct to callback + */ + if (!msg->hdr.Flags.server) { + struct guest_msgs cmd; + if (!uuid_le_cmp(dev->channelTypeGuid, + UltraVhbaChannelProtocolGuid)) { + wait_for_valid_guid(&((CHANNEL_HEADER + __iomem *) (dev-> + chanptr))-> + Type); + if (!ULTRA_VHBA_CHANNEL_OK_CLIENT + (dev->chanptr, NULL)) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed:[CLIENT]VHBA dev %d chan invalid.", + devNo); + POSTCODE_LINUX_4 + (DEVICE_CREATE_FAILURE_PC, + devNo, busNo, + POSTCODE_SEVERITY_ERR); + result = CONTROLVM_RESP_ERROR_CHANNEL_INVALID; + goto Away; + } + cmd.msgtype = GUEST_ADD_VHBA; + cmd.add_vhba.chanptr = dev->chanptr; + cmd.add_vhba.busNo = busNo; + cmd.add_vhba.deviceNo = devNo; + cmd.add_vhba.devInstGuid = + dev->devInstGuid; + cmd.add_vhba.intr = dev->intr; + } else + if (!uuid_le_cmp(dev->channelTypeGuid, + UltraVnicChannelProtocolGuid)) { + wait_for_valid_guid(&((CHANNEL_HEADER + __iomem *) (dev-> + chanptr))-> + Type); + if (!ULTRA_VNIC_CHANNEL_OK_CLIENT + (dev->chanptr, NULL)) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: VNIC[CLIENT] dev %d chan invalid.", + devNo); + POSTCODE_LINUX_4 + (DEVICE_CREATE_FAILURE_PC, + devNo, busNo, + POSTCODE_SEVERITY_ERR); + result = CONTROLVM_RESP_ERROR_CHANNEL_INVALID; + goto Away; + } + cmd.msgtype = GUEST_ADD_VNIC; + cmd.add_vnic.chanptr = dev->chanptr; + cmd.add_vnic.busNo = busNo; + cmd.add_vnic.deviceNo = devNo; + cmd.add_vnic.devInstGuid = + dev->devInstGuid; + cmd.add_vhba.intr = dev->intr; + } else { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: unknown channelTypeGuid.\n"); + POSTCODE_LINUX_4 + (DEVICE_CREATE_FAILURE_PC, devNo, + busNo, POSTCODE_SEVERITY_ERR); + result = CONTROLVM_RESP_ERROR_CHANNEL_TYPE_UNKNOWN; + goto Away; + } + + if (!VirtControlChanFunc) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: virtpci callback not registered."); + POSTCODE_LINUX_4 + (DEVICE_CREATE_FAILURE_PC, devNo, + busNo, POSTCODE_SEVERITY_ERR); + result = CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE; + goto Away; + } + + if (!VirtControlChanFunc(&cmd)) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: virtpci GUEST_ADD_[VHBA||VNIC] returned error."); + POSTCODE_LINUX_4 + (DEVICE_CREATE_FAILURE_PC, devNo, + busNo, POSTCODE_SEVERITY_ERR); + result = CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR; + goto Away; + } + } + bus->device[devNo] = dev; + POSTCODE_LINUX_4(DEVICE_CREATE_SUCCESS_PC, devNo, busNo, + POSTCODE_SEVERITY_INFO); + return CONTROLVM_RESP_SUCCESS; + } + } + read_unlock(&BusListLock); + + LOGERR("CONTROLVM_DEVICE_CREATE Failed: failed to find bus %d.", busNo); + POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + result = CONTROLVM_RESP_ERROR_BUS_INVALID; + +Away: + if (!msg->hdr.Flags.testMessage) { + uislib_iounmap(dev->chanptr); + dev->chanptr = NULL; + } + + kfree(dev); + return result; +} + +static int +pause_device(CONTROLVM_MESSAGE *msg) +{ + U32 busNo, devNo; + struct bus_info *bus; + struct device_info *dev; + struct guest_msgs cmd; + + busNo = msg->cmd.deviceChangeState.busNo; + devNo = msg->cmd.deviceChangeState.devNo; + + read_lock(&BusListLock); + for (bus = BusListHead; bus; bus = bus->next) { + if (bus->busNo == busNo) { + /* make sure the device number is valid */ + if (devNo >= bus->deviceCount) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE:pause Failed: device(%d) >= deviceCount(%d).", + devNo, bus->deviceCount); + read_unlock(&BusListLock); + return CONTROLVM_RESP_ERROR_DEVICE_INVALID; + } + /* make sure this device exists */ + dev = bus->device[devNo]; + if (!dev) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE:pause Failed: device %d does not exist.", + devNo); + read_unlock(&BusListLock); + return CONTROLVM_RESP_ERROR_ALREADY_DONE; + } + read_unlock(&BusListLock); + /* the msg is bound for virtpci; send + * guest_msgs struct to callback + */ + if (!uuid_le_cmp(dev->channelTypeGuid, + UltraVhbaChannelProtocolGuid)) { + cmd.msgtype = GUEST_PAUSE_VHBA; + cmd.pause_vhba.chanptr = dev->chanptr; + } else + if (!uuid_le_cmp(dev->channelTypeGuid, + UltraVnicChannelProtocolGuid)) { + cmd.msgtype = GUEST_PAUSE_VNIC; + cmd.pause_vnic.chanptr = dev->chanptr; + } else { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE:pause Failed: unknown channelTypeGuid.\n"); + return + CONTROLVM_RESP_ERROR_CHANNEL_TYPE_UNKNOWN; + } + + if (!VirtControlChanFunc) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE Failed: virtpci callback not registered."); + return + CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE; + } + + if (!VirtControlChanFunc(&cmd)) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE:pause Failed: virtpci GUEST_PAUSE_[VHBA||VNIC] returned error."); + return CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR; + } + break; + } + } + + if (!bus) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE:pause Failed: bus %d does not exist", + busNo); + read_unlock(&BusListLock); + return CONTROLVM_RESP_ERROR_BUS_INVALID; + } + + return CONTROLVM_RESP_SUCCESS; +} + +static int +resume_device(CONTROLVM_MESSAGE *msg) +{ + U32 busNo, devNo; + struct bus_info *bus; + struct device_info *dev; + struct guest_msgs cmd; + + busNo = msg->cmd.deviceChangeState.busNo; + devNo = msg->cmd.deviceChangeState.devNo; + + read_lock(&BusListLock); + for (bus = BusListHead; bus; bus = bus->next) { + if (bus->busNo == busNo) { + /* make sure the device number is valid */ + if (devNo >= bus->deviceCount) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE:resume Failed: device(%d) >= deviceCount(%d).", + devNo, bus->deviceCount); + read_unlock(&BusListLock); + return CONTROLVM_RESP_ERROR_DEVICE_INVALID; + } + /* make sure this device exists */ + dev = bus->device[devNo]; + if (!dev) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE:resume Failed: device %d does not exist.", + devNo); + read_unlock(&BusListLock); + return CONTROLVM_RESP_ERROR_ALREADY_DONE; + } + read_unlock(&BusListLock); + /* the msg is bound for virtpci; send + * guest_msgs struct to callback + */ + if (!uuid_le_cmp(dev->channelTypeGuid, + UltraVhbaChannelProtocolGuid)) { + cmd.msgtype = GUEST_RESUME_VHBA; + cmd.resume_vhba.chanptr = dev->chanptr; + } else + if (!uuid_le_cmp(dev->channelTypeGuid, + UltraVnicChannelProtocolGuid)) { + cmd.msgtype = GUEST_RESUME_VNIC; + cmd.resume_vnic.chanptr = dev->chanptr; + } else { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE:resume Failed: unknown channelTypeGuid.\n"); + return + CONTROLVM_RESP_ERROR_CHANNEL_TYPE_UNKNOWN; + } + + if (!VirtControlChanFunc) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE Failed: virtpci callback not registered."); + return + CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE; + } + + if (!VirtControlChanFunc(&cmd)) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE:resume Failed: virtpci GUEST_RESUME_[VHBA||VNIC] returned error."); + return CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR; + } + break; + } + } + + if (!bus) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE:resume Failed: bus %d does not exist", + busNo); + read_unlock(&BusListLock); + return CONTROLVM_RESP_ERROR_BUS_INVALID; + } + + return CONTROLVM_RESP_SUCCESS; +} + +static int +destroy_device(CONTROLVM_MESSAGE *msg, char *buf) +{ + U32 busNo, devNo; + struct bus_info *bus; + struct device_info *dev; + struct guest_msgs cmd; + + busNo = msg->cmd.destroyDevice.busNo; + devNo = msg->cmd.destroyDevice.devNo; + + read_lock(&BusListLock); + LOGINF("destroy_device called for busNo=%u, devNo=%u", busNo, devNo); + for (bus = BusListHead; bus; bus = bus->next) { + if (bus->busNo == busNo) { + /* make sure the device number is valid */ + if (devNo >= bus->deviceCount) { + LOGERR("CONTROLVM_DEVICE_DESTORY Failed: device(%d) >= deviceCount(%d).", + devNo, bus->deviceCount); + read_unlock(&BusListLock); + return CONTROLVM_RESP_ERROR_DEVICE_INVALID; + } + /* make sure this device exists */ + dev = bus->device[devNo]; + if (!dev) { + LOGERR("CONTROLVM_DEVICE_DESTROY Failed: device %d does not exist.", + devNo); + read_unlock(&BusListLock); + return CONTROLVM_RESP_ERROR_ALREADY_DONE; + } + read_unlock(&BusListLock); + /* the msg is bound for virtpci; send + * guest_msgs struct to callback + */ + if (!uuid_le_cmp(dev->channelTypeGuid, + UltraVhbaChannelProtocolGuid)) { + cmd.msgtype = GUEST_DEL_VHBA; + cmd.del_vhba.chanptr = dev->chanptr; + } else + if (!uuid_le_cmp(dev->channelTypeGuid, + UltraVnicChannelProtocolGuid)) { + cmd.msgtype = GUEST_DEL_VNIC; + cmd.del_vnic.chanptr = dev->chanptr; + } else { + LOGERR("CONTROLVM_DEVICE_DESTROY Failed: unknown channelTypeGuid.\n"); + return + CONTROLVM_RESP_ERROR_CHANNEL_TYPE_UNKNOWN; + } + + if (!VirtControlChanFunc) { + LOGERR("CONTROLVM_DEVICE_DESTORY Failed: virtpci callback not registered."); + return + CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE; + } + + if (!VirtControlChanFunc(&cmd)) { + LOGERR("CONTROLVM_DEVICE_DESTROY Failed: virtpci GUEST_DEL_[VHBA||VNIC] returned error."); + return CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR; + } +/* you must disable channel interrupts BEFORE you unmap the channel, + * because if you unmap first, there may still be some activity going + * on which accesses the channel and you will get a "unable to handle + * kernel paging request" + */ + if (dev->polling) { + LOGINF("calling uislib_disable_channel_interrupts"); + uislib_disable_channel_interrupts(busNo, devNo); + } + /* unmap the channel memory for the device. */ + if (!msg->hdr.Flags.testMessage) { + LOGINF("destroy_device, doing iounmap"); + uislib_iounmap(dev->chanptr); + } + kfree(dev); + bus->device[devNo] = NULL; + break; + } + } + + if (!bus) { + LOGERR("CONTROLVM_DEVICE_DESTROY Failed: bus %d does not exist", + busNo); + read_unlock(&BusListLock); + return CONTROLVM_RESP_ERROR_BUS_INVALID; + } + + return CONTROLVM_RESP_SUCCESS; +} + +static int +init_chipset(CONTROLVM_MESSAGE *msg, char *buf) +{ + POSTCODE_LINUX_2(CHIPSET_INIT_ENTRY_PC, POSTCODE_SEVERITY_INFO); + + MaxBusCount = msg->cmd.initChipset.busCount; + PlatformNumber = msg->cmd.initChipset.platformNumber; + PhysicalDataChan = 0; + + /* We need to make sure we have our functions registered + * before processing messages. If we are a test vehicle the + * testMessage for init_chipset will be set. We can ignore the + * waits for the callbacks, since this will be manually entered + * from a user. If no testMessage is set, we will wait for the + * functions. + */ + if (!msg->hdr.Flags.testMessage) + WAIT_ON_CALLBACK(VirtControlChanFunc); + + chipset_inited = 1; + POSTCODE_LINUX_2(CHIPSET_INIT_EXIT_PC, POSTCODE_SEVERITY_INFO); + + return CONTROLVM_RESP_SUCCESS; +} + +static int +delete_bus_glue(U32 busNo) +{ + CONTROLVM_MESSAGE msg; + + init_msg_header(&msg, CONTROLVM_BUS_DESTROY, 0, 0); + msg.cmd.destroyBus.busNo = busNo; + if (destroy_bus(&msg, NULL) != CONTROLVM_RESP_SUCCESS) { + LOGERR("destroy_bus failed. busNo=0x%x\n", busNo); + return 0; + } + return 1; +} + +static int +delete_device_glue(U32 busNo, U32 devNo) +{ + CONTROLVM_MESSAGE msg; + + init_msg_header(&msg, CONTROLVM_DEVICE_DESTROY, 0, 0); + msg.cmd.destroyDevice.busNo = busNo; + msg.cmd.destroyDevice.devNo = devNo; + if (destroy_device(&msg, NULL) != CONTROLVM_RESP_SUCCESS) { + LOGERR("destroy_device failed. busNo=0x%x devNo=0x%x\n", busNo, + devNo); + return 0; + } + return 1; +} + +int +uislib_client_inject_add_bus(U32 busNo, uuid_le instGuid, + U64 channelAddr, ulong nChannelBytes) +{ + CONTROLVM_MESSAGE msg; + + LOGINF("enter busNo=0x%x\n", busNo); + /* step 0: init the chipset */ + POSTCODE_LINUX_3(CHIPSET_INIT_ENTRY_PC, busNo, POSTCODE_SEVERITY_INFO); + + if (!chipset_inited) { + /* step: initialize the chipset */ + init_msg_header(&msg, CONTROLVM_CHIPSET_INIT, 0, 0); + /* this change is needed so that console will come up + * OK even when the bus 0 create comes in late. If the + * bus 0 create is the first create, then the add_vnic + * will work fine, but if the bus 0 create arrives + * after number 4, then the add_vnic will fail, and the + * ultraboot will fail. + */ + msg.cmd.initChipset.busCount = 23; + msg.cmd.initChipset.switchCount = 0; + if (init_chipset(&msg, NULL) != CONTROLVM_RESP_SUCCESS) { + LOGERR("init_chipset failed.\n"); + return 0; + } + LOGINF("chipset initialized\n"); + POSTCODE_LINUX_3(CHIPSET_INIT_EXIT_PC, busNo, + POSTCODE_SEVERITY_INFO); + } + + /* step 1: create a bus */ + POSTCODE_LINUX_3(BUS_CREATE_ENTRY_PC, busNo, POSTCODE_SEVERITY_WARNING); + init_msg_header(&msg, CONTROLVM_BUS_CREATE, 0, 0); + msg.cmd.createBus.busNo = busNo; + msg.cmd.createBus.deviceCount = 23; /* devNo+1; */ + msg.cmd.createBus.channelAddr = channelAddr; + msg.cmd.createBus.channelBytes = nChannelBytes; + if (create_bus(&msg, NULL) != CONTROLVM_RESP_SUCCESS) { + LOGERR("create_bus failed.\n"); + POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, busNo, + POSTCODE_SEVERITY_ERR); + return 0; + } + POSTCODE_LINUX_3(BUS_CREATE_EXIT_PC, busNo, POSTCODE_SEVERITY_INFO); + + return 1; +} +EXPORT_SYMBOL_GPL(uislib_client_inject_add_bus); + + +int +uislib_client_inject_del_bus(U32 busNo) +{ + return delete_bus_glue(busNo); +} +EXPORT_SYMBOL_GPL(uislib_client_inject_del_bus); + +int +uislib_client_inject_pause_vhba(U32 busNo, U32 devNo) +{ + CONTROLVM_MESSAGE msg; + int rc; + + init_msg_header(&msg, CONTROLVM_DEVICE_CHANGESTATE, 0, 0); + msg.cmd.deviceChangeState.busNo = busNo; + msg.cmd.deviceChangeState.devNo = devNo; + msg.cmd.deviceChangeState.state = SegmentStateStandby; + rc = pause_device(&msg); + if (rc != CONTROLVM_RESP_SUCCESS) { + LOGERR("VHBA pause_device failed. busNo=0x%x devNo=0x%x\n", + busNo, devNo); + return rc; + } + return 0; +} +EXPORT_SYMBOL_GPL(uislib_client_inject_pause_vhba); + +int +uislib_client_inject_resume_vhba(U32 busNo, U32 devNo) +{ + CONTROLVM_MESSAGE msg; + int rc; + + init_msg_header(&msg, CONTROLVM_DEVICE_CHANGESTATE, 0, 0); + msg.cmd.deviceChangeState.busNo = busNo; + msg.cmd.deviceChangeState.devNo = devNo; + msg.cmd.deviceChangeState.state = SegmentStateRunning; + rc = resume_device(&msg); + if (rc != CONTROLVM_RESP_SUCCESS) { + LOGERR("VHBA resume_device failed. busNo=0x%x devNo=0x%x\n", + busNo, devNo); + return rc; + } + return 0; + +} +EXPORT_SYMBOL_GPL(uislib_client_inject_resume_vhba); + +int +uislib_client_inject_add_vhba(U32 busNo, U32 devNo, + U64 phys_chan_addr, U32 chan_bytes, + int is_test_addr, uuid_le instGuid, + struct InterruptInfo *intr) +{ + CONTROLVM_MESSAGE msg; + + LOGINF(" enter busNo=0x%x devNo=0x%x\n", busNo, devNo); + /* chipset init'ed with bus bus has been previously created - + * Verify it still exists step 2: create the VHBA device on the + * bus + */ + POSTCODE_LINUX_4(VHBA_CREATE_ENTRY_PC, devNo, busNo, + POSTCODE_SEVERITY_INFO); + + init_msg_header(&msg, CONTROLVM_DEVICE_CREATE, 0, 0); + if (is_test_addr) + /* signify that the physical channel address does NOT + * need to be ioremap()ed + */ + msg.hdr.Flags.testMessage = 1; + msg.cmd.createDevice.busNo = busNo; + msg.cmd.createDevice.devNo = devNo; + msg.cmd.createDevice.devInstGuid = instGuid; + if (intr) + msg.cmd.createDevice.intr = *intr; + else + memset(&msg.cmd.createDevice.intr, 0, + sizeof(struct InterruptInfo)); + msg.cmd.createDevice.channelAddr = phys_chan_addr; + if (chan_bytes < MIN_IO_CHANNEL_SIZE) { + LOGERR("wrong channel size.chan_bytes = 0x%x IO_CHANNEL_SIZE= 0x%x\n", + chan_bytes, (unsigned int) MIN_IO_CHANNEL_SIZE); + POSTCODE_LINUX_4(VHBA_CREATE_FAILURE_PC, chan_bytes, + MIN_IO_CHANNEL_SIZE, POSTCODE_SEVERITY_ERR); + return 0; + } + msg.cmd.createDevice.channelBytes = chan_bytes; + msg.cmd.createDevice.dataTypeGuid = UltraVhbaChannelProtocolGuid; + if (create_device(&msg, NULL) != CONTROLVM_RESP_SUCCESS) { + LOGERR("VHBA create_device failed.\n"); + POSTCODE_LINUX_4(VHBA_CREATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + return 0; + } + POSTCODE_LINUX_4(VHBA_CREATE_SUCCESS_PC, devNo, busNo, + POSTCODE_SEVERITY_INFO); + return 1; +} +EXPORT_SYMBOL_GPL(uislib_client_inject_add_vhba); + +int +uislib_client_inject_del_vhba(U32 busNo, U32 devNo) +{ + return delete_device_glue(busNo, devNo); +} +EXPORT_SYMBOL_GPL(uislib_client_inject_del_vhba); + +int +uislib_client_inject_add_vnic(U32 busNo, U32 devNo, + U64 phys_chan_addr, U32 chan_bytes, + int is_test_addr, uuid_le instGuid, + struct InterruptInfo *intr) +{ + CONTROLVM_MESSAGE msg; + + LOGINF(" enter busNo=0x%x devNo=0x%x\n", busNo, devNo); + /* chipset init'ed with bus bus has been previously created - + * Verify it still exists step 2: create the VNIC device on the + * bus + */ + POSTCODE_LINUX_4(VNIC_CREATE_ENTRY_PC, devNo, busNo, + POSTCODE_SEVERITY_INFO); + + init_msg_header(&msg, CONTROLVM_DEVICE_CREATE, 0, 0); + if (is_test_addr) + /* signify that the physical channel address does NOT + * need to be ioremap()ed + */ + msg.hdr.Flags.testMessage = 1; + msg.cmd.createDevice.busNo = busNo; + msg.cmd.createDevice.devNo = devNo; + msg.cmd.createDevice.devInstGuid = instGuid; + if (intr) + msg.cmd.createDevice.intr = *intr; + else + memset(&msg.cmd.createDevice.intr, 0, + sizeof(struct InterruptInfo)); + msg.cmd.createDevice.channelAddr = phys_chan_addr; + if (chan_bytes < MIN_IO_CHANNEL_SIZE) { + LOGERR("wrong channel size.chan_bytes = 0x%x IO_CHANNEL_SIZE= 0x%x\n", + chan_bytes, (unsigned int) MIN_IO_CHANNEL_SIZE); + POSTCODE_LINUX_4(VNIC_CREATE_FAILURE_PC, chan_bytes, + MIN_IO_CHANNEL_SIZE, POSTCODE_SEVERITY_ERR); + return 0; + } + msg.cmd.createDevice.channelBytes = chan_bytes; + msg.cmd.createDevice.dataTypeGuid = UltraVnicChannelProtocolGuid; + if (create_device(&msg, NULL) != CONTROLVM_RESP_SUCCESS) { + LOGERR("VNIC create_device failed.\n"); + POSTCODE_LINUX_4(VNIC_CREATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + return 0; + } + + POSTCODE_LINUX_4(VNIC_CREATE_SUCCESS_PC, devNo, busNo, + POSTCODE_SEVERITY_INFO); + return 1; +} +EXPORT_SYMBOL_GPL(uislib_client_inject_add_vnic); + +int +uislib_client_inject_pause_vnic(U32 busNo, U32 devNo) +{ + CONTROLVM_MESSAGE msg; + int rc; + + init_msg_header(&msg, CONTROLVM_DEVICE_CHANGESTATE, 0, 0); + msg.cmd.deviceChangeState.busNo = busNo; + msg.cmd.deviceChangeState.devNo = devNo; + msg.cmd.deviceChangeState.state = SegmentStateStandby; + rc = pause_device(&msg); + if (rc != CONTROLVM_RESP_SUCCESS) { + LOGERR("VNIC pause_device failed. busNo=0x%x devNo=0x%x\n", + busNo, devNo); + return -1; + } + return 0; +} +EXPORT_SYMBOL_GPL(uislib_client_inject_pause_vnic); + +int +uislib_client_inject_resume_vnic(U32 busNo, U32 devNo) +{ + CONTROLVM_MESSAGE msg; + int rc; + + init_msg_header(&msg, CONTROLVM_DEVICE_CHANGESTATE, 0, 0); + msg.cmd.deviceChangeState.busNo = busNo; + msg.cmd.deviceChangeState.devNo = devNo; + msg.cmd.deviceChangeState.state = SegmentStateRunning; + rc = resume_device(&msg); + if (rc != CONTROLVM_RESP_SUCCESS) { + LOGERR("VNIC resume_device failed. busNo=0x%x devNo=0x%x\n", + busNo, devNo); + return -1; + } + return 0; + +} +EXPORT_SYMBOL_GPL(uislib_client_inject_resume_vnic); + +int +uislib_client_inject_del_vnic(U32 busNo, U32 devNo) +{ + return delete_device_glue(busNo, devNo); +} +EXPORT_SYMBOL_GPL(uislib_client_inject_del_vnic); + +static int +uislib_client_add_vnic(U32 busNo) +{ + BOOL busCreated = FALSE; + int devNo = 0; /* Default to 0, since only one device + * will be created for this bus... */ + CONTROLVM_MESSAGE msg; + + init_msg_header(&msg, CONTROLVM_BUS_CREATE, 0, 0); + msg.hdr.Flags.testMessage = 1; + msg.cmd.createBus.busNo = busNo; + msg.cmd.createBus.deviceCount = 4; + msg.cmd.createBus.channelAddr = 0; + msg.cmd.createBus.channelBytes = 0; + if (create_bus(&msg, NULL) != CONTROLVM_RESP_SUCCESS) { + LOGERR("client create_bus failed"); + return 0; + } + busCreated = TRUE; + + init_msg_header(&msg, CONTROLVM_DEVICE_CREATE, 0, 0); + msg.hdr.Flags.testMessage = 1; + msg.cmd.createDevice.busNo = busNo; + msg.cmd.createDevice.devNo = devNo; + msg.cmd.createDevice.devInstGuid = NULL_UUID_LE; + memset(&msg.cmd.createDevice.intr, 0, sizeof(struct InterruptInfo)); + msg.cmd.createDevice.channelAddr = PhysicalDataChan; + msg.cmd.createDevice.channelBytes = MIN_IO_CHANNEL_SIZE; + msg.cmd.createDevice.dataTypeGuid = UltraVnicChannelProtocolGuid; + if (create_device(&msg, NULL) != CONTROLVM_RESP_SUCCESS) { + LOGERR("client create_device failed"); + goto AwayCleanup; + } + + return 1; + +AwayCleanup: + if (busCreated) { + init_msg_header(&msg, CONTROLVM_BUS_DESTROY, 0, 0); + msg.hdr.Flags.testMessage = 1; + msg.cmd.destroyBus.busNo = busNo; + if (destroy_bus(&msg, NULL) != CONTROLVM_RESP_SUCCESS) + LOGERR("client destroy_bus failed.\n"); + } + + return 0; +} /* end uislib_client_add_vnic */ +EXPORT_SYMBOL_GPL(uislib_client_add_vnic); + +static int +uislib_client_delete_vnic(U32 busNo) +{ + int devNo = 0; /* Default to 0, since only one device + * will be created for this bus... */ + CONTROLVM_MESSAGE msg; + + init_msg_header(&msg, CONTROLVM_DEVICE_DESTROY, 0, 0); + msg.hdr.Flags.testMessage = 1; + msg.cmd.destroyDevice.busNo = busNo; + msg.cmd.destroyDevice.devNo = devNo; + if (destroy_device(&msg, NULL) != CONTROLVM_RESP_SUCCESS) { + /* Don't error exit - try to see if bus can be destroyed... */ + LOGERR("client destroy_device failed.\n"); + } + + init_msg_header(&msg, CONTROLVM_BUS_DESTROY, 0, 0); + msg.hdr.Flags.testMessage = 1; + msg.cmd.destroyBus.busNo = busNo; + if (destroy_bus(&msg, NULL) != CONTROLVM_RESP_SUCCESS) + LOGERR("client destroy_bus failed.\n"); + + return 1; +} +EXPORT_SYMBOL_GPL(uislib_client_delete_vnic); +/* end client_delete_vnic */ + +void * +uislib_cache_alloc(struct kmem_cache *cur_pool, char *fn, int ln) +{ + /* __GFP_NORETRY means "ok to fail", meaning kmalloc() can + * return NULL. If you do NOT specify __GFP_NORETRY, Linux + * will go to extreme measures to get memory for you (like, + * invoke oom killer), which will probably cripple the system. + */ + void *p = kmem_cache_alloc(cur_pool, GFP_ATOMIC | __GFP_NORETRY); + if (p == NULL) { + LOGERR("uislib_malloc failed to alloc uiscmdrsp @%s:%d", + fn, ln); + return NULL; + } + return p; +} +EXPORT_SYMBOL_GPL(uislib_cache_alloc); + +void +uislib_cache_free(struct kmem_cache *cur_pool, void *p, char *fn, int ln) +{ + if (p == NULL) { + LOGERR("uislib_free NULL pointer @%s:%d", fn, ln); + return; + } + kmem_cache_free(cur_pool, p); +} +EXPORT_SYMBOL_GPL(uislib_cache_free); + +/*****************************************************/ +/* proc filesystem callback functions */ +/*****************************************************/ + +#define PLINE(...) uisutil_add_proc_line_ex(&tot, buff, \ + buff_len, __VA_ARGS__) + +static int +info_debugfs_read_helper(char **buff, int *buff_len) +{ + int i, tot = 0; + struct bus_info *bus; + + if (PLINE("\nBuses:\n") < 0) + goto err_done; + + read_lock(&BusListLock); + for (bus = BusListHead; bus; bus = bus->next) { + + if (PLINE(" bus=0x%p, busNo=%d, deviceCount=%d\n", + bus, bus->busNo, bus->deviceCount) < 0) + goto err_done_unlock; + + + if (PLINE(" Devices:\n") < 0) + goto err_done_unlock; + + for (i = 0; i < bus->deviceCount; i++) { + if (bus->device[i]) { + if (PLINE(" busNo %d, device[%i]: 0x%p, chanptr=0x%p, swtch=0x%p\n", + bus->busNo, i, bus->device[i], + bus->device[i]->chanptr, + bus->device[i]->swtch) < 0) + goto err_done_unlock; + + if (PLINE(" first_busy_cnt=%llu, moved_to_tail_cnt=%llu, last_on_list_cnt=%llu\n", + bus->device[i]->first_busy_cnt, + bus->device[i]->moved_to_tail_cnt, + bus->device[i]->last_on_list_cnt) < 0) + goto err_done_unlock; + } + } + } + read_unlock(&BusListLock); + + if (PLINE("UisUtils_Registered_Services: %d\n", + atomic_read(&UisUtils_Registered_Services)) < 0) + goto err_done; + if (PLINE("cycles_before_wait %llu wait_cycles:%llu\n", + cycles_before_wait, wait_cycles) < 0) + goto err_done; + if (PLINE("tot_wakeup_cnt %llu:tot_wait_cnt %llu:tot_schedule_cnt %llu\n", + tot_wakeup_cnt, tot_wait_cnt, tot_schedule_cnt) < 0) + goto err_done; + if (PLINE("en_smart_wakeup %d\n", en_smart_wakeup) < 0) + goto err_done; + if (PLINE("tot_moved_to_tail_cnt %llu\n", tot_moved_to_tail_cnt) < 0) + goto err_done; + + return tot; + +err_done_unlock: + read_unlock(&BusListLock); +err_done: + return -1; +} + +static ssize_t +info_debugfs_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + char *temp; + int totalBytes = 0; + int remaining_bytes = PROC_READ_BUFFER_SIZE; + +/* *start = buf; */ + if (ProcReadBuffer == NULL) { + DBGINF("ProcReadBuffer == NULL; allocating buffer.\n."); + ProcReadBuffer = vmalloc(PROC_READ_BUFFER_SIZE); + + if (ProcReadBuffer == NULL) { + LOGERR("failed to allocate buffer to provide proc data.\n"); + return -ENOMEM; + } + } + + temp = ProcReadBuffer; + + if ((*offset == 0) || (!ProcReadBufferValid)) { + DBGINF("calling info_debugfs_read_helper.\n"); + /* if the read fails, then -1 will be returned */ + totalBytes = info_debugfs_read_helper(&temp, &remaining_bytes); + ProcReadBufferValid = 1; + } else + totalBytes = strlen(ProcReadBuffer); + + return simple_read_from_buffer(buf, len, offset, + ProcReadBuffer, totalBytes); +} + +static struct device_info * +find_dev(U32 busNo, U32 devNo) +{ + struct bus_info *bus; + struct device_info *dev = NULL; + + read_lock(&BusListLock); + for (bus = BusListHead; bus; bus = bus->next) { + if (bus->busNo == busNo) { + /* make sure the device number is valid */ + if (devNo >= bus->deviceCount) { + LOGERR("%s bad busNo, devNo=%d,%d", + __func__, + (int) (busNo), (int) (devNo)); + goto Away; + } + dev = bus->device[devNo]; + if (!dev) + LOGERR("%s bad busNo, devNo=%d,%d", + __func__, + (int) (busNo), (int) (devNo)); + goto Away; + } + } +Away: + read_unlock(&BusListLock); + return dev; +} + +/* This thread calls the "interrupt" function for each device that has + * enabled such using uislib_enable_channel_interrupts(). The "interrupt" + * function typically reads and processes the devices's channel input + * queue. This thread repeatedly does this, until the thread is told to stop + * (via uisthread_stop()). Sleeping rules: + * - If we have called the "interrupt" function for all devices, and all of + * them have reported "nothing processed" (returned 0), then we will go to + * sleep for a maximum of POLLJIFFIES_NORMAL jiffies. + * - If anyone calls uislib_force_channel_interrupt(), the above jiffy + * sleep will be interrupted, and we will resume calling the "interrupt" + * function for all devices. + * - The list of devices is dynamically re-ordered in order to + * attempt to preserve fairness. Whenever we spin thru the list of + * devices and call the dev->interrupt() function, if we find + * devices which report that there is still more work to do, the + * the first such device we find is moved to the end of the device + * list. This ensures that extremely busy devices don't starve out + * less-busy ones. + * + */ +static int +Process_Incoming(void *v) +{ + unsigned long long cur_cycles, old_cycles, idle_cycles, delta_cycles; + struct list_head *new_tail = NULL; + int i; + UIS_DAEMONIZE("dev_incoming"); + for (i = 0; i < 16; i++) { + old_cycles = get_cycles(); + wait_event_timeout(Wakeup_Polling_Device_Channels, + 0, POLLJIFFIES_NORMAL); + cur_cycles = get_cycles(); + if (wait_cycles == 0) { + wait_cycles = (cur_cycles - old_cycles); + } else { + if (wait_cycles < (cur_cycles - old_cycles)) + wait_cycles = (cur_cycles - old_cycles); + } + } + LOGINF("wait_cycles=%llu", wait_cycles); + cycles_before_wait = wait_cycles; + idle_cycles = 0; + Go_Polling_Device_Channels = 0; + while (1) { + struct list_head *lelt, *tmp; + struct device_info *dev = NULL; + + /* poll each channel for input */ + LOCKSEM_UNINTERRUPTIBLE(&Lock_Polling_Device_Channels); + new_tail = NULL; + list_for_each_safe(lelt, tmp, &List_Polling_Device_Channels) { + int rc = 0; + dev = list_entry(lelt, struct device_info, + list_polling_device_channels); + LOCKSEM_UNINTERRUPTIBLE(&dev->interrupt_callback_lock); + if (dev->interrupt) + rc = dev->interrupt(dev->interrupt_context); + else + continue; + UNLOCKSEM(&dev->interrupt_callback_lock); + if (rc) { + /* dev->interrupt returned, but there + * is still more work to do. + * Reschedule work to occur as soon as + * possible. */ + idle_cycles = 0; + if (new_tail == NULL) { + dev->first_busy_cnt++; + if (! + (list_is_last + (lelt, + &List_Polling_Device_Channels))) { + new_tail = lelt; + dev->moved_to_tail_cnt++; + } else + dev->last_on_list_cnt++; + } + + } + if (Incoming_ThreadInfo.should_stop) + break; + } + if (new_tail != NULL) { + tot_moved_to_tail_cnt++; + list_move_tail(new_tail, &List_Polling_Device_Channels); + } + UNLOCKSEM(&Lock_Polling_Device_Channels); + cur_cycles = get_cycles(); + delta_cycles = cur_cycles - old_cycles; + old_cycles = cur_cycles; + + /* At this point, we have scanned thru all of the + * channels, and at least one of the following is true: + * - there is no input waiting on any of the channels + * - we have received a signal to stop this thread + */ + if (Incoming_ThreadInfo.should_stop) + break; + if (en_smart_wakeup == 0xFF) { + LOGINF("en_smart_wakeup set to 0xff, to force exiting process_incoming"); + break; + } + /* wait for POLLJIFFIES_NORMAL jiffies, or until + * someone wakes up Wakeup_Polling_Device_Channels, + * whichever comes first only do a wait when we have + * been idle for cycles_before_wait cycles. + */ + if (idle_cycles > cycles_before_wait) { + Go_Polling_Device_Channels = 0; + tot_wait_cnt++; + wait_event_timeout(Wakeup_Polling_Device_Channels, + Go_Polling_Device_Channels, + POLLJIFFIES_NORMAL); + Go_Polling_Device_Channels = 1; + } else { + tot_schedule_cnt++; + schedule(); + idle_cycles = idle_cycles + delta_cycles; + } + } + DBGINF("exiting.\n"); + complete_and_exit(&Incoming_ThreadInfo.has_stopped, 0); +} + +static BOOL +Initialize_incoming_thread(void) +{ + if (Incoming_Thread_Started) + return TRUE; + if (!uisthread_start(&Incoming_ThreadInfo, + &Process_Incoming, NULL, "dev_incoming")) { + LOGERR("uisthread_start Initialize_incoming_thread ****FAILED"); + return FALSE; + } + Incoming_Thread_Started = TRUE; + return TRUE; +} + +/* Add a new device/channel to the list being processed by + * Process_Incoming(). + * <interrupt> - indicates the function to call periodically. + * <interrupt_context> - indicates the data to pass to the <interrupt> + * function. + */ +void +uislib_enable_channel_interrupts(U32 busNo, U32 devNo, + int (*interrupt)(void *), + void *interrupt_context) +{ + struct device_info *dev; + dev = find_dev(busNo, devNo); + if (!dev) { + LOGERR("%s busNo=%d, devNo=%d", __func__, (int) (busNo), + (int) (devNo)); + return; + } + LOCKSEM_UNINTERRUPTIBLE(&Lock_Polling_Device_Channels); + Initialize_incoming_thread(); + dev->interrupt = interrupt; + dev->interrupt_context = interrupt_context; + dev->polling = TRUE; + list_add_tail(&(dev->list_polling_device_channels), + &List_Polling_Device_Channels); + UNLOCKSEM(&Lock_Polling_Device_Channels); +} +EXPORT_SYMBOL_GPL(uislib_enable_channel_interrupts); + +/* Remove a device/channel from the list being processed by + * Process_Incoming(). + */ +void +uislib_disable_channel_interrupts(U32 busNo, U32 devNo) +{ + struct device_info *dev; + dev = find_dev(busNo, devNo); + if (!dev) { + LOGERR("%s busNo=%d, devNo=%d", __func__, (int) (busNo), + (int) (devNo)); + return; + } + LOCKSEM_UNINTERRUPTIBLE(&Lock_Polling_Device_Channels); + list_del(&dev->list_polling_device_channels); + dev->polling = FALSE; + dev->interrupt = NULL; + UNLOCKSEM(&Lock_Polling_Device_Channels); +} +EXPORT_SYMBOL_GPL(uislib_disable_channel_interrupts); + +static void +do_wakeup_polling_device_channels(struct work_struct *dummy) +{ + if (!Go_Polling_Device_Channels) { + Go_Polling_Device_Channels = 1; + wake_up(&Wakeup_Polling_Device_Channels); + } +} + +static DECLARE_WORK(Work_wakeup_polling_device_channels, + do_wakeup_polling_device_channels); + +/* Call this function when you want to send a hint to Process_Incoming() that + * your device might have more requests. + */ +void +uislib_force_channel_interrupt(U32 busNo, U32 devNo) +{ + if (en_smart_wakeup == 0) + return; + if (Go_Polling_Device_Channels) + return; + /* The point of using schedule_work() instead of just doing + * the work inline is to force a slight delay before waking up + * the Process_Incoming() thread. + */ + tot_wakeup_cnt++; + schedule_work(&Work_wakeup_polling_device_channels); +} +EXPORT_SYMBOL_GPL(uislib_force_channel_interrupt); + +/*****************************************************/ +/* Module Init & Exit functions */ +/*****************************************************/ + +static int __init +uislib_mod_init(void) +{ + + if (!unisys_spar_platform) + return -ENODEV; + + LOGINF("MONITORAPIS"); + + LOGINF("sizeof(struct uiscmdrsp):%lu bytes\n", + (ulong) sizeof(struct uiscmdrsp)); + LOGINF("sizeof(struct phys_info):%lu\n", + (ulong) sizeof(struct phys_info)); + LOGINF("sizeof(uiscmdrsp_scsi):%lu\n", + (ulong) sizeof(struct uiscmdrsp_scsi)); + LOGINF("sizeof(uiscmdrsp_net):%lu\n", + (ulong) sizeof(struct uiscmdrsp_net)); + LOGINF("sizeof(CONTROLVM_MESSAGE):%lu bytes\n", + (ulong) sizeof(CONTROLVM_MESSAGE)); + LOGINF("sizeof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL):%lu bytes\n", + (ulong) sizeof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL)); + LOGINF("sizeof(CHANNEL_HEADER):%lu bytes\n", + (ulong) sizeof(CHANNEL_HEADER)); + LOGINF("sizeof(ULTRA_IO_CHANNEL_PROTOCOL):%lu bytes\n", + (ulong) sizeof(ULTRA_IO_CHANNEL_PROTOCOL)); + LOGINF("SIZEOF_CMDRSP:%lu bytes\n", SIZEOF_CMDRSP); + LOGINF("SIZEOF_PROTOCOL:%lu bytes\n", SIZEOF_PROTOCOL); + + /* initialize global pointers to NULL */ + BusListHead = NULL; + BusListCount = MaxBusCount = 0; + rwlock_init(&BusListLock); + VirtControlChanFunc = NULL; + + /* Issue VMCALL_GET_CONTROLVM_ADDR to get CtrlChanPhysAddr and + * then map this physical address to a virtual address. */ + POSTCODE_LINUX_2(DRIVER_ENTRY_PC, POSTCODE_SEVERITY_INFO); + + dir_debugfs = debugfs_create_dir(DIR_DEBUGFS_ENTRY, NULL); + if (dir_debugfs) { + info_debugfs_entry = debugfs_create_file( + INFO_DEBUGFS_ENTRY_FN, 0444, dir_debugfs, NULL, + &debugfs_info_fops); + + platformnumber_debugfs_read = debugfs_create_u32( + PLATFORMNUMBER_DEBUGFS_ENTRY_FN, 0444, dir_debugfs, + &PlatformNumber); + + cycles_before_wait_debugfs_read = debugfs_create_u64( + CYCLES_BEFORE_WAIT_DEBUGFS_ENTRY_FN, 0666, dir_debugfs, + &cycles_before_wait); + + smart_wakeup_debugfs_entry = debugfs_create_bool( + SMART_WAKEUP_DEBUGFS_ENTRY_FN, 0666, dir_debugfs, + &en_smart_wakeup); + } + + POSTCODE_LINUX_3(DRIVER_EXIT_PC, 0, POSTCODE_SEVERITY_INFO); + return 0; +} + +static void __exit +uislib_mod_exit(void) +{ + if (ProcReadBuffer) { + vfree(ProcReadBuffer); + ProcReadBuffer = NULL; + } + + debugfs_remove(info_debugfs_entry); + debugfs_remove(smart_wakeup_debugfs_entry); + debugfs_remove(cycles_before_wait_debugfs_read); + debugfs_remove(platformnumber_debugfs_read); + debugfs_remove(dir_debugfs); + + DBGINF("goodbye.\n"); + return; +} + +module_init(uislib_mod_init); +module_exit(uislib_mod_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Usha Srinivasan"); +MODULE_ALIAS("uislib"); + /* this is extracted during depmod and kept in modules.dep */ diff --git a/drivers/staging/unisys/uislib/uisqueue.c b/drivers/staging/unisys/uislib/uisqueue.c new file mode 100644 index 00000000000..d4a6074cfaf --- /dev/null +++ b/drivers/staging/unisys/uislib/uisqueue.c @@ -0,0 +1,160 @@ +/* uisqueue.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. + */ + +/* @ALL_INSPECTED */ +#include <linux/kernel.h> +#include <linux/module.h> + +#include "uisutils.h" + +#include "chanstub.h" + +/* this is shorter than using __FILE__ (full path name) in + * debug/info/error messages */ +#define CURRENT_FILE_PC UISLIB_PC_uisqueue_c +#define __MYFILE__ "uisqueue.c" + +#define CHECK_CACHE_ALIGN 0 + +/*****************************************************/ +/* Exported functions */ +/*****************************************************/ +unsigned long long +uisqueue_InterlockedOr(unsigned long long __iomem *Target, + unsigned long long Set) +{ + unsigned long long i; + unsigned long long j; + + j = readq(Target); + do { + i = j; + j = uislibcmpxchg64((__force unsigned long long *)Target, + i, i | Set, sizeof(*(Target))); + + } while (i != j); + + return j; +} +EXPORT_SYMBOL_GPL(uisqueue_InterlockedOr); + +unsigned long long +uisqueue_InterlockedAnd(unsigned long long __iomem *Target, + unsigned long long Set) +{ + unsigned long long i; + unsigned long long j; + + j = readq(Target); + do { + i = j; + j = uislibcmpxchg64((__force unsigned long long *)Target, + i, i & Set, sizeof(*(Target))); + + } while (i != j); + + return j; +} +EXPORT_SYMBOL_GPL(uisqueue_InterlockedAnd); + +static U8 +do_locked_client_insert(struct uisqueue_info *queueinfo, + unsigned int whichqueue, + void *pSignal, + spinlock_t *lock, + unsigned char issueInterruptIfEmpty, + U64 interruptHandle, U8 *channelId) +{ + unsigned long flags; + unsigned char queueWasEmpty; + unsigned int locked = 0; + unsigned int acquired = 0; + U8 rc = 0; + + spin_lock_irqsave(lock, flags); + locked = 1; + + if (!ULTRA_CHANNEL_CLIENT_ACQUIRE_OS(queueinfo->chan, channelId, NULL)) + goto Away; + + acquired = 1; + + queueWasEmpty = visor_signalqueue_empty(queueinfo->chan, whichqueue); + if (!visor_signal_insert(queueinfo->chan, whichqueue, pSignal)) + goto Away; + ULTRA_CHANNEL_CLIENT_RELEASE_OS(queueinfo->chan, channelId, NULL); + acquired = 0; + spin_unlock_irqrestore(lock, flags); + locked = 0; + + queueinfo->packets_sent++; + + rc = 1; +Away: + if (acquired) { + ULTRA_CHANNEL_CLIENT_RELEASE_OS(queueinfo->chan, channelId, + NULL); + acquired = 0; + } + if (locked) { + spin_unlock_irqrestore((spinlock_t *) lock, flags); + locked = 0; + } + + return rc; +} + +int +uisqueue_put_cmdrsp_with_lock_client(struct uisqueue_info *queueinfo, + struct uiscmdrsp *cmdrsp, + unsigned int whichqueue, + void *insertlock, + unsigned char issueInterruptIfEmpty, + U64 interruptHandle, + char oktowait, U8 *channelId) +{ + while (!do_locked_client_insert(queueinfo, whichqueue, cmdrsp, + (spinlock_t *) insertlock, + issueInterruptIfEmpty, + interruptHandle, channelId)) { + if (oktowait != OK_TO_WAIT) { + LOGERR("****FAILED visor_signal_insert failed; cannot wait; insert aborted\n"); + return 0; /* failed to queue */ + } + /* try again */ + LOGERR("****FAILED visor_signal_insert failed; waiting to try again\n"); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(10)); + } + return 1; +} +EXPORT_SYMBOL_GPL(uisqueue_put_cmdrsp_with_lock_client); + +/* uisqueue_get_cmdrsp gets the cmdrsp entry at the head of the queue + * returns NULL if queue is empty */ +int +uisqueue_get_cmdrsp(struct uisqueue_info *queueinfo, + void *cmdrsp, unsigned int whichqueue) +{ + if (!visor_signal_remove(queueinfo->chan, whichqueue, cmdrsp)) + return 0; + + queueinfo->packets_received++; + + return 1; /* Success */ +} +EXPORT_SYMBOL_GPL(uisqueue_get_cmdrsp); diff --git a/drivers/staging/unisys/uislib/uisthread.c b/drivers/staging/unisys/uislib/uisthread.c new file mode 100644 index 00000000000..c93ab04c3cf --- /dev/null +++ b/drivers/staging/unisys/uislib/uisthread.c @@ -0,0 +1,85 @@ +/* uisthread.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. + */ + +/* @ALL_INSPECTED */ +#include <asm/processor.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include "uniklog.h" +#include "uisutils.h" +#include "uisthread.h" + +#define KILL(a, b, c) kill_pid(find_vpid(a), b, c) + +/* this is shorter than using __FILE__ (full path name) in + * debug/info/error messages + */ +#define CURRENT_FILE_PC UISLIB_PC_uisthread_c +#define __MYFILE__ "uisthread.c" + +/*****************************************************/ +/* Exported functions */ +/*****************************************************/ + +/* returns 0 for failure, 1 for success */ +int +uisthread_start(struct uisthread_info *thrinfo, + int (*threadfn)(void *), void *thrcontext, char *name) +{ + thrinfo->should_stop = 0; + /* used to stop the thread */ + init_completion(&thrinfo->has_stopped); + thrinfo->task = kthread_create(threadfn, thrcontext, name, NULL); + if (IS_ERR(thrinfo->task)) { + thrinfo->id = 0; + return 0; /* failure */ + } + thrinfo->id = thrinfo->task->pid; + wake_up_process(thrinfo->task); + LOGINF("started thread pid:%d\n", thrinfo->id); + return 1; + +} +EXPORT_SYMBOL_GPL(uisthread_start); + +void +uisthread_stop(struct uisthread_info *thrinfo) +{ + int ret; + int stopped = 0; + if (thrinfo->id == 0) + return; /* thread not running */ + + LOGINF("uisthread_stop stopping id:%d\n", thrinfo->id); + thrinfo->should_stop = 1; + ret = KILL(thrinfo->id, SIGHUP, 1); + if (ret) { + LOGERR("unable to signal thread %d\n", ret); + } else { + /* give up if the thread has NOT died in 1 minute */ + if (wait_for_completion_timeout(&thrinfo->has_stopped, 60 * HZ)) + stopped = 1; + else + LOGERR("timed out trying to signal thread\n"); + } + if (stopped) { + LOGINF("uisthread_stop stopped id:%d\n", thrinfo->id); + thrinfo->id = 0; + } +} +EXPORT_SYMBOL_GPL(uisthread_stop); diff --git a/drivers/staging/unisys/uislib/uisutils.c b/drivers/staging/unisys/uislib/uisutils.c new file mode 100644 index 00000000000..0f1bb739975 --- /dev/null +++ b/drivers/staging/unisys/uislib/uisutils.c @@ -0,0 +1,341 @@ +/* uisutils.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. + */ + +#include <linux/string.h> +#include <linux/slab.h> +#include <commontypes.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include "uniklog.h" +#include "uisutils.h" +#include "version.h" +#include "vbushelper.h" +#include <linux/uuid.h> +#include <linux/skbuff.h> +#include <linux/uuid.h> +#ifdef CONFIG_HIGHMEM +#include <linux/highmem.h> +#endif + +/* this is shorter than using __FILE__ (full path name) in + * debug/info/error messages + */ +#define CURRENT_FILE_PC UISLIB_PC_uisutils_c +#define __MYFILE__ "uisutils.c" + +/* exports */ +atomic_t UisUtils_Registered_Services = ATOMIC_INIT(0); + /* num registrations via + * uisctrl_register_req_handler() or + * uisctrl_register_req_handler_ex() */ + + +/*****************************************************/ +/* Utility functions */ +/*****************************************************/ + +int +uisutil_add_proc_line_ex(int *total, char **buffer, int *buffer_remaining, + char *format, ...) +{ + va_list args; + int len; + + DBGINF("buffer = 0x%p : *buffer = 0x%p.\n", buffer, *buffer); + va_start(args, format); + len = vsnprintf(*buffer, *buffer_remaining, format, args); + if (len >= *buffer_remaining) { + *buffer += *buffer_remaining; + *total += *buffer_remaining; + *buffer_remaining = 0; + LOGERR("bytes remaining is too small!\n"); + return -1; + } + *buffer_remaining -= len; + *buffer += len; + *total += len; + return len; +} +EXPORT_SYMBOL_GPL(uisutil_add_proc_line_ex); + +int +uisctrl_register_req_handler(int type, void *fptr, + ULTRA_VBUS_DEVICEINFO *chipset_DriverInfo) +{ + LOGINF("type = %d, fptr = 0x%p.\n", type, fptr); + + switch (type) { + case 2: + if (fptr) { + if (!VirtControlChanFunc) + atomic_inc(&UisUtils_Registered_Services); + VirtControlChanFunc = fptr; + } else { + if (VirtControlChanFunc) + atomic_dec(&UisUtils_Registered_Services); + VirtControlChanFunc = NULL; + } + break; + + default: + LOGERR("invalid type %d.\n", type); + return 0; + } + if (chipset_DriverInfo) + BusDeviceInfo_Init(chipset_DriverInfo, + "chipset", "uislib", + VERSION, NULL, __DATE__, __TIME__); + + return 1; +} +EXPORT_SYMBOL_GPL(uisctrl_register_req_handler); + +int +uisctrl_register_req_handler_ex(uuid_le switchTypeGuid, + const char *switch_type_name, + int (*controlfunc)(struct io_msgs *), + unsigned long min_channel_bytes, + int (*Server_Channel_Ok)(unsigned long + channelBytes), + int (*Server_Channel_Init) + (void *x, unsigned char *clientStr, + U32 clientStrLen, U64 bytes), + ULTRA_VBUS_DEVICEINFO *chipset_DriverInfo) +{ + ReqHandlerInfo_t *pReqHandlerInfo; + int rc = 0; /* assume failure */ + LOGINF("type=%pUL, controlfunc=0x%p.\n", + &switchTypeGuid, controlfunc); + if (!controlfunc) { + LOGERR("%pUL: controlfunc must be supplied\n", &switchTypeGuid); + goto Away; + } + if (!Server_Channel_Ok) { + LOGERR("%pUL: Server_Channel_Ok must be supplied\n", + &switchTypeGuid); + goto Away; + } + if (!Server_Channel_Init) { + LOGERR("%pUL: Server_Channel_Init must be supplied\n", + &switchTypeGuid); + goto Away; + } + pReqHandlerInfo = ReqHandlerAdd(switchTypeGuid, + switch_type_name, + controlfunc, + min_channel_bytes, + Server_Channel_Ok, Server_Channel_Init); + if (!pReqHandlerInfo) { + LOGERR("failed to add %pUL to server list\n", &switchTypeGuid); + goto Away; + } + + atomic_inc(&UisUtils_Registered_Services); + rc = 1; /* success */ +Away: + if (rc) { + if (chipset_DriverInfo) + BusDeviceInfo_Init(chipset_DriverInfo, + "chipset", "uislib", + VERSION, NULL, + __DATE__, __TIME__); + } else + LOGERR("failed to register type %pUL.\n", &switchTypeGuid); + + return rc; +} +EXPORT_SYMBOL_GPL(uisctrl_register_req_handler_ex); + +int +uisctrl_unregister_req_handler_ex(uuid_le switchTypeGuid) +{ + int rc = 0; /* assume failure */ + LOGINF("type=%pUL.\n", &switchTypeGuid); + if (ReqHandlerDel(switchTypeGuid) < 0) { + LOGERR("failed to remove %pUL from server list\n", + &switchTypeGuid); + goto Away; + } + atomic_dec(&UisUtils_Registered_Services); + rc = 1; /* success */ +Away: + if (!rc) + LOGERR("failed to unregister type %pUL.\n", &switchTypeGuid); + return rc; +} +EXPORT_SYMBOL_GPL(uisctrl_unregister_req_handler_ex); + +/* + * unsigned int uisutil_copy_fragsinfo_from_skb(unsigned char *calling_ctx, + * void *skb_in, + * unsigned int firstfraglen, + * unsigned int frags_max, + * struct phys_info frags[]) + * + * calling_ctx - input - a string that is displayed to show + * who called * this func + * void *skb_in - skb whose frag info we're copying type is hidden so we + * don't need to include skbbuff in uisutils.h which is + * included in non-networking code. + * unsigned int firstfraglen - input - length of first fragment in skb + * unsigned int frags_max - input - max len of frags array + * struct phys_info frags[] - output - frags array filled in on output + * return value indicates number of + * entries filled in frags + */ +unsigned int +uisutil_copy_fragsinfo_from_skb(unsigned char *calling_ctx, void *skb_in, + unsigned int firstfraglen, + unsigned int frags_max, + struct phys_info frags[]) +{ + unsigned int count = 0, ii, size, offset = 0, numfrags; + struct sk_buff *skb = skb_in; + + numfrags = skb_shinfo(skb)->nr_frags; + + while (firstfraglen) { + if (count == frags_max) { + LOGERR("%s frags array too small: max:%d count:%d\n", + calling_ctx, frags_max, count); + return -1; /* failure */ + } + frags[count].pi_pfn = + page_to_pfn(virt_to_page(skb->data + offset)); + frags[count].pi_off = + (unsigned long) (skb->data + offset) & PI_PAGE_MASK; + size = + min(firstfraglen, + (unsigned int) (PI_PAGE_SIZE - frags[count].pi_off)); + /* can take smallest of firstfraglen(what's left) OR + * bytes left in the page + */ + frags[count].pi_len = size; + firstfraglen -= size; + offset += size; + count++; + } + if (numfrags) { + if ((count + numfrags) > frags_max) { + LOGERR("**** FAILED %s frags array too small: max:%d count+nr_frags:%d\n", + calling_ctx, frags_max, count + numfrags); + return -1; /* failure */ + } + + for (ii = 0; ii < numfrags; ii++) { + count = add_physinfo_entries(page_to_pfn(skb_frag_page(&skb_shinfo(skb)->frags[ii])), /* pfn */ + skb_shinfo(skb)->frags[ii]. + page_offset, + skb_shinfo(skb)->frags[ii]. + size, count, frags_max, + frags); + if (count == 0) { + LOGERR("**** FAILED to add physinfo entries\n"); + return -1; /* failure */ + } + } + } + if (skb_shinfo(skb)->frag_list) { + struct sk_buff *skbinlist; + int c; + for (skbinlist = skb_shinfo(skb)->frag_list; skbinlist; + skbinlist = skbinlist->next) { + + c = uisutil_copy_fragsinfo_from_skb("recursive", + skbinlist, + skbinlist->len - + skbinlist->data_len, + frags_max - count, + &frags[count]); + if (c == -1) { + LOGERR("**** FAILED recursive call failed\n"); + return -1; + } + count += c; + } + } + return count; +} +EXPORT_SYMBOL_GPL(uisutil_copy_fragsinfo_from_skb); + +static LIST_HEAD(ReqHandlerInfo_list); /* list of ReqHandlerInfo_t */ +static DEFINE_SPINLOCK(ReqHandlerInfo_list_lock); + +ReqHandlerInfo_t * +ReqHandlerAdd(uuid_le switchTypeGuid, + const char *switch_type_name, + int (*controlfunc)(struct io_msgs *), + unsigned long min_channel_bytes, + int (*Server_Channel_Ok)(unsigned long channelBytes), + int (*Server_Channel_Init) + (void *x, unsigned char *clientStr, U32 clientStrLen, U64 bytes)) +{ + ReqHandlerInfo_t *rc = NULL; + + rc = kzalloc(sizeof(*rc), GFP_ATOMIC); + if (!rc) + return NULL; + rc->switchTypeGuid = switchTypeGuid; + rc->controlfunc = controlfunc; + rc->min_channel_bytes = min_channel_bytes; + rc->Server_Channel_Ok = Server_Channel_Ok; + rc->Server_Channel_Init = Server_Channel_Init; + if (switch_type_name) + strncpy(rc->switch_type_name, switch_type_name, + sizeof(rc->switch_type_name) - 1); + spin_lock(&ReqHandlerInfo_list_lock); + list_add_tail(&(rc->list_link), &ReqHandlerInfo_list); + spin_unlock(&ReqHandlerInfo_list_lock); + + return rc; +} + +ReqHandlerInfo_t * +ReqHandlerFind(uuid_le switchTypeGuid) +{ + struct list_head *lelt, *tmp; + ReqHandlerInfo_t *entry = NULL; + spin_lock(&ReqHandlerInfo_list_lock); + list_for_each_safe(lelt, tmp, &ReqHandlerInfo_list) { + entry = list_entry(lelt, ReqHandlerInfo_t, list_link); + if (uuid_le_cmp(entry->switchTypeGuid, switchTypeGuid) == 0) { + spin_unlock(&ReqHandlerInfo_list_lock); + return entry; + } + } + spin_unlock(&ReqHandlerInfo_list_lock); + return NULL; +} + +int +ReqHandlerDel(uuid_le switchTypeGuid) +{ + struct list_head *lelt, *tmp; + ReqHandlerInfo_t *entry = NULL; + int rc = -1; + spin_lock(&ReqHandlerInfo_list_lock); + list_for_each_safe(lelt, tmp, &ReqHandlerInfo_list) { + entry = list_entry(lelt, ReqHandlerInfo_t, list_link); + if (uuid_le_cmp(entry->switchTypeGuid, switchTypeGuid) == 0) { + list_del(lelt); + kfree(entry); + rc++; + } + } + spin_unlock(&ReqHandlerInfo_list_lock); + return rc; +} diff --git a/drivers/staging/unisys/virthba/Kconfig b/drivers/staging/unisys/virthba/Kconfig new file mode 100644 index 00000000000..c0d7986e78c --- /dev/null +++ b/drivers/staging/unisys/virthba/Kconfig @@ -0,0 +1,10 @@ +# +# Unisys virthba configuration +# + +config UNISYS_VIRTHBA + tristate "Unisys virthba driver" + depends on UNISYSSPAR && UNISYS_VISORCHIPSET && UNISYS_CHANNELSTUB && UNISYS_UISLIB && UNISYS_VIRTPCI && SCSI + ---help--- + If you say Y here, you will enable the Unisys virthba driver. + diff --git a/drivers/staging/unisys/virthba/Makefile b/drivers/staging/unisys/virthba/Makefile new file mode 100644 index 00000000000..632b1c08b97 --- /dev/null +++ b/drivers/staging/unisys/virthba/Makefile @@ -0,0 +1,16 @@ +# +# Makefile for Unisys virthba +# + +obj-$(CONFIG_UNISYS_VIRTHBA) += virthba.o + +ccflags-y += -Idrivers/staging/unisys/include +ccflags-y += -Idrivers/staging/unisys/uislib +ccflags-y += -Idrivers/staging/unisys/timskmod +ccflags-y += -Idrivers/staging/unisys/visorchipset +ccflags-y += -Idrivers/staging/unisys/virtpci +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/virthba/virthba.c b/drivers/staging/unisys/virthba/virthba.c new file mode 100644 index 00000000000..5c5aa700176 --- /dev/null +++ b/drivers/staging/unisys/virthba/virthba.c @@ -0,0 +1,1834 @@ +/* virthba.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 + +/* if you want to turn on some debugging of write device data or read + * device data, define these two undefs. You will probably want to + * customize the code which is here since it was written assuming + * reading and writing a specific data file df.64M.txt which is a + * 64Megabyte file created by Art Nilson using a scritp I wrote called + * cr_test_data.pl. The data file consists of 256 byte lines of text + * which start with an 8 digit sequence number, a colon, and then + * letters after that */ + +#undef DBGINF + +#include <linux/kernel.h> +#ifdef CONFIG_MODVERSIONS +#include <config/modversions.h> +#endif + +#include "uniklog.h" +#include "diagnostics/appos_subsystems.h" +#include "uisutils.h" +#include "uisqueue.h" +#include "uisthread.h" + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_device.h> +#include <asm/param.h> +#include <linux/proc_fs.h> +#include <linux/types.h> + +#include "virthba.h" +#include "virtpci.h" +#include "visorchipset.h" +#include "version.h" +#include "guestlinuxdebug.h" +/* this is shorter than using __FILE__ (full path name) in + * debug/info/error messages + */ +#define CURRENT_FILE_PC VIRT_HBA_PC_virthba_c +#define __MYFILE__ "virthba.c" + +/* NOTE: L1_CACHE_BYTES >=128 */ +#define DEVICE_ATTRIBUTE struct device_attribute + +/*****************************************************/ +/* Forward declarations */ +/*****************************************************/ +static int virthba_probe(struct virtpci_dev *dev, + const struct pci_device_id *id); +static void virthba_remove(struct virtpci_dev *dev); +static int virthba_abort_handler(struct scsi_cmnd *scsicmd); +static int virthba_bus_reset_handler(struct scsi_cmnd *scsicmd); +static int virthba_device_reset_handler(struct scsi_cmnd *scsicmd); +static int virthba_host_reset_handler(struct scsi_cmnd *scsicmd); +static const char *virthba_get_info(struct Scsi_Host *shp); +static int virthba_ioctl(struct scsi_device *dev, int cmd, void __user *arg); +static int virthba_queue_command_lck(struct scsi_cmnd *scsicmd, + void (*virthba_cmnd_done)(struct scsi_cmnd *)); + +static const struct x86_cpu_id unisys_spar_ids[] = { + { X86_VENDOR_INTEL, 6, 62, X86_FEATURE_ANY }, + {} +}; + +/* Autoload */ +MODULE_DEVICE_TABLE(x86cpu, unisys_spar_ids); + +#ifdef DEF_SCSI_QCMD +static DEF_SCSI_QCMD(virthba_queue_command) +#else +#define virthba_queue_command virthba_queue_command_lck +#endif + + +static int virthba_slave_alloc(struct scsi_device *scsidev); +static int virthba_slave_configure(struct scsi_device *scsidev); +static void virthba_slave_destroy(struct scsi_device *scsidev); +static int process_incoming_rsps(void *); +static int virthba_serverup(struct virtpci_dev *virtpcidev); +static int virthba_serverdown(struct virtpci_dev *virtpcidev, u32 state); +static void doDiskAddRemove(struct work_struct *work); +static void virthba_serverdown_complete(struct work_struct *work); + +static ssize_t info_proc_read(struct file *file, char __user *buf, + size_t len, loff_t *offset); +static ssize_t rqwu_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos); +static ssize_t enable_ints_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos); +static ssize_t enable_ints_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos); + +/*****************************************************/ +/* Globals */ +/*****************************************************/ + +static int rsltq_wait_usecs = 4000; /* Default 4ms */ +static unsigned int MaxBuffLen; + +/* Module options */ +static char *virthba_options = "NONE"; + +static const struct pci_device_id virthba_id_table[] = { + {PCI_DEVICE(PCI_VENDOR_ID_UNISYS, PCI_DEVICE_ID_VIRTHBA)}, + {0}, +}; + +/* export virthba_id_table */ +MODULE_DEVICE_TABLE(pci, virthba_id_table); + +static struct workqueue_struct *virthba_serverdown_workqueue; + +static struct virtpci_driver virthba_driver = { + .name = "uisvirthba", + .version = VERSION, + .vertag = NULL, + .build_date = __DATE__, + .build_time = __TIME__, + .id_table = virthba_id_table, + .probe = virthba_probe, + .remove = virthba_remove, + .resume = virthba_serverup, + .suspend = virthba_serverdown +}; + +/* The Send and Recive Buffers of the IO Queue may both be full */ +#define MAX_PENDING_REQUESTS (MIN_NUMSIGNALS*2) +#define INTERRUPT_VECTOR_MASK 0x3F + +struct scsipending { + char cmdtype; /* Type of pointer that is being stored */ + void *sent; /* The Data being tracked */ + /* struct scsi_cmnd *type for virthba_queue_command */ + /* struct uiscmdrsp *type for management commands */ +}; + +#define VIRTHBA_ERROR_COUNT 30 +#define IOS_ERROR_THRESHOLD 1000 +struct virtdisk_info { + U32 valid; + U32 channel, id, lun; /* Disk Path */ + atomic_t ios_threshold; + atomic_t error_count; + struct virtdisk_info *next; +}; +/* Each Scsi_Host has a host_data area that contains this struct. */ +struct virthba_info { + struct Scsi_Host *scsihost; + struct virtpci_dev *virtpcidev; + struct list_head dev_info_list; + struct chaninfo chinfo; + struct InterruptInfo intr; /* use recvInterrupt info to receive + interrupts when IOs complete */ + int interrupt_vector; + struct scsipending pending[MAX_PENDING_REQUESTS]; /* Tracks the requests + that have been */ + /* forwarded to the IOVM and haven't returned yet */ + unsigned int nextinsert; /* Start search for next pending + free slot here */ + spinlock_t privlock; + bool serverdown; + bool serverchangingstate; + unsigned long long acquire_failed_cnt; + unsigned long long interrupts_rcvd; + unsigned long long interrupts_notme; + unsigned long long interrupts_disabled; + struct work_struct serverdown_completion; + U64 __iomem *flags_addr; + atomic_t interrupt_rcvd; + wait_queue_head_t rsp_queue; + struct virtdisk_info head; +}; + +/* Work Data for DARWorkQ */ +struct diskaddremove { + U8 add; /* 0-remove, 1-add */ + struct Scsi_Host *shost; /* Scsi Host for this virthba instance */ + U32 channel, id, lun; /* Disk Path */ + struct diskaddremove *next; +}; + +#define virtpci_dev_to_virthba_virthba_get_info(d) \ + container_of(d, struct virthba_info, virtpcidev) + +static DEVICE_ATTRIBUTE *virthba_shost_attrs[]; +static struct scsi_host_template virthba_driver_template = { + .name = "Unisys Virtual HBA", + .proc_name = "uisvirthba", + .info = virthba_get_info, + .ioctl = virthba_ioctl, + .queuecommand = virthba_queue_command, + .eh_abort_handler = virthba_abort_handler, + .eh_device_reset_handler = virthba_device_reset_handler, + .eh_bus_reset_handler = virthba_bus_reset_handler, + .eh_host_reset_handler = virthba_host_reset_handler, + .shost_attrs = virthba_shost_attrs, + +#define VIRTHBA_MAX_CMNDS 128 + .can_queue = VIRTHBA_MAX_CMNDS, + .sg_tablesize = 64, /* largest number of address/length pairs */ + .this_id = -1, + .slave_alloc = virthba_slave_alloc, + .slave_configure = virthba_slave_configure, + .slave_destroy = virthba_slave_destroy, + .use_clustering = ENABLE_CLUSTERING, +}; + +struct virthba_devices_open { + struct virthba_info *virthbainfo; +}; + +static const struct file_operations proc_info_fops = { + .read = info_proc_read, +}; + +static const struct file_operations proc_rqwu_fops = { + .write = rqwu_proc_write, +}; + +static const struct file_operations proc_enable_ints_fops = { + .read = enable_ints_read, + .write = enable_ints_write, +}; + + +#define VIRTHBASOPENMAX 1 +/* array of open devices maintained by open() and close(); */ +static struct virthba_devices_open VirtHbasOpen[VIRTHBASOPENMAX]; +static struct proc_dir_entry *virthba_proc_dir; +static struct proc_dir_entry *info_proc_entry; +static struct proc_dir_entry *rqwaitus_proc_entry; +static struct proc_dir_entry *enable_ints_proc_entry; +#define INFO_PROC_ENTRY_FN "info" +#define ENABLE_INTS_ENTRY_FN "enable_ints" +#define RQWU_PROC_ENTRY_FN "rqwait_usecs" +#define DIR_PROC_ENTRY "virthba" + +/*****************************************************/ +/* Local Functions */ +/*****************************************************/ +static int +add_scsipending_entry(struct virthba_info *vhbainfo, char cmdtype, void *new) +{ + unsigned long flags; + int insert_location; + + spin_lock_irqsave(&vhbainfo->privlock, flags); + insert_location = vhbainfo->nextinsert; + while (vhbainfo->pending[insert_location].sent != NULL) { + insert_location = (insert_location + 1) % MAX_PENDING_REQUESTS; + if (insert_location == (int) vhbainfo->nextinsert) { + LOGERR("Queue should be full. insert_location<<%d>> Unable to find open slot for pending commands.\n", + insert_location); + spin_unlock_irqrestore(&vhbainfo->privlock, flags); + return -1; + } + } + + vhbainfo->pending[insert_location].cmdtype = cmdtype; + vhbainfo->pending[insert_location].sent = new; + vhbainfo->nextinsert = (insert_location + 1) % MAX_PENDING_REQUESTS; + spin_unlock_irqrestore(&vhbainfo->privlock, flags); + + return insert_location; +} + +static unsigned int +add_scsipending_entry_with_wait(struct virthba_info *vhbainfo, char cmdtype, + void *new) +{ + int insert_location = add_scsipending_entry(vhbainfo, cmdtype, new); + + while (insert_location == -1) { + LOGERR("Failed to find empty queue slot. Waiting to try again\n"); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(10)); + insert_location = add_scsipending_entry(vhbainfo, cmdtype, new); + } + + return (unsigned int) insert_location; +} + +static void * +del_scsipending_entry(struct virthba_info *vhbainfo, uintptr_t del) +{ + unsigned long flags; + void *sent = NULL; + + if (del >= MAX_PENDING_REQUESTS) { + LOGERR("Invalid queue position <<%lu>> given to delete. MAX_PENDING_REQUESTS <<%d>>\n", + (unsigned long) del, MAX_PENDING_REQUESTS); + } else { + spin_lock_irqsave(&vhbainfo->privlock, flags); + + if (vhbainfo->pending[del].sent == NULL) + LOGERR("Deleting already cleared queue entry at <<%lu>>.\n", + (unsigned long) del); + + sent = vhbainfo->pending[del].sent; + + vhbainfo->pending[del].cmdtype = 0; + vhbainfo->pending[del].sent = NULL; + spin_unlock_irqrestore(&vhbainfo->privlock, flags); + } + + return sent; +} + +/* DARWorkQ (Disk Add/Remove) */ +static struct work_struct DARWorkQ; +static struct diskaddremove *DARWorkQHead; +static spinlock_t DARWorkQLock; +static unsigned short DARWorkQSched; +#define QUEUE_DISKADDREMOVE(dar) { \ + spin_lock_irqsave(&DARWorkQLock, flags); \ + if (!DARWorkQHead) { \ + DARWorkQHead = dar; \ + dar->next = NULL; \ + } \ + else { \ + dar->next = DARWorkQHead; \ + DARWorkQHead = dar; \ + } \ + if (!DARWorkQSched) { \ + schedule_work(&DARWorkQ); \ + DARWorkQSched = 1; \ + } \ + spin_unlock_irqrestore(&DARWorkQLock, flags); \ +} + +static inline void +SendDiskAddRemove(struct diskaddremove *dar) +{ + struct scsi_device *sdev; + int error; + + sdev = scsi_device_lookup(dar->shost, dar->channel, dar->id, dar->lun); + if (sdev) { + if (!(dar->add)) + scsi_remove_device(sdev); + } else if (dar->add) { + error = + scsi_add_device(dar->shost, dar->channel, dar->id, + dar->lun); + if (error) + LOGERR("Failed scsi_add_device: host_no=%d[chan=%d:id=%d:lun=%d]\n", + dar->shost->host_no, dar->channel, dar->id, + dar->lun); + } else + LOGERR("Failed scsi_device_lookup:[chan=%d:id=%d:lun=%d]\n", + dar->channel, dar->id, dar->lun); + kfree(dar); +} + +/*****************************************************/ +/* DARWorkQ Handler Thread */ +/*****************************************************/ +static void +doDiskAddRemove(struct work_struct *work) +{ + struct diskaddremove *dar; + struct diskaddremove *tmphead; + int i = 0; + unsigned long flags; + + spin_lock_irqsave(&DARWorkQLock, flags); + tmphead = DARWorkQHead; + DARWorkQHead = NULL; + DARWorkQSched = 0; + spin_unlock_irqrestore(&DARWorkQLock, flags); + while (tmphead) { + dar = tmphead; + tmphead = dar->next; + SendDiskAddRemove(dar); + i++; + } +} + +/*****************************************************/ +/* Routine to add entry to DARWorkQ */ +/*****************************************************/ +static void +process_disk_notify(struct Scsi_Host *shost, struct uiscmdrsp *cmdrsp) +{ + struct diskaddremove *dar; + unsigned long flags; + + dar = kzalloc(sizeof(struct diskaddremove), GFP_ATOMIC); + if (dar) { + dar->add = cmdrsp->disknotify.add; + dar->shost = shost; + dar->channel = cmdrsp->disknotify.channel; + dar->id = cmdrsp->disknotify.id; + dar->lun = cmdrsp->disknotify.lun; + QUEUE_DISKADDREMOVE(dar); + } else { + LOGERR("kmalloc failed for dar. host_no=%d[chan=%d:id=%d:lun=%d]\n", + shost->host_no, cmdrsp->disknotify.channel, + cmdrsp->disknotify.id, cmdrsp->disknotify.lun); + } +} + +/*****************************************************/ +/* Probe Remove Functions */ +/*****************************************************/ +static irqreturn_t +virthba_ISR(int irq, void *dev_id) +{ + struct virthba_info *virthbainfo = (struct virthba_info *) dev_id; + CHANNEL_HEADER __iomem *pChannelHeader; + SIGNAL_QUEUE_HEADER __iomem *pqhdr; + U64 mask; + unsigned long long rc1; + + if (virthbainfo == NULL) + return IRQ_NONE; + virthbainfo->interrupts_rcvd++; + pChannelHeader = virthbainfo->chinfo.queueinfo->chan; + if (((readq(&pChannelHeader->Features) + & ULTRA_IO_IOVM_IS_OK_WITH_DRIVER_DISABLING_INTS) != 0) + && ((readq(&pChannelHeader->Features) & + ULTRA_IO_DRIVER_DISABLES_INTS) != + 0)) { + virthbainfo->interrupts_disabled++; + mask = ~ULTRA_CHANNEL_ENABLE_INTS; + rc1 = uisqueue_InterlockedAnd(virthbainfo->flags_addr, mask); + } + if (visor_signalqueue_empty(pChannelHeader, IOCHAN_FROM_IOPART)) { + virthbainfo->interrupts_notme++; + return IRQ_NONE; + } + pqhdr = (SIGNAL_QUEUE_HEADER __iomem *) + ((char __iomem *) pChannelHeader + + readq(&pChannelHeader->oChannelSpace)) + IOCHAN_FROM_IOPART; + writeq(readq(&pqhdr->NumInterruptsReceived) + 1, + &pqhdr->NumInterruptsReceived); + atomic_set(&virthbainfo->interrupt_rcvd, 1); + wake_up_interruptible(&virthbainfo->rsp_queue); + return IRQ_HANDLED; +} + +static int +virthba_probe(struct virtpci_dev *virtpcidev, const struct pci_device_id *id) +{ + int error; + struct Scsi_Host *scsihost; + struct virthba_info *virthbainfo; + int rsp; + int i; + irq_handler_t handler = virthba_ISR; + CHANNEL_HEADER __iomem *pChannelHeader; + SIGNAL_QUEUE_HEADER __iomem *pqhdr; + U64 mask; + + LOGVER("entering virthba_probe...\n"); + LOGVER("virtpcidev busNo<<%d>>devNo<<%d>>", virtpcidev->busNo, + virtpcidev->deviceNo); + + LOGINF("entering virthba_probe...\n"); + LOGINF("virtpcidev busNo<<%d>>devNo<<%d>>", virtpcidev->busNo, + virtpcidev->deviceNo); + POSTCODE_LINUX_2(VHBA_PROBE_ENTRY_PC, POSTCODE_SEVERITY_INFO); + /* call scsi_host_alloc to register a scsi host adapter + * instance - this virthba that has just been created is an + * instance of a scsi host adapter. This scsi_host_alloc + * function allocates a new Scsi_Host struct & performs basic + * initializatoin. The host is not published to the scsi + * midlayer until scsi_add_host is called. + */ + DBGINF("calling scsi_host_alloc.\n"); + + /* arg 2 passed in length of extra space we want allocated + * with scsi_host struct for our own use scsi_host_alloc + * assign host_no + */ + scsihost = scsi_host_alloc(&virthba_driver_template, + sizeof(struct virthba_info)); + if (scsihost == NULL) + return -ENODEV; + + DBGINF("scsihost: 0x%p, scsihost->this_id: %d, host_no: %d.\n", + scsihost, scsihost->this_id, scsihost->host_no); + + scsihost->this_id = UIS_MAGIC_VHBA; + /* linux treats max-channel differently than max-id & max-lun. + * In the latter cases, those two values result in 0 to max-1 + * (inclusive) being scanned. But in the case of channels, the + * scan is 0 to max (inclusive); so we will subtract one from + * the max-channel value. + */ + LOGINF("virtpcidev->scsi.max.max_channel=%u, max_id=%u, max_lun=%u, cmd_per_lun=%u, max_io_size=%u\n", + (unsigned) virtpcidev->scsi.max.max_channel - 1, + (unsigned) virtpcidev->scsi.max.max_id, + (unsigned) virtpcidev->scsi.max.max_lun, + (unsigned) virtpcidev->scsi.max.cmd_per_lun, + (unsigned) virtpcidev->scsi.max.max_io_size); + scsihost->max_channel = (unsigned) virtpcidev->scsi.max.max_channel; + scsihost->max_id = (unsigned) virtpcidev->scsi.max.max_id; + scsihost->max_lun = (unsigned) virtpcidev->scsi.max.max_lun; + scsihost->cmd_per_lun = (unsigned) virtpcidev->scsi.max.cmd_per_lun; + scsihost->max_sectors = + (unsigned short) (virtpcidev->scsi.max.max_io_size >> 9); + scsihost->sg_tablesize = + (unsigned short) (virtpcidev->scsi.max.max_io_size / PAGE_SIZE); + if (scsihost->sg_tablesize > MAX_PHYS_INFO) + scsihost->sg_tablesize = MAX_PHYS_INFO; + LOGINF("scsihost->max_channel=%u, max_id=%u, max_lun=%u, cmd_per_lun=%u, max_sectors=%hu, sg_tablesize=%hu\n", + scsihost->max_channel, scsihost->max_id, scsihost->max_lun, + scsihost->cmd_per_lun, scsihost->max_sectors, + scsihost->sg_tablesize); + LOGINF("scsihost->can_queue=%u, scsihost->cmd_per_lun=%u, max_sectors=%hu, sg_tablesize=%hu\n", + scsihost->can_queue, scsihost->cmd_per_lun, scsihost->max_sectors, + scsihost->sg_tablesize); + + DBGINF("calling scsi_add_host\n"); + + /* this creates "host%d" in sysfs. If 2nd argument is NULL, + * then this generic /sys/devices/platform/host? device is + * created and /sys/scsi_host/host? -> + * /sys/devices/platform/host? If 2nd argument is not NULL, + * then this generic /sys/devices/<path>/host? is created and + * host? points to that device instead. + */ + error = scsi_add_host(scsihost, &virtpcidev->generic_dev); + if (error) { + LOGERR("scsi_add_host ****FAILED 0x%x TBD - RECOVER\n", error); + POSTCODE_LINUX_2(VHBA_PROBE_FAILURE_PC, POSTCODE_SEVERITY_ERR); + /* decr refcount on scsihost which was incremented by + * scsi_add_host so the scsi_host gets deleted + */ + scsi_host_put(scsihost); + return -ENODEV; + } + + virthbainfo = (struct virthba_info *) scsihost->hostdata; + memset(virthbainfo, 0, sizeof(struct virthba_info)); + for (i = 0; i < VIRTHBASOPENMAX; i++) { + if (VirtHbasOpen[i].virthbainfo == NULL) { + VirtHbasOpen[i].virthbainfo = virthbainfo; + break; + } + } + virthbainfo->interrupt_vector = -1; + virthbainfo->chinfo.queueinfo = &virtpcidev->queueinfo; + virthbainfo->virtpcidev = virtpcidev; + spin_lock_init(&virthbainfo->chinfo.insertlock); + + DBGINF("generic_dev: 0x%p, queueinfo: 0x%p.\n", + &virtpcidev->generic_dev, &virtpcidev->queueinfo); + + init_waitqueue_head(&virthbainfo->rsp_queue); + spin_lock_init(&virthbainfo->privlock); + memset(&virthbainfo->pending, 0, sizeof(virthbainfo->pending)); + virthbainfo->serverdown = false; + virthbainfo->serverchangingstate = false; + + virthbainfo->intr = virtpcidev->intr; + /* save of host within virthba_info */ + virthbainfo->scsihost = scsihost; + + /* save of host within virtpci_dev */ + virtpcidev->scsi.scsihost = scsihost; + + /* Setup workqueue for serverdown messages */ + INIT_WORK(&virthbainfo->serverdown_completion, + virthba_serverdown_complete); + + writeq(readq(&virthbainfo->chinfo.queueinfo->chan->Features) | + ULTRA_IO_CHANNEL_IS_POLLING, + &virthbainfo->chinfo.queueinfo->chan->Features); + /* start thread that will receive scsicmnd responses */ + DBGINF("starting rsp thread -- queueinfo: 0x%p, threadinfo: 0x%p.\n", + virthbainfo->chinfo.queueinfo, &virthbainfo->chinfo.threadinfo); + + pChannelHeader = virthbainfo->chinfo.queueinfo->chan; + pqhdr = (SIGNAL_QUEUE_HEADER __iomem *) + ((char __iomem *)pChannelHeader + + readq(&pChannelHeader->oChannelSpace)) + IOCHAN_FROM_IOPART; + virthbainfo->flags_addr = &pqhdr->FeatureFlags; + + if (!uisthread_start(&virthbainfo->chinfo.threadinfo, + process_incoming_rsps, + virthbainfo, "vhba_incoming")) { + LOGERR("uisthread_start rsp ****FAILED\n"); + /* decr refcount on scsihost which was incremented by + * scsi_add_host so the scsi_host gets deleted + */ + POSTCODE_LINUX_2(VHBA_PROBE_FAILURE_PC, POSTCODE_SEVERITY_ERR); + scsi_host_put(scsihost); + return -ENODEV; + } + LOGINF("sendInterruptHandle=0x%16llX", + virthbainfo->intr.sendInterruptHandle); + LOGINF("recvInterruptHandle=0x%16llX", + virthbainfo->intr.recvInterruptHandle); + LOGINF("recvInterruptVector=0x%8X", + virthbainfo->intr.recvInterruptVector); + LOGINF("recvInterruptShared=0x%2X", + virthbainfo->intr.recvInterruptShared); + LOGINF("scsihost.hostt->name=%s", scsihost->hostt->name); + virthbainfo->interrupt_vector = + virthbainfo->intr.recvInterruptHandle & INTERRUPT_VECTOR_MASK; + rsp = request_irq(virthbainfo->interrupt_vector, handler, IRQF_SHARED, + scsihost->hostt->name, virthbainfo); + if (rsp != 0) { + LOGERR("request_irq(%d) uislib_virthba_ISR request failed with rsp=%d\n", + virthbainfo->interrupt_vector, rsp); + virthbainfo->interrupt_vector = -1; + POSTCODE_LINUX_2(VHBA_PROBE_FAILURE_PC, POSTCODE_SEVERITY_ERR); + } else { + U64 __iomem *Features_addr = + &virthbainfo->chinfo.queueinfo->chan->Features; + LOGERR("request_irq(%d) uislib_virthba_ISR request succeeded\n", + virthbainfo->interrupt_vector); + mask = ~(ULTRA_IO_CHANNEL_IS_POLLING | + ULTRA_IO_DRIVER_DISABLES_INTS); + uisqueue_InterlockedAnd(Features_addr, mask); + mask = ULTRA_IO_DRIVER_ENABLES_INTS; + uisqueue_InterlockedOr(Features_addr, mask); + rsltq_wait_usecs = 4000000; + } + + DBGINF("calling scsi_scan_host.\n"); + scsi_scan_host(scsihost); + DBGINF("return from scsi_scan_host.\n"); + + LOGINF("virthba added scsihost:0x%p\n", scsihost); + POSTCODE_LINUX_2(VHBA_PROBE_EXIT_PC, POSTCODE_SEVERITY_INFO); + return 0; +} + +static void +virthba_remove(struct virtpci_dev *virtpcidev) +{ + struct virthba_info *virthbainfo; + struct Scsi_Host *scsihost = + (struct Scsi_Host *) virtpcidev->scsi.scsihost; + + LOGINF("virtpcidev busNo<<%d>>devNo<<%d>>", virtpcidev->busNo, + virtpcidev->deviceNo); + virthbainfo = (struct virthba_info *) scsihost->hostdata; + if (virthbainfo->interrupt_vector != -1) + free_irq(virthbainfo->interrupt_vector, virthbainfo); + LOGINF("Removing virtpcidev: 0x%p, virthbainfo: 0x%p\n", virtpcidev, + virthbainfo); + + DBGINF("removing scsihost: 0x%p, scsihost->this_id: %d\n", scsihost, + scsihost->this_id); + scsi_remove_host(scsihost); + + DBGINF("stopping thread.\n"); + uisthread_stop(&virthbainfo->chinfo.threadinfo); + + DBGINF("calling scsi_host_put\n"); + + /* decr refcount on scsihost which was incremented by + * scsi_add_host so the scsi_host gets deleted + */ + scsi_host_put(scsihost); + LOGINF("virthba removed scsi_host.\n"); +} + +static int +forward_vdiskmgmt_command(VDISK_MGMT_TYPES vdiskcmdtype, + struct Scsi_Host *scsihost, + struct uisscsi_dest *vdest) +{ + struct uiscmdrsp *cmdrsp; + struct virthba_info *virthbainfo = + (struct virthba_info *) scsihost->hostdata; + int notifyresult = 0xffff; + wait_queue_head_t notifyevent; + + LOGINF("vDiskMgmt:%d %d:%d:%d\n", vdiskcmdtype, + vdest->channel, vdest->id, vdest->lun); + + if (virthbainfo->serverdown || virthbainfo->serverchangingstate) { + DBGINF("Server is down/changing state. Returning Failure.\n"); + return FAILED; + } + + cmdrsp = kzalloc(SIZEOF_CMDRSP, GFP_ATOMIC); + if (cmdrsp == NULL) { + LOGERR("kmalloc of cmdrsp failed.\n"); + return FAILED; /* reject */ + } + + init_waitqueue_head(¬ifyevent); + + /* issue VDISK_MGMT_CMD + * set type to command - as opposed to task mgmt + */ + cmdrsp->cmdtype = CMD_VDISKMGMT_TYPE; + /* specify the event that has to be triggered when this cmd is + * complete + */ + cmdrsp->vdiskmgmt.notify = (void *) ¬ifyevent; + cmdrsp->vdiskmgmt.notifyresult = (void *) ¬ifyresult; + + /* save destination */ + cmdrsp->vdiskmgmt.vdisktype = vdiskcmdtype; + cmdrsp->vdiskmgmt.vdest.channel = vdest->channel; + cmdrsp->vdiskmgmt.vdest.id = vdest->id; + cmdrsp->vdiskmgmt.vdest.lun = vdest->lun; + cmdrsp->vdiskmgmt.scsicmd = + (void *) (uintptr_t) + add_scsipending_entry_with_wait(virthbainfo, CMD_VDISKMGMT_TYPE, + (void *) cmdrsp); + + uisqueue_put_cmdrsp_with_lock_client(virthbainfo->chinfo.queueinfo, + cmdrsp, IOCHAN_TO_IOPART, + &virthbainfo->chinfo.insertlock, + DONT_ISSUE_INTERRUPT, (U64) NULL, + OK_TO_WAIT, "vhba"); + LOGINF("VdiskMgmt waiting on event notifyevent=0x%p\n", + cmdrsp->scsitaskmgmt.notify); + wait_event(notifyevent, notifyresult != 0xffff); + LOGINF("VdiskMgmt complete; result:%d\n", cmdrsp->vdiskmgmt.result); + kfree(cmdrsp); + return SUCCESS; +} + +/*****************************************************/ +/* Scsi Host support functions */ +/*****************************************************/ + +static int +forward_taskmgmt_command(TASK_MGMT_TYPES tasktype, struct scsi_device *scsidev) +{ + struct uiscmdrsp *cmdrsp; + struct virthba_info *virthbainfo = + (struct virthba_info *) scsidev->host->hostdata; + int notifyresult = 0xffff; + wait_queue_head_t notifyevent; + + LOGINF("TaskMgmt:%d %d:%d:%d\n", tasktype, + scsidev->channel, scsidev->id, scsidev->lun); + + if (virthbainfo->serverdown || virthbainfo->serverchangingstate) { + DBGINF("Server is down/changing state. Returning Failure.\n"); + return FAILED; + } + + cmdrsp = kzalloc(SIZEOF_CMDRSP, GFP_ATOMIC); + if (cmdrsp == NULL) { + LOGERR("kmalloc of cmdrsp failed.\n"); + return FAILED; /* reject */ + } + + init_waitqueue_head(¬ifyevent); + + /* issue TASK_MGMT_ABORT_TASK */ + /* set type to command - as opposed to task mgmt */ + cmdrsp->cmdtype = CMD_SCSITASKMGMT_TYPE; + /* specify the event that has to be triggered when this */ + /* cmd is complete */ + cmdrsp->scsitaskmgmt.notify = (void *) ¬ifyevent; + cmdrsp->scsitaskmgmt.notifyresult = (void *) ¬ifyresult; + + /* save destination */ + cmdrsp->scsitaskmgmt.tasktype = tasktype; + cmdrsp->scsitaskmgmt.vdest.channel = scsidev->channel; + cmdrsp->scsitaskmgmt.vdest.id = scsidev->id; + cmdrsp->scsitaskmgmt.vdest.lun = scsidev->lun; + cmdrsp->scsitaskmgmt.scsicmd = + (void *) (uintptr_t) + add_scsipending_entry_with_wait(virthbainfo, + CMD_SCSITASKMGMT_TYPE, + (void *) cmdrsp); + + uisqueue_put_cmdrsp_with_lock_client(virthbainfo->chinfo.queueinfo, + cmdrsp, IOCHAN_TO_IOPART, + &virthbainfo->chinfo.insertlock, + DONT_ISSUE_INTERRUPT, (U64) NULL, + OK_TO_WAIT, "vhba"); + LOGINF("TaskMgmt waiting on event notifyevent=0x%p\n", + cmdrsp->scsitaskmgmt.notify); + wait_event(notifyevent, notifyresult != 0xffff); + LOGINF("TaskMgmt complete; result:%d\n", cmdrsp->scsitaskmgmt.result); + kfree(cmdrsp); + return SUCCESS; +} + +/* The abort handler returns SUCCESS if it has succeeded to make LLDD + * and all related hardware forget about the scmd. + */ +static int +virthba_abort_handler(struct scsi_cmnd *scsicmd) +{ + /* issue TASK_MGMT_ABORT_TASK */ + struct scsi_device *scsidev; + struct virtdisk_info *vdisk; + + scsidev = scsicmd->device; + for (vdisk = &((struct virthba_info *) scsidev->host->hostdata)->head; + vdisk->next; vdisk = vdisk->next) { + if ((scsidev->channel == vdisk->channel) + && (scsidev->id == vdisk->id) + && (scsidev->lun == vdisk->lun)) { + if (atomic_read(&vdisk->error_count) < + VIRTHBA_ERROR_COUNT) { + atomic_inc(&vdisk->error_count); + POSTCODE_LINUX_2(VHBA_COMMAND_HANDLER_PC, + POSTCODE_SEVERITY_INFO); + } else + atomic_set(&vdisk->ios_threshold, + IOS_ERROR_THRESHOLD); + } + } + return forward_taskmgmt_command(TASK_MGMT_ABORT_TASK, scsicmd->device); +} + +static int +virthba_bus_reset_handler(struct scsi_cmnd *scsicmd) +{ + /* issue TASK_MGMT_TARGET_RESET for each target on the bus */ + struct scsi_device *scsidev; + struct virtdisk_info *vdisk; + + scsidev = scsicmd->device; + for (vdisk = &((struct virthba_info *) scsidev->host->hostdata)->head; + vdisk->next; vdisk = vdisk->next) { + if ((scsidev->channel == vdisk->channel) + && (scsidev->id == vdisk->id) + && (scsidev->lun == vdisk->lun)) { + if (atomic_read(&vdisk->error_count) < + VIRTHBA_ERROR_COUNT) { + atomic_inc(&vdisk->error_count); + POSTCODE_LINUX_2(VHBA_COMMAND_HANDLER_PC, + POSTCODE_SEVERITY_INFO); + } else + atomic_set(&vdisk->ios_threshold, + IOS_ERROR_THRESHOLD); + } + } + return forward_taskmgmt_command(TASK_MGMT_BUS_RESET, scsicmd->device); +} + +static int +virthba_device_reset_handler(struct scsi_cmnd *scsicmd) +{ + /* issue TASK_MGMT_LUN_RESET */ + struct scsi_device *scsidev; + struct virtdisk_info *vdisk; + + scsidev = scsicmd->device; + for (vdisk = &((struct virthba_info *) scsidev->host->hostdata)->head; + vdisk->next; vdisk = vdisk->next) { + if ((scsidev->channel == vdisk->channel) + && (scsidev->id == vdisk->id) + && (scsidev->lun == vdisk->lun)) { + if (atomic_read(&vdisk->error_count) < + VIRTHBA_ERROR_COUNT) { + atomic_inc(&vdisk->error_count); + POSTCODE_LINUX_2(VHBA_COMMAND_HANDLER_PC, + POSTCODE_SEVERITY_INFO); + } else + atomic_set(&vdisk->ios_threshold, + IOS_ERROR_THRESHOLD); + } + } + return forward_taskmgmt_command(TASK_MGMT_LUN_RESET, scsicmd->device); +} + +static int +virthba_host_reset_handler(struct scsi_cmnd *scsicmd) +{ + /* issue TASK_MGMT_TARGET_RESET for each target on each bus for host */ + LOGERR("virthba_host_reset_handler Not yet implemented\n"); + return SUCCESS; +} + +static char virthba_get_info_str[256]; + +static const char * +virthba_get_info(struct Scsi_Host *shp) +{ + /* Return version string */ + sprintf(virthba_get_info_str, "virthba, version %s\n", VIRTHBA_VERSION); + return virthba_get_info_str; +} + +static int +virthba_ioctl(struct scsi_device *dev, int cmd, void __user *arg) +{ + DBGINF("In virthba_ioctl: ioctl: cmd=0x%x\n", cmd); + return -EINVAL; +} + +/* This returns SCSI_MLQUEUE_DEVICE_BUSY if the signal queue to IOpart + * is full. + */ +static int +virthba_queue_command_lck(struct scsi_cmnd *scsicmd, + void (*virthba_cmnd_done)(struct scsi_cmnd *)) +{ + struct scsi_device *scsidev = scsicmd->device; + int insert_location; + unsigned char op; + unsigned char *cdb = scsicmd->cmnd; + struct Scsi_Host *scsihost = scsidev->host; + struct uiscmdrsp *cmdrsp; + unsigned int i; + struct virthba_info *virthbainfo = + (struct virthba_info *) scsihost->hostdata; + struct scatterlist *sg = NULL; + struct scatterlist *sgl = NULL; + int sg_failed = 0; + + if (virthbainfo->serverdown || virthbainfo->serverchangingstate) { + DBGINF("Server is down/changing state. Returning SCSI_MLQUEUE_DEVICE_BUSY.\n"); + return SCSI_MLQUEUE_DEVICE_BUSY; + } + + cmdrsp = kzalloc(SIZEOF_CMDRSP, GFP_ATOMIC); + if (cmdrsp == NULL) { + LOGERR("kmalloc of cmdrsp failed.\n"); + return 1; /* reject the command */ + } + + /* now saving everything we need from scsi_cmd into cmdrsp + * before we queue cmdrsp set type to command - as opposed to + * task mgmt + */ + cmdrsp->cmdtype = CMD_SCSI_TYPE; + /* save the pending insertion location. Deletion from pending + * will return the scsicmd pointer for completion + */ + insert_location = + add_scsipending_entry(virthbainfo, CMD_SCSI_TYPE, (void *) scsicmd); + if (insert_location != -1) { + cmdrsp->scsi.scsicmd = (void *) (uintptr_t) insert_location; + } else { + LOGERR("Queue is full. Returning busy.\n"); + kfree(cmdrsp); + return SCSI_MLQUEUE_DEVICE_BUSY; + } + /* save done function that we have call when cmd is complete */ + scsicmd->scsi_done = virthba_cmnd_done; + /* save destination */ + cmdrsp->scsi.vdest.channel = scsidev->channel; + cmdrsp->scsi.vdest.id = scsidev->id; + cmdrsp->scsi.vdest.lun = scsidev->lun; + /* save datadir */ + cmdrsp->scsi.data_dir = scsicmd->sc_data_direction; + memcpy(cmdrsp->scsi.cmnd, cdb, MAX_CMND_SIZE); + + cmdrsp->scsi.bufflen = scsi_bufflen(scsicmd); + + /* keep track of the max buffer length so far. */ + if (cmdrsp->scsi.bufflen > MaxBuffLen) + MaxBuffLen = cmdrsp->scsi.bufflen; + + if (scsi_sg_count(scsicmd) > MAX_PHYS_INFO) { + LOGERR("scsicmd use_sg:%d greater than MAX:%d\n", + scsi_sg_count(scsicmd), MAX_PHYS_INFO); + del_scsipending_entry(virthbainfo, (uintptr_t) insert_location); + kfree(cmdrsp); + return 1; /* reject the command */ + } + + /* This is what we USED to do when we assumed we were running */ + /* uissd & virthba on the same Linux system. */ + /* cmdrsp->scsi.buffer = scsicmd->request_buffer; */ + /* The following code does NOT make that assumption. */ + /* convert buffer to phys information */ + if (scsi_sg_count(scsicmd) == 0) { + if (scsi_bufflen(scsicmd) > 0) { + LOGERR("**** FAILED No scatter list for bufflen > 0\n"); + BUG_ON(scsi_sg_count(scsicmd) == 0); + } + DBGINF("No sg; buffer:0x%p bufflen:%d\n", + scsi_sglist(scsicmd), scsi_bufflen(scsicmd)); + } else { + /* buffer is scatterlist - copy it out */ + sgl = scsi_sglist(scsicmd); + + for_each_sg(sgl, sg, scsi_sg_count(scsicmd), i) { + + cmdrsp->scsi.gpi_list[i].address = sg_phys(sg); + cmdrsp->scsi.gpi_list[i].length = sg->length; + if ((i != 0) && (sg->offset != 0)) + LOGINF("Offset on a sg_entry other than zero =<<%d>>.\n", + sg->offset); + } + + if (sg_failed) { + LOGERR("Start sg_list dump (entries %d, bufflen %d)...\n", + scsi_sg_count(scsicmd), cmdrsp->scsi.bufflen); + for_each_sg(sgl, sg, scsi_sg_count(scsicmd), i) { + LOGERR(" Entry(%d): page->[0x%p], phys->[0x%Lx], off(%d), len(%d)\n", + i, sg_page(sg), + (unsigned long long) sg_phys(sg), + sg->offset, sg->length); + } + LOGERR("Done sg_list dump.\n"); + /* BUG(); ***** For now, let it fail in uissd + * if it is a problem, as it might just + * work + */ + } + + cmdrsp->scsi.guest_phys_entries = scsi_sg_count(scsicmd); + } + + op = cdb[0]; + i = uisqueue_put_cmdrsp_with_lock_client(virthbainfo->chinfo.queueinfo, + cmdrsp, IOCHAN_TO_IOPART, + &virthbainfo->chinfo. + insertlock, + DONT_ISSUE_INTERRUPT, + (U64) NULL, DONT_WAIT, "vhba"); + if (i == 0) { + /* queue must be full - and we said don't wait - return busy */ + LOGERR("uisqueue_put_cmdrsp_with_lock ****FAILED\n"); + kfree(cmdrsp); + del_scsipending_entry(virthbainfo, (uintptr_t) insert_location); + return SCSI_MLQUEUE_DEVICE_BUSY; + } + + /* we're done with cmdrsp space - data from it has been copied + * into channel - free it now. + */ + kfree(cmdrsp); + return 0; /* non-zero implies host/device is busy */ +} + +static int +virthba_slave_alloc(struct scsi_device *scsidev) +{ + /* this called by the midlayer before scan for new devices - + * LLD can alloc any struct & do init if needed. + */ + struct virtdisk_info *vdisk; + struct virtdisk_info *tmpvdisk; + struct virthba_info *virthbainfo; + struct Scsi_Host *scsihost = (struct Scsi_Host *) scsidev->host; + + virthbainfo = (struct virthba_info *) scsihost->hostdata; + if (!virthbainfo) { + LOGERR("Could not find virthba_info for scsihost\n"); + return 0; /* even though we errored, treat as success */ + } + for (vdisk = &virthbainfo->head; vdisk->next; vdisk = vdisk->next) { + if (vdisk->next->valid && + (vdisk->next->channel == scsidev->channel) && + (vdisk->next->id == scsidev->id) && + (vdisk->next->lun == scsidev->lun)) + return 0; + } + tmpvdisk = kzalloc(sizeof(struct virtdisk_info), GFP_ATOMIC); + if (!tmpvdisk) { /* error allocating */ + LOGERR("Could not allocate memory for disk\n"); + return 0; + } + + tmpvdisk->channel = scsidev->channel; + tmpvdisk->id = scsidev->id; + tmpvdisk->lun = scsidev->lun; + tmpvdisk->valid = 1; + vdisk->next = tmpvdisk; + return 0; /* success */ +} + +static int +virthba_slave_configure(struct scsi_device *scsidev) +{ + return 0; /* success */ +} + +static void +virthba_slave_destroy(struct scsi_device *scsidev) +{ + /* midlevel calls this after device has been quiesced and + * before it is to be deleted. + */ + struct virtdisk_info *vdisk, *delvdisk; + struct virthba_info *virthbainfo; + struct Scsi_Host *scsihost = (struct Scsi_Host *) scsidev->host; + + virthbainfo = (struct virthba_info *) scsihost->hostdata; + if (!virthbainfo) + LOGERR("Could not find virthba_info for scsihost\n"); + for (vdisk = &virthbainfo->head; vdisk->next; vdisk = vdisk->next) { + if (vdisk->next->valid && + (vdisk->next->channel == scsidev->channel) && + (vdisk->next->id == scsidev->id) && + (vdisk->next->lun == scsidev->lun)) { + delvdisk = vdisk->next; + vdisk->next = vdisk->next->next; + kfree(delvdisk); + return; + } + } + return; +} + +/*****************************************************/ +/* Scsi Cmnd support thread */ +/*****************************************************/ + +static void +do_scsi_linuxstat(struct uiscmdrsp *cmdrsp, struct scsi_cmnd *scsicmd) +{ + struct virtdisk_info *vdisk; + struct scsi_device *scsidev; + struct sense_data *sd; + + scsidev = scsicmd->device; + memcpy(scsicmd->sense_buffer, cmdrsp->scsi.sensebuf, MAX_SENSE_SIZE); + sd = (struct sense_data *) scsicmd->sense_buffer; + + /* Do not log errors for disk-not-present inquiries */ + if ((cmdrsp->scsi.cmnd[0] == INQUIRY) && + (host_byte(cmdrsp->scsi.linuxstat) == DID_NO_CONNECT) && + (cmdrsp->scsi.addlstat == ADDL_SEL_TIMEOUT)) + return; + + /* Okay see what our error_count is here.... */ + for (vdisk = &((struct virthba_info *) scsidev->host->hostdata)->head; + vdisk->next; vdisk = vdisk->next) { + if ((scsidev->channel != vdisk->channel) + || (scsidev->id != vdisk->id) + || (scsidev->lun != vdisk->lun)) + continue; + + if (atomic_read(&vdisk->error_count) < VIRTHBA_ERROR_COUNT) { + atomic_inc(&vdisk->error_count); + LOGERR("SCSICMD ****FAILED scsicmd:0x%p op:0x%x <%d:%d:%d:%d> 0x%x-0x%x-0x%x-0x%x-0x%x.\n", + scsicmd, cmdrsp->scsi.cmnd[0], + scsidev->host->host_no, scsidev->id, + scsidev->channel, scsidev->lun, + cmdrsp->scsi.linuxstat, sd->Valid, sd->SenseKey, + sd->AdditionalSenseCode, + sd->AdditionalSenseCodeQualifier); + if (atomic_read(&vdisk->error_count) == + VIRTHBA_ERROR_COUNT) { + LOGERR("Throtling SCSICMD errors disk <%d:%d:%d:%d>\n", + scsidev->host->host_no, scsidev->id, + scsidev->channel, scsidev->lun); + } + atomic_set(&vdisk->ios_threshold, IOS_ERROR_THRESHOLD); + } + } +} + +static void +do_scsi_nolinuxstat(struct uiscmdrsp *cmdrsp, struct scsi_cmnd *scsicmd) +{ + struct scsi_device *scsidev; + unsigned char buf[36]; + struct scatterlist *sg; + unsigned int i; + char *thispage; + char *thispage_orig; + int bufind = 0; + struct virtdisk_info *vdisk; + + scsidev = scsicmd->device; + if ((cmdrsp->scsi.cmnd[0] == INQUIRY) + && (cmdrsp->scsi.bufflen >= MIN_INQUIRY_RESULT_LEN)) { + if (cmdrsp->scsi.no_disk_result == 0) + return; + + /* Linux scsi code is weird; it wants + * a device at Lun 0 to issue report + * luns, but we don't want a disk + * there so we'll present a processor + * there. */ + SET_NO_DISK_INQUIRY_RESULT(buf, cmdrsp->scsi.bufflen, + scsidev->lun, + DEV_DISK_CAPABLE_NOT_PRESENT, + DEV_NOT_CAPABLE); + + if (scsi_sg_count(scsicmd) == 0) { + if (scsi_bufflen(scsicmd) > 0) { + LOGERR("**** FAILED No scatter list for bufflen > 0\n"); + BUG_ON(scsi_sg_count(scsicmd) == + 0); + } + memcpy(scsi_sglist(scsicmd), buf, + cmdrsp->scsi.bufflen); + return; + } + + sg = scsi_sglist(scsicmd); + for (i = 0; i < scsi_sg_count(scsicmd); i++) { + DBGVER("copying OUT OF buf into 0x%p %d\n", + sg_page(sg + i), sg[i].length); + thispage_orig = kmap_atomic(sg_page(sg + i)); + thispage = (void *) ((unsigned long)thispage_orig | + sg[i].offset); + memcpy(thispage, buf + bufind, sg[i].length); + kunmap_atomic(thispage_orig); + bufind += sg[i].length; + } + } else { + + vdisk = &((struct virthba_info *)scsidev->host->hostdata)->head; + for ( ; vdisk->next; vdisk = vdisk->next) { + if ((scsidev->channel != vdisk->channel) + || (scsidev->id != vdisk->id) + || (scsidev->lun != vdisk->lun)) + continue; + + if (atomic_read(&vdisk->ios_threshold) > 0) { + atomic_dec(&vdisk->ios_threshold); + if (atomic_read(&vdisk->ios_threshold) == 0) { + LOGERR("Resetting error count for disk\n"); + atomic_set(&vdisk->error_count, 0); + } + } + } + } +} + +static void +complete_scsi_command(struct uiscmdrsp *cmdrsp, struct scsi_cmnd *scsicmd) +{ + DBGINF("cmdrsp: 0x%p, scsistat:0x%x.\n", cmdrsp, cmdrsp->scsi.scsistat); + + /* take what we need out of cmdrsp and complete the scsicmd */ + scsicmd->result = cmdrsp->scsi.linuxstat; + if (cmdrsp->scsi.linuxstat) + do_scsi_linuxstat(cmdrsp, scsicmd); + else + do_scsi_nolinuxstat(cmdrsp, scsicmd); + + if (scsicmd->scsi_done) { + DBGVER("Scsi_DONE\n"); + scsicmd->scsi_done(scsicmd); + } +} + +static inline void +complete_vdiskmgmt_command(struct uiscmdrsp *cmdrsp) +{ + /* copy the result of the taskmgmt and */ + /* wake up the error handler that is waiting for this */ + *(int *) cmdrsp->vdiskmgmt.notifyresult = cmdrsp->vdiskmgmt.result; + wake_up_all((wait_queue_head_t *) cmdrsp->vdiskmgmt.notify); + LOGINF("set notify result to %d\n", cmdrsp->vdiskmgmt.result); +} + +static inline void +complete_taskmgmt_command(struct uiscmdrsp *cmdrsp) +{ + /* copy the result of the taskmgmt and */ + /* wake up the error handler that is waiting for this */ + *(int *) cmdrsp->scsitaskmgmt.notifyresult = + cmdrsp->scsitaskmgmt.result; + wake_up_all((wait_queue_head_t *) cmdrsp->scsitaskmgmt.notify); + LOGINF("set notify result to %d\n", cmdrsp->scsitaskmgmt.result); +} + +static void +drain_queue(struct virthba_info *virthbainfo, struct chaninfo *dc, + struct uiscmdrsp *cmdrsp) +{ + unsigned long flags; + int qrslt = 0; + struct scsi_cmnd *scsicmd; + struct Scsi_Host *shost = virthbainfo->scsihost; + + while (1) { + spin_lock_irqsave(&virthbainfo->chinfo.insertlock, flags); + if (!ULTRA_CHANNEL_CLIENT_ACQUIRE_OS(dc->queueinfo->chan, + "vhba", NULL)) { + spin_unlock_irqrestore(&virthbainfo->chinfo.insertlock, + flags); + virthbainfo->acquire_failed_cnt++; + break; + } + qrslt = uisqueue_get_cmdrsp(dc->queueinfo, cmdrsp, + IOCHAN_FROM_IOPART); + ULTRA_CHANNEL_CLIENT_RELEASE_OS(dc->queueinfo->chan, + "vhba", NULL); + spin_unlock_irqrestore(&virthbainfo->chinfo.insertlock, flags); + if (qrslt == 0) + break; + if (cmdrsp->cmdtype == CMD_SCSI_TYPE) { + /* scsicmd location is returned by the + * deletion + */ + scsicmd = del_scsipending_entry(virthbainfo, + (uintptr_t) cmdrsp->scsi.scsicmd); + if (!scsicmd) + break; + /* complete the orig cmd */ + complete_scsi_command(cmdrsp, scsicmd); + } else if (cmdrsp->cmdtype == CMD_SCSITASKMGMT_TYPE) { + if (!del_scsipending_entry(virthbainfo, + (uintptr_t) cmdrsp->scsitaskmgmt.scsicmd)) + break; + complete_taskmgmt_command(cmdrsp); + } else if (cmdrsp->cmdtype == CMD_NOTIFYGUEST_TYPE) { + /* The vHba pointer has no meaning in + * a Client/Guest Partition. Let's be + * safe and set it to NULL now. Do + * not use it here! */ + cmdrsp->disknotify.vHba = NULL; + process_disk_notify(shost, cmdrsp); + } else if (cmdrsp->cmdtype == CMD_VDISKMGMT_TYPE) { + if (!del_scsipending_entry(virthbainfo, + (uintptr_t) cmdrsp->vdiskmgmt.scsicmd)) + break; + complete_vdiskmgmt_command(cmdrsp); + } else + LOGERR("Invalid cmdtype %d\n", cmdrsp->cmdtype); + /* cmdrsp is now available for reuse */ + } +} + + +/* main function for the thread that waits for scsi commands to arrive + * in a specified queue + */ +static int +process_incoming_rsps(void *v) +{ + struct virthba_info *virthbainfo = v; + struct chaninfo *dc = &virthbainfo->chinfo; + struct uiscmdrsp *cmdrsp = NULL; + const int SZ = sizeof(struct uiscmdrsp); + U64 mask; + unsigned long long rc1; + + UIS_DAEMONIZE("vhba_incoming"); + /* alloc once and reuse */ + cmdrsp = kmalloc(SZ, GFP_ATOMIC); + if (cmdrsp == NULL) { + LOGERR("process_incoming_rsps ****FAILED to malloc - thread exiting\n"); + complete_and_exit(&dc->threadinfo.has_stopped, 0); + return 0; + } + mask = ULTRA_CHANNEL_ENABLE_INTS; + while (1) { + wait_event_interruptible_timeout(virthbainfo->rsp_queue, + (atomic_read(&virthbainfo->interrupt_rcvd) == 1), + usecs_to_jiffies(rsltq_wait_usecs)); + atomic_set(&virthbainfo->interrupt_rcvd, 0); + /* drain queue */ + drain_queue(virthbainfo, dc, cmdrsp); + rc1 = uisqueue_InterlockedOr(virthbainfo->flags_addr, mask); + if (dc->threadinfo.should_stop) + break; + } + + kfree(cmdrsp); + + DBGINF("exiting processing incoming rsps.\n"); + complete_and_exit(&dc->threadinfo.has_stopped, 0); +} + +/*****************************************************/ +/* proc filesystem functions */ +/*****************************************************/ + +static ssize_t +info_proc_read(struct file *file, char __user *buf, size_t len, loff_t *offset) +{ + int length = 0; + U64 phys_flags_addr; + int i; + struct virthba_info *virthbainfo; + 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; + + for (i = 0; i < VIRTHBASOPENMAX; i++) { + if (VirtHbasOpen[i].virthbainfo == NULL) + continue; + + virthbainfo = VirtHbasOpen[i].virthbainfo; + length += sprintf(vbuf + length, "CHANSOCK is not defined.\n"); + + length += sprintf(vbuf + length, "MaxBuffLen:%u\n", MaxBuffLen); + + length += sprintf(vbuf + length, "\nvirthba result queue poll wait:%d usecs.\n", + rsltq_wait_usecs); + + length += sprintf(vbuf + length, + "\nModule build: Date:%s Time:%s\n", + __DATE__, __TIME__); + length += sprintf(vbuf + length, "\ninterrupts_rcvd = %llu, interrupts_disabled = %llu\n", + virthbainfo->interrupts_rcvd, + virthbainfo->interrupts_disabled); + length += sprintf(vbuf + length, "\ninterrupts_notme = %llu,\n", + virthbainfo->interrupts_notme); + phys_flags_addr = virt_to_phys((__force void *) + virthbainfo->flags_addr); + length += sprintf(vbuf + length, "flags_addr = %p, phys_flags_addr=0x%016llx, FeatureFlags=%llu\n", + virthbainfo->flags_addr, phys_flags_addr, + (__le64)readq(virthbainfo->flags_addr)); + length += sprintf(vbuf + length, "acquire_failed_cnt:%llu\n", + virthbainfo->acquire_failed_cnt); + 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 +enable_ints_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + return 0; +} + +static ssize_t +enable_ints_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[4]; + int i, new_value; + struct virthba_info *virthbainfo; + U64 __iomem *Features_addr; + U64 mask; + + if (count >= ARRAY_SIZE(buf)) + return -EINVAL; + + buf[count] = '\0'; + if (copy_from_user(buf, buffer, count)) { + LOGERR("copy_from_user failed. buf<<%.*s>> count<<%lu>>\n", + (int) count, buf, count); + return -EFAULT; + } + + i = sscanf(buf, "%d", &new_value); + + if (i < 1) { + LOGERR("Failed to scan value for enable_ints, buf<<%.*s>>", + (int) count, buf); + return -EFAULT; + } + + /* set all counts to new_value usually 0 */ + for (i = 0; i < VIRTHBASOPENMAX; i++) { + if (VirtHbasOpen[i].virthbainfo != NULL) { + virthbainfo = VirtHbasOpen[i].virthbainfo; + Features_addr = + &virthbainfo->chinfo.queueinfo->chan->Features; + if (new_value == 1) { + mask = ~(ULTRA_IO_CHANNEL_IS_POLLING | + ULTRA_IO_DRIVER_DISABLES_INTS); + uisqueue_InterlockedAnd(Features_addr, mask); + mask = ULTRA_IO_DRIVER_ENABLES_INTS; + uisqueue_InterlockedOr(Features_addr, mask); + rsltq_wait_usecs = 4000000; + } else { + mask = ~(ULTRA_IO_DRIVER_ENABLES_INTS | + ULTRA_IO_DRIVER_DISABLES_INTS); + uisqueue_InterlockedAnd(Features_addr, mask); + mask = ULTRA_IO_CHANNEL_IS_POLLING; + uisqueue_InterlockedOr(Features_addr, mask); + rsltq_wait_usecs = 4000; + } + } + } + return count; +} + +static ssize_t +rqwu_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[16]; + int i, usecs; + + if (count >= ARRAY_SIZE(buf)) + return -EINVAL; + + if (copy_from_user(buf, buffer, count)) { + LOGERR("copy_from_user failed. buf<<%.*s>> count<<%lu>>\n", + (int) count, buf, count); + return -EFAULT; + } + + i = sscanf(buf, "%d", &usecs); + + if (i < 1) { + LOGERR("Failed to scan value for rqwait_usecs buf<<%.*s>>", + (int) count, buf); + return -EFAULT; + } + + /* set global wait time */ + rsltq_wait_usecs = usecs; + return count; +} + +/* As per VirtpciFunc returns 1 for success and 0 for failure */ +static int +virthba_serverup(struct virtpci_dev *virtpcidev) +{ + struct virthba_info *virthbainfo = + (struct virthba_info *) ((struct Scsi_Host *) virtpcidev->scsi. + scsihost)->hostdata; + + DBGINF("virtpcidev busNo<<%d>>devNo<<%d>>", virtpcidev->busNo, + virtpcidev->deviceNo); + + if (!virthbainfo->serverdown) { + DBGINF("Server up message received while server is already up.\n"); + return 1; + } + if (virthbainfo->serverchangingstate) { + LOGERR("Server already processing change state message\n"); + return 0; + } + + virthbainfo->serverchangingstate = true; + /* Must transition channel to ATTACHED state BEFORE we + * can start using the device again + */ + ULTRA_CHANNEL_CLIENT_TRANSITION(virthbainfo->chinfo.queueinfo->chan, + dev_name(&virtpcidev->generic_dev), + CHANNELCLI_ATTACHED, NULL); + + /* Start Processing the IOVM Response Queue Again */ + if (!uisthread_start(&virthbainfo->chinfo.threadinfo, + process_incoming_rsps, + virthbainfo, "vhba_incoming")) { + LOGERR("uisthread_start rsp ****FAILED\n"); + return 0; + } + virthbainfo->serverdown = false; + virthbainfo->serverchangingstate = false; + + return 1; +} + +static void +virthba_serverdown_complete(struct work_struct *work) +{ + struct virthba_info *virthbainfo; + struct virtpci_dev *virtpcidev; + int i; + struct scsipending *pendingdel = NULL; + struct scsi_cmnd *scsicmd = NULL; + struct uiscmdrsp *cmdrsp; + unsigned long flags; + + virthbainfo = container_of(work, struct virthba_info, + serverdown_completion); + + /* Stop Using the IOVM Response Queue (queue should be drained + * by the end) + */ + uisthread_stop(&virthbainfo->chinfo.threadinfo); + + /* Fail Commands that weren't completed */ + spin_lock_irqsave(&virthbainfo->privlock, flags); + for (i = 0; i < MAX_PENDING_REQUESTS; i++) { + pendingdel = &(virthbainfo->pending[i]); + switch (pendingdel->cmdtype) { + case CMD_SCSI_TYPE: + scsicmd = (struct scsi_cmnd *) pendingdel->sent; + scsicmd->result = (DID_RESET << 16); + if (scsicmd->scsi_done) + scsicmd->scsi_done(scsicmd); + break; + case CMD_SCSITASKMGMT_TYPE: + cmdrsp = (struct uiscmdrsp *) pendingdel->sent; + DBGINF("cmdrsp=0x%x, notify=0x%x\n", cmdrsp, + cmdrsp->scsitaskmgmt.notify); + *(int *) cmdrsp->scsitaskmgmt.notifyresult = + TASK_MGMT_FAILED; + wake_up_all((wait_queue_head_t *) + cmdrsp->scsitaskmgmt.notify); + break; + case CMD_VDISKMGMT_TYPE: + cmdrsp = (struct uiscmdrsp *) pendingdel->sent; + *(int *) cmdrsp->vdiskmgmt.notifyresult = + VDISK_MGMT_FAILED; + wake_up_all((wait_queue_head_t *) + cmdrsp->vdiskmgmt.notify); + break; + default: + if (pendingdel->sent != NULL) + LOGERR("Unknown command type: 0x%x. Only freeing list structure.\n", + pendingdel->cmdtype); + } + pendingdel->cmdtype = 0; + pendingdel->sent = NULL; + } + spin_unlock_irqrestore(&virthbainfo->privlock, flags); + + virtpcidev = virthbainfo->virtpcidev; + + DBGINF("virtpcidev busNo<<%d>>devNo<<%d>>", virtpcidev->busNo, + virtpcidev->deviceNo); + virthbainfo->serverdown = true; + virthbainfo->serverchangingstate = false; + /* Return the ServerDown response to Command */ + visorchipset_device_pause_response(virtpcidev->busNo, + virtpcidev->deviceNo, 0); +} + +/* As per VirtpciFunc returns 1 for success and 0 for failure */ +static int +virthba_serverdown(struct virtpci_dev *virtpcidev, u32 state) +{ + struct virthba_info *virthbainfo = + (struct virthba_info *) ((struct Scsi_Host *) virtpcidev->scsi. + scsihost)->hostdata; + + DBGINF("virthba_serverdown"); + DBGINF("virtpcidev busNo<<%d>>devNo<<%d>>", virtpcidev->busNo, + virtpcidev->deviceNo); + + if (!virthbainfo->serverdown && !virthbainfo->serverchangingstate) { + virthbainfo->serverchangingstate = true; + queue_work(virthba_serverdown_workqueue, + &virthbainfo->serverdown_completion); + } else if (virthbainfo->serverchangingstate) { + LOGERR("Server already processing change state message\n"); + return 0; + } else + LOGERR("Server already down, but another server down message received."); + + return 1; +} + +/*****************************************************/ +/* Module Init & Exit functions */ +/*****************************************************/ + +static int __init +virthba_parse_line(char *str) +{ + DBGINF("In virthba_parse_line %s\n", str); + return 1; +} + +static void __init +virthba_parse_options(char *line) +{ + char *next = line; + + POSTCODE_LINUX_2(VHBA_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO); + if (line == NULL || !*line) + return; + while ((line = next) != NULL) { + next = strchr(line, ' '); + if (next != NULL) + *next++ = 0; + if (!virthba_parse_line(line)) + DBGINF("Unknown option '%s'\n", line); + } + + POSTCODE_LINUX_2(VHBA_CREATE_EXIT_PC, POSTCODE_SEVERITY_INFO); +} + +static int __init +virthba_mod_init(void) +{ + int error; + int i; + + if (!unisys_spar_platform) + return -ENODEV; + + LOGINF("Entering virthba_mod_init...\n"); + + POSTCODE_LINUX_2(VHBA_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO); + virthba_parse_options(virthba_options); + + error = virtpci_register_driver(&virthba_driver); + if (error < 0) { + LOGERR("register ****FAILED 0x%x\n", error); + POSTCODE_LINUX_3(VHBA_CREATE_FAILURE_PC, error, + POSTCODE_SEVERITY_ERR); + } else { + /* create the proc directories */ + virthba_proc_dir = proc_mkdir(DIR_PROC_ENTRY, NULL); + info_proc_entry = proc_create(INFO_PROC_ENTRY_FN, 0, + virthba_proc_dir, + &proc_info_fops); + rqwaitus_proc_entry = proc_create(RQWU_PROC_ENTRY_FN, 0, + virthba_proc_dir, + &proc_rqwu_fops); + enable_ints_proc_entry = proc_create(ENABLE_INTS_ENTRY_FN, 0, + virthba_proc_dir, + &proc_enable_ints_fops); + + /* Initialize DARWorkQ */ + INIT_WORK(&DARWorkQ, doDiskAddRemove); + spin_lock_init(&DARWorkQLock); + + /* clear out array */ + for (i = 0; i < VIRTHBASOPENMAX; i++) + VirtHbasOpen[i].virthbainfo = NULL; + /* Initialize the serverdown workqueue */ + virthba_serverdown_workqueue = + create_singlethread_workqueue("virthba_serverdown"); + if (virthba_serverdown_workqueue == NULL) { + LOGERR("**** FAILED virthba_serverdown_workqueue creation\n"); + POSTCODE_LINUX_2(VHBA_CREATE_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + error = -1; + } + } + + POSTCODE_LINUX_2(VHBA_CREATE_EXIT_PC, POSTCODE_SEVERITY_INFO); + LOGINF("Leaving virthba_mod_init\n"); + return error; +} + +static ssize_t +virthba_acquire_lun(struct device *cdev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uisscsi_dest vdest; + struct Scsi_Host *shost = class_to_shost(cdev); + int i; + + i = sscanf(buf, "%d-%d-%d", &vdest.channel, &vdest.id, &vdest.lun); + if (i != 3) + return i; + + return forward_vdiskmgmt_command(VDISK_MGMT_ACQUIRE, shost, &vdest); +} + +static ssize_t +virthba_release_lun(struct device *cdev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uisscsi_dest vdest; + struct Scsi_Host *shost = class_to_shost(cdev); + int i; + + i = sscanf(buf, "%d-%d-%d", &vdest.channel, &vdest.id, &vdest.lun); + if (i != 3) + return i; + + return forward_vdiskmgmt_command(VDISK_MGMT_RELEASE, shost, &vdest); +} + +#define CLASS_DEVICE_ATTR(_name, _mode, _show, _store) \ + struct device_attribute class_device_attr_##_name = \ + __ATTR(_name, _mode, _show, _store) + +static CLASS_DEVICE_ATTR(acquire_lun, S_IWUSR, NULL, virthba_acquire_lun); +static CLASS_DEVICE_ATTR(release_lun, S_IWUSR, NULL, virthba_release_lun); + +static DEVICE_ATTRIBUTE *virthba_shost_attrs[] = { + &class_device_attr_acquire_lun, + &class_device_attr_release_lun, + NULL +}; + +static void __exit +virthba_mod_exit(void) +{ + LOGINF("entering virthba_mod_exit...\n"); + + virtpci_unregister_driver(&virthba_driver); + /* unregister is going to call virthba_remove */ + /* destroy serverdown completion workqueue */ + if (virthba_serverdown_workqueue) { + destroy_workqueue(virthba_serverdown_workqueue); + virthba_serverdown_workqueue = NULL; + } + + if (info_proc_entry) + remove_proc_entry(INFO_PROC_ENTRY_FN, virthba_proc_dir); + + if (rqwaitus_proc_entry) + remove_proc_entry(RQWU_PROC_ENTRY_FN, NULL); + + if (enable_ints_proc_entry) + remove_proc_entry(ENABLE_INTS_ENTRY_FN, NULL); + + if (virthba_proc_dir) + remove_proc_entry(DIR_PROC_ENTRY, NULL); + + LOGINF("Leaving virthba_mod_exit\n"); + +} + +/* specify function to be run at module insertion time */ +module_init(virthba_mod_init); + +/* specify function to be run when module is removed */ +module_exit(virthba_mod_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Usha Srinivasan"); +MODULE_ALIAS("uisvirthba"); + /* this is extracted during depmod and kept in modules.dep */ +/* module parameter */ +module_param(virthba_options, charp, S_IRUGO); diff --git a/drivers/staging/unisys/virthba/virthba.h b/drivers/staging/unisys/virthba/virthba.h new file mode 100644 index 00000000000..d4b809b0c7b --- /dev/null +++ b/drivers/staging/unisys/virthba/virthba.h @@ -0,0 +1,31 @@ +/* virthba.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 HBA driver header + */ + + + +#ifndef __VIRTHBA_H__ +#define __VIRTHBA_H__ + + +#define VIRTHBA_VERSION "01.00" + + +#endif /* __VIRTHBA_H__ */ diff --git a/drivers/staging/unisys/virtpci/Kconfig b/drivers/staging/unisys/virtpci/Kconfig new file mode 100644 index 00000000000..e59efcbc4d3 --- /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 00000000000..f9399aabddd --- /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 00000000000..71246feb154 --- /dev/null +++ b/drivers/staging/unisys/virtpci/virtpci.c @@ -0,0 +1,1770 @@ +/* virtpci.c + * + * Copyright (C) 2010 - 2013 UNISYS CORPORATION + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + */ + +#define EXPORT_SYMTAB + +#include <linux/kernel.h> +#ifdef CONFIG_MODVERSIONS +#include <config/modversions.h> +#endif +#include "uniklog.h" +#include "diagnostics/appos_subsystems.h" +#include "uisutils.h" +#include "commontypes.h" +#include "vbuschannel.h" +#include "vbushelper.h" +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/device.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/mod_devicetable.h> +#include <linux/proc_fs.h> +#include <linux/if_ether.h> +#include <linux/version.h> +#include "version.h" +#include "guestlinuxdebug.h" +#include "timskmodutils.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 __iomem *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 <info> 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 <info> 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 <info> to the + * ULTRA_VBUS_CHANNEL_PROTOCOL.DevInfo[<devix>]. + */ +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 = kzalloc(sizeof(struct device), GFP_ATOMIC); + + POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO); + if (!vbus) + return 0; + + dev_set_name(vbus, "vbus%d", addparams->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 = (__force void *)addparams->chanptr; + + /* register a virt bus device - + * this bus shows up under /sys/devices with .name value + * "virtpci%d" any devices added to this bus then show up under + * /sys/devices/virtpci0 + */ + ret = device_register(vbus); + if (ret) { + LOGERR("device_register FAILED:%d\n", ret); + POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR); + return 0; + } + write_vbus_chpInfo(vbus->platform_data /* chanptr */ , + &Chipset_DriverInfo); + write_vbus_busInfo(vbus->platform_data /* chanptr */ , &Bus_DriverInfo); + LOGINF("Added vbus %d; device %s created successfully\n", + addparams->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) { \ + memcpy_fromio(&scsi.wwnn, \ + &((ULTRA_IO_CHANNEL_PROTOCOL __iomem *) \ + chanptr)->vhba.wwnn, \ + sizeof(struct vhba_wwnn)); \ + memcpy_fromio(&scsi.max, \ + &((ULTRA_IO_CHANNEL_PROTOCOL __iomem *) \ + chanptr)->vhba.max, \ + sizeof(struct vhba_config_max)); \ + } + +/* find bus device with the busid that matches - match_busid matches bus_id */ +#define GET_BUS_DEV(busno) { \ + sprintf(busid, "vbus%d", busno); \ + vbus = bus_find_device(&virtpci_bus_type, NULL, \ + (void *)busid, match_busid); \ + if (!vbus) { \ + LOGERR("**** FAILED to find vbus %s\n", busid); \ + return 0; \ + } \ +} + +/* adds a vhba + * returns 0 failure, 1 success, + */ +static int add_vhba(struct add_virt_guestpart *addparams) +{ + int i; + struct scsi_adap_info scsi; + struct device *vbus; + unsigned char busid[BUS_ID_SIZE]; + + POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO); + if (!WAIT_FOR_IO_CHANNEL + ((ULTRA_IO_CHANNEL_PROTOCOL __iomem *) addparams->chanptr)) { + LOGERR("Timed out. Channel not ready\n"); + POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR); + return 0; + } + + GET_SCSIADAPINFO_FROM_CHANPTR(addparams->chanptr); + + GET_BUS_DEV(addparams->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_fromio(net.mac_addr, \ + ((ULTRA_IO_CHANNEL_PROTOCOL __iomem *) \ + chanptr)->vnic.macaddr, \ + MAX_MACADDR_LEN); \ + net.num_rcv_bufs = \ + readl(&((ULTRA_IO_CHANNEL_PROTOCOL __iomem *) \ + chanptr)->vnic.num_rcv_bufs); \ + net.mtu = readl(&((ULTRA_IO_CHANNEL_PROTOCOL __iomem *) \ + chanptr)->vnic.mtu); \ + memcpy_fromio(&net.zoneGuid, \ + &((ULTRA_IO_CHANNEL_PROTOCOL __iomem *) \ + chanptr)->vnic.zoneGuid, \ + 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 + ((ULTRA_IO_CHANNEL_PROTOCOL __iomem *) addparams->chanptr)) { + LOGERR("Timed out, channel not ready\n"); + POSTCODE_LINUX_2(VPCI_CREATE_FAILURE_PC, POSTCODE_SEVERITY_ERR); + return 0; + } + + GET_NETADAPINFO_FROM_CHANPTR(addparams->chanptr); + + GET_BUS_DEV(addparams->busNo); + + LOGINF("Adding vnic macaddr:%02x:%02x:%02x:%02x:%02x:%02x rcvbufs:%d mtu:%d chanptr:%p%pUL\n", + net.mac_addr[0], net.mac_addr[1], net.mac_addr[2], net.mac_addr[3], + net.mac_addr[4], net.mac_addr[5], net.num_rcv_bufs, net.mtu, + addparams->chanptr, &net.zoneGuid); + 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 */ +/*****************************************************/ + +static const struct pci_device_id * +virtpci_match_device(const struct pci_device_id *ids, + const struct virtpci_dev *dev) +{ + while (ids->vendor || ids->subvendor || ids->class_mask) { + DBGINF("ids->vendor:%x dev->vendor:%x ids->device:%x dev->device:%x\n", + ids->vendor, dev->vendor, ids->device, dev->device); + + if ((ids->vendor == dev->vendor) + && (ids->device == dev->device)) + return ids; + + ids++; + } + return NULL; +} + +/* NOTE: !!!!!! This function is called when a new device is added +* for this bus. Or, it is called for existing devices when a new +* driver is added for this bus. It returns nonzero if a given device +* can be handled by the given driver. +*/ +static int virtpci_bus_match(struct device *dev, struct device_driver *drv) +{ + struct virtpci_dev *virtpcidev = device_to_virtpci_dev(dev); + struct virtpci_driver *virtpcidrv = driver_to_virtpci_driver(drv); + int match = 0; + + DBGINF("In virtpci_bus_match dev->bus_id:%s drv->name:%s\n", + dev->bus_id, drv->name); + + /* check ids list for a match */ + if (virtpci_match_device(virtpcidrv->id_table, virtpcidev)) + match = 1; + + DBGINF("returning match:%d\n", match); + return match; /* 0 - no match; 1 - yes it matches */ +} + +static int virtpci_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + DBGINF("In virtpci_hotplug\n"); + /* add variables to the environment prior to the generation of + * hotplug events to user space + */ + if (add_uevent_var(env, "VIRTPCI_VERSION=%s", VIRTPCI_VERSION)) + return -ENOMEM; + return 0; +} + +static int virtpci_device_suspend(struct device *dev, pm_message_t state) +{ + DBGINF("In virtpci_device_suspend -NYI ****\n"); + return 0; +} + +static int virtpci_device_resume(struct device *dev) +{ + DBGINF("In virtpci_device_resume -NYI ****\n"); + return 0; +} + +/* For a child device just created on a client bus, fill in + * information about the driver that is controlling this device into + * the 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 __iomem *pIoChan = NULL; + struct device *pDev; + + LOGINF("virtpci_device_add parentbus:%p chanptr:%p\n", parentbus, + addparams->chanptr); + + POSTCODE_LINUX_2(VPCI_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO); + + if ((devtype != VIRTHBA_TYPE) && (devtype != VIRTNIC_TYPE)) { + LOGERR("**** FAILED to add device; devtype:%d not vhba:%d or vnic:%d\n", + devtype, VIRTHBA_TYPE, VIRTNIC_TYPE); + POSTCODE_LINUX_3(VPCI_CREATE_FAILURE_PC, devtype, + POSTCODE_SEVERITY_ERR); + return 0; + } + + /* add a Virtual Device */ + virtpcidev = kzalloc(sizeof(struct virtpci_dev), GFP_ATOMIC); + if (virtpcidev == NULL) { + LOGERR("can't add device - malloc FALLED\n"); + POSTCODE_LINUX_2(MALLOC_FAILURE_PC, POSTCODE_SEVERITY_ERR); + return 0; + } + + /* initialize stuff unique to virtpci_dev struct */ + virtpcidev->devtype = devtype; + if (devtype == VIRTHBA_TYPE) { + virtpcidev->device = PCI_DEVICE_ID_VIRTHBA; + virtpcidev->scsi = *scsi; + } else { + virtpcidev->device = PCI_DEVICE_ID_VIRTNIC; + virtpcidev->net = *net; + } + virtpcidev->vendor = PCI_VENDOR_ID_UNISYS; + virtpcidev->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 __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->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), + 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), + 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[16]; + int type, i, action = 0xffff; + unsigned int busno, deviceno; + void __iomem *chanptr; + struct add_vbus_guestpart busaddparams; + struct add_virt_guestpart addparams; + struct del_vbus_guestpart busdelparams; + struct del_virt_guestpart delparams; +#ifdef STORAGE_CHANNEL + U64 storagechannel; +#endif + +#define PRINT_USAGE_RETURN {\ + LOGERR("usage: 0-0-<chanptr> ==> delete vhba\n"); \ + LOGERR("usage: 0-1-<chanptr>-<busNo>-<deviceNo> ==> add vhba\n"); \ + LOGERR("usage: 0-f-<busNo> ==> delete all vhbas\n"); \ + LOGERR("\n"); \ + LOGERR("usage: 1-0-<chanptr> ==> delete vnic\n"); \ + LOGERR("usage: 1-1-<chanptr>-<busNo>-<deviceNo> ==> add vnic\n"); \ + LOGERR("usage: 1-f-<busNo> ==> delete all vnics\n"); \ + LOGERR("\n"); \ + LOGERR("usage: 6-0-<busNo> ==> delete vbus\n"); \ + LOGERR("usage: 6-1-<busNo> ==> add vbus\n"); \ + LOGERR("usage: 6-f ==> delete all vbuses\n"); \ + LOGERR("usage: 98-<busNo>-<deviceNo> ==> INJECT Client delete vnic\n"); \ + LOGERR("usage: 99-<chanptr>-<busNo>-<deviceNo> ==> INJECT Client add vnic\n"); \ + return -EINVAL; \ +} + + if (count >= ARRAY_SIZE(buf)) + 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 */ + NULL_UUID_LE, /* 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; + + + if (!unisys_spar_platform) + return -ENODEV; + + 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 00000000000..f7be17b669c --- /dev/null +++ b/drivers/staging/unisys/virtpci/virtpci.h @@ -0,0 +1,105 @@ +/* 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 <linux/version.h> +#include <linux/uuid.h> + +#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 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__ */ diff --git a/drivers/staging/unisys/visorchannel/Kconfig b/drivers/staging/unisys/visorchannel/Kconfig new file mode 100644 index 00000000000..41c3b4b997e --- /dev/null +++ b/drivers/staging/unisys/visorchannel/Kconfig @@ -0,0 +1,10 @@ +# +# Unisys visorchannel configuration +# + +config UNISYS_VISORCHANNEL + tristate "Unisys visorchannel driver" + depends on UNISYSSPAR && UNISYS_VISORUTIL + ---help--- + If you say Y here, you will enable the Unisys visorchannel driver. + diff --git a/drivers/staging/unisys/visorchannel/Makefile b/drivers/staging/unisys/visorchannel/Makefile new file mode 100644 index 00000000000..f0060be55bc --- /dev/null +++ b/drivers/staging/unisys/visorchannel/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for Unisys visorchannel +# + +obj-$(CONFIG_UNISYS_VISORCHANNEL) += visorchannel.o + +visorchannel-y := visorchannel_main.o visorchannel_funcs.o + +ccflags-y += -Idrivers/staging/unisys/include +ccflags-y += -Idrivers/staging/unisys/common-spar/include +ccflags-y += -Idrivers/staging/unisys/common-spar/include/channels +ccflags-y += -Idrivers/staging/unisys/visorutil +ccflags-y += -DCONFIG_SPAR_GUEST -DGUESTDRIVERBUILD -DNOAUTOVERSION + diff --git a/drivers/staging/unisys/visorchannel/globals.h b/drivers/staging/unisys/visorchannel/globals.h new file mode 100644 index 00000000000..07653b8dea7 --- /dev/null +++ b/drivers/staging/unisys/visorchannel/globals.h @@ -0,0 +1,29 @@ +/* globals.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. + */ + +#ifndef __VISORCHANNEL_GLOBALS_H__ +#define __VISORCHANNEL_GLOBALS_H__ + +#include "uniklog.h" +#include "timskmod.h" +#include "memregion.h" +#include "version.h" + +#define MYDRVNAME "visorchannel" + + +#endif diff --git a/drivers/staging/unisys/visorchannel/visorchannel.h b/drivers/staging/unisys/visorchannel/visorchannel.h new file mode 100644 index 00000000000..ecf0d11117e --- /dev/null +++ b/drivers/staging/unisys/visorchannel/visorchannel.h @@ -0,0 +1,78 @@ +/* visorchannel.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. + */ + +#ifndef __VISORCHANNEL_H__ +#define __VISORCHANNEL_H__ + +#include <linux/uuid.h> + +#include "commontypes.h" +#include "memregion.h" +#include "channel.h" +#ifndef HOSTADDRESS +#define HOSTADDRESS U64 +#endif +#ifndef BOOL +#define BOOL int +#endif + +/* VISORCHANNEL is an opaque structure to users. + * Fields are declared only in the implementation .c files. + */ +typedef struct VISORCHANNEL_Tag VISORCHANNEL; + +/* Note that for visorchannel_create() and visorchannel_create_overlapped(), + * <channelBytes> and <guid> arguments may be 0 if we are a channel CLIENT. + * In this case, the values can simply be read from the channel header. + */ +VISORCHANNEL *visorchannel_create(HOSTADDRESS physaddr, + ulong channelBytes, uuid_le guid); +VISORCHANNEL *visorchannel_create_overlapped(ulong channelBytes, + VISORCHANNEL *parent, ulong off, + uuid_le guid); +VISORCHANNEL *visorchannel_create_with_lock(HOSTADDRESS physaddr, + ulong channelBytes, uuid_le guid); +VISORCHANNEL *visorchannel_create_overlapped_with_lock(ulong channelBytes, + VISORCHANNEL *parent, + ulong off, uuid_le guid); +void visorchannel_destroy(VISORCHANNEL *channel); +int visorchannel_read(VISORCHANNEL *channel, ulong offset, + void *local, ulong nbytes); +int visorchannel_write(VISORCHANNEL *channel, ulong offset, + void *local, ulong nbytes); +int visorchannel_clear(VISORCHANNEL *channel, ulong offset, + U8 ch, ulong nbytes); +BOOL visorchannel_signalremove(VISORCHANNEL *channel, U32 queue, void *msg); +BOOL visorchannel_signalinsert(VISORCHANNEL *channel, U32 queue, void *msg); +int visorchannel_signalqueue_slots_avail(VISORCHANNEL *channel, U32 queue); +int visorchannel_signalqueue_max_slots(VISORCHANNEL *channel, U32 queue); + +HOSTADDRESS visorchannel_get_physaddr(VISORCHANNEL *channel); +ulong visorchannel_get_nbytes(VISORCHANNEL *channel); +char *visorchannel_id(VISORCHANNEL *channel, char *s); +char *visorchannel_zoneid(VISORCHANNEL *channel, char *s); +U64 visorchannel_get_clientpartition(VISORCHANNEL *channel); +uuid_le visorchannel_get_uuid(VISORCHANNEL *channel); +MEMREGION *visorchannel_get_memregion(VISORCHANNEL *channel); +char *visorchannel_uuid_id(uuid_le *guid, char *s); +void visorchannel_debug(VISORCHANNEL *channel, int nQueues, + struct seq_file *seq, U32 off); +void visorchannel_dump_section(VISORCHANNEL *chan, char *s, + int off, int len, struct seq_file *seq); +void __iomem *visorchannel_get_header(VISORCHANNEL *channel); + +#endif diff --git a/drivers/staging/unisys/visorchannel/visorchannel_funcs.c b/drivers/staging/unisys/visorchannel/visorchannel_funcs.c new file mode 100644 index 00000000000..a44da7c84ae --- /dev/null +++ b/drivers/staging/unisys/visorchannel/visorchannel_funcs.c @@ -0,0 +1,673 @@ +/* visorchannel_funcs.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. + */ + +/* + * This provides Supervisor channel communication primitives, which are + * independent of the mechanism used to access the channel data. All channel + * data is accessed using the memregion abstraction. (memregion has both + * a CM2 implementation and a direct memory implementation.) + */ + +#include "globals.h" +#include "visorchannel.h" +#include <linux/uuid.h> + +#define MYDRVNAME "visorchannel" + +struct VISORCHANNEL_Tag { + MEMREGION *memregion; /* from visor_memregion_create() */ + CHANNEL_HEADER chan_hdr; + uuid_le guid; + ulong size; + BOOL needs_lock; + spinlock_t insert_lock; + spinlock_t remove_lock; + + struct { + SIGNAL_QUEUE_HEADER req_queue; + SIGNAL_QUEUE_HEADER rsp_queue; + SIGNAL_QUEUE_HEADER event_queue; + SIGNAL_QUEUE_HEADER ack_queue; + } safe_uis_queue; +}; + +/* Creates the VISORCHANNEL abstraction for a data area in memory, but does + * NOT modify this data area. + */ +static VISORCHANNEL * +visorchannel_create_guts(HOSTADDRESS physaddr, ulong channelBytes, + VISORCHANNEL *parent, ulong off, uuid_le guid, + BOOL needs_lock) +{ + VISORCHANNEL *p = NULL; + void *rc = NULL; + + p = kmalloc(sizeof(VISORCHANNEL), GFP_KERNEL|__GFP_NORETRY); + if (p == NULL) { + ERRDRV("allocation failed: (status=0)\n"); + rc = NULL; + goto Away; + } + p->memregion = NULL; + p->needs_lock = needs_lock; + spin_lock_init(&p->insert_lock); + spin_lock_init(&p->remove_lock); + + /* prepare chan_hdr (abstraction to read/write channel memory) */ + if (parent == NULL) + p->memregion = + visor_memregion_create(physaddr, sizeof(CHANNEL_HEADER)); + else + p->memregion = + visor_memregion_create_overlapped(parent->memregion, + off, + sizeof(CHANNEL_HEADER)); + if (p->memregion == NULL) { + ERRDRV("visor_memregion_create failed failed: (status=0)\n"); + rc = NULL; + goto Away; + } + if (visor_memregion_read(p->memregion, 0, &p->chan_hdr, + sizeof(CHANNEL_HEADER)) < 0) { + ERRDRV("visor_memregion_read failed: (status=0)\n"); + rc = NULL; + goto Away; + } + if (channelBytes == 0) + /* we had better be a CLIENT of this channel */ + channelBytes = (ulong) p->chan_hdr.Size; + if (uuid_le_cmp(guid, NULL_UUID_LE) == 0) + /* we had better be a CLIENT of this channel */ + guid = p->chan_hdr.Type; + if (visor_memregion_resize(p->memregion, channelBytes) < 0) { + ERRDRV("visor_memregion_resize failed: (status=0)\n"); + rc = NULL; + goto Away; + } + p->size = channelBytes; + p->guid = guid; + + rc = p; +Away: + + if (rc == NULL) { + if (p != NULL) { + visorchannel_destroy(p); + p = NULL; + } + } + return rc; +} + +VISORCHANNEL * +visorchannel_create(HOSTADDRESS physaddr, ulong channelBytes, uuid_le guid) +{ + return visorchannel_create_guts(physaddr, channelBytes, NULL, 0, guid, + FALSE); +} +EXPORT_SYMBOL_GPL(visorchannel_create); + +VISORCHANNEL * +visorchannel_create_with_lock(HOSTADDRESS physaddr, ulong channelBytes, + uuid_le guid) +{ + return visorchannel_create_guts(physaddr, channelBytes, NULL, 0, guid, + TRUE); +} +EXPORT_SYMBOL_GPL(visorchannel_create_with_lock); + +VISORCHANNEL * +visorchannel_create_overlapped(ulong channelBytes, + VISORCHANNEL *parent, ulong off, uuid_le guid) +{ + return visorchannel_create_guts(0, channelBytes, parent, off, guid, + FALSE); +} +EXPORT_SYMBOL_GPL(visorchannel_create_overlapped); + +VISORCHANNEL * +visorchannel_create_overlapped_with_lock(ulong channelBytes, + VISORCHANNEL *parent, ulong off, + uuid_le guid) +{ + return visorchannel_create_guts(0, channelBytes, parent, off, guid, + TRUE); +} +EXPORT_SYMBOL_GPL(visorchannel_create_overlapped_with_lock); + +void +visorchannel_destroy(VISORCHANNEL *channel) +{ + if (channel == NULL) + return; + if (channel->memregion != NULL) { + visor_memregion_destroy(channel->memregion); + channel->memregion = NULL; + } + kfree(channel); +} +EXPORT_SYMBOL_GPL(visorchannel_destroy); + +HOSTADDRESS +visorchannel_get_physaddr(VISORCHANNEL *channel) +{ + return visor_memregion_get_physaddr(channel->memregion); +} +EXPORT_SYMBOL_GPL(visorchannel_get_physaddr); + +ulong +visorchannel_get_nbytes(VISORCHANNEL *channel) +{ + return channel->size; +} +EXPORT_SYMBOL_GPL(visorchannel_get_nbytes); + +char * +visorchannel_uuid_id(uuid_le *guid, char *s) +{ + sprintf(s, "%pUL", guid); + return s; +} +EXPORT_SYMBOL_GPL(visorchannel_uuid_id); + +char * +visorchannel_id(VISORCHANNEL *channel, char *s) +{ + return visorchannel_uuid_id(&channel->guid, s); +} +EXPORT_SYMBOL_GPL(visorchannel_id); + +char * +visorchannel_zoneid(VISORCHANNEL *channel, char *s) +{ + return visorchannel_uuid_id(&channel->chan_hdr.ZoneGuid, s); +} +EXPORT_SYMBOL_GPL(visorchannel_zoneid); + +HOSTADDRESS +visorchannel_get_clientpartition(VISORCHANNEL *channel) +{ + return channel->chan_hdr.PartitionHandle; +} +EXPORT_SYMBOL_GPL(visorchannel_get_clientpartition); + +uuid_le +visorchannel_get_uuid(VISORCHANNEL *channel) +{ + return channel->guid; +} +EXPORT_SYMBOL_GPL(visorchannel_get_uuid); + +MEMREGION * +visorchannel_get_memregion(VISORCHANNEL *channel) +{ + return channel->memregion; +} +EXPORT_SYMBOL_GPL(visorchannel_get_memregion); + +int +visorchannel_read(VISORCHANNEL *channel, ulong offset, + void *local, ulong nbytes) +{ + int rc = visor_memregion_read(channel->memregion, offset, + local, nbytes); + if ((rc >= 0) && (offset == 0) && (nbytes >= sizeof(CHANNEL_HEADER))) + memcpy(&channel->chan_hdr, local, sizeof(CHANNEL_HEADER)); + return rc; +} +EXPORT_SYMBOL_GPL(visorchannel_read); + +int +visorchannel_write(VISORCHANNEL *channel, ulong offset, + void *local, ulong nbytes) +{ + if (offset == 0 && nbytes >= sizeof(CHANNEL_HEADER)) + memcpy(&channel->chan_hdr, local, sizeof(CHANNEL_HEADER)); + return visor_memregion_write(channel->memregion, offset, local, nbytes); +} +EXPORT_SYMBOL_GPL(visorchannel_write); + +int +visorchannel_clear(VISORCHANNEL *channel, ulong offset, U8 ch, ulong nbytes) +{ + int rc = -1; + int bufsize = 65536; + int written = 0; + U8 *buf = vmalloc(bufsize); + + if (buf == NULL) { + ERRDRV("%s failed memory allocation", __func__); + goto Away; + } + memset(buf, ch, bufsize); + while (nbytes > 0) { + ulong thisbytes = bufsize; + int x = -1; + if (nbytes < thisbytes) + thisbytes = nbytes; + x = visor_memregion_write(channel->memregion, offset + written, + buf, thisbytes); + if (x < 0) { + rc = x; + goto Away; + } + written += thisbytes; + nbytes -= thisbytes; + } + rc = 0; + +Away: + if (buf != NULL) { + vfree(buf); + buf = NULL; + } + return rc; +} +EXPORT_SYMBOL_GPL(visorchannel_clear); + +void __iomem * +visorchannel_get_header(VISORCHANNEL *channel) +{ + return (void __iomem *) &(channel->chan_hdr); +} +EXPORT_SYMBOL_GPL(visorchannel_get_header); + +/** Return offset of a specific SIGNAL_QUEUE_HEADER from the beginning of a + * channel header + */ +#define SIG_QUEUE_OFFSET(chan_hdr, q) \ + ((chan_hdr)->oChannelSpace + ((q) * sizeof(SIGNAL_QUEUE_HEADER))) + +/** Return offset of a specific queue entry (data) from the beginning of a + * channel header + */ +#define SIG_DATA_OFFSET(chan_hdr, q, sig_hdr, slot) \ + (SIG_QUEUE_OFFSET(chan_hdr, q) + (sig_hdr)->oSignalBase + \ + ((slot) * (sig_hdr)->SignalSize)) + +/** Write the contents of a specific field within a SIGNAL_QUEUE_HEADER back + * into host memory + */ +#define SIG_WRITE_FIELD(channel, queue, sig_hdr, FIELD) \ + (visor_memregion_write(channel->memregion, \ + SIG_QUEUE_OFFSET(&channel->chan_hdr, queue)+ \ + offsetof(SIGNAL_QUEUE_HEADER, FIELD), \ + &((sig_hdr)->FIELD), \ + sizeof((sig_hdr)->FIELD)) >= 0) + +static BOOL +sig_read_header(VISORCHANNEL *channel, U32 queue, + SIGNAL_QUEUE_HEADER *sig_hdr) +{ + BOOL rc = FALSE; + + if (channel->chan_hdr.oChannelSpace < sizeof(CHANNEL_HEADER)) { + ERRDRV("oChannelSpace too small: (status=%d)\n", rc); + goto Away; + } + + /* Read the appropriate SIGNAL_QUEUE_HEADER into local memory. */ + + if (visor_memregion_read(channel->memregion, + SIG_QUEUE_OFFSET(&channel->chan_hdr, queue), + sig_hdr, sizeof(SIGNAL_QUEUE_HEADER)) < 0) { + ERRDRV("queue=%d SIG_QUEUE_OFFSET=%d", + queue, (int)SIG_QUEUE_OFFSET(&channel->chan_hdr, queue)); + ERRDRV("visor_memregion_read of signal queue failed: (status=%d)\n", rc); + goto Away; + } + rc = TRUE; +Away: + return rc; +} + +static BOOL +sig_do_data(VISORCHANNEL *channel, U32 queue, + SIGNAL_QUEUE_HEADER *sig_hdr, U32 slot, void *data, BOOL is_write) +{ + BOOL rc = FALSE; + int signal_data_offset = SIG_DATA_OFFSET(&channel->chan_hdr, queue, + sig_hdr, slot); + if (is_write) { + if (visor_memregion_write(channel->memregion, + signal_data_offset, + data, sig_hdr->SignalSize) < 0) { + ERRDRV("visor_memregion_write of signal data failed: (status=%d)\n", rc); + goto Away; + } + } else { + if (visor_memregion_read(channel->memregion, signal_data_offset, + data, sig_hdr->SignalSize) < 0) { + ERRDRV("visor_memregion_read of signal data failed: (status=%d)\n", rc); + goto Away; + } + } + rc = TRUE; +Away: + return rc; +} + +static inline BOOL +sig_read_data(VISORCHANNEL *channel, U32 queue, + SIGNAL_QUEUE_HEADER *sig_hdr, U32 slot, void *data) +{ + return sig_do_data(channel, queue, sig_hdr, slot, data, FALSE); +} + +static inline BOOL +sig_write_data(VISORCHANNEL *channel, U32 queue, + SIGNAL_QUEUE_HEADER *sig_hdr, U32 slot, void *data) +{ + return sig_do_data(channel, queue, sig_hdr, slot, data, TRUE); +} + +static inline unsigned char +safe_sig_queue_validate(pSIGNAL_QUEUE_HEADER psafe_sqh, + pSIGNAL_QUEUE_HEADER punsafe_sqh, + U32 *phead, U32 *ptail) +{ + if ((*phead >= psafe_sqh->MaxSignalSlots) + || (*ptail >= psafe_sqh->MaxSignalSlots)) { + /* Choose 0 or max, maybe based on current tail value */ + *phead = 0; + *ptail = 0; + + /* Sync with client as necessary */ + punsafe_sqh->Head = *phead; + punsafe_sqh->Tail = *ptail; + + ERRDRV("safe_sig_queue_validate: head = 0x%x, tail = 0x%x, MaxSlots = 0x%x", + *phead, *ptail, psafe_sqh->MaxSignalSlots); + return 0; + } + return 1; +} /* end safe_sig_queue_validate */ + +BOOL +visorchannel_signalremove(VISORCHANNEL *channel, U32 queue, void *msg) +{ + BOOL rc = FALSE; + SIGNAL_QUEUE_HEADER sig_hdr; + + if (channel->needs_lock) + spin_lock(&channel->remove_lock); + + if (!sig_read_header(channel, queue, &sig_hdr)) { + rc = FALSE; + goto Away; + } + if (sig_hdr.Head == sig_hdr.Tail) { + rc = FALSE; /* no signals to remove */ + goto Away; + } + sig_hdr.Tail = (sig_hdr.Tail + 1) % sig_hdr.MaxSignalSlots; + if (!sig_read_data(channel, queue, &sig_hdr, sig_hdr.Tail, msg)) { + ERRDRV("sig_read_data failed: (status=%d)\n", rc); + goto Away; + } + sig_hdr.NumSignalsReceived++; + + /* For each data field in SIGNAL_QUEUE_HEADER that was modified, + * update host memory. + */ + MEMORYBARRIER; + if (!SIG_WRITE_FIELD(channel, queue, &sig_hdr, Tail)) { + ERRDRV("visor_memregion_write of Tail failed: (status=%d)\n", + rc); + goto Away; + } + if (!SIG_WRITE_FIELD(channel, queue, &sig_hdr, NumSignalsReceived)) { + ERRDRV("visor_memregion_write of NumSignalsReceived failed: (status=%d)\n", rc); + goto Away; + } + rc = TRUE; +Away: + if (channel->needs_lock) + spin_unlock(&channel->remove_lock); + + return rc; +} +EXPORT_SYMBOL_GPL(visorchannel_signalremove); + +BOOL +visorchannel_signalinsert(VISORCHANNEL *channel, U32 queue, void *msg) +{ + BOOL rc = FALSE; + SIGNAL_QUEUE_HEADER sig_hdr; + + if (channel->needs_lock) + spin_lock(&channel->insert_lock); + + if (!sig_read_header(channel, queue, &sig_hdr)) { + rc = FALSE; + goto Away; + } + + sig_hdr.Head = ((sig_hdr.Head + 1) % sig_hdr.MaxSignalSlots); + if (sig_hdr.Head == sig_hdr.Tail) { + sig_hdr.NumOverflows++; + if (!SIG_WRITE_FIELD(channel, queue, &sig_hdr, NumOverflows)) { + ERRDRV("visor_memregion_write of NumOverflows failed: (status=%d)\n", rc); + goto Away; + } + rc = FALSE; + goto Away; + } + + if (!sig_write_data(channel, queue, &sig_hdr, sig_hdr.Head, msg)) { + ERRDRV("sig_write_data failed: (status=%d)\n", rc); + goto Away; + } + sig_hdr.NumSignalsSent++; + + /* For each data field in SIGNAL_QUEUE_HEADER that was modified, + * update host memory. + */ + MEMORYBARRIER; + if (!SIG_WRITE_FIELD(channel, queue, &sig_hdr, Head)) { + ERRDRV("visor_memregion_write of Head failed: (status=%d)\n", + rc); + goto Away; + } + if (!SIG_WRITE_FIELD(channel, queue, &sig_hdr, NumSignalsSent)) { + ERRDRV("visor_memregion_write of NumSignalsSent failed: (status=%d)\n", rc); + goto Away; + } + rc = TRUE; +Away: + if (channel->needs_lock) + spin_unlock(&channel->insert_lock); + + return rc; +} +EXPORT_SYMBOL_GPL(visorchannel_signalinsert); + + +int +visorchannel_signalqueue_slots_avail(VISORCHANNEL *channel, U32 queue) +{ + SIGNAL_QUEUE_HEADER sig_hdr; + U32 slots_avail, slots_used; + U32 head, tail; + + if (!sig_read_header(channel, queue, &sig_hdr)) + return 0; + head = sig_hdr.Head; + tail = sig_hdr.Tail; + if (head < tail) + head = head + sig_hdr.MaxSignalSlots; + slots_used = (head - tail); + slots_avail = sig_hdr.MaxSignals - slots_used; + return (int) slots_avail; +} +EXPORT_SYMBOL_GPL(visorchannel_signalqueue_slots_avail); + +int +visorchannel_signalqueue_max_slots(VISORCHANNEL *channel, U32 queue) +{ + SIGNAL_QUEUE_HEADER sig_hdr; + if (!sig_read_header(channel, queue, &sig_hdr)) + return 0; + return (int) sig_hdr.MaxSignals; +} +EXPORT_SYMBOL_GPL(visorchannel_signalqueue_max_slots); + +static void +sigqueue_debug(SIGNAL_QUEUE_HEADER *q, int which, struct seq_file *seq) +{ + seq_printf(seq, "Signal Queue #%d\n", which); + seq_printf(seq, " VersionId = %lu\n", (ulong) q->VersionId); + seq_printf(seq, " Type = %lu\n", (ulong) q->Type); + seq_printf(seq, " oSignalBase = %llu\n", + (long long) q->oSignalBase); + seq_printf(seq, " SignalSize = %lu\n", (ulong) q->SignalSize); + seq_printf(seq, " MaxSignalSlots = %lu\n", + (ulong) q->MaxSignalSlots); + seq_printf(seq, " MaxSignals = %lu\n", (ulong) q->MaxSignals); + seq_printf(seq, " FeatureFlags = %-16.16Lx\n", + (long long) q->FeatureFlags); + seq_printf(seq, " NumSignalsSent = %llu\n", + (long long) q->NumSignalsSent); + seq_printf(seq, " NumSignalsReceived = %llu\n", + (long long) q->NumSignalsReceived); + seq_printf(seq, " NumOverflows = %llu\n", + (long long) q->NumOverflows); + seq_printf(seq, " Head = %lu\n", (ulong) q->Head); + seq_printf(seq, " Tail = %lu\n", (ulong) q->Tail); +} + +void +visorchannel_debug(VISORCHANNEL *channel, int nQueues, + struct seq_file *seq, U32 off) +{ + HOSTADDRESS addr = 0; + ulong nbytes = 0, nbytes_region = 0; + MEMREGION *memregion = NULL; + CHANNEL_HEADER hdr; + CHANNEL_HEADER *phdr = &hdr; + int i = 0; + int errcode = 0; + + if (channel == NULL) { + ERRDRV("%s no channel", __func__); + return; + } + memregion = channel->memregion; + if (memregion == NULL) { + ERRDRV("%s no memregion", __func__); + return; + } + addr = visor_memregion_get_physaddr(memregion); + nbytes_region = visor_memregion_get_nbytes(memregion); + errcode = visorchannel_read(channel, off, + phdr, sizeof(CHANNEL_HEADER)); + if (errcode < 0) { + seq_printf(seq, + "Read of channel header failed with errcode=%d)\n", + errcode); + if (off == 0) { + phdr = &channel->chan_hdr; + seq_puts(seq, "(following data may be stale)\n"); + } else + return; + } + nbytes = (ulong) (phdr->Size); + seq_printf(seq, "--- Begin channel @0x%-16.16Lx for 0x%lx bytes (region=0x%lx bytes) ---\n", + addr + off, nbytes, nbytes_region); + seq_printf(seq, "Type = %pUL\n", &phdr->Type); + seq_printf(seq, "ZoneGuid = %pUL\n", &phdr->ZoneGuid); + seq_printf(seq, "Signature = 0x%-16.16Lx\n", + (long long) phdr->Signature); + seq_printf(seq, "LegacyState = %lu\n", (ulong) phdr->LegacyState); + seq_printf(seq, "SrvState = %lu\n", (ulong) phdr->SrvState); + seq_printf(seq, "CliStateBoot = %lu\n", (ulong) phdr->CliStateBoot); + seq_printf(seq, "CliStateOS = %lu\n", (ulong) phdr->CliStateOS); + seq_printf(seq, "HeaderSize = %lu\n", (ulong) phdr->HeaderSize); + seq_printf(seq, "Size = %llu\n", (long long) phdr->Size); + seq_printf(seq, "Features = 0x%-16.16llx\n", + (long long) phdr->Features); + seq_printf(seq, "PartitionHandle = 0x%-16.16llx\n", + (long long) phdr->PartitionHandle); + seq_printf(seq, "Handle = 0x%-16.16llx\n", + (long long) phdr->Handle); + seq_printf(seq, "VersionId = %lu\n", (ulong) phdr->VersionId); + seq_printf(seq, "oChannelSpace = %llu\n", + (long long) phdr->oChannelSpace); + if ((phdr->oChannelSpace == 0) || (errcode < 0)) + ; + else + for (i = 0; i < nQueues; i++) { + SIGNAL_QUEUE_HEADER q; + errcode = visorchannel_read(channel, + off + phdr->oChannelSpace + + (i * sizeof(q)), + &q, sizeof(q)); + if (errcode < 0) { + seq_printf(seq, + "failed to read signal queue #%d from channel @0x%-16.16Lx errcode=%d\n", + i, addr, errcode); + continue; + } + sigqueue_debug(&q, i, seq); + } + seq_printf(seq, "--- End channel @0x%-16.16Lx for 0x%lx bytes ---\n", + addr + off, nbytes); +} +EXPORT_SYMBOL_GPL(visorchannel_debug); + +void +visorchannel_dump_section(VISORCHANNEL *chan, char *s, + int off, int len, struct seq_file *seq) +{ + char *buf, *tbuf, *fmtbuf; + int fmtbufsize = 0; + int i; + int errcode = 0; + + fmtbufsize = 100 * COVQ(len, 16); + buf = kmalloc(len, GFP_KERNEL|__GFP_NORETRY); + fmtbuf = kmalloc(fmtbufsize, GFP_KERNEL|__GFP_NORETRY); + if (buf == NULL || fmtbuf == NULL) + goto Away; + + errcode = visorchannel_read(chan, off, buf, len); + if (errcode < 0) { + ERRDRV("%s failed to read %s from channel errcode=%d", + s, __func__, errcode); + goto Away; + } + seq_printf(seq, "channel %s:\n", s); + tbuf = buf; + while (len > 0) { + i = (len < 16) ? len : 16; + hex_dump_to_buffer(tbuf, i, 16, 1, fmtbuf, fmtbufsize, TRUE); + seq_printf(seq, "%s\n", fmtbuf); + tbuf += 16; + len -= 16; + } + +Away: + if (buf != NULL) { + kfree(buf); + buf = NULL; + } + if (fmtbuf != NULL) { + kfree(fmtbuf); + fmtbuf = NULL; + } +} +EXPORT_SYMBOL_GPL(visorchannel_dump_section); diff --git a/drivers/staging/unisys/visorchannel/visorchannel_main.c b/drivers/staging/unisys/visorchannel/visorchannel_main.c new file mode 100644 index 00000000000..f4be2e62c97 --- /dev/null +++ b/drivers/staging/unisys/visorchannel/visorchannel_main.c @@ -0,0 +1,52 @@ +/* visorchannel_main.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. + */ + +/* + * This is a module "wrapper" around visorchannel_funcs. + */ + +#include "globals.h" +#include "channel.h" +#include "visorchannel.h" +#include <linux/uuid.h> + +#define MYDRVNAME "visorchannel" + +static int __init +visorchannel_init(void) +{ + if (!unisys_spar_platform) + return -ENODEV; + + INFODRV("driver version %s loaded", VERSION); + return 0; +} + +static void +visorchannel_exit(void) +{ + INFODRV("driver unloaded"); +} + +module_init(visorchannel_init); +module_exit(visorchannel_exit); + +MODULE_AUTHOR("Unisys"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Supervisor channel driver for service partition: ver " + VERSION); +MODULE_VERSION(VERSION); diff --git a/drivers/staging/unisys/visorchipset/Kconfig b/drivers/staging/unisys/visorchipset/Kconfig new file mode 100644 index 00000000000..7ca2fbca9d5 --- /dev/null +++ b/drivers/staging/unisys/visorchipset/Kconfig @@ -0,0 +1,10 @@ +# +# Unisys visorchipset configuration +# + +config UNISYS_VISORCHIPSET + tristate "Unisys visorchipset driver" + depends on UNISYSSPAR && UNISYS_VISORUTIL && UNISYS_VISORCHANNEL + ---help--- + If you say Y here, you will enable the Unisys visorchipset driver. + diff --git a/drivers/staging/unisys/visorchipset/Makefile b/drivers/staging/unisys/visorchipset/Makefile new file mode 100644 index 00000000000..ead4b9c0271 --- /dev/null +++ b/drivers/staging/unisys/visorchipset/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for Unisys visorchipset +# + +obj-$(CONFIG_UNISYS_VISORCHIPSET) += visorchipset.o + +visorchipset-y := visorchipset_main.o controlvm_direct.o file.o \ + parser.o + +ccflags-y += -Idrivers/staging/unisys/include +ccflags-y += -Idrivers/staging/unisys/uislib +ccflags-y += -Idrivers/staging/unisys/visorchannel +ccflags-y += -Idrivers/staging/unisys/common-spar/include +ccflags-y += -Idrivers/staging/unisys/common-spar/include/channels +ccflags-y += -Idrivers/staging/unisys/visorutil +ccflags-y += -Iinclude/generated +ccflags-y += -DCONFIG_SPAR_GUEST -DGUESTDRIVERBUILD -DNOAUTOVERSION + diff --git a/drivers/staging/unisys/visorchipset/controlvm.h b/drivers/staging/unisys/visorchipset/controlvm.h new file mode 100644 index 00000000000..012891c3f21 --- /dev/null +++ b/drivers/staging/unisys/visorchipset/controlvm.h @@ -0,0 +1,27 @@ +/* controlvm.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. + */ + +#ifndef __CONTROLVM_H__ +#define __CONTROLVM_H__ + +#include "timskmod.h" + +int controlvm_init(void); +void controlvm_deinit(void); +HOSTADDRESS controlvm_get_channel_address(void); + +#endif diff --git a/drivers/staging/unisys/visorchipset/controlvm_direct.c b/drivers/staging/unisys/visorchipset/controlvm_direct.c new file mode 100644 index 00000000000..cd10e3a2a07 --- /dev/null +++ b/drivers/staging/unisys/visorchipset/controlvm_direct.c @@ -0,0 +1,62 @@ +/* controlvm_direct.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. + */ + +/* This is a controlvm-related code that is dependent upon firmware running + * on a virtual partition. + */ + +#include "globals.h" +#include "uisutils.h" +#include "controlvm.h" +#define CURRENT_FILE_PC VISOR_CHIPSET_PC_controlvm_direct_c + + +/* We can fill in this code when we learn how to make vmcalls... */ + + + +int controlvm_init(void) +{ + return 0; +} + + + +void controlvm_deinit(void) +{ +} + + + +HOSTADDRESS controlvm_get_channel_address(void) +{ + static BOOL warned = FALSE; + U64 addr = 0; + + U32 size = 0; + + if (!VMCALL_SUCCESSFUL(Issue_VMCALL_IO_CONTROLVM_ADDR(&addr, &size))) { + if (!warned) { + ERRDRV("%s - vmcall to determine controlvm channel addr failed", + __func__); + warned = TRUE; + } + return 0; + } + INFODRV("controlvm addr=%Lx", addr); + return addr; +} diff --git a/drivers/staging/unisys/visorchipset/file.c b/drivers/staging/unisys/visorchipset/file.c new file mode 100644 index 00000000000..fccc4f0c3a4 --- /dev/null +++ b/drivers/staging/unisys/visorchipset/file.c @@ -0,0 +1,226 @@ +/* file.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. + */ + +/* This contains the implementation that allows a usermode program to + * communicate with the visorchipset driver using a device/file interface. + */ + +#include "globals.h" +#include "visorchannel.h" +#include <linux/mm.h> +#include <linux/fs.h> +#include "uisutils.h" +#include "file.h" + +#define CURRENT_FILE_PC VISOR_CHIPSET_PC_file_c + +static struct cdev Cdev; +static VISORCHANNEL **PControlVm_channel; +static dev_t MajorDev = -1; /**< indicates major num for device */ +static BOOL Registered = FALSE; + +static int visorchipset_open(struct inode *inode, struct file *file); +static int visorchipset_release(struct inode *inode, struct file *file); +static int visorchipset_mmap(struct file *file, struct vm_area_struct *vma); +#ifdef HAVE_UNLOCKED_IOCTL +long visorchipset_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +#else +int visorchipset_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); +#endif + +static const struct file_operations visorchipset_fops = { + .owner = THIS_MODULE, + .open = visorchipset_open, + .read = NULL, + .write = NULL, +#ifdef HAVE_UNLOCKED_IOCTL + .unlocked_ioctl = visorchipset_ioctl, +#else + .ioctl = visorchipset_ioctl, +#endif + .release = visorchipset_release, + .mmap = visorchipset_mmap, +}; + +int +visorchipset_file_init(dev_t majorDev, VISORCHANNEL **pControlVm_channel) +{ + int rc = -1; + + PControlVm_channel = pControlVm_channel; + MajorDev = majorDev; + cdev_init(&Cdev, &visorchipset_fops); + Cdev.owner = THIS_MODULE; + if (MAJOR(MajorDev) == 0) { + /* dynamic major device number registration required */ + if (alloc_chrdev_region(&MajorDev, 0, 1, MYDRVNAME) < 0) { + ERRDRV("Unable to allocate+register char device %s", + MYDRVNAME); + goto Away; + } + Registered = TRUE; + INFODRV("New major number %d registered\n", MAJOR(MajorDev)); + } else { + /* static major device number registration required */ + if (register_chrdev_region(MajorDev, 1, MYDRVNAME) < 0) { + ERRDRV("Unable to register char device %s", MYDRVNAME); + goto Away; + } + Registered = TRUE; + INFODRV("Static major number %d registered\n", MAJOR(MajorDev)); + } + if (cdev_add(&Cdev, MKDEV(MAJOR(MajorDev), 0), 1) < 0) { + ERRDRV("failed to create char device: (status=%d)\n", rc); + goto Away; + } + INFODRV("Registered char device for %s (major=%d)", + MYDRVNAME, MAJOR(MajorDev)); + rc = 0; +Away: + return rc; +} + +void +visorchipset_file_cleanup(void) +{ + if (Cdev.ops != NULL) + cdev_del(&Cdev); + Cdev.ops = NULL; + if (Registered) { + if (MAJOR(MajorDev) >= 0) { + unregister_chrdev_region(MajorDev, 1); + MajorDev = MKDEV(0, 0); + } + Registered = FALSE; + } +} + +static int +visorchipset_open(struct inode *inode, struct file *file) +{ + unsigned minor_number = iminor(inode); + int rc = -ENODEV; + + DEBUGDRV("%s", __func__); + if (minor_number != 0) + goto Away; + file->private_data = NULL; + rc = 0; +Away: + if (rc < 0) + ERRDRV("%s minor=%d failed", __func__, minor_number); + return rc; +} + +static int +visorchipset_release(struct inode *inode, struct file *file) +{ + DEBUGDRV("%s", __func__); + return 0; +} + +static int +visorchipset_mmap(struct file *file, struct vm_area_struct *vma) +{ + ulong physAddr = 0; + ulong offset = vma->vm_pgoff << PAGE_SHIFT; + GUEST_PHYSICAL_ADDRESS addr = 0; + + /* sv_enable_dfp(); */ + DEBUGDRV("%s", __func__); + if (offset & (PAGE_SIZE - 1)) { + ERRDRV("%s virtual address NOT page-aligned!", __func__); + return -ENXIO; /* need aligned offsets */ + } + switch (offset) { + case VISORCHIPSET_MMAP_CONTROLCHANOFFSET: + vma->vm_flags |= VM_IO; + if (*PControlVm_channel == NULL) { + ERRDRV("%s no controlvm channel yet", __func__); + return -ENXIO; + } + visorchannel_read(*PControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + gpControlChannel), &addr, + sizeof(addr)); + if (addr == 0) { + ERRDRV("%s control channel address is 0", __func__); + return -ENXIO; + } + physAddr = (ulong) (addr); + DEBUGDRV("mapping physical address = 0x%lx", physAddr); + if (remap_pfn_range(vma, vma->vm_start, + physAddr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + /*pgprot_noncached */ + (vma->vm_page_prot))) { + ERRDRV("%s remap_pfn_range failed", __func__); + return -EAGAIN; + } + break; + default: + return -ENOSYS; + } + DEBUGDRV("%s success!", __func__); + return 0; +} + +#ifdef HAVE_UNLOCKED_IOCTL +long +visorchipset_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +#else +int +visorchipset_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +#endif +{ + int rc = SUCCESS; + S64 adjustment; + S64 vrtc_offset; + DBGINF("entered visorchipset_ioctl, cmd=%d", cmd); + switch (cmd) { + case VMCALL_QUERY_GUEST_VIRTUAL_TIME_OFFSET: + /* get the physical rtc offset */ + vrtc_offset = Issue_VMCALL_QUERY_GUEST_VIRTUAL_TIME_OFFSET(); + if (copy_to_user + ((void __user *)arg, &vrtc_offset, sizeof(vrtc_offset))) { + rc = -EFAULT; + goto Away; + } + DBGINF("insde visorchipset_ioctl, cmd=%d, vrtc_offset=%lld", + cmd, vrtc_offset); + break; + case VMCALL_UPDATE_PHYSICAL_TIME: + if (copy_from_user + (&adjustment, (void __user *)arg, sizeof(adjustment))) { + rc = -EFAULT; + goto Away; + } + DBGINF("insde visorchipset_ioctl, cmd=%d, adjustment=%lld", cmd, + adjustment); + rc = Issue_VMCALL_UPDATE_PHYSICAL_TIME(adjustment); + break; + default: + LOGERR("visorchipset_ioctl received invalid command"); + rc = -EFAULT; + break; + } +Away: + DBGINF("exiting %d!", rc); + return rc; +} diff --git a/drivers/staging/unisys/visorchipset/file.h b/drivers/staging/unisys/visorchipset/file.h new file mode 100644 index 00000000000..21bb906242e --- /dev/null +++ b/drivers/staging/unisys/visorchipset/file.h @@ -0,0 +1,26 @@ +/* file.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. + */ + +#ifndef __FILE_H__ +#define __FILE_H__ + +#include "globals.h" + +int visorchipset_file_init(dev_t majorDev, VISORCHANNEL **pControlVm_channel); +void visorchipset_file_cleanup(void); + +#endif diff --git a/drivers/staging/unisys/visorchipset/globals.h b/drivers/staging/unisys/visorchipset/globals.h new file mode 100644 index 00000000000..0fe14599f18 --- /dev/null +++ b/drivers/staging/unisys/visorchipset/globals.h @@ -0,0 +1,45 @@ +/* globals.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. + */ + + +#ifndef __VISORCHIPSET_GLOBALS_H__ +#define __VISORCHIPSET_GLOBALS_H__ + +#include "uniklog.h" +#include "diagnostics/appos_subsystems.h" +#include "timskmod.h" +#include "visorchipset.h" +#include "visorchipset_umode.h" +#include "version.h" + +#define MYDRVNAME "visorchipset" + + +/* module parameters */ + +extern int visorchipset_testvnic; +extern int visorchipset_testvnicclient; +extern int visorchipset_testmsg; +extern int visorchipset_major; +extern int visorchipset_serverregwait; +extern int visorchipset_clientregwait; +extern int visorchipset_testteardown; +extern int visorchipset_disable_controlvm; +extern int visorchipset_crash_kernel; +extern int visorchipset_holdchipsetready; + +#endif diff --git a/drivers/staging/unisys/visorchipset/parser.c b/drivers/staging/unisys/visorchipset/parser.c new file mode 100644 index 00000000000..4274dd2dbbd --- /dev/null +++ b/drivers/staging/unisys/visorchipset/parser.c @@ -0,0 +1,475 @@ +/* parser.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. + */ + +#include "parser.h" +#include "memregion.h" +#include "controlvmchannel.h" +#include <linux/ctype.h> +#include <linux/mm.h> +#include <linux/uuid.h> + +#define MYDRVNAME "visorchipset_parser" +#define CURRENT_FILE_PC VISOR_CHIPSET_PC_parser_c + +/* We will refuse to allocate more than this many bytes to copy data from + * incoming payloads. This serves as a throttling mechanism. + */ +#define MAX_CONTROLVM_PAYLOAD_BYTES (1024*128) +static ulong Controlvm_Payload_Bytes_Buffered; + +struct PARSER_CONTEXT_Tag { + ulong allocbytes; + ulong param_bytes; + u8 *curr; + ulong bytes_remaining; + BOOL byte_stream; + char data[0]; +}; + +static PARSER_CONTEXT * +parser_init_guts(U64 addr, U32 bytes, BOOL isLocal, + BOOL hasStandardPayloadHeader, BOOL *tryAgain) +{ + int allocbytes = sizeof(PARSER_CONTEXT) + bytes; + PARSER_CONTEXT *rc = NULL; + PARSER_CONTEXT *ctx = NULL; + MEMREGION *rgn = NULL; + ULTRA_CONTROLVM_PARAMETERS_HEADER *phdr = NULL; + + if (tryAgain) + *tryAgain = FALSE; + if (!hasStandardPayloadHeader) + /* alloc and 0 extra byte to ensure payload is + * '\0'-terminated + */ + allocbytes++; + if ((Controlvm_Payload_Bytes_Buffered + bytes) + > MAX_CONTROLVM_PAYLOAD_BYTES) { + ERRDRV("%s (%s:%d) - prevented allocation of %d bytes to prevent exceeding throttling max (%d)", + __func__, __FILE__, __LINE__, allocbytes, + MAX_CONTROLVM_PAYLOAD_BYTES); + if (tryAgain) + *tryAgain = TRUE; + rc = NULL; + goto Away; + } + ctx = kzalloc(allocbytes, GFP_KERNEL|__GFP_NORETRY); + if (ctx == NULL) { + ERRDRV("%s (%s:%d) - failed to allocate %d bytes", + __func__, __FILE__, __LINE__, allocbytes); + if (tryAgain) + *tryAgain = TRUE; + rc = NULL; + goto Away; + } + + ctx->allocbytes = allocbytes; + ctx->param_bytes = bytes; + ctx->curr = NULL; + ctx->bytes_remaining = 0; + ctx->byte_stream = FALSE; + if (isLocal) { + void *p; + if (addr > virt_to_phys(high_memory - 1)) { + ERRDRV("%s - bad local address (0x%-16.16Lx for %lu)", + __func__, + (unsigned long long) addr, (ulong) bytes); + rc = NULL; + goto Away; + } + p = __va((ulong) (addr)); + memcpy(ctx->data, p, bytes); + } else { + rgn = visor_memregion_create(addr, bytes); + if (!rgn) { + rc = NULL; + goto Away; + } + if (visor_memregion_read(rgn, 0, ctx->data, bytes) < 0) { + rc = NULL; + goto Away; + } + } + if (!hasStandardPayloadHeader) { + ctx->byte_stream = TRUE; + rc = ctx; + goto Away; + } + phdr = (ULTRA_CONTROLVM_PARAMETERS_HEADER *) (ctx->data); + if (phdr->TotalLength != bytes) { + ERRDRV("%s - bad total length %lu (should be %lu)", + __func__, + (ulong) (phdr->TotalLength), (ulong) (bytes)); + rc = NULL; + goto Away; + } + if (phdr->TotalLength < phdr->HeaderLength) { + ERRDRV("%s - total length < header length (%lu < %lu)", + __func__, + (ulong) (phdr->TotalLength), + (ulong) (phdr->HeaderLength)); + rc = NULL; + goto Away; + } + if (phdr->HeaderLength < sizeof(ULTRA_CONTROLVM_PARAMETERS_HEADER)) { + ERRDRV("%s - header is too small (%lu < %lu)", + __func__, + (ulong) (phdr->HeaderLength), + (ulong) (sizeof(ULTRA_CONTROLVM_PARAMETERS_HEADER))); + rc = NULL; + goto Away; + } + + rc = ctx; +Away: + if (rgn) { + visor_memregion_destroy(rgn); + rgn = NULL; + } + if (rc) + Controlvm_Payload_Bytes_Buffered += ctx->param_bytes; + else { + if (ctx) { + parser_done(ctx); + ctx = NULL; + } + } + return rc; +} + +PARSER_CONTEXT * +parser_init(U64 addr, U32 bytes, BOOL isLocal, BOOL *tryAgain) +{ + return parser_init_guts(addr, bytes, isLocal, TRUE, tryAgain); +} + +/* Call this instead of parser_init() if the payload area consists of just + * a sequence of bytes, rather than a ULTRA_CONTROLVM_PARAMETERS_HEADER + * structures. Afterwards, you can call parser_simpleString_get() or + * parser_byteStream_get() to obtain the data. + */ +PARSER_CONTEXT * +parser_init_byteStream(U64 addr, U32 bytes, BOOL isLocal, BOOL *tryAgain) +{ + return parser_init_guts(addr, bytes, isLocal, FALSE, tryAgain); +} + +/* Obtain '\0'-terminated copy of string in payload area. + */ +char * +parser_simpleString_get(PARSER_CONTEXT *ctx) +{ + if (!ctx->byte_stream) + return NULL; + return ctx->data; /* note this IS '\0'-terminated, because of + * the num of bytes we alloc+clear in + * parser_init_byteStream() */ +} + +/* Obtain a copy of the buffer in the payload area. + */ +void * +parser_byteStream_get(PARSER_CONTEXT *ctx, ulong *nbytes) +{ + if (!ctx->byte_stream) + return NULL; + if (nbytes) + *nbytes = ctx->param_bytes; + return (void *) ctx->data; +} + +uuid_le +parser_id_get(PARSER_CONTEXT *ctx) +{ + ULTRA_CONTROLVM_PARAMETERS_HEADER *phdr = NULL; + + if (ctx == NULL) { + ERRDRV("%s (%s:%d) - no context", + __func__, __FILE__, __LINE__); + return NULL_UUID_LE; + } + phdr = (ULTRA_CONTROLVM_PARAMETERS_HEADER *) (ctx->data); + return phdr->Id; +} + +void +parser_param_start(PARSER_CONTEXT *ctx, PARSER_WHICH_STRING which_string) +{ + ULTRA_CONTROLVM_PARAMETERS_HEADER *phdr = NULL; + + if (ctx == NULL) { + ERRDRV("%s (%s:%d) - no context", + __func__, __FILE__, __LINE__); + goto Away; + } + phdr = (ULTRA_CONTROLVM_PARAMETERS_HEADER *) (ctx->data); + switch (which_string) { + case PARSERSTRING_INITIATOR: + ctx->curr = ctx->data + phdr->InitiatorOffset; + ctx->bytes_remaining = phdr->InitiatorLength; + break; + case PARSERSTRING_TARGET: + ctx->curr = ctx->data + phdr->TargetOffset; + ctx->bytes_remaining = phdr->TargetLength; + break; + case PARSERSTRING_CONNECTION: + ctx->curr = ctx->data + phdr->ConnectionOffset; + ctx->bytes_remaining = phdr->ConnectionLength; + break; + case PARSERSTRING_NAME: + ctx->curr = ctx->data + phdr->NameOffset; + ctx->bytes_remaining = phdr->NameLength; + break; + default: + ERRDRV("%s - bad which_string %d", __func__, which_string); + break; + } + +Away: + return; +} + +void +parser_done(PARSER_CONTEXT *ctx) +{ + if (!ctx) + return; + Controlvm_Payload_Bytes_Buffered -= ctx->param_bytes; + kfree(ctx); +} + +/** Return length of string not counting trailing spaces. */ +static int +string_length_no_trail(char *s, int len) +{ + int i = len - 1; + while (i >= 0) { + if (!isspace(s[i])) + return i + 1; + i--; + } + return 0; +} + +/** Grab the next name and value out of the parameter buffer. + * The entire parameter buffer looks like this: + * <name>=<value>\0 + * <name>=<value>\0 + * ... + * \0 + * If successful, the next <name> value is returned within the supplied + * <nam> buffer (the value is always upper-cased), and the corresponding + * <value> is returned within a kmalloc()ed buffer, whose pointer is + * provided as the return value of this function. + * (The total number of bytes allocated is strlen(<value>)+1.) + * + * NULL is returned to indicate failure, which can occur for several reasons: + * - all <name>=<value> pairs have already been processed + * - bad parameter + * - parameter buffer ends prematurely (couldn't find an '=' or '\0' within + * the confines of the parameter buffer) + * - the <nam> buffer is not large enough to hold the <name> of the next + * parameter + */ +void * +parser_param_get(PARSER_CONTEXT *ctx, char *nam, int namesize) +{ + u8 *pscan, *pnam = nam; + ulong nscan; + int value_length = -1, orig_value_length = -1; + void *value = NULL; + int i; + int closing_quote = 0; + + if (!ctx) + return NULL; + pscan = ctx->curr; + nscan = ctx->bytes_remaining; + if (nscan == 0) + return NULL; + if (*pscan == '\0') + /* This is the normal return point after you have processed + * all of the <name>=<value> pairs in a syntactically-valid + * parameter buffer. + */ + return NULL; + + /* skip whitespace */ + while (isspace(*pscan)) { + pscan++; + nscan--; + if (nscan == 0) + return NULL; + } + + while (*pscan != ':') { + if (namesize <= 0) { + ERRDRV("%s - name too big", __func__); + return NULL; + } + *pnam = toupper(*pscan); + pnam++; + namesize--; + pscan++; + nscan--; + if (nscan == 0) { + ERRDRV("%s - unexpected end of input parsing name", + __func__); + return NULL; + } + } + if (namesize <= 0) { + ERRDRV("%s - name too big", __func__); + return NULL; + } + *pnam = '\0'; + nam[string_length_no_trail(nam, strlen(nam))] = '\0'; + + /* point to char immediately after ":" in "<name>:<value>" */ + pscan++; + nscan--; + /* skip whitespace */ + while (isspace(*pscan)) { + pscan++; + nscan--; + if (nscan == 0) { + ERRDRV("%s - unexpected end of input looking for value", + __func__); + return NULL; + } + } + if (nscan == 0) { + ERRDRV("%s - unexpected end of input looking for value", + __func__); + return NULL; + } + if (*pscan == '\'' || *pscan == '"') { + closing_quote = *pscan; + pscan++; + nscan--; + if (nscan == 0) { + ERRDRV("%s - unexpected end of input after %c", + __func__, closing_quote); + return NULL; + } + } + + /* look for a separator character, terminator character, or + * end of data + */ + for (i = 0, value_length = -1; i < nscan; i++) { + if (closing_quote) { + if (pscan[i] == '\0') { + ERRDRV("%s - unexpected end of input parsing quoted value", __func__); + return NULL; + } + if (pscan[i] == closing_quote) { + value_length = i; + break; + } + } else + if (pscan[i] == ',' || pscan[i] == ';' + || pscan[i] == '\0') { + value_length = i; + break; + } + } + if (value_length < 0) { + if (closing_quote) { + ERRDRV("%s - unexpected end of input parsing quoted value", __func__); + return NULL; + } + value_length = nscan; + } + orig_value_length = value_length; + if (closing_quote == 0) + value_length = string_length_no_trail(pscan, orig_value_length); + value = kmalloc(value_length + 1, GFP_KERNEL|__GFP_NORETRY); + if (value == NULL) + return NULL; + memcpy(value, pscan, value_length); + ((u8 *) (value))[value_length] = '\0'; + + pscan += orig_value_length; + nscan -= orig_value_length; + + /* skip past separator or closing quote */ + if (nscan > 0) { + if (*pscan != '\0') { + pscan++; + nscan--; + } + } + + if (closing_quote && (nscan > 0)) { + /* we still need to skip around the real separator if present */ + /* first, skip whitespace */ + while (isspace(*pscan)) { + pscan++; + nscan--; + if (nscan == 0) + break; + } + if (nscan > 0) { + if (*pscan == ',' || *pscan == ';') { + pscan++; + nscan--; + } else if (*pscan != '\0') { + ERRDRV("%s - missing separator after quoted string", __func__); + kfree(value); + value = NULL; + return NULL; + } + } + } + ctx->curr = pscan; + ctx->bytes_remaining = nscan; + return value; +} + +void * +parser_string_get(PARSER_CONTEXT *ctx) +{ + u8 *pscan; + ulong nscan; + int value_length = -1; + void *value = NULL; + int i; + + if (!ctx) + return NULL; + pscan = ctx->curr; + nscan = ctx->bytes_remaining; + if (nscan == 0) + return NULL; + if (!pscan) + return NULL; + for (i = 0, value_length = -1; i < nscan; i++) + if (pscan[i] == '\0') { + value_length = i; + break; + } + if (value_length < 0) /* '\0' was not included in the length */ + value_length = nscan; + value = kmalloc(value_length + 1, GFP_KERNEL|__GFP_NORETRY); + if (value == NULL) + return NULL; + if (value_length > 0) + memcpy(value, pscan, value_length); + ((u8 *) (value))[value_length] = '\0'; + return value; +} diff --git a/drivers/staging/unisys/visorchipset/parser.h b/drivers/staging/unisys/visorchipset/parser.h new file mode 100644 index 00000000000..be85fd68c0c --- /dev/null +++ b/drivers/staging/unisys/visorchipset/parser.h @@ -0,0 +1,47 @@ +/* parser.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. + */ + +#ifndef __PARSER_H__ +#define __PARSER_H__ + +#include <linux/uuid.h> + +#include "uniklog.h" +#include "timskmod.h" +#include "channel.h" + +typedef enum { + PARSERSTRING_INITIATOR, + PARSERSTRING_TARGET, + PARSERSTRING_CONNECTION, + PARSERSTRING_NAME, +} PARSER_WHICH_STRING; + +typedef struct PARSER_CONTEXT_Tag PARSER_CONTEXT; + +PARSER_CONTEXT *parser_init(U64 addr, U32 bytes, BOOL isLocal, BOOL *tryAgain); +PARSER_CONTEXT *parser_init_byteStream(U64 addr, U32 bytes, BOOL isLocal, + BOOL *tryAgain); +void parser_param_start(PARSER_CONTEXT *ctx, PARSER_WHICH_STRING which_string); +void *parser_param_get(PARSER_CONTEXT *ctx, char *nam, int namesize); +void *parser_string_get(PARSER_CONTEXT *ctx); +uuid_le parser_id_get(PARSER_CONTEXT *ctx); +char *parser_simpleString_get(PARSER_CONTEXT *ctx); +void *parser_byteStream_get(PARSER_CONTEXT *ctx, ulong *nbytes); +void parser_done(PARSER_CONTEXT *ctx); + +#endif diff --git a/drivers/staging/unisys/visorchipset/testing.h b/drivers/staging/unisys/visorchipset/testing.h new file mode 100644 index 00000000000..015d502cbb1 --- /dev/null +++ b/drivers/staging/unisys/visorchipset/testing.h @@ -0,0 +1,42 @@ +/* testing.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. + */ + +#ifndef __VISORCHIPSET_TESTING_H__ +#define __VISORCHIPSET_TESTING_H__ + +#define VISORCHIPSET_TEST_PROC +#include <linux/uuid.h> +#include "globals.h" +#include "controlvmchannel.h" + +void test_produce_test_message(CONTROLVM_MESSAGE *msg, int isLocalTestAddr); +BOOL test_consume_test_message(CONTROLVM_MESSAGE *msg); +void test_manufacture_vnic_client_add(void *p); +void test_manufacture_vnic_client_add_phys(HOSTADDRESS addr); +void test_manufacture_preamble_messages(void); +void test_manufacture_device_attach(ulong busNo, ulong devNo); +void test_manufacture_device_add(ulong busNo, ulong devNo, uuid_le dataTypeGuid, + void *pChannel); +void test_manufacture_add_bus(ulong busNo, ulong maxDevices, + uuid_le id, u8 *name, BOOL isServer); +void test_manufacture_device_destroy(ulong busNo, ulong devNo); +void test_manufacture_bus_destroy(ulong busNo); +void test_manufacture_detach_externalPort(ulong switchNo, ulong externalPortNo); +void test_manufacture_detach_internalPort(ulong switchNo, ulong internalPortNo); +void test_cleanup(void); + +#endif diff --git a/drivers/staging/unisys/visorchipset/visorchipset.h b/drivers/staging/unisys/visorchipset/visorchipset.h new file mode 100644 index 00000000000..e01cc7207ba --- /dev/null +++ b/drivers/staging/unisys/visorchipset/visorchipset.h @@ -0,0 +1,309 @@ +/* visorchipset.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. + */ + +#ifndef __VISORCHIPSET_H__ +#define __VISORCHIPSET_H__ + +#include <linux/uuid.h> + +#include "timskmod.h" +#include "channel.h" +#include "controlvmchannel.h" +#include "parser.h" +#include "procobjecttree.h" +#include "vbusdeviceinfo.h" +#include "vbushelper.h" + +/** Describes the state from the perspective of which controlvm messages have + * been received for a bus or device. + */ +typedef struct { + U32 created:1; + U32 attached:1; + U32 configured:1; + U32 running:1; + /* Add new fields above. */ + /* Remaining bits in this 32-bit word are unused. */ +} VISORCHIPSET_STATE; + +typedef enum { + /** address is guest physical, but outside of the physical memory + * region that is controlled by the running OS (this is the normal + * address type for Supervisor channels) + */ + ADDRTYPE_localPhysical, + + /** address is guest physical, and withIN the confines of the + * physical memory controlled by the running OS. + */ + ADDRTYPE_localTest, +} VISORCHIPSET_ADDRESSTYPE; + +typedef enum { + CRASH_dev, + CRASH_bus, +} CRASH_OBJ_TYPE; + +/** Attributes for a particular Supervisor channel. + */ +typedef struct { + VISORCHIPSET_ADDRESSTYPE addrType; + HOSTADDRESS channelAddr; + struct InterruptInfo intr; + U64 nChannelBytes; + uuid_le channelTypeGuid; + uuid_le channelInstGuid; + +} VISORCHIPSET_CHANNEL_INFO; + +/** Attributes for a particular Supervisor device. + * Any visorchipset client can query these attributes using + * visorchipset_get_client_device_info() or + * visorchipset_get_server_device_info(). + */ +typedef struct { + struct list_head entry; + U32 busNo; + U32 devNo; + uuid_le devInstGuid; + VISORCHIPSET_STATE state; + VISORCHIPSET_CHANNEL_INFO chanInfo; + U32 Reserved1; /* CONTROLVM_ID */ + U64 Reserved2; + U32 switchNo; /* when devState.attached==1 */ + U32 internalPortNo; /* when devState.attached==1 */ + CONTROLVM_MESSAGE_HEADER pendingMsgHdr; /* CONTROLVM_MESSAGE */ + /** For private use by the bus driver */ + void *bus_driver_context; + +} VISORCHIPSET_DEVICE_INFO; + +static inline VISORCHIPSET_DEVICE_INFO * +finddevice(struct list_head *list, U32 busNo, U32 devNo) +{ + VISORCHIPSET_DEVICE_INFO *p; + + list_for_each_entry(p, list, entry) { + if (p->busNo == busNo && p->devNo == devNo) + return p; + } + return NULL; +} + +static inline void delbusdevices(struct list_head *list, U32 busNo) +{ + VISORCHIPSET_DEVICE_INFO *p, *tmp; + + list_for_each_entry_safe(p, tmp, list, entry) { + if (p->busNo == busNo) { + list_del(&p->entry); + kfree(p); + } + } +} + +/** Attributes for a particular Supervisor bus. + * (For a service partition acting as the server for buses/devices, there + * is a 1-to-1 relationship between busses and guest partitions.) + * Any visorchipset client can query these attributes using + * visorchipset_get_client_bus_info() or visorchipset_get_bus_info(). + */ +typedef struct { + struct list_head entry; + U32 busNo; + VISORCHIPSET_STATE state; + VISORCHIPSET_CHANNEL_INFO chanInfo; + uuid_le partitionGuid; + U64 partitionHandle; + U8 *name; /* UTF8 */ + U8 *description; /* UTF8 */ + U64 Reserved1; + U32 Reserved2; + MYPROCOBJECT *procObject; + struct { + U32 server:1; + /* Add new fields above. */ + /* Remaining bits in this 32-bit word are unused. */ + } flags; + CONTROLVM_MESSAGE_HEADER pendingMsgHdr; /* CONTROLVM MsgHdr */ + /** For private use by the bus driver */ + void *bus_driver_context; + U64 devNo; + +} VISORCHIPSET_BUS_INFO; + +static inline VISORCHIPSET_BUS_INFO * +findbus(struct list_head *list, U32 busNo) +{ + VISORCHIPSET_BUS_INFO *p; + + list_for_each_entry(p, list, entry) { + if (p->busNo == busNo) + return p; + } + return NULL; +} + +/** Attributes for a particular Supervisor switch. + */ +typedef struct { + U32 switchNo; + VISORCHIPSET_STATE state; + uuid_le switchTypeGuid; + U8 *authService1; + U8 *authService2; + U8 *authService3; + U8 *securityContext; + U64 Reserved; + U32 Reserved2; /* CONTROLVM_ID */ + struct device dev; + BOOL dev_exists; + CONTROLVM_MESSAGE_HEADER pendingMsgHdr; + +} VISORCHIPSET_SWITCH_INFO; + +/** Attributes for a particular Supervisor external port, which is connected + * to a specific switch. + */ +typedef struct { + U32 switchNo; + U32 externalPortNo; + VISORCHIPSET_STATE state; + uuid_le networkZoneGuid; + int pdPort; + U8 *ip; + U8 *ipNetmask; + U8 *ipBroadcast; + U8 *ipNetwork; + U8 *ipGateway; + U8 *ipDNS; + U64 Reserved1; + U32 Reserved2; /* CONTROLVM_ID */ + struct device dev; + BOOL dev_exists; + CONTROLVM_MESSAGE_HEADER pendingMsgHdr; + +} VISORCHIPSET_EXTERNALPORT_INFO; + +/** Attributes for a particular Supervisor internal port, which is how a + * device connects to a particular switch. + */ +typedef struct { + U32 switchNo; + U32 internalPortNo; + VISORCHIPSET_STATE state; + U32 busNo; /* valid only when state.attached == 1 */ + U32 devNo; /* valid only when state.attached == 1 */ + U64 Reserved1; + U32 Reserved2; /* CONTROLVM_ID */ + CONTROLVM_MESSAGE_HEADER pendingMsgHdr; + MYPROCOBJECT *procObject; + +} VISORCHIPSET_INTERNALPORT_INFO; + +/* These functions will be called from within visorchipset when certain + * events happen. (The implementation of these functions is outside of + * visorchipset.) + */ +typedef struct { + void (*bus_create)(ulong busNo); + void (*bus_destroy)(ulong busNo); + void (*device_create)(ulong busNo, ulong devNo); + void (*device_destroy)(ulong busNo, ulong devNo); + void (*device_pause)(ulong busNo, ulong devNo); + void (*device_resume)(ulong busNo, ulong devNo); + int (*get_channel_info)(uuid_le typeGuid, ulong *minSize, + ulong *maxSize); +} VISORCHIPSET_BUSDEV_NOTIFIERS; + +/* These functions live inside visorchipset, and will be called to indicate + * responses to specific events (by code outside of visorchipset). + * For now, the value for each response is simply either: + * 0 = it worked + * -1 = it failed + */ +typedef struct { + void (*bus_create)(ulong busNo, int response); + void (*bus_destroy)(ulong busNo, int response); + void (*device_create)(ulong busNo, ulong devNo, int response); + void (*device_destroy)(ulong busNo, ulong devNo, int response); + void (*device_pause)(ulong busNo, ulong devNo, int response); + void (*device_resume)(ulong busNo, ulong devNo, int response); +} VISORCHIPSET_BUSDEV_RESPONDERS; + +/** Register functions (in the bus driver) to get called by visorchipset + * whenever a bus or device appears for which this service partition is + * to be the server for. visorchipset will fill in <responders>, to + * indicate functions the bus driver should call to indicate message + * responses. + */ +void +visorchipset_register_busdev_client(VISORCHIPSET_BUSDEV_NOTIFIERS *notifiers, + VISORCHIPSET_BUSDEV_RESPONDERS *responders, + ULTRA_VBUS_DEVICEINFO *driverInfo); + +/** Register functions (in the bus driver) to get called by visorchipset + * whenever a bus or device appears for which this service partition is + * to be the client for. visorchipset will fill in <responders>, to + * indicate functions the bus driver should call to indicate message + * responses. + */ +void +visorchipset_register_busdev_server(VISORCHIPSET_BUSDEV_NOTIFIERS *notifiers, + VISORCHIPSET_BUSDEV_RESPONDERS *responders, + ULTRA_VBUS_DEVICEINFO *driverInfo); + +typedef void (*SPARREPORTEVENT_COMPLETE_FUNC) (CONTROLVM_MESSAGE *msg, + int status); + +void visorchipset_device_pause_response(ulong busNo, ulong devNo, int response); + +BOOL visorchipset_get_bus_info(ulong busNo, VISORCHIPSET_BUS_INFO *busInfo); +BOOL visorchipset_get_device_info(ulong busNo, ulong devNo, + VISORCHIPSET_DEVICE_INFO *devInfo); +BOOL visorchipset_get_switch_info(ulong switchNo, + VISORCHIPSET_SWITCH_INFO *switchInfo); +BOOL visorchipset_get_externalport_info(ulong switchNo, ulong externalPortNo, + VISORCHIPSET_EXTERNALPORT_INFO + *externalPortInfo); +BOOL visorchipset_set_bus_context(ulong busNo, void *context); +BOOL visorchipset_set_device_context(ulong busNo, ulong devNo, void *context); +int visorchipset_chipset_ready(void); +int visorchipset_chipset_selftest(void); +int visorchipset_chipset_notready(void); +void visorchipset_controlvm_respond_reportEvent(CONTROLVM_MESSAGE *msg, + void *payload); +void visorchipset_save_message(CONTROLVM_MESSAGE *msg, CRASH_OBJ_TYPE type); +void *visorchipset_cache_alloc(struct kmem_cache *pool, + BOOL ok_to_block, char *fn, int ln); +void visorchipset_cache_free(struct kmem_cache *pool, void *p, + char *fn, int ln); + +#if defined(TRANSMITFILE_DEBUG) || defined(DEBUG) +#define DBG_GETFILE_PAYLOAD(msg, controlvm_header) \ + LOGINF(msg, \ + (ulong)controlvm_header.PayloadVmOffset, \ + (ulong)controlvm_header.PayloadMaxBytes) +#define DBG_GETFILE(fmt, ...) LOGINF(fmt, ##__VA_ARGS__) +#define DBG_PUTFILE(fmt, ...) LOGINF(fmt, ##__VA_ARGS__) +#else +#define DBG_GETFILE_PAYLOAD(msg, controlvm_header) +#define DBG_GETFILE(fmt, ...) +#define DBG_PUTFILE(fmt, ...) +#endif + +#endif diff --git a/drivers/staging/unisys/visorchipset/visorchipset_main.c b/drivers/staging/unisys/visorchipset/visorchipset_main.c new file mode 100644 index 00000000000..0a602b9c130 --- /dev/null +++ b/drivers/staging/unisys/visorchipset/visorchipset_main.c @@ -0,0 +1,2955 @@ +/* visorchipset_main.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. + */ + +#include "globals.h" +#include "controlvm.h" +#include "visorchipset.h" +#include "procobjecttree.h" +#include "visorchannel.h" +#include "periodic_work.h" +#include "testing.h" +#include "file.h" +#include "parser.h" +#include "uniklog.h" +#include "uisutils.h" +#include "controlvmcompletionstatus.h" +#include "guestlinuxdebug.h" + +#include <linux/nls.h> +#include <linux/netdevice.h> +#include <linux/platform_device.h> +#include <linux/uuid.h> + +#define CURRENT_FILE_PC VISOR_CHIPSET_PC_visorchipset_main_c +#define TEST_VNIC_PHYSITF "eth0" /* physical network itf for + * vnic loopback test */ +#define TEST_VNIC_SWITCHNO 1 +#define TEST_VNIC_BUSNO 9 + +#define MAX_NAME_SIZE 128 +#define MAX_IP_SIZE 50 +#define MAXOUTSTANDINGCHANNELCOMMAND 256 +#define POLLJIFFIES_CONTROLVMCHANNEL_FAST 1 +#define POLLJIFFIES_CONTROLVMCHANNEL_SLOW 100 + +/* When the controlvm channel is idle for at least MIN_IDLE_SECONDS, +* we switch to slow polling mode. As soon as we get a controlvm +* message, we switch back to fast polling mode. +*/ +#define MIN_IDLE_SECONDS 10 +static ulong Poll_jiffies = POLLJIFFIES_CONTROLVMCHANNEL_FAST; +static ulong Most_recent_message_jiffies; /* when we got our last + * controlvm message */ +static inline char * +NONULLSTR(char *s) +{ + if (s) + return s; + else + return ""; +} + +static int serverregistered; +static int clientregistered; + +#define MAX_CHIPSET_EVENTS 2 +static U8 chipset_events[MAX_CHIPSET_EVENTS] = { 0, 0 }; + +static struct delayed_work Periodic_controlvm_work; +static struct workqueue_struct *Periodic_controlvm_workqueue; +static DEFINE_SEMAPHORE(NotifierLock); + +typedef struct { + CONTROLVM_MESSAGE message; + unsigned int crc; +} MESSAGE_ENVELOPE; + +static CONTROLVM_MESSAGE_HEADER g_DiagMsgHdr; +static CONTROLVM_MESSAGE_HEADER g_ChipSetMsgHdr; +static CONTROLVM_MESSAGE_HEADER g_DelDumpMsgHdr; +static const uuid_le UltraDiagPoolChannelProtocolGuid = + ULTRA_DIAG_POOL_CHANNEL_PROTOCOL_GUID; +/* 0xffffff is an invalid Bus/Device number */ +static ulong g_diagpoolBusNo = 0xffffff; +static ulong g_diagpoolDevNo = 0xffffff; +static CONTROLVM_MESSAGE_PACKET g_DeviceChangeStatePacket; + +/* Only VNIC and VHBA channels are sent to visorclientbus (aka + * "visorhackbus") + */ +#define FOR_VISORHACKBUS(channel_type_guid) \ + (((uuid_le_cmp(channel_type_guid, UltraVnicChannelProtocolGuid) == 0)\ + || (uuid_le_cmp(channel_type_guid, UltraVhbaChannelProtocolGuid) == 0))) +#define FOR_VISORBUS(channel_type_guid) (!(FOR_VISORHACKBUS(channel_type_guid))) + +#define is_diagpool_channel(channel_type_guid) \ + (uuid_le_cmp(channel_type_guid, UltraDiagPoolChannelProtocolGuid) == 0) + +typedef enum { + PARTPROP_invalid, + PARTPROP_name, + PARTPROP_description, + PARTPROP_handle, + PARTPROP_busNumber, + /* add new properties above, but don't forget to change + * InitPartitionProperties() and show_partition_property() also... + */ + PARTPROP_last +} PARTITION_property; +static const char *PartitionTypeNames[] = { "partition", NULL }; + +static char *PartitionPropertyNames[PARTPROP_last + 1]; +static void +InitPartitionProperties(void) +{ + char **p = PartitionPropertyNames; + p[PARTPROP_invalid] = ""; + p[PARTPROP_name] = "name"; + p[PARTPROP_description] = "description"; + p[PARTPROP_handle] = "handle"; + p[PARTPROP_busNumber] = "busNumber"; + p[PARTPROP_last] = NULL; +} + +typedef enum { + CTLVMPROP_invalid, + CTLVMPROP_physAddr, + CTLVMPROP_controlChannelAddr, + CTLVMPROP_controlChannelBytes, + CTLVMPROP_sparBootPart, + CTLVMPROP_sparStoragePart, + CTLVMPROP_livedumpLength, + CTLVMPROP_livedumpCrc32, + /* add new properties above, but don't forget to change + * InitControlVmProperties() show_controlvm_property() also... + */ + CTLVMPROP_last +} CONTROLVM_property; + +static const char *ControlVmTypeNames[] = { "controlvm", NULL }; + +static char *ControlVmPropertyNames[CTLVMPROP_last + 1]; +static void +InitControlVmProperties(void) +{ + char **p = ControlVmPropertyNames; + p[CTLVMPROP_invalid] = ""; + p[CTLVMPROP_physAddr] = "physAddr"; + p[CTLVMPROP_controlChannelAddr] = "controlChannelAddr"; + p[CTLVMPROP_controlChannelBytes] = "controlChannelBytes"; + p[CTLVMPROP_sparBootPart] = "spar_boot_part"; + p[CTLVMPROP_sparStoragePart] = "spar_storage_part"; + p[CTLVMPROP_livedumpLength] = "livedumpLength"; + p[CTLVMPROP_livedumpCrc32] = "livedumpCrc32"; + p[CTLVMPROP_last] = NULL; +} + +static MYPROCOBJECT *ControlVmObject; +static MYPROCTYPE *PartitionType; +static MYPROCTYPE *ControlVmType; + +#define VISORCHIPSET_DIAG_PROC_ENTRY_FN "diagdump" +static struct proc_dir_entry *diag_proc_dir; + +#define VISORCHIPSET_CHIPSET_PROC_ENTRY_FN "chipsetready" +static struct proc_dir_entry *chipset_proc_dir; + +#define VISORCHIPSET_PARAHOTPLUG_PROC_ENTRY_FN "parahotplug" +static struct proc_dir_entry *parahotplug_proc_dir; + +static LIST_HEAD(BusInfoList); +static LIST_HEAD(DevInfoList); + +static struct proc_dir_entry *ProcDir; +static VISORCHANNEL *ControlVm_channel; + +static ssize_t visorchipset_proc_read_writeonly(struct file *file, + char __user *buf, + size_t len, loff_t *offset); +static ssize_t proc_read_installer(struct file *file, char __user *buf, + size_t len, loff_t *offset); +static ssize_t proc_write_installer(struct file *file, + const char __user *buffer, + size_t count, loff_t *ppos); +static ssize_t proc_read_toolaction(struct file *file, char __user *buf, + size_t len, loff_t *offset); +static ssize_t proc_write_toolaction(struct file *file, + const char __user *buffer, + size_t count, loff_t *ppos); +static ssize_t proc_read_bootToTool(struct file *file, char __user *buf, + size_t len, loff_t *offset); +static ssize_t proc_write_bootToTool(struct file *file, + const char __user *buffer, + size_t count, loff_t *ppos); +static const struct file_operations proc_installer_fops = { + .read = proc_read_installer, + .write = proc_write_installer, +}; + +static const struct file_operations proc_toolaction_fops = { + .read = proc_read_toolaction, + .write = proc_write_toolaction, +}; + +static const struct file_operations proc_bootToTool_fops = { + .read = proc_read_bootToTool, + .write = proc_write_bootToTool, +}; + +typedef struct { + U8 __iomem *ptr; /* pointer to base address of payload pool */ + U64 offset; /* offset from beginning of controlvm + * channel to beginning of payload * pool */ + U32 bytes; /* number of bytes in payload pool */ +} CONTROLVM_PAYLOAD_INFO; + +/* Manages the request payload in the controlvm channel */ +static CONTROLVM_PAYLOAD_INFO ControlVm_payload_info; + +static pCHANNEL_HEADER Test_Vnic_channel; + +typedef struct { + CONTROLVM_MESSAGE_HEADER Dumpcapture_header; + CONTROLVM_MESSAGE_HEADER Gettextdump_header; + CONTROLVM_MESSAGE_HEADER Dumpcomplete_header; + BOOL Gettextdump_outstanding; + u32 crc32; + ulong length; + atomic_t buffers_in_use; + ulong destination; +} LIVEDUMP_INFO; +/* Manages the info for a CONTROLVM_DUMP_CAPTURESTATE / + * CONTROLVM_DUMP_GETTEXTDUMP / CONTROLVM_DUMP_COMPLETE conversation. + */ +static LIVEDUMP_INFO LiveDump_info; + +/* The following globals are used to handle the scenario where we are unable to + * offload the payload from a controlvm message due to memory requirements. In + * this scenario, we simply stash the controlvm message, then attempt to + * process it again the next time controlvm_periodic_work() runs. + */ +static CONTROLVM_MESSAGE ControlVm_Pending_Msg; +static BOOL ControlVm_Pending_Msg_Valid = FALSE; + +/* Pool of struct putfile_buffer_entry, for keeping track of pending (incoming) + * TRANSMIT_FILE PutFile payloads. + */ +static struct kmem_cache *Putfile_buffer_list_pool; +static const char Putfile_buffer_list_pool_name[] = + "controlvm_putfile_buffer_list_pool"; + +/* This identifies a data buffer that has been received via a controlvm messages + * in a remote --> local CONTROLVM_TRANSMIT_FILE conversation. + */ +struct putfile_buffer_entry { + struct list_head next; /* putfile_buffer_entry list */ + PARSER_CONTEXT *parser_ctx; /* points to buffer containing input data */ +}; + +/* List of struct putfile_request *, via next_putfile_request member. + * Each entry in this list identifies an outstanding TRANSMIT_FILE + * conversation. + */ +static LIST_HEAD(Putfile_request_list); + +/* This describes a buffer and its current state of transfer (e.g., how many + * bytes have already been supplied as putfile data, and how many bytes are + * remaining) for a putfile_request. + */ +struct putfile_active_buffer { + /* a payload from a controlvm message, containing a file data buffer */ + PARSER_CONTEXT *parser_ctx; + /* points within data area of parser_ctx to next byte of data */ + u8 *pnext; + /* # bytes left from <pnext> to the end of this data buffer */ + size_t bytes_remaining; +}; + +#define PUTFILE_REQUEST_SIG 0x0906101302281211 +/* This identifies a single remote --> local CONTROLVM_TRANSMIT_FILE + * conversation. Structs of this type are dynamically linked into + * <Putfile_request_list>. + */ +struct putfile_request { + u64 sig; /* PUTFILE_REQUEST_SIG */ + + /* header from original TransmitFile request */ + CONTROLVM_MESSAGE_HEADER controlvm_header; + u64 file_request_number; /* from original TransmitFile request */ + + /* link to next struct putfile_request */ + struct list_head next_putfile_request; + + /* most-recent sequence number supplied via a controlvm message */ + u64 data_sequence_number; + + /* head of putfile_buffer_entry list, which describes the data to be + * supplied as putfile data; + * - this list is added to when controlvm messages come in that supply + * file data + * - this list is removed from via the hotplug program that is actually + * consuming these buffers to write as file data */ + struct list_head input_buffer_list; + spinlock_t req_list_lock; /* lock for input_buffer_list */ + + /* waiters for input_buffer_list to go non-empty */ + wait_queue_head_t input_buffer_wq; + + /* data not yet read within current putfile_buffer_entry */ + struct putfile_active_buffer active_buf; + + /* <0 = failed, 0 = in-progress, >0 = successful; */ + /* note that this must be set with req_list_lock, and if you set <0, */ + /* it is your responsibility to also free up all of the other objects */ + /* in this struct (like input_buffer_list, active_buf.parser_ctx) */ + /* before releasing the lock */ + int completion_status; +}; + +static atomic_t Visorchipset_cache_buffers_in_use = ATOMIC_INIT(0); + +struct parahotplug_request { + struct list_head list; + int id; + unsigned long expiration; + CONTROLVM_MESSAGE msg; +}; + +static LIST_HEAD(Parahotplug_request_list); +static DEFINE_SPINLOCK(Parahotplug_request_list_lock); /* lock for above */ +static void parahotplug_process_list(void); + +/* Manages the info for a CONTROLVM_DUMP_CAPTURESTATE / + * CONTROLVM_REPORTEVENT. + */ +static VISORCHIPSET_BUSDEV_NOTIFIERS BusDev_Server_Notifiers; +static VISORCHIPSET_BUSDEV_NOTIFIERS BusDev_Client_Notifiers; + +static void bus_create_response(ulong busNo, int response); +static void bus_destroy_response(ulong busNo, int response); +static void device_create_response(ulong busNo, ulong devNo, int response); +static void device_destroy_response(ulong busNo, ulong devNo, int response); +static void device_resume_response(ulong busNo, ulong devNo, int response); + +static VISORCHIPSET_BUSDEV_RESPONDERS BusDev_Responders = { + .bus_create = bus_create_response, + .bus_destroy = bus_destroy_response, + .device_create = device_create_response, + .device_destroy = device_destroy_response, + .device_pause = visorchipset_device_pause_response, + .device_resume = device_resume_response, +}; + +/* info for /dev/visorchipset */ +static dev_t MajorDev = -1; /**< indicates major num for device */ + +/* /sys/devices/platform/visorchipset */ +static struct platform_device Visorchipset_platform_device = { + .name = "visorchipset", + .id = -1, +}; + +/* Function prototypes */ +static void controlvm_respond(CONTROLVM_MESSAGE_HEADER *msgHdr, int response); +static void controlvm_respond_chipset_init(CONTROLVM_MESSAGE_HEADER *msgHdr, + int response, + ULTRA_CHIPSET_FEATURE features); +static void controlvm_respond_physdev_changestate(CONTROLVM_MESSAGE_HEADER * + msgHdr, int response, + ULTRA_SEGMENT_STATE state); + +static void +show_partition_property(struct seq_file *f, void *ctx, int property) +{ + VISORCHIPSET_BUS_INFO *info = (VISORCHIPSET_BUS_INFO *) (ctx); + + switch (property) { + case PARTPROP_name: + seq_printf(f, "%s\n", NONULLSTR(info->name)); + break; + case PARTPROP_description: + seq_printf(f, "%s\n", NONULLSTR(info->description)); + break; + case PARTPROP_handle: + seq_printf(f, "0x%-16.16Lx\n", info->partitionHandle); + break; + case PARTPROP_busNumber: + seq_printf(f, "%d\n", info->busNo); + break; + default: + seq_printf(f, "(%d??)\n", property); + break; + } +} + +static void +show_controlvm_property(struct seq_file *f, void *ctx, int property) +{ + /* Note: ctx is not needed since we only have 1 controlvm channel */ + switch (property) { + case CTLVMPROP_physAddr: + if (ControlVm_channel == NULL) + seq_puts(f, "0x0\n"); + else + seq_printf(f, "0x%-16.16Lx\n", + visorchannel_get_physaddr + (ControlVm_channel)); + break; + case CTLVMPROP_controlChannelAddr: + if (ControlVm_channel == NULL) + seq_puts(f, "0x0\n"); + else { + GUEST_PHYSICAL_ADDRESS addr = 0; + visorchannel_read(ControlVm_channel, + offsetof + (ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + gpControlChannel), &addr, + sizeof(addr)); + seq_printf(f, "0x%-16.16Lx\n", (u64) (addr)); + } + break; + case CTLVMPROP_controlChannelBytes: + if (ControlVm_channel == NULL) + seq_puts(f, "0x0\n"); + else { + U32 bytes = 0; + visorchannel_read(ControlVm_channel, + offsetof + (ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + ControlChannelBytes), &bytes, + sizeof(bytes)); + seq_printf(f, "%lu\n", (ulong) (bytes)); + } + break; + case CTLVMPROP_sparBootPart: + seq_puts(f, "0:0:0:0/1\n"); + break; + case CTLVMPROP_sparStoragePart: + seq_puts(f, "0:0:0:0/2\n"); + break; + case CTLVMPROP_livedumpLength: + seq_printf(f, "%lu\n", LiveDump_info.length); + break; + case CTLVMPROP_livedumpCrc32: + seq_printf(f, "%lu\n", (ulong) LiveDump_info.crc32); + break; + default: + seq_printf(f, "(%d??)\n", property); + break; + } +} + +static void +proc_Init(void) +{ + if (ProcDir == NULL) { + ProcDir = proc_mkdir(MYDRVNAME, NULL); + if (ProcDir == NULL) { + LOGERR("failed to create /proc directory %s", + MYDRVNAME); + POSTCODE_LINUX_2(CHIPSET_INIT_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + } + } +} + +static void +proc_DeInit(void) +{ + if (ProcDir != NULL) + remove_proc_entry(MYDRVNAME, NULL); + ProcDir = NULL; +} + +#if 0 +static void +testUnicode(void) +{ + wchar_t unicodeString[] = { 'a', 'b', 'c', 0 }; + char s[sizeof(unicodeString) * NLS_MAX_CHARSET_SIZE]; + wchar_t unicode2[99]; + + /* NOTE: Either due to a bug, or feature I don't understand, the + * kernel utf8_mbstowcs() and utf_wcstombs() do NOT copy the + * trailed NUL byte!! REALLY!!!!! Arrrrgggghhhhh + */ + + LOGINF("sizeof(wchar_t) = %d", sizeof(wchar_t)); + LOGINF("utf8_wcstombs=%d", + chrs = utf8_wcstombs(s, unicodeString, sizeof(s))); + if (chrs >= 0) + s[chrs] = '\0'; /* GRRRRRRRR */ + LOGINF("s='%s'", s); + LOGINF("utf8_mbstowcs=%d", chrs = utf8_mbstowcs(unicode2, s, 100)); + if (chrs >= 0) + unicode2[chrs] = 0; /* GRRRRRRRR */ + if (memcmp(unicodeString, unicode2, sizeof(unicodeString)) == 0) + LOGINF("strings match... good"); + else + LOGINF("strings did not match!!"); +} +#endif + +static void +busInfo_clear(void *v) +{ + VISORCHIPSET_BUS_INFO *p = (VISORCHIPSET_BUS_INFO *) (v); + + if (p->procObject) { + visor_proc_DestroyObject(p->procObject); + p->procObject = NULL; + } + kfree(p->name); + p->name = NULL; + + kfree(p->description); + p->description = NULL; + + p->state.created = 0; + memset(p, 0, sizeof(VISORCHIPSET_BUS_INFO)); +} + +static void +devInfo_clear(void *v) +{ + VISORCHIPSET_DEVICE_INFO *p = (VISORCHIPSET_DEVICE_INFO *) (v); + p->state.created = 0; + memset(p, 0, sizeof(VISORCHIPSET_DEVICE_INFO)); +} + +static U8 +check_chipset_events(void) +{ + int i; + U8 send_msg = 1; + /* Check events to determine if response should be sent */ + for (i = 0; i < MAX_CHIPSET_EVENTS; i++) + send_msg &= chipset_events[i]; + return send_msg; +} + +static void +clear_chipset_events(void) +{ + int i; + /* Clear chipset_events */ + for (i = 0; i < MAX_CHIPSET_EVENTS; i++) + chipset_events[i] = 0; +} + +void +visorchipset_register_busdev_server(VISORCHIPSET_BUSDEV_NOTIFIERS *notifiers, + VISORCHIPSET_BUSDEV_RESPONDERS *responders, + ULTRA_VBUS_DEVICEINFO *driverInfo) +{ + LOCKSEM_UNINTERRUPTIBLE(&NotifierLock); + if (notifiers == NULL) { + memset(&BusDev_Server_Notifiers, 0, + sizeof(BusDev_Server_Notifiers)); + serverregistered = 0; /* clear flag */ + } else { + BusDev_Server_Notifiers = *notifiers; + serverregistered = 1; /* set flag */ + } + if (responders) + *responders = BusDev_Responders; + if (driverInfo) + BusDeviceInfo_Init(driverInfo, "chipset", "visorchipset", + VERSION, NULL, __DATE__, __TIME__); + + UNLOCKSEM(&NotifierLock); +} +EXPORT_SYMBOL_GPL(visorchipset_register_busdev_server); + +void +visorchipset_register_busdev_client(VISORCHIPSET_BUSDEV_NOTIFIERS *notifiers, + VISORCHIPSET_BUSDEV_RESPONDERS *responders, + ULTRA_VBUS_DEVICEINFO *driverInfo) +{ + LOCKSEM_UNINTERRUPTIBLE(&NotifierLock); + if (notifiers == NULL) { + memset(&BusDev_Client_Notifiers, 0, + sizeof(BusDev_Client_Notifiers)); + clientregistered = 0; /* clear flag */ + } else { + BusDev_Client_Notifiers = *notifiers; + clientregistered = 1; /* set flag */ + } + if (responders) + *responders = BusDev_Responders; + if (driverInfo) + BusDeviceInfo_Init(driverInfo, "chipset(bolts)", "visorchipset", + VERSION, NULL, __DATE__, __TIME__); + UNLOCKSEM(&NotifierLock); +} +EXPORT_SYMBOL_GPL(visorchipset_register_busdev_client); + +static void +cleanup_controlvm_structures(void) +{ + VISORCHIPSET_BUS_INFO *bi, *tmp_bi; + VISORCHIPSET_DEVICE_INFO *di, *tmp_di; + + list_for_each_entry_safe(bi, tmp_bi, &BusInfoList, entry) { + busInfo_clear(bi); + list_del(&bi->entry); + kfree(bi); + } + + list_for_each_entry_safe(di, tmp_di, &DevInfoList, entry) { + devInfo_clear(di); + list_del(&di->entry); + kfree(di); + } +} + +static void +chipset_init(CONTROLVM_MESSAGE *inmsg) +{ + static int chipset_inited; + ULTRA_CHIPSET_FEATURE features = 0; + int rc = CONTROLVM_RESP_SUCCESS; + + POSTCODE_LINUX_2(CHIPSET_INIT_ENTRY_PC, POSTCODE_SEVERITY_INFO); + if (chipset_inited) { + LOGERR("CONTROLVM_CHIPSET_INIT Failed: Already Done."); + rc = -CONTROLVM_RESP_ERROR_ALREADY_DONE; + goto Away; + } + chipset_inited = 1; + POSTCODE_LINUX_2(CHIPSET_INIT_EXIT_PC, POSTCODE_SEVERITY_INFO); + + /* Set features to indicate we support parahotplug (if Command + * also supports it). */ + features = + inmsg->cmd.initChipset. + features & ULTRA_CHIPSET_FEATURE_PARA_HOTPLUG; + + /* Set the "reply" bit so Command knows this is a + * features-aware driver. */ + features |= ULTRA_CHIPSET_FEATURE_REPLY; + +Away: + if (rc < 0) + cleanup_controlvm_structures(); + if (inmsg->hdr.Flags.responseExpected) + controlvm_respond_chipset_init(&inmsg->hdr, rc, features); +} + +static void +controlvm_init_response(CONTROLVM_MESSAGE *msg, + CONTROLVM_MESSAGE_HEADER *msgHdr, int response) +{ + memset(msg, 0, sizeof(CONTROLVM_MESSAGE)); + memcpy(&msg->hdr, msgHdr, sizeof(CONTROLVM_MESSAGE_HEADER)); + msg->hdr.PayloadBytes = 0; + msg->hdr.PayloadVmOffset = 0; + msg->hdr.PayloadMaxBytes = 0; + if (response < 0) { + msg->hdr.Flags.failed = 1; + msg->hdr.CompletionStatus = (U32) (-response); + } +} + +static void +controlvm_respond(CONTROLVM_MESSAGE_HEADER *msgHdr, int response) +{ + CONTROLVM_MESSAGE outmsg; + if (!ControlVm_channel) + return; + controlvm_init_response(&outmsg, msgHdr, response); + /* For DiagPool channel DEVICE_CHANGESTATE, we need to send + * back the deviceChangeState structure in the packet. */ + if (msgHdr->Id == CONTROLVM_DEVICE_CHANGESTATE + && g_DeviceChangeStatePacket.deviceChangeState.busNo == + g_diagpoolBusNo + && g_DeviceChangeStatePacket.deviceChangeState.devNo == + g_diagpoolDevNo) + outmsg.cmd = g_DeviceChangeStatePacket; + if (outmsg.hdr.Flags.testMessage == 1) { + LOGINF("%s controlvm_msg=0x%x response=%d for test message", + __func__, outmsg.hdr.Id, response); + return; + } + if (!visorchannel_signalinsert(ControlVm_channel, + CONTROLVM_QUEUE_REQUEST, &outmsg)) { + LOGERR("signalinsert failed!"); + return; + } +} + +static void +controlvm_respond_chipset_init(CONTROLVM_MESSAGE_HEADER *msgHdr, int response, + ULTRA_CHIPSET_FEATURE features) +{ + CONTROLVM_MESSAGE outmsg; + if (!ControlVm_channel) + return; + controlvm_init_response(&outmsg, msgHdr, response); + outmsg.cmd.initChipset.features = features; + if (!visorchannel_signalinsert(ControlVm_channel, + CONTROLVM_QUEUE_REQUEST, &outmsg)) { + LOGERR("signalinsert failed!"); + return; + } +} + +static void +controlvm_respond_physdev_changestate(CONTROLVM_MESSAGE_HEADER *msgHdr, + int response, ULTRA_SEGMENT_STATE state) +{ + CONTROLVM_MESSAGE outmsg; + if (!ControlVm_channel) + return; + controlvm_init_response(&outmsg, msgHdr, response); + outmsg.cmd.deviceChangeState.state = state; + outmsg.cmd.deviceChangeState.flags.physicalDevice = 1; + if (!visorchannel_signalinsert(ControlVm_channel, + CONTROLVM_QUEUE_REQUEST, &outmsg)) { + LOGERR("signalinsert failed!"); + return; + } +} + +void +visorchipset_save_message(CONTROLVM_MESSAGE *msg, CRASH_OBJ_TYPE type) +{ + U32 localSavedCrashMsgOffset; + U16 localSavedCrashMsgCount; + + /* get saved message count */ + if (visorchannel_read(ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + SavedCrashMsgCount), + &localSavedCrashMsgCount, sizeof(U16)) < 0) { + LOGERR("failed to get Saved Message Count"); + POSTCODE_LINUX_2(CRASH_DEV_CTRL_RD_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + + if (localSavedCrashMsgCount != CONTROLVM_CRASHMSG_MAX) { + LOGERR("Saved Message Count incorrect %d", + localSavedCrashMsgCount); + POSTCODE_LINUX_3(CRASH_DEV_COUNT_FAILURE_PC, + localSavedCrashMsgCount, + POSTCODE_SEVERITY_ERR); + return; + } + + /* get saved crash message offset */ + if (visorchannel_read(ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + SavedCrashMsgOffset), + &localSavedCrashMsgOffset, sizeof(U32)) < 0) { + LOGERR("failed to get Saved Message Offset"); + POSTCODE_LINUX_2(CRASH_DEV_CTRL_RD_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + + if (type == CRASH_bus) { + if (visorchannel_write(ControlVm_channel, + localSavedCrashMsgOffset, + msg, sizeof(CONTROLVM_MESSAGE)) < 0) { + LOGERR("SAVE_MSG_BUS_FAILURE: Failed to write CrashCreateBusMsg!"); + POSTCODE_LINUX_2(SAVE_MSG_BUS_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + } else { + if (visorchannel_write(ControlVm_channel, + localSavedCrashMsgOffset + + sizeof(CONTROLVM_MESSAGE), msg, + sizeof(CONTROLVM_MESSAGE)) < 0) { + LOGERR("SAVE_MSG_DEV_FAILURE: Failed to write CrashCreateDevMsg!"); + POSTCODE_LINUX_2(SAVE_MSG_DEV_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + } +} +EXPORT_SYMBOL_GPL(visorchipset_save_message); + +static void +bus_responder(CONTROLVM_ID cmdId, ulong busNo, int response) +{ + VISORCHIPSET_BUS_INFO *p = NULL; + BOOL need_clear = FALSE; + + p = findbus(&BusInfoList, busNo); + if (!p) { + LOGERR("internal error busNo=%lu", busNo); + return; + } + if (response < 0) { + if ((cmdId == CONTROLVM_BUS_CREATE) && + (response != (-CONTROLVM_RESP_ERROR_ALREADY_DONE))) + /* undo the row we just created... */ + delbusdevices(&DevInfoList, busNo); + } else { + if (cmdId == CONTROLVM_BUS_CREATE) + p->state.created = 1; + if (cmdId == CONTROLVM_BUS_DESTROY) + need_clear = TRUE; + } + + if (p->pendingMsgHdr.Id == CONTROLVM_INVALID) { + LOGERR("bus_responder no pending msg"); + return; /* no controlvm response needed */ + } + if (p->pendingMsgHdr.Id != (U32) cmdId) { + LOGERR("expected=%d, found=%d", cmdId, p->pendingMsgHdr.Id); + return; + } + controlvm_respond(&p->pendingMsgHdr, response); + p->pendingMsgHdr.Id = CONTROLVM_INVALID; + if (need_clear) { + busInfo_clear(p); + delbusdevices(&DevInfoList, busNo); + } +} + +static void +device_changestate_responder(CONTROLVM_ID cmdId, + ulong busNo, ulong devNo, int response, + ULTRA_SEGMENT_STATE responseState) +{ + VISORCHIPSET_DEVICE_INFO *p = NULL; + CONTROLVM_MESSAGE outmsg; + + if (!ControlVm_channel) + return; + + p = finddevice(&DevInfoList, busNo, devNo); + if (!p) { + LOGERR("internal error; busNo=%lu, devNo=%lu", busNo, devNo); + return; + } + if (p->pendingMsgHdr.Id == CONTROLVM_INVALID) { + LOGERR("device_responder no pending msg"); + return; /* no controlvm response needed */ + } + if (p->pendingMsgHdr.Id != cmdId) { + LOGERR("expected=%d, found=%d", cmdId, p->pendingMsgHdr.Id); + return; + } + + controlvm_init_response(&outmsg, &p->pendingMsgHdr, response); + + outmsg.cmd.deviceChangeState.busNo = busNo; + outmsg.cmd.deviceChangeState.devNo = devNo; + outmsg.cmd.deviceChangeState.state = responseState; + + if (!visorchannel_signalinsert(ControlVm_channel, + CONTROLVM_QUEUE_REQUEST, &outmsg)) { + LOGERR("signalinsert failed!"); + return; + } + + p->pendingMsgHdr.Id = CONTROLVM_INVALID; +} + +static void +device_responder(CONTROLVM_ID cmdId, ulong busNo, ulong devNo, int response) +{ + VISORCHIPSET_DEVICE_INFO *p = NULL; + BOOL need_clear = FALSE; + + p = finddevice(&DevInfoList, busNo, devNo); + if (!p) { + LOGERR("internal error; busNo=%lu, devNo=%lu", busNo, devNo); + return; + } + if (response >= 0) { + if (cmdId == CONTROLVM_DEVICE_CREATE) + p->state.created = 1; + if (cmdId == CONTROLVM_DEVICE_DESTROY) + need_clear = TRUE; + } + + if (p->pendingMsgHdr.Id == CONTROLVM_INVALID) { + LOGERR("device_responder no pending msg"); + return; /* no controlvm response needed */ + } + if (p->pendingMsgHdr.Id != (U32) cmdId) { + LOGERR("expected=%d, found=%d", cmdId, p->pendingMsgHdr.Id); + return; + } + controlvm_respond(&p->pendingMsgHdr, response); + p->pendingMsgHdr.Id = CONTROLVM_INVALID; + if (need_clear) + devInfo_clear(p); +} + +static void +bus_epilog(U32 busNo, + U32 cmd, CONTROLVM_MESSAGE_HEADER *msgHdr, + int response, BOOL needResponse) +{ + BOOL notified = FALSE; + + VISORCHIPSET_BUS_INFO *pBusInfo = findbus(&BusInfoList, busNo); + + if (!pBusInfo) { + LOGERR("HUH? bad busNo=%d", busNo); + return; + } + if (needResponse) { + memcpy(&pBusInfo->pendingMsgHdr, msgHdr, + sizeof(CONTROLVM_MESSAGE_HEADER)); + } else + pBusInfo->pendingMsgHdr.Id = CONTROLVM_INVALID; + + LOCKSEM_UNINTERRUPTIBLE(&NotifierLock); + if (response == CONTROLVM_RESP_SUCCESS) { + switch (cmd) { + case CONTROLVM_BUS_CREATE: + /* We can't tell from the bus_create + * information which of our 2 bus flavors the + * devices on this bus will ultimately end up. + * FORTUNATELY, it turns out it is harmless to + * send the bus_create to both of them. We can + * narrow things down a little bit, though, + * because we know: - BusDev_Server can handle + * either server or client devices + * - BusDev_Client can handle ONLY client + * devices */ + if (BusDev_Server_Notifiers.bus_create) { + (*BusDev_Server_Notifiers.bus_create) (busNo); + notified = TRUE; + } + if ((!pBusInfo->flags.server) /*client */ && + BusDev_Client_Notifiers.bus_create) { + (*BusDev_Client_Notifiers.bus_create) (busNo); + notified = TRUE; + } + break; + case CONTROLVM_BUS_DESTROY: + if (BusDev_Server_Notifiers.bus_destroy) { + (*BusDev_Server_Notifiers.bus_destroy) (busNo); + notified = TRUE; + } + if ((!pBusInfo->flags.server) /*client */ && + BusDev_Client_Notifiers.bus_destroy) { + (*BusDev_Client_Notifiers.bus_destroy) (busNo); + notified = TRUE; + } + break; + } + } + if (notified) + /* The callback function just called above is responsible + * for calling the appropriate VISORCHIPSET_BUSDEV_RESPONDERS + * function, which will call bus_responder() + */ + ; + else + bus_responder(cmd, busNo, response); + UNLOCKSEM(&NotifierLock); +} + +static void +device_epilog(U32 busNo, U32 devNo, ULTRA_SEGMENT_STATE state, U32 cmd, + CONTROLVM_MESSAGE_HEADER *msgHdr, int response, + BOOL needResponse, BOOL for_visorbus) +{ + VISORCHIPSET_BUSDEV_NOTIFIERS *notifiers = NULL; + BOOL notified = FALSE; + + VISORCHIPSET_DEVICE_INFO *pDevInfo = + finddevice(&DevInfoList, busNo, devNo); + char *envp[] = { + "SPARSP_DIAGPOOL_PAUSED_STATE = 1", + NULL + }; + + if (!pDevInfo) { + LOGERR("HUH? bad busNo=%d, devNo=%d", busNo, devNo); + return; + } + if (for_visorbus) + notifiers = &BusDev_Server_Notifiers; + else + notifiers = &BusDev_Client_Notifiers; + if (needResponse) { + memcpy(&pDevInfo->pendingMsgHdr, msgHdr, + sizeof(CONTROLVM_MESSAGE_HEADER)); + } else + pDevInfo->pendingMsgHdr.Id = CONTROLVM_INVALID; + + LOCKSEM_UNINTERRUPTIBLE(&NotifierLock); + if (response >= 0) { + switch (cmd) { + case CONTROLVM_DEVICE_CREATE: + if (notifiers->device_create) { + (*notifiers->device_create) (busNo, devNo); + notified = TRUE; + } + break; + case CONTROLVM_DEVICE_CHANGESTATE: + /* ServerReady / ServerRunning / SegmentStateRunning */ + if (state.Alive == SegmentStateRunning.Alive && + state.Operating == SegmentStateRunning.Operating) { + if (notifiers->device_resume) { + (*notifiers->device_resume) (busNo, + devNo); + notified = TRUE; + } + } + /* ServerNotReady / ServerLost / SegmentStateStandby */ + else if (state.Alive == SegmentStateStandby.Alive && + state.Operating == + SegmentStateStandby.Operating) { + /* technically this is standby case + * where server is lost + */ + if (notifiers->device_pause) { + (*notifiers->device_pause) (busNo, + devNo); + notified = TRUE; + } + } else if (state.Alive == SegmentStatePaused.Alive && + state.Operating == + SegmentStatePaused.Operating) { + /* this is lite pause where channel is + * still valid just 'pause' of it + */ + if (busNo == g_diagpoolBusNo + && devNo == g_diagpoolDevNo) { + LOGINF("DEVICE_CHANGESTATE(DiagpoolChannel busNo=%d devNo=%d is pausing...)", + busNo, devNo); + /* this will trigger the + * diag_shutdown.sh script in + * the visorchipset hotplug */ + kobject_uevent_env + (&Visorchipset_platform_device.dev. + kobj, KOBJ_ONLINE, envp); + } + } + break; + case CONTROLVM_DEVICE_DESTROY: + if (notifiers->device_destroy) { + (*notifiers->device_destroy) (busNo, devNo); + notified = TRUE; + } + break; + } + } + if (notified) + /* The callback function just called above is responsible + * for calling the appropriate VISORCHIPSET_BUSDEV_RESPONDERS + * function, which will call device_responder() + */ + ; + else + device_responder(cmd, busNo, devNo, response); + UNLOCKSEM(&NotifierLock); +} + +static void +bus_create(CONTROLVM_MESSAGE *inmsg) +{ + CONTROLVM_MESSAGE_PACKET *cmd = &inmsg->cmd; + ulong busNo = cmd->createBus.busNo; + int rc = CONTROLVM_RESP_SUCCESS; + VISORCHIPSET_BUS_INFO *pBusInfo = NULL; + + + pBusInfo = findbus(&BusInfoList, busNo); + if (pBusInfo && (pBusInfo->state.created == 1)) { + LOGERR("CONTROLVM_BUS_CREATE Failed: bus %lu already exists", + busNo); + POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_ALREADY_DONE; + goto Away; + } + pBusInfo = kzalloc(sizeof(VISORCHIPSET_BUS_INFO), GFP_KERNEL); + if (pBusInfo == NULL) { + LOGERR("CONTROLVM_BUS_CREATE Failed: bus %lu kzalloc failed", + busNo); + POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_KMALLOC_FAILED; + goto Away; + } + + INIT_LIST_HEAD(&pBusInfo->entry); + pBusInfo->busNo = busNo; + pBusInfo->devNo = cmd->createBus.deviceCount; + + POSTCODE_LINUX_3(BUS_CREATE_ENTRY_PC, busNo, POSTCODE_SEVERITY_INFO); + + if (inmsg->hdr.Flags.testMessage == 1) + pBusInfo->chanInfo.addrType = ADDRTYPE_localTest; + else + pBusInfo->chanInfo.addrType = ADDRTYPE_localPhysical; + + pBusInfo->flags.server = inmsg->hdr.Flags.server; + pBusInfo->chanInfo.channelAddr = cmd->createBus.channelAddr; + pBusInfo->chanInfo.nChannelBytes = cmd->createBus.channelBytes; + pBusInfo->chanInfo.channelTypeGuid = cmd->createBus.busDataTypeGuid; + pBusInfo->chanInfo.channelInstGuid = cmd->createBus.busInstGuid; + + list_add(&pBusInfo->entry, &BusInfoList); + + POSTCODE_LINUX_3(BUS_CREATE_EXIT_PC, busNo, POSTCODE_SEVERITY_INFO); + +Away: + bus_epilog(busNo, CONTROLVM_BUS_CREATE, &inmsg->hdr, + rc, inmsg->hdr.Flags.responseExpected == 1); +} + +static void +bus_destroy(CONTROLVM_MESSAGE *inmsg) +{ + CONTROLVM_MESSAGE_PACKET *cmd = &inmsg->cmd; + ulong busNo = cmd->destroyBus.busNo; + VISORCHIPSET_BUS_INFO *pBusInfo; + int rc = CONTROLVM_RESP_SUCCESS; + + pBusInfo = findbus(&BusInfoList, busNo); + if (!pBusInfo) { + LOGERR("CONTROLVM_BUS_DESTROY Failed: bus %lu invalid", busNo); + rc = -CONTROLVM_RESP_ERROR_BUS_INVALID; + goto Away; + } + if (pBusInfo->state.created == 0) { + LOGERR("CONTROLVM_BUS_DESTROY Failed: bus %lu already destroyed", + busNo); + rc = -CONTROLVM_RESP_ERROR_ALREADY_DONE; + goto Away; + } + +Away: + bus_epilog(busNo, CONTROLVM_BUS_DESTROY, &inmsg->hdr, + rc, inmsg->hdr.Flags.responseExpected == 1); +} + +static void +bus_configure(CONTROLVM_MESSAGE *inmsg, PARSER_CONTEXT *parser_ctx) +{ + CONTROLVM_MESSAGE_PACKET *cmd = &inmsg->cmd; + ulong busNo = cmd->configureBus.busNo; + VISORCHIPSET_BUS_INFO *pBusInfo = NULL; + int rc = CONTROLVM_RESP_SUCCESS; + char s[99]; + + busNo = cmd->configureBus.busNo; + POSTCODE_LINUX_3(BUS_CONFIGURE_ENTRY_PC, busNo, POSTCODE_SEVERITY_INFO); + + pBusInfo = findbus(&BusInfoList, busNo); + if (!pBusInfo) { + LOGERR("CONTROLVM_BUS_CONFIGURE Failed: bus %lu invalid", + busNo); + POSTCODE_LINUX_3(BUS_CONFIGURE_FAILURE_PC, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_BUS_INVALID; + goto Away; + } + if (pBusInfo->state.created == 0) { + LOGERR("CONTROLVM_BUS_CONFIGURE Failed: Invalid bus %lu - not created yet", + busNo); + POSTCODE_LINUX_3(BUS_CONFIGURE_FAILURE_PC, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_BUS_INVALID; + goto Away; + } + /* TBD - add this check to other commands also... */ + if (pBusInfo->pendingMsgHdr.Id != CONTROLVM_INVALID) { + LOGERR("CONTROLVM_BUS_CONFIGURE Failed: bus %lu MsgId=%u outstanding", + busNo, (uint) pBusInfo->pendingMsgHdr.Id); + POSTCODE_LINUX_3(BUS_CONFIGURE_FAILURE_PC, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_MESSAGE_ID_INVALID_FOR_CLIENT; + goto Away; + } + + pBusInfo->partitionHandle = cmd->configureBus.guestHandle; + pBusInfo->partitionGuid = parser_id_get(parser_ctx); + parser_param_start(parser_ctx, PARSERSTRING_NAME); + pBusInfo->name = parser_string_get(parser_ctx); + + visorchannel_uuid_id(&pBusInfo->partitionGuid, s); + pBusInfo->procObject = + visor_proc_CreateObject(PartitionType, s, (void *) (pBusInfo)); + if (pBusInfo->procObject == NULL) { + LOGERR("CONTROLVM_BUS_CONFIGURE Failed: busNo=%lu failed to create /proc entry", + busNo); + POSTCODE_LINUX_3(BUS_CONFIGURE_FAILURE_PC, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_KMALLOC_FAILED; + goto Away; + } + POSTCODE_LINUX_3(BUS_CONFIGURE_EXIT_PC, busNo, POSTCODE_SEVERITY_INFO); +Away: + bus_epilog(busNo, CONTROLVM_BUS_CONFIGURE, &inmsg->hdr, + rc, inmsg->hdr.Flags.responseExpected == 1); +} + +static void +my_device_create(CONTROLVM_MESSAGE *inmsg) +{ + CONTROLVM_MESSAGE_PACKET *cmd = &inmsg->cmd; + ulong busNo = cmd->createDevice.busNo; + ulong devNo = cmd->createDevice.devNo; + VISORCHIPSET_DEVICE_INFO *pDevInfo = NULL; + VISORCHIPSET_BUS_INFO *pBusInfo = NULL; + int rc = CONTROLVM_RESP_SUCCESS; + + pDevInfo = finddevice(&DevInfoList, busNo, devNo); + if (pDevInfo && (pDevInfo->state.created == 1)) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: busNo=%lu, devNo=%lu already exists", + busNo, devNo); + POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_ALREADY_DONE; + goto Away; + } + pBusInfo = findbus(&BusInfoList, busNo); + if (!pBusInfo) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: Invalid bus %lu - out of range", + busNo); + POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_BUS_INVALID; + goto Away; + } + if (pBusInfo->state.created == 0) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: Invalid bus %lu - not created yet", + busNo); + POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_BUS_INVALID; + goto Away; + } + pDevInfo = kzalloc(sizeof(VISORCHIPSET_DEVICE_INFO), GFP_KERNEL); + if (pDevInfo == NULL) { + LOGERR("CONTROLVM_DEVICE_CREATE Failed: busNo=%lu, devNo=%lu kmaloc failed", + busNo, devNo); + POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_KMALLOC_FAILED; + goto Away; + } + + INIT_LIST_HEAD(&pDevInfo->entry); + pDevInfo->busNo = busNo; + pDevInfo->devNo = devNo; + pDevInfo->devInstGuid = cmd->createDevice.devInstGuid; + POSTCODE_LINUX_4(DEVICE_CREATE_ENTRY_PC, devNo, busNo, + POSTCODE_SEVERITY_INFO); + + if (inmsg->hdr.Flags.testMessage == 1) + pDevInfo->chanInfo.addrType = ADDRTYPE_localTest; + else + pDevInfo->chanInfo.addrType = ADDRTYPE_localPhysical; + pDevInfo->chanInfo.channelAddr = cmd->createDevice.channelAddr; + pDevInfo->chanInfo.nChannelBytes = cmd->createDevice.channelBytes; + pDevInfo->chanInfo.channelTypeGuid = cmd->createDevice.dataTypeGuid; + pDevInfo->chanInfo.intr = cmd->createDevice.intr; + list_add(&pDevInfo->entry, &DevInfoList); + POSTCODE_LINUX_4(DEVICE_CREATE_EXIT_PC, devNo, busNo, + POSTCODE_SEVERITY_INFO); +Away: + /* get the bus and devNo for DiagPool channel */ + if (is_diagpool_channel(pDevInfo->chanInfo.channelTypeGuid)) { + g_diagpoolBusNo = busNo; + g_diagpoolDevNo = devNo; + LOGINF("CONTROLVM_DEVICE_CREATE for DiagPool channel: busNo=%lu, devNo=%lu", + g_diagpoolBusNo, g_diagpoolDevNo); + } + device_epilog(busNo, devNo, SegmentStateRunning, + CONTROLVM_DEVICE_CREATE, &inmsg->hdr, rc, + inmsg->hdr.Flags.responseExpected == 1, + FOR_VISORBUS(pDevInfo->chanInfo.channelTypeGuid)); +} + +static void +my_device_changestate(CONTROLVM_MESSAGE *inmsg) +{ + CONTROLVM_MESSAGE_PACKET *cmd = &inmsg->cmd; + ulong busNo = cmd->deviceChangeState.busNo; + ulong devNo = cmd->deviceChangeState.devNo; + ULTRA_SEGMENT_STATE state = cmd->deviceChangeState.state; + VISORCHIPSET_DEVICE_INFO *pDevInfo = NULL; + int rc = CONTROLVM_RESP_SUCCESS; + + pDevInfo = finddevice(&DevInfoList, busNo, devNo); + if (!pDevInfo) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE Failed: busNo=%lu, devNo=%lu invalid (doesn't exist)", + busNo, devNo); + POSTCODE_LINUX_4(DEVICE_CHANGESTATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_DEVICE_INVALID; + goto Away; + } + if (pDevInfo->state.created == 0) { + LOGERR("CONTROLVM_DEVICE_CHANGESTATE Failed: busNo=%lu, devNo=%lu invalid (not created)", + busNo, devNo); + POSTCODE_LINUX_4(DEVICE_CHANGESTATE_FAILURE_PC, devNo, busNo, + POSTCODE_SEVERITY_ERR); + rc = -CONTROLVM_RESP_ERROR_DEVICE_INVALID; + } +Away: + if ((rc >= CONTROLVM_RESP_SUCCESS) && pDevInfo) + device_epilog(busNo, devNo, state, CONTROLVM_DEVICE_CHANGESTATE, + &inmsg->hdr, rc, + inmsg->hdr.Flags.responseExpected == 1, + FOR_VISORBUS(pDevInfo->chanInfo.channelTypeGuid)); +} + +static void +my_device_destroy(CONTROLVM_MESSAGE *inmsg) +{ + CONTROLVM_MESSAGE_PACKET *cmd = &inmsg->cmd; + ulong busNo = cmd->destroyDevice.busNo; + ulong devNo = cmd->destroyDevice.devNo; + VISORCHIPSET_DEVICE_INFO *pDevInfo = NULL; + int rc = CONTROLVM_RESP_SUCCESS; + + pDevInfo = finddevice(&DevInfoList, busNo, devNo); + if (!pDevInfo) { + LOGERR("CONTROLVM_DEVICE_DESTROY Failed: busNo=%lu, devNo=%lu invalid", + busNo, devNo); + rc = -CONTROLVM_RESP_ERROR_DEVICE_INVALID; + goto Away; + } + if (pDevInfo->state.created == 0) { + LOGERR("CONTROLVM_DEVICE_DESTROY Failed: busNo=%lu, devNo=%lu already destroyed", + busNo, devNo); + rc = -CONTROLVM_RESP_ERROR_ALREADY_DONE; + } + +Away: + if ((rc >= CONTROLVM_RESP_SUCCESS) && pDevInfo) + device_epilog(busNo, devNo, SegmentStateRunning, + CONTROLVM_DEVICE_DESTROY, &inmsg->hdr, rc, + inmsg->hdr.Flags.responseExpected == 1, + FOR_VISORBUS(pDevInfo->chanInfo.channelTypeGuid)); +} + +/* When provided with the physical address of the controlvm channel + * (phys_addr), the offset to the payload area we need to manage + * (offset), and the size of this payload area (bytes), fills in the + * CONTROLVM_PAYLOAD_INFO struct. Returns TRUE for success or FALSE + * for failure. + */ +static int +initialize_controlvm_payload_info(HOSTADDRESS phys_addr, U64 offset, U32 bytes, + CONTROLVM_PAYLOAD_INFO *info) +{ + U8 __iomem *payload = NULL; + int rc = CONTROLVM_RESP_SUCCESS; + + if (info == NULL) { + LOGERR("HUH ? CONTROLVM_PAYLOAD_INIT Failed : Programmer check at %s:%d", + __FILE__, __LINE__); + rc = -CONTROLVM_RESP_ERROR_PAYLOAD_INVALID; + goto Away; + } + memset(info, 0, sizeof(CONTROLVM_PAYLOAD_INFO)); + if ((offset == 0) || (bytes == 0)) { + LOGERR("CONTROLVM_PAYLOAD_INIT Failed: RequestPayloadOffset=%llu RequestPayloadBytes=%llu!", + (u64) offset, (u64) bytes); + rc = -CONTROLVM_RESP_ERROR_PAYLOAD_INVALID; + goto Away; + } + payload = ioremap_cache(phys_addr + offset, bytes); + if (payload == NULL) { + LOGERR("CONTROLVM_PAYLOAD_INIT Failed: ioremap_cache %llu for %llu bytes failed", + (u64) offset, (u64) bytes); + rc = -CONTROLVM_RESP_ERROR_IOREMAP_FAILED; + goto Away; + } + + info->offset = offset; + info->bytes = bytes; + info->ptr = payload; + LOGINF("offset=%llu, bytes=%lu, ptr=%p", + (u64) (info->offset), (ulong) (info->bytes), info->ptr); + +Away: + if (rc < 0) { + if (payload != NULL) { + iounmap(payload); + payload = NULL; + } + } + return rc; +} + +static void +destroy_controlvm_payload_info(CONTROLVM_PAYLOAD_INFO *info) +{ + if (info->ptr != NULL) { + iounmap(info->ptr); + info->ptr = NULL; + } + memset(info, 0, sizeof(CONTROLVM_PAYLOAD_INFO)); +} + +static void +initialize_controlvm_payload(void) +{ + HOSTADDRESS phys_addr = visorchannel_get_physaddr(ControlVm_channel); + U64 payloadOffset = 0; + U32 payloadBytes = 0; + if (visorchannel_read(ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + RequestPayloadOffset), + &payloadOffset, sizeof(payloadOffset)) < 0) { + LOGERR("CONTROLVM_PAYLOAD_INIT Failed to read controlvm channel!"); + POSTCODE_LINUX_2(CONTROLVM_INIT_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + if (visorchannel_read(ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + RequestPayloadBytes), + &payloadBytes, sizeof(payloadBytes)) < 0) { + LOGERR("CONTROLVM_PAYLOAD_INIT Failed to read controlvm channel!"); + POSTCODE_LINUX_2(CONTROLVM_INIT_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + initialize_controlvm_payload_info(phys_addr, + payloadOffset, payloadBytes, + &ControlVm_payload_info); +} + +/* Send ACTION=online for DEVPATH=/sys/devices/platform/visorchipset. + * Returns CONTROLVM_RESP_xxx code. + */ +int +visorchipset_chipset_ready(void) +{ + kobject_uevent(&Visorchipset_platform_device.dev.kobj, KOBJ_ONLINE); + return CONTROLVM_RESP_SUCCESS; +} +EXPORT_SYMBOL_GPL(visorchipset_chipset_ready); + +int +visorchipset_chipset_selftest(void) +{ + char env_selftest[20]; + char *envp[] = { env_selftest, NULL }; + sprintf(env_selftest, "SPARSP_SELFTEST=%d", 1); + kobject_uevent_env(&Visorchipset_platform_device.dev.kobj, KOBJ_CHANGE, + envp); + return CONTROLVM_RESP_SUCCESS; +} +EXPORT_SYMBOL_GPL(visorchipset_chipset_selftest); + +/* Send ACTION=offline for DEVPATH=/sys/devices/platform/visorchipset. + * Returns CONTROLVM_RESP_xxx code. + */ +int +visorchipset_chipset_notready(void) +{ + kobject_uevent(&Visorchipset_platform_device.dev.kobj, KOBJ_OFFLINE); + return CONTROLVM_RESP_SUCCESS; +} +EXPORT_SYMBOL_GPL(visorchipset_chipset_notready); + +static void +chipset_ready(CONTROLVM_MESSAGE_HEADER *msgHdr) +{ + int rc = visorchipset_chipset_ready(); + if (rc != CONTROLVM_RESP_SUCCESS) + rc = -rc; + if (msgHdr->Flags.responseExpected && !visorchipset_holdchipsetready) + controlvm_respond(msgHdr, rc); + if (msgHdr->Flags.responseExpected && visorchipset_holdchipsetready) { + /* Send CHIPSET_READY response when all modules have been loaded + * and disks mounted for the partition + */ + g_ChipSetMsgHdr = *msgHdr; + LOGINF("Holding CHIPSET_READY response"); + } +} + +static void +chipset_selftest(CONTROLVM_MESSAGE_HEADER *msgHdr) +{ + int rc = visorchipset_chipset_selftest(); + if (rc != CONTROLVM_RESP_SUCCESS) + rc = -rc; + if (msgHdr->Flags.responseExpected) + controlvm_respond(msgHdr, rc); +} + +static void +chipset_notready(CONTROLVM_MESSAGE_HEADER *msgHdr) +{ + int rc = visorchipset_chipset_notready(); + if (rc != CONTROLVM_RESP_SUCCESS) + rc = -rc; + if (msgHdr->Flags.responseExpected) + controlvm_respond(msgHdr, rc); +} + +/* This is your "one-stop" shop for grabbing the next message from the + * CONTROLVM_QUEUE_EVENT queue in the controlvm channel. + */ +static BOOL +read_controlvm_event(CONTROLVM_MESSAGE *msg) +{ + if (visorchannel_signalremove(ControlVm_channel, + CONTROLVM_QUEUE_EVENT, msg)) { + /* got a message */ + if (msg->hdr.Flags.testMessage == 1) { + LOGERR("ignoring bad CONTROLVM_QUEUE_EVENT msg with controlvm_msg_id=0x%x because Flags.testMessage is nonsensical (=1)", msg->hdr.Id); + return FALSE; + } else + return TRUE; + } + return FALSE; +} + +/* + * The general parahotplug flow works as follows. The visorchipset + * driver receives a DEVICE_CHANGESTATE message from Command + * specifying a physical device to enable or disable. The CONTROLVM + * message handler calls parahotplug_process_message, which then adds + * the message to a global list and kicks off a udev event which + * causes a user level script to enable or disable the specified + * device. The udev script then writes to + * /proc/visorchipset/parahotplug, which causes parahotplug_proc_write + * to get called, at which point the appropriate CONTROLVM message is + * retrieved from the list and responded to. + */ + +#define PARAHOTPLUG_TIMEOUT_MS 2000 + +/* + * Generate unique int to match an outstanding CONTROLVM message with a + * udev script /proc response + */ +static int +parahotplug_next_id(void) +{ + static atomic_t id = ATOMIC_INIT(0); + return atomic_inc_return(&id); +} + +/* + * Returns the time (in jiffies) when a CONTROLVM message on the list + * should expire -- PARAHOTPLUG_TIMEOUT_MS in the future + */ +static unsigned long +parahotplug_next_expiration(void) +{ + return jiffies + PARAHOTPLUG_TIMEOUT_MS * HZ / 1000; +} + +/* + * Create a parahotplug_request, which is basically a wrapper for a + * CONTROLVM_MESSAGE that we can stick on a list + */ +static struct parahotplug_request * +parahotplug_request_create(CONTROLVM_MESSAGE *msg) +{ + struct parahotplug_request *req = + kmalloc(sizeof(struct parahotplug_request), + GFP_KERNEL|__GFP_NORETRY); + if (req == NULL) + return NULL; + + req->id = parahotplug_next_id(); + req->expiration = parahotplug_next_expiration(); + req->msg = *msg; + + return req; +} + +/* + * Free a parahotplug_request. + */ +static void +parahotplug_request_destroy(struct parahotplug_request *req) +{ + kfree(req); +} + +/* + * Cause uevent to run the user level script to do the disable/enable + * specified in (the CONTROLVM message in) the specified + * parahotplug_request + */ +static void +parahotplug_request_kickoff(struct parahotplug_request *req) +{ + CONTROLVM_MESSAGE_PACKET *cmd = &req->msg.cmd; + char env_cmd[40], env_id[40], env_state[40], env_bus[40], env_dev[40], + env_func[40]; + char *envp[] = { + env_cmd, env_id, env_state, env_bus, env_dev, env_func, NULL + }; + + sprintf(env_cmd, "SPAR_PARAHOTPLUG=1"); + sprintf(env_id, "SPAR_PARAHOTPLUG_ID=%d", req->id); + sprintf(env_state, "SPAR_PARAHOTPLUG_STATE=%d", + cmd->deviceChangeState.state.Active); + sprintf(env_bus, "SPAR_PARAHOTPLUG_BUS=%d", + cmd->deviceChangeState.busNo); + sprintf(env_dev, "SPAR_PARAHOTPLUG_DEVICE=%d", + cmd->deviceChangeState.devNo >> 3); + sprintf(env_func, "SPAR_PARAHOTPLUG_FUNCTION=%d", + cmd->deviceChangeState.devNo & 0x7); + + LOGINF("parahotplug_request_kickoff: state=%d, bdf=%d/%d/%d, id=%u\n", + cmd->deviceChangeState.state.Active, + cmd->deviceChangeState.busNo, cmd->deviceChangeState.devNo >> 3, + cmd->deviceChangeState.devNo & 7, req->id); + + kobject_uevent_env(&Visorchipset_platform_device.dev.kobj, KOBJ_CHANGE, + envp); +} + +/* + * Remove any request from the list that's been on there too long and + * respond with an error. + */ +static void +parahotplug_process_list(void) +{ + struct list_head *pos = NULL; + struct list_head *tmp = NULL; + + spin_lock(&Parahotplug_request_list_lock); + + list_for_each_safe(pos, tmp, &Parahotplug_request_list) { + struct parahotplug_request *req = + list_entry(pos, struct parahotplug_request, list); + if (time_after_eq(jiffies, req->expiration)) { + list_del(pos); + if (req->msg.hdr.Flags.responseExpected) + controlvm_respond_physdev_changestate( + &req->msg.hdr, + CONTROLVM_RESP_ERROR_DEVICE_UDEV_TIMEOUT, + req->msg.cmd.deviceChangeState.state); + parahotplug_request_destroy(req); + } + } + + spin_unlock(&Parahotplug_request_list_lock); +} + +/* + * Called from the /proc handler, which means the user script has + * finished the enable/disable. Find the matching identifier, and + * respond to the CONTROLVM message with success. + */ +static int +parahotplug_request_complete(int id, U16 active) +{ + struct list_head *pos = NULL; + struct list_head *tmp = NULL; + + spin_lock(&Parahotplug_request_list_lock); + + /* Look for a request matching "id". */ + list_for_each_safe(pos, tmp, &Parahotplug_request_list) { + struct parahotplug_request *req = + list_entry(pos, struct parahotplug_request, list); + if (req->id == id) { + /* Found a match. Remove it from the list and + * respond. + */ + list_del(pos); + spin_unlock(&Parahotplug_request_list_lock); + req->msg.cmd.deviceChangeState.state.Active = active; + if (req->msg.hdr.Flags.responseExpected) + controlvm_respond_physdev_changestate( + &req->msg.hdr, CONTROLVM_RESP_SUCCESS, + req->msg.cmd.deviceChangeState.state); + parahotplug_request_destroy(req); + return 0; + } + } + + spin_unlock(&Parahotplug_request_list_lock); + return -1; +} + +/* + * Enables or disables a PCI device by kicking off a udev script + */ +static void +parahotplug_process_message(CONTROLVM_MESSAGE *inmsg) +{ + struct parahotplug_request *req; + + req = parahotplug_request_create(inmsg); + + if (req == NULL) { + LOGERR("parahotplug_process_message: couldn't allocate request"); + return; + } + + if (inmsg->cmd.deviceChangeState.state.Active) { + /* For enable messages, just respond with success + * right away. This is a bit of a hack, but there are + * issues with the early enable messages we get (with + * either the udev script not detecting that the device + * is up, or not getting called at all). Fortunately + * the messages that get lost don't matter anyway, as + * devices are automatically enabled at + * initialization. + */ + parahotplug_request_kickoff(req); + controlvm_respond_physdev_changestate(&inmsg->hdr, + CONTROLVM_RESP_SUCCESS, + inmsg->cmd. + deviceChangeState.state); + parahotplug_request_destroy(req); + } else { + /* For disable messages, add the request to the + * request list before kicking off the udev script. It + * won't get responded to until the script has + * indicated it's done. + */ + spin_lock(&Parahotplug_request_list_lock); + list_add_tail(&(req->list), &Parahotplug_request_list); + spin_unlock(&Parahotplug_request_list_lock); + + parahotplug_request_kickoff(req); + } +} + +/* + * Gets called when the udev script writes to + * /proc/visorchipset/parahotplug. Expects input in the form of "<id> + * <active>" where <id> is the identifier passed to the script that + * matches a request on the request list, and <active> is 0 or 1 + * indicating whether the device is now enabled or not. + */ +static ssize_t +parahotplug_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[64]; + uint id; + ushort active; + + if (count > sizeof(buf) - 1) { + LOGERR("parahotplug_proc_write: count (%d) exceeds size of buffer (%d)", + (int) count, (int) sizeof(buf)); + return -EINVAL; + } + if (copy_from_user(buf, buffer, count)) { + LOGERR("parahotplug_proc_write: copy_from_user failed"); + return -EFAULT; + } + buf[count] = '\0'; + + if (sscanf(buf, "%u %hu", &id, &active) != 2) { + id = 0; + active = 0; + } + + if (active != 1 && active != 0) { + LOGERR("parahotplug_proc_write: invalid active field"); + return -EINVAL; + } + + parahotplug_request_complete((int) id, (U16) active); + + return count; +} + +static const struct file_operations parahotplug_proc_fops = { + .owner = THIS_MODULE, + .read = visorchipset_proc_read_writeonly, + .write = parahotplug_proc_write, +}; + +/* Process a controlvm message. + * Return result: + * FALSE - this function will return FALSE only in the case where the + * controlvm message was NOT processed, but processing must be + * retried before reading the next controlvm message; a + * scenario where this can occur is when we need to throttle + * the allocation of memory in which to copy out controlvm + * payload data + * TRUE - processing of the controlvm message completed, + * either successfully or with an error. + */ +static BOOL +handle_command(CONTROLVM_MESSAGE inmsg, HOSTADDRESS channel_addr) +{ + CONTROLVM_MESSAGE_PACKET *cmd = &inmsg.cmd; + U64 parametersAddr = 0; + U32 parametersBytes = 0; + PARSER_CONTEXT *parser_ctx = NULL; + BOOL isLocalAddr = FALSE; + CONTROLVM_MESSAGE ackmsg; + + /* create parsing context if necessary */ + isLocalAddr = (inmsg.hdr.Flags.testMessage == 1); + if (channel_addr == 0) { + LOGERR("HUH? channel_addr is 0!"); + return TRUE; + } + parametersAddr = channel_addr + inmsg.hdr.PayloadVmOffset; + parametersBytes = inmsg.hdr.PayloadBytes; + + /* Parameter and channel addresses within test messages actually lie + * within our OS-controlled memory. We need to know that, because it + * makes a difference in how we compute the virtual address. + */ + if (parametersAddr != 0 && parametersBytes != 0) { + BOOL retry = FALSE; + parser_ctx = + parser_init_byteStream(parametersAddr, parametersBytes, + isLocalAddr, &retry); + if (!parser_ctx) { + if (retry) { + LOGWRN("throttling to copy payload"); + return FALSE; + } + LOGWRN("parsing failed"); + LOGWRN("inmsg.hdr.Id=0x%lx", (ulong) inmsg.hdr.Id); + LOGWRN("parametersAddr=0x%llx", (u64) parametersAddr); + LOGWRN("parametersBytes=%lu", (ulong) parametersBytes); + LOGWRN("isLocalAddr=%d", isLocalAddr); + } + } + + if (!isLocalAddr) { + controlvm_init_response(&ackmsg, &inmsg.hdr, + CONTROLVM_RESP_SUCCESS); + if ((ControlVm_channel) + && + (!visorchannel_signalinsert + (ControlVm_channel, CONTROLVM_QUEUE_ACK, &ackmsg))) + LOGWRN("failed to send ACK failed"); + } + switch (inmsg.hdr.Id) { + case CONTROLVM_CHIPSET_INIT: + LOGINF("CHIPSET_INIT(#busses=%lu,#switches=%lu)", + (ulong) inmsg.cmd.initChipset.busCount, + (ulong) inmsg.cmd.initChipset.switchCount); + chipset_init(&inmsg); + break; + case CONTROLVM_BUS_CREATE: + LOGINF("BUS_CREATE(%lu,#devs=%lu)", + (ulong) cmd->createBus.busNo, + (ulong) cmd->createBus.deviceCount); + bus_create(&inmsg); + break; + case CONTROLVM_BUS_DESTROY: + LOGINF("BUS_DESTROY(%lu)", (ulong) cmd->destroyBus.busNo); + bus_destroy(&inmsg); + break; + case CONTROLVM_BUS_CONFIGURE: + LOGINF("BUS_CONFIGURE(%lu)", (ulong) cmd->configureBus.busNo); + bus_configure(&inmsg, parser_ctx); + break; + case CONTROLVM_DEVICE_CREATE: + LOGINF("DEVICE_CREATE(%lu,%lu)", + (ulong) cmd->createDevice.busNo, + (ulong) cmd->createDevice.devNo); + my_device_create(&inmsg); + break; + case CONTROLVM_DEVICE_CHANGESTATE: + if (cmd->deviceChangeState.flags.physicalDevice) { + LOGINF("DEVICE_CHANGESTATE for physical device (%lu,%lu, active=%lu)", + (ulong) cmd->deviceChangeState.busNo, + (ulong) cmd->deviceChangeState.devNo, + (ulong) cmd->deviceChangeState.state.Active); + parahotplug_process_message(&inmsg); + } else { + LOGINF("DEVICE_CHANGESTATE for virtual device (%lu,%lu, state.Alive=0x%lx)", + (ulong) cmd->deviceChangeState.busNo, + (ulong) cmd->deviceChangeState.devNo, + (ulong) cmd->deviceChangeState.state.Alive); + /* save the hdr and cmd structures for later use */ + /* when sending back the response to Command */ + my_device_changestate(&inmsg); + g_DiagMsgHdr = inmsg.hdr; + g_DeviceChangeStatePacket = inmsg.cmd; + break; + } + break; + case CONTROLVM_DEVICE_DESTROY: + LOGINF("DEVICE_DESTROY(%lu,%lu)", + (ulong) cmd->destroyDevice.busNo, + (ulong) cmd->destroyDevice.devNo); + my_device_destroy(&inmsg); + break; + case CONTROLVM_DEVICE_CONFIGURE: + LOGINF("DEVICE_CONFIGURE(%lu,%lu)", + (ulong) cmd->configureDevice.busNo, + (ulong) cmd->configureDevice.devNo); + /* no op for now, just send a respond that we passed */ + if (inmsg.hdr.Flags.responseExpected) + controlvm_respond(&inmsg.hdr, CONTROLVM_RESP_SUCCESS); + break; + case CONTROLVM_CHIPSET_READY: + LOGINF("CHIPSET_READY"); + chipset_ready(&inmsg.hdr); + break; + case CONTROLVM_CHIPSET_SELFTEST: + LOGINF("CHIPSET_SELFTEST"); + chipset_selftest(&inmsg.hdr); + break; + case CONTROLVM_CHIPSET_STOP: + LOGINF("CHIPSET_STOP"); + chipset_notready(&inmsg.hdr); + break; + default: + LOGERR("unrecognized controlvm cmd=%d", (int) inmsg.hdr.Id); + if (inmsg.hdr.Flags.responseExpected) + controlvm_respond(&inmsg.hdr, + -CONTROLVM_RESP_ERROR_MESSAGE_ID_UNKNOWN); + break; + } + + if (parser_ctx != NULL) { + parser_done(parser_ctx); + parser_ctx = NULL; + } + return TRUE; +} + +static void +controlvm_periodic_work(struct work_struct *work) +{ + VISORCHIPSET_CHANNEL_INFO chanInfo; + CONTROLVM_MESSAGE inmsg; + char s[99]; + BOOL gotACommand = FALSE; + BOOL handle_command_failed = FALSE; + static U64 Poll_Count; + + /* make sure visorbus server is registered for controlvm callbacks */ + if (visorchipset_serverregwait && !serverregistered) + goto Away; + /* make sure visorclientbus server is regsitered for controlvm + * callbacks + */ + if (visorchipset_clientregwait && !clientregistered) + goto Away; + + memset(&chanInfo, 0, sizeof(VISORCHIPSET_CHANNEL_INFO)); + if (!ControlVm_channel) { + HOSTADDRESS addr = controlvm_get_channel_address(); + if (addr != 0) { + ControlVm_channel = + visorchannel_create_with_lock + (addr, + sizeof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL), + UltraControlvmChannelProtocolGuid); + if (ControlVm_channel == NULL) + LOGERR("failed to create controlvm channel"); + else if (ULTRA_CONTROLVM_CHANNEL_OK_CLIENT + (visorchannel_get_header(ControlVm_channel), + NULL)) { + LOGINF("Channel %s (ControlVm) discovered", + visorchannel_id(ControlVm_channel, s)); + initialize_controlvm_payload(); + } else { + LOGERR("controlvm channel is invalid"); + visorchannel_destroy(ControlVm_channel); + ControlVm_channel = NULL; + } + } + } + + Poll_Count++; + if ((ControlVm_channel != NULL) || (Poll_Count >= 250)) + ; /* keep going */ + else + goto Away; + + /* Check events to determine if response to CHIPSET_READY + * should be sent + */ + if (visorchipset_holdchipsetready + && (g_ChipSetMsgHdr.Id != CONTROLVM_INVALID)) { + if (check_chipset_events() == 1) { + LOGINF("Sending CHIPSET_READY response"); + controlvm_respond(&g_ChipSetMsgHdr, 0); + clear_chipset_events(); + memset(&g_ChipSetMsgHdr, 0, + sizeof(CONTROLVM_MESSAGE_HEADER)); + } + } + + if (ControlVm_channel) { + while (visorchannel_signalremove(ControlVm_channel, + CONTROLVM_QUEUE_RESPONSE, + &inmsg)) { + if (inmsg.hdr.PayloadMaxBytes != 0) { + LOGERR("Payload of size %lu returned @%lu with unexpected message id %d.", + (ulong) inmsg.hdr.PayloadMaxBytes, + (ulong) inmsg.hdr.PayloadVmOffset, + inmsg.hdr.Id); + } + } + if (!gotACommand) { + if (ControlVm_Pending_Msg_Valid) { + /* we throttled processing of a prior + * msg, so try to process it again + * rather than reading a new one + */ + inmsg = ControlVm_Pending_Msg; + ControlVm_Pending_Msg_Valid = FALSE; + gotACommand = TRUE; + } else + gotACommand = read_controlvm_event(&inmsg); + } + } + + handle_command_failed = FALSE; + while (gotACommand && (!handle_command_failed)) { + Most_recent_message_jiffies = jiffies; + if (ControlVm_channel) { + if (handle_command(inmsg, + visorchannel_get_physaddr + (ControlVm_channel))) + gotACommand = read_controlvm_event(&inmsg); + else { + /* this is a scenario where throttling + * is required, but probably NOT an + * error...; we stash the current + * controlvm msg so we will attempt to + * reprocess it on our next loop + */ + handle_command_failed = TRUE; + ControlVm_Pending_Msg = inmsg; + ControlVm_Pending_Msg_Valid = TRUE; + } + + } else { + handle_command(inmsg, 0); + gotACommand = FALSE; + } + } + + /* parahotplug_worker */ + parahotplug_process_list(); + +Away: + + if (time_after(jiffies, + Most_recent_message_jiffies + (HZ * MIN_IDLE_SECONDS))) { + /* it's been longer than MIN_IDLE_SECONDS since we + * processed our last controlvm message; slow down the + * polling + */ + if (Poll_jiffies != POLLJIFFIES_CONTROLVMCHANNEL_SLOW) { + LOGINF("switched to slow controlvm polling"); + Poll_jiffies = POLLJIFFIES_CONTROLVMCHANNEL_SLOW; + } + } else { + if (Poll_jiffies != POLLJIFFIES_CONTROLVMCHANNEL_FAST) { + Poll_jiffies = POLLJIFFIES_CONTROLVMCHANNEL_FAST; + LOGINF("switched to fast controlvm polling"); + } + } + + queue_delayed_work(Periodic_controlvm_workqueue, + &Periodic_controlvm_work, Poll_jiffies); +} + +static void +setup_crash_devices_work_queue(struct work_struct *work) +{ + + CONTROLVM_MESSAGE localCrashCreateBusMsg; + CONTROLVM_MESSAGE localCrashCreateDevMsg; + CONTROLVM_MESSAGE msg; + HOSTADDRESS host_addr; + U32 localSavedCrashMsgOffset; + U16 localSavedCrashMsgCount; + + /* make sure visorbus server is registered for controlvm callbacks */ + if (visorchipset_serverregwait && !serverregistered) + goto Away; + + /* make sure visorclientbus server is regsitered for controlvm + * callbacks + */ + if (visorchipset_clientregwait && !clientregistered) + goto Away; + + POSTCODE_LINUX_2(CRASH_DEV_ENTRY_PC, POSTCODE_SEVERITY_INFO); + + /* send init chipset msg */ + msg.hdr.Id = CONTROLVM_CHIPSET_INIT; + msg.cmd.initChipset.busCount = 23; + msg.cmd.initChipset.switchCount = 0; + + chipset_init(&msg); + + host_addr = controlvm_get_channel_address(); + if (!host_addr) { + LOGERR("Huh? Host address is NULL"); + POSTCODE_LINUX_2(CRASH_DEV_HADDR_NULL, POSTCODE_SEVERITY_ERR); + return; + } + + ControlVm_channel = + visorchannel_create_with_lock + (host_addr, + sizeof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL), + UltraControlvmChannelProtocolGuid); + + if (ControlVm_channel == NULL) { + LOGERR("failed to create controlvm channel"); + POSTCODE_LINUX_2(CRASH_DEV_CONTROLVM_NULL, + POSTCODE_SEVERITY_ERR); + return; + } + + /* get saved message count */ + if (visorchannel_read(ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + SavedCrashMsgCount), + &localSavedCrashMsgCount, sizeof(U16)) < 0) { + LOGERR("failed to get Saved Message Count"); + POSTCODE_LINUX_2(CRASH_DEV_CTRL_RD_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + + if (localSavedCrashMsgCount != CONTROLVM_CRASHMSG_MAX) { + LOGERR("Saved Message Count incorrect %d", + localSavedCrashMsgCount); + POSTCODE_LINUX_3(CRASH_DEV_COUNT_FAILURE_PC, + localSavedCrashMsgCount, + POSTCODE_SEVERITY_ERR); + return; + } + + /* get saved crash message offset */ + if (visorchannel_read(ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + SavedCrashMsgOffset), + &localSavedCrashMsgOffset, sizeof(U32)) < 0) { + LOGERR("failed to get Saved Message Offset"); + POSTCODE_LINUX_2(CRASH_DEV_CTRL_RD_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + + /* read create device message for storage bus offset */ + if (visorchannel_read(ControlVm_channel, + localSavedCrashMsgOffset, + &localCrashCreateBusMsg, + sizeof(CONTROLVM_MESSAGE)) < 0) { + LOGERR("CRASH_DEV_RD_BUS_FAIULRE: Failed to read CrashCreateBusMsg!"); + POSTCODE_LINUX_2(CRASH_DEV_RD_BUS_FAIULRE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + + /* read create device message for storage device */ + if (visorchannel_read(ControlVm_channel, + localSavedCrashMsgOffset + + sizeof(CONTROLVM_MESSAGE), + &localCrashCreateDevMsg, + sizeof(CONTROLVM_MESSAGE)) < 0) { + LOGERR("CRASH_DEV_RD_DEV_FAIULRE: Failed to read CrashCreateDevMsg!"); + POSTCODE_LINUX_2(CRASH_DEV_RD_DEV_FAIULRE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + + /* reuse IOVM create bus message */ + if (localCrashCreateBusMsg.cmd.createBus.channelAddr != 0) + bus_create(&localCrashCreateBusMsg); + else { + LOGERR("CrashCreateBusMsg is null, no dump will be taken"); + POSTCODE_LINUX_2(CRASH_DEV_BUS_NULL_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + + /* reuse create device message for storage device */ + if (localCrashCreateDevMsg.cmd.createDevice.channelAddr != 0) + my_device_create(&localCrashCreateDevMsg); + else { + LOGERR("CrashCreateDevMsg is null, no dump will be taken"); + POSTCODE_LINUX_2(CRASH_DEV_DEV_NULL_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + return; + } + LOGINF("Bus and device ready for dumping"); + POSTCODE_LINUX_2(CRASH_DEV_EXIT_PC, POSTCODE_SEVERITY_INFO); + return; + +Away: + + Poll_jiffies = POLLJIFFIES_CONTROLVMCHANNEL_SLOW; + + queue_delayed_work(Periodic_controlvm_workqueue, + &Periodic_controlvm_work, Poll_jiffies); +} + +static void +bus_create_response(ulong busNo, int response) +{ + bus_responder(CONTROLVM_BUS_CREATE, busNo, response); +} + +static void +bus_destroy_response(ulong busNo, int response) +{ + bus_responder(CONTROLVM_BUS_DESTROY, busNo, response); +} + +static void +device_create_response(ulong busNo, ulong devNo, int response) +{ + device_responder(CONTROLVM_DEVICE_CREATE, busNo, devNo, response); +} + +static void +device_destroy_response(ulong busNo, ulong devNo, int response) +{ + device_responder(CONTROLVM_DEVICE_DESTROY, busNo, devNo, response); +} + +void +visorchipset_device_pause_response(ulong busNo, ulong devNo, int response) +{ + + device_changestate_responder(CONTROLVM_DEVICE_CHANGESTATE, + busNo, devNo, response, + SegmentStateStandby); +} +EXPORT_SYMBOL_GPL(visorchipset_device_pause_response); + +static void +device_resume_response(ulong busNo, ulong devNo, int response) +{ + device_changestate_responder(CONTROLVM_DEVICE_CHANGESTATE, + busNo, devNo, response, + SegmentStateRunning); +} + +BOOL +visorchipset_get_bus_info(ulong busNo, VISORCHIPSET_BUS_INFO *busInfo) +{ + void *p = findbus(&BusInfoList, busNo); + if (!p) { + LOGERR("(%lu) failed", busNo); + return FALSE; + } + memcpy(busInfo, p, sizeof(VISORCHIPSET_BUS_INFO)); + return TRUE; +} +EXPORT_SYMBOL_GPL(visorchipset_get_bus_info); + +BOOL +visorchipset_set_bus_context(ulong busNo, void *context) +{ + VISORCHIPSET_BUS_INFO *p = findbus(&BusInfoList, busNo); + if (!p) { + LOGERR("(%lu) failed", busNo); + return FALSE; + } + p->bus_driver_context = context; + return TRUE; +} +EXPORT_SYMBOL_GPL(visorchipset_set_bus_context); + +BOOL +visorchipset_get_device_info(ulong busNo, ulong devNo, + VISORCHIPSET_DEVICE_INFO *devInfo) +{ + void *p = finddevice(&DevInfoList, busNo, devNo); + if (!p) { + LOGERR("(%lu,%lu) failed", busNo, devNo); + return FALSE; + } + memcpy(devInfo, p, sizeof(VISORCHIPSET_DEVICE_INFO)); + return TRUE; +} +EXPORT_SYMBOL_GPL(visorchipset_get_device_info); + +BOOL +visorchipset_set_device_context(ulong busNo, ulong devNo, void *context) +{ + VISORCHIPSET_DEVICE_INFO *p = finddevice(&DevInfoList, busNo, devNo); + if (!p) { + LOGERR("(%lu,%lu) failed", busNo, devNo); + return FALSE; + } + p->bus_driver_context = context; + return TRUE; +} +EXPORT_SYMBOL_GPL(visorchipset_set_device_context); + +/* Generic wrapper function for allocating memory from a kmem_cache pool. + */ +void * +visorchipset_cache_alloc(struct kmem_cache *pool, BOOL ok_to_block, + char *fn, int ln) +{ + gfp_t gfp; + void *p; + + if (ok_to_block) + gfp = GFP_KERNEL; + else + gfp = GFP_ATOMIC; + /* __GFP_NORETRY means "ok to fail", meaning + * kmem_cache_alloc() can return NULL, implying the caller CAN + * cope with failure. If you do NOT specify __GFP_NORETRY, + * Linux will go to extreme measures to get memory for you + * (like, invoke oom killer), which will probably cripple the + * system. + */ + gfp |= __GFP_NORETRY; + p = kmem_cache_alloc(pool, gfp); + if (!p) { + LOGERR("kmem_cache_alloc failed early @%s:%d\n", fn, ln); + return NULL; + } + atomic_inc(&Visorchipset_cache_buffers_in_use); + return p; +} + +/* Generic wrapper function for freeing memory from a kmem_cache pool. + */ +void +visorchipset_cache_free(struct kmem_cache *pool, void *p, char *fn, int ln) +{ + if (!p) { + LOGERR("NULL pointer @%s:%d\n", fn, ln); + return; + } + atomic_dec(&Visorchipset_cache_buffers_in_use); + kmem_cache_free(pool, p); +} + +#define gettoken(bufp) strsep(bufp, " -\t\n") + +static ssize_t +chipset_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char buf[512]; + char *token, *p; + + if (count > sizeof(buf) - 1) { + LOGERR("chipset_proc_write: count (%d) exceeds size of buffer (%d)", + (int) count, (int) sizeof(buffer)); + return -EINVAL; + } + if (copy_from_user(buf, buffer, count)) { + LOGERR("chipset_proc_write: copy_from_user failed"); + return -EFAULT; + } + buf[count] = '\0'; + + p = buf; + token = gettoken(&p); + + if (strcmp(token, "CALLHOMEDISK_MOUNTED") == 0) { + token = gettoken(&p); + /* The Call Home Disk has been mounted */ + if (strcmp(token, "0") == 0) + chipset_events[0] = 1; + } else if (strcmp(token, "MODULES_LOADED") == 0) { + token = gettoken(&p); + /* All modules for the partition have been loaded */ + if (strcmp(token, "0") == 0) + chipset_events[1] = 1; + } else if (token == NULL) { + /* No event specified */ + LOGERR("No event was specified to send CHIPSET_READY response"); + return -1; + } else { + /* Unsupported event specified */ + LOGERR("%s is an invalid event for sending CHIPSET_READY response", token); + return -1; + } + + return count; +} + +static ssize_t +visorchipset_proc_read_writeonly(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + return 0; +} + +/** + * Reads the InstallationError, InstallationTextId, + * InstallationRemainingSteps fields of ControlVMChannel. + */ +static ssize_t +proc_read_installer(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + int length = 0; + U16 remainingSteps; + U32 error, textId; + char *vbuf; + loff_t pos = *offset; + + if (!ControlVm_channel) + return -ENODEV; + + if (pos < 0) + return -EINVAL; + + if (pos > 0 || !len) + return 0; + + vbuf = kzalloc(len, GFP_KERNEL); + if (!vbuf) + return -ENOMEM; + + visorchannel_read(ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + InstallationRemainingSteps), &remainingSteps, + sizeof(U16)); + visorchannel_read(ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + InstallationError), &error, sizeof(U32)); + visorchannel_read(ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + InstallationTextId), &textId, sizeof(U32)); + + length = sprintf(vbuf, "%u %u %u\n", remainingSteps, error, textId); + if (copy_to_user(buf, vbuf, length)) { + kfree(vbuf); + return -EFAULT; + } + + kfree(vbuf); + *offset += length; + return length; +} + +/** + * Writes to the InstallationError, InstallationTextId, + * InstallationRemainingSteps fields of + * ControlVMChannel. + * Input: RemainingSteps Error TextId + * Limit 32 characters input + */ +#define UINT16_MAX (65535U) +#define UINT32_MAX (4294967295U) +static ssize_t +proc_write_installer(struct file *file, + const char __user *buffer, size_t count, loff_t *ppos) +{ + char buf[32]; + U16 remainingSteps; + U32 error, textId; + + if (!ControlVm_channel) + return -ENODEV; + + /* Check to make sure there is no buffer overflow */ + if (count > (sizeof(buf) - 1)) + return -EINVAL; + + if (copy_from_user(buf, buffer, count)) { + WARN(1, "Error copying from user space\n"); + return -EFAULT; + } + + if (sscanf(buf, "%hu %i %i", &remainingSteps, &error, &textId) != 3) { + remainingSteps = UINT16_MAX; + error = UINT32_MAX; + textId = UINT32_MAX; + } + + if (remainingSteps != UINT16_MAX) { + if (visorchannel_write + (ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + InstallationRemainingSteps), &remainingSteps, + sizeof(U16)) < 0) + WARN(1, "Installation Status Write Failed - Write function error - RemainingSteps = %d\n", + remainingSteps); + } + + if (error != UINT32_MAX) { + if (visorchannel_write + (ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + InstallationError), &error, sizeof(U32)) < 0) + WARN(1, "Installation Status Write Failed - Write function error - Error = %d\n", + error); + } + + if (textId != UINT32_MAX) { + if (visorchannel_write + (ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + InstallationTextId), &textId, sizeof(U32)) < 0) + WARN(1, "Installation Status Write Failed - Write function error - TextId = %d\n", + textId); + } + + /* So this function isn't called multiple times, must return + * size of buffer + */ + return count; +} + +/** + * Reads the ToolAction field of ControlVMChannel. + */ +static ssize_t +proc_read_toolaction(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + int length = 0; + U8 toolAction; + char *vbuf; + loff_t pos = *offset; + + if (!ControlVm_channel) + return -ENODEV; + + if (pos < 0) + return -EINVAL; + + if (pos > 0 || !len) + return 0; + + vbuf = kzalloc(len, GFP_KERNEL); + if (!vbuf) + return -ENOMEM; + + visorchannel_read(ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + ToolAction), &toolAction, sizeof(U8)); + + length = sprintf(vbuf, "%u\n", toolAction); + if (copy_to_user(buf, vbuf, length)) { + kfree(vbuf); + return -EFAULT; + } + + kfree(vbuf); + *offset += length; + return length; +} + +/** + * Writes to the ToolAction field of ControlVMChannel. + * Input: ToolAction + * Limit 3 characters input + */ +#define UINT8_MAX (255U) +static ssize_t +proc_write_toolaction(struct file *file, + const char __user *buffer, size_t count, loff_t *ppos) +{ + char buf[3]; + U8 toolAction; + + if (!ControlVm_channel) + return -ENODEV; + + /* Check to make sure there is no buffer overflow */ + if (count > (sizeof(buf) - 1)) + return -EINVAL; + + if (copy_from_user(buf, buffer, count)) { + WARN(1, "Error copying from user space\n"); + return -EFAULT; + } + + if (sscanf(buf, "%hhd", &toolAction) != 1) + toolAction = UINT8_MAX; + + if (toolAction != UINT8_MAX) { + if (visorchannel_write + (ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, ToolAction), + &toolAction, sizeof(U8)) < 0) + WARN(1, "Installation ToolAction Write Failed - ToolAction = %d\n", + toolAction); + } + + /* So this function isn't called multiple times, must return + * size of buffer + */ + return count; +} + +/** + * Reads the EfiSparIndication.BootToTool field of ControlVMChannel. + */ +static ssize_t +proc_read_bootToTool(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + int length = 0; + ULTRA_EFI_SPAR_INDICATION efiSparIndication; + char *vbuf; + loff_t pos = *offset; + + if (!ControlVm_channel) + return -ENODEV; + + if (pos < 0) + return -EINVAL; + + if (pos > 0 || !len) + return 0; + + vbuf = kzalloc(len, GFP_KERNEL); + if (!vbuf) + return -ENOMEM; + + visorchannel_read(ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, + EfiSparIndication), &efiSparIndication, + sizeof(ULTRA_EFI_SPAR_INDICATION)); + + length = sprintf(vbuf, "%d\n", (int) efiSparIndication.BootToTool); + if (copy_to_user(buf, vbuf, length)) { + kfree(vbuf); + return -EFAULT; + } + + kfree(vbuf); + *offset += length; + return length; +} + +/** + * Writes to the EfiSparIndication.BootToTool field of ControlVMChannel. + * Input: 1 or 0 (1 being on, 0 being off) + */ +static ssize_t +proc_write_bootToTool(struct file *file, + const char __user *buffer, size_t count, loff_t *ppos) +{ + char buf[3]; + int inputVal; + ULTRA_EFI_SPAR_INDICATION efiSparIndication; + + if (!ControlVm_channel) + return -ENODEV; + + /* Check to make sure there is no buffer overflow */ + if (count > (sizeof(buf) - 1)) + return -EINVAL; + + if (copy_from_user(buf, buffer, count)) { + WARN(1, "Error copying from user space\n"); + return -EFAULT; + } + + if (sscanf(buf, "%i", &inputVal) != 1) + inputVal = 0; + + efiSparIndication.BootToTool = (inputVal == 1 ? 1 : 0); + + if (visorchannel_write + (ControlVm_channel, + offsetof(ULTRA_CONTROLVM_CHANNEL_PROTOCOL, EfiSparIndication), + &efiSparIndication, sizeof(ULTRA_EFI_SPAR_INDICATION)) < 0) + printk + ("Installation BootToTool Write Failed - BootToTool = %d\n", + (int) efiSparIndication.BootToTool); + + /* So this function isn't called multiple times, must return + * size of buffer + */ + return count; +} + +static const struct file_operations chipset_proc_fops = { + .owner = THIS_MODULE, + .read = visorchipset_proc_read_writeonly, + .write = chipset_proc_write, +}; + +static int __init +visorchipset_init(void) +{ + int rc = 0, x = 0; + struct proc_dir_entry *installer_file; + struct proc_dir_entry *toolaction_file; + struct proc_dir_entry *bootToTool_file; + + if (!unisys_spar_platform) + return -ENODEV; + + LOGINF("chipset driver version %s loaded", VERSION); + /* process module options */ + POSTCODE_LINUX_2(DRIVER_ENTRY_PC, POSTCODE_SEVERITY_INFO); + + LOGINF("option - testvnic=%d", visorchipset_testvnic); + LOGINF("option - testvnicclient=%d", visorchipset_testvnicclient); + LOGINF("option - testmsg=%d", visorchipset_testmsg); + LOGINF("option - testteardown=%d", visorchipset_testteardown); + LOGINF("option - major=%d", visorchipset_major); + LOGINF("option - serverregwait=%d", visorchipset_serverregwait); + LOGINF("option - clientregwait=%d", visorchipset_clientregwait); + LOGINF("option - holdchipsetready=%d", visorchipset_holdchipsetready); + + memset(&BusDev_Server_Notifiers, 0, sizeof(BusDev_Server_Notifiers)); + memset(&BusDev_Client_Notifiers, 0, sizeof(BusDev_Client_Notifiers)); + memset(&ControlVm_payload_info, 0, sizeof(ControlVm_payload_info)); + memset(&LiveDump_info, 0, sizeof(LiveDump_info)); + atomic_set(&LiveDump_info.buffers_in_use, 0); + + if (visorchipset_testvnic) { + ERRDRV("testvnic option no longer supported: (status = %d)\n", + x); + POSTCODE_LINUX_3(CHIPSET_INIT_FAILURE_PC, x, DIAG_SEVERITY_ERR); + rc = x; + goto Away; + } + + controlvm_init(); + MajorDev = MKDEV(visorchipset_major, 0); + rc = visorchipset_file_init(MajorDev, &ControlVm_channel); + if (rc < 0) { + ERRDRV("visorchipset_file_init(MajorDev, &ControlVm_channel): error (status=%d)\n", rc); + POSTCODE_LINUX_2(CHIPSET_INIT_FAILURE_PC, DIAG_SEVERITY_ERR); + goto Away; + } + + proc_Init(); + memset(PartitionPropertyNames, 0, sizeof(PartitionPropertyNames)); + memset(ControlVmPropertyNames, 0, sizeof(ControlVmPropertyNames)); + InitPartitionProperties(); + InitControlVmProperties(); + + PartitionType = visor_proc_CreateType(ProcDir, PartitionTypeNames, + (const char **) + PartitionPropertyNames, + &show_partition_property); + ControlVmType = + visor_proc_CreateType(ProcDir, ControlVmTypeNames, + (const char **) ControlVmPropertyNames, + &show_controlvm_property); + + ControlVmObject = visor_proc_CreateObject(ControlVmType, NULL, NULL); + + /* Setup Installation fields */ + installer_file = proc_create("installer", 0644, ProcDir, + &proc_installer_fops); + /* Setup the ToolAction field */ + toolaction_file = proc_create("toolaction", 0644, ProcDir, + &proc_toolaction_fops); + /* Setup the BootToTool field */ + bootToTool_file = proc_create("boottotool", 0644, ProcDir, + &proc_bootToTool_fops); + + memset(&g_DiagMsgHdr, 0, sizeof(CONTROLVM_MESSAGE_HEADER)); + + chipset_proc_dir = proc_create(VISORCHIPSET_CHIPSET_PROC_ENTRY_FN, + 0644, ProcDir, &chipset_proc_fops); + memset(&g_ChipSetMsgHdr, 0, sizeof(CONTROLVM_MESSAGE_HEADER)); + + parahotplug_proc_dir = + proc_create(VISORCHIPSET_PARAHOTPLUG_PROC_ENTRY_FN, 0200, + ProcDir, ¶hotplug_proc_fops); + memset(&g_DelDumpMsgHdr, 0, sizeof(CONTROLVM_MESSAGE_HEADER)); + + Putfile_buffer_list_pool = + kmem_cache_create(Putfile_buffer_list_pool_name, + sizeof(struct putfile_buffer_entry), + 0, SLAB_HWCACHE_ALIGN, NULL); + if (!Putfile_buffer_list_pool) { + ERRDRV("failed to alloc Putfile_buffer_list_pool: (status=-1)\n"); + POSTCODE_LINUX_2(CHIPSET_INIT_FAILURE_PC, DIAG_SEVERITY_ERR); + rc = -1; + goto Away; + } + if (visorchipset_disable_controlvm) { + LOGINF("visorchipset_init:controlvm disabled"); + } else { + /* if booting in a crash kernel */ + if (visorchipset_crash_kernel) + INIT_DELAYED_WORK(&Periodic_controlvm_work, + setup_crash_devices_work_queue); + else + INIT_DELAYED_WORK(&Periodic_controlvm_work, + controlvm_periodic_work); + Periodic_controlvm_workqueue = + create_singlethread_workqueue("visorchipset_controlvm"); + + if (Periodic_controlvm_workqueue == NULL) { + ERRDRV("cannot create controlvm workqueue: (status=%d)\n", + -ENOMEM); + POSTCODE_LINUX_2(CREATE_WORKQUEUE_FAILED_PC, + DIAG_SEVERITY_ERR); + rc = -ENOMEM; + goto Away; + } + Most_recent_message_jiffies = jiffies; + Poll_jiffies = POLLJIFFIES_CONTROLVMCHANNEL_FAST; + rc = queue_delayed_work(Periodic_controlvm_workqueue, + &Periodic_controlvm_work, Poll_jiffies); + if (rc < 0) { + ERRDRV("queue_delayed_work(Periodic_controlvm_workqueue, &Periodic_controlvm_work, Poll_jiffies): error (status=%d)\n", rc); + POSTCODE_LINUX_2(QUEUE_DELAYED_WORK_PC, + DIAG_SEVERITY_ERR); + goto Away; + } + + } + + Visorchipset_platform_device.dev.devt = MajorDev; + if (platform_device_register(&Visorchipset_platform_device) < 0) { + ERRDRV("platform_device_register(visorchipset) failed: (status=-1)\n"); + POSTCODE_LINUX_2(DEVICE_REGISTER_FAILURE_PC, DIAG_SEVERITY_ERR); + rc = -1; + goto Away; + } + LOGINF("visorchipset device created"); + POSTCODE_LINUX_2(CHIPSET_INIT_SUCCESS_PC, POSTCODE_SEVERITY_INFO); + rc = 0; +Away: + if (rc) { + LOGERR("visorchipset_init failed"); + POSTCODE_LINUX_3(CHIPSET_INIT_FAILURE_PC, rc, + POSTCODE_SEVERITY_ERR); + } + return rc; +} + +static void +visorchipset_exit(void) +{ + char s[99]; + POSTCODE_LINUX_2(DRIVER_EXIT_PC, POSTCODE_SEVERITY_INFO); + + if (visorchipset_disable_controlvm) { + ; + } else { + cancel_delayed_work(&Periodic_controlvm_work); + flush_workqueue(Periodic_controlvm_workqueue); + destroy_workqueue(Periodic_controlvm_workqueue); + Periodic_controlvm_workqueue = NULL; + destroy_controlvm_payload_info(&ControlVm_payload_info); + } + Test_Vnic_channel = NULL; + if (Putfile_buffer_list_pool) { + kmem_cache_destroy(Putfile_buffer_list_pool); + Putfile_buffer_list_pool = NULL; + } + if (ControlVmObject) { + visor_proc_DestroyObject(ControlVmObject); + ControlVmObject = NULL; + } + cleanup_controlvm_structures(); + + if (ControlVmType) { + visor_proc_DestroyType(ControlVmType); + ControlVmType = NULL; + } + if (PartitionType) { + visor_proc_DestroyType(PartitionType); + PartitionType = NULL; + } + if (diag_proc_dir) { + remove_proc_entry(VISORCHIPSET_DIAG_PROC_ENTRY_FN, ProcDir); + diag_proc_dir = NULL; + } + memset(&g_DiagMsgHdr, 0, sizeof(CONTROLVM_MESSAGE_HEADER)); + + if (chipset_proc_dir) { + remove_proc_entry(VISORCHIPSET_CHIPSET_PROC_ENTRY_FN, ProcDir); + chipset_proc_dir = NULL; + } + memset(&g_ChipSetMsgHdr, 0, sizeof(CONTROLVM_MESSAGE_HEADER)); + + if (parahotplug_proc_dir) { + remove_proc_entry(VISORCHIPSET_PARAHOTPLUG_PROC_ENTRY_FN, + ProcDir); + parahotplug_proc_dir = NULL; + } + + memset(&g_DelDumpMsgHdr, 0, sizeof(CONTROLVM_MESSAGE_HEADER)); + + proc_DeInit(); + if (ControlVm_channel != NULL) { + LOGINF("Channel %s (ControlVm) disconnected", + visorchannel_id(ControlVm_channel, s)); + visorchannel_destroy(ControlVm_channel); + ControlVm_channel = NULL; + } + controlvm_deinit(); + visorchipset_file_cleanup(); + POSTCODE_LINUX_2(DRIVER_EXIT_PC, POSTCODE_SEVERITY_INFO); + LOGINF("chipset driver unloaded"); +} + +module_param_named(testvnic, visorchipset_testvnic, int, S_IRUGO); +MODULE_PARM_DESC(visorchipset_testvnic, "1 to test vnic, using dummy VNIC connected via a loopback to a physical ethernet"); +int visorchipset_testvnic = 0; + +module_param_named(testvnicclient, visorchipset_testvnicclient, int, S_IRUGO); +MODULE_PARM_DESC(visorchipset_testvnicclient, "1 to test vnic, using real VNIC channel attached to a separate IOVM guest"); +int visorchipset_testvnicclient = 0; + +module_param_named(testmsg, visorchipset_testmsg, int, S_IRUGO); +MODULE_PARM_DESC(visorchipset_testmsg, + "1 to manufacture the chipset, bus, and switch messages"); +int visorchipset_testmsg = 0; + +module_param_named(major, visorchipset_major, int, S_IRUGO); +MODULE_PARM_DESC(visorchipset_major, "major device number to use for the device node"); +int visorchipset_major = 0; + +module_param_named(serverregwait, visorchipset_serverregwait, int, S_IRUGO); +MODULE_PARM_DESC(visorchipset_serverreqwait, + "1 to have the module wait for the visor bus to register"); +int visorchipset_serverregwait = 0; /* default is off */ +module_param_named(clientregwait, visorchipset_clientregwait, int, S_IRUGO); +MODULE_PARM_DESC(visorchipset_clientregwait, "1 to have the module wait for the visorclientbus to register"); +int visorchipset_clientregwait = 1; /* default is on */ +module_param_named(testteardown, visorchipset_testteardown, int, S_IRUGO); +MODULE_PARM_DESC(visorchipset_testteardown, + "1 to test teardown of the chipset, bus, and switch"); +int visorchipset_testteardown = 0; /* default is off */ +module_param_named(disable_controlvm, visorchipset_disable_controlvm, int, + S_IRUGO); +MODULE_PARM_DESC(visorchipset_disable_controlvm, + "1 to disable polling of controlVm channel"); +int visorchipset_disable_controlvm = 0; /* default is off */ +module_param_named(crash_kernel, visorchipset_crash_kernel, int, S_IRUGO); +MODULE_PARM_DESC(visorchipset_crash_kernel, + "1 means we are running in crash kernel"); +int visorchipset_crash_kernel = 0; /* default is running in non-crash kernel */ +module_param_named(holdchipsetready, visorchipset_holdchipsetready, + int, S_IRUGO); +MODULE_PARM_DESC(visorchipset_holdchipsetready, + "1 to hold response to CHIPSET_READY"); +int visorchipset_holdchipsetready = 0; /* default is to send CHIPSET_READY + * response immediately */ +module_init(visorchipset_init); +module_exit(visorchipset_exit); + +MODULE_AUTHOR("Unisys"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Supervisor chipset driver for service partition: ver " + VERSION); +MODULE_VERSION(VERSION); diff --git a/drivers/staging/unisys/visorchipset/visorchipset_umode.h b/drivers/staging/unisys/visorchipset/visorchipset_umode.h new file mode 100644 index 00000000000..06ba5b7e425 --- /dev/null +++ b/drivers/staging/unisys/visorchipset/visorchipset_umode.h @@ -0,0 +1,37 @@ +/* visorchipset_umode.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. + */ + +/** @file ********************************************************************* + * + * This describes structures needed for the interface between the + * visorchipset driver and a user-mode component that opens the device. + * + ****************************************************************************** + */ + +#ifndef __VISORCHIPSET_UMODE_H +#define __VISORCHIPSET_UMODE_H + + + +/** The user-mode program can access the control channel buffer directly + * via this memory map. + */ +#define VISORCHIPSET_MMAP_CONTROLCHANOFFSET (0x00000000) +#define VISORCHIPSET_MMAP_CONTROLCHANSIZE (0x00400000) /* 4MB */ + +#endif /* __VISORCHIPSET_UMODE_H */ diff --git a/drivers/staging/unisys/visorutil/Kconfig b/drivers/staging/unisys/visorutil/Kconfig new file mode 100644 index 00000000000..4ff61a7c506 --- /dev/null +++ b/drivers/staging/unisys/visorutil/Kconfig @@ -0,0 +1,10 @@ +# +# Unisys timskmod configuration +# + +config UNISYS_VISORUTIL + tristate "Unisys visorutil driver" + depends on UNISYSSPAR + ---help--- + If you say Y here, you will enable the Unisys visorutil driver. + diff --git a/drivers/staging/unisys/visorutil/Makefile b/drivers/staging/unisys/visorutil/Makefile new file mode 100644 index 00000000000..3f463888dce --- /dev/null +++ b/drivers/staging/unisys/visorutil/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for Unisys timskmod +# + +obj-$(CONFIG_UNISYS_VISORUTIL) += visorutil.o + +visorutil-y := charqueue.o easyproc.o periodic_work.o procobjecttree.o \ + memregion_direct.o visorkmodutils.o + +ccflags-y += -Idrivers/staging/unisys/include +ccflags-y += -DCONFIG_SPAR_GUEST -DGUESTDRIVERBUILD -DNOAUTOVERSION diff --git a/drivers/staging/unisys/visorutil/charqueue.c b/drivers/staging/unisys/visorutil/charqueue.c new file mode 100644 index 00000000000..22241c7b4f7 --- /dev/null +++ b/drivers/staging/unisys/visorutil/charqueue.c @@ -0,0 +1,143 @@ +/* charqueue.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. + */ + +/* + * Simple character queue implementation for Linux kernel mode. + */ + +#include "charqueue.h" + +#define MYDRVNAME "charqueue" + +#define IS_EMPTY(charqueue) (charqueue->head == charqueue->tail) + + + +struct CHARQUEUE_Tag { + int alloc_size; + int nslots; + spinlock_t lock; + int head, tail; + unsigned char buf[0]; +}; + + + +CHARQUEUE *visor_charqueue_create(ulong nslots) +{ + int alloc_size = sizeof(CHARQUEUE) + nslots + 1; + CHARQUEUE *cq = kmalloc(alloc_size, GFP_KERNEL|__GFP_NORETRY); + + if (cq == NULL) { + ERRDRV("visor_charqueue_create allocation failed (alloc_size=%d)", + alloc_size); + return NULL; + } + cq->alloc_size = alloc_size; + cq->nslots = nslots; + cq->head = cq->tail = 0; + spin_lock_init(&cq->lock); + return cq; +} +EXPORT_SYMBOL_GPL(visor_charqueue_create); + + + +void visor_charqueue_enqueue(CHARQUEUE *charqueue, unsigned char c) +{ + int alloc_slots = charqueue->nslots+1; /* 1 slot is always empty */ + + spin_lock(&charqueue->lock); + charqueue->head = (charqueue->head+1) % alloc_slots; + if (charqueue->head == charqueue->tail) + /* overflow; overwrite the oldest entry */ + charqueue->tail = (charqueue->tail+1) % alloc_slots; + charqueue->buf[charqueue->head] = c; + spin_unlock(&charqueue->lock); +} +EXPORT_SYMBOL_GPL(visor_charqueue_enqueue); + + + +BOOL visor_charqueue_is_empty(CHARQUEUE *charqueue) +{ + BOOL b; + + spin_lock(&charqueue->lock); + b = IS_EMPTY(charqueue); + spin_unlock(&charqueue->lock); + return b; +} +EXPORT_SYMBOL_GPL(visor_charqueue_is_empty); + + + +static int charqueue_dequeue_1(CHARQUEUE *charqueue) +{ + int alloc_slots = charqueue->nslots + 1; /* 1 slot is always empty */ + + if (IS_EMPTY(charqueue)) + return -1; + charqueue->tail = (charqueue->tail+1) % alloc_slots; + return charqueue->buf[charqueue->tail]; +} + + + +int charqueue_dequeue(CHARQUEUE *charqueue) +{ + int rc; + + spin_lock(&charqueue->lock); + rc = charqueue_dequeue_1(charqueue); + spin_unlock(&charqueue->lock); + return rc; +} + + + +int visor_charqueue_dequeue_n(CHARQUEUE *charqueue, unsigned char *buf, int n) +{ + int rc, counter = 0, c; + + spin_lock(&charqueue->lock); + for (;;) { + if (n <= 0) + break; /* no more buffer space */ + c = charqueue_dequeue_1(charqueue); + if (c < 0) + break; /* no more input */ + *buf = (unsigned char)(c); + buf++; + n--; + counter++; + } + rc = counter; + spin_unlock(&charqueue->lock); + return rc; +} +EXPORT_SYMBOL_GPL(visor_charqueue_dequeue_n); + + + +void visor_charqueue_destroy(CHARQUEUE *charqueue) +{ + if (charqueue == NULL) + return; + kfree(charqueue); +} +EXPORT_SYMBOL_GPL(visor_charqueue_destroy); diff --git a/drivers/staging/unisys/visorutil/charqueue.h b/drivers/staging/unisys/visorutil/charqueue.h new file mode 100644 index 00000000000..d6f16587a36 --- /dev/null +++ b/drivers/staging/unisys/visorutil/charqueue.h @@ -0,0 +1,37 @@ +/* charqueue.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. + */ + +#ifndef __CHARQUEUE_H__ +#define __CHARQUEUE_H__ + +#include "uniklog.h" +#include "timskmod.h" + +/* CHARQUEUE is an opaque structure to users. + * Fields are declared only in the implementation .c files. + */ +typedef struct CHARQUEUE_Tag CHARQUEUE; + +CHARQUEUE *visor_charqueue_create(ulong nslots); +void visor_charqueue_enqueue(CHARQUEUE *charqueue, unsigned char c); +int charqueue_dequeue(CHARQUEUE *charqueue); +int visor_charqueue_dequeue_n(CHARQUEUE *charqueue, unsigned char *buf, int n); +BOOL visor_charqueue_is_empty(CHARQUEUE *charqueue); +void visor_charqueue_destroy(CHARQUEUE *charqueue); + +#endif + diff --git a/drivers/staging/unisys/visorutil/easyproc.c b/drivers/staging/unisys/visorutil/easyproc.c new file mode 100644 index 00000000000..3b388494e2a --- /dev/null +++ b/drivers/staging/unisys/visorutil/easyproc.c @@ -0,0 +1,377 @@ +/* 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. + */ + +/** @file ********************************************************************* + * + * Handle procfs-specific tasks. + * Note that this file does not know about any module-specific things, nor + * does it know anything about what information to reveal as part of the proc + * entries. The 2 functions that take care of displaying device and + * driver specific information are passed as parameters to + * visor_easyproc_InitDriver(). + * + * void show_device_info(struct seq_file *seq, void *p); + * void show_driver_info(struct seq_file *seq); + * + * The second parameter to show_device_info is actually a pointer to the + * device-specific info to show. It is the context that was originally + * passed to visor_easyproc_InitDevice(). + * + ****************************************************************************** + */ + +#include <linux/proc_fs.h> + +#include "uniklog.h" +#include "timskmod.h" +#include "easyproc.h" + +#define MYDRVNAME "easyproc" + + + +/* + * /proc/<ProcId> ProcDir + * /proc/<ProcId>/driver ProcDriverDir + * /proc/<ProcId>/driver/diag ProcDriverDiagFile + * /proc/<ProcId>/device ProcDeviceDir + * /proc/<ProcId>/device/0 procDevicexDir + * /proc/<ProcId>/device/0/diag procDevicexDiagFile + */ + + +static ssize_t proc_write_device(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos); +static ssize_t proc_write_driver(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos); + +static struct proc_dir_entry * + createProcDir(char *name, struct proc_dir_entry *parent) +{ + struct proc_dir_entry *p = proc_mkdir_mode(name, S_IFDIR, parent); + + if (p == NULL) + ERRDRV("failed to create /proc directory %s", name); + return p; +} + +static int seq_show_driver(struct seq_file *seq, void *offset); +static int proc_open_driver(struct inode *inode, struct file *file) +{ + return single_open(file, seq_show_driver, PDE_DATA(inode)); +} +static const struct file_operations proc_fops_driver = { + .open = proc_open_driver, + .read = seq_read, + .write = proc_write_driver, + .llseek = seq_lseek, + .release = single_release, +}; + +static int seq_show_device(struct seq_file *seq, void *offset); +static int seq_show_device_property(struct seq_file *seq, void *offset); +static int proc_open_device(struct inode *inode, struct file *file) +{ + return single_open(file, seq_show_device, PDE_DATA(inode)); +} +static const struct file_operations proc_fops_device = { + .open = proc_open_device, + .read = seq_read, + .write = proc_write_device, + .llseek = seq_lseek, + .release = single_release, +}; +static int proc_open_device_property(struct inode *inode, struct file *file) +{ + return single_open(file, seq_show_device_property, PDE_DATA(inode)); +} +static const struct file_operations proc_fops_device_property = { + .open = proc_open_device_property, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + + + +void visor_easyproc_InitDriver(struct easyproc_driver_info *pdriver, + char *procId, + void (*show_driver_info)(struct seq_file *), + void (*show_device_info)(struct seq_file *, + void *)) +{ + memset(pdriver, 0, sizeof(struct easyproc_driver_info)); + pdriver->ProcId = procId; + if (pdriver->ProcId == NULL) + ERRDRV("ProcId cannot be NULL (trouble ahead)!"); + pdriver->Show_driver_info = show_driver_info; + pdriver->Show_device_info = show_device_info; + if (pdriver->ProcDir == NULL) + pdriver->ProcDir = createProcDir(pdriver->ProcId, NULL); + if ((pdriver->ProcDir != NULL) && (pdriver->ProcDriverDir == NULL)) + pdriver->ProcDriverDir = createProcDir("driver", + pdriver->ProcDir); + if ((pdriver->ProcDir != NULL) && (pdriver->ProcDeviceDir == NULL)) + pdriver->ProcDeviceDir = createProcDir("device", + pdriver->ProcDir); + if ((pdriver->ProcDriverDir != NULL) && + (pdriver->ProcDriverDiagFile == NULL)) { + pdriver->ProcDriverDiagFile = + proc_create_data("diag", 0, + pdriver->ProcDriverDir, + &proc_fops_driver, pdriver); + if (pdriver->ProcDriverDiagFile == NULL) + ERRDRV("failed to register /proc/%s/driver/diag entry", + pdriver->ProcId); + } +} +EXPORT_SYMBOL_GPL(visor_easyproc_InitDriver); + + + +void visor_easyproc_InitDriverEx(struct easyproc_driver_info *pdriver, + char *procId, + void (*show_driver_info)(struct seq_file *), + void (*show_device_info)(struct seq_file *, + void *), + void (*write_driver_info)(char *buf, + size_t count, + loff_t *ppos), + void (*write_device_info)(char *buf, + size_t count, + loff_t *ppos, + void *p)) +{ + visor_easyproc_InitDriver(pdriver, procId, + show_driver_info, show_device_info); + pdriver->Write_driver_info = write_driver_info; + pdriver->Write_device_info = write_device_info; +} +EXPORT_SYMBOL_GPL(visor_easyproc_InitDriverEx); + + + +void visor_easyproc_DeInitDriver(struct easyproc_driver_info *pdriver) +{ + if (pdriver->ProcDriverDiagFile != NULL) { + remove_proc_entry("diag", pdriver->ProcDriverDir); + pdriver->ProcDriverDiagFile = NULL; + } + if (pdriver->ProcDriverDir != NULL) { + remove_proc_entry("driver", pdriver->ProcDir); + pdriver->ProcDriverDir = NULL; + } + if (pdriver->ProcDeviceDir != NULL) { + remove_proc_entry("device", pdriver->ProcDir); + pdriver->ProcDeviceDir = NULL; + } + if (pdriver->ProcDir != NULL) { + remove_proc_entry(pdriver->ProcId, NULL); + pdriver->ProcDir = NULL; + } + pdriver->ProcId = NULL; + pdriver->Show_driver_info = NULL; + pdriver->Show_device_info = NULL; + pdriver->Write_driver_info = NULL; + pdriver->Write_device_info = NULL; +} +EXPORT_SYMBOL_GPL(visor_easyproc_DeInitDriver); + + + +void visor_easyproc_InitDevice(struct easyproc_driver_info *pdriver, + struct easyproc_device_info *p, int devno, + void *devdata) +{ + if ((pdriver->ProcDeviceDir != NULL) && (p->procDevicexDir == NULL)) { + char s[29]; + + sprintf(s, "%d", devno); + p->procDevicexDir = createProcDir(s, pdriver->ProcDeviceDir); + p->devno = devno; + } + p->devdata = devdata; + p->pdriver = pdriver; + p->devno = devno; + if ((p->procDevicexDir != NULL) && (p->procDevicexDiagFile == NULL)) { + p->procDevicexDiagFile = + proc_create_data("diag", 0, p->procDevicexDir, + &proc_fops_device, p); + if (p->procDevicexDiagFile == NULL) + ERRDEVX(devno, "failed to register /proc/%s/device/%d/diag entry", + pdriver->ProcId, devno + ); + } + memset(&(p->device_property_info[0]), 0, + sizeof(p->device_property_info)); +} +EXPORT_SYMBOL_GPL(visor_easyproc_InitDevice); + + + +void visor_easyproc_CreateDeviceProperty(struct easyproc_device_info *p, + void (*show_property_info) + (struct seq_file *, void *), + char *property_name) +{ + size_t i; + struct easyproc_device_property_info *px = NULL; + + if (p->procDevicexDir == NULL) { + ERRDRV("state error"); + return; + } + for (i = 0; i < ARRAY_SIZE(p->device_property_info); i++) { + if (p->device_property_info[i].procEntry == NULL) { + px = &(p->device_property_info[i]); + break; + } + } + if (!px) { + ERRDEVX(p->devno, "too many device properties"); + return; + } + px->devdata = p->devdata; + px->pdriver = p->pdriver; + px->procEntry = proc_create_data(property_name, 0, p->procDevicexDir, + &proc_fops_device_property, px); + if (strlen(property_name)+1 > sizeof(px->property_name)) { + ERRDEVX(p->devno, "device property name %s too long", + property_name); + return; + } + strcpy(px->property_name, property_name); + if (px->procEntry == NULL) { + ERRDEVX(p->devno, "failed to register /proc/%s/device/%d/%s entry", + p->pdriver->ProcId, p->devno, property_name + ); + return; + } + px->show_device_property_info = show_property_info; +} +EXPORT_SYMBOL_GPL(visor_easyproc_CreateDeviceProperty); + + + +void visor_easyproc_DeInitDevice(struct easyproc_driver_info *pdriver, + struct easyproc_device_info *p, int devno) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(p->device_property_info); i++) { + if (p->device_property_info[i].procEntry != NULL) { + struct easyproc_device_property_info *px = + &(p->device_property_info[i]); + remove_proc_entry(px->property_name, p->procDevicexDir); + px->procEntry = NULL; + } + } + if (p->procDevicexDiagFile != NULL) { + remove_proc_entry("diag", p->procDevicexDir); + p->procDevicexDiagFile = NULL; + } + if (p->procDevicexDir != NULL) { + char s[29]; + + sprintf(s, "%d", devno); + remove_proc_entry(s, pdriver->ProcDeviceDir); + p->procDevicexDir = NULL; + } + p->devdata = NULL; + p->pdriver = NULL; +} +EXPORT_SYMBOL_GPL(visor_easyproc_DeInitDevice); + + + +static int seq_show_driver(struct seq_file *seq, void *offset) +{ + struct easyproc_driver_info *p = + (struct easyproc_driver_info *)(seq->private); + if (!p) + return 0; + (*(p->Show_driver_info))(seq); + return 0; +} + + + +static int seq_show_device(struct seq_file *seq, void *offset) +{ + struct easyproc_device_info *p = + (struct easyproc_device_info *)(seq->private); + if ((!p) || (!(p->pdriver))) + return 0; + (*(p->pdriver->Show_device_info))(seq, p->devdata); + return 0; +} + + + +static int seq_show_device_property(struct seq_file *seq, void *offset) +{ + struct easyproc_device_property_info *p = + (struct easyproc_device_property_info *)(seq->private); + if ((!p) || (!(p->show_device_property_info))) + return 0; + (*(p->show_device_property_info))(seq, p->devdata); + return 0; +} + + + +static ssize_t proc_write_driver(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct seq_file *seq = (struct seq_file *)file->private_data; + struct easyproc_driver_info *p = NULL; + char local_buf[256]; + + if (seq == NULL) + return 0; + p = (struct easyproc_driver_info *)(seq->private); + if ((!p) || (!(p->Write_driver_info))) + return 0; + if (count >= sizeof(local_buf)) + return -ENOMEM; + if (copy_from_user(local_buf, buffer, count)) + return -EFAULT; + local_buf[count] = '\0'; /* be friendly */ + (*(p->Write_driver_info))(local_buf, count, ppos); + return count; +} + + + +static ssize_t proc_write_device(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct seq_file *seq = (struct seq_file *)file->private_data; + struct easyproc_device_info *p = NULL; + char local_buf[256]; + + if (seq == NULL) + return 0; + p = (struct easyproc_device_info *)(seq->private); + if ((!p) || (!(p->pdriver)) || (!(p->pdriver->Write_device_info))) + return 0; + if (count >= sizeof(local_buf)) + return -ENOMEM; + if (copy_from_user(local_buf, buffer, count)) + return -EFAULT; + local_buf[count] = '\0'; /* be friendly */ + (*(p->pdriver->Write_device_info))(local_buf, count, ppos, p->devdata); + return count; +} diff --git a/drivers/staging/unisys/visorutil/easyproc.h b/drivers/staging/unisys/visorutil/easyproc.h new file mode 100644 index 00000000000..6ce7d5eb6ad --- /dev/null +++ b/drivers/staging/unisys/visorutil/easyproc.h @@ -0,0 +1,92 @@ +/* easyproc.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. + */ + +/** @file ********************************************************************* + * + * This describes the interfaces necessary for a simple /proc file + * implementation for a driver. + * + ****************************************************************************** + */ + +#ifndef __EASYPROC_H__ +#define __EASYPROC_H__ + +#include "timskmod.h" + + +struct easyproc_driver_info { + struct proc_dir_entry *ProcDir; + struct proc_dir_entry *ProcDriverDir; + struct proc_dir_entry *ProcDriverDiagFile; + struct proc_dir_entry *ProcDeviceDir; + char *ProcId; + void (*Show_device_info)(struct seq_file *seq, void *p); + void (*Show_driver_info)(struct seq_file *seq); + void (*Write_device_info)(char *buf, size_t count, + loff_t *ppos, void *p); + void (*Write_driver_info)(char *buf, size_t count, loff_t *ppos); +}; + +/* property is a file under /proc/<x>/device/<x>/<property_name> */ +struct easyproc_device_property_info { + char property_name[25]; + struct proc_dir_entry *procEntry; + struct easyproc_driver_info *pdriver; + void *devdata; + void (*show_device_property_info)(struct seq_file *seq, void *p); +}; + +struct easyproc_device_info { + struct proc_dir_entry *procDevicexDir; + struct proc_dir_entry *procDevicexDiagFile; + struct easyproc_driver_info *pdriver; + void *devdata; + int devno; + /* allow for a number of custom properties for each device: */ + struct easyproc_device_property_info device_property_info[10]; +}; + +void visor_easyproc_InitDevice(struct easyproc_driver_info *pdriver, + struct easyproc_device_info *p, int devno, + void *devdata); +void visor_easyproc_DeInitDevice(struct easyproc_driver_info *pdriver, + struct easyproc_device_info *p, int devno); +void visor_easyproc_InitDriver(struct easyproc_driver_info *pdriver, + char *procId, + void (*show_driver_info)(struct seq_file *), + void (*show_device_info)(struct seq_file *, + void *)); +void visor_easyproc_InitDriverEx(struct easyproc_driver_info *pdriver, + char *procId, + void (*show_driver_info)(struct seq_file *), + void (*show_device_info)(struct seq_file *, + void *), + void (*Write_driver_info)(char *buf, + size_t count, + loff_t *ppos), + void (*Write_device_info)(char *buf, + size_t count, + loff_t *ppos, + void *p)); +void visor_easyproc_DeInitDriver(struct easyproc_driver_info *pdriver); +void visor_easyproc_CreateDeviceProperty(struct easyproc_device_info *p, + void (*show_property_info) + (struct seq_file *, void *), + char *property_name); + +#endif diff --git a/drivers/staging/unisys/visorutil/memregion.h b/drivers/staging/unisys/visorutil/memregion.h new file mode 100644 index 00000000000..f4a65d2fcf0 --- /dev/null +++ b/drivers/staging/unisys/visorutil/memregion.h @@ -0,0 +1,43 @@ +/* memregion.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. + */ + +#ifndef __MEMREGION_H__ +#define __MEMREGION_H__ + +#include "timskmod.h" + +/* MEMREGION is an opaque structure to users. + * Fields are declared only in the implementation .c files. + */ +typedef struct MEMREGION_Tag MEMREGION; + +MEMREGION *visor_memregion_create(HOSTADDRESS physaddr, ulong nbytes); +MEMREGION *visor_memregion_create_overlapped(MEMREGION *parent, + ulong offset, ulong nbytes); +int visor_memregion_resize(MEMREGION *memregion, ulong newsize); +int visor_memregion_read(MEMREGION *memregion, + ulong offset, void *dest, ulong nbytes); +int visor_memregion_write(MEMREGION *memregion, + ulong offset, void *src, ulong nbytes); +void visor_memregion_destroy(MEMREGION *memregion); +HOSTADDRESS visor_memregion_get_physaddr(MEMREGION *memregion); +ulong visor_memregion_get_nbytes(MEMREGION *memregion); +void memregion_dump(MEMREGION *memregion, char *s, + ulong off, ulong len, struct seq_file *seq); +void __iomem *visor_memregion_get_pointer(MEMREGION *memregion); + +#endif diff --git a/drivers/staging/unisys/visorutil/memregion_direct.c b/drivers/staging/unisys/visorutil/memregion_direct.c new file mode 100644 index 00000000000..28dfba0490f --- /dev/null +++ b/drivers/staging/unisys/visorutil/memregion_direct.c @@ -0,0 +1,223 @@ +/* memregion_direct.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. + */ + +/* + * This is an implementation of memory regions that can be used to read/write + * channel memory (in main memory of the host system) from code running in + * a virtual partition. + */ +#include "uniklog.h" +#include "timskmod.h" +#include "memregion.h" + +#define MYDRVNAME "memregion" + +struct MEMREGION_Tag { + HOSTADDRESS physaddr; + ulong nbytes; + void __iomem *mapped; + BOOL requested; + BOOL overlapped; +}; + +static BOOL mapit(MEMREGION *memregion); +static void unmapit(MEMREGION *memregion); + +MEMREGION * +visor_memregion_create(HOSTADDRESS physaddr, ulong nbytes) +{ + MEMREGION *rc = NULL; + MEMREGION *memregion = kzalloc(sizeof(MEMREGION), + GFP_KERNEL | __GFP_NORETRY); + if (memregion == NULL) { + ERRDRV("visor_memregion_create allocation failed"); + return NULL; + } + memregion->physaddr = physaddr; + memregion->nbytes = nbytes; + memregion->overlapped = FALSE; + if (!mapit(memregion)) { + rc = NULL; + goto Away; + } + rc = memregion; +Away: + if (rc == NULL) { + if (memregion != NULL) { + visor_memregion_destroy(memregion); + memregion = NULL; + } + } + return rc; +} +EXPORT_SYMBOL_GPL(visor_memregion_create); + +MEMREGION * +visor_memregion_create_overlapped(MEMREGION *parent, ulong offset, ulong nbytes) +{ + MEMREGION *memregion = NULL; + + if (parent == NULL) { + ERRDRV("%s parent is NULL", __func__); + return NULL; + } + if (parent->mapped == NULL) { + ERRDRV("%s parent is not mapped!", __func__); + return NULL; + } + if ((offset >= parent->nbytes) || + ((offset + nbytes) >= parent->nbytes)) { + ERRDRV("%s range (%lu,%lu) out of parent range", + __func__, offset, nbytes); + return NULL; + } + memregion = kzalloc(sizeof(MEMREGION), GFP_KERNEL|__GFP_NORETRY); + if (memregion == NULL) { + ERRDRV("%s allocation failed", __func__); + return NULL; + } + + memregion->physaddr = parent->physaddr + offset; + memregion->nbytes = nbytes; + memregion->mapped = ((u8 __iomem *) (parent->mapped)) + offset; + memregion->requested = FALSE; + memregion->overlapped = TRUE; + return memregion; +} +EXPORT_SYMBOL_GPL(visor_memregion_create_overlapped); + + +static BOOL +mapit(MEMREGION *memregion) +{ + ulong physaddr = (ulong) (memregion->physaddr); + ulong nbytes = memregion->nbytes; + + memregion->requested = FALSE; + if (!request_mem_region(physaddr, nbytes, MYDRVNAME)) + ERRDRV("cannot reserve channel memory @0x%lx for 0x%lx-- no big deal", physaddr, nbytes); + else + memregion->requested = TRUE; + memregion->mapped = ioremap_cache(physaddr, nbytes); + if (memregion->mapped == NULL) { + ERRDRV("cannot ioremap_cache channel memory @0x%lx for 0x%lx", + physaddr, nbytes); + return FALSE; + } + return TRUE; +} + +static void +unmapit(MEMREGION *memregion) +{ + if (memregion->mapped != NULL) { + iounmap(memregion->mapped); + memregion->mapped = NULL; + } + if (memregion->requested) { + release_mem_region((ulong) (memregion->physaddr), + memregion->nbytes); + memregion->requested = FALSE; + } +} + +HOSTADDRESS +visor_memregion_get_physaddr(MEMREGION *memregion) +{ + return memregion->physaddr; +} +EXPORT_SYMBOL_GPL(visor_memregion_get_physaddr); + +ulong +visor_memregion_get_nbytes(MEMREGION *memregion) +{ + return memregion->nbytes; +} +EXPORT_SYMBOL_GPL(visor_memregion_get_nbytes); + +void __iomem * +visor_memregion_get_pointer(MEMREGION *memregion) +{ + return memregion->mapped; +} +EXPORT_SYMBOL_GPL(visor_memregion_get_pointer); + +int +visor_memregion_resize(MEMREGION *memregion, ulong newsize) +{ + if (newsize == memregion->nbytes) + return 0; + if (memregion->overlapped) + /* no error check here - we no longer know the + * parent's range! + */ + memregion->nbytes = newsize; + else { + unmapit(memregion); + memregion->nbytes = newsize; + if (!mapit(memregion)) + return -1; + } + return 0; +} +EXPORT_SYMBOL_GPL(visor_memregion_resize); + + +static int +memregion_readwrite(BOOL is_write, + MEMREGION *memregion, ulong offset, + void *local, ulong nbytes) +{ + if (offset + nbytes > memregion->nbytes) { + ERRDRV("memregion_readwrite offset out of range!!"); + return -EFAULT; + } + if (is_write) + memcpy_toio(memregion->mapped + offset, local, nbytes); + else + memcpy_fromio(local, memregion->mapped + offset, nbytes); + + return 0; +} + +int +visor_memregion_read(MEMREGION *memregion, ulong offset, void *dest, + ulong nbytes) +{ + return memregion_readwrite(FALSE, memregion, offset, dest, nbytes); +} +EXPORT_SYMBOL_GPL(visor_memregion_read); + +int +visor_memregion_write(MEMREGION *memregion, ulong offset, void *src, + ulong nbytes) +{ + return memregion_readwrite(TRUE, memregion, offset, src, nbytes); +} +EXPORT_SYMBOL_GPL(visor_memregion_write); + +void +visor_memregion_destroy(MEMREGION *memregion) +{ + if (memregion == NULL) + return; + if (!memregion->overlapped) + unmapit(memregion); + kfree(memregion); +} +EXPORT_SYMBOL_GPL(visor_memregion_destroy); + diff --git a/drivers/staging/unisys/visorutil/periodic_work.c b/drivers/staging/unisys/visorutil/periodic_work.c new file mode 100644 index 00000000000..38a60ce8c27 --- /dev/null +++ b/drivers/staging/unisys/visorutil/periodic_work.c @@ -0,0 +1,237 @@ +/* periodic_work.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. + */ + +/* + * Helper functions to schedule periodic work in Linux kernel mode. + */ + +#include "uniklog.h" +#include "timskmod.h" +#include "periodic_work.h" + +#define MYDRVNAME "periodic_work" + + + +struct PERIODIC_WORK_Tag { + rwlock_t lock; + struct delayed_work work; + void (*workfunc)(void *); + void *workfuncarg; + BOOL is_scheduled; + BOOL want_to_stop; + ulong jiffy_interval; + struct workqueue_struct *workqueue; + const char *devnam; +}; + + + +static void periodic_work_func(struct work_struct *work) +{ + PERIODIC_WORK *periodic_work = + container_of(work, struct PERIODIC_WORK_Tag, work.work); + (*periodic_work->workfunc)(periodic_work->workfuncarg); +} + + + +PERIODIC_WORK *visor_periodic_work_create(ulong jiffy_interval, + struct workqueue_struct *workqueue, + void (*workfunc)(void *), + void *workfuncarg, + const char *devnam) +{ + PERIODIC_WORK *periodic_work = kzalloc(sizeof(PERIODIC_WORK), + GFP_KERNEL | __GFP_NORETRY); + if (periodic_work == NULL) { + ERRDRV("periodic_work allocation failed "); + return NULL; + } + rwlock_init(&periodic_work->lock); + periodic_work->jiffy_interval = jiffy_interval; + periodic_work->workqueue = workqueue; + periodic_work->workfunc = workfunc; + periodic_work->workfuncarg = workfuncarg; + periodic_work->devnam = devnam; + return periodic_work; +} +EXPORT_SYMBOL_GPL(visor_periodic_work_create); + + + +void visor_periodic_work_destroy(PERIODIC_WORK *periodic_work) +{ + if (periodic_work == NULL) + return; + kfree(periodic_work); +} +EXPORT_SYMBOL_GPL(visor_periodic_work_destroy); + + + +/** Call this from your periodic work worker function to schedule the next + * call. + * If this function returns FALSE, there was a failure and the + * periodic work is no longer scheduled + */ +BOOL visor_periodic_work_nextperiod(PERIODIC_WORK *periodic_work) +{ + BOOL rc = FALSE; + + write_lock(&periodic_work->lock); + if (periodic_work->want_to_stop) { + periodic_work->is_scheduled = FALSE; + periodic_work->want_to_stop = FALSE; + rc = TRUE; /* yes, TRUE; see visor_periodic_work_stop() */ + goto Away; + } else if (queue_delayed_work(periodic_work->workqueue, + &periodic_work->work, + periodic_work->jiffy_interval) < 0) { + ERRDEV(periodic_work->devnam, "queue_delayed_work failed!"); + periodic_work->is_scheduled = FALSE; + rc = FALSE; + goto Away; + } + rc = TRUE; +Away: + write_unlock(&periodic_work->lock); + return rc; +} +EXPORT_SYMBOL_GPL(visor_periodic_work_nextperiod); + + + +/** This function returns TRUE iff new periodic work was actually started. + * If this function returns FALSE, then no work was started + * (either because it was already started, or because of a failure). + */ +BOOL visor_periodic_work_start(PERIODIC_WORK *periodic_work) +{ + BOOL rc = FALSE; + + write_lock(&periodic_work->lock); + if (periodic_work->is_scheduled) { + rc = FALSE; + goto Away; + } + if (periodic_work->want_to_stop) { + ERRDEV(periodic_work->devnam, + "dev_start_periodic_work failed!"); + rc = FALSE; + goto Away; + } + INIT_DELAYED_WORK(&periodic_work->work, &periodic_work_func); + if (queue_delayed_work(periodic_work->workqueue, + &periodic_work->work, + periodic_work->jiffy_interval) < 0) { + ERRDEV(periodic_work->devnam, + "%s queue_delayed_work failed!", __func__); + rc = FALSE; + goto Away; + } + periodic_work->is_scheduled = TRUE; + rc = TRUE; +Away: + write_unlock(&periodic_work->lock); + return rc; + +} +EXPORT_SYMBOL_GPL(visor_periodic_work_start); + + + + +/** This function returns TRUE iff your call actually stopped the periodic + * work. + * + * -- PAY ATTENTION... this is important -- + * + * NO NO #1 + * + * Do NOT call this function from some function that is running on the + * same workqueue as the work you are trying to stop might be running + * on! If you violate this rule, visor_periodic_work_stop() MIGHT work, + * but it also MIGHT get hung up in an infinite loop saying + * "waiting for delayed work...". This will happen if the delayed work + * you are trying to cancel has been put in the workqueue list, but can't + * run yet because we are running that same workqueue thread right now. + * + * Bottom line: If you need to call visor_periodic_work_stop() from a + * workitem, be sure the workitem is on a DIFFERENT workqueue than the + * workitem that you are trying to cancel. + * + * If I could figure out some way to check for this "no no" condition in + * the code, I would. It would have saved me the trouble of writing this + * long comment. And also, don't think this is some "theoretical" race + * condition. It is REAL, as I have spent the day chasing it. + * + * NO NO #2 + * + * Take close note of the locks that you own when you call this function. + * You must NOT own any locks that are needed by the periodic work + * function that is currently installed. If you DO, a deadlock may result, + * because stopping the periodic work often involves waiting for the last + * iteration of the periodic work function to complete. Again, if you hit + * this deadlock, you will get hung up in an infinite loop saying + * "waiting for delayed work...". + */ +BOOL visor_periodic_work_stop(PERIODIC_WORK *periodic_work) +{ + BOOL stopped_something = FALSE; + + write_lock(&periodic_work->lock); + stopped_something = periodic_work->is_scheduled && + (!periodic_work->want_to_stop); + while (periodic_work->is_scheduled) { + periodic_work->want_to_stop = TRUE; + if (cancel_delayed_work(&periodic_work->work)) { + /* We get here if the delayed work was pending as + * delayed work, but was NOT run. + */ + ASSERT(periodic_work->is_scheduled); + periodic_work->is_scheduled = FALSE; + } else { + /* If we get here, either the delayed work: + * - was run, OR, + * - is running RIGHT NOW on another processor, OR, + * - wasn't even scheduled (there is a miniscule + * timing window where this could be the case) + * flush_workqueue() would make sure it is finished + * executing, but that still isn't very useful, which + * explains the loop... + */ + } + if (periodic_work->is_scheduled) { + write_unlock(&periodic_work->lock); + WARNDEV(periodic_work->devnam, + "waiting for delayed work..."); + /* We rely on the delayed work function running here, + * and eventually calling + * visor_periodic_work_nextperiod(), + * which will see that want_to_stop is set, and + * subsequently clear is_scheduled. + */ + SLEEPJIFFIES(10); + write_lock(&periodic_work->lock); + } else + periodic_work->want_to_stop = FALSE; + } + write_unlock(&periodic_work->lock); + return stopped_something; +} +EXPORT_SYMBOL_GPL(visor_periodic_work_stop); diff --git a/drivers/staging/unisys/visorutil/procobjecttree.c b/drivers/staging/unisys/visorutil/procobjecttree.c new file mode 100644 index 00000000000..5c8c95c5151 --- /dev/null +++ b/drivers/staging/unisys/visorutil/procobjecttree.c @@ -0,0 +1,354 @@ +/* procobjecttree.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. + */ + +#include "procobjecttree.h" + +#define MYDRVNAME "procobjecttree" + + + +/** This is context info that we stash in each /proc file entry, which we + * need in order to call the callback function that supplies the /proc read + * info for that file. + */ +typedef struct { + void (*show_property)(struct seq_file *, void *, int); + MYPROCOBJECT *procObject; + int propertyIndex; + +} PROCDIRENTRYCONTEXT; + +/** This describes the attributes of a tree rooted at + * <procDirRoot>/<name[0]>/<name[1]>/... + * Properties for each object of this type will be located under + * <procDirRoot>/<name[0]>/<name[1]>/.../<objectName>/<propertyName>. + */ +struct MYPROCTYPE_Tag { + const char **name; /**< node names for this type, ending with NULL */ + int nNames; /**< num of node names in <name> */ + + /** root dir for this type tree in /proc */ + struct proc_dir_entry *procDirRoot; + + struct proc_dir_entry **procDirs; /**< for each node in <name> */ + + /** bottom dir where objects will be rooted; i.e., this is + * <procDirRoot>/<name[0]>/<name[1]>/.../, which is the same as the + * last entry in the <procDirs> array. */ + struct proc_dir_entry *procDir; + + /** name for each property that objects of this type can have */ + const char **propertyNames; + + int nProperties; /**< num of names in <propertyNames> */ + + /** Call this, passing MYPROCOBJECT.context and the property index + * whenever someone reads the proc entry */ + void (*show_property)(struct seq_file *, void *, int); +}; + + + +struct MYPROCOBJECT_Tag { + MYPROCTYPE *type; + + /** This is the name of the dir node in /proc under which the + * properties of this object will appear as files. */ + char *name; + + int namesize; /**< number of bytes allocated for name */ + void *context; /**< passed to MYPROCTYPE.show_property */ + + /** <type.procDirRoot>/<type.name[0]>/<type.name[1]>/.../<name> */ + struct proc_dir_entry *procDir; + + /** a proc dir entry for each of the properties of the object; + * properties are identified in MYPROCTYPE.propertyNames, so each of + * the <procDirProperties> describes a single file like + * <type.procDirRoot>/<type.name[0]>/<type.name[1]>/... + * /<name>/<propertyName> + */ + struct proc_dir_entry **procDirProperties; + + /** this is a holding area for the context information that is needed + * to run the /proc callback function */ + PROCDIRENTRYCONTEXT *procDirPropertyContexts; +}; + + + +static struct proc_dir_entry * +createProcDir(const char *name, struct proc_dir_entry *parent) +{ + struct proc_dir_entry *p = proc_mkdir_mode(name, S_IFDIR, parent); + + if (p == NULL) + ERRDRV("failed to create /proc directory %s", name); + return p; +} + +static struct proc_dir_entry * +createProcFile(const char *name, struct proc_dir_entry *parent, + const struct file_operations *fops, void *data) +{ + struct proc_dir_entry *p = proc_create_data(name, 0, parent, + fops, data); + if (p == NULL) + ERRDRV("failed to create /proc file %s", name); + return p; +} + +static int seq_show(struct seq_file *seq, void *offset); +static int proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, seq_show, PDE_DATA(inode)); +} + +static const struct file_operations proc_fops = { + .open = proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + + + +MYPROCTYPE *visor_proc_CreateType(struct proc_dir_entry *procDirRoot, + const char **name, + const char **propertyNames, + void (*show_property)(struct seq_file *, + void *, int)) +{ + int i = 0; + MYPROCTYPE *rc = NULL, *type = NULL; + struct proc_dir_entry *parent = NULL; + + if (procDirRoot == NULL) { + ERRDRV("procDirRoot cannot be NULL!\n"); + goto Away; + } + if (name == NULL || name[0] == NULL) { + ERRDRV("name must contain at least 1 node name!\n"); + goto Away; + } + type = kzalloc(sizeof(MYPROCTYPE), GFP_KERNEL | __GFP_NORETRY); + if (type == NULL) { + ERRDRV("out of memory\n"); + goto Away; + } + type->name = name; + type->propertyNames = propertyNames; + type->nProperties = 0; + type->nNames = 0; + type->show_property = show_property; + type->procDirRoot = procDirRoot; + if (type->propertyNames != NULL) + while (type->propertyNames[type->nProperties] != NULL) + type->nProperties++; + while (type->name[type->nNames] != NULL) + type->nNames++; + type->procDirs = kzalloc((type->nNames + 1) * + sizeof(struct proc_dir_entry *), + GFP_KERNEL | __GFP_NORETRY); + if (type->procDirs == NULL) { + ERRDRV("out of memory\n"); + goto Away; + } + parent = procDirRoot; + for (i = 0; i < type->nNames; i++) { + type->procDirs[i] = createProcDir(type->name[i], parent); + if (type->procDirs[i] == NULL) { + rc = NULL; + goto Away; + } + parent = type->procDirs[i]; + } + type->procDir = type->procDirs[type->nNames-1]; + rc = type; +Away: + if (rc == NULL) { + if (type != NULL) { + visor_proc_DestroyType(type); + type = NULL; + } + } + return rc; +} +EXPORT_SYMBOL_GPL(visor_proc_CreateType); + + + +void visor_proc_DestroyType(MYPROCTYPE *type) +{ + if (type == NULL) + return; + if (type->procDirs != NULL) { + int i = type->nNames-1; + + while (i >= 0) { + if (type->procDirs[i] != NULL) { + struct proc_dir_entry *parent = NULL; + + if (i == 0) + parent = type->procDirRoot; + else + parent = type->procDirs[i-1]; + remove_proc_entry(type->name[i], parent); + } + i--; + } + kfree(type->procDirs); + type->procDirs = NULL; + } + kfree(type); +} +EXPORT_SYMBOL_GPL(visor_proc_DestroyType); + + + +MYPROCOBJECT *visor_proc_CreateObject(MYPROCTYPE *type, + const char *name, void *context) +{ + MYPROCOBJECT *obj = NULL, *rc = NULL; + int i = 0; + + if (type == NULL) { + ERRDRV("type cannot be NULL\n"); + goto Away; + } + obj = kzalloc(sizeof(MYPROCOBJECT), GFP_KERNEL | __GFP_NORETRY); + if (obj == NULL) { + ERRDRV("out of memory\n"); + goto Away; + } + obj->type = type; + obj->context = context; + if (name == NULL) { + obj->name = NULL; + obj->procDir = type->procDir; + } else { + obj->namesize = strlen(name)+1; + obj->name = kmalloc(obj->namesize, GFP_KERNEL | __GFP_NORETRY); + if (obj->name == NULL) { + obj->namesize = 0; + ERRDRV("out of memory\n"); + goto Away; + } + strcpy(obj->name, name); + obj->procDir = createProcDir(obj->name, type->procDir); + if (obj->procDir == NULL) { + goto Away; + } + } + obj->procDirPropertyContexts = + kzalloc((type->nProperties + 1) * sizeof(PROCDIRENTRYCONTEXT), + GFP_KERNEL | __GFP_NORETRY); + if (obj->procDirPropertyContexts == NULL) { + ERRDRV("out of memory\n"); + goto Away; + } + obj->procDirProperties = + kzalloc((type->nProperties + 1) * sizeof(struct proc_dir_entry *), + GFP_KERNEL | __GFP_NORETRY); + if (obj->procDirProperties == NULL) { + ERRDRV("out of memory\n"); + goto Away; + } + for (i = 0; i < type->nProperties; i++) { + obj->procDirPropertyContexts[i].procObject = obj; + obj->procDirPropertyContexts[i].propertyIndex = i; + obj->procDirPropertyContexts[i].show_property = + type->show_property; + if (type->propertyNames[i][0] != '\0') { + /* only create properties that have names */ + obj->procDirProperties[i] = + createProcFile(type->propertyNames[i], + obj->procDir, &proc_fops, + &obj->procDirPropertyContexts[i]); + if (obj->procDirProperties[i] == NULL) { + rc = NULL; + goto Away; + } + } + } + rc = obj; +Away: + if (rc == NULL) { + if (obj != NULL) { + visor_proc_DestroyObject(obj); + obj = NULL; + } + } + return rc; +} +EXPORT_SYMBOL_GPL(visor_proc_CreateObject); + + + +void visor_proc_DestroyObject(MYPROCOBJECT *obj) +{ + MYPROCTYPE *type = NULL; + + if (obj == NULL) + return; + type = obj->type; + if (type == NULL) + return; + if (obj->procDirProperties != NULL) { + int i = 0; + + for (i = 0; i < type->nProperties; i++) { + if (obj->procDirProperties[i] != NULL) { + remove_proc_entry(type->propertyNames[i], + obj->procDir); + obj->procDirProperties[i] = NULL; + } + } + kfree(obj->procDirProperties); + obj->procDirProperties = NULL; + } + if (obj->procDirPropertyContexts != NULL) { + kfree(obj->procDirPropertyContexts); + obj->procDirPropertyContexts = NULL; + } + if (obj->procDir != NULL) { + if (obj->name != NULL) + remove_proc_entry(obj->name, type->procDir); + obj->procDir = NULL; + } + if (obj->name != NULL) { + kfree(obj->name); + obj->name = NULL; + } + kfree(obj); +} +EXPORT_SYMBOL_GPL(visor_proc_DestroyObject); + + + +static int seq_show(struct seq_file *seq, void *offset) +{ + PROCDIRENTRYCONTEXT *ctx = (PROCDIRENTRYCONTEXT *)(seq->private); + + if (ctx == NULL) { + ERRDRV("I don't have a freakin' clue..."); + return 0; + } + (*ctx->show_property)(seq, ctx->procObject->context, + ctx->propertyIndex); + return 0; +} diff --git a/drivers/staging/unisys/visorutil/visorkmodutils.c b/drivers/staging/unisys/visorutil/visorkmodutils.c new file mode 100644 index 00000000000..10d77cb6ee9 --- /dev/null +++ b/drivers/staging/unisys/visorutil/visorkmodutils.c @@ -0,0 +1,126 @@ +/* timskmodutils.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. + */ + +#include "uniklog.h" +#include "timskmod.h" + +#define MYDRVNAME "timskmodutils" + +/* s-Par uses the Intel processor's VT-X features to separate groups of + * processors into partitions. The firmware sets the hypervisor bit and + * reports an ID in the HV capabilities leaf so that the partition's OS + * knows s-Par is present and managing the processors. + */ + +#define UNISYS_SPAR_LEAF_ID 0x40000000 + +/* The s-Par leaf ID returns "UnisysSpar64" encoded across ebx, ecx, edx */ +#define UNISYS_SPAR_ID_EBX 0x73696e55 +#define UNISYS_SPAR_ID_ECX 0x70537379 +#define UNISYS_SPAR_ID_EDX 0x34367261 + +int unisys_spar_platform; +EXPORT_SYMBOL_GPL(unisys_spar_platform); + +/** Callers to interfaces that set __GFP_NORETRY flag below + * must check for a NULL (error) result as we are telling the + * kernel interface that it is okay to fail. + */ + +void *kmalloc_kernel(size_t siz) +{ + return kmalloc(siz, GFP_KERNEL | __GFP_NORETRY); +} + +/* Use these handy-dandy seq_file_xxx functions if you want to call some + * functions that write stuff into a seq_file, but you actually just want + * to dump that output into a buffer. Use them as follows: + * - call visor_seq_file_new_buffer to create the seq_file (you supply the buf) + * - call whatever functions you want that take a seq_file as an argument + * (the buf you supplied will get the output data) + * - call visor_seq_file_done_buffer to dispose of your seq_file + */ +struct seq_file *visor_seq_file_new_buffer(void *buf, size_t buf_size) +{ + struct seq_file *rc = NULL; + struct seq_file *m = kmalloc_kernel(sizeof(struct seq_file)); + + if (m == NULL) { + rc = NULL; + goto Away; + } + memset(m, 0, sizeof(struct seq_file)); + m->buf = buf; + m->size = buf_size; + rc = m; +Away: + if (rc == NULL) { + visor_seq_file_done_buffer(m); + m = NULL; + } + return rc; +} +EXPORT_SYMBOL_GPL(visor_seq_file_new_buffer); + + + +void visor_seq_file_done_buffer(struct seq_file *m) +{ + if (!m) + return; + kfree(m); +} +EXPORT_SYMBOL_GPL(visor_seq_file_done_buffer); + +static __init uint32_t +visorutil_spar_detect(void) +{ + unsigned int eax, ebx, ecx, edx; + + if (cpu_has_hypervisor) { + /* check the ID */ + cpuid(UNISYS_SPAR_LEAF_ID, &eax, &ebx, &ecx, &edx); + return (ebx == UNISYS_SPAR_ID_EBX) && + (ecx == UNISYS_SPAR_ID_ECX) && + (edx == UNISYS_SPAR_ID_EDX); + } else + return 0; + +} + + + + +static __init int +visorutil_mod_init(void) +{ + if (visorutil_spar_detect()) { + unisys_spar_platform = TRUE; + return 0; + } else + return -ENODEV; +} + +static __exit void +visorutil_mod_exit(void) +{ +} + +module_init(visorutil_mod_init); +module_exit(visorutil_mod_exit); + +MODULE_LICENSE("GPL"); |
