- 根目录:
- drivers
- staging
- unisys
- uislib
- uislib.c
/* uislib.c
*
* Copyright (C) 2010 - 2013 UNISYS CORPORATION
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for more
* details.
*/
/* @ALL_INSPECTED */
#define EXPORT_SYMTAB
#include <linux/kernel.h>
#include <linux/highmem.h>
#ifdef CONFIG_MODVERSIONS
#include <config/modversions.h>
#endif
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/types.h>
#include <linux/uuid.h>
#include <linux/version.h>
#include "diagnostics/appos_subsystems.h"
#include "uisutils.h"
#include "vbuschannel.h"
#include <linux/proc_fs.h>
#include <linux/uaccess.h> /* for copy_from_user */
#include <linux/ctype.h> /* for toupper */
#include <linux/list.h>
#include "sparstop.h"
#include "visorchipset.h"
#include "version.h"
#include "guestlinuxdebug.h"
#define SET_PROC_OWNER(x, y)
#define POLLJIFFIES_NORMAL 1
/* Choose whether or not you want to wakeup the request-polling thread
* after an IO termination:
* this is shorter than using __FILE__ (full path name) in
* debug/info/error messages
*/
#define CURRENT_FILE_PC UISLIB_PC_uislib_c
#define __MYFILE__ "uislib.c"
/* global function pointers that act as callback functions into virtpcimod */
int (*virt_control_chan_func)(struct guest_msgs *);
static int debug_buf_valid;
static char *debug_buf; /* Note this MUST be global,
* because the contents must */
static unsigned int chipset_inited;
#define WAIT_ON_CALLBACK(handle) \
do { \
if (handle) \
break; \
UIS_THREAD_WAIT; \
} while (1)
static struct bus_info *bus_list;
static rwlock_t bus_list_lock;
static int bus_list_count; /* number of buses in the list */
static int max_bus_count; /* maximum number of buses expected */
static u64 phys_data_chan;
static int platform_no;
static struct uisthread_info incoming_ti;
static BOOL incoming_started = FALSE;
static LIST_HEAD(poll_dev_chan);
static unsigned long long tot_moved_to_tail_cnt;
static unsigned long long tot_wait_cnt;
static unsigned long long tot_wakeup_cnt;
static unsigned long long tot_schedule_cnt;
static int en_smart_wakeup = 1;
static DEFINE_SEMAPHORE(poll_dev_lock); /* unlocked */
static DECLARE_WAIT_QUEUE_HEAD(poll_dev_wake_q);
static int poll_dev_start;
#define CALLHOME_PROC_ENTRY_FN "callhome"
#define CALLHOME_THROTTLED_PROC_ENTRY_FN "callhome_throttled"
#define DIR_DEBUGFS_ENTRY "uislib"
static struct dentry *dir_debugfs;
#define PLATFORMNUMBER_DEBUGFS_ENTRY_FN "platform"
static struct dentry *platformnumber_debugfs_read;
#define CYCLES_BEFORE_WAIT_DEBUGFS_ENTRY_FN "cycles_before_wait"
static struct dentry *cycles_before_wait_debugfs_read;
#define SMART_WAKEUP_DEBUGFS_ENTRY_FN "smart_wakeup"
static struct dentry *smart_wakeup_debugfs_entry;
#define INFO_DEBUGFS_ENTRY_FN "info"
static struct dentry *info_debugfs_entry;
static unsigned long long cycles_before_wait, wait_cycles;
/*****************************************************/
/* local functions */
/*****************************************************/
static ssize_t info_debugfs_read(struct file *file, char __user *buf,
size_t len, loff_t *offset);
static const struct file_operations debugfs_info_fops = {
.read = info_debugfs_read,
};
static void
init_msg_header(struct controlvm_message *msg, u32 id, uint rsp, uint svr)
{
memset(msg, 0, sizeof(struct controlvm_message));
msg->hdr.id = id;
msg->hdr.flags.response_expected = rsp;
msg->hdr.flags.server = svr;
}
static __iomem void *init_vbus_channel(u64 ch_addr, u32 ch_bytes)
{
void __iomem *ch = uislib_ioremap_cache(ch_addr, ch_bytes);
if (!ch)
return NULL;
if (!SPAR_VBUS_CHANNEL_OK_CLIENT(ch)) {
uislib_iounmap(ch);
return NULL;
}
return ch;
}
static int
create_bus(struct controlvm_message *msg, char *buf)
{
u32 bus_no, dev_count;
struct bus_info *tmp, *bus;
size_t size;
if (max_bus_count == bus_list_count) {
POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, max_bus_count,
POSTCODE_SEVERITY_ERR);
return CONTROLVM_RESP_ERROR_MAX_BUSES;
}
bus_no = msg->cmd.create_bus.bus_no;
dev_count = msg->cmd.create_bus.dev_count;
POSTCODE_LINUX_4(BUS_CREATE_ENTRY_PC, bus_no, dev_count,
POSTCODE_SEVERITY_INFO);
size =
sizeof(struct bus_info) +
(dev_count * sizeof(struct device_info *));
bus = kzalloc(size, GFP_ATOMIC);
if (!bus) {
POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, bus_no,
POSTCODE_SEVERITY_ERR);
return CONTROLVM_RESP_ERROR_KMALLOC_FAILED;
}
/* Currently by default, the bus Number is the GuestHandle.
* Configure Bus message can override this.
*/
if (msg->hdr.flags.test_message) {
/* This implies we're the IOVM so set guest handle to 0... */
bus->guest_handle = 0;
bus->bus_no = bus_no;
bus->local_vnic = 1;
} else {
bus->bus_no = bus_no;
bus->guest_handle = bus_no;
}
sprintf(bus->name, "%d", (int)bus->bus_no);
bus->device_count = dev_count;
bus->device =
(struct device_info **)((char *)bus + sizeof(struct bus_info));
bus->bus_inst_uuid = msg->cmd.create_bus.bus_inst_uuid;
bus->bus_channel_bytes = 0;
bus->bus_channel = NULL;
/* add bus to our bus list - but check for duplicates first */
read_lock(&bus_list_lock);
for (tmp = bus_list; tmp; tmp = tmp->next) {
if (tmp->bus_no == bus->bus_no)
break;
}
read_unlock(&bus_list_lock);
if (tmp) {
/* found a bus already in the list with same bus_no -
* reject add
*/
POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, bus->bus_no,
POSTCODE_SEVERITY_ERR);
kfree(bus);
return CONTROLVM_RESP_ERROR_ALREADY_DONE;
}
if ((msg->cmd.create_bus.channel_addr != 0) &&
(msg->cmd.create_bus.channel_bytes != 0)) {
bus->bus_channel_bytes = msg->cmd.create_bus.channel_bytes;
bus->bus_channel =
init_vbus_channel(msg->cmd.create_bus.channel_addr,
msg->cmd.create_bus.channel_bytes);
}
/* the msg is bound for virtpci; send guest_msgs struct to callback */
if (!msg->hdr.flags.server) {
struct guest_msgs cmd;
cmd.msgtype = GUEST_ADD_VBUS;
cmd.add_vbus.bus_no = bus_no;
cmd.add_vbus.chanptr = bus->bus_channel;
cmd.add_vbus.dev_count = dev_count;
cmd.add_vbus.bus_uuid = msg->cmd.create_bus.bus_data_type_uuid;
cmd.add_vbus.instance_uuid = msg->cmd.create_bus.bus_inst_uuid;
if (!virt_control_chan_func) {
POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, bus->bus_no,
POSTCODE_SEVERITY_ERR);
kfree(bus);
return CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE;
}
if (!virt_control_chan_func(&cmd)) {
POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, bus->bus_no,
POSTCODE_SEVERITY_ERR);
kfree(bus);
return
CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR;
}
}
/* add bus at the head of our list */
write_lock(&bus_list_lock);
if (!bus_list) {
bus_list = bus;
} else {
bus->next = bus_list;
bus_list = bus;
}
bus_list_count++;
write_unlock(&bus_list_lock);
POSTCODE_LINUX_3(BUS_CREATE_EXIT_PC, bus->bus_no,
POSTCODE_SEVERITY_INFO);
return CONTROLVM_RESP_SUCCESS;
}
static int
destroy_bus(struct controlvm_message *msg, char *buf)
{
int i;
struct bus_info *bus, *prev = NULL;
struct guest_msgs cmd;
u32 bus_no;
bus_no = msg->cmd.destroy_bus.bus_no;
read_lock(&bus_list_lock);
bus = bus_list;
while (bus) {
if (bus->bus_no == bus_no)
break;
prev = bus;
bus = bus->next;
}
if (!bus) {
read_unlock(&bus_list_lock);
return CONTROLVM_RESP_ERROR_ALREADY_DONE;
}
/* verify that this bus has no devices. */
for (i = 0; i < bus->device_count; i++) {
if (bus->device[i]) {
read_unlock(&bus_list_lock);
return CONTROLVM_RESP_ERROR_BUS_DEVICE_ATTACHED;
}
}
read_unlock(&bus_list_lock);
if (msg->hdr.flags.server)
goto remove;
/* client messages require us to call the virtpci callback associated
with this bus. */
cmd.msgtype = GUEST_DEL_VBUS;
cmd.del_vbus.bus_no = bus_no;
if (!virt_control_chan_func)
return CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE;
if (!virt_control_chan_func(&cmd))
return CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR;
/* finally, remove the bus from the list */
remove:
write_lock(&bus_list_lock);
if (prev) /* not at head */
prev->next = bus->next;
else
bus_list = bus->next;
bus_list_count--;
write_unlock(&bus_list_lock);
if (bus->bus_channel) {
uislib_iounmap(bus->bus_channel);
bus->bus_channel = NULL;
}
kfree(bus);
return CONTROLVM_RESP_SUCCESS;
}
static int create_device(struct controlvm_message *msg, char *buf)
{
struct device_info *dev;
struct bus_info *bus;
struct guest_msgs cmd;
u32 bus_no, dev_no;
int result = CONTROLVM_RESP_SUCCESS;
u64 min_size = MIN_IO_CHANNEL_SIZE;
struct req_handler_info *req_handler;
bus_no = msg->cmd.create_device.bus_no;
dev_no = msg->cmd.create_device.dev_no;
POSTCODE_LINUX_4(DEVICE_CREATE_ENTRY_PC, dev_no, bus_no,
POSTCODE_SEVERITY_INFO);
dev = kzalloc(sizeof(*dev), GFP_ATOMIC);
if (!dev) {
POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, dev_no, bus_no,
POSTCODE_SEVERITY_ERR);
return CONTROLVM_RESP_ERROR_KMALLOC_FAILED;
}
dev->channel_uuid = msg->cmd.create_device.data_type_uuid;
dev->intr = msg->cmd.create_device.intr;
dev->channel_addr = msg->cmd.create_device.channel_addr;
dev->bus_no = bus_no;
dev->dev_no = dev_no;
sema_init(&dev->interrupt_callback_lock, 1); /* unlocked */
sprintf(dev->devid, "vbus%u:dev%u", (unsigned)bus_no, (unsigned)dev_no);
/* map the channel memory for the device. */
if (msg->hdr.flags.test_message) {
dev->chanptr = (void __iomem *)__va(dev->channel_addr);
} else {
req_handler = req_handler_find(dev->channel_uuid);
if (req_handler)
/* generic service handler registered for this
* channel
*/
min_size = req_handler->min_channel_bytes;
if (min_size > msg->cmd.create_device.channel_bytes) {
POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, dev_no,
bus_no, POSTCODE_SEVERITY_ERR);
result = CONTROLVM_RESP_ERROR_CHANNEL_SIZE_TOO_SMALL;
goto cleanup;
}
dev->chanptr =
uislib_ioremap_cache(dev->channel_addr,
msg->cmd.create_device.channel_bytes);
if (!dev->chanptr) {
result = CONTROLVM_RESP_ERROR_IOREMAP_FAILED;
POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, dev_no,
bus_no, POSTCODE_SEVERITY_ERR);
goto cleanup;
}
}
dev->instance_uuid = msg->cmd.create_device.dev_inst_uuid;
dev->channel_bytes = msg->cmd.create_device.channel_bytes;
read_lock(&bus_list_lock);
for (bus = bus_list; bus; bus = bus->next) {
if (bus->bus_no != bus_no)
continue;
/* make sure the device number is valid */
if (dev_no >= bus->device_count) {
result = CONTROLVM_RESP_ERROR_MAX_DEVICES;
POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, dev_no,
bus_no, POSTCODE_SEVERITY_ERR);
read_unlock(&bus_list_lock);
goto cleanup;
}
/* make sure this device is not already set */
if (bus->device[dev_no]) {
POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC,
dev_no, bus_no,
POSTCODE_SEVERITY_ERR);
result = CONTROLVM_RESP_ERROR_ALREADY_DONE;
read_unlock(&bus_list_lock);
goto cleanup;
}
read_unlock(&bus_list_lock);
/* the msg is bound for virtpci; send
* guest_msgs struct to callback
*/
if (msg->hdr.flags.server) {
bus->device[dev_no] = dev;
POSTCODE_LINUX_4(DEVICE_CREATE_SUCCESS_PC, dev_no,
bus_no, POSTCODE_SEVERITY_INFO);
return CONTROLVM_RESP_SUCCESS;
}
if (uuid_le_cmp(dev->channel_uuid,
spar_vhba_channel_protocol_uuid) == 0) {
wait_for_valid_guid(&((struct channel_header __iomem *)
(dev->chanptr))->chtype);
if (!SPAR_VHBA_CHANNEL_OK_CLIENT(dev->chanptr)) {
POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC,
dev_no, bus_no,
POSTCODE_SEVERITY_ERR);
result = CONTROLVM_RESP_ERROR_CHANNEL_INVALID;
goto cleanup;
}
cmd.msgtype = GUEST_ADD_VHBA;
cmd.add_vhba.chanptr = dev->chanptr;
cmd.add_vhba.bus_no = bus_no;
cmd.add_vhba.device_no = dev_no;
cmd.add_vhba.instance_uuid = dev->instance_uuid;
cmd.add_vhba.intr = dev->intr;
} else if (uuid_le_cmp(dev->channel_uuid,
spar_vnic_channel_protocol_uuid) == 0) {
wait_for_valid_guid(&((struct channel_header __iomem *)
(dev->chanptr))->chtype);
if (!SPAR_VNIC_CHANNEL_OK_CLIENT(dev->chanptr)) {
POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC,
dev_no, bus_no,
POSTCODE_SEVERITY_ERR);
result = CONTROLVM_RESP_ERROR_CHANNEL_INVALID;
goto cleanup;
}
cmd.msgtype = GUEST_ADD_VNIC;
cmd.add_vnic.chanptr = dev->chanptr;
cmd.add_vnic.bus_no = bus_no;
cmd.add_vnic.device_no = dev_no;
cmd.add_vnic.instance_uuid = dev->instance_uuid;
cmd.add_vhba.intr = dev->intr;
} else {
POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, dev_no,
bus_no, POSTCODE_SEVERITY_ERR);
result = CONTROLVM_RESP_ERROR_CHANNEL_TYPE_UNKNOWN;
goto cleanup;
}
if (!virt_control_chan_func) {
POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, dev_no,
bus_no, POSTCODE_SEVERITY_ERR);
result = CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE;
goto cleanup;
}
if (!virt_control_chan_func(&cmd)) {
POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, dev_no,
bus_no, POSTCODE_SEVERITY_ERR);
result =
CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR;
goto cleanup;
}
bus->device[dev_no] = dev;
POSTCODE_LINUX_4(DEVICE_CREATE_SUCCESS_PC, dev_no,
bus_no, POSTCODE_SEVERITY_INFO);
return CONTROLVM_RESP_SUCCESS;
}
read_unlock(&bus_list_lock);
POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, dev_no, bus_no,
POSTCODE_SEVERITY_ERR);
result = CONTROLVM_RESP_ERROR_BUS_INVALID;
cleanup:
if (!msg->hdr.flags.test_message) {
uislib_iounmap(dev->chanptr);
dev->chanptr = NULL;
}
kfree(dev);
return result;
}
static int pause_device(struct controlvm_message *msg)
{
u32 bus_no, dev_no;
struct bus_info *bus;
struct device_info *dev;
struct guest_msgs cmd;
int retval = CONTROLVM_RESP_SUCCESS;
bus_no = msg->cmd.device_change_state.bus_no;
dev_no = msg->cmd.device_change_state.dev_no;
read_lock(&bus_list_lock);
for (bus = bus_list; bus; bus = bus->next) {
if (bus->bus_no == bus_no) {
/* make sure the device number is valid */
if (dev_no >= bus->device_count) {
retval = CONTROLVM_RESP_ERROR_DEVICE_INVALID;
} else {
/* make sure this device exists */
dev = bus->device[dev_no];
if (!dev) {
retval =
CONTROLVM_RESP_ERROR_ALREADY_DONE;
}
}
break;
}
}
if (!bus)
retval = CONTROLVM_RESP_ERROR_BUS_INVALID;
read_unlock(&bus_list_lock);
if (retval == CONTROLVM_RESP_SUCCESS) {
/* the msg is bound for virtpci; send
* guest_msgs struct to callback
*/
if (uuid_le_cmp(dev->channel_uuid,
spar_vhba_channel_protocol_uuid) == 0) {
cmd.msgtype = GUEST_PAUSE_VHBA;
cmd.pause_vhba.chanptr = dev->chanptr;
} else if (uuid_le_cmp(dev->channel_uuid,
spar_vnic_channel_protocol_uuid) == 0) {
cmd.msgtype = GUEST_PAUSE_VNIC;
cmd.pause_vnic.chanptr = dev->chanptr;
} else {
return CONTROLVM_RESP_ERROR_CHANNEL_TYPE_UNKNOWN;
}
if (!virt_control_chan_func)
return CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE;
if (!virt_control_chan_func(&cmd)) {
return
CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR;
}
}
return retval;
}
static int resume_device(struct controlvm_message *msg)
{
u32 bus_no, dev_no;
struct bus_info *bus;
struct device_info *dev;
struct guest_msgs cmd;
int retval = CONTROLVM_RESP_SUCCESS;
bus_no = msg->cmd.device_change_state.bus_no;
dev_no = msg->cmd.device_change_state.dev_no;
read_lock(&bus_list_lock);
for (bus = bus_list; bus; bus = bus->next) {
if (bus->bus_no == bus_no) {
/* make sure the device number is valid */
if (dev_no >= bus->device_count) {
retval = CONTROLVM_RESP_ERROR_DEVICE_INVALID;
} else {
/* make sure this device exists */
dev = bus->device[dev_no];
if (!dev) {
retval =
CONTROLVM_RESP_ERROR_ALREADY_DONE;
}
}
break;
}
}
if (!bus)
retval = CONTROLVM_RESP_ERROR_BUS_INVALID;
read_unlock(&bus_list_lock);
/* the msg is bound for virtpci; send
* guest_msgs struct to callback
*/
if (retval == CONTROLVM_RESP_SUCCESS) {
if (uuid_le_cmp(dev->channel_uuid,
spar_vhba_channel_protocol_uuid) == 0) {
cmd.msgtype = GUEST_RESUME_VHBA;
cmd.resume_vhba.chanptr = dev->chanptr;
} else if (uuid_le_cmp(dev->channel_uuid,
spar_vnic_channel_protocol_uuid) == 0) {
cmd.msgtype = GUEST_RESUME_VNIC;
cmd.resume_vnic.chanptr = dev->chanptr;
} else {
return CONTROLVM_RESP_ERROR_CHANNEL_TYPE_UNKNOWN;
}
if (!virt_control_chan_func)
return CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE;
if (!virt_control_chan_func(&cmd)) {
return
CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR;
}
}
return retval;
}
static int destroy_device(struct controlvm_message *msg, char *buf)
{
u32 bus_no, dev_no;
struct bus_info *bus;
struct device_info *dev;
struct guest_msgs cmd;
int retval = CONTROLVM_RESP_SUCCESS;
bus_no = msg->cmd.destroy_device.bus_no;
dev_no = msg->cmd.destroy_device.bus_no;
read_lock(&bus_list_lock);
for (bus = bus_list; bus; bus = bus->next) {
if (bus->bus_no == bus_no) {
/* make sure the device number is valid */
if (dev_no >= bus->device_count) {
retval = CONTROLVM_RESP_ERROR_DEVICE_INVALID;
} else {
/* make sure this device exists */
dev = bus->device[dev_no];
if (!dev) {
retval =
CONTROLVM_RESP_ERROR_ALREADY_DONE;
}
}
break;
}
}
if (!bus)
retval = CONTROLVM_RESP_ERROR_BUS_INVALID;
read_unlock(&bus_list_lock);
if (retval == CONTROLVM_RESP_SUCCESS) {
/* the msg is bound for virtpci; send
* guest_msgs struct to callback
*/
if (uuid_le_cmp(dev->channel_uuid,
spar_vhba_channel_protocol_uuid) == 0) {
cmd.msgtype = GUEST_DEL_VHBA;
cmd.del_vhba.chanptr = dev->chanptr;
} else if (uuid_le_cmp(dev->channel_uuid,
spar_vnic_channel_protocol_uuid) == 0) {
cmd.msgtype = GUEST_DEL_VNIC;
cmd.del_vnic.chanptr = dev->chanptr;
} else {
return
CONTROLVM_RESP_ERROR_CHANNEL_TYPE_UNKNOWN;
}
if (!virt_control_chan_func) {
return
CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_FAILURE;
}
if (!virt_control_chan_func(&cmd)) {
return
CONTROLVM_RESP_ERROR_VIRTPCI_DRIVER_CALLBACK_ERROR;
}
/* you must disable channel interrupts BEFORE you unmap the channel,
* because if you unmap first, there may still be some activity going
* on which accesses the channel and you will get a "unable to handle
* kernel paging request"
*/
if (dev->polling)
uislib_disable_channel_interrupts(bus_no, dev_no);
/* unmap the channel memory for the device. */
if (!msg->hdr.flags.test_message)
uislib_iounmap(dev->chanptr);
kfree(dev);
bus->device[dev_no] = NULL;
}
return retval;
}
static int
init_chipset(struct controlvm_message *msg, char *buf)
{
POSTCODE_LINUX_2(CHIPSET_INIT_ENTRY_PC, POSTCODE_SEVERITY_INFO);
max_bus_count = msg->cmd.init_chipset.bus_count;
platform_no = msg->cmd.init_chipset.platform_number;
phys_data_chan = 0;
/* We need to make sure we have our functions registered
* before processing messages. If we are a test vehicle the
* test_message for init_chipset will be set. We can ignore the
* waits for the callbacks, since this will be manually entered
* from a user. If no test_message is set, we will wait for the
* functions.
*/
if (!msg->hdr.flags.test_message)
WAIT_ON_CALLBACK(virt_control_chan_func);
chipset_inited = 1;
POSTCODE_LINUX_2(CHIPSET_INIT_EXIT_PC, POSTCODE_SEVERITY_INFO);
return CONTROLVM_RESP_SUCCESS;
}
static int delete_bus_glue(u32 bus_no)
{
struct controlvm_message msg;
init_msg_header(&msg, CONTROLVM_BUS_DESTROY, 0, 0);
msg.cmd.destroy_bus.bus_no = bus_no;
if (destroy_bus(&msg, NULL) != CONTROLVM_RESP_SUCCESS)
return 0;
return 1;
}
static int delete_device_glue(u32 bus_no, u32 dev_no)
{
struct controlvm_message msg;
init_msg_header(&msg, CONTROLVM_DEVICE_DESTROY, 0, 0);
msg.cmd.destroy_device.bus_no = bus_no;
msg.cmd.destroy_device.dev_no = dev_no;
if (destroy_device(&msg, NULL) != CONTROLVM_RESP_SUCCESS)
return 0;
return 1;
}
int
uislib_client_inject_add_bus(u32 bus_no, uuid_le inst_uuid,
u64 channel_addr, ulong n_channel_bytes)
{
struct controlvm_message msg;
/* step 0: init the chipset */
POSTCODE_LINUX_3(CHIPSET_INIT_ENTRY_PC, bus_no, POSTCODE_SEVERITY_INFO);
if (!chipset_inited) {
/* step: initialize the chipset */
init_msg_header(&msg, CONTROLVM_CHIPSET_INIT, 0, 0);
/* this change is needed so that console will come up
* OK even when the bus 0 create comes in late. If the
* bus 0 create is the first create, then the add_vnic
* will work fine, but if the bus 0 create arrives
* after number 4, then the add_vnic will fail, and the
* ultraboot will fail.
*/
msg.cmd.init_chipset.bus_count = 23;
msg.cmd.init_chipset.switch_count = 0;
if (init_chipset(&msg, NULL) != CONTROLVM_RESP_SUCCESS)
return 0;
POSTCODE_LINUX_3(CHIPSET_INIT_EXIT_PC, bus_no,
POSTCODE_SEVERITY_INFO);
}
/* step 1: create a bus */
POSTCODE_LINUX_3(BUS_CREATE_ENTRY_PC, bus_no,
POSTCODE_SEVERITY_WARNING);
init_msg_header(&msg, CONTROLVM_BUS_CREATE, 0, 0);
msg.cmd.create_bus.bus_no = bus_no;
msg.cmd.create_bus.dev_count = 23; /* devNo+1; */
msg.cmd.create_bus.channel_addr = channel_addr;
msg.cmd.create_bus.channel_bytes = n_channel_bytes;
if (create_bus(&msg, NULL) != CONTROLVM_RESP_SUCCESS) {
POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, bus_no,
POSTCODE_SEVERITY_ERR);
return 0;
}
POSTCODE_LINUX_3(BUS_CREATE_EXIT_PC, bus_no, POSTCODE_SEVERITY_INFO);
return 1;
}
EXPORT_SYMBOL_GPL(uislib_client_inject_add_bus);
int
uislib_client_inject_del_bus(u32 bus_no)
{
return delete_bus_glue(bus_no);
}
EXPORT_SYMBOL_GPL(uislib_client_inject_del_bus);
int
uislib_client_inject_pause_vhba(u32 bus_no, u32 dev_no)
{
struct controlvm_message msg;
int rc;
init_msg_header(&msg, CONTROLVM_DEVICE_CHANGESTATE, 0, 0);
msg.cmd.device_change_state.bus_no = bus_no;
msg.cmd.device_change_state.dev_no = dev_no;
msg.cmd.device_change_state.state = segment_state_standby;
rc = pause_device(&msg);
if (rc != CONTROLVM_RESP_SUCCESS)
return rc;
return 0;
}
EXPORT_SYMBOL_GPL(uislib_client_inject_pause_vhba);
int
uislib_client_inject_resume_vhba(u32 bus_no, u32 dev_no)
{
struct controlvm_message msg;
int rc;
init_msg_header(&msg, CONTROLVM_DEVICE_CHANGESTATE, 0, 0);
msg.cmd.device_change_state.bus_no = bus_no;
msg.cmd.device_change_state.dev_no = dev_no;
msg.cmd.device_change_state.state = segment_state_running;
rc = resume_device(&msg);
if (rc != CONTROLVM_RESP_SUCCESS)
return rc;
return 0;
}
EXPORT_SYMBOL_GPL(uislib_client_inject_resume_vhba);
int
uislib_client_inject_add_vhba(u32 bus_no, u32 dev_no,
u64 phys_chan_addr, u32 chan_bytes,
int is_test_addr, uuid_le inst_uuid,
struct irq_info *intr)
{
struct controlvm_message msg;
/* chipset init'ed with bus bus has been previously created -
* Verify it still exists step 2: create the VHBA device on the
* bus
*/
POSTCODE_LINUX_4(VHBA_CREATE_ENTRY_PC, dev_no, bus_no,
POSTCODE_SEVERITY_INFO);
init_msg_header(&msg, CONTROLVM_DEVICE_CREATE, 0, 0);
if (is_test_addr)
/* signify that the physical channel address does NOT
* need to be ioremap()ed
*/
msg.hdr.flags.test_message = 1;
msg.cmd.create_device.bus_no = bus_no;
msg.cmd.create_device.dev_no = dev_no;
msg.cmd.create_device.dev_inst_uuid = inst_uuid;
if (intr)
msg.cmd.create_device.intr = *intr;
else
memset(&msg.cmd.create_device.intr, 0,
sizeof(struct irq_info));
msg.cmd.create_device.channel_addr = phys_chan_addr;
if (chan_bytes < MIN_IO_CHANNEL_SIZE) {
POSTCODE_LINUX_4(VHBA_CREATE_FAILURE_PC, chan_bytes,
MIN_IO_CHANNEL_SIZE, POSTCODE_SEVERITY_ERR);
return 0;
}
msg.cmd.create_device.channel_bytes = chan_bytes;
msg.cmd.create_device.data_type_uuid = spar_vhba_channel_protocol_uuid;
if (create_device(&msg, NULL) != CONTROLVM_RESP_SUCCESS) {
POSTCODE_LINUX_4(VHBA_CREATE_FAILURE_PC, dev_no, bus_no,
POSTCODE_SEVERITY_ERR);
return 0;
}
POSTCODE_LINUX_4(VHBA_CREATE_SUCCESS_PC, dev_no, bus_no,
POSTCODE_SEVERITY_INFO);
return 1;
}
EXPORT_SYMBOL_GPL(uislib_client_inject_add_vhba);
int
uislib_client_inject_del_vhba(u32 bus_no, u32 dev_no)
{
return delete_device_glue(bus_no, dev_no);
}
EXPORT_SYMBOL_GPL(uislib_client_inject_del_vhba);
int
uislib_client_inject_add_vnic(u32 bus_no, u32 dev_no,
u64 phys_chan_addr, u32 chan_bytes,
int is_test_addr, uuid_le inst_uuid,
struct irq_info *intr)
{
struct controlvm_message msg;
/* chipset init'ed with bus bus has been previously created -
* Verify it still exists step 2: create the VNIC device on the
* bus
*/
POSTCODE_LINUX_4(VNIC_CREATE_ENTRY_PC, dev_no, bus_no,
POSTCODE_SEVERITY_INFO);
init_msg_header(&msg, CONTROLVM_DEVICE_CREATE, 0, 0);
if (is_test_addr)
/* signify that the physical channel address does NOT
* need to be ioremap()ed
*/
msg.hdr.flags.test_message = 1;
msg.cmd.create_device.bus_no = bus_no;
msg.cmd.create_device.dev_no = dev_no;
msg.cmd.create_device.dev_inst_uuid = inst_uuid;
if (intr)
msg.cmd.create_device.intr = *intr;
else
memset(&msg.cmd.create_device.intr, 0,
sizeof(struct irq_info));
msg.cmd.create_device.channel_addr = phys_chan_addr;
if (chan_bytes < MIN_IO_CHANNEL_SIZE) {
POSTCODE_LINUX_4(VNIC_CREATE_FAILURE_PC, chan_bytes,
MIN_IO_CHANNEL_SIZE, POSTCODE_SEVERITY_ERR);
return 0;
}
msg.cmd.create_device.channel_bytes = chan_bytes;
msg.cmd.create_device.data_type_uuid = spar_vnic_channel_protocol_uuid;
if (create_device(&msg, NULL) != CONTROLVM_RESP_SUCCESS) {
POSTCODE_LINUX_4(VNIC_CREATE_FAILURE_PC, dev_no, bus_no,
POSTCODE_SEVERITY_ERR);
return 0;
}
POSTCODE_LINUX_4(VNIC_CREATE_SUCCESS_PC, dev_no, bus_no,
POSTCODE_SEVERITY_INFO);
return 1;
}
EXPORT_SYMBOL_GPL(uislib_client_inject_add_vnic);
int
uislib_client_inject_pause_vnic(u32 bus_no, u32 dev_no)
{
struct controlvm_message msg;
int rc;
init_msg_header(&msg, CONTROLVM_DEVICE_CHANGESTATE, 0, 0);
msg.cmd.device_change_state.bus_no = bus_no;
msg.cmd.device_change_state.dev_no = dev_no;
msg.cmd.device_change_state.state = segment_state_standby;
rc = pause_device(&msg);
if (rc != CONTROLVM_RESP_SUCCESS)
return -1;
return 0;
}
EXPORT_SYMBOL_GPL(uislib_client_inject_pause_vnic);
int
uislib_client_inject_resume_vnic(u32 bus_no, u32 dev_no)
{
struct controlvm_message msg;
int rc;
init_msg_header(&msg, CONTROLVM_DEVICE_CHANGESTATE, 0, 0);
msg.cmd.device_change_state.bus_no = bus_no;
msg.cmd.device_change_state.dev_no = dev_no;
msg.cmd.device_change_state.state = segment_state_running;
rc = resume_device(&msg);
if (rc != CONTROLVM_RESP_SUCCESS)
return -1;
return 0;
}
EXPORT_SYMBOL_GPL(uislib_client_inject_resume_vnic);
int
uislib_client_inject_del_vnic(u32 bus_no, u32 dev_no)
{
return delete_device_glue(bus_no, dev_no);
}
EXPORT_SYMBOL_GPL(uislib_client_inject_del_vnic);
void *
uislib_cache_alloc(struct kmem_cache *cur_pool, char *fn, int ln)
{
/* __GFP_NORETRY means "ok to fail", meaning kmalloc() can
* return NULL. If you do NOT specify __GFP_NORETRY, Linux
* will go to extreme measures to get memory for you (like,
* invoke oom killer), which will probably cripple the system.
*/
void *p = kmem_cache_alloc(cur_pool, GFP_ATOMIC | __GFP_NORETRY);
if (!p)
return NULL;
return p;
}
EXPORT_SYMBOL_GPL(uislib_cache_alloc);
void
uislib_cache_free(struct kmem_cache *cur_pool, void *p, char *fn, int ln)
{
if (!p)
return;
kmem_cache_free(cur_pool, p);
}
EXPORT_SYMBOL_GPL(uislib_cache_free);
/*****************************************************/
/* proc filesystem callback functions */
/*****************************************************/
#define PLINE(...) uisutil_add_proc_line_ex(&tot, buff, \
buff_len, __VA_ARGS__)
static int
info_debugfs_read_helper(char **buff, int *buff_len)
{
int i, tot = 0;
struct bus_info *bus;
if (PLINE("\nBuses:\n") < 0)
goto err_done;
read_lock(&bus_list_lock);
for (bus = bus_list; bus; bus = bus->next) {
if (PLINE(" bus=0x%p, busNo=%d, deviceCount=%d\n",
bus, bus->bus_no, bus->device_count) < 0)
goto err_done_unlock;
if (PLINE(" Devices:\n") < 0)
goto err_done_unlock;
for (i = 0; i < bus->device_count; i++) {
if (bus->device[i]) {
if (PLINE(" busNo %d, device[%i]: 0x%p, chanptr=0x%p, swtch=0x%p\n",
bus->bus_no, i, bus->device[i],
bus->device[i]->chanptr,
bus->device[i]->swtch) < 0)
goto err_done_unlock;
if (PLINE(" first_busy_cnt=%llu, moved_to_tail_cnt=%llu, last_on_list_cnt=%llu\n",
bus->device[i]->first_busy_cnt,
bus->device[i]->moved_to_tail_cnt,
bus->device[i]->last_on_list_cnt) < 0)
goto err_done_unlock;
}
}
}
read_unlock(&bus_list_lock);
if (PLINE("UisUtils_Registered_Services: %d\n",
atomic_read(&uisutils_registered_services)) < 0)
goto err_done;
if (PLINE("cycles_before_wait %llu wait_cycles:%llu\n",
cycles_before_wait, wait_cycles) < 0)
goto err_done;
if (PLINE("tot_wakeup_cnt %llu:tot_wait_cnt %llu:tot_schedule_cnt %llu\n",
tot_wakeup_cnt, tot_wait_cnt, tot_schedule_cnt) < 0)
goto err_done;
if (PLINE("en_smart_wakeup %d\n", en_smart_wakeup) < 0)
goto err_done;
if (PLINE("tot_moved_to_tail_cnt %llu\n", tot_moved_to_tail_cnt) < 0)
goto err_done;
return tot;
err_done_unlock:
read_unlock(&bus_list_lock);
err_done:
return -1;
}
static ssize_t info_debugfs_read(struct file *file, char __user *buf,
size_t len, loff_t *offset)
{
char *temp;
int total_bytes = 0;
int remaining_bytes = PROC_READ_BUFFER_SIZE;
/* *start = buf; */
if (!debug_buf) {
debug_buf = vmalloc(PROC_READ_BUFFER_SIZE);
if (!debug_buf)
return -ENOMEM;
}
temp = debug_buf;
if ((*offset == 0) || (!debug_buf_valid)) {
/* if the read fails, then -1 will be returned */
total_bytes = info_debugfs_read_helper(&temp, &remaining_bytes);
debug_buf_valid = 1;
} else {
total_bytes = strlen(debug_buf);
}
return simple_read_from_buffer(buf, len, offset,
debug_buf, total_bytes);
}
static struct device_info *find_dev(u32 bus_no, u32 dev_no)
{
struct bus_info *bus;
struct device_info *dev = NULL;
read_lock(&bus_list_lock);
for (bus = bus_list; bus; bus = bus->next) {
if (bus->bus_no == bus_no) {
/* make sure the device number is valid */
if (dev_no >= bus->device_count)
break;
dev = bus->device[dev_no];
break;
}
}
read_unlock(&bus_list_lock);
return dev;
}
/* This thread calls the "interrupt" function for each device that has
* enabled such using uislib_enable_channel_interrupts(). The "interrupt"
* function typically reads and processes the devices's channel input
* queue. This thread repeatedly does this, until the thread is told to stop
* (via uisthread_stop()). Sleeping rules:
* - If we have called the "interrupt" function for all devices, and all of
* them have reported "nothing processed" (returned 0), then we will go to
* sleep for a maximum of POLLJIFFIES_NORMAL jiffies.
* - If anyone calls uislib_force_channel_interrupt(), the above jiffy
* sleep will be interrupted, and we will resume calling the "interrupt"
* function for all devices.
* - The list of devices is dynamically re-ordered in order to
* attempt to preserve fairness. Whenever we spin thru the list of
* devices and call the dev->interrupt() function, if we find
* devices which report that there is still more work to do, the
* the first such device we find is moved to the end of the device
* list. This ensures that extremely busy devices don't starve out
* less-busy ones.
*
*/
static int process_incoming(void *v)
{
unsigned long long cur_cycles, old_cycles, idle_cycles, delta_cycles;
struct list_head *new_tail = NULL;
int i;
UIS_DAEMONIZE("dev_incoming");
for (i = 0; i < 16; i++) {
old_cycles = get_cycles();
wait_event_timeout(poll_dev_wake_q,
0, POLLJIFFIES_NORMAL);
cur_cycles = get_cycles();
if (wait_cycles == 0) {
wait_cycles = (cur_cycles - old_cycles);
} else {
if (wait_cycles < (cur_cycles - old_cycles))
wait_cycles = (cur_cycles - old_cycles);
}
}
cycles_before_wait = wait_cycles;
idle_cycles = 0;
poll_dev_start = 0;
while (1) {
struct list_head *lelt, *tmp;
struct device_info *dev = NULL;
/* poll each channel for input */
down(&poll_dev_lock);
new_tail = NULL;
list_for_each_safe(lelt, tmp, &poll_dev_chan) {
int rc = 0;
dev = list_entry(lelt, struct device_info,
list_polling_device_channels);
down(&dev->interrupt_callback_lock);
if (dev->interrupt)
rc = dev->interrupt(dev->interrupt_context);
else
continue;
up(&dev->interrupt_callback_lock);
if (rc) {
/* dev->interrupt returned, but there
* is still more work to do.
* Reschedule work to occur as soon as
* possible. */
idle_cycles = 0;
if (!new_tail) {
dev->first_busy_cnt++;
if (!
(list_is_last
(lelt,
&poll_dev_chan))) {
new_tail = lelt;
dev->moved_to_tail_cnt++;
} else {
dev->last_on_list_cnt++;
}
}
}
if (kthread_should_stop())
break;
}
if (new_tail) {
tot_moved_to_tail_cnt++;
list_move_tail(new_tail, &poll_dev_chan);
}
up(&poll_dev_lock);
cur_cycles = get_cycles();
delta_cycles = cur_cycles - old_cycles;
old_cycles = cur_cycles;
/* At this point, we have scanned thru all of the
* channels, and at least one of the following is true:
* - there is no input waiting on any of the channels
* - we have received a signal to stop this thread
*/
if (kthread_should_stop())
break;
if (en_smart_wakeup == 0xFF)
break;
/* wait for POLLJIFFIES_NORMAL jiffies, or until
* someone wakes up poll_dev_wake_q,
* whichever comes first only do a wait when we have
* been idle for cycles_before_wait cycles.
*/
if (idle_cycles > cycles_before_wait) {
poll_dev_start = 0;
tot_wait_cnt++;
wait_event_timeout(poll_dev_wake_q,
poll_dev_start,
POLLJIFFIES_NORMAL);
poll_dev_start = 1;
} else {
tot_schedule_cnt++;
schedule();
idle_cycles = idle_cycles + delta_cycles;
}
}
complete_and_exit(&incoming_ti.has_stopped, 0);
}
static BOOL
initialize_incoming_thread(void)
{
if (incoming_started)
return TRUE;
if (!uisthread_start(&incoming_ti,
&process_incoming, NULL, "dev_incoming")) {
return FALSE;
}
incoming_started = TRUE;
return TRUE;
}
/* Add a new device/channel to the list being processed by
* process_incoming().
* <interrupt> - indicates the function to call periodically.
* <interrupt_context> - indicates the data to pass to the <interrupt>
* function.
*/
void
uislib_enable_channel_interrupts(u32 bus_no, u32 dev_no,
int (*interrupt)(void *),
void *interrupt_context)
{
struct device_info *dev;
dev = find_dev(bus_no, dev_no);
if (!dev)
return;
down(&poll_dev_lock);
initialize_incoming_thread();
dev->interrupt = interrupt;
dev->interrupt_context = interrupt_context;
dev->polling = TRUE;
list_add_tail(&dev->list_polling_device_channels,
&poll_dev_chan);
up(&poll_dev_lock);
}
EXPORT_SYMBOL_GPL(uislib_enable_channel_interrupts);
/* Remove a device/channel from the list being processed by
* process_incoming().
*/
void
uislib_disable_channel_interrupts(u32 bus_no, u32 dev_no)
{
struct device_info *dev;
dev = find_dev(bus_no, dev_no);
if (!dev)
return;
down(&poll_dev_lock);
list_del(&dev->list_polling_device_channels);
dev->polling = FALSE;
dev->interrupt = NULL;
up(&poll_dev_lock);
}
EXPORT_SYMBOL_GPL(uislib_disable_channel_interrupts);
static void
do_wakeup_polling_device_channels(struct work_struct *dummy)
{
if (!poll_dev_start) {
poll_dev_start = 1;
wake_up(&poll_dev_wake_q);
}
}
static DECLARE_WORK(work_wakeup_polling_device_channels,
do_wakeup_polling_device_channels);
/* Call this function when you want to send a hint to process_incoming() that
* your device might have more requests.
*/
void
uislib_force_channel_interrupt(u32 bus_no, u32 dev_no)
{
if (en_smart_wakeup == 0)
return;
if (poll_dev_start)
return;
/* The point of using schedule_work() instead of just doing
* the work inline is to force a slight delay before waking up
* the process_incoming() thread.
*/
tot_wakeup_cnt++;
schedule_work(&work_wakeup_polling_device_channels);
}
EXPORT_SYMBOL_GPL(uislib_force_channel_interrupt);
/*****************************************************/
/* Module Init & Exit functions */
/*****************************************************/
static int __init
uislib_mod_init(void)
{
if (!unisys_spar_platform)
return -ENODEV;
/* initialize global pointers to NULL */
bus_list = NULL;
bus_list_count = 0;
max_bus_count = 0;
rwlock_init(&bus_list_lock);
virt_control_chan_func = NULL;
/* Issue VMCALL_GET_CONTROLVM_ADDR to get CtrlChanPhysAddr and
* then map this physical address to a virtual address. */
POSTCODE_LINUX_2(DRIVER_ENTRY_PC, POSTCODE_SEVERITY_INFO);
dir_debugfs = debugfs_create_dir(DIR_DEBUGFS_ENTRY, NULL);
if (dir_debugfs) {
info_debugfs_entry = debugfs_create_file(
INFO_DEBUGFS_ENTRY_FN, 0444, dir_debugfs, NULL,
&debugfs_info_fops);
platformnumber_debugfs_read = debugfs_create_u32(
PLATFORMNUMBER_DEBUGFS_ENTRY_FN, 0444, dir_debugfs,
&platform_no);
cycles_before_wait_debugfs_read = debugfs_create_u64(
CYCLES_BEFORE_WAIT_DEBUGFS_ENTRY_FN, 0666, dir_debugfs,
&cycles_before_wait);
smart_wakeup_debugfs_entry = debugfs_create_bool(
SMART_WAKEUP_DEBUGFS_ENTRY_FN, 0666, dir_debugfs,
&en_smart_wakeup);
}
POSTCODE_LINUX_3(DRIVER_EXIT_PC, 0, POSTCODE_SEVERITY_INFO);
return 0;
}
static void __exit
uislib_mod_exit(void)
{
if (debug_buf) {
vfree(debug_buf);
debug_buf = NULL;
}
debugfs_remove(info_debugfs_entry);
debugfs_remove(smart_wakeup_debugfs_entry);
debugfs_remove(cycles_before_wait_debugfs_read);
debugfs_remove(platformnumber_debugfs_read);
debugfs_remove(dir_debugfs);
}
module_init(uislib_mod_init);
module_exit(uislib_mod_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Usha Srinivasan");
MODULE_ALIAS("uislib");
/* this is extracted during depmod and kept in modules.dep */