On Sul, 2004-09-12 at 23:42, Dave Airlie wrote:
> The worst things that will happen for all concerened is this:
> Jon does all this work on a merged solution outside the kernel, and it
> works well, and the X team decide to do a decent X on mesa-solo on Jons
> super-DRM, now the super-DRM gets pushed via the X tree and distributions
> start relasing kernels with it merged into it 

Unlikely. Its rapidly unmaintainable because the core kernel changes
will obsolete it (see for example KGI).

> I think yourself and Linus's ideas for a locking scheme look good, I also
> know they won't please Jon too much as he can see where the potential
> ineffecienes with saving/restore card state on driver swap are, especailly
> on running fbcon and X on a dual-head card with different users.

Well this is what I came up with so far. It creates a vga class so you
can bind the drivers to functions of the card (and we can add/remove
functions later as appropriate), tells functions about each other and
now implements Linux lock proposal as I understood it.

Alan

/*
 * drivers/video/vga-driver.c
 *
 */

#include <linux/pci.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <asm/semaphore.h>
#include "vga_class.h"

/*
 *  Registration of video drivers and handling of hot-pluggable devices.
 */

static LIST_HEAD(vga_devices);	/* No I don't know why its not DECLARE_LIST_HEAD either */

/**
 * vga_match_one_device - Tell if a PCI device structure has a matching
 *                        PCI device id structure
 * @id: single PCI device id structure to match
 * @dev: the PCI device structure to match against
 * 
 * Returns the matching vga_device_id structure or %NULL if there is no match.
 */

static inline const struct vga_device_id *
vga_match_one_device(const struct vga_device_id *id, const struct pci_dev *dev)
{
	if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&
	    (id->device == PCI_ANY_ID || id->device == dev->device) &&
	    (id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&
	    (id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&
	    !((id->class ^ dev->class) & id->class_mask))
		return id;
	return NULL;
}


/**
 * vga_match_device - Tell if a VGA device structure has a matching
 *                    PCI device id structure
 * @ids: array of PCI device id structures to search in
 * @dev: the PCI device structure to match against
 * 
 * Used by a driver to check whether a PCI device present in the
 * system is in its list of supported devices.Returns the matching
 * vga_device_id structure or %NULL if there is no match.
 */
static const struct vga_device_id *
vga_match_device(const struct vga_device_id *ids, const struct vga_dev *vdev)
{
	struct pci_dev *pdev = vdev->pci_dev;
	while (ids->vendor || ids->subvendor || ids->class_mask) {
		if (vga_match_one_device(ids, pdev) && ids->unit == vdev->unit)
			return ids;
		ids++;
	}
	return NULL;
}


static void vga_device_notify_install(struct vga_dev *vdev, int type)
{
	int i;
	for(i = TYPE_MEM; i < TYPE_LAST; i++) {
		struct vga_dev *v = vdev->shared->device[i];
		if(v != NULL && v->driver && i != type)
			v->driver->notify_attach(v, type);
	}
}

static void vga_device_notify_remove(struct vga_dev *vdev, int type)
{
	int i;
	for(i = TYPE_MEM; i < TYPE_LAST; i++) {
		struct vga_dev *v = vdev->shared->device[i];
		if(v != NULL && v->driver && i != type)
			v->driver->notify_detach(v, type);
	}
}

/**
 * vga_device_probe_static()
 * 
 * returns 0 and sets vdev->driver when drv claims vdev, else error.
 */
static int
vga_device_probe_static(struct vga_driver *vdrv, struct vga_dev *vdev)
{		   
	int error = -ENODEV;
	const struct vga_device_id *id;

	if (!vdrv->id_table)
		return error;
	id = vga_match_device(vdrv->id_table, vdev);
	if (id)
		error = vdrv->probe(vdev, id);
	if (error >= 0) {
		vdev->driver = vdrv;
		down(&vdev->shared->shared_sem);
		vdev->shared->users++;
		vga_device_notify_install(vdev, vdev->unit);
		up(&vdev->shared->shared_sem);
		error = 0;
	}
	return error;
}

/**
 * __vga_device_probe()
 * 
 * returns 0  on success, else error.
 * side-effect: vdev->driver is set to vdrv when drv claims vdev.
 */
static int
__vga_device_probe(struct vga_driver *vdrv, struct vga_dev *vdev)
{		   
	int error = 0;

	if (!vdev->driver && vdrv->probe) {
		error = vga_device_probe_static(vdrv, vdev);
	}
	return error;
}

static int vga_device_probe(struct device *dev)
{
	int error = 0;
	struct vga_driver *drv;
	struct vga_dev *vdev;

	drv = to_vga_driver(dev->driver);
	vdev = to_vga_dev(dev);
	vga_dev_get(vdev);
	error = __vga_device_probe(drv, vdev);
	if (error)
		vga_dev_put(vdev);

	return error;
}

static int vga_device_remove(struct device *dev)
{
	struct vga_dev *vdev = to_vga_dev(dev);
	struct vga_driver *vdrv = vdev->driver;

	down(&vdev->shared->shared_sem);	
	vdev->shared->users--;
	if (vdrv) {
		vga_device_notify_remove(vdev, vdev->unit);
		if (vdrv->remove)
			vdrv->remove(vdev, vdev->shared->count);
		vdev->driver = NULL;
	}
	vdev->shared->device[vdev->unit] = NULL;
	up(&vdev->shared->shared_sem);	
	
	vga_dev_put(vdev);
	return 0;
}

static int vga_device_suspend(struct device *dev, u32 state)
{
	return 0;
}


/* 
 * Default resume method for devices that have no driver provided resume,
 * or not even a driver at all.
 */

static int vga_device_resume(struct device *dev)
{
	return 0;
}

static struct kobj_type vga_driver_kobj_type = {
};

/**
 * vga_register_driver - register a new pci driver
 * @drv: the driver structure to register
 * 
 * Adds the driver structure to the list of registered drivers
 * Returns the number of vga devices which were claimed by the driver
 * during registration.  The driver remains registered even if the
 * return value is zero.
 */

int vga_register_driver(struct vga_driver *drv)
{
	/* initialize common driver fields */
	drv->driver.name = drv->name;
	drv->driver.bus = &vga_bus_type;
	drv->driver.probe = vga_device_probe;
	drv->driver.remove = vga_device_remove;
	drv->driver.kobj.ktype = &vga_driver_kobj_type;

	/* register with core */
	return driver_register(&drv->driver);
}

/**
 * vga_unregister_driver - unregister a pci driver
 * @drv: the driver structure to unregister
 * 
 * Deletes the driver structure from the list of registered VGA drivers,
 * gives it a chance to clean up by calling its remove() function for
 * each device it was responsible for, and marks those devices as
 * driverless.
 */

void vga_unregister_driver(struct vga_driver *vdrv)
{
	driver_unregister(&vdrv->driver);
}

/**
 * vga_dev_driver - get the vga_driver of a device
 * @dev: the device to query
 * @type: type of device
 *
 * Returns the appropriate vga_driver structure or %NULL if there is no 
 * registered driver for the device. The shared block is used so that
 * you can pass your own vdev to receive driver information about
 * other attached drivers to the same PCI device. Caller must be careful
 * about locking and use of shared_sem.
 */

struct vga_driver *vga_dev_driver(const struct vga_dev *vdev, int type)
{
	struct vga_dev *v = vdev->shared->device[type];
	if(v == NULL)
		return NULL;
	return v->driver;
}

/**
 * vga_bus_match - Tell if a VGA device structure has a matching PCI device id structure
 * @ids: array of PCI device id structures to search in
 * @dev: the VGA device structure to match against
 * 
 * Used by a driver to check whether a VGA device present in the
 * system is in its list of supported devices.Returns the matching
 * vga_device_id structure or %NULL if there is no match.
 */

static int vga_bus_match(struct device *dev, struct device_driver * drv) 
{
	const struct vga_dev *vdev = to_vga_dev(dev);
	struct vga_driver *vdrv = to_vga_driver(drv);
	const struct vga_device_id * ids = vdrv->id_table;
	if (!ids)
		return 0;
	return vga_match_device(ids, vdev) ? 1 : 0;
}

/**
 * vga_dev_get - increments the reference count of the pci device structure
 * @dev: the device being referenced
 *
 * Each live reference to a device should be refcounted.
 *
 * Drivers for VGA devices should normally record such references in
 * their probe() methods, when they bind to a device, and release
 * them by calling vga_dev_put(), in their disconnect() methods.
 *
 * A pointer to the device with the incremented reference counter is returned.
 */
struct vga_dev *vga_dev_get(struct vga_dev *vdev)
{
	struct device *tmp;

	if (!vdev)
		return NULL;

	tmp = get_device(&vdev->dev);
	if (tmp)        
		return to_vga_dev(tmp);
	else
		return NULL;
}

/**
 * vga_dev_put - release a use of the vga_dev structure
 * @dev: device that's been disconnected
 *
 * Must be called when a user of a device is finished with it.  When the last
 * user of the device calls this function, the memory of the device is freed.
 */
void vga_dev_put(struct vga_dev *dev)
{
	if (dev)
		put_device(&dev->dev);
}

/* For now */
int vga_hotplug (struct device *dev, char **envp, int num_envp,
		 char *buffer, int buffer_size)
{
	return -ENODEV;
}

struct bus_type vga_bus_type = {
	.name		= "vga",
	.match		= vga_bus_match,
	.hotplug	= vga_hotplug,
	.suspend	= vga_device_suspend,
	.resume		= vga_device_resume,
};



/*
 *	Helper for VESAfb and friends.
 */

static int mmio_resource_overlap(struct pci_dev *dev, int i, unsigned long mmio)
{
	unsigned long st = pci_resource_start(dev, i);
	unsigned long size = pci_resource_len(dev, i);
	unsigned long flags = pci_resource_flags(dev, i);
	
	if(st == 0 || size == 0)
	   	return 0;
	if(st + size < mmio)
		return 0;
	if(st > mmio)
		return 0;
	if(flags & IORESOURCE_IO)
		return 0;
	return 1;
}

static struct pci_dev *vga_find_by_mmio(unsigned long mmio)
{
	struct list_head *l;
	list_for_each(l, &vga_devices) {
		struct vga_dev *vdev = list_entry(l, struct vga_dev, next);
		int i;
		for (i = 0; i < 6; i++) {
			if (mmio_resource_overlap(vdev->pci_dev, i, mmio))
				return vdev->pci_dev;
		}
	}
	/* Check ISA window routing ? */
	return NULL;
}


/*
 *	Big locking as suggested by Linus. Drivers can of course be
 *	more friendly and work together on some things.
 */
 
void vga_take_lock(struct vga_dev *vdev, struct vga_driver *vdrv, void *context)
{
	struct vga_shared *v = vdev->shared;
	
	down(&v->shared_sem);
	down(&v->fb_sem);
	if(v->lock_owner != vdrv || v->lock_context != context)
	{
		if(v->lock_release)
			v->lock_release(vdev, v->lock_context);
		v->lock_release = NULL;
	}
	v->lock_owner = vdrv;
	v->lock_context = context;
}

EXPORT_SYMBOL_GPL(vga_take_lock);

/*
 *	Drop the big locking
 */
 
void vga_drop_lock(struct vga_dev *vdev, void (*lock_release)(struct vga_dev *, void *))
{
	struct vga_shared *v = vdev->shared;
	v->lock_release = lock_release;
	up(&v->fb_sem);
	up(&v->shared_sem);
}

EXPORT_SYMBOL_GPL(vga_drop_lock);

/*
 *	VGA device discovery from the PCI side
 */
 
 
/**
 * vga_release_dev - free a pci device structure when all users of it are finished.
 * @dev: device that's been disconnected
 *
 * Will be called only by the device core when all users of this vga device are
 * done.
 */

static void vga_release_dev(struct device *dev)
{
	struct vga_dev *vdev = to_vga_dev(dev);
	/* We want this very late because the remove methods might want to
	   use the locks */
	vga_take_lock(vdev, NULL, NULL);
	vga_drop_lock(vdev, NULL);
	down(&vdev->shared->shared_sem);
	vdev->shared->device[vdev->unit] = NULL;
	vdev->shared->count --;
	up(&vdev->shared->shared_sem);
	if(vdev->shared->count == 0)
		kfree(vdev->shared);
	kfree(vdev);
}

/**
 *	vga_remove_one		-	Remove vga adapter
 *	@pdev: PCI device
 *
 *	A VGA adapter has been removed. We must propogate this into the
 *	VGA bus world
 */
 
static void vga_remove_one(struct pci_dev *pdev)
{
	struct vga_dev *vdev = (struct vga_dev *)pdev->dev.driver_data;
	device_unregister(&vdev->dev);
	/* Remove from lists etc here */
	vga_dev_put(vdev);
}

/**
 *	vga_found_one		-	Add vga adapter
 *	@pdev: PCI device
 *	@ent: matching PCI entity
 *
 *	Allocate and install a new VGA class entity set after the PCI layer
 *	discovers it.  We create device objects for the frame buffer, 
 *	memory manager and dri objects at the moment. Additional frame
 *	buffer objects (multihead) can be allocated by the callers.
 */
 
static int vga_found_one(struct pci_dev *pdev, 
					const struct pci_device_id *ent)
{
	struct vga_shared *vshar = kmalloc(sizeof(*vshar), GFP_KERNEL);
	int i;
	if(vshar == NULL)
		return -ENOMEM;
	
	memset(vshar, 0, sizeof(*vshar));
	init_MUTEX(&vshar->shared_sem);
	init_MUTEX(&vshar->fb_sem);
	vshar->lock_owner = NULL;
	
	for(i = TYPE_MEM; i <= TYPE_FB0; i++)
	{
		struct vga_dev *vdev = kmalloc(sizeof(*vdev), GFP_KERNEL);
		if(vdev == NULL)
			return -ENOMEM;
		memset(vdev, 0, sizeof(*vdev));
	
		vdev->pci_dev = pdev;
		vdev->shared = vshar;
		vdev->unit = i;
		
		vshar->device[i] = vdev;
		vshar->count ++;
	
		vdev->dev.bus = &vga_bus_type;
		vdev->dev.parent = NULL;	/* ? */
		vdev->dev.driver_data = pdev;
		device_initialize(&vdev->dev);
		vdev->dev.release = vga_release_dev;
		vdev->dev.dma_mask = pdev->dev.dma_mask;
		vdev->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask;
		vga_dev_get(vdev);

		INIT_LIST_HEAD(&vdev->next);
		list_add_tail(&vdev->next, &vga_devices);
	}
	return 1;
}

/*
 *	Match all video VGA class objects
 */
 
static struct pci_device_id vga_id_table[] = {
	{ PCI_DEVICE_CLASS(((PCI_CLASS_DISPLAY_VGA << 8) | 0x00), ~0), },
	{ 0, }
};

MODULE_DEVICE_TABLE(pci, vga_id_table);

static struct pci_driver vga_driver = {
	.name		= "vga",
	.probe		= vga_found_one,
	.remove		= vga_remove_one,
	.id_table	= vga_id_table,
	/* Could use suspend/resume hooks ? */
};
 
EXPORT_SYMBOL(vga_register_driver);
EXPORT_SYMBOL(vga_unregister_driver);
EXPORT_SYMBOL(vga_dev_driver);
EXPORT_SYMBOL(vga_bus_type);
EXPORT_SYMBOL(vga_dev_get);
EXPORT_SYMBOL(vga_dev_put);

int __init vga_driver_init(void)
{
	if( bus_register(&vga_bus_type) < 0)
		printk(KERN_ERR "Unable to register VGA bus type.\n");
	return pci_module_init(&vga_driver);
}


postcore_initcall(vga_driver_init);
#define TYPE_MEM		0	/* Memory manager */
#define TYPE_DRI		1	/* Direct render agent */
#define TYPE_FB0		2	/* Frame buffer head 0 */
#define TYPE_FB1		3	/* Frame buffer head 1 */
#define TYPE_FB2		4	/* Frame buffer head 2 */
#define TYPE_FB3		5	/* Frame buffer head 3 */
#define TYPE_LAST		5
#define NUM_TYPES		6

struct vga_shared {
	struct vga_dev *device[NUM_TYPES];
	struct semaphore shared_sem;
	int count;			/* Devices */
	int users;			/* Active users */
	struct semaphore fb_sem;
	void (*lock_release)(struct vga_dev *, void *);
	void *lock_context;
	struct vga_driver *lock_owner;
};

struct vga_dev {
	struct list_head next;		/* All VGA devices */
	struct list_head router_list;	/* By VGA router (not yet done) */
	struct vga_driver *driver;
	struct pci_dev *pci_dev;
	struct device dev;
	int unit;
	struct vga_shared *shared;
};

struct vga_device_id {
	__u32 vendor, device;		/* Vendor and device ID or PCI_ANY_ID*/
	__u32 subvendor, subdevice;	/* Subsystem ID's or PCI_ANY_ID */
	__u32 class, class_mask;	/* (class,subclass,prog-if) triplet */
	__u32 unit;			/* Logical unit for attach */
	kernel_ulong_t driver_data;	/* Data private to the driver */
};


#define	to_vga_dev(n) container_of(n, struct vga_dev, dev)

struct vga_driver {
	struct list_head node;
	int type;
	char *name;
	int (*probe) (struct vga_dev *vdev, const struct vga_device_id *id);
	void (*remove) (struct vga_dev *dev, int users);	/* Device removed (NULL if not a hot-plug capable driver) */
	int  (*suspend) (struct vga_dev *vdev, u32 state);	/* Device suspended */	int  (*resume) (struct vga_dev *vdev);	                /* Device woken up */
	void (*notify_attach) (struct vga_dev *, int);
	void (*notify_detach) (struct vga_dev *, int);
	struct device_driver driver;
	struct vga_device_id *id_table;
};

#define to_vga_driver(drv) container_of(drv, struct vga_driver, driver)

extern struct bus_type vga_bus_type;

extern int vga_register_driver(struct vga_driver *drv);
extern void vg_unregister_driver(struct vga_driver *drv);
extern struct vga_driver *vga_dev_driver(const struct vga_dev *, int);
extern struct bus_type vga_bus_type;
extern struct vga_dev *vga_dev_get(struct vga_dev *);
extern void vga_dev_put(struct vga_dev *);

Reply via email to