Re: radeon-pre-2

From: Alan Cox
Date: Mon Sep 13 2004 - 07:32:09 EST


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 *);