Re: radeon-pre-2

Linus Torvalds
2004-09-11T17:43:12Z

On Sat, 11 Sep 2004, Jon Smirl wrote:
>
> Coprocessor 3D mode is deeply pipelined
> 2D mode is immediate

Now it is _you_ who confuse "3D mode" and "2D mode".

THERE IS NO SUCH THING.

There is only hardware.

> How can you build a system that process swaps between these two modes?

You don't switch between the two non-existent modes. You serialize on
hardware accesses.

If the 2D graphics driver uses the accelerator, it takes the accelerator
lock.

If the 2D graphics just uses the framebufferm it takes the framebuffer
lock.

If there are hardware dependencies on the accelerator and the framebuffer,
then the two lock pointers point to the same lock.

The locks themselves need to have a release mechanism, of course, the same
way we already have "stateful" locks for X interactions where something
needs to be done when a lock is given to another person. So the locks
can't just be regular spinlocks or semaphores, they need to be slightly
more complicated things where each user has an "ID" cookie.

This way:
- the hardware-independent part really _is_ hardware-independent. It
literally hass _no_ clue what the different users do.
- the hardware-independent part makes no policy at all. It just has a set
of lock pointers, and a generic locking mechanism which basically looks
something like

/*
* Generic "release lock"
*/
typedef struct release_lock_struct {
struct semaphore sem;
void *cookie;
void (*releasefn)(release_lock_t *, void *);
} release_lock_t;

/*
* get a lock. Return 1 if the resource had been owned by
* somebody else in the meantime. We may need to re-initialize
* hw state if so..
*
* Otherwise, just return 0, to let the caller know that it
* didn't have anybody else screw with its state in between.
*
* (The caller could just keep this information by tracking
* it's "releasefn()" calls, of course, since if another
* locker came in, we would have asked it to release the
* info. The return value is just a convenient interface).
*/
int get_lock(release_lock_t *lock, void *cookie,
void (*releasefn)(release_lock_t *, void *))
{
down(lock->sem);

/* Same old owner? Coolio.. */
if (lock->cookie == cookie)
return 0;

/* Tell the previous lock owner to clean up */
lock->releasefn(lock, lock->cookie);

/* We now have a new owner */
lock->releasefn = releasefn;
lock->cookie = cookie;
return 1;
}

/*
* Release the lock
*/
void put_lock(release_lock_t *lock, void *cookie)
{
/*
* People screw up all the time. Only let the owner
* release the lock. Complain loudly when somebody gets
* mixed up.
*/
BUG_ON(lock->cookie != cookie);
up(lock->sem);
}

Notice? There's _zero_ hardware knowledge here. This can work for
video-cards, but it can work for anything else too.

And yes, maybe you need more complicated locks than the above, but hey,
they probably don't need to be _much_ more complicated.

So how would you use the above? Just combine it with the "graphics
subsystem" specific list of locks, which you attach to each device some
way (so that each driver that _shares_ the hardware device can find it):

struct gfx_lock_ptr {
release_lock_t *accelerator;
release_lock_t *framebuffer;
release_lock_t *memorymanager;
..
};

struct gfx_data {
struct gfx_lock_ptr ptr;
release_lock_t locks[MAXLOCKS];
};

and then the code just does

- hw-independent gfx code initializes the locks to defaults:

gfx_data->ptr.accelerator = gfx_data->locks + 0;
gfx_data->ptr.framebuffer = gfx_data->locks + 1;
gfx_data->ptr.memorymanager = gfx_data->locks + 2;
...
dev->gfx_data = gfx_data;

- the _low_level_ drivers at their initialization will know what hardware
structures are shared, so they do (when _they_ init, and notice how
this doesn't actually matter in which order they loaded - they can both
do this thing without knowing about each other):

/* This chip can't access the frame buffer when the accelerator is busy */
gfx_data->ptr.accelerator = gfx_data->ptr.framebuffer;

- then the low-level drivers just do

/* execute command */
if (get_lock(gfx_data->ptr.accelerator, mydev, myrelease)) {
/*
* Somebody else used the accelerator earlier, need to
* re-load my cached pointers:
*/
mydev->offset = readl(command_buffer + CMD_OFFSET);
}
WRITE_RING(mydev, cmd);
WRITE_RING(mydev, arg);
put_lock(gfx_data->ptr.accelerator, mydev);

- and then the "myrelease" function just does whatever writer-side
synchronization needed (wait for commands to flush, for example).

Do I know what the hell I'm talking abut? No. You should see the above as
a very quick "this _kind_ of approach should work", and work out the
details. But I really think something like the above would be quite
doable. No?

And yes, it requires changes to _both_ DRM and to fbcon, but the changes
are hopefully fairly independent, and it doesn't really require that they
share any fundamental code - it only requires a fbcon driver and a DRM
driver for the same hardware to agree on an use some _very_ simple
serialization.

Linus