From b4b7ff4b08e691656c9d77c758fc355833128ac0 Mon Sep 17 00:00:00 2001 From: André Fabian Silva Delgado Date: Wed, 20 Jan 2016 14:01:31 -0300 Subject: Linux-libre 4.4-gnu --- .../Documentation/ABI/sysfs-platform-visorchipset | 2 + drivers/staging/unisys/Documentation/overview.txt | 458 +++++--- drivers/staging/unisys/Kconfig | 2 + drivers/staging/unisys/Makefile | 2 + drivers/staging/unisys/TODO | 25 +- drivers/staging/unisys/include/channel.h | 19 +- drivers/staging/unisys/include/diagchannel.h | 5 - drivers/staging/unisys/include/iochannel.h | 26 +- drivers/staging/unisys/include/periodic_work.h | 12 +- drivers/staging/unisys/visorbus/periodic_work.c | 8 +- drivers/staging/unisys/visorbus/visorchannel.c | 8 +- drivers/staging/unisys/visorbus/visorchipset.c | 2 +- drivers/staging/unisys/visorbus/vmcallinterface.h | 54 +- drivers/staging/unisys/visorhba/Kconfig | 14 + drivers/staging/unisys/visorhba/Makefile | 10 + drivers/staging/unisys/visorhba/visorhba_main.c | 1241 ++++++++++++++++++++ drivers/staging/unisys/visorinput/Kconfig | 10 + drivers/staging/unisys/visorinput/Makefile | 7 + .../staging/unisys/visorinput/ultrainputreport.h | 74 ++ drivers/staging/unisys/visorinput/visorinput.c | 715 +++++++++++ drivers/staging/unisys/visornic/visornic_main.c | 70 +- 21 files changed, 2467 insertions(+), 297 deletions(-) create mode 100644 drivers/staging/unisys/visorhba/Kconfig create mode 100644 drivers/staging/unisys/visorhba/Makefile create mode 100644 drivers/staging/unisys/visorhba/visorhba_main.c create mode 100644 drivers/staging/unisys/visorinput/Kconfig create mode 100644 drivers/staging/unisys/visorinput/Makefile create mode 100644 drivers/staging/unisys/visorinput/ultrainputreport.h create mode 100644 drivers/staging/unisys/visorinput/visorinput.c (limited to 'drivers/staging/unisys') diff --git a/drivers/staging/unisys/Documentation/ABI/sysfs-platform-visorchipset b/drivers/staging/unisys/Documentation/ABI/sysfs-platform-visorchipset index 28f8f1233..b0498ff32 100644 --- a/drivers/staging/unisys/Documentation/ABI/sysfs-platform-visorchipset +++ b/drivers/staging/unisys/Documentation/ABI/sysfs-platform-visorchipset @@ -1,3 +1,5 @@ +This file describes sysfs entries beneath /devices/platform/visorchipset. + What: install/error Date: 7/18/2014 KernelVersion: TBD diff --git a/drivers/staging/unisys/Documentation/overview.txt b/drivers/staging/unisys/Documentation/overview.txt index 8d078e4de..c2d8dd4a2 100644 --- a/drivers/staging/unisys/Documentation/overview.txt +++ b/drivers/staging/unisys/Documentation/overview.txt @@ -1,76 +1,282 @@ +1. Overview +----------- -Overview - -This document describes the driver set for Unisys Secure Partitioning (s-Par®). +This document describes the driver set for Unisys Secure Partitioning +(s-Par(R)). 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 +normally be unsharable, specifically: + +* visornic - network interface +* visorhba - scsi disk adapter +* visorinput - keyboard and mouse + +These drivers conform to the standard Linux bus/device model described +within Documentation/driver-model/, and utilize a driver named visorbus to +present the virtual busses involved. Drivers in the 'visor*' driver set are +commonly referred to as "guest drivers" or "client drivers". All drivers +except visorbus expose a device of a specific usable class to the Linux guest +environment (e.g., block, network, or input), and are collectively referred +to as "function drivers". + +The back-end for each device is owned and managed by a small, +single-purpose service partition in the s-Par firmware, which communicates +with each guest partition sharing that device through an area of shared memory +called a "channel". In s-Par nomenclature, the back-end is often referred to +as the "service partition", "IO partition" (for virtual network and scsi disk +devices), or "console partition" (for virtual keyboard and mouse devices). + +Each virtual device requires exactly 1 dedicated channel, which the guest +driver and back-end use to communicate. The hypervisor need not intervene +(other than normal interrupt handling) in the interactions that occur across +this channel. + +NOT covered in this document: + +* s-Par also supports sharing physical PCI adapters via SR-IOV, but + because this requires no specific support in the guest partitions, it will + not be discussed in this document. Shared SR-IOV devices should be used + wherever possible for highest performance. + +* Because the s-Par back-end provides a standard EFI framebuffer to each + guest, the already-existing efifb Linux driver is used to provide guest + video access. Thus, the only s-Par-unique support that is necessary to + provide a guest graphics console are for keyboard and mouse (via visorinput). + + +2. Driver Descriptions +---------------------- + +2.1. visorbus +------------- + +2.1.1. Overview +--------------- + +The visorbus driver handles the virtual busses on which all of the virtual +devices reside. It provides a registration function named +visorbus_register_visor_driver() that is called by each of the function +drivers at initialization time, which the function driver uses to tell +visorbus about the device classes (via specifying a list of device type +GUIDs) it wants to handle. For use by function drivers, visorbus provides +implementation for struct visor_driver and struct visor_device, as well +as utility functions for communicating with the back-end. + +visorbus is associated with ACPI id "PNP0A07" in modules.alias, so if built +as a module it will typically be loaded automatically via standard udev or +systemd (God help us) configurations. + +visorbus can similarly force auto-loading of function drivers for virtual +devices it discovers, as it includes a MODALIAS environment variable of this +form in the hotplug uevent environment when each virtual device is +discovered: + + visorbus: + +visorbus notifies each function driver when a device of its registered class +arrives and departs, by calling the function driver's probe() and remove() +methods. + +The actual struct device objects that correspond to each virtual bus and +each virtual device are created and owned by visorbus. These device objects +are created in response to messages from the s-Par back-end received on a +special control channel called the "controlvm channel" (each guest partition +has access to exactly 1 controlvm channel), and have a lifetime that is +independent of the function drivers that control them. + +2.1.2. "struct visor device" Function Driver Interfaces +------------------------------------------------------- + +The interface between visorbus and its function drivers is defined in +visorbus.h, and described below. + +When a visor function driver loads, it calls visorbus_register_visor_driver() +to register itself with visorbus. The significant information passed in this +exchange is as follows: + +* the GUID(s) of the channel type(s) that are handled by this driver, as + well as a "friendly name" identifying each (this will be published under + /sys/devices/visorbus/dev) + +* the addresses of callback functions to be called whenever a virtual + device/channel with the appropriate channel-type GUID(s) appears or + disappears + +* the address of a "channel_interrupt" function, which will be automatically + called at specific intervals to enable the driver to poll the device + channel for activity + +The following functions implemented within each function driver will be +called automatically by the visorbus driver at appropriate times: + +* The probe() function notifies about the creation of each new virtual + device/channel instance. + +* The remove() function notifies about the destruction of a virtual + device/channel instance. + +* The channel_interrupt() function is called at frequent intervals to + give the function driver an opportunity to poll the virtual device channel + for requests. Information is passed to this function to enable the + function driver to use the visorchannel_signalinsert() and + visorchannel_signalremove() functions to respond to and initiate activity + over the channel. (Note that since it is the visorbus driver that + determines when this is called, it is very easy to switch to + interrupt-driven mechanisms when available for particular virtual device + types.) + +* The pause() function is called should it ever be necessary to direct the + function driver to temporarily stop accessing the device channel. An + example of when this is needed is when the service partition implementing + the back-end of the virtual device needs to be recovered. After a + successful return of pause(), the function driver must not access the + device channel until a subsequent resume() occurs. + +* The resume() function is the "book-end" to pause(), and is described above. + +If/when a function driver creates a Linux device (that needs to be accessed +from usermode), it calls visorbus_registerdevnode(), passing the major and +minor number of the device. (Of course not all function drivers will need +to do this.) This simply creates the appropriate "devmajorminor" sysfs entry +described below, so that a hotplug script can use it to create a device node. + +2.1.3. sysfs Advertised Information +----------------------------------- + +Because visorbus is a standard Linux bus driver in the model described in +Documentation/driver-model/, the hierarchy of s-Par virtual devices is +published in the sysfs tree beneath /bus/visorbus/, e.g., +/sys/bus/visorbus/devices/ might look like: + + vbus1:dev1 -> ../../../devices/visorbus1/vbus1:dev1 + vbus1:dev2 -> ../../../devices/visorbus1/vbus1:dev2 + vbus1:dev3 -> ../../../devices/visorbus1/vbus1:dev3 + vbus2:dev0 -> ../../../devices/visorbus2/vbus2:dev0 + vbus2:dev1 -> ../../../devices/visorbus2/vbus2:dev1 + vbus2:dev2 -> ../../../devices/visorbus2/vbus2:dev2 + visorbus1 -> ../../../devices/visorbus1 + visorbus2 -> ../../../devices/visorbus2 + +visor_device notes: + +* Each visorbus entry denotes the existence of a struct visor_device + denoting virtual bus #. A unique s-Par channel exists for each such + virtual bus. + +* Virtual bus numbers uniquely identify s-Par back-end service partitions. + In this example, bus 1 corresponds to the s-Par console partition + (controls keyboard, video, and mouse), whereas bus 2 corresponds to the + s-Par IO partition (controls network and disk). + +* Each vbus:dev entry denotes the existence of a struct visor_device + denoting virtual device # outboard of virtual bus #. A unique s-Par + channel exists for each such virtual device. + +* If a function driver has loaded and claimed a particular device, the + bus/visorbus/devices/vbus:dev/driver symlink will indicate that + function driver. + +Every active visorbus device will have a sysfs subtree under: + + /sys/devices/visorbus/vbus:dev/ + +The following files exist under /sys/devices/visorbus/vbus:dev: + + subsystem link to sysfs tree that describes the + visorbus bus type; e.g.: + ../../../bus/visorbus + + driver link to sysfs tree that describes the + function driver controlling this device; + e.g.: + ../../../bus/visorbus/drivers/visorhba + Note that this "driver" link will not exist + if the appropriate function driver has not + been loaded yet. + + devmajorminor + + if applicable, each file here identifies (via + ... its file contents) the + ":" needed for a device node to + enable access from usermode. There is exactly + one file here for each different device node + that can be accessed (from usermode). Note + that this info is provided by a particular + function driver, so these will not exist + until AFTER the appropriate function driver + controlling this device class is loaded. + + channel properties of the device channel (all in + ascii text format) + + clientpartition handle identifying the guest (client) side + of this channel, e.g. 0x10000000. + + nbytes total size of this channel in bytes + + physaddr the guest physical address for the base of + the channel + + typeguid a GUID identifying the channel type, in + xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx notation + + typename a "friendly name" for this channel type, e.g., + "keyboard". Note that this name is provided by + a particular function driver, so "typename" + will return an empty string until AFTER the + appropriate function driver controlling this + channel type is loaded + + zoneguid a GUID identifying the channel zone, in + xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx notation + + +2.2. visorhba +------------- + +The visorhba driver registers with visorbus as the function driver to +handle virtual scsi disk devices, specified using the +SPAR_VHBA_CHANNEL_PROTOCOL_UUID type in the visorbus_register_visor_driver() +call. visorhba uses scsi_add_host() to expose a Linux block device +(e.g., /sys/block/) in the guest environment for each s-Par virtual device. + +visorhba 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 +When compiled as a module, visorhba can be autoloaded by visorbus in +standard udev/systemd environments, as it includes the modules.alias +definition: + + "visorbus:"+SPAR_VHBA_CHANNEL_PROTOCOL_UUID_STR + +i.e.: + + alias visorbus:414815ed-c58c-11da-95a9-00e08161165f visorhba + -The virtnic module provides a paravirtualized network interface to a +2.3. visornic +------------- + +The visornic driver registers with visorbus as the function driver to +handle virtual network devices, specified using the +SPAR_VNIC_CHANNEL_PROTOCOL_UUID type in the visorbus_register_visor_driver() +call. visornic uses register_netdev() to expose a Linux device of class net +(e.g., /sys/class/net/) in the guest environment for each s-Par virtual +device. + +visornic 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 @@ -79,96 +285,72 @@ 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. +When compiled as a module, visornic can be autoloaded by visorbus in +standard udev/systemd environments, as it includes the modules.alias +definition: -sparstop + "visorbus:"+SPAR_VNIC_CHANNEL_PROTOCOL_UUID_STR -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. +i.e.: -visordiag + alias visorbus:8cd5994d-c58e-11da-95a9-00e08161165f visornic -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 +2.4. visorinput +--------------- -The modules described in this section provide functions and -abstractions to support the modules described in the previous -sections, to avoid having duplicated functionality. +The visorinput driver registers with visorbus as the function driver to +handle human input devices, specified using the +SPAR_KEYBOARD_CHANNEL_PROTOCOL_UUID and SPAR_MOUSE_CHANNEL_PROTOCOL_UUID +types in the visorbus_register_visor_driver() call. visorinput uses +input_register_device() to expose devices of class input +(e.g., /sys/class/input/) for virtual keyboard and virtual mouse devices. +A s-Par virtual keyboard device maps 1-to-1 with a Linux input device +named "visor Keyboard", while a s-Par virtual mouse device has 2 Linux input +devices created for it: 1 named "visor Wheel", and 1 named "visor Mouse". -visornoop +By registering as input class devices, modern versions of X will +automatically find and properly use s-Par virtual keyboard and mouse devices. +As the s-Par back-end reports keyboard and mouse activity via events on the +virtual device channel, the visorinput driver delivers the activity to the +Linux environment by calling input_report_key() and input_report_abs(). -The visornoop module is a placeholder that responds to device -create/destroy messages that are currently not in use by linux guests. +You can interact with the guest console using the usyscon Partition Desktop +(a.k.a., "pd") application, provided as part of s-Par. After installing the +usyscon Partition Desktop into a Linux environment via the +usyscon_partitiondesktop-*.rpm, or into a Windows environment via +PartitionDesktop.msi, you will be able to launch a console for your guest +Linux environment by clicking the console icon in the s-Par web UI. -visoruislib +When compiled as a module, visorinput can be autoloaded by visorbus in +standard udev/systemd environments, as it includes the modules.alias +definition: -The visoruislib module is a support library, used to handle requests -from virtpci. + "visorbus:"+SPAR_MOUSE_CHANNEL_PROTOCOL_UUID_STR + "visorbus:"+SPAR_KEYBOARD_CHANNEL_PROTOCOL_UUID_STR -visorchannelstub +i.e.: -The visorchannelstub module provides support routines for storing and -retrieving data from a channel. + alias visorbus:c73416d0-b0b8-44af-b304-9d2ae99f1b3d visorinput + alias visorbus:addf07d4-94a9-46e2-81c3-61abcdbdbd87 visorinput -visorchannel -The visorchannel module is a support library that abstracts reading -and writing a channel in memory. +3. Minimum Required Driver Set +------------------------------ -visorutil +visorbus is required for every Linux guest running under s-Par. -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. +visorhba is typically required for a Linux guest running under s-Par, as it +is required if your guest boot disk is a virtual device provided by the s-Par +back-end, which is the default configuration. However, for advanced +configurations where the Linux guest boots via an SR-IOV-provided HBA or +SAN disk for example, visorhba is not technically required. -Minimum Required Driver Set +visornic is typically required for a Linux guest running under s-Par, as it +is required if your guest network interface is a virtual device provided by +the s-Par back-end, which is the default configuration. However, for +configurations where the Linux guest is provided with an SR-IOV NIC +for example, visornic is not technically required. -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. +visorinput is only required for a Linux guest running under s-Par if you +require graphics-mode access to your guest console. diff --git a/drivers/staging/unisys/Kconfig b/drivers/staging/unisys/Kconfig index 624abe66c..4f1f5e624 100644 --- a/drivers/staging/unisys/Kconfig +++ b/drivers/staging/unisys/Kconfig @@ -13,5 +13,7 @@ if UNISYSSPAR source "drivers/staging/unisys/visorbus/Kconfig" source "drivers/staging/unisys/visornic/Kconfig" +source "drivers/staging/unisys/visorinput/Kconfig" +source "drivers/staging/unisys/visorhba/Kconfig" endif # UNISYSSPAR diff --git a/drivers/staging/unisys/Makefile b/drivers/staging/unisys/Makefile index a515ebc4f..20eb09853 100644 --- a/drivers/staging/unisys/Makefile +++ b/drivers/staging/unisys/Makefile @@ -3,3 +3,5 @@ # obj-$(CONFIG_UNISYS_VISORBUS) += visorbus/ obj-$(CONFIG_UNISYS_VISORNIC) += visornic/ +obj-$(CONFIG_UNISYS_VISORINPUT) += visorinput/ +obj-$(CONFIG_UNISYS_VISORHBA) += visorhba/ diff --git a/drivers/staging/unisys/TODO b/drivers/staging/unisys/TODO index 034ac61c4..d863f266b 100644 --- a/drivers/staging/unisys/TODO +++ b/drivers/staging/unisys/TODO @@ -1,19 +1,14 @@ 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 - + - enhance visornic to use channel_interrupt() hook instead of a + kernel thread + - enhance visorhba to use channel_interrupt() hook instead of a + kernel thread + - teach visorbus to handle virtual interrupts triggered by s-Par + back-end, and call function driver's channel_interrupt() function + when they occur + - enhance debugfs interfaces (e.g., per device, etc.) + - upgrade/remove deprecated workqueue operations + - move individual drivers into proper driver subsystems Patches to: Greg Kroah-Hartman diff --git a/drivers/staging/unisys/include/channel.h b/drivers/staging/unisys/include/channel.h index da0b5387f..c6c24423a 100644 --- a/drivers/staging/unisys/include/channel.h +++ b/drivers/staging/unisys/include/channel.h @@ -32,7 +32,7 @@ */ #define __SUPERVISOR_CHANNEL_H__ -#define SIGNATURE_16(A, B) ((A) | (B<<8)) +#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) \ @@ -42,10 +42,10 @@ #define lengthof(TYPE, MEMBER) (sizeof(((TYPE *)0)->MEMBER)) #endif #ifndef COVERQ -#define COVERQ(v, d) (((v)+(d)-1) / (d)) +#define COVERQ(v, d) (((v) + (d) - 1) / (d)) #endif #ifndef COVER -#define COVER(v, d) ((d)*COVERQ(v, d)) +#define COVER(v, d) ((d) * COVERQ(v, d)) #endif #define ULTRA_CHANNEL_PROTOCOL_SIGNATURE SIGNATURE_32('E', 'C', 'N', 'L') @@ -152,7 +152,6 @@ ULTRA_CHANNELCLI_STRING(u32 v) #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 */ struct channel_header { u64 signature; /* Signature */ @@ -192,7 +191,7 @@ struct channel_header { u8 filler[1]; /* Pad out to 128 byte cacheline */ /* Please add all new single-byte values below here */ u8 recover_channel; -}; +} __packed; #define ULTRA_CHANNEL_ENABLE_INTS (0x1ULL << 0) @@ -230,9 +229,7 @@ struct signal_queue_header { * to denote trouble with client's * fields */ u8 filler[12]; /* Pad out to 64 byte cacheline */ -}; - -#pragma pack(pop) +} __packed; #define spar_signal_init(chan, QHDRFLD, QDATAFLD, QDATATYPE, ver, typ) \ do { \ @@ -241,11 +238,11 @@ struct signal_queue_header { chan->QHDRFLD.chtype = typ; \ chan->QHDRFLD.size = sizeof(chan->QDATAFLD); \ chan->QHDRFLD.signal_size = sizeof(QDATATYPE); \ - chan->QHDRFLD.sig_base_offset = (u64)(chan->QDATAFLD)- \ + chan->QHDRFLD.sig_base_offset = (u64)(chan->QDATAFLD) - \ (u64)(&chan->QHDRFLD); \ chan->QHDRFLD.max_slots = \ - sizeof(chan->QDATAFLD)/sizeof(QDATATYPE); \ - chan->QHDRFLD.max_signals = chan->QHDRFLD.max_slots-1; \ + sizeof(chan->QDATAFLD) / sizeof(QDATATYPE); \ + chan->QHDRFLD.max_signals = chan->QHDRFLD.max_slots - 1;\ } while (0) /* Generic function useful for validating any type of channel when it is diff --git a/drivers/staging/unisys/include/diagchannel.h b/drivers/staging/unisys/include/diagchannel.h index d2d35685d..6e813c77b 100644 --- a/drivers/staging/unisys/include/diagchannel.h +++ b/drivers/staging/unisys/include/diagchannel.h @@ -16,11 +16,6 @@ #ifndef _DIAG_CHANNEL_H_ #define _DIAG_CHANNEL_H_ -#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... - */ - /* 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 diff --git a/drivers/staging/unisys/include/iochannel.h b/drivers/staging/unisys/include/iochannel.h index a55981234..14e656ff7 100644 --- a/drivers/staging/unisys/include/iochannel.h +++ b/drivers/staging/unisys/include/iochannel.h @@ -147,6 +147,10 @@ struct phys_info { u16 pi_len; } __packed; +#define MIN_NUMSIGNALS 64 + +/* structs with pragma pack */ + struct guest_phys_info { u64 address; u64 length; @@ -183,7 +187,7 @@ struct vhba_config_max { /* 20 bytes */ } __packed; struct uiscmdrsp_scsi { - void *scsicmd; /* the handle to the cmd that was received - + u64 handle; /* 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 */ @@ -437,24 +441,22 @@ struct uiscmdrsp_scsitaskmgmt { struct uisscsi_dest vdest; /* the vdisk for which this task mgmt is generated */ - void *scsicmd; + u64 handle; - /* This is some handle that the guest has saved off for its own use. + /* This is a 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; + u64 notify_handle; /* 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 thread waiting for taskmgmt * command completion. Its value is preserved by iopart & returned * as is in the task mgmt rsp. */ - void *notifyresult; + u64 notifyresult_handle; /* this is a handle to location in guest where the result of the * taskmgmt command (result field) is to saved off when the response @@ -486,24 +488,22 @@ struct uiscmdrsp_vdiskmgmt { struct uisscsi_dest vdest; /* the vdisk for which this task mgmt is generated */ - void *scsicmd; + u64 handle; - /* This is some handle that the guest has saved off for its own use. + /* This is a 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; + u64 notify_handle; /* For linux guests, this is a pointer to wait_queue_head that a * thread is waiting on to see if the tskmgmt command has completed. - * For win32 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 thread waiting for taskmgmt * command completion. Its value is preserved by iopart & returned * as is in the task mgmt rsp. */ - void *notifyresult; + u64 notifyresult_handle; /* this is a handle to location in guest where the result of the * taskmgmt command (result field) is to saved off when the response diff --git a/drivers/staging/unisys/include/periodic_work.h b/drivers/staging/unisys/include/periodic_work.h index 4e19c28dc..0b3335a4b 100644 --- a/drivers/staging/unisys/include/periodic_work.h +++ b/drivers/staging/unisys/include/periodic_work.h @@ -21,17 +21,17 @@ #include #include - /* PERIODIC_WORK an opaque structure to users. * Fields are declared only in the implementation .c files. */ struct periodic_work; -struct periodic_work *visor_periodic_work_create(ulong jiffy_interval, - struct workqueue_struct *workqueue, - void (*workfunc)(void *), - void *workfuncarg, - const char *devnam); +struct 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(struct periodic_work *pw); bool visor_periodic_work_nextperiod(struct periodic_work *pw); bool visor_periodic_work_start(struct periodic_work *pw); diff --git a/drivers/staging/unisys/visorbus/periodic_work.c b/drivers/staging/unisys/visorbus/periodic_work.c index a3631c359..115b7aa95 100644 --- a/drivers/staging/unisys/visorbus/periodic_work.c +++ b/drivers/staging/unisys/visorbus/periodic_work.c @@ -86,8 +86,8 @@ bool visor_periodic_work_nextperiod(struct periodic_work *pw) pw->want_to_stop = false; rc = true; /* yes, true; see visor_periodic_work_stop() */ goto unlock; - } else if (queue_delayed_work(pw->workqueue, &pw->work, - pw->jiffy_interval) < 0) { + } else if (!queue_delayed_work(pw->workqueue, &pw->work, + pw->jiffy_interval)) { pw->is_scheduled = false; rc = false; goto unlock; @@ -117,8 +117,8 @@ bool visor_periodic_work_start(struct periodic_work *pw) goto unlock; } INIT_DELAYED_WORK(&pw->work, &periodic_work_func); - if (queue_delayed_work(pw->workqueue, &pw->work, - pw->jiffy_interval) < 0) { + if (!queue_delayed_work(pw->workqueue, &pw->work, + pw->jiffy_interval)) { rc = false; goto unlock; } diff --git a/drivers/staging/unisys/visorbus/visorchannel.c b/drivers/staging/unisys/visorbus/visorchannel.c index 2693c46af..a4e117f10 100644 --- a/drivers/staging/unisys/visorbus/visorchannel.c +++ b/drivers/staging/unisys/visorbus/visorchannel.c @@ -279,7 +279,7 @@ visorchannel_clear(struct visorchannel *channel, ulong offset, u8 ch, int written = 0; u8 *buf; - buf = (u8 *) __get_free_page(GFP_KERNEL); + buf = (u8 *)__get_free_page(GFP_KERNEL); if (!buf) return -ENOMEM; @@ -301,7 +301,7 @@ visorchannel_clear(struct visorchannel *channel, ulong offset, u8 ch, err = 0; cleanup: - free_page((unsigned long) buf); + free_page((unsigned long)buf); return err; } EXPORT_SYMBOL_GPL(visorchannel_clear); @@ -332,7 +332,7 @@ EXPORT_SYMBOL_GPL(visorchannel_get_header); */ #define SIG_WRITE_FIELD(channel, queue, sig_hdr, FIELD) \ (visorchannel_write(channel, \ - SIG_QUEUE_OFFSET(&channel->chan_hdr, queue)+ \ + SIG_QUEUE_OFFSET(&channel->chan_hdr, queue) +\ offsetof(struct signal_queue_header, FIELD), \ &((sig_hdr)->FIELD), \ sizeof((sig_hdr)->FIELD)) >= 0) @@ -468,7 +468,7 @@ signalinsert_inner(struct visorchannel *channel, u32 queue, void *msg) SIG_QUEUE_OFFSET(&channel->chan_hdr, queue) + offsetof(struct signal_queue_header, num_overflows), - &(sig_hdr.num_overflows), + &sig_hdr.num_overflows, sizeof(sig_hdr.num_overflows)); return false; } diff --git a/drivers/staging/unisys/visorbus/visorchipset.c b/drivers/staging/unisys/visorbus/visorchipset.c index 94419c36d..07594f438 100644 --- a/drivers/staging/unisys/visorbus/visorchipset.c +++ b/drivers/staging/unisys/visorbus/visorchipset.c @@ -696,7 +696,7 @@ struct visor_busdev { static int match_visorbus_dev_by_id(struct device *dev, void *data) { struct visor_device *vdev = to_visor_device(dev); - struct visor_busdev *id = (struct visor_busdev *)data; + struct visor_busdev *id = data; u32 bus_no = id->bus_no; u32 dev_no = id->dev_no; diff --git a/drivers/staging/unisys/visorbus/vmcallinterface.h b/drivers/staging/unisys/visorbus/vmcallinterface.h index 7abd27a61..c8d8483bd 100644 --- a/drivers/staging/unisys/visorbus/vmcallinterface.h +++ b/drivers/staging/unisys/visorbus/vmcallinterface.h @@ -46,21 +46,13 @@ enum vmcall_monitor_interface_method_tuple { /* VMCALL identification tuples */ 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 @@ -90,9 +82,6 @@ enum vmcall_monitor_interface_method_tuple { /* VMCALL identification tuples */ /* Structures for IO VMCALLs */ -/* ///////////// BEGIN PRAGMA PACK PUSH 1 ///////////////////////// */ -/* ///////////// ONLY STRUCT TYPE SHOULD BE BELOW */ -#pragma pack(push, 1) /* Parameters to VMCALL_IO_CONTROLVM_ADDR interface */ struct vmcall_io_controlvm_addr_params { /* The Guest-relative physical address of the ControlVm channel. @@ -102,47 +91,6 @@ struct vmcall_io_controlvm_addr_params { * in with the appropriate address. */ u32 channel_bytes; /* contents provided by this VMCALL (OUT) */ u8 unused[4]; /* Unused Bytes in the 64-Bit Aligned Struct */ -}; - -#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 */ -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 address; /* contents provided by this VMCALL (OUT) */ -}; - -#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 */ -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 address; /* contents provided by this VMCALL (OUT) */ -}; - -#pragma pack(pop) -/* ///////////// END PRAGMA PACK PUSH 1 /////////////////////////// */ - -/* Parameters to VMCALL_CHANNEL_MISMATCH interface */ -struct vmcall_channel_version_mismatch_params { - u8 chname[32]; /* Null terminated string giving name of channel - * (IN) */ - u8 item_name[32]; /* Null terminated string giving name of - * mismatched item (IN) */ - u32 line_no; /* line# where invoked. (IN) */ - u8 file_name[36]; /* source code where invoked - Null terminated - * string (IN) */ -}; +} __packed; #endif /* __IOMONINTF_H__ */ diff --git a/drivers/staging/unisys/visorhba/Kconfig b/drivers/staging/unisys/visorhba/Kconfig new file mode 100644 index 000000000..241d80382 --- /dev/null +++ b/drivers/staging/unisys/visorhba/Kconfig @@ -0,0 +1,14 @@ +# +# Unisys visorhba configuration +# + +config UNISYS_VISORHBA + tristate "Unisys visorhba driver" + depends on UNISYSSPAR && UNISYS_VISORBUS && SCSI + ---help--- + The Unisys visorhba driver provides support for s-Par HBA + devices exposed on the s-Par visorbus. When a message is sent + to visorbus to create a HBA device, the probe function of + visorhba is called to create the scsi device. + If you say Y here, you will enable the Unisys visorhba driver. + diff --git a/drivers/staging/unisys/visorhba/Makefile b/drivers/staging/unisys/visorhba/Makefile new file mode 100644 index 000000000..a8a8e0e0f --- /dev/null +++ b/drivers/staging/unisys/visorhba/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for Unisys channel +# + +obj-$(CONFIG_UNISYS_VISORHBA) += visorhba.o + +visorhba-y := visorhba_main.o + +ccflags-y += -Idrivers/staging/unisys/include + diff --git a/drivers/staging/unisys/visorhba/visorhba_main.c b/drivers/staging/unisys/visorhba/visorhba_main.c new file mode 100644 index 000000000..c119f20df --- /dev/null +++ b/drivers/staging/unisys/visorhba/visorhba_main.c @@ -0,0 +1,1241 @@ +/* Copyright (c) 2012 - 2015 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 +#include +#include +#include +#include +#include +#include + +#include "visorbus.h" +#include "iochannel.h" + +/* The Send and Receive Buffers of the IO Queue may both be full */ + +#define IOS_ERROR_THRESHOLD 1000 +/* MAX_BUF = 6 lines x 10 MAXVHBA x 80 characters + * = 4800 bytes ~ 2^13 = 8192 bytes + */ +#define MAX_BUF 8192 +#define MAX_PENDING_REQUESTS (MIN_NUMSIGNALS * 2) +#define VISORHBA_ERROR_COUNT 30 +#define VISORHBA_OPEN_MAX 1 + +static int visorhba_queue_command_lck(struct scsi_cmnd *scsicmd, + void (*visorhba_cmnd_done) + (struct scsi_cmnd *)); +#ifdef DEF_SCSI_QCMD +static DEF_SCSI_QCMD(visorhba_queue_command) +#else +#define visorhba_queue_command visorhba_queue_command_lck +#endif +static int visorhba_probe(struct visor_device *dev); +static void visorhba_remove(struct visor_device *dev); +static int visorhba_pause(struct visor_device *dev, + visorbus_state_complete_func complete_func); +static int visorhba_resume(struct visor_device *dev, + visorbus_state_complete_func complete_func); + +static ssize_t info_debugfs_read(struct file *file, char __user *buf, + size_t len, loff_t *offset); +static struct dentry *visorhba_debugfs_dir; +static const struct file_operations debugfs_info_fops = { + .read = info_debugfs_read, +}; + +/* GUIDS for HBA channel type supported by this driver */ +static struct visor_channeltype_descriptor visorhba_channel_types[] = { + /* Note that the only channel type we expect to be reported by the + * bus driver is the SPAR_VHBA channel. + */ + { SPAR_VHBA_CHANNEL_PROTOCOL_UUID, "sparvhba" }, + { NULL_UUID_LE, NULL } +}; + +/* This is used to tell the visor bus driver which types of visor devices + * we support, and what functions to call when a visor device that we support + * is attached or removed. + */ +static struct visor_driver visorhba_driver = { + .name = "visorhba", + .owner = THIS_MODULE, + .channel_types = visorhba_channel_types, + .probe = visorhba_probe, + .remove = visorhba_remove, + .pause = visorhba_pause, + .resume = visorhba_resume, + .channel_interrupt = NULL, +}; +MODULE_DEVICE_TABLE(visorbus, visorhba_channel_types); +MODULE_ALIAS("visorbus:" SPAR_VHBA_CHANNEL_PROTOCOL_UUID_STR); + +struct visor_thread_info { + struct task_struct *task; + struct completion has_stopped; + int id; +}; + +struct visordisk_info { + u32 valid; + u32 channel, id, lun; /* Disk Path */ + atomic_t ios_threshold; + atomic_t error_count; + struct visordisk_info *next; +}; + +struct scsipending { + struct uiscmdrsp cmdrsp; + void *sent; /* The Data being tracked */ + char cmdtype; /* Type of pointer that is being stored */ +}; + +/* Work Data for dar_work_queue */ +struct diskaddremove { + u8 add; /* 0-remove, 1-add */ + struct Scsi_Host *shost; /* Scsi Host for this visorhba instance */ + u32 channel, id, lun; /* Disk Path */ + struct diskaddremove *next; +}; + +/* Each scsi_host has a host_data area that contains this struct. */ +struct visorhba_devdata { + struct Scsi_Host *scsihost; + struct visor_device *dev; + struct list_head dev_info_list; + /* Tracks the requests that have been forwarded to + * the IOVM and haven't returned yet + */ + struct scsipending pending[MAX_PENDING_REQUESTS]; + /* Start search for next pending free slot here */ + unsigned int nextinsert; + spinlock_t privlock; /* lock to protect data in devdata */ + 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; + u64 __iomem *flags_addr; + atomic_t interrupt_rcvd; + wait_queue_head_t rsp_queue; + struct visordisk_info head; + unsigned int max_buff_len; + int devnum; + struct visor_thread_info threadinfo; + int thread_wait_ms; +}; + +struct visorhba_devices_open { + struct visorhba_devdata *devdata; +}; + +static struct visorhba_devices_open visorhbas_open[VISORHBA_OPEN_MAX]; + +#define for_each_vdisk_match(iter, list, match) \ + for (iter = &list->head; iter->next; iter = iter->next) \ + if ((iter->channel == match->channel) && \ + (iter->id == match->id) && \ + (iter->lun == match->lun)) +/** + * visor_thread_start - starts a thread for the device + * @thrinfo: The thread to start + * @threadfn: Function the thread starts + * @thrcontext: Context to pass to the thread, i.e. devdata + * @name: string describing name of thread + * + * Starts a thread for the device. + * + * Return 0 on success; + */ +static int visor_thread_start(struct visor_thread_info *thrinfo, + int (*threadfn)(void *), + void *thrcontext, char *name) +{ + /* used to stop the thread */ + init_completion(&thrinfo->has_stopped); + thrinfo->task = kthread_run(threadfn, thrcontext, name); + if (IS_ERR(thrinfo->task)) { + thrinfo->id = 0; + return PTR_ERR(thrinfo->task); + } + thrinfo->id = thrinfo->task->pid; + return 0; +} + +/** + * add_scsipending_entry - save off io command that is pending in + * Service Partition + * @devdata: Pointer to devdata + * @cmdtype: Specifies the type of command pending + * @new: The command to be saved + * + * Saves off the io command that is being handled by the Service + * Partition so that it can be handled when it completes. If new is + * NULL it is assumed the entry refers only to the cmdrsp. + * Returns insert_location where entry was added, + * SCSI_MLQUEUE_DEVICE_BUSY if it can't + */ +static int add_scsipending_entry(struct visorhba_devdata *devdata, + char cmdtype, void *new) +{ + unsigned long flags; + struct scsipending *entry; + int insert_location; + + spin_lock_irqsave(&devdata->privlock, flags); + insert_location = devdata->nextinsert; + while (devdata->pending[insert_location].sent) { + insert_location = (insert_location + 1) % MAX_PENDING_REQUESTS; + if (insert_location == (int)devdata->nextinsert) { + spin_unlock_irqrestore(&devdata->privlock, flags); + return -1; + } + } + + entry = &devdata->pending[insert_location]; + memset(&entry->cmdrsp, 0, sizeof(entry->cmdrsp)); + entry->cmdtype = cmdtype; + if (new) + entry->sent = new; + else /* wants to send cmdrsp */ + entry->sent = &entry->cmdrsp; + devdata->nextinsert = (insert_location + 1) % MAX_PENDING_REQUESTS; + spin_unlock_irqrestore(&devdata->privlock, flags); + + return insert_location; +} + +/** + * del_scsipending_enty - removes an entry from the pending array + * @devdata: Device holding the pending array + * @del: Entry to remove + * + * Removes the entry pointed at by del and returns it. + * Returns the scsipending entry pointed at + */ +static void *del_scsipending_ent(struct visorhba_devdata *devdata, + int del) +{ + unsigned long flags; + void *sent = NULL; + + if (del < MAX_PENDING_REQUESTS) { + spin_lock_irqsave(&devdata->privlock, flags); + sent = devdata->pending[del].sent; + + devdata->pending[del].cmdtype = 0; + devdata->pending[del].sent = NULL; + spin_unlock_irqrestore(&devdata->privlock, flags); + } + + return sent; +} + +/** + * get_scsipending_cmdrsp - return the cmdrsp stored in a pending entry + * #ddata: Device holding the pending array + * @ent: Entry that stores the cmdrsp + * + * Each scsipending entry has a cmdrsp in it. The cmdrsp is only valid + * if the "sent" field is not NULL + * Returns a pointer to the cmdrsp. + */ +static struct uiscmdrsp *get_scsipending_cmdrsp(struct visorhba_devdata *ddata, + int ent) +{ + if (ddata->pending[ent].sent) + return &ddata->pending[ent].cmdrsp; + + return NULL; +} + +/** + * forward_taskmgmt_command - send taskmegmt command to the Service + * Partition + * @tasktype: Type of taskmgmt command + * @scsidev: Scsidev that issued command + * + * Create a cmdrsp packet and send it to the Serivce Partition + * that will service this request. + * Returns whether the command was queued successfully or not. + */ +static int forward_taskmgmt_command(enum task_mgmt_types tasktype, + struct scsi_cmnd *scsicmd) +{ + struct uiscmdrsp *cmdrsp; + struct scsi_device *scsidev = scsicmd->device; + struct visorhba_devdata *devdata = + (struct visorhba_devdata *)scsidev->host->hostdata; + int notifyresult = 0xffff; + wait_queue_head_t notifyevent; + int scsicmd_id = 0; + + if (devdata->serverdown || devdata->serverchangingstate) + return FAILED; + + scsicmd_id = add_scsipending_entry(devdata, CMD_SCSITASKMGMT_TYPE, + NULL); + if (scsicmd_id < 0) + return FAILED; + + cmdrsp = get_scsipending_cmdrsp(devdata, scsicmd_id); + + init_waitqueue_head(¬ifyevent); + + /* issue TASK_MGMT_ABORT_TASK */ + cmdrsp->cmdtype = CMD_SCSITASKMGMT_TYPE; + /* specify the event that has to be triggered when this */ + /* cmd is complete */ + cmdrsp->scsitaskmgmt.notify_handle = (u64)¬ifyevent; + cmdrsp->scsitaskmgmt.notifyresult_handle = (u64)¬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.handle = scsicmd_id; + + if (!visorchannel_signalinsert(devdata->dev->visorchannel, + IOCHAN_TO_IOPART, + cmdrsp)) + goto err_del_scsipending_ent; + + /* It can take the Service Partition up to 35 seconds to complete + * an IO in some cases, so wait 45 seconds and error out + */ + if (!wait_event_timeout(notifyevent, notifyresult != 0xffff, + msecs_to_jiffies(45000))) + goto err_del_scsipending_ent; + + if (tasktype == TASK_MGMT_ABORT_TASK) + scsicmd->result = (DID_ABORT << 16); + else + scsicmd->result = (DID_RESET << 16); + + scsicmd->scsi_done(scsicmd); + + return SUCCESS; + +err_del_scsipending_ent: + del_scsipending_ent(devdata, scsicmd_id); + return FAILED; +} + +/** + * visorhba_abort_handler - Send TASK_MGMT_ABORT_TASK + * @scsicmd: The scsicmd that needs aborted + * + * Returns SUCCESS if inserted, failure otherwise + * + */ +static int visorhba_abort_handler(struct scsi_cmnd *scsicmd) +{ + /* issue TASK_MGMT_ABORT_TASK */ + struct scsi_device *scsidev; + struct visordisk_info *vdisk; + struct visorhba_devdata *devdata; + + scsidev = scsicmd->device; + devdata = (struct visorhba_devdata *)scsidev->host->hostdata; + for_each_vdisk_match(vdisk, devdata, scsidev) { + if (atomic_read(&vdisk->error_count) < VISORHBA_ERROR_COUNT) + atomic_inc(&vdisk->error_count); + else + atomic_set(&vdisk->ios_threshold, IOS_ERROR_THRESHOLD); + } + return forward_taskmgmt_command(TASK_MGMT_ABORT_TASK, scsicmd); +} + +/** + * visorhba_device_reset_handler - Send TASK_MGMT_LUN_RESET + * @scsicmd: The scsicmd that needs aborted + * + * Returns SUCCESS if inserted, failure otherwise + */ +static int visorhba_device_reset_handler(struct scsi_cmnd *scsicmd) +{ + /* issue TASK_MGMT_LUN_RESET */ + struct scsi_device *scsidev; + struct visordisk_info *vdisk; + struct visorhba_devdata *devdata; + + scsidev = scsicmd->device; + devdata = (struct visorhba_devdata *)scsidev->host->hostdata; + for_each_vdisk_match(vdisk, devdata, scsidev) { + if (atomic_read(&vdisk->error_count) < VISORHBA_ERROR_COUNT) + atomic_inc(&vdisk->error_count); + else + atomic_set(&vdisk->ios_threshold, IOS_ERROR_THRESHOLD); + } + return forward_taskmgmt_command(TASK_MGMT_LUN_RESET, scsicmd); +} + +/** + * visorhba_bus_reset_handler - Send TASK_MGMT_TARGET_RESET for each + * target on the bus + * @scsicmd: The scsicmd that needs aborted + * + * Returns SUCCESS + */ +static int visorhba_bus_reset_handler(struct scsi_cmnd *scsicmd) +{ + struct scsi_device *scsidev; + struct visordisk_info *vdisk; + struct visorhba_devdata *devdata; + + scsidev = scsicmd->device; + devdata = (struct visorhba_devdata *)scsidev->host->hostdata; + for_each_vdisk_match(vdisk, devdata, scsidev) { + if (atomic_read(&vdisk->error_count) < VISORHBA_ERROR_COUNT) + atomic_inc(&vdisk->error_count); + else + atomic_set(&vdisk->ios_threshold, IOS_ERROR_THRESHOLD); + } + return forward_taskmgmt_command(TASK_MGMT_BUS_RESET, scsicmd); +} + +/** + * visorhba_host_reset_handler - Not supported + * @scsicmd: The scsicmd that needs aborted + * + * Not supported, return SUCCESS + * Returns SUCCESS + */ +static int +visorhba_host_reset_handler(struct scsi_cmnd *scsicmd) +{ + /* issue TASK_MGMT_TARGET_RESET for each target on each bus for host */ + return SUCCESS; +} + +/** + * visorhba_get_info + * @shp: Scsi host that is requesting information + * + * Returns string with info + */ +static const char *visorhba_get_info(struct Scsi_Host *shp) +{ + /* Return version string */ + return "visorhba"; +} + +/** + * visorhba_queue_command_lck -- queues command to the Service Partition + * @scsicmd: Command to be queued + * @vsiorhba_cmnd_done: Done command to call when scsicmd is returned + * + * Queues to scsicmd to the ServicePartition after converting it to a + * uiscmdrsp structure. + * + * Returns success if queued to the Service Partition, otherwise + * failure. + */ +static int +visorhba_queue_command_lck(struct scsi_cmnd *scsicmd, + void (*visorhba_cmnd_done)(struct scsi_cmnd *)) +{ + struct uiscmdrsp *cmdrsp; + struct scsi_device *scsidev = scsicmd->device; + int insert_location; + unsigned char op; + unsigned char *cdb = scsicmd->cmnd; + struct Scsi_Host *scsihost = scsidev->host; + unsigned int i; + struct visorhba_devdata *devdata = + (struct visorhba_devdata *)scsihost->hostdata; + struct scatterlist *sg = NULL; + struct scatterlist *sglist = NULL; + int err = 0; + + if (devdata->serverdown || devdata->serverchangingstate) + return SCSI_MLQUEUE_DEVICE_BUSY; + + insert_location = add_scsipending_entry(devdata, CMD_SCSI_TYPE, + (void *)scsicmd); + + if (insert_location < 0) + return SCSI_MLQUEUE_DEVICE_BUSY; + + cmdrsp = get_scsipending_cmdrsp(devdata, insert_location); + + cmdrsp->cmdtype = CMD_SCSI_TYPE; + /* save the pending insertion location. Deletion from pending + * will return the scsicmd pointer for completion + */ + cmdrsp->scsi.handle = insert_location; + + /* save done function that we have call when cmd is complete */ + scsicmd->scsi_done = visorhba_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 > devdata->max_buff_len) + devdata->max_buff_len = cmdrsp->scsi.bufflen; + + if (scsi_sg_count(scsicmd) > MAX_PHYS_INFO) { + err = SCSI_MLQUEUE_DEVICE_BUSY; + goto err_del_scsipending_ent; + } + + /* convert buffer to phys information */ + /* buffer is scatterlist - copy it out */ + sglist = scsi_sglist(scsicmd); + + for_each_sg(sglist, sg, scsi_sg_count(scsicmd), i) { + cmdrsp->scsi.gpi_list[i].address = sg_phys(sg); + cmdrsp->scsi.gpi_list[i].length = sg->length; + } + cmdrsp->scsi.guest_phys_entries = scsi_sg_count(scsicmd); + + op = cdb[0]; + if (!visorchannel_signalinsert(devdata->dev->visorchannel, + IOCHAN_TO_IOPART, + cmdrsp)) { + /* queue must be full and we aren't going to wait */ + err = SCSI_MLQUEUE_DEVICE_BUSY; + goto err_del_scsipending_ent; + } + return 0; + +err_del_scsipending_ent: + del_scsipending_ent(devdata, insert_location); + return err; +} + +/** + * visorhba_slave_alloc - called when new disk is discovered + * @scsidev: New disk + * + * Create a new visordisk_info structure and add it to our + * list of vdisks. + * + * Returns success when created, otherwise error. + */ +static int visorhba_slave_alloc(struct scsi_device *scsidev) +{ + /* this is called by the midlayer before scan for new devices -- + * LLD can alloc any struct & do init if needed. + */ + struct visordisk_info *vdisk; + struct visordisk_info *tmpvdisk; + struct visorhba_devdata *devdata; + struct Scsi_Host *scsihost = (struct Scsi_Host *)scsidev->host; + + devdata = (struct visorhba_devdata *)scsihost->hostdata; + if (!devdata) + return 0; /* even though we errored, treat as success */ + + for_each_vdisk_match(vdisk, devdata, scsidev) + return 0; /* already allocated return success */ + + tmpvdisk = kzalloc(sizeof(*tmpvdisk), GFP_ATOMIC); + if (!tmpvdisk) + return -ENOMEM; + + tmpvdisk->channel = scsidev->channel; + tmpvdisk->id = scsidev->id; + tmpvdisk->lun = scsidev->lun; + vdisk->next = tmpvdisk; + return 0; +} + +/** + * visorhba_slave_destroy - disk is going away + * @scsidev: scsi device going away + * + * Disk is going away, clean up resources. + * Returns void. + */ +static void visorhba_slave_destroy(struct scsi_device *scsidev) +{ + /* midlevel calls this after device has been quiesced and + * before it is to be deleted. + */ + struct visordisk_info *vdisk, *delvdisk; + struct visorhba_devdata *devdata; + struct Scsi_Host *scsihost = (struct Scsi_Host *)scsidev->host; + + devdata = (struct visorhba_devdata *)scsihost->hostdata; + for_each_vdisk_match(vdisk, devdata, scsidev) { + delvdisk = vdisk->next; + vdisk->next = delvdisk->next; + kfree(delvdisk); + return; + } +} + +static struct scsi_host_template visorhba_driver_template = { + .name = "Unisys Visor HBA", + .info = visorhba_get_info, + .queuecommand = visorhba_queue_command, + .eh_abort_handler = visorhba_abort_handler, + .eh_device_reset_handler = visorhba_device_reset_handler, + .eh_bus_reset_handler = visorhba_bus_reset_handler, + .eh_host_reset_handler = visorhba_host_reset_handler, + .shost_attrs = NULL, +#define visorhba_MAX_CMNDS 128 + .can_queue = visorhba_MAX_CMNDS, + .sg_tablesize = 64, + .this_id = -1, + .slave_alloc = visorhba_slave_alloc, + .slave_destroy = visorhba_slave_destroy, + .use_clustering = ENABLE_CLUSTERING, +}; + +/** + * info_debugfs_read - debugfs interface to dump visorhba states + * @file: Debug file + * @buf: buffer to send back to user + * @len: len that can be written to buf + * @offset: offset into buf + * + * Dumps information about the visorhba driver and devices + * TODO: Make this per vhba + * Returns bytes_read + */ +static ssize_t info_debugfs_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + ssize_t bytes_read = 0; + int str_pos = 0; + u64 phys_flags_addr; + int i; + struct visorhba_devdata *devdata; + char *vbuf; + + if (len > MAX_BUF) + len = MAX_BUF; + vbuf = kzalloc(len, GFP_KERNEL); + if (!vbuf) + return -ENOMEM; + + for (i = 0; i < VISORHBA_OPEN_MAX; i++) { + if (!visorhbas_open[i].devdata) + continue; + + devdata = visorhbas_open[i].devdata; + + str_pos += scnprintf(vbuf + str_pos, + len - str_pos, "max_buff_len:%u\n", + devdata->max_buff_len); + + str_pos += scnprintf(vbuf + str_pos, len - str_pos, + "\ninterrupts_rcvd = %llu, interrupts_disabled = %llu\n", + devdata->interrupts_rcvd, + devdata->interrupts_disabled); + str_pos += scnprintf(vbuf + str_pos, + len - str_pos, "\ninterrupts_notme = %llu,\n", + devdata->interrupts_notme); + phys_flags_addr = virt_to_phys((__force void *) + devdata->flags_addr); + str_pos += scnprintf(vbuf + str_pos, len - str_pos, + "flags_addr = %p, phys_flags_addr=0x%016llx, FeatureFlags=%llu\n", + devdata->flags_addr, phys_flags_addr, + (__le64)readq(devdata->flags_addr)); + str_pos += scnprintf(vbuf + str_pos, + len - str_pos, "acquire_failed_cnt:%llu\n", + devdata->acquire_failed_cnt); + str_pos += scnprintf(vbuf + str_pos, len - str_pos, "\n"); + } + + bytes_read = simple_read_from_buffer(buf, len, offset, vbuf, str_pos); + kfree(vbuf); + return bytes_read; +} + +/** + * visorhba_serverdown_complete - Called when we are done cleaning up + * from serverdown + * @work: work structure for this serverdown request + * + * Called when we are done cleanning up from serverdown, stop processing + * queue, fail pending IOs. + * Returns void when finished cleaning up + */ +static void visorhba_serverdown_complete(struct visorhba_devdata *devdata) +{ + int i; + struct scsipending *pendingdel = NULL; + struct scsi_cmnd *scsicmd = NULL; + struct uiscmdrsp *cmdrsp; + unsigned long flags; + + /* Stop using the IOVM response queue (queue should be drained + * by the end) + */ + kthread_stop(devdata->threadinfo.task); + + /* Fail commands that weren't completed */ + spin_lock_irqsave(&devdata->privlock, flags); + for (i = 0; i < MAX_PENDING_REQUESTS; i++) { + pendingdel = &devdata->pending[i]; + switch (pendingdel->cmdtype) { + case CMD_SCSI_TYPE: + scsicmd = pendingdel->sent; + scsicmd->result = DID_RESET << 16; + if (scsicmd->scsi_done) + scsicmd->scsi_done(scsicmd); + break; + case CMD_SCSITASKMGMT_TYPE: + cmdrsp = pendingdel->sent; + cmdrsp->scsitaskmgmt.notifyresult_handle + = TASK_MGMT_FAILED; + wake_up_all((wait_queue_head_t *) + cmdrsp->scsitaskmgmt.notify_handle); + break; + case CMD_VDISKMGMT_TYPE: + cmdrsp = pendingdel->sent; + cmdrsp->vdiskmgmt.notifyresult_handle + = VDISK_MGMT_FAILED; + wake_up_all((wait_queue_head_t *) + cmdrsp->vdiskmgmt.notify_handle); + break; + default: + break; + } + pendingdel->cmdtype = 0; + pendingdel->sent = NULL; + } + spin_unlock_irqrestore(&devdata->privlock, flags); + + devdata->serverdown = true; + devdata->serverchangingstate = false; +} + +/** + * visorhba_serverdown - Got notified that the IOVM is down + * @devdata: visorhba that is being serviced by downed IOVM. + * + * Something happened to the IOVM, return immediately and + * schedule work cleanup work. + * Return SUCCESS or EINVAL + */ +static int visorhba_serverdown(struct visorhba_devdata *devdata) +{ + if (!devdata->serverdown && !devdata->serverchangingstate) { + devdata->serverchangingstate = true; + visorhba_serverdown_complete(devdata); + } else if (devdata->serverchangingstate) { + return -EINVAL; + } + return 0; +} + +/** + * do_scsi_linuxstat - scsi command returned linuxstat + * @cmdrsp: response from IOVM + * @scsicmd: Command issued. + * + * Don't log errors for disk-not-present inquiries + * Returns void + */ +static void +do_scsi_linuxstat(struct uiscmdrsp *cmdrsp, struct scsi_cmnd *scsicmd) +{ + struct visorhba_devdata *devdata; + struct visordisk_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.... */ + devdata = (struct visorhba_devdata *)scsidev->host->hostdata; + for_each_vdisk_match(vdisk, devdata, scsidev) { + if (atomic_read(&vdisk->error_count) < VISORHBA_ERROR_COUNT) { + atomic_inc(&vdisk->error_count); + atomic_set(&vdisk->ios_threshold, IOS_ERROR_THRESHOLD); + } + } +} + +/** + * do_scsi_nolinuxstat - scsi command didn't have linuxstat + * @cmdrsp: response from IOVM + * @scsicmd: Command issued. + * + * Handle response when no linuxstat was returned + * Returns void + */ +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 *this_page; + char *this_page_orig; + int bufind = 0; + struct visordisk_info *vdisk; + struct visorhba_devdata *devdata; + + 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 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) { + memcpy(scsi_sglist(scsicmd), buf, + cmdrsp->scsi.bufflen); + return; + } + + sg = scsi_sglist(scsicmd); + for (i = 0; i < scsi_sg_count(scsicmd); i++) { + this_page_orig = kmap_atomic(sg_page(sg + i)); + this_page = (void *)((unsigned long)this_page_orig | + sg[i].offset); + memcpy(this_page, buf + bufind, sg[i].length); + kunmap_atomic(this_page_orig); + } + } else { + devdata = (struct visorhba_devdata *)scsidev->host->hostdata; + for_each_vdisk_match(vdisk, devdata, scsidev) { + if (atomic_read(&vdisk->ios_threshold) > 0) { + atomic_dec(&vdisk->ios_threshold); + if (atomic_read(&vdisk->ios_threshold) == 0) + atomic_set(&vdisk->error_count, 0); + } + } + } +} + +/** + * complete_scsi_command - complete a scsi command + * @uiscmdrsp: Response from Service Partition + * @scsicmd: The scsi command + * + * Response returned by the Service Partition, finish it and send + * completion to the scsi midlayer. + * Returns void. + */ +static void +complete_scsi_command(struct uiscmdrsp *cmdrsp, struct scsi_cmnd *scsicmd) +{ + /* 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); + + scsicmd->scsi_done(scsicmd); +} + +/* DELETE VDISK TASK MGMT COMMANDS */ +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 + */ + cmdrsp->vdiskmgmt.notifyresult_handle = cmdrsp->vdiskmgmt.result; + wake_up_all((wait_queue_head_t *)cmdrsp->vdiskmgmt.notify_handle); +} + +/** + * complete_taskmgmt_command - complete task management + * @cmdrsp: Response from the IOVM + * + * Service Partition returned the result of the task management + * command. Wake up anyone waiting for it. + * Returns void + */ +static inline void complete_taskmgmt_command(struct uiscmdrsp *cmdrsp) +{ + /* copy the result of the taskgmgt and + * wake up the error handler that is waiting for this + */ + cmdrsp->vdiskmgmt.notifyresult_handle = cmdrsp->vdiskmgmt.result; + wake_up_all((wait_queue_head_t *)cmdrsp->scsitaskmgmt.notify_handle); +} + +static struct work_struct dar_work_queue; +static struct diskaddremove *dar_work_queue_head; +static spinlock_t dar_work_queue_lock; /* Lock to protet dar_work_queue_head */ +static unsigned short dar_work_queue_sched; + +/** + * queue_disk_add_remove - IOSP has sent us a add/remove request + * @dar: disk add/remove request + * + * Queue the work needed to add/remove a disk. + * Returns void + */ +static inline void queue_disk_add_remove(struct diskaddremove *dar) +{ + unsigned long flags; + + spin_lock_irqsave(&dar_work_queue_lock, flags); + if (!dar_work_queue_head) { + dar_work_queue_head = dar; + dar->next = NULL; + } else { + dar->next = dar_work_queue_head; + dar_work_queue_head = dar; + } + if (!dar_work_queue_sched) { + schedule_work(&dar_work_queue); + dar_work_queue_sched = 1; + } + spin_unlock_irqrestore(&dar_work_queue_lock, flags); +} + +/** + * process_disk_notify - IOSP has sent a process disk notify event + * @shost: Scsi hot + * @cmdrsp: Response from the IOSP + * + * Queue it to the work queue. + * Return void. + */ +static void process_disk_notify(struct Scsi_Host *shost, + struct uiscmdrsp *cmdrsp) +{ + struct diskaddremove *dar; + + dar = kzalloc(sizeof(*dar), 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_disk_add_remove(dar); + } +} + +/** + * drain_queue - pull responses out of iochannel + * @cmdrsp: Response from the IOSP + * @devdata: device that owns this iochannel + * + * Pulls responses out of the iochannel and process the responses. + * Restuns void + */ +static void +drain_queue(struct uiscmdrsp *cmdrsp, struct visorhba_devdata *devdata) +{ + struct scsi_cmnd *scsicmd; + struct Scsi_Host *shost = devdata->scsihost; + + while (1) { + if (!visorchannel_signalremove(devdata->dev->visorchannel, + IOCHAN_FROM_IOPART, + cmdrsp)) + break; /* queue empty */ + + if (cmdrsp->cmdtype == CMD_SCSI_TYPE) { + /* scsicmd location is returned by the + * deletion + */ + scsicmd = del_scsipending_ent(devdata, + cmdrsp->scsi.handle); + if (!scsicmd) + break; + /* complete the orig cmd */ + complete_scsi_command(cmdrsp, scsicmd); + } else if (cmdrsp->cmdtype == CMD_SCSITASKMGMT_TYPE) { + if (!del_scsipending_ent(devdata, + cmdrsp->scsitaskmgmt.handle)) + break; + complete_taskmgmt_command(cmdrsp); + } else if (cmdrsp->cmdtype == CMD_NOTIFYGUEST_TYPE) { + /* The vHba pointer has no meaning in a + * guest partition. Let's be safe and set it + * to NULL now. Do not use it here! + */ + cmdrsp->disknotify.v_hba = NULL; + process_disk_notify(shost, cmdrsp); + } else if (cmdrsp->cmdtype == CMD_VDISKMGMT_TYPE) { + if (!del_scsipending_ent(devdata, + cmdrsp->vdiskmgmt.handle)) + break; + complete_vdiskmgmt_command(cmdrsp); + } + /* cmdrsp is now available for resuse */ + } +} + +/** + * process_incoming_rsps - Process responses from IOSP + * @v: void pointer to visorhba_devdata + * + * Main function for the thread that processes the responses + * from the IO Service Partition. When the queue is empty, wait + * to check to see if it is full again. + */ +static int process_incoming_rsps(void *v) +{ + struct visorhba_devdata *devdata = v; + struct uiscmdrsp *cmdrsp = NULL; + const int size = sizeof(*cmdrsp); + + cmdrsp = kmalloc(size, GFP_ATOMIC); + if (!cmdrsp) + return -ENOMEM; + + while (1) { + if (kthread_should_stop()) + break; + wait_event_interruptible_timeout( + devdata->rsp_queue, (atomic_read( + &devdata->interrupt_rcvd) == 1), + msecs_to_jiffies(devdata->thread_wait_ms)); + /* drain queue */ + drain_queue(cmdrsp, devdata); + } + kfree(cmdrsp); + return 0; +} + +/** + * visorhba_pause - function to handle visorbus pause messages + * @dev: device that is pausing. + * @complete_func: function to call when finished + * + * Something has happened to the IO Service Partition that is + * handling this device. Quiet this device and reset commands + * so that the Service Partition can be corrected. + * Returns SUCCESS + */ +static int visorhba_pause(struct visor_device *dev, + visorbus_state_complete_func complete_func) +{ + struct visorhba_devdata *devdata = dev_get_drvdata(&dev->device); + + visorhba_serverdown(devdata); + complete_func(dev, 0); + return 0; +} + +/** + * visorhba_resume - function called when the IO Service Partition is back + * @dev: device that is pausing. + * @complete_func: function to call when finished + * + * Yay! The IO Service Partition is back, the channel has been wiped + * so lets re-establish connection and start processing responses. + * Returns 0 on success, error on failure. + */ +static int visorhba_resume(struct visor_device *dev, + visorbus_state_complete_func complete_func) +{ + struct visorhba_devdata *devdata; + + devdata = dev_get_drvdata(&dev->device); + if (!devdata) + return -EINVAL; + + if (devdata->serverdown && !devdata->serverchangingstate) + devdata->serverchangingstate = 1; + + visor_thread_start(&devdata->threadinfo, process_incoming_rsps, + devdata, "vhba_incming"); + + devdata->serverdown = false; + devdata->serverchangingstate = false; + + return 0; +} + +/** + * visorhba_probe - device has been discovered, do acquire + * @dev: visor_device that was discovered + * + * A new HBA was discovered, do the initial connections of it. + * Return 0 on success, otherwise error. + */ +static int visorhba_probe(struct visor_device *dev) +{ + struct Scsi_Host *scsihost; + struct vhba_config_max max; + struct visorhba_devdata *devdata = NULL; + int i, err, channel_offset; + u64 features; + + scsihost = scsi_host_alloc(&visorhba_driver_template, + sizeof(*devdata)); + if (!scsihost) + return -ENODEV; + + channel_offset = offsetof(struct spar_io_channel_protocol, + vhba.max); + err = visorbus_read_channel(dev, channel_offset, &max, + sizeof(struct vhba_config_max)); + if (err < 0) + goto err_scsi_host_put; + + scsihost->max_id = (unsigned)max.max_id; + scsihost->max_lun = (unsigned)max.max_lun; + scsihost->cmd_per_lun = (unsigned)max.cmd_per_lun; + scsihost->max_sectors = + (unsigned short)(max.max_io_size >> 9); + scsihost->sg_tablesize = + (unsigned short)(max.max_io_size / PAGE_SIZE); + if (scsihost->sg_tablesize > MAX_PHYS_INFO) + scsihost->sg_tablesize = MAX_PHYS_INFO; + err = scsi_add_host(scsihost, &dev->device); + if (err < 0) + goto err_scsi_host_put; + + devdata = (struct visorhba_devdata *)scsihost->hostdata; + for (i = 0; i < VISORHBA_OPEN_MAX; i++) { + if (!visorhbas_open[i].devdata) { + visorhbas_open[i].devdata = devdata; + break; + } + } + + devdata->dev = dev; + dev_set_drvdata(&dev->device, devdata); + + init_waitqueue_head(&devdata->rsp_queue); + spin_lock_init(&devdata->privlock); + devdata->serverdown = false; + devdata->serverchangingstate = false; + devdata->scsihost = scsihost; + + channel_offset = offsetof(struct spar_io_channel_protocol, + channel_header.features); + err = visorbus_read_channel(dev, channel_offset, &features, 8); + if (err) + goto err_scsi_remove_host; + features |= ULTRA_IO_CHANNEL_IS_POLLING; + err = visorbus_write_channel(dev, channel_offset, &features, 8); + if (err) + goto err_scsi_remove_host; + + devdata->thread_wait_ms = 2; + visor_thread_start(&devdata->threadinfo, process_incoming_rsps, + devdata, "vhba_incoming"); + + scsi_scan_host(scsihost); + + return 0; + +err_scsi_remove_host: + scsi_remove_host(scsihost); + +err_scsi_host_put: + scsi_host_put(scsihost); + return err; +} + +/** + * visorhba_remove - remove a visorhba device + * @dev: Device to remove + * + * Removes the visorhba device. + * Returns void. + */ +static void visorhba_remove(struct visor_device *dev) +{ + struct visorhba_devdata *devdata = dev_get_drvdata(&dev->device); + struct Scsi_Host *scsihost = NULL; + + if (!devdata) + return; + + scsihost = devdata->scsihost; + kthread_stop(devdata->threadinfo.task); + scsi_remove_host(scsihost); + scsi_host_put(scsihost); + + dev_set_drvdata(&dev->device, NULL); +} + +/** + * visorhba_init - driver init routine + * + * Initialize the visorhba driver and register it with visorbus + * to handle s-Par virtual host bus adapter. + */ +static int visorhba_init(void) +{ + struct dentry *ret; + int rc = -ENOMEM; + + visorhba_debugfs_dir = debugfs_create_dir("visorhba", NULL); + if (!visorhba_debugfs_dir) + return -ENOMEM; + + ret = debugfs_create_file("info", S_IRUSR, visorhba_debugfs_dir, NULL, + &debugfs_info_fops); + + if (!ret) { + rc = -EIO; + goto cleanup_debugfs; + } + + rc = visorbus_register_visor_driver(&visorhba_driver); + if (rc) + goto cleanup_debugfs; + + return rc; + +cleanup_debugfs: + debugfs_remove_recursive(visorhba_debugfs_dir); + + return rc; +} + +/** + * visorhba_cleanup - driver exit routine + * + * Unregister driver from the bus and free up memory. + */ +static void visorhba_exit(void) +{ + visorbus_unregister_visor_driver(&visorhba_driver); + debugfs_remove_recursive(visorhba_debugfs_dir); +} + +module_init(visorhba_init); +module_exit(visorhba_exit); + +MODULE_AUTHOR("Unisys"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("s-Par hba driver"); diff --git a/drivers/staging/unisys/visorinput/Kconfig b/drivers/staging/unisys/visorinput/Kconfig new file mode 100644 index 000000000..d83deb413 --- /dev/null +++ b/drivers/staging/unisys/visorinput/Kconfig @@ -0,0 +1,10 @@ +# +# Unisys visorinput configuration +# + +config UNISYS_VISORINPUT + tristate "Unisys visorinput driver" + depends on UNISYSSPAR && UNISYS_VISORBUS && FB + ---help--- + If you say Y here, you will enable the Unisys visorinput driver. + diff --git a/drivers/staging/unisys/visorinput/Makefile b/drivers/staging/unisys/visorinput/Makefile new file mode 100644 index 000000000..beedca7f0 --- /dev/null +++ b/drivers/staging/unisys/visorinput/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for Unisys visorinput +# + +obj-$(CONFIG_UNISYS_VISORINPUT) += visorinput.o + +ccflags-y += -Idrivers/staging/unisys/include diff --git a/drivers/staging/unisys/visorinput/ultrainputreport.h b/drivers/staging/unisys/visorinput/ultrainputreport.h new file mode 100644 index 000000000..3e6a52f4b --- /dev/null +++ b/drivers/staging/unisys/visorinput/ultrainputreport.h @@ -0,0 +1,74 @@ +/* Copyright (C) 2010 - 2015 UNISYS CORPORATION + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * 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 __SPAR_ULTRAINPUTREPORT_H__ +#define __SPAR_ULTRAINPUTREPORT_H__ + +#include + +#include "ultrainputreport.h" + +/* Identifies mouse and keyboard activity which is specified by the firmware to + * the host using the cmsimpleinput protocol. @ingroup coretypes + */ +enum ultra_inputaction { + inputaction_none = 0, + inputaction_xy_motion = 1, /* only motion; arg1=x, arg2=y */ + inputaction_mouse_button_down = 2, /* arg1: 1=left,2=center,3=right */ + inputaction_mouse_button_up = 3, /* arg1: 1=left,2=center,3=right */ + inputaction_mouse_button_click = 4, /* arg1: 1=left,2=center,3=right */ + inputaction_mouse_button_dclick = 5, /* arg1: 1=left,2=center, + 3=right */ + inputaction_wheel_rotate_away = 6, /* arg1: wheel rotation away from + user */ + inputaction_wheel_rotate_toward = 7, /* arg1: wheel rotation toward + user */ + inputaction_set_max_xy = 8, /* set screen maxXY; arg1=x, arg2=y */ + inputaction_key_down = 64, /* arg1: scancode, as follows: + If arg1 <= 0xff, it's a 1-byte + scancode and arg1 is that scancode. + If arg1 > 0xff, it's a 2-byte + scanecode, with the 1st byte in the + low 8 bits, and the 2nd byte in the + high 8 bits. E.g., the right ALT key + would appear as x'38e0'. */ + inputaction_key_up = 65, /* arg1: scancode (in same format as + inputaction_keyDown) */ + inputaction_set_locking_key_state = 66, + /* arg1: scancode (in same format + as inputaction_keyDown); + MUST refer to one of the + locking keys, like capslock, + numlock, or scrolllock + arg2: 1 iff locking key should be + in the LOCKED position + (e.g., light is ON) */ + inputaction_key_down_up = 67, /* arg1: scancode (in same format + as inputaction_keyDown) */ + inputaction_last +}; + +struct ultra_inputactivity { + u16 action; + u16 arg1; + u16 arg2; + u16 arg3; +} __packed; + +struct ultra_inputreport { + u64 seq_no; + struct ultra_inputactivity activity; +} __packed; + +#endif diff --git a/drivers/staging/unisys/visorinput/visorinput.c b/drivers/staging/unisys/visorinput/visorinput.c new file mode 100644 index 000000000..5c16f6634 --- /dev/null +++ b/drivers/staging/unisys/visorinput/visorinput.c @@ -0,0 +1,715 @@ +/* visorinput.c + * + * Copyright (C) 2011 - 2015 UNISYS CORPORATION + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * 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 driver lives in a generic guest Linux partition, and registers to + * receive keyboard and mouse channels from the visorbus driver. It reads + * inputs from such channels, and delivers it to the Linux OS in the + * standard way the Linux expects for input drivers. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "version.h" +#include "visorbus.h" +#include "channel.h" +#include "ultrainputreport.h" + +/* Keyboard channel {c73416d0-b0b8-44af-b304-9d2ae99f1b3d} */ +#define SPAR_KEYBOARD_CHANNEL_PROTOCOL_UUID \ + UUID_LE(0xc73416d0, 0xb0b8, 0x44af, \ + 0xb3, 0x4, 0x9d, 0x2a, 0xe9, 0x9f, 0x1b, 0x3d) +#define SPAR_KEYBOARD_CHANNEL_PROTOCOL_UUID_STR "c73416d0-b0b8-44af-b304-9d2ae99f1b3d" + +/* Mouse channel {addf07d4-94a9-46e2-81c3-61abcdbdbd87} */ +#define SPAR_MOUSE_CHANNEL_PROTOCOL_UUID \ + UUID_LE(0xaddf07d4, 0x94a9, 0x46e2, \ + 0x81, 0xc3, 0x61, 0xab, 0xcd, 0xbd, 0xbd, 0x87) +#define SPAR_MOUSE_CHANNEL_PROTOCOL_UUID_STR \ + "addf07d4-94a9-46e2-81c3-61abcdbdbd87" + +#define PIXELS_ACROSS_DEFAULT 800 +#define PIXELS_DOWN_DEFAULT 600 +#define KEYCODE_TABLE_BYTES 256 + +enum visorinput_device_type { + visorinput_keyboard, + visorinput_mouse, +}; + +/* + * This is the private data that we store for each device. + * A pointer to this struct is maintained via + * dev_get_drvdata() / dev_set_drvdata() for each struct device. + */ +struct visorinput_devdata { + struct visor_device *dev; + struct rw_semaphore lock_visor_dev; /* lock for dev */ + struct input_dev *visorinput_dev; + bool paused; + unsigned int keycode_table_bytes; /* size of following array */ + /* for keyboard devices: visorkbd_keycode[] + visorkbd_ext_keycode[] */ + unsigned char keycode_table[0]; +}; + +static const uuid_le spar_keyboard_channel_protocol_uuid = + SPAR_KEYBOARD_CHANNEL_PROTOCOL_UUID; +static const uuid_le spar_mouse_channel_protocol_uuid = + SPAR_MOUSE_CHANNEL_PROTOCOL_UUID; + +/* + * Borrowed from drivers/input/keyboard/atakbd.c + * This maps 1-byte scancodes to keycodes. + */ +static const unsigned char visorkbd_keycode[KEYCODE_TABLE_BYTES] = { + /* American layout */ + [0] = KEY_GRAVE, + [1] = KEY_ESC, + [2] = KEY_1, + [3] = KEY_2, + [4] = KEY_3, + [5] = KEY_4, + [6] = KEY_5, + [7] = KEY_6, + [8] = KEY_7, + [9] = KEY_8, + [10] = KEY_9, + [11] = KEY_0, + [12] = KEY_MINUS, + [13] = KEY_EQUAL, + [14] = KEY_BACKSPACE, + [15] = KEY_TAB, + [16] = KEY_Q, + [17] = KEY_W, + [18] = KEY_E, + [19] = KEY_R, + [20] = KEY_T, + [21] = KEY_Y, + [22] = KEY_U, + [23] = KEY_I, + [24] = KEY_O, + [25] = KEY_P, + [26] = KEY_LEFTBRACE, + [27] = KEY_RIGHTBRACE, + [28] = KEY_ENTER, + [29] = KEY_LEFTCTRL, + [30] = KEY_A, + [31] = KEY_S, + [32] = KEY_D, + [33] = KEY_F, + [34] = KEY_G, + [35] = KEY_H, + [36] = KEY_J, + [37] = KEY_K, + [38] = KEY_L, + [39] = KEY_SEMICOLON, + [40] = KEY_APOSTROPHE, + [41] = KEY_GRAVE, /* FIXME, '#' */ + [42] = KEY_LEFTSHIFT, + [43] = KEY_BACKSLASH, /* FIXME, '~' */ + [44] = KEY_Z, + [45] = KEY_X, + [46] = KEY_C, + [47] = KEY_V, + [48] = KEY_B, + [49] = KEY_N, + [50] = KEY_M, + [51] = KEY_COMMA, + [52] = KEY_DOT, + [53] = KEY_SLASH, + [54] = KEY_RIGHTSHIFT, + [55] = KEY_KPASTERISK, + [56] = KEY_LEFTALT, + [57] = KEY_SPACE, + [58] = KEY_CAPSLOCK, + [59] = KEY_F1, + [60] = KEY_F2, + [61] = KEY_F3, + [62] = KEY_F4, + [63] = KEY_F5, + [64] = KEY_F6, + [65] = KEY_F7, + [66] = KEY_F8, + [67] = KEY_F9, + [68] = KEY_F10, + [69] = KEY_NUMLOCK, + [70] = KEY_SCROLLLOCK, + [71] = KEY_KP7, + [72] = KEY_KP8, + [73] = KEY_KP9, + [74] = KEY_KPMINUS, + [75] = KEY_KP4, + [76] = KEY_KP5, + [77] = KEY_KP6, + [78] = KEY_KPPLUS, + [79] = KEY_KP1, + [80] = KEY_KP2, + [81] = KEY_KP3, + [82] = KEY_KP0, + [83] = KEY_KPDOT, + [86] = KEY_102ND, /* enables UK backslash+pipe key, + * and FR lessthan+greaterthan key + */ + [87] = KEY_F11, + [88] = KEY_F12, + [90] = KEY_KPLEFTPAREN, + [91] = KEY_KPRIGHTPAREN, + [92] = KEY_KPASTERISK, /* FIXME */ + [93] = KEY_KPASTERISK, + [94] = KEY_KPPLUS, + [95] = KEY_HELP, + [96] = KEY_KPENTER, + [97] = KEY_RIGHTCTRL, + [98] = KEY_KPSLASH, + [99] = KEY_KPLEFTPAREN, + [100] = KEY_KPRIGHTPAREN, + [101] = KEY_KPSLASH, + [102] = KEY_HOME, + [103] = KEY_UP, + [104] = KEY_PAGEUP, + [105] = KEY_LEFT, + [106] = KEY_RIGHT, + [107] = KEY_END, + [108] = KEY_DOWN, + [109] = KEY_PAGEDOWN, + [110] = KEY_INSERT, + [111] = KEY_DELETE, + [112] = KEY_MACRO, + [113] = KEY_MUTE +}; + +/* + * This maps the in extended scancodes of the form "0xE0 " into + * keycodes. + */ +static const unsigned char visorkbd_ext_keycode[KEYCODE_TABLE_BYTES] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 */ + 0, 0, 0, 0, KEY_KPENTER, KEY_RIGHTCTRL, 0, 0, /* 0x18 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 */ + KEY_RIGHTALT, 0, 0, 0, 0, 0, 0, 0, /* 0x28 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 */ + KEY_RIGHTALT /* AltGr */, 0, 0, 0, 0, 0, 0, 0, /* 0x38 */ + 0, 0, 0, 0, 0, 0, 0, KEY_HOME, /* 0x40 */ + KEY_UP, KEY_PAGEUP, 0, KEY_LEFT, 0, KEY_RIGHT, 0, KEY_END, /* 0x48 */ + KEY_DOWN, KEY_PAGEDOWN, KEY_INSERT, KEY_DELETE, 0, 0, 0, 0, /* 0x50 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x58 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 */ +}; + +static int visorinput_open(struct input_dev *visorinput_dev) +{ + struct visorinput_devdata *devdata = input_get_drvdata(visorinput_dev); + + if (!devdata) { + pr_err("%s input_get_drvdata(%p) returned NULL\n", + __func__, visorinput_dev); + return -EINVAL; + } + dev_dbg(&visorinput_dev->dev, "%s opened\n", __func__); + visorbus_enable_channel_interrupts(devdata->dev); + return 0; +} + +static void visorinput_close(struct input_dev *visorinput_dev) +{ + struct visorinput_devdata *devdata = input_get_drvdata(visorinput_dev); + + if (!devdata) { + pr_err("%s input_get_drvdata(%p) returned NULL\n", + __func__, visorinput_dev); + return; + } + dev_dbg(&visorinput_dev->dev, "%s closed\n", __func__); + visorbus_disable_channel_interrupts(devdata->dev); +} + +/* + * register_client_keyboard() initializes and returns a Linux input node that + * we can use to deliver keyboard inputs to Linux. We of course do this when + * we see keyboard inputs coming in on a keyboard channel. + */ +static struct input_dev * +register_client_keyboard(void *devdata, /* opaque on purpose */ + unsigned char *keycode_table) + +{ + int i, error; + struct input_dev *visorinput_dev; + + visorinput_dev = input_allocate_device(); + if (!visorinput_dev) + return NULL; + + visorinput_dev->name = "visor Keyboard"; + visorinput_dev->phys = "visorkbd:input0"; + visorinput_dev->id.bustype = BUS_VIRTUAL; + visorinput_dev->id.vendor = 0x0001; + visorinput_dev->id.product = 0x0001; + visorinput_dev->id.version = 0x0100; + + visorinput_dev->evbit[0] = BIT_MASK(EV_KEY) | + BIT_MASK(EV_REP) | + BIT_MASK(EV_LED); + visorinput_dev->ledbit[0] = BIT_MASK(LED_CAPSL) | + BIT_MASK(LED_SCROLLL) | + BIT_MASK(LED_NUML); + visorinput_dev->keycode = keycode_table; + visorinput_dev->keycodesize = 1; /* sizeof(unsigned char) */ + visorinput_dev->keycodemax = KEYCODE_TABLE_BYTES; + + for (i = 1; i < visorinput_dev->keycodemax; i++) + set_bit(keycode_table[i], visorinput_dev->keybit); + for (i = 1; i < visorinput_dev->keycodemax; i++) + set_bit(keycode_table[i + KEYCODE_TABLE_BYTES], + visorinput_dev->keybit); + + visorinput_dev->open = visorinput_open; + visorinput_dev->close = visorinput_close; + input_set_drvdata(visorinput_dev, devdata); /* pre input_register! */ + + error = input_register_device(visorinput_dev); + if (error) { + input_free_device(visorinput_dev); + return NULL; + } + return visorinput_dev; +} + +static struct input_dev * +register_client_mouse(void *devdata /* opaque on purpose */) +{ + int error; + struct input_dev *visorinput_dev = NULL; + int xres, yres; + struct fb_info *fb0; + + visorinput_dev = input_allocate_device(); + if (!visorinput_dev) + return NULL; + + visorinput_dev->name = "visor Mouse"; + visorinput_dev->phys = "visormou:input0"; + visorinput_dev->id.bustype = BUS_VIRTUAL; + visorinput_dev->id.vendor = 0x0001; + visorinput_dev->id.product = 0x0002; + visorinput_dev->id.version = 0x0100; + + visorinput_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + set_bit(BTN_LEFT, visorinput_dev->keybit); + set_bit(BTN_RIGHT, visorinput_dev->keybit); + set_bit(BTN_MIDDLE, visorinput_dev->keybit); + + if (registered_fb[0]) { + fb0 = registered_fb[0]; + xres = fb0->var.xres_virtual; + yres = fb0->var.yres_virtual; + } else { + xres = PIXELS_ACROSS_DEFAULT; + yres = PIXELS_DOWN_DEFAULT; + } + input_set_abs_params(visorinput_dev, ABS_X, 0, xres, 0, 0); + input_set_abs_params(visorinput_dev, ABS_Y, 0, yres, 0, 0); + + visorinput_dev->open = visorinput_open; + visorinput_dev->close = visorinput_close; + input_set_drvdata(visorinput_dev, devdata); /* pre input_register! */ + + error = input_register_device(visorinput_dev); + if (error) { + input_free_device(visorinput_dev); + return NULL; + } + + input_set_capability(visorinput_dev, EV_REL, REL_WHEEL); + + return visorinput_dev; +} + +static struct visorinput_devdata * +devdata_create(struct visor_device *dev, enum visorinput_device_type devtype) +{ + struct visorinput_devdata *devdata = NULL; + unsigned int extra_bytes = 0; + + if (devtype == visorinput_keyboard) + /* allocate room for devdata->keycode_table, filled in below */ + extra_bytes = KEYCODE_TABLE_BYTES * 2; + devdata = kzalloc(sizeof(*devdata) + extra_bytes, GFP_KERNEL); + if (!devdata) + return NULL; + devdata->dev = dev; + + /* + * This is an input device in a client guest partition, + * so we need to create whatever input nodes are necessary to + * deliver our inputs to the guest OS. + */ + switch (devtype) { + case visorinput_keyboard: + devdata->keycode_table_bytes = extra_bytes; + memcpy(devdata->keycode_table, visorkbd_keycode, + KEYCODE_TABLE_BYTES); + memcpy(devdata->keycode_table + KEYCODE_TABLE_BYTES, + visorkbd_ext_keycode, KEYCODE_TABLE_BYTES); + devdata->visorinput_dev = register_client_keyboard + (devdata, devdata->keycode_table); + if (!devdata->visorinput_dev) + goto cleanups_register; + break; + case visorinput_mouse: + devdata->visorinput_dev = register_client_mouse(devdata); + if (!devdata->visorinput_dev) + goto cleanups_register; + break; + } + + init_rwsem(&devdata->lock_visor_dev); + + return devdata; + +cleanups_register: + kfree(devdata); + return NULL; +} + +static int +visorinput_probe(struct visor_device *dev) +{ + struct visorinput_devdata *devdata = NULL; + uuid_le guid; + enum visorinput_device_type devtype; + + guid = visorchannel_get_uuid(dev->visorchannel); + if (uuid_le_cmp(guid, spar_mouse_channel_protocol_uuid) == 0) + devtype = visorinput_mouse; + else if (uuid_le_cmp(guid, spar_keyboard_channel_protocol_uuid) == 0) + devtype = visorinput_keyboard; + else + return -ENODEV; + devdata = devdata_create(dev, devtype); + if (!devdata) + return -ENOMEM; + dev_set_drvdata(&dev->device, devdata); + return 0; +} + +static void +unregister_client_input(struct input_dev *visorinput_dev) +{ + if (visorinput_dev) + input_unregister_device(visorinput_dev); +} + +static void +visorinput_remove(struct visor_device *dev) +{ + struct visorinput_devdata *devdata = dev_get_drvdata(&dev->device); + + if (!devdata) + return; + + visorbus_disable_channel_interrupts(dev); + + /* + * due to above, at this time no thread of execution will be + * in visorinput_channel_interrupt() + */ + + down_write(&devdata->lock_visor_dev); + dev_set_drvdata(&dev->device, NULL); + unregister_client_input(devdata->visorinput_dev); + up_write(&devdata->lock_visor_dev); + kfree(devdata); +} + +/* + * Make it so the current locking state of the locking key indicated by + * is as indicated by (1=locked, 0=unlocked). + */ +static void +handle_locking_key(struct input_dev *visorinput_dev, + int keycode, int desired_state) +{ + int led; + + switch (keycode) { + case KEY_CAPSLOCK: + led = LED_CAPSL; + break; + case KEY_SCROLLLOCK: + led = LED_SCROLLL; + break; + case KEY_NUMLOCK: + led = LED_NUML; + break; + default: + led = -1; + break; + } + if (led >= 0) { + int old_state = (test_bit(led, visorinput_dev->led) != 0); + + if (old_state != desired_state) { + input_report_key(visorinput_dev, keycode, 1); + input_sync(visorinput_dev); + input_report_key(visorinput_dev, keycode, 0); + input_sync(visorinput_dev); + __change_bit(led, visorinput_dev->led); + } + } +} + +/* + * is either a 1-byte scancode, or an extended 16-bit scancode + * with 0xE0 in the low byte and the extended scancode value in the next + * higher byte. + */ +static int +scancode_to_keycode(int scancode) +{ + int keycode; + + if (scancode > 0xff) + keycode = visorkbd_ext_keycode[(scancode >> 8) & 0xff]; + else + keycode = visorkbd_keycode[scancode]; + return keycode; +} + +static int +calc_button(int x) +{ + switch (x) { + case 1: + return BTN_LEFT; + case 2: + return BTN_MIDDLE; + case 3: + return BTN_RIGHT; + default: + return -1; + } +} + +/* + * This is used only when this driver is active as an input driver in the + * client guest partition. It is called periodically so we can obtain inputs + * from the channel, and deliver them to the guest OS. + */ +static void +visorinput_channel_interrupt(struct visor_device *dev) +{ + struct ultra_inputreport r; + int scancode, keycode; + struct input_dev *visorinput_dev; + int xmotion, ymotion, zmotion, button; + int i; + + struct visorinput_devdata *devdata = dev_get_drvdata(&dev->device); + + if (!devdata) + return; + + down_write(&devdata->lock_visor_dev); + if (devdata->paused) /* don't touch device/channel when paused */ + goto out_locked; + + visorinput_dev = devdata->visorinput_dev; + if (!visorinput_dev) + goto out_locked; + + while (visorchannel_signalremove(dev->visorchannel, 0, &r)) { + scancode = r.activity.arg1; + keycode = scancode_to_keycode(scancode); + switch (r.activity.action) { + case inputaction_key_down: + input_report_key(visorinput_dev, keycode, 1); + input_sync(visorinput_dev); + break; + case inputaction_key_up: + input_report_key(visorinput_dev, keycode, 0); + input_sync(visorinput_dev); + break; + case inputaction_key_down_up: + input_report_key(visorinput_dev, keycode, 1); + input_sync(visorinput_dev); + input_report_key(visorinput_dev, keycode, 0); + input_sync(visorinput_dev); + break; + case inputaction_set_locking_key_state: + handle_locking_key(visorinput_dev, keycode, + r.activity.arg2); + break; + case inputaction_xy_motion: + xmotion = r.activity.arg1; + ymotion = r.activity.arg2; + input_report_abs(visorinput_dev, ABS_X, xmotion); + input_report_abs(visorinput_dev, ABS_Y, ymotion); + input_sync(visorinput_dev); + break; + case inputaction_mouse_button_down: + button = calc_button(r.activity.arg1); + if (button < 0) + break; + input_report_key(visorinput_dev, button, 1); + input_sync(visorinput_dev); + break; + case inputaction_mouse_button_up: + button = calc_button(r.activity.arg1); + if (button < 0) + break; + input_report_key(visorinput_dev, button, 0); + input_sync(visorinput_dev); + break; + case inputaction_mouse_button_click: + button = calc_button(r.activity.arg1); + if (button < 0) + break; + input_report_key(visorinput_dev, button, 1); + + input_sync(visorinput_dev); + input_report_key(visorinput_dev, button, 0); + input_sync(visorinput_dev); + break; + case inputaction_mouse_button_dclick: + button = calc_button(r.activity.arg1); + if (button < 0) + break; + for (i = 0; i < 2; i++) { + input_report_key(visorinput_dev, button, 1); + input_sync(visorinput_dev); + input_report_key(visorinput_dev, button, 0); + input_sync(visorinput_dev); + } + break; + case inputaction_wheel_rotate_away: + zmotion = r.activity.arg1; + input_report_rel(visorinput_dev, REL_WHEEL, 1); + input_sync(visorinput_dev); + break; + case inputaction_wheel_rotate_toward: + zmotion = r.activity.arg1; + input_report_rel(visorinput_dev, REL_WHEEL, -1); + input_sync(visorinput_dev); + break; + } + } +out_locked: + up_write(&devdata->lock_visor_dev); +} + +static int +visorinput_pause(struct visor_device *dev, + visorbus_state_complete_func complete_func) +{ + int rc; + struct visorinput_devdata *devdata = dev_get_drvdata(&dev->device); + + if (!devdata) { + rc = -ENODEV; + goto out; + } + + down_write(&devdata->lock_visor_dev); + if (devdata->paused) { + rc = -EBUSY; + goto out_locked; + } + devdata->paused = true; + complete_func(dev, 0); + rc = 0; +out_locked: + up_write(&devdata->lock_visor_dev); +out: + return rc; +} + +static int +visorinput_resume(struct visor_device *dev, + visorbus_state_complete_func complete_func) +{ + int rc; + struct visorinput_devdata *devdata = dev_get_drvdata(&dev->device); + + if (!devdata) { + rc = -ENODEV; + goto out; + } + down_write(&devdata->lock_visor_dev); + if (!devdata->paused) { + rc = -EBUSY; + goto out_locked; + } + devdata->paused = false; + complete_func(dev, 0); + rc = 0; +out_locked: + up_write(&devdata->lock_visor_dev); +out: + return rc; +} + +/* GUIDS for all channel types supported by this driver. */ +static struct visor_channeltype_descriptor visorinput_channel_types[] = { + { SPAR_KEYBOARD_CHANNEL_PROTOCOL_UUID, "keyboard"}, + { SPAR_MOUSE_CHANNEL_PROTOCOL_UUID, "mouse"}, + { NULL_UUID_LE, NULL } +}; + +static struct visor_driver visorinput_driver = { + .name = "visorinput", + .vertag = NULL, + .owner = THIS_MODULE, + .channel_types = visorinput_channel_types, + .probe = visorinput_probe, + .remove = visorinput_remove, + .channel_interrupt = visorinput_channel_interrupt, + .pause = visorinput_pause, + .resume = visorinput_resume, +}; + +static int +visorinput_init(void) +{ + return visorbus_register_visor_driver(&visorinput_driver); +} + +static void +visorinput_cleanup(void) +{ + visorbus_unregister_visor_driver(&visorinput_driver); +} + +module_init(visorinput_init); +module_exit(visorinput_cleanup); + +MODULE_DEVICE_TABLE(visorbus, visorinput_channel_types); + +MODULE_AUTHOR("Unisys"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("s-Par human input driver for guest Linux"); +MODULE_VERSION(VERSION); + +MODULE_ALIAS("visorbus:" SPAR_MOUSE_CHANNEL_PROTOCOL_UUID_STR); +MODULE_ALIAS("visorbus:" SPAR_KEYBOARD_CHANNEL_PROTOCOL_UUID_STR); diff --git a/drivers/staging/unisys/visornic/visornic_main.c b/drivers/staging/unisys/visornic/visornic_main.c index 9d3c1e282..296b11cea 100644 --- a/drivers/staging/unisys/visornic/visornic_main.c +++ b/drivers/staging/unisys/visornic/visornic_main.c @@ -37,9 +37,6 @@ */ #define MAX_BUF 163840 -static spinlock_t dev_num_pool_lock; -static void *dev_num_pool; /**< pool to grab device numbers from */ - static int visornic_probe(struct visor_device *dev); static void visornic_remove(struct visor_device *dev); static int visornic_pause(struct visor_device *dev, @@ -113,20 +110,17 @@ struct chanstat { }; struct visornic_devdata { - int devnum; unsigned short enabled; /* 0 disabled 1 enabled to receive */ unsigned short enab_dis_acked; /* NET_RCV_ENABLE/DISABLE acked by * IOPART */ struct visor_device *dev; - char name[99]; - struct list_head list_all; /* < link within list_all_devices list */ struct net_device *netdev; struct net_device_stats net_stats; atomic_t interrupt_rcvd; wait_queue_head_t rsp_queue; struct sk_buff **rcvbuf; - u64 uniquenum; /* TODO figure out why not used */ + u64 incarnation_id; /* lets IOPART know about re-birth */ unsigned short old_flags; /* flags as they were prior to * set_multicast_list */ @@ -201,12 +195,6 @@ struct visornic_devdata { struct uiscmdrsp cmdrsp[SIZEOF_CMDRSP]; }; - -/* List of all visornic_devdata structs, - * linked via the list_all member - */ -static LIST_HEAD(list_all_devices); -static DEFINE_SPINLOCK(lock_all_devices); static int visornic_poll(struct napi_struct *napi, int budget); static void poll_for_irq(unsigned long v); @@ -443,7 +431,7 @@ post_skb(struct uiscmdrsp *cmdrsp, cmdrsp->net.rcvpost.frag.pi_off = (unsigned long)skb->data & PI_PAGE_MASK; cmdrsp->net.rcvpost.frag.pi_len = skb->len; - cmdrsp->net.rcvpost.unique_num = devdata->uniquenum; + cmdrsp->net.rcvpost.unique_num = devdata->incarnation_id; if ((cmdrsp->net.rcvpost.frag.pi_off + skb->len) <= PI_PAGE_SIZE) { cmdrsp->net.type = NET_RCV_POST; @@ -1373,25 +1361,11 @@ visornic_rx(struct uiscmdrsp *cmdrsp) static struct visornic_devdata * devdata_initialize(struct visornic_devdata *devdata, struct visor_device *dev) { - int devnum = -1; - if (!devdata) return NULL; memset(devdata, '\0', sizeof(struct visornic_devdata)); - spin_lock(&dev_num_pool_lock); - devnum = find_first_zero_bit(dev_num_pool, MAXDEVICES); - set_bit(devnum, dev_num_pool); - spin_unlock(&dev_num_pool_lock); - if (devnum == MAXDEVICES) - devnum = -1; - if (devnum < 0) - return NULL; - devdata->devnum = devnum; devdata->dev = dev; - strncpy(devdata->name, dev_name(&dev->device), sizeof(devdata->name)); - spin_lock(&lock_all_devices); - list_add_tail(&devdata->list_all, &list_all_devices); - spin_unlock(&lock_all_devices); + devdata->incarnation_id = get_jiffies_64(); return devdata; } @@ -1404,12 +1378,6 @@ devdata_initialize(struct visornic_devdata *devdata, struct visor_device *dev) */ static void devdata_release(struct visornic_devdata *devdata) { - spin_lock(&dev_num_pool_lock); - clear_bit(devdata->devnum, dev_num_pool); - spin_unlock(&dev_num_pool_lock); - spin_lock(&lock_all_devices); - list_del(&devdata->list_all); - spin_unlock(&lock_all_devices); kfree(devdata->rcvbuf); kfree(devdata->cmdrsp_rcv); kfree(devdata->xmit_cmdrsp); @@ -1621,7 +1589,21 @@ send_rcv_posts_if_needed(struct visornic_devdata *devdata) } /** - * draing_queue - drains the response queue + * drain_resp_queue - drains and ignores all messages from the resp queue + * @cmdrsp: io channel command response message + * @devdata: visornic device to drain + */ +static void +drain_resp_queue(struct uiscmdrsp *cmdrsp, struct visornic_devdata *devdata) +{ + while (visorchannel_signalremove(devdata->dev->visorchannel, + IOCHAN_FROM_IOPART, + cmdrsp)) + ; +} + +/** + * service_resp_queue - drains the response queue * @cmdrsp: io channel command response message * @devdata: visornic device to drain * @@ -1810,6 +1792,8 @@ static int visornic_probe(struct visor_device *dev) err = -ENOMEM; goto cleanup_netdev; } + /* don't trust messages laying around in the channel */ + drain_resp_queue(devdata->cmdrsp, devdata); devdata->netdev = netdev; dev_set_drvdata(&dev->device, devdata); @@ -1830,8 +1814,8 @@ static int visornic_probe(struct visor_device *dev) goto cleanup_netdev; } - devdata->rcvbuf = kzalloc(sizeof(struct sk_buff *) * - devdata->num_rcv_bufs, GFP_KERNEL); + devdata->rcvbuf = kcalloc(devdata->num_rcv_bufs, + sizeof(struct sk_buff *), GFP_KERNEL); if (!devdata->rcvbuf) { err = -ENOMEM; goto cleanup_rcvbuf; @@ -1901,6 +1885,7 @@ static int visornic_probe(struct visor_device *dev) } features |= ULTRA_IO_CHANNEL_IS_POLLING; + features |= ULTRA_IO_DRIVER_SUPPORTS_ENHANCED_RCVBUF_CHECKING; err = visorbus_write_channel(dev, channel_offset, &features, 8); if (err) { dev_err(&dev->device, @@ -1964,7 +1949,6 @@ static void host_side_disappeared(struct visornic_devdata *devdata) unsigned long flags; spin_lock_irqsave(&devdata->priv_lock, flags); - sprintf(devdata->name, "", devdata->devnum); devdata->dev = NULL; /* indicate device destroyed */ spin_unlock_irqrestore(&devdata->priv_lock, flags); } @@ -2126,11 +2110,6 @@ static int visornic_init(void) if (!visornic_timeout_reset_workqueue) goto cleanup_workqueue; - spin_lock_init(&dev_num_pool_lock); - dev_num_pool = kzalloc(BITS_TO_LONGS(MAXDEVICES), GFP_KERNEL); - if (!dev_num_pool) - goto cleanup_workqueue; - err = visorbus_register_visor_driver(&visornic_driver); if (!err) return 0; @@ -2160,9 +2139,6 @@ static void visornic_cleanup(void) destroy_workqueue(visornic_timeout_reset_workqueue); } debugfs_remove_recursive(visornic_debugfs_dir); - - kfree(dev_num_pool); - dev_num_pool = NULL; } module_init(visornic_init); -- cgit v1.2.3-54-g00ecf