summaryrefslogblamecommitdiffstats
path: root/drivers/staging/unisys/visorbus/visorbus_main.c
blob: 813d29ea0c4ef0961f75e30655557b9e4c43caa2 (plain) (tree)
1
2
3
4
5
6
7
8

                  
                                               

                       


                                                                          









                                                                      
                     
                             
                    


                            
                            
 

                            
                       


                                 
                             

                                                  



                                                    

                                                                             



                                                                             






















                                                            
                                                              



                            


























                                                                               
                                                              



                            


                                                     
                                 


                                  
                                          
                                          











                                                                          

                                                              

                                                               



                                                                  






















                                                                              
                                                         






                                                                 






                                                                 
















































                                                                                
                                                         
 
                   




















                                                                    


                                                                      

                                                                               
 


                                                         

                                                  
                                                                       

 

                                                                             
 


                                                         

                                                  
                                                                     

 





                                                                             

                                                  
                                                                              

 

                                                                               
 
                                                         

                   
                                

                                               
                                                                

 

                                                                               
 
                                                         

                   
                                

                                               
                                                                    

 

                                                                               
 
                                                         
                  

                                                 

                                        
                                                  
                         
                                   





                                                                                





                                       







                                               
                    

  


                                                  

  
                                                                  

                                  

  
                                                       
 










                                                                               


                                                                   

                                                                          
 
                                                           

 


                                                                 
                                                         
 
                                                                           

 


                                                                 
                                                         
 
                                                            

 


                                                               

                                                                 
 
                                                         

 


                                                                

                                                                 
 
                                                           

 


                                                             
                                                         

                    

                                                         





                                  


                                                                  


                                                          






                                              


                                                    
                                       
                                                                                                 
                                                                   

                            
                                              









                                                                          
                                              












                                                                            

                                                          













                                                                                

















                                                
 


                                              
 



                                                          


















                                                              


                                                                
                                                                    







                                                             


                             
                                        



































                                                                            
                




                                            



                               
                                              
                                   





                                                                        
         
 
                                            
                   








                                                                           





                                              
                                  










                                                                             


                                 
                 















































                                                                             


                                                                       





















                                                                          

                                                






































                                                                             































                                                                             
                                             
 
               

                                                 


                                                                                
 

                                                                      
                                                     



                                                      







                                                                          
                             






                                                                     
                                                  
























                                                                          

                                                                  

     
                                 






                                             



                                        
          


                                                           
                                                                        

                               

                                                                            
                            

                                                                         

                               

                                                   
                               
         
                 


                                             

                                       








                                                                            
                          

                                                                   
                          



                                             

                                       








                                                                            
                          

                                                                   
                          















                                                                         
                          

                                                                   
                          











                                                                    
                                  




                                              
                                              

                                     
                       
 
                                                                          

                       
 


                                                                        
 
                                                            






                                                                      


                                                                           







                                                                         
                                                                             



                                                                      


                                                                               



                                                      

                                             
 
                                     
                                              

                                                                      

                                                          

                               
 



                                                         
 
                                                

                                                              

                                
         
 





                                                                     
                
                                
         
                    
                                                               
                                           
                 




                                                      
                                             

                                                               
                                                 




                                                                         
                    


                                                        
         


                                        







                                                        

                                                 






                                                                               

















                                                                            
           
                                            
 

                                         

                                                                              
                                      
                                                                             

                   

                                                               



                                                                 
                                          
                                                           


           
                                             
 
                                 
                                           
                                                          


           
                                                    
 
               

                                              



                                                                
                                           



                                                               

                                                                          


                                                                          


           
                                                     
 
                                      

                                              
                                                                   






                                                                    
                                                                 

                          
                       
 
                             
                                                                          
                       

                                                                      


                                                                    
                                                         






                                                                    
                                                                  

                           
                       
 
                              
                                                                           
                       


                                                                      

                         
                                                          






                                                                      
                                                                             
 
               
                                        
                                                                           





                                                               
                       
 
                                                  



                                             
 



                                            








                                                                      



                                                     
 
                                    
                                                                  




                                                                       

                                                          
                                       



                                                     
 
                                     
                                                                    
         
                     
                             
                                             
                    
                                              
                                             



           
                                                   
 
                                                             


           
                                                    
 
                                                              







                               
   





                                                                      
                                                     

                                            















                                                                               


                                                          




               

                                                             


                  
    



                                              
                                                       













                                                                              
                                                                

                                                                          
                                         





                                                        



                                                                  



                                                                      
 

                                                                     
/* visorbus_main.c
 *
 * Copyright � 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.
 */

#include <linux/uuid.h>

#include "visorbus.h"
#include "visorbus_private.h"
#include "version.h"
#include "periodic_work.h"
#include "vbuschannel.h"
#include "guestlinuxdebug.h"
#include "vmcallinterface.h"

#define MYDRVNAME "visorbus"

/* module parameters */
static int visorbus_debug;
static int visorbus_forcematch;
static int visorbus_forcenomatch;
static int visorbus_debugref;
#define SERIALLOOPBACKCHANADDR (100 * 1024 * 1024)

#define CURRENT_FILE_PC VISOR_BUS_PC_visorbus_main_c
#define POLLJIFFIES_TESTWORK         100
#define POLLJIFFIES_NORMALCHANNEL     10

static int busreg_rc = -ENODEV; /* stores the result from bus registration */

static int visorbus_uevent(struct device *xdev, struct kobj_uevent_env *env);
static int visorbus_match(struct device *xdev, struct device_driver *xdrv);
static void fix_vbus_dev_info(struct visor_device *visordev);

/*  BUS type attributes
 *
 *  define & implement display of bus attributes under
 *  /sys/bus/visorbus.
 *
 */

static ssize_t version_show(struct bus_type *bus, char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%s\n", VERSION);
}

static BUS_ATTR_RO(version);

static struct attribute *visorbus_bus_attrs[] = {
	&bus_attr_version.attr,
	NULL,
};

static const struct attribute_group visorbus_bus_group = {
	.attrs = visorbus_bus_attrs,
};

static const struct attribute_group *visorbus_bus_groups[] = {
	&visorbus_bus_group,
	NULL,
};

/*
 * DEVICE type attributes
 *
 * The modalias file will contain the guid of the device.
 */
static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
			     char *buf)
{
	struct visor_device *vdev;
	uuid_le guid;

	vdev = to_visor_device(dev);
	guid = visorchannel_get_uuid(vdev->visorchannel);
	return snprintf(buf, PAGE_SIZE, "visorbus:%pUl\n", &guid);
}
static DEVICE_ATTR_RO(modalias);

static struct attribute *visorbus_dev_attrs[] = {
	&dev_attr_modalias.attr,
	NULL,
};

/* sysfs example for bridge-only sysfs files using device_type's */
static const struct attribute_group visorbus_dev_group = {
	.attrs = visorbus_dev_attrs,
};

static const struct attribute_group *visorbus_dev_groups[] = {
	&visorbus_dev_group,
	NULL,
};

/** This describes the TYPE of bus.
 *  (Don't confuse this with an INSTANCE of the bus.)
 */
struct bus_type visorbus_type = {
	.name = "visorbus",
	.match = visorbus_match,
	.uevent = visorbus_uevent,
	.dev_groups = visorbus_dev_groups,
	.bus_groups = visorbus_bus_groups,
};

static struct delayed_work periodic_work;

/* YES, we need 2 workqueues.
 * The reason is, workitems on the test queue may need to cancel
 * workitems on the other queue.  You will be in for trouble if you try to
 * do this with workitems queued on the same workqueue.
 */
static struct workqueue_struct *periodic_test_workqueue;
static struct workqueue_struct *periodic_dev_workqueue;
static long long bus_count;	/** number of bus instances */
					/** ever-increasing */

static void chipset_bus_create(struct visor_device *bus_info);
static void chipset_bus_destroy(struct visor_device *bus_info);
static void chipset_device_create(struct visor_device *dev_info);
static void chipset_device_destroy(struct visor_device *dev_info);
static void chipset_device_pause(struct visor_device *dev_info);
static void chipset_device_resume(struct visor_device *dev_info);

/** These functions are implemented herein, and are called by the chipset
 *  driver to notify us about specific events.
 */
static struct visorchipset_busdev_notifiers chipset_notifiers = {
	.bus_create = chipset_bus_create,
	.bus_destroy = chipset_bus_destroy,
	.device_create = chipset_device_create,
	.device_destroy = chipset_device_destroy,
	.device_pause = chipset_device_pause,
	.device_resume = chipset_device_resume,
};

/** These functions are implemented in the chipset driver, and we call them
 *  herein when we want to acknowledge a specific event.
 */
static struct visorchipset_busdev_responders chipset_responders;

/* filled in with info about parent chipset driver when we register with it */
static struct ultra_vbus_deviceinfo chipset_driverinfo;
/* filled in with info about this driver, wrt it servicing client busses */
static struct ultra_vbus_deviceinfo clientbus_driverinfo;

/** list of visor_device structs, linked via .list_all */
static LIST_HEAD(list_all_bus_instances);
/** list of visor_device structs, linked via .list_all */
static LIST_HEAD(list_all_device_instances);

static int
visorbus_uevent(struct device *xdev, struct kobj_uevent_env *env)
{
	struct visor_device *dev;
	uuid_le guid;

	dev = to_visor_device(xdev);
	guid = visorchannel_get_uuid(dev->visorchannel);

	if (add_uevent_var(env, "MODALIAS=visorbus:%pUl", &guid))
		return -ENOMEM;
	return 0;
}

/* This is called automatically upon adding a visor_device (device_add), or
 * adding a visor_driver (visorbus_register_visor_driver), and returns 1 iff the
 * provided driver can control the specified device.
 */
static int
visorbus_match(struct device *xdev, struct device_driver *xdrv)
{
	uuid_le channel_type;
	int rc = 0;
	int i;
	struct visor_device *dev;
	struct visor_driver *drv;

	dev = to_visor_device(xdev);
	drv = to_visor_driver(xdrv);
	channel_type = visorchannel_get_uuid(dev->visorchannel);
	if (visorbus_forcematch) {
		rc = 1;
		goto away;
	}
	if (visorbus_forcenomatch)
		goto away;

	if (!drv->channel_types)
		goto away;
	for (i = 0;
	     (uuid_le_cmp(drv->channel_types[i].guid, NULL_UUID_LE) != 0) ||
	     (drv->channel_types[i].name);
	     i++)
		if (uuid_le_cmp(drv->channel_types[i].guid,
				channel_type) == 0) {
			rc = i + 1;
			goto away;
		}
away:
	return rc;
}

/** This is called when device_unregister() is called for the bus device
 *  instance, after all other tasks involved with destroying the device
 *  are complete.
 */
static void
visorbus_release_busdevice(struct device *xdev)
{
	struct visor_device *dev = dev_get_drvdata(xdev);

	kfree(dev);
}

/** This is called when device_unregister() is called for each child
 *  device instance.
 */
static void
visorbus_release_device(struct device *xdev)
{
	struct visor_device *dev = to_visor_device(xdev);

	if (dev->periodic_work) {
		visor_periodic_work_destroy(dev->periodic_work);
		dev->periodic_work = NULL;
	}
	if (dev->visorchannel) {
		visorchannel_destroy(dev->visorchannel);
		dev->visorchannel = NULL;
	}
	kfree(dev);
}

/* begin implementation of specific channel attributes to appear under
* /sys/bus/visorbus<x>/dev<y>/channel
*/
static ssize_t physaddr_show(struct device *dev, struct device_attribute *attr,
			     char *buf)
{
	struct visor_device *vdev = to_visor_device(dev);

	if (!vdev->visorchannel)
		return 0;
	return snprintf(buf, PAGE_SIZE, "0x%Lx\n",
			visorchannel_get_physaddr(vdev->visorchannel));
}

static ssize_t nbytes_show(struct device *dev, struct device_attribute *attr,
			   char *buf)
{
	struct visor_device *vdev = to_visor_device(dev);

	if (!vdev->visorchannel)
		return 0;
	return snprintf(buf, PAGE_SIZE, "0x%lx\n",
			visorchannel_get_nbytes(vdev->visorchannel));
}

static ssize_t clientpartition_show(struct device *dev,
				    struct device_attribute *attr, char *buf)
{
	struct visor_device *vdev = to_visor_device(dev);

	if (!vdev->visorchannel)
		return 0;
	return snprintf(buf, PAGE_SIZE, "0x%Lx\n",
			visorchannel_get_clientpartition(vdev->visorchannel));
}

static ssize_t typeguid_show(struct device *dev, struct device_attribute *attr,
			     char *buf)
{
	struct visor_device *vdev = to_visor_device(dev);
	char s[99];

	if (!vdev->visorchannel)
		return 0;
	return snprintf(buf, PAGE_SIZE, "%s\n",
			visorchannel_id(vdev->visorchannel, s));
}

static ssize_t zoneguid_show(struct device *dev, struct device_attribute *attr,
			     char *buf)
{
	struct visor_device *vdev = to_visor_device(dev);
	char s[99];

	if (!vdev->visorchannel)
		return 0;
	return snprintf(buf, PAGE_SIZE, "%s\n",
			visorchannel_zoneid(vdev->visorchannel, s));
}

static ssize_t typename_show(struct device *dev, struct device_attribute *attr,
			     char *buf)
{
	struct visor_device *vdev = to_visor_device(dev);
	int i = 0;
	struct bus_type *xbus = dev->bus;
	struct device_driver *xdrv = dev->driver;
	struct visor_driver *drv = NULL;

	if (!vdev->visorchannel || !xbus || !xdrv)
		return 0;
	i = xbus->match(dev, xdrv);
	if (!i)
		return 0;
	drv = to_visor_driver(xdrv);
	return snprintf(buf, PAGE_SIZE, "%s\n", drv->channel_types[i - 1].name);
}

static DEVICE_ATTR_RO(physaddr);
static DEVICE_ATTR_RO(nbytes);
static DEVICE_ATTR_RO(clientpartition);
static DEVICE_ATTR_RO(typeguid);
static DEVICE_ATTR_RO(zoneguid);
static DEVICE_ATTR_RO(typename);

static struct attribute *channel_attrs[] = {
		&dev_attr_physaddr.attr,
		&dev_attr_nbytes.attr,
		&dev_attr_clientpartition.attr,
		&dev_attr_typeguid.attr,
		&dev_attr_zoneguid.attr,
		&dev_attr_typename.attr,
		NULL
};

static struct attribute_group channel_attr_grp = {
		.name = "channel",
		.attrs = channel_attrs,
};

static const struct attribute_group *visorbus_channel_groups[] = {
		&channel_attr_grp,
		NULL
};

/* end implementation of specific channel attributes */

/*  BUS instance attributes
 *
 *  define & implement display of bus attributes under
 *  /sys/bus/visorbus/busses/visorbus<n>.
 *
 *  This is a bit hoaky because the kernel does not yet have the infrastructure
 *  to separate bus INSTANCE attributes from bus TYPE attributes...
 *  so we roll our own.  See businst.c / businst.h.
 *
 */

static ssize_t partition_handle_show(struct device *dev,
				     struct device_attribute *attr,
				     char *buf) {
	struct visor_device *vdev = to_visor_device(dev);
	u64 handle = visorchannel_get_clientpartition(vdev->visorchannel);

	return snprintf(buf, PAGE_SIZE, "0x%Lx\n", handle);
}

static ssize_t partition_guid_show(struct device *dev,
				   struct device_attribute *attr,
				   char *buf) {
	struct visor_device *vdev = to_visor_device(dev);

	return snprintf(buf, PAGE_SIZE, "{%pUb}\n", &vdev->partition_uuid);
}

static ssize_t partition_name_show(struct device *dev,
				   struct device_attribute *attr,
				   char *buf) {
	struct visor_device *vdev = to_visor_device(dev);

	return snprintf(buf, PAGE_SIZE, "%s\n", vdev->name);
}

static ssize_t channel_addr_show(struct device *dev,
				 struct device_attribute *attr,
				 char *buf) {
	struct visor_device *vdev = to_visor_device(dev);
	u64 addr = visorchannel_get_physaddr(vdev->visorchannel);

	return snprintf(buf, PAGE_SIZE, "0x%Lx\n", addr);
}

static ssize_t channel_bytes_show(struct device *dev,
				  struct device_attribute *attr,
				  char *buf) {
	struct visor_device *vdev = to_visor_device(dev);
	u64 nbytes = visorchannel_get_nbytes(vdev->visorchannel);

	return snprintf(buf, PAGE_SIZE, "0x%Lx\n", nbytes);
}

static ssize_t channel_id_show(struct device *dev,
			       struct device_attribute *attr,
			       char *buf) {
	struct visor_device *vdev = to_visor_device(dev);
	int len = 0;

	if (vdev->visorchannel) {
		visorchannel_id(vdev->visorchannel, buf);
		len = strlen(buf);
		buf[len++] = '\n';
	}
	return len;
}

static ssize_t client_bus_info_show(struct device *dev,
				    struct device_attribute *attr,
				    char *buf) {
	struct visor_device *vdev = to_visor_device(dev);
	struct visorchannel *channel = vdev->visorchannel;

	int i, x, remain = PAGE_SIZE;
	unsigned long off;
	char *p = buf;
	u8 *partition_name;
	struct ultra_vbus_deviceinfo dev_info;

	partition_name = "";
	if (channel) {
		if (vdev->name)
			partition_name = vdev->name;
		x = snprintf(p, remain,
			     "Client device / client driver info for %s partition (vbus #%d):\n",
			     partition_name, vdev->chipset_dev_no);
		p += x;
		remain -= x;
		x = visorchannel_read(channel,
				      offsetof(struct
					       spar_vbus_channel_protocol,
					       chp_info),
				      &dev_info, sizeof(dev_info));
		if (x >= 0) {
			x = vbuschannel_devinfo_to_string(&dev_info, p,
							  remain, -1);
			p += x;
			remain -= x;
		}
		x = visorchannel_read(channel,
				      offsetof(struct
					       spar_vbus_channel_protocol,
					       bus_info),
				      &dev_info, sizeof(dev_info));
		if (x >= 0) {
			x = vbuschannel_devinfo_to_string(&dev_info, p,
							  remain, -1);
			p += x;
			remain -= x;
		}
		off = offsetof(struct spar_vbus_channel_protocol, dev_info);
		i = 0;
		while (off + sizeof(dev_info) <=
		       visorchannel_get_nbytes(channel)) {
			x = visorchannel_read(channel,
					      off, &dev_info, sizeof(dev_info));
			if (x >= 0) {
				x = vbuschannel_devinfo_to_string
				    (&dev_info, p, remain, i);
				p += x;
				remain -= x;
			}
			off += sizeof(dev_info);
			i++;
		}
	}
	return PAGE_SIZE - remain;
}

static DEVICE_ATTR_RO(partition_handle);
static DEVICE_ATTR_RO(partition_guid);
static DEVICE_ATTR_RO(partition_name);
static DEVICE_ATTR_RO(channel_addr);
static DEVICE_ATTR_RO(channel_bytes);
static DEVICE_ATTR_RO(channel_id);
static DEVICE_ATTR_RO(client_bus_info);

static struct attribute *dev_attrs[] = {
		&dev_attr_partition_handle.attr,
		&dev_attr_partition_guid.attr,
		&dev_attr_partition_name.attr,
		&dev_attr_channel_addr.attr,
		&dev_attr_channel_bytes.attr,
		&dev_attr_channel_id.attr,
		&dev_attr_client_bus_info.attr,
		NULL
};

static struct attribute_group dev_attr_grp = {
		.attrs = dev_attrs,
};

static const struct attribute_group *visorbus_groups[] = {
		&dev_attr_grp,
		NULL
};

/*  DRIVER attributes
 *
 *  define & implement display of driver attributes under
 *  /sys/bus/visorbus/drivers/<drivername>.
 *
 */

static ssize_t
DRIVER_ATTR_version(struct device_driver *xdrv, char *buf)
{
	struct visor_driver *drv = to_visor_driver(xdrv);

	return snprintf(buf, PAGE_SIZE, "%s\n", drv->version);
}

static int
register_driver_attributes(struct visor_driver *drv)
{
	struct driver_attribute version =
	    __ATTR(version, S_IRUGO, DRIVER_ATTR_version, NULL);
	drv->version_attr = version;
	return driver_create_file(&drv->driver, &drv->version_attr);
}

static void
unregister_driver_attributes(struct visor_driver *drv)
{
	driver_remove_file(&drv->driver, &drv->version_attr);
}

static void
dev_periodic_work(void *xdev)
{
	struct visor_device *dev = xdev;
	struct visor_driver *drv = to_visor_driver(dev->device.driver);

	down(&dev->visordriver_callback_lock);
	if (drv->channel_interrupt)
		drv->channel_interrupt(dev);
	up(&dev->visordriver_callback_lock);
	if (!visor_periodic_work_nextperiod(dev->periodic_work))
		put_device(&dev->device);
}

static void
dev_start_periodic_work(struct visor_device *dev)
{
	if (dev->being_removed)
		return;
	/* now up by at least 2 */
	get_device(&dev->device);
	if (!visor_periodic_work_start(dev->periodic_work))
		put_device(&dev->device);
}

static void
dev_stop_periodic_work(struct visor_device *dev)
{
	if (visor_periodic_work_stop(dev->periodic_work))
		put_device(&dev->device);
}

/** This is called automatically upon adding a visor_device (device_add), or
 *  adding a visor_driver (visorbus_register_visor_driver), but only after
 *  visorbus_match has returned 1 to indicate a successful match between
 *  driver and device.
 */
static int
visordriver_probe_device(struct device *xdev)
{
	int res;
	struct visor_driver *drv;
	struct visor_device *dev;

	drv = to_visor_driver(xdev->driver);
	dev = to_visor_device(xdev);

	if (!drv->probe)
		return -ENODEV;

	down(&dev->visordriver_callback_lock);
	dev->being_removed = false;

	res = drv->probe(dev);
	if (res >= 0) {
		/* success: reference kept via unmatched get_device() */
		get_device(&dev->device);
		fix_vbus_dev_info(dev);
	}

	up(&dev->visordriver_callback_lock);
	return res;
}

/** This is called when device_unregister() is called for each child device
 *  instance, to notify the appropriate visorbus_driver that the device is
 *  going away, and to decrease the reference count of the device.
 */
static int
visordriver_remove_device(struct device *xdev)
{
	struct visor_device *dev;
	struct visor_driver *drv;

	dev = to_visor_device(xdev);
	drv = to_visor_driver(xdev->driver);
	down(&dev->visordriver_callback_lock);
	dev->being_removed = true;
	/*
	 * ensure that the dev->being_removed flag is set before we start the
	 * actual removal
	 */
	wmb();
	if (drv) {
		if (drv->remove)
			drv->remove(dev);
	}
	up(&dev->visordriver_callback_lock);
	dev_stop_periodic_work(dev);

	put_device(&dev->device);

	return 0;
}

/** A particular type of visor driver calls this function to register
 *  the driver.  The caller MUST fill in the following fields within the
 *  #drv structure:
 *      name, version, owner, channel_types, probe, remove
 *
 *  Here's how the whole Linux bus / driver / device model works.
 *
 *  At system start-up, the visorbus kernel module is loaded, which registers
 *  visorbus_type as a bus type, using bus_register().
 *
 *  All kernel modules that support particular device types on a
 *  visorbus bus are loaded.  Each of these kernel modules calls
 *  visorbus_register_visor_driver() in their init functions, passing a
 *  visor_driver struct.  visorbus_register_visor_driver() in turn calls
 *  register_driver(&visor_driver.driver).  This .driver member is
 *  initialized with generic methods (like probe), whose sole responsibility
 *  is to act as a broker for the real methods, which are within the
 *  visor_driver struct.  (This is the way the subclass behavior is
 *  implemented, since visor_driver is essentially a subclass of the
 *  generic driver.)  Whenever a driver_register() happens, core bus code in
 *  the kernel does (see device_attach() in drivers/base/dd.c):
 *
 *      for each dev associated with the bus (the bus that driver is on) that
 *      does not yet have a driver
 *          if bus.match(dev,newdriver) == yes_matched  ** .match specified
 *                                                 ** during bus_register().
 *              newdriver.probe(dev)  ** for visor drivers, this will call
 *                    ** the generic driver.probe implemented in visorbus.c,
 *                    ** which in turn calls the probe specified within the
 *                    ** struct visor_driver (which was specified by the
 *                    ** actual device driver as part of
 *                    ** visorbus_register_visor_driver()).
 *
 *  The above dance also happens when a new device appears.
 *  So the question is, how are devices created within the system?
 *  Basically, just call device_add(dev).  See pci_bus_add_devices().
 *  pci_scan_device() shows an example of how to build a device struct.  It
 *  returns the newly-created struct to pci_scan_single_device(), who adds it
 *  to the list of devices at PCIBUS.devices.  That list of devices is what
 *  is traversed by pci_bus_add_devices().
 *
 */
int visorbus_register_visor_driver(struct visor_driver *drv)
{
	int rc = 0;

	if (busreg_rc < 0)
		return -ENODEV; /*can't register on a nonexistent bus*/

	drv->driver.name = drv->name;
	drv->driver.bus = &visorbus_type;
	drv->driver.probe = visordriver_probe_device;
	drv->driver.remove = visordriver_remove_device;
	drv->driver.owner = drv->owner;

	/* driver_register does this:
	 *   bus_add_driver(drv)
	 *   ->if (drv.bus)  ** (bus_type) **
	 *       driver_attach(drv)
	 *         for each dev with bus type of drv.bus
	 *           if (!dev.drv)  ** no driver assigned yet **
	 *             if (bus.match(dev,drv))  [visorbus_match]
	 *               dev.drv = drv
	 *               if (!drv.probe(dev))   [visordriver_probe_device]
	 *                 dev.drv = NULL
	 */

	rc = driver_register(&drv->driver);
	if (rc < 0)
		return rc;
	rc = register_driver_attributes(drv);
	if (rc < 0)
		driver_unregister(&drv->driver);
	return rc;
}
EXPORT_SYMBOL_GPL(visorbus_register_visor_driver);

/** A particular type of visor driver calls this function to unregister
 *  the driver, i.e., within its module_exit function.
 */
void
visorbus_unregister_visor_driver(struct visor_driver *drv)
{
	unregister_driver_attributes(drv);
	driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(visorbus_unregister_visor_driver);

int
visorbus_read_channel(struct visor_device *dev, unsigned long offset,
		      void *dest, unsigned long nbytes)
{
	return visorchannel_read(dev->visorchannel, offset, dest, nbytes);
}
EXPORT_SYMBOL_GPL(visorbus_read_channel);

int
visorbus_write_channel(struct visor_device *dev, unsigned long offset,
		       void *src, unsigned long nbytes)
{
	return visorchannel_write(dev->visorchannel, offset, src, nbytes);
}
EXPORT_SYMBOL_GPL(visorbus_write_channel);

int
visorbus_clear_channel(struct visor_device *dev, unsigned long offset, u8 ch,
		       unsigned long nbytes)
{
	return visorchannel_clear(dev->visorchannel, offset, ch, nbytes);
}
EXPORT_SYMBOL_GPL(visorbus_clear_channel);

/** We don't really have a real interrupt, so for now we just call the
 *  interrupt function periodically...
 */
void
visorbus_enable_channel_interrupts(struct visor_device *dev)
{
	dev_start_periodic_work(dev);
}
EXPORT_SYMBOL_GPL(visorbus_enable_channel_interrupts);

void
visorbus_disable_channel_interrupts(struct visor_device *dev)
{
	dev_stop_periodic_work(dev);
}
EXPORT_SYMBOL_GPL(visorbus_disable_channel_interrupts);

/** This is how everything starts from the device end.
 *  This function is called when a channel first appears via a ControlVM
 *  message.  In response, this function allocates a visor_device to
 *  correspond to the new channel, and attempts to connect it the appropriate
 *  driver.  If the appropriate driver is found, the visor_driver.probe()
 *  function for that driver will be called, and will be passed the new
 *  visor_device that we just created.
 *
 *  It's ok if the appropriate driver is not yet loaded, because in that case
 *  the new device struct will just stick around in the bus' list of devices.
 *  When the appropriate driver calls visorbus_register_visor_driver(), the
 *  visor_driver.probe() for the new driver will be called with the new
 *  device.
 */
static int
create_visor_device(struct visor_device *dev)
{
	int rc;
	u32 chipset_bus_no = dev->chipset_bus_no;
	u32 chipset_dev_no = dev->chipset_dev_no;

	POSTCODE_LINUX_4(DEVICE_CREATE_ENTRY_PC, chipset_dev_no, chipset_bus_no,
			 POSTCODE_SEVERITY_INFO);

	sema_init(&dev->visordriver_callback_lock, 1);	/* unlocked */
	dev->device.bus = &visorbus_type;
	dev->device.groups = visorbus_channel_groups;
	device_initialize(&dev->device);
	dev->device.release = visorbus_release_device;
	/* keep a reference just for us (now 2) */
	get_device(&dev->device);
	dev->periodic_work =
		visor_periodic_work_create(POLLJIFFIES_NORMALCHANNEL,
					   periodic_dev_workqueue,
					   dev_periodic_work,
					   dev, dev_name(&dev->device));
	if (!dev->periodic_work) {
		POSTCODE_LINUX_3(DEVICE_CREATE_FAILURE_PC, chipset_dev_no,
				 DIAG_SEVERITY_ERR);
		rc = -EINVAL;
		goto away;
	}

	/* bus_id must be a unique name with respect to this bus TYPE
	 * (NOT bus instance).  That's why we need to include the bus
	 * number within the name.
	 */
	dev_set_name(&dev->device, "vbus%u:dev%u",
		     chipset_bus_no, chipset_dev_no);

	/*  device_add does this:
	 *    bus_add_device(dev)
	 *    ->device_attach(dev)
	 *      ->for each driver drv registered on the bus that dev is on
	 *          if (dev.drv)  **  device already has a driver **
	 *            ** not sure we could ever get here... **
	 *          else
	 *            if (bus.match(dev,drv)) [visorbus_match]
	 *              dev.drv = drv
	 *              if (!drv.probe(dev))  [visordriver_probe_device]
	 *                dev.drv = NULL
	 *
	 *  Note that device_add does NOT fail if no driver failed to
	 *  claim the device.  The device will be linked onto
	 *  bus_type.klist_devices regardless (use bus_for_each_dev).
	 */
	rc = device_add(&dev->device);
	if (rc < 0) {
		POSTCODE_LINUX_3(DEVICE_ADD_PC, chipset_bus_no,
				 DIAG_SEVERITY_ERR);
		goto away;
	}

	list_add_tail(&dev->list_all, &list_all_device_instances);
	return 0;

away:
	put_device(&dev->device);
	return rc;
}

static void
remove_visor_device(struct visor_device *dev)
{
	list_del(&dev->list_all);
	put_device(&dev->device);
	device_unregister(&dev->device);
}

static int
get_vbus_header_info(struct visorchannel *chan,
		     struct spar_vbus_headerinfo *hdr_info)
{
	if (!SPAR_VBUS_CHANNEL_OK_CLIENT(visorchannel_get_header(chan)))
		return -EINVAL;

	if (visorchannel_read(chan, sizeof(struct channel_header), hdr_info,
			      sizeof(*hdr_info)) < 0) {
		return -EIO;
	}
	if (hdr_info->struct_bytes < sizeof(struct spar_vbus_headerinfo))
		return -EINVAL;

	if (hdr_info->device_info_struct_bytes <
	    sizeof(struct ultra_vbus_deviceinfo)) {
		return -EINVAL;
	}
	return 0;
}

/* Write the contents of <info> to the struct
 * spar_vbus_channel_protocol.chp_info.
 */

static int
write_vbus_chp_info(struct visorchannel *chan,
		    struct spar_vbus_headerinfo *hdr_info,
		    struct ultra_vbus_deviceinfo *info)
{
	int off = sizeof(struct channel_header) + hdr_info->chp_info_offset;

	if (hdr_info->chp_info_offset == 0)
		return -1;

	if (visorchannel_write(chan, off, info, sizeof(*info)) < 0)
		return -1;
	return 0;
}

/* Write the contents of <info> to the struct
 * spar_vbus_channel_protocol.bus_info.
 */

static int
write_vbus_bus_info(struct visorchannel *chan,
		    struct spar_vbus_headerinfo *hdr_info,
		    struct ultra_vbus_deviceinfo *info)
{
	int off = sizeof(struct channel_header) + hdr_info->bus_info_offset;

	if (hdr_info->bus_info_offset == 0)
		return -1;

	if (visorchannel_write(chan, off, info, sizeof(*info)) < 0)
		return -1;
	return 0;
}

/* Write the contents of <info> to the
 * struct spar_vbus_channel_protocol.dev_info[<devix>].
 */
static int
write_vbus_dev_info(struct visorchannel *chan,
		    struct spar_vbus_headerinfo *hdr_info,
		    struct ultra_vbus_deviceinfo *info, int devix)
{
	int off =
	    (sizeof(struct channel_header) + hdr_info->dev_info_offset) +
	    (hdr_info->device_info_struct_bytes * devix);

	if (hdr_info->dev_info_offset == 0)
		return -1;

	if (visorchannel_write(chan, off, info, sizeof(*info)) < 0)
		return -1;
	return 0;
}

/* For a child device just created on a client bus, fill in
 * information about the driver that is controlling this device into
 * the the appropriate slot within the vbus channel of the bus
 * instance.
 */
static void
fix_vbus_dev_info(struct visor_device *visordev)
{
	int i;
	struct visor_device *bdev;
	struct visor_driver *visordrv;
	int bus_no = visordev->chipset_bus_no;
	int dev_no = visordev->chipset_dev_no;
	struct ultra_vbus_deviceinfo dev_info;
	const char *chan_type_name = NULL;
	struct spar_vbus_headerinfo *hdr_info;

	if (!visordev->device.driver)
		return;

	hdr_info = (struct spar_vbus_headerinfo *)visordev->vbus_hdr_info;
	if (!hdr_info)
		return;

	bdev = visorbus_get_device_by_id(bus_no, BUS_ROOT_DEVICE, NULL);
	if (!bdev)
		return;

	visordrv = to_visor_driver(visordev->device.driver);

	/* Within the list of device types (by GUID) that the driver
	 * says it supports, find out which one of those types matches
	 * the type of this device, so that we can include the device
	 * type name
	 */
	for (i = 0; visordrv->channel_types[i].name; i++) {
		if (memcmp(&visordrv->channel_types[i].guid,
			   &visordev->channel_type_guid,
			   sizeof(visordrv->channel_types[i].guid)) == 0) {
			chan_type_name = visordrv->channel_types[i].name;
			break;
		}
	}

	bus_device_info_init(&dev_info, chan_type_name,
			     visordrv->name, visordrv->version,
			     visordrv->vertag);
	write_vbus_dev_info(bdev->visorchannel, hdr_info, &dev_info, dev_no);

	/* Re-write bus+chipset info, because it is possible that this
	* was previously written by our evil counterpart, virtpci.
	*/
	write_vbus_chp_info(bdev->visorchannel, hdr_info, &chipset_driverinfo);
	write_vbus_bus_info(bdev->visorchannel, hdr_info,
			    &clientbus_driverinfo);
}

/** Create a device instance for the visor bus itself.
 */
static int
create_bus_instance(struct visor_device *dev)
{
	int id = dev->chipset_bus_no;
	struct spar_vbus_headerinfo *hdr_info;

	POSTCODE_LINUX_2(BUS_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);

	hdr_info = kzalloc(sizeof(*hdr_info), GFP_KERNEL);
	if (!hdr_info)
		return -ENOMEM;

	dev_set_name(&dev->device, "visorbus%d", id);
	dev->device.bus = &visorbus_type;
	dev->device.groups = visorbus_groups;
	dev->device.release = visorbus_release_busdevice;

	if (device_register(&dev->device) < 0) {
		POSTCODE_LINUX_3(DEVICE_CREATE_FAILURE_PC, id,
				 POSTCODE_SEVERITY_ERR);
		kfree(hdr_info);
		return -ENODEV;
	}

	if (get_vbus_header_info(dev->visorchannel, hdr_info) >= 0) {
		dev->vbus_hdr_info = (void *)hdr_info;
		write_vbus_chp_info(dev->visorchannel, hdr_info,
				    &chipset_driverinfo);
		write_vbus_bus_info(dev->visorchannel, hdr_info,
				    &clientbus_driverinfo);
	} else {
		kfree(hdr_info);
	}
	bus_count++;
	list_add_tail(&dev->list_all, &list_all_bus_instances);
	dev_set_drvdata(&dev->device, dev);
	return 0;
}

/** Remove a device instance for the visor bus itself.
 */
static void
remove_bus_instance(struct visor_device *dev)
{
	/* Note that this will result in the release method for
	 * dev->dev being called, which will call
	 * visorbus_release_busdevice().  This has something to do with
	 * the put_device() done in device_unregister(), but I have never
	 * successfully been able to trace thru the code to see where/how
	 * release() gets called.  But I know it does.
	 */
	bus_count--;
	if (dev->visorchannel) {
		visorchannel_destroy(dev->visorchannel);
		dev->visorchannel = NULL;
	}
	kfree(dev->vbus_hdr_info);
	list_del(&dev->list_all);
	device_unregister(&dev->device);
}

/** Create and register the one-and-only one instance of
 *  the visor bus type (visorbus_type).
 */
static int
create_bus_type(void)
{
	busreg_rc = bus_register(&visorbus_type);
	return busreg_rc;
}

/** Remove the one-and-only one instance of the visor bus type (visorbus_type).
 */
static void
remove_bus_type(void)
{
	bus_unregister(&visorbus_type);
}

/** Remove all child visor bus device instances.
 */
static void
remove_all_visor_devices(void)
{
	struct list_head *listentry, *listtmp;

	list_for_each_safe(listentry, listtmp, &list_all_device_instances) {
		struct visor_device *dev = list_entry(listentry,
						      struct visor_device,
						      list_all);
		remove_visor_device(dev);
	}
}

static void
chipset_bus_create(struct visor_device *dev)
{
	int rc;
	u32 bus_no = dev->chipset_bus_no;

	POSTCODE_LINUX_3(BUS_CREATE_ENTRY_PC, bus_no, POSTCODE_SEVERITY_INFO);
	rc = create_bus_instance(dev);
	POSTCODE_LINUX_3(BUS_CREATE_EXIT_PC, bus_no, POSTCODE_SEVERITY_INFO);

	if (rc < 0)
		POSTCODE_LINUX_3(BUS_CREATE_FAILURE_PC, bus_no,
				 POSTCODE_SEVERITY_ERR);
	else
		POSTCODE_LINUX_3(CHIPSET_INIT_SUCCESS_PC, bus_no,
				 POSTCODE_SEVERITY_INFO);

	if (chipset_responders.bus_create)
		(*chipset_responders.bus_create) (dev, rc);
}

static void
chipset_bus_destroy(struct visor_device *dev)
{
	remove_bus_instance(dev);
	if (chipset_responders.bus_destroy)
		(*chipset_responders.bus_destroy)(dev, 0);
}

static void
chipset_device_create(struct visor_device *dev_info)
{
	int rc;
	u32 bus_no = dev_info->chipset_bus_no;
	u32 dev_no = dev_info->chipset_dev_no;

	POSTCODE_LINUX_4(DEVICE_CREATE_ENTRY_PC, dev_no, bus_no,
			 POSTCODE_SEVERITY_INFO);

	rc = create_visor_device(dev_info);
	if (chipset_responders.device_create)
		chipset_responders.device_create(dev_info, rc);

	if (rc < 0)
		POSTCODE_LINUX_4(DEVICE_CREATE_FAILURE_PC, dev_no, bus_no,
				 POSTCODE_SEVERITY_ERR);
	else
		POSTCODE_LINUX_4(DEVICE_CREATE_SUCCESS_PC, dev_no, bus_no,
				 POSTCODE_SEVERITY_INFO);
}

static void
chipset_device_destroy(struct visor_device *dev_info)
{
	remove_visor_device(dev_info);

	if (chipset_responders.device_destroy)
		(*chipset_responders.device_destroy) (dev_info, 0);
}

/* This is the callback function specified for a function driver, to
 * be called when a pending "pause device" operation has been
 * completed.
 */
static void
pause_state_change_complete(struct visor_device *dev, int status)
{
	if (!dev->pausing)
		return;

	dev->pausing = false;
	if (!chipset_responders.device_pause) /* this can never happen! */
		return;

	/* Notify the chipset driver that the pause is complete, which
	 * will presumably want to send some sort of response to the
	 * initiator.
	 */
	(*chipset_responders.device_pause) (dev, status);
}

/* This is the callback function specified for a function driver, to
 * be called when a pending "resume device" operation has been
 * completed.
 */
static void
resume_state_change_complete(struct visor_device *dev, int status)
{
	if (!dev->resuming)
		return;

	dev->resuming = false;
	if (!chipset_responders.device_resume) /* this can never happen! */
		return;

	/* Notify the chipset driver that the resume is complete,
	 * which will presumably want to send some sort of response to
	 * the initiator.
	 */
	(*chipset_responders.device_resume) (dev, status);
}

/* Tell the subordinate function driver for a specific device to pause
 * or resume that device.  Result is returned asynchronously via a
 * callback function.
 */
static void
initiate_chipset_device_pause_resume(struct visor_device *dev, bool is_pause)
{
	int rc;
	struct visor_driver *drv = NULL;
	void (*notify_func)(struct visor_device *dev, int response) = NULL;

	if (is_pause)
		notify_func = chipset_responders.device_pause;
	else
		notify_func = chipset_responders.device_resume;
	if (!notify_func)
		return;

	drv = to_visor_driver(dev->device.driver);
	if (!drv) {
		(*notify_func)(dev, -ENODEV);
		return;
	}

	if (dev->pausing || dev->resuming) {
		(*notify_func)(dev, -EBUSY);
		return;
	}

	/* Note that even though both drv->pause() and drv->resume
	 * specify a callback function, it is NOT necessary for us to
	 * increment our local module usage count.  Reason is, there
	 * is already a linkage dependency between child function
	 * drivers and visorbus, so it is already IMPOSSIBLE to unload
	 * visorbus while child function drivers are still running.
	 */
	if (is_pause) {
		if (!drv->pause) {
			(*notify_func)(dev, -EINVAL);
			return;
		}

		dev->pausing = true;
		rc = drv->pause(dev, pause_state_change_complete);
	} else {
		/* This should be done at BUS resume time, but an
		 * existing problem prevents us from ever getting a bus
		 * resume...  This hack would fail to work should we
		 * ever have a bus that contains NO devices, since we
		 * would never even get here in that case.
		 */
		fix_vbus_dev_info(dev);
		if (!drv->resume) {
			(*notify_func)(dev, -EINVAL);
			return;
		}

		dev->resuming = true;
		rc = drv->resume(dev, resume_state_change_complete);
	}
	if (rc < 0) {
		if (is_pause)
			dev->pausing = false;
		else
			dev->resuming = false;
		(*notify_func)(dev, -EINVAL);
	}
}

static void
chipset_device_pause(struct visor_device *dev_info)
{
	initiate_chipset_device_pause_resume(dev_info, true);
}

static void
chipset_device_resume(struct visor_device *dev_info)
{
	initiate_chipset_device_pause_resume(dev_info, false);
}

struct channel_size_info {
	uuid_le guid;
	unsigned long min_size;
	unsigned long max_size;
};

int
visorbus_init(void)
{
	int rc = 0;

	POSTCODE_LINUX_3(DRIVER_ENTRY_PC, rc, POSTCODE_SEVERITY_INFO);
	bus_device_info_init(&clientbus_driverinfo,
			     "clientbus", "visorbus",
			     VERSION, NULL);

	rc = create_bus_type();
	if (rc < 0) {
		POSTCODE_LINUX_2(BUS_CREATE_ENTRY_PC, DIAG_SEVERITY_ERR);
		goto away;
	}

	periodic_dev_workqueue = create_singlethread_workqueue("visorbus_dev");
	if (!periodic_dev_workqueue) {
		POSTCODE_LINUX_2(CREATE_WORKQUEUE_PC, DIAG_SEVERITY_ERR);
		rc = -ENOMEM;
		goto away;
	}

	/* This enables us to receive notifications when devices appear for
	 * which this service partition is to be a server for.
	 */
	visorchipset_register_busdev(&chipset_notifiers,
				     &chipset_responders,
				     &chipset_driverinfo);

	rc = 0;

away:
	if (rc)
		POSTCODE_LINUX_3(CHIPSET_INIT_FAILURE_PC, rc,
				 POSTCODE_SEVERITY_ERR);
	return rc;
}

void
visorbus_exit(void)
{
	struct list_head *listentry, *listtmp;

	visorchipset_register_busdev(NULL, NULL, NULL);
	remove_all_visor_devices();

	flush_workqueue(periodic_dev_workqueue); /* better not be any work! */
	destroy_workqueue(periodic_dev_workqueue);
	periodic_dev_workqueue = NULL;

	if (periodic_test_workqueue) {
		cancel_delayed_work(&periodic_work);
		flush_workqueue(periodic_test_workqueue);
		destroy_workqueue(periodic_test_workqueue);
		periodic_test_workqueue = NULL;
	}

	list_for_each_safe(listentry, listtmp, &list_all_bus_instances) {
		struct visor_device *dev = list_entry(listentry,
						      struct visor_device,
						      list_all);
		remove_bus_instance(dev);
	}
	remove_bus_type();
}

module_param_named(debug, visorbus_debug, int, S_IRUGO);
MODULE_PARM_DESC(visorbus_debug, "1 to debug");

module_param_named(forcematch, visorbus_forcematch, int, S_IRUGO);
MODULE_PARM_DESC(visorbus_forcematch,
		 "1 to force a successful dev <--> drv match");

module_param_named(forcenomatch, visorbus_forcenomatch, int, S_IRUGO);
MODULE_PARM_DESC(visorbus_forcenomatch,
		 "1 to force an UNsuccessful dev <--> drv match");

module_param_named(debugref, visorbus_debugref, int, S_IRUGO);
MODULE_PARM_DESC(visorbus_debugref, "1 to debug reference counting");